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.
Files changed (30) hide show
  1. package/assets/.appweaver/STACK-PROFILES.md +1 -1
  2. package/assets/.appweaver/orchestrator/orchestrator.py +5 -0
  3. package/assets/.appweaver/orchestrator/rev-templates/domain-rule-bindings.skeleton.md +10 -3
  4. package/assets/.appweaver/orchestrator/rev-templates/tech-rule-bindings.skeleton.md +38 -11
  5. package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +2 -1
  6. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-design.orch.yaml +607 -123
  7. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +625 -66
  8. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement-ui.orch.yaml +407 -134
  9. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +452 -143
  10. package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +78 -14
  11. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan-ui.orch.yaml +597 -0
  12. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +688 -0
  13. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-feature.orch.yaml +3 -3
  14. package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +23 -23
  15. package/assets/.appweaver/orchestrator/workflows/aw-spec-story-plan.orch.yaml +945 -0
  16. package/assets/.appweaver/orchestrator/workflows/aw-spec-sync-stories.orch.yaml +1040 -0
  17. package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +2069 -28
  18. package/assets/.appweaver/scripts/bash/aw-xref.sh +2 -1
  19. package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +1973 -13
  20. package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +2 -1
  21. package/assets/.appweaver/scripts/python/aw-xref.py +246 -20
  22. package/assets/.appweaver/templates/design-template.md +88 -54
  23. package/assets/.appweaver/templates/spec-template.md +12 -10
  24. package/assets/.appweaver/templates/story-template.md +48 -8
  25. package/assets/.appweaver/templates/ui-design-template.md +71 -39
  26. package/assets/guardrails/projects/dotnet-default-project/domain-rule-bindings.md +19 -7
  27. package/assets/guardrails/projects/dotnet-default-project/tech-rule-bindings.md +35 -26
  28. package/package.json +1 -1
  29. package/assets/.appweaver/orchestrator/workflows/aw-spec-design-ui.orch.yaml +0 -386
  30. package/assets/.appweaver/orchestrator/workflows/aw-spec-design.orch.yaml +0 -382
@@ -1,27 +1,67 @@
1
1
  # =============================================================================
2
- # aw-spec-analyze-design (orchestrator) — the spec↔design conformance GATE
2
+ # aw-spec-analyze-design (orchestrator) — the spec ↔ FEATURE PLAN conformance GATE
3
3
  #
4
- # The single, discrete conformance gate on the forward path:
5
- # specify → design → analyze-design → implement.
6
- # spec.md is the ANCHOR. Confirm design.md STRICTLY follows the spec (+ the domain
7
- # gate overlays as a backstop); on drift, REPORT the gaps and ASK whether to repair,
8
- # then UPDATE design.md to conform (spec wins). Design AUTHORS; this stage VALIDATES.
4
+ # The discrete conformance gate on the forward path:
5
+ # specify / sync-spec → plan (+ plan-ui) → ANALYZE-DESIGN → sync-stories → story-plan
6
+ # spec.md is the ANCHOR. This stage proves that the feature's PLAN DOCUMENTS —
7
+ # specs/<Aggregate>/backend-plan.md and, where a frontend lane is declared,
8
+ # specs/<Aggregate>/frontend-plan.md — strictly follow it, in BOTH directions: every
9
+ # spec id has a design home, and every design element has a spec reason.
9
10
  #
10
- # BOOTSTRAP-ONLY, and it NEVER opens, merges or closes a change. It gates a feature
11
- # whose spec.md + design.md ARE the masters. With a change OPEN the delta docs are the
12
- # authoritative in-flight edit and the master pair must not be rewritten under them, so
13
- # the run STOPS and routes to /appweaver analyze-delta-design (the delta's own gate).
14
- # The change is closed by close-delta, the SINGLE cr-archive caller, and only after
15
- # finalize-delta has merged the delta into the masters. Also runnable on demand to
16
- # re-anchor a hand-edited spec — again, only when no change is open.
11
+ # LOAD-BEARING, not advisory. story-plan reads backend-plan.md as BINDING feature
12
+ # architecture and implement generates from the story pass beneath it, so a plan that
13
+ # drifted from the spec propagates into code through documents that never re-ask the
14
+ # question. This is the only stage that asks it — and it writes a machine-readable
15
+ # verdict (plan-analyze-report.json) beside the human report, so a pipeline can branch
16
+ # on the answer instead of parsing prose (§7 of Workflow-To-Build.md).
17
+ #
18
+ # ONE COMMAND, BOTH LANES — deliberately, unlike plan / plan-ui. Those split because
19
+ # each lane's AUTHORING rules are a different set (tech-rule-bindings gives each its
20
+ # own, and one command would hand the phase both lanes' whole rule surface). This stage
21
+ # loads no lane rule set at all: its checklist is the DOMAIN gate overlay, which is
22
+ # lane-neutral. And the question it exists to answer is a UNION question that neither
23
+ # lane can answer alone — a UX- has its only home in frontend-plan.md, a BR- only in
24
+ # backend-plan.md, and an FR- with a user-facing surface needs BOTH. Gating one lane at
25
+ # a time would either miss every UX- or report every UI-bearing FR- as uncovered in the
26
+ # service lane. There is no /appweaver analyze-design-ui, and there will not be one.
27
+ #
28
+ # IT DOES NOT AUTHOR, AND IT DOES NOT SCAFFOLD. Each feature plan has exactly ONE
29
+ # author (/appweaver plan, /appweaver plan-ui). This stage never creates the document
30
+ # it gates: an empty template conjured here would be measured against the spec, found
31
+ # wholly non-conformant, and then written by the gate — authoring under another name,
32
+ # with none of the author's lane rules, code reading or interface publication. A
33
+ # declared lane with no plan STOPS and routes to that lane's author.
34
+ #
35
+ # FRESHNESS FIRST. A plan is conformant to the spec version it DERIVES FROM; certifying
36
+ # one the spec has moved past is a false green, because it conforms to a contract that
37
+ # no longer holds. plan-check runs per lane and the run STOPS on `behind` /
38
+ # `edited_behind`, routing to plan / plan-ui — whose update mode conforms exactly the
39
+ # ids the ledger moved and appends the plan's own history row. `untracked` (a migrated
40
+ # legacy master with no baseline) is ADMITTED: the whole document is analysed against
41
+ # the whole spec, and the stamp establishes the baseline that was missing.
42
+ #
43
+ # REPAIR IS BOUNDED AND RECORDED. On approval the gate closes the gaps it found and
44
+ # nothing else — no new design, no version, no id. plan-stamp then appends the plan's
45
+ # own Change History row naming the ids and re-baselines the body hash, so the rewrite
46
+ # is IN the ledger and story-plan does not later read it as an unexplained hand edit.
47
+ # The verdict records the gaps AS FOUND alongside the final status, so a green earned
48
+ # by repair stays visible as one.
49
+ #
50
+ # ROUTES OUT, by what the gap actually is:
51
+ # • the plan lags what the ledger moved → /appweaver plan · /appweaver plan-ui
52
+ # • the plan is wrong and the spec is right → repair here, or hand-edit the plan and
53
+ # re-run plan with accept_manual: true
54
+ # • the SPEC is wrong → /appweaver sync-spec (never repaired here)
55
+ # • a gate-overlay constraint was never lifted into the spec (LIFT-FAILURE:)
56
+ # → /appweaver specify
17
57
  #
