aw-spec-cli 0.1.21 → 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 (39) hide show
  1. package/assets/.appweaver/STACK-PROFILES.md +87 -8
  2. package/assets/.appweaver/orchestrator/modules/delta-plan.orch.yaml +30 -0
  3. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +41 -8
  4. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +62 -2
  5. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +18 -5
  6. package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +115 -27
  7. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +225 -27
  8. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +39 -0
  9. package/assets/.appweaver/orchestrator/workflows/aw-spec-recall-delta.orch.yaml +28 -7
  10. package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +100 -0
  11. package/assets/.appweaver/orchestrator/workflows/aw-spec-ui-model.orch.yaml +141 -0
  12. package/assets/.appweaver/orchestrator/workflows/aw-spec-ui-prototype.orch.yaml +386 -0
  13. package/assets/.appweaver/orchestrator/workflows/aw-spec-workflow-model.orch.yaml +135 -0
  14. package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +190 -209
  15. package/assets/.appweaver/scripts/bash/aw-xref.sh +4 -1
  16. package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +182 -224
  17. package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +4 -1
  18. package/assets/.appweaver/scripts/python/aw-crstate.py +127 -2
  19. package/assets/.appweaver/scripts/python/aw-gaps.py +119 -0
  20. package/assets/.appweaver/scripts/python/aw-payload-parity.py +17 -1
  21. package/assets/.appweaver/scripts/python/aw-plan-coverage.py +84 -4
  22. package/assets/.appweaver/scripts/python/aw-plangate.py +216 -0
  23. package/assets/.appweaver/scripts/python/aw-principles.py +108 -0
  24. package/assets/.appweaver/scripts/python/aw-stack.py +6 -0
  25. package/assets/.appweaver/scripts/python/aw-xref.py +141 -4
  26. package/assets/.appweaver/stack-profiles/angular.json +26 -0
  27. package/assets/.appweaver/stack-profiles/blazor-wasm.json +38 -0
  28. package/assets/.appweaver/stack-profiles/dotnet-webapi.json +38 -0
  29. package/assets/.appweaver/stack-profiles/java-spring.json +19 -0
  30. package/assets/.appweaver/stack-profiles/nextjs.json +26 -0
  31. package/assets/.appweaver/stack-profiles/node-express.json +26 -0
  32. package/assets/.appweaver/stack-profiles/python-fastapi.json +15 -0
  33. package/assets/.appweaver/stack-profiles/react-vite.json +26 -0
  34. package/assets/.appweaver/stack-profiles/sveltekit.json +26 -0
  35. package/assets/.appweaver/stack-profiles/vue-vite.json +26 -0
  36. package/assets/.appweaver/templates/ui-model-template.md +67 -0
  37. package/assets/.appweaver/templates/workflow-model-template.md +62 -0
  38. package/assets/guardrails/technology/dotnet-webapi/framework-api-map.md +44 -0
  39. package/package.json +1 -1
@@ -128,7 +128,7 @@
128
128
 
129
129
  name: aw-spec-implement
130
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."
131
- version: 3.7.0
131
+ version: 3.10.0
132
132
  agent_agnostic: true
133
133
  dialect: aw-spec.orchestrator/v1
134
134
 
@@ -207,10 +207,18 @@ vars:
207
207
  be_files: []
208
208
  # COMPLETENESS, per lane — see step 8b and the checks after each lane generates.
209
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"
210
215
  be_elements: []
211
216
  be_more_files: []
212
217
  be_elements_more: []
213
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: ""
214
222
  be_completion_ran: "false"
215
223
  be_gap_names: ""
216
224
  be_gaps_text: "none"
@@ -224,10 +232,18 @@ vars:
224
232
  fe_mode: "skip" # skip | bootstrap | delta
225
233
  fe_files: []
226
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"
227
240
  fe_elements: []
228
241
  fe_more_files: []
229
242
  fe_elements_more: []
230
243
  fe_complete: "true"
244
+ fe_outcome: ""
245
+ fe_satisfied: "false"
246
+ fe_coverage_note: ""
231
247
  fe_completion_ran: "false"
232
248
  fe_gap_names: ""
233
249
  fe_gaps_text: "none"
@@ -287,6 +303,7 @@ workflow:
287
303
  - { name: be_ecosystem, from: "$.backend_ecosystem" }
288
304
  - { name: be_build, from: "$.backend_build" }
289
305
  - { name: be_test, from: "$.backend_test" }
306
+ - { name: be_reserved, from: "$.backend_reserved_csv" }
290
307
  - { name: fe_declared, from: "$.frontend_declared" }
