aw-spec-cli 0.1.18 → 0.1.20
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 +63 -31
- package/assets/.appweaver/orchestrator/modules/delta-plan.orch.yaml +62 -14
- package/assets/.appweaver/orchestrator/modules/delta-spec.orch.yaml +10 -6
- package/assets/.appweaver/orchestrator/modules/lane-gate.orch.yaml +9 -1
- package/assets/.appweaver/orchestrator/modules/tech-select.orch.yaml +21 -0
- package/assets/.appweaver/orchestrator/orchestrator.py +23 -1
- package/assets/.appweaver/orchestrator/rev-templates/delta-design-template.skeleton.md +1 -1
- package/assets/.appweaver/orchestrator/rev-templates/design-template.skeleton.md +22 -13
- 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 +6 -4
- package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +399 -131
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan-delta.orch.yaml +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +199 -38
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +159 -25
- package/assets/.appweaver/orchestrator/workflows/aw-spec-build.orch.yaml +20 -9
- package/assets/.appweaver/orchestrator/workflows/aw-spec-characterize.orch.yaml +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-close-delta.orch.yaml +183 -40
- package/assets/.appweaver/orchestrator/workflows/aw-spec-deploy.orch.yaml +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +311 -36
- package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +221 -40
- package/assets/.appweaver/orchestrator/workflows/aw-spec-inventory.orch.yaml +217 -174
- package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +494 -109
- package/assets/.appweaver/orchestrator/workflows/aw-spec-parity.orch.yaml +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +83 -18
- package/assets/.appweaver/orchestrator/workflows/aw-spec-recall-delta.orch.yaml +319 -0
- 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-specify.orch.yaml +143 -34
- package/assets/.appweaver/scripts/bash/aw-inventory.sh +15 -0
- package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +878 -51
- package/assets/.appweaver/scripts/bash/aw-xref.sh +3 -0
- package/assets/.appweaver/scripts/bash/common.sh +28 -1
- package/assets/.appweaver/scripts/powershell/aw-inventory.ps1 +15 -0
- package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +868 -51
- package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +3 -0
- package/assets/.appweaver/scripts/python/aw-inventory.py +1832 -65
- package/assets/.appweaver/scripts/python/aw-xref.py +500 -81
- 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 +56 -40
- 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 +24 -21
- package/assets/.appweaver/templates/ui-delta-design-template.md +1 -1
- package/assets/.appweaver/templates/ui-design-template.md +11 -7
- package/assets/foundations/dotnet/ModularMonolith/ModularMonolith.pack.txt +4489 -361
- package/assets/foundations/dotnet/VerticalSlice/VerticalSlice.pack.txt +2274 -125
- package/assets/guardrails/domain/healthcare/auditing.domain.md +3 -3
- package/assets/guardrails/domain/pharma/auditing.domain.md +3 -3
- package/assets/guardrails/technology/dotnet-webapi/api-versioning.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/auditing.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/authorization.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/clean-architecture.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/cqrs.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/data-classification.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/eventing.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/folder-structure.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/modular-monolith.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/persistence.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/vertical-slices.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/zero-trust.md +1 -1
- package/assets/guardrails/technology/nextjs/accessibility-implementation.md +1 -1
- package/assets/guardrails/technology/nextjs/api-integration.dotnet-webapi.md +1 -1
- package/assets/guardrails/technology/nextjs/api-integration.java-spring.md +1 -1
- package/assets/guardrails/technology/nextjs/auth-session.md +1 -1
- package/assets/guardrails/technology/nextjs/component-architecture.md +1 -1
- package/assets/guardrails/technology/nextjs/design-system.md +1 -1
- package/assets/guardrails/technology/nextjs/error-handling.md +1 -1
- package/assets/guardrails/technology/nextjs/forms-and-validation.md +1 -1
- package/assets/guardrails/technology/nextjs/i18n.md +1 -1
- package/assets/guardrails/technology/nextjs/performance-budgets.md +1 -1
- package/assets/guardrails/technology/nextjs/routing-navigation.md +1 -1
- package/assets/guardrails/technology/nextjs/state-management.md +1 -1
- package/package.json +1 -1
|
@@ -30,14 +30,23 @@
|
|
|
30
30
|
# item merged into the spec is carried by the spec until the frontend plan is re-derived from
|
|
31
31
|
# it — not dropped, re-derived.
|
|
32
32
|
#
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
33
|
+
# THE RE-DERIVATION IS `plan main`. After this merge the masters carry the change and the
|
|
34
|
+
# lane plans do not, so the next step for the plans is to be derived again from the two
|
|
35
|
+
# documents this stage just moved: `/appweaver plan main` asks to re-derive them, carries
|
|
36
|
+
# the recorded architectural decisions and workarounds forward, and rewrites whatever the
|
|
37
|
+
# spec and model no longer support. Until that is run the master plans stand at their last
|
|
38
|
+
# authored content while spec.md and domain-model.md have moved ahead of them — visible,
|
|
39
|
+
# because analyze-plan gates the masters against exactly these two documents and reports the
|
|
40
|
+
# drift. What this stage must NOT do is hide that by merging a delta plan: the plan would
|
|
41
|
+
# then agree with the spec without any run of `plan` ever having derived it.
|
|
42
|
+
#
|
|
43
|
+
# ✅ THE DETECTION IS BUILT (2026-09-14). analyze-plan leaves `.aw-plan-gate.json` when it
|
|
44
|
+
# PASSES — the sha256 of spec.md, the context's domain-model.md and each lane plan it read —
|
|
45
|
+
# and `implement` reads it through plan-gate-check before any lane generates, refusing a
|
|
46
|
+
# master run whose plans are no longer the ones the gate passed. So after this merge the
|
|
47
|
+
# plans are not merely "visibly behind": the next `implement` on the master path says so and
|
|
48
|
+
# stops, naming `plan main`. A feature with no receipt has never been gated since the receipt
|
|
49
|
+
# existed and is reported, never refused.
|
|
41
50
|
#
|
|
42
51
|
# specify → plan → analyze-plan-delta → implement → build → analyze-slice-delta
|
|
43
52
|
# → finalize-delta ← you are here
|
|
@@ -65,16 +74,64 @@
|
|
|
65
74
|
# that describes an intention.
|
|
66
75
|
# finalize-delta edits ONLY spec.md and domain-model.md; it generates no code.
|
|
67
76
|
#
|
|
68
|
-
#
|
|
77
|
+
# WHICH CHANGE IS MERGED IS ASKED, NEVER INFERRED. MANY changes may be open at once, and
|
|
78
|
+
# active_version.json's `active_change` is only the most recently opened or activated — every
|
|
79
|
+
# /appweaver specify overwrites it. Taking that field as "the" change is how the delta built
|
|
80
|
+
# by implement stays open while a freshly-opened stub is merged into spec.md: cr-setup
|
|
81
|
+
# scaffolds delta-spec.md from the template the moment it opens a change, so an empty stub
|
|
82
|
+
# passes an exists check. This stage therefore opens with the same list → pick → activate
|
|
83
|
+
# sequence as plan, implement, analyze-plan-delta and recall-delta, and the argument doubles
|
|
84
|
+
# as the answer (`finalize-delta CR-…` skips the prompt).
|
|
85
|
+
#
|
|
86
|
+
# THE DIRECTORY IS THE AUTHORITY for what is open — cr-list reads changes/<id>/ and reports
|
|
87
|
+
# `tracked` separately, so a change whose ledger row was lost is still offered. cr-activate
|
|
88
|
+
# then REFUSES it, which is the outcome that belongs here: a change the ledger does not carry
|
|
89
|
+
# is one close-delta could never archive, so it must not reach the masters.
|
|
90
|
+
#
|
|
91
|
+
# THE MERGE FENCE — THREE REFUSALS, read off disk in one cr-merge-check call and placed
|
|
92
|
+
# before the downstream fence, because they are free while that one runs a test suite:
|
|
93
|
+
#
|
|
94
|
+
# REBASE. The delta carries the master version it was authored against (`**Base version**:`,
|
|
95
|
+
# stamped by cr-setup). If the master has moved, the delta's MODIFIED items name a BEFORE
|
|
96
|
+
# spec.md no longer says, and merging them is last-writer-wins over work that landed.
|
|
97
|
+
#
|
|
98
|
+
# MERGED-NOT-CLOSED. The masters can move WITHOUT the version moving, because this stage
|
|
99
|
+
# merges and close-delta bumps. In that window spec.md carries a change the number does not
|
|
100
|
+
# reflect, so a sibling sharing the same base_version compares EQUAL and would merge onto
|
|
101
|
+
# masters that had already shifted. The version alone is not a proxy for "the masters
|
|
102
|
+
# moved"; the id appearing in spec.md is the other half of the answer.
|
|
103
|
+
#
|
|
104
|
+
# 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. `depends_on` is
|
|
106
|
+
# stamped nowhere, so the wave is the only dependency signal that exists — and it is the
|
|
107
|
+
# conservative one, since same-wave changes are independent by definition.
|
|
108
|
+
#
|
|
109
|
+
# All three are RAISE-then-VERIFY and fail-closed: each flag is raised only by the good
|
|
110
|
+
# state, so a check that did not run leaves the merge refused rather than waved through.
|
|
111
|
+
#
|
|
112
|
+
# NO LOCK. A lock guards CONCURRENT runs; this pipeline is operator-driven and sequential,
|
|
113
|
+
# and the three refusals above cover the sequential case completely. A lockfile would add a
|
|
114
|
+
# stale-lock failure mode to guard a scenario the kit does not have.
|
|
115
|
+
#
|
|
116
|
+
# THE MASTERS ARE SNAPSHOTTED to changes/<id>/pre-merge/ immediately before the rewrite —
|
|
117
|
+
# the pipeline's own record of what they said beforehand, archived with the change.
|
|
118
|
+
#
|
|
119
|
+
# Launch: /appweaver finalize-delta [CR-id] (a bare id skips the prompt)
|
|
69
120
|
# Prereq: an OPEN change whose delta was implemented.
|
|
70
121
|
# =============================================================================
|
|
71
122
|
|
|
72
123
|
name: aw-spec-finalize-delta
|
|
73
|
-
description: Merge
|
|
74
|
-
version: 2.
|
|
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 downstream fence over the transitive blast radius (fence_green), which writes a triage report and asks 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.
|
|
125
|
+
version: 2.4.0
|
|
75
126
|
agent_agnostic: true
|
|
76
127
|
dialect: aw-spec.orchestrator/v1
|
|
77
128
|
|
|
129
|
+
inputs:
|
|
130
|
+
user_input:
|
|
131
|
+
description: "Optional CR id of the change to merge. A bare id selects it and skips the prompt; anything else is ignored and the menu is shown."
|
|
132
|
+
required: false
|
|
133
|
+
bind: USER_INPUT
|
|
134
|
+
|
|
78
135
|
scripts:
|
|
79
136
|
actions:
|
|
80
137
|
sh: .appweaver/scripts/bash/aw-spec-actions.sh
|
|
@@ -88,7 +145,8 @@ standards:
|
|
|
88
145
|
|
|
89
146
|
vars:
|
|
90
147
|
active_change: ""
|
|
91
|
-
|
|
148
|
+
cr_choice: "" # the LABEL the pick returns ("CR-… · wave 2 · title"); scripts take its first token
|
|
149
|
+
has_changes: "false" # from cr-list — the DIRECTORY's answer to "is anything open", not the ledger's
|
|
92
150
|
version: "" # the CURRENT version — unchanged by this stage
|
|
93
151
|
next_version: "" # what close-delta will bump to; the number the Change History rows carry
|
|
94
152
|
downstream_count: "0" # transitive + placeholder-filtered dependents (blast radius); drives the gate
|
|
@@ -101,6 +159,34 @@ vars:
|
|
|
101
159
|
delta_model_exists: "false" # false ⇒ a behaviour-only change: the spec merge is the whole merge
|
|
102
160
|
model_orphan: "false" # a model delta whose context has no master to merge it into
|
|
103
161
|
model_master_ok: "true" # lowered by model_orphan_raise; the gate reads only this
|
|
162
|
+
# THE MERGE FENCE. All three flags default DOWN and are raised only by the good state, so
|
|
163
|
+
# a cr-merge-check that errored or a field that was renamed leaves the merge refused.
|
|
164
|
+
# Fail-closed is the only safe default in front of an in-place edit of the masters.
|
|
165
|
+
rebase_ok: "false"
|
|
166
|
+
merged_open_ok: "false"
|
|
167
|
+
no_blockers: "false"
|
|
168
|
+
rebase_needed: "true" # the raw readings, defaulted to the blocking value for the
|
|
169
|
+
has_merged_open: "true" # same reason — an unanswered question is not a clean answer
|
|
170
|
+
has_blockers: "true"
|
|
171
|
+
base_version: ""
|
|
172
|
+
current_version: ""
|
|
173
|
+
blocker_ids: ""
|
|
174
|
+
blocker_count: "0"
|
|
175
|
+
merged_open_ids: ""
|
|
176
|
+
merge_wave: "1"
|
|
177
|
+
snapshot_dir: "" # changes/<id>/pre-merge/ — the before-image recall-delta restores
|
|
178
|
+
snapshot_count: "0"
|
|
179
|
+
# THE OKF OVERVIEW'S SUBJECT. The Overview step selects a `## Aggregate:` section BY
|
|
180
|
+
# NAME, so it needs the aggregate, which is not always the feature: a feature that is
|
|
181
|
+
# one capability over a shared aggregate (PatientRegistration over Patient) has no
|
|
182
|
+
# section of its own name. delta_aggregates is what the change's own model delta
|
|
183
|
+
# declares; it is empty for a behaviour-only change, which falls back to the aggregate
|
|
184
|
+
# the feature answers for (feature-aggregates — the spec's Acts-on line).
|
|
185
|
+
delta_aggregates: ""
|
|
186
|
+
overview_scope: ""
|
|
187
|
+
overview_section_ok: "false"
|
|
188
|
+
overview_written: "false"
|
|
189
|
+
overview_reason: ""
|
|
104
190
|
backend_plan: "" # READ, never written here — the OKF edges are parsed out of it
|
|
105
191
|
backend_plan_exists: "false"
|
|
106
192
|
|
|
@@ -126,7 +212,7 @@ workflow:
|
|
|
126
212
|
action: verify_context
|
|
127
213
|
require_non_empty: feature_dir
|
|
128
214
|
on_empty: stop
|
|
129
|
-
on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the
|
|
215
|
+
on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the feature, e.g. Order or PatientRegistration), then re-run finalize-delta."
|
|
130
216
|
|
|
131
217
|
- id: verify_spec
|
|
132
218
|
action: verify_context
|
|
@@ -134,24 +220,64 @@ workflow:
|
|
|
134
220
|
on_false: stop
|
|
135
221
|
on_false_message: "No spec.md for the current feature — there is no master to merge into. Run /appweaver specify first."
|
|
136
222
|
|
|
137
|
-
# 1b.
|
|
223
|
+
# 1b. WHICH CHANGE IS BEING MERGED. The evidence is changes/<CR-id>/ on disk; a closed
|
|
224
|
+
# change has moved to changes/archive/. No synthetic rows: `main` is not a change and
|
|
225
|
+
# there is nothing to open here, so the menu is the real open changes and nothing else.
|
|
226
|
+
# `matched_label` resolves a CR id passed as the argument to the whole option string,
|
|
227
|
+
# which is how a bare `finalize-delta CR-…` skips the prompt.
|
|
228
|
+
- id: cr_list
|
|
229
|
+
action: execute_script
|
|
230
|
+
script: { ref: actions }
|
|
231
|
+
subcommand: cr-list
|
|
232
|
+
args: ["--feature-dir", "{{feature_dir}}", "--preselect", "{{USER_INPUT}}"]
|
|
233
|
+
outputs:
|
|
234
|
+
- { name: change_options, from: "$.select_options" }
|
|
235
|
+
- { name: matched_label, from: "$.matched_label" }
|
|
236
|
+
- { name: has_changes, from: "$.has_changes" }
|
|
237
|
+
- { name: change_count, from: "$.count" }
|
|
238
|
+
|
|
239
|
+
- id: require_changes
|
|
240
|
+
action: verify_context
|
|
241
|
+
require_true: has_changes
|
|
242
|
+
on_false: stop
|
|
243
|
+
on_false_message: "No open change on this feature — nothing to finalize. Run /appweaver specify → plan → analyze-plan-delta → implement first. A change that already shipped was archived by /appweaver close-delta and is under changes/archive/, where a merge does not reach it."
|
|
244
|
+
|
|
245
|
+
# single_option_confirm so a feature with exactly one open change still confirms the id
|
|
246
|
+
# out loud. This stage edits the two documents that DEFINE the slice, and picking
|
|
247
|
+
# silently is how the wrong change is merged on a feature that has two open.
|
|
248
|
+
- id: pick_change
|
|
249
|
+
action: user_select
|
|
250
|
+
prompt: "Which open change is being MERGED into spec.md and the context's domain-model.md? Merge the change that was implemented and passed analyze-slice-delta — a change opened but never authored carries an empty delta-spec, and merging it writes nothing while consuming the stage. Deltas sharing a wave are independent; a higher wave is built on top of the lower ones, so merge those first."
|
|
251
|
+
options_from_variable: change_options
|
|
252
|
+
preselect_from: matched_label
|
|
253
|
+
single_option_confirm: true
|
|
254
|
+
outputs:
|
|
255
|
+
- { name: cr_choice, from: selection }
|
|
256
|
+
|
|
257
|
+
# Make the chosen change the ACTIVE one — analyze-slice and close-delta still read
|
|
258
|
+
# active_change, so the stages after this one must follow the same delta. The activate
|
|
259
|
+
# also VALIDATES: it refuses an id the ledger does not carry and stops the run, which is
|
|
260
|
+
# the right outcome for a merge target close-delta could never archive.
|
|
261
|
+
- id: activate
|
|
262
|
+
action: execute_script
|
|
263
|
+
script: { ref: actions }
|
|
264
|
+
subcommand: cr-activate
|
|
265
|
+
args: ["--cr-id", "{{cr_choice}}", "--feature-dir", "{{feature_dir}}"]
|
|
266
|
+
outputs:
|
|
267
|
+
- { name: active_change, from: "$.active_change" }
|
|
268
|
+
|
|
269
|
+
# 1b-i. The VERSION spine, read after the pick and used for the Change History row only.
|
|
270
|
+
# This call is no longer the selector — `active_change` comes from the pick above —
|
|
271
|
+
# and version-read moves nothing, so what comes back is the current number.
|
|
138
272
|
- id: read_change
|
|
139
273
|
action: execute_script
|
|
140
274
|
script: { ref: actions }
|
|
141
275
|
subcommand: version-read
|
|
142
276
|
args: ["--feature-dir", "{{feature_dir}}"]
|
|
143
277
|
outputs:
|
|
144
|
-
- { name: active_change, from: "$.active_change" }
|
|
145
|
-
- { name: has_change, from: "$.has_change" }
|
|
146
278
|
- { name: version, from: "$.version" }
|
|
147
279
|
- { name: next_version, from: "$.next_version" }
|
|
148
280
|
|
|
149
|
-
- id: require_change
|
|
150
|
-
action: verify_context
|
|
151
|
-
require_true: has_change
|
|
152
|
-
on_false: stop
|
|
153
|
-
on_false_message: "No open change on this feature — nothing to finalize. Run /appweaver specify → plan → analyze-plan-delta → implement first."
|
|
154
|
-
|
|
155
281
|
# 1b-ii. The change's own documents, from the ONE resolver that knows the layout: the
|
|
156
282
|
# delta-spec sits at FEATURE level (changes/<id>/) and the model delta at CONTEXT
|
|
157
283
|
# level (specs/<Context>/domain-model.<id>.delta.md), beside the master it merges
|
|
@@ -179,7 +305,7 @@ workflow:
|
|
|
179
305
|
action: verify_context
|
|
180
306
|
require_true: delta_spec_exists
|
|
181
307
|
on_false: stop
|
|
182
|
-
on_false_message: "Change {{active_change}} has no delta-spec.md at {{delta_spec}} — nothing to merge into spec.md. Run /appweaver specify to author the change, or /appweaver recall-delta to withdraw it."
|
|
308
|
+
on_false_message: "Change {{active_change}} has no delta-spec.md at {{delta_spec}} — nothing to merge into spec.md. Run /appweaver specify to author the change, or /appweaver recall-delta {{active_change}} to withdraw it (its documents are archived, the version does not move, and code implement already generated is left alone)."
|
|
183
309
|
|
|
184
310
|
# A model delta with no master to merge it into. specify creates domain-model.md the
|
|
185
311
|
# first time it models a context, so this is a context whose master was moved or
|
|
@@ -204,6 +330,74 @@ workflow:
|
|
|
204
330
|
on_false: stop
|
|
205
331
|
on_false_message: "Change {{active_change}} carries a model delta ({{delta_model}}) but its bounded context has no domain-model.md master at {{domain_model}}. Spec and model merge in one transaction, so this stage will not merge the spec half alone. Restore the context's domain-model.md (it is what specify creates when it first models the context) and re-run /appweaver finalize-delta."
|
|
206
332
|
|
|
333
|
+
# 1b-iii. THE MERGE FENCE — the three refusals that make a parallel merge safe, all read
|
|
334
|
+
# off disk in ONE call, and all placed BEFORE the downstream fence because they are
|
|
335
|
+
# free while that one runs a test suite. Refusing cheaply first is the whole ordering.
|
|
336
|
+
#
|
|
337
|
+
# REBASE: the delta carries the master version it was authored against (cr-setup stamps
|
|
338
|
+
# `**Base version**:` when the change opens). If the master has moved since, the
|
|
339
|
+
# delta's MODIFIED items name a BEFORE the master no longer says, and merging them is
|
|
340
|
+
# last-writer-wins over work that already landed.
|
|
341
|
+
#
|
|
342
|
+
# MERGED-NOT-CLOSED: the masters can move WITHOUT the version moving, because this
|
|
343
|
+
# stage merges and close-delta bumps. Between the two, spec.md carries a change the
|
|
344
|
+
# number does not yet reflect — so a sibling delta sharing the same base_version would
|
|
345
|
+
# compare EQUAL and merge onto masters that had already shifted. The id in spec.md is
|
|
346
|
+
# the proof, the same evidence recall-delta's merged-refusal reads.
|
|
347
|
+
#
|
|
348
|
+
# PREDECESSOR: a lower wave is committed to landing first, and `plan` put exactly those
|
|
349
|
+
# deltas in this one's context as the base it was planned on top of. Merging out of
|
|
350
|
+
# wave order folds in behaviour assuming a predecessor the masters do not carry.
|
|
351
|
+
#
|
|
352
|
+
# NO on_error: continue. A gate that cannot run must not be a gate that passes, and the
|
|
353
|
+
# raise-then-verify below is fail-closed for the same reason: each flag is raised ONLY
|
|
354
|
+
# by the good state, so a field that never arrived leaves it down and the merge refuses.
|
|
355
|
+
- id: merge_check
|
|
356
|
+
action: execute_script
|
|
357
|
+
script: { ref: actions }
|
|
358
|
+
subcommand: cr-merge-check
|
|
359
|
+
args: ["--cr-id", "{{active_change}}", "--feature-dir", "{{feature_dir}}"]
|
|
360
|
+
outputs:
|
|
361
|
+
- { name: rebase_needed, from: "$.rebase_needed" }
|
|
362
|
+
- { name: base_version, from: "$.base_version" }
|
|
363
|
+
- { name: current_version, from: "$.current_version" }
|
|
364
|
+
- { name: has_blockers, from: "$.has_blockers" }
|
|
365
|
+
- { name: blocker_ids, from: "$.blocker_ids" }
|
|
366
|
+
- { name: blocker_count, from: "$.blocker_count" }
|
|
367
|
+
- { name: has_merged_open, from: "$.has_merged_open" }
|
|
368
|
+
- { name: merged_open_ids, from: "$.merged_open_ids" }
|
|
369
|
+
- { name: merge_wave, from: "$.wave" }
|
|
370
|
+
|
|
371
|
+
- id: rebase_ok_raise
|
|
372
|
+
condition: "{{rebase_needed}} == false"
|
|
373
|
+
action: set
|
|
374
|
+
vars: { rebase_ok: "true" }
|
|
375
|
+
- id: require_rebase
|
|
376
|
+
action: verify_context
|
|
377
|
+
require_true: rebase_ok
|
|
378
|
+
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, re-stamp the delta's `**Base version**:` to {{current_version}}, then re-run /appweaver finalize-delta {{active_change}}."
|
|
380
|
+
|
|
381
|
+
- id: merged_open_ok_raise
|
|
382
|
+
condition: "{{has_merged_open}} == false"
|
|
383
|
+
action: set
|
|
384
|
+
vars: { merged_open_ok: "true" }
|
|
385
|
+
- id: require_no_merged_open
|
|
386
|
+
action: verify_context
|
|
387
|
+
require_true: merged_open_ok
|
|
388
|
+
on_false: stop
|
|
389
|
+
on_false_message: "finalize-delta REFUSED — {{merged_open_ids}} has already been MERGED into spec.md but not yet CLOSED, so the masters carry it while the version still reads {{current_version}}. Change {{active_change}} was authored against those pre-merge masters and the version comparison cannot see the difference. Finish the merged change first: /appweaver analyze-slice then /appweaver close-delta {{merged_open_ids}}, which bumps the version — then rebase this one against the new number and re-run /appweaver finalize-delta {{active_change}}."
|
|
390
|
+
|
|
391
|
+
- id: no_blockers_raise
|
|
392
|
+
condition: "{{has_blockers}} == false"
|
|
393
|
+
action: set
|
|
394
|
+
vars: { no_blockers: "true" }
|
|
395
|
+
- id: require_no_blockers
|
|
396
|
+
action: verify_context
|
|
397
|
+
require_true: no_blockers
|
|
398
|
+
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: {{blocker_ids}}. 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. Merge and close the lower-wave change(s) first (/appweaver finalize-delta <CR-id> → analyze-slice → close-delta), 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, /appweaver cr-wave can correct its wave."
|
|
400
|
+
|
|
207
401
|
# 1c. DOWNSTREAM GATE (hard, with a REVIEWED override). Who depends on this aggregate —
|
|
208
402
|
# its OKF Cross-References, TRANSITIVELY (C→B→A all count) and placeholder-filtered
|
|
209
403
|
# (the "_(none observed)_" row is NOT a dependent) = the real blast radius. If any
|
|
@@ -347,6 +541,29 @@ workflow:
|
|
|
347
541
|
action: dispatch_hooks
|
|
348
542
|
hooks_from_variable: before_hooks
|
|
349
543
|
|
|
544
|
+
# 2b. SNAPSHOT THE MASTERS — the last thing before they are rewritten, and after every
|
|
545
|
+
# gate including the downstream override, so the copy is taken only by the attempt
|
|
546
|
+
# that actually merges. finalize-delta may be attempted more than once (a red fence
|
|
547
|
+
# aborts it), and a snapshot from an aborted attempt would claim to be the before-image
|
|
548
|
+
# of a merge that never happened.
|
|
549
|
+
#
|
|
550
|
+
# This is the ONLY record of what the masters said beforehand that belongs to the
|
|
551
|
+
# pipeline rather than to git, and it travels with the change: changes/<id>/ is what
|
|
552
|
+
# cr-archive moves, so a closed change carries the before-image of the documents it
|
|
553
|
+
# moved, in the archive, for as long as the repo exists.
|
|
554
|
+
#
|
|
555
|
+
# ⌛ The RESTORE is not built. recall-delta still refuses a merged change outright
|
|
556
|
+
# rather than un-merging from this copy — the snapshot is what makes that buildable,
|
|
557
|
+
# and the rebase gate above is what will answer whether restoring is still safe.
|
|
558
|
+
- id: snapshot_masters
|
|
559
|
+
action: execute_script
|
|
560
|
+
script: { ref: actions }
|
|
561
|
+
subcommand: cr-snapshot
|
|
562
|
+
args: ["--cr-id", "{{active_change}}", "--feature-dir", "{{feature_dir}}"]
|
|
563
|
+
outputs:
|
|
564
|
+
- { name: snapshot_dir, from: "$.dir" }
|
|
565
|
+
- { name: snapshot_count, from: "$.count" }
|
|
566
|
+
|
|
350
567
|
# 3. THE MERGE: the change's two halves into the two masters, in place.
|
|
351
568
|
# Context is fed via build_context — agent_generate reads the ENVELOPE, not a
|
|
352
569
|
# step-level context: key (unlike agent_author/agent_query which read both).
|
|
@@ -399,8 +616,13 @@ workflow:
|
|
|
399
616
|
that placeholder; a section the delta does not touch keeps its "None" verbatim.
|
|
400
617
|
- Apply every MODIFIED item as an in-place edit of the existing item; apply every
|
|
401
618
|
REMOVED item by removing it. Keep the rest of spec.md VERBATIM.
|
|
402
|
-
- Append a Change History row for {{active_change}} (date + one-line summary)
|
|
403
|
-
Its Version cell is
|
|
619
|
+
- Append a Change History row for {{active_change}} (date + one-line summary), with
|
|
620
|
+
the CR id in its CR cell. Its Version cell is the literal text `(pending)` — write
|
|
621
|
+
that, not a number. THE VERSION DOES NOT EXIST YET: close-delta issues it, and it
|
|
622
|
+
stamps this cell at the same instant it stamps the headers. A number written here
|
|
623
|
+
would be a prediction, and a change that merged but never closed — an abandoned
|
|
624
|
+
run, a red analyze-slice — would leave spec.md asserting a version nothing issued.
|
|
625
|
+
Do not invent a version anywhere, including in this cell.
|
|
404
626
|
To domain-model.md — ONLY where it and a model delta are both in context:
|
|
405
627
|
- THE MERGE UNIT IS ONE `## Aggregate: <Name>` SECTION, and the ONLY sections you may
|
|
406
628
|
open are the ones the model delta's "Aggregates touched" header names. Every other
|
|
@@ -434,9 +656,9 @@ workflow:
|
|
|
434
656
|
version it bumps to — the model's row and its version land together or the row
|
|
435
657
|
carries a number nothing else agrees with.
|
|
436
658
|
Leave the `**Version**:` HEADER of spec.md exactly as it is — it still reads
|
|
437
|
-
{{version}} and does not move here. close-delta stamps it
|
|
438
|
-
moment it bumps active_version.json, so every carrier
|
|
439
|
-
step by one mechanism. Do not invent a version anywhere.
|
|
659
|
+
{{version}} and does not move here. close-delta stamps it, and the Change History
|
|
660
|
+
row's `(pending)` cell, at the moment it bumps active_version.json, so every carrier
|
|
661
|
+
of the number changes in one step by one mechanism. Do not invent a version anywhere.
|
|
440
662
|
Preserve every unrelated section of BOTH masters byte-for-byte. Do NOT touch the
|
|
441
663
|
delta documents (close-delta archives them later), do NOT open or edit any lane plan,
|
|
442
664
|
and do NOT touch any source code. Report the files you changed.
|
|
@@ -478,6 +700,44 @@ workflow:
|
|
|
478
700
|
# splice (the Cross-References rows other slices own are untouched). Skipped where
|
|
479
701
|
# the context has no model: there would be nothing in context to distil, and a
|
|
480
702
|
# query with no source invents one. Best-effort; an empty answer is a no-op.
|
|
703
|
+
# WHICH AGGREGATE SECTION(S) THIS CHANGE ACTUALLY TOUCHED, read off the model delta's
|
|
704
|
+
# own `## Aggregate:` headings rather than assumed from the feature name. The Overview
|
|
705
|
+
# step below selects a section by name, and a feature whose name is not an aggregate
|
|
706
|
+
# name — one capability over a shared aggregate — selects nothing, producing an empty
|
|
707
|
+
# answer that the writer then declines. Driving it off the delta's declared aggregates
|
|
708
|
+
# makes a model change exact.
|
|
709
|
+
#
|
|
710
|
+
# A BEHAVIOUR-ONLY CHANGE HAS NO MODEL DELTA, so this reports none and the step falls
|
|
711
|
+
# back to the aggregate the feature answers for — which is correct, not a degradation:
|
|
712
|
+
# there is no model change to distil and the concept's existing Overview stands.
|
|
713
|
+
- id: cr_aggregates
|
|
714
|
+
action: execute_script
|
|
715
|
+
on_error: continue # informational — a missing model delta is the ordinary case
|
|
716
|
+
script: { ref: actions }
|
|
717
|
+
subcommand: cr-overlap
|
|
718
|
+
args: ["--cr-id", "{{active_change}}", "--feature-dir", "{{feature_dir}}"]
|
|
719
|
+
outputs:
|
|
720
|
+
- { name: delta_aggregates, from: "$.aggregates_csv" }
|
|
721
|
+
- { name: delta_aggregate_list, from: "$.aggregates" }
|
|
722
|
+
|
|
723
|
+
# The fallback subject: the aggregate this feature answers for, which is the feature
|
|
724
|
+
# itself on a 1:1 slice and the shared aggregate on a capability over one.
|
|
725
|
+
- id: feature_aggregates
|
|
726
|
+
action: execute_script
|
|
727
|
+
script: { ref: xref }
|
|
728
|
+
subcommand: feature-aggregates
|
|
729
|
+
args: ["--feature-dir", "{{feature_dir}}"]
|
|
730
|
+
outputs:
|
|
731
|
+
- { name: owned_csv, from: "$.owned_csv" }
|
|
732
|
+
|
|
733
|
+
- id: overview_scope_feature
|
|
734
|
+
action: set
|
|
735
|
+
vars: { overview_scope: "{{owned_csv}}" }
|
|
736
|
+
- id: overview_scope_delta
|
|
737
|
+
condition: "{{delta_aggregates}} != "
|
|
738
|
+
action: set
|
|
739
|
+
vars: { overview_scope: "{{delta_aggregates}}" }
|
|
740
|
+
|
|
481
741
|
# Re-scope the envelope first. build_context REPLACES it and it PERSISTS across the
|
|
482
742
|
# steps that follow, so without this the Overview query would still be holding the
|
|
483
743
|
# merge's payload — the delta-spec and the model delta among it — and would distil the
|
|
@@ -491,13 +751,20 @@ workflow:
|
|
|
491
751
|
condition: "{{domain_model_exists}} == true"
|
|
492
752
|
action: agent_query
|
|
493
753
|
include_standards: false
|
|
494
|
-
schema: { overview: "<2-4 sentences of prose>" }
|
|
495
|
-
mock: { overview: "" }
|
|
754
|
+
schema: { overview: "<2-4 sentences of prose>", section_found: true }
|
|
755
|
+
mock: { overview: "", section_found: false }
|
|
496
756
|
task: |
|
|
497
|
-
Write the OKF knowledge-concept Overview for the aggregate `{{
|
|
498
|
-
merged domain-model.md in context. That document models the WHOLE bounded context
|
|
499
|
-
read ONLY the `## Aggregate: {{
|
|
500
|
-
Invariants` naming it); the other aggregates belong to other concepts.
|
|
757
|
+
Write the OKF knowledge-concept Overview for the aggregate `{{overview_scope}}`, from
|
|
758
|
+
the merged domain-model.md in context. That document models the WHOLE bounded context
|
|
759
|
+
— read ONLY the `## Aggregate: {{overview_scope}}` section (and any `## Cross-Aggregate
|
|
760
|
+
Invariants` naming it); the other aggregates belong to other concepts. Where more than
|
|
761
|
+
one aggregate is named above, this change touched several: write the Overview for the
|
|
762
|
+
FIRST one, which is the concept this feature owns.
|
|
763
|
+
|
|
764
|
+
Set section_found=false if domain-model.md has no `## Aggregate:` section under that
|
|
765
|
+
name, and answer with an empty overview. Do NOT substitute a different section: the
|
|
766
|
+
name is how the concept and the model are keyed to each other, and describing the
|
|
767
|
+
wrong aggregate is worse than describing none.
|
|
501
768
|
2-4 sentences of PROSE: name the aggregate ROOT and what the aggregate IS; state the
|
|
502
769
|
INVARIANTS it guards (its BR-n rules); state its TRANSACTION BOUNDARY — what it OWNS
|
|
503
770
|
inside the boundary vs what it only REFERENCES by id. Plain prose only: no headings,
|
|
@@ -505,7 +772,8 @@ workflow:
|
|
|
505
772
|
(those live in the Depends-On / Cross-References tables). Describe only what the model
|
|
506
773
|
states — invent nothing. If the section is absent, answer with an empty string.
|
|
507
774
|
outputs:
|
|
508
|
-
- { name: okf_overview,
|
|
775
|
+
- { name: okf_overview, from: "$.overview" }
|
|
776
|
+
- { name: overview_section_ok, from: "$.section_found" }
|
|
509
777
|
|
|
510
778
|
- id: okf_overview_write
|
|
511
779
|
condition: "{{domain_model_exists}} == true"
|
|
@@ -514,6 +782,9 @@ workflow:
|
|
|
514
782
|
script: { ref: xref }
|
|
515
783
|
subcommand: concept-overview
|
|
516
784
|
args: ["--feature-dir", "{{feature_dir}}", "--text", "{{okf_overview}}"]
|
|
785
|
+
outputs:
|
|
786
|
+
- { name: overview_written, from: "$.written" }
|
|
787
|
+
- { name: overview_reason, from: "$.reason" }
|
|
517
788
|
|
|
518
789
|
# 4c. Anchor the OKF bundle's freshness journal to the current commit (log.md, OKF §7).
|
|
519
790
|
# Best-effort + git-aware: a no-op when there is no bundle or no commit, so it never
|
|
@@ -555,7 +826,11 @@ outputs:
|
|
|
555
826
|
gate_decision: "{{gate_decision}}"
|
|
556
827
|
xref_edge_count: "{{xref_edge_count}}"
|
|
557
828
|
domain_model: "{{domain_model}}"
|
|
829
|
+
# The before-image of both masters, taken immediately before the rewrite and archived with
|
|
830
|
+
# the change by close-delta. What an un-merge would restore from.
|
|
831
|
+
pre_merge_snapshot: "{{snapshot_dir}}"
|
|
832
|
+
base_version: "{{base_version}}"
|
|
558
833
|
model_merged: "{{delta_model_exists}}" # true ⇒ domain-model.md was the second target of this merge.
|
|
559
834
|
# false = a behaviour-only change: the spec merge was the
|
|
560
835
|
# whole merge, not a half of one.
|
|
561
|
-
next: "Delta MERGED into spec.md, and into the context's domain-model.md one aggregate section at a time where the change carried a model delta (model_merged={{delta_model_exists}} — false means a behaviour-only change, so the spec merge was the whole merge). The lane plans were NOT touched — they are
|
|
836
|
+
next: "Delta MERGED into spec.md, and into the context's domain-model.md one aggregate section at a time where the change carried a model delta (model_merged={{delta_model_exists}} — false means a behaviour-only change, so the spec merge was the whole merge). The lane plans were NOT touched — they are DERIVED from these two masters, so backend-plan.md / frontend-plan.md now stand BEHIND the spec and model at their last authored content. That drift starts here and lasts until /appweaver plan main re-derives them, carrying the recorded decisions (DA-n) and workarounds (WA-n) forward and rewriting what the spec and model no longer support. analyze-plan is not what closes it and cannot repair it while this change is open: with a change open it runs READ-ONLY, reporting the drift into plan-analyze-report.md without rewriting either plan. The full gate — check, repair, stop — comes back once the last change is closed, so the order is close-delta, then plan main, then analyze-plan. OKF edges reconciled ({{xref_edge_count}} dep(s)), {{downstream_count}} downstream slice(s) fenced (gate: {{gate_decision}}). The concept Overview was distilled for aggregate(s) `{{overview_scope}}` — taken from what this change's model delta DECLARES it touched, not assumed from the feature name (empty on a behaviour-only change, which falls back to the aggregate the feature answers for). section_found={{overview_section_ok}}, written={{overview_written}}{{overview_reason}} — written=false is normal for a behaviour-only change (nothing to distil, the existing Overview stands), but section_found=false means domain-model.md has no `## Aggregate:` section under that name and the concept's Overview was left as it was: check that the spec's Acts on aggregate(s) line names an aggregate the model defines. Change {{active_change}} is STILL OPEN and the version is still {{version}}. Next: /appweaver analyze-slice — it now reads masters that carry this change, and a red result is still recoverable because nothing has been archived. Then /appweaver close-delta archives the change and bumps the version."
|