aw-spec-cli 0.1.13 → 0.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/assets/.appweaver/STACK-PROFILES.md +1 -1
  2. package/assets/.appweaver/orchestrator/orchestrator.py +5 -0
  3. package/assets/.appweaver/orchestrator/rev-templates/domain-rule-bindings.skeleton.md +10 -3
  4. package/assets/.appweaver/orchestrator/rev-templates/tech-rule-bindings.skeleton.md +38 -11
  5. package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +2 -1
  6. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-design.orch.yaml +607 -123
  7. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +625 -66
  8. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement-ui.orch.yaml +407 -134
  9. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +452 -143
  10. package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +78 -14
  11. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan-ui.orch.yaml +597 -0
  12. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +688 -0
  13. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-feature.orch.yaml +3 -3
  14. package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +23 -23
  15. package/assets/.appweaver/orchestrator/workflows/aw-spec-story-plan.orch.yaml +945 -0
  16. package/assets/.appweaver/orchestrator/workflows/aw-spec-sync-stories.orch.yaml +1040 -0
  17. package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +2069 -28
  18. package/assets/.appweaver/scripts/bash/aw-xref.sh +2 -1
  19. package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +1973 -13
  20. package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +2 -1
  21. package/assets/.appweaver/scripts/python/aw-xref.py +246 -20
  22. package/assets/.appweaver/templates/design-template.md +88 -54
  23. package/assets/.appweaver/templates/spec-template.md +12 -10
  24. package/assets/.appweaver/templates/story-template.md +48 -8
  25. package/assets/.appweaver/templates/ui-design-template.md +71 -39
  26. package/assets/guardrails/projects/dotnet-default-project/domain-rule-bindings.md +19 -7
  27. package/assets/guardrails/projects/dotnet-default-project/tech-rule-bindings.md +35 -26
  28. package/package.json +1 -1
  29. package/assets/.appweaver/orchestrator/workflows/aw-spec-design-ui.orch.yaml +0 -386
  30. package/assets/.appweaver/orchestrator/workflows/aw-spec-design.orch.yaml +0 -382
@@ -1,33 +1,57 @@
1
1
  # =============================================================================
2
- # aw-spec-implement (orchestrator) — model-driven code generation from the DESIGN
2
+ # aw-spec-implement (orchestrator) — code generation from ONE STORY'S DESIGN
3
3
  #
4
- # The spec-anchored Implement phase: GENERATE the source files + paired tests for
5
- # the aggregate STRICTLY from its single high-level design.md — the mediator between
6
- # spec and code. Implement does NOT read spec.md: `analyze` already guaranteed the
7
- # design conforms to the spec (spec is the anchor), so the design is the complete,
8
- # authoritative source for WHAT to build. The target STACK is bound here from the
9
- # constitution + the active stack profile (the design is technology-agnostic).
10
- # Prereqs: run /appweaver design then analyze-design first (the gate conforms the
11
- # design to the spec + closes any open change), and scaffold the project once —
12
- # implement GENERATES into that project tree, so with no project there is nowhere
13
- # to place the files. /appweaver foundation unpacks one where a bundle ships for the
14
- # stack; a stack with no bundle scaffolds with its own tooling instead, and the gate
15
- # below accepts either, since it tests for a project rather than for a bundle.
4
+ # The executable end of the story loop: GENERATE the service lane's source files and
5
+ # paired tests for ONE STORY, strictly from the LATEST design pass in that story's own
6
+ # service design — specs/<F>/stories/<ID>/story-backend-plan.md.
16
7
  #
17
- # Next: Build (compile + fix; reconcile only high-level variations into design.md).
8
+ # IT BUILDS A SLICE, NOT A FEATURE. The old contract generated the whole aggregate from
9
+ # the feature design every run, which is only correct once: the second run rewrites the
10
+ # first story's code from a document that has since moved. A story design names the
11
+ # files it changes, so this phase edits an existing tree — creating only what the pass
12
+ # says is new — and its OKF write is a MERGE, never a replace.
18
13
  #
19
- # Launch: /appweaver implement [strategy] [guidance]
14
+ # DESIGN-ONLY, AND THE DESIGN IS THE STORY'S. spec.md is deliberately NOT a generation
15
+ # input: generating from the spec routes around every gate between them and makes the
16
+ # design advisory without anyone deciding that it should. The spec appears once, AFTER
17
+ # the code exists, in a read-only acceptance check that reports and never repairs.
18
+ #
19
+ # THE LATEST PASS WINS, AND A HAND EDIT TO IT IS HONOURED. Passes append; earlier ones
20
+ # are superseded history. The pass this run builds from is named in the prompt and in
21
+ # the outputs, so "which design produced this code" has one answer.
22
+ #
23
+ # INPUT CHANNELS, each with one job:
24
+ # story-backend-plan.md the LATEST pass = the complete WHAT for this slice
25
+ # stories/<ID>.md the story's acceptance criteria — what "done" means
26
+ # current code (OKF) what EXISTS, per lane, so this run edits rather than recreates
27
+ # interfaces.md the PUBLISHED surface the generated code must match
28
+ # adr.md what was DECIDED — binding unless a pass supersedes it
29
+ # constitution + rules HOW to build it (stack, layout, standards)
30
+ # backend-plan.md is NOT a generation input. The pass was designed inside it and is the
31
+ # concrete form of it; re-reading the plan here would let code drift from the pass back
32
+ # toward the architecture, with no gate between the two. It is read for two DERIVED
33
+ # facts only — the aggregate's cross-slice edges and its Overview prose — both of which
34
+ # are feature-level properties no single story can state.
35
+ #
36
+ # GATES (all loud, all before any write): the service lane declared with its source path;
37
+ # a scaffolded project under that path; an active feature; an OPEN story; and that story
38
+ # having a service design at all. A design behind the current spec, or a story the spec
39
+ # has moved past, is ANNOUNCED — never refused: the story is legitimately in flight, and
40
+ # the spec may have moved on ids it does not carry. The route out of both is named.
41
+ #
42
+ # Launch: /appweaver implement [<story-id>] [guidance] (asks when ambiguous)
43
+ # Prereq: /appweaver story-plan (the story's design) · Next: /appweaver build
20
44
  # =============================================================================
