aw-spec-cli 0.1.26 → 0.1.28

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 (137) hide show
  1. package/assets/.agents/skills/appweaver/SKILL.md +7 -47
  2. package/assets/.appweaver/ORCH-DIALECT.md +13 -9
  3. package/assets/.appweaver/RULE-PARTITION.md +11 -12
  4. package/assets/.appweaver/STACK-PROFILES.md +260 -108
  5. package/assets/.appweaver/domain.json +1 -1
  6. package/assets/.appweaver/interpreter/run-skill.py +28 -7
  7. package/assets/.appweaver/orchestrator/AGENT-PROFILE.md +38 -10
  8. package/assets/.appweaver/orchestrator/LAUNCHER.md +88 -0
  9. package/assets/.appweaver/orchestrator/README.md +120 -53
  10. package/assets/.appweaver/orchestrator/adapters/claude_agent_sdk.py +1 -1
  11. package/assets/.appweaver/orchestrator/modules/delta-plan.orch.yaml +17 -7
  12. package/assets/.appweaver/orchestrator/modules/domain-select.orch.yaml +6 -4
  13. package/assets/.appweaver/orchestrator/modules/foundation-lane.orch.yaml +237 -0
  14. package/assets/.appweaver/orchestrator/modules/foundation-pack.orch.yaml +392 -0
  15. package/assets/.appweaver/orchestrator/modules/foundation-spec.orch.yaml +548 -0
  16. package/assets/.appweaver/orchestrator/modules/lane-gate.orch.yaml +4 -4
  17. package/assets/.appweaver/orchestrator/modules/tech-select.orch.yaml +3 -3
  18. package/assets/.appweaver/orchestrator/orchestrator.py +162 -63
  19. package/assets/.appweaver/orchestrator/rev-templates/delta-design-template.skeleton.md +5 -4
  20. package/assets/.appweaver/orchestrator/rev-templates/design-template.skeleton.md +3 -1
  21. package/assets/.appweaver/orchestrator/rev-templates/domain-constitution.skeleton.md +2 -2
  22. package/assets/.appweaver/orchestrator/rev-templates/domain-requirements.skeleton.md +2 -2
  23. package/assets/.appweaver/orchestrator/rev-templates/domain-rule-bindings.skeleton.md +21 -13
  24. package/assets/.appweaver/orchestrator/rev-templates/frontend-rule-file.skeleton.md +1 -1
  25. package/assets/.appweaver/orchestrator/rev-templates/tech-rule-bindings.skeleton.md +28 -31
  26. package/assets/.appweaver/orchestrator/rev-templates/technical-constitution.skeleton.md +2 -2
  27. package/assets/.appweaver/orchestrator/rev-templates/technical-rule-file.skeleton.md +1 -1
  28. package/assets/.appweaver/orchestrator/rev-templates/ui-constitution.skeleton.md +3 -3
  29. package/assets/.appweaver/orchestrator/rev-templates/ui-delta-design-template.skeleton.md +4 -1
  30. package/assets/.appweaver/orchestrator/rev-templates/ui-design-template.skeleton.md +6 -4
  31. package/assets/.appweaver/orchestrator/workflows/_smoke.orch.yaml +41 -3
  32. package/assets/.appweaver/orchestrator/workflows/aw-spec-add-component.orch.yaml +1 -1
  33. package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +17 -16
  34. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan-delta.orch.yaml +7 -1
  35. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +7 -1
  36. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +3 -3
  37. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +3 -3
  38. package/assets/.appweaver/orchestrator/workflows/aw-spec-build.orch.yaml +4 -4
  39. package/assets/.appweaver/orchestrator/workflows/aw-spec-characterize.orch.yaml +2 -2
  40. package/assets/.appweaver/orchestrator/workflows/aw-spec-constitution.orch.yaml +61 -12
  41. package/assets/.appweaver/orchestrator/workflows/aw-spec-deploy.orch.yaml +1 -1
  42. package/assets/.appweaver/orchestrator/workflows/aw-spec-foundation.orch.yaml +172 -293
  43. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +253 -44
  44. package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +72 -71
  45. package/assets/.appweaver/orchestrator/workflows/aw-spec-parity.orch.yaml +2 -2
  46. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +38 -14
  47. package/assets/.appweaver/orchestrator/workflows/aw-spec-run.orch.yaml +4 -4
  48. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-bounded-context.orch.yaml +26 -2
  49. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-domain.orch.yaml +21 -15
  50. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-stack.orch.yaml +14 -14
  51. package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +3 -3
  52. package/assets/.appweaver/orchestrator/workflows/aw-spec-ui-model.orch.yaml +3 -3
  53. package/assets/.appweaver/orchestrator/workflows/aw-spec-ui-prototype.orch.yaml +6 -6
  54. package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +371 -131
  55. package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +293 -76
  56. package/assets/.appweaver/scripts/python/aw-foundation.py +885 -20
  57. package/assets/.appweaver/scripts/python/aw-inventory.py +28 -21
  58. package/assets/.appweaver/scripts/python/aw-plan-coverage.py +2 -2
  59. package/assets/.appweaver/scripts/python/aw-rev-constitution.py +6 -6
  60. package/assets/.appweaver/scripts/python/aw-stack.py +676 -99
  61. package/assets/.appweaver/stack-profiles/angular.json +2 -1
  62. package/assets/.appweaver/stack-profiles/blazor-wasm.json +2 -1
  63. package/assets/.appweaver/stack-profiles/dotnet-webapi.json +18 -3
  64. package/assets/.appweaver/stack-profiles/go-chi.json +6 -1
  65. package/assets/.appweaver/stack-profiles/java-spring.json +14 -5
  66. package/assets/.appweaver/stack-profiles/nextjs.json +2 -1
  67. package/assets/.appweaver/stack-profiles/node-express.json +8 -2
  68. package/assets/.appweaver/stack-profiles/python-fastapi.json +9 -2
  69. package/assets/.appweaver/stack-profiles/react-vite.json +2 -1
  70. package/assets/.appweaver/stack-profiles/sveltekit.json +2 -1
  71. package/assets/.appweaver/stack-profiles/vue-vite.json +2 -1
  72. package/assets/.appweaver/stacks.json +40 -1
  73. package/assets/.appweaver/templates/constitution-ui-template.md +2 -2
  74. package/assets/.appweaver/templates/delta-design-template.md +7 -5
  75. package/assets/.appweaver/templates/design-template.md +3 -0
  76. package/assets/.appweaver/templates/ui-delta-design-template.md +1 -1
  77. package/assets/.appweaver/templates/ui-design-template.md +6 -4
  78. package/assets/.claude/skills/appweaver/SKILL.md +7 -46
  79. package/assets/foundations/dotnet/ModularMonolith/ModularMonolith.Reference.rules.json +31 -0
  80. package/assets/foundations/dotnet/ModularMonolith/ModularMonolith.lane.json +6 -0
  81. package/assets/foundations/dotnet/VerticalSlice/VerticalSlice.Reference.rules.json +16 -0
  82. package/assets/foundations/dotnet/VerticalSlice/VerticalSlice.lane.json +6 -0
  83. package/assets/foundations/foundation.spec.template.txt +98 -0
  84. package/assets/foundations/java/Hexagonal/Hexagonal.lane.json +6 -0
  85. package/assets/foundations/java/Hexagonal/Hexagonal.pack.txt +8 -1
  86. package/assets/foundations/java/Hexagonal/unpack-foundation.sh +4 -5
  87. package/assets/foundations/node/NextAppRouter/NextAppRouter.spec.txt +68 -0
  88. package/assets/guardrails/README.md +31 -17
  89. package/assets/guardrails/{projects/dotnet-default-project → project-specific}/domain-rule-bindings.md +25 -13
  90. package/assets/guardrails/project-specific/tech-rule-bindings.md +296 -0
  91. package/assets/guardrails/technology/dotnet-webapi/PACKAGING.md +58 -4
  92. package/assets/guardrails/technology/dotnet-webapi/api-versioning.md +1 -1
  93. package/assets/guardrails/technology/dotnet-webapi/auditing.md +1 -1
  94. package/assets/guardrails/technology/dotnet-webapi/authorization.md +1 -1
  95. package/assets/guardrails/technology/dotnet-webapi/clean-architecture.md +1 -1
  96. package/assets/guardrails/technology/dotnet-webapi/coding-standards.md +1 -1
  97. package/assets/guardrails/technology/dotnet-webapi/cqrs.md +1 -1
  98. package/assets/guardrails/technology/dotnet-webapi/data-classification.md +1 -1
  99. package/assets/guardrails/technology/dotnet-webapi/dependency-policy.md +1 -1
  100. package/assets/guardrails/technology/dotnet-webapi/eventing.md +1 -1
  101. package/assets/guardrails/technology/dotnet-webapi/folder-structure.md +1 -1
  102. package/assets/guardrails/technology/dotnet-webapi/framework-api-map.md +1 -1
  103. package/assets/guardrails/technology/dotnet-webapi/modular-monolith.md +1 -1
  104. package/assets/guardrails/technology/dotnet-webapi/persistence.md +1 -1
  105. package/assets/guardrails/technology/dotnet-webapi/testing.md +1 -1
  106. package/assets/guardrails/technology/dotnet-webapi/vertical-slices.md +1 -1
  107. package/assets/guardrails/technology/dotnet-webapi/zero-trust.md +1 -1
  108. package/assets/guardrails/technology/nextjs/accessibility-implementation.md +1 -1
  109. package/assets/guardrails/technology/nextjs/api-client.md +1 -1
  110. package/assets/guardrails/technology/nextjs/api-integration.dotnet-webapi.md +1 -1
  111. package/assets/guardrails/technology/nextjs/api-integration.java-spring.md +1 -1
  112. package/assets/guardrails/technology/nextjs/auth-session.md +1 -1
  113. package/assets/guardrails/technology/nextjs/client-security.md +1 -1
  114. package/assets/guardrails/technology/nextjs/component-architecture.md +1 -1
  115. package/assets/guardrails/technology/nextjs/component-conventions.md +2 -2
  116. package/assets/guardrails/technology/nextjs/design-system.md +1 -1
  117. package/assets/guardrails/technology/nextjs/error-handling.md +1 -1
  118. package/assets/guardrails/technology/nextjs/forms-and-validation.md +1 -1
  119. package/assets/guardrails/technology/nextjs/frontend-testing.md +1 -1
  120. package/assets/guardrails/technology/nextjs/i18n.md +1 -1
  121. package/assets/guardrails/technology/nextjs/observability.md +1 -1
  122. package/assets/guardrails/technology/nextjs/performance-budgets.md +1 -1
  123. package/assets/guardrails/technology/nextjs/routing-navigation.md +1 -1
  124. package/assets/guardrails/technology/nextjs/state-management.md +1 -1
  125. package/assets/guardrails/technology/nextjs/ui-constitution.md +3 -3
  126. package/package.json +1 -1
  127. package/assets/.agents/skills/appweaver-demo/SKILL.md +0 -59
  128. package/assets/.appweaver/orchestrator/.smoke-out/smoke.md +0 -6
  129. package/assets/.appweaver/orchestrator/workflows/aw-spec-demo.orch.yaml +0 -100
  130. package/assets/.claude/skills/appweaver-demo/SKILL.md +0 -59
  131. package/assets/.kilocode/agent.json +0 -28
  132. package/assets/.kilocode/workflows/appweaver-demo.md +0 -59
  133. package/assets/.kilocode/workflows/appweaver.md +0 -55
  134. package/assets/guardrails/projects/dotnet-default-project/tech-rule-bindings.md +0 -271
  135. /package/assets/guardrails/{projects/dotnet-default-project → project-specific}/domain/domain-requirements.md +0 -0
  136. /package/assets/guardrails/{projects/dotnet-default-project → project-specific}/frontend/ui-requirements.md +0 -0
  137. /package/assets/guardrails/{projects/dotnet-default-project → project-specific}/service/technical-requirements.md +0 -0
