aw-spec-cli 0.1.20 → 0.1.22

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 (62) hide show
  1. package/assets/.appweaver/ORCH-DIALECT.md +8 -3
  2. package/assets/.appweaver/STACK-PROFILES.md +87 -8
  3. package/assets/.appweaver/orchestrator/modules/delta-plan.orch.yaml +30 -0
  4. package/assets/.appweaver/orchestrator/modules/delta-spec.orch.yaml +22 -1
  5. package/assets/.appweaver/orchestrator/orchestrator.py +65 -3
  6. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan-delta.orch.yaml +6 -6
  7. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +144 -52
  8. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +78 -10
  9. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +283 -25
  10. package/assets/.appweaver/orchestrator/workflows/aw-spec-constitution.orch.yaml +4 -4
  11. package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +202 -40
  12. package/assets/.appweaver/orchestrator/workflows/aw-spec-foundation.orch.yaml +140 -36
  13. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +557 -57
  14. package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +3 -3
  15. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +39 -0
  16. package/assets/.appweaver/orchestrator/workflows/aw-spec-recall-delta.orch.yaml +28 -7
  17. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-stack.orch.yaml +47 -23
  18. package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +101 -1
  19. package/assets/.appweaver/orchestrator/workflows/aw-spec-ui-model.orch.yaml +141 -0
  20. package/assets/.appweaver/orchestrator/workflows/aw-spec-ui-prototype.orch.yaml +386 -0
  21. package/assets/.appweaver/orchestrator/workflows/aw-spec-workflow-model.orch.yaml +135 -0
  22. package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +772 -216
  23. package/assets/.appweaver/scripts/bash/aw-xref.sh +4 -1
  24. package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +708 -209
  25. package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +4 -1
  26. package/assets/.appweaver/scripts/python/aw-crstate.py +319 -0
  27. package/assets/.appweaver/scripts/python/aw-foundation.py +151 -0
  28. package/assets/.appweaver/scripts/python/aw-gaps.py +119 -0
  29. package/assets/.appweaver/scripts/python/aw-payload-parity.py +19 -0
  30. package/assets/.appweaver/scripts/python/aw-plan-coverage.py +503 -0
  31. package/assets/.appweaver/scripts/python/aw-plangate.py +216 -0
  32. package/assets/.appweaver/scripts/python/aw-principles.py +108 -0
  33. package/assets/.appweaver/scripts/python/aw-stack.py +257 -28
  34. package/assets/.appweaver/scripts/python/aw-testlog.py +148 -0
  35. package/assets/.appweaver/scripts/python/aw-xref.py +141 -4
  36. package/assets/.appweaver/stack-profiles/angular.json +26 -0
  37. package/assets/.appweaver/stack-profiles/blazor-wasm.json +38 -0
  38. package/assets/.appweaver/stack-profiles/dotnet-webapi.json +39 -0
  39. package/assets/.appweaver/stack-profiles/java-spring.json +20 -0
  40. package/assets/.appweaver/stack-profiles/nextjs.json +26 -0
  41. package/assets/.appweaver/stack-profiles/node-express.json +27 -0
  42. package/assets/.appweaver/stack-profiles/python-fastapi.json +16 -0
  43. package/assets/.appweaver/stack-profiles/react-vite.json +26 -0
  44. package/assets/.appweaver/stack-profiles/sveltekit.json +26 -0
  45. package/assets/.appweaver/stack-profiles/vue-vite.json +26 -0
  46. package/assets/.appweaver/templates/constitution-domain-template.md +17 -2
  47. package/assets/.appweaver/templates/ui-model-template.md +67 -0
  48. package/assets/.appweaver/templates/workflow-model-template.md +62 -0
  49. package/assets/guardrails/projects/dotnet-default-project/domain-rule-bindings.md +10 -6
  50. package/assets/guardrails/projects/dotnet-default-project/tech-rule-bindings.md +4 -0
  51. package/assets/guardrails/technology/dotnet-webapi/auditing.md +3 -1
  52. package/assets/guardrails/technology/dotnet-webapi/coding-standards.md +5 -3
  53. package/assets/guardrails/technology/dotnet-webapi/cqrs.md +5 -1
  54. package/assets/guardrails/technology/dotnet-webapi/domain-driven-design.md +13 -5
  55. package/assets/guardrails/technology/dotnet-webapi/examples/cqrs.md +2 -0
  56. package/assets/guardrails/technology/dotnet-webapi/examples/domain-driven-design.md +6 -2
  57. package/assets/guardrails/technology/dotnet-webapi/framework-api-map.md +114 -0
  58. package/assets/guardrails/technology/dotnet-webapi/technology-constitution.md +3 -0
  59. package/assets/guardrails/technology/dotnet-webapi/testing.md +10 -0
  60. package/assets/guardrails/technology/nextjs/ui-constitution.md +8 -1
  61. package/package.json +1 -1
  62. package/assets/.appweaver/orchestrator/.state/smoke.state.json +0 -32
