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,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.
|
|
31
|
-
#
|
|
32
|
-
# ⌛ /appweaver analyze-design-ui — the spec↔design.ui.md conformance gate that would
|
|
33
|
-
# run between design-ui and this phase — is not built yet. Until it is, the two-way
|
|
34
|
-
# check that no UX-/US- fell between the two designs is design.ui.md §7 read by hand.
|
|
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.)
|
|
35
35
|
#
|
|
36
|
-
#
|
|
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.
|
|
37
42
|
#
|
|
38
|
-
# Launch: /appweaver implement-ui [
|
|
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,56 @@ 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
|
+
service_design_doc: ""
|
|
108
|
+
# the feature's current code, per lane (OKF)
|
|
109
|
+
okf_concept: ""
|
|
110
|
+
coverage_digest: ""
|
|
111
|
+
fe_source_csv: ""
|
|
112
|
+
fe_source_count: "0"
|
|
113
|
+
be_source_count: "0"
|
|
114
|
+
# generation + enrichment
|
|
115
|
+
files: ""
|
|
116
|
+
accept_ok: "true"
|
|
117
|
+
accept_gaps: ""
|
|
118
|
+
|
|
71
119
|
workflow:
|
|
72
120
|
|
|
73
121
|
# 0. FRONTEND code-generation rule files → sticky standards (configuration, not
|
|
@@ -78,45 +126,146 @@ workflow:
|
|
|
78
126
|
module: .appweaver/orchestrator/modules/tech-select.orch.yaml
|
|
79
127
|
with: { command: aw-spec-implement-ui }
|
|
80
128
|
|
|
81
|
-
# 1.
|
|
82
|
-
#
|
|
129
|
+
# 1. The active feature. spec.md is resolved here for the acceptance check ONLY —
|
|
130
|
+
# it is not a generation input.
|
|
83
131
|
- id: feature_paths
|
|
84
132
|
action: execute_script
|
|
85
133
|
on_error: continue # never crash raw on 'no feature set' — the gate below explains it
|
|
86
134
|
script: { ref: actions }
|
|
87
135
|
subcommand: feature-paths
|
|
88
136
|
outputs:
|
|
89
|
-
- { name: feature_dir,
|
|
90
|
-
- { name:
|
|
91
|
-
- { name: ui_design_exists, from: "$.ui_design_exists" }
|
|
92
|
-
|
|
137
|
+
- { name: feature_dir, from: "$.feature_dir" }
|
|
138
|
+
- { name: spec_file, from: "$.spec_file" }
|
|
93
139
|
- id: require_feature
|
|
94
140
|
action: verify_context
|
|
95
141
|
require_non_empty: feature_dir
|
|
96
142
|
on_empty: stop
|
|
97
143
|
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
144
|
|
|
99
|
-
-
|
|
145
|
+
# 2. WHICH STORY — the same resolution story-plan and implement use.
|
|
146
|
+
- id: story_scan
|
|
147
|
+
action: execute_script
|
|
148
|
+
script: { ref: actions }
|
|
149
|
+
subcommand: story-plan-check
|
|
150
|
+
args: ["--feature-dir", "{{feature_dir}}", "--input", "{{USER_INPUT}}"]
|
|
151
|
+
outputs:
|
|
152
|
+
- { name: any_open, from: "$.any_open" }
|
|
153
|
+
- { name: open_ids, from: "$.open_ids" }
|
|
154
|
+
- { name: digest, from: "$.digest" }
|
|
155
|
+
- { name: resolved, from: "$.resolved" }
|
|
156
|
+
- { name: needs_pick, from: "$.needs_pick" }
|
|
157
|
+
- { name: pick_reason, from: "$.pick_reason" }
|
|
158
|
+
- { name: story, from: "$.story" }
|
|
159
|
+
- { name: story_path, from: "$.story_path" }
|
|
160
|
+
- { name: story_open, from: "$.story_open" }
|
|
161
|
+
- { name: status_msg, from: "$.status_msg" }
|
|
162
|
+
- { name: story_title, from: "$.story_title" }
|
|
163
|
+
- { name: exercises_csv, from: "$.exercises_csv" }
|
|
164
|
+
- { name: constraints_csv, from: "$.constraints_csv" }
|
|
165
|
+
- { name: current_version, from: "$.current_version" }
|
|
166
|
+
- { name: stale_csv, from: "$.stale_csv" }
|
|
167
|
+
- { name: stale_note, from: "$.stale_note" }
|
|
168
|
+
- { name: story_dir, from: "$.story_dir" }
|
|
169
|
+
- { name: adr_path, from: "$.adr_path" }
|
|
170
|
+
- { name: if_path, from: "$.if_path" }
|
|
171
|
+
- { name: design_doc, from: "$.fe_doc_path" }
|
|
172
|
+
- { name: design_exists, from: "$.fe_doc_exists" }
|
|
173
|
+
- { name: design_pass, from: "$.fe_last_pass" }
|
|
174
|
+
- { name: design_spec_version, from: "$.fe_doc_spec_version" }
|
|
175
|
+
- { name: design_stale, from: "$.fe_doc_stale" }
|
|
176
|
+
- { name: service_design_doc, from: "$.be_doc_path" }
|
|
177
|
+
- id: require_open_backlog
|
|
178
|
+
action: verify_context
|
|
179
|
+
require_true: any_open
|
|
180
|
+
on_false: stop
|
|
181
|
+
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)."
|
|
182
|
+
- id: ask_story
|
|
183
|
+
condition: "{{needs_pick}} == true"
|
|
184
|
+
action: ask
|
|
185
|
+
prompt: |
|
|
186
|
+
Which story is this UI implementation for? ({{pick_reason}})
|
|
187
|
+
|
|
188
|
+
{{digest}}
|
|
189
|
+
options_from: "{{open_ids}}"
|
|
190
|
+
answer_key: picked_story
|
|
191
|
+
answer_var: picked_story
|
|
192
|
+
- id: story_pick_check
|
|
193
|
+
condition: "{{needs_pick}} == true"
|
|
194
|
+
action: execute_script
|
|
195
|
+
script: { ref: actions }
|
|
196
|
+
subcommand: story-plan-check
|
|
197
|
+
args: ["--feature-dir", "{{feature_dir}}", "--story", "{{picked_story}}"]
|
|
198
|
+
outputs:
|
|
199
|
+
- { name: resolved, from: "$.resolved" }
|
|
200
|
+
- { name: story, from: "$.story" }
|
|
201
|
+
- { name: story_path, from: "$.story_path" }
|
|
202
|
+
- { name: story_open, from: "$.story_open" }
|
|
203
|
+
- { name: status_msg, from: "$.status_msg" }
|
|
204
|
+
- { name: story_title, from: "$.story_title" }
|
|
205
|
+
- { name: exercises_csv, from: "$.exercises_csv" }
|
|
206
|
+
- { name: constraints_csv, from: "$.constraints_csv" }
|
|
207
|
+
- { name: stale_csv, from: "$.stale_csv" }
|
|
208
|
+
- { name: stale_note, from: "$.stale_note" }
|
|
209
|
+
- { name: story_dir, from: "$.story_dir" }
|
|
210
|
+
- { name: design_doc, from: "$.fe_doc_path" }
|
|
211
|
+
- { name: design_exists, from: "$.fe_doc_exists" }
|
|
212
|
+
- { name: design_pass, from: "$.fe_last_pass" }
|
|
213
|
+
- { name: design_spec_version, from: "$.fe_doc_spec_version" }
|
|
214
|
+
- { name: design_stale, from: "$.fe_doc_stale" }
|
|
215
|
+
- { name: service_design_doc, from: "$.be_doc_path" }
|
|
216
|
+
- id: require_story
|
|
217
|
+
action: verify_context
|
|
218
|
+
require_true: resolved
|
|
219
|
+
on_false: stop
|
|
220
|
+
on_false_message: |
|
|
221
|
+
Could not resolve a story from '{{picked_story}}'. Open stories:
|
|
222
|
+
{{digest}}
|
|
223
|
+
Re-run implement-ui naming one of them.
|
|
224
|
+
- id: require_open_story
|
|
225
|
+
action: verify_context
|
|
226
|
+
require_true: story_open
|
|
227
|
+
on_false: stop
|
|
228
|
+
on_false_message: "{{status_msg}}"
|
|
229
|
+
|
|
230
|
+
# 3. THE STORY MUST HAVE A UI DESIGN. A story with a service design and no UI one is
|
|
231
|
+
# not a failure — it is a slice with no user-facing surface, and the message says
|
|
232
|
+
# so rather than implying something is broken.
|
|
233
|
+
- id: require_design
|
|
100
234
|
action: verify_context
|
|
101
|
-
require_true:
|
|
235
|
+
require_true: design_exists
|
|
102
236
|
on_false: stop
|
|
103
|
-
on_false_message: "
|
|
237
|
+
on_false_message: "Story {{story}} has no UI design — {{story_dir}}/story-frontend-plan.md does not exist. 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}}."
|
|
238
|
+
|
|
239
|
+
# 3a. Announced, never refused — the same contract implement keeps. design_stale is a
|
|
240
|
+
# JSON boolean, so `condition:` does the deciding and the value gate is only the
|
|
241
|
+
# vehicle that prints (law 43); stale_csv is a string and compares directly.
|
|
242
|
+
- id: design_stale_notice
|
|
243
|
+
condition: "{{design_stale}} == true"
|
|
244
|
+
action: verify_context
|
|
245
|
+
require_value: design_stale
|
|
246
|
+
equals: "false"
|
|
247
|
+
on_mismatch: continue
|
|
248
|
+
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."
|
|
249
|
+
- id: story_stale_notice
|
|
250
|
+
action: verify_context
|
|
251
|
+
require_value: stale_csv
|
|
252
|
+
equals: ""
|
|
253
|
+
on_mismatch: continue
|
|
254
|
+
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."
|
|
104
255
|
|
|
105
|
-
#
|
|
106
|
-
#
|
|
107
|
-
# project gate, which searches that subtree.
|
|
256
|
+
# 4. THE FRONTEND LANE and its source path — the only subtree this phase may write
|
|
257
|
+
# into, resolved before the project gate that searches it.
|
|
108
258
|
- id: lane_gate
|
|
109
259
|
action: include
|
|
110
260
|
module: .appweaver/orchestrator/modules/lane-gate.orch.yaml
|
|
111
261
|
with: { require: frontend, command: "/appweaver implement-ui" }
|
|
112
262
|
|
|
113
|
-
#
|
|
114
|
-
#
|
|
115
|
-
#
|
|
116
|
-
#
|
|
117
|
-
#
|
|
118
|
-
#
|
|
119
|
-
# generated into anyway, reporting green.
|
|
263
|
+
# 5. Require a frontend PROJECT under the LANE'S OWN path. detect-project searches the
|
|
264
|
+
# lane subtree first and falls back to the whole repository, reporting which scope
|
|
265
|
+
# answered. THAT DISTINCTION IS THE GATE HERE. A repo-scope hit in a two-lane
|
|
266
|
+
# project is the service lane's manifest, and accepting it is exactly the failure
|
|
267
|
+
# the lane model exists to prevent — a frontend that was never scaffolded,
|
|
268
|
+
# generated into anyway, reporting green.
|
|
120
269
|
- id: detect_ui_project
|
|
121
270
|
action: execute_script
|
|
122
271
|
script: { ref: actions }
|
|
@@ -126,15 +275,31 @@ workflow:
|
|
|
126
275
|
- { name: ui_project_present, from: "$.scaffolded" }
|
|
127
276
|
- { name: project_markers, from: "$.markers" }
|
|
128
277
|
- { name: project_scope, from: "$.scope" }
|
|
129
|
-
|
|
130
278
|
- id: ui_project_gate
|
|
131
279
|
action: verify_context
|
|
132
280
|
require_value: project_scope
|
|
133
281
|
equals: "lane"
|
|
134
282
|
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
|
|
283
|
+
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
284
|
|
|
137
|
-
#
|
|
285
|
+
# 6. WHAT ALREADY EXISTS, per lane, from the feature's OKF concept. The UI list is
|
|
286
|
+
# what this run edits; the service count tells the pass whether the contract it
|
|
287
|
+
# binds to has been built yet. Advisory — a first UI story generates into a clean
|
|
288
|
+
# tree and a missing bundle must never block generation.
|
|
289
|
+
- id: coverage
|
|
290
|
+
action: execute_script
|
|
291
|
+
on_error: continue
|
|
292
|
+
script: { ref: xref }
|
|
293
|
+
subcommand: concept-coverage
|
|
294
|
+
args: ["--feature-dir", "{{feature_dir}}", "--ids", "{{exercises_csv}}"]
|
|
295
|
+
outputs:
|
|
296
|
+
- { name: okf_concept, from: "$.concept" }
|
|
297
|
+
- { name: coverage_digest, from: "$.digest" }
|
|
298
|
+
- { name: fe_source_csv, from: "$.frontend_csv" }
|
|
299
|
+
- { name: fe_source_count, from: "$.frontend_count" }
|
|
300
|
+
- { name: be_source_count, from: "$.backend_count" }
|
|
301
|
+
|
|
302
|
+
# 7. Pre-execution hooks.
|
|
138
303
|
- id: resolve_pre_hooks
|
|
139
304
|
action: execute_script
|
|
140
305
|
script: { ref: actions }
|
|
@@ -146,81 +311,119 @@ workflow:
|
|
|
146
311
|
action: dispatch_hooks
|
|
147
312
|
hooks_from_variable: before_hooks
|
|
148
313
|
|
|
149
|
-
#
|
|
150
|
-
#
|
|
151
|
-
#
|
|
314
|
+
# ═══════════════════ GENERATION — one story's UI slice, one pass ═════════════
|
|
315
|
+
# The pass constrains WHAT to build; constitution.ui.md, the lane and the loaded
|
|
316
|
+
# standards fix HOW. spec.md is deliberately absent — the acceptance check below is
|
|
317
|
+
# where it gets its say.
|
|
152
318
|
- id: ctx_generate
|
|
153
319
|
action: build_context
|
|
320
|
+
vars:
|
|
321
|
+
story_id: "{{story}}"
|
|
322
|
+
story_title: "{{story_title}}"
|
|
323
|
+
exercises_csv: "{{exercises_csv}}"
|
|
324
|
+
constraints_csv: "{{constraints_csv}}"
|
|
325
|
+
pass_number: "{{design_pass}}"
|
|
326
|
+
coverage_digest: "{{coverage_digest}}"
|
|
327
|
+
ui_source_files_csv: "{{fe_source_csv}}"
|
|
328
|
+
ui_source_file_count: "{{fe_source_count}}"
|
|
329
|
+
stale_note: "{{stale_note}}"
|
|
154
330
|
context:
|
|
155
|
-
- { path: "{{
|
|
331
|
+
- { path: "{{design_doc}}" } # the story's UI design — the LATEST pass is the input
|
|
332
|
+
- { path: "{{story_path}}" } # the story's acceptance criteria — what "done" means
|
|
333
|
+
- { path: "{{if_path}}", optional: true } # the PUBLISHED surface this slice binds to, by IF- id
|
|
334
|
+
- { path: "{{service_design_doc}}", optional: true } # the SAME story's service design — the newer word on that surface
|
|
335
|
+
- { path: "{{adr_path}}", optional: true } # ratified decisions — binding unless a pass supersedes them
|
|
156
336
|
- { path: .appweaver/memory/constitution.ui.md, optional: true } # the lane's ratified UX-* base — conventions, forbidden patterns, gates
|
|
157
337
|
- id: generate
|
|
158
338
|
action: agent_generate
|
|
159
|
-
tools: "Read,Write,Edit"
|
|
339
|
+
tools: "Read,Write,Edit" # the existing UI code arrives as {{ui_source_files_csv}} paths, not inlined
|
|
160
340
|
permission_mode: acceptEdits
|
|
161
341
|
task: |
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
342
|
+
Implement story {{story_id}} — {{story_title}} — in the frontend lane, from
|
|
343
|
+
"## Design pass {{pass_number}}" of the UI story design in context.
|
|
344
|
+
Guidance: {{USER_INPUT}}
|
|
345
|
+
|
|
346
|
+
THAT PASS IS THE ONLY SOURCE OF WHAT TO BUILD. It is the LAST pass in the
|
|
347
|
+
document; every earlier pass is superseded history, and any of them may carry
|
|
348
|
+
hand edits that were deliberate. Read the last one and build exactly it. Do NOT
|
|
349
|
+
consult or infer from spec.md — it is not in context on purpose.
|
|
350
|
+
|
|
351
|
+
THIS IS ONE SLICE OF AN EXISTING FRONTEND, NOT THE WHOLE FEATURE.
|
|
352
|
+
- The pass's "### Screens & components" section names, file by file, what to
|
|
353
|
+
create and what to modify. Create ONLY the files it names as new; modify the
|
|
354
|
+
named existing files IN PLACE. Every other file belongs to work already done —
|
|
355
|
+
do not regenerate, reformat or "tidy" it.
|
|
356
|
+
- The story delivers {{exercises_csv}} and must satisfy {{constraints_csv}}, of
|
|
357
|
+
which the UX- constraints are yours to realise. Anything outside those ids is
|
|
358
|
+
another story's work, even if you can see it is missing.
|
|
359
|
+
- The code that already exists for this feature, per lane, is below. The UI files
|
|
360
|
+
({{ui_source_file_count}} on disk) are yours to read before you edit; the
|
|
361
|
+
service list is there so you can see what the contract is actually backed by.
|
|
362
|
+
|
|
363
|
+
{{coverage_digest}}
|
|
364
|
+
|
|
365
|
+
{{ui_source_files_csv}}
|
|
366
|
+
|
|
367
|
+
BIND THE STACK: this lane is `{{lane_id}}` — {{lane_title}}, language
|
|
368
|
+
{{lane_language}} ({{lane_ecosystem}}), declared in .appweaver/stacks.json and
|
|
369
|
+
built with `{{lane_build}}`, tested with `{{lane_test}}`. Generate in THAT
|
|
370
|
+
framework and no other, using its idioms, its project layout and its test
|
|
371
|
+
framework. Take the router, state approach, component library, design system and
|
|
372
|
+
styling from the UI constitution + the loaded standards; the design is
|
|
373
|
+
technology-agnostic, so do not re-decide anything already fixed there. If the
|
|
374
|
+
loaded standards describe a DIFFERENT stack than the one above, the lane wins on
|
|
375
|
+
framework and layout — follow the standards' INTENT and STOP to surface the
|
|
376
|
+
mismatch in your summary.
|
|
377
|
+
|
|
378
|
+
WRITE ONLY UNDER `{{lane_source_path}}` — the source path the `{{lane_id}}` lane
|
|
379
|
+
declares. NEVER touch the service lane's code, not even to add a field the UI
|
|
380
|
+
wants: a gap in the service surface is a contract delta raised back to that lane,
|
|
381
|
+
not a file you edit. Do not create a parallel source tree because the layout
|
|
382
|
+
inside this one is unfamiliar; if the pass needs a place this subtree has no home
|
|
383
|
+
for, STOP and surface it.
|
|
384
|
+
|
|
166
385
|
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.
|
|
386
|
+
- THE PASS IS THE ONLY DOMAIN INPUT. You have it, the story, and the lane's
|
|
387
|
+
frontend standards — and NO domain rule files. Introduce NO domain rule,
|
|
388
|
+
identifier, terminology or behaviour the pass does not state. If a domain
|
|
389
|
+
specific seems missing, STOP and surface it; never invent it from memory.
|
|
189
390
|
- Follow the UI constitution's folder structure, naming conventions and forbidden
|
|
190
391
|
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
|
-
|
|
392
|
+
WITHIN that subtree — the pass says WHAT to build; these say HOW.
|
|
393
|
+
- REALISE THE PASS EXACTLY, changing nothing about WHAT it specifies:
|
|
394
|
+
• its "### Approach" — the routes and screens it touches, and where this
|
|
395
|
+
slice's state lives. Anything the design says must NOT be held never reaches
|
|
396
|
+
client state, storage, the URL or telemetry.
|
|
397
|
+
• its "### Screens & components" — each component with the responsibility, the
|
|
398
|
+
input shapes and the events the pass gives it. Compose the shared components
|
|
399
|
+
it says it reuses; do not re-implement them.
|
|
400
|
+
• its "### Contract binding" — call each interface BY ITS IF- id as the pass
|
|
401
|
+
names it, with the request it states, and render EVERY state it lists:
|
|
402
|
+
in-flight, empty, error and the failure behaviour. These are requirements,
|
|
403
|
+
not polish, and they are the first thing a generator drops.
|
|
404
|
+
- THE CONTRACT IS NOT YOURS TO INVENT. interfaces.md (in context) is the
|
|
405
|
+
published surface; bind to the latest entry per id. If the pass names an
|
|
406
|
+
interface the log does not carry, STOP and surface it — do not invent an
|
|
407
|
+
endpoint, and do not reach into the service lane to add one. Where the SAME
|
|
408
|
+
story's service design is in context, it is the newer word on what that surface
|
|
409
|
+
will be; bind to it and say so in your summary.
|
|
209
410
|
- THE CLIENT IS NOT AN AUTHORITY. A client-side validation or permission check
|
|
210
411
|
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
|
-
|
|
412
|
+
server-side behaviour, no persistence and no business-rule evaluation here.
|
|
413
|
+
- ADRs are DECIDED — follow them. The pass may propose a superseding one; a
|
|
414
|
+
proposal is not yet ratified, so follow the pass and change nothing else.
|
|
415
|
+
- Author a paired test for every acceptance criterion in the story and every
|
|
416
|
+
behaviour the pass's "### Tests" section names — including the unwanted-state
|
|
417
|
+
cases from its contract binding and an accessibility assertion per interactive
|
|
418
|
+
surface — and cite the requirement id ({{exercises_csv}}, {{constraints_csv}})
|
|
419
|
+
each test exists for. A snapshot alone is not a test.
|
|
420
|
+
- Do NOT add behaviour the pass does not state, and do NOT omit any it does. If
|
|
421
|
+
the pass looks wrong or incomplete, STOP and surface it — it is fixed by
|
|
422
|
+
re-running /appweaver story-plan, never compensated for here.
|
|
219
423
|
- This feature OWNS its files; never regenerate a file another feature owns, and
|
|
220
424
|
do not modify the app shell, global layout or design system — they belong to no
|
|
221
425
|
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.
|
|
426
|
+
{{stale_note}}
|
|
224
427
|
outputs:
|
|
225
428
|
- { name: files, from: "$.files" }
|
|
226
429
|
|
|
@@ -228,24 +431,92 @@ workflow:
|
|
|
228
431
|
action: verify_context
|
|
229
432
|
require_non_empty: files
|
|
230
433
|
on_empty: stop
|
|
231
|
-
on_empty_message: "No files generated.
|
|
232
|
-
|
|
233
|
-
#
|
|
234
|
-
#
|
|
235
|
-
#
|
|
236
|
-
#
|
|
237
|
-
#
|
|
238
|
-
#
|
|
239
|
-
#
|
|
434
|
+
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."
|
|
435
|
+
|
|
436
|
+
# 8. OKF — MERGE, into the UI LANE'S KEY. The feature has ONE concept across both
|
|
437
|
+
# lanes: `--lane frontend` writes `ui_source_files`, so the service lane's
|
|
438
|
+
# `source_files` is untouched whichever lane implements first, and `--merge` unions
|
|
439
|
+
# this story's files with what the feature's earlier UI stories wrote. The
|
|
440
|
+
# concept's Overview is NOT re-authored here: it describes the aggregate and its
|
|
441
|
+
# invariants, which is the service design's subject, and rewriting it from a UI
|
|
442
|
+
# slice would trade a domain description for an experience one.
|
|
240
443
|
- id: xref_concept
|
|
241
444
|
action: execute_script
|
|
242
445
|
script: { ref: xref }
|
|
243
446
|
subcommand: concept-upsert
|
|
244
|
-
args: ["--feature-dir", "{{feature_dir}}", "--source-files", "{{files}}", "--merge", "--origin", "greenfield"]
|
|
447
|
+
args: ["--feature-dir", "{{feature_dir}}", "--source-files", "{{files}}", "--merge", "--lane", "frontend", "--origin", "greenfield"]
|
|
245
448
|
outputs:
|
|
246
449
|
- { name: okf_concept, from: "$.concept" }
|
|
247
450
|
|
|
248
|
-
#
|
|
451
|
+
# ═══════════════ ACCEPTANCE — the ONE place the spec is read ═════════════════
|
|
452
|
+
# READ-ONLY and REPORT-ONLY: it never edits code, never edits the pass, and never
|
|
453
|
+
# stops the run. A gap means one of two documents is wrong, and which one is a human
|
|
454
|
+
# decision — the message names both routes.
|
|
455
|
+
- id: ctx_accept
|
|
456
|
+
action: build_context
|
|
457
|
+
vars:
|
|
458
|
+
story_id: "{{story}}"
|
|
459
|
+
exercises_csv: "{{exercises_csv}}"
|
|
460
|
+
constraints_csv: "{{constraints_csv}}"
|
|
461
|
+
files_csv: "{{files}}"
|
|
462
|
+
context:
|
|
463
|
+
- { path: "{{story_path}}" } # the acceptance criteria
|
|
464
|
+
- { path: "{{spec_file}}" } # the contract — the ONLY step in this workflow that reads it
|
|
465
|
+
- { path: "{{design_doc}}" } # what was designed
|
|
466
|
+
- id: accept_check
|
|
467
|
+
action: agent_query
|
|
468
|
+
tools: Read # the generated files arrive as paths, not inlined
|
|
469
|
+
include_standards: false
|
|
470
|
+
schema: { ok: "<true|false>", gaps: "<one line per gap, or empty>" }
|
|
471
|
+
mock: { ok: "true", gaps: "" }
|
|
472
|
+
task: |
|
|
473
|
+
Story {{story_id}} was just implemented in the frontend lane. Judge ONLY this:
|
|
474
|
+
does the UI that now exists satisfy the requirements the story delivers, AS THE
|
|
475
|
+
SPEC WORDS THEM?
|
|
476
|
+
|
|
477
|
+
In scope: {{exercises_csv}} (delivered) and {{constraints_csv}} (must hold) — the
|
|
478
|
+
UX- ids among them are this lane's to realise. Resolve each id's wording from
|
|
479
|
+
spec.md in context; that wording, not the design's restatement of it, is what the
|
|
480
|
+
code is measured against.
|
|
481
|
+
|
|
482
|
+
Read the files this run wrote:
|
|
483
|
+
{{files_csv}}
|
|
484
|
+
plus anything they import that you need to follow. Do not modify a single file.
|
|
485
|
+
|
|
486
|
+
Report a gap ONLY where the UI fails to deliver an in-scope id as the spec words
|
|
487
|
+
it, or breaks an in-scope constraint. These are NOT gaps, and reporting them is
|
|
488
|
+
noise that hides the real ones:
|
|
489
|
+
- server-side behaviour — this lane cannot implement it and must not;
|
|
490
|
+
- a requirement no story has claimed yet, or one another story delivers;
|
|
491
|
+
- style, naming, structure or test-coverage opinions — other gates own those;
|
|
492
|
+
- anything the design pass deliberately deferred and named under its risks.
|
|
493
|
+
|
|
494
|
+
One line per gap: the id, what the spec requires, and what the UI does instead.
|
|
495
|
+
Set ok=false only when you found at least one. If the SPEC and the DESIGN disagree
|
|
496
|
+
about an in-scope id, that IS a gap — say which document you believe is wrong and
|
|
497
|
+
why, because the two routes out of it are different commands.
|
|
498
|
+
outputs:
|
|
499
|
+
- { name: accept_ok, from: "$.ok" }
|
|
500
|
+
- { name: accept_gaps, from: "$.gaps" }
|
|
501
|
+
- id: accept_notice
|
|
502
|
+
condition: "{{accept_ok}} != true"
|
|
503
|
+
action: verify_context
|
|
504
|
+
require_value: accept_ok
|
|
505
|
+
equals: "true"
|
|
506
|
+
on_mismatch: continue
|
|
507
|
+
on_mismatch_message: |
|
|
508
|
+
ACCEPTANCE — story {{story}}'s UI does not yet satisfy the spec wording of the ids it delivers:
|
|
509
|
+
|
|
510
|
+
{{accept_gaps}}
|
|
511
|
+
|
|
512
|
+
Nothing was changed to fix this: the code follows UI design pass {{design_pass}}, so a gap
|
|
513
|
+
here means either the design was wrong or the spec has moved. Route by which it is —
|
|
514
|
+
the design is wrong: /appweaver story-plan {{story}} (a new pass supersedes this one);
|
|
515
|
+
the spec is wrong: /appweaver sync-spec, then sync-stories, then story-plan.
|
|
516
|
+
Run /appweaver build either way — a design that is right and code that does not compile
|
|
517
|
+
is a third thing, and build is where that one surfaces.
|
|
518
|
+
|
|
519
|
+
# 9. Post-execution hooks.
|
|
249
520
|
- id: resolve_post_hooks
|
|
250
521
|
action: execute_script
|
|
251
522
|
script: { ref: actions }
|
|
@@ -258,10 +529,12 @@ workflow:
|
|
|
258
529
|
hooks_from_variable: after_hooks
|
|
259
530
|
|
|
260
531
|
outputs:
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
lane_source_path: "{{lane_source_path}}"
|
|
532
|
+
story: "{{story}}"
|
|
533
|
+
built_from: "{{design_doc}} — UI design pass {{design_pass}} (designed against spec {{design_spec_version}}; spec now {{current_version}})"
|
|
534
|
+
requirements: "{{exercises_csv}}"
|
|
265
535
|
files: "{{files}}"
|
|
266
|
-
|
|
267
|
-
|
|
536
|
+
lane: "{{lane_id}} → {{lane_source_path}}"
|
|
537
|
+
okf_concept: "{{okf_concept}} (this run's files MERGED into ui_source_files; the service lane's source_files is untouched)"
|
|
538
|
+
acceptance: "ok={{accept_ok}} · gaps: {{accept_gaps}}"
|
|
539
|
+
stale_ids: "{{stale_csv}}"
|
|
540
|
+
next: "/appweaver build — it compiles every declared lane, so a break in either surfaces there. Then /appweaver analyze-slice. The story stays OPEN until /appweaver close-story ratifies its ADRs, flips its interface entries live and archives the passes."
|