aw-spec-cli 0.1.21 → 0.1.23
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.
- package/assets/.appweaver/STACK-PROFILES.md +87 -8
- package/assets/.appweaver/orchestrator/modules/delta-plan.orch.yaml +30 -0
- package/assets/.appweaver/orchestrator/orchestrator.py +47 -3
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +41 -8
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +62 -2
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +18 -5
- package/assets/.appweaver/orchestrator/workflows/aw-spec-constitution.orch.yaml +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-dependency-tree.orch.yaml +4 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +115 -27
- package/assets/.appweaver/orchestrator/workflows/aw-spec-foundation.orch.yaml +5 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +225 -27
- package/assets/.appweaver/orchestrator/workflows/aw-spec-inventory.orch.yaml +4 -15
- package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +39 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-recall-delta.orch.yaml +28 -7
- package/assets/.appweaver/orchestrator/workflows/aw-spec-select-domain.orch.yaml +3 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +104 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-ui-model.orch.yaml +159 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-ui-prototype.orch.yaml +386 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-workflow-model.orch.yaml +153 -0
- package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +203 -212
- package/assets/.appweaver/scripts/bash/aw-xref.sh +4 -1
- package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +191 -224
- package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +4 -1
- package/assets/.appweaver/scripts/python/aw-crstate.py +127 -2
- package/assets/.appweaver/scripts/python/aw-gaps.py +119 -0
- package/assets/.appweaver/scripts/python/aw-payload-parity.py +17 -1
- package/assets/.appweaver/scripts/python/aw-plan-coverage.py +84 -4
- package/assets/.appweaver/scripts/python/aw-plangate.py +216 -0
- package/assets/.appweaver/scripts/python/aw-principles.py +108 -0
- package/assets/.appweaver/scripts/python/aw-stack.py +6 -0
- package/assets/.appweaver/scripts/python/aw-xref.py +141 -4
- package/assets/.appweaver/stack-profiles/angular.json +26 -0
- package/assets/.appweaver/stack-profiles/blazor-wasm.json +38 -0
- package/assets/.appweaver/stack-profiles/dotnet-webapi.json +38 -0
- package/assets/.appweaver/stack-profiles/java-spring.json +19 -0
- package/assets/.appweaver/stack-profiles/nextjs.json +26 -0
- package/assets/.appweaver/stack-profiles/node-express.json +26 -0
- package/assets/.appweaver/stack-profiles/python-fastapi.json +15 -0
- package/assets/.appweaver/stack-profiles/react-vite.json +26 -0
- package/assets/.appweaver/stack-profiles/sveltekit.json +26 -0
- package/assets/.appweaver/stack-profiles/vue-vite.json +26 -0
- package/assets/.appweaver/templates/ui-model-template.md +67 -0
- package/assets/.appweaver/templates/workflow-model-template.md +62 -0
- package/assets/foundations/dotnet/ModularMonolith/ModularMonolith.pack.txt +3 -3
- package/assets/foundations/dotnet/VerticalSlice/VerticalSlice.pack.txt +5 -123
- package/assets/foundations/java/Hexagonal/Hexagonal.pack.txt +784 -0
- package/assets/foundations/java/Hexagonal/pack-foundation.sh +357 -0
- package/assets/foundations/java/Hexagonal/unpack-foundation.sh +399 -0
- package/assets/guardrails/technology/dotnet-webapi/framework-api-map.md +44 -0
- 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.
|
|
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,
|
|
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,
|
|
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
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
# PARTIAL GENERATION. A non-empty file list is not a finished lane: a pass that
|
|
972
|
-
# part-way returns the files it did write.
|
|
973
|
-
# ways — the
|
|
974
|
-
#
|
|
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
|
-
|
|
1270
|
-
|
|
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."
|
|
@@ -264,18 +264,7 @@ outputs:
|
|
|
264
264
|
coverage_pct: "{{coverage_pct}}"
|
|
265
265
|
split_candidates: "{{split_candidates}}"
|
|
266
266
|
contexts_ordered: "{{contexts_ordered}}"
|
|
267
|
-
|
|
268
|
-
next:
|
|
269
|
-
|
|
270
|
-
proposed from `{{chosen_strategy}}` evidence ({{coverage_pct}}% of source
|
|
271
|
-
covered), ordered into {{wave_count}} dependency wave(s).
|
|
272
|
-
|
|
273
|
-
READ AND EDIT specs/proposed-inventory.md — it is the boundary map, and it is
|
|
274
|
-
yours. Rename features and contexts, move a feature to another context, merge
|
|
275
|
-
two by giving them one name, split one by adding a row with the same Aggregate,
|
|
276
|
-
and set Depends On to change the order. {{split_candidates}} split(s) were
|
|
277
|
-
PROPOSED with their evidence and none were applied.
|
|
278
|
-
|
|
279
|
-
Then run `/appweaver inventory apply` to validate those edits and regenerate the
|
|
280
|
-
machine map, and onboard the contexts one at a time in the order it reports:
|
|
281
|
-
{{contexts_ordered}}
|
|
267
|
+
# Sits INSIDE outputs: — the engine resolves and returns wf["outputs"] only, so a
|
|
268
|
+
# next: at the document's top level is parsed and then dropped, and the workflow
|
|
269
|
+
# ends with no suggested successor at all.
|
|
270
|
+
next: "{{feature_count}} feature(s) across {{context_count}} bounded context(s) were proposed from `{{chosen_strategy}}` evidence ({{coverage_pct}}% of source covered), ordered into {{wave_count}} dependency wave(s). READ AND EDIT specs/proposed-inventory.md — it is the boundary map, and it is yours. Rename features and contexts, move a feature to another context, merge two by giving them one name, split one by adding a row with the same Aggregate, and set Depends On to change the order. {{split_candidates}} split(s) were PROPOSED with their evidence and none were applied. Then run /appweaver inventory apply to validate those edits and regenerate the machine map. Next: /appweaver onboard <Context> — once per bounded context, one at a time, in the order inventory apply reports: {{contexts_ordered}}. Then /appweaver adopt <Feature> per feature you are about to change."
|
|
@@ -2210,4 +2210,4 @@ outputs:
|
|
|
2210
2210
|
ui_delta_design_template_files: "{{ui_delta_design_template_files}}"
|
|
2211
2211
|
technical_validation_ok: "{{technical_ok}}"
|
|
2212
2212
|
domain_validation_ok: "{{domain_ok}}"
|
|
2213
|
-
next: "Onboarded bounded context {{bc}} ({{bc_feature_count}} feature(s): {{bc_features}}). Its aggregate concepts ({{bc_aggregates}}) are in {{bundle_dir}}, each keyed `bounded_context: {{bc}}` and listing the features that act on it — the bundle is the project's knowledge, one concept per aggregate, never one per feature — and its specs tree now exists at {{bc_dir}}/ with a directory per feature ({{feature_dirs}}) — EMPTY on purpose: the directory says the feature is known, and the documents that describe it are written per feature by /appweaver adopt and per context by /appweaver specify. {{bc}} is now the ACTIVE bounded context (.appweaver/context.json), so /appweaver select-feature lists exactly these features and nothing from a neighbouring context; switch with /appweaver select-bounded-context. ONBOARD RUNS ONCE PER CONTEXT: contexts already done — {{onboarded_contexts}}; still open — {{remaining_contexts}}; the order the map gives is {{contexts_ordered}}. Run /appweaver onboard <NextContext> for each remaining one, or with no argument to take the next one automatically. THE PROJECT TIER IS WRITTEN ONCE, BY THE FIRST CONTEXT (this run was first: {{is_first}}). That tier is the PROVISIONAL governance scaffold — the split constitution, the domain rule set at guardrails/projects/{{domain_slug}}/domain/*.domain.md + domain-rule-bindings.md, each declared LANE\'s rule set and constitution half, the activated domain {{active_domain}}, and the code-derived design / delta-design / domain-model template overrides in .appweaver/templates/overrides/. A later context does NOT re-author any of it: re-deriving project principles from one context\'s evidence is the scoping bias the governance tier exists to avoid. Amend it with /appweaver constitution. ONE HALF AND ONE RULE SET PER LANE: service_lane / ui_lane report which lanes .appweaver/stacks.json declared, and the declaration is what decided. A lane declared (none) in select-stack gets none of it, which is the correct outcome and not an omission. LANE DECLARATION: read, never written — select-stack is its sole writer, and this run scanned the source paths it named (scanned_service_root / scanned_ui_root); scanned_backend_files / scanned_ui_files report each. ui_source_evidence_found false means the UI lane\'s declared path held nothing a scan recognises as UI, so its governance is thin — move the source path with /appweaver select-stack and re-run. The build/test/run commands on each lane are that PROFILE\'s defaults and may not match a legacy layout — review them. Because the domain is activated, resolve-domain + resolve-tech route the WHOLE forward pipeline to these code-derived rules — never another project\'s rules as a fallback. THE BOUNDARIES CAME FROM THE MAP, not from this run: /appweaver inventory proposed them and you confirmed them in specs/proposed-inventory.md. To change a boundary, edit that file and run /appweaver inventory apply — a context whose concepts already exist is not re-onboarded. Develop an EXISTING feature just-in-time: /appweaver adopt <Feature> reverse-engineers its spec.md +
|
|
2213
|
+
next: "Onboarded bounded context {{bc}} ({{bc_feature_count}} feature(s): {{bc_features}}). Its aggregate concepts ({{bc_aggregates}}) are in {{bundle_dir}}, each keyed `bounded_context: {{bc}}` and listing the features that act on it — the bundle is the project's knowledge, one concept per aggregate, never one per feature — and its specs tree now exists at {{bc_dir}}/ with a directory per feature ({{feature_dirs}}) — EMPTY on purpose: the directory says the feature is known, and the documents that describe it are written per feature by /appweaver adopt and per context by /appweaver specify. {{bc}} is now the ACTIVE bounded context (.appweaver/context.json), so /appweaver select-feature lists exactly these features and nothing from a neighbouring context; switch with /appweaver select-bounded-context. ONBOARD RUNS ONCE PER CONTEXT: contexts already done — {{onboarded_contexts}}; still open — {{remaining_contexts}}; the order the map gives is {{contexts_ordered}}. Run /appweaver onboard <NextContext> for each remaining one, or with no argument to take the next one automatically. THE PROJECT TIER IS WRITTEN ONCE, BY THE FIRST CONTEXT (this run was first: {{is_first}}). That tier is the PROVISIONAL governance scaffold — the split constitution, the domain rule set at guardrails/projects/{{domain_slug}}/domain/*.domain.md + domain-rule-bindings.md, each declared LANE\'s rule set and constitution half, the activated domain {{active_domain}}, and the code-derived design / delta-design / domain-model template overrides in .appweaver/templates/overrides/. A later context does NOT re-author any of it: re-deriving project principles from one context\'s evidence is the scoping bias the governance tier exists to avoid. Amend it with /appweaver constitution. ONE HALF AND ONE RULE SET PER LANE: service_lane / ui_lane report which lanes .appweaver/stacks.json declared, and the declaration is what decided. A lane declared (none) in select-stack gets none of it, which is the correct outcome and not an omission. LANE DECLARATION: read, never written — select-stack is its sole writer, and this run scanned the source paths it named (scanned_service_root / scanned_ui_root); scanned_backend_files / scanned_ui_files report each. ui_source_evidence_found false means the UI lane\'s declared path held nothing a scan recognises as UI, so its governance is thin — move the source path with /appweaver select-stack and re-run. The build/test/run commands on each lane are that PROFILE\'s defaults and may not match a legacy layout — review them. Because the domain is activated, resolve-domain + resolve-tech route the WHOLE forward pipeline to these code-derived rules — never another project\'s rules as a fallback. THE BOUNDARIES CAME FROM THE MAP, not from this run: /appweaver inventory proposed them and you confirmed them in specs/proposed-inventory.md. To change a boundary, edit that file and run /appweaver inventory apply — a context whose concepts already exist is not re-onboarded. Develop an EXISTING feature just-in-time: /appweaver adopt <Feature> reverse-engineers its spec.md + backend-plan.md + source-map.md and deepens the OKF concept of the aggregate it acts on. Develop a NEW feature straight through the forward pipeline — /appweaver select-feature FIRST (specify and every stage after it read the active feature and STOP without one), then specify → plan → analyze-plan → implement → build → analyze-slice → run. Then drive every change through the CR delta loop (/appweaver specify → plan → analyze-plan-delta → implement → build → analyze-slice-delta → finalize-delta → analyze-slice → close-delta). If a validation_ok is false, its constitution half needs a manual touch (see its Sync Impact Report)."
|
|
@@ -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
|
-
#
|
|
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
|
|
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,
|
|
182
|
-
- { name: merge_evidence,
|
|
183
|
-
- { name: has_dependants,
|
|
184
|
-
- { name: dependant_ids,
|
|
185
|
-
- { name: 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
|
|
@@ -80,4 +80,6 @@ outputs:
|
|
|
80
80
|
domain_rule_bindings: "{{map_path}}"
|
|
81
81
|
domain_rule_bindings_exists: "{{map_exists}}"
|
|
82
82
|
note: "Active project '{{domain}}' persisted to .appweaver/domain.json. It selects TWO rule maps under guardrails/projects/{{domain}}/ — domain-rule-bindings.md (DOMAIN rules; read by constitution + specify only, because the spec is the pivot) and tech-rule-bindings.md (TECHNICAL rules; read by 10 workflows incl. design, implement, build and run). The rule FILES themselves live in shared corpora — guardrails/technology/<stack>/ and guardrails/domain/<industry>/ — and the maps are what bind this project to them, so two projects can share a corpus and differ only in their maps and their own three requirements files. domain_rule_bindings_exists reports the domain half only. A map that is absent, or a command with no heading in one, loads nothing for that class and SOFT-CONTINUES — an absent heading is a valid state, never a failure."
|
|
83
|
-
|
|
83
|
+
# constitution comes BEFORE select-feature: it reads the domain rule map this
|
|
84
|
+
# workflow just bound, and specify declares its domain half as REQUIRED context.
|
|
85
|
+
next: "/appweaver constitution → select-feature → specify. /appweaver constitution authors the constitution halves from the domain rule map this run just bound — specify declares constitution.domain.md as REQUIRED context, and plan and implement read constitution.technical.md (plus constitution.ui.md on a frontend lane), so it runs before the first feature rather than after. /appweaver select-feature then picks the feature to build; every stage from specify onward reads that active feature and stops without it. Re-run /appweaver select-domain any time to switch projects; it rebinds both maps at once, and /appweaver constitution picks the new bindings up on its next run."
|