aw-spec-cli 0.1.32 → 0.1.34

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 (75) hide show
  1. package/README.md +76 -81
  2. package/assets/.appweaver/DESIGN-FORMAT.md +56 -17
  3. package/assets/.appweaver/STACK-PROFILES.md +95 -60
  4. package/assets/.appweaver/orchestrator/LAUNCHER.md +1 -1
  5. package/assets/.appweaver/orchestrator/modules/delta-plan.orch.yaml +173 -32
  6. package/assets/.appweaver/orchestrator/modules/domain-select.orch.yaml +29 -1
  7. package/assets/.appweaver/orchestrator/modules/foundation-pack.orch.yaml +7 -4
  8. package/assets/.appweaver/orchestrator/modules/foundation-spec.orch.yaml +49 -60
  9. package/assets/.appweaver/orchestrator/modules/tech-select.orch.yaml +58 -0
  10. package/assets/.appweaver/orchestrator/orchestrator.py +26 -8
  11. package/assets/.appweaver/orchestrator/rev-templates/delta-design-template.skeleton.md +20 -16
  12. package/assets/.appweaver/orchestrator/rev-templates/design-template.skeleton.md +30 -6
  13. package/assets/.appweaver/orchestrator/rev-templates/ui-delta-design-template.skeleton.md +9 -7
  14. package/assets/.appweaver/orchestrator/rev-templates/ui-design-template.skeleton.md +5 -5
  15. package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +341 -36
  16. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan-delta.orch.yaml +254 -43
  17. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +248 -68
  18. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +251 -20
  19. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +316 -10
  20. package/assets/.appweaver/orchestrator/workflows/aw-spec-build.orch.yaml +6 -47
  21. package/assets/.appweaver/orchestrator/workflows/aw-spec-close-delta.orch.yaml +62 -16
  22. package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +132 -9
  23. package/assets/.appweaver/orchestrator/workflows/aw-spec-fix-test-coverage.orch.yaml +965 -0
  24. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +222 -169
  25. package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +28 -12
  26. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +453 -116
  27. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-lane.orch.yaml +7 -4
  28. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-stack.orch.yaml +83 -6
  29. package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +45 -2
  30. package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +324 -74
  31. package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +275 -44
  32. package/assets/.appweaver/scripts/python/aw-foundation.py +16 -13
  33. package/assets/.appweaver/scripts/python/aw-gaps.py +11 -3
  34. package/assets/.appweaver/scripts/python/aw-model-guard.py +295 -0
  35. package/assets/.appweaver/scripts/python/aw-plan-coverage.py +412 -3
  36. package/assets/.appweaver/scripts/python/aw-plangate.py +23 -16
  37. package/assets/.appweaver/scripts/python/aw-stack.py +1794 -265
  38. package/assets/.appweaver/stack-profiles/dotnet-webapi.json +0 -2
  39. package/assets/.appweaver/stack-profiles/java-spring.json +0 -2
  40. package/assets/.appweaver/templates/api-plan-template.md +107 -0
  41. package/assets/.appweaver/templates/delta-api-plan-template.md +87 -0
  42. package/assets/.appweaver/templates/delta-design-template.md +25 -23
  43. package/assets/.appweaver/templates/delta-spec-template.md +3 -3
  44. package/assets/.appweaver/templates/design-template.md +20 -42
  45. package/assets/.appweaver/templates/domain-model-template.md +3 -0
  46. package/assets/.appweaver/templates/ui-delta-design-template.md +11 -8
  47. package/assets/.appweaver/templates/ui-design-template.md +4 -4
  48. package/assets/foundations/foundation.spec.template.txt +25 -18
  49. package/assets/guardrails/README.md +61 -25
  50. package/assets/guardrails/methodology/ux/accessibility-standards.md +38 -0
  51. package/assets/guardrails/methodology/ux/content-design.md +34 -0
  52. package/assets/guardrails/methodology/ux/interaction-states.md +45 -0
  53. package/assets/guardrails/methodology/ux/journey-mapping.md +41 -0
  54. package/assets/guardrails/project-specific/domain/domain-requirements.md +25 -38
  55. package/assets/guardrails/project-specific/domain-rule-bindings.md +33 -69
  56. package/assets/guardrails/project-specific/frontend/ui-requirements.md +2 -2
  57. package/assets/guardrails/project-specific/service/technical-requirements.md +2 -2
  58. package/assets/guardrails/project-specific/tech-rule-bindings.md +16 -13
  59. package/assets/guardrails/sample-healthcare/domain/domain-requirements.md +58 -0
  60. package/assets/guardrails/sample-healthcare/domain-rule-bindings.md +138 -0
  61. package/assets/guardrails/sample-healthcare/frontend/ui-requirements.md +43 -0
  62. package/assets/guardrails/sample-healthcare/service/technical-requirements.md +40 -0
  63. package/assets/guardrails/sample-healthcare/tech-rule-bindings.md +303 -0
  64. package/dist/commands/init.d.ts.map +1 -1
  65. package/dist/commands/init.js +216 -7
  66. package/dist/commands/init.js.map +1 -1
  67. package/dist/utils/config.js +1 -1
  68. package/dist/utils/config.js.map +1 -1
  69. package/dist/utils/foundation.d.ts +36 -0
  70. package/dist/utils/foundation.d.ts.map +1 -0
  71. package/dist/utils/foundation.js +109 -0
  72. package/dist/utils/foundation.js.map +1 -0
  73. package/dist/utils/paths.js +1 -1
  74. package/dist/utils/paths.js.map +1 -1
  75. package/package.json +1 -1
