aw-spec-cli 0.1.23 → 0.1.25
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 +3 -3
- package/assets/.appweaver/RULE-PARTITION.md +41 -28
- package/assets/.appweaver/orchestrator/orchestrator.py +92 -14
- package/assets/.appweaver/orchestrator/rev-templates/ai-rules.reference.md +3 -3
- package/assets/.appweaver/orchestrator/rev-templates/delta-design-template.skeleton.md +19 -12
- package/assets/.appweaver/orchestrator/rev-templates/design-template.skeleton.md +25 -17
- package/assets/.appweaver/orchestrator/rev-templates/domain-requirements.skeleton.md +2 -2
- package/assets/.appweaver/orchestrator/rev-templates/domain-rule-bindings.skeleton.md +10 -10
- package/assets/.appweaver/orchestrator/rev-templates/tech-rule-bindings.skeleton.md +28 -17
- package/assets/.appweaver/orchestrator/rev-templates/technical-constitution.skeleton.md +22 -24
- package/assets/.appweaver/orchestrator/rev-templates/technical-rule-file.skeleton.md +1 -1
- package/assets/.appweaver/orchestrator/rev-templates/ui-delta-design-template.skeleton.md +29 -19
- package/assets/.appweaver/orchestrator/rev-templates/ui-design-template.skeleton.md +27 -20
- package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +184 -37
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan-delta.orch.yaml +18 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +19 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +18 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-inventory.orch.yaml +55 -29
- package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +650 -205
- package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +19 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-select-feature.orch.yaml +5 -5
- package/assets/.appweaver/orchestrator/workflows/aw-spec-select-stack.orch.yaml +2 -2
- package/assets/.appweaver/orchestrator/workflows/aw-spec-ui-model.orch.yaml +13 -3
- package/assets/.appweaver/orchestrator/workflows/aw-spec-ui-prototype.orch.yaml +40 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-workflow-model.orch.yaml +2 -1
- package/assets/.appweaver/scripts/bash/aw-rev-constitution.sh +1 -1
- package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +69 -7
- package/assets/.appweaver/scripts/powershell/aw-rev-constitution.ps1 +1 -1
- package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +58 -2
- package/assets/.appweaver/scripts/python/aw-inventory.py +1809 -156
- package/assets/.appweaver/scripts/python/aw-rev-constitution.py +106 -145
- package/assets/.appweaver/scripts/python/aw-stack.py +45 -1
- package/assets/.appweaver/scripts/python/aw-xref.py +73 -2
- package/assets/.appweaver/scripts/python/validate_okf.py +10 -5
- package/assets/.appweaver/templates/okf-concept-template.md +10 -2
- package/assets/.appweaver/templates/proposed-inventory-template.md +100 -48
- package/assets/.appweaver/templates/source-map-template.md +14 -6
- package/package.json +1 -1
|
@@ -88,11 +88,11 @@ the raw object, not its JSON string.
|
|
|
88
88
|
| action | who runs | what it does |
|
|
89
89
|
|---|---|---|
|
|
90
90
|
| `set` | engine | assign vars (`vars:` or decl-style `parameters:`); raw-object aware |
|
|
91
|
-
| `build_context` | engine | REPLACE the per-request envelope: `system:`/`context:` file lists (+ `context_from: <var>` holding paths a prior
|
|
91
|
+
| `build_context` | engine | REPLACE the per-request envelope: `system:`/`context:` file lists (+ `context_from: <var>` — or a list of vars, appended in order — holding paths a prior step produced, + `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` 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 |
|
|
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 (a JSON object on its last stdout line is still bound, so a gate can show the script's own reason; with none, the 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
|
-
| `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 |
|
|
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; a reported path not on disk is dropped from `files` (and listed in `files_not_written`) before outputs bind, so a `require_non_empty` gate on it proves something was written. `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}` |
|
|
97
97
|
| `agent_author` | fresh agent | F5 — **authored artifact** (decl `llm_generate`): the INTERPRETER owns `output_target: {type: file, path}`. Generation is payload-only under every strategy (mapped `context:` + sticky envelope + standards are the entire world) and never writes — the ENGINE writes the returned content (sdk-apply degrades to sdk). Optional `context:` (per-request extras), `include_standards: false` (narrow payload), `tools:` (read-only opt-in for a task that names files by path) |
|
|
98
98
|
| `agent_query` | fresh agent | F6 — generic payload-only structured JSON (decl `llm_query`): `schema:` shapes the required fence, `outputs[]` binds it, `mock:` supplies the mock-transport result; one malformed-JSON retry on fresh strategies. Same tools contract as `agent_author` — none unless the step declares `tools:` |
|
|
@@ -21,9 +21,9 @@ governs. It also stops domain detail leaking straight into code authoring withou
|
|
|
21
21
|
pinned in a reviewable requirement.
|
|
22
22
|
|
|
23
23
|
> **Lift, then gate.** If a domain constraint is needed downstream, it must first be *lifted*
|
|
24
|
-
> into the spec as an id. A gate that finds a constraint in neither the spec nor the
|
|
24
|
+
> into the spec as an id. A gate that finds a constraint in neither the spec nor the plans
|
|
25
25
|
> reports `LIFT-FAILURE:` and routes back to `specify` — it must never invent the rule into a
|
|
26
|
-
>
|
|
26
|
+
> plan or into code.
|
|
27
27
|
|
|
28
28
|
---
|
|
29
29
|
|
|
@@ -42,13 +42,13 @@ A command with **no heading** in a map loads nothing mapped for that class and
|
|
|
42
42
|
### The constitution halves are not mapped
|
|
43
43
|
|
|
44
44
|
- `.appweaver/memory/constitution.technical.md` — the technology base. Standing input to
|
|
45
|
-
**
|
|
45
|
+
the **service lane of `plan` and of `implement`**. Domain-agnostic.
|
|
46
46
|
- `.appweaver/memory/constitution.ui.md` — the **frontend lane's** base, a UX-* principle series.
|
|
47
47
|
Standing input to the **FRONTEND LANE of `plan` and of `implement`**. Domain-agnostic for the same reason the
|
|
48
48
|
technical base is, and it exists only where a frontend lane does — a backend-only project has
|
|
49
49
|
no such file.
|
|
50
50
|
- `.appweaver/memory/constitution.domain.md` — the active domain's addenda. Standing input to
|
|
51
|
-
**specify** (and the gate checklist). Deliberately **not** loaded by
|
|
51
|
+
**specify** (and the gate checklist). Deliberately **not** loaded by plan or implement.
|
|
52
52
|
- `.appweaver/memory/constitution.md` — the composed human/compatibility view. The pipeline loads
|
|
53
53
|
the halves, not this file.
|
|
54
54
|
|
|
@@ -74,11 +74,13 @@ lane loads the technical base, the frontend lane loads the UI base. What they sh
|
|
|
74
74
|
**HOW to model**; **WHAT** the model is stays in the domain tier.
|
|
75
75
|
- Constitution: the **domain** half.
|
|
76
76
|
|
|
77
|
-
### `
|
|
78
|
-
- **Domain rule files: NEVER.** Domain reaches
|
|
79
|
-
-
|
|
80
|
-
|
|
81
|
-
|
|
77
|
+
### `plan` — *architecture, both lanes; domain input = the spec tier only*
|
|
78
|
+
- **Domain rule files: NEVER.** Domain reaches plan solely as spec ids and the context's
|
|
79
|
+
`domain-model.md`.
|
|
80
|
+
- **Technical rule files: yes** — the architecture and cross-cutting rules each lane's plan
|
|
81
|
+
mediates the spec against: `## /aw-spec-plan` for the service lane, `## /aw-spec-plan-ui`
|
|
82
|
+
for the frontend lane.
|
|
83
|
+
- Constitution: the **technical** half in the service lane, the **UI** half in the frontend one.
|
|
82
84
|
|
|
83
85
|
### `implement` — *code generation, both lanes, both paths*
|
|
84
86
|
- **Domain rule files: NEVER.** The lane's plan is the only input describing what to BUILD.
|
|
@@ -97,7 +99,7 @@ lane loads the technical base, the frontend lane loads the UI base. What they sh
|
|
|
97
99
|
|
|
98
100
|
### `build` — *compile + repair*
|
|
99
101
|
- **Technical rule files: yes** — the layering and convention rules the compile-fixer respects.
|
|
100
|
-
- No domain files: build repairs against
|
|
102
|
+
- No domain files: build repairs against the plans' intent, which the domain never reaches.
|
|
101
103
|
|
|
102
104
|
### `run` — *runtime*
|
|
103
105
|
- **Technical rule files:** only if the project evidences a runtime/hosting concern.
|
|
@@ -121,23 +123,24 @@ lane loads the technical base, the frontend lane loads the UI base. What they sh
|
|
|
121
123
|
- Authors all three halves itself, and generates the rule set each one ratifies. The rule maps do
|
|
122
124
|
not exist yet when it starts, so its authority is the **code**: every principle and every
|
|
123
125
|
generated rule file is evidenced from source, and a greenfield rule file is never an input.
|
|
124
|
-
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
126
|
+
- **Which lanes exist is the declaration's answer.** `select-stack` runs first and writes
|
|
127
|
+
`.appweaver/stacks.json`; onboard refuses to start without it. A lane declared there gets its
|
|
128
|
+
half and its rule set, a lane declared `(none)` gets neither, and the scan — pointed at the
|
|
129
|
+
declared source path — decides what the governance SAYS, never whether the lane exists. A
|
|
130
|
+
declared UI lane whose path holds no recognisable UI source is reported, and its governance is
|
|
131
|
+
authored thin rather than skipped.
|
|
132
|
+
- The **UI half** is authored from the lane's code, not from a map heading, because at onboard
|
|
133
|
+
there is no map to read. The order is therefore inverted: the half is authored from the code,
|
|
134
|
+
then the `frontend/` rule set is generated as its detail layer, then the lane's three headings
|
|
135
|
+
are appended to the technical map — `## /aw-spec-ui-constitution` (what a LATER `constitution`
|
|
136
|
+
run re-ratifies from, so it behaves like the greenfield case above), `## /aw-spec-plan-ui` and
|
|
137
|
+
`## /aw-spec-implement-ui` (what the frontend lane of `plan` and `implement` load).
|
|
138
|
+
- `onboard` **never writes** `.appweaver/stacks.json`. `lane-declare` is its sole writer and
|
|
139
|
+
`select-stack` its only caller, so every lane is one a person confirmed.
|
|
137
140
|
- The **document templates** it derives obey the same partition as the halves that govern them:
|
|
138
|
-
the
|
|
141
|
+
the backend plan + delta pair is authored against `constitution.technical.md`, the frontend
|
|
139
142
|
lane's pair against `constitution.ui.md` and the generated `frontend/` rules, and neither
|
|
140
|
-
reads a domain file. This is invariant 7 applied one level up — a lane's template describes
|
|
143
|
+
reads a domain file. The domain-model template is lane-neutral. This is invariant 7 applied one level up — a lane's template describes
|
|
141
144
|
that lane's code and no other's.
|
|
142
145
|
- **No greenfield artifact is ever in context.** Where a step needs a shape reference it reads a
|
|
143
146
|
neutral skeleton from `.appweaver/orchestrator/rev-templates/`, which carries the format and no
|
|
@@ -145,19 +148,29 @@ lane loads the technical base, the frontend lane loads the UI base. What they sh
|
|
|
145
148
|
so an instruction not to copy an exemplar would be the only control — absence is the stronger
|
|
146
149
|
one.
|
|
147
150
|
- Ordering is preserved for the same reason as above: technical half → UI half → domain half.
|
|
151
|
+
- **Per bounded context.** The first context to onboard authors every tier above; each later
|
|
152
|
+
context amends the tiers that exist, additively and from its own code, under the same
|
|
153
|
+
partition — the service tier's step sees no domain file, the frontend tier's step no service
|
|
154
|
+
rule file, and the domain tier's step reads the technical half only to continue its
|
|
155
|
+
numbering. A tier that does not exist yet (a lane declared after an earlier onboard) is
|
|
156
|
+
authored in full, in the order above. The document templates are written once per lane and
|
|
157
|
+
never amended.
|
|
148
158
|
|
|
149
159
|
### `adopt` (brownfield)
|
|
150
160
|
- Same partition, reversed: the reversed **spec** is authored with domain rules in context; the
|
|
151
|
-
reversed **
|
|
161
|
+
reversed **plan** is authored with the technical set only, so the plan stays domain-free.
|
|
162
|
+
- Each lane's reversed plan is authored under its own lane's rules, as in `plan`: the backend
|
|
163
|
+
plan under `## /aw-spec-adopt`, and the frontend plan under `## /aw-spec-plan-ui`, which
|
|
164
|
+
REPLACES the backend set before that step rather than stacking on it.
|
|
152
165
|
- Technical files here are **reference, not authority — the CODE wins.**
|
|
153
166
|
|
|
154
167
|
---
|
|
155
168
|
|
|
156
169
|
## Invariants
|
|
157
170
|
|
|
158
|
-
1. **No domain rule file is ever loaded by `
|
|
171
|
+
1. **No domain rule file is ever loaded by `plan`, `implement` or `build`** — on either
|
|
159
172
|
lane and on either path.
|
|
160
|
-
2. **Domain reaches
|
|
173
|
+
2. **Domain reaches the plans and code only through the SPEC TIER** — as spec ids, and as the
|
|
161
174
|
bounded context's `domain-model.md` (plus a change's pending model delta) that those ids
|
|
162
175
|
point into. Never as a domain rule file: the rules were resolved into the model and the
|
|
163
176
|
spec at `specify`, and a phase downstream of that reads the resolution, never re-derives
|
|
@@ -163,6 +163,34 @@ def _extract_json(text):
|
|
|
163
163
|
return {}
|
|
164
164
|
|
|
165
165
|
|
|
166
|
+
def _last_json_line(text):
|
|
167
|
+
"""The object on stdout's LAST line — the script contract — or None when that
|
|
168
|
+
line is missing or is not a JSON object."""
|
|
169
|
+
lines = (text or "").strip().splitlines()
|
|
170
|
+
if not lines:
|
|
171
|
+
return None
|
|
172
|
+
try:
|
|
173
|
+
data = json.loads(lines[-1])
|
|
174
|
+
except Exception:
|
|
175
|
+
return None
|
|
176
|
+
return data if isinstance(data, dict) else None
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _script_reason(data, stderr, returncode):
|
|
180
|
+
"""Why a script failed, in its own words where it gave any. A script that
|
|
181
|
+
prints `{"ok": "false", "error": …}` and exits non-zero has already said why;
|
|
182
|
+
stderr is often only its progress log, so the JSON is read first."""
|
|
183
|
+
data = data or {}
|
|
184
|
+
for key in ("error", "problems", "message", "reason"):
|
|
185
|
+
v = data.get(key)
|
|
186
|
+
if isinstance(v, str) and v.strip():
|
|
187
|
+
return v.strip()
|
|
188
|
+
errs = data.get("errors")
|
|
189
|
+
if isinstance(errs, list) and errs:
|
|
190
|
+
return "; ".join(str(e) for e in errs)
|
|
191
|
+
return (stderr or "").strip() or f"exit code {returncode}, no output"
|
|
192
|
+
|
|
193
|
+
|
|
166
194
|
def _strip_fence(text):
|
|
167
195
|
"""If the ENTIRE reply is one fenced code block, unwrap it (models often
|
|
168
196
|
fence authored file content despite being told not to)."""
|
|
@@ -200,6 +228,9 @@ class Orchestrator:
|
|
|
200
228
|
# assembly time (item H). Distinct, run-cumulative, surfaced in the run
|
|
201
229
|
# result + the context_missing/_count vars so a workflow can gate on it.
|
|
202
230
|
self.missing_required = []
|
|
231
|
+
# paths an agent_generate reply named as written that were not on disk —
|
|
232
|
+
# run-cumulative, reported at the end beside missing_required.
|
|
233
|
+
self.reported_missing = []
|
|
203
234
|
self.last_result = {}
|
|
204
235
|
# agent-specific transport profile (None in mock mode). This engine is
|
|
205
236
|
# general — the profile (loaded from a .claude/.agents/.kilocode file)
|
|
@@ -407,14 +438,21 @@ class Orchestrator:
|
|
|
407
438
|
against the run-cumulative set (cf. concept-paths' `missing`).
|
|
408
439
|
|
|
409
440
|
A path that resolves to "" is an unbound {{var}} on this branch, not a
|
|
410
|
-
missing file — skipped silently, as before.
|
|
441
|
+
missing file — skipped silently, as before.
|
|
442
|
+
|
|
443
|
+
A file named twice is sent once, compared by the file it resolves to rather
|
|
444
|
+
than by spelling: a script returns `knowledge/x.md` where a workflow writes
|
|
445
|
+
`{{bundle_dir}}/x.md` with an absolute bundle_dir, and both are one file."""
|
|
411
446
|
files, seen = [], set()
|
|
412
447
|
for p, optional in entries:
|
|
413
448
|
rp = self.resolve(p)
|
|
414
|
-
if not rp
|
|
449
|
+
if not rp:
|
|
450
|
+
continue
|
|
451
|
+
key = os.path.normcase(os.path.realpath(rp))
|
|
452
|
+
if key in seen:
|
|
415
453
|
continue
|
|
416
454
|
if os.path.exists(rp):
|
|
417
|
-
seen.add(
|
|
455
|
+
seen.add(key)
|
|
418
456
|
files.append(rp)
|
|
419
457
|
elif optional:
|
|
420
458
|
self.emit(f"[{kind}] optional missing, skip {rp}")
|
|
@@ -694,10 +732,11 @@ class Orchestrator:
|
|
|
694
732
|
def act_build_context(self, step):
|
|
695
733
|
ctx = list(step.get("context", []) or [])
|
|
696
734
|
# dynamic context: append a var holding a list of paths — e.g. the files a
|
|
697
|
-
# prior agent_generate reported it wrote (since IT, not we, chose them).
|
|
735
|
+
# prior agent_generate reported it wrote (since IT, not we, chose them). A list
|
|
736
|
+
# of var names appends each in order.
|
|
698
737
|
cf = step.get("context_from")
|
|
699
|
-
if cf:
|
|
700
|
-
ctx += list(self.vars.get(
|
|
738
|
+
for name in (cf if isinstance(cf, list) else [cf] if cf else []):
|
|
739
|
+
ctx += list(self.vars.get(name) or [])
|
|
701
740
|
self.envelope = {"system": step.get("system", []) or [], "context": ctx}
|
|
702
741
|
for k, v in (step.get("vars", {}) or {}).items():
|
|
703
742
|
self.vars[k] = self.resolve(v)
|
|
@@ -733,6 +772,30 @@ class Orchestrator:
|
|
|
733
772
|
v = ",".join(str(x) for x in v)
|
|
734
773
|
return (self.resolve(str(v)) or "").strip()
|
|
735
774
|
|
|
775
|
+
def _written_only(self, step, structured):
|
|
776
|
+
"""The generation result with `files` narrowed to paths that exist on disk.
|
|
777
|
+
|
|
778
|
+
`files` is the paths the agent SAYS it wrote. Every workflow gates on it with
|
|
779
|
+
`require_non_empty`, which proved only that the reply named something: a reply
|
|
780
|
+
listing a file whose write had failed passed the gate, and the next step read
|
|
781
|
+
a template that was never written. A path that is not on disk is dropped here
|
|
782
|
+
and announced, so an existing gate stops when nothing real was written, and a
|
|
783
|
+
step that builds context from the list gets only real files. The unfiltered
|
|
784
|
+
list stays in the replay cache, so every replay re-checks the disk."""
|
|
785
|
+
if not isinstance(structured, dict) or not isinstance(structured.get("files"), list):
|
|
786
|
+
return structured
|
|
787
|
+
kept, missing = [], []
|
|
788
|
+
for f in structured["files"]:
|
|
789
|
+
(kept if isinstance(f, str) and f.strip() and os.path.exists(f) else missing).append(f)
|
|
790
|
+
if not missing:
|
|
791
|
+
return structured
|
|
792
|
+
self.emit(f"[agent_generate] {step.get('id')}: {len(missing)} reported file(s) "
|
|
793
|
+
f"not on disk, dropped: {', '.join(str(m) for m in missing)}")
|
|
794
|
+
for m in missing:
|
|
795
|
+
if m not in self.reported_missing:
|
|
796
|
+
self.reported_missing.append(m)
|
|
797
|
+
return dict(structured, files=kept, files_not_written=missing)
|
|
798
|
+
|
|
736
799
|
def act_agent_generate(self, step):
|
|
737
800
|
tools = step.get("tools", "Read,Write,Edit,Bash")
|
|
738
801
|
permission_mode = step.get("permission_mode", "acceptEdits")
|
|
@@ -743,7 +806,7 @@ class Orchestrator:
|
|
|
743
806
|
# bind its cached structured result instead of executing again.
|
|
744
807
|
ck = self._key(step)
|
|
745
808
|
if self.checkpoint and ck in self.cache:
|
|
746
|
-
structured = self.cache[ck]
|
|
809
|
+
structured = self._written_only(step, self.cache[ck])
|
|
747
810
|
self.last_result = structured
|
|
748
811
|
self._bind(step, structured)
|
|
749
812
|
self.emit(f"[agent_generate] (cached) result={json.dumps(structured)[:200]}")
|
|
@@ -790,9 +853,10 @@ class Orchestrator:
|
|
|
790
853
|
else: # spawn
|
|
791
854
|
structured, _ = self._run_fresh(system, user, tools, permission_mode,
|
|
792
855
|
model=step.get("model"))
|
|
793
|
-
self.last_result = structured
|
|
794
856
|
if self.checkpoint:
|
|
795
857
|
self.cache[ck] = structured
|
|
858
|
+
structured = self._written_only(step, structured)
|
|
859
|
+
self.last_result = structured
|
|
796
860
|
self._bind(step, structured)
|
|
797
861
|
self.emit(f"[agent_generate] strategy={strat or 'mock'} "
|
|
798
862
|
f"result={json.dumps(structured)[:200]}")
|
|
@@ -902,12 +966,18 @@ class Orchestrator:
|
|
|
902
966
|
return
|
|
903
967
|
proc = subprocess.run(runner + [sub] + args, capture_output=True, text=True)
|
|
904
968
|
if proc.returncode != 0:
|
|
969
|
+
# A script that fails can still have answered: many print their verdict as
|
|
970
|
+
# JSON (`ok: false` plus the reason) and exit non-zero. Under `on_error:
|
|
971
|
+
# continue` that object is bound like a success's, so the gate after the
|
|
972
|
+
# step reads the script's own reason instead of a blank. A failure with no
|
|
973
|
+
# JSON binds nothing, as before.
|
|
974
|
+
data = _last_json_line(proc.stdout)
|
|
975
|
+
reason = _script_reason(data, proc.stderr, proc.returncode)
|
|
905
976
|
if p.get("on_error") == "continue":
|
|
906
|
-
self.emit(f"[execute_script] {sub} failed (continuing): "
|
|
907
|
-
|
|
908
|
-
self._bind(step, {})
|
|
977
|
+
self.emit(f"[execute_script] {sub} failed (continuing): {reason[:200]}")
|
|
978
|
+
self._bind(step, data or {})
|
|
909
979
|
return
|
|
910
|
-
raise Stop(f"script failed ({sub}): {
|
|
980
|
+
raise Stop(f"script failed ({sub}): {reason[:500]}")
|
|
911
981
|
try:
|
|
912
982
|
data = json.loads(proc.stdout.strip().splitlines()[-1])
|
|
913
983
|
except Exception as e:
|
|
@@ -1426,7 +1496,8 @@ class Orchestrator:
|
|
|
1426
1496
|
self._bind_inputs()
|
|
1427
1497
|
self._run_steps(self.wf.get("workflow", []) or [])
|
|
1428
1498
|
return {"status": "ok", "outputs": self.resolve(self.wf.get("outputs", {}) or {}),
|
|
1429
|
-
"vars": self.vars, "missing_required": list(self.missing_required)
|
|
1499
|
+
"vars": self.vars, "missing_required": list(self.missing_required),
|
|
1500
|
+
"reported_not_written": list(self.reported_missing)}
|
|
1430
1501
|
|
|
1431
1502
|
|
|
1432
1503
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -1915,7 +1986,8 @@ def main(argv=None):
|
|
|
1915
1986
|
return 2
|
|
1916
1987
|
except Stop as e:
|
|
1917
1988
|
result = {"status": "stopped", "reason": str(e), "vars": orch.vars,
|
|
1918
|
-
"missing_required": list(orch.missing_required)
|
|
1989
|
+
"missing_required": list(orch.missing_required),
|
|
1990
|
+
"reported_not_written": list(orch.reported_missing)}
|
|
1919
1991
|
_clear_checkpoint(opts)
|
|
1920
1992
|
|
|
1921
1993
|
if opts.json:
|
|
@@ -1933,6 +2005,12 @@ def main(argv=None):
|
|
|
1933
2005
|
"them was assembled without them:")
|
|
1934
2006
|
for m in result["missing_required"]:
|
|
1935
2007
|
print(f" - {m}")
|
|
2008
|
+
if result.get("reported_not_written"):
|
|
2009
|
+
print(f"\nWARNING: {len(result['reported_not_written'])} file(s) a generation "
|
|
2010
|
+
"reported as written are not on disk — dropped from its result, so no "
|
|
2011
|
+
"later step read them:")
|
|
2012
|
+
for m in result["reported_not_written"]:
|
|
2013
|
+
print(f" - {m}")
|
|
1936
2014
|
if result.get("outputs"):
|
|
1937
2015
|
print(json.dumps(result["outputs"], indent=2))
|
|
1938
2016
|
return 0 if result["status"] == "ok" else 1
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
> Pure operating rules — domain-agnostic, stack-agnostic. Applies to every brownfield
|
|
13
13
|
> reverse-engineering authoring step (onboard's constitution + rule-set authors, adopt).
|
|
14
14
|
|
|
15
|
-
The **code, the deterministic scan, and any adopted spec.md /
|
|
16
|
-
OKF bundle are ground truth.** Author only what they
|
|
17
|
-
habit and any greenfield template ideal.
|
|
15
|
+
The **code, the deterministic scan, and any adopted spec.md / backend-plan.md /
|
|
16
|
+
frontend-plan.md / source-map.md + the OKF bundle are ground truth.** Author only what they
|
|
17
|
+
evidence; they override any pre-trained habit and any greenfield template ideal.
|
|
18
18
|
|
|
19
19
|
## Evidence discipline
|
|
20
20
|
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
TEMPLATE that onboard installs at
|
|
7
7
|
.appweaver/templates/overrides/delta-design-template.md. It carries the neutral
|
|
8
8
|
bones of a change record — the classification triple, the model bridge, the
|
|
9
|
-
wave discipline, the impact and regression sections, the two ledgers,
|
|
10
|
-
|
|
9
|
+
wave discipline, the impact and regression sections, the two ledgers, what
|
|
10
|
+
happens to the record at merge — and NOTHING ELSE.
|
|
11
11
|
|
|
12
12
|
IT NAMES NO LAYERING AND NO TECHNOLOGY. The "Section" column below is a SLOT:
|
|
13
13
|
it is filled with the ACTUAL sections of the master plan template generated
|
|
@@ -23,9 +23,10 @@
|
|
|
23
23
|
|
|
24
24
|
DISCIPLINE THIS SKELETON ENFORCES (structurally — there is no project content
|
|
25
25
|
here to leak):
|
|
26
|
-
• ONE MASTER PER LANE — a change
|
|
26
|
+
• ONE MASTER PER LANE — a change is planned against
|
|
27
27
|
specs/<Context>/<Feature>/backend-plan.md; it never opens a second plan
|
|
28
|
-
document
|
|
28
|
+
document and never edits the master. Neither does finalize-delta: the
|
|
29
|
+
master is re-derived from the merged spec and model by `plan main`.
|
|
29
30
|
• ADDED / MODIFIED / REMOVED — the complete classification. A shared element
|
|
30
31
|
is MODIFIED in place, never duplicated into the change.
|
|
31
32
|
• EVERY ROW SERVES A SPEC ID — the delta reads the delta-spec and the domain
|
|
@@ -54,8 +55,11 @@
|
|
|
54
55
|
|
|
55
56
|
<!--
|
|
56
57
|
This delta applies to the SINGLE master plan at
|
|
57
|
-
specs/[Context]/[Feature]/backend-plan.md — never a new plan doc
|
|
58
|
-
master
|
|
58
|
+
specs/[Context]/[Feature]/backend-plan.md — never a new plan doc. It does not
|
|
59
|
+
touch the master, and neither does finalize-delta: the master is DERIVED from
|
|
60
|
+
spec.md and domain-model.md, the two documents finalize-delta merges, so
|
|
61
|
+
`plan main` re-derives it rather than patching it from this file. close-delta
|
|
62
|
+
archives this document as the record of how the change was built.
|
|
59
63
|
|
|
60
64
|
IT PLANS AGAINST THE EFFECTIVE MODEL — the master domain-model.md, this change's
|
|
61
65
|
own model delta, and the PREDECESSOR deltas (the open ones in a LOWER wave,
|
|
@@ -145,15 +149,18 @@ changed.]
|
|
|
145
149
|
|
|
146
150
|
## New assumptions / deviations (from this change)
|
|
147
151
|
|
|
148
|
-
<!-- Anything this change defers
|
|
149
|
-
|
|
152
|
+
<!-- Anything this change defers. It stays this document's record — the master's
|
|
153
|
+
DA-n / WA-n ledgers are re-derived with the rest of the master plan, not
|
|
154
|
+
patched from here. -->
|
|
150
155
|
|
|
151
156
|
- **DA-[n]**: [assumption to clarify later] — or `None`
|
|
152
157
|
- **WA-[n]**: [deviation to clear later] — or `None`
|
|
153
158
|
|
|
154
159
|
## On merge
|
|
155
160
|
|
|
156
|
-
`finalize-delta` merges
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
`
|
|
161
|
+
`finalize-delta` merges the change's SPEC half into `spec.md` and its MODEL half into the
|
|
162
|
+
context's `domain-model.md`, in one transaction, and leaves the change OPEN. It does NOT
|
|
163
|
+
merge this document: the master plan is derived from those two, so `plan main` re-derives
|
|
164
|
+
it rather than patching it. `close-delta` archives this file with the change, bumps the
|
|
165
|
+
minor version and writes the Change History rows. None of it is this document's job — it
|
|
166
|
+
records the change, it does not apply it.
|
|
@@ -16,10 +16,11 @@
|
|
|
16
16
|
CODE, never copied.
|
|
17
17
|
|
|
18
18
|
THIS IS A TEMPLATE OF A TEMPLATE. What onboard generates from it is not a
|
|
19
|
-
filled
|
|
20
|
-
`
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
filled plan — it is the project's backend plan TEMPLATE, which the service lane
|
|
20
|
+
of `plan` and `adopt` then fill once per feature into
|
|
21
|
+
specs/<Context>/<Feature>/backend-plan.md. So the generated file keeps
|
|
22
|
+
[PLACEHOLDER] tokens and authoring comments; what onboard replaces is the
|
|
23
|
+
STRUCTURE (which sections exist, what they are called), not the content.
|
|
23
24
|
|
|
24
25
|
WHAT IS PINNED, AND WHY (DESIGN-FORMAT.md § Brownfield variant is the
|
|
25
26
|
authority — these are the only fixed points, and each earns it by being
|
|
@@ -31,8 +32,8 @@
|
|
|
31
32
|
lets a derived layout and the greenfield layout be read by one tool.
|
|
32
33
|
• The traceability spine (§4) and both ledgers (§5 DA-n, §6 WA-n) survive
|
|
33
34
|
every layout — the analyze gates read them.
|
|
34
|
-
• The §0–§6 numbering itself:
|
|
35
|
-
|
|
35
|
+
• The §0–§6 numbering itself: `plan`'s bootstrap_bf branch names those
|
|
36
|
+
sections in its prompt.
|
|
36
37
|
Everything else — how many §2 axes there are, what they are called, what
|
|
37
38
|
lives in each — is the code's answer, not the kit's.
|
|
38
39
|
|
|
@@ -51,13 +52,15 @@
|
|
|
51
52
|
raises, contracts as field shapes. The stack binds at `implement`.
|
|
52
53
|
═══════════════════════════════════════════════════════════════════════════
|
|
53
54
|
-->
|
|
54
|
-
#
|
|
55
|
+
# Backend Plan: [FEATURE]
|
|
55
56
|
|
|
56
|
-
**Feature**: `[Feature]`
|
|
57
|
+
**Feature**: `[Feature]` <!-- the capability scope the developer chose; the aggregate(s) it acts on are the spec's "Acts on aggregate(s)" line -->
|
|
57
58
|
|
|
58
59
|
**Plan home**: `specs/[Context]/[Feature]/backend-plan.md`
|
|
59
60
|
|
|
60
|
-
**Source spec**: `specs/[
|
|
61
|
+
**Source spec**: `specs/[Context]/[Feature]/spec.md`
|
|
62
|
+
|
|
63
|
+
**Source model**: `specs/[Context]/domain-model.md` <!-- where the context has one -->
|
|
61
64
|
|
|
62
65
|
**Version**: v1.0
|
|
63
66
|
|
|
@@ -66,9 +69,10 @@
|
|
|
66
69
|
<!--
|
|
67
70
|
WHAT THIS DOCUMENT IS
|
|
68
71
|
=====================
|
|
69
|
-
The single, living High-Level Design for ONE
|
|
70
|
-
|
|
71
|
-
|
|
72
|
+
The single, living High-Level Design for ONE feature — the capability scope the
|
|
73
|
+
developer chose, over the aggregate(s) its spec says it acts on — and the
|
|
74
|
+
MEDIATOR between spec.md (WHAT/WHY) and the generated code (HOW). It carries
|
|
75
|
+
structure, contracts, behaviour and decisions; it is not code.
|
|
72
76
|
|
|
73
77
|
ANCHORING RULES
|
|
74
78
|
- Every design element traces UP to an id (FR-/BR-/CN-/DC-/IC-/US-/SC-) and the
|
|
@@ -110,11 +114,11 @@
|
|
|
110
114
|
|
|
111
115
|
## 0. Design Overview
|
|
112
116
|
|
|
113
|
-
<!-- One paragraph: the shape of this
|
|
117
|
+
<!-- One paragraph: the shape of this feature and the 3–5 decisions that
|
|
114
118
|
matter most. The project's standing structural convention is NOT restated
|
|
115
119
|
here — it is §1.1. -->
|
|
116
120
|
|
|
117
|
-
[The architectural essence of this
|
|
121
|
+
[The architectural essence of this feature in 3–6 sentences.]
|
|
118
122
|
|
|
119
123
|
### Ubiquitous Language (design delta)
|
|
120
124
|
|
|
@@ -245,9 +249,13 @@
|
|
|
245
249
|
|
|
246
250
|
## Change History
|
|
247
251
|
|
|
248
|
-
<!--
|
|
249
|
-
|
|
250
|
-
|
|
252
|
+
<!-- A row is appended when this plan is RE-DERIVED to carry a change: the plan
|
|
253
|
+
is derived from spec.md and the context's domain-model.md, and finalize-delta
|
|
254
|
+
merges a change into those two, never into this one (`plan main` re-derives
|
|
255
|
+
it). close-delta bumps the version and stamps it into the header, so a header
|
|
256
|
+
ahead of the newest row means the plan has not been re-derived since. A
|
|
257
|
+
change is planned as a DELTA in the change's own directory, never as a new
|
|
258
|
+
plan document. Newest last. -->
|
|
251
259
|
|
|
252
260
|
| Version | Date | Change | User stories | CR |
|
|
253
261
|
|---------|------|--------|--------------|-----|
|
|
@@ -20,7 +20,7 @@ generated_by: aw-spec-onboard/[VERSION]
|
|
|
20
20
|
[DOMAIN: Bounded Contexts / Modules]
|
|
21
21
|
=====================================================================
|
|
22
22
|
- [Context / module] — aggregate root: **[Aggregate]** (adopted — current evidence); owns
|
|
23
|
-
[entities] and the value objects ([named VOs]). See [[domain-
|
|
23
|
+
[entities] and the value objects ([named VOs]). See [[domain-model.domain]].
|
|
24
24
|
- [Named external reference / port] — [role]; not adopted as an aggregate.
|
|
25
25
|
- [… list each bounded context observed in the OKF overview / aggregate index …]
|
|
26
26
|
|
|
@@ -45,4 +45,4 @@ generated_by: aw-spec-onboard/[VERSION]
|
|
|
45
45
|
|
|
46
46
|
## Related
|
|
47
47
|
|
|
48
|
-
- [[domain-
|
|
48
|
+
- [[domain-model.domain]] · [[<generated siblings>]]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
rev-templates/domain-rule-bindings.skeleton.md — SHAPE-ONLY reference for the project's
|
|
3
3
|
domain-rule-bindings.md (resolve-domain reads it by that exact name — the symmetric
|
|
4
|
-
twin of tech-rule-bindings.md, which resolve-tech reads). It shows the five per-command headings, the RULE PARTITION (
|
|
4
|
+
twin of tech-rule-bindings.md, which resolve-tech reads). It shows the five per-command headings, the RULE PARTITION (plan +
|
|
5
5
|
implement get NO domain file), the bullet shape, and the quick-reference
|
|
6
6
|
table. It lists NO real rule files — every bullet is a placeholder for a file
|
|
7
7
|
the author actually generated from THIS project's evidence.
|
|
@@ -15,9 +15,9 @@ Per-command list of the **[PROJECT_NAME] domain** rule files loaded (via `resolv
|
|
|
15
15
|
|
|
16
16
|
**The model (see `.appweaver/RULE-PARTITION.md`):** domain files feed only three
|
|
17
17
|
consumers — **specify** (authors them into the spec as FR/BR/CN-/DC-/IC-), **constitution** (the
|
|
18
|
-
domain principles), and the **gates** (conformance checklist). **
|
|
19
|
-
**NO** domain file — the domain reaches them solely through the spec
|
|
20
|
-
partition; preserve it exactly.
|
|
18
|
+
domain principles), and the **gates** (conformance checklist). **plan** and **implement** load
|
|
19
|
+
**NO** domain file — the domain reaches them solely through the spec's ids and the context's
|
|
20
|
+
domain-model.md. This is the rule partition; preserve it exactly.
|
|
21
21
|
|
|
22
22
|
- All paths are workspace-relative and live in `guardrails/projects/[project]/domain/`.
|
|
23
23
|
- `resolve-domain --command <name>` collects the `- ` bullets under `## /<name>`.
|
|
@@ -32,18 +32,18 @@ partition; preserve it exactly.
|
|
|
32
32
|
|
|
33
33
|
## /aw-spec-specify
|
|
34
34
|
<!-- The FULL generated domain set. Specify models the domain and LIFTS the overlay constraints
|
|
35
|
-
into the spec's CN-/DC-/IC- (
|
|
35
|
+
into the spec's CN-/DC-/IC- (plan/implement never see these files). -->
|
|
36
36
|
- guardrails/projects/[project]/domain/[each generated domain rule file].md
|
|
37
37
|
|
|
38
38
|
## /aw-spec-plan
|
|
39
|
-
_None —
|
|
39
|
+
_None — plan loads no domain file. Domain reaches plan through the spec and the context's domain-model.md._
|
|
40
40
|
|
|
41
41
|
## /aw-spec-implement
|
|
42
|
-
_None — implement loads no domain file. Domain reaches implement
|
|
42
|
+
_None — implement loads no domain file. Domain reaches implement through the lane plans and the context's domain-model.md._
|
|
43
43
|
|
|
44
44
|
## /aw-spec-gate
|
|
45
45
|
<!-- Conformance checklist for the gates (analyze-plan · analyze-slice): the domain-derived
|
|
46
|
-
constraints
|
|
46
|
+
constraints plan/implement were NOT given as authoring input, VALIDATED against output. -->
|
|
47
47
|
- guardrails/projects/[project]/domain/[each *.domain.md overlay to validate against].md
|
|
48
48
|
|
|
49
49
|
---
|
|
@@ -54,6 +54,6 @@ _None — implement loads no domain file. Domain reaches implement only through
|
|
|
54
54
|
|---|---|---|
|
|
55
55
|
| /aw-spec-constitution | [n] | domain principles |
|
|
56
56
|
| /aw-spec-specify | [n] | author into spec (lift → CN-/DC-/IC-) |
|
|
57
|
-
| /aw-spec-plan | 0 | none — spec
|
|
58
|
-
| /aw-spec-implement | 0 | none —
|
|
57
|
+
| /aw-spec-plan | 0 | none — the spec and the domain model carry the domain |
|
|
58
|
+
| /aw-spec-implement | 0 | none — the lane plans and the domain model carry the domain |
|
|
59
59
|
| /aw-spec-gate | [n] | conformance checklist (validate, don't author) |
|