[CmdletBinding()] param( [ValidateSet('All', 'ScriptoriumAI', 'HyperTwist', 'VectorShell')] [string]$Product = 'All', [string]$WorkspaceBase = 'C:\Workspaces', [switch]$ForceManifestRefresh ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' function New-StableGuid { param([Parameter(Mandatory = $true)][string]$InputString) $bytes = [System.Text.Encoding]::UTF8.GetBytes($InputString.ToLowerInvariant()) $md5 = [System.Security.Cryptography.MD5]::Create() try { $hash = $md5.ComputeHash($bytes) } finally { $md5.Dispose() } $hex = ($hash | ForEach-Object { $_.ToString('x2') }) -join '' $guidText = '{0}-{1}-{2}-{3}-{4}' -f $hex.Substring(0, 8), $hex.Substring(8, 4), $hex.Substring(12, 4), $hex.Substring(16, 4), $hex.Substring(20, 12) return "{${guidText}}".ToUpperInvariant() } function Ensure-Directory { param([Parameter(Mandatory = $true)][string]$Path) if (-not (Test-Path -LiteralPath $Path)) { New-Item -ItemType Directory -Path $Path | Out-Null } } function Write-Utf8File { param( [Parameter(Mandatory = $true)][string]$Path, [Parameter(Mandatory = $true)][AllowEmptyString()][string[]]$Lines ) $parent = Split-Path -Path $Path -Parent if ($parent) { Ensure-Directory -Path $parent } [System.IO.File]::WriteAllLines($Path, $Lines, [System.Text.UTF8Encoding]::new($false)) } function New-RelativePath { param( [Parameter(Mandatory = $true)][string]$BasePath, [Parameter(Mandatory = $true)][string]$TargetPath ) $baseResolved = (Resolve-Path -LiteralPath $BasePath).Path.TrimEnd('\') + '\' $targetResolved = (Resolve-Path -LiteralPath $TargetPath).Path $baseUri = [System.Uri]$baseResolved $targetUri = [System.Uri]$targetResolved $relativeUri = $baseUri.MakeRelativeUri($targetUri) return [System.Uri]::UnescapeDataString($relativeUri.ToString()).Replace('/', '\') } function New-MirrorPath { param( [Parameter(Mandatory = $true)][string]$MirrorRoot, [Parameter(Mandatory = $true)][string]$Owner, [Parameter(Mandatory = $true)][string]$Repo ) return Join-Path (Join-Path $MirrorRoot $Owner) $Repo } function New-RepositoryEntry { param( [Parameter(Mandatory = $true)][string]$Key, [Parameter(Mandatory = $true)][string]$Name, [string]$RepoUrl, [Parameter(Mandatory = $true)][string]$LocalPath, [Parameter(Mandatory = $true)][string]$Purpose, [Parameter(Mandatory = $true)][string]$Language, [Parameter(Mandatory = $true)][string]$IncludeMode, [Parameter(Mandatory = $true)][string]$Storage, [bool]$Active = $true, [object[]]$ProjectFiles = @(), [string]$License = '', [string]$LicenseStatus = '', [string]$MirrorClass = '', [string]$WorkflowRole = '', [bool]$ModelAAllowedSourceAccess = $true, [bool]$ModelBAllowedSourceAccess = $true, [string]$HandoffDoc = '', [string[]]$Notes = @() ) return [ordered]@{ key = $Key name = $Name repoUrl = $RepoUrl localPath = $LocalPath purpose = $Purpose language = $Language includeMode = $IncludeMode storage = $Storage active = $Active projectFiles = $ProjectFiles license = $License licenseStatus = $LicenseStatus mirrorClass = $MirrorClass workflowRole = $WorkflowRole modelAAllowedSourceAccess = $ModelAAllowedSourceAccess modelBAllowedSourceAccess = $ModelBAllowedSourceAccess handoffDoc = $HandoffDoc notes = $Notes } } function New-ExternalMirrorEntry { param( [Parameter(Mandatory = $true)][string]$Key, [Parameter(Mandatory = $true)][string]$Name, [Parameter(Mandatory = $true)][string]$RepoUrl, [Parameter(Mandatory = $true)][string]$MirrorRoot, [Parameter(Mandatory = $true)][string]$Owner, [Parameter(Mandatory = $true)][string]$Repo, [Parameter(Mandatory = $true)][string]$Purpose, [Parameter(Mandatory = $true)][string]$Language, [Parameter(Mandatory = $true)][string]$License, [Parameter(Mandatory = $true)][string]$LicenseStatus, [Parameter(Mandatory = $true)][string]$MirrorClass, [Parameter(Mandatory = $true)][string]$WorkflowRole, [string]$HandoffDoc = '', [string[]]$Notes = @(), [bool]$Active = $true ) $localPath = New-MirrorPath -MirrorRoot $MirrorRoot -Owner $Owner -Repo $Repo $modelBAllowed = $true if ($MirrorClass -eq 'restrictive') { $modelBAllowed = $false } return New-RepositoryEntry -Key $Key -Name $Name -RepoUrl $RepoUrl -LocalPath $localPath -Purpose $Purpose -Language $Language -IncludeMode 'root' -Storage 'external-mirror' -Active:$Active -ProjectFiles @() -License $License -LicenseStatus $LicenseStatus -MirrorClass $MirrorClass -WorkflowRole $WorkflowRole -ModelAAllowedSourceAccess $true -ModelBAllowedSourceAccess $modelBAllowed -HandoffDoc $HandoffDoc -Notes $Notes } function Get-HyperTwistMirrorEntries { param( [Parameter(Mandatory = $true)][string]$PermissiveMirrorsRoot, [Parameter(Mandatory = $true)][string]$RestrictiveMirrorsRoot, [Parameter(Mandatory = $true)][string]$CleanRoomSpecsRoot ) $entries = [System.Collections.Generic.List[object]]::new() $entries.Add((New-ExternalMirrorEntry -Key 'hactarce-hyperspeedcube' -Name 'HactarCE - Hyperspeedcube' -RepoUrl 'https://github.com/HactarCE/Hyperspeedcube.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'HactarCE' -Repo 'Hyperspeedcube' -Purpose 'Runtime-side hypercubing foundation anchor and engine donor.' -Language 'Rust' -License 'MIT OR Apache-2.0' -LicenseStatus 'dual-permissive' -MirrorClass 'permissive' -WorkflowRole 'foundation-donor')) $entries.Add((New-ExternalMirrorEntry -Key 'cubing-cubing-js' -Name 'cubing - cubing.js' -RepoUrl 'https://github.com/cubing/cubing.js.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'cubing' -Repo 'cubing.js' -Purpose 'Canonical classic-cubing semantics, rendering, scramble/search, and smartcube donor consumed through bounded MPL-aware dependency seams.' -Language 'TypeScript' -License 'MPL-2.0 OR GPL-3.0-or-later' -LicenseStatus 'dual-weak-copyleft-library-use-ok' -MirrorClass 'permissive' -WorkflowRole 'boundary-sensitive-donor' -Notes @('Preferred use is package dependency, adapter boundary, or bounded companion surface rather than a deep private fork.', 'If HyperTwist modifies cubing.js source files directly, those modifications must be published.', 'Preserve vendored-code and third-party notices documented by the upstream repo.'))) $entries.Add((New-ExternalMirrorEntry -Key 'kkoomen-qbr' -Name 'kkoomen - qbr' -RepoUrl 'https://github.com/kkoomen/qbr.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'kkoomen' -Repo 'qbr' -Purpose 'Primary classic-cube live recognition and calibration donor.' -Language 'Python' -License 'MIT' -LicenseStatus 'permissive' -MirrorClass 'permissive' -WorkflowRole 'foundation-donor')) $entries.Add((New-ExternalMirrorEntry -Key 'vivaansinghvi07-rubix-cube-solver' -Name 'vivaansinghvi07 - rubix-cube-solver' -RepoUrl 'https://github.com/vivaansinghvi07/rubix-cube-solver.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'vivaansinghvi07' -Repo 'rubix-cube-solver' -Purpose 'Secondary browser/video reconstruction companion for the vision stack.' -Language 'JavaScript' -License 'MIT' -LicenseStatus 'permissive' -MirrorClass 'permissive' -WorkflowRole 'parallel-foundation-donor')) $entries.Add((New-ExternalMirrorEntry -Key 'cahidenes-rubiks-cube-solver' -Name 'cahidenes - rubiks-cube-solver' -RepoUrl 'https://github.com/cahidenes/rubiks-cube-solver.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'cahidenes' -Repo 'rubiks-cube-solver' -Purpose 'Recognition heuristics and solver-bridge donor.' -Language 'Python' -License 'MIT' -LicenseStatus 'permissive' -MirrorClass 'permissive' -WorkflowRole 'strategic-donor')) $entries.Add((New-ExternalMirrorEntry -Key 'tentone-rubix-solver' -Name 'tentone - rubix-solver' -RepoUrl 'https://github.com/tentone/rubix-solver.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'tentone' -Repo 'rubix-solver' -Purpose 'C++ and OpenCV perception-heuristics donor.' -Language 'C++' -License 'MIT' -LicenseStatus 'permissive' -MirrorClass 'permissive' -WorkflowRole 'strategic-donor')) $entries.Add((New-ExternalMirrorEntry -Key 'yakupbilen-drl-rubiks-cube' -Name 'yakupbilen - drl-rubiks-cube' -RepoUrl 'https://github.com/yakupbilen/drl-rubiks-cube.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'yakupbilen' -Repo 'drl-rubiks-cube' -Purpose 'RL and search experimentation bench for the coaching stack.' -Language 'Python' -License 'MIT' -LicenseStatus 'permissive' -MirrorClass 'permissive' -WorkflowRole 'training-bench')) $entries.Add((New-ExternalMirrorEntry -Key 'brianpeiris-riftsketch' -Name 'brianpeiris - RiftSketch' -RepoUrl 'https://github.com/brianpeiris/RiftSketch.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'brianpeiris' -Repo 'RiftSketch' -Purpose 'XR interaction reference and immersion donor.' -Language 'JavaScript' -License 'MIT' -LicenseStatus 'permissive' -MirrorClass 'permissive' -WorkflowRole 'reference-donor')) $entries.Add((New-ExternalMirrorEntry -Key 'nuilab-code-vr' -Name 'NuiLab - code-vr' -RepoUrl 'https://github.com/NuiLab/code-vr.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'NuiLab' -Repo 'code-vr' -Purpose 'XR pedagogy and onboarding donor.' -Language 'Rust' -License 'MIT' -LicenseStatus 'permissive' -MirrorClass 'permissive' -WorkflowRole 'reference-donor')) $entries.Add((New-ExternalMirrorEntry -Key 'hypercubers-hypercubing-xyz' -Name 'Hypercubers - hypercubing.xyz' -RepoUrl 'https://github.com/Hypercubers/hypercubing.xyz.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'Hypercubers' -Repo 'hypercubing.xyz' -Purpose 'Knowledge, curriculum, notation, and community donor for the hypercubing half of HyperTwist.' -Language 'Markdown/Python' -License 'MIT' -LicenseStatus 'permissive' -MirrorClass 'permissive' -WorkflowRole 'knowledge-foundation-donor' -Notes @('Use the GitHub source repo as canonical for active evaluation.', 'The local downloaded C:\\HyperTwist\\Hypercubing.xyz snapshot is intentionally excluded from active evaluation for now.'))) $entries.Add((New-ExternalMirrorEntry -Key 'roice3-magictile' -Name 'roice3 - MagicTile' -RepoUrl 'https://github.com/roice3/MagicTile.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'roice3' -Repo 'MagicTile' -Purpose 'Top-tier non-Euclidean geometry, topology, and generalized puzzle-construction donor for the hypercubing expansion lane.' -Language 'C#' -License 'MIT' -LicenseStatus 'permissive' -MirrorClass 'permissive' -WorkflowRole 'strategic-donor' -Notes @('Treat as a geometry/topology donor under the Hyperspeedcube anchor, not as the main product shell.', 'The underlying R3 tiling and topology layers are the primary salvage target, not the WinForms/OpenTK shell.'))) $entries.Add((New-ExternalMirrorEntry -Key 'cutelyaware-magiccube4d' -Name 'cutelyaware - magiccube4d' -RepoUrl 'https://github.com/cutelyaware/magiccube4d.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'cutelyaware' -Repo 'magiccube4d' -Purpose 'Legacy 4D hypercubing interaction, history, macro, and generic puzzle-description donor with explicit attribution and notice obligations.' -Language 'Java' -License 'Custom permissive attribution-request license' -LicenseStatus 'custom-permissive-attribution' -MirrorClass 'permissive' -WorkflowRole 'strategic-donor' -Notes @('Preserve upstream attribution in natural product-facing places such as About dialogs, source comments, or third-party notices, and include the project link http://superliminal.com/cube/cube.htm.', 'Primary salvage targets are 4D interaction grammar, history and macro semantics, generic puzzle boundaries, and proven legacy hypercubing workflow.', 'Track the provenance note in com\\donhatchsw\\util\\MyMath.java in project license records, but do not treat it as a standing blocker.'))) $entries.Add((New-ExternalMirrorEntry -Key 'roice3-magic120cell' -Name 'roice3 - Magic120Cell' -RepoUrl 'https://github.com/roice3/Magic120Cell.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'roice3' -Repo 'Magic120Cell' -Purpose 'Specialized 4D interaction, visibility, and puzzle-specific UX donor for the hypercubing expansion family.' -Language 'C++/C#' -License 'MIT' -LicenseStatus 'permissive' -MirrorClass 'permissive' -WorkflowRole 'strategic-donor' -Notes @('Treat as a specialized descendant in the MagicTile family, not as the main runtime or geometry foundation.', 'Primary salvage targets are 4D interaction grammar, visibility modes, twist-history UX, and advanced teaching-surface behavior.'))) $entries.Add((New-ExternalMirrorEntry -Key 'roice3-magiccube5d' -Name 'roice3 - MagicCube5D' -RepoUrl 'https://github.com/roice3/MagicCube5D.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'roice3' -Repo 'MagicCube5D' -Purpose 'Specialized 5D cube-family interaction, progress, macro, and teaching-surface donor.' -Language 'C++/C#' -License 'MIT' -LicenseStatus 'permissive' -MirrorClass 'permissive' -WorkflowRole 'strategic-donor' -Notes @('Treat as a specialized cube-family donor below Hyperspeedcube and MagicTile, not as a product-shell foundation.', 'Primary salvage targets are 5D interaction grammar, macros, solved-progress reporting, sticker/cubie finding, slice handling, and advanced teaching-surface behavior.'))) $entries.Add((New-ExternalMirrorEntry -Key 'poliva-cubedex' -Name 'poliva - cubedex' -RepoUrl 'https://github.com/poliva/cubedex.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'poliva' -Repo 'cubedex' -Purpose 'Smartcube-aware training UX and offline-first donor.' -Language 'TypeScript' -License 'MIT' -LicenseStatus 'permissive' -MirrorClass 'permissive' -WorkflowRole 'strategic-donor')) $entries.Add((New-ExternalMirrorEntry -Key 'tao-yu-alg-trainer' -Name 'tao-yu - Alg-Trainer' -RepoUrl 'https://github.com/tao-yu/Alg-Trainer.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'tao-yu' -Repo 'Alg-Trainer' -Purpose 'Conceptual training-lane foundation candidate and heavy donor.' -Language 'JavaScript' -License 'MIT' -LicenseStatus 'permissive' -MirrorClass 'permissive' -WorkflowRole 'foundation-donor')) $entries.Add((New-ExternalMirrorEntry -Key 'lykos-cube-trainer' -Name 'Lykos - cube_trainer' -RepoUrl 'https://github.com/Lykos/cube_trainer.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'Lykos' -Repo 'cube_trainer' -Purpose 'Persistent training-domain and coaching-backend donor.' -Language 'Ruby' -License 'MIT' -LicenseStatus 'permissive' -MirrorClass 'permissive' -WorkflowRole 'strategic-donor')) $entries.Add((New-ExternalMirrorEntry -Key 'newyork-anthonyng-rubiks-cross-trainer' -Name 'newyork-anthonyng - rubiks-cross-trainer' -RepoUrl 'https://github.com/newyork-anthonyng/rubiks-cross-trainer.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'newyork-anthonyng' -Repo 'rubiks-cross-trainer' -Purpose 'Focused CFOP cross-drill donor.' -Language 'JavaScript' -License 'MIT' -LicenseStatus 'permissive' -MirrorClass 'permissive' -WorkflowRole 'focused-donor')) $entries.Add((New-ExternalMirrorEntry -Key 'abunickabhi-5style-trainer' -Name 'abunickabhi - 5style-Trainer' -RepoUrl 'https://github.com/abunickabhi/5style-Trainer.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'abunickabhi' -Repo '5style-Trainer' -Purpose 'Focused advanced-training donor for 5-style and BLD practice.' -Language 'JavaScript' -License 'MIT' -LicenseStatus 'permissive' -MirrorClass 'permissive' -WorkflowRole 'focused-donor')) $entries.Add((New-ExternalMirrorEntry -Key 'aarav2709-kubetimr' -Name 'Aarav2709 - KubeTimr' -RepoUrl 'https://github.com/Aarav2709/KubeTimr.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'Aarav2709' -Repo 'KubeTimr' -Purpose 'Focused timer, splits, stats, and local-practice donor.' -Language 'TypeScript' -License 'MIT' -LicenseStatus 'permissive' -MirrorClass 'permissive' -WorkflowRole 'focused-donor')) $entries.Add((New-ExternalMirrorEntry -Key 'amontesl-codexr' -Name 'aMonteSl - CodeXR' -RepoUrl 'https://github.com/aMonteSl/CodeXR.git' -MirrorRoot $RestrictiveMirrorsRoot -Owner 'aMonteSl' -Repo 'CodeXR' -Purpose 'Off-domain XR reference under strong copyleft; keep only as restrictive reference material.' -Language 'TypeScript' -License 'GPL-3.0' -LicenseStatus 'strong-copyleft' -MirrorClass 'restrictive' -WorkflowRole 'reference-only' -Notes @('Model B must not read this mirror.'))) $entries.Add((New-ExternalMirrorEntry -Key 'avikaufman-rubix-cube-trainer' -Name 'AviKaufman - Rubix-cube-trainer' -RepoUrl 'https://github.com/AviKaufman/Rubix-cube-trainer.git' -MirrorRoot $RestrictiveMirrorsRoot -Owner 'AviKaufman' -Repo 'Rubix-cube-trainer' -Purpose 'Clean-room pedagogy benchmark for beginner-method lesson flow.' -Language 'TypeScript' -License 'All rights reserved' -LicenseStatus 'all-rights-reserved' -MirrorClass 'restrictive' -WorkflowRole 'clean-room-candidate' -HandoffDoc (Join-Path $CleanRoomSpecsRoot 'avi-kaufman-rubix-cube-trainer.model-a.md') -Notes @('Model B must not read this mirror.', 'Use only the scrubbed Model A dossier and acceptance tests for later implementation.'))) $entries.Add((New-ExternalMirrorEntry -Key 'ambisinister-blindsolve' -Name 'ambisinister - blindsolve' -RepoUrl 'https://github.com/ambisinister/blindsolve.git' -MirrorRoot $RestrictiveMirrorsRoot -Owner 'ambisinister' -Repo 'blindsolve' -Purpose 'Clean-room BLD memo benchmark for hide-reveal and letter-pair rehearsal flow.' -Language 'Python' -License 'No explicit license found' -LicenseStatus 'license-missing' -MirrorClass 'restrictive' -WorkflowRole 'clean-room-candidate' -HandoffDoc (Join-Path $CleanRoomSpecsRoot 'ambisinister-blindsolve.model-a.md') -Notes @('Model B must not read this mirror.', 'Use only the scrubbed Model A dossier and acceptance tests for later implementation.'))) $entries.Add((New-ExternalMirrorEntry -Key 'alinen-cube' -Name 'alinen - cube' -RepoUrl 'https://github.com/alinen/cube.git' -MirrorRoot $RestrictiveMirrorsRoot -Owner 'alinen' -Repo 'cube' -Purpose 'Clean-room planner benchmark for modular state, task, and step-planner behavior.' -Language 'C#' -License 'No explicit license found' -LicenseStatus 'license-missing' -MirrorClass 'restrictive' -WorkflowRole 'clean-room-candidate' -HandoffDoc (Join-Path $CleanRoomSpecsRoot 'alinen-cube.model-a.md') -Notes @('Model B must not read this mirror.', 'Use only the scrubbed Model A dossier and acceptance tests for later implementation.'))) $entries.Add((New-ExternalMirrorEntry -Key 'brownan-rubiks-cube-solver' -Name 'brownan - Rubiks-Cube-Solver' -RepoUrl 'https://github.com/brownan/Rubiks-Cube-Solver.git' -MirrorRoot $RestrictiveMirrorsRoot -Owner 'brownan' -Repo 'Rubiks-Cube-Solver' -Purpose 'Primary benchmark oracle and approved clean-room target for a future first-party optimal-solver lane.' -Language 'C' -License 'GPL-3.0' -LicenseStatus 'strong-copyleft' -MirrorClass 'restrictive' -WorkflowRole 'clean-room-candidate' -HandoffDoc (Join-Path $CleanRoomSpecsRoot 'brownan-rubiks-cube-solver.model-a.md') -Notes @('Retain this repo as a correctness and optimality oracle even if a first-party solver is later implemented.', 'Model B must not read this mirror.'))) $entries.Add((New-ExternalMirrorEntry -Key 'mathewkj2048-rubiks-cube-simulator' -Name 'MathewKJ2048 - Rubiks-cube-simulator' -RepoUrl 'https://github.com/MathewKJ2048/Rubiks-cube-simulator.git' -MirrorRoot $RestrictiveMirrorsRoot -Owner 'MathewKJ2048' -Repo 'Rubiks-cube-simulator' -Purpose 'Low-priority clean-room simulator benchmark for lightweight move grammar, history, and timed playback behavior.' -Language 'Java' -License 'GPL-3.0' -LicenseStatus 'strong-copyleft' -MirrorClass 'restrictive' -WorkflowRole 'clean-room-candidate' -HandoffDoc (Join-Path $CleanRoomSpecsRoot 'mathewkj2048-rubiks-cube-simulator.model-a.md') -Notes @('Model B must not read this mirror.', 'Use only the scrubbed Model A dossier and acceptance tests for later implementation.'))) $entries.Add((New-ExternalMirrorEntry -Key 'vwcwong-cubesim' -Name 'vwcwong - CubeSim' -RepoUrl 'https://github.com/vwcwong/CubeSim.git' -MirrorRoot $RestrictiveMirrorsRoot -Owner 'vwcwong' -Repo 'CubeSim' -Purpose 'Clean-room state/history/simulator benchmark for renderer-independent cube-domain design.' -Language 'Python' -License 'GPL-3.0' -LicenseStatus 'strong-copyleft' -MirrorClass 'restrictive' -WorkflowRole 'clean-room-candidate' -HandoffDoc (Join-Path $CleanRoomSpecsRoot 'vwcwong-cubesim.model-a.md') -Notes @('Model B must not read this mirror.', 'Use only the scrubbed Model A dossier and acceptance tests for later implementation.'))) return $entries } function Get-ProductConfig { param( [Parameter(Mandatory = $true)][ValidateSet('ScriptoriumAI', 'HyperTwist', 'VectorShell')][string]$Name, [Parameter(Mandatory = $true)][string]$WorkspaceBasePath ) $workspaceRoot = Join-Path $WorkspaceBasePath $Name $mirrorsRoot = Join-Path $workspaceRoot 'mirrors' $permissiveMirrorsRoot = Join-Path $mirrorsRoot 'permissive' $restrictiveMirrorsRoot = Join-Path $mirrorsRoot 'restrictive' $cleanRoomSpecsRoot = Join-Path $workspaceRoot 'clean-room-specs' $implementationWorkspacesRoot = Join-Path $workspaceRoot 'implementation-workspaces' $modelBWorkspacesRoot = Join-Path $implementationWorkspacesRoot 'model-b' $scratchWorkspacesRoot = Join-Path $implementationWorkspacesRoot 'scratch' $generatedRoot = Join-Path $workspaceRoot 'generated' $cacheRoot = Join-Path $workspaceRoot 'cache' $plannedAppRoot = Join-Path $workspaceRoot 'app' switch ($Name) { 'ScriptoriumAI' { return [ordered]@{ Name = $Name RepoRoot = 'C:\ScriptoriumAI' DocsRoot = 'C:\ScriptoriumAI\docs' WorkspaceRoot = $workspaceRoot PlannedAppRoot = $plannedAppRoot MirrorsRoot = $mirrorsRoot PermissiveMirrorsRoot = $permissiveMirrorsRoot RestrictiveMirrorsRoot = $restrictiveMirrorsRoot CleanRoomSpecsRoot = $cleanRoomSpecsRoot ImplementationWorkspacesRoot = $implementationWorkspacesRoot ModelBWorkspacesRoot = $modelBWorkspacesRoot ScratchWorkspacesRoot = $scratchWorkspacesRoot GeneratedRoot = $generatedRoot CacheRoot = $cacheRoot RepositoryEntries = @( (New-RepositoryEntry -Key 'app' -Name 'ScriptoriumAI App' -RepoUrl '' -LocalPath 'C:\ScriptoriumAI' -Purpose 'First-party product repository.' -Language 'mixed' -IncludeMode 'project' -Storage 'app-repo' -ProjectFiles @( [ordered]@{ name = 'ScriptoriumAI'; path = 'C:\ScriptoriumAI\ScriptoriumAI.vcxproj'; kind = 'cpp' }, [ordered]@{ name = 'scriptoriumai-ui'; path = 'C:\ScriptoriumAI\scriptoriumai-ui\scriptoriumai-ui.esproj'; kind = 'js' }, [ordered]@{ name = 'ScriptoriumAI-Server'; path = 'C:\ScriptoriumAI\ScriptoriumAI-Server\ScriptoriumAI-Server.esproj'; kind = 'js' } ) -License 'first-party' -LicenseStatus 'first-party' -MirrorClass 'app-repo' -WorkflowRole 'first-party' -ModelAAllowedSourceAccess $true -ModelBAllowedSourceAccess $true), (New-RepositoryEntry -Key 'gitnexus' -Name 'GitNexus' -RepoUrl 'https://github.com/abhigyanpatwari/GitNexus.git' -LocalPath 'C:\ScriptoriumAI\mirrors\GitNexus' -Purpose 'Pinned internal code-intelligence tooling retained as an in-repo submodule.' -Language 'TypeScript' -IncludeMode 'root' -Storage 'submodule' -License 'unknown' -LicenseStatus 'pending' -MirrorClass 'in-repo' -WorkflowRole 'pinned-dependency'), (New-RepositoryEntry -Key 'claude-skills' -Name 'claude-skills' -RepoUrl 'https://github.com/alirezarezvani/claude-skills.git' -LocalPath 'C:\ScriptoriumAI\mirrors\claude-skills' -Purpose 'Pinned skills corpus used by the skills-management flow.' -Language 'Markdown' -IncludeMode 'root' -Storage 'submodule' -License 'unknown' -LicenseStatus 'pending' -MirrorClass 'in-repo' -WorkflowRole 'pinned-dependency'), (New-RepositoryEntry -Key 'awesome-claude-skills' -Name 'awesome-claude-skills' -RepoUrl 'https://github.com/ComposioHQ/awesome-claude-skills.git' -LocalPath 'C:\ScriptoriumAI\mirrors\awesome-claude-skills' -Purpose 'Pinned scout source for skill ingestion.' -Language 'Markdown' -IncludeMode 'root' -Storage 'submodule' -License 'unknown' -LicenseStatus 'pending' -MirrorClass 'in-repo' -WorkflowRole 'pinned-dependency'), (New-RepositoryEntry -Key 'latexocr' -Name 'latexocr' -RepoUrl 'https://github.com/lukas-blecher/LaTeX-OCR.git' -LocalPath 'C:\ScriptoriumAI\mirrors\latexocr' -Purpose 'OCR runtime used by equation-to-LaTeX services.' -Language 'Python' -IncludeMode 'root' -Storage 'submodule' -License 'unknown' -LicenseStatus 'pending' -MirrorClass 'in-repo' -WorkflowRole 'pinned-dependency'), (New-RepositoryEntry -Key 'pdfdelta' -Name 'pdfdelta' -RepoUrl 'https://github.com/balajisivaraman/pdfdelta.git' -LocalPath 'C:\ScriptoriumAI\mirrors\pdfdelta' -Purpose 'PDF differencing assets and examples referenced by runboard flows.' -Language 'mixed' -IncludeMode 'root' -Storage 'submodule' -License 'unknown' -LicenseStatus 'pending' -MirrorClass 'in-repo' -WorkflowRole 'pinned-dependency'), (New-RepositoryEntry -Key 'pdfmd' -Name 'pdfmd' -RepoUrl 'https://github.com/reez/pdfmd.git' -LocalPath 'C:\ScriptoriumAI\mirrors\pdfmd' -Purpose 'PDF-to-Markdown tooling kept pinned for intake and tests.' -Language 'mixed' -IncludeMode 'root' -Storage 'submodule' -License 'unknown' -LicenseStatus 'pending' -MirrorClass 'in-repo' -WorkflowRole 'pinned-dependency'), (New-RepositoryEntry -Key 'code2video' -Name 'code2video' -RepoUrl 'https://github.com/itsjavi/code2video.git' -LocalPath 'C:\ScriptoriumAI\mirrors\code2video' -Purpose 'Code-to-video transform engine referenced by server services.' -Language 'mixed' -IncludeMode 'root' -Storage 'submodule' -License 'unknown' -LicenseStatus 'pending' -MirrorClass 'in-repo' -WorkflowRole 'pinned-dependency'), (New-RepositoryEntry -Key 'paper2video' -Name 'paper2video' -RepoUrl 'https://github.com/harish-03/paper2video.git' -LocalPath 'C:\ScriptoriumAI\mirrors\paper2video' -Purpose 'Paper-to-video transform engine referenced by server services.' -Language 'mixed' -IncludeMode 'root' -Storage 'submodule' -License 'unknown' -LicenseStatus 'pending' -MirrorClass 'in-repo' -WorkflowRole 'pinned-dependency'), (New-RepositoryEntry -Key 'liquid-glass-react' -Name 'liquid-glass-react' -RepoUrl 'https://github.com/rdev/liquid-glass-react.git' -LocalPath 'C:\ScriptoriumAI\mirrors\liquid-glass-react' -Purpose 'Pinned visual donor for the glass UI implementation.' -Language 'TypeScript' -IncludeMode 'root' -Storage 'submodule' -License 'unknown' -LicenseStatus 'pending' -MirrorClass 'in-repo' -WorkflowRole 'pinned-dependency') ) } } 'HyperTwist' { $entries = [System.Collections.Generic.List[object]]::new() $entries.Add((New-RepositoryEntry -Key 'app' -Name 'HyperTwist App' -RepoUrl '' -LocalPath 'C:\HyperTwist' -Purpose 'First-party Unreal product repository. Open C:\HyperTwist\UnrealHyperTwist\UnrealHyperTwist.uproject; generated Visual Studio project files are disposable host state.' -Language 'mixed' -IncludeMode 'root' -Storage 'app-repo' -ProjectFiles @() -License 'first-party' -LicenseStatus 'first-party' -MirrorClass 'app-repo' -WorkflowRole 'first-party' -ModelAAllowedSourceAccess $true -ModelBAllowedSourceAccess $true -Notes @('Canonical IDE/build entry: C:\HyperTwist\UnrealHyperTwist\UnrealHyperTwist.uproject.', 'Do not regenerate the retired repo-root catch-all HyperTwist.vcxproj.'))) foreach ($entry in (Get-HyperTwistMirrorEntries -PermissiveMirrorsRoot $permissiveMirrorsRoot -RestrictiveMirrorsRoot $restrictiveMirrorsRoot -CleanRoomSpecsRoot $cleanRoomSpecsRoot)) { $entries.Add($entry) } return [ordered]@{ Name = $Name RepoRoot = 'C:\HyperTwist' DocsRoot = 'C:\HyperTwist\docs' WorkspaceRoot = $workspaceRoot PlannedAppRoot = $plannedAppRoot MirrorsRoot = $mirrorsRoot PermissiveMirrorsRoot = $permissiveMirrorsRoot RestrictiveMirrorsRoot = $restrictiveMirrorsRoot CleanRoomSpecsRoot = $cleanRoomSpecsRoot ImplementationWorkspacesRoot = $implementationWorkspacesRoot ModelBWorkspacesRoot = $modelBWorkspacesRoot ScratchWorkspacesRoot = $scratchWorkspacesRoot GeneratedRoot = $generatedRoot CacheRoot = $cacheRoot RepositoryEntries = @($entries) } } 'VectorShell' { return [ordered]@{ Name = $Name RepoRoot = 'C:\VectorShell' DocsRoot = 'C:\VectorShell\docs' WorkspaceRoot = $workspaceRoot PlannedAppRoot = $plannedAppRoot MirrorsRoot = $mirrorsRoot PermissiveMirrorsRoot = $permissiveMirrorsRoot RestrictiveMirrorsRoot = $restrictiveMirrorsRoot CleanRoomSpecsRoot = $cleanRoomSpecsRoot ImplementationWorkspacesRoot = $implementationWorkspacesRoot ModelBWorkspacesRoot = $modelBWorkspacesRoot ScratchWorkspacesRoot = $scratchWorkspacesRoot GeneratedRoot = $generatedRoot CacheRoot = $cacheRoot RepositoryEntries = @( (New-RepositoryEntry -Key 'app' -Name 'VectorShell App' -RepoUrl '' -LocalPath 'C:\VectorShell' -Purpose 'First-party product repository.' -Language 'mixed' -IncludeMode 'project' -Storage 'app-repo' -ProjectFiles @( [ordered]@{ name = 'VectorShell'; path = 'C:\VectorShell\VectorShell.vcxproj'; kind = 'cpp' } ) -License 'first-party' -LicenseStatus 'first-party' -MirrorClass 'app-repo' -WorkflowRole 'first-party' -ModelAAllowedSourceAccess $true -ModelBAllowedSourceAccess $true) ) } } } } function New-ManifestObject { param([Parameter(Mandatory = $true)][hashtable]$Config) return [ordered]@{ schemaVersion = 2 productName = $Config.Name generatedAtUtc = (Get-Date).ToUniversalTime().ToString('o') workspace = [ordered]@{ workspaceRoot = $Config.WorkspaceRoot currentAppRoot = $Config.RepoRoot plannedAppRoot = $Config.PlannedAppRoot mirrorsRoot = $Config.MirrorsRoot permissiveMirrorsRoot = $Config.PermissiveMirrorsRoot restrictiveMirrorsRoot = $Config.RestrictiveMirrorsRoot cleanRoomSpecsRoot = $Config.CleanRoomSpecsRoot implementationWorkspacesRoot = $Config.ImplementationWorkspacesRoot modelBWorkspacesRoot = $Config.ModelBWorkspacesRoot scratchWorkspacesRoot = $Config.ScratchWorkspacesRoot generatedRoot = $Config.GeneratedRoot cacheRoot = $Config.CacheRoot } policy = [ordered]@{ defaultIncludeMode = 'root' promoteToProjectWhenEditing = $true shippingSource = 'currentAppRoot' moveAppIntoWorkspaceDeferred = $true restrictiveMirrorsModelAOnly = $true modelBMayReadRestrictiveMirrors = $false benchmarkOracleDefaultUsage = 'correctness-reference-and-test-oracle' cleanRoomDefaultUsage = 'model-a-spec-then-isolated-model-b-implementation' notes = @( 'The live application repository remains at currentAppRoot for now.', 'plannedAppRoot exists as the future migration target once a deliberate move is approved.', 'Use storage=submodule only for repos that must remain pinned inside the product repo.', 'Use storage=external-mirror for large intake queues and promote to project mode only when actively editing.', 'Restrictive mirrors are for Model A research custody only. Model B must consume scrubbed handoff artifacts instead of source.', 'Benchmark oracles may remain restrictive. Their main value is correctness, regression, and quality comparison rather than code reuse.' ) } repositories = $Config.RepositoryEntries } } function Write-WorkspaceReadmes { param([Parameter(Mandatory = $true)][hashtable]$Config) Write-Utf8File -Path (Join-Path $Config.WorkspaceRoot 'README.md') -Lines @( "# $($Config.Name) external workspace", '', "This workspace is the external mirror and handoff control plane for $($Config.Name).", '', "Current live repo: $($Config.RepoRoot)", "Planned future app folder: $($Config.PlannedAppRoot)", "Permissive mirrors root: $($Config.PermissiveMirrorsRoot)", "Restrictive mirrors root: $($Config.RestrictiveMirrorsRoot)", "Clean-room specs root: $($Config.CleanRoomSpecsRoot)", "Implementation workspaces root: $($Config.ImplementationWorkspacesRoot)", '', 'Operating rules:', '- Keep first-party shipping code in the live app repo until an explicit migration is approved.', '- Place permissive donor repos under mirrors\permissive.', '- Place GPL, no-license, and all-rights-reserved repos under mirrors\restrictive.', '- Treat clean-room-specs as the safe handoff zone from Model A to Model B.', '- Treat implementation-workspaces\model-b as the isolated implementation surface that must not read restrictive mirrors.' ) Write-Utf8File -Path (Join-Path $Config.PlannedAppRoot 'README.md') -Lines @( "# $($Config.Name) app placeholder", '', "This folder is reserved for a future migration of the live repo into the external workspace layout.", '', "Current live repo path: $($Config.RepoRoot)", '', 'Do not move the repository here implicitly. The migration should be a separate, deliberate step.' ) Write-Utf8File -Path (Join-Path $Config.MirrorsRoot 'README.md') -Lines @( "# $($Config.Name) mirrors", '', 'This folder is split by exposure and licensing posture:', '- permissive: MIT, Apache, BSD, or otherwise acceptable direct donor mirrors', '- restrictive: GPL, no-license, all-rights-reserved, or otherwise source-restricted mirrors', '', 'Mirror rules:', '- Mirror repos by owner\repo to avoid collisions.', '- Keep manifest metadata current; do not treat ad hoc clone folders as authoritative.', '- Restrictive mirrors are Model A only.' ) Write-Utf8File -Path (Join-Path $Config.PermissiveMirrorsRoot 'README.md') -Lines @( "# $($Config.Name) permissive mirrors", '', 'Use this root for repos whose source may be read during normal donor evaluation and, when appropriate, direct integration work.', '', 'Default policy:', '- includeMode = root', '- storage = external-mirror', '- Model A source access = allowed', '- Model B source access = allowed if the repo remains in the permissive donor lane' ) Write-Utf8File -Path (Join-Path $Config.RestrictiveMirrorsRoot 'README.md') -Lines @( "# $($Config.Name) restrictive mirrors", '', 'Use this root for GPL, no-license, all-rights-reserved, and otherwise source-restricted mirrors.', '', 'Critical policy:', '- Model A source access = allowed for research, benchmark-oracle work, and clean-room specification work', '- Model B source access = forbidden', '- Later implementation must consume only scrubbed handoff artifacts from clean-room-specs' ) Write-Utf8File -Path (Join-Path $Config.CleanRoomSpecsRoot 'README.md') -Lines @( "# $($Config.Name) clean-room specs", '', 'This root is the safe handoff zone for scrubbed Model A outputs.', '', 'Expected contents:', '- behavior specifications', '- architecture notes at a black-box level', '- acceptance tests', '- no-copy guardrails', '- benchmark-oracle usage notes when a restrictive repo is kept only as a correctness reference', '', 'Recommended naming pattern:', '- -.model-a.md for repo-specific clean-room handoff files', '- -.oracle.md for restrictive oracle guidance files', '- benchmark-oracle-vs-clean-room.md for the general distinction guide', '- model-a-model-b-prompts.md for reusable role prompts' ) Write-Utf8File -Path (Join-Path $Config.ImplementationWorkspacesRoot 'README.md') -Lines @( "# $($Config.Name) implementation workspaces", '', 'This root is for isolated build and implementation work that consumes only approved inputs.', '', 'Expected subfolders:', '- model-b: isolated clean-room implementation workspace', '- scratch: safe local experimentation that does not read restrictive mirrors' ) Write-Utf8File -Path (Join-Path $Config.ModelBWorkspacesRoot 'README.md') -Lines @( "# $($Config.Name) model-b workspace", '', 'This directory is reserved for isolated clean-room implementation work.', '', 'Rules:', '- do not clone or mount restrictive mirrors here', '- consume only scrubbed specs, tests, and handoff notes from clean-room-specs', '- if a session has already read restrictive source, do not use that session here' ) Write-Utf8File -Path (Join-Path $Config.ScratchWorkspacesRoot 'README.md') -Lines @( "# $($Config.Name) scratch workspace", '', 'Use this directory for safe local experiments, temporary validation harnesses, and non-authoritative test projects.', '', 'Do not treat this folder as the long-term source of truth.' ) Write-Utf8File -Path (Join-Path $Config.GeneratedRoot 'README.md') -Lines @( "# $($Config.Name) generated artifacts", '', 'This directory is generated from repos.manifest.json.', '', 'Artifacts:', '- main.sln: first-party projects plus active pinned integrations', '- catalog.sln: lightweight catalog view of mirrors, specs, and pinned dependencies', '- active.slnf: current working-set filter' ) Write-Utf8File -Path (Join-Path $Config.CacheRoot 'README.md') -Lines @( "# $($Config.Name) cache", '', 'Use this directory for clone cache, bare mirrors, temporary sync state, or generated lookup tables that should not live in the live app repo.' ) } function Write-WorkspaceGitignore { param([Parameter(Mandatory = $true)][hashtable]$Config) Write-Utf8File -Path (Join-Path $Config.WorkspaceRoot '.gitignore') -Lines @( '.vs/', 'mirrors/', 'cache/', 'logs/', 'tmp/', 'implementation-workspaces/model-b/*', '!implementation-workspaces/model-b/README.md', 'implementation-workspaces/scratch/*', '!implementation-workspaces/scratch/README.md', '*.user', '*.suo' ) } function Write-ManifestFile { param( [Parameter(Mandatory = $true)][hashtable]$Config, [Parameter(Mandatory = $true)][bool]$RefreshManifest ) $manifestPath = Join-Path $Config.WorkspaceRoot 'repos.manifest.json' if ((-not (Test-Path -LiteralPath $manifestPath)) -or $RefreshManifest) { $manifest = New-ManifestObject -Config $Config $json = $manifest | ConvertTo-Json -Depth 10 [System.IO.File]::WriteAllText($manifestPath, $json + [Environment]::NewLine, [System.Text.UTF8Encoding]::new($false)) } } function Get-Manifest { param([Parameter(Mandatory = $true)][hashtable]$Config) $manifestPath = Join-Path $Config.WorkspaceRoot 'repos.manifest.json' return Get-Content -LiteralPath $manifestPath -Raw | ConvertFrom-Json } function Get-ProjectEntriesFromManifest { param([Parameter(Mandatory = $true)]$Manifest) $entries = [System.Collections.Generic.List[hashtable]]::new() foreach ($repository in $Manifest.repositories) { foreach ($projectFile in $repository.projectFiles) { $kind = if ($projectFile.kind) { [string]$projectFile.kind } else { if ($projectFile.path -like '*.vcxproj') { 'cpp' } else { 'js' } } $entries.Add([ordered]@{ Name = [string]$projectFile.name Path = [string]$projectFile.path Kind = $kind Guid = New-StableGuid "project::$($projectFile.path)" }) } } return $entries } function Write-SolutionFile { param( [Parameter(Mandatory = $true)][string]$SolutionPath, [Parameter(Mandatory = $true)][hashtable[]]$ProjectEntries, [Parameter(Mandatory = $true)][hashtable[]]$FolderEntries ) $folderProjectTypeGuid = '{2150E333-8FDC-42A3-9474-1A3956D46DE8}' $jsProjectTypeGuid = '{54A90642-561A-4BB1-A94E-469ADEE60C69}' $cppProjectTypeGuid = '{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}' $solutionDir = Split-Path -Path $SolutionPath -Parent $lines = [System.Collections.Generic.List[string]]::new() $lines.Add('') $lines.Add('Microsoft Visual Studio Solution File, Format Version 12.00') $lines.Add('# Visual Studio Version 17') $lines.Add('VisualStudioVersion = 17.14.36307.6') $lines.Add('MinimumVisualStudioVersion = 10.0.40219.1') foreach ($project in $ProjectEntries) { $projectTypeGuid = if ($project.Kind -eq 'js') { $jsProjectTypeGuid } else { $cppProjectTypeGuid } $relativeProjectPath = New-RelativePath -BasePath $solutionDir -TargetPath $project.Path $lines.Add("Project(`"$projectTypeGuid`") = `"$($project.Name)`", `"$relativeProjectPath`", `"$($project.Guid)`"") $lines.Add('EndProject') } foreach ($folder in $FolderEntries) { $folderFiles = @() if ($null -ne $folder.Files) { $folderFiles = @($folder.Files | Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_) -and (Test-Path -LiteralPath $_) }) } $lines.Add("Project(`"$folderProjectTypeGuid`") = `"$($folder.Name)`", `"$($folder.Name)`", `"$($folder.Guid)`"") if ($folderFiles.Count -gt 0) { $lines.Add("`tProjectSection(SolutionItems) = preProject") foreach ($filePath in $folderFiles) { $relativeFilePath = New-RelativePath -BasePath $solutionDir -TargetPath $filePath $lines.Add("`t`t$relativeFilePath = $relativeFilePath") } $lines.Add("`tEndProjectSection") } $lines.Add('EndProject') } $lines.Add('Global') $lines.Add("`tGlobalSection(SolutionConfigurationPlatforms) = preSolution") $lines.Add("`t`tDebug|x64 = Debug|x64") $lines.Add("`t`tRelease|x64 = Release|x64") $lines.Add("`tEndGlobalSection") $lines.Add("`tGlobalSection(ProjectConfigurationPlatforms) = postSolution") foreach ($project in $ProjectEntries) { if ($project.Kind -eq 'cpp') { $lines.Add("`t`t$($project.Guid).Debug|x64.ActiveCfg = Debug|x64") $lines.Add("`t`t$($project.Guid).Debug|x64.Build.0 = Debug|x64") $lines.Add("`t`t$($project.Guid).Release|x64.ActiveCfg = Release|x64") $lines.Add("`t`t$($project.Guid).Release|x64.Build.0 = Release|x64") } else { $lines.Add("`t`t$($project.Guid).Debug|x64.ActiveCfg = Debug|Any CPU") $lines.Add("`t`t$($project.Guid).Release|x64.ActiveCfg = Release|Any CPU") } } $lines.Add("`tEndGlobalSection") $nestedFolders = @($FolderEntries | Where-Object { $null -ne $_.ParentGuid -and $_.ParentGuid -ne '' }) if ($nestedFolders.Count -gt 0) { $lines.Add("`tGlobalSection(NestedProjects) = preSolution") foreach ($folder in $nestedFolders) { $lines.Add("`t`t$($folder.Guid) = $($folder.ParentGuid)") } $lines.Add("`tEndGlobalSection") } $lines.Add("`tGlobalSection(SolutionProperties) = preSolution") $lines.Add("`t`tHideSolutionNode = FALSE") $lines.Add("`tEndGlobalSection") $lines.Add('EndGlobal') [System.IO.File]::WriteAllLines($SolutionPath, $lines, [System.Text.UTF8Encoding]::new($false)) } function Write-WorkspaceArtifacts { param( [Parameter(Mandatory = $true)][hashtable]$Config, [Parameter(Mandatory = $true)]$Manifest ) $projectEntries = Get-ProjectEntriesFromManifest -Manifest $Manifest $workspaceConfigFiles = @( (Join-Path $Config.WorkspaceRoot 'README.md'), (Join-Path $Config.WorkspaceRoot 'repos.manifest.json'), (Join-Path $Config.WorkspaceRoot '.gitignore'), (Join-Path $Config.PlannedAppRoot 'README.md'), (Join-Path $Config.MirrorsRoot 'README.md'), (Join-Path $Config.PermissiveMirrorsRoot 'README.md'), (Join-Path $Config.RestrictiveMirrorsRoot 'README.md'), (Join-Path $Config.CleanRoomSpecsRoot 'README.md'), (Join-Path $Config.ImplementationWorkspacesRoot 'README.md'), (Join-Path $Config.ModelBWorkspacesRoot 'README.md'), (Join-Path $Config.ScratchWorkspacesRoot 'README.md'), (Join-Path $Config.GeneratedRoot 'README.md'), (Join-Path $Config.CacheRoot 'README.md') ) $workspaceConfigFolder = [ordered]@{ Name = 'Workspace Config' Guid = New-StableGuid "$($Config.Name)::workspace-config" ParentGuid = $null Files = $workspaceConfigFiles } $mainFolders = [System.Collections.Generic.List[hashtable]]::new() $mainFolders.Add($workspaceConfigFolder) $pinnedIntegrations = @($Manifest.repositories | Where-Object { $_.storage -eq 'submodule' -and $_.active }) if ($pinnedIntegrations.Count -gt 0) { $pinnedRootGuid = New-StableGuid "$($Config.Name)::pinned-root" $mainFolders.Add([ordered]@{ Name = 'Pinned Integrations' Guid = $pinnedRootGuid ParentGuid = $null Files = @() }) foreach ($repository in $pinnedIntegrations) { $mainFolders.Add([ordered]@{ Name = [string]$repository.name Guid = New-StableGuid "$($Config.Name)::main::$($repository.key)" ParentGuid = $pinnedRootGuid Files = @() }) } } Write-SolutionFile -SolutionPath (Join-Path $Config.GeneratedRoot 'main.sln') -ProjectEntries $projectEntries -FolderEntries $mainFolders $catalogFolders = [System.Collections.Generic.List[hashtable]]::new() $catalogFolders.Add($workspaceConfigFolder) $submoduleRepos = @($Manifest.repositories | Where-Object { $_.storage -eq 'submodule' }) $permissiveRepos = @($Manifest.repositories | Where-Object { $_.storage -eq 'external-mirror' -and $_.mirrorClass -eq 'permissive' }) $restrictiveRepos = @($Manifest.repositories | Where-Object { $_.storage -eq 'external-mirror' -and $_.mirrorClass -eq 'restrictive' }) $cleanRoomDocs = @($Manifest.repositories | Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_.handoffDoc) } | ForEach-Object { [string]$_.handoffDoc } | Select-Object -Unique) if ($submoduleRepos.Count -gt 0) { $submoduleRootGuid = New-StableGuid "$($Config.Name)::catalog::submodules" $catalogFolders.Add([ordered]@{ Name = 'Pinned Submodules' Guid = $submoduleRootGuid ParentGuid = $null Files = @() }) foreach ($repository in $submoduleRepos) { $catalogFolders.Add([ordered]@{ Name = [string]$repository.name Guid = New-StableGuid "$($Config.Name)::catalog::$($repository.key)" ParentGuid = $submoduleRootGuid Files = @() }) } } $permissiveRootGuid = New-StableGuid "$($Config.Name)::catalog::permissive" $catalogFolders.Add([ordered]@{ Name = 'Permissive Mirrors' Guid = $permissiveRootGuid ParentGuid = $null Files = @((Join-Path $Config.PermissiveMirrorsRoot 'README.md')) }) foreach ($repository in $permissiveRepos) { $catalogFolders.Add([ordered]@{ Name = [string]$repository.name Guid = New-StableGuid "$($Config.Name)::catalog::$($repository.key)" ParentGuid = $permissiveRootGuid Files = @() }) } $restrictiveRootGuid = New-StableGuid "$($Config.Name)::catalog::restrictive" $catalogFolders.Add([ordered]@{ Name = 'Restrictive Mirrors' Guid = $restrictiveRootGuid ParentGuid = $null Files = @((Join-Path $Config.RestrictiveMirrorsRoot 'README.md')) }) foreach ($repository in $restrictiveRepos) { $catalogFolders.Add([ordered]@{ Name = [string]$repository.name Guid = New-StableGuid "$($Config.Name)::catalog::$($repository.key)" ParentGuid = $restrictiveRootGuid Files = @() }) } if ($cleanRoomDocs.Count -gt 0) { $cleanRoomRootGuid = New-StableGuid "$($Config.Name)::catalog::clean-room" $catalogFolders.Add([ordered]@{ Name = 'Clean-Room Specs' Guid = $cleanRoomRootGuid ParentGuid = $null Files = @((Join-Path $Config.CleanRoomSpecsRoot 'README.md')) + $cleanRoomDocs }) } Write-SolutionFile -SolutionPath (Join-Path $Config.GeneratedRoot 'catalog.sln') -ProjectEntries $projectEntries -FolderEntries $catalogFolders $solutionFilterPath = Join-Path $Config.GeneratedRoot 'active.slnf' $relativeProjectPaths = @($projectEntries | ForEach-Object { New-RelativePath -BasePath $Config.GeneratedRoot -TargetPath $_.Path }) $solutionFilter = [ordered]@{ solution = [ordered]@{ path = 'main.sln' projects = $relativeProjectPaths } } | ConvertTo-Json -Depth 5 [System.IO.File]::WriteAllText($solutionFilterPath, $solutionFilter + [Environment]::NewLine, [System.Text.UTF8Encoding]::new($false)) } function Initialize-Workspace { param([Parameter(Mandatory = $true)][hashtable]$Config) foreach ($path in @( $Config.WorkspaceRoot, $Config.PlannedAppRoot, $Config.MirrorsRoot, $Config.PermissiveMirrorsRoot, $Config.RestrictiveMirrorsRoot, $Config.CleanRoomSpecsRoot, $Config.ImplementationWorkspacesRoot, $Config.ModelBWorkspacesRoot, $Config.ScratchWorkspacesRoot, $Config.GeneratedRoot, $Config.CacheRoot )) { Ensure-Directory -Path $path } Write-WorkspaceReadmes -Config $Config Write-WorkspaceGitignore -Config $Config Write-ManifestFile -Config $Config -RefreshManifest:$ForceManifestRefresh $manifest = Get-Manifest -Config $Config Write-WorkspaceArtifacts -Config $Config -Manifest $manifest } $productNames = if ($Product -eq 'All') { @('ScriptoriumAI', 'HyperTwist', 'VectorShell') } else { @($Product) } foreach ($productName in $productNames) { $config = Get-ProductConfig -Name $productName -WorkspaceBasePath $WorkspaceBase Initialize-Workspace -Config $config Write-Host "Initialized external workspace scaffold for $productName at $($config.WorkspaceRoot)" -ForegroundColor Green }