@@ -3,14 +3,22 @@
3
3
  #
4
4
  # Included by /appweaver plan on its DELTA branch (a master plan already exists).
5
5
  # It authors, inside the change's own directory beside its delta-spec:
6
+ # • changes/<cr-id>/delta-api-plan.md when a backend lane is declared — FIRST
6
7
  # • changes/<cr-id>/delta-backend-plan.md when a backend lane is declared
7
8
  # • changes/<cr-id>/delta-frontend-plan.md when a frontend lane is declared
8
- # and touches NEITHER master. The change stays OPEN; `implement` generates the code from
9
- # these documents (it asks which change, and takes the delta plans of the answer),
10
- # finalize-delta merges the change's SPEC and MODEL halves (never a lane plan — a master
11
- # plan is derived from those two and is re-derived from them), and close-delta archives
12
- # these plans with the change at the end. No version bump here, no code (that is
13
- # /appweaver implement).
9
+ # and touches NO master. The change stays OPEN; `implement` generates the code from these
10
+ # documents (it asks which change, and takes the delta plans of the answer), finalize-delta
11
+ # merges the change's SPEC and MODEL halves and, by script, its delta API plan into
12
+ # api-plan.md (never a lane plan — a master plan is derived from those and is re-derived
13
+ # from them), and close-delta archives these plans with the change at the end. No version
14
+ # bump here, no code (that is /appweaver implement).
15
+ #
16
+ # THE CHANGE'S API COMES FIRST. Its delta API plan — the endpoints it adds, changes and
17
+ # removes, and the frontend lane's requests it answers — is authored before its delta
18
+ # backend plan, which then realises each endpoint; the frontend delta plan binds to it. A
19
+ # change planned before delta API plans existed keeps its API in its delta backend plan:
20
+ # the scaffold refuses to put an empty delta API plan beside it, and the delta backend plan
21
+ # is re-planned with the API in it, as before.
14
22
  #
15
23
  # IT DOES NOT CHOOSE THE CHANGE — the caller already did. `plan` asks one question at the
16
24
  # top of its run (which is why a re-plan of the MASTER and a delta are the same command),
@@ -46,17 +54,18 @@
46
54
  # from the caller already narrowed to the developer's working lanes (.appweaver/lane.json)
47
55
  # and to the lanes on the change's **Lanes** line, so a lane that is somebody else's, or
48
56
  # that the change does not touch, gets no delta plan here. The other half of the change is
49
- # READ: the frontend delta plan binds to delta_backend_plan_read, the change's service delta
50
- # plan as the backend developer left it — absent for a frontend-only change, whose bindings
51
- # are to the master's published surface.
57
+ # READ: the frontend delta plan binds to delta_api_read, the change's API as the backend
58
+ # developer left it (its delta API plan, or the delta backend plan of an older change) —
59
+ # absent for a frontend-only change, whose bindings are to the feature's API (api_read).
52
60
  #
53
61
  # EXPECTS caller vars: feature_dir, spec_file, backend_plan, frontend_plan, domain_model,
54
62
  # be_existing, fe_existing, fe_lane_id, source_files, source_count, USER_INPUT,
55
- # delta_backend_plan_read,
63
+ # api_read, delta_api_read, req_backend_text,
56
64
  # and — resolved by the caller from the change it chose and activated — cr_id, cr_dir,
57
65
  # delta_spec, delta_model, wave, predecessor_files, predecessor_count, predecessor_ids,
58
66
  # sibling_count, sibling_ids, related_docs.
59
- # PRODUCES: delta_backend_plan, delta_frontend_plan, downstream_count, impact_report.
67
+ # PRODUCES: delta_api_plan, delta_backend_plan, delta_frontend_plan, downstream_count,
68
+ # impact_report.
60
69
  # =============================================================================
61
70
 
62
71
  name: delta-plan
@@ -91,13 +100,57 @@ vars:
91
100
  related_docs: []
92
101
  delta_backend_plan: ""
93
102
  delta_frontend_plan: ""
94
- delta_backend_plan_read: ""
103
+ api_read: ""
104
+ delta_api_read: ""
105
+ req_backend_text: "none"
106
+ # The change's delta API plan. delta_api_go is false for a lane not worked here and for a
107
+ # change whose delta backend plan still carries its API (the scaffold is then `blocked`).
108
+ delta_api_plan: ""
109
+ delta_api_blocked: "false"
110
+ delta_api_go: "false"
111
+ be_delta_api_rule: "" # what the delta backend plan is told about where the change's API is — set below
95
112
 
96
113
  workflow:
97
114
 
98
115
  # 1. Scaffold this change's plan document for each lane whose MASTER already exists.
99
116
  # A lane whose master was scaffolded in this same run has just been authored from
100
117
  # the whole spec — there is nothing for a delta to add to it — so it gets none.
