sdtk-ops-kit 0.2.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 +146 -0
- package/assets/manifest/toolkit-bundle.manifest.json +187 -0
- package/assets/manifest/toolkit-bundle.sha256.txt +36 -0
- package/assets/toolkit/toolkit/AGENTS.md +65 -0
- package/assets/toolkit/toolkit/SDTKOPS_TOOLKIT.md +166 -0
- package/assets/toolkit/toolkit/install.ps1 +138 -0
- package/assets/toolkit/toolkit/scripts/install-claude-skills.ps1 +81 -0
- package/assets/toolkit/toolkit/scripts/install-codex-skills.ps1 +127 -0
- package/assets/toolkit/toolkit/scripts/uninstall-claude-skills.ps1 +65 -0
- package/assets/toolkit/toolkit/scripts/uninstall-codex-skills.ps1 +53 -0
- package/assets/toolkit/toolkit/sdtk-spec.config.json +6 -0
- package/assets/toolkit/toolkit/sdtk-spec.config.profiles.example.json +12 -0
- package/assets/toolkit/toolkit/skills/ops-backup/SKILL.md +93 -0
- package/assets/toolkit/toolkit/skills/ops-backup/references/backup-script-patterns.md +108 -0
- package/assets/toolkit/toolkit/skills/ops-ci-cd/SKILL.md +88 -0
- package/assets/toolkit/toolkit/skills/ops-ci-cd/references/pipeline-examples.md +113 -0
- package/assets/toolkit/toolkit/skills/ops-compliance/SKILL.md +105 -0
- package/assets/toolkit/toolkit/skills/ops-container/SKILL.md +95 -0
- package/assets/toolkit/toolkit/skills/ops-container/references/k8s-manifest-patterns.md +116 -0
- package/assets/toolkit/toolkit/skills/ops-cost/SKILL.md +88 -0
- package/assets/toolkit/toolkit/skills/ops-debug/SKILL.md +311 -0
- package/assets/toolkit/toolkit/skills/ops-debug/references/root-cause-tracing.md +138 -0
- package/assets/toolkit/toolkit/skills/ops-deploy/SKILL.md +102 -0
- package/assets/toolkit/toolkit/skills/ops-discover/SKILL.md +102 -0
- package/assets/toolkit/toolkit/skills/ops-incident/SKILL.md +113 -0
- package/assets/toolkit/toolkit/skills/ops-incident/references/communication-templates.md +34 -0
- package/assets/toolkit/toolkit/skills/ops-incident/references/postmortem-template.md +69 -0
- package/assets/toolkit/toolkit/skills/ops-incident/references/runbook-template.md +69 -0
- package/assets/toolkit/toolkit/skills/ops-infra-plan/SKILL.md +123 -0
- package/assets/toolkit/toolkit/skills/ops-infra-plan/references/iac-patterns.md +141 -0
- package/assets/toolkit/toolkit/skills/ops-monitor/SKILL.md +110 -0
- package/assets/toolkit/toolkit/skills/ops-monitor/references/alert-rules.md +80 -0
- package/assets/toolkit/toolkit/skills/ops-monitor/references/slo-templates.md +83 -0
- package/assets/toolkit/toolkit/skills/ops-parallel/SKILL.md +177 -0
- package/assets/toolkit/toolkit/skills/ops-plan/SKILL.md +169 -0
- package/assets/toolkit/toolkit/skills/ops-security-infra/SKILL.md +126 -0
- package/assets/toolkit/toolkit/skills/ops-security-infra/references/cicd-security-pipeline.md +55 -0
- package/assets/toolkit/toolkit/skills/ops-security-infra/references/security-headers.md +24 -0
- package/assets/toolkit/toolkit/skills/ops-verify/SKILL.md +180 -0
- package/bin/sdtk-ops.js +14 -0
- package/package.json +46 -0
- package/src/commands/generate.js +12 -0
- package/src/commands/help.js +53 -0
- package/src/commands/init.js +86 -0
- package/src/commands/runtime.js +201 -0
- package/src/index.js +65 -0
- package/src/lib/args.js +107 -0
- package/src/lib/errors.js +41 -0
- package/src/lib/powershell.js +65 -0
- package/src/lib/scope.js +58 -0
- package/src/lib/toolkit-payload.js +123 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
param(
|
|
2
|
+
[Parameter(Mandatory = $true)]
|
|
3
|
+
[ValidateSet('codex', 'claude')]
|
|
4
|
+
[string]$Runtime,
|
|
5
|
+
|
|
6
|
+
[string]$ProjectPath,
|
|
7
|
+
|
|
8
|
+
[ValidateSet('project', 'user', '')]
|
|
9
|
+
[string]$Scope = '',
|
|
10
|
+
|
|
11
|
+
[switch]$Force,
|
|
12
|
+
[switch]$SkipRuntimeAssets,
|
|
13
|
+
[switch]$SkipSkills,
|
|
14
|
+
[switch]$Quiet
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
$ErrorActionPreference = 'Stop'
|
|
18
|
+
Set-StrictMode -Version Latest
|
|
19
|
+
|
|
20
|
+
function Copy-File {
|
|
21
|
+
param(
|
|
22
|
+
[Parameter(Mandatory = $true)][string]$SourcePath,
|
|
23
|
+
[Parameter(Mandatory = $true)][string]$DestinationPath,
|
|
24
|
+
[Parameter(Mandatory = $true)][bool]$Overwrite
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
if (-not (Test-Path -LiteralPath $SourcePath)) {
|
|
28
|
+
throw "Missing source file: $SourcePath"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if ((Test-Path -LiteralPath $DestinationPath) -and -not $Overwrite) {
|
|
32
|
+
if (-not $Quiet) {
|
|
33
|
+
Write-Warning "Already exists (skipping). Use -Force to overwrite: $DestinationPath"
|
|
34
|
+
}
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
$parent = Split-Path -Parent $DestinationPath
|
|
39
|
+
if ($parent -and -not (Test-Path -LiteralPath $parent)) {
|
|
40
|
+
New-Item -ItemType Directory -Force -Path $parent | Out-Null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
Copy-Item -LiteralPath $SourcePath -Destination $DestinationPath -Force
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function Resolve-OrCreateDirectory {
|
|
47
|
+
param(
|
|
48
|
+
[Parameter(Mandatory = $true)][string]$Path
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
if (-not (Test-Path -LiteralPath $Path)) {
|
|
52
|
+
New-Item -ItemType Directory -Force -Path $Path | Out-Null
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return (Resolve-Path -LiteralPath $Path).Path
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
$toolkitRoot = (Resolve-Path -LiteralPath $PSScriptRoot).Path
|
|
59
|
+
if (-not $ProjectPath) {
|
|
60
|
+
$ProjectPath = (Get-Location).Path
|
|
61
|
+
}
|
|
62
|
+
$projectRoot = Resolve-OrCreateDirectory -Path $ProjectPath
|
|
63
|
+
|
|
64
|
+
if (-not $Scope) {
|
|
65
|
+
$Scope = if ($Runtime -eq 'claude') { 'project' } else { 'user' }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if ($Runtime -eq 'codex' -and $Scope -eq 'project') {
|
|
69
|
+
Write-Error "Codex does not support project-local skills. Use -Scope user instead."
|
|
70
|
+
exit 1
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if ($SkipSkills) {
|
|
74
|
+
if (-not $Quiet) {
|
|
75
|
+
Write-Warning "-SkipSkills is deprecated. Use -SkipRuntimeAssets instead."
|
|
76
|
+
}
|
|
77
|
+
$SkipRuntimeAssets = $true
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (-not $Quiet) {
|
|
81
|
+
Write-Host "SDTK-OPS installer"
|
|
82
|
+
Write-Host " Runtime: $Runtime"
|
|
83
|
+
Write-Host " Scope: $Scope"
|
|
84
|
+
Write-Host " Project: $projectRoot"
|
|
85
|
+
Write-Host ""
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
$sharedFiles = @(
|
|
89
|
+
'AGENTS.md',
|
|
90
|
+
'sdtk-spec.config.json',
|
|
91
|
+
'sdtk-spec.config.profiles.example.json'
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
foreach ($fileName in $sharedFiles) {
|
|
95
|
+
Copy-File `
|
|
96
|
+
-SourcePath (Join-Path $toolkitRoot $fileName) `
|
|
97
|
+
-DestinationPath (Join-Path $projectRoot $fileName) `
|
|
98
|
+
-Overwrite ([bool]$Force)
|
|
99
|
+
|
|
100
|
+
if (-not $Quiet) {
|
|
101
|
+
Write-Host " [OK] $fileName"
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (-not $SkipRuntimeAssets) {
|
|
106
|
+
$scriptPath = if ($Runtime -eq 'claude') {
|
|
107
|
+
Join-Path $toolkitRoot 'scripts/install-claude-skills.ps1'
|
|
108
|
+
} else {
|
|
109
|
+
Join-Path $toolkitRoot 'scripts/install-codex-skills.ps1'
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (-not (Test-Path -LiteralPath $scriptPath)) {
|
|
113
|
+
throw "Missing runtime installer: $scriptPath"
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (-not $Quiet) {
|
|
117
|
+
Write-Host ""
|
|
118
|
+
Write-Host "Installing $Runtime runtime assets..."
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
$params = @{
|
|
122
|
+
Scope = $Scope
|
|
123
|
+
Quiet = [bool]$Quiet
|
|
124
|
+
}
|
|
125
|
+
if ($Runtime -eq 'claude') {
|
|
126
|
+
$params.ProjectPath = $projectRoot
|
|
127
|
+
}
|
|
128
|
+
if ($Force) {
|
|
129
|
+
$params.Force = $true
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
& $scriptPath @params
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (-not $Quiet) {
|
|
136
|
+
Write-Host ""
|
|
137
|
+
Write-Host "SDTK-OPS installation complete."
|
|
138
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
param(
|
|
2
|
+
[string]$ProjectPath,
|
|
3
|
+
[ValidateSet('project', 'user')]
|
|
4
|
+
[string]$Scope = 'project',
|
|
5
|
+
[switch]$Force,
|
|
6
|
+
[switch]$Quiet
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
$ErrorActionPreference = 'Stop'
|
|
10
|
+
Set-StrictMode -Version Latest
|
|
11
|
+
|
|
12
|
+
$toolkitRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
|
|
13
|
+
$skillsSource = Join-Path $toolkitRoot 'skills'
|
|
14
|
+
if (-not (Test-Path -LiteralPath $skillsSource)) {
|
|
15
|
+
throw "Missing toolkit skills source directory: $skillsSource"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
$managedSkills = @(Get-ChildItem -LiteralPath $skillsSource -Directory | Sort-Object Name)
|
|
19
|
+
if ($managedSkills.Count -ne 15) {
|
|
20
|
+
throw "Expected 15 managed SDTK-OPS skills but found $($managedSkills.Count)."
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if ($Scope -eq 'user') {
|
|
24
|
+
$skillsDest = Join-Path $HOME '.claude/skills'
|
|
25
|
+
} else {
|
|
26
|
+
if (-not $ProjectPath) {
|
|
27
|
+
throw "ProjectPath is required for Claude project scope installation."
|
|
28
|
+
}
|
|
29
|
+
if (-not (Test-Path -LiteralPath $ProjectPath)) {
|
|
30
|
+
New-Item -ItemType Directory -Force -Path $ProjectPath | Out-Null
|
|
31
|
+
}
|
|
32
|
+
$projectRoot = (Resolve-Path -LiteralPath $ProjectPath).Path
|
|
33
|
+
$skillsDest = Join-Path $projectRoot '.claude/skills'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
New-Item -ItemType Directory -Force -Path $skillsDest | Out-Null
|
|
37
|
+
|
|
38
|
+
$copied = 0
|
|
39
|
+
$skipped = 0
|
|
40
|
+
foreach ($skillDir in $managedSkills) {
|
|
41
|
+
$skillFile = Join-Path $skillDir.FullName 'SKILL.md'
|
|
42
|
+
if (-not (Test-Path -LiteralPath $skillFile)) {
|
|
43
|
+
throw "Managed skill is missing SKILL.md: $($skillDir.FullName)"
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
$destDir = Join-Path $skillsDest $skillDir.Name
|
|
47
|
+
if (Test-Path -LiteralPath $destDir) {
|
|
48
|
+
if (-not $Force) {
|
|
49
|
+
$skipped++
|
|
50
|
+
if (-not $Quiet) {
|
|
51
|
+
Write-Warning "Skill already installed (skipping). Use -Force to overwrite: $($skillDir.Name)"
|
|
52
|
+
}
|
|
53
|
+
continue
|
|
54
|
+
}
|
|
55
|
+
Remove-Item -LiteralPath $destDir -Recurse -Force
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
Copy-Item -LiteralPath $skillDir.FullName -Destination $destDir -Recurse -Force
|
|
59
|
+
$copied++
|
|
60
|
+
if (-not $Quiet) {
|
|
61
|
+
Write-Host "Installed: $($skillDir.Name)"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
$installedNow = @(
|
|
66
|
+
Get-ChildItem -LiteralPath $skillsDest -Directory -ErrorAction SilentlyContinue |
|
|
67
|
+
Where-Object { $managedSkills.Name -contains $_.Name } |
|
|
68
|
+
Select-Object -ExpandProperty Name
|
|
69
|
+
)
|
|
70
|
+
if ($installedNow.Count -ne 15) {
|
|
71
|
+
throw "Claude skill install incomplete. Expected 15 managed skills at $skillsDest but found $($installedNow.Count)."
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (-not $Quiet) {
|
|
75
|
+
Write-Host ""
|
|
76
|
+
Write-Host "Install summary:"
|
|
77
|
+
Write-Host "- Scope: $Scope"
|
|
78
|
+
Write-Host "- Destination: $skillsDest"
|
|
79
|
+
Write-Host "- Copied: $copied"
|
|
80
|
+
Write-Host "- Skipped: $skipped"
|
|
81
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
param(
|
|
2
|
+
[ValidateSet('project', 'user')]
|
|
3
|
+
[string]$Scope = 'user',
|
|
4
|
+
[switch]$Force,
|
|
5
|
+
[switch]$Quiet
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
if ($Scope -eq 'project') {
|
|
9
|
+
Write-Error "Codex does not support project-local skills. Use -Scope user instead."
|
|
10
|
+
exit 1
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
$ErrorActionPreference = 'Stop'
|
|
14
|
+
Set-StrictMode -Version Latest
|
|
15
|
+
|
|
16
|
+
function Write-Utf8NoBomFile {
|
|
17
|
+
param(
|
|
18
|
+
[Parameter(Mandatory = $true)][string]$Path,
|
|
19
|
+
[Parameter(Mandatory = $true)][string]$Content
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
|
|
23
|
+
[System.IO.File]::WriteAllText($Path, $Content, $utf8NoBom)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function Rewrite-SkillFrontmatterName {
|
|
27
|
+
param(
|
|
28
|
+
[Parameter(Mandatory = $true)][string]$SkillFilePath,
|
|
29
|
+
[Parameter(Mandatory = $true)][string]$ExpectedName
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
$content = Get-Content -LiteralPath $SkillFilePath -Raw -Encoding UTF8
|
|
33
|
+
$newline = if ($content.Contains("`r`n")) { "`r`n" } else { "`n" }
|
|
34
|
+
$match = [regex]::Match(
|
|
35
|
+
$content,
|
|
36
|
+
'\A(?<prefix>\s*(?:<!--[\s\S]*?-->\s*)?)---\r?\n(?<frontmatter>[\s\S]*?)\r?\n---(?<suffix>[\s\S]*)\z'
|
|
37
|
+
)
|
|
38
|
+
if (-not $match.Success) {
|
|
39
|
+
throw "Skill file is missing a valid YAML frontmatter block: $SkillFilePath"
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
$frontmatter = $match.Groups['frontmatter'].Value
|
|
43
|
+
if ($frontmatter -notmatch '(?m)^name:\s*(?<name>[^\r\n]+)\s*$') {
|
|
44
|
+
throw "Skill file is missing a frontmatter name field: $SkillFilePath"
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
$updatedFrontmatter = [regex]::Replace(
|
|
48
|
+
$frontmatter,
|
|
49
|
+
'(?m)^name:\s*[^\r\n]+\s*$',
|
|
50
|
+
"name: $ExpectedName",
|
|
51
|
+
1
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
$updatedContent = $match.Groups['prefix'].Value + '---' + $newline + $updatedFrontmatter + $newline + '---' + $match.Groups['suffix'].Value
|
|
55
|
+
Write-Utf8NoBomFile -Path $SkillFilePath -Content $updatedContent
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
$toolkitRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
|
|
59
|
+
$skillsSource = Join-Path $toolkitRoot 'skills'
|
|
60
|
+
if (-not (Test-Path -LiteralPath $skillsSource)) {
|
|
61
|
+
throw "Missing toolkit skills source directory: $skillsSource"
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
$managedSkills = @(Get-ChildItem -LiteralPath $skillsSource -Directory | Sort-Object Name)
|
|
65
|
+
if ($managedSkills.Count -ne 15) {
|
|
66
|
+
throw "Expected 15 managed SDTK-OPS skills but found $($managedSkills.Count)."
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
$codexHome = if ($env:CODEX_HOME) { $env:CODEX_HOME } else { Join-Path $HOME '.codex' }
|
|
70
|
+
$skillsDest = Join-Path $codexHome 'skills'
|
|
71
|
+
New-Item -ItemType Directory -Force -Path $skillsDest | Out-Null
|
|
72
|
+
|
|
73
|
+
$copied = 0
|
|
74
|
+
$skipped = 0
|
|
75
|
+
foreach ($skillDir in $managedSkills) {
|
|
76
|
+
$skillFile = Join-Path $skillDir.FullName 'SKILL.md'
|
|
77
|
+
if (-not (Test-Path -LiteralPath $skillFile)) {
|
|
78
|
+
throw "Managed skill is missing SKILL.md: $($skillDir.FullName)"
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
$destName = "sdtk-$($skillDir.Name)"
|
|
82
|
+
$destDir = Join-Path $skillsDest $destName
|
|
83
|
+
if (Test-Path -LiteralPath $destDir) {
|
|
84
|
+
if (-not $Force) {
|
|
85
|
+
$skipped++
|
|
86
|
+
if (-not $Quiet) {
|
|
87
|
+
Write-Warning "Skill already installed (skipping). Use -Force to overwrite: $destName"
|
|
88
|
+
}
|
|
89
|
+
continue
|
|
90
|
+
}
|
|
91
|
+
Remove-Item -LiteralPath $destDir -Recurse -Force
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
Copy-Item -LiteralPath $skillDir.FullName -Destination $destDir -Recurse -Force
|
|
95
|
+
try {
|
|
96
|
+
Rewrite-SkillFrontmatterName -SkillFilePath (Join-Path $destDir 'SKILL.md') -ExpectedName $destName
|
|
97
|
+
} catch {
|
|
98
|
+
if (Test-Path -LiteralPath $destDir) {
|
|
99
|
+
Remove-Item -LiteralPath $destDir -Recurse -Force
|
|
100
|
+
}
|
|
101
|
+
throw
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
$copied++
|
|
105
|
+
if (-not $Quiet) {
|
|
106
|
+
Write-Host "Installed: $destName"
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
$expectedNames = $managedSkills.Name | ForEach-Object { "sdtk-$_" }
|
|
111
|
+
$installedNow = @(
|
|
112
|
+
Get-ChildItem -LiteralPath $skillsDest -Directory -ErrorAction SilentlyContinue |
|
|
113
|
+
Where-Object { $expectedNames -contains $_.Name } |
|
|
114
|
+
Select-Object -ExpandProperty Name
|
|
115
|
+
)
|
|
116
|
+
if ($installedNow.Count -ne 15) {
|
|
117
|
+
throw "Codex skill install incomplete. Expected 15 managed skills at $skillsDest but found $($installedNow.Count)."
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (-not $Quiet) {
|
|
121
|
+
Write-Host ""
|
|
122
|
+
Write-Host "Install summary:"
|
|
123
|
+
Write-Host "- Scope: user"
|
|
124
|
+
Write-Host "- Destination: $skillsDest"
|
|
125
|
+
Write-Host "- Copied: $copied"
|
|
126
|
+
Write-Host "- Skipped: $skipped"
|
|
127
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
param(
|
|
2
|
+
[string]$ProjectPath,
|
|
3
|
+
[ValidateSet('project', 'user')]
|
|
4
|
+
[string]$Scope = 'project',
|
|
5
|
+
[switch]$All,
|
|
6
|
+
[switch]$Quiet
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
$ErrorActionPreference = 'Stop'
|
|
10
|
+
Set-StrictMode -Version Latest
|
|
11
|
+
|
|
12
|
+
$toolkitRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
|
|
13
|
+
$skillsSource = Join-Path $toolkitRoot 'skills'
|
|
14
|
+
if (-not (Test-Path -LiteralPath $skillsSource)) {
|
|
15
|
+
throw "Missing toolkit skills source directory: $skillsSource"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
$managedSkillNames = @(
|
|
19
|
+
Get-ChildItem -LiteralPath $skillsSource -Directory | Sort-Object Name | Select-Object -ExpandProperty Name
|
|
20
|
+
)
|
|
21
|
+
if ($managedSkillNames.Count -ne 15) {
|
|
22
|
+
throw "Expected 15 managed SDTK-OPS skills but found $($managedSkillNames.Count)."
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if ($Scope -eq 'user') {
|
|
26
|
+
$skillsDest = Join-Path $HOME '.claude/skills'
|
|
27
|
+
} else {
|
|
28
|
+
if (-not $ProjectPath) {
|
|
29
|
+
throw "ProjectPath is required for Claude project scope uninstall."
|
|
30
|
+
}
|
|
31
|
+
if (-not (Test-Path -LiteralPath $ProjectPath)) {
|
|
32
|
+
New-Item -ItemType Directory -Force -Path $ProjectPath | Out-Null
|
|
33
|
+
}
|
|
34
|
+
$projectRoot = (Resolve-Path -LiteralPath $ProjectPath).Path
|
|
35
|
+
$skillsDest = Join-Path $projectRoot '.claude/skills'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (-not (Test-Path -LiteralPath $skillsDest)) {
|
|
39
|
+
if (-not $Quiet) {
|
|
40
|
+
Write-Host "Claude skills directory not found: $skillsDest"
|
|
41
|
+
}
|
|
42
|
+
exit 0
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
$removed = New-Object System.Collections.Generic.List[string]
|
|
46
|
+
$missing = New-Object System.Collections.Generic.List[string]
|
|
47
|
+
foreach ($name in $managedSkillNames) {
|
|
48
|
+
$dest = Join-Path $skillsDest $name
|
|
49
|
+
if (-not (Test-Path -LiteralPath $dest)) {
|
|
50
|
+
$missing.Add($name) | Out-Null
|
|
51
|
+
continue
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
Remove-Item -LiteralPath $dest -Recurse -Force
|
|
55
|
+
$removed.Add($name) | Out-Null
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (-not $Quiet) {
|
|
59
|
+
Write-Host ""
|
|
60
|
+
Write-Host "Uninstall summary:"
|
|
61
|
+
Write-Host "- Scope: $Scope"
|
|
62
|
+
Write-Host "- Destination: $skillsDest"
|
|
63
|
+
Write-Host "- Removed: $($removed.Count)"
|
|
64
|
+
Write-Host "- Missing/Skipped: $($missing.Count)"
|
|
65
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
param(
|
|
2
|
+
[switch]$All,
|
|
3
|
+
[switch]$Quiet
|
|
4
|
+
)
|
|
5
|
+
|
|
6
|
+
$ErrorActionPreference = 'Stop'
|
|
7
|
+
Set-StrictMode -Version Latest
|
|
8
|
+
|
|
9
|
+
$toolkitRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
|
|
10
|
+
$skillsSource = Join-Path $toolkitRoot 'skills'
|
|
11
|
+
if (-not (Test-Path -LiteralPath $skillsSource)) {
|
|
12
|
+
throw "Missing toolkit skills source directory: $skillsSource"
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
$managedSkillNames = @(
|
|
16
|
+
Get-ChildItem -LiteralPath $skillsSource -Directory |
|
|
17
|
+
Sort-Object Name |
|
|
18
|
+
ForEach-Object { "sdtk-$($_.Name)" }
|
|
19
|
+
)
|
|
20
|
+
if ($managedSkillNames.Count -ne 15) {
|
|
21
|
+
throw "Expected 15 managed SDTK-OPS skills but found $($managedSkillNames.Count)."
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
$codexHome = if ($env:CODEX_HOME) { $env:CODEX_HOME } else { Join-Path $HOME '.codex' }
|
|
25
|
+
$skillsDest = Join-Path $codexHome 'skills'
|
|
26
|
+
if (-not (Test-Path -LiteralPath $skillsDest)) {
|
|
27
|
+
if (-not $Quiet) {
|
|
28
|
+
Write-Host "Codex skills directory not found: $skillsDest"
|
|
29
|
+
}
|
|
30
|
+
exit 0
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
$removed = New-Object System.Collections.Generic.List[string]
|
|
34
|
+
$missing = New-Object System.Collections.Generic.List[string]
|
|
35
|
+
foreach ($name in $managedSkillNames) {
|
|
36
|
+
$dest = Join-Path $skillsDest $name
|
|
37
|
+
if (-not (Test-Path -LiteralPath $dest)) {
|
|
38
|
+
$missing.Add($name) | Out-Null
|
|
39
|
+
continue
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
Remove-Item -LiteralPath $dest -Recurse -Force
|
|
43
|
+
$removed.Add($name) | Out-Null
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (-not $Quiet) {
|
|
47
|
+
Write-Host ""
|
|
48
|
+
Write-Host "Uninstall summary:"
|
|
49
|
+
Write-Host "- Scope: user"
|
|
50
|
+
Write-Host "- Destination: $skillsDest"
|
|
51
|
+
Write-Host "- Removed: $($removed.Count)"
|
|
52
|
+
Write-Host "- Missing/Skipped: $($missing.Count)"
|
|
53
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "Example profiles config for SDTK-OPS. Copy to sdtk-spec.config.profiles.json and customize for your internal runtime targets.",
|
|
3
|
+
"profiles": {
|
|
4
|
+
"default": {
|
|
5
|
+
"stack": {
|
|
6
|
+
"backend": "UPDATE_ME",
|
|
7
|
+
"frontend": "UPDATE_ME",
|
|
8
|
+
"database": "UPDATE_ME"
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<!-- Based on agency-agents by AgentLand Contributors (MIT License, 2025). Adapted for SDTK-OPS. -->
|
|
2
|
+
---
|
|
3
|
+
name: ops-backup
|
|
4
|
+
description: Backup and disaster recovery. Use when designing backup strategies, disaster recovery plans, or restore procedures -- covers RTO/RPO definition, backup verification, and cross-region replication.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Ops Backup
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
Backups are only valuable if they can be restored inside the time and data-loss limits the business expects. Define recovery targets first, automate execution, store copies away from the primary failure zone, and test restores on a schedule.
|
|
12
|
+
|
|
13
|
+
## The Iron Law
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
NO BACKUP STRATEGY IS COMPLETE UNTIL A RESTORE HAS BEEN TESTED
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## RTO And RPO Framework
|
|
20
|
+
|
|
21
|
+
- **RTO**
|
|
22
|
+
- recovery time objective
|
|
23
|
+
- how long the service can be unavailable
|
|
24
|
+
- **RPO**
|
|
25
|
+
- recovery point objective
|
|
26
|
+
- how much data loss is acceptable
|
|
27
|
+
|
|
28
|
+
## Service Tiers
|
|
29
|
+
|
|
30
|
+
| Tier | RPO | RTO | Replication Scope |
|
|
31
|
+
|------|-----|-----|-------------------|
|
|
32
|
+
| Critical | under 5 min | under 30 min | cross-region |
|
|
33
|
+
| Important | under 1 hour | under 4 hours | cross-availability-zone |
|
|
34
|
+
| Standard | under 24 hours | under 24 hours | same region |
|
|
35
|
+
|
|
36
|
+
## Backup Strategies
|
|
37
|
+
|
|
38
|
+
| Strategy | When To Use | Typical RPO | Storage Cost |
|
|
39
|
+
|----------|-------------|-------------|--------------|
|
|
40
|
+
| Full | small datasets or periodic baselines | longer | high |
|
|
41
|
+
| Incremental | regular backups with storage efficiency | medium | low |
|
|
42
|
+
| Differential | simpler restore chain with moderate storage use | medium | medium |
|
|
43
|
+
| Continuous replication | critical systems with near-real-time recovery needs | very low | high |
|
|
44
|
+
|
|
45
|
+
## <HARD-GATE>
|
|
46
|
+
|
|
47
|
+
Do not claim backup coverage until all are true:
|
|
48
|
+
- execution is automated
|
|
49
|
+
- backup data is encrypted at rest with GPG, KMS, or equivalent
|
|
50
|
+
- an off-site copy exists in a different region or failure domain
|
|
51
|
+
- retention and cleanup are enforced automatically
|
|
52
|
+
- restore testing happens at least quarterly with documented results
|
|
53
|
+
- monitoring alerts fire on backup failure
|
|
54
|
+
|
|
55
|
+
## Monthly Restore Drill
|
|
56
|
+
|
|
57
|
+
Run this procedure:
|
|
58
|
+
1. select a recent backup at random
|
|
59
|
+
2. restore it into an isolated environment
|
|
60
|
+
3. verify data integrity
|
|
61
|
+
4. verify the application can use the restored data
|
|
62
|
+
5. record total restore time and any issues
|
|
63
|
+
6. update the runbook if the drill exposed gaps
|
|
64
|
+
|
|
65
|
+
## Disaster Recovery Plan Template
|
|
66
|
+
|
|
67
|
+
| Component | Primary Region | DR Region | Failover Method | RTO |
|
|
68
|
+
|-----------|----------------|-----------|-----------------|-----|
|
|
69
|
+
| api | primary-a | secondary-a | redeploy from immutable artifact | 30 min |
|
|
70
|
+
| database | primary-a | secondary-a | replica promote or restore | 30 min |
|
|
71
|
+
| object storage | primary-a | secondary-a | replicated bucket failover | 60 min |
|
|
72
|
+
|
|
73
|
+
## Script Patterns
|
|
74
|
+
|
|
75
|
+
Use `./references/backup-script-patterns.md` for backup, encryption, verification, upload, and retention patterns. Treat provider-specific storage commands as replaceable implementation details.
|
|
76
|
+
|
|
77
|
+
## Common Mistakes
|
|
78
|
+
|
|
79
|
+
| Mistake | Why it fails |
|
|
80
|
+
|---------|--------------|
|
|
81
|
+
| Never test restores | Teams discover backup gaps during the outage |
|
|
82
|
+
| Keep backup copies in the same failure domain | Regional or account-level failure destroys recovery path |
|
|
83
|
+
| Skip encryption | Backup media becomes a security incident |
|
|
84
|
+
| No retention policy | Storage grows without limit and old data stays forever |
|
|
85
|
+
| Manual backup execution | The process fails precisely when people are busiest |
|
|
86
|
+
|
|
87
|
+
## Execution Handoff
|
|
88
|
+
|
|
89
|
+
After backup design is approved:
|
|
90
|
+
- implement storage and retention changes through `ops-infra-plan`
|
|
91
|
+
- validate restore evidence with `ops-verify`
|
|
92
|
+
- use `ops-monitor` so backup failures page the right team
|
|
93
|
+
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
<!-- Based on agency-agents by AgentLand Contributors (MIT License, 2025). Adapted for SDTK-OPS. -->
|
|
2
|
+
|
|
3
|
+
# Backup Script Patterns
|
|
4
|
+
|
|
5
|
+
## Shell Pattern
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
#!/usr/bin/env bash
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
BACKUP_ROOT="${BACKUP_ROOT:-/backups}"
|
|
12
|
+
LOG_FILE="${LOG_FILE:-/var/log/backup.log}"
|
|
13
|
+
RETENTION_DAYS="${RETENTION_DAYS:-30}"
|
|
14
|
+
ENCRYPTION_KEY="${ENCRYPTION_KEY:?Set ENCRYPTION_KEY to a passphrase file or key reference}"
|
|
15
|
+
OBJECT_STORE_TARGET="${OBJECT_STORE_TARGET:?Set OBJECT_STORE_TARGET to the remote backup location}"
|
|
16
|
+
NOTIFICATION_WEBHOOK="${NOTIFICATION_WEBHOOK:?Set NOTIFICATION_WEBHOOK through the environment}"
|
|
17
|
+
|
|
18
|
+
log() {
|
|
19
|
+
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
notify_failure() {
|
|
23
|
+
local message="$1"
|
|
24
|
+
curl -X POST -H 'Content-type: application/json' \
|
|
25
|
+
--data "{\"text\":\"Backup failed: $message\"}" \
|
|
26
|
+
"$NOTIFICATION_WEBHOOK"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
handle_error() {
|
|
30
|
+
local error_message="$1"
|
|
31
|
+
log "ERROR: $error_message"
|
|
32
|
+
notify_failure "$error_message"
|
|
33
|
+
exit 1
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
backup_database() {
|
|
37
|
+
local db_name="$1"
|
|
38
|
+
local backup_file="${BACKUP_ROOT}/db/${db_name}_$(date +%Y%m%d_%H%M%S).sql.gz"
|
|
39
|
+
|
|
40
|
+
mkdir -p "$(dirname "$backup_file")"
|
|
41
|
+
|
|
42
|
+
if ! pg_dump -h "$DB_HOST" -U "$DB_USER" -d "$db_name" | gzip > "$backup_file"; then
|
|
43
|
+
handle_error "Database backup failed for $db_name"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
if ! gpg --cipher-algo AES256 --compress-algo 1 --s2k-mode 3 \
|
|
47
|
+
--s2k-digest-algo SHA512 --s2k-count 65536 --symmetric \
|
|
48
|
+
--passphrase-file "$ENCRYPTION_KEY" "$backup_file"; then
|
|
49
|
+
handle_error "Database backup encryption failed for $db_name"
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
rm "$backup_file"
|
|
53
|
+
log "Database backup completed for $db_name"
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
backup_files() {
|
|
57
|
+
local source_dir="$1"
|
|
58
|
+
local backup_name="$2"
|
|
59
|
+
local backup_file="${BACKUP_ROOT}/files/${backup_name}_$(date +%Y%m%d_%H%M%S).tar.gz.gpg"
|
|
60
|
+
|
|
61
|
+
mkdir -p "$(dirname "$backup_file")"
|
|
62
|
+
|
|
63
|
+
if ! tar -czf - -C "$source_dir" . | \
|
|
64
|
+
gpg --cipher-algo AES256 --compress-algo 0 --s2k-mode 3 \
|
|
65
|
+
--s2k-digest-algo SHA512 --s2k-count 65536 --symmetric \
|
|
66
|
+
--passphrase-file "$ENCRYPTION_KEY" \
|
|
67
|
+
--output "$backup_file"; then
|
|
68
|
+
handle_error "File backup failed for $source_dir"
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
log "File backup completed for $source_dir"
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
verify_backup() {
|
|
75
|
+
local backup_file="$1"
|
|
76
|
+
|
|
77
|
+
if ! gpg --quiet --batch --passphrase-file "$ENCRYPTION_KEY" \
|
|
78
|
+
--decrypt "$backup_file" > /dev/null 2>&1; then
|
|
79
|
+
handle_error "Backup integrity check failed for $backup_file"
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
log "Backup integrity verified for $backup_file"
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
upload_backup() {
|
|
86
|
+
local local_file="$1"
|
|
87
|
+
local remote_path="$2"
|
|
88
|
+
|
|
89
|
+
if ! cloud-storage-copy "$local_file" "${OBJECT_STORE_TARGET}/${remote_path}"; then
|
|
90
|
+
handle_error "Remote upload failed for $local_file"
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
log "Remote upload completed for $local_file"
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
cleanup_old_backups() {
|
|
97
|
+
find "$BACKUP_ROOT" -name "*.gpg" -mtime +"$RETENTION_DAYS" -delete
|
|
98
|
+
log "Cleanup completed"
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Pattern Notes
|
|
103
|
+
|
|
104
|
+
- keep notification endpoints in environment variables, never in version control
|
|
105
|
+
- replace `cloud-storage-copy` with the provider-specific upload command used by your platform
|
|
106
|
+
- verify every backup after encryption and before declaring success
|
|
107
|
+
- keep restore testing separate from production systems
|
|
108
|
+
|