aw-spec-cli 0.1.18 → 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.
Files changed (77) hide show
  1. package/assets/.appweaver/DESIGN-FORMAT.md +63 -31
  2. package/assets/.appweaver/orchestrator/modules/delta-plan.orch.yaml +62 -14
  3. package/assets/.appweaver/orchestrator/modules/delta-spec.orch.yaml +10 -6
  4. package/assets/.appweaver/orchestrator/modules/lane-gate.orch.yaml +9 -1
  5. package/assets/.appweaver/orchestrator/modules/tech-select.orch.yaml +21 -0
  6. package/assets/.appweaver/orchestrator/orchestrator.py +23 -1
  7. package/assets/.appweaver/orchestrator/rev-templates/delta-design-template.skeleton.md +1 -1
  8. package/assets/.appweaver/orchestrator/rev-templates/design-template.skeleton.md +22 -13
  9. package/assets/.appweaver/orchestrator/rev-templates/ui-delta-design-template.skeleton.md +1 -1
  10. package/assets/.appweaver/orchestrator/rev-templates/ui-design-template.skeleton.md +6 -4
  11. package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +399 -131
  12. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan-delta.orch.yaml +1 -1
  13. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +199 -38
  14. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +1 -1
  15. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +159 -25
  16. package/assets/.appweaver/orchestrator/workflows/aw-spec-build.orch.yaml +20 -9
  17. package/assets/.appweaver/orchestrator/workflows/aw-spec-characterize.orch.yaml +1 -1
  18. package/assets/.appweaver/orchestrator/workflows/aw-spec-close-delta.orch.yaml +183 -40
  19. package/assets/.appweaver/orchestrator/workflows/aw-spec-deploy.orch.yaml +1 -1
  20. package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +311 -36
  21. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +221 -40
  22. package/assets/.appweaver/orchestrator/workflows/aw-spec-inventory.orch.yaml +217 -174
  23. package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +494 -109
  24. package/assets/.appweaver/orchestrator/workflows/aw-spec-parity.orch.yaml +1 -1
  25. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +83 -18
  26. package/assets/.appweaver/orchestrator/workflows/aw-spec-recall-delta.orch.yaml +319 -0
  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-specify.orch.yaml +143 -34
  30. package/assets/.appweaver/scripts/bash/aw-inventory.sh +15 -0
  31. package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +878 -51
  32. package/assets/.appweaver/scripts/bash/aw-xref.sh +3 -0
  33. package/assets/.appweaver/scripts/bash/common.sh +28 -1
  34. package/assets/.appweaver/scripts/powershell/aw-inventory.ps1 +15 -0
  35. package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +868 -51
  36. package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +3 -0
  37. package/assets/.appweaver/scripts/python/aw-inventory.py +1832 -65
  38. package/assets/.appweaver/scripts/python/aw-xref.py +500 -81
  39. package/assets/.appweaver/templates/delta-design-template.md +1 -1
  40. package/assets/.appweaver/templates/delta-domain-model-template.md +1 -1
  41. package/assets/.appweaver/templates/delta-spec-template.md +4 -3
  42. package/assets/.appweaver/templates/design-template.md +56 -40
  43. package/assets/.appweaver/templates/okf-concept-template.md +11 -1
  44. package/assets/.appweaver/templates/proposed-inventory-template.md +127 -0
  45. package/assets/.appweaver/templates/source-map-template.md +5 -3
  46. package/assets/.appweaver/templates/spec-template.md +24 -21
  47. package/assets/.appweaver/templates/ui-delta-design-template.md +1 -1
  48. package/assets/.appweaver/templates/ui-design-template.md +11 -7
  49. package/assets/foundations/dotnet/ModularMonolith/ModularMonolith.pack.txt +4489 -361
  50. package/assets/foundations/dotnet/VerticalSlice/VerticalSlice.pack.txt +2274 -125
  51. package/assets/guardrails/domain/healthcare/auditing.domain.md +3 -3
  52. package/assets/guardrails/domain/pharma/auditing.domain.md +3 -3
  53. package/assets/guardrails/technology/dotnet-webapi/api-versioning.md +1 -1
  54. package/assets/guardrails/technology/dotnet-webapi/auditing.md +1 -1
  55. package/assets/guardrails/technology/dotnet-webapi/authorization.md +1 -1
  56. package/assets/guardrails/technology/dotnet-webapi/clean-architecture.md +1 -1
  57. package/assets/guardrails/technology/dotnet-webapi/cqrs.md +1 -1
  58. package/assets/guardrails/technology/dotnet-webapi/data-classification.md +1 -1
  59. package/assets/guardrails/technology/dotnet-webapi/eventing.md +1 -1
  60. package/assets/guardrails/technology/dotnet-webapi/folder-structure.md +1 -1
  61. package/assets/guardrails/technology/dotnet-webapi/modular-monolith.md +1 -1
  62. package/assets/guardrails/technology/dotnet-webapi/persistence.md +1 -1
  63. package/assets/guardrails/technology/dotnet-webapi/vertical-slices.md +1 -1
  64. package/assets/guardrails/technology/dotnet-webapi/zero-trust.md +1 -1
  65. package/assets/guardrails/technology/nextjs/accessibility-implementation.md +1 -1
  66. package/assets/guardrails/technology/nextjs/api-integration.dotnet-webapi.md +1 -1
  67. package/assets/guardrails/technology/nextjs/api-integration.java-spring.md +1 -1
  68. package/assets/guardrails/technology/nextjs/auth-session.md +1 -1
  69. package/assets/guardrails/technology/nextjs/component-architecture.md +1 -1
  70. package/assets/guardrails/technology/nextjs/design-system.md +1 -1
  71. package/assets/guardrails/technology/nextjs/error-handling.md +1 -1
  72. package/assets/guardrails/technology/nextjs/forms-and-validation.md +1 -1
  73. package/assets/guardrails/technology/nextjs/i18n.md +1 -1
  74. package/assets/guardrails/technology/nextjs/performance-budgets.md +1 -1
  75. package/assets/guardrails/technology/nextjs/routing-navigation.md +1 -1
  76. package/assets/guardrails/technology/nextjs/state-management.md +1 -1
  77. package/package.json +1 -1
@@ -2,11 +2,21 @@
2
2
  # aw-spec-adopt (orchestrator) — the JUST-IN-TIME per-feature step of Brownfield DEV
3
3
  #
4
4
  # Develop an EXISTING feature by first reverse-engineering ONE aggregate / vertical
5
- # slice from its code into a living spec.md + aligned design.md (+ a source-map.md
6
- # sidecar), THEN reconciling this feature's evidence into the project governance that
7
- # /appweaver onboard established. Adopt is the inverse of implement: implement reads
8
- # spec+design and generates code; adopt reads code and generates spec+design — equally
9
- # a reasoning step, stamped `origin: reverse-engineered` with per-item confidence.
5
+ # slice from its code into a living spec.md, the bounded context's domain-model.md and
6
+ # aligned lane plans (+ a source-map.md sidecar), THEN reconciling this feature's evidence into the project governance that
7
+ # /appweaver onboard established. Adopt is the inverse of implement: implement reads the
8
+ # plans + the model and generates code; adopt reads code and generates the spec, the model
9
+ # and the plans — equally a reasoning step, stamped `origin: reverse-engineered` with
10
+ # per-item confidence.
11
+ #
12
+ # THE SPEC/MODEL SPLIT HOLDS ON THE REVERSE TOO (2026-09-14). The spec says what the slice
13
+ # DOES and cites BR-nnn by id; the bounded context's domain-model.md says what the domain IS
14
+ # and carries the aggregate root, its boundary, its entities and the TEXT of every invariant.
15
+ # Adopt used to write the model INTO the spec as Key Entities / Business Rules, which left a
16
+ # brownfield feature as the only kind the forward gates could not fully check: analyze-plan's
17
+ # check 4 has nothing to check without a model, and implement generated with no authority on
18
+ # the entity names it was using. The model is CONTEXT-level and shared, so a second adopted
19
+ # slice ADDS its aggregate section and never touches the ones already there.
10
20
  #
11
21
  # onboard → ADOPT <Feature> (JIT, one feature at a time) → [ forward / CR delta loop ] ← you are here
12
22
  #
@@ -14,12 +24,15 @@
14
24
  # develop, never the whole codebase (that scale is what onboard's shallow feature map
15
25
  # avoids). A NEW feature (no existing code) is NOT adopted — it is authored straight
16
26
  # through the forward pipeline (/appweaver specify → plan → implement), which
17
- # onboard already made brownfield-ready (the code-derived design template is the
27
+ # onboard already made brownfield-ready (the code-derived plan template is the
18
28
  # project default). So adopt = reverse an EXISTING feature; the forward pipeline = a
19
29
  # new one. Both then evolve through the CR delta loop.
20
30
  #
21
- # The reverse output is a NORMAL feature: spec.md + design.md are the very documents
22
- # the CR delta loop + forward pipeline consume unchanged. All brownfield provenance
31
+ # The reverse output is a NORMAL feature: spec.md, domain-model.md and backend-plan.md /
32
+ # frontend-plan.md are the very documents the CR delta loop + forward pipeline consume
33
+ # unchanged — under those names, so nothing downstream needs a brownfield special case.
34
+ # plan-scaffold's backend/frontend lanes also MIGRATE a legacy design.md / design.ui.md left
35
+ # by an adopt that ran before the rename, so an already-adopted repo is carried forward. All brownfield provenance
23
36
  # lives in `origin:` frontmatter + the source-map sidecar. Adopt inits
