aw-spec-cli 0.1.16 → 0.1.18

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.
Files changed (70) hide show
  1. package/assets/.appweaver/DESIGN-FORMAT.md +18 -18
  2. package/assets/.appweaver/ORCH-DIALECT.md +1 -3
  3. package/assets/.appweaver/RULE-PARTITION.md +29 -13
  4. package/assets/.appweaver/STACK-PROFILES.md +10 -9
  5. package/assets/.appweaver/orchestrator/.state/smoke.state.json +32 -0
  6. package/assets/.appweaver/orchestrator/AGENT-PROFILE.md +4 -1
  7. package/assets/.appweaver/orchestrator/modules/delta-plan.orch.yaml +251 -0
  8. package/assets/.appweaver/orchestrator/modules/delta-spec.orch.yaml +104 -35
  9. package/assets/.appweaver/orchestrator/orchestrator.py +5 -4
  10. package/assets/.appweaver/orchestrator/rev-templates/delta-design-template.skeleton.md +94 -36
  11. package/assets/.appweaver/orchestrator/rev-templates/design-template.skeleton.md +1 -1
  12. package/assets/.appweaver/orchestrator/rev-templates/domain-model-template.skeleton.md +115 -0
  13. package/assets/.appweaver/orchestrator/rev-templates/domain-rule-bindings.skeleton.md +2 -9
  14. package/assets/.appweaver/orchestrator/rev-templates/tech-rule-bindings.skeleton.md +16 -40
  15. package/assets/.appweaver/orchestrator/rev-templates/ui-delta-design-template.skeleton.md +38 -17
  16. package/assets/.appweaver/orchestrator/rev-templates/ui-design-template.skeleton.md +8 -8
  17. package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +10 -11
  18. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan-delta.orch.yaml +673 -0
  19. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +535 -0
  20. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +643 -0
  21. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +84 -701
  22. package/assets/.appweaver/orchestrator/workflows/aw-spec-build.orch.yaml +4 -4
  23. package/assets/.appweaver/orchestrator/workflows/aw-spec-characterize.orch.yaml +7 -7
  24. package/assets/.appweaver/orchestrator/workflows/aw-spec-close-delta.orch.yaml +16 -14
  25. package/assets/.appweaver/orchestrator/workflows/aw-spec-constitution.orch.yaml +2 -2
  26. package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +243 -144
  27. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +877 -578
  28. package/assets/.appweaver/orchestrator/workflows/aw-spec-inventory.orch.yaml +4 -4
  29. package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +32 -91
  30. package/assets/.appweaver/orchestrator/workflows/aw-spec-parity.orch.yaml +2 -2
  31. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +836 -514
  32. package/assets/.appweaver/orchestrator/workflows/aw-spec-run.orch.yaml +2 -2
  33. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-bounded-context.orch.yaml +132 -0
  34. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-feature.orch.yaml +38 -5
  35. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-stack.orch.yaml +3 -3
  36. package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +271 -979
  37. package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +1085 -3385
  38. package/assets/.appweaver/scripts/bash/aw-xref.sh +1 -2
  39. package/assets/.appweaver/scripts/bash/common.sh +47 -0
  40. package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +914 -3220
  41. package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +1 -2
  42. package/assets/.appweaver/scripts/python/aw-payload-parity.py +19 -3
  43. package/assets/.appweaver/scripts/python/aw-stack.py +6 -2
  44. package/assets/.appweaver/scripts/python/aw-xref.py +86 -350
  45. package/assets/.appweaver/templates/constitution-ui-template.md +1 -1
  46. package/assets/.appweaver/templates/delta-design-template.md +120 -30
  47. package/assets/.appweaver/templates/delta-domain-model-template.md +81 -0
  48. package/assets/.appweaver/templates/delta-spec-template.md +43 -17
  49. package/assets/.appweaver/templates/design-template.md +68 -98
  50. package/assets/.appweaver/templates/domain-model-template.md +130 -0
  51. package/assets/.appweaver/templates/okf-concept-template.md +2 -2
  52. package/assets/.appweaver/templates/source-map-template.md +3 -3
  53. package/assets/.appweaver/templates/spec-template.md +100 -122
  54. package/assets/.appweaver/templates/ui-delta-design-template.md +159 -0
  55. package/assets/.appweaver/templates/ui-design-template.md +46 -75
  56. package/assets/guardrails/projects/dotnet-default-project/domain-rule-bindings.md +7 -18
  57. package/assets/guardrails/projects/dotnet-default-project/tech-rule-bindings.md +34 -39
  58. package/assets/guardrails/requirement-spec.txt +2 -2
  59. package/assets/guardrails/technology/dotnet-webapi/testing.md +1 -1
  60. package/package.json +1 -1
  61. package/assets/.appweaver/orchestrator/modules/delta-design.orch.yaml +0 -105
  62. package/assets/.appweaver/orchestrator/modules/spec-state-gate.orch.yaml +0 -87
  63. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-delta-design.orch.yaml +0 -251
  64. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-design.orch.yaml +0 -813
  65. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement-delta.orch.yaml +0 -253
  66. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement-ui.orch.yaml +0 -663
  67. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan-ui.orch.yaml +0 -600
  68. package/assets/.appweaver/orchestrator/workflows/aw-spec-story-plan.orch.yaml +0 -1147
  69. package/assets/.appweaver/orchestrator/workflows/aw-spec-sync-stories.orch.yaml +0 -1121
  70. package/assets/.appweaver/templates/story-template.md +0 -178