@@ -0,0 +1,237 @@
1
+ # =============================================================================
2
+ # foundation-lane (orchestrator) — scaffold ONE declared lane
3
+ #
4
+ # Included by aw-spec-foundation once per lane kind — backend first, then frontend — with
5
+ # that lane's fields passed through `with:`. It answers "what is this lane built from?"
6
+ # and hands off:
7
+ #
8
+ # 1. Can the lane take a foundation — does it run its commands inside its lane root folder
9
+ # and keep its tests there? If not, it is reported and passed over. Then: does it
10
+ # already hold files? If so, ask to overwrite them; "no" skips the lane and nothing
11
+ # else is asked. Each lane is asked on its own.
12
+ # 2. List what fits the lane: every pack and spec under foundations/<ecosystem>/ whose
13
+ # `# kind:` / `# profiles:` header admits this lane, then [manual spec] and [skip].
14
+ # 3. Pick one. A fast-path name (`/appweaver foundation VerticalSlice App`) preselects
15
+ # its entry in whichever lane lists it.
16
+ # 4. pack -> foundation-pack (unpack the verified bundle, as before)
17
+ # spec | manual spec -> foundation-spec (an agent builds the project from the spec)
18
+ # skip -> nothing is written.
19
+ #
20
+ # Every refusal in here and in the two route modules RETURNS rather than stops, so the
21
+ # caller always reaches the next lane; the reason is in fl_status, which the caller
22
+ # reports per lane at the end of the run.
23
+ #
24
+ # Inputs (via include `with:`):
25
+ # fl_kind, fl_lane_id, fl_profile, fl_title, fl_language, fl_ecosystem,
26
+ # fl_source_path, fl_test_path, fl_workdir, fl_build
27
+ # Shared with the caller: USER_INPUT (the fast path) and `app` (asked once per run, the
28
+ # first time a lane needs it, and reused by the next lane).
29
+ # Produces: fl_status, fl_route, fl_name, fl_spec_path, fl_files, and fl_overwrite ("yes"
30
+ # when the lane already had files and the operator agreed to overwrite them), which the
31
+ # two route modules read.
32
+ # =============================================================================
33
+
34
+ name: foundation-lane
35
+ dialect: aw-spec.orchestrator/v1
36
+
37
+ scripts:
38
+ actions:
39
+ sh: .appweaver/scripts/bash/aw-spec-actions.sh
40
+ ps1: .appweaver/scripts/powershell/aw-spec-actions.ps1
41
+
42
+ workflow:
43
+
44
+ # 0. Vars are shared across the two includes; start this lane clean.
45
+ - id: fl_reset
46
+ action: set
47
+ vars:
48
+ fl_status: "not scaffolded"
49
+ fl_route: ""
50
+ fl_name: ""
51
+ fl_spec_file: ""
52
+ fl_spec_path: ""
53
+ fl_files: ""
54
+ fl_has_files: ""
55
+ fl_files_summary: ""
56
+ fl_self_contained: ""
57
+ fl_shape_reason: ""
58
+ fl_overwrite_answer: ""
59
+ fl_overwrite: ""
60
+ fl_overwrite_note: ""
61
+ fl_go: "true"
62
+ fl_spec_route: ""
63
+
64
+ - id: fl_has_root
65
+ action: verify_context
66
+ require_non_empty: fl_source_path
67
+ on_empty: return
68
+ message_var: fl_status
69
+ on_empty_message: "not scaffolded — lane `{{fl_lane_id}}` declares no lane root folder in .appweaver/stacks.json; re-run /appweaver select-stack and give it its lane root folder"
70
+
71
+ # 1. Does the lane already hold files? Any file in its lane root folder or test path counts,
72
+ # whatever stack wrote it, and so do its build files where its commands run when that
73
+ # is outside the lane (a .NET solution at the repo root). Asked BEFORE the pick list,
74
+ # so "no" skips this lane with nothing else asked and the run moves on to the next one.
75
+ # The first option is the non-interactive default, so an unattended run never
76
+ # overwrites. The answer key carries the lane kind, so a scripted run answers each lane
77
+ # on its own: --answer fl_overwrite_backend=yes --answer fl_overwrite_frontend=no.
78
+ - id: fl_files_check
79
+ action: execute_script
80
+ script: { ref: actions }
81
+ subcommand: foundation-lane-files
82
+ args: ["--source", "{{fl_source_path}}", "--test", "{{fl_test_path}}", "--workdir", "{{fl_workdir}}", "--profile", "{{fl_profile}}"]
83
+ outputs:
84
+ - { name: fl_has_files, from: "$.has_files" }
85
+ - { name: fl_files_summary, from: "$.summary" }
86
+ - { name: fl_self_contained, from: "$.self_contained" }
87
+ - { name: fl_shape_reason, from: "$.shape_reason" }
88
+
89
+ # A foundation is a complete project that lands INSIDE the lane root folder — build files,
90
+ # source and tests — so the lane has to run its commands there and keep its tests there.
91
+ # A lane declared the classic way (code in src/, solution and tests/ at the repo root)
92
+ # suits existing code but would get a project its own build cannot find; it is reported
93
+ # here, before anything is asked, and the run moves on to the next lane.
94
+ - id: fl_shape_gate
95
+ action: verify_context
96
+ require_true: fl_self_contained
97
+ on_false: return
98
+ message_var: fl_status
99
+ on_false_message: "not scaffolded — {{fl_shape_reason}}"
100
+
101
+ - id: fl_ask_overwrite
102
+ action: ask
103
+ condition: "{{fl_has_files}} == true"
104
+ prompt: >
105
+ {{fl_kind}} lane `{{fl_lane_id}}` ({{fl_profile}}) already has files — {{fl_files_summary}}.
106
+ Scaffolding it writes into these folders and replaces files with the same path; files
107
+ with other names stay, and nothing is deleted. Overwrite?
108
+ options: ["no — skip this lane", "yes — overwrite"]
109
+ answer_key: "fl_overwrite_{{fl_kind}}"
110
+ answer_var: fl_overwrite_answer
111
+
112
+ # The answer is free text from the launcher: the full option and a bare `yes` both count.
113
+ - id: fl_overwrite_option
114
+ action: set
115
+ condition: "{{fl_overwrite_answer}} == yes — overwrite"
116
+ vars: { fl_overwrite: "yes" }
117
+
118
+ - id: fl_overwrite_short
119
+ action: set
120
+ condition: "{{fl_overwrite_answer}} == yes"
121
+ vars: { fl_overwrite: "yes" }
122
+
123
+ - id: fl_overwrite_gate
124
+ action: verify_context
125
+ condition: "{{fl_has_files}} == true"
126
+ require_value: fl_overwrite
127
+ equals: "yes"
128
+ on_mismatch: return
129
+ message_var: fl_status
130
+ on_mismatch_message: "skipped — the lane already has files ({{fl_files_summary}}) and overwriting them was declined"
131
+
132
+ - id: fl_overwrite_noted
133
+ action: set
134
+ condition: "{{fl_overwrite}} == yes"
135
+ vars: { fl_overwrite_note: " It already has files, and you chose to overwrite them." }
136
+
137
+ # 2. What fits this lane.
138
+ - id: fl_list
139
+ action: execute_script
140
+ script: { ref: actions }
141
+ subcommand: foundation-list
142
+ args: ["--kind", "{{fl_kind}}", "--profile", "{{fl_profile}}", "--ecosystem", "{{fl_ecosystem}}", "--lane-id", "{{fl_lane_id}}", "--args", "{{USER_INPUT}}"]
143
+ outputs:
144
+ - { name: fl_options, from: "$.options" }
145
+ - { name: fl_count, from: "$.count" }
146
+ - { name: fl_all, from: "$.all_foundations" }
147
+ - { name: fl_preselect, from: "$.preselect_option" }
148
+
149
+ # 3. Pick. There are always at least two options ([manual spec] and [skip]), so this
150
+ # always asks unless the fast path named an entry this lane offers.
151
+ - id: fl_pick
152
+ action: user_select
153
+ prompt: >
154
+ {{fl_kind}} lane `{{fl_lane_id}}` — {{fl_title}} at {{fl_source_path}}.{{fl_overwrite_note}}
155
+ What should it be scaffolded from? A [pack] is a verified project unpacked as-is; a
156
+ [spec] describes a project the agent builds into the lane; [manual spec] lets you give
157
+ that description yourself; [skip] leaves the lane untouched.
158
+ ({{fl_count}} of the foundations shipped fit this lane; all of them: {{fl_all}})
159
+ options_from_variable: fl_options
160
+ preselect_from: fl_preselect
161
+ outputs:
162
+ - { name: fl_selection, from: selection }
163
+
164
+ - id: fl_choice
165
+ action: execute_script
166
+ script: { ref: actions }
167
+ subcommand: foundation-choice
168
+ args: ["--selection", "{{fl_selection}}", "--kind", "{{fl_kind}}", "--profile", "{{fl_profile}}", "--ecosystem", "{{fl_ecosystem}}"]
169
+ outputs:
170
+ - { name: fl_choice_ok, from: "$.ok" }
171
+ - { name: fl_route, from: "$.route" }
172
+ - { name: fl_name, from: "$.name" }
173
+ - { name: fl_spec_file, from: "$.file" }
174
+ - { name: fl_choice_msg, from: "$.reason" }
175
+
176
+ - id: fl_choice_gate
177
+ action: verify_context
178
+ require_true: fl_choice_ok
179
+ on_false: return
180
+ message_var: fl_status
181
+ on_false_message: "not scaffolded — {{fl_choice_msg}}"
182
+
183
+ - id: fl_skip
184
+ action: set
185
+ condition: "{{fl_route}} == skip"
186
+ vars: { fl_go: "" }
187
+
188
+ - id: fl_skip_gate
189
+ action: verify_context
190
+ require_true: fl_go
191
+ on_false: return
192
+ message_var: fl_status
193
+ on_false_message: "skipped — left as it is, by choice"
194
+
195
+ # 4. The project name — asked once per run, the first time a lane needs it, unless the
196
+ # fast path gave it. The next lane reuses it.
197
+ - id: fl_ask_app
198
+ action: ask
199
+ condition: "{{app}} == "
200
+ prompt: >
201
+ Enter the project (application) name, e.g. AppWeaver.Health. A pack renames its
202
+ sentinel to it; a spec build names the project after it, in the form the stack uses.
203
+ It is asked once and used for every lane in this run.
204
+ answer_var: app_typed
205
+
206
+ - id: fl_app_from_typed
207
+ action: set
208
+ condition: "{{app}} == "
209
+ vars: { app: "{{app_typed}}" }
210
+
211
+ - id: fl_app_gate
212
+ action: verify_context
213
+ require_non_empty: app
214
+ on_empty: return
215
+ message_var: fl_status
216
+ on_empty_message: "not scaffolded — no project name was given"
217
+
218
+ # 5. Hand off. The engine has no OR, so the two spec routes set one flag.
219
+ - id: fl_route_spec
220
+ action: set
221
+ condition: "{{fl_route}} == spec"
222
+ vars: { fl_spec_route: "true" }
223
+
224
+ - id: fl_route_manual
225
+ action: set
226
+ condition: "{{fl_route}} == manual"
227
+ vars: { fl_spec_route: "true" }
228
+
229
+ - id: pack
230
+ action: include
231
+ condition: "{{fl_route}} == pack"
232
+ module: .appweaver/orchestrator/modules/foundation-pack.orch.yaml
233
+
234
+ - id: spec
235
+ action: include
236
+ condition: "{{fl_spec_route}} == true"
237
+ module: .appweaver/orchestrator/modules/foundation-spec.orch.yaml
@@ -0,0 +1,392 @@
1
+ # =============================================================================
2
+ # foundation-pack (orchestrator) — scaffold ONE lane by unpacking a foundation pack
3
+ #
4
+ # Included by foundation-lane when the operator picks a `[pack]` entry. Almost entirely
5
+ # deterministic: locate the verified byte-for-byte bundle, confirm, dry-run for
6
+ # collisions, unpack into the lane root folder exactly as packed with the sentinel renamed,
7
+ # optionally add the reference sample on top, and check that no sentinel is left behind.
8
+ #
9
+ # Its LANE SETTINGS (<pack name>.lane.json beside the pack), when it has any, are recorded
10
+ # on the lane once it is unpacked.
11
+ #
12
+ # Every file in a pack lands as packed, replacing a file already there — except the ones
13
+ # its RULE FILE (<pack name>.rules.json beside the pack) says to merge. The model is used
14
+ # ONLY for those merges: an `agent_author` (read-only generation, the ENGINE writes the
15
+ # file) with include_standards: false and a two-file context — the file that was there
16
+ # and the pack's version — following the rule's own instruction.
17
+ #
18
+ # Every refusal RETURNS rather than stops: the run carries on with the next lane, and the
19
+ # reason lands in fl_status (message_var), which the caller reports per lane.
20
+ #
21
+ # Inputs (shared vars, set by foundation-lane):
22
+ # fl_name, app, fl_kind, fl_lane_id, fl_title, fl_language, fl_ecosystem,
23
+ # fl_source_path, fl_test_path, fl_overwrite
24
+ # Produces: fl_status, fl_files (files written), and the fp_* details.
25
+ # =============================================================================
26
+
27
+ name: foundation-pack
28
+ dialect: aw-spec.orchestrator/v1
29
+
30
+ scripts:
31
+ actions:
32
+ sh: .appweaver/scripts/bash/aw-spec-actions.sh
33
+ ps1: .appweaver/scripts/powershell/aw-spec-actions.ps1
34
+
35
+ workflow:
36
+
37
+ # 0. This module runs once per lane in the same run, and vars are shared — the second
38
+ # lane must not inherit the first lane's overwrite answer or reference choice.
39
+ - id: fp_reset
40
+ action: set
41
+ vars:
42
+ fp_overwrite: "no"
43
+ fp_has_collisions: "false"
44
+ fp_want_reference: "no"
45
+ fp_has_reference: "false"
46
+ fp_merge_state: ".appweaver/orchestrator/.state/foundation-{{fl_lane_id}}.merge/"
47
+ fp_rules_ok: ""
48
+ fp_rules_reason: ""
49
+ fp_rules_file: ""
50
+ fp_merges: "[]"
51
+ fp_rules_note: ""
52
+ fp_ref_rules_ok: ""
53
+ fp_ref_rules_reason: ""
54
+ fp_ref_rules_file: ""
55
+ fp_ref_merges: "[]"
56
+ fp_ref_rules_note: ""
57
+ fp_ref_go: "no"
58
+ fp_ok: ""
59
+ fp_written: "0"
60
+ fp_tests_message: ""
61
+ fp_tests_note: ""
62
+ fp_ref_ok: ""
63
+ fp_ref_written: "0"
64
+ fp_ref_tests_message: ""
65
+ fp_ref_note: ""
66
+ fp_clean: ""
67
+ fp_residue: ""
68
+ fp_residue_note: "not checked"
69
+ fp_lane_found: "false"
70
+ fp_lane_file: ""
71
+ fp_lane_contract_run: ""
72
+ fp_lane_contract: ""
73
+ fp_lane_client: ""
74
+ fp_lane_recorded: ""
75
+ fp_lane_note: ""
76
+
77
+ # 1. Locate the bundle + its unpacker and read the (bounded) pack header: the sentinel,
78
+ # the file count, and whether a Reference pack ships beside it.
79
+ - id: fp_locate
80
+ action: execute_script
81
+ script: { ref: actions }
82
+ subcommand: foundation-locate
83
+ args: ["--name", "{{fl_name}}", "--app", "{{app}}", "--out", "{{fl_source_path}}"]
84
+ outputs:
85
+ - { name: fp_locate_ok, from: "$.ok" }
86
+ - { name: fp_locate_msg, from: "$.reason" }
87
+ - { name: fp_available, from: "$.available" }
88
+ - { name: fp_sentinel, from: "$.sentinel" }
89
+ - { name: fp_file_count, from: "$.file_count" }
90
+ - { name: fp_has_reference, from: "$.has_reference" }
91
+
92
+ - id: fp_locate_gate
93
+ action: verify_context
94
+ require_true: fp_locate_ok
95
+ on_false: return
96
+ message_var: fl_status
97
+ on_false_message: "not scaffolded — {{fp_locate_msg}} (packs available: {{fp_available}})"
98
+
99
+ # 1b. The lane already had files and foundation-lane's overwrite question was answered
100
+ # yes before the pick list: that answer stands, so the unpack overwrites and the
101
+ # collision question below is not asked a second time.
102
+ - id: fp_overwrite_agreed
103
+ action: set
104
+ condition: "{{fl_overwrite}} == yes"
105
+ vars: { fp_overwrite: "yes" }
106
+
107
+ # 2. Confirm (checkpoint kind 'ask' under every strategy).
108
+ - id: fp_confirm
109
+ action: ask
110
+ prompt: |
111
+ Foundation: {{fl_name}} pack (sentinel {{fp_sentinel}}, {{fp_file_count}} files)
112
+ App name: {{app}}
113
+ Lane: {{fl_lane_id}} — {{fl_title}}, {{fl_language}} ({{fl_ecosystem}})
114
+ Folder: {{fl_source_path}} <- the pack unpacks here as packed: build files, source and tests
115
+ Tests: {{fl_test_path}}
116
+ Overwrite: {{fp_overwrite}} <- yes: files the pack writes replace existing ones
117
+ The sentinel {{fp_sentinel}} is renamed to {{app}} in file contents and paths, as the
118
+ replacement contract in the pack's header describes. Proceed?
119
+ options: ["yes", "no"]
120
+ answer_var: fp_proceed
121
+
122
+ - id: fp_confirm_gate
123
+ action: verify_context
124
+ require_value: fp_proceed
125
+ equals: "yes"
126
+ on_mismatch: return
127
+ message_var: fl_status
128
+ on_mismatch_message: "not scaffolded — the {{fl_name}} pack was declined at the confirmation; nothing was written to {{fl_source_path}}"
129
+
130
+ # 3. Dry-run to surface collisions, then confirm overwrite if needed — only for a lane
131
+ # that was empty: one that already had files was asked before the pick list (step 1b).
132
+ - id: fp_dry_run
133
+ action: execute_script
134
+ condition: "{{fl_overwrite}} != yes"
135
+ script: { ref: actions }
136
+ subcommand: foundation-unpack
137
+ args: ["--name", "{{fl_name}}", "--app", "{{app}}", "--out", "{{fl_source_path}}", "--dry-run"]
138
+ outputs:
139
+ - { name: fp_has_collisions, from: "$.has_collisions" }
140
+
141
+ - id: fp_ask_overwrite
142
+ action: ask
143
+ condition: "{{fp_has_collisions}} == true"
144
+ prompt: "Files the {{fl_name}} pack writes already exist under {{fl_source_path}}. Overwrite them?"
145
+ options: ["yes", "no"]
146
+ answer_var: fp_overwrite
147
+
148
+ - id: fp_overwrite_gate
149
+ action: verify_context
150
+ condition: "{{fp_has_collisions}} == true"
151
+ require_value: fp_overwrite
152
+ equals: "yes"
153
+ on_mismatch: return
154
+ message_var: fl_status
155
+ on_mismatch_message: "not scaffolded — files the {{fl_name}} pack writes already exist under {{fl_source_path}} and overwriting them was declined"
156
+
157
+ # 4. The pack's rule file: which of its files are merged with what the lane holds now.
158
+ # Read before anything is written, so a broken rule file leaves the lane untouched;
159
+ # the files to merge with are put aside under fp_merge_state.
160
+ - id: fp_rules
161
+ action: execute_script
162
+ script: { ref: actions }
163
+ subcommand: foundation-rules
164
+ args: ["--name", "{{fl_name}}", "--app", "{{app}}", "--out", "{{fl_source_path}}", "--state", "{{fp_merge_state}}"]
165
+ outputs:
166
+ - { name: fp_rules_ok, from: "$.ok" }
167
+ - { name: fp_rules_reason, from: "$.reason" }
168
+ - { name: fp_rules_file, from: "$.rules_file" }
169
+ - { name: fp_merges, from: "$.merges" }
170
+ - { name: fp_rules_note, from: "$.note" }
171
+
172
+ - id: fp_rules_gate
173
+ action: verify_context
174
+ require_true: fp_rules_ok
175
+ on_false: return
176
+ message_var: fl_status
177
+ on_false_message: "not scaffolded — {{fp_rules_reason}}; nothing was written to {{fl_source_path}}"
178
+
179
+ # 5. Unpack for real, and check it worked: an unpacker that refuses (existing files, a
180
+ # bad name) exits non-zero with 0 files written, and reporting that as a scaffold is
181
+ # how a lane with no project gets as far as implement.
182
+ - id: fp_unpack
183
+ action: execute_script
184
+ script: { ref: actions }
185
+ subcommand: foundation-unpack
186
+ args: ["--name", "{{fl_name}}", "--app", "{{app}}", "--out", "{{fl_source_path}}", "--test-path", "{{fl_test_path}}", "--force", "{{fp_overwrite}}"]
187
+ outputs:
188
+ - { name: fp_ok, from: "$.ok" }
189
+ - { name: fp_written, from: "$.written" }
190
+ - { name: fp_tests_message, from: "$.tests_message" }
191
+ - { name: fp_unpack_out, from: "$.stdout" }
192
+
193
+ - id: fp_unpack_gate
194
+ action: verify_context
195
+ require_true: fp_ok
196
+ on_false: return
197
+ message_var: fl_status
198
+ on_false_message: "NOT scaffolded — the {{fl_name}} unpacker failed and wrote {{fp_written}} file(s). Its output ends: {{fp_unpack_out}}"
199
+
200
+ # 5b. The pack's LANE SETTINGS (<pack name>.lane.json beside it): what only the pack
201
+ # knows about the project it scaffolds — chiefly how its service is started on its
202
+ # own so implement can export the API document. Recorded on the lane with lane-set;
203
+ # a key the file leaves empty is not touched, and a pack with no such file leaves
204
+ # the lane on its stack profile's values.
205
+ - id: fp_lane_settings
206
+ action: execute_script
207
+ on_error: continue
208
+ script: { ref: actions }
209
+ subcommand: foundation-pack-lane
210
+ args: ["--name", "{{fl_name}}", "--app", "{{app}}"]
211
+ outputs:
212
+ - { name: fp_lane_found, from: "$.found" }
213
+ - { name: fp_lane_file, from: "$.file" }
214
+ - { name: fp_lane_contract_run, from: "$.contract_run" }
215
+ - { name: fp_lane_contract, from: "$.contract" }
216
+ - { name: fp_lane_client, from: "$.client" }
217
+
218
+ - id: fp_lane_record
219
+ action: execute_script
220
+ condition: "{{fp_lane_found}} == true"
221
+ on_error: continue
222
+ script: { ref: actions }
223
+ subcommand: lane-set
224
+ args: ["--id", "{{fl_lane_id}}", "--set", "contract_run={{fp_lane_contract_run}}", "--set", "contract={{fp_lane_contract}}", "--set", "client={{fp_lane_client}}"]
225
+ outputs:
226
+ - { name: fp_lane_recorded, from: "$.ok" }
227
+
228
+ - id: fp_lane_noted
229
+ action: set
230
+ condition: "{{fp_lane_recorded}} == true"
231
+ vars: { fp_lane_note: "; the pack's API-export settings ({{fp_lane_file}}) recorded on the {{fl_lane_id}} lane" }
232
+
233
+ # 6. The merges the rule file asks for: the file that was there (put aside) and the
234
+ # pack's version (now on disk) become one, as the rule's instruction says.
235
+ - id: fp_merge
236
+ action: loop
237
+ over: "{{fp_merges}}"
238
+ as: m
239
+ steps:
240
+ - id: fp_merge_file
241
+ action: agent_author
242
+ context: ["{{m.existing}}", "{{m.target}}"]
243
+ include_standards: false
244
+ task: |
245
+ Merge two versions of {{m.target}} into one file.
246
+ EXISTING {{m.existing}} — the file as it was in the lane before the {{fl_name}} pack was unpacked
247
+ INCOMING {{m.target}} — the file as the pack ships it, with {{fp_sentinel}} renamed to {{app}}
248
+ The pack's rule file ({{fp_rules_file}}) says how, in its rule for {{m.rule}}:
249
+ {{m.instruction}}
250
+ Change nothing that instruction does not ask for.
251
+ output_target: { type: file, path: "{{m.target}}" }
252
+
253
+ - id: fp_rules_done
254
+ action: execute_script
255
+ script: { ref: actions }
256
+ subcommand: foundation-rules-done
257
+ args: ["--state", "{{fp_merge_state}}"]
258
+
259
+ # 7. Optional reference sample — offered only when the pack ships one. It goes on top
260
+ # of the base project the same way: its own rule file, the unpack, its merges.
261
+ - id: fp_ask_reference
262
+ action: ask
263
+ condition: "{{fp_has_reference}} == true"
264
+ prompt: "The {{fl_name}} foundation also ships a reference sample ({{fl_name}}.Reference.pack.txt): worked example code unpacked on top of the base project. Include it?"
265
+ options: ["yes", "no"]
266
+ answer_var: fp_want_reference
267
+
268
+ - id: fp_ref_rules
269
+ action: execute_script
270
+ condition: "{{fp_want_reference}} == yes"
271
+ script: { ref: actions }
272
+ subcommand: foundation-rules
273
+ args: ["--name", "{{fl_name}}", "--app", "{{app}}", "--out", "{{fl_source_path}}", "--reference", "--state", "{{fp_merge_state}}"]
274
+ outputs:
275
+ - { name: fp_ref_rules_ok, from: "$.ok" }
276
+ - { name: fp_ref_rules_reason, from: "$.reason" }
277
+ - { name: fp_ref_rules_file, from: "$.rules_file" }
278
+ - { name: fp_ref_merges, from: "$.merges" }
279
+ - { name: fp_ref_rules_note, from: "$.note" }
280
+
281
+ # A broken rule file keeps the sample out but leaves the base project in place, so this
282
+ # reports and carries on rather than returning.
283
+ - id: fp_ref_rules_gate
284
+ action: verify_context
285
+ condition: "{{fp_want_reference}} == yes"
286
+ require_true: fp_ref_rules_ok
287
+ on_false: continue
288
+ message_var: fp_ref_note
289
+ on_false_message: "; the reference sample was NOT added — {{fp_ref_rules_reason}}"
290
+
291
+ - id: fp_ref_go
292
+ action: set
293
+ condition: "{{fp_ref_rules_ok}} == true"
294
+ vars: { fp_ref_go: "yes" }
295
+
296
+ - id: fp_reference_unpack
297
+ action: execute_script
298
+ condition: "{{fp_ref_go}} == yes"
299
+ script: { ref: actions }
300
+ subcommand: foundation-unpack
301
+ args: ["--name", "{{fl_name}}", "--app", "{{app}}", "--out", "{{fl_source_path}}", "--test-path", "{{fl_test_path}}", "--reference"]
302
+ outputs:
303
+ - { name: fp_ref_ok, from: "$.ok" }
304
+ - { name: fp_ref_written, from: "$.written" }
305
+ - { name: fp_ref_tests_message, from: "$.tests_message" }
306
+ - { name: fp_ref_out, from: "$.stdout" }
307
+
308
+ # A failed reference sample does not undo the base project, so this reports and carries
309
+ # on to the residue check rather than returning.
310
+ - id: fp_reference_gate
311
+ action: verify_context
312
+ condition: "{{fp_ref_go}} == yes"
313
+ require_true: fp_ref_ok
314
+ on_false: continue
315
+ message_var: fp_ref_note
316
+ on_false_message: "; the reference sample FAILED to unpack ({{fp_ref_out}})"
317
+
318
+ - id: fp_reference_done
319
+ action: set
320
+ condition: "{{fp_ref_ok}} == true"
321
+ vars: { fp_ref_note: "; reference sample added ({{fp_ref_written}} file(s))" }
322
+
323
+ # The sample's own test tree is lifted onto the lane's test path like the base pack's;
324
+ # say what happened to it, or the lane line reports only the base pack's tests.
325
+ - id: fp_reference_tests_noted
326
+ action: set
327
+ condition: "{{fp_ref_tests_message}} != "
328
+ vars: { fp_ref_note: "{{fp_ref_note}} — its tests: {{fp_ref_tests_message}}" }
329
+
330
+ # The sample's merges: what the base project (or the lane before it) had, and the
331
+ # sample's version, become one as the sample's rule file says.
332
+ - id: fp_ref_merge
333
+ action: loop
334
+ condition: "{{fp_ref_ok}} == true"
335
+ over: "{{fp_ref_merges}}"
336
+ as: m
337
+ steps:
338
+ - id: fp_ref_merge_file
339
+ action: agent_author
340
+ context: ["{{m.existing}}", "{{m.target}}"]
341
+ include_standards: false
342
+ task: |
343
+ Merge two versions of {{m.target}} into one file.
344
+ EXISTING {{m.existing}} — the file as it was in the lane before the {{fl_name}} reference sample was unpacked
345
+ INCOMING {{m.target}} — the file as the reference sample ships it, with {{fp_sentinel}} renamed to {{app}}
346
+ The sample's rule file ({{fp_ref_rules_file}}) says how, in its rule for {{m.rule}}:
347
+ {{m.instruction}}
348
+ Change nothing that instruction does not ask for.
349
+ output_target: { type: file, path: "{{m.target}}" }
350
+
351
+ - id: fp_ref_merged_noted
352
+ action: set
353
+ condition: "{{fp_ref_ok}} == true"
354
+ vars: { fp_ref_note: "{{fp_ref_note}}{{fp_ref_rules_note}}" }
355
+
356
+ - id: fp_ref_rules_done
357
+ action: execute_script
358
+ condition: "{{fp_ref_go}} == yes"
359
+ script: { ref: actions }
360
+ subcommand: foundation-rules-done
361
+ args: ["--state", "{{fp_merge_state}}"]
362
+
363
+ # 8. Verify — sentinel residue must be zero in the generated tree.
364
+ - id: fp_verify
365
+ action: execute_script
366
+ script: { ref: actions }
367
+ subcommand: foundation-verify
368
+ args: ["--out", "{{fl_source_path}}", "--sentinel", "{{fp_sentinel}}"]
369
+ outputs:
370
+ - { name: fp_clean, from: "$.clean" }
371
+ - { name: fp_residue, from: "$.residue" }
372
+
373
+ - id: fp_residue_clean
374
+ action: set
375
+ condition: "{{fp_clean}} == true"
376
+ vars: { fp_residue_note: "no sentinel left" }
377
+
378
+ - id: fp_residue_found
379
+ action: set
380
+ condition: "{{fp_clean}} == false"
381
+ vars: { fp_residue_note: "SENTINEL {{fp_sentinel}} STILL IN {{fp_residue}}" }
382
+
383
+ - id: fp_tests_noted
384
+ action: set
385
+ condition: "{{fp_tests_message}} != "
386
+ vars: { fp_tests_note: "; {{fp_tests_message}}" }
387
+
388
+ - id: fp_done
389
+ action: set
390
+ vars:
391
+ fl_files: "{{fp_written}}"
392
+ fl_status: "scaffolded from the {{fl_name}} pack — {{fp_written}} file(s) into {{fl_source_path}}{{fp_tests_note}}{{fp_rules_note}}{{fp_ref_note}}{{fp_lane_note}}; {{fp_residue_note}}"