aw-spec-cli 0.1.23 → 0.1.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/assets/.appweaver/ORCH-DIALECT.md +3 -3
  2. package/assets/.appweaver/RULE-PARTITION.md +41 -28
  3. package/assets/.appweaver/orchestrator/orchestrator.py +92 -14
  4. package/assets/.appweaver/orchestrator/rev-templates/ai-rules.reference.md +3 -3
  5. package/assets/.appweaver/orchestrator/rev-templates/delta-design-template.skeleton.md +19 -12
  6. package/assets/.appweaver/orchestrator/rev-templates/design-template.skeleton.md +25 -17
  7. package/assets/.appweaver/orchestrator/rev-templates/domain-requirements.skeleton.md +2 -2
  8. package/assets/.appweaver/orchestrator/rev-templates/domain-rule-bindings.skeleton.md +10 -10
  9. package/assets/.appweaver/orchestrator/rev-templates/tech-rule-bindings.skeleton.md +28 -17
  10. package/assets/.appweaver/orchestrator/rev-templates/technical-constitution.skeleton.md +22 -24
  11. package/assets/.appweaver/orchestrator/rev-templates/technical-rule-file.skeleton.md +1 -1
  12. package/assets/.appweaver/orchestrator/rev-templates/ui-delta-design-template.skeleton.md +29 -19
  13. package/assets/.appweaver/orchestrator/rev-templates/ui-design-template.skeleton.md +27 -20
  14. package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +184 -37
  15. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan-delta.orch.yaml +18 -0
  16. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +19 -0
  17. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +18 -0
  18. package/assets/.appweaver/orchestrator/workflows/aw-spec-inventory.orch.yaml +55 -29
  19. package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +650 -205
  20. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +19 -0
  21. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-feature.orch.yaml +5 -5
  22. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-stack.orch.yaml +2 -2
  23. package/assets/.appweaver/orchestrator/workflows/aw-spec-ui-model.orch.yaml +13 -3
  24. package/assets/.appweaver/orchestrator/workflows/aw-spec-ui-prototype.orch.yaml +40 -1
  25. package/assets/.appweaver/orchestrator/workflows/aw-spec-workflow-model.orch.yaml +2 -1
  26. package/assets/.appweaver/scripts/bash/aw-rev-constitution.sh +1 -1
  27. package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +69 -7
  28. package/assets/.appweaver/scripts/powershell/aw-rev-constitution.ps1 +1 -1
  29. package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +58 -2
  30. package/assets/.appweaver/scripts/python/aw-inventory.py +1809 -156
  31. package/assets/.appweaver/scripts/python/aw-rev-constitution.py +106 -145
  32. package/assets/.appweaver/scripts/python/aw-stack.py +45 -1
  33. package/assets/.appweaver/scripts/python/aw-xref.py +73 -2
  34. package/assets/.appweaver/scripts/python/validate_okf.py +10 -5
  35. package/assets/.appweaver/templates/okf-concept-template.md +10 -2
  36. package/assets/.appweaver/templates/proposed-inventory-template.md +100 -48
  37. package/assets/.appweaver/templates/source-map-template.md +14 -6
  38. package/package.json +1 -1
@@ -119,6 +119,7 @@ vars:
119
119
  lanes_declared: "false"
120
120
  be_declared: "false"
121
121
  fe_declared: "false"
122
+ fe_excluded: "false" # this feature records no surface in the declared frontend lane (feature-lanes)
122
123
  be_lane_id: ""
123
124
  fe_lane_id: ""
124
125
  active_change: ""
@@ -235,6 +236,23 @@ workflow:
235
236
  - { name: spec_file, from: "$.spec_file" }
236
237
  - { name: spec_exists, from: "$.spec_exists" }
237
238
 
239
+ # THIS FEATURE'S LANES. lane_resolve above read the REPO's declaration; one feature may
240
+ # own no part of a lane's surface — adopt records `lanes: [backend]` in
241
+ # active_version.json for a service-only slice in a repo that also has a UI. Re-read with
242
+ # --feature-dir so a lane the feature records no surface in is absent here: never
243
+ # scaffolded, bootstrapped or delta-planned for it. With no feature resolved the argument
244
+ # is dropped and the repo's answer stands.
245
+ - id: feature_lanes
246
+ action: execute_script
247
+ on_error: continue
248
+ script: { ref: actions }
249
+ subcommand: lane-resolve
250
+ args: ["--feature-dir", "{{feature_dir}}"]
251
+ outputs:
252
+ - { name: be_declared, from: "$.backend_declared" }
253
+ - { name: fe_declared, from: "$.frontend_declared" }
254
+ - { name: fe_excluded, from: "$.frontend_feature_excluded" }
255
+
238
256
  # The BOUNDED CONTEXT's domain model — where the aggregate root, its entities and its
239
257
  # invariants (BR-n) live. The spec cites them by id; this phase records where each is
240
258
  # ENFORCED, so the model has to be in context or the instruction to "follow it exactly"
@@ -1135,6 +1153,7 @@ outputs:
1135
1153
  bounded_context: "{{context}}"
1136
1154
  backend_plan: "{{backend_plan}}" # empty when no backend lane is declared
1137
1155
  frontend_plan: "{{frontend_plan}}" # empty when no frontend lane is declared
1156
+ frontend_excluded: "{{fe_excluded}}" # true = a frontend lane is declared and this feature records no surface in it (feature-lanes)
1138
1157
  domain_model: "{{domain_model}}"
