aw-spec-cli 0.1.31 → 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.
@@ -10,16 +10,20 @@
10
10
  # ← you are here
11
11
  #
12
12
  # It reads back the model the greenfield sequence writes — bounded context →
13
- # aggregate → feature → operation — from the code: the module folder is the context,
14
- # a declared aggregate-root type is the aggregate, a UI features/<Feature>/ folder is
15
- # the capability, and a slice under the module's Features/ is one operation (a user
16
- # story), never a feature. Three passes; the generated document explains each to the
17
- # operator reading it, in the wording of the template
13
+ # aggregate → feature → operation — from the code, whatever its stack: the module folder
14
+ # is the context, a declared aggregate-root type is the aggregate, a UI
15
+ # features/<Feature>/ folder is the capability, and an operation is a slice under the
16
+ # module's Features/, a command/query handler, a use case or an endpoint (a controller
17
+ # action, an annotated or decorated handler, a router call) — never a feature. Three
18
+ # passes; the generated document explains each to the operator reading it, in the
19
+ # wording of the template
18
20
  # .appweaver/templates/proposed-inventory-template.md, overridable per project:
19
21
  # A the aggregates. Four strategies — domain (the roots the code declares),
20
22
  # structure, reference graph, persistence — each offered only where its
21
23
  # evidence exists, each reporting its own numbers. Declared roots that
22
- # operations act on are chosen ahead of any measured strategy.
24
+ # operations act on are chosen ahead of any measured strategy. A project that
25
+ # declares a UI lane and no service lane has one strategy, `ui`: the API
26
+ # resources its UI calls are the aggregates, its calls the operations.
23
27
  # B the operations and their seams — each operation's route, the aggregate it
24
28
  # acts on, whether it creates one. Emits signals, never decisions.
25
29
  # C the features — one per UI feature folder, holding the operations it calls;
@@ -40,8 +44,8 @@
40
44
  # =============================================================================
41
45
 
42
46
  name: aw-spec-inventory
43
- description: Brownfield discovery — scan an existing codebase and propose its Features and Bounded Contexts, in dependency order with their dependencies, as an editable specs/proposed-inventory.md. Runs three passes (aggregate identification by a selectable evidence-gated strategy, a seam overlay over application/API/UI, and a 1:1-by-default feature proposal with evidenced splits offered but never applied). `apply` validates the operator's edits and regenerates the machine map onboard consumes. Never modifies source.
44
- version: 2.0.0
47
+ description: Brownfield discovery — scan an existing codebase in any stack and propose its Features and Bounded Contexts, in dependency order with their dependencies, as an editable specs/proposed-inventory.md. Runs three passes (aggregate identification by a selectable evidence-gated strategy, a seam overlay over application/API/UI, and a 1:1-by-default feature proposal with evidenced splits offered but never applied); a project with a UI lane and no service lane is read from the API calls its UI makes. `apply` validates the operator's edits and regenerates the machine map onboard consumes. Never modifies source.
48
+ version: 2.1.0
45
49
  agent_agnostic: true
46
50
  dialect: aw-spec.orchestrator/v1
47
51
 
@@ -51,10 +55,12 @@ inputs:
51
55
  Either the literal word `apply` — validate the edited specs/proposed-inventory.md
52
56
  and regenerate the machine map — or an optional Pass A strategy name
53
57
  (domain | structure | reference | persistence) to force, plus free-text focus
54
- hints. With no argument, `domain` is proposed where the code declares aggregate
55
- roots that operations act on, and otherwise the highest-coverage available
56
- strategy; the operator confirms it from a menu carrying each strategy's
57
- measured numbers.
58
+ hints. A strategy named as the first word is used without asking; a first word
59
+ that names none forces nothing. With no strategy named, `domain` is proposed where
60
+ the code declares aggregate roots that operations act on, and otherwise the
61
+ highest-coverage available strategy; the operator confirms it from a menu carrying
62
+ each strategy's measured numbers, the proposed one first. A project with no
63
+ service lane is read by `ui` alone.
58
64
  required: false
59
65
  bind: USER_INPUT
60
66
 
@@ -73,7 +79,6 @@ standards:
73
79
 
74
80
  vars:
75
81
  mode: "propose" # apply-mode driver
76
- strategy: "" # forced Pass A strategy; empty = probe default
77
82
  feature_count: "0"
78
83
  proposal_ok: "false"
79
84
 
@@ -142,17 +147,27 @@ workflow:
142
147
 
143
148
  # 1. WHICH LANES, AND WHERE IS THEIR SOURCE. Same declaration onboard reads:
144
149
  # the scan is scoped to the declared subtrees so backend candidates are not
