@superspec/cli 1.1.1 → 1.2.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
@@ -4,13 +4,12 @@
4
4
  import { createRequire } from "module";
5
5
  import { program } from "commander";
6
6
 
7
- // src/commands/init.ts
8
- import { existsSync as existsSync6, writeFileSync as writeFileSync3, readdirSync as readdirSync3 } from "fs";
9
- import { join as join5 } from "path";
10
- import { execSync as execSync2 } from "child_process";
7
+ // src/commands/archive.ts
8
+ import { existsSync as existsSync3, readdirSync as readdirSync2, renameSync } from "fs";
9
+ import { join as join2 } from "path";
11
10
 
12
11
  // src/core/config.ts
13
- import { readFileSync, existsSync } from "fs";
12
+ import { existsSync, readFileSync } from "fs";
14
13
  import { join } from "path";
15
14
  var DEFAULT_CONFIG = {
16
15
  lang: "en",
@@ -72,129 +71,6 @@ function deepMerge(target, source) {
72
71
  return result;
73
72
  }
74
73
 
75
- // src/core/template.ts
76
- import { existsSync as existsSync4, copyFileSync, readFileSync as readFileSync2, writeFileSync } from "fs";
77
- import { join as join3, dirname as dirname2 } from "path";
78
-
79
- // src/utils/paths.ts
80
- import { dirname, join as join2 } from "path";
81
- import { existsSync as existsSync2 } from "fs";
82
- import { fileURLToPath } from "url";
83
- function getPackageRoot() {
84
- const __filename2 = fileURLToPath(import.meta.url);
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), "..", "..");
93
- }
94
-
95
- // src/utils/fs.ts
96
- import { mkdirSync, existsSync as existsSync3, readdirSync } from "fs";
97
- function ensureDir(dir) {
98
- if (!existsSync3(dir)) {
99
- mkdirSync(dir, { recursive: true });
100
- }
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
- }
107
-
108
- // src/core/template.ts
109
- function resolveTemplatePath(templateName, lang = "zh") {
110
- const root = getPackageRoot();
111
- const langPath = join3(root, "templates", lang, templateName);
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;
116
- throw new Error(`Template not found: ${templateName} (lang: ${lang})`);
117
- }
118
- function copyTemplate(templateName, destPath, lang = "zh") {
119
- const srcPath = resolveTemplatePath(templateName, lang);
120
- ensureDir(dirname2(destPath));
121
- copyFileSync(srcPath, destPath);
122
- }
123
- function renderTemplate(templateName, vars = {}, lang = "zh") {
124
- const srcPath = resolveTemplatePath(templateName, lang);
125
- let content = readFileSync2(srcPath, "utf-8");
126
- content = processConditionals(content, vars);
127
- for (const [key, value] of Object.entries(vars)) {
128
- content = content.replaceAll(`{{${key}}}`, value);
129
- }
130
- return content;
131
- }
132
- function processConditionals(content, vars) {
133
- const ifRegex = /\{\{#if\s+(\w+)\}\}([\s\S]*?)\{\{\/if\}\}/g;
134
- return content.replace(ifRegex, (match, varName, innerContent) => {
135
- const value = vars[varName];
136
- if (value && value.trim() !== "") {
137
- return innerContent;
138
- }
139
- return "";
140
- });
141
- }
142
- function writeRenderedTemplate(templateName, destPath, vars = {}, lang = "zh") {
143
- const content = renderTemplate(templateName, vars, lang);
144
- ensureDir(dirname2(destPath));
145
- writeFileSync(destPath, content, "utf-8");
146
- }
147
-
148
- // src/utils/git.ts
149
- import { execSync } from "child_process";
150
- var GIT_TIMEOUT = 1e4;
151
- function isGitRepo() {
152
- try {
153
- execSync("git rev-parse --is-inside-work-tree", { stdio: "ignore", timeout: GIT_TIMEOUT });
154
- return true;
155
- } catch {
156
- return false;
157
- }
158
- }
159
- var SAFE_BRANCH_RE = /^[a-zA-Z0-9._\-/]+$/;
160
- function createBranch(branchName) {
161
- if (!SAFE_BRANCH_RE.test(branchName)) {
162
- throw new Error(`invalid branch name: ${branchName}`);
163
- }
164
- execSync(`git checkout -b ${branchName}`, { stdio: "inherit", timeout: GIT_TIMEOUT });
165
- }
166
- function getDefaultBranch() {
167
- try {
168
- const ref = execSync("git symbolic-ref refs/remotes/origin/HEAD", { encoding: "utf-8", timeout: GIT_TIMEOUT }).trim();
169
- return ref.replace("refs/remotes/origin/", "");
170
- } catch {
171
- try {
172
- execSync("git rev-parse --verify main", { stdio: "ignore", timeout: GIT_TIMEOUT });
173
- return "main";
174
- } catch {
175
- return "master";
176
- }
177
- }
178
- }
179
- function getDiffFiles(base) {
180
- const baseBranch = base || getDefaultBranch();
181
- try {
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();
184
- if (!output) return [];
185
- return output.split("\n").map((line) => {
186
- const [status, ...parts] = line.split(" ");
187
- return { status: status.charAt(0), file: parts.join(" ") };
188
- });
189
- } catch {
190
- return [];
191
- }
192
- }
193
-
194
- // src/prompts/index.ts
195
- import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync2, readdirSync as readdirSync2 } from "fs";
196
- import { join as join4 } from "path";
197
-
198
74
  // src/ui/index.ts
199
75
  import chalk from "chalk";
200
76
  var theme = {
@@ -243,7 +119,9 @@ function boxText(text, width = 50) {
243
119
  }
244
120
  function createBox(lines, width = 52) {
245
121
  const top = theme.border(`${box.topLeft}${box.horizontal.repeat(width - 2)}${box.topRight}`);
246
- const bottom = theme.border(`${box.bottomLeft}${box.horizontal.repeat(width - 2)}${box.bottomRight}`);
122
+ const bottom = theme.border(
123
+ `${box.bottomLeft}${box.horizontal.repeat(width - 2)}${box.bottomRight}`
124
+ );
247
125
  const middle = lines.map((line) => boxText(line, width));
248
126
  return [top, ...middle, bottom].join("\n");
249
127
  }
@@ -299,7 +177,7 @@ function t(en, zh) {
299
177
  function printSummary(items) {
300
178
  const maxLabel = Math.max(...items.map((i) => i.label.length));
301
179
  const width = 50;
302
- console.log(theme.border("\u256D" + "\u2500".repeat(width - 2) + "\u256E"));
180
+ console.log(theme.border(`\u256D${"\u2500".repeat(width - 2)}\u256E`));
303
181
  for (const { label, value } of items) {
304
182
  const padding = " ".repeat(maxLabel - label.length);
305
183
  const line = `${theme.dim(label)}${padding} ${symbol.arrow} ${theme.highlight(value)}`;
@@ -307,181 +185,150 @@ function printSummary(items) {
307
185
  const rightPad = " ".repeat(Math.max(0, width - plainLine.length - 4));
308
186
  console.log(theme.border("\u2502 ") + line + rightPad + theme.border(" \u2502"));
309
187
  }
310
- console.log(theme.border("\u2570" + "\u2500".repeat(width - 2) + "\u256F"));
188
+ console.log(theme.border(`\u2570${"\u2500".repeat(width - 2)}\u256F`));
311
189
  }
312
190
 
313
- // src/prompts/index.ts
314
- var AI_EDITORS = {
315
- claude: {
316
- commands: ".claude/commands",
317
- rules: null
318
- // Claude doesn't use rules files
319
- },
320
- cursor: {
321
- commands: ".cursor/commands",
322
- rules: ".cursor/rules",
323
- rulesFile: "superspec.mdc"
324
- },
325
- qwen: {
326
- commands: ".qwen/commands",
327
- rules: ".qwen/rules",
328
- rulesFile: "superspec.md"
329
- },
330
- opencode: {
331
- commands: ".opencode/commands",
332
- rules: null
333
- },
334
- codex: {
335
- commands: ".codex/commands",
336
- rules: null
337
- },
338
- codebuddy: {
339
- commands: ".codebuddy/commands",
340
- rules: ".codebuddy/rules",
341
- rulesFile: "superspec.md"
342
- },
343
- qoder: {
344
- commands: ".qoder/commands",
345
- rules: ".qoder/rules",
346
- rulesFile: "superspec.md"
191
+ // src/utils/date.ts
192
+ function getDateString() {
193
+ const d = /* @__PURE__ */ new Date();
194
+ return `${d.getFullYear()}${String(d.getMonth() + 1).padStart(2, "0")}${String(d.getDate()).padStart(2, "0")}`;
195
+ }
196
+
197
+ // src/utils/fs.ts
198
+ import { existsSync as existsSync2, mkdirSync, readdirSync } from "fs";
199
+ function ensureDir(dir) {
200
+ if (!existsSync2(dir)) {
201
+ mkdirSync(dir, { recursive: true });
347
202
  }
348
- };
349
- function installRules(cwd, editor) {
350
- const config = AI_EDITORS[editor];
351
- if (!config.rules) {
203
+ }
204
+ function resolveChangeNames(changesDir, name, archiveDirName) {
205
+ if (!existsSync2(changesDir)) return [];
206
+ if (name) return [name];
207
+ return readdirSync(changesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name !== archiveDirName).map((e) => e.name);
208
+ }
209
+
210
+ // src/commands/archive.ts
211
+ async function archiveCommand(name, options) {
212
+ const cwd = process.cwd();
213
+ const config = loadConfig(cwd);
214
+ const changesDir = join2(cwd, config.specDir, "changes");
215
+ const archiveDir = join2(cwd, config.specDir, "changes", config.archive.dir);
216
+ if (!existsSync3(changesDir)) {
217
+ log.warn(`${symbol.warn} ${t("no changes directory found", "\u672A\u627E\u5230 changes \u76EE\u5F55")}`);
352
218
  return;
353
219
  }
354
- const rulesDir = join4(cwd, config.rules);
355
- ensureDir(rulesDir);
356
- const promptSrc = join4(getPackageRoot(), "prompts", "rules.md");
357
- if (existsSync5(promptSrc)) {
358
- const content = readFileSync3(promptSrc, "utf-8");
359
- const rulesFile = "rulesFile" in config ? config.rulesFile : "superspec.md";
360
- const destPath = join4(rulesDir, rulesFile);
361
- writeFileSync2(destPath, content, "utf-8");
362
- log.success(`${symbol.ok} ${config.rules}/${rulesFile}`);
363
- }
364
- }
365
- var SS_START = "<!-- superspec:start -->";
366
- var SS_END = "<!-- superspec:end -->";
367
- function installAgentsMd(cwd) {
368
- const agentsMdPath = join4(cwd, "AGENTS.md");
369
- const agentPromptSrc = join4(getPackageRoot(), "prompts", "agents.md");
370
- if (!existsSync5(agentPromptSrc)) return;
371
- const newContent = readFileSync3(agentPromptSrc, "utf-8");
372
- const wrapped = `${SS_START}
373
- ${newContent}
374
- ${SS_END}`;
375
- if (existsSync5(agentsMdPath)) {
376
- const existing = readFileSync3(agentsMdPath, "utf-8");
377
- const startIdx = existing.indexOf(SS_START);
378
- const endIdx = existing.indexOf(SS_END);
379
- if (startIdx !== -1 && endIdx !== -1) {
380
- const before = existing.slice(0, startIdx);
381
- const after = existing.slice(endIdx + SS_END.length);
382
- writeFileSync2(agentsMdPath, before + wrapped + after, "utf-8");
383
- } else if (existing.includes("SuperSpec")) {
384
- writeFileSync2(agentsMdPath, existing, "utf-8");
385
- } else {
386
- writeFileSync2(agentsMdPath, existing + "\n\n" + wrapped, "utf-8");
220
+ if (options.all) {
221
+ const entries = readdirSync2(changesDir, { withFileTypes: true }).filter(
222
+ (e) => e.isDirectory() && e.name !== config.archive.dir
223
+ );
224
+ if (entries.length === 0) {
225
+ log.warn(`${symbol.warn} ${t("no changes to archive", "\u6CA1\u6709\u53EF\u5F52\u6863\u7684\u53D8\u66F4")}`);
226
+ return;
227
+ }
228
+ log.info(`${symbol.start} ${t("archiving all changes...", "\u5F52\u6863\u6240\u6709\u53D8\u66F4...")}`);
229
+ for (const entry of entries) {
230
+ archiveOne(entry.name, changesDir, archiveDir, config);
231
+ }
232
+ } else if (name) {
233
+ const changePath = join2(changesDir, name);
234
+ if (!existsSync3(changePath)) {
235
+ log.warn(`${symbol.warn} ${t(`change "${name}" not found`, `\u53D8\u66F4 "${name}" \u4E0D\u5B58\u5728`)}`);
236
+ return;
387
237
  }
238
+ log.info(`${symbol.start} ${t(`archiving: ${name}`, `\u5F52\u6863\u53D8\u66F4: ${name}`)}`);
239
+ archiveOne(name, changesDir, archiveDir, config);
388
240
  } else {
389
- writeFileSync2(agentsMdPath, wrapped, "utf-8");
241
+ log.warn(`${symbol.warn} ${t("specify a name or use --all", "\u8BF7\u6307\u5B9A\u53D8\u66F4\u540D\u79F0\u6216\u4F7F\u7528 --all")}`);
242
+ return;
390
243
  }
391
- log.success(`${symbol.ok} AGENTS.md`);
244
+ log.info(`${symbol.start} ${t("archive done!", "\u5F52\u6863\u5B8C\u6210\uFF01")}`);
392
245
  }
393
- function installCommands(cwd, editor, lang = "zh") {
394
- const config = AI_EDITORS[editor];
395
- const commandsDir = join4(cwd, config.commands);
396
- ensureDir(commandsDir);
397
- const templatesDir = join4(getPackageRoot(), "templates", lang, "commands");
398
- const fallbackDir = join4(getPackageRoot(), "templates", "zh", "commands");
399
- const sourceDir = existsSync5(templatesDir) ? templatesDir : fallbackDir;
400
- if (!existsSync5(sourceDir)) {
401
- log.warn(`${symbol.warn} Commands templates not found: ${sourceDir}`);
246
+ function archiveOne(name, changesDir, archiveDir, config) {
247
+ ensureDir(archiveDir);
248
+ const src = join2(changesDir, name);
249
+ const dateStr = config.archive.datePrefix ? `${getDateString()}-` : "";
250
+ const dest = join2(archiveDir, `${dateStr}${name}`);
251
+ if (existsSync3(dest)) {
252
+ log.warn(` ${symbol.warn} ${t("archive target exists", "\u5F52\u6863\u76EE\u6807\u5DF2\u5B58\u5728")}: ${dest}`);
402
253
  return;
403
254
  }
404
- const commandFiles = readdirSync2(sourceDir).filter((f) => f.endsWith(".md"));
405
- for (const file of commandFiles) {
406
- const srcPath = join4(sourceDir, file);
407
- const destPath = join4(commandsDir, file);
408
- const content = readFileSync3(srcPath, "utf-8");
409
- writeFileSync2(destPath, content, "utf-8");
410
- }
411
- log.success(`${symbol.ok} ${config.commands}/ (${commandFiles.length} commands)`);
255
+ renameSync(src, dest);
256
+ log.success(` ${symbol.ok} ${name} \u2192 archive/${dateStr}${name}`);
412
257
  }
413
258
 
414
- // src/commands/init.ts
415
- async function initCommand(options) {
416
- const cwd = process.cwd();
417
- const configPath = join5(cwd, "superspec.config.json");
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")}`);
420
- return;
421
- }
422
- const lang = options.lang || "zh";
423
- setLang(lang);
424
- printLogo("small");
425
- console.log(theme.dim(" Spec-Driven Development Toolkit\n"));
426
- const config = getDefaultConfig();
427
- config.lang = lang;
428
- const aiEditor = options.ai;
429
- if (aiEditor && AI_EDITORS[aiEditor]) {
430
- config.aiEditor = aiEditor;
259
+ // src/commands/create.ts
260
+ import { existsSync as existsSync5 } from "fs";
261
+ import { join as join4 } from "path";
262
+
263
+ // src/utils/git.ts
264
+ import { execSync } from "child_process";
265
+ var GIT_TIMEOUT = 1e4;
266
+ function isGitRepo() {
267
+ try {
268
+ execSync("git rev-parse --is-inside-work-tree", { stdio: "ignore", timeout: GIT_TIMEOUT });
269
+ return true;
270
+ } catch {
271
+ return false;
431
272
  }
432
- const specDir = join5(cwd, config.specDir);
433
- const existingFiles = readdirSync3(cwd).filter((f) => !f.startsWith(".") && f !== "node_modules");
434
- if (existingFiles.length > 0 && !options.force) {
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")}`);
437
- console.log();
273
+ }
274
+ var SAFE_BRANCH_RE = /^[a-zA-Z0-9._\-/]+$/;
275
+ function createBranch(branchName) {
276
+ if (!SAFE_BRANCH_RE.test(branchName)) {
277
+ throw new Error(`invalid branch name: ${branchName}`);
438
278
  }
439
- log.section(t("Creating Configuration", "\u521B\u5EFA\u914D\u7F6E"));
440
- writeFileSync3(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
441
- log.success(`${symbol.file} superspec.config.json`);
442
- log.section(t("Creating Directory Structure", "\u521B\u5EFA\u76EE\u5F55\u7ED3\u6784"));
443
- ensureDir(join5(specDir, "changes"));
444
- ensureDir(join5(specDir, "templates"));
445
- log.success(`${symbol.folder} ${config.specDir}/changes/`);
446
- log.success(`${symbol.folder} ${config.specDir}/templates/`);
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) {
279
+ execSync(`git checkout -b ${branchName}`, { stdio: "inherit", timeout: GIT_TIMEOUT });
280
+ }
281
+ function getDefaultBranch() {
282
+ try {
283
+ const ref = execSync("git symbolic-ref refs/remotes/origin/HEAD", {
284
+ encoding: "utf-8",
285
+ timeout: GIT_TIMEOUT
286
+ }).trim();
287
+ return ref.replace("refs/remotes/origin/", "");
288
+ } catch {
450
289
  try {
451
- copyTemplate(tpl, join5(specDir, "templates", tpl), lang);
290
+ execSync("git rev-parse --verify main", { stdio: "ignore", timeout: GIT_TIMEOUT });
291
+ return "main";
452
292
  } catch {
293
+ return "master";
453
294
  }
454
295
  }
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"));
457
- installAgentsMd(cwd);
458
- if (aiEditor && AI_EDITORS[aiEditor]) {
459
- installRules(cwd, aiEditor);
460
- installCommands(cwd, aiEditor, lang);
461
- }
462
- if (options.git !== false && !isGitRepo()) {
463
- execSync2("git init", { cwd, stdio: "inherit" });
464
- log.success(`${symbol.git} git init`);
296
+ }
297
+ function getDiffFiles(base) {
298
+ const baseBranch = base || getDefaultBranch();
299
+ try {
300
+ const mergeBase = execSync(`git merge-base ${baseBranch} HEAD`, {
301
+ encoding: "utf-8",
302
+ timeout: GIT_TIMEOUT
303
+ }).trim();
304
+ const output = execSync(`git diff --name-status ${mergeBase}`, {
305
+ encoding: "utf-8",
306
+ timeout: 3e4
307
+ }).trim();
308
+ if (!output) return [];
309
+ return output.split("\n").map((line) => {
310
+ const [status, ...parts] = line.split(" ");
311
+ return { status: status.charAt(0), file: parts.join(" ") };
312
+ });
313
+ } catch {
314
+ return [];
465
315
  }
466
- console.log();
467
- printSummary([
468
- { label: "Config", value: "superspec.config.json" },
469
- { label: "Spec dir", value: `${config.specDir}/` },
470
- { label: "AI agent", value: options.ai },
471
- { label: "Language", value: lang }
472
- ]);
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>`);
475
316
  }
476
317
 
477
- // src/commands/create.ts
478
- import { existsSync as existsSync7 } from "fs";
479
- import { join as join6 } from "path";
480
-
481
- // src/utils/date.ts
482
- function getDateString() {
483
- const d = /* @__PURE__ */ new Date();
484
- return `${d.getFullYear()}${String(d.getMonth() + 1).padStart(2, "0")}${String(d.getDate()).padStart(2, "0")}`;
318
+ // src/utils/paths.ts
319
+ import { existsSync as existsSync4 } from "fs";
320
+ import { dirname, join as join3 } from "path";
321
+ import { fileURLToPath } from "url";
322
+ function getPackageRoot() {
323
+ const __filename2 = fileURLToPath(import.meta.url);
324
+ let dir = dirname(__filename2);
325
+ while (dir !== dirname(dir)) {
326
+ if (existsSync4(join3(dir, "package.json")) && existsSync4(join3(dir, "templates"))) {
327
+ return dir;
328
+ }
329
+ dir = dirname(dir);
330
+ }
331
+ return join3(dirname(__filename2), "..", "..");
485
332
  }
486
333
 
487
334
  // src/utils/template.ts
@@ -507,8 +354,6 @@ async function createCommand(feature, options) {
507
354
  const branchPrefix = options.branchPrefix || config.branchPrefix;
508
355
  const boost = options.boost || config.boost;
509
356
  const strategy = options.creative ? "create" : config.strategy;
510
- const description = options.description || "";
511
- const lang = options.lang || config.lang || "en";
512
357
  const templateVars = {
513
358
  prefix: branchPrefix,
514
359
  intentType: options.intentType,
@@ -518,9 +363,11 @@ async function createCommand(feature, options) {
518
363
  };
519
364
  const changeNameTemplate = options.changeNameTemplate || config.changeNameTemplate || "{date}-{feature}";
520
365
  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}`);
366
+ const changePath = join4(cwd, specDir, "changes", changeFolderName);
367
+ if (existsSync5(changePath)) {
368
+ log.warn(
369
+ `${symbol.warn} ${t(`change "${changeFolderName}" already exists`, `\u53D8\u66F4 "${changeFolderName}" \u5DF2\u5B58\u5728`)}: ${changePath}`
370
+ );
524
371
  return;
525
372
  }
526
373
  log.title(`${t("Creating Change", "\u521B\u5EFA\u53D8\u66F4")}: ${changeFolderName}`);
@@ -531,28 +378,11 @@ async function createCommand(feature, options) {
531
378
  log.boost(`${symbol.bolt} ${t("Boost mode enabled", "\u589E\u5F3A\u6A21\u5F0F\u5DF2\u542F\u7528")}`);
532
379
  }
533
380
  if (strategy === "create") {
534
- log.boost(`${symbol.bolt} ${t("Creative mode: exploring new solutions", "\u521B\u9020\u6A21\u5F0F\uFF1A\u63A2\u7D22\u65B0\u65B9\u6848")}`);
381
+ log.boost(
382
+ `${symbol.bolt} ${t("Creative mode: exploring new solutions", "\u521B\u9020\u6A21\u5F0F\uFF1A\u63A2\u7D22\u65B0\u65B9\u6848")}`
383
+ );
535
384
  }
536
385
  ensureDir(changePath);
537
- const vars = {
538
- name: changeFolderName,
539
- date: templateVars.date,
540
- boost: boost ? "true" : "false",
541
- strategy,
542
- description
543
- };
544
- const artifacts = boost ? config.boostArtifacts : config.artifacts;
545
- log.section(t("Generating Artifacts", "\u751F\u6210 Artifacts"));
546
- for (const artifact of artifacts) {
547
- const templateFile = config.templates[artifact] || `${artifact}.md`;
548
- const destPath = join6(changePath, `${artifact}.md`);
549
- try {
550
- writeRenderedTemplate(templateFile, destPath, vars, lang);
551
- log.success(`${symbol.ok} ${artifact}.md`);
552
- } catch (e) {
553
- log.error(`${symbol.fail} ${artifact}.md: ${e.message}`);
554
- }
555
- }
556
386
  if (options.branch !== false && isGitRepo()) {
557
387
  const branchTemplate = options.branchTemplate || config.branchTemplate || "{prefix}{date}-{feature}";
558
388
  const branchName = renderNameTemplate(branchTemplate, templateVars, true);
@@ -565,168 +395,17 @@ async function createCommand(feature, options) {
565
395
  }
566
396
  log.done(t("Change created successfully!", "\u53D8\u66F4\u521B\u5EFA\u6210\u529F\uFF01"));
567
397
  log.dim(`${t("Path", "\u8DEF\u5F84")}: ${specDir}/changes/${changeFolderName}/`);
568
- if (boost) {
569
- log.dim(`${t("Workflow", "\u5DE5\u4F5C\u6D41")}: /ss-create \u2192 /ss-tasks \u2192 /ss-apply (boost)`);
570
- } else {
571
- log.dim(`${t("Workflow", "\u5DE5\u4F5C\u6D41")}: /ss-tasks \u2192 /ss-apply`);
572
- }
573
- log.dim(`${t("Next", "\u4E0B\u4E00\u6B65")}: superspec lint ${changeFolderName}`);
574
- }
575
-
576
- // src/commands/archive.ts
577
- import { existsSync as existsSync8, readdirSync as readdirSync4, renameSync } from "fs";
578
- import { join as join7 } from "path";
579
- async function archiveCommand(name, options) {
580
- const cwd = process.cwd();
581
- const config = loadConfig(cwd);
582
- const changesDir = join7(cwd, config.specDir, "changes");
583
- const archiveDir = join7(cwd, config.specDir, "changes", config.archive.dir);
584
- if (!existsSync8(changesDir)) {
585
- log.warn(`${symbol.warn} ${t("no changes directory found", "\u672A\u627E\u5230 changes \u76EE\u5F55")}`);
586
- return;
587
- }
588
- if (options.all) {
589
- const entries = readdirSync4(changesDir, { withFileTypes: true }).filter(
590
- (e) => e.isDirectory() && e.name !== config.archive.dir
591
- );
592
- if (entries.length === 0) {
593
- log.warn(`${symbol.warn} ${t("no changes to archive", "\u6CA1\u6709\u53EF\u5F52\u6863\u7684\u53D8\u66F4")}`);
594
- return;
595
- }
596
- log.info(`${symbol.start} ${t("archiving all changes...", "\u5F52\u6863\u6240\u6709\u53D8\u66F4...")}`);
597
- for (const entry of entries) {
598
- archiveOne(entry.name, changesDir, archiveDir, config);
599
- }
600
- } else if (name) {
601
- const changePath = join7(changesDir, name);
602
- if (!existsSync8(changePath)) {
603
- log.warn(`${symbol.warn} ${t(`change "${name}" not found`, `\u53D8\u66F4 "${name}" \u4E0D\u5B58\u5728`)}`);
604
- return;
605
- }
606
- log.info(`${symbol.start} ${t(`archiving: ${name}`, `\u5F52\u6863\u53D8\u66F4: ${name}`)}`);
607
- archiveOne(name, changesDir, archiveDir, config);
608
- } else {
609
- log.warn(`${symbol.warn} ${t("specify a name or use --all", "\u8BF7\u6307\u5B9A\u53D8\u66F4\u540D\u79F0\u6216\u4F7F\u7528 --all")}`);
610
- return;
611
- }
612
- log.info(`${symbol.start} ${t("archive done!", "\u5F52\u6863\u5B8C\u6210\uFF01")}`);
613
- }
614
- function archiveOne(name, changesDir, archiveDir, config) {
615
- ensureDir(archiveDir);
616
- const src = join7(changesDir, name);
617
- const dateStr = config.archive.datePrefix ? `${getDateString()}-` : "";
618
- const dest = join7(archiveDir, `${dateStr}${name}`);
619
- if (existsSync8(dest)) {
620
- log.warn(` ${symbol.warn} ${t("archive target exists", "\u5F52\u6863\u76EE\u6807\u5DF2\u5B58\u5728")}: ${dest}`);
621
- return;
622
- }
623
- renameSync(src, dest);
624
- log.success(` ${symbol.ok} ${name} \u2192 archive/${dateStr}${name}`);
398
+ log.dim(`${t("Templates", "\u6A21\u677F\u53C2\u8003")}: ${specDir}/templates/`);
399
+ const expectedArtifacts = boost ? config.boostArtifacts : config.artifacts;
400
+ log.dim(`${t("Expected artifacts", "\u9884\u671F Artifacts")}: ${expectedArtifacts.join(", ")}`);
401
+ log.dim(
402
+ `${t("Next", "\u4E0B\u4E00\u6B65")}: ${t("AI generates artifacts on demand via /ss-create", "AI \u6309\u9700\u901A\u8FC7 /ss-create \u751F\u6210 artifacts")}`
403
+ );
625
404
  }
626
405
 
627
- // src/commands/update.ts
628
- import { existsSync as existsSync9 } from "fs";
629
- import { join as join8 } from "path";
630
- async function updateCommand() {
631
- const cwd = process.cwd();
632
- const config = loadConfig(cwd);
633
- const specDir = join8(cwd, config.specDir);
634
- const lang = config.lang || "zh";
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")}`);
637
- return;
638
- }
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`);
641
- ensureDir(join8(specDir, "templates"));
642
- for (const tpl of templateNames) {
643
- try {
644
- copyTemplate(tpl, join8(specDir, "templates", tpl), lang);
645
- } catch {
646
- }
647
- }
648
- log.success(` ${symbol.ok} ${t("templates updated", "\u6A21\u677F\u66F4\u65B0")} (${lang})`);
649
- installAgentsMd(cwd);
650
- const aiEditor = config.aiEditor;
651
- if (aiEditor && AI_EDITORS[aiEditor]) {
652
- installRules(cwd, aiEditor);
653
- installCommands(cwd, aiEditor, lang);
654
- }
655
- log.info(`${symbol.start} ${t("update done!", "\u66F4\u65B0\u5B8C\u6210\uFF01")}`);
656
- }
657
-
658
- // src/commands/lint.ts
659
- import { existsSync as existsSync11 } from "fs";
660
- import { join as join10 } from "path";
661
-
662
- // src/core/lint.ts
663
- import { readFileSync as readFileSync4, existsSync as existsSync10, readdirSync as readdirSync5 } from "fs";
664
- import { join as join9, basename } from "path";
665
- function lintArtifact(filePath, targetLines, hardLines) {
666
- const artifact = basename(filePath);
667
- if (!existsSync10(filePath)) {
668
- return { artifact, lines: 0, status: "ok", message: "not found" };
669
- }
670
- const content = readFileSync4(filePath, "utf-8");
671
- const lines = content.split("\n").length;
672
- if (lines > hardLines) {
673
- return { artifact, lines, status: "error", message: `${lines} lines exceeds hard limit (${hardLines}). Must split.` };
674
- }
675
- if (lines > targetLines) {
676
- return { artifact, lines, status: "warn", message: `${lines} lines exceeds target (${targetLines}). Consider splitting.` };
677
- }
678
- return { artifact, lines, status: "ok", message: `${lines} lines` };
679
- }
680
- function lintChange(changePath, targetLines, hardLines) {
681
- if (!existsSync10(changePath)) return [];
682
- const files = readdirSync5(changePath).filter((f) => f.endsWith(".md"));
683
- return files.map((f) => lintArtifact(join9(changePath, f), targetLines, hardLines));
684
- }
685
-
686
- // src/commands/lint.ts
687
- async function lintCommand(name) {
688
- const cwd = process.cwd();
689
- const config = loadConfig(cwd);
690
- const changesDir = join10(cwd, config.specDir, "changes");
691
- const { targetLines, hardLines } = config.limits;
692
- const names = resolveChangeNames(changesDir, name, config.archive.dir);
693
- if (names.length === 0) {
694
- log.warn(`${symbol.warn} ${t("no changes to lint", "\u6CA1\u6709\u53EF\u68C0\u67E5\u7684\u53D8\u66F4")}`);
695
- return;
696
- }
697
- let hasIssues = false;
698
- for (const n of names) {
699
- const changePath = join10(changesDir, n);
700
- if (!existsSync11(changePath)) {
701
- log.warn(`${symbol.warn} "${n}" ${t("not found", "\u672A\u627E\u5230")}`);
702
- continue;
703
- }
704
- const results = lintChange(changePath, targetLines, hardLines);
705
- log.info(`${symbol.start} ${n}`);
706
- for (const r of results) {
707
- if (r.status === "error") {
708
- log.error(` ${symbol.fail} ${r.artifact}: ${r.message}`);
709
- hasIssues = true;
710
- } else if (r.status === "warn") {
711
- log.warn(` ${symbol.warn} ${r.artifact}: ${r.message}`);
712
- hasIssues = true;
713
- } else {
714
- log.success(` ${symbol.ok} ${r.artifact}: ${r.message}`);
715
- }
716
- }
717
- }
718
- if (!hasIssues) {
719
- log.info(`${symbol.start} ${t("all artifacts within limits", "\u6240\u6709 artifact \u5747\u5728\u9650\u5236\u8303\u56F4\u5185")}`);
720
- }
721
- }
722
-
723
- // src/commands/validate.ts
724
- import { existsSync as existsSync13 } from "fs";
725
- import { join as join12 } from "path";
726
-
727
- // src/core/validate.ts
728
- import { readFileSync as readFileSync5, existsSync as existsSync12 } from "fs";
729
- import { join as join11 } from "path";
406
+ // src/commands/deps.ts
407
+ import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync2, writeFileSync } from "fs";
408
+ import { join as join5 } from "path";
730
409
 
731
410
  // src/core/frontmatter.ts
732
411
  var FM_REGEX = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/;
@@ -779,149 +458,391 @@ function addDependency(content, depName) {
779
458
  deps2.push(depName);
780
459
  }
781
460
  meta.depends_on = deps2;
782
- return serializeFrontmatter(meta) + "\n" + body;
461
+ return `${serializeFrontmatter(meta)}
462
+ ${body}`;
783
463
  }
784
464
  function removeDependency(content, depName) {
785
465
  const { meta, body } = parseFrontmatter(content);
786
466
  const deps2 = Array.isArray(meta.depends_on) ? meta.depends_on : [];
787
467
  meta.depends_on = deps2.filter((d) => d !== depName);
788
- return serializeFrontmatter(meta) + "\n" + body;
468
+ return `${serializeFrontmatter(meta)}
469
+ ${body}`;
789
470
  }
790
471
 
791
- // src/core/validate.ts
792
- function extractIds(content, pattern) {
793
- const matches = content.match(pattern);
794
- return matches ? [...new Set(matches)] : [];
472
+ // src/commands/deps.ts
473
+ async function depsAddCommand(name, options) {
474
+ const cwd = process.cwd();
475
+ const config = loadConfig(cwd);
476
+ const proposalPath = join5(cwd, config.specDir, "changes", name, "proposal.md");
477
+ if (!existsSync6(proposalPath)) {
478
+ log.warn(`${symbol.warn} "${name}" ${t("not found", "\u672A\u627E\u5230")}`);
479
+ return;
480
+ }
481
+ const content = readFileSync2(proposalPath, "utf-8");
482
+ const updated = addDependency(content, options.on);
483
+ writeFileSync(proposalPath, updated, "utf-8");
484
+ log.success(`${symbol.ok} ${name} \u2192 depends_on: ${options.on}`);
795
485
  }
796
- function validateChange(changePath, checkDeps = false) {
797
- const issues = [];
798
- const read = (name) => {
799
- const p = join11(changePath, name);
800
- return existsSync12(p) ? readFileSync5(p, "utf-8") : null;
801
- };
802
- const proposal = read("proposal.md");
803
- const spec = read("spec.md");
804
- const tasks = read("tasks.md");
805
- if (!proposal) issues.push({ level: "warn", artifact: "proposal.md", message: "missing" });
806
- if (!tasks) issues.push({ level: "warn", artifact: "tasks.md", message: "missing" });
807
- if (proposal && spec) {
808
- const proposalGoals = extractIds(proposal, /US-\d+/g);
809
- const specUS = extractIds(spec, /US-\d+/g);
810
- for (const id of proposalGoals) {
811
- if (!specUS.includes(id)) {
812
- issues.push({ level: "error", artifact: "spec.md", message: `${id} from proposal not found in spec` });
813
- }
814
- }
486
+ async function depsRemoveCommand(name, options) {
487
+ const cwd = process.cwd();
488
+ const config = loadConfig(cwd);
489
+ const proposalPath = join5(cwd, config.specDir, "changes", name, "proposal.md");
490
+ if (!existsSync6(proposalPath)) {
491
+ log.warn(`${symbol.warn} "${name}" ${t("not found", "\u672A\u627E\u5230")}`);
492
+ return;
815
493
  }
816
- if (spec && tasks) {
817
- const specFR = extractIds(spec, /FR-\d+/g);
818
- const tasksFR = extractIds(tasks, /FR-\d+/g);
819
- for (const id of specFR) {
820
- if (!tasksFR.includes(id)) {
821
- issues.push({ level: "warn", artifact: "tasks.md", message: `${id} from spec not referenced in tasks` });
822
- }
494
+ const content = readFileSync2(proposalPath, "utf-8");
495
+ const updated = removeDependency(content, options.on);
496
+ writeFileSync(proposalPath, updated, "utf-8");
497
+ log.success(`${symbol.ok} ${name} \u2192 ${t("removed dependency", "\u79FB\u9664\u4F9D\u8D56")}: ${options.on}`);
498
+ }
499
+ async function depsListCommand(name) {
500
+ const cwd = process.cwd();
501
+ const config = loadConfig(cwd);
502
+ const changesDir = join5(cwd, config.specDir, "changes");
503
+ if (name) {
504
+ const proposalPath = join5(changesDir, name, "proposal.md");
505
+ if (!existsSync6(proposalPath)) {
506
+ log.warn(`${symbol.warn} "${name}" ${t("not found", "\u672A\u627E\u5230")}`);
507
+ return;
823
508
  }
824
- for (const id of tasksFR) {
825
- if (!specFR.includes(id)) {
826
- issues.push({ level: "error", artifact: "tasks.md", message: `${id} referenced but not defined in spec` });
509
+ const content = readFileSync2(proposalPath, "utf-8");
510
+ const { meta } = parseFrontmatter(content);
511
+ const deps2 = Array.isArray(meta.depends_on) ? meta.depends_on : [];
512
+ log.info(`${symbol.start} ${name}`);
513
+ if (deps2.length === 0) {
514
+ log.dim(` ${t("no dependencies", "\u65E0\u4F9D\u8D56")}`);
515
+ } else {
516
+ for (const d of deps2) {
517
+ log.dim(` \u2192 ${d}`);
827
518
  }
828
519
  }
520
+ return;
829
521
  }
830
- if (spec) {
831
- const acIds = extractIds(spec, /AC-\d+\.\d+/g);
832
- const frIds = extractIds(spec, /FR-\d+/g);
833
- for (const ac of acIds) {
834
- const frNum = ac.replace("AC-", "").split(".")[0];
835
- const parentFR = `FR-${frNum}`;
836
- if (!frIds.includes(parentFR)) {
837
- issues.push({ level: "warn", artifact: "spec.md", message: `${ac} has no parent ${parentFR}` });
838
- }
522
+ if (!existsSync6(changesDir)) {
523
+ log.warn(`${symbol.warn} ${t("no changes directory", "\u672A\u627E\u5230 changes \u76EE\u5F55")}`);
524
+ return;
525
+ }
526
+ const entries = readdirSync3(changesDir, { withFileTypes: true }).filter(
527
+ (e) => e.isDirectory() && e.name !== config.archive.dir
528
+ );
529
+ log.info(`${symbol.start} ${t("dependency graph", "\u4F9D\u8D56\u5173\u7CFB")}`);
530
+ for (const entry of entries) {
531
+ const proposalPath = join5(changesDir, entry.name, "proposal.md");
532
+ if (!existsSync6(proposalPath)) continue;
533
+ const content = readFileSync2(proposalPath, "utf-8");
534
+ const { meta } = parseFrontmatter(content);
535
+ const deps2 = Array.isArray(meta.depends_on) ? meta.depends_on : [];
536
+ if (deps2.length > 0) {
537
+ log.dim(` ${entry.name} \u2192 [${deps2.join(", ")}]`);
538
+ } else {
539
+ log.dim(` ${entry.name}`);
839
540
  }
840
541
  }
841
- if (checkDeps && proposal) {
842
- const { meta, body } = parseFrontmatter(proposal);
843
- const fmDeps = Array.isArray(meta.depends_on) ? meta.depends_on : [];
844
- const contentRefs = extractIds(body, /depends[_ ]on[:\s]+(\S+)/gi);
845
- const mentioned = contentRefs.map((m) => m.replace(/depends[_ ]on[:\s]+/i, ""));
846
- for (const dep of mentioned) {
847
- if (!fmDeps.includes(dep)) {
848
- issues.push({ level: "warn", artifact: "proposal.md", message: `content mentions "${dep}" but not in frontmatter depends_on` });
849
- }
542
+ }
543
+
544
+ // src/commands/init.ts
545
+ import { execSync as execSync2 } from "child_process";
546
+ import { existsSync as existsSync9, readdirSync as readdirSync5, writeFileSync as writeFileSync4 } from "fs";
547
+ import { join as join8 } from "path";
548
+
549
+ // src/core/template.ts
550
+ import { copyFileSync, existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
551
+ import { dirname as dirname2, join as join6 } from "path";
552
+ function resolveTemplatePath(templateName, lang = "zh") {
553
+ const root = getPackageRoot();
554
+ const langPath = join6(root, "templates", lang, templateName);
555
+ if (existsSync7(langPath)) return langPath;
556
+ const fallbackLang = lang === "zh" ? "en" : "zh";
557
+ const fallback = join6(root, "templates", fallbackLang, templateName);
558
+ if (existsSync7(fallback)) return fallback;
559
+ throw new Error(`Template not found: ${templateName} (lang: ${lang})`);
560
+ }
561
+ function copyTemplate(templateName, destPath, lang = "zh") {
562
+ const srcPath = resolveTemplatePath(templateName, lang);
563
+ ensureDir(dirname2(destPath));
564
+ copyFileSync(srcPath, destPath);
565
+ }
566
+
567
+ // src/prompts/index.ts
568
+ import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
569
+ import { join as join7 } from "path";
570
+ var AI_EDITORS = {
571
+ claude: {
572
+ commands: ".claude/commands",
573
+ rules: null
574
+ // Claude doesn't use rules files
575
+ },
576
+ cursor: {
577
+ commands: ".cursor/commands",
578
+ rules: ".cursor/rules",
579
+ rulesFile: "superspec.mdc"
580
+ },
581
+ qwen: {
582
+ commands: ".qwen/commands",
583
+ rules: ".qwen/rules",
584
+ rulesFile: "superspec.md"
585
+ },
586
+ opencode: {
587
+ commands: ".opencode/commands",
588
+ rules: null
589
+ },
590
+ codex: {
591
+ commands: ".codex/commands",
592
+ rules: null
593
+ },
594
+ codebuddy: {
595
+ commands: ".codebuddy/commands",
596
+ rules: ".codebuddy/rules",
597
+ rulesFile: "superspec.md"
598
+ },
599
+ qoder: {
600
+ commands: ".qoder/commands",
601
+ rules: ".qoder/rules",
602
+ rulesFile: "superspec.md"
603
+ }
604
+ };
605
+ function installRules(cwd, editor) {
606
+ const config = AI_EDITORS[editor];
607
+ if (!config.rules) {
608
+ return;
609
+ }
610
+ const rulesDir = join7(cwd, config.rules);
611
+ ensureDir(rulesDir);
612
+ const promptSrc = join7(getPackageRoot(), "prompts", "rules.md");
613
+ if (existsSync8(promptSrc)) {
614
+ const content = readFileSync4(promptSrc, "utf-8");
615
+ const rulesFile = "rulesFile" in config ? config.rulesFile : "superspec.md";
616
+ const destPath = join7(rulesDir, rulesFile);
617
+ writeFileSync3(destPath, content, "utf-8");
618
+ log.success(`${symbol.ok} ${config.rules}/${rulesFile}`);
619
+ }
620
+ }
621
+ var SS_START = "<!-- superspec:start -->";
622
+ var SS_END = "<!-- superspec:end -->";
623
+ function installAgentsMd(cwd) {
624
+ const agentsMdPath = join7(cwd, "AGENTS.md");
625
+ const agentPromptSrc = join7(getPackageRoot(), "prompts", "agents.md");
626
+ if (!existsSync8(agentPromptSrc)) return;
627
+ const newContent = readFileSync4(agentPromptSrc, "utf-8");
628
+ const wrapped = `${SS_START}
629
+ ${newContent}
630
+ ${SS_END}`;
631
+ if (existsSync8(agentsMdPath)) {
632
+ const existing = readFileSync4(agentsMdPath, "utf-8");
633
+ const startIdx = existing.indexOf(SS_START);
634
+ const endIdx = existing.indexOf(SS_END);
635
+ if (startIdx !== -1 && endIdx !== -1) {
636
+ const before = existing.slice(0, startIdx);
637
+ const after = existing.slice(endIdx + SS_END.length);
638
+ writeFileSync3(agentsMdPath, before + wrapped + after, "utf-8");
639
+ } else if (existing.includes("SuperSpec")) {
640
+ writeFileSync3(agentsMdPath, existing, "utf-8");
641
+ } else {
642
+ writeFileSync3(agentsMdPath, `${existing}
643
+
644
+ ${wrapped}`, "utf-8");
850
645
  }
851
- const changesDir = join11(changePath, "..");
852
- for (const dep of fmDeps) {
853
- if (!existsSync12(join11(changesDir, dep))) {
854
- issues.push({ level: "error", artifact: "proposal.md", message: `depends_on "${dep}" not found in changes` });
855
- }
646
+ } else {
647
+ writeFileSync3(agentsMdPath, wrapped, "utf-8");
648
+ }
649
+ log.success(`${symbol.ok} AGENTS.md`);
650
+ }
651
+ function installCommands(cwd, editor, lang = "zh") {
652
+ const config = AI_EDITORS[editor];
653
+ const commandsDir = join7(cwd, config.commands);
654
+ ensureDir(commandsDir);
655
+ const templatesDir = join7(getPackageRoot(), "templates", lang, "commands");
656
+ const fallbackDir = join7(getPackageRoot(), "templates", "zh", "commands");
657
+ const sourceDir = existsSync8(templatesDir) ? templatesDir : fallbackDir;
658
+ if (!existsSync8(sourceDir)) {
659
+ log.warn(`${symbol.warn} Commands templates not found: ${sourceDir}`);
660
+ return;
661
+ }
662
+ const commandFiles = readdirSync4(sourceDir).filter((f) => f.endsWith(".md"));
663
+ for (const file of commandFiles) {
664
+ const srcPath = join7(sourceDir, file);
665
+ const destPath = join7(commandsDir, file);
666
+ const content = readFileSync4(srcPath, "utf-8");
667
+ writeFileSync3(destPath, content, "utf-8");
668
+ }
669
+ log.success(`${symbol.ok} ${config.commands}/ (${commandFiles.length} commands)`);
670
+ }
671
+
672
+ // src/commands/init.ts
673
+ async function initCommand(options) {
674
+ const cwd = process.cwd();
675
+ const configPath = join8(cwd, "superspec.config.json");
676
+ if (existsSync9(configPath) && !options.force) {
677
+ log.warn(
678
+ `${symbol.warn} ${t("superspec.config.json already exists, use --force to overwrite", "superspec.config.json \u5DF2\u5B58\u5728\uFF0C\u4F7F\u7528 --force \u8986\u76D6")}`
679
+ );
680
+ return;
681
+ }
682
+ const lang = options.lang || "zh";
683
+ setLang(lang);
684
+ printLogo("small");
685
+ console.log(theme.dim(" Spec-Driven Development Toolkit\n"));
686
+ const config = getDefaultConfig();
687
+ config.lang = lang;
688
+ const aiEditor = options.ai;
689
+ if (aiEditor && AI_EDITORS[aiEditor]) {
690
+ config.aiEditor = aiEditor;
691
+ }
692
+ const specDir = join8(cwd, config.specDir);
693
+ const existingFiles = readdirSync5(cwd).filter((f) => !f.startsWith(".") && f !== "node_modules");
694
+ if (existingFiles.length > 0 && !options.force) {
695
+ log.warn(
696
+ `${symbol.warn} ${t(`current directory is not empty (${existingFiles.length} items)`, `\u5F53\u524D\u76EE\u5F55\u975E\u7A7A\uFF08${existingFiles.length} \u9879\uFF09`)}`
697
+ );
698
+ log.dim(
699
+ ` ${t("template files will be merged with existing content", "\u6A21\u677F\u6587\u4EF6\u5C06\u4E0E\u73B0\u6709\u5185\u5BB9\u5408\u5E76")}`
700
+ );
701
+ console.log();
702
+ }
703
+ log.section(t("Creating Configuration", "\u521B\u5EFA\u914D\u7F6E"));
704
+ writeFileSync4(configPath, `${JSON.stringify(config, null, 2)}
705
+ `, "utf-8");
706
+ log.success(`${symbol.file} superspec.config.json`);
707
+ log.section(t("Creating Directory Structure", "\u521B\u5EFA\u76EE\u5F55\u7ED3\u6784"));
708
+ ensureDir(join8(specDir, "changes"));
709
+ ensureDir(join8(specDir, "templates"));
710
+ log.success(`${symbol.folder} ${config.specDir}/changes/`);
711
+ log.success(`${symbol.folder} ${config.specDir}/templates/`);
712
+ log.section(t("Installing Templates", "\u5B89\u88C5\u6A21\u677F"));
713
+ const templateNames = Object.values(config.templates).map(
714
+ (v) => v.endsWith(".md") ? v : `${v}.md`
715
+ );
716
+ for (const tpl of templateNames) {
717
+ try {
718
+ copyTemplate(tpl, join8(specDir, "templates", tpl), lang);
719
+ } catch {
856
720
  }
857
721
  }
858
- return issues;
722
+ log.success(`${symbol.ok} ${templateNames.length} ${t("templates", "\u4E2A\u6A21\u677F")} (${lang})`);
723
+ log.section(t("Installing AI Agent Files", "\u5B89\u88C5 AI Agent \u6587\u4EF6"));
724
+ installAgentsMd(cwd);
725
+ if (aiEditor && AI_EDITORS[aiEditor]) {
726
+ installRules(cwd, aiEditor);
727
+ installCommands(cwd, aiEditor, lang);
728
+ }
729
+ if (options.git !== false && !isGitRepo()) {
730
+ execSync2("git init", { cwd, stdio: "inherit" });
731
+ log.success(`${symbol.git} git init`);
732
+ }
733
+ console.log();
734
+ printSummary([
735
+ { label: "Config", value: "superspec.config.json" },
736
+ { label: "Spec dir", value: `${config.specDir}/` },
737
+ { label: "AI agent", value: options.ai },
738
+ { label: "Language", value: lang }
739
+ ]);
740
+ log.done(t("SuperSpec initialized successfully!", "SuperSpec \u521D\u59CB\u5316\u6210\u529F\uFF01"));
741
+ log.dim(`${t("Next", "\u4E0B\u4E00\u6B65")}: superspec create <feature>`);
742
+ }
743
+
744
+ // src/commands/lint.ts
745
+ import { existsSync as existsSync11 } from "fs";
746
+ import { join as join10 } from "path";
747
+
748
+ // src/core/lint.ts
749
+ import { existsSync as existsSync10, readdirSync as readdirSync6, readFileSync as readFileSync5 } from "fs";
750
+ import { basename, join as join9 } from "path";
751
+ function lintArtifact(filePath, targetLines, hardLines) {
752
+ const artifact = basename(filePath);
753
+ if (!existsSync10(filePath)) {
754
+ return { artifact, lines: 0, status: "ok", message: "not found" };
755
+ }
756
+ const content = readFileSync5(filePath, "utf-8");
757
+ const lines = content.split("\n").length;
758
+ if (lines > hardLines) {
759
+ return {
760
+ artifact,
761
+ lines,
762
+ status: "error",
763
+ message: `${lines} lines exceeds hard limit (${hardLines}). Must split.`
764
+ };
765
+ }
766
+ if (lines > targetLines) {
767
+ return {
768
+ artifact,
769
+ lines,
770
+ status: "warn",
771
+ message: `${lines} lines exceeds target (${targetLines}). Consider splitting.`
772
+ };
773
+ }
774
+ return { artifact, lines, status: "ok", message: `${lines} lines` };
775
+ }
776
+ function lintChange(changePath, targetLines, hardLines) {
777
+ if (!existsSync10(changePath)) return [];
778
+ const files = readdirSync6(changePath).filter((f) => f.endsWith(".md"));
779
+ return files.map((f) => lintArtifact(join9(changePath, f), targetLines, hardLines));
859
780
  }
860
781
 
861
- // src/commands/validate.ts
862
- async function validateCommand(name, options) {
782
+ // src/commands/lint.ts
783
+ async function lintCommand(name) {
863
784
  const cwd = process.cwd();
864
785
  const config = loadConfig(cwd);
865
- const changesDir = join12(cwd, config.specDir, "changes");
786
+ const changesDir = join10(cwd, config.specDir, "changes");
787
+ const { targetLines, hardLines } = config.limits;
866
788
  const names = resolveChangeNames(changesDir, name, config.archive.dir);
867
789
  if (names.length === 0) {
868
- log.warn(`${symbol.warn} ${t("no changes to validate", "\u6CA1\u6709\u53EF\u9A8C\u8BC1\u7684\u53D8\u66F4")}`);
790
+ log.warn(`${symbol.warn} ${t("no changes to lint", "\u6CA1\u6709\u53EF\u68C0\u67E5\u7684\u53D8\u66F4")}`);
869
791
  return;
870
792
  }
871
- let totalIssues = 0;
793
+ let hasIssues = false;
872
794
  for (const n of names) {
873
- const changePath = join12(changesDir, n);
874
- if (!existsSync13(changePath)) {
795
+ const changePath = join10(changesDir, n);
796
+ if (!existsSync11(changePath)) {
875
797
  log.warn(`${symbol.warn} "${n}" ${t("not found", "\u672A\u627E\u5230")}`);
876
798
  continue;
877
799
  }
878
- const issues = validateChange(changePath, options.checkDeps);
800
+ const results = lintChange(changePath, targetLines, hardLines);
879
801
  log.info(`${symbol.start} ${n}`);
880
- if (issues.length === 0) {
881
- log.success(` ${symbol.ok} ${t("all checks passed", "\u6240\u6709\u68C0\u67E5\u901A\u8FC7")}`);
882
- } else {
883
- for (const issue of issues) {
884
- totalIssues++;
885
- if (issue.level === "error") {
886
- log.error(` ${symbol.fail} [${issue.artifact}] ${issue.message}`);
887
- } else if (issue.level === "warn") {
888
- log.warn(` ${symbol.warn} [${issue.artifact}] ${issue.message}`);
889
- } else {
890
- log.dim(` \u2139 [${issue.artifact}] ${issue.message}`);
891
- }
802
+ for (const r of results) {
803
+ if (r.status === "error") {
804
+ log.error(` ${symbol.fail} ${r.artifact}: ${r.message}`);
805
+ hasIssues = true;
806
+ } else if (r.status === "warn") {
807
+ log.warn(` ${symbol.warn} ${r.artifact}: ${r.message}`);
808
+ hasIssues = true;
809
+ } else {
810
+ log.success(` ${symbol.ok} ${r.artifact}: ${r.message}`);
892
811
  }
893
812
  }
894
813
  }
895
- if (totalIssues === 0) {
896
- log.success(`${symbol.ok} ${t("all validations passed", "\u6240\u6709\u9A8C\u8BC1\u901A\u8FC7")}`);
897
- } else {
898
- log.warn(`${symbol.warn} ${totalIssues} ${t("issue(s) found", "\u4E2A\u95EE\u9898")}`);
814
+ if (!hasIssues) {
815
+ log.info(`${symbol.start} ${t("all artifacts within limits", "\u6240\u6709 artifact \u5747\u5728\u9650\u5236\u8303\u56F4\u5185")}`);
899
816
  }
900
817
  }
901
818
 
902
819
  // src/commands/search.ts
903
- import { existsSync as existsSync14, readFileSync as readFileSync6, readdirSync as readdirSync7 } from "fs";
904
- import { join as join13, basename as basename3 } from "path";
820
+ import { existsSync as existsSync12, readdirSync as readdirSync7, readFileSync as readFileSync6 } from "fs";
821
+ import { basename as basename2, join as join11 } from "path";
905
822
  var DEFAULT_LIMIT = 50;
906
823
  async function searchCommand(query, options) {
907
824
  const cwd = process.cwd();
908
825
  const config = loadConfig(cwd);
909
- const changesDir = join13(cwd, config.specDir, "changes");
910
- if (!existsSync14(changesDir)) {
826
+ const changesDir = join11(cwd, config.specDir, "changes");
827
+ if (!existsSync12(changesDir)) {
911
828
  log.warn(`${symbol.warn} ${t("no changes directory found", "\u672A\u627E\u5230 changes \u76EE\u5F55")}`);
912
829
  return;
913
830
  }
914
831
  const dirs = [];
915
- const activeEntries = readdirSync7(changesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name !== config.archive.dir);
832
+ const activeEntries = readdirSync7(changesDir, { withFileTypes: true }).filter(
833
+ (e) => e.isDirectory() && e.name !== config.archive.dir
834
+ );
916
835
  for (const e of activeEntries) {
917
- dirs.push({ name: e.name, path: join13(changesDir, e.name) });
836
+ dirs.push({ name: e.name, path: join11(changesDir, e.name) });
918
837
  }
919
838
  if (options.archived) {
920
- const archiveDir = join13(changesDir, config.archive.dir);
921
- if (existsSync14(archiveDir)) {
922
- const archivedEntries = readdirSync7(archiveDir, { withFileTypes: true }).filter((e) => e.isDirectory());
839
+ const archiveDir = join11(changesDir, config.archive.dir);
840
+ if (existsSync12(archiveDir)) {
841
+ const archivedEntries = readdirSync7(archiveDir, { withFileTypes: true }).filter(
842
+ (e) => e.isDirectory()
843
+ );
923
844
  for (const e of archivedEntries) {
924
- dirs.push({ name: `${config.archive.dir}/${e.name}`, path: join13(archiveDir, e.name) });
845
+ dirs.push({ name: `${config.archive.dir}/${e.name}`, path: join11(archiveDir, e.name) });
925
846
  }
926
847
  }
927
848
  }
@@ -940,14 +861,14 @@ async function searchCommand(query, options) {
940
861
  }
941
862
  const hits = [];
942
863
  for (const dir of dirs) {
943
- if (!existsSync14(dir.path)) continue;
864
+ if (!existsSync12(dir.path)) continue;
944
865
  const files = readdirSync7(dir.path).filter((f) => f.endsWith(".md"));
945
866
  for (const file of files) {
946
867
  if (options.artifact) {
947
- const artType = basename3(file, ".md");
868
+ const artType = basename2(file, ".md");
948
869
  if (artType !== options.artifact) continue;
949
870
  }
950
- const filePath = join13(dir.path, file);
871
+ const filePath = join11(dir.path, file);
951
872
  const content = readFileSync6(filePath, "utf-8");
952
873
  const lines = content.split("\n");
953
874
  for (let i = 0; i < lines.length; i++) {
@@ -973,90 +894,20 @@ async function searchCommand(query, options) {
973
894
  log.dim(` ${hit.change}/${hit.artifact}:${hit.line} ${hit.text}`);
974
895
  }
975
896
  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
- }
978
- }
979
-
980
- // src/commands/deps.ts
981
- import { existsSync as existsSync15, readFileSync as readFileSync7, writeFileSync as writeFileSync4, readdirSync as readdirSync8 } from "fs";
982
- import { join as join14 } from "path";
983
- async function depsAddCommand(name, options) {
984
- const cwd = process.cwd();
985
- const config = loadConfig(cwd);
986
- const proposalPath = join14(cwd, config.specDir, "changes", name, "proposal.md");
987
- if (!existsSync15(proposalPath)) {
988
- log.warn(`${symbol.warn} "${name}" ${t("not found", "\u672A\u627E\u5230")}`);
989
- return;
990
- }
991
- const content = readFileSync7(proposalPath, "utf-8");
992
- const updated = addDependency(content, options.on);
993
- writeFileSync4(proposalPath, updated, "utf-8");
994
- log.success(`${symbol.ok} ${name} \u2192 depends_on: ${options.on}`);
995
- }
996
- async function depsRemoveCommand(name, options) {
997
- const cwd = process.cwd();
998
- const config = loadConfig(cwd);
999
- const proposalPath = join14(cwd, config.specDir, "changes", name, "proposal.md");
1000
- if (!existsSync15(proposalPath)) {
1001
- log.warn(`${symbol.warn} "${name}" ${t("not found", "\u672A\u627E\u5230")}`);
1002
- return;
1003
- }
1004
- const content = readFileSync7(proposalPath, "utf-8");
1005
- const updated = removeDependency(content, options.on);
1006
- writeFileSync4(proposalPath, updated, "utf-8");
1007
- log.success(`${symbol.ok} ${name} \u2192 ${t("removed dependency", "\u79FB\u9664\u4F9D\u8D56")}: ${options.on}`);
1008
- }
1009
- async function depsListCommand(name) {
1010
- const cwd = process.cwd();
1011
- const config = loadConfig(cwd);
1012
- const changesDir = join14(cwd, config.specDir, "changes");
1013
- if (name) {
1014
- const proposalPath = join14(changesDir, name, "proposal.md");
1015
- if (!existsSync15(proposalPath)) {
1016
- log.warn(`${symbol.warn} "${name}" ${t("not found", "\u672A\u627E\u5230")}`);
1017
- return;
1018
- }
1019
- const content = readFileSync7(proposalPath, "utf-8");
1020
- const { meta } = parseFrontmatter(content);
1021
- const deps2 = Array.isArray(meta.depends_on) ? meta.depends_on : [];
1022
- log.info(`${symbol.start} ${name}`);
1023
- if (deps2.length === 0) {
1024
- log.dim(` ${t("no dependencies", "\u65E0\u4F9D\u8D56")}`);
1025
- } else {
1026
- for (const d of deps2) {
1027
- log.dim(` \u2192 ${d}`);
1028
- }
1029
- }
1030
- return;
1031
- }
1032
- if (!existsSync15(changesDir)) {
1033
- log.warn(`${symbol.warn} ${t("no changes directory", "\u672A\u627E\u5230 changes \u76EE\u5F55")}`);
1034
- return;
1035
- }
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")}`);
1038
- for (const entry of entries) {
1039
- const proposalPath = join14(changesDir, entry.name, "proposal.md");
1040
- if (!existsSync15(proposalPath)) continue;
1041
- const content = readFileSync7(proposalPath, "utf-8");
1042
- const { meta } = parseFrontmatter(content);
1043
- const deps2 = Array.isArray(meta.depends_on) ? meta.depends_on : [];
1044
- if (deps2.length > 0) {
1045
- log.dim(` ${entry.name} \u2192 [${deps2.join(", ")}]`);
1046
- } else {
1047
- log.dim(` ${entry.name}`);
1048
- }
897
+ log.dim(
898
+ ` ... ${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")}`
899
+ );
1049
900
  }
1050
901
  }
1051
902
 
1052
903
  // src/commands/status.ts
1053
- import { existsSync as existsSync16, readFileSync as readFileSync8, readdirSync as readdirSync9 } from "fs";
1054
- import { join as join15 } from "path";
904
+ import { existsSync as existsSync13, readdirSync as readdirSync8, readFileSync as readFileSync7 } from "fs";
905
+ import { join as join12 } from "path";
1055
906
  var ARTIFACT_TYPES = ["proposal", "spec", "tasks", "clarify", "checklist"];
1056
907
  function readStatus(changePath, artifact) {
1057
- const filePath = join15(changePath, `${artifact}.md`);
1058
- if (!existsSync16(filePath)) return "\u2014";
1059
- const content = readFileSync8(filePath, "utf-8");
908
+ const filePath = join12(changePath, `${artifact}.md`);
909
+ if (!existsSync13(filePath)) return "\u2014";
910
+ const content = readFileSync7(filePath, "utf-8");
1060
911
  const { meta } = parseFrontmatter(content);
1061
912
  if (meta.status) return meta.status;
1062
913
  if (content.includes("\u2705")) return "done";
@@ -1079,12 +930,12 @@ function overallStatus(statuses) {
1079
930
  async function statusCommand() {
1080
931
  const cwd = process.cwd();
1081
932
  const config = loadConfig(cwd);
1082
- const changesDir = join15(cwd, config.specDir, "changes");
1083
- if (!existsSync16(changesDir)) {
933
+ const changesDir = join12(cwd, config.specDir, "changes");
934
+ if (!existsSync13(changesDir)) {
1084
935
  log.warn(`${symbol.warn} ${t("no changes directory found", "\u672A\u627E\u5230 changes \u76EE\u5F55")}`);
1085
936
  return;
1086
937
  }
1087
- const entries = readdirSync9(changesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name !== config.archive.dir).sort((a, b) => a.name.localeCompare(b.name));
938
+ const entries = readdirSync8(changesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name !== config.archive.dir).sort((a, b) => a.name.localeCompare(b.name));
1088
939
  if (entries.length === 0) {
1089
940
  log.dim(` ${t("no active changes", "\u65E0\u6D3B\u8DC3\u53D8\u66F4")}`);
1090
941
  return;
@@ -1092,7 +943,7 @@ async function statusCommand() {
1092
943
  const header = ["Change", ...ARTIFACT_TYPES.map((a) => a.slice(0, 8).padEnd(8)), "Status"];
1093
944
  const rows = [];
1094
945
  for (const entry of entries) {
1095
- const changePath = join15(changesDir, entry.name);
946
+ const changePath = join12(changesDir, entry.name);
1096
947
  const statuses = {};
1097
948
  for (const art of ARTIFACT_TYPES) {
1098
949
  statuses[art] = readStatus(changePath, art);
@@ -1115,9 +966,11 @@ async function statusCommand() {
1115
966
  for (const row of rows) {
1116
967
  console.log(formatRow(row));
1117
968
  }
1118
- const archiveDir = join15(changesDir, config.archive.dir);
1119
- if (existsSync16(archiveDir)) {
1120
- const archived = readdirSync9(archiveDir, { withFileTypes: true }).filter((e) => e.isDirectory());
969
+ const archiveDir = join12(changesDir, config.archive.dir);
970
+ if (existsSync13(archiveDir)) {
971
+ const archived = readdirSync8(archiveDir, { withFileTypes: true }).filter(
972
+ (e) => e.isDirectory()
973
+ );
1121
974
  if (archived.length > 0) {
1122
975
  log.dim(`
1123
976
  ${archived.length} ${t("archived change(s)", "\u4E2A\u5DF2\u5F52\u6863\u53D8\u66F4")}`);
@@ -1127,16 +980,18 @@ async function statusCommand() {
1127
980
  async function listCommand(options) {
1128
981
  const cwd = process.cwd();
1129
982
  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));
983
+ const changesDir = join12(cwd, config.specDir, "changes");
984
+ if (!existsSync13(changesDir)) return;
985
+ const entries = readdirSync8(changesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name !== config.archive.dir).sort((a, b) => a.name.localeCompare(b.name));
1133
986
  for (const e of entries) {
1134
987
  console.log(e.name);
1135
988
  }
1136
989
  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());
990
+ const archiveDir = join12(changesDir, config.archive.dir);
991
+ if (existsSync13(archiveDir)) {
992
+ const archived = readdirSync8(archiveDir, { withFileTypes: true }).filter(
993
+ (e) => e.isDirectory()
994
+ );
1140
995
  for (const e of archived) {
1141
996
  console.log(`${config.archive.dir}/${e.name}`);
1142
997
  }
@@ -1144,18 +999,18 @@ async function listCommand(options) {
1144
999
  }
1145
1000
  }
1146
1001
  function stripAnsi(str) {
1147
- return str.replace(/\u001b\[\d+(?:;\d+)*m/g, "").replace(/[✅🟢🟡—]/g, "XX");
1002
+ return str.replace(/\u001b\[\d+(?:;\d+)*m/g, "").replace(/[✅🟢🟡—]/gu, "XX");
1148
1003
  }
1149
1004
 
1150
1005
  // src/commands/sync.ts
1151
- import { existsSync as existsSync18, writeFileSync as writeFileSync5 } from "fs";
1152
- import { join as join17 } from "path";
1006
+ import { existsSync as existsSync15, writeFileSync as writeFileSync5 } from "fs";
1007
+ import { join as join14 } from "path";
1153
1008
 
1154
1009
  // src/core/context.ts
1155
- import { readFileSync as readFileSync9, existsSync as existsSync17 } from "fs";
1156
- import { join as join16 } from "path";
1010
+ import { existsSync as existsSync14, readFileSync as readFileSync8 } from "fs";
1011
+ import { join as join13 } from "path";
1157
1012
  function extractSection(body, heading) {
1158
- const regex = new RegExp(`^##\\s+${heading}[\\s\\S]*?$`, "im");
1013
+ const regex = new RegExp(`^##\\s+(?:${heading})[\\s\\S]*?$`, "im");
1159
1014
  const match = body.match(regex);
1160
1015
  if (!match) return [];
1161
1016
  const startIdx = body.indexOf(match[0]) + match[0].length;
@@ -1189,12 +1044,13 @@ function parseTaskItems(body) {
1189
1044
  function extractFilePaths(body) {
1190
1045
  const paths = /* @__PURE__ */ new Set();
1191
1046
  const regex = /`([^`]+\.[a-zA-Z]+)`/g;
1192
- let match;
1193
- while ((match = regex.exec(body)) !== null) {
1047
+ let match = regex.exec(body);
1048
+ while (match !== null) {
1194
1049
  const p = match[1];
1195
1050
  if (p.includes("/") && !p.startsWith("http") && !p.includes(" ")) {
1196
1051
  paths.add(p);
1197
1052
  }
1053
+ match = regex.exec(body);
1198
1054
  }
1199
1055
  return [...paths];
1200
1056
  }
@@ -1231,21 +1087,17 @@ function classifyGitChanges(gitChanges, taskFiles) {
1231
1087
  }
1232
1088
  function generateContext(changePath, changeName, options = {}) {
1233
1089
  const read = (name) => {
1234
- const p = join16(changePath, name);
1235
- return existsSync17(p) ? readFileSync9(p, "utf-8") : null;
1090
+ const p = join13(changePath, name);
1091
+ return existsSync14(p) ? readFileSync8(p, "utf-8") : null;
1236
1092
  };
1237
1093
  const proposal = read("proposal.md");
1238
1094
  const spec = read("spec.md");
1239
1095
  const tasks = read("tasks.md");
1240
1096
  const clarify = read("clarify.md");
1241
- let strategy = "follow";
1242
- let status = "in-progress";
1243
1097
  const mode = spec ? "boost" : "standard";
1244
- if (proposal) {
1245
- const { meta } = parseFrontmatter(proposal);
1246
- if (meta.strategy) strategy = meta.strategy;
1247
- if (meta.status) status = meta.status;
1248
- }
1098
+ const proposalMeta = proposal ? parseFrontmatter(proposal).meta : {};
1099
+ const strategy = proposalMeta.strategy || "follow";
1100
+ const status = proposalMeta.status || "in-progress";
1249
1101
  const goals = [];
1250
1102
  if (proposal) {
1251
1103
  const { body } = parseFrontmatter(proposal);
@@ -1276,13 +1128,15 @@ function generateContext(changePath, changeName, options = {}) {
1276
1128
  notes = notesSection.join("\n");
1277
1129
  }
1278
1130
  }
1279
- const fm = serializeFrontmatter({
1131
+ const fmData = {
1280
1132
  name: changeName,
1281
1133
  status,
1282
1134
  strategy,
1283
1135
  mode,
1284
1136
  updated: getDateString()
1285
- });
1137
+ };
1138
+ if (proposalMeta.input) fmData.input = proposalMeta.input;
1139
+ const fm = serializeFrontmatter(fmData);
1286
1140
  const lines = [fm, ""];
1287
1141
  if (goals.length > 0) {
1288
1142
  lines.push("## Goals", ...goals, "");
@@ -1320,7 +1174,7 @@ function generateContext(changePath, changeName, options = {}) {
1320
1174
  async function syncCommand(name, opts) {
1321
1175
  const cwd = process.cwd();
1322
1176
  const config = loadConfig(cwd);
1323
- const changesDir = join17(cwd, config.specDir, "changes");
1177
+ const changesDir = join14(cwd, config.specDir, "changes");
1324
1178
  const names = resolveChangeNames(changesDir, name, config.archive.dir);
1325
1179
  if (names.length === 0) {
1326
1180
  log.warn(`${symbol.warn} ${t("no changes found", "\u672A\u627E\u5230\u53D8\u66F4")}`);
@@ -1328,8 +1182,8 @@ async function syncCommand(name, opts) {
1328
1182
  }
1329
1183
  const useGit = opts.git !== false;
1330
1184
  for (const n of names) {
1331
- const changePath = join17(changesDir, n);
1332
- if (!existsSync18(changePath)) {
1185
+ const changePath = join14(changesDir, n);
1186
+ if (!existsSync15(changePath)) {
1333
1187
  log.warn(`${symbol.warn} "${n}" ${t("not found", "\u672A\u627E\u5230")}`);
1334
1188
  continue;
1335
1189
  }
@@ -1337,30 +1191,256 @@ async function syncCommand(name, opts) {
1337
1191
  gitDiff: useGit,
1338
1192
  baseBranch: opts.base
1339
1193
  });
1340
- const destPath = join17(changePath, "context.md");
1194
+ const destPath = join14(changePath, "context.md");
1341
1195
  writeFileSync5(destPath, content, "utf-8");
1342
1196
  log.success(`${symbol.ok} ${t("synced", "\u5DF2\u540C\u6B65")} ${n}/context.md`);
1343
1197
  }
1344
1198
  }
1345
1199
 
1200
+ // src/commands/update.ts
1201
+ import { existsSync as existsSync16 } from "fs";
1202
+ import { join as join15 } from "path";
1203
+ async function updateCommand() {
1204
+ const cwd = process.cwd();
1205
+ const config = loadConfig(cwd);
1206
+ const specDir = join15(cwd, config.specDir);
1207
+ const lang = config.lang || "zh";
1208
+ if (!existsSync16(join15(cwd, "superspec.config.json"))) {
1209
+ log.warn(
1210
+ `${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")}`
1211
+ );
1212
+ return;
1213
+ }
1214
+ log.info(`${symbol.start} ${t("updating SuperSpec...", "\u66F4\u65B0 SuperSpec...")}`);
1215
+ const templateNames = Object.values(config.templates).map(
1216
+ (v) => v.endsWith(".md") ? v : `${v}.md`
1217
+ );
1218
+ ensureDir(join15(specDir, "templates"));
1219
+ for (const tpl of templateNames) {
1220
+ try {
1221
+ copyTemplate(tpl, join15(specDir, "templates", tpl), lang);
1222
+ } catch {
1223
+ }
1224
+ }
1225
+ log.success(` ${symbol.ok} ${t("templates updated", "\u6A21\u677F\u66F4\u65B0")} (${lang})`);
1226
+ installAgentsMd(cwd);
1227
+ const aiEditor = config.aiEditor;
1228
+ if (aiEditor && AI_EDITORS[aiEditor]) {
1229
+ installRules(cwd, aiEditor);
1230
+ installCommands(cwd, aiEditor, lang);
1231
+ }
1232
+ log.info(`${symbol.start} ${t("update done!", "\u66F4\u65B0\u5B8C\u6210\uFF01")}`);
1233
+ }
1234
+
1235
+ // src/commands/validate.ts
1236
+ import { existsSync as existsSync18 } from "fs";
1237
+ import { join as join17 } from "path";
1238
+
1239
+ // src/core/validate.ts
1240
+ import { existsSync as existsSync17, readFileSync as readFileSync9 } from "fs";
1241
+ import { join as join16 } from "path";
1242
+ function extractIds(content, pattern) {
1243
+ const matches = content.match(pattern);
1244
+ return matches ? [...new Set(matches)] : [];
1245
+ }
1246
+ function validateChange(changePath, checkDeps = false) {
1247
+ const issues = [];
1248
+ const read = (name) => {
1249
+ const p = join16(changePath, name);
1250
+ return existsSync17(p) ? readFileSync9(p, "utf-8") : null;
1251
+ };
1252
+ const proposal = read("proposal.md");
1253
+ const spec = read("spec.md");
1254
+ const tasks = read("tasks.md");
1255
+ if (!proposal) issues.push({ level: "warn", artifact: "proposal.md", message: "missing" });
1256
+ if (!tasks) issues.push({ level: "warn", artifact: "tasks.md", message: "missing" });
1257
+ if (proposal && spec) {
1258
+ const proposalGoals = extractIds(proposal, /US-\d+/g);
1259
+ const specUS = extractIds(spec, /US-\d+/g);
1260
+ for (const id of proposalGoals) {
1261
+ if (!specUS.includes(id)) {
1262
+ issues.push({
1263
+ level: "error",
1264
+ artifact: "spec.md",
1265
+ message: `${id} from proposal not found in spec`
1266
+ });
1267
+ }
1268
+ }
1269
+ }
1270
+ if (spec && tasks) {
1271
+ const specFR = extractIds(spec, /FR-\d+/g);
1272
+ const tasksFR = extractIds(tasks, /FR-\d+/g);
1273
+ for (const id of specFR) {
1274
+ if (!tasksFR.includes(id)) {
1275
+ issues.push({
1276
+ level: "warn",
1277
+ artifact: "tasks.md",
1278
+ message: `${id} from spec not referenced in tasks`
1279
+ });
1280
+ }
1281
+ }
1282
+ for (const id of tasksFR) {
1283
+ if (!specFR.includes(id)) {
1284
+ issues.push({
1285
+ level: "error",
1286
+ artifact: "tasks.md",
1287
+ message: `${id} referenced but not defined in spec`
1288
+ });
1289
+ }
1290
+ }
1291
+ }
1292
+ if (spec) {
1293
+ const acIds = extractIds(spec, /AC-\d+\.\d+/g);
1294
+ const frIds = extractIds(spec, /FR-\d+/g);
1295
+ for (const ac of acIds) {
1296
+ const frNum = ac.replace("AC-", "").split(".")[0];
1297
+ const parentFR = `FR-${frNum}`;
1298
+ if (!frIds.includes(parentFR)) {
1299
+ issues.push({
1300
+ level: "warn",
1301
+ artifact: "spec.md",
1302
+ message: `${ac} has no parent ${parentFR}`
1303
+ });
1304
+ }
1305
+ }
1306
+ }
1307
+ if (checkDeps && proposal) {
1308
+ const { meta, body } = parseFrontmatter(proposal);
1309
+ const fmDeps = Array.isArray(meta.depends_on) ? meta.depends_on : [];
1310
+ const contentRefs = extractIds(body, /depends[_ ]on[:\s]+(\S+)/gi);
1311
+ const mentioned = contentRefs.map((m) => m.replace(/depends[_ ]on[:\s]+/i, ""));
1312
+ for (const dep of mentioned) {
1313
+ if (!fmDeps.includes(dep)) {
1314
+ issues.push({
1315
+ level: "warn",
1316
+ artifact: "proposal.md",
1317
+ message: `content mentions "${dep}" but not in frontmatter depends_on`
1318
+ });
1319
+ }
1320
+ }
1321
+ const changesDir = join16(changePath, "..");
1322
+ for (const dep of fmDeps) {
1323
+ if (!existsSync17(join16(changesDir, dep))) {
1324
+ issues.push({
1325
+ level: "error",
1326
+ artifact: "proposal.md",
1327
+ message: `depends_on "${dep}" not found in changes`
1328
+ });
1329
+ }
1330
+ }
1331
+ }
1332
+ return issues;
1333
+ }
1334
+
1335
+ // src/commands/validate.ts
1336
+ async function validateCommand(name, options) {
1337
+ const cwd = process.cwd();
1338
+ const config = loadConfig(cwd);
1339
+ const changesDir = join17(cwd, config.specDir, "changes");
1340
+ const names = resolveChangeNames(changesDir, name, config.archive.dir);
1341
+ if (names.length === 0) {
1342
+ log.warn(`${symbol.warn} ${t("no changes to validate", "\u6CA1\u6709\u53EF\u9A8C\u8BC1\u7684\u53D8\u66F4")}`);
1343
+ return;
1344
+ }
1345
+ let totalIssues = 0;
1346
+ for (const n of names) {
1347
+ const changePath = join17(changesDir, n);
1348
+ if (!existsSync18(changePath)) {
1349
+ log.warn(`${symbol.warn} "${n}" ${t("not found", "\u672A\u627E\u5230")}`);
1350
+ continue;
1351
+ }
1352
+ const issues = validateChange(changePath, options.checkDeps);
1353
+ log.info(`${symbol.start} ${n}`);
1354
+ if (issues.length === 0) {
1355
+ log.success(` ${symbol.ok} ${t("all checks passed", "\u6240\u6709\u68C0\u67E5\u901A\u8FC7")}`);
1356
+ } else {
1357
+ for (const issue of issues) {
1358
+ totalIssues++;
1359
+ if (issue.level === "error") {
1360
+ log.error(` ${symbol.fail} [${issue.artifact}] ${issue.message}`);
1361
+ } else if (issue.level === "warn") {
1362
+ log.warn(` ${symbol.warn} [${issue.artifact}] ${issue.message}`);
1363
+ } else {
1364
+ log.dim(` \u2139 [${issue.artifact}] ${issue.message}`);
1365
+ }
1366
+ }
1367
+ }
1368
+ }
1369
+ if (totalIssues === 0) {
1370
+ log.success(`${symbol.ok} ${t("all validations passed", "\u6240\u6709\u9A8C\u8BC1\u901A\u8FC7")}`);
1371
+ } else {
1372
+ log.warn(`${symbol.warn} ${totalIssues} ${t("issue(s) found", "\u4E2A\u95EE\u9898")}`);
1373
+ }
1374
+ }
1375
+
1346
1376
  // src/cli/index.ts
1347
1377
  var require2 = createRequire(import.meta.url);
1348
1378
  var pkg = require2("../../package.json");
1349
1379
  setLang(loadConfig(process.cwd(), true).lang);
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);
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);
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);
1380
+ program.name("superspec").description(
1381
+ t("Spec-driven development for AI coding assistants", "AI \u7F16\u7801\u52A9\u624B\u7684\u89C4\u683C\u9A71\u52A8\u5F00\u53D1\u5DE5\u5177")
1382
+ ).version(pkg.version);
1383
+ program.command("init").description(t("Initialize SuperSpec in current project", "\u521D\u59CB\u5316 SuperSpec \u5230\u5F53\u524D\u9879\u76EE")).option(
1384
+ "--ai <agent>",
1385
+ t(
1386
+ "AI assistant type: claude, cursor, qwen, opencode, codex, codebuddy, qoder",
1387
+ "AI \u52A9\u624B\u7C7B\u578B: claude, cursor, qwen, opencode, codex, codebuddy, qoder"
1388
+ ),
1389
+ "cursor"
1390
+ ).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);
1391
+ program.command("create <feature>").description(
1392
+ t(
1393
+ "Create change and generate proposal (-b boost mode)",
1394
+ "\u521B\u5EFA\u53D8\u66F4\u5E76\u751F\u6210 proposal\uFF08-b \u589E\u5F3A\u6A21\u5F0F\uFF09"
1395
+ )
1396
+ ).option(
1397
+ "-b, --boost",
1398
+ t(
1399
+ "Boost mode, also generate spec + design + checklist",
1400
+ "\u589E\u5F3A\u6A21\u5F0F\uFF0C\u989D\u5916\u751F\u6210 spec + design + checklist"
1401
+ )
1402
+ ).option(
1403
+ "-c, --creative",
1404
+ t("Creative mode, encourage new approaches", "\u521B\u9020\u6A21\u5F0F\uFF0C\u9F13\u52B1\u63A2\u7D22\u65B0\u65B9\u6848")
1405
+ ).option(
1406
+ "-d, --description <desc>",
1407
+ t("Change description for context", "\u53D8\u66F4\u63CF\u8FF0\uFF0C\u7528\u4E8E\u751F\u6210\u4E0A\u4E0B\u6587")
1408
+ ).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(
1409
+ "--intent-type <type>",
1410
+ t(
1411
+ "Intent type: feature, hotfix, bugfix, refactor, chore",
1412
+ "\u610F\u56FE\u7C7B\u578B\uFF1Afeature, hotfix, bugfix, refactor, chore"
1413
+ )
1414
+ ).option("--branch-prefix <prefix>", t("Custom branch prefix", "\u81EA\u5B9A\u4E49\u5206\u652F\u524D\u7F00")).option(
1415
+ "--branch-template <template>",
1416
+ t(
1417
+ "Branch name template: {prefix}{date}-{feature}-{user}",
1418
+ "\u5206\u652F\u540D\u79F0\u6A21\u677F\uFF1A{prefix}-{date}-{feature}-{user}"
1419
+ )
1420
+ ).option(
1421
+ "--change-name-template <template>",
1422
+ t("Folder name template: {date}-{feature}-{user}", "\u6587\u4EF6\u5939\u540D\u79F0\u6A21\u677F\uFF1A{date}-{feature}-{user}")
1423
+ ).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);
1353
1424
  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);
1354
1425
  program.command("update").description(t("Refresh agent instructions and templates", "\u5237\u65B0 agent \u6307\u4EE4\u548C\u6A21\u677F")).action(updateCommand);
1355
1426
  program.command("lint [name]").description(t("Check artifact line limits", "\u68C0\u67E5 artifact \u884C\u6570\u662F\u5426\u8D85\u9650")).action(lintCommand);
1356
1427
  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);
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);
1428
+ 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(
1429
+ "--artifact <type>",
1430
+ t(
1431
+ "Filter by artifact type (proposal/spec/tasks/clarify/checklist)",
1432
+ "\u6309 artifact \u7C7B\u578B\u8FC7\u6EE4 (proposal/spec/tasks/clarify/checklist)"
1433
+ )
1434
+ ).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
1435
  var deps = program.command("deps").description(t("Manage spec dependencies", "\u7BA1\u7406 spec \u4F9D\u8D56"));
1359
1436
  deps.command("list [name]").description(t("View dependency graph", "\u67E5\u770B\u4F9D\u8D56\u5173\u7CFB")).action(depsListCommand);
1360
1437
  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
1438
  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);
1362
1439
  program.command("status").description(t("View all change statuses", "\u67E5\u770B\u6240\u6709\u53D8\u66F4\u72B6\u6001")).action(statusCommand);
1363
1440
  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);
1441
+ program.command("sync [name]").description(t("Sync git changes to context.md", "\u540C\u6B65 git \u53D8\u66F4\u5230 context.md")).option(
1442
+ "--base <branch>",
1443
+ t("Base branch (default: main/master)", "\u57FA\u51C6\u5206\u652F\uFF08\u9ED8\u8BA4 main/master\uFF09")
1444
+ ).option("--no-git", t("Skip git diff collection", "\u4E0D\u6536\u96C6 git diff \u4FE1\u606F")).action(syncCommand);
1365
1445
  program.parse();
1366
1446
  //# sourceMappingURL=index.js.map