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
@@ -13,9 +13,10 @@
13
13
  #
14
14
  # SELF-DETECTING, off the CHANGES ON DISK (symmetric with how `plan` keys off the plan
15
15
  # documents' own existence):
16
- # • BOOTSTRAP (no change open) → generate from the MASTER plans.
16
+ # • BOOTSTRAP (no open change names a working lane) → generate from the MASTER plans.
17
17
  # Next: /appweaver build → analyze-slice.
18
- # • DELTA (one or more changes/<CR-id>/ open) → ASK WHICH ONE, activate it, and
18
+ # • DELTA (one or more changes/<CR-id>/ open that name a working lane on their
19
+ # **Lanes** line) → ASK WHICH ONE, activate it, and
19
20
  # generate ONLY what that change's delta plans mark ADDED / MODIFIED, into the
20
21
  # tree that is already there. The masters are the BASE, read and never written.
21
22
  # A bare CR id in the argument selects without a prompt; a feature with exactly one
@@ -34,15 +35,34 @@
34
35
  # goes unbuilt; failing silently is how a frontend that was never generated reports green.
35
36
  # Only when NO lane can run does the phase stop, naming every reason it collected.
36
37
  #
38
+ # IT GENERATES THE DEVELOPER'S WORKING LANE. .appweaver/lane.json (select-stack writes the
39
+ # default, select-lane narrows it, not committed) names the lanes this developer works on.
40
+ # Only a working lane is offered, gated, generated and reported on; a declared lane that is
41
+ # somebody else's is neither asked about nor warned about, and the other developer builds
42
+ # it in a run of their own. With the default (every declared lane) nothing changes.
43
+ #
44
+ # A CHANGE TAKES OVER ONLY THE LANES IT NAMES. Each change's delta-spec carries a **Lanes**
45
+ # line. Only the changes naming a working lane are listed (cr-list --lane), so a frontend
46
+ # developer is not pulled into change mode by a backend-only change: their lane is still
47
+ # built from its master plan, and the change is named in a note. On the delta path a
48
+ # working lane the chosen change does not name is skipped with that reason — for a
49
+ # developer on `both`, a backend-only change builds the backend only, and the frontend's
50
+ # master is built by working that lane alone (select-lane frontend) while the change is
51
+ # open. A change with no **Lanes** line names every lane.
52
+ #
37
53
  # IT ASKS BEFORE IT WRITES. Generation is the expensive, side-effecting step of the
38
54
  # pipeline, so each runnable lane confirms first, naming its stack, its plan and the
39
55
  # subtree it will write into. Answering no to one lane leaves the other free to run.
40
56
  #
41
- # THE FRONTEND BINDS TO THE SERVICE AS BUILT. Between the two lanes, the service's API
42
- # document is exported from the running code, the frontend's client is generated from it,
43
- # and the operation ids the frontend plan cites are checked against it; the frontend then
44
- # generates with the document in context. Each of those steps warns rather than stops —
45
- # see the API CONTRACT block before the lane swap.
57
+ # THE BACKEND PUBLISHES THE API DOCUMENT, THE FRONTEND READS IT. When the service lane
58
+ # generates, its API document is exported from the running code and saved beside it, to be
59
+ # committed. When the frontend lane generates, it never starts the service: it reads that
60
+ # committed document plus the operations the backend plan has drafted and the service does
61
+ # not serve yet (api-draft.json), combined into one, generates its client from it and
62
+ # checks the operation ids its plan cites against it. Contract requests the backend plan has
63
+ # not answered are named, and the bindings that wait on them are left unbound while the
64
+ # rest of the lane generates. Every one of those steps warns rather than stops — see the
65
+ # API CONTRACT block before the lane swap.
46
66
  #
47
67
  # THE PLAN SAYS WHAT TO BUILD, THE MODEL SAYS WHAT THE DOMAIN IS. This phase does NOT read
48
68
  # spec.md — analyze-plan already conformed the plans to it, so the plan is the complete,
@@ -52,11 +72,12 @@
52
72
  # AND THAT SENTENCE IS NOW CHECKED. "analyze-plan already conformed them" was an assumption
53
73
  # about the order the operator ran things in: the lane gates test that the plan FILE EXISTS
54
74
  # and nothing more, so `plan` → edit spec.md → `implement` generated at full length from a
55
- # plan nobody had held against the spec it now contradicts. analyze-plan leaves a receipt of
56
- # the bytes it checked and its verdict, and plan-gate-check reads it here, on the bootstrap
57
- # path, before any lane generates. A feature with no receipt has simply never been gated
58
- # since the receipt existed — reported, never refused. One whose receipt no longer matches,
59
- # or whose verdict left plan gaps, stops; one whose verdict left only spec/model gaps warns.
75
+ # plan nobody had held against the spec it now contradicts. analyze-plan leaves a receipt PER
76
+ # LANE of the bytes it checked and that lane's verdict, and plan-gate-check reads each working
77
+ # lane's here, on the bootstrap path, before any lane generates. A lane with no receipt has
78
+ # simply never been gated since the receipt existed — reported, never refused. One whose
79
+ # receipt no longer matches, or whose verdict left plan gaps, is skipped with the reason while
80
+ # the other lane still runs; one whose verdict left only spec/model gaps warns.
60
81
  # 3.2.0 — AND NOT delta-spec.md EITHER. The change path used to inline it "only so the
61
82
  # paired tests can cite traceability ids", which made the rule true on the bootstrap path
62
83
  # and false on the one where changes actually ship: a document in the payload is a document
@@ -133,8 +154,8 @@
133
154
  # =============================================================================
134
155
 
135
156
  name: aw-spec-implement
136
- description: "Generate the aggregate's source files and paired tests from its lane plans — the backend lane from backend-plan.md when a backend stack is declared, the frontend lane from frontend-plan.md when a frontend stack is, both when both are. One command covers backend-only, frontend-only and full-stack projects and absorbs the former implement-ui and implement-delta. Self-detecting off the changes on disk — with none open it generates from the master plans; with one or more open it ASKS which change and generates only what that change's delta plans mark ADDED or MODIFIED, leaving the masters untouched. Every lane is gated on its own lane root folder, its own plan and its own scaffolded project, and a lane missing any of them is reported and skipped rather than failing the other lane; each runnable lane confirms before it writes. Model-driven, and it does not read the spec — analyze-plan already conformed the plans to it. The plan says WHAT to build; the bounded context's domain-model.md, in context alongside it, says what the domain IS — the entity names, the boundary and the text of every invariant the plan cites by id — joined on the delta path by that change's own model delta, since the master is not merged until finalize-delta. The stack binds here from the lane declaration and the constitution halves; the plans stay technology-agnostic."
137
- version: 3.10.0
157
+ description: "Generate the aggregate's source files and paired tests from its lane plans — the backend lane from backend-plan.md when a backend stack is declared, the frontend lane from frontend-plan.md when a frontend stack is, both when both are. One command covers backend-only, frontend-only and full-stack projects and absorbs the former implement-ui and implement-delta. Self-detecting off the changes on disk — with none open that names a lane you work on (its **Lanes** line) it generates from the master plans; with one or more it ASKS which change and generates only what that change's delta plans mark ADDED or MODIFIED, for the lanes the change names, leaving the masters untouched. Every lane is gated on its own lane root folder, its own plan and its own scaffolded project, and a lane missing any of them is reported and skipped rather than failing the other lane; each runnable lane confirms before it writes. Model-driven, and it does not read the spec — analyze-plan already conformed the plans to it. The plan says WHAT to build; the bounded context's domain-model.md, in context alongside it, says what the domain IS — the entity names, the boundary and the text of every invariant the plan cites by id — joined on the delta path by that change's own model delta, since the master is not merged until finalize-delta. The stack binds here from the lane declaration and the constitution halves; the plans stay technology-agnostic."
158
+ version: 3.13.0
138
159
  agent_agnostic: true
139
160
  dialect: aw-spec.orchestrator/v1
140
161
 
@@ -184,20 +205,49 @@ vars:
184
205
  # verdict, and has anything moved since. analyze-plan writes it on every master run; this
