aw-spec-cli 0.1.32 → 0.1.33

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.
@@ -82,11 +82,18 @@ same traceability spine and the two deferral ledgers — with **one difference t
82
82
  are **derived from the code's own organisation** (its folders / projects / modules)
83
83
  rather than the fixed Domain/Application/API/Infrastructure four. A component the
84
84
  code has that fits no textbook layer (a UI tier, a worker, a host project) gets its
85
- own section; a layer the code lacks does not appear. Two neutral anchors are kept
85
+ own section; a layer the code lacks does not appear. Three neutral anchors are kept
86
86
  because every system has them and the tooling keys on them: **§1 is the domain/data
87
- model** (the cross-reference tool reads it for `<Other>Id` reference edges) and one
87
+ model** (the cross-reference tool reads it for `<Other>Id` reference edges), one
88
88
  **§2 subsection is the External Integrations table** (matched by heading keyword, so
89
- `aw-xref` extracts contract edges from either layout). The `debt` ledger records
89
+ `aw-xref` extracts contract edges from either layout), and **each published endpoint
90
+ is an endpoint block** — the greenfield §3 form, `#### `VERB /route`` with its
91
+ **Operation id**, followed by the "Requests from the frontend lane" table — inside
92
+ whichever §2 axis publishes it. The contract scripts read exactly that form: the API
93
+ draft is written from the headings, and the request ledger between the two lanes reads
94
+ the operation ids and the table. An endpoint the code already serves keeps the id it
95
+ publishes; where it publishes none the plan writes `—` and records a §5 assumption,
96
+ never an invented id. The `debt` ledger records
90
97
  only debt the **code itself** evidences (stubs, dead code, torn seams) — being
91
98
  organised unlike a reference architecture is **not** debt, it is the as-built shape;
92
99
  migrating toward a target is a separate modernization decision.
@@ -126,15 +126,19 @@ migration is needed (yes/no); any contract change other systems couple to.]
126
126
 
127
127
  ## Contract deltas for the frontend lane
128
128
 
129
- <!-- What this change adds to or alters in the service's PUBLISHED surface — the
130
- only part of this document the frontend lane may bind against. `None` when
131
- the surface is unchanged. Omit the section entirely where the code has no
132
- frontend lane. The operation id is the endpoint's name in the published API
133
- document; an endpoint that already has one keeps it. -->
129
+ <!-- What this change adds to, alters in or removes from the service's PUBLISHED
130
+ surface — the only part of this document the frontend lane may bind against.
131
+ `None` when the surface is unchanged. Omit the section entirely where the code
132
+ has no frontend lane. The operation id is the endpoint's name in the published
133
+ API document; an endpoint that already has one keeps it. A `changed` or
134
+ `removed` row whose operation the frontend plan binds to is reported by
135
+ analyze-plan-delta (contract-impact reads the Operation id and Change
136
+ columns) until the frontend delta of this change — or a follow-up change —
137
+ carries the matching change. -->
134
138
 
135
139
  | Operation id | Contract | Change | Shape | Serves |
136
140
  |---|---|---|---|---|
137
- | `[OperationId]` | `[contract]` | [added / changed] | [request → response field shapes] | IC-[nnn] |
141
+ | `[OperationId]` | `[contract]` | [added / changed / removed] | [request → response field shapes] | IC-[nnn] |
138
142
 
139
143
  ## Other open deltas
140
144
 
@@ -30,6 +30,13 @@
30
30
  • Exactly one §2 subsection's heading contains the word "Integration".
31
31
  `aw-xref` matches it by keyword to extract contract edges, which is what
32
32
  lets a derived layout and the greenfield layout be read by one tool.
33
+ • Each published endpoint is a `####` ENDPOINT BLOCK — the heading
34
+ `#### `[VERB] [/route]`` and its **Operation id** field — with the
35
+ "Requests from the frontend lane" table after the blocks, inside whichever
36
+ §2 axis publishes them. The contract scripts read exactly this form:
37
+ `contract-draft` drafts api-draft.json from the headings, and
38
+ `contract-requests` reads the operation ids and the table. The axis keeps
39
+ the project's own name; only the block form is fixed.
33
40
  • The traceability spine (§4) and both ledgers (§5 DA-n, §6 WA-n) survive
34
41
  every layout — the analyze gates read them.
35
42
  • The §0–§6 numbering itself: `plan`'s bootstrap_bf branch names those
@@ -169,9 +176,8 @@
169
176
  the titles are the code's answer — the slots below are illustrative
170
177
  positions, not a required set. In each: the components that live there,
171
178
  their behaviour in signature form, and the contracts they expose or
172
- require. A component that PUBLISHES an endpoint names it with its route, verb
173
- and operation id — the endpoint's name in the published API document, which
174
- the frontend plan cites and implement looks the endpoint up by. -->
179
+ require. The endpoints the service PUBLISHES take the endpoint block form
180
+ shown in the axis below. -->
175
181
 
176
182
  ### 2.1 [PROJECT_STRUCTURAL_AXIS_NAME]
177
183
 
@@ -186,6 +192,50 @@
186
192
 
187
193
  <!-- … one per axis the code actually shows, then stop … -->
188
194
 