291
308
  - { name: fe_lane_id, from: "$.frontend_lane_id" }
292
309
  - { name: fe_source_path, from: "$.frontend_source_path" }
@@ -298,6 +315,7 @@ workflow:
298
315
  - { name: fe_ecosystem, from: "$.frontend_ecosystem" }
299
316
  - { name: fe_build, from: "$.frontend_build" }
300
317
  - { name: fe_test, from: "$.frontend_test" }
318
+ - { name: fe_reserved, from: "$.frontend_reserved_csv" }
301
319
 
302
320
  - id: require_lanes
303
321
  action: verify_context
@@ -680,17 +698,60 @@ workflow:
680
698
  action: execute_script
681
699
  script: { ref: actions }
682
700
  subcommand: plan-coverage
683
- args: ["--plan", "{{be_plan}}", "--kind", "backend", "--mode", "{{be_mode}}", "--list-only"]
701
+ args: ["--plan", "{{be_plan}}", "--kind", "backend", "--mode", "{{be_mode}}", "--list-only", "--reserved", "{{be_reserved}}"]
684
702
  outputs:
685
- - { name: be_element_names, from: "$.names_text" }
703
+ - { name: be_element_names, from: "$.names_text" }
704
+ - { name: be_collisions, from: "$.collisions_csv" }
705
+ - { name: be_collisions_text, from: "$.collisions_text" }
686
706
  - id: fe_plan_elements
687
707
  condition: "{{fe_go}} == true"
688
708
  action: execute_script
689
709
  script: { ref: actions }
690
710
  subcommand: plan-coverage
691
- args: ["--plan", "{{fe_plan}}", "--kind", "frontend", "--mode", "{{fe_mode}}", "--list-only"]
711
+ args: ["--plan", "{{fe_plan}}", "--kind", "frontend", "--mode", "{{fe_mode}}", "--list-only", "--reserved", "{{fe_reserved}}"]
692
712
  outputs:
693
- - { name: fe_element_names, from: "$.names_text" }
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" }
694
755
 
695
756
  # 9. Each generating lane's code-generation rule files → sticky standards (configuration,
696
757
  # not hardcoded). Loaded only for a lane that is actually going to generate, and the
@@ -782,6 +843,30 @@ workflow:
782
843
  not even to consume a contract. Do not create a parallel source tree because the
783
844
  layout inside them is unfamiliar; if the plan needs a place they have no home for,
784
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.
785
870
  - A MISSING TEST PROJECT IS CREATED, NEVER A REASON TO SKIP THE TESTS. When
786
871
  `{{be_test_path}}` holds no test project yet, create one there as the loaded
787
872
  testing standard describes, referencing the production project it tests, and
@@ -918,6 +1003,30 @@ workflow:
918
1003
  paired tests under `{{be_test_path}}`. Those subtrees are this lane's, and a file
919
1004
  outside them belongs to another lane or to no lane at all. NEVER write into the
920
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.
921
1030
  - A MISSING TEST PROJECT IS CREATED, NEVER A REASON TO SKIP THE TESTS. When
922
1031
  `{{be_test_path}}` holds no test project yet, create one there as the loaded
923
1032
  testing standard describes and register it wherever `{{be_test}}` discovers tests
@@ -961,17 +1070,17 @@ workflow:
961
1070
  - { name: be_files, from: "$.files" }
962
1071
  - { name: be_elements, from: "$.elements" }
963
1072
 
964
- - id: verify_be_files
965
- condition: "{{be_go}} == true"
966
- action: verify_context
967
- require_non_empty: be_files
968
- on_empty: stop
969
- 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."
970
-
971
- # PARTIAL GENERATION. A non-empty file list is not a finished lane: a pass that stopped
972
- # part-way returns the files it did write. plan-coverage checks every plan element two
973
- # ways — the lane's code must name each identifier the plan introduces, and the model's
974
- # own report must say each element is done — and whatever fails either check gets ONE
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
975
1084
  # completion pass in the same envelope. What is still missing after it stops the run at
976
1085
  # step 10b, once both lanes have had their turn.
977
1086
  - id: be_coverage
@@ -979,11 +1088,33 @@ workflow:
979
1088
  action: execute_script
980
1089
  script: { ref: actions }
981
1090
  subcommand: plan-coverage
982
- args: ["--plan", "{{be_plan}}", "--kind", "backend", "--mode", "{{be_mode}}", "--source-path", "{{be_source_path}}", "--test-path", "{{be_test_path}}", "--reported", "{{be_elements}}"]
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}}"]
983
1092
  outputs:
984
1093
  - { name: be_complete, from: "$.ok" }
1094
+ - { name: be_outcome, from: "$.outcome" }
985
1095
  - { name: be_gap_names, from: "$.gap_names_csv" }
986
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"
1114
+ action: verify_context
1115
+ require_non_empty: be_files
1116
+ on_empty: stop
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."
987
1118
  - id: be_completion_flag
988
1119
  condition: "{{be_complete}} == false"
989
1120
  action: set
@@ -1025,6 +1156,7 @@ workflow:
1025
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}}"]
1026
1157
  outputs:
1027
1158
  - { name: be_complete, from: "$.ok" }
1159
+ - { name: be_outcome, from: "$.outcome" }
1028
1160
  - { name: be_gap_names, from: "$.gap_names_csv" }
1029
1161
  - { name: be_gaps_text, from: "$.gaps_text" }
1030
1162
 
@@ -1124,6 +1256,30 @@ workflow:
1124
1256
  gap in the service surface is a contract delta raised back to that lane. Do not
1125
1257
  create a parallel source tree because the layout inside this one is unfamiliar; if
1126
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.
1127
1283
  - A MISSING TEST SETUP IS CREATED, NEVER A REASON TO SKIP THE TESTS. When the lane
1128
1284
  has no test project or test runner yet, set one up inside the lane's paths as the
1129
1285
  loaded testing standard describes, so that `{{fe_test}}` runs the tests you write.
@@ -1233,6 +1389,30 @@ workflow:
1233
1389
  paired tests under `{{fe_test_path}}`. NEVER touch the service lane's code, not
1234
1390
  even to add a field the UI wants — a gap in the service surface is a contract delta
1235
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.
1236
1416
  - A MISSING TEST SETUP IS CREATED, NEVER A REASON TO SKIP THE TESTS: set one up
1237
1417
  inside the lane's paths so that `{{fe_test}}` runs the tests you write, and name
1238
1418
  any file outside them you had to edit for that in your summary.
@@ -1266,24 +1446,32 @@ workflow:
1266
1446
  - { name: fe_files, from: "$.files" }
1267
1447
  - { name: fe_elements, from: "$.elements" }
1268
1448
 
1269
- - id: verify_fe_files
1270
- condition: "{{fe_go}} == true"
1271
- action: verify_context
1272
- require_non_empty: fe_files
1273
- on_empty: stop
1274
- 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."
1275
-
1276
- # PARTIAL GENERATION — the frontend twin of the backend check above.
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.
1277
1451
  - id: fe_coverage
1278
1452
  condition: "{{fe_go}} == true"
1279
1453
  action: execute_script
1280
1454
  script: { ref: actions }
1281
1455
  subcommand: plan-coverage
1282
- args: ["--plan", "{{fe_plan}}", "--kind", "frontend", "--mode", "{{fe_mode}}", "--source-path", "{{fe_source_path}}", "--test-path", "{{fe_test_path}}", "--reported", "{{fe_elements}}"]
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}}"]
1283
1457
  outputs:
1284
1458
  - { name: fe_complete, from: "$.ok" }
1459
+ - { name: fe_outcome, from: "$.outcome" }
1285
1460
  - { name: fe_gap_names, from: "$.gap_names_csv" }
1286
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"
1471
+ action: verify_context
1472
+ require_non_empty: fe_files
1473
+ on_empty: stop
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."
1287
1475
  - id: fe_completion_flag
1288
1476
  condition: "{{fe_complete}} == false"
1289
1477
  action: set
@@ -1326,6 +1514,7 @@ workflow:
1326
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}}"]
1327
1515
  outputs:
1328
1516
  - { name: fe_complete, from: "$.ok" }
1517
+ - { name: fe_outcome, from: "$.outcome" }
1329
1518
  - { name: fe_gap_names, from: "$.gap_names_csv" }
1330
1519
  - { name: fe_gaps_text, from: "$.gaps_text" }
1331
1520
 
@@ -1475,10 +1664,19 @@ outputs:
1475
1664
  frontend_files: "{{fe_files}}"
1476
1665
  # Every plan element realised, per lane — `true` for a lane that did not generate.
1477
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.
1478
1670
  backend_complete: "{{be_complete}}"
1671
+ backend_outcome: "{{be_outcome}}"
1672
+ backend_satisfied: "{{be_satisfied}}"
1673
+ backend_coverage: "{{be_coverage_note}}"
1479
1674
  backend_completion_pass: "{{be_completion_ran}}"