185
206
  # phase is the reader. Bootstrap path only: a delta generates from delta plans, which
186
207
  # analyze-plan-delta gates, and the master receipt says nothing about those.
187
- gate_tracked: "false"
188
- gate_fresh: "false"
189
- gate_reason: ""
190
- gate_at: ""
191
- gate_verdict: "" # conformant | routed-gaps | gaps — empty with no receipt
192
- gate_routed_csv: "" # the gaps only /appweaver specify can close
193
- gate_may_generate: "true"
194
- gate_no_routed: "true" # lowered when the receipt's verdict is routed-gaps — a warning, not a stop
195
- # DEFAULTED PERMISSIVE, DELIBERATELY, and it is the one flag here that is. Every tree that
196
- # predates the receipt has none, and "never gated" is an unknown, not a stale plan —
197
- # refusing on a missing file would block every existing feature on its first run. The flag
198
- # is lowered only by a receipt that EXISTS and refuses: the bytes moved since it was
208
+ # ONE RECEIPT PER LANE (.aw-plan-gate.<lane>.json, or the shared .aw-plan-gate.json of a
209
+ # feature gated before the per-lane receipts, narrowed to that lane's plan), so a lane is
210
+ # refused only for its own plan and its own verdict.
211
+ be_gate_check: "false"
212
+ be_gate_tracked: "false"
213
+ be_gate_fresh: "false"
214
+ be_gate_reason: ""
215
+ be_gate_at: ""
216
+ be_gate_verdict: "" # conformant | routed-gaps | gaps — empty with no receipt
217
+ be_gate_routed_csv: "" # the gaps only /appweaver specify can close
218
+ be_gate_may_generate: "true"
219
+ be_gate_no_routed: "true" # lowered when the receipt's verdict is routed-gaps — a warning, not a stop
220
+ fe_gate_check: "false"
221
+ fe_gate_tracked: "false"
222
+ fe_gate_fresh: "false"
223
+ fe_gate_reason: ""
224
+ fe_gate_at: ""
225
+ fe_gate_verdict: ""
226
+ fe_gate_routed_csv: ""
227
+ fe_gate_may_generate: "true"
228
+ fe_gate_no_routed: "true"
229
+ # DEFAULTED PERMISSIVE, DELIBERATELY, and they are the only flags here that are. Every
230
+ # tree that predates the receipt has none, and "never gated" is an unknown, not a stale
231
+ # plan — refusing on a missing file would block every existing feature on its first run.
232
+ # Each is lowered only by a receipt that EXISTS and refuses: the bytes moved since it was
199
233
  # written, or its verdict left gaps a plan rewrite closes.
200
- may_generate: "true"
234
+ be_may_generate: "true"
235
+ fe_may_generate: "true"
236
+ # THE WORKING LANES (.appweaver/lane.json).
237
+ be_working: "false"
238
+ fe_working: "false"
239
+ working_lane: ""
240
+ # THE CHANGE'S LANES. cr-list lists only the changes naming a working lane; the chosen
241
+ # change's **Lanes** line then decides which working lanes this delta run builds.
242
+ impl_hidden_csv: "" # open changes left out: they name only a lane not worked here
243
+ impl_preselect_hidden: "" # the argument named one of those
244
+ preselect_visible: "true"
245
+ no_hidden: "true"
246
+ cr_lanes_csv: ""
247
+ be_in_change: "true" # every lane until cr-paths answers (and always on the bootstrap path)
248
+ fe_in_change: "true"
249
+ be_report: "false" # worked AND in this run's target ⇒ its gaps are reported below
250
+ fe_report: "false"
201
251
  # PER-LANE STATE. Each lane resolves its own plan, its own project and its own answer;
202
252
  # a lane that is not declared never resolves any of them and its steps never run.
203
253
  # The engine has no compound conditions, so each AND is an ordered assignment: a step
@@ -254,18 +304,34 @@ vars:
254
304
  fe_gap_names: ""
255
305
  fe_gaps_text: "none"
256
306
  # THE API CONTRACT between the lanes — see the block before the lane swap. Every flag
257
- # starts lowered, so a run without a frontend lane never touches any of it.
258
- contract_go: "false"
307
+ # starts lowered: the backend half publishes only when the service lane generates, and
308
+ # the frontend half reads only when the frontend lane does.
309
+ contract_go: "false" # PUBLISH — the service lane generated in this run
259
310
  contract_ok: ""
260
- contract_doc: ""
261
- contract_doc_exists: "false"
311
+ export_doc: "" # the service's exported document (contracts/openapi.json)
312
+ export_doc_exists: "false"
262
313
  contract_reason: ""
263
314
  contract_already: "false"
264
315
  contract_fresh: "true"
265
316
  contract_log: ""
266
317
  contract_placeholder: ""
267
318
  contract_export_status: ""
268
- contract_status: "not exported — no API document is in context, so take each binding from the plan alone and say so in your summary"
319
+ combine_go: "false" # READ — the frontend lane generates and a service lane is declared
320
+ combined_ok: ""
321
+ combined_status: ""
322
+ combined_reason: ""
323
+ combined_export: ""
324
+ combined_clashes: ""
325
+ clashes_clear: "true"
326
+ contract_doc: "" # what the frontend binds to: the combined document
327
+ contract_doc_exists: "false"
328
+ contract_status: "none — no API document is in context, so take each binding from the plan alone and say so in your summary"
329
+ req_open_csv: "" # the frontend plan's contract requests the backend plan has not answered
330
+ req_backend_text: "none"
331
+ req_summary: ""
332
+ req_clear: "true"
333
+ check_drafted: ""
334
+ drafted_clear: "true"
269
335
  client_go: "false"
270
336
  client_ask: "false"
271
337
  client_answer: ""
@@ -346,6 +412,9 @@ workflow:
346
412
  - { name: fe_reserved, from: "$.frontend_reserved_csv" }
347
413
  - { name: fe_workdir, from: "$.frontend_workdir" }
348
414
  - { name: fe_client, from: "$.frontend_client" }
415
+ - { name: be_working, from: "$.backend_working" }
416
+ - { name: fe_working, from: "$.frontend_working" }
417
+ - { name: working_lane, from: "$.working_lane" }
349
418
 
350
419
  - id: require_lanes
351
420
  action: verify_context
@@ -409,20 +478,46 @@ workflow:
409
478
  - { name: owned_sections, from: "$.owned_sections" }
410
479
 
411
480
  # 2. WHICH CHANGES ARE OPEN — the evidence is changes/<CR-id>/ on disk; a closed change
412
- # has moved to changes/archive/. Any open change makes this run a DELTA run.
413
- # `matched_label` resolves a CR id passed as the argument to the whole option string,
414
- # so the pick below can auto-select it (the engine matches a preselection against the
415
- # option text exactly).
481
+ # has moved to changes/archive/. An open change that names a working lane on its
482
+ # **Lanes** line makes this run a DELTA run; one naming only a lane this developer
483
+ # does not work on is left out (`--lane`, last in the args so an empty value cannot be
484
+ # mistaken for the next flag) and named in a note. `matched_label` resolves a CR id
485
+ # passed as the argument to the whole option string, so the pick below can auto-select
486
+ # it (the engine matches a preselection against the option text exactly).
416
487
  - id: cr_list
417
488
  action: execute_script
418
489
  script: { ref: actions }
419
490
  subcommand: cr-list
420
- args: ["--feature-dir", "{{feature_dir}}", "--preselect", "{{USER_INPUT}}"]
491
+ args: ["--feature-dir", "{{feature_dir}}", "--preselect", "{{USER_INPUT}}", "--lane", "{{working_lane}}"]
421
492
  outputs:
422
493
  - { name: change_options, from: "$.select_options" }
423
494
  - { name: matched_label, from: "$.matched_label" }
424
495
  - { name: has_changes, from: "$.has_changes" }
425
496
  - { name: change_count, from: "$.count" }
