aw-spec-cli 0.1.21 → 0.1.23
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/STACK-PROFILES.md +87 -8
- package/assets/.appweaver/orchestrator/modules/delta-plan.orch.yaml +30 -0
- package/assets/.appweaver/orchestrator/orchestrator.py +47 -3
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +41 -8
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +62 -2
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +18 -5
- package/assets/.appweaver/orchestrator/workflows/aw-spec-constitution.orch.yaml +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-dependency-tree.orch.yaml +4 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +115 -27
- package/assets/.appweaver/orchestrator/workflows/aw-spec-foundation.orch.yaml +5 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +225 -27
- package/assets/.appweaver/orchestrator/workflows/aw-spec-inventory.orch.yaml +4 -15
- package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +39 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-recall-delta.orch.yaml +28 -7
- package/assets/.appweaver/orchestrator/workflows/aw-spec-select-domain.orch.yaml +3 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +104 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-ui-model.orch.yaml +159 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-ui-prototype.orch.yaml +386 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-workflow-model.orch.yaml +153 -0
- package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +203 -212
- package/assets/.appweaver/scripts/bash/aw-xref.sh +4 -1
- package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +191 -224
- package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +4 -1
- package/assets/.appweaver/scripts/python/aw-crstate.py +127 -2
- package/assets/.appweaver/scripts/python/aw-gaps.py +119 -0
- package/assets/.appweaver/scripts/python/aw-payload-parity.py +17 -1
- package/assets/.appweaver/scripts/python/aw-plan-coverage.py +84 -4
- package/assets/.appweaver/scripts/python/aw-plangate.py +216 -0
- package/assets/.appweaver/scripts/python/aw-principles.py +108 -0
- package/assets/.appweaver/scripts/python/aw-stack.py +6 -0
- package/assets/.appweaver/scripts/python/aw-xref.py +141 -4
- package/assets/.appweaver/stack-profiles/angular.json +26 -0
- package/assets/.appweaver/stack-profiles/blazor-wasm.json +38 -0
- package/assets/.appweaver/stack-profiles/dotnet-webapi.json +38 -0
- package/assets/.appweaver/stack-profiles/java-spring.json +19 -0
- package/assets/.appweaver/stack-profiles/nextjs.json +26 -0
- package/assets/.appweaver/stack-profiles/node-express.json +26 -0
- package/assets/.appweaver/stack-profiles/python-fastapi.json +15 -0
- package/assets/.appweaver/stack-profiles/react-vite.json +26 -0
- package/assets/.appweaver/stack-profiles/sveltekit.json +26 -0
- package/assets/.appweaver/stack-profiles/vue-vite.json +26 -0
- package/assets/.appweaver/templates/ui-model-template.md +67 -0
- package/assets/.appweaver/templates/workflow-model-template.md +62 -0
- package/assets/foundations/dotnet/ModularMonolith/ModularMonolith.pack.txt +3 -3
- package/assets/foundations/dotnet/VerticalSlice/VerticalSlice.pack.txt +5 -123
- package/assets/foundations/java/Hexagonal/Hexagonal.pack.txt +784 -0
- package/assets/foundations/java/Hexagonal/pack-foundation.sh +357 -0
- package/assets/foundations/java/Hexagonal/unpack-foundation.sh +399 -0
- package/assets/guardrails/technology/dotnet-webapi/framework-api-map.md +44 -0
- package/package.json +1 -1
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
# aw-xref.sh list --aggregate Y [--root DIR] [--bundle knowledge]
|
|
10
10
|
# aw-xref.sh concept-upsert --aggregate X [--source-files a,b,c] [--description D] [--origin ...] [--root DIR]
|
|
11
11
|
# aw-xref.sh concept-overview --aggregate X (--text PROSE | --text-file F) [--root DIR]
|
|
12
|
-
# aw-xref.sh concept-paths --aggregate X | --feature-dir <dir> [--
|
|
12
|
+
# aw-xref.sh concept-paths --aggregate X | --feature-dir <dir> [--under <subtree>]...
|
|
13
|
+
# [--root DIR] [--bundle knowledge]
|
|
14
|
+
# (--under keeps only paths inside that subtree, so a caller
|
|
15
|
+
# generating ONE lane is not handed another lane's files)
|
|
13
16
|
# aw-xref.sh feature-aggregates --feature-dir <dir> [--root DIR]
|
|
14
17
|
# (the aggregate(s) a feature acts on: its spec's Acts-on line,
|
|
15
18
|
# then the boundary map, then the feature name itself)
|
|
@@ -1230,6 +1230,14 @@ function Invoke-FeaturePaths {
|
|
|
1230
1230
|
if (-not $fd) { Write-Error 'feature-paths: no feature set (run /appweaver select-feature first)'; exit 1 }
|
|
1231
1231
|
if (-not [System.IO.Path]::IsPathRooted($fd)) { $fd = Join-Path $repo $fd }
|
|
1232
1232
|
$feature = Split-Path $fd -Leaf # the aggregate name (feature = specs/<Feature>)
|
|
1233
|
+
# feature_slug is the URL-safe form of that name, and it is what the UI stages use
|
|
1234
|
+
# as a route segment and a directory name (app/<slug>/page.tsx). It is derived here
|
|
1235
|
+
# rather than per workflow so every reader gets the same segment for one feature.
|
|
1236
|
+
# CamelCase splits on the case boundary before the non-alphanumeric squeeze, so
|
|
1237
|
+
# OrderManagement and "Order Management" both reach order-management.
|
|
1238
|
+
$featureSlug = $feature -creplace '([a-z0-9])([A-Z])', '$1-$2'
|
|
1239
|
+
$featureSlug = $featureSlug -creplace '([A-Z]+)([A-Z][a-z])', '$1-$2'
|
|
1240
|
+
$featureSlug = ($featureSlug.ToLowerInvariant() -replace '[^a-z0-9]+', '-').Trim('-')
|
|
1233
1241
|
# The master documents of a feature: spec.md (WHAT/WHY) + one LANE PLAN per declared
|
|
1234
1242
|
# lane (each lane's HOW) — backend-plan.md for the service lane, frontend-plan.md for
|
|
1235
1243
|
# the frontend lane. `plan` writes them; nothing else does.
|
|
@@ -1264,6 +1272,7 @@ function Invoke-FeaturePaths {
|
|
|
1264
1272
|
$specExists = (Test-Path $spec)
|
|
1265
1273
|
Write-Json ([ordered]@{
|
|
1266
1274
|
feature = $feature
|
|
1275
|
+
feature_slug = $featureSlug
|
|
1267
1276
|
feature_dir = $fd
|
|
1268
1277
|
spec_file = $spec
|
|
1269
1278
|
backend_plan = $design
|
|
@@ -1946,6 +1955,21 @@ function Get-LabeledVersion {
|
|
|
1946
1955
|
}
|
|
1947
1956
|
if ($vals.Count -gt 0) { return $vals[-1] } else { return $null }
|
|
1948
1957
|
}
|
|
1958
|
+
|
|
1959
|
+
# The ratified NON-NEGOTIABLE principles. A thin wrapper: the scan lives in
|
|
1960
|
+
# python/aw-principles.py, which BOTH shells invoke, so the list a gate cites cannot
|
|
1961
|
+
# differ between them. Defaults to the two halves specify loads; --file overrides.
|
|
1962
|
+
function Invoke-PrinciplesList {
|
|
1963
|
+
param([string[]]$Rest)
|
|
1964
|
+
$repo = Get-RepoRoot
|
|
1965
|
+
$py = Get-Py
|
|
1966
|
+
if (-not $py) { Write-Error 'principles-list: python3 is required'; exit 1 }
|
|
1967
|
+
$core = Join-Path $PSScriptRoot '..' 'python' 'aw-principles.py'
|
|
1968
|
+
if (-not (Test-Path $core)) { Write-Error "principles-list: core not found at $core"; exit 1 }
|
|
1969
|
+
& $py $core list --root $repo @Rest
|
|
1970
|
+
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1949
1973
|
function Invoke-ConstitutionVerify {
|
|
1950
1974
|
param([string[]]$Rest)
|
|
1951
1975
|
$file = ''; $template = ''
|
|
@@ -2332,7 +2356,12 @@ function Invoke-DeltaPlanScaffold {
|
|
|
2332
2356
|
if ($tpl -and (Test-Path $tpl)) { Copy-Item $tpl $target } else { New-Item -ItemType File -Force -Path $target | Out-Null }
|
|
2333
2357
|
$created = $true
|
|
2334
2358
|
}
|
|
2335
|
-
|
|
2359
|
+
# The lane plan carries the change's wave so a reader of the plan alone can see where it
|
|
2360
|
+
# sits. It is COPIED from the delta-spec, never decided here — so when the spec has no
|
|
2361
|
+
# readable wave the template's `[N]` is left standing, which is what the merge and recall
|
|
2362
|
+
# fences read as "not ordered yet" and refuse on, with the command that stamps it.
|
|
2363
|
+
$scaffoldWave = Get-WaveOfReadable -FeatureDir $fd -CrId $crId -Repo $repo
|
|
2364
|
+
if ($scaffoldWave) { Set-HeaderStamp -Path $target -Name 'Wave' -Value $scaffoldWave }
|
|
2336
2365
|
$overrideActive = Test-Path (Join-Path $repo (".appweaver/templates/overrides/" + $tplName + ".md"))
|
|
2337
2366
|
$templateSource = Resolve-NamedTemplate -Name $tplName -RepoRoot $repo
|
|
2338
2367
|
$tsOut = if ($templateSource) { $templateSource } else { '' }
|
|
@@ -2418,7 +2447,8 @@ function Invoke-TestRun {
|
|
|
2418
2447
|
switch ($Rest[$i]) {
|
|
2419
2448
|
{ $_ -in '--feature-dir','-FeatureDir' } { $featureDir = $Rest[++$i] }
|
|
2420
2449
|
{ $_ -in '--scope','-Scope' } { $scope = $Rest[++$i] }
|
|
2421
|
-
|
|
2450
|
+
# Repeatable, lists UNION — see the bash twin for why the caller passes two.
|
|
2451
|
+
{ $_ -in '--also','-Also' } { $v = $Rest[++$i]; $also = if ($also) { "$also,$v" } else { $v } }
|
|
2422
2452
|
}
|
|
2423
2453
|
}
|
|
2424
2454
|
$repo = Get-RepoRoot
|
|
@@ -2714,16 +2744,33 @@ function Get-CrIdToken {
|
|
|
2714
2744
|
}
|
|
2715
2745
|
|
|
2716
2746
|
# One change's wave, read from the documents that carry it: the delta-spec first, then the
|
|
2717
|
-
# model delta. Both are stamped together, so either answers
|
|
2718
|
-
#
|
|
2719
|
-
|
|
2747
|
+
# model delta. Both are stamped together, so either answers. EMPTY when neither carries a
|
|
2748
|
+
# readable one — `**Wave**: [N]` is the unfilled template line, so "no wave" is a state that
|
|
2749
|
+
# happens, and a caller who WRITES the answer somewhere must not write a number nobody
|
|
2750
|
+
# derived. A phantom wave is worse than none: the merge and recall fences can see that a
|
|
2751
|
+
# wave is missing and say so, but a 1 invented downstream reads to them as fact.
|
|
2752
|
+
# Twin of bash _wave_of_readable.
|
|
2753
|
+
function Get-WaveOfReadable {
|
|
2720
2754
|
param([string]$FeatureDir, [string]$CrId, [string]$Repo)
|
|
2721
2755
|
$ctx = Get-ContextDirForFeature -FeatureDir $FeatureDir -Repo $Repo
|
|
2722
2756
|
foreach ($f in @((Join-Path $FeatureDir "changes/$CrId/delta-spec.md"), (Join-Path $ctx "domain-model.$CrId.delta.md"))) {
|
|
2723
2757
|
if (-not (Test-Path $f)) { continue }
|
|
2724
2758
|
$t = Get-Content $f -Raw -ErrorAction SilentlyContinue
|
|
2725
|
-
if ($t -match '(?m)^\*\*Wave\*\*:\s*(\d+)') {
|
|
2759
|
+
if ($t -match '(?m)^\*\*Wave\*\*:\s*(\d+)') {
|
|
2760
|
+
$w = [int]$Matches[1]
|
|
2761
|
+
if ($w -ge 1) { return "$w" }
|
|
2762
|
+
}
|
|
2726
2763
|
}
|
|
2764
|
+
return ''
|
|
2765
|
+
}
|
|
2766
|
+
|
|
2767
|
+
# The same reading with the floor applied, for the readers that must show a number — a
|
|
2768
|
+
# listing has a column to fill and 1 is the floor for a change opened before waves existed.
|
|
2769
|
+
# Twin of bash _wave_of.
|
|
2770
|
+
function Get-WaveOf {
|
|
2771
|
+
param([string]$FeatureDir, [string]$CrId, [string]$Repo)
|
|
2772
|
+
$w = Get-WaveOfReadable -FeatureDir $FeatureDir -CrId $CrId -Repo $Repo
|
|
2773
|
+
if ($w) { return $w }
|
|
2727
2774
|
return '1'
|
|
2728
2775
|
}
|
|
2729
2776
|
|
|
@@ -3253,8 +3300,12 @@ required = max([d["wave"] for d in known], default=0) + 1
|
|
|
3253
3300
|
current = wave_of(cr)
|
|
3254
3301
|
ok = current is not None and current >= required
|
|
3255
3302
|
|
|
3303
|
+
# A change with NO readable wave is stamped too, not only one sitting too low. The merge
|
|
3304
|
+
# and recall fences refuse on an unordered change and send the operator here, so the one
|
|
3305
|
+
# command they are given has to be able to write the first wave as well as correct a wrong
|
|
3306
|
+
# one — a remedy that declines the case it was named for is no remedy.
|
|
3256
3307
|
stamped = []
|
|
3257
|
-
if stamp and current is
|
|
3308
|
+
if stamp and (current is None or current < required):
|
|
3258
3309
|
line = "**Wave**: %d" % required
|
|
3259
3310
|
pat = re.compile(r"^\*\*Wave\*\*:.*$", re.M)
|
|
3260
3311
|
for p in present:
|
|
@@ -3265,13 +3316,16 @@ if stamp and current is not None and current < required:
|
|
|
3265
3316
|
if stamped:
|
|
3266
3317
|
current, ok = required, True
|
|
3267
3318
|
|
|
3268
|
-
if
|
|
3269
|
-
msg = "%s declares no predecessor, so wave %d is the floor; it carries %s" % (
|
|
3270
|
-
cr, required, current if current is not None else "no wave")
|
|
3271
|
-
elif stamped:
|
|
3319
|
+
if stamped and deps:
|
|
3272
3320
|
msg = "%s depends on %s, so it was raised to wave %d in %s" % (
|
|
3273
3321
|
cr, ", ".join("%s (wave %s)" % (d["cr_id"], d["wave"]) for d in known),
|
|
3274
3322
|
required, ", ".join(stamped))
|
|
3323
|
+
elif stamped:
|
|
3324
|
+
msg = "%s declares no predecessor, so it was stamped at wave %d — the floor — in %s" % (
|
|
3325
|
+
cr, required, ", ".join(stamped))
|
|
3326
|
+
elif not deps:
|
|
3327
|
+
msg = "%s declares no predecessor, so wave %d is the floor; it carries %s" % (
|
|
3328
|
+
cr, required, current if current is not None else "no wave")
|
|
3275
3329
|
elif ok:
|
|
3276
3330
|
msg = "%s is at wave %s, at or above the %d its `## Depends on` requires" % (
|
|
3277
3331
|
cr, current, required)
|
|
@@ -3917,13 +3971,6 @@ function Invoke-CrMergeCheck {
|
|
|
3917
3971
|
if (-not $fd) { Write-Error 'cr-merge-check: no feature set and no --feature-dir'; exit 1 }
|
|
3918
3972
|
$ctx = Get-ContextDirForFeature -FeatureDir $fd -Repo $repo
|
|
3919
3973
|
|
|
3920
|
-
function Get-WaveFile([string]$path) {
|
|
3921
|
-
if (Test-Path -LiteralPath $path) {
|
|
3922
|
-
$m = [regex]::Match((Get-Content -LiteralPath $path -Raw), '(?m)^\*\*Wave\*\*:[ \t]*(\d+)')
|
|
3923
|
-
if ($m.Success) { return [int]$m.Groups[1].Value }
|
|
3924
|
-
}
|
|
3925
|
-
return 1
|
|
3926
|
-
}
|
|
3927
3974
|
function Format-Ver([string]$v) {
|
|
3928
3975
|
$v = "$v".Trim()
|
|
3929
3976
|
if (-not $v) { return '' }
|
|
@@ -3931,7 +3978,6 @@ function Invoke-CrMergeCheck {
|
|
|
3931
3978
|
}
|
|
3932
3979
|
|
|
3933
3980
|
$mySpec = Join-Path $fd "changes/$crId/delta-spec.md"
|
|
3934
|
-
$wave = Get-WaveFile $mySpec
|
|
3935
3981
|
$baseVersion = Format-Ver (Get-MdHeader -Path $mySpec -Name 'Base version')
|
|
3936
3982
|
|
|
3937
3983
|
# The master's CURRENT version, from the ledger — the same authority version-read
|
|
@@ -3947,63 +3993,59 @@ function Invoke-CrMergeCheck {
|
|
|
3947
3993
|
# is absence of evidence and is not treated as a move.
|
|
3948
3994
|
$rebaseNeeded = [bool]($baseVersion -and $current -and ($baseVersion -ne $current))
|
|
3949
3995
|
|
|
3950
|
-
#
|
|
3951
|
-
#
|
|
3952
|
-
#
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
if (-not $m.Success) { continue }
|
|
3970
|
-
$other = $m.Groups[1].Value
|
|
3971
|
-
if (-not $opens.Contains($other)) {
|
|
3972
|
-
$opens[$other] = [ordered]@{ cr_id = $other; wave = (Get-WaveFile $f.FullName); feature = '' }
|
|
3973
|
-
}
|
|
3974
|
-
}
|
|
3975
|
-
}
|
|
3976
|
-
$blockers = @(foreach ($k in ($opens.Keys | Sort-Object)) {
|
|
3977
|
-
if ($k -ne $crId -and $opens[$k].wave -lt $wave) { $opens[$k] }
|
|
3978
|
-
})
|
|
3979
|
-
|
|
3980
|
-
# THE MASTERS CAN MOVE WITHOUT THE VERSION MOVING. finalize-delta merges and close-delta
|
|
3981
|
-
# bumps, so between the two spec.md carries a change the number does not yet reflect —
|
|
3982
|
-
# and a second delta sharing the same base_version would compare equal and merge onto
|
|
3983
|
-
# masters that had already shifted under it. An id appearing in spec.md is the on-disk
|
|
3984
|
-
# proof that the merge ran (the Change History row finalize-delta appends), the same
|
|
3985
|
-
# evidence cr-recall-check reads for the same question.
|
|
3986
|
-
# aw-crstate owns the test, for both shells and all three readers of it: a change is
|
|
3987
|
-
# MERGED when its id is the CR cell of a Change History row — in this feature's spec or
|
|
3988
|
-
# in the context model every feature shares, so a SIBLING feature's merged-unclosed
|
|
3989
|
-
# change is no longer invisible. A sibling blocks only where it touched an aggregate
|
|
3990
|
-
# this delta also names. Fails CLOSED: an unreadable helper reports blocking.
|
|
3991
|
-
$mergedOpen = @()
|
|
3992
|
-
$pyMerged = Get-Py
|
|
3996
|
+
# BOTH CONTEXT-WIDE QUESTIONS COME FROM aw-crstate, the same module the bash payload
|
|
3997
|
+
# loads, so the two shells cannot answer them differently — which they did while each
|
|
3998
|
+
# carried its own copy of the scan.
|
|
3999
|
+
#
|
|
4000
|
+
# PREDECESSORS. Every open change in the context, both halves: changes/<id>/ under each
|
|
4001
|
+
# feature is the authority for "open", and the pending model deltas are unioned in so a
|
|
4002
|
+
# model-only artifact of a half-built change is still seen. A change whose `**Wave**:`
|
|
4003
|
+
# is unreadable is reported as unordered rather than assumed to be wave 1.
|
|
4004
|
+
#
|
|
4005
|
+
# MERGED-OPEN. The masters can move without the version moving: finalize-delta merges
|
|
4006
|
+
# and close-delta bumps, so between the two spec.md carries a change the number does not
|
|
4007
|
+
# yet reflect, and a second delta sharing the same base_version would compare equal and
|
|
4008
|
+
# merge onto masters that had already shifted under it. A change is MERGED when its id
|
|
4009
|
+
# is the CR cell of a Change History row — in this feature's spec or in the context
|
|
4010
|
+
# model every feature shares, so a SIBLING feature's merged-unclosed change is no longer
|
|
4011
|
+
# invisible. A sibling blocks only where it touched an aggregate this delta also names.
|
|
4012
|
+
#
|
|
4013
|
+
# Fails CLOSED: an unreadable helper reports blocking and nothing ordered.
|
|
4014
|
+
$py = Get-Py
|
|
3993
4015
|
$crstate = Join-Path $PSScriptRoot '..' 'python' 'aw-crstate.py'
|
|
3994
|
-
|
|
4016
|
+
# `answered` is what says the reader ran, not the shape of what it returned: NO blocking
|
|
4017
|
+
# merged-open change is an empty array, which is exactly what a failed read looks like.
|
|
4018
|
+
$order = $null; $mergedOpen = @(); $mergedText = ''; $answered = $false
|
|
4019
|
+
$why = 'python or aw-crstate.py not found'
|
|
4020
|
+
if ($py -and (Test-Path $crstate)) {
|
|
3995
4021
|
try {
|
|
3996
|
-
$line = (& $
|
|
3997
|
-
if ($line) { $
|
|
4022
|
+
$line = (& $py $crstate 'wave-order' '--cr' $crId '--feature-dir' $fd '--context-dir' $ctx '--direction' 'lower' '--scope' 'context' | Select-Object -First 1)
|
|
4023
|
+
if ($line) { $order = ($line | ConvertFrom-Json) }
|
|
4024
|
+
$line = (& $py $crstate 'merged-open' '--cr' $crId '--feature-dir' $fd '--context-dir' $ctx | Select-Object -First 1)
|
|
4025
|
+
if ($line) {
|
|
4026
|
+
$parsed = ($line | ConvertFrom-Json)
|
|
4027
|
+
$mergedOpen = @($parsed.blocking); $mergedText = "$($parsed.blocking_text)"; $answered = $true
|
|
4028
|
+
}
|
|
3998
4029
|
} catch {
|
|
3999
|
-
$
|
|
4030
|
+
$order = $null; $answered = $false; $why = "$_"
|
|
4000
4031
|
}
|
|
4001
|
-
} else {
|
|
4002
|
-
$mergedOpen = @([pscustomobject]@{ cr_id = '(merged-open check unavailable)'; blocking = $true })
|
|
4003
4032
|
}
|
|
4033
|
+
if (-not $order) {
|
|
4034
|
+
$order = [pscustomobject]@{ wave = $null; wave_known = $false; related = @(); related_count = 0
|
|
4035
|
+
related_ids = ''; unknown = @(); unknown_count = 0; unknown_ids = ''
|
|
4036
|
+
unknown_blocks = $true; wave_ordering_ok = $false
|
|
4037
|
+
wave_problem = "the wave reader could not be run ($why), so no open change in this context is ordered against $crId at all" }
|
|
4038
|
+
}
|
|
4039
|
+
if (-not $answered) {
|
|
4040
|
+
$mergedOpen = @([pscustomobject]@{ cr_id = "(merged-open check failed: $why)"; blocking = $true })
|
|
4041
|
+
$mergedText = $mergedOpen[0].cr_id
|
|
4042
|
+
}
|
|
4043
|
+
$blockers = @($order.related)
|
|
4004
4044
|
|
|
4005
4045
|
Write-Json ([ordered]@{
|
|
4006
|
-
ok = $true; cr_id = $crId
|
|
4046
|
+
ok = $true; cr_id = $crId
|
|
4047
|
+
wave = $(if ($null -ne $order.wave) { $order.wave } else { '' })
|
|
4048
|
+
wave_known = [bool]$order.wave_known
|
|
4007
4049
|
exists = (Test-Path -LiteralPath (Join-Path $fd "changes/$crId"))
|
|
4008
4050
|
# REBASE half — the masters moved because a change CLOSED and bumped the number.
|
|
4009
4051
|
base_version = $baseVersion; current_version = $current
|
|
@@ -4011,14 +4053,19 @@ function Invoke-CrMergeCheck {
|
|
|
4011
4053
|
base_stamped = [bool]$baseVersion
|
|
4012
4054
|
blockers = @($blockers); blocker_count = @($blockers).Count
|
|
4013
4055
|
has_blockers = (@($blockers).Count -gt 0)
|
|
4014
|
-
blocker_ids =
|
|
4056
|
+
blocker_ids = $order.related_ids
|
|
4057
|
+
# The changes this one cannot be ordered against at all, in either direction.
|
|
4058
|
+
unordered = @($order.unknown); unordered_count = $order.unknown_count
|
|
4059
|
+
unordered_ids = $order.unknown_ids
|
|
4060
|
+
wave_ordering_ok = [bool]$order.wave_ordering_ok
|
|
4061
|
+
wave_problem = $order.wave_problem
|
|
4015
4062
|
# The other way the masters move: a change MERGED and has not closed yet.
|
|
4016
4063
|
merged_open = @($mergedOpen); merged_open_count = @($mergedOpen).Count
|
|
4017
4064
|
has_merged_open = (@($mergedOpen).Count -gt 0)
|
|
4018
|
-
merged_open_ids =
|
|
4019
|
-
# All
|
|
4020
|
-
# condition and a gate reading
|
|
4021
|
-
merge_ok = ((-not $rebaseNeeded) -and (@($blockers).Count -eq 0) -and (@($mergedOpen).Count -eq 0))
|
|
4065
|
+
merged_open_ids = $mergedText
|
|
4066
|
+
# All four refusals as ONE positive flag, because the engine takes no compound
|
|
4067
|
+
# condition and a gate reading four booleans would need four passes over one question.
|
|
4068
|
+
merge_ok = ((-not $rebaseNeeded) -and [bool]$order.wave_ordering_ok -and (@($blockers).Count -eq 0) -and (@($mergedOpen).Count -eq 0))
|
|
4022
4069
|
})
|
|
4023
4070
|
}
|
|
4024
4071
|
|
|
@@ -4039,53 +4086,57 @@ function Invoke-CrRecallCheck {
|
|
|
4039
4086
|
$changes = Join-Path $fd 'changes'
|
|
4040
4087
|
$crDir = Join-Path $changes $crId
|
|
4041
4088
|
|
|
4042
|
-
function Get-WaveOf([string]$dir) {
|
|
4043
|
-
$p = Join-Path $dir 'delta-spec.md'
|
|
4044
|
-
if (Test-Path $p) {
|
|
4045
|
-
$m = [regex]::Match((Get-Content $p -Raw), '(?m)^\*\*Wave\*\*:[ \t]*(\d+)')
|
|
4046
|
-
if ($m.Success) { return [int]$m.Groups[1].Value }
|
|
4047
|
-
}
|
|
4048
|
-
return 1
|
|
4049
|
-
}
|
|
4050
|
-
|
|
4051
4089
|
# MERGED = a Change History row whose CR cell IS this change, in this feature's spec or
|
|
4052
4090
|
# in the shared context model — aw-crstate owns that test for all three readers of it.
|
|
4053
4091
|
# Fails CLOSED: an unreadable helper reports merged, never "never merged", which is the
|
|
4054
4092
|
# answer that would let close-delta archive a change nobody merged.
|
|
4093
|
+
#
|
|
4094
|
+
# DEPENDANTS come from the same module the merge fence reads in the opposite direction.
|
|
4095
|
+
# An unreadable `**Wave**:` is not wave 1 here either: it used to mean the change was
|
|
4096
|
+
# not counted as higher, so recall withdrew the ground from under a dependant it could
|
|
4097
|
+
# not see. Waves have no ceiling, so any unordered change could sit above this one.
|
|
4055
4098
|
$spec = Join-Path $fd 'spec.md'
|
|
4056
4099
|
$merged = $true; $evidence = '(merged check unavailable — treated as merged)'
|
|
4057
|
-
$
|
|
4100
|
+
$py = Get-Py
|
|
4058
4101
|
$crstate = Join-Path $PSScriptRoot '..' 'python' 'aw-crstate.py'
|
|
4059
|
-
|
|
4102
|
+
$order = $null; $why = 'python or aw-crstate.py not found'
|
|
4103
|
+
if ($py -and (Test-Path $crstate)) {
|
|
4060
4104
|
try {
|
|
4061
|
-
$line = (& $
|
|
4105
|
+
$line = (& $py $crstate 'merged' '--cr' $crId '--doc' $spec '--doc' (Join-Path $ctx 'domain-model.md') | Select-Object -First 1)
|
|
4062
4106
|
if ($line) {
|
|
4063
4107
|
$h = $line | ConvertFrom-Json
|
|
4064
4108
|
$merged = [bool]$h.merged; $evidence = [string]$h.evidence
|
|
4065
4109
|
}
|
|
4066
|
-
|
|
4110
|
+
$line = (& $py $crstate 'wave-order' '--cr' $crId '--feature-dir' $fd '--context-dir' $ctx '--direction' 'higher' '--scope' 'feature' | Select-Object -First 1)
|
|
4111
|
+
if ($line) { $order = ($line | ConvertFrom-Json) }
|
|
4112
|
+
} catch { $order = $null; $why = "$_" }
|
|
4067
4113
|
}
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
if ($d.Name -eq $crId -or $d.Name -eq 'archive') { continue }
|
|
4074
|
-
$w = Get-WaveOf $d.FullName
|
|
4075
|
-
if ($w -gt $wave) { $dependants += [ordered]@{ cr_id = $d.Name; wave = $w } }
|
|
4076
|
-
}
|
|
4114
|
+
if (-not $order) {
|
|
4115
|
+
$order = [pscustomobject]@{ wave = $null; wave_known = $false; related = @(); related_count = 0
|
|
4116
|
+
related_ids = ''; unknown = @(); unknown_count = 0; unknown_ids = ''
|
|
4117
|
+
unknown_blocks = $true; wave_ordering_ok = $false
|
|
4118
|
+
wave_problem = "the wave reader could not be run ($why), so no open change in this feature is ordered against $crId at all" }
|
|
4077
4119
|
}
|
|
4120
|
+
$dependants = @($order.related)
|
|
4121
|
+
|
|
4078
4122
|
Write-Json ([ordered]@{
|
|
4079
|
-
ok
|
|
4080
|
-
cr_id
|
|
4081
|
-
exists
|
|
4082
|
-
wave
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4123
|
+
ok = $true
|
|
4124
|
+
cr_id = $crId
|
|
4125
|
+
exists = (Test-Path $crDir)
|
|
4126
|
+
wave = $(if ($null -ne $order.wave) { $order.wave } else { '' })
|
|
4127
|
+
wave_known = [bool]$order.wave_known
|
|
4128
|
+
merged = $merged
|
|
4129
|
+
merge_evidence = $evidence
|
|
4130
|
+
dependants = @($dependants)
|
|
4131
|
+
dependant_ids = $order.related_ids
|
|
4132
|
+
dependant_count = @($dependants).Count
|
|
4133
|
+
has_dependants = (@($dependants).Count -gt 0)
|
|
4134
|
+
# The changes this one cannot be ordered against at all, in either direction.
|
|
4135
|
+
unordered = @($order.unknown)
|
|
4136
|
+
unordered_count = $order.unknown_count
|
|
4137
|
+
unordered_ids = $order.unknown_ids
|
|
4138
|
+
wave_ordering_ok = [bool]$order.wave_ordering_ok
|
|
4139
|
+
wave_problem = $order.wave_problem
|
|
4089
4140
|
})
|
|
4090
4141
|
}
|
|
4091
4142
|
|
|
@@ -4174,71 +4225,26 @@ function Get-FileSha {
|
|
|
4174
4225
|
return (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLower()
|
|
4175
4226
|
}
|
|
4176
4227
|
|
|
4228
|
+
# THE RULE ITSELF LIVES IN python/aw-plangate.py, which both shells invoke. It was a set
|
|
4229
|
+
# of sha comparisons written twice — a heredoc in bash, this function natively — and every
|
|
4230
|
+
# change to it had to be re-derived here by hand and kept in step by eye. It changed twice
|
|
4231
|
+
# that way. These two are now thin wrappers: resolve the feature, hand over.
|
|
4177
4232
|
function Invoke-PlanGateStamp {
|
|
4178
4233
|
param([string[]]$Rest)
|
|
4179
|
-
$fdArg = ''; $
|
|
4234
|
+
$fdArg = ''; $pass = @()
|
|
4180
4235
|
for ($i = 0; $i -lt $Rest.Count; $i++) {
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
{ $_ -in '--spec-version','-SpecVersion' } { $specVersion = $Rest[++$i] }
|
|
4184
|
-
{ $_ -in '--mode','-Mode' } { $mode = $Rest[++$i] }
|
|
4185
|
-
{ $_ -in '--verdict','-Verdict' } { $verdict = $Rest[++$i] }
|
|
4186
|
-
{ $_ -in '--plan-gaps','-PlanGaps' } { $planGaps = $Rest[++$i] }
|
|
4187
|
-
{ $_ -in '--routed','-Routed' } { $routedRaw = $Rest[++$i] }
|
|
4188
|
-
}
|
|
4236
|
+
if ($Rest[$i] -in '--feature-dir','-FeatureDir') { $fdArg = $Rest[++$i] }
|
|
4237
|
+
else { $pass += $Rest[$i] }
|
|
4189
4238
|
}
|
|
4190
|
-
# An unrecognised verdict is recorded as `gaps`, the one that refuses.
|
|
4191
|
-
$verdict = "$verdict".Trim()
|
|
4192
|
-
if ($verdict -notin @('conformant','routed-gaps','gaps')) { $verdict = 'gaps' }
|
|
4193
|
-
$planGapCount = 0
|
|
4194
|
-
if (-not [int]::TryParse("$planGaps", [ref]$planGapCount)) { $planGapCount = 0 }
|
|
4195
|
-
$routed = @()
|
|
4196
|
-
try {
|
|
4197
|
-
$parsedRouted = ConvertFrom-Json -InputObject $(if ("$routedRaw".Trim()) { $routedRaw } else { '[]' }) -NoEnumerate -ErrorAction Stop
|
|
4198
|
-
$routed = @($parsedRouted | ForEach-Object { "$_" })
|
|
4199
|
-
} catch { $routed = @() }
|
|
4200
4239
|
$repo = Get-RepoRoot; $fd = Resolve-FeatureDir -Arg $fdArg -RepoRoot $repo
|
|
4201
4240
|
if (-not $fd) { Write-Error 'plan-gate-stamp: no feature set and no --feature-dir'; exit 1 }
|
|
4202
4241
|
$ctx = Get-ContextDirForFeature -FeatureDir $fd -Repo $repo
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
# that has not been through `plan` since the rename — every brownfield feature `adopt`
|
|
4210
|
-
# produces — is GATED on those documents. Hashing only the new names would leave such a
|
|
4211
|
-
# feature with an empty plan map: a receipt that looks tracked and fresh while the document
|
|
4212
|
-
# implement actually generates from is unwatched.
|
|
4213
|
-
$plans = [ordered]@{}
|
|
4214
|
-
foreach ($name in @('backend-plan.md','frontend-plan.md','design.md','design.ui.md')) {
|
|
4215
|
-
$h = Get-FileSha -Path (Join-Path $fd $name)
|
|
4216
|
-
if ($h) { $plans[$name] = $h }
|
|
4217
|
-
}
|
|
4218
|
-
$rec = [ordered]@{
|
|
4219
|
-
gated = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')
|
|
4220
|
-
gate_mode = $mode
|
|
4221
|
-
spec_version = $specVersion.Trim()
|
|
4222
|
-
verdict = $verdict
|
|
4223
|
-
plan_gaps = $planGapCount
|
|
4224
|
-
routed_gaps = @($routed)
|
|
4225
|
-
spec_sha = (Get-FileSha -Path (Join-Path $fd 'spec.md'))
|
|
4226
|
-
model_sha = (Get-FileSha -Path (Join-Path $ctx 'domain-model.md'))
|
|
4227
|
-
plans = $plans
|
|
4228
|
-
note = ('Written by every master-mode analyze-plan run. It records the bytes that were ' +
|
|
4229
|
-
'checked against each other and the verdict on them, not a version — the plans'' ' +
|
|
4230
|
-
'**Version**: headers are stamped in lockstep by close-delta and cannot answer ' +
|
|
4231
|
-
'this. implement reads it through plan-gate-check: moved bytes or verdict `gaps` ' +
|
|
4232
|
-
'refuse; `routed-gaps` lists what /appweaver specify still has to lift.')
|
|
4233
|
-
}
|
|
4234
|
-
$p = Join-Path $fd '.aw-plan-gate.json'
|
|
4235
|
-
(ConvertTo-Json -InputObject $rec -Depth 6) + "`n" | Set-Content -LiteralPath $p -NoNewline -Encoding utf8
|
|
4236
|
-
Write-Json ([ordered]@{
|
|
4237
|
-
file = (ConvertTo-RepoRelative -Path $p -RepoRoot $repo); gated = $rec.gated
|
|
4238
|
-
spec_version = $rec.spec_version; verdict = $verdict; plan_gaps = $planGapCount
|
|
4239
|
-
routed_gaps = $routed.Count; hashed = @($plans.Keys | Sort-Object)
|
|
4240
|
-
count = $plans.Count; ok = $true
|
|
4241
|
-
})
|
|
4242
|
+
$py = Get-Py
|
|
4243
|
+
if (-not $py) { Write-Error 'plan-gate-stamp: python3 is required'; exit 1 }
|
|
4244
|
+
$core = Join-Path $PSScriptRoot '..' 'python' 'aw-plangate.py'
|
|
4245
|
+
if (-not (Test-Path $core)) { Write-Error "plan-gate-stamp: core not found at $core"; exit 1 }
|
|
4246
|
+
& $py $core stamp --feature-dir $fd --context-dir $ctx --root $repo @pass
|
|
4247
|
+
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
4242
4248
|
}
|
|
4243
4249
|
|
|
4244
4250
|
function Invoke-PlanGateCheck {
|
|
@@ -4250,70 +4256,29 @@ function Invoke-PlanGateCheck {
|
|
|
4250
4256
|
$repo = Get-RepoRoot; $fd = Resolve-FeatureDir -Arg $fdArg -RepoRoot $repo
|
|
4251
4257
|
if (-not $fd) { Write-Error 'plan-gate-check: no feature set and no --feature-dir'; exit 1 }
|
|
4252
4258
|
$ctx = Get-ContextDirForFeature -FeatureDir $fd -Repo $repo
|
|
4253
|
-
$
|
|
4254
|
-
$
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
$
|
|
4259
|
-
file = (ConvertTo-RepoRelative -Path $p -RepoRoot $repo); tracked = [bool]$rec
|
|
4260
|
-
gate_fresh = $false; verdict = ''; may_generate = $true; plan_gaps = 0
|
|
4261
|
-
routed_gaps = @(); routed_csv = ''; spec_moved = $false; model_moved = $false
|
|
4262
|
-
plans_changed = @(); plans_changed_csv = ''; gated = ''; spec_version = ''
|
|
4263
|
-
reason = ''; ok = $true
|
|
4264
|
-
}
|
|
4265
|
-
if (-not $rec) {
|
|
4266
|
-
# Never gated. Not a failure and not a stale plan — an unknown, and the only honest
|
|
4267
|
-
# thing to report about a tree that has not run analyze-plan since this op existed.
|
|
4268
|
-
$out.reason = 'no .aw-plan-gate.json — this feature has not been through analyze-plan in master mode since the gate began leaving a receipt'
|
|
4269
|
-
} else {
|
|
4270
|
-
# ConvertFrom-Json turns an ISO timestamp into a DateTime; render it back in the
|
|
4271
|
-
# form the receipt was written in, as the bash twin reports it.
|
|
4272
|
-
$out.gated = $(if ($rec.gated -is [datetime]) { $rec.gated.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') } else { [string]$rec.gated })
|
|
4273
|
-
$out.spec_version = [string]$rec.spec_version
|
|
4274
|
-
# A receipt from before the verdict existed was only ever written on a clean pass.
|
|
4275
|
-
$out.verdict = $(if ("$($rec.verdict)") { [string]$rec.verdict } else { 'conformant' })
|
|
4276
|
-
$gapCount = 0
|
|
4277
|
-
if ($null -ne $rec.plan_gaps) { [void][int]::TryParse("$($rec.plan_gaps)", [ref]$gapCount) }
|
|
4278
|
-
$out.plan_gaps = $gapCount
|
|
4279
|
-
$out.routed_gaps = @(@($rec.routed_gaps) | Where-Object { $null -ne $_ } | ForEach-Object { "$_" })
|
|
4280
|
-
$out.routed_csv = ($out.routed_gaps -join '; ')
|
|
4281
|
-
$specSha = [string]$rec.spec_sha
|
|
4282
|
-
$out.spec_moved = ($specSha -and $specSha -ne (Get-FileSha -Path (Join-Path $fd 'spec.md')))
|
|
4283
|
-
# A model recorded as absent that is still absent has not moved. A context that
|
|
4284
|
-
# GAINED a domain model since the gate has moved: check 4 never ran against it.
|
|
4285
|
-
$out.model_moved = ([string]$rec.model_sha -ne (Get-FileSha -Path (Join-Path $ctx 'domain-model.md')))
|
|
4286
|
-
$changed = @()
|
|
4287
|
-
if ($rec.plans) {
|
|
4288
|
-
foreach ($name in @($rec.plans.PSObject.Properties.Name | Sort-Object)) {
|
|
4289
|
-
if ((Get-FileSha -Path (Join-Path $fd $name)) -ne [string]$rec.plans.$name) { $changed += $name }
|
|
4290
|
-
}
|
|
4291
|
-
}
|
|
4292
|
-
$out.plans_changed = @($changed)
|
|
4293
|
-
$out.plans_changed_csv = ($changed -join ', ')
|
|
4294
|
-
$out.gate_fresh = (-not ($out.spec_moved -or $out.model_moved -or $changed.Count -gt 0))
|
|
4295
|
-
# Moved bytes refuse whatever the verdict was; unmoved bytes refuse only on `gaps`.
|
|
4296
|
-
$out.may_generate = ($out.gate_fresh -and $out.verdict -ne 'gaps')
|
|
4297
|
-
if (-not $out.gate_fresh) {
|
|
4298
|
-
$bits = @()
|
|
4299
|
-
if ($out.spec_moved) { $bits += 'spec.md has changed' }
|
|
4300
|
-
if ($out.model_moved) { $bits += 'domain-model.md has changed' }
|
|
4301
|
-
if ($changed.Count -gt 0) { $bits += ('{0} changed (a re-derive by `plan main`, or a hand edit)' -f ($changed -join ', ')) }
|
|
4302
|
-
$out.reason = 'since analyze-plan checked them on {0}: {1}' -f $out.gated, ($bits -join '; ')
|
|
4303
|
-
} elseif ($out.verdict -eq 'gaps') {
|
|
4304
|
-
$n = $(if ($out.plan_gaps) { $out.plan_gaps } else { 'some' })
|
|
4305
|
-
$out.reason = 'analyze-plan on {0} left {1} gap(s) a plan rewrite closes, unrepaired' -f $out.gated, $n
|
|
4306
|
-
} elseif ($out.verdict -eq 'routed-gaps') {
|
|
4307
|
-
$out.reason = 'the spec, the model and every gated plan are byte-identical to what analyze-plan checked on {0}; it left {1} gap(s) only /appweaver specify can close: {2}' -f $out.gated, $out.routed_gaps.Count, $out.routed_csv
|
|
4308
|
-
} else {
|
|
4309
|
-
$out.reason = 'the spec, the model and every gated plan are byte-identical to what analyze-plan passed on {0}' -f $out.gated
|
|
4310
|
-
}
|
|
4311
|
-
}
|
|
4312
|
-
Write-Json $out
|
|
4259
|
+
$py = Get-Py
|
|
4260
|
+
if (-not $py) { Write-Error 'plan-gate-check: python3 is required'; exit 1 }
|
|
4261
|
+
$core = Join-Path $PSScriptRoot '..' 'python' 'aw-plangate.py'
|
|
4262
|
+
if (-not (Test-Path $core)) { Write-Error "plan-gate-check: core not found at $core"; exit 1 }
|
|
4263
|
+
& $py $core check --feature-dir $fd --context-dir $ctx --root $repo
|
|
4264
|
+
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
4313
4265
|
}
|
|
4314
4266
|
|
|
4315
4267
|
# Which of a lane plan's elements the generated code realises. One implementation
|
|
4316
4268
|
# (python/aw-plan-coverage.py), called identically by both shells — see the bash twin.
|
|
4269
|
+
# Split a gate's findings by the command that closes each one. Twin of the bash
|
|
4270
|
+
# cmd_gap_route, calling the SAME aw-gaps.py — see the bash comment for why a plan
|
|
4271
|
+
# defect must never be routed to `implement`.
|
|
4272
|
+
function Invoke-GapRoute {
|
|
4273
|
+
param([string[]]$Rest)
|
|
4274
|
+
$py = Get-Py
|
|
4275
|
+
if (-not $py) { Write-Error 'gap-route: python3 is required'; exit 1 }
|
|
4276
|
+
$core = Join-Path $PSScriptRoot '..' 'python' 'aw-gaps.py'
|
|
4277
|
+
if (-not (Test-Path $core)) { Write-Error "gap-route: core not found at $core"; exit 1 }
|
|
4278
|
+
& $py $core route @Rest
|
|
4279
|
+
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
4280
|
+
}
|
|
4281
|
+
|
|
4317
4282
|
function Invoke-PlanCoverage {
|
|
4318
4283
|
param([string[]]$Rest)
|
|
4319
4284
|
$py = Get-Py
|
|
@@ -4468,6 +4433,7 @@ switch ($sub) {
|
|
|
4468
4433
|
'plan-gate-stamp' { Invoke-PlanGateStamp -Rest $rest }
|
|
4469
4434
|
'plan-gate-check' { Invoke-PlanGateCheck -Rest $rest }
|
|
4470
4435
|
'plan-coverage' { Invoke-PlanCoverage -Rest $rest }
|
|
4436
|
+
'gap-route' { Invoke-GapRoute -Rest $rest }
|
|
4471
4437
|
'checklist-read' { Invoke-ChecklistRead -Rest $rest }
|
|
4472
4438
|
'checklist-apply' { Invoke-ChecklistApply -Rest $rest }
|
|
4473
4439
|
'detect-project' { Invoke-DetectProject -Rest $rest }
|
|
@@ -4483,6 +4449,7 @@ switch ($sub) {
|
|
|
4483
4449
|
'setup-constitution' { Invoke-SetupConstitution -Rest $rest }
|
|
4484
4450
|
'compose-constitution' { Invoke-ComposeConstitution -Rest $rest }
|
|
4485
4451
|
'constitution-verify' { Invoke-ConstitutionVerify -Rest $rest }
|
|
4452
|
+
'principles-list' { Invoke-PrinciplesList -Rest $rest }
|
|
4486
4453
|
'agent-context-update' { Invoke-AgentContextUpdate -Rest $rest }
|
|
4487
4454
|
default { Write-Error "Unknown subcommand: $sub"; exit 1 }
|
|
4488
4455
|
}
|
|
@@ -8,7 +8,10 @@
|
|
|
8
8
|
# aw-xref.ps1 list --aggregate Y [--root DIR] [--bundle knowledge]
|
|
9
9
|
# aw-xref.ps1 concept-upsert --aggregate X [--source-files a,b,c] [--description D] [--origin ...] [--root DIR]
|
|
10
10
|
# aw-xref.ps1 concept-overview --aggregate X (--text PROSE | --text-file F) [--root DIR]
|
|
11
|
-
# aw-xref.ps1 concept-paths --aggregate X | --feature-dir <dir> [--
|
|
11
|
+
# aw-xref.ps1 concept-paths --aggregate X | --feature-dir <dir> [--under <subtree>]...
|
|
12
|
+
# [--root DIR] [--bundle knowledge]
|
|
13
|
+
# (--under keeps only paths inside that subtree, so a caller
|
|
14
|
+
# generating ONE lane is not handed another lane's files)
|
|
12
15
|
# aw-xref.ps1 feature-aggregates --feature-dir <dir> [--root DIR]
|
|
13
16
|
# (the aggregate(s) a feature acts on: its spec's Acts-on line,
|
|
14
17
|
# then the boundary map, then the feature name itself)
|