195
+ ### 2.[M] [AXIS_THAT_PUBLISHES_THE_ENDPOINTS]
196
+
197
+ <!-- PINNED FORM, in whichever axis this project publishes its endpoints from. The
198
+ axis keeps the project's own name and its component table; what is fixed is
199
+ each endpoint's block below it and the requests table after the blocks,
200
+ because the contract scripts read exactly these: the `#### `VERB /route``
201
+ heading (contract-draft writes api-draft.json from it), the **Operation id**
202
+ field (the id the service serves, which the frontend plan binds to) and the
203
+ `| Request | Outcome |` table (contract-requests). A project that publishes no
204
+ endpoint omits both. -->
205
+
206
+ | Component | Behaviour (signature) | Pre / Post / Raises | Serves |
207
+ |---|---|---|---|
208
+ | `[Component]` | `[operation](args) -> result` | [pre / post / raises] | FR-00X · US-Y |
209
+
210
+ #### `[VERB] [/route]` — [purpose] · Serves US-Y · FR-00X
211
+
212
+ - **Operation id**: `[OperationId]` <!-- the endpoint's name in the published API document. An endpoint the code already serves keeps the id it publishes, spelled exactly as the code spells it; where it publishes none, write `—` and record a §5 DA-n saying so — never an invented id, which the service's exported document would not carry. A NEW endpoint takes an id unique across the service, and implement gives it exactly that. -->
213
+ - **Auth**: [required role/scope, as enforced]
214
+ - **Request**: `[path/query params]` + body `{ [field]: [type], … }`
215
+ - **Success**: `2xx` → `{ [response shape] }`
216
+ - **Errors** *(the error contract)*:
217
+
218
+ | Status | When | Body shape |
219
+ |---|---|---|
220
+ | [status] | [when it is returned] | [body shape] |
221
+
222
+ - **Maps to**: `[Component]` (§2.n)
223
+
224
+ #### Requests from the frontend lane
225
+
226
+ <!-- THE ANSWERS to the frontend plan's "Contract deltas needed" table, one row
227
+ per RQ-n it raises. PUBLISH: add the endpoint above — under the proposed
228
+ operation id unless it clashes with one this service already uses — and name
229
+ the id here. DECLINE: say why in one line; a request the spec does not
230
+ support is declined as a spec gap for `specify`, never published on a guess.
231
+ Keep every answered row; write `None` instead of the table when the frontend
232
+ plan raises no request — always so for a plan reversed from code, whose
233
+ frontend binds only to what the service already serves. -->
234
+
235
+ | Request | Outcome | Operation id | Why |
236
+ |---|---|---|---|
237
+ | RQ-[n] | published / declined | `[OperationId]` / — | [one line — required when declined] |
238
+
189
239
  ### 2.[N] External Integrations
190
240
 
191
241
  <!-- PINNED ANCHOR — the heading MUST contain the word "Integration"; the
@@ -71,7 +71,18 @@
71
71
  # =============================================================================
72
72
 
73
73
  name: aw-spec-adopt
74
- 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. Every reverse step is scoped to the feature's own operations from the confirmed boundary map, so two features over one aggregate never claim the same operation, and the spec continues the context's BR-nnn numbering from its domain model. Where a frontend lane is declared and this slice owns none of its surface, the feature records lanes=backend so later phases neither author nor demand a frontend plan for it. 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.
74
+ 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 its lane plans — backend-plan.md where a backend lane is declared and the slice has service code, 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. Every reverse step is scoped to the feature's own operations from the confirmed boundary map, so two features over one aggregate never claim the same operation, and the spec continues the context's BR-nnn numbering from its domain model. Where a frontend lane is declared and this slice owns none of its surface, the feature records lanes=backend so later phases neither author nor demand a frontend plan for it. 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.
75
+ # 2.3.0 — the SERVICE half is reversed only where the slice has one (svc_lane: a backend lane
76
+ # is declared and the slice has service files), the twin of ui_lane. A frontend-only
77
+ # project gets no backend plan and no cross-slice edges, and its model is read from
78
+ # the UI files; a UI-only slice in a repo with both lanes records lanes=[frontend].
79
+ # A failed fidelity check names the two ways to resolve it — re-adopt, or correct
80
+ # the documents by hand — and what each leaves behind.
81
+ # 2.2.0 — the reversed plans take part in the API ledger between the lanes: the backend plan
82
+ # writes each endpoint the slice publishes as an endpoint block (`#### `VERB /route``
83
+ # + **Operation id**, the id the code publishes — `—` and a DA-n where it publishes
84
+ # none, never an invented one) with a "Requests from the frontend lane" table, and the
85
+ # frontend plan's Contract Binding rows cite those ids.
75
86
  # 2.1.0 — every reverse step is scoped to the FEATURE, not its aggregate: the boundary map's
76
87
  # operation list (and a sibling feature's operations, which are not this one's)
77
88
  # reaches the spec, model, plan, UI plan, source-map and fidelity steps. The spec step
@@ -87,7 +98,7 @@ description: The JIT per-feature step of brownfield development — reverse-engi
87
98
  # 1.3.0 — reverse design scaffolds from the DEFAULT `design-template` (onboard's code-derived
88
99
  # override) instead of the never-shipped `brownfield-design-template`, unifying the
89
100
  # whole brownfield pipeline on one code-derived template.
90
- version: 2.1.0
101
+ version: 2.3.0
91
102
  agent_agnostic: true
92
103
  dialect: aw-spec.orchestrator/v1
93
104
 
@@ -128,8 +139,12 @@ vars:
128
139
  be_declared: "false"
129
140
  fe_no_surface: "false" # a frontend lane is declared, this slice owns none of its surface, and a backend lane carries it
