aw-spec-cli 0.1.19 → 0.1.21

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/assets/.appweaver/DESIGN-FORMAT.md +17 -9
  2. package/assets/.appweaver/ORCH-DIALECT.md +8 -3
  3. package/assets/.appweaver/orchestrator/modules/delta-spec.orch.yaml +26 -3
  4. package/assets/.appweaver/orchestrator/orchestrator.py +65 -3
  5. package/assets/.appweaver/orchestrator/rev-templates/delta-design-template.skeleton.md +1 -1
  6. package/assets/.appweaver/orchestrator/rev-templates/design-template.skeleton.md +4 -2
  7. package/assets/.appweaver/orchestrator/rev-templates/ui-delta-design-template.skeleton.md +1 -1
  8. package/assets/.appweaver/orchestrator/rev-templates/ui-design-template.skeleton.md +1 -1
  9. package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +121 -42
  10. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan-delta.orch.yaml +7 -7
  11. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +145 -52
  12. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +18 -10
  13. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +306 -28
  14. package/assets/.appweaver/orchestrator/workflows/aw-spec-build.orch.yaml +1 -1
  15. package/assets/.appweaver/orchestrator/workflows/aw-spec-characterize.orch.yaml +1 -1
  16. package/assets/.appweaver/orchestrator/workflows/aw-spec-close-delta.orch.yaml +1 -1
  17. package/assets/.appweaver/orchestrator/workflows/aw-spec-constitution.orch.yaml +4 -4
  18. package/assets/.appweaver/orchestrator/workflows/aw-spec-deploy.orch.yaml +1 -1
  19. package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +114 -30
  20. package/assets/.appweaver/orchestrator/workflows/aw-spec-foundation.orch.yaml +140 -36
  21. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +385 -61
  22. package/assets/.appweaver/orchestrator/workflows/aw-spec-inventory.orch.yaml +217 -174
  23. package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +407 -107
  24. package/assets/.appweaver/orchestrator/workflows/aw-spec-parity.orch.yaml +1 -1
  25. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +49 -11
  26. package/assets/.appweaver/orchestrator/workflows/aw-spec-recall-delta.orch.yaml +1 -1
  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-select-stack.orch.yaml +47 -23
  30. package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +19 -11
  31. package/assets/.appweaver/scripts/bash/aw-inventory.sh +15 -0
  32. package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +683 -92
  33. package/assets/.appweaver/scripts/bash/aw-xref.sh +3 -0
  34. package/assets/.appweaver/scripts/powershell/aw-inventory.ps1 +15 -0
  35. package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +625 -64
  36. package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +3 -0
  37. package/assets/.appweaver/scripts/python/aw-crstate.py +194 -0
  38. package/assets/.appweaver/scripts/python/aw-foundation.py +151 -0
  39. package/assets/.appweaver/scripts/python/aw-inventory.py +1829 -63
  40. package/assets/.appweaver/scripts/python/aw-payload-parity.py +3 -0
  41. package/assets/.appweaver/scripts/python/aw-plan-coverage.py +423 -0
  42. package/assets/.appweaver/scripts/python/aw-stack.py +251 -28
  43. package/assets/.appweaver/scripts/python/aw-testlog.py +148 -0
  44. package/assets/.appweaver/scripts/python/aw-xref.py +500 -81
  45. package/assets/.appweaver/stack-profiles/dotnet-webapi.json +1 -0
  46. package/assets/.appweaver/stack-profiles/java-spring.json +1 -0
  47. package/assets/.appweaver/stack-profiles/node-express.json +1 -0
  48. package/assets/.appweaver/stack-profiles/python-fastapi.json +1 -0
  49. package/assets/.appweaver/templates/constitution-domain-template.md +17 -2
  50. package/assets/.appweaver/templates/delta-design-template.md +1 -1
  51. package/assets/.appweaver/templates/delta-domain-model-template.md +1 -1
  52. package/assets/.appweaver/templates/delta-spec-template.md +4 -3
  53. package/assets/.appweaver/templates/design-template.md +16 -11
  54. package/assets/.appweaver/templates/okf-concept-template.md +11 -1
  55. package/assets/.appweaver/templates/proposed-inventory-template.md +127 -0
  56. package/assets/.appweaver/templates/source-map-template.md +5 -3
  57. package/assets/.appweaver/templates/spec-template.md +17 -17
  58. package/assets/.appweaver/templates/ui-delta-design-template.md +1 -1
  59. package/assets/.appweaver/templates/ui-design-template.md +4 -4
  60. package/assets/foundations/dotnet/ModularMonolith/ModularMonolith.pack.txt +4489 -361
  61. package/assets/foundations/dotnet/VerticalSlice/VerticalSlice.pack.txt +2274 -125
  62. package/assets/guardrails/projects/dotnet-default-project/domain-rule-bindings.md +10 -6
  63. package/assets/guardrails/projects/dotnet-default-project/tech-rule-bindings.md +4 -0
  64. package/assets/guardrails/technology/dotnet-webapi/auditing.md +3 -1
  65. package/assets/guardrails/technology/dotnet-webapi/coding-standards.md +5 -3
  66. package/assets/guardrails/technology/dotnet-webapi/cqrs.md +5 -1
  67. package/assets/guardrails/technology/dotnet-webapi/domain-driven-design.md +13 -5
  68. package/assets/guardrails/technology/dotnet-webapi/examples/cqrs.md +2 -0
  69. package/assets/guardrails/technology/dotnet-webapi/examples/domain-driven-design.md +6 -2
  70. package/assets/guardrails/technology/dotnet-webapi/framework-api-map.md +70 -0
  71. package/assets/guardrails/technology/dotnet-webapi/technology-constitution.md +3 -0
  72. package/assets/guardrails/technology/dotnet-webapi/testing.md +10 -0
  73. package/assets/guardrails/technology/nextjs/ui-constitution.md +8 -1
  74. package/package.json +1 -1
  75. package/assets/.appweaver/orchestrator/.state/smoke.state.json +0 -32
