aw-spec-cli 0.1.14 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/.appweaver/STACK-PROFILES.md +1 -1
- package/assets/.appweaver/orchestrator/modules/spec-state-gate.orch.yaml +87 -0
- package/assets/.appweaver/orchestrator/orchestrator.py +5 -0
- package/assets/.appweaver/orchestrator/rev-templates/domain-rule-bindings.skeleton.md +10 -3
- package/assets/.appweaver/orchestrator/rev-templates/tech-rule-bindings.skeleton.md +38 -11
- package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +2 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-design.orch.yaml +672 -124
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +700 -72
- package/assets/.appweaver/orchestrator/workflows/aw-spec-implement-ui.orch.yaml +531 -135
- package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +573 -145
- package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +78 -14
- package/assets/.appweaver/orchestrator/workflows/aw-spec-plan-ui.orch.yaml +600 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +721 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-select-feature.orch.yaml +3 -3
- package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +119 -32
- package/assets/.appweaver/orchestrator/workflows/aw-spec-story-plan.orch.yaml +1147 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-sync-stories.orch.yaml +1121 -0
- package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +2564 -38
- package/assets/.appweaver/scripts/bash/aw-xref.sh +2 -1
- package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +2458 -22
- package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +2 -1
- package/assets/.appweaver/scripts/python/aw-payload-parity.py +158 -0
- package/assets/.appweaver/scripts/python/aw-xref.py +431 -48
- package/assets/.appweaver/templates/design-template.md +88 -54
- package/assets/.appweaver/templates/spec-template.md +12 -10
- package/assets/.appweaver/templates/story-template.md +83 -12
- package/assets/.appweaver/templates/ui-design-template.md +71 -39
- package/assets/guardrails/projects/dotnet-default-project/domain-rule-bindings.md +19 -7
- package/assets/guardrails/projects/dotnet-default-project/tech-rule-bindings.md +35 -26
- package/package.json +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-design-ui.orch.yaml +0 -386
- package/assets/.appweaver/orchestrator/workflows/aw-spec-design.orch.yaml +0 -382
|
@@ -1,33 +1,57 @@
|
|
|
1
1
|
# =============================================================================
|
|
2
|
-
# aw-spec-implement (orchestrator) —
|
|
2
|
+
# aw-spec-implement (orchestrator) — code generation from ONE STORY'S DESIGN
|
|
3
3
|
#
|
|
4
|
-
# The
|
|
5
|
-
#
|
|
6
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
|
24
|
-
version:
|
|
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 (
|
|
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,91 +63,361 @@ 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
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
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
|
+
# UNASKED IS NOT UNMATCHED. detect-project answers true whenever no --ecosystem was
|
|
98
|
+
# given, and the gate below only reads it on the repo-scope fallback — so the default
|
|
99
|
+
# has to be permissive or a probe that could not run would refuse a scaffolded lane.
|
|
100
|
+
ecosystem_match: "true"
|
|
101
|
+
project_scope: ""
|
|
102
|
+
project_markers: ""
|
|
103
|
+
current_version: ""
|
|
104
|
+
stale_csv: ""
|
|
105
|
+
stale_note: ""
|
|
106
|
+
story_dir: ""
|
|
107
|
+
adr_path: ""
|
|
108
|
+
if_path: ""
|
|
109
|
+
# the story's SERVICE design
|
|
110
|
+
design_doc: ""
|
|
111
|
+
design_exists: "false"
|
|
112
|
+
design_pass: "0"
|
|
113
|
+
design_spec_version: ""
|
|
114
|
+
design_stale: "false"
|
|
115
|
+
# the analyze-design verdict — law 40's evidence. certified defaults FALSE so a probe that
|
|
116
|
+
# could not run fails CLOSED, and the message default carries a route for the same reason.
|
|
117
|
+
pass_plan_hash: ""
|
|
118
|
+
gate_status: ""
|
|
119
|
+
gate_certified: "false"
|
|
120
|
+
gate_aborted: "false"
|
|
121
|
+
gate_gap_refs: ""
|
|
122
|
+
gate_report: ""
|
|
123
|
+
gate_lane_sha: ""
|
|
124
|
+
gate_msg: "The analyze-design verdict could not be read, so this run cannot tell whether the feature plans were ever certified. Run /appweaver analyze-design, then re-run."
|
|
125
|
+
# the feature's current code, per lane (OKF)
|
|
126
|
+
okf_concept: ""
|
|
127
|
+
coverage_digest: ""
|
|
128
|
+
be_source_csv: ""
|
|
129
|
+
be_source_count: "0"
|
|
130
|
+
fe_source_count: "0"
|
|
131
|
+
# generation + enrichment
|
|
132
|
+
files: ""
|
|
133
|
+
xref_wrote: ""
|
|
134
|
+
xref_edge_count: "0"
|
|
135
|
+
okf_overview: ""
|
|
136
|
+
accept_ok: "true"
|
|
137
|
+
accept_gaps: ""
|
|
138
|
+
accept_note: ""
|
|
139
|
+
|
|
55
140
|
workflow:
|
|
56
141
|
|
|
57
142
|
# 0. TECHNICAL rule files → sticky standards (configuration, not hardcoded).
|
|
58
|
-
#
|
|
59
|
-
#
|
|
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).
|
|
143
|
+
# No blocking gate: an unconfigured project soft-continues, because
|
|
144
|
+
# constitution.technical.md still carries the technology base.
|
|
64
145
|
- id: tech_select
|
|
65
146
|
action: include
|
|
66
147
|
module: .appweaver/orchestrator/modules/tech-select.orch.yaml
|
|
67
148
|
with: { command: aw-spec-implement }
|
|
68
149
|
|
|
69
|
-
# 1.
|
|
70
|
-
#
|
|
150
|
+
# 1. The active feature. spec.md and backend-plan.md are resolved here but neither is
|
|
151
|
+
# a generation input — see the header for what each is actually used for.
|
|
71
152
|
- id: feature_paths
|
|
72
153
|
action: execute_script
|
|
73
154
|
on_error: continue # never crash raw on 'no feature set' — the gate below explains it
|
|
74
155
|
script: { ref: actions }
|
|
75
156
|
subcommand: feature-paths
|
|
76
157
|
outputs:
|
|
77
|
-
- { name: feature_dir,
|
|
78
|
-
- { name:
|
|
79
|
-
- { name:
|
|
80
|
-
|
|
158
|
+
- { name: feature_dir, from: "$.feature_dir" }
|
|
159
|
+
- { name: spec_file, from: "$.spec_file" }
|
|
160
|
+
- { name: be_plan, from: "$.backend_plan_file" }
|
|
161
|
+
- { name: be_plan_exists, from: "$.backend_plan_exists" }
|
|
81
162
|
- id: require_feature
|
|
82
163
|
action: verify_context
|
|
83
164
|
require_non_empty: feature_dir
|
|
84
165
|
on_empty: stop
|
|
85
166
|
on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the aggregate, e.g. Order), then re-run implement."
|
|
86
167
|
|
|
87
|
-
-
|
|
168
|
+
# 1b. THE LEDGER FENCE. Generation is design-only by contract, but this run reads the
|
|
169
|
+
# spec twice all the same: stale_csv below is computed against the ledger, and the
|
|
170
|
+
# acceptance pass at the end reads spec.md as the wording the code is judged
|
|
171
|
+
# against. Both are worthless if the version does not name the contents.
|
|
172
|
+
- id: spec_state_guard
|
|
173
|
+
action: include
|
|
174
|
+
module: .appweaver/orchestrator/modules/spec-state-gate.orch.yaml
|
|
175
|
+
with: { command: "/appweaver implement" }
|
|
176
|
+
|
|
177
|
+
# 2. WHICH STORY. Same resolution story-plan uses: an explicit id in the input wins,
|
|
178
|
+
# anything else (nothing named, an unknown id, several) becomes a pick from the open
|
|
179
|
+
# backlog. One scan returns every fact the gates below read.
|
|
180
|
+
- id: story_scan
|
|
181
|
+
action: execute_script
|
|
182
|
+
script: { ref: actions }
|
|
183
|
+
subcommand: story-plan-check
|
|
184
|
+
args: ["--feature-dir", "{{feature_dir}}", "--input", "{{USER_INPUT}}"]
|
|
185
|
+
outputs:
|
|
186
|
+
- { name: any_open, from: "$.any_open" }
|
|
187
|
+
- { name: open_ids, from: "$.open_ids" }
|
|
188
|
+
- { name: digest, from: "$.digest" }
|
|
189
|
+
- { name: resolved, from: "$.resolved" }
|
|
190
|
+
- { name: needs_pick, from: "$.needs_pick" }
|
|
191
|
+
- { name: pick_reason, from: "$.pick_reason" }
|
|
192
|
+
- { name: story, from: "$.story" }
|
|
193
|
+
- { name: story_path, from: "$.story_path" }
|
|
194
|
+
- { name: story_open, from: "$.story_open" }
|
|
195
|
+
- { name: status_msg, from: "$.status_msg" }
|
|
196
|
+
- { name: story_title, from: "$.story_title" }
|
|
197
|
+
- { name: exercises_csv, from: "$.exercises_csv" }
|
|
198
|
+
- { name: constraints_csv, from: "$.constraints_csv" }
|
|
199
|
+
- { name: current_version, from: "$.current_version" }
|
|
200
|
+
- { name: stale_csv, from: "$.stale_csv" }
|
|
201
|
+
- { name: stale_note, from: "$.stale_note" }
|
|
202
|
+
- { name: story_dir, from: "$.story_dir" }
|
|
203
|
+
- { name: adr_path, from: "$.adr_path" }
|
|
204
|
+
- { name: if_path, from: "$.if_path" }
|
|
205
|
+
- { name: design_doc, from: "$.be_doc_path" }
|
|
206
|
+
- { name: design_exists, from: "$.be_doc_exists" }
|
|
207
|
+
- { name: design_pass, from: "$.be_last_pass" }
|
|
208
|
+
- { name: design_spec_version, from: "$.be_doc_spec_version" }
|
|
209
|
+
- { name: design_stale, from: "$.be_doc_stale" }
|
|
210
|
+
- { name: pass_plan_hash, from: "$.be_pass_plan_hash" }
|
|
211
|
+
- id: require_open_backlog
|
|
212
|
+
action: verify_context
|
|
213
|
+
require_true: any_open
|
|
214
|
+
on_false: stop
|
|
215
|
+
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)."
|
|
216
|
+
- id: ask_story
|
|
217
|
+
condition: "{{needs_pick}} == true"
|
|
218
|
+
action: ask
|
|
219
|
+
prompt: |
|
|
220
|
+
Which story is this implementation for? ({{pick_reason}})
|
|
221
|
+
|
|
222
|
+
{{digest}}
|
|
223
|
+
options_from: "{{open_ids}}"
|
|
224
|
+
answer_key: picked_story
|
|
225
|
+
answer_var: picked_story
|
|
226
|
+
- id: story_pick_check
|
|
227
|
+
condition: "{{needs_pick}} == true"
|
|
228
|
+
action: execute_script
|
|
229
|
+
script: { ref: actions }
|
|
230
|
+
subcommand: story-plan-check
|
|
231
|
+
args: ["--feature-dir", "{{feature_dir}}", "--story", "{{picked_story}}"]
|
|
232
|
+
outputs:
|
|
233
|
+
- { name: resolved, from: "$.resolved" }
|
|
234
|
+
- { name: story, from: "$.story" }
|
|
235
|
+
- { name: story_path, from: "$.story_path" }
|
|
236
|
+
- { name: story_open, from: "$.story_open" }
|
|
237
|
+
- { name: status_msg, from: "$.status_msg" }
|
|
238
|
+
- { name: story_title, from: "$.story_title" }
|
|
239
|
+
- { name: exercises_csv, from: "$.exercises_csv" }
|
|
240
|
+
- { name: constraints_csv, from: "$.constraints_csv" }
|
|
241
|
+
- { name: stale_csv, from: "$.stale_csv" }
|
|
242
|
+
- { name: stale_note, from: "$.stale_note" }
|
|
243
|
+
- { name: story_dir, from: "$.story_dir" }
|
|
244
|
+
- { name: design_doc, from: "$.be_doc_path" }
|
|
245
|
+
- { name: design_exists, from: "$.be_doc_exists" }
|
|
246
|
+
- { name: design_pass, from: "$.be_last_pass" }
|
|
247
|
+
- { name: design_spec_version, from: "$.be_doc_spec_version" }
|
|
248
|
+
- { name: design_stale, from: "$.be_doc_stale" }
|
|
249
|
+
- { name: pass_plan_hash, from: "$.be_pass_plan_hash" }
|
|
250
|
+
- id: require_story
|
|
251
|
+
action: verify_context
|
|
252
|
+
require_true: resolved
|
|
253
|
+
on_false: stop
|
|
254
|
+
on_false_message: |
|
|
255
|
+
Could not resolve a story from '{{picked_story}}'. Open stories:
|
|
256
|
+
{{digest}}
|
|
257
|
+
Re-run implement naming one of them.
|
|
258
|
+
- id: require_open_story
|
|
259
|
+
action: verify_context
|
|
260
|
+
require_true: story_open
|
|
261
|
+
on_false: stop
|
|
262
|
+
on_false_message: "{{status_msg}}"
|
|
263
|
+
|
|
264
|
+
# 3. THE STORY MUST HAVE A SERVICE DESIGN. This phase generates from a design pass and
|
|
265
|
+
# from nothing else, so an undesigned story has no input — and the fix is one
|
|
266
|
+
# command, not a judgement call.
|
|
267
|
+
- id: require_design
|
|
88
268
|
action: verify_context
|
|
89
269
|
require_true: design_exists
|
|
90
270
|
on_false: stop
|
|
91
|
-
on_false_message: "
|
|
271
|
+
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."
|
|
272
|
+
|
|
273
|
+
# 3a. A design behind the spec is ANNOUNCED, not refused. The story is legitimately in
|
|
274
|
+
# flight and the spec may have moved on ids it does not carry; stale_csv below is
|
|
275
|
+
# the sharper signal, because it names only the ids THIS story actually carries.
|
|
276
|
+
- id: design_stale_notice
|
|
277
|
+
condition: "{{design_stale}} == true"
|
|
278
|
+
action: verify_context
|
|
279
|
+
require_value: design_stale
|
|
280
|
+
equals: "false"
|
|
281
|
+
on_mismatch: continue
|
|
282
|
+
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."
|
|
283
|
+
# stale_csv is a STRING, so require_value/equals compares cleanly and needs no
|
|
284
|
+
# condition. design_stale above is a JSON boolean, which require_value stringifies as
|
|
285
|
+
# 'True' — it can never equal 'false', so that one is gated by `condition:` and the
|
|
286
|
+
# value gate is only the vehicle that prints (law 43).
|
|
287
|
+
- id: story_stale_notice
|
|
288
|
+
action: verify_context
|
|
289
|
+
require_value: stale_csv
|
|
290
|
+
equals: ""
|
|
291
|
+
on_mismatch: continue
|
|
292
|
+
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."
|
|
293
|
+
|
|
294
|
+
# ═══════ 3a-bis. LAW 40, THE LAST HOP — the plan this pass was designed against ═══════
|
|
295
|
+
# THREE HASHES, not two. story-plan proved the feature plan was certified in the shape it
|
|
296
|
+
# was in when this pass was designed, and story-plan-doc stamped that certified hash into
|
|
297
|
+
# the pass header. But a pass is written once and read forever: comparing only the CURRENT
|
|
298
|
+
# plan to the CURRENT verdict would let a pass designed against plan H1 still generate
|
|
299
|
+
# after the plan was edited to H2 and re-gated — which is precisely the case law 40 names,
|
|
300
|
+
# one level below where it was being checked.
|
|
301
|
+
# pass hash what this pass was designed against (the story pass header)
|
|
302
|
+
# verdict hash what analyze-design last certified (the gate verdict)
|
|
303
|
+
# A MISMATCH IS A STOP, at the same severity as never-gated: continuing ships code built
|
|
304
|
+
# to an architecture nobody approved. Per lane, and only for the lane this command builds.
|
|
305
|
+
- id: gate_verdict
|
|
306
|
+
action: execute_script
|
|
307
|
+
on_error: continue
|
|
308
|
+
script: { ref: actions }
|
|
309
|
+
subcommand: gate-read
|
|
310
|
+
args: ["--feature-dir", "{{feature_dir}}", "--gate", "analyze-design"]
|
|
311
|
+
outputs:
|
|
312
|
+
- { name: gate_status, from: "$.status" }
|
|
313
|
+
- { name: gate_certified, from: "$.certified" }
|
|
314
|
+
- { name: gate_aborted, from: "$.aborted" }
|
|
315
|
+
- { name: gate_gap_refs, from: "$.gap_refs_csv" }
|
|
316
|
+
- { name: gate_report, from: "$.report" }
|
|
317
|
+
- { name: gate_lane_sha, from: "$.backend_sha256" }
|
|
318
|
+
|
|
319
|
+
# Three reasons, three routes; ordered sets, last match wins. Aborted is last because it
|
|
320
|
+
# also carries status "fail" — the red gate_result_open writes BEFORE any analysis — and
|
|
321
|
+
# sending someone to read gaps that were never found is the one wrong answer here.
|
|
322
|
+
- id: gate_msg_never
|
|
323
|
+
action: set
|
|
324
|
+
vars: { gate_msg: "The feature plans for this feature have never been certified: there is no analyze-design verdict beside them. Generation builds from a story pass that sits under an unapproved architecture. Run /appweaver analyze-design, then /appweaver story-plan {{story}}, then re-run /appweaver implement." }
|
|
325
|
+
- id: gate_msg_red
|
|
326
|
+
condition: "{{gate_status}} == fail"
|
|
327
|
+
action: set
|
|
328
|
+
vars: { gate_msg: "The feature plans do NOT conform to the spec: analyze-design's last verdict is FAIL on {{gate_gap_refs}} (report: {{gate_report}}). Close those gaps before generating code beneath them, then re-run /appweaver implement." }
|
|
329
|
+
- id: gate_msg_aborted
|
|
330
|
+
condition: "{{gate_aborted}} == true"
|
|
331
|
+
action: set
|
|
332
|
+
vars: { gate_msg: "The last analyze-design run STARTED and never reached a verdict — the red on disk is the one it writes before any analysis, so nothing was found and there are no gaps to read. The plans are UNCERTIFIED, not rejected. Run /appweaver analyze-design to completion, then re-run /appweaver implement." }
|
|
333
|
+
|
|
334
|
+
- id: gate_certified_guard
|
|
335
|
+
action: verify_context
|
|
336
|
+
require_true: gate_certified
|
|
337
|
+
on_false: stop
|
|
338
|
+
on_false_message: "{{gate_msg}}"
|
|
339
|
+
|
|
340
|
+
# An EMPTY pass hash fails this, and that is correct: a pass written before the hash was
|
|
341
|
+
# stamped cannot say which plan shape it was designed against, so nothing can prove it was
|
|
342
|
+
# designed against the approved one. The route is the same either way — re-design.
|
|
343
|
+
- id: pass_plan_shape_guard
|
|
344
|
+
action: verify_context
|
|
345
|
+
require_value: pass_plan_hash
|
|
346
|
+
equals: "{{gate_lane_sha}}"
|
|
347
|
+
on_mismatch: stop
|
|
348
|
+
on_mismatch_message: "Design pass {{design_pass}} for story {{story}} was designed against a backend plan that is NOT the one analyze-design currently certifies — the plan has been edited and re-gated since, or this pass predates plan-hash stamping and cannot say what it was designed against. Generating from it would build to an architecture nobody approved for this shape of the plan. Re-run /appweaver story-plan {{story}} to design against the currently certified plan, then re-run /appweaver implement."
|
|
92
349
|
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
#
|
|
350
|
+
# 4. THE SERVICE LANE and its source path. This phase writes code, and the lane's root
|
|
351
|
+
# is the only subtree it may write into — so this runs before the project gate,
|
|
352
|
+
# which searches that subtree.
|
|
96
353
|
- id: lane_gate
|
|
97
354
|
action: include
|
|
98
355
|
module: .appweaver/orchestrator/modules/lane-gate.orch.yaml
|
|
99
356
|
with: { require: backend, command: "/appweaver implement" }
|
|
100
357
|
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
#
|
|
106
|
-
#
|
|
107
|
-
#
|
|
108
|
-
#
|
|
109
|
-
#
|
|
358
|
+
# 5. Require the PROJECT — a scaffolded, compiling tree. Implement GENERATES into it;
|
|
359
|
+
# with no project there is nowhere to place the files. Deterministic structure gate:
|
|
360
|
+
# searches the LANE'S OWN subtree for an ecosystem manifest, falling back to the
|
|
361
|
+
# whole repo for the layout that keeps its manifest beside the lane root rather than
|
|
362
|
+
# inside it. How the project got there is not asked.
|
|
363
|
+
# THE FALLBACK IS DELIBERATE AND IT NEEDED A DISCRIMINATOR. Searching the whole repo
|
|
364
|
+
# when the lane's own subtree has no manifest is right — a .NET .sln legitimately sits
|
|
365
|
+
# ABOVE the src/ it builds — but it answered with whatever manifest it found first, so
|
|
366
|
+
# in a two-lane repo where only the frontend is scaffolded, the SERVICE lane's gate
|
|
367
|
+
# passed on the frontend's package.json. Which is exactly the failure implement-ui's
|
|
368
|
+
# `project_scope == lane` rule was written to prevent, one lane over.
|
|
369
|
+
# `--ecosystem` is the fix, and NOT implement-ui's rule: this passes the value
|
|
370
|
+
# lane-gate has already bound from stacks.json (that include runs above), so the repo
|
|
371
|
+
# -scope hit is accepted only when the manifest is one this lane's ecosystem knows.
|
|
372
|
+
# Nothing here re-derives it — a second guess is a second thing to keep in step.
|
|
110
373
|
- id: detect_foundation
|
|
111
374
|
action: execute_script
|
|
112
375
|
script: { ref: actions }
|
|
113
376
|
subcommand: detect-project
|
|
114
|
-
args: ["--root", "{{lane_source_path}}"]
|
|
377
|
+
args: ["--root", "{{lane_source_path}}", "--ecosystem", "{{lane_ecosystem}}"]
|
|
115
378
|
outputs:
|
|
116
379
|
- { name: foundation_present, from: "$.scaffolded" }
|
|
117
380
|
- { name: project_markers, from: "$.markers" }
|
|
118
381
|
- { name: project_scope, from: "$.scope" }
|
|
119
|
-
|
|
382
|
+
- { name: ecosystem_match, from: "$.ecosystem_match" }
|
|
120
383
|
- id: foundation_gate
|
|
121
384
|
action: verify_context
|
|
122
385
|
require_true: foundation_present
|
|
123
386
|
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
|
|
387
|
+
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."
|
|
388
|
+
|
|
389
|
+
# 5b. THE GATE PASSED ON SOMEBODY ELSE'S PROJECT. A repo-scope hit whose manifests are
|
|
390
|
+
# none of this lane's ecosystem means the search fell back past the lane root and
|
|
391
|
+
# found the OTHER lane's project. STOPS rather than warns: the next thing this run
|
|
392
|
+
# does is generate {{lane_language}} code, and it has just been told there is nowhere
|
|
393
|
+
# of that kind to put it — carrying on writes a service tree inside a frontend, or
|
|
394
|
+
# into the repo root. Scoped to the fallback, so the deliberate manifest-above-the-lane
|
|
395
|
+
# layout (a .sln over src/) still passes: that hit IS this lane's ecosystem.
|
|
396
|
+
- id: foundation_ecosystem_gate
|
|
397
|
+
condition: "{{project_scope}} == repo"
|
|
398
|
+
action: verify_context
|
|
399
|
+
require_true: ecosystem_match
|
|
400
|
+
on_false: stop
|
|
401
|
+
on_false_message: "The {{lane_id}} lane's source path `{{lane_source_path}}` holds no project, and the repo-wide fallback found only manifests that are not {{lane_ecosystem}}: {{project_markers}}. That is the OTHER lane's project — this gate passing on it would send {{lane_language}} code into a tree built for something else. Scaffold this lane: /appweaver foundation <FoundationName> <AppName> where a bundle ships for {{lane_profile}}, or create the project with the stack's own tooling under `{{lane_source_path}}`. (A manifest deliberately kept ABOVE the lane root — a .sln over src/ — is accepted, because it is this lane's ecosystem.)"
|
|
402
|
+
|
|
403
|
+
# 6. WHAT ALREADY EXISTS, from the feature's OKF concept, split by lane. This is what
|
|
404
|
+
# makes the run incremental: the pass names the files it changes, and this names the
|
|
405
|
+
# files that are there to change. Advisory — the first story of a greenfield feature
|
|
406
|
+
# has no concept and generates into a clean tree.
|
|
407
|
+
- id: coverage
|
|
408
|
+
action: execute_script
|
|
409
|
+
on_error: continue
|
|
410
|
+
script: { ref: xref }
|
|
411
|
+
subcommand: concept-coverage
|
|
412
|
+
args: ["--feature-dir", "{{feature_dir}}", "--ids", "{{exercises_csv}}"]
|
|
413
|
+
outputs:
|
|
414
|
+
- { name: okf_concept, from: "$.concept" }
|
|
415
|
+
- { name: coverage_digest, from: "$.digest" }
|
|
416
|
+
- { name: be_source_csv, from: "$.backend_csv" }
|
|
417
|
+
- { name: be_source_count, from: "$.backend_count" }
|
|
418
|
+
- { name: fe_source_count, from: "$.frontend_count" }
|
|
125
419
|
|
|
126
|
-
#
|
|
420
|
+
# 7. Pre-execution hooks.
|
|
127
421
|
- id: resolve_pre_hooks
|
|
128
422
|
action: execute_script
|
|
129
423
|
script: { ref: actions }
|
|
@@ -135,74 +429,115 @@ workflow:
|
|
|
135
429
|
action: dispatch_hooks
|
|
136
430
|
hooks_from_variable: before_hooks
|
|
137
431
|
|
|
138
|
-
#
|
|
139
|
-
#
|
|
140
|
-
#
|
|
432
|
+
# ═══════════════════ GENERATION — one story's slice, from one pass ═══════════
|
|
433
|
+
# The pass constrains WHAT to build; the constitution, the lane and the loaded
|
|
434
|
+
# standards fix HOW. spec.md is deliberately absent: the design is the single source
|
|
435
|
+
# here, and the acceptance check below is where the spec gets its say.
|
|
141
436
|
- id: ctx_generate
|
|
142
437
|
action: build_context
|
|
438
|
+
vars:
|
|
439
|
+
story_id: "{{story}}"
|
|
440
|
+
story_title: "{{story_title}}"
|
|
441
|
+
exercises_csv: "{{exercises_csv}}"
|
|
442
|
+
constraints_csv: "{{constraints_csv}}"
|
|
443
|
+
pass_number: "{{design_pass}}"
|
|
444
|
+
coverage_digest: "{{coverage_digest}}"
|
|
445
|
+
source_files_csv: "{{be_source_csv}}"
|
|
446
|
+
source_file_count: "{{be_source_count}}"
|
|
447
|
+
stale_note: "{{stale_note}}"
|
|
143
448
|
context:
|
|
144
|
-
- { path: "{{design_doc}}" }
|
|
449
|
+
- { path: "{{design_doc}}" } # the story's SERVICE design — the LATEST pass is the input
|
|
450
|
+
- { path: "{{story_path}}" } # the story's acceptance criteria — what "done" means
|
|
451
|
+
- { path: "{{if_path}}", optional: true } # the PUBLISHED surface this code must match
|
|
452
|
+
- { path: "{{adr_path}}", optional: true } # ratified decisions — binding unless a pass supersedes them
|
|
145
453
|
- { path: .appweaver/memory/constitution.technical.md, optional: true } # technology base ONLY — no domain
|
|
146
454
|
- id: generate
|
|
147
455
|
action: agent_generate
|
|
148
|
-
tools: "Read,Write,Edit"
|
|
456
|
+
tools: "Read,Write,Edit" # the existing code arrives as {{source_files_csv}} paths, not inlined
|
|
149
457
|
permission_mode: acceptEdits
|
|
150
458
|
task: |
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
459
|
+
Implement story {{story_id}} — {{story_title}} — in the service lane, from
|
|
460
|
+
"## Design pass {{pass_number}}" of the story design in context. Guidance: {{USER_INPUT}}
|
|
461
|
+
|
|
462
|
+
THAT PASS IS THE ONLY SOURCE OF WHAT TO BUILD. It is the LAST pass in the
|
|
463
|
+
document; every earlier pass is superseded history, and any of them may carry
|
|
464
|
+
hand edits that were deliberate. Read the last one and build exactly it. Do NOT
|
|
465
|
+
consult or infer from spec.md — it is not in context on purpose: the design
|
|
466
|
+
already went through the gates that conform it, and generating from the spec
|
|
467
|
+
routes around all of them.
|
|
468
|
+
|
|
469
|
+
THIS IS ONE SLICE OF AN EXISTING FEATURE, NOT THE WHOLE FEATURE.
|
|
470
|
+
- The pass's "### Changes" section names, file by file, what to create and what
|
|
471
|
+
to modify. Create ONLY the files it names as new; modify the named existing
|
|
472
|
+
files IN PLACE. Every other file in this feature belongs to work that is
|
|
473
|
+
already done — do not regenerate, reformat or "tidy" it.
|
|
474
|
+
- The story delivers {{exercises_csv}} and must satisfy {{constraints_csv}} — the
|
|
475
|
+
complete set of bounds the spec declares for this aggregate, stamped into the
|
|
476
|
+
story's frontmatter by the kit rather than transcribed by anyone. Satisfy the
|
|
477
|
+
CN-, DC- and IC- among them. IGNORE the UX-: those are what the UI lane realises,
|
|
478
|
+
and a service-side obligation is stated as a CN- by design. Anything outside those
|
|
479
|
+
ids is another story's work, even if you can see it is
|
|
480
|
+
missing. If the slice genuinely cannot be built without touching shared
|
|
481
|
+
structure, make the smallest change that works and SAY SO in your summary.
|
|
482
|
+
- The code that already exists for this feature, per lane, is below. The service
|
|
483
|
+
files ({{source_file_count}} on disk) are yours to read before you edit; the UI
|
|
484
|
+
list is there so you do not change a surface that already has a client.
|
|
485
|
+
|
|
486
|
+
{{coverage_digest}}
|
|
487
|
+
|
|
488
|
+
{{source_files_csv}}
|
|
489
|
+
|
|
490
|
+
BIND THE STACK: this lane is `{{lane_id}}` — {{lane_title}}, language
|
|
491
|
+
{{lane_language}} ({{lane_ecosystem}}), declared in .appweaver/stacks.json and
|
|
492
|
+
compiled with `{{lane_build}}`, tested with `{{lane_test}}`. Generate in THAT
|
|
493
|
+
language and framework and no other, using its idioms, its project layout and its
|
|
494
|
+
test framework. Take persistence, messaging and the remaining technology choices
|
|
495
|
+
from the constitution's technology base + the loaded standards; the design is
|
|
496
|
+
technology-agnostic, so do not re-decide anything already fixed there. If the
|
|
497
|
+
loaded standards describe a DIFFERENT stack than the one above, the lane wins on
|
|
498
|
+
language and layout — follow the standards' INTENT and STOP to surface the
|
|
499
|
+
mismatch in your summary.
|
|
500
|
+
|
|
501
|
+
WRITE ONLY UNDER `{{lane_source_path}}` — the source path the `{{lane_id}}` lane
|
|
502
|
+
declares. That subtree is this lane's; a file outside it belongs to another lane
|
|
503
|
+
or to no lane at all. Do not create a parallel source tree because the layout
|
|
504
|
+
inside it is unfamiliar; if the pass needs a place this subtree has no home for,
|
|
505
|
+
STOP and surface it.
|
|
506
|
+
|
|
156
507
|
Rules:
|
|
157
|
-
- THE
|
|
158
|
-
standards
|
|
159
|
-
terminology
|
|
160
|
-
|
|
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.
|
|
508
|
+
- THE PASS IS THE ONLY DOMAIN INPUT. You have it, the story, and framework/code
|
|
509
|
+
standards — and NO domain rule files. Introduce NO domain rule, identifier,
|
|
510
|
+
terminology or behaviour the pass does not state. If a domain specific seems
|
|
511
|
+
missing, STOP and surface it; never invent it from memory.
|
|
177
512
|
- Follow the project's folder/layer convention + the loaded coding-standards,
|
|
178
|
-
dependency-policy and testing standards for HOW to structure, name
|
|
179
|
-
files WITHIN that subtree — the
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
- Author a paired test for every
|
|
196
|
-
|
|
197
|
-
(
|
|
198
|
-
|
|
199
|
-
NOT exposed on the public surface
|
|
200
|
-
- Do NOT add behaviour the
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
-
|
|
205
|
-
|
|
513
|
+
dependency-policy and testing standards for HOW to structure, name and place
|
|
514
|
+
files WITHIN that subtree — the pass says WHAT to build; these say HOW.
|
|
515
|
+
- Realise the pass's "### Approach" and "### Changes" exactly, changing nothing
|
|
516
|
+
about WHAT they specify, and preserve every ubiquitous-language name verbatim.
|
|
517
|
+
- INTERFACES: where the pass's "### Interfaces" describes a published surface,
|
|
518
|
+
the entry in interfaces.md (in context) is the contract — match its route,
|
|
519
|
+
shape and semantics. Where the log and the pass disagree, the pass is the
|
|
520
|
+
newer word and the log entry it supersedes is named in it; if neither says
|
|
521
|
+
which, STOP and surface it rather than picking.
|
|
522
|
+
- COMPONENTS: where the pass depends on a framework component (e.g.
|
|
523
|
+
`IEmailService`, `IJobScheduler`), code against that component's ABSTRACTION /
|
|
524
|
+
port as the pass declares — do NOT hand-roll it. Installing the component into
|
|
525
|
+
the project (packages, provider wiring, config) is a SEPARATE step:
|
|
526
|
+
/appweaver add-component <Component>. If the port is not present yet, code to
|
|
527
|
+
it and note add-component as the prerequisite.
|
|
528
|
+
- ADRs are DECIDED — follow them. The pass may propose a superseding one; a
|
|
529
|
+
proposal is not yet ratified, so follow the pass and change nothing else.
|
|
530
|
+
- Author a paired test for every acceptance criterion in the story and every
|
|
531
|
+
behaviour the pass's "### Tests" section names, and cite the requirement id
|
|
532
|
+
({{exercises_csv}}) each test exists for. Include the constraint tests the
|
|
533
|
+
pass names ({{constraints_csv}}) — e.g. an audit record is emitted, a
|
|
534
|
+
restricted field is NOT exposed on the public surface.
|
|
535
|
+
- Do NOT add behaviour the pass does not state, and do NOT omit any it does. If
|
|
536
|
+
the pass looks wrong or incomplete, STOP and surface it — it is fixed by
|
|
537
|
+
re-running /appweaver story-plan, never compensated for here from the spec or
|
|
538
|
+
from memory.
|
|
539
|
+
- This feature OWNS its files; never regenerate a file another feature owns.
|
|
540
|
+
{{stale_note}}
|
|
206
541
|
outputs:
|
|
207
542
|
- { name: files, from: "$.files" }
|
|
208
543
|
|
|
@@ -210,63 +545,153 @@ workflow:
|
|
|
210
545
|
action: verify_context
|
|
211
546
|
require_non_empty: files
|
|
212
547
|
on_empty: stop
|
|
213
|
-
on_empty_message: "No files generated.
|
|
214
|
-
|
|
215
|
-
#
|
|
216
|
-
#
|
|
217
|
-
#
|
|
218
|
-
#
|
|
219
|
-
#
|
|
220
|
-
#
|
|
221
|
-
# author the concept's Overview prose (3c). This is enrichment downstream of a
|
|
222
|
-
# successful generate — it never re-decides WHAT was built.
|
|
548
|
+
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."
|
|
549
|
+
|
|
550
|
+
# 8. OKF — MERGE, never replace. This run wrote ONE STORY's files; the feature's
|
|
551
|
+
# other stories wrote the rest, and a replace would erase them the moment a second
|
|
552
|
+
# story implements. --lane backend puts them in `source_files`, the service lane's
|
|
553
|
+
# key, leaving `ui_source_files` for implement-ui.
|
|
554
|
+
# Known limit: a file the slice DELETED stays in the list; concept-coverage reports
|
|
555
|
+
# it as declared-but-missing drift rather than silently carrying it as real.
|
|
223
556
|
- id: xref_concept
|
|
224
557
|
action: execute_script
|
|
225
558
|
script: { ref: xref }
|
|
226
559
|
subcommand: concept-upsert
|
|
227
|
-
|
|
560
|
+
# --ids + --slice record WHICH REQUIREMENT this run's files realise, as Requirement
|
|
561
|
+
# Anchors rows in the concept. Both halves were already here and thrown away: the ids
|
|
562
|
+
# the story exercises, and the files just generated. Only `adopt` writes a
|
|
563
|
+
# source-map.md, so without this every id on a greenfield feature came back unanchored
|
|
564
|
+
# at analyze-slice and the trace pass had to read the whole tree to find out what
|
|
565
|
+
# implements what. Reconcile-on-write, keyed by slice + lane.
|
|
566
|
+
args: ["--feature-dir", "{{feature_dir}}", "--source-files", "{{files}}", "--merge", "--lane", "backend", "--origin", "greenfield", "--ids", "{{exercises_csv}}", "--slice", "{{story}}"]
|
|
228
567
|
outputs:
|
|
229
|
-
- { name: okf_concept,
|
|
568
|
+
- { name: okf_concept, from: "$.concept" }
|
|
569
|
+
- { name: okf_anchors, from: "$.anchors_written" }
|
|
230
570
|
|
|
571
|
+
# 8a. Cross-slice edges are a FEATURE property, not a story's: which sibling
|
|
572
|
+
# aggregates this one references is stated by the feature plan's domain model,
|
|
573
|
+
# and no single slice can answer it. So this reads backend-plan.md — the one
|
|
574
|
+
# derived fact it takes from the plan — and reconciles on write, so a dropped
|
|
575
|
+
# dependency self-heals out of the used aggregate's Cross-References. Skipped
|
|
576
|
+
# with no plan on disk: there is then nothing to derive edges from.
|
|
231
577
|
- id: xref_record
|
|
578
|
+
condition: "{{be_plan_exists}} == true"
|
|
232
579
|
action: execute_script
|
|
580
|
+
on_error: continue
|
|
233
581
|
script: { ref: xref }
|
|
234
582
|
subcommand: record
|
|
235
|
-
args: ["--feature-dir", "{{feature_dir}}", "--design", "{{
|
|
583
|
+
args: ["--feature-dir", "{{feature_dir}}", "--design", "{{be_plan}}", "--origin", "greenfield"]
|
|
236
584
|
outputs:
|
|
237
585
|
- { name: xref_wrote, from: "$.wrote" }
|
|
238
586
|
- { name: xref_edge_count, from: "$.edge_count" }
|
|
239
587
|
|
|
240
|
-
#
|
|
241
|
-
#
|
|
242
|
-
#
|
|
243
|
-
#
|
|
588
|
+
# 8b. The concept's Overview — the one part a deterministic script cannot produce.
|
|
589
|
+
# It describes the AGGREGATE, which is the feature plan's subject: distilling it
|
|
590
|
+
# from one story's slice would name whatever that slice happened to touch. The
|
|
591
|
+
# model authors prose only; concept-overview splices it into `## Overview` alone,
|
|
592
|
+
# so the Cross-References rows other slices own are structurally out of reach.
|
|
593
|
+
# build_context, not a step-level `context:`: the engine REPLACES the envelope on
|
|
594
|
+
# build_context and only ADDS to it on a step-level list, so naming the plan alone
|
|
595
|
+
# there would have left generation's whole payload — this story's slice included —
|
|
596
|
+
# underneath a prompt that says "describe the whole aggregate, not one story".
|
|
597
|
+
- id: ctx_overview
|
|
598
|
+
condition: "{{be_plan_exists}} == true"
|
|
599
|
+
action: build_context
|
|
600
|
+
context:
|
|
601
|
+
- { path: "{{be_plan}}" }
|
|
244
602
|
- id: okf_overview
|
|
603
|
+
condition: "{{be_plan_exists}} == true"
|
|
245
604
|
action: agent_query
|
|
246
|
-
context: ["{{design_doc}}"]
|
|
247
605
|
include_standards: false
|
|
248
606
|
schema: { overview: "<2-4 sentences of prose>" }
|
|
249
607
|
mock: { overview: "" }
|
|
250
608
|
task: |
|
|
251
|
-
Write the OKF knowledge-concept Overview for the aggregate that
|
|
252
|
-
context) describes. 2-4 sentences of PROSE, distilled from §1 Domain Model:
|
|
609
|
+
Write the OKF knowledge-concept Overview for the aggregate that the backend plan
|
|
610
|
+
(in context) describes. 2-4 sentences of PROSE, distilled from §1 Domain Model:
|
|
253
611
|
- name the aggregate ROOT and what the aggregate IS;
|
|
254
|
-
- state the INVARIANTS it guards
|
|
612
|
+
- state the INVARIANTS it guards;
|
|
255
613
|
- state its TRANSACTION BOUNDARY — what it OWNS inside the boundary vs what it
|
|
256
614
|
only REFERENCES by id.
|
|
257
615
|
Plain prose only: no headings, no bullets, no code fences, no file paths, and no
|
|
258
616
|
restating of the dependency edges (those live in the Depends-On / Cross-References
|
|
259
|
-
tables). Describe only what the
|
|
617
|
+
tables). Describe only what the plan states — invent nothing, and describe the
|
|
618
|
+
whole aggregate rather than the one story that happens to be building now.
|
|
260
619
|
outputs:
|
|
261
620
|
- { name: okf_overview, from: "$.overview" }
|
|
262
|
-
|
|
263
621
|
- id: okf_overview_write
|
|
622
|
+
condition: "{{be_plan_exists}} == true"
|
|
264
623
|
action: execute_script
|
|
265
624
|
script: { ref: xref }
|
|
266
625
|
subcommand: concept-overview
|
|
267
626
|
args: ["--feature-dir", "{{feature_dir}}", "--text", "{{okf_overview}}"]
|
|
268
627
|
|
|
269
|
-
#
|
|
628
|
+
# ═══════════════ ACCEPTANCE — the ONE place the spec is read ═════════════════
|
|
629
|
+
# Generation is design-only by contract; this is the check that the design was not
|
|
630
|
+
# the wrong design. READ-ONLY and REPORT-ONLY: it never edits code, never edits the
|
|
631
|
+
# pass, and never stops the run. A gap here means one of two documents is wrong, and
|
|
632
|
+
# which one is a human decision — the message names both routes.
|
|
633
|
+
- id: ctx_accept
|
|
634
|
+
action: build_context
|
|
635
|
+
vars:
|
|
636
|
+
story_id: "{{story}}"
|
|
637
|
+
exercises_csv: "{{exercises_csv}}"
|
|
638
|
+
constraints_csv: "{{constraints_csv}}"
|
|
639
|
+
files_csv: "{{files}}"
|
|
640
|
+
context:
|
|
641
|
+
- { path: "{{story_path}}" } # the acceptance criteria
|
|
642
|
+
- { path: "{{spec_file}}" } # the contract — the ONLY step in this workflow that reads it
|
|
643
|
+
- { path: "{{design_doc}}" } # what was designed
|
|
644
|
+
- id: accept_check
|
|
645
|
+
action: agent_query
|
|
646
|
+
tools: Read # the generated files arrive as paths, not inlined
|
|
647
|
+
include_standards: false
|
|
648
|
+
schema: { ok: "<true|false>", gaps: "<one line per gap, or empty>" }
|
|
649
|
+
mock: { ok: "true", gaps: "" }
|
|
650
|
+
task: |
|
|
651
|
+
Story {{story_id}} was just implemented. Judge ONLY this: does the code that now
|
|
652
|
+
exists satisfy the requirements the story delivers, AS THE SPEC WORDS THEM?
|
|
653
|
+
|
|
654
|
+
In scope: {{exercises_csv}} (delivered) and {{constraints_csv}} (must hold).
|
|
655
|
+
Resolve each id's wording from spec.md in context — that wording, not the design's
|
|
656
|
+
restatement of it, is what the code is measured against.
|
|
657
|
+
|
|
658
|
+
Read the files this run wrote:
|
|
659
|
+
{{files_csv}}
|
|
660
|
+
plus anything they call that you need to follow. Do not modify a single file.
|
|
661
|
+
|
|
662
|
+
Report a gap ONLY where the code fails to deliver an in-scope id as the spec words
|
|
663
|
+
it, or breaks an in-scope constraint. These are NOT gaps, and reporting them is
|
|
664
|
+
noise that hides the real ones:
|
|
665
|
+
- a requirement no story has claimed yet, or one another story delivers;
|
|
666
|
+
- style, naming, structure or test-coverage opinions — other gates own those;
|
|
667
|
+
- anything the design pass deliberately deferred and named under its risks.
|
|
668
|
+
|
|
669
|
+
One line per gap: the id, what the spec requires, and what the code does instead.
|
|
670
|
+
Set ok=false only when you found at least one. If the SPEC and the DESIGN disagree
|
|
671
|
+
about an in-scope id, that IS a gap — say which document you believe is wrong and
|
|
672
|
+
why, because the two routes out of it are different commands.
|
|
673
|
+
outputs:
|
|
674
|
+
- { name: accept_ok, from: "$.ok" }
|
|
675
|
+
- { name: accept_gaps, from: "$.gaps" }
|
|
676
|
+
- id: accept_notice
|
|
677
|
+
condition: "{{accept_ok}} != true"
|
|
678
|
+
action: verify_context
|
|
679
|
+
require_value: accept_ok
|
|
680
|
+
equals: "true"
|
|
681
|
+
on_mismatch: continue
|
|
682
|
+
on_mismatch_message: |
|
|
683
|
+
ACCEPTANCE — story {{story}} does not yet satisfy the spec wording of the ids it delivers:
|
|
684
|
+
|
|
685
|
+
{{accept_gaps}}
|
|
686
|
+
|
|
687
|
+
Nothing was changed to fix this: the code follows design pass {{design_pass}}, so a gap
|
|
688
|
+
here means either the design was wrong or the spec has moved. Route by which it is —
|
|
689
|
+
the design is wrong: /appweaver story-plan {{story}} (a new pass supersedes this one);
|
|
690
|
+
the spec is wrong: /appweaver sync-spec (⌛ not yet built — until it is, fold the change back by hand under .appweaver/SPEC-EDIT-PROTOCOL.md), then sync-stories, then story-plan.
|
|
691
|
+
Run /appweaver build either way — a design that is right and code that does not compile
|
|
692
|
+
is a third thing, and build is where that one surfaces.
|
|
693
|
+
|
|
694
|
+
# 9. Post-execution hooks.
|
|
270
695
|
- id: resolve_post_hooks
|
|
271
696
|
action: execute_script
|
|
272
697
|
script: { ref: actions }
|
|
@@ -279,9 +704,12 @@ workflow:
|
|
|
279
704
|
hooks_from_variable: after_hooks
|
|
280
705
|
|
|
281
706
|
outputs:
|
|
282
|
-
|
|
283
|
-
|
|
707
|
+
story: "{{story}}"
|
|
708
|
+
built_from: "{{design_doc}} — design pass {{design_pass}} (designed against spec {{design_spec_version}}; spec now {{current_version}})"
|
|
709
|
+
requirements: "{{exercises_csv}}"
|
|
284
710
|
files: "{{files}}"
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
711
|
+
lane: "{{lane_id}} → {{lane_source_path}}"
|
|
712
|
+
okf_concept: "{{okf_concept}} (this run's files MERGED into source_files; {{xref_edge_count}} cross-slice edge(s) recorded into sibling aggregates)"
|
|
713
|
+
acceptance: "ok={{accept_ok}} · gaps: {{accept_gaps}}"
|
|
714
|
+
stale_ids: "{{stale_csv}}"
|
|
715
|
+
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: /appweaver close-story — which would ratify its ADRs, flip its interface entries live and archive the passes — is ⌛ NOT BUILT YET, so there is no transition out of in-flight and no last gate before merge."
|