aw-spec-cli 0.1.19 → 0.1.21

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/assets/.appweaver/DESIGN-FORMAT.md +17 -9
  2. package/assets/.appweaver/ORCH-DIALECT.md +8 -3
  3. package/assets/.appweaver/orchestrator/modules/delta-spec.orch.yaml +26 -3
  4. package/assets/.appweaver/orchestrator/orchestrator.py +65 -3
  5. package/assets/.appweaver/orchestrator/rev-templates/delta-design-template.skeleton.md +1 -1
  6. package/assets/.appweaver/orchestrator/rev-templates/design-template.skeleton.md +4 -2
  7. package/assets/.appweaver/orchestrator/rev-templates/ui-delta-design-template.skeleton.md +1 -1
  8. package/assets/.appweaver/orchestrator/rev-templates/ui-design-template.skeleton.md +1 -1
  9. package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +121 -42
  10. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan-delta.orch.yaml +7 -7
  11. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +145 -52
  12. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +18 -10
  13. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +306 -28
  14. package/assets/.appweaver/orchestrator/workflows/aw-spec-build.orch.yaml +1 -1
  15. package/assets/.appweaver/orchestrator/workflows/aw-spec-characterize.orch.yaml +1 -1
  16. package/assets/.appweaver/orchestrator/workflows/aw-spec-close-delta.orch.yaml +1 -1
  17. package/assets/.appweaver/orchestrator/workflows/aw-spec-constitution.orch.yaml +4 -4
  18. package/assets/.appweaver/orchestrator/workflows/aw-spec-deploy.orch.yaml +1 -1
  19. package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +114 -30
  20. package/assets/.appweaver/orchestrator/workflows/aw-spec-foundation.orch.yaml +140 -36
  21. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +385 -61
  22. package/assets/.appweaver/orchestrator/workflows/aw-spec-inventory.orch.yaml +217 -174
  23. package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +407 -107
  24. package/assets/.appweaver/orchestrator/workflows/aw-spec-parity.orch.yaml +1 -1
  25. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +49 -11
  26. package/assets/.appweaver/orchestrator/workflows/aw-spec-recall-delta.orch.yaml +1 -1
  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-select-stack.orch.yaml +47 -23
  30. package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +19 -11
  31. package/assets/.appweaver/scripts/bash/aw-inventory.sh +15 -0
  32. package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +683 -92
  33. package/assets/.appweaver/scripts/bash/aw-xref.sh +3 -0
  34. package/assets/.appweaver/scripts/powershell/aw-inventory.ps1 +15 -0
  35. package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +625 -64
  36. package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +3 -0
  37. package/assets/.appweaver/scripts/python/aw-crstate.py +194 -0
  38. package/assets/.appweaver/scripts/python/aw-foundation.py +151 -0
  39. package/assets/.appweaver/scripts/python/aw-inventory.py +1829 -63
  40. package/assets/.appweaver/scripts/python/aw-payload-parity.py +3 -0
  41. package/assets/.appweaver/scripts/python/aw-plan-coverage.py +423 -0
  42. package/assets/.appweaver/scripts/python/aw-stack.py +251 -28
  43. package/assets/.appweaver/scripts/python/aw-testlog.py +148 -0
  44. package/assets/.appweaver/scripts/python/aw-xref.py +500 -81
  45. package/assets/.appweaver/stack-profiles/dotnet-webapi.json +1 -0
  46. package/assets/.appweaver/stack-profiles/java-spring.json +1 -0
  47. package/assets/.appweaver/stack-profiles/node-express.json +1 -0
  48. package/assets/.appweaver/stack-profiles/python-fastapi.json +1 -0
  49. package/assets/.appweaver/templates/constitution-domain-template.md +17 -2
  50. package/assets/.appweaver/templates/delta-design-template.md +1 -1
  51. package/assets/.appweaver/templates/delta-domain-model-template.md +1 -1
  52. package/assets/.appweaver/templates/delta-spec-template.md +4 -3
  53. package/assets/.appweaver/templates/design-template.md +16 -11
  54. package/assets/.appweaver/templates/okf-concept-template.md +11 -1
  55. package/assets/.appweaver/templates/proposed-inventory-template.md +127 -0
  56. package/assets/.appweaver/templates/source-map-template.md +5 -3
  57. package/assets/.appweaver/templates/spec-template.md +17 -17
  58. package/assets/.appweaver/templates/ui-delta-design-template.md +1 -1
  59. package/assets/.appweaver/templates/ui-design-template.md +4 -4
  60. package/assets/foundations/dotnet/ModularMonolith/ModularMonolith.pack.txt +4489 -361
  61. package/assets/foundations/dotnet/VerticalSlice/VerticalSlice.pack.txt +2274 -125
  62. package/assets/guardrails/projects/dotnet-default-project/domain-rule-bindings.md +10 -6
  63. package/assets/guardrails/projects/dotnet-default-project/tech-rule-bindings.md +4 -0
  64. package/assets/guardrails/technology/dotnet-webapi/auditing.md +3 -1
  65. package/assets/guardrails/technology/dotnet-webapi/coding-standards.md +5 -3
  66. package/assets/guardrails/technology/dotnet-webapi/cqrs.md +5 -1
  67. package/assets/guardrails/technology/dotnet-webapi/domain-driven-design.md +13 -5
  68. package/assets/guardrails/technology/dotnet-webapi/examples/cqrs.md +2 -0
  69. package/assets/guardrails/technology/dotnet-webapi/examples/domain-driven-design.md +6 -2
  70. package/assets/guardrails/technology/dotnet-webapi/framework-api-map.md +70 -0
  71. package/assets/guardrails/technology/dotnet-webapi/technology-constitution.md +3 -0
  72. package/assets/guardrails/technology/dotnet-webapi/testing.md +10 -0
  73. package/assets/guardrails/technology/nextjs/ui-constitution.md +8 -1
  74. package/package.json +1 -1
  75. package/assets/.appweaver/orchestrator/.state/smoke.state.json +0 -32
