aid-installer 1.1.1 → 2.0.0
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/README.md +2 -2
- package/VERSION +1 -1
- package/bin/aid +154 -20
- package/bin/aid.ps1 +186 -41
- package/dashboard/home.html +41 -4
- package/dashboard/reader/derivation.py +228 -5
- package/dashboard/reader/models.py +22 -0
- package/dashboard/reader/parsers.py +127 -0
- package/dashboard/reader/reader.py +6 -1
- package/dashboard/server/reader.mjs +313 -0
- package/dashboard/server/server.py +12 -0
- package/lib/AidInstallCore.psm1 +247 -53
- package/lib/aid-install-core.sh +170 -13
- package/package.json +9 -6
- package/scripts/vendor.js +0 -98
package/lib/AidInstallCore.psm1
CHANGED
|
@@ -47,6 +47,10 @@
|
|
|
47
47
|
|
|
48
48
|
Set-StrictMode -Version Latest
|
|
49
49
|
|
|
50
|
+
# Enable TLS 1.2 for HTTPS. Windows PowerShell 5.1 (.NET Framework) can default to
|
|
51
|
+
# SSL3/TLS1.0, which GitHub/npm/pypi reject -> downloads fail. Harmless on PS7/.NET Core.
|
|
52
|
+
try { [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 } catch {}
|
|
53
|
+
|
|
50
54
|
# Guard against being imported more than once.
|
|
51
55
|
# Use Get-Variable with -ErrorAction SilentlyContinue to avoid strict-mode failure on first load.
|
|
52
56
|
$_aidLoadedVar = Get-Variable -Name '_AID_INSTALL_CORE_LOADED' -Scope Global -ErrorAction SilentlyContinue
|
|
@@ -678,7 +682,7 @@ function Read-ManifestToolPaths {
|
|
|
678
682
|
try {
|
|
679
683
|
$data = Get-Content -LiteralPath $ManifestPath -Raw | ConvertFrom-Json
|
|
680
684
|
$toolData = if ($data.tools -and ($data.tools.PSObject.Properties.Name -contains $Tool)) { $data.tools.$Tool } else { $null }
|
|
681
|
-
if ($toolData -and $toolData.paths) {
|
|
685
|
+
if ($toolData -and $toolData.PSObject.Properties['paths'] -and $toolData.paths) {
|
|
682
686
|
return @($toolData.paths)
|
|
683
687
|
}
|
|
684
688
|
} catch {}
|
|
@@ -693,7 +697,7 @@ function Read-ManifestToolVersion {
|
|
|
693
697
|
try {
|
|
694
698
|
$data = Get-Content -LiteralPath $ManifestPath -Raw | ConvertFrom-Json
|
|
695
699
|
$toolData = if ($data.tools -and ($data.tools.PSObject.Properties.Name -contains $Tool)) { $data.tools.$Tool } else { $null }
|
|
696
|
-
if ($toolData -and $toolData.version) { return $toolData.version }
|
|
700
|
+
if ($toolData -and $toolData.PSObject.Properties['version'] -and $toolData.version) { return [string]$toolData.version }
|
|
697
701
|
} catch {}
|
|
698
702
|
return ''
|
|
699
703
|
}
|
|
@@ -706,9 +710,15 @@ function Read-ManifestRootAgent {
|
|
|
706
710
|
try {
|
|
707
711
|
$data = Get-Content -LiteralPath $ManifestPath -Raw | ConvertFrom-Json
|
|
708
712
|
$toolData = if ($data.tools -and ($data.tools.PSObject.Properties.Name -contains $Tool)) { $data.tools.$Tool } else { $null }
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
713
|
+
# Guard root_agent_files: absent on old-format manifests; PSObject.Properties['key'] is
|
|
714
|
+
# safe under Set-StrictMode -Version Latest unlike direct property access.
|
|
715
|
+
$raf = if ($toolData -and $toolData.PSObject.Properties['root_agent_files']) { $toolData.root_agent_files } else { $null }
|
|
716
|
+
if ($raf) {
|
|
717
|
+
foreach ($entry in $raf) {
|
|
718
|
+
if ($entry.PSObject.Properties['path'] -and $entry.path -eq $FileName) {
|
|
719
|
+
$sha = if ($entry.PSObject.Properties['sha256']) { $entry.sha256 } else { '' }
|
|
720
|
+
return $sha
|
|
721
|
+
}
|
|
712
722
|
}
|
|
713
723
|
}
|
|
714
724
|
} catch {}
|
|
@@ -723,9 +733,10 @@ function Read-ManifestRootAgentStatus {
|
|
|
723
733
|
try {
|
|
724
734
|
$data = Get-Content -LiteralPath $ManifestPath -Raw | ConvertFrom-Json
|
|
725
735
|
$toolData = if ($data.tools -and ($data.tools.PSObject.Properties.Name -contains $Tool)) { $data.tools.$Tool } else { $null }
|
|
726
|
-
if ($toolData -and $toolData.root_agent_files) {
|
|
727
|
-
|
|
728
|
-
|
|
736
|
+
$raf = if ($toolData -and $toolData.PSObject.Properties['root_agent_files']) { $toolData.root_agent_files } else { $null }
|
|
737
|
+
if ($raf) {
|
|
738
|
+
foreach ($entry in $raf) {
|
|
739
|
+
if ($entry.PSObject.Properties['path'] -and $entry.path -eq $FileName) {
|
|
729
740
|
if ($entry.PSObject.Properties['status']) { return $entry.status }
|
|
730
741
|
return 'owned'
|
|
731
742
|
}
|
|
@@ -880,8 +891,11 @@ function Write-AidManifest {
|
|
|
880
891
|
}
|
|
881
892
|
|
|
882
893
|
# Top-level installed_at: preserve existing.
|
|
894
|
+
# Guard via PSObject.Properties['key'] -- Set-StrictMode -Version Latest (active in this module)
|
|
895
|
+
# throws PropertyNotFoundException on direct access of absent properties. Old-format manifests
|
|
896
|
+
# (schema:1) have no root-level installed_at; new-format ones (manifest_version:1) do.
|
|
883
897
|
$topInstalledAt = $now
|
|
884
|
-
if ($existingData -and $existingData.installed_at) {
|
|
898
|
+
if ($existingData -and $existingData.PSObject.Properties['installed_at'] -and $existingData.installed_at) {
|
|
885
899
|
$topInstalledAt = $existingData.installed_at
|
|
886
900
|
}
|
|
887
901
|
|
|
@@ -895,17 +909,22 @@ function Write-AidManifest {
|
|
|
895
909
|
$tid = $_.Name
|
|
896
910
|
if ($tid -ne $Tool) {
|
|
897
911
|
$t = $_.Value
|
|
898
|
-
|
|
912
|
+
# Guard per-tool properties: old manifests lack root_agent_files; use
|
|
913
|
+
# PSObject.Properties['key'] check before direct access (StrictMode-safe).
|
|
914
|
+
$tP = if ($t.PSObject.Properties['paths'] -and $t.paths) { [System.Collections.Generic.List[string]]($t.paths) } else { [System.Collections.Generic.List[string]]::new() }
|
|
899
915
|
$tR = [System.Collections.Generic.List[hashtable]]::new()
|
|
900
|
-
if ($t.root_agent_files) {
|
|
901
|
-
|
|
916
|
+
$tRaf = if ($t.PSObject.Properties['root_agent_files']) { $t.root_agent_files } else { $null }
|
|
917
|
+
if ($tRaf) {
|
|
918
|
+
foreach ($e in $tRaf) {
|
|
919
|
+
$ePath = if ($e.PSObject.Properties['path']) { $e.path } else { '' }
|
|
920
|
+
$eSha256 = if ($e.PSObject.Properties['sha256']) { $e.sha256 } else { '' }
|
|
902
921
|
$st = if ($e.PSObject.Properties['status']) { $e.status } else { 'owned' }
|
|
903
|
-
$tR.Add(@{ path = $
|
|
922
|
+
if ($ePath) { $tR.Add(@{ path = $ePath; sha256 = $eSha256; status = $st }) }
|
|
904
923
|
}
|
|
905
924
|
}
|
|
906
925
|
$toolsMap[$tid] = @{
|
|
907
|
-
Version = if ($t.version) { $t.version } else { '' }
|
|
908
|
-
InstalledAt = if ($t.installed_at) { $t.installed_at } else { $now }
|
|
926
|
+
Version = if ($t.PSObject.Properties['version'] -and $t.version) { [string]$t.version } else { '' }
|
|
927
|
+
InstalledAt = if ($t.PSObject.Properties['installed_at'] -and $t.installed_at) { $t.installed_at } else { $now }
|
|
909
928
|
Paths = $tP
|
|
910
929
|
RootAgentFiles = $tR
|
|
911
930
|
}
|
|
@@ -921,14 +940,14 @@ function Write-AidManifest {
|
|
|
921
940
|
|
|
922
941
|
# tool installed_at: preserve existing.
|
|
923
942
|
$toolInstalledAt = $now
|
|
924
|
-
if ($existingTool -and $existingTool.installed_at) {
|
|
943
|
+
if ($existingTool -and $existingTool.PSObject.Properties['installed_at'] -and $existingTool.installed_at) {
|
|
925
944
|
$toolInstalledAt = $existingTool.installed_at
|
|
926
945
|
}
|
|
927
946
|
|
|
928
947
|
# De-duplicate paths (union, preserving order: existing first, then new).
|
|
929
948
|
$seenPaths = [System.Collections.Generic.HashSet[string]]::new()
|
|
930
949
|
$mergedPaths = [System.Collections.Generic.List[string]]::new()
|
|
931
|
-
if ($existingTool -and $existingTool.paths) {
|
|
950
|
+
if ($existingTool -and $existingTool.PSObject.Properties['paths'] -and $existingTool.paths) {
|
|
932
951
|
foreach ($p in $existingTool.paths) {
|
|
933
952
|
if ($seenPaths.Add($p)) { $mergedPaths.Add($p) }
|
|
934
953
|
}
|
|
@@ -939,10 +958,13 @@ function Write-AidManifest {
|
|
|
939
958
|
|
|
940
959
|
# Merge root_agent_files: update or add per path.
|
|
941
960
|
$rafMap = [System.Collections.Specialized.OrderedDictionary]::new()
|
|
942
|
-
if ($existingTool -and $existingTool.root_agent_files) {
|
|
943
|
-
|
|
961
|
+
$existingRaf = if ($existingTool -and $existingTool.PSObject.Properties['root_agent_files']) { $existingTool.root_agent_files } else { $null }
|
|
962
|
+
if ($existingRaf) {
|
|
963
|
+
foreach ($e in $existingRaf) {
|
|
964
|
+
$ePath = if ($e.PSObject.Properties['path']) { $e.path } else { '' }
|
|
965
|
+
$eSha256 = if ($e.PSObject.Properties['sha256']) { $e.sha256 } else { '' }
|
|
944
966
|
$st = if ($e.PSObject.Properties['status']) { $e.status } else { 'owned' }
|
|
945
|
-
$rafMap[$
|
|
967
|
+
if ($ePath) { $rafMap[$ePath] = @{ path = $ePath; sha256 = $eSha256; status = $st } }
|
|
946
968
|
}
|
|
947
969
|
}
|
|
948
970
|
foreach ($entry in $RootEntries) {
|
|
@@ -995,23 +1017,27 @@ function Remove-ManifestTool {
|
|
|
995
1017
|
}
|
|
996
1018
|
|
|
997
1019
|
# Build tools map without the target tool.
|
|
1020
|
+
# Guard all per-tool property accesses via PSObject.Properties['key'] (StrictMode-safe).
|
|
998
1021
|
$toolsMap = [System.Collections.Specialized.OrderedDictionary]::new()
|
|
999
1022
|
if ($data.tools) {
|
|
1000
1023
|
$data.tools.PSObject.Properties | ForEach-Object {
|
|
1001
1024
|
$tid = $_.Name
|
|
1002
1025
|
if ($tid -ne $Tool) {
|
|
1003
1026
|
$t = $_.Value
|
|
1004
|
-
$tP = if ($t.paths) { [System.Collections.Generic.List[string]]($t.paths) } else { [System.Collections.Generic.List[string]]::new() }
|
|
1027
|
+
$tP = if ($t.PSObject.Properties['paths'] -and $t.paths) { [System.Collections.Generic.List[string]]($t.paths) } else { [System.Collections.Generic.List[string]]::new() }
|
|
1005
1028
|
$tR = [System.Collections.Generic.List[hashtable]]::new()
|
|
1006
|
-
if ($t.root_agent_files) {
|
|
1007
|
-
|
|
1029
|
+
$tRaf = if ($t.PSObject.Properties['root_agent_files']) { $t.root_agent_files } else { $null }
|
|
1030
|
+
if ($tRaf) {
|
|
1031
|
+
foreach ($e in $tRaf) {
|
|
1032
|
+
$ePath = if ($e.PSObject.Properties['path']) { $e.path } else { '' }
|
|
1033
|
+
$eSha256 = if ($e.PSObject.Properties['sha256']) { $e.sha256 } else { '' }
|
|
1008
1034
|
$st = if ($e.PSObject.Properties['status']) { $e.status } else { 'owned' }
|
|
1009
|
-
$tR.Add(@{ path = $
|
|
1035
|
+
if ($ePath) { $tR.Add(@{ path = $ePath; sha256 = $eSha256; status = $st }) }
|
|
1010
1036
|
}
|
|
1011
1037
|
}
|
|
1012
1038
|
$toolsMap[$tid] = @{
|
|
1013
|
-
Version = if ($t.version) { $t.version } else { '' }
|
|
1014
|
-
InstalledAt = if ($t.installed_at) { $t.installed_at } else { '' }
|
|
1039
|
+
Version = if ($t.PSObject.Properties['version'] -and $t.version) { [string]$t.version } else { '' }
|
|
1040
|
+
InstalledAt = if ($t.PSObject.Properties['installed_at'] -and $t.installed_at) { $t.installed_at } else { '' }
|
|
1015
1041
|
Paths = $tP
|
|
1016
1042
|
RootAgentFiles = $tR
|
|
1017
1043
|
}
|
|
@@ -1024,8 +1050,8 @@ function Remove-ManifestTool {
|
|
|
1024
1050
|
return
|
|
1025
1051
|
}
|
|
1026
1052
|
|
|
1027
|
-
$topIat = if ($data.installed_at) { $data.installed_at } else { ([System.DateTime]::UtcNow.ToString('yyyy-MM-ddTHH:mm:ssZ')) }
|
|
1028
|
-
$topVer = if ($data.aid_version) { $data.aid_version } else { '0.0.0' }
|
|
1053
|
+
$topIat = if ($data.PSObject.Properties['installed_at'] -and $data.installed_at) { $data.installed_at } else { ([System.DateTime]::UtcNow.ToString('yyyy-MM-ddTHH:mm:ssZ')) }
|
|
1054
|
+
$topVer = if ($data.PSObject.Properties['aid_version'] -and $data.aid_version) { $data.aid_version } else { '0.0.0' }
|
|
1029
1055
|
|
|
1030
1056
|
$json = script:Build-ManifestJson -TopInstalledAt $topIat -TopVersion $topVer -ToolsMap $toolsMap
|
|
1031
1057
|
|
|
@@ -1099,7 +1125,7 @@ function Invoke-AidProvisionSharedStateHome {
|
|
|
1099
1125
|
"schema: 1",
|
|
1100
1126
|
"projects:"
|
|
1101
1127
|
)
|
|
1102
|
-
|
|
1128
|
+
[System.IO.File]::WriteAllText($tmp, (($seedLines) -join "`n") + "`n", [System.Text.UTF8Encoding]::new($false))
|
|
1103
1129
|
Move-Item -LiteralPath $tmp -Destination $reg -Force -ErrorAction Stop
|
|
1104
1130
|
} catch {
|
|
1105
1131
|
Remove-Item -LiteralPath $tmp -Force -ErrorAction SilentlyContinue
|
|
@@ -1211,15 +1237,20 @@ function Invoke-PruneToolDirs {
|
|
|
1211
1237
|
# Rule (d): prune now-empty subdirs (deepest first, skip the root itself).
|
|
1212
1238
|
$subdirs = @(Get-ChildItem -LiteralPath $ADir -Recurse -Directory -ErrorAction SilentlyContinue)
|
|
1213
1239
|
# Sort deepest first (longest path first by ordinal).
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1240
|
+
# Guard: pipeline over an empty @() returns $null on PowerShell; [System.Array]::Sort($null)
|
|
1241
|
+
# throws "Value cannot be null" (reproduces on pwsh when the dir has no subdirectories,
|
|
1242
|
+
# e.g. cursor/antigravity idempotent re-run after retired roots are already gone).
|
|
1243
|
+
if ($subdirs.Count -gt 0) {
|
|
1244
|
+
$subdirPaths = [string[]]($subdirs | ForEach-Object { $_.FullName })
|
|
1245
|
+
[System.Array]::Sort($subdirPaths, [System.StringComparer]::Ordinal)
|
|
1246
|
+
[System.Array]::Reverse($subdirPaths)
|
|
1247
|
+
foreach ($dp in $subdirPaths) {
|
|
1248
|
+
if (Test-Path $dp -PathType Container) {
|
|
1249
|
+
$rem = @(Get-ChildItem -LiteralPath $dp -ErrorAction SilentlyContinue) | Select-Object -First 1
|
|
1250
|
+
if (-not $rem) {
|
|
1251
|
+
Remove-Item -LiteralPath $dp -Force
|
|
1252
|
+
if ($AidVerbose) { Write-Host "Pruned dir: $dp" }
|
|
1253
|
+
}
|
|
1223
1254
|
}
|
|
1224
1255
|
}
|
|
1225
1256
|
}
|
|
@@ -1231,6 +1262,8 @@ function Invoke-PruneToolDirs {
|
|
|
1231
1262
|
# that map points copilot-cli at the .github ROOT (forbidden by R1).
|
|
1232
1263
|
# Scope is the R1-compliant set: .github/{agents,skills,aid} only.
|
|
1233
1264
|
# -----------------------------------------------------------------------
|
|
1265
|
+
# Per-tool scoping: new layout (work-005/delivery-001).
|
|
1266
|
+
# Codex unified under .codex/; cursor and antigravity no longer ship rules/.
|
|
1234
1267
|
switch ($Tool) {
|
|
1235
1268
|
'claude-code' {
|
|
1236
1269
|
& $pruneNativeDir (Join-Path $Target '.claude\agents')
|
|
@@ -1238,15 +1271,15 @@ function Invoke-PruneToolDirs {
|
|
|
1238
1271
|
& $pruneAidSubtree (Join-Path $Target '.claude\aid')
|
|
1239
1272
|
}
|
|
1240
1273
|
'codex' {
|
|
1241
|
-
#
|
|
1274
|
+
# New unified layout: everything under .codex/ (agents, skills, aid).
|
|
1242
1275
|
& $pruneNativeDir (Join-Path $Target '.codex\agents')
|
|
1243
|
-
& $pruneNativeDir (Join-Path $Target '.
|
|
1244
|
-
& $pruneAidSubtree (Join-Path $Target '.
|
|
1276
|
+
& $pruneNativeDir (Join-Path $Target '.codex\skills')
|
|
1277
|
+
& $pruneAidSubtree (Join-Path $Target '.codex\aid')
|
|
1245
1278
|
}
|
|
1246
1279
|
'cursor' {
|
|
1280
|
+
# rules/ dir removed from new layout; agents/skills/aid remain.
|
|
1247
1281
|
& $pruneNativeDir (Join-Path $Target '.cursor\agents')
|
|
1248
1282
|
& $pruneNativeDir (Join-Path $Target '.cursor\skills')
|
|
1249
|
-
& $pruneNativeDir (Join-Path $Target '.cursor\rules')
|
|
1250
1283
|
& $pruneAidSubtree (Join-Path $Target '.cursor\aid')
|
|
1251
1284
|
}
|
|
1252
1285
|
'copilot-cli' {
|
|
@@ -1256,7 +1289,8 @@ function Invoke-PruneToolDirs {
|
|
|
1256
1289
|
& $pruneAidSubtree (Join-Path $Target '.github\aid')
|
|
1257
1290
|
}
|
|
1258
1291
|
'antigravity' {
|
|
1259
|
-
|
|
1292
|
+
# rules/ dir removed from new layout; agents/skills/aid remain.
|
|
1293
|
+
& $pruneNativeDir (Join-Path $Target '.agent\agents')
|
|
1260
1294
|
& $pruneNativeDir (Join-Path $Target '.agent\skills')
|
|
1261
1295
|
& $pruneAidSubtree (Join-Path $Target '.agent\aid')
|
|
1262
1296
|
}
|
|
@@ -1267,6 +1301,140 @@ function Invoke-PruneToolDirs {
|
|
|
1267
1301
|
}
|
|
1268
1302
|
}
|
|
1269
1303
|
|
|
1304
|
+
# ---------------------------------------------------------------------------
|
|
1305
|
+
# Retired-root migration sweep (FR7/FR7a)
|
|
1306
|
+
# ---------------------------------------------------------------------------
|
|
1307
|
+
|
|
1308
|
+
# Invoke-MigrateRetiredLayout <target> <tool> [aidVerbose]
|
|
1309
|
+
#
|
|
1310
|
+
# Complete-replacement migration: removes AID-owned content from the static
|
|
1311
|
+
# list of retired AID roots that no longer exist in the new bundle layout.
|
|
1312
|
+
# Called from Install-AidTool BEFORE Invoke-PruneToolDirs (same aid update pass).
|
|
1313
|
+
#
|
|
1314
|
+
# Retired roots swept per tool:
|
|
1315
|
+
# codex: .agents\ (split layout retired)
|
|
1316
|
+
# cursor: .cursor\rules\ (rules dir retired)
|
|
1317
|
+
# antigravity: .agent\rules\ (rules dir retired)
|
|
1318
|
+
#
|
|
1319
|
+
# Ownership markers applied (content-isolation.md rules 1+2):
|
|
1320
|
+
# Marker 1: filename starts with "aid-" (tool-native dir files)
|
|
1321
|
+
# Marker 2: lives inside an "aid\" subtree
|
|
1322
|
+
#
|
|
1323
|
+
# Marker 3 (AID:BEGIN/END region in root files) is NOT touched here;
|
|
1324
|
+
# that is handled by Copy-RootAgentFile exclusively.
|
|
1325
|
+
#
|
|
1326
|
+
# User content (no marker) is NEVER removed.
|
|
1327
|
+
# Idempotent: a no-op when the retired path is already absent.
|
|
1328
|
+
# Sets $script:_MigrateRetiredCount with the count of items removed.
|
|
1329
|
+
function Invoke-MigrateRetiredLayout {
|
|
1330
|
+
param(
|
|
1331
|
+
[string]$Target,
|
|
1332
|
+
[string]$Tool,
|
|
1333
|
+
[bool]$AidVerbose = $false,
|
|
1334
|
+
[bool]$ListOnly = $false # $true = dry-run enumeration only (no removals)
|
|
1335
|
+
)
|
|
1336
|
+
|
|
1337
|
+
$script:_MigrateRetiredCount = 0
|
|
1338
|
+
|
|
1339
|
+
# Determine whether a file is AID-owned (markers 1 or 2).
|
|
1340
|
+
$isAidOwned = {
|
|
1341
|
+
param([System.IO.FileInfo]$File)
|
|
1342
|
+
# Marker 1: filename starts with "aid-".
|
|
1343
|
+
if ($File.Name -like 'aid-*') { return $true }
|
|
1344
|
+
# Marker 2: lives inside an "aid" folder (any ancestor named "aid").
|
|
1345
|
+
$dir = $File.Directory
|
|
1346
|
+
while ($dir -ne $null -and $dir.FullName -ne $Target -and $dir.FullName -ne $dir.Root.FullName) {
|
|
1347
|
+
if ($dir.Name -eq 'aid') { return $true }
|
|
1348
|
+
$dir = $dir.Parent
|
|
1349
|
+
}
|
|
1350
|
+
return $false
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
# Sweep one retired root directory: move AID-owned files to trash, prune empty
|
|
1354
|
+
# dirs, then remove the retired root itself if now empty.
|
|
1355
|
+
# In ListOnly mode: enumerate would-be-moved files, make no changes.
|
|
1356
|
+
$sweepRetiredRoot = {
|
|
1357
|
+
param([string]$RDir)
|
|
1358
|
+
if (-not (Test-Path $RDir -PathType Container)) { return }
|
|
1359
|
+
|
|
1360
|
+
$trashBase = Join-Path $Target (Join-Path '.aid' '.trash')
|
|
1361
|
+
|
|
1362
|
+
# Walk all files; move (or collect) AID-owned ones.
|
|
1363
|
+
$files = @(Get-ChildItem -LiteralPath $RDir -Recurse -File -ErrorAction SilentlyContinue |
|
|
1364
|
+
Sort-Object FullName)
|
|
1365
|
+
foreach ($f in $files) {
|
|
1366
|
+
if (& $isAidOwned $f) {
|
|
1367
|
+
if ($ListOnly) {
|
|
1368
|
+
# Emit path via Write-Output so the caller can capture with $(...).
|
|
1369
|
+
Write-Output $f.FullName
|
|
1370
|
+
$script:_MigrateRetiredCount++
|
|
1371
|
+
} else {
|
|
1372
|
+
# Compute relative path from $Target and move to trash.
|
|
1373
|
+
$rel = $f.FullName.Substring($Target.Length).TrimStart([char]'\', [char]'/')
|
|
1374
|
+
$dest = Join-Path $trashBase $rel
|
|
1375
|
+
$destDir = Split-Path $dest -Parent
|
|
1376
|
+
New-Item -ItemType Directory -Path $destDir -Force -ErrorAction SilentlyContinue | Out-Null
|
|
1377
|
+
Move-Item -LiteralPath $f.FullName -Destination $dest -Force -ErrorAction SilentlyContinue
|
|
1378
|
+
$script:_MigrateRetiredCount++
|
|
1379
|
+
if ($AidVerbose) { Write-Host "Trashed: $($f.FullName) -> $dest" }
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
if ($ListOnly) { return }
|
|
1385
|
+
|
|
1386
|
+
# Prune now-empty subdirs (deepest first).
|
|
1387
|
+
$subdirs = @(Get-ChildItem -LiteralPath $RDir -Recurse -Directory -ErrorAction SilentlyContinue)
|
|
1388
|
+
# Guard: pipeline over an empty @() returns $null on PowerShell; [System.Array]::Sort($null)
|
|
1389
|
+
# throws "Value cannot be null" when the retired root has no subdirectories (e.g. cursor
|
|
1390
|
+
# .cursor/rules/ contains only files, no nested dirs; or on idempotent re-run when the dir
|
|
1391
|
+
# is already absent). Mirrors bash rm -rf / rmdir which silently no-op on absent paths.
|
|
1392
|
+
if ($subdirs.Count -gt 0) {
|
|
1393
|
+
$subPaths = [string[]]($subdirs | ForEach-Object { $_.FullName })
|
|
1394
|
+
[System.Array]::Sort($subPaths, [System.StringComparer]::Ordinal)
|
|
1395
|
+
[System.Array]::Reverse($subPaths)
|
|
1396
|
+
foreach ($dp in $subPaths) {
|
|
1397
|
+
if (Test-Path $dp -PathType Container) {
|
|
1398
|
+
$rem = @(Get-ChildItem -LiteralPath $dp -ErrorAction SilentlyContinue) | Select-Object -First 1
|
|
1399
|
+
if (-not $rem) {
|
|
1400
|
+
Remove-Item -LiteralPath $dp -Force -ErrorAction SilentlyContinue
|
|
1401
|
+
if ($AidVerbose) { Write-Host "Retired dir: $dp" }
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
# Remove the retired root itself if now empty.
|
|
1408
|
+
if (Test-Path $RDir -PathType Container) {
|
|
1409
|
+
$rem = @(Get-ChildItem -LiteralPath $RDir -ErrorAction SilentlyContinue) | Select-Object -First 1
|
|
1410
|
+
if (-not $rem) {
|
|
1411
|
+
Remove-Item -LiteralPath $RDir -Force -ErrorAction SilentlyContinue
|
|
1412
|
+
if ($AidVerbose) { Write-Host "Retired root dir: $RDir" }
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
switch ($Tool) {
|
|
1418
|
+
'codex' {
|
|
1419
|
+
# Retired root: .agents\ (old split layout -- skills\ + aid\ lived here).
|
|
1420
|
+
& $sweepRetiredRoot (Join-Path $Target '.agents')
|
|
1421
|
+
}
|
|
1422
|
+
'cursor' {
|
|
1423
|
+
# Retired root: .cursor\rules\ (rules dir no longer in new layout).
|
|
1424
|
+
& $sweepRetiredRoot (Join-Path $Target '.cursor\rules')
|
|
1425
|
+
}
|
|
1426
|
+
'antigravity' {
|
|
1427
|
+
# Retired root: .agent\rules\ (rules dir no longer in new layout).
|
|
1428
|
+
& $sweepRetiredRoot (Join-Path $Target '.agent\rules')
|
|
1429
|
+
}
|
|
1430
|
+
# claude-code and copilot-cli have no retired roots in this migration.
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
if (-not $ListOnly -and $script:_MigrateRetiredCount -gt 0) {
|
|
1434
|
+
Write-Host " $($script:_MigrateRetiredCount) retired AID file(s) moved to .aid/.trash/"
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1270
1438
|
# ---------------------------------------------------------------------------
|
|
1271
1439
|
# Version marker
|
|
1272
1440
|
# ---------------------------------------------------------------------------
|
|
@@ -1339,16 +1507,13 @@ function Install-AidTool {
|
|
|
1339
1507
|
}
|
|
1340
1508
|
}
|
|
1341
1509
|
'codex' {
|
|
1510
|
+
# New unified layout: .codex\ only (agents, skills, aid all under .codex\).
|
|
1511
|
+
# .agents\ is the RETIRED split layout -- handled by Invoke-MigrateRetiredLayout.
|
|
1342
1512
|
$codexDir = Join-Path $StagingDir '.codex'
|
|
1343
|
-
$agentsDir = Join-Path $StagingDir '.agents'
|
|
1344
1513
|
if (Test-Path $codexDir -PathType Container) {
|
|
1345
1514
|
Copy-AidDir -SrcDir $codexDir -DstDir (Join-Path $Target '.codex') -Force $Force -AidVerbose $AidVerbose
|
|
1346
1515
|
& $collectPaths $codexDir $StagingDir $installPaths
|
|
1347
1516
|
}
|
|
1348
|
-
if (Test-Path $agentsDir -PathType Container) {
|
|
1349
|
-
Copy-AidDir -SrcDir $agentsDir -DstDir (Join-Path $Target '.agents') -Force $Force -AidVerbose $AidVerbose
|
|
1350
|
-
& $collectPaths $agentsDir $StagingDir $installPaths
|
|
1351
|
-
}
|
|
1352
1517
|
}
|
|
1353
1518
|
'cursor' {
|
|
1354
1519
|
$cursorDir = Join-Path $StagingDir '.cursor'
|
|
@@ -1389,10 +1554,33 @@ function Install-AidTool {
|
|
|
1389
1554
|
$installPaths.Add($rootAgentFile)
|
|
1390
1555
|
}
|
|
1391
1556
|
|
|
1557
|
+
# Manifest-seam entry gate (PLAN risk #3, delivery-001->002 seam).
|
|
1558
|
+
# Runs BEFORE Write-AidManifest so a contaminated bundle never writes to disk.
|
|
1559
|
+
# Assert that the new bundle's path set does NOT contain any retired roots.
|
|
1560
|
+
# If a retired path leaked into the new manifest, fail loudly -- do not prune
|
|
1561
|
+
# against a contaminated manifest (content-isolation cornerstone).
|
|
1562
|
+
$retiredRoots = @('.agents/', '.cursor/rules/', '.agent/rules/')
|
|
1563
|
+
foreach ($rr in $retiredRoots) {
|
|
1564
|
+
foreach ($p in $installPaths) {
|
|
1565
|
+
$pNorm = $p -replace '\\', '/'
|
|
1566
|
+
if ($pNorm.StartsWith($rr, [System.StringComparison]::Ordinal)) {
|
|
1567
|
+
[Console]::Error.WriteLine("ERROR: aid-install-core: manifest-seam violation: retired root '$rr' leaked into the new bundle manifest (path: $p). Aborting install to protect user content.")
|
|
1568
|
+
return 1
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1392
1573
|
# Write manifest (merge).
|
|
1393
1574
|
Write-AidManifest -ManifestPath $manifest -Tool $Tool -Version $Version `
|
|
1394
1575
|
-Paths @($installPaths) -RootEntries @($rootEntries)
|
|
1395
1576
|
|
|
1577
|
+
# Retired-root migration sweep (FR7/FR7a).
|
|
1578
|
+
# Remove AID-owned content from retired layout dirs BEFORE the normal prune,
|
|
1579
|
+
# so that old .agents\, .cursor\rules\, .agent\rules\ trees are cleaned up.
|
|
1580
|
+
# A non-zero rc from Install-AidTool is treated as a mid-commit failure (caller
|
|
1581
|
+
# prints the "re-run to heal" message); this function returns 0 (WARN-not-fail).
|
|
1582
|
+
Invoke-MigrateRetiredLayout -Target $Target -Tool $Tool -AidVerbose $AidVerbose
|
|
1583
|
+
|
|
1396
1584
|
# Prune stale AID-owned files (Pillar 2, R7).
|
|
1397
1585
|
# Build a HashSet from the new manifest path set for O(1) lookup.
|
|
1398
1586
|
$pruneSet = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::Ordinal)
|
|
@@ -1547,7 +1735,12 @@ function Get-ManifestToolList {
|
|
|
1547
1735
|
$data.tools.PSObject.Properties | ForEach-Object {
|
|
1548
1736
|
$tid = $_.Name
|
|
1549
1737
|
$t = $_.Value
|
|
1550
|
-
|
|
1738
|
+
# Guard all per-tool property accesses via PSObject.Properties['key'] first.
|
|
1739
|
+
# Set-StrictMode -Version Latest (active in this module) throws
|
|
1740
|
+
# PropertyNotFoundException when accessing a missing property directly
|
|
1741
|
+
# (e.g. $t.root_agent_files on pre-work-005 manifests that lack it).
|
|
1742
|
+
# PSObject.Properties['key'] returns $null safely for absent properties.
|
|
1743
|
+
$ver = if ($t.PSObject.Properties['version']) { [string]$t.version } else { '' }
|
|
1551
1744
|
# Determine root agent file for this tool.
|
|
1552
1745
|
$rootAgent = switch ($tid) {
|
|
1553
1746
|
'claude-code' { 'CLAUDE.md' }
|
|
@@ -1555,9 +1748,10 @@ function Get-ManifestToolList {
|
|
|
1555
1748
|
}
|
|
1556
1749
|
# Read root agent status from manifest.
|
|
1557
1750
|
$rootStatus = ''
|
|
1558
|
-
if ($t.root_agent_files) {
|
|
1559
|
-
|
|
1560
|
-
|
|
1751
|
+
$rafEntries = if ($t.PSObject.Properties['root_agent_files']) { $t.root_agent_files } else { $null }
|
|
1752
|
+
if ($rafEntries) {
|
|
1753
|
+
foreach ($entry in $rafEntries) {
|
|
1754
|
+
if ($entry.PSObject.Properties['path'] -and ($entry.path -eq $rootAgent)) {
|
|
1561
1755
|
$rootStatus = if ($entry.PSObject.Properties['status']) { $entry.status } else { 'owned' }
|
|
1562
1756
|
break
|
|
1563
1757
|
}
|
|
@@ -1761,7 +1955,7 @@ function Get-AidStatus {
|
|
|
1761
1955
|
$aidVersion = ''
|
|
1762
1956
|
try {
|
|
1763
1957
|
$data = Get-Content -LiteralPath $manifest -Raw | ConvertFrom-Json
|
|
1764
|
-
if ($data.aid_version) { $aidVersion = $data.aid_version }
|
|
1958
|
+
if ($data.PSObject.Properties['aid_version'] -and $data.aid_version) { $aidVersion = $data.aid_version }
|
|
1765
1959
|
} catch {}
|
|
1766
1960
|
|
|
1767
1961
|
# Read CLI ref version from $env:AID_HOME/VERSION.
|