497
+ - { name: impl_hidden_csv, from: "$.hidden_ids_csv" }
498
+ - { name: impl_preselect_hidden, from: "$.preselect_hidden" }
499
+
500
+ # `implement CR-…` naming a change for another lane only stops, rather than building
501
+ # something the argument did not ask for.
502
+ - id: preselect_hidden_flag
503
+ condition: "{{impl_preselect_hidden}} != "
504
+ action: set
505
+ vars: { preselect_visible: "false" }
506
+ - id: require_visible_change
507
+ action: verify_context
508
+ require_true: preselect_visible
509
+ on_false: stop
510
+ on_false_message: "Change {{impl_preselect_hidden}} does not touch your working lane `{{working_lane}}` (.appweaver/lane.json): its **Lanes** line names only the other lane, so there is nothing for you to build for it. Run /appweaver select-lane to work on that lane — or, if the change does need your lane, widen it with `.appweaver/scripts/bash/aw-spec-actions.sh cr-lanes --cr-id {{impl_preselect_hidden}} --set both` and plan your half with /appweaver plan {{impl_preselect_hidden}}."
511
+
512
+ - id: hidden_flag
513
+ condition: "{{impl_hidden_csv}} != "
514
+ action: set
515
+ vars: { no_hidden: "false" }
516
+ - id: note_hidden
517
+ action: verify_context
518
+ require_true: no_hidden
519
+ on_false: continue
520
+ on_false_message: "NOTE — open change(s) for a lane you do not work on, left to that lane's developer and not built here: {{impl_hidden_csv}}. They do not put your lane into change mode."
426
521
 
427
522
  - id: mode_delta
428
523
  condition: "{{has_changes}} == true"
@@ -480,6 +575,11 @@ workflow:
480
575
  - { name: delta_frontend_plan, from: "$.delta_frontend_plan" }
481
576
  - { name: delta_frontend_plan_exists, from: "$.delta_frontend_plan_exists" }
482
577
  - { name: wave, from: "$.wave" }
578
+ # The lanes the change names — its delta-spec's **Lanes** line, every lane when it
579
+ # records none.
580
+ - { name: cr_lanes_csv, from: "$.lanes_csv" }
581
+ - { name: be_in_change, from: "$.touches_backend" }
582
+ - { name: fe_in_change, from: "$.touches_frontend" }
483
583
 
484
584
  # 3. WHICH PLAN EACH LANE GENERATES FROM, resolved once per lane from the mode — the
485
585
  # master on the bootstrap path, this change's delta plan on the delta path. Every gate
@@ -508,61 +608,93 @@ workflow:
508
608
  # spec.md, `implement` therefore generated from a plan that was never held against the
509
609
  # spec it now contradicts, silently and at full length.
510
610
  #
511
- # plan-gate-check compares today's bytes against the receipt analyze-plan left — spec.md,
512
- # the context's domain-model.md and each gated lane plan — and reads the verdict that
513
- # run reached. The plans' own `**Version**:` headers cannot answer this and must not be
514
- # used: close-delta stamps them and spec.md to the same number in lockstep whether or not
515
- # a plan was re-derived, so the comparison reads equal always.
611
+ # plan-gate-check compares today's bytes against the receipt analyze-plan left for the
612
+ # lane — spec.md, the context's domain-model.md and that lane's plan — and reads the
613
+ # verdict that run reached for it. The plans' own `**Version**:` headers cannot answer
614
+ # this and must not be used: close-delta stamps them and spec.md to the same number in
615
+ # lockstep whether or not a plan was re-derived, so the comparison reads equal always.
516
616
  #
517
- # THREE OUTCOMES FOR A RECEIPT. Moved bytes refuse. Verdict `gaps` refuses: that run left
518
- # gaps a plan rewrite closes. Verdict `routed-gaps` generates and warns: what is left
519
- # can only be closed in the spec or the model, and it is listed for /appweaver specify.
617
+ # THREE OUTCOMES FOR A RECEIPT. Moved bytes refuse the lane. Verdict `gaps` refuses it:
618
+ # that run left gaps a plan rewrite closes. Verdict `routed-gaps` generates and warns:
619
+ # what is left can only be closed in the spec or the model, and it is listed for
620
+ # /appweaver specify. A refused lane is SKIPPED with the reason, like every other lane
621
+ # gap below — the other lane, whose own receipt is clean, still runs.
520
622
  #
521
- # BOOTSTRAP ONLY. A delta run generates from delta plans that analyze-plan-delta gates;
522
- # the master receipt is silent about them, and reading it there would refuse a change
523
- # for drift in documents this run is not building from.
524
- - id: plan_gate_check
623
+ # PER WORKING LANE, BOOTSTRAP ONLY. A delta run generates from delta plans that
624
+ # analyze-plan-delta gates; the master receipt is silent about them, and reading it
625
+ # there would refuse a change for drift in documents this run is not building from.
626
+ - id: be_gate_arm
525
627
  condition: "{{mode}} == bootstrap"
628
+ action: set
629
+ vars: { be_gate_check: "{{be_working}}" }
630
+ - id: fe_gate_arm
631
+ condition: "{{mode}} == bootstrap"
632
+ action: set
633
+ vars: { fe_gate_check: "{{fe_working}}" }
634
+
635
+ - id: plan_gate_check_backend
636
+ condition: "{{be_gate_check}} == true"
526
637
  action: execute_script
527
638
  script: { ref: actions }
528
639
  subcommand: plan-gate-check
529
- args: ["--feature-dir", "{{feature_dir}}"]
640
+ args: ["--feature-dir", "{{feature_dir}}", "--lane", "backend"]
641
+ outputs:
642
+ - { name: be_gate_tracked, from: "$.tracked" }
643
+ - { name: be_gate_fresh, from: "$.gate_fresh" }
644
+ - { name: be_gate_verdict, from: "$.verdict" }
645
+ - { name: be_gate_may_generate, from: "$.may_generate" }
646
+ - { name: be_gate_routed_csv, from: "$.routed_csv" }
647
+ - { name: be_gate_reason, from: "$.reason" }
648
+ - { name: be_gate_at, from: "$.gated" }
649
+ - id: plan_gate_check_frontend
650
+ condition: "{{fe_gate_check}} == true"
651
+ action: execute_script
652
+ script: { ref: actions }
653
+ subcommand: plan-gate-check
654
+ args: ["--feature-dir", "{{feature_dir}}", "--lane", "frontend"]
530
655
  outputs:
531
- - { name: gate_tracked, from: "$.tracked" }
532
- - { name: gate_fresh, from: "$.gate_fresh" }
533
- - { name: gate_verdict, from: "$.verdict" }
534
- - { name: gate_may_generate, from: "$.may_generate" }
535
- - { name: gate_routed_csv, from: "$.routed_csv" }
536
- - { name: gate_reason, from: "$.reason" }
537
- - { name: gate_at, from: "$.gated" }
656
+ - { name: fe_gate_tracked, from: "$.tracked" }
657
+ - { name: fe_gate_fresh, from: "$.gate_fresh" }
658
+ - { name: fe_gate_verdict, from: "$.verdict" }
659
+ - { name: fe_gate_may_generate, from: "$.may_generate" }
660
+ - { name: fe_gate_routed_csv, from: "$.routed_csv" }
661
+ - { name: fe_gate_reason, from: "$.reason" }
662
+ - { name: fe_gate_at, from: "$.gated" }
538
663
 
539
- # The ordered assignment is the AND: only a TRACKED feature can lower the flag, and
540
- # what lowers it is plan-gate-check's may_generate. An untracked one never reaches
541
- # this step's assignment and generates, which is the whole accommodation for trees
542
- # that have not been gated since the receipt existed.
543
- - id: flag_gate_block
544
- condition: "{{gate_tracked}} == true"
664
+ # The ordered assignment is the AND: only a TRACKED lane can lower its flag, and what
665
+ # lowers it is plan-gate-check's may_generate. An untracked lane never reaches this
666
+ # step's assignment and generates, which is the whole accommodation for trees that
667
+ # have not been gated since the receipt existed.
668
+ - id: be_flag_gate_block
669
+ condition: "{{be_gate_tracked}} == true"
545
670
  action: set
