aw-spec-cli 0.1.16 → 0.1.18

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 (70) hide show
  1. package/assets/.appweaver/DESIGN-FORMAT.md +18 -18
  2. package/assets/.appweaver/ORCH-DIALECT.md +1 -3
  3. package/assets/.appweaver/RULE-PARTITION.md +29 -13
  4. package/assets/.appweaver/STACK-PROFILES.md +10 -9
  5. package/assets/.appweaver/orchestrator/.state/smoke.state.json +32 -0
  6. package/assets/.appweaver/orchestrator/AGENT-PROFILE.md +4 -1
  7. package/assets/.appweaver/orchestrator/modules/delta-plan.orch.yaml +251 -0
  8. package/assets/.appweaver/orchestrator/modules/delta-spec.orch.yaml +104 -35
  9. package/assets/.appweaver/orchestrator/orchestrator.py +5 -4
  10. package/assets/.appweaver/orchestrator/rev-templates/delta-design-template.skeleton.md +94 -36
  11. package/assets/.appweaver/orchestrator/rev-templates/design-template.skeleton.md +1 -1
  12. package/assets/.appweaver/orchestrator/rev-templates/domain-model-template.skeleton.md +115 -0
  13. package/assets/.appweaver/orchestrator/rev-templates/domain-rule-bindings.skeleton.md +2 -9
  14. package/assets/.appweaver/orchestrator/rev-templates/tech-rule-bindings.skeleton.md +16 -40
  15. package/assets/.appweaver/orchestrator/rev-templates/ui-delta-design-template.skeleton.md +38 -17
  16. package/assets/.appweaver/orchestrator/rev-templates/ui-design-template.skeleton.md +8 -8
  17. package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +10 -11
  18. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan-delta.orch.yaml +673 -0
  19. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +535 -0
  20. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +643 -0
  21. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +84 -701
  22. package/assets/.appweaver/orchestrator/workflows/aw-spec-build.orch.yaml +4 -4
  23. package/assets/.appweaver/orchestrator/workflows/aw-spec-characterize.orch.yaml +7 -7
  24. package/assets/.appweaver/orchestrator/workflows/aw-spec-close-delta.orch.yaml +16 -14
  25. package/assets/.appweaver/orchestrator/workflows/aw-spec-constitution.orch.yaml +2 -2
  26. package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +243 -144
  27. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +877 -578
  28. package/assets/.appweaver/orchestrator/workflows/aw-spec-inventory.orch.yaml +4 -4
  29. package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +32 -91
  30. package/assets/.appweaver/orchestrator/workflows/aw-spec-parity.orch.yaml +2 -2
  31. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +836 -514
  32. package/assets/.appweaver/orchestrator/workflows/aw-spec-run.orch.yaml +2 -2
  33. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-bounded-context.orch.yaml +132 -0
  34. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-feature.orch.yaml +38 -5
  35. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-stack.orch.yaml +3 -3
  36. package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +271 -979
  37. package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +1085 -3385
  38. package/assets/.appweaver/scripts/bash/aw-xref.sh +1 -2
  39. package/assets/.appweaver/scripts/bash/common.sh +47 -0
  40. package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +914 -3220
  41. package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +1 -2
  42. package/assets/.appweaver/scripts/python/aw-payload-parity.py +19 -3
  43. package/assets/.appweaver/scripts/python/aw-stack.py +6 -2
  44. package/assets/.appweaver/scripts/python/aw-xref.py +86 -350
  45. package/assets/.appweaver/templates/constitution-ui-template.md +1 -1
  46. package/assets/.appweaver/templates/delta-design-template.md +120 -30
  47. package/assets/.appweaver/templates/delta-domain-model-template.md +81 -0
  48. package/assets/.appweaver/templates/delta-spec-template.md +43 -17
  49. package/assets/.appweaver/templates/design-template.md +68 -98
  50. package/assets/.appweaver/templates/domain-model-template.md +130 -0
  51. package/assets/.appweaver/templates/okf-concept-template.md +2 -2
  52. package/assets/.appweaver/templates/source-map-template.md +3 -3
  53. package/assets/.appweaver/templates/spec-template.md +100 -122
  54. package/assets/.appweaver/templates/ui-delta-design-template.md +159 -0
  55. package/assets/.appweaver/templates/ui-design-template.md +46 -75
  56. package/assets/guardrails/projects/dotnet-default-project/domain-rule-bindings.md +7 -18
  57. package/assets/guardrails/projects/dotnet-default-project/tech-rule-bindings.md +34 -39
  58. package/assets/guardrails/requirement-spec.txt +2 -2
  59. package/assets/guardrails/technology/dotnet-webapi/testing.md +1 -1
  60. package/package.json +1 -1
  61. package/assets/.appweaver/orchestrator/modules/delta-design.orch.yaml +0 -105
  62. package/assets/.appweaver/orchestrator/modules/spec-state-gate.orch.yaml +0 -87
  63. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-delta-design.orch.yaml +0 -251
  64. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-design.orch.yaml +0 -813
  65. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement-delta.orch.yaml +0 -253
  66. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement-ui.orch.yaml +0 -663
  67. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan-ui.orch.yaml +0 -600
  68. package/assets/.appweaver/orchestrator/workflows/aw-spec-story-plan.orch.yaml +0 -1147
  69. package/assets/.appweaver/orchestrator/workflows/aw-spec-sync-stories.orch.yaml +0 -1121
  70. package/assets/.appweaver/templates/story-template.md +0 -178
@@ -1,89 +1,99 @@
1
1
  # =============================================================================
2
- # aw-spec-plan (orchestrator) — the spec-anchored Plan phase (service lane)
2
+ # aw-spec-plan (orchestrator) — the spec-anchored Plan phase, BOTH LANES
3
3
  #
4
- # Authors and MAINTAINS one high-level plan per feature and lane. This workflow owns
5
- # the SERVICE lane's document — specs/<Aggregate>/backend-plan.md — the tech-agnostic
6
- # HLD that MEDIATES between the spec and the code (formerly design.md; the scaffold
7
- # migrates a legacy design.md by renaming it). The frontend lane's twin,
8
- # frontend-plan.md, is owned by /appweaver plan-ui — a SEPARATE command because the
9
- # two lanes are governed by different rule sets (tech-rule-bindings gives each lane
10
- # its own set; one command would hand this phase both lanes' whole rule surface).
11
- # `implement` GENERATES the code from spec + plan (model-driven, not a deterministic
12
- # extract). See .appweaver/DESIGN-FORMAT.md + .appweaver/templates/design-template.md.
13
- # (`plan` re-uses the name of a long-retired phase whose plan.md had zero consumers.)
4
+ # Authors the feature's LANE PLANS — the tech-agnostic HLDs that MEDIATE between the
5
+ # spec and the code:
6
+ # • specs/<Context>/<Feature>/backend-plan.md when a BACKEND lane is declared
7
+ # • specs/<Context>/<Feature>/frontend-plan.md when a FRONTEND lane is declared
8
+ # `implement` GENERATES each lane's code from ITS PLAN ALONE (model-driven, not a
9
+ # deterministic extract) — it never reads the spec, so whatever a plan leaves out is
10
+ # missing from the code. See .appweaver/DESIGN-FORMAT.md + the design-template /
11
+ # ui-design-template.
14
12
  #
15
- # WHERE IT SITS. specify/sync-spec (⌛ sync-spec is not built yet; the hand route is
16
- # .appweaver/SPEC-EDIT-PROTOCOL.md) move the spec; the developer then runs plan, which
17
- # conforms this feature's design to what moved. Story designs (/appweaver story-plan)
18
- # READ this document for the ids they carry and NEVER write back — nothing merges into
19
- # it, so it keeps exactly one writer and cannot be silently rewritten. It is the
20
- # FEATURE-LEVEL ARCHITECTURAL CONFIRMATION: what the feature's structure IS to be,
21
- # tech-agnostic, with the concrete file-level work left to the per-story passes.
13
+ # THE LANES COME FROM stacks.json, NOT FROM A FLAG. select-stack is the sole writer of
14
+ # the lane declaration, and this phase simply plans whichever lanes are there: backend
15
+ # only, frontend only, or both. A lane declared `(none)` produces no plan and no failure.
16
+ # With NO lane declared at all the phase stops and names select-stack — a plan written
17
+ # against no declared stack invents its own.
22
18
  #
23
- # IT READS THE CODE. concept-paths resolves the feature's OKF concept to the source
24
- # files realising it and the authoring steps declare `tools: Read`, so a plan over an
25
- # existing codebase ADJUSTS to the shape that is actually there. The code is the
26
- # what-exists authority; the spec stays the only authority on behaviour and the only
27
- # domain input. source_count 0 = greenfield, and the phase never blocks on it.
19
+ # ONE COMMAND, TWO LANES, TWO RULE SETS. The lanes take their rules from different
20
+ # headings of tech-rule-bindings (`## /aw-spec-plan` for the service, `## /aw-spec-plan-ui`
21
+ # for the frontend), and the frontend set is loaded ONLY when that lane is declared — so
22
+ # a backend-only project sees exactly the rules it did before. In a two-lane project both
23
+ # sets are sticky for the whole run; the lane boundary is held by the prompts instead,
24
+ # and §5 Contract Binding is where it is stated.
28
25
  #
29
- # IT PUBLISHES THE INTERFACES. The feature's published surface lives in
30
- # specs/<Aggregate>/interfaces.md — APPEND-ONLY, script-numbered (IF-nnn, dated,
31
- # status: proposed). This phase writes it and §3 of the plan cites the ids; /appweaver
32
- # plan-ui and the FE story designs CONSUME the latest entry per id. A changed interface
33
- # is a superseding entry, never an edit — one wording, one lifecycle, no second copy.
26
+ # RENAMED FROM `design`. design.md → backend-plan.md, design.ui.md → frontend-plan.md,
27
+ # and the former separate `design-ui` command is FOLDED IN here. The scaffold MIGRATES a
28
+ # legacy document by renaming it, so an existing feature moves to the plan names on its
29
+ # next run instead of needing a flag day.
34
30
  #
35
- # THREE MODES, resolved deterministically (self-detecting on the plan doc's own
36
- # existence, symmetric with how specify keys off spec.md):
37
- # • bootstrap_gf (default) — no backend-plan.md yet: author the master from the
38
- # whole spec, four-layer greenfield template (Domain·Application·API·Infra).
39
- # • bootstrap_bf — same, but onboard installed the code-derived design-template
40
- # override: author with the project's AS-BUILT structure instead.
41
- # • update — backend-plan.md exists: conform it to what the spec's Change History
42
- # ledger says moved SINCE THIS PLAN WAS LAST DERIVED, and nothing else. This
43
- # replaces the former delta-design branch (delta-design + finalize-delta merge);
44
- # in the story flow the spec moves via sync-spec and the plan follows here.
31
+ # TEMPLATE-DRIVEN AUTHORING (greenfield-safe). Each lane's authoring step follows the
32
+ # structure of whichever template is ACTIVE for it, chosen deterministically:
33
+ # • GREENFIELD (default) — the kit's templates: the four-layer service template
34
+ # (Domain·Application·API·Infra) and the experience template (§0–§9).
35
+ # • BROWNFIELD — when /appweaver onboard has GENERATED a code-derived override
36
+ # (design-template / ui-design-template; override_active), a NEW feature added to an
37
+ # existing repo is authored with the as-built structure instead, so it slots into the
38
+ # codebase already there.
39
+ # The variants are separate steps selected by one `*_mode` var per lane, so greenfield
40
+ # authoring never sees the brownfield guidance and vice-versa.
45
41
  #
46
- # TRACKING (the lockstep made real). Every write ends with plan-stamp, which is the
47
- # only writer of the plan's version identity:
48
- # • the doc's **Version** header := the spec version this plan now derives from,
49
- # • an update appends the plan's own Change History row naming the conformed ids,
50
- # • .aw-plan-track.backend-plan.json (beside the spec) records {spec_version,
51
- # sha256} — one track file per plan document, so the two lanes never contend.
52
- # plan-check reads that record back: BEHIND (ledger rows above the derived version →
53
- # the exact ids to conform), EDITED (hash mismatch — the doc was hand-edited since
54
- # the stamp; refused unless accept_manual folds the edits in), UNTRACKED (a migrated
55
- # legacy design.md with no baseline — the update reconciles the WHOLE plan once and
56
- # baselines it). The model writes NO version anywhere; the script stamps.
42
+ # IT READS THE DOMAIN MODEL AND NEVER WRITES IT. The aggregate root, its entities, its
43
+ # events and its invariants (BR-n) live in specs/<Context>/domain-model.md, which
44
+ # `specify` authors. This phase records where each invariant is ENFORCED, not what it
45
+ # says — the model is spec-tier, the plan is realisation-tier.
57
46
  #
