@superspec/cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +1329 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.js +607 -0
- package/dist/index.js.map +1 -0
- package/package.json +63 -0
- package/prompts/agents.md +150 -0
- package/prompts/cursor-rules.md +154 -0
- package/templates/en/checklist.md +62 -0
- package/templates/en/clarify.md +44 -0
- package/templates/en/commands/ss-apply.md +19 -0
- package/templates/en/commands/ss-archive.md +15 -0
- package/templates/en/commands/ss-checklist.md +16 -0
- package/templates/en/commands/ss-clarify.md +17 -0
- package/templates/en/commands/ss-create.md +19 -0
- package/templates/en/commands/ss-deps.md +11 -0
- package/templates/en/commands/ss-link.md +11 -0
- package/templates/en/commands/ss-lint.md +15 -0
- package/templates/en/commands/ss-resume.md +17 -0
- package/templates/en/commands/ss-search.md +12 -0
- package/templates/en/commands/ss-status.md +12 -0
- package/templates/en/commands/ss-tasks.md +19 -0
- package/templates/en/commands/ss-validate.md +15 -0
- package/templates/en/proposal.md +53 -0
- package/templates/en/spec.md +73 -0
- package/templates/en/tasks.md +52 -0
- package/templates/zh/checklist.md +62 -0
- package/templates/zh/clarify.md +44 -0
- package/templates/zh/commands/ss-apply.md +19 -0
- package/templates/zh/commands/ss-archive.md +15 -0
- package/templates/zh/commands/ss-checklist.md +16 -0
- package/templates/zh/commands/ss-clarify.md +17 -0
- package/templates/zh/commands/ss-create.md +19 -0
- package/templates/zh/commands/ss-deps.md +11 -0
- package/templates/zh/commands/ss-link.md +11 -0
- package/templates/zh/commands/ss-lint.md +15 -0
- package/templates/zh/commands/ss-resume.md +17 -0
- package/templates/zh/commands/ss-search.md +12 -0
- package/templates/zh/commands/ss-status.md +12 -0
- package/templates/zh/commands/ss-tasks.md +19 -0
- package/templates/zh/commands/ss-validate.md +15 -0
- package/templates/zh/proposal.md +59 -0
- package/templates/zh/spec.md +73 -0
- package/templates/zh/tasks.md +52 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
// src/core/config.ts
|
|
2
|
+
import { readFileSync, existsSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
var DEFAULT_CONFIG = {
|
|
5
|
+
lang: "zh",
|
|
6
|
+
aiEditor: "cursor",
|
|
7
|
+
specDir: "superspec",
|
|
8
|
+
branchPrefix: "spec/",
|
|
9
|
+
branchTemplate: "{prefix}{name}",
|
|
10
|
+
boost: false,
|
|
11
|
+
strategy: "follow",
|
|
12
|
+
context: [],
|
|
13
|
+
templates: {
|
|
14
|
+
spec: "spec.md",
|
|
15
|
+
proposal: "proposal.md",
|
|
16
|
+
tasks: "tasks.md",
|
|
17
|
+
clarify: "clarify.md",
|
|
18
|
+
checklist: "checklist.md"
|
|
19
|
+
},
|
|
20
|
+
archive: {
|
|
21
|
+
dir: "archive",
|
|
22
|
+
datePrefix: true
|
|
23
|
+
},
|
|
24
|
+
limits: {
|
|
25
|
+
targetLines: 300,
|
|
26
|
+
hardLines: 400
|
|
27
|
+
},
|
|
28
|
+
artifacts: ["proposal"],
|
|
29
|
+
boostArtifacts: ["proposal", "spec", "tasks", "checklist"]
|
|
30
|
+
};
|
|
31
|
+
function loadConfig(projectRoot = process.cwd()) {
|
|
32
|
+
const configPath = join(projectRoot, "superspec.config.json");
|
|
33
|
+
let userConfig = {};
|
|
34
|
+
if (existsSync(configPath)) {
|
|
35
|
+
try {
|
|
36
|
+
userConfig = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
37
|
+
} catch (e) {
|
|
38
|
+
console.warn(`\u26A0 \u914D\u7F6E\u6587\u4EF6\u89E3\u6790\u5931\u8D25: ${e.message}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return deepMerge(DEFAULT_CONFIG, userConfig);
|
|
42
|
+
}
|
|
43
|
+
function getDefaultConfig() {
|
|
44
|
+
return structuredClone(DEFAULT_CONFIG);
|
|
45
|
+
}
|
|
46
|
+
function deepMerge(target, source) {
|
|
47
|
+
const result = { ...target };
|
|
48
|
+
for (const key of Object.keys(source)) {
|
|
49
|
+
if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key]) && target[key] && typeof target[key] === "object") {
|
|
50
|
+
result[key] = deepMerge(target[key], source[key]);
|
|
51
|
+
} else {
|
|
52
|
+
result[key] = source[key];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// src/core/template.ts
|
|
59
|
+
import { existsSync as existsSync3, copyFileSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
60
|
+
import { join as join3, dirname as dirname2 } from "path";
|
|
61
|
+
|
|
62
|
+
// src/utils/paths.ts
|
|
63
|
+
import { dirname, join as join2 } from "path";
|
|
64
|
+
import { fileURLToPath } from "url";
|
|
65
|
+
function getPackageRoot() {
|
|
66
|
+
const __filename2 = fileURLToPath(import.meta.url);
|
|
67
|
+
const __dirname2 = dirname(__filename2);
|
|
68
|
+
return join2(__dirname2, "..", "..");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/utils/fs.ts
|
|
72
|
+
import { mkdirSync, existsSync as existsSync2 } from "fs";
|
|
73
|
+
function ensureDir(dir) {
|
|
74
|
+
if (!existsSync2(dir)) {
|
|
75
|
+
mkdirSync(dir, { recursive: true });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/core/template.ts
|
|
80
|
+
function resolveTemplatePath(templateName, lang = "zh") {
|
|
81
|
+
const root = getPackageRoot();
|
|
82
|
+
const langPath = join3(root, "templates", lang, templateName);
|
|
83
|
+
if (existsSync3(langPath)) return langPath;
|
|
84
|
+
const fallback = join3(root, "templates", "zh", templateName);
|
|
85
|
+
if (existsSync3(fallback)) return fallback;
|
|
86
|
+
throw new Error(`Template not found: ${templateName} (lang: ${lang})`);
|
|
87
|
+
}
|
|
88
|
+
function copyTemplate(templateName, destPath, lang = "zh") {
|
|
89
|
+
const srcPath = resolveTemplatePath(templateName, lang);
|
|
90
|
+
ensureDir(dirname2(destPath));
|
|
91
|
+
copyFileSync(srcPath, destPath);
|
|
92
|
+
}
|
|
93
|
+
function renderTemplate(templateName, vars = {}, lang = "zh") {
|
|
94
|
+
const srcPath = resolveTemplatePath(templateName, lang);
|
|
95
|
+
let content = readFileSync2(srcPath, "utf-8");
|
|
96
|
+
content = processConditionals(content, vars);
|
|
97
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
98
|
+
content = content.replaceAll(`{{${key}}}`, value);
|
|
99
|
+
}
|
|
100
|
+
return content;
|
|
101
|
+
}
|
|
102
|
+
function processConditionals(content, vars) {
|
|
103
|
+
const ifRegex = /\{\{#if\s+(\w+)\}\}([\s\S]*?)\{\{\/if\}\}/g;
|
|
104
|
+
return content.replace(ifRegex, (match, varName, innerContent) => {
|
|
105
|
+
const value = vars[varName];
|
|
106
|
+
if (value && value.trim() !== "") {
|
|
107
|
+
return innerContent;
|
|
108
|
+
}
|
|
109
|
+
return "";
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
function writeRenderedTemplate(templateName, destPath, vars = {}, lang = "zh") {
|
|
113
|
+
const content = renderTemplate(templateName, vars, lang);
|
|
114
|
+
ensureDir(dirname2(destPath));
|
|
115
|
+
writeFileSync(destPath, content, "utf-8");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// src/core/lint.ts
|
|
119
|
+
import { readFileSync as readFileSync3, existsSync as existsSync4, readdirSync } from "fs";
|
|
120
|
+
import { join as join4, basename } from "path";
|
|
121
|
+
|
|
122
|
+
// src/core/validate.ts
|
|
123
|
+
import { readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
|
|
124
|
+
import { join as join5 } from "path";
|
|
125
|
+
|
|
126
|
+
// src/core/context.ts
|
|
127
|
+
import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
|
|
128
|
+
import { join as join6 } from "path";
|
|
129
|
+
|
|
130
|
+
// src/utils/date.ts
|
|
131
|
+
function getDateString() {
|
|
132
|
+
const d = /* @__PURE__ */ new Date();
|
|
133
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// src/utils/git.ts
|
|
137
|
+
import { execSync } from "child_process";
|
|
138
|
+
function isGitRepo() {
|
|
139
|
+
try {
|
|
140
|
+
execSync("git rev-parse --is-inside-work-tree", { stdio: "ignore" });
|
|
141
|
+
return true;
|
|
142
|
+
} catch {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
var SAFE_BRANCH_RE = /^[a-zA-Z0-9._\-/]+$/;
|
|
147
|
+
function createBranch(branchName) {
|
|
148
|
+
if (!SAFE_BRANCH_RE.test(branchName)) {
|
|
149
|
+
throw new Error(`invalid branch name: ${branchName}`);
|
|
150
|
+
}
|
|
151
|
+
execSync(`git checkout -b ${branchName}`, { stdio: "inherit" });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/commands/init.ts
|
|
155
|
+
import { existsSync as existsSync8, writeFileSync as writeFileSync3, readdirSync as readdirSync4 } from "fs";
|
|
156
|
+
import { join as join8 } from "path";
|
|
157
|
+
import { execSync as execSync2 } from "child_process";
|
|
158
|
+
|
|
159
|
+
// src/prompts/index.ts
|
|
160
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync2, readdirSync as readdirSync3 } from "fs";
|
|
161
|
+
import { join as join7 } from "path";
|
|
162
|
+
|
|
163
|
+
// src/ui/index.ts
|
|
164
|
+
import chalk from "chalk";
|
|
165
|
+
var theme = {
|
|
166
|
+
primary: chalk.hex("#6366f1"),
|
|
167
|
+
success: chalk.hex("#22c55e"),
|
|
168
|
+
warning: chalk.hex("#f59e0b"),
|
|
169
|
+
error: chalk.hex("#ef4444"),
|
|
170
|
+
info: chalk.hex("#3b82f6"),
|
|
171
|
+
boost: chalk.hex("#a855f7"),
|
|
172
|
+
dim: chalk.hex("#6b7280"),
|
|
173
|
+
highlight: chalk.hex("#f472b6"),
|
|
174
|
+
border: chalk.hex("#374151"),
|
|
175
|
+
gradient1: chalk.hex("#818cf8"),
|
|
176
|
+
gradient2: chalk.hex("#6366f1"),
|
|
177
|
+
gradient3: chalk.hex("#4f46e5")
|
|
178
|
+
};
|
|
179
|
+
var logo = {
|
|
180
|
+
small: `
|
|
181
|
+
${theme.gradient1(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557")}
|
|
182
|
+
${theme.gradient2(" \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D")}
|
|
183
|
+
${theme.gradient3(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 ")}
|
|
184
|
+
${theme.gradient2(" \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551 ")}
|
|
185
|
+
${theme.gradient1(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}
|
|
186
|
+
${theme.gradient1(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D")}
|
|
187
|
+
`,
|
|
188
|
+
tiny: `
|
|
189
|
+
${theme.gradient1(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557")}
|
|
190
|
+
${theme.gradient2(" \u255A\u2550\u2550\u2550\u2550\u2550\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2550\u2550\u2550\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D")}
|
|
191
|
+
${theme.gradient3(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 ")} ${theme.highlight("Spec-Driven Development")}
|
|
192
|
+
${theme.gradient2(" \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551 ")}
|
|
193
|
+
${theme.gradient1(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}
|
|
194
|
+
${theme.gradient1(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D")}
|
|
195
|
+
`
|
|
196
|
+
};
|
|
197
|
+
var box = {
|
|
198
|
+
topLeft: "\u256D",
|
|
199
|
+
topRight: "\u256E",
|
|
200
|
+
bottomLeft: "\u2570",
|
|
201
|
+
bottomRight: "\u256F",
|
|
202
|
+
horizontal: "\u2500",
|
|
203
|
+
vertical: "\u2502"
|
|
204
|
+
};
|
|
205
|
+
function boxText(text, width = 50) {
|
|
206
|
+
const padding = " ".repeat(Math.max(0, width - text.length - 4));
|
|
207
|
+
return `${theme.border(box.vertical)} ${theme.highlight(text)}${padding} ${theme.border(box.vertical)}`;
|
|
208
|
+
}
|
|
209
|
+
function createBox(lines, width = 52) {
|
|
210
|
+
const top = theme.border(`${box.topLeft}${box.horizontal.repeat(width - 2)}${box.topRight}`);
|
|
211
|
+
const bottom = theme.border(`${box.bottomLeft}${box.horizontal.repeat(width - 2)}${box.bottomRight}`);
|
|
212
|
+
const middle = lines.map((line) => boxText(line, width));
|
|
213
|
+
return [top, ...middle, bottom].join("\n");
|
|
214
|
+
}
|
|
215
|
+
var log = {
|
|
216
|
+
info: (msg) => console.log(theme.info(msg)),
|
|
217
|
+
success: (msg) => console.log(theme.success(msg)),
|
|
218
|
+
warn: (msg) => console.log(theme.warning(msg)),
|
|
219
|
+
error: (msg) => console.log(theme.error(msg)),
|
|
220
|
+
dim: (msg) => console.log(theme.dim(msg)),
|
|
221
|
+
boost: (msg) => console.log(theme.boost(msg)),
|
|
222
|
+
highlight: (msg) => console.log(theme.highlight(msg)),
|
|
223
|
+
title: (msg) => {
|
|
224
|
+
console.log();
|
|
225
|
+
console.log(createBox([msg]));
|
|
226
|
+
console.log();
|
|
227
|
+
},
|
|
228
|
+
section: (msg) => {
|
|
229
|
+
console.log();
|
|
230
|
+
console.log(theme.primary(`\u25C6 ${msg}`));
|
|
231
|
+
console.log(theme.border("\u2500".repeat(50)));
|
|
232
|
+
},
|
|
233
|
+
done: (msg) => {
|
|
234
|
+
console.log();
|
|
235
|
+
console.log(theme.success(`\u2728 ${msg}`));
|
|
236
|
+
console.log();
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
var symbol = {
|
|
240
|
+
start: theme.primary("\u25C6"),
|
|
241
|
+
ok: theme.success("\u2713"),
|
|
242
|
+
fail: theme.error("\u2717"),
|
|
243
|
+
warn: theme.warning("\u26A0"),
|
|
244
|
+
bolt: theme.boost("\u26A1"),
|
|
245
|
+
arrow: theme.dim("\u2192"),
|
|
246
|
+
bullet: theme.dim("\u2022"),
|
|
247
|
+
sparkle: theme.highlight("\u2728"),
|
|
248
|
+
folder: theme.primary("\u{1F4C1}"),
|
|
249
|
+
file: theme.info("\u{1F4C4}"),
|
|
250
|
+
git: theme.warning("\u{1F33F}"),
|
|
251
|
+
ai: theme.boost("\u{1F916}")
|
|
252
|
+
};
|
|
253
|
+
function printLogo(size = "small") {
|
|
254
|
+
console.log(logo[size]);
|
|
255
|
+
}
|
|
256
|
+
function printSummary(items) {
|
|
257
|
+
const maxLabel = Math.max(...items.map((i) => i.label.length));
|
|
258
|
+
const width = 50;
|
|
259
|
+
console.log(theme.border("\u256D" + "\u2500".repeat(width - 2) + "\u256E"));
|
|
260
|
+
for (const { label, value } of items) {
|
|
261
|
+
const padding = " ".repeat(maxLabel - label.length);
|
|
262
|
+
const line = `${theme.dim(label)}${padding} ${symbol.arrow} ${theme.highlight(value)}`;
|
|
263
|
+
const plainLine = line.replace(/\u001b\[\d+m/g, "");
|
|
264
|
+
const rightPad = " ".repeat(Math.max(0, width - plainLine.length - 4));
|
|
265
|
+
console.log(theme.border("\u2502 ") + line + rightPad + theme.border(" \u2502"));
|
|
266
|
+
}
|
|
267
|
+
console.log(theme.border("\u2570" + "\u2500".repeat(width - 2) + "\u256F"));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// src/prompts/index.ts
|
|
271
|
+
var AI_EDITORS = {
|
|
272
|
+
claude: {
|
|
273
|
+
commands: ".claude/commands",
|
|
274
|
+
rules: null
|
|
275
|
+
// Claude doesn't use rules files
|
|
276
|
+
},
|
|
277
|
+
cursor: {
|
|
278
|
+
commands: ".cursor/commands",
|
|
279
|
+
rules: ".cursor/rules",
|
|
280
|
+
rulesFile: "superspec.mdc"
|
|
281
|
+
},
|
|
282
|
+
qwen: {
|
|
283
|
+
commands: ".qwen/commands",
|
|
284
|
+
rules: ".qwen/rules",
|
|
285
|
+
rulesFile: "superspec.md"
|
|
286
|
+
},
|
|
287
|
+
opencode: {
|
|
288
|
+
commands: ".opencode/commands",
|
|
289
|
+
rules: null
|
|
290
|
+
},
|
|
291
|
+
codex: {
|
|
292
|
+
commands: ".codex/commands",
|
|
293
|
+
rules: null
|
|
294
|
+
},
|
|
295
|
+
codebuddy: {
|
|
296
|
+
commands: ".codebuddy/commands",
|
|
297
|
+
rules: ".codebuddy/rules",
|
|
298
|
+
rulesFile: "superspec.md"
|
|
299
|
+
},
|
|
300
|
+
qoder: {
|
|
301
|
+
commands: ".qoder/commands",
|
|
302
|
+
rules: ".qoder/rules",
|
|
303
|
+
rulesFile: "superspec.md"
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
function installRules(cwd, editor) {
|
|
307
|
+
const config = AI_EDITORS[editor];
|
|
308
|
+
if (!config.rules) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
const rulesDir = join7(cwd, config.rules);
|
|
312
|
+
ensureDir(rulesDir);
|
|
313
|
+
const promptSrc = join7(getPackageRoot(), "prompts", "cursor-rules.md");
|
|
314
|
+
if (existsSync7(promptSrc)) {
|
|
315
|
+
const content = readFileSync6(promptSrc, "utf-8");
|
|
316
|
+
const rulesFile = "rulesFile" in config ? config.rulesFile : "superspec.md";
|
|
317
|
+
const destPath = join7(rulesDir, rulesFile);
|
|
318
|
+
writeFileSync2(destPath, content, "utf-8");
|
|
319
|
+
log.success(`${symbol.ok} ${config.rules}/${rulesFile}`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
var SS_START = "<!-- superspec:start -->";
|
|
323
|
+
var SS_END = "<!-- superspec:end -->";
|
|
324
|
+
function installAgentsMd(cwd) {
|
|
325
|
+
const agentsMdPath = join7(cwd, "AGENTS.md");
|
|
326
|
+
const agentPromptSrc = join7(getPackageRoot(), "prompts", "agents.md");
|
|
327
|
+
if (!existsSync7(agentPromptSrc)) return;
|
|
328
|
+
const newContent = readFileSync6(agentPromptSrc, "utf-8");
|
|
329
|
+
const wrapped = `${SS_START}
|
|
330
|
+
${newContent}
|
|
331
|
+
${SS_END}`;
|
|
332
|
+
if (existsSync7(agentsMdPath)) {
|
|
333
|
+
const existing = readFileSync6(agentsMdPath, "utf-8");
|
|
334
|
+
const startIdx = existing.indexOf(SS_START);
|
|
335
|
+
const endIdx = existing.indexOf(SS_END);
|
|
336
|
+
if (startIdx !== -1 && endIdx !== -1) {
|
|
337
|
+
const before = existing.slice(0, startIdx);
|
|
338
|
+
const after = existing.slice(endIdx + SS_END.length);
|
|
339
|
+
writeFileSync2(agentsMdPath, before + wrapped + after, "utf-8");
|
|
340
|
+
} else if (existing.includes("SuperSpec")) {
|
|
341
|
+
writeFileSync2(agentsMdPath, existing, "utf-8");
|
|
342
|
+
} else {
|
|
343
|
+
writeFileSync2(agentsMdPath, existing + "\n\n" + wrapped, "utf-8");
|
|
344
|
+
}
|
|
345
|
+
} else {
|
|
346
|
+
writeFileSync2(agentsMdPath, wrapped, "utf-8");
|
|
347
|
+
}
|
|
348
|
+
log.success(`${symbol.ok} AGENTS.md`);
|
|
349
|
+
}
|
|
350
|
+
function installCommands(cwd, editor, lang = "zh") {
|
|
351
|
+
const config = AI_EDITORS[editor];
|
|
352
|
+
const commandsDir = join7(cwd, config.commands);
|
|
353
|
+
ensureDir(commandsDir);
|
|
354
|
+
const templatesDir = join7(getPackageRoot(), "templates", lang, "commands");
|
|
355
|
+
const fallbackDir = join7(getPackageRoot(), "templates", "zh", "commands");
|
|
356
|
+
const sourceDir = existsSync7(templatesDir) ? templatesDir : fallbackDir;
|
|
357
|
+
if (!existsSync7(sourceDir)) {
|
|
358
|
+
log.warn(`${symbol.warn} Commands templates not found: ${sourceDir}`);
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
const commandFiles = readdirSync3(sourceDir).filter((f) => f.endsWith(".md"));
|
|
362
|
+
for (const file of commandFiles) {
|
|
363
|
+
const srcPath = join7(sourceDir, file);
|
|
364
|
+
const destPath = join7(commandsDir, file);
|
|
365
|
+
const content = readFileSync6(srcPath, "utf-8");
|
|
366
|
+
writeFileSync2(destPath, content, "utf-8");
|
|
367
|
+
}
|
|
368
|
+
log.success(`${symbol.ok} ${config.commands}/ (${commandFiles.length} commands)`);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// src/commands/init.ts
|
|
372
|
+
async function initCommand(options) {
|
|
373
|
+
const cwd = process.cwd();
|
|
374
|
+
const configPath = join8(cwd, "superspec.config.json");
|
|
375
|
+
if (existsSync8(configPath) && !options.force) {
|
|
376
|
+
log.warn(`${symbol.warn} superspec.config.json already exists, use --force to overwrite`);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const lang = options.lang || "zh";
|
|
380
|
+
printLogo("small");
|
|
381
|
+
console.log(theme.dim(" Spec-Driven Development Toolkit\n"));
|
|
382
|
+
const config = getDefaultConfig();
|
|
383
|
+
config.lang = lang;
|
|
384
|
+
const aiEditor = options.ai;
|
|
385
|
+
if (aiEditor && AI_EDITORS[aiEditor]) {
|
|
386
|
+
config.aiEditor = aiEditor;
|
|
387
|
+
}
|
|
388
|
+
const specDir = join8(cwd, config.specDir);
|
|
389
|
+
const existingFiles = readdirSync4(cwd).filter((f) => !f.startsWith(".") && f !== "node_modules");
|
|
390
|
+
if (existingFiles.length > 0 && !options.force) {
|
|
391
|
+
log.warn(`${symbol.warn} Current directory is not empty (${existingFiles.length} items)`);
|
|
392
|
+
log.dim(" Template files will be merged with existing content");
|
|
393
|
+
console.log();
|
|
394
|
+
}
|
|
395
|
+
log.section("Creating Configuration");
|
|
396
|
+
writeFileSync3(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
397
|
+
log.success(`${symbol.file} superspec.config.json`);
|
|
398
|
+
log.section("Creating Directory Structure");
|
|
399
|
+
ensureDir(join8(specDir, "changes"));
|
|
400
|
+
ensureDir(join8(specDir, "templates"));
|
|
401
|
+
log.success(`${symbol.folder} ${config.specDir}/changes/`);
|
|
402
|
+
log.success(`${symbol.folder} ${config.specDir}/templates/`);
|
|
403
|
+
log.section("Installing Templates");
|
|
404
|
+
const templates = ["spec.md", "proposal.md", "tasks.md", "clarify.md", "checklist.md"];
|
|
405
|
+
for (const tpl of templates) {
|
|
406
|
+
copyTemplate(tpl, join8(specDir, "templates", tpl), lang);
|
|
407
|
+
}
|
|
408
|
+
log.success(`${symbol.ok} ${templates.length} templates (${lang})`);
|
|
409
|
+
log.section("Installing AI Agent Files");
|
|
410
|
+
installAgentsMd(cwd);
|
|
411
|
+
if (aiEditor && AI_EDITORS[aiEditor]) {
|
|
412
|
+
installRules(cwd, aiEditor);
|
|
413
|
+
installCommands(cwd, aiEditor, lang);
|
|
414
|
+
}
|
|
415
|
+
if (options.git !== false && !isGitRepo()) {
|
|
416
|
+
execSync2("git init", { cwd, stdio: "inherit" });
|
|
417
|
+
log.success(`${symbol.git} git init`);
|
|
418
|
+
}
|
|
419
|
+
console.log();
|
|
420
|
+
printSummary([
|
|
421
|
+
{ label: "Config", value: "superspec.config.json" },
|
|
422
|
+
{ label: "Spec dir", value: `${config.specDir}/` },
|
|
423
|
+
{ label: "AI agent", value: options.ai },
|
|
424
|
+
{ label: "Language", value: lang }
|
|
425
|
+
]);
|
|
426
|
+
log.done("SuperSpec initialized successfully!");
|
|
427
|
+
log.dim("Next: Run superspec create <name> to create a change");
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// src/commands/create.ts
|
|
431
|
+
import { existsSync as existsSync9 } from "fs";
|
|
432
|
+
import { join as join9 } from "path";
|
|
433
|
+
async function createCommand(name, options) {
|
|
434
|
+
const cwd = process.cwd();
|
|
435
|
+
const config = loadConfig(cwd);
|
|
436
|
+
const specDir = options.specDir || config.specDir;
|
|
437
|
+
const branchPrefix = options.branchPrefix || config.branchPrefix;
|
|
438
|
+
const boost = options.boost || config.boost;
|
|
439
|
+
const strategy = options.creative ? "create" : config.strategy;
|
|
440
|
+
const lang = config.lang || "zh";
|
|
441
|
+
const description = options.description || "";
|
|
442
|
+
const changePath = join9(cwd, specDir, "changes", name);
|
|
443
|
+
if (existsSync9(changePath)) {
|
|
444
|
+
log.warn(`${symbol.warn} Change "${name}" already exists: ${changePath}`);
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
log.title(`Creating Change: ${name}`);
|
|
448
|
+
if (boost) {
|
|
449
|
+
log.boost(`${symbol.bolt} Boost mode enabled`);
|
|
450
|
+
}
|
|
451
|
+
if (strategy === "create") {
|
|
452
|
+
log.boost(`${symbol.bolt} Creative mode: exploring new solutions`);
|
|
453
|
+
}
|
|
454
|
+
ensureDir(changePath);
|
|
455
|
+
const vars = {
|
|
456
|
+
name,
|
|
457
|
+
date: getDateString(),
|
|
458
|
+
boost: boost ? "true" : "false",
|
|
459
|
+
strategy,
|
|
460
|
+
description
|
|
461
|
+
};
|
|
462
|
+
const artifacts = boost ? config.boostArtifacts : config.artifacts;
|
|
463
|
+
log.section("Generating Artifacts");
|
|
464
|
+
for (const artifact of artifacts) {
|
|
465
|
+
const templateFile = config.templates[artifact] || `${artifact}.md`;
|
|
466
|
+
const destPath = join9(changePath, `${artifact}.md`);
|
|
467
|
+
try {
|
|
468
|
+
writeRenderedTemplate(templateFile, destPath, vars, lang);
|
|
469
|
+
log.success(`${symbol.ok} ${artifact}.md`);
|
|
470
|
+
} catch (e) {
|
|
471
|
+
log.error(`${symbol.fail} ${artifact}.md: ${e.message}`);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
if (options.branch !== false && isGitRepo()) {
|
|
475
|
+
const branchTemplate = config.branchTemplate || "{prefix}{name}";
|
|
476
|
+
const branchName = branchTemplate.replace("{prefix}", branchPrefix).replace("{name}", name);
|
|
477
|
+
try {
|
|
478
|
+
createBranch(branchName);
|
|
479
|
+
log.success(`${symbol.ok} Branch: ${branchName}`);
|
|
480
|
+
} catch (e) {
|
|
481
|
+
log.warn(`${symbol.warn} Branch creation failed: ${e.message}`);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
log.done("Change created successfully!");
|
|
485
|
+
log.dim(`Path: ${specDir}/changes/${name}/`);
|
|
486
|
+
if (boost) {
|
|
487
|
+
log.dim(`Workflow: /ss-create \u2192 /ss-tasks \u2192 /ss-apply (boost)`);
|
|
488
|
+
} else {
|
|
489
|
+
log.dim(`Workflow: /ss-tasks \u2192 /ss-apply`);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// src/commands/archive.ts
|
|
494
|
+
import { existsSync as existsSync10, readdirSync as readdirSync5, renameSync } from "fs";
|
|
495
|
+
import { join as join10 } from "path";
|
|
496
|
+
async function archiveCommand(name, options) {
|
|
497
|
+
const cwd = process.cwd();
|
|
498
|
+
const config = loadConfig(cwd);
|
|
499
|
+
const changesDir = join10(cwd, config.specDir, "changes");
|
|
500
|
+
const archiveDir = join10(cwd, config.specDir, "changes", config.archive.dir);
|
|
501
|
+
if (!existsSync10(changesDir)) {
|
|
502
|
+
log.warn(`${symbol.warn} \u6CA1\u6709\u627E\u5230 changes \u76EE\u5F55`);
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
if (options.all) {
|
|
506
|
+
const entries = readdirSync5(changesDir, { withFileTypes: true }).filter(
|
|
507
|
+
(e) => e.isDirectory() && e.name !== config.archive.dir
|
|
508
|
+
);
|
|
509
|
+
if (entries.length === 0) {
|
|
510
|
+
log.warn(`${symbol.warn} \u6CA1\u6709\u53EF\u5F52\u6863\u7684\u53D8\u66F4`);
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
log.info(`${symbol.start} \u5F52\u6863\u6240\u6709\u53D8\u66F4...`);
|
|
514
|
+
for (const entry of entries) {
|
|
515
|
+
archiveOne(entry.name, changesDir, archiveDir, config);
|
|
516
|
+
}
|
|
517
|
+
} else if (name) {
|
|
518
|
+
const changePath = join10(changesDir, name);
|
|
519
|
+
if (!existsSync10(changePath)) {
|
|
520
|
+
log.warn(`${symbol.warn} \u53D8\u66F4 "${name}" \u4E0D\u5B58\u5728`);
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
log.info(`${symbol.start} \u5F52\u6863\u53D8\u66F4: ${name}`);
|
|
524
|
+
archiveOne(name, changesDir, archiveDir, config);
|
|
525
|
+
} else {
|
|
526
|
+
log.warn(`${symbol.warn} \u8BF7\u6307\u5B9A\u53D8\u66F4\u540D\u79F0\u6216\u4F7F\u7528 --all`);
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
log.info(`${symbol.start} \u5F52\u6863\u5B8C\u6210\uFF01`);
|
|
530
|
+
}
|
|
531
|
+
function archiveOne(name, changesDir, archiveDir, config) {
|
|
532
|
+
ensureDir(archiveDir);
|
|
533
|
+
const src = join10(changesDir, name);
|
|
534
|
+
const dateStr = config.archive.datePrefix ? `${getDateString()}-` : "";
|
|
535
|
+
const dest = join10(archiveDir, `${dateStr}${name}`);
|
|
536
|
+
if (existsSync10(dest)) {
|
|
537
|
+
log.warn(` ${symbol.warn} \u5F52\u6863\u76EE\u6807\u5DF2\u5B58\u5728: ${dest}`);
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
renameSync(src, dest);
|
|
541
|
+
log.success(` ${symbol.ok} ${name} \u2192 archive/${dateStr}${name}`);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// src/commands/update.ts
|
|
545
|
+
import { existsSync as existsSync11 } from "fs";
|
|
546
|
+
import { join as join11 } from "path";
|
|
547
|
+
async function updateCommand() {
|
|
548
|
+
const cwd = process.cwd();
|
|
549
|
+
const config = loadConfig(cwd);
|
|
550
|
+
const specDir = join11(cwd, config.specDir);
|
|
551
|
+
const lang = config.lang || "zh";
|
|
552
|
+
if (!existsSync11(join11(cwd, "superspec.config.json"))) {
|
|
553
|
+
log.warn(`${symbol.warn} \u5F53\u524D\u76EE\u5F55\u672A\u521D\u59CB\u5316 SuperSpec\uFF0C\u8BF7\u5148\u8FD0\u884C superspec init`);
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
log.info(`${symbol.start} \u66F4\u65B0 SuperSpec...`);
|
|
557
|
+
const templates = ["spec.md", "proposal.md", "tasks.md", "clarify.md", "checklist.md"];
|
|
558
|
+
ensureDir(join11(specDir, "templates"));
|
|
559
|
+
for (const tpl of templates) {
|
|
560
|
+
copyTemplate(tpl, join11(specDir, "templates", tpl), lang);
|
|
561
|
+
}
|
|
562
|
+
log.success(` ${symbol.ok} \u6A21\u677F\u66F4\u65B0 (${lang})`);
|
|
563
|
+
installAgentsMd(cwd);
|
|
564
|
+
const aiEditor = config.aiEditor;
|
|
565
|
+
if (aiEditor && AI_EDITORS[aiEditor]) {
|
|
566
|
+
installRules(cwd, aiEditor);
|
|
567
|
+
installCommands(cwd, aiEditor, lang);
|
|
568
|
+
}
|
|
569
|
+
log.info(`${symbol.start} \u66F4\u65B0\u5B8C\u6210\uFF01`);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// src/commands/lint.ts
|
|
573
|
+
import { existsSync as existsSync12, readdirSync as readdirSync6 } from "fs";
|
|
574
|
+
import { join as join12 } from "path";
|
|
575
|
+
|
|
576
|
+
// src/commands/validate.ts
|
|
577
|
+
import { existsSync as existsSync13, readdirSync as readdirSync7 } from "fs";
|
|
578
|
+
import { join as join13 } from "path";
|
|
579
|
+
|
|
580
|
+
// src/commands/search.ts
|
|
581
|
+
import { existsSync as existsSync14, readFileSync as readFileSync7, readdirSync as readdirSync8 } from "fs";
|
|
582
|
+
import { join as join14, basename as basename3 } from "path";
|
|
583
|
+
|
|
584
|
+
// src/commands/link.ts
|
|
585
|
+
import { existsSync as existsSync15, readFileSync as readFileSync8, writeFileSync as writeFileSync4, readdirSync as readdirSync9 } from "fs";
|
|
586
|
+
import { join as join15 } from "path";
|
|
587
|
+
|
|
588
|
+
// src/commands/status.ts
|
|
589
|
+
import { existsSync as existsSync16, readFileSync as readFileSync9, readdirSync as readdirSync10 } from "fs";
|
|
590
|
+
import { join as join16 } from "path";
|
|
591
|
+
|
|
592
|
+
// src/commands/context.ts
|
|
593
|
+
import { existsSync as existsSync17, writeFileSync as writeFileSync5, readdirSync as readdirSync11 } from "fs";
|
|
594
|
+
import { join as join17 } from "path";
|
|
595
|
+
|
|
596
|
+
// src/commands/sync.ts
|
|
597
|
+
import { existsSync as existsSync18, writeFileSync as writeFileSync6, readdirSync as readdirSync12 } from "fs";
|
|
598
|
+
import { join as join18 } from "path";
|
|
599
|
+
export {
|
|
600
|
+
archiveCommand,
|
|
601
|
+
createCommand,
|
|
602
|
+
getDefaultConfig,
|
|
603
|
+
initCommand,
|
|
604
|
+
loadConfig,
|
|
605
|
+
updateCommand
|
|
606
|
+
};
|
|
607
|
+
//# sourceMappingURL=index.js.map
|