118
+ #
119
+ # THE CHANGE'S API FIRST, before the delta backend plan, for the reason the master's
120
+ # is: the scaffold refuses (`blocked`) while the change's delta backend plan still
121
+ # carries its API, and a delta backend plan scaffolded first from a template
122
+ # generated before delta API plans existed could carry the old contract table.
123
+ - id: api_delta_scaffold
124
+ condition: "{{be_existing}} == true"
125
+ action: execute_script
126
+ script: { ref: actions }
127
+ subcommand: delta-plan-scaffold
128
+ args: ["--cr-id", "{{cr_id}}", "--lane", "api", "--feature-dir", "{{feature_dir}}"]
129
+ outputs:
130
+ - { name: delta_api_plan, from: "$.delta_plan" }
131
+ - { name: delta_api_blocked, from: "$.blocked" }
132
+
133
+ # Where the change's API is authored, as ordered assignments: the delta API plan on a
134
+ # worked backend lane; its delta backend plan, as before, for a change whose delta
135
+ # backend plan already carries it.
136
+ - id: api_delta_arm
137
+ condition: "{{be_existing}} == true"
138
+ action: set
139
+ vars:
140
+ delta_api_go: "true"
141
+ be_delta_api_rule: "THE CHANGE'S API IS IN delta-api-plan.md (in context), authored just before this document: the endpoints it adds, changes and removes, and the requests it answers. Do NOT restate them here — the API section of this document is the one line pointing to delta-api-plan.md, and where the template has a contract table or an endpoint section (a template generated before delta API plans existed), that line goes in their place. REALISE every endpoint instead: each names under **Maps to** the use-case that serves it, which this document ADDS or MODIFIES, with request and response DTOs that match the endpoint's shapes; a removed endpoint's use-case is REMOVED unless something else still uses it."
142
+ - id: api_delta_in_backend_plan
143
+ condition: "{{delta_api_blocked}} == true"
144
+ action: set
145
+ vars:
146
+ delta_api_go: "false"
147
+ be_delta_api_rule: "THIS CHANGE WAS PLANNED BEFORE DELTA API PLANS EXISTED, so its API stays in this document. PUBLISH WHAT THE FRONTEND LANE NEEDS: fill '## Contract deltas for the frontend lane' with every addition, change or removal to this feature's published surface, one row per endpoint carrying its operation id — a changed endpoint keeps the one the API gives it, a new endpoint gets one unique across the service — and 'None' when the surface is unchanged. THEN WRITE EACH ENDPOINT IT ADDS OR CHANGES IN FULL under '## API — added and changed endpoints', as the API's own endpoint block: the `VERB /route` heading, then **Operation id**, **Request**, **Success** and the **Errors** table, every shape `{ field: type }`. A removed endpoint gets a `removed` row and no block."
148
+ # The frontend delta plan binds to the delta API plan once this run has one.
149
+ - id: delta_api_read_own
150
+ condition: "{{delta_api_go}} == true"
151
+ action: set
152
+ vars: { delta_api_read: "{{delta_api_plan}}" }
153
+
101
154
  - id: be_delta_scaffold
102
155
  condition: "{{be_existing}} == true"
103
156
  action: execute_script
@@ -116,6 +169,89 @@ workflow:
116
169
  outputs:
117
170
  - { name: delta_frontend_plan, from: "$.delta_plan" }
118
171
 