@@ -95,7 +95,7 @@ workflow:
95
95
  action: verify_context
96
96
  require_non_empty: feature_dir
97
97
  on_empty: stop
98
- on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the aggregate, e.g. Order), then re-run parity."
98
+ on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the feature, e.g. Order or PatientRegistration), then re-run parity."
99
99
 
100
100
  - id: verify_spec
101
101
  action: verify_context
@@ -164,6 +164,10 @@ vars:
164
164
  plan_mode: "bootstrap" # bootstrap | rederive | delta — reported, not branched on
165
165
  domain_model: ""
166
166
  domain_model_exists: "false"
167
+ owned_sections: "" # the model section(s) this feature answers for — feature-aggregates
168
+ shared_with_csv: "" # the other features answering for the same aggregate(s)
169
+ one_to_one: "true"
170
+ model_scope_plan: "" # which part of those sections the domain layer realises
167
171
  source_files: [] # the feature's real source files, from its OKF concept
168
172
  source_count: "0"
169
173
 
@@ -241,11 +245,42 @@ workflow:
241
245
  - { name: domain_model, from: "$.domain_model" }
242
246
  - { name: domain_model_exists, from: "$.domain_model_exists" }
243
247
 
248
+ # WHICH PART OF THAT MODEL THIS PLAN REALISES. The model is filed by aggregate, and a
249
+ # feature is a capability scope the developer chose: it may act on several aggregates,
250
+ # and several features may act on one. Resolved from the spec's Acts-on line, then the
251
+ # boundary map, then the feature name. on_error: continue — without an answer the
252
+ # prompt falls back to the model as a whole, which is what it read before.
253
+ - id: feature_aggregates
254
+ action: execute_script
255
+ on_error: continue
256
+ script: { ref: xref }
257
+ subcommand: feature-aggregates
258
+ args: ["--feature-dir", "{{feature_dir}}"]
259
+ outputs:
260
+ - { name: owned_sections, from: "$.owned_sections" }
261
+ - { name: shared_with_csv, from: "$.shared_with_csv" }
262
+ - { name: one_to_one, from: "$.one_to_one" }
263
+
264
+ # HOW MUCH OF THOSE SECTIONS THE DOMAIN LAYER CARRIES. A feature that alone answers for
265
+ # an aggregate realises its whole section. A feature that shares it realises only what
266
+ # its own requirements act on: the rest is a sibling feature's, and planning it here
267
+ # would give two plans the same element to realise. The engine has no conditional
268
+ # prompt text, so the rule is an ordered pair of sets handed to the prompt as one line.
269
+ - id: model_scope_shared
270
+ action: set
271
+ vars:
272
+ model_scope_plan: "{{owned_sections}}, which this feature SHARES with other features. Realise only the entities, value objects, states and BR-nnn this feature's requirements act on, under the model's names and boundary; an element of that section this feature does not use belongs to another feature and is left out — never redefined, renamed or narrowed"
273
+ - id: model_scope_sole
274
+ condition: "{{one_to_one}} == true"
275
+ action: set
276
+ vars:
277
+ model_scope_plan: "{{owned_sections}}. This feature is the only one that answers for it, so the domain layer realises that whole section"
278
+
244
279
  - id: require_feature
245
280
  action: verify_context
246
281
  require_non_empty: feature_dir
247
282
  on_empty: stop
248
- on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the aggregate, e.g. Order), then re-run plan."
283
+ on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the feature, e.g. Order or PatientRegistration), then re-run plan."
249
284
 
250
285
  - id: verify_spec
251
286
  action: verify_context
@@ -531,9 +566,10 @@ workflow:
531
566
  - THE SPEC AND THE DOMAIN MODEL ARE THE ONLY DOMAIN INPUTS. You have them plus the
532
567
  technical standards, and NO domain rule files. Realise what they state, and invent
533
568
  no domain rule they omit (an unavoidable new concept → §7 DA-n).
534
- - THE DOMAIN LAYER (§1) MUST FOLLOW THE CONTEXT'S DOMAIN MODEL EXACTLY: entities &
535
- value objects are its `## Aggregate:` section's entities (SAME names), invariants
536
- are its BR-nnn (cite each, never restate), the aggregate boundary is its Boundary
569
+ - THE DOMAIN LAYER (§1) MUST FOLLOW THE CONTEXT'S DOMAIN MODEL EXACTLY. The
570
+ section(s) this feature answers for: {{model_scope_plan}}. Within that scope,
571
+ entities & value objects are the section's entities (SAME names), invariants are
572
+ its BR-nnn (cite each, never restate), the aggregate boundary is its Boundary
537
573
  block, and states are the lifecycle it describes. The model — not the spec — is
538
574
  where those facts live; the spec cites them by id. This document records WHERE
539
575
  each invariant is ENFORCED, never what it says. Elaborate with domain events /
@@ -631,10 +667,11 @@ workflow:
631
667
  textbook layer gets its own section; a layer the feature does not need does NOT
632
668
  appear.
633
669
  - KEEP THE NEUTRAL ANCHORS (the template pins them): §1.2 is the domain/data MODEL
