aw-spec-cli 0.1.32 → 0.1.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/README.md +76 -81
  2. package/assets/.appweaver/DESIGN-FORMAT.md +56 -17
  3. package/assets/.appweaver/STACK-PROFILES.md +95 -60
  4. package/assets/.appweaver/orchestrator/LAUNCHER.md +1 -1
  5. package/assets/.appweaver/orchestrator/modules/delta-plan.orch.yaml +173 -32
  6. package/assets/.appweaver/orchestrator/modules/domain-select.orch.yaml +29 -1
  7. package/assets/.appweaver/orchestrator/modules/foundation-pack.orch.yaml +7 -4
  8. package/assets/.appweaver/orchestrator/modules/foundation-spec.orch.yaml +49 -60
  9. package/assets/.appweaver/orchestrator/modules/tech-select.orch.yaml +58 -0
  10. package/assets/.appweaver/orchestrator/orchestrator.py +26 -8
  11. package/assets/.appweaver/orchestrator/rev-templates/delta-design-template.skeleton.md +20 -16
  12. package/assets/.appweaver/orchestrator/rev-templates/design-template.skeleton.md +30 -6
  13. package/assets/.appweaver/orchestrator/rev-templates/ui-delta-design-template.skeleton.md +9 -7
  14. package/assets/.appweaver/orchestrator/rev-templates/ui-design-template.skeleton.md +5 -5
  15. package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +341 -36
  16. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan-delta.orch.yaml +254 -43
  17. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +248 -68
  18. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +251 -20
  19. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +316 -10
  20. package/assets/.appweaver/orchestrator/workflows/aw-spec-build.orch.yaml +6 -47
  21. package/assets/.appweaver/orchestrator/workflows/aw-spec-close-delta.orch.yaml +62 -16
  22. package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +132 -9
  23. package/assets/.appweaver/orchestrator/workflows/aw-spec-fix-test-coverage.orch.yaml +965 -0
  24. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +222 -169
  25. package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +28 -12
  26. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +453 -116
  27. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-lane.orch.yaml +7 -4
  28. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-stack.orch.yaml +83 -6
  29. package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +45 -2
  30. package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +324 -74
  31. package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +275 -44
  32. package/assets/.appweaver/scripts/python/aw-foundation.py +16 -13
  33. package/assets/.appweaver/scripts/python/aw-gaps.py +11 -3
  34. package/assets/.appweaver/scripts/python/aw-model-guard.py +295 -0
  35. package/assets/.appweaver/scripts/python/aw-plan-coverage.py +412 -3
  36. package/assets/.appweaver/scripts/python/aw-plangate.py +23 -16
  37. package/assets/.appweaver/scripts/python/aw-stack.py +1794 -265
  38. package/assets/.appweaver/stack-profiles/dotnet-webapi.json +0 -2
  39. package/assets/.appweaver/stack-profiles/java-spring.json +0 -2
  40. package/assets/.appweaver/templates/api-plan-template.md +107 -0
  41. package/assets/.appweaver/templates/delta-api-plan-template.md +87 -0
  42. package/assets/.appweaver/templates/delta-design-template.md +25 -23
  43. package/assets/.appweaver/templates/delta-spec-template.md +3 -3
  44. package/assets/.appweaver/templates/design-template.md +20 -42
  45. package/assets/.appweaver/templates/domain-model-template.md +3 -0
  46. package/assets/.appweaver/templates/ui-delta-design-template.md +11 -8
  47. package/assets/.appweaver/templates/ui-design-template.md +4 -4
  48. package/assets/foundations/foundation.spec.template.txt +25 -18
  49. package/assets/guardrails/README.md +61 -25
  50. package/assets/guardrails/methodology/ux/accessibility-standards.md +38 -0
  51. package/assets/guardrails/methodology/ux/content-design.md +34 -0
  52. package/assets/guardrails/methodology/ux/interaction-states.md +45 -0
  53. package/assets/guardrails/methodology/ux/journey-mapping.md +41 -0
  54. package/assets/guardrails/project-specific/domain/domain-requirements.md +25 -38
  55. package/assets/guardrails/project-specific/domain-rule-bindings.md +33 -69
  56. package/assets/guardrails/project-specific/frontend/ui-requirements.md +2 -2
  57. package/assets/guardrails/project-specific/service/technical-requirements.md +2 -2
  58. package/assets/guardrails/project-specific/tech-rule-bindings.md +16 -13
  59. package/assets/guardrails/sample-healthcare/domain/domain-requirements.md +58 -0
  60. package/assets/guardrails/sample-healthcare/domain-rule-bindings.md +138 -0
  61. package/assets/guardrails/sample-healthcare/frontend/ui-requirements.md +43 -0
  62. package/assets/guardrails/sample-healthcare/service/technical-requirements.md +40 -0
  63. package/assets/guardrails/sample-healthcare/tech-rule-bindings.md +303 -0
  64. package/dist/commands/init.d.ts.map +1 -1
  65. package/dist/commands/init.js +216 -7
  66. package/dist/commands/init.js.map +1 -1
  67. package/dist/utils/config.js +1 -1
  68. package/dist/utils/config.js.map +1 -1
  69. package/dist/utils/foundation.d.ts +36 -0
  70. package/dist/utils/foundation.d.ts.map +1 -0
  71. package/dist/utils/foundation.js +109 -0
  72. package/dist/utils/foundation.js.map +1 -0
  73. package/dist/utils/paths.js +1 -1
  74. package/dist/utils/paths.js.map +1 -1
  75. package/package.json +1 -1