1139
1158
  plan_mode: "{{plan_mode}}"
1140
1159
  plan_target: "{{plan_target}}"
@@ -28,11 +28,11 @@
28
28
  #
29
29
  # list-features (specs/ ∪ features the OKF concepts serve) → user_select (existing | "+ create new") → [ask name] → set-feature
30
30
  #
31
- # Persists .appweaver/feature.json {"feature_directory":"specs/<Feature>"} so every
32
- # later command operates on it: the forward loop (specify/plan/analyze-plan/implement/
33
- # build/analyze-slice/run/deploy) and the CR loop (analyze-plan-delta/implement/
34
- # finalize-delta/close-delta — the standalone `cr` is retired; specify and design author
35
- # the delta docs themselves).
31
+ # Persists .appweaver/feature.json {"feature_directory":"specs/[<Context>/]<Feature>"} so
32
+ # every later command operates on it: the forward loop (specify/plan/analyze-plan/
33
+ # implement/build/analyze-slice/run/deploy), brownfield's adopt, and the CR loop
34
+ # (specify/plan/analyze-plan-delta/implement/build/analyze-slice-delta/finalize-delta/
35
+ # analyze-slice/close-delta — specify and plan author the delta docs themselves).
36
36
  #
37
37
  # Launch:
38
38
  # /appweaver select-feature # interactive: list + add-new
@@ -2,7 +2,7 @@
2
2
  # aw-spec-select-stack (orchestrator) — declare the project's LANES
3
3
  #
4
4
  # THE FIRST COMMAND OF EVERY SEQUENCE. Greenfield runs it before constitution;
5
- # brownfield runs it before onboard. What it writes decides which constitution
5
+ # brownfield runs it before inventory. What it writes decides which constitution
6
6
  # halves are authored, which rule sets are generated, which subtree each phase may
7
7
  # write into, and — in brownfield — which subtree onboard reverse-engineers. It
8
8
  # answers one question per lane, twice:
@@ -317,4 +317,4 @@ outputs:
317
317
  ui_detected: "{{ui_detected}}"
318
318
  prototype: "{{prototype}}"
319
319
  note: "Lanes persisted to .appweaver/stacks.json. Each lane carries a STACK and a SOURCE PATH, and both are load-bearing. The SOURCE PATH is ownership: implement's service lane writes its code only under the service lane's and its frontend lane only under the UI lane's, build/test/run act on it, and onboard reverse-engineers out of it. Each lane also owns a TEST PATH, taken from its stack profile — tests/ beside a .NET src/, the source path itself for a stack that keeps its tests inside — and implement writes the lane's tests there. Overlapping paths of either kind are refused here rather than discovered later as two generators writing one file. The build/test/run commands are project-supplied strings the engine shells out to, which is how it stays stdlib-only and stack-ignorant. api_detected / ui_detected report what this repo's own files pointed at, whether or not it was chosen, as "<stack> at <path>" — or "nothing detected" on a greenfield repo with nothing to detect. A source path that does not exist yet is NORMAL here and reported rather than refused: greenfield declares src/ before implement creates it. The on-disk check runs in the phases that read the subtree — implement, build, run and onboard all call lane-verify and STOP with the fix named. Declining a lane with (none) is a real declaration and removes any lane of that kind a previous run left behind; it is also what stops that lane's constitution half and rule files being authored. `prototype` is not a lane — it has a root and a run command so an operator can open it, and nothing else. Re-run this workflow any time to re-stack a lane, move a source path, or drop a lane with (none); it is the only workflow that may write this file."
320
- next: "GREENFIELD: /appweaver foundation — once per lane, to scaffold a compiling project into each source path declared here (skip it for a lane whose code already exists, or scaffold with the stack's own tooling) — then /appweaver select-domain → constitution → select-feature → specify. BROWNFIELD: /appweaver onboard — it reverse-engineers out of the source paths declared here and generates the project (no select-domain; onboard derives and activates it from the code). Run this workflow FIRST in either sequence: foundation unpacks into the source paths declared here, constitution reads the declaration to decide which halves to author, and onboard reads it to decide what to scan."
320
+ next: "GREENFIELD: /appweaver foundation — once per lane, to scaffold a compiling project into each source path declared here (skip it for a lane whose code already exists, or scaffold with the stack's own tooling) — then /appweaver select-domain → constitution → select-feature → specify. BROWNFIELD: /appweaver inventory — it scans the source paths declared here and proposes the bounded contexts and features as specs/proposed-inventory.md; edit that map, run /appweaver inventory apply, then /appweaver onboard <Context> once per bounded context, which reverse-engineers the governance out of the same source paths and activates it (no select-domain; onboard derives it from the code). Run this workflow FIRST in either sequence: foundation unpacks into the source paths declared here, constitution reads the declaration to decide which halves to author, and inventory and onboard read it to decide what to scan."
@@ -110,10 +110,14 @@ workflow:
110
110
  vars:
111
111
  feature_name: "{{feature}}"
112
112
  feature_dir: "{{feature_dir}}"
113
+ domain: "{{domain}}"
113
114
  user_preferences: "{{USER_INPUT}}"
114
115
  context:
115
116
  - "{{feature_dir}}/ui/workflow-model.md"
116
117
  - "{{spec_file}}"