145
- # drawn from UI source and vice versa.
150
+ # drawn from UI source and vice versa. A lane can be declared before it is built,
151
+ # so a lane root folder that does not exist yet is allowed here: the scan reads
152
+ # the lanes whose folders exist and the proposal names the one it did not read.
146
153
  - id: lane_model
147
154
  action: execute_script
148
155
  condition: "{{mode}} == propose"
149
156
  script: { ref: actions }
150
157
  subcommand: lane-verify
151
- args: ["--command", "/appweaver inventory"]
158
+ args: ["--command", "/appweaver inventory", "--allow-missing-root"]
152
159
  outputs:
153
160
  - { name: lane_model_ok, from: "$.ok" }
154
161
  - { name: lane_model_issues, from: "$.message" }
162
+ - { name: kinds_on_disk, from: "$.kinds_on_disk" }
163
+ - { name: missing_roots, from: "$.missing_roots" }
164
+ - { name: be_declared, from: "$.backend_declared" }
165
+ - { name: be_built, from: "$.backend_root_exists" }
166
+ - { name: be_lane_id, from: "$.backend_lane_id" }
155
167
  - { name: be_root, from: "$.backend_source_path" }
168
+ - { name: fe_declared, from: "$.frontend_declared" }
169
+ - { name: fe_built, from: "$.frontend_root_exists" }
170
+ - { name: fe_lane_id, from: "$.frontend_lane_id" }
156
171
  - { name: fe_root, from: "$.frontend_source_path" }
157
172
 
158
173
  - id: lane_model_gate
@@ -166,16 +181,48 @@ workflow:
166
181
  holds which lane before it starts. Run /appweaver select-stack first — it
167
182
  detects what this repo already is and lets you correct the proposal.
168
183
 
184
+ - id: lane_built_gate
185
+ action: verify_context
186
+ condition: "{{mode}} == propose"
187
+ require_non_empty: kinds_on_disk
188
+ on_empty: stop
189
+ on_empty_message: >
190
+ /appweaver inventory has no code to read: {{missing_roots}}
191
+ Inventory reads the code of the lanes whose folders exist, and no declared lane
192
+ has one yet.
193
+
194
+ # A declared lane with no folder yet is not read. Said once, here, and in the
195
+ # proposal's Scanned line; the run goes on with the lane that exists.
196
+ - id: be_unbuilt_warning
197
+ action: verify_context
198
+ condition: "{{be_declared}}"
199
+ require_true: be_built
200
+ on_false: continue
201
+ on_false_message: >
202
+ Service lane '{{be_lane_id}}' is declared at {{be_root}}, which does not exist
203
+ yet, so it is not read: the map is read from the UI lane alone.
204
+
205
+ - id: fe_unbuilt_warning
206
+ action: verify_context
207
+ condition: "{{fe_declared}}"
208
+ require_true: fe_built
209
+ on_false: continue
210
+ on_false_message: >
211
+ Frontend lane '{{fe_lane_id}}' is declared at {{fe_root}}, which does not exist
212
+ yet, so it is not read: features come from the service lane alone.
213
+
169
214
  # 2. PASS A + B + C. One deterministic call: every AVAILABLE strategy's
170
215
  # clustering is run (cheap — the expensive half is authoring, which does not
171
216
  # happen here), the seam overlay is laid over the chosen one, and the
172
- # proposal is written with its dependency waves.
217
+ # proposal is written with its dependency waves. The request goes to it whole:
218
+ # the script reads a strategy from its first word, and only when that word
219
+ # names one, so focus hints alone force nothing.
173
220
  - id: propose
174
221
  action: execute_script
175
222
  condition: "{{mode}} == propose"
176
223
  script: { ref: inventory }
177
224
  subcommand: propose
178
- args: ["--service-root", "{{be_root}}", "--ui-root", "{{fe_root}}", "--strategy", "{{strategy}}"]
225
+ args: ["--service-root", "{{be_root}}", "--ui-root", "{{fe_root}}", "--strategy", "{{USER_INPUT}}"]
179
226
  outputs:
180
227
  - { name: proposal_ok, from: "$.ok" }
181
228
  - { name: proposal_md, from: "$.proposal_md" }
@@ -190,6 +237,7 @@ workflow:
190
237
  - { name: split_candidates, from: "$.split_candidates" }
191
238
  - { name: contexts_ordered, from: "$.contexts_ordered" }
192
239
  - { name: strategy_options, from: "$.strategy_options" }
240
+ - { name: forced_option, from: "$.forced_option" }
193
241
  - { name: proposal_error, from: "$.error" }
194
242
 
195
243
  # 2b. Substance gate. A scan with nothing to propose is the one outcome that
@@ -204,26 +252,29 @@ workflow:
204
252
  on_mismatch: stop
