aw-spec-cli 0.1.31 → 0.1.32

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.
@@ -113,6 +113,14 @@ project it does not exist yet — the whole point of declaring `backend/` on day
113
113
  `build`, `run`, and before `onboard` reverse-engineers a codebase out of it. That split is what lets one file serve a project with no code yet and a project that is
114
114
  nothing but code.
115
115
 
116
+ A lane can also be declared before it is built on a project that has code — a new front end
117
+ planned beside an existing service, or the reverse. `inventory` and `onboard` read code, so
118
+ they pass `--allow-missing-root` and read the lanes whose folders exist (`kinds_on_disk`): a
119
+ declared lane whose lane root folder is not there yet is read and governed as absent, and
120
+ named in a warning and in the proposal's Scanned line. Its governance is the developer's to
121
+ provide before the forward pipeline builds it. Both stop, with each missing folder named
122
+ (`missing_roots`), only when no declared lane's folder exists.
123
+
116
124
  Two failure classes, deliberately distinguished:
117
125
 
118
126
  - **MISSING** — the phase needs a lane of this kind and none is declared. Reported only for
@@ -691,7 +699,7 @@ implementation and the twins cannot drift.
691
699
  | `contract-check --plan p [--doc d]` | the operation ids a frontend plan cites (its §5 `Operation id` column, or a delta plan's `Contract Binding` rows; never the request table) against the document's `operationId`s — by script, no model. `drafted[]` names the cited ids that are only draft operations |
692
700
  | `contract-impact --delta-backend p --frontend-plan p [--delta-frontend p]` | the operations a change's service delta marks `changed` or `removed` in its "Contract deltas for the frontend lane" table that the frontend master plan binds to, and which of them the change's frontend delta plan does not name (`unaddressed[]`) — `analyze-plan-delta`'s warning. By script, no model |
693
701
  | `lane-resolve [--kind k] [--id lane] [--feature-dir p]` | read; exit 0 always. `exists` reports the file, `declared` reports the lanes — the shipped-but-unfilled seed is `exists:true, declared:false`. Returns the matched lane **and** both kinds flattened — `backend_source_path`, `frontend_root_exists`, `backend_build` … — so a workflow binds one variable instead of calling twice — and the working-lane fields of §2a |
694
- | `lane-verify [--require backend\|frontend]… [--allow-missing-root] [--working-only] [--command phase]` | **the gate**: is the declaration usable for the phase about to run. Never writes, and **always exits 0** — the calling workflow's `verify_context` owns the stop, so the reason arrives as text. `--working-only` checks on disk only the working lanes' root folders and the required kinds (§2a) |
702
+ | `lane-verify [--require backend\|frontend]… [--allow-missing-root] [--working-only] [--command phase]` | **the gate**: is the declaration usable for the phase about to run. Never writes, and **always exits 0** — the calling workflow's `verify_context` owns the stop, so the reason arrives as text. `--working-only` checks on disk only the working lanes' root folders and the required kinds (§2a). Whatever the flags, `kinds_on_disk[]` lists the kinds whose lane root folder exists and `missing_roots` the issue each declared lane without one raises when `--allow-missing-root` is not passed |
695
703
  | `lane-select [--lane backend\|frontend\|both] [--default] [--preselect text]` | read or write `.appweaver/lane.json` (§2a). No argument reads it and returns the `select_options[]` the declared stacks offer; `--lane` writes one of them; `--default` writes the default — `select-stack`'s call |
696
704
  | `cr-lanes [--cr-id id [--feature-dir p] [--stamp] [--set both\|backend\|frontend]]` | the lanes a change touches (§2a). No `--cr-id` returns the `select_options[]` the declared stacks offer, both first; with it, reads that change's `**Lanes**:` line — `lanes[]` is what it touches among the lanes declared now, every declared lane when nothing is recorded. `--set` writes an answer (a whole option line is accepted); `--stamp` alone writes every declared lane |
697
705
  | `slice-gate-stamp --cr-id id --lane backend\|frontend [--feature-dir p] [--tests-green b] [--tests-ran b] [--requirements-ok b] [--findings json]` | write one lane's `analyze-slice-delta` result to `changes/<id>/.aw-slice-gate.<lane>.json` (§2a): `verdict` `pass` when no test failed and the requirements are met, else `fail`, with `tests_ran` (false when the lane declares no test command, so nothing ran), the findings counts and the sha256 of the delta-spec and the lane's delta plan it was gated against. Findings that cannot be read are recorded as `null`, never as none |
@@ -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.4.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.4.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
@@ -335,13 +353,6 @@ workflow:
335
353
  - { name: scoped_service_root, from: "$.declared_service_root" }
336
354
  - { name: scoped_ui_root, from: "$.declared_ui_root" }
337
355
 
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
356
  # 1c. WHICH BOUNDED CONTEXT, AND IS IT THIS ONE'S TURN. onboard runs once per
346
357
  # context, so before anything is written it reads the map inventory
347
358
  # produced and asks four questions: is this a context the map knows, what
@@ -494,13 +505,15 @@ workflow:
494
505
  # The declaration selects the subtree; it never supplies a word of the governance.
495
506
  # No service lane ⇒ the technical lint loop has nothing to check, so its guard is
496
507
  # 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.
508
+ # restores it, because that half does get authored and does get linted. A lane is
509
+ # governed when it is declared AND its lane root folder exists: one declared before it
510
+ # is built has no code to reverse-engineer, so it is governed as absent.
498
511
  - id: svc_lane_absent
499
512
  action: set
500
513
  vars: { svc_lane: "false", technical_ok: "true", setup_tech_flag: "--no-technical" }
501
514
  - id: svc_lane_declared
502
515
  action: set
503
- condition: "{{be_declared}}"
516
+ condition: "{{be_built}}"
504
517
  vars: { svc_lane: "true", technical_ok: "false", setup_tech_flag: "" }
505
518
 
506
519
  # A repo with no service lane. Rare in brownfield and real — a static site, a
@@ -510,7 +523,7 @@ workflow:
510
523
  # someone to notice that guardrails/<p>/service/ is empty.
511
524
  - id: svc_lane_warning
512
525
  action: verify_context
513
- require_true: svc_lane
526
+ require_true: be_declared
514
527
  on_false: continue
515
528
  on_false_message: >
516
529
  No service lane in .appweaver/stacks.json — skipping the TECHNICAL constitution
@@ -521,24 +534,62 @@ workflow:
521
534
  this repo does have backend source, run /appweaver select-stack, declare it and
522
535
  re-run onboard.
523
536
 
537
+ # A service lane declared before it is built — a new service planned beside a front end
538
+ # that exists. Its governance is the developer's to provide before the forward
539
+ # pipeline builds it; this run authors none of it.
540
+ - id: svc_lane_unbuilt_warning
541
+ action: verify_context
542
+ condition: "{{be_declared}}"
543
+ require_true: be_built
544
+ on_false: continue
545
+ on_false_message: >
546
+ Service lane '{{be_lane_id}}' ({{be_profile}}) is declared at {{be_root}}, which
547
+ does not exist yet, so there is no service code to reverse-engineer. This run
548
+ authors none of the service lane's governance — the TECHNICAL constitution half,
549
+ the service rule set and the service design/delta-design template overrides.
550
+ Provide them before building the lane through the forward pipeline. Once the lane
551
+ has code, naming an onboarded context (/appweaver onboard <Context>) reverse-engineers
552
+ them from it instead.
553
+
524
554
  - id: ui_lane_absent
525
555
  action: set
526
556
  vars:
527
557
  ui_lane: "false"
528
558
  ui_evidence: ""
529
559
  ui_declaration_note: "no frontend lane is declared in .appweaver/stacks.json — this repo is governed as backend-only"
530
- - id: ui_lane_declared
560
+ - id: ui_lane_unbuilt
531
561
  action: set
532
562
  condition: "{{fe_declared}}"
563
+ vars:
564
+ 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"
565
+ - id: ui_lane_declared
566
+ action: set
567
+ condition: "{{fe_built}}"
533
568
  vars:
534
569
  ui_lane: "true"
535
570
  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
571
  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
572
 
573
+ # A frontend lane declared before it is built — a new front end planned beside a
574
+ # service that exists. Its governance is the developer's to provide before the forward
575
+ # pipeline builds it; this run authors none of it.
576
+ - id: ui_lane_unbuilt_warning
577
+ action: verify_context
578
+ condition: "{{fe_declared}}"
579
+ require_true: fe_built
580
+ on_false: continue
581
+ on_false_message: >
582
+ Frontend lane '{{fe_lane_id}}' ({{fe_profile}}) is declared at {{fe_root}}, which
583
+ does not exist yet, so there is no UI code to reverse-engineer. This run authors
584
+ none of the frontend lane's governance — constitution.ui.md, the frontend rule set
585
+ and the UI design/delta-design template overrides. Provide them before building the
586
+ lane through the forward pipeline. Once the lane has code, naming an onboarded
587
+ context (/appweaver onboard <Context>) reverse-engineers them from it instead.
588
+
538
589
  # 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
590
+ # path exists — a lane whose folder does not is governed as absent above — so this is
591
+ # not a broken declaration, it is an empty one: a lane scaffolded but not yet built, or
592
+ # a lane root folder one directory too high. Announce and continue rather than stop: the
542
593
  # declaration is a person's statement and is left exactly as it stands, but the UI
543
594
  # governance below will be thin, and it is better to say why now than to let someone
544
595
  # discover a UX base derived from an empty directory.
@@ -706,6 +757,9 @@ workflow:
706
757
  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
758
  aggregate has no UI (and on a backend-only repo, where every concept has none);
708
759
  never fold these paths into `source_files`, which stays the service surface.
760
+ Where the aggregate signals' `strategy` is `ui` (a project with no service
761
+ lane), every file a signal lists is the UI lane's, its `api_files` included: list
762
+ them all under `ui_source_files` and leave `source_files` empty.
709
763
  - `aggregates/index.md` — the BOUNDED-CONTEXT / feature map (no frontmatter beyond
710
764
  OKF index rules). Group the links under a `## <BoundedContext>` heading per
711
765
  context, so the file shows which aggregates belong to which context rather than one
@@ -2656,4 +2710,4 @@ outputs:
2656
2710
  amended_domain_files: "{{amended_domain_files}}"
2657
2711
  amended_service_files: "{{amended_service_files}}"
2658
2712
  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)."
2713
+ 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)."
@@ -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