546
- vars: { may_generate: "{{gate_may_generate}}" }
547
-
548
- - id: require_gate_fresh
549
- action: verify_context
550
- require_true: may_generate
551
- on_false: stop
552
- on_false_message: "implement REFUSED — analyze-plan's receipt does not clear these plans: {{gate_reason}}. This phase generates from the plans and does NOT read the spec, on the strength of that gate having checked them against it, so generating now would realise a plan against requirements nobody held it to. Re-run /appweaver analyze-plan — it re-checks all four checks, repairs the drifting plan on approval, and re-issues the receipt — then re-run /appweaver implement. If what changed was a plan you re-derived with /appweaver plan main, that is the same path: the re-derived plans have not been gated yet. Nothing was generated."
553
-
554
- # THE WARNING for a receipt whose only open gaps belong to specify. Generation goes on:
555
- # no plan rewrite could close them, and the plan is what this phase builds from.
556
- - id: flag_routed_gaps
557
- condition: "{{gate_verdict}} == routed-gaps"
671
+ vars: { be_may_generate: "{{be_gate_may_generate}}" }
672
+ - id: fe_flag_gate_block
673
+ condition: "{{fe_gate_tracked}} == true"
558
674
  action: set
559
- vars: { gate_no_routed: "false" }
675
+ vars: { fe_may_generate: "{{fe_gate_may_generate}}" }
560
676
 
561
- - id: warn_routed_gaps
677
+ # THE WARNING for a receipt whose only open gaps belong to specify. That lane still
678
+ # generates: no plan rewrite could close them, and the plan is what this phase builds
679
+ # from.
680
+ - id: be_flag_routed_gaps
681
+ condition: "{{be_gate_verdict}} == routed-gaps"
682
+ action: set
683
+ vars: { be_gate_no_routed: "false" }
684
+ - id: fe_flag_routed_gaps
685
+ condition: "{{fe_gate_verdict}} == routed-gaps"
686
+ action: set
687
+ vars: { fe_gate_no_routed: "false" }
688
+ - id: warn_be_routed_gaps
562
689
  action: verify_context
563
- require_true: gate_no_routed
690
+ require_true: be_gate_no_routed
564
691
  on_false: continue
565
- on_false_message: "WARNING — analyze-plan left gaps only the spec or the model can close, so the code generated now will not cover them: {{gate_routed_csv}}. Lift each into spec.md / domain-model.md by opening a change with /appweaver specify."
692
+ on_false_message: "WARNING — analyze-plan left backend gaps only the spec or the model can close, so the backend code generated now will not cover them: {{be_gate_routed_csv}}. Lift each into spec.md / domain-model.md by opening a change with /appweaver specify."
693
+ - id: warn_fe_routed_gaps
694
+ action: verify_context
695
+ require_true: fe_gate_no_routed
696
+ on_false: continue
697
+ on_false_message: "WARNING — analyze-plan left frontend gaps only the spec or the model can close, so the frontend code generated now will not cover them: {{fe_gate_routed_csv}}. Lift each into spec.md / domain-model.md by opening a change with /appweaver specify."
566
698
 
567
699
  # 4. THE PROJECT each declared lane generates INTO. Deterministic structure gate:
568
700
  # detect-project searches the LANE'S OWN subtree for an ecosystem manifest (.sln/
@@ -600,55 +732,94 @@ workflow:
600
732
  action: set
601
733
  vars: { fe_project_ok: "true" }
602
734
 
603
- # 5. REPORT EACH DECLARED LANE'S GAPS AND CARRY ON. `on_false: continue` is the whole
735
+ # 5. REPORT EACH WORKING LANE'S GAPS AND CARRY ON. `on_false: continue` is the whole
604
736
  # point: the message is the deliverable, not the stop. A lane that cannot run is named
605
737
  # here with the command that fixes it, and the run continues so the other lane still
606
- # generates. If NOTHING can run, the gate at step 7 stops with that fact.
738
+ # generates. If NOTHING can run, the gate at step 7 stops with that fact. A declared
739
+ # lane this developer does not work on is not reported: it is somebody else's.
740
+ #
741
+ # A WORKING LANE THE CHANGE DOES NOT NAME is skipped with that one reason, and none of
742
+ # the gaps below is reported for it: it has no delta plan by design, and "run plan" is
743
+ # not the fix. be_report / fe_report = worked AND in this run's target.
744
+ - id: be_report_raise
745
+ condition: "{{be_in_change}} == true"
746
+ action: set
747
+ vars: { be_report: "{{be_working}}" }
748
+ - id: fe_report_raise
749
+ condition: "{{fe_in_change}} == true"
750
+ action: set
751
+ vars: { fe_report: "{{fe_working}}" }
752
+ - id: note_be_not_in_change
753
+ condition: "{{be_working}} == true"
754
+ action: verify_context
755
+ require_true: be_in_change
756
+ on_false: continue
757
+ on_false_message: "BACKEND LANE NOT IN THIS CHANGE — change {{cr_id}} names only {{cr_lanes_csv}} on its **Lanes** line, so the backend has nothing to build for it and is skipped. To build the backend from its master plan while this change is open, work that lane alone (/appweaver select-lane backend) and re-run implement: a change that does not name a lane leaves it on its master plan."
758
+ - id: note_fe_not_in_change
759
+ condition: "{{fe_working}} == true"
760
+ action: verify_context
761
+ require_true: fe_in_change
762
+ on_false: continue
763
+ on_false_message: "FRONTEND LANE NOT IN THIS CHANGE — change {{cr_id}} names only {{cr_lanes_csv}} on its **Lanes** line, so the frontend has nothing to build for it and is skipped. To build the frontend from its master plan while this change is open, work that lane alone (/appweaver select-lane frontend) and re-run implement: a change that does not name a lane leaves it on its master plan."
764
+
765
+ - id: warn_be_gate
766
+ condition: "{{be_report}} == true"
767
+ action: verify_context
768
+ require_true: be_may_generate
769
+ on_false: continue
770
+ on_false_message: "BACKEND LANE SKIPPED — analyze-plan's receipt does not clear the backend plan: {{be_gate_reason}}. This phase generates from the plan and does NOT read the spec, on the strength of that gate having checked it, so generating now would realise a plan against requirements nobody held it to. Re-run /appweaver analyze-plan — it re-checks the plan, repairs it on approval and re-issues the backend receipt — then re-run /appweaver implement. If what changed was a plan you re-derived with /appweaver plan main, that is the same path. Nothing is generated for this lane; any other working lane still runs."
771
+ - id: warn_fe_gate
772
+ condition: "{{fe_report}} == true"
773
+ action: verify_context
774
+ require_true: fe_may_generate
775
+ on_false: continue
776
+ on_false_message: "FRONTEND LANE SKIPPED — analyze-plan's receipt does not clear the frontend plan: {{fe_gate_reason}}. This phase generates from the plan and does NOT read the spec, on the strength of that gate having checked it, so generating now would realise a plan against requirements nobody held it to. Re-run /appweaver analyze-plan — it re-checks the plan, repairs it on approval and re-issues the frontend receipt — then re-run /appweaver implement. Nothing is generated for this lane; any other working lane still runs."
607
777
  - id: warn_be_plan
608
- condition: "{{be_declared}} == true"
778
+ condition: "{{be_report}} == true"
609
779
  action: verify_context
610
780
  require_true: be_plan_ok
611
781
  on_false: continue
612
782
  on_false_message: "BACKEND LANE SKIPPED — lane `{{be_lane_id}}` ({{be_title}}) is declared in .appweaver/stacks.json, but the plan this run generates from is not there: {{be_plan}}. Run /appweaver plan (then /appweaver analyze-plan for the masters, or /appweaver analyze-plan-delta for a change) and re-run implement. Nothing is generated for this lane; any other declared lane still runs."
613
783
  - id: warn_be_root
614
- condition: "{{be_declared}} == true"
784
+ condition: "{{be_report}} == true"
615
785
  action: verify_context
616
786
  require_true: be_root_exists
617
787
  on_false: continue
618
788
  on_false_message: "BACKEND LANE SKIPPED — lane `{{be_lane_id}}` ({{be_title}}) declares lane root folder `{{be_source_path}}` in .appweaver/stacks.json, and that path is not a directory in this repository. A lane owns its subtree and this phase writes into it, so there is nothing to generate into: scaffold a project there with /appweaver foundation (a pack or a spec for {{be_profile}}, or a spec you give it), create one with the stack's own tooling, or re-run /appweaver select-stack and point the lane at the directory that really holds its source. Nothing is generated for this lane; any other declared lane still runs."