24
37
  # active_version.json (v1.0) so the CR loop works immediately after.
25
38
  #
@@ -56,7 +69,7 @@
56
69
  # =============================================================================
57
70
 
58
71
  name: aw-spec-adopt
59
- description: The JIT per-feature step of brownfield development — reverse-engineer ONE existing feature into a living spec.md + design.md (+ design.ui.md where a frontend lane is declared and this slice owns part of its surface) + source-map.md (origin reverse-engineered, per-item confidence, code-derived design template), then COMPLETE its shallow OKF concept (deepen it to the reversed reality + link the deep artifacts) and record its cross-slice edges. Fidelity gate is spec/design vs code; the review is reported for out-of-band closure. The governance reconcile — carrying this feature's real evidence into the provisional rule files + constitution onboard wrote — is HELD (reconcile_governance=false), so an ordinary run does not touch the governance tier. A NEW feature is authored via the forward pipeline instead. Output is a normal feature for the CR + forward loop.
72
+ description: The JIT per-feature step of brownfield development — reverse-engineer ONE existing feature into a living spec.md, an aggregate section in the bounded context's shared domain-model.md, and backend-plan.md (+ frontend-plan.md where a frontend lane is declared and this slice owns part of its surface) + source-map.md (origin reverse-engineered, per-item confidence, code-derived plan template), then COMPLETE its shallow OKF concept (deepen it to the reversed reality + link the deep artifacts) and record its cross-slice edges. The spec cites BR-nnn by id and the model defines them, exactly as on the greenfield path, so a reversed slice is checkable by the same gates as any other. Fidelity gate is spec + model + plans vs code, and an invented entity, boundary or invariant is a fabrication exactly as an invented FR- is; the review is reported for out-of-band closure. The governance reconcile — carrying this feature's real evidence into the provisional rule files + constitution onboard wrote — is HELD (reconcile_governance=false), so an ordinary run does not touch the governance tier. A NEW feature is authored via the forward pipeline instead. Output is a normal feature for the CR + forward loop.
60
73
  # 1.5.0 — user_input is now OPTIONAL: adopt CONSUMES the ACTIVE feature (select-feature's
61
74
  # .appweaver/feature.json) when no arg is passed, so onboard→select-feature→adopt needs
62
75
  # no re-typing; an explicit <Feature> still selects directly (and wins). One coalesced
@@ -66,7 +79,7 @@ description: The JIT per-feature step of brownfield development — reverse-engi
66
79
  # 1.3.0 — reverse design scaffolds from the DEFAULT `design-template` (onboard's code-derived
67
80
  # override) instead of the never-shipped `brownfield-design-template`, unifying the
68
81
  # whole brownfield pipeline on one code-derived template.
69
- version: 1.5.0
82
+ version: 2.0.0
70
83
  agent_agnostic: true
71
84
  dialect: aw-spec.orchestrator/v1
72
85
 
@@ -108,8 +121,22 @@ vars:
108
121
  ui_lane: "false" # THIS slice reverses a UI half: a frontend lane is declared AND the slice owns UI files
109
122
  ui_count: "0" # how many UI files the concept attributes to this aggregate (aw-inventory ui_source_files)
110
123
  ui_source_files: ""
111
- ui_design_doc: ""
124
+ frontend_plan: ""
112
125
  ui_tpl_source: ""
126
+ # THE LANE PLANS, under the names the forward pipeline reads. A reversed slice is a
127
+ # NORMAL feature, so it carries backend-plan.md / frontend-plan.md like any other —
128
+ # plan-scaffold's backend/frontend lanes also MIGRATE a legacy design.md / design.ui.md
129
+ # left by an earlier adopt, so a repo that was adopted before the rename is carried
130
+ # forward rather than left with two spellings of one master.
131
+ backend_plan: ""
132
+ # THE BOUNDED CONTEXT'S DOMAIN MODEL — shared by every feature in the context, and the
133
+ # authority on the aggregate root, its entities and the TEXT of every BR-nnn. Reversed
134
+ # here in the same run as the spec, exactly as greenfield `specify` authors the two
135
+ # together, so the model is never behind the spec that cites it.
136
+ context: ""
137
+ context_dir: ""
138
+ domain_model: ""
139
+ domain_model_exists: "false"
113
140
  spec_exists: "false"
114
141
  fidelity_ok: "false"
115
142
  rulemap_exists: "false" # governance-present signal (onboard ran) — bound from resolve-domain
@@ -162,7 +189,36 @@ workflow:
162
189
  action: verify_context
163
190
  require_non_empty: feature_name
164
191
  on_empty: stop
165
- on_empty_message: "No feature to adopt. Either run /appweaver select-feature first (pick the aggregate from the onboard feature map), then /appweaver adopt; or name it directly: /appweaver adopt <Feature>."
192
+ on_empty_message: "No feature to adopt. Either run /appweaver select-feature first (pick the feature from the boundary map onboard laid out), then /appweaver adopt; or name it directly: /appweaver adopt <Feature>."
193
+
194
+ # WHICH BOUNDED CONTEXT THIS FEATURE LANDS IN, and a refusal to guess — the same gate
195
+ # select-feature carries, and needed here for the same reason: this stage also creates a
196
+ # feature directory. scope_ok is false in exactly one state, contexts exist and none is
197
+ # selected, and in that state set-feature resolves the target under a bare specs/ where
198
+ # it is outside every context. Worse on this path than on the forward one, because an
199
+ # adopted name comes from the reversed code rather than from a menu: adopting an
200
+ # aggregate that happens to share a name with an empty bounded context would write that
201
+ # context's own spec.md and orphan every feature meant to live inside it. A repo with no
202
+ # contexts at all passes, which keeps the flat brownfield layout working untouched.
203
+ - id: context_scope
204
+ action: execute_script
205
+ script: { ref: actions }
206
+ subcommand: list-contexts
207
+ outputs:
208
+ - { name: active_context, from: "$.active" }
209
+ - { name: context_count, from: "$.count" }
210
+ - { name: scope_ok, from: "$.scope_ok" }
211
+ - { name: scope_message, from: "$.scope_message" }
212
+
213
+ - id: require_context_scope
214
+ action: verify_context
215
+ require_true: scope_ok
216
+ on_false: stop
217
+ on_false_message: >
218
+ {{scope_message}}
219
+ Adopting reverse-engineers one aggregate into a feature directory, so the context
220
+ that owns it has to be chosen before the directory is created — moving a feature
221
+ between contexts afterwards means re-specifying it in the new one.
166
222
 
167
223
  # Create-or-select the feature dir + persist it active (idempotent in the no-arg
168
224
  # path where it is already active; creates it in the explicit-arg path). set-feature
@@ -178,6 +234,26 @@ workflow:
178
234
  - { name: spec_file, from: "$.spec_file" }
179
235
  - { name: spec_exists, from: "$.spec_exists" }
180
236
 
237
+ # WHICH AGGREGATE THIS FEATURE ACTS ON. Usually the same name — one aggregate, one
238
+ # feature, the 1:1 default — but not when the boundary map split an aggregate into
239
+ # capabilities (PatientRegistration and PatientMerge over Patient). The reverse steps
240
+ # below write and judge the model section BY THIS NAME, and every later command finds
241
+ # the feature's part of the model the same way; writing it under the feature's name
242
+ # instead defines one root twice as soon as a sibling feature is adopted. No spec
243
+ # exists yet at this point, so it resolves from the confirmed boundary map, and a
244
+ # feature the map does not split resolves to its own name — exactly what this
245
+ # workflow always used.
246
+ - id: feature_aggregates
247
+ action: execute_script
248
+ script: { ref: xref }
249
+ subcommand: feature-aggregates
250
+ args: ["--feature-dir", "{{feature_dir}}"]
251
+ outputs:
252
+ - { name: aggregates_csv, from: "$.aggregates_csv" }
253
+ - { name: aggregate_sections, from: "$.sections" }
254
+ - { name: aggregate_source, from: "$.source" }
255
+ - { name: aggregate_primary, from: "$.primary" } # the concept this feature's OKF work lands on
256
+
181
257
  # 1a. One-time baseline: refuse to clobber an already-adopted / authored spec.
182
258
  # After adoption, changes go through the CR delta loop, not adopt.
183
259
  # set-feature returns spec_exists as a JSON boolean; the condition path
@@ -191,14 +267,16 @@ workflow:
191
267
  on_empty: stop
192
268
  on_empty_message: "specs/ already has a spec.md for {{feature_name}} — adopt is a one-time baseline. For a change to an adopted slice use the CR delta loop (/appweaver specify → plan → analyze-plan-delta → implement → build → analyze-slice-delta → finalize-delta → analyze-slice → close-delta); to re-adopt from scratch, remove the feature dir first."
193
269
 