58
- # LANE-AWARE. Lanes come from .appweaver/stacks.json (select-stack declares them):
59
- # • backend lane declared → author backend-plan.md. This is a REQUIREMENT, not a
60
- # default: with no backend stack declared the phase stops and says so, because a
61
- # service plan written against no declared service invents its own stack.
62
- # • frontend lane also declared → the handoff names /appweaver plan-ui.
63
- # • frontend-only, or nothing declared at all → stop, each with its own message.
47
+ # AUTHORS only — the gates are SEPARATE stages. IT ASKS WHAT IT IS PLANNING, exactly as
48
+ # `implement` asks what it is building: list what is open, pick one, activate it. The
49
+ # difference is that this phase can also be pointed at the feature's OWN spec, so the menu
50
+ # carries two synthetic rows beside the open changes:
51
+ # • main → author the MASTER plan for each declared lane, from spec.md + the context's
52
+ # domain model. On a feature that has never been planned this is a BOOTSTRAP and
53
+ # nothing is asked. On one that HAS masters it is a RE-DERIVATION, and it is
54
+ # CONFIRMED first — the run stops on a decline, leaving them untouched.
55
+ # Next: /appweaver analyze-plan (spec↔plan gate) → implement.
56
+ # • CR-… → author THAT change's delta plans ONLY (delta-plan module). The masters are
57
+ # left UNTOUCHED, and finalize-delta does not merge them either: it merges the
58
+ # change's spec and model halves, and a master plan is RE-DERIVED from those — which
59
+ # is what the main row above does. The change stays OPEN.
60
+ # Next: analyze-plan-delta → implement (which asks which change) → build →
61
+ # analyze-slice-delta → finalize-delta → analyze-slice → close-delta.
62
+ # • new → open a plan-only change first, then plan it: a refactor or architectural
63
+ # decision where the realisation of EXISTING spec ids moves with no spec delta. Its
64
+ # delta-spec stays an unfilled stub, and that stub is the signal downstream.
65
+ # The argument doubles as the answer — `plan main`, `plan CR-…` and `plan new` all skip
66
+ # the prompt — and only the WHOLE argument counts, so guidance prose never selects.
64
67
  #
65
- # AUTHORS only — the gates are SEPARATE stages: /appweaver analyze-design proves
66
- # spec ↔ plan conformance after every bootstrap AND every update.
68
+ # THE MENU APPEARS ONLY ONCE THERE IS A MASTER. With none yet there is nothing to choose:
69
+ # a delta needs a master to be a delta OF, and so does a plan-only change. A first run
70
+ # therefore authors the masters with no prompt at all. A change already open on such a
71
+ # feature waits for the next run, when the menu will offer it.
67
72
  #
68
- # Launch: /appweaver plan [guidance] (accept_manual:true folds hand edits in)
73
+ # The forward paths are explicit:
74
+ # NEW : specify → plan → analyze-plan → implement
75
+ # CHANGE : specify (delta-spec + model delta) → plan (delta plans, per change) →
76
+ # analyze-plan-delta → implement (the delta plans) → build →
77
+ # finalize-delta → analyze-slice → close-delta
78
+ #
79
+ # Launch: /appweaver plan [guidance]
80
+ # /appweaver plan main (re-plan the masters, no prompt)
81
+ # /appweaver plan CR-20260910-… (plan THAT open change, no prompt)
82
+ # /appweaver plan new (open a plan-only change and plan it)
69
83
  # Prereq: /appweaver specify
70
84
  # =============================================================================
71
85
 
72
86
  name: aw-spec-plan
73
- description: "Author or update the service lane's high-level plan — specs/<Feature>/backend-plan.md — from the spec. Self-detects: no plan yet ⇒ bootstrap the master (greenfield four-layer template, or the code-derived structure when onboard installed the design-template override); plan exists ⇒ UPDATE — conform exactly the sections serving the ids the spec's Change History moved since this plan's derivation stamp, preserving the Assumptions/Workarounds/Change History ledgers. Tracking is script-owned: plan-stamp moves the **Version** header to the derived spec version, appends the plan's history row and records the body hash; plan-check refuses hand-edited documents unless accept_manual folds them in. Reads the CODE as well as the spec (concept-paths + tools: Read), so a plan over an existing codebase conforms to the as-built shape; the spec remains the only domain and behaviour authority. Publishes the feature's PUBLISHED SURFACE to the append-only interfaces.md (script-assigned IF-nnn, status: proposed) — the anchor /appweaver plan-ui and the FE story designs consume; the plan's API section cites those ids rather than restating the contract. Lane-aware via stacks.json — a declared frontend lane routes its twin to /appweaver plan-ui; frontend-only projects are refused. Authoring only — gate every write at /appweaver analyze-design."
74
- version: 4.1.0
87
+ description: "Author the feature's lane plans from the spec and the bounded context's domain model — backend-plan.md when a backend lane is declared, frontend-plan.md when a frontend lane is, both when both are. Lanes come from .appweaver/stacks.json, so one command covers backend-only, frontend-only and full-stack projects; the former separate design-ui command is folded in. It ASKS what it is planning, the way implement asks what it is building: the master (`main`), one of the OPEN CHANGES, or a new plan-only change for a refactor with no spec delta — the argument doubles as the answer, and the menu appears only once a master exists, so a first run bootstraps with no prompt. `main` on a feature that already has masters RE-DERIVES them from the merged spec.md + domain-model.md — the operation finalize-delta's contract assumes when it merges a change's spec and model halves and no lane plan — and is confirmed before it rewrites anything. A change instead gets delta-backend-plan.md / delta-frontend-plan.md inside changes/<CR-id>/ while the masters stay untouched, written against the effective model (the master model, the change's own model delta, and the pending model deltas of LOWER waves, which are its predecessors) and against the feature's real source files from its OKF concept. Master authoring is template-driven: the kit's templates by default, or the code-derived as-built structure when onboard installed an override. Authoring only — a master gates at analyze-plan; a change ships through the CR chain."
88
+ version: 5.0.0
75
89
  agent_agnostic: true
76
90
  dialect: aw-spec.orchestrator/v1
77
91
 
78
92
  inputs:
79
93
  user_input:
80
- description: Optional plan guidance (architectural constraints, trade-off preferences).
94
+ description: "Optional plan guidance (architectural constraints, trade-off preferences), OR the answer to the one question this phase asks: `main` to re-plan the masters, a bare CR id to plan that open change, `new` to open a plan-only change. Only the whole argument counts as an answer, so guidance prose is never mistaken for one."
81
95
  required: false
82
96
  bind: USER_INPUT
83
- accept_manual:
84
- description: "true ⇒ a hand-edited plan (tracking state edited/edited_behind) is folded in instead of refused: the update keeps the manual edits' intent, reconciles them with the spec, and the stamp re-baselines."
85
- required: false
86
- bind: ACCEPT_MANUAL
87
97
 
88
98
  scripts:
89
99
  actions:
@@ -95,10 +105,13 @@ scripts:
95
105
 
96
106
  # Sticky standards seed = the UNIVERSAL always-on rule only (ai-rules). The TECHNICAL/
97
107
  # architecture rule files are CONFIGURATION, loaded per-command from
98
- # guardrails/projects/<project>/tech-rule-bindings.md under "## /aw-spec-plan" by the
99
- # tech-select module. No domain rule file is loaded here — domain reaches the plan
100
- # solely as spec ids (BR-/CN-/DC-/IC-). The technology base itself arrives via
101
- # constitution.technical.md (referred to directly, as-is — NOT mapped).
108
+ # guardrails/projects/<project>/tech-rule-bindings.md by the tech-select module (see the
109
+ # tech_select steps below), exactly as the DOMAIN rule files are loaded by domain-select.
110
+ # Per the rule-partition this phase loads NO domain rule file — the domain reaches the
111
+ # plan as spec ids (BR-/CN-/DC-/IC-) and as the context's domain model. The technology
112
+ # base itself arrives via constitution.technical.md / constitution.ui.md (referred to
113
+ # directly, as-is — NOT mapped). DDD stays in specify; coding-standards in implement.
114
+ # (missing files skipped at assembly time)
102
115
  standards:
103
116
  - { path: .appweaver/ai-rules.md } # kit-resident: the universal operating rules, every flow
104
117
 
@@ -106,71 +119,98 @@ vars:
106
119
  lanes_declared: "false"
107
120
  be_declared: "false"
108
121
  fe_declared: "false"
109
- be_gate_msg: "No backend lane is declared in .appweaver/stacks.json, so there is no service to plan. Run /appweaver select-stack and declare the backend stack plus its source path, then re-run plan. A project that is genuinely UI-only plans the frontend lane alone with /appweaver plan-ui."
110
- brownfield_plan: "false" # design-scaffold.override_active — onboard installed the code-derived template
111
- plan_created: "false" # scaffold.created — true ⇒ the doc was absent ⇒ bootstrap
112
- plan_migrated: "false" # scaffold renamed a legacy design.md into backend-plan.md this run
113
- plan_mode: "bootstrap_gf" # resolved below → bootstrap_gf | bootstrap_bf | update
114
- track_state: "" # plan-check → fresh | untracked | clean | behind | edited | edited_behind
115
- derived_version: "" # the spec version the plan last derived from (empty when untracked)
116
- current_version: "" # the spec's current ledger version
117
- changed_csv: "" # ids the ledger moved above the derived version
118
- changed_summary: "" # the moved rows' summaries, for the update prompt
119
- manual_ok: "true" # false ⇒ hand edits present and accept_manual not given
120
- plan_stale: "true" # false ⇒ update mode with nothing to do (clean) — friendly stop
121
- stamp_ok: "false"
122
- stamp_error: ""
123
- fe_next: "" # filled when a frontend lane is declared — the plan-ui handoff
124
- source_csv: "" # concept-paths — the feature's source files, as a PATH LIST
125
- source_count: "0" # 0 ⇒ greenfield: no code realises this feature yet
126
- okf_concept: "" # the feature's OKF concept doc (use-case log, source map)
127
- if_ok: "true" # log-append — the interface publication
128
- if_error: ""
129
- if_appended: "0"
130
- if_ids_csv: ""
131
- if_unknown_csv: ""
132
- if_badsup_csv: ""
122
+ be_lane_id: ""
123
+ fe_lane_id: ""
124
+ active_change: ""
125
+ has_change: "false"
126
+ version: ""
127
+ # Per-lane authoring state. Each lane resolves its own mode; a lane that is not
128
+ # declared never resolves one and its steps never run.
129
+ be_mode: "skip" # skip | bootstrap_gf | bootstrap_bf | update
130
+ fe_mode: "skip" # skip | bootstrap_gf | bootstrap_bf | update
131
+ be_brownfield: "false" # plan-scaffold.override_active — onboard installed the code-derived template
132
+ fe_brownfield: "false"
133
+ be_created: "false" # plan-scaffold.created — true ⇒ the document was ABSENT ⇒ bootstrap
134
+ fe_created: "false"
135
+ be_migrated: "false" # a legacy design.md was renamed into backend-plan.md this run
136
+ fe_migrated: "false"
137
+ backend_plan: ""
138
+ frontend_plan: ""
139
+ # PER-LANE BRANCH FLAGS. A lane is either NEW (declared, and its master was just
140
+ # scaffolded ⇒ bootstrap it) or EXISTING (declared, and its master was already there ⇒
141
+ # its change goes through the delta module). The engine has no compound conditions, so
142
+ # each AND is computed as one ordered assignment.
143
+ #
144
+ # BOTH CAN BE TRUE IN ONE RUN, and that case is real: declaring a frontend lane on a
145
+ # feature that already has a backend plan bootstraps the frontend master (from the whole
146
+ # spec — nothing is missing from it) while the backend half of the change is a delta.
147
+ be_new: "false"
148
+ be_existing: "false"
149
+ fe_new: "false"
150
+ fe_existing: "false"
151
+ be_update: "false" # this lane has a master AND the re-derivation was confirmed
152
+ fe_update: "false"
153
+ # WHAT THIS RUN IS POINTED AT, and how it was answered. plan_target is the menu's
154
+ # answer; master_exists is what makes the menu appear at all.
155
+ master_exists: "false" # a DECLARED lane's master plan was already on disk
156
+ plan_target: "main" # main | delta — the answer to the one question this phase asks
157
+ plan_choice: "" # the raw selection (a label): the MAIN row, a change, or the NEW row
158
+ main_label: "" # cr-list --include-main's exact label, to test the selection against
159
+ new_label: "" # cr-list --include-new's
160
+ open_new: "false" # the NEW row was chosen ⇒ open a plan-only change before planning it
161
+ has_changes: "false"
162
+ rederive_needed: "false" # main was chosen on a feature that already has masters
163
+ rederive: "false" # …and the rewrite was confirmed
164
+ plan_mode: "bootstrap" # bootstrap | rederive | delta — reported, not branched on
165
+ domain_model: ""
166
+ domain_model_exists: "false"
167
+ source_files: [] # the feature's real source files, from its OKF concept
168
+ source_count: "0"
133
169
 
134
170
  workflow:
135
171
 
136
- # 0. TECHNICAL rule files → sticky standards (configuration, not hardcoded).
137
- # tech-select reads tech-rule-bindings.md under "## /aw-spec-plan". No blocking
138
- # gate: an unconfigured project soft-continues (constitution.technical.md still
139
- # carries the tech base).
140
- - id: tech_select
141
- action: include
142
- module: .appweaver/orchestrator/modules/tech-select.orch.yaml
143
- with: { command: aw-spec-plan }
144
-
145
- # 1. The BACKEND LANE MUST EXIST. This phase writes a service design document, so
146
- # it asks the declaration whether there is a service lane at all — not the
147
- # lane-gate module, whose on-disk check would refuse a greenfield project that
148
- # has declared `src/` but not scaffolded it yet. A plan is authored before the
149
- # code exists; that is the point of planning. Two distinct refusals (the
150
- # evaluator takes single comparisons only, so the message is picked by an
151
- # ordered set): nothing declared at all, vs a frontend-only project.
172
+ # 0. WHICH LANES EXIST. lane-resolve reads the declaration only — it does NOT check the
173
+ # source path on disk, unlike the lane-gate module, because a plan is authored BEFORE
174
+ # the code exists. That is the point of planning.
152
175
  - id: lane_resolve
153
176
  action: execute_script
154
177
  on_error: continue # never crash raw on an undeclared lane — the gate below explains it
155
178
  script: { ref: actions }
156
179
  subcommand: lane-resolve
157
180
  outputs:
158
- - { name: lanes_declared, from: "$.declared" }
159
- - { name: be_declared, from: "$.backend_declared" }
160
- - { name: fe_declared, from: "$.frontend_declared" }
161
- - id: lane_msg_frontend_only
162
- condition: "{{fe_declared}} == true"
163
- action: set
164
- vars: { be_gate_msg: "Frontend-only project: a frontend lane is declared in .appweaver/stacks.json, a backend lane is not. There is no service to plan here, and authoring backend-plan.md anyway would invent one. Run /appweaver plan-ui for frontend-plan.md; if a service lane does exist, declare it with /appweaver select-stack and re-run plan." }
165
- - id: require_backend_lane
181
+ - { name: lanes_declared, from: "$.declared" }
182
+ - { name: be_declared, from: "$.backend_declared" }
183
+ - { name: be_lane_id, from: "$.backend_lane_id" }
184
+ - { name: be_language, from: "$.backend_language" }
185
+ - { name: be_root, from: "$.backend_source_path" }
186
+ - { name: fe_declared, from: "$.frontend_declared" }
187
+ - { name: fe_lane_id, from: "$.frontend_lane_id" }
188
+ - { name: fe_language, from: "$.frontend_language" }
189
+ - { name: fe_root, from: "$.frontend_source_path" }
190
+
191
+ - id: require_lane
166
192
  action: verify_context
167
- require_true: be_declared
193
+ require_true: lanes_declared
168
194
  on_false: stop
169
- on_false_message: "{{be_gate_msg}}"
170
- - id: fe_handoff
195
+ on_false_message: "No lane is declared in .appweaver/stacks.json, so there is nothing to plan. Run /appweaver select-stack and declare the backend stack, the frontend stack, or both — plus each one's source path — then re-run plan. The declaration is what tells this phase which documents to write; a plan written against no declared stack invents its own."
196
+
197
+ # 1. TECHNICAL rule files → sticky standards (configuration, not hardcoded).
198
+ # The service lane's set is loaded under `## /aw-spec-plan`; the frontend lane's under
199
+ # `## /aw-spec-plan-ui`, and ONLY when that lane is declared — so a backend-only
200
+ # project's standards are exactly what they were before the two commands merged.
201
+ # No blocking gate: an unconfigured project soft-continues (the constitutions still
202
+ # carry each lane's ratified base).
203
+ - id: tech_select
204
+ condition: "{{be_declared}} == true"
205
+ action: include
206
+ module: .appweaver/orchestrator/modules/tech-select.orch.yaml
207
+ with: { command: aw-spec-plan }
208
+
209
+ - id: tech_select_ui
171
210
  condition: "{{fe_declared}} == true"
172
- action: set
173
- vars: { fe_next: "A frontend lane is declared — run /appweaver plan-ui to author/refresh frontend-plan.md against this same spec version." }
211
+ action: include
212
+ module: .appweaver/orchestrator/modules/tech-select.orch.yaml
213
+ with: { command: aw-spec-plan-ui }
174
214
 
175
215
  # 2. Resolve the current feature and require its spec.
176
216
  - id: feature_paths
@@ -179,10 +219,25 @@ workflow:
179
219
  script: { ref: actions }
180
220
  subcommand: feature-paths
181
221
  outputs:
222
+ - { name: feature, from: "$.feature" }
182
223
  - { name: feature_dir, from: "$.feature_dir" }
183
224
  - { name: spec_file, from: "$.spec_file" }
184
225
  - { name: spec_exists, from: "$.spec_exists" }
185
226
 
227
+ # The BOUNDED CONTEXT's domain model — where the aggregate root, its entities and its
228
+ # invariants (BR-n) live. The spec cites them by id; this phase records where each is
229
+ # ENFORCED, so the model has to be in context or the instruction to "follow it exactly"
230
+ # has nothing to follow.
231
+ - id: context_paths
232
+ action: execute_script
233
+ on_error: continue
234
+ script: { ref: actions }
235
+ subcommand: context-paths
236
+ outputs:
237
+ - { name: context, from: "$.context" }
238
+ - { name: domain_model, from: "$.domain_model" }
239
+ - { name: domain_model_exists, from: "$.domain_model_exists" }
240
+
186
241
  - id: require_feature
187
242
  action: verify_context
188
243
  require_non_empty: feature_dir
@@ -195,109 +250,246 @@ workflow:
195
250
  on_false: stop
196
251
  on_false_message: "No spec.md for the current feature. Run /appweaver specify first."
197
252
 
198
- # 3. The spec's own ledger tripwire. A plan derived from an unratified contract is
199
- # conformance theatre: an edit that skipped the Change History has no version, so
200
- # the derivation stamp this run writes would claim a baseline the ledger cannot
201
- # name. spec-check's only ok:false is exactly that case.
202
- - id: spec_guard
203
- action: include
204
- module: .appweaver/orchestrator/modules/spec-state-gate.orch.yaml
205
- with: { command: "/appweaver plan" }
253
+ # 3. Read the open change state (set by specify). The delta path uses it to decide
254
+ # whether there is anything to plan at all.
255
+ - id: read_change
256
+ action: execute_script
257
+ script: { ref: actions }
258
+ subcommand: version-read
259
+ args: ["--feature-dir", "{{feature_dir}}"]
260
+ outputs:
261
+ - { name: active_change, from: "$.active_change" }
262
+ - { name: has_change, from: "$.has_change" }
263
+ - { name: version, from: "$.version" }
206
264
 
207
- # 4. Scaffold backend-plan.md (idempotent). --lane backend resolves the pair
208
- # design-template → backend-plan.md, and MIGRATES a legacy design.md by renaming
209
- # it (created=false, migrated=true) — one document, never two side by side.
210
- - id: plan_scaffold
265
+ # 4. THE FEATURE'S REAL SOURCE FILES, from its OKF concept. A plan over an existing
266
+ # codebase has to conform to the shape that is actually there; the paths arrive as a
267
+ # LIST and the authoring steps declare `tools: Read`, because a legacy tree does not
268
+ # fit in a payload. Read-only, exits 0 with paths=[] when there is no concept yet —
269
+ # source_count 0 IS the greenfield case, and the phase never blocks on it.
270
+ - id: concept_paths
271
+ action: execute_script
272
+ on_error: continue
273
+ script: { ref: xref }
274
+ subcommand: concept-paths
275
+ args: ["--feature-dir", "{{feature_dir}}"]
276
+ outputs:
277
+ - { name: source_files, from: "$.paths" }
278
+ - { name: source_count, from: "$.count" }
279
+
280
+ # 5. Scaffold each DECLARED lane's plan (idempotent — returns the existing path when it
281
+ # already exists, and RENAMES a legacy design.md / design.ui.md into the plan name).
282
+ - id: be_scaffold
283
+ condition: "{{be_declared}} == true"
211
284
  action: execute_script
212
285
  script: { ref: actions }
213
- subcommand: design-scaffold
286
+ subcommand: plan-scaffold
214
287
  args: ["--feature-dir", "{{feature_dir}}", "--lane", "backend"]
215
288
  outputs:
216
- - { name: plan_doc, from: "$.design_doc" }
217
- - { name: brownfield_plan, from: "$.override_active" } # true ⇒ onboard installed the code-derived template
218
- - { name: plan_created, from: "$.created" } # true ⇒ the doc was ABSENT (just scaffolded) ⇒ bootstrap
219
- - { name: plan_migrated, from: "$.migrated" } # true ⇒ a legacy design.md became backend-plan.md this run
220
-
221
- # 4a. Resolve the MODE (ordered override — the evaluator has no compound
222
- # conditions): greenfield default; the code-derived override flips it; an
223
- # EXISTING document flips either into an update.
224
- - id: mode_brownfield
225
- condition: "{{brownfield_plan}} == true"
289
+ - { name: backend_plan, from: "$.plan_doc" }
290
+ - { name: be_brownfield, from: "$.override_active" } # true ⇒ onboard installed the code-derived template
291
+ - { name: be_created, from: "$.created" } # true ⇒ the document was ABSENT ⇒ bootstrap
292
+ - { name: be_migrated, from: "$.migrated" } # true ⇒ a legacy design.md was renamed into place
293
+
294
+ - id: fe_scaffold
295
+ condition: "{{fe_declared}} == true"
296
+ action: execute_script
297
+ script: { ref: actions }
298
+ subcommand: plan-scaffold
299
+ args: ["--feature-dir", "{{feature_dir}}", "--lane", "frontend"]
300
+ outputs:
301
+ - { name: frontend_plan, from: "$.plan_doc" }
302
+ - { name: fe_brownfield, from: "$.override_active" }
303
+ - { name: fe_created, from: "$.created" }
304
+ - { name: fe_migrated, from: "$.migrated" }
305
+
306
+ # 5a. Self-detect NEW-vs-EXISTING per lane, from each plan document's OWN existence
307
+ # (symmetric with specify's spec.md trigger). A scaffold reports created=true only
308
+ # when it had to CREATE the document, so created=false on a DECLARED lane means the
309
+ # plan pre-existed and that lane's change is a delta.
310
+ # A MIGRATED document counts as pre-existing: it is the same plan under its new
311
+ # name, and its content was authored before this run.
312
+ # Taking the lane's own `declared` value under the created/not-created condition is
313
+ # how the AND is expressed — an undeclared lane's flags stay false either way.
314
+ - id: be_flag_new
315
+ condition: "{{be_created}} == true"
316
+ action: set
317
+ vars: { be_new: "{{be_declared}}" }
318
+ - id: be_flag_existing
319
+ condition: "{{be_created}} == false"
226
320
  action: set
227
- vars: { plan_mode: "bootstrap_bf" }
228
- - id: mode_update
229
- condition: "{{plan_created}} == false"
321
+ vars: { be_existing: "{{be_declared}}" }
322
+ - id: fe_flag_new
323
+ condition: "{{fe_created}} == true"
230
324
  action: set
231
- vars: { plan_mode: "update" }
325
+ vars: { fe_new: "{{fe_declared}}" }
326
+ - id: fe_flag_existing
327
+ condition: "{{fe_created}} == false"
328
+ action: set
329
+ vars: { fe_existing: "{{fe_declared}}" }
330
+
331
+ # Either declared lane having a pre-existing master means this feature HAS BEEN PLANNED
332
+ # ONCE. That is the precondition for both of the things the menu below offers — a delta
333
+ # has to have a master to be a delta OF, and a re-derivation has to have a document to
334
+ # re-derive. The two raises are independent so neither can cancel the other.
335
+ - id: master_exists_backend
336
+ condition: "{{be_existing}} == true"
337
+ action: set
338
+ vars: { master_exists: "true" }
339
+ - id: master_exists_frontend
340
+ condition: "{{fe_existing}} == true"
341
+ action: set
342
+ vars: { master_exists: "true" }
232
343
 
233
- # 5. Tracking. plan-check diffs the doc's derivation record against the spec's
234
- # ledger: WHAT moved since this plan was derived (the update's exact work list),
235
- # and whether the doc itself was hand-edited since its stamp.
236
- - id: plan_track
344
+ # ═════════════ 5b. WHAT IS BEING PLANNED — ONE QUESTION, ASKED ONCE ═════════
345
+ # The same opening `implement` has: list what is open, ask which one, activate it. The
346
+ # difference is that this phase can also be pointed at the feature's OWN spec, so the
347
+ # menu carries two synthetic options alongside the open changes (cr-list --include-main
348
+ # / --include-new):
349
+ # main · re-plan the master from spec.md + domain-model.md
350
+ # CR-… · author THAT change's delta plans (one row per open change, by wave)
351
+ # new · open a plan-only change — a refactor or architectural decision with no spec
352
+ # delta. This is the path that used to be taken AUTOMATICALLY whenever a master
353
+ # existed and no change was open; it is now something you ask for, because
354
+ # "re-plan the master" and "open a refactor change" were never the same intent.
355
+ #
356
+ # ONLY WHEN THERE IS A MASTER. With no master yet there is nothing to choose: a delta
357
+ # needs one to be a delta of, and so does a plan-only change. So a first run authors the
358
+ # masters with no prompt at all, exactly as before — and if a change is already open on
359
+ # such a feature, it waits for the next run, when the menu will offer it.
360
+ - id: cr_list
361
+ condition: "{{master_exists}} == true"
237
362
  action: execute_script
238
363
  script: { ref: actions }
239
- subcommand: plan-check
240
- args: ["--feature-dir", "{{feature_dir}}", "--doc", "{{plan_doc}}"]
364
+ subcommand: cr-list
365
+ args: ["--feature-dir", "{{feature_dir}}", "--preselect", "{{USER_INPUT}}", "--include-main", "--include-new"]
366
+ outputs:
367
+ - { name: change_options, from: "$.select_options" }
368
+ - { name: matched_label, from: "$.matched_label" }
369
+ - { name: main_label, from: "$.main_label" }
370
+ - { name: new_label, from: "$.new_label" }
371
+ - { name: has_changes, from: "$.has_changes" }
372
+ - { name: change_count, from: "$.count" }
373
+
374
+ # The argument doubles as the answer: `plan main` and `plan CR-…` both resolve to a
375
+ # label in matched_label and skip the prompt. Only the WHOLE argument counts as one of
376
+ # the synthetic words, so ordinary guidance prose ("new caching layer") never selects.
377
+ - id: pick_target
378
+ condition: "{{master_exists}} == true"
379
+ action: user_select
380
+ prompt: "What is being planned? `main` re-plans the feature's master from its spec and domain model. A CR row authors that change's delta plans only, leaving the masters untouched — deltas sharing a wave are independent, and a higher wave is planned on top of the lower ones. `new` opens a plan-only change, for a refactor or architectural decision with no spec delta."
381
+ options_from_variable: change_options
382
+ preselect_from: matched_label
383
+ single_option_confirm: true
241
384
  outputs:
242
- - { name: track_state, from: "$.state" }
243
- - { name: derived_version, from: "$.derived_version" }
244
- - { name: current_version, from: "$.current_version" }
245
- - { name: changed_csv, from: "$.changed_csv" }
246
- - { name: changed_summary, from: "$.changed_summary" }
247
-
248
- # 5a. Hand-edit fence (LOUD, and unblockable by design): a hash mismatch means the
249
- # document moved outside this workflow. Refuse — unless accept_manual:true asks
250
- # the update to fold the edits in, which keeps their intent, reconciles them
251
- # with the spec, and re-baselines at the stamp.
252
- - id: manual_flag_edited
253
- condition: "{{track_state}} == edited"
385
+ - { name: plan_choice, from: selection }
386
+
387
+ # 5c. The answer, RAISED then KNOCKED DOWN. Anything that is not the MAIN row is a
388
+ # change — either an existing one or the NEW row, which is the same path plus a
389
+ # cr-setup.
390
+ #
391
+ # THE KNOCKDOWN IS NOT DECORATIVE. When the menu never ran, plan_choice and both
392
+ # labels are empty, and the engine's conditions compare RENDERED STRINGS: "" == ""
393
+ # is TRUE, so the NEW test fires on a feature that has no menu, no changes and no
394
+ # master. Without the knockdown a first `plan` run opens a stray plan-only change
395
+ # before authoring its first master. master_exists is exactly "the menu ran", so it
396
+ # is what restores the first-run defaults.
397
+ - id: target_delta
398
+ condition: "{{plan_choice}} != {{main_label}}"
254
399
  action: set
255
- vars: { manual_ok: "false" }
256
- - id: manual_flag_edited_behind
257
- condition: "{{track_state}} == edited_behind"
400
+ vars: { plan_target: "delta", plan_mode: "delta" }
401
+ - id: target_new
402
+ condition: "{{plan_choice}} == {{new_label}}"
258
403
  action: set
259
- vars: { manual_ok: "false" }
260
- - id: manual_accept
261
- condition: "{{ACCEPT_MANUAL}} == true"
404
+ vars: { open_new: "true" }
405
+ - id: target_no_menu
406
+ condition: "{{master_exists}} == false"
262
407
  action: set
263
- vars: { manual_ok: "true" }
264
- - id: manual_fence
265
- action: verify_context
266
- require_true: manual_ok
267
- on_false: stop
268
- on_false_message: "backend-plan.md was hand-edited since its last stamp (tracking state: {{track_state}}). Re-run plan with accept_manual:true to fold the edits in (kept, reconciled with the spec, recorded in the ledgers, re-baselined) — or restore the document to its stamped state."
408
+ vars: { plan_target: "main", plan_mode: "bootstrap", open_new: "false" }
269
409
 
270
- # 5b. Idempotence: an update with nothing to update stops here, friendly.
271
- - id: clean_flag
272
- condition: "{{track_state}} == clean"
410
+ # ═════════ 5d. RE-DERIVING A MASTER IS CONFIRMED, NOT ASSUMED ══════════════
411
+ # `main` on a feature that has never been planned is a bootstrap: there is no document
412
+ # to lose, and nothing is asked. `main` on a feature that HAS one is a REWRITE of a
413
+ # long-lived, hand-edited document, so it is confirmed first and the run stops on a
414
+ # decline. The options are ordered no-first deliberately, against this kit's usual
415
+ # yes-first convention: an unattended run takes the first option, and the safe default
416
+ # for "rewrite the plan this team has been maintaining" is to decline and say so.
417
+ - id: rederive_needed_set
418
+ condition: "{{plan_target}} == main"
273
419
  action: set
274
- vars: { plan_stale: "false" }
275
- - id: clean_stop
420
+ vars: { rederive_needed: "{{master_exists}}" }
421
+
422
+ - id: confirm_rederive
423
+ condition: "{{rederive_needed}} == true"
424
+ action: ask
425
+ prompt: "This feature already has master plan(s). Re-derive them from the current spec.md and domain-model.md? Recorded architectural decisions (DA-n) and workarounds (WA-n) are carried forward, but any section the spec and model no longer support IS REWRITTEN — including hand edits. Answer no to stop without touching them."
426
+ options: ["no", "yes"]
427
+ answer_var: rederive_answer
428
+
429
+ - id: rederive_go
430
+ condition: "{{rederive_answer}} == yes"
431
+ action: set
432
+ vars: { rederive: "true", plan_mode: "rederive" }
433
+
434
+ - id: require_rederive_confirm
435
+ condition: "{{rederive_needed}} == true"
276
436
  action: verify_context
277
- require_true: plan_stale
437
+ require_true: rederive
278
438
  on_false: stop
279
- on_false_message: "backend-plan.md is in step with spec {{current_version}} — nothing to update. If the PLAN is wrong and the spec is right, hand-edit backend-plan.md and re-run /appweaver plan with accept_manual: true — that is what tells this command to keep the edit's intent and reconcile it rather than treat it as drift; without the flag the manual fence stops the run after the edit is already on disk."
280
-
281
- # 5b. THE CODE. The plan is an architectural confirmation, so it must know what the
282
- # feature already looks like on disk: concept-paths resolves the feature's OKF
283
- # concept to the source files realising it, and the authoring steps declare
284
- # `tools: Read` so they can open the ones that matter. A PATH LIST, not inlined
285
- # content — a real codebase does not fit a payload (E60). on_error: continue,
286
- # because a greenfield feature has no concept doc and must not be blocked by it:
287
- # source_count 0 is the greenfield signal the tasks read.
288
- - id: concept_paths
289
- action: execute_script
290
- on_error: continue
291
- script: { ref: xref }
292
- subcommand: concept-paths
293
- args: ["--feature-dir", "{{feature_dir}}"]
294
- outputs:
295
- - { name: source_csv, from: "$.csv" }
296
- - { name: source_count, from: "$.count" }
297
- - { name: okf_concept, from: "$.concept" }
439
+ on_false_message: "Stopped — the master plans were left untouched. Re-run and answer yes to re-derive them, or pick a change instead (/appweaver plan CR-…) to author that change's delta plans without touching a master."
298
440
 
299
- # 6. Pre-execution hooks. (Phase keys keep their historical names — they are the
300
- # hooks CONFIG contract, orthogonal to the command's name.)
441
+ # 5e. Resolve each lane's authoring MODE. Four ordered assignments per lane: the
442
+ # greenfield default for a new lane, the code-derived override replacing it, a
443
+ # `skip` for any lane that is not new — which silences both bootstrap branches on
444
+ # the delta path without needing a compound condition — and finally `update` for a
445
+ # lane whose master exists on a CONFIRMED re-derivation. The raise-then-test pair is
446
+ # how "this lane has a master AND the re-derivation was confirmed" is expressed.
447
+ #
448
+ # A re-derivation has no greenfield/brownfield split: both bootstrap branches differ
449
+ # only in WHICH TEMPLATE supplies the section structure, and in update mode the
450
+ # structure is the existing document's own.
451
+ - id: be_mode_gf
452
+ condition: "{{be_new}} == true"
453
+ action: set
454
+ vars: { be_mode: "bootstrap_gf" }
455
+ - id: be_mode_bf
456
+ condition: "{{be_brownfield}} == true"
457
+ action: set
458
+ vars: { be_mode: "bootstrap_bf" }
459
+ - id: be_mode_skip
460
+ condition: "{{be_new}} == false"
461
+ action: set
462
+ vars: { be_mode: "skip" }
463
+ - id: be_update_raise
464
+ condition: "{{be_existing}} == true"
465
+ action: set
466
+ vars: { be_update: "{{rederive}}" }
467
+ - id: be_mode_update
468
+ condition: "{{be_update}} == true"
469
+ action: set
470
+ vars: { be_mode: "update" }
471
+ - id: fe_mode_gf
472
+ condition: "{{fe_new}} == true"
473
+ action: set
474
+ vars: { fe_mode: "bootstrap_gf" }
475
+ - id: fe_mode_bf
476
+ condition: "{{fe_brownfield}} == true"
477
+ action: set
478
+ vars: { fe_mode: "bootstrap_bf" }
479
+ - id: fe_mode_skip
480
+ condition: "{{fe_new}} == false"
481
+ action: set
482
+ vars: { fe_mode: "skip" }
483
+ - id: fe_update_raise
484
+ condition: "{{fe_existing}} == true"
485
+ action: set
486
+ vars: { fe_update: "{{rederive}}" }
487
+ - id: fe_mode_update
488
+ condition: "{{fe_update}} == true"
489
+ action: set
490
+ vars: { fe_mode: "update" }
491
+
492
+ # 6. Pre-execution hooks.
301
493
  - id: resolve_pre_hooks
302
494
  action: execute_script
303
495
  script: { ref: actions }
@@ -309,70 +501,61 @@ workflow:
309
501
  action: dispatch_hooks
310
502
  hooks_from_variable: before_hooks
311
503
 
312
- # ══════════════════ BOOTSTRAP (GREENFIELD) — author the master ══════════════
313
- # Author the whole HLD in one pass: all four layers as sections, tech-agnostic,
314
- # traced to the spec. build_context REPLACES the per-request envelope; sticky
315
- # standards stay prepended. The ENGINE writes plan_doc; plan-stamp then baselines it.
316
- - id: ctx_plan
317
- condition: "{{plan_mode}} == bootstrap_gf"
504
+ # ═══════════ BACKEND LANE — bootstrap, greenfield four-layer template ═══════
505
+ # Author the whole HLD in one pass: all four layers as sections, tech-agnostic, traced
506
+ # to the spec. build_context REPLACES the per-request envelope; sticky standards stay
507
+ # prepended. The ENGINE writes backend_plan.
508
+ - id: ctx_backend
509
+ condition: "{{be_mode}} == bootstrap_gf"
318
510
  action: build_context
319
511
  context:
320
- - { path: "{{plan_doc}}" } # the copied template = section structure
321
- - { path: "{{spec_file}}" } # the source of truth (FR/BR/CN/DC/IC/SC)
512
+ - { path: "{{backend_plan}}" } # the copied template = section structure
513
+ - { path: "{{spec_file}}" } # BEHAVIOUR: FR/CN/DC/IC/US/SC + cited BR ids
514
+ - { path: "{{domain_model}}", optional: true } # the CONTEXT's canonical model — entities, invariants, boundary
322
515
  - { path: .appweaver/DESIGN-FORMAT.md } # the HLD contract
323
- - { path: "{{feature_dir}}/interfaces.md", optional: true } # the published surface — cite these IF- ids in §3; absent on a first run
324
- - { path: "{{okf_concept}}", optional: true } # the feature's OKF concept: use-case log + source map
325
- - { path: .appweaver/memory/constitution.technical.md, optional: true } # technology base ONLY — no domain (domain enters via the spec)
326
- - id: gen_plan
327
- condition: "{{plan_mode}} == bootstrap_gf"
516
+ - { path: .appweaver/memory/constitution.technical.md, optional: true } # technology base ONLY — no domain (domain enters via the spec + model)
517
+ - id: gen_backend
518
+ condition: "{{be_mode}} == bootstrap_gf"
328
519
  action: agent_author
329
- tools: Read # the current code arrives as {{source_csv}} paths, not inlined
330
- output_target: { type: file, path: "{{plan_doc}}" }
520
+ tools: Read # the feature's EXISTING source arrives as {{source_files}} paths, not inlined
521
+ output_target: { type: file, path: "{{backend_plan}}" }
331
522
  task: |
332
- Author the MASTER service plan (the target backend-plan.md in context) for this
333
- aggregate by filling out the template, based on the feature spec + the loaded
334
- technical/architecture standards. This is the MEDIATOR between the spec and the
335
- generated code. Guidance: {{USER_INPUT}}
523
+ Author the MASTER service-lane plan (the target backend-plan.md in context) for this
524
+ aggregate by filling out the template, based on the feature spec + the context's
525
+ domain model + the loaded technical/architecture standards. This is the MEDIATOR
526
+ between the spec and the generated code. Guidance: {{USER_INPUT}}
336
527
  Rules:
337
- - THE SPEC IS THE ONLY DOMAIN INPUT. You have the spec + technical standards, and
338
- NO domain rule files. The domain arrives as spec ids only — realise them, and
339
- invent no domain rule the spec omits (an unavoidable new concept → §7 DA-n).
340
- - THE CODE IS THE WHAT-EXISTS AUTHORITY, and it is a DIFFERENT authority from the
341
- spec. {{source_count}} source file(s) realise this feature today; read the
342
- ones that bear on the structure you are deciding, and ADJUST this plan to the
343
- shape that is actually there rather than to an idealised one. An empty list is
344
- greenfield — design from a clean slate and say so. The code NEVER overrides the
345
- spec on behaviour and may not introduce a domain rule the spec omits: code that
346
- contradicts the spec is reported (§7 DA-n) and designed to the SPEC, never
347
- quietly blessed. A deliberate divergence from the as-built shape is a §8 WA-n
348
- with its revisit trigger.
349
- {{source_csv}}
350
- - THE PUBLISHED SURFACE IS CITED, NOT RESTATED. Every endpoint, published event
351
- and externally consumed contract belongs to `interfaces.md`, the feature's
352
- append-only interface log — the anchor /appweaver plan-ui and the FE story
353
- designs read. §3 here is the INDEX: cite the IF- id of an interface the log
354
- already carries (it is in context on an update run), and for one being
355
- published for the FIRST time write a one-line description with `(this run)` in
356
- the id cell. NEVER invent an IF- number — the script is the only id authority,
357
- and a later step publishes what you describe. Changing a published interface
358
- means a SUPERSEDING entry, never an edit to an existing one.
528
+ - THE SPEC AND THE DOMAIN MODEL ARE THE ONLY DOMAIN INPUTS. You have them plus the
529
+ technical standards, and NO domain rule files. Realise what they state, and invent
530
+ no domain rule they omit (an unavoidable new concept → §7 DA-n).
531
+ - THE DOMAIN LAYER (§1) MUST FOLLOW THE CONTEXT'S DOMAIN MODEL EXACTLY: entities &
532
+ value objects are its `## Aggregate:` section's entities (SAME names), invariants
533
+ are its BR-nnn (cite each, never restate), the aggregate boundary is its Boundary
534
+ block, and states are the lifecycle it describes. The model — not the spec — is
535
+ where those facts live; the spec cites them by id. This document records WHERE
536
+ each invariant is ENFORCED, never what it says. Elaborate with domain events /
537
+ state machines / behaviour signatures ONLY to realise spec behaviour; do NOT
538
+ invent or contradict. Any unavoidable new concept goes in §7 as a DA-n.
539
+ - THE CODE THAT EXISTS IS THE WHAT-IS AUTHORITY. {{source_count}} source file(s)
540
+ already realise this feature: {{source_files}}. READ them before planning and
541
+ CONFORM to the shape that is there — its module boundaries, its naming, its
542
+ seams. The spec stays the only authority on BEHAVIOUR; the code is the authority
543
+ on what the structure currently IS. With no source files this is a greenfield
544
+ feature and there is nothing to conform to.
359
545
  - Cover the four layers as sections: Domain (§1), Application (§2), API (§3),