@@ -36,8 +36,8 @@
36
36
  # is visible so it can be designed around, and building on it silently is the defect this
37
37
  # check exists to catch.
38
38
  #
39
- # OVERLAP IS COMPUTED, NOT ASKED. cr-overlap reads the `## Aggregate:` headings of every
40
- # pending model delta and reports which other open changes touch the same aggregates.
39
+ # OVERLAP IS COMPUTED, NOT ASKED. cr-overlap reads the `**Aggregates touched**:` header of
40
+ # every pending model delta and reports which other open changes touch the same aggregates.
41
41
  # That is a fact about two files, so a regex answers it exactly and the result is handed
42
42
  # to the model as a finding rather than as a question.
43
43
  #
@@ -153,7 +153,7 @@ workflow:
153
153
  action: verify_context
154
154
  require_non_empty: feature_dir
155
155
  on_empty: stop
156
- on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the aggregate, e.g. Order), then re-run analyze-plan-delta."
156
+ on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the feature, e.g. Order or PatientRegistration), then re-run analyze-plan-delta."
157
157
 
158
158
  - id: verify_spec
159
159
  action: verify_context
@@ -229,9 +229,9 @@ workflow:
229
229
  - { name: sibling_count, from: "$.sibling_count" }
230
230
  - { name: domain_model, from: "$.domain_model" }
231
231
 
232
- # 3b. WHICH OTHER OPEN CHANGES TOUCH THE SAME AGGREGATES. Deterministic: the
233
- # `## Aggregate:` headings of the pending model deltas are the merge unit
234
- # domain-model.md is organised by, so this is a regex over two files rather than a
232
+ # 3b. WHICH OTHER OPEN CHANGES TOUCH THE SAME AGGREGATES. Deterministic: each pending
233
+ # model delta's `**Aggregates touched**:` header names the `## Aggregate:` sections
234
+ # of domain-model.md it merges into, so this is a regex over two files rather than a
235
235
  # judgement. The result reaches the check below as a FINDING, not a question.
236
236
  - id: cr_overlap
237
237
  action: execute_script
@@ -645,7 +645,7 @@ workflow:
645
645
  action: verify_context
646
646
  require_true: analyze_ok
647
647
  on_false: stop
648
- on_false_message: "The delta plans for {{cr_id}} do not realise the delta spec (gaps: {{analyze_gaps}}) — repair was skipped (see {{cr_dir}}/delta-plan-analyze-report.md), or it could not be reconciled in 3 passes. A 'LIFT-FAILURE:' gap means a domain constraint from the gate checklist was never lifted into the delta spec, and a `model` gap means a plan needs structure the effective model does not define — both are fixed by /appweaver specify, which authors the delta spec and the model delta; this gate may not write either. A `wave` gap is closed by declaring '## Depends on' (and raising the wave with cr-wave) or by planning around the sibling. Otherwise fix out of band, or re-run and choose Repair."
648
+ on_false_message: "The delta plans for {{cr_id}} do not realise the delta spec (gaps: {{analyze_gaps}}) — repair was skipped (see {{cr_dir}}/delta-plan-analyze-report.md), or it could not be reconciled in 3 passes. A 'LIFT-FAILURE:' gap means a domain constraint from the gate checklist was never lifted into the delta spec, and a `model` gap means a plan needs structure the effective model does not define — both are fixed by /appweaver specify, which authors the delta spec and the model delta; this gate may not write either. A `wave` gap is closed by declaring '## Depends on' — which is what sets the wave, since specify derives it from that section and the merge fence re-checks it — or by planning around the sibling. Otherwise fix out of band, or re-run and choose Repair."
649
649
 
650
650
  # 8. Post-execution hooks.
651
651
  - id: resolve_post_hooks
@@ -30,7 +30,8 @@
30
30
  # and that is the act a change in flight forbids — the delta docs are the authoritative
31
31
  # edit and the masters must not drift out from under them before finalize-delta merges.
32
32
  # MASTER mode (no change open) — the full gate: check, ask, repair, and STOP on gaps
33
- # that could not be reconciled. spec.md + the lane plans are the masters.
33
+ # a plan rewrite could have closed but did not. spec.md + the lane plans
34
+ # are the masters.
34
35
  # REPORT mode (a change is open) — the same checks, read-only: no ask, no repair, no
