aw-spec-cli 0.1.28 → 0.1.30

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 (67) hide show
  1. package/assets/.amp/agent.json +22 -0
  2. package/assets/.amp/settings-edit.json +5 -0
  3. package/assets/.amp/settings-read.json +12 -0
  4. package/assets/.appweaver/DESIGN-FORMAT.md +9 -0
  5. package/assets/.appweaver/STACK-PROFILES.md +102 -15
  6. package/assets/.appweaver/orchestrator/modules/delta-plan.orch.yaml +40 -18
  7. package/assets/.appweaver/orchestrator/modules/delta-spec.orch.yaml +56 -4
  8. package/assets/.appweaver/orchestrator/modules/lane-gate.orch.yaml +17 -1
  9. package/assets/.appweaver/orchestrator/rev-templates/ui-design-template.skeleton.md +12 -3
  10. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan-delta.orch.yaml +289 -24
  11. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +370 -85
  12. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +349 -65
  13. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +39 -7
  14. package/assets/.appweaver/orchestrator/workflows/aw-spec-build.orch.yaml +63 -4
  15. package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +5 -4
  16. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +430 -138
  17. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +392 -76
  18. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-lane.orch.yaml +104 -0
  19. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-stack.orch.yaml +20 -3
  20. package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +26 -3
  21. package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +314 -44
  22. package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +249 -20
  23. package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +17 -2
  24. package/assets/.appweaver/scripts/python/aw-plangate.py +109 -19
  25. package/assets/.appweaver/scripts/python/aw-stack.py +1295 -9
  26. package/assets/.appweaver/templates/delta-design-template.md +7 -5
  27. package/assets/.appweaver/templates/delta-spec-template.md +2 -0
  28. package/assets/.appweaver/templates/design-template.md +17 -0
  29. package/assets/.appweaver/templates/ui-design-template.md +19 -4
  30. package/assets/.augment/agent.json +22 -0
  31. package/assets/.augment/commands/appweaver.md +12 -0
  32. package/assets/.cline/agent.json +22 -0
  33. package/assets/.clinerules/workflows/appweaver.md +7 -0
  34. package/assets/.codex/agent.json +22 -0
  35. package/assets/.continue/agent.json +22 -0
  36. package/assets/.continue/prompts/appweaver.md +13 -0
  37. package/assets/.cursor/agent.json +22 -0
  38. package/assets/.cursor/commands/appweaver.md +7 -0
  39. package/assets/.devin/agent.json +22 -0
  40. package/assets/.devin/workflows/appweaver.md +11 -0
  41. package/assets/.factory/agent.json +22 -0
  42. package/assets/.factory/commands/appweaver.md +12 -0
  43. package/assets/.gemini/agent.json +22 -0
  44. package/assets/.github/prompts/appweaver.prompt.md +13 -0
  45. package/assets/.kilo/agent.json +28 -0
  46. package/assets/.kilo/agents/appweaver-edit.md +12 -0
  47. package/assets/.kilo/agents/appweaver-read.md +12 -0
  48. package/assets/.kilo/commands/appweaver.md +11 -0
  49. package/assets/.kiro/agent.json +22 -0
  50. package/assets/.kiro/skills/appweaver/SKILL.md +12 -0
  51. package/assets/.opencode/agent.json +22 -0
  52. package/assets/.opencode/agents/appweaver-edit.md +12 -0
  53. package/assets/.opencode/agents/appweaver-read.md +13 -0
  54. package/assets/.opencode/commands/appweaver.md +11 -0
  55. package/assets/.roo/agent.json +22 -0
  56. package/assets/.roo/commands/appweaver.md +12 -0
  57. package/dist/commands/init.d.ts.map +1 -1
  58. package/dist/commands/init.js +14 -1
  59. package/dist/commands/init.js.map +1 -1
  60. package/dist/utils/config.d.ts.map +1 -1
  61. package/dist/utils/config.js +14 -0
  62. package/dist/utils/config.js.map +1 -1
  63. package/dist/utils/copy-assets.d.ts +16 -3
  64. package/dist/utils/copy-assets.d.ts.map +1 -1
  65. package/dist/utils/copy-assets.js +29 -14
  66. package/dist/utils/copy-assets.js.map +1 -1
  67. package/package.json +1 -1
@@ -57,6 +57,22 @@
57
57
  # repair loops stay in analyze-slice, where the merged master is the anchor and the job is
58
58
  # fixing what is BROKEN rather than building what is ABSENT.
59
59
  #
60
+ # ONLY THE LANES THE CHANGE NAMES THAT YOU WORK ON ARE GATED (.appweaver/lane.json). The
61
+ # change menu lists only the changes whose **Lanes** line names a working lane. A lane is
62
+ # gated when the change names it, you work on it and the change has its delta plan, and each
63
+ # gated lane runs its OWN tests (`test-run --kind`), so a backend developer's gate is never
64
+ # failed by the frontend's suite. A named lane somebody else works on is gated by that
65
+ # developer's own run; this one says so in a note and does not wait for it.
66
+ #
67
+ # EACH GATED LANE'S RESULT IS RECORDED in the change's directory, pass or fail, as
68
+ # `.aw-slice-gate.<lane>.json` (slice-gate-stamp), and is committed with the change. pass =
69
+ # no test of the lane failed AND its requirement checks met; a lane that declares no test
70
+ # command is green with nothing run, and the record's tests_ran says so. Two developers
71
+ # write two different files, so their results never collide, and together they are the
72
+ # record of which lanes of the change were shown to be built. The REPORT is split the same
73
+ # way: a developer working one lane of two writes delta-slice-analyze-report.<lane>.md, and
74
+ # a developer on every lane writes delta-slice-analyze-report.md.
75
+ #
60
76
  # NO NEW RULE-MAP HEADING. It loads `## /aw-spec-analyze-slice` — the same testing +
61
77
  # coding-standards set its master twin reads — so no project has to add configuration to
62
78
  # get this gate. The DOMAIN gate overlays are deliberately NOT loaded: full domain
@@ -70,8 +86,8 @@
70
86
  # =============================================================================
71
87
 
72
88
  name: aw-spec-analyze-slice-delta
73
- version: 1.2.0
74
- description: "The delta-spec↔delta-code gate — verify that the open change DOES WHAT IT WAS ASKED TO DO, before it reaches the master documents. delta-spec.md is the anchor, exactly as it is for analyze-plan-delta. Asks which open change it is gating, runs the slice's tests (the first execution on the change path, since build only compiles), then evaluates the code and its paired tests against the change's REQUIREMENTS — every ADDED or MODIFIED delta-spec item realised in code, every one covered by a test that cites its id, every invariant the model delta adds actually enforced — with the delta plan in context as the map from requirement to code and cross-checked in its own right, so an element the plan promised and nobody built is caught too. The dynamic twin of analyze-plan-delta and the delta twin of analyze-slice, which cannot see a missing implementation because a behaviour that was never built leaves no failing test. Verify-only — it reports each finding with the command that closes it and stops; it generates no code and repairs nothing."
89
+ version: 1.3.0
90
+ description: "The delta-spec↔delta-code gate — verify that the open change DOES WHAT IT WAS ASKED TO DO, before it reaches the master documents. delta-spec.md is the anchor, exactly as it is for analyze-plan-delta. Asks which open change it is gating (only the changes naming a lane you work on are listed) and gates only the lanes the change names that you work on. Each gated lane runs its own tests (the first execution on the change path, since build only compiles), then its code and paired tests are evaluated against the change's REQUIREMENTS — every ADDED or MODIFIED delta-spec item realised in code, every one covered by a test that cites its id, every invariant the model delta adds actually enforced — with the delta plan in context as the map from requirement to code and cross-checked in its own right, so an element the plan promised and nobody built is caught too. Each gated lane's pass or fail is recorded in the change as .aw-slice-gate.<lane>.json. The dynamic twin of analyze-plan-delta and the delta twin of analyze-slice, which cannot see a missing implementation because a behaviour that was never built leaves no failing test. Verify-only — it reports each finding with the command that closes it and stops; it generates no code and repairs nothing."
75
91
  agent_agnostic: true
76
92
  dialect: aw-spec.orchestrator/v1
77
93
 
@@ -109,27 +125,71 @@ vars:
109
125
  delta_frontend_plan: ""
110
126
  delta_frontend_plan_exists: "false"
111
127
  domain_model: ""
112
- # WHICH LANES THIS GATE CHECKS — a lane is gated when it is DECLARED and this change has
113
- # a delta plan for it. An experience-untouched change has no frontend delta plan, and
114
- # skipping that lane's checks is correct rather than a pass by omission. Each AND is an
115
- # ordered assignment; the engine has no compound conditions.
128
+ # WHICH LANES THIS GATE CHECKS — a lane is gated when it is DECLARED for this feature, the
129
+ # change's **Lanes** line NAMES it, this developer WORKS on it (.appweaver/lane.json) and
130
+ # the change has a delta plan for it. An experience-untouched change has no frontend delta
131
+ # plan, and skipping that lane's checks is correct rather than a pass by omission. Each AND
132
+ # is an ordered assignment; the engine has no compound conditions.
116
133
  be_declared: "false"
117
134
  fe_declared: "false"
118
135
  fe_excluded: "false" # this feature records no surface in the declared frontend lane (feature-lanes)
136
+ be_working: "false"
137
+ fe_working: "false"
138
+ working_lane: ""
139
+ working_kind: "" # the one lane worked when one lane of two is; empty otherwise
140
+ working_root: "" # the working lane's root and test path when one lane of two is worked
141
+ working_test_path: ""
142
+ under_note: ""
143
+ report_file: "" # delta-slice-analyze-report[.<working_kind>].md in the change
144
+ cr_lanes_csv: ""
145
+ cr_touches_backend: "true" # every lane until cr-paths answers
146
+ cr_touches_frontend: "true"
147
+ be_named: "false" # declared AND the change names it
148
+ fe_named: "false"
149
+ be_awaited: "false" # named, not worked ⇒ gated by the developer who works on it
150
+ fe_awaited: "false"
151
+ no_awaited: "true"
152
+ awaited_txt: ""
153
+ asd_hidden_note: "" # open changes naming only a lane not worked here
154
+ asd_hidden_csv: ""
155
+ asd_preselect_hidden: ""
156
+ preselect_visible: "true"
119
157
  be_gated: "false"
120
158
  fe_gated: "false"
121
159
  any_lane: "false"
122
160
  # Slice evidence.
123
161
  source_files: []
124
162
  source_count: "0"
