sdtk-kit 0.3.3 → 0.3.4

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 CHANGED
@@ -36,7 +36,9 @@ sdtk generate --feature-key USER_PROFILE --feature-name "User Profile"
36
36
  Initialize SDTK workspace in the current or specified project directory.
37
37
 
38
38
  ```bash
39
- sdtk init --runtime <codex|claude> [--project-path <path>] [--force] [--skip-skills]
39
+ sdtk init --runtime <codex|claude> [--project-path <path>] [--force] [--runtime-scope <project|user>] [--skip-runtime-assets]
40
+
41
+ # Deprecated: --skip-skills (use --skip-runtime-assets instead)
40
42
  ```
41
43
 
42
44
  Creates:
@@ -44,7 +46,9 @@ Creates:
44
46
  - `sdtk.config.json` -- project configuration
45
47
  - `sdtk.config.profiles.example.json` -- stack profile examples
46
48
  - `CODEX.md` or `CLAUDE.md` -- runtime adapter
47
- - for `--runtime claude`, local `.claude/skills` and `.claude/skills/references` are installed unless `--skip-skills` is used
49
+ - for `--runtime claude`, skill files are installed into `.claude/skills/` (project scope, default) or `~/.claude/skills/` (user scope) unless `--skip-runtime-assets` is used
50
+ - for `--runtime codex`, skill files are installed into `$CODEX_HOME/skills/` or `~/.codex/skills/` (user scope only) unless `--skip-runtime-assets` is used
51
+ - `--skip-skills` is deprecated; use `--skip-runtime-assets` instead
48
52
 
49
53
  ### `sdtk auth`
50
54
 
