aw-spec-cli 0.1.18 → 0.1.19

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 (53) hide show
  1. package/assets/.appweaver/DESIGN-FORMAT.md +45 -21
  2. package/assets/.appweaver/orchestrator/modules/delta-plan.orch.yaml +62 -14
  3. package/assets/.appweaver/orchestrator/modules/delta-spec.orch.yaml +6 -4
  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/design-template.skeleton.md +18 -11
  8. package/assets/.appweaver/orchestrator/rev-templates/ui-design-template.skeleton.md +5 -3
  9. package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +298 -109
  10. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +161 -34
  11. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +125 -24
  12. package/assets/.appweaver/orchestrator/workflows/aw-spec-build.orch.yaml +18 -7
  13. package/assets/.appweaver/orchestrator/workflows/aw-spec-close-delta.orch.yaml +182 -39
  14. package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +300 -35
  15. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +190 -31
  16. package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +93 -8
  17. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +34 -7
  18. package/assets/.appweaver/orchestrator/workflows/aw-spec-recall-delta.orch.yaml +319 -0
  19. package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +125 -24
  20. package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +846 -35
  21. package/assets/.appweaver/scripts/bash/common.sh +28 -1
  22. package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +843 -46
  23. package/assets/.appweaver/scripts/python/aw-inventory.py +3 -2
  24. package/assets/.appweaver/templates/design-template.md +41 -30
  25. package/assets/.appweaver/templates/spec-template.md +7 -4
  26. package/assets/.appweaver/templates/ui-design-template.md +7 -3
  27. package/assets/guardrails/domain/healthcare/auditing.domain.md +3 -3
  28. package/assets/guardrails/domain/pharma/auditing.domain.md +3 -3
  29. package/assets/guardrails/technology/dotnet-webapi/api-versioning.md +1 -1
  30. package/assets/guardrails/technology/dotnet-webapi/auditing.md +1 -1
  31. package/assets/guardrails/technology/dotnet-webapi/authorization.md +1 -1
  32. package/assets/guardrails/technology/dotnet-webapi/clean-architecture.md +1 -1
  33. package/assets/guardrails/technology/dotnet-webapi/cqrs.md +1 -1
  34. package/assets/guardrails/technology/dotnet-webapi/data-classification.md +1 -1
  35. package/assets/guardrails/technology/dotnet-webapi/eventing.md +1 -1
  36. package/assets/guardrails/technology/dotnet-webapi/folder-structure.md +1 -1
  37. package/assets/guardrails/technology/dotnet-webapi/modular-monolith.md +1 -1
  38. package/assets/guardrails/technology/dotnet-webapi/persistence.md +1 -1
  39. package/assets/guardrails/technology/dotnet-webapi/vertical-slices.md +1 -1
  40. package/assets/guardrails/technology/dotnet-webapi/zero-trust.md +1 -1
  41. package/assets/guardrails/technology/nextjs/accessibility-implementation.md +1 -1
  42. package/assets/guardrails/technology/nextjs/api-integration.dotnet-webapi.md +1 -1
  43. package/assets/guardrails/technology/nextjs/api-integration.java-spring.md +1 -1
  44. package/assets/guardrails/technology/nextjs/auth-session.md +1 -1
  45. package/assets/guardrails/technology/nextjs/component-architecture.md +1 -1
  46. package/assets/guardrails/technology/nextjs/design-system.md +1 -1
  47. package/assets/guardrails/technology/nextjs/error-handling.md +1 -1
  48. package/assets/guardrails/technology/nextjs/forms-and-validation.md +1 -1
  49. package/assets/guardrails/technology/nextjs/i18n.md +1 -1
  50. package/assets/guardrails/technology/nextjs/performance-budgets.md +1 -1
  51. package/assets/guardrails/technology/nextjs/routing-navigation.md +1 -1
  52. package/assets/guardrails/technology/nextjs/state-management.md +1 -1
  53. package/package.json +1 -1
@@ -18,38 +18,77 @@
18
18
  # once the slice is green, domain-conformant and has not broken a referrer.
19
19
  #