35
36
  # stop. It writes plan-analyze-report.md and routes on. The plans are
36
37
  # DERIVED from spec.md + domain-model.md, so after finalize-delta merged a
@@ -40,6 +41,18 @@
40
41
  # to be stale, and a stage that refuses to look is not a gate. It reports; it never writes
41
42
  # a plan.
42
43
  #
44
+ # TWO KINDS OF GAP, SPLIT BY WHO CAN CLOSE THEM (plan-gaps). A PLAN gap is closed by
45
+ # rewriting a lane plan, which this stage may do. A ROUTED gap can only be closed in
46
+ # spec.md or domain-model.md, which this stage may not write: a LIFT-FAILURE (a domain
47
+ # constraint the spec never lifted), structure the model does not define, behaviour the
48
+ # spec does not describe. Only plan gaps drive the repair loop and the stop; routed gaps
49
+ # are reported and handed to /appweaver specify. Stopping on them would stop every feature
50
+ # whose spec missed a constraint, with no repair this stage could ever make.
51
+ #
52
+ # EVERY MASTER RUN LEAVES A RECEIPT (.aw-plan-gate.json), green or not: the bytes it
53
+ # checked and the verdict — conformant, routed-gaps or gaps. implement reads it and
54
+ # refuses moved bytes or `gaps`, and warns on `routed-gaps`.
55
+ #
43
56
  # WHICH MODE IS READ FROM THE DIRECTORY, AND DEFAULTS TO REPORT. changes/<id>/ is the
44
57
  # evidence (cr-list), not active_version.json's `active_change` — the ledger is the most
45
58
  # recently OPENED change and a crashed run can leave the two disagreeing. The permission to
@@ -60,7 +73,7 @@
60
73
 
61
74
  name: aw-spec-analyze-plan
62
75
  description: The spec↔plan conformance gate for every declared lane — confirm backend-plan.md and frontend-plan.md follow spec.md, that they bind to each other's contract, and that neither contradicts the bounded context's domain model (domain overlays are a backstop); with approval, repair the drifting plan. Lanes come from .appweaver/stacks.json, so one command gates backend-only, frontend-only and full-stack projects. Runs in one of two modes, decided from the changes/ directory rather than the ledger and defaulting to the read-only one — MASTER (no change open) checks, repairs on approval and stops on unreconciled gaps; REPORT (a change open) runs the same checks read-only and writes plan-analyze-report.md without asking, repairing or stopping, because the master plans may not be rewritten while a delta is in flight. It never opens, merges or closes a change.
63
- version: 3.2.0
76
+ version: 3.3.0
64
77
  agent_agnostic: true
65
78
  dialect: aw-spec.orchestrator/v1
66
79
 
@@ -68,6 +81,9 @@ scripts:
68
81
  actions:
69
82
  sh: .appweaver/scripts/bash/aw-spec-actions.sh
70
83
  ps1: .appweaver/scripts/powershell/aw-spec-actions.ps1
84
+ xref:
85
+ sh: .appweaver/scripts/bash/aw-xref.sh
86
+ ps1: .appweaver/scripts/powershell/aw-xref.ps1
71
87
 
72
88
  standards:
73
89
  - { path: .appweaver/ai-rules.md } # kit-resident: the universal operating rules, every flow
@@ -87,8 +103,15 @@ vars:
87
103
  no_open_change: "false" # RAISED only by cr-list answering that nothing is open
88
104
  gate_mode: "report" # report | master — reported in the outputs
89
105
  may_repair: "false" # the ask, the repair loop and the conformance stop are MASTER-only
90
- gate_ok: "false" # raised by a conformant run OR by report mode, which cannot repair
91
- stamp_receipt: "false" # master mode AND conformant ⇒ leave the receipt implement reads
106
+ gate_ok: "false" # raised by a run with no plan gap left OR by report mode, which cannot repair
107
+ stamp_receipt: "false" # master mode ⇒ leave the receipt implement reads, whatever the verdict
108
+ # THE GAP SPLIT, from plan-gaps after each analyze pass. Defaulted to the refusing
109
+ # answer, so a classification that never arrived neither ends the loop nor passes.
110
+ plan_ok: "false" # no gap left that a plan rewrite closes
111
+ gate_verdict: "gaps" # conformant | routed-gaps | gaps
112
+ plan_gap_count: "0"
113
+ routed_gaps: "[]" # the gaps only /appweaver specify can close
114
+ routed_csv: ""
92
115
  receipt_file: ""
93
116
  receipt_gated: ""
94
117
  receipt_count: "0"
@@ -122,6 +145,10 @@ vars:
122
145
  be_plan_ctx: ""
123
146
  fe_plan_ctx: ""
124
147
  domain_model: ""
148
+ owned_sections: "" # the model section(s) this feature answers for — feature-aggregates
149
+ shared_with_csv: ""
150
+ one_to_one: "true"
151
+ model_scope_plan: "" # which part of those sections check 4(a) holds the plan to
125
152
 
126
153
  workflow:
127
154
 