21
45
 
22
46
  name: aw-spec-implement
23
- description: Generate the aggregate's source files and paired tests STRICTLY from its design.md, bound to the project's stack. Model-driven, design-only (does not read the spec — analyze already conformed the design to it).
24
- version: 2.1.0
47
+ description: "Generate the service lane's source files and paired tests for ONE STORY, strictly from the LATEST design pass in stories/<ID>/story-backend-plan.md, bound to the declared backend stack and written only under that lane's source path. Model-driven and design-only — the spec is never a generation input; it appears once afterwards in a read-only acceptance check that reports and never repairs. Incremental by contract: it edits the feature's existing tree, creating only what the pass names as new, and MERGES the files it wrote into the feature's OKF concept rather than replacing the list."
48
+ version: 3.0.0
25
49
  agent_agnostic: true
26
50
  dialect: aw-spec.orchestrator/v1
27
51
 
28
52
  inputs:
29
53
  user_input:
30
- description: Optional implementation guidance (stack hints, focus area).
54
+ description: Optional story id and/or implementation guidance (e.g. "Order-001 keep the mapper hand-written").
31
55
  required: false
32
56
  bind: USER_INPUT
33
57
 
@@ -39,74 +63,220 @@ scripts:
39
63
  sh: .appweaver/scripts/bash/aw-xref.sh
40
64
  ps1: .appweaver/scripts/powershell/aw-xref.ps1
41
65
 
42
- # Sticky standards seed = the UNIVERSAL always-on rule only (ai-rules). The
43
- # FRAMEWORK / code-generation rule files are no longer hardcoded here — they are now
44
- # CONFIGURATION, loaded per-command from guardrails/projects/<project>/tech-rule-bindings.md
45
- # by the tech-select module (see the workflow's tech_select step), exactly as design's
46
- # technical rules are. Per the rule-partition, implement still generates from design.md +
47
- # these HOW-to-build standards and loads NO domain rule file — the tech map lists only
48
- # technical files. The architecture principles come via constitution.technical.md
49
- # (referred to directly, as-is — NOT mapped); the structure via design.md. Component
50
- # rule docs are NOT loaded here — implement codes against a component's PORT; adding the
51
- # component itself is a separate step, /appweaver add-component. (missing files skipped)
66
+ # Sticky standards seed = the UNIVERSAL always-on rule only (ai-rules). The FRAMEWORK /
67
+ # code-generation rule files are CONFIGURATION, loaded per-command from
68
+ # guardrails/projects/<project>/tech-rule-bindings.md under "## /aw-spec-implement" by
69
+ # the tech-select module. Per the rule-partition this phase loads NO domain rule file —
70
+ # the domain reached it through spec → feature plan → the story's design pass. The
71
+ # architecture principles arrive via constitution.technical.md (referred to directly,
72
+ # as-is — NOT mapped). Component rule docs are NOT loaded here: implement codes against
73
+ # a component's PORT, and installing the component is /appweaver add-component.
52
74
  standards:
53
75
  - { path: .appweaver/ai-rules.md } # kit-resident: the universal operating rules, every flow
54
76
 
77
+ vars:
78
+ feature_dir: ""
79
+ spec_file: ""
80
+ be_plan: ""
81
+ be_plan_exists: "false"
82
+ # story resolution
83
+ any_open: "false"
84
+ open_ids: "[]"
85
+ digest: ""
86
+ resolved: "false"
87
+ needs_pick: "false"
88
+ pick_reason: ""
89
+ picked_story: ""
90
+ story: ""
91
+ story_path: ""
92
+ story_open: "false"
93
+ status_msg: ""
94
+ story_title: ""
95
+ exercises_csv: ""
96
+ constraints_csv: ""
97
+ current_version: ""
98
+ stale_csv: ""
99
+ stale_note: ""
100
+ story_dir: ""
101
+ adr_path: ""
102
+ if_path: ""
103
+ # the story's SERVICE design
104
+ design_doc: ""
105
+ design_exists: "false"
106
+ design_pass: "0"
107
+ design_spec_version: ""
108
+ design_stale: "false"
109
+ # the feature's current code, per lane (OKF)
110
+ okf_concept: ""
111
+ coverage_digest: ""
112
+ be_source_csv: ""
113
+ be_source_count: "0"
114
+ fe_source_count: "0"
115
+ # generation + enrichment
116
+ files: ""
117
+ xref_wrote: ""
118
+ xref_edge_count: "0"
119
+ okf_overview: ""
120
+ accept_ok: "true"
121
+ accept_gaps: ""
122
+ accept_note: ""
123
+
55
124
  workflow:
56
125
 
57
126
  # 0. TECHNICAL rule files → sticky standards (configuration, not hardcoded).
58
- # tech-select reads guardrails/projects/<project>/tech-rule-bindings.md under
59
- # "## /aw-spec-implement" and loads that set into the sticky standards the
60
- # agent_generate step ALWAYS prepends. No domain rule file is loaded here — the
61
- # tech map lists only technical files (rule-partition intact). No blocking gate:
62
- # an unconfigured project soft-continues (constitution.technical.md still carries
63
- # the tech base, referred to directly below).
127
+ # No blocking gate: an unconfigured project soft-continues, because
128
+ # constitution.technical.md still carries the technology base.
64
129
  - id: tech_select
65
130
  action: include
66
131
  module: .appweaver/orchestrator/modules/tech-select.orch.yaml
67
132
  with: { command: aw-spec-implement }
68
133
 
