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.
- package/assets/.appweaver/STACK-PROFILES.md +9 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-inventory.orch.yaml +77 -26
- package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +81 -27
- package/assets/.appweaver/scripts/powershell/aw-inventory.ps1 +20 -5
- package/assets/.appweaver/scripts/python/aw-inventory.py +2064 -259
- package/assets/.appweaver/scripts/python/aw-stack.py +13 -4
- package/assets/.appweaver/scripts/python/aw-xref.py +63 -43
- package/assets/.appweaver/templates/okf-concept-template.md +3 -1
- package/assets/.appweaver/templates/proposed-inventory-template.md +52 -13
- package/assets/foundations/java/Hexagonal/unpack-foundation.ps1 +168 -0
- package/package.json +1 -1
|
@@ -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
|
|
14
|
-
# a declared aggregate-root type is the aggregate, a UI
|
|
15
|
-
# the capability, and a slice under the
|
|
16
|
-
#
|
|
17
|
-
#
|
|
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.
|
|
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.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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", "{{
|
|
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
|
|
208
|
-
|
|
209
|
-
|
|
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
|
|
222
|
-
where operations act on them, otherwise the highest coverage, ties
|
|
223
|
-
fewest single-file candidates. Coverage is how much code a strategy
|
|
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:
|
|
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
|
|
116
|
-
#
|
|
117
|
-
#
|
|
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.
|
|
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.
|
|
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
|
-
#
|
|
245
|
-
#
|
|
246
|
-
#
|
|
247
|
-
#
|
|
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.
|
|
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: "{{
|
|
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:
|
|
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:
|
|
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
|
|
540
|
-
# broken declaration, it is an empty one: a lane scaffolded but not yet built, or
|
|
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
|
|
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.
|
|
7
|
-
#
|
|
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]
|
|
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
|
-
|
|
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
|