# Vendor a trimmed Playbook snapshot into a target project (offline copy), # then run sync_standards to materialize rulesets\\ and .gitattributes in # the target project root. # # Usage: # powershell -File scripts/vendor_playbook.ps1 -ProjectRoot # powershell -File scripts/vendor_playbook.ps1 -ProjectRoot -Langs tsl,cpp # powershell -File scripts/vendor_playbook.ps1 -ProjectRoot -Langs @("tsl","cpp") -ApplyTemplates # # Options: # -ApplyTemplates Apply CI/lang templates to project root (skip if exists) # # Notes: # - Snapshot is written to: \docs\standards\playbook\ # - Existing snapshot is backed up before overwrite. # - With -ApplyTemplates, CI and lang templates are copied to project root. [CmdletBinding()] param( [Parameter(Mandatory = $false)] [Alias('h', 'help', '?')] [switch]$Help, [Parameter(Mandatory = $false)] [string]$ProjectRoot, [Parameter(Mandatory = $false)] [string[]]$Langs, [Parameter(Mandatory = $false)] [switch]$ApplyTemplates ) $ErrorActionPreference = "Stop" if ($Help) { Write-Host "Usage:" Write-Host " powershell -File scripts/vendor_playbook.ps1 -ProjectRoot " Write-Host " powershell -File scripts/vendor_playbook.ps1 -ProjectRoot -Langs tsl,cpp" Write-Host " powershell -File scripts/vendor_playbook.ps1 -ProjectRoot -Langs @('tsl','cpp') -ApplyTemplates" Write-Host "" Write-Host "Options:" Write-Host " -ProjectRoot Target project root (required)." Write-Host " -Langs Comma/space-separated list or array (default: tsl)." Write-Host " -ApplyTemplates Apply CI/lang templates to project root." Write-Host " -Help Show this help." exit 0 } if (-not $ProjectRoot) { throw "ProjectRoot is required. Use -Help for usage." } function Normalize-Langs([string[]]$InputLangs) { if (-not $InputLangs -or $InputLangs.Count -eq 0) { return @("tsl") } $result = New-Object System.Collections.Generic.List[string] foreach ($item in $InputLangs) { if (-not $item) { continue } foreach ($part in $item.Split(@(',', ' '), [System.StringSplitOptions]::RemoveEmptyEntries)) { if (-not $part) { continue } $result.Add($part) } } if ($result.Count -eq 0) { return @("tsl") } return $result.ToArray() } $Langs = Normalize-Langs $Langs foreach ($lang in $Langs) { if ($lang -match '[\\/]' -or $lang -match '\.\.') { throw "Invalid lang=$lang" } } $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path $Src = (Resolve-Path (Join-Path $ScriptDir "..")).Path New-Item -ItemType Directory -Path $ProjectRoot -Force | Out-Null $DestRootAbs = (Resolve-Path $ProjectRoot).Path $StandardsDir = Join-Path $DestRootAbs "docs/standards" $DestPrefix = Join-Path $StandardsDir "playbook" New-Item -ItemType Directory -Path $StandardsDir -Force | Out-Null $timestamp = Get-Date -Format "yyyyMMddHHmmss" if (Test-Path $DestPrefix) { $bak = Join-Path $StandardsDir "playbook.bak.$timestamp" Move-Item $DestPrefix $bak Write-Host "Backed up existing snapshot -> docs\\standards\\$(Split-Path -Leaf $bak)" } New-Item -ItemType Directory -Path $DestPrefix -Force | Out-Null Copy-Item (Join-Path $Src ".gitattributes") (Join-Path $DestPrefix ".gitattributes") -Force Copy-Item (Join-Path $Src "scripts") $DestPrefix -Recurse -Force Copy-Item (Join-Path $Src "codex") $DestPrefix -Recurse -Force Copy-Item (Join-Path $Src "SKILLS.md") (Join-Path $DestPrefix "SKILLS.md") -Force $DocsDir = Join-Path $DestPrefix "docs" New-Item -ItemType Directory -Path $DocsDir -Force | Out-Null Copy-Item (Join-Path $Src "docs/common") $DocsDir -Recurse -Force $AgentsDir = Join-Path $DestPrefix "rulesets" New-Item -ItemType Directory -Path $AgentsDir -Force | Out-Null Copy-Item (Join-Path $Src "rulesets/index.md") (Join-Path $AgentsDir "index.md") -Force $TemplatesDir = Join-Path $DestPrefix "templates" New-Item -ItemType Directory -Path $TemplatesDir -Force | Out-Null $ciTplSrc = Join-Path (Join-Path $Src "templates") "ci" if (Test-Path $ciTplSrc) { Copy-Item $ciTplSrc $TemplatesDir -Recurse -Force } foreach ($lang in $Langs) { $docsSrc = Join-Path (Join-Path $Src "docs") $lang if (-not (Test-Path $docsSrc)) { throw "Docs not found for lang=$lang ($docsSrc)" } Copy-Item $docsSrc $DocsDir -Recurse -Force $agentsSrc = Join-Path (Join-Path $Src "rulesets") $lang if (-not (Test-Path $agentsSrc)) { throw "Agents ruleset not found for lang=$lang ($agentsSrc)" } Copy-Item $agentsSrc $AgentsDir -Recurse -Force $tplSrc = Join-Path (Join-Path $Src "templates") $lang if (Test-Path $tplSrc) { Copy-Item $tplSrc $TemplatesDir -Recurse -Force } } $langsCsv = ($Langs -join ",") $docLines = New-Object System.Collections.Generic.List[string] $docLines.Add("# 文档导航(Docs Index)") $docLines.Add("") $docLines.Add("本快照为裁剪版 Playbook(langs: $langsCsv)。") $docLines.Add("") $docLines.Add("## 跨语言(common)") $docLines.Add("") $docLines.Add('- 提交信息与版本号:`common/commit_message.md`') function Append-DocsSection([string]$Lang) { switch ($Lang) { "tsl" { $docLines.Add("") $docLines.Add("## TSL(tsl)") $docLines.Add("") $docLines.Add('- 代码风格:`tsl/code_style.md`') $docLines.Add('- 命名规范:`tsl/naming.md`') $docLines.Add('- 语法手册:`tsl/syntax_book/index.md`') $docLines.Add('- 工具链与验证命令(模板):`tsl/toolchain.md`') break } "cpp" { $docLines.Add("") $docLines.Add("## C++(cpp)") $docLines.Add("") $docLines.Add('- 代码风格:`cpp/code_style.md`') $docLines.Add('- 命名规范:`cpp/naming.md`') $docLines.Add('- 工具链与验证命令(模板):`cpp/toolchain.md`') $docLines.Add('- 第三方依赖(Conan):`cpp/dependencies_conan.md`') $docLines.Add('- clangd 配置:`cpp/clangd.md`') break } "python" { $docLines.Add("") $docLines.Add("## Python(python)") $docLines.Add("") $docLines.Add('- 代码风格:`python/style_guide.md`') $docLines.Add('- 工具链:`python/tooling.md`') $docLines.Add('- 配置清单:`python/configuration.md`') break } "markdown" { $docLines.Add("") $docLines.Add("## Markdown(markdown)") $docLines.Add("") $docLines.Add('- 代码块与行内代码格式:`markdown/index.md`') break } } } foreach ($lang in $Langs) { Append-DocsSection $lang } ($docLines -join "`n") | Set-Content -Path (Join-Path $DocsDir "index.md") -Encoding UTF8 $commit = "" try { $commit = (git -C $Src rev-parse HEAD 2>$null) } catch { $commit = "" } if (-not $commit) { $commit = "N/A" } @" # Playbook(裁剪快照) 本目录为从 Playbook vendoring 的裁剪快照(langs: $langsCsv)。 ## 使用 在目标项目根目录执行(多语言一次同步): ```sh sh docs/standards/playbook/scripts/sync_standards.sh -langs $langsCsv ``` 查看规范入口: - `docs/standards/playbook/docs/index.md` - `.agents/index.md` ## Codex skills(可选) 安装到本机(需要先在 `~/.codex/config.toml` 启用 skills;见 `docs/standards/playbook/SKILLS.md`): ```sh sh docs/standards/playbook/scripts/install_codex_skills.sh -all ``` ## CI templates(可选) 目标项目可复制启用的 CI 示例模板(如 Gitea Actions):`templates/ci/`。 "@ | Set-Content -Path (Join-Path $DestPrefix "README.md") -Encoding UTF8 @" # SOURCE - Source: $Src - Commit: $commit - Date: $(Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ") - Langs: $langsCsv - Generated-by: scripts/vendor_playbook.ps1 "@ | Set-Content -Path (Join-Path $DestPrefix "SOURCE.md") -Encoding UTF8 Write-Host "Vendored snapshot -> $DestPrefix" $ProjectAgentsRoot = Join-Path $DestRootAbs ".agents" $ProjectAgentsIndex = Join-Path $ProjectAgentsRoot "index.md" New-Item -ItemType Directory -Path $ProjectAgentsRoot -Force | Out-Null if (-not (Test-Path $ProjectAgentsIndex)) { $agentLines = New-Object System.Collections.Generic.List[string] $agentLines.Add("# .agents(多语言)") $agentLines.Add("") $agentLines.Add("本目录用于存放仓库级/语言级的代理规则集。") $agentLines.Add("") $agentLines.Add("本项目已启用的规则集:") foreach ($lang in $Langs) { switch ($lang) { "tsl" { $agentLines.Add("- .agents/tsl/:TSL 相关规则集(适用于 .tsl/.tsf)"); break } "cpp" { $agentLines.Add("- .agents/cpp/:C++ 相关规则集(C++23,含 Modules)"); break } "python" { $agentLines.Add("- .agents/python/:Python 相关规则集"); break } "markdown" { $agentLines.Add("- .agents/markdown/:Markdown 相关规则集(仅代码格式化)"); break } } } $agentLines.Add("") $agentLines.Add("入口建议从:") foreach ($lang in $Langs) { $agentLines.Add("- .agents/$lang/index.md") } $agentLines.Add("") $agentLines.Add("标准快照文档入口:") $agentLines.Add("") $agentLines.Add("- docs/standards/playbook/docs/index.md") ($agentLines -join "`n") | Set-Content -Path $ProjectAgentsIndex -Encoding UTF8 } $oldSyncRoot = $env:SYNC_ROOT $env:SYNC_ROOT = $DestRootAbs try { & (Join-Path $DestPrefix "scripts/sync_standards.ps1") -Langs $Langs } finally { $env:SYNC_ROOT = $oldSyncRoot } # Apply templates to project root if requested if ($ApplyTemplates) { Write-Host "" Write-Host "Applying templates to project root..." # Helper function: copy file if not exists function Copy-IfNotExists { param([string]$SrcFile, [string]$DstFile) if (Test-Path $SrcFile) { if (Test-Path $DstFile) { Write-Host " Skip (exists): $(Split-Path -Leaf $DstFile)" } else { Copy-Item $SrcFile $DstFile -Force Write-Host " Applied: $(Split-Path -Leaf $DstFile)" } } } # Apply CI templates (Gitea workflows) $ciSrc = Join-Path $DestPrefix "templates/ci/gitea/.gitea" if (Test-Path $ciSrc) { $ciDst = Join-Path $DestRootAbs ".gitea" if (Test-Path $ciDst) { Write-Host " Skip (exists): .gitea/" } else { Copy-Item $ciSrc $ciDst -Recurse -Force Write-Host " Applied: .gitea/" } } # Apply lang-specific templates foreach ($lang in $Langs) { $langSrc = Join-Path $DestPrefix "templates/$lang" if (-not (Test-Path $langSrc)) { continue } switch ($lang) { "cpp" { Copy-IfNotExists (Join-Path $langSrc ".clang-format") (Join-Path $DestRootAbs ".clang-format") Copy-IfNotExists (Join-Path $langSrc ".clangd") (Join-Path $DestRootAbs ".clangd") Copy-IfNotExists (Join-Path $langSrc "CMakeLists.txt") (Join-Path $DestRootAbs "CMakeLists.txt") Copy-IfNotExists (Join-Path $langSrc "CMakeUserPresets.json") (Join-Path $DestRootAbs "CMakeUserPresets.json") Copy-IfNotExists (Join-Path $langSrc "conanfile.txt") (Join-Path $DestRootAbs "conanfile.txt") $conanSrc = Join-Path $langSrc "conan" $conanDst = Join-Path $DestRootAbs "conan" if ((Test-Path $conanSrc) -and -not (Test-Path $conanDst)) { Copy-Item $conanSrc $conanDst -Recurse -Force Write-Host " Applied: conan/" } elseif (Test-Path $conanDst) { Write-Host " Skip (exists): conan/" } break } "python" { Copy-IfNotExists (Join-Path $langSrc ".editorconfig") (Join-Path $DestRootAbs ".editorconfig") Copy-IfNotExists (Join-Path $langSrc ".flake8") (Join-Path $DestRootAbs ".flake8") Copy-IfNotExists (Join-Path $langSrc ".pre-commit-config.yaml") (Join-Path $DestRootAbs ".pre-commit-config.yaml") Copy-IfNotExists (Join-Path $langSrc ".pylintrc") (Join-Path $DestRootAbs ".pylintrc") Copy-IfNotExists (Join-Path $langSrc "pyproject.toml") (Join-Path $DestRootAbs "pyproject.toml") $vscodeSrc = Join-Path $langSrc ".vscode" $vscodeDst = Join-Path $DestRootAbs ".vscode" if ((Test-Path $vscodeSrc) -and -not (Test-Path $vscodeDst)) { Copy-Item $vscodeSrc $vscodeDst -Recurse -Force Write-Host " Applied: .vscode/" } elseif (Test-Path $vscodeDst) { Write-Host " Skip (exists): .vscode/" } break } } } Write-Host "Templates applied." } Write-Host "Done."