aw-spec-cli 0.1.18 → 0.1.20

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 (77) hide show
  1. package/assets/.appweaver/DESIGN-FORMAT.md +63 -31
  2. package/assets/.appweaver/orchestrator/modules/delta-plan.orch.yaml +62 -14
  3. package/assets/.appweaver/orchestrator/modules/delta-spec.orch.yaml +10 -6
  4. package/assets/.appweaver/orchestrator/modules/lane-gate.orch.yaml +9 -1
  5. package/assets/.appweaver/orchestrator/modules/tech-select.orch.yaml +21 -0
  6. package/assets/.appweaver/orchestrator/orchestrator.py +23 -1
  7. package/assets/.appweaver/orchestrator/rev-templates/delta-design-template.skeleton.md +1 -1
  8. package/assets/.appweaver/orchestrator/rev-templates/design-template.skeleton.md +22 -13
  9. package/assets/.appweaver/orchestrator/rev-templates/ui-delta-design-template.skeleton.md +1 -1
  10. package/assets/.appweaver/orchestrator/rev-templates/ui-design-template.skeleton.md +6 -4
  11. package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +399 -131
  12. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan-delta.orch.yaml +1 -1
  13. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +199 -38
  14. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +1 -1
  15. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +159 -25
  16. package/assets/.appweaver/orchestrator/workflows/aw-spec-build.orch.yaml +20 -9
  17. package/assets/.appweaver/orchestrator/workflows/aw-spec-characterize.orch.yaml +1 -1
  18. package/assets/.appweaver/orchestrator/workflows/aw-spec-close-delta.orch.yaml +183 -40
  19. package/assets/.appweaver/orchestrator/workflows/aw-spec-deploy.orch.yaml +1 -1
  20. package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +311 -36
  21. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +221 -40
  22. package/assets/.appweaver/orchestrator/workflows/aw-spec-inventory.orch.yaml +217 -174
  23. package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +494 -109
  24. package/assets/.appweaver/orchestrator/workflows/aw-spec-parity.orch.yaml +1 -1
  25. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +83 -18
  26. package/assets/.appweaver/orchestrator/workflows/aw-spec-recall-delta.orch.yaml +319 -0
  27. package/assets/.appweaver/orchestrator/workflows/aw-spec-run.orch.yaml +1 -1
  28. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-feature.orch.yaml +29 -23
  29. package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +143 -34
  30. package/assets/.appweaver/scripts/bash/aw-inventory.sh +15 -0
  31. package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +878 -51
  32. package/assets/.appweaver/scripts/bash/aw-xref.sh +3 -0
  33. package/assets/.appweaver/scripts/bash/common.sh +28 -1
  34. package/assets/.appweaver/scripts/powershell/aw-inventory.ps1 +15 -0
  35. package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +868 -51
  36. package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +3 -0
  37. package/assets/.appweaver/scripts/python/aw-inventory.py +1832 -65
  38. package/assets/.appweaver/scripts/python/aw-xref.py +500 -81
  39. package/assets/.appweaver/templates/delta-design-template.md +1 -1
  40. package/assets/.appweaver/templates/delta-domain-model-template.md +1 -1
  41. package/assets/.appweaver/templates/delta-spec-template.md +4 -3
  42. package/assets/.appweaver/templates/design-template.md +56 -40
  43. package/assets/.appweaver/templates/okf-concept-template.md +11 -1
  44. package/assets/.appweaver/templates/proposed-inventory-template.md +127 -0
  45. package/assets/.appweaver/templates/source-map-template.md +5 -3
  46. package/assets/.appweaver/templates/spec-template.md +24 -21
  47. package/assets/.appweaver/templates/ui-delta-design-template.md +1 -1
  48. package/assets/.appweaver/templates/ui-design-template.md +11 -7
  49. package/assets/foundations/dotnet/ModularMonolith/ModularMonolith.pack.txt +4489 -361
  50. package/assets/foundations/dotnet/VerticalSlice/VerticalSlice.pack.txt +2274 -125
  51. package/assets/guardrails/domain/healthcare/auditing.domain.md +3 -3
  52. package/assets/guardrails/domain/pharma/auditing.domain.md +3 -3
  53. package/assets/guardrails/technology/dotnet-webapi/api-versioning.md +1 -1
  54. package/assets/guardrails/technology/dotnet-webapi/auditing.md +1 -1
  55. package/assets/guardrails/technology/dotnet-webapi/authorization.md +1 -1
  56. package/assets/guardrails/technology/dotnet-webapi/clean-architecture.md +1 -1
  57. package/assets/guardrails/technology/dotnet-webapi/cqrs.md +1 -1
  58. package/assets/guardrails/technology/dotnet-webapi/data-classification.md +1 -1
  59. package/assets/guardrails/technology/dotnet-webapi/eventing.md +1 -1
  60. package/assets/guardrails/technology/dotnet-webapi/folder-structure.md +1 -1
  61. package/assets/guardrails/technology/dotnet-webapi/modular-monolith.md +1 -1
  62. package/assets/guardrails/technology/dotnet-webapi/persistence.md +1 -1
  63. package/assets/guardrails/technology/dotnet-webapi/vertical-slices.md +1 -1
  64. package/assets/guardrails/technology/dotnet-webapi/zero-trust.md +1 -1
  65. package/assets/guardrails/technology/nextjs/accessibility-implementation.md +1 -1
  66. package/assets/guardrails/technology/nextjs/api-integration.dotnet-webapi.md +1 -1
  67. package/assets/guardrails/technology/nextjs/api-integration.java-spring.md +1 -1
  68. package/assets/guardrails/technology/nextjs/auth-session.md +1 -1
  69. package/assets/guardrails/technology/nextjs/component-architecture.md +1 -1
  70. package/assets/guardrails/technology/nextjs/design-system.md +1 -1
  71. package/assets/guardrails/technology/nextjs/error-handling.md +1 -1
  72. package/assets/guardrails/technology/nextjs/forms-and-validation.md +1 -1
  73. package/assets/guardrails/technology/nextjs/i18n.md +1 -1
  74. package/assets/guardrails/technology/nextjs/performance-budgets.md +1 -1
  75. package/assets/guardrails/technology/nextjs/routing-navigation.md +1 -1
  76. package/assets/guardrails/technology/nextjs/state-management.md +1 -1
  77. package/package.json +1 -1
