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
@@ -19,9 +19,11 @@
19
19
  #
20
20
  # This stage AUTHORS nothing and generates NO code — no model is invoked. It moves
21
21
  # changes/<id>/ into changes/archive/, bumps the minor version, clears active_change, and
22
- # STAMPS that version into the `**Version**:` header of spec.md + backend-plan.md and into
23
- # the change's own Change History row, which finalize-delta left reading `(pending)` — the
24
- # single irreversible act of the CR loop, deliberately isolated in a stage of its own.
22
+ # STAMPS that version into the `**Version**:` header of spec.md, backend-plan.md and
23
+ # api-plan.md and into the change's own Change History rows — spec.md's, and api-plan.md's
24
+ # where finalize-delta merged the change's API into it — which finalize-delta left reading
25
+ # `(pending)`: the single irreversible act of the CR loop, deliberately isolated in a stage
26
+ # of its own.
25
27
  #
26
28
  # TWO SPINES, NOT ONE. The feature's version records what THIS feature shipped. The
27
29
  # bounded context's version records the shared domain-model.md, which every feature in the
@@ -36,24 +38,29 @@
36
38
  # deterministic except the summary, which needs a sentence about the change — and this
37
39
  # stage invokes no model by design. Left unwritten rather than half-filled.
38
40
  #
39
- # THE VERSION SPINE LIVES HERE, AND ONLY HERE. The number has FOUR carriers — spec.md's
40
- # header, backend-plan.md's header, spec.md's Change History row, and active_version.json —
41
- # and all four move in this one step by one deterministic mechanism. Before, backend-plan.md's
41
+ # THE VERSION SPINE LIVES HERE, AND ONLY HERE. The number's carriers — the `**Version**:`
42
+ # headers of spec.md and of the feature's plans (backend-plan.md, api-plan.md and
43
+ # frontend-plan.md, whichever it has), the change's Change History rows in spec.md and
44
+ # api-plan.md, and active_version.json — all move in this one step by one deterministic
45
+ # mechanism. Before, backend-plan.md's
42
46
  # header was bumped by the MODEL at finalize-delta (a stage early, and guessing the target
43
47
  # since the number was never passed into the merge prompt) while spec.md's header was bumped
44
48
  # by nobody at all, leaving the "living truth" document permanently at v1.0.
45
49
  #
46
- # THE CHANGE HISTORY ROW IS THE FOURTH CARRIER, and it joined the spine for the same reason
50
+ # THE CHANGE HISTORY ROW IS A CARRIER TOO, and it joined the spine for the same reason
47
51
  # the headers did. finalize-delta appends the row when it MERGES — a stage before this number
48
52
  # exists — and used to write version-read's `next_version` into it: a PREDICTION of the bump.
49
53
  # A change that merged and never closed (an abandoned run, a red analyze-slice) then left
50
54
  # spec.md asserting a version nothing ever issued. The merge now writes `(pending)` and
51
55
  # version-stamp --cr-id fills it here, so the row and its number land together. That is the
52
- # rule domain-model.md was always held to, and spec.md was the document breaking it.
56
+ # rule domain-model.md was always held to, and spec.md was the document breaking it. The
57
+ # API plan's row, which finalize-delta's API merge writes the same way, is filled by the
58
+ # same call.
53
59
  #
54
60
  # This stage no longer merges the lane plans at all (they are derived from spec.md +
55
61
  # domain-model.md), so a plan header stamped here is stating the version the slice ships as,
56
- # not that the plan was re-derived for it.
62
+ # not that the plan was re-derived for it. api-plan.md is different: it is a master, merged at
63
+ # finalize-delta, so its header states the version its content ships as.
57
64
  #
58
65
  # NOTE: nothing MECHANICALLY proves analyze-slice ran green before this point — the
59
66
  # stage boundary is operator-driven, as everywhere else in this pipeline. And nothing
@@ -63,6 +70,14 @@
63
70
  # back — it is found by `cr-list` (every stage that asks which change shows it), not by
64
71
  # the next command refusing.
65
72
  #
73
+ # THE CHANGE'S API IS MERGED BEFORE IT IS ARCHIVED. finalize-delta merges the change's API
74
+ # into api-plan.md and stops when it cannot, naming the command that finishes the merge. The
75
+ # archive takes the change out of the set contract-combine applies over the feature's draft,
76
+ # so a change archived with its API unmerged takes its endpoints out of the frontend lane's
77
+ # document for good, and api-plan.md never gets them. Step 2c asks `contract-merge --check`
78
+ # before anything moves, and stops when the change carries an API delta that api-plan.md has
79
+ # no Change History row for — every merge writes one. A change with no API delta passes.
80
+ #
66
81
  # WHICH CHANGE IS CLOSED IS ASKED, NEVER INFERRED. MANY changes may be open at once, and