619
789
  - id: warn_be_project
620
- condition: "{{be_declared}} == true"
790
+ condition: "{{be_report}} == true"
621
791
  action: verify_context
622
792
  require_true: be_project_ok
623
793
  on_false: continue
624
794
  on_false_message: "BACKEND LANE SKIPPED — no project found under the {{be_lane_id}} lane root folder `{{be_source_path}}`, nor anywhere else in this repository. This phase GENERATES into an existing project tree, so one has to be scaffolded first: run /appweaver foundation, which scaffolds it from a pack or from a spec (one it ships for {{be_profile}}, or one you give it), or create the project with the stack's own tooling under that path. Any ecosystem manifest marks it — .sln/.csproj, pom.xml, build.gradle(.kts), package.json, go.mod, Cargo.toml or pyproject.toml."
625
795
  - id: warn_fe_plan
626
- condition: "{{fe_declared}} == true"
796
+ condition: "{{fe_report}} == true"
627
797
  action: verify_context
628
798
  require_true: fe_plan_ok
629
799
  on_false: continue
630
800
  on_false_message: "FRONTEND LANE SKIPPED — lane `{{fe_lane_id}}` ({{fe_title}}) is declared in .appweaver/stacks.json, but the plan this run generates from is not there: {{fe_plan}}. Run /appweaver plan (then /appweaver analyze-plan for the masters, or /appweaver analyze-plan-delta for a change) and re-run implement. Nothing is generated for this lane; any other declared lane still runs."
631
801
  - id: warn_fe_root
632
- condition: "{{fe_declared}} == true"
802
+ condition: "{{fe_report}} == true"
633
803
  action: verify_context
634
804
  require_true: fe_root_exists
635
805
  on_false: continue
636
806
  on_false_message: "FRONTEND LANE SKIPPED — lane `{{fe_lane_id}}` ({{fe_title}}) declares lane root folder `{{fe_source_path}}` in .appweaver/stacks.json, and that path is not a directory in this repository. A lane owns its subtree and this phase writes into it, so there is nothing to generate into: scaffold a project there with /appweaver foundation (a pack or a spec for {{fe_profile}}, or a spec you give it), create one with the stack's own tooling, or re-run /appweaver select-stack and point the lane at the directory that really holds its source. Nothing is generated for this lane; any other declared lane still runs."
637
807
  - id: warn_fe_project
638
- condition: "{{fe_declared}} == true"
808
+ condition: "{{fe_report}} == true"
639
809
  action: verify_context
640
810
  require_true: fe_project_ok
641
811
  on_false: continue
642
812
  on_false_message: "FRONTEND LANE SKIPPED — no frontend project found under the {{fe_lane_id}} lane root folder `{{fe_source_path}}`. detect-project searched that subtree and then the whole repository, and answered at scope `{{fe_project_scope}}`; `lane` is the only scope this lane accepts. Scaffold a project there — /appweaver foundation, from a pack or a spec for {{fe_profile}} or a spec you give it, or the stack's own tooling — and re-run implement. A manifest found elsewhere in the repository does NOT count: that one belongs to another lane, and generating this lane's code against it is how a frontend that was never built reports green."
643
813
 
644
- # 6. RUNNABLE = declared AND its lane root folder is on disk AND its plan is there AND its
645
- # project is there, as one ordered assignment plus a knockdown per condition, since
814
+ # 6. RUNNABLE = worked AND its lane root folder is on disk AND its plan is there AND its
815
+ # project is there AND its receipt clears it AND the chosen change names it (always, on
816
+ # the bootstrap path), as one ordered assignment plus a knockdown per condition, since
646
817
  # the engine has no compound conditions. An undeclared lane stays false either way —
647
818
  # its `declared` value is what the raise assigns.
648
819
  - id: be_runnable_raise
649
820
  condition: "{{be_plan_ok}} == true"
650
821
  action: set
651
- vars: { be_runnable: "{{be_declared}}" }
822
+ vars: { be_runnable: "{{be_working}}" }
652
823
  - id: be_runnable_clear_root
653
824
  condition: "{{be_root_exists}} == false"
654
825
  action: set
@@ -657,10 +828,18 @@ workflow:
657
828
  condition: "{{be_project_ok}} == false"
658
829
  action: set
659
830
  vars: { be_runnable: "false" }
831
+ - id: be_runnable_clear_gate
832
+ condition: "{{be_may_generate}} == false"
833
+ action: set
834
+ vars: { be_runnable: "false" }
835
+ - id: be_runnable_clear_change
836
+ condition: "{{be_in_change}} == false"
837
+ action: set
838
+ vars: { be_runnable: "false" }
660
839
  - id: fe_runnable_raise
661
840
  condition: "{{fe_plan_ok}} == true"
662
841
  action: set
663
- vars: { fe_runnable: "{{fe_declared}}" }
842
+ vars: { fe_runnable: "{{fe_working}}" }
664
843
  - id: fe_runnable_clear_root
665
844
  condition: "{{fe_root_exists}} == false"
666
845
  action: set
@@ -669,6 +848,14 @@ workflow:
669
848
  condition: "{{fe_project_ok}} == false"
670
849
  action: set
671
850
  vars: { fe_runnable: "false" }
851
+ - id: fe_runnable_clear_gate
852
+ condition: "{{fe_may_generate}} == false"
853
+ action: set
854
+ vars: { fe_runnable: "false" }
855
+ - id: fe_runnable_clear_change
856
+ condition: "{{fe_in_change}} == false"
857
+ action: set
858
+ vars: { fe_runnable: "false" }
672
859
 
673
860
  # 7. CONFIRM EACH RUNNABLE LANE. Generation writes into the repository and costs a model
674
861
  # pass per lane, so the operator says which lanes this run is for, with the stack, the
@@ -705,7 +892,7 @@ workflow:
705
892
  action: verify_context
706
893
  require_true: generate_any
707
894
  on_false: stop
708
- on_false_message: "Nothing to implement — no declared lane is both ready and confirmed. Either every declared lane was skipped for a reason printed above (its lane root folder, its plan or its project is missing), or each one that was ready was declined at the prompt. Backend lane declared {{be_declared}}, ready {{be_runnable}}; frontend lane declared {{fe_declared}}, ready {{fe_runnable}}. Fix the reasons named above, or re-run and confirm a lane."
895
+ on_false_message: "Nothing to implement — no lane you work on (`{{working_lane}}`, .appweaver/lane.json) is both ready and confirmed. Either every working lane was skipped for a reason printed above (its lane root folder, its plan, its project, its analyze-plan receipt, or a chosen change that does not name it), or each one that was ready was declined at the prompt. Backend lane declared {{be_declared}}, worked {{be_working}}, ready {{be_runnable}}; frontend lane declared {{fe_declared}}, worked {{fe_working}}, ready {{fe_runnable}}. Fix the reasons named above, re-run and confirm a lane, or run /appweaver select-lane to work on another lane."
709
896
 
710
897
  # 8. EACH GENERATING LANE'S authoring MODE — the run's mode where the lane is going, and
711
898
  # `skip` where it is not, which silences both of that lane's branches without a
@@ -1224,27 +1411,35 @@ workflow:
1224
1411
  outputs:
1225
1412
  - { name: okf_concept, from: "$.concept" }
1226
1413
 