172
+ # ═════════════════════ THE CHANGE'S API — the delta API plan ═════════════════
173
+ # The endpoints the change adds, changes and removes, and the requests it answers, in
174
+ # the API plan's own block form — which finalize-delta merges into api-plan.md block by
175
+ # block, so a changed endpoint is written whole. Authored before the delta backend plan,
176
+ # which realises each endpoint.
177
+ - id: ctx_api_delta
178
+ condition: "{{delta_api_go}} == true"
179
+ action: build_context
180
+ context_from: related_docs # the predecessors' delta-specs and every open
181
+ # change's model delta — the same two kinds,
182
+ # and the same opposite licence, as the
183
+ # service lane below.
184
+ context:
185
+ - "{{delta_api_plan}}" # the delta API plan template to fill
186
+ - "{{delta_spec}}" # the spec-side change (from specify)
187
+ - { path: "{{delta_model}}", optional: true }
188
+ - "{{spec_file}}" # the current master spec (READ ONLY)
189
+ - { path: "{{api_read}}", optional: true }
190
+ # THE FEATURE'S API AS IT STANDS (READ ONLY) —
191
+ # api-plan.md, or the backend plan of a feature
192
+ # planned before it. Planned against block by
193
+ # block, never edited here.
194
+ - { path: "{{domain_model}}", optional: true }
195
+ - { path: .appweaver/memory/constitution.technical.md, optional: true }
196
+ - { path: "{{cr_dir}}/delta-slice-analyze-report.md", optional: true }
197
+ - { path: "{{cr_dir}}/delta-slice-analyze-report.backend.md", optional: true }
198
+ # WHAT THE LAST GATE FOUND — a missing or moved
199
+ # endpoint tagged `fix: plan` is closed here.
200
+ - { path: .appweaver/DESIGN-FORMAT.md }
201
+ - id: author_api_delta
202
+ condition: "{{delta_api_go}} == true"
203
+ action: agent_author # plan's technical standards stay ON (default) — correct here
204
+ tools: Read # the feature's EXISTING source arrives as {{source_files}} paths, not inlined
205
+ output_target: { type: file, path: "{{delta_api_plan}}" }
206
+ task: |
207
+ Fill the DELTA API PLAN for change {{cr_id}} (wave {{wave}}) — the target document in
208
+ context: the endpoints this change adds to, changes in and removes from the feature's
209
+ API, and the frontend lane's requests it answers. It is authored before the change's
210
+ delta backend plan, which then realises each endpoint, and finalize-delta merges it
211
+ into api-plan.md by script, every block as it stands. THE DELTA ONLY — do NOT edit the
212
+ feature's api-plan.md or backend-plan.md.
213
+ - THE FEATURE'S API AS IT STANDS is in context: api-plan.md, or the API section of a
214
+ backend plan written before API plans existed. Plan against it, block by block.
215
+ - FILL "## Contract deltas for the frontend lane" with one row per endpoint the change
216
+ adds, changes or removes: Operation id, Contract (`VERB /route`), Change (added /
217
+ changed / removed), Shape, Serves. A changed endpoint keeps the operation id the API
218
+ gives it; a new one takes an id unique across the service. `None` when the change
219
+ leaves the API as it is — what a purely internal change says.
220
+ - THEN WRITE EACH ENDPOINT IT ADDS OR CHANGES IN FULL under "## Endpoints added or
221
+ changed", in the API's own block form: the `### `VERB /route`` heading, then
222
+ **Operation id**, **Auth**, **Request**, **Success**, the **Errors** table and
223
+ **Maps to**, every shape as `{ field: type }`. The merge puts this block in place of
224
+ the API's block, so a changed endpoint is written WHOLE — its unchanged fields too —
225
+ never as the difference. A removed endpoint has its row and no block. A shape in
226
+ prose reaches the frontend untyped.
227
+ - ANSWER THE REQUESTS THIS CHANGE SERVES. Still open in the frontend plan's "Contract
228
+ deltas needed": {{req_backend_text}}. Answer under "## Requests from the frontend
229
+ lane" only those this change's delta-spec gives a basis for — published under an
230
+ operation id, with its endpoint above, or declined with the reason. The others stay
231
+ open for the master. `None` when the change answers none.
232
+ - READ THE CODE FIRST. {{source_count}} source file(s) currently realise this
233
+ feature: {{source_files}}. Where the code already serves an endpoint this change
234
+ touches, its route, verb and operation id are as the code has them.
235
+ - THE LAST GATE'S REPORT, when one is in context (delta-slice-analyze-report.md from
236
+ a run that gated every lane, delta-slice-analyze-report.backend.md from one that
237
+ gated the backend alone; when both are, the one with the later date is the last
238
+ gate). A finding tagged `fix: plan` about an endpoint says THIS document was what was
239
+ wrong — re-derive the rows and blocks it names. EVIDENCE, never a requirement
240
+ source: the delta-spec stays the only authority on what the change must do.
241
+ - THE EFFECTIVE MODEL IS master + this change's delta + the PREDECESSOR deltas in
242
+ context. {{predecessor_count}} predecessor(s) — open changes in a LOWER wave:
243
+ {{predecessor_ids}}. You MAY build on one. {{sibling_count}} SIBLING change(s) at
244
+ the same or a higher wave: {{sibling_ids}} — never assume any of them lands; plan
245
+ against the API as it stands.
246
+ - TWO CASES — decide from the delta-spec in context: SPEC-DRIVEN, where every endpoint
247
+ row serves one of the delta-spec's ids; PLAN-ONLY (an unfilled stub), where an
248
+ endpoint row still traces to an EXISTING spec id — the realisation moves, never the
249
+ requirement.
250
+ - TECHNOLOGY-AGNOSTIC: protocol level only, conceptual types, no code.
251
+ - Do NOT edit the Wave / Base version / Delta spec / Delta backend plan headers —
252
+ scripts stamp those.
253
+ Guidance: {{USER_INPUT}}
254
+
119
255
  # ═════════════════════ BACKEND LANE — the service delta plan ═════════════════
120
256
  - id: ctx_backend_delta
121
257
  condition: "{{be_existing}} == true"
@@ -136,6 +272,11 @@ workflow:
136
272
  # unfilled one, and that is a valid state.
137
273
  - "{{spec_file}}" # the current master spec (READ ONLY)
138
274
  - "{{backend_plan}}" # the current master plan (READ ONLY — never amended here)
275
+ - { path: "{{delta_api_plan}}", optional: true }
276
+ # THE CHANGE'S API, authored just above — the
277
+ # endpoints this plan realises. Absent for a change
278
+ # planned before delta API plans existed, whose API
279
+ # is in this plan.
139
280
  - { path: "{{domain_model}}", optional: true }
140
281
  # the context's CANONICAL model — the aggregate root,
141
282
  # its entities and its invariants live here, not in the
@@ -205,13 +346,7 @@ workflow:
205
346
  that either order of merging works. If consuming one is genuinely unavoidable,
206
347
  that is a dependency, not an overlap: record it under "## Depends on" and say the
207
348
  wave must be raised. Until merged, every one of them is a proposal.