67
82
  # active_version.json's `active_change` is only the most recently opened or activated —
68
83
  # every /appweaver specify overwrites it. Taking that field as "the" change is how a bare
@@ -78,8 +93,8 @@
78
93
  # =============================================================================
79
94
 
80
95
  name: aw-spec-close-delta
81
- description: Close ONE chosen open change — archive changes/<id>/ into changes/archive/, bump the minor version, clear active_change, and stamp that version into the feature's master headers (spec.md, backend-plan.md, and frontend-plan.md where the feature has a UI half), then anchor the close in the OKF freshness log. ASKS which change (a bare CR id as the argument skips the prompt) and confirms even a lone one, rather than taking whichever was last opened. The final CR stage, run after finalize-delta merged the delta and analyze-slice verified the slice. The sole owner of the version spine; invokes no model and generates no code.
82
- version: 1.4.0
96
+ description: Close ONE chosen open change — archive changes/<id>/ into changes/archive/, bump the minor version, clear active_change, and stamp that version into the feature's master headers (spec.md, backend-plan.md, api-plan.md, and frontend-plan.md where the feature has a UI half) and into the change's Change History rows in spec.md and api-plan.md, then anchor the close in the OKF freshness log. ASKS which change (a bare CR id as the argument skips the prompt) and confirms even a lone one, rather than taking whichever was last opened. Stops before the archive when the change carries an API delta that is not merged into api-plan.md. The final CR stage, run after finalize-delta merged the delta and analyze-slice verified the slice. The sole owner of the version spine; invokes no model and generates no code.
97
+ version: 1.6.0
83
98
  agent_agnostic: true
84
99
  dialect: aw-spec.orchestrator/v1
85
100
 
@@ -99,7 +114,7 @@ scripts:
99
114
 
100
115
  # No standards seed and no domain/tech rule selection: this stage authors nothing, so it
101
116
  # has no prompt to govern. It is pure bookkeeping over active_version.json + changes/ +
102
- # the two version headers.
117
+ # the masters' version headers.
103
118
  standards: []
104
119
 
105
120
  vars:
@@ -110,6 +125,8 @@ vars:
110
125
  stamped_docs: "" # which master headers version-stamp actually rewrote
111
126
  stamped_count: "0"
112
127
  history_filled: "false" # whether the merge's `(pending)` Change History row was completed here
128
+ history_rows: "" # every document whose pending row was completed: spec.md, api-plan.md
129
+ api_merged_ok: "true" # false unless contract-merge --check answers that nothing is still to merge
113
130
  # The CONTEXT's spine, moved only when the closing change carried a model delta.
114
131
  # Defaulted so the outputs read a real value on a behaviour-only close, where the step
115
132
  # runs and correctly declines to bump.
@@ -209,6 +226,31 @@ workflow:
209
226
  - { name: delta_model_exists, from: "$.delta_model_exists" }
210
227
  - { name: context_dir, from: "$.context_dir" }
211
228
 
229
+ # 2c. IS THE CHANGE'S API MERGED? Asked before the archive, which takes the change out of
230
+ # the set contract-combine applies: a change archived with its API unmerged takes its
231
+ # endpoints out of the frontend lane's document for good. Read-only. The change's API
232
+ # delta is read as the merge reads it (its delta API plan, or its delta backend plan's
233
+ # API section); api-plan.md's Change History row naming the change is the record of the
234
+ # merge. No on_error, and only an explicit `needed: false` passes: a check that does
235
+ # not answer stops the stage before anything irreversible runs.
236
+ - id: api_merge_check
237
+ action: execute_script
238
+ script: { ref: actions }
239
+ subcommand: contract-merge
240
+ args: ["--feature-dir", "{{feature_dir}}", "--cr", "{{active_change}}", "--check"]
241
+ outputs:
242
+ - { name: api_merge_needed, from: "$.needed" }
243
+ - { name: api_merge_reason, from: "$.reason" }
244
+ - id: api_merge_pending_flag
245
+ condition: "{{api_merge_needed}} != false"
246
+ action: set
247
+ vars: { api_merged_ok: "false" }
248
+ - id: require_api_merged
249
+ action: verify_context
250
+ require_true: api_merged_ok
251
+ on_false: stop
252
+ on_false_message: "close-delta STOPPED before archiving — nothing was archived and the version did not move. Change {{active_change}} carries an API delta that is not merged into the feature's api-plan.md ({{api_merge_reason}}). Archiving it now would take its endpoints out of the frontend lane's document for good, and spend a version number on an API the master does not carry. If finalize-delta stopped at its API merge, finish that merge — `.appweaver/scripts/bash/aw-spec-actions.sh contract-merge --feature-dir {{feature_dir}} --cr {{active_change}}` (PowerShell: `.appweaver/scripts/powershell/aw-spec-actions.ps1 contract-merge --feature-dir {{feature_dir}} --cr {{active_change}}`); it merges by whole endpoint block and a second run changes nothing. If finalize-delta has not run for this change, run /appweaver finalize-delta instead — it merges the spec, the model and the API together — then /appweaver analyze-slice. Then re-run /appweaver close-delta."
253
+
212
254
  # 3. Pre-execution hooks.