20
20
  # This stage AUTHORS nothing and generates NO code — no model is invoked. It moves
21
- # changes/<id>/ into changes/archive/, bumps the minor version, clears active_change,
22
- # and STAMPS that version into the `**Version**:` header of spec.md + backend-plan.md — the
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
23
24
  # single irreversible act of the CR loop, deliberately isolated in a stage of its own.
24
25
  #
25
- # THE VERSION SPINE LIVES HERE, AND ONLY HERE. The number has three carriers — spec.md's
26
- # header, backend-plan.md's header, active_version.json — and all three move in this one step
27
- # by one deterministic mechanism. Before, backend-plan.md's header was bumped by the MODEL at
28
- # finalize-delta (a stage early, and guessing the target since the number was never
29
- # passed into the merge prompt) while spec.md's header was bumped by nobody at all,
30
- # leaving the "living truth" document permanently at v1.0. finalize-delta now writes
31
- # spec.md's Change History row using version-read's `next_version` — the same bump()
32
- # authority this stage applies — and touches neither header. It no longer merges the lane
33
- # plans at all (they are derived from spec.md + domain-model.md), so a plan header stamped
34
- # here is stating the version the slice ships as, not that the plan was re-derived for it.
26
+ # TWO SPINES, NOT ONE. The feature's version records what THIS feature shipped. The
27
+ # bounded context's version records the shared domain-model.md, which every feature in the
28
+ # context reads — so a change to it is a change to all of them and no single feature's
29
+ # number can carry it. This stage owns both, and moves the context's ONLY when the closing
30
+ # change actually merged a model delta: a behaviour-only change leaves domain-model.md
31
+ # alone at finalize and leaves its version alone here. The model's `**Context version**:`
32
+ # header has always declared itself script-stamped, and specify forbids the authoring model
33
+ # from writing it; this is the step that makes both of those statements true.
34
+ #
35
+ # ⌛ The model's own Change History table is still not written by anything. Its columns are
36
+ # deterministic except the summary, which needs a sentence about the change — and this
37
+ # stage invokes no model by design. Left unwritten rather than half-filled.
38
+ #
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
42
+ # header was bumped by the MODEL at finalize-delta (a stage early, and guessing the target
43
+ # since the number was never passed into the merge prompt) while spec.md's header was bumped
44
+ # by nobody at all, leaving the "living truth" document permanently at v1.0.
45
+ #
46
+ # THE CHANGE HISTORY ROW IS THE FOURTH CARRIER, and it joined the spine for the same reason
47
+ # the headers did. finalize-delta appends the row when it MERGES — a stage before this number
48
+ # exists — and used to write version-read's `next_version` into it: a PREDICTION of the bump.
49
+ # A change that merged and never closed (an abandoned run, a red analyze-slice) then left
50
+ # spec.md asserting a version nothing ever issued. The merge now writes `(pending)` and
51
+ # 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.
53
+ #
54
+ # This stage no longer merges the lane plans at all (they are derived from spec.md +
55
+ # 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.
35
57
  #
36
58
  # NOTE: nothing MECHANICALLY proves analyze-slice ran green before this point — the
37
- # stage boundary is operator-driven, as everywhere else in this pipeline. What IS
38
- # enforced is the reverse: while a change is open, `specify` refuses to open another on
39
- # the same feature (_active_version's open-guard), so a forgotten close-delta blocks the
40
- # next change rather than passing silently.
59
+ # stage boundary is operator-driven, as everywhere else in this pipeline. And nothing
60
+ # backstops a forgotten close either: MANY changes may be open at once, so `specify`
61
+ # opens a new one rather than refusing while an earlier one is unclosed. A change that is
62
+ # merged but never closed therefore sits in the open set indefinitely, holding the version
63
+ # back — it is found by `cr-list` (every stage that asks which change shows it), not by
64
+ # the next command refusing.
41
65
  #