@@ -42,6 +42,22 @@
42
42
  # spec.md — analyze-plan already conformed the plans to it, so the plan is the complete,
43
43
  # authoritative source for WHICH elements exist, where each lives and what surface they
44
44
  # publish, and anything missing from it is fixed in `plan`, never compensated for here.
45
+ #
46
+ # AND THAT SENTENCE IS NOW CHECKED. "analyze-plan already conformed them" was an assumption
47
+ # about the order the operator ran things in: the lane gates test that the plan FILE EXISTS
48
+ # and nothing more, so `plan` → edit spec.md → `implement` generated at full length from a
49
+ # plan nobody had held against the spec it now contradicts. analyze-plan leaves a receipt of
50
+ # the bytes it proved and plan-gate-check reads it here, on the bootstrap path, before any
51
+ # lane generates. A feature with no receipt has simply never been gated since the receipt
52
+ # existed — reported, never refused. One that HAS a receipt and no longer matches it stops.
53
+ # 3.2.0 — AND NOT delta-spec.md EITHER. The change path used to inline it "only so the
54
+ # paired tests can cite traceability ids", which made the rule true on the bootstrap path
55
+ # and false on the one where changes actually ship: a document in the payload is a document
56
+ # the generator can reason from, so a gap in a delta plan could be quietly filled from the
57
+ # requirement text and the plan never found to be wrong. Both paths now carry plan + model
58
+ # and nothing else. The ids are still cited and still required — the delta plan names the
59
+ # spec id each element serves, which is exactly what analyze-plan-delta conformed, and an
60
+ # element naming none is a planning gap to surface rather than a traceability to invent.
45
61
  # But since the model split a plan CITES an invariant by id and never restates it, so the
46
62
  # bounded context's domain-model.md is in context too, as the authority on the entity
47
63
  # names, the aggregate boundary, the lifecycle states and the TEXT of every BR-nnn the plan
@@ -76,6 +92,14 @@
76
92
  # found only elsewhere in the repository is the service lane's, and accepting it is how a
77
93
  # frontend that was never scaffolded is generated into anyway and reports green.
78
94
  #
95
+ # A LANE'S GAP DISABLES THAT LANE, NOT THE RUN. Three things a lane needs are checked per
96
+ # lane and reported per lane: its source path is on disk, its plan is there, and a project
97
+ # is scaffolded under it. Any of them missing skips that lane with the command that fixes
98
+ # it named, and every other declared lane still generates; only when NOTHING can run does
99
+ # the run stop, with all the reasons printed. The whole-run stop is reserved for states
100
+ # with no correct lane to fall back on — no lane declared at all, a lane declaring no
101
+ # source path, and two lanes claiming overlapping subtrees.
102
+ #
79
103
  # WHAT IT DOES NOT DO. It never writes spec.md, a master plan or the domain model, and it
80
104
  # never closes a change — finalize-delta merges the change's spec and model halves,
81
105
  # analyze-slice verifies the slice, and close-delta archives it and bumps the version.
@@ -91,8 +115,8 @@
91
115
  # =============================================================================
92
116
 
93
117
  name: aw-spec-implement
94
- 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 plan and its own scaffolded project, and a lane missing either 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."
95
- version: 3.0.0
118
+ description: "Generate the aggregate's source files and paired tests from its lane plans — the backend lane from backend-plan.md when a backend stack is declared, the frontend lane from frontend-plan.md when a frontend stack is, both when both are. One command covers backend-only, frontend-only and full-stack projects and absorbs the former implement-ui and implement-delta. Self-detecting off the changes on disk — with none open it generates from the master plans; with one or more open it ASKS which change and generates only what that change's delta plans mark ADDED or MODIFIED, leaving the masters untouched. Every lane is gated on its own source path, its own plan and its own scaffolded project, and a lane missing any of them is reported and skipped rather than failing the other lane; each runnable lane confirms before it writes. Model-driven, and it does not read the spec — analyze-plan already conformed the plans to it. The plan says WHAT to build; the bounded context's domain-model.md, in context alongside it, says what the domain IS — the entity names, the boundary and the text of every invariant the plan cites by id — joined on the delta path by that change's own model delta, since the master is not merged until finalize-delta. The stack binds here from the lane declaration and the constitution halves; the plans stay technology-agnostic."
119
+ version: 3.4.0
96
120
  agent_agnostic: true