18
58
  # Launch: /appweaver analyze-design [strategy]
19
- # Prereq: /appweaver design (a spec.md + design.md for the active feature).
59
+ # Prereq: /appweaver plan (and /appweaver plan-ui where a frontend lane is declared).
20
60
  # =============================================================================
21
61
 
22
62
  name: aw-spec-analyze-design
23
- description: The spec↔design conformance gate — confirm design.md conforms to spec.md (spec is the anchor; domain overlays are a backstop) and, with approval, repair it. Bootstrap-only — with a change open it stops and routes to analyze-delta-design; it never closes a change.
24
- version: 2.1.0
63
+ description: The spec ↔ feature-plan conformance gate, both lanes in one pass — proves backend-plan.md and frontend-plan.md strictly follow spec.md in both directions (every spec id has a design home; every design element has a spec reason), with the domain gate overlays as a lift backstop. Checks each lane's derivation freshness first and refuses to certify a plan the spec has moved past, routing to plan / plan-ui. Never scaffolds the document it gates. On approval it repairs only the gaps it found and plan-stamp records the repair in the plan's own ledger. Writes a two-directional markdown report and a machine-readable verdict beside it for CI.
64
+ version: 3.0.0
25
65
  agent_agnostic: true
26
66
  dialect: aw-spec.orchestrator/v1
27
67
 
@@ -34,15 +74,84 @@ standards:
34
74
  - { path: .appweaver/ai-rules.md } # kit-resident: the universal operating rules, every flow
35
75
 
36
76
  vars:
77
+ # --- lane scope ---------------------------------------------------------
78
+ be_declared: "false"
79
+ fe_declared: "false"
80
+ be_in_scope: "false" # a backend lane is declared ⇒ backend-plan.md is gated
81
+ fe_in_scope: "false" # a frontend lane is declared ⇒ frontend-plan.md is gated
82
+ have_lane: "false" # at least one lane in scope — false ⇒ nothing to gate
83
+ # --- per-lane derivation tracking (plan-check) --------------------------
84
+ be_state: "" # fresh | untracked | clean | behind | edited | edited_behind
85
+ be_derived: ""
86
+ be_changed: ""
87
+ fe_state: ""
88
+ fe_derived: ""
89
+ fe_changed: ""
90
+ current_version: ""
91
+ # Freshness gates default FALSE so a probe that could not run fails CLOSED. The
92
+ # message vars carry a generic default for the same reason — an empty stop message
93
+ # would leave the user with a refusal and no route.
94
+ be_gate_ok: "false"
95
+ fe_gate_ok: "false"
96
+ be_gate_msg: "The backend plan's derivation check did not complete, so this gate cannot tell whether backend-plan.md is current with the spec. Re-run /appweaver analyze-design; if it keeps failing, run /appweaver plan to re-derive and re-baseline the plan."
97
+ fe_gate_msg: "The frontend plan's derivation check did not complete, so this gate cannot tell whether frontend-plan.md is current with the spec. Re-run /appweaver analyze-design; if it keeps failing, run /appweaver plan-ui to re-derive and re-baseline the plan."
98
+ # --- the analysis -------------------------------------------------------
37
99
  analyze_ok: "false"
100
+ analyze_gaps: ""
101
+ gap_count: "0" # gaps still open at the end of the loop
102
+ gap_refs: ""
103
+ be_needs_repair: "false"
104
+ fe_needs_repair: "false"
105
+ fe_repair_go: "false" # fe_needs_repair AND the frontend lane is in scope
106
+ found_recorded: "false" # the first pass's findings are the report's subject
107
+ found_gaps: ""
108
+ found_count: "0"
109
+ found_refs: ""
110
+ repaired: "false"
111
+ be_stamp_ok: "true" # vacuously true until a repair actually stamps
112
+ fe_stamp_ok: "true"
113
+ be_stamp_error: ""
114
+ fe_stamp_error: ""
115
+ report_path: ""
116
+ result_file: ""
117
+ # --- legacy delta guard -------------------------------------------------
38
118
  active_change: ""
39
119
  has_change: "false"
120
+ no_open_change: "true" # NOT has_change ⇒ the masters are authoritative ⇒ this gate applies
40
121
  version: ""
41
- no_open_change: "true" # NOT has_change ⇒ the masters are authoritative ⇒ this gate applies
42
122
 
43
123
  workflow:
44
124
 
45
- # 1. Resolve the current feature + its spec; require the spec (the anchor).
125
+ # 1. Which lanes exist. The DECLARATION is the question — not the lane-gate module,
126
+ # whose on-disk check would refuse a greenfield project that declared a root it
127
+ # has not scaffolded. A plan is gated as a document, before any code exists.
128
+ - id: lane_resolve
129
+ action: execute_script
130
+ on_error: continue # never crash raw on an undeclared lane — the gates below explain it
131
+ script: { ref: actions }
132
+ subcommand: lane-resolve
133
+ outputs:
134
+ - { name: lanes_declared, from: "$.declared" }
135
+ - { name: be_declared, from: "$.backend_declared" }
136
+ - { name: fe_declared, from: "$.frontend_declared" }
137
+
138
+ - id: be_scope
139
+ condition: "{{be_declared}} == true"
140
+ action: set
141
+ vars: { be_in_scope: "true", have_lane: "true" }
142
+ - id: fe_scope
143
+ condition: "{{fe_declared}} == true"
144
+ action: set
145
+ vars: { fe_in_scope: "true", have_lane: "true" }
146
+ - id: require_lane
147
+ action: verify_context
148
+ require_true: have_lane
149
+ on_false: stop
150
+ on_false_message: "No lane is declared in .appweaver/stacks.json, so there is no plan document to gate. Run /appweaver select-stack to declare the stack(s) and their source paths, then /appweaver plan (and /appweaver plan-ui where there is a UI) before re-running analyze-design."
151
+
152
+ # 2. Resolve the current feature and require its spec — the anchor everything is
153
+ # measured against. feature-paths reports both plan paths whether or not they
154
+ # exist; the freshness gates below decide what that absence means.
46
155
  - id: feature_paths
47
156
  action: execute_script
48
157
  on_error: continue # never crash raw on 'no feature set' — the gate below explains it
@@ -52,6 +161,8 @@ workflow:
52
161
  - { name: feature_dir, from: "$.feature_dir" }
53
162
  - { name: spec_file, from: "$.spec_file" }
54
163
  - { name: spec_exists, from: "$.spec_exists" }
164
+ - { name: be_plan, from: "$.backend_plan_file" }
165
+ - { name: fe_plan, from: "$.frontend_plan_file" }
55
166
 
56
167
  - id: require_feature
57
168
  action: verify_context
@@ -63,10 +174,51 @@ workflow:
63
174
  action: verify_context
64
175
  require_true: spec_exists
65
176
  on_false: stop