194
- # 1b. Resolve this aggregate's OKF bundle concept → its source_files (the code
195
- # that IS the slice) + module dir. The INDEX adopt reverse-engineers FROM;
196
- # the code itself is the source of truth (read during authoring).
270
+ # 1b. Resolve the OKF concept of the aggregate this feature acts on → its source_files
271
+ # + module dir. The bundle is the PROJECT's knowledge and a concept is an aggregate,
272
+ # so a feature that is one capability over a shared aggregate reads the SHARED
273
+ # concept, which lists every capability's code; the source-map below narrows it to
274
+ # this feature's. The code itself is the source of truth (read during authoring).
197
275
  - id: concept
198
276
  action: execute_script
199
277
  script: { ref: inventory }
200
278
  subcommand: concept
201
- args: ["--bundle", "{{bundle_dir}}", "--aggregate", "{{feature_name}}"]
279
+ args: ["--bundle", "{{bundle_dir}}", "--aggregate", "{{aggregate_primary}}"]
202
280
  outputs:
203
281
  - { name: concept_exists, from: "$.exists" }
204
282
  - { name: concept_path, from: "$.concept_path" }
@@ -218,7 +296,7 @@ workflow:
218
296
  action: verify_context
219
297
  require_true: concept_exists
220
298
  on_false: stop
221
- on_false_message: "No feature '{{feature_name}}' in the onboard feature map under {{bundle_dir}}. If you have not onboarded, run /appweaver onboard first, then use the exact feature name from {{bundle_dir}}/aggregates/index.md. If '{{feature_name}}' is a NEW feature (no existing code), do NOT adopt it — author it through the forward pipeline (/appweaver specify → plan → implement), which onboard already made brownfield-ready."
299
+ on_false_message: "No concept for the aggregate '{{aggregate_primary}}' — the one feature '{{feature_name}}' acts on — in the onboard bundle under {{bundle_dir}}. If its bounded context has not been onboarded, run /appweaver onboard <Context> first; {{bundle_dir}}/aggregates/index.md lists each aggregate with the features that act on it. If '{{feature_name}}' is a NEW feature (no existing code), do NOT adopt it — author it through the forward pipeline (/appweaver specify → plan → implement), which onboard already made brownfield-ready."
222
300
 
223
301
  # Governance prerequisite: the reverse steps below are authored AGAINST the project
224
302
  # onboard established (the constitution halves + guardrails/projects/<project>/ rule set).
@@ -235,6 +313,16 @@ workflow:
235
313
  - { name: gov_domain, from: "$.domain" }
236
314
  - { name: rulemap_exists, from: "$.rulemap_exists" }
237
315
 
316
+ # ⌛ OPEN, now that onboard runs once per BOUNDED CONTEXT: this gate asks whether
317
+ # ANY context has been onboarded, and it needs to ask whether THIS FEATURE'S
318
+ # context has. The narrower question is already answered next door — require_concept
319
+ # stops when the feature has no OKF concept, and a feature only has one once its
320
+ # context was onboarded — so the sequence is safe today and the gap is in what the
321
+ # message says, not in what it lets through. Tightening it properly means resolving
322
+ # the feature's context from the concept's `bounded_context:` stamp and naming that
323
+ # context in the refusal. Related and unresolved: `fidelity_gate` is per feature and
324
+ # has no context-level equivalent — a context-wide one would fail a whole context on
325
+ # a single mismapped feature.
238
326
  - id: require_governance
239
327
  action: verify_context
240
328
  require_true: rulemap_exists
@@ -242,27 +330,57 @@ workflow:
242
330
  on_false_message: "No active governance for adopt to work against — run /appweaver onboard first. onboard establishes the PROJECT constitution + guardrails/projects/<project>/ rule set (provisional-from-scan) and activates it. Reversing '{{feature_name}}' without that scaffold would author the spec and design against no code-derived rules at all, and leave them with no governance home."
243
331
 
244
332
  # 2. Scaffold the living docs. The SPEC reuses the forward spec-template (it is
245
- # tech-agnostic WHAT/WHY — no architectural bias). The DESIGN scaffolds from the
333
+ # tech-agnostic WHAT/WHY — no architectural bias). The BACKEND PLAN scaffolds from the
246
334
  # DEFAULT `design-template` — which /appweaver onboard REPLACED with the repo's
247
335
  # code-derived override (resolve_template Priority-1, reverse-engineered from the
248
- # code), so the reversed design's structural sections follow the code's own
336
+ # code), so the reversed plan's structural sections follow the code's own
249
337
  # architecture rather than a fixed greenfield layering, describing the system
250
- # as-built. (Falls back to the greenfield base if onboard has not run.) Both are
338
+ # as-built. (Falls back to the greenfield base if onboard has not run.) The DOMAIN
339
+ # MODEL scaffolds into the bounded CONTEXT, not the feature — it is shared. All are
251
340
  # still normal forward docs (same identity + spine) that the CR delta loop consumes
252
341
  # unchanged. Then init active_version.json (v1.0).
342
+ #
343
+ # THE TEMPLATE IDENTIFIERS KEEP THE `design-template` SPELLING ON PURPOSE. The
344
+ # DOCUMENTS moved to the plan names; the template NAMES did not, because a repo that
345
+ # has already run onboard carries its code-derived override at
346
+ # .appweaver/templates/overrides/design-template.md. Renaming the identifier would
347
+ # make that file stop resolving and silently fall back to the kit's greenfield
348
+ # template — imposing a four-layer structure on a codebase that chose another, which
349
+ # is the exact failure require_ui_template below exists to prevent.
350
+ - id: context_paths
351
+ action: execute_script
352
+ script: { ref: actions }
353
+ subcommand: context-paths
354
+ outputs:
355
+ - { name: context, from: "$.context" }
356
+ - { name: context_dir, from: "$.context_dir" }
357
+ - { name: domain_model, from: "$.domain_model" }
358
+ - { name: domain_model_exists, from: "$.domain_model_exists" }
359
+
253
360
  - id: scaffold_spec
254
361
  action: execute_script
255
362
  script: { ref: actions }
256
363
  subcommand: scaffold
257
364
  args: ["--feature-dir", "{{feature_dir}}", "--allow-existing"]
258
365
 
259
- - id: scaffold_design
366
+ # Idempotent and non-destructive: a context whose model already exists (an earlier
367
+ # feature in it was adopted, or authored greenfield) keeps it, and the reverse below
368
+ # ADDS this aggregate's section rather than replacing the document.
369
+ - id: scaffold_model
370
+ action: execute_script
371
+ script: { ref: actions }
372
+ subcommand: model-scaffold
373
+ args: ["--feature-dir", "{{feature_dir}}"]
374
+ outputs:
375
+ - { name: domain_model, from: "$.domain_model" }
376
+
377
+ - id: scaffold_plan
260
378
  action: execute_script
261
379
  script: { ref: actions }
262
- subcommand: design-scaffold
263
- args: ["--feature-dir", "{{feature_dir}}"] # default `design-template` = onboard's code-derived override (greenfield base if onboard has not run)
380
+ subcommand: plan-scaffold
381
+ args: ["--feature-dir", "{{feature_dir}}", "--lane", "backend"] # `design-template` = onboard's code-derived override (greenfield base if onboard has not run)
264
382
  outputs:
265
- - { name: design_doc, from: "$.design_doc" }
383
+ - { name: backend_plan, from: "$.plan_doc" }
266
384
 
267
385
  - id: version_init
268
386
  action: execute_script
@@ -299,18 +417,18 @@ workflow:
299
417
  condition: "{{ui_count}} == 0"
300
418
  vars: { ui_lane: "false" }
301
419
 
302
- # Scaffold the LANE's design doc from ITS template pair (--lane ui resolves
303
- # ui-design-template -> design.ui.md), the twin of scaffold_design above. onboard
304
- # GENERATED that override from this repo's own UI source, so the reversed UI design
420
+ # Scaffold the LANE's plan from ITS template pair (--lane frontend resolves
421
+ # ui-design-template -> frontend-plan.md), the twin of scaffold_plan above. onboard
422
+ # GENERATED that override from this repo's own UI source, so the reversed frontend plan
305
423
  # follows the code's real composition axes rather than a fixed screen taxonomy.
306
- - id: scaffold_ui_design
424
+ - id: scaffold_ui_plan
307
425
  condition: "{{ui_lane}} == true"
308
426
  action: execute_script
309
427
  script: { ref: actions }
310
- subcommand: design-scaffold
311
- args: ["--feature-dir", "{{feature_dir}}", "--lane", "ui"]
428
+ subcommand: plan-scaffold
429
+ args: ["--feature-dir", "{{feature_dir}}", "--lane", "frontend"]
312
430
  outputs:
313
- - { name: ui_design_doc, from: "$.design_doc" }
431
+ - { name: frontend_plan, from: "$.plan_doc" }
314
432
  - { name: ui_tpl_source, from: "$.template_source" }
315
433
  - { name: ui_tpl_override, from: "$.override_active" }
316
434
 
@@ -365,7 +483,7 @@ workflow:
365
483
  output_target: { type: file, path: "{{spec_file}}" }
366
484
  task: |
367
485
  Reverse-engineer the MASTER specification (the target spec.md in context)