@@ -5,12 +5,13 @@
5
5
  # specify → plan → analyze-plan → implement.
6
6
  #
7
7
  # FOUR CHECKS, one per way a plan can be wrong:
8
- # 1. spec.md → backend-plan.md — every spec item has a service home
8
+ # 1. spec.md → backend-plan.md + api-plan.md — every spec item has a service home, and
9
+ # every endpoint the API plan publishes is realised by a use-case of the backend plan
9
10
  # 2. spec.md → frontend-plan.md — every experience item has a screen home
10
- # 3. backend-plan.md ↔ frontend-plan.md — CONTRACT BINDING: every call the frontend
11
- # makes exists in the service's published surface, with the same field shapes. This
12
- # is the check neither one-sided pass can make: a frontend call the backend never
13
- # defines passes both coverage checks and fails at integration.
11
+ # 3. api-plan.md ↔ frontend-plan.md — CONTRACT BINDING: every call the frontend makes
12
+ # exists in the service's published API, with the same field shapes. This is the
13
+ # check neither one-sided pass can make: a frontend call the backend never defines
14
+ # passes both coverage checks and fails at integration.
14
15
  # 4. the plans → domain-model.md — no invented aggregate, no unenforced invariant. The
15
16
  # model is spec-tier and is READ here, never written.
16
17
  # Plus the OVERLAY BACKSTOP: the domain gate checklist, as a fifth pass over both plans.
@@ -30,19 +31,30 @@
30
31
  # refuses the backend's implement, and the reverse. With the default (every declared
31
32
  # lane) every plan is gated, as before — each in its own receipt.
32
33
  #
34
+ # THE BACKEND LANE IS TWO DOCUMENTS. Its endpoints and its answers to the frontend's
35
+ # requests are in api-plan.md, and backend-plan.md realises each endpoint as a use-case.
36
+ # Both are in context when the backend plan is, both are hashed into the backend receipt,
37
+ # and a repair rewrites the one the gap is in: api-plan.md first, for a gap in an endpoint
38
+ # or a request's answer, then backend-plan.md, which realises what the API plan states. A
39
+ # feature planned before API plans existed keeps its endpoints in its backend plan's API
40
+ # section; that section is then the API every check reads, and an API repair rewrites the
41
+ # backend plan. (`plan main` offers to move them out.)
42
+ #
33
43
  # THE REQUEST LEDGER IS READ BY SCRIPT. What the frontend plan asks of the service sits in
34
- # its §5 "Contract deltas needed" table, one RQ-n per request, and the backend plan answers
35
- # each in its API section's "Requests from the frontend lane" table. contract-requests reads
36
- # both on every pass, so check 3 is TOLD which requests are open (a backend-lane gap: answer
37
- # it, by publishing or declining) and which wait on the frontend (answered and not re-bound,
38
- # or declined). A frontend developer's run therefore reports open requests for the backend
44
+ # its §5 "Contract deltas needed" table, one RQ-n per request, and the API plan answers each
45
+ # in its "Requests from the frontend lane" table. contract-requests reads both on every
46
+ # pass, so check 3 is TOLD which requests are open (a backend-lane gap: answer it, by
47
+ # publishing or declining) and which wait on the frontend (answered and not re-bound, or
48
+ # declined). A frontend developer's run therefore reports open requests for the backend
39
49
  # developer instead of stopping on them, and a backend developer's repair answers them.
40
- # A master run that gates the backend plan also redraws api-draft.json from its API
41
- # section, so the draft the frontend generates against matches the plan as repaired.
50
+ # A master run that gates the backend lane also redraws api-draft.json from the API plan, so
51
+ # the draft the frontend generates against matches the plan as repaired, and warns when the
52
+ # redraw lost an operation the previous draft had.
42
53
  #
43
- # THE FRONTEND RECEIPT HASHES THE BACKEND'S PUBLISHED SURFACE TOO (the API section of
44
- # backend-plan.md): when the backend developer changes it — an answered request, a renamed
45
- # operation — the frontend lane is stale at implement until its plan is gated again.
54
+ # THE FRONTEND RECEIPT HASHES THE SERVICE'S PUBLISHED API TOO (api-plan.md, or the backend
55
+ # plan's API section for a feature planned before it): when the backend developer changes
56
+ # it — an answered request, a renamed operation — the frontend lane is stale at implement
57
+ # until its plan is gated again.
46
58
  #
47
59
  # THIS STAGE VALIDATES; `plan` AUTHORS. A declared lane whose plan document is MISSING
48
60
  # STOPS the run naming /appweaver plan — it is not scaffolded and repaired here. Filling
@@ -108,8 +120,8 @@
108
120
  # =============================================================================
109
121
 
110
122
  name: aw-spec-analyze-plan
111
- 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. Each gated lane runs in one of two modes, decided from the changes/ directory rather than the ledger and defaulting to the read-only one — MASTER (no open change names that lane on its **Lanes** line) checks, repairs on approval and stops on unreconciled gaps; REPORT (an open change names it) runs the same checks read-only, because a master plan may not be rewritten while a delta to that lane is in flight. With no lane in master mode the run writes plan-analyze-report.md without asking, repairing or stopping. It never opens, merges or closes a change.
112
- version: 3.6.0
123
+ description: The spec↔plan conformance gate for every declared lane — confirm backend-plan.md with its api-plan.md and frontend-plan.md follow spec.md, that the frontend binds to the API the service publishes, 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. Each gated lane runs in one of two modes, decided from the changes/ directory rather than the ledger and defaulting to the read-only one — MASTER (no open change names that lane on its **Lanes** line) checks, repairs on approval and stops on unreconciled gaps; REPORT (an open change names it) runs the same checks read-only, because a master plan may not be rewritten while a delta to that lane is in flight. With no lane in master mode the run writes plan-analyze-report.md without asking, repairing or stopping. It never opens, merges or closes a change.
124
+ version: 3.9.1
113
125
  agent_agnostic: true