130
141
  feature_lanes: "" # what record_lanes wrote — empty when the feature follows every declared lane
142
+ xref_edge_count: "0" # xref_record runs only where the slice has a service half
131
143
  fe_root: ""
132
144
  ui_lane: "false" # THIS slice reverses a UI half: a frontend lane is declared AND the slice owns UI files
145
+ svc_lane: "false" # THIS slice reverses a SERVICE half: a backend lane is declared AND the slice has service files
146
+ be_no_surface: "false" # a backend lane is declared, this slice has no service code, and a frontend lane carries it
147
+ model_files: "" # what the model is reversed from: the service files, or the UI files on a slice with none
133
148
  ui_count: "0" # how many UI files the concept attributes to this aggregate (aw-inventory ui_source_files)
134
149
  ui_source_files: ""
135
150
  frontend_plan: ""
@@ -424,14 +439,6 @@ workflow:
424
439
  outputs:
425
440
  - { name: domain_model, from: "$.domain_model" }
426
441
 
427
- - id: scaffold_plan
428
- action: execute_script
429
- script: { ref: actions }
430
- subcommand: plan-scaffold
431
- args: ["--feature-dir", "{{feature_dir}}", "--lane", "backend"] # `design-template` = onboard's code-derived override (greenfield base if onboard has not run)
432
- outputs:
433
- - { name: backend_plan, from: "$.plan_doc" }
434
-
435
442
  - id: version_init
436
443
  action: execute_script
437
444
  script: { ref: actions }
@@ -502,6 +509,69 @@ workflow:
502
509
  outputs:
503
510
  - { name: feature_lanes, from: "$.lanes" }
504
511
 
512
+ # 2d. DOES THIS SLICE HAVE A SERVICE HALF? The twin of 2b: a backend lane must be DECLARED
513
+ # and this aggregate must have service files (the concept's source_files). A
514
+ # frontend-only project declares no backend lane, and its concepts list every file as
515
+ # ui_source_files, so a backend plan there would describe code that does not exist and
516
+ # the fidelity gate would score it as fabricated. Three ordered sets, as in 2b.
517
+ - id: svc_lane_off
518
+ action: set
519
+ vars: { svc_lane: "false" }
520
+ - id: svc_lane_on
521
+ action: set
522
+ condition: "{{be_declared}}"
523
+ vars: { svc_lane: "true" }
524
+ - id: svc_lane_no_code
525
+ action: set
526
+ condition: "{{source_count}} == 0"
527
+ vars: { svc_lane: "false" }
528
+
529
+ # 2e. RECORD A UI-ONLY SLICE in a repo that declares both lanes — the twin of 2c, so plan
530
+ # and the analyze gates do not demand a backend plan for a slice with no service code.
531
+ - id: be_no_surface_off
532
+ action: set
533
+ vars: { be_no_surface: "false" }
534
+ - id: be_no_surface_declared
535
+ action: set
536
+ condition: "{{be_declared}}"
537
+ vars: { be_no_surface: "true" }
538
+ - id: be_no_surface_has_code
539
+ action: set
540
+ condition: "{{source_count}} != 0"
541
+ vars: { be_no_surface: "false" }
542
+ - id: be_no_surface_no_frontend
543
+ action: set
544
+ condition: "{{ui_lane}} != true"
545
+ vars: { be_no_surface: "false" }
546
+ - id: record_lanes_frontend
547
+ condition: "{{be_no_surface}} == true"
548
+ action: execute_script
549
+ script: { ref: actions }
550
+ subcommand: feature-lanes
551
+ args: ["--feature-dir", "{{feature_dir}}", "--set", "frontend"]
552
+ outputs:
553
+ - { name: feature_lanes, from: "$.lanes" }
554
+
555
+ # What the domain model is reversed FROM. The service files where the slice has them; a
556
+ # slice with none keeps its domain in the UI code — the types, states and rules a
557
+ # frontend-only app holds — so the model is read from those.
558
+ - id: model_files_service
559
+ action: set
560
+ vars: { model_files: "{{source_files}}" }
561
+ - id: model_files_ui
562
+ condition: "{{svc_lane}} != true"
563
+ action: set
564
+ vars: { model_files: "{{ui_source_files}}" }
565
+
566
+ - id: scaffold_plan
567
+ condition: "{{svc_lane}} == true"
568
+ action: execute_script
569
+ script: { ref: actions }
570
+ subcommand: plan-scaffold
571
+ args: ["--feature-dir", "{{feature_dir}}", "--lane", "backend"] # `design-template` = onboard's code-derived override (greenfield base if onboard has not run)
572
+ outputs:
573
+ - { name: backend_plan, from: "$.plan_doc" }
574
+
505
575
  # Scaffold the LANE's plan from ITS template pair (--lane frontend resolves
506
576
  # ui-design-template -> frontend-plan.md), the twin of scaffold_plan above. onboard
507
577
  # GENERATED that override from this repo's own UI source, so the reversed frontend plan
@@ -734,7 +804,8 @@ workflow:
734
804
  - Add `origin: reverse-engineered` near the identity block. Do NOT write the Context
735
805
  version or the Change History version column — scripts stamp those.
736
806
 
