@superspec/cli 1.0.0 → 1.1.1
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/dist/cli/index.js +295 -258
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.js +159 -103
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/prompts/agents.md +51 -9
- package/prompts/{cursor-rules.md → rules.md} +7 -7
- package/templates/en/commands/ss-archive.md +12 -2
- package/templates/en/commands/ss-create.md +91 -8
- package/templates/en/commands/ss-link.md +12 -2
- package/templates/en/commands/ss-search.md +12 -3
- package/templates/en/commands/ss-specs.md +54 -0
- package/templates/en/commands/ss-validate.md +12 -2
- package/templates/en/design.md +126 -0
- package/templates/zh/commands/ss-archive.md +12 -2
- package/templates/zh/commands/ss-create.md +91 -8
- package/templates/zh/commands/ss-link.md +12 -2
- package/templates/zh/commands/ss-search.md +12 -3
- package/templates/zh/commands/ss-specs.md +54 -0
- package/templates/zh/commands/ss-validate.md +12 -2
- package/templates/zh/design.md +126 -0
package/dist/index.js
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
import { readFileSync, existsSync } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
var DEFAULT_CONFIG = {
|
|
5
|
-
lang: "
|
|
5
|
+
lang: "en",
|
|
6
6
|
aiEditor: "cursor",
|
|
7
7
|
specDir: "superspec",
|
|
8
|
-
branchPrefix: "
|
|
9
|
-
branchTemplate: "{prefix}{
|
|
8
|
+
branchPrefix: "",
|
|
9
|
+
branchTemplate: "{prefix}{intentType}-{date}-{feature}-{user}",
|
|
10
|
+
changeNameTemplate: "{prefix}{intentType}-{date}-{feature}-{user}",
|
|
10
11
|
boost: false,
|
|
11
12
|
strategy: "follow",
|
|
12
13
|
context: [],
|
|
@@ -15,7 +16,8 @@ var DEFAULT_CONFIG = {
|
|
|
15
16
|
proposal: "proposal.md",
|
|
16
17
|
tasks: "tasks.md",
|
|
17
18
|
clarify: "clarify.md",
|
|
18
|
-
checklist: "checklist.md"
|
|
19
|
+
checklist: "checklist.md",
|
|
20
|
+
design: "design.md"
|
|
19
21
|
},
|
|
20
22
|
archive: {
|
|
21
23
|
dir: "archive",
|
|
@@ -26,16 +28,18 @@ var DEFAULT_CONFIG = {
|
|
|
26
28
|
hardLines: 400
|
|
27
29
|
},
|
|
28
30
|
artifacts: ["proposal"],
|
|
29
|
-
boostArtifacts: ["proposal", "spec", "tasks", "checklist"]
|
|
31
|
+
boostArtifacts: ["proposal", "spec", "design", "tasks", "checklist"]
|
|
30
32
|
};
|
|
31
|
-
function loadConfig(projectRoot = process.cwd()) {
|
|
33
|
+
function loadConfig(projectRoot = process.cwd(), silent = false) {
|
|
32
34
|
const configPath = join(projectRoot, "superspec.config.json");
|
|
33
35
|
let userConfig = {};
|
|
34
36
|
if (existsSync(configPath)) {
|
|
35
37
|
try {
|
|
36
38
|
userConfig = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
37
39
|
} catch (e) {
|
|
38
|
-
|
|
40
|
+
if (!silent) {
|
|
41
|
+
console.warn(`\u26A0 Config file parsing failed: ${e.message}`);
|
|
42
|
+
}
|
|
39
43
|
}
|
|
40
44
|
}
|
|
41
45
|
return deepMerge(DEFAULT_CONFIG, userConfig);
|
|
@@ -46,32 +50,41 @@ function getDefaultConfig() {
|
|
|
46
50
|
function deepMerge(target, source) {
|
|
47
51
|
const result = { ...target };
|
|
48
52
|
for (const key of Object.keys(source)) {
|
|
49
|
-
|
|
50
|
-
|
|
53
|
+
const val = source[key];
|
|
54
|
+
if (val === null || val === void 0) continue;
|
|
55
|
+
if (typeof val === "object" && !Array.isArray(val) && target[key] && typeof target[key] === "object") {
|
|
56
|
+
result[key] = deepMerge(target[key], val);
|
|
51
57
|
} else {
|
|
52
|
-
result[key] =
|
|
58
|
+
result[key] = val;
|
|
53
59
|
}
|
|
54
60
|
}
|
|
55
61
|
return result;
|
|
56
62
|
}
|
|
57
63
|
|
|
58
64
|
// src/core/template.ts
|
|
59
|
-
import { existsSync as
|
|
65
|
+
import { existsSync as existsSync4, copyFileSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
60
66
|
import { join as join3, dirname as dirname2 } from "path";
|
|
61
67
|
|
|
62
68
|
// src/utils/paths.ts
|
|
63
69
|
import { dirname, join as join2 } from "path";
|
|
70
|
+
import { existsSync as existsSync2 } from "fs";
|
|
64
71
|
import { fileURLToPath } from "url";
|
|
65
72
|
function getPackageRoot() {
|
|
66
73
|
const __filename2 = fileURLToPath(import.meta.url);
|
|
67
|
-
|
|
68
|
-
|
|
74
|
+
let dir = dirname(__filename2);
|
|
75
|
+
while (dir !== dirname(dir)) {
|
|
76
|
+
if (existsSync2(join2(dir, "package.json")) && existsSync2(join2(dir, "templates"))) {
|
|
77
|
+
return dir;
|
|
78
|
+
}
|
|
79
|
+
dir = dirname(dir);
|
|
80
|
+
}
|
|
81
|
+
return join2(dirname(__filename2), "..", "..");
|
|
69
82
|
}
|
|
70
83
|
|
|
71
84
|
// src/utils/fs.ts
|
|
72
|
-
import { mkdirSync, existsSync as
|
|
85
|
+
import { mkdirSync, existsSync as existsSync3, readdirSync } from "fs";
|
|
73
86
|
function ensureDir(dir) {
|
|
74
|
-
if (!
|
|
87
|
+
if (!existsSync3(dir)) {
|
|
75
88
|
mkdirSync(dir, { recursive: true });
|
|
76
89
|
}
|
|
77
90
|
}
|
|
@@ -80,9 +93,10 @@ function ensureDir(dir) {
|
|
|
80
93
|
function resolveTemplatePath(templateName, lang = "zh") {
|
|
81
94
|
const root = getPackageRoot();
|
|
82
95
|
const langPath = join3(root, "templates", lang, templateName);
|
|
83
|
-
if (
|
|
84
|
-
const
|
|
85
|
-
|
|
96
|
+
if (existsSync4(langPath)) return langPath;
|
|
97
|
+
const fallbackLang = lang === "zh" ? "en" : "zh";
|
|
98
|
+
const fallback = join3(root, "templates", fallbackLang, templateName);
|
|
99
|
+
if (existsSync4(fallback)) return fallback;
|
|
86
100
|
throw new Error(`Template not found: ${templateName} (lang: ${lang})`);
|
|
87
101
|
}
|
|
88
102
|
function copyTemplate(templateName, destPath, lang = "zh") {
|
|
@@ -116,28 +130,29 @@ function writeRenderedTemplate(templateName, destPath, vars = {}, lang = "zh") {
|
|
|
116
130
|
}
|
|
117
131
|
|
|
118
132
|
// src/core/lint.ts
|
|
119
|
-
import { readFileSync as readFileSync3, existsSync as
|
|
133
|
+
import { readFileSync as readFileSync3, existsSync as existsSync5, readdirSync as readdirSync2 } from "fs";
|
|
120
134
|
import { join as join4, basename } from "path";
|
|
121
135
|
|
|
122
136
|
// src/core/validate.ts
|
|
123
|
-
import { readFileSync as readFileSync4, existsSync as
|
|
137
|
+
import { readFileSync as readFileSync4, existsSync as existsSync6 } from "fs";
|
|
124
138
|
import { join as join5 } from "path";
|
|
125
139
|
|
|
126
140
|
// src/core/context.ts
|
|
127
|
-
import { readFileSync as readFileSync5, existsSync as
|
|
141
|
+
import { readFileSync as readFileSync5, existsSync as existsSync7 } from "fs";
|
|
128
142
|
import { join as join6 } from "path";
|
|
129
143
|
|
|
130
144
|
// src/utils/date.ts
|
|
131
145
|
function getDateString() {
|
|
132
146
|
const d = /* @__PURE__ */ new Date();
|
|
133
|
-
return `${d.getFullYear()}
|
|
147
|
+
return `${d.getFullYear()}${String(d.getMonth() + 1).padStart(2, "0")}${String(d.getDate()).padStart(2, "0")}`;
|
|
134
148
|
}
|
|
135
149
|
|
|
136
150
|
// src/utils/git.ts
|
|
137
151
|
import { execSync } from "child_process";
|
|
152
|
+
var GIT_TIMEOUT = 1e4;
|
|
138
153
|
function isGitRepo() {
|
|
139
154
|
try {
|
|
140
|
-
execSync("git rev-parse --is-inside-work-tree", { stdio: "ignore" });
|
|
155
|
+
execSync("git rev-parse --is-inside-work-tree", { stdio: "ignore", timeout: GIT_TIMEOUT });
|
|
141
156
|
return true;
|
|
142
157
|
} catch {
|
|
143
158
|
return false;
|
|
@@ -148,16 +163,16 @@ function createBranch(branchName) {
|
|
|
148
163
|
if (!SAFE_BRANCH_RE.test(branchName)) {
|
|
149
164
|
throw new Error(`invalid branch name: ${branchName}`);
|
|
150
165
|
}
|
|
151
|
-
execSync(`git checkout -b ${branchName}`, { stdio: "inherit" });
|
|
166
|
+
execSync(`git checkout -b ${branchName}`, { stdio: "inherit", timeout: GIT_TIMEOUT });
|
|
152
167
|
}
|
|
153
168
|
|
|
154
169
|
// src/commands/init.ts
|
|
155
|
-
import { existsSync as
|
|
170
|
+
import { existsSync as existsSync9, writeFileSync as writeFileSync3, readdirSync as readdirSync5 } from "fs";
|
|
156
171
|
import { join as join8 } from "path";
|
|
157
172
|
import { execSync as execSync2 } from "child_process";
|
|
158
173
|
|
|
159
174
|
// src/prompts/index.ts
|
|
160
|
-
import { existsSync as
|
|
175
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync2, readdirSync as readdirSync4 } from "fs";
|
|
161
176
|
import { join as join7 } from "path";
|
|
162
177
|
|
|
163
178
|
// src/ui/index.ts
|
|
@@ -248,11 +263,19 @@ var symbol = {
|
|
|
248
263
|
folder: theme.primary("\u{1F4C1}"),
|
|
249
264
|
file: theme.info("\u{1F4C4}"),
|
|
250
265
|
git: theme.warning("\u{1F33F}"),
|
|
251
|
-
ai: theme.boost("\u{1F916}")
|
|
266
|
+
ai: theme.boost("\u{1F916}"),
|
|
267
|
+
info: theme.info("\u2139")
|
|
252
268
|
};
|
|
253
269
|
function printLogo(size = "small") {
|
|
254
270
|
console.log(logo[size]);
|
|
255
271
|
}
|
|
272
|
+
var _lang = "en";
|
|
273
|
+
function setLang(lang) {
|
|
274
|
+
_lang = lang;
|
|
275
|
+
}
|
|
276
|
+
function t(en, zh) {
|
|
277
|
+
return _lang === "zh" ? zh : en;
|
|
278
|
+
}
|
|
256
279
|
function printSummary(items) {
|
|
257
280
|
const maxLabel = Math.max(...items.map((i) => i.label.length));
|
|
258
281
|
const width = 50;
|
|
@@ -260,7 +283,7 @@ function printSummary(items) {
|
|
|
260
283
|
for (const { label, value } of items) {
|
|
261
284
|
const padding = " ".repeat(maxLabel - label.length);
|
|
262
285
|
const line = `${theme.dim(label)}${padding} ${symbol.arrow} ${theme.highlight(value)}`;
|
|
263
|
-
const plainLine = line.replace(/\u001b\[\d+m/g, "");
|
|
286
|
+
const plainLine = line.replace(/\u001b\[\d+(?:;\d+)*m/g, "");
|
|
264
287
|
const rightPad = " ".repeat(Math.max(0, width - plainLine.length - 4));
|
|
265
288
|
console.log(theme.border("\u2502 ") + line + rightPad + theme.border(" \u2502"));
|
|
266
289
|
}
|
|
@@ -310,8 +333,8 @@ function installRules(cwd, editor) {
|
|
|
310
333
|
}
|
|
311
334
|
const rulesDir = join7(cwd, config.rules);
|
|
312
335
|
ensureDir(rulesDir);
|
|
313
|
-
const promptSrc = join7(getPackageRoot(), "prompts", "
|
|
314
|
-
if (
|
|
336
|
+
const promptSrc = join7(getPackageRoot(), "prompts", "rules.md");
|
|
337
|
+
if (existsSync8(promptSrc)) {
|
|
315
338
|
const content = readFileSync6(promptSrc, "utf-8");
|
|
316
339
|
const rulesFile = "rulesFile" in config ? config.rulesFile : "superspec.md";
|
|
317
340
|
const destPath = join7(rulesDir, rulesFile);
|
|
@@ -324,12 +347,12 @@ var SS_END = "<!-- superspec:end -->";
|
|
|
324
347
|
function installAgentsMd(cwd) {
|
|
325
348
|
const agentsMdPath = join7(cwd, "AGENTS.md");
|
|
326
349
|
const agentPromptSrc = join7(getPackageRoot(), "prompts", "agents.md");
|
|
327
|
-
if (!
|
|
350
|
+
if (!existsSync8(agentPromptSrc)) return;
|
|
328
351
|
const newContent = readFileSync6(agentPromptSrc, "utf-8");
|
|
329
352
|
const wrapped = `${SS_START}
|
|
330
353
|
${newContent}
|
|
331
354
|
${SS_END}`;
|
|
332
|
-
if (
|
|
355
|
+
if (existsSync8(agentsMdPath)) {
|
|
333
356
|
const existing = readFileSync6(agentsMdPath, "utf-8");
|
|
334
357
|
const startIdx = existing.indexOf(SS_START);
|
|
335
358
|
const endIdx = existing.indexOf(SS_END);
|
|
@@ -353,12 +376,12 @@ function installCommands(cwd, editor, lang = "zh") {
|
|
|
353
376
|
ensureDir(commandsDir);
|
|
354
377
|
const templatesDir = join7(getPackageRoot(), "templates", lang, "commands");
|
|
355
378
|
const fallbackDir = join7(getPackageRoot(), "templates", "zh", "commands");
|
|
356
|
-
const sourceDir =
|
|
357
|
-
if (!
|
|
379
|
+
const sourceDir = existsSync8(templatesDir) ? templatesDir : fallbackDir;
|
|
380
|
+
if (!existsSync8(sourceDir)) {
|
|
358
381
|
log.warn(`${symbol.warn} Commands templates not found: ${sourceDir}`);
|
|
359
382
|
return;
|
|
360
383
|
}
|
|
361
|
-
const commandFiles =
|
|
384
|
+
const commandFiles = readdirSync4(sourceDir).filter((f) => f.endsWith(".md"));
|
|
362
385
|
for (const file of commandFiles) {
|
|
363
386
|
const srcPath = join7(sourceDir, file);
|
|
364
387
|
const destPath = join7(commandsDir, file);
|
|
@@ -372,11 +395,12 @@ function installCommands(cwd, editor, lang = "zh") {
|
|
|
372
395
|
async function initCommand(options) {
|
|
373
396
|
const cwd = process.cwd();
|
|
374
397
|
const configPath = join8(cwd, "superspec.config.json");
|
|
375
|
-
if (
|
|
376
|
-
log.warn(`${symbol.warn} superspec.config.json already exists, use --force to overwrite`);
|
|
398
|
+
if (existsSync9(configPath) && !options.force) {
|
|
399
|
+
log.warn(`${symbol.warn} ${t("superspec.config.json already exists, use --force to overwrite", "superspec.config.json \u5DF2\u5B58\u5728\uFF0C\u4F7F\u7528 --force \u8986\u76D6")}`);
|
|
377
400
|
return;
|
|
378
401
|
}
|
|
379
402
|
const lang = options.lang || "zh";
|
|
403
|
+
setLang(lang);
|
|
380
404
|
printLogo("small");
|
|
381
405
|
console.log(theme.dim(" Spec-Driven Development Toolkit\n"));
|
|
382
406
|
const config = getDefaultConfig();
|
|
@@ -386,27 +410,30 @@ async function initCommand(options) {
|
|
|
386
410
|
config.aiEditor = aiEditor;
|
|
387
411
|
}
|
|
388
412
|
const specDir = join8(cwd, config.specDir);
|
|
389
|
-
const existingFiles =
|
|
413
|
+
const existingFiles = readdirSync5(cwd).filter((f) => !f.startsWith(".") && f !== "node_modules");
|
|
390
414
|
if (existingFiles.length > 0 && !options.force) {
|
|
391
|
-
log.warn(`${symbol.warn}
|
|
392
|
-
log.dim("
|
|
415
|
+
log.warn(`${symbol.warn} ${t(`current directory is not empty (${existingFiles.length} items)`, `\u5F53\u524D\u76EE\u5F55\u975E\u7A7A\uFF08${existingFiles.length} \u9879\uFF09`)}`);
|
|
416
|
+
log.dim(` ${t("template files will be merged with existing content", "\u6A21\u677F\u6587\u4EF6\u5C06\u4E0E\u73B0\u6709\u5185\u5BB9\u5408\u5E76")}`);
|
|
393
417
|
console.log();
|
|
394
418
|
}
|
|
395
|
-
log.section("Creating Configuration");
|
|
419
|
+
log.section(t("Creating Configuration", "\u521B\u5EFA\u914D\u7F6E"));
|
|
396
420
|
writeFileSync3(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
397
421
|
log.success(`${symbol.file} superspec.config.json`);
|
|
398
|
-
log.section("Creating Directory Structure");
|
|
422
|
+
log.section(t("Creating Directory Structure", "\u521B\u5EFA\u76EE\u5F55\u7ED3\u6784"));
|
|
399
423
|
ensureDir(join8(specDir, "changes"));
|
|
400
424
|
ensureDir(join8(specDir, "templates"));
|
|
401
425
|
log.success(`${symbol.folder} ${config.specDir}/changes/`);
|
|
402
426
|
log.success(`${symbol.folder} ${config.specDir}/templates/`);
|
|
403
|
-
log.section("Installing Templates");
|
|
404
|
-
const
|
|
405
|
-
for (const tpl of
|
|
406
|
-
|
|
427
|
+
log.section(t("Installing Templates", "\u5B89\u88C5\u6A21\u677F"));
|
|
428
|
+
const templateNames = Object.values(config.templates).map((v) => v.endsWith(".md") ? v : `${v}.md`);
|
|
429
|
+
for (const tpl of templateNames) {
|
|
430
|
+
try {
|
|
431
|
+
copyTemplate(tpl, join8(specDir, "templates", tpl), lang);
|
|
432
|
+
} catch {
|
|
433
|
+
}
|
|
407
434
|
}
|
|
408
|
-
log.success(`${symbol.ok} ${
|
|
409
|
-
log.section("Installing AI Agent Files");
|
|
435
|
+
log.success(`${symbol.ok} ${templateNames.length} ${t("templates", "\u4E2A\u6A21\u677F")} (${lang})`);
|
|
436
|
+
log.section(t("Installing AI Agent Files", "\u5B89\u88C5 AI Agent \u6587\u4EF6"));
|
|
410
437
|
installAgentsMd(cwd);
|
|
411
438
|
if (aiEditor && AI_EDITORS[aiEditor]) {
|
|
412
439
|
installRules(cwd, aiEditor);
|
|
@@ -423,44 +450,73 @@ async function initCommand(options) {
|
|
|
423
450
|
{ label: "AI agent", value: options.ai },
|
|
424
451
|
{ label: "Language", value: lang }
|
|
425
452
|
]);
|
|
426
|
-
log.done("SuperSpec initialized successfully!");
|
|
427
|
-
log.dim("Next:
|
|
453
|
+
log.done(t("SuperSpec initialized successfully!", "SuperSpec \u521D\u59CB\u5316\u6210\u529F\uFF01"));
|
|
454
|
+
log.dim(`${t("Next", "\u4E0B\u4E00\u6B65")}: superspec create <feature>`);
|
|
428
455
|
}
|
|
429
456
|
|
|
430
457
|
// src/commands/create.ts
|
|
431
|
-
import { existsSync as
|
|
458
|
+
import { existsSync as existsSync10 } from "fs";
|
|
432
459
|
import { join as join9 } from "path";
|
|
433
|
-
|
|
460
|
+
|
|
461
|
+
// src/utils/template.ts
|
|
462
|
+
function renderNameTemplate(template, vars, strict = false) {
|
|
463
|
+
let result = template;
|
|
464
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
465
|
+
if (value !== void 0) {
|
|
466
|
+
result = result.replace(new RegExp(`\\{${key}\\}`, "g"), value);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
result = result.replace(/\{[^}]+\}/g, "");
|
|
470
|
+
if (strict) {
|
|
471
|
+
return result.replace(/[^a-zA-Z0-9._\-/]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
472
|
+
}
|
|
473
|
+
return result.replace(/[^\p{L}\p{N}._\-/]/gu, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// src/commands/create.ts
|
|
477
|
+
async function createCommand(feature, options) {
|
|
434
478
|
const cwd = process.cwd();
|
|
435
479
|
const config = loadConfig(cwd);
|
|
436
480
|
const specDir = options.specDir || config.specDir;
|
|
437
481
|
const branchPrefix = options.branchPrefix || config.branchPrefix;
|
|
438
482
|
const boost = options.boost || config.boost;
|
|
439
483
|
const strategy = options.creative ? "create" : config.strategy;
|
|
440
|
-
const lang = config.lang || "zh";
|
|
441
484
|
const description = options.description || "";
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
|
|
485
|
+
const lang = options.lang || config.lang || "en";
|
|
486
|
+
const templateVars = {
|
|
487
|
+
prefix: branchPrefix,
|
|
488
|
+
intentType: options.intentType,
|
|
489
|
+
feature,
|
|
490
|
+
date: getDateString(),
|
|
491
|
+
user: options.user
|
|
492
|
+
};
|
|
493
|
+
const changeNameTemplate = options.changeNameTemplate || config.changeNameTemplate || "{date}-{feature}";
|
|
494
|
+
const changeFolderName = renderNameTemplate(changeNameTemplate, templateVars, false);
|
|
495
|
+
const changePath = join9(cwd, specDir, "changes", changeFolderName);
|
|
496
|
+
if (existsSync10(changePath)) {
|
|
497
|
+
log.warn(`${symbol.warn} ${t(`change "${changeFolderName}" already exists`, `\u53D8\u66F4 "${changeFolderName}" \u5DF2\u5B58\u5728`)}: ${changePath}`);
|
|
445
498
|
return;
|
|
446
499
|
}
|
|
447
|
-
log.title(
|
|
500
|
+
log.title(`${t("Creating Change", "\u521B\u5EFA\u53D8\u66F4")}: ${changeFolderName}`);
|
|
501
|
+
if (options.intentType) {
|
|
502
|
+
log.info(`${t("Intent Type", "\u610F\u56FE\u7C7B\u578B")}: ${options.intentType}`);
|
|
503
|
+
}
|
|
448
504
|
if (boost) {
|
|
449
|
-
log.boost(`${symbol.bolt} Boost mode enabled`);
|
|
505
|
+
log.boost(`${symbol.bolt} ${t("Boost mode enabled", "\u589E\u5F3A\u6A21\u5F0F\u5DF2\u542F\u7528")}`);
|
|
450
506
|
}
|
|
451
507
|
if (strategy === "create") {
|
|
452
|
-
log.boost(`${symbol.bolt} Creative mode: exploring new solutions`);
|
|
508
|
+
log.boost(`${symbol.bolt} ${t("Creative mode: exploring new solutions", "\u521B\u9020\u6A21\u5F0F\uFF1A\u63A2\u7D22\u65B0\u65B9\u6848")}`);
|
|
453
509
|
}
|
|
454
510
|
ensureDir(changePath);
|
|
455
511
|
const vars = {
|
|
456
|
-
name,
|
|
457
|
-
date:
|
|
512
|
+
name: changeFolderName,
|
|
513
|
+
date: templateVars.date,
|
|
458
514
|
boost: boost ? "true" : "false",
|
|
459
515
|
strategy,
|
|
460
516
|
description
|
|
461
517
|
};
|
|
462
518
|
const artifacts = boost ? config.boostArtifacts : config.artifacts;
|
|
463
|
-
log.section("Generating Artifacts");
|
|
519
|
+
log.section(t("Generating Artifacts", "\u751F\u6210 Artifacts"));
|
|
464
520
|
for (const artifact of artifacts) {
|
|
465
521
|
const templateFile = config.templates[artifact] || `${artifact}.md`;
|
|
466
522
|
const destPath = join9(changePath, `${artifact}.md`);
|
|
@@ -472,69 +528,70 @@ async function createCommand(name, options) {
|
|
|
472
528
|
}
|
|
473
529
|
}
|
|
474
530
|
if (options.branch !== false && isGitRepo()) {
|
|
475
|
-
const branchTemplate = config.branchTemplate || "{prefix}{
|
|
476
|
-
const branchName = branchTemplate
|
|
531
|
+
const branchTemplate = options.branchTemplate || config.branchTemplate || "{prefix}{date}-{feature}";
|
|
532
|
+
const branchName = renderNameTemplate(branchTemplate, templateVars, true);
|
|
477
533
|
try {
|
|
478
534
|
createBranch(branchName);
|
|
479
535
|
log.success(`${symbol.ok} Branch: ${branchName}`);
|
|
480
536
|
} catch (e) {
|
|
481
|
-
log.warn(`${symbol.warn}
|
|
537
|
+
log.warn(`${symbol.warn} ${t("branch creation failed", "\u5206\u652F\u521B\u5EFA\u5931\u8D25")}: ${e.message}`);
|
|
482
538
|
}
|
|
483
539
|
}
|
|
484
|
-
log.done("Change created successfully!");
|
|
485
|
-
log.dim(
|
|
540
|
+
log.done(t("Change created successfully!", "\u53D8\u66F4\u521B\u5EFA\u6210\u529F\uFF01"));
|
|
541
|
+
log.dim(`${t("Path", "\u8DEF\u5F84")}: ${specDir}/changes/${changeFolderName}/`);
|
|
486
542
|
if (boost) {
|
|
487
|
-
log.dim(
|
|
543
|
+
log.dim(`${t("Workflow", "\u5DE5\u4F5C\u6D41")}: /ss-create \u2192 /ss-tasks \u2192 /ss-apply (boost)`);
|
|
488
544
|
} else {
|
|
489
|
-
log.dim(
|
|
545
|
+
log.dim(`${t("Workflow", "\u5DE5\u4F5C\u6D41")}: /ss-tasks \u2192 /ss-apply`);
|
|
490
546
|
}
|
|
547
|
+
log.dim(`${t("Next", "\u4E0B\u4E00\u6B65")}: superspec lint ${changeFolderName}`);
|
|
491
548
|
}
|
|
492
549
|
|
|
493
550
|
// src/commands/archive.ts
|
|
494
|
-
import { existsSync as
|
|
551
|
+
import { existsSync as existsSync11, readdirSync as readdirSync6, renameSync } from "fs";
|
|
495
552
|
import { join as join10 } from "path";
|
|
496
553
|
async function archiveCommand(name, options) {
|
|
497
554
|
const cwd = process.cwd();
|
|
498
555
|
const config = loadConfig(cwd);
|
|
499
556
|
const changesDir = join10(cwd, config.specDir, "changes");
|
|
500
557
|
const archiveDir = join10(cwd, config.specDir, "changes", config.archive.dir);
|
|
501
|
-
if (!
|
|
502
|
-
log.warn(`${symbol.warn} \
|
|
558
|
+
if (!existsSync11(changesDir)) {
|
|
559
|
+
log.warn(`${symbol.warn} ${t("no changes directory found", "\u672A\u627E\u5230 changes \u76EE\u5F55")}`);
|
|
503
560
|
return;
|
|
504
561
|
}
|
|
505
562
|
if (options.all) {
|
|
506
|
-
const entries =
|
|
563
|
+
const entries = readdirSync6(changesDir, { withFileTypes: true }).filter(
|
|
507
564
|
(e) => e.isDirectory() && e.name !== config.archive.dir
|
|
508
565
|
);
|
|
509
566
|
if (entries.length === 0) {
|
|
510
|
-
log.warn(`${symbol.warn} \u6CA1\u6709\u53EF\u5F52\u6863\u7684\u53D8\u66F4`);
|
|
567
|
+
log.warn(`${symbol.warn} ${t("no changes to archive", "\u6CA1\u6709\u53EF\u5F52\u6863\u7684\u53D8\u66F4")}`);
|
|
511
568
|
return;
|
|
512
569
|
}
|
|
513
|
-
log.info(`${symbol.start} \u5F52\u6863\u6240\u6709\u53D8\u66F4
|
|
570
|
+
log.info(`${symbol.start} ${t("archiving all changes...", "\u5F52\u6863\u6240\u6709\u53D8\u66F4...")}`);
|
|
514
571
|
for (const entry of entries) {
|
|
515
572
|
archiveOne(entry.name, changesDir, archiveDir, config);
|
|
516
573
|
}
|
|
517
574
|
} else if (name) {
|
|
518
575
|
const changePath = join10(changesDir, name);
|
|
519
|
-
if (!
|
|
520
|
-
log.warn(`${symbol.warn}
|
|
576
|
+
if (!existsSync11(changePath)) {
|
|
577
|
+
log.warn(`${symbol.warn} ${t(`change "${name}" not found`, `\u53D8\u66F4 "${name}" \u4E0D\u5B58\u5728`)}`);
|
|
521
578
|
return;
|
|
522
579
|
}
|
|
523
|
-
log.info(`${symbol.start}
|
|
580
|
+
log.info(`${symbol.start} ${t(`archiving: ${name}`, `\u5F52\u6863\u53D8\u66F4: ${name}`)}`);
|
|
524
581
|
archiveOne(name, changesDir, archiveDir, config);
|
|
525
582
|
} else {
|
|
526
|
-
log.warn(`${symbol.warn} \u8BF7\u6307\u5B9A\u53D8\u66F4\u540D\u79F0\u6216\u4F7F\u7528 --all`);
|
|
583
|
+
log.warn(`${symbol.warn} ${t("specify a name or use --all", "\u8BF7\u6307\u5B9A\u53D8\u66F4\u540D\u79F0\u6216\u4F7F\u7528 --all")}`);
|
|
527
584
|
return;
|
|
528
585
|
}
|
|
529
|
-
log.info(`${symbol.start} \u5F52\u6863\u5B8C\u6210\uFF01`);
|
|
586
|
+
log.info(`${symbol.start} ${t("archive done!", "\u5F52\u6863\u5B8C\u6210\uFF01")}`);
|
|
530
587
|
}
|
|
531
588
|
function archiveOne(name, changesDir, archiveDir, config) {
|
|
532
589
|
ensureDir(archiveDir);
|
|
533
590
|
const src = join10(changesDir, name);
|
|
534
591
|
const dateStr = config.archive.datePrefix ? `${getDateString()}-` : "";
|
|
535
592
|
const dest = join10(archiveDir, `${dateStr}${name}`);
|
|
536
|
-
if (
|
|
537
|
-
log.warn(` ${symbol.warn} \u5F52\u6863\u76EE\u6807\u5DF2\u5B58\u5728: ${dest}`);
|
|
593
|
+
if (existsSync11(dest)) {
|
|
594
|
+
log.warn(` ${symbol.warn} ${t("archive target exists", "\u5F52\u6863\u76EE\u6807\u5DF2\u5B58\u5728")}: ${dest}`);
|
|
538
595
|
return;
|
|
539
596
|
}
|
|
540
597
|
renameSync(src, dest);
|
|
@@ -542,60 +599,59 @@ function archiveOne(name, changesDir, archiveDir, config) {
|
|
|
542
599
|
}
|
|
543
600
|
|
|
544
601
|
// src/commands/update.ts
|
|
545
|
-
import { existsSync as
|
|
602
|
+
import { existsSync as existsSync12 } from "fs";
|
|
546
603
|
import { join as join11 } from "path";
|
|
547
604
|
async function updateCommand() {
|
|
548
605
|
const cwd = process.cwd();
|
|
549
606
|
const config = loadConfig(cwd);
|
|
550
607
|
const specDir = join11(cwd, config.specDir);
|
|
551
608
|
const lang = config.lang || "zh";
|
|
552
|
-
if (!
|
|
553
|
-
log.warn(`${symbol.warn} \u5F53\u524D\u76EE\u5F55\u672A\u521D\u59CB\u5316 SuperSpec\uFF0C\u8BF7\u5148\u8FD0\u884C superspec init`);
|
|
609
|
+
if (!existsSync12(join11(cwd, "superspec.config.json"))) {
|
|
610
|
+
log.warn(`${symbol.warn} ${t("not initialized, run superspec init first", "\u5F53\u524D\u76EE\u5F55\u672A\u521D\u59CB\u5316 SuperSpec\uFF0C\u8BF7\u5148\u8FD0\u884C superspec init")}`);
|
|
554
611
|
return;
|
|
555
612
|
}
|
|
556
|
-
log.info(`${symbol.start} \u66F4\u65B0 SuperSpec
|
|
557
|
-
const
|
|
613
|
+
log.info(`${symbol.start} ${t("updating SuperSpec...", "\u66F4\u65B0 SuperSpec...")}`);
|
|
614
|
+
const templateNames = Object.values(config.templates).map((v) => v.endsWith(".md") ? v : `${v}.md`);
|
|
558
615
|
ensureDir(join11(specDir, "templates"));
|
|
559
|
-
for (const tpl of
|
|
560
|
-
|
|
616
|
+
for (const tpl of templateNames) {
|
|
617
|
+
try {
|
|
618
|
+
copyTemplate(tpl, join11(specDir, "templates", tpl), lang);
|
|
619
|
+
} catch {
|
|
620
|
+
}
|
|
561
621
|
}
|
|
562
|
-
log.success(` ${symbol.ok} \u6A21\u677F\u66F4\u65B0 (${lang})`);
|
|
622
|
+
log.success(` ${symbol.ok} ${t("templates updated", "\u6A21\u677F\u66F4\u65B0")} (${lang})`);
|
|
563
623
|
installAgentsMd(cwd);
|
|
564
624
|
const aiEditor = config.aiEditor;
|
|
565
625
|
if (aiEditor && AI_EDITORS[aiEditor]) {
|
|
566
626
|
installRules(cwd, aiEditor);
|
|
567
627
|
installCommands(cwd, aiEditor, lang);
|
|
568
628
|
}
|
|
569
|
-
log.info(`${symbol.start} \u66F4\u65B0\u5B8C\u6210\uFF01`);
|
|
629
|
+
log.info(`${symbol.start} ${t("update done!", "\u66F4\u65B0\u5B8C\u6210\uFF01")}`);
|
|
570
630
|
}
|
|
571
631
|
|
|
572
632
|
// src/commands/lint.ts
|
|
573
|
-
import { existsSync as
|
|
633
|
+
import { existsSync as existsSync13 } from "fs";
|
|
574
634
|
import { join as join12 } from "path";
|
|
575
635
|
|
|
576
636
|
// src/commands/validate.ts
|
|
577
|
-
import { existsSync as
|
|
637
|
+
import { existsSync as existsSync14 } from "fs";
|
|
578
638
|
import { join as join13 } from "path";
|
|
579
639
|
|
|
580
640
|
// src/commands/search.ts
|
|
581
|
-
import { existsSync as
|
|
641
|
+
import { existsSync as existsSync15, readFileSync as readFileSync7, readdirSync as readdirSync7 } from "fs";
|
|
582
642
|
import { join as join14, basename as basename3 } from "path";
|
|
583
643
|
|
|
584
|
-
// src/commands/
|
|
585
|
-
import { existsSync as
|
|
644
|
+
// src/commands/deps.ts
|
|
645
|
+
import { existsSync as existsSync16, readFileSync as readFileSync8, writeFileSync as writeFileSync4, readdirSync as readdirSync8 } from "fs";
|
|
586
646
|
import { join as join15 } from "path";
|
|
587
647
|
|
|
588
648
|
// src/commands/status.ts
|
|
589
|
-
import { existsSync as
|
|
649
|
+
import { existsSync as existsSync17, readFileSync as readFileSync9, readdirSync as readdirSync9 } from "fs";
|
|
590
650
|
import { join as join16 } from "path";
|
|
591
651
|
|
|
592
|
-
// src/commands/context.ts
|
|
593
|
-
import { existsSync as existsSync17, writeFileSync as writeFileSync5, readdirSync as readdirSync11 } from "fs";
|
|
594
|
-
import { join as join17 } from "path";
|
|
595
|
-
|
|
596
652
|
// src/commands/sync.ts
|
|
597
|
-
import { existsSync as existsSync18, writeFileSync as
|
|
598
|
-
import { join as
|
|
653
|
+
import { existsSync as existsSync18, writeFileSync as writeFileSync5 } from "fs";
|
|
654
|
+
import { join as join17 } from "path";
|
|
599
655
|
export {
|
|
600
656
|
archiveCommand,
|
|
601
657
|
createCommand,
|