114
126
  dialect: aw-spec.orchestrator/v1
115
127
 
@@ -190,6 +202,25 @@ vars:
190
202
  req_frontend_text: "none"
191
203
  req_summary: "no contract request"
192
204
  draft_path: ""
205
+ draft_plan: "" # the document it was drawn from: api-plan.md, or the backend plan
206
+ draft_unread: "0"
207
+ draft_unread_text: "none"
208
+ draft_clean: "true"
209
+ draft_dropped: "0" # operations the previous draft had and this one does not
210
+ draft_dropped_text: "none"
211
+ draft_kept: "true"
212
+ # THE API PLAN — the backend lane's second document. In context with the backend plan;
213
+ # absent for a feature planned before API plans existed, whose API section is then the
214
+ # API every check reads and every API repair rewrites.
215
+ api_plan: ""
216
+ api_plan_exists: "false"
217
+ api_ctx_go: "false"
218
+ api_plan_ctx: ""
219
+ api_repair: "false" # the analyze pass found a gap in an endpoint or a request's answer
220
+ api_repair_go: "false" # …and this pass rewrites api-plan.md for it
221
+ api_in_backend: "false" # …or, with no API plan, rewrites the backend plan's API section
222
+ # What the backend plan's repair is told about the API.
223
+ be_repair_api_rule: "THE ENDPOINTS ARE IN api-plan.md (in context), repaired before this document whenever a gap was in an endpoint or a request's answer. Do NOT publish or restate endpoints here: REALISE each endpoint of the API plan as the use-case its **Maps to** names, with request and response DTOs matching its shapes. A gap in an endpoint itself — its route, shapes, operation id or a request's answer — is the API plan's, not this document's."
193
224
  # PER-LANE PRESENCE. A lane is PRESENT when it is declared and its plan document is on
194
225
  # disk — its plan is then in context — and MISSING when it is WORKED and the document is
195
226
  # not. The engine has no
@@ -262,6 +293,8 @@ workflow:
262
293
  - { name: frontend_plan, from: "$.frontend_plan" }
263
294
  - { name: backend_plan_exists, from: "$.backend_plan_exists" }
264
295
  - { name: frontend_plan_exists, from: "$.frontend_plan_exists" }
296
+ - { name: api_plan, from: "$.api_plan" }
297
+ - { name: api_plan_exists, from: "$.api_plan_exists" }
265
298
 
266
299
  # THIS FEATURE'S LANES. lane_resolve above read the REPO's declaration; one feature may
267
300
  # own no part of a lane's surface — adopt records `lanes: [backend]` in
@@ -538,18 +571,54 @@ workflow:
538
571
  condition: "{{fe_present}} == true"
539
572
  action: set
540
573
  vars: { fe_plan_ctx: "{{frontend_plan}}" }
574
+ # The API plan rides with the backend plan: in context whenever that one is, when it
575
+ # exists. A feature planned before API plans has none, and its backend plan's API section
576
+ # is read in its place — which is also where an API repair then goes.
577
+ - id: api_ctx_arm
578
+ condition: "{{be_present}} == true"
579
+ action: set
580
+ vars: { api_ctx_go: "{{api_plan_exists}}" }
581
+ - id: api_ctx_path
582
+ condition: "{{api_ctx_go}} == true"
583
+ action: set
584
+ vars: { api_plan_ctx: "{{api_plan}}" }
541
585
 
542
586
  # 2b. Load the domain GATE overlays (the domain-rule-bindings `/aw-spec-gate` checklist) as
543
587
  # the conformance checklist — VALIDATION input only, NEVER authoring. The analyze
544
588
  # step sees them (include_standards: true); the repair steps do NOT (a plan conforms
545
- # to the SPEC, not to the overlays). Empty/no-op for a non-domain project.
589
+ # to the SPEC, not to the overlays). Empty/no-op for a non-domain project — a map whose
590
+ # heading holds a `_None_` line. A map with no such heading, or one naming a file that
591
+ # is not on disk, warns and carries on.
546
592
  - id: resolve_gate_overlays
547
593
  action: execute_script
548
594
  script: { ref: actions }
549
595
  subcommand: resolve-domain
550
596
  args: ["--command", "aw-spec-gate"]
551
597
  outputs:
552
- - { name: gate_overlays, from: "$.candidates" }
598
+ - { name: gate_overlays, from: "$.candidates" }
599
+ - { name: gate_domain, from: "$.domain" }
600
+ - { name: gate_map_exists, from: "$.rulemap_exists" }
601
+ - { name: gate_heading_found, from: "$.heading_found" }
602
+ - { name: gate_missing, from: "$.missing" }
603
+ - id: gate_flags
604
+ action: set
605
+ vars: { gate_all_on_disk: "true" }
606
+ - id: gate_flag_missing
607
+ condition: "{{gate_missing}} != []"
608
+ action: set
609
+ vars: { gate_all_on_disk: "false" }
610
+ - id: warn_gate_heading
611
+ condition: "{{gate_map_exists}} == true"
612
+ action: verify_context
613
+ require_true: gate_heading_found
614
+ on_false: continue
615
+ on_false_message: "WARNING — guardrails/{{gate_domain}}/domain-rule-bindings.md has no `## /aw-spec-gate` heading, so the plans are checked against no domain checklist. If that is not intended, add the heading and its overlay files — it must read exactly `## /aw-spec-gate`; a project with no checklist keeps the heading with a `_None_` line."
616
+ - id: warn_gate_missing
617
+ condition: "{{gate_map_exists}} == true"
618
+ action: verify_context
619
+ require_true: gate_all_on_disk
620
+ on_false: continue
621
+ on_false_message: "WARNING — guardrails/{{gate_domain}}/domain-rule-bindings.md maps these checklist files under `## /aw-spec-gate`, but they are not on disk, so the plans were not checked against them: {{gate_missing}}. Correct the paths, or add the files."
553
622
  - id: load_gate_overlays