118
+ - { path: "guardrails/projects/{{domain}}/frontend/design.md", optional: true }
119
+ - { path: "guardrails/projects/{{domain}}/frontend/DESIGN.md", optional: true }
120
+ - { path: "guardrails/projects/{{domain}}/frontend/ui-requirements.md", optional: true }
117
121
  - { path: ".appweaver/templates/ui-model-template.md", optional: true }
118
122
  - { path: ".appweaver/memory/constitution.ui.md", optional: true }
119
123
 
@@ -135,9 +139,15 @@ workflow:
135
139
  - Define supported interaction states per surface (`Loading`, `Empty`, `Populated`, `Filtered / Searched`, `Validation Error`, `Submitting`, `Success`, `Error`).
136
140
  - Specify interactive controls, form fields, action buttons, and table/card layouts.
137
141
  - Map explicit navigation & transition triggers (`On Action -> Opens Surface X`).
138
- 3. Remember the core rule: **Workflow != Single Screen**. A workflow can span multiple surfaces or reuse surfaces with state changes.
139
- 4. Format output according to `.appweaver/templates/ui-model-template.md`.
140
- 5. Write the authored markdown content to `{{feature_dir}}/ui/ui-model.md`.
142
+ 3. **Natural Product Naming & Simplicity**:
143
+ - Use clean, concise, consumer-grade/modern B2B titles for all surfaces and actions (e.g. "Projects List", "New Project", "Project Details", "Edit Project", "Change Status").
144
+ - Avoid bloated, academic, or bureaucratic phrasing (do NOT write "Project Directory & Portfolio Catalog Surface", "Execute Lifecycle Status Transition Dialog", or "Update Project Metadata & Schedule Modal").
145
+ 4. **Progressive Disclosure Architecture**:
146
+ - Structure primary views (cards, tables) to display only essential high-signal data points.
147
+ - Relegate deep governance details, audit logs, and technical metadata to secondary tabs, detail views, or slide-over drawers.
148
+ 5. Remember the core rule: **Workflow != Single Screen**. A workflow can span multiple surfaces or reuse surfaces with state changes.
149
+ 6. Format output according to `.appweaver/templates/ui-model-template.md`.
150
+ 7. Write the authored markdown content to `{{feature_dir}}/ui/ui-model.md`.
141
151
 
142
152
  User Guidance:
143
153
  {{user_preferences}}
@@ -141,6 +141,9 @@ workflow:
141
141
  - { path: "{{domain_model}}", optional: true }
142
142
  - { path: "{{feature_dir}}/ui/workflow-model.md", optional: true }
143
143
  - { path: "{{feature_dir}}/ui/ui-model.md", optional: true }
144
+ - { path: "guardrails/projects/{{domain}}/frontend/design.md", optional: true }
145
+ - { path: "guardrails/projects/{{domain}}/frontend/DESIGN.md", optional: true }
146
+ - { path: "guardrails/projects/{{domain}}/frontend/ui-requirements.md", optional: true }
144
147
  - { path: ".appweaver/memory/constitution.domain.md", optional: true }
145
148
  - { path: ".appweaver/memory/constitution.ui.md", optional: true }
146
149
  - { path: "{{feature_dir}}/changes/{{active_change}}/delta-spec.md", optional: true }
@@ -197,6 +200,10 @@ workflow:
197
200
  - The persona/role switcher must be styled and framed as a normal in-app affordance
198
201
  (e.g. an account/role menu a real user of this kind of app would plausibly have),
199
202
  not as an obvious dev/testing control bolted onto the UI.
203
+ - **Simplicity of language and visual calmness**:
204
+ - Use simple, everyday product language (e.g. "Projects", "Status", "Dates", "Budget").
205
+ - Avoid architectural meta-language or bureaucratic phrasing (e.g. do NOT say "Initiative Lifecycle Distribution", "Workspace Governance & Audit Stream", "Optimistic Concurrency Version").
206
+ - Keep dashboards clean and focused with 3–4 high-signal metrics and concise cards, not dense walls of text or redundant explanatory paragraphs.
200
207
 
201
208
  **Styling must actually render — this is not optional:**
202
209
  - Install and fully configure **Tailwind CSS** (latest stable version, matching whatever
@@ -247,6 +254,7 @@ workflow:
247
254
  The feature specification is taken directly from the bounded context directory and feature path defined in `.appweaver/feature.json`:
248
255
  `{{spec_file}}` (Bounded Context: `{{context_name}}`, Feature: `{{feature_name}}`).
249
256
  Read this specification strictly from `{{spec_file}}`, along with the bounded context's canonical domain model (`{{domain_model}}`),
257
+ the project Design System if present (`guardrails/projects/{{domain}}/frontend/design.md` or `DESIGN.md`, or `{{feature_dir}}/ui/design.md`),
250
258
  and when available, the Workflow Model (`{{feature_dir}}/ui/workflow-model.md`) and UI Model (`{{feature_dir}}/ui/ui-model.md`), plus `{{prototype_root}}/lib/screens-registry.ts` / `screens.json`.
251
259
  If `workflow-model.md` and `ui-model.md` are not provided, derive all UI surfaces, workflows, and interaction states directly from `{{spec_file}}`.
252
260
 
@@ -266,9 +274,24 @@ workflow:
266
274
  see the actual working application screen, fully styled and populated, matching
267
275
  spec.md as if it shipped this way.
268
276
  - Never surface "prototype/mock/demo/sample" language, or raw spec/traceability IDs
269
- (`REQ-###`, `SPEC-SCREEN-###`, etc.) anywhere in this screen's rendered UI (titles,
277
+ (`REQ-###`, `SPEC-SCREEN-###`, `BR-###`, `FR-###`, `CN-###`, `DC-###`, `UX-###`) anywhere in this screen's rendered UI (titles,
270
278
  headers, empty states, tooltips, button copy). Keep all such labeling out of
