aw-spec-cli 0.1.14 → 0.1.16

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 (32) hide show
  1. package/assets/.appweaver/STACK-PROFILES.md +1 -1
  2. package/assets/.appweaver/orchestrator/modules/spec-state-gate.orch.yaml +87 -0
  3. package/assets/.appweaver/orchestrator/orchestrator.py +5 -0
  4. package/assets/.appweaver/orchestrator/rev-templates/domain-rule-bindings.skeleton.md +10 -3
  5. package/assets/.appweaver/orchestrator/rev-templates/tech-rule-bindings.skeleton.md +38 -11
  6. package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +2 -1
  7. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-design.orch.yaml +672 -124
  8. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +700 -72
  9. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement-ui.orch.yaml +531 -135
  10. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +573 -145
  11. package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +78 -14
  12. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan-ui.orch.yaml +600 -0
  13. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +721 -0
  14. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-feature.orch.yaml +3 -3
  15. package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +119 -32
  16. package/assets/.appweaver/orchestrator/workflows/aw-spec-story-plan.orch.yaml +1147 -0
  17. package/assets/.appweaver/orchestrator/workflows/aw-spec-sync-stories.orch.yaml +1121 -0
  18. package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +2564 -38
  19. package/assets/.appweaver/scripts/bash/aw-xref.sh +2 -1
  20. package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +2458 -22
  21. package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +2 -1
  22. package/assets/.appweaver/scripts/python/aw-payload-parity.py +158 -0
  23. package/assets/.appweaver/scripts/python/aw-xref.py +431 -48
  24. package/assets/.appweaver/templates/design-template.md +88 -54
  25. package/assets/.appweaver/templates/spec-template.md +12 -10
  26. package/assets/.appweaver/templates/story-template.md +83 -12
  27. package/assets/.appweaver/templates/ui-design-template.md +71 -39
  28. package/assets/guardrails/projects/dotnet-default-project/domain-rule-bindings.md +19 -7
  29. package/assets/guardrails/projects/dotnet-default-project/tech-rule-bindings.md +35 -26
  30. package/package.json +1 -1
  31. package/assets/.appweaver/orchestrator/workflows/aw-spec-design-ui.orch.yaml +0 -386
  32. 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 (⌛ not yet built — until it is, fold the change back by hand under .appweaver/SPEC-EDIT-PROTOCOL.md) (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,91 @@ 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
+ # The plan shapes this run certifies, re-read after any repair (step 8b). Empty means the
91
+ # lane was not in scope, which every reader treats as "not certified", never as a mismatch.
92
+ be_final_sha: ""
93
+ fe_final_sha: ""
94
+ current_version: ""
95
+ # Freshness gates default FALSE so a probe that could not run fails CLOSED. The
96
+ # message vars carry a generic default for the same reason — an empty stop message
97
+ # would leave the user with a refusal and no route.
98
+ be_gate_ok: "false"
99
+ fe_gate_ok: "false"
100
+ 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."
101
+ 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."
102
+ # --- the analysis -------------------------------------------------------
37
103
  analyze_ok: "false"
104
+ analyze_gaps: ""
105
+ gap_count: "0" # gaps still open at the end of the loop
106
+ gap_refs: ""
107
+ be_needs_repair: "false"
108
+ fe_needs_repair: "false"
109
+ fe_repair_go: "false" # fe_needs_repair AND the frontend lane is in scope
110
+ found_recorded: "false" # the first pass's findings are the report's subject
111
+ found_gaps: ""
112
+ found_count: "0"
113
+ found_refs: ""
114
+ repaired: "false"
115
+ # The ids a repair pass actually rewrote — the model's own declaration, and NOT the same
116
+ # set as gap_refs (which holds every finding, including the ones left deliberately open).
117
+ repaired_refs: ""
118
+ be_stamp_ok: "true" # vacuously true until a repair actually stamps
119
+ fe_stamp_ok: "true"
120
+ be_stamp_error: ""
121
+ fe_stamp_error: ""
122
+ report_path: ""
123
+ result_file: ""
124
+ # --- legacy delta guard -------------------------------------------------
38
125
  active_change: ""
39
126
  has_change: "false"
127
+ no_open_change: "true" # NOT has_change ⇒ the masters are authoritative ⇒ this gate applies
40
128
  version: ""
41
- no_open_change: "true" # NOT has_change ⇒ the masters are authoritative ⇒ this gate applies
42
129
 
43
130
  workflow:
44
131
 
45
- # 1. Resolve the current feature + its spec; require the spec (the anchor).
132
+ # 1. Which lanes exist. The DECLARATION is the question — not the lane-gate module,
133
+ # whose on-disk check would refuse a greenfield project that declared a root it
134
+ # has not scaffolded. A plan is gated as a document, before any code exists.
135
+ - id: lane_resolve
136
+ action: execute_script
137
+ on_error: continue # never crash raw on an undeclared lane — the gates below explain it
138
+ script: { ref: actions }
139
+ subcommand: lane-resolve
140
+ outputs:
141
+ - { name: lanes_declared, from: "$.declared" }
142
+ - { name: be_declared, from: "$.backend_declared" }
143
+ - { name: fe_declared, from: "$.frontend_declared" }
144
+
145
+ - id: be_scope
146
+ condition: "{{be_declared}} == true"
147
+ action: set
148
+ vars: { be_in_scope: "true", have_lane: "true" }
149
+ - id: fe_scope
150
+ condition: "{{fe_declared}} == true"
151
+ action: set
152
+ vars: { fe_in_scope: "true", have_lane: "true" }
153
+ - id: require_lane
154
+ action: verify_context
155
+ require_true: have_lane
156
+ on_false: stop
157
+ 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."
158
+
159
+ # 2. Resolve the current feature and require its spec — the anchor everything is
160
+ # measured against. feature-paths reports both plan paths whether or not they
161
+ # exist; the freshness gates below decide what that absence means.
46
162
  - id: feature_paths
47
163
  action: execute_script
48
164
  on_error: continue # never crash raw on 'no feature set' — the gate below explains it
@@ -52,6 +168,8 @@ workflow:
52
168
  - { name: feature_dir, from: "$.feature_dir" }
53
169
  - { name: spec_file, from: "$.spec_file" }
54
170
  - { name: spec_exists, from: "$.spec_exists" }
171
+ - { name: be_plan, from: "$.backend_plan_file" }
172
+ - { name: fe_plan, from: "$.frontend_plan_file" }
55
173
 
56
174
  - id: require_feature
57
175
  action: verify_context
@@ -63,10 +181,42 @@ workflow:
63
181
  action: verify_context
64
182
  require_true: spec_exists
65
183
  on_false: stop
66
- on_false_message: "No spec.md for the current feature. Run /appweaver specify (and design) first."
184
+ 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."
185
+
186
+ - id: report_paths
187
+ action: set
188
+ vars: { report_path: "{{feature_dir}}/plan-analyze-report.md" }
189
+
190
+ # 2a. INVALIDATE any previous verdict, before anything downstream can stop. A gate
191
+ # that writes its result only on completion leaves the LAST run's verdict on disk
192
+ # for every run that aborts — a freshness stop, a refused stamp — and a pipeline
193
+ # reading that file would act on a certification this run never made. Every exit
194
+ # from here on therefore leaves at least a red; the real verdict overwrites it.
195
+ - id: gate_result_open
196
+ action: execute_script
197
+ # The lenient loader needs a flow sequence on ONE line.
198
+ 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"]
199
+ script: { ref: actions }
200
+ subcommand: gate-result
201
+ outputs:
202
+ - { name: result_file, from: "$.result_file" }
203
+
204
+ # 2b. The spec's own ledger tripwire, the same fence plan and plan-ui carry. This
205
+ # gate compares each plan's derived version against the spec's ledger, so a spec
206
+ # that moved OUTSIDE the ledger makes every one of those comparisons a lie: the
207
+ # plans would be certified current against a version the spec no longer is.
208
+ - id: spec_guard
209
+ action: include
210
+ module: .appweaver/orchestrator/modules/spec-state-gate.orch.yaml
211
+ with: { command: "/appweaver analyze-design" }
67
212
 
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.
213
+ # 2c. LEGACY GUARD. Nothing in the current model opens a change — specify writes the
214
+ # spec directly and the delta family is retired (deletion scheduled). has_change
215
+ # true therefore means a repo left mid-flight by that retired chain: delta-spec /
216
+ # delta-design are still the authoritative in-flight edit, and repairing the
217
+ # master plans under them would drift the masters out from beneath the delta
218
+ # before finalize-delta merges it. Stop BEFORE any analysis so the wrong-stage run
219
+ # has zero side effects.
70
220
  - id: read_change
71
221
  action: execute_script
72
222
  script: { ref: actions }
@@ -77,11 +227,6 @@ workflow:
77
227
  - { name: has_change, from: "$.has_change" }
78
228
  - { name: version, from: "$.version" }
79
229
 
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
230
  - id: flag_change_open
86
231
  condition: "{{has_change}} == true"
87
232
  action: set
@@ -91,22 +236,116 @@ workflow:
91
236
  action: verify_context
92
237
  require_true: no_open_change
93
238
  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."
239
+ 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
240
 
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
241
+ # 3. Per-lane derivation freshness. plan-check reports `fresh` when the document does
242
+ # not exist at all, which is how a declared-but-unplanned lane is caught here
243
+ # rather than by a scaffold this stage deliberately does not perform.
244
+ - id: be_track
99
245
  action: execute_script
246
+ condition: "{{be_in_scope}} == true"
247
+ on_error: continue # a probe that fails binds nothing; be_gate_ok stays false ⇒ fails CLOSED
100
248
  script: { ref: actions }
101
- subcommand: design-scaffold
102
- args: ["--feature-dir", "{{feature_dir}}"]
249
+ subcommand: plan-check
250
+ args: ["--doc", "{{be_plan}}", "--feature-dir", "{{feature_dir}}"]
103
251
  outputs:
104
- - { name: design_doc, from: "$.design_doc" }
252
+ - { name: be_state, from: "$.state" }
253
+ - { name: be_derived, from: "$.derived_version" }
254
+ - { name: be_changed, from: "$.changed_csv" }
255
+ - { name: current_version, from: "$.current_version" }
105
256
 
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.
257
+ - id: fe_track
258
+ action: execute_script
259
+ condition: "{{fe_in_scope}} == true"
260
+ on_error: continue # same fail-closed contract as the backend probe
261
+ script: { ref: actions }
262
+ subcommand: plan-check
263
+ args: ["--doc", "{{fe_plan}}", "--feature-dir", "{{feature_dir}}"]
264
+ outputs:
265
+ - { name: fe_state, from: "$.state" }
266
+ - { name: fe_derived, from: "$.derived_version" }
267
+ - { name: fe_changed, from: "$.changed_csv" }
268
+ - { name: current_version, from: "$.current_version" }
269
+
270
+ # 3a. BACKEND freshness. clean / edited / untracked proceed; the rest stop. `edited`
271
+ # proceeds because a hand-edited plan is exactly what this gate exists to judge —
272
+ # it measures the document as it stands. `untracked` proceeds because a migrated
273
+ # legacy master has no baseline to be behind, and the whole-document analysis plus
274
+ # the stamp is what establishes one.
275
+ - id: be_gate_na
276
+ condition: "{{be_in_scope}} == false"
277
+ action: set
278
+ vars: { be_gate_ok: "true" }
279
+ - id: be_gate_clean
280
+ condition: "{{be_state}} == clean"
281
+ action: set
282
+ vars: { be_gate_ok: "true" }
283
+ - id: be_gate_edited
284
+ condition: "{{be_state}} == edited"
285
+ action: set
286
+ vars: { be_gate_ok: "true" }
287
+ - id: be_gate_untracked
288
+ condition: "{{be_state}} == untracked"
289
+ action: set
290
+ vars: { be_gate_ok: "true" }
291
+ - id: be_msg_fresh
292
+ condition: "{{be_state}} == fresh"
293
+ action: set
294
+ 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." }
295
+ - id: be_msg_behind
296
+ condition: "{{be_state}} == behind"
297
+ action: set
298
+ 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." }
299
+ - id: be_msg_edited_behind
300
+ condition: "{{be_state}} == edited_behind"
301
+ action: set
302
+ 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." }
303
+ - id: require_be_current
304
+ action: verify_context
305
+ require_true: be_gate_ok
306
+ on_false: stop
307
+ on_false_message: "{{be_gate_msg}}"
308
+
309
+ # 3b. FRONTEND freshness — the same contract, routed to the frontend lane's author.
310
+ - id: fe_gate_na
311
+ condition: "{{fe_in_scope}} == false"
312
+ action: set
313
+ vars: { fe_gate_ok: "true" }
314
+ - id: fe_gate_clean
315
+ condition: "{{fe_state}} == clean"
316
+ action: set
317
+ vars: { fe_gate_ok: "true" }
318
+ - id: fe_gate_edited
319
+ condition: "{{fe_state}} == edited"
320
+ action: set
321
+ vars: { fe_gate_ok: "true" }
322
+ - id: fe_gate_untracked
323
+ condition: "{{fe_state}} == untracked"
324
+ action: set
325
+ vars: { fe_gate_ok: "true" }
326
+ - id: fe_msg_fresh
327
+ condition: "{{fe_state}} == fresh"
328
+ action: set
329
+ 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." }
330
+ - id: fe_msg_behind
331
+ condition: "{{fe_state}} == behind"
332
+ action: set
333
+ 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." }
334
+ - id: fe_msg_edited_behind
335
+ condition: "{{fe_state}} == edited_behind"
336
+ action: set
337
+ 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." }
338
+ - id: require_fe_current
339
+ action: verify_context
340
+ require_true: fe_gate_ok
341
+ on_false: stop
342
+ on_false_message: "{{fe_gate_msg}}"
343
+
344
+ # 4. Load the domain GATE overlays (domain-rule-bindings `/aw-spec-gate`) as the
345
+ # conformance CHECKLIST — validation input only, never authoring. The analyze step
346
+ # sees them (include_standards: true); the repair steps do NOT, because a plan
347
+ # conforms to the SPEC and an overlay constraint absent from the spec is a lift
348
+ # failure to report, not a rule to design against. Empty for a non-domain project.
110
349
  - id: resolve_gate_overlays
111
350
  action: execute_script
112
351
  script: { ref: actions }
@@ -119,7 +358,8 @@ workflow:
119
358
  files_from_variable: gate_overlays
120
359
  optional: true
121
360
 
122
- # 3. Pre-execution hooks.
361
+ # 5. Pre-execution hooks. The phase keys stay `before_analyze` / `after_analyze` —
362
+ # they are configuration contract and outlive the rename.
123
363
  - id: resolve_pre_hooks
124
364
  action: execute_script
125
365
  script: { ref: actions }
@@ -131,120 +371,416 @@ workflow:
131
371
  action: dispatch_hooks
132
372
  hooks_from_variable: before_hooks
133
373
 
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×).
374
+ # 6. The context both lanes are judged in. One envelope, because the coverage
375
+ # question spans them: a UX- lives only in the frontend plan, a BR- only in the
376
+ # backend plan, and an FR- with a user-facing surface needs a home in each.
377
+ # Both plans are `optional:` ONLY because a lane may be undeclared — for a
378
+ # declared lane the freshness gate above already proved the file exists, so an
379
+ # in-scope plan is never silently skipped here.
380
+ - id: ctx_analyze
381
+ action: build_context
382
+ context:
383
+ - { path: "{{spec_file}}" }
384
+ - { path: "{{be_plan}}", optional: true }
385
+ - { path: "{{fe_plan}}", optional: true }
386
+ - { path: "{{feature_dir}}/interfaces.md", optional: true } # the published surface both plans cite by IF- id
387
+
388
+ # 7. Conformance loop: analyze → REPORT gaps + ASK → repair the found gaps → re-check.
389
+ # Bounded (3x). "Report and Skip" leaves both plans untouched and stops at the
390
+ # report, which is the right answer whenever the fix belongs to another stage.
137
391
  - id: analyze_loop