97
121
  dialect: aw-spec.orchestrator/v1
98
122
 
@@ -138,6 +162,20 @@ vars:
138
162
  delta_frontend_plan: ""
139
163
  delta_frontend_plan_exists: "false"
140
164
  wave: ""
165
+ # THE GATE RECEIPT — was this feature's plan pair ever PROVEN against this spec, and has
166
+ # anything moved since. analyze-plan writes it on a green master run; this phase is the
167
+ # reader. Bootstrap path only: a delta generates from delta plans, which analyze-plan-delta
168
+ # gates, and the master receipt says nothing about those.
169
+ gate_tracked: "false"
170
+ gate_fresh: "false"
171
+ gate_reason: ""
172
+ gate_at: ""
173
+ # DEFAULTED PERMISSIVE, DELIBERATELY, and it is the one flag here that is. Every tree that
174
+ # predates the receipt has none, and "never gated" is an unknown, not a stale plan —
175
+ # refusing on a missing file would block every existing feature on its first run. The flag
176
+ # is lowered only by a receipt that EXISTS and no longer matches: that tree has been through
177
+ # the gate once, so the bytes moving since is a fact, not an absence of one.
178
+ may_generate: "true"
141
179
  # PER-LANE STATE. Each lane resolves its own plan, its own project and its own answer;
142
180
  # a lane that is not declared never resolves any of them and its steps never run.
143
181
  # The engine has no compound conditions, so each AND is an ordered assignment: a step
@@ -182,22 +220,32 @@ workflow:
182
220
 
183
221
  # 0. THE LANE MODEL. lane-verify with NO --require, because this phase requires neither
184
222
  # kind: a backend-only and a frontend-only project are both correct states, and which
185
- # lanes exist is exactly what it is here to find out. It still reports every DECLARED
186
- # lane whose source path is empty, missing or overlapping another's — a stack named
187
- # against a path that is not there is a mistake in a phase that writes code — and it
188
- # always exits 0, so the reason reaches the gate below as text.
223
+ # lanes exist is exactly what it is here to find out.
224
+ #
225
+ # --allow-missing-root, because a lane whose source path is not on disk is THAT LANE'S
226
+ # gap, not the run's. This phase is per-lane throughout — a missing plan or a missing
227
+ # project is reported at step 5 and the other lane still generates — and a missing root
228
+ # is the same kind of gap, most often a declared frontend that `foundation` has not
229
+ # unpacked yet. With the check left on it arrives here instead, as a whole-run stop
230
+ # that takes the ready lane down with it. It is re-checked per lane at step 5 from
231
+ # `*_root_exists`, which lane-verify reports whether or not the on-disk half is armed.
232
+ #
233
+ # What still stops the run: no lane declared at all, a lane declaring no source path,
234
+ # and two lanes claiming overlapping subtrees. Those are declaration errors with no
235
+ # correct lane to fall back to, and none of them is a state any phase can work around.
189
236
  - id: lane_verify
190
237
  action: execute_script
191
238
  on_error: continue
192
239
  script: { ref: actions }
193
240
  subcommand: lane-verify
194
- args: ["--command", "/appweaver implement"]
241
+ args: ["--command", "/appweaver implement", "--allow-missing-root"]
195
242
  outputs:
196
243
  - { name: lane_ok, from: "$.ok" }
197
244
  - { name: lane_issues, from: "$.message" }
198
245
  - { name: be_declared, from: "$.backend_declared" }
199
246
  - { name: be_lane_id, from: "$.backend_lane_id" }
200
247
  - { name: be_source_path, from: "$.backend_source_path" }
248
+ - { name: be_root_exists, from: "$.backend_root_exists" }
201
249
  - { name: be_profile, from: "$.backend_profile" }
202
250
  - { name: be_title, from: "$.backend_title" }
203
251
  - { name: be_language, from: "$.backend_language" }
@@ -207,6 +255,7 @@ workflow:
207
255
  - { name: fe_declared, from: "$.frontend_declared" }
208
256
  - { name: fe_lane_id, from: "$.frontend_lane_id" }
209
257
  - { name: fe_source_path, from: "$.frontend_source_path" }
258
+ - { name: fe_root_exists, from: "$.frontend_root_exists" }
210
259
  - { name: fe_profile, from: "$.frontend_profile" }
211
260
  - { name: fe_title, from: "$.frontend_title" }
212
261
  - { name: fe_language, from: "$.frontend_language" }
@@ -244,7 +293,7 @@ workflow:
244
293
  action: verify_context
245
294
  require_non_empty: feature_dir
246
295
  on_empty: stop
247
- on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the aggregate, e.g. Order), then re-run implement."
296
+ on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the feature, e.g. Order or PatientRegistration), then re-run implement."
248
297
 
249
298
  # The bounded context's DOMAIN MODEL — in every generating lane's payload as the authority
250
299
  # on what the plan's cited BR-nnn actually require and on the ubiquitous language, and the
@@ -260,6 +309,21 @@ workflow:
260
309
  - { name: domain_model, from: "$.domain_model" }
261
310
  - { name: domain_model_exists, from: "$.domain_model_exists" }
262
311
 