634
- and MUST follow the CONTEXT'S DOMAIN MODEL EXACTLY — entities & value objects are
635
- its aggregate section's entities (SAME names), invariants are its BR-nnn (cite
636
- each and record WHERE each is enforced), the boundary is its Boundary block,
637
- states are its lifecycle; cross-slice references-by-id live here. One §2
670
+ and MUST follow the CONTEXT'S DOMAIN MODEL EXACTLY for the section(s) this feature
671
+ answers for — {{model_scope_plan}}. Entities & value objects are the section's
672
+ entities (SAME names), invariants are its BR-nnn (cite each and record WHERE each
673
+ is enforced), the boundary is its Boundary block, states are its lifecycle;
674
+ cross-slice references-by-id live here. One §2
638
675
  subsection is the EXTERNAL INTEGRATIONS table (its heading contains "Integration")
639
676
  — one port/adapter row per IC-, realising the obligation the spec named.
640
677
  - REALISE THE LIFTED CONSTRAINTS (each cites its spec id): CN- → structural ones
@@ -704,9 +741,10 @@ workflow:
704
741
  A row whose id is gone is deleted along with what it justified. A spec id or model
705
742
  invariant with NO plan element is a GAP — realise it, in the shape the rest of the
706
743
  document already uses.
707
- - THE DOMAIN LAYER (§1) MUST FOLLOW THE CONTEXT'S DOMAIN MODEL EXACTLY: entities &
708
- value objects are its `## Aggregate:` section's entities (SAME names), invariants
709
- are its BR-nnn (cite each, never restate), the aggregate boundary is its Boundary
744
+ - THE DOMAIN LAYER (§1) MUST FOLLOW THE CONTEXT'S DOMAIN MODEL EXACTLY. The
745
+ section(s) this feature answers for: {{model_scope_plan}}. Within that scope,
746
+ entities & value objects are the section's entities (SAME names), invariants are
747
+ its BR-nnn (cite each, never restate), the aggregate boundary is its Boundary
710
748
  block, and states are the lifecycle it describes. Where the model has MOVED since
711
749
  this plan was written, the model is right and the plan is stale.
712
750
  - CARRY THE LEDGERS FORWARD. §7 DA-n architectural decisions and the WA-n
@@ -121,7 +121,7 @@ workflow:
121
121
  action: verify_context
122
122
  require_non_empty: feature_dir
123
123
  on_empty: stop
124
- on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the aggregate, e.g. Order), then re-run recall-delta."
124
+ on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the feature, e.g. Order or PatientRegistration), then re-run recall-delta."
125
125
 
126
126
  # 2. WHICH CHANGES ARE OPEN — the evidence is changes/<CR-id>/ on disk. No synthetic
127
127
  # rows: `main` is not a change and there is nothing to open here, so the menu is the
@@ -64,7 +64,7 @@ workflow:
64
64
  action: verify_context
65
65
  require_non_empty: feature_dir
66
66
  on_empty: stop
67
- on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the aggregate, e.g. Order), then re-run run."
67
+ on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the feature, e.g. Order or PatientRegistration), then re-run run."
68
68
 
69
69
  # Require the SERVICE LANE and its source path. Migrations belong to the backend
70
70
  # lane by definition, and run-migrate searches that lane's root for the project
@@ -1,17 +1,21 @@
1
1
  # =============================================================================
2
2
  # aw-spec-select-feature (orchestrator) — pick or create the active FEATURE
3
3
  #
4
- # feature = AGGREGATE (e.g. Order). This is the FIRST step of the spec-anchored
5
- # pipeline: it fixes the aggregate boundary BEFORE any spec/design exists, so the
6
- # feature's single spec.md + backend-plan.md and every command (CreateOrder/UpdateOrder =
7
- # user stories) hang off one directory. It replaces guessing the aggregate out of prose in
8
- # `specify` — that is why finding #1 (feature=aggregate) is enforced here, not hoped for.
4
+ # A FEATURE IS A CAPABILITY SCOPE THE DEVELOPER CHOOSES (e.g. Order, or
5
+ # PatientRegistration). It may act on several aggregates, and several features may act on
6
+ # one: the aggregates live in the bounded context's domain model and the OKF bundle, above
7
+ # every feature, and the spec names the ones this feature acts on. This is the FIRST step
8
+ # of the spec-anchored pipeline: it fixes the feature's scope BEFORE any spec/plan exists,
9
+ # so its single spec.md + backend-plan.md and every operation (CreateOrder/UpdateOrder =
10
+ # user stories) hang off one directory.
9
11
  #
10
12
  # The pickable list is TWO merged sources (deduped by name): specs/ sub-folders
11
- # (features already scaffolded/adopted) AND the OKF bundle's Aggregate concepts —
12
- # the brownfield feature map `/appweaver onboard` reverse-engineers. So in a
13
- # brownfield repo you pick a real discovered code feature BEFORE anything is under
14
- # specs/; on greenfield (no bundle) the list is specs-only, exactly as before.
13
+ # (features already scaffolded/adopted) AND the features the OKF bundle's concepts are
14
+ # known to serve — from the boundary map the developer confirmed, else from each
15
+ # concept's `features:` list. So in a brownfield repo you pick a real discovered code
16
+ # feature BEFORE anything is under specs/. An aggregate is never offered as a feature
17
+ # nobody defined: on greenfield every feature already has its specs/ directory, so the
18
+ # list is specs-only.
15
19
  #
16
20
  # SCOPED BY THE ACTIVE BOUNDED CONTEXT. When .appweaver/context.json names one, both