205
253
  on_mismatch_message: >
206
254
  /appweaver inventory found nothing to propose. {{proposal_error}}
207
- Check that the service lane in .appweaver/stacks.json names the directory
208
- that holds the application source (/appweaver select-stack re-declares it),
209
- then re-run /appweaver inventory.
255
+ Check that each lane in .appweaver/stacks.json names the directory that holds
256
+ its source (/appweaver select-stack re-declares them), then re-run
257
+ /appweaver inventory.
210
258
 
211
259
  # 3. THE STRATEGY PICK. Offered from the probe, each option carrying its own
212
260
  # measured numbers — features found, share of source covered, how many
213
261
  # one-file candidates it produced. A strategy that would return nothing is
214
262
  # not in the list, because choosing it would be the zero-candidate stop
215
- # arrived at deliberately. One option auto-confirms.
263
+ # arrived at deliberately. One option auto-confirms. A strategy the request
264
+ # named is taken without asking (`forced_option` is its option line, empty when
265
+ # none was named); otherwise the proposed strategy is the first option, so a
266
+ # run that cannot ask takes the proposal.
216
267
  - id: pick_strategy
217
268
  action: user_select
218
269
  condition: "{{mode}} == propose"
219
270
  prompt: >
220
271
  Pass A found these candidate aggregates. Which evidence should the map be
221
- built from? (`{{chosen_strategy}}` is proposed: the roots the code declares
222
- where operations act on them, otherwise the highest coverage, ties broken by
223
- fewest single-file candidates. Coverage is how much code a strategy placed,
224
- not whether it placed it right — compare the aggregate counts too.)
272
+ built from? (`{{chosen_strategy}}` is proposed and listed first: the roots the
273
+ code declares where operations act on them, otherwise the highest coverage, ties
274
+ broken by fewest single-file candidates. Coverage is how much code a strategy
275
+ placed, not whether it placed it right — compare the aggregate counts too.)
225
276
  options_from_variable: strategy_options
226
- preselect_from: chosen_strategy
277
+ preselect_from: forced_option
227
278
  single_option_confirm: true
228
279
  outputs:
229
280
  - { name: picked, from: selection }
@@ -112,9 +112,11 @@
112
112
  # generated, which subtrees are scanned, and what technology the derived rules name.
113
113
  # select-stack detects all three from the repo's own files and proposes them, so answering
114
114
  # it on an existing codebase is mostly confirmation — but it is a person's confirmation,
115
- # which is exactly what detection on a legacy repo needs. A declared lane root folder that is
116
- # not a directory stops this run with the fix named rather than producing a scaffold
117
- # reverse-engineered from nothing.
115
+ # which is exactly what detection on a legacy repo needs. A lane can be declared before
116
+ # it is built: a declared lane whose lane root folder does not exist yet is governed as
117
+ # absent for this run, with a warning naming it, and its governance is left to the
118
+ # developer. When no declared lane's folder exists the run stops, rather than producing a
119
+ # scaffold reverse-engineered from nothing.
118
120
  #
119
121
  # WHICH LANES EXIST is therefore the DECLARATION's answer, and it answers in both
120
122
  # directions — a project that declared `(none)` for the UI lane gets no UI half, no
@@ -149,8 +151,8 @@
149
151
  # =============================================================================
150
152
 
151
153
  name: aw-spec-onboard
152
- 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, never writes one. No migration content; never modifies source.
153
- version: 2.3.0
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.5.0
154
156
  agent_agnostic: true
155
157
  dialect: aw-spec.orchestrator/v1
156
158
 
@@ -189,7 +191,7 @@ standards:
189
191
  vars:
190
192
  # The provenance stamp every generated file carries. It names THIS workflow's version,
191
193
  # so it moves with `version:` above — one value, never a literal repeated per prompt.
192
- generated_by: "aw-spec-onboard/2.3.0"
194
+ generated_by: "aw-spec-onboard/2.5.0"
193
195
  bundle_present: "false" # set by the scan (the bundle holds concepts); reported, gates nothing
194
196
  conformant: "false" # OKF conformance-gate driver
195
197
  pack_present: "false" # the project tier exists ⇒ repo_gov, so this context AMENDS it
@@ -241,24 +243,29 @@ workflow:
241
243
  # what to scan), and what each lane's stack is (so the derived rules name the
242
244
  # right technology).
243
245
  #
244
- # The on-disk check is ON here, unlike in constitution: this run is about to READ
245
- # those directories. A declared lane root folder that is not there stops the run with the fix
246
- # named, rather than producing a governance scaffold reverse-engineered from
247
- # nothing.
246
+ # This run is about to READ those directories, so it needs at least one to exist.
247
+ # A lane can be declared before it is built, so a lane root folder that does not
248
+ # exist yet is allowed here: that lane is governed as absent below, and the scan
249
+ # reads the lanes whose folders exist. When none exists the run stops rather than
250
+ # producing a governance scaffold reverse-engineered from nothing.
248
251
  - id: lane_model
