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.
- package/assets/.appweaver/DESIGN-FORMAT.md +17 -9
- package/assets/.appweaver/ORCH-DIALECT.md +8 -3
- package/assets/.appweaver/orchestrator/modules/delta-spec.orch.yaml +26 -3
- package/assets/.appweaver/orchestrator/orchestrator.py +65 -3
- package/assets/.appweaver/orchestrator/rev-templates/delta-design-template.skeleton.md +1 -1
- package/assets/.appweaver/orchestrator/rev-templates/design-template.skeleton.md +4 -2
- package/assets/.appweaver/orchestrator/rev-templates/ui-delta-design-template.skeleton.md +1 -1
- package/assets/.appweaver/orchestrator/rev-templates/ui-design-template.skeleton.md +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +121 -42
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan-delta.orch.yaml +7 -7
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +145 -52
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +18 -10
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +306 -28
- package/assets/.appweaver/orchestrator/workflows/aw-spec-build.orch.yaml +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-characterize.orch.yaml +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-close-delta.orch.yaml +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-constitution.orch.yaml +4 -4
- package/assets/.appweaver/orchestrator/workflows/aw-spec-deploy.orch.yaml +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +114 -30
- package/assets/.appweaver/orchestrator/workflows/aw-spec-foundation.orch.yaml +140 -36
- package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +385 -61
- package/assets/.appweaver/orchestrator/workflows/aw-spec-inventory.orch.yaml +217 -174
- package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +407 -107
- package/assets/.appweaver/orchestrator/workflows/aw-spec-parity.orch.yaml +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +49 -11
- package/assets/.appweaver/orchestrator/workflows/aw-spec-recall-delta.orch.yaml +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-run.orch.yaml +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-select-feature.orch.yaml +29 -23
- package/assets/.appweaver/orchestrator/workflows/aw-spec-select-stack.orch.yaml +47 -23
- package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +19 -11
- package/assets/.appweaver/scripts/bash/aw-inventory.sh +15 -0
- package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +683 -92
- package/assets/.appweaver/scripts/bash/aw-xref.sh +3 -0
- package/assets/.appweaver/scripts/powershell/aw-inventory.ps1 +15 -0
- package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +625 -64
- package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +3 -0
- package/assets/.appweaver/scripts/python/aw-crstate.py +194 -0
- package/assets/.appweaver/scripts/python/aw-foundation.py +151 -0
- package/assets/.appweaver/scripts/python/aw-inventory.py +1829 -63
- package/assets/.appweaver/scripts/python/aw-payload-parity.py +3 -0
- package/assets/.appweaver/scripts/python/aw-plan-coverage.py +423 -0
- package/assets/.appweaver/scripts/python/aw-stack.py +251 -28
- package/assets/.appweaver/scripts/python/aw-testlog.py +148 -0
- package/assets/.appweaver/scripts/python/aw-xref.py +500 -81
- package/assets/.appweaver/stack-profiles/dotnet-webapi.json +1 -0
- package/assets/.appweaver/stack-profiles/java-spring.json +1 -0
- package/assets/.appweaver/stack-profiles/node-express.json +1 -0
- package/assets/.appweaver/stack-profiles/python-fastapi.json +1 -0
- package/assets/.appweaver/templates/constitution-domain-template.md +17 -2
- package/assets/.appweaver/templates/delta-design-template.md +1 -1
- package/assets/.appweaver/templates/delta-domain-model-template.md +1 -1
- package/assets/.appweaver/templates/delta-spec-template.md +4 -3
- package/assets/.appweaver/templates/design-template.md +16 -11
- package/assets/.appweaver/templates/okf-concept-template.md +11 -1
- package/assets/.appweaver/templates/proposed-inventory-template.md +127 -0
- package/assets/.appweaver/templates/source-map-template.md +5 -3
- package/assets/.appweaver/templates/spec-template.md +17 -17
- package/assets/.appweaver/templates/ui-delta-design-template.md +1 -1
- package/assets/.appweaver/templates/ui-design-template.md +4 -4
- package/assets/foundations/dotnet/ModularMonolith/ModularMonolith.pack.txt +4489 -361
- package/assets/foundations/dotnet/VerticalSlice/VerticalSlice.pack.txt +2274 -125
- package/assets/guardrails/projects/dotnet-default-project/domain-rule-bindings.md +10 -6
- package/assets/guardrails/projects/dotnet-default-project/tech-rule-bindings.md +4 -0
- package/assets/guardrails/technology/dotnet-webapi/auditing.md +3 -1
- package/assets/guardrails/technology/dotnet-webapi/coding-standards.md +5 -3
- package/assets/guardrails/technology/dotnet-webapi/cqrs.md +5 -1
- package/assets/guardrails/technology/dotnet-webapi/domain-driven-design.md +13 -5
- package/assets/guardrails/technology/dotnet-webapi/examples/cqrs.md +2 -0
- package/assets/guardrails/technology/dotnet-webapi/examples/domain-driven-design.md +6 -2
- package/assets/guardrails/technology/dotnet-webapi/framework-api-map.md +70 -0
- package/assets/guardrails/technology/dotnet-webapi/technology-constitution.md +3 -0
- package/assets/guardrails/technology/dotnet-webapi/testing.md +10 -0
- package/assets/guardrails/technology/nextjs/ui-constitution.md +8 -1
- package/package.json +1 -1
- 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
|
|
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
|
|
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
|
|
535
|
-
|
|
536
|
-
|
|
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
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
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
|
|
708
|
-
|
|
709
|
-
|
|
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
|
|
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
|
|
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
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
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
|
|
12
|
-
# the
|
|
13
|
-
# brownfield repo you pick a real discovered code
|
|
14
|
-
#
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
89
|
-
# specs/ sub-folders (already scaffolded/adopted) AND the OKF
|
|
90
|
-
#
|
|
91
|
-
# so a not-yet-adopted code feature is pickable before its
|
|
92
|
-
#
|
|
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 (
|
|
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 —
|
|
124
|
-
|
|
125
|
-
|
|
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
|
|
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
|
|
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.
|
|
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:
|
|
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}}
|
|
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
|
|
137
|
-
# evidence
|
|
138
|
-
# accepted, because a repo that matches no
|
|
139
|
-
# The label after the path says why it is
|
|
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,
|
|
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:
|
|
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}}
|
|
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}}
|
|
293
|
-
ui_detected: "{{ui_detected}}
|
|
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 —
|
|
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
|
|
5
|
-
# /appweaver select-feature
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
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
|
|
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)"
|