@@ -175,11 +202,40 @@ workflow:
175
202
  - { name: domain_model, from: "$.domain_model" }
176
203
  - { name: domain_model_exists, from: "$.domain_model_exists" }
177
204
 
205
+ # WHICH PART OF THAT MODEL CHECK 4 HOLDS THE PLAN TO. A feature is a capability scope the
206
+ # developer chose — it may act on several aggregates, and several features may act on
207
+ # one — so "the plan matches its aggregate's section" is only exact for a feature that
208
+ # alone answers for that aggregate. Resolved from the spec's Acts-on line, then the
209
+ # boundary map, then the feature name.
210
+ - id: feature_aggregates
211
+ action: execute_script
212
+ on_error: continue
213
+ script: { ref: xref }
214
+ subcommand: feature-aggregates
215
+ args: ["--feature-dir", "{{feature_dir}}"]
216
+ outputs:
217
+ - { name: owned_sections, from: "$.owned_sections" }
218
+ - { name: shared_with_csv, from: "$.shared_with_csv" }
219
+ - { name: one_to_one, from: "$.one_to_one" }
220
+
221
+ # A shared section is realised by several plans, each covering what its own feature
222
+ # acts on; flagging every element one plan leaves out would fail each of them for work
223
+ # another feature's plan carries. Ordered pair of sets — no conditional prompt text.
224
+ - id: model_scope_shared
225
+ action: set
226
+ vars:
227
+ model_scope_plan: "{{owned_sections}}, which this feature SHARES with other features: every element the plan realises must carry the model's name and sit inside the model's boundary, but an element of the section the plan leaves out is NOT a gap unless this feature's requirements act on it — another feature's plan realises it"
228
+ - id: model_scope_sole
229
+ condition: "{{one_to_one}} == true"
230
+ action: set
231
+ vars:
232
+ model_scope_plan: "{{owned_sections}}. This feature is the only one that answers for it, so the plan's domain layer must realise that whole section"
233
+
178
234
  - id: require_feature
179
235
  action: verify_context
180
236
  require_non_empty: feature_dir
181
237
  on_empty: stop
182
- on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the aggregate, e.g. Order), then re-run analyze-plan."
238
+ on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the feature, e.g. Order or PatientRegistration), then re-run analyze-plan."
183
239
 
184
240
  - id: verify_spec
185
241
  action: verify_context
@@ -325,19 +381,20 @@ workflow:
325
381
  - { path: "{{be_plan_ctx}}", optional: true }
326
382
  - { path: "{{fe_plan_ctx}}", optional: true }
327
383
 
328
- # 5. Conformance + REPAIR loop (spec is the anchor): check → REPORT gaps + ASK →
329
- # Repair on approval → re-check; "Report and Skip" writes plan-analyze-report.md
330
- # and stops. Bounded (3×). The repair is PER LANE, so a frontend drift never
331
- # rewrites a conformant backend plan.
384
+ # 5. Conformance + REPAIR loop (spec is the anchor): check → split the gaps → REPORT +
385
+ # ASK → Repair on approval → re-check; "Report and Skip" writes plan-analyze-report.md
386
+ # and stops. Bounded (3×). The loop runs while a PLAN gap is open: once only routed
387
+ # gaps remain there is nothing a repair can change. The repair is PER LANE, so a
388
+ # frontend drift never rewrites a conformant backend plan.
332
389
  - id: analyze_loop
333
390
  action: loop
334
- while: "{{analyze_ok}} != true"
391
+ while: "{{plan_ok}} != true"
335
392
  max_iterations: 3
336
393
  steps:
337
394
  - id: analyze
338
395
  action: agent_query
339
396
  include_standards: true # the gate overlays (checklist) are in the standards
340
- schema: { ok: true, backend_repair: false, frontend_repair: false, gaps: [ { severity: "high", check: "<spec-backend|spec-frontend|contract|model|overlay>", lane: "<backend|frontend>", spec_ref: "<FR/BR/CN/DC/IC/UX/US/SC or overlay or —>", where: "<plan section>", gap: "<how it drifts>" } ] }
397
+ schema: { ok: true, backend_repair: false, frontend_repair: false, gaps: [ { severity: "high", check: "<spec-backend|spec-frontend|contract|model|overlay>", lane: "<backend|frontend>", route: "<plan|specify>", spec_ref: "<FR/BR/CN/DC/IC/UX/US/SC or overlay or —>", where: "<plan section>", gap: "<how it drifts>" } ] }
341
398
  mock: { ok: true, backend_repair: false, frontend_repair: false, gaps: [] }
342
399
  task: |
343
400
  spec.md is the ANCHOR. Gate this feature's LANE PLANS against it and against the
@@ -388,9 +445,10 @@ workflow:
388
445
 
389
446
  CHECK 4 — the plans → domain-model.md (when the model is in context; skip if it
390
447
  is not):
391
- (a) the backend plan's DOMAIN layer matches the model EXACTLY — entities and
392
- value objects are its aggregate section's entities (SAME names), the boundary is
393
- its Boundary block, states are the lifecycle it describes;
448
+ (a) the backend plan's DOMAIN layer matches the model EXACTLY. The section(s)
449
+ this feature answers for: {{model_scope_plan}}. Entities and value objects carry
450
+ the section's names (SAME names), the boundary is its Boundary block, states are
451
+ the lifecycle it describes;
394
452
  (b) EVERY invariant (BR-nnn) the spec cites has a named ENFORCEMENT POINT in the