368
- for the aggregate '{{feature_name}}' FROM ITS CODE. The bundle concept (in
486
+ for the feature '{{feature_name}}' FROM ITS CODE. The bundle concept (in
369
487
  context) is the INDEX — its `source_files` list the code that IS this slice;
370
488
  READ those files (under {{module_dir}}, listed below) as the SOURCE OF TRUTH.
371
489
  The concept is a summary; the CODE is authoritative — if they disagree, the
@@ -373,17 +491,26 @@ workflow:
373
491
 
374
492
  Capture what the code ACTUALLY DOES, as-built (bugs/quirks included), in the
375
493
  template's vocabulary:
376
- - The feature IS the aggregate; set the Aggregate field, keep US-1.
494
+ - THIS FEATURE ACTS ON {{aggregates_csv}}. Write exactly that on the Domain Model
495
+ Reference's `**Acts on aggregate(s)**:` line, each name in backticks — every later
496
+ command finds this feature's part of the domain model by reading that line. The
497
+ feature is the capability scope the developer confirmed in the boundary map,
498
+ whatever it is named: its user stories are that capability's operations and no
499
+ others. Sharing a name with an aggregate does not make it that aggregate's only
500
+ feature — other features may act on the same aggregate. Keep US-1.
377
501
  - Each command / endpoint / use-case the code exposes is a USER STORY (US-n),
378
502
  never a separate feature. Derive its Given/When/Then from the code's paths.
379
503
  - System Requirements in EARS (THE SYSTEM SHALL / WHEN / IF…THEN / WHILE /
380
504
  WHERE), each atomic + testable, derived from observed behaviour.
381
- - Always-true invariants the code enforces → Business Rules (BR-n).
382
- - MODEL IN THE TEMPLATE'S OWN VOCABULARY: a ubiquitous language, the
383
- aggregate + its root, entities vs value objects, aggregate-owned
384
- invariants — as domain MODEL, never as the language's own types.
385
- Classify from what the code EXHIBITS. A distinction the code does not
386
- draw is not a gap to fill in: record the model as-built, however thin.
505
+ - Always-true invariants the code enforces get stable BR-nnn ids, CITED here and
506
+ DEFINED in the domain model — see the next bullet.
507
+ - THE DOMAIN MODEL IS NOT THIS DOCUMENT'S. Fill "### Domain Model Reference" with
508
+ the aggregate's NAME and the ids of the invariants this spec relies on
509
+ (BR-nnn), and nothing more. Do NOT write the ubiquitous language, the aggregate
510
+ boundary, the entity/value-object split, the lifecycle states, or the TEXT of any
511
+ invariant: all of that lives in the bounded context's domain-model.md, which the
512
+ NEXT step reverses out of this same code, and several specs act on one aggregate
513
+ so a per-spec copy drifts. Cite by id; never restate.
387
514
  - DOMAIN LIFT — where the CODE enforces them, lift into the three sections
388
515
  (cite the source file): Constraints & NFRs (CN-, e.g. retention /
389
516
  immutability / append-only audit the code implements), Data Classification
@@ -412,38 +539,118 @@ workflow:
412
539
  rule the code does not exhibit. Behaviour that is clearly present is
413
540
  "(certain)".
414
541
  - Reference the source-map: note that legacy-artefact ↔ requirement links
415
- live in `specs/{{feature_name}}/source-map.md` (authored next).
542
+ live in `{{feature_dir}}/source-map.md` (authored next).
416
543
  - This is v1.0: fill the Change History row; keep US-1's "Added: v1.0".
417
544
  - Business-facing + testable; NO implementation mechanics (an external interop
418
545
  OBLIGATION is an IC- requirement, never the adapter design).
419
546
 
420
547
  Source files to read (the slice's code): {{source_files}}
421
548
 
422
- # ═══════════ REVERSE DESIGN — from the reversed SPEC + CODE, AS-BUILT ═══════
549
+ # ══════════ REVERSE DOMAIN MODEL — the CONTEXT's structure, from the CODE ══════
550
+ # The greenfield split, applied to the reverse: the spec says what the system DOES, the
551
+ # bounded context's domain-model.md says what the domain IS — the aggregate root, its
552
+ # boundary, its entities and value objects, its lifecycle, and the TEXT of every BR-nnn
553
+ # the spec cites by id. Authored in the SAME run as the spec, exactly as `specify` does,
554
+ # so the model is never behind the spec that depends on it.
555
+ #
556
+ # THE MODEL IS THE CONTEXT'S, NOT THIS FEATURE'S. Every feature in the bounded context
557
+ # reads it, so adopting a second slice ADDS an aggregate section and must not touch the
558
+ # ones already there — a renumbered BR breaks every citation in every sibling spec,
559
+ # silently. The scaffold above is non-destructive for the same reason.
560
+ #
561
+ # THE CODE IS STILL THE ORACLE. No modelling rule file is loaded, for the reason the
562
+ # reversed spec loads none: a prescriptive standard states what a model OUGHT to be, and
563
+ # an anemic legacy slice must reverse as exactly that. A distinction the code does not
564
+ # draw is not a gap to fill in.
565
+ - id: ctx_reverse_model
566
+ action: build_context
567
+ context:
568
+ - { path: "{{domain_model}}" } # the model — template on the context's first slice, POPULATED on a later one
569
+ - { path: "{{spec_file}}" } # the reversed spec — the BR-nnn ids this model must define
570
+ - { path: "{{concept_path}}" } # source_files + seam
571
+ - { path: .appweaver/memory/constitution.domain.md, optional: true }
572
+ - id: reverse_model
573
+ action: agent_author
574
+ include_standards: false # the technical/arch sticky set is for the plans, never the model
575
+ tools: Read # the structure is reversed from the CODE, which arrives as a path list
576
+ output_target: { type: file, path: "{{domain_model}}" }
577
+ task: |
578
+ Reverse-engineer the BOUNDED CONTEXT's DOMAIN MODEL (the target domain-model.md in
579
+ context) for the aggregate(s) the feature '{{feature_name}}' acts on —
580
+ {{aggregates_csv}} — FROM ITS CODE. The reversed spec (in context) names them and
581
+ cites the invariant ids; this document DEFINES them. READ the source files listed
582
+ below — the code is the SOURCE OF TRUTH.
583
+
584
+ - THE SECTION IS NAMED AFTER THE AGGREGATE, NEVER THE FEATURE: {{aggregate_sections}},
585
+ exactly. Every later command finds this feature's part of the model by that
586
+ heading. Where the feature and the aggregate share a name the two are the same
587
+ thing; where they differ, a section headed '{{feature_name}}' is a defect — the
588
+ next feature over the same aggregate would define its root a second time.
589
+ - IF THAT SECTION ALREADY EXISTS, another feature over the same aggregate was adopted
590
+ first. EXTEND it and never write a second one: add `{{feature_name}}` to its
591
+ `Serving specs` line, and add only what THIS feature's code evidences that the
592
+ section lacks — an entity, a lifecycle state, a BR-nnn numbered after the highest
593
+ one already there. Leave what the earlier feature wrote exactly as it is.
594
+ - OTHERWISE ADD ONE `## Aggregate: <Name>` SECTION per aggregate, `{{feature_name}}`
595
+ on its `Serving specs` line: its boundary
596
+ (owns / references by id / out of scope), entities, value objects, domain events,
597
+ lifecycle states, and every always-true invariant the code enforces as a stable
598
+ BR-nnn with its RULE TEXT. The spec cites those ids and never restates them.
599
+ - IF THE MODEL ALREADY HAS CONTENT, this is the context's SECOND (or later) adopted
600
+ slice. ADD any of this feature's aggregates not already there as a new section and
601
+ EXTEND the ubiquitous language. Do NOT rewrite, renumber or "tidy" the aggregates
602
+ already there — other specs cite their ids, and a renumbered BR breaks every
603
+ citation silently. Add `{{feature_name}}` to the `Serving specs` line of any
604
+ existing aggregate this slice also acts on.
605
+ - MODEL WHAT THE CODE EXHIBITS, NOT WHAT GOOD DESIGN WOULD WANT. Classify entities
606
+ vs value objects by whether the code gives the thing an IDENTITY; draw the
607
+ boundary where the code actually draws it (what it loads and saves together).
608
+ A slice that is anemic, or models nothing at all, reverses as exactly that — an
609
+ aggregate with no invariants is a true finding, and the fidelity gate scores an
610
+ invented one as a fabrication. Where the code's own grouping is unclear, record
611
+ what it shows and mark the item "(inferred)".
612
+ - CROSS-AGGREGATE REFERENCES BY ID ONLY, never a navigable link — where the code
613
+ does hold a direct object reference across a boundary, record the reference by id
614
+ here and leave the coupling fact to the source-map's Seam section.
615
+ - Fill "## Cross-Aggregate Invariants" (XBR-n) with rules the code enforces ACROSS
616
+ two or more aggregates — they belong to no single spec, which is why they can only
617
+ live here. "None (as of v1.0)" when the code shows none; never delete the section.
618
+ - Fill "## Context Map" with this context's relationships to others where the code
619
+ evidences them, or "None (as of v1.0)". Never delete the section.
620
+ - TECHNOLOGY-AGNOSTIC. No types, no persistence, no framework, no layering. WHERE an
621
+ invariant is ENFORCED is a plan concern and belongs in backend-plan.md.
622
+ - Add `origin: reverse-engineered` near the identity block. Do NOT write the Context
623
+ version or the Change History version column — scripts stamp those.
624
+
625
+ Source files to read (the slice's code): {{source_files}}
626
+
627
+ # ═══════════ REVERSE BACKEND PLAN — from the SPEC + MODEL + CODE, AS-BUILT ═══
423
628
  # Anchored to the reversed spec's ids (§4 Traceability → forward-compatible),
424
- # grounded in the code's ACTUAL structure, tech-agnostic. NO domain files (rule
425
- # partition). The scaffolded design.md IS onboard's code-derived `design-template`
426
- # override — its §2 component sections follow the code's own folders/modules, not a
427
- # fixed layering.
428
- - id: ctx_reverse_design
629
+ # grounded in the code's ACTUAL structure, tech-agnostic. NO domain rule files (rule
630
+ # partition) — the domain reaches this plan as the reversed spec's ids and as the
631
+ # context's domain model, which is READ here and never written. The scaffolded
632
+ # backend-plan.md IS onboard's code-derived `design-template` override — its §2
633
+ # component sections follow the code's own folders/modules, not a fixed layering.
634
+ - id: ctx_reverse_plan
429
635
  action: build_context
430
636
  context:
431
- - { path: "{{design_doc}}" } # the scaffolded code-derived template = structure + embedded guidance
432
- - { path: "{{spec_file}}" } # the reversed spec = the ONLY domain input
637
+ - { path: "{{backend_plan}}" } # the scaffolded code-derived template = structure + embedded guidance
638
+ - { path: "{{spec_file}}" } # the reversed spec = WHAT the slice must do
639
+ - { path: "{{domain_model}}" } # the reversed model = what the domain IS — READ, never written here
433
640
  - { path: "{{concept_path}}" } # source_files + seam (structure hints)
434
641
  - { path: .appweaver/DESIGN-FORMAT.md } # the HLD contract (see its Brownfield variant note)
435
642
  - { path: "{{bundle_dir}}/architecture/overview.md", optional: true } # the as-built architecture (inventory) — cite, don't re-derive
436
643
  - { path: .appweaver/memory/constitution.technical.md, optional: true } # technology base ONLY — no domain
437
- - id: reverse_design
644
+ - id: reverse_plan
438
645
  action: agent_author
439
646
  include_standards: true # the technical/architecture sticky set (reference only — the CODE wins)
440
647
  tools: Read # grounded in the CODE, which arrives as {{source_files}} paths
441
- output_target: { type: file, path: "{{design_doc}}" }
648
+ output_target: { type: file, path: "{{backend_plan}}" }
442
649
  task: |
443
- Reverse-engineer the MASTER high-level design (the target design.md in
444
- context — it is the BROWNFIELD design template) — the tech-agnostic HLD that
445
- MEDIATES the reversed spec ↔ the existing code. Ground every element in the
446
- CODE (read the source files listed below) but ANCHOR each to a spec id from
650
+ Reverse-engineer the BACKEND LANE's master plan (the target backend-plan.md in
651
+ context — it is the BROWNFIELD plan template) — the tech-agnostic HLD that
652
+ MEDIATES the reversed spec + domain model ↔ the existing code. Ground every element
653
+ in the CODE (read the source files listed below) but ANCHOR each to a spec id from
447
654
  the reversed spec (in context) so §4 Traceability is complete and the doc is
448
655
  forward-compatible. Guidance: {{feature_name}}
449
656
 
@@ -451,10 +658,12 @@ workflow:
451
658
  it toward any reference architecture; the technical standards in context are a
452
659
  vocabulary to NAME what you find, never a target to bend the code toward.
453
660
  Rules:
454
- - THE SPEC IS THE ONLY DOMAIN INPUT. You have the reversed spec + the code's
455
- actual STRUCTURE — and NO domain rule files. Realise the spec ids; introduce
456
- no domain rule the spec omits. A concept the code exhibits but the spec lacks
457
- → surface it (it belongs in the spec) and record a §5 DA-n; do not smuggle it in.
661
+ - THE SPEC AND THE MODEL ARE THE ONLY DOMAIN INPUTS. You have the reversed spec,
662
+ the reversed domain model and the code's actual STRUCTURE — and NO domain rule
663
+ files. Realise the spec ids; introduce no domain rule neither document states. A
664
+ concept the code exhibits but both lack → surface it (it belongs in the spec or
665
+ the model) and record a §5 DA-n; do not smuggle it in. DO NOT EDIT
666
+ domain-model.md — it is spec-tier and read-only here.
458
667
  - STRUCTURE FOLLOWS THE CODE. Fill §1.1 As-Built Architecture from the code's
459
668
  real shape (its folders / projects / modules — name the style, don't prescribe
460
669
  one). Then create ONE §2 subsection per structural axis the slice's code
@@ -464,11 +673,14 @@ workflow:
464
673
  fits no textbook layer (a UI tier, a worker, a shared library) gets its own
465
674
  section; a layer the code lacks does NOT appear.
466
675
  - KEEP THE NEUTRAL ANCHORS (the template pins these): §1.2 is the domain/data
467
- MODEL and MUST match the spec's Key Entities / Business Rules (same names,
468
- cite BR-/CN-) with cross-slice References-by-id kept here; one §2 subsection
469
- is the EXTERNAL INTEGRATIONS table (heading contains "Integration") realising
470
- the spec's IC-. Realise CN- (structural → §1.2 invariants/events; retention/
471
- audit → §2 persistence / §3 cross-cutting), DC- (§3 + projection surface).
676
+ MODEL and MUST match the CONTEXT'S DOMAIN MODEL exactly — the same entity and
677
+ value-object NAMES, the same boundary, the same lifecycle states — with
678
+ cross-slice References-by-id kept here. Every BR-nnn the model defines gets a
679
+ named ENFORCEMENT POINT in this plan: cite it by id and NEVER restate what it
680
+ says, because the model is the only place that text lives. One §2 subsection is
681
+ the EXTERNAL INTEGRATIONS table (heading contains "Integration") realising the
682
+ spec's IC-. Realise CN- (structural → §1.2 invariants/events; retention/audit →
683
+ §2 persistence / §3 cross-cutting), DC- (§3 + projection surface).
472
684
  - AS-BUILT DEBT, not architecture drift. §6 records ONLY debt the CODE ITSELF