1480
1675
  backend_more_files: "{{be_more_files}}"
1481
1676
  frontend_complete: "{{fe_complete}}"
1677
+ frontend_outcome: "{{fe_outcome}}"
1678
+ frontend_satisfied: "{{fe_satisfied}}"
1679
+ frontend_coverage: "{{fe_coverage_note}}"
1482
1680
  frontend_completion_pass: "{{fe_completion_ran}}"
1483
1681
  frontend_more_files: "{{fe_more_files}}"
1484
1682
  domain_model: "{{domain_model}}" # the master model every generating lane read
@@ -1493,5 +1691,5 @@ outputs:
1493
1691
  plan_gate_at: "{{gate_at}}"
1494
1692
  plan_gate_verdict: "{{gate_verdict}}"
1495
1693
  plan_gate_routed_to_specify: "{{gate_routed_csv}}" # open spec/model gaps this code does not cover
1496
- 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."
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."
1497
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."
@@ -154,6 +154,10 @@ vars:
154
154
  # answer; master_exists is what makes the menu appear at all.
155
155
  master_exists: "false" # a DECLARED lane's master plan was already on disk
156
156
  plan_target: "main" # main | delta — the answer to the one question this phase asks
157
+ # The OKF edges this run publishes from the master service plan (main path only).
158
+ record_master_edges: "false"
159
+ xref_edge_count: "0"
160
+ xref_stubbed: "[]" # used siblings that had no concept yet and were stubbed
157
161
  plan_choice: "" # the raw selection (a label): the MAIN row, a change, or the NEW row
158
162
  main_label: "" # cr-list --include-main's exact label, to test the selection against
159
163
  new_label: "" # cr-list --include-new's
@@ -1069,6 +1073,41 @@ workflow:
1069
1073
  action: include
1070
1074
  module: .appweaver/orchestrator/modules/delta-plan.orch.yaml
1071
1075
 
1076
+ # 6b. CROSS-SLICE EDGES, from the document that states them. `aw-xref record` reads the
1077
+ # MASTER service plan — §1 References-by-id and the External integrations table — and
1078
+ # writes a Cross-References row into every sibling aggregate this one uses. Its input
1079
+ # is the plan and nothing else, yet it used to run only at implement: until a feature
1080
+ # was BUILT it contributed no edges, so `xref list` answered "0 downstream users" for
1081
+ # an aggregate a planned feature already depended on, and `xref tree` sorted the two
1082
+ # into the same wave. The information was in this document the whole time.
1083
+ #
1084
+ # MAIN PATH ONLY. On the delta path the master plan still holds the PRE-change
1085
+ # dependencies, so recording here would publish stale references; finalize-delta
1086
+ # records them after the change merges, which is the same reason implement skips it.
1087
+ # Reconcile-on-write, so re-recording at implement is idempotent and a dependency
1088
+ # dropped by a later re-derivation self-heals. Best-effort: never fails the authoring.
1089
+ - id: record_edges_clear
1090
+ action: set
1091
+ vars: { record_master_edges: "false" }
1092
+ - id: record_edges_main
1093
+ condition: "{{plan_target}} == main"
1094
+ action: set
1095
+ vars: { record_master_edges: "true" }
1096
+ - id: record_edges_no_lane
1097
+ condition: "{{be_mode}} == skip"
1098
+ action: set
1099
+ vars: { record_master_edges: "false" }
1100
+ - id: xref_record
1101
+ condition: "{{record_master_edges}} == true"
1102
+ action: execute_script
1103
+ on_error: continue
1104
+ script: { ref: xref }
1105
+ subcommand: record
1106
+ args: ["--feature-dir", "{{feature_dir}}", "--design", "{{backend_plan}}", "--origin", "auto"]
1107
+ outputs:
1108
+ - { name: xref_edge_count, from: "$.edge_count" }
1109
+ - { name: xref_stubbed, from: "$.stubbed" }
1110
+
1072
1111
  # ═══════════ HANDOFF — authoring only; the gates are SEPARATE stages ═══════
1073
1112
  # • MAIN (bootstrap or re-derivation) → /appweaver analyze-plan (spec↔plan
1074
1113
  # conformance gate) → implement. No change is opened either way.
@@ -95,10 +95,14 @@ vars:
95
95
  has_dependants: "false"
96
96
  dependant_ids: ""
97
97
  dependant_count: "0"