213
255
  - id: resolve_pre_hooks
214
256
  action: execute_script
@@ -236,9 +278,11 @@ workflow:
236
278
  - { name: previous_version, from: "$.previous" }
237
279
  - { name: archived_dir, from: "$.archived_dir" }
238
280
 
239
- # 4b. STAMP the number just computed into the two master headers AND into this change's
240
- # Change History row, so spec.md, backend-plan.md, the row and active_version.json all
241
- # carry it from the same instant by the same mechanism. Previously backend-plan.md's
281
+ # 4b. STAMP the number just computed into the master headers AND into this change's
282
+ # Change History rows, so spec.md, backend-plan.md, api-plan.md, the rows and
283
+ # active_version.json all carry it from the same instant by the same mechanism. The
284
+ # API plan's row is the one finalize-delta's API merge wrote; a change with no API
285
+ # wrote none, and only spec.md's is filled. Previously backend-plan.md's
242
286
  # header was bumped by the MODEL back at finalize-delta (guessing the target — the
243
287
  # number was never passed to it) and spec.md's header was bumped by nobody, so it sat
244
288
  # at v1.0 for the life of the feature.
@@ -259,6 +303,7 @@ workflow:
259
303
  - { name: stamped_docs, from: "$.stamped" }
260
304
  - { name: stamped_count, from: "$.count" }
261
305
  - { name: history_filled, from: "$.history_row_filled" }
306
+ - { name: history_rows, from: "$.history_rows_filled" }
262
307
 
263
308
  # 4c. THE CONTEXT'S OWN VERSION, which is a DIFFERENT spine from the feature's. The
264
309
  # feature version says what this feature shipped; domain-model.md is shared by every
@@ -316,6 +361,7 @@ outputs:
316
361
  previous_version: "{{previous_version}}"
317
362
  archived_dir: "{{archived_dir}}"
318
363
  stamped_docs: "{{stamped_docs}}"
364
+ history_rows: "{{history_rows}}"
319
365
  # The bounded context's own spine — a DIFFERENT number from the feature's, moved only
320
366
  # when this change carried a model delta. context_bumped=false on a behaviour-only
321
367
  # change is the correct outcome, not a skipped step.
@@ -323,4 +369,4 @@ outputs:
323
369
  context_version: "{{context_version}}"
324
370
  context_version_bumped: "{{context_bumped}}"
325
371
  model_touched: "{{delta_model_exists}}"