42
- # Launch: /appweaver close-delta [strategy]
66
+ # WHICH CHANGE IS CLOSED IS ASKED, NEVER INFERRED. MANY changes may be open at once, and
67
+ # active_version.json's `active_change` is only the most recently opened or activated —
68
+ # every /appweaver specify overwrites it. Taking that field as "the" change is how a bare
69
+ # close archives and version-bumps a change nobody merged while the merged one stays open.
70
+ # This stage opens with the same list → pick → activate sequence as finalize-delta, plan,
71
+ # implement, analyze-plan-delta and recall-delta, and the argument doubles as the answer
72
+ # (`close-delta CR-…` skips the prompt). The pick is confirmed even when only one change is
73
+ # open: this is the stage that spends a version number, and that is not undone.
74
+ #
75
+ # Launch: /appweaver close-delta [CR-id] (a bare id skips the prompt)
43
76
  # Prereq: an OPEN change whose delta was merged by finalize-delta and whose slice
44
77
  # passed /appweaver analyze-slice.
45
78
  # =============================================================================
46
79
 
47
80
  name: aw-spec-close-delta
48
- description: Close the 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. 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.
49
- version: 1.1.0
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
50
83
  agent_agnostic: true
51
84
  dialect: aw-spec.orchestrator/v1
52
85
 
86
+ inputs:
87
+ user_input:
88
+ description: "Optional CR id of the change to close. A bare id selects it and skips the prompt; anything else is ignored and the menu is shown."
89
+ required: false
90
+ bind: USER_INPUT
91
+
53
92
  scripts:
54
93
  actions:
55
94
  sh: .appweaver/scripts/bash/aw-spec-actions.sh
@@ -65,10 +104,21 @@ standards: []
65
104
 
66
105
  vars:
67
106
  active_change: ""
68
- has_change: "false"
107
+ cr_choice: "" # the LABEL the pick returns ("CR-… · wave 2 · title"); scripts take its first token
108
+ has_changes: "false" # from cr-list — the DIRECTORY's answer to "is anything open", not the ledger's
69
109
  version: ""
70
110
  stamped_docs: "" # which master headers version-stamp actually rewrote
71
111
  stamped_count: "0"
112
+ history_filled: "false" # whether the merge's `(pending)` Change History row was completed here
113
+ # The CONTEXT's spine, moved only when the closing change carried a model delta.
114
+ # Defaulted so the outputs read a real value on a behaviour-only close, where the step
115
+ # runs and correctly declines to bump.
116
+ delta_model_exists: "false"
117
+ context_bumped: "false"
118
+ context_version: ""
119
+ context_version_before: ""
120
+ context_bump_reason: ""
121
+ bounded_context: ""
72
122
 
73
123
  workflow:
74
124
 
@@ -88,23 +138,76 @@ workflow:
88
138
  on_empty: stop
89
139
  on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the aggregate, e.g. Order), then re-run close-delta."
90
140
 