737
- Source files to read (the slice's code): {{source_files}}
807
+ Source files to read (the slice's code — its UI files where it has no service code,
808
+ the one place a frontend-only app keeps its types, states and rules): {{model_files}}
738
809
 
739
810
  # ═══════════ REVERSE BACKEND PLAN — from the SPEC + MODEL + CODE, AS-BUILT ═══
740
811
  # Anchored to the reversed spec's ids (§4 Traceability → forward-compatible),
@@ -744,6 +815,7 @@ workflow:
744
815
  # backend-plan.md IS onboard's code-derived `design-template` override — its §2
745
816
  # component sections follow the code's own folders/modules, not a fixed layering.
746
817
  - id: ctx_reverse_plan
818
+ condition: "{{svc_lane}} == true"
747
819
  action: build_context
748
820
  context:
749
821
  - { path: "{{backend_plan}}" } # the scaffolded code-derived template = structure + embedded guidance
@@ -754,6 +826,7 @@ workflow:
754
826
  - { path: "{{bundle_dir}}/architecture/overview.md", optional: true } # the as-built architecture (inventory) — cite, don't re-derive
755
827
  - { path: .appweaver/memory/constitution.technical.md, optional: true } # technology base ONLY — no domain
756
828
  - id: reverse_plan
829
+ condition: "{{svc_lane}} == true"
757
830
  action: agent_author
758
831
  include_standards: true # the technical/architecture sticky set (reference only — the CODE wins)
759
832
  tools: Read # grounded in the CODE, which arrives as {{source_files}} paths
@@ -798,6 +871,21 @@ workflow:
798
871
  the EXTERNAL INTEGRATIONS table (heading contains "Integration") realising the
799
872
  spec's IC-. Realise CN- (structural → §1.2 invariants/events; retention/audit →
800
873
  §2 persistence / §3 cross-cutting), DC- (§3 + projection surface).
874
+ - EVERY ENDPOINT THIS FEATURE PUBLISHES IS AN ENDPOINT BLOCK, in the §2 axis that
875
+ publishes it: the heading `#### `VERB /route` — <purpose> · Serves <US/FR ids>`, then
876
+ **Operation id**, Auth, Request, Success, the Errors table and Maps to, exactly as
877
+ the template's endpoint block — never a row in a table of endpoints. The contract
878
+ scripts read this form: the API draft is written from the headings, and the request
879
+ ledger between the lanes reads the operation ids. The **Operation id** is the id the
880
+ code publishes the endpoint under, spelled exactly as the code spells it; the
881
+ boundary map records each operation's published id where the code declares one
882
+ ("published as …" above). Where the code publishes none, write `—` and record a §5
883
+ DA-n saying the endpoint has no published operation id — never invent one: the
884
+ service's exported API document would not carry it, so nothing could bind to it.
885
+ - After the endpoint blocks, a `#### Requests from the frontend lane` heading with
886
+ `None` under it: a frontend reversed from the same code binds only to what the
887
+ service already serves, so no request is open yet. Later plans answer requests
888
+ there.
801
889
  - AS-BUILT DEBT, not architecture drift. §6 records ONLY debt the CODE ITSELF
802
890
  evidences — stubs/no-ops, dead or duplicate code, torn seams (shared table/
803
891
  transaction), TODOs, hard-coded values. The code being organised differently
@@ -835,7 +923,7 @@ workflow:
835
923
  context:
836
924
  - { path: "{{frontend_plan}}" } # the scaffolded code-derived lane template = structure + embedded guidance
837
925
  - { path: "{{spec_file}}" } # the reversed spec — UX- + the user stories this surface serves
838
- - { path: "{{backend_plan}}" } # the backend plan — the contracts the UI consumes, never re-specified here
926
+ - { path: "{{backend_plan}}", optional: true } # the backend plan — the contracts the UI consumes, never re-specified here; absent with no service lane
839
927
  - { path: "{{domain_model}}", optional: true } # the ubiquitous language labels and states must use — READ, never written
840
928
  - { path: .appweaver/memory/constitution.ui.md, optional: true } # the lane's UX-* base, reversed by onboard from this same UI
841
929
  - { path: "{{bundle_dir}}/architecture/frontend.md", optional: true } # the as-built UI architecture + the SHARED surface no slice owns
@@ -863,9 +951,17 @@ workflow:
863
951
  - STAY IN THE LANE. Realise the spec's Experience Standards (UX-) and the
864
952
  interaction its User Stories imply. The backend plan (in context) supplies
865
953
  the CONTRACTS this surface consumes — cite them; do NOT restate, re-derive or
866
- contradict them, and introduce no server-side behaviour here. Where the domain
954
+ contradict them, and introduce no server-side behaviour here. With no backend plan
955
+ in context the service is outside this repo: bind against the routes the UI code
956
+ calls and the spec's IC-, as an external contract. Where the domain
867
957
  model is in context, use its UBIQUITOUS LANGUAGE exactly in labels, states and
868
958
  messages, and cite BR-nnn rather than restating a rule.
959
+ - CONTRACT BINDING CITES OPERATION IDS. Each row of the Contract Binding table names,
960
+ in its Operation id column, the **Operation id** backend-plan.md gives the endpoint
961
+ the UI code calls — `—` where the backend plan records none. The id is how
962
+ implement finds the endpoint, and how a later change that alters or removes it is
963
+ reported to this lane. The "Contract deltas needed" table reads `None`: the UI calls
964
+ only what the service already serves.
869
965
  - STRUCTURE FOLLOWS THE UI CODE. Fill each section from the lane's real shape —
870
966
  its actual routes and screens, its own composition axes (however it groups
871
967
  components), where client state and server data-fetching really live, its
