aw-spec-cli 0.1.16 → 0.1.17

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.
Files changed (70) hide show
  1. package/assets/.appweaver/DESIGN-FORMAT.md +18 -18
  2. package/assets/.appweaver/ORCH-DIALECT.md +1 -3
  3. package/assets/.appweaver/RULE-PARTITION.md +29 -13
  4. package/assets/.appweaver/STACK-PROFILES.md +10 -9
  5. package/assets/.appweaver/orchestrator/.state/smoke.state.json +32 -0
  6. package/assets/.appweaver/orchestrator/AGENT-PROFILE.md +4 -1
  7. package/assets/.appweaver/orchestrator/modules/delta-plan.orch.yaml +251 -0
  8. package/assets/.appweaver/orchestrator/modules/delta-spec.orch.yaml +104 -35
  9. package/assets/.appweaver/orchestrator/orchestrator.py +5 -4
  10. package/assets/.appweaver/orchestrator/rev-templates/delta-design-template.skeleton.md +94 -36
  11. package/assets/.appweaver/orchestrator/rev-templates/design-template.skeleton.md +1 -1
  12. package/assets/.appweaver/orchestrator/rev-templates/domain-model-template.skeleton.md +115 -0
  13. package/assets/.appweaver/orchestrator/rev-templates/domain-rule-bindings.skeleton.md +2 -9
  14. package/assets/.appweaver/orchestrator/rev-templates/tech-rule-bindings.skeleton.md +16 -40
  15. package/assets/.appweaver/orchestrator/rev-templates/ui-delta-design-template.skeleton.md +38 -17
  16. package/assets/.appweaver/orchestrator/rev-templates/ui-design-template.skeleton.md +8 -8
  17. package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +10 -11
  18. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan-delta.orch.yaml +673 -0
  19. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +535 -0
  20. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +643 -0
  21. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +84 -701
  22. package/assets/.appweaver/orchestrator/workflows/aw-spec-build.orch.yaml +4 -4
  23. package/assets/.appweaver/orchestrator/workflows/aw-spec-characterize.orch.yaml +7 -7
  24. package/assets/.appweaver/orchestrator/workflows/aw-spec-close-delta.orch.yaml +16 -14
  25. package/assets/.appweaver/orchestrator/workflows/aw-spec-constitution.orch.yaml +2 -2
  26. package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +243 -144
  27. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +877 -578
  28. package/assets/.appweaver/orchestrator/workflows/aw-spec-inventory.orch.yaml +4 -4
  29. package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +32 -91
  30. package/assets/.appweaver/orchestrator/workflows/aw-spec-parity.orch.yaml +2 -2
  31. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +836 -514
  32. package/assets/.appweaver/orchestrator/workflows/aw-spec-run.orch.yaml +2 -2
  33. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-bounded-context.orch.yaml +132 -0
  34. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-feature.orch.yaml +38 -5
  35. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-stack.orch.yaml +3 -3
  36. package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +271 -979
  37. package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +1085 -3385
  38. package/assets/.appweaver/scripts/bash/aw-xref.sh +1 -2
  39. package/assets/.appweaver/scripts/bash/common.sh +47 -0
  40. package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +914 -3220
  41. package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +1 -2
  42. package/assets/.appweaver/scripts/python/aw-payload-parity.py +19 -3
  43. package/assets/.appweaver/scripts/python/aw-stack.py +6 -2
  44. package/assets/.appweaver/scripts/python/aw-xref.py +86 -350
  45. package/assets/.appweaver/templates/constitution-ui-template.md +1 -1
  46. package/assets/.appweaver/templates/delta-design-template.md +120 -30
  47. package/assets/.appweaver/templates/delta-domain-model-template.md +81 -0
  48. package/assets/.appweaver/templates/delta-spec-template.md +43 -17
  49. package/assets/.appweaver/templates/design-template.md +68 -98
  50. package/assets/.appweaver/templates/domain-model-template.md +130 -0
  51. package/assets/.appweaver/templates/okf-concept-template.md +2 -2
  52. package/assets/.appweaver/templates/source-map-template.md +3 -3
  53. package/assets/.appweaver/templates/spec-template.md +100 -122
  54. package/assets/.appweaver/templates/ui-delta-design-template.md +159 -0
  55. package/assets/.appweaver/templates/ui-design-template.md +46 -75
  56. package/assets/guardrails/projects/dotnet-default-project/domain-rule-bindings.md +7 -18
  57. package/assets/guardrails/projects/dotnet-default-project/tech-rule-bindings.md +34 -39
  58. package/assets/guardrails/requirement-spec.txt +2 -2
  59. package/assets/guardrails/technology/dotnet-webapi/testing.md +1 -1
  60. package/package.json +1 -1
  61. package/assets/.appweaver/orchestrator/modules/delta-design.orch.yaml +0 -105
  62. package/assets/.appweaver/orchestrator/modules/spec-state-gate.orch.yaml +0 -87
  63. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-delta-design.orch.yaml +0 -251
  64. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-design.orch.yaml +0 -813
  65. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement-delta.orch.yaml +0 -253
  66. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement-ui.orch.yaml +0 -663
  67. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan-ui.orch.yaml +0 -600
  68. package/assets/.appweaver/orchestrator/workflows/aw-spec-story-plan.orch.yaml +0 -1147
  69. package/assets/.appweaver/orchestrator/workflows/aw-spec-sync-stories.orch.yaml +0 -1121
  70. package/assets/.appweaver/templates/story-template.md +0 -178
