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