@superspec/cli 1.1.1 → 1.2.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/LICENSE +21 -0
- package/README.md +374 -0
- package/dist/cli/index.js +763 -683
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +30 -30
- package/dist/index.js +275 -301
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/prompts/agents.md +35 -17
- package/prompts/rules.md +48 -27
- package/templates/en/checklist.md +31 -8
- package/templates/en/commands/ss-checklist.md +11 -6
- package/templates/en/commands/ss-create.md +35 -9
- package/templates/en/commands/ss-resume.md +18 -5
- package/templates/en/commands/ss-tasks.md +3 -2
- package/templates/en/proposal.md +14 -2
- package/templates/zh/checklist.md +31 -8
- package/templates/zh/commands/ss-checklist.md +11 -6
- package/templates/zh/commands/ss-create.md +34 -8
- package/templates/zh/commands/ss-resume.md +18 -5
- package/templates/zh/commands/ss-tasks.md +3 -2
- package/templates/zh/proposal.md +14 -2
package/dist/index.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
// src/commands/archive.ts
|
|
2
|
+
import { existsSync as existsSync3, readdirSync as readdirSync2, renameSync } from "fs";
|
|
3
|
+
import { join as join2 } from "path";
|
|
4
|
+
|
|
1
5
|
// src/core/config.ts
|
|
2
|
-
import {
|
|
6
|
+
import { existsSync, readFileSync } from "fs";
|
|
3
7
|
import { join } from "path";
|
|
4
8
|
var DEFAULT_CONFIG = {
|
|
5
9
|
lang: "en",
|
|
@@ -61,120 +65,6 @@ function deepMerge(target, source) {
|
|
|
61
65
|
return result;
|
|
62
66
|
}
|
|
63
67
|
|
|
64
|
-
// src/core/template.ts
|
|
65
|
-
import { existsSync as existsSync4, copyFileSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
66
|
-
import { join as join3, dirname as dirname2 } from "path";
|
|
67
|
-
|
|
68
|
-
// src/utils/paths.ts
|
|
69
|
-
import { dirname, join as join2 } from "path";
|
|
70
|
-
import { existsSync as existsSync2 } from "fs";
|
|
71
|
-
import { fileURLToPath } from "url";
|
|
72
|
-
function getPackageRoot() {
|
|
73
|
-
const __filename2 = fileURLToPath(import.meta.url);
|
|
74
|
-
let dir = dirname(__filename2);
|
|
75
|
-
while (dir !== dirname(dir)) {
|
|
76
|
-
if (existsSync2(join2(dir, "package.json")) && existsSync2(join2(dir, "templates"))) {
|
|
77
|
-
return dir;
|
|
78
|
-
}
|
|
79
|
-
dir = dirname(dir);
|
|
80
|
-
}
|
|
81
|
-
return join2(dirname(__filename2), "..", "..");
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// src/utils/fs.ts
|
|
85
|
-
import { mkdirSync, existsSync as existsSync3, readdirSync } from "fs";
|
|
86
|
-
function ensureDir(dir) {
|
|
87
|
-
if (!existsSync3(dir)) {
|
|
88
|
-
mkdirSync(dir, { recursive: true });
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// src/core/template.ts
|
|
93
|
-
function resolveTemplatePath(templateName, lang = "zh") {
|
|
94
|
-
const root = getPackageRoot();
|
|
95
|
-
const langPath = join3(root, "templates", lang, templateName);
|
|
96
|
-
if (existsSync4(langPath)) return langPath;
|
|
97
|
-
const fallbackLang = lang === "zh" ? "en" : "zh";
|
|
98
|
-
const fallback = join3(root, "templates", fallbackLang, templateName);
|
|
99
|
-
if (existsSync4(fallback)) return fallback;
|
|
100
|
-
throw new Error(`Template not found: ${templateName} (lang: ${lang})`);
|
|
101
|
-
}
|
|
102
|
-
function copyTemplate(templateName, destPath, lang = "zh") {
|
|
103
|
-
const srcPath = resolveTemplatePath(templateName, lang);
|
|
104
|
-
ensureDir(dirname2(destPath));
|
|
105
|
-
copyFileSync(srcPath, destPath);
|
|
106
|
-
}
|
|
107
|
-
function renderTemplate(templateName, vars = {}, lang = "zh") {
|
|
108
|
-
const srcPath = resolveTemplatePath(templateName, lang);
|
|
109
|
-
let content = readFileSync2(srcPath, "utf-8");
|
|
110
|
-
content = processConditionals(content, vars);
|
|
111
|
-
for (const [key, value] of Object.entries(vars)) {
|
|
112
|
-
content = content.replaceAll(`{{${key}}}`, value);
|
|
113
|
-
}
|
|
114
|
-
return content;
|
|
115
|
-
}
|
|
116
|
-
function processConditionals(content, vars) {
|
|
117
|
-
const ifRegex = /\{\{#if\s+(\w+)\}\}([\s\S]*?)\{\{\/if\}\}/g;
|
|
118
|
-
return content.replace(ifRegex, (match, varName, innerContent) => {
|
|
119
|
-
const value = vars[varName];
|
|
120
|
-
if (value && value.trim() !== "") {
|
|
121
|
-
return innerContent;
|
|
122
|
-
}
|
|
123
|
-
return "";
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
function writeRenderedTemplate(templateName, destPath, vars = {}, lang = "zh") {
|
|
127
|
-
const content = renderTemplate(templateName, vars, lang);
|
|
128
|
-
ensureDir(dirname2(destPath));
|
|
129
|
-
writeFileSync(destPath, content, "utf-8");
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// src/core/lint.ts
|
|
133
|
-
import { readFileSync as readFileSync3, existsSync as existsSync5, readdirSync as readdirSync2 } from "fs";
|
|
134
|
-
import { join as join4, basename } from "path";
|
|
135
|
-
|
|
136
|
-
// src/core/validate.ts
|
|
137
|
-
import { readFileSync as readFileSync4, existsSync as existsSync6 } from "fs";
|
|
138
|
-
import { join as join5 } from "path";
|
|
139
|
-
|
|
140
|
-
// src/core/context.ts
|
|
141
|
-
import { readFileSync as readFileSync5, existsSync as existsSync7 } from "fs";
|
|
142
|
-
import { join as join6 } from "path";
|
|
143
|
-
|
|
144
|
-
// src/utils/date.ts
|
|
145
|
-
function getDateString() {
|
|
146
|
-
const d = /* @__PURE__ */ new Date();
|
|
147
|
-
return `${d.getFullYear()}${String(d.getMonth() + 1).padStart(2, "0")}${String(d.getDate()).padStart(2, "0")}`;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// src/utils/git.ts
|
|
151
|
-
import { execSync } from "child_process";
|
|
152
|
-
var GIT_TIMEOUT = 1e4;
|
|
153
|
-
function isGitRepo() {
|
|
154
|
-
try {
|
|
155
|
-
execSync("git rev-parse --is-inside-work-tree", { stdio: "ignore", timeout: GIT_TIMEOUT });
|
|
156
|
-
return true;
|
|
157
|
-
} catch {
|
|
158
|
-
return false;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
var SAFE_BRANCH_RE = /^[a-zA-Z0-9._\-/]+$/;
|
|
162
|
-
function createBranch(branchName) {
|
|
163
|
-
if (!SAFE_BRANCH_RE.test(branchName)) {
|
|
164
|
-
throw new Error(`invalid branch name: ${branchName}`);
|
|
165
|
-
}
|
|
166
|
-
execSync(`git checkout -b ${branchName}`, { stdio: "inherit", timeout: GIT_TIMEOUT });
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// src/commands/init.ts
|
|
170
|
-
import { existsSync as existsSync9, writeFileSync as writeFileSync3, readdirSync as readdirSync5 } from "fs";
|
|
171
|
-
import { join as join8 } from "path";
|
|
172
|
-
import { execSync as execSync2 } from "child_process";
|
|
173
|
-
|
|
174
|
-
// src/prompts/index.ts
|
|
175
|
-
import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync2, readdirSync as readdirSync4 } from "fs";
|
|
176
|
-
import { join as join7 } from "path";
|
|
177
|
-
|
|
178
68
|
// src/ui/index.ts
|
|
179
69
|
import chalk from "chalk";
|
|
180
70
|
var theme = {
|
|
@@ -223,7 +113,9 @@ function boxText(text, width = 50) {
|
|
|
223
113
|
}
|
|
224
114
|
function createBox(lines, width = 52) {
|
|
225
115
|
const top = theme.border(`${box.topLeft}${box.horizontal.repeat(width - 2)}${box.topRight}`);
|
|
226
|
-
const bottom = theme.border(
|
|
116
|
+
const bottom = theme.border(
|
|
117
|
+
`${box.bottomLeft}${box.horizontal.repeat(width - 2)}${box.bottomRight}`
|
|
118
|
+
);
|
|
227
119
|
const middle = lines.map((line) => boxText(line, width));
|
|
228
120
|
return [top, ...middle, bottom].join("\n");
|
|
229
121
|
}
|
|
@@ -279,7 +171,7 @@ function t(en, zh) {
|
|
|
279
171
|
function printSummary(items) {
|
|
280
172
|
const maxLabel = Math.max(...items.map((i) => i.label.length));
|
|
281
173
|
const width = 50;
|
|
282
|
-
console.log(theme.border(
|
|
174
|
+
console.log(theme.border(`\u256D${"\u2500".repeat(width - 2)}\u256E`));
|
|
283
175
|
for (const { label, value } of items) {
|
|
284
176
|
const padding = " ".repeat(maxLabel - label.length);
|
|
285
177
|
const line = `${theme.dim(label)}${padding} ${symbol.arrow} ${theme.highlight(value)}`;
|
|
@@ -287,10 +179,213 @@ function printSummary(items) {
|
|
|
287
179
|
const rightPad = " ".repeat(Math.max(0, width - plainLine.length - 4));
|
|
288
180
|
console.log(theme.border("\u2502 ") + line + rightPad + theme.border(" \u2502"));
|
|
289
181
|
}
|
|
290
|
-
console.log(theme.border(
|
|
182
|
+
console.log(theme.border(`\u2570${"\u2500".repeat(width - 2)}\u256F`));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// src/utils/date.ts
|
|
186
|
+
function getDateString() {
|
|
187
|
+
const d = /* @__PURE__ */ new Date();
|
|
188
|
+
return `${d.getFullYear()}${String(d.getMonth() + 1).padStart(2, "0")}${String(d.getDate()).padStart(2, "0")}`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// src/utils/fs.ts
|
|
192
|
+
import { existsSync as existsSync2, mkdirSync, readdirSync } from "fs";
|
|
193
|
+
function ensureDir(dir) {
|
|
194
|
+
if (!existsSync2(dir)) {
|
|
195
|
+
mkdirSync(dir, { recursive: true });
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// src/commands/archive.ts
|
|
200
|
+
async function archiveCommand(name, options) {
|
|
201
|
+
const cwd = process.cwd();
|
|
202
|
+
const config = loadConfig(cwd);
|
|
203
|
+
const changesDir = join2(cwd, config.specDir, "changes");
|
|
204
|
+
const archiveDir = join2(cwd, config.specDir, "changes", config.archive.dir);
|
|
205
|
+
if (!existsSync3(changesDir)) {
|
|
206
|
+
log.warn(`${symbol.warn} ${t("no changes directory found", "\u672A\u627E\u5230 changes \u76EE\u5F55")}`);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (options.all) {
|
|
210
|
+
const entries = readdirSync2(changesDir, { withFileTypes: true }).filter(
|
|
211
|
+
(e) => e.isDirectory() && e.name !== config.archive.dir
|
|
212
|
+
);
|
|
213
|
+
if (entries.length === 0) {
|
|
214
|
+
log.warn(`${symbol.warn} ${t("no changes to archive", "\u6CA1\u6709\u53EF\u5F52\u6863\u7684\u53D8\u66F4")}`);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
log.info(`${symbol.start} ${t("archiving all changes...", "\u5F52\u6863\u6240\u6709\u53D8\u66F4...")}`);
|
|
218
|
+
for (const entry of entries) {
|
|
219
|
+
archiveOne(entry.name, changesDir, archiveDir, config);
|
|
220
|
+
}
|
|
221
|
+
} else if (name) {
|
|
222
|
+
const changePath = join2(changesDir, name);
|
|
223
|
+
if (!existsSync3(changePath)) {
|
|
224
|
+
log.warn(`${symbol.warn} ${t(`change "${name}" not found`, `\u53D8\u66F4 "${name}" \u4E0D\u5B58\u5728`)}`);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
log.info(`${symbol.start} ${t(`archiving: ${name}`, `\u5F52\u6863\u53D8\u66F4: ${name}`)}`);
|
|
228
|
+
archiveOne(name, changesDir, archiveDir, config);
|
|
229
|
+
} else {
|
|
230
|
+
log.warn(`${symbol.warn} ${t("specify a name or use --all", "\u8BF7\u6307\u5B9A\u53D8\u66F4\u540D\u79F0\u6216\u4F7F\u7528 --all")}`);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
log.info(`${symbol.start} ${t("archive done!", "\u5F52\u6863\u5B8C\u6210\uFF01")}`);
|
|
234
|
+
}
|
|
235
|
+
function archiveOne(name, changesDir, archiveDir, config) {
|
|
236
|
+
ensureDir(archiveDir);
|
|
237
|
+
const src = join2(changesDir, name);
|
|
238
|
+
const dateStr = config.archive.datePrefix ? `${getDateString()}-` : "";
|
|
239
|
+
const dest = join2(archiveDir, `${dateStr}${name}`);
|
|
240
|
+
if (existsSync3(dest)) {
|
|
241
|
+
log.warn(` ${symbol.warn} ${t("archive target exists", "\u5F52\u6863\u76EE\u6807\u5DF2\u5B58\u5728")}: ${dest}`);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
renameSync(src, dest);
|
|
245
|
+
log.success(` ${symbol.ok} ${name} \u2192 archive/${dateStr}${name}`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// src/commands/create.ts
|
|
249
|
+
import { existsSync as existsSync5 } from "fs";
|
|
250
|
+
import { join as join4 } from "path";
|
|
251
|
+
|
|
252
|
+
// src/utils/git.ts
|
|
253
|
+
import { execSync } from "child_process";
|
|
254
|
+
var GIT_TIMEOUT = 1e4;
|
|
255
|
+
function isGitRepo() {
|
|
256
|
+
try {
|
|
257
|
+
execSync("git rev-parse --is-inside-work-tree", { stdio: "ignore", timeout: GIT_TIMEOUT });
|
|
258
|
+
return true;
|
|
259
|
+
} catch {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
var SAFE_BRANCH_RE = /^[a-zA-Z0-9._\-/]+$/;
|
|
264
|
+
function createBranch(branchName) {
|
|
265
|
+
if (!SAFE_BRANCH_RE.test(branchName)) {
|
|
266
|
+
throw new Error(`invalid branch name: ${branchName}`);
|
|
267
|
+
}
|
|
268
|
+
execSync(`git checkout -b ${branchName}`, { stdio: "inherit", timeout: GIT_TIMEOUT });
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// src/utils/paths.ts
|
|
272
|
+
import { existsSync as existsSync4 } from "fs";
|
|
273
|
+
import { dirname, join as join3 } from "path";
|
|
274
|
+
import { fileURLToPath } from "url";
|
|
275
|
+
function getPackageRoot() {
|
|
276
|
+
const __filename2 = fileURLToPath(import.meta.url);
|
|
277
|
+
let dir = dirname(__filename2);
|
|
278
|
+
while (dir !== dirname(dir)) {
|
|
279
|
+
if (existsSync4(join3(dir, "package.json")) && existsSync4(join3(dir, "templates"))) {
|
|
280
|
+
return dir;
|
|
281
|
+
}
|
|
282
|
+
dir = dirname(dir);
|
|
283
|
+
}
|
|
284
|
+
return join3(dirname(__filename2), "..", "..");
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// src/utils/template.ts
|
|
288
|
+
function renderNameTemplate(template, vars, strict = false) {
|
|
289
|
+
let result = template;
|
|
290
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
291
|
+
if (value !== void 0) {
|
|
292
|
+
result = result.replace(new RegExp(`\\{${key}\\}`, "g"), value);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
result = result.replace(/\{[^}]+\}/g, "");
|
|
296
|
+
if (strict) {
|
|
297
|
+
return result.replace(/[^a-zA-Z0-9._\-/]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
298
|
+
}
|
|
299
|
+
return result.replace(/[^\p{L}\p{N}._\-/]/gu, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// src/commands/create.ts
|
|
303
|
+
async function createCommand(feature, options) {
|
|
304
|
+
const cwd = process.cwd();
|
|
305
|
+
const config = loadConfig(cwd);
|
|
306
|
+
const specDir = options.specDir || config.specDir;
|
|
307
|
+
const branchPrefix = options.branchPrefix || config.branchPrefix;
|
|
308
|
+
const boost = options.boost || config.boost;
|
|
309
|
+
const strategy = options.creative ? "create" : config.strategy;
|
|
310
|
+
const templateVars = {
|
|
311
|
+
prefix: branchPrefix,
|
|
312
|
+
intentType: options.intentType,
|
|
313
|
+
feature,
|
|
314
|
+
date: getDateString(),
|
|
315
|
+
user: options.user
|
|
316
|
+
};
|
|
317
|
+
const changeNameTemplate = options.changeNameTemplate || config.changeNameTemplate || "{date}-{feature}";
|
|
318
|
+
const changeFolderName = renderNameTemplate(changeNameTemplate, templateVars, false);
|
|
319
|
+
const changePath = join4(cwd, specDir, "changes", changeFolderName);
|
|
320
|
+
if (existsSync5(changePath)) {
|
|
321
|
+
log.warn(
|
|
322
|
+
`${symbol.warn} ${t(`change "${changeFolderName}" already exists`, `\u53D8\u66F4 "${changeFolderName}" \u5DF2\u5B58\u5728`)}: ${changePath}`
|
|
323
|
+
);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
log.title(`${t("Creating Change", "\u521B\u5EFA\u53D8\u66F4")}: ${changeFolderName}`);
|
|
327
|
+
if (options.intentType) {
|
|
328
|
+
log.info(`${t("Intent Type", "\u610F\u56FE\u7C7B\u578B")}: ${options.intentType}`);
|
|
329
|
+
}
|
|
330
|
+
if (boost) {
|
|
331
|
+
log.boost(`${symbol.bolt} ${t("Boost mode enabled", "\u589E\u5F3A\u6A21\u5F0F\u5DF2\u542F\u7528")}`);
|
|
332
|
+
}
|
|
333
|
+
if (strategy === "create") {
|
|
334
|
+
log.boost(
|
|
335
|
+
`${symbol.bolt} ${t("Creative mode: exploring new solutions", "\u521B\u9020\u6A21\u5F0F\uFF1A\u63A2\u7D22\u65B0\u65B9\u6848")}`
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
ensureDir(changePath);
|
|
339
|
+
if (options.branch !== false && isGitRepo()) {
|
|
340
|
+
const branchTemplate = options.branchTemplate || config.branchTemplate || "{prefix}{date}-{feature}";
|
|
341
|
+
const branchName = renderNameTemplate(branchTemplate, templateVars, true);
|
|
342
|
+
try {
|
|
343
|
+
createBranch(branchName);
|
|
344
|
+
log.success(`${symbol.ok} Branch: ${branchName}`);
|
|
345
|
+
} catch (e) {
|
|
346
|
+
log.warn(`${symbol.warn} ${t("branch creation failed", "\u5206\u652F\u521B\u5EFA\u5931\u8D25")}: ${e.message}`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
log.done(t("Change created successfully!", "\u53D8\u66F4\u521B\u5EFA\u6210\u529F\uFF01"));
|
|
350
|
+
log.dim(`${t("Path", "\u8DEF\u5F84")}: ${specDir}/changes/${changeFolderName}/`);
|
|
351
|
+
log.dim(`${t("Templates", "\u6A21\u677F\u53C2\u8003")}: ${specDir}/templates/`);
|
|
352
|
+
const expectedArtifacts = boost ? config.boostArtifacts : config.artifacts;
|
|
353
|
+
log.dim(`${t("Expected artifacts", "\u9884\u671F Artifacts")}: ${expectedArtifacts.join(", ")}`);
|
|
354
|
+
log.dim(
|
|
355
|
+
`${t("Next", "\u4E0B\u4E00\u6B65")}: ${t("AI generates artifacts on demand via /ss-create", "AI \u6309\u9700\u901A\u8FC7 /ss-create \u751F\u6210 artifacts")}`
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// src/commands/deps.ts
|
|
360
|
+
import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
361
|
+
import { join as join5 } from "path";
|
|
362
|
+
|
|
363
|
+
// src/commands/init.ts
|
|
364
|
+
import { execSync as execSync2 } from "child_process";
|
|
365
|
+
import { existsSync as existsSync9, readdirSync as readdirSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
366
|
+
import { join as join8 } from "path";
|
|
367
|
+
|
|
368
|
+
// src/core/template.ts
|
|
369
|
+
import { copyFileSync, existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
370
|
+
import { dirname as dirname2, join as join6 } from "path";
|
|
371
|
+
function resolveTemplatePath(templateName, lang = "zh") {
|
|
372
|
+
const root = getPackageRoot();
|
|
373
|
+
const langPath = join6(root, "templates", lang, templateName);
|
|
374
|
+
if (existsSync7(langPath)) return langPath;
|
|
375
|
+
const fallbackLang = lang === "zh" ? "en" : "zh";
|
|
376
|
+
const fallback = join6(root, "templates", fallbackLang, templateName);
|
|
377
|
+
if (existsSync7(fallback)) return fallback;
|
|
378
|
+
throw new Error(`Template not found: ${templateName} (lang: ${lang})`);
|
|
379
|
+
}
|
|
380
|
+
function copyTemplate(templateName, destPath, lang = "zh") {
|
|
381
|
+
const srcPath = resolveTemplatePath(templateName, lang);
|
|
382
|
+
ensureDir(dirname2(destPath));
|
|
383
|
+
copyFileSync(srcPath, destPath);
|
|
291
384
|
}
|
|
292
385
|
|
|
293
386
|
// src/prompts/index.ts
|
|
387
|
+
import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
388
|
+
import { join as join7 } from "path";
|
|
294
389
|
var AI_EDITORS = {
|
|
295
390
|
claude: {
|
|
296
391
|
commands: ".claude/commands",
|
|
@@ -335,10 +430,10 @@ function installRules(cwd, editor) {
|
|
|
335
430
|
ensureDir(rulesDir);
|
|
336
431
|
const promptSrc = join7(getPackageRoot(), "prompts", "rules.md");
|
|
337
432
|
if (existsSync8(promptSrc)) {
|
|
338
|
-
const content =
|
|
433
|
+
const content = readFileSync4(promptSrc, "utf-8");
|
|
339
434
|
const rulesFile = "rulesFile" in config ? config.rulesFile : "superspec.md";
|
|
340
435
|
const destPath = join7(rulesDir, rulesFile);
|
|
341
|
-
|
|
436
|
+
writeFileSync3(destPath, content, "utf-8");
|
|
342
437
|
log.success(`${symbol.ok} ${config.rules}/${rulesFile}`);
|
|
343
438
|
}
|
|
344
439
|
}
|
|
@@ -348,25 +443,27 @@ function installAgentsMd(cwd) {
|
|
|
348
443
|
const agentsMdPath = join7(cwd, "AGENTS.md");
|
|
349
444
|
const agentPromptSrc = join7(getPackageRoot(), "prompts", "agents.md");
|
|
350
445
|
if (!existsSync8(agentPromptSrc)) return;
|
|
351
|
-
const newContent =
|
|
446
|
+
const newContent = readFileSync4(agentPromptSrc, "utf-8");
|
|
352
447
|
const wrapped = `${SS_START}
|
|
353
448
|
${newContent}
|
|
354
449
|
${SS_END}`;
|
|
355
450
|
if (existsSync8(agentsMdPath)) {
|
|
356
|
-
const existing =
|
|
451
|
+
const existing = readFileSync4(agentsMdPath, "utf-8");
|
|
357
452
|
const startIdx = existing.indexOf(SS_START);
|
|
358
453
|
const endIdx = existing.indexOf(SS_END);
|
|
359
454
|
if (startIdx !== -1 && endIdx !== -1) {
|
|
360
455
|
const before = existing.slice(0, startIdx);
|
|
361
456
|
const after = existing.slice(endIdx + SS_END.length);
|
|
362
|
-
|
|
457
|
+
writeFileSync3(agentsMdPath, before + wrapped + after, "utf-8");
|
|
363
458
|
} else if (existing.includes("SuperSpec")) {
|
|
364
|
-
|
|
459
|
+
writeFileSync3(agentsMdPath, existing, "utf-8");
|
|
365
460
|
} else {
|
|
366
|
-
|
|
461
|
+
writeFileSync3(agentsMdPath, `${existing}
|
|
462
|
+
|
|
463
|
+
${wrapped}`, "utf-8");
|
|
367
464
|
}
|
|
368
465
|
} else {
|
|
369
|
-
|
|
466
|
+
writeFileSync3(agentsMdPath, wrapped, "utf-8");
|
|
370
467
|
}
|
|
371
468
|
log.success(`${symbol.ok} AGENTS.md`);
|
|
372
469
|
}
|
|
@@ -385,8 +482,8 @@ function installCommands(cwd, editor, lang = "zh") {
|
|
|
385
482
|
for (const file of commandFiles) {
|
|
386
483
|
const srcPath = join7(sourceDir, file);
|
|
387
484
|
const destPath = join7(commandsDir, file);
|
|
388
|
-
const content =
|
|
389
|
-
|
|
485
|
+
const content = readFileSync4(srcPath, "utf-8");
|
|
486
|
+
writeFileSync3(destPath, content, "utf-8");
|
|
390
487
|
}
|
|
391
488
|
log.success(`${symbol.ok} ${config.commands}/ (${commandFiles.length} commands)`);
|
|
392
489
|
}
|
|
@@ -396,7 +493,9 @@ async function initCommand(options) {
|
|
|
396
493
|
const cwd = process.cwd();
|
|
397
494
|
const configPath = join8(cwd, "superspec.config.json");
|
|
398
495
|
if (existsSync9(configPath) && !options.force) {
|
|
399
|
-
log.warn(
|
|
496
|
+
log.warn(
|
|
497
|
+
`${symbol.warn} ${t("superspec.config.json already exists, use --force to overwrite", "superspec.config.json \u5DF2\u5B58\u5728\uFF0C\u4F7F\u7528 --force \u8986\u76D6")}`
|
|
498
|
+
);
|
|
400
499
|
return;
|
|
401
500
|
}
|
|
402
501
|
const lang = options.lang || "zh";
|
|
@@ -412,12 +511,17 @@ async function initCommand(options) {
|
|
|
412
511
|
const specDir = join8(cwd, config.specDir);
|
|
413
512
|
const existingFiles = readdirSync5(cwd).filter((f) => !f.startsWith(".") && f !== "node_modules");
|
|
414
513
|
if (existingFiles.length > 0 && !options.force) {
|
|
415
|
-
log.warn(
|
|
416
|
-
|
|
514
|
+
log.warn(
|
|
515
|
+
`${symbol.warn} ${t(`current directory is not empty (${existingFiles.length} items)`, `\u5F53\u524D\u76EE\u5F55\u975E\u7A7A\uFF08${existingFiles.length} \u9879\uFF09`)}`
|
|
516
|
+
);
|
|
517
|
+
log.dim(
|
|
518
|
+
` ${t("template files will be merged with existing content", "\u6A21\u677F\u6587\u4EF6\u5C06\u4E0E\u73B0\u6709\u5185\u5BB9\u5408\u5E76")}`
|
|
519
|
+
);
|
|
417
520
|
console.log();
|
|
418
521
|
}
|
|
419
522
|
log.section(t("Creating Configuration", "\u521B\u5EFA\u914D\u7F6E"));
|
|
420
|
-
|
|
523
|
+
writeFileSync4(configPath, `${JSON.stringify(config, null, 2)}
|
|
524
|
+
`, "utf-8");
|
|
421
525
|
log.success(`${symbol.file} superspec.config.json`);
|
|
422
526
|
log.section(t("Creating Directory Structure", "\u521B\u5EFA\u76EE\u5F55\u7ED3\u6784"));
|
|
423
527
|
ensureDir(join8(specDir, "changes"));
|
|
@@ -425,7 +529,9 @@ async function initCommand(options) {
|
|
|
425
529
|
log.success(`${symbol.folder} ${config.specDir}/changes/`);
|
|
426
530
|
log.success(`${symbol.folder} ${config.specDir}/templates/`);
|
|
427
531
|
log.section(t("Installing Templates", "\u5B89\u88C5\u6A21\u677F"));
|
|
428
|
-
const templateNames = Object.values(config.templates).map(
|
|
532
|
+
const templateNames = Object.values(config.templates).map(
|
|
533
|
+
(v) => v.endsWith(".md") ? v : `${v}.md`
|
|
534
|
+
);
|
|
429
535
|
for (const tpl of templateNames) {
|
|
430
536
|
try {
|
|
431
537
|
copyTemplate(tpl, join8(specDir, "templates", tpl), lang);
|
|
@@ -454,168 +560,52 @@ async function initCommand(options) {
|
|
|
454
560
|
log.dim(`${t("Next", "\u4E0B\u4E00\u6B65")}: superspec create <feature>`);
|
|
455
561
|
}
|
|
456
562
|
|
|
457
|
-
// src/commands/
|
|
458
|
-
import { existsSync as
|
|
459
|
-
import { join as
|
|
563
|
+
// src/commands/lint.ts
|
|
564
|
+
import { existsSync as existsSync11 } from "fs";
|
|
565
|
+
import { join as join10 } from "path";
|
|
460
566
|
|
|
461
|
-
// src/
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
for (const [key, value] of Object.entries(vars)) {
|
|
465
|
-
if (value !== void 0) {
|
|
466
|
-
result = result.replace(new RegExp(`\\{${key}\\}`, "g"), value);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
result = result.replace(/\{[^}]+\}/g, "");
|
|
470
|
-
if (strict) {
|
|
471
|
-
return result.replace(/[^a-zA-Z0-9._\-/]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
472
|
-
}
|
|
473
|
-
return result.replace(/[^\p{L}\p{N}._\-/]/gu, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
474
|
-
}
|
|
567
|
+
// src/core/lint.ts
|
|
568
|
+
import { existsSync as existsSync10, readdirSync as readdirSync6, readFileSync as readFileSync5 } from "fs";
|
|
569
|
+
import { basename, join as join9 } from "path";
|
|
475
570
|
|
|
476
|
-
// src/commands/
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
const config = loadConfig(cwd);
|
|
480
|
-
const specDir = options.specDir || config.specDir;
|
|
481
|
-
const branchPrefix = options.branchPrefix || config.branchPrefix;
|
|
482
|
-
const boost = options.boost || config.boost;
|
|
483
|
-
const strategy = options.creative ? "create" : config.strategy;
|
|
484
|
-
const description = options.description || "";
|
|
485
|
-
const lang = options.lang || config.lang || "en";
|
|
486
|
-
const templateVars = {
|
|
487
|
-
prefix: branchPrefix,
|
|
488
|
-
intentType: options.intentType,
|
|
489
|
-
feature,
|
|
490
|
-
date: getDateString(),
|
|
491
|
-
user: options.user
|
|
492
|
-
};
|
|
493
|
-
const changeNameTemplate = options.changeNameTemplate || config.changeNameTemplate || "{date}-{feature}";
|
|
494
|
-
const changeFolderName = renderNameTemplate(changeNameTemplate, templateVars, false);
|
|
495
|
-
const changePath = join9(cwd, specDir, "changes", changeFolderName);
|
|
496
|
-
if (existsSync10(changePath)) {
|
|
497
|
-
log.warn(`${symbol.warn} ${t(`change "${changeFolderName}" already exists`, `\u53D8\u66F4 "${changeFolderName}" \u5DF2\u5B58\u5728`)}: ${changePath}`);
|
|
498
|
-
return;
|
|
499
|
-
}
|
|
500
|
-
log.title(`${t("Creating Change", "\u521B\u5EFA\u53D8\u66F4")}: ${changeFolderName}`);
|
|
501
|
-
if (options.intentType) {
|
|
502
|
-
log.info(`${t("Intent Type", "\u610F\u56FE\u7C7B\u578B")}: ${options.intentType}`);
|
|
503
|
-
}
|
|
504
|
-
if (boost) {
|
|
505
|
-
log.boost(`${symbol.bolt} ${t("Boost mode enabled", "\u589E\u5F3A\u6A21\u5F0F\u5DF2\u542F\u7528")}`);
|
|
506
|
-
}
|
|
507
|
-
if (strategy === "create") {
|
|
508
|
-
log.boost(`${symbol.bolt} ${t("Creative mode: exploring new solutions", "\u521B\u9020\u6A21\u5F0F\uFF1A\u63A2\u7D22\u65B0\u65B9\u6848")}`);
|
|
509
|
-
}
|
|
510
|
-
ensureDir(changePath);
|
|
511
|
-
const vars = {
|
|
512
|
-
name: changeFolderName,
|
|
513
|
-
date: templateVars.date,
|
|
514
|
-
boost: boost ? "true" : "false",
|
|
515
|
-
strategy,
|
|
516
|
-
description
|
|
517
|
-
};
|
|
518
|
-
const artifacts = boost ? config.boostArtifacts : config.artifacts;
|
|
519
|
-
log.section(t("Generating Artifacts", "\u751F\u6210 Artifacts"));
|
|
520
|
-
for (const artifact of artifacts) {
|
|
521
|
-
const templateFile = config.templates[artifact] || `${artifact}.md`;
|
|
522
|
-
const destPath = join9(changePath, `${artifact}.md`);
|
|
523
|
-
try {
|
|
524
|
-
writeRenderedTemplate(templateFile, destPath, vars, lang);
|
|
525
|
-
log.success(`${symbol.ok} ${artifact}.md`);
|
|
526
|
-
} catch (e) {
|
|
527
|
-
log.error(`${symbol.fail} ${artifact}.md: ${e.message}`);
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
if (options.branch !== false && isGitRepo()) {
|
|
531
|
-
const branchTemplate = options.branchTemplate || config.branchTemplate || "{prefix}{date}-{feature}";
|
|
532
|
-
const branchName = renderNameTemplate(branchTemplate, templateVars, true);
|
|
533
|
-
try {
|
|
534
|
-
createBranch(branchName);
|
|
535
|
-
log.success(`${symbol.ok} Branch: ${branchName}`);
|
|
536
|
-
} catch (e) {
|
|
537
|
-
log.warn(`${symbol.warn} ${t("branch creation failed", "\u5206\u652F\u521B\u5EFA\u5931\u8D25")}: ${e.message}`);
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
log.done(t("Change created successfully!", "\u53D8\u66F4\u521B\u5EFA\u6210\u529F\uFF01"));
|
|
541
|
-
log.dim(`${t("Path", "\u8DEF\u5F84")}: ${specDir}/changes/${changeFolderName}/`);
|
|
542
|
-
if (boost) {
|
|
543
|
-
log.dim(`${t("Workflow", "\u5DE5\u4F5C\u6D41")}: /ss-create \u2192 /ss-tasks \u2192 /ss-apply (boost)`);
|
|
544
|
-
} else {
|
|
545
|
-
log.dim(`${t("Workflow", "\u5DE5\u4F5C\u6D41")}: /ss-tasks \u2192 /ss-apply`);
|
|
546
|
-
}
|
|
547
|
-
log.dim(`${t("Next", "\u4E0B\u4E00\u6B65")}: superspec lint ${changeFolderName}`);
|
|
548
|
-
}
|
|
571
|
+
// src/commands/search.ts
|
|
572
|
+
import { existsSync as existsSync12, readdirSync as readdirSync7, readFileSync as readFileSync6 } from "fs";
|
|
573
|
+
import { basename as basename2, join as join11 } from "path";
|
|
549
574
|
|
|
550
|
-
// src/commands/
|
|
551
|
-
import { existsSync as
|
|
552
|
-
import { join as
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
}
|
|
562
|
-
if (options.all) {
|
|
563
|
-
const entries = readdirSync6(changesDir, { withFileTypes: true }).filter(
|
|
564
|
-
(e) => e.isDirectory() && e.name !== config.archive.dir
|
|
565
|
-
);
|
|
566
|
-
if (entries.length === 0) {
|
|
567
|
-
log.warn(`${symbol.warn} ${t("no changes to archive", "\u6CA1\u6709\u53EF\u5F52\u6863\u7684\u53D8\u66F4")}`);
|
|
568
|
-
return;
|
|
569
|
-
}
|
|
570
|
-
log.info(`${symbol.start} ${t("archiving all changes...", "\u5F52\u6863\u6240\u6709\u53D8\u66F4...")}`);
|
|
571
|
-
for (const entry of entries) {
|
|
572
|
-
archiveOne(entry.name, changesDir, archiveDir, config);
|
|
573
|
-
}
|
|
574
|
-
} else if (name) {
|
|
575
|
-
const changePath = join10(changesDir, name);
|
|
576
|
-
if (!existsSync11(changePath)) {
|
|
577
|
-
log.warn(`${symbol.warn} ${t(`change "${name}" not found`, `\u53D8\u66F4 "${name}" \u4E0D\u5B58\u5728`)}`);
|
|
578
|
-
return;
|
|
579
|
-
}
|
|
580
|
-
log.info(`${symbol.start} ${t(`archiving: ${name}`, `\u5F52\u6863\u53D8\u66F4: ${name}`)}`);
|
|
581
|
-
archiveOne(name, changesDir, archiveDir, config);
|
|
582
|
-
} else {
|
|
583
|
-
log.warn(`${symbol.warn} ${t("specify a name or use --all", "\u8BF7\u6307\u5B9A\u53D8\u66F4\u540D\u79F0\u6216\u4F7F\u7528 --all")}`);
|
|
584
|
-
return;
|
|
585
|
-
}
|
|
586
|
-
log.info(`${symbol.start} ${t("archive done!", "\u5F52\u6863\u5B8C\u6210\uFF01")}`);
|
|
587
|
-
}
|
|
588
|
-
function archiveOne(name, changesDir, archiveDir, config) {
|
|
589
|
-
ensureDir(archiveDir);
|
|
590
|
-
const src = join10(changesDir, name);
|
|
591
|
-
const dateStr = config.archive.datePrefix ? `${getDateString()}-` : "";
|
|
592
|
-
const dest = join10(archiveDir, `${dateStr}${name}`);
|
|
593
|
-
if (existsSync11(dest)) {
|
|
594
|
-
log.warn(` ${symbol.warn} ${t("archive target exists", "\u5F52\u6863\u76EE\u6807\u5DF2\u5B58\u5728")}: ${dest}`);
|
|
595
|
-
return;
|
|
596
|
-
}
|
|
597
|
-
renameSync(src, dest);
|
|
598
|
-
log.success(` ${symbol.ok} ${name} \u2192 archive/${dateStr}${name}`);
|
|
599
|
-
}
|
|
575
|
+
// src/commands/status.ts
|
|
576
|
+
import { existsSync as existsSync13, readdirSync as readdirSync8, readFileSync as readFileSync7 } from "fs";
|
|
577
|
+
import { join as join12 } from "path";
|
|
578
|
+
|
|
579
|
+
// src/commands/sync.ts
|
|
580
|
+
import { existsSync as existsSync15, writeFileSync as writeFileSync5 } from "fs";
|
|
581
|
+
import { join as join14 } from "path";
|
|
582
|
+
|
|
583
|
+
// src/core/context.ts
|
|
584
|
+
import { existsSync as existsSync14, readFileSync as readFileSync8 } from "fs";
|
|
585
|
+
import { join as join13 } from "path";
|
|
600
586
|
|
|
601
587
|
// src/commands/update.ts
|
|
602
|
-
import { existsSync as
|
|
603
|
-
import { join as
|
|
588
|
+
import { existsSync as existsSync16 } from "fs";
|
|
589
|
+
import { join as join15 } from "path";
|
|
604
590
|
async function updateCommand() {
|
|
605
591
|
const cwd = process.cwd();
|
|
606
592
|
const config = loadConfig(cwd);
|
|
607
|
-
const specDir =
|
|
593
|
+
const specDir = join15(cwd, config.specDir);
|
|
608
594
|
const lang = config.lang || "zh";
|
|
609
|
-
if (!
|
|
610
|
-
log.warn(
|
|
595
|
+
if (!existsSync16(join15(cwd, "superspec.config.json"))) {
|
|
596
|
+
log.warn(
|
|
597
|
+
`${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")}`
|
|
598
|
+
);
|
|
611
599
|
return;
|
|
612
600
|
}
|
|
613
601
|
log.info(`${symbol.start} ${t("updating SuperSpec...", "\u66F4\u65B0 SuperSpec...")}`);
|
|
614
|
-
const templateNames = Object.values(config.templates).map(
|
|
615
|
-
|
|
602
|
+
const templateNames = Object.values(config.templates).map(
|
|
603
|
+
(v) => v.endsWith(".md") ? v : `${v}.md`
|
|
604
|
+
);
|
|
605
|
+
ensureDir(join15(specDir, "templates"));
|
|
616
606
|
for (const tpl of templateNames) {
|
|
617
607
|
try {
|
|
618
|
-
copyTemplate(tpl,
|
|
608
|
+
copyTemplate(tpl, join15(specDir, "templates", tpl), lang);
|
|
619
609
|
} catch {
|
|
620
610
|
}
|
|
621
611
|
}
|
|
@@ -629,29 +619,13 @@ async function updateCommand() {
|
|
|
629
619
|
log.info(`${symbol.start} ${t("update done!", "\u66F4\u65B0\u5B8C\u6210\uFF01")}`);
|
|
630
620
|
}
|
|
631
621
|
|
|
632
|
-
// src/commands/lint.ts
|
|
633
|
-
import { existsSync as existsSync13 } from "fs";
|
|
634
|
-
import { join as join12 } from "path";
|
|
635
|
-
|
|
636
622
|
// src/commands/validate.ts
|
|
637
|
-
import { existsSync as
|
|
638
|
-
import { join as
|
|
639
|
-
|
|
640
|
-
// src/commands/search.ts
|
|
641
|
-
import { existsSync as existsSync15, readFileSync as readFileSync7, readdirSync as readdirSync7 } from "fs";
|
|
642
|
-
import { join as join14, basename as basename3 } from "path";
|
|
643
|
-
|
|
644
|
-
// src/commands/deps.ts
|
|
645
|
-
import { existsSync as existsSync16, readFileSync as readFileSync8, writeFileSync as writeFileSync4, readdirSync as readdirSync8 } from "fs";
|
|
646
|
-
import { join as join15 } from "path";
|
|
623
|
+
import { existsSync as existsSync18 } from "fs";
|
|
624
|
+
import { join as join17 } from "path";
|
|
647
625
|
|
|
648
|
-
// src/
|
|
649
|
-
import { existsSync as existsSync17, readFileSync as readFileSync9
|
|
626
|
+
// src/core/validate.ts
|
|
627
|
+
import { existsSync as existsSync17, readFileSync as readFileSync9 } from "fs";
|
|
650
628
|
import { join as join16 } from "path";
|
|
651
|
-
|
|
652
|
-
// src/commands/sync.ts
|
|
653
|
-
import { existsSync as existsSync18, writeFileSync as writeFileSync5 } from "fs";
|
|
654
|
-
import { join as join17 } from "path";
|
|
655
629
|
export {
|
|
656
630
|
archiveCommand,
|
|
657
631
|
createCommand,
|