@@ -1,1121 +0,0 @@
1
- # =============================================================================
2
- # aw-spec-sync-stories (orchestrator) — raise and refresh the BACKLOG from the spec
3
- #
4
- # Spec-anchored pipeline, spec phase. The spec is the CONTRACT and a story is an
5
- # execution SLICE over it. The two directions between them each have their own
6
- # workflow and neither is shared: sync-stories carries the SPEC forward into the
7
- # backlog; /appweaver sync-spec (⌛ not yet built — .appweaver/SPEC-EDIT-PROTOCOL.md is the
8
- # hand route meanwhile) folds a hand-edited STORY back into the contract.
9
- # Keeping them apart is what stops the spec->story rule having two implementations.
10
- #
11
- # WHAT THIS READS. The spec's `## Change History` — the version ledger. Each row names
12
- # the requirement ids that version touched; each story records the version it last
13
- # synced at. "What has moved since the backlog was last in step" is therefore a read of
14
- # rows and one integer comparison, with no diffing, no hashing and no model involved.
15
- #
16
- # THE WORK SET is the union of the ids named by every row ABOVE THE LOWEST spec_version
17
- # across the OPEN stories — not the newest row alone. Those are the same set whenever the
18
- # backlog is one version behind, which is the ordinary case; they diverge only when a sync
19
- # was skipped, and that is the case worth getting right. Two specify runs with no sync
20
- # between them leave the middle version named by no row anyone reads, and its requirements
21
- # would get no story with nothing to say so. Silent, in the one mechanism built to make
22
- # staleness loud. `ledger-read` computes it and names the versions it covered.
23
- #
24
- # AT v1.0 there is no "since": nothing has moved because nothing has synced. So when NOTHING
25
- # has synced — an empty backlog, or stories predating the field — the work set is every
26
- # FR-/BR- the document declares, and the per-group rules below then create only what has no
27
- # story. A second run over the same spec creates nothing (E42).
28
- #
29
- # A BACKLOG THAT IS ENTIRELY CLOSED IS NOT THAT CASE. A closed story carries the version its
30
- # work was measured against, so it sets the floor exactly as an open one does, and a
31
- # requirement whose closed story is stamped at or above the version that last moved it is
32
- # DELIVERED rather than unsliced. Reading the floor from open stories alone made the last
33
- # story closing look identical to a spec nobody had ever sliced, and one idle re-run then
34
- # raised a fresh story for every requirement in the feature (E72). Cancelled stories are
35
- # excluded throughout: abandoned work delivered nothing and settles nothing.
36
- #
37
- # FR-/BR- ONLY, throughout. CN-/DC-/IC-/UX- move the contract and belong in the ledger
38
- # row, but they are constraints a story CARRIES, not slices of work it delivers — they
39
- # reach the story through its `## Constraints` section and never through `exercises:`.
40
- #
41
- # THE SHAPE OF A RUN.
42
- # 1. the tripwire spec.md hand-edited with no version row -> REFUSE. Every story
43
- # citing an edited requirement is already silently stale, and
44
- # raising a backlog on top would bury the edit rather than name it.
45
- # 2. the work set ledger-read. An empty ledger stops the run with a warning: there
46
- # is no version to sync from, and inventing one raises a backlog
47
- # against a document nobody has committed to.
48
- # 3. grouping the one judgment in the workflow. Requirements that belong to one
49
- # slice become one story; a group may hold one id or several. How
50
- # coarsely is steered by the launch line's strategy word.
51
- # 4. the ask where a group already has open stories in front of it. Asked, not
52
- # decided — see below.
53
- # 5. the writes one file per plan entry, then a deterministic frontmatter stamp.
54
- # 6. coverage the spec's Story Coverage block, regenerated from stories/.
55
- #
56
- # ONE STORY, IF THAT IS WHAT YOU ASKED FOR. `/appweaver sync-stories single-run` puts the
57
- # WHOLE work set into a single story — at v1.0 that is the whole spec, on a later run it is
58
- # the whole update. It is the right shape for a small feature, for a spike, and for the
59
- # change that is one thing however many requirement ids it happened to move.
60
- #
61
- # AND IT IS A REQUEST, NOT A GUARANTEE. Where the work set is too heavy to be picked up as
62
- # one piece, it is split along its DEPENDENCIES rather than written as a story nobody can
63
- # start — and the run says both that it split and what came out. A silent split leaves
64
- # someone believing they have one story when they have six; a silently oversized story is
65
- # worse, because it is the one that never gets picked up at all.
66
- #
67
- # THE ORDER, AND WHY IT IS CALLED A WAVE. Stories leave a sync with no inherent order — an
68
- # id is minting order, which is not delivery order. So the grouping declares which slices
69
- # stand behind which, and the arithmetic lays the result out in WAVES: wave N holds the
70
- # stories whose every dependency sits in a wave below it.
71
- #
72
- # The word is doing work. "Order" and "priority" both promise a queue — a first, a second, a
73
- # third, and a wrong answer whenever two things tie. What is true here is the opposite: TWO
74
- # STORIES IN THE SAME WAVE ARE UNORDERED WITH RESPECT TO EACH OTHER, which is exactly why
75
- # they can be picked up in parallel, and a scheme that cannot say "these two are equal"
76
- # would have to invent a ranking to hide that it cannot. A wave is an ELIGIBILITY LEVEL:
77
- # wave 1 means nothing has to land first, not "do this first". Nothing waits for a whole
78
- # wave to finish, and a story becomes startable the moment its own dependencies are done.
79
- # `wave` is also already the kit's word for this — /appweaver dependency-tree sorts
80
- # migration units into waves the same way — so the backlog and the migration tree read alike.
81
- #
82
- # The edges are a judgment and the levels are not, which is the same split as everywhere
83
- # else here: the model declares what stands behind what, and backlog-plan computes the
84
- # levels, breaks any cycle deterministically, and names an edge that resolves to nothing
85
- # rather than dropping it in silence. `follows:` is NOT an edge — a follow-on carries the
86
- # REMAINDER of a story in flight, and a remainder is often buildable alongside it.
87
- #
88
- # WHY THE ASK. A requirement whose story is already open is the one case with no right
89
- # default. Rewriting the story keeps one slice of work whole; leaving it alone keeps
90
- # somebody's context intact and puts the change in a new story beside it. Both are
91
- # defensible and the machine cannot weigh them, so the author picks — per story, not per
92
- # run. Declining does not abandon the group: the declined story's requirements withdraw
93
- # from it and whatever remains still becomes a story.
94
- #
95
- # AND WHY IN-FLIGHT IS DIFFERENT. Once `stories/<ID>/` exists somebody has run story-plan
96
- # and is designing or building against the requirements as they read then. That story is
97
- # never rewritten. Picking it mints a FOLLOW-ON carrying the change, linked by `follows:`;
98
- # the original stays open and closes having delivered what it was written to deliver
99
- # (§5.5). There is no `superseded` status, so completeness stays computable one way: a
100
- # requirement is delivered when it has NO OPEN STORIES.
101
- #
102
- # WHAT THIS DOES NOT WRITE. The Change History table, including its `Stories` cell. That
103
- # table is inside the region the ledger hashes, so writing it here would be an unledgered
104
- # edit of the contract by the workflow whose whole job is to react to those. The
105
- # spec->story relation is held ONCE, in each story's `exercises:`, and the Story Coverage
106
- # block — which IS fenced out of the hash — is that relation read the other way round.
107
- #
108
- # Launch: /appweaver sync-stories [single-run] [optional guidance on how to slice]
109
- # Prereq: /appweaver specify (the spec and its version row must exist)
110
- # Next: /appweaver story-plan <story-id> (the execution design for one slice)
111
- # =============================================================================
112
-
113
- name: aw-spec-sync-stories
114
- description: "Raise and refresh the story backlog of the ACTIVE feature from its spec. Reads the spec's Change History ledger to find the requirements that have moved since the backlog was last in step, groups them into execution slices, asks before disturbing a story someone already has open, and writes one story per slice — creating, updating in place, or minting a follow-on where a story plan already exists. Slicing is steered by an opening strategy word: `single-run` puts the whole work set into ONE story, splitting it along its dependencies only where that story could not be picked up as one piece, and reporting the split either way; omitted, the number of slices is decided from the requirements. The grouping declares which slices stand behind which, and the kit computes DELIVERY WAVES from those edges — wave N holds the stories whose every dependency sits below it, so stories sharing a wave have no ordering between them and may be picked up in parallel. Writes `depends_on:` and `wave:` into each story's frontmatter, breaks any declared cycle deterministically, and regenerates the spec's Story Coverage block. Writes no requirements — /appweaver specify and /appweaver sync-spec own the contract."
115
- version: 1.1.0
116
- agent_agnostic: true
117
- dialect: aw-spec.orchestrator/v1
118
-
119
- inputs:
120
- user_input:
121
- description: "Optional. An opening STRATEGY word, then optional guidance on how to slice. `single-run` (also `single-story`, `single`) puts the whole work set into ONE story — split along its dependencies only where that story would be too heavy to pick up, and the split is reported. Omit it, or say `auto`, and the number of slices is decided from the requirements alone. Guidance is free prose (\"keep the reporting requirements in one story\", \"split anything touching payments\"); only the FIRST word is read as a strategy, so guidance may use the word \"single\" without changing the mode."
122
- required: false
123
- bind: USER_INPUT
124
-
125
- scripts:
126
- actions:
127
- sh: .appweaver/scripts/bash/aw-spec-actions.sh
128
- ps1: .appweaver/scripts/powershell/aw-spec-actions.ps1
129
-
130
- # The universal operating rules only. A story carries the spec's constraints by id and
131
- # invents none of its own, so the domain and technical rule corpora have nothing to add
132
- # here — specify already lifted everything they impose into the document this reads.
133
- standards:
134
- - { path: .appweaver/ai-rules.md }
135
-
136
- vars:
137
- # A GATE THAT ONLY EXISTS TO STOP. The engine renders a JSON boolean two different ways:
138
- # `{{var}}` json-dumps it (`true`/`false`, which is what `condition:` compares against)
139
- # while verify_context's require_value stringifies it (`True`/`False`). So a boolean
140
- # coming out of a script cannot be gated with require_value/equals — the comparison is
141
- # against a spelling the interpolation never produces, and the gate passes whatever the
142
- # value is. The test therefore lives in `condition:`, and the step it guards fails
143
- # unconditionally: reaching it IS the failure. Used by bootstrap_gate and
144
- # ledger_empty_gate below.
145
- never: "false"
146
-
147
- # THE LAUNCH LINE, TAKEN APART. slice-mode reads the strategy word off the front of the
148
- # input and hands back what is left as guidance, so the prompt below never sees the word
149
- # it is being steered by — and guidance that happens to contain it is still guidance.
150
- strategy: "auto" # auto | single
151
- strategy_note: "" # that choice, in words, for the run report
152
- guidance: "" # the input with the strategy word taken off the front
153
- slice_directive: "" # the grouping instruction the strategy selects
154
- # A SIGNAL, NOT A LIMIT. Read to the model as the point at which a single story is worth
155
- # stopping over, and reported by backlog-plan against the groups that came back. Nothing
156
- # refuses a group for exceeding it: how big a slice can be is a fact about the work and
157
- # the team, and a hard cap here would split a coherent story to satisfy a number.
158
- single_ceiling: "8"
159
- split_note: "" # single-run was asked for — did the work set still split, and why
160
- split_reason: "" # the grouping's own sentence on what made one story undeliverable
161
- split_count: "0"
162
- oversized_csv: "" # groups holding more ids than the ceiling
163
-
164
- version: "" # the spec version the backlog is being brought up to
165
- ledger_ok: "true" # entry tripwire result
166
- ledger_state: "" # clean | unstamped | ledgered_edit | unledgered_edit | rollback | delta_open
167
- ledger_error: ""
168
- needs_bootstrap: "false" # an unfinished first draft is not a contract to slice
169
- ledger_empty: "true" # no version row at all -> warn and stop
170
- row_count: "0"
171
- work_csv: "" # the requirements this run has to account for
172
- work_count: "0"
173
- work_any: "false"
174
- work_source: "" # ledger (rows above the floor) | spec (the v1.0 case)
175
- work_note: "" # how the work set was arrived at, in words
176
- span_versions_csv: "" # the versions it spans
177
- gap: "false" # true = more than one version behind; a sync was skipped
178
- removed_csv: "" # ids a row named that the document no longer declares (E1)
179
- synced_floor: "" # the oldest version a story is still sitting at
180
- floor_source: "" # open | closed | none — which stories that floor was read from
181
- latest_version: ""
182
- backlog_digest: "" # one line per open story, for the grouping prompt
183
- story_count: "0"
184
- open_count: "0"
185
- groups: [] # the model's slices
186
- groups_digest: ""
187
- group_count: "0"
188
- unplaced_csv: "" # work-set ids the grouping left out
189
- repeated_csv: "" # ids it put in two groups
190
- any_candidates: "false" # does any group already have an open story in front of it
191
- candidate_ids: [] # the options of the ask
192
- candidate_csv: ""
193
- ask_digest: "" # those candidates, worded
194
- approved_stories: "" # the author's answer
195
- any_plan: "false"
196
- plan: [] # one entry per story file to write
197
- plan_count: "0"
198
- plan_digest: ""
199
- create_count: "0"
200
- update_count: "0"
201
- followon_count: "0"
202
- skipped_csv: "" # stories the author declined to disturb
203
- plan_notes: "" # what the arithmetic decided that the counts do not show
204
- overflow_csv: "" # stories the write loop's ceiling will not reach
205
- nothing_note: "" # why a run that wrote nothing wrote nothing — see plan_gate
206
- stories_csv: "" # every story this run wrote
207
- wave_digest: "" # the stories this run wrote, laid out by dependency level
208
- wave_count: "0"
209
- max_wave: "0"
210
- cycle_csv: "" # dependency edges dropped to break a declared cycle
211
- unresolved_deps_csv: "" # declared dependencies naming no slice and no open story
212
- uncovered_csv: "" # requirements still with no story at all
213
- delivered_csv: "" # requirements whose every story is closed
214
-
215
- workflow:
216
-
217
- # 0. THE LAUNCH LINE. Read before anything else because it steers the one judgment step,
218
- # and it depends on nothing — no feature, no spec, no backlog.
219
- #
220
- # NOT A `condition:`. The engine compares a whole rendered value, so
221
- # `{{USER_INPUT}} == single-run` matches only an input carrying the strategy and no
222
- # guidance at all — and `/appweaver sync-stories single-run keep the payment rules
223
- # together`, the form the launch line documents, would slice by default while looking
224
- # as though it had been told not to. Silent, and in the one place the author was
225
- # explicit.
226
- - id: slice_mode
227
- action: execute_script
228
- script: { ref: actions }
229
- subcommand: slice-mode
230
- args: ["--input", "{{USER_INPUT}}"]
231
- outputs:
232
- - { name: strategy, from: "$.strategy" }
233
- - { name: guidance, from: "$.guidance" }
234
- - { name: strategy_note, from: "$.note" }
235
-
236
- # 0a. THE GROUPING INSTRUCTION THE STRATEGY SELECTS. Two `set` steps rather than two
237
- # prompts: everything else the grouping is told is the same in both modes, and a
238
- # second copy of those rules is a second place for them to drift.
239
- - id: directive_auto
240
- condition: "{{strategy}} == auto"
241
- action: set
242
- vars:
243
- slice_directive: |
244
- SLICING. How many slices there are is yours to decide, from the work itself.
245
-
246
- - A group is what one person can pick up and deliver as a coherent piece of work.
247
- - SIZE IS A REASON TO SPLIT, NEVER A REASON TO MERGE. Where one requirement is
248
- large enough to be a story on its own, it is one — a group of one is the normal
249
- case, not a failure to group. Do not pad a group to make it look substantial.
250
-
251
- - id: directive_single
252
- condition: "{{strategy}} == single"
253
- action: set
254
- vars:
255
- slice_directive: |
256
- SLICING. ONE STORY was asked for. Put EVERY requirement listed above into a SINGLE
257
- group covering the whole work set, and title it for what the feature delivers as a
258
- whole rather than for any one part of it.
259
-
260
- - SPLIT ONLY IF THAT STORY COULD NOT BE PICKED UP. The test is whether one person
261
- could take it and deliver it as one coherent piece of work — not whether it is
262
- tidy, and not the id count: more than {{single_ceiling}} ids in one story is a
263
- signal worth stopping on, never a verdict on its own.
264
- - IF YOU DO SPLIT, SPLIT ALONG THE DEPENDENCIES. The first group is what everything
265
- else is built on and what needs nothing itself; each group after it holds what
266
- becomes possible once the ones before it exist. Do not split by theme, by section
267
- of the spec, or by id range — a split that does not follow the dependencies
268
- produces stories that cannot be started in any order at all, which is a worse
269
- answer than the single heavy story it was meant to fix.
270
- - SAY WHY, in `split_reason`: one sentence naming what made the single story
271
- undeliverable. Somebody asked for one story and is getting several, and they are
272
- owed the reason rather than left to infer it from the count.
273
-
274
- # 1. CONSUME the active feature (set by select-feature). No derive-names.
275
- - id: feature_paths
276
- action: execute_script
277
- on_error: continue
278
- script: { ref: actions }
279
- subcommand: feature-paths
280
- outputs:
281
- - { name: feature_dir, from: "$.feature_dir" }
282
- - { name: spec_file, from: "$.spec_file" }
283
- - { name: spec_exists, from: "$.spec_exists" }
284
- - { name: needs_bootstrap, from: "$.needs_bootstrap" }
285
-
286
- - id: require_feature
287
- action: verify_context
288
- require_non_empty: feature_dir
289
- on_empty: stop
290
- on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the aggregate, e.g. Order), then /appweaver specify, then re-run sync-stories."
291
-
292
- # 2. AN UNFINISHED FIRST DRAFT IS NOT A CONTRACT. needs_bootstrap covers both "no spec.md"
293
- # and "a specify run that died before writing its v1.0 row" — and the second is the one
294
- # that matters here, because that document IS on disk and does carry requirements. Its
295
- # ledger row is still the template's placeholder, whose "Requirements changed" cell
296
- # literally reads FR-001…FR-00n; syncing off it would raise a backlog against ids
297
- # nobody chose, at a version nobody committed to.
298
- - id: bootstrap_gate
299
- condition: "{{needs_bootstrap}} == true"
300
- action: verify_context
301
- require_true: never
302
- on_false: stop
303
- on_false_message: >
304
- {{spec_file}} is an unfinished first draft — specify has not written its v1.0 Change
305
- History row, so the document is not yet a contract anything can be sliced against.
306
- Re-run /appweaver specify with the same input (it re-authors the draft rather than
307
- extending it), then re-run sync-stories.
308
-
309
- # 3. THE ENTRY TRIPWIRE. The ledger is hand-writable, which is the point — a spec can be
310
- # edited by hand — and also its one silent failure: a body edited with no version row
311
- # leaves every story citing an edited requirement stale with nothing to detect it.
312
- # That is precisely the state this workflow exists to resolve, and precisely the one it
313
- # cannot resolve, because the row is what says WHICH requirements moved. Refusing here
314
- # turns the silent case into a message naming the fix (E13).
315
- #
316
- # `unstamped` and `ledgered_edit` both pass: the first is a spec that predates the
317
- # ledger, the second is a hand edit that did write its row — exactly the Direction A
318
- # case this workflow is for.
319
- #
320
- # No `on_error: continue`: spec-check reports every ledger STATE through its JSON with
321
- # a zero exit, so a non-zero exit is the script itself failing, and continuing past
322
- # that would bind the gate's vars to nothing and produce the refusal with its reason
323
- # blanked out.
324
- - id: ledger_check
325
- action: execute_script
326
- script: { ref: actions }
327
- subcommand: spec-check
328
- args: ["--feature-dir", "{{feature_dir}}"]
329
- outputs:
330
- - { name: ledger_ok, from: "$.ok" }
331
- - { name: ledger_state, from: "$.state" }
332
- - { name: ledger_error, from: "$.error" }
333
- - { name: version, from: "$.version" }
334
-
335
- - id: ledger_gate
336
- action: verify_context
337
- require_true: ledger_ok
338
- on_false: stop
339
- on_false_message: >
340
- /appweaver sync-stories cannot read the backlog's anchor: {{ledger_error}}
341
- A version row is what tells a story that a requirement it cites has moved, so a spec
342
- whose body has changed without one cannot be synced — this run would raise stories
343
- for what the LAST row named and leave the hand edit unnamed and unreconciled.
344
- Resolve the state above, then re-run. (State: {{ledger_state}}.)
345
-
346
- # 3a. THE TWO STATES THAT PASS `ok` AND SHOULD NOT PASS HERE. spec-check's decisive branch
347
- # asks only whether a version ROW sits above the version the kit last recorded, and
348
- # never opens the body — so `ledgered_edit` and `header_behind` are ok:true with a
349
- # document nobody has re-baselined, and every LATER edit lands inside that same window
350
- # and passes the same test. Syncing from there raises a backlog from rows that are
351
- # recorded and a body that is not. The module is shared with plan, plan-ui,
352
- # analyze-design, story-plan, analyze-slice and both implements — the fence is the
353
- # same wherever spec.md is read and not stamped.
354
- - id: spec_state_guard
355
- action: include
356
- module: .appweaver/orchestrator/modules/spec-state-gate.orch.yaml
357
- with: { command: "/appweaver sync-stories" }
358
-
359
- # 4. THE WORK SET. See the header for why it spans versions rather than reading the
360
- # newest row alone, and for the v1.0 case.
361
- - id: ledger_read
362
- action: execute_script
363
- script: { ref: actions }
364
- subcommand: ledger-read
365
- args: ["--feature-dir", "{{feature_dir}}"]
366
- outputs:
367
- - { name: row_count, from: "$.row_count" }
368
- - { name: ledger_empty, from: "$.ledger_empty" }
369
- - { name: work_csv, from: "$.work_csv" }
370
- - { name: work_count, from: "$.work_count" }
371
- - { name: work_any, from: "$.work_any" }
372
- - { name: work_source, from: "$.work_source" }
373
- - { name: work_note, from: "$.work_note" }
374
- - { name: span_versions_csv, from: "$.span_versions_csv" }
375
- - { name: gap, from: "$.gap" }
376
- - { name: removed_csv, from: "$.removed_csv" }
377
- - { name: synced_floor, from: "$.synced_floor" }
378
- - { name: floor_source, from: "$.floor_source" }
379
- - { name: latest_version, from: "$.latest_version" }
380
- - { name: non_story_csv, from: "$.non_story_csv" }
381
- - { name: non_story_any, from: "$.non_story_any" }
382
- - { name: work_kind, from: "$.work_kind" }
383
-
384
- # 4a. AN EMPTY LEDGER STOPS THE RUN. The table is there and carries no version row, which
385
- # means the spec has never been versioned. There is no "since" to sync from, and
386
- # choosing one — the whole document, say — would raise a backlog against requirements
387
- # nobody has committed to a version of. A warning, not a failure: nothing is wrong
388
- # with the document, it simply is not ready to be sliced.
389
- - id: ledger_empty_gate
390
- condition: "{{ledger_empty}} == true"
391
- action: verify_context
392
- require_true: never
393
- on_false: stop
394
- on_false_message: >
395
- The Change History table in {{spec_file}} carries no version row, so there is no
396
- version for a story to sync at and nothing recording what this spec has changed.
397
- Nothing was written. Run /appweaver specify to author or extend the spec — it computes
398
- the row from what it actually changed and stamps the document — then re-run
399
- /appweaver sync-stories. (If the spec was written by hand, add a row naming the
400
- requirement ids it introduced and re-run spec-stamp.)
401
-
402
- # 4a-bis. WHAT MOVED THAT NO STORY CAN BE RAISED FOR. Constraints and success criteria
403
- # travel in the same version rows as requirements, and this workflow can act on
404
- # neither: a story's `exercises:` is FR-/BR- only, because those are DELIVERED, while a
405
- # CN-/DC-/IC-/UX- is MET and an SC- is SCORED. Until now that meant a row naming only
406
- # those produced an empty work set and a run that stopped with "the backlog is already
407
- # in step" — about the exact ids story-plan-check had just reported as stale.
408
- #
409
- # BEFORE the work gate and INDEPENDENT of it, on purpose. A row naming FR-001 AND
410
- # CN-001 has real story work, so the gate below passes and the run continues — and
411
- # branching this notice on "no work" would then have said nothing at all about the
412
- # constraint, which is the same silence one case over. It fires on the ids, never on
413
- # the shape of the run.
414
- - id: non_story_notice
415
- condition: "{{non_story_csv}} != ''"
416
- action: verify_context
417
- require_value: non_story_csv
418
- equals: ""
419
- on_mismatch: continue
420
- on_mismatch_message: >
421
- ALSO MOVED, AND NOT SLICEABLE: {{non_story_csv}}. These are carried by stories, not
422
- delivered by them, so this run raises nothing for them and none of the writes below
423
- touches them.
424
- • CN-/DC-/IC-/UX- are BOUNDS. Every open story is built to satisfy the feature's whole
425
- constraint set, so a moved bound touches all of them: re-run /appweaver story-plan on
426
- each open story to design against the new wording, and see .appweaver/SPEC-EDIT-PROTOCOL.md
427
- §4.2 for the by-hand reconciliation until /appweaver sync-spec ships.
428
- • SC- are feature-level OUTCOMES, scored at /appweaver analyze-slice against the whole
429
- feature — no story claims one, and none should.
430
-
431
- # 4b. NOTHING TO DO IS NOT AN ERROR. Every version row is at or below the oldest open
432
- # story's stamp: the backlog is already in step. Stopping here rather than running an
433
- # empty grouping keeps the no-op cheap and the message honest. Left as `work_any`
434
- # (FR-/BR- only) deliberately — the non-story notice above has already fired by the
435
- # time this is reached, so a constraint-only row now stops with its reason SAID.
436
- - id: work_gate
437
- action: verify_context
438
- require_true: work_any
439
- on_false: stop
440
- on_false_message: >
441
- No story work: every requirement a version row names has either been delivered by a
442
- closed story or is already carried by an open one synced past it, so the backlog is in
443
- step with {{spec_file}} at {{latest_version}}. Nothing was written. ({{work_note}}.)
444
-
445
- # 4c. A REMOVED REQUIREMENT IS A DECISION, NOT A DEFAULT (E1). A row names what a version
446
- # TOUCHED, and that includes ids it deleted. Those leave the work set — there is no
447
- # wording left to write a story from — but every open story citing one is now
448
- # orphaned, delivering a requirement the contract no longer makes. Reported and
449
- # continued: this run can still raise the stories it came to raise, and the orphan
450
- # needs a person, not a rule.
451
- - id: removed_notice
452
- condition: "{{removed_csv}} != ''"
453
- action: verify_context
454
- require_value: removed_csv
455
- equals: ""
456
- on_mismatch: continue
457
- on_mismatch_message: >
458
- A version row names requirements the spec no longer declares: {{removed_csv}}.
459
- They are excluded from this run — a removed requirement has no wording to write a
460
- story from — but any OPEN story citing one is now orphaned, and closing it would
461
- report a requirement delivered that the contract no longer makes. Decide those by
462
- hand: cancel the story, or re-state the requirement via /appweaver specify.
463
-
464
- # 4d. MORE THAN ONE VERSION BEHIND. The span exists precisely so a skipped sync does not
465
- # bury a version, and it does that silently — the middle version's requirements are
466
- # picked up with nothing saying they nearly were not. This says so, because a gap is
467
- # also a fact about how the two documents are being worked: a spec that moved twice
468
- # with no sync between is one whose backlog is not being kept.
469
- - id: gap_notice
470
- condition: "{{gap}} == true"
471
- action: verify_context
472
- require_value: span_versions_csv
473
- equals: ""
474
- on_mismatch: continue
475
- on_mismatch_message: >
476
- The backlog is more than one version behind: this run spans {{span_versions_csv}},
477
- because the oldest {{floor_source}} story is still at {{synced_floor}}. Every requirement those
478
- rows name is in the work set, so nothing is lost — but a version in the middle
479
- reached no story until now.
480
-
481
- # 5. THE BACKLOG AS IT STANDS. Read once, whole, and only from frontmatter — which is why
482
- # §6.1 puts everything the gates need there. Fed to the grouping step so slices are
483
- # proposed against the work that exists rather than into a vacuum.
484
- # No `on_error: continue`. story-refs exits zero on an empty backlog and on a stories
485
- # directory that does not exist yet, which are the ordinary cases — so a non-zero exit
486
- # is the op itself failing to read the backlog, and grouping against a digest that says
487
- # "no open stories" for that reason proposes slices over work that already exists.
488
- - id: backlog_scan
489
- action: execute_script
490
- script: { ref: actions }
491
- subcommand: story-refs
492
- args: ["--feature-dir", "{{feature_dir}}"]
493
- outputs:
494
- - { name: backlog_digest, from: "$.digest" }
495
- - { name: story_count, from: "$.story_count" }
496
- - { name: open_count, from: "$.open_count" }
497
-
498
- # 6. THE ONE JUDGMENT IN THE WORKFLOW. Everything before this is a read and everything
499
- # after it is arithmetic; how coarsely a spec slices into stories is decided here,
500
- # once, in one place (§5.15) — which is why specify writes no stories at all.
501
- #
502
- # The spec is in context because grouping is a judgment about the REQUIREMENTS, not
503
- # about their ids: two that share an aggregate invariant belong together and two that
504
- # merely sort next to each other do not.
505
- # No build_context envelope: a sticky context is carried in front of EVERY later model
506
- # step, and every step below already declares the spec itself — so the envelope would
507
- # send it twice per call and once again for each story written. Each step names what it
508
- # reads, which is also the only place a reader has to look.
509
- - id: group_query
510
- action: agent_query
511
- context:
512
- - "{{spec_file}}"
513
- include_standards: false
514
- task: |
515
- The specification in context is the contract for this aggregate. These requirements
516
- have moved since its story backlog was last in step, and each one needs to reach a
517
- story:
518
-
519
- {{work_csv}}
520
-
521
- ({{work_note}}.)
522
-
523
- The backlog as it stands today:
524
-
525
- {{backlog_digest}}
526
-
527
- {{guidance}}
528
-
529
- Group those requirements into EXECUTION SLICES. One group becomes one story.
530
-
531
- {{slice_directive}}
532
-
533
- WHAT BELONGS IN ONE SLICE.
534
-
535
- - A group is what one person can pick up and deliver as a coherent piece of work.
536
- Judge that on the requirements themselves, as the spec words them — not on their
537
- id numbers, which say nothing about what belongs together.
538
- - PUT TOGETHER what cannot honestly be delivered apart: a behaviour and the business
539
- rule that constrains it; two requirements that share an aggregate invariant, an
540
- entity's lifecycle, or one user-visible outcome. A requirement whose acceptance
541
- cannot be demonstrated without another one is in that other one's group.
542
- - THAT TEST IGNORES WHERE THE SPEC PUTS THEM. Two requirements that read as one piece
543
- of work belong in one group however far apart they are stated, and one that reads
544
- as a separate concern stays separate however adjacent it is. The document's section
545
- order is how it was written down, not how it gets built.
546
- - KEEP APART what merely resembles: two requirements over the same entity that a
547
- reviewer could accept independently are two stories. Coupling them buys nothing and
548
- makes both wait for the slower half.
549
-
550
- WHAT STANDS BEHIND WHAT.
551
-
552
- - DECLARE THE ORDER. In each group's `depends_on`, name the groups in THIS list that
553
- have to land before it — where this slice would be built against something another
554
- slice has not delivered yet. An OPEN STORY from the backlog above may be named the
555
- same way, by its story id. Empty where nothing has to come first.
556
- - THIS IS ABOUT CODE, NOT IMPORTANCE. A group everything else calls into comes first
557
- because of what it builds, not because it matters more. Do not name a group merely
558
- because it is related, or thematically prior, or bigger: an edge that is not real
559
- makes work wait for nothing, and it will not be questioned by anything downstream.
560
- - GROUPS WITH NO EDGE BETWEEN THEM END UP AT THE SAME LEVEL and are picked up in
561
- PARALLEL. That is the outcome to aim for — order only what genuinely has to be
562
- ordered, and leave the rest free to run alongside each other.
563
- - NO CYCLES. If two groups each need the other before they can be built, they are not
564
- two slices — they are one, and should be one group.
565
-
566
- THE RULES THAT DO NOT BEND.
567
-
568
- - Every id in the list above must appear in EXACTLY ONE group. Not zero: an id left
569
- out gets no story and nothing downstream will notice. Not two: two stories both
570
- claiming one requirement makes "is this requirement delivered" unanswerable.
571
- - Use ONLY the ids listed above. Do not pull in a requirement that has not moved, and
572
- do not invent one.
573
- - Give each group a title that names the OUTCOME it delivers, in the spec's own
574
- vocabulary — what a reviewer would see working, not the ids it covers.
575
- - `complexity` is your read of the slice's size: low | medium | high.
576
- - `rationale` is one sentence on why these belong together, or why this one stands
577
- alone. It is read by a person deciding whether to accept the slicing.
578
- schema:
579
- split_reason: "<empty unless ONE story was asked for and you returned more than one; then one sentence on what made the single story undeliverable>"
580
- groups:
581
- - id: g1
582
- title: "<the outcome this slice delivers>"
583
- requirements: ["FR-001", "BR-002"]
584
- depends_on: ["<a group id from this list, or an open story id, that must land first — empty if nothing must>"]
585
- complexity: "low|medium|high"
586
- rationale: "<why these belong in one story>"
587
- # No slicing without a model. Any fixed grouping here would name requirement ids that
588
- # exist in one repo and nowhere else, and backlog-plan drops ids outside the work set — so
589
- # a "realistic" mock degenerates to this one anyway, having first suggested otherwise. A
590
- # mock run therefore exercises the ledger read, the work set and every gate, then stops
591
- # at plan_gate saying the grouping produced nothing. That is the honest outcome.
592
- mock:
593
- groups: []
594
- split_reason: ""
595
- outputs:
596
- - { name: groups, from: "$.groups" }
597
- - { name: split_reason, from: "$.split_reason" }
598
-
599
- # 7. WHICH GROUPS ALREADY HAVE STORIES IN FRONT OF THEM. Deterministic: the model
600
- # proposed the slices, and the arithmetic over them — what each group covers, which
601
- # open stories intersect it, and what the question about each one should say — is
602
- # computed, so the same grouping always produces the same question.
603
- - id: plan_scan
604
- action: execute_script
605
- script: { ref: actions }
606
- subcommand: backlog-plan
607
- args: ["--feature-dir", "{{feature_dir}}", "--groups", "{{groups}}", "--work", "{{work_csv}}", "--strategy", "{{strategy}}", "--ceiling", "{{single_ceiling}}"]
608
- outputs:
609
- - { name: group_count, from: "$.group_count" }
610
- - { name: groups_digest, from: "$.groups_digest" }
611
- - { name: unplaced_csv, from: "$.unplaced_csv" }
612
- - { name: repeated_csv, from: "$.repeated_csv" }
613
- - { name: any_candidates, from: "$.any_candidates" }
614
- - { name: candidate_ids, from: "$.candidate_ids" }
615
- - { name: candidate_csv, from: "$.candidate_csv" }
616
- - { name: ask_digest, from: "$.ask_digest" }
617
- - { name: split_note, from: "$.split_note" }
618
- - { name: split_count, from: "$.split_count" }
619
- - { name: oversized_csv, from: "$.oversized_csv" }
620
-
621
- # 7aa. ONE STORY WAS ASKED FOR AND MORE THAN ONE CAME BACK. A legitimate answer — a slice
622
- # nobody can pick up is not a story, and the split is what the option promises where
623
- # the work set is too heavy. Reported BEFORE the ask, because it changes what the
624
- # author is agreeing to: they asked for one story and are about to approve several.
625
- # Continuing rather than stopping is the point; refusing would leave the only route
626
- # to a large single-story feature being not to use the option at all.
627
- - id: split_notice
628
- condition: "{{split_count}} != 0"
629
- action: verify_context
630
- require_value: split_count
631
- equals: "0"
632
- on_mismatch: continue
633
- on_mismatch_message: >
634
- {{split_note}}. The stories are written in dependency order and each one names what
635
- has to land before it, so the work is still one sequence — it is spread over more
636
- stories than the launch line asked for. Reason given: {{split_reason}}
637
-
638
- # 7ab. A SLICE NOBODY CAN PICK UP. The ceiling is a signal and not a limit, so this
639
- # neither refuses nor rewrites: how big a story can be is a fact about the work and
640
- # the team, and a hard cap here would split a coherent story to satisfy a number.
641
- # Said out loud because an oversized story is the one that stays open — it is easier
642
- # to re-slice now than to discover it three weeks in.
643
- - id: oversized_notice
644
- condition: "{{oversized_csv}} != ''"
645
- action: verify_context
646
- require_value: oversized_csv
647
- equals: ""
648
- on_mismatch: continue
649
- on_mismatch_message: >
650
- These slices carry more than {{single_ceiling}} requirements each: {{oversized_csv}}.
651
- Nothing is refused — the count is a signal, not a limit — but a story that large is
652
- the one that never gets picked up. Re-run /appweaver sync-stories with guidance on
653
- where to divide it if it is genuinely more than one piece of work.
654
-
655
- # 7a. A REQUIREMENT THE GROUPING DROPPED GETS NO STORY, AND THAT IS THE SILENT ONE. The
656
- # prompt above forbids it; this is the check that the model obeyed. Reported rather
657
- # than stopping, because the groups that ARE well formed are still worth writing —
658
- # but the run says which ids it is about to leave uncovered, and spec-coverage at the
659
- # end says so again from the document's own point of view.
660
- - id: unplaced_notice
661
- condition: "{{unplaced_csv}} != ''"
662
- action: verify_context
663
- require_value: unplaced_csv
664
- equals: ""
665
- on_mismatch: continue
666
- on_mismatch_message: >
667
- The grouping left these requirements out of every slice: {{unplaced_csv}}. No story
668
- will be written for them by this run. Re-run /appweaver sync-stories to place them,
669
- or raise them by hand from .appweaver/templates/story-template.md (/appweaver add-story,
670
- which mints the id and stamps the frontmatter, is ⌛ not yet built).
671
-
672
- - id: repeated_notice
673
- condition: "{{repeated_csv}} != ''"
674
- action: verify_context
675
- require_value: repeated_csv
676
- equals: ""
677
- on_mismatch: continue
678
- on_mismatch_message: >
679
- The grouping put these requirements in more than one slice: {{repeated_csv}}. Each
680
- has been kept in the FIRST group that claimed it and removed from the others, so no
681
- requirement gets two stories both claiming it — which would make "does this
682
- requirement still have open stories", the completeness rule, answer for work that was
683
- never really duplicated. No coverage is lost; the slicing may not be the one the
684
- grouping intended, so it is worth a look at the stories this run wrote.
685
-
686
- # 8. THE ASK. See the header for why this is asked rather than decided. Multi-select,
687
- # per story, because the decision is per story: a group can hold one story the author
688
- # is happy to move and another they will not touch, and a single yes/no would force
689
- # the same answer onto both.
690
- #
691
- # A non-interactive run answers with ALL options, which is the settled default in both
692
- # directions and not a shortcut: a not-started story updated in place is §5.4's case 2,
693
- # and an in-flight story picked here mints a follow-on and is never rewritten (§5.5).
694
- # Leaving one out is the choice that needs a person.
695
- - id: ask_update
696
- condition: "{{any_candidates}} == true"
697
- action: ask
698
- multi: true
699
- prompt: |
700
- Some of these requirements already have OPEN stories in front of them.
701
-
702
- {{ask_digest}}
703
-
704
- Pick the stories this sync MAY change.
705
-
706
- PICKED, not started the story is rewritten to cover its whole group and
707
- restamped at {{latest_version}}.
708
- PICKED, in flight the story is left exactly as it is and a FOLLOW-ON is minted
709
- to carry the change, linked back to it.
710
- LEFT OUT the story is not touched at all, AND the requirements it
711
- already covers withdraw from their group. Whatever is left
712
- of that group still becomes a new story, so a group where
713
- one id has a story you will not move still delivers the rest.
714
-
715
- Which may be changed?
716
- options_from: "{{candidate_ids}}"
717
- answer_key: approved_stories
718
- answer_var: approved_stories
719
-
720
- # 9. THE PLAN: one entry per story FILE, each already carrying its id, its path, and the
721
- # ids it must end up citing. Same op, same groups, same backlog as the scan above, so
722
- # the question the author answered and the work that follows from it cannot disagree
723
- # about what a group contains.
724
- - id: plan_build
725
- action: execute_script
726
- script: { ref: actions }
727
- subcommand: backlog-plan
728
- args: ["--plan", "--feature-dir", "{{feature_dir}}", "--groups", "{{groups}}", "--work", "{{work_csv}}", "--approved", "{{approved_stories}}", "--version", "{{latest_version}}", "--cap", "200", "--strategy", "{{strategy}}", "--ceiling", "{{single_ceiling}}"]
729
- outputs:
730
- - { name: plan, from: "$.plan" }
731
- - { name: plan_count, from: "$.plan_count" }
732
- - { name: any_plan, from: "$.any_plan" }
733
- - { name: plan_digest, from: "$.plan_digest" }
734
- - { name: create_count, from: "$.create_count" }
735
- - { name: update_count, from: "$.update_count" }
736
- - { name: followon_count, from: "$.followon_count" }
737
- - { name: skipped_csv, from: "$.skipped_csv" }
738
- - { name: stories_csv, from: "$.stories_csv" }
739
- - { name: plan_notes, from: "$.notes_text" }
740
- - { name: overflow_csv, from: "$.overflow_csv" }
741
- - { name: wave_digest, from: "$.wave_digest" }
742
- - { name: wave_count, from: "$.wave_count" }
743
- - { name: max_wave, from: "$.max_wave" }
744
- - { name: cycle_csv, from: "$.cycle_csv" }
745
- - { name: unresolved_deps_csv, from: "$.unresolved_deps_csv" }
746
-
747
- # 9a. NOTHING TO WRITE HAS TWO CAUSES, AND THEY NEED DIFFERENT ANSWERS. Either the author
748
- # declined every story that stood in the way — a decision, not a failure — or the
749
- # grouping produced no slice at all, which means the one judgment step returned
750
- # nothing usable and the run should be repeated. A single message asserting the first
751
- # cause would send someone looking for a decision they never made. Two ordered sets
752
- # rather than a compound condition, the same idiom specify uses for covered_note.
753
- - id: nothing_grouping
754
- action: set
755
- vars:
756
- nothing_note: >-
757
- the grouping produced no slice for any of them ({{work_count}} requirement(s) went
758
- in, {{group_count}} group(s) came out). Nothing was declined and nothing was
759
- written — re-run /appweaver sync-stories, and if it comes back empty again, raise
760
- the stories by hand from .appweaver/templates/story-template.md (/appweaver add-story
761
- is ⌛ not yet built)
762
-
763
- - id: nothing_declined
764
- condition: "{{skipped_csv}} != ''"
765
- action: set
766
- vars:
767
- nothing_note: >-
768
- every one of them is already covered by a story you chose not to disturb
769
- ({{skipped_csv}}). Those stories keep their existing spec versions, so this sync
770
- will be offered again on the next run
771
-
772
- - id: plan_gate
773
- action: verify_context
774
- require_true: any_plan
775
- on_false: stop
776
- on_false_message: >
777
- Nothing to write for {{work_csv}}: {{nothing_note}}. The backlog is unchanged.
778
-
779
- # 9b. MORE STORIES THAN THE WRITE LOOP WILL WRITE. `over:` stops at max_iterations and
780
- # says nothing — the entries past it are never written and the run still reports
781
- # success, which is the silent failure this whole workflow exists to remove. The cap
782
- # passed to backlog-plan above MUST match write_loop's max_iterations; this is what
783
- # names the stories that fall past it.
784
- - id: overflow_notice
785
- condition: "{{overflow_csv}} != ''"
786
- action: verify_context
787
- require_value: overflow_csv
788
- equals: ""
789
- on_mismatch: continue
790
- on_mismatch_message: >
791
- The plan holds {{plan_count}} stories and this run writes at most 200 of them. These
792
- are not written: {{overflow_csv}}. Their requirements stay in the work set, so a
793
- second /appweaver sync-stories run picks them up — or raise write_loop's
794
- max_iterations (and the --cap it is passed) if a spec of this size is normal here.
795
-
796
- # 9c. A CYCLE WAS DECLARED AND HAD TO BE BROKEN. Two slices each needing the other before
797
- # it can be built is a statement that they are one slice, and the arithmetic cannot
798
- # merge them — the grouping is the judgment. So the edge closing each cycle is
799
- # dropped, deterministically, and named: a backlog with no order at all is worse than
800
- # one missing an edge somebody can re-add, and an edge dropped in silence is worse
801
- # than both.
802
- - id: cycle_notice
803
- condition: "{{cycle_csv}} != ''"
804
- action: verify_context
805
- require_value: cycle_csv
806
- equals: ""
807
- on_mismatch: continue
808
- on_mismatch_message: >
809
- The slicing declared stories that depend on each other in a cycle. These edges were
810
- dropped so the backlog has an order at all: {{cycle_csv}}. Two slices that each need
811
- the other are one slice — check whether those stories should have been written as
812
- one, and add the ordering back by hand in `depends_on:` if the cycle was a mistake in
813
- the naming rather than in the slicing.
814
-
815
- # 9d. AN EDGE POINTING AT NOTHING. Named rather than dropped in silence, because a
816
- # dependency that resolves to no slice and no open story is usually a real ordering
817
- # claim aimed at the wrong name — and the story it should have pointed at now looks
818
- # free to start.
819
- - id: unresolved_deps_notice
820
- condition: "{{unresolved_deps_csv}} != ''"
821
- action: verify_context
822
- require_value: unresolved_deps_csv
823
- equals: ""
824
- on_mismatch: continue
825
- on_mismatch_message: >
826
- The slicing named dependencies that match no slice in this run and no open story:
827
- {{unresolved_deps_csv}}. They set no order, so any story that was meant to wait for
828
- them reads as free to start. Check `depends_on:` on the stories this run wrote.
829
-
830
- # 10. THE WRITES. One pass per plan entry, and each pass is an author step followed by a
831
- # stamp. The three author steps are mutually exclusive on `action`; whichever runs
832
- # writes the whole file.
833
- #
834
- # ENTRIES ARRIVE IN WAVE ORDER, so the stories are written in the order they can be
835
- # picked up in — and a run that meets the ceiling below loses the LAST wave rather
836
- # than an arbitrary slice of the backlog.
837
- #
838
- # THE STAMP IS NOT A FORMALITY. The frontmatter is the gate surface — spec-coverage,
839
- # check-stories, analyze-slice and close-story all read `exercises:`, `spec_version:`
840
- # and `status:` and none of them opens the body, and `depends_on:` / `wave:` are what
841
- # say which stories can start now. So those fields are written by the
842
- # kit from the plan, not taken on trust from the model that wrote the prose, for the
843
- # same reason the Change History row is computed rather than declared: a model that
844
- # drops one id from `exercises:` produces a story that reads perfectly and is
845
- # invisible to every gate built to protect it.
846
- - id: write_loop
847
- action: loop
848
- over: "{{plan}}"
849
- as: p
850
- # MUST MATCH the --cap passed to plan_build above, which is what reports the entries
851
- # that fall past it. The engine's foreach stops here in silence.
852
- max_iterations: 200
853
- steps:
854
-
855
- # 10a. CREATE — a group nothing is building yet.
856
- - id: create_story
857
- condition: "{{p.action}} == create"
858
- action: agent_author
859
- context:
860
- - .appweaver/templates/story-template.md
861
- - "{{spec_file}}"
862
- - { path: .appweaver/memory/constitution.ui.md, optional: true }
863
- output_target: { type: file, path: "{{p.path}}" }
864
- task: |
865
- Write ONE user story into the template in context, for this slice of the
866
- specification:
867
-
868
- story id: {{p.story}}
869
- slice: {{p.group_title}}
870
- requirements: {{p.requirements_csv}}
871
- why one slice: {{p.rationale}}
872
- size: {{p.complexity}}
873
- lands after: {{p.depends_on_csv}}
874
- wave: {{p.wave}}
875
-
876
- Return the COMPLETE story document. Rules:
877
-
878
- - THE SPEC IS THE CONTRACT AND THIS IS A SLICE OF WORK OVER IT. Nothing here
879
- changes it. If writing this story exposes something the spec gets wrong or
880
- leaves out, say so in Notes and leave the requirement alone — the contract
881
- moves through /appweaver sync-spec (⌛ not yet built — until it is, the hand
882
- route is .appweaver/SPEC-EDIT-PROTOCOL.md), never through a story.
883
- - Frontmatter: set `id:` to {{p.story}}, `exercises:` to exactly
884
- [{{p.requirements_csv}}], `status: open`, and `spec_version:` to
885
- {{latest_version}}. Give it a `title:` that names the OUTCOME a reviewer would
886
- see working, in the spec's vocabulary — not the ids it covers.
887
- - `depends_on:` AND `wave:` ARE THE KIT'S, written from the slicing above. Do not
888
- set them and do not write anything that contradicts them. What they cannot say
889
- belongs in "## Notes": WHY the stories named above have to land first, and what
890
- of this slice can be started before they do. A wave is an eligibility level and
891
- not a priority — stories sharing one have no ordering between them and are
892
- picked up in parallel.
893
- - "## Story" is one "As a … I want … so that …", written from the actor the spec
894
- names. If the requirements serve different actors, that is a signal the slice
895
- is wrong — say so in Notes rather than inventing a role that covers both.
896
- - "## Requirements delivered": one line per id, saying what THIS story delivers
897
- OF that requirement. A story rarely delivers a whole requirement; name the
898
- part. Quote no requirement in full — the spec is the wording.
899
- - "## Constraints" — THE IDS ARE STAMPED FOR YOU; WRITE THE LINE EACH. The kit
900
- lifts every CN-, DC-, IC- and UX- the spec declares into this story's
901
- `constraints:` frontmatter, because they are declared aggregate-wide and so the
902
- feature's whole set binds every slice of it. That field is the gate surface;
903
- this section is what makes it READABLE. Write one line per id saying what THIS
904
- slice has to do about it, and where one genuinely does not apply here, say so
905
- plainly in its line — do not omit it. An omission used to be indistinguishable
906
- from a bound nobody noticed, which is exactly how a slice gets built without one.
907
- Carry the Experience Scope floors (device / browser / assistive technology) in
908
- the same way wherever this slice has a UI surface: implement-ui authors from the
909
- spec and the story, so a floor not stated here is one it will be built without.
910
- Do NOT add these ids to `exercises:` — that field is FR-/BR- only, because it is
911
- the delivery relation, and a constraint is not delivered, it is met.
912
- - "## Acceptance": checkable statements in the spec's vocabulary, covering every
913
- requirement in `exercises:` and every constraint listed above that this slice
914
- can be observed to meet. These are what analyze-slice pairs tests against, so
915
- each one must be decidable by looking at the running system — never "works
916
- correctly", never a restatement of the requirement.
917
- - THIS STORY IS THE INPUT TO story-plan, which designs its execution against
918
- the current spec and code. Write it so that design pass needs nothing this
919
- document does not carry:
920
- the actor, the requirement ids, the constraints by id, and acceptance that
921
- settles what "done" means. Detail that pins the outcome down, not detail for
922
- its own sake.
923
- - NO IMPLEMENTATION. No types, endpoints, schemas, frameworks, file layouts or
924
- library choices — those are the design phase's, and a story that names them
925
- has decided something it has no standing to decide.
926
- - Use "## Notes" for sequencing, risks, and what is deliberately OUT of this
927
- slice. Leave the template's guidance comments in place.
928
-
929
- # 10b. FOLLOW-ON — the group's story is already being implemented (§5.5). The original
930
- # is in context to be read, never to be rewritten: this file is a NEW story, and
931
- # what makes the pair coherent is that it carries only the remainder.
932
- - id: followon_story
933
- condition: "{{p.action}} == followon"
934
- action: agent_author
935
- context:
936
- - .appweaver/templates/story-template.md
937
- - "{{spec_file}}"
938
- - "{{p.prior_path}}"
939
- - { path: .appweaver/memory/constitution.ui.md, optional: true }
940
- output_target: { type: file, path: "{{p.path}}" }
941
- task: |
942
- Write a FOLLOW-ON story. {{p.follows}} is open and already being implemented — a
943
- story plan exists and someone is building against the requirements as they read
944
- when it was written. That story is NOT being changed. This one carries what the
945
- spec has moved since.
946
-
947
- new story id: {{p.story}}
948
- follows: {{p.follows}} ("{{p.prior_title}}", which cites {{p.prior_csv}})
949
- slice: {{p.group_title}}
950
- requirements: {{p.requirements_csv}}
951
- why one slice: {{p.rationale}}
952
-
953
- {{p.follows}} is in context. Read it to see what is ALREADY being delivered, and
954
- write only the remainder.
955
-
956
- Return the COMPLETE story document. Rules:
957
-
958
- - Frontmatter: `id: {{p.story}}`, `follows: {{p.follows}}`, `exercises:` exactly
959
- [{{p.requirements_csv}}], `status: open`, `spec_version: {{latest_version}}`.
960
- - THE REMAINDER, NOT THE WHOLE. {{p.follows}} closes having delivered what it was
961
- written to deliver, and this story delivers what the spec now asks for beyond
962
- that. Say in "## Requirements delivered", per id, what has moved and what this
963
- story adds — a reader holding both should see two halves of one requirement,
964
- never two stories racing to deliver the same thing.
965
- - Both stories stay open and both are legitimate. There is no "superseded": a
966
- requirement is delivered when it has NO OPEN STORIES, so the pair is exactly
967
- how a mid-flight change is recorded.
968
- - Name the dependency honestly in "## Notes": what of {{p.follows}} has to land
969
- first, and what can proceed in parallel. `depends_on:` and `wave:` are the
970
- kit's — this story is in wave {{p.wave}}, after {{p.depends_on_csv}} — and
971
- `follows:` is deliberately NOT an ordering link, because the remainder is often
972
- buildable alongside the story it carries the remainder of. Saying which half is
973
- which is exactly what "## Notes" is for.
974
- - Everything the ordinary story rules require still holds: one "As a … I want …
975
- so that …"; "## Constraints" carrying the spec's CN-/DC-/IC-/UX- ids and the
976
- Experience Scope floors that bind this slice, by id, never in `exercises:`;
977
- "## Acceptance" decidable by looking at the running system; no implementation
978
- detail; enough that story-plan can scope its design pass from it directly.
979
-
980
- # 10c. UPDATE — the story is open, no story plan exists, and the author approved it.
981
- # It is rewritten against the spec as it now reads, and it may GAIN requirements:
982
- # the group's unclaimed ids move into it, which is the point of approving it.
983
- - id: update_story
984
- condition: "{{p.action}} == update"
985
- action: agent_author
986
- context:
987
- - "{{p.path}}"
988
- - "{{spec_file}}"
989
- - { path: .appweaver/memory/constitution.ui.md, optional: true }
990
- output_target: { type: file, path: "{{p.path}}" }
991
- task: |
992
- The story {{p.story}} is in context, and the specification it slices has moved.
993
- Bring it back into step.
994
-
995
- story: {{p.story}} ("{{p.prior_title}}")
996
- it cites today: {{p.prior_csv}}
997
- it must now cite: {{p.requirements_csv}}
998
- its slice: {{p.group_title}}
999
- why one slice: {{p.rationale}}
1000
-
1001
- Return the COMPLETE updated story. Rules:
1002
-
1003
- - EDIT IN PLACE. Keep everything the spec's change did not affect exactly as it
1004
- reads — wording, ordering, the Notes someone wrote. This story may already be
1005
- planned around, and an unrelated rewrite costs its reader a re-read for nothing.
1006
- - Frontmatter: `exercises:` becomes exactly [{{p.requirements_csv}}] and
1007
- `spec_version:` becomes {{latest_version}}. Leave `id:`, `status:`,
1008
- `depends_on:`, `wave:`, `follows:`, `work_item:` and `created:` alone.
1009
- `depends_on:` and `wave:` are rewritten from the slicing by the kit, which KEEPS
1010
- the dependencies this story already carries rather than replacing them — an edge
1011
- somebody added by hand may name work outside this feature that the sync has no
1012
- view of. This story is in wave {{p.wave}}.
1013
- - WHERE IT GAINED IDS, the story is genuinely bigger: add a "## Requirements
1014
- delivered" line for each new id, extend "## Acceptance" to cover it, and revisit
1015
- the "## Story" sentence and the title so they still name what this slice
1016
- actually delivers. A story whose title stopped describing it is worse than one
1017
- that was never updated.
1018
- - WHERE AN ID IT ALREADY CITED HAS CHANGED WORDING, restate what this story
1019
- delivers of it and re-check every acceptance line that depended on the old
1020
- reading. That is the whole reason this story is being touched.
1021
- - "## Constraints" — the ids are re-stamped into `constraints:` for you from the
1022
- spec as it now stands; this section's LINES are yours. A bound that has left the
1023
- spec leaves this section; one that has arrived gets its line, with the acceptance
1024
- line that shows it is met. Say plainly where one does not apply to this slice
1025
- rather than dropping it. Never add these ids to `exercises:`.
1026
- - NO IMPLEMENTATION DETAIL, and nothing here changes the spec. If the change
1027
- reveals a problem with the contract, note it and leave the requirement alone.
1028
-
1029
- # 10d. THE FIELDS THE GATES READ, written from the plan. See the block above 10a.
1030
- - id: stamp_story
1031
- action: execute_script
1032
- script: { ref: actions }
1033
- subcommand: story-stamp
1034
- # --title is a FALLBACK, not an override: story-stamp applies it only where the
1035
- # title is missing or still the template's placeholder, so the one the author wrote
1036
- # always wins. It exists because a story with no title reads as "(untitled)" in
1037
- # every later prompt and listing, and losing the name of the work is a needless
1038
- # way to make the backlog harder to read.
1039
- # `--depends-on` is a UNION with what the file already carries, not a replacement:
1040
- # the sync sees one feature's spec, and an edge added by hand may name work outside
1041
- # it. `--wave` is set outright — it is derived from the union, so re-deriving it is
1042
- # the only way it stays true.
1043
- # `--constraints` is a BARE FLAG: the op has the feature dir, so it lifts the ids
1044
- # from spec.md itself. Nothing here enumerates them, because there is nothing to
1045
- # decide — spec constraints are declared aggregate-wide ("must ALWAYS hold for the
1046
- # aggregate, independent of any single operation"), so the feature's whole set binds
1047
- # every slice of it. This is what makes `constraints:` a gate surface the KIT
1048
- # writes; the body's "## Constraints" section keeps the per-slice prose and is
1049
- # reconciled against the field, never overwritten by it.
1050
- args: ["--feature-dir", "{{feature_dir}}", "--story", "{{p.story}}", "--ids", "{{p.requirements_csv}}", "--version", "{{latest_version}}", "--follows", "{{p.follows}}", "--title", "{{p.group_title}}", "--depends-on", "{{p.depends_on_csv}}", "--wave", "{{p.wave}}", "--constraints"]
1051
- outputs:
1052
- - { name: stamped_constraints_csv, from: "$.constraints_csv" }
1053
- - { name: constraints_unlined_csv, from: "$.unlined_csv" }
1054
-
1055
- # 10e. THE RECONCILE REPORT. The stamp owns the ID SET and the model owns the LINE
1056
- # each id gets — "what this slice has to do about it" is the reason the section
1057
- # is worth reading, and a script cannot write it. So a bound that is now in the
1058
- # frontmatter with no line in the body is announced rather than silently added:
1059
- # the gate surface is already closed by the field, and this is only so the
1060
- # missing sentence is loud.
1061
- - id: constraints_reconcile_notice
1062
- condition: "{{constraints_unlined_csv}} != ''"
1063
- action: verify_context
1064
- require_value: constraints_unlined_csv
1065
- equals: ""
1066
- on_mismatch: continue
1067
- on_mismatch_message: >
1068
- {{p.story}} is bound by {{stamped_constraints_csv}} — the ids are stamped into its
1069
- `constraints:` frontmatter, which is what implement, implement-ui and analyze-slice
1070
- read. These have no line under "## Constraints" saying what this slice does about
1071
- them: {{constraints_unlined_csv}}. Add one each, or say plainly that it does not
1072
- apply here. The bound holds either way; what is missing is the reader's half.
1073
-
1074
- # 11. THE RELATION, READ THE OTHER WAY ROUND. Every story declares what it delivers in
1075
- # its own `exercises:`; the spec's Story Coverage block is that relation viewed from
1076
- # the requirements, regenerated wholesale from stories/ rather than patched. It sits
1077
- # between the AW:COVERAGE markers, which the ledger hash cuts out — so writing it is
1078
- # not an unledgered edit of the contract, and this workflow never has to re-stamp a
1079
- # document it did not change.
1080
- - id: coverage
1081
- action: execute_script
1082
- on_error: continue
1083
- script: { ref: actions }
1084
- subcommand: spec-coverage
1085
- args: ["--feature-dir", "{{feature_dir}}"]
1086
- outputs:
1087
- - { name: uncovered_csv, from: "$.uncovered_csv" }
1088
- - { name: delivered_csv, from: "$.delivered_csv" }
1089
-
1090
- - id: resolve_post_hooks
1091
- action: execute_script
1092
- script: { ref: actions }
1093
- subcommand: resolve-hooks
1094
- args: ["--phase", "after_sync_stories"]
1095
- outputs:
1096
- - { name: after_hooks, from: "$.hooks" }
1097
- - id: dispatch_post_hooks
1098
- action: dispatch_hooks
1099
- hooks_from_variable: after_hooks
1100
-
1101
- outputs:
1102
- feature_directory: "{{feature_dir}}"
1103
- spec_file: "{{spec_file}}"
1104
- spec_version: "{{latest_version}}"
1105
- requirements_synced: "{{work_csv}}"
1106
- synced_from: "{{work_note}}"
1107
- versions_covered: "{{span_versions_csv}}"
1108
- requirements_removed: "{{removed_csv}}" # named by a row, no longer in the spec (E1)
1109
- requirements_unplaced: "{{unplaced_csv}}" # the grouping left these out — no story
1110
- requirements_uncovered: "{{uncovered_csv}}" # no story at all, from the spec's own view
1111
- requirements_delivered: "{{delivered_csv}}" # every story citing them is closed
1112
- stories_written: "{{stories_csv}}"
1113
- stories_untouched: "{{skipped_csv}}" # you declined these
1114
- plan_summary: "{{create_count}} created, {{update_count}} updated, {{followon_count}} follow-on"
1115
- plan_notes: "{{plan_notes}}"
1116
- slicing_strategy: "{{strategy}} — {{strategy_note}}"
1117
- slicing_split: "{{split_note}}" # one story asked for; what actually came out
1118
- delivery_waves: "{{wave_digest}}" # wave 1 can be started now; the rest wait
1119
- dependency_cycles: "{{cycle_csv}}" # edges dropped to break a declared cycle
1120
- dependencies_unresolved: "{{unresolved_deps_csv}}" # edges naming no slice and no story
1121
- next: "The backlog is in step with spec.md at {{latest_version}}, laid out over {{wave_count}} delivery wave(s). START WITH WAVE 1 — those stories have nothing in front of them, and stories sharing a wave have no ordering between them, so they can be picked up in parallel; a later wave becomes startable as its own dependencies land, not when the whole wave before it finishes. Make sure /appweaver plan (and /appweaver plan-ui where a frontend lane is declared) has been run for this spec version first — a story design reads the feature plan and stops against one the spec has moved past. Then run /appweaver story-plan <story-id> to design one story's execution, then implement. READ THE FIELDS ABOVE FIRST: dependency_cycles and dependencies_unresolved are ordering the run could not honour, and a story that reads as free to start may not be; requirements_removed leaves open stories orphaned and needs deciding by hand; requirements_unplaced and requirements_uncovered are requirements with no story, which no later gate will raise for you; stories_untouched are the ones you declined, and they stay at their old spec version, so this sync will offer them again next run."