473
685
  evidences — stubs/no-ops, dead or duplicate code, torn seams (shared table/
474
686
  transaction), TODOs, hard-coded values. The code being organised differently
@@ -486,28 +698,29 @@ workflow:
486
698
 
487
699
  # ═══════ REVERSE UI DESIGN — the LANE's half, from the UI code, AS-BUILT ═══
488
700
  # Runs only where this slice HAS a surface (ui_lane). The service design above is
489
- # NOT its parent: design.md realises the spec's FR/BR/CN-, design.ui.md realises the
490
- # spec's UX- and the user stories' interaction, and it CONSUMES design.md's contracts
491
- # rather than restating them. Both are in context for exactly that reason.
492
- - id: ctx_reverse_ui_design
701
+ # NOT its parent: backend-plan.md realises the spec's FR/BR/CN-, frontend-plan.md
702
+ # realises the spec's UX- and the user stories' interaction, and it CONSUMES the backend
703
+ # plan's contracts rather than restating them. Both are in context for exactly that reason.
704
+ - id: ctx_reverse_ui_plan
493
705
  condition: "{{ui_lane}} == true"
494
706
  action: build_context
495
707
  context:
496
- - { path: "{{ui_design_doc}}" } # the scaffolded code-derived lane template = structure + embedded guidance
708
+ - { path: "{{frontend_plan}}" } # the scaffolded code-derived lane template = structure + embedded guidance
497
709
  - { path: "{{spec_file}}" } # the reversed spec — UX- + the user stories this surface serves
498
- - { path: "{{design_doc}}" } # the service design — the contracts the UI consumes, never re-specified here
710
+ - { path: "{{backend_plan}}" } # the backend plan — the contracts the UI consumes, never re-specified here
711
+ - { path: "{{domain_model}}", optional: true } # the ubiquitous language labels and states must use — READ, never written
499
712
  - { path: .appweaver/memory/constitution.ui.md, optional: true } # the lane's UX-* base, reversed by onboard from this same UI
500
713
  - { path: "{{bundle_dir}}/architecture/frontend.md", optional: true } # the as-built UI architecture + the SHARED surface no slice owns
501
- - id: reverse_ui_design
714
+ - id: reverse_ui_plan
502
715
  condition: "{{ui_lane}} == true"
503
716
  action: agent_author
504
717
  include_standards: true # the lane's sticky technical set (reference only — the CODE wins)
505
718
  tools: Read # same, for the lane's surface ({{ui_source_files}})
506
- output_target: { type: file, path: "{{ui_design_doc}}" }
719
+ output_target: { type: file, path: "{{frontend_plan}}" }
507
720
  task: |
508
- Reverse-engineer the FRONTEND LANE's high-level design (the target design.ui.md
509
- in context — it is this project's code-derived UI design template) for the
510
- aggregate '{{feature_name}}', FROM ITS UI CODE. Fill the template; preserve its
721
+ Reverse-engineer the FRONTEND LANE's master plan (the target frontend-plan.md
722
+ in context — it is this project's code-derived UI plan template) for the
723
+ feature '{{feature_name}}', FROM ITS UI CODE. Fill the template; preserve its
511
724
  section order.
