aw-spec-cli 0.1.19 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/.appweaver/DESIGN-FORMAT.md +17 -9
- package/assets/.appweaver/orchestrator/modules/delta-spec.orch.yaml +4 -2
- 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 +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +38 -4
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +41 -8
- 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-deploy.orch.yaml +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +26 -16
- package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +31 -9
- package/assets/.appweaver/orchestrator/workflows/aw-spec-inventory.orch.yaml +217 -174
- package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +405 -105
- 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-specify.orch.yaml +18 -10
- package/assets/.appweaver/scripts/bash/aw-inventory.sh +15 -0
- package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +32 -16
- 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 +25 -5
- package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +3 -0
- package/assets/.appweaver/scripts/python/aw-inventory.py +1829 -63
- package/assets/.appweaver/scripts/python/aw-xref.py +500 -81
- 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/package.json +1 -1
|
@@ -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)"
|
|
@@ -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
|
|
@@ -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)"
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
# Always exits 0 — the workflow's verify_context owns the stop)
|
|
43
43
|
# lane-probe [--kind <k>] [--hint <text>] -> {declared, declared_profile, hint_profile, hint_terms[], signals[], any, evidence}
|
|
44
44
|
# (the FILE signals for "does this lane exist"; resolve-tech carries the rule signal)
|
|
45
|
-
# list-features [--preselect <name>] [--bundle <dir>] -> {features[], names[], count, select_options[], sentinel} (specs/ ∪ OKF
|
|
45
|
+
# list-features [--preselect <name>] [--bundle <dir>] -> {features[], names[], count, select_options[], sentinel} (specs/ ∪ features the OKF concepts serve)
|
|
46
46
|
# set-feature --feature <name> -> {feature, feature_dir, spec_file, existed, created, spec_exists}
|
|
47
47
|
# version-read [--feature-dir <path>] -> {feature, version, active_change, exists, bootstrap_pending}
|
|
48
48
|
# version-init [--feature-dir <path>] [--bootstrap] -> {…, created, bootstrap_pending} (create active_version.json v1.0; --bootstrap marks a FIRST draft in progress)
|
|
@@ -1206,6 +1206,10 @@ cmd_model_scaffold() {
|
|
|
1206
1206
|
# reverse-engineers (the discovered aggregates NOT yet under specs/). This is
|
|
1207
1207
|
# why select-feature can offer real code features in a brownfield repo before
|
|
1208
1208
|
# anything is adopted, instead of an empty specs/ list.
|
|
1209
|
+
# BOTH sources are scoped to the ACTIVE BOUNDED CONTEXT: source 1 by enumerating the
|
|
1210
|
+
# context's own directory, source 2 by the `bounded_context:` key onboard stamps on
|
|
1211
|
+
# every concept it writes. onboard runs once per context, so an unfiltered bundle
|
|
1212
|
+
# offers a neighbouring context's features to a picker that would scaffold them here.
|
|
1209
1213
|
# On greenfield (no bundle) source 2 is empty → identical to the old specs-only
|
|
1210
1214
|
# behaviour. `select_options` = merged names + (an injected --preselect that is not
|
|
1211
1215
|
# yet a feature) + a "create new" sentinel, so select-feature can offer add-new and
|
|
@@ -1223,16 +1227,24 @@ cmd_list_features() {
|
|
|
1223
1227
|
local repo_root; repo_root=$(get_repo_root)
|
|
1224
1228
|
[ -n "$bundle" ] || bundle="knowledge"
|
|
1225
1229
|
case "$bundle" in /*) ;; *) bundle="$repo_root/$bundle" ;; esac
|
|
1226
|
-
# Best-effort OKF aggregate list from the shared python core (single source of
|
|
1227
|
-
# truth for OKF parsing; the ps1 twin calls the SAME script). Empty on greenfield.
|
|
1228
|
-
local okf_json="{}" invpy="$SCRIPT_DIR/../python/aw-inventory.py"
|
|
1229
|
-
if [ -d "$bundle" ] && [ -f "$invpy" ] && command -v python3 >/dev/null 2>&1; then
|
|
1230
|
-
okf_json=$(python3 "$invpy" list-aggregates --bundle "$bundle" 2>/dev/null) || okf_json="{}"
|
|
1231
|
-
fi
|
|
1232
1230
|
# Features are enumerated from the ACTIVE BOUNDED CONTEXT's directory when one is
|
|
1233
1231
|
# set, and from specs/ itself when none is — so a context scopes the picker to its
|
|
1234
1232
|
# own features and a pre-context repo lists exactly what it always did.
|
|
1235
1233
|
local base_rel; base_rel=$(feature_base_rel "$repo_root")
|
|
1234
|
+
# The OKF half is scoped the same way. onboard runs once per bounded context and
|
|
1235
|
+
# stamps each concept it writes, so a bundle that has seen two runs holds two
|
|
1236
|
+
# contexts' aggregates; unfiltered, standing inside one context would offer the
|
|
1237
|
+
# other's features and scaffold them into the wrong tree. Empty when no context is
|
|
1238
|
+
# active, which keeps the flat layout listing everything.
|
|
1239
|
+
local active_ctx=""
|
|
1240
|
+
[ "$base_rel" != "specs" ] && active_ctx="${base_rel##*/}"
|
|
1241
|
+
# Best-effort OKF aggregate list from the shared python core (single source of
|
|
1242
|
+
# truth for OKF parsing AND for the context filter; the ps1 twin calls the SAME
|
|
1243
|
+
# script with the same argument). Empty on greenfield.
|
|
1244
|
+
local okf_json="{}" invpy="$SCRIPT_DIR/../python/aw-inventory.py"
|
|
1245
|
+
if [ -d "$bundle" ] && [ -f "$invpy" ] && command -v python3 >/dev/null 2>&1; then
|
|
1246
|
+
okf_json=$(python3 "$invpy" list-aggregates --bundle "$bundle" --root "$repo_root" --context "$active_ctx" 2>/dev/null) || okf_json="{}"
|
|
1247
|
+
fi
|
|
1236
1248
|
AW_SPECS="$repo_root/$base_rel" AW_BASE_REL="$base_rel" AW_PRESELECT="$preselect" AW_OKF="$okf_json" python3 - <<'PY'
|
|
1237
1249
|
import json, os, glob
|
|
1238
1250
|
base = os.environ["AW_SPECS"]
|
|
@@ -1293,9 +1305,13 @@ if os.path.isdir(base):
|
|
|
1293
1305
|
"version": ver,
|
|
1294
1306
|
"active_change": active_change,
|
|
1295
1307
|
})
|
|
1296
|
-
# Merge OKF-discovered
|
|
1297
|
-
# folder is the adopted/created form of the same
|
|
1298
|
-
|
|
1308
|
+
# Merge the OKF-discovered FEATURES not already scaffolded under specs/ (a specs/
|
|
1309
|
+
# folder is the adopted/created form of the same feature — it wins, no dup).
|
|
1310
|
+
# `pickable` is each aggregate concept offered as the features the map puts on it, so
|
|
1311
|
+
# an aggregate split into capabilities is never itself offered as a feature to pick.
|
|
1312
|
+
# An EMPTY `pickable` is an answer — nothing to offer, e.g. a greenfield bundle whose
|
|
1313
|
+
# features all have specs/ dirs — so only its absence falls back to the raw aggregates.
|
|
1314
|
+
for agg in (okf["pickable"] if "pickable" in okf else okf.get("aggregates") or []):
|
|
1299
1315
|
nm = (agg.get("name") or "").strip()
|
|
1300
1316
|
if not nm or nm.lower() in seen:
|
|
1301
1317
|
continue
|
|
@@ -1328,26 +1344,26 @@ PY
|
|
|
1328
1344
|
}
|
|
1329
1345
|
|
|
1330
1346
|
# ─────────────────────────────── set-feature ───────────────────────────────
|
|
1331
|
-
# Create-or-select
|
|
1332
|
-
# (.appweaver/feature.json).
|
|
1333
|
-
# PascalCase identifier (e.g. "
|
|
1347
|
+
# Create-or-select a feature under specs/ and persist it as the active feature
|
|
1348
|
+
# (.appweaver/feature.json). The name is the developer's capability scope, normalized to
|
|
1349
|
+
# a PascalCase identifier (e.g. "patient registration" -> PatientRegistration). Idempotent:
|
|
1334
1350
|
# selecting an existing feature just re-points feature.json; a new one is created.
|
|
1335
1351
|
cmd_set_feature() {
|
|
1336
1352
|
local raw=""
|
|
1337
1353
|
while [ $# -gt 0 ]; do
|
|
1338
1354
|
case "$1" in
|
|
1339
1355
|
--feature) raw="$2"; shift 2 ;;
|
|
1340
|
-
--help|-h) echo "set-feature --feature <name> (create-or-select the
|
|
1356
|
+
--help|-h) echo "set-feature --feature <name> (create-or-select the feature under specs/)"; return 0 ;;
|
|
1341
1357
|
*) echo "set-feature: unknown arg '$1'" >&2; return 1 ;;
|
|
1342
1358
|
esac
|
|
1343
1359
|
done
|
|
1344
1360
|
[ -n "$raw" ] || { echo "set-feature: --feature is required" >&2; return 1; }
|
|
1345
1361
|
|
|
1346
|
-
# Normalize to a PascalCase
|
|
1362
|
+
# Normalize to a PascalCase identifier.
|
|
1347
1363
|
local cleaned; cleaned=$(echo "$raw" | sed -E 's/[^A-Za-z0-9]+/ /g; s/^ +| +$//g')
|
|
1348
1364
|
local feature; feature=$(_pascal "$cleaned")
|
|
1349
1365
|
if ! [[ "$feature" =~ ^[A-Za-z][A-Za-z0-9]*$ ]]; then
|
|
1350
|
-
echo "set-feature: '$raw' is not a valid feature name (need a letter-led alphanumeric
|
|
1366
|
+
echo "set-feature: '$raw' is not a valid feature name (need a letter-led alphanumeric name, e.g. Order or PatientRegistration)" >&2
|
|
1351
1367
|
return 1
|
|
1352
1368
|
fi
|
|
1353
1369
|
|
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
# aw-xref.sh concept-upsert --aggregate X [--source-files a,b,c] [--description D] [--origin ...] [--root DIR]
|
|
11
11
|
# aw-xref.sh concept-overview --aggregate X (--text PROSE | --text-file F) [--root DIR]
|
|
12
12
|
# aw-xref.sh concept-paths --aggregate X | --feature-dir <dir> [--root DIR] [--bundle knowledge]
|
|
13
|
+
# aw-xref.sh feature-aggregates --feature-dir <dir> [--root DIR]
|
|
14
|
+
# (the aggregate(s) a feature acts on: its spec's Acts-on line,
|
|
15
|
+
# then the boundary map, then the feature name itself)
|
|
13
16
|
set -euo pipefail
|
|
14
17
|
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
15
18
|
exec python3 "$DIR/../python/aw-xref.py" "$@"
|
|
@@ -10,6 +10,21 @@
|
|
|
10
10
|
# [--service-root <p>] [--ui-root <p>] (the declared lane paths:
|
|
11
11
|
# backend signals come from outside the UI subtree, frontend
|
|
12
12
|
# signals from inside it, so neither carries the other's files)
|
|
13
|
+
# aw-inventory.ps1 propose [--root <dir>] [--service-root <p>] [--ui-root <p>]
|
|
14
|
+
# [--strategy structure|reference|persistence] [--out <md>] [--map <json>]
|
|
15
|
+
# (Pass A+B+C: writes the editable specs/proposed-inventory.md
|
|
16
|
+
# and the machine map onboard reads)
|
|
17
|
+
# aw-inventory.ps1 apply [--root <dir>] [--proposal <md>] [--map <json>]
|
|
18
|
+
# (validates the operator's edits to the proposal and
|
|
19
|
+
# regenerates the machine map; reports the row it cannot resolve)
|
|
20
|
+
# aw-inventory.ps1 scaffold-context --context <Name> [--root <dir>] [--map <json>]
|
|
21
|
+
# (creates specs/<Context>/<Feature>/ for every feature the map
|
|
22
|
+
# assigns to that context — directories only, no documents)
|
|
23
|
+
# aw-inventory.ps1 concept --bundle <dir> --aggregate <Name>
|
|
24
|
+
# aw-inventory.ps1 list-aggregates --bundle <dir> [--context <Name>] [--root <dir>] [--map <json>]
|
|
25
|
+
# (--context drops concepts stamped with a DIFFERENT bounded
|
|
26
|
+
# context; unstamped concepts are always kept. `pickable` offers
|
|
27
|
+
# each concept as the features the map puts on it)
|
|
13
28
|
# aw-inventory.ps1 validate --bundle <dir> [--repo-root <dir>]
|
|
14
29
|
$ErrorActionPreference = "Stop"
|
|
15
30
|
$core = Join-Path $PSScriptRoot "..\python\aw-inventory.py"
|
|
@@ -684,6 +684,8 @@ function Invoke-LaneProbe {
|
|
|
684
684
|
# (case-insensitive) — (1) specs/ sub-folders (already scaffolded/adopted) and
|
|
685
685
|
# (2) the OKF bundle's Aggregate concepts (the brownfield feature map, resolved by
|
|
686
686
|
# the SAME shared python core the bash twin calls). Empty source 2 on greenfield.
|
|
687
|
+
# Both are scoped to the ACTIVE BOUNDED CONTEXT — source 1 by the directory listed,
|
|
688
|
+
# source 2 by the `bounded_context:` key onboard stamps on every concept it writes.
|
|
687
689
|
# ─────────────────────── context.json (bounded context) ───────────────────
|
|
688
690
|
# Twin of the bash read_context_json_context_directory / feature_base_rel pair. A
|
|
689
691
|
# bounded context is the container a feature lives in — specs/<Context>/<Feature>/ —
|
|
@@ -861,13 +863,31 @@ function Invoke-ListFeatures {
|
|
|
861
863
|
if (-not $bundle) { $bundle = 'knowledge' }
|
|
862
864
|
if (-not [System.IO.Path]::IsPathRooted($bundle)) { $bundle = Join-Path $repo $bundle }
|
|
863
865
|
|
|
866
|
+
# The OKF half is scoped to the active bounded context the same way. onboard runs
|
|
867
|
+
# once per context and stamps each concept it writes, so a bundle that has seen two
|
|
868
|
+
# runs holds two contexts' aggregates; unfiltered, standing inside one context would
|
|
869
|
+
# offer the other's features and scaffold them into the wrong tree. Empty when no
|
|
870
|
+
# context is active, which keeps the flat layout listing everything.
|
|
871
|
+
$activeCtx = ''
|
|
872
|
+
if ($baseRel -ne 'specs') { $activeCtx = ($baseRel -split '/')[-1] }
|
|
873
|
+
|
|
864
874
|
# Best-effort OKF aggregate list (brownfield feature map). Empty on greenfield.
|
|
865
875
|
$okfAggs = @()
|
|
866
876
|
$py = Get-Py; $invpy = Join-Path $PSScriptRoot '..' 'python' 'aw-inventory.py'
|
|
867
877
|
if ($py -and (Test-Path $bundle) -and (Test-Path $invpy)) {
|
|
868
878
|
try {
|
|
869
|
-
|
|
870
|
-
|
|
879
|
+
# --context only when one is active: Windows PowerShell drops an empty-string
|
|
880
|
+
# argument to a native command, which would leave --context with no value.
|
|
881
|
+
$ctxArgs = @(); if ($activeCtx) { $ctxArgs = @('--context', $activeCtx) }
|
|
882
|
+
$okfRaw = & $py $invpy list-aggregates --bundle $bundle --root $repo @ctxArgs 2>$null
|
|
883
|
+
if ($okfRaw) {
|
|
884
|
+
# The FEATURES the map puts on each concept, not the aggregates themselves —
|
|
885
|
+
# the same list the bash twin reads.
|
|
886
|
+
$okfJson = $okfRaw | ConvertFrom-Json
|
|
887
|
+
# An EMPTY `pickable` is an answer (nothing to offer); only its absence
|
|
888
|
+
# falls back to the raw aggregates.
|
|
889
|
+
$okfAggs = if ($okfJson.PSObject.Properties.Name -contains 'pickable') { @($okfJson.pickable) } else { @($okfJson.aggregates) }
|
|
890
|
+
}
|
|
871
891
|
} catch { $okfAggs = @() }
|
|
872
892
|
}
|
|
873
893
|
|
|
@@ -901,7 +921,7 @@ function Invoke-ListFeatures {
|
|
|
901
921
|
}
|
|
902
922
|
}
|
|
903
923
|
}
|
|
904
|
-
# Merge OKF-discovered
|
|
924
|
+
# Merge the OKF-discovered FEATURES not already scaffolded under specs/.
|
|
905
925
|
foreach ($agg in $okfAggs) {
|
|
906
926
|
$nm = "$($agg.name)".Trim()
|
|
907
927
|
if (-not $nm -or $seen.ContainsKey($nm.ToLower())) { continue }
|
|
@@ -944,11 +964,11 @@ function Invoke-SetFeature {
|
|
|
944
964
|
}
|
|
945
965
|
if (-not $raw) { Write-Error 'set-feature: --feature is required'; exit 1 }
|
|
946
966
|
|
|
947
|
-
# Normalize to a PascalCase
|
|
967
|
+
# Normalize to a PascalCase identifier.
|
|
948
968
|
$cleaned = ($raw -replace '[^A-Za-z0-9]+', ' ').Trim()
|
|
949
969
|
$feature = ConvertTo-Pascal -In $cleaned
|
|
950
970
|
if ($feature -notmatch '^[A-Za-z][A-Za-z0-9]*$') {
|
|
951
|
-
Write-Error "set-feature: '$raw' is not a valid feature name (need a letter-led alphanumeric
|
|
971
|
+
Write-Error "set-feature: '$raw' is not a valid feature name (need a letter-led alphanumeric name, e.g. Order or PatientRegistration)"; exit 1
|
|
952
972
|
}
|
|
953
973
|
|
|
954
974
|
$repo = Get-RepoRoot
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
# aw-xref.ps1 concept-upsert --aggregate X [--source-files a,b,c] [--description D] [--origin ...] [--root DIR]
|
|
10
10
|
# aw-xref.ps1 concept-overview --aggregate X (--text PROSE | --text-file F) [--root DIR]
|
|
11
11
|
# aw-xref.ps1 concept-paths --aggregate X | --feature-dir <dir> [--root DIR] [--bundle knowledge]
|
|
12
|
+
# aw-xref.ps1 feature-aggregates --feature-dir <dir> [--root DIR]
|
|
13
|
+
# (the aggregate(s) a feature acts on: its spec's Acts-on line,
|
|
14
|
+
# then the boundary map, then the feature name itself)
|
|
12
15
|
$ErrorActionPreference = "Stop"
|
|
13
16
|
$core = Join-Path $PSScriptRoot "..\python\aw-xref.py"
|
|
14
17
|
& python3 $core @args
|