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
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# spec-state-gate (orchestrator) — the ledger tripwire, gated on STATE
|
|
3
|
+
#
|
|
4
|
+
# Included by every phase that READS spec.md as an oracle and does not stamp it.
|
|
5
|
+
# It answers one question before that phase starts doing work:
|
|
6
|
+
#
|
|
7
|
+
# is spec.md's version an honest name for spec.md's contents?
|
|
8
|
+
#
|
|
9
|
+
# WHY THE `ok` BOOLEAN IS NOT ENOUGH. spec-check's decisive branch asks only "is
|
|
10
|
+
# there a version ROW above the version the kit last recorded", and never opens the
|
|
11
|
+
# body. Two of its states are therefore ok:true with a body nobody has re-baselined:
|
|
12
|
+
#
|
|
13
|
+
# ledgered_edit a row exists above the recorded version — the edit was RECORDED,
|
|
14
|
+
# but the kit has not re-hashed, so every LATER edit lands inside
|
|
15
|
+
# the same window and passes the same test. Verified: a requirement
|
|
16
|
+
# DELETED with no row naming it passes.
|
|
17
|
+
# header_behind the same, with the document's header not yet caught up.
|
|
18
|
+
#
|
|
19
|
+
# Both are legitimate transient states and both have the same remedy — spec-stamp,
|
|
20
|
+
# which re-baselines the hash and closes the window. That remedy is already named in
|
|
21
|
+
# these phases' own refusal messages and in SPEC-EDIT-PROTOCOL.md.
|
|
22
|
+
#
|
|
23
|
+
# WHY NOT FLIP `ok` INSTEAD. Because specify is the stamper. It MUST keep accepting
|
|
24
|
+
# both states — it is the command that resolves them — so making spec-check refuse
|
|
25
|
+
# them would wedge the one workflow that can fix them. The distinction belongs at the
|
|
26
|
+
# consumer, which is what this module is.
|
|
27
|
+
#
|
|
28
|
+
# WHAT IT DOES NOT DO. It never stamps, never edits and never repairs. Every state
|
|
29
|
+
# spec-check already refuses (unrowed_edit, unledgered_edit, rollback, no_ledger,
|
|
30
|
+
# delta_open) still stops on `ok`, with spec-check's own message.
|
|
31
|
+
#
|
|
32
|
+
# Inputs (via include `with`):
|
|
33
|
+
# command — the phase being gated, quoted back in the failure message
|
|
34
|
+
#
|
|
35
|
+
# Produces: spec_ok / spec_state (the raw answers) and spec_current (true when the
|
|
36
|
+
# state is one this phase may read from), for a caller that wants to say more.
|
|
37
|
+
# =============================================================================
|
|
38
|
+
|
|
39
|
+
name: spec-state-gate
|
|
40
|
+
dialect: aw-spec.orchestrator/v1
|
|
41
|
+
|
|
42
|
+
scripts:
|
|
43
|
+
actions:
|
|
44
|
+
sh: .appweaver/scripts/bash/aw-spec-actions.sh
|
|
45
|
+
ps1: .appweaver/scripts/powershell/aw-spec-actions.ps1
|
|
46
|
+
|
|
47
|
+
workflow:
|
|
48
|
+
|
|
49
|
+
- id: spec_guard_read
|
|
50
|
+
action: execute_script
|
|
51
|
+
script: { ref: actions }
|
|
52
|
+
subcommand: spec-check
|
|
53
|
+
args: ["--feature-dir", "{{feature_dir}}"]
|
|
54
|
+
outputs:
|
|
55
|
+
- { name: spec_ok, from: "$.ok" }
|
|
56
|
+
- { name: spec_state, from: "$.state" }
|
|
57
|
+
|
|
58
|
+
# The states spec-check itself refuses. Kept first and kept separate: its own error
|
|
59
|
+
# text says which one and why, and this module has nothing to add to it.
|
|
60
|
+
- id: spec_guard
|
|
61
|
+
action: verify_context
|
|
62
|
+
require_true: spec_ok
|
|
63
|
+
on_false: stop
|
|
64
|
+
on_false_message: "{{command}} cannot run: spec.md has moved outside the ledger (state: {{spec_state}}), so its version no longer names its contents and nothing read from it can be trusted. Record what changed in a Change History row naming the ids, then re-run spec-stamp to re-baseline — or, when the edit touched no requirement at all, spec-stamp alone (see .appweaver/SPEC-EDIT-PROTOCOL.md)."
|
|
65
|
+
|
|
66
|
+
# The two states that pass `ok` and still leave the body un-baselined. Ordered sets
|
|
67
|
+
# rather than a compound condition — the evaluator takes single comparisons only, so
|
|
68
|
+
# the refusal is the default and each acceptable state lifts it.
|
|
69
|
+
- id: spec_state_default
|
|
70
|
+
action: set
|
|
71
|
+
vars: { spec_current: "false" }
|
|
72
|
+
- id: spec_state_clean
|
|
73
|
+
condition: "{{spec_state}} == clean"
|
|
74
|
+
action: set
|
|
75
|
+
vars: { spec_current: "true" }
|
|
76
|
+
# A pre-ledger spec the kit has never stamped. Legitimate: nothing has been recorded
|
|
77
|
+
# against it, so there is no window between a row and a baseline to fall into.
|
|
78
|
+
- id: spec_state_unstamped
|
|
79
|
+
condition: "{{spec_state}} == unstamped"
|
|
80
|
+
action: set
|
|
81
|
+
vars: { spec_current: "true" }
|
|
82
|
+
|
|
83
|
+
- id: spec_state_gate
|
|
84
|
+
action: verify_context
|
|
85
|
+
require_true: spec_current
|
|
86
|
+
on_false: stop
|
|
87
|
+
on_false_message: "{{command}} cannot run: spec.md is in state '{{spec_state}}' — the edit is recorded in a Change History row, but the kit has not re-baselined the document, so its recorded hash still describes the text before the edit. Until it does, every FURTHER edit passes the same tripwire unnoticed, including a requirement deleted with no row naming it. Run /appweaver specify (which stamps), or re-run spec-stamp to re-baseline directly, then re-run {{command}}."
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
# • the plan lags what the ledger moved → /appweaver plan · /appweaver plan-ui
|
|
52
52
|
# • the plan is wrong and the spec is right → repair here, or hand-edit the plan and
|
|
53
53
|
# re-run plan with accept_manual: true
|
|
54
|
-
# • the SPEC is wrong → /appweaver sync-spec (never repaired here)
|
|
54
|
+
# • 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) (never repaired here)
|
|
55
55
|
# • a gate-overlay constraint was never lifted into the spec (LIFT-FAILURE:)
|
|
56
56
|
# → /appweaver specify
|
|
57
57
|
#
|
|
@@ -87,6 +87,10 @@ vars:
|
|
|
87
87
|
fe_state: ""
|
|
88
88
|
fe_derived: ""
|
|
89
89
|
fe_changed: ""
|
|
90
|
+
# The plan shapes this run certifies, re-read after any repair (step 8b). Empty means the
|
|
91
|
+
# lane was not in scope, which every reader treats as "not certified", never as a mismatch.
|
|
92
|
+
be_final_sha: ""
|
|
93
|
+
fe_final_sha: ""
|
|
90
94
|
current_version: ""
|
|
91
95
|
# Freshness gates default FALSE so a probe that could not run fails CLOSED. The
|
|
92
96
|
# message vars carry a generic default for the same reason — an empty stop message
|
|
@@ -108,6 +112,9 @@ vars:
|
|
|
108
112
|
found_count: "0"
|
|
109
113
|
found_refs: ""
|
|
110
114
|
repaired: "false"
|
|
115
|
+
# The ids a repair pass actually rewrote — the model's own declaration, and NOT the same
|
|
116
|
+
# set as gap_refs (which holds every finding, including the ones left deliberately open).
|
|
117
|
+
repaired_refs: ""
|
|
111
118
|
be_stamp_ok: "true" # vacuously true until a repair actually stamps
|
|
112
119
|
fe_stamp_ok: "true"
|
|
113
120
|
be_stamp_error: ""
|
|
@@ -198,19 +205,10 @@ workflow:
|
|
|
198
205
|
# gate compares each plan's derived version against the spec's ledger, so a spec
|
|
199
206
|
# that moved OUTSIDE the ledger makes every one of those comparisons a lie: the
|
|
200
207
|
# plans would be certified current against a version the spec no longer is.
|
|
201
|
-
- id: spec_guard_read
|
|
202
|
-
action: execute_script
|
|
203
|
-
script: { ref: actions }
|
|
204
|
-
subcommand: spec-check
|
|
205
|
-
args: ["--feature-dir", "{{feature_dir}}"]
|
|
206
|
-
outputs:
|
|
207
|
-
- { name: spec_ok, from: "$.ok" }
|
|
208
|
-
- { name: spec_state, from: "$.state" }
|
|
209
208
|
- id: spec_guard
|
|
210
|
-
action:
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
on_false_message: "spec.md has moved outside the ledger (state: {{spec_state}}), so its version no longer names its contents and no plan can be certified against it. 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 analyze-design."
|
|
209
|
+
action: include
|
|
210
|
+
module: .appweaver/orchestrator/modules/spec-state-gate.orch.yaml
|
|
211
|
+
with: { command: "/appweaver analyze-design" }
|
|
214
212
|
|
|
215
213
|
# 2c. LEGACY GUARD. Nothing in the current model opens a change — specify writes the
|
|
216
214
|
# spec directly and the delta family is retired (deletion scheduled). has_change
|
|
@@ -404,8 +402,8 @@ workflow:
|
|
|
404
402
|
- id: analyze
|
|
405
403
|
action: agent_query
|
|
406
404
|
include_standards: true # the domain gate overlays (the checklist) ride in the standards
|
|
407
|
-
schema: { ok: true, gap_count: 0, gap_refs: "<comma-separated spec ids the gaps touch>", backend_repair: false, frontend_repair: false, gaps: [ { severity: "high", lane: "backend|frontend|cross", spec_ref: "<FR/BR/CN/DC/IC/UX/SC id, or the overlay name, or —>", where: "<document + section>", gap: "<how it drifts>", route: "<the command that fixes it>" } ] }
|
|
408
|
-
mock: { ok: true, gap_count: 0, gap_refs: "", backend_repair: false, frontend_repair: false, gaps: [] }
|
|
405
|
+
schema: { ok: true, gap_count: 0, gap_refs: "<comma-separated spec ids the gaps touch>", repaired_refs: "<comma-separated spec ids THIS repair actually rewrote — empty on the first pass>", backend_repair: false, frontend_repair: false, gaps: [ { severity: "high", lane: "backend|frontend|cross", spec_ref: "<FR/BR/CN/DC/IC/UX/SC id, or the overlay name, or —>", where: "<document + section>", gap: "<how it drifts>", route: "<the command that fixes it>" } ] }
|
|
406
|
+
mock: { ok: true, gap_count: 0, gap_refs: "", repaired_refs: "", backend_repair: false, frontend_repair: false, gaps: [] }
|
|
409
407
|
task: |
|
|
410
408
|
spec.md is the ANCHOR. Judge the feature's PLAN DOCUMENTS against it and list
|
|
411
409
|
EVERY drift as a gap. In scope this run: backend-plan.md = {{be_in_scope}},
|
|
@@ -487,6 +485,15 @@ workflow:
|
|
|
487
485
|
another command. Set gap_count to the number of gaps and gap_refs to their
|
|
488
486
|
spec ids, comma-separated. Set ok=true ONLY when every plan in scope fully
|
|
489
487
|
conforms to the spec AND no checklist constraint is unaccounted for.
|
|
488
|
+
|
|
489
|
+
`repaired_refs` IS A DIFFERENT SET FROM `gap_refs`, and it goes into the plans'
|
|
490
|
+
own Change History. Set it to the spec ids THIS PASS ACTUALLY REWROTE a plan for
|
|
491
|
+
— empty on a pass that repaired nothing, which includes the first pass of every
|
|
492
|
+
run. Never copy gap_refs into it: that set holds every finding, including the
|
|
493
|
+
gaps you were told to leave OPEN (route sync-spec or specify), a LIFT-FAILURE
|
|
494
|
+
whose spec_ref is an overlay name rather than an id, and any "—". Recording one
|
|
495
|
+
of those claims the plan was conformed for a requirement nobody touched, in the
|
|
496
|
+
ledger story-plan and the next /appweaver plan read.
|
|
490
497
|
outputs:
|
|
491
498
|
- { name: analyze_ok, from: "$.ok" }
|
|
492
499
|
- { name: analyze_gaps, from: "$.gaps" }
|
|
@@ -494,6 +501,7 @@ workflow:
|
|
|
494
501
|
- { name: gap_refs, from: "$.gap_refs" }
|
|
495
502
|
- { name: be_needs_repair, from: "$.backend_repair" }
|
|
496
503
|
- { name: fe_needs_repair, from: "$.frontend_repair" }
|
|
504
|
+
- { name: repaired_refs, from: "$.repaired_refs" }
|
|
497
505
|
|
|
498
506
|
# The report's subject is the state the gate FOUND, not what survived repair —
|
|
499
507
|
# a verdict that only ever describes the repaired document cannot show that a
|
|
@@ -521,7 +529,13 @@ workflow:
|
|
|
521
529
|
Change History. Choose Report and Skip whenever the fix belongs elsewhere —
|
|
522
530
|
a wrong SPEC goes through /appweaver sync-spec, a LIFT-FAILURE through
|
|
523
531
|
/appweaver specify.
|
|
524
|
-
|
|
532
|
+
# REPORT FIRST, BECAUSE THE FIRST OPTION IS THE UNATTENDED ANSWER. An `ask` with
|
|
533
|
+
# nobody at the terminal resolves to option one, so an unattended run of this gate
|
|
534
|
+
# takes whichever branch is written here. Repairing means REWRITING both feature
|
|
535
|
+
# plans and re-stamping them, and a CI job that rewrites the documents it was asked
|
|
536
|
+
# to check reports on a state it created. Reporting is the safe default: it writes
|
|
537
|
+
# the report, leaves the plans untouched, and the person who reads it chooses.
|
|
538
|
+
options: ["Report and Skip", "Repair"]
|
|
525
539
|
answer_var: repair_choice
|
|
526
540
|
|
|
527
541
|
- id: skip_break
|
|
@@ -598,7 +612,16 @@ workflow:
|
|
|
598
612
|
action: execute_script
|
|
599
613
|
script: { ref: actions }
|
|
600
614
|
subcommand: plan-stamp
|
|
601
|
-
|
|
615
|
+
# --ids is WHAT THIS REPAIR WROTE, not every gap the pass found. gap_refs is the
|
|
616
|
+
# whole finding set — including gaps the repair prompt is told to leave OPEN (route
|
|
617
|
+
# sync-spec / specify), a LIFT-FAILURE whose spec_ref is an overlay name rather than
|
|
618
|
+
# an id, and the literal "—" the schema permits. Stamping those recorded, in the
|
|
619
|
+
# plan's own Change History, that the repair conformed ids it never touched — and
|
|
620
|
+
# that ledger is what story-plan and the next /appweaver plan read.
|
|
621
|
+
# --note carries the plan's tracking STATE, because a repair over an `edited`
|
|
622
|
+
# document re-baselines a hand edit that /appweaver plan would have refused without
|
|
623
|
+
# accept_manual: true. Admitting the edit is right; adopting it silently is not.
|
|
624
|
+
args: ["--doc", "{{be_plan}}", "--feature-dir", "{{feature_dir}}", "--ids", "{{repaired_refs}}", "--note", "Conformance repair (analyze-design) over a plan in state '{{be_state}}'"]
|
|
602
625
|
outputs:
|
|
603
626
|
- { name: be_stamp_ok, from: "$.ok" }
|
|
604
627
|
- { name: be_stamp_error, from: "$.error" }
|
|
@@ -640,7 +663,9 @@ workflow:
|
|
|
640
663
|
action: execute_script
|
|
641
664
|
script: { ref: actions }
|
|
642
665
|
subcommand: plan-stamp
|
|
643
|
-
|
|
666
|
+
# Same two rules as the service lane: the ids this repair actually wrote, and the
|
|
667
|
+
# tracking state the repair ran over. See stamp_be.
|
|
668
|
+
args: ["--doc", "{{fe_plan}}", "--feature-dir", "{{feature_dir}}", "--ids", "{{repaired_refs}}", "--note", "Conformance repair (analyze-design) over a plan in state '{{fe_state}}'"]
|
|
644
669
|
outputs:
|
|
645
670
|
- { name: fe_stamp_ok, from: "$.ok" }
|
|
646
671
|
- { name: fe_stamp_error, from: "$.error" }
|
|
@@ -680,6 +705,12 @@ workflow:
|
|
|
680
705
|
- "# Plan Analyze Report — <aggregate>", then a line naming the spec version and
|
|
681
706
|
each gated plan with its tracking state.
|
|
682
707
|
- A verdict line: "verdict: CONFORMS ✓" or "verdict: GAPS ✗ · <n> open".
|
|
708
|
+
WHERE A REPAIR RAN OVER A HAND-EDITED PLAN, say so on that line — backend-plan.md
|
|
709
|
+
tracking state was "{{be_state}}" and frontend-plan.md "{{fe_state}}", and an
|
|
710
|
+
`edited` or `edited_behind` there means the repair re-baselined a hand edit that
|
|
711
|
+
/appweaver plan would have refused without accept_manual: true. Name the document,
|
|
712
|
+
the state, and that the edit is now adopted into the plan's baseline — whoever made
|
|
713
|
+
it should confirm the repair kept its intent.
|
|
683
714
|
When repair ran, say so and give both numbers — found and still open.
|
|
684
715
|
- "## Coverage (spec → plan)": a table | spec id | type | lane(s) it needs |
|
|
685
716
|
design home | status |, one row per spec id you can see in the spec, marking
|
|
@@ -694,6 +725,31 @@ workflow:
|
|
|
694
725
|
Do not invent ids, versions or dates beyond the values given above.
|
|
695
726
|
Return the complete report markdown only.
|
|
696
727
|
|
|
728
|
+
# 8b. THE SHAPE BEING CERTIFIED, read at the last possible moment. Taken here rather than
|
|
729
|
+
# from the freshness probes at the top, because the repair loop may have rewritten and
|
|
730
|
+
# re-stamped both plans in between — recording the pre-repair hash would certify a
|
|
731
|
+
# document that no longer exists and fail every downstream comparison on a green run.
|
|
732
|
+
# Per lane and conditional: a lane out of scope has no hash and gets an empty field,
|
|
733
|
+
# which the readers treat as "this lane was not certified" rather than as a mismatch.
|
|
734
|
+
- id: be_final_hash
|
|
735
|
+
condition: "{{be_in_scope}} == true"
|
|
736
|
+
action: execute_script
|
|
737
|
+
on_error: continue
|
|
738
|
+
script: { ref: actions }
|
|
739
|
+
subcommand: plan-check
|
|
740
|
+
args: ["--doc", "{{be_plan}}", "--feature-dir", "{{feature_dir}}"]
|
|
741
|
+
outputs:
|
|
742
|
+
- { name: be_final_sha, from: "$.body_sha256" }
|
|
743
|
+
- id: fe_final_hash
|
|
744
|
+
condition: "{{fe_in_scope}} == true"
|
|
745
|
+
action: execute_script
|
|
746
|
+
on_error: continue
|
|
747
|
+
script: { ref: actions }
|
|
748
|
+
subcommand: plan-check
|
|
749
|
+
args: ["--doc", "{{fe_plan}}", "--feature-dir", "{{feature_dir}}"]
|
|
750
|
+
outputs:
|
|
751
|
+
- { name: fe_final_sha, from: "$.body_sha256" }
|
|
752
|
+
|
|
697
753
|
# 9. The machine-readable verdict, written beside the report by the SCRIPT — CI
|
|
698
754
|
# cannot branch on prose, and a verdict is exactly the kind of dated, derived
|
|
699
755
|
# record the model never writes. `ok` from this step means the file was written;
|
|
@@ -703,7 +759,15 @@ workflow:
|
|
|
703
759
|
script: { ref: actions }
|
|
704
760
|
subcommand: gate-result
|
|
705
761
|
# The lenient loader needs a flow sequence on ONE line.
|
|
706
|
-
|
|
762
|
+
# backend_sha256 / frontend_sha256 are WHAT THIS RUN CERTIFIED — the shape each plan was
|
|
763
|
+
# in when it was judged. Nothing else in the verdict can say it: backend_state and
|
|
764
|
+
# backend_derived do not move when a plan is hand-edited, so a plan approved here and
|
|
765
|
+
# edited afterwards was indistinguishable from one still in the approved shape. story-plan
|
|
766
|
+
# and both implements compare against these, which is what makes law 40 (approved,
|
|
767
|
+
# changed, never re-approved must not reach code) enforceable rather than stated.
|
|
768
|
+
# RE-READ AFTER ANY REPAIR: the loop above may have rewritten and re-stamped the plans, so
|
|
769
|
+
# the hash recorded has to be the one on disk NOW, not the one the freshness probe saw.
|
|
770
|
+
args: ["--gate", "analyze-design", "--feature-dir", "{{feature_dir}}", "--ok", "{{analyze_ok}}", "--spec-version", "{{current_version}}", "--report", "{{report_path}}", "--gap-count", "{{found_count}}", "--gap-refs", "{{found_refs}}", "--field", "gaps_open={{gap_count}}", "--field", "repaired={{repaired}}", "--field", "backend_plan={{be_plan}}", "--field", "backend_in_scope={{be_in_scope}}", "--field", "backend_state={{be_state}}", "--field", "backend_derived={{be_derived}}", "--field", "backend_sha256={{be_final_sha}}", "--field", "frontend_plan={{fe_plan}}", "--field", "frontend_in_scope={{fe_in_scope}}", "--field", "frontend_state={{fe_state}}", "--field", "frontend_derived={{fe_derived}}", "--field", "frontend_sha256={{fe_final_sha}}"]
|
|
707
771
|
outputs:
|
|
708
772
|
- { name: result_file, from: "$.result_file" }
|
|
709
773
|
|
|
@@ -234,6 +234,17 @@ workflow:
|
|
|
234
234
|
on_false: stop
|
|
235
235
|
on_false_message: "No spec.md for the current feature. Run /appweaver specify first — the spec is this gate's oracle, and code cannot be conformed to a contract that does not exist."
|
|
236
236
|
|
|
237
|
+
# THE ORACLE HAS TO BE HONEST ABOUT ITS OWN VERSION. This is the only gate whose
|
|
238
|
+
# declared oracle IS spec.md — every judgement below resolves an id's meaning from it
|
|
239
|
+
# — and it stamps {{current_version}} into the machine-readable verdict a pipeline
|
|
240
|
+
# branches on. A spec whose version no longer names its contents makes that stamp a
|
|
241
|
+
# false claim about what was certified, so the fence every other consumer carries
|
|
242
|
+
# belongs here most of all.
|
|
243
|
+
- id: spec_state_guard
|
|
244
|
+
action: include
|
|
245
|
+
module: .appweaver/orchestrator/modules/spec-state-gate.orch.yaml
|
|
246
|
+
with: { command: "/appweaver analyze-slice" }
|
|
247
|
+
|
|
237
248
|
# 1. WHICH STORY, if any. An id in the input scopes the gate to that story; anything
|
|
238
249
|
# else widens it to the feature. Deliberately NO ask: this gate has to be runnable
|
|
239
250
|
# unattended, and a gate that stops to ask cannot run in CI. Naming a story narrows
|
|
@@ -302,7 +313,7 @@ workflow:
|
|
|
302
313
|
condition: "{{story_resolved}} == false"
|
|
303
314
|
action: set
|
|
304
315
|
vars:
|
|
305
|
-
scope_brief: "SCOPE — the WHOLE FEATURE, because no story was named. In scope: every FR- and BR- you can see in spec.md EXCEPT these, which have OPEN stories and are work in flight rather than drift: {{inflight_csv}}. EXTENT — the whole of each in-scope id. Also in scope are the CN-/DC-/IC-/UX- the spec binds this feature by."
|
|
316
|
+
scope_brief: "SCOPE — the WHOLE FEATURE, because no story was named. In scope: every FR- and BR- you can see in spec.md EXCEPT these, which have OPEN stories and are work in flight rather than drift: {{inflight_csv}}. EXTENT — the whole of each in-scope id. Also in scope are the CN-/DC-/IC-/UX- the spec binds this feature by, and the SC- Success Criteria — this phase is the one the spec names as their verifier, and a feature-scoped run is the only scope at which a feature-level outcome can be judged. SC- is scored differently from every other class; see the SUCCESS CRITERIA rules below."
|
|
306
317
|
- id: bridge_story
|
|
307
318
|
condition: "{{story_be_exists}} == true"
|
|
308
319
|
action: set
|
|
@@ -386,6 +397,14 @@ workflow:
|
|
|
386
397
|
args: ["--feature-dir", "{{feature_dir}}", "--ids", "{{exercises_csv}}"]
|
|
387
398
|
outputs:
|
|
388
399
|
- { name: all_paths, from: "$.all_paths" }
|
|
400
|
+
# THE SAME FILES AS A CSV, for the steps that name them in a prompt instead of
|
|
401
|
+
# inlining them. `context_from: all_paths` APPENDS every one of those paths to the
|
|
402
|
+
# envelope, and the engine reads the envelope IN FULL into the user block — so this
|
|
403
|
+
# gate inlined the feature's whole codebase four times over, and again on every fix
|
|
404
|
+
# iteration. Every consumer of that list can open a file itself; what it needed was
|
|
405
|
+
# to be told which files, not to be handed their contents.
|
|
406
|
+
- { name: all_csv, from: "$.all_csv" }
|
|
407
|
+
- { name: all_count, from: "$.all_count" }
|
|
389
408
|
- { name: paths_missing, from: "$.all_missing_csv" }
|
|
390
409
|
- { name: coverage_digest, from: "$.digest" }
|
|
391
410
|
- { name: be_count, from: "$.backend_count" }
|
|
@@ -443,18 +462,23 @@ workflow:
|
|
|
443
462
|
vars:
|
|
444
463
|
test_log: "{{test_log}}"
|
|
445
464
|
no_test_edit_rule: "{{no_test_edit_rule}}"
|
|
465
|
+
all_csv: "{{all_csv}}"
|
|
466
|
+
all_count: "{{all_count}}"
|
|
446
467
|
context:
|
|
447
468
|
- { path: "{{spec_file}}" }
|
|
448
|
-
context_from: all_paths
|
|
449
469
|
- id: fix
|
|
450
470
|
action: agent_generate
|
|
451
471
|
tools: "Read,Write,Edit,Bash"
|
|
452
472
|
permission_mode: acceptEdits
|
|
453
473
|
task: |
|
|
454
474
|
Tests failed. Make them pass by fixing the code (or a genuinely wrong
|
|
455
|
-
test)
|
|
456
|
-
|
|
475
|
+
test), keeping behaviour aligned with the spec's acceptance criteria (spec
|
|
476
|
+
in context). {{no_test_edit_rule}} Test log:
|
|
457
477
|
{{test_log}}
|
|
478
|
+
|
|
479
|
+
This slice's code, both lanes ({{all_count}} file(s)) — READ the ones the
|
|
480
|
+
failures point at; they are named, not inlined:
|
|
481
|
+
{{all_csv}}
|
|
458
482
|
outputs:
|
|
459
483
|
- { name: fixed_files, from: "$.files" }
|
|
460
484
|
|
|
@@ -549,8 +573,10 @@ workflow:
|
|
|
549
573
|
to a FUNCTIONAL story citing the requirement it moves. Two ways out, and they are not
|
|
550
574
|
interchangeable: restore the tests and make the refactor behaviour-preserving, or
|
|
551
575
|
accept that this work changes behaviour — close this story as cancelled, raise a
|
|
552
|
-
functional story for the change through /appweaver sync-spec (if the contract moves)
|
|
553
|
-
or
|
|
576
|
+
functional story for the change through /appweaver sync-spec (⌛ not yet built — until it is, fold the change back by hand under .appweaver/SPEC-EDIT-PROTOCOL.md) (if the contract moves)
|
|
577
|
+
or by raising a story by hand from .appweaver/templates/story-template.md
|
|
578
|
+
(/appweaver add-story, which mints the id and stamps the frontmatter, is ⌛ not yet
|
|
579
|
+
built), and build it there where an FR- can be traced to it.
|
|
554
580
|
|
|
555
581
|
# ═══════ PASS 3 — DOMAIN CONFORMANCE: code vs the gate overlay checklist ═══════
|
|
556
582
|
# With tests green, scan the feature's CODE against the domain gate overlays (the
|
|
@@ -578,18 +604,26 @@ workflow:
|
|
|
578
604
|
action: build_context
|
|
579
605
|
vars:
|
|
580
606
|
no_test_edit_rule: "{{no_test_edit_rule}}"
|
|
607
|
+
all_csv: "{{all_csv}}"
|
|
608
|
+
all_count: "{{all_count}}"
|
|
581
609
|
context:
|
|
582
610
|
- { path: "{{spec_file}}" }
|
|
583
|
-
context_from: all_paths
|
|
584
611
|
- id: check_overlays
|
|
585
612
|
action: agent_query
|
|
613
|
+
# A query step is PAYLOAD-ONLY unless it declares tools. It read the code because
|
|
614
|
+
# context_from inlined every file; naming them instead without granting Read would
|
|
615
|
+
# leave it reasoning about a codebase it cannot open.
|
|
616
|
+
tools: Read
|
|
586
617
|
include_standards: true # the gate overlays (checklist) are in the standards
|
|
587
618
|
schema: { ok: true, violations: [ { rule: "<overlay rule / id>", where: "<file:symbol>", issue: "<what is violated>" } ] }
|
|
588
619
|
mock: { ok: true, violations: [] }
|
|
589
620
|
task: |
|
|
590
621
|
The domain gate checklist is loaded in the standards (the overlay
|
|
591
|
-
forbidden-patterns, refusal conditions, and CI gates).
|
|
592
|
-
|
|
622
|
+
forbidden-patterns, refusal conditions, and CI gates). This slice's code and
|
|
623
|
+
tests, both lanes ({{all_count}} file(s)) — READ them; they are named, not
|
|
624
|
+
inlined:
|
|
625
|
+
{{all_csv}}
|
|
626
|
+
Check them against EACH checklist rule and list
|
|
593
627
|
EVERY violation, e.g.: a hard delete of a retention-protected aggregate /
|
|
594
628
|
version history / audit record; a second write path bypassing the aggregate
|
|
595
629
|
or interop adapter; an invented/free-text coded value where a validated code
|
|
@@ -666,12 +700,16 @@ workflow:
|
|
|
666
700
|
referrer_names: "{{referrer_names}}"
|
|
667
701
|
referrer_count: "{{referrer_count}}"
|
|
668
702
|
integrity_log: "{{integrity_log}}"
|
|
703
|
+
all_csv: "{{all_csv}}"
|
|
704
|
+
all_count: "{{all_count}}"
|
|
669
705
|
context:
|
|
670
706
|
- { path: "{{spec_file}}" }
|
|
671
|
-
context_from: all_paths
|
|
672
707
|
- id: write_broken_referrers
|
|
673
708
|
action: agent_author
|
|
674
709
|
condition: "{{integrity_ok}} != true"
|
|
710
|
+
# An author step is PAYLOAD-ONLY unless it declares tools. The triage report has to
|
|
711
|
+
# name what this slice CHANGED that the referrers depend on, which means reading it.
|
|
712
|
+
tools: Read
|
|
675
713
|
include_standards: false # a triage report, not generated code
|
|
676
714
|
output_target: { type: file, path: "{{feature_dir}}/broken-referrers.md" }
|
|
677
715
|
task: |
|
|
@@ -681,6 +719,9 @@ workflow:
|
|
|
681
719
|
referrer (that is the referrer's own slice's job). Referrer blast radius
|
|
682
720
|
({{referrer_count}}): {{referrer_names}}. Failing test log:
|
|
683
721
|
{{integrity_log}}
|
|
722
|
+
This slice's code, both lanes ({{all_count}} file(s)) — READ what it changed on the
|
|
723
|
+
published surface to say WHY the referrers broke; the files are named, not inlined:
|
|
724
|
+
{{all_csv}}
|
|
684
725
|
Write markdown, in this order:
|
|
685
726
|
- `# Broken Referrers — <feature>` and a one-line statement of what this file is:
|
|
686
727
|
aggregates that USE this feature and whose tests went red when this feature's
|
|
@@ -730,6 +771,8 @@ workflow:
|
|
|
730
771
|
bridge_note: "{{bridge_note}}"
|
|
731
772
|
coverage_digest: "{{coverage_digest}}"
|
|
732
773
|
stale_note: "{{stale_note}}"
|
|
774
|
+
all_csv: "{{all_csv}}"
|
|
775
|
+
all_count: "{{all_count}}"
|
|
733
776
|
context:
|
|
734
777
|
- { path: "{{spec_file}}" } # THE ORACLE — the contract's own wording
|
|
735
778
|
- { path: "{{story_path}}", optional: true } # the extent: what this slice claims of each id
|
|
@@ -739,12 +782,11 @@ workflow:
|
|
|
739
782
|
- { path: "{{fe_plan}}", optional: true }
|
|
740
783
|
- { path: "{{feature_dir}}/interfaces.md", optional: true } # the published surface, cited by IF- id
|
|
741
784
|
- { path: "{{feature_dir}}/adr.md", optional: true } # ratified decisions
|
|
742
|
-
context_from: all_paths # the CODE — both lanes, from the OKF concept
|
|
743
785
|
- id: trace
|
|
744
786
|
action: agent_query
|
|
745
787
|
tools: Read # follow a call out of the listed files when a trace needs it
|
|
746
788
|
include_standards: false # the contract is the spec, not a rule corpus
|
|
747
|
-
schema: { ok: true, bridge_ok: true, gap_count: 0, gap_refs: "<comma-separated spec ids the gaps touch>", coverage: "<one line per in-scope id — id | what the spec requires | the design element that maps it | the code that realises it | DELIVERED or GAP>", gaps: [ { severity: "high", spec_ref: "<FR/BR/CN/DC/IC/UX id>", kind: "code-missing|code-wrong|bridge-missing|spec-wrong", where: "<file:symbol, or the document>", gap: "<what the spec requires and what is actually there>", route: "<the command that fixes it>" } ], bridge_gaps: "<one line per in-scope id no design maps, or empty>" }
|
|
789
|
+
schema: { ok: true, bridge_ok: true, gap_count: 0, gap_refs: "<comma-separated spec ids the gaps touch>", coverage: "<one line per in-scope id — id | what the spec requires | the design element that maps it | the code that realises it | DELIVERED or GAP>", gaps: [ { severity: "high", spec_ref: "<FR/BR/CN/DC/IC/UX/SC id>", kind: "code-missing|code-wrong|bridge-missing|spec-wrong", where: "<file:symbol, or the document>", gap: "<what the spec requires and what is actually there>", route: "<the command that fixes it>" } ], bridge_gaps: "<one line per in-scope id no design maps, or empty>" }
|
|
748
790
|
mock: { ok: true, bridge_ok: true, gap_count: 0, gap_refs: "", coverage: "", gaps: [], bridge_gaps: "" }
|
|
749
791
|
task: |
|
|
750
792
|
spec.md is the ORACLE. Judge the CODE against it and report every id it fails to
|
|
@@ -757,9 +799,11 @@ workflow:
|
|
|
757
799
|
|
|
758
800
|
{{bridge_note}}
|
|
759
801
|
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
802
|
+
THIS SLICE'S CODE, both lanes, from the OKF concept ({{all_count}} file(s)) — READ
|
|
803
|
+
the ones each id's trace needs, and follow anything they call. They are named here
|
|
804
|
+
rather than inlined, because a real codebase does not fit a payload:
|
|
805
|
+
{{all_csv}}
|
|
806
|
+
Do NOT modify a single file — this step is read-only by contract.
|
|
763
807
|
|
|
764
808
|
{{coverage_digest}}
|
|
765
809
|
|
|
@@ -777,6 +821,29 @@ workflow:
|
|
|
777
821
|
a home in each. A UX- lives only in the UI lane; a BR- only in the service
|
|
778
822
|
lane. Do not report a UI gap for a slice that has no UI lane in scope.
|
|
779
823
|
|
|
824
|
+
SUCCESS CRITERIA (SC-) ARE SCORED AS OUTCOMES, NOT AS REALISATIONS. An SC- is a
|
|
825
|
+
measurable result the whole feature is supposed to produce — "a customer can place
|
|
826
|
+
an order in under two minutes", "90% of first-time users complete an order without
|
|
827
|
+
assistance" — so no design element "maps" one and no file "realises" one. Four
|
|
828
|
+
rules, and they override steps (2) and (3) and the ok/bridge_ok sentences below for
|
|
829
|
+
this class alone:
|
|
830
|
+
• Score each in-scope SC- as MET, NOT MET, or NOT ASSESSABLE FROM A STATIC READ.
|
|
831
|
+
Most are the third: a latency budget, an adoption rate or a support-ticket
|
|
832
|
+
reduction cannot be decided by reading code, and inventing a verdict is worse
|
|
833
|
+
than recording that it needs measuring.
|
|
834
|
+
• NOT ASSESSABLE is not a gap. It does not set ok=false and it is not counted in
|
|
835
|
+
gap_count — say in its `coverage` line what would have to be measured, and by
|
|
836
|
+
what, to settle it.
|
|
837
|
+
• NOT MET does fail ok: reserve it for the case where the code plainly cannot
|
|
838
|
+
satisfy the criterion (the feature has no such surface at all, or the design
|
|
839
|
+
rules the outcome out).
|
|
840
|
+
• An SC- NEVER sets bridge_ok=false and is NEVER a "bridge-missing" gap. There is
|
|
841
|
+
no design element to be missing, so routing one to story-plan sends someone to
|
|
842
|
+
design a document entry that should not exist.
|
|
843
|
+
Every in-scope SC- still gets its `coverage` line. The record is the point: this is
|
|
844
|
+
the phase the spec names as their verifier, and "measured how, and when" is the
|
|
845
|
+
answer it is meant to leave behind.
|
|
846
|
+
|
|
780
847
|
CLASSIFY every gap by `kind`, because the four routes are different commands:
|
|
781
848
|
code-missing the design maps the id and no code realises it → /appweaver implement
|
|
782
849
|
code-wrong code exists and does something else → /appweaver implement
|
|
@@ -798,9 +865,11 @@ workflow:
|
|
|
798
865
|
Fill `coverage` with ONE LINE PER IN-SCOPE ID, delivered or not — that table is the
|
|
799
866
|
traceability record this gate exists to leave behind, and it is written on a green
|
|
800
867
|
run too. Set gap_count and gap_refs from `gaps`. Set ok=true ONLY when every
|
|
801
|
-
in-scope
|
|
802
|
-
|
|
803
|
-
|
|
868
|
+
in-scope FR-/BR-/CN-/DC-/IC-/UX- is delivered as the spec words it and no in-scope
|
|
869
|
+
SC- is NOT MET — an SC- scored not-assessable leaves ok exactly as it was. Set
|
|
870
|
+
bridge_ok=false when any in-scope id OTHER THAN AN SC- has no design mapping it,
|
|
871
|
+
INDEPENDENTLY of ok — working code with a documentation gap is a true state and must
|
|
872
|
+
be reportable as one.
|
|
804
873
|
{{stale_note}}
|
|
805
874
|
outputs:
|
|
806
875
|
- { name: trace_ok, from: "$.ok" }
|
|
@@ -993,4 +1062,4 @@ outputs:
|
|
|
993
1062
|
okf_orphans: "{{okf_orphans}}"
|
|
994
1063
|
stale_ids: "{{stale_csv}}"
|
|
995
1064
|
active_change: "{{active_change}}"
|
|
996
|
-
next: "CHANGE OPEN ({{active_change}}): /appweaver close-delta — the slice delivers the contract, is domain-conformant and broke no referrer, and its delta is already merged into the master docs, so the change may now be archived and the version bumped. NO OPEN CHANGE: /appweaver close-story {{story}}
|
|
1065
|
+
next: "CHANGE OPEN ({{active_change}}): /appweaver close-delta — the slice delivers the contract, is domain-conformant and broke no referrer, and its delta is already merged into the master docs, so the change may now be archived and the version bumped. NO OPEN CHANGE: /appweaver close-story {{story}} would ratify its ADRs, flip its interface entries live and archive its design passes — but it is ⌛ NOT BUILT YET, so nothing closes a story and this one stays OPEN. Until it lands, do not read a green run here as the story being delivered: its ids stay in flight, a feature-scoped analyze-slice keeps subtracting them, and its ADRs and interface entries stay `proposed`. Then /appweaver run."
|