aw-spec-cli 0.1.26 → 0.1.28

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.
Files changed (137) hide show
  1. package/assets/.agents/skills/appweaver/SKILL.md +7 -47
  2. package/assets/.appweaver/ORCH-DIALECT.md +13 -9
  3. package/assets/.appweaver/RULE-PARTITION.md +11 -12
  4. package/assets/.appweaver/STACK-PROFILES.md +260 -108
  5. package/assets/.appweaver/domain.json +1 -1
  6. package/assets/.appweaver/interpreter/run-skill.py +28 -7
  7. package/assets/.appweaver/orchestrator/AGENT-PROFILE.md +38 -10
  8. package/assets/.appweaver/orchestrator/LAUNCHER.md +88 -0
  9. package/assets/.appweaver/orchestrator/README.md +120 -53
  10. package/assets/.appweaver/orchestrator/adapters/claude_agent_sdk.py +1 -1
  11. package/assets/.appweaver/orchestrator/modules/delta-plan.orch.yaml +17 -7
  12. package/assets/.appweaver/orchestrator/modules/domain-select.orch.yaml +6 -4
  13. package/assets/.appweaver/orchestrator/modules/foundation-lane.orch.yaml +237 -0
  14. package/assets/.appweaver/orchestrator/modules/foundation-pack.orch.yaml +392 -0
  15. package/assets/.appweaver/orchestrator/modules/foundation-spec.orch.yaml +548 -0
  16. package/assets/.appweaver/orchestrator/modules/lane-gate.orch.yaml +4 -4
  17. package/assets/.appweaver/orchestrator/modules/tech-select.orch.yaml +3 -3
  18. package/assets/.appweaver/orchestrator/orchestrator.py +162 -63
  19. package/assets/.appweaver/orchestrator/rev-templates/delta-design-template.skeleton.md +5 -4
  20. package/assets/.appweaver/orchestrator/rev-templates/design-template.skeleton.md +3 -1
  21. package/assets/.appweaver/orchestrator/rev-templates/domain-constitution.skeleton.md +2 -2
  22. package/assets/.appweaver/orchestrator/rev-templates/domain-requirements.skeleton.md +2 -2
  23. package/assets/.appweaver/orchestrator/rev-templates/domain-rule-bindings.skeleton.md +21 -13
  24. package/assets/.appweaver/orchestrator/rev-templates/frontend-rule-file.skeleton.md +1 -1
  25. package/assets/.appweaver/orchestrator/rev-templates/tech-rule-bindings.skeleton.md +28 -31
  26. package/assets/.appweaver/orchestrator/rev-templates/technical-constitution.skeleton.md +2 -2
  27. package/assets/.appweaver/orchestrator/rev-templates/technical-rule-file.skeleton.md +1 -1
  28. package/assets/.appweaver/orchestrator/rev-templates/ui-constitution.skeleton.md +3 -3
  29. package/assets/.appweaver/orchestrator/rev-templates/ui-delta-design-template.skeleton.md +4 -1
  30. package/assets/.appweaver/orchestrator/rev-templates/ui-design-template.skeleton.md +6 -4
  31. package/assets/.appweaver/orchestrator/workflows/_smoke.orch.yaml +41 -3
  32. package/assets/.appweaver/orchestrator/workflows/aw-spec-add-component.orch.yaml +1 -1
  33. package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +17 -16
  34. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan-delta.orch.yaml +7 -1
  35. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +7 -1
  36. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +3 -3
  37. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +3 -3
  38. package/assets/.appweaver/orchestrator/workflows/aw-spec-build.orch.yaml +4 -4
  39. package/assets/.appweaver/orchestrator/workflows/aw-spec-characterize.orch.yaml +2 -2
  40. package/assets/.appweaver/orchestrator/workflows/aw-spec-constitution.orch.yaml +61 -12
  41. package/assets/.appweaver/orchestrator/workflows/aw-spec-deploy.orch.yaml +1 -1
  42. package/assets/.appweaver/orchestrator/workflows/aw-spec-foundation.orch.yaml +172 -293
  43. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +253 -44
  44. package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +72 -71
  45. package/assets/.appweaver/orchestrator/workflows/aw-spec-parity.orch.yaml +2 -2
  46. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +38 -14
  47. package/assets/.appweaver/orchestrator/workflows/aw-spec-run.orch.yaml +4 -4
  48. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-bounded-context.orch.yaml +26 -2
  49. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-domain.orch.yaml +21 -15
  50. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-stack.orch.yaml +14 -14
  51. package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +3 -3
  52. package/assets/.appweaver/orchestrator/workflows/aw-spec-ui-model.orch.yaml +3 -3
  53. package/assets/.appweaver/orchestrator/workflows/aw-spec-ui-prototype.orch.yaml +6 -6
  54. package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +371 -131
  55. package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +293 -76
  56. package/assets/.appweaver/scripts/python/aw-foundation.py +885 -20
  57. package/assets/.appweaver/scripts/python/aw-inventory.py +28 -21
  58. package/assets/.appweaver/scripts/python/aw-plan-coverage.py +2 -2
  59. package/assets/.appweaver/scripts/python/aw-rev-constitution.py +6 -6
  60. package/assets/.appweaver/scripts/python/aw-stack.py +676 -99
  61. package/assets/.appweaver/stack-profiles/angular.json +2 -1
  62. package/assets/.appweaver/stack-profiles/blazor-wasm.json +2 -1
  63. package/assets/.appweaver/stack-profiles/dotnet-webapi.json +18 -3
  64. package/assets/.appweaver/stack-profiles/go-chi.json +6 -1
  65. package/assets/.appweaver/stack-profiles/java-spring.json +14 -5
  66. package/assets/.appweaver/stack-profiles/nextjs.json +2 -1
  67. package/assets/.appweaver/stack-profiles/node-express.json +8 -2
  68. package/assets/.appweaver/stack-profiles/python-fastapi.json +9 -2
  69. package/assets/.appweaver/stack-profiles/react-vite.json +2 -1
  70. package/assets/.appweaver/stack-profiles/sveltekit.json +2 -1
  71. package/assets/.appweaver/stack-profiles/vue-vite.json +2 -1
  72. package/assets/.appweaver/stacks.json +40 -1
  73. package/assets/.appweaver/templates/constitution-ui-template.md +2 -2
  74. package/assets/.appweaver/templates/delta-design-template.md +7 -5
  75. package/assets/.appweaver/templates/design-template.md +3 -0
  76. package/assets/.appweaver/templates/ui-delta-design-template.md +1 -1
  77. package/assets/.appweaver/templates/ui-design-template.md +6 -4
  78. package/assets/.claude/skills/appweaver/SKILL.md +7 -46
  79. package/assets/foundations/dotnet/ModularMonolith/ModularMonolith.Reference.rules.json +31 -0
  80. package/assets/foundations/dotnet/ModularMonolith/ModularMonolith.lane.json +6 -0
  81. package/assets/foundations/dotnet/VerticalSlice/VerticalSlice.Reference.rules.json +16 -0
  82. package/assets/foundations/dotnet/VerticalSlice/VerticalSlice.lane.json +6 -0
  83. package/assets/foundations/foundation.spec.template.txt +98 -0
  84. package/assets/foundations/java/Hexagonal/Hexagonal.lane.json +6 -0
  85. package/assets/foundations/java/Hexagonal/Hexagonal.pack.txt +8 -1
  86. package/assets/foundations/java/Hexagonal/unpack-foundation.sh +4 -5
  87. package/assets/foundations/node/NextAppRouter/NextAppRouter.spec.txt +68 -0
  88. package/assets/guardrails/README.md +31 -17
  89. package/assets/guardrails/{projects/dotnet-default-project → project-specific}/domain-rule-bindings.md +25 -13
  90. package/assets/guardrails/project-specific/tech-rule-bindings.md +296 -0
  91. package/assets/guardrails/technology/dotnet-webapi/PACKAGING.md +58 -4
  92. package/assets/guardrails/technology/dotnet-webapi/api-versioning.md +1 -1
  93. package/assets/guardrails/technology/dotnet-webapi/auditing.md +1 -1
  94. package/assets/guardrails/technology/dotnet-webapi/authorization.md +1 -1
  95. package/assets/guardrails/technology/dotnet-webapi/clean-architecture.md +1 -1
  96. package/assets/guardrails/technology/dotnet-webapi/coding-standards.md +1 -1
  97. package/assets/guardrails/technology/dotnet-webapi/cqrs.md +1 -1
  98. package/assets/guardrails/technology/dotnet-webapi/data-classification.md +1 -1
  99. package/assets/guardrails/technology/dotnet-webapi/dependency-policy.md +1 -1
  100. package/assets/guardrails/technology/dotnet-webapi/eventing.md +1 -1
  101. package/assets/guardrails/technology/dotnet-webapi/folder-structure.md +1 -1
  102. package/assets/guardrails/technology/dotnet-webapi/framework-api-map.md +1 -1
  103. package/assets/guardrails/technology/dotnet-webapi/modular-monolith.md +1 -1
  104. package/assets/guardrails/technology/dotnet-webapi/persistence.md +1 -1
  105. package/assets/guardrails/technology/dotnet-webapi/testing.md +1 -1
  106. package/assets/guardrails/technology/dotnet-webapi/vertical-slices.md +1 -1
  107. package/assets/guardrails/technology/dotnet-webapi/zero-trust.md +1 -1
  108. package/assets/guardrails/technology/nextjs/accessibility-implementation.md +1 -1
  109. package/assets/guardrails/technology/nextjs/api-client.md +1 -1
  110. package/assets/guardrails/technology/nextjs/api-integration.dotnet-webapi.md +1 -1
  111. package/assets/guardrails/technology/nextjs/api-integration.java-spring.md +1 -1
  112. package/assets/guardrails/technology/nextjs/auth-session.md +1 -1
  113. package/assets/guardrails/technology/nextjs/client-security.md +1 -1
  114. package/assets/guardrails/technology/nextjs/component-architecture.md +1 -1
  115. package/assets/guardrails/technology/nextjs/component-conventions.md +2 -2
  116. package/assets/guardrails/technology/nextjs/design-system.md +1 -1
  117. package/assets/guardrails/technology/nextjs/error-handling.md +1 -1
  118. package/assets/guardrails/technology/nextjs/forms-and-validation.md +1 -1
  119. package/assets/guardrails/technology/nextjs/frontend-testing.md +1 -1
  120. package/assets/guardrails/technology/nextjs/i18n.md +1 -1
  121. package/assets/guardrails/technology/nextjs/observability.md +1 -1
  122. package/assets/guardrails/technology/nextjs/performance-budgets.md +1 -1
  123. package/assets/guardrails/technology/nextjs/routing-navigation.md +1 -1
  124. package/assets/guardrails/technology/nextjs/state-management.md +1 -1
  125. package/assets/guardrails/technology/nextjs/ui-constitution.md +3 -3
  126. package/package.json +1 -1
  127. package/assets/.agents/skills/appweaver-demo/SKILL.md +0 -59
  128. package/assets/.appweaver/orchestrator/.smoke-out/smoke.md +0 -6
  129. package/assets/.appweaver/orchestrator/workflows/aw-spec-demo.orch.yaml +0 -100
  130. package/assets/.claude/skills/appweaver-demo/SKILL.md +0 -59
  131. package/assets/.kilocode/agent.json +0 -28
  132. package/assets/.kilocode/workflows/appweaver-demo.md +0 -59
  133. package/assets/.kilocode/workflows/appweaver.md +0 -55
  134. package/assets/guardrails/projects/dotnet-default-project/tech-rule-bindings.md +0 -271
  135. /package/assets/guardrails/{projects/dotnet-default-project → project-specific}/domain/domain-requirements.md +0 -0
  136. /package/assets/guardrails/{projects/dotnet-default-project → project-specific}/frontend/ui-requirements.md +0 -0
  137. /package/assets/guardrails/{projects/dotnet-default-project → project-specific}/service/technical-requirements.md +0 -0
