aw-spec-cli 0.1.26 → 0.1.27

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 (98) 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 +3 -3
  4. package/assets/.appweaver/STACK-PROFILES.md +188 -101
  5. package/assets/.appweaver/domain.json +1 -1
  6. package/assets/.appweaver/orchestrator/AGENT-PROFILE.md +38 -10
  7. package/assets/.appweaver/orchestrator/LAUNCHER.md +88 -0
  8. package/assets/.appweaver/orchestrator/README.md +120 -53
  9. package/assets/.appweaver/orchestrator/adapters/claude_agent_sdk.py +1 -1
  10. package/assets/.appweaver/orchestrator/modules/domain-select.orch.yaml +6 -4
  11. package/assets/.appweaver/orchestrator/modules/foundation-lane.orch.yaml +237 -0
  12. package/assets/.appweaver/orchestrator/modules/foundation-pack.orch.yaml +349 -0
  13. package/assets/.appweaver/orchestrator/modules/foundation-spec.orch.yaml +381 -0
  14. package/assets/.appweaver/orchestrator/modules/lane-gate.orch.yaml +4 -4
  15. package/assets/.appweaver/orchestrator/modules/tech-select.orch.yaml +3 -3
  16. package/assets/.appweaver/orchestrator/orchestrator.py +162 -63
  17. package/assets/.appweaver/orchestrator/rev-templates/domain-constitution.skeleton.md +2 -2
  18. package/assets/.appweaver/orchestrator/rev-templates/domain-requirements.skeleton.md +2 -2
  19. package/assets/.appweaver/orchestrator/rev-templates/domain-rule-bindings.skeleton.md +5 -5
  20. package/assets/.appweaver/orchestrator/rev-templates/frontend-rule-file.skeleton.md +1 -1
  21. package/assets/.appweaver/orchestrator/rev-templates/tech-rule-bindings.skeleton.md +14 -14
  22. package/assets/.appweaver/orchestrator/rev-templates/technical-constitution.skeleton.md +2 -2
  23. package/assets/.appweaver/orchestrator/rev-templates/technical-rule-file.skeleton.md +1 -1
  24. package/assets/.appweaver/orchestrator/rev-templates/ui-constitution.skeleton.md +3 -3
  25. package/assets/.appweaver/orchestrator/workflows/_smoke.orch.yaml +41 -3
  26. package/assets/.appweaver/orchestrator/workflows/aw-spec-add-component.orch.yaml +1 -1
  27. package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +13 -13
  28. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +3 -3
  29. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +3 -3
  30. package/assets/.appweaver/orchestrator/workflows/aw-spec-build.orch.yaml +4 -4
  31. package/assets/.appweaver/orchestrator/workflows/aw-spec-characterize.orch.yaml +2 -2
  32. package/assets/.appweaver/orchestrator/workflows/aw-spec-constitution.orch.yaml +61 -12
  33. package/assets/.appweaver/orchestrator/workflows/aw-spec-foundation.orch.yaml +172 -293
  34. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +29 -28
  35. package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +53 -53
  36. package/assets/.appweaver/orchestrator/workflows/aw-spec-parity.orch.yaml +2 -2
  37. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +3 -3
  38. package/assets/.appweaver/orchestrator/workflows/aw-spec-run.orch.yaml +4 -4
  39. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-bounded-context.orch.yaml +26 -2
  40. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-domain.orch.yaml +21 -15
  41. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-stack.orch.yaml +14 -14
  42. package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +2 -2
  43. package/assets/.appweaver/orchestrator/workflows/aw-spec-ui-model.orch.yaml +3 -3
  44. package/assets/.appweaver/orchestrator/workflows/aw-spec-ui-prototype.orch.yaml +6 -6
  45. package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +284 -128
  46. package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +224 -70
  47. package/assets/.appweaver/scripts/python/aw-foundation.py +809 -20
  48. package/assets/.appweaver/scripts/python/aw-inventory.py +28 -21
  49. package/assets/.appweaver/scripts/python/aw-plan-coverage.py +2 -2
  50. package/assets/.appweaver/scripts/python/aw-rev-constitution.py +6 -6
  51. package/assets/.appweaver/scripts/python/aw-stack.py +195 -89
  52. package/assets/.appweaver/stack-profiles/angular.json +2 -1
  53. package/assets/.appweaver/stack-profiles/blazor-wasm.json +2 -1
  54. package/assets/.appweaver/stack-profiles/dotnet-webapi.json +16 -3
  55. package/assets/.appweaver/stack-profiles/go-chi.json +6 -1
  56. package/assets/.appweaver/stack-profiles/java-spring.json +12 -5
  57. package/assets/.appweaver/stack-profiles/nextjs.json +2 -1
  58. package/assets/.appweaver/stack-profiles/node-express.json +8 -2
  59. package/assets/.appweaver/stack-profiles/python-fastapi.json +9 -2
  60. package/assets/.appweaver/stack-profiles/react-vite.json +2 -1
  61. package/assets/.appweaver/stack-profiles/sveltekit.json +2 -1
  62. package/assets/.appweaver/stack-profiles/vue-vite.json +2 -1
  63. package/assets/.appweaver/stacks.json +37 -1
  64. package/assets/.appweaver/templates/constitution-ui-template.md +2 -2
  65. package/assets/.claude/skills/appweaver/SKILL.md +7 -46
  66. package/assets/foundations/dotnet/ModularMonolith/ModularMonolith.Reference.rules.json +31 -0
  67. package/assets/foundations/dotnet/VerticalSlice/VerticalSlice.Reference.rules.json +16 -0
  68. package/assets/foundations/foundation.spec.template.txt +73 -0
  69. package/assets/foundations/java/Hexagonal/Hexagonal.pack.txt +1 -1
  70. package/assets/foundations/java/Hexagonal/unpack-foundation.sh +4 -5
  71. package/assets/foundations/node/NextAppRouter/NextAppRouter.spec.txt +68 -0
  72. package/assets/guardrails/README.md +31 -17
  73. package/assets/guardrails/{projects/dotnet-default-project → project-specific}/domain-rule-bindings.md +5 -5
  74. package/assets/guardrails/{projects/dotnet-default-project → project-specific}/tech-rule-bindings.md +151 -114
  75. package/assets/guardrails/technology/dotnet-webapi/PACKAGING.md +58 -4
  76. package/assets/guardrails/technology/dotnet-webapi/coding-standards.md +1 -1
  77. package/assets/guardrails/technology/dotnet-webapi/dependency-policy.md +1 -1
  78. package/assets/guardrails/technology/dotnet-webapi/folder-structure.md +1 -1
  79. package/assets/guardrails/technology/dotnet-webapi/framework-api-map.md +1 -1
  80. package/assets/guardrails/technology/dotnet-webapi/testing.md +1 -1
  81. package/assets/guardrails/technology/nextjs/api-client.md +1 -1
  82. package/assets/guardrails/technology/nextjs/component-architecture.md +1 -1
  83. package/assets/guardrails/technology/nextjs/component-conventions.md +2 -2
  84. package/assets/guardrails/technology/nextjs/design-system.md +1 -1
  85. package/assets/guardrails/technology/nextjs/frontend-testing.md +1 -1
  86. package/assets/guardrails/technology/nextjs/routing-navigation.md +1 -1
  87. package/assets/guardrails/technology/nextjs/ui-constitution.md +3 -3
  88. package/package.json +1 -1
  89. package/assets/.agents/skills/appweaver-demo/SKILL.md +0 -59
  90. package/assets/.appweaver/orchestrator/.smoke-out/smoke.md +0 -6
  91. package/assets/.appweaver/orchestrator/workflows/aw-spec-demo.orch.yaml +0 -100
  92. package/assets/.claude/skills/appweaver-demo/SKILL.md +0 -59
  93. package/assets/.kilocode/agent.json +0 -28
  94. package/assets/.kilocode/workflows/appweaver-demo.md +0 -59
  95. package/assets/.kilocode/workflows/appweaver.md +0 -55
  96. /package/assets/guardrails/{projects/dotnet-default-project → project-specific}/domain/domain-requirements.md +0 -0
  97. /package/assets/guardrails/{projects/dotnet-default-project → project-specific}/frontend/ui-requirements.md +0 -0
  98. /package/assets/guardrails/{projects/dotnet-default-project → project-specific}/service/technical-requirements.md +0 -0