249
252
  action: execute_script
250
253
  script: { ref: actions }
251
254
  subcommand: lane-verify
252
- args: ["--command", "/appweaver onboard"]
255
+ args: ["--command", "/appweaver onboard", "--allow-missing-root"]
253
256
  outputs:
254
257
  - { name: lane_model_ok, from: "$.ok" }
255
258
  - { name: lane_model_issues, from: "$.message" }
256
259
  - { name: lane_kinds, from: "$.kinds" }
260
+ - { name: kinds_on_disk, from: "$.kinds_on_disk" }
261
+ - { name: missing_roots, from: "$.missing_roots" }
257
262
  - { name: be_declared, from: "$.backend_declared" }
263
+ - { name: be_built, from: "$.backend_root_exists" }
258
264
  - { name: be_lane_id, from: "$.backend_lane_id" }
259
265
  - { name: be_profile, from: "$.backend_profile" }
260
266
  - { name: be_root, from: "$.backend_source_path" }
261
267
  - { name: fe_declared, from: "$.frontend_declared" }
268
+ - { name: fe_built, from: "$.frontend_root_exists" }
262
269
  - { name: fe_lane_id, from: "$.frontend_lane_id" }
263
270
  - { name: fe_profile, from: "$.frontend_profile" }
264
271
  - { name: fe_root, from: "$.frontend_source_path" }
@@ -283,13 +290,24 @@ workflow:
283
290
  nor a UI lane, so onboard has no source to reverse-engineer. Re-run
284
291
  /appweaver select-stack and declare at least one lane.
285
292
 
293
+ - id: lane_built_gate
294
+ action: verify_context
295
+ require_non_empty: kinds_on_disk
296
+ on_empty: stop
297
+ on_empty_message: >
298
+ /appweaver onboard has no code to reverse-engineer: {{missing_roots}}
299
+ onboard derives governance from the code of the lanes whose folders exist, and
300
+ no declared lane has one yet.
301
+
286
302
  # 1. DETERMINISTIC scan of the current codebase → JSON substrate (files, churn,
287
303
  # categories, aggregate_signals = candidate feature roots), and reports whether
288
304
  # a bundle already holds curation.
289
305
  #
290
306
  # SCOPED BY THE DECLARATION. Backend signals come from outside the UI lane's
291
307
  # subtree (and inside the service lane's); frontend signals come from the UI
292
- # lane's subtree alone. This substrate is inlined verbatim into every agent
308
+ # lane's subtree alone. A project with a UI lane and no service lane has no backend
309
+ # signals: its candidates are the API resources the UI calls, the same reading
310
+ # inventory proposed the map from. This substrate is inlined verbatim into every agent
293
311
  # request this run makes, so an unscoped scan puts the whole frontend file list in
294
312
  # front of the step deriving the SERVICE rules and every backend aggregate in front
295
313
  # of the steps deriving the UI's — while each prompt tells the model to ignore what
@@ -314,11 +332,13 @@ workflow:
314
332
  - { name: bundle_dir, from: "$.bundle_dir" }
315
333
  - { name: bundle_present, from: "$.bundle_present" }
316
334
  - { name: bundle_md_count, from: "$.bundle_md_count" }
317
- # 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
318
336
  # where a candidate root reached lane strength (a frontend build config, or a
319
337
  # real set of component/view files) — loose scripts and stylesheets under a