@@ -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,16 @@ 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
+ repairs_left: "true" # lowered on the loop's final pass, which checks and never rewrites
109
+ # THE GAP SPLIT, from plan-gaps after each analyze pass. Defaulted to the refusing
110
+ # answer, so a classification that never arrived neither ends the loop nor passes.
111
+ plan_ok: "false" # no gap left that a plan rewrite closes
112
+ gate_verdict: "gaps" # conformant | routed-gaps | gaps
113
+ plan_gap_count: "0"
114
+ routed_gaps: "[]" # the gaps only /appweaver specify can close
115
+ routed_csv: ""
92
116
  receipt_file: ""
93
117
  receipt_gated: ""
94
118
  receipt_count: "0"
@@ -358,19 +382,30 @@ workflow:
358
382
  - { path: "{{be_plan_ctx}}", optional: true }
359
383
  - { path: "{{fe_plan_ctx}}", optional: true }
360
384
 
361
- # 5. Conformance + REPAIR loop (spec is the anchor): check → REPORT gaps + ASK →
362
- # Repair on approval → re-check; "Report and Skip" writes plan-analyze-report.md
363
- # and stops. Bounded (3×). The repair is PER LANE, so a frontend drift never
364
- # rewrites a conformant backend plan.
385
+ # 5. Conformance + REPAIR loop (spec is the anchor): check → split the gaps → REPORT +
386
+ # ASK → Repair on approval → re-check; "Report and Skip" writes plan-analyze-report.md
387
+ # and stops. The loop runs while a PLAN gap is open: once only routed gaps remain
388
+ # there is nothing a repair can change. The repair is PER LANE, so a frontend drift
389
+ # never rewrites a conformant backend plan.
390
+ #
391
+ # THREE REPAIRS, FOUR PASSES — the last pass checks and never rewrites. With the two
392
+ # equal, the third repair was the last thing to run: no pass ever saw its result, so a
393
+ # run that converged on it still reported `gaps` and told the operator it had not
394
+ # converged. Worse, plan_ok / gate_verdict / plan_gap_count then described the plans as
395
+ # they were BEFORE that repair, while plan-gate-stamp below hashed the files as they
396
+ # are AFTER it — a receipt pairing post-repair bytes with a pre-repair verdict, which
397
+ # plan-gate-check would later read as fresh. The extra pass costs one analyze in the
398
+ # case that used to end ambiguously, and it makes the last thing to touch the plans
399
+ # always earlier than the last thing to judge them, on every exit path.
365
400
  - id: analyze_loop
366
401
  action: loop
367
- while: "{{analyze_ok}} != true"
368
- max_iterations: 3
402
+ while: "{{plan_ok}} != true"
403
+ max_iterations: 4
369
404
  steps:
370
405
  - id: analyze
371
406
  action: agent_query
372
407
  include_standards: true # the gate overlays (checklist) are in the standards
373
- 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>" } ] }
408
+ 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>" } ] }
374
409
  mock: { ok: true, backend_repair: false, frontend_repair: false, gaps: [] }
375
410
  task: |
376
411
  spec.md is the ANCHOR. Gate this feature's LANE PLANS against it and against the
@@ -444,15 +479,37 @@ workflow:
444
479
  /appweaver specify, not the plan — do not fabricate it here).
445
480
 
446
481
  Tag every gap with its `check` and the `lane` whose document must change to close