360
546
  Infrastructure (§4). Keep the template's section order and headings.
361
- - The DOMAIN layer (§1) MUST follow spec.md EXACTLY: entities & value objects
362
- are the spec's Key Entities (SAME names), invariants are the spec's Business
363
- Rules (BR-nnn — cite each), the aggregate boundary is the spec's Aggregate
364
- Boundary, and states are the lifecycle the spec describes. Elaborate with
365
- domain events / state machines / behaviour signatures ONLY to realise spec
366
- behaviour; do NOT invent or contradict. Any unavoidable new concept goes in
367
- §7 as a DA-n assumption.
368
547
  - REALISE THE LIFTED CONSTRAINTS (each cites its spec id):
369
548
  • Constraints & NFRs (CN-) → structural ones (immutability, append-only audit,
370
- no-hard-delete) as §1 invariants/events; retention/performance/audit as §5
371
- Cross-Cutting rows or §4 persistence.
549
+ no-hard-delete) as §1 invariant enforcement/events; retention/performance/audit
550
+ as §5 Cross-Cutting rows or §4 persistence.
372
551
  • Data Classification (DC-) → §5 data-protection + the §2/§3 projection surface
373
552
  (what MUST NOT be exposed in API/logs/traces).
374
553
  • Integration Contracts (IC-) → §4.2 External integrations (one port/adapter row
375
554
  per IC-), realising the obligation the spec named.
555
+ - PUBLISH THE SURFACE THE FRONTEND LANE BINDS TO. §3 states each endpoint's contract
556
+ as field shapes. That section is the ONLY part of this document the frontend plan
557
+ may read; keep it complete and self-contained, because a contract the UI needs and
558
+ this document never states becomes a client-side workaround.
376
559
  - CONSTITUTION-MANDATED CROSS-CUTTING (from the loaded technical constitution, NOT
377
560
  the spec): a NON-NEGOTIABLE technical principle you must apply that has NO
378
561
  per-feature spec id. APPLY it (it is non-negotiable), and because it is not
@@ -385,73 +568,52 @@ workflow:
385
568
  conceptual types. The stack is bound only at `implement` time. Do NOT include
386
569
  code-generation mechanics or the layering/dependency rule — those are
387
570
  solution-wide rules applied in `implement` / the constitution, not per feature.
388
- - Every plan element traces UP to a spec id (FR-/BR-/CN-/DC-/IC-/SC-); the whole
389
- spec traces DOWN — fill §6 Traceability completely, with a row for EVERY
390
- CN-/DC-/IC- so no lifted constraint is silently dropped. NO STORY IDS ANYWHERE:
391
- this plan traces to requirements, which is what keeps it stable while the
392
- backlog churns.
571
+ - Every plan element traces UP to a spec id (FR-/BR-/CN-/DC-/IC-/US-/SC-); the
572
+ whole spec traces DOWN — fill §6 Traceability completely, with a row for EVERY
573
+ CN-/DC-/IC- so no lifted constraint is silently dropped.
393
574
  - Fill the two ledgers: §7 Assumptions (DA-n, to clarify later) and §8
394
575
  Workarounds (WA-n, to clear later). Empty → "None (as of v1.0)".
395
576
  - Physical persistence schema is OUT OF SCOPE (defer to the technical-data-model).
396
- - LEAVE the template's initial Change History row as it stands and write NO
397
- version anywhere — plan-stamp owns the **Version** header and every row.
577
+ - This is v1.0: set the initial Change History row + keep US-1's mapping.
398
578
  - Omit a LAYER section only if the aggregate genuinely has nothing there
399
579
  (state why); never write "N/A". No complete code anywhere.
400
580
 
401
- # ═══════ BOOTSTRAP (BROWNFIELD) — code-derived template, NEW feature, no adopt ═══
402
- # Same contract as the greenfield bootstrap (tech-agnostic HLD, spec is the only
403
- # domain input, full §Traceability + the two ledgers), with ONE difference: the plan
404
- # STRUCTURE follows the code-derived brownfield template + the project's AS-BUILT
405
- # architecture, not the fixed four layers — so a new feature added to an existing
406
- # repo is consistent with the code already there. Runs only when onboard generated
407
- # the code-derived design-template override (plan_mode == bootstrap_bf).
408
- - id: ctx_plan_brownfield
409
- condition: "{{plan_mode}} == bootstrap_bf"
581
+ # ═══ BACKEND LANE — bootstrap, brownfield code-derived template, NEW feature ═══
582
+ # Same contract as the greenfield bootstrap above (tech-agnostic HLD, spec + model are
583
+ # the only domain inputs, full §Traceability + the two ledgers), with ONE difference:
584
+ # the STRUCTURE follows the code-derived template + the project's AS-BUILT architecture,
585
+ # not the fixed four layers — so a new feature added to an existing repo is consistent
586
+ # with the code already there. Runs only when onboard generated the override.
587
+ - id: ctx_backend_brownfield
588
+ condition: "{{be_mode}} == bootstrap_bf"
410
589
  action: build_context
411
590
  context:
412
- - { path: "{{plan_doc}}" } # the scaffolded BROWNFIELD template = code-derived section structure
413
- - { path: "{{spec_file}}" } # the source of truth (FR/BR/CN/DC/IC/SC)
591
+ - { path: "{{backend_plan}}" } # the scaffolded BROWNFIELD template = code-derived section structure
592
+ - { path: "{{spec_file}}" } # BEHAVIOUR: FR/CN/DC/IC/US/SC + cited BR ids
593
+ - { path: "{{domain_model}}", optional: true } # the CONTEXT's canonical model — entities, invariants, boundary
414
594
  - { path: .appweaver/DESIGN-FORMAT.md } # the HLD contract (see its Brownfield variant note)
415
- - { path: "{{feature_dir}}/interfaces.md", optional: true } # the published surface — cite these IF- ids in §3; absent on a first run
416
- - { path: "{{okf_concept}}", optional: true } # the feature's OKF concept: use-case log + source map
417
595
  - { path: knowledge/architecture/overview.md, optional: true } # the project's AS-BUILT architecture (onboard) — align to it
418
596
  - { path: knowledge/architecture/platform.md, optional: true } # the as-built technology base / cross-cutting reality
419
- - { path: .appweaver/memory/constitution.technical.md, optional: true } # technology base ONLY — no domain (domain enters via the spec)
420
- - id: gen_plan_brownfield
421
- condition: "{{plan_mode}} == bootstrap_bf"
597
+ - { path: .appweaver/memory/constitution.technical.md, optional: true } # technology base ONLY — no domain
598
+ - id: gen_backend_brownfield
599
+ condition: "{{be_mode}} == bootstrap_bf"
422
600
  action: agent_author
423
- tools: Read # the current code arrives as {{source_csv}} paths, not inlined
424
- output_target: { type: file, path: "{{plan_doc}}" }
601
+ tools: Read # the feature's EXISTING source arrives as {{source_files}} paths, not inlined
602
+ output_target: { type: file, path: "{{backend_plan}}" }
425
603
  task: |
426
- Author the MASTER service plan (the target backend-plan.md in context — it is the
427
- code-derived BROWNFIELD template) for this NEW feature being added to an EXISTING
428
- codebase, by filling the template from the spec + the loaded technical/architecture
429
- standards. It is the MEDIATOR between the spec and the code `implement` will
430
- generate. Guidance: {{USER_INPUT}}
604
+ Author the MASTER service-lane plan (the target backend-plan.md in context — it is
605
+ the code-derived BROWNFIELD template) for this NEW feature being added to an EXISTING
606
+ codebase, by filling the template from the spec + the context's domain model + the
607
+ loaded technical/architecture standards. It is the MEDIATOR between the spec and the
608
+ code `implement` will generate. Guidance: {{USER_INPUT}}
431
609
  Rules:
432
- - THE SPEC IS THE ONLY DOMAIN INPUT. You have the spec + technical standards + the
433
- project's as-built architecture references, and NO domain rule files. The domain
434
- arrives as spec ids only — realise them, invent no domain rule the spec omits (an
610
+ - THE SPEC AND THE DOMAIN MODEL ARE THE ONLY DOMAIN INPUTS. You have them plus the
611
+ technical standards and the project's as-built architecture references, and NO
612
+ domain rule files. Realise what they state, invent no domain rule they omit (an
435
613
  unavoidable new concept → a §5 DA-n assumption).
436
- - THE CODE IS THE WHAT-EXISTS AUTHORITY, and it is a DIFFERENT authority from the
437
- spec. {{source_count}} source file(s) realise this feature today; read the
438
- ones that bear on the structure you are deciding, and ADJUST this plan to the
439
- shape that is actually there rather than to an idealised one. An empty list is
440
- greenfield — design from a clean slate and say so. The code NEVER overrides the
441
- spec on behaviour and may not introduce a domain rule the spec omits: code that
442
- contradicts the spec is reported (§5 DA-n) and designed to the SPEC, never
443
- quietly blessed. A deliberate divergence from the as-built shape is a §6 WA-n
444
- with its revisit trigger.
445
- {{source_csv}}
446
- - THE PUBLISHED SURFACE IS CITED, NOT RESTATED. Every endpoint, published event
447
- and externally consumed contract belongs to `interfaces.md`, the feature's
448
- append-only interface log — the anchor /appweaver plan-ui and the FE story
449
- designs read. the EXTERNAL-facing §2 subsection here is the INDEX: cite the IF- id of an interface the log
450
- already carries (it is in context on an update run), and for one being
451
- published for the FIRST time write a one-line description with `(this run)` in
452
- the id cell. NEVER invent an IF- number — the script is the only id authority,
453
- and a later step publishes what you describe. Changing a published interface
454
- means a SUPERSEDING entry, never an edit to an existing one.
614
+ - THE CODE THAT EXISTS IS THE WHAT-IS AUTHORITY. {{source_count}} source file(s)
615
+ already realise this feature: {{source_files}}. READ them and CONFORM to the shape
616
+ that is there.
455
617
  - STRUCTURE FOLLOWS THE TEMPLATE + THE PROJECT'S AS-BUILT ARCHITECTURE, NOT the four
456
618
  greenfield layers. Keep the template's own section order and headings (§0 Overview
457
619
  + ubiquitous language; §1 Architecture Context & Domain Model; §2 Component &
@@ -466,17 +628,20 @@ workflow:
466
628
  textbook layer gets its own section; a layer the feature does not need does NOT
467
629
  appear.
468
630
  - KEEP THE NEUTRAL ANCHORS (the template pins them): §1.2 is the domain/data MODEL
469
- and MUST follow spec.md EXACTLY — entities & value objects are the spec's Key
470
- Entities (SAME names), invariants are the spec's Business Rules (BR-nnn, cite
471
- each), the boundary is the spec's Aggregate Boundary, states are its lifecycle;
472
- cross-slice references-by-id live here. One §2 subsection is the EXTERNAL
473
- INTEGRATIONS table (its heading contains "Integration") — one port/adapter row
474
- per IC-, realising the obligation the spec named.
631
+ and MUST follow the CONTEXT'S DOMAIN MODEL EXACTLY — entities & value objects are
632
+ its aggregate section's entities (SAME names), invariants are its BR-nnn (cite
633
+ each and record WHERE each is enforced), the boundary is its Boundary block,
634
+ states are its lifecycle; cross-slice references-by-id live here. One §2
635
+ subsection is the EXTERNAL INTEGRATIONS table (its heading contains "Integration")
636
+ — one port/adapter row per IC-, realising the obligation the spec named.
475
637
  - REALISE THE LIFTED CONSTRAINTS (each cites its spec id): CN- → structural ones
476
- (immutability, append-only audit, no-hard-delete) as §1.2 invariants/events;
477
- retention/performance/audit as §2 persistence or §3 Cross-Cutting rows. DC- → §3
478
- data-protection + the projection surface (what MUST NOT be exposed in
638
+ (immutability, append-only audit, no-hard-delete) as §1.2 invariant enforcement /
639
+ events; retention/performance/audit as §2 persistence or §3 Cross-Cutting rows.
640
+ DC- → §3 data-protection + the projection surface (what MUST NOT be exposed in
479
641
  API/logs/traces).
642
+ - PUBLISH THE SURFACE THE FRONTEND LANE BINDS TO: the section carrying this feature's
643
+ endpoints states each contract as field shapes, completely, because it is the only
644
+ part of this document the frontend plan may read.
480
645
  - CONSTITUTION-MANDATED CROSS-CUTTING (from the loaded technical constitution, NOT
481
646
  the spec): a NON-NEGOTIABLE technical principle with no per-feature spec id —
482
647
  APPLY it, and LOG it as a §5 DA-n whose basis cites the mandating principle
@@ -487,216 +652,369 @@ workflow:
487
652
  bound at `implement` time. Do NOT include code-generation mechanics or the
488
653
  layering/dependency rule (those are solution-wide / constitution rules).
489
654
  - Every plan element traces UP to a spec id; fill §4 Traceability completely (a
490
- row for EVERY FR/BR/CN/DC/IC/SC so no lifted constraint is dropped). NO STORY
491
- IDS ANYWHERE — this plan traces to requirements. Fill the
655
+ row for EVERY FR/BR/CN/DC/IC/US/SC so no lifted constraint is dropped). Fill the
492
656
  §5 Assumptions (DA-n) and §6 Deviations (WA-n) ledgers — "None (as of v1.0)" if