320
- # wwwroot/ are reported in the substrate but are NOT a lane.
321
- - { 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" }
322
342
  - { name: frontend_separable, from: "$.frontend_separable" }
323
343
  - { name: frontend_roots, from: "$.frontend_roots" }
324
344
  - { name: frontend_root_count, from: "$.frontend_root_count" }
@@ -335,13 +355,6 @@ workflow:
335
355
  - { name: scoped_service_root, from: "$.declared_service_root" }
336
356
  - { name: scoped_ui_root, from: "$.declared_ui_root" }
337
357
 
338
- # 1b. Substance gate: no candidate features → nothing to map.
339
- - id: require_aggregates
340
- action: verify_context
341
- require_true: aggregate_count
342
- on_false: stop
343
- on_false_message: "Scan found no candidate features in {{root}} (file_count={{file_count}}). Point /appweaver onboard at a codebase with a recognizable domain/vertical-slice layout, or scan a subtree that contains one."
344
-
345
358
  # 1c. WHICH BOUNDED CONTEXT, AND IS IT THIS ONE'S TURN. onboard runs once per
346
359
  # context, so before anything is written it reads the map inventory
347
360
  # produced and asks four questions: is this a context the map knows, what
@@ -494,13 +507,15 @@ workflow:
494
507
  # The declaration selects the subtree; it never supplies a word of the governance.
495
508
  # No service lane ⇒ the technical lint loop has nothing to check, so its guard is
496
509
  # satisfied rather than left at its "not yet verified" default; a declared lane
497
- # restores it, because that half does get authored and does get linted.
510
+ # restores it, because that half does get authored and does get linted. A lane is
511
+ # governed when it is declared AND its lane root folder exists: one declared before it
512
+ # is built has no code to reverse-engineer, so it is governed as absent.
498
513
  - id: svc_lane_absent
499
514
  action: set
500
515
  vars: { svc_lane: "false", technical_ok: "true", setup_tech_flag: "--no-technical" }
501
516
  - id: svc_lane_declared
502
517
  action: set
503
- condition: "{{be_declared}}"
518
+ condition: "{{be_built}}"
504
519
  vars: { svc_lane: "true", technical_ok: "false", setup_tech_flag: "" }
505
520
 
506
521
  # A repo with no service lane. Rare in brownfield and real — a static site, a
@@ -510,7 +525,7 @@ workflow:
510
525
  # someone to notice that guardrails/<p>/service/ is empty.
511
526
  - id: svc_lane_warning
512
527
  action: verify_context
513
- require_true: svc_lane
528
+ require_true: be_declared
514
529
  on_false: continue
515
530
  on_false_message: >
516
531
  No service lane in .appweaver/stacks.json — skipping the TECHNICAL constitution
@@ -521,24 +536,62 @@ workflow:
521
536
  this repo does have backend source, run /appweaver select-stack, declare it and
522
537
  re-run onboard.
523
538
 
539
+ # A service lane declared before it is built — a new service planned beside a front end
540
+ # that exists. Its governance is the developer's to provide before the forward
541
+ # pipeline builds it; this run authors none of it.
542
+ - id: svc_lane_unbuilt_warning
543
+ action: verify_context
544
+ condition: "{{be_declared}}"
545
+ require_true: be_built
546
+ on_false: continue
547
+ on_false_message: >
548
+ Service lane '{{be_lane_id}}' ({{be_profile}}) is declared at {{be_root}}, which
549
+ does not exist yet, so there is no service code to reverse-engineer. This run
550
+ authors none of the service lane's governance — the TECHNICAL constitution half,
551
+ the service rule set and the service design/delta-design template overrides.
552
+ Provide them before building the lane through the forward pipeline. Once the lane
553
+ has code, naming an onboarded context (/appweaver onboard <Context>) reverse-engineers
554
+ them from it instead.
555
+
524
556
  - id: ui_lane_absent
525
557
  action: set
526
558
  vars:
527
559
  ui_lane: "false"
528
560
  ui_evidence: ""
529
561
  ui_declaration_note: "no frontend lane is declared in .appweaver/stacks.json — this repo is governed as backend-only"
530
- - id: ui_lane_declared
562
+ - id: ui_lane_unbuilt
531
563
  action: set
532
564
  condition: "{{fe_declared}}"
565
+ vars:
566
+ ui_declaration_note: "lane '{{fe_lane_id}}' ({{fe_profile}}) is declared at {{fe_root}}, which does not exist yet — no UI governance was authored for it"
567
+ - id: ui_lane_declared
568
+ action: set
569
+ condition: "{{fe_built}}"
533
570
  vars:
534
571
  ui_lane: "true"
535
572
  ui_evidence: "lane '{{fe_lane_id}}' ({{fe_profile}}) is declared at the lane root folder {{fe_root}}; the scan found UI source there — {{frontend_roots}}"
536
573
  ui_declaration_note: "lane '{{fe_lane_id}}' ({{fe_profile}}) at {{fe_root}} — the declared lane root folder this run reverse-engineered the UI governance out of"
537
574
 
575
+ # A frontend lane declared before it is built — a new front end planned beside a
576
+ # service that exists. Its governance is the developer's to provide before the forward
577
+ # pipeline builds it; this run authors none of it.
578
+ - id: ui_lane_unbuilt_warning
579
+ action: verify_context
580
+ condition: "{{fe_declared}}"
581
+ require_true: fe_built
582
+ on_false: continue
583
+ on_false_message: >
584
+ Frontend lane '{{fe_lane_id}}' ({{fe_profile}}) is declared at {{fe_root}}, which
585
+ does not exist yet, so there is no UI code to reverse-engineer. This run authors
586
+ none of the frontend lane's governance — constitution.ui.md, the frontend rule set
587
+ and the UI design/delta-design template overrides. Provide them before building the
588
+ lane through the forward pipeline. Once the lane has code, naming an onboarded
589
+ context (/appweaver onboard <Context>) reverse-engineers them from it instead.
590
+
538
591
  # A declared UI lane whose lane root folder holds nothing a scan recognises as UI. The
539
- # path exists — lane-verify already refused the run otherwise — so this is not a
540
- # broken declaration, it is an empty one: a lane scaffolded but not yet built, or a
541
- # lane root folder one directory too high. Announce and continue rather than stop: the
592
+ # path exists — a lane whose folder does not is governed as absent above — so this is
593
+ # not a broken declaration, it is an empty one: a lane scaffolded but not yet built, or
594
+ # a lane root folder one directory too high. Announce and continue rather than stop: the
542
595
  # declaration is a person's statement and is left exactly as it stands, but the UI
543
596
  # governance below will be thin, and it is better to say why now than to let someone
544
597
  # discover a UX base derived from an empty directory.
@@ -706,6 +759,9 @@ workflow:
706
759
  confirm those against what the file actually is and move or drop one it got wrong. Keep the key and leave it EMPTY where the
707
760
  aggregate has no UI (and on a backend-only repo, where every concept has none);
708
761
  never fold these paths into `source_files`, which stays the service surface.
762
+ Where the aggregate signals' `strategy` is `ui` (a project with no service
763
+ lane), every file a signal lists is the UI lane's, its `api_files` included: list
764
+ them all under `ui_source_files` and leave `source_files` empty.
709
765
  - `aggregates/index.md` — the BOUNDED-CONTEXT / feature map (no frontmatter beyond
710
766
  OKF index rules). Group the links under a `## <BoundedContext>` heading per
