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
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
# extract). See .appweaver/DESIGN-FORMAT.md + .appweaver/templates/design-template.md.
|
|
13
13
|
# (`plan` re-uses the name of a long-retired phase whose plan.md had zero consumers.)
|
|
14
14
|
#
|
|
15
|
-
# WHERE IT SITS. specify/sync-spec
|
|
15
|
+
# WHERE IT SITS. specify/sync-spec (⌛ sync-spec is not built yet; the hand route is
|
|
16
|
+
# .appweaver/SPEC-EDIT-PROTOCOL.md) move the spec; the developer then runs plan, which
|
|
16
17
|
# conforms this feature's design to what moved. Story designs (/appweaver story-plan)
|
|
17
18
|
# READ this document for the ids they carry and NEVER write back — nothing merges into
|
|
18
19
|
# it, so it keeps exactly one writer and cannot be silently rewritten. It is the
|
|
@@ -198,19 +199,10 @@ workflow:
|
|
|
198
199
|
# conformance theatre: an edit that skipped the Change History has no version, so
|
|
199
200
|
# the derivation stamp this run writes would claim a baseline the ledger cannot
|
|
200
201
|
# name. spec-check's only ok:false is exactly that case.
|
|
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
202
|
- id: spec_guard
|
|
210
|
-
action:
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
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 — a plan derived from an unledgered spec stamps a baseline the ledger cannot name."
|
|
203
|
+
action: include
|
|
204
|
+
module: .appweaver/orchestrator/modules/spec-state-gate.orch.yaml
|
|
205
|
+
with: { command: "/appweaver plan" }
|
|
214
206
|
|
|
215
207
|
# 4. Scaffold backend-plan.md (idempotent). --lane backend resolves the pair
|
|
216
208
|
# design-template → backend-plan.md, and MIGRATES a legacy design.md by renaming
|
|
@@ -284,7 +276,7 @@ workflow:
|
|
|
284
276
|
action: verify_context
|
|
285
277
|
require_true: plan_stale
|
|
286
278
|
on_false: stop
|
|
287
|
-
on_false_message: "backend-plan.md is in step with spec {{current_version}} — nothing to update."
|
|
279
|
+
on_false_message: "backend-plan.md is in step with spec {{current_version}} — nothing to update. If the PLAN is wrong and the spec is right, hand-edit backend-plan.md and re-run /appweaver plan 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."
|
|
288
280
|
|
|
289
281
|
# 5b. THE CODE. The plan is an architectural confirmation, so it must know what the
|
|
290
282
|
# feature already looks like on disk: concept-paths resolves the feature's OKF
|
|
@@ -549,6 +541,13 @@ workflow:
|
|
|
549
541
|
- AN ID THE SPEC NO LONGER DEFINES LOSES ITS REALISATION: remove the elements
|
|
550
542
|
that existed only to serve it (and their §6 rows). An id whose wording moved
|
|
551
543
|
is re-realised from the spec's CURRENT text — the spec is the wording.
|
|
544
|
+
- AN ID THE SPEC HAS NEWLY DEFINED GAINS ONE. An id in the work list that NO
|
|
545
|
+
existing section realises is an ADDITION, not an error and not out of scope:
|
|
546
|
+
give it a home — the §1/§2/§3 elements that realise it, its §4.2 or §5 rows
|
|
547
|
+
where it needs them — and a §6 traceability row like any other. The scope rule
|
|
548
|
+
above says which sections may be touched; it does not say a new requirement may
|
|
549
|
+
not be designed, and reading it that way would let every added requirement
|
|
550
|
+
select nothing to rework and quietly land unrealised.
|
|
552
551
|
- THE SPEC IS THE ONLY DOMAIN INPUT, same as at bootstrap: realise ids, invent
|
|
553
552
|
nothing; an unavoidable new concept is a §7 DA-n.
|
|
554
553
|
- THE CODE IS THE WHAT-EXISTS AUTHORITY, and it is a DIFFERENT authority from the
|
|
@@ -663,6 +662,40 @@ workflow:
|
|
|
663
662
|
- { name: if_unknown_csv, from: "$.unknown_ids_csv" }
|
|
664
663
|
- { name: if_badsup_csv, from: "$.bad_supersedes_csv" }
|
|
665
664
|
|
|
665
|
+
# 7c. THE TWO INTEGRITY SIGNALS log-append ALREADY RETURNS. Both were bound and neither
|
|
666
|
+
# was said out loud — the idiom every other integrity check in these workflows uses
|
|
667
|
+
# is a verify_context that continues, and these had none.
|
|
668
|
+
# unknown_ids_csv an entry's `serves:` names an id the spec does not define,
|
|
669
|
+
# so the BE→FE contract anchor points at nothing.
|
|
670
|
+
# bad_supersedes_csv an entry supersedes one the log does not carry, so what it
|
|
671
|
+
# claims to replace is not there — and readers take the LATEST
|
|
672
|
+
# entry per interface, which is now ambiguous.
|
|
673
|
+
# Announce-only: the entries are appended by the time this is visible, ids are
|
|
674
|
+
# permanent, and an append-only log is corrected by a later entry, never by a rewrite.
|
|
675
|
+
- id: if_unknown_notice
|
|
676
|
+
condition: "{{if_unknown_csv}} != ''"
|
|
677
|
+
action: verify_context
|
|
678
|
+
require_value: if_unknown_csv
|
|
679
|
+
equals: ""
|
|
680
|
+
on_mismatch: continue
|
|
681
|
+
on_mismatch_message: >
|
|
682
|
+
interfaces.md now carries entries whose `serves:` names ids the spec does not define:
|
|
683
|
+
{{if_unknown_csv}}. That is a broken trace anchor in the BE→FE contract — the frontend
|
|
684
|
+
binds to these entries by id, and the requirement they claim to serve is not in
|
|
685
|
+
{{spec_file}}. Either the id is a typo, or the requirement was never specified: fix it
|
|
686
|
+
with a superseding entry (never by editing the log), or take it through /appweaver specify.
|
|
687
|
+
- id: if_badsup_notice
|
|
688
|
+
condition: "{{if_badsup_csv}} != ''"
|
|
689
|
+
action: verify_context
|
|
690
|
+
require_value: if_badsup_csv
|
|
691
|
+
equals: ""
|
|
692
|
+
on_mismatch: continue
|
|
693
|
+
on_mismatch_message: >
|
|
694
|
+
An entry appended to interfaces.md supersedes an id the log does not carry:
|
|
695
|
+
{{if_badsup_csv}}. Nothing was replaced, so the interface it meant to retire is still
|
|
696
|
+
the latest entry for its surface and both now read as current. Append a corrected
|
|
697
|
+
superseding entry naming the real id.
|
|
698
|
+
|
|
666
699
|
# 8. Post-execution hooks.
|
|
667
700
|
- id: resolve_post_hooks
|
|
668
701
|
action: execute_script
|
|
@@ -685,4 +718,4 @@ outputs:
|
|
|
685
718
|
interfaces_published: "{{if_ids_csv}}"
|
|
686
719
|
interfaces_error: "{{if_error}}"
|
|
687
720
|
source_files_read: "{{source_count}}"
|
|
688
|
-
next: "Gate this write: /appweaver analyze-design (spec ↔ plan conformance) — after EVERY bootstrap and EVERY update. Then the backlog and the stories: /appweaver sync-stories, then /appweaver story-plan <id>, which READS this plan and refuses to design against one the spec has moved past. {{fe_next}} READ THE FIELDS ABOVE FIRST: interfaces_published are the IF- ids this run appended to interfaces.md as status:proposed — cite them in the plan's API section on the next update, and never edit the log by hand; interfaces_error non-empty means the extraction was refused and .aw-if-new.tmp.md is still beside the plan for a hand-run of log-append, so THIS RUN PUBLISHED NOTHING; source_files_read 0 means no code was found for this feature, so the plan was written greenfield.
|
|
721
|
+
next: "Gate this write: /appweaver analyze-design (spec ↔ plan conformance) — after EVERY bootstrap and EVERY update. Then the backlog and the stories: /appweaver sync-stories, then /appweaver story-plan <id>, which READS this plan and refuses to design against one the spec has moved past. {{fe_next}} READ THE FIELDS ABOVE FIRST: interfaces_published are the IF- ids this run appended to interfaces.md as status:proposed — cite them in the plan's API section on the next update, and never edit the log by hand; interfaces_error non-empty means the extraction was refused and .aw-if-new.tmp.md is still beside the plan for a hand-run of log-append, so THIS RUN PUBLISHED NOTHING; source_files_read 0 means no code was found for this feature, so the plan was written greenfield."
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
# it wrote. It does not touch stories/. The spec is the CONTRACT; a story is an
|
|
11
11
|
# execution SLICE over it, and the two directions between them each have their own
|
|
12
12
|
# workflow — /appweaver sync-stories refreshes the backlog after the spec moves,
|
|
13
|
-
# /appweaver
|
|
13
|
+
# /appweaver sync-spec (⌛ not yet built — .appweaver/SPEC-EDIT-PROTOCOL.md is the hand
|
|
14
|
+
# route meanwhile) folds a hand-edited story back into the contract. Keeping
|
|
14
15
|
# specify story-blind is what stops the spec->story rule having two implementations.
|
|
15
16
|
#
|
|
16
17
|
# Auto-detects first-vs-subsequent from spec.md:
|
|
@@ -392,6 +393,12 @@ workflow:
|
|
|
392
393
|
# nothing. Fed to the author as a preference, then checked against the diff below —
|
|
393
394
|
# the preference is a nudge, the check after the fact is the detector.
|
|
394
395
|
# Empty until the backlog exists, which degrades to "no preference, no ask".
|
|
396
|
+
# CARRIED, NOT COVERED. `covered_csv` is the DELIVERY relation — the FR-/BR- ids open
|
|
397
|
+
# stories are delivering — and a story also CARRIES the bounds it is built to satisfy.
|
|
398
|
+
# Amending or removing a CN-/DC-/IC-/UX- moves ground somebody is standing on exactly
|
|
399
|
+
# as amending an FR- does, and reading the narrower set here meant it raised no ask at
|
|
400
|
+
# all and printed "nobody has open work" while stories were being built against it.
|
|
401
|
+
# `carried_csv` is exercises: ∪ the `constraints:` frontmatter story-stamp lifts.
|
|
395
402
|
- id: ext_story_context
|
|
396
403
|
condition: "{{needs_bootstrap}} == false"
|
|
397
404
|
action: execute_script
|
|
@@ -401,18 +408,21 @@ workflow:
|
|
|
401
408
|
args: ["--feature-dir", "{{feature_dir}}"]
|
|
402
409
|
outputs:
|
|
403
410
|
- { name: covered_csv, from: "$.covered_csv" }
|
|
411
|
+
- { name: carried_csv, from: "$.carried_csv" }
|
|
412
|
+
- { name: backlog_scanned, from: "$.ok" }
|
|
404
413
|
|
|
405
414
|
# 6b-bis. WORD THE PREFERENCE FOR THE CASE AT HAND. The rule below is an argument for
|
|
406
415
|
# preferring an ADDITION over an amendment, and it only makes sense while somebody is
|
|
407
|
-
# actually building. Interpolating
|
|
416
|
+
# actually building. Interpolating the carried set straight into it left a spec with no
|
|
408
417
|
# backlog reading an eight-line case for adding requirements under a blank list — the
|
|
409
418
|
# logic was vacuous ("one of those" names nothing) but the rhetoric was not, and the
|
|
410
419
|
# first extend of a spec nobody has stories against is exactly where amending in place
|
|
411
|
-
# is the honest thing to do.
|
|
412
|
-
# same idiom as readdit_verdict_na/_real: the no-backlog wording is the default,
|
|
413
|
-
#
|
|
414
|
-
#
|
|
415
|
-
#
|
|
420
|
+
# is the honest thing to do. THREE ORDERED SETS rather than a compound condition, the
|
|
421
|
+
# same idiom as readdit_verdict_na/_real: the no-backlog wording is the default, the
|
|
422
|
+
# preference replaces it where there is open work to protect, and the scan-failed
|
|
423
|
+
# wording is last so it wins over both.
|
|
424
|
+
# Safe on the bootstrap path too — ext_story_context is skipped there, so nothing is
|
|
425
|
+
# bound and only the default can fire.
|
|
416
426
|
- id: ext_covered_none
|
|
417
427
|
action: set
|
|
418
428
|
vars:
|
|
@@ -424,11 +434,13 @@ workflow:
|
|
|
424
434
|
without inventing a companion requirement to avoid touching anything.
|
|
425
435
|
|
|
426
436
|
- id: ext_covered_some
|
|
427
|
-
condition: "{{
|
|
437
|
+
condition: "{{carried_csv}} != ''"
|
|
428
438
|
action: set
|
|
429
439
|
vars:
|
|
430
440
|
covered_note: >
|
|
431
|
-
THESE REQUIREMENTS ARE BEING BUILT RIGHT NOW: {{
|
|
441
|
+
THESE REQUIREMENTS AND BOUNDS ARE BEING BUILT AGAINST RIGHT NOW: {{carried_csv}}.
|
|
442
|
+
(FR-/BR- are what open stories DELIVER; CN-/DC-/IC-/UX- are what they are built to
|
|
443
|
+
MEET — moving either moves ground someone is standing on.) Where the change can
|
|
432
444
|
be expressed EITHER as an amendment to one of those or as a new requirement
|
|
433
445
|
alongside it, prefer the new requirement — an amendment moves ground someone is
|
|
434
446
|
standing on, and a requirement that gains a companion leaves their work correct as
|
|
@@ -438,6 +450,21 @@ workflow:
|
|
|
438
450
|
Requirements NOT in that list have no open work against them: amend those in place
|
|
439
451
|
wherever the change genuinely belongs to them.
|
|
440
452
|
|
|
453
|
+
# THE SCAN ITSELF CAN FAIL, and ext_story_context carries `on_error: continue` because a
|
|
454
|
+
# spec must stay editable when its backlog cannot be read. What it must not do is answer
|
|
455
|
+
# as though the scan had run and found nothing: nothing binds, ext_covered_some cannot
|
|
456
|
+
# fire, and the unconditional default above then tells the author NOBODY HAS OPEN WORK —
|
|
457
|
+
# the strongest claim available, on no evidence at all.
|
|
458
|
+
- id: ext_covered_unknown
|
|
459
|
+
condition: "{{backlog_scanned}} != true"
|
|
460
|
+
action: set
|
|
461
|
+
vars:
|
|
462
|
+
covered_note: >
|
|
463
|
+
THE BACKLOG COULD NOT BE READ THIS RUN, so this run does not know who is building
|
|
464
|
+
what. Treat every requirement as possibly under way: prefer adding a requirement to
|
|
465
|
+
amending one, and where you must amend, say in the Change History row exactly what
|
|
466
|
+
moved so the next sync can find every story that carries it.
|
|
467
|
+
|
|
441
468
|
- id: ext_ctx
|
|
442
469
|
condition: "{{needs_bootstrap}} == false"
|
|
443
470
|
action: build_context
|
|
@@ -755,6 +782,26 @@ workflow:
|
|
|
755
782
|
- { name: disturbing_csv, from: "$.disturbing_csv" }
|
|
756
783
|
- { name: reissued_csv, from: "$.reissued_csv" }
|
|
757
784
|
- { name: change_count, from: "$.change_count" }
|
|
785
|
+
- { name: diff_warnings, from: "$.warnings_note" }
|
|
786
|
+
|
|
787
|
+
# 10a-bis. IDS THE LEDGER CANNOT KEY TO A DEFINITION. The diff keys an id to the line that
|
|
788
|
+
# DEFINES it and hashes the block under it; three authoring forms fall outside that,
|
|
789
|
+
# and each one is silent — the requirement moves, change_count stays 0, no row is
|
|
790
|
+
# appended, and the stamp below re-baselines the new body as clean. A standing
|
|
791
|
+
# property of the document rather than a verdict on this run, so it is reported every
|
|
792
|
+
# time until the requirement is rewritten in the shape the template uses.
|
|
793
|
+
- id: diff_warning_notice
|
|
794
|
+
condition: "{{diff_warnings}} != ''"
|
|
795
|
+
action: verify_context
|
|
796
|
+
require_value: diff_warnings
|
|
797
|
+
equals: ""
|
|
798
|
+
on_mismatch: continue
|
|
799
|
+
on_mismatch_message: >
|
|
800
|
+
THE LEDGER CANNOT SEE PART OF THIS SPEC. {{spec_file}} states requirements in a form
|
|
801
|
+
the version diff cannot key to a definition, so an edit to one of them raises no
|
|
802
|
+
Change History row and no story citing it is ever told: {{diff_warnings}}. Rewrite them
|
|
803
|
+
in the template's shape — `- **FR-001**: …`, defined ABOVE the Change History table,
|
|
804
|
+
with any continuation on the lines directly under the id and no blank line between.
|
|
758
805
|
|
|
759
806
|
# 10b. DID THIS MOVE GROUND SOMEONE IS STANDING ON? The diff is fact, so this is the
|
|
760
807
|
# detector the pre-authoring preference lacked: of the requirements this run
|
|
@@ -1065,6 +1112,23 @@ workflow:
|
|
|
1065
1112
|
args: ["--feature-dir", "{{feature_dir}}", "--ids", "{{disturbing_csv}}"]
|
|
1066
1113
|
outputs:
|
|
1067
1114
|
- { name: disturbed_stories, from: "$.story_csv" }
|
|
1115
|
+
- { name: final_refs_ok, from: "$.ok" }
|
|
1116
|
+
|
|
1117
|
+
# 10h-bis. THE SAME HONESTY THE PRE-AUTHORING SCAN NOW HAS. This step carries
|
|
1118
|
+
# `on_error: continue` so a failed re-scan cannot strand a written document, and on
|
|
1119
|
+
# failure disturbed_stories binds to nothing — which the post-restore report (law 45)
|
|
1120
|
+
# then reads as "nobody was disturbed", the one claim it must not make on no evidence.
|
|
1121
|
+
- id: final_refs_unknown_notice
|
|
1122
|
+
condition: "{{final_refs_ok}} != true"
|
|
1123
|
+
action: verify_context
|
|
1124
|
+
require_value: final_refs_ok
|
|
1125
|
+
equals: "true"
|
|
1126
|
+
on_mismatch: continue
|
|
1127
|
+
on_mismatch_message: >
|
|
1128
|
+
The final backlog re-scan did not complete, so the disturbed-stories list below is
|
|
1129
|
+
NOT a list of nobody — it is unknown. The requirements this run moved are
|
|
1130
|
+
{{disturbing_csv}}; run /appweaver sync-stories to find and reconcile the stories
|
|
1131
|
+
carrying them.
|
|
1068
1132
|
|
|
1069
1133
|
# 10i. AN ID THE LEDGER HAS ALREADY SPENT. Ids are permanent: re-issuing a retired one
|
|
1070
1134
|
# silently re-points stories, designs and code that have already shipped, which is
|
|
@@ -1097,6 +1161,29 @@ workflow:
|
|
|
1097
1161
|
outputs:
|
|
1098
1162
|
- { name: ledger_version, from: "$.version" }
|
|
1099
1163
|
|
|
1164
|
+
# 11a-bis. A PROSE-ONLY EDIT RE-BASELINES WITH NO ROW, AND SAYS SO. ledger-append is
|
|
1165
|
+
# conditional on change_count, because law 5 requires a version row to NAME at least
|
|
1166
|
+
# one id and an edit that touched no requirement has none to give. The stamp below is
|
|
1167
|
+
# not conditional, so this run rewrites the baseline and the document's own history
|
|
1168
|
+
# records nothing — which is right, and was silent. Aggregate Boundary, Experience
|
|
1169
|
+
# Scope and the rest of the load-bearing prose are read by every later phase and have
|
|
1170
|
+
# no staleness signal of their own; the least this can do is say that the baseline
|
|
1171
|
+
# moved and nothing recorded why.
|
|
1172
|
+
- id: prose_only_notice
|
|
1173
|
+
condition: "{{change_count}} == 0"
|
|
1174
|
+
action: verify_context
|
|
1175
|
+
require_value: needs_bootstrap
|
|
1176
|
+
equals: "true"
|
|
1177
|
+
on_mismatch: continue
|
|
1178
|
+
on_mismatch_message: >
|
|
1179
|
+
This run changed {{spec_file}} but moved no requirement: the diff found no id added,
|
|
1180
|
+
modified or removed, so no Change History row was appended — a row must name at least
|
|
1181
|
+
one id, and there is none to name. The document is still RE-BASELINED below, so the
|
|
1182
|
+
next run reads it as clean. Nothing else is: if the edit touched load-bearing prose
|
|
1183
|
+
(Aggregate Boundary, Experience Scope, Scope & Assumptions), no story is stale, no
|
|
1184
|
+
sync will notice, and the only record is this message and your commit. Say what moved
|
|
1185
|
+
in the commit message.
|
|
1186
|
+
|
|
1100
1187
|
# ═══════════════ STORY COVERAGE — both paths, before the baseline ══════════
|
|
1101
1188
|
# 11b. Rewrite the spec's AW:COVERAGE block: every FR-/BR- in the document and the
|
|
1102
1189
|
# stories delivering it, read out of the stories/ frontmatter. The relation is
|
|
@@ -1105,7 +1192,7 @@ workflow:
|
|
|
1105
1192
|
#
|
|
1106
1193
|
# Deliberately OUTSIDE the ledger. The hash guards the CONTRACT, and coverage is a
|
|
1107
1194
|
# projection of the backlog — spec-check, spec-diff and ledger-append all cut this
|
|
1108
|
-
# block out before they look. That is what lets
|
|
1195
|
+
# block out before they look. That is what lets add-story and sync-stories write
|
|
1109
1196
|
# it too without a story creation reading as an unledgered spec edit, or a story
|
|
1110
1197
|
# raised against FR-003 reading as FR-003 having changed.
|
|
1111
1198
|
#
|
|
@@ -120,10 +120,31 @@ vars:
|
|
|
120
120
|
fe_plan: ""
|
|
121
121
|
spec_ok: "false"
|
|
122
122
|
spec_state: ""
|
|
123
|
+
# the analyze-design verdict — law 40's evidence. certified defaults FALSE so a probe
|
|
124
|
+
# that could not run fails CLOSED, and the message default carries a route for the same
|
|
125
|
+
# reason: a refusal with no route is a wedged feature.
|
|
126
|
+
gate_status: ""
|
|
127
|
+
gate_certified: "false"
|
|
128
|
+
gate_aborted: "false"
|
|
129
|
+
gate_gap_refs: ""
|
|
130
|
+
gate_report: ""
|
|
131
|
+
gate_be_sha: ""
|
|
132
|
+
gate_fe_sha: ""
|
|
133
|
+
gate_spec_version: ""
|
|
134
|
+
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 story-plan."
|
|
135
|
+
# what has to land first (read here, gated nowhere — see deps_notice)
|
|
136
|
+
story_wave: ""
|
|
137
|
+
depends_on_csv: ""
|
|
138
|
+
open_deps_csv: ""
|
|
139
|
+
unresolved_deps_csv: ""
|
|
140
|
+
deps_clear: "true"
|
|
141
|
+
deps_note: ""
|
|
123
142
|
# per-lane feature-plan freshness (the one-way coupling: a story pass READS a plan)
|
|
124
143
|
be_plan_state: ""
|
|
125
144
|
be_plan_derived: ""
|
|
126
145
|
be_plan_changed: ""
|
|
146
|
+
be_plan_sha: ""
|
|
147
|
+
fe_plan_sha: ""
|
|
127
148
|
be_plan_note: ""
|
|
128
149
|
be_drop_msg: ""
|
|
129
150
|
be_dropped: "false"
|
|
@@ -246,19 +267,10 @@ workflow:
|
|
|
246
267
|
# 3. The spec's ledger tripwire: no design pass from an unledgered spec — every
|
|
247
268
|
# pass records the spec version it designed against, and an unledgered edit has
|
|
248
269
|
# no version to record.
|
|
249
|
-
- id: spec_guard_read
|
|
250
|
-
action: execute_script
|
|
251
|
-
script: { ref: actions }
|
|
252
|
-
subcommand: spec-check
|
|
253
|
-
args: ["--feature-dir", "{{feature_dir}}"]
|
|
254
|
-
outputs:
|
|
255
|
-
- { name: spec_ok, from: "$.ok" }
|
|
256
|
-
- { name: spec_state, from: "$.state" }
|
|
257
270
|
- id: spec_guard
|
|
258
|
-
action:
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
on_false_message: "spec.md has moved outside the ledger (state: {{spec_state}}). Record what changed in a Change History row (fold it back through the spec workflow), re-run spec-stamp to re-baseline, then re-run story-plan — a design pass derived from an unledgered spec records a baseline the ledger cannot name."
|
|
271
|
+
action: include
|
|
272
|
+
module: .appweaver/orchestrator/modules/spec-state-gate.orch.yaml
|
|
273
|
+
with: { command: "/appweaver story-plan" }
|
|
262
274
|
|
|
263
275
|
# 4. THE FEATURE-PLAN GATES, one per declared lane. A story design is a SLICE of
|
|
264
276
|
# the feature's architecture, so it READS that lane's plan and NEVER writes
|
|
@@ -286,6 +298,10 @@ workflow:
|
|
|
286
298
|
- { name: be_plan_state, from: "$.state" }
|
|
287
299
|
- { name: be_plan_derived, from: "$.derived_version" }
|
|
288
300
|
- { name: be_plan_changed, from: "$.changed_csv" }
|
|
301
|
+
# THE PLAN'S SHAPE, not just whether it moved. The certification check below compares
|
|
302
|
+
# this against the hash analyze-design recorded in its verdict — which is the only way
|
|
303
|
+
# to tell "approved" from "approved, then edited".
|
|
304
|
+
- { name: be_plan_sha, from: "$.body_sha256" }
|
|
289
305
|
- id: fe_plan_read
|
|
290
306
|
condition: "{{fe_in_scope}} == true"
|
|
291
307
|
action: execute_script
|
|
@@ -297,6 +313,7 @@ workflow:
|
|
|
297
313
|
- { name: fe_plan_state, from: "$.state" }
|
|
298
314
|
- { name: fe_plan_derived, from: "$.derived_version" }
|
|
299
315
|
- { name: fe_plan_changed, from: "$.changed_csv" }
|
|
316
|
+
- { name: fe_plan_sha, from: "$.body_sha256" }
|
|
300
317
|
|
|
301
318
|
# 4a. SERVICE lane verdict. Only `clean` and `edited` keep the lane; every other
|
|
302
319
|
# state (including the empty string a crashed probe leaves) drops it.
|
|
@@ -494,6 +511,14 @@ workflow:
|
|
|
494
511
|
- { name: fe_next_pass, from: "$.fe_next_pass" }
|
|
495
512
|
- { name: fe_pass_tmp, from: "$.fe_pass_tmp" }
|
|
496
513
|
- { name: if_tmp, from: "$.if_tmp" }
|
|
514
|
+
- { name: story_wave, from: "$.story_wave" }
|
|
515
|
+
- { name: depends_on_csv, from: "$.depends_on_csv" }
|
|
516
|
+
- { name: open_deps_csv, from: "$.open_deps_csv" }
|
|
517
|
+
- { name: unresolved_deps_csv, from: "$.unresolved_deps_csv" }
|
|
518
|
+
- { name: deps_clear, from: "$.deps_clear" }
|
|
519
|
+
- { name: deps_note, from: "$.deps_note" }
|
|
520
|
+
- { name: be_pass_plan_hash, from: "$.be_pass_plan_hash" }
|
|
521
|
+
- { name: fe_pass_plan_hash, from: "$.fe_pass_plan_hash" }
|
|
497
522
|
- id: require_story
|
|
498
523
|
action: verify_context
|
|
499
524
|
require_true: resolved
|
|
@@ -511,6 +536,25 @@ workflow:
|
|
|
511
536
|
# 5a. A dropped lane is announced HERE — after the story is known, so the notice
|
|
512
537
|
# names the story it applies to, and before any write, so the author can stop
|
|
513
538
|
# and fix the plan rather than discovering the gap after a pass has landed.
|
|
539
|
+
# 5.0 WHAT HAS TO LAND FIRST. sync-stories computes depends_on: and wave: and stamps both
|
|
540
|
+
# into every story, and until now nothing downstream read either — the ordering was
|
|
541
|
+
# recorded and never enforced, in the one place with a story in hand to enforce it on.
|
|
542
|
+
# A NOTICE, NOT A REFUSAL: a dependency is about CODE ordering, and designing against
|
|
543
|
+
# work that has not landed is legitimate — it is BUILDING against it that starts to
|
|
544
|
+
# cost something. Two misses are kept apart: a dependency still OPEN in this feature
|
|
545
|
+
# is work in progress, and one with no story file here is UNRESOLVED, which is the
|
|
546
|
+
# ordinary shape of a cross-feature edge and not at all the same as satisfied.
|
|
547
|
+
# Deliberately NOT "an open story in a lower wave": a story becomes startable when its
|
|
548
|
+
# OWN dependencies land, not when the whole previous wave finishes, so warning on the
|
|
549
|
+
# wave would serialise a level that exists precisely to be unordered. The wave is
|
|
550
|
+
# surfaced and gated by nothing.
|
|
551
|
+
- id: deps_notice
|
|
552
|
+
condition: "{{deps_clear}} != true"
|
|
553
|
+
action: verify_context
|
|
554
|
+
require_true: deps_clear
|
|
555
|
+
on_false: continue
|
|
556
|
+
on_false_message: "{{deps_note}}"
|
|
557
|
+
|
|
514
558
|
- id: be_drop_notice
|
|
515
559
|
condition: "{{be_dropped}} == true"
|
|
516
560
|
action: verify_context
|
|
@@ -526,6 +570,120 @@ workflow:
|
|
|
526
570
|
on_mismatch: continue
|
|
527
571
|
on_mismatch_message: "{{fe_drop_msg}}"
|
|
528
572
|
|
|
573
|
+
# ═══════ 5a-bis. LAW 40 — approved, changed, never re-approved must not reach code ═══════
|
|
574
|
+
# analyze-design certifies the feature plans and writes a machine-readable verdict beside
|
|
575
|
+
# its report. Nothing in the kit read one back, so "was this plan approved, and was it
|
|
576
|
+
# approved in the shape it is in NOW" was a question only a person opening the JSON could
|
|
577
|
+
# answer — and this is the phase that turns a feature plan into a slice design that
|
|
578
|
+
# implement builds from without ever re-asking it.
|
|
579
|
+
#
|
|
580
|
+
# HERE, AND NOT AT be_plan_read. Those probes feed the drop-on-behind decision above;
|
|
581
|
+
# gating there would change WHICH lanes get dropped. This runs after the keep/drop set is
|
|
582
|
+
# settled and before anything is authored, so it can supersede be_keep_edited — that step
|
|
583
|
+
# keeps an edited plan and tells the author it "is authoritative here", which is exactly
|
|
584
|
+
# the state law 40 forbids when nobody certified the edit.
|
|
585
|
+
- id: gate_verdict
|
|
586
|
+
action: execute_script
|
|
587
|
+
on_error: continue
|
|
588
|
+
script: { ref: actions }
|
|
589
|
+
subcommand: gate-read
|
|
590
|
+
args: ["--feature-dir", "{{feature_dir}}", "--gate", "analyze-design"]
|
|
591
|
+
outputs:
|
|
592
|
+
- { name: gate_status, from: "$.status" }
|
|
593
|
+
- { name: gate_certified, from: "$.certified" }
|
|
594
|
+
- { name: gate_aborted, from: "$.aborted" }
|
|
595
|
+
- { name: gate_gap_refs, from: "$.gap_refs_csv" }
|
|
596
|
+
- { name: gate_report, from: "$.report" }
|
|
597
|
+
- { name: gate_be_sha, from: "$.backend_sha256" }
|
|
598
|
+
- { name: gate_fe_sha, from: "$.frontend_sha256" }
|
|
599
|
+
- { name: gate_spec_version, from: "$.spec_version" }
|
|
600
|
+
|
|
601
|
+
# THREE REASONS, THREE ROUTES, and telling them apart is the whole value. Ordered sets,
|
|
602
|
+
# last match wins: never ran → gated red → started and aborted. Aborted is last because it
|
|
603
|
+
# also carries status "fail" (gate_result_open writes a red BEFORE any analysis, so an
|
|
604
|
+
# abandoned run never leaves a stale green) and sending someone to read gaps that were
|
|
605
|
+
# never found is the one wrong answer here.
|
|
606
|
+
- id: gate_msg_never
|
|
607
|
+
action: set
|
|
608
|
+
vars:
|
|
609
|
+
gate_msg: "The feature plans for this feature have never been certified: there is no analyze-design verdict beside them. A story design turns the feature plan into the slice implement builds, so designing against an ungated plan puts an unapproved architecture into code through two documents that never re-ask the question. Run /appweaver analyze-design, then re-run story-plan {{story}}."
|
|
610
|
+
- id: gate_msg_red
|
|
611
|
+
condition: "{{gate_status}} == fail"
|
|
612
|
+
action: set
|
|
613
|
+
vars:
|
|
614
|
+
gate_msg: "The feature plans do NOT conform to the spec: analyze-design's last verdict is FAIL on {{gate_gap_refs}}. The full report is at {{gate_report}}. Close those gaps first — a wrong PLAN is repaired by re-running /appweaver analyze-design and accepting the repair, or by hand-editing it and re-running /appweaver plan (or plan-ui) with accept_manual: true; a wrong SPEC goes through the spec route. Then re-run story-plan {{story}}."
|
|
615
|
+
- id: gate_msg_aborted
|
|
616
|
+
condition: "{{gate_aborted}} == true"
|
|
617
|
+
action: set
|
|
618
|
+
vars:
|
|
619
|
+
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 go and read. The plans are UNCERTIFIED, not rejected. Run /appweaver analyze-design to completion, then re-run story-plan {{story}}."
|
|
620
|
+
|
|
621
|
+
- id: gate_certified_guard
|
|
622
|
+
condition: "{{have_lane}} == true"
|
|
623
|
+
action: verify_context
|
|
624
|
+
require_true: gate_certified
|
|
625
|
+
on_false: stop
|
|
626
|
+
on_false_message: "{{gate_msg}}"
|
|
627
|
+
|
|
628
|
+
# PER KEPT LANE, and only per kept lane. A lane that is undeclared or was dropped above
|
|
629
|
+
# has no certified hash and must not fail the run for lacking one.
|
|
630
|
+
# An EMPTY recorded hash fails the comparison, and that is correct: a verdict that does
|
|
631
|
+
# not say which shape it certified cannot prove this is that shape.
|
|
632
|
+
- id: be_certified_shape
|
|
633
|
+
condition: "{{be_in_scope}} == true"
|
|
634
|
+
action: verify_context
|
|
635
|
+
require_value: be_plan_sha
|
|
636
|
+
equals: "{{gate_be_sha}}"
|
|
637
|
+
on_mismatch: stop
|
|
638
|
+
on_mismatch_message: "backend-plan.md is not the document analyze-design certified — it has been edited since (or the verdict predates hash recording and cannot say what it approved). Designing this slice against it would carry an unapproved architecture into code, which is exactly what law 40 forbids. Re-run /appweaver analyze-design to certify the plan as it now stands, then re-run story-plan {{story}}."
|
|
639
|
+
- id: fe_certified_shape
|
|
640
|
+
condition: "{{fe_in_scope}} == true"
|
|
641
|
+
action: verify_context
|
|
642
|
+
require_value: fe_plan_sha
|
|
643
|
+
equals: "{{gate_fe_sha}}"
|
|
644
|
+
on_mismatch: stop
|
|
645
|
+
on_mismatch_message: "frontend-plan.md is not the document analyze-design certified — it has been edited since (or the verdict predates hash recording and cannot say what it approved). Re-run /appweaver analyze-design to certify the plan as it now stands, then re-run story-plan {{story}}."
|
|
646
|
+
|
|
647
|
+
# ═══════ 5a-ter. WHICH LANES THIS STORY WAS DESIGNED FOR, AND WHICH WERE DROPPED ═══════
|
|
648
|
+
# Two fields, because one of them cannot say the difference. analyze-design STOPS on a
|
|
649
|
+
# lane whose plan is behind; this workflow DROPS it and designs the other — so one command
|
|
650
|
+
# later "this slice has no user-facing surface" and "the UI lane was out of scope when it
|
|
651
|
+
# was designed" are the same state on disk, and a UX--bearing requirement can ship
|
|
652
|
+
# backend-only with a green story-scoped gate.
|
|
653
|
+
# story-stamp is the frontmatter writer, and every one of its fields is guarded by its own
|
|
654
|
+
# flag, so these calls touch nothing else in the story. One per lane per outcome, because
|
|
655
|
+
# the keys UNION: a lane that has ever been designed keeps saying so.
|
|
656
|
+
# NOT story-plan-doc — that writes the PASS, and a dropped lane has no pass, which is the
|
|
657
|
+
# entire point. NOT sync-stories — stories are not designed there.
|
|
658
|
+
- id: stamp_lane_be_designed
|
|
659
|
+
condition: "{{be_in_scope}} == true"
|
|
660
|
+
action: execute_script
|
|
661
|
+
on_error: continue
|
|
662
|
+
script: { ref: actions }
|
|
663
|
+
subcommand: story-stamp
|
|
664
|
+
args: ["--feature-dir", "{{feature_dir}}", "--story", "{{story}}", "--designed-lanes", "backend"]
|
|
665
|
+
- id: stamp_lane_be_dropped
|
|
666
|
+
condition: "{{be_dropped}} == true"
|
|
667
|
+
action: execute_script
|
|
668
|
+
on_error: continue
|
|
669
|
+
script: { ref: actions }
|
|
670
|
+
subcommand: story-stamp
|
|
671
|
+
args: ["--feature-dir", "{{feature_dir}}", "--story", "{{story}}", "--dropped-lanes", "backend"]
|
|
672
|
+
- id: stamp_lane_fe_designed
|
|
673
|
+
condition: "{{fe_in_scope}} == true"
|
|
674
|
+
action: execute_script
|
|
675
|
+
on_error: continue
|
|
676
|
+
script: { ref: actions }
|
|
677
|
+
subcommand: story-stamp
|
|
678
|
+
args: ["--feature-dir", "{{feature_dir}}", "--story", "{{story}}", "--designed-lanes", "frontend"]
|
|
679
|
+
- id: stamp_lane_fe_dropped
|
|
680
|
+
condition: "{{fe_dropped}} == true"
|
|
681
|
+
action: execute_script
|
|
682
|
+
on_error: continue
|
|
683
|
+
script: { ref: actions }
|
|
684
|
+
subcommand: story-stamp
|
|
685
|
+
args: ["--feature-dir", "{{feature_dir}}", "--story", "{{story}}", "--dropped-lanes", "frontend"]
|
|
686
|
+
|
|
529
687
|
# 5b. A pre-split story-plan.md beside the story is REPORTED, never read and never
|
|
530
688
|
# written. Silently leaving it there would leave a design document nobody
|
|
531
689
|
# consumes sitting beside two that are read — and the next person to open the
|
|
@@ -706,7 +864,12 @@ workflow:
|
|
|
706
864
|
action: execute_script
|
|
707
865
|
script: { ref: actions }
|
|
708
866
|
subcommand: story-plan-doc
|
|
709
|
-
|
|
867
|
+
# --plan-hash stamps the CERTIFIED feature-plan hash into the pass header — the one
|
|
868
|
+
# analyze-design recorded, which the guard above has just proved is the plan on disk.
|
|
869
|
+
# A pass is written once and read forever, so without it implement could only ask
|
|
870
|
+
# "is the plan certified NOW", and a pass designed against plan H1 would still
|
|
871
|
+
# generate after the plan was edited to H2 and re-gated — the exact case law 40 names.
|
|
872
|
+
args: ["--feature-dir", "{{feature_dir}}", "--story", "{{story}}", "--lane", "backend", "--pass-file", "{{be_pass_tmp}}", "--spec-version", "{{current_version}}", "--plan-hash", "{{gate_be_sha}}"]
|
|
710
873
|
outputs:
|
|
711
874
|
- { name: be_pass_n, from: "$.pass" }
|
|
712
875
|
- { name: be_created, from: "$.created" }
|
|
@@ -778,6 +941,40 @@ workflow:
|
|
|
778
941
|
- { name: if_unknown_csv, from: "$.unknown_ids_csv" }
|
|
779
942
|
- { name: if_badsup_csv, from: "$.bad_supersedes_csv" }
|
|
780
943
|
|
|
944
|
+
# 7c. THE TWO INTEGRITY SIGNALS log-append ALREADY RETURNS. Both were bound and neither
|
|
945
|
+
# was said out loud — the idiom every other integrity check in these workflows uses
|
|
946
|
+
# is a verify_context that continues, and these had none.
|
|
947
|
+
# unknown_ids_csv an entry's `serves:` names an id the spec does not define,
|
|
948
|
+
# so the BE→FE contract anchor points at nothing.
|
|
949
|
+
# bad_supersedes_csv an entry supersedes one the log does not carry, so what it
|
|
950
|
+
# claims to replace is not there — and readers take the LATEST
|
|
951
|
+
# entry per interface, which is now ambiguous.
|
|
952
|
+
# Announce-only: the entries are appended by the time this is visible, ids are
|
|
953
|
+
# permanent, and an append-only log is corrected by a later entry, never by a rewrite.
|
|
954
|
+
- id: if_unknown_notice
|
|
955
|
+
condition: "{{if_unknown_csv}} != ''"
|
|
956
|
+
action: verify_context
|
|
957
|
+
require_value: if_unknown_csv
|
|
958
|
+
equals: ""
|
|
959
|
+
on_mismatch: continue
|
|
960
|
+
on_mismatch_message: >
|
|
961
|
+
interfaces.md now carries entries whose `serves:` names ids the spec does not define:
|
|
962
|
+
{{if_unknown_csv}}. That is a broken trace anchor in the BE→FE contract — the frontend
|
|
963
|
+
binds to these entries by id, and the requirement they claim to serve is not in
|
|
964
|
+
{{spec_file}}. Either the id is a typo, or the requirement was never specified: fix it
|
|
965
|
+
with a superseding entry (never by editing the log), or take it through /appweaver specify.
|
|
966
|
+
- id: if_badsup_notice
|
|
967
|
+
condition: "{{if_badsup_csv}} != ''"
|
|
968
|
+
action: verify_context
|
|
969
|
+
require_value: if_badsup_csv
|
|
970
|
+
equals: ""
|
|
971
|
+
on_mismatch: continue
|
|
972
|
+
on_mismatch_message: >
|
|
973
|
+
An entry appended to interfaces.md supersedes an id the log does not carry:
|
|
974
|
+
{{if_badsup_csv}}. Nothing was replaced, so the interface it meant to retire is still
|
|
975
|
+
the latest entry for its surface and both now read as current. Append a corrected
|
|
976
|
+
superseding entry naming the real id.
|
|
977
|
+
|
|
781
978
|
# ══════════════════════ THE UI DESIGN (payload-only + Read) ══════════════════
|
|
782
979
|
# The UI lane's rules load here, AFTER the service pass has been written, so a
|
|
783
980
|
# service author is never handed component and styling rules. The pass reads the
|
|
@@ -913,7 +1110,12 @@ workflow:
|
|
|
913
1110
|
action: execute_script
|
|
914
1111
|
script: { ref: actions }
|
|
915
1112
|
subcommand: story-plan-doc
|
|
916
|
-
|
|
1113
|
+
# --plan-hash stamps the CERTIFIED feature-plan hash into the pass header — the one
|
|
1114
|
+
# analyze-design recorded, which the guard above has just proved is the plan on disk.
|
|
1115
|
+
# A pass is written once and read forever, so without it implement could only ask
|
|
1116
|
+
# "is the plan certified NOW", and a pass designed against plan H1 would still
|
|
1117
|
+
# generate after the plan was edited to H2 and re-gated — the exact case law 40 names.
|
|
1118
|
+
args: ["--feature-dir", "{{feature_dir}}", "--story", "{{story}}", "--lane", "frontend", "--pass-file", "{{fe_pass_tmp}}", "--spec-version", "{{current_version}}", "--plan-hash", "{{gate_fe_sha}}"]
|
|
917
1119
|
outputs:
|
|
918
1120
|
- { name: fe_pass_n, from: "$.pass" }
|
|
919
1121
|
- { name: fe_created, from: "$.created" }
|