17
21
  # the listing and the creation happen inside specs/<Context>/ — so a context shows
@@ -22,7 +26,7 @@
22
26
  # ZERO LLM: only scripts + one select + (conditionally) one ask. No generation,
23
27
  # no standards, no domain-select. Pure, deterministic orchestration/governance.
24
28
  #
25
- # list-features (specs/ ∪ OKF aggregates) → user_select (existing | "+ create new") → [ask name] → set-feature
29
+ # list-features (specs/ ∪ features the OKF concepts serve) → user_select (existing | "+ create new") → [ask name] → set-feature
26
30
  #
27
31
  # Persists .appweaver/feature.json {"feature_directory":"specs/<Feature>"} so every
28
32
  # later command operates on it: the forward loop (specify/plan/analyze-plan/implement/
@@ -36,14 +40,14 @@
36
40
  # =============================================================================
37
41
 
38
42
  name: aw-spec-select-feature
39
- description: Pick an existing feature (aggregate) or create a new one, and store it in .appweaver/feature.json so every AW-Spec command operates on it. Zero LLM — scripts + one selection only.
43
+ description: Pick an existing feature (a capability scope you choose) or create a new one, and store it in .appweaver/feature.json so every AW-Spec command operates on it. Zero LLM — scripts + one selection only.
40
44
  version: 1.0.0
41
45
  agent_agnostic: true
42
46
  dialect: aw-spec.orchestrator/v1
43
47
 
44
48
  inputs:
45
49
  user_input:
46
- description: Optional feature (aggregate) name to create or select directly, e.g. Order.
50
+ description: Optional feature name to create or select directly, e.g. Order or PatientRegistration.
47
51
  required: false
48
52
  bind: USER_INPUT
49
53
 
@@ -85,11 +89,12 @@ workflow:
85
89
  on_false_message: >
86
90
  {{scope_message}}
87
91
 
88
- # 1. Enumerate selectable aggregates from TWO merged sources (deduped by name):
89
- # specs/ sub-folders (already scaffolded/adopted) AND the OKF bundle's
90
- # Aggregate concepts — the brownfield feature map onboard reverse-engineers,
91
- # so a not-yet-adopted code feature is pickable before its specs/ dir exists.
92
- # On greenfield (no bundle) the OKF source is empty → specs-only, as before.
92
+ # 1. Enumerate selectable FEATURES from TWO merged sources (deduped by name):
93
+ # specs/ sub-folders (already scaffolded/adopted) AND the features the OKF
94
+ # bundle's aggregate concepts serve — the boundary map first, else each concept's
95
+ # `features:` list — so a not-yet-adopted code feature is pickable before its
96
+ # specs/ dir exists. A concept greenfield implement wrote contributes nothing:
97
+ # its features all have specs/ dirs, and its own name is an aggregate.
93
98
  # `select_options` carries the "+ create new" sentinel and (when USER_INPUT
94
99
  # names a not-yet feature) that name injected — a direct arg is one-list select.
95
100
  - id: list_features
@@ -108,7 +113,7 @@ workflow:
108
113
  # no prompt; the lone sentinel at bootstrap (no features yet) auto-confirms.
109
114
  - id: pick
110
115
  action: user_select
111
- prompt: "Select a feature (the aggregate, e.g. Order) in {{feature_base}}/ — or create a new one:"
116
+ prompt: "Select a feature (a capability scope, e.g. Order or PatientRegistration) in {{feature_base}}/ — or create a new one:"
112
117
  options_from_variable: select_options
113
118
  preselect_from: USER_INPUT
114
119
  single_option_confirm: true
@@ -120,9 +125,10 @@ workflow:
120
125
  action: ask
121
126
  condition: "{{chosen}} == {{new_sentinel}}"
122
127
  prompt: >
123
- Name the new feature — this is the AGGREGATE root (e.g. Order, Invoice,
124
- Customer), NOT a single command. Operations like CreateOrder / UpdateOrder
125
- become user stories inside it:
128
+ Name the new feature — the capability scope you want to specify and build as
129
+ one unit (e.g. Order, PatientRegistration). It may act on several aggregates, and
130
+ other features may act on the same ones; its spec names them. Each operation in
131
+ its scope (e.g. CreateOrder / UpdateOrder) becomes a user story inside it:
126
132
  answer_var: typed_name
127
133
 
128
134
  # 4. Coalesce the target name. The engine has no compound conditions, so this
@@ -136,7 +142,7 @@ workflow:
136
142
  condition: "{{chosen}} == {{new_sentinel}}"
137
143
  vars: { target_name: "{{typed_name}}" }
138
144
 
139
- # 5. Create-or-select the aggregate dir + persist it as the active feature.
145
+ # 5. Create-or-select the feature dir + persist it as the active feature.
140
146
  - id: set_feature
141
147
  action: execute_script
142
148
  script: { ref: actions }
@@ -157,5 +163,5 @@ outputs:
157
163
  context: "{{context}}"
158
164
  created: "{{created}}"
159
165
  spec_exists: "{{spec_exists}}"
160
- note: "Active feature set in .appweaver/feature.json — the aggregate boundary for its single spec.md + backend-plan.md. `context` names the bounded context it was created in, and is empty on a repo with no active context (the flat specs/<Feature> layout, which keeps working unchanged)."
166
+ note: "Active feature set in .appweaver/feature.json — the capability scope that owns its single spec.md + backend-plan.md. `context` names the bounded context it was created in, and is empty on a repo with no active context (the flat specs/<Feature> layout, which keeps working unchanged)."
161
167
  next: "/appweaver specify <what this feature does> (authors {{feature_dir}}/spec.md; each operation = a user story)"