271
279
  user-visible text entirely.
280
+ - **Natural product language over spec jargon**:
281
+ - Translate formal architectural and domain-driven design terms into clean, everyday product vocabulary:
282
+ - Say "Projects" (or simple domain noun) rather than "Portfolio Initiatives" or "Domain Aggregate".
283
+ - Say "Status" rather than "Governance State" or "Lifecycle Progression".
284
+ - Say "Timeline" or "Dates" rather than "Target Timeline Trajectory Bounds".
285
+ - Say "Budget" rather than "Allocated Capital Valuation".
286
+ - Say "Change Status" rather than "Execute Lifecycle Status Transition".
287
+ - Say "New Project" rather than "Create Initiative".
288
+ - Say "Activity" or "History" rather than "Immutable Governance Audit Stream".
289
+ - Avoid robotic, academic, or overly bureaucratic copy. Write like modern, top-tier SaaS products (e.g. Linear, Stripe, Notion).
290
+ - **Progressive disclosure and scannable information density**:
291
+ - Do NOT dump every requirement, invariant, and metadata attribute onto primary cards or lists.
292
+ - Keep primary cards and table rows clean and scannable — display only 3–5 essential, high-signal data points (e.g. Name, Status, Dates, Owner, Key Metric).
293
+ - Place deep operational details (audit histories, concurrency tokens, classification codes, full event trails) behind secondary inspection tabs, modals, or slide-over drawers — never on primary summary cards.
294
+ - Keep interfaces free of long instructional text blocks, redundant help banners, or cluttering meta-labels.
272
295
 
273
296
  2. **Integrate this screen into the app like a real feature, not a spec entry**:
274
297
  - `{{feature_name}}` and any spec title/ID are internal identifiers for you to read —
@@ -319,6 +342,9 @@ workflow:
319
342
  Tailwind/CSS config from the shell is intact. If it's missing or broken, fix it
320
343
  as part of this run and note that in your final summary; don't build a styled-looking
321
344
  screen on top of a broken/absent styling pipeline.
345
+ - If a project visual design system is present in context (`guardrails/projects/{{domain}}/frontend/design.md` or `DESIGN.md`),
346
+ apply its visual design tokens (colors, accent, surface, border, typography, card elevation, and status states)
347
+ consistently across all cards, badges, buttons, forms, and headers on this screen.
322
348
  - Use the shell's existing design tokens and Tailwind utility classes (or whatever
323
349
  styling system the shell uses) rather than inline style objects or
324
350
  ad-hoc CSS that bypasses the shared system — inline styles that "look fine" in
@@ -356,6 +382,17 @@ workflow:
356
382
  original spec title/ID for traceability, add it as a separate non-rendered field
357
383
  (e.g. `specRef`) rather than reusing `title` for it.
358
384
 
385
+ 10. **Structured UI Specification Output (`ui-spec.md`)**:
386
+ - Generate or update `{{feature_dir}}/ui/ui-spec.md` alongside the prototype code.
387
+ - Format as a markdown document with a brief title and capture intro, followed by a SINGLE fenced `json` code block adhering strictly to the JSON schema defined in `skill/references/output-ui-spec.md`:
388
+ - `screen`: user-facing screen name and route
389
+ - `captureNotes`: viewport width, CSS px, DPR, and design system token origin (`guardrails/projects/{{domain}}/frontend/DESIGN.md` or `design.md`)
390
+ - `regions`: ordered list of UI regions (nav-bar, header, step-card, content, dialogs, drawers) with `layout` (`position`, `wrap`, `direction`) and `elements` array
391
+ - `elements`: each element detailing `id`, `kind` (`text`, `button`, `badge`, `avatar`, `card`, `input`, etc.), verbatim `content`, `style` (`shape`, `colorToken`, `typographyToken`, `spacingPx`), `state` (`default`, `active`, `hover`), and `fields`
392
+ - `navigation`: ordered nav items, activeItem, activeStyle
393
+ - `openQuestions`: roll-up of ambiguities, edge cases, and responsive variations
394
+ - Ensure all `colorToken` and `typographyToken` values reference design tokens from the domain design system.
395
+
359
396
  User Preferences / Guidance:
360
397
  {{user_preferences}}
361
398
 
@@ -367,6 +404,7 @@ workflow:
367
404
  "feature": "{{feature_name}}",
368
405
  "slug": "{{feature_slug}}",
369
406
  "route": "/{{feature_slug}}",
407
+ "ui_spec": "{{feature_dir}}/ui/ui-spec.md",
370
408
  "prototype_root": "{{prototype_root}}",
371
409
  "launch_command": "cd {{prototype_root}} && npm run dev"
372
410
  }
@@ -380,6 +418,7 @@ outputs:
380
418
  context_dir: "{{context_dir}}"
381
419
  feature: "{{feature}}"
382
420
  spec_file: "{{spec_file}}"