138
392
  action: loop
139
393
  while: "{{analyze_ok}} != true"
140
394
  max_iterations: 3
141
395
  steps:
396
+ # A stale `true` from a previous iteration must never arm a write, so the
397
+ # frontend repair permission is rebuilt from scratch on every pass.
398
+ - id: fe_repair_reset
399
+ action: set
400
+ vars: { fe_repair_go: "false" }
401
+
142
402
  - id: analyze
143
403
  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: [] }
404
+ include_standards: true # the domain gate overlays (the checklist) ride in the standards
405
+ schema: { ok: true, gap_count: 0, gap_refs: "<comma-separated spec ids the gaps touch>", repaired_refs: "<comma-separated spec ids THIS repair actually rewrote — empty on the first pass>", 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>" } ] }
406
+ mock: { ok: true, gap_count: 0, gap_refs: "", repaired_refs: "", backend_repair: false, frontend_repair: false, gaps: [] }
148
407
  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.
408
+ spec.md is the ANCHOR. Judge the feature's PLAN DOCUMENTS against it and list
409
+ EVERY drift as a gap. In scope this run: backend-plan.md = {{be_in_scope}},
410
+ frontend-plan.md = {{fe_in_scope}}. Judge ONLY the plans in scope; never
411
+ report a gap against a lane that is not declared.
412
+
413
+ SECTION NUMBERS ARE NOT STABLE, so identify every section by its ROLE and by
414
+ the heading actually present in the document in front of you. A brownfield
415
+ project's plans come from the code-derived template /appweaver onboard
416
+ installs, whose layout differs from the greenfield one (its traceability,
417
+ assumptions and deviations sections carry different numbers, and its
418
+ component sections are named after the project's own structural axes). Any
419
+ "§n" below is the GREENFIELD number, given only to say WHICH section is
420
+ meant. Put the heading you actually found in `where`, and never report a
421
+ missing section on the strength of a number.
422
+
423
+ (1) FORWARD — COVERAGE. Every spec item has a design home, in the RIGHT lane:
424
+ • FR- (requirement) — a home in backend-plan.md; and ALSO a screen/
425
+ component/state in frontend-plan.md when it has a user-facing surface.
426
+ • BR- (business rule) — backend-plan.md only, as a domain invariant.
427
+ • CN- (constraint) — wherever it binds. A CN- that binds both lanes needs
428
+ a home in BOTH; one that binds a single lane needs only that one.
429
+ • DC- (data classification) — backend enforcement, plus what the frontend
430
+ withholds from the client wherever a client sees the data.
431
+ • IC- (integration contract) — the backend adapter/port that realises it;
432
+ the frontend binds to it through interfaces.md, never directly.
433
+ • UX- (experience standard) — frontend-plan.md ONLY. backend-plan.md does
434
+ not carry them, so a UX- with no frontend row is a DROPPED requirement
435
+ and is always a HIGH gap.
436
+ • SC- (success criterion) — wherever it is met and how it is verified.
437
+ A lifted CN-/DC-/IC- with no row in the relevant Traceability section is a
438
+ HIGH gap: those sections are the two-way guarantee and must be complete.
439
+ Edge Cases carry no ids by design — do not invent one for them.
440
+
441
+ (2) THE DOMAIN MODEL MATCHES THE SPEC EXACTLY (backend). Entities and value
442
+ objects are the spec's Key Entities under the same names, invariants are
443
+ the spec's Business Rules, and the boundary is the spec's Aggregate
444
+ Boundary. A renamed or invented entity is a HIGH gap.
445
+
446
+ (3) BACKWARD — JUSTIFICATION. No design element exceeds or contradicts the
447
+ spec. Anything without a spec basis is a drift UNLESS it is already logged
448
+ as a DA-n assumption or a WA-n workaround in that plan's own ledgers — a
449
+ logged item with a stated basis (including "per Const. §<n>" for standing
450
+ technical machinery the constitution mandates) is accounted for, not a gap.
451
+
452
+ (4) CROSS-LANE — the checks neither lane can make alone. Report these with
453
+ lane "cross":
454
+ • an FR- with a user-facing surface designed in one lane only;
455
+ • a CN-/DC-/IC- that binds both lanes and is honoured in only one;
456
+ • the frontend's contract-binding section (greenfield §5) citing an IF-
457
+ id that interfaces.md does not publish, or an operation it needs that
458
+ has no IF- entry and is not already raised as a contract gap;
459
+ • either plan RESTATING the wording of a published interface instead of
460
+ citing its IF- id — the spec owns requirement wording and interfaces.md
461
+ owns contract wording; a second copy is drift by construction;
462
+ • the two plans contradicting each other on the same behaviour.
463
+
464
+ (5) COHERENCE. The backend layers hold (Application→Domain, API→Application,
465
+ Infrastructure implements the declared ports); the frontend's state,
466
+ components and flows are consistent with its own navigation. Both stay
467
+ high-level and technology-agnostic.
468
+
469
+ (6) OVERLAY BACKSTOP. The domain gate checklist is loaded in the standards.
470
+ For each constraint it states (retention, immutability, append-only audit,
471
+ data exposure, interop obligation …), confirm a plan realises it VIA a
472
+ spec CN-/DC-/IC-. If a checklist constraint is in NEITHER the spec nor a
473
+ plan, it is a HIGH gap: set spec_ref to the overlay name and PREFIX the
474
+ gap text with "LIFT-FAILURE:". It was never lifted into the spec, so the
475
+ fix is /appweaver specify — never fabricate it into a plan.
476
+
477
+ For each gap set `route` to the command that actually fixes it: repair here
478
+ when the plan is wrong and the spec is right; /appweaver sync-spec when the
479
+ SPEC is what is wrong; /appweaver specify for a LIFT-FAILURE; /appweaver plan
480
+ or /appweaver plan-ui when the plan simply lags the spec.
481
+
482
+ Set backend_repair true only if at least one gap can be closed by rewriting
483
+ backend-plan.md, and frontend_repair likewise for frontend-plan.md — false for
484
+ a lane that is not in scope, and false when every gap on that lane routes to
485
+ another command. Set gap_count to the number of gaps and gap_refs to their
486
+ spec ids, comma-separated. Set ok=true ONLY when every plan in scope fully
487
+ conforms to the spec AND no checklist constraint is unaccounted for.
488
+
489
+ `repaired_refs` IS A DIFFERENT SET FROM `gap_refs`, and it goes into the plans'
490
+ own Change History. Set it to the spec ids THIS PASS ACTUALLY REWROTE a plan for
491
+ — empty on a pass that repaired nothing, which includes the first pass of every
492
+ run. Never copy gap_refs into it: that set holds every finding, including the
493
+ gaps you were told to leave OPEN (route sync-spec or specify), a LIFT-FAILURE
494
+ whose spec_ref is an overlay name rather than an id, and any "—". Recording one
495
+ of those claims the plan was conformed for a requirement nobody touched, in the
496
+ ledger story-plan and the next /appweaver plan read.
172
497
  outputs:
173
- - { name: analyze_ok, from: "$.ok" }
174
- - { name: analyze_gaps, from: "$.gaps" }
498
+ - { name: analyze_ok, from: "$.ok" }
499
+ - { name: analyze_gaps, from: "$.gaps" }
500
+ - { name: gap_count, from: "$.gap_count" }
501
+ - { name: gap_refs, from: "$.gap_refs" }
502
+ - { name: be_needs_repair, from: "$.backend_repair" }
503
+ - { name: fe_needs_repair, from: "$.frontend_repair" }
504
+ - { name: repaired_refs, from: "$.repaired_refs" }
505
+
506
+ # The report's subject is the state the gate FOUND, not what survived repair —
507
+ # a verdict that only ever describes the repaired document cannot show that a
508
+ # repair happened, and a green earned by repair must stay visible as one.
509
+ - id: record_found
510
+ condition: "{{found_recorded}} == false"
511
+ action: set
512
+ vars:
513
+ found_gaps: "{{analyze_gaps}}"
514
+ found_count: "{{gap_count}}"
515
+ found_refs: "{{gap_refs}}"
516
+ found_recorded: "true"
517
+
175
518
  - id: conform_break
176
519
  action: break
177
520
  condition: "{{analyze_ok}} == true"
521
+
178
522
  - id: ask_repair
179
523
  action: ask
180
524
  prompt: |
181
- Analyze found spec↔design gaps (spec.md is the anchor):
525
+ Spec ↔ plan gaps found (spec.md is the anchor; {{gap_count}} gap(s) on {{gap_refs}}):
182
526
  {{analyze_gaps}}
183
- Repair design.md to conform to the spec now, or report the gaps and stop?
184
- options: ["Repair", "Report and Skip"]
527
+ Repair the plan document(s) to conform now, or write the report and stop?
528
+ Repair closes only these gaps and records the rewrite in the plan's own
529
+ Change History. Choose Report and Skip whenever the fix belongs elsewhere —
530
+ a wrong SPEC goes through /appweaver sync-spec, a LIFT-FAILURE through
531
+ /appweaver specify.
532
+ # REPORT FIRST, BECAUSE THE FIRST OPTION IS THE UNATTENDED ANSWER. An `ask` with
533
+ # nobody at the terminal resolves to option one, so an unattended run of this gate
534
+ # takes whichever branch is written here. Repairing means REWRITING both feature
535
+ # plans and re-stamping them, and a CI job that rewrites the documents it was asked
536
+ # to check reports on a state it created. Reporting is the safe default: it writes
537
+ # the report, leaves the plans untouched, and the person who reads it chooses.
538
+ options: ["Report and Skip", "Repair"]
185
539
  answer_var: repair_choice
186
- - id: write_report
540
+
541
+ - id: skip_break
542
+ action: break
187
543
  condition: "{{repair_choice}} == Report and Skip"
544
+
545
+ - id: mark_repaired
546
+ action: set
547
+ vars: { repaired: "true" }
548
+
549
+ # The frontend lane's write permission is the AND of "the model found something
550
+ # to fix there" and "that lane is in scope" — the evaluator takes single
551
+ # comparisons only, so the second half arrives as the assigned value.
552
+ - id: fe_repair_arm
553
+ condition: "{{fe_needs_repair}} == true"
554
+ action: set
555
+ vars: { fe_repair_go: "{{fe_in_scope}}" }
556
+
557
+ - id: reconcile_be
558
+ condition: "{{be_needs_repair}} == true"
188
559
  action: agent_author
189
- context: ["{{design_doc}}"]
190
- include_standards: false
191
- output_target: { type: file, path: "{{feature_dir}}/design-analyze-report.md" }
560
+ include_standards: false # rewrite-in-place: the SPEC anchors, not the overlays
561
+ output_target: { type: file, path: "{{be_plan}}" }
192
562
  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):
563
+ spec.md is the ANCHOR — update backend-plan.md (in context) to CONFORM to it,
564
+ closing the gaps below whose lane is "backend" or "cross" AND whose route is
565
+ this repair. The spec WINS every time.
195
566
  {{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
567
+ - SECTION NUMBERS ARE NOT STABLE: this document may be the greenfield
568
+ four-layer template or the code-derived one /appweaver onboard installs,
569
+ whose layout differs. Work from the headings actually in the document; the
570
+ "§n" below name the GREENFIELD section so you know which one is meant.
571
+ NEVER renumber or reorder what is there.
572
+ - Give each uncovered spec FR-/BR-/CN-/DC-/IC-/SC- a design home: a lifted
573
+ CN-/DC-/IC- lands in a domain invariant (§1), the persistence component
574
+ (§4.1), an external-integrations adapter row (§4.2), or a cross-cutting
575
+ row (§5).
576
+ - Realign the domain model to the spec: entity and value-object names = the
577
+ spec's Key Entities, invariants = the spec's Business Rules, boundary = the
578
+ spec's Aggregate Boundary.
579
+ - REMOVE, or move to a DA-n assumption, any design element with no spec basis.
580
+ EXCEPTION: do NOT remove a cross-cutting element the technical constitution
581
+ mandates — standing technical or infrastructure machinery rather than
582
+ invented domain logic. MOVE it to a DA-n citing the mandating principle
583
+ ("per Const. §<n>"); removing it could violate the constitution. Reserve
584
+ REMOVE for genuine over-reach — invented domain entities, rules or behaviour
585
+ the spec omits.
586
+ - The published-surface section (§3) is an INDEX into interfaces.md: cite IF-
587
+ ids, never restate a published contract's wording. If a gap says an operation
588
+ has no IF- entry, note it there as a contract gap to publish — do not invent
589
+ an IF- id, and never edit interfaces.md from here; /appweaver plan is its
590
+ only publisher.
591
+ - A "LIFT-FAILURE:" gap CANNOT be closed here: the constraint is not in the
592
+ spec and you must not invent a domain rule the spec omits. Leave it open and
593
+ add a DA-n reading "lift-failure: <constraint> — re-run /appweaver specify to
594
+ lift it into CN-/DC-/IC-".
595
+ - Leave every gap routed to /appweaver sync-spec or /appweaver specify OPEN.
596
+ Fixing the plan to match a wrong spec buries the defect in the document that
597
+ is supposed to reveal it.
598
+ - Keep the Traceability section (§6) in sync with what you changed. Preserve
599
+ every already-conformant section VERBATIM, and preserve the Assumptions (§7),
600
+ Workarounds/Deviations (§8) and Change History (§9) sections as LEDGERS —
601
+ append to them, never rewrite, renumber or drop them. Dropping the Change
602
+ History section is refused downstream and stops the run. Write NO version and
603
+ NO history row: plan-stamp does both.
604
+ - Do NOT change spec.md, frontend-plan.md, interfaces.md or adr.md.
605
+ Return the COMPLETE updated backend-plan.md content only.
606
+
607
+ # plan-stamp is the plan's only version authority. Repairing without stamping
608
+ # would leave the document reading as an unexplained hand edit to story-plan and
609
+ # to the next /appweaver plan, and the rewrite would be absent from its ledger.
610
+ - id: stamp_be
611
+ condition: "{{be_needs_repair}} == true"
612
+ action: execute_script
613
+ script: { ref: actions }
614
+ subcommand: plan-stamp
615
+ # --ids is WHAT THIS REPAIR WROTE, not every gap the pass found. gap_refs is the
616
+ # whole finding set — including gaps the repair prompt is told to leave OPEN (route
617
+ # sync-spec / specify), a LIFT-FAILURE whose spec_ref is an overlay name rather than
618
+ # an id, and the literal "—" the schema permits. Stamping those recorded, in the
619
+ # plan's own Change History, that the repair conformed ids it never touched — and
620
+ # that ledger is what story-plan and the next /appweaver plan read.
621
+ # --note carries the plan's tracking STATE, because a repair over an `edited`
622
+ # document re-baselines a hand edit that /appweaver plan would have refused without
623
+ # accept_manual: true. Admitting the edit is right; adopting it silently is not.
624
+ args: ["--doc", "{{be_plan}}", "--feature-dir", "{{feature_dir}}", "--ids", "{{repaired_refs}}", "--note", "Conformance repair (analyze-design) over a plan in state '{{be_state}}'"]
625
+ outputs:
626
+ - { name: be_stamp_ok, from: "$.ok" }
627
+ - { name: be_stamp_error, from: "$.error" }
628
+
629
+ - id: reconcile_fe
630
+ condition: "{{fe_repair_go}} == true"
208
631
  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}}" }
632
+ include_standards: false
633
+ output_target: { type: file, path: "{{fe_plan}}" }
212
634
  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):
635
+ spec.md is the ANCHOR — update frontend-plan.md (in context) to CONFORM to it,
636
+ closing the gaps below whose lane is "frontend" or "cross" AND whose route is
637
+ this repair. The spec WINS every time.
215
638
  {{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.
639
+ - Give every uncovered UX- a home — this document is their ONLY home — and
640
+ every FR- with a user-facing surface a screen, component or state.
641
+ - Honour each CN-/DC- that binds this lane: a DC- says what the client is not
642
+ shown, a CN- a bound the experience must meet.
643
+ - SECTION NUMBERS ARE NOT STABLE — work from the headings actually in the
644
+ document; the "§n" here name the greenfield sections so you know which is
645
+ meant, and nothing is renumbered or reordered.
646
+ - The Contract Binding section (§5) cites interfaces.md by IF- id and never
647
+ restates a contract's wording. An operation with no published entry keeps the
648
+ "(unpublished)" marker and is listed under contract gaps to raise — this
649
+ lane never writes interfaces.md; /appweaver plan publishes it.
650
+ - REMOVE, or move to a DA-n, any element with no spec basis, except what
651
+ constitution.ui.md mandates — that moves to a DA-n citing the basis.
652
+ - Leave every gap routed to /appweaver sync-spec or /appweaver specify OPEN.
653
+ - Keep the Traceability section (§7) in sync. Preserve every already-conformant
654
+ section VERBATIM, and preserve the Assumptions (§8), Workarounds (§9) and
655
+ Change History sections as LEDGERS — append, never rewrite, renumber or drop.
656
+ Dropping the Change History section is refused downstream and stops the run.
657
+ Write NO version and NO history row: plan-stamp does both.
658
+ - Do NOT change spec.md, backend-plan.md, interfaces.md or adr.md.
659
+ Return the COMPLETE updated frontend-plan.md content only.
660
+
661
+ - id: stamp_fe
662
+ condition: "{{fe_repair_go}} == true"
663
+ action: execute_script
664
+ script: { ref: actions }
665
+ subcommand: plan-stamp
666
+ # Same two rules as the service lane: the ids this repair actually wrote, and the
667
+ # tracking state the repair ran over. See stamp_be.
668
+ args: ["--doc", "{{fe_plan}}", "--feature-dir", "{{feature_dir}}", "--ids", "{{repaired_refs}}", "--note", "Conformance repair (analyze-design) over a plan in state '{{fe_state}}'"]
669
+ outputs:
670
+ - { name: fe_stamp_ok, from: "$.ok" }
671
+ - { name: fe_stamp_error, from: "$.error" }
672
+
673
+ # 7b. The ledger-survival fence. plan-stamp refuses to baseline a document whose
674
+ # Change History section went missing, so ok=false here means a repair landed on
675
+ # disk having dropped the plan's ledger. That is the one loss this model never
676
+ # absorbs silently — stop on it, before any verdict is written.
677
+ - id: be_stamp_fence
678
+ action: verify_context
679
+ require_true: be_stamp_ok
680
+ on_false: stop
681
+ 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."
682
+ - id: fe_stamp_fence
683
+ action: verify_context
684
+ require_true: fe_stamp_ok
685
+ on_false: stop
686
+ 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."
687
+
688
+ # 8. The human report — written on EVERY run, green or red. It is the two-directional
689
+ # coverage record, and a gate that leaves nothing behind when it passes gives a
690
+ # reviewer no evidence that it ran at all.
691
+ - id: write_report
692
+ action: agent_author
693
+ include_standards: false
694
+ output_target: { type: file, path: "{{report_path}}" }
695
+ task: |
696
+ Write the Plan Analyze Report for this feature to the output.
697
+ Verdict: conformant={{analyze_ok}}. Gaps as FOUND: {{found_count}} on
698
+ {{found_refs}}. Gaps still open: {{gap_count}}. Repair was run: {{repaired}}.
699
+ Lanes gated: backend={{be_in_scope}} (tracking {{be_state}}, derived from spec
700
+ {{be_derived}}), frontend={{fe_in_scope}} (tracking {{fe_state}}, derived from
701
+ spec {{fe_derived}}). Spec version: {{current_version}}.
702
+ The gaps as found:
703
+ {{found_gaps}}
704
+ Markdown format:
705
+ - "# Plan Analyze Report — <aggregate>", then a line naming the spec version and
706
+ each gated plan with its tracking state.
707
+ - A verdict line: "verdict: CONFORMS ✓" or "verdict: GAPS ✗ · <n> open".
708
+ WHERE A REPAIR RAN OVER A HAND-EDITED PLAN, say so on that line — backend-plan.md
709
+ tracking state was "{{be_state}}" and frontend-plan.md "{{fe_state}}", and an
710
+ `edited` or `edited_behind` there means the repair re-baselined a hand edit that
711
+ /appweaver plan would have refused without accept_manual: true. Name the document,
712
+ the state, and that the edit is now adopted into the plan's baseline — whoever made
713
+ it should confirm the repair kept its intent.
714
+ When repair ran, say so and give both numbers — found and still open.
715
+ - "## Coverage (spec → plan)": a table | spec id | type | lane(s) it needs |
716
+ design home | status |, one row per spec id you can see in the spec, marking
717
+ each covered or UNCOVERED. This is the forward half of the two-way guarantee
718
+ and it is the reason this report exists — write it even on a green run.
719
+ - "## Gaps": a table | # | severity | lane | spec ref | where | gap | route |.
720
+ Omit the section when there were none.
721
+ - "## How to resolve": group by route and give the exact command for each — the
722
+ spec is the anchor, so a wrong plan is repaired here or via /appweaver plan
723
+ with accept_manual, a wrong SPEC goes through /appweaver sync-spec, and a
724
+ "LIFT-FAILURE:" goes through /appweaver specify. Omit on a green run.
725
+ Do not invent ids, versions or dates beyond the values given above.
726
+ Return the complete report markdown only.
727
+
728
+ # 8b. THE SHAPE BEING CERTIFIED, read at the last possible moment. Taken here rather than
729
+ # from the freshness probes at the top, because the repair loop may have rewritten and
730
+ # re-stamped both plans in between — recording the pre-repair hash would certify a
731
+ # document that no longer exists and fail every downstream comparison on a green run.
732
+ # Per lane and conditional: a lane out of scope has no hash and gets an empty field,
733
+ # which the readers treat as "this lane was not certified" rather than as a mismatch.
734
+ - id: be_final_hash
735
+ condition: "{{be_in_scope}} == true"
736
+ action: execute_script
737
+ on_error: continue
738
+ script: { ref: actions }
739
+ subcommand: plan-check
740
+ args: ["--doc", "{{be_plan}}", "--feature-dir", "{{feature_dir}}"]
741
+ outputs:
742
+ - { name: be_final_sha, from: "$.body_sha256" }
743
+ - id: fe_final_hash
744
+ condition: "{{fe_in_scope}} == true"
745
+ action: execute_script
746
+ on_error: continue
747
+ script: { ref: actions }
748
+ subcommand: plan-check
749
+ args: ["--doc", "{{fe_plan}}", "--feature-dir", "{{feature_dir}}"]
750
+ outputs:
751
+ - { name: fe_final_sha, from: "$.body_sha256" }
752
+
753
+ # 9. The machine-readable verdict, written beside the report by the SCRIPT — CI
754
+ # cannot branch on prose, and a verdict is exactly the kind of dated, derived
755
+ # record the model never writes. `ok` from this step means the file was written;
756
+ # the pass/fail verdict is `status` inside it.
757
+ - id: gate_result
758
+ action: execute_script
759
+ script: { ref: actions }
760
+ subcommand: gate-result
761
+ # The lenient loader needs a flow sequence on ONE line.
762
+ # backend_sha256 / frontend_sha256 are WHAT THIS RUN CERTIFIED — the shape each plan was
763
+ # in when it was judged. Nothing else in the verdict can say it: backend_state and
764
+ # backend_derived do not move when a plan is hand-edited, so a plan approved here and
765
+ # edited afterwards was indistinguishable from one still in the approved shape. story-plan
766
+ # and both implements compare against these, which is what makes law 40 (approved,
767
+ # changed, never re-approved must not reach code) enforceable rather than stated.
768
+ # RE-READ AFTER ANY REPAIR: the loop above may have rewritten and re-stamped the plans, so
769
+ # the hash recorded has to be the one on disk NOW, not the one the freshness probe saw.
770
+ 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", "backend_sha256={{be_final_sha}}", "--field", "frontend_plan={{fe_plan}}", "--field", "frontend_in_scope={{fe_in_scope}}", "--field", "frontend_state={{fe_state}}", "--field", "frontend_derived={{fe_derived}}", "--field", "frontend_sha256={{fe_final_sha}}"]
771
+ outputs:
772
+ - { name: result_file, from: "$.result_file" }
237
773
 
238
774
  - id: analyze_gate
239
775
  action: verify_context
240
776
  require_true: analyze_ok
241
777
  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."
778
+ 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
779
 
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.)
780
+ # (No version is written here by design. This gate proves conformance; close-story is
781
+ # the version authority downstream, and plan-stamp the plans' own.)
246
782
 
247
- # 5. Post-execution hooks.
783
+ # 10. Post-execution hooks.
248
784
  - id: resolve_post_hooks
249
785
  action: execute_script
250
786
  script: { ref: actions }
@@ -258,8 +794,20 @@ workflow:
258
794
 
259
795
  outputs:
260
796
  feature_dir: "{{feature_dir}}"
261
- design_doc: "{{design_doc}}"
797
+ spec_version: "{{current_version}}"
798
+ backend_plan: "{{be_plan}}"
799
+ backend_in_scope: "{{be_in_scope}}"
800
+ backend_state: "{{be_state}}"
801
+ backend_derived: "{{be_derived}}"
802
+ frontend_plan: "{{fe_plan}}"
803
+ frontend_in_scope: "{{fe_in_scope}}"
804
+ frontend_state: "{{fe_state}}"
805
+ frontend_derived: "{{fe_derived}}"
262
806
  conformant: "{{analyze_ok}}"
807
+ gaps_found: "{{found_count}}"
808
+ gaps_open: "{{gap_count}}"
809
+ repaired: "{{repaired}}"
810
+ report: "{{report_path}}"
811
+ result_json: "{{result_file}}"
263
812
  gaps: "{{analyze_gaps}}"
264
- version: "{{version}}"
265
- next: "/appweaver implement (design conforms to the spec; version unchanged — this gate closes nothing)"
813
+ 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."