@@ -14,8 +14,13 @@
14
14
  # evidence sits in, and both are only ever proposals — an operator selects from the
15
15
  # list or types a path the kit has never seen.
16
16
  #
17
- # stack-list --suggest backend → select stack → ask source path → lane-declare
18
- # stack-list --suggest frontend → select stack → ask source path → lane-declare
17
+ # stack-list --suggest backend → select stack → stack-list --profile → ask source path → lane-declare
18
+ # stack-list --suggest frontend → select stack → stack-list --profile → ask source path → lane-declare
19
+ #
20
+ # THE SOURCE-PATH OPTIONS ARE BUILT AFTER THE STACK IS CHOSEN. Most profiles of a kind
21
+ # share a default root (six frontend stacks default to web/), so a list built before the
22
+ # choice cannot say which stack an option belongs to. The second stack-list call answers
23
+ # for the chosen stack alone.
19
24
  #
20
25
  # (none) IS A REAL ANSWER FOR EITHER LANE, and it is how a project says "I do not
21
26
  # have one of these". A backend-only project answers (none) to the UI lane; a
@@ -56,7 +61,7 @@
56
61
 
57
62
  name: aw-spec-select-stack
58
63
  description: Declare the project's lanes — each lane's STACK and its SOURCE PATH — and persist them to .appweaver/stacks.json, the single channel every later workflow resolves and gates on. The first command of both the greenfield and the brownfield sequence, and the sole writer of the lane model.
59
- version: 2.0.0
64
+ version: 2.3.0
60
65
  agent_agnostic: true
61
66
  dialect: aw-spec.orchestrator/v1
62
67
 
@@ -93,9 +98,7 @@ workflow:
93
98
  - { name: api_options, from: "$.select_options" }
94
99
  - { name: api_ids, from: "$.ids" }
95
100
  - { name: api_preselect, from: "$.preselect" }
96
- - { name: api_root_options, from: "$.root_options" }
97
- - { name: api_detected, from: "$.detected_profile" }
98
- - { name: api_detected_root, from: "$.detected_root" }
101
+ - { name: api_detected, from: "$.detected_summary" }
99
102
  - { name: api_none_sentinel, from: "$.none_sentinel" }
100
103
 
101
104
  # 2. No profiles installed is a setup error, not an empty menu. Gated on the
@@ -114,7 +117,7 @@ workflow:
114
117
  # does a detected stack; a project with exactly one candidate auto-confirms.
115
118
  - id: select_api_stack
116
119
  action: user_select
117
- prompt: "Select the API (backend) stack — it owns the service lane. This repo's own files point at: {{api_detected}} (leading the list). (none) declares a project with no service lane at all, and stops the technical constitution half and the service rule files being authored:"
120
+ prompt: "Select the API (backend) stack — it owns the service lane. This repo's own files point at: {{api_detected}}. A detected stack leads the list. (none) declares a project with no service lane at all, and stops the technical constitution half and the service rule files being authored:"
118
121
  options_from_variable: api_options
119
122
  preselect_from: api_preselect
120
123
  single_option_confirm: true
@@ -133,14 +136,24 @@ workflow:
133
136
  condition: "{{chosen_api}} == {{api_none_sentinel}}"
134
137
  vars: { api_selected: "" }
135
138
 
136
- # 5. WHERE the service lane's source lives. Offered best-first — where the
137
- # evidence actually sits, then each profile's own default — and free text is
138
- # accepted, because a repo that matches no profile still has a source path.
139
- # The label after the path says why it is offered and is stripped on the way in.
139
+ # 5. WHERE the service lane's source lives. Offered best-first for the CHOSEN stack —
140
+ # where its evidence sits, then its own default, then where another stack's
141
+ # evidence sits — and free text is accepted, because a repo that matches no
142
+ # profile still has a source path. The label after the path says why it is
143
+ # offered and is stripped on the way in.
144
+ - id: list_api_roots
145
+ action: execute_script
146
+ condition: "{{api_selected}}"
147
+ script: { ref: actions }
148
+ subcommand: stack-list
149
+ args: ["--kind", "backend", "--suggest", "--profile", "{{chosen_api}}"]
150
+ outputs:
151
+ - { name: api_root_options, from: "$.root_options" }
152
+
140
153
  - id: ask_api_source_path
141
154
  action: ask
142
155
  condition: "{{api_selected}}"
143
- prompt: "Service lane SOURCE PATH — the subtree this lane owns, the only place implement may write, and the directory build/test/run act on. Pick one or type your own:"
156
+ prompt: "Service lane SOURCE PATH — the subtree this lane owns, where implement writes its code, and the directory build/test/run act on. Its tests go to the stack's test path beside it (tests/ next to src/ for .NET), shown in the summary. Pick one or type your own:"
144
157
  options_from_variable: api_root_options
145
158
  answer_var: api_source_path
146
159
 
@@ -159,6 +172,7 @@ workflow:
159
172
  - { name: api_lane_id, from: "$.lane_id" }
160
173
  - { name: api_profile, from: "$.profile" }
161
174
  - { name: api_root, from: "$.source_path" }
175
+ - { name: api_test_path, from: "$.test_path" }
162
176
  - { name: api_root_exists, from: "$.root_exists" }
163
177
 
164
178
  # ═══════════════ UI LANE ════════════════════════════════════════════════════
@@ -176,15 +190,13 @@ workflow:
176
190
  - { name: ui_options, from: "$.select_options" }