@@ -9,7 +9,7 @@ makes) and `.appweaver/stack-profiles/*.json` (the candidates it chooses from).
9
9
 
10
10
  ## 1. What a lane is
11
11
 
12
- A **lane** is a technology target that owns disjoint source paths, compiles and tests
12
+ A **lane** is a technology target that owns its own lane root folder, compiles and tests
13
13
  independently, and carries its own technical rules and constitution half — while
14
14
  *sharing* the feature, the spec, the domain, the version, and the knowledge bundle with
15
15
  every other lane.
@@ -22,7 +22,7 @@ and why nothing in the format assumes there are only two lanes.
22
22
 
23
23
  **Lanes get map headings, never a `--lane` flag.** Rule maps are keyed on
24
24
  `## /aw-spec-<name>`, so a lane's rule set is reached under its own heading —
25
- `## /aw-spec-implement-ui` for the frontend lane of `implement`, and so on. A flag would have
25
+ `## /aw-spec-implement-frontend` for the frontend lane of `implement`, and so on. A flag would have
26
26
  no heading to key on. Where the two lanes are folded into one command, as `plan` and
27
27
  `implement` are, the heading names the lane and the command dispatches on the declaration.
28
28
 
@@ -33,9 +33,10 @@ no heading to key on. Where the two lanes are folded into one command, as `plan`
33
33
  ```jsonc
34
34
  {
35
35
  "lanes": [
36
- { "id": "service", "kind": "backend", "profile": "dotnet-webapi", "root": "src/",
37
- "build": "dotnet build", "test": "dotnet test", "run": "dotnet run --project src/" },
38
- { "id": "web", "kind": "frontend", "profile": "react-vite", "root": "web/",
36
+ { "id": "service", "kind": "backend", "profile": "dotnet-webapi", "root": "backend/",
37
+ "run_from": "root",
38
+ "build": "dotnet build", "test": "dotnet test", "run": "dotnet run --project {{host}}" },
39
+ { "id": "web", "kind": "frontend", "profile": "react-vite", "root": "frontend/",
39
40
  "run_from": "root",
40
41
  "build": "npm run build", "test": "npm test", "run": "npm run dev" }
41
42
  ],
@@ -50,16 +51,19 @@ no heading to key on. Where the two lanes are folded into one command, as `plan`
50
51
  | `profile` | the stack profile the lane was declared from. Records the technology, so re-stacking a lane never renames the lane. `detected` where the kit ships no profile for what the code is |
51
52
  | `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
