aw-spec-cli 0.1.19 → 0.1.21
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/DESIGN-FORMAT.md +17 -9
- package/assets/.appweaver/ORCH-DIALECT.md +8 -3
- package/assets/.appweaver/orchestrator/modules/delta-spec.orch.yaml +26 -3
- package/assets/.appweaver/orchestrator/orchestrator.py +65 -3
- package/assets/.appweaver/orchestrator/rev-templates/delta-design-template.skeleton.md +1 -1
- package/assets/.appweaver/orchestrator/rev-templates/design-template.skeleton.md +4 -2
- package/assets/.appweaver/orchestrator/rev-templates/ui-delta-design-template.skeleton.md +1 -1
- package/assets/.appweaver/orchestrator/rev-templates/ui-design-template.skeleton.md +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +121 -42
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan-delta.orch.yaml +7 -7
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +145 -52
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +18 -10
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +306 -28
- package/assets/.appweaver/orchestrator/workflows/aw-spec-build.orch.yaml +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-characterize.orch.yaml +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-close-delta.orch.yaml +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-constitution.orch.yaml +4 -4
- package/assets/.appweaver/orchestrator/workflows/aw-spec-deploy.orch.yaml +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +114 -30
- package/assets/.appweaver/orchestrator/workflows/aw-spec-foundation.orch.yaml +140 -36
- package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +385 -61
- package/assets/.appweaver/orchestrator/workflows/aw-spec-inventory.orch.yaml +217 -174
- package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +407 -107
- package/assets/.appweaver/orchestrator/workflows/aw-spec-parity.orch.yaml +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +49 -11
- package/assets/.appweaver/orchestrator/workflows/aw-spec-recall-delta.orch.yaml +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-run.orch.yaml +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-select-feature.orch.yaml +29 -23
- package/assets/.appweaver/orchestrator/workflows/aw-spec-select-stack.orch.yaml +47 -23
- package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +19 -11
- package/assets/.appweaver/scripts/bash/aw-inventory.sh +15 -0
- package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +683 -92
- package/assets/.appweaver/scripts/bash/aw-xref.sh +3 -0
- package/assets/.appweaver/scripts/powershell/aw-inventory.ps1 +15 -0
- package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +625 -64
- package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +3 -0
- package/assets/.appweaver/scripts/python/aw-crstate.py +194 -0
- package/assets/.appweaver/scripts/python/aw-foundation.py +151 -0
- package/assets/.appweaver/scripts/python/aw-inventory.py +1829 -63
- package/assets/.appweaver/scripts/python/aw-payload-parity.py +3 -0
- package/assets/.appweaver/scripts/python/aw-plan-coverage.py +423 -0
- package/assets/.appweaver/scripts/python/aw-stack.py +251 -28
- package/assets/.appweaver/scripts/python/aw-testlog.py +148 -0
- package/assets/.appweaver/scripts/python/aw-xref.py +500 -81
- package/assets/.appweaver/stack-profiles/dotnet-webapi.json +1 -0
- package/assets/.appweaver/stack-profiles/java-spring.json +1 -0
- package/assets/.appweaver/stack-profiles/node-express.json +1 -0
- package/assets/.appweaver/stack-profiles/python-fastapi.json +1 -0
- package/assets/.appweaver/templates/constitution-domain-template.md +17 -2
- package/assets/.appweaver/templates/delta-design-template.md +1 -1
- package/assets/.appweaver/templates/delta-domain-model-template.md +1 -1
- package/assets/.appweaver/templates/delta-spec-template.md +4 -3
- package/assets/.appweaver/templates/design-template.md +16 -11
- package/assets/.appweaver/templates/okf-concept-template.md +11 -1
- package/assets/.appweaver/templates/proposed-inventory-template.md +127 -0
- package/assets/.appweaver/templates/source-map-template.md +5 -3
- package/assets/.appweaver/templates/spec-template.md +17 -17
- package/assets/.appweaver/templates/ui-delta-design-template.md +1 -1
- package/assets/.appweaver/templates/ui-design-template.md +4 -4
- package/assets/foundations/dotnet/ModularMonolith/ModularMonolith.pack.txt +4489 -361
- package/assets/foundations/dotnet/VerticalSlice/VerticalSlice.pack.txt +2274 -125
- package/assets/guardrails/projects/dotnet-default-project/domain-rule-bindings.md +10 -6
- package/assets/guardrails/projects/dotnet-default-project/tech-rule-bindings.md +4 -0
- package/assets/guardrails/technology/dotnet-webapi/auditing.md +3 -1
- package/assets/guardrails/technology/dotnet-webapi/coding-standards.md +5 -3
- package/assets/guardrails/technology/dotnet-webapi/cqrs.md +5 -1
- package/assets/guardrails/technology/dotnet-webapi/domain-driven-design.md +13 -5
- package/assets/guardrails/technology/dotnet-webapi/examples/cqrs.md +2 -0
- package/assets/guardrails/technology/dotnet-webapi/examples/domain-driven-design.md +6 -2
- package/assets/guardrails/technology/dotnet-webapi/framework-api-map.md +70 -0
- package/assets/guardrails/technology/dotnet-webapi/technology-constitution.md +3 -0
- package/assets/guardrails/technology/dotnet-webapi/testing.md +10 -0
- package/assets/guardrails/technology/nextjs/ui-constitution.md +8 -1
- package/package.json +1 -1
- package/assets/.appweaver/orchestrator/.state/smoke.state.json +0 -32
|
@@ -11,9 +11,18 @@
|
|
|
11
11
|
# "Slice" is the unit under analysis: the aggregate's code, its paired tests, and
|
|
12
12
|
# its OKF knowledge concept. Three passes over it —
|
|
13
13
|
# 1. BEHAVIOUR run the tests (paired to the spec's acceptance criteria by the
|
|
14
|
-
# design docs); on red, fix up to 3
|
|
14
|
+
# design docs); on red, fix up to 3×, then stop — the passes below would
|
|
15
|
+
# be scoring code already known to be wrong. A slice with NOTHING to run
|
|
16
|
+
# (no test project, or a filter that selected zero tests) is a different
|
|
17
|
+
# state: no fix attempt can help, so it warns, every other pass runs, and
|
|
18
|
+
# the gate fails at the END with what they found.
|
|
15
19
|
# 2. CONFORMANCE check the code against the domain gate overlays AND the bounded
|
|
16
20
|
# context's domain-model.md (validate-only).
|
|
21
|
+
# 2b. COMPOSITION is every dependency the slice injects actually BOUND in the service
|
|
22
|
+
# lane's composition root? The one failure class the other passes cannot
|
|
23
|
+
# see: a container graph is not compile-checked, so an unregistered port
|
|
24
|
+
# builds green, passes unit tests that construct their subject directly,
|
|
25
|
+
# and fails on the first request. Fixed up to 2×.
|
|
17
26
|
# 3. KNOWLEDGE refresh + review the slice's OKF concept (advisory).
|
|
18
27
|
# Every fix made here is recorded in that concept, which is also where the slice's
|
|
19
28
|
# file list is READ from (2.3.0 — `aw-xref concept-paths`, replacing the retired
|
|
@@ -62,7 +71,7 @@
|
|
|
62
71
|
|
|
63
72
|
name: aw-spec-analyze-slice
|
|
64
73
|
description: The design↔code conformance gate — run the slice's tests against its acceptance criteria, check the code against the domain gate overlays AND the bounded context's domain-model.md (every invariant the slice's own aggregate section states must be enforced as written; one another aggregate owns is reported, not blocking), verify the change did not break any referring aggregate (the OKF referrer blast radius) and write a triage report before stopping if it did, refresh + review its OKF concept, and record every fix in it. Sits after finalize-delta on the change path, so the two masters it scores against already carry the change, and before close-delta, so a red gate leaves the change open and recoverable.
|
|
65
|
-
version: 2.
|
|
74
|
+
version: 2.6.0
|
|
66
75
|
agent_agnostic: true
|
|
67
76
|
dialect: aw-spec.orchestrator/v1
|
|
68
77
|
|
|
@@ -87,7 +96,33 @@ standards:
|
|
|
87
96
|
|
|
88
97
|
vars:
|
|
89
98
|
test_ok: "false"
|
|
99
|
+
# TWO WAYS PASS 1 IS NOT GREEN, and they are not the same failure. `tests_red` is a suite
|
|
100
|
+
# that RAN and failed — the fix loop's business, and a stop. `tests_missing` is a slice
|
|
101
|
+
# with nothing to run (no test project, or a slice filter that selected zero tests):
|
|
102
|
+
# nothing to repair, so the loop leaves at once, the run carries on through every other
|
|
103
|
+
# pass, and the gate fails at the end with what they found. See PASS 1.
|
|
104
|
+
tests_red: "false"
|
|
105
|
+
tests_missing: "false"
|
|
106
|
+
tests_present: "true"
|
|
107
|
+
slice_testable: "true"
|
|
108
|
+
not_green: "false"
|
|
109
|
+
test_excerpt: ""
|
|
110
|
+
test_failing_lanes: ""
|
|
111
|
+
test_no_setup: "false"
|
|
112
|
+
test_setup_reason: ""
|
|
113
|
+
zero_match: "false"
|
|
90
114
|
conformance_ok: "false"
|
|
115
|
+
# Composition pass (PASS 2b). Defaults to PASSED, because a project with no service lane
|
|
116
|
+
# has no container to check — the flag is lowered only where a backend lane is declared.
|
|
117
|
+
wiring_ok: "true"
|
|
118
|
+
unbound: ""
|
|
119
|
+
be_declared: "false"
|
|
120
|
+
be_lane_id: ""
|
|
121
|
+
be_source_path: ""
|
|
122
|
+
wiring_notes: ""
|
|
123
|
+
# The integrity fence blocks through this, not through `integrity_ok` — a fence skipped
|
|
124
|
+
# because there were no tests to run must not read as a broken referrer.
|
|
125
|
+
integrity_gate_ok: "true"
|
|
91
126
|
# Invariants the model states under ANOTHER aggregate's section that this slice appears
|
|
92
127
|
# to touch — reported, never blocking, since this slice does not own them. Defaulted so
|
|
93
128
|
# the gate message and the outputs read a real value on a run that skipped the pass.
|
|
@@ -128,15 +163,16 @@ workflow:
|
|
|
128
163
|
script: { ref: actions }
|
|
129
164
|
subcommand: feature-paths
|
|
130
165
|
outputs:
|
|
131
|
-
- { name: feature,
|
|
132
|
-
- { name: feature_dir,
|
|
133
|
-
- { name: spec_file,
|
|
166
|
+
- { name: feature, from: "$.feature" }
|
|
167
|
+
- { name: feature_dir, from: "$.feature_dir" }
|
|
168
|
+
- { name: spec_file, from: "$.spec_file" }
|
|
169
|
+
- { name: backend_plan, from: "$.backend_plan" } # the ports + §4.3 wiring the composition pass reads
|
|
134
170
|
|
|
135
171
|
- id: require_feature
|
|
136
172
|
action: verify_context
|
|
137
173
|
require_non_empty: feature_dir
|
|
138
174
|
on_empty: stop
|
|
139
|
-
on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the
|
|
175
|
+
on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the feature, e.g. Order or PatientRegistration), then re-run analyze-slice."
|
|
140
176
|
|
|
141
177
|
# The bounded context's DOMAIN MODEL — in every pass's context beside the spec, and the
|
|
142
178
|
# authority the conformance pass scores against. Since the model split the spec no longer
|
|
@@ -162,6 +198,38 @@ workflow:
|
|
|
162
198
|
- { name: domain_model, from: "$.domain_model" }
|
|
163
199
|
- { name: domain_model_exists, from: "$.domain_model_exists" }
|
|
164
200
|
|
|
201
|
+
# WHICH PART OF THAT MODEL THIS GATE SCORES. The model is filed by aggregate, the specs
|
|
202
|
+
# tree by feature. For a feature that is one capability over a shared aggregate
|
|
203
|
+
# (PatientRegistration over Patient) no section carries the feature's name, and scoring
|
|
204
|
+
# "the BR-nnn under this feature's section" found none and PASSED — every invariant the
|
|
205
|
+
# feature relies on silently demoted to a non-blocking note. Resolved from the spec's
|
|
206
|
+
# Acts-on line, then the boundary map, then the feature name.
|
|
207
|
+
- id: feature_aggregates
|
|
208
|
+
action: execute_script
|
|
209
|
+
script: { ref: xref }
|
|
210
|
+
subcommand: feature-aggregates
|
|
211
|
+
args: ["--feature-dir", "{{feature_dir}}"]
|
|
212
|
+
outputs:
|
|
213
|
+
- { name: owned_sections, from: "$.owned_sections" }
|
|
214
|
+
- { name: one_to_one, from: "$.one_to_one" }
|
|
215
|
+
|
|
216
|
+
# WHICH OF THOSE INVARIANTS BLOCK. A feature that alone answers for an aggregate owns
|
|
217
|
+
# its section outright, and every BR-nnn under it has to be enforced in this code. A
|
|
218
|
+
# feature that shares the aggregate with other features — whatever it is named —
|
|
219
|
+
# answers only for what its spec cites: the rest of that section is the other features'
|
|
220
|
+
# code, and blocking on it would fail each of them for work another one does. The
|
|
221
|
+
# engine has no conditional prompt text, so the rule is chosen by an ordered pair of
|
|
222
|
+
# sets and handed to the pass as one sentence.
|
|
223
|
+
- id: model_scope_capability
|
|
224
|
+
action: set
|
|
225
|
+
vars:
|
|
226
|
+
model_scope_rule: "this feature SHARES those sections with other features, so BLOCKING covers only the BR-nnn this feature's spec CITES from them; any other BR-nnn stated there belongs to another feature and is a model_notes entry, not a violation"
|
|
227
|
+
- id: model_scope_owned
|
|
228
|
+
condition: "{{one_to_one}} == true"
|
|
229
|
+
action: set
|
|
230
|
+
vars:
|
|
231
|
+
model_scope_rule: "this feature is the ONLY one that answers for that section, so BLOCKING covers EVERY BR-nnn stated there, whether or not the spec cites it"
|
|
232
|
+
|
|
165
233
|
# Is a change OPEN on this feature? Read for NAVIGATION only — see the vars note. Safe
|
|
166
234
|
# on the bootstrap path: with no active_version.json, version-read returns
|
|
167
235
|
# active_change="" / has_change=false and exits 0, so nothing here is conditional on it.
|
|
@@ -224,13 +292,55 @@ workflow:
|
|
|
224
292
|
- { name: test_log, from: "$.log_tail" }
|
|
225
293
|
- { name: test_matched, from: "$.matched" }
|
|
226
294
|
- { name: test_scoped, from: "$.scoped" }
|
|
295
|
+
# The lines that NAME the failure, per failing lane, with the command and exit
|
|
296
|
+
# code that produced them — what the fix step reads. A positional tail can be
|
|
297
|
+
# entirely green while the failure sits above it.
|
|
298
|
+
- { name: test_excerpt, from: "$.failing_excerpt" }
|
|
299
|
+
- { name: test_failing_lanes, from: "$.failing_lanes" }
|
|
300
|
+
# "No suite ran, and here is why" — no test script, no runner, no test project.
|
|
301
|
+
# Distinct from a red suite, and from a scoped filter that matched nothing.
|
|
302
|
+
- { name: test_no_setup, from: "$.no_setup" }
|
|
303
|
+
- { name: test_setup_reason, from: "$.setup_reason" }
|
|
227
304
|
- id: green_break
|
|
228
305
|
action: break
|
|
229
306
|
condition: "{{test_ok}} == true"
|
|
307
|
+
# WAS A SUITE ACTUALLY RUN? Two ways it was not, and both are STRUCTURAL: there is no
|
|
308
|
+
# failing test to repair, and three fix attempts against one only spend model passes
|
|
309
|
+
# to re-discover the same emptiness.
|
|
310
|
+
# • `no_setup` — test-run read the runner's own words: no test script wired up, no
|
|
311
|
+
# runner installed, no test project. It carries the reason.
|
|
312
|
+
# • `matched=false` on a SCOPED run — a .NET slice filter that selected zero tests.
|
|
313
|
+
# Only meaningful when scoped, since `matched` reads a .NET-shaped summary line;
|
|
314
|
+
# a failing jest run says "Tests: 3 failed" and would otherwise read as empty.
|
|
315
|
+
# The engine has no compound conditions, so each AND is an ordered assignment —
|
|
316
|
+
# `not_green` carries "the run was not green" into both tests.
|
|
317
|
+
- id: flag_not_green
|
|
318
|
+
action: set
|
|
319
|
+
vars: { not_green: "true" }
|
|
320
|
+
- id: flag_no_setup
|
|
321
|
+
condition: "{{test_no_setup}} == true"
|
|
322
|
+
action: set
|
|
323
|
+
vars: { tests_missing: "{{not_green}}" }
|
|
324
|
+
- id: flag_zero_match
|
|
325
|
+
condition: "{{test_scoped}} == true"
|
|
326
|
+
action: set
|
|
327
|
+
vars: { zero_match: "true" }
|
|
328
|
+
- id: unflag_zero_match
|
|
329
|
+
condition: "{{test_matched}} == true"
|
|
330
|
+
action: set
|
|
331
|
+
vars: { zero_match: "false" }
|
|
332
|
+
- id: flag_tests_missing
|
|
333
|
+
condition: "{{zero_match}} == true"
|
|
334
|
+
action: set
|
|
335
|
+
vars: { tests_missing: "{{not_green}}" }
|
|
336
|
+
- id: missing_break
|
|
337
|
+
action: break
|
|
338
|
+
condition: "{{tests_missing}} == true"
|
|
230
339
|
- id: ctx_fix
|
|
231
340
|
action: build_context
|
|
232
341
|
vars:
|
|
233
342
|
test_log: "{{test_log}}"
|
|
343
|
+
test_excerpt: "{{test_excerpt}}"
|
|
234
344
|
context:
|
|
235
345
|
- { path: "{{spec_file}}" }
|
|
236
346
|
- { path: "{{domain_model}}", optional: true }
|
|
@@ -240,29 +350,64 @@ workflow:
|
|
|
240
350
|
tools: "Read,Write,Edit,Bash"
|
|
241
351
|
permission_mode: acceptEdits
|
|
242
352
|
task: |
|
|
243
|
-
Tests failed
|
|
353
|
+
Tests failed (tool {{test_tool}}, failing lane(s) {{test_failing_lanes}}; each
|
|
354
|
+
excerpt below is labelled with the lane, its command and its exit code).
|
|
355
|
+
Make them pass by fixing the code (or a genuinely wrong
|
|
244
356
|
test) in the files in context, keeping behaviour aligned with the spec's
|
|
245
357
|
acceptance criteria (spec in context). Where the spec cites an invariant
|
|
246
358
|
by id (BR-nnn), the bounded context's domain-model.md in context carries
|
|
247
359
|
its text and the entity names it is stated over — read the rule there
|
|
248
|
-
rather than inferring it from the id.
|
|
360
|
+
rather than inferring it from the id.
|
|
361
|
+
FIX WHAT THE LOG ACTUALLY SAYS. The failing lines are below; if they do not
|
|
362
|
+
show a real failure, say so and change nothing rather than inventing a repair.
|
|
363
|
+
Failing lines:
|
|
364
|
+
{{test_excerpt}}
|
|
365
|
+
Log tail (context, may be from a lane that passed):
|
|
249
366
|
{{test_log}}
|
|
250
367
|
outputs:
|
|
251
368
|
- { name: fixed_files, from: "$.files" }
|
|
252
369
|
|
|
253
|
-
|
|
370
|
+
# THE RED GATE — a suite that RAN and failed, after 3 fix attempts. It stops here, and
|
|
371
|
+
# deliberately: the passes below score the code against the model, the container and the
|
|
372
|
+
# referrers, and running them over code whose own tests fail reports findings nobody can
|
|
373
|
+
# act on until the tests are green. The structural case does NOT come here (see below).
|
|
374
|
+
- id: flag_tests_red
|
|
375
|
+
condition: "{{test_ok}} != true"
|
|
376
|
+
action: set
|
|
377
|
+
vars: { tests_red: "true" }
|
|
378
|
+
- id: unflag_tests_red
|
|
379
|
+
condition: "{{tests_missing}} == true"
|
|
380
|
+
action: set
|
|
381
|
+
vars: { tests_red: "false", slice_testable: "false", tests_present: "false" }
|
|
382
|
+
- id: red_gate
|
|
254
383
|
action: verify_context
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
384
|
+
require_value: tests_red
|
|
385
|
+
equals: "false"
|
|
386
|
+
on_mismatch: stop
|
|
387
|
+
on_mismatch_message: >
|
|
258
388
|
Slice tests not green after 3 fix attempts (tool={{test_tool}}, scoped={{test_scoped}},
|
|
259
|
-
matched={{test_matched}}).
|
|
260
|
-
per-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
389
|
+
matched={{test_matched}}). scoped=true means a per-feature name filter ran; scoped=false
|
|
390
|
+
means this stack has no per-test selector, so the lane's whole declared suite ran and
|
|
391
|
+
every failure in it counts here, including ones this feature did not cause.
|
|
392
|
+
Failing lines (lane(s) {{test_failing_lanes}}):
|
|
393
|
+
{{test_excerpt}}
|
|
394
|
+
Last log: {{test_log}}
|
|
395
|
+
|
|
396
|
+
# THE STRUCTURAL CASE — nothing to run. It WARNS and carries on, so the passes below
|
|
397
|
+
# still report what they find; the gate fails at the end, once they have. Before this,
|
|
398
|
+
# a slice with no tests stopped here and the conformance, composition, integrity and
|
|
399
|
+
# knowledge passes never executed on any run.
|
|
400
|
+
- id: warn_no_tests
|
|
401
|
+
action: verify_context
|
|
402
|
+
require_true: tests_present
|
|
403
|
+
on_false: continue
|
|
404
|
+
on_false_message: >
|
|
405
|
+
WARNING — this slice has NO TESTS TO RUN (tool={{test_tool}}, scoped={{test_scoped}},
|
|
406
|
+
matched={{test_matched}}). Reason: {{test_setup_reason}} — or, where that is blank,
|
|
407
|
+
the slice filter selected zero tests because no test name carries the aggregate's
|
|
408
|
+
name. Nothing failed and nothing passed. PASS 1 proves nothing on this run, and the integrity fence below
|
|
409
|
+
is skipped for the same reason — every other pass still runs, and this gate fails at
|
|
410
|
+
the end with what they found.
|
|
266
411
|
|
|
267
412
|
# ═══════ PASS 2 — DOMAIN CONFORMANCE: code vs the gate overlay checklist (validation) ═══════
|
|
268
413
|
# With tests green, scan the feature's CODE against the domain gate overlays (the
|
|
@@ -316,8 +461,9 @@ workflow:
|
|
|
316
461
|
authority on what the domain IS. The spec cites invariants by id; this document
|
|
317
462
|
states them. It describes the WHOLE context while this gate scores ONE feature,
|
|
318
463
|
so scope matters:
|
|
319
|
-
• BLOCKING —
|
|
320
|
-
|
|
464
|
+
• BLOCKING — the invariants of the section(s) this feature answers for,
|
|
465
|
+
{{owned_sections}}: {{model_scope_rule}}. Each blocking rule has to be
|
|
466
|
+
enforced somewhere in this code, and the
|
|
321
467
|
enforcement has to say what the model says. An owned invariant with no
|
|
322
468
|
enforcement, or one enforced with a different threshold, condition or
|
|
323
469
|
direction than the model states, is a `violations` entry with the BR id as
|
|
@@ -326,7 +472,7 @@ workflow:
|
|
|
326
472
|
• BLOCKING — the ubiquitous language of that same section. An entity, field or
|
|
327
473
|
state the code names differently from the model is a violation; the model's
|
|
328
474
|
name is the right one.
|
|
329
|
-
• NOT BLOCKING — anything the model states under
|
|
475
|
+
• NOT BLOCKING — anything the model states under any section not named above.
|
|
330
476
|
This slice does not own those and often should not enforce them. Report each
|
|
331
477
|
one this code appears to touch as a `model_notes` entry, naming the owning
|
|
332
478
|
aggregate, and carry on.
|
|
@@ -335,8 +481,8 @@ workflow:
|
|
|
335
481
|
invariant's text from its id, and never treat a rule the model does not state
|
|
336
482
|
as one.
|
|
337
483
|
|
|
338
|
-
ok=true ONLY when no checklist rule is violated AND no invariant
|
|
339
|
-
|
|
484
|
+
ok=true ONLY when no checklist rule is violated AND no BLOCKING invariant above
|
|
485
|
+
is unenforced or contradicted. `model_notes` never affects ok.
|
|
340
486
|
outputs:
|
|
341
487
|
- { name: conformance_ok, from: "$.ok" }
|
|
342
488
|
- { name: conformance_violations, from: "$.violations" }
|
|
@@ -352,8 +498,8 @@ workflow:
|
|
|
352
498
|
Fix these domain-conformance violations in the feature's code (in context),
|
|
353
499
|
keeping the design's behaviour intact. A violation whose `rule` is an overlay
|
|
354
500
|
rule is fixed against the gate checklist in the standards; one whose `rule` is a
|
|
355
|
-
BR-nnn is fixed against the text domain-model.md states for it under
|
|
356
|
-
|
|
501
|
+
BR-nnn is fixed against the text domain-model.md states for it under its section
|
|
502
|
+
among {{owned_sections}} — enforce what the model says, where the code
|
|
357
503
|
already owns that decision, and name entities and fields the way the model names
|
|
358
504
|
them. Do NOT weaken or delete a test to hide a violation, and do NOT edit
|
|
359
505
|
domain-model.md or the spec to match the code: this is a gate on the code, and a
|
|
@@ -369,6 +515,99 @@ workflow:
|
|
|
369
515
|
on_false: stop
|
|
370
516
|
on_false_message: "Code fails domain conformance after 2 fix attempts (violations: {{conformance_violations}}). Each entry is either a gate-checklist rule or an invariant (BR-nnn) the aggregate's own section of {{domain_model}} states and this code does not enforce as written. Both are constraints plan/implement must satisfy — fix the code, or if the constraint itself is wrong, correct it upstream (/appweaver specify → plan) rather than editing the model to match what was built. Invariants owned by other aggregates in this context were reported, not enforced here: {{model_notes}}"
|
|
371
517
|
|
|
518
|
+
# ═══════ PASS 2b — COMPOSITION: is everything the slice injects actually BOUND? ═══════
|
|
519
|
+
# The one failure class both other passes are blind to. A dependency-injection graph is
|
|
520
|
+
# not compile-checked, so a port that is injected and never registered builds green, runs
|
|
521
|
+
# every unit test green (they construct their subject directly), and fails on the first
|
|
522
|
+
# request that needs it. The mock run shipped exactly that: six of seven handlers took
|
|
523
|
+
# ports the composition root never bound.
|
|
524
|
+
#
|
|
525
|
+
# It reads FILES, so the query declares tools — the composition root is shared
|
|
526
|
+
# infrastructure and is often not among the slice's own concept paths. Registration by
|
|
527
|
+
# assembly scan or convention counts; assuming one without finding it does not.
|
|
528
|
+
- id: lane_resolve
|
|
529
|
+
action: execute_script
|
|
530
|
+
on_error: continue
|
|
531
|
+
script: { ref: actions }
|
|
532
|
+
subcommand: lane-resolve
|
|
533
|
+
outputs:
|
|
534
|
+
- { name: be_declared, from: "$.backend_declared" }
|
|
535
|
+
- { name: be_lane_id, from: "$.backend_lane_id" }
|
|
536
|
+
- { name: be_source_path, from: "$.backend_source_path" }
|
|
537
|
+
- id: wiring_arm
|
|
538
|
+
condition: "{{be_declared}} == true"
|
|
539
|
+
action: set
|
|
540
|
+
vars: { wiring_ok: "false" }
|
|
541
|
+
|
|
542
|
+
- id: wiring_loop
|
|
543
|
+
condition: "{{be_declared}} == true"
|
|
544
|
+
action: loop
|
|
545
|
+
while: "{{wiring_ok}} != true"
|
|
546
|
+
max_iterations: 2
|
|
547
|
+
steps:
|
|
548
|
+
- id: ctx_wiring
|
|
549
|
+
action: build_context
|
|
550
|
+
context:
|
|
551
|
+
- { path: "{{backend_plan}}", optional: true } # §2.1 ports + §4.1 adapters + §4.3 wiring
|
|
552
|
+
context_from: all_paths
|
|
553
|
+
- id: check_wiring
|
|
554
|
+
action: agent_query
|
|
555
|
+
tools: "Read,Grep"
|
|
556
|
+
schema: { ok: true, unbound: [ { dependency: "<injected type>", injected_by: "<file:type>", checked: "<the registration file(s) read>" } ], notes: [ "<a binding worth a second look>" ] }
|
|
557
|
+
mock: { ok: true, unbound: [], notes: [] }
|
|
558
|
+
task: |
|
|
559
|
+
WOULD THIS SLICE RUN? Check the service lane's composition against its code.
|
|
560
|
+
The lane is `{{be_lane_id}}` and its source path is `{{be_source_path}}`; the
|
|
561
|
+
backend plan is in context, and its §2.1 Ports, §4.1 Persistence and §4.3 Wiring
|
|
562
|
+
say what has to be bound.
|
|
563
|
+
|
|
564
|
+
1. Collect every dependency this slice's own types take by INJECTION — handler,
|
|
565
|
+
endpoint, adapter and service constructors, and the ports the plan names.
|
|
566
|
+
2. Read the lane's composition root: wherever this stack registers services (a
|
|
567
|
+
Program/Startup file, a registration/module/configuration class, a container
|
|
568
|
+
module, an assembly scan). Open the files; do not infer from names.
|
|
569
|
+
3. For each dependency, say whether the container binds it — an explicit
|
|
570
|
+
registration, an assembly scan or convention you have CONFIRMED covers that
|
|
571
|
+
type, or the framework itself providing it (a logger, a clock, an options
|
|
572
|
+
object, the HTTP context). A scan you assumed rather than found is not a
|
|
573
|
+
binding.
|
|
574
|
+
|
|
575
|
+
Every dependency with no binding is an `unbound` entry, naming where it is
|
|
576
|
+
injected and which registration file(s) you read. A binding that exists but whose
|
|
577
|
+
LIFETIME contradicts what it holds — a singleton capturing a scoped unit of work,
|
|
578
|
+
a scoped dependency in a singleton — goes in `notes`, which never affects ok.
|
|
579
|
+
Judge only this slice's types; another feature's unbound port is not this gate's.
|
|
580
|
+
ok=true only when every dependency this slice injects is bound.
|
|
581
|
+
outputs:
|
|
582
|
+
- { name: wiring_ok, from: "$.ok" }
|
|
583
|
+
- { name: unbound, from: "$.unbound" }
|
|
584
|
+
- { name: wiring_notes, from: "$.notes" }
|
|
585
|
+
- id: wiring_break
|
|
586
|
+
action: break
|
|
587
|
+
condition: "{{wiring_ok}} == true"
|
|
588
|
+
- id: fix_wiring
|
|
589
|
+
action: agent_generate
|
|
590
|
+
tools: "Read,Write,Edit,Bash"
|
|
591
|
+
permission_mode: acceptEdits
|
|
592
|
+
task: |
|
|
593
|
+
Bind these dependencies in the `{{be_lane_id}}` lane's composition root, under
|
|
594
|
+
`{{be_source_path}}`: {{unbound}}
|
|
595
|
+
Register each one the way this project already registers its neighbours — same
|
|
596
|
+
file, same helper, same lifetime convention — and bind the implementation the
|
|
597
|
+
plan's §4.1 / §4.3 names, never a new one you invent. Change no behaviour: this
|
|
598
|
+
is wiring, not design. If a dependency has NO implementation to bind because
|
|
599
|
+
nothing was ever written for it, do not stub one — leave it and say so, since
|
|
600
|
+
that is a generation gap /appweaver implement closes.
|
|
601
|
+
Then re-run the slice's tests (Bash) to confirm they stay green.
|
|
602
|
+
outputs:
|
|
603
|
+
- { name: wiring_fixes, from: "$.files" }
|
|
604
|
+
|
|
605
|
+
- id: wiring_gate
|
|
606
|
+
action: verify_context
|
|
607
|
+
require_true: wiring_ok
|
|
608
|
+
on_false: stop
|
|
609
|
+
on_false_message: "The service lane's container does not bind everything this slice injects, after 2 fix attempts: {{unbound}}. A dependency-injection graph is not compile-checked, so this builds green and fails on the first request that needs it. Bind each one in the lane's composition root under {{be_source_path}} — or, if nothing was ever written to bind, run /appweaver implement to generate it. Lifetime notes (not blocking): {{wiring_notes}}"
|
|
610
|
+
|
|
372
611
|
# ═══════ INTEGRITY — did this feature's change break any aggregate that USES it? ═══════
|
|
373
612
|
# The slice is green + domain-conformant on its own. But a change to a feature can break
|
|
374
613
|
# any OTHER aggregate that references it. Ask the OKF Cross-References index "who refers to
|
|
@@ -389,7 +628,12 @@ workflow:
|
|
|
389
628
|
- { name: referrer_count, from: "$.referrer_closure_count" }
|
|
390
629
|
- { name: referrer_names, from: "$.referrer_closure" }
|
|
391
630
|
|
|
631
|
+
# Skipped when PASS 1 found nothing to run: this is the same test command over a wider
|
|
632
|
+
# scope, so with no tests it would report the same emptiness a second time and fail a
|
|
633
|
+
# fence over it. `integrity_gate_ok` is what blocks, and it stays true for a fence that
|
|
634
|
+
# never ran — `integrity_ok` keeps reporting what actually happened.
|
|
392
635
|
- id: run_integrity
|
|
636
|
+
condition: "{{tests_missing}} == false"
|
|
393
637
|
action: execute_script
|
|
394
638
|
on_error: continue
|
|
395
639
|
script: { ref: actions }
|
|
@@ -398,6 +642,12 @@ workflow:
|
|
|
398
642
|
outputs:
|
|
399
643
|
- { name: integrity_ok, from: "$.fence_green" }
|
|
400
644
|
- { name: integrity_log, from: "$.log_tail" }
|
|
645
|
+
# Ordered assignment again: a fence that is not green blocks UNLESS it was skipped, and
|
|
646
|
+
# `tests_missing` carries exactly that (it reads "true" only when the fence did not run).
|
|
647
|
+
- id: integrity_verdict
|
|
648
|
+
condition: "{{integrity_ok}} != true"
|
|
649
|
+
action: set
|
|
650
|
+
vars: { integrity_gate_ok: "{{tests_missing}}" }
|
|
401
651
|
|
|
402
652
|
# On a RED fence, author a durable triage report BEFORE stopping — a broken referrer
|
|
403
653
|
# means someone must go fix ANOTHER slice, so this is a record, not scrollback (mirrors
|
|
@@ -417,7 +667,7 @@ workflow:
|
|
|
417
667
|
context_from: all_paths
|
|
418
668
|
- id: write_broken_referrers
|
|
419
669
|
action: agent_author
|
|
420
|
-
condition: "{{
|
|
670
|
+
condition: "{{integrity_gate_ok}} == false"
|
|
421
671
|
include_standards: false # a triage report, not generated code
|
|
422
672
|
output_target: { type: file, path: "{{feature_dir}}/broken-referrers.md" }
|
|
423
673
|
task: |
|
|
@@ -455,7 +705,7 @@ workflow:
|
|
|
455
705
|
|
|
456
706
|
- id: integrity_gate
|
|
457
707
|
action: verify_context
|
|
458
|
-
require_true:
|
|
708
|
+
require_true: integrity_gate_ok
|
|
459
709
|
on_false: stop
|
|
460
710
|
on_false_message: "Integrity check failed — this feature's change breaks a test in an aggregate that REFERS to it. Referrer blast radius ({{referrer_count}}): {{referrer_names}}. Triage written to {{feature_dir}}/broken-referrers.md — resolve each in the referrer's own slice (or reconsider this feature's contract change), then re-run. Last log: {{integrity_log}}"
|
|
461
711
|
|
|
@@ -512,6 +762,26 @@ workflow:
|
|
|
512
762
|
- { name: okf_ok, from: "$.ok" }
|
|
513
763
|
- { name: okf_orphans, from: "$.orphan_count" }
|
|
514
764
|
|
|
765
|
+
# THE LAST GATE — a slice with no tests fails HERE, not at PASS 1, so everything the
|
|
766
|
+
# other passes found has already been reported and any fix they made is on disk. It sits
|
|
767
|
+
# before the after-hooks, because an extension wired to `after_analyze_slice` runs on a
|
|
768
|
+
# slice this gate has passed.
|
|
769
|
+
- id: untested_gate
|
|
770
|
+
action: verify_context
|
|
771
|
+
require_true: tests_present
|
|
772
|
+
on_false: stop
|
|
773
|
+
on_false_message: >
|
|
774
|
+
The slice was analysed but NOT tested — it has no tests to run (tool={{test_tool}},
|
|
775
|
+
scoped={{test_scoped}}, matched={{test_matched}}). Reason: {{test_setup_reason}}.
|
|
776
|
+
So PASS 1 and the integrity fence proved nothing. Everything above ran: domain
|
|
777
|
+
conformance={{conformance_ok}}, composition={{wiring_ok}}, OKF index={{okf_ok}}.
|
|
778
|
+
A lane whose test command is not wired up is fixed in that lane — add the script or
|
|
779
|
+
the runner its declared command expects, or correct the command in
|
|
780
|
+
.appweaver/stacks.json. Otherwise, generate the missing tests —
|
|
781
|
+
/appweaver implement writes a paired test per plan element, under the test path the
|
|
782
|
+
lane declares — or, if the tests exist, name them so each carries its aggregate's
|
|
783
|
+
name, which is what the slice filter selects on. Then re-run /appweaver analyze-slice.
|
|
784
|
+
|
|
515
785
|
- id: resolve_post_hooks
|
|
516
786
|
action: execute_script
|
|
517
787
|
script: { ref: actions }
|
|
@@ -537,7 +807,15 @@ outputs:
|
|
|
537
807
|
# Reported by the conformance pass and never gated — surfaced so a real cross-aggregate
|
|
538
808
|
# leak is visible rather than only living in one query's answer.
|
|
539
809
|
model_notes: "{{model_notes}}"
|
|
810
|
+
# The composition pass. `true` with no backend lane declared — there is no container.
|
|
811
|
+
# `wiring_notes` are lifetime observations, reported and never gated.
|
|
812
|
+
wiring_ok: "{{wiring_ok}}"
|
|
813
|
+
wiring_notes: "{{wiring_notes}}"
|
|
540
814
|
test_tool: "{{test_tool}}"
|
|
815
|
+
# false when the slice had no tests to run — PASS 1 and the integrity fence proved
|
|
816
|
+
# nothing on this run, and the gate failed at the end saying so.
|
|
817
|
+
tests_present: "{{tests_present}}"
|
|
818
|
+
tests_matched: "{{test_matched}}"
|
|
541
819
|
referrer_count: "{{referrer_count}}"
|
|
542
820
|
referrer_blast_radius: "{{referrer_names}}"
|
|
543
821
|
okf_concept: "{{okf_concept}}"
|
|
@@ -82,7 +82,7 @@ workflow:
|
|
|
82
82
|
action: verify_context
|
|
83
83
|
require_non_empty: feature_dir
|
|
84
84
|
on_empty: stop
|
|
85
|
-
on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the
|
|
85
|
+
on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the feature, e.g. Order or PatientRegistration), then re-run build."
|
|
86
86
|
|
|
87
87
|
# Require A LANE — not a KIND of lane. build-compile runs EVERY declared lane's own
|
|
88
88
|
# build command, so a two-lane project compiles both (the case the old first-match-wins
|
|
@@ -106,7 +106,7 @@ workflow:
|
|
|
106
106
|
action: verify_context
|
|
107
107
|
require_non_empty: feature_dir
|
|
108
108
|
on_empty: stop
|
|
109
|
-
on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the
|
|
109
|
+
on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the feature, e.g. Order or PatientRegistration), then re-run characterize."
|
|
110
110
|
|
|
111
111
|
- id: verify_spec
|
|
112
112
|
action: verify_context
|
|
@@ -136,7 +136,7 @@ workflow:
|
|
|
136
136
|
action: verify_context
|
|
137
137
|
require_non_empty: feature_dir
|
|
138
138
|
on_empty: stop
|
|
139
|
-
on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the
|
|
139
|
+
on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the feature, e.g. Order or PatientRegistration), then re-run close-delta."
|
|
140
140
|
|
|
141
141
|
# 2. WHICH CHANGE IS BEING CLOSED. The evidence is changes/<CR-id>/ on disk; a change
|
|
142
142
|
# already closed has moved to changes/archive/. On the BOOTSTRAP path (implement →
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
|
|
81
81
|
name: aw-spec-constitution
|
|
82
82
|
description: Create or update the split project constitution — constitution.technical.md (technology base) + constitution.ui.md (the frontend lane's base, only where a lane exists) + constitution.domain.md (active domain addenda), each version-bumped with a Sync Impact Report, then composed into constitution.md. Project-level, run-once/amend — not a per-feature phase.
|
|
83
|
-
version: 2.
|
|
83
|
+
version: 2.5.0
|
|
84
84
|
agent_agnostic: true
|
|
85
85
|
dialect: aw-spec.orchestrator/v1
|
|
86
86
|
|
|
@@ -561,9 +561,9 @@ workflow:
|
|
|
561
561
|
invent principles, and do NOT reach for a rule file the map did not supply. If NO
|
|
562
562
|
domain rule file was loaded, say so in the Sync Impact Report and keep the principles
|
|
563
563
|
to what the user input actually supports.
|
|
564
|
-
- Author ONLY the domain layer: the
|
|
565
|
-
principles, domain folder additions, regulatory posture &
|
|
566
|
-
domain governance overrides. For each domain principle, state which technical
|
|
564
|
+
- Author ONLY the domain layer: the project identity, the adopted modelling
|
|
565
|
+
standard, the domain principles, domain folder additions, regulatory posture &
|
|
566
|
+
security addenda, and domain governance overrides. For each domain principle, state which technical
|
|
567
567
|
principle it EXTENDS. Do NOT restate the technical half's principles or the base
|
|
568
568
|
governance — reference them. Where constitution.technical.md is absent from
|
|
569
569
|
context the project declared no service lane and there is no technical half to
|
|
@@ -36,7 +36,7 @@ workflow:
|
|
|
36
36
|
action: verify_context
|
|
37
37
|
require_non_empty: feature_dir
|
|
38
38
|
on_empty: stop
|
|
39
|
-
on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the
|
|
39
|
+
on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the feature, e.g. Order or PatientRegistration), then re-run deploy."
|
|
40
40
|
|
|
41
41
|
# Pre-deploy hooks (e.g. a final smoke/build gate configured by the project).
|
|
42
42
|
- id: resolve_pre_hooks
|