711
767
  context, so the file shows which aggregates belong to which context rather than one
@@ -2249,6 +2305,16 @@ workflow:
2249
2305
  signature form + the ports/contracts. Keep exactly one subsection whose heading
2250
2306
  contains the word "Integration" (the External Integrations table — one port/adapter row
2251
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.
2252
2318
  - §3 Cross-Cutting Concerns — one row per concern THIS project's governance carries, read
2253
2319
  from the technical constitution (Serves = a spec id or a §5 DA-n). Do not import a
2254
2320
  standard concern list the constitution does not evidence.
@@ -2307,9 +2373,14 @@ workflow:
2307
2373
  the generated master template. Never name a layering — neither one you know nor one the
2308
2374
  master did not derive.
2309
2375
  - CARRY OVER the neutral parts of rev-templates/delta-design-template.skeleton.md (in
2310
- context): Change summary, Cross-section impact, Regression targets, New assumptions/
2311
- deviations (DA-n / WA-n), On merge — word for word: finalize-delta merges the spec and
2312
- 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 +
2313
2384
  contracts, no code); a shared element is MODIFIED in place, never duplicated; every
2314
2385
  changed element serves a spec id.
2315
2386
  - This aligns with the delta-plan module, which classifies "per the master's OWN
@@ -2656,4 +2727,4 @@ outputs:
2656
2727
  amended_domain_files: "{{amended_domain_files}}"
2657
2728
  amended_service_files: "{{amended_service_files}}"
2658
2729
  amended_ui_files: "{{amended_ui_files}}"