125
- test_ok: "false"
126
- test_tool: ""
127
- test_log: ""
128
- test_excerpt: ""
129
- test_scoped: "false"
130
- test_ran: "false"
131
- test_matched: "false"
132
- # Verdict.
163
+ # Each gated lane's own test run. `*_test_vote` is what a lane casts into test_ok — its
164
+ # result when gated, "true" when not — so a lane that was not gated never votes, and a
165
+ # gated lane whose run returned nothing votes red.
166
+ be_test_ok: "false"
167
+ be_test_tool: ""
168
+ be_test_log: ""
169
+ be_test_excerpt: ""
170
+ be_test_scoped: "false"
171
+ be_test_ran: "false"
172
+ be_test_matched: "false"
173
+ be_test_vote: "true"
174
+ fe_test_ok: "false"
175
+ fe_test_tool: ""
176
+ fe_test_log: ""
177
+ fe_test_excerpt: ""
178
+ fe_test_scoped: "false"
179
+ fe_test_ran: "false"
180
+ fe_test_matched: "false"
181
+ fe_test_vote: "true"
182
+ test_ok: "false" # every gated lane's tests green
183
+ # Each lane's state in words, for the report and the stop message; a gated lane's ends on
184
+ # its recorded verdict (pass | fail).
185
+ be_state: "not gated — no backend lane for this feature"
186
+ fe_state: "not gated — no frontend lane for this feature"
187
+ be_verdict: ""
188
+ fe_verdict: ""
189
+ # Verdict. `*_req_vote` is what a lane casts into requirements_ok: "true" when it was not
190
+ # gated; when it was, true only on its check's explicit ok=true.
191
+ be_req_vote: "true"
192
+ fe_req_vote: "true"
133
193
  requirements_ok: "false"
134
194
  # Findings split by the command that closes them (gap-route). Defaults read as
135
195
  # "nothing found", so a run where the router could not execute still prints.
@@ -199,7 +259,8 @@ workflow:
199
259
  # active_version.json for a service-only slice in a repo that also has a UI. Re-read with
200
260
  # --feature-dir so a lane the feature records no surface in is absent here: its code is
201
261
  # not gated against a plan it never had. With no feature resolved the argument is dropped
202
- # and the repo's answer stands.
262
+ # and the repo's answer stands. The WORKING lanes are read here too, so a lane the feature
263
+ # records no surface in is not worked either.
203
264
  - id: feature_lanes
204
265
  action: execute_script
205
266
  on_error: continue
@@ -207,9 +268,15 @@ workflow:
207
268
  subcommand: lane-resolve
208
269
  args: ["--feature-dir", "{{feature_dir}}"]
209
270
  outputs:
210
- - { name: be_declared, from: "$.backend_declared" }
211
- - { name: fe_declared, from: "$.frontend_declared" }
212
- - { name: fe_excluded, from: "$.frontend_feature_excluded" }
271
+ - { name: be_declared, from: "$.backend_declared" }
272
+ - { name: fe_declared, from: "$.frontend_declared" }
273
+ - { name: fe_excluded, from: "$.frontend_feature_excluded" }
274
+ - { name: be_working, from: "$.backend_working" }
275
+ - { name: fe_working, from: "$.frontend_working" }
276
+ - { name: working_lane, from: "$.working_lane" }
277
+ - { name: working_kind, from: "$.working_kind" }
278
+ - { name: working_root, from: "$.working_root" }
279
+ - { name: working_test_path, from: "$.working_test_path" }
213
280
 
214
281
  - id: require_feature
215
282
  action: verify_context
@@ -221,26 +288,44 @@ workflow:
221
288
  # cr-list enumerates the open changes (evidence = changes/<CR-id>/ on disk), a bare CR
222
289
  # id in the argument auto-selects, one open change auto-confirms, and the pick is
223
290
  # ACTIVATED so finalize-delta and close-delta act on the change this gate cleared.
291
+ # Only the changes naming a working lane on their **Lanes** line are listed (`--lane`,
292
+ # last in the args so an empty value cannot be mistaken for the next flag).
224
293
  - id: cr_list
225
294
  action: execute_script
226
295
  script: { ref: actions }
227
296
  subcommand: cr-list
228
- args: ["--feature-dir", "{{feature_dir}}", "--preselect", "{{USER_INPUT}}"]
297
+ args: ["--feature-dir", "{{feature_dir}}", "--preselect", "{{USER_INPUT}}", "--lane", "{{working_lane}}"]
229
298
  outputs:
230
299
  - { name: change_options, from: "$.select_options" }
231
300
  - { name: matched_label, from: "$.matched_label" }
232
301
  - { name: has_changes, from: "$.has_changes" }
233
302
  - { name: change_count, from: "$.count" }
303
+ - { name: asd_hidden_csv, from: "$.hidden_ids_csv" }
304
+ - { name: asd_preselect_hidden, from: "$.preselect_hidden" }
305
+
306
+ - id: preselect_hidden_flag
307
+ condition: "{{asd_preselect_hidden}} != "
308
+ action: set
309
+ vars: { preselect_visible: "false" }
310
+ - id: require_visible_change
311
+ action: verify_context
312
+ require_true: preselect_visible
313
+ on_false: stop
314
+ on_false_message: "Change {{asd_preselect_hidden}} does not touch your working lane `{{working_lane}}` (.appweaver/lane.json): its **Lanes** line names only the other lane, so there is no code of yours to gate. Its own lane's developer gates it — or run /appweaver select-lane to work on that lane."
315
+ - id: hidden_note_set
316
+ condition: "{{asd_hidden_csv}} != "
317
+ action: set
318
+ vars: { asd_hidden_note: " Open for a lane you do not work on, and not listed: {{asd_hidden_csv}}." }
234
319
 
235
320
  - id: require_change
236
321
  action: verify_context
237
322
  require_true: has_changes
238
323
  on_false: stop
239
- on_false_message: "No open change on this feature — there is no delta to gate. This is the CHANGE path's gate; on the bootstrap path the whole slice is gated by /appweaver analyze-slice after build. A change is opened by /appweaver specify."
324
+ on_false_message: "No open change on this feature names your working lane `{{working_lane}}` — there is no delta to gate.{{asd_hidden_note}} This is the CHANGE path's gate; on the bootstrap path the whole slice is gated by /appweaver analyze-slice after build. A change is opened by /appweaver specify."
240
325
 
241
326
  - id: pick_change
242
327
  action: user_select
