aw-spec-cli 0.1.20 → 0.1.22
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/ORCH-DIALECT.md +8 -3
- package/assets/.appweaver/STACK-PROFILES.md +87 -8
- package/assets/.appweaver/orchestrator/modules/delta-plan.orch.yaml +30 -0
- package/assets/.appweaver/orchestrator/modules/delta-spec.orch.yaml +22 -1
- package/assets/.appweaver/orchestrator/orchestrator.py +65 -3
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan-delta.orch.yaml +6 -6
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +144 -52
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +78 -10
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +283 -25
- package/assets/.appweaver/orchestrator/workflows/aw-spec-constitution.orch.yaml +4 -4
- package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +202 -40
- package/assets/.appweaver/orchestrator/workflows/aw-spec-foundation.orch.yaml +140 -36
- package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +557 -57
- package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +3 -3
- package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +39 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-recall-delta.orch.yaml +28 -7
- package/assets/.appweaver/orchestrator/workflows/aw-spec-select-stack.orch.yaml +47 -23
- package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +101 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-ui-model.orch.yaml +141 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-ui-prototype.orch.yaml +386 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-workflow-model.orch.yaml +135 -0
- package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +772 -216
- package/assets/.appweaver/scripts/bash/aw-xref.sh +4 -1
- package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +708 -209
- package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +4 -1
- package/assets/.appweaver/scripts/python/aw-crstate.py +319 -0
- package/assets/.appweaver/scripts/python/aw-foundation.py +151 -0
- package/assets/.appweaver/scripts/python/aw-gaps.py +119 -0
- package/assets/.appweaver/scripts/python/aw-payload-parity.py +19 -0
- package/assets/.appweaver/scripts/python/aw-plan-coverage.py +503 -0
- package/assets/.appweaver/scripts/python/aw-plangate.py +216 -0
- package/assets/.appweaver/scripts/python/aw-principles.py +108 -0
- package/assets/.appweaver/scripts/python/aw-stack.py +257 -28
- package/assets/.appweaver/scripts/python/aw-testlog.py +148 -0
- package/assets/.appweaver/scripts/python/aw-xref.py +141 -4
- package/assets/.appweaver/stack-profiles/angular.json +26 -0
- package/assets/.appweaver/stack-profiles/blazor-wasm.json +38 -0
- package/assets/.appweaver/stack-profiles/dotnet-webapi.json +39 -0
- package/assets/.appweaver/stack-profiles/java-spring.json +20 -0
- package/assets/.appweaver/stack-profiles/nextjs.json +26 -0
- package/assets/.appweaver/stack-profiles/node-express.json +27 -0
- package/assets/.appweaver/stack-profiles/python-fastapi.json +16 -0
- package/assets/.appweaver/stack-profiles/react-vite.json +26 -0
- package/assets/.appweaver/stack-profiles/sveltekit.json +26 -0
- package/assets/.appweaver/stack-profiles/vue-vite.json +26 -0
- package/assets/.appweaver/templates/constitution-domain-template.md +17 -2
- package/assets/.appweaver/templates/ui-model-template.md +67 -0
- package/assets/.appweaver/templates/workflow-model-template.md +62 -0
- package/assets/guardrails/projects/dotnet-default-project/domain-rule-bindings.md +10 -6
- package/assets/guardrails/projects/dotnet-default-project/tech-rule-bindings.md +4 -0
- package/assets/guardrails/technology/dotnet-webapi/auditing.md +3 -1
- package/assets/guardrails/technology/dotnet-webapi/coding-standards.md +5 -3
- package/assets/guardrails/technology/dotnet-webapi/cqrs.md +5 -1
- package/assets/guardrails/technology/dotnet-webapi/domain-driven-design.md +13 -5
- package/assets/guardrails/technology/dotnet-webapi/examples/cqrs.md +2 -0
- package/assets/guardrails/technology/dotnet-webapi/examples/domain-driven-design.md +6 -2
- package/assets/guardrails/technology/dotnet-webapi/framework-api-map.md +114 -0
- package/assets/guardrails/technology/dotnet-webapi/technology-constitution.md +3 -0
- package/assets/guardrails/technology/dotnet-webapi/testing.md +10 -0
- package/assets/guardrails/technology/nextjs/ui-constitution.md +8 -1
- package/package.json +1 -1
- package/assets/.appweaver/orchestrator/.state/smoke.state.json +0 -32
|
@@ -137,7 +137,7 @@
|
|
|
137
137
|
|
|
138
138
|
name: aw-spec-onboard
|
|
139
139
|
description: Onboard an existing codebase for brownfield DEVELOPMENT — a deterministic whole-repo scan produces a high-level OKF feature map (folders/concepts) PLUS the project-level governance scaffold (split constitution + guardrails/projects/brf-<domain>/ with its DOMAIN rule files at domain/ + domain-rule-bindings + code-derived TECHNICAL rule files at service/ + tech-rule-bindings + activated domain), all provisional-from-scan and refined per slice by adopt. Where the scan finds a frontend lane in the CODE, it also authors that lane's half — constitution.ui.md (UX-*) — and its code-derived rule files at brf-<domain>/frontend/, mapped under `## /aw-spec-ui-constitution`; a backend-only repo gets none of it. Every rule tier is reverse-engineered from the CODE alone (never a greenfield rule file), evidenced-only, and WIRED per-workflow so the whole forward pipeline AND adopt govern against THIS codebase — never a greenfield fallback. It also reverse-engineers the repo's own technical structure into a project-specific design + delta-design template pair, installed as the project template overrides, and — where the lane exists — the frontend lane's own pair from the UI source. Every authoring step is shaped by a NEUTRAL rev-templates skeleton; no greenfield constitution, rule file or document template is ever placed in context. Accepts an optional concerns list to look for (onboard decides which are actually present). Reads any declared lane, never writes one. No migration content; never modifies source.
|
|
140
|
-
version: 2.
|
|
140
|
+
version: 2.1.0
|
|
141
141
|
agent_agnostic: true
|
|
142
142
|
dialect: aw-spec.orchestrator/v1
|
|
143
143
|
|
|
@@ -1270,8 +1270,8 @@ workflow:
|
|
|
1270
1270
|
numeral is whatever the code supported — never a value carried over from another
|
|
1271
1271
|
constitution. If it mandates no modelling methodology, say so plainly rather than
|
|
1272
1272
|
supplying one.
|
|
1273
|
-
- Author ONLY the domain layer: domain principles, domain folder
|
|
1274
|
-
regulatory/security posture, domain governance — from the project + charter. NUMBER
|
|
1273
|
+
- Author ONLY the domain layer: project identity, domain principles, domain folder
|
|
1274
|
+
additions, regulatory/security posture, domain governance — from the project + charter. NUMBER
|
|
1275
1275
|
them by CONTINUING the technical half's roman numerals: READ
|
|
1276
1276
|
constitution.technical.md (in context), find its LAST numeral N, start the first
|
|
1277
1277
|
domain principle at N+1 (do NOT assume it ends at XV). TITLE each by DOMAIN
|
|
@@ -154,6 +154,10 @@ vars:
|
|
|
154
154
|
# answer; master_exists is what makes the menu appear at all.
|
|
155
155
|
master_exists: "false" # a DECLARED lane's master plan was already on disk
|
|
156
156
|
plan_target: "main" # main | delta — the answer to the one question this phase asks
|
|
157
|
+
# The OKF edges this run publishes from the master service plan (main path only).
|
|
158
|
+
record_master_edges: "false"
|
|
159
|
+
xref_edge_count: "0"
|
|
160
|
+
xref_stubbed: "[]" # used siblings that had no concept yet and were stubbed
|
|
157
161
|
plan_choice: "" # the raw selection (a label): the MAIN row, a change, or the NEW row
|
|
158
162
|
main_label: "" # cr-list --include-main's exact label, to test the selection against
|
|
159
163
|
new_label: "" # cr-list --include-new's
|
|
@@ -1069,6 +1073,41 @@ workflow:
|
|
|
1069
1073
|
action: include
|
|
1070
1074
|
module: .appweaver/orchestrator/modules/delta-plan.orch.yaml
|
|
1071
1075
|
|
|
1076
|
+
# 6b. CROSS-SLICE EDGES, from the document that states them. `aw-xref record` reads the
|
|
1077
|
+
# MASTER service plan — §1 References-by-id and the External integrations table — and
|
|
1078
|
+
# writes a Cross-References row into every sibling aggregate this one uses. Its input
|
|
1079
|
+
# is the plan and nothing else, yet it used to run only at implement: until a feature
|
|
1080
|
+
# was BUILT it contributed no edges, so `xref list` answered "0 downstream users" for
|
|
1081
|
+
# an aggregate a planned feature already depended on, and `xref tree` sorted the two
|
|
1082
|
+
# into the same wave. The information was in this document the whole time.
|
|
1083
|
+
#
|
|
1084
|
+
# MAIN PATH ONLY. On the delta path the master plan still holds the PRE-change
|
|
1085
|
+
# dependencies, so recording here would publish stale references; finalize-delta
|
|
1086
|
+
# records them after the change merges, which is the same reason implement skips it.
|
|
1087
|
+
# Reconcile-on-write, so re-recording at implement is idempotent and a dependency
|
|
1088
|
+
# dropped by a later re-derivation self-heals. Best-effort: never fails the authoring.
|
|
1089
|
+
- id: record_edges_clear
|
|
1090
|
+
action: set
|
|
1091
|
+
vars: { record_master_edges: "false" }
|
|
1092
|
+
- id: record_edges_main
|
|
1093
|
+
condition: "{{plan_target}} == main"
|
|
1094
|
+
action: set
|
|
1095
|
+
vars: { record_master_edges: "true" }
|
|
1096
|
+
- id: record_edges_no_lane
|
|
1097
|
+
condition: "{{be_mode}} == skip"
|
|
1098
|
+
action: set
|
|
1099
|
+
vars: { record_master_edges: "false" }
|
|
1100
|
+
- id: xref_record
|
|
1101
|
+
condition: "{{record_master_edges}} == true"
|
|
1102
|
+
action: execute_script
|
|
1103
|
+
on_error: continue
|
|
1104
|
+
script: { ref: xref }
|
|
1105
|
+
subcommand: record
|
|
1106
|
+
args: ["--feature-dir", "{{feature_dir}}", "--design", "{{backend_plan}}", "--origin", "auto"]
|
|
1107
|
+
outputs:
|
|
1108
|
+
- { name: xref_edge_count, from: "$.edge_count" }
|
|
1109
|
+
- { name: xref_stubbed, from: "$.stubbed" }
|
|
1110
|
+
|
|
1072
1111
|
# ═══════════ HANDOFF — authoring only; the gates are SEPARATE stages ═══════
|
|
1073
1112
|
# • MAIN (bootstrap or re-derivation) → /appweaver analyze-plan (spec↔plan
|
|
1074
1113
|
# conformance gate) → implement. No change is opened either way.
|
|
@@ -95,10 +95,14 @@ vars:
|
|
|
95
95
|
has_dependants: "false"
|
|
96
96
|
dependant_ids: ""
|
|
97
97
|
dependant_count: "0"
|
|
98
|
-
#
|
|
98
|
+
wave_problem: "" # which changes could not be ordered, and why that ends the check
|
|
99
|
+
unordered_ids: ""
|
|
100
|
+
unordered_count: "0"
|
|
101
|
+
# The three refusal flags, all DOWN by default and raised only by the good state — see
|
|
99
102
|
# the gates below for why fail-closed is the right default here.
|
|
100
103
|
not_merged: "false"
|
|
101
104
|
no_dependants: "false"
|
|
105
|
+
wave_ordering_ok: "false"
|
|
102
106
|
recall_confirmed: "false"
|
|
103
107
|
recalled: "false"
|
|
104
108
|
archived_dir: ""
|
|
@@ -170,7 +174,7 @@ workflow:
|
|
|
170
174
|
- { name: delta_model, from: "$.delta_model" }
|
|
171
175
|
- { name: wave, from: "$.wave" }
|
|
172
176
|
|
|
173
|
-
# 4. THE
|
|
177
|
+
# 4. THE THREE REFUSALS, all answered from disk in one call — see the header for why each
|
|
174
178
|
# one is a stop rather than a warning.
|
|
175
179
|
- id: recall_check
|
|
176
180
|
action: execute_script
|
|
@@ -178,11 +182,16 @@ workflow:
|
|
|
178
182
|
subcommand: cr-recall-check
|
|
179
183
|
args: ["--cr-id", "{{cr_choice}}", "--feature-dir", "{{feature_dir}}"]
|
|
180
184
|
outputs:
|
|
181
|
-
- { name: merged,
|
|
182
|
-
- { name: merge_evidence,
|
|
183
|
-
- { name: has_dependants,
|
|
184
|
-
- { name: dependant_ids,
|
|
185
|
-
- { name: dependant_count,
|
|
185
|
+
- { name: merged, from: "$.merged" }
|
|
186
|
+
- { name: merge_evidence, from: "$.merge_evidence" }
|
|
187
|
+
- { name: has_dependants, from: "$.has_dependants" }
|
|
188
|
+
- { name: dependant_ids, from: "$.dependant_ids" }
|
|
189
|
+
- { name: dependant_count, from: "$.dependant_count" }
|
|
190
|
+
- { name: wave, from: "$.wave" }
|
|
191
|
+
- { name: wave_ordering_ok, from: "$.wave_ordering_ok" }
|
|
192
|
+
- { name: wave_problem, from: "$.wave_problem" }
|
|
193
|
+
- { name: unordered_ids, from: "$.unordered_ids" }
|
|
194
|
+
- { name: unordered_count, from: "$.unordered_count" }
|
|
186
195
|
|
|
187
196
|
# Each refusal is a RAISE then a gate, not a conditional gate: the raise fires only on
|
|
188
197
|
# the good state, so a value that never arrived (the script errored, the field was
|
|
@@ -199,6 +208,18 @@ workflow:
|
|
|
199
208
|
on_false: stop
|
|
200
209
|
on_false_message: "Change {{cr_id}} has already been MERGED — /appweaver finalize-delta folded it into spec.md and the context's domain-model.md, and the spec's Change History carries it: {{merge_evidence}}. The masters now describe behaviour that includes this change, so withdrawing it would leave them describing work no open change accounts for. A recall cannot un-merge, and this stage will not pretend otherwise. If the change should not have landed, finish it (/appweaver analyze-slice → close-delta) and then open a NEW change that reverses it with /appweaver specify — that keeps both the landing and the reversal in the ledger, which is what a reader of the spec needs to see."
|
|
201
210
|
|
|
211
|
+
# WAVE ORDERING IS A PRECONDITION OF THE FENCE BELOW, not a finding of it. The dependant
|
|
212
|
+
# test is `their wave > mine`, and a wave that cannot be read used to answer that as 1, so
|
|
213
|
+
# an unstamped change failed the test and was not counted — recall then withdrew the
|
|
214
|
+
# ground from under a change it could not see, which is the failure this stage exists to
|
|
215
|
+
# prevent. Waves have no ceiling, so unlike the merge fence there is no floor that makes
|
|
216
|
+
# an unordered neighbour harmless: every one of them holds recall until it is stamped.
|
|
217
|
+
- id: require_wave_ordering
|
|
218
|
+
action: verify_context
|
|
219
|
+
require_true: wave_ordering_ok
|
|
220
|
+
on_false: stop
|
|
221
|
+
on_false_message: "Recall REFUSED — the wave fence cannot order change {{cr_id}}, so it cannot tell you what was built on top of it: {{wave_problem}}. Withdrawing a change that something else was planned against is exactly what this stage refuses to do blind. A wave is stamped by a script; `**Wave**: [N]` is the unfilled template line, which means the stamp never ran on that document. Stamp each one — `.appweaver/scripts/bash/aw-spec-actions.sh cr-wave-derive --cr-id <CR-id> --stamp` derives the wave from that change's own `## Depends on` section and writes it — then re-run /appweaver recall-delta {{cr_id}}."
|
|
222
|
+
|
|
202
223
|
- id: no_dependants_raise
|
|
203
224
|
condition: "{{has_dependants}} == false"
|
|
204
225
|
action: set
|
|
@@ -14,8 +14,13 @@
|
|
|
14
14
|
# evidence sits in, and both are only ever proposals — an operator selects from the
|
|
15
15
|
# list or types a path the kit has never seen.
|
|
16
16
|
#
|
|
17
|
-
# stack-list --suggest backend → select stack → ask source path → lane-declare
|
|
18
|
-
# stack-list --suggest frontend → select stack → ask source path → lane-declare
|
|
17
|
+
# stack-list --suggest backend → select stack → stack-list --profile → ask source path → lane-declare
|
|
18
|
+
# stack-list --suggest frontend → select stack → stack-list --profile → ask source path → lane-declare
|
|
19
|
+
#
|
|
20
|
+
# THE SOURCE-PATH OPTIONS ARE BUILT AFTER THE STACK IS CHOSEN. Most profiles of a kind
|
|
21
|
+
# share a default root (six frontend stacks default to web/), so a list built before the
|
|
22
|
+
# choice cannot say which stack an option belongs to. The second stack-list call answers
|
|
23
|
+
# for the chosen stack alone.
|
|
19
24
|
#
|
|
20
25
|
# (none) IS A REAL ANSWER FOR EITHER LANE, and it is how a project says "I do not
|
|
21
26
|
# have one of these". A backend-only project answers (none) to the UI lane; a
|
|
@@ -56,7 +61,7 @@
|
|
|
56
61
|
|
|
57
62
|
name: aw-spec-select-stack
|
|
58
63
|
description: Declare the project's lanes — each lane's STACK and its SOURCE PATH — and persist them to .appweaver/stacks.json, the single channel every later workflow resolves and gates on. The first command of both the greenfield and the brownfield sequence, and the sole writer of the lane model.
|
|
59
|
-
version: 2.
|
|
64
|
+
version: 2.3.0
|
|
60
65
|
agent_agnostic: true
|
|
61
66
|
dialect: aw-spec.orchestrator/v1
|
|
62
67
|
|
|
@@ -93,9 +98,7 @@ workflow:
|
|
|
93
98
|
- { name: api_options, from: "$.select_options" }
|
|
94
99
|
- { name: api_ids, from: "$.ids" }
|
|
95
100
|
- { name: api_preselect, from: "$.preselect" }
|
|
96
|
-
- { name:
|
|
97
|
-
- { name: api_detected, from: "$.detected_profile" }
|
|
98
|
-
- { name: api_detected_root, from: "$.detected_root" }
|
|
101
|
+
- { name: api_detected, from: "$.detected_summary" }
|
|
99
102
|
- { name: api_none_sentinel, from: "$.none_sentinel" }
|
|
100
103
|
|
|
101
104
|
# 2. No profiles installed is a setup error, not an empty menu. Gated on the
|
|
@@ -114,7 +117,7 @@ workflow:
|
|
|
114
117
|
# does a detected stack; a project with exactly one candidate auto-confirms.
|
|
115
118
|
- id: select_api_stack
|
|
116
119
|
action: user_select
|
|
117
|
-
prompt: "Select the API (backend) stack — it owns the service lane. This repo's own files point at: {{api_detected}}
|
|
120
|
+
prompt: "Select the API (backend) stack — it owns the service lane. This repo's own files point at: {{api_detected}}. A detected stack leads the list. (none) declares a project with no service lane at all, and stops the technical constitution half and the service rule files being authored:"
|
|
118
121
|
options_from_variable: api_options
|
|
119
122
|
preselect_from: api_preselect
|
|
120
123
|
single_option_confirm: true
|
|
@@ -133,14 +136,24 @@ workflow:
|
|
|
133
136
|
condition: "{{chosen_api}} == {{api_none_sentinel}}"
|
|
134
137
|
vars: { api_selected: "" }
|
|
135
138
|
|
|
136
|
-
# 5. WHERE the service lane's source lives. Offered best-first
|
|
137
|
-
# evidence
|
|
138
|
-
# accepted, because a repo that matches no
|
|
139
|
-
# The label after the path says why it is
|
|
139
|
+
# 5. WHERE the service lane's source lives. Offered best-first for the CHOSEN stack —
|
|
140
|
+
# where its evidence sits, then its own default, then where another stack's
|
|
141
|
+
# evidence sits — and free text is accepted, because a repo that matches no
|
|
142
|
+
# profile still has a source path. The label after the path says why it is
|
|
143
|
+
# offered and is stripped on the way in.
|
|
144
|
+
- id: list_api_roots
|
|
145
|
+
action: execute_script
|
|
146
|
+
condition: "{{api_selected}}"
|
|
147
|
+
script: { ref: actions }
|
|
148
|
+
subcommand: stack-list
|
|
149
|
+
args: ["--kind", "backend", "--suggest", "--profile", "{{chosen_api}}"]
|
|
150
|
+
outputs:
|
|
151
|
+
- { name: api_root_options, from: "$.root_options" }
|
|
152
|
+
|
|
140
153
|
- id: ask_api_source_path
|
|
141
154
|
action: ask
|
|
142
155
|
condition: "{{api_selected}}"
|
|
143
|
-
prompt: "Service lane SOURCE PATH — the subtree this lane owns,
|
|
156
|
+
prompt: "Service lane SOURCE PATH — the subtree this lane owns, where implement writes its code, and the directory build/test/run act on. Its tests go to the stack's test path beside it (tests/ next to src/ for .NET), shown in the summary. Pick one or type your own:"
|
|
144
157
|
options_from_variable: api_root_options
|
|
145
158
|
answer_var: api_source_path
|
|
146
159
|
|
|
@@ -159,6 +172,7 @@ workflow:
|
|
|
159
172
|
- { name: api_lane_id, from: "$.lane_id" }
|
|
160
173
|
- { name: api_profile, from: "$.profile" }
|
|
161
174
|
- { name: api_root, from: "$.source_path" }
|
|
175
|
+
- { name: api_test_path, from: "$.test_path" }
|
|
162
176
|
- { name: api_root_exists, from: "$.root_exists" }
|
|
163
177
|
|
|
164
178
|
# ═══════════════ UI LANE ════════════════════════════════════════════════════
|
|
@@ -176,15 +190,13 @@ workflow:
|
|
|
176
190
|
- { name: ui_options, from: "$.select_options" }
|
|
177
191
|
- { name: ui_ids, from: "$.ids" }
|
|
178
192
|
- { name: ui_preselect, from: "$.preselect" }
|
|
179
|
-
- { name:
|
|
180
|
-
- { name: ui_detected, from: "$.detected_profile" }
|
|
181
|
-
- { name: ui_detected_root, from: "$.detected_root" }
|
|
193
|
+
- { name: ui_detected, from: "$.detected_summary" }
|
|
182
194
|
- { name: ui_none_sentinel, from: "$.none_sentinel" }
|
|
183
195
|
|
|
184
196
|
# 8. Choose the UI stack.
|
|
185
197
|
- id: select_ui_stack
|
|
186
198
|
action: user_select
|
|
187
|
-
prompt: "Select the UI (frontend) stack — it owns the web lane, and its root must not overlap the service lane's. This repo's own files point at: {{ui_detected}}
|
|
199
|
+
prompt: "Select the UI (frontend) stack — it owns the web lane, and its root must not overlap the service lane's. This repo's own files point at: {{ui_detected}}. A detected stack leads the list. (none) declares a backend-only project, and stops the UI constitution half and the frontend rule files being authored:"
|
|
188
200
|
options_from_variable: ui_options
|
|
189
201
|
preselect_from: ui_preselect
|
|
190
202
|
single_option_confirm: true
|
|
@@ -199,7 +211,16 @@ workflow:
|
|
|
199
211
|
condition: "{{chosen_ui}} == {{ui_none_sentinel}}"
|
|
200
212
|
vars: { ui_selected: "" }
|
|
201
213
|
|
|
202
|
-
# 9. WHERE the UI lane's source lives.
|
|
214
|
+
# 9. WHERE the UI lane's source lives, offered for the chosen UI stack.
|
|
215
|
+
- id: list_ui_roots
|
|
216
|
+
action: execute_script
|
|
217
|
+
condition: "{{ui_selected}}"
|
|
218
|
+
script: { ref: actions }
|
|
219
|
+
subcommand: stack-list
|
|
220
|
+
args: ["--kind", "frontend", "--suggest", "--profile", "{{chosen_ui}}"]
|
|
221
|
+
outputs:
|
|
222
|
+
- { name: ui_root_options, from: "$.root_options" }
|
|
223
|
+
|
|
203
224
|
- id: ask_ui_source_path
|
|
204
225
|
action: ask
|
|
205
226
|
condition: "{{ui_selected}}"
|
|
@@ -217,6 +238,7 @@ workflow:
|
|
|
217
238
|
- { name: ui_lane_id, from: "$.lane_id" }
|
|
218
239
|
- { name: ui_profile, from: "$.profile" }
|
|
219
240
|
- { name: ui_root, from: "$.source_path" }
|
|
241
|
+
- { name: ui_test_path, from: "$.test_path" }
|
|
220
242
|
- { name: ui_root_exists, from: "$.root_exists" }
|
|
221
243
|
|
|
222
244
|
# ═══════════════ CHECK + REPORT ═════════════════════════════════════════════
|
|
@@ -257,7 +279,7 @@ workflow:
|
|
|
257
279
|
- id: api_summary_lane
|
|
258
280
|
action: set
|
|
259
281
|
condition: "{{api_declared}}"
|
|
260
|
-
vars: { api_summary: "{{api_lane_id}} ({{api_profile}}) at {{api_root}} — source path exists: {{api_root_exists}}" }
|
|
282
|
+
vars: { api_summary: "{{api_lane_id}} ({{api_profile}}) at {{api_root}}, tests at {{api_test_path}} — source path exists: {{api_root_exists}}" }
|
|
261
283
|
|
|
262
284
|
- id: ui_summary_none
|
|
263
285
|
action: set
|
|
@@ -265,7 +287,7 @@ workflow:
|
|
|
265
287
|
- id: ui_summary_lane
|
|
266
288
|
action: set
|
|
267
289
|
condition: "{{ui_declared}}"
|
|
268
|
-
vars: { ui_summary: "{{ui_lane_id}} ({{ui_profile}}) at {{ui_root}} — source path exists: {{ui_root_exists}}" }
|
|
290
|
+
vars: { ui_summary: "{{ui_lane_id}} ({{ui_profile}}) at {{ui_root}}, tests at {{ui_test_path}} — source path exists: {{ui_root_exists}}" }
|
|
269
291
|
|
|
270
292
|
# 12. Read the finished declaration back through the reader every later workflow
|
|
271
293
|
# uses, so what this run reports is what they will resolve.
|
|
@@ -287,10 +309,12 @@ outputs:
|
|
|
287
309
|
ui_lane: "{{ui_summary}}"
|
|
288
310
|
api_source_path: "{{api_root}}"
|
|
289
311
|
ui_source_path: "{{ui_root}}"
|
|
312
|
+
api_test_path: "{{api_test_path}}"
|
|
313
|
+
ui_test_path: "{{ui_test_path}}"
|
|
290
314
|
api_source_path_exists: "{{be_root_exists}}"
|
|
291
315
|
ui_source_path_exists: "{{fe_root_exists}}"
|
|
292
|
-
api_detected: "{{api_detected}}
|
|
293
|
-
ui_detected: "{{ui_detected}}
|
|
316
|
+
api_detected: "{{api_detected}}"
|
|
317
|
+
ui_detected: "{{ui_detected}}"
|
|
294
318
|
prototype: "{{prototype}}"
|
|
295
|
-
note: "Lanes persisted to .appweaver/stacks.json. Each lane carries a STACK and a SOURCE PATH, and both are load-bearing. The SOURCE PATH is ownership: implement's service lane writes only under the service lane's and its frontend lane only under the UI lane's, build/test/run act on it, and onboard reverse-engineers out of it —
|
|
296
|
-
next: "GREENFIELD: /appweaver select-domain → constitution → select-feature → specify. BROWNFIELD: /appweaver onboard — it reverse-engineers out of the source paths declared here and generates the project (no select-domain; onboard derives and activates it from the code). Run this workflow FIRST in either sequence: constitution reads the declaration to decide which halves to author, and onboard reads it to decide what to scan."
|
|
319
|
+
note: "Lanes persisted to .appweaver/stacks.json. Each lane carries a STACK and a SOURCE PATH, and both are load-bearing. The SOURCE PATH is ownership: implement's service lane writes its code only under the service lane's and its frontend lane only under the UI lane's, build/test/run act on it, and onboard reverse-engineers out of it. Each lane also owns a TEST PATH, taken from its stack profile — tests/ beside a .NET src/, the source path itself for a stack that keeps its tests inside — and implement writes the lane's tests there. Overlapping paths of either kind are refused here rather than discovered later as two generators writing one file. The build/test/run commands are project-supplied strings the engine shells out to, which is how it stays stdlib-only and stack-ignorant. api_detected / ui_detected report what this repo's own files pointed at, whether or not it was chosen, as "<stack> at <path>" — or "nothing detected" on a greenfield repo with nothing to detect. A source path that does not exist yet is NORMAL here and reported rather than refused: greenfield declares src/ before implement creates it. The on-disk check runs in the phases that read the subtree — implement, build, run and onboard all call lane-verify and STOP with the fix named. Declining a lane with (none) is a real declaration and removes any lane of that kind a previous run left behind; it is also what stops that lane's constitution half and rule files being authored. `prototype` is not a lane — it has a root and a run command so an operator can open it, and nothing else. Re-run this workflow any time to re-stack a lane, move a source path, or drop a lane with (none); it is the only workflow that may write this file."
|
|
320
|
+
next: "GREENFIELD: /appweaver foundation — once per lane, to scaffold a compiling project into each source path declared here (skip it for a lane whose code already exists, or scaffold with the stack's own tooling) — then /appweaver select-domain → constitution → select-feature → specify. BROWNFIELD: /appweaver onboard — it reverse-engineers out of the source paths declared here and generates the project (no select-domain; onboard derives and activates it from the code). Run this workflow FIRST in either sequence: foundation unpacks into the source paths declared here, constitution reads the declaration to decide which halves to author, and onboard reads it to decide what to scan."
|
|
@@ -107,6 +107,14 @@ standards:
|
|
|
107
107
|
|
|
108
108
|
vars:
|
|
109
109
|
spec_all_pass: "false" # validation-loop guard, flipped by the validate query
|
|
110
|
+
nn_count: "0" # ratified NON-NEGOTIABLE principles, from principles-list.
|
|
111
|
+
# 0 = a project that ratified none, which skips the gate.
|
|
112
|
+
nn_titles: "" # those principles, for the judge's prompt
|
|
113
|
+
nn_compliant: "false" # per-clarification verdict. DEFAULTS TO REFUSE: the gate
|
|
114
|
+
# reads it, so a judge that never answered stops the run
|
|
115
|
+
# rather than passing an unexamined answer through.
|
|
116
|
+
nn_principle: "" # the principle a departing answer was judged against
|
|
117
|
+
nn_why: "" # what it requires and what the answer does instead
|
|
110
118
|
checklist_total: "0" # bound by checklist-read
|
|
111
119
|
checklist_passed: "0" # stays 0 when the checklist has no items to score
|
|
112
120
|
cr_id: "" # set on the DELTA path only
|
|
@@ -520,6 +528,23 @@ workflow:
|
|
|
520
528
|
|
|
521
529
|
# 6. Resolve [NEEDS CLARIFICATION] markers interactively (≤3). Non-interactive
|
|
522
530
|
# runs answer with the first/recommended option (a documented default).
|
|
531
|
+
#
|
|
532
|
+
# THE DEFAULT IS WHY THIS IS GOVERNED. A non-interactive run answers with options[0],
|
|
533
|
+
# so whatever the extraction listed first becomes project policy unattended — and a
|
|
534
|
+
# clarification is asked at exactly the altitude where a ratified principle already
|
|
535
|
+
# has an answer. The constitution halves ARE in context here (build_context put them
|
|
536
|
+
# in the envelope, which include_standards: false does not touch), but nothing told
|
|
537
|
+
# the extraction that a principle outranks a question, and nothing checked the answer
|
|
538
|
+
# afterwards. Both halves of that are below: the ordering rule in the task, and the
|
|
539
|
+
# per-answer gate inside the loop.
|
|
540
|
+
- id: nn_principles
|
|
541
|
+
action: execute_script
|
|
542
|
+
script: { ref: actions }
|
|
543
|
+
subcommand: principles-list
|
|
544
|
+
outputs:
|
|
545
|
+
- { name: nn_count, from: "$.count" }
|
|
546
|
+
- { name: nn_titles, from: "$.titles_csv" }
|
|
547
|
+
|
|
523
548
|
- id: extract_clarifications
|
|
524
549
|
action: agent_query
|
|
525
550
|
context: ["{{quality_target}}"]
|
|
@@ -528,6 +553,15 @@ workflow:
|
|
|
528
553
|
Extract up to 3 unresolved "[NEEDS CLARIFICATION: <question>]" markers from
|
|
529
554
|
the document in context (a feature spec, or one change's delta-spec), most
|
|
530
555
|
scope/security/UX-impacting first. Return {"clarifications":[]} if there are none.
|
|
556
|
+
|
|
557
|
+
A RATIFIED PRINCIPLE IS NOT A QUESTION. The constitution halves in context carry
|
|
558
|
+
principles marked (NON-NEGOTIABLE). Where one of them already settles a marker,
|
|
559
|
+
it is not a clarification: DROP it from the list — the answer is the principle,
|
|
560
|
+
and the document is corrected to match it in the quality loop. Offering it as a
|
|
561
|
+
choice invites a project to vote on something already ratified.
|
|
562
|
+
For a marker that merely TOUCHES such a principle, every option you list must be
|
|
563
|
+
compatible with it, and the most compliant option must be FIRST — an unattended
|
|
564
|
+
run takes the first one.
|
|
531
565
|
schema: { clarifications: [ { id: c1, question: "<the question>", options: ["<answer A>", "<answer B>", "<answer C>"] } ] }
|
|
532
566
|
mock: { clarifications: [] }
|
|
533
567
|
outputs:
|
|
@@ -545,6 +579,72 @@ workflow:
|
|
|
545
579
|
options_from: "{{c.options}}"
|
|
546
580
|
answer_key: "{{c.id}}"
|
|
547
581
|
answer_var: c_answer
|
|
582
|
+
|
|
583
|
+
# THE ANSWER IS CHECKED BEFORE IT IS WRITTEN IN. Placed between the ask and the
|
|
584
|
+
# rewrite on purpose: once resolve_clar has folded the answer into a requirement it
|
|
585
|
+
# is ordinary spec prose, and catching it then means unpicking a document rather
|
|
586
|
+
# than declining an answer. Judged one answer at a time so the stop can quote the
|
|
587
|
+
# question that caused it.
|
|
588
|
+
#
|
|
589
|
+
# The agent judges ONLY against the principles nn_principles found — a citation
|
|
590
|
+
# outside that list is visibly wrong, which is the point of enumerating them with a
|
|
591
|
+
# script instead of asking one agent to both find the principle and object to it.
|
|
592
|
+
#
|
|
593
|
+
# ASKED IN THE POSITIVE, AND RESET FIRST. The gate below reads nn_compliant, so an
|
|
594
|
+
# answer the judge never delivered — a malformed reply, a step that did not run —
|
|
595
|
+
# leaves "false" standing and STOPS. Asking "does it contradict?" would invert that:
|
|
596
|
+
# the same silence would read as no contradiction found. The reset is what makes it
|
|
597
|
+
# per-iteration; vars outlive a loop pass, so without it the second clarification
|
|
598
|
+
# would inherit the first one's verdict.
|
|
599
|
+
- id: governance_reset
|
|
600
|
+
action: set
|
|
601
|
+
vars:
|
|
602
|
+
nn_compliant: "false"
|
|
603
|
+
nn_principle: ""
|
|
604
|
+
nn_why: ""
|
|
605
|
+
|
|
606
|
+
# Skipped where a project ratified no NON-NEGOTIABLE principle: with nothing to
|
|
607
|
+
# depart from there is nothing to ask, and the gate is skipped on the same
|
|
608
|
+
# condition rather than reading the "false" this reset just wrote.
|
|
609
|
+
- id: governance_check
|
|
610
|
+
condition: "{{nn_count}} != 0"
|
|
611
|
+
action: agent_query
|
|
612
|
+
context: ["{{quality_target}}"]
|
|
613
|
+
include_standards: false
|
|
614
|
+
task: |
|
|
615
|
+
The constitution halves in context ratify these NON-NEGOTIABLE principles:
|
|
616
|
+
{{nn_titles}}
|
|
617
|
+
A question was raised about this specification and has just been answered:
|
|
618
|
+
Question: {{c.question}}
|
|
619
|
+
Answer: {{c_answer}}
|
|
620
|
+
Is that answer COMPLIANT with those principles — that is, can the specification
|
|
621
|
+
be amended to say it without asserting something a principle forbids or omitting
|
|
622
|
+
something a principle requires?
|
|
623
|
+
Judge only against the principles listed above, and when it is not compliant
|
|
624
|
+
cite the one it departs from by its id. A principle the question does not engage
|
|
625
|
+
is not a departure, and neither is an answer a principle simply does not speak
|
|
626
|
+
to; where none is engaged the answer is compliant. Report a departure only where
|
|
627
|
+
you can state what the principle requires and what the answer does instead.
|
|
628
|
+
schema: { compliant: true, principle: "<id, or empty>", why: "<one sentence, or empty>" }
|
|
629
|
+
mock: { compliant: true, principle: "", why: "" }
|
|
630
|
+
outputs:
|
|
631
|
+
- { name: nn_compliant, from: "$.compliant" }
|
|
632
|
+
- { name: nn_principle, from: "$.principle" }
|
|
633
|
+
- { name: nn_why, from: "$.why" }
|
|
634
|
+
|
|
635
|
+
# FAIL CLOSED. The constitution's own Governance says a deviation with no named
|
|
636
|
+
# approver is a defect, not a decision — and this answer has no approver at all: it
|
|
637
|
+
# was settled in the run, by an operator picking from a menu or by a non-interactive
|
|
638
|
+
# default. Recording it and carrying on is how an unapproved departure from a
|
|
639
|
+
# NON-NEGOTIABLE principle reaches code, so the run stops here and the choice goes
|
|
640
|
+
# back to a human who can either comply or amend the constitution properly.
|
|
641
|
+
- id: governance_gate
|
|
642
|
+
condition: "{{nn_count}} != 0"
|
|
643
|
+
action: verify_context
|
|
644
|
+
require_true: nn_compliant
|
|
645
|
+
on_false: stop
|
|
646
|
+
on_false_message: "The answer to \"{{c.question}}\" departs from NON-NEGOTIABLE principle {{nn_principle}}: {{nn_why}}. That principle is ratified in .appweaver/memory/ and this answer has no approver — it was settled in this run, not by a domain owner, and the constitution's Governance section holds that a deviation with no named approver is a defect rather than a decision. Re-run specify and answer in a way the principle allows; if the project genuinely intends to depart from it, amend the constitution through its own amendment process FIRST (which names the approver the departure needs) and re-run. Nothing was written to {{quality_target}} for this clarification."
|
|
647
|
+
|
|
548
648
|
- id: resolve_clar
|
|
549
649
|
action: agent_author
|
|
550
650
|
context: ["{{quality_target}}"]
|
|
@@ -670,5 +770,5 @@ outputs:
|
|
|
670
770
|
authored_document: "{{quality_target}}" # spec.md (bootstrap) | delta-spec.md (delta)
|
|
671
771
|
checklist_file: "{{checklist_file}}"
|
|
672
772
|
checklist_quality: "{{checklist_passed}}/{{checklist_total}} items passing"
|
|
673
|
-
note: "Behaviour and structure were authored together: the spec says what the system must do, {{domain_model}} says what the domain IS. The aggregate root, its entities and its invariants (BR-n) live in the model and are cited from the spec by id — never restated there, because several specs act on one aggregate and a per-spec copy drifts. On the DELTA path neither master was touched: this run wrote changes/{{cr_id}}/delta-spec.md and {{delta_model}}, and that model delta sits BESIDE the master where every other open delta can see it while being authored. WAVE {{wave}} is its parallel-development level — deltas sharing a wave are independent, a higher wave is implemented only after the lower ones land
|
|
773
|
+
note: "Behaviour and structure were authored together: the spec says what the system must do, {{domain_model}} says what the domain IS. The aggregate root, its entities and its invariants (BR-n) live in the model and are cited from the spec by id — never restated there, because several specs act on one aggregate and a per-spec copy drifts. On the DELTA path neither master was touched: this run wrote changes/{{cr_id}}/delta-spec.md and {{delta_model}}, and that model delta sits BESIDE the master where every other open delta can see it while being authored. WAVE {{wave}} is its parallel-development level — deltas sharing a wave are independent, a higher wave is implemented only after the lower ones land. The wave is DERIVED from the model delta's `## Depends on`: one above the highest delta named there, stamped into both halves as soon as it was authored ({{wave_message}}). To move it, correct that section and re-run `.appweaver/scripts/bash/aw-spec-actions.sh cr-wave-derive --cr-id {{cr_id}} --stamp`; finalize-delta refuses a change whose wave contradicts it. {{open_count}} change(s) are open on this feature."
|
|
674
774
|
next: "/appweaver plan — BOOTSTRAP authors backend-plan.md from the spec + the domain model; a DELTA (this run opened change {{cr_id}}, wave {{wave}}) authors the delta plans for it. A change then flows: analyze-plan-delta → implement → build → analyze-slice-delta → finalize-delta → analyze-slice → close-delta (finalize-delta merges the delta into spec.md + domain-model.md — not the lane plans, which are derived from those two — but leaves the change open; analyze-slice verifies the slice against the merged master; close-delta archives the change and bumps the version). ⚠️ With more than one change open, plan, analyze-plan-delta and implement ASK which one they are for (pass the id as the argument to skip the prompt) and activate the answer; finalize-delta and close-delta still act on whichever change is active — so finish or close the others deliberately."
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# aw-spec-ui-model (orchestrator) — Derive UI Model from Workflow Model
|
|
3
|
+
#
|
|
4
|
+
# Stage 3 of the 4-Stage Spec-Driven UI Generation Pipeline.
|
|
5
|
+
# Consumes `specs/<Feature>/ui/workflow-model.md` and maps workflows into concrete UI surfaces
|
|
6
|
+
# (Pages, Sub-Pages, Drawers, Modals, Dialogs), interaction states (Loading, Empty, Populated, Filtered, Error, Success),
|
|
7
|
+
# user actions, and surface transitions.
|
|
8
|
+
# Writes output artifact to `specs/<Feature>/ui/ui-model.md`.
|
|
9
|
+
#
|
|
10
|
+
# Launch:
|
|
11
|
+
# /appweaver ui-model [strategy] [guidance]
|
|
12
|
+
# =============================================================================
|
|
13
|
+
|
|
14
|
+
name: aw-spec-ui-model
|
|
15
|
+
description: Derive the UI Model (specs/<Feature>/ui/ui-model.md) from workflow-model.md, specifying UI surfaces (pages, dialogs, drawers, modals), routes, interaction states, actions, and navigation transitions.
|
|
16
|
+
version: 1.0.0
|
|
17
|
+
agent_agnostic: true
|
|
18
|
+
dialect: aw-spec.orchestrator/v1
|
|
19
|
+
|
|
20
|
+
inputs:
|
|
21
|
+
user_input:
|
|
22
|
+
description: Optional UI layout or surface guidance.
|
|
23
|
+
required: false
|
|
24
|
+
bind: USER_INPUT
|
|
25
|
+
|
|
26
|
+
scripts:
|
|
27
|
+
actions:
|
|
28
|
+
sh: .appweaver/scripts/bash/aw-spec-actions.sh
|
|
29
|
+
ps1: .appweaver/scripts/powershell/aw-spec-actions.ps1
|
|
30
|
+
|
|
31
|
+
vars:
|
|
32
|
+
feature: ""
|
|
33
|
+
feature_dir: ""
|
|
34
|
+
feature_slug: ""
|
|
35
|
+
spec_file: ""
|
|
36
|
+
spec_exists: "false"
|
|
37
|
+
workflow_model_file: ""
|
|
38
|
+
workflow_model_exists: "false"
|
|
39
|
+
active_change: ""
|
|
40
|
+
has_change: "false"
|
|
41
|
+
domain: ""
|
|
42
|
+
prototype_root: ""
|
|
43
|
+
|
|
44
|
+
workflow:
|
|
45
|
+
|
|
46
|
+
# 1. Resolve active feature and paths
|
|
47
|
+
- id: feature_paths
|
|
48
|
+
action: execute_script
|
|
49
|
+
on_error: continue
|
|
50
|
+
script: { ref: actions }
|
|
51
|
+
subcommand: feature-paths
|
|
52
|
+
outputs:
|
|
53
|
+
- { name: feature, from: "$.feature" }
|
|
54
|
+
- { name: feature_dir, from: "$.feature_dir" }
|
|
55
|
+
- { name: feature_slug, from: "$.feature_slug" }
|
|
56
|
+
- { name: spec_file, from: "$.spec_file" }
|
|
57
|
+
- { name: spec_exists, from: "$.spec_exists" }
|
|
58
|
+
- { name: domain, from: "$.domain" }
|
|
59
|
+
- { name: prototype_root, from: "$.prototype_root" }
|
|
60
|
+
|
|
61
|
+
- id: require_feature
|
|
62
|
+
action: verify_context
|
|
63
|
+
require_non_empty: feature_dir
|
|
64
|
+
on_empty: stop
|
|
65
|
+
on_empty_message: "No active feature selected. Run /appweaver select-feature first."
|
|
66
|
+
|
|
67
|
+
- id: require_spec
|
|
68
|
+
action: verify_context
|
|
69
|
+
require_true: spec_exists
|
|
70
|
+
on_false: stop
|
|
71
|
+
on_false_message: "No specification found for this feature. Run /appweaver specify first."
|
|
72
|
+
|
|
73
|
+
# 2. Check workflow-model.md existence
|
|
74
|
+
- id: check_workflow_model
|
|
75
|
+
action: execute_script
|
|
76
|
+
on_error: continue
|
|
77
|
+
script: { ref: actions }
|
|
78
|
+
subcommand: path-exists
|
|
79
|
+
args: ["--path", "{{feature_dir}}/ui/workflow-model.md"]
|
|
80
|
+
outputs:
|
|
81
|
+
- { name: workflow_model_exists, from: "$.exists" }
|
|
82
|
+
|
|
83
|
+
- id: require_workflow_model
|
|
84
|
+
action: verify_context
|
|
85
|
+
require_true: workflow_model_exists
|
|
86
|
+
on_false: stop
|
|
87
|
+
on_false_message: "No Workflow Model found at specs/{{feature}}/ui/workflow-model.md. Run /appweaver workflow-model first."
|
|
88
|
+
|
|
89
|
+
# 3. Assemble workflow model & template context
|
|
90
|
+
- id: ctx_ui_model
|
|
91
|
+
action: build_context
|
|
92
|
+
vars:
|
|
93
|
+
feature_name: "{{feature}}"
|
|
94
|
+
feature_dir: "{{feature_dir}}"
|
|
95
|
+
user_preferences: "{{USER_INPUT}}"
|
|
96
|
+
context:
|
|
97
|
+
- "{{feature_dir}}/ui/workflow-model.md"
|
|
98
|
+
- "{{spec_file}}"
|
|
99
|
+
- { path: ".appweaver/templates/ui-model-template.md", optional: true }
|
|
100
|
+
- { path: ".appweaver/memory/constitution.ui.md", optional: true }
|
|
101
|
+
|
|
102
|
+
# 4. Generate UI Model Artifact (specs/<Feature>/ui/ui-model.md)
|
|
103
|
+
- id: generate_ui_model
|
|
104
|
+
action: agent_generate
|
|
105
|
+
tools: "Read,Write,Edit,Bash"
|
|
106
|
+
permission_mode: acceptEdits
|
|
107
|
+
task: |
|
|
108
|
+
You are authoring Stage 3 (UI Model) of the 4-Stage Spec-Driven UI Pipeline for feature `{{feature_name}}`.
|
|
109
|
+
|
|
110
|
+
Target Artifact Path: `{{feature_dir}}/ui/ui-model.md`
|
|
111
|
+
|
|
112
|
+
Instructions:
|
|
113
|
+
1. Read `specs/{{feature}}/ui/workflow-model.md` and `spec.md` in context.
|
|
114
|
+
2. Translate EVERY workflow (WF-1, WF-2, ...) into explicit **UI Surfaces**:
|
|
115
|
+
- Determine the appropriate UI surface type for each workflow step: `Page`, `Sub-Page`, `Dialog`, `Drawer`, `Modal`, `View`.
|
|
116
|
+
- Map routes or component mount paths (e.g. `/{{feature_slug}}`, `/{{feature_slug}}/create`, modal dialog triggers).
|
|
117
|
+
- Define supported interaction states per surface (`Loading`, `Empty`, `Populated`, `Filtered / Searched`, `Validation Error`, `Submitting`, `Success`, `Error`).
|
|
118
|
+
- Specify interactive controls, form fields, action buttons, and table/card layouts.
|
|
119
|
+
- Map explicit navigation & transition triggers (`On Action -> Opens Surface X`).
|
|
120
|
+
3. Remember the core rule: **Workflow != Single Screen**. A workflow can span multiple surfaces or reuse surfaces with state changes.
|
|
121
|
+
4. Format output according to `.appweaver/templates/ui-model-template.md`.
|
|
122
|
+
5. Write the authored markdown content to `{{feature_dir}}/ui/ui-model.md`.
|
|
123
|
+
|
|
124
|
+
User Guidance:
|
|
125
|
+
{{user_preferences}}
|
|
126
|
+
|
|
127
|
+
End your final reply with a summary and a ```json fence:
|
|
128
|
+
```json
|
|
129
|
+
{
|
|
130
|
+
"status": "ok",
|
|
131
|
+
"feature": "{{feature_name}}",
|
|
132
|
+
"ui_model_path": "{{feature_dir}}/ui/ui-model.md"
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
outputs:
|
|
136
|
+
- { name: result, from: "$" }
|
|
137
|
+
|
|
138
|
+
outputs:
|
|
139
|
+
feature: "{{feature}}"
|
|
140
|
+
ui_model_path: "{{feature_dir}}/ui/ui-model.md"
|
|
141
|
+
next: "Run /appweaver ui-prototype to generate the executable Next.js prototype application."
|