177
191
  - { name: ui_ids, from: "$.ids" }
178
192
  - { name: ui_preselect, from: "$.preselect" }
179
- - { name: ui_root_options, from: "$.root_options" }
180
- - { name: ui_detected, from: "$.detected_profile" }
181
- - { name: ui_detected_root, from: "$.detected_root" }
193
+ - { name: ui_detected, from: "$.detected_summary" }
182
194
  - { name: ui_none_sentinel, from: "$.none_sentinel" }
183
195
 
184
196
  # 8. Choose the UI stack.
185
197
  - id: select_ui_stack
186
198
  action: user_select
187
- prompt: "Select the UI (frontend) stack — it owns the web lane, and its root must not overlap the service lane's. This repo's own files point at: {{ui_detected}} (leading the list). (none) declares a backend-only project, and stops the UI constitution half and the frontend rule files being authored:"
199
+ prompt: "Select the UI (frontend) stack — it owns the web lane, and its root must not overlap the service lane's. This repo's own files point at: {{ui_detected}}. A detected stack leads the list. (none) declares a backend-only project, and stops the UI constitution half and the frontend rule files being authored:"
188
200
  options_from_variable: ui_options
189
201
  preselect_from: ui_preselect
190
202
  single_option_confirm: true
@@ -199,7 +211,16 @@ workflow:
199
211
  condition: "{{chosen_ui}} == {{ui_none_sentinel}}"
200
212
  vars: { ui_selected: "" }
201
213
 
202
- # 9. WHERE the UI lane's source lives.
214
+ # 9. WHERE the UI lane's source lives, offered for the chosen UI stack.
215
+ - id: list_ui_roots
216
+ action: execute_script
217
+ condition: "{{ui_selected}}"
218
+ script: { ref: actions }
219
+ subcommand: stack-list
220
+ args: ["--kind", "frontend", "--suggest", "--profile", "{{chosen_ui}}"]
221
+ outputs:
222
+ - { name: ui_root_options, from: "$.root_options" }
223
+
203
224
  - id: ask_ui_source_path
204
225
  action: ask
205
226
  condition: "{{ui_selected}}"
@@ -217,6 +238,7 @@ workflow:
217
238
  - { name: ui_lane_id, from: "$.lane_id" }
218
239
  - { name: ui_profile, from: "$.profile" }
219
240
  - { name: ui_root, from: "$.source_path" }
241
+ - { name: ui_test_path, from: "$.test_path" }
220
242
  - { name: ui_root_exists, from: "$.root_exists" }
221
243
 
222
244
  # ═══════════════ CHECK + REPORT ═════════════════════════════════════════════
@@ -257,7 +279,7 @@ workflow:
257
279
  - id: api_summary_lane
258
280
  action: set
259
281
  condition: "{{api_declared}}"
260
- vars: { api_summary: "{{api_lane_id}} ({{api_profile}}) at {{api_root}} — source path exists: {{api_root_exists}}" }
282
+ vars: { api_summary: "{{api_lane_id}} ({{api_profile}}) at {{api_root}}, tests at {{api_test_path}} — source path exists: {{api_root_exists}}" }
261
283
 
262
284
  - id: ui_summary_none
263
285
  action: set
@@ -265,7 +287,7 @@ workflow:
265
287
  - id: ui_summary_lane
266
288
  action: set
267
289
  condition: "{{ui_declared}}"
268
- vars: { ui_summary: "{{ui_lane_id}} ({{ui_profile}}) at {{ui_root}} — source path exists: {{ui_root_exists}}" }
290
+ vars: { ui_summary: "{{ui_lane_id}} ({{ui_profile}}) at {{ui_root}}, tests at {{ui_test_path}} — source path exists: {{ui_root_exists}}" }
269
291
 
270
292
  # 12. Read the finished declaration back through the reader every later workflow
271
293
  # uses, so what this run reports is what they will resolve.
@@ -287,10 +309,12 @@ outputs:
287
309
  ui_lane: "{{ui_summary}}"
288
310
  api_source_path: "{{api_root}}"
289
311
  ui_source_path: "{{ui_root}}"
312
+ api_test_path: "{{api_test_path}}"
313
+ ui_test_path: "{{ui_test_path}}"
290
314
  api_source_path_exists: "{{be_root_exists}}"
291
315
  ui_source_path_exists: "{{fe_root_exists}}"
292
- api_detected: "{{api_detected}} at {{api_detected_root}}"
293
- ui_detected: "{{ui_detected}} at {{ui_detected_root}}"
316
+ api_detected: "{{api_detected}}"
317
+ ui_detected: "{{ui_detected}}"
294
318
  prototype: "{{prototype}}"