@@ -890,7 +986,7 @@ workflow:
890
986
  context:
891
987
  - { path: .appweaver/templates/source-map-template.md } # the sidecar structure
892
988
  - { path: "{{spec_file}}" }
893
- - { path: "{{backend_plan}}" }
989
+ - { path: "{{backend_plan}}", optional: true } # the service lane's plan, where this slice has one
894
990
  - { path: "{{frontend_plan}}", optional: true } # the lane's design, where this slice has one
895
991
  - { path: "{{concept_path}}" } # both file lists to map
896
992
  - id: source_map
@@ -945,7 +1041,7 @@ workflow:
945
1041
  context:
946
1042
  - "{{spec_file}}"
947
1043
  - "{{domain_model}}"
948
- - "{{backend_plan}}"
1044
+ - { path: "{{backend_plan}}", optional: true } # the service lane's plan, where this slice has one
949
1045
  - { path: "{{frontend_plan}}", optional: true } # the lane's plan, where this slice has one
950
1046
  - "{{feature_dir}}/source-map.md"
951
1047
  - "{{concept_path}}"
@@ -1006,7 +1102,12 @@ workflow:
1006
1102
  condition: "{{report_needed}} == true"
1007
1103
  action: agent_author
1008
1104
  include_standards: false
1009
- context: ["{{spec_file}}", "{{domain_model}}", "{{backend_plan}}", "{{feature_dir}}/source-map.md"]
1105
+ context:
1106
+ - "{{spec_file}}"
1107
+ - "{{domain_model}}"
1108
+ - { path: "{{backend_plan}}", optional: true }
1109
+ - { path: "{{frontend_plan}}", optional: true }
1110
+ - "{{feature_dir}}/source-map.md"
1010
1111
  output_target: { type: file, path: "{{feature_dir}}/adopt-review-report.md" }
1011
1112
  task: |
1012
1113
  Write an Adopt Review report (the target) for the feature '{{feature_name}}' (acting on {{aggregates_csv}}) —
@@ -1014,8 +1115,10 @@ workflow:
1014
1115
  - "# Adopt Review — {{feature_name}}" + today's date + "origin: reverse-engineered".
1015
1116
  - "verdict:" line — "BLOCKING ✗ · <n>" if blocking gaps exist, else "REVIEW ○ · <m> item(s)".
1016
1117
  - "## Blocking gaps" (if any): table | # | kind | artefact | detail | — MUST be
1017
- resolved before the slice is trustworthy (fix by re-reading the code / refining
1018
- spec+design, then re-run adopt-review or re-adopt).
1118
+ resolved before the slice is trustworthy, one of two ways: re-adopt — remove
1119
+ {{feature_dir}} and run /appweaver adopt {{feature_name}} again, which reverses
1120
+ the documents afresh from the code, so edits made to them by hand are not kept —
1121
+ or correct the documents by hand and keep them.
1019
1122
  Blocking gaps: {{blocking_gaps}}
1020
1123
  - "## Review items" (close out of band): table | # | kind | where | confirm |.
1021
1124
  These are inferred-vs-certain calls, as-built quirks, and missing-invariant
@@ -1034,7 +1137,7 @@ workflow:
1034
1137
  action: verify_context
1035
1138
  require_true: fidelity_ok
1036
1139
  on_false: stop
1037
- 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. A `foreign-operation` gap means a document claims an operation the boundary map gives only to another feature; remove it here — it belongs to that feature's adopt."
1140
+ 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. Resolve it one of two ways. RE-ADOPT: remove {{feature_dir}} and run /appweaver adopt {{feature_name}} again; the documents are reversed afresh from the code, so edits made to them first are not kept. Removing the folder leaves what this run added to {{domain_model}} (git diff shows it), and a re-adopt extends it, so remove that first. CORRECT BY HAND: re-read the source and refine spec.md, {{domain_model}}, the lane plans this slice has (backend: {{backend_plan}}; frontend: {{frontend_plan}}) and source-map.md — map the unmapped code, remove what is fabricated. This run stopped before it completed the OKF concept {{aggregate_primary}} and recorded cross-slice edges, so on this path both stay as onboard left them. 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. A `foreign-operation` gap means a document claims an operation the boundary map gives only to another feature; remove it here — it belongs to that feature's adopt."
1038
1141
 
1039
1142
  # ═══════════ OKF CONCEPT — COMPLETE the shallow scan concept (deep reverse) ═══
1040
1143
  # onboard wrote this aggregate's OKF concept SHALLOW (a discovery stub — enough to pick the
@@ -1051,7 +1154,8 @@ workflow:
1051
1154
  - { path: "{{concept_path}}" } # the shallow onboard concept to complete
1052
1155
  - { path: "{{spec_file}}" } # the deep reversed spec (entities, US, BR/CN/DC/IC)
1053
1156
  - { path: "{{domain_model}}" } # the deep reversed model (entities, boundary, invariants)
1054
- - { path: "{{backend_plan}}" } # the deep reversed plan (components, ports, integrations)
1157
+ - { path: "{{backend_plan}}", optional: true } # the deep reversed service plan (components, ports, integrations), where the slice has one
1158
+ - { path: "{{frontend_plan}}", optional: true } # the deep reversed UI plan, where the slice has one
1055
1159
  - { path: "{{feature_dir}}/source-map.md" } # the source anchor (files, seam, quirks)
1056
1160
  - { path: .appweaver/templates/okf-concept-template.md } # OKF v0.1 format authority