326
- next: "Change {{active_change}} CLOSED — archived to {{archived_dir}}, version {{previous_version}} -> {{version}}, stamped into {{stamped_count}} master header(s) ({{stamped_docs}}) and into this change's Change History row (row completed={{history_filled}} — it read `(pending)` from the moment finalize-delta merged, because the number did not exist until now; false means it was already filled, which is the ordinary outcome of a re-run). spec.md, backend-plan.md, that row and active_version.json now all read {{version}} — and frontend-plan.md too where the feature has a UI half ({{stamped_docs}} names what was actually rewritten); the masters describe the shipped state and the feature is free to take a new change. Bounded context `{{bounded_context}}`: context version moved={{context_bumped}} ({{context_version_before}} -> {{context_version}}) — {{context_bump_reason}}. That number is the CONTEXT's, not this feature's: domain-model.md is shared by every feature in it, so only a change that merged a model delta moves it. THE LANE PLANS STILL STAND WHERE THEY WERE AUTHORED: finalize-delta merged this change into spec.md and domain-model.md only, because backend-plan.md / frontend-plan.md are DERIVED from those two and nothing derives them again on its own. Re-derive with /appweaver plan main (it carries DA-n / WA-n forward and asks before rewriting), then /appweaver analyze-plan re-anchors the spec↔plan gate — it repairs and stops only once NO change is open, and runs read-only until then. Next: /appweaver run → deploy, or start the next change with /appweaver specify."
372
+ next: "Change {{active_change}} CLOSED — archived to {{archived_dir}}, version {{previous_version}} -> {{version}}, stamped into {{stamped_count}} master header(s) ({{stamped_docs}}) and into this change's Change History row (row completed={{history_filled}} — it read `(pending)` from the moment finalize-delta merged, because the number did not exist until now; false means it was already filled, which is the ordinary outcome of a re-run). Rows completed: {{history_rows}} — api-plan.md's is there when finalize-delta merged the change's API into it. spec.md, backend-plan.md, api-plan.md, those rows and active_version.json now all read {{version}} — and frontend-plan.md too where the feature has a UI half ({{stamped_docs}} names what was actually rewritten); the masters describe the shipped state and the feature is free to take a new change. Bounded context `{{bounded_context}}`: context version moved={{context_bumped}} ({{context_version_before}} -> {{context_version}}) — {{context_bump_reason}}. That number is the CONTEXT's, not this feature's: domain-model.md is shared by every feature in it, so only a change that merged a model delta moves it. THE LANE PLANS STILL STAND WHERE THEY WERE AUTHORED: finalize-delta merged this change into spec.md, domain-model.md and api-plan.md only, because backend-plan.md / frontend-plan.md are DERIVED from those and nothing derives them again on its own. Re-derive with /appweaver plan main (it carries DA-n / WA-n forward and asks before rewriting), then /appweaver analyze-plan re-anchors the spec↔plan gate — it repairs and stops only once NO change is open, and runs read-only until then. Next: /appweaver run → deploy, or start the next change with /appweaver specify."
@@ -2,7 +2,8 @@
2
2
  # aw-spec-finalize-delta (orchestrator) — MERGE the delta into the master docs
3
3
  #
4
4
  # The fourth of the five CR stages. Fold the open change's delta into the two documents that
5
- # DEFINE the slice — the feature's spec.md and the bounded context's domain-model.md — then
5
+ # DEFINE the slice — the feature's spec.md and the bounded context's domain-model.md — and its
6
+ # API delta into the feature's api-plan.md, the API contract the frontend lane binds to; then
6
7
  # refresh the OKF knowledge layer. After this the masters describe the shipped-plus-change
7
8
  # state, but THE CHANGE REMAINS OPEN.
8
9
  #
@@ -14,6 +15,18 @@
14
15
  # requirement reached is a rule nothing asks for. ONE agent step writes both, so there is no
15
16
  # window in which one is in and the other is not.
16
17
  #
18
+ # THE API PLAN IS THE THIRD MASTER, MERGED BY SCRIPT AFTER THE TWO:
19
+ # • specs/<Feature>/api-plan.md ← changes/<id>/delta-api-plan.md
20
+ # The endpoints the service publishes are agreed between the lanes, not derived from the
21
+ # spec, so api-plan.md is a master like spec.md and is merged, not re-derived. `contract-merge`
22
+ # merges whole endpoint blocks, keyed by operation id, with no model involved, and redraws the
23
+ # feature's api-draft.json from the result. It runs once the spec and model merge has landed
24
+ # and is not part of that transaction: the script is deterministic, a second run changes
25
+ # nothing, and a run that did not answer stops this stage naming the command that finishes it.
26
+ # A change planned before delta API plans existed keeps its API in its delta backend plan, and
27
+ # the script reads it there; a feature planned before API plans existed keeps its endpoints in
28
+ # its backend plan, and its first merge moves them into a new api-plan.md (api-split) first.
29
+ #
17
30
  # THE MODEL MERGES ONE `## Aggregate:` SECTION AT A TIME — the section is the merge unit, and
18
31
  # only the sections the delta's "Aggregates touched" header names are opened. Two open deltas
19
32
  # on DIFFERENT aggregates therefore never collide; two on the SAME one are a real semantic
@@ -73,7 +86,9 @@
73
86
  # proves it ran — the stage boundary is operator-driven, as everywhere else in this
74
87
  # pipeline — but it is the difference between a master that describes the system and one
75
88
  # that describes an intention.