208
- - PUBLISH WHAT THE FRONTEND LANE NEEDS. Fill "## Contract deltas for the frontend
209
- lane" with every addition or change to this feature's published surface, as field
210
- shapes. That table is the only part of this document the frontend delta plan may
211
- bind against; a contract the UI needs and this document never states becomes a
212
- client-side workaround. "None" when the surface is unchanged. Each row carries the
213
- endpoint's operation id: a changed endpoint keeps the one the master plan gives
214
- it, and a new endpoint gets one unique across the service.
349
+ - {{be_delta_api_rule}}
215
350
  - Stay technology-agnostic (no code) and keep the master's form: behaviour as
216
351
  signatures + pre/post/invariant/raises, contracts as field shapes.
217
352
  - TWO CASES — decide from the delta-spec in context:
@@ -260,13 +395,17 @@ workflow:
260
395
  - { path: "{{delta_model}}", optional: true }
261
396
  - "{{spec_file}}" # the current master spec (READ ONLY)
262
397
  - "{{frontend_plan}}" # the current master plan (READ ONLY — never amended here)
263
- - { path: "{{delta_backend_plan_read}}", optional: true }
264
- # THE SERVICE HALF of this same change — its contract
265
- # table is what this lane binds to. READ here, and
398
+ - { path: "{{delta_api_read}}", optional: true }
399
+ # THIS CHANGE'S API — its delta API plan, or the
400
+ # delta backend plan of a change planned before
401
+ # those — is what this lane binds to. READ here, and
266
402
  # authored above only when the backend is a working
267
403
  # lane: another developer may own it. Optional: an
268
- # experience-only change has no service delta, and a
404
+ # experience-only change has no API delta, and a
269
405
  # frontend-only project has no service lane at all.
406
+ - { path: "{{api_read}}", optional: true }
407
+ # THE FEATURE'S API as it stands — for the endpoints
408
+ # this change leaves alone.
270
409
  - { path: "{{domain_model}}", optional: true }
271
410
  - { path: .appweaver/memory/constitution.ui.md, optional: true }
272
411
  - { path: "{{cr_dir}}/delta-slice-analyze-report.md", optional: true }
@@ -315,15 +454,17 @@ workflow:
315
454
  delta-spec does not describe is a SPEC gap: log it as a DA-n that routes back to
316
455
  `specify`, never invent the requirement here.
317
456
  - THE LANE BOUNDARY IS THE PUBLISHED SURFACE. Bind against the delta-spec's IC- ids
318
- and, where the service delta is in context, its "Contract deltas for the frontend
319
- lane" table. CITE those contracts; do not restate, re-derive or contradict them,
320
- and introduce no server-side behaviour or persistence assumption. What the
321
- experience needs and the surface does not offer goes under "Contract deltas
322
- needed", raised back to the service lane — never worked around in the client.
457
+ and, where this change's API is in context, its "Contract deltas for the frontend
458
+ lane" table and the endpoint blocks it writes; for an endpoint the change leaves
459
+ alone, the feature's API in context. CITE those contracts; do not restate,
460
+ re-derive or contradict them, and introduce no server-side behaviour or persistence
461
+ assumption. What the experience needs and the surface does not offer goes under
462
+ "Contract deltas needed", raised back to the service lane — never worked around in
463
+ the client.
323
464
  - A BINDING ROW'S ELEMENT LEADS WITH ITS OPERATION ID — `OperationId` · `VERB
324
- /route` — copied exactly from the service delta's table or, for an endpoint the
325
- change does not touch, the master backend plan. Implement looks the endpoint up by
326
- it in the service's exported API document.
465
+ /route` — copied exactly from this change's API or, for an endpoint the change does
466
+ not touch, the feature's. Implement looks the endpoint up by it in the API document
467
+ drafted from the API plans, this change's included.
327
468
  - UX- IS THIS DOCUMENT'S TO REALISE. The service plan excludes the spec's Experience
328
469
  Standards by contract, so a UX- item in the delta-spec that reaches neither plan
329
470
  is a dropped requirement.
@@ -341,7 +482,7 @@ workflow:
341
482
  - TECHNOLOGY-AGNOSTIC: no framework, router, state library, component library or
342
483
  styling approach — responsibilities, field shapes, events and states only. The
343
484
  header's Lane field is `{{fe_lane_id}}`.
344
- - Do NOT edit the Wave / Base version / Delta spec / Service delta headers — scripts
485
+ - Do NOT edit the Wave / Base version / Delta spec / API delta headers — scripts
345
486
  stamp those.
346
487
  Guidance: {{USER_INPUT}}
347
488
 
@@ -5,7 +5,10 @@
5
5
  # `action: include`) by every domain-aware converted skill. Same flow:
6
6
  # 1. resolve the active domain + candidate rule files for {{command}}
7
7
  # 2. Domain gate: no domain / no domain-rule-bindings → ask Continue (technical only)
8
- # or EXIT (stops the whole skill, no side effects)
8
+ # or EXIT (stops the whole skill, no side effects). Where the map exists but loads
9
+ # less than it says — no `## /{{command}}` heading (absent or misspelled; a heading
10
+ # present and deliberately empty does not warn), or a mapped file not on disk — WARN
11
+ # and carry on; the engine returns each warning in the run result.
9
12
  # 3. Selection box: all / subset / none
10
13
  # 4. load the selected rule files — into the STICKY STANDARDS SET (D3: domain
11
14
  # rules are SYSTEM standards; every later generation sees them without the
@@ -43,7 +46,32 @@ workflow:
43
46
  outputs:
44
47
  - { name: domain, from: "$.domain" }
45
48
  - { name: dg_rulemap_exists, from: "$.rulemap_exists" }
49
+ - { name: dg_heading_found, from: "$.heading_found" }
46
50
  - { name: dg_candidates, from: "$.candidates" }
51
+ - { name: dg_missing, from: "$.missing" }
52
+
53
+ # 1b. What the map says and does not load — warnings, never stops, and only where the
54
+ # map exists (its absence is the gate below). The engine has no "list is not empty"
55
+ # test, so the missing list sets a flag false when it has entries.
56
+ - id: dg_flags
57
+ action: set
58
+ vars: { dg_all_on_disk: "true" }
59
+ - id: dg_flag_missing
60
+ condition: "{{dg_missing}} != []"
61
+ action: set
62
+ vars: { dg_all_on_disk: "false" }
63
+ - id: warn_no_heading
64
+ condition: "{{dg_rulemap_exists}} == true"
65
+ action: verify_context
66
+ require_true: dg_heading_found
67
+ on_false: continue
68
+ on_false_message: "WARNING — guardrails/{{domain}}/domain-rule-bindings.md has no `## /{{command}}` heading, so this step loads no domain rules from the map. If that is not intended, add the heading and its rule files — it must read exactly `## /{{command}}`."
69
+ - id: warn_missing
70
+ condition: "{{dg_rulemap_exists}} == true"
71
+ action: verify_context
72
+ require_true: dg_all_on_disk
73
+ on_false: continue
74
+ on_false_message: "WARNING — guardrails/{{domain}}/domain-rule-bindings.md maps these files under `## /{{command}}`, but they are not on disk, so they were not loaded: {{dg_missing}}. Correct the paths, or add the files."
47
75
 
48
76
  # 2. Domain gate — only when the project or its domain-rule-bindings.md is missing.
49
77
  - id: gate
@@ -68,6 +68,7 @@ workflow:
68
68
  fp_residue_note: "not checked"
69
69
  fp_lane_found: "false"
70
70
  fp_lane_file: ""
71
+ fp_lane_contract_write: ""
71
72
  fp_lane_contract_run: ""
72
73
  fp_lane_contract: ""
73
74
  fp_lane_client: ""
@@ -198,8 +199,9 @@ workflow:
198
199
  on_false_message: "NOT scaffolded — the {{fl_name}} unpacker failed and wrote {{fp_written}} file(s). Its output ends: {{fp_unpack_out}}"
199
200
 
200
201
  # 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;
202
+ # knows about the project it scaffolds — chiefly how its service's API document is
203
+ # written, or how the service is started on its own to fetch it, so analyze-slice
204
+ # can check the API it serves against the plan. Recorded on the lane with lane-set;
203
205
  # a key the file leaves empty is not touched, and a pack with no such file leaves
204
206
  # the lane on its stack profile's values.
205
207
  - id: fp_lane_settings
@@ -211,6 +213,7 @@ workflow:
211
213
  outputs:
212
214
  - { name: fp_lane_found, from: "$.found" }
213
215
  - { name: fp_lane_file, from: "$.file" }
216
+ - { name: fp_lane_contract_write, from: "$.contract_write" }
214
217
  - { name: fp_lane_contract_run, from: "$.contract_run" }
215
218
  - { name: fp_lane_contract, from: "$.contract" }
216
219
  - { name: fp_lane_client, from: "$.client" }
@@ -221,14 +224,14 @@ workflow:
221
224
  on_error: continue
222
225
  script: { ref: actions }
223
226
  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}}"]
227
+ args: ["--id", "{{fl_lane_id}}", "--set", "contract_write={{fp_lane_contract_write}}", "--set", "contract_run={{fp_lane_contract_run}}", "--set", "contract={{fp_lane_contract}}", "--set", "client={{fp_lane_client}}"]
225
228
  outputs:
226
229
  - { name: fp_lane_recorded, from: "$.ok" }
227
230
 
228
231
  - id: fp_lane_noted
229
232
  action: set
230
233
  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" }
234
+ vars: { fp_lane_note: "; the pack's API-document settings ({{fp_lane_file}}) recorded on the {{fl_lane_id}} lane" }
232
235
 
233
236
  # 6. The merges the rule file asks for: the file that was there (put aside) and the
234
237
  # pack's version (now on disk) become one, as the rule's instruction says.
@@ -69,6 +69,7 @@ workflow:
69
69
  fs_build_ack: ""
70
70
  fs_build_ok: ""
71
71
  fs_build_note: "build not run — /appweaver build compiles it and repairs compile errors"
72
+ fs_hdr_contract_write: ""
72
73
  fs_hdr_contract_run: ""
73
74
  fs_hdr_contract: ""
74
75
  fs_hdr_client: ""
@@ -76,20 +77,17 @@ workflow:
76
77
  fs_placeholder: ""
77
78
  fs_is_backend: "false"
78
79
  fs_is_frontend: "false"
79
- fs_need_run: "false"
80
- fs_need_url: "false"
80
+ fs_need_write: "false"
81
81
  fs_need_client: "false"
82
- fs_ans_contract_run: ""
83
- fs_ans_contract: ""
82
+ fs_ans_contract_write: ""
84
83
  fs_ans_client: ""