1057
1161
  - id: complete_concept
@@ -1109,6 +1213,7 @@ workflow:
1109
1213
  # reconcile-on-write helper as forward implement, stamped origin reverse-engineered,
1110
1214
  # additive to the inventory bundle. Runs only after fidelity passes.
1111
1215
  - id: xref_record
1216
+ condition: "{{svc_lane}} == true" # edges are read off the service plan, as forward implement records them
1112
1217
  action: execute_script
1113
1218
  script: { ref: xref }
1114
1219
  subcommand: record
@@ -1331,9 +1436,10 @@ outputs:
1331
1436
  frontend_plan: "{{frontend_plan}}" # empty where this slice has no UI half
1332
1437
  domain_model: "{{domain_model}}" # the CONTEXT's, shared — this run ADDED an aggregate section to it
1333
1438
  bounded_context: "{{context}}"
1439
+ svc_lane: "{{svc_lane}}"
1334
1440
  ui_lane: "{{ui_lane}}"
1335
1441
  ui_count: "{{ui_count}}"
1336
- feature_lanes: "{{feature_lanes}}" # empty = every declared lane; [backend] = a service-only slice in a repo with a frontend
1442
+ feature_lanes: "{{feature_lanes}}" # empty = every declared lane; [backend] = a service-only slice in a repo with a frontend; [frontend] = a UI-only slice in a repo with a service
1337
1443
  operations: "{{operations_csv}}" # this feature's operations, from the boundary map
1338
1444
  baseline_commit: "{{baseline_commit}}"
1339
1445
  source_map: "{{feature_dir}}/source-map.md"
@@ -1352,4 +1458,4 @@ outputs:
1352
1458
  composed_file: "{{composed_file}}"
1353
1459
  technical_reconcile_ok: "{{technical_ok}}"
1354
1460
  domain_reconcile_ok: "{{domain_ok}}"
