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