312
+ # WHICH PART OF THAT MODEL IS THIS FEATURE'S. The model is filed by aggregate, the specs
313
+ # tree by feature, and the two names differ for a feature that is one capability over a
314
+ # shared aggregate (PatientRegistration over Patient) — reading the model by the
315
+ # feature's name finds no section there. Resolved from the spec's Acts-on line, then the
316
+ # boundary map, then the feature name, so a 1:1 feature resolves to exactly the section
317
+ # this workflow always read.
318
+ - id: feature_aggregates
319
+ action: execute_script
320
+ script: { ref: xref }
321
+ subcommand: feature-aggregates
322
+ args: ["--feature-dir", "{{feature_dir}}"]
323
+ outputs:
324
+ - { name: owned_csv, from: "$.owned_csv" }
325
+ - { name: owned_sections, from: "$.owned_sections" }
326
+
263
327
  # 2. WHICH CHANGES ARE OPEN — the evidence is changes/<CR-id>/ on disk; a closed change
264
328
  # has moved to changes/archive/. Any open change makes this run a DELTA run.
265
329
  # `matched_label` resolves a CR id passed as the argument to the whole option string,
@@ -318,6 +382,10 @@ workflow:
318
382
  outputs:
319
383
  - { name: cr_id, from: "$.cr_id" }
320
384
  - { name: cr_dir, from: "$.cr_dir" }
385
+ # Resolved for reporting and DELIBERATELY NOT put in any context — see the header.
386
+ # This phase realises the delta PLAN; the requirement text reaching the generator is
387
+ # what lets a gap in the plan be compensated for instead of found. Do not add it to
388
+ # ctx_backend_delta / ctx_frontend_delta.
321
389
  - { name: delta_spec, from: "$.delta_spec" }
322
390
  # THIS CHANGE'S MODEL HALF — the structure the delta plans realise. It is context for
323
391
  # every generating lane, exactly as the master model is: the delta plan cites the
@@ -350,6 +418,49 @@ workflow:
350
418
  action: set
351
419
  vars: { fe_plan: "{{delta_frontend_plan}}", fe_plan_ok: "{{delta_frontend_plan_exists}}" }
352
420
 
421
+ # 3b. WERE THESE PLANS PROVEN AGAINST THIS SPEC. This phase does not read spec.md — the
422
+ # header's reason is "analyze-plan already conformed the plans to it", which was an
423
+ # assumption about the order the operator ran things in and was checked by nothing:
424
+ # the lane gates below test that the plan FILE EXISTS and stop there. `plan`, edit
425
+ # spec.md, `implement` therefore generated from a plan that was never held against the
426
+ # spec it now contradicts, silently and at full length.
427
+ #
428
+ # plan-gate-check compares today's bytes against the receipt analyze-plan left when it
429
+ # passed — spec.md, the context's domain-model.md and each gated lane plan. The plans'
430
+ # own `**Version**:` headers cannot answer this and must not be used: close-delta stamps
431
+ # them and spec.md to the same number in lockstep whether or not a plan was re-derived,
432
+ # so the comparison reads equal always.
433
+ #
434
+ # BOOTSTRAP ONLY. A delta run generates from delta plans that analyze-plan-delta gates;
435
+ # the master receipt is silent about them, and reading it there would refuse a change
436
+ # for drift in documents this run is not building from.
437
+ - id: plan_gate_check
438
+ condition: "{{mode}} == bootstrap"
439
+ action: execute_script
440
+ script: { ref: actions }
441
+ subcommand: plan-gate-check
442
+ args: ["--feature-dir", "{{feature_dir}}"]
443
+ outputs:
444
+ - { name: gate_tracked, from: "$.tracked" }
445
+ - { name: gate_fresh, from: "$.gate_fresh" }
446
+ - { name: gate_reason, from: "$.reason" }
447
+ - { name: gate_at, from: "$.gated" }
448
+
449
+ # The ordered assignment is the AND: only a TRACKED feature can lower the flag, and
450
+ # what lowers it is the gate no longer being fresh. An untracked one never reaches
451
+ # this step's assignment and generates, which is the whole accommodation for trees
452
+ # that have not been gated since the receipt existed.
453
+ - id: flag_gate_block
454
+ condition: "{{gate_tracked}} == true"
455
+ action: set
456
+ vars: { may_generate: "{{gate_fresh}}" }
457
+
458
+ - id: require_gate_fresh
459
+ action: verify_context
460
+ require_true: may_generate
461
+ on_false: stop
462
+ on_false_message: "implement REFUSED — the master plans are no longer the ones analyze-plan passed: {{gate_reason}}. This phase generates from the plans and does NOT read the spec, on the strength of that gate having conformed them to it, so generating now would realise a plan against requirements nobody checked it against. Re-run /appweaver analyze-plan — it re-checks all four checks, repairs the drifting plan on approval, and re-issues the receipt — then re-run /appweaver implement. If what changed was a plan you re-derived with /appweaver plan main, that is the same path: the re-derived plans have not been gated yet. Nothing was generated."
463
+
353
464
  # 4. THE PROJECT each declared lane generates INTO. Deterministic structure gate:
354
465
  # detect-project searches the LANE'S OWN subtree for an ecosystem manifest (.sln/
355
466
  # .csproj, pom.xml, build.gradle, package.json, go.mod, Cargo.toml, pyproject.toml),
@@ -396,6 +507,12 @@ workflow:
396
507
  require_true: be_plan_ok
397
508
  on_false: continue
