@superspec/cli 1.0.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.
Files changed (44) hide show
  1. package/dist/cli/index.js +1329 -0
  2. package/dist/cli/index.js.map +1 -0
  3. package/dist/index.d.ts +52 -0
  4. package/dist/index.js +607 -0
  5. package/dist/index.js.map +1 -0
  6. package/package.json +63 -0
  7. package/prompts/agents.md +150 -0
  8. package/prompts/cursor-rules.md +154 -0
  9. package/templates/en/checklist.md +62 -0
  10. package/templates/en/clarify.md +44 -0
  11. package/templates/en/commands/ss-apply.md +19 -0
  12. package/templates/en/commands/ss-archive.md +15 -0
  13. package/templates/en/commands/ss-checklist.md +16 -0
  14. package/templates/en/commands/ss-clarify.md +17 -0
  15. package/templates/en/commands/ss-create.md +19 -0
  16. package/templates/en/commands/ss-deps.md +11 -0
  17. package/templates/en/commands/ss-link.md +11 -0
  18. package/templates/en/commands/ss-lint.md +15 -0
  19. package/templates/en/commands/ss-resume.md +17 -0
  20. package/templates/en/commands/ss-search.md +12 -0
  21. package/templates/en/commands/ss-status.md +12 -0
  22. package/templates/en/commands/ss-tasks.md +19 -0
  23. package/templates/en/commands/ss-validate.md +15 -0
  24. package/templates/en/proposal.md +53 -0
  25. package/templates/en/spec.md +73 -0
  26. package/templates/en/tasks.md +52 -0
  27. package/templates/zh/checklist.md +62 -0
  28. package/templates/zh/clarify.md +44 -0
  29. package/templates/zh/commands/ss-apply.md +19 -0
  30. package/templates/zh/commands/ss-archive.md +15 -0
  31. package/templates/zh/commands/ss-checklist.md +16 -0
  32. package/templates/zh/commands/ss-clarify.md +17 -0
  33. package/templates/zh/commands/ss-create.md +19 -0
  34. package/templates/zh/commands/ss-deps.md +11 -0
  35. package/templates/zh/commands/ss-link.md +11 -0
  36. package/templates/zh/commands/ss-lint.md +15 -0
  37. package/templates/zh/commands/ss-resume.md +17 -0
  38. package/templates/zh/commands/ss-search.md +12 -0
  39. package/templates/zh/commands/ss-status.md +12 -0
  40. package/templates/zh/commands/ss-tasks.md +19 -0
  41. package/templates/zh/commands/ss-validate.md +15 -0
  42. package/templates/zh/proposal.md +59 -0
  43. package/templates/zh/spec.md +73 -0
  44. package/templates/zh/tasks.md +52 -0
@@ -0,0 +1,1329 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli/index.ts
4
+ import { createRequire } from "module";
5
+ import { program } from "commander";
6
+
7
+ // src/commands/init.ts
8
+ import { existsSync as existsSync5, writeFileSync as writeFileSync3, readdirSync as readdirSync2 } from "fs";
9
+ import { join as join5 } from "path";
10
+ import { execSync as execSync2 } from "child_process";
11
+
12
+ // src/core/config.ts
13
+ import { readFileSync, existsSync } from "fs";
14
+ import { join } from "path";
15
+ var DEFAULT_CONFIG = {
16
+ lang: "zh",
17
+ aiEditor: "cursor",
18
+ specDir: "superspec",
19
+ branchPrefix: "spec/",
20
+ branchTemplate: "{prefix}{name}",
21
+ boost: false,
22
+ strategy: "follow",
23
+ context: [],
24
+ templates: {
25
+ spec: "spec.md",
26
+ proposal: "proposal.md",
27
+ tasks: "tasks.md",
28
+ clarify: "clarify.md",
29
+ checklist: "checklist.md"
30
+ },
31
+ archive: {
32
+ dir: "archive",
33
+ datePrefix: true
34
+ },
35
+ limits: {
36
+ targetLines: 300,
37
+ hardLines: 400
38
+ },
39
+ artifacts: ["proposal"],
40
+ boostArtifacts: ["proposal", "spec", "tasks", "checklist"]
41
+ };
42
+ function loadConfig(projectRoot = process.cwd()) {
43
+ const configPath = join(projectRoot, "superspec.config.json");
44
+ let userConfig = {};
45
+ if (existsSync(configPath)) {
46
+ try {
47
+ userConfig = JSON.parse(readFileSync(configPath, "utf-8"));
48
+ } catch (e) {
49
+ console.warn(`\u26A0 \u914D\u7F6E\u6587\u4EF6\u89E3\u6790\u5931\u8D25: ${e.message}`);
50
+ }
51
+ }
52
+ return deepMerge(DEFAULT_CONFIG, userConfig);
53
+ }
54
+ function getDefaultConfig() {
55
+ return structuredClone(DEFAULT_CONFIG);
56
+ }
57
+ function deepMerge(target, source) {
58
+ const result = { ...target };
59
+ 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]);
62
+ } else {
63
+ result[key] = source[key];
64
+ }
65
+ }
66
+ return result;
67
+ }
68
+
69
+ // src/core/template.ts
70
+ import { existsSync as existsSync3, copyFileSync, readFileSync as readFileSync2, writeFileSync } from "fs";
71
+ import { join as join3, dirname as dirname2 } from "path";
72
+
73
+ // src/utils/paths.ts
74
+ import { dirname, join as join2 } from "path";
75
+ import { fileURLToPath } from "url";
76
+ function getPackageRoot() {
77
+ const __filename2 = fileURLToPath(import.meta.url);
78
+ const __dirname2 = dirname(__filename2);
79
+ return join2(__dirname2, "..", "..");
80
+ }
81
+
82
+ // src/utils/fs.ts
83
+ import { mkdirSync, existsSync as existsSync2 } from "fs";
84
+ function ensureDir(dir) {
85
+ if (!existsSync2(dir)) {
86
+ mkdirSync(dir, { recursive: true });
87
+ }
88
+ }
89
+
90
+ // src/core/template.ts
91
+ function resolveTemplatePath(templateName, lang = "zh") {
92
+ const root = getPackageRoot();
93
+ 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;
97
+ throw new Error(`Template not found: ${templateName} (lang: ${lang})`);
98
+ }
99
+ function copyTemplate(templateName, destPath, lang = "zh") {
100
+ const srcPath = resolveTemplatePath(templateName, lang);
101
+ ensureDir(dirname2(destPath));
102
+ copyFileSync(srcPath, destPath);
103
+ }
104
+ function renderTemplate(templateName, vars = {}, lang = "zh") {
105
+ const srcPath = resolveTemplatePath(templateName, lang);
106
+ let content = readFileSync2(srcPath, "utf-8");
107
+ content = processConditionals(content, vars);
108
+ for (const [key, value] of Object.entries(vars)) {
109
+ content = content.replaceAll(`{{${key}}}`, value);
110
+ }
111
+ return content;
112
+ }
113
+ function processConditionals(content, vars) {
114
+ const ifRegex = /\{\{#if\s+(\w+)\}\}([\s\S]*?)\{\{\/if\}\}/g;
115
+ return content.replace(ifRegex, (match, varName, innerContent) => {
116
+ const value = vars[varName];
117
+ if (value && value.trim() !== "") {
118
+ return innerContent;
119
+ }
120
+ return "";
121
+ });
122
+ }
123
+ function writeRenderedTemplate(templateName, destPath, vars = {}, lang = "zh") {
124
+ const content = renderTemplate(templateName, vars, lang);
125
+ ensureDir(dirname2(destPath));
126
+ writeFileSync(destPath, content, "utf-8");
127
+ }
128
+
129
+ // src/utils/git.ts
130
+ import { execSync } from "child_process";
131
+ function isGitRepo() {
132
+ try {
133
+ execSync("git rev-parse --is-inside-work-tree", { stdio: "ignore" });
134
+ return true;
135
+ } catch {
136
+ return false;
137
+ }
138
+ }
139
+ var SAFE_BRANCH_RE = /^[a-zA-Z0-9._\-/]+$/;
140
+ function createBranch(branchName) {
141
+ if (!SAFE_BRANCH_RE.test(branchName)) {
142
+ throw new Error(`invalid branch name: ${branchName}`);
143
+ }
144
+ execSync(`git checkout -b ${branchName}`, { stdio: "inherit" });
145
+ }
146
+ function getDefaultBranch() {
147
+ try {
148
+ const ref = execSync("git symbolic-ref refs/remotes/origin/HEAD", { encoding: "utf-8" }).trim();
149
+ return ref.replace("refs/remotes/origin/", "");
150
+ } catch {
151
+ try {
152
+ execSync("git rev-parse --verify main", { stdio: "ignore" });
153
+ return "main";
154
+ } catch {
155
+ return "master";
156
+ }
157
+ }
158
+ }
159
+ function getDiffFiles(base) {
160
+ const baseBranch = base || getDefaultBranch();
161
+ 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();
164
+ if (!output) return [];
165
+ return output.split("\n").map((line) => {
166
+ const [status, ...parts] = line.split(" ");
167
+ return { status: status.charAt(0), file: parts.join(" ") };
168
+ });
169
+ } catch {
170
+ return [];
171
+ }
172
+ }
173
+
174
+ // src/prompts/index.ts
175
+ import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2, readdirSync } from "fs";
176
+ import { join as join4 } from "path";
177
+
178
+ // src/ui/index.ts
179
+ import chalk from "chalk";
180
+ var theme = {
181
+ primary: chalk.hex("#6366f1"),
182
+ success: chalk.hex("#22c55e"),
183
+ warning: chalk.hex("#f59e0b"),
184
+ error: chalk.hex("#ef4444"),
185
+ info: chalk.hex("#3b82f6"),
186
+ boost: chalk.hex("#a855f7"),
187
+ dim: chalk.hex("#6b7280"),
188
+ highlight: chalk.hex("#f472b6"),
189
+ border: chalk.hex("#374151"),
190
+ gradient1: chalk.hex("#818cf8"),
191
+ gradient2: chalk.hex("#6366f1"),
192
+ gradient3: chalk.hex("#4f46e5")
193
+ };
194
+ var logo = {
195
+ small: `
196
+ ${theme.gradient1(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557")}
197
+ ${theme.gradient2(" \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D")}
198
+ ${theme.gradient3(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 ")}
199
+ ${theme.gradient2(" \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551 ")}
200
+ ${theme.gradient1(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}
201
+ ${theme.gradient1(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D")}
202
+ `,
203
+ tiny: `
204
+ ${theme.gradient1(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557")}
205
+ ${theme.gradient2(" \u255A\u2550\u2550\u2550\u2550\u2550\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2550\u2550\u2550\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D")}
206
+ ${theme.gradient3(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 ")} ${theme.highlight("Spec-Driven Development")}
207
+ ${theme.gradient2(" \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551 ")}
208
+ ${theme.gradient1(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}
209
+ ${theme.gradient1(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D")}
210
+ `
211
+ };
212
+ var box = {
213
+ topLeft: "\u256D",
214
+ topRight: "\u256E",
215
+ bottomLeft: "\u2570",
216
+ bottomRight: "\u256F",
217
+ horizontal: "\u2500",
218
+ vertical: "\u2502"
219
+ };
220
+ function boxText(text, width = 50) {
221
+ const padding = " ".repeat(Math.max(0, width - text.length - 4));
222
+ return `${theme.border(box.vertical)} ${theme.highlight(text)}${padding} ${theme.border(box.vertical)}`;
223
+ }
224
+ function createBox(lines, width = 52) {
225
+ const top = theme.border(`${box.topLeft}${box.horizontal.repeat(width - 2)}${box.topRight}`);
226
+ const bottom = theme.border(`${box.bottomLeft}${box.horizontal.repeat(width - 2)}${box.bottomRight}`);
227
+ const middle = lines.map((line) => boxText(line, width));
228
+ return [top, ...middle, bottom].join("\n");
229
+ }
230
+ var log = {
231
+ info: (msg) => console.log(theme.info(msg)),
232
+ success: (msg) => console.log(theme.success(msg)),
233
+ warn: (msg) => console.log(theme.warning(msg)),
234
+ error: (msg) => console.log(theme.error(msg)),
235
+ dim: (msg) => console.log(theme.dim(msg)),
236
+ boost: (msg) => console.log(theme.boost(msg)),
237
+ highlight: (msg) => console.log(theme.highlight(msg)),
238
+ title: (msg) => {
239
+ console.log();
240
+ console.log(createBox([msg]));
241
+ console.log();
242
+ },
243
+ section: (msg) => {
244
+ console.log();
245
+ console.log(theme.primary(`\u25C6 ${msg}`));
246
+ console.log(theme.border("\u2500".repeat(50)));
247
+ },
248
+ done: (msg) => {
249
+ console.log();
250
+ console.log(theme.success(`\u2728 ${msg}`));
251
+ console.log();
252
+ }
253
+ };
254
+ var symbol = {
255
+ start: theme.primary("\u25C6"),
256
+ ok: theme.success("\u2713"),
257
+ fail: theme.error("\u2717"),
258
+ warn: theme.warning("\u26A0"),
259
+ bolt: theme.boost("\u26A1"),
260
+ arrow: theme.dim("\u2192"),
261
+ bullet: theme.dim("\u2022"),
262
+ sparkle: theme.highlight("\u2728"),
263
+ folder: theme.primary("\u{1F4C1}"),
264
+ file: theme.info("\u{1F4C4}"),
265
+ git: theme.warning("\u{1F33F}"),
266
+ ai: theme.boost("\u{1F916}")
267
+ };
268
+ function printLogo(size = "small") {
269
+ console.log(logo[size]);
270
+ }
271
+ function printSummary(items) {
272
+ const maxLabel = Math.max(...items.map((i) => i.label.length));
273
+ const width = 50;
274
+ console.log(theme.border("\u256D" + "\u2500".repeat(width - 2) + "\u256E"));
275
+ for (const { label, value } of items) {
276
+ const padding = " ".repeat(maxLabel - label.length);
277
+ const line = `${theme.dim(label)}${padding} ${symbol.arrow} ${theme.highlight(value)}`;
278
+ const plainLine = line.replace(/\u001b\[\d+m/g, "");
279
+ const rightPad = " ".repeat(Math.max(0, width - plainLine.length - 4));
280
+ console.log(theme.border("\u2502 ") + line + rightPad + theme.border(" \u2502"));
281
+ }
282
+ console.log(theme.border("\u2570" + "\u2500".repeat(width - 2) + "\u256F"));
283
+ }
284
+
285
+ // src/prompts/index.ts
286
+ var AI_EDITORS = {
287
+ claude: {
288
+ commands: ".claude/commands",
289
+ rules: null
290
+ // Claude doesn't use rules files
291
+ },
292
+ cursor: {
293
+ commands: ".cursor/commands",
294
+ rules: ".cursor/rules",
295
+ rulesFile: "superspec.mdc"
296
+ },
297
+ qwen: {
298
+ commands: ".qwen/commands",
299
+ rules: ".qwen/rules",
300
+ rulesFile: "superspec.md"
301
+ },
302
+ opencode: {
303
+ commands: ".opencode/commands",
304
+ rules: null
305
+ },
306
+ codex: {
307
+ commands: ".codex/commands",
308
+ rules: null
309
+ },
310
+ codebuddy: {
311
+ commands: ".codebuddy/commands",
312
+ rules: ".codebuddy/rules",
313
+ rulesFile: "superspec.md"
314
+ },
315
+ qoder: {
316
+ commands: ".qoder/commands",
317
+ rules: ".qoder/rules",
318
+ rulesFile: "superspec.md"
319
+ }
320
+ };
321
+ function installRules(cwd, editor) {
322
+ const config = AI_EDITORS[editor];
323
+ if (!config.rules) {
324
+ return;
325
+ }
326
+ const rulesDir = join4(cwd, config.rules);
327
+ ensureDir(rulesDir);
328
+ const promptSrc = join4(getPackageRoot(), "prompts", "cursor-rules.md");
329
+ if (existsSync4(promptSrc)) {
330
+ const content = readFileSync3(promptSrc, "utf-8");
331
+ const rulesFile = "rulesFile" in config ? config.rulesFile : "superspec.md";
332
+ const destPath = join4(rulesDir, rulesFile);
333
+ writeFileSync2(destPath, content, "utf-8");
334
+ log.success(`${symbol.ok} ${config.rules}/${rulesFile}`);
335
+ }
336
+ }
337
+ var SS_START = "<!-- superspec:start -->";
338
+ var SS_END = "<!-- superspec:end -->";
339
+ function installAgentsMd(cwd) {
340
+ const agentsMdPath = join4(cwd, "AGENTS.md");
341
+ const agentPromptSrc = join4(getPackageRoot(), "prompts", "agents.md");
342
+ if (!existsSync4(agentPromptSrc)) return;
343
+ const newContent = readFileSync3(agentPromptSrc, "utf-8");
344
+ const wrapped = `${SS_START}
345
+ ${newContent}
346
+ ${SS_END}`;
347
+ if (existsSync4(agentsMdPath)) {
348
+ const existing = readFileSync3(agentsMdPath, "utf-8");
349
+ const startIdx = existing.indexOf(SS_START);
350
+ const endIdx = existing.indexOf(SS_END);
351
+ if (startIdx !== -1 && endIdx !== -1) {
352
+ const before = existing.slice(0, startIdx);
353
+ const after = existing.slice(endIdx + SS_END.length);
354
+ writeFileSync2(agentsMdPath, before + wrapped + after, "utf-8");
355
+ } else if (existing.includes("SuperSpec")) {
356
+ writeFileSync2(agentsMdPath, existing, "utf-8");
357
+ } else {
358
+ writeFileSync2(agentsMdPath, existing + "\n\n" + wrapped, "utf-8");
359
+ }
360
+ } else {
361
+ writeFileSync2(agentsMdPath, wrapped, "utf-8");
362
+ }
363
+ log.success(`${symbol.ok} AGENTS.md`);
364
+ }
365
+ function installCommands(cwd, editor, lang = "zh") {
366
+ const config = AI_EDITORS[editor];
367
+ const commandsDir = join4(cwd, config.commands);
368
+ ensureDir(commandsDir);
369
+ const templatesDir = join4(getPackageRoot(), "templates", lang, "commands");
370
+ const fallbackDir = join4(getPackageRoot(), "templates", "zh", "commands");
371
+ const sourceDir = existsSync4(templatesDir) ? templatesDir : fallbackDir;
372
+ if (!existsSync4(sourceDir)) {
373
+ log.warn(`${symbol.warn} Commands templates not found: ${sourceDir}`);
374
+ return;
375
+ }
376
+ const commandFiles = readdirSync(sourceDir).filter((f) => f.endsWith(".md"));
377
+ for (const file of commandFiles) {
378
+ const srcPath = join4(sourceDir, file);
379
+ const destPath = join4(commandsDir, file);
380
+ const content = readFileSync3(srcPath, "utf-8");
381
+ writeFileSync2(destPath, content, "utf-8");
382
+ }
383
+ log.success(`${symbol.ok} ${config.commands}/ (${commandFiles.length} commands)`);
384
+ }
385
+
386
+ // src/commands/init.ts
387
+ async function initCommand(options) {
388
+ const cwd = process.cwd();
389
+ 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`);
392
+ return;
393
+ }
394
+ const lang = options.lang || "zh";
395
+ printLogo("small");
396
+ console.log(theme.dim(" Spec-Driven Development Toolkit\n"));
397
+ const config = getDefaultConfig();
398
+ config.lang = lang;
399
+ const aiEditor = options.ai;
400
+ if (aiEditor && AI_EDITORS[aiEditor]) {
401
+ config.aiEditor = aiEditor;
402
+ }
403
+ const specDir = join5(cwd, config.specDir);
404
+ const existingFiles = readdirSync2(cwd).filter((f) => !f.startsWith(".") && f !== "node_modules");
405
+ 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");
408
+ console.log();
409
+ }
410
+ log.section("Creating Configuration");
411
+ writeFileSync3(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
412
+ log.success(`${symbol.file} superspec.config.json`);
413
+ log.section("Creating Directory Structure");
414
+ ensureDir(join5(specDir, "changes"));
415
+ ensureDir(join5(specDir, "templates"));
416
+ log.success(`${symbol.folder} ${config.specDir}/changes/`);
417
+ 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);
422
+ }
423
+ log.success(`${symbol.ok} ${templates.length} templates (${lang})`);
424
+ log.section("Installing AI Agent Files");
425
+ installAgentsMd(cwd);
426
+ if (aiEditor && AI_EDITORS[aiEditor]) {
427
+ installRules(cwd, aiEditor);
428
+ installCommands(cwd, aiEditor, lang);
429
+ }
430
+ if (options.git !== false && !isGitRepo()) {
431
+ execSync2("git init", { cwd, stdio: "inherit" });
432
+ log.success(`${symbol.git} git init`);
433
+ }
434
+ console.log();
435
+ printSummary([
436
+ { label: "Config", value: "superspec.config.json" },
437
+ { label: "Spec dir", value: `${config.specDir}/` },
438
+ { label: "AI agent", value: options.ai },
439
+ { label: "Language", value: lang }
440
+ ]);
441
+ log.done("SuperSpec initialized successfully!");
442
+ log.dim("Next: Run superspec create <name> to create a change");
443
+ }
444
+
445
+ // src/commands/create.ts
446
+ import { existsSync as existsSync6 } from "fs";
447
+ import { join as join6 } from "path";
448
+
449
+ // src/utils/date.ts
450
+ function getDateString() {
451
+ const d = /* @__PURE__ */ new Date();
452
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
453
+ }
454
+
455
+ // src/commands/create.ts
456
+ async function createCommand(name, options) {
457
+ const cwd = process.cwd();
458
+ const config = loadConfig(cwd);
459
+ const specDir = options.specDir || config.specDir;
460
+ const branchPrefix = options.branchPrefix || config.branchPrefix;
461
+ const boost = options.boost || config.boost;
462
+ const strategy = options.creative ? "create" : config.strategy;
463
+ const lang = config.lang || "zh";
464
+ 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}`);
468
+ return;
469
+ }
470
+ log.title(`Creating Change: ${name}`);
471
+ if (boost) {
472
+ log.boost(`${symbol.bolt} Boost mode enabled`);
473
+ }
474
+ if (strategy === "create") {
475
+ log.boost(`${symbol.bolt} Creative mode: exploring new solutions`);
476
+ }
477
+ ensureDir(changePath);
478
+ const vars = {
479
+ name,
480
+ date: getDateString(),
481
+ boost: boost ? "true" : "false",
482
+ strategy,
483
+ description
484
+ };
485
+ const artifacts = boost ? config.boostArtifacts : config.artifacts;
486
+ log.section("Generating Artifacts");
487
+ for (const artifact of artifacts) {
488
+ const templateFile = config.templates[artifact] || `${artifact}.md`;
489
+ const destPath = join6(changePath, `${artifact}.md`);
490
+ try {
491
+ writeRenderedTemplate(templateFile, destPath, vars, lang);
492
+ log.success(`${symbol.ok} ${artifact}.md`);
493
+ } catch (e) {
494
+ log.error(`${symbol.fail} ${artifact}.md: ${e.message}`);
495
+ }
496
+ }
497
+ if (options.branch !== false && isGitRepo()) {
498
+ const branchTemplate = config.branchTemplate || "{prefix}{name}";
499
+ const branchName = branchTemplate.replace("{prefix}", branchPrefix).replace("{name}", name);
500
+ try {
501
+ createBranch(branchName);
502
+ log.success(`${symbol.ok} Branch: ${branchName}`);
503
+ } catch (e) {
504
+ log.warn(`${symbol.warn} Branch creation failed: ${e.message}`);
505
+ }
506
+ }
507
+ log.done("Change created successfully!");
508
+ log.dim(`Path: ${specDir}/changes/${name}/`);
509
+ if (boost) {
510
+ log.dim(`Workflow: /ss-create \u2192 /ss-tasks \u2192 /ss-apply (boost)`);
511
+ } else {
512
+ log.dim(`Workflow: /ss-tasks \u2192 /ss-apply`);
513
+ }
514
+ }
515
+
516
+ // src/commands/archive.ts
517
+ import { existsSync as existsSync7, readdirSync as readdirSync3, renameSync } from "fs";
518
+ import { join as join7 } from "path";
519
+ async function archiveCommand(name, options) {
520
+ const cwd = process.cwd();
521
+ const config = loadConfig(cwd);
522
+ const changesDir = join7(cwd, config.specDir, "changes");
523
+ 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`);
526
+ return;
527
+ }
528
+ if (options.all) {
529
+ const entries = readdirSync3(changesDir, { withFileTypes: true }).filter(
530
+ (e) => e.isDirectory() && e.name !== config.archive.dir
531
+ );
532
+ if (entries.length === 0) {
533
+ log.warn(`${symbol.warn} \u6CA1\u6709\u53EF\u5F52\u6863\u7684\u53D8\u66F4`);
534
+ return;
535
+ }
536
+ log.info(`${symbol.start} \u5F52\u6863\u6240\u6709\u53D8\u66F4...`);
537
+ for (const entry of entries) {
538
+ archiveOne(entry.name, changesDir, archiveDir, config);
539
+ }
540
+ } else if (name) {
541
+ const changePath = join7(changesDir, name);
542
+ if (!existsSync7(changePath)) {
543
+ log.warn(`${symbol.warn} \u53D8\u66F4 "${name}" \u4E0D\u5B58\u5728`);
544
+ return;
545
+ }
546
+ log.info(`${symbol.start} \u5F52\u6863\u53D8\u66F4: ${name}`);
547
+ archiveOne(name, changesDir, archiveDir, config);
548
+ } else {
549
+ log.warn(`${symbol.warn} \u8BF7\u6307\u5B9A\u53D8\u66F4\u540D\u79F0\u6216\u4F7F\u7528 --all`);
550
+ return;
551
+ }
552
+ log.info(`${symbol.start} \u5F52\u6863\u5B8C\u6210\uFF01`);
553
+ }
554
+ function archiveOne(name, changesDir, archiveDir, config) {
555
+ ensureDir(archiveDir);
556
+ const src = join7(changesDir, name);
557
+ const dateStr = config.archive.datePrefix ? `${getDateString()}-` : "";
558
+ const dest = join7(archiveDir, `${dateStr}${name}`);
559
+ if (existsSync7(dest)) {
560
+ log.warn(` ${symbol.warn} \u5F52\u6863\u76EE\u6807\u5DF2\u5B58\u5728: ${dest}`);
561
+ return;
562
+ }
563
+ renameSync(src, dest);
564
+ log.success(` ${symbol.ok} ${name} \u2192 archive/${dateStr}${name}`);
565
+ }
566
+
567
+ // src/commands/update.ts
568
+ import { existsSync as existsSync8 } from "fs";
569
+ import { join as join8 } from "path";
570
+ async function updateCommand() {
571
+ const cwd = process.cwd();
572
+ const config = loadConfig(cwd);
573
+ const specDir = join8(cwd, config.specDir);
574
+ 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`);
577
+ return;
578
+ }
579
+ log.info(`${symbol.start} \u66F4\u65B0 SuperSpec...`);
580
+ const templates = ["spec.md", "proposal.md", "tasks.md", "clarify.md", "checklist.md"];
581
+ ensureDir(join8(specDir, "templates"));
582
+ for (const tpl of templates) {
583
+ copyTemplate(tpl, join8(specDir, "templates", tpl), lang);
584
+ }
585
+ log.success(` ${symbol.ok} \u6A21\u677F\u66F4\u65B0 (${lang})`);
586
+ installAgentsMd(cwd);
587
+ const aiEditor = config.aiEditor;
588
+ if (aiEditor && AI_EDITORS[aiEditor]) {
589
+ installRules(cwd, aiEditor);
590
+ installCommands(cwd, aiEditor, lang);
591
+ }
592
+ log.info(`${symbol.start} \u66F4\u65B0\u5B8C\u6210\uFF01`);
593
+ }
594
+
595
+ // src/commands/lint.ts
596
+ import { existsSync as existsSync10, readdirSync as readdirSync5 } from "fs";
597
+ import { join as join10 } from "path";
598
+
599
+ // src/core/lint.ts
600
+ import { readFileSync as readFileSync4, existsSync as existsSync9, readdirSync as readdirSync4 } from "fs";
601
+ import { join as join9, basename } from "path";
602
+ function lintArtifact(filePath, targetLines, hardLines) {
603
+ const artifact = basename(filePath);
604
+ if (!existsSync9(filePath)) {
605
+ return { artifact, lines: 0, status: "ok", message: "not found" };
606
+ }
607
+ const content = readFileSync4(filePath, "utf-8");
608
+ const lines = content.split("\n").length;
609
+ if (lines > hardLines) {
610
+ return { artifact, lines, status: "error", message: `${lines} lines exceeds hard limit (${hardLines}). Must split.` };
611
+ }
612
+ if (lines > targetLines) {
613
+ return { artifact, lines, status: "warn", message: `${lines} lines exceeds target (${targetLines}). Consider splitting.` };
614
+ }
615
+ return { artifact, lines, status: "ok", message: `${lines} lines` };
616
+ }
617
+ function lintChange(changePath, targetLines, hardLines) {
618
+ if (!existsSync9(changePath)) return [];
619
+ const files = readdirSync4(changePath).filter((f) => f.endsWith(".md"));
620
+ return files.map((f) => lintArtifact(join9(changePath, f), targetLines, hardLines));
621
+ }
622
+
623
+ // src/commands/lint.ts
624
+ async function lintCommand(name) {
625
+ const cwd = process.cwd();
626
+ const config = loadConfig(cwd);
627
+ const changesDir = join10(cwd, config.specDir, "changes");
628
+ 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
+ }
640
+ if (names.length === 0) {
641
+ log.warn(`${symbol.warn} no changes to lint`);
642
+ return;
643
+ }
644
+ let hasIssues = false;
645
+ for (const n of names) {
646
+ const changePath = join10(changesDir, n);
647
+ if (!existsSync10(changePath)) {
648
+ log.warn(`${symbol.warn} "${n}" not found`);
649
+ continue;
650
+ }
651
+ const results = lintChange(changePath, targetLines, hardLines);
652
+ log.info(`${symbol.start} ${n}`);
653
+ for (const r of results) {
654
+ if (r.status === "error") {
655
+ log.error(` ${symbol.fail} ${r.artifact}: ${r.message}`);
656
+ hasIssues = true;
657
+ } else if (r.status === "warn") {
658
+ log.warn(` ${symbol.warn} ${r.artifact}: ${r.message}`);
659
+ hasIssues = true;
660
+ } else {
661
+ log.success(` ${symbol.ok} ${r.artifact}: ${r.message}`);
662
+ }
663
+ }
664
+ }
665
+ if (!hasIssues) {
666
+ log.info(`${symbol.start} all artifacts within limits`);
667
+ }
668
+ }
669
+
670
+ // src/commands/validate.ts
671
+ import { existsSync as existsSync12, readdirSync as readdirSync7 } from "fs";
672
+ import { join as join12 } from "path";
673
+
674
+ // src/core/validate.ts
675
+ import { readFileSync as readFileSync5, existsSync as existsSync11 } from "fs";
676
+ import { join as join11 } from "path";
677
+
678
+ // src/core/frontmatter.ts
679
+ var FM_REGEX = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/;
680
+ function parseFrontmatter(content) {
681
+ const match = content.match(FM_REGEX);
682
+ if (!match) {
683
+ return { meta: {}, body: content };
684
+ }
685
+ const meta = {};
686
+ const lines = match[1].split("\n");
687
+ for (const line of lines) {
688
+ const idx = line.indexOf(":");
689
+ if (idx === -1) continue;
690
+ const key = line.slice(0, idx).trim();
691
+ let value = line.slice(idx + 1).trim();
692
+ if (value === "[]") {
693
+ value = [];
694
+ } else if (value.startsWith("[") && value.endsWith("]")) {
695
+ value = value.slice(1, -1).split(",").map((s) => s.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
696
+ } else if (value === "true") {
697
+ value = true;
698
+ } else if (value === "false") {
699
+ value = false;
700
+ }
701
+ meta[key] = value;
702
+ }
703
+ return { meta, body: match[2] };
704
+ }
705
+ function serializeFrontmatter(meta) {
706
+ const lines = ["---"];
707
+ for (const [key, value] of Object.entries(meta)) {
708
+ if (Array.isArray(value)) {
709
+ if (value.length === 0) {
710
+ lines.push(`${key}: []`);
711
+ } else {
712
+ lines.push(`${key}: [${value.map((v) => `"${v}"`).join(", ")}]`);
713
+ }
714
+ } else {
715
+ lines.push(`${key}: ${value}`);
716
+ }
717
+ }
718
+ lines.push("---");
719
+ return lines.join("\n");
720
+ }
721
+ function addDependency(content, depName) {
722
+ 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);
726
+ }
727
+ meta.depends_on = deps;
728
+ return serializeFrontmatter(meta) + "\n" + body;
729
+ }
730
+ function removeDependency(content, depName) {
731
+ 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);
734
+ return serializeFrontmatter(meta) + "\n" + body;
735
+ }
736
+
737
+ // src/core/validate.ts
738
+ function extractIds(content, pattern) {
739
+ const matches = content.match(pattern);
740
+ return matches ? [...new Set(matches)] : [];
741
+ }
742
+ function validateChange(changePath, checkDeps = false) {
743
+ const issues = [];
744
+ const read = (name) => {
745
+ const p = join11(changePath, name);
746
+ return existsSync11(p) ? readFileSync5(p, "utf-8") : null;
747
+ };
748
+ const proposal = read("proposal.md");
749
+ const spec = read("spec.md");
750
+ const tasks = read("tasks.md");
751
+ if (!proposal) issues.push({ level: "warn", artifact: "proposal.md", message: "missing" });
752
+ if (!tasks) issues.push({ level: "warn", artifact: "tasks.md", message: "missing" });
753
+ if (proposal && spec) {
754
+ const proposalGoals = extractIds(proposal, /US-\d+/g);
755
+ const specUS = extractIds(spec, /US-\d+/g);
756
+ for (const id of proposalGoals) {
757
+ if (!specUS.includes(id)) {
758
+ issues.push({ level: "error", artifact: "spec.md", message: `${id} from proposal not found in spec` });
759
+ }
760
+ }
761
+ }
762
+ if (spec && tasks) {
763
+ const specFR = extractIds(spec, /FR-\d+/g);
764
+ const tasksFR = extractIds(tasks, /FR-\d+/g);
765
+ for (const id of specFR) {
766
+ if (!tasksFR.includes(id)) {
767
+ issues.push({ level: "warn", artifact: "tasks.md", message: `${id} from spec not referenced in tasks` });
768
+ }
769
+ }
770
+ for (const id of tasksFR) {
771
+ if (!specFR.includes(id)) {
772
+ issues.push({ level: "error", artifact: "tasks.md", message: `${id} referenced but not defined in spec` });
773
+ }
774
+ }
775
+ }
776
+ if (spec) {
777
+ const acIds = extractIds(spec, /AC-\d+\.\d+/g);
778
+ const frIds = extractIds(spec, /FR-\d+/g);
779
+ for (const ac of acIds) {
780
+ const frNum = ac.replace("AC-", "").split(".")[0];
781
+ const parentFR = `FR-${frNum}`;
782
+ if (!frIds.includes(parentFR)) {
783
+ issues.push({ level: "warn", artifact: "spec.md", message: `${ac} has no parent ${parentFR}` });
784
+ }
785
+ }
786
+ }
787
+ if (checkDeps && proposal) {
788
+ const { meta, body } = parseFrontmatter(proposal);
789
+ const fmDeps = Array.isArray(meta.depends_on) ? meta.depends_on : [];
790
+ const contentRefs = extractIds(body, /depends[_ ]on[:\s]+(\S+)/gi);
791
+ const mentioned = contentRefs.map((m) => m.replace(/depends[_ ]on[:\s]+/i, ""));
792
+ for (const dep of mentioned) {
793
+ if (!fmDeps.includes(dep)) {
794
+ issues.push({ level: "warn", artifact: "proposal.md", message: `content mentions "${dep}" but not in frontmatter depends_on` });
795
+ }
796
+ }
797
+ const changesDir = join11(changePath, "..");
798
+ for (const dep of fmDeps) {
799
+ if (!existsSync11(join11(changesDir, dep))) {
800
+ issues.push({ level: "error", artifact: "proposal.md", message: `depends_on "${dep}" not found in changes` });
801
+ }
802
+ }
803
+ }
804
+ return issues;
805
+ }
806
+
807
+ // src/commands/validate.ts
808
+ async function validateCommand(name, options) {
809
+ const cwd = process.cwd();
810
+ const config = loadConfig(cwd);
811
+ 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
+ }
823
+ if (names.length === 0) {
824
+ log.warn(`${symbol.warn} no changes to validate`);
825
+ return;
826
+ }
827
+ let totalIssues = 0;
828
+ for (const n of names) {
829
+ const changePath = join12(changesDir, n);
830
+ if (!existsSync12(changePath)) {
831
+ log.warn(`${symbol.warn} "${n}" not found`);
832
+ continue;
833
+ }
834
+ const issues = validateChange(changePath, options.checkDeps);
835
+ log.info(`${symbol.start} ${n}`);
836
+ if (issues.length === 0) {
837
+ log.success(` ${symbol.ok} all checks passed`);
838
+ } else {
839
+ for (const issue of issues) {
840
+ totalIssues++;
841
+ if (issue.level === "error") {
842
+ log.error(` ${symbol.fail} [${issue.artifact}] ${issue.message}`);
843
+ } else if (issue.level === "warn") {
844
+ log.warn(` ${symbol.warn} [${issue.artifact}] ${issue.message}`);
845
+ } else {
846
+ log.dim(` \u2139 [${issue.artifact}] ${issue.message}`);
847
+ }
848
+ }
849
+ }
850
+ }
851
+ if (totalIssues === 0) {
852
+ log.success(`${symbol.ok} all validations passed`);
853
+ } else {
854
+ log.warn(`${symbol.warn} ${totalIssues} issue(s) found`);
855
+ }
856
+ }
857
+
858
+ // src/commands/search.ts
859
+ import { existsSync as existsSync13, readFileSync as readFileSync6, readdirSync as readdirSync8 } from "fs";
860
+ import { join as join13, basename as basename3 } from "path";
861
+ async function searchCommand(query, options) {
862
+ const cwd = process.cwd();
863
+ const config = loadConfig(cwd);
864
+ const changesDir = join13(cwd, config.specDir, "changes");
865
+ if (!existsSync13(changesDir)) {
866
+ log.warn(`${symbol.warn} no changes directory found`);
867
+ return;
868
+ }
869
+ const dirs = [];
870
+ const activeEntries = readdirSync8(changesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name !== config.archive.dir);
871
+ for (const e of activeEntries) {
872
+ dirs.push({ name: e.name, path: join13(changesDir, e.name) });
873
+ }
874
+ if (options.archived) {
875
+ const archiveDir = join13(changesDir, config.archive.dir);
876
+ if (existsSync13(archiveDir)) {
877
+ const archivedEntries = readdirSync8(archiveDir, { withFileTypes: true }).filter((e) => e.isDirectory());
878
+ for (const e of archivedEntries) {
879
+ dirs.push({ name: `${config.archive.dir}/${e.name}`, path: join13(archiveDir, e.name) });
880
+ }
881
+ }
882
+ }
883
+ const queryLower = query.toLowerCase();
884
+ const hits = [];
885
+ for (const dir of dirs) {
886
+ if (!existsSync13(dir.path)) continue;
887
+ const files = readdirSync8(dir.path).filter((f) => f.endsWith(".md"));
888
+ for (const file of files) {
889
+ if (options.artifact) {
890
+ const artType = basename3(file, ".md");
891
+ if (artType !== options.artifact) continue;
892
+ }
893
+ const filePath = join13(dir.path, file);
894
+ const content = readFileSync6(filePath, "utf-8");
895
+ const lines = content.split("\n");
896
+ for (let i = 0; i < lines.length; i++) {
897
+ if (lines[i].toLowerCase().includes(queryLower)) {
898
+ hits.push({
899
+ change: dir.name,
900
+ artifact: file,
901
+ line: i + 1,
902
+ text: lines[i].trim()
903
+ });
904
+ }
905
+ }
906
+ }
907
+ }
908
+ if (hits.length === 0) {
909
+ log.warn(`${symbol.warn} no results for "${query}"`);
910
+ return;
911
+ }
912
+ log.info(`${symbol.start} ${hits.length} result(s) for "${query}"`);
913
+ for (const hit of hits) {
914
+ log.dim(` ${hit.change}/${hit.artifact}:${hit.line} ${hit.text}`);
915
+ }
916
+ }
917
+
918
+ // src/commands/link.ts
919
+ import { existsSync as existsSync14, readFileSync as readFileSync7, writeFileSync as writeFileSync4, readdirSync as readdirSync9 } from "fs";
920
+ import { join as join14 } from "path";
921
+ async function linkCommand(name, options) {
922
+ const cwd = process.cwd();
923
+ const config = loadConfig(cwd);
924
+ const proposalPath = join14(cwd, config.specDir, "changes", name, "proposal.md");
925
+ if (!existsSync14(proposalPath)) {
926
+ log.warn(`${symbol.warn} "${name}" not found`);
927
+ return;
928
+ }
929
+ const content = readFileSync7(proposalPath, "utf-8");
930
+ const updated = addDependency(content, options.dependsOn);
931
+ writeFileSync4(proposalPath, updated, "utf-8");
932
+ log.success(`${symbol.ok} ${name} \u2192 depends_on: ${options.dependsOn}`);
933
+ }
934
+ async function unlinkCommand(name, options) {
935
+ const cwd = process.cwd();
936
+ const config = loadConfig(cwd);
937
+ const proposalPath = join14(cwd, config.specDir, "changes", name, "proposal.md");
938
+ if (!existsSync14(proposalPath)) {
939
+ log.warn(`${symbol.warn} "${name}" not found`);
940
+ return;
941
+ }
942
+ const content = readFileSync7(proposalPath, "utf-8");
943
+ const updated = removeDependency(content, options.dependsOn);
944
+ writeFileSync4(proposalPath, updated, "utf-8");
945
+ log.success(`${symbol.ok} ${name} \u2192 removed dependency: ${options.dependsOn}`);
946
+ }
947
+ async function depsCommand(name) {
948
+ const cwd = process.cwd();
949
+ const config = loadConfig(cwd);
950
+ const changesDir = join14(cwd, config.specDir, "changes");
951
+ if (name) {
952
+ const proposalPath = join14(changesDir, name, "proposal.md");
953
+ if (!existsSync14(proposalPath)) {
954
+ log.warn(`${symbol.warn} "${name}" not found`);
955
+ return;
956
+ }
957
+ const content = readFileSync7(proposalPath, "utf-8");
958
+ const { meta } = parseFrontmatter(content);
959
+ const deps = Array.isArray(meta.depends_on) ? meta.depends_on : [];
960
+ log.info(`${symbol.start} ${name}`);
961
+ if (deps.length === 0) {
962
+ log.dim(" no dependencies");
963
+ } else {
964
+ for (const d of deps) {
965
+ log.dim(` \u2192 ${d}`);
966
+ }
967
+ }
968
+ return;
969
+ }
970
+ if (!existsSync14(changesDir)) {
971
+ log.warn(`${symbol.warn} no changes directory`);
972
+ return;
973
+ }
974
+ const entries = readdirSync9(changesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name !== config.archive.dir);
975
+ log.info(`${symbol.start} dependency graph`);
976
+ for (const entry of entries) {
977
+ const proposalPath = join14(changesDir, entry.name, "proposal.md");
978
+ if (!existsSync14(proposalPath)) continue;
979
+ const content = readFileSync7(proposalPath, "utf-8");
980
+ 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(", ")}]`);
984
+ } else {
985
+ log.dim(` ${entry.name}`);
986
+ }
987
+ }
988
+ }
989
+
990
+ // src/commands/status.ts
991
+ import { existsSync as existsSync15, readFileSync as readFileSync8, readdirSync as readdirSync10 } from "fs";
992
+ import { join as join15 } from "path";
993
+ var ARTIFACT_TYPES = ["proposal", "spec", "tasks", "clarify", "checklist"];
994
+ function readStatus(changePath, artifact) {
995
+ const filePath = join15(changePath, `${artifact}.md`);
996
+ if (!existsSync15(filePath)) return "\u2014";
997
+ const content = readFileSync8(filePath, "utf-8");
998
+ const { meta } = parseFrontmatter(content);
999
+ if (meta.status) return meta.status;
1000
+ if (content.includes("\u2705")) return "done";
1001
+ if (content.includes("\u{1F7E2}")) return "ready";
1002
+ return "draft";
1003
+ }
1004
+ function statusIcon(s) {
1005
+ if (s === "\u2014") return "\u2014";
1006
+ if (s === "done" || s === "complete") return "\u2705";
1007
+ if (s === "ready") return "\u{1F7E2}";
1008
+ return "\u{1F7E1}";
1009
+ }
1010
+ function overallStatus(statuses) {
1011
+ const vals = Object.values(statuses).filter((v) => v !== "\u2014");
1012
+ if (vals.length === 0) return "empty";
1013
+ if (vals.every((v) => v === "done" || v === "complete")) return "\u2705 Done";
1014
+ if (vals.every((v) => v === "ready" || v === "done" || v === "complete")) return "\u{1F7E2} Ready";
1015
+ return "\u{1F7E1} Draft";
1016
+ }
1017
+ async function statusCommand() {
1018
+ const cwd = process.cwd();
1019
+ const config = loadConfig(cwd);
1020
+ const changesDir = join15(cwd, config.specDir, "changes");
1021
+ if (!existsSync15(changesDir)) {
1022
+ log.warn(`${symbol.warn} no changes directory found`);
1023
+ return;
1024
+ }
1025
+ const entries = readdirSync10(changesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name !== config.archive.dir).sort((a, b) => a.name.localeCompare(b.name));
1026
+ if (entries.length === 0) {
1027
+ log.dim(" no active changes");
1028
+ return;
1029
+ }
1030
+ const header = ["Change", ...ARTIFACT_TYPES.map((a) => a.slice(0, 8).padEnd(8)), "Status"];
1031
+ const rows = [];
1032
+ for (const entry of entries) {
1033
+ const changePath = join15(changesDir, entry.name);
1034
+ const statuses = {};
1035
+ for (const art of ARTIFACT_TYPES) {
1036
+ statuses[art] = readStatus(changePath, art);
1037
+ }
1038
+ rows.push([
1039
+ entry.name,
1040
+ ...ARTIFACT_TYPES.map((a) => statusIcon(statuses[a])),
1041
+ overallStatus(statuses)
1042
+ ]);
1043
+ }
1044
+ const colWidths = header.map((h, i) => {
1045
+ const maxData = rows.reduce((max, row) => Math.max(max, stripAnsi(row[i]).length), 0);
1046
+ return Math.max(stripAnsi(h).length, maxData);
1047
+ });
1048
+ const divider = colWidths.map((w) => "-".repeat(w + 2)).join("+");
1049
+ const formatRow = (row) => row.map((cell, i) => ` ${cell.padEnd(colWidths[i])} `).join("|");
1050
+ log.info(`${symbol.start} changes`);
1051
+ console.log(formatRow(header));
1052
+ console.log(divider);
1053
+ for (const row of rows) {
1054
+ console.log(formatRow(row));
1055
+ }
1056
+ const archiveDir = join15(changesDir, config.archive.dir);
1057
+ if (existsSync15(archiveDir)) {
1058
+ const archived = readdirSync10(archiveDir, { withFileTypes: true }).filter((e) => e.isDirectory());
1059
+ if (archived.length > 0) {
1060
+ log.dim(`
1061
+ ${archived.length} archived change(s)`);
1062
+ }
1063
+ }
1064
+ }
1065
+ function stripAnsi(str) {
1066
+ return str.replace(/\u001b\[[0-9;]*m/g, "").replace(/[✅🟢🟡]/g, "XX");
1067
+ }
1068
+
1069
+ // src/commands/context.ts
1070
+ import { existsSync as existsSync17, writeFileSync as writeFileSync5, readdirSync as readdirSync11 } from "fs";
1071
+ import { join as join17 } from "path";
1072
+
1073
+ // src/core/context.ts
1074
+ import { readFileSync as readFileSync9, existsSync as existsSync16 } from "fs";
1075
+ import { join as join16 } from "path";
1076
+ function extractSection(body, heading) {
1077
+ const regex = new RegExp(`^##\\s+${heading}[\\s\\S]*?$`, "im");
1078
+ const match = body.match(regex);
1079
+ if (!match) return [];
1080
+ const startIdx = body.indexOf(match[0]) + match[0].length;
1081
+ const rest = body.slice(startIdx);
1082
+ const nextSection = rest.match(/^##\s+/m);
1083
+ const sectionContent = nextSection ? rest.slice(0, nextSection.index) : rest;
1084
+ return sectionContent.split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith("<!--"));
1085
+ }
1086
+ function parseTaskItems(body) {
1087
+ const lines = body.split("\n");
1088
+ const items = [];
1089
+ let done = 0;
1090
+ let total = 0;
1091
+ for (const line of lines) {
1092
+ const trimmed = line.trim();
1093
+ const checked = trimmed.match(/^-\s*\[x\]\s+(.*)/i);
1094
+ const unchecked = trimmed.match(/^-\s*\[\s\]\s+(.*)/);
1095
+ if (checked) {
1096
+ total++;
1097
+ done++;
1098
+ const desc = checked[1].trim();
1099
+ items.push(`- [x] ${desc}`);
1100
+ } else if (unchecked) {
1101
+ total++;
1102
+ const desc = unchecked[1].trim();
1103
+ items.push(`- [ ] ${desc}`);
1104
+ }
1105
+ }
1106
+ return { total, done, items };
1107
+ }
1108
+ function extractFilePaths(body) {
1109
+ const paths = /* @__PURE__ */ new Set();
1110
+ const regex = /`([^`]+\.[a-zA-Z]+)`/g;
1111
+ let match;
1112
+ while ((match = regex.exec(body)) !== null) {
1113
+ const p = match[1];
1114
+ if (p.includes("/") && !p.startsWith("http") && !p.includes(" ")) {
1115
+ paths.add(p);
1116
+ }
1117
+ }
1118
+ return [...paths];
1119
+ }
1120
+ function extractDecisions(body) {
1121
+ const lines = body.split("\n");
1122
+ const decisions = [];
1123
+ for (const line of lines) {
1124
+ const trimmed = line.trim();
1125
+ if (/^\|?\s*D\d+\s*\|/.test(trimmed)) {
1126
+ const cells = trimmed.split("|").map((c) => c.trim()).filter(Boolean);
1127
+ if (cells.length >= 2) {
1128
+ decisions.push(`- ${cells[0]}: ${cells[1]}`);
1129
+ }
1130
+ }
1131
+ }
1132
+ return decisions;
1133
+ }
1134
+ var STATUS_LABELS = {
1135
+ A: "added",
1136
+ M: "modified",
1137
+ D: "deleted",
1138
+ R: "renamed"
1139
+ };
1140
+ function classifyGitChanges(gitChanges, taskFiles) {
1141
+ const taskFileSet = new Set(taskFiles);
1142
+ const lines = [];
1143
+ for (const { status, file } of gitChanges) {
1144
+ const label = STATUS_LABELS[status] || status;
1145
+ const inTasks = taskFileSet.has(file) || taskFiles.some((tf) => file.endsWith(tf) || tf.endsWith(file));
1146
+ const tag = inTasks ? "" : " (unplanned)";
1147
+ lines.push(`- ${label}: ${file}${tag}`);
1148
+ }
1149
+ return lines;
1150
+ }
1151
+ function generateContext(changePath, changeName, options = {}) {
1152
+ const read = (name) => {
1153
+ const p = join16(changePath, name);
1154
+ return existsSync16(p) ? readFileSync9(p, "utf-8") : null;
1155
+ };
1156
+ const proposal = read("proposal.md");
1157
+ const spec = read("spec.md");
1158
+ const tasks = read("tasks.md");
1159
+ const clarify = read("clarify.md");
1160
+ let strategy = "follow";
1161
+ let status = "in-progress";
1162
+ const mode = spec ? "boost" : "standard";
1163
+ if (proposal) {
1164
+ const { meta } = parseFrontmatter(proposal);
1165
+ if (meta.strategy) strategy = meta.strategy;
1166
+ if (meta.status) status = meta.status;
1167
+ }
1168
+ const goals = [];
1169
+ if (proposal) {
1170
+ const { body } = parseFrontmatter(proposal);
1171
+ const goalLines = extractSection(body, "\u76EE\u6807|Goals");
1172
+ for (const line of goalLines) {
1173
+ const cleaned = line.replace(/^-\s*\[.\]\s*/, "- ").replace(/^-\s*/, "");
1174
+ if (cleaned) goals.push(`- ${cleaned}`);
1175
+ }
1176
+ }
1177
+ let progress = { total: 0, done: 0, items: [] };
1178
+ let files = [];
1179
+ if (tasks) {
1180
+ const { body } = parseFrontmatter(tasks);
1181
+ progress = parseTaskItems(body);
1182
+ files = extractFilePaths(body);
1183
+ }
1184
+ const decisions = [];
1185
+ if (clarify) {
1186
+ const { body } = parseFrontmatter(clarify);
1187
+ decisions.push(...extractDecisions(body));
1188
+ }
1189
+ const existingContext = read("context.md");
1190
+ let notes = "";
1191
+ if (existingContext) {
1192
+ const { body } = parseFrontmatter(existingContext);
1193
+ const notesSection = extractSection(body, "Notes");
1194
+ if (notesSection.length > 0) {
1195
+ notes = notesSection.join("\n");
1196
+ }
1197
+ }
1198
+ const fm = serializeFrontmatter({
1199
+ name: changeName,
1200
+ status,
1201
+ strategy,
1202
+ mode,
1203
+ updated: getDateString()
1204
+ });
1205
+ const lines = [fm, ""];
1206
+ if (goals.length > 0) {
1207
+ lines.push("## Goals", ...goals, "");
1208
+ }
1209
+ if (progress.total > 0) {
1210
+ lines.push(`## Progress (${progress.done}/${progress.total} tasks)`);
1211
+ lines.push(...progress.items, "");
1212
+ }
1213
+ if (decisions.length > 0) {
1214
+ lines.push("## Decisions", ...decisions, "");
1215
+ }
1216
+ if (files.length > 0) {
1217
+ lines.push("## Affected Files");
1218
+ for (const f of files) {
1219
+ lines.push(`- ${f}`);
1220
+ }
1221
+ lines.push("");
1222
+ }
1223
+ if (options.gitDiff !== false) {
1224
+ const gitChanges = getDiffFiles(options.baseBranch);
1225
+ if (gitChanges.length > 0) {
1226
+ const classified = classifyGitChanges(gitChanges, files);
1227
+ lines.push("## Git Changes", ...classified, "");
1228
+ }
1229
+ }
1230
+ lines.push("## Notes");
1231
+ if (notes) {
1232
+ lines.push(notes);
1233
+ }
1234
+ lines.push("");
1235
+ return lines.join("\n");
1236
+ }
1237
+
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
+ // 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
+ async function syncCommand(name, opts) {
1275
+ const cwd = process.cwd();
1276
+ 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
+ }
1289
+ if (names.length === 0) {
1290
+ log.warn(`${symbol.warn} no changes found`);
1291
+ return;
1292
+ }
1293
+ for (const n of names) {
1294
+ const changePath = join18(changesDir, n);
1295
+ if (!existsSync18(changePath)) {
1296
+ log.warn(`${symbol.warn} "${n}" not found`);
1297
+ continue;
1298
+ }
1299
+ const content = generateContext(changePath, n, {
1300
+ gitDiff: true,
1301
+ baseBranch: opts.base
1302
+ });
1303
+ const destPath = join18(changePath, "context.md");
1304
+ writeFileSync6(destPath, content, "utf-8");
1305
+ log.success(`${symbol.ok} synced ${n}/context.md`);
1306
+ }
1307
+ }
1308
+
1309
+ // src/cli/index.ts
1310
+ var require2 = createRequire(import.meta.url);
1311
+ var pkg = require2("../../package.json");
1312
+ var isZh = loadConfig().lang === "zh";
1313
+ var t = (en, zh) => isZh ? zh : en;
1314
+ 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
+ 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);
1317
+ 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
+ program.command("update").description(t("Refresh agent instructions and templates", "\u5237\u65B0 agent \u6307\u4EE4\u548C\u6A21\u677F")).action(updateCommand);
1319
+ program.command("lint [name]").description(t("Check artifact line limits", "\u68C0\u67E5 artifact \u884C\u6570\u662F\u5426\u8D85\u9650")).action(lintCommand);
1320
+ 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);
1325
+ 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);
1328
+ program.parse();
1329
+ //# sourceMappingURL=index.js.map