447
- it. Set backend_repair true if any gap needs the backend plan rewritten, and
448
- frontend_repair likewise. (If a plan is still the empty template, treat the WHOLE
449
- spec as gaps against it.) Set ok=true ONLY when every applicable check passes and
450
- no checklist constraint is unaccounted for.
482
+ it, and with its `route` — who can close it:
483
+ - route `specify` when ONLY spec.md or domain-model.md can close it: every
484
+ LIFT-FAILURE; a plan element needing an aggregate, entity, event or invariant
485
+ the model does not define; a state or behaviour the plan needs that the spec
486
+ does not describe and no DA-n records.
487
+ - route `plan` for everything a rewrite of a lane plan closes: a missing plan
488
+ home, a name that differs from the model's, an invariant restated instead of
489
+ cited, a contract binding, an element with no spec basis.
490
+ Set backend_repair true if any `plan`-route gap needs the backend plan rewritten,
491
+ and frontend_repair likewise. (If a plan is still the empty template, treat the
492
+ WHOLE spec as gaps against it.) Set ok=true ONLY when every applicable check
493
+ passes and no checklist constraint is unaccounted for.
451
494
  outputs:
452
495
  - { name: analyze_ok, from: "$.ok" }
453
496
  - { name: analyze_gaps, from: "$.gaps" }
454
497
  - { name: backend_repair, from: "$.backend_repair" }
455
498
  - { name: frontend_repair, from: "$.frontend_repair" }
499
+
500
+ # WHO CAN CLOSE EACH GAP, decided by script from the tags above: a LIFT-FAILURE is
501
+ # routed to specify whatever its tag says, and an untagged gap is a plan gap.
502
+ - id: classify_gaps
503
+ action: execute_script
504
+ script: { ref: actions }
505
+ subcommand: plan-gaps
506
+ args: ["--gaps", "{{analyze_gaps}}"]
507
+ outputs:
508
+ - { name: plan_ok, from: "$.plan_ok" }
509
+ - { name: gate_verdict, from: "$.verdict" }
510
+ - { name: plan_gap_count, from: "$.plan_gaps" }
511
+ - { name: routed_gaps, from: "$.routed" }
512
+ - { name: routed_csv, from: "$.routed_csv" }
456
513
  - id: conform_break
457
514
  action: break
458
515
  condition: "{{analyze_ok}} == true"
@@ -495,12 +552,32 @@ workflow:
495
552
  action: break
496
553
  condition: "{{may_repair}} == false"
497
554
 
555
+ # Only routed gaps left: no repair can change them, so do not ask for one.
556
+ - id: routed_only_break
557
+ action: break
558
+ condition: "{{plan_ok}} == true"
559
+
560
+ # THE LAST PASS CONFIRMS THE LAST REPAIR AND DOES NOT ADD ONE. Raised for every pass
561
+ # and lowered on the final one, so the ask below and both lane rewrites are skipped
562
+ # there while the analyze above still runs and still sets the verdict.
563
+ - id: repairs_left_yes
564
+ action: set
565
+ vars: { repairs_left: "true" }
566
+ - id: repairs_left_no
567
+ condition: "{{iteration}} == 3"
568
+ action: set
569
+ vars: { repairs_left: "false" }
570
+
498
571
  - id: ask_repair
572
+ condition: "{{repairs_left}} == true"
499
573
  action: ask
500
574
  prompt: |
501
575
  analyze-plan found spec↔plan gaps (spec.md is the anchor; domain-model.md is
502
576
  read-only here):
503
577
  {{analyze_gaps}}
578
+ {{plan_gap_count}} gap(s) have route `plan` — a repair rewrites the plan to close
579
+ them. Gaps with route `specify` are not repaired here; they are reported and
580
+ belong to /appweaver specify.
504
581
  Repair the drifting plan(s) to conform now, or report the gaps and stop?
505
582
  options: ["Repair", "Report and Skip"]
506
583
  answer_var: repair_choice
@@ -528,9 +605,11 @@ workflow:
528
605
  condition: "{{repair_choice}} == Report and Skip"
529
606
 
530
607
  # WHICH DOCUMENT GETS REWRITTEN. A repair runs only where the analyze pass reported
531
- # drift in that lane AND that lane is present — two ordered assignments per lane,
532
- # cleared first so a lane that was repaired on the previous iteration and is clean
533
- # on this one is not rewritten again.
608
+ # drift in that lane AND that lane is present AND this pass still carries a repair —
609
+ # three ordered assignments per lane, because the dialect takes no compound
610
+ # condition. Cleared first so a lane that was repaired on the previous iteration and
611
+ # is clean on this one is not rewritten again, and cleared last on the final pass,
612
+ # which exists to check the previous repair rather than to add another.
534
613
  - id: be_repair_clear