554
623
  action: load_standards
555
624
  files_from_variable: gate_overlays
@@ -576,6 +645,7 @@ workflow:
576
645
  context:
577
646
  - { path: "{{spec_file}}" } # THE ANCHOR
578
647
  - { path: "{{domain_model}}", optional: true } # spec-tier, READ here and never written
648
+ - { path: "{{api_plan_ctx}}", optional: true } # the service's published API, with the backend plan
579
649
  - { path: "{{be_plan_ctx}}", optional: true }
580
650
  - { path: "{{fe_plan_ctx}}", optional: true }
581
651
 
@@ -613,11 +683,19 @@ workflow:
613
683
  - { name: req_frontend_text, from: "$.frontend_text" }
614
684
  - { name: req_summary, from: "$.summary" }
615
685
 
686
+ # With no API plan the backend plan's API section is the API, so its repair carries
687
+ # the rules the API repair does — the requests as this pass reads them included.
688
+ - id: be_repair_api_rule_in_plan
689
+ condition: "{{api_plan_exists}} == false"
690
+ action: set
691
+ vars:
692
+ be_repair_api_rule: "THE ENDPOINTS ARE IN THIS DOCUMENT'S API SECTION — this feature was planned before API plans existed. CONTRACT gaps whose lane is `backend`: PUBLISH the missing surface there as endpoint blocks (the `VERB /route` heading, **Operation id**, Request, Success, the Errors table, every shape `{ field: type }`), so the frontend plan has something real to bind to. ANSWER EVERY OPEN CONTRACT REQUEST: {{req_backend_text}}. Record each in the API section's 'Requests from the frontend lane' table (| Request | Outcome | Operation id | Why |; add the table if there is none): PUBLISH it as an endpoint, under the operation id it proposes unless that clashes with one already in use, or DECLINE it with the reason — a request the spec gives no basis for is declined as a spec gap for /appweaver specify, never published on a guess. Keep every row the table already has."
693
+
616
694
  - id: analyze
617
695
  action: agent_query
618
696
  include_standards: true # the gate overlays (checklist) are in the standards
619
- 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>" } ] }
620
- mock: { ok: true, backend_repair: false, frontend_repair: false, gaps: [] }
697
+ schema: { ok: true, backend_repair: false, api_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>" } ] }
698
+ mock: { ok: true, backend_repair: false, api_repair: false, frontend_repair: false, gaps: [] }
621
699
  task: |
622
700
  spec.md is the ANCHOR. Gate this feature's LANE PLANS against it and against the
623
701
  bounded context's domain model, and list EVERY drift as a gap. Run the checks
@@ -631,16 +709,32 @@ workflow:
631
709
  gap still carries the `lane` whose document must change to close it, even when
632
710
  that lane is not gated here — it is reported for that lane's developer.
633
711
 
634
- CHECK 1 — spec.md → backend-plan.md (when the backend plan is in context):
712
+ THE SERVICE'S API is api-plan.md when it is in context — the endpoints the
713
+ service publishes and its answers to the frontend's requests, a document of the
714
+ backend lane beside backend-plan.md, which realises each endpoint as a use-case.
715
+ With no api-plan.md in context the feature was planned before API plans existed,
716
+ and the backend plan's API section is the API. Below, "the API" means whichever
717
+ of the two this feature has.
718
+
719
+ CHECK 1 — spec.md → backend-plan.md and the API (when the backend plan is in
720
+ context):
635
721
  (a) every spec item — FR / Business Rule / Constraint (CN-) / Data
636
722
  Classification (DC-) / Integration Contract (IC-) / user story / success
637
723
  criterion — has a service home, and the Traceability section is complete. A
638
724
  lifted CN-/DC-/IC- with no plan row is a HIGH gap. EXCEPT the Experience
639
725
  Standards (UX-), which the service plan excludes by contract;
640
- (b) NO plan element exceeds or contradicts the spec — anything without a spec
641
- basis is a drift unless already logged as a DA-n assumption;
726
+ (b) NO plan element — and no endpoint of the API — exceeds or contradicts the
727
+ spec: anything without a spec basis is a drift unless already logged as a DA-n
728
+ assumption;
642
729
  (c) the layers are coherent (Application→Domain, API→Application, Infra
643
- implements the declared ports) and it stays high-level + technology-agnostic.
730
+ implements the declared ports) and it stays high-level + technology-agnostic;
731
+ (d) with an api-plan.md: every endpoint it publishes names under **Maps to** a
732
+ use-case the backend plan defines, with DTOs matching its shapes — an endpoint
733
+ nothing realises is a HIGH gap on lane `backend` — and the backend plan restates
734
+ no endpoint (its API section is the one line pointing to api-plan.md). Every
735
+ endpoint is a `VERB /route` block with its shapes as `{ field: type }`: one
736
+ written as a table row or in prose reaches neither api-draft.json nor the
737
+ frontend, a HIGH gap.
644
738
 
