aw-spec-cli 0.1.9 → 0.1.11
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/ORCH-DIALECT.md +17 -5
- package/assets/.appweaver/orchestrator/README.md +1 -1
- package/assets/.appweaver/orchestrator/adapters/claude_agent_sdk.py +2 -2
- package/assets/.appweaver/orchestrator/adapters/google_antigravity.py +28 -6
- package/assets/.appweaver/orchestrator/modules/delta-spec.orch.yaml +13 -1
- package/assets/.appweaver/orchestrator/orchestrator.py +126 -39
- package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +5 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-characterize.orch.yaml +1 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +1 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +888 -141
- package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +826 -16
- package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +780 -12
- package/assets/.appweaver/stack-profiles/java-spring.json +3 -3
- package/assets/.appweaver/templates/spec-template.md +73 -62
- package/assets/.appweaver/templates/story-template.md +107 -0
- package/assets/guardrails/technology/java-spring/ai-rules.md +505 -0
- package/assets/guardrails/technology/java-spring/api-versioning.md +782 -0
- package/assets/guardrails/technology/java-spring/authorization.md +618 -0
- package/assets/guardrails/technology/java-spring/clean-architecture.md +960 -0
- package/assets/guardrails/technology/java-spring/coding-standards.md +821 -0
- package/assets/guardrails/technology/java-spring/dependency-policy.md +91 -0
- package/assets/guardrails/technology/java-spring/domain-driven-design.md +467 -0
- package/assets/guardrails/technology/java-spring/domain_abstraction.md +424 -0
- package/assets/guardrails/technology/java-spring/folder-structure.md +1537 -0
- package/assets/guardrails/technology/java-spring/global_exception_handler.md +247 -0
- package/assets/guardrails/technology/java-spring/logging.md +215 -0
- package/assets/guardrails/technology/java-spring/modular-monolith.md +77 -0
- package/assets/guardrails/technology/java-spring/persistence.md +316 -0
- package/assets/guardrails/technology/java-spring/problem_details.md +98 -0
- package/assets/guardrails/technology/java-spring/result_mapping.md +48 -0
- package/assets/guardrails/technology/java-spring/technology-constitution.md +168 -0
- package/assets/guardrails/technology/java-spring/testing.md +601 -0
- package/assets/guardrails/technology/java-spring/zero-trust.md +210 -0
- package/dist/index.js +0 -0
- package/dist/utils/config.d.ts +22 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +76 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/copy-assets.d.ts.map +1 -1
- package/dist/utils/copy-assets.js +4 -11
- package/dist/utils/copy-assets.js.map +1 -1
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +11 -7
- package/dist/utils/paths.js.map +1 -1
- package/package.json +2 -1
- package/assets/guardrails/technology/java-spring/README.md +0 -62
- package/dist/commands/update.d.ts +0 -3
- package/dist/commands/update.d.ts.map +0 -1
- package/dist/commands/update.js +0 -142
- package/dist/commands/update.js.map +0 -1
- package/dist/utils/manifest.d.ts +0 -31
- package/dist/utils/manifest.d.ts.map +0 -1
- package/dist/utils/manifest.js +0 -91
- package/dist/utils/manifest.js.map +0 -1
- package/dist/utils/update-engine.d.ts +0 -52
- package/dist/utils/update-engine.d.ts.map +0 -1
- package/dist/utils/update-engine.js +0 -203
- package/dist/utils/update-engine.js.map +0 -1
|
@@ -6,47 +6,54 @@
|
|
|
6
6
|
# NOT guess or create the feature — it CONSUMES the active one, and is phase-pure
|
|
7
7
|
# (spec side only; the single design.md is the `design` phase).
|
|
8
8
|
#
|
|
9
|
+
# specify writes ONE artifact: spec.md, plus the Change History row that records what
|
|
10
|
+
# it wrote. It does not touch stories/. The spec is the CONTRACT; a story is an
|
|
11
|
+
# execution SLICE over it, and the two directions between them each have their own
|
|
12
|
+
# workflow — /appweaver update-stories refreshes the backlog after the spec moves,
|
|
13
|
+
# /appweaver update-spec folds a hand-edited story back into the contract. Keeping
|
|
14
|
+
# specify story-blind is what stops the spec->story rule having two implementations.
|
|
15
|
+
#
|
|
9
16
|
# Auto-detects first-vs-subsequent from spec.md:
|
|
10
|
-
# • BOOTSTRAP (no spec.md) → author the
|
|
11
|
-
# •
|
|
12
|
-
# and
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
17
|
+
# • BOOTSTRAP (no spec.md) → author the spec from the template + init active_version.
|
|
18
|
+
# • EXTEND (spec.md exists) → edit spec.md IN PLACE: add the requirements the input
|
|
19
|
+
# describes, and amend the ones it changes. No delta document, no open change, no
|
|
20
|
+
# merge — the spec is the living truth and this is a write to it.
|
|
21
|
+
#
|
|
22
|
+
# Both paths then run ONE converge block — checklist + clarify + validate — over
|
|
23
|
+
# spec.md, and both finish by re-baselining the ledger hash.
|
|
16
24
|
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
# changes/<cr-id>/. The master is not read, not graded, NOT written.
|
|
22
|
-
# The pairing is bound from create-checklist's own output (checklist_file + target →
|
|
23
|
-
# quality_target), so the checklist and the document it grades can never diverge.
|
|
24
|
-
# Why it matters: the delta is authored relative to the master, so a "quality" rewrite
|
|
25
|
-
# of the master mid-change would leave the delta computed against a stale baseline —
|
|
26
|
-
# and in brownfield it would silently edit an adopted, code-derived spec that nothing
|
|
27
|
-
# downstream re-checks for fidelity. Narrow rewrites run include_standards:false.
|
|
28
|
-
# Accuracy of the delta AGAINST the master (id continuity, conflicts) stays where it
|
|
29
|
-
# belongs: /appweaver analyze-delta-design.
|
|
25
|
+
# THE LEDGER. spec.md's `## Change History` is the sync anchor between the contract and
|
|
26
|
+
# its backlog: each row names the requirement ids that version touched, and each story
|
|
27
|
+
# records the version it last synced at, so "is this story stale" is one integer
|
|
28
|
+
# comparison. Three properties of how this workflow writes it are load-bearing:
|
|
30
29
|
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
#
|
|
30
|
+
# 1. The row is COMPUTED, not declared, on BOTH paths. An extend snapshots spec.md before
|
|
31
|
+
# authoring and diffs requirement ids after the converge loop has finished with it; a
|
|
32
|
+
# bootstrap diffs the finished document against nothing, which yields every id it
|
|
33
|
+
# carries and is exactly the v1.0 row's content. A row the model declared could omit a
|
|
34
|
+
# requirement the quality loop rewrote on its way past, and an omitted id is silent —
|
|
35
|
+
# update-stories would never learn it moved.
|
|
36
|
+
# 2. The row and the hash are written by ONE call (ledger-append), so a row can never
|
|
37
|
+
# exist without the hash that proves the body it describes.
|
|
38
|
+
# 3. The ledger is checked on ENTRY, not only stamped on exit. A spec hand-edited with
|
|
39
|
+
# no row is the ledger's one silent failure, and running specify over the top would
|
|
40
|
+
# bury that edit under a version bump that never named it.
|
|
35
41
|
#
|
|
36
|
-
# Launch: /appweaver specify [strategy] <what the feature
|
|
42
|
+
# Launch: /appweaver specify [strategy] <what the feature needs, or what changes>
|
|
37
43
|
# e.g. /appweaver specify Customers place an order for one or more items
|
|
38
44
|
# Prereq: /appweaver select-feature (sets the active aggregate)
|
|
45
|
+
# Next: /appweaver update-stories (turns the new/changed requirements into stories)
|
|
39
46
|
# =============================================================================
|
|
40
47
|
|
|
41
48
|
name: aw-spec-specify
|
|
42
|
-
description: Author the specification of the ACTIVE feature (aggregate).
|
|
43
|
-
version:
|
|
49
|
+
description: Author or extend the specification of the ACTIVE feature (aggregate). The first call writes spec.md from the template; a later call edits spec.md in place, adding the requirements the input describes and amending the ones it changes. Both paths run the checklist/clarify/validate converge loop over spec.md. An extend appends a Change History row whose requirement ids are COMPUTED from a before/after diff, and re-baselines the ledger hash. Writes no stories — /appweaver update-stories owns the backlog. Spec phase only; design is separate.
|
|
50
|
+
version: 3.4.0
|
|
44
51
|
agent_agnostic: true
|
|
45
52
|
dialect: aw-spec.orchestrator/v1
|
|
46
53
|
|
|
47
54
|
inputs:
|
|
48
55
|
user_input:
|
|
49
|
-
description: What the feature (first call) or
|
|
56
|
+
description: What the feature should do (first call), or what is being added or changed (later calls), in natural language.
|
|
50
57
|
required: true
|
|
51
58
|
bind: USER_INPUT
|
|
52
59
|
|
|
@@ -74,14 +81,44 @@ vars:
|
|
|
74
81
|
spec_all_pass: "false" # validation-loop guard, flipped by the validate query
|
|
75
82
|
checklist_total: "0" # bound by checklist-read
|
|
76
83
|
checklist_passed: "0" # stays 0 when the checklist has no items to score
|
|
77
|
-
cr_id: "" # set on the DELTA path only
|
|
78
|
-
cr_dir: "" # set on the DELTA path only — changes/<cr-id>/
|
|
79
|
-
quality_target: "" # the document the converge loops grade AND rewrite:
|
|
80
|
-
# spec.md (bootstrap) | delta-spec.md (delta). Bound from
|
|
81
|
-
# create-checklist's own output so it always agrees with
|
|
82
|
-
# the checklist. NEVER hand-set to spec_file on the delta
|
|
83
|
-
# path — that is what silently drifts an adopted spec.
|
|
84
84
|
version: "" # current active_version.json version (for the report)
|
|
85
|
+
ledger_ok: "true" # entry tripwire result; the bootstrap path has no ledger to check
|
|
86
|
+
ledger_state: "bootstrap" # clean | unstamped | ledgered_edit | unledgered_edit | rollback | delta_open
|
|
87
|
+
ledger_error: ""
|
|
88
|
+
open_change: "" # a delta change open on this feature — reported, and only
|
|
89
|
+
# blocking once its delta-spec has actually been authored
|
|
90
|
+
snapshot: "" # pre-authoring copy of spec.md; the diff's "before"
|
|
91
|
+
changed_csv: "" # the ids this run actually touched — what the ledger row records
|
|
92
|
+
story_ids_csv: "" # changed, narrowed to FR-/BR- — the only classes a story can cite
|
|
93
|
+
removed_csv: "" # ids this run DELETED — every story citing one is orphaned (E1)
|
|
94
|
+
modified_csv: "" # ids amended in place, as distinct from added or removed
|
|
95
|
+
disturbing_csv: "" # modified ∪ removed — the ids an existing story could feel
|
|
96
|
+
change_count: "0" # 0 on an extend that changed no requirement: no row is written
|
|
97
|
+
ledger_version: "" # the version the row was written at
|
|
98
|
+
covered_csv: "" # ids with OPEN stories, read before authoring as a preference
|
|
99
|
+
covered_note: "" # that preference, WORDED for the case at hand — see ext_covered_*
|
|
100
|
+
disturbed_any: "false" # did this run move a requirement someone is building against
|
|
101
|
+
disturbed_csv: "" # which of them
|
|
102
|
+
disturbed_ids: "" # the same, as a list — the options of the per-requirement ask
|
|
103
|
+
disturbed_stories: "" # the open stories citing those ids
|
|
104
|
+
disturbed_flying: "" # the subset already past story-spec — a follow-on, not an edit
|
|
105
|
+
may_change: "" # the requirements the author approved for amendment
|
|
106
|
+
approved_csv: "" # the same, normalised
|
|
107
|
+
preserve_csv: "" # disturbed minus approved — restored and re-expressed additively
|
|
108
|
+
preserve_count: "0" # 0 = everything was approved, so nothing is re-authored
|
|
109
|
+
marker_count: "0" # [NEEDS CLARIFICATION] markers, counted before the model is asked
|
|
110
|
+
clarifications: [] # what the model made of them; empty when there were none to make
|
|
111
|
+
reissued_csv: "" # ids this run re-used after the ledger had already spent them
|
|
112
|
+
held_csv: "" # the preserved ids the restore actually put back
|
|
113
|
+
violated_csv: "" # the preserved ids it did not — amended without approval
|
|
114
|
+
quality_accept: "" # the author's answer when the spec ships still failing
|
|
115
|
+
readdit_pass: "false" # guard for the restore's own quality loop
|
|
116
|
+
readdit_failing: "" # what that loop found wanting in the requirements it minted
|
|
117
|
+
readdit_verdict: "true" # that loop's verdict, or "nothing was re-authored" when it never ran
|
|
118
|
+
readdit_accept: "" # the author's answer when the re-authored spec ships still failing
|
|
119
|
+
boot_row_version: "" # non-empty once the v1.0 row has actually been written
|
|
120
|
+
stamp_hold: "false" # true = do not re-baseline the hash; the document is unfinished
|
|
121
|
+
uncovered_csv: "" # requirements with no story at all — nobody is building these
|
|
85
122
|
|
|
86
123
|
workflow:
|
|
87
124
|
|
|
@@ -95,6 +132,11 @@ workflow:
|
|
|
95
132
|
- { name: feature_dir, from: "$.feature_dir" }
|
|
96
133
|
- { name: spec_file, from: "$.spec_file" }
|
|
97
134
|
- { name: spec_exists, from: "$.spec_exists" }
|
|
135
|
+
# needs_bootstrap = no spec.md, OR a first run that never finished. version-init raises
|
|
136
|
+
# a marker and only ledger-ids lowers it, so a bootstrap that died after writing spec.md
|
|
137
|
+
# is RE-RUN rather than mistaken for a spec to extend (which re-applied the same input
|
|
138
|
+
# over an unfinished v1.0 and bumped it to v1.1).
|
|
139
|
+
- { name: needs_bootstrap, from: "$.needs_bootstrap" }
|
|
98
140
|
|
|
99
141
|
- id: require_feature
|
|
100
142
|
action: verify_context
|
|
@@ -134,23 +176,30 @@ workflow:
|
|
|
134
176
|
hooks_from_variable: before_hooks
|
|
135
177
|
|
|
136
178
|
# ═══════════════════════ BOOTSTRAP — no spec.md yet ═══════════════════════
|
|
137
|
-
#
|
|
138
|
-
|
|
139
|
-
|
|
179
|
+
# Init the version state, THEN copy the spec template into the (already-existing)
|
|
180
|
+
# feature dir. That order is the point: --bootstrap raises the unfinished-first-draft
|
|
181
|
+
# marker, and raising it BEFORE spec.md exists means no crash can leave a document on
|
|
182
|
+
# disk that the next fresh run mistakes for a spec to extend. Scaffolding first left a
|
|
183
|
+
# window of exactly two calls wide in which a bare template was on disk with no marker
|
|
184
|
+
# beside it — and a bare template is `unstamped`, which the entry tripwire passes.
|
|
185
|
+
# --bootstrap is specify's, not the primitive's: adopt inits the same file after
|
|
186
|
+
# reversing a real spec out of code, and must not be marked unfinished.
|
|
187
|
+
- id: boot_version_init
|
|
188
|
+
condition: "{{needs_bootstrap}} == true"
|
|
140
189
|
action: execute_script
|
|
141
190
|
script: { ref: actions }
|
|
142
|
-
subcommand:
|
|
143
|
-
args: ["--feature-dir", "{{feature_dir}}", "--
|
|
191
|
+
subcommand: version-init
|
|
192
|
+
args: ["--feature-dir", "{{feature_dir}}", "--bootstrap"]
|
|
144
193
|
|
|
145
|
-
- id:
|
|
146
|
-
condition: "{{
|
|
194
|
+
- id: boot_scaffold
|
|
195
|
+
condition: "{{needs_bootstrap}} == true"
|
|
147
196
|
action: execute_script
|
|
148
197
|
script: { ref: actions }
|
|
149
|
-
subcommand:
|
|
150
|
-
args: ["--feature-dir", "{{feature_dir}}"]
|
|
198
|
+
subcommand: scaffold
|
|
199
|
+
args: ["--feature-dir", "{{feature_dir}}", "--allow-existing"]
|
|
151
200
|
|
|
152
201
|
- id: boot_ctx
|
|
153
|
-
condition: "{{
|
|
202
|
+
condition: "{{needs_bootstrap}} == true"
|
|
154
203
|
action: build_context
|
|
155
204
|
context:
|
|
156
205
|
- "{{spec_file}}" # the copied template = section structure
|
|
@@ -173,18 +222,25 @@ workflow:
|
|
|
173
222
|
# no file and no ux/ rules mapped, the section resolves to
|
|
174
223
|
# "None" and the spec is unchanged from a backend-only one.
|
|
175
224
|
- id: boot_generate
|
|
176
|
-
condition: "{{
|
|
225
|
+
condition: "{{needs_bootstrap}} == true"
|
|
177
226
|
action: agent_author
|
|
178
227
|
output_target: { type: file, path: "{{spec_file}}" }
|
|
179
228
|
task: |
|
|
180
|
-
Author the
|
|
181
|
-
|
|
229
|
+
Author the specification for this aggregate by filling out the template in
|
|
230
|
+
context. Base everything on:
|
|
182
231
|
|
|
183
232
|
{{USER_INPUT}}
|
|
184
233
|
|
|
185
234
|
Rules for the content you generate:
|
|
186
|
-
- The feature IS the aggregate; set the Aggregate field
|
|
235
|
+
- The feature IS the aggregate; set the Aggregate field.
|
|
187
236
|
- Preserve the template's section order and headings.
|
|
237
|
+
- THIS DOCUMENT HOLDS NO USER STORIES. Every operation on the aggregate
|
|
238
|
+
(CreateOrder, UpdateOrder, …) is a REQUIREMENT (FR-n) here — never a user
|
|
239
|
+
story, never an "As a … I want …" block, never a Given/When/Then acceptance
|
|
240
|
+
scenario, and never a section for any of those. A story is an execution slice
|
|
241
|
+
that lives beside this file in stories/ and cites the ids below; it is written
|
|
242
|
+
by /appweaver update-stories, not here. Design and code trace to the ids in
|
|
243
|
+
this document, which is what lets the backlog churn without disturbing them.
|
|
188
244
|
- SPECIFY IS THE ONLY PHASE THAT READS THE DOMAIN RULES. Design and implement
|
|
189
245
|
work from the spec/design, never the domain docs — so this spec is the SOLE
|
|
190
246
|
carrier of the domain into the pipeline. Model the domain COMPLETELY here, or
|
|
@@ -205,7 +261,10 @@ workflow:
|
|
|
205
261
|
but never how the adapter is built.
|
|
206
262
|
- Write for non-technical business stakeholders.
|
|
207
263
|
- Write System Requirements in EARS as the template shows (THE SYSTEM SHALL
|
|
208
|
-
/ WHEN…/ IF…THEN / WHILE…/ WHERE…) — each atomic and testable.
|
|
264
|
+
/ WHEN…/ IF…THEN / WHILE…/ WHERE…) — each atomic and testable. An EARS
|
|
265
|
+
requirement IS its own acceptance criterion; do not add a separate one.
|
|
266
|
+
- Give every requirement a STABLE id, numbered from 001 in its own class. Ids are
|
|
267
|
+
permanent: they are what stories cite and what design traces to.
|
|
209
268
|
- Capture always-true BUSINESS invariants as Business Rules (BR-n), and state
|
|
210
269
|
the aggregate boundary (what it owns vs references by id, and out of scope).
|
|
211
270
|
Record reasonable defaults in Assumptions.
|
|
@@ -238,91 +297,253 @@ workflow:
|
|
|
238
297
|
standard that governs every such behaviour is a UX-n.
|
|
239
298
|
Where neither a UI constitution nor ux/ rules are in context there is nothing to
|
|
240
299
|
lift: write "None (as of v1.0)" in both — never delete either section.
|
|
241
|
-
- This is v1.0:
|
|
242
|
-
"
|
|
300
|
+
- This is v1.0: leave the initial Change History row's version, date and
|
|
301
|
+
"Requirements changed" cell as the template has them. The kit rewrites that row
|
|
302
|
+
from the finished document, so ids listed here are discarded — the same rule that
|
|
303
|
+
makes every later version row an observation rather than a claim. Leave its Stories cell as "—";
|
|
304
|
+
update-stories fills that in when it raises the backlog.
|
|
305
|
+
- LEAVE THE "## Story Coverage" BLOCK ALONE, placeholder row and all. Everything
|
|
306
|
+
between the AW:COVERAGE markers is generated from the stories/ directory after
|
|
307
|
+
this step; anything written there by hand is discarded at the next write.
|
|
243
308
|
- ALWAYS include a "## Success Criteria" section with 2-4 measurable,
|
|
244
|
-
technology-agnostic, verifiable SC-nnn outcomes derived from the
|
|
245
|
-
|
|
246
|
-
|
|
309
|
+
technology-agnostic, verifiable SC-nnn outcomes derived from the requirements.
|
|
310
|
+
It is MANDATORY — never drop it; when a metric must be inferred, state a
|
|
311
|
+
reasonable, testable target rather than omitting the section.
|
|
247
312
|
- Add at most 3 "[NEEDS CLARIFICATION: <question>]" markers, and only for
|
|
248
313
|
choices that materially affect scope/UX and have no reasonable default.
|
|
249
314
|
- Every section the template marks *(mandatory)* — at minimum System
|
|
250
|
-
Requirements and Success Criteria — plus Business Need
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
never write "N/A".
|
|
315
|
+
Requirements and Success Criteria — plus Business Need and the Change History
|
|
316
|
+
row MUST be present and populated. The four lift sections (Constraints & NFRs,
|
|
317
|
+
Data Classification, Integration Contracts, Experience Standards) and Experience
|
|
318
|
+
Scope MUST also be present — populated when their rules apply, else
|
|
319
|
+
"None (as of v1.0)". You may omit only a genuinely inapplicable OPTIONAL item
|
|
320
|
+
(e.g. the WHERE optional-feature requirement, or an empty Assumptions list) —
|
|
321
|
+
never a mandatory section, and never write "N/A".
|
|
258
322
|
- Do NOT embed any checklist and NO design/implementation.
|
|
259
323
|
|
|
260
|
-
#
|
|
261
|
-
#
|
|
262
|
-
#
|
|
263
|
-
#
|
|
264
|
-
# stays OPEN; finalize-delta merges the delta into spec.md later in the chain. Runs in
|
|
265
|
-
# this context (shares vars + the DDD + domain sticky standards). The former standalone
|
|
266
|
-
# `cr` capture's spec half now lives here. Downstream: design (delta-design) →
|
|
267
|
-
# analyze-delta-design → implement-delta → build → finalize-delta → analyze-slice →
|
|
268
|
-
# close-delta (close-delta is what finally archives the change and bumps the version).
|
|
269
|
-
- id: delta_spec_module
|
|
270
|
-
condition: "{{spec_exists}} == true"
|
|
271
|
-
action: include
|
|
272
|
-
module: .appweaver/orchestrator/modules/delta-spec.orch.yaml
|
|
273
|
-
|
|
274
|
-
# ══════ CONVERGE — checklist + clarify + validate, ON THE DOCUMENT JUST WRITTEN ══════
|
|
275
|
-
# These loops run on WHICHEVER document this invocation authored — the master spec on
|
|
276
|
-
# the bootstrap path, the delta-spec on the change path. They must NEVER write the
|
|
277
|
-
# master while a change is open: the delta is authored relative to the master, so
|
|
278
|
-
# mutating the master here would leave the delta computed against a stale baseline
|
|
279
|
-
# AND (brownfield) silently edit an adopted, code-derived spec that no downstream
|
|
280
|
-
# stage re-checks for fidelity. Hence quality_target — bound from the SAME script call
|
|
281
|
-
# that creates the checklist, so the checklist and the graded document cannot diverge.
|
|
324
|
+
# ═══════════════════════ EXTEND — spec.md already exists ═══════════════════
|
|
325
|
+
# 5. THE ENTRY TRIPWIRE. Before writing a word, establish that the document on disk is
|
|
326
|
+
# the one the ledger describes. Two states stop the run, and both are states where
|
|
327
|
+
# writing would destroy information rather than add it:
|
|
282
328
|
#
|
|
283
|
-
#
|
|
284
|
-
#
|
|
285
|
-
#
|
|
286
|
-
|
|
287
|
-
|
|
329
|
+
# unledgered_edit spec.md was hand-edited and no Change History row was added.
|
|
330
|
+
# Appending this run's row would bump the version past that edit
|
|
331
|
+
# without ever naming its ids, so update-stories could not
|
|
332
|
+
# reconcile it and every story citing it stays silently stale.
|
|
333
|
+
# delta_open a change is open AND its delta-spec has been authored, so a delta
|
|
334
|
+
# is measured against this master. Temporary — the guard retires with
|
|
335
|
+
# the delta family — but while both exist it is the one silent way
|
|
336
|
+
# the two ways of changing a spec collide. Narrowed to an AUTHORED
|
|
337
|
+
# delta because cr-setup scaffolds a delta-spec for every change it
|
|
338
|
+
# opens: gating on the open flag alone refused every extend for the
|
|
339
|
+
# whole life of a DESIGN-only change, which strands nothing, and the
|
|
340
|
+
# refusal named a chain the operator may be retiring. The message now
|
|
341
|
+
# also names the way OUT (cr-archive) rather than only the way through.
|
|
342
|
+
#
|
|
343
|
+
# `unstamped` and `ledgered_edit` both pass: the first is a spec that predates the
|
|
344
|
+
# ledger, the second is a hand edit that did write its row. Both are re-baselined by
|
|
345
|
+
# the spec-stamp at the end of the run.
|
|
346
|
+
# No `on_error: continue`: spec-check reports every ledger STATE through its JSON with a
|
|
347
|
+
# zero exit, so a non-zero exit is the script itself failing. Continuing past that bound
|
|
348
|
+
# the gate's vars to nothing and produced the refusal message with its reason blanked out
|
|
349
|
+
# ("cannot write spec.md: (State: .)"), which is the right decision told unreadably.
|
|
350
|
+
- id: ext_ledger_check
|
|
351
|
+
condition: "{{needs_bootstrap}} == false"
|
|
288
352
|
action: execute_script
|
|
289
353
|
script: { ref: actions }
|
|
290
|
-
subcommand:
|
|
354
|
+
subcommand: spec-check
|
|
355
|
+
args: ["--feature-dir", "{{feature_dir}}"]
|
|
356
|
+
outputs:
|
|
357
|
+
- { name: ledger_ok, from: "$.ok" }
|
|
358
|
+
- { name: ledger_state, from: "$.state" }
|
|
359
|
+
- { name: ledger_error, from: "$.error" }
|
|
360
|
+
- { name: open_change, from: "$.open_change" }
|
|
361
|
+
- { name: version, from: "$.version" }
|
|
362
|
+
|
|
363
|
+
- id: ext_ledger_gate
|
|
364
|
+
condition: "{{needs_bootstrap}} == false"
|
|
365
|
+
action: verify_context
|
|
366
|
+
require_true: ledger_ok
|
|
367
|
+
on_false: stop
|
|
368
|
+
on_false_message: >
|
|
369
|
+
/appweaver specify cannot write spec.md: {{ledger_error}}
|
|
370
|
+
The Change History is how a story learns that a requirement it cites has moved —
|
|
371
|
+
a version that does not name the ids it changed leaves every affected story
|
|
372
|
+
stale with nothing to detect it. Resolve the state above, then re-run specify.
|
|
373
|
+
(State: {{ledger_state}}.)
|
|
374
|
+
|
|
375
|
+
# 6. Snapshot the document as it stands. This is the diff's "before", and it is what
|
|
376
|
+
# lets the Change History row be computed from what the run actually did rather than
|
|
377
|
+
# from what it claimed. Taken BEFORE authoring and consumed AFTER the converge loop,
|
|
378
|
+
# so a requirement the quality pass rewrote in passing is still recorded.
|
|
379
|
+
- id: ext_snapshot
|
|
380
|
+
condition: "{{needs_bootstrap}} == false"
|
|
381
|
+
action: execute_script
|
|
382
|
+
script: { ref: actions }
|
|
383
|
+
subcommand: spec-snapshot
|
|
384
|
+
args: ["--feature-dir", "{{feature_dir}}"]
|
|
385
|
+
outputs:
|
|
386
|
+
- { name: snapshot, from: "$.snapshot" }
|
|
387
|
+
|
|
388
|
+
# 6b. WHO IS BUILDING WHAT. A read-only scan of stories/ for the requirements that have
|
|
389
|
+
# open stories against them. specify writes no stories, but it is about to change a
|
|
390
|
+
# contract other people may already be building against, and knowing which ids those
|
|
391
|
+
# are is the difference between an amendment that costs a re-sync and one that costs
|
|
392
|
+
# nothing. Fed to the author as a preference, then checked against the diff below —
|
|
393
|
+
# the preference is a nudge, the check after the fact is the detector.
|
|
394
|
+
# Empty until the backlog exists, which degrades to "no preference, no ask".
|
|
395
|
+
- id: ext_story_context
|
|
396
|
+
condition: "{{needs_bootstrap}} == false"
|
|
397
|
+
action: execute_script
|
|
398
|
+
on_error: continue
|
|
399
|
+
script: { ref: actions }
|
|
400
|
+
subcommand: story-refs
|
|
291
401
|
args: ["--feature-dir", "{{feature_dir}}"]
|
|
292
402
|
outputs:
|
|
293
|
-
- { name:
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
#
|
|
297
|
-
#
|
|
298
|
-
#
|
|
299
|
-
#
|
|
300
|
-
|
|
301
|
-
|
|
403
|
+
- { name: covered_csv, from: "$.covered_csv" }
|
|
404
|
+
|
|
405
|
+
# 6b-bis. WORD THE PREFERENCE FOR THE CASE AT HAND. The rule below is an argument for
|
|
406
|
+
# preferring an ADDITION over an amendment, and it only makes sense while somebody is
|
|
407
|
+
# actually building. Interpolating covered_csv straight into it left a spec with no
|
|
408
|
+
# backlog reading an eight-line case for adding requirements under a blank list — the
|
|
409
|
+
# logic was vacuous ("one of those" names nothing) but the rhetoric was not, and the
|
|
410
|
+
# first extend of a spec nobody has stories against is exactly where amending in place
|
|
411
|
+
# is the honest thing to do. Two ordered sets rather than a compound condition, the
|
|
412
|
+
# same idiom as readdit_verdict_na/_real: the no-backlog wording is the default, and
|
|
413
|
+
# the preference replaces it only when there is open work to protect.
|
|
414
|
+
# Safe on the bootstrap path too — ext_story_context is skipped there, so covered_csv
|
|
415
|
+
# is empty and the second step cannot fire.
|
|
416
|
+
- id: ext_covered_none
|
|
417
|
+
action: set
|
|
418
|
+
vars:
|
|
419
|
+
covered_note: >
|
|
420
|
+
NOBODY HAS OPEN WORK AGAINST ANY REQUIREMENT IN THIS SPEC. No story cites any id in
|
|
421
|
+
this document, so nothing you do here can strand work already under way. Amend an
|
|
422
|
+
existing requirement where the change genuinely belongs to it, and add a new one
|
|
423
|
+
where the change is genuinely new — let the contract read the way it should read,
|
|
424
|
+
without inventing a companion requirement to avoid touching anything.
|
|
425
|
+
|
|
426
|
+
- id: ext_covered_some
|
|
427
|
+
condition: "{{covered_csv}} != ''"
|
|
428
|
+
action: set
|
|
429
|
+
vars:
|
|
430
|
+
covered_note: >
|
|
431
|
+
THESE REQUIREMENTS ARE BEING BUILT RIGHT NOW: {{covered_csv}}. Where the change can
|
|
432
|
+
be expressed EITHER as an amendment to one of those or as a new requirement
|
|
433
|
+
alongside it, prefer the new requirement — an amendment moves ground someone is
|
|
434
|
+
standing on, and a requirement that gains a companion leaves their work correct as
|
|
435
|
+
written. This is a preference, not a prohibition: when the change genuinely
|
|
436
|
+
contradicts what one of them says, amend it. A contract that states two
|
|
437
|
+
incompatible things to avoid disturbing someone is worse than the disturbance.
|
|
438
|
+
Requirements NOT in that list have no open work against them: amend those in place
|
|
439
|
+
wherever the change genuinely belongs to them.
|
|
440
|
+
|
|
441
|
+
- id: ext_ctx
|
|
442
|
+
condition: "{{needs_bootstrap}} == false"
|
|
443
|
+
action: build_context
|
|
444
|
+
context:
|
|
445
|
+
- "{{spec_file}}" # the living spec — edited in place
|
|
446
|
+
- .appweaver/memory/constitution.domain.md # same domain frame as the bootstrap path
|
|
447
|
+
- { path: .appweaver/memory/constitution.ui.md, optional: true }
|
|
448
|
+
|
|
449
|
+
- id: ext_generate
|
|
450
|
+
condition: "{{needs_bootstrap}} == false"
|
|
451
|
+
action: agent_author
|
|
452
|
+
output_target: { type: file, path: "{{spec_file}}" }
|
|
453
|
+
task: |
|
|
454
|
+
Update the specification in context — the living contract for this aggregate —
|
|
455
|
+
to carry this change:
|
|
456
|
+
|
|
457
|
+
{{USER_INPUT}}
|
|
458
|
+
|
|
459
|
+
Return the COMPLETE updated document. Rules:
|
|
460
|
+
- EDIT IN PLACE. This is the living spec, not a change document. Add what the
|
|
461
|
+
input describes; amend what it changes; leave everything else EXACTLY as it is,
|
|
462
|
+
character for character. Do not restate, reword, reorder or "improve" a
|
|
463
|
+
requirement the input did not touch — an unrelated edit here reads downstream
|
|
464
|
+
as a contract change and pulls stories into rework for nothing.
|
|
465
|
+
- NEW requirements take the NEXT FREE id in their class. Ids are permanent and
|
|
466
|
+
are NEVER re-issued: if FR-004 was removed at some point, the next functional
|
|
467
|
+
requirement is still FR-006 and not FR-004. Stories, designs and code cite
|
|
468
|
+
these ids, so a reused id silently re-points work that has already shipped.
|
|
469
|
+
- NEVER RENUMBER an existing requirement. If a requirement is genuinely obsolete,
|
|
470
|
+
remove it and say so — do not renumber the ones after it to close the gap.
|
|
471
|
+
- AMEND, don't duplicate. When the input changes existing behaviour, edit that
|
|
472
|
+
requirement where it stands. Adding a second requirement that contradicts the
|
|
473
|
+
first leaves the aggregate with two answers and no way to tell which holds.
|
|
474
|
+
- {{covered_note}}
|
|
475
|
+
- THIS DOCUMENT HOLDS NO USER STORIES. Do not add an "As a … I want …" block, a
|
|
476
|
+
Given/When/Then acceptance scenario, or a section for either. Stories live in
|
|
477
|
+
stories/ beside this file and are written by /appweaver update-stories.
|
|
478
|
+
- DO NOT TOUCH the "**Version**" header or the "## Change History" table, and leave everything between the AW:COVERAGE markers exactly as it is. Both are
|
|
479
|
+
written by the kit after this step, from a diff of what you actually changed —
|
|
480
|
+
editing them here produces a duplicate row and a version nothing agrees on.
|
|
481
|
+
- Keep every section, in order. A section with nothing to say keeps its "None"
|
|
482
|
+
text; never delete one, and never write "N/A".
|
|
483
|
+
- Everything the bootstrap rules require of this document still holds: EARS form
|
|
484
|
+
for functional requirements, business-facing language, no implementation
|
|
485
|
+
mechanics, the domain lift into CN-/DC-/IC- and the experience lift into UX-,
|
|
486
|
+
stable ids throughout, and Success Criteria present and measurable.
|
|
487
|
+
- Where the change genuinely leaves an open question, mark it
|
|
488
|
+
"[NEEDS CLARIFICATION: <question>]" (at most 3, scope/UX/security only).
|
|
489
|
+
|
|
490
|
+
# ══════ CONVERGE — checklist + clarify + validate, over spec.md, both paths ══════
|
|
491
|
+
# One target now that the delta document is gone, so the checklist, the clarify loop
|
|
492
|
+
# and the validation loop all read and rewrite the same file the run authored. The
|
|
493
|
+
# loops may touch any part of the spec; that is deliberate and safe, because the
|
|
494
|
+
# Change History row is computed from a diff taken AFTER they finish rather than from
|
|
495
|
+
# a declaration made before they start.
|
|
496
|
+
#
|
|
497
|
+
# 6c. DROP THE AUTHORING ENVELOPE. build_context sets a STICKY context that every later
|
|
498
|
+
# model step carries in front of its own, so the two constitutions the author needed
|
|
499
|
+
# were re-sent with all ~10 converge calls — a checklist grading pass paying for the
|
|
500
|
+
# domain rules on every iteration. The converge steps each declare what they read, so
|
|
501
|
+
# emptying the envelope here costs nothing; the two that genuinely judge the domain
|
|
502
|
+
# lift (validate and its patch) name the constitutions themselves.
|
|
503
|
+
- id: converge_ctx
|
|
504
|
+
action: build_context
|
|
505
|
+
context: []
|
|
506
|
+
|
|
507
|
+
# 7. Quality checklist (shared script — static content, NO model). Idempotent in content.
|
|
508
|
+
- id: create_checklist
|
|
302
509
|
action: execute_script
|
|
303
510
|
script: { ref: actions }
|
|
304
511
|
subcommand: create-checklist
|
|
305
|
-
args: ["--feature-dir", "{{feature_dir}}"
|
|
512
|
+
args: ["--feature-dir", "{{feature_dir}}"]
|
|
306
513
|
outputs:
|
|
307
|
-
- { name: checklist_file,
|
|
308
|
-
- { name: quality_target, from: "$.target" } # → changes/<cr-id>/delta-spec.md
|
|
514
|
+
- { name: checklist_file, from: "$.checklist_file" }
|
|
309
515
|
|
|
310
|
-
#
|
|
516
|
+
# 8. Resolve [NEEDS CLARIFICATION] markers interactively (≤3). Non-interactive
|
|
311
517
|
# runs answer with the first/recommended option (a documented default).
|
|
518
|
+
#
|
|
519
|
+
# Counted by a script before the model is asked. Most runs leave no marker, and asking
|
|
520
|
+
# a model to confirm that costs a round trip per run to be told "none" — the model's
|
|
521
|
+
# real work is turning a marker into a question with answerable options, which is only
|
|
522
|
+
# worth paying for once there is a marker to turn.
|
|
523
|
+
- id: spec_markers
|
|
524
|
+
action: execute_script
|
|
525
|
+
script: { ref: actions }
|
|
526
|
+
subcommand: spec-markers
|
|
527
|
+
args: ["--feature-dir", "{{feature_dir}}"]
|
|
528
|
+
outputs:
|
|
529
|
+
- { name: marker_count, from: "$.marker_count" }
|
|
530
|
+
|
|
312
531
|
- id: extract_clarifications
|
|
532
|
+
condition: "{{marker_count}} != 0"
|
|
313
533
|
action: agent_query
|
|
314
|
-
context: ["{{
|
|
534
|
+
context: ["{{spec_file}}"]
|
|
315
535
|
include_standards: false
|
|
316
536
|
task: |
|
|
317
|
-
Extract up to 3 unresolved "[NEEDS CLARIFICATION: <question>]" markers from
|
|
318
|
-
|
|
319
|
-
|
|
537
|
+
Extract up to 3 unresolved "[NEEDS CLARIFICATION: <question>]" markers from the
|
|
538
|
+
feature spec in context, most scope/security/UX-impacting first. Return
|
|
539
|
+
{"clarifications":[]} if there are none.
|
|
320
540
|
schema: { clarifications: [ { id: c1, question: "<the question>", options: ["<answer A>", "<answer B>", "<answer C>"] } ] }
|
|
321
541
|
mock: { clarifications: [] }
|
|
322
542
|
outputs:
|
|
323
543
|
- { name: clarifications, from: "$.clarifications" }
|
|
324
544
|
|
|
325
545
|
- id: clarify_loop
|
|
546
|
+
condition: "{{marker_count}} != 0"
|
|
326
547
|
action: loop
|
|
327
548
|
over: "{{clarifications}}"
|
|
328
549
|
as: c
|
|
@@ -336,18 +557,18 @@ workflow:
|
|
|
336
557
|
answer_var: c_answer
|
|
337
558
|
- id: resolve_clar
|
|
338
559
|
action: agent_author
|
|
339
|
-
context: ["{{
|
|
340
|
-
include_standards: false # rewrite-in-place: the
|
|
560
|
+
context: ["{{spec_file}}"]
|
|
561
|
+
include_standards: false # rewrite-in-place: the spec only
|
|
341
562
|
task: |
|
|
342
|
-
Replace the matching "[NEEDS CLARIFICATION]" marker in the
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
563
|
+
Replace the matching "[NEEDS CLARIFICATION]" marker in the spec in context
|
|
564
|
+
with this answer and update the affected requirement/section accordingly.
|
|
565
|
+
Preserve structure, headings and every requirement id. Do not touch the
|
|
566
|
+
"**Version**" header or the "## Change History" table, and leave everything between the AW:COVERAGE markers exactly as it is.
|
|
346
567
|
Question: {{c.question}}
|
|
347
568
|
Answer: {{c_answer}}
|
|
348
|
-
output_target: { type: file, path: "{{
|
|
569
|
+
output_target: { type: file, path: "{{spec_file}}" }
|
|
349
570
|
|
|
350
|
-
#
|
|
571
|
+
# 9. Spec-quality validation loop — evaluate the spec against the checklist,
|
|
351
572
|
# fix failing items, re-check (max 3), then score the checklist file.
|
|
352
573
|
- id: checklist_read
|
|
353
574
|
action: execute_script
|
|
@@ -364,15 +585,22 @@ workflow:
|
|
|
364
585
|
steps:
|
|
365
586
|
- id: validate
|
|
366
587
|
action: agent_query
|
|
367
|
-
|
|
588
|
+
# The domain and UI constitutions ride along HERE and nowhere else in the converge
|
|
589
|
+
# block: the Domain Lift and Experience Lift items ask whether the spec carries what
|
|
590
|
+
# those files impose, and that is not judgeable from the spec alone. Optional, not
|
|
591
|
+
# required — ext_ctx/boot_ctx already announce a missing domain constitution once,
|
|
592
|
+
# and repeating that announcement per iteration is noise, not signal.
|
|
593
|
+
context:
|
|
594
|
+
- "{{checklist_file}}"
|
|
595
|
+
- "{{spec_file}}"
|
|
596
|
+
- { path: .appweaver/memory/constitution.domain.md, optional: true }
|
|
597
|
+
- { path: .appweaver/memory/constitution.ui.md, optional: true }
|
|
368
598
|
include_standards: false
|
|
369
599
|
task: |
|
|
370
|
-
Evaluate the
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
(An item about remaining [NEEDS CLARIFICATION] markers passes once none
|
|
375
|
-
remain.) In "failing", quote the offending/absent part per failed item.
|
|
600
|
+
Evaluate the feature spec (in context) against each checklist item (in
|
|
601
|
+
context, 1-based in order). An item passes when the spec satisfies it. (An
|
|
602
|
+
item about remaining [NEEDS CLARIFICATION] markers passes once none remain.)
|
|
603
|
+
In "failing", quote the offending/absent part per failed item.
|
|
376
604
|
schema: { all_pass: false, results: { "1": true, "2": false }, failing: [ { n: 2, issue: "<quote>" } ] }
|
|
377
605
|
mock: { all_pass: true, results: {}, failing: [] }
|
|
378
606
|
outputs:
|
|
@@ -382,41 +610,552 @@ workflow:
|
|
|
382
610
|
- id: pass_break
|
|
383
611
|
action: break
|
|
384
612
|
condition: "{{spec_all_pass}} == true"
|
|
613
|
+
# Only while another pass remains. The loop is validate -> break-if-pass -> patch, so
|
|
614
|
+
# hitting max_iterations always landed on a PATCH nobody re-read: the document shipped
|
|
615
|
+
# with ungraded text and score_checklist wrote the pre-patch verdict into the checklist
|
|
616
|
+
# file. Skipping the last patch costs one model call less and makes the last thing that
|
|
617
|
+
# happened to the document a grading.
|
|
385
618
|
- id: patch_spec
|
|
619
|
+
condition: "{{iteration}} != 2"
|
|
386
620
|
action: agent_author
|
|
387
|
-
context:
|
|
621
|
+
context:
|
|
622
|
+
- "{{spec_file}}"
|
|
623
|
+
- { path: .appweaver/memory/constitution.domain.md, optional: true }
|
|
624
|
+
- { path: .appweaver/memory/constitution.ui.md, optional: true }
|
|
388
625
|
include_standards: false
|
|
389
626
|
task: |
|
|
390
|
-
Fix these quality failures in the
|
|
391
|
-
|
|
392
|
-
business-facing and testable.
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
never expand it into a full specification, never copy in unchanged master
|
|
396
|
-
content to satisfy an item, and never edit the master spec.
|
|
627
|
+
Fix these quality failures in the spec in context, preserving its section
|
|
628
|
+
order, headings and every requirement id. Do NOT add implementation detail;
|
|
629
|
+
keep it business-facing and testable. Do NOT add user stories or acceptance
|
|
630
|
+
scenarios — this document holds neither. Do NOT touch the "**Version**"
|
|
631
|
+
header or the "## Change History" table, and leave everything between the AW:COVERAGE markers exactly as it is.
|
|
397
632
|
Failures: {{spec_failing}}
|
|
398
|
-
output_target: { type: file, path: "{{
|
|
633
|
+
output_target: { type: file, path: "{{spec_file}}" }
|
|
399
634
|
|
|
400
635
|
- id: score_checklist
|
|
401
636
|
action: execute_script
|
|
402
637
|
condition: "{{checklist_total}} != 0"
|
|
403
638
|
script: { ref: actions }
|
|
404
639
|
subcommand: checklist-apply
|
|
405
|
-
|
|
640
|
+
# --all-pass because the per-item map is the model's and a pass routinely comes back
|
|
641
|
+
# with a partial map or none at all, which scored a passing spec as "0/25 items passing".
|
|
642
|
+
args: ["--file", "{{checklist_file}}", "--results", "{{spec_results}}", "--all-pass", "{{spec_all_pass}}"]
|
|
406
643
|
outputs:
|
|
407
644
|
- { name: checklist_passed, from: "$.after_checked" }
|
|
408
645
|
|
|
409
|
-
#
|
|
410
|
-
|
|
646
|
+
# 9b. THE SPEC STILL FAILS. The loop grades, patches, and grades again; when it runs out of
|
|
647
|
+
# iterations the document is left failing and every step after here would have carried
|
|
648
|
+
# on as if it had passed — appending a version row for a spec the run itself judged red,
|
|
649
|
+
# with a checklist score beside it that need not agree.
|
|
650
|
+
#
|
|
651
|
+
# Asked, not decided, and RECORD IS FIRST on purpose. spec.md is already written by this
|
|
652
|
+
# point: a run that refuses the ledger row leaves a document with no row, which is the
|
|
653
|
+
# `unledgered_edit` state the next run refuses outright. Recording a red spec is honest
|
|
654
|
+
# and recoverable — the row names what moved, so update-stories still works, and the
|
|
655
|
+
# failures are in the checklist file. Stopping is the safer-feeling option that costs
|
|
656
|
+
# more, so the prompt says what it costs and how to undo it.
|
|
657
|
+
- id: quality_ask
|
|
658
|
+
condition: "{{spec_all_pass}} != true"
|
|
659
|
+
action: ask
|
|
660
|
+
prompt: |
|
|
661
|
+
The spec still fails checklist items after the quality loop gave up
|
|
662
|
+
({{checklist_passed}}/{{checklist_total}} passing). The failures are listed in
|
|
663
|
+
{{checklist_file}}.
|
|
664
|
+
Outstanding: {{spec_failing}}
|
|
665
|
+
spec.md is ALREADY WRITTEN either way — the only question is whether this run records
|
|
666
|
+
what it changed.
|
|
667
|
+
options:
|
|
668
|
+
- "Record it — the version row is what keeps the stories in step; fix the spec next"
|
|
669
|
+
- "Stop — write no version row, and repair spec.md by hand first"
|
|
670
|
+
answer_key: quality_accept
|
|
671
|
+
answer_var: quality_accept
|
|
672
|
+
|
|
673
|
+
- id: quality_gate
|
|
674
|
+
condition: "{{spec_all_pass}} != true"
|
|
675
|
+
action: verify_context
|
|
676
|
+
require_value: quality_accept
|
|
677
|
+
equals: "Record it — the version row is what keeps the stories in step; fix the spec next"
|
|
678
|
+
on_mismatch: stop
|
|
679
|
+
on_mismatch_message: >
|
|
680
|
+
Stopped at your request with no version row written. The quality failures are listed in
|
|
681
|
+
{{checklist_file}}.
|
|
682
|
+
On a FIRST spec nothing else is needed: the bootstrap is still marked unfinished, so the
|
|
683
|
+
next specify re-authors it rather than extending it.
|
|
684
|
+
On an EXISTING spec, spec.md HAS been rewritten and the next specify will refuse it as an
|
|
685
|
+
unledgered edit until one of two things is true — add a Change History row naming the ids
|
|
686
|
+
you changed and re-run spec-stamp, or revert the document from the copy taken before this
|
|
687
|
+
run, which is beside it at {{snapshot}}.
|
|
688
|
+
|
|
689
|
+
# ═══════════════ THE FIRST LEDGER ROW — bootstrap only ═════════════════════
|
|
690
|
+
# 9b. The v1.0 row, OBSERVED. Every other version row is computed from a before/after
|
|
691
|
+
# diff for the reason this file opens with: a row the model DECLARED can omit an id it
|
|
692
|
+
# moved, and an omitted id is silent. v1.0 was the one exception — written by the
|
|
693
|
+
# author from a template whose placeholder reads "FR-001…FR-00n", so nothing
|
|
694
|
+
# downstream could trust it and update-stories had to be told to enumerate from the
|
|
695
|
+
# document instead. Diffing against no snapshot yields every id the finished spec
|
|
696
|
+
# carries, which is exactly the v1.0 row's content, so the first row is now derived
|
|
697
|
+
# the same way as the rest and the special case downstream goes away.
|
|
698
|
+
#
|
|
699
|
+
# on_error: continue — spec.md is already written, and a spec so empty that the diff
|
|
700
|
+
# found no id at all must not be stranded on disk with no ledger row. It keeps what
|
|
701
|
+
# the author wrote and says so in the run log.
|
|
702
|
+
- id: boot_diff
|
|
703
|
+
condition: "{{needs_bootstrap}} == true"
|
|
704
|
+
action: execute_script
|
|
705
|
+
script: { ref: actions }
|
|
706
|
+
subcommand: spec-diff
|
|
707
|
+
args: ["--feature-dir", "{{feature_dir}}", "--snapshot", ""]
|
|
708
|
+
outputs:
|
|
709
|
+
- { name: changed_csv, from: "$.changed_csv" }
|
|
710
|
+
- { name: story_ids_csv, from: "$.story_ids_csv" }
|
|
711
|
+
- { name: change_count, from: "$.change_count" }
|
|
712
|
+
|
|
713
|
+
# The hold is the other half of that on_error. spec-stamp runs on every path and
|
|
714
|
+
# re-baselines the hash, so a soft-failed ledger-ids used to leave a first draft with
|
|
715
|
+
# NO version row and a hash saying the document was in step — the next run's tripwire
|
|
716
|
+
# reads `clean` and every tool that trusts the hash believes the spec is finished.
|
|
717
|
+
# Held here and released only by a row that actually landed, so a bootstrap that did
|
|
718
|
+
# not complete stays unstamped AND still marked pending: an honest re-author.
|
|
719
|
+
- id: boot_stamp_hold
|
|
720
|
+
condition: "{{needs_bootstrap}} == true"
|
|
721
|
+
action: set
|
|
722
|
+
vars: { stamp_hold: "true", version: "v1.0 (draft — no version row yet)" }
|
|
723
|
+
|
|
724
|
+
- id: boot_ledger_ids
|
|
725
|
+
condition: "{{needs_bootstrap}} == true"
|
|
726
|
+
action: execute_script
|
|
727
|
+
on_error: continue
|
|
728
|
+
script: { ref: actions }
|
|
729
|
+
subcommand: ledger-ids
|
|
730
|
+
args: ["--feature-dir", "{{feature_dir}}", "--ids", "{{changed_csv}}"]
|
|
731
|
+
outputs:
|
|
732
|
+
- { name: boot_row_version, from: "$.version" }
|
|
733
|
+
|
|
734
|
+
- id: boot_stamp_release
|
|
735
|
+
condition: "{{boot_row_version}} != ''"
|
|
736
|
+
action: set
|
|
737
|
+
vars: { stamp_hold: "false" }
|
|
738
|
+
|
|
739
|
+
# ═════════════════════════ THE LEDGER ROW — extend only ════════════════════
|
|
740
|
+
# 10. What changed, observed. Diffing the finished document against the snapshot taken
|
|
741
|
+
# before authoring gives the requirement ids this run added, amended or removed —
|
|
742
|
+
# including any the converge loop touched on its way past, which is the whole
|
|
743
|
+
# reason the diff is taken here and not before the loop.
|
|
744
|
+
- id: ext_diff
|
|
745
|
+
condition: "{{needs_bootstrap}} == false"
|
|
746
|
+
action: execute_script
|
|
747
|
+
script: { ref: actions }
|
|
748
|
+
subcommand: spec-diff
|
|
749
|
+
args: ["--feature-dir", "{{feature_dir}}", "--snapshot", "{{snapshot}}"]
|
|
750
|
+
outputs:
|
|
751
|
+
- { name: changed_csv, from: "$.changed_csv" }
|
|
752
|
+
- { name: story_ids_csv, from: "$.story_ids_csv" }
|
|
753
|
+
- { name: removed_csv, from: "$.removed_csv" }
|
|
754
|
+
- { name: modified_csv, from: "$.modified_csv" }
|
|
755
|
+
- { name: disturbing_csv, from: "$.disturbing_csv" }
|
|
756
|
+
- { name: reissued_csv, from: "$.reissued_csv" }
|
|
757
|
+
- { name: change_count, from: "$.change_count" }
|
|
758
|
+
|
|
759
|
+
# 10b. DID THIS MOVE GROUND SOMEONE IS STANDING ON? The diff is fact, so this is the
|
|
760
|
+
# detector the pre-authoring preference lacked: of the requirements this run
|
|
761
|
+
# MODIFIED or REMOVED, which have open stories against them. An ADDED requirement
|
|
762
|
+
# is never in this set — no story cites an id that did not exist a moment ago.
|
|
763
|
+
# NO `on_error: continue` here, unlike the pre-authoring scan above. That one is a
|
|
764
|
+
# preference and degrades honestly to "no preference"; this one is the only thing
|
|
765
|
+
# standing between an amendment and the people building against it. Continuing past a
|
|
766
|
+
# failure bound disturbed_any to nothing, which is falsy, which SKIPS the ask — the
|
|
767
|
+
# check designed to stop a silent amendment failing open into a silent amendment.
|
|
768
|
+
- id: ext_disturbed
|
|
769
|
+
condition: "{{change_count}} != 0"
|
|
770
|
+
action: execute_script
|
|
771
|
+
script: { ref: actions }
|
|
772
|
+
subcommand: story-refs
|
|
773
|
+
args: ["--feature-dir", "{{feature_dir}}", "--ids", "{{disturbing_csv}}"]
|
|
774
|
+
outputs:
|
|
775
|
+
- { name: disturbed_any, from: "$.any" }
|
|
776
|
+
- { name: disturbed_csv, from: "$.affected_csv" }
|
|
777
|
+
- { name: disturbed_ids, from: "$.affected_ids" }
|
|
778
|
+
- { name: disturbed_stories, from: "$.story_csv" }
|
|
779
|
+
- { name: disturbed_flying, from: "$.in_flight_csv" }
|
|
780
|
+
|
|
781
|
+
# 10c. THE ASK. Changing a requirement someone is building against is a decision with a
|
|
782
|
+
# cost the machine cannot weigh: it may be the only honest way to state the new
|
|
783
|
+
# contract, or it may be an amendment that could equally have been an addition. The
|
|
784
|
+
# two outcomes differ sharply for the people downstream —
|
|
785
|
+
#
|
|
786
|
+
# amend the contract moves. Every open story citing it goes stale, and
|
|
787
|
+
# update-stories reconciles them: edited in place if no story-spec
|
|
788
|
+
# exists yet, carried by a follow-on if one does (§5.5).
|
|
789
|
+
# add the amended requirements go back to how they read, and the change is
|
|
790
|
+
# expressed as new requirements alongside them. Nobody's work goes
|
|
791
|
+
# stale, because nothing they cite has moved, and the new requirements
|
|
792
|
+
# become new stories.
|
|
793
|
+
#
|
|
794
|
+
# Asked rather than decided, for the same reason §5.4 asks on indirect impact:
|
|
795
|
+
# there is no oracle for "should this have been additive", and a wrong silent
|
|
796
|
+
# answer either strands work or splits one contract across two requirements.
|
|
797
|
+
# Amend is first, so a non-interactive run keeps what the author actually wrote.
|
|
798
|
+
- id: ask_disturb
|
|
799
|
+
condition: "{{disturbed_any}} == true"
|
|
800
|
+
action: ask
|
|
801
|
+
multi: true
|
|
802
|
+
prompt: |
|
|
803
|
+
This change MODIFIED or REMOVED requirements that people have open stories against.
|
|
804
|
+
Requirements: {{disturbed_csv}}
|
|
805
|
+
Open stories: {{disturbed_stories}}
|
|
806
|
+
Already in flight (story-spec exists): {{disturbed_flying}}
|
|
807
|
+
Pick the ones this change MAY alter. Each one you pick moves the contract: its open
|
|
808
|
+
stories go stale, and update-stories reconciles them — editing a story in place, or
|
|
809
|
+
minting a follow-on where a story-spec already exists. Each one you LEAVE OUT is put
|
|
810
|
+
back exactly as it read, and the change it was carrying is expressed as a new
|
|
811
|
+
requirement alongside it, so the work already under way stays correct.
|
|
812
|
+
Which may be changed?
|
|
813
|
+
options_from: "{{disturbed_ids}}"
|
|
814
|
+
answer_key: may_change
|
|
815
|
+
answer_var: may_change
|
|
816
|
+
|
|
817
|
+
# 10d. The complement: disturbed minus approved is what must go back. Computed by a
|
|
818
|
+
# script because the orchestrator binds values and never computes them.
|
|
819
|
+
- id: ext_preserve
|
|
820
|
+
condition: "{{disturbed_any}} == true"
|
|
821
|
+
action: execute_script
|
|
822
|
+
script: { ref: actions }
|
|
823
|
+
subcommand: spec-preserve
|
|
824
|
+
args: ["--disturbing", "{{disturbed_csv}}", "--approved", "{{may_change}}"]
|
|
825
|
+
outputs:
|
|
826
|
+
- { name: preserve_csv, from: "$.preserve_csv" }
|
|
827
|
+
- { name: preserve_count, from: "$.preserve_count" }
|
|
828
|
+
- { name: approved_csv, from: "$.approved_csv" }
|
|
829
|
+
|
|
830
|
+
# 10e. Restore what was not approved. The snapshot is still on disk, so both versions
|
|
831
|
+
# are in context: put those requirements back exactly as they read, and carry what
|
|
832
|
+
# they were expressing in new requirements instead. Requirements the author DID
|
|
833
|
+
# approve stay amended — this is per requirement, not all-or-nothing.
|
|
834
|
+
- id: ext_readdit
|
|
835
|
+
condition: "{{preserve_count}} != 0"
|
|
836
|
+
action: agent_author
|
|
837
|
+
# The constitutions its own grader and patcher already read. Minting requirements
|
|
838
|
+
# without them meant the step that WRITES the new contract was the only one in the
|
|
839
|
+
# chain flying blind, and the two steps after it then graded and patched what it
|
|
840
|
+
# produced against rules it had never seen.
|
|
841
|
+
context:
|
|
842
|
+
- "{{spec_file}}"
|
|
843
|
+
- "{{snapshot}}"
|
|
844
|
+
- { path: .appweaver/memory/constitution.domain.md, optional: true }
|
|
845
|
+
- { path: .appweaver/memory/constitution.ui.md, optional: true }
|
|
846
|
+
include_standards: false
|
|
847
|
+
output_target: { type: file, path: "{{spec_file}}" }
|
|
848
|
+
task: |
|
|
849
|
+
Two versions of this specification are in context: the CURRENT one at
|
|
850
|
+
{{spec_file}}, and the SNAPSHOT of it taken before this run edited it, at
|
|
851
|
+
{{snapshot}}. Some of the requirements this run changed
|
|
852
|
+
were NOT approved for change, because people have open stories against them.
|
|
853
|
+
|
|
854
|
+
Restore, and re-express additively: {{preserve_csv}}
|
|
855
|
+
Leave amended as they now stand: {{approved_csv}}
|
|
856
|
+
|
|
857
|
+
Return the COMPLETE document. Rules:
|
|
858
|
+
- Restore each requirement in the FIRST list to EXACTLY its snapshot wording. If one
|
|
859
|
+
was removed, put it back where it was, with its original id.
|
|
860
|
+
- Carry whatever those restored requirements were expressing in NEW requirements
|
|
861
|
+
instead, each taking the next free id in its class. Where a new requirement
|
|
862
|
+
narrows, extends or supersedes a restored one, say so in its text and name the
|
|
863
|
+
id — the pair has to read as one coherent contract, not two rules that contradict
|
|
864
|
+
each other.
|
|
865
|
+
- Requirements in the SECOND list were approved for change. Leave them exactly as
|
|
866
|
+
the current document has them; do not restore them and do not re-express them.
|
|
867
|
+
- Where a restored requirement's change genuinely cannot be expressed without
|
|
868
|
+
contradicting it, leave that ONE amended as it now stands and add a note naming
|
|
869
|
+
it. A contract that states two incompatible things is worse than an amendment
|
|
870
|
+
somebody has to reconcile.
|
|
871
|
+
- Every other part of the document stays exactly as it currently reads. Do not
|
|
872
|
+
touch the "**Version**" header or the "## Change History" table, and leave everything between the AW:COVERAGE markers exactly as it is.
|
|
873
|
+
|
|
874
|
+
# 10f. GRADE WHAT THE RESTORE WROTE. ext_readdit runs after the converge loop has
|
|
875
|
+
# finished, so the requirements it mints to carry the change would otherwise be the
|
|
876
|
+
# only ones in the document never checked for EARS form, testability or coherence —
|
|
877
|
+
# the additive path would produce weaker requirements than the ordinary one, which
|
|
878
|
+
# is precisely backwards, since it is the path taken when people are already
|
|
879
|
+
# building against this contract.
|
|
880
|
+
#
|
|
881
|
+
# Graded over the WHOLE document rather than only the new ids, because the point of
|
|
882
|
+
# a restored requirement plus its new companion is that the pair reads as ONE
|
|
883
|
+
# contract; only a whole-document pass can see that they do. Two iterations, and
|
|
884
|
+
# the patch is forbidden from touching either the restored or the approved
|
|
885
|
+
# requirements — otherwise the fix could re-disturb what the author just protected.
|
|
886
|
+
- id: readdit_check_loop
|
|
887
|
+
condition: "{{preserve_count}} != 0"
|
|
888
|
+
action: loop
|
|
889
|
+
while: "{{readdit_pass}} != true"
|
|
890
|
+
max_iterations: 2
|
|
891
|
+
steps:
|
|
892
|
+
- id: readdit_validate
|
|
893
|
+
action: agent_query
|
|
894
|
+
# The same sources the main validate gets. Without them this pass could not judge the
|
|
895
|
+
# Domain Lift or Experience Lift items at all, so the requirements minted by the
|
|
896
|
+
# ADDITIVE path — the path taken precisely when people are already building against
|
|
897
|
+
# this contract — were graded more thinly than the ones the ordinary path writes.
|
|
898
|
+
context:
|
|
899
|
+
- "{{checklist_file}}"
|
|
900
|
+
- "{{spec_file}}"
|
|
901
|
+
- { path: .appweaver/memory/constitution.domain.md, optional: true }
|
|
902
|
+
- { path: .appweaver/memory/constitution.ui.md, optional: true }
|
|
903
|
+
include_standards: false
|
|
904
|
+
task: |
|
|
905
|
+
The spec in context was just re-authored: some requirements were restored to
|
|
906
|
+
their earlier wording, and NEW requirements were added to carry the change they
|
|
907
|
+
were expressing. That happened after the quality pass, so the new requirements
|
|
908
|
+
have never been graded.
|
|
909
|
+
Restored (must read exactly as they did): {{preserve_csv}}
|
|
910
|
+
Amended with approval: {{approved_csv}}
|
|
911
|
+
Evaluate the document against each checklist item (in context, 1-based in
|
|
912
|
+
order), paying particular attention to the requirements added by the
|
|
913
|
+
re-authoring: EARS form, atomic and testable, stable ids, no implementation
|
|
914
|
+
mechanics. Judge also whether each restored requirement and the new one carrying
|
|
915
|
+
its change read as ONE coherent contract rather than two rules that contradict
|
|
916
|
+
each other. In "failing", quote the offending part per failed item.
|
|
917
|
+
schema: { all_pass: false, failing: [ { n: 2, issue: "<quote>" } ] }
|
|
918
|
+
mock: { all_pass: true, failing: [] }
|
|
919
|
+
outputs:
|
|
920
|
+
- { name: readdit_pass, from: "$.all_pass" }
|
|
921
|
+
- { name: readdit_failing, from: "$.failing" }
|
|
922
|
+
- id: readdit_break
|
|
923
|
+
action: break
|
|
924
|
+
condition: "{{readdit_pass}} == true"
|
|
925
|
+
# Same reason as patch_spec: max_iterations is 2, so iteration 1 grades and stops.
|
|
926
|
+
- id: readdit_patch
|
|
927
|
+
condition: "{{iteration}} != 1"
|
|
928
|
+
action: agent_author
|
|
929
|
+
context:
|
|
930
|
+
- "{{spec_file}}"
|
|
931
|
+
- { path: .appweaver/memory/constitution.domain.md, optional: true }
|
|
932
|
+
- { path: .appweaver/memory/constitution.ui.md, optional: true }
|
|
933
|
+
include_standards: false
|
|
934
|
+
task: |
|
|
935
|
+
Fix these quality failures in the spec in context.
|
|
936
|
+
Failures: {{readdit_failing}}
|
|
937
|
+
Constraints that override the failures if they ever conflict:
|
|
938
|
+
- Do NOT modify these restored requirements — their wording is deliberate and
|
|
939
|
+
people are building against it: {{preserve_csv}}
|
|
940
|
+
- Do NOT modify these approved requirements either: {{approved_csv}}
|
|
941
|
+
- Fix the NEW requirements instead: put them in EARS form, make them atomic and
|
|
942
|
+
testable, and keep each one's stated relationship to the requirement whose
|
|
943
|
+
change it carries.
|
|
944
|
+
- Preserve section order, headings and every requirement id. Do not touch the
|
|
945
|
+
"**Version**" header or the "## Change History" table, and leave everything
|
|
946
|
+
between the AW:COVERAGE markers exactly as it is.
|
|
947
|
+
output_target: { type: file, path: "{{spec_file}}" }
|
|
948
|
+
|
|
949
|
+
# 10f-bis. THE RESTORE'S SPEC STILL FAILS. Same shape, and the same stakes, as the ask
|
|
950
|
+
# after the main quality loop — with one difference that makes it matter more: this
|
|
951
|
+
# is the path taken when people are ALREADY BUILDING against these requirements, and
|
|
952
|
+
# what the loop grades is the brand-new text minted to carry a change the author
|
|
953
|
+
# refused to make in place. Without this, a red restore appended a version row and
|
|
954
|
+
# told those authors their work was reconciled.
|
|
955
|
+
#
|
|
956
|
+
# Two ordered sets rather than a compound condition: the verdict is "nothing to
|
|
957
|
+
# grade" by default, and becomes the loop's own answer only on the path where the
|
|
958
|
+
# loop actually ran. Record is first for the reason it is first above — spec.md is
|
|
959
|
+
# written by now, and a run that refuses the row leaves the unledgered_edit state.
|
|
960
|
+
- id: readdit_verdict_na
|
|
961
|
+
action: set
|
|
962
|
+
vars: { readdit_verdict: "true" }
|
|
963
|
+
- id: readdit_verdict_real
|
|
964
|
+
condition: "{{preserve_count}} != 0"
|
|
965
|
+
action: set
|
|
966
|
+
vars: { readdit_verdict: "{{readdit_pass}}" }
|
|
967
|
+
|
|
968
|
+
- id: readdit_ask
|
|
969
|
+
condition: "{{readdit_verdict}} != true"
|
|
970
|
+
action: ask
|
|
971
|
+
prompt: |
|
|
972
|
+
The re-authored spec still fails its quality pass. These requirements were restored
|
|
973
|
+
to their earlier wording and NEW ones were minted to carry the change instead, and
|
|
974
|
+
the grader is not satisfied with the result:
|
|
975
|
+
Restored: {{preserve_csv}}
|
|
976
|
+
Failing: {{readdit_failing}}
|
|
977
|
+
spec.md is ALREADY WRITTEN either way — the only question is whether this run records
|
|
978
|
+
what it changed.
|
|
979
|
+
options:
|
|
980
|
+
- "Record it — the version row is what keeps the stories in step; fix the spec next"
|
|
981
|
+
- "Stop — write no version row, and repair spec.md by hand first"
|
|
982
|
+
answer_key: readdit_accept
|
|
983
|
+
answer_var: readdit_accept
|
|
984
|
+
|
|
985
|
+
- id: readdit_gate
|
|
986
|
+
condition: "{{readdit_verdict}} != true"
|
|
987
|
+
action: verify_context
|
|
988
|
+
require_value: readdit_accept
|
|
989
|
+
equals: "Record it — the version row is what keeps the stories in step; fix the spec next"
|
|
990
|
+
on_mismatch: stop
|
|
991
|
+
on_mismatch_message: >
|
|
992
|
+
Stopped at your request with no version row written. What the grader found is above,
|
|
993
|
+
and the checklist is at {{checklist_file}}.
|
|
994
|
+
spec.md HAS been rewritten, so the next specify will refuse it as an unledgered edit
|
|
995
|
+
until one of two things is true — add a Change History row naming the ids you changed
|
|
996
|
+
and re-run spec-stamp, or revert the document from the copy taken before this run,
|
|
997
|
+
which is beside it at {{snapshot}}.
|
|
998
|
+
|
|
999
|
+
# 10g. Re-diff after re-authoring AND its quality pass, so the ledger row records what
|
|
1000
|
+
# finally landed rather than what the first pass wrote. Skipped when everything was
|
|
1001
|
+
# approved. Must run after the gate above: a patch there can add or reword a
|
|
1002
|
+
# requirement, and a row computed before it would miss that.
|
|
1003
|
+
- id: ext_rediff
|
|
1004
|
+
condition: "{{preserve_count}} != 0"
|
|
1005
|
+
action: execute_script
|
|
1006
|
+
script: { ref: actions }
|
|
1007
|
+
subcommand: spec-diff
|
|
1008
|
+
args: ["--feature-dir", "{{feature_dir}}", "--snapshot", "{{snapshot}}"]
|
|
1009
|
+
outputs:
|
|
1010
|
+
- { name: changed_csv, from: "$.changed_csv" }
|
|
1011
|
+
- { name: story_ids_csv, from: "$.story_ids_csv" }
|
|
1012
|
+
- { name: removed_csv, from: "$.removed_csv" }
|
|
1013
|
+
- { name: modified_csv, from: "$.modified_csv" }
|
|
1014
|
+
- { name: disturbing_csv, from: "$.disturbing_csv" }
|
|
1015
|
+
- { name: reissued_csv, from: "$.reissued_csv" }
|
|
1016
|
+
- { name: change_count, from: "$.change_count" }
|
|
1017
|
+
|
|
1018
|
+
# 10g-bis. DID THE RESTORE HOLD. ext_readdit is a model asked to put requirements back to
|
|
1019
|
+
# their earlier wording, and until now nothing checked that it did. The re-diff is the
|
|
1020
|
+
# proof: a requirement the author refused to move must have dropped out of the
|
|
1021
|
+
# disturbing set. One still in it was amended WITHOUT approval — and the run would
|
|
1022
|
+
# otherwise close by telling that author, in the same sentence, that it was left alone.
|
|
1023
|
+
#
|
|
1024
|
+
# It announces rather than stops. spec.md is already written by this point, so a stop
|
|
1025
|
+
# here leaves a document on disk with no ledger row, which is the state the next run's
|
|
1026
|
+
# tripwire refuses. Announcing keeps the ledger row honest — the violated ids ARE in
|
|
1027
|
+
# it, so update-stories still reconciles their stories — and moves them out of the
|
|
1028
|
+
# "preserved" column of the report into a column that says what actually happened.
|
|
1029
|
+
- id: ext_restore_check
|
|
1030
|
+
condition: "{{preserve_count}} != 0"
|
|
1031
|
+
action: execute_script
|
|
1032
|
+
script: { ref: actions }
|
|
1033
|
+
subcommand: spec-preserve
|
|
1034
|
+
args: ["--verify", "--preserved", "{{preserve_csv}}", "--disturbing", "{{disturbing_csv}}"]
|
|
1035
|
+
outputs:
|
|
1036
|
+
- { name: held_csv, from: "$.held_csv" }
|
|
1037
|
+
- { name: violated_csv, from: "$.violated_csv" }
|
|
1038
|
+
- { name: restore_ok, from: "$.restore_ok" }
|
|
1039
|
+
|
|
1040
|
+
- id: ext_restore_gate
|
|
1041
|
+
condition: "{{preserve_count}} != 0"
|
|
1042
|
+
action: verify_context
|
|
1043
|
+
require_true: restore_ok
|
|
1044
|
+
on_false: continue
|
|
1045
|
+
on_false_message: >
|
|
1046
|
+
The re-authoring did NOT restore {{violated_csv}} — you declined to change these and
|
|
1047
|
+
they moved anyway. They are recorded in this version's Change History row, so
|
|
1048
|
+
/appweaver update-stories will reconcile the stories citing them rather than leaving
|
|
1049
|
+
them silently stale; check that the wording it landed on is one you can live with.
|
|
1050
|
+
|
|
1051
|
+
# 10h. Re-read the affected stories from the FINAL document. After a restore, the
|
|
1052
|
+
# requirements that were put back disturb nobody any more, and their stories must
|
|
1053
|
+
# drop out of the report — otherwise the run tells people to go and reconcile work
|
|
1054
|
+
# that this run deliberately left alone. Same call as 10b, over what actually landed.
|
|
1055
|
+
# Keyed on disturbed_any, not change_count: a restore that put everything back can leave
|
|
1056
|
+
# the run with nothing changed at all, and on that path a change_count gate skipped this
|
|
1057
|
+
# step and left the report naming the stories from BEFORE the restore — telling people to
|
|
1058
|
+
# go and reconcile work this run deliberately left alone.
|
|
1059
|
+
- id: ext_final_refs
|
|
1060
|
+
condition: "{{disturbed_any}} == true"
|
|
1061
|
+
action: execute_script
|
|
1062
|
+
on_error: continue
|
|
1063
|
+
script: { ref: actions }
|
|
1064
|
+
subcommand: story-refs
|
|
1065
|
+
args: ["--feature-dir", "{{feature_dir}}", "--ids", "{{disturbing_csv}}"]
|
|
1066
|
+
outputs:
|
|
1067
|
+
- { name: disturbed_stories, from: "$.story_csv" }
|
|
1068
|
+
|
|
1069
|
+
# 10i. AN ID THE LEDGER HAS ALREADY SPENT. Ids are permanent: re-issuing a retired one
|
|
1070
|
+
# silently re-points stories, designs and code that have already shipped, which is
|
|
1071
|
+
# why every authoring prompt forbids it — and until now nothing checked. Announced,
|
|
1072
|
+
# not blocked, for the same reason as the restore gate: the document is written by
|
|
1073
|
+
# the time this is visible, and refusing here would strand it. Deciding WHAT to do
|
|
1074
|
+
# about a re-issued id is still the author's, and is not yet automated.
|
|
1075
|
+
- id: ext_reissue_gate
|
|
1076
|
+
condition: "{{change_count}} != 0"
|
|
1077
|
+
action: verify_context
|
|
1078
|
+
require_value: reissued_csv
|
|
1079
|
+
equals: ""
|
|
1080
|
+
on_mismatch: continue
|
|
1081
|
+
on_mismatch_message: >
|
|
1082
|
+
{{reissued_csv}} was introduced by this run, but the Change History has already
|
|
1083
|
+
spent it. An id is permanent — whatever cited it before now points at something
|
|
1084
|
+
else. Renumber the new requirement to the next free id in its class before anyone
|
|
1085
|
+
builds against it.
|
|
1086
|
+
|
|
1087
|
+
# 11. One call writes the row, bumps the version, stamps the header and re-hashes, so
|
|
1088
|
+
# a row cannot exist without the hash that proves the body it describes. Skipped
|
|
1089
|
+
# when the run changed no requirement — a version that names no ids is a bump
|
|
1090
|
+
# update-stories can do nothing with.
|
|
1091
|
+
- id: ext_ledger_append
|
|
1092
|
+
condition: "{{change_count}} != 0"
|
|
1093
|
+
action: execute_script
|
|
1094
|
+
script: { ref: actions }
|
|
1095
|
+
subcommand: ledger-append
|
|
1096
|
+
args: ["--feature-dir", "{{feature_dir}}", "--ids", "{{changed_csv}}", "--summary", "{{USER_INPUT}}"]
|
|
1097
|
+
outputs:
|
|
1098
|
+
- { name: ledger_version, from: "$.version" }
|
|
1099
|
+
|
|
1100
|
+
# ═══════════════ STORY COVERAGE — both paths, before the baseline ══════════
|
|
1101
|
+
# 11b. Rewrite the spec's AW:COVERAGE block: every FR-/BR- in the document and the
|
|
1102
|
+
# stories delivering it, read out of the stories/ frontmatter. The relation is
|
|
1103
|
+
# stored once, in the story; this is that relation the other way round, which is
|
|
1104
|
+
# why the block is regenerated wholesale and hand edits to it are discarded.
|
|
1105
|
+
#
|
|
1106
|
+
# Deliberately OUTSIDE the ledger. The hash guards the CONTRACT, and coverage is a
|
|
1107
|
+
# projection of the backlog — spec-check, spec-diff and ledger-append all cut this
|
|
1108
|
+
# block out before they look. That is what lets user-story and update-stories write
|
|
1109
|
+
# it too without a story creation reading as an unledgered spec edit, or a story
|
|
1110
|
+
# raised against FR-003 reading as FR-003 having changed.
|
|
1111
|
+
#
|
|
1112
|
+
# Run on the bootstrap path as well, where it lists every requirement as uncovered:
|
|
1113
|
+
# a new spec that nobody is building yet is exactly what it should say.
|
|
1114
|
+
- id: coverage_refresh
|
|
1115
|
+
action: execute_script
|
|
1116
|
+
on_error: continue
|
|
1117
|
+
script: { ref: actions }
|
|
1118
|
+
subcommand: spec-coverage
|
|
1119
|
+
args: ["--feature-dir", "{{feature_dir}}"]
|
|
1120
|
+
outputs:
|
|
1121
|
+
- { name: uncovered_csv, from: "$.uncovered_csv" }
|
|
1122
|
+
|
|
1123
|
+
# ═══════════════════ RE-BASELINE — both paths, always last ═════════════════
|
|
1124
|
+
# 12. Record the finished document's hash. Everything above may have rewritten spec.md
|
|
1125
|
+
# after the ledger row was appended (the converge loop on the bootstrap path, and
|
|
1126
|
+
# nothing on the extend path — but the order is what guarantees that), so the hash
|
|
1127
|
+
# is taken here, once, over the document as it will be read next. Without this the
|
|
1128
|
+
# next run's entry tripwire would see a changed body and refuse a spec that is
|
|
1129
|
+
# perfectly in step.
|
|
1130
|
+
# Skipped when the bootstrap's v1.0 row never landed: a hash is a claim that the
|
|
1131
|
+
# document on disk is the one the ledger describes, and there is no ledger row to
|
|
1132
|
+
# describe it. Leaving it unstamped keeps that honest and costs nothing — the
|
|
1133
|
+
# bootstrap marker is still up, so the next run re-authors rather than extends.
|
|
1134
|
+
- id: spec_stamp
|
|
1135
|
+
condition: "{{stamp_hold}} != true"
|
|
411
1136
|
action: execute_script
|
|
412
1137
|
script: { ref: actions }
|
|
413
|
-
subcommand:
|
|
1138
|
+
subcommand: spec-stamp
|
|
414
1139
|
args: ["--feature-dir", "{{feature_dir}}"]
|
|
415
1140
|
outputs:
|
|
416
|
-
- { name: version,
|
|
417
|
-
|
|
1141
|
+
- { name: version, from: "$.version" }
|
|
1142
|
+
|
|
1143
|
+
# 13. Post-execution hooks: resolve (script) then dispatch (invoking agent).
|
|
1144
|
+
# 12b. Say so when it was held. Everything downstream reads `version`, and a first draft
|
|
1145
|
+
# with no row and no hash is not a baseline anything should build on.
|
|
1146
|
+
- id: boot_incomplete_notice
|
|
1147
|
+
condition: "{{stamp_hold}} == true"
|
|
1148
|
+
action: verify_context
|
|
1149
|
+
require_value: stamp_hold
|
|
1150
|
+
equals: "false"
|
|
1151
|
+
on_mismatch: continue
|
|
1152
|
+
on_mismatch_message: >
|
|
1153
|
+
The first draft was written to {{spec_file}}, but its v1.0 Change History row could not
|
|
1154
|
+
be added — so the document is deliberately left UNSTAMPED and still marked an unfinished
|
|
1155
|
+
bootstrap. Do not treat it as a baseline. Re-run /appweaver specify with the same input
|
|
1156
|
+
(it re-authors the draft rather than extending it), or add the v1.0 row by hand naming
|
|
1157
|
+
the requirement ids and re-run spec-stamp.
|
|
418
1158
|
|
|
419
|
-
# 9. Post-execution hooks: resolve (script) then dispatch (invoking agent).
|
|
420
1159
|
- id: resolve_post_hooks
|
|
421
1160
|
action: execute_script
|
|
422
1161
|
script: { ref: actions }
|
|
@@ -432,8 +1171,16 @@ outputs:
|
|
|
432
1171
|
feature_directory: "{{feature_dir}}"
|
|
433
1172
|
spec_file: "{{spec_file}}"
|
|
434
1173
|
version: "{{version}}"
|
|
435
|
-
|
|
436
|
-
|
|
1174
|
+
requirements_changed: "{{changed_csv}}" # every class — what the ledger row records
|
|
1175
|
+
requirements_for_stories: "{{story_ids_csv}}" # the FR-/BR- subset — what update-stories acts on
|
|
1176
|
+
requirements_removed: "{{removed_csv}}"
|
|
1177
|
+
requirements_amended: "{{modified_csv}}" # what finally stands amended, ask or no ask
|
|
1178
|
+
requirements_preserved: "{{held_csv}}" # the ones the restore actually put back
|
|
1179
|
+
requirements_not_restored: "{{violated_csv}}" # declined, but moved anyway
|
|
1180
|
+
requirements_reissued: "{{reissued_csv}}" # ids the ledger had already spent
|
|
1181
|
+
open_change: "{{open_change}}" # a delta change open on this feature, if any
|
|
1182
|
+
requirements_uncovered: "{{uncovered_csv}}"
|
|
1183
|
+
stories_disturbed: "{{disturbed_stories}}"
|
|
437
1184
|
checklist_file: "{{checklist_file}}"
|
|
438
1185
|
checklist_quality: "{{checklist_passed}}/{{checklist_total}} items passing"
|
|
439
|
-
next: "/appweaver
|
|
1186
|
+
next: "spec.md is at {{version}}. Run /appweaver update-stories — it raises or refreshes the backlog for the requirements this run touched that a story can cite ({{story_ids_csv}}), and nothing else keeps the contract and its stories in step. That is the FR-/BR- subset of requirements_changed on purpose: the ledger row records every class this run moved, but only these are slices of work. Then /appweaver design authors design.md from the spec. READ THE FIELDS ABOVE BEFORE YOU DO: requirements_removed orphans every story citing them and needs deciding by hand; requirements_not_restored moved despite you declining them; requirements_reissued point at something other than what cited them before; open_change will be merged into this document by finalize-delta; and checklist_quality is what the spec actually scored."
|