535
614
  action: set
536
615
  vars: { be_repair: "false" }
@@ -538,6 +617,10 @@ workflow:
538
617
  condition: "{{backend_repair}} == true"
539
618
  action: set
540
619
  vars: { be_repair: "{{be_present}}" }
620
+ - id: be_repair_last_pass
621
+ condition: "{{repairs_left}} == false"
622
+ action: set
623
+ vars: { be_repair: "false" }
541
624
  - id: fe_repair_clear
542
625
  action: set
543
626
  vars: { fe_repair: "false" }
@@ -545,6 +628,10 @@ workflow:
545
628
  condition: "{{frontend_repair}} == true"
546
629
  action: set
547
630
  vars: { fe_repair: "{{fe_present}}" }
631
+ - id: fe_repair_last_pass
632
+ condition: "{{repairs_left}} == false"
633
+ action: set
634
+ vars: { fe_repair: "false" }
548
635
 
549
636
  - id: repair_backend
550
637
  condition: "{{be_repair}} == true"
@@ -553,7 +640,8 @@ workflow:
553
640
  output_target: { type: file, path: "{{backend_plan}}" }
554
641
  task: |
555
642
  spec.md is the ANCHOR — update backend-plan.md (in context) to CONFORM to it,
556
- closing the gaps whose lane is `backend` (the spec WINS every time):
643
+ closing the gaps whose lane is `backend` and whose route is `plan` (the spec WINS
644
+ every time). Leave `specify`-route gaps as the DA-n notes described below:
557
645
  {{analyze_gaps}}
