sdtk-design-kit 0.3.1 → 0.3.2
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/package.json +1 -1
- package/src/commands/init.js +84 -24
- package/src/lib/runtime-skills.js +46 -0
package/package.json
CHANGED
package/src/commands/init.js
CHANGED
|
@@ -7,41 +7,67 @@ const { ValidationError } = require("../lib/errors");
|
|
|
7
7
|
const {
|
|
8
8
|
assertProjectLocalPath,
|
|
9
9
|
describeDesignPaths,
|
|
10
|
+
isPathInsideOrEqual,
|
|
10
11
|
resolveProjectPath,
|
|
11
12
|
} = require("../lib/design-paths");
|
|
13
|
+
const {
|
|
14
|
+
VALID_RUNTIMES,
|
|
15
|
+
VALID_SCOPES,
|
|
16
|
+
defaultScope,
|
|
17
|
+
resolveSkillsDir,
|
|
18
|
+
} = require("../lib/runtime-skills");
|
|
12
19
|
|
|
13
20
|
const INIT_FLAG_DEFS = {
|
|
14
21
|
help: { type: "boolean" },
|
|
15
22
|
"project-path": { type: "string" },
|
|
16
23
|
force: { type: "boolean" },
|
|
17
24
|
"no-state": { type: "boolean" },
|
|
25
|
+
runtime: { type: "string" },
|
|
26
|
+
"runtime-scope": { type: "string" },
|
|
27
|
+
global: { type: "boolean" },
|
|
18
28
|
};
|
|
19
29
|
|
|
20
30
|
const PACKAGE_ROOT = path.resolve(__dirname, "..", "..");
|
|
21
31
|
const DESIGN_PROTOTYPE_SKILL_SOURCE = path.join(PACKAGE_ROOT, "skills", "design-prototype");
|
|
22
|
-
const DESIGN_PROTOTYPE_SKILL_TARGETS = [
|
|
23
|
-
path.join(".claude", "skills", "design-prototype"),
|
|
24
|
-
path.join(".agents", "skills", "design-prototype"),
|
|
25
|
-
];
|
|
26
32
|
|
|
27
33
|
function toPosix(value) {
|
|
28
34
|
return String(value || "").replace(/\\/g, "/");
|
|
29
35
|
}
|
|
30
36
|
|
|
37
|
+
// Report a target either project-relative (when inside the project) or as an
|
|
38
|
+
// absolute posix path (e.g. for user/global skills under the home directory).
|
|
39
|
+
function reportPath(targetPath, projectPath) {
|
|
40
|
+
if (isPathInsideOrEqual(targetPath, projectPath)) {
|
|
41
|
+
return toPosix(path.relative(projectPath, targetPath));
|
|
42
|
+
}
|
|
43
|
+
return toPosix(targetPath);
|
|
44
|
+
}
|
|
45
|
+
|
|
31
46
|
function cmdInitHelp() {
|
|
32
47
|
console.log(`SDTK-DESIGN Init
|
|
33
48
|
|
|
34
49
|
Usage:
|
|
35
|
-
sdtk-design init [--
|
|
50
|
+
sdtk-design init [--runtime <claude|codex>] [--runtime-scope <project|user>]
|
|
51
|
+
[--global] [--project-path <path>] [--force] [--no-state]
|
|
36
52
|
|
|
37
53
|
Example:
|
|
38
54
|
sdtk-design init
|
|
55
|
+
sdtk-design init --runtime codex
|
|
56
|
+
sdtk-design init --runtime claude --global
|
|
57
|
+
|
|
58
|
+
Runtime:
|
|
59
|
+
--runtime claude Install the design-prototype skill under .claude/skills (default).
|
|
60
|
+
--runtime codex Install it under .codex/skills (launch Codex with CODEX_HOME).
|
|
61
|
+
--runtime-scope project Project-local skills dir (default for claude).
|
|
62
|
+
--runtime-scope user User/global skills dir (default for codex).
|
|
63
|
+
--global Shorthand for --runtime-scope user (~/.claude or ~/.codex).
|
|
39
64
|
|
|
40
65
|
Creates:
|
|
41
66
|
docs/design/
|
|
42
67
|
docs/design/wireframes/
|
|
43
68
|
docs/design/reviews/
|
|
44
69
|
docs/design/README.md
|
|
70
|
+
<runtime skills dir>/design-prototype/ (e.g. .claude/skills or .codex/skills)
|
|
45
71
|
.sdtk/design/manifest.json unless --no-state is used
|
|
46
72
|
|
|
47
73
|
Safety:
|
|
@@ -139,41 +165,70 @@ function listSkillFiles(sourceDir) {
|
|
|
139
165
|
return results.sort();
|
|
140
166
|
}
|
|
141
167
|
|
|
142
|
-
|
|
168
|
+
// Install the bundled design-prototype skill into a single runtime skills dir
|
|
169
|
+
// (.claude/skills or .codex/skills, per the resolved runtime/scope). For a
|
|
170
|
+
// project-local target the path is guarded inside the project root; for a
|
|
171
|
+
// user/global target (home directory) traversal is guarded against the skills
|
|
172
|
+
// target dir instead, since the destination is intentionally outside the project.
|
|
173
|
+
function installDesignPrototypeSkill(skillsDir, projectPath, force, result) {
|
|
143
174
|
if (!fs.existsSync(DESIGN_PROTOTYPE_SKILL_SOURCE) || !fs.statSync(DESIGN_PROTOTYPE_SKILL_SOURCE).isDirectory()) {
|
|
144
175
|
throw new ValidationError(`Bundled design-prototype skill is missing: ${DESIGN_PROTOTYPE_SKILL_SOURCE}. No project files were changed.`);
|
|
145
176
|
}
|
|
146
177
|
|
|
147
|
-
|
|
148
|
-
|
|
178
|
+
const targetDir = path.join(skillsDir, "design-prototype");
|
|
179
|
+
if (isPathInsideOrEqual(targetDir, projectPath)) {
|
|
149
180
|
assertProjectLocalPath(targetDir, projectPath, "skill directory");
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
result.created.push(toPosix(path.relative(projectPath, targetFile)));
|
|
181
|
+
}
|
|
182
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
183
|
+
result.created.push(reportPath(targetDir, projectPath) + "/");
|
|
184
|
+
|
|
185
|
+
for (const sourceFile of listSkillFiles(DESIGN_PROTOTYPE_SKILL_SOURCE)) {
|
|
186
|
+
const relativeFile = path.relative(DESIGN_PROTOTYPE_SKILL_SOURCE, sourceFile);
|
|
187
|
+
const targetFile = path.join(targetDir, relativeFile);
|
|
188
|
+
if (!isPathInsideOrEqual(targetFile, targetDir)) {
|
|
189
|
+
throw new ValidationError(`Refusing to write skill file outside its target directory: ${targetFile}`);
|
|
190
|
+
}
|
|
191
|
+
if (fs.existsSync(targetFile) && !force) {
|
|
192
|
+
result.skipped.push(reportPath(targetFile, projectPath));
|
|
193
|
+
continue;
|
|
164
194
|
}
|
|
195
|
+
fs.mkdirSync(path.dirname(targetFile), { recursive: true });
|
|
196
|
+
fs.copyFileSync(sourceFile, targetFile);
|
|
197
|
+
result.created.push(reportPath(targetFile, projectPath));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function resolveRuntimeAndScope({ runtime, runtimeScope, global: globalFlag }) {
|
|
202
|
+
const resolvedRuntime = runtime || "claude";
|
|
203
|
+
if (!VALID_RUNTIMES.includes(resolvedRuntime)) {
|
|
204
|
+
throw new ValidationError(`--runtime must be one of: ${VALID_RUNTIMES.join(", ")}. Got: ${resolvedRuntime}.`);
|
|
205
|
+
}
|
|
206
|
+
const resolvedScope = globalFlag ? "user" : (runtimeScope || defaultScope(resolvedRuntime));
|
|
207
|
+
if (!VALID_SCOPES.includes(resolvedScope)) {
|
|
208
|
+
throw new ValidationError(`--runtime-scope must be one of: ${VALID_SCOPES.join(", ")}. Got: ${resolvedScope}.`);
|
|
165
209
|
}
|
|
210
|
+
return { runtime: resolvedRuntime, scope: resolvedScope };
|
|
166
211
|
}
|
|
167
212
|
|
|
168
|
-
function runDesignInit({ projectPath, force = false, state = true }) {
|
|
213
|
+
function runDesignInit({ projectPath, force = false, state = true, runtime, runtimeScope, global: globalFlag }) {
|
|
169
214
|
const resolvedProjectPath = resolveProjectPath(projectPath || process.cwd());
|
|
170
215
|
if (!fs.existsSync(resolvedProjectPath) || !fs.statSync(resolvedProjectPath).isDirectory()) {
|
|
171
216
|
throw new ValidationError(`--project-path is not a valid directory: ${resolvedProjectPath}. No project files were changed.`);
|
|
172
217
|
}
|
|
173
218
|
|
|
219
|
+
const { runtime: resolvedRuntime, scope: resolvedScope } = resolveRuntimeAndScope({
|
|
220
|
+
runtime,
|
|
221
|
+
runtimeScope,
|
|
222
|
+
global: globalFlag,
|
|
223
|
+
});
|
|
224
|
+
const skillsDir = resolveSkillsDir(resolvedRuntime, resolvedScope, resolvedProjectPath);
|
|
225
|
+
|
|
174
226
|
const paths = describeDesignPaths(resolvedProjectPath);
|
|
175
227
|
const result = {
|
|
176
228
|
projectPath: resolvedProjectPath,
|
|
229
|
+
runtime: resolvedRuntime,
|
|
230
|
+
scope: resolvedScope,
|
|
231
|
+
skillsDir,
|
|
177
232
|
created: [],
|
|
178
233
|
skipped: [],
|
|
179
234
|
stateEnabled: Boolean(state),
|
|
@@ -183,7 +238,7 @@ function runDesignInit({ projectPath, force = false, state = true }) {
|
|
|
183
238
|
ensureDirectory(paths.wireframesPath, resolvedProjectPath, result);
|
|
184
239
|
ensureDirectory(paths.reviewsPath, resolvedProjectPath, result);
|
|
185
240
|
writeManagedFile(paths.designReadmePath, designReadmeContent(), resolvedProjectPath, force, result);
|
|
186
|
-
installDesignPrototypeSkill(resolvedProjectPath, force, result);
|
|
241
|
+
installDesignPrototypeSkill(skillsDir, resolvedProjectPath, force, result);
|
|
187
242
|
|
|
188
243
|
if (state) {
|
|
189
244
|
ensureDirectory(paths.designStatePath, resolvedProjectPath, result);
|
|
@@ -201,9 +256,14 @@ function cmdInit(args) {
|
|
|
201
256
|
projectPath: flags["project-path"],
|
|
202
257
|
force: Boolean(flags.force),
|
|
203
258
|
state: !flags["no-state"],
|
|
259
|
+
runtime: flags.runtime,
|
|
260
|
+
runtimeScope: flags["runtime-scope"],
|
|
261
|
+
global: Boolean(flags.global),
|
|
204
262
|
});
|
|
205
263
|
|
|
206
264
|
console.log(`[design] Initialized SDTK-DESIGN workspace: ${result.projectPath}`);
|
|
265
|
+
console.log(`[design] Runtime: ${result.runtime} (scope: ${result.scope})`);
|
|
266
|
+
console.log(`[design] Skill: ${path.join(result.skillsDir, "design-prototype")}`);
|
|
207
267
|
console.log(`[design] Created: ${result.created.length}`);
|
|
208
268
|
console.log(`[design] Skipped: ${result.skipped.length}`);
|
|
209
269
|
console.log(`[design] State: ${result.stateEnabled ? ".sdtk/design enabled" : "disabled by --no-state"}`);
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// Runtime/scope → skills directory resolver for SDTK-DESIGN.
|
|
4
|
+
//
|
|
5
|
+
// Mirrors the convention used by the runtime-aware kits (sdtk-spec/ops/code,
|
|
6
|
+
// see their src/lib/scope.js): claude installs skills under `.claude/skills`,
|
|
7
|
+
// codex under `.codex/skills` (honoring CODEX_HOME for the user/global scope).
|
|
8
|
+
// Each kit is an independent npm package, so this small resolver is duplicated
|
|
9
|
+
// rather than imported across package boundaries.
|
|
10
|
+
|
|
11
|
+
const path = require("path");
|
|
12
|
+
const os = require("os");
|
|
13
|
+
|
|
14
|
+
const VALID_RUNTIMES = ["claude", "codex"];
|
|
15
|
+
const VALID_SCOPES = ["project", "user"];
|
|
16
|
+
|
|
17
|
+
// Claude defaults to project-local; Codex defaults to user/global.
|
|
18
|
+
function defaultScope(runtime) {
|
|
19
|
+
return runtime === "claude" ? "project" : "user";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function resolveCodexHome(scope, projectPath) {
|
|
23
|
+
if (scope === "project") {
|
|
24
|
+
return path.join(projectPath || process.cwd(), ".codex");
|
|
25
|
+
}
|
|
26
|
+
return process.env.CODEX_HOME || path.join(os.homedir(), ".codex");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Absolute skills directory for the given runtime/scope/project root.
|
|
30
|
+
function resolveSkillsDir(runtime, scope, projectPath) {
|
|
31
|
+
if (runtime === "claude") {
|
|
32
|
+
if (scope === "user") {
|
|
33
|
+
return path.join(os.homedir(), ".claude", "skills");
|
|
34
|
+
}
|
|
35
|
+
return path.join(projectPath || process.cwd(), ".claude", "skills");
|
|
36
|
+
}
|
|
37
|
+
return path.join(resolveCodexHome(scope, projectPath), "skills");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = {
|
|
41
|
+
VALID_RUNTIMES,
|
|
42
|
+
VALID_SCOPES,
|
|
43
|
+
defaultScope,
|
|
44
|
+
resolveCodexHome,
|
|
45
|
+
resolveSkillsDir,
|
|
46
|
+
};
|