aw-spec-cli 0.1.15 → 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/orchestrator/modules/spec-state-gate.orch.yaml +87 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-design.orch.yaml +83 -19
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +88 -19
- package/assets/.appweaver/orchestrator/workflows/aw-spec-implement-ui.orch.yaml +131 -8
- package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +126 -7
- package/assets/.appweaver/orchestrator/workflows/aw-spec-plan-ui.orch.yaml +28 -25
- package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +48 -15
- package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +97 -10
- package/assets/.appweaver/orchestrator/workflows/aw-spec-story-plan.orch.yaml +216 -14
- package/assets/.appweaver/orchestrator/workflows/aw-spec-sync-stories.orch.yaml +108 -27
- package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +515 -30
- package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +504 -28
- package/assets/.appweaver/scripts/python/aw-payload-parity.py +158 -0
- package/assets/.appweaver/scripts/python/aw-xref.py +205 -48
- package/assets/.appweaver/templates/story-template.md +37 -6
- package/package.json +1 -1
|
@@ -104,6 +104,23 @@ vars:
|
|
|
104
104
|
design_pass: "0"
|
|
105
105
|
design_spec_version: ""
|
|
106
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."
|
|
107
124
|
service_design_doc: ""
|
|
108
125
|
# the feature's current code, per lane (OKF)
|
|
109
126
|
okf_concept: ""
|
|
@@ -142,6 +159,13 @@ workflow:
|
|
|
142
159
|
on_empty: stop
|
|
143
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."
|
|
144
161
|
|
|
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
|
+
|
|
145
169
|
# 2. WHICH STORY — the same resolution story-plan and implement use.
|
|
146
170
|
- id: story_scan
|
|
147
171
|
action: execute_script
|
|
@@ -173,6 +197,11 @@ workflow:
|
|
|
173
197
|
- { name: design_pass, from: "$.fe_last_pass" }
|
|
174
198
|
- { name: design_spec_version, from: "$.fe_doc_spec_version" }
|
|
175
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" }
|
|
176
205
|
- { name: service_design_doc, from: "$.be_doc_path" }
|
|
177
206
|
- id: require_open_backlog
|
|
178
207
|
action: verify_context
|
|
@@ -212,6 +241,11 @@ workflow:
|
|
|
212
241
|
- { name: design_pass, from: "$.fe_last_pass" }
|
|
213
242
|
- { name: design_spec_version, from: "$.fe_doc_spec_version" }
|
|
214
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" }
|
|
215
249
|
- { name: service_design_doc, from: "$.be_doc_path" }
|
|
216
250
|
- id: require_story
|
|
217
251
|
action: verify_context
|
|
@@ -230,11 +264,34 @@ workflow:
|
|
|
230
264
|
# 3. THE STORY MUST HAVE A UI DESIGN. A story with a service design and no UI one is
|
|
231
265
|
# not a failure — it is a slice with no user-facing surface, and the message says
|
|
232
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
|
+
|
|
233
290
|
- id: require_design
|
|
234
291
|
action: verify_context
|
|
235
292
|
require_true: design_exists
|
|
236
293
|
on_false: stop
|
|
237
|
-
on_false_message: "
|
|
294
|
+
on_false_message: "{{no_design_msg}}"
|
|
238
295
|
|
|
239
296
|
# 3a. Announced, never refused — the same contract implement keeps. design_stale is a
|
|
240
297
|
# JSON boolean, so `condition:` does the deciding and the value gate is only the
|
|
@@ -253,6 +310,62 @@ workflow:
|
|
|
253
310
|
on_mismatch: continue
|
|
254
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."
|
|
255
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
|
+
|
|
256
369
|
# 4. THE FRONTEND LANE and its source path — the only subtree this phase may write
|
|
257
370
|
# into, resolved before the project gate that searches it.
|
|
258
371
|
- id: lane_gate
|
|
@@ -353,9 +466,12 @@ workflow:
|
|
|
353
466
|
create and what to modify. Create ONLY the files it names as new; modify the
|
|
354
467
|
named existing files IN PLACE. Every other file belongs to work already done —
|
|
355
468
|
do not regenerate, reformat or "tidy" it.
|
|
356
|
-
- The story delivers {{exercises_csv}} and must satisfy {{constraints_csv}}
|
|
357
|
-
|
|
358
|
-
|
|
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.
|
|
359
475
|
- The code that already exists for this feature, per lane, is below. The UI files
|
|
360
476
|
({{ui_source_file_count}} on disk) are yours to read before you edit; the
|
|
361
477
|
service list is there so you can see what the contract is actually backed by.
|
|
@@ -444,9 +560,16 @@ workflow:
|
|
|
444
560
|
action: execute_script
|
|
445
561
|
script: { ref: xref }
|
|
446
562
|
subcommand: concept-upsert
|
|
447
|
-
|
|
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}}"]
|
|
448
570
|
outputs:
|
|
449
|
-
- { name: okf_concept,
|
|
571
|
+
- { name: okf_concept, from: "$.concept" }
|
|
572
|
+
- { name: okf_anchors, from: "$.anchors_written" }
|
|
450
573
|
|
|
451
574
|
# ═══════════════ ACCEPTANCE — the ONE place the spec is read ═════════════════
|
|
452
575
|
# READ-ONLY and REPORT-ONLY: it never edits code, never edits the pass, and never
|
|
@@ -512,7 +635,7 @@ workflow:
|
|
|
512
635
|
Nothing was changed to fix this: the code follows UI design pass {{design_pass}}, so a gap
|
|
513
636
|
here means either the design was wrong or the spec has moved. Route by which it is —
|
|
514
637
|
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.
|
|
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.
|
|
516
639
|
Run /appweaver build either way — a design that is right and code that does not compile
|
|
517
640
|
is a third thing, and build is where that one surfaces.
|
|
518
641
|
|
|
@@ -537,4 +660,4 @@ outputs:
|
|
|
537
660
|
okf_concept: "{{okf_concept}} (this run's files MERGED into ui_source_files; the service lane's source_files is untouched)"
|
|
538
661
|
acceptance: "ok={{accept_ok}} · gaps: {{accept_gaps}}"
|
|
539
662
|
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
|
|
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."
|
|
@@ -94,6 +94,12 @@ vars:
|
|
|
94
94
|
story_title: ""
|
|
95
95
|
exercises_csv: ""
|
|
96
96
|
constraints_csv: ""
|
|
97
|
+
# UNASKED IS NOT UNMATCHED. detect-project answers true whenever no --ecosystem was
|
|
98
|
+
# given, and the gate below only reads it on the repo-scope fallback — so the default
|
|
99
|
+
# has to be permissive or a probe that could not run would refuse a scaffolded lane.
|
|
100
|
+
ecosystem_match: "true"
|
|
101
|
+
project_scope: ""
|
|
102
|
+
project_markers: ""
|
|
97
103
|
current_version: ""
|
|
98
104
|
stale_csv: ""
|
|
99
105
|
stale_note: ""
|
|
@@ -106,6 +112,16 @@ vars:
|
|
|
106
112
|
design_pass: "0"
|
|
107
113
|
design_spec_version: ""
|
|
108
114
|
design_stale: "false"
|
|
115
|
+
# the analyze-design verdict — law 40's evidence. certified defaults FALSE so a probe that
|
|
116
|
+
# could not run fails CLOSED, and the message default carries a route for the same reason.
|
|
117
|
+
pass_plan_hash: ""
|
|
118
|
+
gate_status: ""
|
|
119
|
+
gate_certified: "false"
|
|
120
|
+
gate_aborted: "false"
|
|
121
|
+
gate_gap_refs: ""
|
|
122
|
+
gate_report: ""
|
|
123
|
+
gate_lane_sha: ""
|
|
124
|
+
gate_msg: "The analyze-design verdict could not be read, so this run cannot tell whether the feature plans were ever certified. Run /appweaver analyze-design, then re-run."
|
|
109
125
|
# the feature's current code, per lane (OKF)
|
|
110
126
|
okf_concept: ""
|
|
111
127
|
coverage_digest: ""
|
|
@@ -149,6 +165,15 @@ workflow:
|
|
|
149
165
|
on_empty: stop
|
|
150
166
|
on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the aggregate, e.g. Order), then re-run implement."
|
|
151
167
|
|
|
168
|
+
# 1b. THE LEDGER FENCE. Generation is design-only by contract, but this run reads the
|
|
169
|
+
# spec twice all the same: stale_csv below is computed against the ledger, and the
|
|
170
|
+
# acceptance pass at the end reads spec.md as the wording the code is judged
|
|
171
|
+
# against. Both are worthless if the version does not name the contents.
|
|
172
|
+
- id: spec_state_guard
|
|
173
|
+
action: include
|
|
174
|
+
module: .appweaver/orchestrator/modules/spec-state-gate.orch.yaml
|
|
175
|
+
with: { command: "/appweaver implement" }
|
|
176
|
+
|
|
152
177
|
# 2. WHICH STORY. Same resolution story-plan uses: an explicit id in the input wins,
|
|
153
178
|
# anything else (nothing named, an unknown id, several) becomes a pick from the open
|
|
154
179
|
# backlog. One scan returns every fact the gates below read.
|
|
@@ -182,6 +207,7 @@ workflow:
|
|
|
182
207
|
- { name: design_pass, from: "$.be_last_pass" }
|
|
183
208
|
- { name: design_spec_version, from: "$.be_doc_spec_version" }
|
|
184
209
|
- { name: design_stale, from: "$.be_doc_stale" }
|
|
210
|
+
- { name: pass_plan_hash, from: "$.be_pass_plan_hash" }
|
|
185
211
|
- id: require_open_backlog
|
|
186
212
|
action: verify_context
|
|
187
213
|
require_true: any_open
|
|
@@ -220,6 +246,7 @@ workflow:
|
|
|
220
246
|
- { name: design_pass, from: "$.be_last_pass" }
|
|
221
247
|
- { name: design_spec_version, from: "$.be_doc_spec_version" }
|
|
222
248
|
- { name: design_stale, from: "$.be_doc_stale" }
|
|
249
|
+
- { name: pass_plan_hash, from: "$.be_pass_plan_hash" }
|
|
223
250
|
- id: require_story
|
|
224
251
|
action: verify_context
|
|
225
252
|
require_true: resolved
|
|
@@ -264,6 +291,62 @@ workflow:
|
|
|
264
291
|
on_mismatch: continue
|
|
265
292
|
on_mismatch_message: "The spec has moved on ids THIS story carries: {{stale_csv}}. Whatever this run generates for them realises superseded wording. Consider /appweaver sync-stories to bring the backlog back in step, then /appweaver story-plan {{story}}, before building."
|
|
266
293
|
|
|
294
|
+
# ═══════ 3a-bis. LAW 40, THE LAST HOP — the plan this pass was designed against ═══════
|
|
295
|
+
# THREE HASHES, not two. story-plan proved the feature plan was certified in the shape it
|
|
296
|
+
# was in when this pass was designed, and story-plan-doc stamped that certified hash into
|
|
297
|
+
# the pass header. But a pass is written once and read forever: comparing only the CURRENT
|
|
298
|
+
# plan to the CURRENT verdict would let a pass designed against plan H1 still generate
|
|
299
|
+
# after the plan was edited to H2 and re-gated — which is precisely the case law 40 names,
|
|
300
|
+
# one level below where it was being checked.
|
|
301
|
+
# pass hash what this pass was designed against (the story pass header)
|
|
302
|
+
# verdict hash what analyze-design last certified (the gate verdict)
|
|
303
|
+
# A MISMATCH IS A STOP, at the same severity as never-gated: continuing ships code built
|
|
304
|
+
# to an architecture nobody approved. Per lane, and only for the lane this command builds.
|
|
305
|
+
- id: gate_verdict
|
|
306
|
+
action: execute_script
|
|
307
|
+
on_error: continue
|
|
308
|
+
script: { ref: actions }
|
|
309
|
+
subcommand: gate-read
|
|
310
|
+
args: ["--feature-dir", "{{feature_dir}}", "--gate", "analyze-design"]
|
|
311
|
+
outputs:
|
|
312
|
+
- { name: gate_status, from: "$.status" }
|
|
313
|
+
- { name: gate_certified, from: "$.certified" }
|
|
314
|
+
- { name: gate_aborted, from: "$.aborted" }
|
|
315
|
+
- { name: gate_gap_refs, from: "$.gap_refs_csv" }
|
|
316
|
+
- { name: gate_report, from: "$.report" }
|
|
317
|
+
- { name: gate_lane_sha, from: "$.backend_sha256" }
|
|
318
|
+
|
|
319
|
+
# Three reasons, three routes; ordered sets, last match wins. Aborted is last because it
|
|
320
|
+
# also carries status "fail" — the red gate_result_open writes BEFORE any analysis — and
|
|
321
|
+
# sending someone to read gaps that were never found is the one wrong answer here.
|
|
322
|
+
- id: gate_msg_never
|
|
323
|
+
action: set
|
|
324
|
+
vars: { gate_msg: "The feature plans for this feature have never been certified: there is no analyze-design verdict beside them. Generation builds from a story pass that sits under an unapproved architecture. Run /appweaver analyze-design, then /appweaver story-plan {{story}}, then re-run /appweaver implement." }
|
|
325
|
+
- id: gate_msg_red
|
|
326
|
+
condition: "{{gate_status}} == fail"
|
|
327
|
+
action: set
|
|
328
|
+
vars: { gate_msg: "The feature plans do NOT conform to the spec: analyze-design's last verdict is FAIL on {{gate_gap_refs}} (report: {{gate_report}}). Close those gaps before generating code beneath them, then re-run /appweaver implement." }
|
|
329
|
+
- id: gate_msg_aborted
|
|
330
|
+
condition: "{{gate_aborted}} == true"
|
|
331
|
+
action: set
|
|
332
|
+
vars: { gate_msg: "The last analyze-design run STARTED and never reached a verdict — the red on disk is the one it writes before any analysis, so nothing was found and there are no gaps to read. The plans are UNCERTIFIED, not rejected. Run /appweaver analyze-design to completion, then re-run /appweaver implement." }
|
|
333
|
+
|
|
334
|
+
- id: gate_certified_guard
|
|
335
|
+
action: verify_context
|
|
336
|
+
require_true: gate_certified
|
|
337
|
+
on_false: stop
|
|
338
|
+
on_false_message: "{{gate_msg}}"
|
|
339
|
+
|
|
340
|
+
# An EMPTY pass hash fails this, and that is correct: a pass written before the hash was
|
|
341
|
+
# stamped cannot say which plan shape it was designed against, so nothing can prove it was
|
|
342
|
+
# designed against the approved one. The route is the same either way — re-design.
|
|
343
|
+
- id: pass_plan_shape_guard
|
|
344
|
+
action: verify_context
|
|
345
|
+
require_value: pass_plan_hash
|
|
346
|
+
equals: "{{gate_lane_sha}}"
|
|
347
|
+
on_mismatch: stop
|
|
348
|
+
on_mismatch_message: "Design pass {{design_pass}} for story {{story}} was designed against a backend plan that is NOT the one analyze-design currently certifies — the plan has been edited and re-gated since, or this pass predates plan-hash stamping and cannot say what it was designed against. Generating from it would build to an architecture nobody approved for this shape of the plan. Re-run /appweaver story-plan {{story}} to design against the currently certified plan, then re-run /appweaver implement."
|
|
349
|
+
|
|
267
350
|
# 4. THE SERVICE LANE and its source path. This phase writes code, and the lane's root
|
|
268
351
|
# is the only subtree it may write into — so this runs before the project gate,
|
|
269
352
|
# which searches that subtree.
|
|
@@ -277,21 +360,46 @@ workflow:
|
|
|
277
360
|
# searches the LANE'S OWN subtree for an ecosystem manifest, falling back to the
|
|
278
361
|
# whole repo for the layout that keeps its manifest beside the lane root rather than
|
|
279
362
|
# inside it. How the project got there is not asked.
|
|
363
|
+
# THE FALLBACK IS DELIBERATE AND IT NEEDED A DISCRIMINATOR. Searching the whole repo
|
|
364
|
+
# when the lane's own subtree has no manifest is right — a .NET .sln legitimately sits
|
|
365
|
+
# ABOVE the src/ it builds — but it answered with whatever manifest it found first, so
|
|
366
|
+
# in a two-lane repo where only the frontend is scaffolded, the SERVICE lane's gate
|
|
367
|
+
# passed on the frontend's package.json. Which is exactly the failure implement-ui's
|
|
368
|
+
# `project_scope == lane` rule was written to prevent, one lane over.
|
|
369
|
+
# `--ecosystem` is the fix, and NOT implement-ui's rule: this passes the value
|
|
370
|
+
# lane-gate has already bound from stacks.json (that include runs above), so the repo
|
|
371
|
+
# -scope hit is accepted only when the manifest is one this lane's ecosystem knows.
|
|
372
|
+
# Nothing here re-derives it — a second guess is a second thing to keep in step.
|
|
280
373
|
- id: detect_foundation
|
|
281
374
|
action: execute_script
|
|
282
375
|
script: { ref: actions }
|
|
283
376
|
subcommand: detect-project
|
|
284
|
-
args: ["--root", "{{lane_source_path}}"]
|
|
377
|
+
args: ["--root", "{{lane_source_path}}", "--ecosystem", "{{lane_ecosystem}}"]
|
|
285
378
|
outputs:
|
|
286
379
|
- { name: foundation_present, from: "$.scaffolded" }
|
|
287
380
|
- { name: project_markers, from: "$.markers" }
|
|
288
381
|
- { name: project_scope, from: "$.scope" }
|
|
382
|
+
- { name: ecosystem_match, from: "$.ecosystem_match" }
|
|
289
383
|
- id: foundation_gate
|
|
290
384
|
action: verify_context
|
|
291
385
|
require_true: foundation_present
|
|
292
386
|
on_false: stop
|
|
293
387
|
on_false_message: "No project found under the {{lane_id}} lane's source path `{{lane_source_path}}`, nor anywhere else in this repository. Implement GENERATES this story's code into an existing project tree, so one has to be scaffolded first: run /appweaver foundation <FoundationName> <AppName> where a foundation bundle ships for this stack, or create the project with the stack's own tooling under that path. Any ecosystem manifest marks it — .sln/.csproj, pom.xml, build.gradle(.kts), package.json, go.mod, Cargo.toml or pyproject.toml."
|
|
294
388
|
|
|
389
|
+
# 5b. THE GATE PASSED ON SOMEBODY ELSE'S PROJECT. A repo-scope hit whose manifests are
|
|
390
|
+
# none of this lane's ecosystem means the search fell back past the lane root and
|
|
391
|
+
# found the OTHER lane's project. STOPS rather than warns: the next thing this run
|
|
392
|
+
# does is generate {{lane_language}} code, and it has just been told there is nowhere
|
|
393
|
+
# of that kind to put it — carrying on writes a service tree inside a frontend, or
|
|
394
|
+
# into the repo root. Scoped to the fallback, so the deliberate manifest-above-the-lane
|
|
395
|
+
# layout (a .sln over src/) still passes: that hit IS this lane's ecosystem.
|
|
396
|
+
- id: foundation_ecosystem_gate
|
|
397
|
+
condition: "{{project_scope}} == repo"
|
|
398
|
+
action: verify_context
|
|
399
|
+
require_true: ecosystem_match
|
|
400
|
+
on_false: stop
|
|
401
|
+
on_false_message: "The {{lane_id}} lane's source path `{{lane_source_path}}` holds no project, and the repo-wide fallback found only manifests that are not {{lane_ecosystem}}: {{project_markers}}. That is the OTHER lane's project — this gate passing on it would send {{lane_language}} code into a tree built for something else. Scaffold this lane: /appweaver foundation <FoundationName> <AppName> where a bundle ships for {{lane_profile}}, or create the project with the stack's own tooling under `{{lane_source_path}}`. (A manifest deliberately kept ABOVE the lane root — a .sln over src/ — is accepted, because it is this lane's ecosystem.)"
|
|
402
|
+
|
|
295
403
|
# 6. WHAT ALREADY EXISTS, from the feature's OKF concept, split by lane. This is what
|
|
296
404
|
# makes the run incremental: the pass names the files it changes, and this names the
|
|
297
405
|
# files that are there to change. Advisory — the first story of a greenfield feature
|
|
@@ -363,8 +471,12 @@ workflow:
|
|
|
363
471
|
to modify. Create ONLY the files it names as new; modify the named existing
|
|
364
472
|
files IN PLACE. Every other file in this feature belongs to work that is
|
|
365
473
|
already done — do not regenerate, reformat or "tidy" it.
|
|
366
|
-
- The story delivers {{exercises_csv}} and must satisfy {{constraints_csv}}
|
|
367
|
-
|
|
474
|
+
- The story delivers {{exercises_csv}} and must satisfy {{constraints_csv}} — the
|
|
475
|
+
complete set of bounds the spec declares for this aggregate, stamped into the
|
|
476
|
+
story's frontmatter by the kit rather than transcribed by anyone. Satisfy the
|
|
477
|
+
CN-, DC- and IC- among them. IGNORE the UX-: those are what the UI lane realises,
|
|
478
|
+
and a service-side obligation is stated as a CN- by design. Anything outside those
|
|
479
|
+
ids is another story's work, even if you can see it is
|
|
368
480
|
missing. If the slice genuinely cannot be built without touching shared
|
|
369
481
|
structure, make the smallest change that works and SAY SO in your summary.
|
|
370
482
|
- The code that already exists for this feature, per lane, is below. The service
|
|
@@ -445,9 +557,16 @@ workflow:
|
|
|
445
557
|
action: execute_script
|
|
446
558
|
script: { ref: xref }
|
|
447
559
|
subcommand: concept-upsert
|
|
448
|
-
|
|
560
|
+
# --ids + --slice record WHICH REQUIREMENT this run's files realise, as Requirement
|
|
561
|
+
# Anchors rows in the concept. Both halves were already here and thrown away: the ids
|
|
562
|
+
# the story exercises, and the files just generated. Only `adopt` writes a
|
|
563
|
+
# source-map.md, so without this every id on a greenfield feature came back unanchored
|
|
564
|
+
# at analyze-slice and the trace pass had to read the whole tree to find out what
|
|
565
|
+
# implements what. Reconcile-on-write, keyed by slice + lane.
|
|
566
|
+
args: ["--feature-dir", "{{feature_dir}}", "--source-files", "{{files}}", "--merge", "--lane", "backend", "--origin", "greenfield", "--ids", "{{exercises_csv}}", "--slice", "{{story}}"]
|
|
449
567
|
outputs:
|
|
450
|
-
- { name: okf_concept,
|
|
568
|
+
- { name: okf_concept, from: "$.concept" }
|
|
569
|
+
- { name: okf_anchors, from: "$.anchors_written" }
|
|
451
570
|
|
|
452
571
|
# 8a. Cross-slice edges are a FEATURE property, not a story's: which sibling
|
|
453
572
|
# aggregates this one references is stated by the feature plan's domain model,
|
|
@@ -568,7 +687,7 @@ workflow:
|
|
|
568
687
|
Nothing was changed to fix this: the code follows design pass {{design_pass}}, so a gap
|
|
569
688
|
here means either the design was wrong or the spec has moved. Route by which it is —
|
|
570
689
|
the design is wrong: /appweaver story-plan {{story}} (a new pass supersedes this one);
|
|
571
|
-
the spec is wrong: /appweaver sync-spec, then sync-stories, then story-plan.
|
|
690
|
+
the spec is wrong: /appweaver sync-spec (⌛ not yet built — until it is, fold the change back by hand under .appweaver/SPEC-EDIT-PROTOCOL.md), then sync-stories, then story-plan.
|
|
572
691
|
Run /appweaver build either way — a design that is right and code that does not compile
|
|
573
692
|
is a third thing, and build is where that one surfaces.
|
|
574
693
|
|
|
@@ -593,4 +712,4 @@ outputs:
|
|
|
593
712
|
okf_concept: "{{okf_concept}} (this run's files MERGED into source_files; {{xref_edge_count}} cross-slice edge(s) recorded into sibling aggregates)"
|
|
594
713
|
acceptance: "ok={{accept_ok}} · gaps: {{accept_gaps}}"
|
|
595
714
|
stale_ids: "{{stale_csv}}"
|
|
596
|
-
next: "/appweaver build — it compiles every declared lane. Then /appweaver analyze-slice (spec ↔ code, through the design), and /appweaver implement-ui if this story has a UI design. The story stays OPEN
|
|
715
|
+
next: "/appweaver build — it compiles every declared lane. Then /appweaver analyze-slice (spec ↔ code, through the design), and /appweaver implement-ui if this story has a UI design. The story stays OPEN: /appweaver close-story — which would ratify its ADRs, flip its interface entries live and archive the passes — is ⌛ NOT BUILT YET, so there is no transition out of in-flight and no last gate before merge."
|
|
@@ -30,15 +30,17 @@
|
|
|
30
30
|
# stamped at the same moment never contends with it. Hand edits are refused unless accept_manual:true
|
|
31
31
|
# folds them in. The model writes NO version anywhere.
|
|
32
32
|
#
|
|
33
|
-
# WHERE IT SITS. specify/sync-spec
|
|
33
|
+
# WHERE IT SITS. specify/sync-spec (⌛ sync-spec is not built yet; the hand route is
|
|
34
|
+
# .appweaver/SPEC-EDIT-PROTOCOL.md) move the spec; the developer then runs plan (the
|
|
34
35
|
# service lane) and plan-ui, which conform each lane's design to what moved. FE story
|
|
35
36
|
# designs READ this document for the ids they carry and NEVER write back — nothing
|
|
36
37
|
# merges into it, so it keeps exactly one writer. It is the FEATURE-LEVEL
|
|
37
38
|
# ARCHITECTURAL CONFIRMATION for the experience, with the concrete component-level
|
|
38
39
|
# work left to the per-story passes.
|
|
39
40
|
#
|
|
40
|
-
# IT READS THE CODE. concept-
|
|
41
|
-
# realising its UI today
|
|
41
|
+
# IT READS THE CODE — ITS OWN LANE'S. concept-coverage resolves the feature's OKF concept
|
|
42
|
+
# to the `ui_source_files` realising its UI today (the service lane's `source_files` is a
|
|
43
|
+
# different key and a different tree) and the authoring steps declare `tools: Read`, so a plan over
|
|
42
44
|
# an existing frontend ADJUSTS to the composition, naming and state handling actually
|
|
43
45
|
# there. The code is the what-exists authority; the spec stays the only authority on
|
|
44
46
|
# required behaviour. source_count 0 = greenfield, and the phase never blocks on it.
|
|
@@ -90,7 +92,7 @@ standards:
|
|
|
90
92
|
- { path: .appweaver/ai-rules.md } # kit-resident: the universal operating rules, every flow
|
|
91
93
|
|
|
92
94
|
vars:
|
|
93
|
-
source_csv: "" # concept-
|
|
95
|
+
source_csv: "" # concept-coverage — the UI files realising this feature today
|
|
94
96
|
source_count: "0" # 0 ⇒ greenfield: no frontend code for this feature yet
|
|
95
97
|
okf_concept: "" # the feature's OKF concept doc
|
|
96
98
|
ui_plan_mode: "bootstrap_gf" # resolved below → bootstrap_gf | bootstrap_bf | update
|
|
@@ -167,19 +169,10 @@ workflow:
|
|
|
167
169
|
|
|
168
170
|
# 1b. The spec's own ledger tripwire — a plan derived from an unledgered spec
|
|
169
171
|
# stamps a baseline the ledger cannot name (same fence as plan's).
|
|
170
|
-
- id: spec_guard_read
|
|
171
|
-
action: execute_script
|
|
172
|
-
script: { ref: actions }
|
|
173
|
-
subcommand: spec-check
|
|
174
|
-
args: ["--feature-dir", "{{feature_dir}}"]
|
|
175
|
-
outputs:
|
|
176
|
-
- { name: spec_ok, from: "$.ok" }
|
|
177
|
-
- { name: spec_state, from: "$.state" }
|
|
178
172
|
- id: spec_guard
|
|
179
|
-
action:
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
on_false_message: "spec.md has moved outside the ledger (state: {{spec_state}}). Record what changed in a Change History row (or fold it back through /appweaver sync-spec), re-run spec-stamp to re-baseline, then re-run plan-ui."
|
|
173
|
+
action: include
|
|
174
|
+
module: .appweaver/orchestrator/modules/spec-state-gate.orch.yaml
|
|
175
|
+
with: { command: "/appweaver plan-ui" }
|
|
183
176
|
|
|
184
177
|
# 2. Scaffold frontend-plan.md (idempotent). --lane frontend resolves the pair
|
|
185
178
|
# ui-design-template → frontend-plan.md, and MIGRATES a legacy design.ui.md by
|
|
@@ -245,24 +238,28 @@ workflow:
|
|
|
245
238
|
action: verify_context
|
|
246
239
|
require_true: plan_stale
|
|
247
240
|
on_false: stop
|
|
248
|
-
on_false_message: "frontend-plan.md is in step with spec {{current_version}} — nothing to update."
|
|
241
|
+
on_false_message: "frontend-plan.md is in step with spec {{current_version}} — nothing to update. If the PLAN is wrong and the spec is right, hand-edit frontend-plan.md and re-run /appweaver plan-ui with accept_manual: true — that is what tells this command to keep the edit's intent and reconcile it rather than treat it as drift; without the flag the manual fence stops the run after the edit is already on disk."
|
|
249
242
|
|
|
250
243
|
# 5. Pre-execution hooks.
|
|
251
|
-
# THE CODE. The UI plan is an architectural confirmation too, so it must
|
|
252
|
-
# this feature's experience already looks like on disk
|
|
253
|
-
#
|
|
254
|
-
#
|
|
244
|
+
# THE CODE — THIS LANE'S. The UI plan is an architectural confirmation too, so it must
|
|
245
|
+
# know what this feature's experience already looks like on disk. concept-COVERAGE, not
|
|
246
|
+
# concept-paths: a feature has ONE OKF concept holding both lanes' file lists under
|
|
247
|
+
# different keys, and concept-paths returns the SERVICE lane's `source_files`. Reading it
|
|
248
|
+
# here planned the frontend against the backend's tree — a feature with a built frontend
|
|
249
|
+
# and no backend was planned as greenfield, and one with both handed the UI author the
|
|
250
|
+
# service lane's files to adjust to. concept-coverage splits the two and this binds
|
|
251
|
+
# `ui_source_files`. A PATH LIST, not inlined content (E60). on_error: continue — a
|
|
255
252
|
# greenfield feature has no concept doc and must never be blocked by one;
|
|
256
253
|
# source_count 0 is the greenfield signal the tasks read.
|
|
257
254
|
- id: concept_paths
|
|
258
255
|
action: execute_script
|
|
259
256
|
on_error: continue
|
|
260
257
|
script: { ref: xref }
|
|
261
|
-
subcommand: concept-
|
|
258
|
+
subcommand: concept-coverage
|
|
262
259
|
args: ["--feature-dir", "{{feature_dir}}"]
|
|
263
260
|
outputs:
|
|
264
|
-
- { name: source_csv, from: "$.
|
|
265
|
-
- { name: source_count, from: "$.
|
|
261
|
+
- { name: source_csv, from: "$.frontend_csv" }
|
|
262
|
+
- { name: source_count, from: "$.frontend_count" }
|
|
266
263
|
- { name: okf_concept, from: "$.concept" }
|
|
267
264
|
|
|
268
265
|
- id: resolve_pre_hooks
|
|
@@ -515,6 +512,12 @@ workflow:
|
|
|
515
512
|
moved is re-realised from the spec's CURRENT text — the spec is the wording.
|
|
516
513
|
A UX- is THIS document's to realise: a moved UX- with no home here after the
|
|
517
514
|
update is a DROPPED requirement.
|
|
515
|
+
- AN ID THE SPEC HAS NEWLY DEFINED GAINS ONE. An id in the work list that NO
|
|
516
|
+
existing section realises is an ADDITION: give it a home in the sections that
|
|
517
|
+
realise it and a traceability row like any other. The scope rule above says
|
|
518
|
+
which sections may be touched; it does not say a new requirement may not be
|
|
519
|
+
designed, and reading it that way would let every added UX- or FR- with a
|
|
520
|
+
user-facing surface select nothing to rework and land unrealised.
|
|
518
521
|
- THE CODE IS THE WHAT-EXISTS AUTHORITY, and it is a DIFFERENT authority from the
|
|
519
522
|
spec. {{source_count}} file(s) realise this feature's experience today; re-read the
|
|
520
523
|
ones the moved ids touch (code state is read FRESH every run, never assumed) and ADJUST this plan to the composition,
|
|
@@ -594,4 +597,4 @@ outputs:
|
|
|
594
597
|
lane_root: "{{lane_root}}"
|
|
595
598
|
derived_from_spec: "{{current_version}}"
|
|
596
599
|
conformed_ids: "{{changed_csv}}"
|
|
597
|
-
next: "frontend-plan.md written for the {{lane_id}} lane at {{ui_plan_doc}} (mode: {{ui_plan_mode}}). Gate this write: /appweaver analyze-design — one command over BOTH lanes, which is what proves no UX- fell between them; it reads §7 Traceability as the two-way spine. CHECK §5 CONTRACT BINDING: it binds to interfaces.md, which /appweaver plan publishes — if that log was absent this run, §5 was bound from the spec's IC- ids alone and a DA-n says so, which means running plan first and re-running this command is what turns an assumption into the real contract. Contract deltas needed goes back to the service lane, never into the client. source_files_read {{source_count}} is how many existing UI files this plan was written against; 0 means greenfield. Then /appweaver implement-ui.
|
|
600
|
+
next: "frontend-plan.md written for the {{lane_id}} lane at {{ui_plan_doc}} (mode: {{ui_plan_mode}}). Gate this write: /appweaver analyze-design — one command over BOTH lanes, which is what proves no UX- fell between them; it reads §7 Traceability as the two-way spine. CHECK §5 CONTRACT BINDING: it binds to interfaces.md, which /appweaver plan publishes — if that log was absent this run, §5 was bound from the spec's IC- ids alone and a DA-n says so, which means running plan first and re-running this command is what turns an assumption into the real contract. Contract deltas needed goes back to the service lane, never into the client. source_files_read {{source_count}} is how many existing UI files this plan was written against; 0 means greenfield. Then /appweaver implement-ui."
|