645
739
  CHECK 2 — spec.md → frontend-plan.md (when the frontend plan is in context):
646
740
  (a) every Experience Standard (UX-) has a home here — the service plan excludes
@@ -654,21 +748,21 @@ workflow:
654
748
  (d) it stays technology-agnostic — no framework, router, state library, component
655
749
  library or styling approach.
656
750
 
657
- CHECK 3 — CONTRACT BINDING, backend-plan.md ↔ frontend-plan.md (ONLY when
751
+ CHECK 3 — CONTRACT BINDING, the API ↔ frontend-plan.md (ONLY when
658
752
  {{both_lanes}} is true; skip entirely otherwise). This is the check neither
659
753
  one-sided pass can make, and it runs whichever of the two plans is gated:
660
754
  (a) EVERY call, endpoint or payload the frontend plan binds to EXISTS in the
661
- service plan's published API surface. One that does not is a HIGH gap on the
662
- `contract` check — lane `backend` when the spec requires the capability (the
663
- service must publish it), lane `frontend` when the experience invented it;
755
+ API. One that does not is a HIGH gap on the `contract` check — lane `backend`
756
+ when the spec requires the capability (the service must publish it), lane
757
+ `frontend` when the experience invented it;
664
758
  (b) the FIELD SHAPES agree in both directions — a field the frontend reads that
665
759
  the service never returns, and a required field the service demands that the
666
760
  frontend never sends, are both HIGH gaps;
667
761
  (c) THE REQUEST LEDGER, read by script from both plans ({{req_summary}}). OPEN —
668
762
  raised in the frontend plan's "Contract deltas needed" and not answered in the
669
- backend plan's "Requests from the frontend lane": {{req_backend_text}}. Report
670
- EACH as a HIGH `contract` gap on lane `backend`, route `plan`, naming the RQ-n:
671
- the backend plan must publish it or decline it. WAITING ON THE FRONTEND PLAN —
763
+ API's "Requests from the frontend lane": {{req_backend_text}}. Report EACH as a
764
+ HIGH `contract` gap on lane `backend`, route `plan`, naming the RQ-n: the API
765
+ must publish it or decline it. WAITING ON THE FRONTEND PLAN —
672
766
  answered and not yet re-bound, or declined: {{req_frontend_text}}. Report EACH as
673
767
  a HIGH `contract` gap on lane `frontend`, route `plan`. `none` means nothing in
674
768
  that group. A binding row that cites an open request's proposed id is that
@@ -676,12 +770,13 @@ workflow:
676
770
  (d) the frontend plan cites the service's published surface and does NOT restate,
677
771
  re-derive or contradict it, and states no server-side behaviour or persistence
678
772
  assumption;
679
- (e) OPERATION IDS MATCH. Every §5 row's Operation id is the one the backend plan's
680
- §3 gives that endpoint, spelled exactly, and every §3 endpoint has one, unique
681
- across the plan. A §5 row with no id (`—` without a DA-n) or with an id §3 does not
682
- name is a HIGH `contract` gap on lane `frontend`; a §3 endpoint with no id, or two
683
- endpoints sharing one, is one on lane `backend`. Implement finds each endpoint by
684
- this id in the service's exported API document.
773
+ (e) OPERATION IDS MATCH. Every §5 row's Operation id is the one the API gives
774
+ that endpoint, spelled exactly, and every endpoint of the API has one, unique
775
+ across the service. A §5 row with no id (`—` without a DA-n) or with an id the API
776
+ does not name is a HIGH `contract` gap on lane `frontend`; an endpoint with no id,
777
+ or two endpoints sharing one, is one on lane `backend`. Implement finds each
778
+ endpoint by this id in the API document drafted from the API, and analyze-slice
779
+ checks the service serves it under it.
685
780
 
686
781
  CHECK 4 — the plans → domain-model.md (when the model is in context; skip if it
687
782
  is not):
@@ -716,14 +811,18 @@ workflow:
716
811
  - route `plan` for everything a rewrite of a lane plan closes: a missing plan
717
812
  home, a name that differs from the model's, an invariant restated instead of
718
813
  cited, a contract binding, an element with no spec basis.
719
- Set backend_repair true if any `plan`-route gap needs the backend plan rewritten,
720
- and frontend_repair likewise. (If a plan is still the empty template, treat the
814
+ Set api_repair true if any `plan`-route gap is in an endpoint of the API or in a
815
+ request's answer — its route, shapes, operation id, a missing endpoint, an open
816
+ request — and backend_repair true if any needs the backend plan itself rewritten
817
+ (a use-case, the domain, a port, the ledgers); both when both are. Set
818
+ frontend_repair likewise. (If a plan is still the empty template, treat the
721
819
  WHOLE spec as gaps against it.) Set ok=true ONLY when every applicable check
722
820
  passes and no checklist constraint is unaccounted for.
723
821
  outputs:
724
822
  - { name: analyze_ok, from: "$.ok" }
725
823
  - { name: analyze_gaps, from: "$.gaps" }
726
824
  - { name: backend_repair, from: "$.backend_repair" }
825
+ - { name: api_repair, from: "$.api_repair" }
727
826
  - { name: frontend_repair, from: "$.frontend_repair" }
728
827
 
729
828
  # WHO CAN CLOSE EACH GAP, decided by script from the tags above: a LIFT-FAILURE is
@@ -777,9 +876,9 @@ workflow:
777
876
  - "## How to read this" — state BOTH readings and that only the operator knows
778
877
  which applies, because it depends on whether finalize-delta has already run:
779
878
  (a) AFTER finalize-delta merged a change, these gaps are EXPECTED. The lane
780
- plans are DERIVED from spec.md + domain-model.md and finalize-delta merges the
781
- change into those two documents only, so the plans stand at their last authored
782
- content and the spec has moved ahead of them. The remedy is
879
+ plans are DERIVED from spec.md + domain-model.md + api-plan.md and finalize-delta
880
+ merges the change into those documents only, so the lane plans stand at their
881
+ last authored content and the spec and the API have moved ahead of them. The remedy is
783
882
  /appweaver plan main (which re-derives them, carrying DA-n / WA-n forward),
784
883
  after /appweaver close-delta, not a repair of either plan.
785
884
  (b) BEFORE finalize-delta, the masters have not moved, so these gaps are real
@@ -869,6 +968,31 @@ workflow:
869
968
  condition: "{{backend_repair}} == true"
870
969
  action: set
871
970
  vars: { be_repair: "{{be_may_repair}}" }
971
+ # THE API'S OWN REPAIR, by the same ordered assignments. With no api-plan.md the API is
972
+ # the backend plan's API section, so an API gap raises the backend repair instead.
973
+ - id: api_repair_clear
974
+ action: set
975
+ vars: { api_repair_go: "false", api_in_backend: "false" }
976
+ - id: api_repair_flag
977
+ condition: "{{api_repair}} == true"
978
+ action: set
979
+ vars: { api_repair_go: "{{be_may_repair}}" }
980
+ - id: api_repair_in_backend
981
+ condition: "{{api_plan_exists}} == false"
982
+ action: set
983
+ vars: { api_in_backend: "{{api_repair_go}}" }
984
+ - id: api_repair_no_plan
985
+ condition: "{{api_plan_exists}} == false"
986
+ action: set
987
+ vars: { api_repair_go: "false" }
988
+ - id: be_repair_for_api
989
+ condition: "{{api_in_backend}} == true"
990
+ action: set
991
+ vars: { be_repair: "true" }
992
+ - id: api_repair_last_pass
993
+ condition: "{{repairs_left}} == false"
994
+ action: set
995
+ vars: { api_repair_go: "false" }
872
996
  - id: be_repair_last_pass
873
997
  condition: "{{repairs_left}} == false"
874
998
  action: set
@@ -885,6 +1009,40 @@ workflow:
885
1009
  action: set
886
1010
  vars: { fe_repair: "false" }
887
1011
 
1012
+ # The API first, so the backend repair below reads the API as repaired: files in the
1013
+ # envelope are read when each request is assembled.
1014
+ - id: repair_api
1015
+ condition: "{{api_repair_go}} == true"
1016
+ action: agent_author
1017
+ include_standards: false # rewrite-in-place: spec anchors — NOT the overlays
1018
+ output_target: { type: file, path: "{{api_plan}}" }
1019
+ task: |
1020
+ spec.md is the ANCHOR — update api-plan.md (in context), the endpoints this
1021
+ service publishes, to CONFORM to it, closing the gaps whose lane is `backend` that
1022
+ are in an endpoint or a request's answer (the spec WINS every time):
1023
+ {{analyze_gaps}}
1024
+ - CONTRACT gaps: PUBLISH the missing endpoint as a block under "## Endpoints" —
1025
+ the `### `VERB /route`` heading, then **Operation id**, **Auth**, **Request**,
1026
+ **Success**, the **Errors** table and **Maps to** — every shape as
1027
+ `{ field: type }`, so the frontend plan has something real to bind to. Correct a
1028
+ shape or an error contract the spec contradicts.
1029
+ - OPERATION IDS ARE STABLE: an endpoint keeps the id it has; one without an id gets
1030
+ one unique across the service; two endpoints sharing one are split.
1031
+ - ANSWER EVERY OPEN CONTRACT REQUEST: {{req_backend_text}}. Record each in
1032
+ "Requests from the frontend lane" (| Request | Outcome | Operation id | Why |):
1033
+ PUBLISH it as an endpoint, under the operation id it proposes unless that
1034
+ clashes with one already in use, or DECLINE it with the reason — a request the
1035
+ spec gives no basis for is declined as a spec gap for /appweaver specify, never
1036
+ published on a guess. Keep every row the table already has.
1037
+ - REMOVE an endpoint no spec id calls for. The draft drawn after this run reports
1038
+ every operation the previous api-draft.json had and this document no longer has.
1039
+ - **Maps to** names the use-case the backend plan realises the endpoint with; a
1040
+ new endpoint names the use-case the backend plan's repair will add.
1041
+ - Preserve every already-conformant block VERBATIM. Do NOT change the spec, the
1042
+ domain model, the backend plan or the frontend plan. Add one Change History row
1043
+ for the repair; do not invent a version number.
1044
+ Return the COMPLETE updated api-plan.md content only.
1045
+
888
1046
  - id: repair_backend
889
1047
  condition: "{{be_repair}} == true"
890
1048
  action: agent_author
@@ -906,15 +1064,7 @@ workflow:
906
1064
  what one says. DO NOT EDIT domain-model.md — it is spec-tier and read-only
907
1065
  here. If a gap can only be closed by ADDING to the model, you cannot close it:
908
1066
  leave it open and add a DA-n saying it routes to /appweaver specify.
909
- - CONTRACT gaps whose lane is `backend`: PUBLISH the missing surface in the API
910
- section as field shapes, so the frontend plan has something real to bind to.
911
- - ANSWER EVERY OPEN CONTRACT REQUEST: {{req_backend_text}}. Record each in the API
912
- section's "Requests from the frontend lane" table (| Request | Outcome |
913
- Operation id | Why |; add the table if there is none): PUBLISH it as an endpoint,
914
- under the operation id it proposes unless that clashes with one already in use,
915
- or DECLINE it with the reason — a request the spec gives no basis for is
916
- declined as a spec gap for /appweaver specify, never published on a guess.
917
- Keep every row the table already has.
1067
+ - {{be_repair_api_rule}}
918
1068
  - REMOVE, or move to a DA-n assumption, any plan element with no spec basis —
919
1069
  never keep a plan that exceeds or contradicts the spec. EXCEPTION: do NOT
920
1070
  remove a cross-cutting element that is (or may be) mandated by the technical
@@ -928,9 +1078,9 @@ workflow:
928
1078
  note "lift-failure: <constraint> — re-run /appweaver specify to lift it into
929
1079
  CN-/DC-/IC-". (The gate will stop and direct the user to specify.)
930
1080
  - Keep the Traceability section in sync. Preserve every already-conformant
931
- section VERBATIM. Do NOT change the spec, the domain model or the frontend plan.
932
- Close only the gaps whose lane is `backend`; a frontend gap is not this
933
- document's.
1081
+ section VERBATIM. Do NOT change the spec, the domain model, the frontend plan or
1082
+ api-plan.md. Close only the gaps whose lane is `backend`; a frontend gap is not
1083
+ this document's.
934
1084
  Return the COMPLETE updated backend-plan.md content only.
935
1085
 
936
1086
  - id: repair_frontend
@@ -951,10 +1101,10 @@ workflow:
951
1101
  - Realise each CN-/DC-/IC-/SC- with an experience obligation, citing its spec id
952
1102
  — the authorization surface and access conditions, what may not reach the
953
1103
  client or its logs, storage and URLs, the binding rows, the performance row.
954
- - CONTRACT gaps whose lane is `frontend`: bind ONLY to the service plan's
955
- published surface (in context). CITE it; do not restate, re-derive or
956
- contradict it, and introduce NO server-side behaviour or persistence
957
- assumption. A capability the experience needs that the surface does not offer
1104
+ - CONTRACT gaps whose lane is `frontend`: bind ONLY to the service's published
1105
+ API in context — api-plan.md, or the API section of a backend plan written
1106
+ before API plans existed. CITE it; do not restate, re-derive or contradict it,
1107
+ and introduce NO server-side behaviour or persistence assumption. A capability the experience needs that the surface does not offer
958
1108
  is a CONTRACT REQUEST in §5's "Contract deltas needed" table — a new RQ-n, the
959
1109
  operation id it proposes, what it sends and receives, the spec ids it serves —
960
1110
  and the binding row that needs it cites that id; it is the service lane's to
@@ -964,7 +1114,7 @@ workflow:
964
1114
  declined one, or log a DA-n routing the need to /appweaver specify.
965
1115
  - Use the domain model's UBIQUITOUS LANGUAGE exactly in labels, states and
966
1116
  messages, and cite BR-nnn rather than restating a rule. DO NOT EDIT
967
- domain-model.md or backend-plan.md.
1117
+ domain-model.md, api-plan.md or backend-plan.md.
968
1118
  - A state the UI can reach that the spec does not describe is a SPEC gap: log it
969
1119
  as a DA-n that routes to /appweaver specify; never invent the requirement here.
970
1120
  - Stay TECHNOLOGY-AGNOSTIC: no framework, router, state library, component
@@ -999,9 +1149,13 @@ workflow:
999
1149
  action: set
1000
1150
  vars: { stamp_fe: "{{fe_may_repair}}" }
1001
1151
 
1002
- # THE DRAFT, redrawn from the backend plan as this run leaves it — a repair may have
1003
- # published an endpoint or answered a request. Master mode only, like the receipt, and
1004
- # only for a gated backend plan. Advisory: it never fails the gate.
1152
+ # THE DRAFT, redrawn from the API plan as this run leaves it (the backend plan's API
1153
+ # section, for a feature planned before API plans existed) — a repair may have published
1154
+ # an endpoint or answered a request. It is the API contract the frontend lane binds to
1155
+ # and analyze-slice checks the code against. Master mode only, like the receipt, and only
1156
+ # for a gated backend lane. Advisory: it never fails the gate. A shape it cannot type is
1157
+ # reported, because the frontend's client gets that data untyped, and so is an operation
1158
+ # the previous draft had and this one does not, because the frontend may still bind to it.
1005
1159
  - id: contract_draft
1006
1160
  condition: "{{stamp_be}} == true"
1007
1161
  action: execute_script
@@ -1010,7 +1164,32 @@ workflow:
1010
1164
  subcommand: contract-draft
1011
1165
  args: ["--feature-dir", "{{feature_dir}}"]
1012
1166
  outputs:
1013
- - { name: draft_path, from: "$.path" }
1167
+ - { name: draft_path, from: "$.path" }
1168
+ - { name: draft_plan, from: "$.plan" }
1169
+ - { name: draft_unread, from: "$.unread_count" }
1170
+ - { name: draft_unread_text, from: "$.unread_text" }
1171
+ - { name: draft_dropped, from: "$.dropped_count" }
1172
+ - { name: draft_dropped_text, from: "$.dropped_text" }
1173
+ - id: draft_unread_flag
1174
+ condition: "{{draft_unread}} != 0"
1175
+ action: set
1176
+ vars: { draft_clean: "false" }
1177
+ - id: warn_draft_unread
1178
+ condition: "{{stamp_be}} == true"
1179
+ action: verify_context
1180
+ require_true: draft_clean
1181
+ on_false: continue
1182
+ on_false_message: "API DRAFT — {{draft_unread}} endpoint shape(s) in {{draft_plan}} are not written as `{ field: type }`, so the frontend's client gets that data untyped: {{draft_unread_text}}. Write each request and response the way the template's endpoint block does, then re-run analyze-plan."
1183
+ - id: draft_dropped_flag
1184
+ condition: "{{draft_dropped}} != 0"
1185
+ action: set
1186
+ vars: { draft_kept: "false" }
1187
+ - id: warn_draft_dropped
1188
+ condition: "{{stamp_be}} == true"
1189
+ action: verify_context
1190
+ require_true: draft_kept
1191
+ on_false: continue
1192
+ on_false_message: "API DRAFT — {{draft_dropped}} operation(s) the previous api-draft.json had are no longer in {{draft_plan}}: {{draft_dropped_text}}. The frontend lane may still bind to them. If a repair removed them on purpose, the frontend plan has to stop binding to them; if not, restore their blocks in {{draft_plan}} and re-run analyze-plan."
1014
1193
 
1015
1194
  - id: plan_gate_stamp_backend
1016
1195
  condition: "{{stamp_be}} == true"
@@ -1072,6 +1251,7 @@ outputs:
1072
1251
  feature_dir: "{{feature_dir}}"
1073
1252
  bounded_context: "{{context}}"
1074
1253
  backend_plan: "{{backend_plan}}" # empty when no backend lane is declared
1254
+ api_plan: "{{api_plan_ctx}}" # api-plan.md, gated with the backend plan; empty for a feature planned before API plans, whose backend plan carries its API
1075
1255
  frontend_plan: "{{frontend_plan}}" # empty when no frontend lane is declared
1076
1256
  frontend_excluded: "{{fe_excluded}}" # true = a frontend lane is declared and this feature records no surface in it (feature-lanes)
1077
1257
  domain_model: "{{domain_model}}"
@@ -1092,7 +1272,7 @@ outputs:
1092
1272
  frontend_receipt: "{{fe_receipt}}" # .aw-plan-gate.frontend.json, with the frontend's own verdict ({{fe_verdict}})
1093
1273
  gate_receipt_at: "{{receipt_gated}}"
1094
1274
  other_lane_gaps: "{{other_csv}}" # findings in a plan this run does not repair — another developer's lane, or one an open change names
1095
- contract_requests: "{{req_summary}}" # the frontend plan's RQ-n requests and the backend plan's answers, as this run left them
1096
- api_draft: "{{draft_path}}" # api-draft.json, redrawn from the gated backend plan's API section — commit it with the plan
1097
- note: "Ran for your working lane `{{working_lane}}` — backend plan: {{be_lane_mode}}; frontend plan: {{fe_lane_mode}} ({{open_count}} change(s) open: {{open_ids}}). A lane is in master mode unless an open change names it on its **Lanes** line. Gated: spec→backend-plan ({{be_gated}}), spec→frontend-plan ({{fe_gated}}), the plan↔plan contract binding ({{both_lanes}} — it needs both plans, and reads the one you do not work on without repairing it) and each gated plan against domain-model.md. {{other_gaps}} finding(s) are in a plan this run does not repair — a lane you do not work on, for its developer, or a lane an open change names — and are reported, not repaired: {{other_csv}}. The domain model was READ, never written: a gap that needs a new entity, event or invariant is fixed by /appweaver specify. A plan in REPORT mode was not rewritten either — a master plan may not move while a delta that names its lane is in flight."
1098
- next: "MASTER mode (no open change names the lane, 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 (an open change names the lane — backend: {{be_open_ids}}; frontend: {{fe_open_ids}}): that lane's plan was checked read-only, and a run with no lane in master mode 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."
1275
+ contract_requests: "{{req_summary}}" # the frontend plan's RQ-n requests and the API plan's answers, as this run left them
1276
+ api_draft: "{{draft_path}}" # api-draft.json, redrawn from {{draft_plan}} — commit it with the plans
1277
+ note: "Ran for your working lane `{{working_lane}}` — backend plan: {{be_lane_mode}}; frontend plan: {{fe_lane_mode}} ({{open_count}} change(s) open: {{open_ids}}). A lane is in master mode unless an open change names it on its **Lanes** line. Gated: spec→backend-plan ({{be_gated}}), spec→frontend-plan ({{fe_gated}}), the frontend plan's contract binding against the API ({{both_lanes}} — it needs both lanes' plans, and reads the one you do not work on without repairing it) and each gated plan against domain-model.md. {{other_gaps}} finding(s) are in a plan this run does not repair — a lane you do not work on, for its developer, or a lane an open change names — and are reported, not repaired: {{other_csv}}. The domain model was READ, never written: a gap that needs a new entity, event or invariant is fixed by /appweaver specify. A plan in REPORT mode was not rewritten either — a master plan may not move while a delta that names its lane is in flight."
1278
+ next: "MASTER mode (no open change names the lane, 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 (an open change names the lane — backend: {{be_open_ids}}; frontend: {{fe_open_ids}}): that lane's plan was checked read-only, and a run with no lane in master mode 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 + api-plan.md and finalize-delta moves only those, 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."