aw-spec-cli 0.1.18 → 0.1.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/assets/.appweaver/DESIGN-FORMAT.md +63 -31
  2. package/assets/.appweaver/orchestrator/modules/delta-plan.orch.yaml +62 -14
  3. package/assets/.appweaver/orchestrator/modules/delta-spec.orch.yaml +10 -6
  4. package/assets/.appweaver/orchestrator/modules/lane-gate.orch.yaml +9 -1
  5. package/assets/.appweaver/orchestrator/modules/tech-select.orch.yaml +21 -0
  6. package/assets/.appweaver/orchestrator/orchestrator.py +23 -1
  7. package/assets/.appweaver/orchestrator/rev-templates/delta-design-template.skeleton.md +1 -1
  8. package/assets/.appweaver/orchestrator/rev-templates/design-template.skeleton.md +22 -13
  9. package/assets/.appweaver/orchestrator/rev-templates/ui-delta-design-template.skeleton.md +1 -1
  10. package/assets/.appweaver/orchestrator/rev-templates/ui-design-template.skeleton.md +6 -4
  11. package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +399 -131
  12. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan-delta.orch.yaml +1 -1
  13. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +199 -38
  14. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +1 -1
  15. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +159 -25
  16. package/assets/.appweaver/orchestrator/workflows/aw-spec-build.orch.yaml +20 -9
  17. package/assets/.appweaver/orchestrator/workflows/aw-spec-characterize.orch.yaml +1 -1
  18. package/assets/.appweaver/orchestrator/workflows/aw-spec-close-delta.orch.yaml +183 -40
  19. package/assets/.appweaver/orchestrator/workflows/aw-spec-deploy.orch.yaml +1 -1
  20. package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +311 -36
  21. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +221 -40
  22. package/assets/.appweaver/orchestrator/workflows/aw-spec-inventory.orch.yaml +217 -174
  23. package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +494 -109
  24. package/assets/.appweaver/orchestrator/workflows/aw-spec-parity.orch.yaml +1 -1
  25. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +83 -18
  26. package/assets/.appweaver/orchestrator/workflows/aw-spec-recall-delta.orch.yaml +319 -0
  27. package/assets/.appweaver/orchestrator/workflows/aw-spec-run.orch.yaml +1 -1
  28. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-feature.orch.yaml +29 -23
  29. package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +143 -34
  30. package/assets/.appweaver/scripts/bash/aw-inventory.sh +15 -0
  31. package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +878 -51
  32. package/assets/.appweaver/scripts/bash/aw-xref.sh +3 -0
  33. package/assets/.appweaver/scripts/bash/common.sh +28 -1
  34. package/assets/.appweaver/scripts/powershell/aw-inventory.ps1 +15 -0
  35. package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +868 -51
  36. package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +3 -0
  37. package/assets/.appweaver/scripts/python/aw-inventory.py +1832 -65
  38. package/assets/.appweaver/scripts/python/aw-xref.py +500 -81
  39. package/assets/.appweaver/templates/delta-design-template.md +1 -1
  40. package/assets/.appweaver/templates/delta-domain-model-template.md +1 -1
  41. package/assets/.appweaver/templates/delta-spec-template.md +4 -3
  42. package/assets/.appweaver/templates/design-template.md +56 -40
  43. package/assets/.appweaver/templates/okf-concept-template.md +11 -1
  44. package/assets/.appweaver/templates/proposed-inventory-template.md +127 -0
  45. package/assets/.appweaver/templates/source-map-template.md +5 -3
  46. package/assets/.appweaver/templates/spec-template.md +24 -21
  47. package/assets/.appweaver/templates/ui-delta-design-template.md +1 -1
  48. package/assets/.appweaver/templates/ui-design-template.md +11 -7
  49. package/assets/foundations/dotnet/ModularMonolith/ModularMonolith.pack.txt +4489 -361
  50. package/assets/foundations/dotnet/VerticalSlice/VerticalSlice.pack.txt +2274 -125
  51. package/assets/guardrails/domain/healthcare/auditing.domain.md +3 -3
  52. package/assets/guardrails/domain/pharma/auditing.domain.md +3 -3
  53. package/assets/guardrails/technology/dotnet-webapi/api-versioning.md +1 -1
  54. package/assets/guardrails/technology/dotnet-webapi/auditing.md +1 -1
  55. package/assets/guardrails/technology/dotnet-webapi/authorization.md +1 -1
  56. package/assets/guardrails/technology/dotnet-webapi/clean-architecture.md +1 -1
  57. package/assets/guardrails/technology/dotnet-webapi/cqrs.md +1 -1
  58. package/assets/guardrails/technology/dotnet-webapi/data-classification.md +1 -1
  59. package/assets/guardrails/technology/dotnet-webapi/eventing.md +1 -1
  60. package/assets/guardrails/technology/dotnet-webapi/folder-structure.md +1 -1
  61. package/assets/guardrails/technology/dotnet-webapi/modular-monolith.md +1 -1
  62. package/assets/guardrails/technology/dotnet-webapi/persistence.md +1 -1
  63. package/assets/guardrails/technology/dotnet-webapi/vertical-slices.md +1 -1
  64. package/assets/guardrails/technology/dotnet-webapi/zero-trust.md +1 -1
  65. package/assets/guardrails/technology/nextjs/accessibility-implementation.md +1 -1
  66. package/assets/guardrails/technology/nextjs/api-integration.dotnet-webapi.md +1 -1
  67. package/assets/guardrails/technology/nextjs/api-integration.java-spring.md +1 -1
  68. package/assets/guardrails/technology/nextjs/auth-session.md +1 -1
  69. package/assets/guardrails/technology/nextjs/component-architecture.md +1 -1
  70. package/assets/guardrails/technology/nextjs/design-system.md +1 -1
  71. package/assets/guardrails/technology/nextjs/error-handling.md +1 -1
  72. package/assets/guardrails/technology/nextjs/forms-and-validation.md +1 -1
  73. package/assets/guardrails/technology/nextjs/i18n.md +1 -1
  74. package/assets/guardrails/technology/nextjs/performance-budgets.md +1 -1
  75. package/assets/guardrails/technology/nextjs/routing-navigation.md +1 -1
  76. package/assets/guardrails/technology/nextjs/state-management.md +1 -1
  77. package/package.json +1 -1
@@ -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
@@ -25,13 +25,30 @@
25
25
  # rule set, without the template selection and without the feature's real source files,
26
26
  # which is exactly the work `plan` does properly.
27
27
  #
28
- # BOOTSTRAP-ONLY, and it NEVER opens, merges or closes a change. It gates a feature whose
29
- # spec.md + lane plans ARE the masters. With a change OPEN the delta docs are the
30
- # authoritative in-flight edit and the master pair must not be rewritten under them, so
31
- # the run STOPS and routes to /appweaver analyze-plan-delta (the delta's own gate). The
32
- # change is closed by close-delta, the SINGLE cr-archive caller, and only after
28
+ # TWO MODES, AND THE WRITE IS WHAT SEPARATES THEM. This stage NEVER opens, merges or
29
+ # closes a change; what it can do is REWRITE the master lane plans through its repair loop,
30
+ # and that is the act a change in flight forbids — the delta docs are the authoritative
31
+ # edit and the masters must not drift out from under them before finalize-delta merges.
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.
34
+ # REPORT mode (a change is open) — the same checks, read-only: no ask, no repair, no
35
+ # stop. It writes plan-analyze-report.md and routes on. The plans are
36
+ # DERIVED from spec.md + domain-model.md, so after finalize-delta merged a
37
+ # change the drift it reports is the EXPECTED consequence of that merge and
38
+ # the remedy is /appweaver plan main, not a repair here.
39
+ # Report mode exists because the finalize→close window is exactly when the plans are known
40
+ # to be stale, and a stage that refuses to look is not a gate. It reports; it never writes
41
+ # a plan.
42
+ #
43
+ # WHICH MODE IS READ FROM THE DIRECTORY, AND DEFAULTS TO REPORT. changes/<id>/ is the
44
+ # evidence (cr-list), not active_version.json's `active_change` — the ledger is the most
45
+ # recently OPENED change and a crashed run can leave the two disagreeing. The permission to
46
+ # write is RAISED only by cr-list answering that nothing is open, so a reading that never
47
+ # arrives leaves this stage read-only rather than rewriting masters mid-delta.
48
+ #
49
+ # The change is closed by close-delta, the SINGLE cr-archive caller, and only after
33
50
  # finalize-delta has merged the delta into the masters. Also runnable on demand to
34
- # re-anchor a hand-edited spec — again, only when no change is open.
51
+ # re-anchor a hand-edited spec — in master mode, which means once every change is closed.
35
52
  #
36
53
  # RENAMED FROM `analyze-design`, following design → plan. It also absorbs the ⌛
37
54
  # `analyze-design-ui` phase that was never built as its own command: the frontend lane is
@@ -42,8 +59,8 @@
42
59
  # =============================================================================
43
60
 
44
61
  name: aw-spec-analyze-plan
45
- 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. Bootstrap-only — with a change open it stops and routes to analyze-plan-delta; it never closes a change.
46
- version: 3.0.0
62
+ 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
47
64
  agent_agnostic: true
48
65
  dialect: aw-spec.orchestrator/v1
49
66
 
@@ -58,9 +75,23 @@ standards:
58
75
  vars:
59
76
  analyze_ok: "false"
60
77
  active_change: ""
61
- has_change: "false"
62
78
  version: ""
63
- no_open_change: "true" # NOT has_change ⇒ the masters are authoritative ⇒ this gate applies
79
+ # WHICH MODE THIS RUN IS IN, read from the changes/ DIRECTORY and defaulted to the
80
+ # read-only answer. has_changes starts at the BLOCKING value because an unanswered
81
+ # question is not a clean answer: cr-list runs without on_error: continue, so a failure
82
+ # stops the run outright, and a field that never arrived leaves this stage read-only
83
+ # instead of letting the repair loop rewrite master plans while a delta is in flight.
84
+ has_changes: "true"
85
+ open_ids: "" # cr-list's ids_csv — what is open, named, for the operator
86
+ open_count: "0"
87
+ no_open_change: "false" # RAISED only by cr-list answering that nothing is open
88
+ gate_mode: "report" # report | master — reported in the outputs
89
+ 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
92
+ receipt_file: ""
93
+ receipt_gated: ""
94
+ receipt_count: "0"
64
95
  lanes_declared: "false"
65
96
  be_declared: "false"
66
97
  fe_declared: "false"
@@ -91,6 +122,10 @@ vars:
91
122
  be_plan_ctx: ""
92
123
  fe_plan_ctx: ""
93
124
  domain_model: ""
125
+ owned_sections: "" # the model section(s) this feature answers for — feature-aggregates
126
+ shared_with_csv: ""
127
+ one_to_one: "true"
128
+ model_scope_plan: "" # which part of those sections check 4(a) holds the plan to
94
129
 
95
130
  workflow:
96
131
 
@@ -144,11 +179,40 @@ workflow:
144
179
  - { name: domain_model, from: "$.domain_model" }
145
180
  - { name: domain_model_exists, from: "$.domain_model_exists" }
146
181
 
182
+ # WHICH PART OF THAT MODEL CHECK 4 HOLDS THE PLAN TO. A feature is a capability scope the
183
+ # developer chose — it may act on several aggregates, and several features may act on
184
+ # one — so "the plan matches its aggregate's section" is only exact for a feature that
185
+ # alone answers for that aggregate. Resolved from the spec's Acts-on line, then the
186
+ # boundary map, then the feature name.
187
+ - id: feature_aggregates
188
+ action: execute_script
189
+ on_error: continue
190
+ script: { ref: xref }
191
+ subcommand: feature-aggregates
192
+ args: ["--feature-dir", "{{feature_dir}}"]
193
+ outputs:
194
+ - { name: owned_sections, from: "$.owned_sections" }
195
+ - { name: shared_with_csv, from: "$.shared_with_csv" }
196
+ - { name: one_to_one, from: "$.one_to_one" }
197
+
198
+ # A shared section is realised by several plans, each covering what its own feature
199
+ # acts on; flagging every element one plan leaves out would fail each of them for work
200
+ # another feature's plan carries. Ordered pair of sets — no conditional prompt text.
201
+ - id: model_scope_shared
202
+ action: set
203
+ vars:
204
+ 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"
205
+ - id: model_scope_sole
206
+ condition: "{{one_to_one}} == true"
207
+ action: set
208
+ vars:
209
+ 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"
210
+
147
211
  - id: require_feature
148
212
  action: verify_context
149
213
  require_non_empty: feature_dir
150
214
  on_empty: stop
151
- on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the aggregate, e.g. Order), then re-run analyze-plan."
215
+ 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."
152
216
 
153
217
  - id: verify_spec
154
218
  action: verify_context
@@ -156,33 +220,46 @@ workflow:
156
220
  on_false: stop
157
221
  on_false_message: "No spec.md for the current feature. Run /appweaver specify (and /appweaver plan) first."
158
222
 
159
- # 1b. Read the open change (opened by specify, or by plan on a plan-only change).
160
- # has_change drives the BOOTSTRAP-ONLY guard below — it closes nothing.
161
- - id: read_change
223
+ # 1b. IS ANY CHANGE OPEN — asked of the changes/ DIRECTORY, which is what "open" means.
224
+ # Not version-read's `has_change`: that is bool(active_change), the most recently
225
+ # OPENED change in active_version.json, and a run that died between cr-setup writing
226
+ # the directory and the ledger recording it leaves the two disagreeing. The stage
227
+ # that may rewrite masters has to read the authority every other delta stage reads.
228
+ # No on_error: continue — a cr-list that cannot answer stops the run.
229
+ - id: cr_list
162
230
  action: execute_script
163
231
  script: { ref: actions }
164
- subcommand: version-read
232
+ subcommand: cr-list
165
233
  args: ["--feature-dir", "{{feature_dir}}"]
166
234
  outputs:
235
+ - { name: has_changes, from: "$.has_changes" }
236
+ - { name: open_ids, from: "$.ids_csv" }
237
+ - { name: open_count, from: "$.count" }
167
238
  - { name: active_change, from: "$.active_change" }
168
- - { name: has_change, from: "$.has_change" }
169
- - { name: version, from: "$.version" }
170
-
171
- # 1c. BOOTSTRAP-ONLY GUARD. A change open ⇒ the delta spec + delta plans are the
172
- # authoritative in-flight edit and this stage is the WRONG gate: its repair loop
173
- # rewrites the MASTER plans, which would drift the masters out from under the delta
174
- # before finalize-delta merges it. Stop BEFORE any authoring so the wrong-stage run
175
- # has zero side effects, and route to the delta's own gate.
176
- - id: flag_change_open
177
- condition: "{{has_change}} == true"
239
+
240
+ - id: read_version
241
+ action: execute_script
242
+ script: { ref: actions }
243
+ subcommand: version-read
244
+ args: ["--feature-dir", "{{feature_dir}}"]
245
+ outputs:
246
+ - { name: version, from: "$.version" }
247
+
248
+ # 1c. THE WRITE PERMISSION, raised then verified. Nothing open ⇒ spec.md + the lane plans
249
+ # ARE the masters ⇒ this stage owns them and may repair. Anything else — a change
250
+ # open, or an answer that never arrived — leaves may_repair down and the run is
251
+ # read-only: the checks still run and still report, but no plan is rewritten under a
252
+ # delta in flight. The permission is raised by the good state, never knocked down by
253
+ # the bad one, so the failure direction is refusal to write.
254
+ - id: no_open_change_raise
255
+ condition: "{{has_changes}} == false"
178
256
  action: set
179
- vars: { no_open_change: "false" }
257
+ vars: { no_open_change: "true" }
180
258
 
181
- - id: require_no_change
182
- action: verify_context
183
- require_true: no_open_change
184
- on_false: stop
185
- on_false_message: "Change {{active_change}} is OPEN on this feature — analyze-plan is the BOOTSTRAP gate and must not rewrite the master plans while a delta is in flight. Gate the delta instead: /appweaver analyze-plan-delta → implement → build → analyze-slice-delta → finalize-delta (merges the delta into spec.md and domain-model.md — not the lane plans, which are derived from those two) → analyze-slice → close-delta (archives the change + bumps the version). Re-run analyze-plan once every change is closed, to re-anchor the masters."
259
+ - id: enter_master_mode
260
+ condition: "{{no_open_change}} == true"
261
+ action: set
262
+ vars: { gate_mode: "master", may_repair: "true" }
186
263
 
187
264
  # 2. WHICH LANES ARE GATED. Present = declared AND on disk; missing = declared AND not.
188
265
  # Taking the lane's own `declared` value under the exists condition is how the AND is
@@ -344,9 +421,10 @@ workflow:
344
421
 
345
422
  CHECK 4 — the plans → domain-model.md (when the model is in context; skip if it
346
423
  is not):
347
- (a) the backend plan's DOMAIN layer matches the model EXACTLY — entities and
348
- value objects are its aggregate section's entities (SAME names), the boundary is
349
- its Boundary block, states are the lifecycle it describes;
424
+ (a) the backend plan's DOMAIN layer matches the model EXACTLY. The section(s)
425
+ this feature answers for: {{model_scope_plan}}. Entities and value objects carry
426
+ the section's names (SAME names), the boundary is its Boundary block, states are
427
+ the lifecycle it describes;
350
428
  (b) EVERY invariant (BR-nnn) the spec cites has a named ENFORCEMENT POINT in the
351
429
  backend plan. An invariant nothing enforces is a HIGH gap; a plan that RESTATES
352
430
  what an invariant says instead of citing it by id is a gap too — the model is the
@@ -378,6 +456,45 @@ workflow:
378
456
  - id: conform_break
379
457
  action: break
380
458
  condition: "{{analyze_ok}} == true"
459
+
460
+ # REPORT MODE — a change is open (or the mode could not be established), so the
461
+ # master plans are off limits and there is nothing to ask: repair is not on offer.
462
+ # Write the report and leave the loop. The report itself has to name the two
463
+ # readings, because the same gaps mean opposite things either side of the merge.
464
+ - id: write_report_readonly
465
+ condition: "{{may_repair}} == false"
466
+ action: agent_author
467
+ include_standards: false
468
+ output_target: { type: file, path: "{{feature_dir}}/plan-analyze-report.md" }
469
+ task: |
470
+ Write a Plan Analyze Report to the output from these spec↔plan conformance gaps
471
+ (spec.md is the ANCHOR). This run was READ-ONLY: change(s) {{open_ids}} are open
472
+ on this feature, so the master lane plans were checked but NOT repaired.
473
+ {{analyze_gaps}}
474
+ Markdown format:
475
+ - "# Plan Analyze Report — <aggregate>" plus today's date.
476
+ - A verdict line: "verdict: GAPS ✗ · <n> gap(s) · read-only (change open: {{open_ids}})".
477
+ - "## Gaps" as a table: | # | check | lane | severity | spec ref | plan location | gap |.
478
+ - "## How to read this" — state BOTH readings and that only the operator knows
479
+ which applies, because it depends on whether finalize-delta has already run:
480
+ (a) AFTER finalize-delta merged a change, these gaps are EXPECTED. The lane
481
+ plans are DERIVED from spec.md + domain-model.md and finalize-delta merges the
482
+ change into those two documents only, so the plans stand at their last authored
483
+ content and the spec has moved ahead of them. The remedy is
484
+ /appweaver plan main (which re-derives them, carrying DA-n / WA-n forward),
485
+ after /appweaver close-delta, not a repair of either plan.
486
+ (b) BEFORE finalize-delta, the masters have not moved, so these gaps are real
487
+ master drift — but this is still not the stage that fixes it, because the
488
+ change in flight is planned in its OWN delta plans. Gate those with
489
+ /appweaver analyze-plan-delta, and re-run /appweaver analyze-plan in master
490
+ mode once every change is closed.
491
+ - "## Not repaired here": this report is the whole output of a read-only run. No
492
+ plan document was written.
493
+ Return the complete report markdown only.
494
+ - id: readonly_break
495
+ action: break
496
+ condition: "{{may_repair}} == false"
497
+
381
498
  - id: ask_repair
382
499
  action: ask
383
500
  prompt: |
@@ -501,13 +618,53 @@ workflow:
501
618
  already-conformant section VERBATIM.
502
619
  Return the COMPLETE updated frontend-plan.md content only.
503
620
 
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
+
504
635
  - id: analyze_gate
505
636
  action: verify_context
506
- require_true: analyze_ok
637
+ require_true: gate_ok
507
638
  on_false: stop
508
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."
509
640
 
510
- # (No change-close here by design — this stage only ever runs with NO change open,
641
+ # 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.
646
+ #
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.
651
+ - id: flag_receipt
652
+ condition: "{{may_repair}} == true"
653
+ action: set
654
+ vars: { stamp_receipt: "{{analyze_ok}}" }
655
+
656
+ - id: plan_gate_stamp
657
+ condition: "{{stamp_receipt}} == true"
658
+ action: execute_script
659
+ script: { ref: actions }
660
+ subcommand: plan-gate-stamp
661
+ args: ["--feature-dir", "{{feature_dir}}", "--spec-version", "{{version}}", "--mode", "{{gate_mode}}"]
662
+ outputs:
663
+ - { name: receipt_file, from: "$.file" }
664
+ - { name: receipt_gated, from: "$.gated" }
665
+ - { name: receipt_count, from: "$.count" }
666
+
667
+ # (No change-close here by design — this stage never archives anything in either mode,
511
668
  # and cr-archive belongs to close-delta, which runs after finalize-delta's merge.)
512
669
 
513
670
  # 6. Post-execution hooks.
@@ -531,5 +688,9 @@ outputs:
531
688
  conformant: "{{analyze_ok}}"
532
689
  gaps: "{{analyze_gaps}}"
533
690
  version: "{{version}}"
534
- note: "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."
535
- next: "/appweaver implement (the plans conform to the spec; version unchanged — this gate closes nothing)"
691
+ gate_mode: "{{gate_mode}}" # master (repair allowed) | report (read-only)
692
+ 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
694
+ gate_receipt_at: "{{receipt_gated}}"
695
+ 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."
@@ -182,7 +182,7 @@ workflow:
182
182
  action: verify_context
183
183
  require_non_empty: feature_dir
184
184
  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."
185
+ 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
186
 
187
187
  # 3. WHICH CHANGE. Same selection contract as plan, analyze-plan-delta and implement:
188
188
  # cr-list enumerates the open changes (evidence = changes/<CR-id>/ on disk), a bare CR
@@ -3,13 +3,17 @@
3
3
  #
4
4
  # The second of the two discrete conformance gates on the forward path, and the
5
5
  # sibling of analyze-plan:
6
- # analyze-plan gates spec ↔ plan — STATIC, before implement
7
- # analyze-slice gates plan ↔ slice — DYNAMIC, after build
6
+ # analyze-plan gates spec + model ↔ plans — STATIC, before implement
7
+ # analyze-slice gates spec + model ↔ code — DYNAMIC, after build
8
+ # Both anchor on the same two masters. The difference is what they score against them and
9
+ # how: analyze-plan reads documents and never runs anything; this gate runs the slice's
10
+ # tests and reads the code they cover.
8
11
  # "Slice" is the unit under analysis: the aggregate's code, its paired tests, and
9
12
  # its OKF knowledge concept. Three passes over it —
10
13
  # 1. BEHAVIOUR run the tests (paired to the spec's acceptance criteria by the
11
14
  # design docs); on red, fix up to 3×.
12
- # 2. CONFORMANCE check the code against the domain gate overlays (validate-only).
15
+ # 2. CONFORMANCE check the code against the domain gate overlays AND the bounded
16
+ # context's domain-model.md (validate-only).
13
17
  # 3. KNOWLEDGE refresh + review the slice's OKF concept (advisory).
14
18
  # Every fix made here is recorded in that concept, which is also where the slice's
15
19
  # file list is READ from (2.3.0 — `aw-xref concept-paths`, replacing the retired
@@ -17,7 +21,22 @@
17
21
  # longer carries; the paired `design-reconcile` write-back is retired with it, its job
18
22
  # already done by the concept-upsert --merge steps in pass 3).
19
23
  #
20
- # TWO POSITIONS, one gate — and in BOTH the master spec.md is the whole truth:
24
+ # TWO MASTERS, NOT ONE (2.4.0). The spec says what the feature must DO; the bounded
25
+ # context's domain-model.md says what the domain IS — the aggregate boundary, the entities,
26
+ # and the text of every invariant the spec cites by BR id. Since the model split, the spec
27
+ # carries the ids and not the rules, so a gate reading only the spec scores code against a
28
+ # document that no longer states what it is checking for. Both are in every pass's context
29
+ # and the conformance pass scores against both.
30
+ #
31
+ # SCOPE IS WHAT KEEPS THE MODEL FAIR HERE. domain-model.md describes the whole context
32
+ # while this gate scores ONE feature. An invariant under the slice's own `## Aggregate:`
33
+ # section is BLOCKING — unenforced or enforced differently than stated is a violation, and
34
+ # so is naming an entity or field differently from the model. An invariant under another
35
+ # aggregate's section is REPORTED and never blocking: this slice does not own it and often
36
+ # should not enforce it. A feature outside every bounded context has no model, and the pass
37
+ # degrades to overlays + spec, exactly as it behaved before.
38
+ #
39
+ # TWO POSITIONS, one gate — and in BOTH the merged masters are the whole truth:
21
40
  # BOOTSTRAP implement → build → analyze-slice → run
22
41
  # CHANGE implement (the delta) → build → analyze-slice-delta → finalize-delta
23
42
  # → analyze-slice → close-delta
@@ -42,8 +61,8 @@
42
61
  # =============================================================================
43
62
 
44
63
  name: aw-spec-analyze-slice
45
- description: The design↔code conformance gate — run the slice's tests against its acceptance criteria, check the code against the domain gate overlays, verify the change did not break any referring aggregate (the OKF referrer blast radius) and write a triage report before stopping if it did, refresh + review its OKF concept, and record every fix in it. Sits after finalize-delta on the change path, so the master spec it scores against already carries the change, and before close-delta, so a red gate leaves the change open and recoverable.
46
- version: 2.3.0
64
+ description: The design↔code conformance gate — run the slice's tests against its acceptance criteria, check the code against the domain gate overlays AND the bounded context's domain-model.md (every invariant the slice's own aggregate section states must be enforced as written; one another aggregate owns is reported, not blocking), verify the change did not break any referring aggregate (the OKF referrer blast radius) and write a triage report before stopping if it did, refresh + review its OKF concept, and record every fix in it. Sits after finalize-delta on the change path, so the two masters it scores against already carry the change, and before close-delta, so a red gate leaves the change open and recoverable.
65
+ version: 2.4.0
47
66
  agent_agnostic: true
48
67
  dialect: aw-spec.orchestrator/v1
49
68
 
@@ -69,6 +88,10 @@ standards:
69
88
  vars:
70
89
  test_ok: "false"
71
90
  conformance_ok: "false"
91
+ # Invariants the model states under ANOTHER aggregate's section that this slice appears
92
+ # to touch — reported, never blocking, since this slice does not own them. Defaulted so
93
+ # the gate message and the outputs read a real value on a run that skipped the pass.
94
+ model_notes: ""
72
95
  # NAVIGATION ONLY. Whether a change is open decides where this gate sends you next
73
96
  # (close-delta vs run) — nothing else in this workflow reads them. On the CHANGE path
74
97
  # finalize-delta has already merged the delta into the master docs, so the delta files
@@ -105,6 +128,7 @@ workflow:
105
128
  script: { ref: actions }
106
129
  subcommand: feature-paths
107
130
  outputs:
131
+ - { name: feature, from: "$.feature" }
108
132
  - { name: feature_dir, from: "$.feature_dir" }
109
133
  - { name: spec_file, from: "$.spec_file" }
110
134
 
@@ -112,7 +136,63 @@ workflow:
112
136
  action: verify_context
113
137
  require_non_empty: feature_dir
114
138
  on_empty: stop
115
- on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the aggregate, e.g. Order), then re-run analyze-slice."
139
+ 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-slice."
140
+
141
+ # The bounded context's DOMAIN MODEL — in every pass's context beside the spec, and the
142
+ # authority the conformance pass scores against. Since the model split the spec no longer
143
+ # states the aggregate boundary, the entities or the text of an invariant: it cites BR-nnn
144
+ # by id and the model holds them. A gate reading only the spec therefore scores code
145
+ # against a document that no longer carries what it is checking for.
146
+ #
147
+ # On the CHANGE path this gate runs after finalize-delta, which has already merged the
148
+ # change's model half into the master — so the master is complete here and the change's
149
+ # `domain-model.<CR>.delta.md` is deliberately NOT loaded, exactly as its delta-spec is not.
150
+ #
151
+ # optional: on every context entry below, because a feature outside every bounded context
152
+ # legitimately has no model and a required file that is missing announces itself.
153
+ # on_error: continue for the same reason — a missing model degrades this gate to its
154
+ # pre-model behaviour (spec + overlays), it never blocks.
155
+ - id: context_paths
156
+ action: execute_script
157
+ on_error: continue
158
+ script: { ref: actions }
159
+ subcommand: context-paths
160
+ outputs:
161
+ - { name: context, from: "$.context" }
162
+ - { name: domain_model, from: "$.domain_model" }
163
+ - { name: domain_model_exists, from: "$.domain_model_exists" }
164
+
165
+ # WHICH PART OF THAT MODEL THIS GATE SCORES. The model is filed by aggregate, the specs
166
+ # tree by feature. For a feature that is one capability over a shared aggregate
167
+ # (PatientRegistration over Patient) no section carries the feature's name, and scoring
168
+ # "the BR-nnn under this feature's section" found none and PASSED — every invariant the
169
+ # feature relies on silently demoted to a non-blocking note. Resolved from the spec's
170
+ # Acts-on line, then the boundary map, then the feature name.
171
+ - id: feature_aggregates
172
+ action: execute_script
173
+ script: { ref: xref }
174
+ subcommand: feature-aggregates
175
+ args: ["--feature-dir", "{{feature_dir}}"]
176
+ outputs:
177
+ - { name: owned_sections, from: "$.owned_sections" }
178
+ - { name: one_to_one, from: "$.one_to_one" }
179
+
180
+ # WHICH OF THOSE INVARIANTS BLOCK. A feature that alone answers for an aggregate owns
181
+ # its section outright, and every BR-nnn under it has to be enforced in this code. A
182
+ # feature that shares the aggregate with other features — whatever it is named —
183
+ # answers only for what its spec cites: the rest of that section is the other features'
184
+ # code, and blocking on it would fail each of them for work another one does. The
185
+ # engine has no conditional prompt text, so the rule is chosen by an ordered pair of
186
+ # sets and handed to the pass as one sentence.
187
+ - id: model_scope_capability
188
+ action: set
189
+ vars:
190
+ model_scope_rule: "this feature SHARES those sections with other features, so BLOCKING covers only the BR-nnn this feature's spec CITES from them; any other BR-nnn stated there belongs to another feature and is a model_notes entry, not a violation"
191
+ - id: model_scope_owned
192
+ condition: "{{one_to_one}} == true"
193
+ action: set
194
+ vars:
195
+ model_scope_rule: "this feature is the ONLY one that answers for that section, so BLOCKING covers EVERY BR-nnn stated there, whether or not the spec cites it"
116
196
 