421
+ ui_spec_file: "{{feature_dir}}/ui/ui-spec.md"
383
422
  route: "/{{feature_slug}}"
384
423
  prototype_root: "{{prototype_root}}"
385
424
  launch_hint: "First time: cd {{prototype_root}} && npm install && npm run dev — after that, just rerun ui-prototype per feature and refresh the browser."
@@ -123,8 +123,9 @@ workflow:
123
123
  1. Read the feature specification (`spec.md` + active `delta-spec.md`) in context.
124
124
  2. Identify ALL discrete user workflows required to fulfill the feature's capabilities, functional requirements (FRs), business rules (BRs), and user personas.
125
125
  3. For EACH user workflow (WF-1, WF-2, ...), detail:
126
+ - **Workflow Title**: Name concisely after real, plain-English user actions (e.g. "WF-1: Browse Projects", "WF-2: Create Project", "WF-3: View Project Details", "WF-4: Edit Project", "WF-5: Change Status" — avoid bureaucratic or formal titles like "Execute Lifecycle Status Transition").
126
127
  - **Actor / Persona**: Which role executes this flow (e.g. Project Manager, Delivery Head).
127
- - **Goal**: Clear statement of user goal.
128
+ - **Goal**: Clear statement of user goal in natural language.
128
129
  - **Preconditions**: Initial system state, permissions, or prerequisite conditions.
129
130
  - **Primary Flow**: Step-by-step numbered interaction steps from start to completion.
130
131
  - **Decision Points & Alternative Paths**: Clear branch logic (e.g. If user cancels, If user requests correction).
@@ -6,7 +6,7 @@
6
6
  # ONCE in ../python/aw-rev-constitution.py; this wrapper exists only so the
7
7
  # orchestrator's execute_script (sh:/ps1: dispatch) can invoke it. Args pass through.
8
8
  #
9
- # aw-rev-constitution.sh list-adopted [--root <dir>] [--specs <dir>] [--bundle <dir>]
9
+ # aw-rev-constitution.sh project-context [--root <dir>] [--bundle <dir>]
10
10
  # aw-rev-constitution.sh project-setup --name <domain> [--fallback <name>] [--root <dir>]
11
11
  set -euo pipefail
12
12
  DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -35,9 +35,12 @@
35
35
  # [--build <c>] [--test <c>] [--run <c>] [--prototype-root <p>] [--prototype-run <c>] [--no-prototype]
36
36
  # -> {declared, lane, lanes[], lane_count, source_path, root_exists, prototype, written}
37
37
  # (SOLE WRITER of .appweaver/stacks.json; callers = select-stack, onboard)
38
- # lane-resolve [--kind <k>] [--id <lane>] -> {declared, lanes[], found, lane, root, source_path, build, test, run,
39
- # backend_*/frontend_* (declared, root, source_path, root_exists, build, test, run), prototype}
40
- # (absence is a valid state: no stacks.json -> declared:false, exit 0)
38
+ # lane-resolve [--kind <k>] [--id <lane>] [--feature-dir <path>]
39
+ # -> {declared, lanes[], found, lane, root, source_path, build, test, run,
40
+ # backend_*/frontend_* (declared, root, source_path, root_exists, build, test, run,
41
+ # feature_excluded), feature_lanes[], prototype}
42
+ # (absence is a valid state: no stacks.json -> declared:false, exit 0;
43
+ # --feature-dir reports a lane the feature records no surface in as not declared)
41
44
  # lane-verify [--require backend|frontend]… [--allow-missing-root] [--command <phase>]
42
45
  # -> {ok, declared, issues[], issue_count, message, backend_*/frontend_*}
43
46
  # (the GATE: is the declaration usable for the phase about to run.
@@ -49,6 +52,8 @@
49
52
  # version-read [--feature-dir <path>] -> {feature, version, active_change, exists, bootstrap_pending}
50
53
  # version-init [--feature-dir <path>] [--bootstrap] -> {…, created, bootstrap_pending} (create active_version.json v1.0; --bootstrap marks a FIRST draft in progress)
51
54
  # version-bootstrap-done [--feature-dir <path>] -> {…, cleared} (the ONLY clearer of the --bootstrap marker; call it when the first draft is finished)
55
+ # feature-lanes [--feature-dir <path>] [--set backend|frontend|backend,frontend] [--clear] -> {…, lanes[]}
56
+ # (the declared lanes this feature has a surface in; empty = every declared lane)
52
57
  # feature-paths -> {feature, feature_slug, feature_dir, spec_file, backend_plan, frontend_plan, *_exists,
53
58
  # design_file/ui_design_file (aliases of the two plans), spec_exists, bootstrap_pending, needs_bootstrap}
54
59
  # checklist-read --file <path> -> {exists, total, checked, items[]}
@@ -798,7 +803,10 @@ cmd_lane_resolve() {
798
803
  while [ $# -gt 0 ]; do
799
804
  case "$1" in
800
805
  --kind|--id) fwd+=("$1" "$2"); shift 2 ;;
801
- --help|-h) echo "lane-resolve [--kind backend|frontend] [--id <lane>]"; return 0 ;;
806
+ # An EMPTY --feature-dir is dropped: a workflow renders the argument whole, and a
807
+ # feature that is not resolved yet means "the repo's declaration as it stands".
808
+ --feature-dir) if [ -n "$2" ]; then fwd+=("--feature-dir" "$2"); fi; shift 2 ;;
809
+ --help|-h) echo "lane-resolve [--kind backend|frontend] [--id <lane>] [--feature-dir <path>]"; return 0 ;;
802
810
  *) echo "lane-resolve: unknown arg '$1'" >&2; return 1 ;;