91
- # 2. There must be a change to close. On the BOOTSTRAP path (implement → build →
92
- # analyze-slice) there is none and this stage does not apply at all.
141
+ # 2. WHICH CHANGE IS BEING CLOSED. The evidence is changes/<CR-id>/ on disk; a change
142
+ # already closed has moved to changes/archive/. On the BOOTSTRAP path (implement →
143
+ # build → analyze-slice) there is none and this stage does not apply at all.
144
+ # `matched_label` resolves a CR id passed as the argument to the whole option string,
145
+ # which is how a bare `close-delta CR-…` skips the prompt.
146
+ - id: cr_list
147
+ action: execute_script
148
+ script: { ref: actions }
149
+ subcommand: cr-list
150
+ args: ["--feature-dir", "{{feature_dir}}", "--preselect", "{{USER_INPUT}}"]
151
+ outputs:
152
+ - { name: change_options, from: "$.select_options" }
153
+ - { name: matched_label, from: "$.matched_label" }
154
+ - { name: has_changes, from: "$.has_changes" }
155
+ - { name: change_count, from: "$.count" }
156
+
157
+ - id: require_changes
158
+ action: verify_context
159
+ require_true: has_changes
160
+ on_false: stop
161
+ on_false_message: "No open change on this feature — nothing to close. A change is opened by /appweaver specify and merged by /appweaver finalize-delta before it can be closed here. If you have not merged yet, run /appweaver finalize-delta → analyze-slice first. A change already closed is under changes/archive/."
162
+
163
+ # single_option_confirm so a feature with exactly one open change still confirms the id
164
+ # out loud. This is the stage that spends a version number and archives the change's
165
+ # documents, and neither is undone.
166
+ - id: pick_change
167
+ action: user_select
168
+ prompt: "Which open change is being CLOSED? Close the change finalize-delta MERGED and analyze-slice then verified — closing one that was never merged archives its delta documents and spends a version number on work the masters do not carry. To withdraw an unmerged change instead, stop and run /appweaver recall-delta, which archives it and bumps nothing."
169
+ options_from_variable: change_options
170
+ preselect_from: matched_label
171
+ single_option_confirm: true
172
+ outputs:
173
+ - { name: cr_choice, from: selection }
174
+
175
+ # Make the chosen change the ACTIVE one, and VALIDATE it in the same call: cr-activate
176
+ # refuses an id the ledger does not carry, which here means a directory with no ledger row
177
+ # — cr-archive would find nothing to drop from the open set and the version would move for
178
+ # a change the ledger never knew about.
179
+ - id: activate
180
+ action: execute_script
181
+ script: { ref: actions }
182
+ subcommand: cr-activate
183
+ args: ["--cr-id", "{{cr_choice}}", "--feature-dir", "{{feature_dir}}"]
184
+ outputs:
185
+ - { name: active_change, from: "$.active_change" }
186
+
187
+ # 2a. The version BEFORE the bump, for the log line below. cr-archive computes and
188
+ # returns the new one; this is no longer the selector.
93
189
  - id: read_change
94
190
  action: execute_script
95
191
  script: { ref: actions }
96
192
  subcommand: version-read
97
193
  args: ["--feature-dir", "{{feature_dir}}"]
98
194
  outputs:
99
- - { name: active_change, from: "$.active_change" }
100
- - { name: has_change, from: "$.has_change" }
101
195
  - { name: version, from: "$.version" }
102
196
 
103
- - id: require_change
104
- action: verify_context
105
- require_true: has_change
106
- on_false: stop
107
- on_false_message: "No open change on this feature — nothing to close. A change is opened by /appweaver specify (or design) and merged by /appweaver finalize-delta before it can be closed here. If you have not merged yet, run /appweaver finalize-delta → analyze-slice first."
197
+ # 2b. DID THIS CHANGE TOUCH THE DOMAIN MODEL? Asked HERE, before the archive, because
198
+ # cr-archive moves the change's model delta out from beside the master and into
199
+ # changes/archive/<id>/ — after that the evidence is gone from where this question
200
+ # looks for it. The answer decides whether the CONTEXT version moves at step 4c.
201
+ - id: cr_paths
202
+ action: execute_script
203
+ on_error: continue
204
+ script: { ref: actions }
205
+ subcommand: cr-paths
206
+ args: ["--cr-id", "{{active_change}}", "--feature-dir", "{{feature_dir}}"]
207
+ outputs:
208
+ - { name: delta_model, from: "$.delta_model" }
209
+ - { name: delta_model_exists, from: "$.delta_model_exists" }
210
+ - { name: context_dir, from: "$.context_dir" }
108
211
 
109
212
  # 3. Pre-execution hooks.
110
213
  - id: resolve_pre_hooks
@@ -133,20 +236,53 @@ workflow:
133
236
  - { name: previous_version, from: "$.previous" }
134
237
  - { name: archived_dir, from: "$.archived_dir" }
135
238
 
