aw-spec-cli 0.1.20 → 0.1.22
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/STACK-PROFILES.md +87 -8
- package/assets/.appweaver/orchestrator/modules/delta-plan.orch.yaml +30 -0
- 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 +144 -52
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +78 -10
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +283 -25
- package/assets/.appweaver/orchestrator/workflows/aw-spec-constitution.orch.yaml +4 -4
- package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +202 -40
- package/assets/.appweaver/orchestrator/workflows/aw-spec-foundation.orch.yaml +140 -36
- package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +557 -57
- package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +3 -3
- package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +39 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-recall-delta.orch.yaml +28 -7
- package/assets/.appweaver/orchestrator/workflows/aw-spec-select-stack.orch.yaml +47 -23
- package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +101 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-ui-model.orch.yaml +141 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-ui-prototype.orch.yaml +386 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-workflow-model.orch.yaml +135 -0
- package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +772 -216
- package/assets/.appweaver/scripts/bash/aw-xref.sh +4 -1
- package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +708 -209
- package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +4 -1
- package/assets/.appweaver/scripts/python/aw-crstate.py +319 -0
- package/assets/.appweaver/scripts/python/aw-foundation.py +151 -0
- package/assets/.appweaver/scripts/python/aw-gaps.py +119 -0
- package/assets/.appweaver/scripts/python/aw-payload-parity.py +19 -0
- package/assets/.appweaver/scripts/python/aw-plan-coverage.py +503 -0
- package/assets/.appweaver/scripts/python/aw-plangate.py +216 -0
- package/assets/.appweaver/scripts/python/aw-principles.py +108 -0
- package/assets/.appweaver/scripts/python/aw-stack.py +257 -28
- package/assets/.appweaver/scripts/python/aw-testlog.py +148 -0
- package/assets/.appweaver/scripts/python/aw-xref.py +141 -4
- package/assets/.appweaver/stack-profiles/angular.json +26 -0
- package/assets/.appweaver/stack-profiles/blazor-wasm.json +38 -0
- package/assets/.appweaver/stack-profiles/dotnet-webapi.json +39 -0
- package/assets/.appweaver/stack-profiles/java-spring.json +20 -0
- package/assets/.appweaver/stack-profiles/nextjs.json +26 -0
- package/assets/.appweaver/stack-profiles/node-express.json +27 -0
- package/assets/.appweaver/stack-profiles/python-fastapi.json +16 -0
- package/assets/.appweaver/stack-profiles/react-vite.json +26 -0
- package/assets/.appweaver/stack-profiles/sveltekit.json +26 -0
- package/assets/.appweaver/stack-profiles/vue-vite.json +26 -0
- package/assets/.appweaver/templates/constitution-domain-template.md +17 -2
- package/assets/.appweaver/templates/ui-model-template.md +67 -0
- package/assets/.appweaver/templates/workflow-model-template.md +62 -0
- 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 +114 -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).
|
|
@@ -51,15 +51,17 @@ no heading to key on. Where the two lanes are folded into one command, as `plan`
|
|
|
51
51
|
| `stack` | *(optional)* what the code was found to be, when `profile` is `detected` — e.g. `asp.net-webforms`. The honest record for a legacy lane |
|
|
52
52
|
| `origin` | *(optional)* `reverse-engineered` on a lane `onboard` derived from an existing codebase. Absent means a person declared it, which is the greenfield shape and the historical one |
|
|
53
53
|
| `root` | the lane's **source path** — the subtree it owns (see §3). `--source-path` and `--root` are two spellings of this one field |
|
|
54
|
+
| `test_root` | *(optional)* the lane's **test path**, written by hand only when the profile's answer does not fit this layout. Absent, `lane-resolve` / `lane-verify` work it out from the profile at read time (§4.3), so no lane has to be re-declared for it |
|
|
54
55
|
| `run_from` | *(optional)* **where the commands run**: `repo` (the default, and the shape of every lane written before this key existed) at the repository root, or `root` inside the lane's own source path. See §3.1 |
|
|
55
56
|
| `build` / `test` / `run` | project-supplied command strings the engine shells out to. These are the matched **profile's** defaults and may not fit a legacy layout — reviewing them is what `select-stack` is for. `{{root}}` in a command resolves to this lane's `root` (§4.2) |
|
|
56
57
|
| `migrate` | *(optional)* the lane's schema-migration command, recorded only when the profile declares one. Absent, `run-migrate` falls back to `dotnet ef` and otherwise reports that nothing ran (§4.2) |
|
|
57
58
|
|
|
58
59
|
### Three properties matter more than the schema
|
|
59
60
|
|
|
60
|
-
1. **`root` is ownership, and it is the source path.** `implement`'s service lane writes
|
|
61
|
-
under a backend lane's root and its frontend lane only under a frontend lane's,
|
|
62
|
-
`build`/`test`/`run` act on it, and `onboard` reverse-engineers out of it.
|
|
61
|
+
1. **`root` is ownership, and it is the source path.** `implement`'s service lane writes its
|
|
62
|
+
code only under a backend lane's root and its frontend lane only under a frontend lane's,
|
|
63
|
+
`build`/`test`/`run` act on it, and `onboard` reverse-engineers out of it. The lane's
|
|
64
|
+
**test path** (§4.3) is owned the same way, and is where `implement` writes the lane's tests.
|
|
63
65
|
2. **The commands are project-supplied strings.** The engine stays stdlib-only and
|
|
64
66
|
stack-ignorant. This is how `build` already avoids knowing what a compiler is, and it
|
|
65
67
|
is the only thing that makes "any frontend stack" tractable.
|
|
@@ -151,12 +153,17 @@ Declaring **`(none)`** for a lane is a real answer and the way a project says it
|
|
|
151
153
|
## 3. The ownership rule
|
|
152
154
|
|
|
153
155
|
**Lane roots own their subtree and must be disjoint.** Two generators must never be able
|
|
154
|
-
to write the same file.
|
|
156
|
+
to write the same file. A lane owns its test path too, so the rule covers both: no source or
|
|
157
|
+
test path of one lane may overlap a source or test path of another.
|
|
155
158
|
|
|
156
|
-
`lane-declare` refuses a declaration whose
|
|
159
|
+
`lane-declare` refuses a declaration whose paths contain, or are contained by, another
|
|
157
160
|
lane's — compared as `/`-terminated paths, so `src/` is clear of `src-gen/` but collides
|
|
158
161
|
with `src/api/`. The refusal is a non-zero exit and **`stacks.json` is left untouched**.
|
|
159
162
|
|
|
163
|
+
The one write `implement` may make outside a lane's two paths is registering a test project
|
|
164
|
+
it had to create, in the file the lane's test command discovers tests through — the solution
|
|
165
|
+
file for .NET. `analyze-slice-delta`'s placement check accepts that edit and nothing else.
|
|
166
|
+
|
|
160
167
|
This is checked at declaration time on purpose. The alternative — discovering it when two
|
|
161
168
|
implement phases fight over a file — is a much worse place to find out.
|
|
162
169
|
|
|
@@ -176,12 +183,15 @@ workflow.**
|
|
|
176
183
|
"language": "TypeScript", // shown in the menu, parenthesised
|
|
177
184
|
"ecosystem": "node", // which stack-partitioned corpus this stack draws from
|
|
178
185
|
"root": "web/", // the lane root this profile defaults to
|
|
186
|
+
"test_root": "", // optional — where tests go when not inside root — see §4.3
|
|
179
187
|
"run_from": "root", // where the commands run — see §3.1
|
|
180
188
|
"build": "npm run build",
|
|
181
189
|
"test": "npm test",
|
|
182
190
|
"run": "npm run dev",
|
|
183
191
|
"migrate": "", // optional — schema migration, if the stack has one
|
|
184
192
|
"prototype_run": "npm run dev --prefix prototype", // frontend only — fills the prototype block
|
|
193
|
+
"reserved_names": ["Event", "Request", "Response"], // optional — names this stack's AMBIENT
|
|
194
|
+
// scope already defines; see §4.4
|
|
185
195
|
"detect": ["web/package.json", "web/vite.config.*"] // ANY-of markers — how stack-detect recognises this stack
|
|
186
196
|
}
|
|
187
197
|
```
|
|
@@ -206,10 +216,23 @@ trees that are physically per-ecosystem:
|
|
|
206
216
|
compiling project and a component runbook edits a host project's build files — both are
|
|
207
217
|
executable artifacts in one toolchain, so offering a `dotnet` bundle to a Spring repo does not
|
|
208
218
|
produce a scaffold, it produces a second foreign project beside the real one. `foundation-list`
|
|
209
|
-
therefore narrows
|
|
210
|
-
|
|
219
|
+
therefore narrows by ecosystem, and reports `all_foundations` alongside so the gate can say
|
|
220
|
+
*what exists and why none of it applies*. `--ecosystem` narrows to the ONE lane being
|
|
221
|
+
scaffolded, which is what `foundation` passes: a repo declaring a .NET service and a Node UI
|
|
222
|
+
must not be offered its .NET bundle while the UI lane is the target. Without that argument the
|
|
223
|
+
filter falls back to every declared lane's ecosystem, and a repo that has declared nothing yet
|
|
211
224
|
is not filtered — there is no answer to filter on.
|
|
212
225
|
|
|
226
|
+
**A scaffold lands in a lane.** `foundation` unpacks a bundle into the SOURCE PATH of the lane
|
|
227
|
+
it is scaffolding — never the repository root, and there is no output-directory argument. One
|
|
228
|
+
bundle fills one lane, so a fullstack repo runs `foundation` once per lane. This is what makes
|
|
229
|
+
every phase's "write only into your lane" rule true of the scaffold as well as of generated
|
|
230
|
+
code: `implement`, `build`, `test` and `run` all resolve the same path the bundle unpacked into.
|
|
231
|
+
A pack carrying its own test tree has it lifted onto the lane's **test path** (§4.3) afterwards,
|
|
232
|
+
because for most backend stacks that path is a sibling of the source path rather than a
|
|
233
|
+
directory inside it. Lanes must therefore be declared before `foundation` runs, which is why
|
|
234
|
+
`select-stack` is the first command of both sequences.
|
|
235
|
+
|
|
213
236
|
The technical RULE corpora at `guardrails/technology/<stack>/` are keyed by **profile id**, not
|
|
214
237
|
ecosystem, because Spring and Quarkus are both `java` and do not share a persistence rule. They
|
|
215
238
|
are selected through the project's `tech-rule-bindings.md` rather than filtered here — see §7.
|
|
@@ -274,6 +297,61 @@ about a backend, and the two errors are not symmetric. A false positive authors
|
|
|
274
297
|
the project never asked for; a false negative only produces a warning that names the fix. Pick
|
|
275
298
|
ids accordingly — a profile whose id is a common English word will be matched too eagerly.
|
|
276
299
|
|
|
300
|
+
### 4.4 `reserved_names` — names the stack already defines
|
|
301
|
+
|
|
302
|
+
A stack's ambient scope defines types nobody imported: C#'s implicit usings put `System`,
|
|
303
|
+
`System.Threading.Tasks`, `Microsoft.AspNetCore.Http` and `Microsoft.AspNetCore.Routing` in
|
|
304
|
+
every file of a `Microsoft.NET.Sdk.Web` project; a browser bundle has `Event`, `Request` and
|
|
305
|
+
`Response`; `java.lang` is always imported; `typing` is star-imported by convention.
|
|
306
|
+
|
|
307
|
+
A domain noun that matches one of those is **not a naming mistake** — `Route`, `Task`,
|
|
308
|
+
`Order` and `Activity` are ordinary domain vocabulary, the plan carries the ubiquitous
|
|
309
|
+
language, and the generator spells it verbatim. But written bare it is ambiguous, and in C#
|
|
310
|
+
it does not compile (CS0104). The conflict is invisible in the file: the framework name
|
|
311
|
+
arrives through an implicit import and appears in no line of it.
|
|
312
|
+
|
|
313
|
+
`reserved_names` lists the ones that are BOTH ambient on this stack AND plausible domain
|
|
314
|
+
vocabulary — curated, not an exhaustive dump of the standard library. `lane-resolve` and
|
|
315
|
+
`lane-verify` report it as `<kind>_reserved_names` / `<kind>_reserved_csv`; `implement`
|
|
316
|
+
passes it to `plan-coverage --reserved`, which intersects it with the plan's own element
|
|
317
|
+
names and hands the generating lane the actual collisions before it writes anything. The
|
|
318
|
+
per-stack resolution convention is in that stack's rule corpus — for `dotnet-webapi`, in
|
|
319
|
+
[[framework-api-map]]: qualify with the enclosing namespace segment (`Domain.Route`), never
|
|
320
|
+
rename the domain type.
|
|
321
|
+
|
|
322
|
+
Absent, nothing is reported and generation is unchanged.
|
|
323
|
+
|
|
324
|
+
### 4.3 `test_root` — where a lane's tests go
|
|
325
|
+
|
|
326
|
+
Most backend stacks keep their tests **beside** the source, not inside it: .NET's `tests/` next
|
|
327
|
+
to `src/`, Maven's `src/test/java/` next to `src/main/java/`, pytest's `tests/` next to `app/`.
|
|
328
|
+
A lane that owned only its source path could not hold its own tests, so each lane also owns a
|
|
329
|
+
**test path**, and `implement` writes the lane's paired tests there.
|
|
330
|
+
|
|
331
|
+
A profile's `test_root` is the test path that pairs with its default `root`. `lane-resolve` and
|
|
332
|
+
`lane-verify` report it per kind as `backend_test_path` / `frontend_test_path`, worked out at
|
|
333
|
+
read time:
|
|
334
|
+
|
|
335
|
+
| The lane | Its test path |
|
|
336
|
+
|---|---|
|
|
337
|
+
| declares `test_root` itself | that value |
|
|
338
|
+
| sits at the profile's default root (`src/`) | the profile's `test_root` (`tests/`) |
|
|
339
|
+
| sits at a path ending in the default root (`backend/src/`) | the profile's `test_root` under the same prefix (`backend/tests/`) |
|
|
340
|
+
| anything else, or the profile declares no `test_root` | the source path itself |
|
|
341
|
+
|
|
342
|
+
The last row is right for every shipped frontend (tests under `web/tests/`), for Go's
|
|
343
|
+
colocated `_test.go` files, and for a Maven lane rooted at the directory holding its
|
|
344
|
+
`pom.xml`, where `src/test/java/` is already inside the lane. A layout the table gets wrong is
|
|
345
|
+
fixed by writing `test_root` on the lane in `stacks.json`.
|
|
346
|
+
|
|
347
|
+
| Profile | `root` | `test_root` |
|
|
348
|
+
|---|---|---|
|
|
349
|
+
| `dotnet-webapi` | `src/` | `tests/` |
|
|
350
|
+
| `java-spring` | `src/main/java/` | `src/test/java/` |
|
|
351
|
+
| `python-fastapi` | `app/` | `tests/` |
|
|
352
|
+
| `node-express` | `src/` | `tests/` |
|
|
353
|
+
| `go-chi` and every frontend | — | *(none — inside the source path)* |
|
|
354
|
+
|
|
277
355
|
---
|
|
278
356
|
|
|
279
357
|
## 5. Who may write this file
|
|
@@ -398,7 +476,8 @@ set**, so a caller binds outputs without knowing which branch ran.
|
|
|
398
476
|
| `test-run` | every declared lane's own `test`, via `run-phase` — **except** `--scope slice` on a repo where a .NET toolchain is present, which keeps the VSTest `--filter` path | the historic ladder |
|
|
399
477
|
| `run-migrate` | a lane's declared `migrate` (§4.2) when there is one; otherwise `dotnet ef` searched under the **service lane's root** rather than an assumed `src/` | `dotnet ef` under `src/` |
|
|
400
478
|
| `find-host-project` | searches the service lane's root, and reports the lane's `ecosystem` | searches `src/` |
|
|
401
|
-
| `foundation-list`, `list-components` | narrowed to the
|
|
479
|
+
| `foundation-list`, `list-components` | narrowed to the `ecosystem` of the lane being scaffolded, else of every declared lane (§4.1) | unfiltered / `dotnet` |
|
|
480
|
+
| `foundation-unpack` | unpacks into the lane's **source path**, and lifts a packed test tree onto its **test path** (§4.1, §4.3) | cannot run — `foundation` stops until a lane is declared |
|
|
402
481
|
| `resolve-tech` | expands stack placeholders and flags a corpus mismatch (§7a) | no placeholders resolve; nothing is flagged |
|
|
403
482
|
|
|
404
483
|
**Why `--scope slice` is the one exception.** VSTest's `--filter FullyQualifiedName~` is the
|
|
@@ -130,6 +130,14 @@ workflow:
|
|
|
130
130
|
# the context's CANONICAL model — the aggregate root,
|
|
131
131
|
# its entities and its invariants live here, not in the
|
|
132
132
|
# spec and not in this plan.
|
|
133
|
+
- { path: "{{cr_dir}}/delta-slice-analyze-report.md", optional: true }
|
|
134
|
+
# WHAT THE LAST GATE FOUND, when this change
|
|
135
|
+
# has already been through analyze-slice-delta.
|
|
136
|
+
# A `plan` finding there says THIS document is
|
|
137
|
+
# what was wrong, and re-deriving it is how that
|
|
138
|
+
# finding is closed — the alternative is hand-
|
|
139
|
+
# editing a generated plan, which every
|
|
140
|
+
# authoring rule forbids. Absent on a first pass.
|
|
133
141
|
- { path: .appweaver/DESIGN-FORMAT.md }
|
|
134
142
|
- id: author_backend_delta
|
|
135
143
|
condition: "{{be_existing}} == true"
|
|
@@ -148,6 +156,13 @@ workflow:
|
|
|
148
156
|
IS, and a delta that ignores the difference plans an edit to something that is not
|
|
149
157
|
there. Where the two disagree, plan against the code and record the divergence as
|
|
150
158
|
a WA-n.
|
|
159
|
+
- THE LAST GATE'S REPORT, when one is in context (delta-slice-analyze-report.md).
|
|
160
|
+
It records what analyze-slice-delta found the last time this change was gated. A
|
|
161
|
+
finding tagged `fix: plan` says THIS document was what was wrong — re-derive the
|
|
162
|
+
rows it names, against the code it names. EVIDENCE, never a requirement source: the
|
|
163
|
+
delta-spec stays the only authority on what the change must do, and a finding
|
|
164
|
+
tagged `implement` or `specify` is closed elsewhere and is not this pass's work.
|
|
165
|
+
Absent on a first pass, which is the normal case.
|
|
151
166
|
- Classify every change to the plan as ADDED / MODIFIED / REMOVED against the
|
|
152
167
|
master, per the master's OWN section structure (whatever sections it actually has
|
|
153
168
|
— do not assume a fixed layering), plus the cross-section impact, the regression
|
|
@@ -231,6 +246,14 @@ workflow:
|
|
|
231
246
|
# frontend-only project has no service lane at all.
|
|
232
247
|
- { path: "{{domain_model}}", optional: true }
|
|
233
248
|
- { path: .appweaver/memory/constitution.ui.md, optional: true }
|
|
249
|
+
- { path: "{{cr_dir}}/delta-slice-analyze-report.md", optional: true }
|
|
250
|
+
# WHAT THE LAST GATE FOUND, when this change
|
|
251
|
+
# has already been through analyze-slice-delta.
|
|
252
|
+
# A `plan` finding there says THIS document is
|
|
253
|
+
# what was wrong, and re-deriving it is how that
|
|
254
|
+
# finding is closed — the alternative is hand-
|
|
255
|
+
# editing a generated plan, which every
|
|
256
|
+
# authoring rule forbids. Absent on a first pass.
|
|
234
257
|
- { path: .appweaver/DESIGN-FORMAT.md }
|
|
235
258
|
- id: author_frontend_delta
|
|
236
259
|
condition: "{{fe_existing}} == true"
|
|
@@ -247,6 +270,13 @@ workflow:
|
|
|
247
270
|
feature: {{source_files}}. Read the ones this change touches — a delta that plans
|
|
248
271
|
an edit to a component that is not there, or that duplicates one that is, costs a
|
|
249
272
|
whole implementation pass to discover.
|
|
273
|
+
- THE LAST GATE'S REPORT, when one is in context (delta-slice-analyze-report.md).
|
|
274
|
+
It records what analyze-slice-delta found the last time this change was gated. A
|
|
275
|
+
finding tagged `fix: plan` says THIS document was what was wrong — re-derive the
|
|
276
|
+
rows it names, against the code it names. EVIDENCE, never a requirement source: the
|
|
277
|
+
delta-spec stays the only authority on what the change must do, and a finding
|
|
278
|
+
tagged `implement` or `specify` is closed elsewhere and is not this pass's work.
|
|
279
|
+
Absent on a first pass, which is the normal case.
|
|
250
280
|
- Classify every change ADDED / MODIFIED / REMOVED against the master, per the
|
|
251
281
|
master's OWN sections. A shared component or a shared state container is MODIFIED
|
|
252
282
|
in place, never copied into the change.
|
|
@@ -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
|