84
+ fs_set_write: ""
85
85
  fs_set_run: ""
86
86
  fs_set_url: ""
87
87
  fs_set_client: ""
88
- fs_cur_contract_run: ""
89
- fs_cur_contract: ""
88
+ fs_cur_contract_write: ""
90
89
  fs_cur_client: ""
91
- fs_cur_run_shown: "none — the export is skipped until one is set"
92
- fs_cur_url_shown: "none — the export is skipped until one is set"
90
+ fs_cur_write_shown: "none — the API is not checked against the plan until one is set"
93
91
  fs_cur_client_shown: "none — implement asks for one before it generates the frontend"
94
92
  fs_lane_written: ""
95
93
  fs_lane_note: ""
@@ -152,6 +150,7 @@ workflow:
152
150
  - { name: fs_source, from: "$.source" }
153
151
  - { name: fs_title, from: "$.title" }
154
152
  - { name: fs_reason, from: "$.reason" }
153
+ - { name: fs_hdr_contract_write, from: "$.contract_write" }
155
154
  - { name: fs_hdr_contract_run, from: "$.contract_run" }
156
155
  - { name: fs_hdr_contract, from: "$.contract" }
157
156
  - { name: fs_hdr_client, from: "$.client" }
@@ -170,6 +169,7 @@ workflow:
170
169
  - { name: fs_source, from: "$.source" }
171
170
  - { name: fs_title, from: "$.title" }
172
171
  - { name: fs_reason, from: "$.reason" }
172
+ - { name: fs_hdr_contract_write, from: "$.contract_write" }
173
173
  - { name: fs_hdr_contract_run, from: "$.contract_run" }
174
174
  - { name: fs_hdr_contract, from: "$.contract" }
175
175
  - { name: fs_hdr_client, from: "$.client" }
@@ -421,14 +421,17 @@ workflow:
421
421
  condition: "{{fs_build_ok}} == false"
422
422
  vars: { fs_build_note: "build FAILED — run /appweaver build, which repairs compile errors: {{fs_build_tail}}" }
423
423
 
424
- # 8. THE API CONTRACT SETTINGS — what implement uses between the lanes to export the
425
- # service's OpenAPI document and generate the frontend's client from it
426
- # (STACK-PROFILES.md §4.5). A backend lane needs its start command and document URL,
427
- # a frontend lane its client command. Each comes from the spec's header
428
- # (`# contract-run:`, `# contract:`, `# client:`, read at capture); one the header
429
- # does not give is asked for, and an empty answer keeps the lane's current value —
430
- # the stack profile's, unless something set it already. What there is, is recorded
431
- # on the lane with lane-set, as the pack route records a pack's .lane.json.
424
+ # 8. THE API CONTRACT SETTINGS — what the lanes use for the API between them
425
+ # (STACK-PROFILES.md §4.5). A frontend lane needs its client command, which generates
426
+ # its API client from the API documents drawn from the features' API plans. A backend lane may say how its
427
+ # service's as-built API document is written — `contract_write`, a command that writes
428
+ # it with no server started — which analyze-slice runs to check the API the code
429
+ # serves against the plan; or how the service is started to fetch it (`contract_run`
430
+ # + `contract`), which only the header gives. Each comes from the spec's header
431
+ # (`# contract-write:`, `# contract-run:`, `# contract:`, `# client:`, read at capture);
432
+ # the one the header does not give is asked for, and an empty answer keeps the lane's
433
+ # current value. What there is, is recorded on the lane with lane-set, as the pack
434
+ # route records a pack's .lane.json.
432
435
  - id: fs_kind_backend
433
436
  action: set
434
437
  condition: "{{fl_kind}} == backend"
@@ -437,14 +440,15 @@ workflow:
437
440
  action: set
438
441
  condition: "{{fl_kind}} == frontend"
439
442
  vars: { fs_is_frontend: "true" }
440
- - id: fs_need_run_arm
443
+ # Asked only when the header names no way at all: a start command it gives is a way.
444
+ - id: fs_need_write_arm
441
445
  action: set
442
- condition: "{{fs_hdr_contract_run}} == "
443
- vars: { fs_need_run: "{{fs_is_backend}}" }
444
- - id: fs_need_url_arm
446
+ condition: "{{fs_hdr_contract_write}} == "
447
+ vars: { fs_need_write: "{{fs_is_backend}}" }
448
+ - id: fs_need_write_run
445
449
  action: set
446
- condition: "{{fs_hdr_contract}} == "
447
- vars: { fs_need_url: "{{fs_is_backend}}" }
450
+ condition: "{{fs_hdr_contract_run}} != "
451
+ vars: { fs_need_write: "false" }
448
452
  - id: fs_need_client_arm
449
453
  action: set
450
454
  condition: "{{fs_hdr_client}} == "
@@ -458,47 +462,36 @@ workflow:
458
462
  subcommand: lane-resolve
459
463
  args: ["--id", "{{fl_lane_id}}"]
460
464
  outputs:
461
- - { name: fs_cur_contract_run, from: "$.backend_contract_run" }
462
- - { name: fs_cur_contract, from: "$.backend_contract" }
463
- - { name: fs_cur_client, from: "$.frontend_client" }
464
- - id: fs_cur_run_known
465
+ - { name: fs_cur_contract_write, from: "$.backend_contract_write" }
466
+ - { name: fs_cur_client, from: "$.frontend_client" }
467
+ - id: fs_cur_write_known
465
468
  action: set