398
509
  on_false_message: "BACKEND LANE SKIPPED — lane `{{be_lane_id}}` ({{be_title}}) is declared in .appweaver/stacks.json, but the plan this run generates from is not there: {{be_plan}}. Run /appweaver plan (then /appweaver analyze-plan for the masters, or /appweaver analyze-plan-delta for a change) and re-run implement. Nothing is generated for this lane; any other declared lane still runs."
510
+ - id: warn_be_root
511
+ condition: "{{be_declared}} == true"
512
+ action: verify_context
513
+ require_true: be_root_exists
514
+ on_false: continue
515
+ on_false_message: "BACKEND LANE SKIPPED — lane `{{be_lane_id}}` ({{be_title}}) declares source path `{{be_source_path}}` in .appweaver/stacks.json, and that path is not a directory in this repository. A lane owns its subtree and this phase writes into it, so there is nothing to generate into: unpack a project there with /appweaver foundation <FoundationName> <AppName> where a bundle ships for {{be_profile}}, create one with the stack's own tooling, or re-run /appweaver select-stack and point the lane at the directory that really holds its source. Nothing is generated for this lane; any other declared lane still runs."
399
516
  - id: warn_be_project
400
517
  condition: "{{be_declared}} == true"
401
518
  action: verify_context
@@ -408,6 +525,12 @@ workflow:
408
525
  require_true: fe_plan_ok
409
526
  on_false: continue
410
527
  on_false_message: "FRONTEND LANE SKIPPED — lane `{{fe_lane_id}}` ({{fe_title}}) is declared in .appweaver/stacks.json, but the plan this run generates from is not there: {{fe_plan}}. Run /appweaver plan (then /appweaver analyze-plan for the masters, or /appweaver analyze-plan-delta for a change) and re-run implement. Nothing is generated for this lane; any other declared lane still runs."
528
+ - id: warn_fe_root
529
+ condition: "{{fe_declared}} == true"
530
+ action: verify_context
531
+ require_true: fe_root_exists
532
+ on_false: continue
533
+ on_false_message: "FRONTEND LANE SKIPPED — lane `{{fe_lane_id}}` ({{fe_title}}) declares source path `{{fe_source_path}}` in .appweaver/stacks.json, and that path is not a directory in this repository. A lane owns its subtree and this phase writes into it, so there is nothing to generate into: unpack a project there with /appweaver foundation <FoundationName> <AppName> where a bundle ships for {{fe_profile}}, create one with the stack's own tooling, or re-run /appweaver select-stack and point the lane at the directory that really holds its source. Nothing is generated for this lane; any other declared lane still runs."
411
534
  - id: warn_fe_project
412
535
  condition: "{{fe_declared}} == true"
413
536
  action: verify_context
@@ -415,14 +538,18 @@ workflow:
415
538
  on_false: continue
416
539
  on_false_message: "FRONTEND LANE SKIPPED — no frontend project found under the {{fe_lane_id}} lane's source path `{{fe_source_path}}`. detect-project searched that subtree and then the whole repository, and answered at scope `{{fe_project_scope}}`; `lane` is the only scope this lane accepts. Scaffold a project there — /appweaver foundation <FoundationName> <AppName> where a bundle ships for {{fe_profile}}, or the stack's own tooling — and re-run implement. A manifest found elsewhere in the repository does NOT count: that one belongs to another lane, and generating this lane's code against it is how a frontend that was never built reports green."
417
540
 
418
- # 6. RUNNABLE = declared AND its plan is there AND its project is there, as an ordered
419
- # assignment plus a knockdown, since the engine has no compound conditions. An
420
- # undeclared lane stays false either way — its `declared` value is what the raise
421
- # assigns.
541
+ # 6. RUNNABLE = declared AND its source path is on disk AND its plan is there AND its
542
+ # project is there, as one ordered assignment plus a knockdown per condition, since
543
+ # the engine has no compound conditions. An undeclared lane stays false either way —
544
+ # its `declared` value is what the raise assigns.
422
545
  - id: be_runnable_raise
423
546
  condition: "{{be_plan_ok}} == true"
424
547
  action: set
425
548
  vars: { be_runnable: "{{be_declared}}" }
549
+ - id: be_runnable_clear_root
550
+ condition: "{{be_root_exists}} == false"
551
+ action: set
552
+ vars: { be_runnable: "false" }
426
553
  - id: be_runnable_clear
427
554
  condition: "{{be_project_ok}} == false"
428
555
  action: set
@@ -431,6 +558,10 @@ workflow:
431
558
  condition: "{{fe_plan_ok}} == true"
432
559
  action: set
433
560
  vars: { fe_runnable: "{{fe_declared}}" }
561
+ - id: fe_runnable_clear_root
562
+ condition: "{{fe_root_exists}} == false"
563
+ action: set
564
+ vars: { fe_runnable: "false" }
434
565
  - id: fe_runnable_clear
435
566
  condition: "{{fe_project_ok}} == false"
436
567
  action: set
@@ -471,7 +602,7 @@ workflow:
471
602
  action: verify_context
472
603
  require_true: generate_any
473
604
  on_false: stop