53
  | `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
- | `root` | the lane's **source path** — the subtree it owns (see §3). `--source-path` and `--root` are two spellings of this one field |
54
+ | `root` | the **lane root folder** — the subtree the lane owns (see §3). `--source-path` and `--root` are two spellings of this one field |
54
55
  | `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 |
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 |
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
+ | `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 root folder. `select-stack` decides it from where the stack's build file sits — see §3.1 |
57
+ | `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`, as seen from where the command runs (§4.2) |
57
58
  | `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) |
59
+ | `contract_run` | *(optional, backend)* the command that starts the service on its own so `implement` can export its API document (§4.5). Absent, the profile's is used. `foundation` sets it from a pack's `<pack>.lane.json` |
60
+ | `contract` | *(optional, backend)* the URL the running service serves its API document at (§4.5). Absent, the profile's is used |
61
+ | `client` | *(optional, frontend)* the command that generates the frontend's API client from that document; `{{contract}}` in it is the document's path. No shipped profile has one: `implement` asks the developer the first time and records the answer here (§4.5) |
58
62
 
59
63
  ### Three properties matter more than the schema
60
64
 
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,
65
+ 1. **`root` is ownership, and it is the lane root folder.** `implement`'s service lane writes its
66
+ code only under the backend lane root folder and its frontend lane only under the frontend one,
63
67
  `build`/`test`/`run` act on it, and `onboard` reverse-engineers out of it. The lane's
64
68
  **test path** (§4.3) is owned the same way, and is where `implement` writes the lane's tests.
65
69
  2. **The commands are project-supplied strings.** The engine stays stdlib-only and
@@ -71,33 +75,39 @@ no heading to key on. Where the two lanes are folded into one command, as `plan`
71
75
 
72
76
  ### 3.1 `root` is where the code is; `run_from` is where the commands run
73
77
 
74
- These are two questions and for most stacks they have two answers. `dotnet build` needs the
75
- solution file at the top while the lane owns `src/`; `go build ./...` needs the go.mod there
76
- while the lane owns `internal/`. Those profiles leave `run_from` at its default and run at
77
- the repository root.
78
-
79
- A stack whose **manifest lives inside the subtree** needs the other answer, and cannot work
80
- without it: a Maven wrapper at `backend/orderservice/mvnw` is unreachable from the repository
81
- root, so `java-spring` declares `run_from: "root"`. The five npm frontends and `blazor-wasm`
82
- declare it too, which is what let their commands stop hardcoding `--prefix web` — the lane
83
- already declares that directory, and a lane rooted at `client/` was previously building `web/`
84
- regardless.
78
+ These are two questions, and an existing repository can give them two answers: a classic
79
+ .NET repository keeps its solution file at the top while the lane owns `src/`, and a classic
80
+ Go repository keeps `go.mod` there while the lane owns `internal/`. Their commands have to run
81
+ at the repository root.
82
+
83
+ **A new project is one self-contained folder per lane.** Every backend profile defaults to
84
+ `backend/` and every frontend profile to `frontend/`, with `run_from: "root"`: the lane's
85
+ build files, source and tests all live inside its lane root folder, and its commands run there.
86
+ `foundation` builds exactly that — a pack unpacks into the lane root folder as packed, a spec build
87
+ creates the whole project inside it — and reports a lane that does not run its commands in its
88
+ own folder or keeps its tests outside it, rather than scaffolding a project its own build
89
+ cannot find.
90
+
91
+ **`select-stack` answers `run_from` from the disk.** Each backend profile lists the files its
92
+ commands need (`build_files`: `*.sln`, `pom.xml`, `go.mod`, `package.json`, …). When a lane is
93
+ declared, `lane-declare` looks for them: directly inside the lane root folder ⇒ `root`; only at the
94
+ repository root ⇒ `repo`; nowhere yet — a greenfield lane — ⇒ the profile's own answer, which is
95
+ inside the lane. So a classic repository keeps the shape it has, a Maven module whose `mvnw`
96
+ sits in the lane root folder runs there, and a new project gets the self-contained layout, from
97
+ one profile. `--run-from` overrides it, and so does editing `stacks.json`.
85
98
 
86
99
  Two consequences worth knowing:
87
100
 
88
- - **Declare a `run_from: "root"` lane at the directory holding its manifest**, not at its
89
- source directory. `java-spring`'s profile default root is `src/main/java/`, which is where
90
- the *code* goes; detection proposes the directory `pom.xml` actually sits in, which is the
91
- one to accept. A lane rooted at `src/main/java/` will fail loudly with `./mvnw: not found`
92
- rather than silently.
101
+ - **Declare a lane at the directory holding its build file** when the code already exists and
102
+ should run there — a Maven lane at the folder with `pom.xml`, not at `src/main/java/`.
93
103
  - **A single-module project declares `.`** — stored as `./`, the repository root. It owns
94
104
  everything, so it overlaps every other lane by definition and cannot be combined with one.
95
105
 
96
- ### Where the source path is checked against the disk
106
+ ### Where the lane root folder is checked against the disk
97
107
 
98
108
  `lane-declare` records a path; it does **not** require the path to exist. In a greenfield
99
- project it does not exist yet — the whole point of declaring `src/` on day one is that
100
- `implement` will create it.
109
+ project it does not exist yet — the whole point of declaring `backend/` on day one is that
110
+ `foundation` and `implement` will create it.
101
111
 
102
112
  `lane-verify` is the check, and it runs where the path has to be real: before `implement`,
103
113
  `build`, `run`, and before `onboard` reverse-engineers a codebase out of it. That split is what lets one file serve a project with no code yet and a project that is
@@ -108,7 +118,7 @@ Two failure classes, deliberately distinguished:
108
118
  - **MISSING** — the phase needs a lane of this kind and none is declared. Reported only for
109
119
  the kinds a caller names with `--require`, because "no frontend lane" is a correct and
110
120
  common state that must never fail a backend build.
111
- - **BROKEN** — a lane *is* declared and its source path is empty, is not a directory, or
121
+ - **BROKEN** — a lane *is* declared and its lane root folder is empty, is not a directory, or
112
122
  overlaps another lane's. Checked for **every** declared lane whether or not the caller