466
- condition: "{{fs_cur_contract_run}} != "
467
- vars: { fs_cur_run_shown: "{{fs_cur_contract_run}}" }
468
- - id: fs_cur_url_known
469
- action: set
470
- condition: "{{fs_cur_contract}} != "
471
- vars: { fs_cur_url_shown: "{{fs_cur_contract}}" }
469
+ condition: "{{fs_cur_contract_write}} != "
470
+ vars: { fs_cur_write_shown: "{{fs_cur_contract_write}}" }
472
471
  - id: fs_cur_client_known
473
472
  action: set
474
473
  condition: "{{fs_cur_client}} != "
475
474
  vars: { fs_cur_client_shown: "{{fs_cur_client}}" }
476
475
 
477
- - id: fs_ask_contract_run
478
- action: ask
479
- condition: "{{fs_need_run}} == true"
480
- prompt: |
481
- How does the API in lane `{{fl_lane_id}}` start ON ITS OWN? /appweaver implement starts
482
- it with this command to fetch its OpenAPI document before it generates the frontend.
483
- The spec's header gives no `# contract-run:`.
484
- Type the command — it runs in {{fl_workdir}} — with no orchestration host and nothing
485
- the API cannot reach on this machine.
486
- Leave it empty to keep the current one: {{fs_cur_run_shown}}
487
- answer_var: fs_ans_contract_run
488
- - id: fs_ask_contract
476
+ - id: fs_ask_contract_write
489
477
  action: ask
490
- condition: "{{fs_need_url}} == true"
478
+ condition: "{{fs_need_write}} == true"
491
479
  prompt: |
492
- At what URL does that running API serve its OpenAPI document? The spec's header gives
493
- no `# contract:`.
494
- Leave it empty to keep the current one: {{fs_cur_url_shown}}
495
- answer_var: fs_ans_contract
480
+ How is the API document of lane `{{fl_lane_id}}` written, with no server started?
481
+ analyze-slice runs this command once the code is built and checks the API it
482
+ writes against backend-plan.md — the frontend lane is bound to the plan, never to
483
+ the running service. The spec's header gives no `# contract-write:`.
484
+ Type a command that writes the service's OpenAPI JSON to {{fs_placeholder}} — the
485
+ stack's own generator, or a script that loads the application and dumps its
486
+ document. It runs in {{fl_workdir}}.
487
+ Leave it empty to keep the current one: {{fs_cur_write_shown}}
488
+ answer_var: fs_ans_contract_write
496
489
  - id: fs_ask_client
497
490
  action: ask
498
491
  condition: "{{fs_need_client}} == true"
499
492
  prompt: |
500
- Which command generates this frontend's API client from the service's OpenAPI
501
- document? The spec's header gives no `# client:`.
493
+ Which command generates this frontend's API client from an OpenAPI document? The
494
+ spec's header gives no `# client:`.
502
495
  It runs in {{fl_workdir}}, and {{fs_placeholder}} in it stands for the document's path —
503
496
  for example: npx openapi-typescript {{fs_placeholder}} -o lib/api/schema.d.ts
504
497
  Leave it empty to keep the current one: {{fs_cur_client_shown}}
@@ -509,15 +502,11 @@ workflow:
509
502
  - id: fs_set_backend
510
503
  action: set
511
504
  condition: "{{fs_is_backend}} == true"
512
- vars: { fs_set_run: "{{fs_hdr_contract_run}}", fs_set_url: "{{fs_hdr_contract}}" }
513
- - id: fs_set_run_answer
514
- action: set
515
- condition: "{{fs_need_run}} == true"
516
- vars: { fs_set_run: "{{fs_ans_contract_run}}" }
517
- - id: fs_set_url_answer
505
+ vars: { fs_set_write: "{{fs_hdr_contract_write}}", fs_set_run: "{{fs_hdr_contract_run}}", fs_set_url: "{{fs_hdr_contract}}" }
506
+ - id: fs_set_write_answer
518
507
  action: set
519
- condition: "{{fs_need_url}} == true"
520
- vars: { fs_set_url: "{{fs_ans_contract}}" }
508
+ condition: "{{fs_need_write}} == true"
509
+ vars: { fs_set_write: "{{fs_ans_contract_write}}" }
521
510
  - id: fs_set_frontend
522
511
  action: set
523
512
  condition: "{{fs_is_frontend}} == true"
@@ -532,7 +521,7 @@ workflow:
532
521
  on_error: continue
533
522
  script: { ref: actions }
534
523
  subcommand: lane-set
535
- args: ["--id", "{{fl_lane_id}}", "--set", "contract_run={{fs_set_run}}", "--set", "contract={{fs_set_url}}", "--set", "client={{fs_set_client}}"]
524
+ args: ["--id", "{{fl_lane_id}}", "--set", "contract_write={{fs_set_write}}", "--set", "contract_run={{fs_set_run}}", "--set", "contract={{fs_set_url}}", "--set", "client={{fs_set_client}}"]
536
525
  outputs:
537
526
  - { name: fs_lane_written, from: "$.written" }
538
527
  - id: fs_lane_noted