98
- # The two refusal flags, both DOWN by default and raised only by the good state — see
98
+ wave_problem: "" # which changes could not be ordered, and why that ends the check
99
+ unordered_ids: ""
100
+ unordered_count: "0"
101
+ # The three refusal flags, all DOWN by default and raised only by the good state — see
99
102
  # the gates below for why fail-closed is the right default here.
100
103
  not_merged: "false"
101
104
  no_dependants: "false"
105
+ wave_ordering_ok: "false"
102
106
  recall_confirmed: "false"
103
107
  recalled: "false"
104
108
  archived_dir: ""
@@ -170,7 +174,7 @@ workflow:
170
174
  - { name: delta_model, from: "$.delta_model" }
171
175
  - { name: wave, from: "$.wave" }
172
176
 
173
- # 4. THE TWO REFUSALS, both answered from disk in one call — see the header for why each
177
+ # 4. THE THREE REFUSALS, all answered from disk in one call — see the header for why each
174
178
  # one is a stop rather than a warning.
175
179
  - id: recall_check
176
180
  action: execute_script
@@ -178,11 +182,16 @@ workflow:
178
182
  subcommand: cr-recall-check
179
183
  args: ["--cr-id", "{{cr_choice}}", "--feature-dir", "{{feature_dir}}"]
180
184
  outputs:
181
- - { name: merged, from: "$.merged" }
182
- - { name: merge_evidence, from: "$.merge_evidence" }
183
- - { name: has_dependants, from: "$.has_dependants" }
184
- - { name: dependant_ids, from: "$.dependant_ids" }
185
- - { name: dependant_count, from: "$.dependant_count" }
185
+ - { name: merged, from: "$.merged" }
186
+ - { name: merge_evidence, from: "$.merge_evidence" }
187
+ - { name: has_dependants, from: "$.has_dependants" }
188
+ - { name: dependant_ids, from: "$.dependant_ids" }
189
+ - { name: dependant_count, from: "$.dependant_count" }
190
+ - { name: wave, from: "$.wave" }
191
+ - { name: wave_ordering_ok, from: "$.wave_ordering_ok" }
192
+ - { name: wave_problem, from: "$.wave_problem" }
193
+ - { name: unordered_ids, from: "$.unordered_ids" }
194
+ - { name: unordered_count, from: "$.unordered_count" }
186
195
 
187
196
  # Each refusal is a RAISE then a gate, not a conditional gate: the raise fires only on
188
197
  # the good state, so a value that never arrived (the script errored, the field was
@@ -199,6 +208,18 @@ workflow:
199
208
  on_false: stop
200
209
  on_false_message: "Change {{cr_id}} has already been MERGED — /appweaver finalize-delta folded it into spec.md and the context's domain-model.md, and the spec's Change History carries it: {{merge_evidence}}. The masters now describe behaviour that includes this change, so withdrawing it would leave them describing work no open change accounts for. A recall cannot un-merge, and this stage will not pretend otherwise. If the change should not have landed, finish it (/appweaver analyze-slice → close-delta) and then open a NEW change that reverses it with /appweaver specify — that keeps both the landing and the reversal in the ledger, which is what a reader of the spec needs to see."
201
210
 
211
+ # WAVE ORDERING IS A PRECONDITION OF THE FENCE BELOW, not a finding of it. The dependant
212
+ # test is `their wave > mine`, and a wave that cannot be read used to answer that as 1, so
213
+ # an unstamped change failed the test and was not counted — recall then withdrew the
214
+ # ground from under a change it could not see, which is the failure this stage exists to
215
+ # prevent. Waves have no ceiling, so unlike the merge fence there is no floor that makes
216
+ # an unordered neighbour harmless: every one of them holds recall until it is stamped.
217
+ - id: require_wave_ordering
218
+ action: verify_context
219
+ require_true: wave_ordering_ok
220
+ on_false: stop
221
+ on_false_message: "Recall REFUSED — the wave fence cannot order change {{cr_id}}, so it cannot tell you what was built on top of it: {{wave_problem}}. Withdrawing a change that something else was planned against is exactly what this stage refuses to do blind. A wave is stamped by a script; `**Wave**: [N]` is the unfilled template line, which means the stamp never ran on that document. Stamp each one — `.appweaver/scripts/bash/aw-spec-actions.sh cr-wave-derive --cr-id <CR-id> --stamp` derives the wave from that change's own `## Depends on` section and writes it — then re-run /appweaver recall-delta {{cr_id}}."
222
+
202
223
  - id: no_dependants_raise
203
224
  condition: "{{has_dependants}} == false"
204
225
  action: set