66
- on_false_message: "No spec.md for the current feature. Run /appweaver specify (and design) first."
177
+ on_false_message: "No spec.md for the current feature. Run /appweaver specify first — the spec is this gate's anchor, and there is nothing to measure a plan against without it."
178
+
179
+ - id: report_paths
180
+ action: set
181
+ vars: { report_path: "{{feature_dir}}/plan-analyze-report.md" }
182
+
183
+ # 2a. INVALIDATE any previous verdict, before anything downstream can stop. A gate
184
+ # that writes its result only on completion leaves the LAST run's verdict on disk
185
+ # for every run that aborts — a freshness stop, a refused stamp — and a pipeline
186
+ # reading that file would act on a certification this run never made. Every exit
187
+ # from here on therefore leaves at least a red; the real verdict overwrites it.
188
+ - id: gate_result_open
189
+ action: execute_script
190
+ # The lenient loader needs a flow sequence on ONE line.
191
+ args: ["--gate", "analyze-design", "--feature-dir", "{{feature_dir}}", "--ok", "false", "--report", "{{report_path}}", "--note", "analyze-design started and has not reached a verdict; a run that stops before certifying leaves this red on purpose"]
192
+ script: { ref: actions }
193
+ subcommand: gate-result
194
+ outputs:
195
+ - { name: result_file, from: "$.result_file" }
196
+
197
+ # 2b. The spec's own ledger tripwire, the same fence plan and plan-ui carry. This
198
+ # gate compares each plan's derived version against the spec's ledger, so a spec
199
+ # that moved OUTSIDE the ledger makes every one of those comparisons a lie: the
200
+ # plans would be certified current against a version the spec no longer is.
201
+ - id: spec_guard_read
202
+ action: execute_script
203
+ script: { ref: actions }
204
+ subcommand: spec-check
205
+ args: ["--feature-dir", "{{feature_dir}}"]
206
+ outputs:
207
+ - { name: spec_ok, from: "$.ok" }
208
+ - { name: spec_state, from: "$.state" }
209
+ - id: spec_guard
210
+ action: verify_context
211
+ require_true: spec_ok
212
+ on_false: stop
213
+ on_false_message: "spec.md has moved outside the ledger (state: {{spec_state}}), so its version no longer names its contents and no plan can be certified against it. Record what changed in a Change History row (or fold it back through /appweaver sync-spec), re-run spec-stamp to re-baseline, then re-run analyze-design."
67
214
 
68
- # 1b. Read the open change (opened by specify, or by design on a design-only change).
69
- # has_change drives the BOOTSTRAP-ONLY guard below — it closes nothing.
215
+ # 2c. LEGACY GUARD. Nothing in the current model opens a change — specify writes the
216
+ # spec directly and the delta family is retired (deletion scheduled). has_change
217
+ # true therefore means a repo left mid-flight by that retired chain: delta-spec /
218
+ # delta-design are still the authoritative in-flight edit, and repairing the
219
+ # master plans under them would drift the masters out from beneath the delta
220
+ # before finalize-delta merges it. Stop BEFORE any analysis so the wrong-stage run
221
+ # has zero side effects.
70
222
  - id: read_change
71
223
  action: execute_script
72
224
  script: { ref: actions }
@@ -77,11 +229,6 @@ workflow:
77
229
  - { name: has_change, from: "$.has_change" }
78
230
  - { name: version, from: "$.version" }
79
231
 
80
- # 1c. BOOTSTRAP-ONLY GUARD. A change open ⇒ delta-spec/delta-design are the
81
- # authoritative in-flight edit and this stage is the WRONG gate: its repair loop
82
- # rewrites the MASTER design.md, which would drift the masters out from under the
83
- # delta before finalize-delta merges it. Stop BEFORE any scaffold/authoring so the
84
- # wrong-stage run has zero side effects, and route to the delta's own gate.
85
232
  - id: flag_change_open
86
233
  condition: "{{has_change}} == true"
87
234
  action: set
@@ -91,22 +238,116 @@ workflow:
91
238
  action: verify_context
92
239
  require_true: no_open_change
93
240
  on_false: stop
94
- on_false_message: "Change {{active_change}} is OPEN on this feature — analyze-design is the BOOTSTRAP gate and must not rewrite the master design.md while a delta is in flight. Gate the delta instead: /appweaver analyze-delta-design → implement-delta → build → finalize-delta (merges the delta into spec.md + design.md) → analyze-slice → close-delta (archives the change + bumps the version). Re-run analyze-design once the change is closed, to re-anchor the masters."
241
+ on_false_message: "Change {{active_change}} is still OPEN on this feature — a leftover from the retired delta chain. This gate must not repair the master plans while a delta is in flight beneath them. Finish that change (/appweaver analyze-delta-design → implement-delta → build → finalize-delta → analyze-slice → close-delta) or abandon it, then re-run analyze-design to re-anchor the masters against the current spec."
95
242
 
96
- # 2. Ensure design.md exists (idempotent scaffold — returns the existing path, or
97
- # a fresh template the repair loop will author to conform to the spec).
98
- - id: design_scaffold
243
+ # 3. Per-lane derivation freshness. plan-check reports `fresh` when the document does
244
+ # not exist at all, which is how a declared-but-unplanned lane is caught here
245
+ # rather than by a scaffold this stage deliberately does not perform.
246
+ - id: be_track
99
247
  action: execute_script
248
+ condition: "{{be_in_scope}} == true"
249
+ on_error: continue # a probe that fails binds nothing; be_gate_ok stays false ⇒ fails CLOSED
100
250
  script: { ref: actions }
101
- subcommand: design-scaffold
102
- args: ["--feature-dir", "{{feature_dir}}"]
251
+ subcommand: plan-check
252
+ args: ["--doc", "{{be_plan}}", "--feature-dir", "{{feature_dir}}"]
253
+ outputs:
254
+ - { name: be_state, from: "$.state" }
255
+ - { name: be_derived, from: "$.derived_version" }
256
+ - { name: be_changed, from: "$.changed_csv" }
257
+ - { name: current_version, from: "$.current_version" }
258
+
259
+ - id: fe_track
260
+ action: execute_script
261
+ condition: "{{fe_in_scope}} == true"
262
+ on_error: continue # same fail-closed contract as the backend probe
263
+ script: { ref: actions }
264
+ subcommand: plan-check
265
+ args: ["--doc", "{{fe_plan}}", "--feature-dir", "{{feature_dir}}"]
103
266
  outputs:
104
- - { name: design_doc, from: "$.design_doc" }
267
+ - { name: fe_state, from: "$.state" }
268
+ - { name: fe_derived, from: "$.derived_version" }
269
+ - { name: fe_changed, from: "$.changed_csv" }
270
+ - { name: current_version, from: "$.current_version" }
271
+
272
+ # 3a. BACKEND freshness. clean / edited / untracked proceed; the rest stop. `edited`
273
+ # proceeds because a hand-edited plan is exactly what this gate exists to judge —
274
+ # it measures the document as it stands. `untracked` proceeds because a migrated
275
+ # legacy master has no baseline to be behind, and the whole-document analysis plus
276
+ # the stamp is what establishes one.
277
+ - id: be_gate_na
278
+ condition: "{{be_in_scope}} == false"
279
+ action: set
280
+ vars: { be_gate_ok: "true" }
281
+ - id: be_gate_clean
282
+ condition: "{{be_state}} == clean"
283
+ action: set
284
+ vars: { be_gate_ok: "true" }
285
+ - id: be_gate_edited
286
+ condition: "{{be_state}} == edited"
287
+ action: set
288
+ vars: { be_gate_ok: "true" }
289
+ - id: be_gate_untracked
290
+ condition: "{{be_state}} == untracked"
291
+ action: set
292
+ vars: { be_gate_ok: "true" }
293
+ - id: be_msg_fresh
294
+ condition: "{{be_state}} == fresh"
295
+ action: set
296
+ vars: { be_gate_msg: "A backend lane is declared but there is no backend-plan.md for this feature, so there is nothing to gate on the service lane — and this stage does not create the document it gates. Run /appweaver plan to author it from the spec, then re-run analyze-design." }
297
+ - id: be_msg_behind
298
+ condition: "{{be_state}} == behind"
299
+ action: set
300
+ vars: { be_gate_msg: "backend-plan.md derives from spec {{be_derived}} and the spec is now at {{current_version}} — certifying it would green-light a plan that conforms to a contract no longer in force. Requirements the ledger moved since: {{be_changed}}. Run /appweaver plan (update mode conforms exactly those ids and appends the plan's own history row), then re-run analyze-design." }
301
+ - id: be_msg_edited_behind
302
+ condition: "{{be_state}} == edited_behind"
303
+ action: set
304
+ vars: { be_gate_msg: "backend-plan.md was hand-edited AND the spec has moved past it (derived from {{be_derived}}, spec now {{current_version}}; ids moved: {{be_changed}}). Run /appweaver plan with accept_manual: true — it folds the manual edits in, conforms the moved ids and re-baselines — then re-run analyze-design." }
305
+ - id: require_be_current
306
+ action: verify_context
307
+ require_true: be_gate_ok
308
+ on_false: stop
309
+ on_false_message: "{{be_gate_msg}}"
310
+
311
+ # 3b. FRONTEND freshness — the same contract, routed to the frontend lane's author.
312
+ - id: fe_gate_na
313
+ condition: "{{fe_in_scope}} == false"
314
+ action: set
315
+ vars: { fe_gate_ok: "true" }
316
+ - id: fe_gate_clean
317
+ condition: "{{fe_state}} == clean"
318
+ action: set
319
+ vars: { fe_gate_ok: "true" }
320
+ - id: fe_gate_edited
321
+ condition: "{{fe_state}} == edited"
322
+ action: set
323
+ vars: { fe_gate_ok: "true" }
324
+ - id: fe_gate_untracked
325
+ condition: "{{fe_state}} == untracked"
326
+ action: set
327
+ vars: { fe_gate_ok: "true" }
328
+ - id: fe_msg_fresh
329
+ condition: "{{fe_state}} == fresh"
330
+ action: set
331
+ vars: { fe_gate_msg: "A frontend lane is declared but there is no frontend-plan.md for this feature. The spec's Experience Standards (UX-) have their ONLY design home there, so a green from this gate would claim a coverage this feature does not have. Run /appweaver plan-ui to author it, then re-run analyze-design." }
332
+ - id: fe_msg_behind
333
+ condition: "{{fe_state}} == behind"
334
+ action: set
335
+ vars: { fe_gate_msg: "frontend-plan.md derives from spec {{fe_derived}} and the spec is now at {{current_version}} — ids the ledger moved since: {{fe_changed}}. Run /appweaver plan-ui, then re-run analyze-design." }
336
+ - id: fe_msg_edited_behind
337
+ condition: "{{fe_state}} == edited_behind"
338
+ action: set
339
+ vars: { fe_gate_msg: "frontend-plan.md was hand-edited AND the spec has moved past it (derived from {{fe_derived}}, spec now {{current_version}}; ids moved: {{fe_changed}}). Run /appweaver plan-ui with accept_manual: true, then re-run analyze-design." }
340
+ - id: require_fe_current
341
+ action: verify_context
342
+ require_true: fe_gate_ok
343
+ on_false: stop
344
+ on_false_message: "{{fe_gate_msg}}"
105
345
 
106
- # 2b. Load the domain GATE overlays (the domain-rule-bindings `/aw-spec-gate` checklist) as
107
- # the conformance checklist — VALIDATION input only, NEVER authoring. The analyze
108
- # step sees them (include_standards: true); reconcile does NOT (design conforms to
109
- # the SPEC, not to the overlays). Empty/no-op for a non-domain project.
346
+ # 4. Load the domain GATE overlays (domain-rule-bindings `/aw-spec-gate`) as the
347
+ # conformance CHECKLIST — validation input only, never authoring. The analyze step
348
+ # sees them (include_standards: true); the repair steps do NOT, because a plan
349
+ # conforms to the SPEC and an overlay constraint absent from the spec is a lift
350
+ # failure to report, not a rule to design against. Empty for a non-domain project.
110
351
  - id: resolve_gate_overlays
111
352
  action: execute_script
112
353
  script: { ref: actions }
@@ -119,7 +360,8 @@ workflow:
119
360
  files_from_variable: gate_overlays
120
361
  optional: true
121
362
 
122
- # 3. Pre-execution hooks.
363
+ # 5. Pre-execution hooks. The phase keys stay `before_analyze` / `after_analyze` —
364
+ # they are configuration contract and outlive the rename.
123
365
  - id: resolve_pre_hooks
124
366
  action: execute_script
125
367
  script: { ref: actions }
@@ -131,120 +373,350 @@ workflow:
131
373
  action: dispatch_hooks
132
374
  hooks_from_variable: before_hooks
133
375
 
134
- # 4. Conformance + REPAIR loop (spec is the anchor): check → REPORT gaps + ASK →
135
- # Repair on approval → re-check; "Report and Skip" writes design-analyze-report.md
136
- # and stops. Bounded (3×).
376
+ # 6. The context both lanes are judged in. One envelope, because the coverage
377
+ # question spans them: a UX- lives only in the frontend plan, a BR- only in the
378
+ # backend plan, and an FR- with a user-facing surface needs a home in each.
379
+ # Both plans are `optional:` ONLY because a lane may be undeclared — for a
380
+ # declared lane the freshness gate above already proved the file exists, so an
381
+ # in-scope plan is never silently skipped here.
382
+ - id: ctx_analyze
383
+ action: build_context
384
+ context:
385
+ - { path: "{{spec_file}}" }
386
+ - { path: "{{be_plan}}", optional: true }
387
+ - { path: "{{fe_plan}}", optional: true }
388
+ - { path: "{{feature_dir}}/interfaces.md", optional: true } # the published surface both plans cite by IF- id
389
+
390
+ # 7. Conformance loop: analyze → REPORT gaps + ASK → repair the found gaps → re-check.
391
+ # Bounded (3x). "Report and Skip" leaves both plans untouched and stops at the
392
+ # report, which is the right answer whenever the fix belongs to another stage.
137
393
  - id: analyze_loop
138
394
  action: loop
139
395
  while: "{{analyze_ok}} != true"
140
396
  max_iterations: 3
141
397
  steps:
398
+ # A stale `true` from a previous iteration must never arm a write, so the
399
+ # frontend repair permission is rebuilt from scratch on every pass.
400
+ - id: fe_repair_reset
401
+ action: set
402
+ vars: { fe_repair_go: "false" }
403
+
142
404
  - id: analyze