1227
- # ══════════════ THE API CONTRACT — the built service, as the frontend sees it ═══
1228
- # The frontend binds to the service through its published API document, and the document
1229
- # that counts is the one the CODE serves: routes, verbs, parameters, field names and
1230
- # status codes as built, which the plans only describe. So before the frontend lane
1231
- # generates, three steps, each of which WARNS and carries on — the frontend still
1232
- # generates, from its plan, when one of them cannot run:
1233
- # 1. EXPORT — start the service with its lane's `contract_run`, save what it serves at
1234
- # `contract` under its lane root folder, stop it (contract-export). The two values
1235
- # come from the lane in stacks.json — where foundation records a pack's own — or
1236
- # else from the stack profile.
1237
- # 2. CLIENT — run the frontend lane's `client` command, which generates the client from
1238
- # that document. A lane with none yet asks the developer once and records the
1239
- # answer on the lane; an empty answer carries on without a generated client.
1240
- # 3. CHECK — every operation id the frontend plan cites must be in the document
1241
- # (contract-check, by script).
1242
- # It runs whenever the frontend lane does, including when the service was built in an
1243
- # earlier run, and only where a service lane is declared.
1414
+ # ══════════════ THE API CONTRACT — published by the backend, read by the frontend ═══
1415
+ # The frontend binds to the service through its API document, and the document that
1416
+ # counts is the one the CODE serves: routes, verbs, parameters, field names and status
1417
+ # codes as built, which the plans only describe. THE BACKEND PUBLISHES IT AND THE FRONTEND
1418
+ # READS IT, so the two lanes can be built by different developers at different times.
1419
+ # Every step below WARNS and carries on — the frontend still generates, from its plan,
1420
+ # when one of them cannot run.
1421
+ # 1. PUBLISH (when the service lane generated in this run) — start the service with
1422
+ # its lane's `contract_run`, save what it serves at `contract` under its lane root
1423
+ # folder, stop it (contract-export). The two values come from the lane in
1424
+ # stacks.json — where foundation records a pack's own — or else from the stack
1425
+ # profile. The document is committed with the service, and `build` refreshes it
1426
+ # after a green compile. A frontend run never starts the service: its developer may
1427
+ # have neither the service built nor its runtime installed.
1428
+ # 2. REQUESTS — the frontend plan's contract requests the backend plan has not answered
1429
+ # yet (contract-requests), named to whichever lane runs.
1430
+ # 3. COMBINE (when the frontend lane generates) — the committed document plus every
1431
+ # operation a backend plan has drafted (api-draft.json) and the service does not
1432
+ # serve yet, written to .appweaver/contracts/openapi.combined.json (contract-combine).
1433
+ # That is how a frontend binds to an endpoint the backend has planned and not built.
1434
+ # 4. CLIENT — run the frontend lane's `client` command against the combined document. A
1435
+ # lane with none yet asks the developer once and records the answer on the lane; an
1436
+ # empty answer carries on without a generated client.
1437
+ # 5. CHECK — every operation id the frontend plan cites must be in the combined
1438
+ # document (contract-check, by script); a cited draft operation is named apart.
1244
1439
  - id: contract_arm
1245
- condition: "{{fe_go}} == true"
1440
+ condition: "{{be_go}} == true"
1246
1441
  action: set
1247
- vars: { contract_go: "{{be_declared}}" }
1442
+ vars: { contract_go: "true" }
1248
1443
  - id: contract_export
1249
1444
  condition: "{{contract_go}} == true"
1250
1445
  action: execute_script
@@ -1253,20 +1448,19 @@ workflow:
1253
1448
  subcommand: contract-export
1254
1449
  outputs:
1255
1450
  - { name: contract_ok, from: "$.ok" }
1256
- - { name: contract_doc, from: "$.doc" }
1257
- - { name: contract_doc_exists, from: "$.doc_exists" }
1451
+ - { name: export_doc, from: "$.doc" }
1452
+ - { name: export_doc_exists, from: "$.doc_exists" }
1258
1453
  - { name: contract_reason, from: "$.reason" }
1259
1454
  - { name: contract_already, from: "$.already_running" }
1260
1455
  - { name: contract_ops, from: "$.operations" }
1261
1456
  - { name: contract_log, from: "$.log_tail" }
1262
- - { name: contract_placeholder, from: "$.placeholder" }
1263
1457
  - { name: contract_export_status, from: "$.status" }
1264
1458
  - id: warn_contract_export
1265
1459
  condition: "{{contract_go}} == true"
1266
1460
  action: verify_context
1267
1461
  require_true: contract_ok
1268
1462
  on_false: continue
1269
- on_false_message: "API DOCUMENT NOT EXPORTED — {{contract_reason}}. The service lane `{{be_lane_id}}` is started with its `contract_run` command and its document fetched from its `contract` URL, both read from .appweaver/stacks.json or else its stack profile; correct them on the lane there if they do not fit this project, start anything the service needs, and re-run implement. Until then the frontend lane binds from its plan alone. Service output: {{contract_log}}"
1463
+ on_false_message: "API DOCUMENT NOT EXPORTED — {{contract_reason}}. The service lane `{{be_lane_id}}` is started with its `contract_run` command and its document fetched from its `contract` URL, both read from .appweaver/stacks.json or else its stack profile; correct them on the lane there if they do not fit this project, start anything the service needs, and re-run implement or build. Until it is exported and committed, the frontend lane binds to the last committed document plus the backend plan's draft. Service output: {{contract_log}}"
1270
1464
  - id: contract_stale_flag
1271
1465
  condition: "{{contract_already}} == true"
1272
1466
  action: set
@@ -1277,12 +1471,78 @@ workflow:
1277
1471
  require_true: contract_fresh
1278
1472
  on_false: continue
1279
1473
  on_false_message: "WARNING — the API document was {{contract_reason}}. Stop that service and re-run implement to export the code as it is now."
1280
- - id: contract_status_set
1281
- condition: "{{contract_doc_exists}} == true"
1474
+
1475
+ # 2. THE REQUEST LEDGER — read from both plans, for whichever lane runs.
1476
+ - id: contract_requests
1477
+ action: execute_script
1478
+ on_error: continue
1479
+ script: { ref: actions }
1480
+ subcommand: contract-requests
1481
+ args: ["--feature-dir", "{{feature_dir}}"]
1482
+ outputs:
1483
+ - { name: req_open_csv, from: "$.open_csv" }
1484
+ - { name: req_backend_text, from: "$.backend_text" }
1485
+ - { name: req_summary, from: "$.summary" }
1486
+ - id: req_open_flag
1487
+ condition: "{{req_open_csv}} != "
1488
+ action: set
1489
+ vars: { req_clear: "false" }
1490
+ - id: warn_requests_backend
1491
+ condition: "{{be_go}} == true"
1492
+ action: verify_context
1493
+ require_true: req_clear
1494
+ on_false: continue
1495
+ on_false_message: "FRONTEND CONTRACT REQUESTS OPEN — the frontend plan asks the service for {{req_open_csv}}, and backend-plan.md has not answered them: {{req_backend_text}}. This run generated the backend plan as it stands. Answer each request — publish it under an operation id, or decline it with the reason — with /appweaver plan or /appweaver analyze-plan for the backend lane, then re-run implement."
1496
+ - id: warn_requests_frontend
1497
+ condition: "{{fe_go}} == true"
1498
+ action: verify_context
1499
+ require_true: req_clear
1500
+ on_false: continue
1501
+ on_false_message: "CONTRACT REQUESTS STILL OPEN — {{req_open_csv}}: the frontend plan asks the service for these and the backend plan has not answered them. The bindings that wait on them are left unbound and marked in this run, and the rest of the frontend lane generates. The backend lane's developer answers them in backend-plan.md; re-run implement once they are published."
1502
+
1503
+ # 3. THE COMBINED DOCUMENT — what the frontend reads, never the running service.
1504
+ - id: combine_arm
1505
+ condition: "{{fe_go}} == true"
1506
+ action: set
1507
+ vars: { combine_go: "{{be_declared}}" }
1508
+ - id: contract_combine
1509
+ condition: "{{combine_go}} == true"
1510
+ action: execute_script
1511
+ on_error: continue
1512
+ script: { ref: actions }
1513
+ subcommand: contract-combine
1514
+ outputs:
1515
+ - { name: combined_ok, from: "$.ok" }
1516
+ - { name: contract_doc, from: "$.doc" }
1517
+ - { name: combined_status, from: "$.status" }
1518
+ - { name: combined_reason, from: "$.reason" }
1519
+ - { name: combined_export, from: "$.export" }
1520
+ - { name: combined_clashes, from: "$.clashes_csv" }
1521
+ - { name: contract_placeholder, from: "$.placeholder" }
1522
+ - id: warn_no_document
1523
+ condition: "{{combine_go}} == true"
1524
+ action: verify_context
1525
+ require_true: combined_ok
1526
+ on_false: continue
1527
+ on_false_message: "NO API DOCUMENT — {{combined_reason}}. The backend lane publishes {{combined_export}} when its implement or build runs, and drafts its plan's endpoints into api-draft.json when plan or analyze-plan runs; once they are committed and pulled here, re-run implement. Until then the frontend lane binds from its plan alone."
1528
+ - id: contract_doc_set
1529
+ condition: "{{combined_ok}} == true"
1282
1530
  action: set