2659
- next: "Onboarded bounded context {{bc}} ({{bc_feature_count}} feature(s): {{bc_features}}). Its aggregate concepts ({{bc_aggregates}}) are in {{bundle_dir}}, each keyed `bounded_context: {{bc}}` and listing the features that act on it — the bundle is the project's knowledge, one concept per aggregate, never one per feature — and its specs tree now exists at {{bc_dir}}/ with a directory per feature ({{feature_dirs}}) — EMPTY on purpose: the directory says the feature is known, and the documents that describe it are written per feature by /appweaver adopt and per context by /appweaver specify. {{bc}} is now the ACTIVE bounded context (.appweaver/context.json), so /appweaver select-feature lists exactly these features and nothing from a neighbouring context; switch with /appweaver select-bounded-context. ONBOARD RUNS ONCE PER CONTEXT: contexts already done — {{onboarded_contexts}}; still open — {{remaining_contexts}}; the order the map gives is {{contexts_ordered}}. Run /appweaver onboard <NextContext> for each remaining one, or with no argument to take the next one automatically. GOVERNANCE — {{gov_mode}}. The project tier is the PROVISIONAL governance scaffold — the split constitution, the domain rule set at guardrails/{{domain_slug}}/domain/*.domain.md + domain-rule-bindings.md, each declared LANE's rule set and constitution half, the activated domain {{active_domain}}, and the code-derived design / delta-design / domain-model template overrides in .appweaver/templates/overrides/. The first context authors it; every later context AMENDS it from its own code, additively — rules, principles and map entries its code evidences, never a rewrite, renumbering or deletion of what an earlier context wrote — and a lane tier that did not exist yet is authored in full. Files this run amended — domain: {{amended_domain_files}}; service: {{amended_service_files}}; frontend: {{amended_ui_files}}. Search them for `Divergence ({{bc}})`: each marks a rule this context's code contradicts, left as written for you to decide with /appweaver constitution, which is also how you amend the tier by hand. The templates are written once per lane and never amended. To re-run this governance pass later — after declaring a lane with /appweaver select-stack, say — name an onboarded context: /appweaver onboard <Context>. ONE HALF AND ONE RULE SET PER LANE: service_lane / ui_lane report which lanes .appweaver/stacks.json declared, and the declaration is what decided. A lane declared (none) in select-stack gets none of it, which is the correct outcome and not an omission. LANE DECLARATION: read, never written — select-stack is its sole writer, and this run scanned the lane root folders it named (scanned_service_root / scanned_ui_root); scanned_backend_files / scanned_ui_files report each. ui_source_evidence_found false means the declared lane root folder of the UI lane held nothing a scan recognises as UI, so its governance is thin — move the lane root folder with /appweaver select-stack and re-run. The build/test/run commands on each lane are that PROFILE's defaults and may not match a legacy layout — review them. Because the domain is activated, resolve-domain + resolve-tech route the WHOLE forward pipeline to these code-derived rules — never another project's rules as a fallback. THE BOUNDARIES CAME FROM THE MAP, not from this run: /appweaver inventory proposed them and you confirmed them in specs/proposed-inventory.md. To change a boundary, edit that file and run /appweaver inventory apply — a context whose concepts already exist is not re-mapped; naming it re-runs only its governance pass. Develop an EXISTING feature just-in-time: /appweaver adopt <Feature> reverse-engineers its spec.md + backend-plan.md + source-map.md and deepens the OKF concept of the aggregate it acts on. Develop a NEW feature straight through the forward pipeline — /appweaver select-feature FIRST (specify and every stage after it read the active feature and STOP without one), then specify → plan → analyze-plan → implement → build → analyze-slice → run. 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), and re-derive the master plans after it with /appweaver plan main → analyze-plan — finalize-delta merges a change into spec.md and domain-model.md, never into the plans. If a validation_ok is false, its constitution half needs a manual touch (see its Sync Impact Report)."
2730
+ next: "Onboarded bounded context {{bc}} ({{bc_feature_count}} feature(s): {{bc_features}}). Its aggregate concepts ({{bc_aggregates}}) are in {{bundle_dir}}, each keyed `bounded_context: {{bc}}` and listing the features that act on it — the bundle is the project's knowledge, one concept per aggregate, never one per feature — and its specs tree now exists at {{bc_dir}}/ with a directory per feature ({{feature_dirs}}) — EMPTY on purpose: the directory says the feature is known, and the documents that describe it are written per feature by /appweaver adopt and per context by /appweaver specify. {{bc}} is now the ACTIVE bounded context (.appweaver/context.json), so /appweaver select-feature lists exactly these features and nothing from a neighbouring context; switch with /appweaver select-bounded-context. ONBOARD RUNS ONCE PER CONTEXT: contexts already done — {{onboarded_contexts}}; still open — {{remaining_contexts}}; the order the map gives is {{contexts_ordered}}. Run /appweaver onboard <NextContext> for each remaining one, or with no argument to take the next one automatically. GOVERNANCE — {{gov_mode}}. The project tier is the PROVISIONAL governance scaffold — the split constitution, the domain rule set at guardrails/{{domain_slug}}/domain/*.domain.md + domain-rule-bindings.md, each declared LANE's rule set and constitution half, the activated domain {{active_domain}}, and the code-derived design / delta-design / domain-model template overrides in .appweaver/templates/overrides/. The first context authors it; every later context AMENDS it from its own code, additively — rules, principles and map entries its code evidences, never a rewrite, renumbering or deletion of what an earlier context wrote — and a lane tier that did not exist yet is authored in full. Files this run amended — domain: {{amended_domain_files}}; service: {{amended_service_files}}; frontend: {{amended_ui_files}}. Search them for `Divergence ({{bc}})`: each marks a rule this context's code contradicts, left as written for you to decide with /appweaver constitution, which is also how you amend the tier by hand. The templates are written once per lane and never amended. To re-run this governance pass later — after declaring a lane with /appweaver select-stack, say — name an onboarded context: /appweaver onboard <Context>. ONE HALF AND ONE RULE SET PER LANE: service_lane / ui_lane report which lanes this run governed — each lane .appweaver/stacks.json declares whose lane root folder exists. A lane declared (none) in select-stack gets none of it, which is the correct outcome and not an omission, and so does a lane whose folder does not exist yet: its governance is yours to provide before the forward pipeline builds it. LANE DECLARATION: read, never written — select-stack is its sole writer, and this run scanned the lane root folders it named (scanned_service_root / scanned_ui_root); scanned_backend_files / scanned_ui_files report each. ui_source_evidence_found false means the declared lane root folder of the UI lane held nothing a scan recognises as UI, so its governance is thin — move the lane root folder with /appweaver select-stack and re-run. The build/test/run commands on each lane are that PROFILE's defaults and may not match a legacy layout — review them. Because the domain is activated, resolve-domain + resolve-tech route the WHOLE forward pipeline to these code-derived rules — never another project's rules as a fallback. THE BOUNDARIES CAME FROM THE MAP, not from this run: /appweaver inventory proposed them and you confirmed them in specs/proposed-inventory.md. To change a boundary, edit that file and run /appweaver inventory apply — a context whose concepts already exist is not re-mapped; naming it re-runs only its governance pass. Develop an EXISTING feature just-in-time: /appweaver adopt <Feature> reverse-engineers its spec.md + backend-plan.md + source-map.md and deepens the OKF concept of the aggregate it acts on. Develop a NEW feature straight through the forward pipeline — /appweaver select-feature FIRST (specify and every stage after it read the active feature and STOP without one), then specify → plan → analyze-plan → implement → build → analyze-slice → run. 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), and re-derive the master plans after it with /appweaver plan main → analyze-plan — finalize-delta merges a change into spec.md and domain-model.md, never into the plans. If a validation_ok is false, its constitution half needs a manual touch (see its Sync Impact Report)."
@@ -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."
@@ -3,17 +3,19 @@
3
3
  # The brownfield inventory logic lives ONCE in ../python/aw-inventory.py (a
