@superspec/cli 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -5,7 +5,7 @@ import { createRequire } from "module";
5
5
  import { program } from "commander";
6
6
 
7
7
  // src/commands/init.ts
8
- import { existsSync as existsSync5, writeFileSync as writeFileSync3, readdirSync as readdirSync2 } from "fs";
8
+ import { existsSync as existsSync6, writeFileSync as writeFileSync3, readdirSync as readdirSync3 } from "fs";
9
9
  import { join as join5 } from "path";
10
10
  import { execSync as execSync2 } from "child_process";
11
11
 
@@ -13,11 +13,12 @@ import { execSync as execSync2 } from "child_process";
13
13
  import { readFileSync, existsSync } from "fs";
14
14
  import { join } from "path";
15
15
  var DEFAULT_CONFIG = {
16
- lang: "zh",
16
+ lang: "en",
17
17
  aiEditor: "cursor",
18
18
  specDir: "superspec",
19
- branchPrefix: "spec/",
20
- branchTemplate: "{prefix}{name}",
19
+ branchPrefix: "",
20
+ branchTemplate: "{prefix}{intentType}-{date}-{feature}-{user}",
21
+ changeNameTemplate: "{prefix}{intentType}-{date}-{feature}-{user}",
21
22
  boost: false,
22
23
  strategy: "follow",
23
24
  context: [],
@@ -26,7 +27,8 @@ var DEFAULT_CONFIG = {
26
27
  proposal: "proposal.md",
27
28
  tasks: "tasks.md",
28
29
  clarify: "clarify.md",
29
- checklist: "checklist.md"
30
+ checklist: "checklist.md",
31
+ design: "design.md"
30
32
  },
31
33
  archive: {
32
34
  dir: "archive",
@@ -37,16 +39,18 @@ var DEFAULT_CONFIG = {
37
39
  hardLines: 400
38
40
  },
39
41
  artifacts: ["proposal"],
40
- boostArtifacts: ["proposal", "spec", "tasks", "checklist"]
42
+ boostArtifacts: ["proposal", "spec", "design", "tasks", "checklist"]
41
43
  };
42
- function loadConfig(projectRoot = process.cwd()) {
44
+ function loadConfig(projectRoot = process.cwd(), silent = false) {
43
45
  const configPath = join(projectRoot, "superspec.config.json");
44
46
  let userConfig = {};
45
47
  if (existsSync(configPath)) {
46
48
  try {
47
49
  userConfig = JSON.parse(readFileSync(configPath, "utf-8"));
48
50
  } catch (e) {
49
- console.warn(`\u26A0 \u914D\u7F6E\u6587\u4EF6\u89E3\u6790\u5931\u8D25: ${e.message}`);
51
+ if (!silent) {
52
+ console.warn(`\u26A0 Config file parsing failed: ${e.message}`);
53
+ }
50
54
  }
51
55
  }
52
56
  return deepMerge(DEFAULT_CONFIG, userConfig);
@@ -57,43 +61,58 @@ function getDefaultConfig() {
57
61
  function deepMerge(target, source) {
58
62
  const result = { ...target };
59
63
  for (const key of Object.keys(source)) {
60
- if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key]) && target[key] && typeof target[key] === "object") {
61
- result[key] = deepMerge(target[key], source[key]);
64
+ const val = source[key];
65
+ if (val === null || val === void 0) continue;
66
+ if (typeof val === "object" && !Array.isArray(val) && target[key] && typeof target[key] === "object") {
67
+ result[key] = deepMerge(target[key], val);
62
68
  } else {
63
- result[key] = source[key];
69
+ result[key] = val;
64
70
  }
65
71
  }
66
72
  return result;
67
73
  }
68
74
 
69
75
  // src/core/template.ts
70
- import { existsSync as existsSync3, copyFileSync, readFileSync as readFileSync2, writeFileSync } from "fs";
76
+ import { existsSync as existsSync4, copyFileSync, readFileSync as readFileSync2, writeFileSync } from "fs";
71
77
  import { join as join3, dirname as dirname2 } from "path";
72
78
 
73
79
  // src/utils/paths.ts
74
80
  import { dirname, join as join2 } from "path";
81
+ import { existsSync as existsSync2 } from "fs";
75
82
  import { fileURLToPath } from "url";
76
83
  function getPackageRoot() {
77
84
  const __filename2 = fileURLToPath(import.meta.url);
78
- const __dirname2 = dirname(__filename2);
79
- return join2(__dirname2, "..", "..");
85
+ let dir = dirname(__filename2);
86
+ while (dir !== dirname(dir)) {
87
+ if (existsSync2(join2(dir, "package.json")) && existsSync2(join2(dir, "templates"))) {
88
+ return dir;
89
+ }
90
+ dir = dirname(dir);
91
+ }
92
+ return join2(dirname(__filename2), "..", "..");
80
93
  }
81
94
 
82
95
  // src/utils/fs.ts
83
- import { mkdirSync, existsSync as existsSync2 } from "fs";
96
+ import { mkdirSync, existsSync as existsSync3, readdirSync } from "fs";
84
97
  function ensureDir(dir) {
85
- if (!existsSync2(dir)) {
98
+ if (!existsSync3(dir)) {
86
99
  mkdirSync(dir, { recursive: true });
87
100
  }
88
101
  }
102
+ function resolveChangeNames(changesDir, name, archiveDirName) {
103
+ if (!existsSync3(changesDir)) return [];
104
+ if (name) return [name];
105
+ return readdirSync(changesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name !== archiveDirName).map((e) => e.name);
106
+ }
89
107
 
90
108
  // src/core/template.ts
91
109
  function resolveTemplatePath(templateName, lang = "zh") {
92
110
  const root = getPackageRoot();
93
111
  const langPath = join3(root, "templates", lang, templateName);
94
- if (existsSync3(langPath)) return langPath;
95
- const fallback = join3(root, "templates", "zh", templateName);
96
- if (existsSync3(fallback)) return fallback;
112
+ if (existsSync4(langPath)) return langPath;
113
+ const fallbackLang = lang === "zh" ? "en" : "zh";
114
+ const fallback = join3(root, "templates", fallbackLang, templateName);
115
+ if (existsSync4(fallback)) return fallback;
97
116
  throw new Error(`Template not found: ${templateName} (lang: ${lang})`);
98
117
  }
99
118
  function copyTemplate(templateName, destPath, lang = "zh") {
@@ -128,9 +147,10 @@ function writeRenderedTemplate(templateName, destPath, vars = {}, lang = "zh") {
128
147
 
129
148
  // src/utils/git.ts
130
149
  import { execSync } from "child_process";
150
+ var GIT_TIMEOUT = 1e4;
131
151
  function isGitRepo() {
132
152
  try {
133
- execSync("git rev-parse --is-inside-work-tree", { stdio: "ignore" });
153
+ execSync("git rev-parse --is-inside-work-tree", { stdio: "ignore", timeout: GIT_TIMEOUT });
134
154
  return true;
135
155
  } catch {
136
156
  return false;
@@ -141,15 +161,15 @@ function createBranch(branchName) {
141
161
  if (!SAFE_BRANCH_RE.test(branchName)) {
142
162
  throw new Error(`invalid branch name: ${branchName}`);
143
163
  }
144
- execSync(`git checkout -b ${branchName}`, { stdio: "inherit" });
164
+ execSync(`git checkout -b ${branchName}`, { stdio: "inherit", timeout: GIT_TIMEOUT });
145
165
  }
146
166
  function getDefaultBranch() {
147
167
  try {
148
- const ref = execSync("git symbolic-ref refs/remotes/origin/HEAD", { encoding: "utf-8" }).trim();
168
+ const ref = execSync("git symbolic-ref refs/remotes/origin/HEAD", { encoding: "utf-8", timeout: GIT_TIMEOUT }).trim();
149
169
  return ref.replace("refs/remotes/origin/", "");
150
170
  } catch {
151
171
  try {
152
- execSync("git rev-parse --verify main", { stdio: "ignore" });
172
+ execSync("git rev-parse --verify main", { stdio: "ignore", timeout: GIT_TIMEOUT });
153
173
  return "main";
154
174
  } catch {
155
175
  return "master";
@@ -159,8 +179,8 @@ function getDefaultBranch() {
159
179
  function getDiffFiles(base) {
160
180
  const baseBranch = base || getDefaultBranch();
161
181
  try {
162
- const mergeBase = execSync(`git merge-base ${baseBranch} HEAD`, { encoding: "utf-8" }).trim();
163
- const output = execSync(`git diff --name-status ${mergeBase}`, { encoding: "utf-8" }).trim();
182
+ const mergeBase = execSync(`git merge-base ${baseBranch} HEAD`, { encoding: "utf-8", timeout: GIT_TIMEOUT }).trim();
183
+ const output = execSync(`git diff --name-status ${mergeBase}`, { encoding: "utf-8", timeout: 3e4 }).trim();
164
184
  if (!output) return [];
165
185
  return output.split("\n").map((line) => {
166
186
  const [status, ...parts] = line.split(" ");
@@ -172,7 +192,7 @@ function getDiffFiles(base) {
172
192
  }
173
193
 
174
194
  // src/prompts/index.ts
175
- import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2, readdirSync } from "fs";
195
+ import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync2, readdirSync as readdirSync2 } from "fs";
176
196
  import { join as join4 } from "path";
177
197
 
178
198
  // src/ui/index.ts
@@ -263,11 +283,19 @@ var symbol = {
263
283
  folder: theme.primary("\u{1F4C1}"),
264
284
  file: theme.info("\u{1F4C4}"),
265
285
  git: theme.warning("\u{1F33F}"),
266
- ai: theme.boost("\u{1F916}")
286
+ ai: theme.boost("\u{1F916}"),
287
+ info: theme.info("\u2139")
267
288
  };
268
289
  function printLogo(size = "small") {
269
290
  console.log(logo[size]);
270
291
  }
292
+ var _lang = "en";
293
+ function setLang(lang) {
294
+ _lang = lang;
295
+ }
296
+ function t(en, zh) {
297
+ return _lang === "zh" ? zh : en;
298
+ }
271
299
  function printSummary(items) {
272
300
  const maxLabel = Math.max(...items.map((i) => i.label.length));
273
301
  const width = 50;
@@ -275,7 +303,7 @@ function printSummary(items) {
275
303
  for (const { label, value } of items) {
276
304
  const padding = " ".repeat(maxLabel - label.length);
277
305
  const line = `${theme.dim(label)}${padding} ${symbol.arrow} ${theme.highlight(value)}`;
278
- const plainLine = line.replace(/\u001b\[\d+m/g, "");
306
+ const plainLine = line.replace(/\u001b\[\d+(?:;\d+)*m/g, "");
279
307
  const rightPad = " ".repeat(Math.max(0, width - plainLine.length - 4));
280
308
  console.log(theme.border("\u2502 ") + line + rightPad + theme.border(" \u2502"));
281
309
  }
@@ -325,8 +353,8 @@ function installRules(cwd, editor) {
325
353
  }
326
354
  const rulesDir = join4(cwd, config.rules);
327
355
  ensureDir(rulesDir);
328
- const promptSrc = join4(getPackageRoot(), "prompts", "cursor-rules.md");
329
- if (existsSync4(promptSrc)) {
356
+ const promptSrc = join4(getPackageRoot(), "prompts", "rules.md");
357
+ if (existsSync5(promptSrc)) {
330
358
  const content = readFileSync3(promptSrc, "utf-8");
331
359
  const rulesFile = "rulesFile" in config ? config.rulesFile : "superspec.md";
332
360
  const destPath = join4(rulesDir, rulesFile);
@@ -339,12 +367,12 @@ var SS_END = "<!-- superspec:end -->";
339
367
  function installAgentsMd(cwd) {
340
368
  const agentsMdPath = join4(cwd, "AGENTS.md");
341
369
  const agentPromptSrc = join4(getPackageRoot(), "prompts", "agents.md");
342
- if (!existsSync4(agentPromptSrc)) return;
370
+ if (!existsSync5(agentPromptSrc)) return;
343
371
  const newContent = readFileSync3(agentPromptSrc, "utf-8");
344
372
  const wrapped = `${SS_START}
345
373
  ${newContent}
346
374
  ${SS_END}`;
347
- if (existsSync4(agentsMdPath)) {
375
+ if (existsSync5(agentsMdPath)) {
348
376
  const existing = readFileSync3(agentsMdPath, "utf-8");
349
377
  const startIdx = existing.indexOf(SS_START);
350
378
  const endIdx = existing.indexOf(SS_END);
@@ -368,12 +396,12 @@ function installCommands(cwd, editor, lang = "zh") {
368
396
  ensureDir(commandsDir);
369
397
  const templatesDir = join4(getPackageRoot(), "templates", lang, "commands");
370
398
  const fallbackDir = join4(getPackageRoot(), "templates", "zh", "commands");
371
- const sourceDir = existsSync4(templatesDir) ? templatesDir : fallbackDir;
372
- if (!existsSync4(sourceDir)) {
399
+ const sourceDir = existsSync5(templatesDir) ? templatesDir : fallbackDir;
400
+ if (!existsSync5(sourceDir)) {
373
401
  log.warn(`${symbol.warn} Commands templates not found: ${sourceDir}`);
374
402
  return;
375
403
  }
376
- const commandFiles = readdirSync(sourceDir).filter((f) => f.endsWith(".md"));
404
+ const commandFiles = readdirSync2(sourceDir).filter((f) => f.endsWith(".md"));
377
405
  for (const file of commandFiles) {
378
406
  const srcPath = join4(sourceDir, file);
379
407
  const destPath = join4(commandsDir, file);
@@ -387,11 +415,12 @@ function installCommands(cwd, editor, lang = "zh") {
387
415
  async function initCommand(options) {
388
416
  const cwd = process.cwd();
389
417
  const configPath = join5(cwd, "superspec.config.json");
390
- if (existsSync5(configPath) && !options.force) {
391
- log.warn(`${symbol.warn} superspec.config.json already exists, use --force to overwrite`);
418
+ if (existsSync6(configPath) && !options.force) {
419
+ 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")}`);
392
420
  return;
393
421
  }
394
422
  const lang = options.lang || "zh";
423
+ setLang(lang);
395
424
  printLogo("small");
396
425
  console.log(theme.dim(" Spec-Driven Development Toolkit\n"));
397
426
  const config = getDefaultConfig();
@@ -401,27 +430,30 @@ async function initCommand(options) {
401
430
  config.aiEditor = aiEditor;
402
431
  }
403
432
  const specDir = join5(cwd, config.specDir);
404
- const existingFiles = readdirSync2(cwd).filter((f) => !f.startsWith(".") && f !== "node_modules");
433
+ const existingFiles = readdirSync3(cwd).filter((f) => !f.startsWith(".") && f !== "node_modules");
405
434
  if (existingFiles.length > 0 && !options.force) {
406
- log.warn(`${symbol.warn} Current directory is not empty (${existingFiles.length} items)`);
407
- log.dim(" Template files will be merged with existing content");
435
+ log.warn(`${symbol.warn} ${t(`current directory is not empty (${existingFiles.length} items)`, `\u5F53\u524D\u76EE\u5F55\u975E\u7A7A\uFF08${existingFiles.length} \u9879\uFF09`)}`);
436
+ log.dim(` ${t("template files will be merged with existing content", "\u6A21\u677F\u6587\u4EF6\u5C06\u4E0E\u73B0\u6709\u5185\u5BB9\u5408\u5E76")}`);
408
437
  console.log();
409
438
  }
410
- log.section("Creating Configuration");
439
+ log.section(t("Creating Configuration", "\u521B\u5EFA\u914D\u7F6E"));
411
440
  writeFileSync3(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
412
441
  log.success(`${symbol.file} superspec.config.json`);
413
- log.section("Creating Directory Structure");
442
+ log.section(t("Creating Directory Structure", "\u521B\u5EFA\u76EE\u5F55\u7ED3\u6784"));
414
443
  ensureDir(join5(specDir, "changes"));
415
444
  ensureDir(join5(specDir, "templates"));
416
445
  log.success(`${symbol.folder} ${config.specDir}/changes/`);
417
446
  log.success(`${symbol.folder} ${config.specDir}/templates/`);
418
- log.section("Installing Templates");
419
- const templates = ["spec.md", "proposal.md", "tasks.md", "clarify.md", "checklist.md"];
420
- for (const tpl of templates) {
421
- copyTemplate(tpl, join5(specDir, "templates", tpl), lang);
447
+ log.section(t("Installing Templates", "\u5B89\u88C5\u6A21\u677F"));
448
+ const templateNames = Object.values(config.templates).map((v) => v.endsWith(".md") ? v : `${v}.md`);
449
+ for (const tpl of templateNames) {
450
+ try {
451
+ copyTemplate(tpl, join5(specDir, "templates", tpl), lang);
452
+ } catch {
453
+ }
422
454
  }
423
- log.success(`${symbol.ok} ${templates.length} templates (${lang})`);
424
- log.section("Installing AI Agent Files");
455
+ log.success(`${symbol.ok} ${templateNames.length} ${t("templates", "\u4E2A\u6A21\u677F")} (${lang})`);
456
+ log.section(t("Installing AI Agent Files", "\u5B89\u88C5 AI Agent \u6587\u4EF6"));
425
457
  installAgentsMd(cwd);
426
458
  if (aiEditor && AI_EDITORS[aiEditor]) {
427
459
  installRules(cwd, aiEditor);
@@ -438,52 +470,79 @@ async function initCommand(options) {
438
470
  { label: "AI agent", value: options.ai },
439
471
  { label: "Language", value: lang }
440
472
  ]);
441
- log.done("SuperSpec initialized successfully!");
442
- log.dim("Next: Run superspec create <name> to create a change");
473
+ log.done(t("SuperSpec initialized successfully!", "SuperSpec \u521D\u59CB\u5316\u6210\u529F\uFF01"));
474
+ log.dim(`${t("Next", "\u4E0B\u4E00\u6B65")}: superspec create <feature>`);
443
475
  }
444
476
 
445
477
  // src/commands/create.ts
446
- import { existsSync as existsSync6 } from "fs";
478
+ import { existsSync as existsSync7 } from "fs";
447
479
  import { join as join6 } from "path";
448
480
 
449
481
  // src/utils/date.ts
450
482
  function getDateString() {
451
483
  const d = /* @__PURE__ */ new Date();
452
- return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
484
+ return `${d.getFullYear()}${String(d.getMonth() + 1).padStart(2, "0")}${String(d.getDate()).padStart(2, "0")}`;
485
+ }
486
+
487
+ // src/utils/template.ts
488
+ function renderNameTemplate(template, vars, strict = false) {
489
+ let result = template;
490
+ for (const [key, value] of Object.entries(vars)) {
491
+ if (value !== void 0) {
492
+ result = result.replace(new RegExp(`\\{${key}\\}`, "g"), value);
493
+ }
494
+ }
495
+ result = result.replace(/\{[^}]+\}/g, "");
496
+ if (strict) {
497
+ return result.replace(/[^a-zA-Z0-9._\-/]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
498
+ }
499
+ return result.replace(/[^\p{L}\p{N}._\-/]/gu, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
453
500
  }
454
501
 
455
502
  // src/commands/create.ts
456
- async function createCommand(name, options) {
503
+ async function createCommand(feature, options) {
457
504
  const cwd = process.cwd();
458
505
  const config = loadConfig(cwd);
459
506
  const specDir = options.specDir || config.specDir;
460
507
  const branchPrefix = options.branchPrefix || config.branchPrefix;
461
508
  const boost = options.boost || config.boost;
462
509
  const strategy = options.creative ? "create" : config.strategy;
463
- const lang = config.lang || "zh";
464
510
  const description = options.description || "";
465
- const changePath = join6(cwd, specDir, "changes", name);
466
- if (existsSync6(changePath)) {
467
- log.warn(`${symbol.warn} Change "${name}" already exists: ${changePath}`);
511
+ const lang = options.lang || config.lang || "en";
512
+ const templateVars = {
513
+ prefix: branchPrefix,
514
+ intentType: options.intentType,
515
+ feature,
516
+ date: getDateString(),
517
+ user: options.user
518
+ };
519
+ const changeNameTemplate = options.changeNameTemplate || config.changeNameTemplate || "{date}-{feature}";
520
+ const changeFolderName = renderNameTemplate(changeNameTemplate, templateVars, false);
521
+ const changePath = join6(cwd, specDir, "changes", changeFolderName);
522
+ if (existsSync7(changePath)) {
523
+ log.warn(`${symbol.warn} ${t(`change "${changeFolderName}" already exists`, `\u53D8\u66F4 "${changeFolderName}" \u5DF2\u5B58\u5728`)}: ${changePath}`);
468
524
  return;
469
525
  }
470
- log.title(`Creating Change: ${name}`);
526
+ log.title(`${t("Creating Change", "\u521B\u5EFA\u53D8\u66F4")}: ${changeFolderName}`);
527
+ if (options.intentType) {
528
+ log.info(`${t("Intent Type", "\u610F\u56FE\u7C7B\u578B")}: ${options.intentType}`);
529
+ }
471
530
  if (boost) {
472
- log.boost(`${symbol.bolt} Boost mode enabled`);
531
+ log.boost(`${symbol.bolt} ${t("Boost mode enabled", "\u589E\u5F3A\u6A21\u5F0F\u5DF2\u542F\u7528")}`);
473
532
  }
474
533
  if (strategy === "create") {
475
- log.boost(`${symbol.bolt} Creative mode: exploring new solutions`);
534
+ log.boost(`${symbol.bolt} ${t("Creative mode: exploring new solutions", "\u521B\u9020\u6A21\u5F0F\uFF1A\u63A2\u7D22\u65B0\u65B9\u6848")}`);
476
535
  }
477
536
  ensureDir(changePath);
478
537
  const vars = {
479
- name,
480
- date: getDateString(),
538
+ name: changeFolderName,
539
+ date: templateVars.date,
481
540
  boost: boost ? "true" : "false",
482
541
  strategy,
483
542
  description
484
543
  };
485
544
  const artifacts = boost ? config.boostArtifacts : config.artifacts;
486
- log.section("Generating Artifacts");
545
+ log.section(t("Generating Artifacts", "\u751F\u6210 Artifacts"));
487
546
  for (const artifact of artifacts) {
488
547
  const templateFile = config.templates[artifact] || `${artifact}.md`;
489
548
  const destPath = join6(changePath, `${artifact}.md`);
@@ -495,69 +554,70 @@ async function createCommand(name, options) {
495
554
  }
496
555
  }
497
556
  if (options.branch !== false && isGitRepo()) {
498
- const branchTemplate = config.branchTemplate || "{prefix}{name}";
499
- const branchName = branchTemplate.replace("{prefix}", branchPrefix).replace("{name}", name);
557
+ const branchTemplate = options.branchTemplate || config.branchTemplate || "{prefix}{date}-{feature}";
558
+ const branchName = renderNameTemplate(branchTemplate, templateVars, true);
500
559
  try {
501
560
  createBranch(branchName);
502
561
  log.success(`${symbol.ok} Branch: ${branchName}`);
503
562
  } catch (e) {
504
- log.warn(`${symbol.warn} Branch creation failed: ${e.message}`);
563
+ log.warn(`${symbol.warn} ${t("branch creation failed", "\u5206\u652F\u521B\u5EFA\u5931\u8D25")}: ${e.message}`);
505
564
  }
506
565
  }
507
- log.done("Change created successfully!");
508
- log.dim(`Path: ${specDir}/changes/${name}/`);
566
+ log.done(t("Change created successfully!", "\u53D8\u66F4\u521B\u5EFA\u6210\u529F\uFF01"));
567
+ log.dim(`${t("Path", "\u8DEF\u5F84")}: ${specDir}/changes/${changeFolderName}/`);
509
568
  if (boost) {
510
- log.dim(`Workflow: /ss-create \u2192 /ss-tasks \u2192 /ss-apply (boost)`);
569
+ log.dim(`${t("Workflow", "\u5DE5\u4F5C\u6D41")}: /ss-create \u2192 /ss-tasks \u2192 /ss-apply (boost)`);
511
570
  } else {
512
- log.dim(`Workflow: /ss-tasks \u2192 /ss-apply`);
571
+ log.dim(`${t("Workflow", "\u5DE5\u4F5C\u6D41")}: /ss-tasks \u2192 /ss-apply`);
513
572
  }
573
+ log.dim(`${t("Next", "\u4E0B\u4E00\u6B65")}: superspec lint ${changeFolderName}`);
514
574
  }
515
575
 
516
576
  // src/commands/archive.ts
517
- import { existsSync as existsSync7, readdirSync as readdirSync3, renameSync } from "fs";
577
+ import { existsSync as existsSync8, readdirSync as readdirSync4, renameSync } from "fs";
518
578
  import { join as join7 } from "path";
519
579
  async function archiveCommand(name, options) {
520
580
  const cwd = process.cwd();
521
581
  const config = loadConfig(cwd);
522
582
  const changesDir = join7(cwd, config.specDir, "changes");
523
583
  const archiveDir = join7(cwd, config.specDir, "changes", config.archive.dir);
524
- if (!existsSync7(changesDir)) {
525
- log.warn(`${symbol.warn} \u6CA1\u6709\u627E\u5230 changes \u76EE\u5F55`);
584
+ if (!existsSync8(changesDir)) {
585
+ log.warn(`${symbol.warn} ${t("no changes directory found", "\u672A\u627E\u5230 changes \u76EE\u5F55")}`);
526
586
  return;
527
587
  }
528
588
  if (options.all) {
529
- const entries = readdirSync3(changesDir, { withFileTypes: true }).filter(
589
+ const entries = readdirSync4(changesDir, { withFileTypes: true }).filter(
530
590
  (e) => e.isDirectory() && e.name !== config.archive.dir
531
591
  );
532
592
  if (entries.length === 0) {
533
- log.warn(`${symbol.warn} \u6CA1\u6709\u53EF\u5F52\u6863\u7684\u53D8\u66F4`);
593
+ log.warn(`${symbol.warn} ${t("no changes to archive", "\u6CA1\u6709\u53EF\u5F52\u6863\u7684\u53D8\u66F4")}`);
534
594
  return;
535
595
  }
536
- log.info(`${symbol.start} \u5F52\u6863\u6240\u6709\u53D8\u66F4...`);
596
+ log.info(`${symbol.start} ${t("archiving all changes...", "\u5F52\u6863\u6240\u6709\u53D8\u66F4...")}`);
537
597
  for (const entry of entries) {
538
598
  archiveOne(entry.name, changesDir, archiveDir, config);
539
599
  }
540
600
  } else if (name) {
541
601
  const changePath = join7(changesDir, name);
542
- if (!existsSync7(changePath)) {
543
- log.warn(`${symbol.warn} \u53D8\u66F4 "${name}" \u4E0D\u5B58\u5728`);
602
+ if (!existsSync8(changePath)) {
603
+ log.warn(`${symbol.warn} ${t(`change "${name}" not found`, `\u53D8\u66F4 "${name}" \u4E0D\u5B58\u5728`)}`);
544
604
  return;
545
605
  }
546
- log.info(`${symbol.start} \u5F52\u6863\u53D8\u66F4: ${name}`);
606
+ log.info(`${symbol.start} ${t(`archiving: ${name}`, `\u5F52\u6863\u53D8\u66F4: ${name}`)}`);
547
607
  archiveOne(name, changesDir, archiveDir, config);
548
608
  } else {
549
- log.warn(`${symbol.warn} \u8BF7\u6307\u5B9A\u53D8\u66F4\u540D\u79F0\u6216\u4F7F\u7528 --all`);
609
+ log.warn(`${symbol.warn} ${t("specify a name or use --all", "\u8BF7\u6307\u5B9A\u53D8\u66F4\u540D\u79F0\u6216\u4F7F\u7528 --all")}`);
550
610
  return;
551
611
  }
552
- log.info(`${symbol.start} \u5F52\u6863\u5B8C\u6210\uFF01`);
612
+ log.info(`${symbol.start} ${t("archive done!", "\u5F52\u6863\u5B8C\u6210\uFF01")}`);
553
613
  }
554
614
  function archiveOne(name, changesDir, archiveDir, config) {
555
615
  ensureDir(archiveDir);
556
616
  const src = join7(changesDir, name);
557
617
  const dateStr = config.archive.datePrefix ? `${getDateString()}-` : "";
558
618
  const dest = join7(archiveDir, `${dateStr}${name}`);
559
- if (existsSync7(dest)) {
560
- log.warn(` ${symbol.warn} \u5F52\u6863\u76EE\u6807\u5DF2\u5B58\u5728: ${dest}`);
619
+ if (existsSync8(dest)) {
620
+ log.warn(` ${symbol.warn} ${t("archive target exists", "\u5F52\u6863\u76EE\u6807\u5DF2\u5B58\u5728")}: ${dest}`);
561
621
  return;
562
622
  }
563
623
  renameSync(src, dest);
@@ -565,43 +625,46 @@ function archiveOne(name, changesDir, archiveDir, config) {
565
625
  }
566
626
 
567
627
  // src/commands/update.ts
568
- import { existsSync as existsSync8 } from "fs";
628
+ import { existsSync as existsSync9 } from "fs";
569
629
  import { join as join8 } from "path";
570
630
  async function updateCommand() {
571
631
  const cwd = process.cwd();
572
632
  const config = loadConfig(cwd);
573
633
  const specDir = join8(cwd, config.specDir);
574
634
  const lang = config.lang || "zh";
575
- if (!existsSync8(join8(cwd, "superspec.config.json"))) {
576
- log.warn(`${symbol.warn} \u5F53\u524D\u76EE\u5F55\u672A\u521D\u59CB\u5316 SuperSpec\uFF0C\u8BF7\u5148\u8FD0\u884C superspec init`);
635
+ if (!existsSync9(join8(cwd, "superspec.config.json"))) {
636
+ 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")}`);
577
637
  return;
578
638
  }
579
- log.info(`${symbol.start} \u66F4\u65B0 SuperSpec...`);
580
- const templates = ["spec.md", "proposal.md", "tasks.md", "clarify.md", "checklist.md"];
639
+ log.info(`${symbol.start} ${t("updating SuperSpec...", "\u66F4\u65B0 SuperSpec...")}`);
640
+ const templateNames = Object.values(config.templates).map((v) => v.endsWith(".md") ? v : `${v}.md`);
581
641
  ensureDir(join8(specDir, "templates"));
582
- for (const tpl of templates) {
583
- copyTemplate(tpl, join8(specDir, "templates", tpl), lang);
642
+ for (const tpl of templateNames) {
643
+ try {
644
+ copyTemplate(tpl, join8(specDir, "templates", tpl), lang);
645
+ } catch {
646
+ }
584
647
  }
585
- log.success(` ${symbol.ok} \u6A21\u677F\u66F4\u65B0 (${lang})`);
648
+ log.success(` ${symbol.ok} ${t("templates updated", "\u6A21\u677F\u66F4\u65B0")} (${lang})`);
586
649
  installAgentsMd(cwd);
587
650
  const aiEditor = config.aiEditor;
588
651
  if (aiEditor && AI_EDITORS[aiEditor]) {
589
652
  installRules(cwd, aiEditor);
590
653
  installCommands(cwd, aiEditor, lang);
591
654
  }
592
- log.info(`${symbol.start} \u66F4\u65B0\u5B8C\u6210\uFF01`);
655
+ log.info(`${symbol.start} ${t("update done!", "\u66F4\u65B0\u5B8C\u6210\uFF01")}`);
593
656
  }
594
657
 
595
658
  // src/commands/lint.ts
596
- import { existsSync as existsSync10, readdirSync as readdirSync5 } from "fs";
659
+ import { existsSync as existsSync11 } from "fs";
597
660
  import { join as join10 } from "path";
598
661
 
599
662
  // src/core/lint.ts
600
- import { readFileSync as readFileSync4, existsSync as existsSync9, readdirSync as readdirSync4 } from "fs";
663
+ import { readFileSync as readFileSync4, existsSync as existsSync10, readdirSync as readdirSync5 } from "fs";
601
664
  import { join as join9, basename } from "path";
602
665
  function lintArtifact(filePath, targetLines, hardLines) {
603
666
  const artifact = basename(filePath);
604
- if (!existsSync9(filePath)) {
667
+ if (!existsSync10(filePath)) {
605
668
  return { artifact, lines: 0, status: "ok", message: "not found" };
606
669
  }
607
670
  const content = readFileSync4(filePath, "utf-8");
@@ -615,8 +678,8 @@ function lintArtifact(filePath, targetLines, hardLines) {
615
678
  return { artifact, lines, status: "ok", message: `${lines} lines` };
616
679
  }
617
680
  function lintChange(changePath, targetLines, hardLines) {
618
- if (!existsSync9(changePath)) return [];
619
- const files = readdirSync4(changePath).filter((f) => f.endsWith(".md"));
681
+ if (!existsSync10(changePath)) return [];
682
+ const files = readdirSync5(changePath).filter((f) => f.endsWith(".md"));
620
683
  return files.map((f) => lintArtifact(join9(changePath, f), targetLines, hardLines));
621
684
  }
622
685
 
@@ -626,26 +689,16 @@ async function lintCommand(name) {
626
689
  const config = loadConfig(cwd);
627
690
  const changesDir = join10(cwd, config.specDir, "changes");
628
691
  const { targetLines, hardLines } = config.limits;
629
- if (!existsSync10(changesDir)) {
630
- log.warn(`${symbol.warn} no changes directory found`);
631
- return;
632
- }
633
- const names = [];
634
- if (name) {
635
- names.push(name);
636
- } else {
637
- const entries = readdirSync5(changesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name !== config.archive.dir);
638
- names.push(...entries.map((e) => e.name));
639
- }
692
+ const names = resolveChangeNames(changesDir, name, config.archive.dir);
640
693
  if (names.length === 0) {
641
- log.warn(`${symbol.warn} no changes to lint`);
694
+ log.warn(`${symbol.warn} ${t("no changes to lint", "\u6CA1\u6709\u53EF\u68C0\u67E5\u7684\u53D8\u66F4")}`);
642
695
  return;
643
696
  }
644
697
  let hasIssues = false;
645
698
  for (const n of names) {
646
699
  const changePath = join10(changesDir, n);
647
- if (!existsSync10(changePath)) {
648
- log.warn(`${symbol.warn} "${n}" not found`);
700
+ if (!existsSync11(changePath)) {
701
+ log.warn(`${symbol.warn} "${n}" ${t("not found", "\u672A\u627E\u5230")}`);
649
702
  continue;
650
703
  }
651
704
  const results = lintChange(changePath, targetLines, hardLines);
@@ -663,16 +716,16 @@ async function lintCommand(name) {
663
716
  }
664
717
  }
665
718
  if (!hasIssues) {
666
- log.info(`${symbol.start} all artifacts within limits`);
719
+ log.info(`${symbol.start} ${t("all artifacts within limits", "\u6240\u6709 artifact \u5747\u5728\u9650\u5236\u8303\u56F4\u5185")}`);
667
720
  }
668
721
  }
669
722
 
670
723
  // src/commands/validate.ts
671
- import { existsSync as existsSync12, readdirSync as readdirSync7 } from "fs";
724
+ import { existsSync as existsSync13 } from "fs";
672
725
  import { join as join12 } from "path";
673
726
 
674
727
  // src/core/validate.ts
675
- import { readFileSync as readFileSync5, existsSync as existsSync11 } from "fs";
728
+ import { readFileSync as readFileSync5, existsSync as existsSync12 } from "fs";
676
729
  import { join as join11 } from "path";
677
730
 
678
731
  // src/core/frontmatter.ts
@@ -689,6 +742,7 @@ function parseFrontmatter(content) {
689
742
  if (idx === -1) continue;
690
743
  const key = line.slice(0, idx).trim();
691
744
  let value = line.slice(idx + 1).trim();
745
+ if (key === "") continue;
692
746
  if (value === "[]") {
693
747
  value = [];
694
748
  } else if (value.startsWith("[") && value.endsWith("]")) {
@@ -720,17 +774,17 @@ function serializeFrontmatter(meta) {
720
774
  }
721
775
  function addDependency(content, depName) {
722
776
  const { meta, body } = parseFrontmatter(content);
723
- const deps = Array.isArray(meta.depends_on) ? meta.depends_on : [];
724
- if (!deps.includes(depName)) {
725
- deps.push(depName);
777
+ const deps2 = Array.isArray(meta.depends_on) ? meta.depends_on : [];
778
+ if (!deps2.includes(depName)) {
779
+ deps2.push(depName);
726
780
  }
727
- meta.depends_on = deps;
781
+ meta.depends_on = deps2;
728
782
  return serializeFrontmatter(meta) + "\n" + body;
729
783
  }
730
784
  function removeDependency(content, depName) {
731
785
  const { meta, body } = parseFrontmatter(content);
732
- const deps = Array.isArray(meta.depends_on) ? meta.depends_on : [];
733
- meta.depends_on = deps.filter((d) => d !== depName);
786
+ const deps2 = Array.isArray(meta.depends_on) ? meta.depends_on : [];
787
+ meta.depends_on = deps2.filter((d) => d !== depName);
734
788
  return serializeFrontmatter(meta) + "\n" + body;
735
789
  }
736
790
 
@@ -743,7 +797,7 @@ function validateChange(changePath, checkDeps = false) {
743
797
  const issues = [];
744
798
  const read = (name) => {
745
799
  const p = join11(changePath, name);
746
- return existsSync11(p) ? readFileSync5(p, "utf-8") : null;
800
+ return existsSync12(p) ? readFileSync5(p, "utf-8") : null;
747
801
  };
748
802
  const proposal = read("proposal.md");
749
803
  const spec = read("spec.md");
@@ -796,7 +850,7 @@ function validateChange(changePath, checkDeps = false) {
796
850
  }
797
851
  const changesDir = join11(changePath, "..");
798
852
  for (const dep of fmDeps) {
799
- if (!existsSync11(join11(changesDir, dep))) {
853
+ if (!existsSync12(join11(changesDir, dep))) {
800
854
  issues.push({ level: "error", artifact: "proposal.md", message: `depends_on "${dep}" not found in changes` });
801
855
  }
802
856
  }
@@ -809,32 +863,22 @@ async function validateCommand(name, options) {
809
863
  const cwd = process.cwd();
810
864
  const config = loadConfig(cwd);
811
865
  const changesDir = join12(cwd, config.specDir, "changes");
812
- if (!existsSync12(changesDir)) {
813
- log.warn(`${symbol.warn} no changes directory found`);
814
- return;
815
- }
816
- const names = [];
817
- if (name) {
818
- names.push(name);
819
- } else {
820
- const entries = readdirSync7(changesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name !== config.archive.dir);
821
- names.push(...entries.map((e) => e.name));
822
- }
866
+ const names = resolveChangeNames(changesDir, name, config.archive.dir);
823
867
  if (names.length === 0) {
824
- log.warn(`${symbol.warn} no changes to validate`);
868
+ log.warn(`${symbol.warn} ${t("no changes to validate", "\u6CA1\u6709\u53EF\u9A8C\u8BC1\u7684\u53D8\u66F4")}`);
825
869
  return;
826
870
  }
827
871
  let totalIssues = 0;
828
872
  for (const n of names) {
829
873
  const changePath = join12(changesDir, n);
830
- if (!existsSync12(changePath)) {
831
- log.warn(`${symbol.warn} "${n}" not found`);
874
+ if (!existsSync13(changePath)) {
875
+ log.warn(`${symbol.warn} "${n}" ${t("not found", "\u672A\u627E\u5230")}`);
832
876
  continue;
833
877
  }
834
878
  const issues = validateChange(changePath, options.checkDeps);
835
879
  log.info(`${symbol.start} ${n}`);
836
880
  if (issues.length === 0) {
837
- log.success(` ${symbol.ok} all checks passed`);
881
+ log.success(` ${symbol.ok} ${t("all checks passed", "\u6240\u6709\u68C0\u67E5\u901A\u8FC7")}`);
838
882
  } else {
839
883
  for (const issue of issues) {
840
884
  totalIssues++;
@@ -849,42 +893,55 @@ async function validateCommand(name, options) {
849
893
  }
850
894
  }
851
895
  if (totalIssues === 0) {
852
- log.success(`${symbol.ok} all validations passed`);
896
+ log.success(`${symbol.ok} ${t("all validations passed", "\u6240\u6709\u9A8C\u8BC1\u901A\u8FC7")}`);
853
897
  } else {
854
- log.warn(`${symbol.warn} ${totalIssues} issue(s) found`);
898
+ log.warn(`${symbol.warn} ${totalIssues} ${t("issue(s) found", "\u4E2A\u95EE\u9898")}`);
855
899
  }
856
900
  }
857
901
 
858
902
  // src/commands/search.ts
859
- import { existsSync as existsSync13, readFileSync as readFileSync6, readdirSync as readdirSync8 } from "fs";
903
+ import { existsSync as existsSync14, readFileSync as readFileSync6, readdirSync as readdirSync7 } from "fs";
860
904
  import { join as join13, basename as basename3 } from "path";
905
+ var DEFAULT_LIMIT = 50;
861
906
  async function searchCommand(query, options) {
862
907
  const cwd = process.cwd();
863
908
  const config = loadConfig(cwd);
864
909
  const changesDir = join13(cwd, config.specDir, "changes");
865
- if (!existsSync13(changesDir)) {
866
- log.warn(`${symbol.warn} no changes directory found`);
910
+ if (!existsSync14(changesDir)) {
911
+ log.warn(`${symbol.warn} ${t("no changes directory found", "\u672A\u627E\u5230 changes \u76EE\u5F55")}`);
867
912
  return;
868
913
  }
869
914
  const dirs = [];
870
- const activeEntries = readdirSync8(changesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name !== config.archive.dir);
915
+ const activeEntries = readdirSync7(changesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name !== config.archive.dir);
871
916
  for (const e of activeEntries) {
872
917
  dirs.push({ name: e.name, path: join13(changesDir, e.name) });
873
918
  }
874
919
  if (options.archived) {
875
920
  const archiveDir = join13(changesDir, config.archive.dir);
876
- if (existsSync13(archiveDir)) {
877
- const archivedEntries = readdirSync8(archiveDir, { withFileTypes: true }).filter((e) => e.isDirectory());
921
+ if (existsSync14(archiveDir)) {
922
+ const archivedEntries = readdirSync7(archiveDir, { withFileTypes: true }).filter((e) => e.isDirectory());
878
923
  for (const e of archivedEntries) {
879
924
  dirs.push({ name: `${config.archive.dir}/${e.name}`, path: join13(archiveDir, e.name) });
880
925
  }
881
926
  }
882
927
  }
883
- const queryLower = query.toLowerCase();
928
+ let matcher;
929
+ if (options.regex) {
930
+ try {
931
+ const re = new RegExp(query, "i");
932
+ matcher = (line) => re.test(line);
933
+ } catch (e) {
934
+ log.error(`${symbol.fail} ${t("invalid regex", "\u65E0\u6548\u6B63\u5219")}: ${e.message}`);
935
+ return;
936
+ }
937
+ } else {
938
+ const queryLower = query.toLowerCase();
939
+ matcher = (line) => line.toLowerCase().includes(queryLower);
940
+ }
884
941
  const hits = [];
885
942
  for (const dir of dirs) {
886
- if (!existsSync13(dir.path)) continue;
887
- const files = readdirSync8(dir.path).filter((f) => f.endsWith(".md"));
943
+ if (!existsSync14(dir.path)) continue;
944
+ const files = readdirSync7(dir.path).filter((f) => f.endsWith(".md"));
888
945
  for (const file of files) {
889
946
  if (options.artifact) {
890
947
  const artType = basename3(file, ".md");
@@ -894,7 +951,7 @@ async function searchCommand(query, options) {
894
951
  const content = readFileSync6(filePath, "utf-8");
895
952
  const lines = content.split("\n");
896
953
  for (let i = 0; i < lines.length; i++) {
897
- if (lines[i].toLowerCase().includes(queryLower)) {
954
+ if (matcher(lines[i])) {
898
955
  hits.push({
899
956
  change: dir.name,
900
957
  artifact: file,
@@ -906,81 +963,86 @@ async function searchCommand(query, options) {
906
963
  }
907
964
  }
908
965
  if (hits.length === 0) {
909
- log.warn(`${symbol.warn} no results for "${query}"`);
966
+ log.warn(`${symbol.warn} ${t(`no results for "${query}"`, `"${query}" \u65E0\u7ED3\u679C`)}`);
910
967
  return;
911
968
  }
912
- log.info(`${symbol.start} ${hits.length} result(s) for "${query}"`);
913
- for (const hit of hits) {
969
+ const limit = options.limit ? parseInt(options.limit, 10) : DEFAULT_LIMIT;
970
+ const shown = hits.slice(0, limit);
971
+ log.info(`${symbol.start} ${hits.length} ${t("result(s) for", "\u6761\u7ED3\u679C\uFF0C\u641C\u7D22")} "${query}"`);
972
+ for (const hit of shown) {
914
973
  log.dim(` ${hit.change}/${hit.artifact}:${hit.line} ${hit.text}`);
915
974
  }
975
+ if (hits.length > limit) {
976
+ log.dim(` ... ${hits.length - limit} ${t("more result(s), use --limit to show more", "\u6761\u66F4\u591A\u7ED3\u679C\uFF0C\u4F7F\u7528 --limit \u663E\u793A\u66F4\u591A")}`);
977
+ }
916
978
  }
917
979
 
918
- // src/commands/link.ts
919
- import { existsSync as existsSync14, readFileSync as readFileSync7, writeFileSync as writeFileSync4, readdirSync as readdirSync9 } from "fs";
980
+ // src/commands/deps.ts
981
+ import { existsSync as existsSync15, readFileSync as readFileSync7, writeFileSync as writeFileSync4, readdirSync as readdirSync8 } from "fs";
920
982
  import { join as join14 } from "path";
921
- async function linkCommand(name, options) {
983
+ async function depsAddCommand(name, options) {
922
984
  const cwd = process.cwd();
923
985
  const config = loadConfig(cwd);
924
986
  const proposalPath = join14(cwd, config.specDir, "changes", name, "proposal.md");
925
- if (!existsSync14(proposalPath)) {
926
- log.warn(`${symbol.warn} "${name}" not found`);
987
+ if (!existsSync15(proposalPath)) {
988
+ log.warn(`${symbol.warn} "${name}" ${t("not found", "\u672A\u627E\u5230")}`);
927
989
  return;
928
990
  }
929
991
  const content = readFileSync7(proposalPath, "utf-8");
930
- const updated = addDependency(content, options.dependsOn);
992
+ const updated = addDependency(content, options.on);
931
993
  writeFileSync4(proposalPath, updated, "utf-8");
932
- log.success(`${symbol.ok} ${name} \u2192 depends_on: ${options.dependsOn}`);
994
+ log.success(`${symbol.ok} ${name} \u2192 depends_on: ${options.on}`);
933
995
  }
934
- async function unlinkCommand(name, options) {
996
+ async function depsRemoveCommand(name, options) {
935
997
  const cwd = process.cwd();
936
998
  const config = loadConfig(cwd);
937
999
  const proposalPath = join14(cwd, config.specDir, "changes", name, "proposal.md");
938
- if (!existsSync14(proposalPath)) {
939
- log.warn(`${symbol.warn} "${name}" not found`);
1000
+ if (!existsSync15(proposalPath)) {
1001
+ log.warn(`${symbol.warn} "${name}" ${t("not found", "\u672A\u627E\u5230")}`);
940
1002
  return;
941
1003
  }
942
1004
  const content = readFileSync7(proposalPath, "utf-8");
943
- const updated = removeDependency(content, options.dependsOn);
1005
+ const updated = removeDependency(content, options.on);
944
1006
  writeFileSync4(proposalPath, updated, "utf-8");
945
- log.success(`${symbol.ok} ${name} \u2192 removed dependency: ${options.dependsOn}`);
1007
+ log.success(`${symbol.ok} ${name} \u2192 ${t("removed dependency", "\u79FB\u9664\u4F9D\u8D56")}: ${options.on}`);
946
1008
  }
947
- async function depsCommand(name) {
1009
+ async function depsListCommand(name) {
948
1010
  const cwd = process.cwd();
949
1011
  const config = loadConfig(cwd);
950
1012
  const changesDir = join14(cwd, config.specDir, "changes");
951
1013
  if (name) {
952
1014
  const proposalPath = join14(changesDir, name, "proposal.md");
953
- if (!existsSync14(proposalPath)) {
954
- log.warn(`${symbol.warn} "${name}" not found`);
1015
+ if (!existsSync15(proposalPath)) {
1016
+ log.warn(`${symbol.warn} "${name}" ${t("not found", "\u672A\u627E\u5230")}`);
955
1017
  return;
956
1018
  }
957
1019
  const content = readFileSync7(proposalPath, "utf-8");
958
1020
  const { meta } = parseFrontmatter(content);
959
- const deps = Array.isArray(meta.depends_on) ? meta.depends_on : [];
1021
+ const deps2 = Array.isArray(meta.depends_on) ? meta.depends_on : [];
960
1022
  log.info(`${symbol.start} ${name}`);
961
- if (deps.length === 0) {
962
- log.dim(" no dependencies");
1023
+ if (deps2.length === 0) {
1024
+ log.dim(` ${t("no dependencies", "\u65E0\u4F9D\u8D56")}`);
963
1025
  } else {
964
- for (const d of deps) {
1026
+ for (const d of deps2) {
965
1027
  log.dim(` \u2192 ${d}`);
966
1028
  }
967
1029
  }
968
1030
  return;
969
1031
  }
970
- if (!existsSync14(changesDir)) {
971
- log.warn(`${symbol.warn} no changes directory`);
1032
+ if (!existsSync15(changesDir)) {
1033
+ log.warn(`${symbol.warn} ${t("no changes directory", "\u672A\u627E\u5230 changes \u76EE\u5F55")}`);
972
1034
  return;
973
1035
  }
974
- const entries = readdirSync9(changesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name !== config.archive.dir);
975
- log.info(`${symbol.start} dependency graph`);
1036
+ const entries = readdirSync8(changesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name !== config.archive.dir);
1037
+ log.info(`${symbol.start} ${t("dependency graph", "\u4F9D\u8D56\u5173\u7CFB")}`);
976
1038
  for (const entry of entries) {
977
1039
  const proposalPath = join14(changesDir, entry.name, "proposal.md");
978
- if (!existsSync14(proposalPath)) continue;
1040
+ if (!existsSync15(proposalPath)) continue;
979
1041
  const content = readFileSync7(proposalPath, "utf-8");
980
1042
  const { meta } = parseFrontmatter(content);
981
- const deps = Array.isArray(meta.depends_on) ? meta.depends_on : [];
982
- if (deps.length > 0) {
983
- log.dim(` ${entry.name} \u2192 [${deps.join(", ")}]`);
1043
+ const deps2 = Array.isArray(meta.depends_on) ? meta.depends_on : [];
1044
+ if (deps2.length > 0) {
1045
+ log.dim(` ${entry.name} \u2192 [${deps2.join(", ")}]`);
984
1046
  } else {
985
1047
  log.dim(` ${entry.name}`);
986
1048
  }
@@ -988,12 +1050,12 @@ async function depsCommand(name) {
988
1050
  }
989
1051
 
990
1052
  // src/commands/status.ts
991
- import { existsSync as existsSync15, readFileSync as readFileSync8, readdirSync as readdirSync10 } from "fs";
1053
+ import { existsSync as existsSync16, readFileSync as readFileSync8, readdirSync as readdirSync9 } from "fs";
992
1054
  import { join as join15 } from "path";
993
1055
  var ARTIFACT_TYPES = ["proposal", "spec", "tasks", "clarify", "checklist"];
994
1056
  function readStatus(changePath, artifact) {
995
1057
  const filePath = join15(changePath, `${artifact}.md`);
996
- if (!existsSync15(filePath)) return "\u2014";
1058
+ if (!existsSync16(filePath)) return "\u2014";
997
1059
  const content = readFileSync8(filePath, "utf-8");
998
1060
  const { meta } = parseFrontmatter(content);
999
1061
  if (meta.status) return meta.status;
@@ -1018,13 +1080,13 @@ async function statusCommand() {
1018
1080
  const cwd = process.cwd();
1019
1081
  const config = loadConfig(cwd);
1020
1082
  const changesDir = join15(cwd, config.specDir, "changes");
1021
- if (!existsSync15(changesDir)) {
1022
- log.warn(`${symbol.warn} no changes directory found`);
1083
+ if (!existsSync16(changesDir)) {
1084
+ log.warn(`${symbol.warn} ${t("no changes directory found", "\u672A\u627E\u5230 changes \u76EE\u5F55")}`);
1023
1085
  return;
1024
1086
  }
1025
- const entries = readdirSync10(changesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name !== config.archive.dir).sort((a, b) => a.name.localeCompare(b.name));
1087
+ const entries = readdirSync9(changesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name !== config.archive.dir).sort((a, b) => a.name.localeCompare(b.name));
1026
1088
  if (entries.length === 0) {
1027
- log.dim(" no active changes");
1089
+ log.dim(` ${t("no active changes", "\u65E0\u6D3B\u8DC3\u53D8\u66F4")}`);
1028
1090
  return;
1029
1091
  }
1030
1092
  const header = ["Change", ...ARTIFACT_TYPES.map((a) => a.slice(0, 8).padEnd(8)), "Status"];
@@ -1047,31 +1109,50 @@ async function statusCommand() {
1047
1109
  });
1048
1110
  const divider = colWidths.map((w) => "-".repeat(w + 2)).join("+");
1049
1111
  const formatRow = (row) => row.map((cell, i) => ` ${cell.padEnd(colWidths[i])} `).join("|");
1050
- log.info(`${symbol.start} changes`);
1112
+ log.info(`${symbol.start} ${t("changes", "\u53D8\u66F4\u5217\u8868")}`);
1051
1113
  console.log(formatRow(header));
1052
1114
  console.log(divider);
1053
1115
  for (const row of rows) {
1054
1116
  console.log(formatRow(row));
1055
1117
  }
1056
1118
  const archiveDir = join15(changesDir, config.archive.dir);
1057
- if (existsSync15(archiveDir)) {
1058
- const archived = readdirSync10(archiveDir, { withFileTypes: true }).filter((e) => e.isDirectory());
1119
+ if (existsSync16(archiveDir)) {
1120
+ const archived = readdirSync9(archiveDir, { withFileTypes: true }).filter((e) => e.isDirectory());
1059
1121
  if (archived.length > 0) {
1060
1122
  log.dim(`
1061
- ${archived.length} archived change(s)`);
1123
+ ${archived.length} ${t("archived change(s)", "\u4E2A\u5DF2\u5F52\u6863\u53D8\u66F4")}`);
1124
+ }
1125
+ }
1126
+ }
1127
+ async function listCommand(options) {
1128
+ const cwd = process.cwd();
1129
+ const config = loadConfig(cwd);
1130
+ const changesDir = join15(cwd, config.specDir, "changes");
1131
+ if (!existsSync16(changesDir)) return;
1132
+ const entries = readdirSync9(changesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name !== config.archive.dir).sort((a, b) => a.name.localeCompare(b.name));
1133
+ for (const e of entries) {
1134
+ console.log(e.name);
1135
+ }
1136
+ if (options.archived) {
1137
+ const archiveDir = join15(changesDir, config.archive.dir);
1138
+ if (existsSync16(archiveDir)) {
1139
+ const archived = readdirSync9(archiveDir, { withFileTypes: true }).filter((e) => e.isDirectory());
1140
+ for (const e of archived) {
1141
+ console.log(`${config.archive.dir}/${e.name}`);
1142
+ }
1062
1143
  }
1063
1144
  }
1064
1145
  }
1065
1146
  function stripAnsi(str) {
1066
- return str.replace(/\u001b\[[0-9;]*m/g, "").replace(/[✅🟢🟡]/g, "XX");
1147
+ return str.replace(/\u001b\[\d+(?:;\d+)*m/g, "").replace(/[✅🟢🟡—]/g, "XX");
1067
1148
  }
1068
1149
 
1069
- // src/commands/context.ts
1070
- import { existsSync as existsSync17, writeFileSync as writeFileSync5, readdirSync as readdirSync11 } from "fs";
1150
+ // src/commands/sync.ts
1151
+ import { existsSync as existsSync18, writeFileSync as writeFileSync5 } from "fs";
1071
1152
  import { join as join17 } from "path";
1072
1153
 
1073
1154
  // src/core/context.ts
1074
- import { readFileSync as readFileSync9, existsSync as existsSync16 } from "fs";
1155
+ import { readFileSync as readFileSync9, existsSync as existsSync17 } from "fs";
1075
1156
  import { join as join16 } from "path";
1076
1157
  function extractSection(body, heading) {
1077
1158
  const regex = new RegExp(`^##\\s+${heading}[\\s\\S]*?$`, "im");
@@ -1151,7 +1232,7 @@ function classifyGitChanges(gitChanges, taskFiles) {
1151
1232
  function generateContext(changePath, changeName, options = {}) {
1152
1233
  const read = (name) => {
1153
1234
  const p = join16(changePath, name);
1154
- return existsSync16(p) ? readFileSync9(p, "utf-8") : null;
1235
+ return existsSync17(p) ? readFileSync9(p, "utf-8") : null;
1155
1236
  };
1156
1237
  const proposal = read("proposal.md");
1157
1238
  const spec = read("spec.md");
@@ -1220,7 +1301,7 @@ function generateContext(changePath, changeName, options = {}) {
1220
1301
  }
1221
1302
  lines.push("");
1222
1303
  }
1223
- if (options.gitDiff !== false) {
1304
+ if (options.gitDiff === true) {
1224
1305
  const gitChanges = getDiffFiles(options.baseBranch);
1225
1306
  if (gitChanges.length > 0) {
1226
1307
  const classified = classifyGitChanges(gitChanges, files);
@@ -1235,95 +1316,51 @@ function generateContext(changePath, changeName, options = {}) {
1235
1316
  return lines.join("\n");
1236
1317
  }
1237
1318
 
1238
- // src/commands/context.ts
1239
- async function contextCommand(name) {
1240
- const cwd = process.cwd();
1241
- const config = loadConfig(cwd);
1242
- const changesDir = join17(cwd, config.specDir, "changes");
1243
- if (!existsSync17(changesDir)) {
1244
- log.warn(`${symbol.warn} no changes directory found`);
1245
- return;
1246
- }
1247
- const names = [];
1248
- if (name) {
1249
- names.push(name);
1250
- } else {
1251
- const entries = readdirSync11(changesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name !== config.archive.dir);
1252
- names.push(...entries.map((e) => e.name));
1253
- }
1254
- if (names.length === 0) {
1255
- log.warn(`${symbol.warn} no changes found`);
1256
- return;
1257
- }
1258
- for (const n of names) {
1259
- const changePath = join17(changesDir, n);
1260
- if (!existsSync17(changePath)) {
1261
- log.warn(`${symbol.warn} "${n}" not found`);
1262
- continue;
1263
- }
1264
- const content = generateContext(changePath, n);
1265
- const destPath = join17(changePath, "context.md");
1266
- writeFileSync5(destPath, content, "utf-8");
1267
- log.success(`${symbol.ok} ${n}/context.md`);
1268
- }
1269
- }
1270
-
1271
1319
  // src/commands/sync.ts
1272
- import { existsSync as existsSync18, writeFileSync as writeFileSync6, readdirSync as readdirSync12 } from "fs";
1273
- import { join as join18 } from "path";
1274
1320
  async function syncCommand(name, opts) {
1275
1321
  const cwd = process.cwd();
1276
1322
  const config = loadConfig(cwd);
1277
- const changesDir = join18(cwd, config.specDir, "changes");
1278
- if (!existsSync18(changesDir)) {
1279
- log.warn(`${symbol.warn} no changes directory found`);
1280
- return;
1281
- }
1282
- const names = [];
1283
- if (name) {
1284
- names.push(name);
1285
- } else {
1286
- const entries = readdirSync12(changesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name !== config.archive.dir);
1287
- names.push(...entries.map((e) => e.name));
1288
- }
1323
+ const changesDir = join17(cwd, config.specDir, "changes");
1324
+ const names = resolveChangeNames(changesDir, name, config.archive.dir);
1289
1325
  if (names.length === 0) {
1290
- log.warn(`${symbol.warn} no changes found`);
1326
+ log.warn(`${symbol.warn} ${t("no changes found", "\u672A\u627E\u5230\u53D8\u66F4")}`);
1291
1327
  return;
1292
1328
  }
1329
+ const useGit = opts.git !== false;
1293
1330
  for (const n of names) {
1294
- const changePath = join18(changesDir, n);
1331
+ const changePath = join17(changesDir, n);
1295
1332
  if (!existsSync18(changePath)) {
1296
- log.warn(`${symbol.warn} "${n}" not found`);
1333
+ log.warn(`${symbol.warn} "${n}" ${t("not found", "\u672A\u627E\u5230")}`);
1297
1334
  continue;
1298
1335
  }
1299
1336
  const content = generateContext(changePath, n, {
1300
- gitDiff: true,
1337
+ gitDiff: useGit,
1301
1338
  baseBranch: opts.base
1302
1339
  });
1303
- const destPath = join18(changePath, "context.md");
1304
- writeFileSync6(destPath, content, "utf-8");
1305
- log.success(`${symbol.ok} synced ${n}/context.md`);
1340
+ const destPath = join17(changePath, "context.md");
1341
+ writeFileSync5(destPath, content, "utf-8");
1342
+ log.success(`${symbol.ok} ${t("synced", "\u5DF2\u540C\u6B65")} ${n}/context.md`);
1306
1343
  }
1307
1344
  }
1308
1345
 
1309
1346
  // src/cli/index.ts
1310
1347
  var require2 = createRequire(import.meta.url);
1311
1348
  var pkg = require2("../../package.json");
1312
- var isZh = loadConfig().lang === "zh";
1313
- var t = (en, zh) => isZh ? zh : en;
1349
+ setLang(loadConfig(process.cwd(), true).lang);
1314
1350
  program.name("superspec").description(t("Spec-driven development for AI coding assistants", "AI \u7F16\u7801\u52A9\u624B\u7684\u89C4\u683C\u9A71\u52A8\u5F00\u53D1\u5DE5\u5177")).version(pkg.version);
1315
1351
  program.command("init").description(t("Initialize SuperSpec in current project", "\u521D\u59CB\u5316 SuperSpec \u5230\u5F53\u524D\u9879\u76EE")).option("--ai <agent>", t("AI assistant type: claude, cursor, qwen, opencode, codex, codebuddy, qoder", "AI \u52A9\u624B\u7C7B\u578B: claude, cursor, qwen, opencode, codex, codebuddy, qoder"), "cursor").option("--lang <lang>", t("Template language: zh, en", "\u6A21\u677F\u8BED\u8A00: zh, en"), "en").option("--force", t("Force overwrite existing config", "\u5F3A\u5236\u8986\u76D6\u5DF2\u6709\u914D\u7F6E")).option("--no-git", t("Skip git initialization", "\u8DF3\u8FC7 git \u521D\u59CB\u5316")).action(initCommand);
1316
- program.command("create <name>").description(t("Create change and generate proposal (-b boost mode)", "\u521B\u5EFA\u53D8\u66F4\u5E76\u751F\u6210 proposal\uFF08-b \u589E\u5F3A\u6A21\u5F0F\uFF09")).option("-b, --boost", t("Boost mode, also generate spec + checklist", "\u589E\u5F3A\u6A21\u5F0F\uFF0C\u989D\u5916\u751F\u6210 spec + checklist")).option("-c, --creative", t("Creative mode, encourage new approaches", "\u521B\u9020\u6A21\u5F0F\uFF0C\u9F13\u52B1\u63A2\u7D22\u65B0\u65B9\u6848")).option("-d, --description <desc>", t("Change description for context", "\u53D8\u66F4\u63CF\u8FF0\uFF0C\u7528\u4E8E\u751F\u6210\u4E0A\u4E0B\u6587")).option("--no-branch", t("Skip git branch creation", "\u4E0D\u521B\u5EFA git \u5206\u652F")).option("--spec-dir <dir>", t("Custom spec folder name", "\u81EA\u5B9A\u4E49 spec \u6587\u4EF6\u5939\u540D\u79F0")).option("--branch-prefix <prefix>", t("Custom branch prefix", "\u81EA\u5B9A\u4E49\u5206\u652F\u524D\u7F00")).action(createCommand);
1352
+ program.command("create <feature>").description(t("Create change and generate proposal (-b boost mode)", "\u521B\u5EFA\u53D8\u66F4\u5E76\u751F\u6210 proposal\uFF08-b \u589E\u5F3A\u6A21\u5F0F\uFF09")).option("-b, --boost", t("Boost mode, also generate spec + checklist", "\u589E\u5F3A\u6A21\u5F0F\uFF0C\u989D\u5916\u751F\u6210 spec + checklist")).option("-c, --creative", t("Creative mode, encourage new approaches", "\u521B\u9020\u6A21\u5F0F\uFF0C\u9F13\u52B1\u63A2\u7D22\u65B0\u65B9\u6848")).option("-d, --description <desc>", t("Change description for context", "\u53D8\u66F4\u63CF\u8FF0\uFF0C\u7528\u4E8E\u751F\u6210\u4E0A\u4E0B\u6587")).option("--spec-dir <dir>", t("Custom spec folder name", "\u81EA\u5B9A\u4E49 spec \u6587\u4EF6\u5939\u540D\u79F0")).option("--no-branch", t("Skip git branch creation", "\u4E0D\u521B\u5EFA git \u5206\u652F")).option("--intent-type <type>", t("Intent type: feature, hotfix, bugfix, refactor, chore", "\u610F\u56FE\u7C7B\u578B\uFF1Afeature, hotfix, bugfix, refactor, chore")).option("--branch-prefix <prefix>", t("Custom branch prefix", "\u81EA\u5B9A\u4E49\u5206\u652F\u524D\u7F00")).option("--branch-template <template>", t("Branch name template: {prefix}{date}-{feature}-{user}", "\u5206\u652F\u540D\u79F0\u6A21\u677F\uFF1A{prefix}-{date}-{feature}-{user}")).option("--change-name-template <template>", t("Folder name template: {date}-{feature}-{user}", "\u6587\u4EF6\u5939\u540D\u79F0\u6A21\u677F\uFF1A{date}-{feature}-{user}")).option("--user <user>", t("Developer identifier (e.g. jay)", "\u5F00\u53D1\u8005\u6807\u8BC6\uFF08\u5982 jay\uFF09")).option("--lang <lang>", t("SDD document language: zh, en", "SDD \u6587\u6863\u8BED\u8A00: zh, en")).action(createCommand);
1317
1353
  program.command("archive [name]").description(t("Archive completed changes", "\u5F52\u6863\u5DF2\u5B8C\u6210\u7684\u53D8\u66F4")).option("--all", t("Archive all completed changes", "\u5F52\u6863\u6240\u6709\u5DF2\u5B8C\u6210\u7684\u53D8\u66F4")).action(archiveCommand);
1318
1354
  program.command("update").description(t("Refresh agent instructions and templates", "\u5237\u65B0 agent \u6307\u4EE4\u548C\u6A21\u677F")).action(updateCommand);
1319
1355
  program.command("lint [name]").description(t("Check artifact line limits", "\u68C0\u67E5 artifact \u884C\u6570\u662F\u5426\u8D85\u9650")).action(lintCommand);
1320
1356
  program.command("validate [name]").description(t("Cross-validate artifact consistency", "\u4EA4\u53C9\u9A8C\u8BC1 artifact \u4E00\u81F4\u6027")).option("--check-deps", t("Also check dependency consistency", "\u540C\u65F6\u68C0\u67E5\u4F9D\u8D56\u4E00\u81F4\u6027")).action(validateCommand);
1321
- program.command("search <query>").description(t("Search change contents", "\u641C\u7D22\u53D8\u66F4\u5185\u5BB9")).option("--archived", t("Include archived changes", "\u5305\u542B\u5DF2\u5F52\u6863\u7684\u53D8\u66F4")).option("--artifact <type>", t("Filter by artifact type (proposal/spec/tasks/clarify/checklist)", "\u6309 artifact \u7C7B\u578B\u8FC7\u6EE4 (proposal/spec/tasks/clarify/checklist)")).action(searchCommand);
1322
- program.command("link <name>").description(t("Add spec dependency", "\u6DFB\u52A0 spec \u4F9D\u8D56")).requiredOption("--depends-on <other>", t("Dependency spec name", "\u4F9D\u8D56\u7684 spec \u540D\u79F0")).action(linkCommand);
1323
- program.command("unlink <name>").description(t("Remove spec dependency", "\u79FB\u9664 spec \u4F9D\u8D56")).requiredOption("--depends-on <other>", t("Dependency to remove", "\u8981\u79FB\u9664\u7684\u4F9D\u8D56\u540D\u79F0")).action(unlinkCommand);
1324
- program.command("deps [name]").description(t("View dependency graph", "\u67E5\u770B\u4F9D\u8D56\u5173\u7CFB")).action(depsCommand);
1357
+ program.command("search <query>").description(t("Search change contents", "\u641C\u7D22\u53D8\u66F4\u5185\u5BB9")).option("--archived", t("Include archived changes", "\u5305\u542B\u5DF2\u5F52\u6863\u7684\u53D8\u66F4")).option("--artifact <type>", t("Filter by artifact type (proposal/spec/tasks/clarify/checklist)", "\u6309 artifact \u7C7B\u578B\u8FC7\u6EE4 (proposal/spec/tasks/clarify/checklist)")).option("--limit <n>", t("Max results to show (default: 50)", "\u6700\u5927\u663E\u793A\u7ED3\u679C\u6570\uFF08\u9ED8\u8BA4 50\uFF09")).option("-E, --regex", t("Use regex pattern matching", "\u4F7F\u7528\u6B63\u5219\u8868\u8FBE\u5F0F\u5339\u914D")).action(searchCommand);
1358
+ var deps = program.command("deps").description(t("Manage spec dependencies", "\u7BA1\u7406 spec \u4F9D\u8D56"));
1359
+ deps.command("list [name]").description(t("View dependency graph", "\u67E5\u770B\u4F9D\u8D56\u5173\u7CFB")).action(depsListCommand);
1360
+ deps.command("add <name>").description(t("Add spec dependency", "\u6DFB\u52A0 spec \u4F9D\u8D56")).requiredOption("--on <other>", t("Dependency spec name", "\u4F9D\u8D56\u7684 spec \u540D\u79F0")).action(depsAddCommand);
1361
+ deps.command("remove <name>").description(t("Remove spec dependency", "\u79FB\u9664 spec \u4F9D\u8D56")).requiredOption("--on <other>", t("Dependency to remove", "\u8981\u79FB\u9664\u7684\u4F9D\u8D56\u540D\u79F0")).action(depsRemoveCommand);
1325
1362
  program.command("status").description(t("View all change statuses", "\u67E5\u770B\u6240\u6709\u53D8\u66F4\u72B6\u6001")).action(statusCommand);
1326
- program.command("context [name]").description(t("Generate/refresh context.md summary (for vibe coding)", "\u751F\u6210/\u5237\u65B0 context.md \u4E0A\u4E0B\u6587\u6458\u8981\uFF08\u7528\u4E8E vibe coding\uFF09")).action(contextCommand);
1327
- program.command("sync [name]").description(t("Sync git changes to context.md (collect facts, no auto-check tasks)", "\u540C\u6B65 git \u53D8\u66F4\u5230 context.md\uFF08\u6536\u96C6\u4E8B\u5B9E\uFF0C\u4E0D\u81EA\u52A8\u52FE\u9009 task\uFF09")).option("--base <branch>", t("Base branch (default: main/master)", "\u57FA\u51C6\u5206\u652F\uFF08\u9ED8\u8BA4 main/master\uFF09")).action(syncCommand);
1363
+ program.command("list").description(t("List change names (for scripting)", "\u5217\u51FA\u53D8\u66F4\u540D\u79F0\uFF08\u7528\u4E8E\u811A\u672C\uFF09")).option("--archived", t("Include archived changes", "\u5305\u542B\u5DF2\u5F52\u6863\u7684\u53D8\u66F4")).action(listCommand);
1364
+ program.command("sync [name]").description(t("Sync git changes to context.md", "\u540C\u6B65 git \u53D8\u66F4\u5230 context.md")).option("--base <branch>", t("Base branch (default: main/master)", "\u57FA\u51C6\u5206\u652F\uFF08\u9ED8\u8BA4 main/master\uFF09")).option("--no-git", t("Skip git diff collection", "\u4E0D\u6536\u96C6 git diff \u4FE1\u606F")).action(syncCommand);
1328
1365
  program.parse();
1329
1366
  //# sourceMappingURL=index.js.map