136
- # 4b. STAMP the number just computed into the two master headers, so spec.md,
137
- # backend-plan.md and active_version.json all carry it from the same instant by the
138
- # same mechanism. Previously backend-plan.md's header was bumped by the MODEL back at
139
- # finalize-delta (guessing the target — the number was never passed to it) and
140
- # spec.md's header was bumped by nobody, so it sat at v1.0 for the life of the
141
- # feature. Deterministic and idempotent; only the header value moves.
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
242
+ # header was bumped by the MODEL back at finalize-delta (guessing the target — the
243
+ # number was never passed to it) and spec.md's header was bumped by nobody, so it sat
244
+ # at v1.0 for the life of the feature.
245
+ #
246
+ # --cr-id is what completes the row. finalize-delta appends it at MERGE time, a stage
247
+ # before this number exists, so it writes `(pending)` in the Version cell rather than
248
+ # version-read's prediction of the bump: a change that merged and never closed would
249
+ # otherwise leave spec.md asserting a version nothing ever issued. The row is found by
250
+ # its CR cell, and only a still-pending cell is written — a row already carrying a
251
+ # number belongs to a change that closed, and rewriting it would rewrite history.
252
+ # Deterministic and idempotent; only the values move.
142
253
  - id: stamp_version
143
254
  action: execute_script
144
255
  script: { ref: actions }
145
256
  subcommand: version-stamp
146
- args: ["--feature-dir", "{{feature_dir}}", "--version", "{{version}}"]
257
+ args: ["--feature-dir", "{{feature_dir}}", "--version", "{{version}}", "--cr-id", "{{active_change}}"]
258
+ outputs:
259
+ - { name: stamped_docs, from: "$.stamped" }
260
+ - { name: stamped_count, from: "$.count" }
261
+ - { name: history_filled, from: "$.history_row_filled" }
262
+
263
+ # 4c. THE CONTEXT'S OWN VERSION, which is a DIFFERENT spine from the feature's. The
264
+ # feature version says what this feature shipped; domain-model.md is shared by every
265
+ # feature in the bounded context, so a change to it is a change to all of them and no
266
+ # single feature's number can record it. The model's header has always declared
267
+ # itself script-stamped and specify forbids the authoring model from writing it —
268
+ # this is the step that makes both statements true.
269
+ #
270
+ # Conditional on the change having carried a model delta: a behaviour-only change
271
+ # leaves domain-model.md alone at finalize and must leave its version alone here.
272
+ # `bumped: false` is a normal outcome, not a failure — most changes never touch the
273
+ # model — so on_error: continue and the gate is the report, not a stop.
274
+ - id: bump_context_version
275
+ action: execute_script
276
+ on_error: continue
277
+ script: { ref: actions }
278
+ subcommand: context-version-bump
279
+ args: ["--feature-dir", "{{feature_dir}}", "--cr-id", "{{active_change}}", "--touched-model", "{{delta_model_exists}}"]
147
280
  outputs:
148
- - { name: stamped_docs, from: "$.stamped" }
149
- - { name: stamped_count, from: "$.count" }
281
+ - { name: context_bumped, from: "$.bumped" }
282
+ - { name: context_version, from: "$.version" }
283
+ - { name: context_version_before, from: "$.previous" }
284
+ - { name: context_bump_reason, from: "$.reason" }
285
+ - { name: bounded_context, from: "$.context" }
150
286
 
151
287
  # 5. Anchor the CLOSE in the OKF bundle's freshness journal (log.md, OKF §7).
152
288
  # finalize-delta logs the MERGE; this logs the SHIP — the version bump is the event
@@ -180,4 +316,11 @@ outputs:
180
316
  previous_version: "{{previous_version}}"
181
317
  archived_dir: "{{archived_dir}}"
182
318
  stamped_docs: "{{stamped_docs}}"
183
- next: "Change {{active_change}} CLOSED — archived to {{archived_dir}}, version {{previous_version}} -> {{version}}, stamped into {{stamped_count}} master header(s) ({{stamped_docs}}). spec.md, backend-plan.md 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. Next: /appweaver run → deploy, or start the next change with /appweaver specify."
319
+ # The bounded context's own spine — a DIFFERENT number from the feature's, moved only
320
+ # when this change carried a model delta. context_bumped=false on a behaviour-only
321
+ # change is the correct outcome, not a skipped step.
322
+ bounded_context: "{{bounded_context}}"
323
+ context_version: "{{context_version}}"
324
+ context_version_bumped: "{{context_bumped}}"
325
+ 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."