243
- prompt: "Which open change is being gated? This checks that the change was actually BUILT, before finalize-delta merges it into the masters."
328
+ prompt: "Which open change is being gated? This checks that the change was actually BUILT, before finalize-delta merges it into the masters.{{asd_hidden_note}}"
244
329
  options_from_variable: change_options
245
330
  preselect_from: matched_label
246
331
  single_option_confirm: true
@@ -271,16 +356,70 @@ workflow:
271
356
  - { name: delta_frontend_plan_exists, from: "$.delta_frontend_plan_exists" }
272
357
  - { name: wave, from: "$.wave" }
273
358
  - { name: domain_model, from: "$.domain_model" }
274
-
275
- # 3a. WHICH LANES ARE GATED — declared AND this change has a delta plan for that lane.
359
+ # The lanes this change touches — its delta-spec's **Lanes** line, every lane when it
360
+ # records none.
361
+ - { name: cr_lanes_csv, from: "$.lanes_csv" }
362
+ - { name: cr_touches_backend, from: "$.touches_backend" }
363
+ - { name: cr_touches_frontend, from: "$.touches_frontend" }
364
+
365
+ # 3a. WHICH LANES ARE GATED. Declared for this feature AND named on the change's **Lanes**
366
+ # line (NAMED). A named lane somebody else works on is AWAITED — its own developer
367
+ # gates it. A named lane you work on is GATED when this change has its delta plan.
368
+ - id: be_named_set
369
+ action: set
370
+ vars: { be_named: "{{be_declared}}" }
371
+ - id: fe_named_set
372
+ action: set
373
+ vars: { fe_named: "{{fe_declared}}" }
374
+ - id: be_not_in_change
375
+ condition: "{{cr_touches_backend}} == false"
376
+ action: set
377
+ vars: { be_named: "false" }
378
+ - id: fe_not_in_change
379
+ condition: "{{cr_touches_frontend}} == false"
380
+ action: set
381
+ vars: { fe_named: "false" }
382
+ - id: be_flag_awaited
383
+ condition: "{{be_working}} == false"
384
+ action: set
385
+ vars: { be_awaited: "{{be_named}}" }
386
+ - id: fe_flag_awaited
387
+ condition: "{{fe_working}} == false"
388
+ action: set
389
+ vars: { fe_awaited: "{{fe_named}}" }
276
390
  - id: be_gated_flag
277
391
  condition: "{{delta_backend_plan_exists}} == true"
278
392
  action: set
279
- vars: { be_gated: "{{be_declared}}" }
393
+ vars: { be_gated: "{{be_named}}" }
280
394
  - id: fe_gated_flag
281
395
  condition: "{{delta_frontend_plan_exists}} == true"
282
396
  action: set
283
- vars: { fe_gated: "{{fe_declared}}" }
397
+ vars: { fe_gated: "{{fe_named}}" }
398
+ - id: be_not_worked
399
+ condition: "{{be_working}} == false"
400
+ action: set
401
+ vars: { be_gated: "false" }
402
+ - id: fe_not_worked
403
+ condition: "{{fe_working}} == false"
404
+ action: set
405
+ vars: { fe_gated: "false" }
406
+
407
+ # The other developer's half: said, never waited for. Only a lane that is not worked can
408
+ # be awaited, so at most one is.
409
+ - id: awaited_be_txt
410
+ condition: "{{be_awaited}} == true"
411
+ action: set
412
+ vars: { awaited_txt: "backend", no_awaited: "false" }
413
+ - id: awaited_fe_txt
414
+ condition: "{{fe_awaited}} == true"
415
+ action: set
416
+ vars: { awaited_txt: "frontend", no_awaited: "false" }
417
+ - id: note_awaited
418
+ action: verify_context
419
+ require_true: no_awaited
420
+ on_false: continue
421
+ on_false_message: "NOTE — change {{cr_id}} also names the {{awaited_txt}} lane, which you do not work on (.appweaver/lane.json). Its developer gates that half in their own run, and its result is recorded beside yours in {{cr_dir}}/.aw-slice-gate.{{awaited_txt}}.json. This run tests, checks and records your half only."
422
+
284
423
  - id: any_lane_be
285
424
  condition: "{{be_gated}} == true"
286
425
  action: set
@@ -294,7 +433,7 @@ workflow:
294
433
  action: verify_context
295
434
  require_true: any_lane
296
435
  on_false: stop
297
- on_false_message: "Change {{cr_id}} has no delta plan for any declared lane — backend declared {{be_declared}} (delta plan {{delta_backend_plan_exists}}), frontend declared {{fe_declared}} (delta plan {{delta_frontend_plan_exists}}). There is nothing to gate the code against. Run /appweaver plan {{cr_id}} to author the delta plans, then analyze-plan-delta → implement → build, and gate it here."
436
+ on_false_message: "Change {{cr_id}} has no delta plan for a lane it names that you work on — it names `{{cr_lanes_csv}}`, you work on `{{working_lane}}` (.appweaver/lane.json); backend declared {{be_declared}} (delta plan {{delta_backend_plan_exists}}), frontend declared {{fe_declared}} (delta plan {{delta_frontend_plan_exists}}). There is nothing of yours to gate the code against. Run /appweaver plan {{cr_id}} to author the delta plans, then analyze-plan-delta → implement → build, and gate it here."
298
437
 
299
438
  # 4. THE SLICE'S REAL SOURCE FILES, from its OKF concept — the same resolution
300
439
  # analyze-slice uses. They arrive as a PATH LIST and the check steps declare
@@ -303,21 +442,27 @@ workflow:
303
442
  # use-case or an endpoint and never a file. That is why this gate is a model pass over
304
443
  # real files rather than a deterministic existence check — the same reason design-paths
305
444
  # was retired when the plan stopped carrying ```lang path=…``` fences.
445
+ # `--under` keeps the working lane's own two subtrees when the developer works on one
446
+ # lane of two.
306
447
  - id: concept_paths
307
448
  action: execute_script
308
449
  on_error: continue
309
450
  script: { ref: xref }
310
451
  subcommand: concept-paths
311
- args: ["--feature-dir", "{{feature_dir}}"]
452
+ args: ["--feature-dir", "{{feature_dir}}", "--under", "{{working_root}}", "--under", "{{working_test_path}}"]
312
453
  outputs:
313
454
  - { name: source_files, from: "$.paths" }
314
455
  - { name: source_count, from: "$.count" }
315
456
 
457
+ - id: under_note_set
458
+ condition: "{{working_root}} != "
459
+ action: set
460
+ vars: { under_note: " under your working lane's folders (`{{working_root}}`, `{{working_test_path}}`)" }
316
461
  - id: require_source
317
462
  action: verify_context
318
463
  require_non_empty: source_files
319
464
  on_empty: stop
320
- on_empty_message: "The OKF concept for {{feature}} lists no source files, so there is no implementation to gate. Either /appweaver implement has not run for change {{cr_id}} yet, or it ran and reported no files. Run /appweaver implement {{cr_id}} → build, then re-run this gate."
465
+ on_empty_message: "The OKF concept for {{feature}} lists no source files{{under_note}}, so there is no implementation to gate. Either /appweaver implement has not run for change {{cr_id}} yet, or it ran and reported no files. Run /appweaver implement {{cr_id}} → build, then re-run this gate."
321
466
 
322
467
  # 5. Pre-execution hooks.
323
468
  - id: resolve_pre_hooks
@@ -338,19 +483,22 @@ workflow:
338
483
  # scope=slice: this feature's tests only. Referring aggregates are the downstream fence's
339
484
  # subject in finalize-delta and the integrity pass's in analyze-slice; this gate is about
340
485
  # THIS change.
486
+ # ONE RUN PER GATED LANE (`--kind`), so each lane's result is its own: a lane is not failed
487
+ # by the other lane's suite, and a lane that is not gated runs nothing.
341
488
  # NO FIX LOOP, deliberately — see the header. A red fence is reported and stops.
342
- - id: run_tests
489
+ - id: run_tests_backend
490
+ condition: "{{be_gated}} == true"
343
491
  action: execute_script
344
492
  on_error: continue
345
493
  script: { ref: actions }
346
494
  subcommand: test-run
347
- args: ["--feature-dir", "{{feature_dir}}", "--scope", "slice"]
495
+ args: ["--feature-dir", "{{feature_dir}}", "--scope", "slice", "--kind", "backend"]
348
496
  outputs:
349
- - { name: test_ok, from: "$.fence_green" }
350
- - { name: test_tool, from: "$.tool" }
351
- - { name: test_log, from: "$.log_tail" }
352
- - { name: test_excerpt, from: "$.failing_excerpt" } # the lines that NAME the failure
353
- - { name: test_scoped, from: "$.scoped" }
497
+ - { name: be_test_ok, from: "$.fence_green" }
498
+ - { name: be_test_tool, from: "$.tool" }
499
+ - { name: be_test_log, from: "$.log_tail" }
500
+ - { name: be_test_excerpt, from: "$.failing_excerpt" } # the lines that NAME the failure
501
+ - { name: be_test_scoped, from: "$.scoped" }
354
502
  # A GREEN FENCE IS NOT ALWAYS AN EXECUTED SUITE. test-run reports fence_green true
355
503
  # with ran=false on a project whose lanes declare no test command — the vacuous pass
356
504
  # its own ladder warns about. It is not made fatal here (that would fail every project
@@ -358,8 +506,45 @@ workflow:
358
506
  # but it IS reported, so "BUILT ✓" is never read as "the tests passed" when nothing
359
507
  # ran. The requirement checks below are what actually answer whether the change
360
508
  # does what it asked for, and they do not depend on a suite existing.
361
- - { name: test_ran, from: "$.ran" }
362
- - { name: test_matched, from: "$.matched" }
509
+ - { name: be_test_ran, from: "$.ran" }
510
+ - { name: be_test_matched, from: "$.matched" }
511
+ - id: run_tests_frontend
512
+ condition: "{{fe_gated}} == true"
513
+ action: execute_script
514
+ on_error: continue
515
+ script: { ref: actions }
516
+ subcommand: test-run
517
+ args: ["--feature-dir", "{{feature_dir}}", "--scope", "slice", "--kind", "frontend"]
518
+ outputs:
519
+ - { name: fe_test_ok, from: "$.fence_green" }
520
+ - { name: fe_test_tool, from: "$.tool" }
521
+ - { name: fe_test_log, from: "$.log_tail" }
522
+ - { name: fe_test_excerpt, from: "$.failing_excerpt" }
523
+ - { name: fe_test_scoped, from: "$.scoped" }
524
+ - { name: fe_test_ran, from: "$.ran" }
525
+ - { name: fe_test_matched, from: "$.matched" }
526
+
527
+ # test_ok = every gated lane's tests green — a raise and two knock-downs, each lane voting
528
+ # only when it was gated.
529
+ - id: be_test_vote_set
530
+ condition: "{{be_gated}} == true"
531
+ action: set
532
+ vars: { be_test_vote: "{{be_test_ok}}" }
533
+ - id: fe_test_vote_set
534
+ condition: "{{fe_gated}} == true"
535
+ action: set
536
+ vars: { fe_test_vote: "{{fe_test_ok}}" }
537
+ - id: tests_seed
538
+ action: set
539
+ vars: { test_ok: "true" }
540
+ - id: tests_clear_backend
541
+ condition: "{{be_test_vote}} == false"
542
+ action: set
543
+ vars: { test_ok: "false" }
544
+ - id: tests_clear_frontend
545
+ condition: "{{fe_test_vote}} == false"
546
+ action: set
547
+ vars: { test_ok: "false" }
363
548
 
364
549
  # ══════ PASS 2 — REQUIREMENTS. Does the code do what the change asked for? ══════
365
550
  # THE REQUIREMENTS ARE THE ANCHOR; the plan is the map; the masters are read-only context
@@ -564,16 +749,34 @@ workflow:
564
749
 
565
750
  # ═══════════════════════════ THE VERDICT ═══════════════════════════
566
751
  # requirements_ok = every GATED lane passed. Expressed as a raise-then-knock-down chain,
567
- # because the engine has no compound conditions and an ungated lane must not vote.
752
+ # because the engine has no compound conditions and an ungated lane must not vote. A gated
753
+ # lane votes met only on an explicit ok=true: a check that answered without `ok` votes not
754
+ # met, the same reading its .aw-slice-gate record gives it.
755
+ - id: be_req_vote_gated
756
+ condition: "{{be_gated}} == true"
757
+ action: set
758
+ vars: { be_req_vote: "false" }
759
+ - id: be_req_vote_met
760
+ condition: "{{be_ok}} == true"
761
+ action: set
762
+ vars: { be_req_vote: "true" }
763
+ - id: fe_req_vote_gated
764
+ condition: "{{fe_gated}} == true"
765
+ action: set
766
+ vars: { fe_req_vote: "false" }
767
+ - id: fe_req_vote_met
768
+ condition: "{{fe_ok}} == true"
769
+ action: set
770
+ vars: { fe_req_vote: "true" }
568
771
  - id: requirements_seed
569
772
  action: set
570
773
  vars: { requirements_ok: "true" }
571
774
  - id: requirements_clear_backend
572
- condition: "{{be_ok}} == false"
775
+ condition: "{{be_req_vote}} == false"
573
776
  action: set
574
777
  vars: { requirements_ok: "false" }
575
778
  - id: requirements_clear_frontend
576
- condition: "{{fe_ok}} == false"
779
+ condition: "{{fe_req_vote}} == false"
577
780
  action: set
578
781
  vars: { requirements_ok: "false" }
579
782
 
@@ -601,16 +804,84 @@ workflow:
601
804
  - { name: fix_review, from: "$.review_text" }
602
805
  - { name: fix_review_n, from: "$.review_count" }
603
806
 
604
- # gate_ok = the tests ran green AND every gated lane's requirements are met.
807
+ # gate_ok = every gated lane's tests green AND every gated lane's requirements are met.
605
808
  - id: gate_flag
606
809
  condition: "{{test_ok}} == true"
607
810
  action: set
608
811
  vars: { gate_ok: "{{requirements_ok}}" }
609
812
 
813
+ # EACH GATED LANE'S RESULT, RECORDED IN THE CHANGE as .aw-slice-gate.<lane>.json — pass
814
+ # when no test of the lane failed and its requirement checks are met, else fail, with
815
+ # whether a suite actually ran. It is written before the gate below stops, so a red result
816
+ # replaces an earlier green one instead of leaving it standing.
817
+ - id: stamp_backend
818
+ condition: "{{be_gated}} == true"
819
+ action: execute_script
820
+ on_error: continue
821
+ script: { ref: actions }
822
+ subcommand: slice-gate-stamp
823
+ args: ["--cr-id", "{{cr_id}}", "--feature-dir", "{{feature_dir}}", "--tests-green", "{{be_test_ok}}", "--tests-ran", "{{be_test_ran}}", "--requirements-ok", "{{be_req_vote}}", "--findings", "{{be_gaps}}", "--lane", "backend"]
824
+ outputs:
825
+ - { name: be_verdict, from: "$.verdict" }
826
+ - id: stamp_frontend
827
+ condition: "{{fe_gated}} == true"
828
+ action: execute_script
829
+ on_error: continue
830
+ script: { ref: actions }
831
+ subcommand: slice-gate-stamp
832
+ args: ["--cr-id", "{{cr_id}}", "--feature-dir", "{{feature_dir}}", "--tests-green", "{{fe_test_ok}}", "--tests-ran", "{{fe_test_ran}}", "--requirements-ok", "{{fe_req_vote}}", "--findings", "{{fe_gaps}}", "--lane", "frontend"]
833
+ outputs:
834
+ - { name: fe_verdict, from: "$.verdict" }
835
+
836
+ # EACH LANE'S STATE IN WORDS. Ordered from the least to the most specific; a gated lane
837
+ # ends on its test result, its requirement verdict and what was recorded.
838
+ - id: be_state_unnamed
839
+ condition: "{{be_declared}} == true"
840
+ action: set
841
+ vars: { be_state: "not gated — the change's **Lanes** line does not name it" }
842
+ - id: be_state_no_plan
843
+ condition: "{{be_named}} == true"
844
+ action: set
845
+ vars: { be_state: "not gated — this change has no backend delta plan" }
846
+ - id: be_state_awaited
847
+ condition: "{{be_awaited}} == true"
848
+ action: set
849
+ vars: { be_state: "not gated here — you do not work on it, and its own developer gates it" }
850
+ - id: be_state_gated
851
+ condition: "{{be_gated}} == true"
852
+ action: set
853
+ vars: { be_state: "gated — tests green={{be_test_ok}} (tool `{{be_test_tool}}`, actually ran={{be_test_ran}}), requirements met={{be_req_vote}}, recorded `{{be_verdict}}` in .aw-slice-gate.backend.json" }
854
+ - id: fe_state_unnamed
855
+ condition: "{{fe_declared}} == true"
856
+ action: set
857
+ vars: { fe_state: "not gated — the change's **Lanes** line does not name it" }
858
+ - id: fe_state_no_plan
859
+ condition: "{{fe_named}} == true"
860
+ action: set
861
+ vars: { fe_state: "not gated — this change has no frontend delta plan" }
862
+ - id: fe_state_awaited
863
+ condition: "{{fe_awaited}} == true"
864
+ action: set
865
+ vars: { fe_state: "not gated here — you do not work on it, and its own developer gates it" }
866
+ - id: fe_state_gated
867
+ condition: "{{fe_gated}} == true"
868
+ action: set
869
+ vars: { fe_state: "gated — tests green={{fe_test_ok}} (tool `{{fe_test_tool}}`, actually ran={{fe_test_ran}}), requirements met={{fe_req_vote}}, recorded `{{fe_verdict}}` in .aw-slice-gate.frontend.json" }
870
+
610
871
  # THE REPORT IS ALWAYS WRITTEN — green or red. A green one is the record that this change
611
872
  # was verified built before it was merged, which is what makes the merge auditable; a red
612
873
  # one is the work list. It lands beside the change's other documents, so close-delta
613
- # archives it with them.
874
+ # archives it with them. A developer working one lane of two writes their own,
875
+ # delta-slice-analyze-report.<lane>.md, so two developers gating the same change never
876
+ # rewrite each other's report; a developer on every lane writes
877
+ # delta-slice-analyze-report.md.
878
+ - id: report_file_set
879
+ action: set
880
+ vars: { report_file: "{{cr_dir}}/delta-slice-analyze-report.md" }
881
+ - id: report_file_lane
882
+ condition: "{{working_kind}} != "
883
+ action: set
884
+ vars: { report_file: "{{cr_dir}}/delta-slice-analyze-report.{{working_kind}}.md" }
614
885
  # An EMPTY envelope, on purpose. build_context REPLACES the payload and PERSISTS, so
615
886
  # without this the report would inherit whichever lane's documents were checked last —
616
887
  # and a report step holding the plans it is reporting on starts re-deriving findings
@@ -622,27 +893,31 @@ workflow:
622
893
  - id: write_report
623
894
  action: agent_author
624
895
  include_standards: false
625
- output_target: { type: file, path: "{{cr_dir}}/delta-slice-analyze-report.md" }
896
+ output_target: { type: file, path: "{{report_file}}" }
626
897
  task: |
627
898
  Write a Delta Slice Analyze Report for change {{cr_id}} (wave {{wave}}) to the
628
899
  output. This gate asked whether the change's CODE does what its DELTA SPEC asked for
629
900
  — the requirements are the anchor, the delta plan was the map — before finalize-delta
630
901
  merges the change into spec.md and domain-model.md.
631
- Evidence:
632
- - tests: green={{test_ok}}, tool `{{test_tool}}`, actually ran={{test_ran}},
633
- matched a slice filter={{test_matched}}, slice-scoped={{test_scoped}}. Failing
634
- lines: {{test_excerpt}} · Log tail: {{test_log}}
635
- - service lane gated={{be_gated}}, verdict ok={{be_ok}}, gaps: {{be_gaps}}
636
- - experience lane gated={{fe_gated}}, verdict ok={{fe_ok}}, gaps: {{fe_gaps}}
902
+ Evidence — the change names the lanes `{{cr_lanes_csv}}`; this developer works on
903
+ `{{working_lane}}`. Each gated lane ran its own tests and was checked on its own:
904
+ - service lane: {{be_state}}. Matched a slice filter={{be_test_matched}},
905
+ slice-scoped={{be_test_scoped}}. Failing lines: {{be_test_excerpt}} · Log tail:
906
+ {{be_test_log}} · gaps: {{be_gaps}}
907
+ - experience lane: {{fe_state}}. Matched a slice filter={{fe_test_matched}},
908
+ slice-scoped={{fe_test_scoped}}. Failing lines: {{fe_test_excerpt}} · Log tail:
909
+ {{fe_test_log}} · gaps: {{fe_gaps}}
637
910
  Markdown format:
638
911
  - "# Delta Slice Analyze Report — {{cr_id}}" plus today's date.
639
- - A verdict line: "verdict: REQUIREMENTS MET ✓" when the tests are green and every
640
- gated lane passed, else "verdict: NOT READY ✗ · <n> finding(s)".
641
- - "## Evidence" — the test fence (tool, green or red, and the log tail when red), and
642
- which lanes were gated. A lane that was NOT gated is listed as "not gated — this
643
- change has no delta plan for it", never as a pass. WHERE `actually ran` IS FALSE,
644
- say so in that section in plain words — the fence went green because this project's
645
- lanes declare no test command, not because a suite passed — and note that the
912
+ - A verdict line: "verdict: REQUIREMENTS MET ✓" when every gated lane's tests are
913
+ green and every gated lane passed, else "verdict: NOT READY ✗ · <n> finding(s)".
914
+ - "## Evidence" — one entry per lane, with the lane's state as given above. For a
915
+ gated lane: its test fence (tool, green or red, and the log tail when red), its
916
+ requirement verdict, and the result recorded in its .aw-slice-gate.<lane>.json. A
917
+ lane that was NOT gated is listed with the reason given, never as a pass — and one
918
+ its own developer gates is said to be theirs, not missing. WHERE A GATED LANE'S
919
+ `actually ran` IS FALSE, say so in plain words — its fence went green because the
920
+ lane declares no test command, not because a suite passed — and note that the
646
921
  requirement findings below, not the fence, are what answer whether the change does
647
922
  what it was asked to.
648
923
  - "## Findings" as a table — | # | check | lane | severity | delta-spec ref | where |
@@ -671,9 +946,8 @@ workflow:
671
946
  require_true: gate_ok
672
947
  on_false: stop
673
948
  on_false_message: >
674
- Change {{cr_id}} does NOT yet meet its delta spec. Tests green={{test_ok}} (tool
675
- `{{test_tool}}`); service lane gated={{be_gated}} ok={{be_ok}}; experience lane
676
- gated={{fe_gated}} ok={{fe_ok}}. {{gap_summary}}
949
+ Change {{cr_id}} does NOT yet meet its delta spec. Service lane: {{be_state}}.
950
+ Experience lane: {{fe_state}}. {{gap_summary}}
677
951
 
678
952
  EACH FINDING IS CLOSED BY A DIFFERENT COMMAND — they are not interchangeable, and a
679
953
  plan defect sent to implement comes back unchanged, because implement generates FROM
@@ -691,7 +965,7 @@ workflow:
691
965
  the code that maps to it. Re-running /appweaver plan {{cr_id}} re-derives the delta
692
966
  plans with this report in context, so a plan finding is fixed by re-deriving rather
693
967
  than by hand-editing a generated document. The full report is at
694
- {{cr_dir}}/delta-slice-analyze-report.md. DO NOT run finalize-delta yet: merging now
968
+ {{report_file}}. DO NOT run finalize-delta yet: merging now
695
969
  would write requirements into spec.md and domain-model.md that the code does not
696
970
  satisfy, and the masters would describe an intention rather than the system. Then
697
971
  re-run build and re-run this gate.
@@ -714,16 +988,26 @@ outputs:
714
988
  cr_dir: "{{cr_dir}}"
715
989
  wave: "{{wave}}"
716
990
  built: "{{gate_ok}}"
717
- tests_green: "{{test_ok}}"
718
- tests_ran: "{{test_ran}}" # false + green = the project declares no test command
719
- test_tool: "{{test_tool}}"
991
+ tests_green: "{{test_ok}}" # every gated lane's tests
992
+ working_lane: "{{working_lane}}"
993
+ change_lanes: "{{cr_lanes_csv}}"
720
994
  backend_gated: "{{be_gated}}"
995
+ backend_state: "{{be_state}}"
996
+ backend_tests_green: "{{be_test_ok}}"
997
+ backend_tests_ran: "{{be_test_ran}}" # false + green = the lane declares no test command
998
+ backend_test_tool: "{{be_test_tool}}"
721
999
  backend_ok: "{{be_ok}}"
722
1000
  backend_gaps: "{{be_gaps}}"
1001
+ backend_verdict: "{{be_verdict}}" # as recorded in .aw-slice-gate.backend.json
723
1002
  frontend_gated: "{{fe_gated}}"
1003
+ frontend_state: "{{fe_state}}"
1004
+ frontend_tests_green: "{{fe_test_ok}}"
1005
+ frontend_tests_ran: "{{fe_test_ran}}"
1006
+ frontend_test_tool: "{{fe_test_tool}}"
724
1007
  frontend_ok: "{{fe_ok}}"
725
1008
  frontend_gaps: "{{fe_gaps}}"
726
- report: "{{cr_dir}}/delta-slice-analyze-report.md"
1009
+ frontend_verdict: "{{fe_verdict}}" # as recorded in .aw-slice-gate.frontend.json
1010
+ report: "{{report_file}}" # delta-slice-analyze-report.<lane>.md when one lane of two is worked
727
1011
  source_count: "{{source_count}}"
728
- note: "Gated change {{cr_id}} (wave {{wave}}) — delta-spec.md is the ANCHOR, so the question answered here is whether the CODE does what the change asked for, not whether it matches the plan. The delta plan is the MAP from a requirement to the element that serves it, and is cross-checked in its own right; the change's model delta supplies the invariants the code must enforce, and they are requirements too. The masters are read-only context for what was already there — they do not carry this change until finalize-delta merges it. Only a lane with a delta plan for THIS change is gated: backend {{be_gated}}, frontend {{fe_gated}} — an ungated lane is reported as not gated, never as a pass. The tests ran here for the first time on the change path, since build only compiles. This gate verifies: it generated no code and repaired nothing, and every finding names the command that closes it."
1012
+ note: "Gated change {{cr_id}} (wave {{wave}}) — delta-spec.md is the ANCHOR, so the question answered here is whether the CODE does what the change asked for, not whether it matches the plan. The delta plan is the MAP from a requirement to the element that serves it, and is cross-checked in its own right; the change's model delta supplies the invariants the code must enforce, and they are requirements too. The masters are read-only context for what was already there — they do not carry this change until finalize-delta merges it. Only a lane the change names, that you work on and that has a delta plan for THIS change is gated: backend — {{be_state}}; frontend — {{fe_state}}. An ungated lane is reported as not gated, never as a pass, and a lane somebody else works on is gated in their own run. Each gated lane ran its own tests, here for the first time on the change path since build only compiles, and its pass or fail is recorded in {{cr_dir}}/.aw-slice-gate.<lane>.json. This gate verifies: it generated no code and repaired nothing, and every finding names the command that closes it."
729
1013
  next: "/appweaver finalize-delta → analyze-slice → close-delta. The change MEETS ITS SPEC and is still OPEN: finalize-delta merges it into spec.md + the context's domain-model.md — writing requirements the code has now been shown to satisfy — analyze-slice then scores the WHOLE slice against those merged masters (behaviour, domain conformance and the referrer blast radius) while a red result is still recoverable, and close-delta LAST archives the change and bumps the version."