558
646
  - Add a plan home for each uncovered spec FR/BR/CN/DC/IC/US/SC (a lifted
559
647
  CN-/DC-/IC- lands in a §1 domain invariant enforcement · the persistence
@@ -591,7 +679,9 @@ workflow:
591
679
  output_target: { type: file, path: "{{frontend_plan}}" }
592
680
  task: |
593
681
  spec.md is the ANCHOR — update frontend-plan.md (in context) to CONFORM to it,
594
- closing the gaps whose lane is `frontend` (the spec WINS every time):
682
+ closing the gaps whose lane is `frontend` and whose route is `plan` (the spec WINS
683
+ every time). Leave each `specify`-route gap open, logged as a DA-n that routes to
684
+ /appweaver specify:
595
685
  {{analyze_gaps}}
596
686
  - Give every uncovered Experience Standard (UX-), user story and
597
687
  interaction-state FR- a home: a screen in the navigation section, a flow row,
@@ -618,52 +708,51 @@ workflow:
618
708
  already-conformant section VERBATIM.
619
709
  Return the COMPLETE updated frontend-plan.md content only.
620
710
 
621
- # THE STOP IS MASTER MODE'S ALONE. Two independent raises, because the two reasons to
622
- # pass are unrelated: the plans conform, or this run was never allowed to change them.
623
- # A read-only run that found gaps has done its whole job by reporting them — stopping
624
- # would fail the operator for drift that finalize-delta created on purpose and that only
625
- # /appweaver plan main can close.
626
- - id: gate_ok_conformant
627
- condition: "{{analyze_ok}} == true"
628
- action: set
629
- vars: { gate_ok: "true" }
630
- - id: gate_ok_readonly
631
- condition: "{{may_repair}} == false"
632
- action: set
633
- vars: { gate_ok: "true" }
634
-
635
- - id: analyze_gate
636
- action: verify_context
637
- require_true: gate_ok
638
- on_false: stop
639
- 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."
640
-
641
711
  # THE RECEIPT. implement generates from the plans and deliberately does not read the
642
- # spec, on the strength of this stage having conformed them to it — which until now was
643
- # an assumption about the order the operator ran things in, checked by nothing. Record
644
- # WHAT was proven: the bytes of spec.md, the context's domain-model.md and each lane plan
645
- # that was present. plan-gate-check reads it at implement.
712
+ # spec, on the strength of this stage having checked them against it. Record WHAT was
713
+ # checked — the bytes of spec.md, the context's domain-model.md and each lane plan that
714
+ # was present — and the VERDICT on them. plan-gate-check reads it at implement.
646
715
  #
647
- # ONLY A GREEN MASTER RUN LEAVES ONE. The AND is the kit's ordered assignment — under
648
- # master mode, take the conformance verdict as the answer — because the engine has no
649
- # compound conditions. A REPORT-mode run proves nothing about the masters (it may not
650
- # even repair them), and a red master run proved the opposite.
716
+ # EVERY MASTER RUN LEAVES ONE, and it is written BEFORE the stop below, so a red run
717
+ # records its `gaps` verdict too and implement refuses it. A REPORT-mode run leaves
718
+ # none: it checked the masters while a delta was in flight, and they are about to move.
651
719
  - id: flag_receipt
652
720
  condition: "{{may_repair}} == true"
653
721
  action: set
654
- vars: { stamp_receipt: "{{analyze_ok}}" }
722
+ vars: { stamp_receipt: "true" }
655
723
 
656
724
  - id: plan_gate_stamp
657
725
  condition: "{{stamp_receipt}} == true"
658
726
  action: execute_script
659
727
  script: { ref: actions }
660
728
  subcommand: plan-gate-stamp
661
- args: ["--feature-dir", "{{feature_dir}}", "--spec-version", "{{version}}", "--mode", "{{gate_mode}}"]
729
+ args: ["--feature-dir", "{{feature_dir}}", "--spec-version", "{{version}}", "--mode", "{{gate_mode}}", "--verdict", "{{gate_verdict}}", "--plan-gaps", "{{plan_gap_count}}", "--routed", "{{routed_gaps}}"]
662
730
  outputs:
663
731
  - { name: receipt_file, from: "$.file" }
664
732
  - { name: receipt_gated, from: "$.gated" }
665
733
  - { name: receipt_count, from: "$.count" }
666
734
 
735
+ # THE STOP IS MASTER MODE'S ALONE, and only a PLAN gap triggers it. Two independent
736
+ # raises, because the two reasons to pass are unrelated: no gap is left that a plan
737
+ # rewrite closes, or this run was never allowed to rewrite. A read-only run that found
738
+ # gaps has done its whole job by reporting them — stopping would fail the operator for
739
+ # drift that finalize-delta created on purpose and that only /appweaver plan main can
740
+ # close. Routed gaps pass here and are listed in the outputs for /appweaver specify.
741
+ - id: gate_ok_conformant
742
+ condition: "{{plan_ok}} == true"
743
+ action: set
744
+ vars: { gate_ok: "true" }
745
+ - id: gate_ok_readonly
746
+ condition: "{{may_repair}} == false"
747
+ action: set
748
+ vars: { gate_ok: "true" }
749
+
750
+ - id: analyze_gate
751
+ action: verify_context
752
+ require_true: gate_ok
753
+ on_false: stop
754
+ 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}}). Either you chose `Report and Skip`, in which case plan-analyze-report.md beside the plans lists them — or repair ran up to three times and the fourth pass, which checks and does not rewrite, still found these. Every gap above was read off the plans as they stand on disk now. 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."
755
+
667
756
  # (No change-close here by design — this stage never archives anything in either mode,
668
757
  # and cr-archive belongs to close-delta, which runs after finalize-delta's merge.)
669
758
 
@@ -687,10 +776,13 @@ outputs:
687
776
  domain_model: "{{domain_model}}"
688
777
  conformant: "{{analyze_ok}}"
689
778
  gaps: "{{analyze_gaps}}"
779
+ verdict: "{{gate_verdict}}" # conformant | routed-gaps | gaps
780
+ plan_gaps: "{{plan_gap_count}}" # open gaps a plan rewrite closes
781
+ routed_to_specify: "{{routed_csv}}" # open gaps only /appweaver specify can close
690
782
  version: "{{version}}"
691
783
  gate_mode: "{{gate_mode}}" # master (repair allowed) | report (read-only)
692
784
  open_changes: "{{open_ids}}" # empty in master mode, by definition
693
- gate_receipt: "{{receipt_file}}" # written only by a GREEN master run; implement reads it
785
+ gate_receipt: "{{receipt_file}}" # written by every master run, with its verdict; implement reads it
694
786
  gate_receipt_at: "{{receipt_gated}}"
695
787
  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."
