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.
- package/assets/.appweaver/STACK-PROFILES.md +1 -1
- 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 +607 -123
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +625 -66
- package/assets/.appweaver/orchestrator/workflows/aw-spec-implement-ui.orch.yaml +407 -134
- package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +452 -143
- package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +78 -14
- package/assets/.appweaver/orchestrator/workflows/aw-spec-plan-ui.orch.yaml +597 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +688 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-select-feature.orch.yaml +3 -3
- package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +23 -23
- package/assets/.appweaver/orchestrator/workflows/aw-spec-story-plan.orch.yaml +945 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-sync-stories.orch.yaml +1040 -0
- package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +2069 -28
- package/assets/.appweaver/scripts/bash/aw-xref.sh +2 -1
- package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +1973 -13
- package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +2 -1
- package/assets/.appweaver/scripts/python/aw-xref.py +246 -20
- 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 +48 -8
- 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,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
|
-
#
|
|
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
|
+
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
|
-
#
|
|
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).
|
|
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.
|
|
70
|
-
#
|
|
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,
|
|
78
|
-
- { name:
|
|
79
|
-
- { name:
|
|
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
|
-
-
|
|
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: "
|
|
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
|
-
#
|
|
94
|
-
#
|
|
95
|
-
#
|
|
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
|
-
#
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
#
|
|
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
|
|
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
|
-
#
|
|
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
|
-
#
|
|
139
|
-
#
|
|
140
|
-
#
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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", "{{
|
|
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
|
-
#
|
|
241
|
-
#
|
|
242
|
-
#
|
|
243
|
-
#
|
|
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
|
|
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
|
|
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
|
|
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
|
-
#
|
|
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
|
-
|
|
283
|
-
|
|
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
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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."
|