76
- # finalize-delta edits ONLY spec.md and domain-model.md; it generates no code.
89
+ # finalize-delta edits ONLY spec.md and domain-model.md, and by script api-plan.md and the
90
+ # api-draft.json drawn from it — plus, on a feature whose endpoints were still in its backend
91
+ # plan, that plan, to move them out. It generates no code.
77
92
  #
78
93
  # WHICH CHANGE IS MERGED IS ASKED, NEVER INFERRED. MANY changes may be open at once, and
79
94
  # active_version.json's `active_change` is only the most recently opened or activated — every
@@ -138,8 +153,8 @@
138
153
  # =============================================================================
139
154
 
140
155
  name: aw-spec-finalize-delta
141
- description: Merge ONE chosen open change's delta-spec into the feature's spec.md and its model delta into the bounded context's domain-model.md — one aggregate section at a time, only the aggregates the delta names — in a single transaction, and append the spec's Change History row. ASKS which change (a bare CR id as the argument skips the prompt) and activates it, rather than taking whichever was last opened. Fenced by three hard refusals read off disk before anything is written — a moved base_version (rebase first), another change merged but not yet closed, and an open LOWER-wave predecessor — then by a SCOPED regression fence over the whole blast radius (fence_green) — the transitive REFERRER closure and the features that CO-OWN the aggregates this delta touches, which hold no edge to it but whose code is in the same files — writing a triage report and asking Abort/Override on red. Snapshots both masters to changes/<id>/pre-merge/ immediately before the rewrite. The lane plans are NOT merged — backend-plan.md and frontend-plan.md are derived from these two documents and are re-derived by /appweaver plan main. Leaves the change OPEN — analyze-slice runs next against the merged masters, and close-delta archives the change and bumps the version.
142
- version: 2.5.0
156
+ description: Merge ONE chosen open change's delta-spec into the feature's spec.md and its model delta into the bounded context's domain-model.md — one aggregate section at a time, only the aggregates the delta names — in a single transaction, and append the spec's Change History row; then merge its delta API plan into the feature's api-plan.md by script — whole endpoint blocks keyed by operation id, the answered frontend requests and a Change History row — and redraw api-draft.json from it. ASKS which change (a bare CR id as the argument skips the prompt) and activates it, rather than taking whichever was last opened. Fenced by three hard refusals read off disk before anything is written — a moved base_version (rebase first), another change merged but not yet closed, and an open LOWER-wave predecessor — then by a SCOPED regression fence over the whole blast radius (fence_green) — the transitive REFERRER closure and the features that CO-OWN the aggregates this delta touches, which hold no edge to it but whose code is in the same files — writing a triage report and asking Abort/Override on red. Snapshots the masters, the backend plan and the API draft to changes/<id>/pre-merge/ immediately before the rewrite. The lane plans are NOT merged — backend-plan.md and frontend-plan.md are derived from these two documents and are re-derived by /appweaver plan main. Leaves the change OPEN — analyze-slice runs next against the merged masters, and close-delta archives the change and bumps the version.
157
+ version: 2.6.0
143
158
  agent_agnostic: true
144
159
  dialect: aw-spec.orchestrator/v1
145
160
 
@@ -230,6 +245,27 @@ vars:
230
245
  overview_reason: ""
231
246
  backend_plan: "" # READ, never written here — the OKF edges are parsed out of it
232
247
  backend_plan_exists: "false"
248
+ delta_api_plan: "" # changes/<id>/delta-api-plan.md — the change's API half
249
+ # THE API MERGE (contract-merge). api_merge_ok defaults DOWN and is raised only by a run
250
+ # that answered, so a script that failed leaves the stop after it to say so.
251
+ api_merge_ok: "false"
252
+ api_merged: "false" # false with a reason: the change has no API to merge
253
+ api_merge_written: "false"
254
+ api_split: "false" # the feature's endpoints were moved out of its backend plan first
255
+ api_plan: ""
256
+ api_delta: "" # the document the change's API was read from
257
+ api_added: ""
258
+ api_changed: ""
259
+ api_removed: ""
260
+ api_requests: "" # the frontend requests whose answers joined the API plan
261
+ api_history_row: "false"
262
+ api_draft: ""
263
+ api_draft_operations: "0"
264
+ api_dropped_text: "none" # operations the redraw dropped that the change does not remove
265
+ api_merge_warning: ""
266
+ api_merge_reason: ""
267
+ api_merge_clean: "true"
268
+ api_draft_kept: "true"
233
269
 
234
270
  workflow:
235
271
 
@@ -336,6 +372,7 @@ workflow:
336
372
  - { name: delta_model, from: "$.delta_model" }