69
- # 1. Resolve the current feature + its design; require the DESIGN (implement's
70
- # only input). No spec — analyze already conformed the design to the spec.
134
+ # 1. The active feature. spec.md and backend-plan.md are resolved here but neither is
135
+ # a generation input — see the header for what each is actually used for.
71
136
  - id: feature_paths
72
137
  action: execute_script
73
138
  on_error: continue # never crash raw on 'no feature set' — the gate below explains it
74
139
  script: { ref: actions }
75
140
  subcommand: feature-paths
76
141
  outputs:
77
- - { name: feature_dir, from: "$.feature_dir" }
78
- - { name: design_doc, from: "$.design_file" }
79
- - { name: design_exists, from: "$.design_exists" }
80
-
142
+ - { name: feature_dir, from: "$.feature_dir" }
143
+ - { name: spec_file, from: "$.spec_file" }
144
+ - { name: be_plan, from: "$.backend_plan_file" }
145
+ - { name: be_plan_exists, from: "$.backend_plan_exists" }
81
146
  - id: require_feature
82
147
  action: verify_context
83
148
  require_non_empty: feature_dir
84
149
  on_empty: stop
85
150
  on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the aggregate, e.g. Order), then re-run implement."
86
151
 
87
- - id: verify_design
152
+ # 2. WHICH STORY. Same resolution story-plan uses: an explicit id in the input wins,
153
+ # anything else (nothing named, an unknown id, several) becomes a pick from the open
154
+ # backlog. One scan returns every fact the gates below read.
155
+ - id: story_scan
156
+ action: execute_script
157
+ script: { ref: actions }
158
+ subcommand: story-plan-check
159
+ args: ["--feature-dir", "{{feature_dir}}", "--input", "{{USER_INPUT}}"]
160
+ outputs:
161
+ - { name: any_open, from: "$.any_open" }
162
+ - { name: open_ids, from: "$.open_ids" }
163
+ - { name: digest, from: "$.digest" }
164
+ - { name: resolved, from: "$.resolved" }
165
+ - { name: needs_pick, from: "$.needs_pick" }
166
+ - { name: pick_reason, from: "$.pick_reason" }
167
+ - { name: story, from: "$.story" }
168
+ - { name: story_path, from: "$.story_path" }
169
+ - { name: story_open, from: "$.story_open" }
170
+ - { name: status_msg, from: "$.status_msg" }
171
+ - { name: story_title, from: "$.story_title" }
172
+ - { name: exercises_csv, from: "$.exercises_csv" }
173
+ - { name: constraints_csv, from: "$.constraints_csv" }
174
+ - { name: current_version, from: "$.current_version" }
175
+ - { name: stale_csv, from: "$.stale_csv" }
176
+ - { name: stale_note, from: "$.stale_note" }
177
+ - { name: story_dir, from: "$.story_dir" }
178
+ - { name: adr_path, from: "$.adr_path" }
179
+ - { name: if_path, from: "$.if_path" }
180
+ - { name: design_doc, from: "$.be_doc_path" }
181
+ - { name: design_exists, from: "$.be_doc_exists" }
182
+ - { name: design_pass, from: "$.be_last_pass" }
183
+ - { name: design_spec_version, from: "$.be_doc_spec_version" }
184
+ - { name: design_stale, from: "$.be_doc_stale" }
185
+ - id: require_open_backlog
186
+ action: verify_context
187
+ require_true: any_open
188
+ on_false: stop
189
+ on_false_message: "No open stories for this feature — there is nothing to implement. Stories are minted by /appweaver specify (bootstrap) and /appweaver sync-stories (when the spec moves)."
190
+ - id: ask_story
191
+ condition: "{{needs_pick}} == true"
192
+ action: ask
193
+ prompt: |
194
+ Which story is this implementation for? ({{pick_reason}})
195
+
196
+ {{digest}}
197
+ options_from: "{{open_ids}}"
198
+ answer_key: picked_story
199
+ answer_var: picked_story
200
+ - id: story_pick_check
201
+ condition: "{{needs_pick}} == true"
202
+ action: execute_script
203
+ script: { ref: actions }
204
+ subcommand: story-plan-check
205
+ args: ["--feature-dir", "{{feature_dir}}", "--story", "{{picked_story}}"]
206
+ outputs:
207
+ - { name: resolved, from: "$.resolved" }
208
+ - { name: story, from: "$.story" }
209
+ - { name: story_path, from: "$.story_path" }
210
+ - { name: story_open, from: "$.story_open" }
211
+ - { name: status_msg, from: "$.status_msg" }
212
+ - { name: story_title, from: "$.story_title" }
213
+ - { name: exercises_csv, from: "$.exercises_csv" }
214
+ - { name: constraints_csv, from: "$.constraints_csv" }
215
+ - { name: stale_csv, from: "$.stale_csv" }
216
+ - { name: stale_note, from: "$.stale_note" }
217
+ - { name: story_dir, from: "$.story_dir" }
218
+ - { name: design_doc, from: "$.be_doc_path" }
219
+ - { name: design_exists, from: "$.be_doc_exists" }
220
+ - { name: design_pass, from: "$.be_last_pass" }
221
+ - { name: design_spec_version, from: "$.be_doc_spec_version" }
222
+ - { name: design_stale, from: "$.be_doc_stale" }
223
+ - id: require_story
224
+ action: verify_context
225
+ require_true: resolved
226
+ on_false: stop
227
+ on_false_message: |
228
+ Could not resolve a story from '{{picked_story}}'. Open stories:
229
+ {{digest}}
230
+ Re-run implement naming one of them.
231
+ - id: require_open_story
232
+ action: verify_context
233
+ require_true: story_open
234
+ on_false: stop
235
+ on_false_message: "{{status_msg}}"
236
+
237
+ # 3. THE STORY MUST HAVE A SERVICE DESIGN. This phase generates from a design pass and
238
+ # from nothing else, so an undesigned story has no input — and the fix is one
239
+ # command, not a judgement call.
240
+ - id: require_design
88
241
  action: verify_context