@@ -3,7 +3,7 @@
3
3
  # aw-spec-actions.ps1 — common action dispatcher for the aw-spec orchestrator
4
4
  # (Windows / PowerShell mirror of aw-spec-actions.sh).
5
5
  #
6
- # ONE shared script for every integration (.claude / .agents / .kilocode). The
6
+ # ONE shared script for every integration (.claude / .agents / .kilo). The
7
7
  # orchestrator workflows reach these subcommands through `execute_script` steps.
8
8
  # Each subcommand performs deterministic, governance-safe work and prints a single
9
9
  # JSON object on stdout. NONE of these actions author spec/code content — content
@@ -107,7 +107,7 @@ function Invoke-ResolveDomain {
107
107
  try { $domain = (Get-Content $domainJson -Raw | ConvertFrom-Json).domain } catch { $domain = '' }
108
108
  if (-not $domain) { $domain = '' }
109
109
  }
110
- $rulemap = Join-Path $repo "guardrails/projects/$domain/domain-rule-bindings.md"
110
+ $rulemap = Join-Path $repo "guardrails/$domain/domain-rule-bindings.md"
111
111
  $rulemapExists = ($domain -ne '') -and (Test-Path $rulemap)
112
112
 
113
113
  $candidates = @()
@@ -132,7 +132,7 @@ function Invoke-ResolveDomain {
132
132
 
133
133
  # ─────────────────────────── resolve-tech ───────────────────────────────────
134
134
  # Technical rule-file mapping — parallel to resolve-domain, but reads
135
- # guardrails/projects/<project>/tech-rule-bindings.md. The TECHNICAL rule files live in the
135
+ # guardrails/<project>/tech-rule-bindings.md. The TECHNICAL rule files live in the
136
136
  # shared per-stack corpora at guardrails/technology/<stack>/ (rules) and
137
137
  # guardrails/technology/<stack>/examples/ (the worked sample for a rule file), and are
138
138
  # configured per-command in this map, so they can be tuned per project WITHOUT editing the
@@ -162,7 +162,7 @@ function Invoke-ResolveTech {
162
162
  try { $domain = (Get-Content $domainJson -Raw | ConvertFrom-Json).domain } catch { $domain = '' }
163
163
  if (-not $domain) { $domain = '' }
164
164
  }
165
- $techmap = Join-Path $repo "guardrails/projects/$domain/tech-rule-bindings.md"
165
+ $techmap = Join-Path $repo "guardrails/$domain/tech-rule-bindings.md"
166
166
  $techmapExists = ($domain -ne '') -and (Test-Path $techmap)
167
167
 
168
168
  # The declared stacks, so one map can be written for any of them.
@@ -512,15 +512,16 @@ function Invoke-CreateChecklist {
512
512
  }
513
513
 
514
514
  # ─────────────────────────── list-domains ──────────────────────────────────
515
- # The selectable projects: immediate subdirs of guardrails/projects/ that carry a rule
516
- # map. The map is what makes a directory a project, exactly as a *.pack.txt is what makes
517
- # a foundations/ directory a foundation — without that test any stray sibling would be
518
- # offered as a governance target. EITHER map qualifies, because onboard writes the domain
519
- # map several steps before the technical one and a half-built project must stay visible.
515
+ # The projects: immediate subdirs of guardrails/ that carry a rule map. The map is what
516
+ # makes a directory a project, exactly as a *.pack.txt is what makes a foundations/
517
+ # directory a foundation — the shared corpora technology/ and domain/ sit beside the
518
+ # projects and carry no map, so they are never listed. EITHER map qualifies, because
519
+ # onboard writes the domain map several steps before the technical one and a half-built
520
+ # project must stay visible.
520
521
  function Invoke-ListDomains {
521
522
  param([string[]]$Rest)
522
523
  $repo = Get-RepoRoot
523
- $base = Join-Path $repo 'guardrails/projects'
524
+ $base = Join-Path $repo 'guardrails'
524
525
  $domains = @()
525
526
  if (Test-Path $base) {
526
527
  $domains = @(Get-ChildItem -Directory $base -ErrorAction SilentlyContinue | Where-Object {
@@ -541,7 +542,7 @@ function Invoke-SetDomain {
541
542
  if (-not $domain) { Write-Error 'set-domain: --domain is required'; exit 1 }
542
543
 
543
544
  $repo = Get-RepoRoot
544
- $mapRel = "guardrails/projects/$domain/domain-rule-bindings.md"
545
+ $mapRel = "guardrails/$domain/domain-rule-bindings.md"
545
546
  $mapExists = Test-Path (Join-Path $repo $mapRel)
546
547
 
547
548
  New-Item -ItemType Directory -Force -Path (Join-Path $repo '.appweaver') | Out-Null
@@ -642,7 +643,7 @@ function Invoke-LaneResolve {
642
643
 
643
644
  # The gate: is the declaration USABLE for the phase about to run. Twin of bash
644
645
  # cmd_lane_verify. --require names a lane kind the phase cannot run without;
645
- # --allow-missing-root is the greenfield state, where a source path is declared
646
+ # --allow-missing-root is the greenfield state, where a lane root folder is declared
646
647
  # before implement creates it.
647
648
  function Invoke-LaneVerify {
648
649
  param([string[]]$Rest)
@@ -811,6 +812,9 @@ function Invoke-ListContexts {
811
812
  # Twin of bash cmd_set_context. Creates the context directory but NOT
812
813
  # domain-model.md — specify is the only writer of the model, and an empty
813
814
  # placeholder would read as "the model exists" to every later gate.
815
+ # Clears .appweaver/feature.json when the selected context does not contain the
816
+ # active feature (a flat specs/<Feature> included), so no feature-scoped command
817
+ # writes into the context the developer just left.
814
818
  function Invoke-SetContext {
815
819
  param([string[]]$Rest)
816
820
  $raw = ''
@@ -840,6 +844,18 @@ function Invoke-SetContext {
840
844
  [ordered]@{ context = $context; context_directory = $contextDirRel } | ConvertTo-Json -Compress |
841
845
  Set-Content -Path (Join-Path $repo '.appweaver/context.json') -Encoding UTF8
842
846
 
847
+ $activeFd = "$(Read-FeatureDir -RepoRoot $repo)" -replace '\\', '/'
848
+ $repoPrefix = ($repo -replace '\\', '/').TrimEnd('/') + '/'
849
+ if ($activeFd.StartsWith($repoPrefix, [System.StringComparison]::Ordinal)) { $activeFd = $activeFd.Substring($repoPrefix.Length) }
850
+ if ($activeFd.StartsWith('./', [System.StringComparison]::Ordinal)) { $activeFd = $activeFd.Substring(2) }
851
+ $activeFd = $activeFd.TrimEnd('/')
852
+ $featureCleared = $false; $clearedFeature = ''
853
+ if ($activeFd -and -not $activeFd.StartsWith("$contextDirRel/", [System.StringComparison]::Ordinal)) {
854
+ @{ feature_directory = '' } | ConvertTo-Json -Compress |
855
+ Set-Content -Path (Join-Path $repo '.appweaver/feature.json') -Encoding UTF8
856
+ $featureCleared = $true; $clearedFeature = $activeFd
857
+ }
858
+
843
859
  Write-Json ([ordered]@{
844
860
  context = $context
845
861
  context_dir = $contextDirRel
@@ -847,6 +863,8 @@ function Invoke-SetContext {
847
863
  existed = [bool]$existed
848
864
  created = (-not $existed)
849
865
  domain_model_exists = (Test-Path (Join-Path $contextDir 'domain-model.md'))
866
+ feature_cleared = $featureCleared
867
+ cleared_feature = $clearedFeature
850
868
  written = $true
851
869
  })
852
870
  }
@@ -1033,6 +1051,43 @@ function Invoke-SetFeature {
1033
1051
  })
1034
1052
  }
1035
1053
 
1054
+ # ─────────────────────────── default-scope ─────────────────────────────────
1055
+ # Twin of bash cmd_default_scope. Acts only on a project with no specs yet — no
1056
+ # directory under specs/ and no active feature — and creates through set-context and
1057
+ # set-feature, so both pointer files are written exactly as the select commands write
1058
+ # them. context_dir / feature_dir report the active pair after the call, created or not.
1059
+ function Invoke-DefaultScope {
1060
+ param([string[]]$Rest)
1061
+ $ctx = ''; $feat = ''
1062
+ for ($i = 0; $i -lt $Rest.Count; $i++) {
1063
+ if ($Rest[$i] -in '--context','-Context') { $ctx = $Rest[++$i] }
1064
+ elseif ($Rest[$i] -in '--feature','-Feature') { $feat = $Rest[++$i] }
1065
+ }
1066
+ if (-not $ctx -or -not $feat) { Write-Error 'default-scope: --context and --feature are required'; exit 1 }
1067
+
1068
+ $repo = Get-RepoRoot
1069
+ $specs = Join-Path $repo 'specs'
1070
+ $reason = ''
1071
+ if ((Test-Path $specs) -and @(Get-ChildItem -Directory -Force $specs -ErrorAction SilentlyContinue).Count -gt 0) {
1072
+ $reason = 'specs/ already holds a bounded context or a feature'
1073
+ } elseif ("$(Read-FeatureDir -RepoRoot $repo)") {
1074
+ $reason = 'a feature is already active in .appweaver/feature.json'
1075
+ }
1076
+
1077
+ $created = $false
1078
+ if (-not $reason) {
1079
+ $null = Invoke-SetContext -Rest @('--context', $ctx)
1080
+ $null = Invoke-SetFeature -Rest @('--feature', $feat)
1081
+ $created = $true
1082
+ }
1083
+ Write-Json ([ordered]@{
1084
+ created = $created
1085
+ skipped_reason = $reason
1086
+ context_dir = "$(Get-ContextDirectory -Repo $repo)"
1087
+ feature_dir = "$(Read-FeatureDir -RepoRoot $repo)"
1088
+ })
1089
+ }
1090
+
1036
1091
  # ═══════════════════════════ active_version.json ════════════════════════════
1037
1092
  # Per-feature version state. Shells out to the SAME python as the bash twin so the
1038
1093
  # bump/timestamp logic is byte-identical across shells. (See bash _active_version.)
@@ -1483,7 +1538,7 @@ function Invoke-DetectProject {
1483
1538
  $repo = Get-RepoRoot
1484
1539
  $scope = 'repo'
1485
1540
  $start = $repo
1486
- # A lane's source path arrives as the value declare stored, so it may carry a
1541
+ # A lane root folder arrives as the value declare stored, so it may carry a
1487
1542
  # trailing slash; an option label ("path — why") never reaches a script argument.
1488
1543
  $rel = ($scopeRoot -replace '\\','/').TrimStart('.','/').TrimEnd('/')
1489
1544
  if ($rel -and (Test-Path -PathType Container (Join-Path $repo $rel))) {
@@ -1533,52 +1588,118 @@ function Get-FoundationDirs {
1533
1588
  }
1534
1589
 
1535
1590
  # ──────────────────────────── foundation-list ──────────────────────────────
1536
- # List available foundations (foundations/<stack>/<Name>/ with a real <Name>.pack.txt)
1537
- # for the user to PICK — deterministic, no LLM. Optional --args fast path mirrors bash,
1538
- # including a third token coming back as `rejected_out` rather than as a destination:
1539
- # see the bash twin for why a scaffold has no caller-chosen output directory.
1591
+ # What ONE lane can be scaffolded from, as a pick list: its packs and specs, then
1592
+ # "[manual spec]" and "[skip]". Twin of bash cmd_foundation_list — see it for the three
1593
+ # filters. Both delegate to aw-foundation.py, so the scan has one implementation.
1594
+ function Invoke-AwFoundationCore {
1595
+ param([string]$Op, [string[]]$Rest)
1596
+ $py = Get-Py
1597
+ if (-not $py) { Write-Error 'aw-foundation: python3 is required to list foundations'; exit 1 }
1598
+ $core = Join-Path $PSScriptRoot '..' 'python' 'aw-foundation.py'
1599
+ if (-not (Test-Path $core)) { Write-Error "aw-foundation: core not found at $core"; exit 1 }
1600
+ $repo = Get-RepoRoot
1601
+ & $py $core '--repo' $repo $Op @Rest
1602
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
1603
+ }
1604
+
1540
1605
  function Invoke-FoundationList {
1541
1606
  param([string[]]$Rest)
1542
- $raw = ''; $ecoFilter = ''
1543
- for ($i = 0; $i -lt $Rest.Count; $i++) { switch ($Rest[$i]) { '--args' { $raw = $Rest[++$i] } '--ecosystem' { $ecoFilter = $Rest[++$i] } } }
1544
- $repo = Get-RepoRoot
1607
+ $fwd = @(); $ecoGiven = $false
1608
+ for ($i = 0; $i -lt $Rest.Count; $i++) {
1609
+ switch ($Rest[$i]) {
1610
+ { $_ -in '--args','--kind','--profile','--lane-id' } { $fwd += $Rest[$i]; $fwd += $Rest[++$i] }
1611
+ '--ecosystem' { $fwd += '--ecosystem'; $fwd += $Rest[++$i]; $ecoGiven = $true }
1612
+ default { Write-Error "foundation-list: unknown arg '$($Rest[$i])'"; exit 1 }
1613
+ }
1614
+ }
1615
+ # Without --ecosystem: every declared lane's ecosystem, and no filter on a bare repo.
1616
+ if (-not $ecoGiven) { foreach ($e in @(Get-Ecosystems)) { $fwd += '--ecosystem'; $fwd += $e } }
1617
+ Invoke-AwFoundationCore -Op 'list' -Rest $fwd
1618
+ }
1545
1619
 
1546
- # WHICH foundations this repo may be offered. A foundation bundle is a whole
1547
- # compiling project in one ecosystem's languages and build system, so unpacking a
1548
- # .NET one into a Spring repo does not produce a scaffold — it produces a second,
1549
- # foreign project beside the real one. --ecosystem narrows to the ONE lane being
1550
- # scaffolded; without it the filter is every declared lane's ecosystem, and nothing
1551
- # at all on a bare repo.
1552
- $laneEcos = if ($ecoFilter) { @($ecoFilter) } else { @(Get-Ecosystems) }
1553
- $foundations = @(); $all = @(); $ecoOf = @()
1554
- foreach ($f in @(Get-FoundationDirs $repo)) {
1555
- if (-not (Test-Path (Join-Path $f.Dir "$($f.Name).pack.txt"))) { continue }
1556
- $all += $f.Name; $ecoOf += $f.Stack
1557
- if ($laneEcos.Count -eq 0 -or $laneEcos -contains $f.Stack) { $foundations += $f.Name }
1558
- }
1559
- $preName = ''; $preApp = ''; $rejectedOut = ''
1560
- if ($raw) { $t = $raw -split '\s+' | Where-Object { $_ }; if ($t.Count -ge 1) { $preName = $t[0] }; if ($t.Count -ge 2) { $preApp = $t[1] }; if ($t.Count -ge 3) { $rejectedOut = $t[2] } }
1561
- $match = if ($foundations -contains $preName) { $preName } else { '' }
1562
- Write-Json ([ordered]@{
1563
- foundations = @($foundations); count = $foundations.Count
1564
- all_foundations = @($all); all_count = $all.Count
1565
- ecosystems = @($ecoOf); lane_ecosystems = @($laneEcos)
1566
- filtered = ($laneEcos.Count -gt 0)
1567
- preselect_name = $match; preselect_app = $preApp; rejected_out = $rejectedOut
1568
- })
1620
+ # ─────────────────────────── foundation-choice ─────────────────────────────
1621
+ # Resolve the picked option back to its entry (route, name, file). Twin of bash.
1622
+ function Invoke-FoundationChoice {
1623
+ param([string[]]$Rest)
1624
+ $fwd = @()
1625
+ for ($i = 0; $i -lt $Rest.Count; $i++) {
1626
+ switch ($Rest[$i]) {
1627
+ { $_ -in '--selection','--kind','--profile','--ecosystem' } { $fwd += $Rest[$i]; $fwd += $Rest[++$i] }
1628
+ default { Write-Error "foundation-choice: unknown arg '$($Rest[$i])'"; exit 1 }
1629
+ }
1630
+ }
1631
+ Invoke-AwFoundationCore -Op 'choice' -Rest $fwd
1632
+ }
1633
+
1634
+ # ────────────────────────── foundation-spec-capture ────────────────────────
1635
+ # Copy the spec a lane is about to be built from to .appweaver/foundation/<lane>.spec.md.
1636
+ # Twin of bash cmd_foundation_spec_capture.
1637
+ function Invoke-FoundationSpecCapture {
1638
+ param([string[]]$Rest)
1639
+ $fwd = @()
1640
+ for ($i = 0; $i -lt $Rest.Count; $i++) {
1641
+ switch ($Rest[$i]) {
1642
+ { $_ -in '--lane','--kind','--file','--input' } { $fwd += $Rest[$i]; $fwd += $Rest[++$i] }
1643
+ default { Write-Error "foundation-spec-capture: unknown arg '$($Rest[$i])'"; exit 1 }
1644
+ }
1645
+ }
1646
+ Invoke-AwFoundationCore -Op 'spec-capture' -Rest $fwd
1647
+ }
1648
+
1649
+ # ─────────────────────────── foundation-lane-files ─────────────────────────
1650
+ # What a lane already holds, asked before anything is scaffolded into it. Twin of
1651
+ # bash cmd_foundation_lane_files.
1652
+ function Invoke-FoundationLaneFiles {
1653
+ param([string[]]$Rest)
1654
+ $fwd = @()
1655
+ for ($i = 0; $i -lt $Rest.Count; $i++) {
1656
+ switch ($Rest[$i]) {
1657
+ { $_ -in '--source','--test','--workdir','--profile' } { $fwd += $Rest[$i]; $fwd += $Rest[++$i] }
1658
+ default { Write-Error "foundation-lane-files: unknown arg '$($Rest[$i])'"; exit 1 }
1659
+ }
1660
+ }
1661
+ Invoke-AwFoundationCore -Op 'lane-files' -Rest $fwd
1662
+ }
1663
+
1664
+ # ─────────────────────── foundation-snapshot / -outside ────────────────────
1665
+ # What a spec build did outside its lane root folder: `snapshot` records the repo before
1666
+ # the build, `outside` lists what changed elsewhere afterwards and, with --undo, removes
1667
+ # what was created. Twins of bash cmd_foundation_snapshot / cmd_foundation_outside.
1668
+ function Invoke-FoundationSnapshot {
1669
+ param([string[]]$Rest)
1670
+ $fwd = @()
1671
+ for ($i = 0; $i -lt $Rest.Count; $i++) {
1672
+ switch ($Rest[$i]) {
1673
+ '--out' { $fwd += $Rest[$i]; $fwd += $Rest[++$i] }
1674
+ default { Write-Error "foundation-snapshot: unknown arg '$($Rest[$i])'"; exit 1 }
1675
+ }
1676
+ }
1677
+ Invoke-AwFoundationCore -Op 'snapshot' -Rest $fwd
1678
+ }
1679
+
1680
+ function Invoke-FoundationOutside {
1681
+ param([string[]]$Rest)
1682
+ $fwd = @()
1683
+ for ($i = 0; $i -lt $Rest.Count; $i++) {
1684
+ switch ($Rest[$i]) {
1685
+ { $_ -in '--snapshot','--lane' } { $fwd += $Rest[$i]; $fwd += $Rest[++$i] }
1686
+ '--undo' { $fwd += $Rest[$i] }
1687
+ default { Write-Error "foundation-outside: unknown arg '$($Rest[$i])'"; exit 1 }
1688
+ }
1689
+ }
1690
+ Invoke-AwFoundationCore -Op 'outside' -Rest $fwd
1569
1691
  }
1570
1692
 
1571
1693
  function Invoke-FoundationLocate {
1572
1694
  param([string[]]$Rest)
1573
- $name = ''; $app = ''; $out = '.'; $raw = ''
1574
- for ($i = 0; $i -lt $Rest.Count; $i++) { switch ($Rest[$i]) { '--name' { $name = $Rest[++$i] } '--app' { $app = $Rest[++$i] } '--out' { $out = $Rest[++$i] } '--args' { $raw = $Rest[++$i] } } }
1575
- if ($raw) { $t = $raw -split '\s+' | Where-Object { $_ }; if ($t.Count -ge 1) { $name = $t[0] }; if ($t.Count -ge 2) { $app = $t[1] }; if ($t.Count -ge 3) { $out = $t[2] } }
1695
+ $name = ''; $app = ''; $out = '.'
1696
+ for ($i = 0; $i -lt $Rest.Count; $i++) { switch ($Rest[$i]) { '--name' { $name = $Rest[++$i] } '--app' { $app = $Rest[++$i] } '--out' { $out = $Rest[++$i] } } }
1576
1697
  $repo = Get-RepoRoot
1577
1698
  $dirs = @(Get-FoundationDirs $repo)
1578
1699
  # available = every foundation dir (Get-FoundationDirs already requires a *.pack.txt)
1579
1700
  $available = @($dirs | ForEach-Object { $_.Name })
1580
1701
  function _fail($r) { Write-Json ([ordered]@{ ok = $false; reason = $r; available = @($available) }) }
1581
- if (-not $name -or -not $app) { _fail 'Usage: /aw-spec-foundation <FoundationName> <AppName> [OutputDirectory]'; return }
1702
+ if (-not $name -or -not $app) { _fail 'foundation-locate needs --name <FoundationName> and --app <AppName>'; return }
1582
1703
  $found = $dirs | Where-Object { $_.Name -eq $name } | Select-Object -First 1
1583
1704
  if (-not $found) { _fail "Foundation '$name' not found (expected foundations/<stack>/$name/)"; return }
1584
1705
  $stack = $found.Stack; $rel = $found.Rel; $fdir = $found.Dir
@@ -1593,15 +1714,17 @@ function Invoke-FoundationLocate {
1593
1714
  $sm = [regex]::Match($header, '(?i)sentinel[^A-Za-z0-9_.]*([A-Za-z0-9_.]+)')
1594
1715
  if ($sm.Success) { $sentinel = $sm.Groups[1].Value }
1595
1716
  $fc = 0; $fm = [regex]::Match($header, '(?i)file[^0-9]*([0-9]+)'); if ($fm.Success) { $fc = [int]$fm.Groups[1].Value }
1717
+ # A reference sample is offered only when its pack is there to unpack.
1718
+ $hasReference = Test-Path (Join-Path $fdir "$name.Reference.pack.txt")
1596
1719
  Write-Json ([ordered]@{
1597
1720
  ok = $true; reason = ''; name = $name; stack = $stack; app = $app; out_dir = $out
1598
1721
  pack_file = "$rel/$name.pack.txt"; unpacker = $unpacker; runner = $runner
1599
- sentinel = $sentinel; file_count = $fc; available = @($available)
1722
+ sentinel = $sentinel; file_count = $fc; has_reference = $hasReference; available = @($available)
1600
1723
  })
1601
1724
  }
1602
1725
 
1603
1726
  # ─────────────────────────── foundation-unpack ─────────────────────────────
1604
- # --out is the LANE'S SOURCE PATH and --test-path its other subtree; the pack's own test
1727
+ # --out is the LANE ROOT FOLDER and --test-path its other subtree; the pack's own test
1605
1728
  # tree is lifted onto the latter by aw-foundation.py. Twin of the bash cmd_foundation_unpack
1606
1729
  # — see it for why a scaffold goes into a lane rather than into the repo root.
1607
1730
  function Invoke-FoundationUnpack {
@@ -1613,11 +1736,11 @@ function Invoke-FoundationUnpack {
1613
1736
  if (-not $name -or -not $app) { Write-Error 'foundation-unpack: --name and --app required'; exit 1 }
1614
1737
  $repo = Get-RepoRoot
1615
1738
  $found = @(Get-FoundationDirs $repo) | Where-Object { $_.Name -eq $name } | Select-Object -First 1
1616
- $fdir = if ($found) { $found.Dir } else { Join-Path $repo "foundations/$name" }
1617
1739
  $packbase = if ($reference) { "$name.Reference" } else { $name }
1618
1740
  $stem = if ($reference) { 'unpack-reference-foundation' } else { 'unpack-foundation' }
1619
- $pack = Join-Path $fdir "$packbase.pack.txt"
1620
- if (-not (Test-Path $pack)) { Write-Json ([ordered]@{ ok = $false; mode = $(if ($dry) { 'dry' } else { 'real' }); written = 0; has_collisions = $false; stdout = "pack not found: $packbase.pack.txt" }); return }
1741
+ $pack = if ($found) { Join-Path $found.Dir "$packbase.pack.txt" } else { '' }
1742
+ $fdir = if ($found) { $found.Dir } else { '' }
1743
+ if (-not $pack -or -not (Test-Path $pack)) { Write-Json ([ordered]@{ ok = $false; mode = $(if ($dry) { 'dry' } else { 'real' }); written = 0; has_collisions = $false; stdout = "pack not found: $packbase.pack.txt" }); return }
1621
1744
  $cmd = $null; $cargs = @()
1622
1745
  if ((Get-Command pwsh -ErrorAction SilentlyContinue) -and (Test-Path (Join-Path $fdir "$stem.ps1"))) {
1623
1746
  $cmd = 'pwsh'; $cargs = @('-NoProfile','-File',(Join-Path $fdir "$stem.ps1"),'-PackFile',$pack,'-TargetAppName',$app,'-OutDir',$out)
@@ -1657,20 +1780,32 @@ function Invoke-FoundationUnpack {
1657
1780
  Write-Json ([ordered]@{ ok = ($rc -eq 0); mode = $(if ($dry) { 'dry' } else { 'real' }); written = $written; has_collisions = $hasColl; tests_moved = $testsMoved; tests_message = $testsMsg; stdout = ($outp.Substring([Math]::Max(0,$outp.Length-800))) })
1658
1781
  }
1659
1782
 
1660
- # ─────────────────────── foundation-stash-dbcontext ────────────────────────
1661
- function Invoke-FoundationStashDbContext {
1783
+ # ───────────────────── foundation-rules / -rules-done ──────────────────────
1784
+ # What a pack's rule file (<pack name>.rules.json) asks of an unpack, and the clean-up
1785
+ # after its merges. Twins of bash cmd_foundation_rules / cmd_foundation_rules_done.
1786
+ function Invoke-FoundationRules {
1662
1787
  param([string[]]$Rest)
1663
- $out = '.'
1664
- for ($i = 0; $i -lt $Rest.Count; $i++) { if ($Rest[$i] -eq '--out') { $out = $Rest[++$i] } }
1665
- $repo = Get-RepoRoot
1666
- if (-not [System.IO.Path]::IsPathRooted($out)) { $out = Join-Path $repo $out }
1667
- # Searched from the unpack root itself, which IS the lane's source path — a `src/`
1668
- # segment underneath it is the old repo-root layout and is not there any more.
1669
- $ctx = Get-ChildItem -LiteralPath $out -Recurse -Filter 'ApplicationDbContext.cs' -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName
1670
- if (-not $ctx) { Write-Json ([ordered]@{ found = $false; dbcontext = ''; backup = '' }); return }
1671
- $bak = "$ctx.foundation.bak"
1672
- Copy-Item $ctx $bak
1673
- Write-Json ([ordered]@{ found = $true; dbcontext = $ctx; backup = $bak })
1788
+ $fwd = @()
1789
+ for ($i = 0; $i -lt $Rest.Count; $i++) {
1790
+ switch ($Rest[$i]) {
1791
+ { $_ -in '--name','--app','--out','--state' } { $fwd += $Rest[$i]; $fwd += $Rest[++$i] }
1792
+ '--reference' { $fwd += $Rest[$i] }
1793
+ default { Write-Error "foundation-rules: unknown arg '$($Rest[$i])'"; exit 1 }
1794
+ }
1795
+ }
1796
+ Invoke-AwFoundationCore -Op 'rules' -Rest $fwd
1797
+ }
1798
+
1799
+ function Invoke-FoundationRulesDone {
1800
+ param([string[]]$Rest)
1801
+ $fwd = @()
1802
+ for ($i = 0; $i -lt $Rest.Count; $i++) {
1803
+ switch ($Rest[$i]) {
1804
+ '--state' { $fwd += $Rest[$i]; $fwd += $Rest[++$i] }
1805
+ default { Write-Error "foundation-rules-done: unknown arg '$($Rest[$i])'"; exit 1 }
1806
+ }
1807
+ }
1808
+ Invoke-AwFoundationCore -Op 'rules-done' -Rest $fwd
1674
1809
  }
1675
1810
 
1676
1811
  # ─────────────────────────── foundation-verify ─────────────────────────────
@@ -2144,7 +2279,7 @@ function Invoke-ListComponents {
2144
2279
  function Invoke-FindHostProject {
2145
2280
  param([string[]]$Rest)
2146
2281
  $repo = Get-RepoRoot
2147
- # WHERE the host lives. The service lane's declared source path when there is one,
2282
+ # WHERE the host lives. The declared lane root folder of the service lane when there is one,
2148
2283
  # 'src' when there is not — the same question run-migrate asks, and the same answer.
2149
2284
  $svcRoot = Get-LaneField -Field 'backend_root'
2150
2285
  if (-not $svcRoot) { $svcRoot = 'src' }
@@ -2461,13 +2596,25 @@ function Get-Ecosystems {
2461
2596
  function Invoke-BuildCompile {
2462
2597
  param([string[]]$Rest)
2463
2598
  $repo = Get-RepoRoot
2599
+ # --kind builds ONE declared lane — what `foundation` uses to check the lane it just
2600
+ # created without compiling a lane it did not touch. Ignored on the undeclared ladder.
2601
+ $kind = ''
2602
+ for ($i = 0; $i -lt $Rest.Count; $i++) {
2603
+ switch ($Rest[$i]) {
2604
+ '--feature-dir' { $i++ }
2605
+ '--kind' { $kind = $Rest[++$i] }
2606
+ default { Write-Error "build-compile: unknown arg '$($Rest[$i])'"; exit 1 }
2607
+ }
2608
+ }
2464
2609
 
2465
2610
  # A DECLARED lane model is the answer, and it is a better answer than the ladder
2466
2611
  # below in exactly the case that matters: a repo with two lanes. The sniffer takes
2467
2612
  # the first toolchain it finds and silently never builds the second, which reports
2468
2613
  # green for a frontend that was never compiled.
2469
2614
  if ((Get-LaneField -Field 'lane_count') -and (Get-LaneField -Field 'lane_count') -ne '0') {
2470
- Invoke-AwStackCore -Op 'run-phase' -Rest @('--phase', 'build')
2615
+ $phaseArgs = @('--phase', 'build')
2616
+ if ($kind) { $phaseArgs += @('--kind', $kind) }
2617
+ Invoke-AwStackCore -Op 'run-phase' -Rest $phaseArgs
2471
2618
  return
2472
2619
  }
2473
2620
 
@@ -2673,7 +2820,7 @@ function Invoke-RunMigrate {
2673
2820
  return
2674
2821
  }
2675
2822
 
2676
- # WHERE to look for the migration project. The service lane's declared source path
2823
+ # WHERE to look for the migration project. The declared lane root folder of the service lane
2677
2824
  # when there is one, 'src' when there is not. Migrations belong to the backend lane
2678
2825
  # by definition, so a repo whose service code sits in 'Legacy/App/' or 'backend/'
2679
2826
  # gets searched where its code actually is instead of being told it has no .NET
@@ -2691,7 +2838,7 @@ function Invoke-RunMigrate {
2691
2838
  }
2692
2839
  if ($efOk) {
2693
2840
  # Prefer *Service.csproj (API host with ApplicationDbContext); fall back to first
2694
- # non-AppHost/Web/ServiceDefaults/test project under the service lane's root.
2841
+ # non-AppHost/Web/ServiceDefaults/test project under the service lane root folder.
2695
2842
  $project = Get-ChildItem -LiteralPath $svcDir -Filter '*.Service.csproj' -Recurse -ErrorAction SilentlyContinue |
2696
2843
  Select-Object -First 1 -ExpandProperty FullName
2697
2844
  if (-not $project) {
@@ -4455,6 +4602,7 @@ switch ($sub) {
4455
4602
  'set-context' { Invoke-SetContext -Rest $rest }
4456
4603
  'list-features' { Invoke-ListFeatures -Rest $rest }
4457
4604
  'set-feature' { Invoke-SetFeature -Rest $rest }
4605
+ 'default-scope' { Invoke-DefaultScope -Rest $rest }
4458
4606
  'version-read' { Invoke-VersionState 'read' $rest }
4459
4607
  'version-init' { Invoke-VersionState 'init' $rest }
4460
4608
  'version-bootstrap-done' { Invoke-VersionState 'bootstrap-done' $rest }
@@ -4494,10 +4642,16 @@ switch ($sub) {
4494
4642
  'checklist-apply' { Invoke-ChecklistApply -Rest $rest }
4495
4643
  'detect-project' { Invoke-DetectProject -Rest $rest }
4496
4644
  'foundation-list' { Invoke-FoundationList -Rest $rest }
4645
+ 'foundation-choice' { Invoke-FoundationChoice -Rest $rest }
4646
+ 'foundation-spec-capture' { Invoke-FoundationSpecCapture -Rest $rest }
4647
+ 'foundation-lane-files' { Invoke-FoundationLaneFiles -Rest $rest }
4648
+ 'foundation-snapshot' { Invoke-FoundationSnapshot -Rest $rest }
4649
+ 'foundation-outside' { Invoke-FoundationOutside -Rest $rest }
4497
4650
  'foundation-locate' { Invoke-FoundationLocate -Rest $rest }
4498
4651
  'foundation-unpack' { Invoke-FoundationUnpack -Rest $rest }
4499
4652
  'foundation-verify' { Invoke-FoundationVerify -Rest $rest }
4500
- 'foundation-stash-dbcontext' { Invoke-FoundationStashDbContext -Rest $rest }
4653
+ 'foundation-rules' { Invoke-FoundationRules -Rest $rest }
4654
+ 'foundation-rules-done' { Invoke-FoundationRulesDone -Rest $rest }
4501
4655
  'list-components' { Invoke-ListComponents -Rest $rest }
4502
4656
  'component-catalog' { Invoke-ComponentCatalog -Rest $rest }
4503
4657
  'component-verify' { Invoke-ComponentVerify -Rest $rest }