117
197
  # Is a change OPEN on this feature? Read for NAVIGATION only — see the vars note. Safe
118
198
  # on the bootstrap path: with no active_version.json, version-read returns
@@ -185,6 +265,7 @@ workflow:
185
265
  test_log: "{{test_log}}"
186
266
  context:
187
267
  - { path: "{{spec_file}}" }
268
+ - { path: "{{domain_model}}", optional: true }
188
269
  context_from: all_paths
189
270
  - id: fix
190
271
  action: agent_generate
@@ -193,7 +274,10 @@ workflow:
193
274
  task: |
194
275
  Tests failed. Make them pass by fixing the code (or a genuinely wrong
195
276
  test) in the files in context, keeping behaviour aligned with the spec's
196
- acceptance criteria (spec in context). Test log:
277
+ acceptance criteria (spec in context). Where the spec cites an invariant
278
+ by id (BR-nnn), the bounded context's domain-model.md in context carries
279
+ its text and the entity names it is stated over — read the rule there
280
+ rather than inferring it from the id. Test log:
197
281
  {{test_log}}
198
282
  outputs:
199
283
  - { name: fixed_files, from: "$.files" }
@@ -238,27 +322,58 @@ workflow:
238
322
  action: build_context
239
323
  context:
240
324
  - { path: "{{spec_file}}" }
325
+ - { path: "{{domain_model}}", optional: true }
241
326
  context_from: all_paths
242
327
  - id: check_overlays
243
328
  action: agent_query
244
329
  include_standards: true # the gate overlays (checklist) are in the standards
245
- schema: { ok: true, violations: [ { rule: "<overlay rule / id>", where: "<file:symbol>", issue: "<what is violated>" } ] }
246
- mock: { ok: true, violations: [] }
330
+ schema: { ok: true, violations: [ { rule: "<overlay rule / BR-nnn>", where: "<file:symbol>", issue: "<what is violated>" } ], model_notes: [ { rule: "<BR-nnn>", owner: "<the aggregate that states it>", issue: "<what is unenforced or mismatched>" } ] }
331
+ mock: { ok: true, violations: [], model_notes: [] }
247
332
  task: |
248
- The domain gate checklist is loaded in the standards (the overlay
249
- forbidden-patterns, refusal conditions, and CI gates). Check the feature's
250
- generated code + tests (in context) against EACH checklist rule and list
251
- EVERY violation, e.g.: a hard delete of a retention-protected aggregate /
252
- version history / audit record; a second write path bypassing the aggregate
253
- or interop adapter; an invented/free-text coded value where a validated code
254
- is required; a missing or weakened audit emission on an audited operation; a
255
- data-classification-restricted field exposed on the public surface or written
256
- to logs/metrics/traces. Also confirm the constraint TESTS exist (audit is
257
- emitted, a restricted field is not exposed, an interop projection round-trips).
258
- ok=true ONLY when no checklist rule is violated.
333
+ TWO authorities, checked in one pass over the feature's generated code + tests
334
+ (in context).
335
+
336
+ 1. THE DOMAIN GATE CHECKLIST, loaded in the standards (the overlay
337
+ forbidden-patterns, refusal conditions, and CI gates). Check the code against
338
+ EACH checklist rule and list EVERY violation, e.g.: a hard delete of a
339
+ retention-protected aggregate / version history / audit record; a second write
340
+ path bypassing the aggregate or interop adapter; an invented/free-text coded
341
+ value where a validated code is required; a missing or weakened audit emission
342
+ on an audited operation; a data-classification-restricted field exposed on the
343
+ public surface or written to logs/metrics/traces. Also confirm the constraint
344
+ TESTS exist (audit is emitted, a restricted field is not exposed, an interop
345
+ projection round-trips).
346
+
347
+ 2. THE BOUNDED CONTEXT'S DOMAIN MODEL, `domain-model.md` in context — the
348
+ authority on what the domain IS. The spec cites invariants by id; this document
349
+ states them. It describes the WHOLE context while this gate scores ONE feature,
350
+ so scope matters:
351
+ • BLOCKING — the invariants of the section(s) this feature answers for,
352
+ {{owned_sections}}: {{model_scope_rule}}. Each blocking rule has to be
353
+ enforced somewhere in this code, and the
354
+ enforcement has to say what the model says. An owned invariant with no
355
+ enforcement, or one enforced with a different threshold, condition or
356
+ direction than the model states, is a `violations` entry with the BR id as
357
+ its `rule`. The model wins: it is what finalize-delta merged and what the
358
+ plans were conformed against.
359
+ • BLOCKING — the ubiquitous language of that same section. An entity, field or
360
+ state the code names differently from the model is a violation; the model's
361
+ name is the right one.
362
+ • NOT BLOCKING — anything the model states under any section not named above.
363
+ This slice does not own those and often should not enforce them. Report each
364
+ one this code appears to touch as a `model_notes` entry, naming the owning
365
+ aggregate, and carry on.
366
+ If no domain-model.md is in context, this feature is outside every bounded
367
+ context: skip part 2 entirely and report nothing for it. Never infer an
368
+ invariant's text from its id, and never treat a rule the model does not state
369
+ as one.
370
+
371
+ ok=true ONLY when no checklist rule is violated AND no BLOCKING invariant above
372
+ is unenforced or contradicted. `model_notes` never affects ok.
259
373
  outputs:
260
374
  - { name: conformance_ok, from: "$.ok" }
261
375
  - { name: conformance_violations, from: "$.violations" }
376
+ - { name: model_notes, from: "$.model_notes" }
262
377
  - id: conformance_break
263
378
  action: break
264
379
  condition: "{{conformance_ok}} == true"
@@ -268,9 +383,15 @@ workflow:
268
383
  permission_mode: acceptEdits
269
384
  task: |
270
385
  Fix these domain-conformance violations in the feature's code (in context),
271
- following the gate checklist rules (in the standards) and keeping the design's
272
- behaviour intact. Do NOT weaken or delete a test to hide a violation — fix the
273
- code. After fixing, re-run the tests (Bash) to confirm they stay green.
386
+ keeping the design's behaviour intact. A violation whose `rule` is an overlay
387
+ rule is fixed against the gate checklist in the standards; one whose `rule` is a
388
+ BR-nnn is fixed against the text domain-model.md states for it under its section
389
+ among {{owned_sections}} — enforce what the model says, where the code
390
+ already owns that decision, and name entities and fields the way the model names
391
+ them. Do NOT weaken or delete a test to hide a violation, and do NOT edit
392
+ domain-model.md or the spec to match the code: this is a gate on the code, and a
393
+ model that is genuinely wrong is corrected upstream through /appweaver specify.
394
+ After fixing, re-run the tests (Bash) to confirm they stay green.
274
395
  Violations: {{conformance_violations}}
275
396
  outputs:
276
397
  - { name: conformance_fixes, from: "$.files" }
@@ -279,7 +400,7 @@ workflow:
279
400
  action: verify_context
280
401
  require_true: conformance_ok
281
402
  on_false: stop
282
- on_false_message: "Code violates the domain gate checklist after 2 fix attempts (violations: {{conformance_violations}}). These are the domain constraints plan/implement must satisfy — fix the code, or if the constraint itself is wrong, correct it upstream (/appweaver specify → plan)."
403
+ on_false_message: "Code fails domain conformance after 2 fix attempts (violations: {{conformance_violations}}). Each entry is either a gate-checklist rule or an invariant (BR-nnn) the aggregate's own section of {{domain_model}} states and this code does not enforce as written. Both are constraints plan/implement must satisfy — fix the code, or if the constraint itself is wrong, correct it upstream (/appweaver specify → plan) rather than editing the model to match what was built. Invariants owned by other aggregates in this context were reported, not enforced here: {{model_notes}}"
283
404
 
284
405
  # ═══════ INTEGRITY — did this feature's change break any aggregate that USES it? ═══════
285
406
  # The slice is green + domain-conformant on its own. But a change to a feature can break
@@ -325,6 +446,7 @@ workflow:
325
446
  integrity_log: "{{integrity_log}}"
326
447
  context:
327
448
  - { path: "{{spec_file}}" }
449
+ - { path: "{{domain_model}}", optional: true }
328
450
  context_from: all_paths
329
451
  - id: write_broken_referrers
330
452
  action: agent_author
@@ -436,6 +558,18 @@ workflow:
436
558
 
437
559
  outputs:
438
560
  feature_dir: "{{feature_dir}}"
561
+ bounded_context: "{{context}}"
562
+ # The second master this gate scored against — the RESOLVED path, which context-paths
563
+ # reports whether or not the file is there. `model_scored` is the one that says whether
564
+ # it actually reached the passes: false means the conformance pass checked the gate
565
+ # overlays alone, either because the feature is outside every bounded context or because
566
+ # its context has not been modelled yet.
567
+ domain_model: "{{domain_model}}"
568
+ model_scored: "{{domain_model_exists}}"
569
+ # Invariants another aggregate in this context owns that this slice appears to touch.
570
+ # Reported by the conformance pass and never gated — surfaced so a real cross-aggregate
571
+ # leak is visible rather than only living in one query's answer.
572
+ model_notes: "{{model_notes}}"
439
573
  test_tool: "{{test_tool}}"
440
574
  referrer_count: "{{referrer_count}}"
441
575
  referrer_blast_radius: "{{referrer_names}}"