89
242
  require_true: design_exists
90
243
  on_false: stop
91
- on_false_message: "No design.md for the current feature. Run /appweaver design then /appweaver analyze-design first — implement generates STRICTLY from the conformed design."
244
+ on_false_message: "Story {{story}} has no service design — {{story_dir}}/story-backend-plan.md does not exist. Run /appweaver story-plan {{story}} first: it designs the slice against the feature's backend plan, the current code and the published interfaces. implement generates from that pass and has no other input."
92
245
 
93
- # 1a. Require the SERVICE LANE and its source path. Implement writes code, and
94
- # the lane's root is the subtree it is allowed to write into — so this runs
95
- # before the foundation gate, which searches that subtree for a project.
246
+ # 3a. A design behind the spec is ANNOUNCED, not refused. The story is legitimately in
247
+ # flight and the spec may have moved on ids it does not carry; stale_csv below is
248
+ # the sharper signal, because it names only the ids THIS story actually carries.
249
+ - id: design_stale_notice
250
+ condition: "{{design_stale}} == true"
251
+ action: verify_context
252
+ require_value: design_stale
253
+ equals: "false"
254
+ on_mismatch: continue
255
+ on_mismatch_message: "Design pass {{design_pass}} was written against spec {{design_spec_version}} and the spec is now at {{current_version}}. Generating from it is not wrong — the move may not touch this story — but if it does, the code inherits a contract that no longer holds. Re-run /appweaver story-plan {{story}} to design against the current spec, or check the ids named below."
256
+ # stale_csv is a STRING, so require_value/equals compares cleanly and needs no
257
+ # condition. design_stale above is a JSON boolean, which require_value stringifies as
258
+ # 'True' — it can never equal 'false', so that one is gated by `condition:` and the
259
+ # value gate is only the vehicle that prints (law 43).
260
+ - id: story_stale_notice
261
+ action: verify_context
262
+ require_value: stale_csv
263
+ equals: ""
264
+ on_mismatch: continue
265
+ on_mismatch_message: "The spec has moved on ids THIS story carries: {{stale_csv}}. Whatever this run generates for them realises superseded wording. Consider /appweaver sync-stories to bring the backlog back in step, then /appweaver story-plan {{story}}, before building."
266
+
267
+ # 4. THE SERVICE LANE and its source path. This phase writes code, and the lane's root
268
+ # is the only subtree it may write into — so this runs before the project gate,
269
+ # which searches that subtree.
96
270
  - id: lane_gate
97
271
  action: include
98
272
  module: .appweaver/orchestrator/modules/lane-gate.orch.yaml
99
273
  with: { require: backend, command: "/appweaver implement" }
100
274
 
101
- # 1b. Require the PROJECT — a scaffolded, compiling tree — to be present. Implement
102
- # GENERATES the aggregate's code INTO it; with no project there is nowhere to place
103
- # the files. Deterministic structure gate: searches the LANE'S OWN subtree for an
104
- # ecosystem manifest (.sln/.csproj, pom.xml, build.gradle, package.json, go.mod,
105
- # Cargo.toml, pyproject.toml), falling back to the whole repo for the layout that
106
- # keeps its manifest beside the lane root rather than inside it, and ignoring the
107
- # framework/spec/guardrails/foundations dirs — never trusts the model for this.
108
- # How the project got there is not asked: `foundation` unpacked it, or the team
109
- # scaffolded it with the stack's own tooling.
275
+ # 5. Require the PROJECT — a scaffolded, compiling tree. Implement GENERATES into it;
276
+ # with no project there is nowhere to place the files. Deterministic structure gate:
277
+ # searches the LANE'S OWN subtree for an ecosystem manifest, falling back to the
278
+ # whole repo for the layout that keeps its manifest beside the lane root rather than
279
+ # inside it. How the project got there is not asked.
110
280
  - id: detect_foundation
111
281
  action: execute_script
112
282
  script: { ref: actions }
@@ -116,14 +286,30 @@ workflow:
116
286
  - { name: foundation_present, from: "$.scaffolded" }
117
287
  - { name: project_markers, from: "$.markers" }
118
288
  - { name: project_scope, from: "$.scope" }
119
-
120
289
  - id: foundation_gate
121
290
  action: verify_context
122
291
  require_true: foundation_present
123
292
  on_false: stop
124
- on_false_message: "No project found under the {{lane_id}} lane's source path `{{lane_source_path}}`, nor anywhere else in this repository. Implement GENERATES the aggregate's code into an existing project tree, so one has to be scaffolded first: run /appweaver foundation <FoundationName> <AppName> where a foundation bundle ships for this stack, or create the project with the stack's own tooling under that path. Any ecosystem manifest marks it — .sln/.csproj, pom.xml, build.gradle(.kts), package.json, go.mod, Cargo.toml or pyproject.toml."
293
+ on_false_message: "No project found under the {{lane_id}} lane's source path `{{lane_source_path}}`, nor anywhere else in this repository. Implement GENERATES this story's code into an existing project tree, so one has to be scaffolded first: run /appweaver foundation <FoundationName> <AppName> where a foundation bundle ships for this stack, or create the project with the stack's own tooling under that path. Any ecosystem manifest marks it — .sln/.csproj, pom.xml, build.gradle(.kts), package.json, go.mod, Cargo.toml or pyproject.toml."
125
294
 