395
453
  backend plan. An invariant nothing enforces is a HIGH gap; a plan that RESTATES
396
454
  what an invariant says instead of citing it by id is a gap too — the model is the
@@ -410,15 +468,37 @@ workflow:
410
468
  /appweaver specify, not the plan — do not fabricate it here).
411
469
 
412
470
  Tag every gap with its `check` and the `lane` whose document must change to close
413
- it. Set backend_repair true if any gap needs the backend plan rewritten, and
414
- frontend_repair likewise. (If a plan is still the empty template, treat the WHOLE
415
- spec as gaps against it.) Set ok=true ONLY when every applicable check passes and
416
- no checklist constraint is unaccounted for.
471
+ it, and with its `route` — who can close it:
472
+ - route `specify` when ONLY spec.md or domain-model.md can close it: every
473
+ LIFT-FAILURE; a plan element needing an aggregate, entity, event or invariant
474
+ the model does not define; a state or behaviour the plan needs that the spec
475
+ does not describe and no DA-n records.
476
+ - route `plan` for everything a rewrite of a lane plan closes: a missing plan
477
+ home, a name that differs from the model's, an invariant restated instead of
478
+ cited, a contract binding, an element with no spec basis.
479
+ Set backend_repair true if any `plan`-route gap needs the backend plan rewritten,
480
+ and frontend_repair likewise. (If a plan is still the empty template, treat the
481
+ WHOLE spec as gaps against it.) Set ok=true ONLY when every applicable check
482
+ passes and no checklist constraint is unaccounted for.
417
483
  outputs:
418
484
  - { name: analyze_ok, from: "$.ok" }
419
485
  - { name: analyze_gaps, from: "$.gaps" }
420
486
  - { name: backend_repair, from: "$.backend_repair" }
421
487
  - { name: frontend_repair, from: "$.frontend_repair" }
488
+
489
+ # WHO CAN CLOSE EACH GAP, decided by script from the tags above: a LIFT-FAILURE is
490
+ # routed to specify whatever its tag says, and an untagged gap is a plan gap.
491
+ - id: classify_gaps
492
+ action: execute_script
493
+ script: { ref: actions }
494
+ subcommand: plan-gaps
495
+ args: ["--gaps", "{{analyze_gaps}}"]
496
+ outputs:
497
+ - { name: plan_ok, from: "$.plan_ok" }
498
+ - { name: gate_verdict, from: "$.verdict" }
499
+ - { name: plan_gap_count, from: "$.plan_gaps" }
500
+ - { name: routed_gaps, from: "$.routed" }
501
+ - { name: routed_csv, from: "$.routed_csv" }
422
502
  - id: conform_break
423
503
  action: break
424
504
  condition: "{{analyze_ok}} == true"
@@ -461,12 +541,20 @@ workflow:
461
541
  action: break
462
542
  condition: "{{may_repair}} == false"
463
543
 
544
+ # Only routed gaps left: no repair can change them, so do not ask for one.
545
+ - id: routed_only_break
546
+ action: break
547
+ condition: "{{plan_ok}} == true"
548
+
464
549
  - id: ask_repair
465
550
  action: ask
466
551
  prompt: |
467
552
  analyze-plan found spec↔plan gaps (spec.md is the anchor; domain-model.md is
468
553
  read-only here):
469
554
  {{analyze_gaps}}
555
+ {{plan_gap_count}} gap(s) have route `plan` — a repair rewrites the plan to close
556
+ them. Gaps with route `specify` are not repaired here; they are reported and
557
+ belong to /appweaver specify.
470
558
  Repair the drifting plan(s) to conform now, or report the gaps and stop?
471
559
  options: ["Repair", "Report and Skip"]
472
560
  answer_var: repair_choice
@@ -519,7 +607,8 @@ workflow:
519
607
  output_target: { type: file, path: "{{backend_plan}}" }
520
608
  task: |
521
609
  spec.md is the ANCHOR — update backend-plan.md (in context) to CONFORM to it,
522
- closing the gaps whose lane is `backend` (the spec WINS every time):
610
+ closing the gaps whose lane is `backend` and whose route is `plan` (the spec WINS
611
+ every time). Leave `specify`-route gaps as the DA-n notes described below:
523
612
  {{analyze_gaps}}