@@ -1,1147 +0,0 @@
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
- # the analyze-design verdict — law 40's evidence. certified defaults FALSE so a probe
124
- # that could not run fails CLOSED, and the message default carries a route for the same
125
- # reason: a refusal with no route is a wedged feature.
126
- gate_status: ""
127
- gate_certified: "false"
128
- gate_aborted: "false"
129
- gate_gap_refs: ""
130
- gate_report: ""
131
- gate_be_sha: ""
132
- gate_fe_sha: ""
133
- gate_spec_version: ""
134
- gate_msg: "The analyze-design verdict could not be read, so this run cannot tell whether the feature plans were ever certified. Run /appweaver analyze-design, then re-run story-plan."
135
- # what has to land first (read here, gated nowhere — see deps_notice)
136
- story_wave: ""
137
- depends_on_csv: ""
138
- open_deps_csv: ""
139
- unresolved_deps_csv: ""
140
- deps_clear: "true"
141
- deps_note: ""
142
- # per-lane feature-plan freshness (the one-way coupling: a story pass READS a plan)
143
- be_plan_state: ""
144
- be_plan_derived: ""
145
- be_plan_changed: ""
146
- be_plan_sha: ""
147
- fe_plan_sha: ""
148
- be_plan_note: ""
149
- be_drop_msg: ""
150
- be_dropped: "false"
151
- drops_note: "none — every declared lane was designed this run"
152
- fe_plan_state: ""
153
- fe_plan_derived: ""
154
- fe_plan_changed: ""
155
- fe_plan_note: ""
156
- fe_drop_msg: ""
157
- fe_dropped: "false"
158
- # story-plan-check bindings
159
- any_open: "false"
160
- open_ids: "[]"
161
- open_ids_csv: ""
162
- digest: ""
163
- resolved: "false"
164
- needs_pick: "false"
165
- pick_reason: ""
166
- story: ""
167
- story_path: ""
168
- story_open: "false"
169
- status_msg: ""
170
- story_title: ""
171
- exercises_csv: ""
172
- constraints_csv: ""
173
- story_spec_version: ""
174
- current_version: ""
175
- stale_csv: ""
176
- stale_note: ""
177
- story_dir: ""
178
- legacy_doc: ""
179
- legacy_doc_exists: "false"
180
- be_doc_path: ""
181
- be_doc_exists: "false"
182
- be_next_pass: "1"
183
- be_pass_tmp: ""
184
- fe_doc_path: ""
185
- fe_doc_exists: "false"
186
- fe_next_pass: "1"
187
- fe_pass_tmp: ""
188
- if_tmp: ""
189
- adr_path: ""
190
- if_path: ""
191
- picked_story: ""
192
- # OKF code coverage (the feature's current code, per lane)
193
- okf_concept: ""
194
- coverage_digest: ""
195
- be_source_csv: ""
196
- be_source_count: "0"
197
- fe_source_csv: ""
198
- fe_source_count: "0"
199
- coverage_drift: "0"
200
- used_by_csv: ""
201
- unanchored_csv: ""
202
- source_map: ""
203
- # write results
204
- be_pass_n: "0"
205
- be_created: "false"
206
- fe_pass_n: "0"
207
- fe_created: "false"
208
- if_appended: "0"
209
- if_ids_csv: ""
210
- if_unknown_csv: ""
211
- if_badsup_csv: ""
212
- if_ok: "true"
213
- if_error: ""
214
-
215
- workflow:
216
-
217
- # 1. WHICH LANES EXIST — the DECLARATION in stacks.json, not a directory on disk
218
- # (greenfield designs before the code exists). At least one is required; which
219
- # ones are actually designed is settled by the plan gates in step 4.
220
- - id: lane_resolve
221
- action: execute_script
222
- on_error: continue # never crash raw on an undeclared lane — the gate below explains it
223
- script: { ref: actions }
224
- subcommand: lane-resolve
225
- outputs:
226
- - { name: lanes_declared, from: "$.declared" }
227
- - { name: be_declared, from: "$.backend_declared" }
228
- - { name: fe_declared, from: "$.frontend_declared" }
229
- - id: be_scope
230
- condition: "{{be_declared}} == true"
231
- action: set
232
- vars: { be_in_scope: "true", have_lane: "true" }
233
- - id: fe_scope
234
- condition: "{{fe_declared}} == true"
235
- action: set
236
- vars: { fe_in_scope: "true", have_lane: "true" }
237
- - id: require_lane
238
- action: verify_context
239
- require_true: have_lane
240
- on_false: stop
241
- on_false_message: "{{lane_gate_msg}}"
242
-
243
- # 2. Active feature + its spec. feature-paths reports both plan paths whether or
244
- # not they exist; step 4 decides what an absence means for that lane.
245
- - id: feature_paths
246
- action: execute_script
247
- on_error: continue # never crash raw on 'no feature set' — the gate below explains it
248
- script: { ref: actions }
249
- subcommand: feature-paths
250
- outputs:
251
- - { name: feature_dir, from: "$.feature_dir" }
252
- - { name: spec_file, from: "$.spec_file" }
253
- - { name: spec_exists, from: "$.spec_exists" }
254
- - { name: be_plan, from: "$.backend_plan_file" }
255
- - { name: fe_plan, from: "$.frontend_plan_file" }
256
- - id: require_feature
257
- action: verify_context
258
- require_non_empty: feature_dir
259
- on_empty: stop
260
- on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the aggregate, e.g. Order), then re-run story-plan."
261
- - id: verify_spec
262
- action: verify_context
263
- require_true: spec_exists
264
- on_false: stop
265
- 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."
266
-
267
- # 3. The spec's ledger tripwire: no design pass from an unledgered spec — every
268
- # pass records the spec version it designed against, and an unledgered edit has
269
- # no version to record.
270
- - id: spec_guard
271
- action: include
272
- module: .appweaver/orchestrator/modules/spec-state-gate.orch.yaml
273
- with: { command: "/appweaver story-plan" }
274
-
275
- # 4. THE FEATURE-PLAN GATES, one per declared lane. A story design is a SLICE of
276
- # the feature's architecture, so it READS that lane's plan and NEVER writes
277
- # back. The freshness check stops a slice inheriting an architecture the spec
278
- # has already moved past while the script-written pass header stamps the CURRENT
279
- # spec version over it — the pass would read as designed against a spec the plan
280
- # never saw.
281
- #
282
- # A FAILING GATE DROPS ITS LANE, IT DOES NOT STOP THE RUN. Missing, behind,
283
- # edited_behind and untracked all mean the same thing — this lane has no usable
284
- # architecture right now — and none of them make the OTHER lane's design wrong.
285
- # Each drop is announced with its own route, and the run stops only if both
286
- # lanes are gone. `untracked` drops for the reason it always did: until one
287
- # /appweaver plan run baselines the document this gate cannot tell clean from
288
- # behind, and a gate that cannot tell is worse than one that asks. `edited` is
289
- # admitted and announced INTO the pass — the plan is the architect's document.
290
- - id: be_plan_read
291
- condition: "{{be_in_scope}} == true"
292
- action: execute_script
293
- on_error: continue # a probe that fails binds nothing; the lane stays out of scope ⇒ fails CLOSED
294
- script: { ref: actions }
295
- subcommand: plan-check
296
- args: ["--doc", "{{be_plan}}", "--feature-dir", "{{feature_dir}}"]
297
- outputs:
298
- - { name: be_plan_state, from: "$.state" }
299
- - { name: be_plan_derived, from: "$.derived_version" }
300
- - { name: be_plan_changed, from: "$.changed_csv" }
301
- # THE PLAN'S SHAPE, not just whether it moved. The certification check below compares
302
- # this against the hash analyze-design recorded in its verdict — which is the only way
303
- # to tell "approved" from "approved, then edited".
304
- - { name: be_plan_sha, from: "$.body_sha256" }
305
- - id: fe_plan_read
306
- condition: "{{fe_in_scope}} == true"
307
- action: execute_script
308
- on_error: continue # same fail-closed contract as the service probe
309
- script: { ref: actions }
310
- subcommand: plan-check
311
- args: ["--doc", "{{fe_plan}}", "--feature-dir", "{{feature_dir}}"]
312
- outputs:
313
- - { name: fe_plan_state, from: "$.state" }
314
- - { name: fe_plan_derived, from: "$.derived_version" }
315
- - { name: fe_plan_changed, from: "$.changed_csv" }
316
- - { name: fe_plan_sha, from: "$.body_sha256" }
317
-
318
- # 4a. SERVICE lane verdict. Only `clean` and `edited` keep the lane; every other
319
- # state (including the empty string a crashed probe leaves) drops it.
320
- - id: be_drop_default
321
- condition: "{{be_in_scope}} == true"
322
- action: set
323
- vars:
324
- be_in_scope: "false"
325
- be_dropped: "true"
326
- 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."
327
- - id: be_keep_clean
328
- condition: "{{be_plan_state}} == clean"
329
- action: set
330
- vars: { be_in_scope: "true", be_dropped: "false", be_drop_msg: "" }
331
- - id: be_keep_edited
332
- condition: "{{be_plan_state}} == edited"
333
- action: set
334
- vars:
335
- be_in_scope: "true"
336
- be_dropped: "false"
337
- be_drop_msg: ""
338
- 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."
339
- - id: be_msg_fresh
340
- condition: "{{be_plan_state}} == fresh"
341
- action: set
342
- 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." }
343
- - id: be_msg_behind
344
- condition: "{{be_plan_state}} == behind"
345
- action: set
346
- 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." }
347
- - id: be_msg_edited_behind
348
- condition: "{{be_plan_state}} == edited_behind"
349
- action: set
350
- 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." }
351
- - id: be_msg_untracked
352
- condition: "{{be_plan_state}} == untracked"
353
- action: set
354
- 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." }
355
-
356
- # 4b. UI lane verdict — the same contract, routed to the frontend lane's author.
357
- - id: fe_drop_default
358
- condition: "{{fe_in_scope}} == true"
359
- action: set
360
- vars:
361
- fe_in_scope: "false"
362
- fe_dropped: "true"
363
- 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."
364
- - id: fe_keep_clean
365
- condition: "{{fe_plan_state}} == clean"
366
- action: set
367
- vars: { fe_in_scope: "true", fe_dropped: "false", fe_drop_msg: "" }
368
- - id: fe_keep_edited
369
- condition: "{{fe_plan_state}} == edited"
370
- action: set
371
- vars:
372
- fe_in_scope: "true"
373
- fe_dropped: "false"
374
- fe_drop_msg: ""
375
- 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."
376
- - id: fe_msg_fresh
377
- condition: "{{fe_plan_state}} == fresh"
378
- action: set
379
- 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." }
380
- - id: fe_msg_behind
381
- condition: "{{fe_plan_state}} == behind"
382
- action: set
383
- 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." }
384
- - id: fe_msg_edited_behind
385
- condition: "{{fe_plan_state}} == edited_behind"
386
- action: set
387
- 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." }
388
- - id: fe_msg_untracked
389
- condition: "{{fe_plan_state}} == untracked"
390
- action: set
391
- 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." }
392
-
393
- # 4c. At least one lane must survive. When none does, BOTH reasons travel in the
394
- # stop message — a run that reported only the first would send the author back
395
- # for a second refusal.
396
- - id: have_be
397
- condition: "{{be_in_scope}} == true"
398
- action: set
399
- vars: { have_design_lane: "true" }
400
- - id: have_fe
401
- condition: "{{fe_in_scope}} == true"
402
- action: set
403
- vars: { have_design_lane: "true" }
404
- - id: require_design_lane
405
- action: verify_context
406
- require_true: have_design_lane
407
- on_false: stop
408
- on_false_message: |
409
- No lane has a usable feature plan, so there is nothing to design this story against.
410
-
411
- {{be_drop_msg}}
412
-
413
- {{fe_drop_msg}}
414
-
415
- # 4d. What the run did NOT design, in one line for the outputs. Both branches
416
- # compose the same expression: either message is empty when its lane is fine,
417
- # so one set covers a drop on either lane or on both.
418
- - id: drops_note_be
419
- condition: "{{be_dropped}} == true"
420
- action: set
421
- vars: { drops_note: "{{be_drop_msg}} {{fe_drop_msg}}" }
422
- - id: drops_note_fe
423
- condition: "{{fe_dropped}} == true"
424
- action: set
425
- vars: { drops_note: "{{be_drop_msg}} {{fe_drop_msg}}" }
426
-
427
- # 5. WHICH STORY. The check resolves an explicit id in the input; anything else
428
- # (nothing named, an unknown id, several ids) becomes a pick from the open
429
- # backlog. All state the gates below read comes back in one scan, including
430
- # both lanes' document and pass state.
431
- - id: story_scan
432
- action: execute_script
433
- script: { ref: actions }
434
- subcommand: story-plan-check
435
- args: ["--feature-dir", "{{feature_dir}}", "--input", "{{USER_INPUT}}"]
436
- outputs:
437
- - { name: any_open, from: "$.any_open" }
438
- - { name: open_ids, from: "$.open_ids" }
439
- - { name: open_ids_csv, from: "$.open_ids_csv" }
440
- - { name: digest, from: "$.digest" }
441
- - { name: resolved, from: "$.resolved" }
442
- - { name: needs_pick, from: "$.needs_pick" }
443
- - { name: pick_reason, from: "$.pick_reason" }
444
- - { name: story, from: "$.story" }
445
- - { name: story_path, from: "$.story_path" }
446
- - { name: story_open, from: "$.story_open" }
447
- - { name: status_msg, from: "$.status_msg" }
448
- - { name: story_title, from: "$.story_title" }
449
- - { name: exercises_csv, from: "$.exercises_csv" }
450
- - { name: constraints_csv, from: "$.constraints_csv" }
451
- - { name: story_spec_version, from: "$.story_spec_version" }
452
- - { name: current_version, from: "$.current_version" }
453
- - { name: stale_csv, from: "$.stale_csv" }
454
- - { name: stale_note, from: "$.stale_note" }
455
- - { name: story_dir, from: "$.story_dir" }
456
- - { name: legacy_doc, from: "$.legacy_doc" }
457
- - { name: legacy_doc_exists, from: "$.legacy_doc_exists" }
458
- - { name: be_doc_path, from: "$.be_doc_path" }
459
- - { name: be_doc_exists, from: "$.be_doc_exists" }
460
- - { name: be_next_pass, from: "$.be_next_pass" }
461
- - { name: be_pass_tmp, from: "$.be_pass_tmp" }
462
- - { name: fe_doc_path, from: "$.fe_doc_path" }
463
- - { name: fe_doc_exists, from: "$.fe_doc_exists" }
464
- - { name: fe_next_pass, from: "$.fe_next_pass" }
465
- - { name: fe_pass_tmp, from: "$.fe_pass_tmp" }
466
- - { name: if_tmp, from: "$.if_tmp" }
467
- - { name: adr_path, from: "$.adr_path" }
468
- - { name: if_path, from: "$.if_path" }
469
- - id: require_open_backlog
470
- action: verify_context
471
- require_true: any_open
472
- on_false: stop
473
- 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)."
474
- - id: ask_story
475
- condition: "{{needs_pick}} == true"
476
- action: ask
477
- prompt: |
478
- Which story is this design pass for? ({{pick_reason}})
479
-
480
- {{digest}}
481
- options_from: "{{open_ids}}"
482
- answer_key: picked_story
483
- answer_var: picked_story
484
- - id: story_pick_check
485
- condition: "{{needs_pick}} == true"
486
- action: execute_script
487
- script: { ref: actions }
488
- subcommand: story-plan-check
489
- args: ["--feature-dir", "{{feature_dir}}", "--story", "{{picked_story}}"]
490
- outputs:
491
- - { name: resolved, from: "$.resolved" }
492
- - { name: story, from: "$.story" }
493
- - { name: story_path, from: "$.story_path" }
494
- - { name: story_open, from: "$.story_open" }
495
- - { name: status_msg, from: "$.status_msg" }
496
- - { name: story_title, from: "$.story_title" }
497
- - { name: exercises_csv, from: "$.exercises_csv" }
498
- - { name: constraints_csv, from: "$.constraints_csv" }
499
- - { name: story_spec_version, from: "$.story_spec_version" }
500
- - { name: stale_csv, from: "$.stale_csv" }
501
- - { name: stale_note, from: "$.stale_note" }
502
- - { name: story_dir, from: "$.story_dir" }
503
- - { name: legacy_doc, from: "$.legacy_doc" }
504
- - { name: legacy_doc_exists, from: "$.legacy_doc_exists" }
505
- - { name: be_doc_path, from: "$.be_doc_path" }
506
- - { name: be_doc_exists, from: "$.be_doc_exists" }
507
- - { name: be_next_pass, from: "$.be_next_pass" }
508
- - { name: be_pass_tmp, from: "$.be_pass_tmp" }
509
- - { name: fe_doc_path, from: "$.fe_doc_path" }
510
- - { name: fe_doc_exists, from: "$.fe_doc_exists" }
511
- - { name: fe_next_pass, from: "$.fe_next_pass" }
512
- - { name: fe_pass_tmp, from: "$.fe_pass_tmp" }
513
- - { name: if_tmp, from: "$.if_tmp" }
514
- - { name: story_wave, from: "$.story_wave" }
515
- - { name: depends_on_csv, from: "$.depends_on_csv" }
516
- - { name: open_deps_csv, from: "$.open_deps_csv" }
517
- - { name: unresolved_deps_csv, from: "$.unresolved_deps_csv" }
518
- - { name: deps_clear, from: "$.deps_clear" }
519
- - { name: deps_note, from: "$.deps_note" }
520
- - { name: be_pass_plan_hash, from: "$.be_pass_plan_hash" }
521
- - { name: fe_pass_plan_hash, from: "$.fe_pass_plan_hash" }
522
- - id: require_story
523
- action: verify_context
524
- require_true: resolved
525
- on_false: stop
526
- on_false_message: |
527
- Could not resolve a story from '{{picked_story}}'. Open stories:
528
- {{digest}}
529
- Re-run story-plan naming one of them.
530
- - id: require_open_story
531
- action: verify_context
532
- require_true: story_open
533
- on_false: stop
534
- on_false_message: "{{status_msg}}"
535
-
536
- # 5a. A dropped lane is announced HERE — after the story is known, so the notice
537
- # names the story it applies to, and before any write, so the author can stop
538
- # and fix the plan rather than discovering the gap after a pass has landed.
539
- # 5.0 WHAT HAS TO LAND FIRST. sync-stories computes depends_on: and wave: and stamps both
540
- # into every story, and until now nothing downstream read either — the ordering was
541
- # recorded and never enforced, in the one place with a story in hand to enforce it on.
542
- # A NOTICE, NOT A REFUSAL: a dependency is about CODE ordering, and designing against
543
- # work that has not landed is legitimate — it is BUILDING against it that starts to
544
- # cost something. Two misses are kept apart: a dependency still OPEN in this feature
545
- # is work in progress, and one with no story file here is UNRESOLVED, which is the
546
- # ordinary shape of a cross-feature edge and not at all the same as satisfied.
547
- # Deliberately NOT "an open story in a lower wave": a story becomes startable when its
548
- # OWN dependencies land, not when the whole previous wave finishes, so warning on the
549
- # wave would serialise a level that exists precisely to be unordered. The wave is
550
- # surfaced and gated by nothing.
551
- - id: deps_notice
552
- condition: "{{deps_clear}} != true"
553
- action: verify_context
554
- require_true: deps_clear
555
- on_false: continue
556
- on_false_message: "{{deps_note}}"
557
-
558
- - id: be_drop_notice
559
- condition: "{{be_dropped}} == true"
560
- action: verify_context
561
- require_value: be_dropped
562
- equals: "false"
563
- on_mismatch: continue
564
- on_mismatch_message: "{{be_drop_msg}}"
565
- - id: fe_drop_notice
566
- condition: "{{fe_dropped}} == true"
567
- action: verify_context
568
- require_value: fe_dropped
569
- equals: "false"
570
- on_mismatch: continue
571
- on_mismatch_message: "{{fe_drop_msg}}"
572
-
573
- # ═══════ 5a-bis. LAW 40 — approved, changed, never re-approved must not reach code ═══════
574
- # analyze-design certifies the feature plans and writes a machine-readable verdict beside
575
- # its report. Nothing in the kit read one back, so "was this plan approved, and was it
576
- # approved in the shape it is in NOW" was a question only a person opening the JSON could
577
- # answer — and this is the phase that turns a feature plan into a slice design that
578
- # implement builds from without ever re-asking it.
579
- #
580
- # HERE, AND NOT AT be_plan_read. Those probes feed the drop-on-behind decision above;
581
- # gating there would change WHICH lanes get dropped. This runs after the keep/drop set is
582
- # settled and before anything is authored, so it can supersede be_keep_edited — that step
583
- # keeps an edited plan and tells the author it "is authoritative here", which is exactly
584
- # the state law 40 forbids when nobody certified the edit.
585
- - id: gate_verdict
586
- action: execute_script
587
- on_error: continue
588
- script: { ref: actions }
589
- subcommand: gate-read
590
- args: ["--feature-dir", "{{feature_dir}}", "--gate", "analyze-design"]
591
- outputs:
592
- - { name: gate_status, from: "$.status" }
593
- - { name: gate_certified, from: "$.certified" }
594
- - { name: gate_aborted, from: "$.aborted" }
595
- - { name: gate_gap_refs, from: "$.gap_refs_csv" }
596
- - { name: gate_report, from: "$.report" }
597
- - { name: gate_be_sha, from: "$.backend_sha256" }
598
- - { name: gate_fe_sha, from: "$.frontend_sha256" }
599
- - { name: gate_spec_version, from: "$.spec_version" }
600
-
601
- # THREE REASONS, THREE ROUTES, and telling them apart is the whole value. Ordered sets,
602
- # last match wins: never ran → gated red → started and aborted. Aborted is last because it
603
- # also carries status "fail" (gate_result_open writes a red BEFORE any analysis, so an
604
- # abandoned run never leaves a stale green) and sending someone to read gaps that were
605
- # never found is the one wrong answer here.
606
- - id: gate_msg_never
607
- action: set
608
- vars:
609
- gate_msg: "The feature plans for this feature have never been certified: there is no analyze-design verdict beside them. A story design turns the feature plan into the slice implement builds, so designing against an ungated plan puts an unapproved architecture into code through two documents that never re-ask the question. Run /appweaver analyze-design, then re-run story-plan {{story}}."
610
- - id: gate_msg_red
611
- condition: "{{gate_status}} == fail"
612
- action: set
613
- vars:
614
- gate_msg: "The feature plans do NOT conform to the spec: analyze-design's last verdict is FAIL on {{gate_gap_refs}}. The full report is at {{gate_report}}. Close those gaps first — a wrong PLAN is repaired by re-running /appweaver analyze-design and accepting the repair, or by hand-editing it and re-running /appweaver plan (or plan-ui) with accept_manual: true; a wrong SPEC goes through the spec route. Then re-run story-plan {{story}}."
615
- - id: gate_msg_aborted
616
- condition: "{{gate_aborted}} == true"
617
- action: set
618
- vars:
619
- gate_msg: "The last analyze-design run STARTED and never reached a verdict — the red on disk is the one it writes before any analysis, so nothing was found and there are no gaps to go and read. The plans are UNCERTIFIED, not rejected. Run /appweaver analyze-design to completion, then re-run story-plan {{story}}."
620
-
621
- - id: gate_certified_guard
622
- condition: "{{have_lane}} == true"
623
- action: verify_context
624
- require_true: gate_certified
625
- on_false: stop
626
- on_false_message: "{{gate_msg}}"
627
-
628
- # PER KEPT LANE, and only per kept lane. A lane that is undeclared or was dropped above
629
- # has no certified hash and must not fail the run for lacking one.
630
- # An EMPTY recorded hash fails the comparison, and that is correct: a verdict that does
631
- # not say which shape it certified cannot prove this is that shape.
632
- - id: be_certified_shape
633
- condition: "{{be_in_scope}} == true"
634
- action: verify_context
635
- require_value: be_plan_sha
636
- equals: "{{gate_be_sha}}"
637
- on_mismatch: stop
638
- on_mismatch_message: "backend-plan.md is not the document analyze-design certified — it has been edited since (or the verdict predates hash recording and cannot say what it approved). Designing this slice against it would carry an unapproved architecture into code, which is exactly what law 40 forbids. Re-run /appweaver analyze-design to certify the plan as it now stands, then re-run story-plan {{story}}."
639
- - id: fe_certified_shape
640
- condition: "{{fe_in_scope}} == true"
641
- action: verify_context
642
- require_value: fe_plan_sha
643
- equals: "{{gate_fe_sha}}"
644
- on_mismatch: stop
645
- on_mismatch_message: "frontend-plan.md is not the document analyze-design certified — it has been edited since (or the verdict predates hash recording and cannot say what it approved). Re-run /appweaver analyze-design to certify the plan as it now stands, then re-run story-plan {{story}}."
646
-
647
- # ═══════ 5a-ter. WHICH LANES THIS STORY WAS DESIGNED FOR, AND WHICH WERE DROPPED ═══════
648
- # Two fields, because one of them cannot say the difference. analyze-design STOPS on a
649
- # lane whose plan is behind; this workflow DROPS it and designs the other — so one command
650
- # later "this slice has no user-facing surface" and "the UI lane was out of scope when it
651
- # was designed" are the same state on disk, and a UX--bearing requirement can ship
652
- # backend-only with a green story-scoped gate.
653
- # story-stamp is the frontmatter writer, and every one of its fields is guarded by its own
654
- # flag, so these calls touch nothing else in the story. One per lane per outcome, because
655
- # the keys UNION: a lane that has ever been designed keeps saying so.
656
- # NOT story-plan-doc — that writes the PASS, and a dropped lane has no pass, which is the
657
- # entire point. NOT sync-stories — stories are not designed there.
658
- - id: stamp_lane_be_designed
659
- condition: "{{be_in_scope}} == true"
660
- action: execute_script
661
- on_error: continue
662
- script: { ref: actions }
663
- subcommand: story-stamp
664
- args: ["--feature-dir", "{{feature_dir}}", "--story", "{{story}}", "--designed-lanes", "backend"]
665
- - id: stamp_lane_be_dropped
666
- condition: "{{be_dropped}} == true"
667
- action: execute_script
668
- on_error: continue
669
- script: { ref: actions }
670
- subcommand: story-stamp
671
- args: ["--feature-dir", "{{feature_dir}}", "--story", "{{story}}", "--dropped-lanes", "backend"]
672
- - id: stamp_lane_fe_designed
673
- condition: "{{fe_in_scope}} == true"
674
- action: execute_script
675
- on_error: continue
676
- script: { ref: actions }
677
- subcommand: story-stamp
678
- args: ["--feature-dir", "{{feature_dir}}", "--story", "{{story}}", "--designed-lanes", "frontend"]
679
- - id: stamp_lane_fe_dropped
680
- condition: "{{fe_dropped}} == true"
681
- action: execute_script
682
- on_error: continue
683
- script: { ref: actions }
684
- subcommand: story-stamp
685
- args: ["--feature-dir", "{{feature_dir}}", "--story", "{{story}}", "--dropped-lanes", "frontend"]
686
-
687
- # 5b. A pre-split story-plan.md beside the story is REPORTED, never read and never
688
- # written. Silently leaving it there would leave a design document nobody
689
- # consumes sitting beside two that are read — and the next person to open the
690
- # story directory has no way to tell which of the three implement builds from.
691
- - id: legacy_doc_notice
692
- condition: "{{legacy_doc_exists}} == true"
693
- action: verify_context
694
- require_value: legacy_doc_exists
695
- equals: "false"
696
- on_mismatch: continue
697
- 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."
698
-
699
- # 6. THE FEATURE'S CODE, from its OKF concept — what IS, split by lane, with the
700
- # drift, the dependencies and the blast radius a bare path list drops. Advisory:
701
- # a greenfield feature has no concept yet and designs from an empty current
702
- # state, so a missing bundle must never block the design. --ids reports which of
703
- # the story's requirements already have a code anchor where the feature carries
704
- # a source map; where it does not, every id comes back unanchored and the digest
705
- # says so rather than implying the code is absent.
706
- - id: coverage
707
- action: execute_script
708
- on_error: continue
709
- script: { ref: xref }
710
- subcommand: concept-coverage
711
- args: ["--feature-dir", "{{feature_dir}}", "--ids", "{{exercises_csv}}"]
712
- outputs:
713
- - { name: okf_concept, from: "$.concept" }
714
- - { name: coverage_digest, from: "$.digest" }
715
- - { name: be_source_csv, from: "$.backend_csv" }
716
- - { name: be_source_count, from: "$.backend_count" }
717
- - { name: fe_source_csv, from: "$.frontend_csv" }
718
- - { name: fe_source_count, from: "$.frontend_count" }
719
- - { name: coverage_drift, from: "$.missing_count" }
720
- - { name: used_by_csv, from: "$.used_by_csv" }
721
- - { name: unanchored_csv, from: "$.unanchored_csv" }
722
- - { name: source_map, from: "$.source_map" }
723
- - id: drift_notice
724
- condition: "{{coverage_drift}} != 0"
725
- action: verify_context
726
- require_value: coverage_drift
727
- equals: "0"
728
- on_mismatch: continue
729
- 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."
730
-
731
- # 7. Pre-execution hooks.
732
- - id: resolve_pre_hooks
733
- action: execute_script
734
- script: { ref: actions }
735
- subcommand: resolve-hooks
736
- args: ["--phase", "before_story_plan"]
737
- outputs:
738
- - { name: before_hooks, from: "$.hooks" }
739
- - id: dispatch_pre_hooks
740
- action: dispatch_hooks
741
- hooks_from_variable: before_hooks
742
-
743
- # ════════════════════ THE SERVICE DESIGN (payload-only + Read) ═══════════════
744
- # Lane rules first, then the pass. The doc itself is in context on a re-run
745
- # (optional — absent before pass 1), so a later pass SEES earlier passes and any
746
- # manual edits instead of contradicting them blind. Code arrives as a PATH LIST;
747
- # the step declares Read to open it.
748
- - id: tech_select_be
749
- condition: "{{be_in_scope}} == true"
750
- action: include
751
- module: .appweaver/orchestrator/modules/tech-select.orch.yaml
752
- with: { command: aw-spec-plan }
753
-
754
- - id: ctx_be
755
- condition: "{{be_in_scope}} == true"
756
- action: build_context
757
- vars:
758
- story_id: "{{story}}"
759
- story_title: "{{story_title}}"
760
- exercises_csv: "{{exercises_csv}}"
761
- constraints_csv: "{{constraints_csv}}"
762
- spec_current_version: "{{current_version}}"
763
- stale_note: "{{stale_note}}"
764
- pass_number: "{{be_next_pass}}"
765
- coverage_digest: "{{coverage_digest}}"
766
- source_files_csv: "{{be_source_csv}}"
767
- source_file_count: "{{be_source_count}}"
768
- feature_plan_note: "{{be_plan_note}}"
769
- ui_lane_note: "{{fe_drop_msg}}"
770
- guidance: "{{USER_INPUT}}"
771
- context:
772
- - { path: "{{story_path}}" } # the slice: exercises, Constraints, Acceptance
773
- - { path: "{{spec_file}}" } # the contract — the only WHAT authority
774
- - { path: "{{feature_dir}}/backend-plan.md" } # the feature architecture this slice sits inside (READ-ONLY)
775
- - { path: "{{be_doc_path}}", optional: true } # earlier service passes + manual edits (pass 2+)
776
- - { path: "{{adr_path}}", optional: true } # ratified decisions — binding unless superseded
777
- - { path: "{{if_path}}", optional: true } # the published surface (latest entry per id wins)
778
- - { path: "{{okf_concept}}", optional: true } # the feature's OKF concept (use-case log, source map)
779
- - { path: .appweaver/memory/constitution.technical.md, optional: true }
780
- - id: gen_be
781
- condition: "{{be_in_scope}} == true"
782
- action: agent_author
783
- tools: Read # the current code arrives as {{source_files_csv}} paths, not inlined
784
- output_target: { type: file, path: "{{be_pass_tmp}}" }
785
- task: |
786
- Write SERVICE design pass {{pass_number}} for story {{story_id}} — {{story_title}}.
787
- Output the PASS BODY ONLY: no "## Design pass" header (the script writes and
788
- numbers it), no version numbers, no dates. Guidance: {{guidance}}
789
-
790
- SCOPE — the story's ids and NOTHING else: it delivers {{exercises_csv}} and
791
- must satisfy {{constraints_csv}}. Resolve every id's wording from spec.md in
792
- context. Design work serving any other requirement is overreach — if touching
793
- shared structure is genuinely unavoidable, name it under "### Risks & out of
794
- scope" with the reason; do not design it here.
795
-
796
- THIS IS THE SERVICE LANE ONLY. Screens, components, client state and styling
797
- are the UI design's subject and are written to a separate document. Where the
798
- slice has a user-facing surface, design the CONTRACT that serves it — the
799
- endpoint or event and its shape — and stop there.
800
- {{ui_lane_note}}
801
-
802
- WHAT ALREADY EXISTS — the feature's code coverage, read from its OKF concept.
803
- The BACKEND list is yours; the FRONTEND list is there so you do not design a
804
- contract that ignores the client that already calls one.
805
-
806
- {{coverage_digest}}
807
-
808
- AUTHORITY, per input channel — on a conflict, stop at the defect, never pick
809
- silently:
810
- - spec.md is the ONLY authority for WHAT. A contradiction between spec and
811
- story wording is a defect: flag it in the pass and design to the spec.
812
- - The CURRENT CODE is the authority for what exists. Read the service files
813
- listed here ({{source_file_count}} on disk); an empty list means greenfield —
814
- say so in "### Current state" and design from a clean slate.
815
- {{source_files_csv}}
816
- - backend-plan.md is the FEATURE ARCHITECTURE this slice sits inside, and it
817
- is BINDING: design within its layering, its ports and use-cases (§2), its
818
- published surface (§3) and its cross-cutting decisions (§5). This pass reads
819
- it ONE WAY — it NEVER writes back, and nothing here merges into it. Where the
820
- slice genuinely cannot fit the plan, say so under "### Risks & out of scope"
821
- and stop at the defect; the route is to change the spec and re-run
822
- /appweaver plan, never to design around the plan here.
823
- {{feature_plan_note}}
824
- - The constitution and loaded technical rules are STANDARDS — non-negotiable.
825
- - adr.md entries are DECIDED — follow them, or propose a superseding ADR under
826
- "### Proposed ADRs" with the trade-off; never silently deviate.
827
- - interfaces.md is the PUBLISHED surface — the latest entry per id is the
828
- contract. Changing one means a superseding proposed entry, described under
829
- "### Interfaces".
830
- {{stale_note}}
831
-
832
- STRUCTURE — exactly these subsections, in this order:
833
- ### Scope — the ids and the story's acceptance, one line each, resolved
834
- from the spec (cite ids; never restate spec wording at length)
835
- ### Current state — what the service code has today that this slice touches
836
- (file-level, from the coverage above; "greenfield — no code
837
- yet" when the backend list is empty)
838
- ### Approach — the implementation strategy and WHY it fits the standards
839
- and ADRs; concrete, this is the bridge to real code
840
- ### Changes — file-by-file: each file to create or modify and what
841
- changes in it (signatures, wiring, migrations)
842
- ### Interfaces — published-surface changes in prose (endpoint/event, shape,
843
- what it serves, what it supersedes) — or "None."
844
- ### Tests — what proves the acceptance criteria, named per criterion
845
- ### Proposed ADRs — ONLY a deliberate choice among rule-compliant alternatives,
846
- a rejected alternative with the reason, or a trade-off with
847
- its revisit trigger. Never restate what code shows or a rule
848
- mandates. "None." when there is nothing durable to record.
849
- ### Risks & out of scope — divergences flagged above, unavoidable shared-structure
850
- touches, what this slice deliberately leaves out
851
-
852
- This pass is EPHEMERAL scaffolding — it is archived with the story, never
853
- merged anywhere. Be concrete and name real paths, classes and signatures; a
854
- later pass supersedes this one wholesale, so write nothing here that must
855
- outlive the story (that is what the ADR and interface logs are for). Do not
856
- write the story id into the body — the document's directory is its identity.
857
-
858
- # 8. APPEND — story-plan-doc is the only writer of either lane's design.
859
- # Script-owned header ("## Design pass N — <date> (spec vX)"), frontmatter
860
- # stamps, temp file removed. Creating the first document in stories/<ID>/ is
861
- # what flips the story in-flight.
862
- - id: append_be
863
- condition: "{{be_in_scope}} == true"
864
- action: execute_script
865
- script: { ref: actions }
866
- subcommand: story-plan-doc
867
- # --plan-hash stamps the CERTIFIED feature-plan hash into the pass header — the one
868
- # analyze-design recorded, which the guard above has just proved is the plan on disk.
869
- # A pass is written once and read forever, so without it implement could only ask
870
- # "is the plan certified NOW", and a pass designed against plan H1 would still
871
- # generate after the plan was edited to H2 and re-gated — the exact case law 40 names.
872
- args: ["--feature-dir", "{{feature_dir}}", "--story", "{{story}}", "--lane", "backend", "--pass-file", "{{be_pass_tmp}}", "--spec-version", "{{current_version}}", "--plan-hash", "{{gate_be_sha}}"]
873
- outputs:
874
- - { name: be_pass_n, from: "$.pass" }
875
- - { name: be_created, from: "$.created" }
876
-
877
- # ═══════════════ THE INTERFACE ENTRIES (payload-only, from the doc) ══════════
878
- # Formalise the SERVICE pass's "### Interfaces" into append-only log entries.
879
- # Separate step so the script stays the only id authority: entries arrive as
880
- # IF-NEW blocks and log-append assigns IF-nnn, the date and status: proposed.
881
- # The UI lane never runs this — it consumes the log, it does not publish into it.
882
- - id: ctx_if
883
- condition: "{{be_in_scope}} == true"
884
- action: build_context
885
- vars:
886
- pass_number: "{{be_pass_n}}"
887
- exercises_csv: "{{exercises_csv}}"
888
- context:
889
- - { path: "{{be_doc_path}}" } # the service design, latest pass included
890
- - { path: "{{if_path}}", optional: true } # existing entries — what a change supersedes
891
- - id: gen_if
892
- condition: "{{be_in_scope}} == true"
893
- action: agent_author
894
- output_target: { type: file, path: "{{if_tmp}}" }
895
- task: |
896
- From "## Design pass {{pass_number}}" (the LAST pass) of the service story
897
- design in context, extract its "### Interfaces" section into formal
898
- interface-log entries.
899
-
900
- THE FEATURE PLAN PUBLISHED THE SURFACE ALREADY. /appweaver plan writes this
901
- feature's endpoints, events and external contracts into the log when it
902
- authors or conforms backend-plan.md, so most passes have nothing to add here.
903
- Output an entry ONLY where this slice CHANGES the published surface — a new
904
- interface the log does not carry, or one it carries that this pass designs
905
- differently (a SUPERSEDING entry naming the id it replaces). An interface the
906
- log already carries unchanged is NOT re-published: a second entry saying the
907
- same thing splits one contract in two.
908
-
909
- If that section says None, describes no published-surface change (an endpoint,
910
- event, or contract consumed outside this service), or describes only what the
911
- log already carries — output exactly:
912
- none
913
-
914
- Otherwise output ONE block per changed interface, nothing else:
915
-
916
- ## IF-NEW — <METHOD /route, or the event name>
917
-
918
- serves: <the FR-/BR- ids from {{exercises_csv}} this interface exists for>
919
- supersedes: <the IF-nnn id it replaces from the interface log in context, or none>
920
-
921
- <a concise request/response or payload sketch — shapes, not implementations>
922
-
923
- Every block starts with "## IF-NEW — " exactly: the script assigns real ids,
924
- the date and status: proposed. Entries are proposals against the as-built
925
- check at story close — describe the surface the pass actually designed, and
926
- invent nothing the pass does not contain.
927
- - id: append_if
928
- condition: "{{be_in_scope}} == true"
929
- action: execute_script
930
- on_error: continue # a malformed extraction is refused LOUDLY but must not burn
931
- # the pass that already landed: the temp file survives for a
932
- # hand-run of log-append; if_error carries the refusal below
933
- script: { ref: actions }
934
- subcommand: log-append
935
- args: ["--feature-dir", "{{feature_dir}}", "--log", "interfaces", "--entries-file", "{{if_tmp}}"]
936
- outputs:
937
- - { name: if_ok, from: "$.ok" }
938
- - { name: if_error, from: "$.error" }
939
- - { name: if_appended, from: "$.appended" }
940
- - { name: if_ids_csv, from: "$.ids_csv" }
941
- - { name: if_unknown_csv, from: "$.unknown_ids_csv" }
942
- - { name: if_badsup_csv, from: "$.bad_supersedes_csv" }
943
-
944
- # 7c. THE TWO INTEGRITY SIGNALS log-append ALREADY RETURNS. Both were bound and neither
945
- # was said out loud — the idiom every other integrity check in these workflows uses
946
- # is a verify_context that continues, and these had none.
947
- # unknown_ids_csv an entry's `serves:` names an id the spec does not define,
948
- # so the BE→FE contract anchor points at nothing.
949
- # bad_supersedes_csv an entry supersedes one the log does not carry, so what it
950
- # claims to replace is not there — and readers take the LATEST
951
- # entry per interface, which is now ambiguous.
952
- # Announce-only: the entries are appended by the time this is visible, ids are
953
- # permanent, and an append-only log is corrected by a later entry, never by a rewrite.
954
- - id: if_unknown_notice
955
- condition: "{{if_unknown_csv}} != ''"
956
- action: verify_context
957
- require_value: if_unknown_csv
958
- equals: ""
959
- on_mismatch: continue
960
- on_mismatch_message: >
961
- interfaces.md now carries entries whose `serves:` names ids the spec does not define:
962
- {{if_unknown_csv}}. That is a broken trace anchor in the BE→FE contract — the frontend
963
- binds to these entries by id, and the requirement they claim to serve is not in
964
- {{spec_file}}. Either the id is a typo, or the requirement was never specified: fix it
965
- with a superseding entry (never by editing the log), or take it through /appweaver specify.
966
- - id: if_badsup_notice
967
- condition: "{{if_badsup_csv}} != ''"
968
- action: verify_context
969
- require_value: if_badsup_csv
970
- equals: ""
971
- on_mismatch: continue
972
- on_mismatch_message: >
973
- An entry appended to interfaces.md supersedes an id the log does not carry:
974
- {{if_badsup_csv}}. Nothing was replaced, so the interface it meant to retire is still
975
- the latest entry for its surface and both now read as current. Append a corrected
976
- superseding entry naming the real id.
977
-
978
- # ══════════════════════ THE UI DESIGN (payload-only + Read) ══════════════════
979
- # The UI lane's rules load here, AFTER the service pass has been written, so a
980
- # service author is never handed component and styling rules. The pass reads the
981
- # service story design of the SAME story where there is one: that document, not a
982
- # guess, is what this slice's screens bind to.
983
- - id: tech_select_fe
984
- condition: "{{fe_in_scope}} == true"
985
- action: include
986
- module: .appweaver/orchestrator/modules/tech-select.orch.yaml
987
- with: { command: aw-spec-plan-ui }
988
-
989
- - id: ctx_fe
990
- condition: "{{fe_in_scope}} == true"
991
- action: build_context
992
- vars:
993
- story_id: "{{story}}"
994
- story_title: "{{story_title}}"
995
- exercises_csv: "{{exercises_csv}}"
996
- constraints_csv: "{{constraints_csv}}"
997
- spec_current_version: "{{current_version}}"
998
- stale_note: "{{stale_note}}"
999
- pass_number: "{{fe_next_pass}}"
1000
- coverage_digest: "{{coverage_digest}}"
1001
- ui_source_files_csv: "{{fe_source_csv}}"
1002
- ui_source_file_count: "{{fe_source_count}}"
1003
- feature_plan_note: "{{fe_plan_note}}"
1004
- service_lane_note: "{{be_drop_msg}}"
1005
- guidance: "{{USER_INPUT}}"
1006
- context:
1007
- - { path: "{{story_path}}" } # the slice: exercises, Constraints, Acceptance
1008
- - { path: "{{spec_file}}" } # the contract — the only WHAT authority
1009
- - { path: "{{feature_dir}}/frontend-plan.md" } # the UI architecture this slice sits inside (READ-ONLY)
1010
- - { path: "{{fe_doc_path}}", optional: true } # earlier UI passes + manual edits (pass 2+)
1011
- - { path: "{{be_doc_path}}", optional: true } # this story's service design — what these screens bind to
1012
- - { path: "{{adr_path}}", optional: true } # ratified decisions — binding unless superseded
1013
- - { path: "{{if_path}}", optional: true } # the published surface this slice CONSUMES
1014
- - { path: "{{okf_concept}}", optional: true } # the feature's OKF concept (use-case log, source map)
1015
- - { path: .appweaver/memory/constitution.ui.md, optional: true }
1016
- - id: gen_fe
1017
- condition: "{{fe_in_scope}} == true"
1018
- action: agent_author
1019
- tools: Read # the current UI code arrives as {{ui_source_files_csv}} paths, not inlined
1020
- output_target: { type: file, path: "{{fe_pass_tmp}}" }
1021
- task: |
1022
- Write UI design pass {{pass_number}} for story {{story_id}} — {{story_title}}.
1023
- Output the PASS BODY ONLY: no "## Design pass" header (the script writes and
1024
- numbers it), no version numbers, no dates. Guidance: {{guidance}}
1025
-
1026
- SCOPE — the story's ids and NOTHING else: it delivers {{exercises_csv}} and
1027
- must satisfy {{constraints_csv}}, of which the UX- constraints are yours to
1028
- realise. Resolve every id's wording from spec.md in context. Design work
1029
- serving any other requirement is overreach — if touching a shared shell,
1030
- layout or design-system component is genuinely unavoidable, name it under
1031
- "### Risks & out of scope" with the reason; do not design it here.
1032
-
1033
- THIS IS THE UI LANE ONLY. Persistence, domain rules, service layering and
1034
- server-side validation belong to the service design and are written to a
1035
- separate document. Where this slice needs behaviour from the service, bind to
1036
- the PUBLISHED CONTRACT and stop there.
1037
- {{service_lane_note}}
1038
-
1039
- WHAT ALREADY EXISTS — the feature's code coverage, read from its OKF concept.
1040
- The FRONTEND list is yours; the BACKEND list is there so you bind to code that
1041
- exists rather than to an endpoint nobody wrote.
1042
-
1043
- {{coverage_digest}}
1044
-
1045
- AUTHORITY, per input channel — on a conflict, stop at the defect, never pick
1046
- silently:
1047
- - spec.md is the ONLY authority for WHAT. A contradiction between spec and
1048
- story wording is a defect: flag it in the pass and design to the spec.
1049
- - The CURRENT CODE is the authority for what exists. Read the UI files listed
1050
- here ({{ui_source_file_count}} on disk); an empty list means this slice has
1051
- no UI yet — say so in "### Current state" and design from a clean slate.
1052
- {{ui_source_files_csv}}
1053
- - frontend-plan.md is the FEATURE'S UI ARCHITECTURE this slice sits inside, and
1054
- it is BINDING: design within its navigation and screen structure (§1), its
1055
- client-state placement (§2), its component composition (§3) and its
1056
- cross-cutting decisions (§6). This pass reads it ONE WAY — it NEVER writes
1057
- back, and nothing here merges into it. Where the slice genuinely cannot fit
1058
- the plan, say so under "### Risks & out of scope" and stop at the defect; the
1059
- route is to change the spec and re-run /appweaver plan-ui, never to design
1060
- around the plan here.
1061
- {{feature_plan_note}}
1062
- - The constitution and loaded technical rules are STANDARDS — non-negotiable.
1063
- - adr.md entries are DECIDED — follow them, or propose a superseding ADR under
1064
- "### Proposed ADRs" with the trade-off; never silently deviate.
1065
- - interfaces.md is the PUBLISHED surface and this lane CONSUMES it: bind to the
1066
- latest entry per id and INVENT NOTHING. An interface this slice needs that
1067
- the log does not carry is a DEFECT, not a gap for you to fill — name it under
1068
- "### Risks & out of scope" with the shape you needed; the route is
1069
- /appweaver story-plan on the service lane, which publishes it. Where this
1070
- story's service design is in context, it is the fresher word on what this
1071
- slice's surface will be — bind to it and say which entry it supersedes.
1072
- {{stale_note}}
1073
-
1074
- STRUCTURE — exactly these subsections, in this order:
1075
- ### Scope — the ids and the story's acceptance, one line each,
1076
- resolved from the spec (cite ids; never restate spec
1077
- wording at length)
1078
- ### Current state — what the UI code has today that this slice touches
1079
- (file-level, from the coverage above; "no UI for this
1080
- slice yet" when the frontend list is empty)
1081
- ### Approach — routes and screens touched, where the state for this
1082
- slice lives and why that placement fits the plan, and
1083
- how the slice fits the existing composition
1084
- ### Screens & components — file-by-file: each component, route, store or style
1085
- file to create or modify and what changes in it (props,
1086
- state, events)
1087
- ### Contract binding — every published interface this slice calls, BY ITS
1088
- IF-nnn id, with the request it sends and the response
1089
- it renders; and for each one what the user sees while
1090
- it is in flight, when it is empty, and when it fails
1091
- ### Tests — what proves the acceptance criteria, named per
1092
- criterion (component, interaction and accessibility)
1093
- ### Proposed ADRs — ONLY a deliberate choice among rule-compliant
1094
- alternatives, a rejected alternative with the reason, or
1095
- a trade-off with its revisit trigger. Never restate what
1096
- code shows or a rule mandates. "None." when there is
1097
- nothing durable to record.
1098
- ### Risks & out of scope — divergences flagged above, contracts this slice needs
1099
- that are not published, unavoidable shared-shell
1100
- touches, what this slice deliberately leaves out
1101
-
1102
- This pass is EPHEMERAL scaffolding — it is archived with the story, never
1103
- merged anywhere. Be concrete and name real paths, components and props; a later
1104
- pass supersedes this one wholesale, so write nothing here that must outlive the
1105
- story (that is what the ADR and interface logs are for). Do not write the story
1106
- id into the body — the document's directory is its identity.
1107
-
1108
- - id: append_fe
1109
- condition: "{{fe_in_scope}} == true"
1110
- action: execute_script
1111
- script: { ref: actions }
1112
- subcommand: story-plan-doc
1113
- # --plan-hash stamps the CERTIFIED feature-plan hash into the pass header — the one
1114
- # analyze-design recorded, which the guard above has just proved is the plan on disk.
1115
- # A pass is written once and read forever, so without it implement could only ask
1116
- # "is the plan certified NOW", and a pass designed against plan H1 would still
1117
- # generate after the plan was edited to H2 and re-gated — the exact case law 40 names.
1118
- args: ["--feature-dir", "{{feature_dir}}", "--story", "{{story}}", "--lane", "frontend", "--pass-file", "{{fe_pass_tmp}}", "--spec-version", "{{current_version}}", "--plan-hash", "{{gate_fe_sha}}"]
1119
- outputs:
1120
- - { name: fe_pass_n, from: "$.pass" }
1121
- - { name: fe_created, from: "$.created" }
1122
-
1123
- # 9. Post-execution hooks.
1124
- - id: resolve_post_hooks
1125
- action: execute_script
1126
- script: { ref: actions }
1127
- subcommand: resolve-hooks
1128
- args: ["--phase", "after_story_plan"]
1129
- outputs:
1130
- - { name: after_hooks, from: "$.hooks" }
1131
- - id: dispatch_post_hooks
1132
- action: dispatch_hooks
1133
- hooks_from_variable: after_hooks
1134
-
1135
- outputs:
1136
- story: "{{story}}"
1137
- service_design: "{{be_doc_path}} (in scope: {{be_in_scope}}, pass {{be_pass_n}})"
1138
- ui_design: "{{fe_doc_path}} (in scope: {{fe_in_scope}}, pass {{fe_pass_n}})"
1139
- lanes_not_designed: "{{drops_note}}"
1140
- designed_against_spec: "{{current_version}}"
1141
- 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}})"
1142
- 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}}]"
1143
- 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"
1144
- stale_ids: "{{stale_csv}}"
1145
- interfaces_appended: "{{if_ids_csv}}"
1146
- 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)"
1147
- 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)."