696
- 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."
788
+ 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.2.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,11 +124,24 @@ 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"
130
131
  # Verdict.
131
132
  requirements_ok: "false"
133
+ # Findings split by the command that closes them (gap-route). Defaults read as
134
+ # "nothing found", so a run where the router could not execute still prints.
135
+ gap_summary: "no findings"
136
+ gap_total: "0"
137
+ fix_code: "none"
138
+ fix_code_n: "0"
139
+ fix_plan: "none"
140
+ fix_plan_n: "0"
141
+ fix_spec: "none"
142
+ fix_spec_n: "0"
143
+ fix_review: "none"
144
+ fix_review_n: "0"
132
145
  gate_ok: "false"
133
146
 
134
147
  workflow:
@@ -155,9 +168,11 @@ workflow:
155
168
  - { name: be_declared, from: "$.backend_declared" }
156
169
  - { name: be_lane_id, from: "$.backend_lane_id" }
157
170
  - { name: be_source_path, from: "$.backend_source_path" }
171
+ - { name: be_test_path, from: "$.backend_test_path" }
158
172
  - { name: fe_declared, from: "$.frontend_declared" }
159
173
  - { name: fe_lane_id, from: "$.frontend_lane_id" }
160
174
  - { name: fe_source_path, from: "$.frontend_source_path" }
175
+ - { name: fe_test_path, from: "$.frontend_test_path" }
161
176
 
162
177
  - id: require_lane
163
178
  action: verify_context
@@ -316,6 +331,7 @@ workflow:
316
331
  - { name: test_ok, from: "$.fence_green" }
317
332
  - { name: test_tool, from: "$.tool" }
318
333
  - { name: test_log, from: "$.log_tail" }
334
+ - { name: test_excerpt, from: "$.failing_excerpt" } # the lines that NAME the failure
319
335
  - { name: test_scoped, from: "$.scoped" }
320
336
  # A GREEN FENCE IS NOT ALWAYS AN EXECUTED SUITE. test-run reports fence_green true
321
337
  # with ran=false on a project whose lanes declare no test command — the vacuous pass
@@ -414,9 +430,11 @@ workflow:
414
430
  the pre-existing slice.
415
431
 
416
432
  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.
433
+ owns it: this lane's production code under its source path `{{be_source_path}}`, its
434
+ tests under its test path `{{be_test_path}}`. A service-lane element written into the
435
+ frontend lane's subtree — or outside every lane's paths — is a HIGH `placement` gap.
436
+ One edit outside them is allowed: registering a test project this change created in
437
+ the file the lane's test command discovers tests through (e.g. a .NET solution file).
420
438
 
421
439
  PLAN-ONLY CHANGE — if the delta-spec is an unfilled stub, this change deliberately
422
440
  alters only HOW the EXISTING spec is realised (a refactor, an architectural decision).
@@ -506,9 +524,11 @@ workflow:
506
524
  gap. Judge it only over what this change touched.
507
525
 
508
526
  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.
527
+ source path, `{{fe_source_path}}`, or — for a test — its test path, `{{fe_test_path}}`.
528
+ A file written into the service lane's subtree is a HIGH `placement` gap — a gap in the
529
+ service surface is a contract delta raised back to that lane, never a client-side edit
530
+ to it. The one edit allowed outside them is the test setup this change had to create,
531
+ when the lane's test command discovers tests through a file elsewhere.
512
532
 
513
533
  PLAN-ONLY CHANGE — if the delta-spec is an unfilled stub, this change alters only HOW
514
534
  the existing spec is realised. Checks 1 to 3 have no anchor and are SKIPPED, not
@@ -539,6 +559,30 @@ workflow:
539
559
  action: set
540
560
  vars: { requirements_ok: "false" }
541
561
 