1283
- vars: { contract_status: "{{contract_export_status}}; it is in context" }
1531
+ vars:
1532
+ contract_doc_exists: "true"
1533
+ contract_status: "{{combined_status}}; it is in context"
1534
+ - id: clashes_flag
1535
+ condition: "{{combined_clashes}} != "
1536
+ action: set
1537
+ vars: { clashes_clear: "false" }
1538
+ - id: warn_clashes
1539
+ condition: "{{combine_go}} == true"
1540
+ action: verify_context
1541
+ require_true: clashes_clear
1542
+ on_false: continue
1543
+ on_false_message: "DRAFT LEFT OUT — {{combined_clashes}}. The backend plan drafts an endpoint the service already serves under a different operation id, so the frontend binds to what the service serves. Correct the id in backend-plan.md (/appweaver plan or analyze-plan for the backend lane)."
1284
1544
 
1285
- # 2. THE CLIENT, generated from the document — so only once there is one.
1545
+ # 4. THE CLIENT, generated from the combined document — so only once there is one.
1286
1546
  - id: client_arm
1287
1547
  condition: "{{contract_doc_exists}} == true"
1288
1548
  action: set
@@ -1326,6 +1586,7 @@ workflow:
1326
1586
  on_error: continue
1327
1587
  script: { ref: actions }
1328
1588
  subcommand: contract-client
1589
+ args: ["--doc", "{{contract_doc}}"]
1329
1590
  outputs:
1330
1591
  - { name: client_ok, from: "$.ok" }
1331
1592
  - { name: client_cmd, from: "$.cmd" }
@@ -1349,23 +1610,34 @@ workflow:
1349
1610
  on_false: continue
1350
1611
  on_false_message: "NO API CLIENT GENERATED — the frontend lane `{{fe_lane_id}}` has no working `client` command, so this run writes its calls from the API document {{contract_doc}} instead. Set `client` on the lane in .appweaver/stacks.json (`{{contract_placeholder}}` stands for the document) to generate the client on the next run."
1351
1612
 
1352
- # 3. THE CHECK — the operation ids the frontend plan cites, against the document.
1613
+ # 5. THE CHECK — the operation ids the frontend plan cites, against the combined document.
1353
1614
  - id: contract_check
1354
- condition: "{{contract_doc_exists}} == true"
1615
+ condition: "{{client_go}} == true"
1355
1616
  action: execute_script
1356
1617
  on_error: continue
1357
1618
  script: { ref: actions }
1358
1619
  subcommand: contract-check
1359
1620
  args: ["--plan", "{{fe_plan}}", "--doc", "{{contract_doc}}"]
1360
1621
  outputs:
1361
- - { name: check_ok, from: "$.ok" }
1362
- - { name: check_reason, from: "$.reason" }
1622
+ - { name: check_ok, from: "$.ok" }
1623
+ - { name: check_reason, from: "$.reason" }
1624
+ - { name: check_drafted, from: "$.drafted_csv" }
1363
1625
  - id: warn_contract_check
1364
- condition: "{{contract_doc_exists}} == true"
1626
+ condition: "{{client_go}} == true"
1365
1627
  action: verify_context
1366
1628
  require_true: check_ok
1367
1629
  on_false: continue
1368
- on_false_message: "CONTRACT CHECK — {{check_reason}}. Each binding in {{fe_plan}} names the endpoint it calls by the operation id the backend plan gives it, and the built service must serve that id. Either the plan cites the wrong id (fix it with /appweaver plan, then analyze-plan) or the service never gave the endpoint that id (re-run implement for the backend lane). The frontend lane still generates; these bindings are where a call will fail."
1630
+ on_false_message: "CONTRACT CHECK — {{check_reason}}. Each binding in {{fe_plan}} names the endpoint it calls by the operation id the backend plan gives it, or the id its contract request proposes. An id in no document belongs to a contract request the backend has not answered (open now: [{{req_open_csv}}]), or the plan cites the wrong id (fix it with /appweaver plan, then analyze-plan), or an endpoint the service never gave that id (re-run implement for the backend lane). The frontend lane still generates; these bindings are where a call will fail."
1631
+ - id: drafted_flag
1632
+ condition: "{{check_drafted}} != "
1633
+ action: set
1634
+ vars: { drafted_clear: "false" }
1635
+ - id: warn_drafted
1636
+ condition: "{{client_go}} == true"
1637
+ action: verify_context
1638
+ require_true: drafted_clear
1639
+ on_false: continue
1640
+ on_false_message: "BOUND TO DRAFT OPERATIONS — {{check_drafted}}: the backend plan describes these and the service does not serve them yet. The frontend binds to them through the client as to any other operation; the calls fail at run time until the backend lane implements them and publishes its API document."
1369
1641
 
1370
1642
  # ══════════════ THE LANE SWAP — the frontend's rules replace the backend's ═══
1371
1643
  # build_context already scoped the two lanes' PAYLOADS apart; this is the same boundary
@@ -1395,7 +1667,7 @@ workflow:
1395
1667
  - { path: "{{fe_plan}}" }
1396
1668
  - { path: "{{domain_model}}", optional: true } # the UBIQUITOUS LANGUAGE this lane's labels, states and messages use — and what a cited BR-nnn requires
1397
1669
  - { path: .appweaver/memory/constitution.ui.md, optional: true } # the lane's ratified UX-* base — conventions, forbidden patterns, gates
1398
- - { path: "{{contract_doc}}", optional: true } # the service's API document as BUILT — what every §5 binding calls
1670
+ - { path: "{{contract_doc}}", optional: true } # the combined API document — the service as built, plus drafted operations it does not serve yet
1399
1671
  - id: gen_frontend
1400
1672
  condition: "{{fe_mode}} == bootstrap"
1401
1673
  action: agent_generate
@@ -1495,9 +1767,16 @@ workflow:
1495
1767
  parameters, request and response field names and their casing, status codes and
1496
1768
  error codes. Never take a route, a field name or a paging convention from memory,
1497
1769
  from convention or from the loaded standards' examples: the document is what the
1498
- built service actually answers. A §5 row whose id the document does not have is a
1499
- broken binding — realise the screen, leave that call unbound, report the element
1500
- `blocked` naming the id, and never guess a substitute.
1770
+ service answers. An operation marked `x-aw-draft` is one the backend plan
1771
+ describes and the service does not serve yet: bind to it exactly like any other,
1772
+ through the client, and never stub, mock or fake its response in production code.
1773
+ - A BINDING THAT WAITS ON AN OPEN CONTRACT REQUEST (open now: [{{req_open_csv}}]) is
1774
+ not broken: its operation is in no document because the backend has not answered. Realise
1775
+ the screen or component in full, write nothing that pretends to call the service
1776
+ where that call goes, mark the spot with a comment citing the RQ-n and the proposed
1777
+ operation id, and report the element `done`. Any OTHER §5 row whose id the
1778
+ document does not have is a broken binding — realise the screen, leave that call
1779
+ unbound, report the element `blocked` naming the id, and never guess a substitute.
1501
1780
  - THE CLIENT: {{client_status}}. The client lives in this lane's client layer; a
1502
1781
  generated client is called, never edited.
1503
1782
  - THE CLIENT IS NOT AN AUTHORITY. A client-side validation or permission check
@@ -1541,7 +1820,7 @@ workflow:
1541
1820
  - { path: "{{frontend_plan}}", optional: true } # the master plan — READ ONLY, never amended here
1542
1821
  - { path: "{{domain_model}}", optional: true } # the master model — the ubiquitous language as it stands BEFORE this change
