aw-spec-cli 0.1.20 → 0.1.22

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 (62) hide show
  1. package/assets/.appweaver/ORCH-DIALECT.md +8 -3
  2. package/assets/.appweaver/STACK-PROFILES.md +87 -8
  3. package/assets/.appweaver/orchestrator/modules/delta-plan.orch.yaml +30 -0
  4. package/assets/.appweaver/orchestrator/modules/delta-spec.orch.yaml +22 -1
  5. package/assets/.appweaver/orchestrator/orchestrator.py +65 -3
  6. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan-delta.orch.yaml +6 -6
  7. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +144 -52
  8. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +78 -10
  9. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +283 -25
  10. package/assets/.appweaver/orchestrator/workflows/aw-spec-constitution.orch.yaml +4 -4
  11. package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +202 -40
  12. package/assets/.appweaver/orchestrator/workflows/aw-spec-foundation.orch.yaml +140 -36
  13. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +557 -57
  14. package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +3 -3
  15. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +39 -0
  16. package/assets/.appweaver/orchestrator/workflows/aw-spec-recall-delta.orch.yaml +28 -7
  17. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-stack.orch.yaml +47 -23
  18. package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +101 -1
  19. package/assets/.appweaver/orchestrator/workflows/aw-spec-ui-model.orch.yaml +141 -0
  20. package/assets/.appweaver/orchestrator/workflows/aw-spec-ui-prototype.orch.yaml +386 -0
  21. package/assets/.appweaver/orchestrator/workflows/aw-spec-workflow-model.orch.yaml +135 -0
  22. package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +772 -216
  23. package/assets/.appweaver/scripts/bash/aw-xref.sh +4 -1
  24. package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +708 -209
  25. package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +4 -1
  26. package/assets/.appweaver/scripts/python/aw-crstate.py +319 -0
  27. package/assets/.appweaver/scripts/python/aw-foundation.py +151 -0
  28. package/assets/.appweaver/scripts/python/aw-gaps.py +119 -0
  29. package/assets/.appweaver/scripts/python/aw-payload-parity.py +19 -0
  30. package/assets/.appweaver/scripts/python/aw-plan-coverage.py +503 -0
  31. package/assets/.appweaver/scripts/python/aw-plangate.py +216 -0
  32. package/assets/.appweaver/scripts/python/aw-principles.py +108 -0
  33. package/assets/.appweaver/scripts/python/aw-stack.py +257 -28
  34. package/assets/.appweaver/scripts/python/aw-testlog.py +148 -0
  35. package/assets/.appweaver/scripts/python/aw-xref.py +141 -4
  36. package/assets/.appweaver/stack-profiles/angular.json +26 -0
  37. package/assets/.appweaver/stack-profiles/blazor-wasm.json +38 -0
  38. package/assets/.appweaver/stack-profiles/dotnet-webapi.json +39 -0
  39. package/assets/.appweaver/stack-profiles/java-spring.json +20 -0
  40. package/assets/.appweaver/stack-profiles/nextjs.json +26 -0
  41. package/assets/.appweaver/stack-profiles/node-express.json +27 -0
  42. package/assets/.appweaver/stack-profiles/python-fastapi.json +16 -0
  43. package/assets/.appweaver/stack-profiles/react-vite.json +26 -0
  44. package/assets/.appweaver/stack-profiles/sveltekit.json +26 -0
  45. package/assets/.appweaver/stack-profiles/vue-vite.json +26 -0
  46. package/assets/.appweaver/templates/constitution-domain-template.md +17 -2
  47. package/assets/.appweaver/templates/ui-model-template.md +67 -0
  48. package/assets/.appweaver/templates/workflow-model-template.md +62 -0
  49. package/assets/guardrails/projects/dotnet-default-project/domain-rule-bindings.md +10 -6
  50. package/assets/guardrails/projects/dotnet-default-project/tech-rule-bindings.md +4 -0
  51. package/assets/guardrails/technology/dotnet-webapi/auditing.md +3 -1
  52. package/assets/guardrails/technology/dotnet-webapi/coding-standards.md +5 -3
  53. package/assets/guardrails/technology/dotnet-webapi/cqrs.md +5 -1
  54. package/assets/guardrails/technology/dotnet-webapi/domain-driven-design.md +13 -5
  55. package/assets/guardrails/technology/dotnet-webapi/examples/cqrs.md +2 -0
  56. package/assets/guardrails/technology/dotnet-webapi/examples/domain-driven-design.md +6 -2
  57. package/assets/guardrails/technology/dotnet-webapi/framework-api-map.md +114 -0
  58. package/assets/guardrails/technology/dotnet-webapi/technology-constitution.md +3 -0
  59. package/assets/guardrails/technology/dotnet-webapi/testing.md +10 -0
  60. package/assets/guardrails/technology/nextjs/ui-constitution.md +8 -1
  61. package/package.json +1 -1
  62. package/assets/.appweaver/orchestrator/.state/smoke.state.json +0 -32
@@ -47,9 +47,10 @@
47
47
  # about the order the operator ran things in: the lane gates test that the plan FILE EXISTS
48
48
  # and nothing more, so `plan` → edit spec.md → `implement` generated at full length from a
49
49
  # plan nobody had held against the spec it now contradicts. analyze-plan leaves a receipt of
50
- # the bytes it proved and plan-gate-check reads it here, on the bootstrap path, before any
51
- # lane generates. A feature with no receipt has simply never been gated since the receipt
52
- # existed — reported, never refused. One that HAS a receipt and no longer matches it stops.
50
+ # the bytes it checked and its verdict, and plan-gate-check reads it here, on the bootstrap
51
+ # path, before any lane generates. A feature with no receipt has simply never been gated
52
+ # since the receipt existed — reported, never refused. One whose receipt no longer matches,
53
+ # or whose verdict left plan gaps, stops; one whose verdict left only spec/model gaps warns.
53
54
  # 3.2.0 — AND NOT delta-spec.md EITHER. The change path used to inline it "only so the
54
55
  # paired tests can cite traceability ids", which made the rule true on the bootstrap path
55
56
  # and false on the one where changes actually ship: a document in the payload is a document
@@ -86,11 +87,22 @@
86
87
  # • FRONTEND — `## /aw-spec-implement-ui`, both paths. There was never a delta heading
87
88
  # for this lane because the delta half of it was never built; it is built here.
88
89
  #
89
- # ONE SUBTREE PER LANE. Each lane owns the source path stacks.json declares for it and
90
- # writes nothing outside it. A file written elsewhere belongs to the other lane or to no
91
- # lane at all, and the project gate is deliberately stricter for the frontend: a manifest
92
- # found only elsewhere in the repository is the service lane's, and accepting it is how a
93
- # frontend that was never scaffolded is generated into anyway and reports green.
90
+ # TWO PATHS PER LANE. Each lane owns the source path stacks.json declares for it and the
91
+ # test path lane-verify resolves beside it — `tests/` next to a .NET `src/`, the source
92
+ # path itself for a stack that keeps its tests inside — and writes nothing outside them.
93
+ # The one exception is registering a test project the lane had to create, in whatever
94
+ # file the lane's test command discovers tests through (a .NET solution file). A file
95
+ # written elsewhere belongs to the other lane or to no lane at all, and the project gate
96
+ # is deliberately stricter for the frontend: a manifest found only elsewhere in the
97
+ # repository is the service lane's, and accepting it is how a frontend that was never
98
+ # scaffolded is generated into anyway and reports green.
99
+ #
100
+ # A FILE LIST IS NOT A FINISHED LANE. Each generating lane is handed its plan's element
101
+ # list and reports every element back as done or blocked; `plan-coverage` then checks the
102
+ # code under the lane's source path for every identifier the plan introduces. Whatever
103
+ # either check leaves open gets one completion pass, and anything still open after it
104
+ # stops the run once both lanes have generated — so a pass that stopped at five use-cases
105
+ # of seven is reported, not shipped.
94
106
  #
95
107
  # A LANE'S GAP DISABLES THAT LANE, NOT THE RUN. Three things a lane needs are checked per
96
108
  # lane and reported per lane: its source path is on disk, its plan is there, and a project
@@ -116,7 +128,7 @@
116
128
 
117
129
  name: aw-spec-implement
118
130
  description: "Generate the aggregate's source files and paired tests from its lane plans — the backend lane from backend-plan.md when a backend stack is declared, the frontend lane from frontend-plan.md when a frontend stack is, both when both are. One command covers backend-only, frontend-only and full-stack projects and absorbs the former implement-ui and implement-delta. Self-detecting off the changes on disk — with none open it generates from the master plans; with one or more open it ASKS which change and generates only what that change's delta plans mark ADDED or MODIFIED, leaving the masters untouched. Every lane is gated on its own source path, its own plan and its own scaffolded project, and a lane missing any of them is reported and skipped rather than failing the other lane; each runnable lane confirms before it writes. Model-driven, and it does not read the spec — analyze-plan already conformed the plans to it. The plan says WHAT to build; the bounded context's domain-model.md, in context alongside it, says what the domain IS — the entity names, the boundary and the text of every invariant the plan cites by id — joined on the delta path by that change's own model delta, since the master is not merged until finalize-delta. The stack binds here from the lane declaration and the constitution halves; the plans stay technology-agnostic."
119
- version: 3.4.0
131
+ version: 3.10.0
120
132
  agent_agnostic: true
121
133
  dialect: aw-spec.orchestrator/v1
122
134
 
@@ -162,19 +174,23 @@ vars:
162
174
  delta_frontend_plan: ""
163
175
  delta_frontend_plan_exists: "false"
164
176
  wave: ""
165
- # THE GATE RECEIPT — was this feature's plan pair ever PROVEN against this spec, and has
166
- # anything moved since. analyze-plan writes it on a green master run; this phase is the
167
- # reader. Bootstrap path only: a delta generates from delta plans, which analyze-plan-delta
168
- # gates, and the master receipt says nothing about those.
177
+ # THE GATE RECEIPT — was this feature's plan pair checked against this spec, with what
178
+ # verdict, and has anything moved since. analyze-plan writes it on every master run; this
179
+ # phase is the reader. Bootstrap path only: a delta generates from delta plans, which
180
+ # analyze-plan-delta gates, and the master receipt says nothing about those.
169
181
  gate_tracked: "false"
170
182
  gate_fresh: "false"
171
183
  gate_reason: ""
172
184
  gate_at: ""
185
+ gate_verdict: "" # conformant | routed-gaps | gaps — empty with no receipt
186
+ gate_routed_csv: "" # the gaps only /appweaver specify can close
187
+ gate_may_generate: "true"
188
+ gate_no_routed: "true" # lowered when the receipt's verdict is routed-gaps — a warning, not a stop
173
189
  # DEFAULTED PERMISSIVE, DELIBERATELY, and it is the one flag here that is. Every tree that
174
190
  # predates the receipt has none, and "never gated" is an unknown, not a stale plan —
175
191
  # refusing on a missing file would block every existing feature on its first run. The flag
176
- # is lowered only by a receipt that EXISTS and no longer matches: that tree has been through
177
- # the gate once, so the bytes moving since is a fact, not an absence of one.
192
+ # is lowered only by a receipt that EXISTS and refuses: the bytes moved since it was
193
+ # written, or its verdict left gaps a plan rewrite closes.
178
194
  may_generate: "true"
179
195
  # PER-LANE STATE. Each lane resolves its own plan, its own project and its own answer;
180
196
  # a lane that is not declared never resolves any of them and its steps never run.
@@ -189,6 +205,23 @@ vars:
189
205
  be_go: "false"
190
206
  be_mode: "skip" # skip | bootstrap | delta
191
207
  be_files: []
208
+ # COMPLETENESS, per lane — see step 8b and the checks after each lane generates.
209
+ be_element_names: ""
210
+ be_existing_text: " (not resolved)"
211
+ be_reserved: ""
212
+ be_collisions: ""
213
+ be_collisions_text: "none"
214
+ be_existing_count: "0"
215
+ be_elements: []
216
+ be_more_files: []
217
+ be_elements_more: []
218
+ be_complete: "true"
219
+ be_outcome: "" # complete | already-implemented | gaps | no-plan-elements
220
+ be_satisfied: "false" # the lane was already fully realised, so this run wrote nothing
221
+ be_coverage_note: ""
222
+ be_completion_ran: "false"
223
+ be_gap_names: ""
224
+ be_gaps_text: "none"
192
225
  fe_plan: ""
193
226
  fe_plan_ok: "false"
194
227
  fe_project_ok: "false"
@@ -198,6 +231,23 @@ vars:
198
231
  fe_go: "false"
199
232
  fe_mode: "skip" # skip | bootstrap | delta
200
233
  fe_files: []
234
+ fe_element_names: ""
235
+ fe_existing_text: " (not resolved)"
236
+ fe_reserved: ""
237
+ fe_collisions: ""
238
+ fe_collisions_text: "none"
239
+ fe_existing_count: "0"
240
+ fe_elements: []
241
+ fe_more_files: []
242
+ fe_elements_more: []
243
+ fe_complete: "true"
244
+ fe_outcome: ""
245
+ fe_satisfied: "false"
246
+ fe_coverage_note: ""
247
+ fe_completion_ran: "false"
248
+ fe_gap_names: ""
249
+ fe_gaps_text: "none"
250
+ lanes_complete: "true"
201
251
  generate_any: "false"
202
252
  record_edges: "false"
203
253
  overview_ok: "false"
@@ -245,6 +295,7 @@ workflow:
245
295
  - { name: be_declared, from: "$.backend_declared" }
246
296
  - { name: be_lane_id, from: "$.backend_lane_id" }
247
297
  - { name: be_source_path, from: "$.backend_source_path" }
298
+ - { name: be_test_path, from: "$.backend_test_path" }
248
299
  - { name: be_root_exists, from: "$.backend_root_exists" }
249
300
  - { name: be_profile, from: "$.backend_profile" }
250
301
  - { name: be_title, from: "$.backend_title" }
@@ -252,9 +303,11 @@ workflow:
252
303
  - { name: be_ecosystem, from: "$.backend_ecosystem" }
253
304
  - { name: be_build, from: "$.backend_build" }
254
305
  - { name: be_test, from: "$.backend_test" }
306
+ - { name: be_reserved, from: "$.backend_reserved_csv" }
255
307
  - { name: fe_declared, from: "$.frontend_declared" }
256
308
  - { name: fe_lane_id, from: "$.frontend_lane_id" }
257
309
  - { name: fe_source_path, from: "$.frontend_source_path" }
310
+ - { name: fe_test_path, from: "$.frontend_test_path" }
258
311
  - { name: fe_root_exists, from: "$.frontend_root_exists" }
259
312
  - { name: fe_profile, from: "$.frontend_profile" }
260
313
  - { name: fe_title, from: "$.frontend_title" }
@@ -262,6 +315,7 @@ workflow:
262
315
  - { name: fe_ecosystem, from: "$.frontend_ecosystem" }
263
316
  - { name: fe_build, from: "$.frontend_build" }
264
317
  - { name: fe_test, from: "$.frontend_test" }
318
+ - { name: fe_reserved, from: "$.frontend_reserved_csv" }
265
319
 
266
320
  - id: require_lanes
267
321
  action: verify_context
@@ -425,11 +479,15 @@ workflow:
425
479
  # spec.md, `implement` therefore generated from a plan that was never held against the
426
480
  # spec it now contradicts, silently and at full length.
427
481
  #
428
- # plan-gate-check compares today's bytes against the receipt analyze-plan left when it
429
- # passed — spec.md, the context's domain-model.md and each gated lane plan. The plans'
430
- # own `**Version**:` headers cannot answer this and must not be used: close-delta stamps
431
- # them and spec.md to the same number in lockstep whether or not a plan was re-derived,
432
- # so the comparison reads equal always.
482
+ # plan-gate-check compares today's bytes against the receipt analyze-plan left — spec.md,
483
+ # the context's domain-model.md and each gated lane plan — and reads the verdict that
484
+ # run reached. The plans' own `**Version**:` headers cannot answer this and must not be
485
+ # used: close-delta stamps them and spec.md to the same number in lockstep whether or not
486
+ # a plan was re-derived, so the comparison reads equal always.
487
+ #
488
+ # THREE OUTCOMES FOR A RECEIPT. Moved bytes refuse. Verdict `gaps` refuses: that run left
489
+ # gaps a plan rewrite closes. Verdict `routed-gaps` generates and warns: what is left
490
+ # can only be closed in the spec or the model, and it is listed for /appweaver specify.
433
491
  #
434
492
  # BOOTSTRAP ONLY. A delta run generates from delta plans that analyze-plan-delta gates;
435
493
  # the master receipt is silent about them, and reading it there would refuse a change
@@ -441,25 +499,41 @@ workflow:
441
499
  subcommand: plan-gate-check