474
- on_false_message: "Nothing to implement — no declared lane is both ready and confirmed. Either every declared lane was skipped for a reason printed above (its plan or its project is missing), or each one that was ready was declined at the prompt. Backend lane declared {{be_declared}}, ready {{be_runnable}}; frontend lane declared {{fe_declared}}, ready {{fe_runnable}}. Fix the reasons named above, or re-run and confirm a lane."
605
+ on_false_message: "Nothing to implement — no declared lane is both ready and confirmed. Either every declared lane was skipped for a reason printed above (its source path, its plan or its project is missing), or each one that was ready was declined at the prompt. Backend lane declared {{be_declared}}, ready {{be_runnable}}; frontend lane declared {{fe_declared}}, ready {{fe_runnable}}. Fix the reasons named above, or re-run and confirm a lane."
475
606
 
476
607
  # 8. EACH GENERATING LANE'S authoring MODE — the run's mode where the lane is going, and
477
608
  # `skip` where it is not, which silences both of that lane's branches without a
@@ -490,18 +621,21 @@ workflow:
490
621
  # backend's heading is keyed by mode (see the header). No blocking gate: an
491
622
  # unconfigured project soft-continues, since the constitution halves still carry each
492
623
  # lane's ratified base.
624
+ #
625
+ # EACH LANE'S RULES ARE LOADED BESIDE ITS OWN STEPS, AND THE SECOND LANE REPLACES
626
+ # THE FIRST'S. Both includes used to run here, before either lane generated — and
627
+ # sticky standards only accumulate, so on a full-stack run the backend generator was
628
+ # shown `## /aw-spec-implement-ui` and the frontend generator the service set. Each
629
+ # prompt says which lane it is in, but it said so inside an envelope that contradicted
630
+ # it, and the envelope is the part the model cannot argue with. The frontend include
631
+ # now sits immediately before the FRONTEND sections below and passes replace, which
632
+ # resets to the workflow's declared seed (ai-rules) before loading the UI set.
493
633
  - id: tech_select
494
634
  condition: "{{be_go}} == true"
495
635
  action: include
496
636
  module: .appweaver/orchestrator/modules/tech-select.orch.yaml
497
637
  with: { command: "{{be_tech_command}}" }
498
638
 
499
- - id: tech_select_ui
500
- condition: "{{fe_go}} == true"
501
- action: include
502
- module: .appweaver/orchestrator/modules/tech-select.orch.yaml
503
- with: { command: aw-spec-implement-ui }
504
-
505
639
  # 10. Pre-execution hooks — see the hook-phase vars above.
506
640
  - id: resolve_pre_hooks
507
641
  action: execute_script
@@ -551,9 +685,9 @@ workflow:
551
685
  - THE MODEL IS READ, NEVER MINED FOR WORK. It describes the WHOLE bounded context,
552
686
  while this run builds one feature. Realise only what the PLAN calls for: an entity,
553
687
  event or invariant in the model that no plan element realises is a PLANNING gap —
554
- surface it, do not implement it. `{{feature}}` is the aggregate this feature owns;
555
- any other `## Aggregate:` section belongs to another slice, and you read it only to
556
- honour a reference the plan makes, never to generate its code.
688
+ surface it, do not implement it. The section(s) this feature answers for are
689
+ {{owned_sections}}; any other `## Aggregate:` section you read only to honour a
690
+ reference the plan makes, never to generate its code.
557
691
  - BIND THE STACK: this lane is `{{be_lane_id}}` — {{be_title}}, language
558
692
  {{be_language}} ({{be_ecosystem}}), declared in .appweaver/stacks.json and compiled
559
693
  with `{{be_build}}`, tested with `{{be_test}}`. Generate in THAT language and
@@ -596,7 +730,14 @@ workflow:
596
730
  - Do NOT add behaviour the plan does not state, and do NOT omit any it does. If the
597
731
  plan looks wrong or incomplete, STOP and surface it (it is fixed in plan /
598
732
  analyze-plan) — never compensate from the spec or from memory.
599
- - This aggregate OWNS its files; never regenerate a file another feature owns.
733
+ - SHARED AGGREGATE FILES ARE EXTENDED, NEVER REGENERATED. A feature is a capability
734
+ scope, and other features may act on the same aggregate: its root, entities,
735
+ value objects and repository are shared code. Where such a file already exists,
736
+ ADD only the members this plan needs and leave every existing member, signature
737
+ and test exactly as it is — another feature's code depends on them. Create a file
738
+ only when it does not exist yet. A file that serves only another feature (its
739
+ use-case, endpoint or handler) is not this feature's to touch; if this plan needs
740
+ one changed, STOP and surface it.
600
741
  - Where generated code depends on a plan Assumption (DA-n) or Workaround (WA-n), mark
601
742
  it with a comment citing that id so it is easy to revisit later.
602
743
  outputs:
@@ -605,14 +746,22 @@ workflow:
605
746
  # ═══════════ BACKEND LANE — delta, generate ONLY what the change adds ════════
606
747
  # The delta plan is the authoritative source of WHAT changed — analyze-plan-delta already
607
748
  # conformed it to the delta spec. The MASTER plan is the BASE (the unchanged elements the