493
657
  empty. §6 records only genuine deviations, not "organised unlike a reference
494
658
  architecture" (that is the as-built shape, §1.1).
495
- - LEAVE the template's initial Change History row as it stands and write NO
496
- version anywhere — plan-stamp owns the **Version** header and every row.
497
- Omit a section only if the feature genuinely has nothing there (state why); no
498
- complete code anywhere.
499
-
500
- # ══════════════════ UPDATE — conform the existing plan to the moved spec ═════
501
- # The work list is COMPUTED, never guessed: the ledger rows above this plan's
502
- # derivation stamp name exactly the ids to conform. Everything else stays
503
- # byte-identical — the analyze gate reads an unrelated "improvement" as drift.
504
- - id: ctx_plan_update
505
- condition: "{{plan_mode}} == update"
659
+ - This is v1.0: set the initial Change History row + keep US-1's mapping. Omit a
660
+ section only if the feature genuinely has nothing there (state why); no complete
661
+ code anywhere.
662
+
663
+ # ═══════════ BACKEND LANE — RE-DERIVE an existing master ════════════════════
664
+ # `main` on a feature that already has a master. This is the operation finalize-delta's
665
+ # contract has always assumed exists: it merges a change's SPEC and MODEL halves and
666
+ # merges no lane plan, on the stated grounds that "a master plan is DERIVED from those
667
+ # two and is re-derived from them". This is where that re-derivation happens.
668
+ #
669
+ # IT REVISES, IT DOES NOT REFILL. The document in context is the CURRENT master, not a
670
+ # blank template, and it is the section structure — which is why this branch has no
671
+ # greenfield/brownfield split. Content that the spec and model still support survives
672
+ # verbatim; content they no longer support is rewritten; content they now require and
673
+ # the document lacks is added. Nothing is preserved on the grounds that it is already
674
+ # written: an element with no spec id behind it is exactly what the analyze-plan gate
675
+ # calls over-reach.
676
+ - id: ctx_backend_update
677
+ condition: "{{be_mode}} == update"
506
678
  action: build_context
507
679
  context:
508
- - { path: "{{plan_doc}}" } # the CURRENT plan — the document being conformed
509
- - { path: "{{spec_file}}" } # the source of truth at {{current_version}}
510
- - { path: .appweaver/DESIGN-FORMAT.md } # the HLD contract
511
- - { path: "{{feature_dir}}/interfaces.md", optional: true } # the published surface — cite these IF- ids in §3; absent on a first run
512
- - { path: "{{okf_concept}}", optional: true } # the feature's OKF concept: use-case log + source map
513
- - { path: knowledge/architecture/overview.md, optional: true } # brownfield repos: the as-built architecture
514
- - { path: knowledge/architecture/platform.md, optional: true }
515
- - { path: .appweaver/memory/constitution.technical.md, optional: true }
516
- - id: gen_plan_update
517
- condition: "{{plan_mode}} == update"
680
+ - { path: "{{backend_plan}}" } # the CURRENT master = what is being revised, and its section structure
681
+ - { path: "{{spec_file}}" } # BEHAVIOUR, carrying every change finalize-delta has merged
682
+ - { path: "{{domain_model}}", optional: true } # the CONTEXT's canonical model — entities, invariants, boundary
683
+ - { path: .appweaver/DESIGN-FORMAT.md } # the HLD contract
684
+ - { path: .appweaver/memory/constitution.technical.md, optional: true } # technology base ONLY
685
+ - id: gen_backend_update
686
+ condition: "{{be_mode}} == update"
518
687
  action: agent_author
519
- tools: Read # the current code arrives as {{source_csv}} paths, not inlined
520
- output_target: { type: file, path: "{{plan_doc}}" }
688
+ tools: Read # the feature's EXISTING source arrives as {{source_files}} paths, not inlined
689
+ output_target: { type: file, path: "{{backend_plan}}" }
521
690
  task: |
