aw-spec-cli 0.1.29 → 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.
- package/assets/.amp/agent.json +22 -0
- package/assets/.amp/settings-edit.json +5 -0
- package/assets/.amp/settings-read.json +12 -0
- package/assets/.appweaver/DESIGN-FORMAT.md +9 -0
- package/assets/.appweaver/STACK-PROFILES.md +102 -15
- package/assets/.appweaver/orchestrator/modules/delta-plan.orch.yaml +40 -18
- package/assets/.appweaver/orchestrator/modules/delta-spec.orch.yaml +56 -4
- package/assets/.appweaver/orchestrator/modules/lane-gate.orch.yaml +17 -1
- package/assets/.appweaver/orchestrator/rev-templates/ui-design-template.skeleton.md +12 -3
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan-delta.orch.yaml +289 -24
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +370 -85
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +349 -65
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +39 -7
- package/assets/.appweaver/orchestrator/workflows/aw-spec-build.orch.yaml +63 -4
- package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +5 -4
- package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +430 -138
- package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +392 -76
- package/assets/.appweaver/orchestrator/workflows/aw-spec-select-lane.orch.yaml +104 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-select-stack.orch.yaml +20 -3
- package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +26 -3
- package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +314 -44
- package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +249 -20
- package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +17 -2
- package/assets/.appweaver/scripts/python/aw-plangate.py +109 -19
- package/assets/.appweaver/scripts/python/aw-stack.py +1295 -9
- package/assets/.appweaver/templates/delta-design-template.md +7 -5
- package/assets/.appweaver/templates/delta-spec-template.md +2 -0
- package/assets/.appweaver/templates/design-template.md +17 -0
- package/assets/.appweaver/templates/ui-design-template.md +19 -4
- package/assets/.augment/agent.json +22 -0
- package/assets/.augment/commands/appweaver.md +12 -0
- package/assets/.cline/agent.json +22 -0
- package/assets/.clinerules/workflows/appweaver.md +7 -0
- package/assets/.codex/agent.json +22 -0
- package/assets/.continue/agent.json +22 -0
- package/assets/.continue/prompts/appweaver.md +13 -0
- package/assets/.cursor/agent.json +22 -0
- package/assets/.cursor/commands/appweaver.md +7 -0
- package/assets/.devin/agent.json +22 -0
- package/assets/.devin/workflows/appweaver.md +11 -0
- package/assets/.factory/agent.json +22 -0
- package/assets/.factory/commands/appweaver.md +12 -0
- package/assets/.gemini/agent.json +22 -0
- package/assets/.github/prompts/appweaver.prompt.md +13 -0
- package/assets/.kilo/agent.json +28 -0
- package/assets/.kilo/agents/appweaver-edit.md +12 -0
- package/assets/.kilo/agents/appweaver-read.md +12 -0
- package/assets/.kilo/commands/appweaver.md +11 -0
- package/assets/.kiro/agent.json +22 -0
- package/assets/.kiro/skills/appweaver/SKILL.md +12 -0
- package/assets/.opencode/agent.json +22 -0
- package/assets/.opencode/agents/appweaver-edit.md +12 -0
- package/assets/.opencode/agents/appweaver-read.md +13 -0
- package/assets/.opencode/commands/appweaver.md +11 -0
- package/assets/.roo/agent.json +22 -0
- package/assets/.roo/commands/appweaver.md +12 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +14 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +14 -0
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/copy-assets.d.ts +16 -3
- package/dist/utils/copy-assets.d.ts.map +1 -1
- package/dist/utils/copy-assets.js +29 -14
- package/dist/utils/copy-assets.js.map +1 -1
- 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
|
|
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
|
|
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
|
|
42
|
-
# document is exported from the running code
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
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
|
|
56
|
-
# the bytes it checked and
|
|
57
|
-
# path, before any lane generates. A
|
|
58
|
-
# since the receipt existed — reported, never refused. One whose
|
|
59
|
-
# or whose verdict left plan gaps,
|
|
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
|
|
137
|
-
version: 3.
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
#
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
#
|
|
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
|
-
|
|
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
|
|
258
|
-
|
|
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
|
-
|
|
261
|
-
|
|
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
|
-
|
|
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/.
|
|
413
|
-
#
|
|
414
|
-
#
|
|
415
|
-
#
|
|
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
|
|
512
|
-
# the context's domain-model.md and
|
|
513
|
-
# run reached. The plans' own `**Version**:` headers cannot answer
|
|
514
|
-
# used: close-delta stamps them and spec.md to the same number in
|
|
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:
|
|
518
|
-
# gaps a plan rewrite closes. Verdict `routed-gaps` generates and warns:
|
|
519
|
-
# can only be closed in the spec or the model, and it is listed for
|
|
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
|
|
522
|
-
# the master receipt is silent about them, and reading it
|
|
523
|
-
# for drift in documents this run is not building from.
|
|
524
|
-
- id:
|
|
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:
|
|
532
|
-
- { name:
|
|
533
|
-
- { name:
|
|
534
|
-
- { name:
|
|
535
|
-
- { name:
|
|
536
|
-
- { name:
|
|
537
|
-
- { name:
|
|
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
|
|
540
|
-
#
|
|
541
|
-
#
|
|
542
|
-
#
|
|
543
|
-
- id:
|
|
544
|
-
condition: "{{
|
|
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: {
|
|
547
|
-
|
|
548
|
-
|
|
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: {
|
|
675
|
+
vars: { fe_may_generate: "{{fe_gate_may_generate}}" }
|
|
560
676
|
|
|
561
|
-
|
|
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:
|
|
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: {{
|
|
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
|
|
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: "{{
|
|
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: "{{
|
|
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: "{{
|
|
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: "{{
|
|
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: "{{
|
|
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: "{{
|
|
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 =
|
|
645
|
-
# project is there
|
|
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: "{{
|
|
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: "{{
|
|
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
|
|
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
|
|
1228
|
-
# The frontend binds to the service through its
|
|
1229
|
-
#
|
|
1230
|
-
#
|
|
1231
|
-
#
|
|
1232
|
-
# generates, from its plan,
|
|
1233
|
-
#
|
|
1234
|
-
#
|
|
1235
|
-
#
|
|
1236
|
-
#
|
|
1237
|
-
#
|
|
1238
|
-
#
|
|
1239
|
-
#
|
|
1240
|
-
#
|
|
1241
|
-
#
|
|
1242
|
-
#
|
|
1243
|
-
#
|
|
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: "{{
|
|
1440
|
+
condition: "{{be_go}} == true"
|
|
1246
1441
|
action: set
|
|
1247
|
-
vars: { contract_go: "
|
|
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:
|
|
1257
|
-
- { name:
|
|
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
|
|
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
|
-
|
|
1281
|
-
|
|
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:
|
|
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
|
-
#
|
|
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
|
-
#
|
|
1613
|
+
# 5. THE CHECK — the operation ids the frontend plan cites, against the combined document.
|
|
1353
1614
|
- id: contract_check
|
|
1354
|
-
condition: "{{
|
|
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,
|
|
1362
|
-
- { name: check_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: "{{
|
|
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,
|
|
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
|
|
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
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
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
|
|
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}}).
|
|
1710
|
-
|
|
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
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
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."
|