aw-spec-cli 0.1.20 → 0.1.22
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/ORCH-DIALECT.md +8 -3
- package/assets/.appweaver/STACK-PROFILES.md +87 -8
- package/assets/.appweaver/orchestrator/modules/delta-plan.orch.yaml +30 -0
- package/assets/.appweaver/orchestrator/modules/delta-spec.orch.yaml +22 -1
- package/assets/.appweaver/orchestrator/orchestrator.py +65 -3
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan-delta.orch.yaml +6 -6
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +144 -52
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +78 -10
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +283 -25
- package/assets/.appweaver/orchestrator/workflows/aw-spec-constitution.orch.yaml +4 -4
- package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +202 -40
- package/assets/.appweaver/orchestrator/workflows/aw-spec-foundation.orch.yaml +140 -36
- package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +557 -57
- package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +3 -3
- package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +39 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-recall-delta.orch.yaml +28 -7
- package/assets/.appweaver/orchestrator/workflows/aw-spec-select-stack.orch.yaml +47 -23
- package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +101 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-ui-model.orch.yaml +141 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-ui-prototype.orch.yaml +386 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-workflow-model.orch.yaml +135 -0
- package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +772 -216
- package/assets/.appweaver/scripts/bash/aw-xref.sh +4 -1
- package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +708 -209
- package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +4 -1
- package/assets/.appweaver/scripts/python/aw-crstate.py +319 -0
- package/assets/.appweaver/scripts/python/aw-foundation.py +151 -0
- package/assets/.appweaver/scripts/python/aw-gaps.py +119 -0
- package/assets/.appweaver/scripts/python/aw-payload-parity.py +19 -0
- package/assets/.appweaver/scripts/python/aw-plan-coverage.py +503 -0
- package/assets/.appweaver/scripts/python/aw-plangate.py +216 -0
- package/assets/.appweaver/scripts/python/aw-principles.py +108 -0
- package/assets/.appweaver/scripts/python/aw-stack.py +257 -28
- package/assets/.appweaver/scripts/python/aw-testlog.py +148 -0
- package/assets/.appweaver/scripts/python/aw-xref.py +141 -4
- package/assets/.appweaver/stack-profiles/angular.json +26 -0
- package/assets/.appweaver/stack-profiles/blazor-wasm.json +38 -0
- package/assets/.appweaver/stack-profiles/dotnet-webapi.json +39 -0
- package/assets/.appweaver/stack-profiles/java-spring.json +20 -0
- package/assets/.appweaver/stack-profiles/nextjs.json +26 -0
- package/assets/.appweaver/stack-profiles/node-express.json +27 -0
- package/assets/.appweaver/stack-profiles/python-fastapi.json +16 -0
- package/assets/.appweaver/stack-profiles/react-vite.json +26 -0
- package/assets/.appweaver/stack-profiles/sveltekit.json +26 -0
- package/assets/.appweaver/stack-profiles/vue-vite.json +26 -0
- package/assets/.appweaver/templates/constitution-domain-template.md +17 -2
- package/assets/.appweaver/templates/ui-model-template.md +67 -0
- package/assets/.appweaver/templates/workflow-model-template.md +62 -0
- 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 +114 -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
|
@@ -40,10 +40,10 @@
|
|
|
40
40
|
# drift. What this stage must NOT do is hide that by merging a delta plan: the plan would
|
|
41
41
|
# then agree with the spec without any run of `plan` ever having derived it.
|
|
42
42
|
#
|
|
43
|
-
# ✅ THE DETECTION IS BUILT (2026-09-14).
|
|
44
|
-
#
|
|
45
|
-
# and `implement` reads it through plan-gate-check before any
|
|
46
|
-
# master run whose plans are no longer the ones the gate
|
|
43
|
+
# ✅ THE DETECTION IS BUILT (2026-09-14). Every master-mode analyze-plan run leaves
|
|
44
|
+
# `.aw-plan-gate.json` — the sha256 of spec.md, the context's domain-model.md and each lane
|
|
45
|
+
# plan it read, and its verdict — and `implement` reads it through plan-gate-check before any
|
|
46
|
+
# lane generates, refusing a master run whose plans are no longer the ones the gate checked. So after this merge the
|
|
47
47
|
# plans are not merely "visibly behind": the next `implement` on the master path says so and
|
|
48
48
|
# stops, naming `plan main`. A feature with no receipt has never been gated since the receipt
|
|
49
49
|
# existed and is reported, never refused.
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
# risk of "fixing" the change's new tests back toward pre-change behaviour.
|
|
61
61
|
# 2. A red analyze-slice still lands on an OPEN change, so the remedy is "fix and
|
|
62
62
|
# re-run" rather than a NEW change to repair the one already shipped.
|
|
63
|
-
# The
|
|
63
|
+
# The regression fence below still guards the MERGE (a change that breaks a dependent or a co-owner
|
|
64
64
|
# must not reach the master docs); close-delta guards nothing — it is the bookkeeping.
|
|
65
65
|
#
|
|
66
66
|
# Prereqs: run /appweaver implement (answering it with THIS change), then build, then
|
|
@@ -89,22 +89,38 @@
|
|
|
89
89
|
# is one close-delta could never archive, so it must not reach the masters.
|
|
90
90
|
#
|
|
91
91
|
# THE MERGE FENCE — THREE REFUSALS, read off disk in one cr-merge-check call and placed
|
|
92
|
-
# before the
|
|
92
|
+
# before the regression fence, because they are free while that one runs a test suite:
|
|
93
93
|
#
|
|
94
94
|
# REBASE. The delta carries the master version it was authored against (`**Base version**:`,
|
|
95
95
|
# stamped by cr-setup). If the master has moved, the delta's MODIFIED items name a BEFORE
|
|
96
96
|
# spec.md no longer says, and merging them is last-writer-wins over work that landed.
|
|
97
|
+
# cr-rebase runs FIRST and re-stamps that header — and `**Base context version**` — when
|
|
98
|
+
# the move is one this delta survives: the masters moved, and everything that landed in
|
|
99
|
+
# between touched OTHER aggregates than the ones this delta names. Nothing else in the kit
|
|
100
|
+
# could ever write those headers, so before this the only way past the refusal was a hand
|
|
101
|
+
# edit the authoring prompts forbid. Where a landed change DOES share an aggregate, the
|
|
102
|
+
# stamp is left alone and the refusal below names it.
|
|
97
103
|
#
|
|
98
104
|
# MERGED-NOT-CLOSED. The masters can move WITHOUT the version moving, because this stage
|
|
99
|
-
# merges and close-delta bumps. In that window
|
|
100
|
-
# reflect, so a sibling sharing the same base_version compares EQUAL and would merge
|
|
101
|
-
# masters that had already shifted. The version alone is not a proxy for "the masters
|
|
102
|
-
# moved";
|
|
105
|
+
# merges and close-delta bumps. In that window the masters carry a change the number does
|
|
106
|
+
# not reflect, so a sibling sharing the same base_version compares EQUAL and would merge
|
|
107
|
+
# onto masters that had already shifted. The version alone is not a proxy for "the masters
|
|
108
|
+
# moved"; a Change History row naming the change is the other half of the answer. That test
|
|
109
|
+
# lives in aw-crstate — one definition for this fence, cr-recall-check and close-delta —
|
|
110
|
+
# and it reads the CR CELL of a row, not the id anywhere in the file, which used to fire on
|
|
111
|
+
# an id quoted in prose. It looks at the context model too, which every feature shares, so
|
|
112
|
+
# a SIBLING feature's merged-unclosed change is visible; a sibling counts only where it
|
|
113
|
+
# touched an aggregate this delta also names.
|
|
103
114
|
#
|
|
104
115
|
# PREDECESSOR. A lower wave is committed to landing first, and `plan` put exactly those
|
|
105
|
-
# deltas in this one's context as the base it was planned on top of.
|
|
106
|
-
#
|
|
107
|
-
#
|
|
116
|
+
# deltas in this one's context as the base it was planned on top of. Same-wave changes are
|
|
117
|
+
# independent by definition, so the wave is the conservative signal.
|
|
118
|
+
#
|
|
119
|
+
# DECLARED DEPENDENCIES vs THE WAVE. The wave is a number; `## Depends on` in the change's
|
|
120
|
+
# own delta documents is what that number is supposed to encode. specify derives the wave
|
|
121
|
+
# from that section, and this stage re-checks it (cr-wave-derive): a change naming a
|
|
122
|
+
# predecessor at the same or a higher wave contradicts itself, and the predecessor fence
|
|
123
|
+
# above — which sees only numbers — would let the two merge in either order.
|
|
108
124
|
#
|
|
109
125
|
# All three are RAISE-then-VERIFY and fail-closed: each flag is raised only by the good
|
|
110
126
|
# state, so a check that did not run leaves the merge refused rather than waved through.
|
|
@@ -121,8 +137,8 @@
|
|
|
121
137
|
# =============================================================================
|
|
122
138
|
|
|
123
139
|
name: aw-spec-finalize-delta
|
|
124
|
-
description: Merge ONE chosen open change's delta-spec into the feature's spec.md and its model delta into the bounded context's domain-model.md — one aggregate section at a time, only the aggregates the delta names — in a single transaction, and append the spec's Change History row. ASKS which change (a bare CR id as the argument skips the prompt) and activates it, rather than taking whichever was last opened. Fenced by three hard refusals read off disk before anything is written — a moved base_version (rebase first), another change merged but not yet closed, and an open LOWER-wave predecessor — then by a SCOPED
|
|
125
|
-
version: 2.
|
|
140
|
+
description: Merge ONE chosen open change's delta-spec into the feature's spec.md and its model delta into the bounded context's domain-model.md — one aggregate section at a time, only the aggregates the delta names — in a single transaction, and append the spec's Change History row. ASKS which change (a bare CR id as the argument skips the prompt) and activates it, rather than taking whichever was last opened. Fenced by three hard refusals read off disk before anything is written — a moved base_version (rebase first), another change merged but not yet closed, and an open LOWER-wave predecessor — then by a SCOPED regression fence over the whole blast radius (fence_green) — the transitive REFERRER closure and the features that CO-OWN the aggregates this delta touches, which hold no edge to it but whose code is in the same files — writing a triage report and asking Abort/Override on red. Snapshots both masters to changes/<id>/pre-merge/ immediately before the rewrite. The lane plans are NOT merged — backend-plan.md and frontend-plan.md are derived from these two documents and are re-derived by /appweaver plan main. Leaves the change OPEN — analyze-slice runs next against the merged masters, and close-delta archives the change and bumps the version.
|
|
141
|
+
version: 2.5.0
|
|
126
142
|
agent_agnostic: true
|
|
127
143
|
dialect: aw-spec.orchestrator/v1
|
|
128
144
|
|
|
@@ -149,22 +165,42 @@ vars:
|
|
|
149
165
|
has_changes: "false" # from cr-list — the DIRECTORY's answer to "is anything open", not the ledger's
|
|
150
166
|
version: "" # the CURRENT version — unchanged by this stage
|
|
151
167
|
next_version: "" # what close-delta will bump to; the number the Change History rows carry
|
|
152
|
-
downstream_count: "0" # transitive + placeholder-filtered dependents
|
|
168
|
+
downstream_count: "0" # transitive + placeholder-filtered dependents THAT HAVE CODE; drives the gate
|
|
153
169
|
downstream_csv: "" # the blast-radius names, fed to the scoped fence's --also and shown to the human
|
|
170
|
+
unbuilt_count: "0" # dependants that are planned and not implemented: real, with nothing to re-test
|
|
171
|
+
unbuilt_csv: ""
|
|
172
|
+
reached_count: "0" # the whole closure — what this change reaches, built or not
|
|
173
|
+
co_owner_count: "0" # features that CO-OWN the aggregates this delta touches
|
|
174
|
+
co_owner_csv: "" # their names, fed to the same fence as a second --also
|
|
175
|
+
co_owner_aggs: "" # the aggregates the co-owner search was scoped to
|
|
176
|
+
co_owner_source: "" # `delta` (the change's own header) or `feature-owned` (the fallback)
|
|
177
|
+
blast_any: "" # truthy when EITHER half of the blast radius is non-empty
|
|
154
178
|
fence_ok: "true" # default green so the gate is a no-op when nothing downstream
|
|
155
179
|
gate_decision: "clean" # audit: 'clean' unless a RED fence was deliberately overridden
|
|
180
|
+
# The change's own `## Depends on` vs its wave — the fence that keeps the number and the
|
|
181
|
+
# prose from drifting apart. Defaults green, so a run that cannot read them is not blocked.
|
|
182
|
+
wave_ok: "true"
|
|
183
|
+
wave_required: ""
|
|
184
|
+
wave_message: ""
|
|
185
|
+
# The automatic rebase (cr-rebase). Defaults say "nothing moved, nothing stamped",
|
|
186
|
+
# so a step that could not run leaves the existing refusal to speak.
|
|
187
|
+
rebase_clean: "false"
|
|
188
|
+
rebase_stamped: ""
|
|
189
|
+
rebase_conflicts: ""
|
|
190
|
+
rebase_message: ""
|
|
156
191
|
domain_model: "" # specs/<Context>/domain-model.md — the second merge target
|
|
157
192
|
domain_model_exists: "false"
|
|
158
193
|
delta_model: "" # specs/<Context>/domain-model.<id>.delta.md — the model half of the change
|
|
159
194
|
delta_model_exists: "false" # false ⇒ a behaviour-only change: the spec merge is the whole merge
|
|
160
195
|
model_orphan: "false" # a model delta whose context has no master to merge it into
|
|
161
196
|
model_master_ok: "true" # lowered by model_orphan_raise; the gate reads only this
|
|
162
|
-
# THE MERGE FENCE. All
|
|
197
|
+
# THE MERGE FENCE. All four flags default DOWN and are raised only by the good state, so
|
|
163
198
|
# a cr-merge-check that errored or a field that was renamed leaves the merge refused.
|
|
164
199
|
# Fail-closed is the only safe default in front of an in-place edit of the masters.
|
|
165
200
|
rebase_ok: "false"
|
|
166
201
|
merged_open_ok: "false"
|
|
167
202
|
no_blockers: "false"
|
|
203
|
+
wave_ordering_ok: "false"
|
|
168
204
|
rebase_needed: "true" # the raw readings, defaulted to the blocking value for the
|
|
169
205
|
has_merged_open: "true" # same reason — an unanswered question is not a clean answer
|
|
170
206
|
has_blockers: "true"
|
|
@@ -173,7 +209,11 @@ vars:
|
|
|
173
209
|
blocker_ids: ""
|
|
174
210
|
blocker_count: "0"
|
|
175
211
|
merged_open_ids: ""
|
|
176
|
-
|
|
212
|
+
wave_problem: "" # which changes could not be ordered, and why that ends the check
|
|
213
|
+
unordered_ids: ""
|
|
214
|
+
unordered_count: "0"
|
|
215
|
+
merge_wave: "" # EMPTY, not 1: an unreadable wave is not the floor, and the
|
|
216
|
+
# gate below refuses rather than letting it pass as one
|
|
177
217
|
snapshot_dir: "" # changes/<id>/pre-merge/ — the before-image recall-delta restores
|
|
178
218
|
snapshot_count: "0"
|
|
179
219
|
# THE OKF OVERVIEW'S SUBJECT. The Overview step selects a `## Aggregate:` section BY
|
|
@@ -352,6 +392,28 @@ workflow:
|
|
|
352
392
|
# NO on_error: continue. A gate that cannot run must not be a gate that passes, and the
|
|
353
393
|
# raise-then-verify below is fail-closed for the same reason: each flag is raised ONLY
|
|
354
394
|
# by the good state, so a field that never arrived leaves it down and the merge refuses.
|
|
395
|
+
# AUTO-REBASE FIRST, and only where it is EARNED. `**Base version**` was written once by
|
|
396
|
+
# cr-setup and nothing could ever update it — every other site reads it, and every
|
|
397
|
+
# authoring prompt forbids the model from writing it — so a moved master left the operator
|
|
398
|
+
# hand-editing the delta-spec in the middle of an automated loop. cr-rebase re-stamps it
|
|
399
|
+
# (and `**Base context version**`, which until now nothing read at all) when the masters
|
|
400
|
+
# moved and NOTHING THAT LANDED IN BETWEEN touched the aggregates this delta names: then
|
|
401
|
+
# the delta still describes the document it was written against, and the stamp is
|
|
402
|
+
# bookkeeping. A landed change sharing an aggregate, or one whose model delta cannot be
|
|
403
|
+
# read, leaves the stamp alone — and the refusal below fires with the overlap named.
|
|
404
|
+
# It runs BEFORE cr-merge-check so that check reads the stamp this step just settled.
|
|
405
|
+
- id: auto_rebase
|
|
406
|
+
action: execute_script
|
|
407
|
+
on_error: continue
|
|
408
|
+
script: { ref: actions }
|
|
409
|
+
subcommand: cr-rebase
|
|
410
|
+
args: ["--cr-id", "{{active_change}}", "--feature-dir", "{{feature_dir}}", "--stamp"]
|
|
411
|
+
outputs:
|
|
412
|
+
- { name: rebase_clean, from: "$.clean" }
|
|
413
|
+
- { name: rebase_stamped, from: "$.stamped" }
|
|
414
|
+
- { name: rebase_conflicts, from: "$.conflict_ids" }
|
|
415
|
+
- { name: rebase_message, from: "$.message" }
|
|
416
|
+
|
|
355
417
|
- id: merge_check
|
|
356
418
|
action: execute_script
|
|
357
419
|
script: { ref: actions }
|
|
@@ -367,6 +429,10 @@ workflow:
|
|
|
367
429
|
- { name: has_merged_open, from: "$.has_merged_open" }
|
|
368
430
|
- { name: merged_open_ids, from: "$.merged_open_ids" }
|
|
369
431
|
- { name: merge_wave, from: "$.wave" }
|
|
432
|
+
- { name: wave_ordering_ok, from: "$.wave_ordering_ok" }
|
|
433
|
+
- { name: wave_problem, from: "$.wave_problem" }
|
|
434
|
+
- { name: unordered_ids, from: "$.unordered_ids" }
|
|
435
|
+
- { name: unordered_count, from: "$.unordered_count" }
|
|
370
436
|
|
|
371
437
|
- id: rebase_ok_raise
|
|
372
438
|
condition: "{{rebase_needed}} == false"
|
|
@@ -376,7 +442,7 @@ workflow:
|
|
|
376
442
|
action: verify_context
|
|
377
443
|
require_true: rebase_ok
|
|
378
444
|
on_false: stop
|
|
379
|
-
on_false_message: "finalize-delta REFUSED — change {{active_change}} needs a REBASE. It was authored against master version {{base_version}}, and the feature now stands at {{current_version}}: another change finalized and closed while this one was open. Its MODIFIED items name a BEFORE that spec.md no longer says, so merging now would overwrite work that already landed. Rebase: run /appweaver analyze-plan-delta {{active_change}} against the current master, fix what it reports
|
|
445
|
+
on_false_message: "finalize-delta REFUSED — change {{active_change}} needs a REBASE. It was authored against master version {{base_version}}, and the feature now stands at {{current_version}}: another change finalized and closed while this one was open. Its MODIFIED items name a BEFORE that spec.md no longer says, so merging now would overwrite work that already landed. The automatic re-stamp did not apply, because what landed in between touches this delta's own aggregates: {{rebase_message}}. Rebase for real: run /appweaver analyze-plan-delta {{active_change}} against the current master, fix what it reports — then `.appweaver/scripts/bash/aw-spec-actions.sh cr-rebase --cr-id {{active_change}} --stamp` re-stamps `**Base version**` and `**Base context version**` once the overlap with {{rebase_conflicts}} is resolved, and /appweaver finalize-delta {{active_change}} can run again."
|
|
380
446
|
|
|
381
447
|
- id: merged_open_ok_raise
|
|
382
448
|
condition: "{{has_merged_open}} == false"
|
|
@@ -386,7 +452,21 @@ workflow:
|
|
|
386
452
|
action: verify_context
|
|
387
453
|
require_true: merged_open_ok
|
|
388
454
|
on_false: stop
|
|
389
|
-
on_false_message: "finalize-delta REFUSED —
|
|
455
|
+
on_false_message: "finalize-delta REFUSED — a change has already been MERGED into a master but not yet CLOSED, so the masters carry it while the version still reads {{current_version}}: {{merged_open_ids}}. The feature named beside it is where it lives — domain-model.md is shared, so a SIBLING feature's merge moves a master this delta was authored against, and it is counted here when it touched an aggregate this delta also names. Change {{active_change}} was authored against those pre-merge masters and the version comparison cannot see the difference. Finish the merged change first, under ITS OWN feature: select that feature (/appweaver select-feature), then /appweaver analyze-slice and /appweaver close-delta on it, which bumps the version — then rebase this one against the new number and re-run /appweaver finalize-delta {{active_change}}."
|
|
456
|
+
|
|
457
|
+
# WAVE ORDERING IS A PRECONDITION OF THE FENCE BELOW, not a finding of it. The predecessor
|
|
458
|
+
# test is `their wave < mine`, and a wave that cannot be read used to answer that question
|
|
459
|
+
# as 1 — the floor — so an unstamped delta posed as a predecessor to everything above it,
|
|
460
|
+
# and an unstamped SUBJECT compared against nothing at all and sailed through. Neither
|
|
461
|
+
# showed up as a refusal, because both look exactly like "no blockers found".
|
|
462
|
+
# It is narrow by construction: nothing sits below the floor, so a change AT wave 1 is
|
|
463
|
+
# never held up by a neighbour nobody stamped. Above it, the fence says so instead of
|
|
464
|
+
# guessing.
|
|
465
|
+
- id: require_wave_ordering
|
|
466
|
+
action: verify_context
|
|
467
|
+
require_true: wave_ordering_ok
|
|
468
|
+
on_false: stop
|
|
469
|
+
on_false_message: "finalize-delta REFUSED — the wave fence cannot order change {{active_change}}, so it cannot tell you whether a predecessor is still open: {{wave_problem}}. A wave is stamped by a script and read by both merge gates; `**Wave**: [N]` is the unfilled template line, which means the stamp never ran on that document. Stamp each one — `.appweaver/scripts/bash/aw-spec-actions.sh cr-wave-derive --cr-id <CR-id> --stamp` derives the wave from that change's own `## Depends on` section and writes it — then re-run /appweaver finalize-delta {{active_change}}. A change in another feature is stamped under that feature (/appweaver select-feature first)."
|
|
390
470
|
|
|
391
471
|
- id: no_blockers_raise
|
|
392
472
|
condition: "{{has_blockers}} == false"
|
|
@@ -396,21 +476,48 @@ workflow:
|
|
|
396
476
|
action: verify_context
|
|
397
477
|
require_true: no_blockers
|
|
398
478
|
on_false: stop
|
|
399
|
-
on_false_message: "finalize-delta REFUSED — change {{active_change}} is at wave {{merge_wave}} and {{blocker_count}} open change(s) sit at a LOWER wave
|
|
479
|
+
on_false_message: "finalize-delta REFUSED — change {{active_change}} is at wave {{merge_wave}} and {{blocker_count}} open change(s) sit at a LOWER wave. A lower wave is committed to landing FIRST, and this delta was planned with those in its context as the base it builds on — merging it now folds in behaviour that assumes a predecessor the masters do not yet carry. THE LOWER-WAVE CHANGES: {{blocker_ids}}. This scan is context-wide, so one of them may belong to a feature you have never opened — and finalize-delta resolves a change under the ACTIVE feature. Select the feature named beside a change (/appweaver select-feature) before running /appweaver finalize-delta <CR-id> → analyze-slice → close-delta on it; then rebase and re-run this one. Same-wave changes are independent and are not counted here; if this delta does NOT in fact build on them, correct its `## Depends on` section (naming only the deltas it truly builds on) and re-run /appweaver specify's wave derivation — `.appweaver/scripts/bash/aw-spec-actions.sh cr-wave-derive --cr-id {{active_change}} --stamp` applies it directly."
|
|
480
|
+
|
|
481
|
+
# THE DECLARED DEPENDENCIES AND THE WAVE MUST AGREE. The fence above orders changes by
|
|
482
|
+
# their wave NUMBER; this one checks that number against what the change's own documents
|
|
483
|
+
# SAY under `## Depends on`. Before this, the two could diverge silently: an author named
|
|
484
|
+
# a predecessor in prose, cr-setup left the wave at the predecessor's own level, and the
|
|
485
|
+
# fence above then read the two as independent and let them merge in either order.
|
|
486
|
+
- id: wave_declared_check
|
|
487
|
+
action: execute_script
|
|
488
|
+
on_error: continue
|
|
489
|
+
script: { ref: actions }
|
|
490
|
+
subcommand: cr-wave-derive
|
|
491
|
+
args: ["--cr-id", "{{active_change}}"]
|
|
492
|
+
outputs:
|
|
493
|
+
- { name: wave_ok, from: "$.ok" }
|
|
494
|
+
- { name: wave_required, from: "$.required_wave" }
|
|
495
|
+
- { name: wave_message, from: "$.message" }
|
|
496
|
+
- id: require_wave_matches_depends
|
|
497
|
+
action: verify_context
|
|
498
|
+
require_true: wave_ok
|
|
499
|
+
on_false: stop
|
|
500
|
+
on_false_message: "finalize-delta REFUSED — change {{active_change}} contradicts its own `## Depends on`: {{wave_message}}. The blocker fence above compares wave NUMBERS, so it would let the two merge in either order and this delta could land before the change it says it builds on. Either raise this change to wave {{wave_required}} (`.appweaver/scripts/bash/aw-spec-actions.sh cr-wave-derive --cr-id {{active_change}} --stamp`, which reads the same section), or correct `## Depends on` in its delta documents if it does not in fact build on them."
|
|
400
501
|
|
|
401
502
|
# 1c. DOWNSTREAM GATE (hard, with a REVIEWED override). Who depends on this aggregate —
|
|
402
503
|
# its OKF Cross-References, TRANSITIVELY (C→B→A all count) and placeholder-filtered
|
|
403
504
|
# (the "_(none observed)_" row is NOT a dependent) = the real blast radius. If any
|
|
404
505
|
# slice does, this change could ripple into it, so run a SCOPED regression fence over
|
|
405
|
-
# this feature + its
|
|
506
|
+
# this feature + its blast radius (--scope slice --also <referrers> --also <co-owners>) BEFORE the
|
|
406
507
|
# doc merge, so a block leaves the repo untouched. The fence binds fence_GREEN (ran
|
|
407
508
|
# AND matched >=1), not raw ok, so an empty / zero-match run is RED, never a vacuous
|
|
408
509
|
# pass. If RED we do NOT hard-stop blindly: we WRITE a triage report, then surface the
|
|
409
|
-
# failing log +
|
|
510
|
+
# failing log + affected slices and ASK the developer — Abort (a real downstream
|
|
410
511
|
# break: fix it first) or, having reviewed the log (pre-existing / flaky / unrelated),
|
|
411
512
|
# deliberately Override and close anyway. Non-interactive / CI runs take the FIRST
|
|
412
513
|
# option (Abort), so automation never silently overrides. Skipped when nothing depends
|
|
413
|
-
# on this slice
|
|
514
|
+
# on this slice and nothing co-owns its aggregates, or the fence is green.
|
|
515
|
+
# THE FENCE RUNS TESTS, so it takes the BUILT half of the closure. Edges are read off
|
|
516
|
+
# the lane PLAN, not off code, so a dependant can be fully planned and hold none:
|
|
517
|
+
# selecting it would hand the fence a slice with no tests, and the fence counts a
|
|
518
|
+
# zero-match run as RED. An unbuilt dependant is still reported — it is a real
|
|
519
|
+
# dependant with nothing to re-test yet, which is a different thing from being
|
|
520
|
+
# unaffected, and it is what /appweaver implement on that feature will settle.
|
|
414
521
|
- id: xref_impact
|
|
415
522
|
action: execute_script
|
|
416
523
|
on_error: continue
|
|
@@ -418,16 +525,49 @@ workflow:
|
|
|
418
525
|
subcommand: list
|
|
419
526
|
args: ["--feature-dir", "{{feature_dir}}"]
|
|
420
527
|
outputs:
|
|
421
|
-
- { name: downstream_count, from: "$.
|
|
422
|
-
- { name: downstream_csv, from: "$.
|
|
528
|
+
- { name: downstream_count, from: "$.built_count" } # transitive, placeholder-filtered, HAS CODE
|
|
529
|
+
- { name: downstream_csv, from: "$.built_closure_csv" } # blast-radius names → --also + display
|
|
530
|
+
- { name: unbuilt_count, from: "$.unbuilt_count" } # dependants planned but never implemented
|
|
531
|
+
- { name: unbuilt_csv, from: "$.unbuilt_closure_csv" }
|
|
532
|
+
- { name: reached_count, from: "$.referrer_closure_count" } # the whole closure, for the report
|
|
533
|
+
|
|
534
|
+
# THE OTHER HALF OF THE BLAST RADIUS. A referrer reaches this aggregate through an edge;
|
|
535
|
+
# a CO-OWNER has no edge to it because it is not a user of the aggregate — it is one of
|
|
536
|
+
# the capabilities the aggregate is made of, and its code is in the very files this merge
|
|
537
|
+
# rewrites. `Encounter.cs` is extended by every feature claiming Encounter, so a change to
|
|
538
|
+
# it can break a co-owner while every referrer still passes. Scoped to the aggregates THIS
|
|
539
|
+
# delta names, so a feature owning two aggregates does not drag the second one's co-owners
|
|
540
|
+
# through a fence they cannot have been affected by.
|
|
541
|
+
- id: xref_co_owners
|
|
542
|
+
action: execute_script
|
|
543
|
+
on_error: continue
|
|
544
|
+
script: { ref: xref }
|
|
545
|
+
subcommand: co-owners
|
|
546
|
+
args: ["--cr", "{{active_change}}", "--feature-dir", "{{feature_dir}}"]
|
|
547
|
+
outputs:
|
|
548
|
+
- { name: co_owner_count, from: "$.count" }
|
|
549
|
+
- { name: co_owner_csv, from: "$.features_csv" }
|
|
550
|
+
- { name: co_owner_aggs, from: "$.aggregates_csv" }
|
|
551
|
+
- { name: co_owner_source, from: "$.source" }
|
|
552
|
+
|
|
553
|
+
# The fence runs when EITHER half is non-empty. Two ordered assignments rather than one
|
|
554
|
+
# condition, because the dialect has no compound conditions.
|
|
555
|
+
- id: blast_from_referrers
|
|
556
|
+
condition: "{{downstream_count}} != 0"
|
|
557
|
+
action: set
|
|
558
|
+
vars: { blast_any: "true" }
|
|
559
|
+
- id: blast_from_co_owners
|
|
560
|
+
condition: "{{co_owner_count}} != 0"
|
|
561
|
+
action: set
|
|
562
|
+
vars: { blast_any: "true" }
|
|
423
563
|
|
|
424
564
|
- id: run_downstream_fence
|
|
425
|
-
condition: "{{
|
|
565
|
+
condition: "{{blast_any}}"
|
|
426
566
|
action: execute_script
|
|
427
567
|
on_error: continue
|
|
428
568
|
script: { ref: actions }
|
|
429
569
|
subcommand: test-run
|
|
430
|
-
args: ["--feature-dir", "{{feature_dir}}", "--scope", "slice", "--also", "{{downstream_csv}}"]
|
|
570
|
+
args: ["--feature-dir", "{{feature_dir}}", "--scope", "slice", "--also", "{{downstream_csv}}", "--also", "{{co_owner_csv}}"]
|
|
431
571
|
outputs:
|
|
432
572
|
- { name: fence_ok, from: "$.fence_green" }
|
|
433
573
|
- { name: fence_tool, from: "$.tool" }
|
|
@@ -441,11 +581,14 @@ workflow:
|
|
|
441
581
|
# only when nothing depends on this slice (no fence ran, nothing to report).
|
|
442
582
|
- id: ctx_broken_downstream
|
|
443
583
|
action: build_context
|
|
444
|
-
condition: "{{
|
|
584
|
+
condition: "{{blast_any}}"
|
|
445
585
|
vars:
|
|
446
586
|
active_change: "{{active_change}}"
|
|
447
587
|
downstream_count: "{{downstream_count}}"
|
|
448
588
|
downstream_csv: "{{downstream_csv}}"
|
|
589
|
+
co_owner_count: "{{co_owner_count}}"
|
|
590
|
+
co_owner_csv: "{{co_owner_csv}}"
|
|
591
|
+
co_owner_aggs: "{{co_owner_aggs}}"
|
|
449
592
|
fence_ok: "{{fence_ok}}"
|
|
450
593
|
fence_log: "{{fence_log}}"
|
|
451
594
|
context:
|
|
@@ -456,14 +599,27 @@ workflow:
|
|
|
456
599
|
- { path: "{{delta_backend_plan}}", optional: true }
|
|
457
600
|
- id: write_broken_downstream
|
|
458
601
|
action: agent_author
|
|
459
|
-
condition: "{{
|
|
602
|
+
condition: "{{blast_any}}"
|
|
460
603
|
include_standards: false # a triage report, not generated code
|
|
461
604
|
output_target: { type: file, path: "{{feature_dir}}/changes/{{active_change}}/broken-downstream.md" }
|
|
462
605
|
task: |
|
|
463
606
|
Author the DOWNSTREAM report for change {{active_change}}: the result of re-running this
|
|
464
|
-
change against
|
|
465
|
-
downstream slice (that is the downstream slice's own job).
|
|
466
|
-
|
|
607
|
+
change against everything it could have broken. finalize-delta does NOT fix a
|
|
608
|
+
downstream slice (that is the downstream slice's own job).
|
|
609
|
+
THE BLAST RADIUS HAS TWO HALVES and they fail for different reasons — say which is which
|
|
610
|
+
wherever the report names a slice:
|
|
611
|
+
- DEPENDENTS ({{downstream_count}}): {{downstream_csv}} — aggregates that USE this one
|
|
612
|
+
through a Cross-Reference edge. A failure here is a broken CONTRACT between two slices.
|
|
613
|
+
- NOT YET BUILT ({{unbuilt_count}}): {{unbuilt_csv}} — aggregates that also depend on this
|
|
614
|
+
one and hold no code yet, so the fence had nothing to run for them. Edges come from the
|
|
615
|
+
lane PLAN, so these are real dependants whose contract with this aggregate has never
|
|
616
|
+
been exercised. Say so plainly: they were NOT tested, which is not the same as passing,
|
|
617
|
+
and /appweaver implement on those features is what settles them.
|
|
618
|
+
- CO-OWNERS ({{co_owner_count}}): {{co_owner_csv}} — features that share the aggregate(s)
|
|
619
|
+
this change touches ({{co_owner_aggs}}). They hold no edge to it: their code is IN the
|
|
620
|
+
files this merge rewrites, so a failure here is this change breaking a sibling
|
|
621
|
+
capability of the same aggregate, not a contract between two of them.
|
|
622
|
+
Either list may be empty. The fence result was fence_green={{fence_ok}}
|
|
467
623
|
(true = every downstream test passed; false = at least one failed). Test log:
|
|
468
624
|
{{fence_log}}
|
|
469
625
|
Always write the file (it is regenerated on every finalize-delta run so a green close never
|
|
@@ -471,8 +627,9 @@ workflow:
|
|
|
471
627
|
|
|
472
628
|
IF fence_green is TRUE (all passed): write a SHORT all-clear — the heading
|
|
473
629
|
`# Broken Downstream — change {{active_change}}`, then a few lines stating that the
|
|
474
|
-
{{downstream_count}} slice(s)
|
|
475
|
-
this change and all passed, so nothing
|
|
630
|
+
{{downstream_count}} dependent slice(s) ({{downstream_csv}}) and {{co_owner_count}} co-owner
|
|
631
|
+
feature(s) ({{co_owner_csv}}) were re-run against this change and all passed, so nothing it
|
|
632
|
+
could reach broke. No per-slice sections; invent nothing.
|
|
476
633
|
|
|
477
634
|
IF fence_green is FALSE (something failed): write the triage record —
|
|
478
635
|
- `# Broken Downstream — change {{active_change}}` and a one-line statement: aggregates that
|
|
@@ -480,10 +637,11 @@ workflow:
|
|
|
480
637
|
finalize-delta's downstream gate. Note it is the record a human reads while deciding Abort vs
|
|
481
638
|
Override, and if the change is Overridden it is archived with the change as the record of
|
|
482
639
|
what was knowingly closed over.
|
|
483
|
-
- A SUMMARY line: total
|
|
484
|
-
- One `##` section per
|
|
485
|
-
slice by matching the failing test names in the log to the slice's name
|
|
486
|
-
|
|
640
|
+
- A SUMMARY line: total slices in the blast radius (dependents + co-owners), and how many have a failing test.
|
|
641
|
+
- One `##` section per slice with a failing test, from EITHER half — attribute a failure to a
|
|
642
|
+
slice by matching the failing test names in the log to the slice's name, and label the
|
|
643
|
+
section `dependent` or `co-owner`. Under it, on their
|
|
644
|
+
own labelled lines: the aggregate or feature, the failing test(s) named in the log, what
|
|
487
645
|
broke (as far as the log shows), and a RESOLUTION line — either this change broke the
|
|
488
646
|
downstream slice's expected contract (fix it in the downstream slice's OWN CR, or re-baseline
|
|
489
647
|
its golden-master) or the failure is pre-existing / flaky / unrelated (a candidate for a
|
|
@@ -701,7 +859,7 @@ workflow:
|
|
|
701
859
|
# the context has no model: there would be nothing in context to distil, and a
|
|
702
860
|
# query with no source invents one. Best-effort; an empty answer is a no-op.
|
|
703
861
|
# WHICH AGGREGATE SECTION(S) THIS CHANGE ACTUALLY TOUCHED, read off the model delta's
|
|
704
|
-
# own
|
|
862
|
+
# own `**Aggregates touched**:` header rather than assumed from the feature name. The Overview
|
|
705
863
|
# step below selects a section by name, and a feature whose name is not an aggregate
|
|
706
864
|
# name — one capability over a shared aggregate — selects nothing, producing an empty
|
|
707
865
|
# answer that the writer then declines. Driving it off the delta's declared aggregates
|
|
@@ -830,6 +988,10 @@ outputs:
|
|
|
830
988
|
# the change by close-delta. What an un-merge would restore from.
|
|
831
989
|
pre_merge_snapshot: "{{snapshot_dir}}"
|
|
832
990
|
base_version: "{{base_version}}"
|
|
991
|
+
# What the automatic rebase did before the fence read the stamp — empty when the delta was
|
|
992
|
+
# already current, and the reason it could not when a landed change shared its aggregates.
|
|
993
|
+
rebased: "{{rebase_stamped}}"
|
|
994
|
+
rebase_note: "{{rebase_message}}"
|
|
833
995
|
model_merged: "{{delta_model_exists}}" # true ⇒ domain-model.md was the second target of this merge.
|
|
834
996
|
# false = a behaviour-only change: the spec merge was the
|
|
835
997
|
# whole merge, not a half of one.
|