512
725
 
513
726
  THE UI CODE IS THE SOURCE OF TRUTH — describe the surface AS-BUILT, quirks
@@ -516,9 +729,11 @@ workflow:
516
729
  surface with no loading states is recorded as having none, NEVER as needing them.
517
730
  Rules:
518
731
  - STAY IN THE LANE. Realise the spec's Experience Standards (UX-) and the
519
- interaction its User Stories imply. The service design (in context) supplies
732
+ interaction its User Stories imply. The backend plan (in context) supplies
520
733
  the CONTRACTS this surface consumes — cite them; do NOT restate, re-derive or
521
- contradict them, and introduce no server-side behaviour here.
734
+ contradict them, and introduce no server-side behaviour here. Where the domain
735
+ model is in context, use its UBIQUITOUS LANGUAGE exactly in labels, states and
736
+ messages, and cite BR-nnn rather than restating a rule.
522
737
  - STRUCTURE FOLLOWS THE UI CODE. Fill each section from the lane's real shape —
523
738
  its actual routes and screens, its own composition axes (however it groups
524
739
  components), where client state and server data-fetching really live, its
@@ -543,8 +758,8 @@ workflow:
543
758
  context:
544
759
  - { path: .appweaver/templates/source-map-template.md } # the sidecar structure
545
760
  - { path: "{{spec_file}}" }
546
- - { path: "{{design_doc}}" }
547
- - { path: "{{ui_design_doc}}", optional: true } # the lane's design, where this slice has one
761
+ - { path: "{{backend_plan}}" }
762
+ - { path: "{{frontend_plan}}", optional: true } # the lane's design, where this slice has one
548
763
  - { path: "{{concept_path}}" } # both file lists to map
549
764
  - id: source_map
550
765
  action: agent_author
@@ -553,15 +768,21 @@ workflow:
553
768
  output_target: { type: file, path: "{{feature_dir}}/source-map.md" }
554
769
  task: |
555
770
  Author the source-map sidecar (the target in context) from the template,
556
- for aggregate '{{feature_name}}'. It is the SOURCE ANCHOR of the traceability
557
- spine — it keeps spec.md / design.md clean while recording provenance here.
771
+ for the feature '{{feature_name}}'. It is the SOURCE ANCHOR of the traceability
772
+ spine — it keeps spec.md / backend-plan.md clean while recording provenance here.
558
773
  - One row per legacy artefact in the bundle concept's `source_files` (read
559
774
  them under {{module_dir}}): map each to the spec ids (FR/BR/US/CN/DC/IC)
560
- and the design element (§/name) that now describe it, with CONFIDENCE
775
+ and the backend-plan element (§/name) that now describe it, with CONFIDENCE
561
776
  (certain | inferred). EVERY source file MUST appear — the Fidelity gate
562
777
  checks full coverage both ways.
778
+ - THE CONCEPT IS THE AGGREGATE'S ('{{aggregate_primary}}') and lists every feature's
779
+ code on it. Where its `features:` names features besides '{{feature_name}}', a
780
+ file that serves ONLY one of those sibling capabilities is not this feature's:
781
+ record it under Unmapped / open as `sibling: <Feature>`, never as a mapped row.
782
+ The mapped rows ARE this feature's file list — later commands hand exactly those
783
+ files to their fix loops.
563
784
  - Where this slice has a UI half, its files map the same way, to the UX-/US-
564
- they realise and the design.ui.md section (§/name) that describes them —
785
+ they realise and the frontend-plan.md section (§/name) that describes them —
565
786
  one spine, both lanes. UI files to map: {{ui_source_files}}
566
787
  - Also map DB tables, endpoints, and background jobs the slice owns.
567
788
  - Fill the Seam section: the boundary this slice owns (endpoint / table
@@ -573,35 +794,58 @@ workflow:
573
794
  - Baseline commit = the inventory HEAD.
574
795
  Source files to map: {{source_files}}
575
796
 
576
- # ═══════════════ FIDELITY — does spec+design describe the CODE? ═════════════
577
- # spec/design ↔ CODE (NOT spec ↔ design). The agent can READ the source files.
578
- # BLOCKING gaps = unmapped code / fabricated spec-or-design (structural). The
579
- # rest (inferred items, quirks, missing-invariant candidates) are REVIEW items
580
- # → written to an Adopt Review report and closed OUT OF BAND (Brownfield B1·R).
797
+ # ═══════ FIDELITY — do spec + model + plans describe the CODE? ══════════════
798
+ # documents ↔ CODE (NOT document ↔ document). The agent can READ the source files.
799
+ # BLOCKING gaps = unmapped code / a fabricated spec id, model element or plan element
800
+ # (structural). The rest (inferred items, quirks, missing-invariant candidates) are
801
+ # REVIEW items → written to an Adopt Review report and closed OUT OF BAND (Brownfield
802
+ # B1·R).
803
+ #
804
+ # THE MODEL IS JUDGED THE SAME WAY AS EVERYTHING ELSE, and it is the document most
805
+ # likely to be embellished: a reverse that "tidies" an anemic legacy slice into a clean
806
+ # aggregate produces a model the code does not have, which every later plan and slice
807
+ # gate would then be checked against. An invented entity, boundary or invariant is a
808
+ # fabrication exactly as an invented FR- is.
581
809
  - id: fidelity
582
810
  action: agent_query
583
811
  context:
584
812
  - "{{spec_file}}"
585
- - "{{design_doc}}"
586
- - { path: "{{ui_design_doc}}", optional: true } # the lane's design, where this slice has one
813
+ - "{{domain_model}}"
814
+ - "{{backend_plan}}"
815
+ - { path: "{{frontend_plan}}", optional: true } # the lane's plan, where this slice has one
587
816
  - "{{feature_dir}}/source-map.md"
588
817
  - "{{concept_path}}"
589
818
  include_standards: false
590
- tools: Read # the check is spec/design vs CODE; with no code it degrades to spec vs design and certifies fabrication as ok
591
- schema: { ok: true, report_needed: false, blocking_gaps: [ { severity: "high", artefact: "<source file / spec id / design element>", kind: "unmapped-code | fabricated-spec | fabricated-design", detail: "<why>" } ], review_items: [ { kind: "inferred | as-built-quirk | missing-invariant | confidence", where: "<spec/design/source-map location>", note: "<what to confirm out of band>" } ] }
819
+ tools: Read # the check is documents vs CODE; with no code it degrades to doc vs doc and certifies fabrication as ok
820
+ schema: { ok: true, report_needed: false, blocking_gaps: [ { severity: "high", artefact: "<source file / spec id / model element / plan element>", kind: "unmapped-code | fabricated-spec | fabricated-model | fabricated-plan", detail: "<why>" } ], review_items: [ { kind: "inferred | as-built-quirk | missing-invariant | confidence", where: "<spec/model/plan/source-map location>", note: "<what to confirm out of band>" } ] }
592
821
  mock: { ok: true, report_needed: false, blocking_gaps: [], review_items: [] }
593
822
  task: |
594
823
  FIDELITY CHECK — do the reversed documents actually describe the CODE
595
- (spec/design ↔ CODE, NOT spec ↔ design)? Use the source-map (in context) and
824
+ (documents ↔ CODE, NOT document ↔ document)? Use the source-map (in context) and
596
825
  READ the slice's source files (the concept's `source_files`, under
597
- {{module_dir}}). Where design.ui.md is in context, it is judged the same way
826
+ {{module_dir}}). Where frontend-plan.md is in context, it is judged the same way
598
827
  against the slice's UI files ({{ui_source_files}}) — a screen, state store or
599
- route it describes that the UI code does not have is a fabricated-design, and a
828
+ route it describes that the UI code does not have is a fabricated-plan, and a
600
829
  UI file with no home in the source-map is unmapped-code. Classify findings:
601
830
  - BLOCKING (structural — set ok=false if any):
602
- (1) unmapped-code: a source file with NO spec/design home in the source-map;
603
- (2) fabricated-spec / fabricated-design: a spec id or design element with NO
604
- basis in the code (reverse-engineering must not invent behaviour).
831
+ (1) unmapped-code: a source file with NO home in the source-map — one recorded
832
+ there as `sibling: <Feature>` has a home;
833
+ (2) fabricated-spec / fabricated-model / fabricated-plan: a spec id, model
834
+ element or plan element with NO basis in the code (reverse-engineering must
835
+ not invent behaviour or structure). For the MODEL specifically: an entity or
836
+ value object the code does not have, an aggregate boundary it does not draw,
837
+ a lifecycle state it never enters, or a BR-nnn whose rule nothing enforces.
838
+ An anemic slice reversing as an anemic model is CORRECT, not a gap.
839
+ - ONLY THIS FEATURE'S SECTIONS ARE THIS RUN'S. domain-model.md is the CONTEXT's and
840
+ may already carry sections from earlier adopted slices — judge ONLY
841
+ {{aggregate_sections}}. Do not report a sibling aggregate as unmapped because this
842
+ slice's code does not contain it. Where one of those sections' `Serving specs`
843
+ lists an EARLIER feature as well, the aggregate is shared: judge what this run
844
+ ADDED to it, and do not score the earlier feature's entities, states or
845
+ invariants as fabricated because this slice's code does not touch them.
846
+ - ALSO BLOCKING (fabricated-model): a `## Aggregate:` section this run created under
847
+ a name that is not one of {{aggregates_csv}} — most often '{{feature_name}}', the
848
+ feature's own name, used as if it were the aggregate's.
605
849
  - REVIEW (does NOT block ok — these are the Adopt review worklist, closed out
606
850
  of band): items marked "(inferred)"; as-built quirks the code shows but the