295
- note: "Lanes persisted to .appweaver/stacks.json. Each lane carries a STACK and a SOURCE PATH, and both are load-bearing. The SOURCE PATH is ownership: implement's service lane writes only under the service lane's and its frontend lane only under the UI lane's, build/test/run act on it, and onboard reverse-engineers out of it — overlapping paths are refused here rather than discovered later as two generators writing one file. The build/test/run commands are project-supplied strings the engine shells out to, which is how it stays stdlib-only and stack-ignorant. api_detected / ui_detected report what this repo's own files pointed at, whether or not it was chosen — empty on a greenfield repo with nothing to detect. A source path that does not exist yet is NORMAL here and reported rather than refused: greenfield declares src/ before implement creates it. The on-disk check runs in the phases that read the subtree — implement, build, run and onboard all call lane-verify and STOP with the fix named. Declining a lane with (none) is a real declaration and removes any lane of that kind a previous run left behind; it is also what stops that lane's constitution half and rule files being authored. `prototype` is not a lane — it has a root and a run command so an operator can open it, and nothing else. Re-run this workflow any time to re-stack a lane, move a source path, or drop a lane with (none); it is the only workflow that may write this file."
296
- next: "GREENFIELD: /appweaver select-domain → constitution → select-feature → specify. BROWNFIELD: /appweaver onboard — it reverse-engineers out of the source paths declared here and generates the project (no select-domain; onboard derives and activates it from the code). Run this workflow FIRST in either sequence: constitution reads the declaration to decide which halves to author, and onboard reads it to decide what to scan."
319
+ note: "Lanes persisted to .appweaver/stacks.json. Each lane carries a STACK and a SOURCE PATH, and both are load-bearing. The SOURCE PATH is ownership: implement's service lane writes its code only under the service lane's and its frontend lane only under the UI lane's, build/test/run act on it, and onboard reverse-engineers out of it. Each lane also owns a TEST PATH, taken from its stack profile — tests/ beside a .NET src/, the source path itself for a stack that keeps its tests inside — and implement writes the lane's tests there. Overlapping paths of either kind are refused here rather than discovered later as two generators writing one file. The build/test/run commands are project-supplied strings the engine shells out to, which is how it stays stdlib-only and stack-ignorant. api_detected / ui_detected report what this repo's own files pointed at, whether or not it was chosen, as "<stack> at <path>" — or "nothing detected" on a greenfield repo with nothing to detect. A source path that does not exist yet is NORMAL here and reported rather than refused: greenfield declares src/ before implement creates it. The on-disk check runs in the phases that read the subtree — implement, build, run and onboard all call lane-verify and STOP with the fix named. Declining a lane with (none) is a real declaration and removes any lane of that kind a previous run left behind; it is also what stops that lane's constitution half and rule files being authored. `prototype` is not a lane — it has a root and a run command so an operator can open it, and nothing else. Re-run this workflow any time to re-stack a lane, move a source path, or drop a lane with (none); it is the only workflow that may write this file."
320
+ next: "GREENFIELD: /appweaver foundation — once per lane, to scaffold a compiling project into each source path declared here (skip it for a lane whose code already exists, or scaffold with the stack's own tooling) — then /appweaver select-domain → constitution → select-feature → specify. BROWNFIELD: /appweaver onboard — it reverse-engineers out of the source paths declared here and generates the project (no select-domain; onboard derives and activates it from the code). Run this workflow FIRST in either sequence: foundation unpacks into the source paths declared here, constitution reads the declaration to decide which halves to author, and onboard reads it to decide what to scan."
@@ -1,8 +1,9 @@
1
1
  # =============================================================================
2
2
  # aw-spec-specify (orchestrator) — author the SPEC for the ACTIVE feature
3
3
  #
4
- # Spec-anchored pipeline, spec phase. feature = AGGREGATE (picked/created by
5
- # /appweaver select-feature, which sets .appweaver/feature.json). specify does
4
+ # Spec-anchored pipeline, spec phase. The feature is a capability scope the developer
5
+ # picked/created with /appweaver select-feature (.appweaver/feature.json); it may act on
6
+ # several aggregates, and several features may act on one. specify does
6
7
  # NOT guess or create the feature — it CONSUMES the active one, and is phase-pure
7
8
  # (spec side only; the single backend-plan.md is the `design` phase).
8
9
  #
@@ -163,7 +164,7 @@ workflow:
163
164
  action: verify_context
164
165
  require_non_empty: feature_dir
165
166
  on_empty: stop
166
- on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the aggregate, e.g. Order), then re-run specify."
167
+ on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the feature, e.g. Order or PatientRegistration), then re-run specify."
167
168
 
168
169
  # 1b. Resolve the BOUNDED CONTEXT's domain model — the canonical home of the aggregate
169
170
  # root, its entities and its invariants, which this spec references and never
@@ -325,13 +326,17 @@ workflow:
325
326
  action: agent_author
326
327
  output_target: { type: file, path: "{{spec_file}}" }
327
328
  task: |
328
- Author the MASTER specification for this aggregate by filling out the
329
+ Author the MASTER specification for the feature `{{feature}}` by filling out the
329
330
  template in context. This is the first user story, US-1. Base everything on:
330
331
 
331
332
  {{USER_INPUT}}
332
333
 
333
334
  Rules for the content you generate:
334
- - The feature IS the aggregate slice; set the Aggregate field and keep US-1's id.
335
+ - The feature is a CAPABILITY SCOPE the developer chose; set the Feature field to
336
+ `{{feature}}` and keep US-1's id. It may act on several aggregates, and other
337
+ features may act on the same ones: name every aggregate its requirements act on
338
+ in the Domain Model Reference's `**Acts on aggregate(s)**:` line, each in
339
+ backticks — every later command finds this feature's part of the model there.
335
340
  - Preserve the template's section order and headings.
336
341
  - SPECIFY IS THE ONLY PHASE THAT READS THE DOMAIN RULES. Design and implement
337
342
  work from the spec + the domain model, never the domain docs — so this pair is
@@ -436,11 +441,14 @@ workflow:
436
441
  section per root: its boundary (owns / references by id / out of scope), entities,
437
442
  value objects, domain events, and its invariants as stable BR-nnn ids. The spec
438
443
  cites those ids; this document defines them.
