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,52 +1,58 @@
|
|
|
1
1
|
# =============================================================================
|
|
2
|
-
# aw-spec-implement-ui (orchestrator) —
|
|
2
|
+
# aw-spec-implement-ui (orchestrator) — UI generation from ONE STORY'S DESIGN
|
|
3
3
|
#
|
|
4
|
-
# The frontend lane's
|
|
5
|
-
# and paired tests for
|
|
6
|
-
#
|
|
7
|
-
#
|
|
4
|
+
# The frontend lane's executable end of the story loop: GENERATE the screens,
|
|
5
|
+
# components, client state and paired tests for ONE STORY, strictly from the LATEST
|
|
6
|
+
# design pass in that story's own UI design —
|
|
7
|
+
# specs/<F>/stories/<ID>/story-frontend-plan.md. It is the twin of `implement`, one
|
|
8
|
+
# lane over, and keeps the same contract:
|
|
8
9
|
#
|
|
9
|
-
# •
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
# •
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
10
|
+
# • ONE SLICE, NOT THE FEATURE. The pass names the files it changes; this phase edits
|
|
11
|
+
# an existing tree, creating only what the pass says is new, and MERGES its files
|
|
12
|
+
# into the feature's OKF concept rather than replacing the list.
|
|
13
|
+
# • DESIGN-ONLY. spec.md is not a generation input. It is read once, AFTER the code
|
|
14
|
+
# exists, by a read-only acceptance check that reports and never repairs.
|
|
15
|
+
# • THE FRAMEWORK BINDS HERE. The design is technology-agnostic — a "component" is a
|
|
16
|
+
# responsibility, its inputs are field shapes, its states are named. The framework,
|
|
17
|
+
# router, state library, component library and styling approach come from
|
|
18
|
+
# constitution.ui.md + the lane declaration + this command's rule set.
|
|
19
|
+
# • ONE SUBTREE. The lane owns the source path .appweaver/stacks.json declares for it
|
|
20
|
+
# and nothing outside it. A file written elsewhere belongs to the service lane.
|
|
21
|
+
#
|
|
22
|
+
# THE UI LANE CONSUMES THE CONTRACT; IT NEVER DEFINES ONE. interfaces.md is the
|
|
23
|
+
# published surface and the pass binds to it by IF- id. A contract this slice needs that
|
|
24
|
+
# the log does not carry is a DEFECT to surface — never an endpoint to invent, and never
|
|
25
|
+
# a reason to reach into the service lane's code. Where the SAME story has a service
|
|
26
|
+
# design it is in context too, as the newer word on what that surface will be.
|
|
19
27
|
#
|
|
20
28
|
# WHY A SEPARATE COMMAND, NOT A UI BRANCH INSIDE `implement`. The two lanes take their
|
|
21
29
|
# code-generation rules from different map headings and write into disjoint subtrees.
|
|
22
30
|
# Keying the rule maps on `## /aw-spec-implement-ui` gives the lane its own rule set for
|
|
23
31
|
# zero mechanism change; a UI branch inside `implement` would generate frontend code
|
|
24
|
-
# under the service lane's standards.
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
# the lane's own source path — implement-ui GENERATES into that tree, so with no project
|
|
28
|
-
# there is nowhere to place the files. The project gate is deliberately LANE-SCOPED: a
|
|
29
|
-
# manifest found only elsewhere in the repository is the service lane's, and accepting
|
|
30
|
-
# it is how a frontend that was never built reports green.
|
|
32
|
+
# under the service lane's standards. (`story-plan` designs BOTH lanes in one run
|
|
33
|
+
# because everything ahead of its authoring step is shared; here nothing is — the
|
|
34
|
+
# lane, the subtree, the rules and the OKF key all differ.)
|
|
31
35
|
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
#
|
|
36
|
+
# GATES (all loud, all before any write): the frontend lane declared with its source
|
|
37
|
+
# path; a project scaffolded under THAT path specifically — a manifest found only
|
|
38
|
+
# elsewhere is the service lane's, and accepting it is how a frontend that was never
|
|
39
|
+
# built reports green; an active feature; an OPEN story; and that story having a UI
|
|
40
|
+
# design at all. A design behind the current spec, or a story the spec has moved past,
|
|
41
|
+
# is ANNOUNCED, never refused; the route out of both is named.
|
|
35
42
|
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
# Launch: /appweaver implement-ui [strategy] [guidance]
|
|
43
|
+
# Launch: /appweaver implement-ui [<story-id>] [guidance] (asks when ambiguous)
|
|
44
|
+
# Prereq: /appweaver story-plan (the story's UI design) · Next: /appweaver build
|
|
39
45
|
# =============================================================================
|
|
40
46
|
|
|
41
47
|
name: aw-spec-implement-ui
|
|
42
|
-
description: Generate the frontend lane's screens, components, client state and paired tests
|
|
43
|
-
version:
|
|
48
|
+
description: "Generate the frontend lane's screens, components, client state and paired tests for ONE STORY, strictly from the LATEST design pass in stories/<ID>/story-frontend-plan.md, bound to the declared UI 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. Consumes interfaces.md by IF- id and never publishes into it. Incremental by contract: it edits the lane's existing tree and MERGES its files into the feature's OKF concept under ui_source_files, so the service lane's list is untouched."
|
|
49
|
+
version: 2.0.0
|
|
44
50
|
agent_agnostic: true
|
|
45
51
|
dialect: aw-spec.orchestrator/v1
|
|
46
52
|
|
|
47
53
|
inputs:
|
|
48
54
|
user_input:
|
|
49
|
-
description: Optional implementation guidance (
|
|
55
|
+
description: Optional story id and/or implementation guidance (e.g. "Order-001 reuse the shared date picker").
|
|
50
56
|
required: false
|
|
51
57
|
bind: USER_INPUT
|
|
52
58
|
|
|
@@ -60,14 +66,73 @@ scripts:
|
|
|
60
66
|
|
|
61
67
|
# Sticky standards seed = the UNIVERSAL always-on rule only (ai-rules). The lane's
|
|
62
68
|
# FRONTEND code-generation rule files are CONFIGURATION, loaded per-command from
|
|
63
|
-
# guardrails/projects/<project>/tech-rule-bindings.md under `## /aw-spec-implement-ui`
|
|
64
|
-
# the tech-select module. Per the rule-partition this phase loads NO domain rule
|
|
65
|
-
# the domain reached the lane through spec →
|
|
66
|
-
# arrives via constitution.ui.md (referred to
|
|
67
|
-
#
|
|
69
|
+
# guardrails/projects/<project>/tech-rule-bindings.md under `## /aw-spec-implement-ui`
|
|
70
|
+
# by the tech-select module. Per the rule-partition this phase loads NO domain rule
|
|
71
|
+
# file — the domain reached the lane through spec → frontend-plan.md → the story's
|
|
72
|
+
# design pass. The lane's ratified base arrives via constitution.ui.md (referred to
|
|
73
|
+
# directly, as-is — NOT mapped).
|
|
68
74
|
standards:
|
|
69
75
|
- { path: .appweaver/ai-rules.md } # kit-resident: the universal operating rules, every flow
|
|
70
76
|
|
|
77
|
+
vars:
|
|
78
|
+
feature_dir: ""
|
|
79
|
+
spec_file: ""
|
|
80
|
+
# story resolution
|
|
81
|
+
any_open: "false"
|
|
82
|
+
open_ids: "[]"
|
|
83
|
+
digest: ""
|
|
84
|
+
resolved: "false"
|
|
85
|
+
needs_pick: "false"
|
|
86
|
+
pick_reason: ""
|
|
87
|
+
picked_story: ""
|
|
88
|
+
story: ""
|
|
89
|
+
story_path: ""
|
|
90
|
+
story_open: "false"
|
|
91
|
+
status_msg: ""
|
|
92
|
+
story_title: ""
|
|
93
|
+
exercises_csv: ""
|
|
94
|
+
constraints_csv: ""
|
|
95
|
+
current_version: ""
|
|
96
|
+
stale_csv: ""
|
|
97
|
+
stale_note: ""
|
|
98
|
+
story_dir: ""
|
|
99
|
+
adr_path: ""
|
|
100
|
+
if_path: ""
|
|
101
|
+
# the story's UI design (+ the SERVICE design of the same story, for the contract)
|
|
102
|
+
design_doc: ""
|
|
103
|
+
design_exists: "false"
|
|
104
|
+
design_pass: "0"
|
|
105
|
+
design_spec_version: ""
|
|
106
|
+
design_stale: "false"
|
|
107
|
+
# WHICH LANES story-plan designed and which it dropped. Absent on a story designed before
|
|
108
|
+
# the keys existed, which is why the default message below keeps both readings.
|
|
109
|
+
designed_lanes: ""
|
|
110
|
+
dropped_lanes: ""
|
|
111
|
+
lane_keys_present: "false"
|
|
112
|
+
fe_lane_dropped: "false"
|
|
113
|
+
no_design_msg: ""
|
|
114
|
+
# the analyze-design verdict — law 40's evidence. certified defaults FALSE so a probe that
|
|
115
|
+
# could not run fails CLOSED, and the message default carries a route for the same reason.
|
|
116
|
+
pass_plan_hash: ""
|
|
117
|
+
gate_status: ""
|
|
118
|
+
gate_certified: "false"
|
|
119
|
+
gate_aborted: "false"
|
|
120
|
+
gate_gap_refs: ""
|
|
121
|
+
gate_report: ""
|
|
122
|
+
gate_lane_sha: ""
|
|
123
|
+
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."
|
|
124
|
+
service_design_doc: ""
|
|
125
|
+
# the feature's current code, per lane (OKF)
|
|
126
|
+
okf_concept: ""
|
|
127
|
+
coverage_digest: ""
|
|
128
|
+
fe_source_csv: ""
|
|
129
|
+
fe_source_count: "0"
|
|
130
|
+
be_source_count: "0"
|
|
131
|
+
# generation + enrichment
|
|
132
|
+
files: ""
|
|
133
|
+
accept_ok: "true"
|
|
134
|
+
accept_gaps: ""
|
|
135
|
+
|
|
71
136
|
workflow:
|
|
72
137
|
|
|
73
138
|
# 0. FRONTEND code-generation rule files → sticky standards (configuration, not
|
|
@@ -78,45 +143,242 @@ workflow:
|
|
|
78
143
|
module: .appweaver/orchestrator/modules/tech-select.orch.yaml
|
|
79
144
|
with: { command: aw-spec-implement-ui }
|
|
80
145
|
|
|
81
|
-
# 1.
|
|
82
|
-
#
|
|
146
|
+
# 1. The active feature. spec.md is resolved here for the acceptance check ONLY —
|
|
147
|
+
# it is not a generation input.
|
|
83
148
|
- id: feature_paths
|
|
84
149
|
action: execute_script
|
|
85
150
|
on_error: continue # never crash raw on 'no feature set' — the gate below explains it
|
|
86
151
|
script: { ref: actions }
|
|
87
152
|
subcommand: feature-paths
|
|
88
153
|
outputs:
|
|
89
|
-
- { name: feature_dir,
|
|
90
|
-
- { name:
|
|
91
|
-
- { name: ui_design_exists, from: "$.ui_design_exists" }
|
|
92
|
-
|
|
154
|
+
- { name: feature_dir, from: "$.feature_dir" }
|
|
155
|
+
- { name: spec_file, from: "$.spec_file" }
|
|
93
156
|
- id: require_feature
|
|
94
157
|
action: verify_context
|
|
95
158
|
require_non_empty: feature_dir
|
|
96
159
|
on_empty: stop
|
|
97
160
|
on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the aggregate, e.g. Order), then re-run implement-ui."
|
|
98
161
|
|
|
99
|
-
|
|
162
|
+
# 1b. THE LEDGER FENCE — the same one implement carries, for the same two readings:
|
|
163
|
+
# the staleness computation below and the acceptance pass at the end.
|
|
164
|
+
- id: spec_state_guard
|
|
165
|
+
action: include
|
|
166
|
+
module: .appweaver/orchestrator/modules/spec-state-gate.orch.yaml
|
|
167
|
+
with: { command: "/appweaver implement-ui" }
|
|
168
|
+
|
|
169
|
+
# 2. WHICH STORY — the same resolution story-plan and implement use.
|
|
170
|
+
- id: story_scan
|
|
171
|
+
action: execute_script
|
|
172
|
+
script: { ref: actions }
|
|
173
|
+
subcommand: story-plan-check
|
|
174
|
+
args: ["--feature-dir", "{{feature_dir}}", "--input", "{{USER_INPUT}}"]
|
|
175
|
+
outputs:
|
|
176
|
+
- { name: any_open, from: "$.any_open" }
|
|
177
|
+
- { name: open_ids, from: "$.open_ids" }
|
|
178
|
+
- { name: digest, from: "$.digest" }
|
|
179
|
+
- { name: resolved, from: "$.resolved" }
|
|
180
|
+
- { name: needs_pick, from: "$.needs_pick" }
|
|
181
|
+
- { name: pick_reason, from: "$.pick_reason" }
|
|
182
|
+
- { name: story, from: "$.story" }
|
|
183
|
+
- { name: story_path, from: "$.story_path" }
|
|
184
|
+
- { name: story_open, from: "$.story_open" }
|
|
185
|
+
- { name: status_msg, from: "$.status_msg" }
|
|
186
|
+
- { name: story_title, from: "$.story_title" }
|
|
187
|
+
- { name: exercises_csv, from: "$.exercises_csv" }
|
|
188
|
+
- { name: constraints_csv, from: "$.constraints_csv" }
|
|
189
|
+
- { name: current_version, from: "$.current_version" }
|
|
190
|
+
- { name: stale_csv, from: "$.stale_csv" }
|
|
191
|
+
- { name: stale_note, from: "$.stale_note" }
|
|
192
|
+
- { name: story_dir, from: "$.story_dir" }
|
|
193
|
+
- { name: adr_path, from: "$.adr_path" }
|
|
194
|
+
- { name: if_path, from: "$.if_path" }
|
|
195
|
+
- { name: design_doc, from: "$.fe_doc_path" }
|
|
196
|
+
- { name: design_exists, from: "$.fe_doc_exists" }
|
|
197
|
+
- { name: design_pass, from: "$.fe_last_pass" }
|
|
198
|
+
- { name: design_spec_version, from: "$.fe_doc_spec_version" }
|
|
199
|
+
- { name: design_stale, from: "$.fe_doc_stale" }
|
|
200
|
+
- { name: pass_plan_hash, from: "$.fe_pass_plan_hash" }
|
|
201
|
+
- { name: designed_lanes, from: "$.designed_lanes_csv" }
|
|
202
|
+
- { name: dropped_lanes, from: "$.dropped_lanes_csv" }
|
|
203
|
+
- { name: lane_keys_present, from: "$.lane_keys_present" }
|
|
204
|
+
- { name: fe_lane_dropped, from: "$.fe_lane_dropped" }
|
|
205
|
+
- { name: service_design_doc, from: "$.be_doc_path" }
|
|
206
|
+
- id: require_open_backlog
|
|
100
207
|
action: verify_context
|
|
101
|
-
require_true:
|
|
208
|
+
require_true: any_open
|
|
102
209
|
on_false: stop
|
|
103
|
-
on_false_message: "No
|
|
210
|
+
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)."
|
|
211
|
+
- id: ask_story
|
|
212
|
+
condition: "{{needs_pick}} == true"
|
|
213
|
+
action: ask
|
|
214
|
+
prompt: |
|
|
215
|
+
Which story is this UI implementation for? ({{pick_reason}})
|
|
104
216
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
217
|
+
{{digest}}
|
|
218
|
+
options_from: "{{open_ids}}"
|
|
219
|
+
answer_key: picked_story
|
|
220
|
+
answer_var: picked_story
|
|
221
|
+
- id: story_pick_check
|
|
222
|
+
condition: "{{needs_pick}} == true"
|
|
223
|
+
action: execute_script
|
|
224
|
+
script: { ref: actions }
|
|
225
|
+
subcommand: story-plan-check
|
|
226
|
+
args: ["--feature-dir", "{{feature_dir}}", "--story", "{{picked_story}}"]
|
|
227
|
+
outputs:
|
|
228
|
+
- { name: resolved, from: "$.resolved" }
|
|
229
|
+
- { name: story, from: "$.story" }
|
|
230
|
+
- { name: story_path, from: "$.story_path" }
|
|
231
|
+
- { name: story_open, from: "$.story_open" }
|
|
232
|
+
- { name: status_msg, from: "$.status_msg" }
|
|
233
|
+
- { name: story_title, from: "$.story_title" }
|
|
234
|
+
- { name: exercises_csv, from: "$.exercises_csv" }
|
|
235
|
+
- { name: constraints_csv, from: "$.constraints_csv" }
|
|
236
|
+
- { name: stale_csv, from: "$.stale_csv" }
|
|
237
|
+
- { name: stale_note, from: "$.stale_note" }
|
|
238
|
+
- { name: story_dir, from: "$.story_dir" }
|
|
239
|
+
- { name: design_doc, from: "$.fe_doc_path" }
|
|
240
|
+
- { name: design_exists, from: "$.fe_doc_exists" }
|
|
241
|
+
- { name: design_pass, from: "$.fe_last_pass" }
|
|
242
|
+
- { name: design_spec_version, from: "$.fe_doc_spec_version" }
|
|
243
|
+
- { name: design_stale, from: "$.fe_doc_stale" }
|
|
244
|
+
- { name: pass_plan_hash, from: "$.fe_pass_plan_hash" }
|
|
245
|
+
- { name: designed_lanes, from: "$.designed_lanes_csv" }
|
|
246
|
+
- { name: dropped_lanes, from: "$.dropped_lanes_csv" }
|
|
247
|
+
- { name: lane_keys_present, from: "$.lane_keys_present" }
|
|
248
|
+
- { name: fe_lane_dropped, from: "$.fe_lane_dropped" }
|
|
249
|
+
- { name: service_design_doc, from: "$.be_doc_path" }
|
|
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-ui 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 UI DESIGN. A story with a service design and no UI one is
|
|
265
|
+
# not a failure — it is a slice with no user-facing surface, and the message says
|
|
266
|
+
# so rather than implying something is broken.
|
|
267
|
+
# WHICH OF THE TWO READINGS IT IS, where the story says. analyze-design STOPS on a lane
|
|
268
|
+
# whose plan is behind; story-plan DROPS it and designs the other — so one command later
|
|
269
|
+
# "this slice has no user-facing surface" and "the UI lane was out of scope when this was
|
|
270
|
+
# designed" are the same absence on disk, and the message below could only offer both.
|
|
271
|
+
# story-plan now stamps `designed_lanes:` and `dropped_lanes:`, so the answer is recorded.
|
|
272
|
+
# ORDERED SETS, most specific last. The default is the two-reading message, which stays
|
|
273
|
+
# exactly right for a story designed BEFORE those keys existed: it has neither, and a
|
|
274
|
+
# missing key must never be read as "no user-facing surface".
|
|
275
|
+
- id: no_design_msg_default
|
|
276
|
+
action: set
|
|
277
|
+
vars:
|
|
278
|
+
no_design_msg: "Story {{story}} has no UI design — {{story_dir}}/story-frontend-plan.md does not exist, and the story records no designed/dropped lanes (it was designed before the kit stamped them). Either this slice has no user-facing surface (nothing to do here; run /appweaver implement for the service half), or the UI lane was out of scope when it was designed — a frontend lane that is undeclared, or a frontend-plan.md that is missing or behind the spec, drops that lane from a story-plan run and says so. Fix whichever it was and re-run /appweaver story-plan {{story}}."
|
|
279
|
+
- id: no_design_msg_no_surface
|
|
280
|
+
condition: "{{lane_keys_present}} == true"
|
|
281
|
+
action: set
|
|
282
|
+
vars:
|
|
283
|
+
no_design_msg: "Story {{story}} has no UI design and its UI lane was NOT dropped — the story records designed lanes [{{designed_lanes}}] and dropped lanes [{{dropped_lanes}}], so this slice genuinely has no user-facing surface. There is nothing for implement-ui to do here; run /appweaver implement for the service half. If it SHOULD have a UI surface, declare the frontend lane and re-run /appweaver story-plan {{story}}."
|
|
284
|
+
- id: no_design_msg_dropped
|
|
285
|
+
condition: "{{fe_lane_dropped}} == true"
|
|
286
|
+
action: set
|
|
287
|
+
vars:
|
|
288
|
+
no_design_msg: "Story {{story}} has no UI design because the UI LANE WAS DROPPED when it was designed — the story records dropped lanes [{{dropped_lanes}}]. This is not a slice without a user-facing surface; it is one whose UI half was never designed, because the frontend lane was undeclared or frontend-plan.md was missing or behind the spec. Fix that lane (/appweaver plan-ui, or declare it with /appweaver select-stack), then re-run /appweaver story-plan {{story}} and this command."
|
|
289
|
+
|
|
290
|
+
- id: require_design
|
|
291
|
+
action: verify_context
|
|
292
|
+
require_true: design_exists
|
|
293
|
+
on_false: stop
|
|
294
|
+
on_false_message: "{{no_design_msg}}"
|
|
295
|
+
|
|
296
|
+
# 3a. Announced, never refused — the same contract implement keeps. design_stale is a
|
|
297
|
+
# JSON boolean, so `condition:` does the deciding and the value gate is only the
|
|
298
|
+
# vehicle that prints (law 43); stale_csv is a string and compares directly.
|
|
299
|
+
- id: design_stale_notice
|
|
300
|
+
condition: "{{design_stale}} == true"
|
|
301
|
+
action: verify_context
|
|
302
|
+
require_value: design_stale
|
|
303
|
+
equals: "false"
|
|
304
|
+
on_mismatch: continue
|
|
305
|
+
on_mismatch_message: "UI 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."
|
|
306
|
+
- id: story_stale_notice
|
|
307
|
+
action: verify_context
|
|
308
|
+
require_value: stale_csv
|
|
309
|
+
equals: ""
|
|
310
|
+
on_mismatch: continue
|
|
311
|
+
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."
|
|
312
|
+
|
|
313
|
+
# ═══════ 3a-bis. LAW 40, THE LAST HOP — the plan this pass was designed against ═══════
|
|
314
|
+
# THREE HASHES, not two. story-plan proved the feature plan was certified in the shape it
|
|
315
|
+
# was in when this pass was designed, and story-plan-doc stamped that certified hash into
|
|
316
|
+
# the pass header. But a pass is written once and read forever: comparing only the CURRENT
|
|
317
|
+
# plan to the CURRENT verdict would let a pass designed against plan H1 still generate
|
|
318
|
+
# after the plan was edited to H2 and re-gated — which is precisely the case law 40 names,
|
|
319
|
+
# one level below where it was being checked.
|
|
320
|
+
# pass hash what this pass was designed against (the story pass header)
|
|
321
|
+
# verdict hash what analyze-design last certified (the gate verdict)
|
|
322
|
+
# A MISMATCH IS A STOP, at the same severity as never-gated: continuing ships code built
|
|
323
|
+
# to an architecture nobody approved. Per lane, and only for the lane this command builds.
|
|
324
|
+
- id: gate_verdict
|
|
325
|
+
action: execute_script
|
|
326
|
+
on_error: continue
|
|
327
|
+
script: { ref: actions }
|
|
328
|
+
subcommand: gate-read
|
|
329
|
+
args: ["--feature-dir", "{{feature_dir}}", "--gate", "analyze-design"]
|
|
330
|
+
outputs:
|
|
331
|
+
- { name: gate_status, from: "$.status" }
|
|
332
|
+
- { name: gate_certified, from: "$.certified" }
|
|
333
|
+
- { name: gate_aborted, from: "$.aborted" }
|
|
334
|
+
- { name: gate_gap_refs, from: "$.gap_refs_csv" }
|
|
335
|
+
- { name: gate_report, from: "$.report" }
|
|
336
|
+
- { name: gate_lane_sha, from: "$.frontend_sha256" }
|
|
337
|
+
|
|
338
|
+
# Three reasons, three routes; ordered sets, last match wins. Aborted is last because it
|
|
339
|
+
# also carries status "fail" — the red gate_result_open writes BEFORE any analysis — and
|
|
340
|
+
# sending someone to read gaps that were never found is the one wrong answer here.
|
|
341
|
+
- id: gate_msg_never
|
|
342
|
+
action: set
|
|
343
|
+
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-ui." }
|
|
344
|
+
- id: gate_msg_red
|
|
345
|
+
condition: "{{gate_status}} == fail"
|
|
346
|
+
action: set
|
|
347
|
+
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-ui." }
|
|
348
|
+
- id: gate_msg_aborted
|
|
349
|
+
condition: "{{gate_aborted}} == true"
|
|
350
|
+
action: set
|
|
351
|
+
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-ui." }
|
|
352
|
+
|
|
353
|
+
- id: gate_certified_guard
|
|
354
|
+
action: verify_context
|
|
355
|
+
require_true: gate_certified
|
|
356
|
+
on_false: stop
|
|
357
|
+
on_false_message: "{{gate_msg}}"
|
|
358
|
+
|
|
359
|
+
# An EMPTY pass hash fails this, and that is correct: a pass written before the hash was
|
|
360
|
+
# stamped cannot say which plan shape it was designed against, so nothing can prove it was
|
|
361
|
+
# designed against the approved one. The route is the same either way — re-design.
|
|
362
|
+
- id: pass_plan_shape_guard
|
|
363
|
+
action: verify_context
|
|
364
|
+
require_value: pass_plan_hash
|
|
365
|
+
equals: "{{gate_lane_sha}}"
|
|
366
|
+
on_mismatch: stop
|
|
367
|
+
on_mismatch_message: "Design pass {{design_pass}} for story {{story}} was designed against a frontend 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-ui."
|
|
368
|
+
|
|
369
|
+
# 4. THE FRONTEND LANE and its source path — the only subtree this phase may write
|
|
370
|
+
# into, resolved before the project gate that searches it.
|
|
108
371
|
- id: lane_gate
|
|
109
372
|
action: include
|
|
110
373
|
module: .appweaver/orchestrator/modules/lane-gate.orch.yaml
|
|
111
374
|
with: { require: frontend, command: "/appweaver implement-ui" }
|
|
112
375
|
|
|
113
|
-
#
|
|
114
|
-
#
|
|
115
|
-
#
|
|
116
|
-
#
|
|
117
|
-
#
|
|
118
|
-
#
|
|
119
|
-
# generated into anyway, reporting green.
|
|
376
|
+
# 5. Require a frontend PROJECT under the LANE'S OWN path. detect-project searches the
|
|
377
|
+
# lane subtree first and falls back to the whole repository, reporting which scope
|
|
378
|
+
# answered. THAT DISTINCTION IS THE GATE HERE. A repo-scope hit in a two-lane
|
|
379
|
+
# project is the service lane's manifest, and accepting it is exactly the failure
|
|
380
|
+
# the lane model exists to prevent — a frontend that was never scaffolded,
|
|
381
|
+
# generated into anyway, reporting green.
|
|
120
382
|
- id: detect_ui_project
|
|
121
383
|
action: execute_script
|
|
122
384
|
script: { ref: actions }
|
|
@@ -126,15 +388,31 @@ workflow:
|
|
|
126
388
|
- { name: ui_project_present, from: "$.scaffolded" }
|
|
127
389
|
- { name: project_markers, from: "$.markers" }
|
|
128
390
|
- { name: project_scope, from: "$.scope" }
|
|
129
|
-
|
|
130
391
|
- id: ui_project_gate
|
|
131
392
|
action: verify_context
|
|
132
393
|
require_value: project_scope
|
|
133
394
|
equals: "lane"
|
|
134
395
|
on_mismatch: stop
|
|
135
|
-
on_mismatch_message: "No frontend project found under the {{lane_id}} lane's source path `{{lane_source_path}}`. implement-ui GENERATES this
|
|
396
|
+
on_mismatch_message: "No frontend project found under the {{lane_id}} lane's source path `{{lane_source_path}}`. implement-ui GENERATES this story's UI into an existing project tree, so one has to be scaffolded there 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 inside the lane's path marks it — package.json, go.mod, pyproject.toml, Cargo.toml, pom.xml, build.gradle(.kts) or .sln/.csproj. A manifest found elsewhere in the repository does NOT count: that one belongs to another lane, and generating this lane's code against it is how a frontend that was never built reports green."
|
|
136
397
|
|
|
137
|
-
#
|
|
398
|
+
# 6. WHAT ALREADY EXISTS, per lane, from the feature's OKF concept. The UI list is
|
|
399
|
+
# what this run edits; the service count tells the pass whether the contract it
|
|
400
|
+
# binds to has been built yet. Advisory — a first UI story generates into a clean
|
|
401
|
+
# tree and a missing bundle must never block generation.
|
|
402
|
+
- id: coverage
|
|
403
|
+
action: execute_script
|
|
404
|
+
on_error: continue
|
|
405
|
+
script: { ref: xref }
|
|
406
|
+
subcommand: concept-coverage
|
|
407
|
+
args: ["--feature-dir", "{{feature_dir}}", "--ids", "{{exercises_csv}}"]
|
|
408
|
+
outputs:
|
|
409
|
+
- { name: okf_concept, from: "$.concept" }
|
|
410
|
+
- { name: coverage_digest, from: "$.digest" }
|
|
411
|
+
- { name: fe_source_csv, from: "$.frontend_csv" }
|
|
412
|
+
- { name: fe_source_count, from: "$.frontend_count" }
|
|
413
|
+
- { name: be_source_count, from: "$.backend_count" }
|
|
414
|
+
|
|
415
|
+
# 7. Pre-execution hooks.
|
|
138
416
|
- id: resolve_pre_hooks
|
|
139
417
|
action: execute_script
|
|
140
418
|
script: { ref: actions }
|
|
@@ -146,81 +424,122 @@ workflow:
|
|
|
146
424
|
action: dispatch_hooks
|
|
147
425
|
hooks_from_variable: before_hooks
|
|
148
426
|
|
|
149
|
-
#
|
|
150
|
-
#
|
|
151
|
-
#
|
|
427
|
+
# ═══════════════════ GENERATION — one story's UI slice, one pass ═════════════
|
|
428
|
+
# The pass constrains WHAT to build; constitution.ui.md, the lane and the loaded
|
|
429
|
+
# standards fix HOW. spec.md is deliberately absent — the acceptance check below is
|
|
430
|
+
# where it gets its say.
|
|
152
431
|
- id: ctx_generate
|
|
153
432
|
action: build_context
|
|
433
|
+
vars:
|
|
434
|
+
story_id: "{{story}}"
|
|
435
|
+
story_title: "{{story_title}}"
|
|
436
|
+
exercises_csv: "{{exercises_csv}}"
|
|
437
|
+
constraints_csv: "{{constraints_csv}}"
|
|
438
|
+
pass_number: "{{design_pass}}"
|
|
439
|
+
coverage_digest: "{{coverage_digest}}"
|
|
440
|
+
ui_source_files_csv: "{{fe_source_csv}}"
|
|
441
|
+
ui_source_file_count: "{{fe_source_count}}"
|
|
442
|
+
stale_note: "{{stale_note}}"
|
|
154
443
|
context:
|
|
155
|
-
- { path: "{{
|
|
444
|
+
- { path: "{{design_doc}}" } # the story's UI design — the LATEST pass is the input
|
|
445
|
+
- { path: "{{story_path}}" } # the story's acceptance criteria — what "done" means
|
|
446
|
+
- { path: "{{if_path}}", optional: true } # the PUBLISHED surface this slice binds to, by IF- id
|
|
447
|
+
- { path: "{{service_design_doc}}", optional: true } # the SAME story's service design — the newer word on that surface
|
|
448
|
+
- { path: "{{adr_path}}", optional: true } # ratified decisions — binding unless a pass supersedes them
|
|
156
449
|
- { path: .appweaver/memory/constitution.ui.md, optional: true } # the lane's ratified UX-* base — conventions, forbidden patterns, gates
|
|
157
450
|
- id: generate
|
|
158
451
|
action: agent_generate
|
|
159
|
-
tools: "Read,Write,Edit"
|
|
452
|
+
tools: "Read,Write,Edit" # the existing UI code arrives as {{ui_source_files_csv}} paths, not inlined
|
|
160
453
|
permission_mode: acceptEdits
|
|
161
454
|
task: |
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
455
|
+
Implement story {{story_id}} — {{story_title}} — in the frontend lane, from
|
|
456
|
+
"## Design pass {{pass_number}}" of the UI story design in context.
|
|
457
|
+
Guidance: {{USER_INPUT}}
|
|
458
|
+
|
|
459
|
+
THAT PASS IS THE ONLY SOURCE OF WHAT TO BUILD. It is the LAST pass in the
|
|
460
|
+
document; every earlier pass is superseded history, and any of them may carry
|
|
461
|
+
hand edits that were deliberate. Read the last one and build exactly it. Do NOT
|
|
462
|
+
consult or infer from spec.md — it is not in context on purpose.
|
|
463
|
+
|
|
464
|
+
THIS IS ONE SLICE OF AN EXISTING FRONTEND, NOT THE WHOLE FEATURE.
|
|
465
|
+
- The pass's "### Screens & components" section names, file by file, what to
|
|
466
|
+
create and what to modify. Create ONLY the files it names as new; modify the
|
|
467
|
+
named existing files IN PLACE. Every other file belongs to work already done —
|
|
468
|
+
do not regenerate, reformat or "tidy" it.
|
|
469
|
+
- The story delivers {{exercises_csv}} and must satisfy {{constraints_csv}} — the
|
|
470
|
+
complete set of bounds the spec declares for this aggregate, stamped into the
|
|
471
|
+
story's frontmatter by the kit rather than transcribed by anyone. ALL of them
|
|
472
|
+
apply here: the UX- are yours to realise, and the CN-/DC-/IC- still bind what this
|
|
473
|
+
surface may show, send and store. Anything outside those ids is another story's
|
|
474
|
+
work, even if you can see it is missing.
|
|
475
|
+
- The code that already exists for this feature, per lane, is below. The UI files
|
|
476
|
+
({{ui_source_file_count}} on disk) are yours to read before you edit; the
|
|
477
|
+
service list is there so you can see what the contract is actually backed by.
|
|
478
|
+
|
|
479
|
+
{{coverage_digest}}
|
|
480
|
+
|
|
481
|
+
{{ui_source_files_csv}}
|
|
482
|
+
|
|
483
|
+
BIND THE STACK: this lane is `{{lane_id}}` — {{lane_title}}, language
|
|
484
|
+
{{lane_language}} ({{lane_ecosystem}}), declared in .appweaver/stacks.json and
|
|
485
|
+
built with `{{lane_build}}`, tested with `{{lane_test}}`. Generate in THAT
|
|
486
|
+
framework and no other, using its idioms, its project layout and its test
|
|
487
|
+
framework. Take the router, state approach, component library, design system and
|
|
488
|
+
styling from the UI constitution + the loaded standards; the design is
|
|
489
|
+
technology-agnostic, so do not re-decide anything already fixed there. If the
|
|
490
|
+
loaded standards describe a DIFFERENT stack than the one above, the lane wins on
|
|
491
|
+
framework and layout — follow the standards' INTENT and STOP to surface the
|
|
492
|
+
mismatch in your summary.
|
|
493
|
+
|
|
494
|
+
WRITE ONLY UNDER `{{lane_source_path}}` — the source path the `{{lane_id}}` lane
|
|
495
|
+
declares. NEVER touch the service lane's code, not even to add a field the UI
|
|
496
|
+
wants: a gap in the service surface is a contract delta raised back to that lane,
|
|
497
|
+
not a file you edit. Do not create a parallel source tree because the layout
|
|
498
|
+
inside this one is unfamiliar; if the pass needs a place this subtree has no home
|
|
499
|
+
for, STOP and surface it.
|
|
500
|
+
|
|
166
501
|
Rules:
|
|
167
|
-
- THE
|
|
168
|
-
frontend standards
|
|
169
|
-
identifier, terminology or behaviour the
|
|
170
|
-
|
|
171
|
-
missing, STOP and surface it; never invent it from memory.
|
|
172
|
-
- BIND THE STACK: this lane is `{{lane_id}}` — {{lane_title}}, language
|
|
173
|
-
{{lane_language}} ({{lane_ecosystem}}), declared in .appweaver/stacks.json and
|
|
174
|
-
built with `{{lane_build}}`, tested with `{{lane_test}}`. Generate in THAT
|
|
175
|
-
framework and no other, using its idioms, its project layout and its test
|
|
176
|
-
framework. Take the router, state approach, component library, design system and
|
|
177
|
-
styling from the UI constitution + the loaded standards; the design is
|
|
178
|
-
technology-agnostic, so do not re-decide anything already fixed there. If the
|
|
179
|
-
loaded standards describe a DIFFERENT stack than the one above, the lane wins on
|
|
180
|
-
framework and layout — follow the standards' INTENT and STOP to surface the
|
|
181
|
-
mismatch in your summary.
|
|
182
|
-
- WRITE ONLY UNDER `{{lane_source_path}}` — the source path the `{{lane_id}}` lane
|
|
183
|
-
declares. That subtree is this lane's; a file outside it belongs to the service
|
|
184
|
-
lane or to no lane. NEVER touch the service lane's code, not even to add a field
|
|
185
|
-
the UI wants: a gap in the service surface is a contract delta raised back to
|
|
186
|
-
that lane. Do not create a parallel source tree because the layout inside this
|
|
187
|
-
one is unfamiliar; if the design needs a place this subtree has no home for,
|
|
188
|
-
STOP and surface it.
|
|
502
|
+
- THE PASS IS THE ONLY DOMAIN INPUT. You have it, the story, and the lane's
|
|
503
|
+
frontend standards — and NO domain rule files. Introduce NO domain rule,
|
|
504
|
+
identifier, terminology or behaviour the pass does not state. If a domain
|
|
505
|
+
specific seems missing, STOP and surface it; never invent it from memory.
|
|
189
506
|
- Follow the UI constitution's folder structure, naming conventions and forbidden
|
|
190
507
|
patterns, plus the loaded standards, for HOW to structure, name and place files
|
|
191
|
-
WITHIN that subtree — the
|
|
192
|
-
- REALISE
|
|
193
|
-
•
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
508
|
+
WITHIN that subtree — the pass says WHAT to build; these say HOW.
|
|
509
|
+
- REALISE THE PASS EXACTLY, changing nothing about WHAT it specifies:
|
|
510
|
+
• its "### Approach" — the routes and screens it touches, and where this
|
|
511
|
+
slice's state lives. Anything the design says must NOT be held never reaches
|
|
512
|
+
client state, storage, the URL or telemetry.
|
|
513
|
+
• its "### Screens & components" — each component with the responsibility, the
|
|
514
|
+
input shapes and the events the pass gives it. Compose the shared components
|
|
515
|
+
it says it reuses; do not re-implement them.
|
|
516
|
+
• its "### Contract binding" — call each interface BY ITS IF- id as the pass
|
|
517
|
+
names it, with the request it states, and render EVERY state it lists:
|
|
518
|
+
in-flight, empty, error and the failure behaviour. These are requirements,
|
|
519
|
+
not polish, and they are the first thing a generator drops.
|
|
520
|
+
- THE CONTRACT IS NOT YOURS TO INVENT. interfaces.md (in context) is the
|
|
521
|
+
published surface; bind to the latest entry per id. If the pass names an
|
|
522
|
+
interface the log does not carry, STOP and surface it — do not invent an
|
|
523
|
+
endpoint, and do not reach into the service lane to add one. Where the SAME
|
|
524
|
+
story's service design is in context, it is the newer word on what that surface
|
|
525
|
+
will be; bind to it and say so in your summary.
|
|
209
526
|
- THE CLIENT IS NOT AN AUTHORITY. A client-side validation or permission check
|
|
210
527
|
restates a server rule for feedback; it never replaces it. Generate no
|
|
211
|
-
server-side behaviour, no persistence and no business
|
|
212
|
-
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
528
|
+
server-side behaviour, no persistence and no business-rule evaluation here.
|
|
529
|
+
- ADRs are DECIDED — follow them. The pass may propose a superseding one; a
|
|
530
|
+
proposal is not yet ratified, so follow the pass and change nothing else.
|
|
531
|
+
- Author a paired test for every acceptance criterion in the story and every
|
|
532
|
+
behaviour the pass's "### Tests" section names — including the unwanted-state
|
|
533
|
+
cases from its contract binding and an accessibility assertion per interactive
|
|
534
|
+
surface — and cite the requirement id ({{exercises_csv}}, {{constraints_csv}})
|
|
535
|
+
each test exists for. A snapshot alone is not a test.
|
|
536
|
+
- Do NOT add behaviour the pass does not state, and do NOT omit any it does. If
|
|
537
|
+
the pass looks wrong or incomplete, STOP and surface it — it is fixed by
|
|
538
|
+
re-running /appweaver story-plan, never compensated for here.
|
|
219
539
|
- This feature OWNS its files; never regenerate a file another feature owns, and
|
|
220
540
|
do not modify the app shell, global layout or design system — they belong to no
|
|
221
541
|
feature. If one of them has to change, STOP and surface it.
|
|
222
|
-
|
|
223
|
-
mark it with a comment citing that id so it is easy to revisit later.
|
|
542
|
+
{{stale_note}}
|
|
224
543
|
outputs:
|
|
225
544
|
- { name: files, from: "$.files" }
|
|
226
545
|
|
|
@@ -228,24 +547,99 @@ workflow:
|
|
|
228
547
|
action: verify_context
|
|
229
548
|
require_non_empty: files
|
|
230
549
|
on_empty: stop
|
|
231
|
-
on_empty_message: "No files generated.
|
|
232
|
-
|
|
233
|
-
#
|
|
234
|
-
#
|
|
235
|
-
#
|
|
236
|
-
#
|
|
237
|
-
#
|
|
238
|
-
#
|
|
239
|
-
#
|
|
550
|
+
on_empty_message: "No files generated for story {{story}}. Check that UI design pass {{design_pass}} in {{design_doc}} actually describes changes (a pass whose '### Screens & components' section is empty has nothing to build), then re-run — or re-run /appweaver story-plan {{story}} to design the slice properly first."
|
|
551
|
+
|
|
552
|
+
# 8. OKF — MERGE, into the UI LANE'S KEY. The feature has ONE concept across both
|
|
553
|
+
# lanes: `--lane frontend` writes `ui_source_files`, so the service lane's
|
|
554
|
+
# `source_files` is untouched whichever lane implements first, and `--merge` unions
|
|
555
|
+
# this story's files with what the feature's earlier UI stories wrote. The
|
|
556
|
+
# concept's Overview is NOT re-authored here: it describes the aggregate and its
|
|
557
|
+
# invariants, which is the service design's subject, and rewriting it from a UI
|
|
558
|
+
# slice would trade a domain description for an experience one.
|
|
240
559
|
- id: xref_concept
|
|
241
560
|
action: execute_script
|
|
242
561
|
script: { ref: xref }
|
|
243
562
|
subcommand: concept-upsert
|
|
244
|
-
|
|
563
|
+
# --ids + --slice record WHICH REQUIREMENT this run's files realise, as Requirement
|
|
564
|
+
# Anchors rows in the concept. Both halves were already here and thrown away: the ids
|
|
565
|
+
# the story exercises, and the files just generated. Only `adopt` writes a
|
|
566
|
+
# source-map.md, so without this every id on a greenfield feature came back unanchored
|
|
567
|
+
# at analyze-slice and the trace pass had to read the whole tree to find out what
|
|
568
|
+
# implements what. Reconcile-on-write, keyed by slice + lane.
|
|
569
|
+
args: ["--feature-dir", "{{feature_dir}}", "--source-files", "{{files}}", "--merge", "--lane", "frontend", "--origin", "greenfield", "--ids", "{{exercises_csv}}", "--slice", "{{story}}"]
|
|
245
570
|
outputs:
|
|
246
|
-
- { name: okf_concept,
|
|
571
|
+
- { name: okf_concept, from: "$.concept" }
|
|
572
|
+
- { name: okf_anchors, from: "$.anchors_written" }
|
|
573
|
+
|
|
574
|
+
# ═══════════════ ACCEPTANCE — the ONE place the spec is read ═════════════════
|
|
575
|
+
# READ-ONLY and REPORT-ONLY: it never edits code, never edits the pass, and never
|
|
576
|
+
# stops the run. A gap means one of two documents is wrong, and which one is a human
|
|
577
|
+
# decision — the message names both routes.
|
|
578
|
+
- id: ctx_accept
|
|
579
|
+
action: build_context
|
|
580
|
+
vars:
|
|
581
|
+
story_id: "{{story}}"
|
|
582
|
+
exercises_csv: "{{exercises_csv}}"
|
|
583
|
+
constraints_csv: "{{constraints_csv}}"
|
|
584
|
+
files_csv: "{{files}}"
|
|
585
|
+
context:
|
|
586
|
+
- { path: "{{story_path}}" } # the acceptance criteria
|
|
587
|
+
- { path: "{{spec_file}}" } # the contract — the ONLY step in this workflow that reads it
|
|
588
|
+
- { path: "{{design_doc}}" } # what was designed
|
|
589
|
+
- id: accept_check
|
|
590
|
+
action: agent_query
|
|
591
|
+
tools: Read # the generated files arrive as paths, not inlined
|
|
592
|
+
include_standards: false
|
|
593
|
+
schema: { ok: "<true|false>", gaps: "<one line per gap, or empty>" }
|
|
594
|
+
mock: { ok: "true", gaps: "" }
|
|
595
|
+
task: |
|
|
596
|
+
Story {{story_id}} was just implemented in the frontend lane. Judge ONLY this:
|
|
597
|
+
does the UI that now exists satisfy the requirements the story delivers, AS THE
|
|
598
|
+
SPEC WORDS THEM?
|
|
599
|
+
|
|
600
|
+
In scope: {{exercises_csv}} (delivered) and {{constraints_csv}} (must hold) — the
|
|
601
|
+
UX- ids among them are this lane's to realise. Resolve each id's wording from
|
|
602
|
+
spec.md in context; that wording, not the design's restatement of it, is what the
|
|
603
|
+
code is measured against.
|
|
604
|
+
|
|
605
|
+
Read the files this run wrote:
|
|
606
|
+
{{files_csv}}
|
|
607
|
+
plus anything they import that you need to follow. Do not modify a single file.
|
|
608
|
+
|
|
609
|
+
Report a gap ONLY where the UI fails to deliver an in-scope id as the spec words
|
|
610
|
+
it, or breaks an in-scope constraint. These are NOT gaps, and reporting them is
|
|
611
|
+
noise that hides the real ones:
|
|
612
|
+
- server-side behaviour — this lane cannot implement it and must not;
|
|
613
|
+
- a requirement no story has claimed yet, or one another story delivers;
|
|
614
|
+
- style, naming, structure or test-coverage opinions — other gates own those;
|
|
615
|
+
- anything the design pass deliberately deferred and named under its risks.
|
|
616
|
+
|
|
617
|
+
One line per gap: the id, what the spec requires, and what the UI does instead.
|
|
618
|
+
Set ok=false only when you found at least one. If the SPEC and the DESIGN disagree
|
|
619
|
+
about an in-scope id, that IS a gap — say which document you believe is wrong and
|
|
620
|
+
why, because the two routes out of it are different commands.
|
|
621
|
+
outputs:
|
|
622
|
+
- { name: accept_ok, from: "$.ok" }
|
|
623
|
+
- { name: accept_gaps, from: "$.gaps" }
|
|
624
|
+
- id: accept_notice
|
|
625
|
+
condition: "{{accept_ok}} != true"
|
|
626
|
+
action: verify_context
|
|
627
|
+
require_value: accept_ok
|
|
628
|
+
equals: "true"
|
|
629
|
+
on_mismatch: continue
|
|
630
|
+
on_mismatch_message: |
|
|
631
|
+
ACCEPTANCE — story {{story}}'s UI does not yet satisfy the spec wording of the ids it delivers:
|
|
632
|
+
|
|
633
|
+
{{accept_gaps}}
|
|
634
|
+
|
|
635
|
+
Nothing was changed to fix this: the code follows UI design pass {{design_pass}}, so a gap
|
|
636
|
+
here means either the design was wrong or the spec has moved. Route by which it is —
|
|
637
|
+
the design is wrong: /appweaver story-plan {{story}} (a new pass supersedes this one);
|
|
638
|
+
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.
|
|
639
|
+
Run /appweaver build either way — a design that is right and code that does not compile
|
|
640
|
+
is a third thing, and build is where that one surfaces.
|
|
247
641
|
|
|
248
|
-
#
|
|
642
|
+
# 9. Post-execution hooks.
|
|
249
643
|
- id: resolve_post_hooks
|
|
250
644
|
action: execute_script
|
|
251
645
|
script: { ref: actions }
|
|
@@ -258,10 +652,12 @@ workflow:
|
|
|
258
652
|
hooks_from_variable: after_hooks
|
|
259
653
|
|
|
260
654
|
outputs:
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
lane_source_path: "{{lane_source_path}}"
|
|
655
|
+
story: "{{story}}"
|
|
656
|
+
built_from: "{{design_doc}} — UI design pass {{design_pass}} (designed against spec {{design_spec_version}}; spec now {{current_version}})"
|
|
657
|
+
requirements: "{{exercises_csv}}"
|
|
265
658
|
files: "{{files}}"
|
|
266
|
-
|
|
267
|
-
|
|
659
|
+
lane: "{{lane_id}} → {{lane_source_path}}"
|
|
660
|
+
okf_concept: "{{okf_concept}} (this run's files MERGED into ui_source_files; the service lane's source_files is untouched)"
|
|
661
|
+
acceptance: "ok={{accept_ok}} · gaps: {{accept_gaps}}"
|
|
662
|
+
stale_ids: "{{stale_csv}}"
|
|
663
|
+
next: "/appweaver build — it compiles every declared lane, so a break in either surfaces there. Then /appweaver analyze-slice. 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."
|