607
851
  spec under-states; invariants the code enforces that may be missing/weak;
@@ -620,10 +864,10 @@ workflow:
620
864
  condition: "{{report_needed}} == true"
621
865
  action: agent_author
622
866
  include_standards: false
623
- context: ["{{spec_file}}", "{{design_doc}}", "{{feature_dir}}/source-map.md"]
867
+ context: ["{{spec_file}}", "{{domain_model}}", "{{backend_plan}}", "{{feature_dir}}/source-map.md"]
624
868
  output_target: { type: file, path: "{{feature_dir}}/adopt-review-report.md" }
625
869
  task: |
626
- Write an Adopt Review report (the target) for aggregate '{{feature_name}}' —
870
+ Write an Adopt Review report (the target) for the feature '{{feature_name}}' (acting on {{aggregates_csv}}) —
627
871
  the out-of-band worklist for this reverse-engineered baseline. Markdown:
628
872
  - "# Adopt Review — {{feature_name}}" + today's date + "origin: reverse-engineered".
629
873
  - "verdict:" line — "BLOCKING ✗ · <n>" if blocking gaps exist, else "REVIEW ○ · <m> item(s)".
@@ -637,22 +881,23 @@ workflow:
637
881
  spec.md (a resolved [NEEDS CLARIFICATION] / Assumption) or log a deviation.
638
882
  Review items: {{review_items}}
639
883
  - "## How to proceed": resolve blocking gaps first; then the slice is a normal
640
- feature — run /appweaver analyze-plan (spec↔design gate) if desired, then
641
- drive every change through the CR delta loop. If a change edits untested legacy
642
- behaviour with blast radius, characterize just that surface first (optional
643
- on-touch fence) — it is not a required phase.
884
+ feature — run /appweaver analyze-plan (the spec↔plan gate, which also checks both
885
+ plans against the context's domain model) if desired, then drive every change
886
+ through the CR delta loop. If a change edits untested legacy behaviour with blast
887
+ radius, characterize just that surface first (optional on-touch fence) — it is
888
+ not a required phase.
644
889
  Return the report markdown only.
645
890
 
646
891
  - id: fidelity_gate
647
892
  action: verify_context
648
893
  require_true: fidelity_ok
649
894
  on_false: stop
650
- on_false_message: "Fidelity FAILED for {{feature_name}} — the reversed spec/design do not match the code (blocking gaps: {{blocking_gaps}}). See specs/{{feature_name}}/adopt-review-report.md. Fix the unmapped code / remove fabricated items (re-read the source, refine spec.md + design.md + source-map.md), then re-run /appweaver adopt (after removing the dir) or resolve out of band."
895
+ on_false_message: "Fidelity FAILED for {{feature_name}} — the reversed documents do not match the code (blocking gaps: {{blocking_gaps}}). See {{feature_dir}}/adopt-review-report.md. Fix the unmapped code / remove fabricated items (re-read the source, refine spec.md, {{domain_model}}, backend-plan.md and source-map.md), then re-run /appweaver adopt (after removing the dir) or resolve out of band. A `fabricated-model` gap means the reverse gave this slice structure its code does not have — an entity, a boundary, a lifecycle state or a BR-nnn nothing enforces; the fix is to record the domain as thin as the code really is, never to leave it flattering."
651
896
 
652
897
  # ═══════════ OKF CONCEPT — COMPLETE the shallow scan concept (deep reverse) ═══
653
898
  # onboard wrote this aggregate's OKF concept SHALLOW (a discovery stub — enough to pick the
654
- # feature). Now that the deep reverse produced a TRUSTWORTHY spec.md + design.md +
655
- # source-map.md, COMPLETE the concept: deepen its detail to the reversed reality, LINK the
899
+ # feature). Now that the deep reverse produced a TRUSTWORTHY spec.md + domain-model.md +
900
+ # backend-plan.md + source-map.md, COMPLETE the concept: deepen its detail to the reversed reality, LINK the
656
901
  # authored artifacts, refresh source_files, and mark it ADOPTED. RECONCILE, not rewrite —
657
902
  # keep OKF format + the bundle links; do NOT touch the Depends-On / Cross-References
658
903
  # sections (xref_record owns those, next). Runs only after fidelity passes (deepened from
@@ -663,7 +908,8 @@ workflow:
663
908
  context:
664
909
  - { path: "{{concept_path}}" } # the shallow onboard concept to complete
665
910
  - { path: "{{spec_file}}" } # the deep reversed spec (entities, US, BR/CN/DC/IC)
666
- - { path: "{{design_doc}}" } # the deep reversed design (components, ports, integrations)
911
+ - { path: "{{domain_model}}" } # the deep reversed model (entities, boundary, invariants)
912
+ - { path: "{{backend_plan}}" } # the deep reversed plan (components, ports, integrations)
667
913
  - { path: "{{feature_dir}}/source-map.md" } # the source anchor (files, seam, quirks)
668
914
  - { path: .appweaver/templates/okf-concept-template.md } # OKF v0.1 format authority
669
915
  - id: complete_concept
@@ -671,42 +917,57 @@ workflow:
671
917
  tools: "Read,Write,Edit"
672
918
  permission_mode: acceptEdits
673
919
  task: |
674
- COMPLETE the OKF aggregate concept for '{{feature_name}}' at {{concept_path}} — onboard
675
- wrote it SHALLOW (a discovery stub from the whole-repo scan). The deep reverse has now
676
- produced a TRUSTWORTHY spec.md + design.md + source-map.md (in context); deepen the
920
+ COMPLETE the OKF concept of the aggregate '{{aggregate_primary}}' at {{concept_path}} —
921
+ the aggregate this run adopted the feature '{{feature_name}}' over. onboard wrote it
922
+ SHALLOW (a discovery stub from the whole-repo scan). The deep reverse has now
923
+ produced a TRUSTWORTHY spec.md + domain-model.md + backend-plan.md + source-map.md (in
924
+ context); deepen the
677
925
  concept to match and LINK those artifacts. Follow the OKF concept template in context.
926
+ THE CONCEPT IS THE AGGREGATE'S, NOT THE FEATURE'S. Where its `features:` names more
927
+ than one feature, another feature's adopt may already have deepened it, and nothing
928
+ that run wrote is this run's to remove — reconcile ADDITIVELY.
678
929
  RECONCILE, do NOT rewrite:
679
930
  - DEEPEN the responsibilities + the owned entities / value objects / domain events /
680
- commands (user stories) / API endpoints / persistence to match the reversed spec +
681
- design — correct any drift the shallow scan got wrong (the deep docs win).
682
- - LINK the deep artifacts: add bundle links to specs/{{feature_name}}/spec.md, design.md,
683
- and source-map.md so the concept points at the deep truth.
684
- - Refresh `source_files` from the source-map (grouped by the code's OWN folders, per
685
- OKF), and set `timestamp` to the baseline (inventory HEAD) commit time.
686
- - Update frontmatter: keep `origin: reverse-engineered`; mark the concept ADOPTED /
687
- deep-reversed (no longer a shallow scan stub) and stamp `generated_by: aw-spec-adopt`.
931
+ commands (user stories) / API endpoints / persistence to match the reversed spec,
932
+ model and plan — the ENTITIES and VALUE OBJECTS come from domain-model.md, which is
933
+ the authority on them; correct any drift the shallow scan got wrong (the deep docs
934
+ win). A sibling capability's commands and endpoints stay where they are.
935
+ - LINK the deep artifacts: add bundle links to {{feature_dir}}/spec.md,
936
+ backend-plan.md, source-map.md and the context's domain-model.md so the concept
937
+ points at the deep truth. Keep every link another feature's adopt added.
938
+ - UNION `source_files` with the source-map's mapped files (grouped by the code's OWN
939
+ folders, per OKF) — never drop a file already listed, which may be a sibling
940
+ capability's — and set `timestamp` to the baseline (inventory HEAD) commit time.
941
+ - Update frontmatter: keep `origin: reverse-engineered`; make sure `features:`
942
+ includes '{{feature_name}}'; stamp `generated_by: aw-spec-adopt`; and record that
943
+ '{{feature_name}}' is ADOPTED / deep-reversed. Where several features act on the
944
+ aggregate, record WHICH of them are adopted rather than marking the whole concept
945
+ done — the others are still shallow.
688
946
  - Resolve any `open_questions` the deep reverse answered; keep the rest.
689
947
  - Do NOT touch the concept's Depends-On / Cross-References sections (xref_record
690
948
  maintains those next), and do NOT edit any OTHER aggregate concept.