@@ -83,6 +87,36 @@ sdtk generate --feature-key <UPPER_SNAKE_CASE> --feature-name "<text>" [--projec
83
87
 
84
88
  Output files include: project initiation, BA spec, flow-action spec, PRD, backlog, architecture design, database spec, API specs (OpenAPI + endpoints + design detail + flow list), UI design layout, implementation plan, test cases, and QA release report.
85
89
 
90
+ ### `sdtk runtime`
91
+
92
+ Manage runtime skill assets independently of `sdtk init`.
93
+
94
+ ```bash
95
+ sdtk runtime install --runtime <codex|claude> [--scope <project|user>]
96
+ sdtk runtime uninstall --runtime <codex|claude> [--scope <project|user>]
97
+ sdtk runtime status --runtime <codex|claude>
98
+ ```
99
+
100
+ Scope defaults:
101
+ - `claude`: `project` (installs to `.claude/skills/`); `user` installs to `~/.claude/skills/`
102
+ - `codex`: `user` only (installs to `$CODEX_HOME/skills/` or `~/.codex/skills/`); project scope is not supported (Gate C0)
103
+
104
+ Examples:
105
+
106
+ ```bash
107
+ # Install Claude skills at project scope (default)
108
+ sdtk runtime install --runtime claude
109
+
110
+ # Install Claude skills at user scope
111
+ sdtk runtime install --runtime claude --scope user
112
+
113
+ # Check installed runtime assets
114
+ sdtk runtime status --runtime claude
115
+
116
+ # Remove runtime assets
117
+ sdtk runtime uninstall --runtime claude --scope project
118
+ ```
119
+
86
120
  ### `sdtk --help` / `sdtk --version`
87
121
 
88
122
  ```bash
@@ -1,5 +1,5 @@
1
1
  {
2
- "buildTimestamp": "2026-03-07T00:53:22Z",
2
+ "buildTimestamp": "2026-03-07T02:40:24Z",
3
3
  "files": [
4
4
  {
5
5
  "path": "toolkit/AGENTS.md",
@@ -8,8 +8,8 @@
8
8
  },
9
9
  {
10
10
  "path": "toolkit/install.ps1",
11
- "sha256": "531da17e88a05b6946a5012f7b13a2871212df1fe0d052720f9511e02aba7e32",
12
- "size": 8830
11
+ "sha256": "e717a2791fb597d7a21f57f4c4790dbc545e954f0af3ca0c47f160f78c8a2474",
12
+ "size": 9777
13
13
  },
14
14
  {
15
15
  "path": "toolkit/runtimes/claude/CLAUDE_TEMPLATE.md",
@@ -26,10 +26,20 @@
26
26
  "sha256": "5c1f5442fd3c26b8bf62db4b25e9f1c4207258c7fe52f12ed533968f77dfbf65",
27
27
  "size": 10557
28
28
  },
29
+ {
30
+ "path": "toolkit/scripts/install-claude-skills.ps1",
31
+ "sha256": "8c66c27262ad4abc3c42bbcbe3a97752a5f29956ac93397133e6780354911549",
32
+ "size": 4315
33
+ },
29
34
  {
30
35
  "path": "toolkit/scripts/install-codex-skills.ps1",
31
- "sha256": "68a82e421d0309e47cdb79e949b1044c17375ad7fd52e2ab9ccdc9dd33f8cdd0",
32
- "size": 9175
36
+ "sha256": "ca64e0c2a0f13137bc6b780a57442de46a3e0551778bbcbadd4eb779c21cb203",
37
+ "size": 9427
38
+ },
39
+ {
40
+ "path": "toolkit/scripts/uninstall-claude-skills.ps1",
41
+ "sha256": "809ffc1f96528ac1cd453214cd1c2d231ca0753f0d3c19b4a20f11835a64a152",
42
+ "size": 4482
33
43
  },
34
44
  {
35
45
  "path": "toolkit/scripts/uninstall-codex-skills.ps1",
@@ -397,7 +407,7 @@
397
407
  "size": 3255
398
408
  }
399
409
  ],
400
- "sourceCommit": "4437ad41659a38e9e80dcbf32cb03f9b6df64e0d",
401
- "version": "0.3.3",
402
- "fileCount": 79
410
+ "sourceCommit": "965d6ad1f80a42b8b49c4296ec7a96ffef2655e3",
411
+ "version": "0.3.4",
412
+ "fileCount": 81
403
413
  }
@@ -1,9 +1,11 @@
1
1
  868430474b46e4e8d27ed26b780a794936e84cf4f0278f2cc3d061f54877f498 toolkit/AGENTS.md
2
- 531da17e88a05b6946a5012f7b13a2871212df1fe0d052720f9511e02aba7e32 toolkit/install.ps1
2
+ e717a2791fb597d7a21f57f4c4790dbc545e954f0af3ca0c47f160f78c8a2474 toolkit/install.ps1
3
3
  0f3e0bfe1b5165250f0da4da5790a9e6220023752713088a0076eb7f17fd6397 toolkit/runtimes/claude/CLAUDE_TEMPLATE.md
4
4
  4154c15c71f44d2f2caf07fb41722fa65d4f9ec7e78f798ee084effd12345c62 toolkit/runtimes/codex/CODEX_TEMPLATE.md
5
5
  5c1f5442fd3c26b8bf62db4b25e9f1c4207258c7fe52f12ed533968f77dfbf65 toolkit/scripts/init-feature.ps1
6
- 68a82e421d0309e47cdb79e949b1044c17375ad7fd52e2ab9ccdc9dd33f8cdd0 toolkit/scripts/install-codex-skills.ps1
6
+ 8c66c27262ad4abc3c42bbcbe3a97752a5f29956ac93397133e6780354911549 toolkit/scripts/install-claude-skills.ps1
7
+ ca64e0c2a0f13137bc6b780a57442de46a3e0551778bbcbadd4eb779c21cb203 toolkit/scripts/install-codex-skills.ps1
8
+ 809ffc1f96528ac1cd453214cd1c2d231ca0753f0d3c19b4a20f11835a64a152 toolkit/scripts/uninstall-claude-skills.ps1
7
9
  e0d462bd6dcdb17cc003d459063ff341bd2e4f60b245cda9b7e9b9e94b3cf01f toolkit/scripts/uninstall-codex-skills.ps1
8
10
  de2921da9ce504487d9c7d97bf862b65d0ba00b9f1593a4fbfadf41ac36adb49 toolkit/sdtk.config.json
9
11
  21b1b0212f85c76dc6a89ae5936b39c4c1df7837a333f7a3db337f3039babb7b toolkit/sdtk.config.profiles.example.json
@@ -2,9 +2,12 @@ param(
2
2
  [string]$ProjectPath,
3
3
  [switch]$Force,
4
4
  [switch]$SkipSkills,
5
+ [switch]$SkipRuntimeAssets,
5
6
  [switch]$Quiet,
6
7
  [ValidateSet('codex', 'claude')]
7
- [string]$Runtime = 'codex'
8
+ [string]$Runtime = 'codex',
9
+ [ValidateSet('project', 'user', '')]
10
+ [string]$Scope = ''
8
11
  )
9
12
 
10
13
  $ErrorActionPreference = 'Stop'
@@ -181,6 +184,15 @@ if (-not $ProjectPath) {
181
184
  }
182
185
  $projectRoot = Resolve-Path -LiteralPath $ProjectPath
183
186
 
187
+ # Merge SkipRuntimeAssets and legacy SkipSkills
188
+ if ($SkipRuntimeAssets) { $SkipSkills = $true }
189
+
190
+ # Default scope: project for Claude, user for Codex
191
+ if (-not $Scope -or $Scope -eq '') {
192
+ if ($Runtime -eq 'claude') { $Scope = 'project' }
193
+ else { $Scope = 'user' }
194
+ }
195
+
184
196
  if (-not $Quiet) {
185
197
  Write-Host "SDTK toolkit : $toolkitRoot"
186
198
  Write-Host "Project root : $projectRoot"
@@ -217,17 +229,29 @@ if (($Runtime -eq 'codex') -and (-not $SkipSkills)) {
217
229
  }
218
230
 
219
231
  Write-Host ""
220
- Write-Host "Installing Codex skills globally..."
232
+ Write-Host "Installing Codex skills (scope: $Scope)..."
221
233
  if ($Force) {
222
- & $skillInstaller -Force | Out-Host
234
+ & $skillInstaller -Scope $Scope -Force | Out-Host
223
235
  } else {
224
- & $skillInstaller | Out-Host
236
+ & $skillInstaller -Scope $Scope | Out-Host
225
237
  }
226
238
  }
227
239
  elseif (($Runtime -eq 'claude') -and (-not $SkipSkills)) {
228
- Write-Host ""
229
- Write-Host "Installing Claude Code skills..."
230
- Install-ClaudeSkills -ToolkitRoot $toolkitRoot -ProjectRoot $projectRoot -Overwrite ([bool]$Force)
240
+ $skillInstaller = Join-Path $toolkitRoot 'scripts/install-claude-skills.ps1'
241
+ if (-not (Test-Path -LiteralPath $skillInstaller)) {
242
+ # Fallback to inline Install-ClaudeSkills for backward compatibility
243
+ Write-Host ""
244
+ Write-Host "Installing Claude Code skills..."
245
+ Install-ClaudeSkills -ToolkitRoot $toolkitRoot -ProjectRoot $projectRoot -Overwrite ([bool]$Force)
246
+ } else {
247
+ Write-Host ""
248
+ Write-Host "Installing Claude Code skills (scope: $Scope)..."
249
+ if ($Force) {
250
+ & $skillInstaller -Scope $Scope -ProjectPath $projectRoot.ToString() -Force | Out-Host
251
+ } else {
252
+ & $skillInstaller -Scope $Scope -ProjectPath $projectRoot.ToString() | Out-Host
253
+ }
254
+ }
231
255
  }
232
256
 
233
257
  if (-not $Quiet) {
@@ -0,0 +1,129 @@
1
+ param(
2
+ [string]$ProjectPath,
3
+ [ValidateSet('project', 'user')]
4
+ [string]$Scope = 'project',
5
+ [switch]$Force
6
+ )
7
+
8
+ $ErrorActionPreference = 'Stop'
9
+ Set-StrictMode -Version Latest
10
+
11
+ function Copy-File {
12
+ param(
13
+ [Parameter(Mandatory = $true)][string]$SourcePath,
14
+ [Parameter(Mandatory = $true)][string]$DestinationPath,
15
+ [Parameter(Mandatory = $true)][bool]$Overwrite
16
+ )
17
+
18
+ if (-not (Test-Path -LiteralPath $SourcePath)) {
19
+ throw "Missing source: $SourcePath"
20
+ }
21
+
22
+ if (Test-Path -LiteralPath $DestinationPath) {
23
+ if (-not $Overwrite) {
24
+ Write-Warning "Already exists (skipping). Use -Force to overwrite: $DestinationPath"
25
+ return
26
+ }
27
+ }
28
+
29
+ $parent = Split-Path -Parent $DestinationPath
30
+ if ($parent -and -not (Test-Path -LiteralPath $parent)) {
31
+ New-Item -ItemType Directory -Force -Path $parent | Out-Null
32
+ }
33
+
34
+ Copy-Item -LiteralPath $SourcePath -Destination $DestinationPath -Force
35
+ }
36
+
37
+ $toolkitRoot = Resolve-Path (Join-Path $PSScriptRoot '..')
38
+
39
+ # Resolve destination based on scope
40
+ if ($Scope -eq 'user') {
41
+ $skillsDest = Join-Path $HOME '.claude/skills'
42
+ Write-Host "Scope: user (installing to $skillsDest)"
43
+ } else {
44
+ if (-not $ProjectPath) {
45
+ $ProjectPath = (Resolve-Path (Join-Path $toolkitRoot '..')).Path
46
+ }
47
+ $projectRoot = Resolve-Path -LiteralPath $ProjectPath
48
+ $skillsDest = Join-Path $projectRoot '.claude/skills'
49
+ Write-Host "Scope: project (installing to $skillsDest)"
50
+ }
51
+
52
+ $skillsSource = Join-Path $toolkitRoot 'skills-claude'
53
+ if (-not (Test-Path -LiteralPath $skillsSource)) {
54
+ throw "Claude skills source not found: $skillsSource"
55
+ }
56
+
57
+ $skillCount = 0
58
+ foreach ($skillDir in (Get-ChildItem -Path $skillsSource -Directory)) {
59
+ $srcFile = Join-Path $skillDir.FullName 'SKILL.md'
60
+ if (-not (Test-Path -LiteralPath $srcFile)) { continue }
61
+
62
+ $destFile = Join-Path $skillsDest "$($skillDir.Name)/SKILL.md"
63
+ Copy-File -SourcePath $srcFile -DestinationPath $destFile -Overwrite ([bool]$Force)
64
+ $skillCount++
65
+ }
66
+
67
+ # Install reference files
68
+ $refDest = Join-Path $skillsDest 'references'
69
+ if (-not (Test-Path -LiteralPath $refDest)) {
70
+ New-Item -ItemType Directory -Force -Path $refDest | Out-Null
71
+ }
72
+
73
+ $refCount = 0
74
+ $missingRefs = @()
75
+
76
+ # 6 files from toolkit/templates/docs/
77
+ $templateRefs = @(
78
+ @{ Src = 'templates/docs/api/YAML_CREATION_RULES.md' },
79
+ @{ Src = 'templates/docs/api/API_DESIGN_FLOWCHART_CREATION_RULES.md' },
80
+ @{ Src = 'templates/docs/api/FLOWCHART_CREATION_RULES.md' },
81
+ @{ Src = 'templates/docs/api/API_DESIGN_CREATION_RULES.md' },
82
+ @{ Src = 'templates/docs/specs/FLOW_ACTION_SPEC_CREATION_RULES.md' },
83
+ @{ Src = 'templates/docs/qa/TEST_CASE_CREATION_RULES.md' }
84
+ )
85
+
86
+ foreach ($ref in $templateRefs) {
87
+ $srcPath = Join-Path $toolkitRoot $ref.Src
88
+ $fileName = Split-Path -Leaf $srcPath
89
+ $destPath = Join-Path $refDest $fileName
90
+ if (Test-Path -LiteralPath $srcPath) {
91
+ Copy-File -SourcePath $srcPath -DestinationPath $destPath -Overwrite ([bool]$Force)
92
+ $refCount++
93
+ } else {
94
+ $missingRefs += $srcPath
95
+ }
96
+ }
97
+
98
+ # 3 files from toolkit/skills/sdtk-screen-design-spec/references/
99
+ $screenRefs = @('numbering-rules.md', 'figma-mcp.md', 'excel-image-export.md')
100
+ foreach ($fileName in $screenRefs) {
101
+ $srcPath = Join-Path $toolkitRoot "skills/sdtk-screen-design-spec/references/$fileName"
102
+ $destPath = Join-Path $refDest $fileName
103
+ if (Test-Path -LiteralPath $srcPath) {
104
+ Copy-File -SourcePath $srcPath -DestinationPath $destPath -Overwrite ([bool]$Force)
105
+ $refCount++
106
+ } else {
107
+ $missingRefs += $srcPath
108
+ }
109
+ }
110
+
111
+ # Fail-fast: abort if any reference files are missing
112
+ if ($missingRefs.Count -gt 0) {
113
+ $list = ($missingRefs | ForEach-Object { " - $_" }) -join "`n"
114
+ throw "Claude install failed. Missing reference files:`n$list"
115
+ }
116
+
117
+ # Strict count assertions
118
+ $expectedSkills = 13
119
+ $expectedRefs = 9
120
+ if ($skillCount -ne $expectedSkills) {
121
+ throw "Claude install failed. Expected $expectedSkills skills but installed $skillCount."
122
+ }
123
+ if ($refCount -ne $expectedRefs) {
124
+ throw "Claude install failed. Expected $expectedRefs reference files but installed $refCount."
125
+ }
126
+
127
+ Write-Host " Skills installed: $skillCount"
128
+ Write-Host " Reference files : $refCount"
129
+ Write-Host " Destination : $skillsDest"
@@ -1,7 +1,15 @@
1
1
  param(
2
+ [ValidateSet('project', 'user')]
3
+ [string]$Scope = 'user',
2
4
  [switch]$Force
3
5
  )