1355
- 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. In the second case, where a frontend lane IS declared, the feature records lanes=[backend] in its active_version.json (feature_lanes shows it), so plan and the analyze gates treat the frontend lane as absent for THIS feature; run `aw-spec-actions feature-lanes --clear` if it later gains a UI. 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/{{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."
1461
+ next: "Adopted {{feature_name}} (spec.md, source-map.md and the lane plans this slice has — backend: {{backend_plan}}; frontend: {{frontend_plan}} — 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. In the second case, where a frontend lane IS declared, the feature records lanes=[backend] in its active_version.json (feature_lanes shows it), so plan and the analyze gates treat the frontend lane as absent for THIS feature; run `aw-spec-actions feature-lanes --clear` if it later gains a UI. SERVICE HALF: svc_lane={{svc_lane}} — false where the repo declares no backend lane (a frontend-only project: no backend plan, no cross-slice edges, and the model was read from the UI code) or this slice has no service code; in the second case, where a frontend lane carries it, the feature records lanes=[frontend] the same way. 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/{{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."
@@ -152,7 +152,7 @@
152
152
 
153
153
  name: aw-spec-onboard
154
154
  description: Onboard an existing codebase for brownfield DEVELOPMENT — a deterministic whole-repo scan produces a high-level OKF feature map (folders/concepts) PLUS the project-level governance scaffold (split constitution + guardrails/brf-<domain>/ with its DOMAIN rule files at domain/ + domain-rule-bindings + code-derived TECHNICAL rule files at service/ + tech-rule-bindings + activated domain), all provisional-from-scan. The first context authors that tier; every later context amends it additively from its own code (new rules, principles and map entries — never a rewrite, renumbering or deletion; a contradiction is recorded as a Divergence note), and a tier that does not exist yet, such as a lane declared after an earlier onboard, is authored in full. Naming an already-onboarded context re-runs that governance pass alone. Where the scan finds a frontend lane in the CODE, it also authors that lane's half — constitution.ui.md (UX-*) — and its code-derived rule files at brf-<domain>/frontend/, mapped under `## /aw-spec-ui-constitution`, `## /aw-spec-plan-frontend` and `## /aw-spec-implement-frontend`; a backend-only repo gets none of it. Every rule tier is reverse-engineered from the CODE alone (never a greenfield rule file), evidenced-only, and WIRED per-workflow so the whole forward pipeline AND adopt govern against THIS codebase — never a greenfield fallback. It also reverse-engineers the repo's own technical structure into a project-specific design + delta-design template pair, installed as the project template overrides, and — where the lane exists — the frontend lane's own pair from the UI source. Every authoring step is shaped by a NEUTRAL rev-templates skeleton; no greenfield constitution, rule file or document template is ever placed in context. Accepts an optional concerns list to look for (onboard decides which are actually present). Reads any declared lane whose folder exists, never writes one; a lane declared before it is built is governed as absent and its governance left to the developer. No migration content; never modifies source.
155
- version: 2.4.0
155
+ version: 2.5.0
156
156
  agent_agnostic: true
157
157
  dialect: aw-spec.orchestrator/v1
158
158
 
@@ -191,7 +191,7 @@ standards:
191
191
  vars:
192
192
  # The provenance stamp every generated file carries. It names THIS workflow's version,
193
193
  # so it moves with `version:` above — one value, never a literal repeated per prompt.
194
- generated_by: "aw-spec-onboard/2.4.0"
194
+ generated_by: "aw-spec-onboard/2.5.0"
195
195
  bundle_present: "false" # set by the scan (the bundle holds concepts); reported, gates nothing
196
196
  conformant: "false" # OKF conformance-gate driver
197
197
  pack_present: "false" # the project tier exists ⇒ repo_gov, so this context AMENDS it
@@ -332,11 +332,13 @@ workflow:
332
332
  - { name: bundle_dir, from: "$.bundle_dir" }
333
333
  - { name: bundle_present, from: "$.bundle_present" }
334
334
  - { name: bundle_md_count, from: "$.bundle_md_count" }
335
- # The frontend lane, as the CODE evidences it. `frontend_present` is true only
335
+ # The frontend lane, as the CODE evidences it. `frontend_evidenced` is true only
336
336
  # where a candidate root reached lane strength (a frontend build config, or a
337
337
  # real set of component/view files) — loose scripts and stylesheets under a
338
- # wwwroot/ are reported in the substrate but are NOT a lane.
339
- - { name: frontend_present, from: "$.frontend_present" }
338
+ # wwwroot/ are reported in the substrate but are NOT a lane. It is the code's
339
+ # answer, kept apart from `frontend_present` (the UI rule tier exists, from
340
+ # pack_setup); which lanes this run governs is the declaration's answer, ui_lane.
341
+ - { name: frontend_evidenced, from: "$.frontend_present" }
340
342
  - { name: frontend_separable, from: "$.frontend_separable" }
341
343
  - { name: frontend_roots, from: "$.frontend_roots" }
342
344
  - { name: frontend_root_count, from: "$.frontend_root_count" }
@@ -2303,6 +2305,16 @@ workflow:
2303
2305
  signature form + the ports/contracts. Keep exactly one subsection whose heading
2304
2306
  contains the word "Integration" (the External Integrations table — one port/adapter row
2305
2307
  per IC-): aw-xref matches it by keyword, so it is pinned regardless of layout.
2308
+ - THE PUBLISHED SURFACE IS A PINNED FORM. In the §2 axis this project publishes its
2309
+ endpoints from (titled with the project's own name for it), keep the skeleton's
2310
+ endpoint block exactly — the heading `#### `[VERB] [/route]` — [purpose] · Serves …`,
2311
+ then **Operation id**, Auth, Request, Success, the Errors table and Maps to — and the
2312
+ `#### Requests from the frontend lane` table after the blocks. The contract scripts
2313
+ read exactly this form: the API draft is written from the headings and the request
2314
+ ledger between the lanes reads the operation ids and the table, so endpoints listed
2315
+ in a table instead leave both empty. Keep the Operation id comment: an endpoint the
2316
+ code already serves keeps the id it publishes, `—` with a §5 DA-n where it publishes
2317
+ none, never an invented one. Omit both only where the code publishes no endpoint.
2306
2318
  - §3 Cross-Cutting Concerns — one row per concern THIS project's governance carries, read
2307
2319
  from the technical constitution (Serves = a spec id or a §5 DA-n). Do not import a
2308
2320
  standard concern list the constitution does not evidence.
@@ -2361,9 +2373,14 @@ workflow:
2361
2373
  the generated master template. Never name a layering — neither one you know nor one the
2362
2374
  master did not derive.
2363
2375
  - CARRY OVER the neutral parts of rev-templates/delta-design-template.skeleton.md (in
2364
- context): Change summary, Cross-section impact, Regression targets, New assumptions/
2365
- deviations (DA-n / WA-n), On merge — word for word: finalize-delta merges the spec and
2366
- the model, never this plan, which `plan main` re-derives. Keep behaviour technology-agnostic (signatures +
2376
+ context): Change summary, Model changes this plan realises, Depends on, Cross-section
2377
+ impact, Contract deltas for the frontend lane, Other open deltas, Regression targets,
2378
+ New assumptions/deviations (DA-n / WA-n), On merge — word for word: finalize-delta
2379
+ merges the spec and the model, never this plan, which `plan main` re-derives. Keep
2380
+ the contract table's Operation id and Change columns and its added / changed /
2381
+ removed values: analyze-plan-delta reads them to warn when a change alters or
2382
+ removes an operation the frontend binds to. Omit that section only where the code
2383
+ has no frontend lane. Keep behaviour technology-agnostic (signatures +
2367
2384
  contracts, no code); a shared element is MODIFIED in place, never duplicated; every
2368
2385
  changed element serves a spec id.
2369
2386
  - This aligns with the delta-plan module, which classifies "per the master's OWN
@@ -117,7 +117,7 @@
117
117
 
118
118
  name: aw-spec-plan
119
119
  description: "Author the feature's lane plans from the spec and the bounded context's domain model — backend-plan.md when a backend lane is declared, frontend-plan.md when a frontend lane is, both when both are. Lanes come from .appweaver/stacks.json, so one command covers backend-only, frontend-only and full-stack projects; the former separate design-ui command is folded in. It ASKS what it is planning, the way implement asks what it is building: the master (`main`), one of the OPEN CHANGES, or a new plan-only change for a refactor with no spec delta — the argument doubles as the answer, and the menu appears only once a master exists, so a first run bootstraps with no prompt. `main` on a feature that already has masters RE-DERIVES them from the merged spec.md + domain-model.md — the operation finalize-delta's contract assumes when it merges a change's spec and model halves and no lane plan — and is confirmed before it rewrites anything. A change instead gets delta-backend-plan.md / delta-frontend-plan.md inside changes/<CR-id>/ while the masters stay untouched, written against the effective model (the master model, the change's own model delta, and the pending model deltas of LOWER waves, which are its predecessors) and against the feature's real source files from its OKF concept. Master authoring is template-driven: the kit's templates by default, or the code-derived as-built structure when onboard installed an override. Authoring only — a master gates at analyze-plan; a change ships through the CR chain."
120
- version: 5.5.0
120
+ version: 5.5.1
121
121
  agent_agnostic: true
122
122
  dialect: aw-spec.orchestrator/v1
123
123
 
@@ -880,7 +880,11 @@ workflow:
880
880
  - NAME EVERY ENDPOINT with its route, verb and operation id — its name in the
881
881
  service's published API document, unique across the service. Where the code
882
882
  already gives the endpoint one, record THAT id exactly as the code spells it; the
883
- frontend plan cites it and implement finds the endpoint by it.
883
+ frontend plan cites it and implement finds the endpoint by it. An endpoint the code
884
+ already serves under no id keeps `—`, with a §5 DA-n saying so — never an invented
885
+ id. Write each endpoint as the template's endpoint block — the heading
886
+ `#### `VERB /route` — …` with its **Operation id** field — never as a row in a table
887
+ of endpoints: the API draft and the request ledger between the lanes read that form.
884
888
  - ANSWER THE FRONTEND LANE'S CONTRACT REQUESTS. Still open in the frontend plan's
885
889
  "Contract deltas needed" table: {{req_backend_text}}. Answer each one in a
886
890
  "Requests from the frontend lane" table beside this feature's endpoints (| Request
@@ -16,7 +16,10 @@
16
16
  # still READ the other lane where they need it: a frontend plan binds to the backend
17
17
  # plan's published surface, which the backend developer owns. specify, finalize-delta and
18
18
  # close-delta do NOT follow it: the spec is written whole, from its rules and standards,
19
- # and a change merges and closes with whatever its lanes have delivered.
19
+ # and a change merges and closes with whatever its lanes have delivered. Brownfield adopt
20
+ # does not follow it either: it reverses a feature once, every half its code has, because
21
+ # it cannot run a second time for the other lane, so the other lane's developer finds
22
+ # their plan already written.
20
23
  #
21
24
  # THE OPTIONS COME FROM stacks.json. Both lanes declared → backend, frontend and both.
22
25
  # One lane declared → that lane only, picked without a question (there is nothing to
@@ -32,8 +35,8 @@
32
35
  # =============================================================================
33
36
 
34
37
  name: aw-spec-select-lane
35
- description: Choose the lane this developer works on — backend, frontend or both — and store it in .appweaver/lane.json, which is not committed, so each developer keeps their own. The options come from the stacks declared in .appweaver/stacks.json, all three when both lanes are declared and only the declared one otherwise. plan, analyze-plan, implement, build and analyze-slice then write only that lane while still reading the other; specify, finalize-delta and close-delta do not follow it. select-stack writes the default (every declared lane); run this to narrow it.
36
- version: 1.0.0
38
+ description: Choose the lane this developer works on — backend, frontend or both — and store it in .appweaver/lane.json, which is not committed, so each developer keeps their own. The options come from the stacks declared in .appweaver/stacks.json, all three when both lanes are declared and only the declared one otherwise. plan, analyze-plan, implement, build and analyze-slice then write only that lane while still reading the other; specify, finalize-delta, close-delta and brownfield adopt do not follow it. select-stack writes the default (every declared lane); run this to narrow it.
39
+ version: 1.0.1
37
40
  agent_agnostic: true
38
41
  dialect: aw-spec.orchestrator/v1
39
42
 
@@ -100,5 +103,5 @@ outputs:
100
103
  working_lane: "{{working_lane}}"
101
104
  working_kinds: "{{working_kinds}}"
102
105
  lane_file: "{{lane_file}}"
103
- note: "Working lane `{{working_lane}}` stored in {{lane_file}}. The file is not committed — it is this developer's choice, and another developer on the same repository keeps their own. From now on plan, analyze-plan, implement, build and analyze-slice write only the working lane: they plan it, gate it with its own receipt, generate it, compile it and test it. They still READ the other lane where they need it — a frontend plan binds to the operation ids the backend plan publishes. specify writes the whole spec whatever lane is chosen, and finalize-delta and close-delta merge and close a change with whatever its lanes have delivered. Re-running /appweaver select-stack resets the working lane to every declared lane."
106
+ note: "Working lane `{{working_lane}}` stored in {{lane_file}}. The file is not committed — it is this developer's choice, and another developer on the same repository keeps their own. From now on plan, analyze-plan, implement, build and analyze-slice write only the working lane: they plan it, gate it with its own receipt, generate it, compile it and test it. They still READ the other lane where they need it — a frontend plan binds to the operation ids the backend plan publishes. specify writes the whole spec whatever lane is chosen, and finalize-delta and close-delta merge and close a change with whatever its lanes have delivered. Brownfield adopt reverses a feature once, every half its code has, whatever lane is chosen, so the other lane's developer finds their plan already written. Re-running /appweaver select-stack resets the working lane to every declared lane."
104
107
  next: "Run the lane's flow on the active feature: /appweaver plan → analyze-plan → implement → build → analyze-slice. Switch lanes any time with /appweaver select-lane backend | frontend | both."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aw-spec-cli",
3
- "version": "0.1.32",
3
+ "version": "0.1.33",
4
4
  "description": "AppWeaver Spec-Driven Development CLI - scaffolds projects with vertical slices, DDD, CQRS, and agent skills",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",