691
- Append a one-line `log.md` entry (bundle root) under today's date noting '{{feature_name}}'
692
- was deep-adopted. Write ONLY {{concept_path}} (+ its dir index.md / bundle log.md if a
949
+ Append a one-line `log.md` entry (bundle root) under today's date noting the feature
950
+ '{{feature_name}}' of '{{aggregate_primary}}' was deep-adopted. Write ONLY {{concept_path}} (+ its dir index.md / bundle log.md if a
693
951
  link or description changed). Report what you deepened.
694
952
  outputs:
695
953
  - { name: concept_completed, from: "$.files" }
696
954
 
697
- # 3b. OKF cross-references (brownfield seed). The reversed design.md now names this
955
+ # 3b. OKF cross-references (brownfield seed). The reversed backend-plan.md now names this
698
956
  # slice's cross-aggregate references (§1 References-by-id) + integration contracts
699
957
  # (its External integrations table, IC-); record them into the shared OKF bundle — a
700
958
  # Cross-References row in every
701
959
  # SIBLING aggregate this slice uses (their inventory concepts already exist, so this
702
- # resolves them rather than stubbing), plus this slice's own Depends-On. Same
960
+ # resolves them rather than stubbing), plus the Depends-On of the aggregate this
961
+ # feature acts on — the union over every feature on that aggregate, so adopting one
962
+ # capability never erases another's edges. Keyed by --feature-dir so record resolves
963
+ # the aggregate itself rather than being told a feature name. Same
703
964
  # reconcile-on-write helper as forward implement, stamped origin reverse-engineered,
704
965
  # additive to the inventory bundle. Runs only after fidelity passes.
705
966
  - id: xref_record
706
967
  action: execute_script
707
968
  script: { ref: xref }
708
969
  subcommand: record
709
- args: ["--using", "{{feature_name}}", "--design", "{{design_doc}}", "--bundle", "{{bundle_dir}}", "--origin", "reverse-engineered"]
970
+ args: ["--feature-dir", "{{feature_dir}}", "--design", "{{backend_plan}}", "--bundle", "{{bundle_dir}}", "--origin", "reverse-engineered"]
710
971
  outputs:
711
972
  - { name: xref_wrote, from: "$.wrote" }
712
973
  - { name: xref_edge_count, from: "$.edge_count" }
@@ -738,7 +999,7 @@ workflow:
738
999
  action: build_context
739
1000
  context:
740
1001
  - { path: "{{spec_file}}" } # the feature's real BR-/CN-/DC-/IC-
741
- - { path: "{{design_doc}}" }
1002
+ - { path: "{{backend_plan}}" }
742
1003
  - { path: "{{feature_dir}}/source-map.md" }
743
1004
  - { path: "guardrails/projects/{{gov_domain}}/domain-rule-bindings.md", optional: true }
744
1005
  - { path: .appweaver/RULE-PARTITION.md } # the rule-partition authority (which command reads what)
@@ -751,7 +1012,7 @@ workflow:
751
1012
  RECONCILE the domain rule-file set in guardrails/projects/{{gov_domain}}/domain/ with the
752
1013
  newly-adopted feature '{{feature_name}}'. onboard authored these rule files from the
753
1014
  whole-repo scan with per-feature specifics marked `PROVISIONAL — reconciled at
754
- adopt`. READ this feature's reversed spec.md + design.md + source-map.md (in
1015
+ adopt`. READ this feature's reversed spec.md + backend-plan.md + source-map.md (in
755
1016
  context — the real evidence) and the EXISTING rule files under
756
1017
  guardrails/projects/{{gov_domain}}/.
757
1018
 
@@ -780,7 +1041,7 @@ workflow:
780
1041
  action: build_context
781
1042
  context:
782
1043
  - { path: "{{constitution_technical_file}}" }
783
- - { path: "{{design_doc}}" }
1044
+ - { path: "{{backend_plan}}" }
784
1045
  - { path: "{{feature_dir}}/source-map.md" }
785
1046
  - id: reconcile_technical
786
1047
  condition: "{{reconcile_governance}} == true" # HELD — governance reconcile paused (see vars)
@@ -790,7 +1051,7 @@ workflow:
790
1051
  task: |
791
1052
  RECONCILE the TECHNICAL constitution (target in context) with the adopted feature
792
1053
  '{{feature_name}}'. onboard discovered the technology principle SET from the scan and
793
- marked per-feature code coordinates `PROVISIONAL`. READ this feature's design.md +
1054
+ marked per-feature code coordinates `PROVISIONAL`. READ this feature's backend-plan.md +
794
1055
  source-map.md (in context — the code-coordinate bridge: real file paths / type names).
795
1056
 
796
1057
  RECONCILE, do NOT re-author:
@@ -921,12 +1182,19 @@ workflow:
921
1182
  outputs:
922
1183
  feature_dir: "{{feature_dir}}"
923
1184
  spec_file: "{{spec_file}}"
924
- design_doc: "{{design_doc}}"
925
- ui_design_doc: "{{ui_design_doc}}" # empty where this slice has no UI half
1185
+ backend_plan: "{{backend_plan}}"
1186
+ frontend_plan: "{{frontend_plan}}" # empty where this slice has no UI half
1187
+ domain_model: "{{domain_model}}" # the CONTEXT's, shared — this run ADDED an aggregate section to it
1188
+ bounded_context: "{{context}}"
926
1189
  ui_lane: "{{ui_lane}}"
927
1190
  ui_count: "{{ui_count}}"
928
1191
  source_map: "{{feature_dir}}/source-map.md"
929
- aggregate: "{{feature_name}}"
1192
+ feature: "{{feature_name}}"
1193
+ # What the feature acts on. Equal to the feature on a 1:1 slice; not on a feature that
1194
+ # is one capability over a shared aggregate. aggregate_source says which of the spec,
1195
+ # the boundary map or the feature name answered.
1196
+ aggregate: "{{aggregates_csv}}"
1197
+ aggregate_source: "{{aggregate_source}}"
930
1198
  source_count: "{{source_count}}"
931
1199
  review_items: "{{review_items}}"
932
1200
  concept_completed: "{{concept_completed}}"
@@ -936,4 +1204,4 @@ outputs:
936
1204
  composed_file: "{{composed_file}}"
937
1205
  technical_reconcile_ok: "{{technical_ok}}"
938
1206
  domain_reconcile_ok: "{{domain_ok}}"
939
- next: "Adopted {{feature_name}} (spec.md + design.md + source-map.md, v1.0) and COMPLETED its OKF concept (deepened + linked the deep artifacts; {{xref_edge_count}} cross-slice edge(s) recorded). UI HALF: ui_lane={{ui_lane}} — where it is true this slice also has design.ui.md, reversed from the {{ui_count}} UI file(s) the feature map attributes to it; where it is false the repo declared no frontend lane, or this aggregate owns no part of its surface, and a service-only adoption is the correct outcome rather than a gap. The SHARED UI surface (app shell, layout, design system) belongs to no slice and stays in {{bundle_dir}}/architecture/frontend.md. NOTE — governance reconcile is currently HELD (reconcile_governance=false): this run did NOT modify the constitution halves or the guardrails/projects/{{gov_domain}}/ rule set, so they keep onboard's provisional scaffold. Close any items in specs/{{feature_name}}/adopt-review-report.md out of band. The slice is now a NORMAL feature: refine via /appweaver analyze-plan, then drive every change through the CR delta loop (/appweaver specify → plan → analyze-plan-delta → implement → build → analyze-slice-delta → finalize-delta → analyze-slice → close-delta). If a change edits untested legacy behaviour with blast radius, characterize just that surface first (optional on-touch fence). To restore governance reconcile, set reconcile_governance to \"true\" in the workflow vars."
1207
+ next: "Adopted {{feature_name}} (spec.md + backend-plan.md + source-map.md, v1.0) and wrote {{aggregate_sections}} — the model section(s) for the aggregate(s) it acts on, resolved from the {{aggregate_source}} — in the bounded context `{{context}}`'s shared {{domain_model}}. The spec cites BR-nnn by id, the model carries their text, so this slice is checkable by the same gates as a greenfield one and analyze-plan's check 4 now has a model to check against. A section another feature over the same aggregate wrote first was EXTENDED, never duplicated; every other section is untouched. COMPLETED the OKF concept of {{aggregate_primary}} (deepened + linked the deep artifacts; {{xref_edge_count}} cross-slice edge(s) recorded). UI HALF: ui_lane={{ui_lane}} — where it is true this slice also has frontend-plan.md, reversed from the {{ui_count}} UI file(s) the feature map attributes to it; where it is false the repo declared no frontend lane, or this aggregate owns no part of its surface, and a service-only adoption is the correct outcome rather than a gap. The SHARED UI surface (app shell, layout, design system) belongs to no slice and stays in {{bundle_dir}}/architecture/frontend.md. NOTE — governance reconcile is currently HELD (reconcile_governance=false): this run did NOT modify the constitution halves or the guardrails/projects/{{gov_domain}}/ rule set, so they keep onboard's provisional scaffold. Close any items in {{feature_dir}}/adopt-review-report.md out of band. The slice is now a NORMAL feature: refine via /appweaver analyze-plan, then drive every change through the CR delta loop (/appweaver specify → plan → analyze-plan-delta → implement → build → analyze-slice-delta → finalize-delta → analyze-slice → close-delta). If a change edits untested legacy behaviour with blast radius, characterize just that surface first (optional on-touch fence). To restore governance reconcile, set reconcile_governance to \"true\" in the workflow vars."