feat(manifest): restructure factory.yaml with core/modules schema + manifest-driven bootstrap
factory.yaml: new schema (schema_version 1, factory_version 0.3.0) - core.components[] with id/source/version/type/install/health_check - versions from bundled lib/: projects-meta-mcp 2.25.0, wiki-graph 0.3.1, interns-mcp 0.3.3 - modules[]: tasks, wiki, workshop, admin — each with creates[] describing gitea_repo / local_clone / claude_md_snippet / setup_skill actions - removed hardcoded git_host/git_org (those belong in ~/.config/factory/home.toml per design) bootstrap.ps1: add Read-FactoryManifest + manifest-driven MCP build loop - indent-aware YAML parser prevents health_check.type from clobbering component.type - MCP build loop now reads from manifest instead of hardcoded server names - reports factory version at startup Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
114
bootstrap.ps1
114
bootstrap.ps1
@@ -16,6 +16,60 @@ function Test-Command {
|
||||
$null = Get-Command $Name -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
function Read-FactoryManifest {
|
||||
param([string]$FactoryPath)
|
||||
|
||||
$yamlPath = "$FactoryPath\factory.yaml"
|
||||
if (-not (Test-Path $yamlPath)) {
|
||||
Write-Gap "factory.yaml not found at $yamlPath"
|
||||
return $null
|
||||
}
|
||||
|
||||
$lines = Get-Content $yamlPath
|
||||
$manifest = @{
|
||||
schema_version = ""
|
||||
factory_version = ""
|
||||
core_components = [System.Collections.Generic.List[hashtable]]::new()
|
||||
modules = [System.Collections.Generic.List[hashtable]]::new()
|
||||
}
|
||||
|
||||
$section = ""
|
||||
$currentItem = $null
|
||||
$propPrefix = "" # exact indent of direct item properties (avoids nested key collisions)
|
||||
|
||||
foreach ($line in $lines) {
|
||||
if ($line -match '^\s*#' -or $line -match '^\s*$') { continue }
|
||||
|
||||
if ($line -match '^schema_version:\s*"([^"]+)"') { $manifest.schema_version = $matches[1]; continue }
|
||||
if ($line -match '^factory_version:\s*"([^"]+)"') { $manifest.factory_version = $matches[1]; continue }
|
||||
|
||||
if ($line -match '^core:') { $section = ""; $currentItem = $null; continue }
|
||||
if ($line -match '^\s+components:') { $section = "core_components"; $currentItem = $null; continue }
|
||||
if ($line -match '^modules:') { $section = "modules"; $currentItem = $null; continue }
|
||||
|
||||
# List item start — detect indent to build exact property prefix
|
||||
if ($line -match '^(\s+)-\s+id:\s+(\S+)') {
|
||||
$itemIndent = $matches[1].Length
|
||||
$propPrefix = " " * ($itemIndent + 2)
|
||||
$currentItem = @{ id = $matches[2]; type = ""; source = ""; version = ""; description = ""; optional = $true }
|
||||
if ($section -eq "core_components") { $manifest.core_components.Add($currentItem) }
|
||||
elseif ($section -eq "modules") { $manifest.modules.Add($currentItem) }
|
||||
continue
|
||||
}
|
||||
|
||||
if (-not $currentItem) { continue }
|
||||
|
||||
# Match only at direct-property indent (prevents health_check.type clobbering component.type)
|
||||
if ($line -match "^${propPrefix}type:\s+(\S+)") { $currentItem.type = $matches[1]; continue }
|
||||
if ($line -match "^${propPrefix}source:\s+(\S+)") { $currentItem.source = $matches[1]; continue }
|
||||
if ($line -match "^${propPrefix}version:\s+`"([^`"]+)`"") { $currentItem.version = $matches[1]; continue }
|
||||
if ($line -match "^${propPrefix}description:\s+`"([^`"]+)`"") { $currentItem.description = $matches[1]; continue }
|
||||
if ($line -match "^${propPrefix}optional:\s+(\S+)") { $currentItem.optional = ($matches[1] -eq "true"); continue }
|
||||
}
|
||||
|
||||
return $manifest
|
||||
}
|
||||
|
||||
function Write-Step {
|
||||
param([string]$Msg)
|
||||
Write-Host "`n▶ $Msg" -ForegroundColor Cyan
|
||||
@@ -174,47 +228,55 @@ $lines = @(
|
||||
|
||||
Write-Ok "Wrote $HOME_TOML"
|
||||
|
||||
# Build MCP servers from lib/
|
||||
# Read factory manifest
|
||||
Write-Step "Reading factory manifest"
|
||||
|
||||
$manifest = Read-FactoryManifest -FactoryPath $factoryLocal
|
||||
if (-not $manifest) { exit 1 }
|
||||
Write-Ok "factory v$($manifest.factory_version) (schema $($manifest.schema_version))"
|
||||
$coreIds = ($manifest.core_components | ForEach-Object { $_.id }) -join ", "
|
||||
$moduleIds = ($manifest.modules | ForEach-Object { $_.id }) -join ", "
|
||||
Write-Host " Core: $coreIds"
|
||||
Write-Host " Modules: $moduleIds"
|
||||
|
||||
# Build MCP servers from lib/ (driven by manifest core.components)
|
||||
Write-Step "Building MCP servers"
|
||||
|
||||
$libPath = "$factoryLocal\lib"
|
||||
|
||||
if (Test-Path $libPath) {
|
||||
# TypeScript servers (npm install + npm run build)
|
||||
foreach ($server in @("projects-meta-mcp", "wiki-graph")) {
|
||||
$serverPath = "$libPath\$server"
|
||||
if (Test-Path $serverPath) {
|
||||
Write-Host " Building $server..."
|
||||
Push-Location $serverPath
|
||||
foreach ($component in $manifest.core_components) {
|
||||
$srcPath = "$factoryLocal\$($component.source)"
|
||||
if (-not (Test-Path $srcPath)) { continue }
|
||||
|
||||
if ($component.type -eq "typescript") {
|
||||
Write-Host " Building $($component.id) v$($component.version)..."
|
||||
Push-Location $srcPath
|
||||
try {
|
||||
npm install --silent 2>&1 | Out-Null
|
||||
npm run build 2>&1 | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Gap "$server: build failed (npm run build exited $LASTEXITCODE)"
|
||||
Write-Gap "$($component.id): build failed (npm run build exited $LASTEXITCODE)"
|
||||
} else {
|
||||
Write-Ok "$server built"
|
||||
Write-Ok "$($component.id) v$($component.version) built"
|
||||
}
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Python server (interns-mcp) — venv + pip install
|
||||
$internsMcpPath = "$libPath\interns-mcp"
|
||||
if (Test-Path $internsMcpPath) {
|
||||
Write-Host " Setting up interns-mcp..."
|
||||
Push-Location $internsMcpPath
|
||||
try {
|
||||
python -m venv .venv 2>&1 | Out-Null
|
||||
& ".venv\Scripts\pip.exe" install -e . --quiet
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Gap "interns-mcp: pip install failed"
|
||||
} else {
|
||||
Write-Ok "interns-mcp installed"
|
||||
} elseif ($component.type -eq "python") {
|
||||
Write-Host " Setting up $($component.id) v$($component.version)..."
|
||||
Push-Location $srcPath
|
||||
try {
|
||||
python -m venv .venv 2>&1 | Out-Null
|
||||
& ".venv\Scripts\pip.exe" install -e . --quiet
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Gap "$($component.id): pip install failed"
|
||||
} else {
|
||||
Write-Ok "$($component.id) v$($component.version) installed"
|
||||
}
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user