803
811
  esac
804
812
  done
@@ -1470,15 +1478,15 @@ _resolve_feature_dir() { # $1 = --feature-dir arg (may be empty), $2 = repo_ro
1470
1478
  printf '%s' "$fd"
1471
1479
  }
1472
1480
 
1473
- # Shared reader/mutator: op in {read,init,bootstrap-done,open,activate,close}. Prints one
1481
+ # Shared reader/mutator: op in {read,init,bootstrap-done,lanes,open,activate,close}. Prints one
1474
1482
  # JSON object.
1475
1483
  # MANY CHANGES MAY BE OPEN ON ONE FEATURE. open_changes[] is the record; active_change is
1476
1484
  # the most recently opened OR the one a later stage activated, and is kept correct for
1477
1485
  # every stage that still resolves "the" change through it. `force` is accepted and unused
1478
1486
  # on open — the guard it overrode is gone — and is kept so existing callers do not have to
1479
1487
  # change.
1480
- _active_version() { # $1 = abs feature_dir, $2 = op, $3 = cr_id (open/close), $4 = force (accepted, inert), $5 = bootstrap ("1", init only)
1481
- AW_FD="$1" AW_OP="$2" AW_CR="${3:-}" AW_FORCE="${4:-}" AW_BOOT="${5:-}" python3 - <<'PY'
1488
+ _active_version() { # $1 = abs feature_dir, $2 = op, $3 = cr_id (open/close), $4 = force (accepted, inert), $5 = bootstrap ("1", init only), $6 = lanes (lanes only)
1489
+ AW_FD="$1" AW_OP="$2" AW_CR="${3:-}" AW_FORCE="${4:-}" AW_BOOT="${5:-}" AW_LANES="${6:-}" python3 - <<'PY'
1482
1490
  import json, os, re, datetime, sys
1483
1491
  fd = os.environ["AW_FD"]; op = os.environ["AW_OP"]; cr = os.environ.get("AW_CR", "")
1484
1492
  force = os.environ.get("AW_FORCE", "")
@@ -1529,6 +1537,31 @@ elif op == "init":
1529
1537
  d["bootstrap_pending"] = True
1530
1538
  save(d)
1531
1539
  out["created"] = (not existed)
1540
+ elif op == "lanes":
1541
+ # WHICH DECLARED LANES THIS FEATURE HAS A SURFACE IN. Absent means every lane the repo
1542
+ # declares, which is the ordinary case and needs no record. A feature that genuinely has
1543
+ # no part of one lane's surface — a job-only or shared-kernel slice in a repo that also
1544
+ # has a UI — records the kinds it does have, and `lane-resolve --feature-dir` then reports
1545
+ # the other lane as not declared FOR THIS FEATURE, so no phase plans, gates or builds a
1546
+ # surface the feature does not own. `all` removes the record; an empty value only reads.
1547
+ # Refuses to create the file: init is its one creator, and a record with no version
1548
+ # spine would read as a feature nobody specified.
1549
+ want = os.environ.get("AW_LANES", "").strip()
1550
+ if want and not existed:
1551
+ out["ok"] = False
1552
+ out["error"] = "no active_version.json for this feature yet — it is created by specify or adopt"
1553
+ elif want == "all":
1554
+ d.pop("lanes", None)
1555
+ save(d)
1556
+ elif want:
1557
+ kinds = [k.strip() for k in want.split(",") if k.strip()]
1558
+ bad = [k for k in kinds if k not in ("backend", "frontend")]
1559
+ if bad or not kinds:
1560
+ out["ok"] = False
1561
+ out["error"] = "lanes takes backend, frontend or both (comma-separated), or all; got '%s'" % want
1562
+ else:
1563
+ d["lanes"] = [k for k in ("backend", "frontend") if k in kinds]
1564
+ save(d)
1532
1565
  elif op == "bootstrap-done":
1533
1566
  # THE ONLY THING THAT CLEARS THE MARKER, called by the phase that raised it once the
1534
1567
  # first draft is actually finished. It is a separate op, not a side effect of writing
@@ -1628,6 +1661,7 @@ elif op == "close":
1628
1661
  out["previous"] = prev; out["closed_change"] = closed
1629
1662
  out["open_changes"] = opens; out["open_count"] = len(opens)
1630
1663
  out["bootstrap_pending"] = bool(d.get("bootstrap_pending"))
1664
+ out["lanes"] = list(d.get("lanes") or []) # empty = every declared lane
1631
1665
  out["has_change"] = bool(out.get("active_change", "")) # clean branch signal for design
1632
1666
  # What a close from THIS state would produce. One bump() authority, read by BOTH
1633
1667
  # finalize-delta (to write the right number into the Change History rows it appends)
@@ -1642,6 +1676,33 @@ PY
1642
1676
  cmd_version_read() { _version_cmd "read" "$@"; }
1643
1677
  cmd_version_init() { _version_cmd "init" "$@"; }
1644
1678
  cmd_version_bootstrap_done() { _version_cmd "bootstrap-done" "$@"; }
1679
+
1680
+ # The lane kinds this feature has a surface in, recorded in its active_version.json. adopt
1681
+ # records `backend` for a slice that owns no part of a declared frontend's surface, and
1682
+ # `lane-resolve --feature-dir` reads it back. No argument reads; --clear drops the record so
1683
+ # the feature follows every declared lane again.
1684
+ cmd_feature_lanes() {
1685
+ local fd_arg="" lanes=""
1686
+ while [ $# -gt 0 ]; do
1687
+ case "$1" in
1688
+ --feature-dir) fd_arg="$2"; shift 2 ;;
1689
+ --set) lanes="$2"; shift 2 ;;
1690
+ --clear) lanes="all"; shift ;;
1691
+ --help|-h) echo "feature-lanes [--feature-dir <path>] [--set backend|frontend|backend,frontend] [--clear]"; return 0 ;;
1692
+ *) echo "feature-lanes: unknown arg '$1'" >&2; return 1 ;;
1693
+ esac
1694
+ done
1695
+ local repo_root; repo_root=$(get_repo_root)
1696
+ local fd; fd=$(_resolve_feature_dir "$fd_arg" "$repo_root") || { echo "feature-lanes: no feature set and no --feature-dir" >&2; return 1; }
1697
+ local out; out=$(_active_version "$fd" "lanes" "" "" "" "$lanes")
1698
+ printf '%s\n' "$out"
1699
+ # A refused write is a failure the caller has to see: the orchestrator reports a
1700
+ # failed script from stderr, so the reason goes there too.
1701
+ if printf '%s' "$out" | grep -q '"ok": false'; then
1702
+ printf '%s' "$out" | python3 -c 'import json,sys; print("feature-lanes: " + json.load(sys.stdin).get("error", "refused"), file=sys.stderr)'
1703
+ return 1
1704
+ fi
1705
+ }
1645
1706
  _version_cmd() { # $1 = op (read|init), then --feature-dir arg
1646
1707
  local op="$1"; shift
1647
1708
  local fd_arg="" boot=""
@@ -4997,6 +5058,7 @@ case "$sub" in
4997
5058
  version-read) cmd_version_read "$@" ;;