143
405
  action: agent_query
144
- context: ["{{spec_file}}", "{{design_doc}}"]
145
- include_standards: true # the gate overlays (checklist) are in the standards
146
- schema: { ok: true, gaps: [ { severity: "high", spec_ref: "<FR/BR/CN/DC/IC/US/SC or overlay or —>", where: "<design section>", gap: "<how it drifts>" } ] }
147
- mock: { ok: true, gaps: [] }
406
+ include_standards: true # the domain gate overlays (the checklist) ride in the standards
407
+ schema: { ok: true, gap_count: 0, gap_refs: "<comma-separated spec ids the gaps touch>", backend_repair: false, frontend_repair: false, gaps: [ { severity: "high", lane: "backend|frontend|cross", spec_ref: "<FR/BR/CN/DC/IC/UX/SC id, or the overlay name, or —>", where: "<document + section>", gap: "<how it drifts>", route: "<the command that fixes it>" } ] }
408
+ mock: { ok: true, gap_count: 0, gap_refs: "", backend_repair: false, frontend_repair: false, gaps: [] }
148
409
  task: |
149
- spec.md is the ANCHOR. Check that design.md (in context) STRICTLY follows
150
- the spec (in context) and list EVERY drift as a gap:
151
- (1) every spec item — FR / Business Rule / Constraint (CN-) / Data
152
- Classification (DC-) / Integration Contract (IC-) / user story / success
153
- criterion — has a design home (the Traceability section is complete). A lifted
154
- CN-/DC-/IC- with no design row is a HIGH gap;
155
- (2) the DOMAIN model matches the spec EXACTLY — entities/value objects are
156
- the spec's Key Entities (same names), invariants are the spec's Business
157
- Rules, and the boundary is the spec's Aggregate Boundary;
158
- (3) NO design element exceeds or contradicts the spec — anything without a
159
- spec basis is a drift unless already logged as a DA-n assumption;
160
- (4) the layers are coherent (Application→Domain, API→Application, Infra
161
- implements the declared ports) and it stays high-level + technology-agnostic.
162
- (5) OVERLAY BACKSTOP — the domain gate checklist is loaded in the standards.
163
- For each constraint it states (retention, immutability, append-only audit,
164
- data-exposure, interop obligation, etc.), confirm design.md realises it via a
165
- spec CN-/DC-/IC-. If a checklist constraint is present in NEITHER the spec nor
166
- the design, it is a HIGH gap — set spec_ref to the overlay name and PREFIX the
167
- gap text with "LIFT-FAILURE:" (it was never lifted into the spec; the fix is
168
- /appweaver specify, not design — do not fabricate it in the design).
169
- (If design.md is still the empty template, treat the WHOLE spec as gaps.)
170
- Set ok=true ONLY when design.md fully conforms to the spec AND no checklist
171
- constraint is unaccounted for.
410
+ spec.md is the ANCHOR. Judge the feature's PLAN DOCUMENTS against it and list
411
+ EVERY drift as a gap. In scope this run: backend-plan.md = {{be_in_scope}},
412
+ frontend-plan.md = {{fe_in_scope}}. Judge ONLY the plans in scope; never
413
+ report a gap against a lane that is not declared.
414
+
415
+ SECTION NUMBERS ARE NOT STABLE, so identify every section by its ROLE and by
416
+ the heading actually present in the document in front of you. A brownfield
417
+ project's plans come from the code-derived template /appweaver onboard
418
+ installs, whose layout differs from the greenfield one (its traceability,
419
+ assumptions and deviations sections carry different numbers, and its
420
+ component sections are named after the project's own structural axes). Any
421
+ "§n" below is the GREENFIELD number, given only to say WHICH section is
422
+ meant. Put the heading you actually found in `where`, and never report a
423
+ missing section on the strength of a number.
424
+
425
+ (1) FORWARD — COVERAGE. Every spec item has a design home, in the RIGHT lane:
426
+ • FR- (requirement) — a home in backend-plan.md; and ALSO a screen/
427
+ component/state in frontend-plan.md when it has a user-facing surface.
428
+ • BR- (business rule) — backend-plan.md only, as a domain invariant.
429
+ • CN- (constraint) — wherever it binds. A CN- that binds both lanes needs
430
+ a home in BOTH; one that binds a single lane needs only that one.
431
+ • DC- (data classification) — backend enforcement, plus what the frontend
432
+ withholds from the client wherever a client sees the data.
433
+ • IC- (integration contract) — the backend adapter/port that realises it;
434
+ the frontend binds to it through interfaces.md, never directly.
435
+ • UX- (experience standard) — frontend-plan.md ONLY. backend-plan.md does
436
+ not carry them, so a UX- with no frontend row is a DROPPED requirement
437
+ and is always a HIGH gap.
438
+ • SC- (success criterion) — wherever it is met and how it is verified.
439
+ A lifted CN-/DC-/IC- with no row in the relevant Traceability section is a
440
+ HIGH gap: those sections are the two-way guarantee and must be complete.
441
+ Edge Cases carry no ids by design — do not invent one for them.
442
+
443
+ (2) THE DOMAIN MODEL MATCHES THE SPEC EXACTLY (backend). Entities and value
444
+ objects are the spec's Key Entities under the same names, invariants are
445
+ the spec's Business Rules, and the boundary is the spec's Aggregate
446
+ Boundary. A renamed or invented entity is a HIGH gap.
447
+
448
+ (3) BACKWARD — JUSTIFICATION. No design element exceeds or contradicts the
449
+ spec. Anything without a spec basis is a drift UNLESS it is already logged
450
+ as a DA-n assumption or a WA-n workaround in that plan's own ledgers — a
451
+ logged item with a stated basis (including "per Const. §<n>" for standing
452
+ technical machinery the constitution mandates) is accounted for, not a gap.
453
+
454
+ (4) CROSS-LANE — the checks neither lane can make alone. Report these with
455
+ lane "cross":
456
+ • an FR- with a user-facing surface designed in one lane only;
457
+ • a CN-/DC-/IC- that binds both lanes and is honoured in only one;
458
+ • the frontend's contract-binding section (greenfield §5) citing an IF-
459
+ id that interfaces.md does not publish, or an operation it needs that
460
+ has no IF- entry and is not already raised as a contract gap;
461
+ • either plan RESTATING the wording of a published interface instead of
462
+ citing its IF- id — the spec owns requirement wording and interfaces.md
463
+ owns contract wording; a second copy is drift by construction;
464
+ • the two plans contradicting each other on the same behaviour.
465
+
466
+ (5) COHERENCE. The backend layers hold (Application→Domain, API→Application,
467
+ Infrastructure implements the declared ports); the frontend's state,
468
+ components and flows are consistent with its own navigation. Both stay
469
+ high-level and technology-agnostic.
470
+
471
+ (6) OVERLAY BACKSTOP. The domain gate checklist is loaded in the standards.
472
+ For each constraint it states (retention, immutability, append-only audit,
473
+ data exposure, interop obligation …), confirm a plan realises it VIA a
474
+ spec CN-/DC-/IC-. If a checklist constraint is in NEITHER the spec nor a
475
+ plan, it is a HIGH gap: set spec_ref to the overlay name and PREFIX the
476
+ gap text with "LIFT-FAILURE:". It was never lifted into the spec, so the
477
+ fix is /appweaver specify — never fabricate it into a plan.
478
+
479
+ For each gap set `route` to the command that actually fixes it: repair here
480
+ when the plan is wrong and the spec is right; /appweaver sync-spec when the
481
+ SPEC is what is wrong; /appweaver specify for a LIFT-FAILURE; /appweaver plan
482
+ or /appweaver plan-ui when the plan simply lags the spec.
483
+
484
+ Set backend_repair true only if at least one gap can be closed by rewriting
485
+ backend-plan.md, and frontend_repair likewise for frontend-plan.md — false for
486
+ a lane that is not in scope, and false when every gap on that lane routes to
487
+ another command. Set gap_count to the number of gaps and gap_refs to their
488
+ spec ids, comma-separated. Set ok=true ONLY when every plan in scope fully
489
+ conforms to the spec AND no checklist constraint is unaccounted for.
172
490
  outputs:
173
- - { name: analyze_ok, from: "$.ok" }
174
- - { name: analyze_gaps, from: "$.gaps" }
491
+ - { name: analyze_ok, from: "$.ok" }
492
+ - { name: analyze_gaps, from: "$.gaps" }
493
+ - { name: gap_count, from: "$.gap_count" }
494
+ - { name: gap_refs, from: "$.gap_refs" }
495
+ - { name: be_needs_repair, from: "$.backend_repair" }
496
+ - { name: fe_needs_repair, from: "$.frontend_repair" }
497
+
498
+ # The report's subject is the state the gate FOUND, not what survived repair —
499
+ # a verdict that only ever describes the repaired document cannot show that a
500
+ # repair happened, and a green earned by repair must stay visible as one.
501
+ - id: record_found
502
+ condition: "{{found_recorded}} == false"
503
+ action: set
504
+ vars:
505
+ found_gaps: "{{analyze_gaps}}"
506
+ found_count: "{{gap_count}}"
507
+ found_refs: "{{gap_refs}}"
508
+ found_recorded: "true"
509
+
175
510
  - id: conform_break
176
511
  action: break
177
512
  condition: "{{analyze_ok}} == true"
513
+
178
514
  - id: ask_repair
179
515
  action: ask
180
516
  prompt: |
181
- Analyze found spec↔design gaps (spec.md is the anchor):
517
+ Spec ↔ plan gaps found (spec.md is the anchor; {{gap_count}} gap(s) on {{gap_refs}}):
182
518
  {{analyze_gaps}}
183
- Repair design.md to conform to the spec now, or report the gaps and stop?
519
+ Repair the plan document(s) to conform now, or write the report and stop?
520
+ Repair closes only these gaps and records the rewrite in the plan's own
521
+ Change History. Choose Report and Skip whenever the fix belongs elsewhere —
522
+ a wrong SPEC goes through /appweaver sync-spec, a LIFT-FAILURE through
523
+ /appweaver specify.
184
524
  options: ["Repair", "Report and Skip"]
185
525
  answer_var: repair_choice
186
- - id: write_report
526
+
527
+ - id: skip_break
528
+ action: break
187
529
  condition: "{{repair_choice}} == Report and Skip"
530
+
531
+ - id: mark_repaired
532
+ action: set
533
+ vars: { repaired: "true" }
534
+
535
+ # The frontend lane's write permission is the AND of "the model found something
536
+ # to fix there" and "that lane is in scope" — the evaluator takes single
537
+ # comparisons only, so the second half arrives as the assigned value.
538
+ - id: fe_repair_arm
539
+ condition: "{{fe_needs_repair}} == true"
540
+ action: set
541
+ vars: { fe_repair_go: "{{fe_in_scope}}" }
542
+
543
+ - id: reconcile_be
544
+ condition: "{{be_needs_repair}} == true"
188
545
  action: agent_author
189
- context: ["{{design_doc}}"]
190
- include_standards: false
191
- output_target: { type: file, path: "{{feature_dir}}/design-analyze-report.md" }
546
+ include_standards: false # rewrite-in-place: the SPEC anchors, not the overlays
547
+ output_target: { type: file, path: "{{be_plan}}" }
192
548
  task: |
193
- Write a Design Analyze Report to the output from these spec↔design
194
- conformance gaps (spec.md is the ANCHOR; the user chose to skip repair):
549
+ spec.md is the ANCHOR — update backend-plan.md (in context) to CONFORM to it,
550
+ closing the gaps below whose lane is "backend" or "cross" AND whose route is
551
+ this repair. The spec WINS every time.
195
552
  {{analyze_gaps}}
196
- Markdown format:
197
- - "# Design Analyze Report — <aggregate>" plus today's date.
198
- - A verdict line: "verdict: GAPS ✗ · <n> gap(s)".
199
- - "## Gaps" as a table: | # | severity | spec ref | design location | gap |.
200
- - "## How to resolve": the spec is the anchor — update design.md to follow
201
- the spec for each gap (out of band), then re-run /appweaver analyze-design
202
- to clear the report.
203
- Return the complete report markdown only.
204
- - id: skip_break
205
- action: break
206
- condition: "{{repair_choice}} == Report and Skip"
207
- - id: reconcile
553
+ - SECTION NUMBERS ARE NOT STABLE: this document may be the greenfield
554
+ four-layer template or the code-derived one /appweaver onboard installs,
555
+ whose layout differs. Work from the headings actually in the document; the
556
+ "§n" below name the GREENFIELD section so you know which one is meant.
557
+ NEVER renumber or reorder what is there.
558
+ - Give each uncovered spec FR-/BR-/CN-/DC-/IC-/SC- a design home: a lifted
559
+ CN-/DC-/IC- lands in a domain invariant (§1), the persistence component
560
+ (§4.1), an external-integrations adapter row (§4.2), or a cross-cutting
561
+ row (§5).
562
+ - Realign the domain model to the spec: entity and value-object names = the
563
+ spec's Key Entities, invariants = the spec's Business Rules, boundary = the
564
+ spec's Aggregate Boundary.
565
+ - REMOVE, or move to a DA-n assumption, any design element with no spec basis.
566
+ EXCEPTION: do NOT remove a cross-cutting element the technical constitution
567
+ mandates — standing technical or infrastructure machinery rather than
568
+ invented domain logic. MOVE it to a DA-n citing the mandating principle
569
+ ("per Const. §<n>"); removing it could violate the constitution. Reserve
570
+ REMOVE for genuine over-reach — invented domain entities, rules or behaviour
571
+ the spec omits.
572
+ - The published-surface section (§3) is an INDEX into interfaces.md: cite IF-
573
+ ids, never restate a published contract's wording. If a gap says an operation
574
+ has no IF- entry, note it there as a contract gap to publish — do not invent
575
+ an IF- id, and never edit interfaces.md from here; /appweaver plan is its
576
+ only publisher.
577
+ - A "LIFT-FAILURE:" gap CANNOT be closed here: the constraint is not in the
578
+ spec and you must not invent a domain rule the spec omits. Leave it open and
579
+ add a DA-n reading "lift-failure: <constraint> — re-run /appweaver specify to
580
+ lift it into CN-/DC-/IC-".
581
+ - Leave every gap routed to /appweaver sync-spec or /appweaver specify OPEN.
582
+ Fixing the plan to match a wrong spec buries the defect in the document that
583
+ is supposed to reveal it.
584
+ - Keep the Traceability section (§6) in sync with what you changed. Preserve
585
+ every already-conformant section VERBATIM, and preserve the Assumptions (§7),
586
+ Workarounds/Deviations (§8) and Change History (§9) sections as LEDGERS —
587
+ append to them, never rewrite, renumber or drop them. Dropping the Change
588
+ History section is refused downstream and stops the run. Write NO version and
589
+ NO history row: plan-stamp does both.
590
+ - Do NOT change spec.md, frontend-plan.md, interfaces.md or adr.md.
591
+ Return the COMPLETE updated backend-plan.md content only.
592
+
593
+ # plan-stamp is the plan's only version authority. Repairing without stamping
594
+ # would leave the document reading as an unexplained hand edit to story-plan and
595
+ # to the next /appweaver plan, and the rewrite would be absent from its ledger.
596
+ - id: stamp_be
597
+ condition: "{{be_needs_repair}} == true"
598
+ action: execute_script
599
+ script: { ref: actions }
600
+ subcommand: plan-stamp
601
+ args: ["--doc", "{{be_plan}}", "--feature-dir", "{{feature_dir}}", "--ids", "{{gap_refs}}", "--note", "Conformance repair (analyze-design)"]
602
+ outputs:
603
+ - { name: be_stamp_ok, from: "$.ok" }
604
+ - { name: be_stamp_error, from: "$.error" }
605
+
606
+ - id: reconcile_fe
607
+ condition: "{{fe_repair_go}} == true"
208
608
  action: agent_author
209
- context: ["{{spec_file}}", "{{design_doc}}"]
210
- include_standards: false # rewrite-in-place: spec anchors — NOT the overlays
211
- output_target: { type: file, path: "{{design_doc}}" }
609
+ include_standards: false
610
+ output_target: { type: file, path: "{{fe_plan}}" }
212
611
  task: |
213
- spec.md is the ANCHOR — update design.md (in context) to CONFORM to it,
214
- closing these gaps (the spec WINS every time):
612
+ spec.md is the ANCHOR — update frontend-plan.md (in context) to CONFORM to it,
613
+ closing the gaps below whose lane is "frontend" or "cross" AND whose route is
614
+ this repair. The spec WINS every time.
215
615
  {{analyze_gaps}}
216
- - Add a design home for each uncovered spec FR/BR/CN/DC/IC/US/SC (a lifted
217
- CN-/DC-/IC- lands in a §1 domain invariant · the persistence component or an
218
- External Integrations table adapter row · a Cross-Cutting Concerns row).
219
- - Realign the DOMAIN model to the spec: entity/value-object names = the
220
- spec's Key Entities, invariants = the spec's Business Rules, boundary =
221
- the spec's Aggregate Boundary.
222
- - REMOVE, or move to a DA-n assumption, any design element with no spec
223
- basis — never keep design that exceeds or contradicts the spec. EXCEPTION:
224
- do NOT remove a cross-cutting element that is (or may be) mandated by the
225
- technical constitution — it cites a principle as its basis ("per Const.
226
- §<n>"), or it is standing technical/infrastructure machinery rather than
227
- invented domain logic. MOVE such an element to a DA-n citing the mandating
228
- principle; removing it could violate the constitution. Reserve REMOVE for
229
- genuine over-reach — invented domain entities/rules/behaviour the spec omits.
230
- - A "LIFT-FAILURE:" gap you CANNOT close — the constraint is not in the spec,
231
- and you must NOT invent a domain rule the spec omits. Leave it open and add a
232
- DA-n note "lift-failure: <constraint> — re-run /appweaver specify to lift
233
- it into CN-/DC-/IC-". (The gate will stop and direct the user to specify.)
234
- - Keep the Traceability section in sync. Preserve every already-conformant section
235
- VERBATIM. Do NOT change the spec.
236
- Return the COMPLETE updated design.md content only.
616
+ - Give every uncovered UX- a home — this document is their ONLY home — and
617
+ every FR- with a user-facing surface a screen, component or state.
618
+ - Honour each CN-/DC- that binds this lane: a DC- says what the client is not
619
+ shown, a CN- a bound the experience must meet.
620
+ - SECTION NUMBERS ARE NOT STABLE — work from the headings actually in the
621
+ document; the "§n" here name the greenfield sections so you know which is
622
+ meant, and nothing is renumbered or reordered.
623
+ - The Contract Binding section (§5) cites interfaces.md by IF- id and never
624
+ restates a contract's wording. An operation with no published entry keeps the
625
+ "(unpublished)" marker and is listed under contract gaps to raise — this
626
+ lane never writes interfaces.md; /appweaver plan publishes it.
627
+ - REMOVE, or move to a DA-n, any element with no spec basis, except what
628
+ constitution.ui.md mandates — that moves to a DA-n citing the basis.
629
+ - Leave every gap routed to /appweaver sync-spec or /appweaver specify OPEN.
630
+ - Keep the Traceability section (§7) in sync. Preserve every already-conformant
631
+ section VERBATIM, and preserve the Assumptions (§8), Workarounds (§9) and
632
+ Change History sections as LEDGERS — append, never rewrite, renumber or drop.
633
+ Dropping the Change History section is refused downstream and stops the run.
634
+ Write NO version and NO history row: plan-stamp does both.
635
+ - Do NOT change spec.md, backend-plan.md, interfaces.md or adr.md.
636
+ Return the COMPLETE updated frontend-plan.md content only.
637
+
638
+ - id: stamp_fe
639
+ condition: "{{fe_repair_go}} == true"
640
+ action: execute_script
641
+ script: { ref: actions }
642
+ subcommand: plan-stamp
643
+ args: ["--doc", "{{fe_plan}}", "--feature-dir", "{{feature_dir}}", "--ids", "{{gap_refs}}", "--note", "Conformance repair (analyze-design)"]
644
+ outputs:
645
+ - { name: fe_stamp_ok, from: "$.ok" }
646
+ - { name: fe_stamp_error, from: "$.error" }
647
+
648
+ # 7b. The ledger-survival fence. plan-stamp refuses to baseline a document whose
649
+ # Change History section went missing, so ok=false here means a repair landed on
650
+ # disk having dropped the plan's ledger. That is the one loss this model never
651
+ # absorbs silently — stop on it, before any verdict is written.
652
+ - id: be_stamp_fence
653
+ action: verify_context
654
+ require_true: be_stamp_ok
655
+ on_false: stop
656
+ on_false_message: "The repair to backend-plan.md was written but NOT baselined: {{be_stamp_error}}. The rewrite dropped a section plan-stamp refuses to stamp over (its Change History ledger). Restore that section in backend-plan.md — git diff shows what the repair removed — then re-run analyze-design."
657
+ - id: fe_stamp_fence
658
+ action: verify_context
659
+ require_true: fe_stamp_ok
660
+ on_false: stop
661
+ on_false_message: "The repair to frontend-plan.md was written but NOT baselined: {{fe_stamp_error}}. The rewrite dropped a section plan-stamp refuses to stamp over (its Change History ledger). Restore that section in frontend-plan.md — git diff shows what the repair removed — then re-run analyze-design."
662
+
663
+ # 8. The human report — written on EVERY run, green or red. It is the two-directional
664
+ # coverage record, and a gate that leaves nothing behind when it passes gives a
665
+ # reviewer no evidence that it ran at all.
666
+ - id: write_report
667
+ action: agent_author
668
+ include_standards: false
669
+ output_target: { type: file, path: "{{report_path}}" }
670
+ task: |
671
+ Write the Plan Analyze Report for this feature to the output.
672
+ Verdict: conformant={{analyze_ok}}. Gaps as FOUND: {{found_count}} on
673
+ {{found_refs}}. Gaps still open: {{gap_count}}. Repair was run: {{repaired}}.
674
+ Lanes gated: backend={{be_in_scope}} (tracking {{be_state}}, derived from spec
675
+ {{be_derived}}), frontend={{fe_in_scope}} (tracking {{fe_state}}, derived from
676
+ spec {{fe_derived}}). Spec version: {{current_version}}.
677
+ The gaps as found:
678
+ {{found_gaps}}
679
+ Markdown format:
680
+ - "# Plan Analyze Report — <aggregate>", then a line naming the spec version and
681
+ each gated plan with its tracking state.
682
+ - A verdict line: "verdict: CONFORMS ✓" or "verdict: GAPS ✗ · <n> open".
683
+ When repair ran, say so and give both numbers — found and still open.
684
+ - "## Coverage (spec → plan)": a table | spec id | type | lane(s) it needs |
685
+ design home | status |, one row per spec id you can see in the spec, marking
686
+ each covered or UNCOVERED. This is the forward half of the two-way guarantee
687
+ and it is the reason this report exists — write it even on a green run.
688
+ - "## Gaps": a table | # | severity | lane | spec ref | where | gap | route |.
689
+ Omit the section when there were none.
690
+ - "## How to resolve": group by route and give the exact command for each — the
691
+ spec is the anchor, so a wrong plan is repaired here or via /appweaver plan
692
+ with accept_manual, a wrong SPEC goes through /appweaver sync-spec, and a
693
+ "LIFT-FAILURE:" goes through /appweaver specify. Omit on a green run.
694
+ Do not invent ids, versions or dates beyond the values given above.
695
+ Return the complete report markdown only.
696
+
697
+ # 9. The machine-readable verdict, written beside the report by the SCRIPT — CI
698
+ # cannot branch on prose, and a verdict is exactly the kind of dated, derived
699
+ # record the model never writes. `ok` from this step means the file was written;
700
+ # the pass/fail verdict is `status` inside it.
701
+ - id: gate_result
702
+ action: execute_script
703
+ script: { ref: actions }
704
+ subcommand: gate-result
705
+ # The lenient loader needs a flow sequence on ONE line.
706
+ args: ["--gate", "analyze-design", "--feature-dir", "{{feature_dir}}", "--ok", "{{analyze_ok}}", "--spec-version", "{{current_version}}", "--report", "{{report_path}}", "--gap-count", "{{found_count}}", "--gap-refs", "{{found_refs}}", "--field", "gaps_open={{gap_count}}", "--field", "repaired={{repaired}}", "--field", "backend_plan={{be_plan}}", "--field", "backend_in_scope={{be_in_scope}}", "--field", "backend_state={{be_state}}", "--field", "backend_derived={{be_derived}}", "--field", "frontend_plan={{fe_plan}}", "--field", "frontend_in_scope={{fe_in_scope}}", "--field", "frontend_state={{fe_state}}", "--field", "frontend_derived={{fe_derived}}"]
707
+ outputs:
708
+ - { name: result_file, from: "$.result_file" }
237
709
 
238
710
  - id: analyze_gate
239
711
  action: verify_context
240
712
  require_true: analyze_ok
241
713
  on_false: stop
242
- on_false_message: "design.md does not conform to the spec (gaps: {{analyze_gaps}}) — repair was skipped (see design-analyze-report.md), or it could not be reconciled in 3 passes. A 'LIFT-FAILURE:' gap means a domain constraint from the gate checklist was never lifted into the spec — re-run /appweaver specify to add it (CN-/DC-/IC-). Otherwise fix out of band, or re-run and choose Repair; resolve an ambiguous spec via /appweaver specify."
714
+ on_false_message: "The feature plans do not conform to spec {{current_version}} — {{gap_count}} gap(s) open on {{gap_refs}}. The full two-directional report is at {{report_path}} and the machine-readable verdict at {{result_file}}. Repair was skipped, or the gaps could not be closed in 3 passes. Route by gap: a wrong PLAN is repaired here or by hand-editing it and re-running /appweaver plan (or plan-ui) with accept_manual: true; a wrong SPEC goes through /appweaver sync-spec; a 'LIFT-FAILURE:' gap means a domain constraint from the gate checklist was never lifted into the spec, so re-run /appweaver specify to add it as a CN-/DC-/IC-."
243
715
 
244
- # (No change-close here by design — this stage only ever runs with NO change open,
245
- # and cr-archive belongs to close-delta, which runs after finalize-delta's merge.)
716
+ # (No version is written here by design. This gate proves conformance; close-story is
717
+ # the version authority downstream, and plan-stamp the plans' own.)
246
718
 
247
- # 5. Post-execution hooks.
719
+ # 10. Post-execution hooks.
248
720
  - id: resolve_post_hooks
249
721
  action: execute_script
250
722
  script: { ref: actions }
@@ -258,8 +730,20 @@ workflow:
258
730
 
259
731
  outputs:
260
732
  feature_dir: "{{feature_dir}}"
261
- design_doc: "{{design_doc}}"
733
+ spec_version: "{{current_version}}"
734
+ backend_plan: "{{be_plan}}"
735
+ backend_in_scope: "{{be_in_scope}}"
736
+ backend_state: "{{be_state}}"
737
+ backend_derived: "{{be_derived}}"
738
+ frontend_plan: "{{fe_plan}}"
739
+ frontend_in_scope: "{{fe_in_scope}}"
740
+ frontend_state: "{{fe_state}}"
741
+ frontend_derived: "{{fe_derived}}"
262
742
  conformant: "{{analyze_ok}}"
743
+ gaps_found: "{{found_count}}"
744
+ gaps_open: "{{gap_count}}"
745
+ repaired: "{{repaired}}"
746
+ report: "{{report_path}}"
747
+ result_json: "{{result_file}}"
263
748
  gaps: "{{analyze_gaps}}"
264
- version: "{{version}}"
265
- next: "/appweaver implement (design conforms to the spec; version unchanged — this gate closes nothing)"
749
+ next: "Both lanes' plans conform to spec {{current_version}} — the verdict is at {{result_file}} for CI and the coverage report at {{report_path}}. Next: /appweaver sync-stories to plan the backlog against this spec version, then /appweaver story-plan <story-id> for the slice, which reads backend-plan.md as binding feature architecture. Re-run this gate after EVERY /appweaver plan and /appweaver plan-ui — it is what keeps that binding honest. NOTE repaired=true means this gate rewrote a plan to close its own findings and plan-stamp recorded the rewrite in that plan's Change History; gaps_found is what it found before repairing, so review the diff rather than reading the green alone."