608
- # delta depends on), and the delta spec is here ONLY so the paired tests can cite
609
- # traceability ids.
749
+ # delta depends on).
750
+ #
751
+ # NO DELTA-SPEC HERE, exactly as the bootstrap path carries no spec. It used to be
752
+ # inlined "only so the paired tests can cite traceability ids", but a document in the
753
+ # payload is a document the generator can reason from, and the whole point of the
754
+ # contract is that this phase realises the PLAN: with the requirement text in reach, a
755
+ # gap in the plan can be silently compensated for from the spec and the plan is never
756
+ # found to be wrong. The ids are still available and still required — the delta plan
757
+ # cites the spec id each element serves, which is what analyze-plan-delta conformed.
758
+ # A plan that does not name an id is a planning gap to surface, and that is now visible
759
+ # instead of being papered over here.
610
760
  - id: ctx_backend_delta
611
761
  condition: "{{be_mode}} == delta"
612
762
  action: build_context
613
763
  context:
614
764
  - { path: "{{be_plan}}" } # this change's delta-backend-plan.md
615
- - { path: "{{delta_spec}}", optional: true }
616
765
  - { path: "{{delta_model}}", optional: true } # this change's MODEL half — what it ADDS to / CHANGES in the model
617
766
  - { path: "{{backend_plan}}", optional: true } # the master plan — READ ONLY, never amended here
618
767
  - { path: "{{domain_model}}", optional: true } # the master model — the domain as it stands BEFORE this change
@@ -642,9 +791,12 @@ workflow:
642
791
  winning wherever they differ — it is the newer statement, and the master has not
643
792
  been merged yet (finalize-delta does that after this phase). Read there what every
644
793
  BR-nnn the delta plan cites actually requires; the plan records WHERE each is
645
- enforced and never restates it. You have NO domain rule files and NO spec beyond
646
- the delta-spec's ids — if a domain specific is in none of them, STOP and surface it
647
- (it is fixed in specify / plan / analyze-plan-delta), never invent it from memory.
794
+ enforced and never restates it. You have NO domain rule files and NO spec AT ALL —
795
+ not the master and not this change's delta-spec. The requirement ids are in the
796
+ delta plan, which cites the one each element serves; that is the whole of what you
797
+ get. If a domain specific is in neither the plan nor the model, STOP and surface it
798
+ (it is fixed in specify / plan / analyze-plan-delta), never invent it from memory
799
+ and never infer it from an id.
648
800
  - THE MODEL IS READ, NEVER MINED FOR WORK. Realise only what the DELTA PLAN calls
649
801
  for: an item in the model delta that no delta-plan element realises is a PLANNING
650
802
  gap — surface it, do not implement it. Everything in the master model that this
@@ -682,7 +834,10 @@ workflow:
682
834
  the port isn't present, code to it and note that as the prerequisite.
683
835
  - Author a paired test for every ADDED/MODIFIED behaviour / use-case / endpoint;
684
836
  derive the cases from the delta plan's pre/post/invariant contracts and cite the
685
- delta-spec id (FR / BR / CN / DC / IC / US).
837
+ requirement id THE DELTA PLAN NAMES for that element (FR / BR / CN / DC / IC / US).
838
+ The spec is not in context: take the id from the plan and cite it verbatim, never
839
+ guess one from the element's shape. A delta-plan element that names no id is a
840
+ planning gap — surface it rather than inventing the traceability.
686
841
  - REGRESSION: for each element in the delta plan's "Regression targets", verify (add
687
842
  or adjust a test where needed) that the existing behaviour still holds after a
688
843
  shared element changed — but do NOT rewrite that existing behaviour.
@@ -714,6 +869,22 @@ workflow:
714
869
  outputs:
715
870
  - { name: okf_concept, from: "$.concept" }
716
871
 
872
+ # ══════════════ THE LANE SWAP — the frontend's rules replace the backend's ═══
873
+ # build_context already scoped the two lanes' PAYLOADS apart; this is the same boundary
874
+ # for their STANDARDS, which the envelope carries and which nothing used to reset. It
875
+ # runs here rather than up with the backend's include because standards accumulate:
876
+ # loading both before either lane generated is precisely what showed each generator the
877
+ # other lane's rule set. `replace` resets to the declared seed (ai-rules stays) and then
878
+ # loads `## /aw-spec-implement-ui`, so from here down the envelope is the frontend's.
879
+ #
880
+ # Nothing after the frontend sections regenerates backend code — the OKF steps that
881
+ # follow run include_standards: false — so the swap needs no undo.
882
+ - id: tech_select_ui
883
+ condition: "{{fe_go}} == true"
884
+ action: include
885
+ module: .appweaver/orchestrator/modules/tech-select.orch.yaml
886
+ with: { command: aw-spec-implement-ui, replace: "true" }
887
+
717
888
  # ═══════════ FRONTEND LANE — bootstrap, generate from the MASTER plan ════════
718
889
  # The experience twin of the service generation, one lane over. Its own build_context,
719
890
  # because the envelope persists across steps and the two lanes must not see each other's
@@ -815,7 +986,8 @@ workflow:
815
986
  action: build_context
816
987
  context:
817
988
  - { path: "{{fe_plan}}" } # this change's delta-frontend-plan.md
818
- - { path: "{{delta_spec}}", optional: true }
989
+ # No delta-spec — see the service lane's note above: this phase realises the PLAN,
990
+ # and the ids the tests cite come from the plan's own rows.
819
991
  - { path: "{{delta_model}}", optional: true } # this change's MODEL half — new terms, states and rules the UI must speak
820
992
  - { path: "{{frontend_plan}}", optional: true } # the master plan — READ ONLY, never amended here