522
- UPDATE the master service plan (the target backend-plan.md in context) so it
523
- conforms to the CURRENT spec. Tracking state: {{track_state}}. The spec moved
524
- from {{derived_version}} (this plan's derivation) to {{current_version}}; the
525
- ledger rows above the derivation name what moved:
526
- ids: {{changed_csv}}
527
- changes: {{changed_summary}}
528
- Guidance: {{USER_INPUT}}
691
+ RE-DERIVE the MASTER service-lane plan in context from the current spec + the
692
+ context's domain model. The document you are given is the plan as it stands today;
693
+ produce the plan this feature's spec and model call for NOW. Guidance: {{USER_INPUT}}
529
694
  Rules:
530
- - SCOPE IS THE COMPUTED WORK LIST. Rework ONLY the sections and rows that realise
531
- the ids above — their §1 invariants/entities, the §2/§3 elements serving them,
532
- the §4.2 integration rows, the §5 cross-cutting rows and the §6 traceability
533
- rows. Every other line stays BYTE-IDENTICAL; an unrelated "improvement" is
534
- drift the analyze gate will fail. EXCEPTION — tracking state `untracked`: this
535
- document has no baseline (a migrated legacy design.md), so reconcile the WHOLE
536
- plan to the spec once; the stamp then baselines it.
537
- - accept_manual: {{ACCEPT_MANUAL}}. When true, the document carries hand edits
538
- that were deliberately accepted: KEEP their intent, reconcile them with the
539
- spec, and record each kept deviation honestly — a new concept as a §7 DA-n, a
540
- deliberate shortcut as a §8 WA-n. Never silently revert a hand edit.
541
- - AN ID THE SPEC NO LONGER DEFINES LOSES ITS REALISATION: remove the elements
542
- that existed only to serve it (and their §6 rows). An id whose wording moved
543
- is re-realised from the spec's CURRENT text — the spec is the wording.
544
- - AN ID THE SPEC HAS NEWLY DEFINED GAINS ONE. An id in the work list that NO
545
- existing section realises is an ADDITION, not an error and not out of scope:
546
- give it a home — the §1/§2/§3 elements that realise it, its §4.2 or §5 rows
547
- where it needs them — and a §6 traceability row like any other. The scope rule
548
- above says which sections may be touched; it does not say a new requirement may
549
- not be designed, and reading it that way would let every added requirement
550
- select nothing to rework and quietly land unrealised.
551
- - THE SPEC IS THE ONLY DOMAIN INPUT, same as at bootstrap: realise ids, invent
552
- nothing; an unavoidable new concept is a §7 DA-n.
553
- - THE CODE IS THE WHAT-EXISTS AUTHORITY, and it is a DIFFERENT authority from the
554
- spec. {{source_count}} source file(s) realise this feature today; re-read the
555
- ones the moved ids touch (code state is read FRESH every run, never assumed), and ADJUST this plan to the
556
- shape that is actually there rather than to an idealised one. An empty list is
557
- greenfield — design from a clean slate and say so. The code NEVER overrides the
558
- spec on behaviour and may not introduce a domain rule the spec omits: code that
559
- contradicts the spec is reported (§7 DA-n) and designed to the SPEC, never
560
- quietly blessed. A deliberate divergence from the as-built shape is a §8 WA-n
561
- with its revisit trigger.
562
- {{source_csv}}
563
- - THE PUBLISHED SURFACE IS CITED, NOT RESTATED. `interfaces.md` is in context:
564
- §3 cites the IF- id of every interface the log already carries. A moved id that
565
- CHANGES a published interface is described in §3 with `(this run)` in the id
566
- cell — a later step publishes it as a SUPERSEDING entry, and the next update
567
- cites the new id. Never invent an IF- number and never edit the log by hand.
568
- - PRESERVE THE LEDGERS: §7 Assumptions, §8 Workarounds and §9 Change History
569
- keep every existing entry and their numbering — append, never rewrite. Write
570
- NO version numbers anywhere and ADD NO Change History row: the stamp step
571
- appends the row and moves the **Version** header.
572
- - §6 TRACEABILITY STAYS COMPLETE both ways — every CN-/DC-/IC- still has a row
573
- after the update; no element without a spec reason.
574
- - TECHNOLOGY-AGNOSTIC, no complete code, same contract as at bootstrap.
575
-
576
- # 7. STAMP — the only writer of the plan's version identity, and the ledger-survival
577
- # check: it REFUSES (no baseline written) if the update dropped the Change History
578
- # section, so a lost ledger can never be silently baselined over.
579
- - id: stamp_bootstrap
580
- condition: "{{plan_mode}} != update"
581
- action: execute_script
582
- script: { ref: actions }
583
- subcommand: plan-stamp
584
- args: ["--feature-dir", "{{feature_dir}}", "--doc", "{{plan_doc}}", "--no-row"]
585
- outputs:
586
- - { name: stamp_ok, from: "$.ok" }
587
- - { name: stamp_error, from: "$.error" }
588
- - { name: current_version, from: "$.version" }
589
- - id: stamp_update
590
- condition: "{{plan_mode}} == update"
591
- action: execute_script
592
- script: { ref: actions }
593
- subcommand: plan-stamp
594
- args: ["--feature-dir", "{{feature_dir}}", "--doc", "{{plan_doc}}", "--ids", "{{changed_csv}}", "--note", "Conformed to spec {{current_version}}"]
595
- outputs:
596
- - { name: stamp_ok, from: "$.ok" }
597
- - { name: stamp_error, from: "$.error" }
598
- - id: stamp_fence
599
- action: verify_context
600
- require_true: stamp_ok
601
- on_false: stop
602
- on_false_message: "plan-stamp refused: {{stamp_error}} — backend-plan.md has been written but NOT baselined. Restore the missing ledger section, then re-run plan (or plan-stamp) to record the baseline."
603
-
604
- # ═══════════ PUBLISH THE SURFACE — the interface log (payload-only) ════════
605
- # §3 of the plan describes this feature's published surface; this pair turns what it
606
- # newly describes into APPEND-ONLY log entries. Separate steps so the script stays the
607
- # only id authority: entries arrive as IF-NEW blocks and log-append assigns IF-nnn,
608
- # the date and status: proposed. It runs AFTER the stamp — the log lives in its own
609
- # file, so appending to it cannot invalidate the baseline just recorded.
610
- #
611
- # This is the BE→FE contract anchor: /appweaver plan-ui and the FE story designs read
612
- # the latest entry per id. A changed interface is published as a SUPERSEDING entry,
613
- # never an edit — which is why the log is append-only and the plan cites ids.
614
- - id: ctx_if
695
+ - THIS IS A REVISION, NOT A RE-AUTHORING. Keep the existing document's section
696
+ structure, its wording, and every element the spec and model still support —
697
+ byte-for-byte where nothing requires a change. A re-derivation that rewrites
698
+ untouched sections destroys the review history of a document a team maintains.
699
+ - WHAT THE SPEC AND MODEL SAY NOW WINS. Every §4 Traceability row must resolve to a
700
+ spec id that STILL EXISTS; every §1 invariant to a BR-nnn the model STILL states.
701
+ A row whose id is gone is deleted along with what it justified. A spec id or model
702
+ invariant with NO plan element is a GAP — realise it, in the shape the rest of the
703
+ document already uses.
704
+ - THE DOMAIN LAYER (§1) MUST FOLLOW THE CONTEXT'S DOMAIN MODEL EXACTLY: entities &
705
+ value objects are its `## Aggregate:` section's entities (SAME names), invariants
706
+ are its BR-nnn (cite each, never restate), the aggregate boundary is its Boundary
707
+ block, and states are the lifecycle it describes. Where the model has MOVED since
708
+ this plan was written, the model is right and the plan is stale.
709
+ - CARRY THE LEDGERS FORWARD. §7 DA-n architectural decisions and the WA-n
710
+ workarounds are DECISIONS THIS TEAM MADE, not derivations from the spec — keep
711
+ every one, with its id and its basis, unless the thing it decided about is gone.
712
+ Never renumber a surviving entry: its id is cited elsewhere.
713
+ - THE CODE THAT EXISTS IS THE WHAT-IS AUTHORITY. {{source_count}} source file(s)
714
+ realise this feature: {{source_files}}. READ them, and where the plan and the code
715
+ disagree about STRUCTURE, the code is what is actually there — conform to it and
716
+ record the divergence rather than describing a shape nobody built. The spec stays
717
+ the only authority on BEHAVIOUR.
718
+ - TECHNOLOGY-AGNOSTIC, exactly as the master already is: signatures + pre/post/
719
+ invariant/raises pseudocode, contracts as field shapes, conceptual types. Never
720
+ language code; the stack binds at `implement` time.
721
+ - ADD ONE Change History row for this re-derivation, describing what MOVED and why
722
+ (the spec ids and BR-nnn that drove it). Do NOT invent a version number — the
723
+ version spine belongs to close-delta.
724
+
725
+ # ═══════════ FRONTEND LANE — bootstrap, greenfield experience template ═══════
726
+ # The experience twin of the service plan, and the MEDIATOR between spec.md and the
727
+ # frontend code. Its sections are the experience, not the four clean-architecture
728
+ # layers. UX- lives HERE and only here — the service plan excludes the spec's Experience
729
+ # Standards by contract, so a UX- that reaches neither plan is a dropped requirement.
730
+ - id: ctx_frontend
731
+ condition: "{{fe_mode}} == bootstrap_gf"
615
732
  action: build_context
616
- vars:
617
- derived_version: "{{current_version}}"
618
733
  context:
619
- - { path: "{{plan_doc}}" } # the plan as just written — §3 is the source
620
- - { path: "{{feature_dir}}/interfaces.md", optional: true } # existing entries — what a change supersedes
621
- - id: gen_if
734
+ - { path: "{{frontend_plan}}" } # the copied template = section structure
735
+ - { path: "{{spec_file}}" } # the source of truth (UX/US/FR/CN/DC/IC/SC)
736
+ - { path: "{{domain_model}}", optional: true } # the ubiquitous language this lane's labels and messages use
737
+ - { path: .appweaver/DESIGN-FORMAT.md } # the design-doc contract + its frontend lane variant
738
+ - { path: .appweaver/memory/constitution.ui.md, optional: true } # the lane's ratified UX-* base — optional: a lane may be declared before it is ratified
739
+ - { path: "{{backend_plan}}", optional: true } # §5 ONLY — the service's published surface, never its internals
740
+ - id: gen_frontend
741
+ condition: "{{fe_mode}} == bootstrap_gf"
622
742
  action: agent_author
623
- output_target: { type: file, path: "{{feature_dir}}/.aw-if-new.tmp.md" }
743
+ output_target: { type: file, path: "{{frontend_plan}}" }
624
744
  task: |
625
- From the plan document in context, extract this feature's PUBLISHED SURFACE into
626
- formal interface-log entries: the endpoints, published events and externally
627
- consumed contracts its API/published-surface section describes.
745
+ Author the MASTER frontend-lane plan (the target frontend-plan.md in context) for
746
+ this feature's EXPERIENCE by filling out the template from the feature spec + the
747
+ loaded frontend standards. This is the frontend lane's MEDIATOR between the spec
748
+ and the components `implement` will generate. Guidance: {{USER_INPUT}}
749
+ Rules:
750
+ - THE SPEC IS THE ONLY DOMAIN INPUT. You have the spec + frontend standards, and
751
+ NO domain rule files. The domain arrives as spec ids only — realise them, and
752
+ invent no domain rule the spec omits (an unavoidable new concept → §8 DA-n).
753
+ Where the context's domain model is in context, use its UBIQUITOUS LANGUAGE
754
+ exactly — the same terms in labels, states and messages, no synonyms — and cite
755
+ its BR-nnn rather than restating a rule.
756
+ - Keep the template's section order and headings: §0 Design Overview, §1
757
+ Navigation & Screen Structure, §2 Client State, §3 Component Structure, §4
758
+ Interaction & Flow, §5 Contract Binding, §6 Cross-Cutting, §7 Traceability,
759
+ §8 Assumptions, §9 Workarounds, Change History.
760
+ - REALISE THE SPEC'S EXPERIENCE CONTENT:
761
+ • Experience Standards (UX-) are THIS document's to realise — the service plan
762
+ excludes them by contract, so a UX- with no home here is a DROPPED
763
+ requirement, not a tidy omission. Every UX- gets a §7 row.
764
+ • Experience Scope (actors · channels · device/browser/AT floors) bounds §1
765
+ and §6 — design for the floors the spec names and no more.
766
+ • Each User Story (US-) gets a screen home in §1 and a flow in §4. A US- with
767
+ no screen is a coverage failure.
768
+ • Interaction-state requirements are ordinary FR- ids (the EARS
769
+ unwanted-behaviour form). Each one gets a §4 row.
770
+ - REALISE THE LIFTED CONSTRAINTS, each citing its spec id:
771
+ • CN- → §6 Authorization surface and the §1 access conditions (what is hidden
772
+ vs disabled vs enforced server-side — the client never becomes the authority).
773
+ • DC- → §2 "what must NOT be held" and the §6 data-exposure row: what may not
774
+ reach the client, its logs, its storage or its URLs.
775
+ • IC-/SC- → §5 binding rows and the §6 performance row respectively.
776
+ - §4 INTERACTION & FLOW AND §6 CROSS-CUTTING ARE NEVER OMITTED, however thin the
777
+ feature. Loading, empty, error, permission-denied and in-flight states, plus
778
+ validation timing, focus placement and keyboard reachability, are where most UI
779
+ defects live. A state the UI can reach that the spec does not describe is a SPEC
780
+ gap: log it as a §8 DA-n and say it routes to `specify`; never invent the
781
+ requirement here.
782
+ - §5 IS THE LANE BOUNDARY. Bind against the service's PUBLISHED surface only —
783
+ the spec's IC- ids, and where the backend plan is in context, the API contracts
784
+ it states. CITE those contracts; do NOT restate, re-derive or contradict them,
785
+ and introduce NO server-side behaviour, persistence assumption or handler detail.
786
+ Anything the experience needs that the surface does not offer is a CONTRACT
787
+ DELTA raised back to the service lane under "Contract deltas needed" — never a
788
+ workaround in the client. Where no service plan is in context, bind from the
789
+ spec's IC- surface and record that as a §8 DA-n.
790
+ - CONSTITUTION-MANDATED CROSS-CUTTING (from the loaded UI constitution, NOT the
791
+ spec): a NON-NEGOTIABLE UX principle you must apply that has no per-feature spec
792
+ id. APPLY it, and because it is not spec-derived, LOG it as a §8 DA-n whose
793
+ basis cites the mandating principle ("per constitution.ui.md UX-<n>"); set the
794
+ corresponding §6 row's "Serves" to that DA-n. Do NOT fabricate a spec UX- for
795
+ it, and do NOT leave it uncited.
796
+ - TECHNOLOGY-AGNOSTIC: name no framework, router, state library, component library
797
+ or styling approach. Describe a component by its RESPONSIBILITY, its INPUTS as
798
+ field shapes, the EVENTS it raises and the STATES it can be in; describe routing
799
+ as a conceptual shape. The framework binds at implement time. Do NOT include
800
+ file placement, naming conventions or test mechanics — those are the UI
801
+ constitution's and `implement`'s, identical for every feature.
802
+ - The header's Lane field is `{{fe_lane_id}}`. That identifier is the ONLY thing you
803
+ are told about the lane: its framework and language are deliberately withheld,
804
+ because a plan that knows them starts naming them.
805
+ - STAY IN THE LANE'S SCOPE. This feature's screens only. The app shell, global
806
+ layout and the design system belong to no single feature: cite them under
807
+ "Screens this feature does NOT own" / "Reused from the design system", and do
808
+ not claim or redesign them here.
809
+ - §7 TRACEABILITY IS THE SPINE — fill it completely, both ways: a row for EVERY
810
+ UX-, US-, and for every CN-/DC-/IC-/SC- with an experience obligation, so no
811
+ lifted constraint is silently dropped; and no UI element without a spec reason.
812
+ - Fill the two ledgers: §8 Assumptions (DA-n, to clarify later) and §9 Workarounds
813
+ (WA-n, to clear later). Empty → "None (as of v1.0)".
814
+ - This is v1.0: set the initial Change History row. No component code anywhere.
628
815
 
629
- Output ONLY entries that are NOT ALREADY in the interface log in context, or that
630
- SUPERSEDE an entry there because the plan now describes them differently. An
631
- interface the log already carries unchanged is NOT re-published. If there is
632
- nothing to publish — no published surface, or every one already logged unchanged
633
- — output exactly:
634
- none
816
+ # ═══ FRONTEND LANE — bootstrap, brownfield code-derived template, NEW feature ═══
817
+ # Same contract as above (technology-agnostic, spec is the only domain input, full §7
818
+ # and both ledgers), with ONE difference: the STRUCTURE follows the code-derived
819
+ # template's own composition axes and the lane's as-built conventions, so a new feature
820
+ # slots into the app already there instead of introducing a second convention.
821
+ - id: ctx_frontend_brownfield
822
+ condition: "{{fe_mode}} == bootstrap_bf"
823
+ action: build_context
824
+ context:
825
+ - { path: "{{frontend_plan}}" } # the scaffolded CODE-DERIVED template = as-built section structure
826
+ - { path: "{{spec_file}}" } # the source of truth (UX/US/FR/CN/DC/IC/SC)
827
+ - { path: "{{domain_model}}", optional: true } # the ubiquitous language this lane's labels and messages use
828
+ - { path: .appweaver/DESIGN-FORMAT.md } # the design-doc contract + its frontend lane variant
829
+ - { path: .appweaver/memory/constitution.ui.md, optional: true } # the lane's UX-* base, reversed by onboard from this same UI
830
+ - { path: knowledge/architecture/frontend.md, optional: true } # the as-built UI architecture + the SHARED surface no slice owns
831
+ - { path: "{{backend_plan}}", optional: true } # §5 ONLY — the service's published surface, never its internals
832
+ - id: gen_frontend_brownfield
833
+ condition: "{{fe_mode}} == bootstrap_bf"
834
+ action: agent_author
835
+ tools: Read # the lane's EXISTING source arrives as {{source_files}} paths, not inlined
836
+ output_target: { type: file, path: "{{frontend_plan}}" }
837
+ task: |
838
+ Author the MASTER frontend-lane plan (the target frontend-plan.md in context — it is
839
+ this project's CODE-DERIVED UI template) for this NEW feature being added to an
840
+ EXISTING frontend, by filling the template from the feature spec + the loaded
841
+ frontend standards. It is the MEDIATOR between the spec and the components
842
+ `implement` will generate. Guidance: {{USER_INPUT}}
843
+ Rules:
844
+ - THE SPEC IS THE ONLY DOMAIN INPUT. You have the spec + frontend standards + the
845
+ lane's as-built architecture reference, and NO domain rule files. The domain
846
+ arrives as spec ids only; an unavoidable new concept is an Assumption (DA-n).
847
+ Where the context's domain model is in context, use its ubiquitous language
848
+ exactly and cite its BR-nnn rather than restating a rule.
849
+ - THE CODE THAT EXISTS IS THE WHAT-IS AUTHORITY. {{source_count}} source file(s)
850
+ already realise this feature: {{source_files}}. READ them and MATCH the app's
851
+ screen organisation, its state and data-fetching placement and its styling
852
+ approach, so the feature slots in rather than introducing a second convention.
853
+ - STRUCTURE FOLLOWS THE TEMPLATE AND THE LANE AS-BUILT, not a fixed screen
854
+ taxonomy. Keep the template's own section order and headings, and create one
855
+ component subsection per composition axis THIS feature actually needs, titled
856
+ with the lane's OWN name for it.
857
+ - KEEP THE NEUTRAL ANCHORS the template pins: the navigation/screen inventory, the
858
+ client-state table, the interaction-and-flow section, the cross-cutting table
859
+ and the traceability spine exist in every UI plan, whatever the axes are
860
+ called. INTERACTION & FLOW and CROSS-CUTTING are NEVER omitted, however thin the
861
+ feature — loading, empty, error, permission-denied and in-flight states, focus
862
+ placement and keyboard reachability are where most UI defects live.
863
+ - REALISE THE SPEC'S EXPERIENCE CONTENT: every Experience Standard (UX-) gets a
864
+ traceability row (the service plan excludes them by contract, so this is their
865
+ only home), every User Story a screen and a flow, every interaction-state FR- a
866
+ flow row. Realise the lifted constraints citing their ids — CN- as the
867
+ authorization surface and access conditions, DC- as what may not reach the client
868
+ or its logs, storage and URLs, IC- as binding rows, SC- as the performance
869
+ decision.
870
+ - THE LANE BOUNDARY: bind against the service's PUBLISHED surface only — the
871
+ spec's IC- ids and, where the backend plan is in context, the API contracts it
872
+ states. Cite them; do not restate, re-derive or contradict them, and introduce no
873
+ server-side behaviour. What the experience needs and the surface lacks is a
874
+ contract delta raised back to the service lane, never a client workaround.
875
+ - CONSTITUTION-MANDATED CROSS-CUTTING with no per-feature spec id: apply it, and
876
+ log it as a DA-n citing the mandating principle ("per constitution.ui.md
877
+ UX-<n>"). Do not fabricate a spec id for it and do not leave it uncited.
878
+ - TECHNOLOGY-AGNOSTIC: name no framework, router, state library, component library
879
+ or styling approach — responsibilities, field shapes, events and states only.
880
+ Naming the lane's as-built CONVENTION is not naming a framework; naming the
881
+ library that implements it is. The framework binds at implement time.
882
+ - The header's Lane field is `{{fe_lane_id}}`. That identifier is the ONLY thing you
883
+ are told about the lane: its framework and language are deliberately withheld,
884
+ because a plan that knows them starts naming them.
885
+ - THE SHARED SURFACE (app shell, global layout, design system) belongs to no
886
+ feature. Cite it; do not copy it in and do not claim it here.
887
+ - Fill the traceability spine completely, both ways, and both ledgers —
888
+ Assumptions (DA-n) and Workarounds (WA-n), "None (as of v1.0)" when empty.
889
+ - This is v1.0: set the initial Change History row. No component code anywhere.
890
+
891
+ # ═══════════ FRONTEND LANE — RE-DERIVE an existing master ═══════════════════
892
+ # The experience twin of the service re-derivation, and the same contract: revise the
893
+ # document that is there against the spec as it stands now, keep what still holds, and
894
+ # never refill from a template.
895
+ - id: ctx_frontend_update
896
+ condition: "{{fe_mode}} == update"
897
+ action: build_context
898
+ context:
899
+ - { path: "{{frontend_plan}}" } # the CURRENT master = what is being revised
900
+ - { path: "{{spec_file}}" } # UX/US/FR/CN/DC/IC/SC as they stand now
901
+ - { path: "{{domain_model}}", optional: true } # the ubiquitous language this lane's labels and messages use
902
+ - { path: .appweaver/DESIGN-FORMAT.md } # the design-doc contract + its frontend lane variant
903
+ - { path: .appweaver/memory/constitution.ui.md, optional: true } # the lane's ratified UX-* base
904
+ - { path: "{{backend_plan}}", optional: true } # §5 ONLY — the service's published surface, never its internals
905
+ - id: gen_frontend_update
906
+ condition: "{{fe_mode}} == update"
907
+ action: agent_author
908
+ tools: Read # the feature's EXISTING source arrives as {{source_files}} paths, not inlined
909
+ output_target: { type: file, path: "{{frontend_plan}}" }
910
+ task: |
911
+ RE-DERIVE the MASTER frontend-lane plan in context from the current spec. The
912
+ document you are given is the plan as it stands today; produce the plan this
913
+ feature's spec calls for NOW. Guidance: {{USER_INPUT}}
914
+ Rules:
915
+ - THIS IS A REVISION, NOT A RE-AUTHORING. Keep the existing document's section
916
+ structure and every element the spec still supports — byte-for-byte where nothing
917
+ requires a change.
918
+ - WHAT THE SPEC SAYS NOW WINS. Every §7 Traceability row must resolve to a spec id
919
+ that STILL EXISTS; a row whose id is gone is deleted along with what it justified.
920
+ Every UX- and every US- in the spec needs a home here — UX- lives in THIS document
921
+ and only here, so a UX- with no §7 row is a DROPPED requirement, and a US- with no
922
+ screen in §1 and flow in §4 is a coverage failure.
923
+ - CARRY THE LEDGERS FORWARD. §8 DA-n and §9 WA-n are decisions this team made, not
924
+ derivations from the spec — keep every one with its id and basis unless the thing
925
+ it decided about is gone, and never renumber a survivor.
926
+ - THE CODE THAT EXISTS IS THE WHAT-IS AUTHORITY for STRUCTURE. {{source_count}}
927
+ source file(s) realise this feature: {{source_files}}. Read them and conform to
928
+ the component boundaries and naming actually there.
929
+ - THE LANE BOUNDARY IS UNCHANGED: bind against the service's PUBLISHED surface only
930
+ — the spec's IC- ids and, where the backend plan is in context, the API contracts
931
+ it states. What the experience needs and the surface lacks is a contract delta
932
+ raised back to the service lane, never a client workaround.
933
+ - TECHNOLOGY-AGNOSTIC: no framework, router, state library, component library or
934
+ styling approach — responsibilities, field shapes, events and states only.
935
+ - ADD ONE Change History row for this re-derivation, describing what MOVED and why.
936
+ Do NOT invent a version number — the version spine belongs to close-delta.
635
937
 
636
- Otherwise output ONE block per interface, nothing else:
938
+ # ══════════════ DELTA — resolve the chosen change, then delegate ════════════
939
+ # A change was chosen at 5b. Everything that RESOLVES it lives here, beside the question
940
+ # that asked it, so this command's opening reads the same as `implement`'s: list → ask →
941
+ # activate → paths. The delta-plan module below is then pure AUTHORING.
637
942
 
638
- ## IF-NEW — <METHOD /route, or the event name>
943
+ # The NEW row: there is no change yet, so open one. A PLAN-ONLY change — the spec is
944
+ # unchanged and the realisation of EXISTING spec ids moves (a refactor, an architectural
945
+ # decision). cr-setup scaffolds the delta-spec, which stays an unfilled stub on this
946
+ # path — that stub IS the signal to every downstream gate that no requirement moved.
947
+ - id: open_change
948
+ condition: "{{open_new}} == true"
949
+ action: execute_script
950
+ script: { ref: actions }
951
+ subcommand: cr-setup
952
+ args: ["--feature-dir", "{{feature_dir}}", "--title", "{{USER_INPUT}}"]
953
+ outputs:
954
+ - { name: opened_cr, from: "$.cr_id" }
639
955
 
640
- serves: <the FR-/BR- ids from the spec this interface exists for>
641
- supersedes: <the IF-nnn id it replaces from the log in context, or none>
956
+ # Coalesce the two ways in. An existing change arrived as a LABEL ("CR-… · wave 2 ·
957
+ # title") and the scripts take its first token; a just-opened one arrives as a bare id.
958
+ - id: cr_choice_picked
959
+ condition: "{{plan_target}} == delta"
960
+ action: set
961
+ vars: { cr_choice: "{{plan_choice}}" }
962
+ - id: cr_choice_opened
963
+ condition: "{{open_new}} == true"
964
+ action: set
965
+ vars: { cr_choice: "{{opened_cr}}" }
642
966
 
643
- <the contract: request/response or payload shapes, auth, status codes and the
644
- error contract — shapes, not implementations>
967
+ # Make the chosen change the ACTIVE one. Every stage after this still resolves the
968
+ # change through active_version.json.active_change, so without this a plan authored for
969
+ # CR-B would be gated, implemented and merged as CR-C.
970
+ - id: activate
971
+ condition: "{{plan_target}} == delta"
972
+ action: execute_script
973
+ script: { ref: actions }
974
+ subcommand: cr-activate
975
+ args: ["--cr-id", "{{cr_choice}}", "--feature-dir", "{{feature_dir}}"]
976
+ outputs:
977
+ - { name: active_change, from: "$.active_change" }
645
978
 
646
- Every block starts with "## IF-NEW — " exactly: the script assigns the real ids,
647
- the date and status: proposed. Describe only what the plan actually contains —
648
- invent no interface the plan does not describe.
649
- - id: append_if
979
+ # That change's documents, its wave, and the model deltas that come BEFORE it — the open
980
+ # ones in a LOWER wave, which are the deltas committed to landing first and so the only
981
+ # ones a plan may build on. Same-wave siblings are counted, never assumed.
982
+ - id: cr_paths
983
+ condition: "{{plan_target}} == delta"
650
984
  action: execute_script
651
- on_error: continue # a malformed extraction is reported LOUDLY but must not undo a
652
- # plan that already landed and baselined: the temp file survives
653
- # for a hand-run of log-append; if_error carries the refusal
654
985
  script: { ref: actions }
655
- subcommand: log-append
656
- args: ["--feature-dir", "{{feature_dir}}", "--log", "interfaces", "--entries-file", "{{feature_dir}}/.aw-if-new.tmp.md"]
986
+ subcommand: cr-paths
987
+ args: ["--cr-id", "{{cr_choice}}", "--feature-dir", "{{feature_dir}}"]
657
988
  outputs:
658
- - { name: if_ok, from: "$.ok" }
659
- - { name: if_error, from: "$.error" }
660
- - { name: if_appended, from: "$.appended" }
661
- - { name: if_ids_csv, from: "$.ids_csv" }
662
- - { name: if_unknown_csv, from: "$.unknown_ids_csv" }
663
- - { name: if_badsup_csv, from: "$.bad_supersedes_csv" }
664
-
665
- # 7c. THE TWO INTEGRITY SIGNALS log-append ALREADY RETURNS. Both were bound and neither
666
- # was said out loud — the idiom every other integrity check in these workflows uses
667
- # is a verify_context that continues, and these had none.
668
- # unknown_ids_csv an entry's `serves:` names an id the spec does not define,
669
- # so the BE→FE contract anchor points at nothing.
670
- # bad_supersedes_csv an entry supersedes one the log does not carry, so what it
671
- # claims to replace is not there — and readers take the LATEST
672
- # entry per interface, which is now ambiguous.
673
- # Announce-only: the entries are appended by the time this is visible, ids are
674
- # permanent, and an append-only log is corrected by a later entry, never by a rewrite.
675
- - id: if_unknown_notice
676
- condition: "{{if_unknown_csv}} != ''"
677
- action: verify_context
678
- require_value: if_unknown_csv
679
- equals: ""
680
- on_mismatch: continue
681
- on_mismatch_message: >
682
- interfaces.md now carries entries whose `serves:` names ids the spec does not define:
683
- {{if_unknown_csv}}. That is a broken trace anchor in the BE→FE contract — the frontend
684
- binds to these entries by id, and the requirement they claim to serve is not in
685
- {{spec_file}}. Either the id is a typo, or the requirement was never specified: fix it
686
- with a superseding entry (never by editing the log), or take it through /appweaver specify.
687
- - id: if_badsup_notice
688
- condition: "{{if_badsup_csv}} != ''"
689
- action: verify_context
690
- require_value: if_badsup_csv
691
- equals: ""
692
- on_mismatch: continue
693
- on_mismatch_message: >
694
- An entry appended to interfaces.md supersedes an id the log does not carry:
695
- {{if_badsup_csv}}. Nothing was replaced, so the interface it meant to retire is still
696
- the latest entry for its surface and both now read as current. Append a corrected
697
- superseding entry naming the real id.
698
-
699
- # 8. Post-execution hooks.
989
+ - { name: cr_id, from: "$.cr_id" } # normalised: the label's first token
990
+ - { name: cr_dir, from: "$.cr_dir" }
991
+ - { name: delta_spec, from: "$.delta_spec" }
992
+ - { name: delta_model, from: "$.delta_model" }
993
+ - { name: wave, from: "$.wave" }
994
+ - { name: predecessor_files, from: "$.predecessor_files" }
995
+ - { name: predecessor_count, from: "$.predecessor_count" }
996
+ - { name: sibling_count, from: "$.sibling_count" }
997
+
998
+ # Author that change's delta plans — one per declared lane whose MASTER exists, inside
999
+ # changes/<CR-id>/ beside its delta-spec. The masters are left UNTOUCHED; the change
1000
+ # stays OPEN and finalize-delta merges it later. Runs in this context (shares vars +
1001
+ # the lane rule sets + source_files).
1002
+ - id: delta_plan_module
1003
+ condition: "{{plan_target}} == delta"
1004
+ action: include
1005
+ module: .appweaver/orchestrator/modules/delta-plan.orch.yaml
1006
+
1007
+ # ═══════════ HANDOFF — authoring only; the gates are SEPARATE stages ═══════
1008
+ # • MAIN (bootstrap or re-derivation) → /appweaver analyze-plan (spec↔plan
1009
+ # conformance gate) → implement. No change is opened either way.
1010
+ # • DELTA → the chosen change stays OPEN with filled delta plans. Ship it via the CR
1011
+ # chain: /appweaver analyze-plan-delta → implement → build → analyze-slice-delta
1012
+ # → finalize-delta (merges the delta into spec.md + domain-model.md — not the lane
1013
+ # plans, which are derived from those two; the change stays OPEN) → analyze-slice
1014
+ # → close-delta (archives the change, bumps the minor version). Re-deriving the
1015
+ # masters from the merged documents is `plan main` on a later run.
1016
+
1017
+ # 7. Post-execution hooks.
700
1018
  - id: resolve_post_hooks
701
1019
  action: execute_script
702
1020
  script: { ref: actions }
@@ -710,12 +1028,16 @@ workflow:
710
1028
 
711
1029
  outputs:
712
1030
  feature_dir: "{{feature_dir}}"
713
- plan_doc: "{{plan_doc}}"
1031
+ bounded_context: "{{context}}"
1032
+ backend_plan: "{{backend_plan}}" # empty when no backend lane is declared
1033
+ frontend_plan: "{{frontend_plan}}" # empty when no frontend lane is declared
1034
+ domain_model: "{{domain_model}}"
714
1035
  plan_mode: "{{plan_mode}}"
715
- migrated_legacy_design: "{{plan_migrated}}"
716
- derived_from_spec: "{{current_version}}"
717
- conformed_ids: "{{changed_csv}}"
718
- interfaces_published: "{{if_ids_csv}}"
719
- interfaces_error: "{{if_error}}"
720
- source_files_read: "{{source_count}}"
721
- next: "Gate this write: /appweaver analyze-design (spec ↔ plan conformance) — after EVERY bootstrap and EVERY update. Then the backlog and the stories: /appweaver sync-stories, then /appweaver story-plan <id>, which READS this plan and refuses to design against one the spec has moved past. {{fe_next}} READ THE FIELDS ABOVE FIRST: interfaces_published are the IF- ids this run appended to interfaces.md as status:proposed — cite them in the plan's API section on the next update, and never edit the log by hand; interfaces_error non-empty means the extraction was refused and .aw-if-new.tmp.md is still beside the plan for a hand-run of log-append, so THIS RUN PUBLISHED NOTHING; source_files_read 0 means no code was found for this feature, so the plan was written greenfield."
1036
+ plan_target: "{{plan_target}}"
1037
+ be_mode: "{{be_mode}}"
1038
+ fe_mode: "{{fe_mode}}"
1039
+ version: "{{version}}"
1040
+ change_id: "{{active_change}}"
1041
+ source_count: "{{source_count}}"
1042
+ note: "This run was pointed at `{{plan_target}}` (plan_mode {{plan_mode}}); backend lane {{be_mode}}, frontend lane {{fe_mode}}. Lanes come from .appweaver/stacks.json: backend declared = {{be_declared}}, frontend declared = {{fe_declared}}. A declared lane gets a plan; an undeclared one gets nothing and that is not a failure. A lane whose mode is `update` had its MASTER re-derived from the current spec and model, with its DA-n and WA-n ledgers carried forward — that rewrite was confirmed before it ran. The domain model at {{domain_model}} was READ, never written — it is spec-tier, and this phase records where each invariant is ENFORCED rather than what it says."
1043
+ next: "MAIN (masters authored or re-derived): /appweaver analyze-plan (spec↔plan gate) → implement. DELTA (change {{active_change}} open, delta plans authored): /appweaver analyze-plan-delta → implement → build → analyze-slice-delta → finalize-delta → analyze-slice → close-delta — implement is the same command on both paths and ASKS which open change it is building, so pass the id (/appweaver implement {{active_change}}) to skip the prompt (finalize-delta merges the delta into spec.md and domain-model.md — not the lane plans, which are derived from those two — but leaves the change open; analyze-slice verifies the slice against the merged masters; close-delta archives the change and bumps the minor version). Once a change has merged, /appweaver plan main re-derives the masters from the documents it merged into."