562
+ # WHO CAN CLOSE WHAT. Both lanes' findings carry a `fix` tag already; this turns the tag
563
+ # into routing. It matters most for a `plan` finding: the delta plan is what is wrong, and
564
+ # `implement` generates FROM that plan, so sending it there reproduces the defect — which
565
+ # is what left the mock run's ChiefComplaintField gap closable only by hand-editing a
566
+ # generated plan. Deterministic, and it never drops a finding: one with no recognised
567
+ # `fix` is routed to `review` and counted, rather than folded into the largest bucket.
568
+ - id: route_gaps
569
+ action: execute_script
570
+ on_error: continue
571
+ script: { ref: actions }
572
+ subcommand: gap-route
573
+ args: ["--cr", "{{cr_id}}", "--gaps", "{{be_gaps}}", "--gaps", "{{fe_gaps}}"]
574
+ outputs:
575
+ - { name: gap_summary, from: "$.summary" }
576
+ - { name: gap_total, from: "$.total" }
577
+ - { name: fix_code, from: "$.implement_text" }
578
+ - { name: fix_code_n, from: "$.implement_count" }
579
+ - { name: fix_plan, from: "$.plan_text" }
580
+ - { name: fix_plan_n, from: "$.plan_count" }
581
+ - { name: fix_spec, from: "$.specify_text" }
582
+ - { name: fix_spec_n, from: "$.specify_count" }
583
+ - { name: fix_review, from: "$.review_text" }
584
+ - { name: fix_review_n, from: "$.review_count" }
585
+
542
586
  # gate_ok = the tests ran green AND every gated lane's requirements are met.
543
587
  - id: gate_flag
544
588
  condition: "{{test_ok}} == true"
@@ -568,8 +612,8 @@ workflow:
568
612
  merges the change into spec.md and domain-model.md.
569
613
  Evidence:
570
614
  - 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}}
615
+ matched a slice filter={{test_matched}}, slice-scoped={{test_scoped}}. Failing
616
+ lines: {{test_excerpt}} · Log tail: {{test_log}}
573
617
  - service lane gated={{be_gated}}, verdict ok={{be_ok}}, gaps: {{be_gaps}}
574
618
  - experience lane gated={{fe_gated}}, verdict ok={{fe_ok}}, gaps: {{fe_gaps}}
575
619
  Markdown format:
@@ -608,7 +652,31 @@ workflow:
608
652
  action: verify_context
609
653
  require_true: gate_ok
610
654
  on_false: stop
611
- on_false_message: "Change {{cr_id}} does NOT yet meet its delta spec. Tests green={{test_ok}} (tool `{{test_tool}}`); service lane gated={{be_gated}} ok={{be_ok}}; experience lane gated={{fe_gated}} ok={{fe_ok}}. Findings — backend: {{be_gaps}} · frontend: {{fe_gaps}}. The full report, with the command that closes each finding, is at {{cr_dir}}/delta-slice-analyze-report.md. DO NOT run finalize-delta yet: merging now would write requirements into spec.md and domain-model.md that the code does not satisfy, and the masters would describe an intention rather than the system. Fix the findings (a `requirement` or `plan` finding is usually /appweaver implement {{cr_id}}), re-run build, then re-run this gate."
655
+ on_false_message: >
656
+ Change {{cr_id}} does NOT yet meet its delta spec. Tests green={{test_ok}} (tool
657
+ `{{test_tool}}`); service lane gated={{be_gated}} ok={{be_ok}}; experience lane
658
+ gated={{fe_gated}} ok={{fe_ok}}. {{gap_summary}}
659
+
660
+ EACH FINDING IS CLOSED BY A DIFFERENT COMMAND — they are not interchangeable, and a
661
+ plan defect sent to implement comes back unchanged, because implement generates FROM
662
+ the plan.
663
+
664
+ SPEC / MODEL ({{fix_spec_n}}): {{fix_spec}}
665
+
666
+ DELTA PLAN ({{fix_plan_n}}): {{fix_plan}}
667
+
668
+ CODE ({{fix_code_n}}): {{fix_code}}
669
+
670
+ UNROUTED ({{fix_review_n}}): {{fix_review}}
671
+
672
+ Work them in that order — the spec before the plan that realises it, the plan before
673
+ the code that maps to it. Re-running /appweaver plan {{cr_id}} re-derives the delta
674
+ plans with this report in context, so a plan finding is fixed by re-deriving rather
675
+ than by hand-editing a generated document. The full report is at
676
+ {{cr_dir}}/delta-slice-analyze-report.md. DO NOT run finalize-delta yet: merging now
677
+ would write requirements into spec.md and domain-model.md that the code does not
678
+ satisfy, and the masters would describe an intention rather than the system. Then
679
+ re-run build and re-run this gate.
612
680
 
613
681
  # 6. Post-execution hooks.
614
682
  - id: resolve_post_hooks