337
373
  - { name: delta_model_exists, from: "$.delta_model_exists" }
338
374
  - { name: delta_backend_plan, from: "$.delta_backend_plan" }
375
+ - { name: delta_api_plan, from: "$.delta_api_plan" }
339
376
  - { name: domain_model, from: "$.domain_model" }
340
377
  - { name: domain_model_exists, from: "$.domain_model_exists" }
341
378
 
@@ -595,9 +632,11 @@ workflow:
595
632
  context:
596
633
  - { path: "{{spec_file}}" }
597
634
  - { path: "{{delta_spec}}" }
598
- # The delta plan is how the change was BUILT — the most direct account of what could
599
- # have broken a dependent. Optional: a frontend-only change never has one.
635
+ # The delta plans are how the change was BUILT — the most direct account of what could
636
+ # have broken a dependent, and the delta API plan of a broken contract between slices.
637
+ # Optional: a frontend-only change never has them.
600
638
  - { path: "{{delta_backend_plan}}", optional: true }
639
+ - { path: "{{delta_api_plan}}", optional: true }
601
640
  - id: write_broken_downstream
602
641
  action: agent_author
603
642
  condition: "{{blast_any}}"
@@ -755,6 +794,8 @@ workflow:
755
794
  these two documents and are re-derived by /appweaver plan main after this merge; neither is
756
795
  in context and neither is to be opened, read or edited. Do not restate a plan's
757
796
  wording in a master: the plan is how the change was built, not what was agreed.
797
+ api-plan.md is not yours either: the change's API is merged into it by script once
798
+ this merge has landed, so do not open or edit it.
758
799
  BOTH MASTERS MOVE TOGETHER. Where there is a model delta, edit spec.md and
759
800
  domain-model.md in the same pass — a merged requirement citing a BR- the model never
760
801
  gained, or a merged invariant no requirement reached, is exactly the split state this
@@ -819,8 +860,8 @@ workflow:
819
860
  row's `(pending)` cell, at the moment it bumps active_version.json, so every carrier
820
861
  of the number changes in one step by one mechanism. Do not invent a version anywhere.
821
862
  Preserve every unrelated section of BOTH masters byte-for-byte. Do NOT touch the
822
- delta documents (close-delta archives them later), do NOT open or edit any lane plan,
823
- and do NOT touch any source code. Report the files you changed.
863
+ delta documents (close-delta archives them later), do NOT open or edit any lane plan
864
+ or the API plan, and do NOT touch any source code. Report the files you changed.
824
865
  outputs:
825
866
  - { name: merged_docs, from: "$.files" }
826
867
 
@@ -957,6 +998,81 @@ workflow:
957
998
  outputs:
958
999
  - { name: xref_log, from: "$.log" }
959
1000
 
1001
+ # 4d. THE API MERGE — the change's delta API plan into the feature's api-plan.md, by script.
1002
+ # Whole endpoint blocks, keyed by operation id (by verb and route for a block without
1003
+ # one, which keeps the id its row or the block it replaces gives that route): a changed
1004
+ # block replaces the API plan's, an added one goes after the last, a
1005
+ # removed one is deleted, and a Contract deltas row with no block is written from the
1006
+ # row and reported — never over a block the change wrote on the same route. The requests the change answers replace or join the requests table,
1007
+ # and a Change History row is written with `(pending)` in its Version cell for
1008
+ # close-delta to stamp. api-draft.json is redrawn from the result in the same call.
1009
+ # A change with no API delta merges nothing, which is the whole outcome for a
1010
+ # behaviour-only or frontend-only change. One planned before delta API plans existed
1011
+ # is read from its delta backend plan's API section; a feature with no api-plan.md yet
1012
+ # has its endpoints moved out of its backend plan into one first (api-split).
1013
+ # After the spec and model merge, so the API never moves when the merge above wrote
1014
+ # nothing; after the OKF refresh, so a run that fails here still leaves the knowledge
1015
+ # layer current. on_error: continue, so a failure reaches the stop below, which names
1016
+ # the command that finishes the merge.
1017
+ - id: merge_api
1018
+ action: execute_script
1019
+ on_error: continue
1020
+ script: { ref: actions }
1021
+ subcommand: contract-merge
1022
+ args: ["--feature-dir", "{{feature_dir}}", "--cr", "{{active_change}}"]
1023
+ outputs:
1024
+ - { name: api_merge_ok, from: "$.ok" }
1025
+ - { name: api_merged, from: "$.merged" }
1026
+ - { name: api_merge_written, from: "$.written" }
1027
+ - { name: api_split, from: "$.split" }
1028
+ - { name: api_plan, from: "$.path" }
1029
+ - { name: api_delta, from: "$.delta" }
1030
+ - { name: api_added, from: "$.added" }
1031
+ - { name: api_changed, from: "$.changed" }
1032
+ - { name: api_removed, from: "$.removed" }
1033
+ - { name: api_requests, from: "$.requests" }
1034
+ - { name: api_history_row, from: "$.history_row" }
1035
+ - { name: api_draft, from: "$.draft" }
1036
+ - { name: api_draft_operations, from: "$.draft_operations" }
1037
+ - { name: api_dropped_text, from: "$.dropped_text" }
1038
+ - { name: api_merge_warning, from: "$.warning" }
1039
+ - { name: api_merge_reason, from: "$.reason" }
1040
+
1041
+ # spec.md and domain-model.md already carry the change when this fires. Re-running
1042
+ # finalize-delta would merge them a second time and retake the pre-merge snapshot from the
1043
+ # merged masters, so the message names the script instead, which a second run leaves as it
1044
+ # is. Stopping here keeps the post hooks from taking a half-merged change for a merged one,
1045
+ # and keeps the change from reaching close-delta, whose archive would take its endpoints out
1046
+ # of the frontend lane's document for good.
1047
+ - id: require_api_merged
1048
+ action: verify_context
1049
+ require_true: api_merge_ok
1050
+ on_false: stop
1051
+ on_false_message: "finalize-delta STOPPED after the merge — change {{active_change}} IS merged into spec.md and the context's domain-model.md, but merging its API into the feature's api-plan.md did not complete: the contract-merge script did not answer. Run it to see why and, once the cause is fixed, to finish the merge — `.appweaver/scripts/bash/aw-spec-actions.sh contract-merge --feature-dir {{feature_dir}} --cr {{active_change}}` (PowerShell: `.appweaver/scripts/powershell/aw-spec-actions.ps1 contract-merge --feature-dir {{feature_dir}} --cr {{active_change}}`). It merges by whole endpoint block and a second run changes nothing. Do NOT re-run /appweaver finalize-delta: it would merge spec.md and domain-model.md a second time. Then continue with /appweaver analyze-slice; do not run /appweaver close-delta until the API is merged, because archiving the change takes its endpoints out of the frontend lane's document."
1052
+
1053
+ # What the merge could not do exactly as the change says: an endpoint written from its
1054
+ # Contract deltas row because the change wrote no block for it, a row whose route the
1055
+ # change's block took under another id, or a changed endpoint the API plan did not have.
1056
+ # Reported, not blocking — the change is merged either way.
1057
+ - id: api_merge_clean_flag
1058
+ condition: "{{api_merge_warning}} != "
1059
+ action: set
1060
+ vars: { api_merge_clean: "false" }
1061
+ - id: warn_api_merge
1062
+ action: verify_context
1063
+ require_true: api_merge_clean
1064
+ on_false: continue
1065
+ on_false_message: "{{api_merge_warning}}"
1066
+ - id: api_draft_dropped_flag
1067
+ condition: "{{api_dropped_text}} != none"
1068
+ action: set
1069
+ vars: { api_draft_kept: "false" }
1070
+ - id: warn_api_draft_dropped
1071
+ action: verify_context
1072
+ require_true: api_draft_kept
1073
+ on_false: continue
1074
+ on_false_message: "API DRAFT — after the merge, {{api_draft}} no longer has operation(s) the previous draft had and change {{active_change}} does not remove: {{api_dropped_text}}. The frontend lane may still bind to them. If they were dropped on purpose, remove them through a change; if not, restore their blocks in {{api_plan}}."
1075
+
960
1076
  # 5. NO ARCHIVE, NO VERSION BUMP HERE. Both moved to close-delta, which runs after
961
1077
  # analyze-slice — see the header. The change stays OPEN past this stage on purpose:
962
1078
  # it is what keeps a red analyze-slice recoverable ("fix and re-run") instead of
@@ -996,4 +1112,11 @@ outputs:
996
1112
  model_merged: "{{delta_model_exists}}" # true ⇒ domain-model.md was the second target of this merge.
997
1113
  # false = a behaviour-only change: the spec merge was the
998
1114
  # whole merge, not a half of one.
999
- next: "Delta MERGED into spec.md, and into the context's domain-model.md one aggregate section at a time where the change carried a model delta (model_merged={{delta_model_exists}} — false means a behaviour-only change, so the spec merge was the whole merge). The lane plans were NOT touched — they are DERIVED from these two masters, so backend-plan.md / frontend-plan.md now stand BEHIND the spec and model at their last authored content. That drift starts here and lasts until /appweaver plan main re-derives them, carrying the recorded decisions (DA-n) and workarounds (WA-n) forward and rewriting what the spec and model no longer support. analyze-plan is not what closes it and cannot repair it while this change is open: with a change open it runs READ-ONLY, reporting the drift into plan-analyze-report.md without rewriting either plan. The full gate — check, repair, stop — comes back once the last change is closed, so the order is close-delta, then plan main, then analyze-plan. OKF edges reconciled ({{xref_edge_count}} dep(s)), {{downstream_count}} downstream slice(s) fenced (gate: {{gate_decision}}). The concept Overview was distilled for aggregate(s) `{{overview_scope}}` — taken from what this change's model delta DECLARES it touched, not assumed from the feature name (empty on a behaviour-only change, which falls back to the aggregate the feature answers for). section_found={{overview_section_ok}}, written={{overview_written}}{{overview_reason}} — written=false is normal for a behaviour-only change (nothing to distil, the existing Overview stands), but section_found=false means domain-model.md has no `## Aggregate:` section under that name and the concept's Overview was left as it was: check that the spec's Acts on aggregate(s) line names an aggregate the model defines. Change {{active_change}} is STILL OPEN and the version is still {{version}}. Next: /appweaver analyze-slice — it now reads masters that carry this change, and a red result is still recoverable because nothing has been archived. Then /appweaver close-delta archives the change and bumps the version."
1115
+ # The API plan, the third master: whether the change's API was merged into it, and the
1116
+ # draft redrawn from it. api_split=true ⇒ the feature's endpoints were moved out of its
1117
+ # backend plan into a new api-plan.md first.
1118
+ api_plan: "{{api_plan}}"
1119
+ api_merged: "{{api_merged}}"
1120
+ api_split: "{{api_split}}"
1121
+ api_draft: "{{api_draft}}"
1122
+ next: "Delta MERGED into spec.md, and into the context's domain-model.md one aggregate section at a time where the change carried a model delta (model_merged={{delta_model_exists}} — false means a behaviour-only change, so the spec merge was the whole merge). THE API: merged={{api_merged}} into {{api_plan}} from {{api_delta}} — added {{api_added}}, changed {{api_changed}}, removed {{api_removed}}, requests answered {{api_requests}}, Change History row written={{api_history_row}} (its version reads `(pending)` until close-delta stamps it); {{api_draft}} redrawn with {{api_draft_operations}} operation(s). merged=false means the change has no API to merge: {{api_merge_reason}}. api_split={{api_split}} — true means the feature's endpoints and requests table were first moved out of its backend plan into api-plan.md, the only edit this stage makes to a lane plan. The lane plans were NOT merged — they are DERIVED from these masters, so backend-plan.md / frontend-plan.md now stand BEHIND the spec and model at their last authored content. That drift starts here and lasts until /appweaver plan main re-derives them, carrying the recorded decisions (DA-n) and workarounds (WA-n) forward and rewriting what the spec and model no longer support. analyze-plan is not what closes it and cannot repair it while this change is open: with a change open it runs READ-ONLY, reporting the drift into plan-analyze-report.md without rewriting either plan. The full gate — check, repair, stop — comes back once the last change is closed, so the order is close-delta, then plan main, then analyze-plan. OKF edges reconciled ({{xref_edge_count}} dep(s)), {{downstream_count}} downstream slice(s) fenced (gate: {{gate_decision}}). The concept Overview was distilled for aggregate(s) `{{overview_scope}}` — taken from what this change's model delta DECLARES it touched, not assumed from the feature name (empty on a behaviour-only change, which falls back to the aggregate the feature answers for). section_found={{overview_section_ok}}, written={{overview_written}}{{overview_reason}} — written=false is normal for a behaviour-only change (nothing to distil, the existing Overview stands), but section_found=false means domain-model.md has no `## Aggregate:` section under that name and the concept's Overview was left as it was: check that the spec's Acts on aggregate(s) line names an aggregate the model defines. Change {{active_change}} is STILL OPEN and the version is still {{version}}. Next: /appweaver analyze-slice — it now reads masters that carry this change, and a red result is still recoverable because nothing has been archived. Then /appweaver close-delta archives the change and bumps the version."