4
6
 
7
+ # Gate C0: Codex does not support project-local skills
8
+ if ($Scope -eq 'project') {
9
+ Write-Error "Codex does not support project-local skills. Use --scope user instead."
10
+ exit 1
11
+ }
12
+
5
13
  $ErrorActionPreference = 'Stop'
6
14
  Set-StrictMode -Version Latest
7
15
 
@@ -0,0 +1,139 @@
1
+ param(
2
+ [string]$ProjectPath,
3
+ [ValidateSet('project', 'user')]
4
+ [string]$Scope = 'project',
5
+ [string]$SkillName,
6
+ [switch]$All,
7
+ [switch]$BackupExisting,
8
+ [string]$BackupPath
9
+ )
10
+
11
+ $ErrorActionPreference = 'Stop'
12
+ Set-StrictMode -Version Latest
13
+
14
+ function Backup-Directory {
15
+ param(
16
+ [Parameter(Mandatory = $true)][string]$SourcePath,
17
+ [Parameter(Mandatory = $true)][string]$BackupRoot,
18
+ [Parameter(Mandatory = $true)][string]$Name
19
+ )
20
+
21
+ if (-not (Test-Path -LiteralPath $SourcePath)) {
22
+ return $null
23
+ }
24
+
25
+ New-Item -ItemType Directory -Force -Path $BackupRoot | Out-Null
26
+ $dest = Join-Path $BackupRoot $Name
27
+ if (Test-Path -LiteralPath $dest) {
28
+ Remove-Item -LiteralPath $dest -Recurse -Force
29
+ }
30
+ Copy-Item -LiteralPath $SourcePath -Destination $dest -Recurse -Force
31
+ return $dest
32
+ }
33
+
34
+ if ($All -and $SkillName) {
35
+ throw "Use either -All or -SkillName, not both."
36
+ }
37
+
38
+ $toolkitRoot = Resolve-Path (Join-Path $PSScriptRoot '..')
39
+ $skillsSrc = Join-Path $toolkitRoot 'skills-claude'
40
+ if (-not (Test-Path -LiteralPath $skillsSrc)) {
41
+ throw "Missing toolkit Claude skills source directory: $skillsSrc"
42
+ }
43
+
44
+ $managedSkillNames = Get-ChildItem -LiteralPath $skillsSrc -Directory | Select-Object -ExpandProperty Name
45
+ if (-not $managedSkillNames -or $managedSkillNames.Count -eq 0) {
46
+ throw "No managed Claude skills found in: $skillsSrc"
47
+ }
48
+
49
+ $targetNames = @()
50
+ if ($SkillName) {
51
+ if ($managedSkillNames -notcontains $SkillName) {
52
+ throw "Skill '$SkillName' is not in managed Claude skills list. Known skills: $($managedSkillNames -join ', ')"
53
+ }
54
+ $targetNames = @($SkillName)
55
+ } elseif ($All) {
56
+ $targetNames = @($managedSkillNames)
57
+ } else {
58
+ $targetNames = @($managedSkillNames)
59
+ }
60
+
61
+ # Resolve destination based on scope
62
+ if ($Scope -eq 'user') {
63
+ $skillsDest = Join-Path $HOME '.claude/skills'
64
+ } else {
65
+ if (-not $ProjectPath) {
66
+ $ProjectPath = (Resolve-Path (Join-Path $toolkitRoot '..')).Path
67
+ }
68
+ $projectRoot = Resolve-Path -LiteralPath $ProjectPath
69
+ $skillsDest = Join-Path $projectRoot '.claude/skills'
70
+ }
71
+
72
+ if (-not (Test-Path -LiteralPath $skillsDest)) {
73
+ Write-Host "Claude skills directory not found: $skillsDest"
74
+ exit 0
75
+ }
76
+
77
+ $backupRootResolved = $null
78
+ if ($BackupExisting) {
79
+ if (-not $BackupPath -or $BackupPath.Trim().Length -eq 0) {
80
+ $timestamp = Get-Date -Format 'yyyyMMdd-HHmmss'
81
+ $parentDir = if ($Scope -eq 'user') { Join-Path $HOME '.claude' } else { $projectRoot }
82
+ $BackupPath = Join-Path $parentDir (Join-Path 'skills-backups' ("uninstall-" + $timestamp))
83
+ }
84
+ New-Item -ItemType Directory -Force -Path $BackupPath | Out-Null
85
+ $backupRootResolved = (Resolve-Path -LiteralPath $BackupPath).Path
86
+ Write-Host "Backup mode enabled: $backupRootResolved"
87
+ }
88
+
89
+ $removed = New-Object System.Collections.Generic.List[string]
90
+ $missing = New-Object System.Collections.Generic.List[string]
91
+
92
+ foreach ($name in $targetNames) {
93
+ $dest = Join-Path $skillsDest $name
94
+ if (-not (Test-Path -LiteralPath $dest)) {
95
+ $missing.Add($name) | Out-Null
96
+ Write-Warning "Skill not installed, skipping: $name"
97
+ continue
98
+ }
99
+
100
+ if ($BackupExisting) {
101
+ $backupDest = Backup-Directory -SourcePath $dest -BackupRoot $backupRootResolved -Name $name
102
+ if ($backupDest) {
103
+ Write-Host "Backed up: $name -> $backupDest"
104
+ }
105
+ }
106
+
107
+ Remove-Item -LiteralPath $dest -Recurse -Force
108
+ $removed.Add($name) | Out-Null
109
+ Write-Host "Uninstalled: $name"
110
+ }
111
+
112
+ # Also remove references directory if uninstalling all
113
+ if ($All -or (-not $SkillName)) {
114
+ $refDir = Join-Path $skillsDest 'references'
115
+ if (Test-Path -LiteralPath $refDir) {
116
+ if ($BackupExisting) {
117
+ Backup-Directory -SourcePath $refDir -BackupRoot $backupRootResolved -Name 'references' | Out-Null
118
+ Write-Host "Backed up: references -> $backupRootResolved/references"
119
+ }
120
+ Remove-Item -LiteralPath $refDir -Recurse -Force
121
+ Write-Host "Removed references directory."
122
+ }
123
+ }
124
+
125
+ Write-Host ""
126
+ Write-Host "Uninstall summary:"
127
+ Write-Host "- Scope: $Scope"
128
+ Write-Host "- Destination: $skillsDest"
129
+ Write-Host "- Removed: $($removed.Count)"
130
+ if ($removed.Count -gt 0) {
131
+ $removed | ForEach-Object { Write-Host " - $_" }
132
+ }
133
+ Write-Host "- Missing/Skipped: $($missing.Count)"
134
+ if ($missing.Count -gt 0) {
135
+ $missing | ForEach-Object { Write-Host " - $_" }
136
+ }
137
+ if ($backupRootResolved) {
138
+ Write-Host "- Backup directory: $backupRootResolved"
139
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sdtk-kit",
3
- "version": "0.3.3",
3
+ "version": "0.3.4",
4
4
  "description": "SDTK CLI toolkit for deterministic software documentation workflows",
5
5
  "bin": {
6
6
  "sdtk": "./bin/sdtk.js"
@@ -9,15 +9,36 @@ function cmdHelp() {
9
9
  "",
10
10
  "Commands:",
11
11
  " init Initialize SDTK workspace (runtime adapter + config)",
12
+ " runtime Manage runtime asset installation (install, uninstall, status)",
12
13
  " auth Manage GitHub authentication and entitlement",
13
14
  " generate Scaffold feature documentation from templates (17 files)",
14
15
  "",
15
16
  "Init options:",
16
- " --runtime <codex|claude> Runtime adapter (default: codex)",
17
+ " --runtime <codex|claude> Runtime adapter (default: codex)",
18
+ " --runtime-scope <project|user> Install scope (default: project for claude, user for codex)",
19
+ " --project-path <path> Target project directory (default: cwd)",
20
+ " --force Overwrite existing files",
21
+ " --skip-runtime-assets Skip runtime asset installation",
22
+ " --skip-skills (deprecated, use --skip-runtime-assets)",
23
+ " --verbose Show detailed PowerShell script output",
24
+ "",
25
+ "Runtime subcommands:",
26
+ " sdtk runtime install --runtime <codex|claude> [--scope <project|user>]",
27
+ " sdtk runtime uninstall --runtime <codex|claude> [--scope <project|user>] [--all]",
28
+ " sdtk runtime status --runtime <codex|claude>",
29
+ "",
30
+ "Runtime options:",
31
+ " --runtime <codex|claude> Target runtime (required)",
32
+ " --scope <project|user> Install scope (default: project for claude, user for codex)",
17
33
  " --project-path <path> Target project directory (default: cwd)",
18
- " --force Overwrite existing files",
19
- " --skip-skills Skip skill installation (Codex and Claude)",
20
- " --verbose Show detailed PowerShell script output",
34
+ " --force Overwrite existing skills",
35
+ " --all Uninstall all managed skills",
36
+ " --verbose Show detailed script output",
37
+ "",
38
+ "Scope behavior:",
39
+ " project Skills are installed inside the repo (Claude only: .claude/skills/)",
40
+ " user Skills are installed in the runtime home (~/.claude/skills/ or ~/.codex/skills/)",
41
+ " Note: Codex supports user scope only. Project scope is not available for Codex.",
21
42
  "",
22
43
  "Auth options:",
23
44
  " --token <value> Store GitHub PAT",
@@ -60,6 +81,11 @@ function cmdHelp() {
60
81
  " sdtk auth --verify",
61
82
  ' sdtk generate --feature-key USER_PROFILE --feature-name "User Profile"',
62
83
  " sdtk generate --feature-key ORDER_MGMT --feature-name \"Order Management\" --validate-only",
84
+ " sdtk init --runtime claude --runtime-scope user",
85
+ " sdtk runtime install --runtime claude --scope project",
86
+ " sdtk runtime install --runtime codex --scope user",
87
+ " sdtk runtime status --runtime claude",
88
+ " sdtk runtime uninstall --runtime claude --scope project --all",
63
89
  "",
64
90
  " # Override entitlement repo (bash/zsh):",
65
91
  " export SDTK_ENTITLEMENT_REPO=owner/repo",
@@ -5,12 +5,15 @@ const { parseFlags, validateChoice } = require("../lib/args");
5
5
  const { verify, resolvePayloadFile } = require("../lib/toolkit-payload");
6
6
  const { runScript } = require("../lib/powershell");
7
7
  const { ValidationError } = require("../lib/errors");
8
+ const { VALID_SCOPES, defaultScope, isProjectScopeSupported } = require("../lib/scope");
8
9
 
9
10
  const FLAG_DEFS = {
10
11
  runtime: { type: "string" },
11
12
  "project-path": { type: "string" },
13
+ "runtime-scope": { type: "string" },
12
14
  force: { type: "boolean" },
13
15
  "skip-skills": { type: "boolean" },
16
+ "skip-runtime-assets": { type: "boolean" },
14
17
  verbose: { type: "boolean" },
15
18
  };
16
19
 
@@ -23,6 +26,25 @@ async function cmdInit(args) {
23
26
  const runtime = flags.runtime || "codex";
24
27
  validateChoice(runtime, VALID_RUNTIMES, "runtime");
25
28
 
29
+ // Resolve scope
30
+ const scope = flags["runtime-scope"] || defaultScope(runtime);
31
+ validateChoice(scope, VALID_SCOPES, "runtime-scope");
32
+
33
+ // Gate C0: Codex does not support project-local skills
34
+ if (scope === "project" && !isProjectScopeSupported(runtime)) {
35
+ throw new ValidationError(
36
+ "Codex does not support project-local skills. Use --runtime-scope user instead."
37
+ );
38
+ }
39
+
40
+ // Handle deprecated --skip-skills
41
+ const skipAssets = flags["skip-runtime-assets"] || flags["skip-skills"];
42
+ if (flags["skip-skills"] && !flags["skip-runtime-assets"]) {
43
+ console.warn(
44
+ "Warning: --skip-skills is deprecated. Use --skip-runtime-assets instead."
45
+ );
46
+ }
47
+
26
48
  // Resolve project path
27
49
  const projectPath = flags["project-path"]
28
50
  ? path.resolve(flags["project-path"])
@@ -38,13 +60,15 @@ async function cmdInit(args) {
38
60
  const params = {
39
61
  ProjectPath: projectPath,
40
62
  Runtime: runtime,
63
+ Scope: scope,
41
64
  };
42
65
  if (flags.force) params.Force = true;
43
- if (flags["skip-skills"]) params.SkipSkills = true;
66
+ if (skipAssets) params.SkipRuntimeAssets = true;
44
67
  if (!flags.verbose) params.Quiet = true;
45
68
 
46
69
  console.log(`Initializing SDTK workspace...`);
47
70
  console.log(` Runtime: ${runtime}`);
71
+ console.log(` Scope: ${scope}`);
48
72
  console.log(` Project: ${projectPath}`);
49
73
  console.log("");
50
74
 
@@ -0,0 +1,217 @@
1
+ "use strict";
2
+
3
+ const path = require("path");
4
+ const fs = require("fs");
5
+ const { parseFlags, requireFlag, validateChoice } = require("../lib/args");
6
+ const { verify, resolvePayloadFile } = require("../lib/toolkit-payload");
7
+ const { runScript } = require("../lib/powershell");
8
+ const { ValidationError } = require("../lib/errors");
9
+ const {
10
+ VALID_SCOPES,
11
+ defaultScope,
12
+ isProjectScopeSupported,
13
+ resolveSkillsDir,
14
+ managedSkillNames,
15
+ } = require("../lib/scope");
16
+
17
+ const FLAG_DEFS = {
18
+ runtime: { type: "string" },
19
+ scope: { type: "string" },
20
+ "project-path": { type: "string" },
21
+ force: { type: "boolean" },
22
+ all: { type: "boolean" },
23
+ verbose: { type: "boolean" },
24
+ };
25
+
26
+ const VALID_RUNTIMES = ["codex", "claude"];
27
+ const SUBCOMMANDS = ["install", "uninstall", "status"];
28
+
29
+ function validateScopeForRuntime(runtime, scope) {
30
+ if (scope === "project" && !isProjectScopeSupported(runtime)) {
31
+ throw new ValidationError(
32
+ `Codex does not support project-local skills. Use --scope user instead.`
33
+ );
34
+ }
35
+ }
36
+
37
+ async function cmdRuntimeInstall(flags) {
38
+ const runtime = requireFlag(flags, "runtime", "runtime");
39
+ validateChoice(runtime, VALID_RUNTIMES, "runtime");
40
+
41
+ const scope = flags.scope || defaultScope(runtime);
42
+ validateChoice(scope, VALID_SCOPES, "scope");
43
+ validateScopeForRuntime(runtime, scope);
44
+
45
+ const projectPath = flags["project-path"]
46
+ ? path.resolve(flags["project-path"])
47
+ : process.cwd();
48
+
49
+ verify();
50
+
51
+ const scriptName =
52
+ runtime === "claude"
53
+ ? "toolkit/scripts/install-claude-skills.ps1"
54
+ : "toolkit/scripts/install-codex-skills.ps1";
55
+ const script = resolvePayloadFile(scriptName);
56
+
57
+ const params = { Scope: scope };
58
+ if (runtime === "claude") {
59
+ params.ProjectPath = projectPath;
60
+ }
61
+ if (flags.force) params.Force = true;
62
+
63
+ console.log(`Installing ${runtime} runtime assets...`);
64
+ console.log(` Runtime: ${runtime}`);
65
+ console.log(` Scope: ${scope}`);
66
+ if (scope === "project") {
67
+ console.log(` Project: ${projectPath}`);
68
+ }
69
+ console.log("");
70
+
71
+ const result = await runScript(script, params, {
72
+ silent: !flags.verbose,
73
+ });
74
+
75
+ if (result.exitCode !== 0) {
76
+ if (result.stderr) console.error(result.stderr);
77
+ throw new ValidationError(
78
+ `Runtime install failed (exit code ${result.exitCode}).`
79
+ );
80
+ }
81
+
82
+ console.log("");
83
+ console.log(`${runtime} runtime assets installed successfully.`);
84
+ return 0;
85
+ }
86
+
87
+ async function cmdRuntimeUninstall(flags) {
88
+ const runtime = requireFlag(flags, "runtime", "runtime");
89
+ validateChoice(runtime, VALID_RUNTIMES, "runtime");
90
+
91
+ const scope = flags.scope || defaultScope(runtime);
92
+ validateChoice(scope, VALID_SCOPES, "scope");
93
+ validateScopeForRuntime(runtime, scope);
94
+
95
+ const projectPath = flags["project-path"]
96
+ ? path.resolve(flags["project-path"])
97
+ : process.cwd();
98
+
99
+ verify();
100
+
101
+ const scriptName =
102
+ runtime === "claude"
103
+ ? "toolkit/scripts/uninstall-claude-skills.ps1"
104
+ : "toolkit/scripts/uninstall-codex-skills.ps1";
105
+ const script = resolvePayloadFile(scriptName);
106
+
107
+ const params = {};
108
+ if (runtime === "claude") {
109
+ params.Scope = scope;
110
+ params.ProjectPath = projectPath;
111
+ }
112
+ if (flags.all) params.All = true;
113
+
114
+ console.log(`Uninstalling ${runtime} runtime assets...`);
115
+ console.log(` Runtime: ${runtime}`);
116
+ console.log(` Scope: ${scope}`);
117
+ console.log("");
118
+
119
+ const result = await runScript(script, params, {
120
+ silent: !flags.verbose,
121
+ });
122
+
123
+ if (result.exitCode !== 0) {
124
+ if (result.stderr) console.error(result.stderr);
125
+ throw new ValidationError(
126
+ `Runtime uninstall failed (exit code ${result.exitCode}).`
127
+ );
128
+ }
129
+
130
+ console.log("");
131
+ console.log(`${runtime} runtime assets uninstalled successfully.`);
132
+ return 0;
133
+ }
134
+
135
+ function cmdRuntimeStatus(flags) {
136
+ const runtime = requireFlag(flags, "runtime", "runtime");
137
+ validateChoice(runtime, VALID_RUNTIMES, "runtime");
138
+
139
+ const projectPath = flags["project-path"]
140
+ ? path.resolve(flags["project-path"])
141
+ : process.cwd();
142
+
143
+ console.log(`Runtime: ${runtime}`);
144
+ console.log("");
145
+
146
+ // Check both scopes
147
+ const scopes = runtime === "claude" ? ["project", "user"] : ["user"];
148
+
149
+ for (const scope of scopes) {
150
+ const skillsDir = resolveSkillsDir(runtime, scope, projectPath);
151
+ const exists = fs.existsSync(skillsDir);
152
+ let skillCount = 0;
153
+
154
+ const managed = managedSkillNames(runtime);
155
+ let installedNames = [];
156
+
157
+ if (exists) {
158
+ try {
159
+ const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
160
+ installedNames = entries
161
+ .filter((e) => e.isDirectory() && managed.includes(e.name))
162
+ .map((e) => e.name);
163
+ skillCount = installedNames.length;
164
+ } catch {
165
+ // Directory not readable
166
+ }
167
+ }
168
+
169
+ const allInstalled = skillCount === managed.length;
170
+
171
+ console.log(` Scope: ${scope}`);
172
+ console.log(` Path: ${skillsDir}`);
173
+ const statusLabel = !exists
174
+ ? "not installed"
175
+ : allInstalled
176
+ ? `installed (${skillCount}/${managed.length} SDTK skills)`
177
+ : `partial (${skillCount}/${managed.length} SDTK skills)`;
178
+ console.log(` Status: ${statusLabel}`);
179
+ if (exists && skillCount < managed.length) {
180
+ const missing = managed.filter((n) => !installedNames.includes(n));
181
+ console.log(` Missing: ${missing.join(", ")}`);
182
+ }
183
+ console.log("");
184
+ }
185
+
186
+ return 0;
187
+ }
188
+
189
+ async function cmdRuntime(args) {
190
+ const { flags, positional } = parseFlags(args, FLAG_DEFS);
191
+
192
+ const subcommand = positional[0];
193
+ if (!subcommand) {
194
+ throw new ValidationError(
195
+ `Missing subcommand. Usage: sdtk runtime <${SUBCOMMANDS.join("|")}> [options]`
196
+ );
197
+ }
198
+
199
+ if (!SUBCOMMANDS.includes(subcommand)) {
200
+ throw new ValidationError(
201
+ `Unknown subcommand: "${subcommand}". Must be one of: ${SUBCOMMANDS.join(", ")}`
202
+ );
203
+ }
204
+
205
+ switch (subcommand) {
206
+ case "install":
207
+ return cmdRuntimeInstall(flags);
208
+ case "uninstall":
209
+ return cmdRuntimeUninstall(flags);
210
+ case "status":
211
+ return cmdRuntimeStatus(flags);
212
+ }
213
+ }
214
+
215
+ module.exports = {
216
+ cmdRuntime,
217
+ };
package/src/index.js CHANGED
@@ -4,6 +4,7 @@ const { cmdHelp } = require("./commands/help");
4
4
  const { cmdInit } = require("./commands/init");
5
5
  const { cmdAuth } = require("./commands/auth");
6
6
  const { cmdGenerate } = require("./commands/generate");
7
+ const { cmdRuntime } = require("./commands/runtime");
7
8
  const { ValidationError, CliError } = require("./lib/errors");
8
9
 
9
10
  function getVersion() {
@@ -25,7 +26,7 @@ function parseCommand(argv) {
25
26
  return { command: first, args: rest };
26
27
  }
27
28
 
28
- const COMMANDS = new Set(["help", "version", "init", "auth", "generate"]);
29
+ const COMMANDS = new Set(["help", "version", "init", "auth", "generate", "runtime"]);
29
30
 
30
31
  async function run(argv) {
31
32
  const { command, args } = parseCommand(argv);
@@ -48,6 +49,8 @@ async function run(argv) {
48
49
  return cmdAuth(args);
49
50
  case "generate":
50
51
  return cmdGenerate(args);
52
+ case "runtime":
53
+ return cmdRuntime(args);
51
54
  }
52
55
  }
53
56
 
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+
3
+ const path = require("path");
4
+ const os = require("os");
5
+
6
+ const VALID_SCOPES = ["project", "user"];
7
+
8
+ /**
9
+ * Returns the default scope for a given runtime.
10
+ * Claude defaults to project-local, Codex defaults to user/global.
11
+ */
12
+ function defaultScope(runtime) {
13
+ return runtime === "claude" ? "project" : "user";
14
+ }
15
+
16
+ /**
17
+ * Returns true if the given runtime supports project-local scope.
18
+ * Gate C0: Codex does not support project-local skills.
19
+ */
20
+ function isProjectScopeSupported(runtime) {
21
+ return runtime === "claude";
22
+ }
23
+
24
+ /**
25
+ * Resolves the skills directory for a given runtime, scope, and project path.
26
+ */
27
+ function resolveSkillsDir(runtime, scope, projectPath) {
28
+ if (runtime === "claude") {
29
+ if (scope === "user") {
30
+ return path.join(os.homedir(), ".claude", "skills");
31
+ }
32
+ return path.join(projectPath || process.cwd(), ".claude", "skills");
33
+ }
34
+
35
+ // Codex: always user/global scope
36
+ const codexHome = process.env.CODEX_HOME || path.join(os.homedir(), ".codex");
37
+ return path.join(codexHome, "skills");
38
+ }
39
+
40
+ /**
41
+ * SDTK-managed skill directory names per runtime.
42
+ * Used by runtime status to distinguish SDTK skills from unrelated user skills.
43
+ */
44
+ const MANAGED_CODEX_SKILLS = [
45
+ "sdtk-api-design-spec", "sdtk-api-doc", "sdtk-arch", "sdtk-ba",
46
+ "sdtk-design-layout", "sdtk-dev", "sdtk-dev-backend", "sdtk-dev-frontend",
47
+ "sdtk-orchestrator", "sdtk-pm", "sdtk-qa", "sdtk-screen-design-spec",
48
+ "sdtk-test-case-spec",
49
+ ];
50
+
51
+ const MANAGED_CLAUDE_SKILLS = [
52
+ "api-design-spec", "api-doc", "arch", "ba",
53
+ "design-layout", "dev", "dev-backend", "dev-frontend",
54
+ "orchestrator", "pm", "qa", "screen-design-spec",
55
+ "test-case-spec",
56
+ ];
57
+
58
+ function managedSkillNames(runtime) {
59
+ return runtime === "claude" ? MANAGED_CLAUDE_SKILLS : MANAGED_CODEX_SKILLS;
60
+ }
61
+
62
+ module.exports = {
63
+ VALID_SCOPES,
64
+ defaultScope,
65
+ isProjectScopeSupported,
66
+ resolveSkillsDir,
67
+ managedSkillNames,
68
+ };