aw-spec-cli 0.1.20 → 0.1.21
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 +8 -3
- package/assets/.appweaver/orchestrator/modules/delta-spec.orch.yaml +22 -1
- package/assets/.appweaver/orchestrator/orchestrator.py +65 -3
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan-delta.orch.yaml +6 -6
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +107 -48
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +17 -9
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +265 -20
- package/assets/.appweaver/orchestrator/workflows/aw-spec-constitution.orch.yaml +4 -4
- package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +89 -15
- package/assets/.appweaver/orchestrator/workflows/aw-spec-foundation.orch.yaml +140 -36
- package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +354 -52
- package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +3 -3
- package/assets/.appweaver/orchestrator/workflows/aw-spec-select-stack.orch.yaml +47 -23
- package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +1 -1
- package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +651 -76
- package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +600 -59
- package/assets/.appweaver/scripts/python/aw-crstate.py +194 -0
- package/assets/.appweaver/scripts/python/aw-foundation.py +151 -0
- package/assets/.appweaver/scripts/python/aw-payload-parity.py +3 -0
- package/assets/.appweaver/scripts/python/aw-plan-coverage.py +423 -0
- package/assets/.appweaver/scripts/python/aw-stack.py +251 -28
- package/assets/.appweaver/scripts/python/aw-testlog.py +148 -0
- package/assets/.appweaver/stack-profiles/dotnet-webapi.json +1 -0
- package/assets/.appweaver/stack-profiles/java-spring.json +1 -0
- package/assets/.appweaver/stack-profiles/node-express.json +1 -0
- package/assets/.appweaver/stack-profiles/python-fastapi.json +1 -0
- package/assets/.appweaver/templates/constitution-domain-template.md +17 -2
- package/assets/guardrails/projects/dotnet-default-project/domain-rule-bindings.md +10 -6
- package/assets/guardrails/projects/dotnet-default-project/tech-rule-bindings.md +4 -0
- package/assets/guardrails/technology/dotnet-webapi/auditing.md +3 -1
- package/assets/guardrails/technology/dotnet-webapi/coding-standards.md +5 -3
- package/assets/guardrails/technology/dotnet-webapi/cqrs.md +5 -1
- package/assets/guardrails/technology/dotnet-webapi/domain-driven-design.md +13 -5
- package/assets/guardrails/technology/dotnet-webapi/examples/cqrs.md +2 -0
- package/assets/guardrails/technology/dotnet-webapi/examples/domain-driven-design.md +6 -2
- package/assets/guardrails/technology/dotnet-webapi/framework-api-map.md +70 -0
- package/assets/guardrails/technology/dotnet-webapi/technology-constitution.md +3 -0
- package/assets/guardrails/technology/dotnet-webapi/testing.md +10 -0
- package/assets/guardrails/technology/nextjs/ui-constitution.md +8 -1
- package/package.json +1 -1
- package/assets/.appweaver/orchestrator/.state/smoke.state.json +0 -32
|
@@ -90,7 +90,7 @@ the raw object, not its JSON string.
|
|
|
90
90
|
| `set` | engine | assign vars (`vars:` or decl-style `parameters:`); raw-object aware |
|
|
91
91
|
| `build_context` | engine | REPLACE the per-request envelope: `system:`/`context:` file lists (+ `context_from: <var>` holding paths a prior generation reported, + `vars:`) |
|
|
92
92
|
| `load_standards` | engine | F4 — append to the **sticky standards set** prepended to every system block (dedup, `optional:`, `files:`/`files_from_variable`). This is the byte-stable, prompt-cacheable prefix |
|
|
93
|
-
| `execute_script` | engine | F1 — run a `scripts:` ref (`subcommand:` + `args:`; sh/ps1 via `--os`); stdout's last line = JSON, bound via `outputs[]`; `on_error: continue`
|
|
93
|
+
| `execute_script` | engine | F1 — run a `scripts:` ref (`subcommand:` + `args:`; sh/ps1 via `--os`); stdout's last line = JSON, bound via `outputs[]`; `on_error: continue` tolerates a script that ran and exited non-zero (its outputs bind empty) — never an undeclared ref, which the pre-flight refuses before step 1; cached across resumes; runs for real even in mock transport |
|
|
94
94
|
| `verify_context` | engine | F2 — gates: `required_files` (+`allow_missing`), `require_non_empty` (+`on_empty[_message]`), `require_true` (+`on_false[_message]`), `require_value`+`equals` (+`on_mismatch[_message]`). `stop` raises a terminal stop |
|
|
95
95
|
| `agent_generate` | fresh agent | demo contract (code work): agent DECIDES file paths per the loaded standards, writes with its tools, reports `files[]` in the trailing ```json fence. `sdk-apply` = read-only generation + invoking agent applies |
|
|
96
96
|
| `agent_review` | fresh agent | payload-only review of `review_of_var`/`review_of` files (inlined into THIS request, never sticky) → `{pass, issues}` |
|
|
@@ -159,8 +159,13 @@ orchestrator.py <workflow.orch.yaml>
|
|
|
159
159
|
## Validation & smoke
|
|
160
160
|
|
|
161
161
|
- `--validate` every workflow/module (unknown actions/strategies, duplicate
|
|
162
|
-
ids, script refs + files on disk
|
|
163
|
-
|
|
162
|
+
ids, script refs + files on disk — including the refs of every included
|
|
163
|
+
module, which resolve against the caller's `scripts:` — `output_target.path`,
|
|
164
|
+
module paths, unproduced `{{vars}}`, dialect header).
|
|
165
|
+
- Every run starts with the same checks. A workflow with an ERROR is not
|
|
166
|
+
started: the engine exits 1 with `status: stopped` and the defects in the
|
|
167
|
+
reason, before any step runs and before a checkpoint response is ingested.
|
|
168
|
+
Warnings never block.
|
|
164
169
|
- `workflows/_smoke.orch.yaml` exercises every construct with no model and no
|
|
165
170
|
writes outside `.smoke-out`: run it `--non-interactive` (CI) and with
|
|
166
171
|
`--checkpoint` (protocol walk-through).
|
|
@@ -140,7 +140,7 @@ workflow:
|
|
|
140
140
|
touches the same aggregate or the same FR ids, say so under "Other open deltas"
|
|
141
141
|
in Impact. If THIS change only makes sense after one of them lands, say so in the
|
|
142
142
|
Wave rationale — that is a wave dependency, and the wave must then be RAISED above
|
|
143
|
-
that delta's
|
|
143
|
+
that delta's — name it under `## Depends on` and the wave is raised from that. Do not silently assume their
|
|
144
144
|
changes: until merged they are proposals, not the master.
|
|
145
145
|
- Do NOT edit the Wave / Created / Base version headers — scripts stamp those.
|
|
146
146
|
Change request: {{USER_INPUT}}
|
|
@@ -183,4 +183,25 @@ workflow:
|
|
|
183
183
|
entry MUST sit in a LOWER wave than this delta ({{wave}}). "None" when it stands
|
|
184
184
|
alone. Reading another delta is NOT depending on it — only assuming its changes is.
|
|
185
185
|
- Do NOT edit the Wave / Created / Base context version headers — scripts stamp those.
|
|
186
|
+
Naming a delta under "## Depends on" is what RAISES this change's wave, immediately
|
|
187
|
+
after you write it; you never set the number yourself.
|
|
186
188
|
Change request: {{USER_INPUT}}
|
|
189
|
+
|
|
190
|
+
# 4. THE WAVE THE DOCUMENTS THEMSELVES IMPLY. cr-setup stamped a provisional wave before
|
|
191
|
+
# either half existed — the highest already open, which is right for a change that
|
|
192
|
+
# stands alone and wrong for one that builds on another: it lands at its predecessor's
|
|
193
|
+
# wave, where the blocker fence reads the two as independent and lets them merge in
|
|
194
|
+
# either order. Now that "## Depends on" is written, that declaration decides: the wave
|
|
195
|
+
# is raised to one above the highest delta named there, in BOTH halves, so the number
|
|
196
|
+
# and the prose cannot drift apart. A change that names none keeps what it has, and one
|
|
197
|
+
# already sitting higher is left alone. The merge fence re-checks the same rule.
|
|
198
|
+
- id: derive_wave
|
|
199
|
+
action: execute_script
|
|
200
|
+
on_error: continue
|
|
201
|
+
script: { ref: actions }
|
|
202
|
+
subcommand: cr-wave-derive
|
|
203
|
+
args: ["--cr-id", "{{cr_id}}", "--stamp"]
|
|
204
|
+
outputs:
|
|
205
|
+
- { name: wave, from: "$.wave" }
|
|
206
|
+
- { name: wave_depends, from: "$.depends_on" }
|
|
207
|
+
- { name: wave_message, from: "$.message" }
|
|
@@ -1458,6 +1458,39 @@ def _refs_in(v):
|
|
|
1458
1458
|
return found
|
|
1459
1459
|
|
|
1460
1460
|
|
|
1461
|
+
def _module_script_refs(mpath, base_dir, seen):
|
|
1462
|
+
"""(step id, script ref) for every execute_script step an included module runs,
|
|
1463
|
+
following nested includes. Modules that cannot be read contribute nothing — the
|
|
1464
|
+
include check already warns about a module missing from disk."""
|
|
1465
|
+
path = next((c for c in (mpath, os.path.join(base_dir, mpath)) if c and os.path.exists(c)), "")
|
|
1466
|
+
if not path or path in seen:
|
|
1467
|
+
return []
|
|
1468
|
+
seen.add(path)
|
|
1469
|
+
try:
|
|
1470
|
+
with open(path, encoding="utf-8") as fh:
|
|
1471
|
+
mwf = load_yaml(fh.read())
|
|
1472
|
+
except Exception:
|
|
1473
|
+
return []
|
|
1474
|
+
out = []
|
|
1475
|
+
|
|
1476
|
+
def walk(ss):
|
|
1477
|
+
for s in ss or []:
|
|
1478
|
+
if not isinstance(s, dict):
|
|
1479
|
+
continue
|
|
1480
|
+
p = s.get("parameters") or s
|
|
1481
|
+
if s.get("action") == "execute_script":
|
|
1482
|
+
ref = (p.get("script") or {}).get("ref")
|
|
1483
|
+
if ref:
|
|
1484
|
+
out.append((s.get("id") or "step", ref))
|
|
1485
|
+
elif s.get("action") == "include" and s.get("module"):
|
|
1486
|
+
out.extend(_module_script_refs(s["module"], base_dir, seen))
|
|
1487
|
+
elif s.get("action") == "loop":
|
|
1488
|
+
walk(s.get("steps"))
|
|
1489
|
+
|
|
1490
|
+
walk((mwf or {}).get("workflow"))
|
|
1491
|
+
return out
|
|
1492
|
+
|
|
1493
|
+
|
|
1461
1494
|
def validate_workflow(wf, base_dir):
|
|
1462
1495
|
errors, warnings = [], []
|
|
1463
1496
|
if not isinstance(wf, dict):
|
|
@@ -1551,6 +1584,13 @@ def validate_workflow(wf, base_dir):
|
|
|
1551
1584
|
errors.append(f"{loc}: include needs 'module'")
|
|
1552
1585
|
elif not (os.path.exists(m) or os.path.exists(os.path.join(base_dir, m))):
|
|
1553
1586
|
warnings.append(f"{loc}: include module not found on disk: {m}")
|
|
1587
|
+
elif scripts:
|
|
1588
|
+
# A module declares no scripts: of its own — its refs resolve against
|
|
1589
|
+
# THIS workflow's map, so an undeclared one is this workflow's defect.
|
|
1590
|
+
for mid, ref in _module_script_refs(m, base_dir, set()):
|
|
1591
|
+
if ref not in scripts:
|
|
1592
|
+
errors.append(f"{loc}: included step {mid} uses script ref "
|
|
1593
|
+
f"'{ref}', not declared in top-level scripts:")
|
|
1554
1594
|
if action == "agent_query" and not step.get("outputs"):
|
|
1555
1595
|
warnings.append(f"{loc}: agent_query has no outputs[] to bind")
|
|
1556
1596
|
if action == "agent_author" and not (p.get("output_target") or {}).get("path"):
|
|
@@ -1682,6 +1722,31 @@ def main(argv=None):
|
|
|
1682
1722
|
print(f"\n{opts.workflow}: {len(errors)} error(s), {len(warnings)} warning(s)")
|
|
1683
1723
|
return 1 if errors else 0
|
|
1684
1724
|
|
|
1725
|
+
# ---- pre-flight: a workflow --validate rejects is never started ----------
|
|
1726
|
+
# An undeclared script ref, an unknown action or a duplicate step id is a defect in
|
|
1727
|
+
# the workflow file, and no run can recover from it — `on_error: continue` covers a
|
|
1728
|
+
# script that RAN and failed, not one the workflow never declared. Found mid-run, it
|
|
1729
|
+
# strands the operator after the questions were answered; found here, nothing has
|
|
1730
|
+
# happened yet. Runs BEFORE the checkpoint ingest, so a refused resume leaves the
|
|
1731
|
+
# pending response on disk for the run after the fix. Warnings do not block.
|
|
1732
|
+
try:
|
|
1733
|
+
with open(opts.workflow, encoding="utf-8") as fh:
|
|
1734
|
+
wf = load_yaml(fh.read())
|
|
1735
|
+
errors, _ = validate_workflow(wf, os.path.dirname(os.path.abspath(opts.workflow)))
|
|
1736
|
+
except Exception as e:
|
|
1737
|
+
wf, errors = None, [f"parse: {e}"]
|
|
1738
|
+
if errors:
|
|
1739
|
+
reason = (f"workflow {opts.workflow} has {len(errors)} defect(s) and was not started: "
|
|
1740
|
+
+ "; ".join(errors)
|
|
1741
|
+
+ " — fix the workflow file (python3 .appweaver/orchestrator/orchestrator.py "
|
|
1742
|
+
"<workflow> --validate lists them) and re-run.")
|
|
1743
|
+
if opts.json:
|
|
1744
|
+
print(json.dumps({"status": "stopped", "reason": reason, "errors": errors}, indent=2))
|
|
1745
|
+
else:
|
|
1746
|
+
print("\n=== STOPPED ===")
|
|
1747
|
+
print(reason)
|
|
1748
|
+
return 1
|
|
1749
|
+
|
|
1685
1750
|
# ---- checkpoint mode: load prior state + ingest the agent's response ----
|
|
1686
1751
|
# (before profile/strategy resolution, so a bare `--checkpoint` resume
|
|
1687
1752
|
# restores the original run's profile/strategy/model/os and prefills)
|
|
@@ -1787,9 +1852,6 @@ def main(argv=None):
|
|
|
1787
1852
|
print("ERROR: --transport real needs --strategy (or --agent-profile / --checkpoint)")
|
|
1788
1853
|
return 2
|
|
1789
1854
|
|
|
1790
|
-
with open(opts.workflow, encoding="utf-8") as fh:
|
|
1791
|
-
wf = load_yaml(fh.read())
|
|
1792
|
-
|
|
1793
1855
|
orch = Orchestrator(wf, opts)
|
|
1794
1856
|
try:
|
|
1795
1857
|
result = orch.run()
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
# is visible so it can be designed around, and building on it silently is the defect this
|
|
37
37
|
# check exists to catch.
|
|
38
38
|
#
|
|
39
|
-
# OVERLAP IS COMPUTED, NOT ASKED. cr-overlap reads the
|
|
40
|
-
# pending model delta and reports which other open changes touch the same aggregates.
|
|
39
|
+
# OVERLAP IS COMPUTED, NOT ASKED. cr-overlap reads the `**Aggregates touched**:` header of
|
|
40
|
+
# every pending model delta and reports which other open changes touch the same aggregates.
|
|
41
41
|
# That is a fact about two files, so a regex answers it exactly and the result is handed
|
|
42
42
|
# to the model as a finding rather than as a question.
|
|
43
43
|
#
|
|
@@ -229,9 +229,9 @@ workflow:
|
|
|
229
229
|
- { name: sibling_count, from: "$.sibling_count" }
|
|
230
230
|
- { name: domain_model, from: "$.domain_model" }
|
|
231
231
|
|
|
232
|
-
# 3b. WHICH OTHER OPEN CHANGES TOUCH THE SAME AGGREGATES. Deterministic:
|
|
233
|
-
#
|
|
234
|
-
# domain-model.md
|
|
232
|
+
# 3b. WHICH OTHER OPEN CHANGES TOUCH THE SAME AGGREGATES. Deterministic: each pending
|
|
233
|
+
# model delta's `**Aggregates touched**:` header names the `## Aggregate:` sections
|
|
234
|
+
# of domain-model.md it merges into, so this is a regex over two files rather than a
|
|
235
235
|
# judgement. The result reaches the check below as a FINDING, not a question.
|
|
236
236
|
- id: cr_overlap
|
|
237
237
|
action: execute_script
|
|
@@ -645,7 +645,7 @@ workflow:
|
|
|
645
645
|
action: verify_context
|
|
646
646
|
require_true: analyze_ok
|
|
647
647
|
on_false: stop
|
|
648
|
-
on_false_message: "The delta plans for {{cr_id}} do not realise the delta spec (gaps: {{analyze_gaps}}) — repair was skipped (see {{cr_dir}}/delta-plan-analyze-report.md), or it could not be reconciled in 3 passes. A 'LIFT-FAILURE:' gap means a domain constraint from the gate checklist was never lifted into the delta spec, and a `model` gap means a plan needs structure the effective model does not define — both are fixed by /appweaver specify, which authors the delta spec and the model delta; this gate may not write either. A `wave` gap is closed by declaring '## Depends on'
|
|
648
|
+
on_false_message: "The delta plans for {{cr_id}} do not realise the delta spec (gaps: {{analyze_gaps}}) — repair was skipped (see {{cr_dir}}/delta-plan-analyze-report.md), or it could not be reconciled in 3 passes. A 'LIFT-FAILURE:' gap means a domain constraint from the gate checklist was never lifted into the delta spec, and a `model` gap means a plan needs structure the effective model does not define — both are fixed by /appweaver specify, which authors the delta spec and the model delta; this gate may not write either. A `wave` gap is closed by declaring '## Depends on' — which is what sets the wave, since specify derives it from that section and the merge fence re-checks it — or by planning around the sibling. Otherwise fix out of band, or re-run and choose Repair."
|
|
649
649
|
|
|
650
650
|
# 8. Post-execution hooks.
|
|
651
651
|
- id: resolve_post_hooks
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
# and that is the act a change in flight forbids — the delta docs are the authoritative
|
|
31
31
|
# edit and the masters must not drift out from under them before finalize-delta merges.
|
|
32
32
|
# MASTER mode (no change open) — the full gate: check, ask, repair, and STOP on gaps
|
|
33
|
-
#
|
|
33
|
+
# a plan rewrite could have closed but did not. spec.md + the lane plans
|
|
34
|
+
# are the masters.
|
|
34
35
|
# REPORT mode (a change is open) — the same checks, read-only: no ask, no repair, no
|
|
35
36
|
# stop. It writes plan-analyze-report.md and routes on. The plans are
|
|
36
37
|
# DERIVED from spec.md + domain-model.md, so after finalize-delta merged a
|
|
@@ -40,6 +41,18 @@
|
|
|
40
41
|
# to be stale, and a stage that refuses to look is not a gate. It reports; it never writes
|
|
41
42
|
# a plan.
|
|
42
43
|
#
|
|
44
|
+
# TWO KINDS OF GAP, SPLIT BY WHO CAN CLOSE THEM (plan-gaps). A PLAN gap is closed by
|
|
45
|
+
# rewriting a lane plan, which this stage may do. A ROUTED gap can only be closed in
|
|
46
|
+
# spec.md or domain-model.md, which this stage may not write: a LIFT-FAILURE (a domain
|
|
47
|
+
# constraint the spec never lifted), structure the model does not define, behaviour the
|
|
48
|
+
# spec does not describe. Only plan gaps drive the repair loop and the stop; routed gaps
|
|
49
|
+
# are reported and handed to /appweaver specify. Stopping on them would stop every feature
|
|
50
|
+
# whose spec missed a constraint, with no repair this stage could ever make.
|
|
51
|
+
#
|
|
52
|
+
# EVERY MASTER RUN LEAVES A RECEIPT (.aw-plan-gate.json), green or not: the bytes it
|
|
53
|
+
# checked and the verdict — conformant, routed-gaps or gaps. implement reads it and
|
|
54
|
+
# refuses moved bytes or `gaps`, and warns on `routed-gaps`.
|
|
55
|
+
#
|
|
43
56
|
# WHICH MODE IS READ FROM THE DIRECTORY, AND DEFAULTS TO REPORT. changes/<id>/ is the
|
|
44
57
|
# evidence (cr-list), not active_version.json's `active_change` — the ledger is the most
|
|
45
58
|
# recently OPENED change and a crashed run can leave the two disagreeing. The permission to
|
|
@@ -60,7 +73,7 @@
|
|
|
60
73
|
|
|
61
74
|
name: aw-spec-analyze-plan
|
|
62
75
|
description: The spec↔plan conformance gate for every declared lane — confirm backend-plan.md and frontend-plan.md follow spec.md, that they bind to each other's contract, and that neither contradicts the bounded context's domain model (domain overlays are a backstop); with approval, repair the drifting plan. Lanes come from .appweaver/stacks.json, so one command gates backend-only, frontend-only and full-stack projects. Runs in one of two modes, decided from the changes/ directory rather than the ledger and defaulting to the read-only one — MASTER (no change open) checks, repairs on approval and stops on unreconciled gaps; REPORT (a change open) runs the same checks read-only and writes plan-analyze-report.md without asking, repairing or stopping, because the master plans may not be rewritten while a delta is in flight. It never opens, merges or closes a change.
|
|
63
|
-
version: 3.
|
|
76
|
+
version: 3.3.0
|
|
64
77
|
agent_agnostic: true
|
|
65
78
|
dialect: aw-spec.orchestrator/v1
|
|
66
79
|
|
|
@@ -68,6 +81,9 @@ scripts:
|
|
|
68
81
|
actions:
|
|
69
82
|
sh: .appweaver/scripts/bash/aw-spec-actions.sh
|
|
70
83
|
ps1: .appweaver/scripts/powershell/aw-spec-actions.ps1
|
|
84
|
+
xref:
|
|
85
|
+
sh: .appweaver/scripts/bash/aw-xref.sh
|
|
86
|
+
ps1: .appweaver/scripts/powershell/aw-xref.ps1
|
|
71
87
|
|
|
72
88
|
standards:
|
|
73
89
|
- { path: .appweaver/ai-rules.md } # kit-resident: the universal operating rules, every flow
|
|
@@ -87,8 +103,15 @@ vars:
|
|
|
87
103
|
no_open_change: "false" # RAISED only by cr-list answering that nothing is open
|
|
88
104
|
gate_mode: "report" # report | master — reported in the outputs
|
|
89
105
|
may_repair: "false" # the ask, the repair loop and the conformance stop are MASTER-only
|
|
90
|
-
gate_ok: "false" # raised by a
|
|
91
|
-
stamp_receipt: "false" # master mode
|
|
106
|
+
gate_ok: "false" # raised by a run with no plan gap left OR by report mode, which cannot repair
|
|
107
|
+
stamp_receipt: "false" # master mode ⇒ leave the receipt implement reads, whatever the verdict
|
|
108
|
+
# THE GAP SPLIT, from plan-gaps after each analyze pass. Defaulted to the refusing
|
|
109
|
+
# answer, so a classification that never arrived neither ends the loop nor passes.
|
|
110
|
+
plan_ok: "false" # no gap left that a plan rewrite closes
|
|
111
|
+
gate_verdict: "gaps" # conformant | routed-gaps | gaps
|
|
112
|
+
plan_gap_count: "0"
|
|
113
|
+
routed_gaps: "[]" # the gaps only /appweaver specify can close
|
|
114
|
+
routed_csv: ""
|
|
92
115
|
receipt_file: ""
|
|
93
116
|
receipt_gated: ""
|
|
94
117
|
receipt_count: "0"
|
|
@@ -358,19 +381,20 @@ workflow:
|
|
|
358
381
|
- { path: "{{be_plan_ctx}}", optional: true }
|
|
359
382
|
- { path: "{{fe_plan_ctx}}", optional: true }
|
|
360
383
|
|
|
361
|
-
# 5. Conformance + REPAIR loop (spec is the anchor): check →
|
|
362
|
-
# Repair on approval → re-check; "Report and Skip" writes plan-analyze-report.md
|
|
363
|
-
# and stops. Bounded (3×). The
|
|
364
|
-
#
|
|
384
|
+
# 5. Conformance + REPAIR loop (spec is the anchor): check → split the gaps → REPORT +
|
|
385
|
+
# ASK → Repair on approval → re-check; "Report and Skip" writes plan-analyze-report.md
|
|
386
|
+
# and stops. Bounded (3×). The loop runs while a PLAN gap is open: once only routed
|
|
387
|
+
# gaps remain there is nothing a repair can change. The repair is PER LANE, so a
|
|
388
|
+
# frontend drift never rewrites a conformant backend plan.
|
|
365
389
|
- id: analyze_loop
|
|
366
390
|
action: loop
|
|
367
|
-
while: "{{
|
|
391
|
+
while: "{{plan_ok}} != true"
|
|
368
392
|
max_iterations: 3
|
|
369
393
|
steps:
|
|
370
394
|
- id: analyze
|
|
371
395
|
action: agent_query
|
|
372
396
|
include_standards: true # the gate overlays (checklist) are in the standards
|
|
373
|
-
schema: { ok: true, backend_repair: false, frontend_repair: false, gaps: [ { severity: "high", check: "<spec-backend|spec-frontend|contract|model|overlay>", lane: "<backend|frontend>", spec_ref: "<FR/BR/CN/DC/IC/UX/US/SC or overlay or —>", where: "<plan section>", gap: "<how it drifts>" } ] }
|
|
397
|
+
schema: { ok: true, backend_repair: false, frontend_repair: false, gaps: [ { severity: "high", check: "<spec-backend|spec-frontend|contract|model|overlay>", lane: "<backend|frontend>", route: "<plan|specify>", spec_ref: "<FR/BR/CN/DC/IC/UX/US/SC or overlay or —>", where: "<plan section>", gap: "<how it drifts>" } ] }
|
|
374
398
|
mock: { ok: true, backend_repair: false, frontend_repair: false, gaps: [] }
|
|
375
399
|
task: |
|
|
376
400
|
spec.md is the ANCHOR. Gate this feature's LANE PLANS against it and against the
|
|
@@ -444,15 +468,37 @@ workflow:
|
|
|
444
468
|
/appweaver specify, not the plan — do not fabricate it here).
|
|
445
469
|
|
|
446
470
|
Tag every gap with its `check` and the `lane` whose document must change to close
|
|
447
|
-
it
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
471
|
+
it, and with its `route` — who can close it:
|
|
472
|
+
- route `specify` when ONLY spec.md or domain-model.md can close it: every
|
|
473
|
+
LIFT-FAILURE; a plan element needing an aggregate, entity, event or invariant
|
|
474
|
+
the model does not define; a state or behaviour the plan needs that the spec
|
|
475
|
+
does not describe and no DA-n records.
|
|
476
|
+
- route `plan` for everything a rewrite of a lane plan closes: a missing plan
|
|
477
|
+
home, a name that differs from the model's, an invariant restated instead of
|
|
478
|
+
cited, a contract binding, an element with no spec basis.
|
|
479
|
+
Set backend_repair true if any `plan`-route gap needs the backend plan rewritten,
|
|
480
|
+
and frontend_repair likewise. (If a plan is still the empty template, treat the
|
|
481
|
+
WHOLE spec as gaps against it.) Set ok=true ONLY when every applicable check
|
|
482
|
+
passes and no checklist constraint is unaccounted for.
|
|
451
483
|
outputs:
|
|
452
484
|
- { name: analyze_ok, from: "$.ok" }
|
|
453
485
|
- { name: analyze_gaps, from: "$.gaps" }
|
|
454
486
|
- { name: backend_repair, from: "$.backend_repair" }
|
|
455
487
|
- { name: frontend_repair, from: "$.frontend_repair" }
|
|
488
|
+
|
|
489
|
+
# WHO CAN CLOSE EACH GAP, decided by script from the tags above: a LIFT-FAILURE is
|
|
490
|
+
# routed to specify whatever its tag says, and an untagged gap is a plan gap.
|
|
491
|
+
- id: classify_gaps
|
|
492
|
+
action: execute_script
|
|
493
|
+
script: { ref: actions }
|
|
494
|
+
subcommand: plan-gaps
|
|
495
|
+
args: ["--gaps", "{{analyze_gaps}}"]
|
|
496
|
+
outputs:
|
|
497
|
+
- { name: plan_ok, from: "$.plan_ok" }
|
|
498
|
+
- { name: gate_verdict, from: "$.verdict" }
|
|
499
|
+
- { name: plan_gap_count, from: "$.plan_gaps" }
|
|
500
|
+
- { name: routed_gaps, from: "$.routed" }
|
|
501
|
+
- { name: routed_csv, from: "$.routed_csv" }
|
|
456
502
|
- id: conform_break
|
|
457
503
|
action: break
|
|
458
504
|
condition: "{{analyze_ok}} == true"
|
|
@@ -495,12 +541,20 @@ workflow:
|
|
|
495
541
|
action: break
|
|
496
542
|
condition: "{{may_repair}} == false"
|
|
497
543
|
|
|
544
|
+
# Only routed gaps left: no repair can change them, so do not ask for one.
|
|
545
|
+
- id: routed_only_break
|
|
546
|
+
action: break
|
|
547
|
+
condition: "{{plan_ok}} == true"
|
|
548
|
+
|
|
498
549
|
- id: ask_repair
|
|
499
550
|
action: ask
|
|
500
551
|
prompt: |
|
|
501
552
|
analyze-plan found spec↔plan gaps (spec.md is the anchor; domain-model.md is
|
|
502
553
|
read-only here):
|
|
503
554
|
{{analyze_gaps}}
|
|
555
|
+
{{plan_gap_count}} gap(s) have route `plan` — a repair rewrites the plan to close
|
|
556
|
+
them. Gaps with route `specify` are not repaired here; they are reported and
|
|
557
|
+
belong to /appweaver specify.
|
|
504
558
|
Repair the drifting plan(s) to conform now, or report the gaps and stop?
|
|
505
559
|
options: ["Repair", "Report and Skip"]
|
|
506
560
|
answer_var: repair_choice
|
|
@@ -553,7 +607,8 @@ workflow:
|
|
|
553
607
|
output_target: { type: file, path: "{{backend_plan}}" }
|
|
554
608
|
task: |
|
|
555
609
|
spec.md is the ANCHOR — update backend-plan.md (in context) to CONFORM to it,
|
|
556
|
-
closing the gaps whose lane is `backend` (the spec WINS
|
|
610
|
+
closing the gaps whose lane is `backend` and whose route is `plan` (the spec WINS
|
|
611
|
+
every time). Leave `specify`-route gaps as the DA-n notes described below:
|
|
557
612
|
{{analyze_gaps}}
|
|
558
613
|
- Add a plan home for each uncovered spec FR/BR/CN/DC/IC/US/SC (a lifted
|
|
559
614
|
CN-/DC-/IC- lands in a §1 domain invariant enforcement · the persistence
|
|
@@ -591,7 +646,9 @@ workflow:
|
|
|
591
646
|
output_target: { type: file, path: "{{frontend_plan}}" }
|
|
592
647
|
task: |
|
|
593
648
|
spec.md is the ANCHOR — update frontend-plan.md (in context) to CONFORM to it,
|
|
594
|
-
closing the gaps whose lane is `frontend` (the spec WINS
|
|
649
|
+
closing the gaps whose lane is `frontend` and whose route is `plan` (the spec WINS
|
|
650
|
+
every time). Leave each `specify`-route gap open, logged as a DA-n that routes to
|
|
651
|
+
/appweaver specify:
|
|
595
652
|
{{analyze_gaps}}
|
|
596
653
|
- Give every uncovered Experience Standard (UX-), user story and
|
|
597
654
|
interaction-state FR- a home: a screen in the navigation section, a flow row,
|
|
@@ -618,52 +675,51 @@ workflow:
|
|
|
618
675
|
already-conformant section VERBATIM.
|
|
619
676
|
Return the COMPLETE updated frontend-plan.md content only.
|
|
620
677
|
|
|
621
|
-
# THE STOP IS MASTER MODE'S ALONE. Two independent raises, because the two reasons to
|
|
622
|
-
# pass are unrelated: the plans conform, or this run was never allowed to change them.
|
|
623
|
-
# A read-only run that found gaps has done its whole job by reporting them — stopping
|
|
624
|
-
# would fail the operator for drift that finalize-delta created on purpose and that only
|
|
625
|
-
# /appweaver plan main can close.
|
|
626
|
-
- id: gate_ok_conformant
|
|
627
|
-
condition: "{{analyze_ok}} == true"
|
|
628
|
-
action: set
|
|
629
|
-
vars: { gate_ok: "true" }
|
|
630
|
-
- id: gate_ok_readonly
|
|
631
|
-
condition: "{{may_repair}} == false"
|
|
632
|
-
action: set
|
|
633
|
-
vars: { gate_ok: "true" }
|
|
634
|
-
|
|
635
|
-
- id: analyze_gate
|
|
636
|
-
action: verify_context
|
|
637
|
-
require_true: gate_ok
|
|
638
|
-
on_false: stop
|
|
639
|
-
on_false_message: "The lane plans do not conform to the spec (gaps: {{analyze_gaps}}) — repair was skipped (see plan-analyze-report.md), or it could not be reconciled in 3 passes. A 'LIFT-FAILURE:' gap means a domain constraint from the gate checklist was never lifted into the spec, and a `model` gap means the plan needs an entity or invariant domain-model.md does not define — both are fixed by /appweaver specify, which authors the spec and the model; this gate may not write either. A `contract` gap is closed in the lane its row names. Otherwise fix out of band, or re-run and choose Repair."
|
|
640
|
-
|
|
641
678
|
# THE RECEIPT. implement generates from the plans and deliberately does not read the
|
|
642
|
-
# spec, on the strength of this stage having
|
|
643
|
-
#
|
|
644
|
-
#
|
|
645
|
-
# that was present. plan-gate-check reads it at implement.
|
|
679
|
+
# spec, on the strength of this stage having checked them against it. Record WHAT was
|
|
680
|
+
# checked — the bytes of spec.md, the context's domain-model.md and each lane plan that
|
|
681
|
+
# was present — and the VERDICT on them. plan-gate-check reads it at implement.
|
|
646
682
|
#
|
|
647
|
-
#
|
|
648
|
-
#
|
|
649
|
-
#
|
|
650
|
-
# even repair them), and a red master run proved the opposite.
|
|
683
|
+
# EVERY MASTER RUN LEAVES ONE, and it is written BEFORE the stop below, so a red run
|
|
684
|
+
# records its `gaps` verdict too and implement refuses it. A REPORT-mode run leaves
|
|
685
|
+
# none: it checked the masters while a delta was in flight, and they are about to move.
|
|
651
686
|
- id: flag_receipt
|
|
652
687
|
condition: "{{may_repair}} == true"
|
|
653
688
|
action: set
|
|
654
|
-
vars: { stamp_receipt: "
|
|
689
|
+
vars: { stamp_receipt: "true" }
|
|
655
690
|
|
|
656
691
|
- id: plan_gate_stamp
|
|
657
692
|
condition: "{{stamp_receipt}} == true"
|
|
658
693
|
action: execute_script
|
|
659
694
|
script: { ref: actions }
|
|
660
695
|
subcommand: plan-gate-stamp
|
|
661
|
-
args: ["--feature-dir", "{{feature_dir}}", "--spec-version", "{{version}}", "--mode", "{{gate_mode}}"]
|
|
696
|
+
args: ["--feature-dir", "{{feature_dir}}", "--spec-version", "{{version}}", "--mode", "{{gate_mode}}", "--verdict", "{{gate_verdict}}", "--plan-gaps", "{{plan_gap_count}}", "--routed", "{{routed_gaps}}"]
|
|
662
697
|
outputs:
|
|
663
698
|
- { name: receipt_file, from: "$.file" }
|
|
664
699
|
- { name: receipt_gated, from: "$.gated" }
|
|
665
700
|
- { name: receipt_count, from: "$.count" }
|
|
666
701
|
|
|
702
|
+
# THE STOP IS MASTER MODE'S ALONE, and only a PLAN gap triggers it. Two independent
|
|
703
|
+
# raises, because the two reasons to pass are unrelated: no gap is left that a plan
|
|
704
|
+
# rewrite closes, or this run was never allowed to rewrite. A read-only run that found
|
|
705
|
+
# gaps has done its whole job by reporting them — stopping would fail the operator for
|
|
706
|
+
# drift that finalize-delta created on purpose and that only /appweaver plan main can
|
|
707
|
+
# close. Routed gaps pass here and are listed in the outputs for /appweaver specify.
|
|
708
|
+
- id: gate_ok_conformant
|
|
709
|
+
condition: "{{plan_ok}} == true"
|
|
710
|
+
action: set
|
|
711
|
+
vars: { gate_ok: "true" }
|
|
712
|
+
- id: gate_ok_readonly
|
|
713
|
+
condition: "{{may_repair}} == false"
|
|
714
|
+
action: set
|
|
715
|
+
vars: { gate_ok: "true" }
|
|
716
|
+
|
|
717
|
+
- id: analyze_gate
|
|
718
|
+
action: verify_context
|
|
719
|
+
require_true: gate_ok
|
|
720
|
+
on_false: stop
|
|
721
|
+
on_false_message: "The lane plans do not conform to the spec: {{plan_gap_count}} gap(s) a plan rewrite closes are still open (all gaps: {{analyze_gaps}}) — repair was skipped (see plan-analyze-report.md), or it did not converge in 3 passes. The receipt records verdict `gaps`, so /appweaver implement will refuse until this gate is re-run clean. A `contract` gap is closed in the lane its row names. Gaps routed to `specify` ({{routed_csv}}) are not what stopped this run: they need /appweaver specify, which authors the spec and the model. Fix out of band, or re-run and choose Repair."
|
|
722
|
+
|
|
667
723
|
# (No change-close here by design — this stage never archives anything in either mode,
|
|
668
724
|
# and cr-archive belongs to close-delta, which runs after finalize-delta's merge.)
|
|
669
725
|
|
|
@@ -687,10 +743,13 @@ outputs:
|
|
|
687
743
|
domain_model: "{{domain_model}}"
|
|
688
744
|
conformant: "{{analyze_ok}}"
|
|
689
745
|
gaps: "{{analyze_gaps}}"
|
|
746
|
+
verdict: "{{gate_verdict}}" # conformant | routed-gaps | gaps
|
|
747
|
+
plan_gaps: "{{plan_gap_count}}" # open gaps a plan rewrite closes
|
|
748
|
+
routed_to_specify: "{{routed_csv}}" # open gaps only /appweaver specify can close
|
|
690
749
|
version: "{{version}}"
|
|
691
750
|
gate_mode: "{{gate_mode}}" # master (repair allowed) | report (read-only)
|
|
692
751
|
open_changes: "{{open_ids}}" # empty in master mode, by definition
|
|
693
|
-
gate_receipt: "{{receipt_file}}" # written
|
|
752
|
+
gate_receipt: "{{receipt_file}}" # written by every master run, with its verdict; implement reads it
|
|
694
753
|
gate_receipt_at: "{{receipt_gated}}"
|
|
695
754
|
note: "Ran in {{gate_mode}} mode ({{open_count}} change(s) open: {{open_ids}}). Checked: spec→backend-plan ({{be_present}}), spec→frontend-plan ({{fe_present}}), the plan↔plan contract binding ({{both_lanes}} — it needs both lanes) and both plans against domain-model.md. The domain model was READ, never written: a gap that needs a new entity, event or invariant is fixed by /appweaver specify. In REPORT mode no plan was rewritten either — the master plans are derived documents and may not move while a delta is in flight."
|
|
696
|
-
next: "MASTER mode (no change open,
|
|
755
|
+
next: "MASTER mode (no change open, verdict={{gate_verdict}}): /appweaver implement — no gap a plan rewrite closes is left; version unchanged, this gate closes nothing. If the verdict is routed-gaps, implement generates and warns: {{routed_csv}} — each is a spec or model gap, lifted by opening a change with /appweaver specify. REPORT mode (change(s) {{open_ids}} open): this run was read-only and wrote plan-analyze-report.md only. If finalize-delta has already merged, the gaps ARE that merge — the lane plans are derived from spec.md + domain-model.md and finalize-delta moves only those two, so re-derive with /appweaver plan main after /appweaver close-delta, then re-run /appweaver analyze-plan in master mode. If the change has NOT merged yet, gate it where it lives: /appweaver analyze-plan-delta."
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
# =============================================================================
|
|
71
71
|
|
|
72
72
|
name: aw-spec-analyze-slice-delta
|
|
73
|
-
version: 1.
|
|
73
|
+
version: 1.1.0
|
|
74
74
|
description: "The delta-spec↔delta-code gate — verify that the open change DOES WHAT IT WAS ASKED TO DO, before it reaches the master documents. delta-spec.md is the anchor, exactly as it is for analyze-plan-delta. Asks which open change it is gating, runs the slice's tests (the first execution on the change path, since build only compiles), then evaluates the code and its paired tests against the change's REQUIREMENTS — every ADDED or MODIFIED delta-spec item realised in code, every one covered by a test that cites its id, every invariant the model delta adds actually enforced — with the delta plan in context as the map from requirement to code and cross-checked in its own right, so an element the plan promised and nobody built is caught too. The dynamic twin of analyze-plan-delta and the delta twin of analyze-slice, which cannot see a missing implementation because a behaviour that was never built leaves no failing test. Verify-only — it reports each finding with the command that closes it and stops; it generates no code and repairs nothing."
|
|
75
75
|
agent_agnostic: true
|
|
76
76
|
dialect: aw-spec.orchestrator/v1
|
|
@@ -124,6 +124,7 @@ vars:
|
|
|
124
124
|
test_ok: "false"
|
|
125
125
|
test_tool: ""
|
|
126
126
|
test_log: ""
|
|
127
|
+
test_excerpt: ""
|
|
127
128
|
test_scoped: "false"
|
|
128
129
|
test_ran: "false"
|
|
129
130
|
test_matched: "false"
|
|
@@ -155,9 +156,11 @@ workflow:
|
|
|
155
156
|
- { name: be_declared, from: "$.backend_declared" }
|
|
156
157
|
- { name: be_lane_id, from: "$.backend_lane_id" }
|
|
157
158
|
- { name: be_source_path, from: "$.backend_source_path" }
|
|
159
|
+
- { name: be_test_path, from: "$.backend_test_path" }
|
|
158
160
|
- { name: fe_declared, from: "$.frontend_declared" }
|
|
159
161
|
- { name: fe_lane_id, from: "$.frontend_lane_id" }
|
|
160
162
|
- { name: fe_source_path, from: "$.frontend_source_path" }
|
|
163
|
+
- { name: fe_test_path, from: "$.frontend_test_path" }
|
|
161
164
|
|
|
162
165
|
- id: require_lane
|
|
163
166
|
action: verify_context
|
|
@@ -316,6 +319,7 @@ workflow:
|
|
|
316
319
|
- { name: test_ok, from: "$.fence_green" }
|
|
317
320
|
- { name: test_tool, from: "$.tool" }
|
|
318
321
|
- { name: test_log, from: "$.log_tail" }
|
|
322
|
+
- { name: test_excerpt, from: "$.failing_excerpt" } # the lines that NAME the failure
|
|
319
323
|
- { name: test_scoped, from: "$.scoped" }
|
|
320
324
|
# A GREEN FENCE IS NOT ALWAYS AN EXECUTED SUITE. test-run reports fence_green true
|
|
321
325
|
# with ran=false on a project whose lanes declare no test command — the vacuous pass
|
|
@@ -414,9 +418,11 @@ workflow:
|
|
|
414
418
|
the pre-existing slice.
|
|
415
419
|
|
|
416
420
|
CHECK 7 — PLACEMENT. Every file this change added or edited sits under the lane that
|
|
417
|
-
owns it: this lane's source path
|
|
418
|
-
|
|
419
|
-
`placement` gap.
|
|
421
|
+
owns it: this lane's production code under its source path `{{be_source_path}}`, its
|
|
422
|
+
tests under its test path `{{be_test_path}}`. A service-lane element written into the
|
|
423
|
+
frontend lane's subtree — or outside every lane's paths — is a HIGH `placement` gap.
|
|
424
|
+
One edit outside them is allowed: registering a test project this change created in
|
|
425
|
+
the file the lane's test command discovers tests through (e.g. a .NET solution file).
|
|
420
426
|
|
|
421
427
|
PLAN-ONLY CHANGE — if the delta-spec is an unfilled stub, this change deliberately
|
|
422
428
|
alters only HOW the EXISTING spec is realised (a refactor, an architectural decision).
|
|
@@ -506,9 +512,11 @@ workflow:
|
|
|
506
512
|
gap. Judge it only over what this change touched.
|
|
507
513
|
|
|
508
514
|
CHECK 7 — PLACEMENT. Every file this change added or edited sits under this lane's
|
|
509
|
-
source path, `{{fe_source_path}}
|
|
510
|
-
HIGH `placement` gap — a gap in the
|
|
511
|
-
that lane, never a client-side edit
|
|
515
|
+
source path, `{{fe_source_path}}`, or — for a test — its test path, `{{fe_test_path}}`.
|
|
516
|
+
A file written into the service lane's subtree is a HIGH `placement` gap — a gap in the
|
|
517
|
+
service surface is a contract delta raised back to that lane, never a client-side edit
|
|
518
|
+
to it. The one edit allowed outside them is the test setup this change had to create,
|
|
519
|
+
when the lane's test command discovers tests through a file elsewhere.
|
|
512
520
|
|
|
513
521
|
PLAN-ONLY CHANGE — if the delta-spec is an unfilled stub, this change alters only HOW
|
|
514
522
|
the existing spec is realised. Checks 1 to 3 have no anchor and are SKIPPED, not
|
|
@@ -568,8 +576,8 @@ workflow:
|
|
|
568
576
|
merges the change into spec.md and domain-model.md.
|
|
569
577
|
Evidence:
|
|
570
578
|
- tests: green={{test_ok}}, tool `{{test_tool}}`, actually ran={{test_ran}},
|
|
571
|
-
matched a slice filter={{test_matched}}, slice-scoped={{test_scoped}}.
|
|
572
|
-
{{test_log}}
|
|
579
|
+
matched a slice filter={{test_matched}}, slice-scoped={{test_scoped}}. Failing
|
|
580
|
+
lines: {{test_excerpt}} · Log tail: {{test_log}}
|
|
573
581
|
- service lane gated={{be_gated}}, verdict ok={{be_ok}}, gaps: {{be_gaps}}
|
|
574
582
|
- experience lane gated={{fe_gated}}, verdict ok={{fe_ok}}, gaps: {{fe_gaps}}
|
|
575
583
|
Markdown format:
|