aw-spec-cli 0.1.27 → 0.1.29
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/RULE-PARTITION.md +8 -9
- package/assets/.appweaver/STACK-PROFILES.md +74 -9
- package/assets/.appweaver/interpreter/run-skill.py +28 -7
- package/assets/.appweaver/orchestrator/modules/delta-plan.orch.yaml +17 -7
- package/assets/.appweaver/orchestrator/modules/domain-select.orch.yaml +1 -1
- package/assets/.appweaver/orchestrator/modules/foundation-pack.orch.yaml +44 -1
- package/assets/.appweaver/orchestrator/modules/foundation-spec.orch.yaml +172 -5
- package/assets/.appweaver/orchestrator/rev-templates/delta-design-template.skeleton.md +5 -4
- package/assets/.appweaver/orchestrator/rev-templates/design-template.skeleton.md +3 -1
- package/assets/.appweaver/orchestrator/rev-templates/domain-rule-bindings.skeleton.md +16 -8
- package/assets/.appweaver/orchestrator/rev-templates/tech-rule-bindings.skeleton.md +15 -18
- package/assets/.appweaver/orchestrator/rev-templates/ui-delta-design-template.skeleton.md +4 -1
- package/assets/.appweaver/orchestrator/rev-templates/ui-design-template.skeleton.md +6 -4
- package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +7 -6
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan-delta.orch.yaml +7 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +7 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-deploy.orch.yaml +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +224 -16
- package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +20 -19
- package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +35 -11
- package/assets/.appweaver/orchestrator/workflows/aw-spec-select-stack.orch.yaml +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +1 -1
- package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +91 -7
- package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +70 -7
- package/assets/.appweaver/scripts/python/aw-foundation.py +88 -12
- package/assets/.appweaver/scripts/python/aw-stack.py +481 -10
- package/assets/.appweaver/stack-profiles/dotnet-webapi.json +2 -0
- package/assets/.appweaver/stack-profiles/java-spring.json +2 -0
- package/assets/.appweaver/stacks.json +5 -2
- package/assets/.appweaver/templates/delta-design-template.md +7 -5
- package/assets/.appweaver/templates/design-template.md +3 -0
- package/assets/.appweaver/templates/ui-delta-design-template.md +1 -1
- package/assets/.appweaver/templates/ui-design-template.md +6 -4
- package/assets/foundations/dotnet/ModularMonolith/ModularMonolith.lane.json +6 -0
- package/assets/foundations/dotnet/VerticalSlice/VerticalSlice.lane.json +6 -0
- package/assets/foundations/foundation.spec.template.txt +26 -1
- package/assets/foundations/java/Hexagonal/Hexagonal.lane.json +6 -0
- package/assets/foundations/java/Hexagonal/Hexagonal.pack.txt +7 -0
- package/assets/guardrails/project-specific/domain-rule-bindings.md +20 -8
- package/assets/guardrails/project-specific/tech-rule-bindings.md +25 -37
- package/assets/guardrails/technology/dotnet-webapi/api-versioning.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/auditing.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/authorization.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/clean-architecture.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/coding-standards.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/cqrs.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/data-classification.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/dependency-policy.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/eventing.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/folder-structure.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/framework-api-map.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/modular-monolith.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/persistence.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/testing.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/vertical-slices.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/zero-trust.md +1 -1
- package/assets/guardrails/technology/nextjs/accessibility-implementation.md +1 -1
- package/assets/guardrails/technology/nextjs/api-client.md +1 -1
- package/assets/guardrails/technology/nextjs/api-integration.dotnet-webapi.md +1 -1
- package/assets/guardrails/technology/nextjs/api-integration.java-spring.md +1 -1
- package/assets/guardrails/technology/nextjs/auth-session.md +1 -1
- package/assets/guardrails/technology/nextjs/client-security.md +1 -1
- package/assets/guardrails/technology/nextjs/component-architecture.md +1 -1
- package/assets/guardrails/technology/nextjs/component-conventions.md +1 -1
- package/assets/guardrails/technology/nextjs/design-system.md +1 -1
- package/assets/guardrails/technology/nextjs/error-handling.md +1 -1
- package/assets/guardrails/technology/nextjs/forms-and-validation.md +1 -1
- package/assets/guardrails/technology/nextjs/frontend-testing.md +1 -1
- package/assets/guardrails/technology/nextjs/i18n.md +1 -1
- package/assets/guardrails/technology/nextjs/observability.md +1 -1
- package/assets/guardrails/technology/nextjs/performance-budgets.md +1 -1
- package/assets/guardrails/technology/nextjs/routing-navigation.md +1 -1
- package/assets/guardrails/technology/nextjs/state-management.md +1 -1
- package/package.json +1 -1
|
@@ -78,8 +78,8 @@ lane loads the technical base, the frontend lane loads the UI base. What they sh
|
|
|
78
78
|
- **Domain rule files: NEVER.** Domain reaches plan solely as spec ids and the context's
|
|
79
79
|
`domain-model.md`.
|
|
80
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,
|
|
82
|
-
for the frontend lane.
|
|
81
|
+
mediates the spec against: `## /aw-spec-plan-backend` for the service lane,
|
|
82
|
+
`## /aw-spec-plan-frontend` for the frontend lane.
|
|
83
83
|
- Constitution: the **technical** half in the service lane, the **UI** half in the frontend one.
|
|
84
84
|
|
|
85
85
|
### `implement` — *code generation, both lanes, both paths*
|
|
@@ -91,10 +91,9 @@ lane loads the technical base, the frontend lane loads the UI base. What they sh
|
|
|
91
91
|
`domain-model.<CR-id>.delta.md` joins it, since the master is not merged until
|
|
92
92
|
finalize-delta. The model is READ, never mined for work: what it holds and no plan element
|
|
93
93
|
realises is a planning gap, not code to invent.
|
|
94
|
-
- **Technical rule files: yes** — the code-generation and convention set, keyed per lane
|
|
95
|
-
|
|
96
|
-
`## /aw-spec-implement-
|
|
97
|
-
for the frontend lane either way.
|
|
94
|
+
- **Technical rule files: yes** — the code-generation and convention set, keyed per lane, the
|
|
95
|
+
same on the master and the delta path: `## /aw-spec-implement-backend` for the service lane,
|
|
96
|
+
`## /aw-spec-implement-frontend` for the frontend lane.
|
|
98
97
|
- Constitution: the **technical** half in the service lane, the **UI** half in the frontend one.
|
|
99
98
|
|
|
100
99
|
### `build` — *compile + repair*
|
|
@@ -133,8 +132,8 @@ lane loads the technical base, the frontend lane loads the UI base. What they sh
|
|
|
133
132
|
there is no map to read. The order is therefore inverted: the half is authored from the code,
|
|
134
133
|
then the `frontend/` rule set is generated as its detail layer, then the lane's three headings
|
|
135
134
|
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-
|
|
137
|
-
`## /aw-spec-implement-
|
|
135
|
+
run re-ratifies from, so it behaves like the greenfield case above), `## /aw-spec-plan-frontend`
|
|
136
|
+
and `## /aw-spec-implement-frontend` (what the frontend lane of `plan` and `implement` load).
|
|
138
137
|
- `onboard` **never writes** `.appweaver/stacks.json`. `lane-declare` is its sole writer and
|
|
139
138
|
`select-stack` its only caller, so every lane is one a person confirmed.
|
|
140
139
|
- The **document templates** it derives obey the same partition as the halves that govern them:
|
|
@@ -160,7 +159,7 @@ lane loads the technical base, the frontend lane loads the UI base. What they sh
|
|
|
160
159
|
- Same partition, reversed: the reversed **spec** is authored with domain rules in context; the
|
|
161
160
|
reversed **plan** is authored with the technical set only, so the plan stays domain-free.
|
|
162
161
|
- 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-
|
|
162
|
+
plan under `## /aw-spec-adopt-backend`, and the frontend plan under `## /aw-spec-plan-frontend`, which
|
|
164
163
|
REPLACES the backend set before that step rather than stacking on it.
|
|
165
164
|
- Technical files here are **reference, not authority — the CODE wins.**
|
|
166
165
|
|
|
@@ -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-
|
|
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
|
|
|
@@ -56,6 +56,9 @@ no heading to key on. Where the two lanes are folded into one command, as `plan`
|
|
|
56
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
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) |
|
|
58
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) |
|
|
59
62
|
|
|
60
63
|
### Three properties matter more than the schema
|
|
61
64
|
|
|
@@ -217,6 +220,11 @@ workflow.**
|
|
|
217
220
|
"host_patterns": [], // optional — where the runnable project sits, for
|
|
218
221
|
// `{{host}}` in a command — see §4.2
|
|
219
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
|
|
220
228
|
"prototype_run": "npm run dev --prefix prototype", // frontend only — fills the prototype block
|
|
221
229
|
"reserved_names": ["Event", "Request", "Response"], // optional — names this stack's AMBIENT
|
|
222
230
|
// scope already defines; see §4.4
|
|
@@ -434,18 +442,66 @@ A layout the table gets wrong is fixed by writing `test_root` on the lane in `st
|
|
|
434
442
|
| `go-chi` | `backend/` | *(inside the lane root folder)* | `internal/` | — |
|
|
435
443
|
| every frontend | `frontend/` | *(inside the lane root folder)* | `web/` | — |
|
|
436
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 |
|
|
465
|
+
|---|---|---|
|
|
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
|
+
```
|
|
486
|
+
|
|
437
487
|
---
|
|
438
488
|
|
|
439
489
|
## 5. Who may write this file
|
|
440
490
|
|
|
441
|
-
**`lane-declare` writes
|
|
442
|
-
|
|
491
|
+
**`lane-declare` writes whole lanes, and it has exactly ONE caller** — so the rule is a
|
|
492
|
+
grep, not a convention:
|
|
443
493
|
|
|
444
494
|
```sh
|
|
445
495
|
grep -rl "lane-declare" .appweaver/orchestrator/workflows/ # must print exactly one file
|
|
446
496
|
# aw-spec-select-stack.orch.yaml — a person picks
|
|
447
497
|
```
|
|
448
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
|
+
|
|
449
505
|
`onboard` used to be a second caller. Before `select-stack` was the first command of the
|
|
450
506
|
brownfield sequence a repo could reach `onboard` undeclared, so `onboard` ran its own
|
|
451
507
|
detection pass, recorded what it found marked `origin: reverse-engineered`, and was gated
|
|
@@ -545,7 +601,11 @@ implementation and the twins cannot drift.
|
|
|
545
601
|
|---|---|
|
|
546
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 |
|
|
547
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 |
|
|
548
|
-
| `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 —
|
|
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 |
|
|
549
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 |
|
|
550
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 |
|
|
551
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` |
|
|
@@ -610,15 +670,20 @@ they are content the kit cannot invent:
|
|
|
610
670
|
| `{{backend_ecosystem}}` / `{{frontend_ecosystem}}` | that lane's `ecosystem` |
|
|
611
671
|
|
|
612
672
|
```md
|
|
613
|
-
## /aw-spec-plan
|
|
673
|
+
## /aw-spec-plan-backend
|
|
614
674
|
- guardrails/technology/{{stack}}/persistence.md
|
|
615
|
-
|
|
675
|
+
|
|
676
|
+
## /aw-spec-plan-frontend
|
|
677
|
+
- guardrails/technology/{{frontend_stack}}/component-architecture.md
|
|
678
|
+
- guardrails/technology/{{frontend_stack}}/api-integration.{{backend_stack}}.md
|
|
616
679
|
```
|
|
617
680
|
|
|
618
681
|
One map, read from a Spring repo, loads `java-spring/persistence.md`; read from a .NET repo it
|
|
619
|
-
loads `dotnet-webapi/persistence.md`.
|
|
620
|
-
|
|
621
|
-
|
|
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.
|
|
622
687
|
|
|
623
688
|
**A bullet that spells the stack out is still used**, because a shared corpus may be referenced
|
|
624
689
|
on purpose. But when that directory is not one the repo declares, `resolve-tech` returns
|
|
@@ -62,13 +62,18 @@ def _indent(line):
|
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
def _strip_comment(s):
|
|
65
|
-
"""Strip a trailing ' #...' comment that is outside quotes/flow.
|
|
65
|
+
"""Strip a trailing ' #...' comment that is outside quotes/flow. Inside double
|
|
66
|
+
quotes a backslash escapes the next character, so \\" does not end the string."""
|
|
66
67
|
out, i, depth = [], 0, 0
|
|
67
68
|
quote = None
|
|
68
69
|
while i < len(s):
|
|
69
70
|
c = s[i]
|
|
70
71
|
if quote:
|
|
71
72
|
out.append(c)
|
|
73
|
+
if quote == '"' and c == "\\" and i + 1 < len(s):
|
|
74
|
+
out.append(s[i + 1])
|
|
75
|
+
i += 2
|
|
76
|
+
continue
|
|
72
77
|
if c == quote:
|
|
73
78
|
quote = None
|
|
74
79
|
elif c in ("'", '"'):
|
|
@@ -93,6 +98,9 @@ def _scalar(tok):
|
|
|
93
98
|
if tok == "" or tok == "~" or tok.lower() == "null":
|
|
94
99
|
return None
|
|
95
100
|
if len(tok) >= 2 and tok[0] == tok[-1] and tok[0] in "'\"":
|
|
101
|
+
if tok[0] == '"':
|
|
102
|
+
# A double-quoted value reads \" as " and \\ as \, as YAML does.
|
|
103
|
+
return re.sub(r'\\(["\\])', r"\1", tok[1:-1])
|
|
96
104
|
return tok[1:-1]
|
|
97
105
|
low = tok.lower()
|
|
98
106
|
if low == "true":
|
|
@@ -140,7 +148,9 @@ def _parse_flow(s):
|
|
|
140
148
|
while pos[0] < len(s):
|
|
141
149
|
ch = s[pos[0]]
|
|
142
150
|
if quote:
|
|
143
|
-
if ch ==
|
|
151
|
+
if quote == '"' and ch == "\\":
|
|
152
|
+
pos[0] += 1 # the escaped character never closes the quote
|
|
153
|
+
elif ch == quote:
|
|
144
154
|
quote = None
|
|
145
155
|
elif ch in "'\"":
|
|
146
156
|
quote = ch
|
|
@@ -328,12 +338,23 @@ def _parse_block_scalar(lines, parent_indent, folded, chomp):
|
|
|
328
338
|
trailing += 1
|
|
329
339
|
|
|
330
340
|
if folded:
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
341
|
+
# YAML folding: one line break between two lines becomes a space, a line break
|
|
342
|
+
# followed by k empty lines becomes k newlines, and the break before or after a
|
|
343
|
+
# more-indented line is kept as it is.
|
|
344
|
+
parts, prev, empties = [], None, 0
|
|
345
|
+
for ln in block:
|
|
346
|
+
if ln == "":
|
|
347
|
+
empties += 1
|
|
348
|
+
continue
|
|
349
|
+
if prev is None:
|
|
350
|
+
parts.append("\n" * empties)
|
|
351
|
+
elif prev[:1] not in " \t" and ln[:1] not in " \t":
|
|
352
|
+
parts.append("\n" * empties if empties else " ")
|
|
335
353
|
else:
|
|
336
|
-
|
|
354
|
+
parts.append("\n" * (empties + 1))
|
|
355
|
+
parts.append(ln)
|
|
356
|
+
prev, empties = ln, 0
|
|
357
|
+
text = "".join(parts)
|
|
337
358
|
else:
|
|
338
359
|
text = "\n".join(block)
|
|
339
360
|
|
|
@@ -130,6 +130,9 @@ workflow:
|
|
|
130
130
|
# the context's CANONICAL model — the aggregate root,
|
|
131
131
|
# its entities and its invariants live here, not in the
|
|
132
132
|
# spec and not in this plan.
|
|
133
|
+
- { path: .appweaver/memory/constitution.technical.md, optional: true }
|
|
134
|
+
# the lane's ratified technology base, the same one
|
|
135
|
+
# the master plan is authored under — no domain.
|
|
133
136
|
- { path: "{{cr_dir}}/delta-slice-analyze-report.md", optional: true }
|
|
134
137
|
# WHAT THE LAST GATE FOUND, when this change
|
|
135
138
|
# has already been through analyze-slice-delta.
|
|
@@ -191,7 +194,9 @@ workflow:
|
|
|
191
194
|
lane" with every addition or change to this feature's published surface, as field
|
|
192
195
|
shapes. That table is the only part of this document the frontend delta plan may
|
|
193
196
|
bind against; a contract the UI needs and this document never states becomes a
|
|
194
|
-
client-side workaround. "None" when the surface is unchanged.
|
|
197
|
+
client-side workaround. "None" when the surface is unchanged. Each row carries the
|
|
198
|
+
endpoint's operation id: a changed endpoint keeps the one the master plan gives
|
|
199
|
+
it, and a new endpoint gets one unique across the service.
|
|
195
200
|
- Stay technology-agnostic (no code) and keep the master's form: behaviour as
|
|
196
201
|
signatures + pre/post/invariant/raises, contracts as field shapes.
|
|
197
202
|
- TWO CASES — decide from the delta-spec in context:
|
|
@@ -208,11 +213,12 @@ workflow:
|
|
|
208
213
|
|
|
209
214
|
# ══════════ THE LANE SWAP — the frontend's rules replace the backend's ═══════
|
|
210
215
|
# The delta twin of the swap `plan` performs before its own master frontend sections.
|
|
211
|
-
#
|
|
212
|
-
#
|
|
213
|
-
#
|
|
214
|
-
# resets to the workflow's declared seed and loads
|
|
215
|
-
#
|
|
216
|
+
# The caller loads only the service lane's rules (`## /aw-spec-plan-backend`, when that
|
|
217
|
+
# lane is declared) before including this module, and sticky standards only accumulate,
|
|
218
|
+
# so without this the experience delta plan would be authored with the service rules in
|
|
219
|
+
# its envelope. `replace` resets to the workflow's declared seed and loads
|
|
220
|
+
# `## /aw-spec-plan-frontend`, so the backend delta above is authored under the service
|
|
221
|
+
# set and everything below this line under the frontend's.
|
|
216
222
|
#
|
|
217
223
|
# Conditioned on the same flag as the lane it precedes: a change with no frontend half
|
|
218
224
|
# authors nothing here and must not have its envelope swapped out from under the
|
|
@@ -221,7 +227,7 @@ workflow:
|
|
|
221
227
|
condition: "{{fe_existing}} == true"
|
|
222
228
|
action: include
|
|
223
229
|
module: .appweaver/orchestrator/modules/tech-select.orch.yaml
|
|
224
|
-
with: { command: aw-spec-plan-
|
|
230
|
+
with: { command: aw-spec-plan-frontend, replace: "true" }
|
|
225
231
|
|
|
226
232
|
# ═════════════════════ FRONTEND LANE — the experience delta plan ═════════════
|
|
227
233
|
- id: ctx_frontend_delta
|
|
@@ -292,6 +298,10 @@ workflow:
|
|
|
292
298
|
and introduce no server-side behaviour or persistence assumption. What the
|
|
293
299
|
experience needs and the surface does not offer goes under "Contract deltas
|
|
294
300
|
needed", raised back to the service lane — never worked around in the client.
|
|
301
|
+
- A BINDING ROW'S ELEMENT LEADS WITH ITS OPERATION ID — `OperationId` · `VERB
|
|
302
|
+
/route` — copied exactly from the service delta's table or, for an endpoint the
|
|
303
|
+
change does not touch, the master backend plan. Implement looks the endpoint up by
|
|
304
|
+
it in the service's exported API document.
|
|
295
305
|
- UX- IS THIS DOCUMENT'S TO REALISE. The service plan excludes the spec's Experience
|
|
296
306
|
Standards by contract, so a UX- item in the delta-spec that reaches neither plan
|
|
297
307
|
is a dropped requirement.
|
|
@@ -62,7 +62,7 @@ workflow:
|
|
|
62
62
|
require_value: dg_gate
|
|
63
63
|
equals: "continue"
|
|
64
64
|
on_mismatch: stop
|
|
65
|
-
on_mismatch_message: "Exited — run /appweaver select-domain, or set
|
|
65
|
+
on_mismatch_message: "Exited — run /appweaver select-domain, or set 'domain' in .appweaver/domain.json to a folder under guardrails/ that holds domain-rule-bindings.md (project-specific by default), then re-run."
|
|
66
66
|
|
|
67
67
|
# 3. Selection box — only when a rule map with candidates exists.
|
|
68
68
|
- id: select_mode
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
# collisions, unpack into the lane root folder exactly as packed with the sentinel renamed,
|
|
7
7
|
# optionally add the reference sample on top, and check that no sentinel is left behind.
|
|
8
8
|
#
|
|
9
|
+
# Its LANE SETTINGS (<pack name>.lane.json beside the pack), when it has any, are recorded
|
|
10
|
+
# on the lane once it is unpacked.
|
|
11
|
+
#
|
|
9
12
|
# Every file in a pack lands as packed, replacing a file already there — except the ones
|
|
10
13
|
# its RULE FILE (<pack name>.rules.json beside the pack) says to merge. The model is used
|
|
11
14
|
# ONLY for those merges: an `agent_author` (read-only generation, the ENGINE writes the
|
|
@@ -63,6 +66,13 @@ workflow:
|
|
|
63
66
|
fp_clean: ""
|
|
64
67
|
fp_residue: ""
|
|
65
68
|
fp_residue_note: "not checked"
|
|
69
|
+
fp_lane_found: "false"
|
|
70
|
+
fp_lane_file: ""
|
|
71
|
+
fp_lane_contract_run: ""
|
|
72
|
+
fp_lane_contract: ""
|
|
73
|
+
fp_lane_client: ""
|
|
74
|
+
fp_lane_recorded: ""
|
|
75
|
+
fp_lane_note: ""
|
|
66
76
|
|
|
67
77
|
# 1. Locate the bundle + its unpacker and read the (bounded) pack header: the sentinel,
|
|
68
78
|
# the file count, and whether a Reference pack ships beside it.
|
|
@@ -187,6 +197,39 @@ workflow:
|
|
|
187
197
|
message_var: fl_status
|
|
188
198
|
on_false_message: "NOT scaffolded — the {{fl_name}} unpacker failed and wrote {{fp_written}} file(s). Its output ends: {{fp_unpack_out}}"
|
|
189
199
|
|
|
200
|
+
# 5b. The pack's LANE SETTINGS (<pack name>.lane.json beside it): what only the pack
|
|
201
|
+
# knows about the project it scaffolds — chiefly how its service is started on its
|
|
202
|
+
# own so implement can export the API document. Recorded on the lane with lane-set;
|
|
203
|
+
# a key the file leaves empty is not touched, and a pack with no such file leaves
|
|
204
|
+
# the lane on its stack profile's values.
|
|
205
|
+
- id: fp_lane_settings
|
|
206
|
+
action: execute_script
|
|
207
|
+
on_error: continue
|
|
208
|
+
script: { ref: actions }
|
|
209
|
+
subcommand: foundation-pack-lane
|
|
210
|
+
args: ["--name", "{{fl_name}}", "--app", "{{app}}"]
|
|
211
|
+
outputs:
|
|
212
|
+
- { name: fp_lane_found, from: "$.found" }
|
|
213
|
+
- { name: fp_lane_file, from: "$.file" }
|
|
214
|
+
- { name: fp_lane_contract_run, from: "$.contract_run" }
|
|
215
|
+
- { name: fp_lane_contract, from: "$.contract" }
|
|
216
|
+
- { name: fp_lane_client, from: "$.client" }
|
|
217
|
+
|
|
218
|
+
- id: fp_lane_record
|
|
219
|
+
action: execute_script
|
|
220
|
+
condition: "{{fp_lane_found}} == true"
|
|
221
|
+
on_error: continue
|
|
222
|
+
script: { ref: actions }
|
|
223
|
+
subcommand: lane-set
|
|
224
|
+
args: ["--id", "{{fl_lane_id}}", "--set", "contract_run={{fp_lane_contract_run}}", "--set", "contract={{fp_lane_contract}}", "--set", "client={{fp_lane_client}}"]
|
|
225
|
+
outputs:
|
|
226
|
+
- { name: fp_lane_recorded, from: "$.ok" }
|
|
227
|
+
|
|
228
|
+
- id: fp_lane_noted
|
|
229
|
+
action: set
|
|
230
|
+
condition: "{{fp_lane_recorded}} == true"
|
|
231
|
+
vars: { fp_lane_note: "; the pack's API-export settings ({{fp_lane_file}}) recorded on the {{fl_lane_id}} lane" }
|
|
232
|
+
|
|
190
233
|
# 6. The merges the rule file asks for: the file that was there (put aside) and the
|
|
191
234
|
# pack's version (now on disk) become one, as the rule's instruction says.
|
|
192
235
|
- id: fp_merge
|
|
@@ -346,4 +389,4 @@ workflow:
|
|
|
346
389
|
action: set
|
|
347
390
|
vars:
|
|
348
391
|
fl_files: "{{fp_written}}"
|
|
349
|
-
fl_status: "scaffolded from the {{fl_name}} pack — {{fp_written}} file(s) into {{fl_source_path}}{{fp_tests_note}}{{fp_rules_note}}{{fp_ref_note}}; {{fp_residue_note}}"
|
|
392
|
+
fl_status: "scaffolded from the {{fl_name}} pack — {{fp_written}} file(s) into {{fl_source_path}}{{fp_tests_note}}{{fp_rules_note}}{{fp_ref_note}}{{fp_lane_note}}; {{fp_residue_note}}"
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
# gates on), and its own build command can be run on it.
|
|
23
23
|
#
|
|
24
24
|
# The spec actually used is copied to .appweaver/foundation/<lane>.spec.md — the record of
|
|
25
|
-
# what the project was built from.
|
|
25
|
+
# what the project was built from. The API-contract settings its header gives — or the
|
|
26
|
+
# operator types when it gives none — are recorded on the lane once the project is built.
|
|
26
27
|
#
|
|
27
28
|
# Every refusal RETURNS rather than stops, with the reason in fl_status, so the run
|
|
28
29
|
# carries on with the next lane.
|
|
@@ -68,6 +69,30 @@ workflow:
|
|
|
68
69
|
fs_build_ack: ""
|
|
69
70
|
fs_build_ok: ""
|
|
70
71
|
fs_build_note: "build not run — /appweaver build compiles it and repairs compile errors"
|
|
72
|
+
fs_hdr_contract_run: ""
|
|
73
|
+
fs_hdr_contract: ""
|
|
74
|
+
fs_hdr_client: ""
|
|
75
|
+
fs_settings_text: "none"
|
|
76
|
+
fs_placeholder: ""
|
|
77
|
+
fs_is_backend: "false"
|
|
78
|
+
fs_is_frontend: "false"
|
|
79
|
+
fs_need_run: "false"
|
|
80
|
+
fs_need_url: "false"
|
|
81
|
+
fs_need_client: "false"
|
|
82
|
+
fs_ans_contract_run: ""
|
|
83
|
+
fs_ans_contract: ""
|
|
84
|
+
fs_ans_client: ""
|
|
85
|
+
fs_set_run: ""
|
|
86
|
+
fs_set_url: ""
|
|
87
|
+
fs_set_client: ""
|
|
88
|
+
fs_cur_contract_run: ""
|
|
89
|
+
fs_cur_contract: ""
|
|
90
|
+
fs_cur_client: ""
|
|
91
|
+
fs_cur_run_shown: "none — the export is skipped until one is set"
|
|
92
|
+
fs_cur_url_shown: "none — the export is skipped until one is set"
|
|
93
|
+
fs_cur_client_shown: "none — implement asks for one before it generates the frontend"
|
|
94
|
+
fs_lane_written: ""
|
|
95
|
+
fs_lane_note: ""
|
|
71
96
|
|
|
72
97
|
# 1. A lane that already had files reaches this module only when the operator agreed, in
|
|
73
98
|
# foundation-lane, to overwrite them. The agent is told so, with what that means for
|
|
@@ -120,26 +145,36 @@ workflow:
|
|
|
120
145
|
condition: "{{fl_route}} == spec"
|
|
121
146
|
script: { ref: actions }
|
|
122
147
|
subcommand: foundation-spec-capture
|
|
123
|
-
args: ["--lane", "{{fl_lane_id}}", "--kind", "{{fl_kind}}", "--file", "{{fl_spec_file}}"]
|
|
148
|
+
args: ["--lane", "{{fl_lane_id}}", "--kind", "{{fl_kind}}", "--file", "{{fl_spec_file}}", "--app", "{{app}}"]
|
|
124
149
|
outputs:
|
|
125
150
|
- { name: fs_ok, from: "$.ok" }
|
|
126
151
|
- { name: fs_spec_path, from: "$.spec_path" }
|
|
127
152
|
- { name: fs_source, from: "$.source" }
|
|
128
153
|
- { name: fs_title, from: "$.title" }
|
|
129
154
|
- { name: fs_reason, from: "$.reason" }
|
|
155
|
+
- { name: fs_hdr_contract_run, from: "$.contract_run" }
|
|
156
|
+
- { name: fs_hdr_contract, from: "$.contract" }
|
|
157
|
+
- { name: fs_hdr_client, from: "$.client" }
|
|
158
|
+
- { name: fs_settings_text, from: "$.settings_text" }
|
|
159
|
+
- { name: fs_placeholder, from: "$.placeholder" }
|
|
130
160
|
|
|
131
161
|
- id: fs_capture_manual
|
|
132
162
|
action: execute_script
|
|
133
163
|
condition: "{{fl_route}} == manual"
|
|
134
164
|
script: { ref: actions }
|
|
135
165
|
subcommand: foundation-spec-capture
|
|
136
|
-
args: ["--lane", "{{fl_lane_id}}", "--kind", "{{fl_kind}}", "--input", "{{fs_spec_input}}"]
|
|
166
|
+
args: ["--lane", "{{fl_lane_id}}", "--kind", "{{fl_kind}}", "--input", "{{fs_spec_input}}", "--app", "{{app}}"]
|
|
137
167
|
outputs:
|
|
138
168
|
- { name: fs_ok, from: "$.ok" }
|
|
139
169
|
- { name: fs_spec_path, from: "$.spec_path" }
|
|
140
170
|
- { name: fs_source, from: "$.source" }
|
|
141
171
|
- { name: fs_title, from: "$.title" }
|
|
142
172
|
- { name: fs_reason, from: "$.reason" }
|
|
173
|
+
- { name: fs_hdr_contract_run, from: "$.contract_run" }
|
|
174
|
+
- { name: fs_hdr_contract, from: "$.contract" }
|
|
175
|
+
- { name: fs_hdr_client, from: "$.client" }
|
|
176
|
+
- { name: fs_settings_text, from: "$.settings_text" }
|
|
177
|
+
- { name: fs_placeholder, from: "$.placeholder" }
|
|
143
178
|
|
|
144
179
|
- id: fs_capture_gate
|
|
145
180
|
action: verify_context
|
|
@@ -165,7 +200,7 @@ workflow:
|
|
|
165
200
|
- id: fs_rules_why_project
|
|
166
201
|
action: set
|
|
167
202
|
condition: "{{tech_domain}} == "
|
|
168
|
-
vars: { fs_rules_why: "no project is named in .appweaver/domain.json — run /appweaver select-domain, or set
|
|
203
|
+
vars: { fs_rules_why: "no project is named in .appweaver/domain.json — run /appweaver select-domain, or set 'domain' to a folder under guardrails/ (project-specific by default)" }
|
|
169
204
|
|
|
170
205
|
- id: fs_rules_why_heading
|
|
171
206
|
action: set
|
|
@@ -278,6 +313,19 @@ workflow:
|
|
|
278
313
|
/appweaver implement from the plans, so add none the spec does not ask for.
|
|
279
314
|
- The project must build with `{{fl_build}}` run in {{fl_workdir}}. Restore or
|
|
280
315
|
install what that needs; run the build once if you can and fix what it reports.
|
|
316
|
+
- THE API CONTRACT SETTINGS the spec's header gives: {{fs_settings_text}}. They are
|
|
317
|
+
recorded on the lane, and /appweaver implement uses them before it generates the
|
|
318
|
+
frontend, so the project you build has to honour them exactly:
|
|
319
|
+
· a START COMMAND (backend) — the API starts with exactly that command, run in
|
|
320
|
+
{{fl_workdir}}, ON ITS OWN: no orchestration host, and nothing it needs that
|
|
321
|
+
is not on this machine. Name the project so the path the command names exists.
|
|
322
|
+
· a DOCUMENT URL (backend) — the running API serves its OpenAPI document at
|
|
323
|
+
exactly that URL, over http, on the port it names.
|
|
324
|
+
· a CLIENT COMMAND (frontend) — that command can run in {{fl_workdir}}: install
|
|
325
|
+
its tool as a development dependency. Do not run it; the service's document
|
|
326
|
+
does not exist yet.
|
|
327
|
+
A backend built without them still serves an OpenAPI document; say at what URL,
|
|
328
|
+
and how the API starts on its own, in your summary.
|
|
281
329
|
- Do not commit, push, or change git configuration.
|
|
282
330
|
|
|
283
331
|
List every file you created or changed in `files` (paths relative to the repository
|
|
@@ -373,9 +421,128 @@ workflow:
|
|
|
373
421
|
condition: "{{fs_build_ok}} == false"
|
|
374
422
|
vars: { fs_build_note: "build FAILED — run /appweaver build, which repairs compile errors: {{fs_build_tail}}" }
|
|
375
423
|
|
|
424
|
+
# 8. THE API CONTRACT SETTINGS — what implement uses between the lanes to export the
|
|
425
|
+
# service's OpenAPI document and generate the frontend's client from it
|
|
426
|
+
# (STACK-PROFILES.md §4.5). A backend lane needs its start command and document URL,
|
|
427
|
+
# a frontend lane its client command. Each comes from the spec's header
|
|
428
|
+
# (`# contract-run:`, `# contract:`, `# client:`, read at capture); one the header
|
|
429
|
+
# does not give is asked for, and an empty answer keeps the lane's current value —
|
|
430
|
+
# the stack profile's, unless something set it already. What there is, is recorded
|
|
431
|
+
# on the lane with lane-set, as the pack route records a pack's .lane.json.
|
|
432
|
+
- id: fs_kind_backend
|
|
433
|
+
action: set
|
|
434
|
+
condition: "{{fl_kind}} == backend"
|
|
435
|
+
vars: { fs_is_backend: "true" }
|
|
436
|
+
- id: fs_kind_frontend
|
|
437
|
+
action: set
|
|
438
|
+
condition: "{{fl_kind}} == frontend"
|
|
439
|
+
vars: { fs_is_frontend: "true" }
|
|
440
|
+
- id: fs_need_run_arm
|
|
441
|
+
action: set
|
|
442
|
+
condition: "{{fs_hdr_contract_run}} == "
|
|
443
|
+
vars: { fs_need_run: "{{fs_is_backend}}" }
|
|
444
|
+
- id: fs_need_url_arm
|
|
445
|
+
action: set
|
|
446
|
+
condition: "{{fs_hdr_contract}} == "
|
|
447
|
+
vars: { fs_need_url: "{{fs_is_backend}}" }
|
|
448
|
+
- id: fs_need_client_arm
|
|
449
|
+
action: set
|
|
450
|
+
condition: "{{fs_hdr_client}} == "
|
|
451
|
+
vars: { fs_need_client: "{{fs_is_frontend}}" }
|
|
452
|
+
|
|
453
|
+
# The lane's current values, shown as what an empty answer keeps.
|
|
454
|
+
- id: fs_current
|
|
455
|
+
action: execute_script
|
|
456
|
+
on_error: continue
|
|
457
|
+
script: { ref: actions }
|
|
458
|
+
subcommand: lane-resolve
|
|
459
|
+
args: ["--id", "{{fl_lane_id}}"]
|
|
460
|
+
outputs:
|
|
461
|
+
- { name: fs_cur_contract_run, from: "$.backend_contract_run" }
|
|
462
|
+
- { name: fs_cur_contract, from: "$.backend_contract" }
|
|
463
|
+
- { name: fs_cur_client, from: "$.frontend_client" }
|
|
464
|
+
- id: fs_cur_run_known
|
|
465
|
+
action: set
|
|
466
|
+
condition: "{{fs_cur_contract_run}} != "
|
|
467
|
+
vars: { fs_cur_run_shown: "{{fs_cur_contract_run}}" }
|
|
468
|
+
- id: fs_cur_url_known
|
|
469
|
+
action: set
|
|
470
|
+
condition: "{{fs_cur_contract}} != "
|
|
471
|
+
vars: { fs_cur_url_shown: "{{fs_cur_contract}}" }
|
|
472
|
+
- id: fs_cur_client_known
|
|
473
|
+
action: set
|
|
474
|
+
condition: "{{fs_cur_client}} != "
|
|
475
|
+
vars: { fs_cur_client_shown: "{{fs_cur_client}}" }
|
|
476
|
+
|
|
477
|
+
- id: fs_ask_contract_run
|
|
478
|
+
action: ask
|
|
479
|
+
condition: "{{fs_need_run}} == true"
|
|
480
|
+
prompt: |
|
|
481
|
+
How does the API in lane `{{fl_lane_id}}` start ON ITS OWN? /appweaver implement starts
|
|
482
|
+
it with this command to fetch its OpenAPI document before it generates the frontend.
|
|
483
|
+
The spec's header gives no `# contract-run:`.
|
|
484
|
+
Type the command — it runs in {{fl_workdir}} — with no orchestration host and nothing
|
|
485
|
+
the API cannot reach on this machine.
|
|
486
|
+
Leave it empty to keep the current one: {{fs_cur_run_shown}}
|
|
487
|
+
answer_var: fs_ans_contract_run
|
|
488
|
+
- id: fs_ask_contract
|
|
489
|
+
action: ask
|
|
490
|
+
condition: "{{fs_need_url}} == true"
|
|
491
|
+
prompt: |
|
|
492
|
+
At what URL does that running API serve its OpenAPI document? The spec's header gives
|
|
493
|
+
no `# contract:`.
|
|
494
|
+
Leave it empty to keep the current one: {{fs_cur_url_shown}}
|
|
495
|
+
answer_var: fs_ans_contract
|
|
496
|
+
- id: fs_ask_client
|
|
497
|
+
action: ask
|
|
498
|
+
condition: "{{fs_need_client}} == true"
|
|
499
|
+
prompt: |
|
|
500
|
+
Which command generates this frontend's API client from the service's OpenAPI
|
|
501
|
+
document? The spec's header gives no `# client:`.
|
|
502
|
+
It runs in {{fl_workdir}}, and {{fs_placeholder}} in it stands for the document's path —
|
|
503
|
+
for example: npx openapi-typescript {{fs_placeholder}} -o lib/api/schema.d.ts
|
|
504
|
+
Leave it empty to keep the current one: {{fs_cur_client_shown}}
|
|
505
|
+
answer_var: fs_ans_client
|
|
506
|
+
|
|
507
|
+
# What to record: the header's value for this lane's kind, or the answer where it gave
|
|
508
|
+
# none. An empty value is left out by lane-set, so the lane keeps what it has.
|
|
509
|
+
- id: fs_set_backend
|
|
510
|
+
action: set
|
|
511
|
+
condition: "{{fs_is_backend}} == true"
|
|
512
|
+
vars: { fs_set_run: "{{fs_hdr_contract_run}}", fs_set_url: "{{fs_hdr_contract}}" }
|
|
513
|
+
- id: fs_set_run_answer
|
|
514
|
+
action: set
|
|
515
|
+
condition: "{{fs_need_run}} == true"
|
|
516
|
+
vars: { fs_set_run: "{{fs_ans_contract_run}}" }
|
|
517
|
+
- id: fs_set_url_answer
|
|
518
|
+
action: set
|
|
519
|
+
condition: "{{fs_need_url}} == true"
|
|
520
|
+
vars: { fs_set_url: "{{fs_ans_contract}}" }
|
|
521
|
+
- id: fs_set_frontend
|
|
522
|
+
action: set
|
|
523
|
+
condition: "{{fs_is_frontend}} == true"
|
|
524
|
+
vars: { fs_set_client: "{{fs_hdr_client}}" }
|
|
525
|
+
- id: fs_set_client_answer
|
|
526
|
+
action: set
|
|
527
|
+
condition: "{{fs_need_client}} == true"
|
|
528
|
+
vars: { fs_set_client: "{{fs_ans_client}}" }
|
|
529
|
+
|
|
530
|
+
- id: fs_lane_record
|
|
531
|
+
action: execute_script
|
|
532
|
+
on_error: continue
|
|
533
|
+
script: { ref: actions }
|
|
534
|
+
subcommand: lane-set
|
|
535
|
+
args: ["--id", "{{fl_lane_id}}", "--set", "contract_run={{fs_set_run}}", "--set", "contract={{fs_set_url}}", "--set", "client={{fs_set_client}}"]
|
|
536
|
+
outputs:
|
|
537
|
+
- { name: fs_lane_written, from: "$.written" }
|
|
538
|
+
- id: fs_lane_noted
|
|
539
|
+
action: set
|
|
540
|
+
condition: "{{fs_lane_written}} == true"
|
|
541
|
+
vars: { fs_lane_note: "; API-contract settings recorded on the {{fl_lane_id}} lane" }
|
|
542
|
+
|
|
376
543
|
- id: fs_done
|
|
377
544
|
action: set
|
|
378
545
|
vars:
|
|
379
546
|
fl_spec_path: "{{fs_spec_path}}"
|
|
380
547
|
fl_files: "{{fs_files}}"
|
|
381
|
-
fl_status: "built from the spec in {{fs_spec_path}} (from {{fs_source}}) into {{fl_source_path}} — {{fs_summary}}; {{fs_build_note}}"
|
|
548
|
+
fl_status: "built from the spec in {{fs_spec_path}} (from {{fs_source}}) into {{fl_source_path}} — {{fs_summary}}; {{fs_build_note}}{{fs_lane_note}}"
|