442
500
  args: ["--feature-dir", "{{feature_dir}}"]
443
501
  outputs:
444
- - { name: gate_tracked, from: "$.tracked" }
445
- - { name: gate_fresh, from: "$.gate_fresh" }
446
- - { name: gate_reason, from: "$.reason" }
447
- - { name: gate_at, from: "$.gated" }
502
+ - { name: gate_tracked, from: "$.tracked" }
503
+ - { name: gate_fresh, from: "$.gate_fresh" }
504
+ - { name: gate_verdict, from: "$.verdict" }
505
+ - { name: gate_may_generate, from: "$.may_generate" }
506
+ - { name: gate_routed_csv, from: "$.routed_csv" }
507
+ - { name: gate_reason, from: "$.reason" }
508
+ - { name: gate_at, from: "$.gated" }
448
509
 
449
510
  # The ordered assignment is the AND: only a TRACKED feature can lower the flag, and
450
- # what lowers it is the gate no longer being fresh. An untracked one never reaches
511
+ # what lowers it is plan-gate-check's may_generate. An untracked one never reaches
451
512
  # this step's assignment and generates, which is the whole accommodation for trees
452
513
  # that have not been gated since the receipt existed.
453
514
  - id: flag_gate_block
454
515
  condition: "{{gate_tracked}} == true"
455
516
  action: set
456
- vars: { may_generate: "{{gate_fresh}}" }
517
+ vars: { may_generate: "{{gate_may_generate}}" }
457
518
 
458
519
  - id: require_gate_fresh
459
520
  action: verify_context
460
521
  require_true: may_generate
461
522
  on_false: stop
462
- on_false_message: "implement REFUSED — the master plans are no longer the ones analyze-plan passed: {{gate_reason}}. This phase generates from the plans and does NOT read the spec, on the strength of that gate having conformed them to it, so generating now would realise a plan against requirements nobody checked it against. Re-run /appweaver analyze-plan — it re-checks all four checks, repairs the drifting plan on approval, and re-issues the receipt — then re-run /appweaver implement. If what changed was a plan you re-derived with /appweaver plan main, that is the same path: the re-derived plans have not been gated yet. Nothing was generated."
523
+ on_false_message: "implement REFUSED — analyze-plan's receipt does not clear these plans: {{gate_reason}}. This phase generates from the plans and does NOT read the spec, on the strength of that gate having checked them against it, so generating now would realise a plan against requirements nobody held it to. Re-run /appweaver analyze-plan — it re-checks all four checks, repairs the drifting plan on approval, and re-issues the receipt — then re-run /appweaver implement. If what changed was a plan you re-derived with /appweaver plan main, that is the same path: the re-derived plans have not been gated yet. Nothing was generated."
524
+
525
+ # THE WARNING for a receipt whose only open gaps belong to specify. Generation goes on:
526
+ # no plan rewrite could close them, and the plan is what this phase builds from.
527
+ - id: flag_routed_gaps
528
+ condition: "{{gate_verdict}} == routed-gaps"
529
+ action: set
530
+ vars: { gate_no_routed: "false" }
531
+
532
+ - id: warn_routed_gaps
533
+ action: verify_context
534
+ require_true: gate_no_routed
535
+ on_false: continue
536
+ on_false_message: "WARNING — analyze-plan left gaps only the spec or the model can close, so the code generated now will not cover them: {{gate_routed_csv}}. Lift each into spec.md / domain-model.md by opening a change with /appweaver specify."
463
537
 
464
538
  # 4. THE PROJECT each declared lane generates INTO. Deterministic structure gate:
465
539
  # detect-project searches the LANE'S OWN subtree for an ecosystem manifest (.sln/
@@ -575,7 +649,7 @@ workflow:
575
649
  - id: ask_backend
576
650
  condition: "{{be_runnable}} == true"
577
651
  action: ask
578
- prompt: "Implement the BACKEND lane now? Lane `{{be_lane_id}}` — {{be_title}}, {{be_language}} ({{be_ecosystem}}) — generating from {{be_plan}} into {{be_source_path}}."
652
+ prompt: "Implement the BACKEND lane now? Lane `{{be_lane_id}}` — {{be_title}}, {{be_language}} ({{be_ecosystem}}) — generating from {{be_plan}} into {{be_source_path}}, tests into {{be_test_path}}."
579
653
  options: ["yes", "no"]
580
654
  answer_var: be_answer
581
655
  - id: be_go_set
@@ -586,7 +660,7 @@ workflow:
586
660
  - id: ask_frontend
587
661
  condition: "{{fe_runnable}} == true"
588
662
  action: ask
589
- prompt: "Implement the FRONTEND lane now? Lane `{{fe_lane_id}}` — {{fe_title}}, {{fe_language}} ({{fe_ecosystem}}) — generating from {{fe_plan}} into {{fe_source_path}}."
663
+ prompt: "Implement the FRONTEND lane now? Lane `{{fe_lane_id}}` — {{fe_title}}, {{fe_language}} ({{fe_ecosystem}}) — generating from {{fe_plan}} into {{fe_source_path}}, tests into {{fe_test_path}}."
590
664
  options: ["yes", "no"]
591
665
  answer_var: fe_answer
592
666
  - id: fe_go_set
@@ -616,6 +690,69 @@ workflow:
616
690
  action: set
617
691
  vars: { fe_mode: "{{mode}}" }
618
692
 
693
+ # 8b. EACH GENERATING LANE'S ELEMENT LIST, read off its plan by plan-coverage. The
694
+ # generation prompt carries it, and the model reports every name on it — so an
695
+ # element it did not get to is declared rather than silently absent.
696
+ - id: be_plan_elements
697
+ condition: "{{be_go}} == true"
698
+ action: execute_script
699
+ script: { ref: actions }
700
+ subcommand: plan-coverage
701
+ args: ["--plan", "{{be_plan}}", "--kind", "backend", "--mode", "{{be_mode}}", "--list-only", "--reserved", "{{be_reserved}}"]
702
+ outputs:
703
+ - { name: be_element_names, from: "$.names_text" }
704
+ - { name: be_collisions, from: "$.collisions_csv" }
705
+ - { name: be_collisions_text, from: "$.collisions_text" }
706
+ - id: fe_plan_elements
707
+ condition: "{{fe_go}} == true"
708
+ action: execute_script
709
+ script: { ref: actions }
710
+ subcommand: plan-coverage
711
+ args: ["--plan", "{{fe_plan}}", "--kind", "frontend", "--mode", "{{fe_mode}}", "--list-only", "--reserved", "{{fe_reserved}}"]
712
+ outputs:
713
+ - { name: fe_element_names, from: "$.names_text" }
714
+ - { name: fe_collisions, from: "$.collisions_csv" }
715
+ - { name: fe_collisions_text, from: "$.collisions_text" }
716
+
717
+ # 8c. THE CODE THAT IS ALREADY THERE, per lane, from the aggregate's OKF concept. This
718
+ # is the step that WRITES code, and it was the only one that resolved no source at
719
+ # all — plan, delta-plan, analyze-slice, build and run all read the real tree this
720
+ # way. Without it a lane extending a shared aggregate had to go and discover the
721
+ # established conventions for itself, or not bother, which is how two features end
722
+ # up spelling the same pattern two ways.
723
+ #
724
+ # A PATH LIST, not file contents: the generation steps declare `tools: Read` and open
725
+ # what they need, because a brownfield tree does not fit in a payload. Nothing is
726
+ # truncated — a file left off the list is a file the model does not know to EXTEND
727
+ # and may regenerate instead.
728
+ #
729
+ # `--under` narrows the concept to the lane's own two subtrees. A concept spans every
730
+ # lane that realises the aggregate, so without it the backend pass is handed the UI's
731
+ # files — a subtree it is forbidden to write into two rules later.
732
+ #
733
+ # Read-only, and greenfield-safe: no concept yet exits 0 with count 0, which is the
734
+ # first feature on a fresh tree and blocks nothing.
735
+ - id: be_existing_code
736
+ condition: "{{be_go}} == true"
737
+ action: execute_script
738
+ on_error: continue
739
+ script: { ref: xref }
740
+ subcommand: concept-paths
741
+ args: ["--feature-dir", "{{feature_dir}}", "--under", "{{be_source_path}}", "--under", "{{be_test_path}}"]
742
+ outputs:
743
+ - { name: be_existing_text, from: "$.paths_text" }
744
+ - { name: be_existing_count, from: "$.count" }
745
+ - id: fe_existing_code
746
+ condition: "{{fe_go}} == true"
747
+ action: execute_script
748
+ on_error: continue
749
+ script: { ref: xref }
750
+ subcommand: concept-paths
751
+ args: ["--feature-dir", "{{feature_dir}}", "--under", "{{fe_source_path}}", "--under", "{{fe_test_path}}"]
752
+ outputs:
753
+ - { name: fe_existing_text, from: "$.paths_text" }
754
+ - { name: fe_existing_count, from: "$.count" }
755
+
619
756
  # 9. Each generating lane's code-generation rule files → sticky standards (configuration,