113
123
  asked about it. A stack named against a path that is not there is a mistake in every
114
124
  phase, and letting it through means discovering it as an empty build or a generator
@@ -118,11 +128,25 @@ Two failure classes, deliberately distinguished:
118
128
 
119
129
  ### The file ships empty, and may be filled in by hand
120
130
 
121
- The kit installs `.appweaver/stacks.json` holding `{"lanes": []}`, alongside `domain.json` and
131
+ The kit installs `.appweaver/stacks.json` with an empty `lanes` array, alongside `domain.json` and
122
132
  `feature.json`. It is there so a project can open it and fill it in directly — the block at
123
133
  the top of this section is the whole schema, and `select-stack` writes nothing a person
124
134
  could not have typed.
125
135
 
136
+ **The shipped file carries a sample under `_lane_format`**: one backend and one frontend lane
137
+ with every field above filled in (.NET and Next.js), plus a `prototype` block. No command
138
+ reads that key, and `select-stack` leaves it in place, so it is safe to keep. To fill the file
139
+ by hand, copy the lanes you need into `lanes` and edit them. The samples' optional fields hold
140
+ values that change nothing when copied (`stack` is the profile's own title, `origin: "declared"`
141
+ is the default, `test_root` is the profile's own answer), except `migrate`, which is a real
142
+ command: keep it only if the lane has migrations to run. The frontend sample's `migrate` is
143
+ empty because a Next.js lane has none.
144
+
145
+ **Never leave an empty or half-filled entry in `lanes`.** Every entry there is a declared lane,
146
+ whatever its values: `select-stack` replaces a lane only when its `id` matches, so it adds its
147
+ lane beside a blank one instead of replacing it, and `lane-verify` then stops every phase on the
148
+ blank lane's missing lane root folder.
149
+
126
150
  **An empty file and an absent one are the same state.** Shipping the file means its presence
127
151
  no longer says anything about whether a lane was declared, so `declared` reports on the
128
152
  `lanes` array and never on the file: `resolve` reports `exists: true, declared: false` for
@@ -131,7 +155,7 @@ the file is missing, naming which of the two it found.
131
155
 
132
156
  Hand-editing and `select-stack` are the same file and the same schema, so they interleave
133
157
  freely: fill in the service lane by hand and run `select-stack` later to add the UI one, or
134
- run it first and correct a source path in the editor. What hand-editing gives up is the
158
+ run it first and correct a lane root folder in the editor. What hand-editing gives up is the
135
159
  checking `select-stack` does on the way in — an unknown `profile`, a missing `root`, and two
136
160
  lanes whose roots overlap are all refused there and merely written here. `lane-verify` still
137
161
  catches the last two before any phase acts on them; a profile id that matches nothing
@@ -152,7 +176,7 @@ Declaring **`(none)`** for a lane is a real answer and the way a project says it
152
176
 
153
177
  ## 3. The ownership rule
154
178
 
155
- **Lane roots own their subtree and must be disjoint.** Two generators must never be able
179
+ **Lane root folders own their subtree and must be disjoint.** Two generators must never be able
156
180
  to write the same file. A lane owns its test path too, so the rule covers both: no source or
157
181
  test path of one lane may overlap a source or test path of another.
158
182
 
@@ -182,13 +206,25 @@ workflow.**
182
206
  "title": "React + Vite", // shown in the menu
183
207
  "language": "TypeScript", // shown in the menu, parenthesised
184
208
  "ecosystem": "node", // which stack-partitioned corpus this stack draws from
185
- "root": "web/", // the lane root this profile defaults to
209
+ "root": "frontend/", // the folder a NEW lane of this stack gets — see §3.1
186
210
  "test_root": "", // optional — where tests go when not inside root — see §4.3
187
- "run_from": "root", // where the commands run — see §3.1
211
+ "run_from": "root", // where the commands run for a new lane — see §3.1
212
+ "conventional_root": "web/", // optional — where this stack's code usually sits in an
213
+ // EXISTING repository; `detect` is written against it
214
+ "conventional_test_root": "", // optional — the test path that pairs with it — see §4.3
215
+ "build_files": [], // optional — the files its commands need (`*.sln`,
216
+ // `go.mod`); decides `run_from` at declare time — §3.1
188
217
  "build": "npm run build",
189
218
  "test": "npm test",
190
219
  "run": "npm run dev",
220
+ "host_patterns": [], // optional — where the runnable project sits, for
221
+ // `{{host}}` in a command — see §4.2
191
222
  "migrate": "", // optional — schema migration, if the stack has one
223
+ "contract_run": "", // optional, backend — start the service to export
224
+ // its API document — see §4.5
225
+ "contract": "", // optional, backend — the URL it serves it at
226
+ "client": "", // optional, frontend — generate the API client
227
+ // from `{{contract}}` — see §4.5
192
228
  "prototype_run": "npm run dev --prefix prototype", // frontend only — fills the prototype block
193
229
  "reserved_names": ["Event", "Request", "Response"], // optional — names this stack's AMBIENT
194
230
  // scope already defines; see §4.4
@@ -209,49 +245,96 @@ trees that are physically per-ecosystem:
209
245
 
210
246
  | Tree | Keyed by | Read through |
211
247
  |---|---|---|
212
- | `foundations/<ecosystem>/<Name>/` | `ecosystem` | `foundation-list`, `foundation-locate` |
248
+ | `foundations/<ecosystem>/<Name>/` | `ecosystem`, then each pack/spec's `# kind:` / `# profiles:` header | `foundation-list`, `foundation-locate` |
213
249
  | `foundations/<ecosystem>/components/<C>/` | `ecosystem` | `list-components`, `add-component` |
214
250
 
215
251
  **Why those two are filtered and the rule corpora are not.** A foundation bundle is a whole
216
252
  compiling project and a component runbook edits a host project's build files — both are
217
253
  executable artifacts in one toolchain, so offering a `dotnet` bundle to a Spring repo does not
218
254
  produce a scaffold, it produces a second foreign project beside the real one. `foundation-list`
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
255
+ therefore narrows by ecosystem, and reports `all_foundations` alongside so the pick list can say
256
+ *what exists and how much of it applies*. `--ecosystem` narrows to the ONE lane being
221
257
  scaffolded, which is what `foundation` passes: a repo declaring a .NET service and a Node UI
222
258
  must not be offered its .NET bundle while the UI lane is the target. Without that argument the
223
259
  filter falls back to every declared lane's ecosystem, and a repo that has declared nothing yet
224
260
  is not filtered — there is no answer to filter on.
225
261
 
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.
262
+ **Ecosystem alone is not narrow enough for a lane**, because it is shared across kinds: the
263
+ five JavaScript frontends and `node-express` are all `node`, and `blazor-wasm` shares `dotnet`
264
+ with the service stacks. So each pack and spec may open with header lines that `foundation-list`
265
+ filters on as well — `# kind: backend|frontend` (absent = backend, the kind every pack shipped
266
+ before the key existed scaffolds) and `# profiles: <id>, …` (absent = any profile of that kind).
267
+
268
+ **Packs and specs.** A directory under `foundations/<ecosystem>/` may hold a pack
269
+ (`<Name>.pack.txt` — a verified project unpacked byte-for-byte), any number of specs
270
+ (`*.spec.txt` — a description an agent builds the project from), or both; each is its own
271
+ entry in the lane's pick list, after which come `[manual spec]` (a spec given at the prompt)
272
+ and `[skip]`. `foundations/foundation.spec.template.txt` is the shape of a spec. A spec build
273
+ loads the technical rules the active project maps under `## /aw-spec-foundation-backend` or
274
+ `## /aw-spec-foundation-frontend` (§7), and the spec it used is kept at
275
+ `.appweaver/foundation/<lane>.spec.md`.
276
+
277
+ **A scaffold lands in a lane.** `foundation` scaffolds into the FOLDER of each lane — its
278
+ lane root folder, never a directory named on the command line — and the lane is self-contained
279
+ (§3.1): a pack unpacks there exactly as packed, build files, source and tests together, and a
280
+ spec build creates the whole project there and may write nowhere else — checked, not trusted:
281
+ the repo is recorded before the build and compared after it, and a build that created, changed
282
+ or deleted anything outside the lane root folder is "NOT accepted", with what it created there
283
+ removed again and what it changed or deleted listed for the operator. One run covers every
284
+ declared lane, backend then frontend, and each lane gets its own pick. A lane that does not run
285
+ its commands in its lane root folder, or keeps its tests outside it, is reported and passed over. A
286
+ lane that already holds files — any file in its source or test path — is asked "Overwrite?"
287
+ before its pick list; "no" skips the lane, and "yes" replaces same-path files and deletes
288
+ nothing. A pack's reference sample goes on top of the base project the same way. The one
289
+ exception is a pack's **rule file** (`<pack name>.rules.json` beside the pack): the files and
290
+ folders it names with `merge` are combined with the file already there, by an agent following
291
+ the rule's own instruction, instead of being replaced — the .NET reference samples use it to
292
+ merge their DbContext into the base project's. The format is in
293
+ [PACKAGING.md](../guardrails/technology/dotnet-webapi/PACKAGING.md#the-rule-file-pack-namerulesjson).
294
+ This is what makes every phase's "write only into your lane" rule true of the scaffold
295
+ as well as of generated code: `implement`, `build`, `test` and `run` all resolve the same
296
+ paths. Lanes must therefore be declared before `foundation` runs, which is why `select-stack`
297
+ is the first command of both sequences.
235
298
 
236
299
  The technical RULE corpora at `guardrails/technology/<stack>/` are keyed by **profile id**, not
237
300
  ecosystem, because Spring and Quarkus are both `java` and do not share a persistence rule. They
238
301
  are selected through the project's `tech-rule-bindings.md` rather than filtered here — see §7.
239
302
 
240
- ### 4.2 `run_from`, `{{root}}` and `migrate`
303
+ ### 4.2 `run_from`, `{{root}}`, `{{host}}` and `migrate`
241
304
 
242
- A profile command sometimes has to name the source path — `dotnet run --project src/`. Written
305
+ A profile command sometimes has to name the lane root folder — `python -m compileall app`. Written
243
306
  literally that is the profile's *default* root frozen into the command, and it stays frozen when
244
307
  a lane declares a different one. Write **`{{root}}`** instead and it resolves to the lane's own
245
- root, expanded at run time so a hand-edited `root` in `stacks.json` stays consistent with it:
308
+ root as seen from where the command runs, expanded at run time so a hand-edited `root` in
309
+ `stacks.json` stays consistent with it:
246
310
 
247
311
  ```jsonc
248
- "run": "dotnet run --project {{root}}", // Services/Api/ ⇒ dotnet run --project Services/Api
249
- "build": "python -m compileall {{root}}" // repo-root lane ⇒ python -m compileall .
312
+ "build": "python -m compileall {{root}}" // app/, run at the repo root ⇒ python -m compileall app
313
+ // a run_from: root lane ⇒ python -m compileall .
314
+ ```
315
+
316
+ Some commands need a *project* inside the lane root folder rather than the folder itself.
317
+ `dotnet run --project <folder>` needs exactly one project in that folder, and a solution has
318
+ several — a pack's runnable one is `src/<App>.AppHost`, the .NET Aspire host. Write
319
+ **`{{host}}`** and it resolves to that project's folder, from the profile's `host_patterns`:
320
+ paths relative to the lane root folder, best first, `**/` for any depth, case ignored. The
321
+ first pattern that matches exactly one file names the project; build output, dependency and
322
+ test folders are never searched. It is looked up when the command is read, so a project
323
+ scaffolded after the lane was declared is found, and with no single match `{{host}}` falls
324
+ back to `{{root}}`:
325
+
326
+ ```jsonc
327
+ "run": "dotnet run --project {{host}}",
328
+ "host_patterns": ["**/*.AppHost.csproj", "**/*.Api.csproj", "**/*.Service.csproj"]
329
+ // backend/ holding src/Acme.AppHost/ and src/Acme.Service/ ⇒ dotnet run --project src/Acme.AppHost
330
+ // backend/ holding src/Acme.Api/ and src/Acme.Domain/ ⇒ dotnet run --project src/Acme.Api
250
331
  ```
251
332
 
252
333
  A command with no placeholder is used verbatim, so every literal command already on disk is
253
- unaffected. `{{root}}` is the answer when the command needs the *path*; `run_from: "root"` (§3.1)
254
- is the answer when it needs the *working directory*.
334
+ unaffected. A lane keeps the command it was declared with: one declared before `{{host}}` was
335
+ in its profile picks it up when select-stack declares it again. `{{root}}` and `{{host}}` are
336
+ the answer when the command needs a *path*; `run_from: "root"` (§3.1) is the answer when it
337
+ needs the *working directory*.
255
338
 
256
339
  `migrate` is an optional fourth phase command, alongside `build`/`test`/`run`. **No shipped
257
340
  profile declares one**, deliberately: Flyway, Liquibase, Alembic, Prisma and golang-migrate all
@@ -263,17 +346,21 @@ elsewhere rather than implying a migration succeeded.
263
346
  `detect` is an **ANY-of** list, not an all-of: `java-spring` names both `pom.xml` and
264
347
  `build.gradle` because a project has one or the other, and requiring both would make it
265
348
  undetectable. `stack-detect` scores a profile by how many of its patterns hit. Patterns are
266
- matched first as written (repo-root-relative, with `**` spanning separators), then relocated
267
- under any directory prefix — so a profile shipping `web/package.json` still recognises the
268
- same stack in `client/` or `apps/portal/`, which a brownfield repo that never saw our
349
+ written against the layout an EXISTING repository of the stack usually has — the profile's
350
+ `conventional_root` (`web/package.json`, `src/**/*.csproj`) — and matched first as written
351
+ (repo-root-relative, with `**` spanning separators), then relocated under any directory prefix,
352
+ with that root segment substituted — so a profile shipping `web/package.json` still recognises
353
+ the same stack in `client/` or `apps/portal/`, which a brownfield repo that never saw our
269
354
  profiles will need. An anchored hit outranks a relocated one.
270
355
 
271
356
  `detect` and `root` are independent, and deliberately so: `go-chi` detects on `go.mod` at the
272
- repo root and declares a root of `internal/`. Detection reports the shallowest directory its
273
- evidence actually sits in, and that directory is what `select-stack` offers first when it asks
274
- for the lane's source path — ahead of the profile's own default, because a repo that already
275
- exists did not choose its layout from our profiles. The operator picks from the list or types
276
- a path the kit has never seen.
357
+ repo root, keeps its code under `internal/` in an existing repository, and gives a new lane
358
+ `backend/`. Detection reports the shallowest directory its evidence actually sits in; evidence
359
+ only at the repository root (`App.sln`, `go.mod`) defers to the `conventional_root` when that
360
+ directory is there, then to `root`. That directory is what `select-stack` offers first when it
361
+ asks for the lane root folder — ahead of the profile's own default, because a repo that
362
+ already exists did not choose its layout from our profiles. The operator picks from the list or
363
+ types a path the kit has never seen.
277
364
 
278
365
  **Detection leads the menu; it never fills `preselect`.** The engine treats `preselect_from`
279
366
  as the answer, ahead of a headless `--select`, so filling it from a scan would let a repo's
@@ -323,47 +410,98 @@ Absent, nothing is reported and generation is unchanged.
323
410
 
324
411
  ### 4.3 `test_root` — where a lane's tests go
325
412
 
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.
413
+ In an existing repository most backend stacks keep their tests **beside** the source, not
414
+ inside it: .NET's `tests/` next to `src/`, Maven's `src/test/java/` next to `src/main/java/`,
415
+ pytest's `tests/` next to `app/`. A new, self-contained lane keeps them inside its lane root folder
416
+ (`backend/tests/`). Either way each lane owns a **test path**, and `implement` writes the lane's
417
+ paired tests there.
330
418
 
331
- A profile's `test_root` is the test path that pairs with its default `root`. `lane-resolve` and
419
+ A profile's `test_root` is the test path that pairs with its default `root`, and its
420
+ `conventional_test_root` the one that pairs with its `conventional_root`. `lane-resolve` and
332
421
  `lane-verify` report it per kind as `backend_test_path` / `frontend_test_path`, worked out at
333
422
  read time:
334
423
 
335
424
  | The lane | Its test path |
336
425
  |---|---|
337
426
  | 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` |
427
+ | sits at the profile's default root (`backend/`) | the profile's `test_root` (`backend/tests/`) |
428
+ | sits at a path ending in the default root (`apps/backend/`) | the profile's `test_root` under the same prefix (`apps/backend/tests/`) |
429
+ | sits at — or at a path ending in — the conventional root (`src/`, `svc/src/main/java/`) | the `conventional_test_root`, the same way (`tests/`, `svc/src/test/java/`) |
430
+ | anything else, or the profile declares neither | the lane root folder itself |
431
+
432
+ The last row is right for every shipped frontend (tests under `frontend/tests/`), for Go's
433
+ colocated `_test.go` files, and for a lane at any other folder name, whose tests are inside it.
434
+ A layout the table gets wrong is fixed by writing `test_root` on the lane in `stacks.json`.
435
+
436
+ | Profile | `root` | `test_root` | `conventional_root` | `conventional_test_root` |
437
+ |---|---|---|---|---|
438
+ | `dotnet-webapi` | `backend/` | `backend/tests/` | `src/` | `tests/` |
439
+ | `java-spring` | `backend/` | `backend/src/test/java/` | `src/main/java/` | `src/test/java/` |
440
+ | `python-fastapi` | `backend/` | `backend/tests/` | `app/` | `tests/` |
441
+ | `node-express` | `backend/` | `backend/tests/` | `src/` | `tests/` |
442
+ | `go-chi` | `backend/` | *(inside the lane root folder)* | `internal/` | — |
443
+ | every frontend | `frontend/` | *(inside the lane root folder)* | `web/` | — |
444
+
445
+ ### 4.5 `contract_run`, `contract` and `client` — the API contract between the lanes
446
+
447
+ The frontend lane binds to the service through the service's published API document, and
448
+ the document that counts is the one the **built code** serves. `implement` therefore runs
449
+ three steps between the two lanes, each of which warns rather than stops:
450
+
451
+ 1. **Export** (`contract-export`) — start the service with the backend lane's
452
+ `contract_run`, fetch its `contract` URL, write the document to
453
+ `<service lane root folder>/contracts/openapi.json`, and stop the service with
454
+ everything it started. A service already answering at the URL is used as it is, and
455
+ the run says it may be older than the code.
456
+ 2. **Client** (`contract-client`) — run the frontend lane's `client` command, with
457
+ `{{contract}}` standing for that document as seen from where the command runs.
458
+ 3. **Check** (`contract-check`) — every operation id the frontend plan cites must be an
459
+ `operationId` in the document.
460
+
461
+ Each key is read from the lane first and from its profile second, so a lane declared
462
+ before the keys existed still answers them. Where the values come from:
463
+
464
+ | Key | Profile default | Set on the lane by |
348
465
  |---|---|---|
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)* |
466
+ | `contract_run` | `dotnet-webapi`: `dotnet run --project {{host}} -- --environment Development --urls http://localhost:5000`<br>`java-spring`: `./mvnw spring-boot:run -Dspring-boot.run.arguments=--server.port=8080` | `foundation`: on the pack route from the pack's `<pack>.lane.json` — the shipped .NET packs start their API project alone (the AppHost needs Docker), the Java `Hexagonal` pack its one Maven module; on the spec route from the spec's `# contract-run:` header line, or the operator's answer when it has none |
467
+ | `contract` | `dotnet-webapi`: `http://localhost:5000/swagger/v1/swagger.json`<br>`java-spring`: `http://localhost:8080/v3/api-docs` (springdoc's default path) | `foundation`, the same way — the `# contract:` header line on the spec route |
468
+ | `client` | none — no generator is assumed | `foundation` on the spec route, from the `# client:` header line or the operator's answer; else `implement`, which asks the first time and records the answer |
469
+
470
+ On the spec route an empty answer keeps the lane's current value, so a lane nobody set
471
+ anything on stays on the profile's default. `foundations/foundation.spec.template.txt`
472
+ shows the three header lines; `<App>` in them is the app name given at `foundation`.
473
+
474
+ `--environment Development` matters for the .NET packs: outside Development they redirect
475
+ to HTTPS, and the export fetches over http. Their API projects also read
476
+ `ConnectionStrings:SqliteConnection` when started without the AppHost, and the packs'
477
+ configuration carries none, so their commands pass one. A pack's `<pack>.lane.json` sits
478
+ beside its `.pack.txt`, names the pack's sentinel the way its files do, and is renamed with
479
+ them:
480
+
481
+ ```json
482
+ {"format": "aw-spec-pack-lane/v1",
483
+ "contract_run": "dotnet run --project src/AppWeaver.VerticalSlice.Service -- --environment Development --urls http://localhost:5000 --ConnectionStrings:SqliteConnection \"Data Source=App_Data/app.db\"",
484
+ "contract": "http://localhost:5000/swagger/v1/swagger.json"}
485
+ ```
354
486
 
355
487
  ---
356
488
 
357
489
  ## 5. Who may write this file
358
490
 
359
- **`lane-declare` writes it. Nothing else does, and it has exactly ONE caller** — so the
360
- rule is a grep, not a convention:
491
+ **`lane-declare` writes whole lanes, and it has exactly ONE caller** — so the rule is a
492
+ grep, not a convention:
361
493
 
362
494
  ```sh
363
495
  grep -rl "lane-declare" .appweaver/orchestrator/workflows/ # must print exactly one file
364
496
  # aw-spec-select-stack.orch.yaml — a person picks
365
497
  ```
366
498
 
499
+ **`lane-set` changes three keys on a lane that is already there, and nothing else** —
500
+ `contract_run`, `contract` and `client` (§4.5). Its callers are `foundation`, recording
501
+ what a pack knows about starting its service, and `implement`, recording the client
502
+ command a developer types. They cannot use `lane-declare`, which rebuilds the whole lane
503
+ and would reset every command on it to the profile's default.
504
+
367
505
  `onboard` used to be a second caller. Before `select-stack` was the first command of the
368
506
  brownfield sequence a repo could reach `onboard` undeclared, so `onboard` ran its own
369
507
  detection pass, recorded what it found marked `origin: reverse-engineered`, and was gated
@@ -390,11 +528,12 @@ re-runs the roots-disjoint one on every read (see §2).
390
528
 
391
529
  Consequences worth stating, because they are the reason for the design:
392
530
 
393
- - **`foundation` / `ui-foundation` scaffold *against* a declared lane** and verify what
394
- they produced against it — stopping `STACK-MISMATCH:` rather than rewriting the
395
- declaration to fit the scaffold. A wrong declaration surfaces at the scaffold instead of
396
- being absorbed by it, and the lane a project *chose* stays the authority over the lane a
397
- generator *happened to produce*.
531
+ - **`foundation` scaffolds *into* a declared lane** and never rewrites the declaration to
532
+ fit what it produced. Each lane is scaffolded into its own source and test paths, and a
533
+ spec build must leave a project of the lane's own stack inside the lane — otherwise that
534
+ lane is reported "NOT usable" and the run moves on to the next one. A wrong declaration
535
+ surfaces at the scaffold instead of being absorbed by it, and the lane a project *chose*
536
+ stays the authority over the lane a generator *happened to produce*.
398
537
  - **`build` runs every declared lane's own build command**, in declaration order, and
399
538
  fails if any lane fails. Without a declaration it falls back to the historic
400
539
  first-match-wins sniffer, which is right for one lane and silently skips the second —
@@ -411,7 +550,7 @@ follows from it:
411
550
  | Which rule sets does `onboard` generate? | the same, per lane — `service/` and `frontend/` under the project folder |
412
551
  | Which subtree does `implement` write into? | that lane's `root` |
413
552
  | What does `build` compile, and `run` migrate? | every declared lane's own command, against its own root |
414
- | What does `onboard` scan? | the declared source paths — backend signals from outside the UI subtree, frontend signals from inside it |
553
+ | What does `onboard` scan? | the declared lane root folders — backend signals from outside the UI subtree, frontend signals from inside it |
415
554
 
416
555
  This is a change of authority, and worth being explicit about. The question "does this
417
556
  project have a UI lane" used to be answered by OR-ing three heuristics: a
@@ -441,13 +580,15 @@ constraint where it previously had none. That is a deliberate trade: what it buy
441
580
  every later phase can ask this file a question and get an answer, instead of inferring one.
442
581
 
443
582
  ```
444
- GREENFIELD select-stack → select-domain → constitution → select-feature → specify → …
583
+ GREENFIELD select-stack → constitution → [select-feature] → specify → …
445
584
  BROWNFIELD select-stack → onboard → select-feature → adopt → …
446
585
  ```
447
586
 
448
- `select-domain` is greenfield-only: `onboard` derives the project from the code and
449
- activates it. On an existing codebase `select-stack` is mostly confirmation — it detects the
450
- stack and the source path from the repo's own files and leads the menu with them — but it is
587
+ Neither sequence selects a project. Greenfield uses `guardrails/project-specific/`, which
588
+ `.appweaver/domain.json` names as shipped; `onboard` creates `guardrails/brf-<domain>/` from
589
+ the code and points `domain.json` at it. The optional `select-domain` switches `domain.json` to
590
+ another project folder under `guardrails/` that already exists. On an existing codebase `select-stack` is mostly confirmation — it detects the
591
+ stack and the lane root folder from the repo's own files and leads the menu with them — but it is
451
592
  a person's confirmation, which is what detection on a legacy repo needs.
452
593
 
453
594
  ## 6. Subcommands
@@ -460,7 +601,11 @@ implementation and the twins cannot drift.
460
601
  |---|---|
461
602
  | `stack-list [--kind k] [--preselect text] [--include-none] [--suggest]` | enumerate the installed profiles; returns `select_options[]`, and `preselect` when the text names one of *this kind's* ids. `--suggest` also runs detection, so the menu **leads with** the stack this repo's files point at and `root_options[]` leads with the directory the evidence sits in |
462
603
  | `stack-detect [--kind k] [--exclude-root p]…` | match the repo's OWN files against the installed profiles — the brownfield candidate list, since an existing repo already answered the question. Writes nothing. `--exclude-root` keeps a service profile from relocating onto a subtree another lane owns |
463
- | `lane-declare --profile <id\|label\|none\|detected> [--kind k] [--id lane] [--source-path\|--root p] [--stack label] [--origin declared\|reverse-engineered] [--build/--test/--run/--migrate c] [--run-from repo\|root] [--no-prototype] [--require-existing-root]` | upsert a lane — **the sole writer**. `detected` declares a stack the kit ships no profile for (needs `--stack` and a source path), which is how a legacy WebForms, JSP or ERB lane gets recorded at all |
604
+ | `lane-declare --profile <id\|label\|none\|detected> [--kind k] [--id lane] [--source-path\|--root p] [--stack label] [--origin declared\|reverse-engineered] [--build/--test/--run/--migrate c] [--run-from repo\|root] [--no-prototype] [--require-existing-root]` | upsert a WHOLE lane — its one caller is `select-stack` (§5). `detected` declares a stack the kit ships no profile for (needs `--stack` and a lane root folder), which is how a legacy WebForms, JSP or ERB lane gets recorded at all |
605
+ | `lane-set (--id lane\|--kind k) [--set KEY=VALUE]… [--unset KEY]…` | change only `contract_run`, `contract` or `client` on a lane already declared, leaving the rest of it alone. An empty VALUE changes nothing; `--unset` puts the key back on the profile's value. Called by `foundation` (a pack's lane settings) and `implement` (the client command a developer supplies) — re-declaring would reset every other command |
606
+ | `contract-export [--timeout s]` | start the service lane with `contract_run`, fetch `contract`, save it to `<service root>/contracts/openapi.json`, stop the service and everything it started. A service already answering at the URL is used as it is, and said so (§4.5) |
607
+ | `contract-client` | `run-phase --phase client` for the frontend lane — its `client` command, `{{contract}}` expanded |
608
+ | `contract-check --plan p [--doc d]` | the operation ids a frontend plan cites (its §5 `Operation id` column, or a delta plan's `Contract Binding` rows) against the document's `operationId`s — by script, no model |
464
609
  | `lane-resolve [--kind k] [--id lane]` | read; exit 0 always. `exists` reports the file, `declared` reports the lanes — the shipped-but-unfilled seed is `exists:true, declared:false`. Returns the matched lane **and** both kinds flattened — `backend_source_path`, `frontend_root_exists`, `backend_build` … — so a workflow binds one variable instead of calling twice |
465
610
  | `lane-verify [--require backend\|frontend]… [--allow-missing-root] [--command phase]` | **the gate**: is the declaration usable for the phase about to run. Never writes, and **always exits 0** — the calling workflow's `verify_context` owns the stop, so the reason arrives as text |
466
611
  | `lane-probe [--kind k] [--hint text]` | read; the **file** signals for "does this lane exist" — a declared lane, and a stack named in free prose. Retained for callers that want a soft hint; the authoritative answer is the declaration, read through `lane-resolve` or `lane-verify` |
@@ -474,10 +619,11 @@ set**, so a caller binds outputs without knowing which branch ran.
474
619
  |---|---|---|
475
620
  | `build-compile` | every declared lane's own `build`, via `run-phase` | the historic first-match-wins ladder |
476
621
  | `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 |
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/` |
478
- | `find-host-project` | searches the service lane's root, and reports the lane's `ecosystem` | searches `src/` |
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 |
622
+ | `run-migrate` | a lane's declared `migrate` (§4.2) when there is one; otherwise `dotnet ef` searched under the **service lane root folder** rather than an assumed `src/` | `dotnet ef` under `src/` |
623
+ | `find-host-project` | searches the service lane root folder, and reports the lane's `ecosystem` | searches `src/` |
624
+ | `foundation-list`, `list-components` | narrowed to the `ecosystem` of the lane being scaffolded, else of every declared lane; `foundation-list` also by each entry's `# kind:` / `# profiles:` (§4.1) | unfiltered / `dotnet` |
625
+ | `foundation-unpack` | unpacks into the **lane root folder**, and lifts a packed test tree onto its **test path** (§4.1, §4.3) | cannot run — `foundation` stops until a lane is declared |
626
+ | `build-compile --kind` | that one lane's own `build` — what `foundation` runs after a spec build | the ladder; `--kind` is ignored |
481
627
  | `resolve-tech` | expands stack placeholders and flags a corpus mismatch (§7a) | no placeholders resolve; nothing is flagged |
482
628
 
483
629
  **Why `--scope slice` is the one exception.** VSTest's `--filter FullyQualifiedName~` is the
@@ -498,16 +644,17 @@ No workflow or script changes. If adding a stack ever requires touching a workfl
498
644
  model is wrong and that is the bug to fix.
499
645
 
500
646
  That gives the new stack every phase that is driven by a **command** — build, test, run,
501
- migrate — plus the correct source path everywhere, immediately. Two things are opt-in because
647
+ migrate — plus the correct lane root folder everywhere, immediately. Two things are opt-in because
502
648
  they are content the kit cannot invent:
503
649
 
504
650
  - **Technical rules** — a corpus at `guardrails/technology/<id>/`, mapped from the project's
505
651
  `tech-rule-bindings.md` (§7a). Until one exists the stack runs with the universal
506
652
  `ai-rules.md` and the technical constitution, which is the greenfield-safe default, not an
507
653
  error.
508
- - **A foundation bundle** — `foundations/<ecosystem>/<Name>/`, only if you want `/appweaver
509
- foundation` to scaffold this stack. Scaffolding with the stack's own tooling and then
510
- declaring the result with `select-stack` is fully supported: `implement` generates INTO a
654
+ - **A foundation pack or spec** — `foundations/<ecosystem>/<Name>/`, only if you want
655
+ `/appweaver foundation` to offer one for this stack (give it `# kind:` / `# profiles:`
656
+ header lines). Without one, `foundation` still offers `[manual spec]` for the lane, and
657
+ scaffolding with the stack's own tooling is fully supported: `implement` generates INTO a
511
658
  project tree and does not require that the kit created it.
512
659
 
513
660
  ## 7a. Mapping rules to a stack without pinning the map to one
@@ -523,15 +670,20 @@ they are content the kit cannot invent:
523
670
  | `{{backend_ecosystem}}` / `{{frontend_ecosystem}}` | that lane's `ecosystem` |
524
671
 
525
672
  ```md
526
- ## /aw-spec-plan
673
+ ## /aw-spec-plan-backend
527
674
  - guardrails/technology/{{stack}}/persistence.md
528
- - guardrails/technology/{{frontend_stack}}/ui-constitution.md
675
+
676
+ ## /aw-spec-plan-frontend
677
+ - guardrails/technology/{{frontend_stack}}/component-architecture.md
678
+ - guardrails/technology/{{frontend_stack}}/api-integration.{{backend_stack}}.md
529
679
  ```
530
680
 
531
681
  One map, read from a Spring repo, loads `java-spring/persistence.md`; read from a .NET repo it
532
- loads `dotnet-webapi/persistence.md`. A placeholder for a lane that is not declared **skips**
533
- that bullet — a frontend rule in a backend-only repo is not an error — and a resolved path with
534
- no file on disk is reported under `missing` rather than silently dropped.
682
+ loads `dotnet-webapi/persistence.md`. Each heading holds one lane's rules, and a workflow loads
683
+ a lane's heading only when that lane runs. A placeholder for a lane that is not declared
684
+ **skips** that bullet — in a frontend-only repo the `api-integration.{{backend_stack}}.md`
685
+ pairing bullet is skipped, not an error — and a resolved path with no file on disk is reported
686
+ under `missing` rather than silently dropped.
535
687
 
536
688
  **A bullet that spells the stack out is still used**, because a shared corpus may be referenced
537
689
  on purpose. But when that directory is not one the repo declares, `resolve-tech` returns