821
993
  - { path: "{{domain_model}}", optional: true } # the master model — the ubiquitous language as it stands BEFORE this change
@@ -846,9 +1018,10 @@ workflow:
846
1018
  READ TOGETHER, the delta winning wherever they differ — the master has not been
847
1019
  merged yet. A TERM THIS CHANGE INTRODUCES OR RENAMES IS IN THE DELTA, and the UI
848
1020
  must speak it exactly as the delta spells it, in labels, states and messages. Read
849
- there what any BR-nnn the delta plan cites requires. NO domain rule files, and no
850
- spec beyond the delta-spec's ids; if a domain specific is in none of them, STOP and
851
- surface it.
1021
+ there what any BR-nnn the delta plan cites requires. NO domain rule files, and NO
1022
+ spec at all — not the master and not this change's delta-spec; the requirement ids
1023
+ come from the delta plan, which cites the one each element serves. If a domain
1024
+ specific is in neither the plan nor the model, STOP and surface it.
852
1025
  - THE MODEL IS READ, NEVER MINED FOR WORK, and reading a rule is not permission to
853
1026
  move it. Realise only what the DELTA PLAN calls for — an item in the model delta
854
1027
  that no delta-plan element realises is a PLANNING gap, and a client-side check
@@ -872,7 +1045,9 @@ workflow:
872
1045
  evaluation.
873
1046
  - Author a paired test for every ADDED/MODIFIED screen, interaction and state,
874
1047
  including the unwanted-behaviour states and an accessibility assertion per
875
- interactive surface, citing the delta-spec id (UX-/US-/FR-/CN-/DC-/IC-/SC-).
1048
+ interactive surface, citing the requirement id THE DELTA PLAN NAMES for that row
1049
+ (UX-/US-/FR-/CN-/DC-/IC-/SC-). The spec is not in context: take the id from the
1050
+ plan and cite it verbatim. A row that names no id is a planning gap — surface it.
876
1051
  - REGRESSION: for each element in the delta plan's regression targets, verify (add or
877
1052
  adjust a test where needed) that the existing behaviour still holds after a shared
878
1053
  component or state container changed — but do NOT rewrite that behaviour.
@@ -959,15 +1134,15 @@ workflow:
959
1134
  schema: { overview: "<2-4 sentences of prose>" }
960
1135
  mock: { overview: "" }
961
1136
  task: |
962
- Write the OKF knowledge-concept Overview for the aggregate `{{feature}}`. 2-4
963
- sentences of PROSE:
1137
+ Write the OKF knowledge-concept Overview for the feature `{{feature}}` — that is,
1138
+ for the aggregate it answers for, {{owned_csv}}. 2-4 sentences of PROSE:
964
1139
  - name the aggregate ROOT and what the aggregate IS;
965
1140
  - state the INVARIANTS it guards (its BR-nnn — what they require, not their ids);
966
1141
  - state its TRANSACTION BOUNDARY — what it OWNS inside the boundary vs what it only
967
1142
  REFERENCES by id.
968
1143
  The bounded context's domain-model.md is the source where it is in context: read
969
- ONLY its `## Aggregate: {{feature}}` section — the other aggregates are other
970
- slices' concepts. Where it is not in context, distil the same three things from
1144
+ ONLY {{owned_sections}} — the other sections belong to other aggregates. Where more
1145
+ than one is named, describe the FIRST. Where it is not in context, distil the same three things from
971
1146
  backend-plan.md's domain section instead.
972
1147
  Plain prose only: no headings, no bullets, no code fences, no file paths, and no
973
1148
  restating of the dependency edges (those live in the Depends-On / Cross-References
@@ -1010,5 +1185,11 @@ outputs:
1010
1185
  delta_model: "{{delta_model}}" # its second half on the delta path — empty on the bootstrap path
1011
1186
  okf_concept: "{{okf_concept}}"
1012
1187
  xref_edge_count: "{{xref_edge_count}}"
1013
- 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."
1188
+ # Bootstrap path only. tracked=false means this feature has never passed analyze-plan
1189
+ # since the gate began leaving a receipt — an unknown, not a stale plan, and it blocks
1190
+ # nothing.
1191
+ plan_gate_tracked: "{{gate_tracked}}"
1192
+ plan_gate_fresh: "{{gate_fresh}}"
1193
+ plan_gate_at: "{{gate_at}}"
1194
+ note: "Lanes come from .appweaver/stacks.json — backend declared {{be_declared}} (ready {{be_runnable}}, implemented {{be_go}}), frontend declared {{fe_declared}} (ready {{fe_runnable}}, implemented {{fe_go}}). A declared lane that was skipped printed its reason above and did not stop the other one; an undeclared lane produces nothing and that is not a failure. Each generating lane read its PLAN for what to build and {{domain_model}} for what the domain is — plus {{delta_model}} on the delta path, since the master model does not carry the change until finalize-delta merges it. Every one of those documents was READ and none was written: this phase generates code and nothing else. On the bootstrap path the plans were checked against analyze-plan's receipt before anything generated (tracked={{gate_tracked}}, fresh={{gate_fresh}}, gated {{gate_at}}) — tracked=false means this feature has never passed analyze-plan since the gate began recording what it proved, which is an unknown rather than a stale plan and blocks nothing; run /appweaver analyze-plan once to start the record."
1014
1195
  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."