126
- # 2. Pre-execution hooks.
295
+ # 6. WHAT ALREADY EXISTS, from the feature's OKF concept, split by lane. This is what
296
+ # makes the run incremental: the pass names the files it changes, and this names the
297
+ # files that are there to change. Advisory — the first story of a greenfield feature
298
+ # has no concept and generates into a clean tree.
299
+ - id: coverage
300
+ action: execute_script
301
+ on_error: continue
302
+ script: { ref: xref }
303
+ subcommand: concept-coverage
304
+ args: ["--feature-dir", "{{feature_dir}}", "--ids", "{{exercises_csv}}"]
305
+ outputs:
306
+ - { name: okf_concept, from: "$.concept" }
307
+ - { name: coverage_digest, from: "$.digest" }
308
+ - { name: be_source_csv, from: "$.backend_csv" }
309
+ - { name: be_source_count, from: "$.backend_count" }
310
+ - { name: fe_source_count, from: "$.frontend_count" }
311
+
312
+ # 7. Pre-execution hooks.
127
313
  - id: resolve_pre_hooks
128
314
  action: execute_script
129
315
  script: { ref: actions }
@@ -135,74 +321,111 @@ workflow:
135
321
  action: dispatch_hooks
136
322
  hooks_from_variable: before_hooks
137
323
 
138
- # 3. Model-driven generation: design.md → source files + paired tests.
139
- # The design constrains WHAT to build; the constitution/stack profile fixes HOW.
140
- # The spec is deliberately NOT in context — the design is the single source here.
324
+ # ═══════════════════ GENERATION — one story's slice, from one pass ═══════════
325
+ # The pass constrains WHAT to build; the constitution, the lane and the loaded
326
+ # standards fix HOW. spec.md is deliberately absent: the design is the single source
327
+ # here, and the acceptance check below is where the spec gets its say.
141
328
  - id: ctx_generate
142
329
  action: build_context
330
+ vars:
331
+ story_id: "{{story}}"
332
+ story_title: "{{story_title}}"
333
+ exercises_csv: "{{exercises_csv}}"
334
+ constraints_csv: "{{constraints_csv}}"
335
+ pass_number: "{{design_pass}}"
336
+ coverage_digest: "{{coverage_digest}}"
337
+ source_files_csv: "{{be_source_csv}}"
338
+ source_file_count: "{{be_source_count}}"
339
+ stale_note: "{{stale_note}}"
143
340
  context:
144
- - { path: "{{design_doc}}" }
341
+ - { path: "{{design_doc}}" } # the story's SERVICE design — the LATEST pass is the input
342
+ - { path: "{{story_path}}" } # the story's acceptance criteria — what "done" means
343
+ - { path: "{{if_path}}", optional: true } # the PUBLISHED surface this code must match
344
+ - { path: "{{adr_path}}", optional: true } # ratified decisions — binding unless a pass supersedes them
145
345
  - { path: .appweaver/memory/constitution.technical.md, optional: true } # technology base ONLY — no domain
146
346
  - id: generate
147
347
  action: agent_generate
148
- tools: "Read,Write,Edit"
348
+ tools: "Read,Write,Edit" # the existing code arrives as {{source_files_csv}} paths, not inlined
149
349
  permission_mode: acceptEdits
150
350
  task: |
151
- Generate the production code AND paired tests for this aggregate STRICTLY from
152
- its design.md (in context). The design is the complete, authoritative source of
153
- WHAT to build — do NOT consult or infer from the spec; `analyze` already
154
- conformed the design to the spec. This phase is the HOME of the
155
- technology-specific, solution-wide code-generation rules. Guidance: {{USER_INPUT}}
351
+ Implement story {{story_id}} — {{story_title}} — in the service lane, from
352
+ "## Design pass {{pass_number}}" of the story design in context. Guidance: {{USER_INPUT}}
353
+
354
+ THAT PASS IS THE ONLY SOURCE OF WHAT TO BUILD. It is the LAST pass in the
355
+ document; every earlier pass is superseded history, and any of them may carry
356
+ hand edits that were deliberate. Read the last one and build exactly it. Do NOT
357
+ consult or infer from spec.md — it is not in context on purpose: the design
358
+ already went through the gates that conform it, and generating from the spec
359
+ routes around all of them.
360
+
361
+ THIS IS ONE SLICE OF AN EXISTING FEATURE, NOT THE WHOLE FEATURE.
362
+ - The pass's "### Changes" section names, file by file, what to create and what
363
+ to modify. Create ONLY the files it names as new; modify the named existing
364
+ files IN PLACE. Every other file in this feature belongs to work that is
365
+ already done — do not regenerate, reformat or "tidy" it.
366
+ - The story delivers {{exercises_csv}} and must satisfy {{constraints_csv}}.
367
+ Anything outside those ids is another story's work, even if you can see it is
368
+ missing. If the slice genuinely cannot be built without touching shared
369
+ structure, make the smallest change that works and SAY SO in your summary.
370
+ - The code that already exists for this feature, per lane, is below. The service
371
+ files ({{source_file_count}} on disk) are yours to read before you edit; the UI
372
+ list is there so you do not change a surface that already has a client.
373
+
374
+ {{coverage_digest}}
375
+
376
+ {{source_files_csv}}
377
+
378
+ BIND THE STACK: this lane is `{{lane_id}}` — {{lane_title}}, language
379
+ {{lane_language}} ({{lane_ecosystem}}), declared in .appweaver/stacks.json and
380
+ compiled with `{{lane_build}}`, tested with `{{lane_test}}`. Generate in THAT
381
+ language and framework and no other, using its idioms, its project layout and its
382
+ test framework. Take persistence, messaging and the remaining technology choices
383
+ from the constitution's technology base + the loaded standards; the design is
384
+ technology-agnostic, so do not re-decide anything already fixed there. If the
385
+ loaded standards describe a DIFFERENT stack than the one above, the lane wins on
386
+ language and layout — follow the standards' INTENT and STOP to surface the
387
+ mismatch in your summary.
388
+
389
+ WRITE ONLY UNDER `{{lane_source_path}}` — the source path the `{{lane_id}}` lane
390
+ declares. That subtree is this lane's; a file outside it belongs to another lane
391
+ or to no lane at all. Do not create a parallel source tree because the layout
392
+ inside it is unfamiliar; if the pass needs a place this subtree has no home for,
393
+ STOP and surface it.
394
+
156
395
  Rules:
157
- - THE DESIGN IS THE ONLY DOMAIN INPUT. You have design.md + framework/code
158
- standards, and NO domain rule files. Introduce NO domain rule, identifier,
159
- terminology, or behaviour that the design does not state — everything domain
160
- already passed through spec → design. If a domain specific seems missing, STOP
161
- and surface it; never invent it from memory.
162
- - BIND THE STACK: this lane is `{{lane_id}}` — {{lane_title}}, language
163
- {{lane_language}} ({{lane_ecosystem}}), declared in .appweaver/stacks.json and
164
- compiled with `{{lane_build}}`, tested with `{{lane_test}}`. Generate in THAT
165
- language and framework and no other, using its idioms, its project layout and
166
- its test framework. Take persistence, messaging and the remaining technology
167
- choices from the constitution's technology base + the loaded standards; the
168
- design is technology-agnostic, so do not re-decide anything already fixed there.
169
- If the loaded standards describe a DIFFERENT stack than the one above, the lane
170
- wins on language and layout — the standards' stack-specific mechanics do not
171
- apply, so follow their INTENT and STOP to surface the mismatch in your summary.
172
- - WRITE ONLY UNDER `{{lane_source_path}}` — the source path the `{{lane_id}}`
173
- lane declares in .appweaver/stacks.json. That subtree is this lane's, and a file
174
- outside it belongs to another lane or to no lane at all. Do not create a
175
- parallel source tree because the layout inside it is unfamiliar; if the design
176
- needs a place this subtree has no home for, STOP and surface it.
396
+ - THE PASS IS THE ONLY DOMAIN INPUT. You have it, the story, and framework/code
397
+ standards — and NO domain rule files. Introduce NO domain rule, identifier,
398
+ terminology or behaviour the pass does not state. If a domain specific seems
399
+ missing, STOP and surface it; never invent it from memory.
177
400
  - Follow the project's folder/layer convention + the loaded coding-standards,
178
- dependency-policy and testing standards for HOW to structure, name, and place
179
- files WITHIN that subtree — the design says WHAT to build; these standards
180
- (identical for every feature) say HOW.
181
- - COMPONENTS: when the design's External Integrations table depends on a framework
182
- component (e.g. `IEmailService`, `IJobScheduler`), code against that component's
183
- ABSTRACTION / port as the design declares — do NOT hand-roll it. Installing the
184
- component into the project (packages, provider wiring, config) is a SEPARATE step:
185
- /appweaver add-component <Component>. If the port isn't present yet, code to
186
- it and note add-component as the prerequisite; do not load component rule docs here.
187
- - Realise EVERY design element as code, changing nothing about WHAT it specifies:
188
- one file per Domain type / Application use-case / API endpoint / Infrastructure
189
- adapter, placed by layer. Preserve the ubiquitous-language names verbatim.
190
- - Enforce EXACTLY as the design states: every Domain invariant, Application
191
- handler orchestration, and API error contract; the cross-cutting constraints in
192
- the Cross-Cutting Concerns table (audit / retention / data-exposure — the
193
- CN-/DC- realisations); and each integration contract (IC-) in the External
194
- Integrations table. Implement every port the design declares.
195
- - Author a paired test for every design behaviour / use-case / endpoint; derive
196
- the cases from the design's pre/post/invariant contracts and cite the spec id
197
- (FR / BR / CN / DC / IC / US) from the design's Traceability section. Include tests
198
- for the constraints — e.g. an audit record is emitted, a DC-restricted field is
199
- NOT exposed on the public surface, an IC- projection round-trips.
200
- - Do NOT add behaviour the design does not state, and do NOT omit any it does.
201
- If the design looks wrong or incomplete, STOP and surface it (it is fixed in
202
- design/analyze) — never compensate from the spec or from memory.
203
- - This aggregate OWNS its files; never regenerate a file another feature owns.
204
- - Where generated code depends on a design Assumption (DA-n) or Workaround
205
- (WA-n), mark it with a comment citing that id so it is easy to revisit later.
401
+ dependency-policy and testing standards for HOW to structure, name and place
402
+ files WITHIN that subtree — the pass says WHAT to build; these say HOW.
403
+ - Realise the pass's "### Approach" and "### Changes" exactly, changing nothing
404
+ about WHAT they specify, and preserve every ubiquitous-language name verbatim.
405
+ - INTERFACES: where the pass's "### Interfaces" describes a published surface,
406
+ the entry in interfaces.md (in context) is the contract — match its route,
407
+ shape and semantics. Where the log and the pass disagree, the pass is the
408
+ newer word and the log entry it supersedes is named in it; if neither says
409
+ which, STOP and surface it rather than picking.
410
+ - COMPONENTS: where the pass depends on a framework component (e.g.
411
+ `IEmailService`, `IJobScheduler`), code against that component's ABSTRACTION /
412
+ port as the pass declares — do NOT hand-roll it. Installing the component into
413
+ the project (packages, provider wiring, config) is a SEPARATE step:
414
+ /appweaver add-component <Component>. If the port is not present yet, code to
415
+ it and note add-component as the prerequisite.
416
+ - ADRs are DECIDED — follow them. The pass may propose a superseding one; a
417
+ proposal is not yet ratified, so follow the pass and change nothing else.
418
+ - Author a paired test for every acceptance criterion in the story and every
419
+ behaviour the pass's "### Tests" section names, and cite the requirement id
420
+ ({{exercises_csv}}) each test exists for. Include the constraint tests the
421
+ pass names ({{constraints_csv}}) — e.g. an audit record is emitted, a
422
+ restricted field is NOT exposed on the public surface.
423
+ - Do NOT add behaviour the pass does not state, and do NOT omit any it does. If
424
+ the pass looks wrong or incomplete, STOP and surface it — it is fixed by
425
+ re-running /appweaver story-plan, never compensated for here from the spec or
426
+ from memory.
427
+ - This feature OWNS its files; never regenerate a file another feature owns.
428
+ {{stale_note}}
206
429
  outputs:
207
430
  - { name: files, from: "$.files" }
208
431
 
@@ -210,63 +433,146 @@ workflow:
210
433
  action: verify_context
211
434
  require_non_empty: files
212
435
  on_empty: stop
213
- on_empty_message: "No files generated. Ensure /appweaver design produced a filled design.md (not the empty template) and passed analyze, then re-run."
214
-
215
- # 3b. OKF knowledge + cross-references (deterministic, design-derived). Greenfield's
216
- # entry into the shared OKF bundle: author THIS aggregate's concept (its source_files
217
- # = the files just generated), then record its cross-slice edges — write a
218
- # Cross-References row into every SIBLING aggregate it uses (from design.md §1
219
- # References-by-id + its External integrations table), reconcile-on-write so a dropped
220
- # dependency self-heals and a used sibling with no concept yet is stubbed. Finally
221
- # author the concept's Overview prose (3c). This is enrichment downstream of a
222
- # successful generate — it never re-decides WHAT was built.
436
+ on_empty_message: "No files generated for story {{story}}. Check that design pass {{design_pass}} in {{design_doc}} actually describes changes (a pass whose '### Changes' section is empty has nothing to build), then re-run — or re-run /appweaver story-plan {{story}} to design the slice properly first."
437
+
438
+ # 8. OKF — MERGE, never replace. This run wrote ONE STORY's files; the feature's
439
+ # other stories wrote the rest, and a replace would erase them the moment a second
440
+ # story implements. --lane backend puts them in `source_files`, the service lane's
441
+ # key, leaving `ui_source_files` for implement-ui.
442
+ # Known limit: a file the slice DELETED stays in the list; concept-coverage reports
443
+ # it as declared-but-missing drift rather than silently carrying it as real.
223
444
  - id: xref_concept
224
445
  action: execute_script
225
446
  script: { ref: xref }
226
447
  subcommand: concept-upsert
227
- args: ["--feature-dir", "{{feature_dir}}", "--source-files", "{{files}}", "--origin", "greenfield"]
448
+ args: ["--feature-dir", "{{feature_dir}}", "--source-files", "{{files}}", "--merge", "--lane", "backend", "--origin", "greenfield"]
228
449
  outputs:
229
450
  - { name: okf_concept, from: "$.concept" }
230
451
 
452
+ # 8a. Cross-slice edges are a FEATURE property, not a story's: which sibling
453
+ # aggregates this one references is stated by the feature plan's domain model,
454
+ # and no single slice can answer it. So this reads backend-plan.md — the one
455
+ # derived fact it takes from the plan — and reconciles on write, so a dropped
456
+ # dependency self-heals out of the used aggregate's Cross-References. Skipped
457
+ # with no plan on disk: there is then nothing to derive edges from.
231
458
  - id: xref_record
459
+ condition: "{{be_plan_exists}} == true"
232
460
  action: execute_script
461
+ on_error: continue
233
462
  script: { ref: xref }
234
463
  subcommand: record
235
- args: ["--feature-dir", "{{feature_dir}}", "--design", "{{design_doc}}", "--origin", "greenfield"]
464
+ args: ["--feature-dir", "{{feature_dir}}", "--design", "{{be_plan}}", "--origin", "greenfield"]
236
465
  outputs:
237
466
  - { name: xref_wrote, from: "$.wrote" }
238
467
  - { name: xref_edge_count, from: "$.edge_count" }
239
468
 
240
- # 3c. The concept's Overview — the ONE part of the OKF concept a deterministic script
241
- # cannot produce (prose distilled from design.md §1). The model authors the text
242
- # only; concept-overview splices it into the `## Overview` section alone, so the
243
- # Cross-References rows other slices own are structurally out of reach.
469
+ # 8b. The concept's Overview — the one part a deterministic script cannot produce.
470
+ # It describes the AGGREGATE, which is the feature plan's subject: distilling it
471
+ # from one story's slice would name whatever that slice happened to touch. The
472
+ # model authors prose only; concept-overview splices it into `## Overview` alone,
473
+ # so the Cross-References rows other slices own are structurally out of reach.
474
+ # build_context, not a step-level `context:`: the engine REPLACES the envelope on
475
+ # build_context and only ADDS to it on a step-level list, so naming the plan alone
476
+ # there would have left generation's whole payload — this story's slice included —
477
+ # underneath a prompt that says "describe the whole aggregate, not one story".
478
+ - id: ctx_overview
479
+ condition: "{{be_plan_exists}} == true"
480
+ action: build_context
481
+ context:
482
+ - { path: "{{be_plan}}" }
244
483
  - id: okf_overview
484
+ condition: "{{be_plan_exists}} == true"
245
485
  action: agent_query
246
- context: ["{{design_doc}}"]
247
486
  include_standards: false
248
487
  schema: { overview: "<2-4 sentences of prose>" }
249
488
  mock: { overview: "" }
250
489
  task: |
251
- Write the OKF knowledge-concept Overview for the aggregate that design.md (in
252
- context) describes. 2-4 sentences of PROSE, distilled from §1 Domain Model:
490
+ Write the OKF knowledge-concept Overview for the aggregate that the backend plan
491
+ (in context) describes. 2-4 sentences of PROSE, distilled from §1 Domain Model:
253
492
  - name the aggregate ROOT and what the aggregate IS;
254
- - state the INVARIANTS it guards (from §1 / the design's business rules);
493
+ - state the INVARIANTS it guards;
255
494
  - state its TRANSACTION BOUNDARY — what it OWNS inside the boundary vs what it
256
495
  only REFERENCES by id.
257
496
  Plain prose only: no headings, no bullets, no code fences, no file paths, and no
258
497
  restating of the dependency edges (those live in the Depends-On / Cross-References
259
- tables). Describe only what the design states — invent nothing.
498
+ tables). Describe only what the plan states — invent nothing, and describe the
499
+ whole aggregate rather than the one story that happens to be building now.
260
500
  outputs:
261
501
  - { name: okf_overview, from: "$.overview" }
262
-
263
502
  - id: okf_overview_write
503
+ condition: "{{be_plan_exists}} == true"
264
504
  action: execute_script
265
505
  script: { ref: xref }
266
506
  subcommand: concept-overview
267
507
  args: ["--feature-dir", "{{feature_dir}}", "--text", "{{okf_overview}}"]
268
508
 
269
- # 4. Post-execution hooks.
509
+ # ═══════════════ ACCEPTANCE — the ONE place the spec is read ═════════════════
510
+ # Generation is design-only by contract; this is the check that the design was not
511
+ # the wrong design. READ-ONLY and REPORT-ONLY: it never edits code, never edits the
512
+ # pass, and never stops the run. A gap here means one of two documents is wrong, and
513
+ # which one is a human decision — the message names both routes.
514
+ - id: ctx_accept
515
+ action: build_context
516
+ vars:
517
+ story_id: "{{story}}"
518
+ exercises_csv: "{{exercises_csv}}"
519
+ constraints_csv: "{{constraints_csv}}"
520
+ files_csv: "{{files}}"
521
+ context:
522
+ - { path: "{{story_path}}" } # the acceptance criteria
523
+ - { path: "{{spec_file}}" } # the contract — the ONLY step in this workflow that reads it
524
+ - { path: "{{design_doc}}" } # what was designed
525
+ - id: accept_check
526
+ action: agent_query
527
+ tools: Read # the generated files arrive as paths, not inlined
528
+ include_standards: false
529
+ schema: { ok: "<true|false>", gaps: "<one line per gap, or empty>" }
530
+ mock: { ok: "true", gaps: "" }
531
+ task: |
532
+ Story {{story_id}} was just implemented. Judge ONLY this: does the code that now
533
+ exists satisfy the requirements the story delivers, AS THE SPEC WORDS THEM?
534
+
535
+ In scope: {{exercises_csv}} (delivered) and {{constraints_csv}} (must hold).
536
+ Resolve each id's wording from spec.md in context — that wording, not the design's
537
+ restatement of it, is what the code is measured against.
538
+
539
+ Read the files this run wrote:
540
+ {{files_csv}}
541
+ plus anything they call that you need to follow. Do not modify a single file.
542
+
543
+ Report a gap ONLY where the code fails to deliver an in-scope id as the spec words
544
+ it, or breaks an in-scope constraint. These are NOT gaps, and reporting them is
545
+ noise that hides the real ones:
546
+ - a requirement no story has claimed yet, or one another story delivers;
547
+ - style, naming, structure or test-coverage opinions — other gates own those;
548
+ - anything the design pass deliberately deferred and named under its risks.
549
+
550
+ One line per gap: the id, what the spec requires, and what the code does instead.
551
+ Set ok=false only when you found at least one. If the SPEC and the DESIGN disagree
552
+ about an in-scope id, that IS a gap — say which document you believe is wrong and
553
+ why, because the two routes out of it are different commands.
554
+ outputs:
555
+ - { name: accept_ok, from: "$.ok" }
556
+ - { name: accept_gaps, from: "$.gaps" }
557
+ - id: accept_notice
558
+ condition: "{{accept_ok}} != true"
559
+ action: verify_context
560
+ require_value: accept_ok
561
+ equals: "true"
562
+ on_mismatch: continue
563
+ on_mismatch_message: |
564
+ ACCEPTANCE — story {{story}} does not yet satisfy the spec wording of the ids it delivers:
565
+
566
+ {{accept_gaps}}
567
+
568
+ Nothing was changed to fix this: the code follows design pass {{design_pass}}, so a gap
569
+ here means either the design was wrong or the spec has moved. Route by which it is —
570
+ the design is wrong: /appweaver story-plan {{story}} (a new pass supersedes this one);
571
+ the spec is wrong: /appweaver sync-spec, then sync-stories, then story-plan.
572
+ Run /appweaver build either way — a design that is right and code that does not compile
573
+ is a third thing, and build is where that one surfaces.
574
+
575
+ # 9. Post-execution hooks.
270
576
  - id: resolve_post_hooks
271
577
  action: execute_script
272
578
  script: { ref: actions }
@@ -279,9 +585,12 @@ workflow:
279
585
  hooks_from_variable: after_hooks
280
586
 
281
587
  outputs:
282
- feature_dir: "{{feature_dir}}"
283
- design_doc: "{{design_doc}}"
588
+ story: "{{story}}"
589
+ built_from: "{{design_doc}} — design pass {{design_pass}} (designed against spec {{design_spec_version}}; spec now {{current_version}})"
590
+ requirements: "{{exercises_csv}}"
284
591
  files: "{{files}}"
285
- okf_concept: "{{okf_concept}}"
286
- xref_edge_count: "{{xref_edge_count}}"
287
- next: "/appweaver build (OKF concept authored at {{okf_concept}}; {{xref_edge_count}} cross-slice edge(s) recorded into sibling aggregates)"
592
+ lane: "{{lane_id}} → {{lane_source_path}}"
593
+ okf_concept: "{{okf_concept}} (this run's files MERGED into source_files; {{xref_edge_count}} cross-slice edge(s) recorded into sibling aggregates)"
594
+ acceptance: "ok={{accept_ok}} · gaps: {{accept_gaps}}"
595
+ stale_ids: "{{stale_csv}}"
596
+ next: "/appweaver build — it compiles every declared lane. Then /appweaver analyze-slice (spec ↔ code, through the design), and /appweaver implement-ui if this story has a UI design. The story stays OPEN until /appweaver close-story ratifies its ADRs, flips its interface entries live and archives the passes."