aw-spec-cli 0.1.18 → 0.1.19
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 +45 -21
- package/assets/.appweaver/orchestrator/modules/delta-plan.orch.yaml +62 -14
- package/assets/.appweaver/orchestrator/modules/delta-spec.orch.yaml +6 -4
- 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/design-template.skeleton.md +18 -11
- package/assets/.appweaver/orchestrator/rev-templates/ui-design-template.skeleton.md +5 -3
- package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +298 -109
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +161 -34
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +125 -24
- package/assets/.appweaver/orchestrator/workflows/aw-spec-build.orch.yaml +18 -7
- package/assets/.appweaver/orchestrator/workflows/aw-spec-close-delta.orch.yaml +182 -39
- package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +300 -35
- package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +190 -31
- package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +93 -8
- package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +34 -7
- package/assets/.appweaver/orchestrator/workflows/aw-spec-recall-delta.orch.yaml +319 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +125 -24
- package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +846 -35
- package/assets/.appweaver/scripts/bash/common.sh +28 -1
- package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +843 -46
- package/assets/.appweaver/scripts/python/aw-inventory.py +3 -2
- package/assets/.appweaver/templates/design-template.md +41 -30
- package/assets/.appweaver/templates/spec-template.md +7 -4
- package/assets/.appweaver/templates/ui-design-template.md +7 -3
- 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
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# aw-spec-recall-delta (orchestrator) — WITHDRAW a change that never shipped
|
|
3
|
+
#
|
|
4
|
+
# The escape hatch of the CR loop, and the counterpart of close-delta:
|
|
5
|
+
# close-delta the change SHIPPED — archive it and bump the version
|
|
6
|
+
# recall-delta the change is WITHDRAWN — archive it and bump NOTHING
|
|
7
|
+
#
|
|
8
|
+
# specify → plan → analyze-plan-delta → implement → build → analyze-slice-delta
|
|
9
|
+
# → finalize-delta → analyze-slice → close-delta
|
|
10
|
+
# └─ recall-delta may end it anywhere BEFORE finalize-delta ────────────┘
|
|
11
|
+
#
|
|
12
|
+
# WHY IT EXISTS. Changes get abandoned: the requirement was dropped, the approach was
|
|
13
|
+
# wrong, a wider refactor swallowed it. Until this stage there was no way to say so. The
|
|
14
|
+
# only mechanism that took a change out of the open set was `cr-archive`, which archives
|
|
15
|
+
# AND bumps the version in one act, so withdrawing one meant either burning a version
|
|
16
|
+
# number on work that shipped nothing or deleting the directory by hand and editing
|
|
17
|
+
# active_version.json — an operation with no record, no gate, and nothing to stop it being
|
|
18
|
+
# done to the wrong change. The archive and the bump are now separable (`cr-archive
|
|
19
|
+
# --recall`), and this stage is the supported way to reach that half.
|
|
20
|
+
#
|
|
21
|
+
# A RECALL IS NOT A ROLLBACK. It withdraws the change's DOCUMENTS — its delta-spec, its
|
|
22
|
+
# delta plans and its model delta, all archived intact under changes/archive/<id>/ beside a
|
|
23
|
+
# RECALLED.md that records the date and the reason. It does NOT revert code: a change that
|
|
24
|
+
# reached /appweaver implement has already generated source files, and reverting those is
|
|
25
|
+
# git's job, not this pipeline's. The output and the marker both say so, because a stage
|
|
26
|
+
# that half-undoes something while sounding like it undid all of it is worse than no stage.
|
|
27
|
+
#
|
|
28
|
+
# TWO REFUSALS, BOTH MECHANICAL AND BOTH READ OFF DISK.
|
|
29
|
+
#
|
|
30
|
+
# MERGED — finalize-delta has already folded this change into spec.md and the context's
|
|
31
|
+
# domain-model.md, and appended a Change History row carrying its id. The masters now
|
|
32
|
+
# describe behaviour that includes it, so withdrawing the change would leave them
|
|
33
|
+
# describing work no open change accounts for. The remedy for a merged change that should
|
|
34
|
+
# not have landed is a NEW change that reverses it. Detected from the id's presence in
|
|
35
|
+
# spec.md, which is that Change History row.
|
|
36
|
+
#
|
|
37
|
+
# ⏭️ THE UN-MERGE IS DELIBERATELY NOT BUILT (decision, 2026-09-11 — not a missing piece).
|
|
38
|
+
# Both prerequisites now exist: finalize-delta snapshots both masters to
|
|
39
|
+
# changes/<id>/pre-merge/, and cr-merge-check can prove no other delta landed in between,
|
|
40
|
+
# which is exactly the window a restore would be safe in. It is still not built, because a
|
|
41
|
+
# restore is a whole-file overwrite of two long-lived hand-curated documents, and what it
|
|
42
|
+
# would NOT roll back is the rest of what finalize-delta did — the OKF cross-reference
|
|
43
|
+
# edges and the re-authored concept Overview. A command that restores the masters while
|
|
44
|
+
# leaving the knowledge layer describing the withdrawn change is the kind of half-undo
|
|
45
|
+
# this stage's own header refuses to be. The snapshot is kept regardless: it is the record
|
|
46
|
+
# of what the masters said, and an operator who needs it can read it.
|
|
47
|
+
#
|
|
48
|
+
# DEPENDANTS — a wave is a dependency level. Same wave means independent; a HIGHER wave
|
|
49
|
+
# means planned and built on top of the lower ones. Withdrawing a lower-wave change
|
|
50
|
+
# therefore strands every open change above it, so this stage refuses and names them.
|
|
51
|
+
# `depends_on` is stamped nowhere yet (⌛ with the rest of the parallel-delta merge
|
|
52
|
+
# fence), so the wave is the only dependency signal that actually exists — and it is the
|
|
53
|
+
# conservative one, since same-wave siblings are never counted.
|
|
54
|
+
#
|
|
55
|
+
# THE VERSION DOES NOT MOVE. That is the entire point of the stage and the one invariant
|
|
56
|
+
# worth stating twice: a version records what a change SHIPPED as, and a recalled change
|
|
57
|
+
# shipped nothing. Nothing here stamps a header, appends a Change History row, or touches
|
|
58
|
+
# active_version.json's `version` field.
|
|
59
|
+
#
|
|
60
|
+
# Launch: /appweaver recall-delta [CR-id] (a bare id skips the prompt)
|
|
61
|
+
# Prereq: an OPEN change on the active feature that finalize-delta has NOT merged.
|
|
62
|
+
# =============================================================================
|
|
63
|
+
|
|
64
|
+
name: aw-spec-recall-delta
|
|
65
|
+
description: "Withdraw an open change that will not ship — archive changes/<id>/ and its model delta into changes/archive/<id>/ beside a RECALLED.md recording the date and reason, and drop it from active_version.json's open set WITHOUT bumping the version. Refuses a change finalize-delta already merged into the masters (the remedy there is a new change that reverses it) and one that a higher-wave open change is built on top of. Withdraws documents only — code already generated by implement is not reverted. Invokes no model and generates no code."
|
|
66
|
+
version: 1.0.0
|
|
67
|
+
agent_agnostic: true
|
|
68
|
+
dialect: aw-spec.orchestrator/v1
|
|
69
|
+
|
|
70
|
+
inputs:
|
|
71
|
+
user_input:
|
|
72
|
+
description: "Optional CR id of the change to withdraw. A bare id selects it and skips the prompt; anything else is ignored and the menu is shown."
|
|
73
|
+
required: false
|
|
74
|
+
bind: USER_INPUT
|
|
75
|
+
|
|
76
|
+
scripts:
|
|
77
|
+
actions:
|
|
78
|
+
sh: .appweaver/scripts/bash/aw-spec-actions.sh
|
|
79
|
+
ps1: .appweaver/scripts/powershell/aw-spec-actions.ps1
|
|
80
|
+
xref:
|
|
81
|
+
sh: .appweaver/scripts/bash/aw-xref.sh
|
|
82
|
+
ps1: .appweaver/scripts/powershell/aw-xref.ps1
|
|
83
|
+
|
|
84
|
+
# No standards seed and no domain/tech rule selection: this stage authors nothing, so it
|
|
85
|
+
# has no prompt to govern. It is bookkeeping over changes/ + active_version.json, plus the
|
|
86
|
+
# two refusals.
|
|
87
|
+
standards: []
|
|
88
|
+
|
|
89
|
+
vars:
|
|
90
|
+
has_changes: "false"
|
|
91
|
+
cr_choice: ""
|
|
92
|
+
cr_id: ""
|
|
93
|
+
merged: "false"
|
|
94
|
+
merge_evidence: ""
|
|
95
|
+
has_dependants: "false"
|
|
96
|
+
dependant_ids: ""
|
|
97
|
+
dependant_count: "0"
|
|
98
|
+
# The two refusal flags, both DOWN by default and raised only by the good state — see
|
|
99
|
+
# the gates below for why fail-closed is the right default here.
|
|
100
|
+
not_merged: "false"
|
|
101
|
+
no_dependants: "false"
|
|
102
|
+
recall_confirmed: "false"
|
|
103
|
+
recalled: "false"
|
|
104
|
+
archived_dir: ""
|
|
105
|
+
recall_marker: ""
|
|
106
|
+
open_count: "0"
|
|
107
|
+
|
|
108
|
+
workflow:
|
|
109
|
+
|
|
110
|
+
# 1. Resolve the active feature.
|
|
111
|
+
- id: feature_paths
|
|
112
|
+
action: execute_script
|
|
113
|
+
on_error: continue # never crash raw on 'no feature set' — the gate below explains it
|
|
114
|
+
script: { ref: actions }
|
|
115
|
+
subcommand: feature-paths
|
|
116
|
+
outputs:
|
|
117
|
+
- { name: feature, from: "$.feature" }
|
|
118
|
+
- { name: feature_dir, from: "$.feature_dir" }
|
|
119
|
+
|
|
120
|
+
- id: require_feature
|
|
121
|
+
action: verify_context
|
|
122
|
+
require_non_empty: feature_dir
|
|
123
|
+
on_empty: stop
|
|
124
|
+
on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the aggregate, e.g. Order), then re-run recall-delta."
|
|
125
|
+
|
|
126
|
+
# 2. WHICH CHANGES ARE OPEN — the evidence is changes/<CR-id>/ on disk. No synthetic
|
|
127
|
+
# rows: `main` is not a change and there is nothing to open here, so the menu is the
|
|
128
|
+
# real changes and nothing else. `matched_label` resolves a CR id passed as the
|
|
129
|
+
# argument to the whole option string, which is how a bare `recall-delta CR-…` skips
|
|
130
|
+
# the prompt entirely.
|
|
131
|
+
- id: cr_list
|
|
132
|
+
action: execute_script
|
|
133
|
+
script: { ref: actions }
|
|
134
|
+
subcommand: cr-list
|
|
135
|
+
args: ["--feature-dir", "{{feature_dir}}", "--preselect", "{{USER_INPUT}}"]
|
|
136
|
+
outputs:
|
|
137
|
+
- { name: change_options, from: "$.select_options" }
|
|
138
|
+
- { name: matched_label, from: "$.matched_label" }
|
|
139
|
+
- { name: has_changes, from: "$.has_changes" }
|
|
140
|
+
- { name: change_count, from: "$.count" }
|
|
141
|
+
|
|
142
|
+
- id: require_changes
|
|
143
|
+
action: verify_context
|
|
144
|
+
require_true: has_changes
|
|
145
|
+
on_false: stop
|
|
146
|
+
on_false_message: "No open change on this feature — nothing to withdraw. A change is opened by /appweaver specify; one that already shipped was archived by /appweaver close-delta and is under changes/archive/, where a recall does not reach it."
|
|
147
|
+
|
|
148
|
+
# 3. WHICH ONE. single_option_confirm so a feature with exactly one open change still
|
|
149
|
+
# confirms the id out loud — this stage archives documents, and picking silently is
|
|
150
|
+
# how the wrong change gets withdrawn on a feature the operator has two open on.
|
|
151
|
+
- id: pick_change
|
|
152
|
+
action: user_select
|
|
153
|
+
prompt: "Which open change is being WITHDRAWN? Its delta-spec, delta plans and model delta are archived under changes/archive/ and it leaves the open set — with no version bump, because a recalled change ships nothing. Code that /appweaver implement already generated for it is NOT reverted; that is a git operation."
|
|
154
|
+
options_from_variable: change_options
|
|
155
|
+
preselect_from: matched_label
|
|
156
|
+
single_option_confirm: true
|
|
157
|
+
outputs:
|
|
158
|
+
- { name: cr_choice, from: selection }
|
|
159
|
+
|
|
160
|
+
# 3b. That change's documents, and the clean id back out of the label the pick returned.
|
|
161
|
+
- id: cr_paths
|
|
162
|
+
action: execute_script
|
|
163
|
+
script: { ref: actions }
|
|
164
|
+
subcommand: cr-paths
|
|
165
|
+
args: ["--cr-id", "{{cr_choice}}", "--feature-dir", "{{feature_dir}}"]
|
|
166
|
+
outputs:
|
|
167
|
+
- { name: cr_id, from: "$.cr_id" }
|
|
168
|
+
- { name: cr_dir, from: "$.cr_dir" }
|
|
169
|
+
- { name: delta_spec, from: "$.delta_spec" }
|
|
170
|
+
- { name: delta_model, from: "$.delta_model" }
|
|
171
|
+
- { name: wave, from: "$.wave" }
|
|
172
|
+
|
|
173
|
+
# 4. THE TWO REFUSALS, both answered from disk in one call — see the header for why each
|
|
174
|
+
# one is a stop rather than a warning.
|
|
175
|
+
- id: recall_check
|
|
176
|
+
action: execute_script
|
|
177
|
+
script: { ref: actions }
|
|
178
|
+
subcommand: cr-recall-check
|
|
179
|
+
args: ["--cr-id", "{{cr_choice}}", "--feature-dir", "{{feature_dir}}"]
|
|
180
|
+
outputs:
|
|
181
|
+
- { name: merged, from: "$.merged" }
|
|
182
|
+
- { name: merge_evidence, from: "$.merge_evidence" }
|
|
183
|
+
- { name: has_dependants, from: "$.has_dependants" }
|
|
184
|
+
- { name: dependant_ids, from: "$.dependant_ids" }
|
|
185
|
+
- { name: dependant_count, from: "$.dependant_count" }
|
|
186
|
+
|
|
187
|
+
# Each refusal is a RAISE then a gate, not a conditional gate: the raise fires only on
|
|
188
|
+
# the good state, so a value that never arrived (the script errored, the field was
|
|
189
|
+
# renamed) leaves the flag down and the stage refuses. Fail-closed is the only safe
|
|
190
|
+
# default for a gate in front of an archive.
|
|
191
|
+
- id: not_merged_raise
|
|
192
|
+
condition: "{{merged}} == false"
|
|
193
|
+
action: set
|
|
194
|
+
vars: { not_merged: "true" }
|
|
195
|
+
|
|
196
|
+
- id: refuse_merged
|
|
197
|
+
action: verify_context
|
|
198
|
+
require_true: not_merged
|
|
199
|
+
on_false: stop
|
|
200
|
+
on_false_message: "Change {{cr_id}} has already been MERGED — /appweaver finalize-delta folded it into spec.md and the context's domain-model.md, and the spec's Change History carries it: {{merge_evidence}}. The masters now describe behaviour that includes this change, so withdrawing it would leave them describing work no open change accounts for. A recall cannot un-merge, and this stage will not pretend otherwise. If the change should not have landed, finish it (/appweaver analyze-slice → close-delta) and then open a NEW change that reverses it with /appweaver specify — that keeps both the landing and the reversal in the ledger, which is what a reader of the spec needs to see."
|
|
201
|
+
|
|
202
|
+
- id: no_dependants_raise
|
|
203
|
+
condition: "{{has_dependants}} == false"
|
|
204
|
+
action: set
|
|
205
|
+
vars: { no_dependants: "true" }
|
|
206
|
+
|
|
207
|
+
- id: refuse_dependants
|
|
208
|
+
action: verify_context
|
|
209
|
+
require_true: no_dependants
|
|
210
|
+
on_false: stop
|
|
211
|
+
on_false_message: "Change {{cr_id}} is at wave {{wave}} and {{dependant_count}} open change(s) sit at a HIGHER wave: {{dependant_ids}}. A higher wave means planned and built ON TOP of the lower ones, so withdrawing this one strands them — their delta plans were authored against a predecessor that would no longer exist. Withdraw the higher-wave change(s) first (/appweaver recall-delta <CR-id>), or re-plan them against the lower waves that remain (/appweaver plan <CR-id>) before recalling this one. Same-wave changes are independent and are not counted here."
|
|
212
|
+
|
|
213
|
+
# 5. CONFIRM. Ordered no-first against the kit's usual yes-first convention, and
|
|
214
|
+
# deliberately: act_ask takes options[0] non-interactively, so an unattended run
|
|
215
|
+
# DECLINES rather than archiving a change nobody was watching. The reason is asked
|
|
216
|
+
# for in the same breath because it is what the marker records — a withdrawn change
|
|
217
|
+
# with no stated reason is a directory in an archive that nobody can account for later.
|
|
218
|
+
- id: confirm_recall
|
|
219
|
+
action: ask
|
|
220
|
+
prompt: "Withdraw {{cr_id}} (wave {{wave}})? Its documents — {{delta_spec}}, its delta plans and {{delta_model}} — move to changes/archive/{{cr_id}}/ and it leaves the open set. The version does NOT move: this change ships nothing. Any code implement already generated for it stays on disk; revert that with git if it should not remain. Answer no to stop with everything untouched."
|
|
221
|
+
options: ["no", "yes"]
|
|
222
|
+
answer_var: recall_answer
|
|
223
|
+
|
|
224
|
+
- id: recall_go
|
|
225
|
+
condition: "{{recall_answer}} == yes"
|
|
226
|
+
action: set
|
|
227
|
+
vars: { recall_confirmed: "true" }
|
|
228
|
+
|
|
229
|
+
- id: require_confirm
|
|
230
|
+
action: verify_context
|
|
231
|
+
require_true: recall_confirmed
|
|
232
|
+
on_false: stop
|
|
233
|
+
on_false_message: "Stopped — {{cr_id}} is untouched and still open. Nothing was archived and no version moved. Re-run and answer yes to withdraw it, or carry on with it: /appweaver plan {{cr_id}} to (re)author its delta plans, or /appweaver implement {{cr_id}} to build it."
|
|
234
|
+
|
|
235
|
+
- id: ask_reason
|
|
236
|
+
action: ask
|
|
237
|
+
prompt: "Why is {{cr_id}} being withdrawn? One line — it is written into changes/archive/{{cr_id}}/RECALLED.md, which is the only record of why this work stopped."
|
|
238
|
+
options: ["superseded by another change", "requirement dropped", "approach was wrong"]
|
|
239
|
+
answer_var: recall_reason
|
|
240
|
+
|
|
241
|
+
# 6. Pre-execution hooks.
|
|
242
|
+
- id: resolve_pre_hooks
|
|
243
|
+
action: execute_script
|
|
244
|
+
script: { ref: actions }
|
|
245
|
+
subcommand: resolve-hooks
|
|
246
|
+
args: ["--phase", "before_recall_cr"]
|
|
247
|
+
outputs:
|
|
248
|
+
- { name: before_hooks, from: "$.hooks" }
|
|
249
|
+
- id: dispatch_pre_hooks
|
|
250
|
+
action: dispatch_hooks
|
|
251
|
+
hooks_from_variable: before_hooks
|
|
252
|
+
|
|
253
|
+
# 7. THE RECALL. --recall is what separates this from close-delta: the same archive of
|
|
254
|
+
# the directory and of the model delta that lives beside the master, plus the
|
|
255
|
+
# RECALLED.md marker, and NO version bump. The script refuses an id the ledger does
|
|
256
|
+
# not carry and leaves everything in place when it does, so a refusal here is a no-op
|
|
257
|
+
# rather than a half-archived change.
|
|
258
|
+
- id: recall
|
|
259
|
+
action: execute_script
|
|
260
|
+
script: { ref: actions }
|
|
261
|
+
subcommand: cr-archive
|
|
262
|
+
args: ["--feature-dir", "{{feature_dir}}", "--cr-id", "{{cr_choice}}", "--recall", "--reason", "{{recall_reason}}"]
|
|
263
|
+
outputs:
|
|
264
|
+
- { name: recall_ok, from: "$.ok" }
|
|
265
|
+
- { name: recalled, from: "$.recalled" }
|
|
266
|
+
- { name: archived_dir, from: "$.archived_dir" }
|
|
267
|
+
- { name: archived_model, from: "$.archived_model" }
|
|
268
|
+
- { name: recall_marker, from: "$.recall_marker" }
|
|
269
|
+
- { name: version, from: "$.version" }
|
|
270
|
+
- { name: active_change, from: "$.active_change" }
|
|
271
|
+
- { name: open_count, from: "$.open_count" }
|
|
272
|
+
|
|
273
|
+
- id: verify_recalled
|
|
274
|
+
action: verify_context
|
|
275
|
+
require_true: recall_ok
|
|
276
|
+
on_false: stop
|
|
277
|
+
on_false_message: "The withdrawal of {{cr_id}} did not complete and nothing was moved — active_version.json does not carry it as an open change. That happens when the change was already closed or recalled, or when its directory exists on disk but was never registered by /appweaver specify. Check {{feature_dir}}/active_version.json against {{feature_dir}}/changes/."
|
|
278
|
+
|
|
279
|
+
# 8. Anchor the WITHDRAWAL in the OKF bundle's freshness journal (log.md, OKF §7).
|
|
280
|
+
# close-delta logs the ship; this logs the stop, which is exactly as much a part of
|
|
281
|
+
# the feature's history. Best-effort + git-aware: a no-op when there is no bundle.
|
|
282
|
+
- id: xref_log
|
|
283
|
+
action: execute_script
|
|
284
|
+
on_error: continue
|
|
285
|
+
script: { ref: xref }
|
|
286
|
+
subcommand: log
|
|
287
|
+
args: ["--note", "CR {{cr_id}} RECALLED — withdrawn before merge ({{recall_reason}}), archived to {{archived_dir}}, version unchanged at {{version}}"]
|
|
288
|
+
outputs:
|
|
289
|
+
- { name: xref_log, from: "$.log" }
|
|
290
|
+
|
|
291
|
+
# 9. Post-execution hooks.
|
|
292
|
+
- id: resolve_post_hooks
|
|
293
|
+
action: execute_script
|
|
294
|
+
script: { ref: actions }
|
|
295
|
+
subcommand: resolve-hooks
|
|
296
|
+
args: ["--phase", "after_recall_cr"]
|
|
297
|
+
outputs:
|
|
298
|
+
- { name: after_hooks, from: "$.hooks" }
|
|
299
|
+
- id: dispatch_post_hooks
|
|
300
|
+
action: dispatch_hooks
|
|
301
|
+
hooks_from_variable: after_hooks
|
|
302
|
+
|
|
303
|
+
outputs:
|
|
304
|
+
feature: "{{feature}}"
|
|
305
|
+
feature_dir: "{{feature_dir}}"
|
|
306
|
+
cr_id: "{{cr_id}}"
|
|
307
|
+
wave: "{{wave}}"
|
|
308
|
+
reason: "{{recall_reason}}"
|
|
309
|
+
archived_dir: "{{archived_dir}}"
|
|
310
|
+
archived_model: "{{archived_model}}"
|
|
311
|
+
recall_marker: "{{recall_marker}}"
|
|
312
|
+
# The number BEFORE and after are the same number — stated as an output because "nothing
|
|
313
|
+
# moved" is the result this stage exists to produce, and an absent field would read as
|
|
314
|
+
# an unanswered question rather than an answered one.
|
|
315
|
+
version: "{{version}}"
|
|
316
|
+
open_changes_remaining: "{{open_count}}"
|
|
317
|
+
active_change: "{{active_change}}"
|
|
318
|
+
note: "Change {{cr_id}} WITHDRAWN — its delta-spec, delta plans and model delta are archived intact at {{archived_dir}}, with the reason recorded in {{recall_marker}}. The version did NOT move and stands at {{version}}: this change shipped nothing, so no number was spent on it. spec.md and the context's domain-model.md never carried it and were not touched. Code that /appweaver implement generated for this change is still on disk — a recall withdraws documents, not source; revert it with git if it should not stay."
|
|
319
|
+
next: "{{open_count}} change(s) still open on this feature. Carry on with one of them (/appweaver plan <CR-id> → analyze-plan-delta → implement), or with none left the feature is back on its masters: /appweaver plan main re-derives the lane plans from spec.md and the domain model, and /appweaver specify opens a fresh change."
|
|
@@ -15,7 +15,14 @@
|
|
|
15
15
|
# assert different rules for it and nothing arbitrates. Authoring both here is also what
|
|
16
16
|
# keeps the model in step with the spec version: it never waits for plan or implement.
|
|
17
17
|
#
|
|
18
|
-
# Auto-detects first-vs-subsequent from spec.md
|
|
18
|
+
# Auto-detects first-vs-subsequent from feature-paths' needs_bootstrap — NOT from spec.md's
|
|
19
|
+
# presence. The two differ on one state and it is the one that corrupts: a first run that
|
|
20
|
+
# wrote spec.md and then died leaves the file on disk with the unfinished-bootstrap marker
|
|
21
|
+
# still up, and the file alone reads as a finished spec to EXTEND. version-init raises that
|
|
22
|
+
# marker (--bootstrap, which adopt must never pass — a finished reversal is not a crashed
|
|
23
|
+
# draft) and version-bootstrap-done lowers it AFTER the converge block, so the whole
|
|
24
|
+
# authoring window is recoverable. An interrupted draft ASKS before it is overwritten,
|
|
25
|
+
# because a deliberate stop and a crash look identical from here:
|
|
19
26
|
# • BOOTSTRAP (no spec.md) → author the MASTER spec (US-1) + the context's MASTER
|
|
20
27
|
# domain model + init active_version.json. On the context's second feature the model
|
|
21
28
|
# already exists and gains a section rather than being replaced.
|
|
@@ -33,11 +40,12 @@
|
|
|
33
40
|
# defaults a new delta to the wave already in flight (parallel by default); `cr-wave`
|
|
34
41
|
# raises one that turns out to build on another.
|
|
35
42
|
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
43
|
+
# EVERY DELTA STAGE NOW ASKS WHICH CHANGE. plan, analyze-plan-delta, implement,
|
|
44
|
+
# recall-delta, finalize-delta and close-delta all list what is open, ask, and activate the
|
|
45
|
+
# answer, with a bare CR id as the argument doubling as the answer. None of them takes
|
|
46
|
+
# active_version.json's `active_change` on trust — which matters most here, because THIS
|
|
47
|
+
# phase overwrites that field on every run: a bare finalize used to merge whichever change
|
|
48
|
+
# was opened last, and `cr-setup` scaffolds a delta-spec stub the moment it opens one.
|
|
41
49
|
#
|
|
42
50
|
# Both paths then run ONE converge block — checklist + clarify + validate — over the
|
|
43
51
|
# document THIS invocation authored, never the other one:
|
|
@@ -66,7 +74,7 @@
|
|
|
66
74
|
|
|
67
75
|
name: aw-spec-specify
|
|
68
76
|
description: Author the ACTIVE feature's specification AND its bounded context's domain model, together. The aggregate root, its entities, events and invariants (BR-n) live in specs/<Context>/domain-model.md — the one canonical definition every spec in the context cites by id and never restates. First call writes the master spec (US-1) plus the master model; a later call opens its OWN change (many may be open at once) and writes that change's delta-spec and delta-domain-model, leaving both masters untouched for finalize-delta to merge. Each change carries a WAVE — deltas sharing one are independent and developable in parallel, a higher wave is implemented after the lower ones land — and its model delta sits beside the master so every other open delta can see it while being authored. Spec phase only — plan/design is separate.
|
|
69
|
-
version: 3.
|
|
77
|
+
version: 3.5.0
|
|
70
78
|
agent_agnostic: true
|
|
71
79
|
dialect: aw-spec.orchestrator/v1
|
|
72
80
|
|
|
@@ -115,6 +123,19 @@ vars:
|
|
|
115
123
|
# the checklist. NEVER hand-set to spec_file on the delta
|
|
116
124
|
# path — that is what silently drifts an adopted spec.
|
|
117
125
|
version: "" # current active_version.json version (for the report)
|
|
126
|
+
bootstrap_cleared: "false" # whether THIS run lowered the unfinished-first-draft marker
|
|
127
|
+
# THE BOOTSTRAP BRANCH AND ITS CRASH-RESUME. is_bootstrap is what every branch below
|
|
128
|
+
# reads; it starts from feature-paths' needs_bootstrap, which is `no spec.md` OR `a first
|
|
129
|
+
# draft is still marked unfinished`. The second half is the crash case, and it is the one
|
|
130
|
+
# that used to become a delta against a v1.0 nobody finished.
|
|
131
|
+
spec_exists: "false"
|
|
132
|
+
needs_bootstrap: "true" # defaulted to BOOTSTRAP: with no answer, authoring a first
|
|
133
|
+
# draft is recoverable, while extending a spec that may not
|
|
134
|
+
# exist is not
|
|
135
|
+
bootstrap_pending: "false"
|
|
136
|
+
is_bootstrap: "false"
|
|
137
|
+
resume_bootstrap: "false" # bootstrap_pending AND spec.md on disk ⇒ a draft was interrupted
|
|
138
|
+
resume_choice: ""
|
|
118
139
|
|
|
119
140
|
workflow:
|
|
120
141
|
|
|
@@ -129,6 +150,14 @@ workflow:
|
|
|
129
150
|
- { name: feature_dir, from: "$.feature_dir" }
|
|
130
151
|
- { name: spec_file, from: "$.spec_file" }
|
|
131
152
|
- { name: spec_exists, from: "$.spec_exists" }
|
|
153
|
+
# WHICH BRANCH THIS RUN TAKES. needs_bootstrap, not spec_exists — the two differ on
|
|
154
|
+
# exactly one state, and it is the one that corrupts: a FIRST run that wrote spec.md
|
|
155
|
+
# and then died leaves the file on disk with the unfinished-bootstrap marker still up,
|
|
156
|
+
# and branching on the file alone reads that as a finished spec to EXTEND. The next
|
|
157
|
+
# call then opens a change against a v1.0 nobody finished. feature-paths already
|
|
158
|
+
# computed this in both shells; nothing read it.
|
|
159
|
+
- { name: needs_bootstrap, from: "$.needs_bootstrap" }
|
|
160
|
+
- { name: bootstrap_pending, from: "$.bootstrap_pending" }
|
|
132
161
|
|
|
133
162
|
- id: require_feature
|
|
134
163
|
action: verify_context
|
|
@@ -185,27 +214,78 @@ workflow:
|
|
|
185
214
|
action: dispatch_hooks
|
|
186
215
|
hooks_from_variable: before_hooks
|
|
187
216
|
|
|
188
|
-
#
|
|
217
|
+
# ═══════════ WHICH BRANCH — and the one case where it is ASKED ═══════════
|
|
218
|
+
# is_bootstrap takes needs_bootstrap's answer. Two states reach it:
|
|
219
|
+
# no spec.md → an ordinary first run. Nothing is asked.
|
|
220
|
+
# spec.md + the marker still up → a first draft was INTERRUPTED. Re-authoring is
|
|
221
|
+
# the designed recovery, but it OVERWRITES what is
|
|
222
|
+
# on disk, and a deliberate stop is indistinguishable
|
|
223
|
+
# from a crash. So that case, and only that case,
|
|
224
|
+
# asks before it writes.
|
|
225
|
+
- id: branch_from_bootstrap
|
|
226
|
+
action: set
|
|
227
|
+
vars: { is_bootstrap: "{{needs_bootstrap}}" }
|
|
228
|
+
|
|
229
|
+
# The AND, as an ordered assignment (the engine has no compound conditions): under a
|
|
230
|
+
# raised marker, take spec.md's presence as the answer. A marker with no spec.md is a
|
|
231
|
+
# first run that died before writing anything — there is nothing to overwrite and
|
|
232
|
+
# nothing to ask.
|
|
233
|
+
- id: flag_resume
|
|
234
|
+
condition: "{{bootstrap_pending}} == true"
|
|
235
|
+
action: set
|
|
236
|
+
vars: { resume_bootstrap: "{{spec_exists}}" }
|
|
237
|
+
|
|
238
|
+
- id: ask_resume
|
|
239
|
+
condition: "{{resume_bootstrap}} == true"
|
|
240
|
+
action: ask
|
|
241
|
+
prompt: |
|
|
242
|
+
A FIRST DRAFT of this feature's spec.md was started and never finished — the
|
|
243
|
+
unfinished-bootstrap marker in active_version.json is still up, which means the last
|
|
244
|
+
run wrote the file and then stopped before completing it.
|
|
245
|
+
|
|
246
|
+
Re-author it (the draft is replaced from your input, and the domain model with it), or
|
|
247
|
+
treat what is on disk as the finished v1.0 and open a CHANGE against it instead?
|
|
248
|
+
|
|
249
|
+
Re-authoring OVERWRITES specs/…/spec.md. Treat-as-finished keeps it exactly as it is
|
|
250
|
+
and clears the marker, so this is asked once either way.
|
|
251
|
+
options: ["Re-author the first draft", "Treat it as finished"]
|
|
252
|
+
answer_var: resume_choice
|
|
253
|
+
|
|
254
|
+
# Taking it as finished is a decision about the DOCUMENT, so it clears the marker and
|
|
255
|
+
# hands the run to the delta path — the same path an ordinary later specify takes.
|
|
256
|
+
- id: accept_draft
|
|
257
|
+
condition: "{{resume_choice}} == Treat it as finished"
|
|
258
|
+
action: set
|
|
259
|
+
vars: { is_bootstrap: "false" }
|
|
260
|
+
|
|
261
|
+
- id: accept_draft_clear
|
|
262
|
+
condition: "{{resume_choice}} == Treat it as finished"
|
|
263
|
+
action: execute_script
|
|
264
|
+
script: { ref: actions }
|
|
265
|
+
subcommand: version-bootstrap-done
|
|
266
|
+
args: ["--feature-dir", "{{feature_dir}}"]
|
|
267
|
+
|
|
268
|
+
# ═══════════════════════ BOOTSTRAP — no finished spec.md yet ══════════════
|
|
189
269
|
# Copy the spec template into the (already-existing) feature dir + init version.
|
|
190
270
|
- id: boot_scaffold
|
|
191
|
-
condition: "{{
|
|
271
|
+
condition: "{{is_bootstrap}} == true"
|
|
192
272
|
action: execute_script
|
|
193
273
|
script: { ref: actions }
|
|
194
274
|
subcommand: scaffold
|
|
195
275
|
args: ["--feature-dir", "{{feature_dir}}", "--allow-existing"]
|
|
196
276
|
|
|
197
277
|
- id: boot_version_init
|
|
198
|
-
condition: "{{
|
|
278
|
+
condition: "{{is_bootstrap}} == true"
|
|
199
279
|
action: execute_script
|
|
200
280
|
script: { ref: actions }
|
|
201
281
|
subcommand: version-init
|
|
202
|
-
args: ["--feature-dir", "{{feature_dir}}"]
|
|
282
|
+
args: ["--feature-dir", "{{feature_dir}}", "--bootstrap"]
|
|
203
283
|
|
|
204
284
|
# Copy the domain-model template into the CONTEXT (not the feature) when the context
|
|
205
285
|
# has no model yet. Idempotent and non-destructive: the second feature in a context
|
|
206
286
|
# finds the model already there and adds its aggregate to it rather than replacing it.
|
|
207
287
|
- id: boot_model_scaffold
|
|
208
|
-
condition: "{{
|
|
288
|
+
condition: "{{is_bootstrap}} == true"
|
|
209
289
|
action: execute_script
|
|
210
290
|
script: { ref: actions }
|
|
211
291
|
subcommand: model-scaffold
|
|
@@ -215,7 +295,7 @@ workflow:
|
|
|
215
295
|
- { name: model_was_created, from: "$.created" }
|
|
216
296
|
|
|
217
297
|
- id: boot_ctx
|
|
218
|
-
condition: "{{
|
|
298
|
+
condition: "{{is_bootstrap}} == true"
|
|
219
299
|
action: build_context
|
|
220
300
|
context:
|
|
221
301
|
- "{{spec_file}}" # the copied template = section structure
|
|
@@ -241,7 +321,7 @@ workflow:
|
|
|
241
321
|
# no file and no ux/ rules mapped, the section resolves to
|
|
242
322
|
# "None" and the spec is unchanged from a backend-only one.
|
|
243
323
|
- id: boot_generate
|
|
244
|
-
condition: "{{
|
|
324
|
+
condition: "{{is_bootstrap}} == true"
|
|
245
325
|
action: agent_author
|
|
246
326
|
output_target: { type: file, path: "{{spec_file}}" }
|
|
247
327
|
task: |
|
|
@@ -334,7 +414,7 @@ workflow:
|
|
|
334
414
|
# which a frontend-only project has as much as any other; where an invariant is
|
|
335
415
|
# ENFORCED is a plan concern and is recorded in backend-plan.md.
|
|
336
416
|
- id: boot_model_ctx
|
|
337
|
-
condition: "{{
|
|
417
|
+
condition: "{{is_bootstrap}} == true"
|
|
338
418
|
action: build_context
|
|
339
419
|
context:
|
|
340
420
|
- "{{domain_model}}" # the model (template on a first feature, populated on a later one)
|
|
@@ -343,7 +423,7 @@ workflow:
|
|
|
343
423
|
- { path: .appweaver/memory/constitution.ui.md, optional: true }
|
|
344
424
|
|
|
345
425
|
- id: boot_model_generate
|
|
346
|
-
condition: "{{
|
|
426
|
+
condition: "{{is_bootstrap}} == true"
|
|
347
427
|
action: agent_author
|
|
348
428
|
output_target: { type: file, path: "{{domain_model}}" }
|
|
349
429
|
task: |
|
|
@@ -379,15 +459,18 @@ workflow:
|
|
|
379
459
|
|
|
380
460
|
# ═════════════════════════ DELTA — spec.md exists ═════════════════════════
|
|
381
461
|
# A later requirement = a new user story on the SAME aggregate. Delegate to the
|
|
382
|
-
# delta-spec module: it opens the change (cr-setup
|
|
383
|
-
#
|
|
384
|
-
#
|
|
385
|
-
#
|
|
386
|
-
#
|
|
462
|
+
# delta-spec module: it opens the change (cr-setup) and authors BOTH of the change's
|
|
463
|
+
# halves — the delta-spec under changes/<cr-id>/ and the model delta beside the context's
|
|
464
|
+
# master model — leaving the master spec.md and domain-model.md UNTOUCHED. MANY changes
|
|
465
|
+
# may be open at once: every call on an established feature opens its own, the wave is
|
|
466
|
+
# what orders them, and the single-active-change guard that used to refuse a second one
|
|
467
|
+
# is gone. The change stays OPEN; finalize-delta merges both halves later in the chain.
|
|
468
|
+
# Runs in this context (shares vars + the DDD + domain sticky standards). The former
|
|
469
|
+
# standalone `cr` capture's spec half now lives here. Downstream: plan (the delta plans) →
|
|
387
470
|
# analyze-plan-delta → implement → build → analyze-slice-delta → finalize-delta → analyze-slice →
|
|
388
471
|
# close-delta (close-delta is what finally archives the change and bumps the version).
|
|
389
472
|
- id: delta_spec_module
|
|
390
|
-
condition: "{{
|
|
473
|
+
condition: "{{is_bootstrap}} == false"
|
|
391
474
|
action: include
|
|
392
475
|
module: .appweaver/orchestrator/modules/delta-spec.orch.yaml
|
|
393
476
|
|
|
@@ -404,7 +487,7 @@ workflow:
|
|
|
404
487
|
# content; note it rewrites the file, so it is deliberately scoped to the path that
|
|
405
488
|
# authored the document (a delta run no longer resets the master's scored boxes).
|
|
406
489
|
- id: create_checklist
|
|
407
|
-
condition: "{{
|
|
490
|
+
condition: "{{is_bootstrap}} == true"
|
|
408
491
|
action: execute_script
|
|
409
492
|
script: { ref: actions }
|
|
410
493
|
subcommand: create-checklist
|
|
@@ -418,7 +501,7 @@ workflow:
|
|
|
418
501
|
# checklist's items assume a full spec and would mis-fire here, which is exactly why
|
|
419
502
|
# this is a separate checklist rather than the same one re-pointed.
|
|
420
503
|
- id: create_delta_checklist
|
|
421
|
-
condition: "{{
|
|
504
|
+
condition: "{{is_bootstrap}} == false"
|
|
422
505
|
action: execute_script
|
|
423
506
|
script: { ref: actions }
|
|
424
507
|
subcommand: create-checklist
|
|
@@ -526,6 +609,24 @@ workflow:
|
|
|
526
609
|
outputs:
|
|
527
610
|
- { name: checklist_passed, from: "$.after_checked" }
|
|
528
611
|
|
|
612
|
+
# 7b. THE FIRST DRAFT IS FINISHED — lower the marker. Placed HERE, after the converge
|
|
613
|
+
# block, because that is the last thing the bootstrap path does to the document: a
|
|
614
|
+
# run that died inside the checklist or the clarify loop left a spec.md that is
|
|
615
|
+
# present but not yet what it will be, and clearing earlier would certify exactly the
|
|
616
|
+
# half-written state the marker exists to catch. Everything between version-init
|
|
617
|
+
# --bootstrap and this step is the window a crash is recoverable in.
|
|
618
|
+
#
|
|
619
|
+
# Idempotent: clearing a marker that is already down is the ordinary outcome of a
|
|
620
|
+
# re-run and is not an error, so this needs no guard beyond the branch.
|
|
621
|
+
- id: boot_done
|
|
622
|
+
condition: "{{is_bootstrap}} == true"
|
|
623
|
+
action: execute_script
|
|
624
|
+
script: { ref: actions }
|
|
625
|
+
subcommand: version-bootstrap-done
|
|
626
|
+
args: ["--feature-dir", "{{feature_dir}}"]
|
|
627
|
+
outputs:
|
|
628
|
+
- { name: bootstrap_cleared, from: "$.cleared" }
|
|
629
|
+
|
|
529
630
|
# 8. Read the current version for the report (both paths).
|
|
530
631
|
- id: version_read
|
|
531
632
|
action: execute_script
|