524
613
  - Add a plan home for each uncovered spec FR/BR/CN/DC/IC/US/SC (a lifted
525
614
  CN-/DC-/IC- lands in a §1 domain invariant enforcement · the persistence
@@ -557,7 +646,9 @@ workflow:
557
646
  output_target: { type: file, path: "{{frontend_plan}}" }
558
647
  task: |
559
648
  spec.md is the ANCHOR — update frontend-plan.md (in context) to CONFORM to it,
560
- closing the gaps whose lane is `frontend` (the spec WINS every time):
649
+ closing the gaps whose lane is `frontend` and whose route is `plan` (the spec WINS
650
+ every time). Leave each `specify`-route gap open, logged as a DA-n that routes to
651
+ /appweaver specify:
561
652
  {{analyze_gaps}}
562
653
  - Give every uncovered Experience Standard (UX-), user story and
563
654
  interaction-state FR- a home: a screen in the navigation section, a flow row,
@@ -584,52 +675,51 @@ workflow:
584
675
  already-conformant section VERBATIM.
585
676
  Return the COMPLETE updated frontend-plan.md content only.
586
677
 
587
- # THE STOP IS MASTER MODE'S ALONE. Two independent raises, because the two reasons to
588
- # pass are unrelated: the plans conform, or this run was never allowed to change them.
589
- # A read-only run that found gaps has done its whole job by reporting them — stopping
590
- # would fail the operator for drift that finalize-delta created on purpose and that only
591
- # /appweaver plan main can close.
592
- - id: gate_ok_conformant
593
- condition: "{{analyze_ok}} == true"
594
- action: set
595
- vars: { gate_ok: "true" }
596
- - id: gate_ok_readonly
597
- condition: "{{may_repair}} == false"
598
- action: set
599
- vars: { gate_ok: "true" }
600
-
601
- - id: analyze_gate
602
- action: verify_context
603
- require_true: gate_ok
604
- on_false: stop
605
- on_false_message: "The lane plans do not conform to the spec (gaps: {{analyze_gaps}}) — repair was skipped (see plan-analyze-report.md), or it could not be reconciled in 3 passes. A 'LIFT-FAILURE:' gap means a domain constraint from the gate checklist was never lifted into the spec, and a `model` gap means the plan needs an entity or invariant domain-model.md does not define — both are fixed by /appweaver specify, which authors the spec and the model; this gate may not write either. A `contract` gap is closed in the lane its row names. Otherwise fix out of band, or re-run and choose Repair."
606
-
607
678
  # THE RECEIPT. implement generates from the plans and deliberately does not read the
608
- # spec, on the strength of this stage having conformed them to it — which until now was
609
- # an assumption about the order the operator ran things in, checked by nothing. Record
610
- # WHAT was proven: the bytes of spec.md, the context's domain-model.md and each lane plan
611
- # that was present. plan-gate-check reads it at implement.
679
+ # spec, on the strength of this stage having checked them against it. Record WHAT was
680
+ # checked — the bytes of spec.md, the context's domain-model.md and each lane plan that
681
+ # was present — and the VERDICT on them. plan-gate-check reads it at implement.
612
682
  #
613
- # ONLY A GREEN MASTER RUN LEAVES ONE. The AND is the kit's ordered assignment — under
614
- # master mode, take the conformance verdict as the answer — because the engine has no
615
- # compound conditions. A REPORT-mode run proves nothing about the masters (it may not
616
- # even repair them), and a red master run proved the opposite.
683
+ # EVERY MASTER RUN LEAVES ONE, and it is written BEFORE the stop below, so a red run
684
+ # records its `gaps` verdict too and implement refuses it. A REPORT-mode run leaves
685
+ # none: it checked the masters while a delta was in flight, and they are about to move.
617
686
  - id: flag_receipt
618
687
  condition: "{{may_repair}} == true"
619
688
  action: set
620
- vars: { stamp_receipt: "{{analyze_ok}}" }
689
+ vars: { stamp_receipt: "true" }
621
690
 
622
691
  - id: plan_gate_stamp
623
692
  condition: "{{stamp_receipt}} == true"
624
693
  action: execute_script
625
694
  script: { ref: actions }
626
695
  subcommand: plan-gate-stamp
627
- args: ["--feature-dir", "{{feature_dir}}", "--spec-version", "{{version}}", "--mode", "{{gate_mode}}"]
696
+ args: ["--feature-dir", "{{feature_dir}}", "--spec-version", "{{version}}", "--mode", "{{gate_mode}}", "--verdict", "{{gate_verdict}}", "--plan-gaps", "{{plan_gap_count}}", "--routed", "{{routed_gaps}}"]
628
697
  outputs:
629
698
  - { name: receipt_file, from: "$.file" }
630
699
  - { name: receipt_gated, from: "$.gated" }
631
700
  - { name: receipt_count, from: "$.count" }
632
701
 
702
+ # THE STOP IS MASTER MODE'S ALONE, and only a PLAN gap triggers it. Two independent
703
+ # raises, because the two reasons to pass are unrelated: no gap is left that a plan
704
+ # rewrite closes, or this run was never allowed to rewrite. A read-only run that found
705
+ # gaps has done its whole job by reporting them — stopping would fail the operator for
706
+ # drift that finalize-delta created on purpose and that only /appweaver plan main can
707
+ # close. Routed gaps pass here and are listed in the outputs for /appweaver specify.
708
+ - id: gate_ok_conformant
709
+ condition: "{{plan_ok}} == true"
710
+ action: set
711
+ vars: { gate_ok: "true" }
712
+ - id: gate_ok_readonly
713
+ condition: "{{may_repair}} == false"
714
+ action: set
715
+ vars: { gate_ok: "true" }
716
+
717
+ - id: analyze_gate
718
+ action: verify_context
719
+ require_true: gate_ok
720
+ on_false: stop
721
+ on_false_message: "The lane plans do not conform to the spec: {{plan_gap_count}} gap(s) a plan rewrite closes are still open (all gaps: {{analyze_gaps}}) — repair was skipped (see plan-analyze-report.md), or it did not converge in 3 passes. The receipt records verdict `gaps`, so /appweaver implement will refuse until this gate is re-run clean. A `contract` gap is closed in the lane its row names. Gaps routed to `specify` ({{routed_csv}}) are not what stopped this run: they need /appweaver specify, which authors the spec and the model. Fix out of band, or re-run and choose Repair."
722
+
633
723
  # (No change-close here by design — this stage never archives anything in either mode,
634
724
  # and cr-archive belongs to close-delta, which runs after finalize-delta's merge.)
635
725
 
@@ -653,10 +743,13 @@ outputs:
653
743
  domain_model: "{{domain_model}}"
654
744
  conformant: "{{analyze_ok}}"
655
745
  gaps: "{{analyze_gaps}}"
746
+ verdict: "{{gate_verdict}}" # conformant | routed-gaps | gaps
747
+ plan_gaps: "{{plan_gap_count}}" # open gaps a plan rewrite closes
748
+ routed_to_specify: "{{routed_csv}}" # open gaps only /appweaver specify can close
656
749
  version: "{{version}}"
657
750
  gate_mode: "{{gate_mode}}" # master (repair allowed) | report (read-only)
658
751
  open_changes: "{{open_ids}}" # empty in master mode, by definition
659
- gate_receipt: "{{receipt_file}}" # written only by a GREEN master run; implement reads it
752
+ gate_receipt: "{{receipt_file}}" # written by every master run, with its verdict; implement reads it
660
753
  gate_receipt_at: "{{receipt_gated}}"
661
754
  note: "Ran in {{gate_mode}} mode ({{open_count}} change(s) open: {{open_ids}}). Checked: spec→backend-plan ({{be_present}}), spec→frontend-plan ({{fe_present}}), the plan↔plan contract binding ({{both_lanes}} — it needs both lanes) and both plans against domain-model.md. The domain model was READ, never written: a gap that needs a new entity, event or invariant is fixed by /appweaver specify. In REPORT mode no plan was rewritten either — the master plans are derived documents and may not move while a delta is in flight."
662
- next: "MASTER mode (no change open, conformant={{analyze_ok}}): /appweaver implement — the plans conform to the spec; version unchanged, this gate closes nothing. REPORT mode (change(s) {{open_ids}} open): this run was read-only and wrote plan-analyze-report.md only. If finalize-delta has already merged, the gaps ARE that merge — the lane plans are derived from spec.md + domain-model.md and finalize-delta moves only those two, so re-derive with /appweaver plan main after /appweaver close-delta, then re-run /appweaver analyze-plan in master mode. If the change has NOT merged yet, gate it where it lives: /appweaver analyze-plan-delta."
755
+ next: "MASTER mode (no change open, verdict={{gate_verdict}}): /appweaver implement — no gap a plan rewrite closes is left; version unchanged, this gate closes nothing. If the verdict is routed-gaps, implement generates and warns: {{routed_csv}} — each is a spec or model gap, lifted by opening a change with /appweaver specify. REPORT mode (change(s) {{open_ids}} open): this run was read-only and wrote plan-analyze-report.md only. If finalize-delta has already merged, the gaps ARE that merge — the lane plans are derived from spec.md + domain-model.md and finalize-delta moves only those two, so re-derive with /appweaver plan main after /appweaver close-delta, then re-run /appweaver analyze-plan in master mode. If the change has NOT merged yet, gate it where it lives: /appweaver analyze-plan-delta."
@@ -70,7 +70,7 @@
70
70
  # =============================================================================
71
71
 
72
72
  name: aw-spec-analyze-slice-delta
73
- version: 1.0.0
73
+ version: 1.1.0
74
74
  description: "The delta-spec↔delta-code gate — verify that the open change DOES WHAT IT WAS ASKED TO DO, before it reaches the master documents. delta-spec.md is the anchor, exactly as it is for analyze-plan-delta. Asks which open change it is gating, runs the slice's tests (the first execution on the change path, since build only compiles), then evaluates the code and its paired tests against the change's REQUIREMENTS — every ADDED or MODIFIED delta-spec item realised in code, every one covered by a test that cites its id, every invariant the model delta adds actually enforced — with the delta plan in context as the map from requirement to code and cross-checked in its own right, so an element the plan promised and nobody built is caught too. The dynamic twin of analyze-plan-delta and the delta twin of analyze-slice, which cannot see a missing implementation because a behaviour that was never built leaves no failing test. Verify-only — it reports each finding with the command that closes it and stops; it generates no code and repairs nothing."
75
75
  agent_agnostic: true
76
76
  dialect: aw-spec.orchestrator/v1
@@ -124,6 +124,7 @@ vars:
124
124
  test_ok: "false"
125
125
  test_tool: ""
126
126
  test_log: ""
127
+ test_excerpt: ""
127
128
  test_scoped: "false"
128
129
  test_ran: "false"
129
130
  test_matched: "false"
@@ -155,9 +156,11 @@ workflow:
155
156
  - { name: be_declared, from: "$.backend_declared" }
156
157
  - { name: be_lane_id, from: "$.backend_lane_id" }
157
158
  - { name: be_source_path, from: "$.backend_source_path" }
159
+ - { name: be_test_path, from: "$.backend_test_path" }
158
160
  - { name: fe_declared, from: "$.frontend_declared" }
159
161
  - { name: fe_lane_id, from: "$.frontend_lane_id" }
160
162
  - { name: fe_source_path, from: "$.frontend_source_path" }
163
+ - { name: fe_test_path, from: "$.frontend_test_path" }
161
164
 
162
165
  - id: require_lane
163
166
  action: verify_context
@@ -182,7 +185,7 @@ workflow:
182
185
  action: verify_context
183
186
  require_non_empty: feature_dir
184
187
  on_empty: stop
185
- on_empty_message: "No active feature. Run /appweaver select-feature first (pick the aggregate, e.g. Order), then re-run analyze-slice-delta."
188
+ on_empty_message: "No active feature. Run /appweaver select-feature first (pick the feature, e.g. Order or PatientRegistration), then re-run analyze-slice-delta."
186
189
 
187
190
  # 3. WHICH CHANGE. Same selection contract as plan, analyze-plan-delta and implement:
188
191
  # cr-list enumerates the open changes (evidence = changes/<CR-id>/ on disk), a bare CR
@@ -316,6 +319,7 @@ workflow:
316
319
  - { name: test_ok, from: "$.fence_green" }
317
320
  - { name: test_tool, from: "$.tool" }
318
321
  - { name: test_log, from: "$.log_tail" }
322
+ - { name: test_excerpt, from: "$.failing_excerpt" } # the lines that NAME the failure
319
323
  - { name: test_scoped, from: "$.scoped" }
320
324
  # A GREEN FENCE IS NOT ALWAYS AN EXECUTED SUITE. test-run reports fence_green true
321
325
  # with ran=false on a project whose lanes declare no test command — the vacuous pass
@@ -414,9 +418,11 @@ workflow:
414
418
  the pre-existing slice.
415
419
 
416
420
  CHECK 7 — PLACEMENT. Every file this change added or edited sits under the lane that
417
- owns it: this lane's source path is `{{be_source_path}}`. A service-lane element
418
- written into the frontend lane's subtree — or outside every lane root — is a HIGH
419
- `placement` gap.
421
+ owns it: this lane's production code under its source path `{{be_source_path}}`, its
422
+ tests under its test path `{{be_test_path}}`. A service-lane element written into the
423
+ frontend lane's subtree — or outside every lane's paths — is a HIGH `placement` gap.
424
+ One edit outside them is allowed: registering a test project this change created in
425
+ the file the lane's test command discovers tests through (e.g. a .NET solution file).
420
426
 
421
427
  PLAN-ONLY CHANGE — if the delta-spec is an unfilled stub, this change deliberately
422
428
  alters only HOW the EXISTING spec is realised (a refactor, an architectural decision).
@@ -506,9 +512,11 @@ workflow:
506
512
  gap. Judge it only over what this change touched.
507
513
 
508
514
  CHECK 7 — PLACEMENT. Every file this change added or edited sits under this lane's
509
- source path, `{{fe_source_path}}`. A file written into the service lane's subtree is a
510
- HIGH `placement` gap — a gap in the service surface is a contract delta raised back to
511
- that lane, never a client-side edit to it.
515
+ source path, `{{fe_source_path}}`, or — for a test — its test path, `{{fe_test_path}}`.
516
+ A file written into the service lane's subtree is a HIGH `placement` gap — a gap in the
517
+ service surface is a contract delta raised back to that lane, never a client-side edit
518
+ to it. The one edit allowed outside them is the test setup this change had to create,
519
+ when the lane's test command discovers tests through a file elsewhere.
512
520
 
513
521
  PLAN-ONLY CHANGE — if the delta-spec is an unfilled stub, this change alters only HOW
514
522
  the existing spec is realised. Checks 1 to 3 have no anchor and are SKIPPED, not
@@ -568,8 +576,8 @@ workflow:
568
576
  merges the change into spec.md and domain-model.md.
569
577
  Evidence:
570
578
  - tests: green={{test_ok}}, tool `{{test_tool}}`, actually ran={{test_ran}},
571
- matched a slice filter={{test_matched}}, slice-scoped={{test_scoped}}. Log tail:
572
- {{test_log}}
579
+ matched a slice filter={{test_matched}}, slice-scoped={{test_scoped}}. Failing
580
+ lines: {{test_excerpt}} · Log tail: {{test_log}}
573
581
  - service lane gated={{be_gated}}, verdict ok={{be_ok}}, gaps: {{be_gaps}}
574
582
  - experience lane gated={{fe_gated}}, verdict ok={{fe_ok}}, gaps: {{fe_gaps}}
575
583
  Markdown format: