@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/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: "zh",
5
+ lang: "en",
6
6
  aiEditor: "cursor",
7
7
  specDir: "superspec",
8
- branchPrefix: "spec/",
9
- branchTemplate: "{prefix}{name}",
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
- console.warn(`\u26A0 \u914D\u7F6E\u6587\u4EF6\u89E3\u6790\u5931\u8D25: ${e.message}`);
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
- if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key]) && target[key] && typeof target[key] === "object") {
50
- result[key] = deepMerge(target[key], source[key]);
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] = source[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 existsSync3, copyFileSync, readFileSync as readFileSync2, writeFileSync } from "fs";
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
- const __dirname2 = dirname(__filename2);
68
- return join2(__dirname2, "..", "..");
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 existsSync2 } from "fs";
85
+ import { mkdirSync, existsSync as existsSync3, readdirSync } from "fs";
73
86
  function ensureDir(dir) {
74
- if (!existsSync2(dir)) {
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 (existsSync3(langPath)) return langPath;
84
- const fallback = join3(root, "templates", "zh", templateName);
85
- if (existsSync3(fallback)) return fallback;
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 existsSync4, readdirSync } from "fs";
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 existsSync5 } from "fs";
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 existsSync6 } from "fs";
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()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
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 existsSync8, writeFileSync as writeFileSync3, readdirSync as readdirSync4 } from "fs";
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 existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync2, readdirSync as readdirSync3 } from "fs";
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", "cursor-rules.md");
314
- if (existsSync7(promptSrc)) {
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 (!existsSync7(agentPromptSrc)) return;
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 (existsSync7(agentsMdPath)) {
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 = existsSync7(templatesDir) ? templatesDir : fallbackDir;
357
- if (!existsSync7(sourceDir)) {
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 = readdirSync3(sourceDir).filter((f) => f.endsWith(".md"));
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 (existsSync8(configPath) && !options.force) {
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 = readdirSync4(cwd).filter((f) => !f.startsWith(".") && f !== "node_modules");
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} Current directory is not empty (${existingFiles.length} items)`);
392
- log.dim(" Template files will be merged with existing content");
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 templates = ["spec.md", "proposal.md", "tasks.md", "clarify.md", "checklist.md"];
405
- for (const tpl of templates) {
406
- copyTemplate(tpl, join8(specDir, "templates", tpl), lang);
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} ${templates.length} templates (${lang})`);
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: Run superspec create <name> to create a change");
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 existsSync9 } from "fs";
458
+ import { existsSync as existsSync10 } from "fs";
432
459
  import { join as join9 } from "path";
433
- async function createCommand(name, options) {
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 changePath = join9(cwd, specDir, "changes", name);
443
- if (existsSync9(changePath)) {
444
- log.warn(`${symbol.warn} Change "${name}" already exists: ${changePath}`);
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(`Creating Change: ${name}`);
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: getDateString(),
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}{name}";
476
- const branchName = branchTemplate.replace("{prefix}", branchPrefix).replace("{name}", name);
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} Branch creation failed: ${e.message}`);
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(`Path: ${specDir}/changes/${name}/`);
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(`Workflow: /ss-create \u2192 /ss-tasks \u2192 /ss-apply (boost)`);
543
+ log.dim(`${t("Workflow", "\u5DE5\u4F5C\u6D41")}: /ss-create \u2192 /ss-tasks \u2192 /ss-apply (boost)`);
488
544
  } else {
489
- log.dim(`Workflow: /ss-tasks \u2192 /ss-apply`);
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 existsSync10, readdirSync as readdirSync5, renameSync } from "fs";
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 (!existsSync10(changesDir)) {
502
- log.warn(`${symbol.warn} \u6CA1\u6709\u627E\u5230 changes \u76EE\u5F55`);
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 = readdirSync5(changesDir, { withFileTypes: true }).filter(
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 (!existsSync10(changePath)) {
520
- log.warn(`${symbol.warn} \u53D8\u66F4 "${name}" \u4E0D\u5B58\u5728`);
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} \u5F52\u6863\u53D8\u66F4: ${name}`);
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 (existsSync10(dest)) {
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 existsSync11 } from "fs";
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 (!existsSync11(join11(cwd, "superspec.config.json"))) {
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 templates = ["spec.md", "proposal.md", "tasks.md", "clarify.md", "checklist.md"];
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 templates) {
560
- copyTemplate(tpl, join11(specDir, "templates", tpl), lang);
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 existsSync12, readdirSync as readdirSync6 } from "fs";
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 existsSync13, readdirSync as readdirSync7 } from "fs";
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 existsSync14, readFileSync as readFileSync7, readdirSync as readdirSync8 } from "fs";
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/link.ts
585
- import { existsSync as existsSync15, readFileSync as readFileSync8, writeFileSync as writeFileSync4, readdirSync as readdirSync9 } from "fs";
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 existsSync16, readFileSync as readFileSync9, readdirSync as readdirSync10 } from "fs";
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 writeFileSync6, readdirSync as readdirSync12 } from "fs";
598
- import { join as join18 } from "path";
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,