620
757
  # not hardcoded). Loaded only for a lane that is actually going to generate, and the
621
758
  # backend's heading is keyed by mode (see the header). No blocking gate: an
@@ -698,15 +835,47 @@ workflow:
698
835
  loaded standards describe a DIFFERENT stack than the one above, the lane wins on
699
836
  language and layout — the standards' stack-specific mechanics do not apply, so
700
837
  follow their INTENT and STOP to surface the mismatch in your summary.
701
- - WRITE ONLY UNDER `{{be_source_path}}` — the source path the `{{be_lane_id}}` lane
702
- declares in .appweaver/stacks.json. That subtree is this lane's, and a file outside
703
- it belongs to another lane or to no lane at all. NEVER write into the frontend
704
- lane's subtree, not even to consume a contract. Do not create a parallel source
705
- tree because the layout inside it is unfamiliar; if the plan needs a place this
706
- subtree has no home for, STOP and surface it.
838
+ - WRITE ONLY INTO THE LANE'S TWO PATHS, both resolved for the `{{be_lane_id}}` lane
839
+ from .appweaver/stacks.json: production code under `{{be_source_path}}`, paired
840
+ tests under `{{be_test_path}}` (the same directory when the stack keeps its tests
841
+ inside its source). Those subtrees are this lane's, and a file outside them belongs
842
+ to another lane or to no lane at all. NEVER write into the frontend lane's subtree,
843
+ not even to consume a contract. Do not create a parallel source tree because the
844
+ layout inside them is unfamiliar; if the plan needs a place they have no home for,
845
+ STOP and surface it.
846
+ - NAMES THIS STACK ALREADY DEFINES: {{be_collisions_text}}.
847
+ Where that list is not `none`, the plan's name is RIGHT and stays exactly as the
848
+ plan spells it — it is the ubiquitous language, and renaming a domain type to
849
+ dodge a framework one is never the answer. What changes is how it is WRITTEN at
850
+ the use site: qualify it with its own enclosing namespace segment, every time.
851
+ In C# that is `Domain.Route`, not a bare `Route`, wherever the type is named —
852
+ declarations, parameters, return types, generic arguments and doc comments.
853
+ The conflict is INVISIBLE in the file: the framework name arrives through an
854
+ implicit/ambient import and appears in no line of it, so a bare name looks fine,
855
+ compiles in your head, and fails the build as an ambiguous reference (C# CS0104).
856
+ Do not add a `using` alias and do not fully qualify the whole namespace path —
857
+ one convention, so two features never disambiguate the same name two ways.
858
+ - THE CODE THAT IS ALREADY THERE. This aggregate already has {{be_existing_count}} file(s) in
859
+ this lane, listed from its OKF concept:
860
+ {{be_existing_text}}
861
+ READ the ones your elements touch BEFORE writing anything. They are this
862
+ codebase's established conventions — the identifier pattern, the persistence and mapping shape, the endpoint
863
+ and handler wiring, the error and DTO style — and new code follows
864
+ them rather than introducing a second way of doing the same thing. A listed file
865
+ that one of your elements maps onto is EXTENDED: add the member, leave every
866
+ existing member byte-for-byte alone, and never regenerate, rename or move it.
867
+ Nothing is left off that list, so a path not on it does not exist yet. A count of
868
+ 0 means this is the aggregate's first code in this lane and the conventions come
869
+ from the loaded standards instead.
870
+ - A MISSING TEST PROJECT IS CREATED, NEVER A REASON TO SKIP THE TESTS. When
871
+ `{{be_test_path}}` holds no test project yet, create one there as the loaded
872
+ testing standard describes, referencing the production project it tests, and
873
+ register it wherever `{{be_test}}` discovers tests (for .NET, the solution file).
874
+ That registration is the ONLY edit allowed outside the lane's two paths; name the
875
+ file you edited in your summary.
707
876
  - Follow the project's folder/layer convention + the loaded coding-standards,
708
877
  dependency-policy and testing standards for HOW to structure, name and place files
709
- WITHIN that subtree — the plan says WHAT to build; these standards (identical for
878
+ WITHIN those subtrees — the plan says WHAT to build; these standards (identical for
710
879
  every feature) say HOW.
711
880
  - COMPONENTS: when the plan's External Integrations table depends on a framework
712
881
  component (e.g. `IEmailService`, `IJobScheduler`), code against that component's
@@ -727,6 +896,12 @@ workflow:
727
896
  CN / DC / IC / US) from its Traceability section. Include tests for the constraints
728
897
  — e.g. an audit record is emitted, a DC-restricted field is NOT exposed on the
729
898
  public surface, an IC- projection round-trips.
899
+ - WIRE WHAT YOU BUILD, in this same pass. Every port, adapter, handler and endpoint
900
+ the plan introduces is registered in the lane's composition root as the plan's §4.3
901
+ Wiring states. A dependency that is injected and never bound COMPILES GREEN — a
902
+ container graph is not compile-checked — and fails on the first request that needs
903
+ it. Where the project registers by assembly scan or convention, confirm the new
904
+ type is actually covered by it rather than assuming it is.
730
905
  - Do NOT add behaviour the plan does not state, and do NOT omit any it does. If the
731
906
  plan looks wrong or incomplete, STOP and surface it (it is fixed in plan /
732
907
  analyze-plan) — never compensate from the spec or from memory.
@@ -740,8 +915,16 @@ workflow:
740
915
  one changed, STOP and surface it.
741
916
  - Where generated code depends on a plan Assumption (DA-n) or Workaround (WA-n), mark
742
917
  it with a comment citing that id so it is easy to revisit later.
918
+ - REPORT EVERY PLAN ELEMENT. The plan's elements, by name: {{be_element_names}}.
919
+ Add an `elements` list to your closing JSON block with one entry per name, spelled
920
+ exactly as listed: {"element": "<name>", "status": "done" | "blocked", "reason":
921
+ "<what stops it, when blocked>"}. `done` means it is realised in code now,
922
+ including one that already existed; `blocked` means you could not realise it. An
923
+ element missing from the list counts as not done, and the code is checked for
924
+ each name after this step.
743
925
  outputs:
744
926
  - { name: be_files, from: "$.files" }
927
+ - { name: be_elements, from: "$.elements" }
745
928
 
746
929
  # ═══════════ BACKEND LANE — delta, generate ONLY what the change adds ════════
747
930
  # The delta plan is the authoritative source of WHAT changed — analyze-plan-delta already
@@ -816,12 +999,42 @@ workflow:
816
999
  loaded standards describe a DIFFERENT stack than the one above, the lane wins on
817
1000
  language and layout — follow the standards' INTENT and STOP to surface the
818
1001
  mismatch in your summary.
819
- - WRITE ONLY UNDER `{{be_source_path}}` — the source path the `{{be_lane_id}}` lane
820
- declares. That subtree is this lane's, and a file outside it belongs to another
821
- lane or to no lane at all. NEVER write into the frontend lane's subtree.
1002
+ - WRITE ONLY INTO THE LANE'S TWO PATHS: production code under `{{be_source_path}}`,
1003
+ paired tests under `{{be_test_path}}`. Those subtrees are this lane's, and a file
1004
+ outside them belongs to another lane or to no lane at all. NEVER write into the
1005
+ frontend lane's subtree.
1006
+ - NAMES THIS STACK ALREADY DEFINES: {{be_collisions_text}}.
1007
+ Where that list is not `none`, the plan's name is RIGHT and stays exactly as the
1008
+ plan spells it — it is the ubiquitous language, and renaming a domain type to
1009
+ dodge a framework one is never the answer. What changes is how it is WRITTEN at
1010
+ the use site: qualify it with its own enclosing namespace segment, every time.
1011
+ In C# that is `Domain.Route`, not a bare `Route`, wherever the type is named —
1012
+ declarations, parameters, return types, generic arguments and doc comments.
1013
+ The conflict is INVISIBLE in the file: the framework name arrives through an
1014
+ implicit/ambient import and appears in no line of it, so a bare name looks fine,
1015
+ compiles in your head, and fails the build as an ambiguous reference (C# CS0104).
1016
+ Do not add a `using` alias and do not fully qualify the whole namespace path —
1017
+ one convention, so two features never disambiguate the same name two ways.
1018
+ - THE CODE THAT IS ALREADY THERE. This aggregate already has {{be_existing_count}} file(s) in
1019
+ this lane, listed from its OKF concept:
1020
+ {{be_existing_text}}
1021
+ READ the ones your elements touch BEFORE writing anything. They are this
1022
+ codebase's established conventions — the identifier pattern, the persistence and mapping shape, the endpoint
1023
+ and handler wiring, the error and DTO style — and new code follows
1024
+ them rather than introducing a second way of doing the same thing. A listed file
1025
+ that one of your elements maps onto is EXTENDED: add the member, leave every
1026
+ existing member byte-for-byte alone, and never regenerate, rename or move it.
1027
+ Nothing is left off that list, so a path not on it does not exist yet. A count of
1028
+ 0 means this is the aggregate's first code in this lane and the conventions come
1029
+ from the loaded standards instead.
1030
+ - A MISSING TEST PROJECT IS CREATED, NEVER A REASON TO SKIP THE TESTS. When
1031
+ `{{be_test_path}}` holds no test project yet, create one there as the loaded
1032
+ testing standard describes and register it wherever `{{be_test}}` discovers tests
1033
+ (for .NET, the solution file) — the ONLY edit allowed outside the lane's two paths;
1034
+ name the file you edited in your summary.
822
1035
  - Follow the project's EXISTING folder/layer convention + the loaded
823
1036
  coding-standards, dependency-policy and testing standards for HOW to structure,
824
- name and place files WITHIN that subtree. Match the surrounding code already there.
1037
+ name and place files WITHIN those subtrees. Match the surrounding code already there.
825
1038
  - Realise EVERY ADDED/MODIFIED delta element as code: one file per Domain type /
826
1039
  Application use-case / API endpoint / Infrastructure adapter, placed by layer.
827
1040
  Preserve the ubiquitous-language names verbatim. Enforce each new/changed Domain
@@ -838,20 +1051,114 @@ workflow:
838
1051
  The spec is not in context: take the id from the plan and cite it verbatim, never
839
1052
  guess one from the element's shape. A delta-plan element that names no id is a
840
1053
  planning gap — surface it rather than inventing the traceability.
1054
+ - WIRE WHAT YOU BUILD, in this same pass: every port, adapter, handler and endpoint
1055
+ this change introduces is registered in the lane's composition root, as the delta
1056
+ plan states. A dependency injected and never bound compiles green and fails on the
1057
+ first request; where registration is by assembly scan, confirm the new type is
1058
+ covered by it.
841
1059
  - REGRESSION: for each element in the delta plan's "Regression targets", verify (add
842
1060
  or adjust a test where needed) that the existing behaviour still holds after a
843
1061
  shared element changed — but do NOT rewrite that existing behaviour.
844
1062
  - Where generated code depends on a delta Assumption (DA-n) or Workaround (WA-n),
845
1063
  mark it with a comment citing that id.
1064
+ - REPORT EVERY DELTA ELEMENT. The delta plan's ADDED and MODIFIED elements, by name:
1065
+ {{be_element_names}}. Add an `elements` list to your closing JSON block with one
1066
+ entry per name, spelled exactly as listed: {"element": "<name>", "status": "done"
1067
+ | "blocked", "reason": "<what stops it, when blocked>"}. An element missing from
1068
+ the list counts as not done, and the code is checked for each name after this step.
846
1069
  outputs:
847
1070
  - { name: be_files, from: "$.files" }
1071
+ - { name: be_elements, from: "$.elements" }
848
1072
 
849
- - id: verify_be_files
1073
+ # WHAT THIS LANE ACTUALLY DID, judged on the code rather than on the file count. An empty
1074
+ # file list has two opposite meanings — the lane is already built, or the pass achieved
1075
+ # nothing — and only the code tells them apart, so plan-coverage runs FIRST and the gate
1076
+ # below reads its verdict. `--generated` carries the file list: empty makes this the
1077
+ # already-implemented probe, judged on the code alone, because an element no scan can
1078
+ # confirm (a port, an adapter) was confirmed by the run that created it.
1079
+ #
1080
+ # PARTIAL GENERATION. A non-empty file list is not a finished lane either: a pass that
1081
+ # stopped part-way returns the files it did write. Coverage then checks every plan element
1082
+ # two ways — the code must name each identifier the plan introduces, and the model's own
1083
+ # report must say each element is done — and whatever fails either check gets ONE
1084
+ # completion pass in the same envelope. What is still missing after it stops the run at
1085
+ # step 10b, once both lanes have had their turn.
1086
+ - id: be_coverage
850
1087
  condition: "{{be_go}} == true"
1088
+ action: execute_script
1089
+ script: { ref: actions }
1090
+ subcommand: plan-coverage
1091
+ args: ["--plan", "{{be_plan}}", "--kind", "backend", "--mode", "{{be_mode}}", "--source-path", "{{be_source_path}}", "--test-path", "{{be_test_path}}", "--reported", "{{be_elements}}", "--generated", "{{be_files}}"]
1092
+ outputs:
1093
+ - { name: be_complete, from: "$.ok" }
1094
+ - { name: be_outcome, from: "$.outcome" }
1095
+ - { name: be_gap_names, from: "$.gap_names_csv" }
1096
+ - { name: be_gaps_text, from: "$.gaps_text" }
1097
+ - { name: be_coverage_note, from: "$.message" }
1098
+
1099
+ # NOTHING TO DO IS AN OUTCOME, not a failure. A lane whose every plan element is already
1100
+ # realised writes no files, and that is the correct result of re-running implement on a
1101
+ # finished feature — the other lane may still have work, and the OKF merge and the edges
1102
+ # downstream are reconcile-on-write, so the run carries on.
1103
+ - id: be_satisfied_flag
1104
+ condition: "{{be_outcome}} == already-implemented"
1105
+ action: set
1106
+ vars: { be_satisfied: "true" }
1107
+
1108
+ # The one empty-file case that IS unexplained: a plan no element could be read from
1109
+ # proves nothing about the code, so a pass that wrote nothing against it has no evidence
1110
+ # either way. That is the template-not-filled case the old file-count gate was reaching
1111
+ # for, and it is the only one left that stops the run here.
1112
+ - id: verify_be_evidence
1113
+ condition: "{{be_outcome}} == no-plan-elements"
851
1114
  action: verify_context
852
1115
  require_non_empty: be_files
853
1116
  on_empty: stop
854
- on_empty_message: "The backend lane generated no files. Ensure {{be_plan}} is a FILLED plan (not the empty template) and that it passed its gate — /appweaver analyze-plan for a master, /appweaver analyze-plan-delta for a change — then re-run implement."
1117
+ on_empty_message: "The backend lane generated no files, and no plan element could be read from {{be_plan}} — so there is no evidence of what it should have built. The usual cause is an unfilled plan (still the empty template) or one whose headings do not follow the plan template. Fill it with /appweaver plan, check it with /appweaver analyze-plan (analyze-plan-delta for a change), then re-run implement."
1118
+ - id: be_completion_flag
1119
+ condition: "{{be_complete}} == false"
1120
+ action: set
1121
+ vars: { be_completion_ran: "true" }
1122
+ - id: gen_backend_complete
1123
+ condition: "{{be_completion_ran}} == true"
1124
+ action: agent_generate
1125
+ tools: "Read,Write,Edit"
1126
+ permission_mode: acceptEdits
1127
+ task: |
1128
+ COMPLETE the backend lane. The generation pass before this one left these plan
1129
+ elements unrealised: {{be_gap_names}}.
1130
+ Why each was flagged: {{be_gaps_text}}.
1131
+ The same plan, domain model and constitution are in context, and every rule of that
1132
+ pass still holds: realise each element STRICTLY as the plan states it; production
1133
+ code only under `{{be_source_path}}` and paired tests only under `{{be_test_path}}`;
1134
+ the `{{be_lane_id}}` lane's stack ({{be_title}}, {{be_language}}) and no other;
1135
+ shared aggregate files EXTENDED, never regenerated; nothing the plan does not state.
1136
+ Rules:
1137
+ - Realise each listed element now, with its paired test citing the spec id the plan
1138
+ gives it. Leave what the earlier pass wrote as it is, except to extend a shared file.
1139
+ - Use the plan's name for each element EXACTLY. An element that already exists
1140
+ under a different name is renamed to the plan's name, with its references — the
1141
+ plan's names are the ubiquitous language.
1142
+ - An element the plan names by intent (a port, an adapter, an endpoint route) is done
1143
+ when the code realises it, whatever the framework calls it; report it `done`.
1144
+ - If an element cannot be realised, do not work around it: report it `blocked` and
1145
+ say what stops you. A plan that is wrong is fixed in plan / analyze-plan.
1146
+ - Report every listed element in an `elements` list in your closing JSON block:
1147
+ {"element": "<name as listed>", "status": "done" | "blocked", "reason": "…"}.
1148
+ outputs:
1149
+ - { name: be_more_files, from: "$.files" }
1150
+ - { name: be_elements_more, from: "$.elements" }
1151
+ - id: be_coverage_recheck
1152
+ condition: "{{be_completion_ran}} == true"
1153
+ action: execute_script
1154
+ script: { ref: actions }
1155
+ subcommand: plan-coverage
1156
+ args: ["--plan", "{{be_plan}}", "--kind", "backend", "--mode", "{{be_mode}}", "--source-path", "{{be_source_path}}", "--test-path", "{{be_test_path}}", "--reported", "{{be_elements}}", "--reported", "{{be_elements_more}}"]
1157
+ outputs:
1158
+ - { name: be_complete, from: "$.ok" }
1159
+ - { name: be_outcome, from: "$.outcome" }
1160
+ - { name: be_gap_names, from: "$.gap_names_csv" }
1161
+ - { name: be_gaps_text, from: "$.gaps_text" }
855
1162
 
856
1163
  # OKF knowledge — the feature has ONE concept across both lanes and both modes, so every
857
1164
  # lane's files JOIN it rather than replace it (--merge unions them with what is already
@@ -868,6 +1175,15 @@ workflow:
868
1175
  args: ["--feature-dir", "{{feature_dir}}", "--source-files", "{{be_files}}", "--merge", "--origin", "greenfield"]
869
1176
  outputs:
870
1177
  - { name: okf_concept, from: "$.concept" }
1178
+ - id: xref_concept_backend_more
1179
+ condition: "{{be_completion_ran}} == true"
1180
+ action: execute_script
1181
+ on_error: continue
1182
+ script: { ref: xref }
1183
+ subcommand: concept-upsert
1184
+ args: ["--feature-dir", "{{feature_dir}}", "--source-files", "{{be_more_files}}", "--merge", "--origin", "greenfield"]
1185
+ outputs:
1186
+ - { name: okf_concept, from: "$.concept" }
871
1187
 
872
1188
  # ══════════════ THE LANE SWAP — the frontend's rules replace the backend's ═══
873
1189
  # build_context already scoped the two lanes' PAYLOADS apart; this is the same boundary
@@ -932,16 +1248,46 @@ workflow:
932
1248
  re-decide anything already fixed there. If the loaded standards describe a
933
1249
  DIFFERENT stack than the one above, the lane wins on framework and layout — follow
934
1250
  the standards' INTENT and STOP to surface the mismatch in your summary.
935
- - WRITE ONLY UNDER `{{fe_source_path}}` — the source path the `{{fe_lane_id}}` lane
936
- declares. That subtree is this lane's; a file outside it belongs to the service
937
- lane or to no lane. NEVER touch the service lane's code, not even to add a field
938
- the UI wants: a gap in the service surface is a contract delta raised back to that
939
- lane. Do not create a parallel source tree because the layout inside this one is
940
- unfamiliar; if the plan needs a place this subtree has no home for, STOP and
941
- surface it.
1251
+ - WRITE ONLY INTO THE LANE'S TWO PATHS, both resolved for the `{{fe_lane_id}}` lane:
1252
+ production code under `{{fe_source_path}}`, paired tests under `{{fe_test_path}}`
1253
+ (the same directory when the stack keeps its tests inside its source). Those
1254
+ subtrees are this lane's; a file outside them belongs to the service lane or to no
1255
+ lane. NEVER touch the service lane's code, not even to add a field the UI wants: a
1256
+ gap in the service surface is a contract delta raised back to that lane. Do not
1257
+ create a parallel source tree because the layout inside this one is unfamiliar; if
1258
+ the plan needs a place this subtree has no home for, STOP and surface it.
1259
+ - NAMES THIS STACK ALREADY DEFINES: {{fe_collisions_text}}.
1260
+ Where that list is not `none`, the plan's name is RIGHT and stays exactly as the
1261
+ plan spells it — it is the ubiquitous language, and renaming a domain type to
1262
+ dodge a framework one is never the answer. What changes is how it is WRITTEN at
1263
+ the use site: qualify it with its own enclosing namespace segment, every time.
1264
+ In C# that is `Domain.Route`, not a bare `Route`, wherever the type is named —
1265
+ declarations, parameters, return types, generic arguments and doc comments.
1266
+ The conflict is INVISIBLE in the file: the framework name arrives through an
1267
+ implicit/ambient import and appears in no line of it, so a bare name looks fine,
1268
+ compiles in your head, and fails the build as an ambiguous reference (C# CS0104).
1269
+ Do not add a `using` alias and do not fully qualify the whole namespace path —
1270
+ one convention, so two features never disambiguate the same name two ways.
1271
+ - THE CODE THAT IS ALREADY THERE. This aggregate already has {{fe_existing_count}} file(s) in
1272
+ this lane, listed from its OKF concept:
1273
+ {{fe_existing_text}}
1274
+ READ the ones your elements touch BEFORE writing anything. They are this
1275
+ codebase's established conventions — the component and screen layout, the server-action and data-fetch shape,
1276
+ the state and error handling, the locale-catalogue mechanics — and new code follows
1277
+ them rather than introducing a second way of doing the same thing. A listed file
1278
+ that one of your elements maps onto is EXTENDED: add the member, leave every
1279
+ existing member byte-for-byte alone, and never regenerate, rename or move it.
1280
+ Nothing is left off that list, so a path not on it does not exist yet. A count of
1281
+ 0 means this is the aggregate's first code in this lane and the conventions come
1282
+ from the loaded standards instead.
1283
+ - A MISSING TEST SETUP IS CREATED, NEVER A REASON TO SKIP THE TESTS. When the lane
1284
+ has no test project or test runner yet, set one up inside the lane's paths as the
1285
+ loaded testing standard describes, so that `{{fe_test}}` runs the tests you write.
1286
+ Anything outside the lane's two paths that must change for that is the ONLY such
1287
+ edit allowed; name the file you edited in your summary.
942
1288
  - Follow the UI constitution's folder structure, naming conventions and forbidden
943
1289
  patterns, plus the loaded standards, for HOW to structure, name and place files
944
- WITHIN that subtree — the plan says WHAT to build; these say HOW.
1290
+ WITHIN those subtrees — the plan says WHAT to build; these say HOW.
945
1291
  - REALISE EVERY PLAN ELEMENT, changing nothing about WHAT it specifies:
946
1292
  • §1 — one route/screen per row, with the navigation shape and the access
947
1293
  conditions the row states.
@@ -974,8 +1320,16 @@ workflow:
974
1320
  feature. If one of them has to change, STOP and surface it.
975
1321
  - Where generated code depends on a plan Assumption (DA-n) or Workaround (WA-n), mark
976
1322
  it with a comment citing that id so it is easy to revisit later.
1323
+ - REPORT EVERY PLAN ELEMENT. The plan's elements, by name: {{fe_element_names}}.
1324
+ Add an `elements` list to your closing JSON block with one entry per name, spelled
1325
+ exactly as listed: {"element": "<name>", "status": "done" | "blocked", "reason":
1326
+ "<what stops it, when blocked>"}. `done` means it is realised in code now,
1327
+ including one that already existed; `blocked` means you could not realise it. An
1328
+ element missing from the list counts as not done, and the code is checked for
1329
+ each name after this step.
977
1330
  outputs:
978
1331
  - { name: fe_files, from: "$.files" }
1332
+ - { name: fe_elements, from: "$.elements" }
979
1333
 
980
1334
  # ═══════════ FRONTEND LANE — delta, generate ONLY what the change adds ═══════
981
1335
  # The UI half of the CR loop, which never existed as its own command: the ⌛
@@ -1031,9 +1385,37 @@ workflow:
1031
1385
  `{{fe_test}}`. Generate in THAT framework and no other. Take the router, state
1032
1386
  approach, component library, design system and styling from the UI constitution +
1033
1387
  the loaded standards, and MATCH the code already in the tree.
1034
- - WRITE ONLY UNDER `{{fe_source_path}}`. NEVER touch the service lane's code, not
1388
+ - WRITE ONLY INTO THE LANE'S TWO PATHS: production code under `{{fe_source_path}}`,
1389
+ paired tests under `{{fe_test_path}}`. NEVER touch the service lane's code, not
1035
1390
  even to add a field the UI wants — a gap in the service surface is a contract delta
1036
1391
  raised back to that lane, never worked around in the client.
1392
+ - NAMES THIS STACK ALREADY DEFINES: {{fe_collisions_text}}.
1393
+ Where that list is not `none`, the plan's name is RIGHT and stays exactly as the
1394
+ plan spells it — it is the ubiquitous language, and renaming a domain type to
1395
+ dodge a framework one is never the answer. What changes is how it is WRITTEN at
1396
+ the use site: qualify it with its own enclosing namespace segment, every time.
1397
+ In C# that is `Domain.Route`, not a bare `Route`, wherever the type is named —
1398
+ declarations, parameters, return types, generic arguments and doc comments.
1399
+ The conflict is INVISIBLE in the file: the framework name arrives through an
1400
+ implicit/ambient import and appears in no line of it, so a bare name looks fine,
1401
+ compiles in your head, and fails the build as an ambiguous reference (C# CS0104).
1402
+ Do not add a `using` alias and do not fully qualify the whole namespace path —
1403
+ one convention, so two features never disambiguate the same name two ways.
1404
+ - THE CODE THAT IS ALREADY THERE. This aggregate already has {{fe_existing_count}} file(s) in
1405
+ this lane, listed from its OKF concept:
1406
+ {{fe_existing_text}}
1407
+ READ the ones your elements touch BEFORE writing anything. They are this
1408
+ codebase's established conventions — the component and screen layout, the server-action and data-fetch shape,
1409
+ the state and error handling, the locale-catalogue mechanics — and new code follows
1410
+ them rather than introducing a second way of doing the same thing. A listed file
1411
+ that one of your elements maps onto is EXTENDED: add the member, leave every
1412
+ existing member byte-for-byte alone, and never regenerate, rename or move it.
1413
+ Nothing is left off that list, so a path not on it does not exist yet. A count of
1414
+ 0 means this is the aggregate's first code in this lane and the conventions come
1415
+ from the loaded standards instead.
1416
+ - A MISSING TEST SETUP IS CREATED, NEVER A REASON TO SKIP THE TESTS: set one up
1417
+ inside the lane's paths so that `{{fe_test}}` runs the tests you write, and name
1418
+ any file outside them you had to edit for that in your summary.
1037
1419
  - STATES ARE PART OF THE CHANGE. Every interaction this delta adds or alters ships
1038
1420
  its FULL state set — loading, empty, error, permission-denied, in-flight — plus the
1039
1421
  validation timing, focus placement and keyboard reachability the delta plan states.
@@ -1055,15 +1437,86 @@ workflow:
1055
1437
  feature. If one of them has to change, STOP and surface it.
1056
1438
  - Where generated code depends on a delta Assumption (DA-n) or Workaround (WA-n),
1057
1439
  mark it with a comment citing that id.
1440
+ - REPORT EVERY DELTA ELEMENT. The delta plan's ADDED and MODIFIED elements, by name:
1441
+ {{fe_element_names}}. Add an `elements` list to your closing JSON block with one
1442
+ entry per name, spelled exactly as listed: {"element": "<name>", "status": "done"
1443
+ | "blocked", "reason": "<what stops it, when blocked>"}. An element missing from
1444
+ the list counts as not done, and the code is checked for each name after this step.
1058
1445
  outputs:
1059
1446
  - { name: fe_files, from: "$.files" }
1447
+ - { name: fe_elements, from: "$.elements" }
1060
1448
 
1061
- - id: verify_fe_files
1449
+ # WHAT THIS LANE ACTUALLY DID — the frontend twin of the backend check above, including
1450
+ # the already-implemented probe and the one empty-file case that still stops the run.
1451
+ - id: fe_coverage
1062
1452
  condition: "{{fe_go}} == true"
1453
+ action: execute_script
1454
+ script: { ref: actions }
1455
+ subcommand: plan-coverage
1456
+ args: ["--plan", "{{fe_plan}}", "--kind", "frontend", "--mode", "{{fe_mode}}", "--source-path", "{{fe_source_path}}", "--test-path", "{{fe_test_path}}", "--reported", "{{fe_elements}}", "--generated", "{{fe_files}}"]
1457
+ outputs:
1458
+ - { name: fe_complete, from: "$.ok" }
1459
+ - { name: fe_outcome, from: "$.outcome" }
1460
+ - { name: fe_gap_names, from: "$.gap_names_csv" }
1461
+ - { name: fe_gaps_text, from: "$.gaps_text" }
1462
+ - { name: fe_coverage_note, from: "$.message" }
1463
+
1464
+ - id: fe_satisfied_flag
1465
+ condition: "{{fe_outcome}} == already-implemented"
1466
+ action: set
1467
+ vars: { fe_satisfied: "true" }
1468
+
1469
+ - id: verify_fe_evidence
1470
+ condition: "{{fe_outcome}} == no-plan-elements"
1063
1471
  action: verify_context
1064
1472
  require_non_empty: fe_files
1065
1473
  on_empty: stop
1066
- on_empty_message: "The frontend lane generated no files. Ensure {{fe_plan}} is a FILLED plan (not the empty template) and that it passed its gate — /appweaver analyze-plan for a master, /appweaver analyze-plan-delta for a change — then re-run implement."
1474
+ on_empty_message: "The frontend lane generated no files, and no plan element could be read from {{fe_plan}} — so there is no evidence of what it should have built. The usual cause is an unfilled plan (still the empty template) or one whose headings do not follow the plan template. Fill it with /appweaver plan-ui, check it with /appweaver analyze-plan (analyze-plan-delta for a change), then re-run implement."
1475
+ - id: fe_completion_flag
1476
+ condition: "{{fe_complete}} == false"
1477
+ action: set
1478
+ vars: { fe_completion_ran: "true" }
1479
+ - id: gen_frontend_complete
1480
+ condition: "{{fe_completion_ran}} == true"
1481
+ action: agent_generate
1482
+ tools: "Read,Write,Edit"
1483
+ permission_mode: acceptEdits
1484
+ task: |
1485
+ COMPLETE the frontend lane. The generation pass before this one left these plan
1486
+ elements unrealised: {{fe_gap_names}}.
1487
+ Why each was flagged: {{fe_gaps_text}}.
1488
+ The same plan, domain model and UI constitution are in context, and every rule of
1489
+ that pass still holds: realise each element STRICTLY as the plan states it, with its
1490
+ FULL state set; production code only under `{{fe_source_path}}` and paired tests
1491
+ only under `{{fe_test_path}}`; the `{{fe_lane_id}}` lane's stack ({{fe_title}},
1492
+ {{fe_language}}) and no other; never the service lane's code, the app shell, the
1493
+ global layout or the design system; nothing the plan does not state.
1494
+ Rules:
1495
+ - Realise each listed element now, with its paired test citing the spec id the plan
1496
+ gives it. Leave what the earlier pass wrote as it is.
1497
+ - Use the plan's name for each element EXACTLY. An element that already exists
1498
+ under a different name is renamed to the plan's name, with its references — the
1499
+ plan's names are the ubiquitous language.
1500
+ - A screen or a binding is done when the code realises it, whatever its route or
1501
+ file is called; report it `done`.
1502
+ - If an element cannot be realised, do not work around it: report it `blocked` and
1503
+ say what stops you. A plan that is wrong is fixed in plan / analyze-plan.
1504
+ - Report every listed element in an `elements` list in your closing JSON block:
1505
+ {"element": "<name as listed>", "status": "done" | "blocked", "reason": "…"}.
1506
+ outputs:
1507
+ - { name: fe_more_files, from: "$.files" }
1508
+ - { name: fe_elements_more, from: "$.elements" }
1509
+ - id: fe_coverage_recheck
1510
+ condition: "{{fe_completion_ran}} == true"
1511
+ action: execute_script
1512
+ script: { ref: actions }
1513
+ subcommand: plan-coverage
1514
+ args: ["--plan", "{{fe_plan}}", "--kind", "frontend", "--mode", "{{fe_mode}}", "--source-path", "{{fe_source_path}}", "--test-path", "{{fe_test_path}}", "--reported", "{{fe_elements}}", "--reported", "{{fe_elements_more}}"]
1515
+ outputs:
1516
+ - { name: fe_complete, from: "$.ok" }
1517
+ - { name: fe_outcome, from: "$.outcome" }
1518
+ - { name: fe_gap_names, from: "$.gap_names_csv" }
1519
+ - { name: fe_gaps_text, from: "$.gaps_text" }
1067
1520
 
1068
1521
  - id: xref_concept_frontend
1069
1522
  condition: "{{fe_go}} == true"
@@ -1074,6 +1527,34 @@ workflow:
1074
1527
  args: ["--feature-dir", "{{feature_dir}}", "--source-files", "{{fe_files}}", "--merge", "--origin", "greenfield"]
1075
1528
  outputs:
1076
1529
  - { name: okf_concept, from: "$.concept" }
1530
+ - id: xref_concept_frontend_more
1531
+ condition: "{{fe_completion_ran}} == true"
1532
+ action: execute_script
1533
+ on_error: continue
1534
+ script: { ref: xref }
1535
+ subcommand: concept-upsert
1536
+ args: ["--feature-dir", "{{feature_dir}}", "--source-files", "{{fe_more_files}}", "--merge", "--origin", "greenfield"]
1537
+ outputs:
1538
+ - { name: okf_concept, from: "$.concept" }
1539
+
1540
+ # 10b. A LANE LEFT INCOMPLETE STOPS THE RUN — after both lanes have generated and every
1541
+ # file written is recorded in the OKF concept, and before the edges, the Overview
1542
+ # and the after-hooks, which all assume a finished slice. Re-running implement
1543
+ # finishes the job: shared files are extended, and a file that exists is not
1544
+ # regenerated.
1545
+ - id: lanes_incomplete_be
1546
+ condition: "{{be_complete}} == false"
1547
+ action: set
1548
+ vars: { lanes_complete: "false" }
1549
+ - id: lanes_incomplete_fe
1550
+ condition: "{{fe_complete}} == false"
1551
+ action: set
1552
+ vars: { lanes_complete: "false" }
1553
+ - id: require_complete_lanes
1554
+ action: verify_context
1555
+ require_true: lanes_complete
1556
+ on_false: stop
1557
+ on_false_message: "PARTIAL GENERATION — implement did not realise every plan element, even after one completion pass. Backend lane: {{be_gaps_text}}. Frontend lane: {{fe_gaps_text}}. Everything written so far is kept and recorded in the OKF concept. An element reported `blocked` names what stops it — a plan that is wrong is fixed with /appweaver plan (then analyze-plan or analyze-plan-delta). An element with no code under the lane's source path is still to be written, or was written under a name that is not the plan's: re-run /appweaver implement to finish, which extends existing files rather than regenerating them."
1077
1558
 
1078
1559
  # 11. CROSS-SLICE EDGES — a Cross-References row written into every SIBLING aggregate
1079
1560
  # this one uses, read off the master backend plan (§1 References-by-id + its External
@@ -1181,15 +1662,34 @@ outputs:
1181
1662
  frontend_implemented: "{{fe_go}}"
1182
1663
  frontend_plan: "{{fe_plan}}" # the document the frontend lane generated FROM
1183
1664
  frontend_files: "{{fe_files}}"
1665
+ # Every plan element realised, per lane — `true` for a lane that did not generate.
1666
+ # `*_completion_pass` says whether the first pass left gaps a second one had to close.
1667
+ # `*_outcome` is the one word for what the lane did: complete (it generated and every
1668
+ # element is realised), already-implemented (it wrote nothing because nothing was left
1669
+ # to write), gaps, or no-plan-elements. `*_satisfied` is that second case alone.
1670
+ backend_complete: "{{be_complete}}"
1671
+ backend_outcome: "{{be_outcome}}"
1672
+ backend_satisfied: "{{be_satisfied}}"
1673
+ backend_coverage: "{{be_coverage_note}}"
1674
+ backend_completion_pass: "{{be_completion_ran}}"
1675
+ backend_more_files: "{{be_more_files}}"
1676
+ frontend_complete: "{{fe_complete}}"
1677
+ frontend_outcome: "{{fe_outcome}}"
1678
+ frontend_satisfied: "{{fe_satisfied}}"
1679
+ frontend_coverage: "{{fe_coverage_note}}"
1680
+ frontend_completion_pass: "{{fe_completion_ran}}"
1681
+ frontend_more_files: "{{fe_more_files}}"
1184
1682
  domain_model: "{{domain_model}}" # the master model every generating lane read
1185
1683
  delta_model: "{{delta_model}}" # its second half on the delta path — empty on the bootstrap path
1186
1684
  okf_concept: "{{okf_concept}}"
1187
1685
  xref_edge_count: "{{xref_edge_count}}"
1188
- # Bootstrap path only. tracked=false means this feature has never passed analyze-plan
1189
- # since the gate began leaving a receipt — an unknown, not a stale plan, and it blocks
1190
- # nothing.
1686
+ # Bootstrap path only. tracked=false means this feature has not been through a master
1687
+ # analyze-plan run since the gate began leaving a receipt — an unknown, not a stale plan,
1688
+ # and it blocks nothing.
1191
1689
  plan_gate_tracked: "{{gate_tracked}}"
1192
1690
  plan_gate_fresh: "{{gate_fresh}}"
1193
1691
  plan_gate_at: "{{gate_at}}"
1194
- note: "Lanes come from .appweaver/stacks.json — backend declared {{be_declared}} (ready {{be_runnable}}, implemented {{be_go}}), frontend declared {{fe_declared}} (ready {{fe_runnable}}, implemented {{fe_go}}). A declared lane that was skipped printed its reason above and did not stop the other one; an undeclared lane produces nothing and that is not a failure. Each generating lane read its PLAN for what to build and {{domain_model}} for what the domain is — plus {{delta_model}} on the delta path, since the master model does not carry the change until finalize-delta merges it. Every one of those documents was READ and none was written: this phase generates code and nothing else. On the bootstrap path the plans were checked against analyze-plan's receipt before anything generated (tracked={{gate_tracked}}, fresh={{gate_fresh}}, gated {{gate_at}}) — tracked=false means this feature has never passed analyze-plan since the gate began recording what it proved, which is an unknown rather than a stale plan and blocks nothing; run /appweaver analyze-plan once to start the record."
1692
+ plan_gate_verdict: "{{gate_verdict}}"
1693
+ plan_gate_routed_to_specify: "{{gate_routed_csv}}" # open spec/model gaps this code does not cover
1694
+ note: "Lanes come from .appweaver/stacks.json — backend declared {{be_declared}} (ready {{be_runnable}}, implemented {{be_go}}), frontend declared {{fe_declared}} (ready {{fe_runnable}}, implemented {{fe_go}}). A declared lane that was skipped printed its reason above and did not stop the other one; an undeclared lane produces nothing and that is not a failure. Each generating lane read its PLAN for what to build and {{domain_model}} for what the domain is — plus {{delta_model}} on the delta path, since the master model does not carry the change until finalize-delta merges it. Every one of those documents was READ and none was written: this phase generates code and nothing else. Each lane's outcome is backend={{be_outcome}}, frontend={{fe_outcome}} — `already-implemented` means the lane wrote no files because every plan element was already realised in its source path, which is the correct result of re-running implement on a finished feature and not a failure. On the bootstrap path the plans were checked against analyze-plan's receipt before anything generated (tracked={{gate_tracked}}, fresh={{gate_fresh}}, gated {{gate_at}}) — tracked=false means this feature has never passed analyze-plan since the gate began recording what it proved, which is an unknown rather than a stale plan and blocks nothing; run /appweaver analyze-plan once to start the record."
1195
1695
  next: "This run was mode `{{mode}}`, change `{{active_change}}` (empty on the bootstrap path). BOOTSTRAP → /appweaver build → analyze-slice → run. DELTA → /appweaver build → analyze-slice-delta {{active_change}} → finalize-delta → analyze-slice → close-delta, with the change still OPEN throughout — compile first, then analyze-slice-delta verifies that every element the delta plans marked ADDED or MODIFIED is actually in the code with a paired test (the gate that catches a lane you declined at the prompt above), then finalize-delta merges it into spec.md + the context's domain-model.md while leaving it open, analyze-slice verifies the slice against those merged masters while a red result is still recoverable, and close-delta LAST archives the change and bumps the version."