4998
5059
  version-init) cmd_version_init "$@" ;;
4999
5060
  version-bootstrap-done) cmd_version_bootstrap_done "$@" ;;
5061
+ feature-lanes) cmd_feature_lanes "$@" ;;
5000
5062
  feature-paths) cmd_feature_paths "$@" ;;
5001
5063
  design-scaffold) cmd_design_scaffold "$@" ;;
5002
5064
  # The plan phase's name for the same op. Both spellings reach one implementation:
@@ -5,7 +5,7 @@
5
5
  # ONCE in ..\python\aw-rev-constitution.py; this wrapper exists only so the
6
6
  # orchestrator's execute_script (sh:/ps1: dispatch) can invoke it. Args pass through.
7
7
  #
8
- # aw-rev-constitution.ps1 list-adopted [--root <dir>] [--specs <dir>] [--bundle <dir>]
8
+ # aw-rev-constitution.ps1 project-context [--root <dir>] [--bundle <dir>]
9
9
  # aw-rev-constitution.ps1 project-setup --name <domain> [--fallback <name>] [--root <dir>]
10
10
  $ErrorActionPreference = "Stop"
11
11
  $core = Join-Path $PSScriptRoot "..\python\aw-rev-constitution.py"
@@ -631,6 +631,9 @@ function Invoke-LaneResolve {
631
631
  for ($i = 0; $i -lt $Rest.Count; $i++) {
632
632
  switch ($Rest[$i]) {
633
633
  { $_ -in '--kind','--id' } { $fwd += $Rest[$i]; $fwd += $Rest[++$i] }
634
+ # An EMPTY --feature-dir is dropped: a workflow renders the argument whole, and a
635
+ # feature that is not resolved yet means "the repo's declaration as it stands".
636
+ '--feature-dir' { $v = $Rest[++$i]; if ($v) { $fwd += '--feature-dir'; $fwd += $v } }
634
637
  default { Write-Error "lane-resolve: unknown arg '$($Rest[$i])'"; exit 1 }
635
638
  }
636
639
  }
@@ -1084,6 +1087,31 @@ elif op == "init":
1084
1087
  d["bootstrap_pending"] = True
1085
1088
  save(d)
1086
1089
  out["created"] = (not existed)
1090
+ elif op == "lanes":
1091
+ # WHICH DECLARED LANES THIS FEATURE HAS A SURFACE IN. Absent means every lane the repo
1092
+ # declares, which is the ordinary case and needs no record. A feature that genuinely has
1093
+ # no part of one lane's surface — a job-only or shared-kernel slice in a repo that also
1094
+ # has a UI — records the kinds it does have, and `lane-resolve --feature-dir` then reports
1095
+ # the other lane as not declared FOR THIS FEATURE, so no phase plans, gates or builds a
1096
+ # surface the feature does not own. `all` removes the record; an empty value only reads.
1097
+ # Refuses to create the file: init is its one creator, and a record with no version
1098
+ # spine would read as a feature nobody specified.
1099
+ want = os.environ.get("AW_LANES", "").strip()
1100
+ if want and not existed:
1101
+ out["ok"] = False
1102
+ out["error"] = "no active_version.json for this feature yet — it is created by specify or adopt"
1103
+ elif want == "all":
1104
+ d.pop("lanes", None)
1105
+ save(d)
1106
+ elif want:
1107
+ kinds = [k.strip() for k in want.split(",") if k.strip()]
1108
+ bad = [k for k in kinds if k not in ("backend", "frontend")]
1109
+ if bad or not kinds:
1110
+ out["ok"] = False
1111
+ out["error"] = "lanes takes backend, frontend or both (comma-separated), or all; got '%s'" % want
1112
+ else:
1113
+ d["lanes"] = [k for k in ("backend", "frontend") if k in kinds]
1114
+ save(d)
1087
1115
  elif op == "bootstrap-done":
1088
1116
  # THE ONLY THING THAT CLEARS THE MARKER, called by the phase that raised it once the
1089
1117
  # first draft is actually finished. It is a separate op, not a side effect of writing
@@ -1183,6 +1211,7 @@ elif op == "close":
1183
1211
  out["previous"] = prev; out["closed_change"] = closed
1184
1212
  out["open_changes"] = opens; out["open_count"] = len(opens)
1185
1213
  out["bootstrap_pending"] = bool(d.get("bootstrap_pending"))
1214
+ out["lanes"] = list(d.get("lanes") or []) # empty = every declared lane
1186
1215
  out["has_change"] = bool(out.get("active_change", "")) # clean branch signal for design
1187
1216
  # What a close from THIS state would produce. One bump() authority, read by BOTH
1188
1217
  # finalize-delta (to write the right number into the Change History rows it appends)
@@ -1194,10 +1223,10 @@ print(json.dumps(out))
1194
1223
  '@
1195
1224
 
1196
1225
  function Invoke-ActiveVersion {
1197
- param([string]$FeatureDir, [string]$Op, [string]$CrId = '', [string]$Force = '', [string]$Boot = '')
1226
+ param([string]$FeatureDir, [string]$Op, [string]$CrId = '', [string]$Force = '', [string]$Boot = '', [string]$Lanes = '')
1198
1227
  $py = Get-Py; if (-not $py) { Write-Error "version-$Op`: python3 required"; exit 1 }
1199
1228
  $env:AW_FD = $FeatureDir; $env:AW_OP = $Op; $env:AW_CR = $CrId; $env:AW_FORCE = $Force
1200
- $env:AW_BOOT = $Boot
1229
+ $env:AW_BOOT = $Boot; $env:AW_LANES = $Lanes
1201
1230
  # CrId names the change on BOTH open and close: many may be open at once, so a close
1202
1231
  # has to say which one it is closing rather than assuming the only one.
1203
1232
  $PyActiveVersion | & $py -
@@ -1216,6 +1245,32 @@ function Invoke-VersionState { # op in read|init (feature-dir only)
1216
1245
  Invoke-ActiveVersion -FeatureDir $fd -Op $Op -Boot $boot
1217
1246
  }
1218
1247
 
1248
+ # The lane kinds this feature has a surface in, recorded in its active_version.json. Twin
1249
+ # of bash cmd_feature_lanes: adopt records `backend` for a slice that owns no part of a
1250
+ # declared frontend's surface, and `lane-resolve --feature-dir` reads it back. No argument
1251
+ # reads; --clear drops the record so the feature follows every declared lane again.
1252
+ function Invoke-FeatureLanes {
1253
+ param([string[]]$Rest)
1254
+ $fdArg = ''; $lanes = ''
1255
+ for ($i = 0; $i -lt $Rest.Count; $i++) {
1256
+ switch ($Rest[$i]) {
1257
+ { $_ -in '--feature-dir','-FeatureDir' } { $fdArg = $Rest[++$i] }
1258
+ '--set' { $lanes = $Rest[++$i] }
1259
+ '--clear' { $lanes = 'all' }
1260
+ default { Write-Error "feature-lanes: unknown arg '$($Rest[$i])'"; exit 1 }
1261
+ }
1262
+ }
1263
+ $repo = Get-RepoRoot
1264
+ $fd = Resolve-FeatureDir -Arg $fdArg -RepoRoot $repo
1265
+ if (-not $fd) { Write-Error 'feature-lanes: no feature set and no --feature-dir'; exit 1 }
1266
+ $out = Invoke-ActiveVersion -FeatureDir $fd -Op 'lanes' -Lanes $lanes
1267
+ $out
1268
+ # A refused write is a failure the caller has to see: the orchestrator reports a failed
1269
+ # script from stderr, so the reason goes there too.
1270
+ $res = $out | ConvertFrom-Json
1271
+ if (-not $res.ok) { [Console]::Error.WriteLine("feature-lanes: $($res.error)"); exit 1 }
1272
+ }
1273
+
1219
1274
 
1220
1275
 
1221
1276
  # ─────────────────────────── feature-paths ─────────────────────────────────
@@ -4403,6 +4458,7 @@ switch ($sub) {
4403
4458
  'version-read' { Invoke-VersionState 'read' $rest }
4404
4459
  'version-init' { Invoke-VersionState 'init' $rest }
4405
4460
  'version-bootstrap-done' { Invoke-VersionState 'bootstrap-done' $rest }
4461
+ 'feature-lanes' { Invoke-FeatureLanes -Rest $rest }
4406
4462
  'feature-paths' { Invoke-FeaturePaths -Rest $rest }
4407
4463
  'design-scaffold' { Invoke-DesignScaffold -Rest $rest }
4408
4464
  # The plan phase's name for the same op. Both spellings reach one implementation: