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,41 +1,84 @@
1
1
  # =============================================================================
2
- # aw-spec-analyze-slice (orchestrator) — the design↔code conformance GATE
2
+ # aw-spec-analyze-slice (orchestrator) — the spec ↔ CODE conformance GATE
3
3
  #
4
4
  # The second of the two discrete conformance gates on the forward path, and the
5
5
  # sibling of analyze-design:
6
- # analyze-design gates spec ↔ design — STATIC, before implement
7
- # analyze-slice gates design ↔ slice — DYNAMIC, after build
8
- # "Slice" is the unit under analysis: the aggregate's code, its paired tests, and
9
- # its OKF knowledge concept. Three passes over it —
6
+ # analyze-design gates spec ↔ feature plan — STATIC, before implement
7
+ # analyze-slice gates spec ↔ CODE — DYNAMIC, after build
8
+ #
9
+ # THE SPEC IS THE ORACLE; THE DESIGN IS THE BRIDGE. This gate used to score code
10
+ # against the design, which only ever proved that two documents agreed with each
11
+ # other. The contract is spec.md — its FR-/BR- wording, and the CN-/DC-/IC-/UX- a
12
+ # slice is bound by — and this is the stage that asks whether the code delivers it.
13
+ # The design is how the question is ANSWERABLE: the story's design pass names which
14
+ # elements realise which id, and the feature plan says where those elements live in
15
+ # the architecture. Trace runs spec id → design element → code, and a break at either
16
+ # hop is a different finding with a different route:
17
+ # the code does not deliver an id the design maps → the code is wrong (implement)
18
+ # no design maps an in-scope id at all → the BRIDGE is broken (story-plan)
19
+ # the code is right and the spec words it wrongly → the spec is wrong (sync-spec)
20
+ # The bridge's own integrity is REPORTED, never gated: a documented gap in a design
21
+ # does not make working code fail its contract.
22
+ #
23
+ # SCOPED TO A STORY WHEN ONE IS NAMED. `/appweaver analyze-slice Order-002` judges that
24
+ # story's ids; with no story named the whole feature is judged, MINUS every id that has
25
+ # an open story, because in-flight work is not drift (law 39). And a story narrows the
26
+ # QUESTION, not the file set: the code under analysis is always the slice's whole OKF
27
+ # file list, both lanes, because a requirement can only be judged against everything
28
+ # that could realise it.
29
+ #
30
+ # THE SPEC IS THE WORDING; THE STORY IS THE EXTENT. A story rarely delivers a whole
31
+ # requirement — its "## Requirements delivered" section says which part — so a
32
+ # story-scoped run judges the code against the spec's wording of that part alone. A
33
+ # slice is never failed for the half of a requirement it never claimed.
34
+ #
35
+ # FIVE PASSES over the slice — its code, its paired tests, its OKF concept:
10
36
  # 1. BEHAVIOUR run the tests (paired to the spec's acceptance criteria by the
11
37
  # design docs); on red, fix up to 3×.
12
- # 2. CONFORMANCE check the code against the domain gate overlays (validate-only).
13
- # 3. KNOWLEDGE refresh + review the slice's OKF concept (advisory).
14
- # Every fix made here is recorded in that concept, which is also where the slice's
15
- # file list is READ from (2.3.0 — `aw-xref concept-paths`, replacing the retired
16
- # `design-paths` scan for ```lang path=… fences that the tech-agnostic design.md no
17
- # longer carries; the paired `design-reconcile` write-back is retired with it, its job
18
- # already done by the concept-upsert --merge steps in pass 3).
38
+ # 2. ORACLE a TECHNICAL story delivers no requirement, so the unchanged test
39
+ # suite is the only thing that can say whether behaviour moved: a
40
+ # MODIFIED test fails it, red or green (law 32).
41
+ # 3. CONFORMANCE check the code against the domain gate overlays (bounded repair).
42
+ # 4. INTEGRITY re-run every referring aggregate's tests read-only; a break stops
43
+ # the gate and is written up for a human.
44
+ # 5. TRACE spec ↔ code, through the design. The verdict this gate is about.
45
+ # Then the report + the machine-readable verdict, then the stop. Every fix made along
46
+ # the way is merged into the slice's OKF concept, which is also where the file list is
47
+ # READ from (`aw-xref concept-coverage`, both lanes — `concept-paths` reads only
48
+ # `source_files`, so since implement-ui began writing `ui_source_files` a UI-bearing
49
+ # requirement had no UI code in view to be traced to).
19
50
  #
20
51
  # TWO POSITIONS, one gate — and in BOTH the master spec.md is the whole truth:
21
52
  # BOOTSTRAP implement → build → analyze-slice → run
22
53
  # CHANGE implement-delta → build → finalize-delta → analyze-slice → close-delta
23
54
  # On the CHANGE path this gate deliberately runs AFTER finalize-delta, which has already
24
- # merged the delta into spec.md + design.md — so the master carries the change and needs
55
+ # merged the delta into spec.md + the plans — so the master carries the change and needs
25
56
  # no delta-awareness here. It runs BEFORE close-delta, so the change is still OPEN: a red
26
57
  # result is recoverable ("fix and re-run"), not a defect on an already-shipped version.
27
58
  # The only thing this gate reads the open change for is where to send you next.
28
59
  #
29
- # Launch: /appweaver analyze-slice [strategy]
60
+ # IT REPAIRS TWO THINGS AND AUTHORS NOTHING. A red test and a domain-overlay violation
61
+ # are mechanical, local and have a named rule, so they are fixed in place and recorded.
62
+ # A missing requirement is not: it is feature work, and writing it here would produce
63
+ # code that no design pass names and no gate ever conformed — a gate becoming an author,
64
+ # which is the same reason analyze-design refuses to scaffold the plan it gates.
65
+ #
66
+ # Launch: /appweaver analyze-slice [<story-id>]
30
67
  # Prereq: /appweaver build (compiled code for the active feature).
31
68
  # =============================================================================
32
69
 
33
70
  name: aw-spec-analyze-slice
34
- description: The design↔code conformance gate — run the slice's tests against its acceptance criteria, check the code against the domain gate overlays, verify the change did not break any referring aggregate (the OKF referrer blast radius) and write a triage report before stopping if it did, refresh + review its OKF concept, and record every fix in it. Sits after finalize-delta on the change path, so the master spec it scores against already carries the change, and before close-delta, so a red gate leaves the change open and recoverable.
35
- version: 2.3.0
71
+ description: "The spec ↔ code conformance gate — traces every in-scope requirement from spec.md through the design that bridges it to the code that realises it, and fails the slice when the code does not deliver the contract. Scoped to one story when named, to the feature minus its in-flight ids when not. Runs the slice's tests first (fixing red, bounded), holds a technical story to the unchanged-suite oracle, checks the code against the domain gate overlays, verifies the change broke no referring aggregate, refreshes the OKF concept, then writes a traceability report and a machine-readable verdict beside it. Sits after finalize-delta on the change path, so the master spec it scores against already carries the change, and before close-delta, so a red gate leaves the change open and recoverable."
72
+ version: 3.0.0
36
73
  agent_agnostic: true
37
74
  dialect: aw-spec.orchestrator/v1
38
75
 
76
+ inputs:
77
+ user_input:
78
+ description: Optional story id to scope the gate to (e.g. "Order-002"). With none, the whole feature is judged minus its in-flight ids.
79
+ required: false
80
+ bind: USER_INPUT
81
+
39
82
  scripts:
40
83
  actions:
41
84
  sh: .appweaver/scripts/bash/aw-spec-actions.sh
@@ -45,8 +88,8 @@ scripts:
45
88
  ps1: .appweaver/scripts/powershell/aw-xref.ps1
46
89
 
47
90
  # No hardcoded technical standards seed. The technical rule files the code/conformance
48
- # passes check against (testing + coding-standards) are now CONFIGURATION, loaded per-
49
- # command from guardrails/projects/<project>/tech-rule-bindings.md under "## /aw-spec-analyze-slice"
91
+ # passes check against (testing + coding-standards) are CONFIGURATION, loaded per-command
92
+ # from guardrails/projects/<project>/tech-rule-bindings.md under "## /aw-spec-analyze-slice"
50
93
  # by the tech-select module (see the tech_select step below). The DOMAIN gate overlays are
51
94
  # still loaded separately, as before (resolve_gate_overlays → load_gate_overlays). This gate
52
95
  # does not reference constitution.technical.md. The seed below is behaviour-only — the kit's
@@ -56,24 +99,99 @@ standards:
56
99
  - { path: .appweaver/ai-rules.md } # kit-resident: the universal operating rules, every flow
57
100
 
58
101
  vars:
102
+ # --- the feature and its documents --------------------------------------
103
+ feature_dir: ""
104
+ spec_file: ""
105
+ spec_exists: "false"
106
+ be_plan: ""
107
+ be_plan_exists: "false"
108
+ fe_plan: ""
109
+ fe_plan_exists: "false"
110
+ # --- scope --------------------------------------------------------------
111
+ # Defaults describe the FEATURE-scoped run, which is what happens when no story is
112
+ # named — including on brownfield, where adopt emits an empty backlog by design.
113
+ scope: "feature"
114
+ story: ""
115
+ story_path: ""
116
+ story_title: ""
117
+ story_type: ""
118
+ story_status: ""
119
+ story_dir: ""
120
+ story_resolved: "false"
121
+ is_technical: "false"
122
+ exercises_csv: ""
123
+ constraints_csv: ""
124
+ current_version: ""
125
+ stale_csv: ""
126
+ stale_note: ""
127
+ inflight_csv: ""
128
+ backlog_digest: ""
129
+ # Non-empty only when the caller said SOMETHING and no story came back from it — the
130
+ # one case where widening to feature scope is more likely a typo than a choice.
131
+ unresolved_input: ""
132
+ scope_brief: "SCOPE — the WHOLE FEATURE. No story was named, so every FR- and BR- in spec.md is in scope and each is judged in FULL. EXTENT: the whole of each id."
133
+ story_be_doc: ""
134
+ story_be_exists: "false"
135
+ story_be_pass: "0"
136
+ story_fe_doc: ""
137
+ story_fe_exists: "false"
138
+ story_fe_pass: "0"
139
+ bridge_note: "BRIDGE — the FEATURE PLANS (backend-plan.md, and frontend-plan.md where a frontend lane is declared). No story design is in scope this run, so the plans are the only map from a spec id to the elements that realise it."
140
+ # --- the code under analysis (OKF) --------------------------------------
141
+ coverage_digest: ""
142
+ be_count: "0"
143
+ fe_count: "0"
144
+ paths_missing: ""
145
+ okf_concept: ""
146
+ # --- pass 1: behaviour ---------------------------------------------------
59
147
  test_ok: "false"
148
+ no_test_edit_rule: ""
149
+ fixed_files: ""
150
+ # --- pass 2: the technical-story oracle ----------------------------------
151
+ # Named per case rather than derived, because "the suite is clean" and "the suite
152
+ # could not be read" must never collapse into one another: a gate that treats "could
153
+ # not look" as "nothing found" is the false green the kit refuses everywhere else.
154
+ oracle: "clean"
155
+ oracle_case: ""
156
+ tests_changed_csv: ""
157
+ tests_changed_count: "0"
158
+ tdiff_digest: ""
159
+ tdiff_base: ""
160
+ tdiff_base_kind: ""
161
+ tech_gate_ok: "true"
162
+ tech_note: "Not a technical story — the unchanged-suite oracle does not apply."
163
+ # --- pass 3: domain conformance ------------------------------------------
60
164
  conformance_ok: "false"
61
- # NAVIGATION ONLY. Whether a change is open decides where this gate sends you next
62
- # (close-delta vs run) — nothing else in this workflow reads them. On the CHANGE path
63
- # finalize-delta has already merged the delta into the master docs, so the delta files
64
- # are redundant here and are deliberately NOT loaded into any context.
65
- active_change: ""
66
- has_change: "false"
67
- # Integrity pass (referrer blast radius) defaults — safe values if xref/test-run is
68
- # skipped or errors, so the gate reads a real verdict rather than an empty var.
165
+ conformance_violations: ""
166
+ conformance_fixes: ""
167
+ # --- pass 4: integrity (referrer blast radius) ---------------------------
168
+ # Defaults are safe values if xref/test-run is skipped or errors, so the gate reads a
169
+ # real verdict rather than an empty var.
69
170
  integrity_ok: "false"
70
171
  referrer_csv: ""
71
172
  referrer_count: "0"
72
173
  referrer_names: ""
73
- # Pass-3 (OKF) reporting defaults — the knowledge pass is advisory, so these stay
74
- # readable in the outputs even when a step is skipped or errors out.
174
+ integrity_log: ""
175
+ # --- pass 5: trace (the verdict) -----------------------------------------
176
+ trace_ok: "false"
177
+ bridge_ok: "true"
178
+ trace_gaps: ""
179
+ trace_gap_count: "0"
180
+ trace_gap_refs: ""
181
+ trace_coverage: ""
182
+ bridge_gaps: ""
183
+ # --- OKF knowledge (advisory) --------------------------------------------
75
184
  okf_ok: "unknown"
76
185
  okf_orphans: "0"
186
+ # --- reporting ------------------------------------------------------------
187
+ report_path: ""
188
+ result_file: ""
189
+ # NAVIGATION ONLY. Whether a change is open decides where this gate sends you next
190
+ # (close-delta vs run) — nothing else in this workflow reads them. On the CHANGE path
191
+ # finalize-delta has already merged the delta into the master docs, so the delta files
192
+ # are redundant here and are deliberately NOT loaded into any context.
193
+ active_change: ""
194
+ has_change: "false"
77
195
 
78
196
  workflow:
79
197
 
@@ -94,8 +212,13 @@ workflow:
94
212
  script: { ref: actions }
95
213
  subcommand: feature-paths
96
214
  outputs:
97
- - { name: feature_dir, from: "$.feature_dir" }
98
- - { name: spec_file, from: "$.spec_file" }
215
+ - { name: feature_dir, from: "$.feature_dir" }
216
+ - { name: spec_file, from: "$.spec_file" }
217
+ - { name: spec_exists, from: "$.spec_exists" }
218
+ - { name: be_plan, from: "$.backend_plan_file" }
219
+ - { name: be_plan_exists, from: "$.backend_plan_exists" }
220
+ - { name: fe_plan, from: "$.frontend_plan_file" }
221
+ - { name: fe_plan_exists, from: "$.frontend_plan_exists" }
99
222
 
100
223
  - id: require_feature
101
224
  action: verify_context
@@ -103,6 +226,138 @@ workflow:
103
226
  on_empty: stop
104
227
  on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the aggregate, e.g. Order), then re-run analyze-slice."
105
228
 
229
+ # The anchor. Every judgement below resolves an id's meaning from this document, so a
230
+ # feature without one has nothing for the code to be measured against.
231
+ - id: require_spec
232
+ action: verify_context
233
+ require_true: spec_exists
234
+ on_false: stop
235
+ on_false_message: "No spec.md for the current feature. Run /appweaver specify first — the spec is this gate's oracle, and code cannot be conformed to a contract that does not exist."
236
+
237
+ # 1. WHICH STORY, if any. An id in the input scopes the gate to that story; anything
238
+ # else widens it to the feature. Deliberately NO ask: this gate has to be runnable
239
+ # unattended, and a gate that stops to ask cannot run in CI. Naming a story narrows
240
+ # it, naming none widens it, and both are legitimate runs.
241
+ - id: story_scan
242
+ action: execute_script
243
+ on_error: continue
244
+ script: { ref: actions }
245
+ subcommand: story-plan-check
246
+ args: ["--feature-dir", "{{feature_dir}}", "--input", "{{USER_INPUT}}"]
247
+ outputs:
248
+ - { name: story_resolved, from: "$.resolved" }
249
+ - { name: story, from: "$.story" }
250
+ - { name: story_path, from: "$.story_path" }
251
+ - { name: story_title, from: "$.story_title" }
252
+ - { name: story_type, from: "$.story_type" }
253
+ - { name: story_status, from: "$.story_status" }
254
+ - { name: story_dir, from: "$.story_dir" }
255
+ - { name: is_technical, from: "$.is_technical" }
256
+ - { name: exercises_csv, from: "$.exercises_csv" }
257
+ - { name: constraints_csv, from: "$.constraints_csv" }
258
+ - { name: current_version, from: "$.current_version" }
259
+ - { name: stale_csv, from: "$.stale_csv" }
260
+ - { name: stale_note, from: "$.stale_note" }
261
+ - { name: backlog_digest, from: "$.digest" }
262
+ - { name: story_be_doc, from: "$.be_doc_path" }
263
+ - { name: story_be_exists, from: "$.be_doc_exists" }
264
+ - { name: story_be_pass, from: "$.be_last_pass" }
265
+ - { name: story_fe_doc, from: "$.fe_doc_path" }
266
+ - { name: story_fe_exists, from: "$.fe_doc_exists" }
267
+ - { name: story_fe_pass, from: "$.fe_last_pass" }
268
+
269
+ # WHICH IDS ARE IN FLIGHT. On a feature-scoped run these are subtracted from the
270
+ # question: a requirement with an OPEN story is work someone is doing, and reporting
271
+ # it as missing code turns every partially-built feature red (law 39). Story-scoped
272
+ # runs subtract nothing — the story under analysis IS the work in flight.
273
+ - id: inflight_ids
274
+ action: execute_script
275
+ on_error: continue
276
+ script: { ref: actions }
277
+ subcommand: story-refs
278
+ args: ["--feature-dir", "{{feature_dir}}"]
279
+ outputs:
280
+ - { name: inflight_csv, from: "$.covered_csv" }
281
+
282
+ # 2. SCOPE + BRIDGE, stated once as the two sentences the trace pass reads. Both vars
283
+ # default to the feature-scoped wording (see `vars:`) and are overwritten only when
284
+ # a story actually resolved, so nothing here can half-apply.
285
+ - id: scope_story
286
+ condition: "{{story_resolved}} == true"
287
+ action: set
288
+ vars:
289
+ scope: "story"
290
+ scope_brief: "SCOPE — story {{story}} ({{story_title}}), type {{story_type}}, status {{story_status}}. In scope: {{exercises_csv}} (delivered by this story) and {{constraints_csv}} (bounds it must meet). EXTENT — the story's '## Requirements delivered' section says which PART of each id this slice delivers; judge the code against the spec's wording of THAT PART alone. A slice is never failed for the half of a requirement it never claimed, and an id no story has claimed yet is not this run's business."
291
+ # A technical story delivers no requirement, so `exercises:` is empty BY DESIGN and there
292
+ # is nothing to trace forward. Its real check already ran — the unchanged-suite oracle —
293
+ # and what is left for trace is the bounds it is still held to. Without this the pass
294
+ # would be handed "in scope: (nothing)" and would go looking for something to report.
295
+ - id: scope_story_technical
296
+ condition: "{{is_technical}} == true"
297
+ action: set
298
+ vars:
299
+ scope_brief: "SCOPE — story {{story}} ({{story_title}}) is a TECHNICAL story: a refactor, dependency upgrade or performance fix that delivers NO requirement. `exercises:` is empty by design, so there is no FR-/BR- to trace forward and NONE is expected. Judge only this: the code still meets the bounds this story is held to ({{constraints_csv}}), and nothing it changed has quietly altered behaviour the spec fixes. Its real oracle is the UNCHANGED test suite, which has already been checked this run. Report NO gap for a requirement — an unimplemented requirement is some other story's business, and a technical story that had to change behaviour was already caught upstream."
300
+
301
+ - id: scope_feature_note
302
+ condition: "{{story_resolved}} == false"
303
+ action: set
304
+ vars:
305
+ scope_brief: "SCOPE — the WHOLE FEATURE, because no story was named. In scope: every FR- and BR- you can see in spec.md EXCEPT these, which have OPEN stories and are work in flight rather than drift: {{inflight_csv}}. EXTENT — the whole of each in-scope id. Also in scope are the CN-/DC-/IC-/UX- the spec binds this feature by."
306
+ - id: bridge_story
307
+ condition: "{{story_be_exists}} == true"
308
+ action: set
309
+ vars:
310
+ bridge_note: "BRIDGE — the STORY DESIGN, service lane: design pass {{story_be_pass}} of {{story_be_doc}} (the LAST pass; earlier ones are superseded history). Its '### Changes' section is the map from this slice's ids to the files that realise them. The feature plans are in context underneath it as ARCHITECTURE — where those elements live — not as a second contract."
311
+ - id: bridge_story_ui
312
+ condition: "{{story_fe_exists}} == true"
313
+ action: set
314
+ vars:
315
+ bridge_note: "BRIDGE — the STORY DESIGN, both lanes: service pass {{story_be_pass}} of {{story_be_doc}} and UI pass {{story_fe_pass}} of {{story_fe_doc}} (the LAST pass of each; earlier ones are superseded history). Their '### Changes' sections are the map from this slice's ids to the files that realise them. A requirement with a user-facing surface is realised in BOTH lanes and needs a home in each. The feature plans are in context underneath as ARCHITECTURE, not as a second contract."
316
+
317
+ # 3. WHERE THE VERDICT GOES. A story-scoped verdict written at feature level would be
318
+ # overwritten by the next story's run and then read as the feature's, so each scope
319
+ # keeps its own file. gate-result writes the JSON beside the markdown and names it
320
+ # after it, so a job holding either path holds the other.
321
+ # A named story that matched nothing would otherwise widen the run to feature scope in
322
+ # silence, which is a different gate than the one that was asked for. Announced, never
323
+ # refused: the input may equally have been guidance that names no story at all.
324
+ - id: mark_unresolved_input
325
+ condition: "{{story_resolved}} == false"
326
+ action: set
327
+ vars: { unresolved_input: "{{USER_INPUT}}" }
328
+ - id: unresolved_input_notice
329
+ action: verify_context
330
+ require_value: unresolved_input
331
+ equals: ""
332
+ on_mismatch: continue
333
+ on_mismatch_message: |
334
+ No story matched "{{unresolved_input}}", so this run is FEATURE-SCOPED — every FR- and
335
+ BR- in the spec except the ids with open stories. If you meant to gate one story, re-run
336
+ naming it exactly as its file does. The open backlog:
337
+ {{backlog_digest}}
338
+
339
+ - id: report_paths
340
+ action: set
341
+ vars: { report_path: "{{feature_dir}}/slice-analyze-report.md" }
342
+ - id: report_paths_story
343
+ condition: "{{story_resolved}} == true"
344
+ action: set
345
+ vars: { report_path: "{{story_dir}}/slice-analyze-report.md" }
346
+
347
+ # 3a. INVALIDATE any previous verdict, before anything downstream can stop. A gate that
348
+ # writes its result only on completion leaves the LAST run's verdict on disk for
349
+ # every run that aborts — a red test loop, a broken referrer — and a pipeline
350
+ # reading that file would act on a certification this run never made. Every exit
351
+ # from here on therefore leaves at least a red; the real verdict overwrites it.
352
+ - id: gate_result_open
353
+ action: execute_script
354
+ script: { ref: actions }
355
+ subcommand: gate-result
356
+ # The lenient loader needs a flow sequence on ONE line.
357
+ args: ["--gate", "analyze-slice", "--feature-dir", "{{feature_dir}}", "--ok", "false", "--report", "{{report_path}}", "--story", "{{story}}", "--note", "analyze-slice started and has not reached a verdict; a run that stops before certifying leaves this red on purpose"]
358
+ outputs:
359
+ - { name: result_file, from: "$.result_file" }
360
+
106
361
  # Is a change OPEN on this feature? Read for NAVIGATION only — see the vars note. Safe
107
362
  # on the bootstrap path: with no active_version.json, version-read returns
108
363
  # active_change="" / has_change=false and exits 0, so nothing here is conditional on it.
@@ -115,21 +370,27 @@ workflow:
115
370
  - { name: active_change, from: "$.active_change" }
116
371
  - { name: has_change, from: "$.has_change" }
117
372
 
118
- # The slice's source files, from its OKF concept — the context all three passes below
119
- # work over (test fix loop, integrity triage, conformance). Replaces the retired
120
- # `design-paths` fence scan: design.md is tech-agnostic prose with no ```lang path=…
121
- # code blocks, so that scan returned nothing and every pass ran on an EMPTY file list.
122
- # Read-only, exits 0 with paths=[] when there is no concept yet; on_error: continue so
123
- # a missing bundle degrades the gate to its pre-existing (empty) behaviour, never blocks.
124
- - id: concept_paths
373
+ # 4. THE CODE UNDER ANALYSIS, from the slice's OKF concept — BOTH LANES. Every pass
374
+ # below works over this list. `concept-paths` reads `source_files` alone, so from
375
+ # the moment implement-ui started writing `ui_source_files` a UI-bearing requirement
376
+ # had no UI code in view to be traced to; `all_paths` is the union of the two.
377
+ # A story narrows the QUESTION, not the file set: a requirement can only be judged
378
+ # against everything that could realise it, and the concept records no per-story
379
+ # split. Read-only, exits 0 with empty lists when there is no concept yet;
380
+ # on_error: continue so a missing bundle degrades the gate rather than blocking it.
381
+ - id: concept_coverage
125
382
  action: execute_script
126
383
  on_error: continue
127
384
  script: { ref: xref }
128
- subcommand: concept-paths
129
- args: ["--feature-dir", "{{feature_dir}}"]
385
+ subcommand: concept-coverage
386
+ args: ["--feature-dir", "{{feature_dir}}", "--ids", "{{exercises_csv}}"]
130
387
  outputs:
131
- - { name: all_paths, from: "$.paths" }
132
- - { name: paths_missing, from: "$.missing" }
388
+ - { name: all_paths, from: "$.all_paths" }
389
+ - { name: paths_missing, from: "$.all_missing_csv" }
390
+ - { name: coverage_digest, from: "$.digest" }
391
+ - { name: be_count, from: "$.backend_count" }
392
+ - { name: fe_count, from: "$.frontend_count" }
393
+ - { name: okf_concept, from: "$.concept" }
133
394
 
134
395
  - id: resolve_pre_hooks
135
396
  action: execute_script
@@ -144,6 +405,15 @@ workflow:
144
405
 
145
406
  # ═══════ PASS 1 — BEHAVIOUR: the slice's tests vs the spec's acceptance criteria ═══════
146
407
  # Run tests → (if red) fix → re-run, up to 3×.
408
+ #
409
+ # For a TECHNICAL story the fix loop is told not to touch a test, because a test it
410
+ # edited to reach green is itself the behaviour change pass 2 exists to catch — and
411
+ # pass 2 counts this loop's edits, so the rule and the oracle agree.
412
+ - id: arm_no_test_edit
413
+ condition: "{{is_technical}} == true"
414
+ action: set
415
+ vars:
416
+ no_test_edit_rule: "THIS IS A TECHNICAL STORY. Do NOT modify, weaken, delete or rename a single test — the existing suite is this story's only oracle, and a test that has to change means behaviour changed and the work was a functional story citing a requirement. Fix the CODE until the tests as written pass. If a test genuinely cannot pass without changing it, STOP and say so rather than editing it: that finding is the point of the story, not an obstacle to it."
147
417
  - id: test_loop
148
418
  action: loop
149
419
  while: "{{test_ok}} != true"
@@ -172,6 +442,7 @@ workflow:
172
442
  action: build_context
173
443
  vars:
174
444
  test_log: "{{test_log}}"
445
+ no_test_edit_rule: "{{no_test_edit_rule}}"
175
446
  context:
176
447
  - { path: "{{spec_file}}" }
177
448
  context_from: all_paths
@@ -182,7 +453,7 @@ workflow:
182
453
  task: |
183
454
  Tests failed. Make them pass by fixing the code (or a genuinely wrong
184
455
  test) in the files in context, keeping behaviour aligned with the spec's
185
- acceptance criteria (spec in context). Test log:
456
+ acceptance criteria (spec in context). {{no_test_edit_rule}} Test log:
186
457
  {{test_log}}
187
458
  outputs:
188
459
  - { name: fixed_files, from: "$.files" }
@@ -201,7 +472,87 @@ workflow:
201
472
  ones this feature did not cause; matched is not meaningful in that case. Last log:
202
473
  {{test_log}}
203
474
 
204
- # ═══════ PASS 2 — DOMAIN CONFORMANCE: code vs the gate overlay checklist (validation) ═══════
475
+ # ═══════ PASS 2 — THE TECHNICAL-STORY ORACLE: the suite must be UNCHANGED ═══════
476
+ # A technical story (a refactor, a dependency upgrade, a performance fix) delivers no
477
+ # requirement, so `exercises:` is empty and there is no FR- to trace it to. The
478
+ # existing test suite is its whole oracle, and the rule is stronger than "the tests are
479
+ # green": a test that had to be MODIFIED means behaviour moved, and the work was a
480
+ # functional story citing a requirement (law 32). Run AFTER pass 1 on purpose, so the
481
+ # fix loop's own edits are counted — a gate that had to rewrite a test to reach green
482
+ # has proved the same thing.
483
+ #
484
+ # A new test is not a changed one: adding characterisation tests around a refactor is
485
+ # exactly the work a technical story exists to do. Deletions count as changes.
486
+ - id: test_diff
487
+ action: execute_script
488
+ on_error: continue
489
+ script: { ref: actions }
490
+ subcommand: test-diff
491
+ args: ["--feature-dir", "{{feature_dir}}"]
492
+ outputs:
493
+ - { name: oracle, from: "$.oracle" }
494
+ - { name: tests_changed_csv, from: "$.tests_changed_csv" }
495
+ - { name: tests_changed_count, from: "$.tests_changed_count" }
496
+ - { name: tdiff_digest, from: "$.digest" }
497
+ - { name: tdiff_base, from: "$.base" }
498
+ - { name: tdiff_base_kind, from: "$.base_kind" }
499
+
500
+ # The story's type and the diff's state, combined into ONE string so each case is a
501
+ # single comparison the evaluator can make. Both halves are strings, so this needs no
502
+ # boolean gymnastics — a bool rendered by {{ }} and a bool read by require_value never
503
+ # compare equal (law 43), which is why the type arrives as a word rather than a flag.
504
+ - id: oracle_case_set
505
+ action: set
506
+ vars: { oracle_case: "{{story_type}}/{{oracle}}" }
507
+ - id: oracle_fail
508
+ condition: "{{oracle_case}} == technical/changed"
509
+ action: set
510
+ vars:
511
+ tech_gate_ok: "false"
512
+ tech_note: "FAILED — {{tests_changed_count}} test(s) modified or deleted: {{tests_changed_csv}}"
513
+ - id: oracle_unverified
514
+ condition: "{{oracle_case}} == technical/unverified"
515
+ action: set
516
+ vars:
517
+ tech_note: "UNVERIFIED — this is not a git working copy, so the suite's history cannot be read. The oracle did not pass; it could not run."
518
+ - id: oracle_clean
519
+ condition: "{{oracle_case}} == technical/clean"
520
+ action: set
521
+ vars:
522
+ tech_note: "PASSED — no test was modified or deleted against {{tdiff_base_kind}} baseline {{tdiff_base}}."
523
+
524
+ # An oracle that could not be read never fails the gate — it announces. Refusing on the
525
+ # strength of a check that did not run is the mirror image of passing on one.
526
+ - id: oracle_unverified_notice
527
+ condition: "{{oracle_case}} == technical/unverified"
528
+ action: verify_context
529
+ require_value: oracle
530
+ equals: "clean"
531
+ on_mismatch: continue
532
+ on_mismatch_message: |
533
+ TECHNICAL-STORY ORACLE UNVERIFIED for story {{story}}. A technical story delivers no
534
+ requirement, so the unchanged test suite is the only thing that can say whether its
535
+ behaviour moved — and that history is not readable here (this is not a git working
536
+ copy). Everything else in this gate ran; this one check did not, and it is recorded
537
+ as unverified rather than as a pass in {{result_file}}.
538
+
539
+ - id: oracle_gate
540
+ action: verify_context
541
+ require_true: tech_gate_ok
542
+ on_false: stop
543
+ on_false_message: |
544
+ TECHNICAL-STORY ORACLE FAILED — story {{story}} is type `technical`, and a technical
545
+ story's oracle is the UNCHANGED test suite. These tests were modified or deleted:
546
+ {{tests_changed_csv}}
547
+ {{tdiff_digest}}
548
+ A test that had to change means behaviour changed, and behaviour that changes belongs
549
+ to a FUNCTIONAL story citing the requirement it moves. Two ways out, and they are not
550
+ interchangeable: restore the tests and make the refactor behaviour-preserving, or
551
+ accept that this work changes behaviour — close this story as cancelled, raise a
552
+ functional story for the change through /appweaver sync-spec (if the contract moves)
553
+ or /appweaver add-story, and build it there where an FR- can be traced to it.
554
+
555
+ # ═══════ PASS 3 — DOMAIN CONFORMANCE: code vs the gate overlay checklist ═══════
205
556
  # With tests green, scan the feature's CODE against the domain gate overlays (the
206
557
  # forbidden-patterns / refusal conditions / CI gates). The overlays load as the
207
558
  # checklist — VALIDATION only, never an authoring input to feature code. No-op for a
@@ -225,6 +576,8 @@ workflow:
225
576
  steps:
226
577
  - id: ctx_conformance
227
578
  action: build_context
579
+ vars:
580
+ no_test_edit_rule: "{{no_test_edit_rule}}"
228
581
  context:
229
582
  - { path: "{{spec_file}}" }
230
583
  context_from: all_paths
@@ -259,7 +612,8 @@ workflow:
259
612
  Fix these domain-conformance violations in the feature's code (in context),
260
613
  following the gate checklist rules (in the standards) and keeping the design's
261
614
  behaviour intact. Do NOT weaken or delete a test to hide a violation — fix the
262
- code. After fixing, re-run the tests (Bash) to confirm they stay green.
615
+ code. {{no_test_edit_rule}} After fixing, re-run the tests (Bash) to confirm
616
+ they stay green.
263
617
  Violations: {{conformance_violations}}
264
618
  outputs:
265
619
  - { name: conformance_fixes, from: "$.files" }
@@ -268,9 +622,9 @@ workflow:
268
622
  action: verify_context
269
623
  require_true: conformance_ok
270
624
  on_false: stop
271
- on_false_message: "Code violates the domain gate checklist after 2 fix attempts (violations: {{conformance_violations}}). These are the domain constraints design/implement must satisfy — fix the code, or if the constraint itself is wrong, correct it upstream (/appweaver specify → design)."
625
+ on_false_message: "Code violates the domain gate checklist after 2 fix attempts (violations: {{conformance_violations}}). These are the domain constraints design/implement must satisfy — fix the code, or if the constraint itself is wrong, correct it upstream (/appweaver specify → plan)."
272
626
 
273
- # ═══════ INTEGRITY — did this feature's change break any aggregate that USES it? ═══════
627
+ # ═══════ PASS 4 — INTEGRITY: did this change break any aggregate that USES it? ═══════
274
628
  # The slice is green + domain-conformant on its own. But a change to a feature can break
275
629
  # any OTHER aggregate that references it. Ask the OKF Cross-References index "who refers to
276
630
  # me?" (the transitive referrer closure — C→B→A all count), then RE-RUN the feature's tests
@@ -288,7 +642,7 @@ workflow:
288
642
  outputs:
289
643
  - { name: referrer_csv, from: "$.referrer_closure_csv" }
290
644
  - { name: referrer_count, from: "$.referrer_closure_count" }
291
- - { name: referrer_names, from: "$.referrer_closure" }
645
+ - { name: referrer_names, from: "$.referrer_closure_csv" }
292
646
 
293
647
  - id: run_integrity
294
648
  action: execute_script
@@ -359,7 +713,123 @@ workflow:
359
713
  on_false: stop
360
714
  on_false_message: "Integrity check failed — this feature's change breaks a test in an aggregate that REFERS to it. Referrer blast radius ({{referrer_count}}): {{referrer_names}}. Triage written to {{feature_dir}}/broken-referrers.md — resolve each in the referrer's own slice (or reconsider this feature's contract change), then re-run. Last log: {{integrity_log}}"
361
715
 
362
- # ═══════ PASS 3 — OKF KNOWLEDGE: keep the slice's concept true to what now exists ═══════
716
+ # ═══════ PASS 5 — TRACE: spec ↔ code, through the design ═══════════════════
717
+ # The question this gate exists to answer, asked last because everything before it is a
718
+ # precondition: the code runs, it changed no oracle it should not have, it violates no
719
+ # domain constraint, and it broke nobody. What is left is the only thing that matters —
720
+ # is this the thing the contract asked for?
721
+ #
722
+ # READ-ONLY. It reports and never repairs: a missing requirement is feature work, and
723
+ # writing it here would produce code no design pass names and no gate ever conformed.
724
+ # Nothing between this step and the report can stop the run, so the verdict always
725
+ # reaches both the markdown and the JSON.
726
+ - id: ctx_trace
727
+ action: build_context
728
+ vars:
729
+ scope_brief: "{{scope_brief}}"
730
+ bridge_note: "{{bridge_note}}"
731
+ coverage_digest: "{{coverage_digest}}"
732
+ stale_note: "{{stale_note}}"
733
+ context:
734
+ - { path: "{{spec_file}}" } # THE ORACLE — the contract's own wording
735
+ - { path: "{{story_path}}", optional: true } # the extent: what this slice claims of each id
736
+ - { path: "{{story_be_doc}}", optional: true } # the BRIDGE, service lane — the latest pass
737
+ - { path: "{{story_fe_doc}}", optional: true } # the BRIDGE, UI lane — the latest pass
738
+ - { path: "{{be_plan}}", optional: true } # architecture: where those elements live
739
+ - { path: "{{fe_plan}}", optional: true }
740
+ - { path: "{{feature_dir}}/interfaces.md", optional: true } # the published surface, cited by IF- id
741
+ - { path: "{{feature_dir}}/adr.md", optional: true } # ratified decisions
742
+ context_from: all_paths # the CODE — both lanes, from the OKF concept
743
+ - id: trace
744
+ action: agent_query
745
+ tools: Read # follow a call out of the listed files when a trace needs it
746
+ include_standards: false # the contract is the spec, not a rule corpus
747
+ schema: { ok: true, bridge_ok: true, gap_count: 0, gap_refs: "<comma-separated spec ids the gaps touch>", coverage: "<one line per in-scope id — id | what the spec requires | the design element that maps it | the code that realises it | DELIVERED or GAP>", gaps: [ { severity: "high", spec_ref: "<FR/BR/CN/DC/IC/UX id>", kind: "code-missing|code-wrong|bridge-missing|spec-wrong", where: "<file:symbol, or the document>", gap: "<what the spec requires and what is actually there>", route: "<the command that fixes it>" } ], bridge_gaps: "<one line per in-scope id no design maps, or empty>" }
748
+ mock: { ok: true, bridge_ok: true, gap_count: 0, gap_refs: "", coverage: "", gaps: [], bridge_gaps: "" }
749
+ task: |
750
+ spec.md is the ORACLE. Judge the CODE against it and report every id it fails to
751
+ deliver. The design is the BRIDGE that makes the question answerable — it says
752
+ which elements realise which id — and it is never the standard: where the design
753
+ and the spec disagree about an in-scope id, the SPEC is what the code is measured
754
+ against, and the disagreement is itself a finding.
755
+
756
+ {{scope_brief}}
757
+
758
+ {{bridge_note}}
759
+
760
+ The code in context is this slice's whole OKF file list, both lanes. Use Read to
761
+ follow anything it calls that a trace needs. Do NOT modify a single file — this
762
+ step is read-only by contract.
763
+
764
+ {{coverage_digest}}
765
+
766
+ HOW TO TRACE each in-scope id, in this order:
767
+ (1) Resolve the id's wording from spec.md. That wording — not the design's
768
+ restatement of it — is the standard.
769
+ (2) Find the DESIGN element that maps it (the story pass's "### Changes" and
770
+ "### Approach", or the feature plan's traceability section). If nothing maps
771
+ it, that is a BRIDGE gap: record it in `bridge_gaps` with kind
772
+ "bridge-missing". Then keep going — code may well deliver the id anyway, and
773
+ an undocumented realisation is a documentation defect, not a broken contract.
774
+ (3) Find the CODE that realises the element, and read it. Judge whether it
775
+ delivers what the spec words, to the extent this scope claims.
776
+ (4) Where the id has a user-facing surface, it is realised in BOTH lanes and needs
777
+ a home in each. A UX- lives only in the UI lane; a BR- only in the service
778
+ lane. Do not report a UI gap for a slice that has no UI lane in scope.
779
+
780
+ CLASSIFY every gap by `kind`, because the four routes are different commands:
781
+ code-missing the design maps the id and no code realises it → /appweaver implement
782
+ code-wrong code exists and does something else → /appweaver implement
783
+ bridge-missing no design maps the id at all → /appweaver story-plan
784
+ spec-wrong the code is right and the SPEC words it wrongly → /appweaver sync-spec
785
+ Use `spec-wrong` sparingly and only with a reason: the spec is the oracle, and
786
+ "the code disagrees" is evidence about the code first.
787
+
788
+ THESE ARE NOT GAPS, and reporting them is noise that hides the real ones:
789
+ - an id that is out of scope for this run — another story's work, or one nobody
790
+ has claimed yet;
791
+ - the part of a requirement this scope explicitly does not claim;
792
+ - style, naming, structure, layering or test-coverage opinions — other gates own
793
+ every one of those;
794
+ - anything a design pass deliberately deferred and named under its risks;
795
+ - a missing element you inferred from the design alone with no spec basis. The
796
+ design is the bridge, not the contract.
797
+
798
+ Fill `coverage` with ONE LINE PER IN-SCOPE ID, delivered or not — that table is the
799
+ traceability record this gate exists to leave behind, and it is written on a green
800
+ run too. Set gap_count and gap_refs from `gaps`. Set ok=true ONLY when every
801
+ in-scope id is delivered as the spec words it. Set bridge_ok=false when any
802
+ in-scope id has no design mapping it, INDEPENDENTLY of ok — working code with a
803
+ documentation gap is a true state and must be reportable as one.
804
+ {{stale_note}}
805
+ outputs:
806
+ - { name: trace_ok, from: "$.ok" }
807
+ - { name: bridge_ok, from: "$.bridge_ok" }
808
+ - { name: trace_gaps, from: "$.gaps" }
809
+ - { name: trace_gap_count, from: "$.gap_count" }
810
+ - { name: trace_gap_refs, from: "$.gap_refs" }
811
+ - { name: trace_coverage, from: "$.coverage" }
812
+ - { name: bridge_gaps, from: "$.bridge_gaps" }
813
+
814
+ # The bridge's integrity is ANNOUNCED, not gated. An id the design never mapped, whose
815
+ # code nonetheless delivers the spec, is a documentation defect — failing the slice for
816
+ # it would refuse working, contract-satisfying code over a missing row in a document
817
+ # the next story-plan pass rewrites anyway.
818
+ - id: bridge_notice
819
+ condition: "{{bridge_ok}} == false"
820
+ action: verify_context
821
+ require_value: bridge_ok
822
+ equals: "true"
823
+ on_mismatch: continue
824
+ on_mismatch_message: |
825
+ BRIDGE INCOMPLETE — in-scope ids that no design maps:
826
+ {{bridge_gaps}}
827
+ The code may well deliver them; what is missing is the record of WHERE. Nothing was
828
+ failed for this. Append a pass that maps them — /appweaver story-plan for the story
829
+ that owns the id, or /appweaver plan when the gap is feature-level — so the next run
830
+ of this gate can trace them instead of inferring them.
831
+
832
+ # ═══════ OKF KNOWLEDGE — keep the slice's concept true to what now exists ═══════
363
833
  # analyze-slice is the LAST stage that changes a greenfield slice's files: both fix loops
364
834
  # above run with Write/Edit, and the conformance loop is explicitly asked to ADD missing
365
835
  # constraint tests. Those files did not exist when implement wrote the OKF concept, so its
@@ -367,20 +837,18 @@ workflow:
367
837
  # the concept holds the full set implement recorded, these two vars hold only what the fix
368
838
  # loops touched), then report cross-reference drift.
369
839
  #
370
- # These two steps are now the WHOLE write-back: the former `design-reconcile` step that
371
- # preceded them was deleted (it copied on-disk file bodies into path-tagged design fences
372
- # — meaningless once design.md became tech-agnostic prose, and it read the same
373
- # fixed_files/paths these steps already own). Recording the files in the concept IS the
374
- # current reconcile.
840
+ # --lane backend: both fix loops repair the code that failed a test or an overlay rule,
841
+ # and that is service-lane code. A UI file edited here would land in the service list —
842
+ # the same defect --lane was added to close — so if that ever becomes possible the fix
843
+ # is a second, lane-split merge, not a wider default.
375
844
  #
376
845
  # Deliberately NOT re-running `record`: the Depends-On/Cross-References edges derive from
377
- # design.md §1 + its External integrations table, and no step in this stage edits
378
- # design.md — the edge sources
379
- # cannot have moved here. `--origin` is likewise omitted: concept-upsert setdefaults it,
380
- # so an adopted (brownfield) slice keeps its `reverse-engineered` provenance instead of
381
- # being restamped greenfield.
846
+ # the feature plan's §1 + its External integrations table, and no step in this stage edits
847
+ # that plan — the edge sources cannot have moved here. `--origin` is likewise omitted:
848
+ # concept-upsert setdefaults it, so an adopted (brownfield) slice keeps its
849
+ # `reverse-engineered` provenance instead of being restamped greenfield.
382
850
  #
383
- # Advisory by design (on_error: continue, no gate): a green, domain-conformant slice is
851
+ # Advisory by design (on_error: continue, no gate): a green, contract-satisfying slice is
384
852
  # never failed over its knowledge index — drift is reported in the outputs and fixed by
385
853
  # `aw-xref validate --prune` / re-running the owning slice's `record`.
386
854
  - id: okf_merge_test_fixes
@@ -389,7 +857,7 @@ workflow:
389
857
  on_error: continue
390
858
  script: { ref: xref }
391
859
  subcommand: concept-upsert
392
- args: ["--feature-dir", "{{feature_dir}}", "--source-files", "{{fixed_files}}", "--merge"]
860
+ args: ["--feature-dir", "{{feature_dir}}", "--source-files", "{{fixed_files}}", "--merge", "--lane", "backend"]
393
861
  outputs:
394
862
  - { name: okf_concept, from: "$.concept" }
395
863
 
@@ -399,7 +867,7 @@ workflow:
399
867
  on_error: continue
400
868
  script: { ref: xref }
401
869
  subcommand: concept-upsert
402
- args: ["--feature-dir", "{{feature_dir}}", "--source-files", "{{conformance_fixes}}", "--merge"]
870
+ args: ["--feature-dir", "{{feature_dir}}", "--source-files", "{{conformance_fixes}}", "--merge", "--lane", "backend"]
403
871
  outputs:
404
872
  - { name: okf_concept, from: "$.concept" }
405
873
 
@@ -412,6 +880,86 @@ workflow:
412
880
  - { name: okf_ok, from: "$.ok" }
413
881
  - { name: okf_orphans, from: "$.orphan_count" }
414
882
 
883
+ # ═══════ THE RECORD — written on EVERY run that gets here, green or red ═══════
884
+ # A gate that leaves nothing behind when it passes gives a reviewer no evidence that it
885
+ # ran at all, and the spec→design→code table is the whole point of tracing rather than
886
+ # merely testing.
887
+ - id: ctx_report
888
+ action: build_context
889
+ vars:
890
+ scope_brief: "{{scope_brief}}"
891
+ trace_coverage: "{{trace_coverage}}"
892
+ trace_gaps: "{{trace_gaps}}"
893
+ bridge_gaps: "{{bridge_gaps}}"
894
+ tech_note: "{{tech_note}}"
895
+ tdiff_digest: "{{tdiff_digest}}"
896
+ - id: write_report
897
+ action: agent_author
898
+ include_standards: false
899
+ output_target: { type: file, path: "{{report_path}}" }
900
+ task: |
901
+ Write the Slice Analyze Report to the output. It records what this gate found
902
+ tracing spec.md through the design to the code.
903
+
904
+ Verdict: contract satisfied={{trace_ok}}, bridge complete={{bridge_ok}}.
905
+ Gaps: {{trace_gap_count}} on {{trace_gap_refs}}.
906
+ Scope: {{scope}}-scoped, story "{{story}}" (empty on a feature-scoped run) ·
907
+ spec version {{current_version}}.
908
+ {{scope_brief}}
909
+ Passes: tests green={{test_ok}} (tool {{test_tool}}); technical oracle —
910
+ {{tech_note}}; domain conformance={{conformance_ok}}; integrity fence={{integrity_ok}}
911
+ over {{referrer_count}} referrer(s) ({{referrer_names}}). Code analysed:
912
+ {{be_count}} service file(s) + {{fe_count}} UI file(s) from the OKF concept
913
+ {{okf_concept}}; declared but absent from disk: {{paths_missing}}.
914
+ Coverage rows:
915
+ {{trace_coverage}}
916
+ Gaps:
917
+ {{trace_gaps}}
918
+ Bridge gaps:
919
+ {{bridge_gaps}}
920
+
921
+ Markdown format:
922
+ - `# Slice Analyze Report — <aggregate>` and, under it, a line naming the scope
923
+ (story id or "whole feature"), the spec version, and when this ran.
924
+ - A verdict line: "verdict: DELIVERS ✓" or "verdict: GAPS ✗ · <n> open". Add a
925
+ second line for the bridge when it is incomplete.
926
+ - `## Traceability (spec → design → code)`: a table
927
+ | spec id | what the spec requires | design element | code | status |
928
+ one row per in-scope id, from the coverage rows above. This is the reason the
929
+ report exists — write it on a green run too. If a row's design cell is empty the
930
+ id is unbridged; say so in the cell rather than leaving it blank.
931
+ - `## Gaps`: a table | # | severity | spec ref | kind | where | gap | route |.
932
+ Omit the section when there were none.
933
+ - `## Passes`: one line each for behaviour, the technical-story oracle, domain
934
+ conformance and integrity, with the result given above. Say plainly when the
935
+ oracle was UNVERIFIED rather than passed.
936
+ - `## How to resolve`: group the gaps by route and give the exact command for
937
+ each — code-missing/code-wrong go to /appweaver implement (correct the design
938
+ pass first if the pass is what is wrong), bridge-missing to /appweaver story-plan,
939
+ spec-wrong to /appweaver sync-spec. Omit on a clean run.
940
+ Do not invent ids, versions, dates or findings beyond the values given above.
941
+ Return the complete report markdown only.
942
+
943
+ # The machine-readable verdict, written beside the report by the SCRIPT — CI cannot
944
+ # branch on prose, and a verdict is exactly the kind of dated, derived record the model
945
+ # never writes. `ok` from this step means the file was written; the pass/fail verdict is
946
+ # `status` inside it.
947
+ - id: gate_result
948
+ action: execute_script
949
+ script: { ref: actions }
950
+ subcommand: gate-result
951
+ # The lenient loader needs a flow sequence on ONE line.
952
+ args: ["--gate", "analyze-slice", "--feature-dir", "{{feature_dir}}", "--ok", "{{trace_ok}}", "--spec-version", "{{current_version}}", "--story", "{{story}}", "--report", "{{report_path}}", "--gap-count", "{{trace_gap_count}}", "--gap-refs", "{{trace_gap_refs}}", "--field", "scope={{scope}}", "--field", "story_type={{story_type}}", "--field", "bridge_ok={{bridge_ok}}", "--field", "tests_green={{test_ok}}", "--field", "test_suite_diff={{oracle}}", "--field", "technical_oracle={{tech_note}}", "--field", "tests_changed={{tests_changed_csv}}", "--field", "domain_conformance={{conformance_ok}}", "--field", "integrity={{integrity_ok}}", "--field", "referrers={{referrer_names}}", "--field", "okf_concept={{okf_concept}}", "--field", "okf_index_ok={{okf_ok}}", "--field", "stale_ids={{stale_csv}}"]
953
+ outputs:
954
+ - { name: result_file, from: "$.result_file" }
955
+
956
+ # THE GATE. Last, so the report and the verdict are on disk whichever way it goes.
957
+ - id: trace_gate
958
+ action: verify_context
959
+ require_true: trace_ok
960
+ on_false: stop
961
+ on_false_message: "The code does not deliver the contract — {{trace_gap_count}} gap(s) on {{trace_gap_refs}}. The traceability report is at {{report_path}} and the machine-readable verdict at {{result_file}}. Nothing was repaired: a missing requirement is feature work, and writing it inside a gate would produce code no design pass names and no gate ever conformed. Route by each gap's `kind` — code-missing / code-wrong: correct the design pass if it is what is wrong (/appweaver story-plan), then /appweaver implement; bridge-missing: /appweaver story-plan, which appends a pass that maps the id; spec-wrong: /appweaver sync-spec, which is the ONLY way the contract moves. Then re-run /appweaver analyze-slice."
962
+
415
963
  - id: resolve_post_hooks
416
964
  action: execute_script
417
965
  script: { ref: actions }
@@ -425,13 +973,24 @@ workflow:
425
973
 
426
974
  outputs:
427
975
  feature_dir: "{{feature_dir}}"
428
- test_tool: "{{test_tool}}"
976
+ scope: "{{scope}} — story {{story}} ({{story_type}}), spec {{current_version}}"
977
+ delivers_contract: "{{trace_ok}}"
978
+ bridge_complete: "{{bridge_ok}}"
979
+ gaps: "{{trace_gap_count}} on {{trace_gap_refs}}"
980
+ gap_detail: "{{trace_gaps}}"
981
+ bridge_gaps: "{{bridge_gaps}}"
982
+ report: "{{report_path}}"
983
+ result_json: "{{result_file}}"
984
+ tests: "green={{test_ok}} (tool {{test_tool}})"
985
+ technical_oracle: "{{tech_note}}"
986
+ domain_conformance: "{{conformance_ok}}"
429
987
  referrer_count: "{{referrer_count}}"
430
988
  referrer_blast_radius: "{{referrer_names}}"
431
- okf_concept: "{{okf_concept}}"
432
- okf_index_ok: "{{okf_ok}}"
433
- okf_orphans: "{{okf_orphans}}"
989
+ code_analysed: "{{be_count}} service + {{fe_count}} UI file(s) from {{okf_concept}}"
434
990
  # Declared in the concept but absent on disk — advisory index drift, never gated.
435
991
  stale_source_files: "{{paths_missing}}"
992
+ okf_index_ok: "{{okf_ok}}"
993
+ okf_orphans: "{{okf_orphans}}"
994
+ stale_ids: "{{stale_csv}}"
436
995
  active_change: "{{active_change}}"
437
- next: "CHANGE OPEN ({{active_change}}): /appweaver close-delta — the slice is green, domain-conformant and did not break a referrer, and its delta is already merged into the master docs, so the change may now be archived and the version bumped. NO OPEN CHANGE: /appweaver run."
996
+ next: "CHANGE OPEN ({{active_change}}): /appweaver close-delta — the slice delivers the contract, is domain-conformant and broke no referrer, and its delta is already merged into the master docs, so the change may now be archived and the version bumped. NO OPEN CHANGE: /appweaver close-story {{story}} to ratify its ADRs, flip its interface entries live and archive its design passes — then /appweaver run."