4
4
  # faithful cross-platform port of Reference/okf-inventory.sh + aggregate-root
5
5
  # signals). This wrapper exists only so the orchestrator's execute_script,
6
- # which dispatches sh:/ps1: variants, can invoke it. All args (subcommand +
7
- # options) pass straight through.
6
+ # which dispatches sh:/ps1: variants, can invoke it. Args pass through, each flag
7
+ # joined to its value (see below).
8
8
  #
9
9
  # aw-inventory.ps1 scan [--root <dir>] [--out <path>] [--bundle <dir>] [--config <yml>]
10
10
  # [--service-root <p>] [--ui-root <p>] (the declared lane paths:
11
11
  # backend signals come from outside the UI subtree, frontend
12
12
  # signals from inside it, so neither carries the other's files)
13
13
  # aw-inventory.ps1 propose [--root <dir>] [--service-root <p>] [--ui-root <p>]
14
- # [--strategy structure|reference|persistence] [--out <md>] [--map <json>]
14
+ # [--strategy domain|structure|reference|persistence|ui]
15
+ # [--out <md>] [--map <json>]
15
16
  # (Pass A+B+C: writes the editable specs/proposed-inventory.md
16
- # and the machine map onboard reads)
17
+ # and the machine map onboard reads; with a UI lane and no
18
+ # service lane the map is read from the UI lane, strategy `ui`)
17
19
  # aw-inventory.ps1 apply [--root <dir>] [--proposal <md>] [--map <json>]
18
20
  # (validates the operator's edits to the proposal and
19
21
  # regenerates the machine map; reports the row it cannot resolve)
@@ -28,5 +30,18 @@
28
30
  # aw-inventory.ps1 validate --bundle <dir> [--repo-root <dir>]
29
31
  $ErrorActionPreference = "Stop"
30
32
  $core = Join-Path $PSScriptRoot "..\python\aw-inventory.py"
31
- & python3 $core @args
33
+ # Each value travels to python as ONE `--flag=value` argument. Windows PowerShell 5.1, and
34
+ # pwsh before 7.3, drop an empty argument when calling a program, which would leave a flag
35
+ # such as --service-root (empty in a project with no service lane) or --strategy (empty on
36
+ # a first run) with the next flag as its value, or with none. Every flag here takes a value.
37
+ $fwd = @()
38
+ for ($i = 0; $i -lt $args.Count; $i++) {
39
+ $a = "$($args[$i])"
40
+ if ($a.StartsWith('--') -and -not $a.Contains('=') -and $i + 1 -lt $args.Count) {
41
+ $fwd += ($a + '=' + $args[++$i])
42
+ } else {
43
+ $fwd += $a
44
+ }
45
+ }
46
+ & python3 $core @fwd
32
47
  exit $LASTEXITCODE