1543
1822
  - { path: .appweaver/memory/constitution.ui.md, optional: true }
1544
- - { path: "{{contract_doc}}", optional: true } # the service's API document as BUILT — what every binding row calls
1823
+ - { path: "{{contract_doc}}", optional: true } # the combined API document — the service as built, plus drafted operations it does not serve yet
1545
1824
  - id: gen_frontend_delta
1546
1825
  condition: "{{fe_mode}} == delta"
1547
1826
  action: agent_generate
@@ -1626,6 +1905,8 @@ workflow:
1626
1905
  the document and take EVERYTHING about the call from it — route, verb, path and
1627
1906
  query parameters, request and response field names and their casing, status
1628
1907
  codes and error codes — never from memory, convention or the standards' examples.
1908
+ An operation marked `x-aw-draft` is planned and not served yet: bind to it through
1909
+ the client like any other, and never stub, mock or fake its response.
1629
1910
  A binding whose id the document does not have is broken: leave that call unbound,
1630
1911
  report the element `blocked` naming the id, and never guess a substitute.
1631
1912
  - THE CLIENT: {{client_status}}. The client lives in this lane's client layer; a
@@ -1706,8 +1987,10 @@ workflow:
1706
1987
  file is called; report it `done`.
1707
1988
  - A binding calls the operation its id names in the service's API document
1708
1989
  ({{contract_status}}), taking route, verb, parameters, field names and status codes
1709
- from there, through the client ({{client_status}}). An id the document does not
1710
- have stays `blocked`; never guess a substitute.
1990
+ from there, through the client ({{client_status}}). A binding that waits on an open
1991
+ contract request (open now: [{{req_open_csv}}]) is marked with a comment citing the RQ-n and
1992
+ its element reported `done`; any other id the document does not have stays
1993
+ `blocked`; never guess a substitute.
1711
1994
  - If an element cannot be realised, do not work around it: report it `blocked` and
1712
1995
  say what stops you. A plan that is wrong is fixed in plan / analyze-plan.
1713
1996
  - Report every listed element in an `elements` list in your closing JSON block:
@@ -1871,6 +2154,9 @@ outputs:
1871
2154
  frontend_implemented: "{{fe_go}}"
1872
2155
  frontend_plan: "{{fe_plan}}" # the document the frontend lane generated FROM
1873
2156
  frontend_files: "{{fe_files}}"
2157
+ api_document: "{{export_doc}}" # exported from the service in this run (backend lane) — commit it, it is how the frontend developer receives the surface
2158
+ frontend_api_document: "{{contract_doc}}" # what the frontend bound to: the export plus the drafted operations it does not serve yet (not committed)
2159
+ contract_requests: "{{req_summary}}" # the frontend plan's RQ-n requests and the backend plan's answers
1874
2160
  # Every plan element realised, per lane — `true` for a lane that did not generate.
1875
2161
  # `*_completion_pass` says whether the first pass left gaps a second one had to close.
1876
2162
  # `*_outcome` is the one word for what the lane did: complete (it generated and every
@@ -1895,10 +2181,16 @@ outputs:
1895
2181
  # Bootstrap path only. tracked=false means this feature has not been through a master
1896
2182
  # analyze-plan run since the gate began leaving a receipt — an unknown, not a stale plan,
1897
2183
  # and it blocks nothing.
1898
- plan_gate_tracked: "{{gate_tracked}}"
1899
- plan_gate_fresh: "{{gate_fresh}}"
1900
- plan_gate_at: "{{gate_at}}"
1901
- plan_gate_verdict: "{{gate_verdict}}"
1902
- plan_gate_routed_to_specify: "{{gate_routed_csv}}" # open spec/model gaps this code does not cover
1903
- note: "Lanes come from .appweaver/stacks.json — backend declared {{be_declared}} (ready {{be_runnable}}, implemented {{be_go}}), frontend declared {{fe_declared}} (ready {{fe_runnable}}, implemented {{fe_go}}). A declared lane that was skipped printed its reason above and did not stop the other one; an undeclared lane produces nothing and that is not a failure. Each generating lane read its PLAN for what to build and {{domain_model}} for what the domain is — plus {{delta_model}} on the delta path, since the master model does not carry the change until finalize-delta merges it. Every one of those documents was READ and none was written: this phase generates code and nothing else. Each lane's outcome is backend={{be_outcome}}, frontend={{fe_outcome}} — `already-implemented` means the lane wrote no files because every plan element was already realised in its lane root folder, which is the correct result of re-running implement on a finished feature and not a failure. On the bootstrap path the plans were checked against analyze-plan's receipt before anything generated (tracked={{gate_tracked}}, fresh={{gate_fresh}}, gated {{gate_at}}) — tracked=false means this feature has never passed analyze-plan since the gate began recording what it proved, which is an unknown rather than a stale plan and blocks nothing; run /appweaver analyze-plan once to start the record."
2184
+ working_lane: "{{working_lane}}" # the developer's working lane(s) — .appweaver/lane.json
2185
+ backend_plan_gate_tracked: "{{be_gate_tracked}}"
2186
+ backend_plan_gate_fresh: "{{be_gate_fresh}}"
2187
+ backend_plan_gate_at: "{{be_gate_at}}"
2188
+ backend_plan_gate_verdict: "{{be_gate_verdict}}"
2189
+ backend_plan_gate_routed_to_specify: "{{be_gate_routed_csv}}" # open spec/model gaps this code does not cover
2190
+ frontend_plan_gate_tracked: "{{fe_gate_tracked}}"
2191
+ frontend_plan_gate_fresh: "{{fe_gate_fresh}}"
2192
+ frontend_plan_gate_at: "{{fe_gate_at}}"
2193
+ frontend_plan_gate_verdict: "{{fe_gate_verdict}}"
2194
+ frontend_plan_gate_routed_to_specify: "{{fe_gate_routed_csv}}"
2195
+ note: "Lanes come from .appweaver/stacks.json, and the ones you work on from .appweaver/lane.json (`{{working_lane}}`) — backend declared {{be_declared}} (worked {{be_working}}, ready {{be_runnable}}, implemented {{be_go}}), frontend declared {{fe_declared}} (worked {{fe_working}}, ready {{fe_runnable}}, implemented {{fe_go}}). A declared lane you do not work on is left to its developer and is not reported here. A declared lane that was skipped printed its reason above and did not stop the other one; an undeclared lane produces nothing and that is not a failure. Each generating lane read its PLAN for what to build and {{domain_model}} for what the domain is — plus {{delta_model}} on the delta path, since the master model does not carry the change until finalize-delta merges it. Every one of those documents was READ and none was written: this phase generates code and nothing else. Each lane's outcome is backend={{be_outcome}}, frontend={{fe_outcome}} — `already-implemented` means the lane wrote no files because every plan element was already realised in its lane root folder, which is the correct result of re-running implement on a finished feature and not a failure. On the bootstrap path each working lane's plan was checked against that lane's analyze-plan receipt before anything generated (backend tracked={{be_gate_tracked}}, fresh={{be_gate_fresh}}, gated {{be_gate_at}}; frontend tracked={{fe_gate_tracked}}, fresh={{fe_gate_fresh}}, gated {{fe_gate_at}}) — tracked=false means that lane has never passed analyze-plan since the gate began recording what it proved, which is an unknown rather than a stale plan and blocks nothing; run /appweaver analyze-plan once to start the record."
1904
2196
  next: "This run was mode `{{mode}}`, change `{{active_change}}` (empty on the bootstrap path). BOOTSTRAP → /appweaver build → analyze-slice → run. DELTA → /appweaver build → analyze-slice-delta {{active_change}} → finalize-delta → analyze-slice → close-delta, with the change still OPEN throughout — compile first, then analyze-slice-delta verifies that every element the delta plans marked ADDED or MODIFIED is actually in the code with a paired test (the gate that catches a lane you declined at the prompt above), then finalize-delta merges it into spec.md + the context's domain-model.md while leaving it open, analyze-slice verifies the slice against those merged masters while a red result is still recoverable, and close-delta LAST archives the change and bumps the version."