439
- - IF THE MODEL ALREADY HAS CONTENT, this is the context's SECOND (or later) feature.
440
- ADD this feature's aggregate as a new section and EXTEND the ubiquitous language.
441
- Do NOT rewrite, renumber or "tidy" the aggregates already there — other specs cite
442
- their ids, and a renumbered BR breaks every citation silently. Add `{{feature}}` to
443
- the `Serving specs` line of any existing aggregate this feature also acts on.
444
+ - IF THE MODEL ALREADY HAS CONTENT, this is the context's SECOND (or later) feature,
445
+ and the aggregates it acts on may already be there. An aggregate that already has
446
+ a section is EXTENDED in place — add what this feature's requirements need, and add
447
+ `{{feature}}` to its `Serving specs` line — never duplicated, and never given a
448
+ second section named after the feature. Only an aggregate the model does not have
449
+ yet gets a new section. EXTEND the ubiquitous language. Do NOT rewrite, renumber
450
+ or "tidy" what is already there — other specs cite those ids, and a renumbered BR
451
+ breaks every citation silently.
444
452
  - DDD IS THE RATIFIED MODELLING STANDARD (NON-NEGOTIABLE → the modelling-methodology
445
453
  standard in the sticky standards, wired per-project via tech-rule-bindings
446
454
  "## /aw-spec-specify", plus the domain's own DDD overlay): a consistent ubiquitous
@@ -662,5 +670,5 @@ outputs:
662
670
  authored_document: "{{quality_target}}" # spec.md (bootstrap) | delta-spec.md (delta)
663
671
  checklist_file: "{{checklist_file}}"
664
672
  checklist_quality: "{{checklist_passed}}/{{checklist_total}} items passing"
665
- note: "Behaviour and structure were authored together: the spec says what the system must do, {{domain_model}} says what the domain IS. The aggregate root, its entities and its invariants (BR-n) live in the model and are cited from the spec by id — never restated there, because several specs act on one aggregate and a per-spec copy drifts. On the DELTA path neither master was touched: this run wrote changes/{{cr_id}}/delta-spec.md and {{delta_model}}, and that model delta sits BESIDE the master where every other open delta can see it while being authored. WAVE {{wave}} is its parallel-development level — deltas sharing a wave are independent, a higher wave is implemented only after the lower ones land; `cr-wave --cr-id {{cr_id}} --wave <n>` moves it. {{open_count}} change(s) are open on this feature."
673
+ note: "Behaviour and structure were authored together: the spec says what the system must do, {{domain_model}} says what the domain IS. The aggregate root, its entities and its invariants (BR-n) live in the model and are cited from the spec by id — never restated there, because several specs act on one aggregate and a per-spec copy drifts. On the DELTA path neither master was touched: this run wrote changes/{{cr_id}}/delta-spec.md and {{delta_model}}, and that model delta sits BESIDE the master where every other open delta can see it while being authored. WAVE {{wave}} is its parallel-development level — deltas sharing a wave are independent, a higher wave is implemented only after the lower ones land. The wave is DERIVED from the model delta's `## Depends on`: one above the highest delta named there, stamped into both halves as soon as it was authored ({{wave_message}}). To move it, correct that section and re-run `.appweaver/scripts/bash/aw-spec-actions.sh cr-wave-derive --cr-id {{cr_id}} --stamp`; finalize-delta refuses a change whose wave contradicts it. {{open_count}} change(s) are open on this feature."
666
674
  next: "/appweaver plan — BOOTSTRAP authors backend-plan.md from the spec + the domain model; a DELTA (this run opened change {{cr_id}}, wave {{wave}}) authors the delta plans for it. A change then flows: analyze-plan-delta → implement → build → analyze-slice-delta → finalize-delta → analyze-slice → close-delta (finalize-delta merges the delta into spec.md + domain-model.md — not the lane plans, which are derived from those two — but leaves the change open; analyze-slice verifies the slice against the merged master; close-delta archives the change and bumps the version). ⚠️ With more than one change open, plan, analyze-plan-delta and implement ASK which one they are for (pass the id as the argument to skip the prompt) and activate the answer; finalize-delta and close-delta still act on whichever change is active — so finish or close the others deliberately."
@@ -11,6 +11,21 @@
11
11
  # [--service-root <p>] [--ui-root <p>] (the declared lane paths:
12
12
  # backend signals come from outside the UI subtree, frontend
13
13
  # signals from inside it, so neither carries the other's files)
14
+ # aw-inventory.sh propose [--root <dir>] [--service-root <p>] [--ui-root <p>]
15
+ # [--strategy structure|reference|persistence] [--out <md>] [--map <json>]
16
+ # (Pass A+B+C: writes the editable specs/proposed-inventory.md
17
+ # and the machine map onboard reads)
18
+ # aw-inventory.sh apply [--root <dir>] [--proposal <md>] [--map <json>]
19
+ # (validates the operator's edits to the proposal and
20
+ # regenerates the machine map; reports the row it cannot resolve)
21
+ # aw-inventory.sh scaffold-context --context <Name> [--root <dir>] [--map <json>]
22
+ # (creates specs/<Context>/<Feature>/ for every feature the map
23
+ # assigns to that context — directories only, no documents)
24
+ # aw-inventory.sh concept --bundle <dir> --aggregate <Name>
25
+ # aw-inventory.sh list-aggregates --bundle <dir> [--context <Name>] [--root <dir>] [--map <json>]
26
+ # (--context drops concepts stamped with a DIFFERENT bounded
27
+ # context; unstamped concepts are always kept. `pickable` offers
28
+ # each concept as the features the map puts on it)
14
29
  # aw-inventory.sh validate --bundle <dir> [--repo-root <dir>]
15
30
  set -euo pipefail
16
31
  DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"