aw-spec-cli 0.1.13 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/.appweaver/STACK-PROFILES.md +1 -1
- package/assets/.appweaver/orchestrator/orchestrator.py +5 -0
- package/assets/.appweaver/orchestrator/rev-templates/domain-rule-bindings.skeleton.md +10 -3
- package/assets/.appweaver/orchestrator/rev-templates/tech-rule-bindings.skeleton.md +38 -11
- package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +2 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-design.orch.yaml +607 -123
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +625 -66
- package/assets/.appweaver/orchestrator/workflows/aw-spec-implement-ui.orch.yaml +407 -134
- package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +452 -143
- package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +78 -14
- package/assets/.appweaver/orchestrator/workflows/aw-spec-plan-ui.orch.yaml +597 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +688 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-select-feature.orch.yaml +3 -3
- package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +23 -23
- package/assets/.appweaver/orchestrator/workflows/aw-spec-story-plan.orch.yaml +945 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-sync-stories.orch.yaml +1040 -0
- package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +2069 -28
- package/assets/.appweaver/scripts/bash/aw-xref.sh +2 -1
- package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +1973 -13
- package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +2 -1
- package/assets/.appweaver/scripts/python/aw-xref.py +246 -20
- package/assets/.appweaver/templates/design-template.md +88 -54
- package/assets/.appweaver/templates/spec-template.md +12 -10
- package/assets/.appweaver/templates/story-template.md +48 -8
- package/assets/.appweaver/templates/ui-design-template.md +71 -39
- package/assets/guardrails/projects/dotnet-default-project/domain-rule-bindings.md +19 -7
- package/assets/guardrails/projects/dotnet-default-project/tech-rule-bindings.md +35 -26
- package/package.json +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-design-ui.orch.yaml +0 -386
- package/assets/.appweaver/orchestrator/workflows/aw-spec-design.orch.yaml +0 -382
|
@@ -0,0 +1,945 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# aw-spec-story-plan (orchestrator) — the per-story execution design, BOTH LANES
|
|
3
|
+
#
|
|
4
|
+
# Authors ONE story's ephemeral design as ONE DOCUMENT PER LANE:
|
|
5
|
+
# stories/<ID>/story-backend-plan.md the service design → /appweaver implement
|
|
6
|
+
# stories/<ID>/story-frontend-plan.md the UI design → /appweaver implement-ui
|
|
7
|
+
# Neither is a maintained design: each is designed FROM the current truth every
|
|
8
|
+
# time, left behind (archived with the story) at close, and never merged anywhere.
|
|
9
|
+
# The durable knowledge routes to append-only logs instead:
|
|
10
|
+
# • adr.md — each pass PROPOSES ADRs; they are ratified at story close
|
|
11
|
+
# • interfaces.md — the SERVICE pass appends published-surface changes here
|
|
12
|
+
# (status: proposed; story close verifies them against the
|
|
13
|
+
# as-built and flips them to live). The UI pass CONSUMES that
|
|
14
|
+
# log — it never publishes into it.
|
|
15
|
+
#
|
|
16
|
+
# ONE COMMAND, TWO DOCUMENTS. One command because everything before the authoring
|
|
17
|
+
# is shared and must not be answered twice: which story, whether its spec is
|
|
18
|
+
# ledgered, whether it has gone stale, and what code already exists. Two documents
|
|
19
|
+
# because the two designs are authored under different rule sets, from different
|
|
20
|
+
# code, and are read by different implement commands — folded into one file, every
|
|
21
|
+
# one of those becomes a section boundary the next reader has to trust. There is no
|
|
22
|
+
# /appweaver story-plan-ui, and there will not be one.
|
|
23
|
+
#
|
|
24
|
+
# LANE SCOPE IS WHAT IS ACTUALLY THERE. A lane is designed when it is DECLARED in
|
|
25
|
+
# stacks.json and has a USABLE feature plan. A declared lane whose plan is missing,
|
|
26
|
+
# or which the spec has moved past, is dropped from THIS RUN and said out loud with
|
|
27
|
+
# its route — it is not a reason to refuse the lane that is ready. The run stops
|
|
28
|
+
# only when NO lane survives, and then the message carries both lanes' reasons.
|
|
29
|
+
#
|
|
30
|
+
# INPUT CHANNELS, each with one job and no overlap:
|
|
31
|
+
# spec.md what MUST be (the contract — the only WHAT authority)
|
|
32
|
+
# backend-plan.md the FEATURE ARCHITECTURE the service slice sits inside
|
|
33
|
+
# frontend-plan.md the FEATURE ARCHITECTURE the UI slice sits inside
|
|
34
|
+
# — both binding, and both read ONE WAY: no story pass ever
|
|
35
|
+
# writes back, so each feature plan keeps exactly one writer
|
|
36
|
+
# (/appweaver plan · /appweaver plan-ui)
|
|
37
|
+
# OKF code coverage what IS — the feature's existing code SPLIT BY LANE, its
|
|
38
|
+
# drift, its dependencies and its blast radius, plus per-id
|
|
39
|
+
# code anchors where a source map exists (concept-coverage)
|
|
40
|
+
# constitution + rules what must ALWAYS hold (standards, via tech-select)
|
|
41
|
+
# adr.md what was DECIDED and why (binding unless superseded)
|
|
42
|
+
# interfaces.md what is PUBLISHED (superseded only by new proposed entries)
|
|
43
|
+
# The feature blueprint is deliberately NOT an input — it is a read-only render for
|
|
44
|
+
# humans; coherence flows through the logs and the code, never through it.
|
|
45
|
+
#
|
|
46
|
+
# THE DESIGN IS YOURS TO EDIT BEFORE YOU BUILD. Each document is a plain markdown
|
|
47
|
+
# file, and a hand edit to its latest pass is what implement then builds. Re-running
|
|
48
|
+
# story-plan does not overwrite that edit: every run APPENDS a
|
|
49
|
+
# "## Design pass N — <date> (spec vX)" section under a SCRIPT-written header, so
|
|
50
|
+
# edit to adjust and re-run to redesign. THE LATEST PASS WINS; earlier passes are
|
|
51
|
+
# the iteration history. Pass numbers are per lane, so designing one lane leaves the
|
|
52
|
+
# other's history untouched. Writing into stories/<ID>/ is what flips the story
|
|
53
|
+
# IN-FLIGHT (sync-stories' isdir test) and drives follow-on minting — the documents
|
|
54
|
+
# must live nowhere else.
|
|
55
|
+
#
|
|
56
|
+
# RULE SETS ARE LOADED PER LANE, IN LANE ORDER — the service rules
|
|
57
|
+
# (## /aw-spec-plan) before the service pass, the UI rules (## /aw-spec-plan-ui)
|
|
58
|
+
# before the UI pass. The engine's standards set is sticky and cumulative, so the UI
|
|
59
|
+
# pass also carries the service rules where both lanes run. That direction is the
|
|
60
|
+
# deliberate one: a UI author reading service layering rules is noise, while a
|
|
61
|
+
# service author reading component and styling rules is misdirection.
|
|
62
|
+
#
|
|
63
|
+
# GATES (all loud, all before any write): at least one lane declared in stacks.json;
|
|
64
|
+
# active feature + spec.md present; the spec-check ledger tripwire (no design from
|
|
65
|
+
# an unledgered spec); per-lane plan freshness (fresh/behind/edited_behind/untracked
|
|
66
|
+
# drop that lane and name the fix; a hand edit still in step is announced in the
|
|
67
|
+
# pass, not refused); the story exists and is OPEN (closed/cancelled/draft each get
|
|
68
|
+
# their own message). A stale story (ledger rows above its stamp naming ids it
|
|
69
|
+
# carries — exercises AND its Constraints section) is announced in the pass, not
|
|
70
|
+
# refused: a declined sync-stories rewrite is a legitimate author decision.
|
|
71
|
+
#
|
|
72
|
+
# Launch: /appweaver story-plan [<story-id>] [guidance] (asks when ambiguous)
|
|
73
|
+
# Prereq: /appweaver plan and/or /appweaver plan-ui (the feature plans this slices)
|
|
74
|
+
# + a story (specify/sync-stories mint them) · Next: implement · implement-ui
|
|
75
|
+
# =============================================================================
|
|
76
|
+
|
|
77
|
+
name: aw-spec-story-plan
|
|
78
|
+
description: "Author or extend ONE story's ephemeral execution design, one document per lane — stories/<ID>/story-backend-plan.md and stories/<ID>/story-frontend-plan.md. Each run APPENDS a dated design pass per lane in scope (script-numbered; the latest pass wins, and a manual edit to the latest pass is what implement builds) designed from the current spec, that lane's binding feature plan (read one way — a story pass never writes back), the feature's lane-split OKF code coverage, the technical standards, and the feature's append-only ADR + interface logs. A lane is designed when it is declared AND has a usable plan; a lane whose plan is missing or behind the spec is dropped from the run and said out loud, and the run stops only when no lane survives. The service pass appends published-surface changes to interfaces.md as proposed entries; the UI pass consumes that log. Refuses non-open stories; announces (never hides) a spec that moved above the story's stamp."
|
|
79
|
+
version: 2.0.0
|
|
80
|
+
agent_agnostic: true
|
|
81
|
+
dialect: aw-spec.orchestrator/v1
|
|
82
|
+
|
|
83
|
+
inputs:
|
|
84
|
+
user_input:
|
|
85
|
+
description: Optional story id and/or design guidance (e.g. "Payment-003 keep the dedupe table in-process").
|
|
86
|
+
required: false
|
|
87
|
+
bind: USER_INPUT
|
|
88
|
+
|
|
89
|
+
scripts:
|
|
90
|
+
actions:
|
|
91
|
+
sh: .appweaver/scripts/bash/aw-spec-actions.sh
|
|
92
|
+
ps1: .appweaver/scripts/powershell/aw-spec-actions.ps1
|
|
93
|
+
xref:
|
|
94
|
+
sh: .appweaver/scripts/bash/aw-xref.sh
|
|
95
|
+
ps1: .appweaver/scripts/powershell/aw-xref.ps1
|
|
96
|
+
|
|
97
|
+
# Sticky standards seed = the universal always-on rule only. The TECHNICAL rule
|
|
98
|
+
# files are CONFIGURATION, loaded per lane from tech-rule-bindings.md — the service
|
|
99
|
+
# pass under "## /aw-spec-plan" and the UI pass under "## /aw-spec-plan-ui". Each
|
|
100
|
+
# story design applies the SAME HOW rules as the feature plan it slices, so both
|
|
101
|
+
# reuse their lane's existing slot rather than minting a new one.
|
|
102
|
+
standards:
|
|
103
|
+
- { path: .appweaver/ai-rules.md }
|
|
104
|
+
|
|
105
|
+
vars:
|
|
106
|
+
# lane declaration + lane scope (declared AND usably planned)
|
|
107
|
+
lanes_declared: "false"
|
|
108
|
+
be_declared: "false"
|
|
109
|
+
fe_declared: "false"
|
|
110
|
+
be_in_scope: "false"
|
|
111
|
+
fe_in_scope: "false"
|
|
112
|
+
have_lane: "false"
|
|
113
|
+
have_design_lane: "false"
|
|
114
|
+
lane_gate_msg: "No lane is declared in .appweaver/stacks.json, so there is nothing to design this story against. Run /appweaver select-stack and declare the stack(s) plus their source paths, then re-run story-plan."
|
|
115
|
+
# feature + spec
|
|
116
|
+
feature_dir: ""
|
|
117
|
+
spec_file: ""
|
|
118
|
+
spec_exists: "false"
|
|
119
|
+
be_plan: ""
|
|
120
|
+
fe_plan: ""
|
|
121
|
+
spec_ok: "false"
|
|
122
|
+
spec_state: ""
|
|
123
|
+
# per-lane feature-plan freshness (the one-way coupling: a story pass READS a plan)
|
|
124
|
+
be_plan_state: ""
|
|
125
|
+
be_plan_derived: ""
|
|
126
|
+
be_plan_changed: ""
|
|
127
|
+
be_plan_note: ""
|
|
128
|
+
be_drop_msg: ""
|
|
129
|
+
be_dropped: "false"
|
|
130
|
+
drops_note: "none — every declared lane was designed this run"
|
|
131
|
+
fe_plan_state: ""
|
|
132
|
+
fe_plan_derived: ""
|
|
133
|
+
fe_plan_changed: ""
|
|
134
|
+
fe_plan_note: ""
|
|
135
|
+
fe_drop_msg: ""
|
|
136
|
+
fe_dropped: "false"
|
|
137
|
+
# story-plan-check bindings
|
|
138
|
+
any_open: "false"
|
|
139
|
+
open_ids: "[]"
|
|
140
|
+
open_ids_csv: ""
|
|
141
|
+
digest: ""
|
|
142
|
+
resolved: "false"
|
|
143
|
+
needs_pick: "false"
|
|
144
|
+
pick_reason: ""
|
|
145
|
+
story: ""
|
|
146
|
+
story_path: ""
|
|
147
|
+
story_open: "false"
|
|
148
|
+
status_msg: ""
|
|
149
|
+
story_title: ""
|
|
150
|
+
exercises_csv: ""
|
|
151
|
+
constraints_csv: ""
|
|
152
|
+
story_spec_version: ""
|
|
153
|
+
current_version: ""
|
|
154
|
+
stale_csv: ""
|
|
155
|
+
stale_note: ""
|
|
156
|
+
story_dir: ""
|
|
157
|
+
legacy_doc: ""
|
|
158
|
+
legacy_doc_exists: "false"
|
|
159
|
+
be_doc_path: ""
|
|
160
|
+
be_doc_exists: "false"
|
|
161
|
+
be_next_pass: "1"
|
|
162
|
+
be_pass_tmp: ""
|
|
163
|
+
fe_doc_path: ""
|
|
164
|
+
fe_doc_exists: "false"
|
|
165
|
+
fe_next_pass: "1"
|
|
166
|
+
fe_pass_tmp: ""
|
|
167
|
+
if_tmp: ""
|
|
168
|
+
adr_path: ""
|
|
169
|
+
if_path: ""
|
|
170
|
+
picked_story: ""
|
|
171
|
+
# OKF code coverage (the feature's current code, per lane)
|
|
172
|
+
okf_concept: ""
|
|
173
|
+
coverage_digest: ""
|
|
174
|
+
be_source_csv: ""
|
|
175
|
+
be_source_count: "0"
|
|
176
|
+
fe_source_csv: ""
|
|
177
|
+
fe_source_count: "0"
|
|
178
|
+
coverage_drift: "0"
|
|
179
|
+
used_by_csv: ""
|
|
180
|
+
unanchored_csv: ""
|
|
181
|
+
source_map: ""
|
|
182
|
+
# write results
|
|
183
|
+
be_pass_n: "0"
|
|
184
|
+
be_created: "false"
|
|
185
|
+
fe_pass_n: "0"
|
|
186
|
+
fe_created: "false"
|
|
187
|
+
if_appended: "0"
|
|
188
|
+
if_ids_csv: ""
|
|
189
|
+
if_unknown_csv: ""
|
|
190
|
+
if_badsup_csv: ""
|
|
191
|
+
if_ok: "true"
|
|
192
|
+
if_error: ""
|
|
193
|
+
|
|
194
|
+
workflow:
|
|
195
|
+
|
|
196
|
+
# 1. WHICH LANES EXIST — the DECLARATION in stacks.json, not a directory on disk
|
|
197
|
+
# (greenfield designs before the code exists). At least one is required; which
|
|
198
|
+
# ones are actually designed is settled by the plan gates in step 4.
|
|
199
|
+
- id: lane_resolve
|
|
200
|
+
action: execute_script
|
|
201
|
+
on_error: continue # never crash raw on an undeclared lane — the gate below explains it
|
|
202
|
+
script: { ref: actions }
|
|
203
|
+
subcommand: lane-resolve
|
|
204
|
+
outputs:
|
|
205
|
+
- { name: lanes_declared, from: "$.declared" }
|
|
206
|
+
- { name: be_declared, from: "$.backend_declared" }
|
|
207
|
+
- { name: fe_declared, from: "$.frontend_declared" }
|
|
208
|
+
- id: be_scope
|
|
209
|
+
condition: "{{be_declared}} == true"
|
|
210
|
+
action: set
|
|
211
|
+
vars: { be_in_scope: "true", have_lane: "true" }
|
|
212
|
+
- id: fe_scope
|
|
213
|
+
condition: "{{fe_declared}} == true"
|
|
214
|
+
action: set
|
|
215
|
+
vars: { fe_in_scope: "true", have_lane: "true" }
|
|
216
|
+
- id: require_lane
|
|
217
|
+
action: verify_context
|
|
218
|
+
require_true: have_lane
|
|
219
|
+
on_false: stop
|
|
220
|
+
on_false_message: "{{lane_gate_msg}}"
|
|
221
|
+
|
|
222
|
+
# 2. Active feature + its spec. feature-paths reports both plan paths whether or
|
|
223
|
+
# not they exist; step 4 decides what an absence means for that lane.
|
|
224
|
+
- id: feature_paths
|
|
225
|
+
action: execute_script
|
|
226
|
+
on_error: continue # never crash raw on 'no feature set' — the gate below explains it
|
|
227
|
+
script: { ref: actions }
|
|
228
|
+
subcommand: feature-paths
|
|
229
|
+
outputs:
|
|
230
|
+
- { name: feature_dir, from: "$.feature_dir" }
|
|
231
|
+
- { name: spec_file, from: "$.spec_file" }
|
|
232
|
+
- { name: spec_exists, from: "$.spec_exists" }
|
|
233
|
+
- { name: be_plan, from: "$.backend_plan_file" }
|
|
234
|
+
- { name: fe_plan, from: "$.frontend_plan_file" }
|
|
235
|
+
- id: require_feature
|
|
236
|
+
action: verify_context
|
|
237
|
+
require_non_empty: feature_dir
|
|
238
|
+
on_empty: stop
|
|
239
|
+
on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the aggregate, e.g. Order), then re-run story-plan."
|
|
240
|
+
- id: verify_spec
|
|
241
|
+
action: verify_context
|
|
242
|
+
require_true: spec_exists
|
|
243
|
+
on_false: stop
|
|
244
|
+
on_false_message: "No spec.md for the current feature. Run /appweaver specify first — a story design designs against the contract, and there is none."
|
|
245
|
+
|
|
246
|
+
# 3. The spec's ledger tripwire: no design pass from an unledgered spec — every
|
|
247
|
+
# pass records the spec version it designed against, and an unledgered edit has
|
|
248
|
+
# no version to record.
|
|
249
|
+
- id: spec_guard_read
|
|
250
|
+
action: execute_script
|
|
251
|
+
script: { ref: actions }
|
|
252
|
+
subcommand: spec-check
|
|
253
|
+
args: ["--feature-dir", "{{feature_dir}}"]
|
|
254
|
+
outputs:
|
|
255
|
+
- { name: spec_ok, from: "$.ok" }
|
|
256
|
+
- { name: spec_state, from: "$.state" }
|
|
257
|
+
- id: spec_guard
|
|
258
|
+
action: verify_context
|
|
259
|
+
require_true: spec_ok
|
|
260
|
+
on_false: stop
|
|
261
|
+
on_false_message: "spec.md has moved outside the ledger (state: {{spec_state}}). Record what changed in a Change History row (fold it back through the spec workflow), re-run spec-stamp to re-baseline, then re-run story-plan — a design pass derived from an unledgered spec records a baseline the ledger cannot name."
|
|
262
|
+
|
|
263
|
+
# 4. THE FEATURE-PLAN GATES, one per declared lane. A story design is a SLICE of
|
|
264
|
+
# the feature's architecture, so it READS that lane's plan and NEVER writes
|
|
265
|
+
# back. The freshness check stops a slice inheriting an architecture the spec
|
|
266
|
+
# has already moved past while the script-written pass header stamps the CURRENT
|
|
267
|
+
# spec version over it — the pass would read as designed against a spec the plan
|
|
268
|
+
# never saw.
|
|
269
|
+
#
|
|
270
|
+
# A FAILING GATE DROPS ITS LANE, IT DOES NOT STOP THE RUN. Missing, behind,
|
|
271
|
+
# edited_behind and untracked all mean the same thing — this lane has no usable
|
|
272
|
+
# architecture right now — and none of them make the OTHER lane's design wrong.
|
|
273
|
+
# Each drop is announced with its own route, and the run stops only if both
|
|
274
|
+
# lanes are gone. `untracked` drops for the reason it always did: until one
|
|
275
|
+
# /appweaver plan run baselines the document this gate cannot tell clean from
|
|
276
|
+
# behind, and a gate that cannot tell is worse than one that asks. `edited` is
|
|
277
|
+
# admitted and announced INTO the pass — the plan is the architect's document.
|
|
278
|
+
- id: be_plan_read
|
|
279
|
+
condition: "{{be_in_scope}} == true"
|
|
280
|
+
action: execute_script
|
|
281
|
+
on_error: continue # a probe that fails binds nothing; the lane stays out of scope ⇒ fails CLOSED
|
|
282
|
+
script: { ref: actions }
|
|
283
|
+
subcommand: plan-check
|
|
284
|
+
args: ["--doc", "{{be_plan}}", "--feature-dir", "{{feature_dir}}"]
|
|
285
|
+
outputs:
|
|
286
|
+
- { name: be_plan_state, from: "$.state" }
|
|
287
|
+
- { name: be_plan_derived, from: "$.derived_version" }
|
|
288
|
+
- { name: be_plan_changed, from: "$.changed_csv" }
|
|
289
|
+
- id: fe_plan_read
|
|
290
|
+
condition: "{{fe_in_scope}} == true"
|
|
291
|
+
action: execute_script
|
|
292
|
+
on_error: continue # same fail-closed contract as the service probe
|
|
293
|
+
script: { ref: actions }
|
|
294
|
+
subcommand: plan-check
|
|
295
|
+
args: ["--doc", "{{fe_plan}}", "--feature-dir", "{{feature_dir}}"]
|
|
296
|
+
outputs:
|
|
297
|
+
- { name: fe_plan_state, from: "$.state" }
|
|
298
|
+
- { name: fe_plan_derived, from: "$.derived_version" }
|
|
299
|
+
- { name: fe_plan_changed, from: "$.changed_csv" }
|
|
300
|
+
|
|
301
|
+
# 4a. SERVICE lane verdict. Only `clean` and `edited` keep the lane; every other
|
|
302
|
+
# state (including the empty string a crashed probe leaves) drops it.
|
|
303
|
+
- id: be_drop_default
|
|
304
|
+
condition: "{{be_in_scope}} == true"
|
|
305
|
+
action: set
|
|
306
|
+
vars:
|
|
307
|
+
be_in_scope: "false"
|
|
308
|
+
be_dropped: "true"
|
|
309
|
+
be_drop_msg: "SERVICE lane not designed: backend-plan.md could not be checked against the spec (state: '{{be_plan_state}}'). Run /appweaver plan for this feature, then re-run story-plan."
|
|
310
|
+
- id: be_keep_clean
|
|
311
|
+
condition: "{{be_plan_state}} == clean"
|
|
312
|
+
action: set
|
|
313
|
+
vars: { be_in_scope: "true", be_dropped: "false", be_drop_msg: "" }
|
|
314
|
+
- id: be_keep_edited
|
|
315
|
+
condition: "{{be_plan_state}} == edited"
|
|
316
|
+
action: set
|
|
317
|
+
vars:
|
|
318
|
+
be_in_scope: "true"
|
|
319
|
+
be_dropped: "false"
|
|
320
|
+
be_drop_msg: ""
|
|
321
|
+
be_plan_note: "NOTE — backend-plan.md carries hand edits that /appweaver plan has not baselined. It is still in step with the spec, so it is authoritative here; design to it as it stands."
|
|
322
|
+
- id: be_msg_fresh
|
|
323
|
+
condition: "{{be_plan_state}} == fresh"
|
|
324
|
+
action: set
|
|
325
|
+
vars: { be_drop_msg: "SERVICE lane not designed: a backend lane is declared but this feature has no backend-plan.md, so there is no feature architecture to slice. Run /appweaver plan — it authors the plan from spec.md and publishes this feature's interfaces, both of which the service design pass reads — then re-run story-plan." }
|
|
326
|
+
- id: be_msg_behind
|
|
327
|
+
condition: "{{be_plan_state}} == behind"
|
|
328
|
+
action: set
|
|
329
|
+
vars: { be_drop_msg: "SERVICE lane not designed: backend-plan.md derives from spec {{be_plan_derived}} and the spec has moved past it (ids since: {{be_plan_changed}}). Designing a slice against it would inherit an architecture the spec has outrun. Run /appweaver plan to conform it, then re-run story-plan." }
|
|
330
|
+
- id: be_msg_edited_behind
|
|
331
|
+
condition: "{{be_plan_state}} == edited_behind"
|
|
332
|
+
action: set
|
|
333
|
+
vars: { be_drop_msg: "SERVICE lane not designed: backend-plan.md has been hand-edited AND the spec has moved past it (derived from {{be_plan_derived}}; ids since: {{be_plan_changed}}). Run /appweaver plan with accept_manual: true — it reconciles the edit and the spec change together — then re-run story-plan." }
|
|
334
|
+
- id: be_msg_untracked
|
|
335
|
+
condition: "{{be_plan_state}} == untracked"
|
|
336
|
+
action: set
|
|
337
|
+
vars: { be_drop_msg: "SERVICE lane not designed: backend-plan.md has no derivation record — a reversed or hand-placed plan that no baseline names, so nothing can say which spec version it matches and this gate cannot tell fresh from stale. Run /appweaver plan once: it reconciles the WHOLE plan to the current spec and baselines it. Then re-run story-plan." }
|
|
338
|
+
|
|
339
|
+
# 4b. UI lane verdict — the same contract, routed to the frontend lane's author.
|
|
340
|
+
- id: fe_drop_default
|
|
341
|
+
condition: "{{fe_in_scope}} == true"
|
|
342
|
+
action: set
|
|
343
|
+
vars:
|
|
344
|
+
fe_in_scope: "false"
|
|
345
|
+
fe_dropped: "true"
|
|
346
|
+
fe_drop_msg: "UI lane not designed: frontend-plan.md could not be checked against the spec (state: '{{fe_plan_state}}'). Run /appweaver plan-ui for this feature, then re-run story-plan."
|
|
347
|
+
- id: fe_keep_clean
|
|
348
|
+
condition: "{{fe_plan_state}} == clean"
|
|
349
|
+
action: set
|
|
350
|
+
vars: { fe_in_scope: "true", fe_dropped: "false", fe_drop_msg: "" }
|
|
351
|
+
- id: fe_keep_edited
|
|
352
|
+
condition: "{{fe_plan_state}} == edited"
|
|
353
|
+
action: set
|
|
354
|
+
vars:
|
|
355
|
+
fe_in_scope: "true"
|
|
356
|
+
fe_dropped: "false"
|
|
357
|
+
fe_drop_msg: ""
|
|
358
|
+
fe_plan_note: "NOTE — frontend-plan.md carries hand edits that /appweaver plan-ui has not baselined. It is still in step with the spec, so it is authoritative here; design to it as it stands."
|
|
359
|
+
- id: fe_msg_fresh
|
|
360
|
+
condition: "{{fe_plan_state}} == fresh"
|
|
361
|
+
action: set
|
|
362
|
+
vars: { fe_drop_msg: "UI lane not designed: a frontend lane is declared but this feature has no frontend-plan.md, so there is no UI architecture to slice. Run /appweaver plan-ui — it authors the plan from spec.md and the published interfaces, which the UI design pass reads — then re-run story-plan." }
|
|
363
|
+
- id: fe_msg_behind
|
|
364
|
+
condition: "{{fe_plan_state}} == behind"
|
|
365
|
+
action: set
|
|
366
|
+
vars: { fe_drop_msg: "UI lane not designed: frontend-plan.md derives from spec {{fe_plan_derived}} and the spec has moved past it (ids since: {{fe_plan_changed}}). Run /appweaver plan-ui to conform it, then re-run story-plan." }
|
|
367
|
+
- id: fe_msg_edited_behind
|
|
368
|
+
condition: "{{fe_plan_state}} == edited_behind"
|
|
369
|
+
action: set
|
|
370
|
+
vars: { fe_drop_msg: "UI lane not designed: frontend-plan.md has been hand-edited AND the spec has moved past it (derived from {{fe_plan_derived}}; ids since: {{fe_plan_changed}}). Run /appweaver plan-ui with accept_manual: true, then re-run story-plan." }
|
|
371
|
+
- id: fe_msg_untracked
|
|
372
|
+
condition: "{{fe_plan_state}} == untracked"
|
|
373
|
+
action: set
|
|
374
|
+
vars: { fe_drop_msg: "UI lane not designed: frontend-plan.md has no derivation record — a reversed or hand-placed plan that no baseline names, so this gate cannot tell fresh from stale. Run /appweaver plan-ui once to reconcile and baseline it, then re-run story-plan." }
|
|
375
|
+
|
|
376
|
+
# 4c. At least one lane must survive. When none does, BOTH reasons travel in the
|
|
377
|
+
# stop message — a run that reported only the first would send the author back
|
|
378
|
+
# for a second refusal.
|
|
379
|
+
- id: have_be
|
|
380
|
+
condition: "{{be_in_scope}} == true"
|
|
381
|
+
action: set
|
|
382
|
+
vars: { have_design_lane: "true" }
|
|
383
|
+
- id: have_fe
|
|
384
|
+
condition: "{{fe_in_scope}} == true"
|
|
385
|
+
action: set
|
|
386
|
+
vars: { have_design_lane: "true" }
|
|
387
|
+
- id: require_design_lane
|
|
388
|
+
action: verify_context
|
|
389
|
+
require_true: have_design_lane
|
|
390
|
+
on_false: stop
|
|
391
|
+
on_false_message: |
|
|
392
|
+
No lane has a usable feature plan, so there is nothing to design this story against.
|
|
393
|
+
|
|
394
|
+
{{be_drop_msg}}
|
|
395
|
+
|
|
396
|
+
{{fe_drop_msg}}
|
|
397
|
+
|
|
398
|
+
# 4d. What the run did NOT design, in one line for the outputs. Both branches
|
|
399
|
+
# compose the same expression: either message is empty when its lane is fine,
|
|
400
|
+
# so one set covers a drop on either lane or on both.
|
|
401
|
+
- id: drops_note_be
|
|
402
|
+
condition: "{{be_dropped}} == true"
|
|
403
|
+
action: set
|
|
404
|
+
vars: { drops_note: "{{be_drop_msg}} {{fe_drop_msg}}" }
|
|
405
|
+
- id: drops_note_fe
|
|
406
|
+
condition: "{{fe_dropped}} == true"
|
|
407
|
+
action: set
|
|
408
|
+
vars: { drops_note: "{{be_drop_msg}} {{fe_drop_msg}}" }
|
|
409
|
+
|
|
410
|
+
# 5. WHICH STORY. The check resolves an explicit id in the input; anything else
|
|
411
|
+
# (nothing named, an unknown id, several ids) becomes a pick from the open
|
|
412
|
+
# backlog. All state the gates below read comes back in one scan, including
|
|
413
|
+
# both lanes' document and pass state.
|
|
414
|
+
- id: story_scan
|
|
415
|
+
action: execute_script
|
|
416
|
+
script: { ref: actions }
|
|
417
|
+
subcommand: story-plan-check
|
|
418
|
+
args: ["--feature-dir", "{{feature_dir}}", "--input", "{{USER_INPUT}}"]
|
|
419
|
+
outputs:
|
|
420
|
+
- { name: any_open, from: "$.any_open" }
|
|
421
|
+
- { name: open_ids, from: "$.open_ids" }
|
|
422
|
+
- { name: open_ids_csv, from: "$.open_ids_csv" }
|
|
423
|
+
- { name: digest, from: "$.digest" }
|
|
424
|
+
- { name: resolved, from: "$.resolved" }
|
|
425
|
+
- { name: needs_pick, from: "$.needs_pick" }
|
|
426
|
+
- { name: pick_reason, from: "$.pick_reason" }
|
|
427
|
+
- { name: story, from: "$.story" }
|
|
428
|
+
- { name: story_path, from: "$.story_path" }
|
|
429
|
+
- { name: story_open, from: "$.story_open" }
|
|
430
|
+
- { name: status_msg, from: "$.status_msg" }
|
|
431
|
+
- { name: story_title, from: "$.story_title" }
|
|
432
|
+
- { name: exercises_csv, from: "$.exercises_csv" }
|
|
433
|
+
- { name: constraints_csv, from: "$.constraints_csv" }
|
|
434
|
+
- { name: story_spec_version, from: "$.story_spec_version" }
|
|
435
|
+
- { name: current_version, from: "$.current_version" }
|
|
436
|
+
- { name: stale_csv, from: "$.stale_csv" }
|
|
437
|
+
- { name: stale_note, from: "$.stale_note" }
|
|
438
|
+
- { name: story_dir, from: "$.story_dir" }
|
|
439
|
+
- { name: legacy_doc, from: "$.legacy_doc" }
|
|
440
|
+
- { name: legacy_doc_exists, from: "$.legacy_doc_exists" }
|
|
441
|
+
- { name: be_doc_path, from: "$.be_doc_path" }
|
|
442
|
+
- { name: be_doc_exists, from: "$.be_doc_exists" }
|
|
443
|
+
- { name: be_next_pass, from: "$.be_next_pass" }
|
|
444
|
+
- { name: be_pass_tmp, from: "$.be_pass_tmp" }
|
|
445
|
+
- { name: fe_doc_path, from: "$.fe_doc_path" }
|
|
446
|
+
- { name: fe_doc_exists, from: "$.fe_doc_exists" }
|
|
447
|
+
- { name: fe_next_pass, from: "$.fe_next_pass" }
|
|
448
|
+
- { name: fe_pass_tmp, from: "$.fe_pass_tmp" }
|
|
449
|
+
- { name: if_tmp, from: "$.if_tmp" }
|
|
450
|
+
- { name: adr_path, from: "$.adr_path" }
|
|
451
|
+
- { name: if_path, from: "$.if_path" }
|
|
452
|
+
- id: require_open_backlog
|
|
453
|
+
action: verify_context
|
|
454
|
+
require_true: any_open
|
|
455
|
+
on_false: stop
|
|
456
|
+
on_false_message: "No open stories for this feature — there is nothing to design. Stories are minted by /appweaver specify (bootstrap) and /appweaver sync-stories (when the spec moves)."
|
|
457
|
+
- id: ask_story
|
|
458
|
+
condition: "{{needs_pick}} == true"
|
|
459
|
+
action: ask
|
|
460
|
+
prompt: |
|
|
461
|
+
Which story is this design pass for? ({{pick_reason}})
|
|
462
|
+
|
|
463
|
+
{{digest}}
|
|
464
|
+
options_from: "{{open_ids}}"
|
|
465
|
+
answer_key: picked_story
|
|
466
|
+
answer_var: picked_story
|
|
467
|
+
- id: story_pick_check
|
|
468
|
+
condition: "{{needs_pick}} == true"
|
|
469
|
+
action: execute_script
|
|
470
|
+
script: { ref: actions }
|
|
471
|
+
subcommand: story-plan-check
|
|
472
|
+
args: ["--feature-dir", "{{feature_dir}}", "--story", "{{picked_story}}"]
|
|
473
|
+
outputs:
|
|
474
|
+
- { name: resolved, from: "$.resolved" }
|
|
475
|
+
- { name: story, from: "$.story" }
|
|
476
|
+
- { name: story_path, from: "$.story_path" }
|
|
477
|
+
- { name: story_open, from: "$.story_open" }
|
|
478
|
+
- { name: status_msg, from: "$.status_msg" }
|
|
479
|
+
- { name: story_title, from: "$.story_title" }
|
|
480
|
+
- { name: exercises_csv, from: "$.exercises_csv" }
|
|
481
|
+
- { name: constraints_csv, from: "$.constraints_csv" }
|
|
482
|
+
- { name: story_spec_version, from: "$.story_spec_version" }
|
|
483
|
+
- { name: stale_csv, from: "$.stale_csv" }
|
|
484
|
+
- { name: stale_note, from: "$.stale_note" }
|
|
485
|
+
- { name: story_dir, from: "$.story_dir" }
|
|
486
|
+
- { name: legacy_doc, from: "$.legacy_doc" }
|
|
487
|
+
- { name: legacy_doc_exists, from: "$.legacy_doc_exists" }
|
|
488
|
+
- { name: be_doc_path, from: "$.be_doc_path" }
|
|
489
|
+
- { name: be_doc_exists, from: "$.be_doc_exists" }
|
|
490
|
+
- { name: be_next_pass, from: "$.be_next_pass" }
|
|
491
|
+
- { name: be_pass_tmp, from: "$.be_pass_tmp" }
|
|
492
|
+
- { name: fe_doc_path, from: "$.fe_doc_path" }
|
|
493
|
+
- { name: fe_doc_exists, from: "$.fe_doc_exists" }
|
|
494
|
+
- { name: fe_next_pass, from: "$.fe_next_pass" }
|
|
495
|
+
- { name: fe_pass_tmp, from: "$.fe_pass_tmp" }
|
|
496
|
+
- { name: if_tmp, from: "$.if_tmp" }
|
|
497
|
+
- id: require_story
|
|
498
|
+
action: verify_context
|
|
499
|
+
require_true: resolved
|
|
500
|
+
on_false: stop
|
|
501
|
+
on_false_message: |
|
|
502
|
+
Could not resolve a story from '{{picked_story}}'. Open stories:
|
|
503
|
+
{{digest}}
|
|
504
|
+
Re-run story-plan naming one of them.
|
|
505
|
+
- id: require_open_story
|
|
506
|
+
action: verify_context
|
|
507
|
+
require_true: story_open
|
|
508
|
+
on_false: stop
|
|
509
|
+
on_false_message: "{{status_msg}}"
|
|
510
|
+
|
|
511
|
+
# 5a. A dropped lane is announced HERE — after the story is known, so the notice
|
|
512
|
+
# names the story it applies to, and before any write, so the author can stop
|
|
513
|
+
# and fix the plan rather than discovering the gap after a pass has landed.
|
|
514
|
+
- id: be_drop_notice
|
|
515
|
+
condition: "{{be_dropped}} == true"
|
|
516
|
+
action: verify_context
|
|
517
|
+
require_value: be_dropped
|
|
518
|
+
equals: "false"
|
|
519
|
+
on_mismatch: continue
|
|
520
|
+
on_mismatch_message: "{{be_drop_msg}}"
|
|
521
|
+
- id: fe_drop_notice
|
|
522
|
+
condition: "{{fe_dropped}} == true"
|
|
523
|
+
action: verify_context
|
|
524
|
+
require_value: fe_dropped
|
|
525
|
+
equals: "false"
|
|
526
|
+
on_mismatch: continue
|
|
527
|
+
on_mismatch_message: "{{fe_drop_msg}}"
|
|
528
|
+
|
|
529
|
+
# 5b. A pre-split story-plan.md beside the story is REPORTED, never read and never
|
|
530
|
+
# written. Silently leaving it there would leave a design document nobody
|
|
531
|
+
# consumes sitting beside two that are read — and the next person to open the
|
|
532
|
+
# story directory has no way to tell which of the three implement builds from.
|
|
533
|
+
- id: legacy_doc_notice
|
|
534
|
+
condition: "{{legacy_doc_exists}} == true"
|
|
535
|
+
action: verify_context
|
|
536
|
+
require_value: legacy_doc_exists
|
|
537
|
+
equals: "false"
|
|
538
|
+
on_mismatch: continue
|
|
539
|
+
on_mismatch_message: "{{legacy_doc}} is a story design from before the lane split. Nothing reads it any more — the service design now lives in story-backend-plan.md and the UI design in story-frontend-plan.md. Fold anything still worth keeping into this run's pass and delete it, or leave it as history; it will not reach implement either way."
|
|
540
|
+
|
|
541
|
+
# 6. THE FEATURE'S CODE, from its OKF concept — what IS, split by lane, with the
|
|
542
|
+
# drift, the dependencies and the blast radius a bare path list drops. Advisory:
|
|
543
|
+
# a greenfield feature has no concept yet and designs from an empty current
|
|
544
|
+
# state, so a missing bundle must never block the design. --ids reports which of
|
|
545
|
+
# the story's requirements already have a code anchor where the feature carries
|
|
546
|
+
# a source map; where it does not, every id comes back unanchored and the digest
|
|
547
|
+
# says so rather than implying the code is absent.
|
|
548
|
+
- id: coverage
|
|
549
|
+
action: execute_script
|
|
550
|
+
on_error: continue
|
|
551
|
+
script: { ref: xref }
|
|
552
|
+
subcommand: concept-coverage
|
|
553
|
+
args: ["--feature-dir", "{{feature_dir}}", "--ids", "{{exercises_csv}}"]
|
|
554
|
+
outputs:
|
|
555
|
+
- { name: okf_concept, from: "$.concept" }
|
|
556
|
+
- { name: coverage_digest, from: "$.digest" }
|
|
557
|
+
- { name: be_source_csv, from: "$.backend_csv" }
|
|
558
|
+
- { name: be_source_count, from: "$.backend_count" }
|
|
559
|
+
- { name: fe_source_csv, from: "$.frontend_csv" }
|
|
560
|
+
- { name: fe_source_count, from: "$.frontend_count" }
|
|
561
|
+
- { name: coverage_drift, from: "$.missing_count" }
|
|
562
|
+
- { name: used_by_csv, from: "$.used_by_csv" }
|
|
563
|
+
- { name: unanchored_csv, from: "$.unanchored_csv" }
|
|
564
|
+
- { name: source_map, from: "$.source_map" }
|
|
565
|
+
- id: drift_notice
|
|
566
|
+
condition: "{{coverage_drift}} != 0"
|
|
567
|
+
action: verify_context
|
|
568
|
+
require_value: coverage_drift
|
|
569
|
+
equals: "0"
|
|
570
|
+
on_mismatch: continue
|
|
571
|
+
on_mismatch_message: "The OKF concept for this feature declares {{coverage_drift}} source file(s) that are not on disk — a file moved or deleted without the owning slice being re-run. The design pass reads only what exists, so the index, not the design, is what is wrong. The drifted paths are named in the coverage block; /appweaver analyze-slice or the next implement on that slice re-writes the list."
|
|
572
|
+
|
|
573
|
+
# 7. Pre-execution hooks.
|
|
574
|
+
- id: resolve_pre_hooks
|
|
575
|
+
action: execute_script
|
|
576
|
+
script: { ref: actions }
|
|
577
|
+
subcommand: resolve-hooks
|
|
578
|
+
args: ["--phase", "before_story_plan"]
|
|
579
|
+
outputs:
|
|
580
|
+
- { name: before_hooks, from: "$.hooks" }
|
|
581
|
+
- id: dispatch_pre_hooks
|
|
582
|
+
action: dispatch_hooks
|
|
583
|
+
hooks_from_variable: before_hooks
|
|
584
|
+
|
|
585
|
+
# ════════════════════ THE SERVICE DESIGN (payload-only + Read) ═══════════════
|
|
586
|
+
# Lane rules first, then the pass. The doc itself is in context on a re-run
|
|
587
|
+
# (optional — absent before pass 1), so a later pass SEES earlier passes and any
|
|
588
|
+
# manual edits instead of contradicting them blind. Code arrives as a PATH LIST;
|
|
589
|
+
# the step declares Read to open it.
|
|
590
|
+
- id: tech_select_be
|
|
591
|
+
condition: "{{be_in_scope}} == true"
|
|
592
|
+
action: include
|
|
593
|
+
module: .appweaver/orchestrator/modules/tech-select.orch.yaml
|
|
594
|
+
with: { command: aw-spec-plan }
|
|
595
|
+
|
|
596
|
+
- id: ctx_be
|
|
597
|
+
condition: "{{be_in_scope}} == true"
|
|
598
|
+
action: build_context
|
|
599
|
+
vars:
|
|
600
|
+
story_id: "{{story}}"
|
|
601
|
+
story_title: "{{story_title}}"
|
|
602
|
+
exercises_csv: "{{exercises_csv}}"
|
|
603
|
+
constraints_csv: "{{constraints_csv}}"
|
|
604
|
+
spec_current_version: "{{current_version}}"
|
|
605
|
+
stale_note: "{{stale_note}}"
|
|
606
|
+
pass_number: "{{be_next_pass}}"
|
|
607
|
+
coverage_digest: "{{coverage_digest}}"
|
|
608
|
+
source_files_csv: "{{be_source_csv}}"
|
|
609
|
+
source_file_count: "{{be_source_count}}"
|
|
610
|
+
feature_plan_note: "{{be_plan_note}}"
|
|
611
|
+
ui_lane_note: "{{fe_drop_msg}}"
|
|
612
|
+
guidance: "{{USER_INPUT}}"
|
|
613
|
+
context:
|
|
614
|
+
- { path: "{{story_path}}" } # the slice: exercises, Constraints, Acceptance
|
|
615
|
+
- { path: "{{spec_file}}" } # the contract — the only WHAT authority
|
|
616
|
+
- { path: "{{feature_dir}}/backend-plan.md" } # the feature architecture this slice sits inside (READ-ONLY)
|
|
617
|
+
- { path: "{{be_doc_path}}", optional: true } # earlier service passes + manual edits (pass 2+)
|
|
618
|
+
- { path: "{{adr_path}}", optional: true } # ratified decisions — binding unless superseded
|
|
619
|
+
- { path: "{{if_path}}", optional: true } # the published surface (latest entry per id wins)
|
|
620
|
+
- { path: "{{okf_concept}}", optional: true } # the feature's OKF concept (use-case log, source map)
|
|
621
|
+
- { path: .appweaver/memory/constitution.technical.md, optional: true }
|
|
622
|
+
- id: gen_be
|
|
623
|
+
condition: "{{be_in_scope}} == true"
|
|
624
|
+
action: agent_author
|
|
625
|
+
tools: Read # the current code arrives as {{source_files_csv}} paths, not inlined
|
|
626
|
+
output_target: { type: file, path: "{{be_pass_tmp}}" }
|
|
627
|
+
task: |
|
|
628
|
+
Write SERVICE design pass {{pass_number}} for story {{story_id}} — {{story_title}}.
|
|
629
|
+
Output the PASS BODY ONLY: no "## Design pass" header (the script writes and
|
|
630
|
+
numbers it), no version numbers, no dates. Guidance: {{guidance}}
|
|
631
|
+
|
|
632
|
+
SCOPE — the story's ids and NOTHING else: it delivers {{exercises_csv}} and
|
|
633
|
+
must satisfy {{constraints_csv}}. Resolve every id's wording from spec.md in
|
|
634
|
+
context. Design work serving any other requirement is overreach — if touching
|
|
635
|
+
shared structure is genuinely unavoidable, name it under "### Risks & out of
|
|
636
|
+
scope" with the reason; do not design it here.
|
|
637
|
+
|
|
638
|
+
THIS IS THE SERVICE LANE ONLY. Screens, components, client state and styling
|
|
639
|
+
are the UI design's subject and are written to a separate document. Where the
|
|
640
|
+
slice has a user-facing surface, design the CONTRACT that serves it — the
|
|
641
|
+
endpoint or event and its shape — and stop there.
|
|
642
|
+
{{ui_lane_note}}
|
|
643
|
+
|
|
644
|
+
WHAT ALREADY EXISTS — the feature's code coverage, read from its OKF concept.
|
|
645
|
+
The BACKEND list is yours; the FRONTEND list is there so you do not design a
|
|
646
|
+
contract that ignores the client that already calls one.
|
|
647
|
+
|
|
648
|
+
{{coverage_digest}}
|
|
649
|
+
|
|
650
|
+
AUTHORITY, per input channel — on a conflict, stop at the defect, never pick
|
|
651
|
+
silently:
|
|
652
|
+
- spec.md is the ONLY authority for WHAT. A contradiction between spec and
|
|
653
|
+
story wording is a defect: flag it in the pass and design to the spec.
|
|
654
|
+
- The CURRENT CODE is the authority for what exists. Read the service files
|
|
655
|
+
listed here ({{source_file_count}} on disk); an empty list means greenfield —
|
|
656
|
+
say so in "### Current state" and design from a clean slate.
|
|
657
|
+
{{source_files_csv}}
|
|
658
|
+
- backend-plan.md is the FEATURE ARCHITECTURE this slice sits inside, and it
|
|
659
|
+
is BINDING: design within its layering, its ports and use-cases (§2), its
|
|
660
|
+
published surface (§3) and its cross-cutting decisions (§5). This pass reads
|
|
661
|
+
it ONE WAY — it NEVER writes back, and nothing here merges into it. Where the
|
|
662
|
+
slice genuinely cannot fit the plan, say so under "### Risks & out of scope"
|
|
663
|
+
and stop at the defect; the route is to change the spec and re-run
|
|
664
|
+
/appweaver plan, never to design around the plan here.
|
|
665
|
+
{{feature_plan_note}}
|
|
666
|
+
- The constitution and loaded technical rules are STANDARDS — non-negotiable.
|
|
667
|
+
- adr.md entries are DECIDED — follow them, or propose a superseding ADR under
|
|
668
|
+
"### Proposed ADRs" with the trade-off; never silently deviate.
|
|
669
|
+
- interfaces.md is the PUBLISHED surface — the latest entry per id is the
|
|
670
|
+
contract. Changing one means a superseding proposed entry, described under
|
|
671
|
+
"### Interfaces".
|
|
672
|
+
{{stale_note}}
|
|
673
|
+
|
|
674
|
+
STRUCTURE — exactly these subsections, in this order:
|
|
675
|
+
### Scope — the ids and the story's acceptance, one line each, resolved
|
|
676
|
+
from the spec (cite ids; never restate spec wording at length)
|
|
677
|
+
### Current state — what the service code has today that this slice touches
|
|
678
|
+
(file-level, from the coverage above; "greenfield — no code
|
|
679
|
+
yet" when the backend list is empty)
|
|
680
|
+
### Approach — the implementation strategy and WHY it fits the standards
|
|
681
|
+
and ADRs; concrete, this is the bridge to real code
|
|
682
|
+
### Changes — file-by-file: each file to create or modify and what
|
|
683
|
+
changes in it (signatures, wiring, migrations)
|
|
684
|
+
### Interfaces — published-surface changes in prose (endpoint/event, shape,
|
|
685
|
+
what it serves, what it supersedes) — or "None."
|
|
686
|
+
### Tests — what proves the acceptance criteria, named per criterion
|
|
687
|
+
### Proposed ADRs — ONLY a deliberate choice among rule-compliant alternatives,
|
|
688
|
+
a rejected alternative with the reason, or a trade-off with
|
|
689
|
+
its revisit trigger. Never restate what code shows or a rule
|
|
690
|
+
mandates. "None." when there is nothing durable to record.
|
|
691
|
+
### Risks & out of scope — divergences flagged above, unavoidable shared-structure
|
|
692
|
+
touches, what this slice deliberately leaves out
|
|
693
|
+
|
|
694
|
+
This pass is EPHEMERAL scaffolding — it is archived with the story, never
|
|
695
|
+
merged anywhere. Be concrete and name real paths, classes and signatures; a
|
|
696
|
+
later pass supersedes this one wholesale, so write nothing here that must
|
|
697
|
+
outlive the story (that is what the ADR and interface logs are for). Do not
|
|
698
|
+
write the story id into the body — the document's directory is its identity.
|
|
699
|
+
|
|
700
|
+
# 8. APPEND — story-plan-doc is the only writer of either lane's design.
|
|
701
|
+
# Script-owned header ("## Design pass N — <date> (spec vX)"), frontmatter
|
|
702
|
+
# stamps, temp file removed. Creating the first document in stories/<ID>/ is
|
|
703
|
+
# what flips the story in-flight.
|
|
704
|
+
- id: append_be
|
|
705
|
+
condition: "{{be_in_scope}} == true"
|
|
706
|
+
action: execute_script
|
|
707
|
+
script: { ref: actions }
|
|
708
|
+
subcommand: story-plan-doc
|
|
709
|
+
args: ["--feature-dir", "{{feature_dir}}", "--story", "{{story}}", "--lane", "backend", "--pass-file", "{{be_pass_tmp}}", "--spec-version", "{{current_version}}"]
|
|
710
|
+
outputs:
|
|
711
|
+
- { name: be_pass_n, from: "$.pass" }
|
|
712
|
+
- { name: be_created, from: "$.created" }
|
|
713
|
+
|
|
714
|
+
# ═══════════════ THE INTERFACE ENTRIES (payload-only, from the doc) ══════════
|
|
715
|
+
# Formalise the SERVICE pass's "### Interfaces" into append-only log entries.
|
|
716
|
+
# Separate step so the script stays the only id authority: entries arrive as
|
|
717
|
+
# IF-NEW blocks and log-append assigns IF-nnn, the date and status: proposed.
|
|
718
|
+
# The UI lane never runs this — it consumes the log, it does not publish into it.
|
|
719
|
+
- id: ctx_if
|
|
720
|
+
condition: "{{be_in_scope}} == true"
|
|
721
|
+
action: build_context
|
|
722
|
+
vars:
|
|
723
|
+
pass_number: "{{be_pass_n}}"
|
|
724
|
+
exercises_csv: "{{exercises_csv}}"
|
|
725
|
+
context:
|
|
726
|
+
- { path: "{{be_doc_path}}" } # the service design, latest pass included
|
|
727
|
+
- { path: "{{if_path}}", optional: true } # existing entries — what a change supersedes
|
|
728
|
+
- id: gen_if
|
|
729
|
+
condition: "{{be_in_scope}} == true"
|
|
730
|
+
action: agent_author
|
|
731
|
+
output_target: { type: file, path: "{{if_tmp}}" }
|
|
732
|
+
task: |
|
|
733
|
+
From "## Design pass {{pass_number}}" (the LAST pass) of the service story
|
|
734
|
+
design in context, extract its "### Interfaces" section into formal
|
|
735
|
+
interface-log entries.
|
|
736
|
+
|
|
737
|
+
THE FEATURE PLAN PUBLISHED THE SURFACE ALREADY. /appweaver plan writes this
|
|
738
|
+
feature's endpoints, events and external contracts into the log when it
|
|
739
|
+
authors or conforms backend-plan.md, so most passes have nothing to add here.
|
|
740
|
+
Output an entry ONLY where this slice CHANGES the published surface — a new
|
|
741
|
+
interface the log does not carry, or one it carries that this pass designs
|
|
742
|
+
differently (a SUPERSEDING entry naming the id it replaces). An interface the
|
|
743
|
+
log already carries unchanged is NOT re-published: a second entry saying the
|
|
744
|
+
same thing splits one contract in two.
|
|
745
|
+
|
|
746
|
+
If that section says None, describes no published-surface change (an endpoint,
|
|
747
|
+
event, or contract consumed outside this service), or describes only what the
|
|
748
|
+
log already carries — output exactly:
|
|
749
|
+
none
|
|
750
|
+
|
|
751
|
+
Otherwise output ONE block per changed interface, nothing else:
|
|
752
|
+
|
|
753
|
+
## IF-NEW — <METHOD /route, or the event name>
|
|
754
|
+
|
|
755
|
+
serves: <the FR-/BR- ids from {{exercises_csv}} this interface exists for>
|
|
756
|
+
supersedes: <the IF-nnn id it replaces from the interface log in context, or none>
|
|
757
|
+
|
|
758
|
+
<a concise request/response or payload sketch — shapes, not implementations>
|
|
759
|
+
|
|
760
|
+
Every block starts with "## IF-NEW — " exactly: the script assigns real ids,
|
|
761
|
+
the date and status: proposed. Entries are proposals against the as-built
|
|
762
|
+
check at story close — describe the surface the pass actually designed, and
|
|
763
|
+
invent nothing the pass does not contain.
|
|
764
|
+
- id: append_if
|
|
765
|
+
condition: "{{be_in_scope}} == true"
|
|
766
|
+
action: execute_script
|
|
767
|
+
on_error: continue # a malformed extraction is refused LOUDLY but must not burn
|
|
768
|
+
# the pass that already landed: the temp file survives for a
|
|
769
|
+
# hand-run of log-append; if_error carries the refusal below
|
|
770
|
+
script: { ref: actions }
|
|
771
|
+
subcommand: log-append
|
|
772
|
+
args: ["--feature-dir", "{{feature_dir}}", "--log", "interfaces", "--entries-file", "{{if_tmp}}"]
|
|
773
|
+
outputs:
|
|
774
|
+
- { name: if_ok, from: "$.ok" }
|
|
775
|
+
- { name: if_error, from: "$.error" }
|
|
776
|
+
- { name: if_appended, from: "$.appended" }
|
|
777
|
+
- { name: if_ids_csv, from: "$.ids_csv" }
|
|
778
|
+
- { name: if_unknown_csv, from: "$.unknown_ids_csv" }
|
|
779
|
+
- { name: if_badsup_csv, from: "$.bad_supersedes_csv" }
|
|
780
|
+
|
|
781
|
+
# ══════════════════════ THE UI DESIGN (payload-only + Read) ══════════════════
|
|
782
|
+
# The UI lane's rules load here, AFTER the service pass has been written, so a
|
|
783
|
+
# service author is never handed component and styling rules. The pass reads the
|
|
784
|
+
# service story design of the SAME story where there is one: that document, not a
|
|
785
|
+
# guess, is what this slice's screens bind to.
|
|
786
|
+
- id: tech_select_fe
|
|
787
|
+
condition: "{{fe_in_scope}} == true"
|
|
788
|
+
action: include
|
|
789
|
+
module: .appweaver/orchestrator/modules/tech-select.orch.yaml
|
|
790
|
+
with: { command: aw-spec-plan-ui }
|
|
791
|
+
|
|
792
|
+
- id: ctx_fe
|
|
793
|
+
condition: "{{fe_in_scope}} == true"
|
|
794
|
+
action: build_context
|
|
795
|
+
vars:
|
|
796
|
+
story_id: "{{story}}"
|
|
797
|
+
story_title: "{{story_title}}"
|
|
798
|
+
exercises_csv: "{{exercises_csv}}"
|
|
799
|
+
constraints_csv: "{{constraints_csv}}"
|
|
800
|
+
spec_current_version: "{{current_version}}"
|
|
801
|
+
stale_note: "{{stale_note}}"
|
|
802
|
+
pass_number: "{{fe_next_pass}}"
|
|
803
|
+
coverage_digest: "{{coverage_digest}}"
|
|
804
|
+
ui_source_files_csv: "{{fe_source_csv}}"
|
|
805
|
+
ui_source_file_count: "{{fe_source_count}}"
|
|
806
|
+
feature_plan_note: "{{fe_plan_note}}"
|
|
807
|
+
service_lane_note: "{{be_drop_msg}}"
|
|
808
|
+
guidance: "{{USER_INPUT}}"
|
|
809
|
+
context:
|
|
810
|
+
- { path: "{{story_path}}" } # the slice: exercises, Constraints, Acceptance
|
|
811
|
+
- { path: "{{spec_file}}" } # the contract — the only WHAT authority
|
|
812
|
+
- { path: "{{feature_dir}}/frontend-plan.md" } # the UI architecture this slice sits inside (READ-ONLY)
|
|
813
|
+
- { path: "{{fe_doc_path}}", optional: true } # earlier UI passes + manual edits (pass 2+)
|
|
814
|
+
- { path: "{{be_doc_path}}", optional: true } # this story's service design — what these screens bind to
|
|
815
|
+
- { path: "{{adr_path}}", optional: true } # ratified decisions — binding unless superseded
|
|
816
|
+
- { path: "{{if_path}}", optional: true } # the published surface this slice CONSUMES
|
|
817
|
+
- { path: "{{okf_concept}}", optional: true } # the feature's OKF concept (use-case log, source map)
|
|
818
|
+
- { path: .appweaver/memory/constitution.ui.md, optional: true }
|
|
819
|
+
- id: gen_fe
|
|
820
|
+
condition: "{{fe_in_scope}} == true"
|
|
821
|
+
action: agent_author
|
|
822
|
+
tools: Read # the current UI code arrives as {{ui_source_files_csv}} paths, not inlined
|
|
823
|
+
output_target: { type: file, path: "{{fe_pass_tmp}}" }
|
|
824
|
+
task: |
|
|
825
|
+
Write UI design pass {{pass_number}} for story {{story_id}} — {{story_title}}.
|
|
826
|
+
Output the PASS BODY ONLY: no "## Design pass" header (the script writes and
|
|
827
|
+
numbers it), no version numbers, no dates. Guidance: {{guidance}}
|
|
828
|
+
|
|
829
|
+
SCOPE — the story's ids and NOTHING else: it delivers {{exercises_csv}} and
|
|
830
|
+
must satisfy {{constraints_csv}}, of which the UX- constraints are yours to
|
|
831
|
+
realise. Resolve every id's wording from spec.md in context. Design work
|
|
832
|
+
serving any other requirement is overreach — if touching a shared shell,
|
|
833
|
+
layout or design-system component is genuinely unavoidable, name it under
|
|
834
|
+
"### Risks & out of scope" with the reason; do not design it here.
|
|
835
|
+
|
|
836
|
+
THIS IS THE UI LANE ONLY. Persistence, domain rules, service layering and
|
|
837
|
+
server-side validation belong to the service design and are written to a
|
|
838
|
+
separate document. Where this slice needs behaviour from the service, bind to
|
|
839
|
+
the PUBLISHED CONTRACT and stop there.
|
|
840
|
+
{{service_lane_note}}
|
|
841
|
+
|
|
842
|
+
WHAT ALREADY EXISTS — the feature's code coverage, read from its OKF concept.
|
|
843
|
+
The FRONTEND list is yours; the BACKEND list is there so you bind to code that
|
|
844
|
+
exists rather than to an endpoint nobody wrote.
|
|
845
|
+
|
|
846
|
+
{{coverage_digest}}
|
|
847
|
+
|
|
848
|
+
AUTHORITY, per input channel — on a conflict, stop at the defect, never pick
|
|
849
|
+
silently:
|
|
850
|
+
- spec.md is the ONLY authority for WHAT. A contradiction between spec and
|
|
851
|
+
story wording is a defect: flag it in the pass and design to the spec.
|
|
852
|
+
- The CURRENT CODE is the authority for what exists. Read the UI files listed
|
|
853
|
+
here ({{ui_source_file_count}} on disk); an empty list means this slice has
|
|
854
|
+
no UI yet — say so in "### Current state" and design from a clean slate.
|
|
855
|
+
{{ui_source_files_csv}}
|
|
856
|
+
- frontend-plan.md is the FEATURE'S UI ARCHITECTURE this slice sits inside, and
|
|
857
|
+
it is BINDING: design within its navigation and screen structure (§1), its
|
|
858
|
+
client-state placement (§2), its component composition (§3) and its
|
|
859
|
+
cross-cutting decisions (§6). This pass reads it ONE WAY — it NEVER writes
|
|
860
|
+
back, and nothing here merges into it. Where the slice genuinely cannot fit
|
|
861
|
+
the plan, say so under "### Risks & out of scope" and stop at the defect; the
|
|
862
|
+
route is to change the spec and re-run /appweaver plan-ui, never to design
|
|
863
|
+
around the plan here.
|
|
864
|
+
{{feature_plan_note}}
|
|
865
|
+
- The constitution and loaded technical rules are STANDARDS — non-negotiable.
|
|
866
|
+
- adr.md entries are DECIDED — follow them, or propose a superseding ADR under
|
|
867
|
+
"### Proposed ADRs" with the trade-off; never silently deviate.
|
|
868
|
+
- interfaces.md is the PUBLISHED surface and this lane CONSUMES it: bind to the
|
|
869
|
+
latest entry per id and INVENT NOTHING. An interface this slice needs that
|
|
870
|
+
the log does not carry is a DEFECT, not a gap for you to fill — name it under
|
|
871
|
+
"### Risks & out of scope" with the shape you needed; the route is
|
|
872
|
+
/appweaver story-plan on the service lane, which publishes it. Where this
|
|
873
|
+
story's service design is in context, it is the fresher word on what this
|
|
874
|
+
slice's surface will be — bind to it and say which entry it supersedes.
|
|
875
|
+
{{stale_note}}
|
|
876
|
+
|
|
877
|
+
STRUCTURE — exactly these subsections, in this order:
|
|
878
|
+
### Scope — the ids and the story's acceptance, one line each,
|
|
879
|
+
resolved from the spec (cite ids; never restate spec
|
|
880
|
+
wording at length)
|
|
881
|
+
### Current state — what the UI code has today that this slice touches
|
|
882
|
+
(file-level, from the coverage above; "no UI for this
|
|
883
|
+
slice yet" when the frontend list is empty)
|
|
884
|
+
### Approach — routes and screens touched, where the state for this
|
|
885
|
+
slice lives and why that placement fits the plan, and
|
|
886
|
+
how the slice fits the existing composition
|
|
887
|
+
### Screens & components — file-by-file: each component, route, store or style
|
|
888
|
+
file to create or modify and what changes in it (props,
|
|
889
|
+
state, events)
|
|
890
|
+
### Contract binding — every published interface this slice calls, BY ITS
|
|
891
|
+
IF-nnn id, with the request it sends and the response
|
|
892
|
+
it renders; and for each one what the user sees while
|
|
893
|
+
it is in flight, when it is empty, and when it fails
|
|
894
|
+
### Tests — what proves the acceptance criteria, named per
|
|
895
|
+
criterion (component, interaction and accessibility)
|
|
896
|
+
### Proposed ADRs — ONLY a deliberate choice among rule-compliant
|
|
897
|
+
alternatives, a rejected alternative with the reason, or
|
|
898
|
+
a trade-off with its revisit trigger. Never restate what
|
|
899
|
+
code shows or a rule mandates. "None." when there is
|
|
900
|
+
nothing durable to record.
|
|
901
|
+
### Risks & out of scope — divergences flagged above, contracts this slice needs
|
|
902
|
+
that are not published, unavoidable shared-shell
|
|
903
|
+
touches, what this slice deliberately leaves out
|
|
904
|
+
|
|
905
|
+
This pass is EPHEMERAL scaffolding — it is archived with the story, never
|
|
906
|
+
merged anywhere. Be concrete and name real paths, components and props; a later
|
|
907
|
+
pass supersedes this one wholesale, so write nothing here that must outlive the
|
|
908
|
+
story (that is what the ADR and interface logs are for). Do not write the story
|
|
909
|
+
id into the body — the document's directory is its identity.
|
|
910
|
+
|
|
911
|
+
- id: append_fe
|
|
912
|
+
condition: "{{fe_in_scope}} == true"
|
|
913
|
+
action: execute_script
|
|
914
|
+
script: { ref: actions }
|
|
915
|
+
subcommand: story-plan-doc
|
|
916
|
+
args: ["--feature-dir", "{{feature_dir}}", "--story", "{{story}}", "--lane", "frontend", "--pass-file", "{{fe_pass_tmp}}", "--spec-version", "{{current_version}}"]
|
|
917
|
+
outputs:
|
|
918
|
+
- { name: fe_pass_n, from: "$.pass" }
|
|
919
|
+
- { name: fe_created, from: "$.created" }
|
|
920
|
+
|
|
921
|
+
# 9. Post-execution hooks.
|
|
922
|
+
- id: resolve_post_hooks
|
|
923
|
+
action: execute_script
|
|
924
|
+
script: { ref: actions }
|
|
925
|
+
subcommand: resolve-hooks
|
|
926
|
+
args: ["--phase", "after_story_plan"]
|
|
927
|
+
outputs:
|
|
928
|
+
- { name: after_hooks, from: "$.hooks" }
|
|
929
|
+
- id: dispatch_post_hooks
|
|
930
|
+
action: dispatch_hooks
|
|
931
|
+
hooks_from_variable: after_hooks
|
|
932
|
+
|
|
933
|
+
outputs:
|
|
934
|
+
story: "{{story}}"
|
|
935
|
+
service_design: "{{be_doc_path}} (in scope: {{be_in_scope}}, pass {{be_pass_n}})"
|
|
936
|
+
ui_design: "{{fe_doc_path}} (in scope: {{fe_in_scope}}, pass {{fe_pass_n}})"
|
|
937
|
+
lanes_not_designed: "{{drops_note}}"
|
|
938
|
+
designed_against_spec: "{{current_version}}"
|
|
939
|
+
designed_within_plans: "backend-plan.md ({{be_plan_state}}, derived from spec {{be_plan_derived}}) · frontend-plan.md ({{fe_plan_state}}, derived from spec {{fe_plan_derived}})"
|
|
940
|
+
code_coverage: "OKF concept {{okf_concept}} — service {{be_source_count}} file(s), UI {{fe_source_count}} file(s), {{coverage_drift}} declared-but-missing; downstream of this aggregate: [{{used_by_csv}}]"
|
|
941
|
+
requirements_without_code_anchor: "{{unanchored_csv}} — anchors read from [{{source_map}}]; an EMPTY bracket means this feature carries no requirement-to-code index, so the ids above are unproven either way, not uncovered"
|
|
942
|
+
stale_ids: "{{stale_csv}}"
|
|
943
|
+
interfaces_appended: "{{if_ids_csv}}"
|
|
944
|
+
interface_warnings: "serves ids not in spec: [{{if_unknown_csv}}] · supersedes targets not in log: [{{if_badsup_csv}}] · append refusal: [{{if_error}}] (empty brackets = clean; a refusal keeps the temp entries file beside the doc — fix it and run the log-append action by hand rather than burning a new pass)"
|
|
945
|
+
next: "THE LATEST PASS WINS, per lane — read '## Design pass {{be_pass_n}}' in the service design and '## Design pass {{fe_pass_n}}' in the UI design; earlier passes are history and manual edits there survive. EDIT EITHER DOCUMENT BEFORE YOU BUILD: implement reads the latest pass as it stands, so a hand correction is honoured; re-run story-plan instead to redesign. Interface entries appended as status:proposed ({{if_ids_csv}}) — story close verifies them against the as-built and flips them live. Proposed ADRs (if any) sit in the passes for ratification into adr.md at story close. stale_ids non-empty means the spec moved above this story's stamp — consider /appweaver sync-stories. Then: /appweaver implement (service) · /appweaver implement-ui (UI)."
|