6aspec 2.0.0-dev.2
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/.6aspec/rules/biz/api_rule.md +578 -0
- package/.6aspec/rules/biz/background_job_rule.md +719 -0
- package/.6aspec/rules/biz/c_user_system_rule.md +240 -0
- package/.6aspec/rules/biz/code.md +39 -0
- package/.6aspec/rules/biz/event_subscriber_rule.md +529 -0
- package/.6aspec/rules/biz/project-structure.md +90 -0
- package/.6aspec/rules/biz/scheduled_job_rule.md +850 -0
- package/.6aspec/rules/brown/brown_archive_sop.md +132 -0
- package/.6aspec/rules/brown/brown_constitution.md +20 -0
- package/.6aspec/rules/brown/brown_continue_sop.md +97 -0
- package/.6aspec/rules/brown/brown_design_sop.md +155 -0
- package/.6aspec/rules/brown/brown_ff_sop.md +194 -0
- package/.6aspec/rules/brown/brown_impact_sop.md +296 -0
- package/.6aspec/rules/brown/brown_implement_sop.md +133 -0
- package/.6aspec/rules/brown/brown_list_sop.md +69 -0
- package/.6aspec/rules/brown/brown_new_sop.md +257 -0
- package/.6aspec/rules/brown/brown_proposal_sop.md +160 -0
- package/.6aspec/rules/brown/brown_quick_sop.md +134 -0
- package/.6aspec/rules/brown/brown_review_sop.md +270 -0
- package/.6aspec/rules/brown/brown_rollback_sop.md +188 -0
- package/.6aspec/rules/brown/brown_specs_sop.md +227 -0
- package/.6aspec/rules/brown/brown_status_sop.md +135 -0
- package/.6aspec/rules/brown/brown_tasks_sop.md +202 -0
- package/.6aspec/rules/brown/brown_understand_sop.md +211 -0
- package/.6aspec/rules/brown/brown_verify_sop.md +360 -0
- package/.6aspec/rules/green/6A_archive_sop.md +301 -0
- package/.6aspec/rules/green/6A_clarify_sop.md +238 -0
- package/.6aspec/rules/green/6A_code_implementation_sop.md +110 -0
- package/.6aspec/rules/green/6A_constitution.md +52 -0
- package/.6aspec/rules/green/6A_continue_sop.md +186 -0
- package/.6aspec/rules/green/6A_design_sop.md +228 -0
- package/.6aspec/rules/green/6A_import_model_table_sop.md +120 -0
- package/.6aspec/rules/green/6A_init_event_list_sop.md +62 -0
- package/.6aspec/rules/green/6A_init_map_sop.md +79 -0
- package/.6aspec/rules/green/6A_model_sop.md +210 -0
- package/.6aspec/rules/green/6A_new_sop.md +319 -0
- package/.6aspec/rules/green/6A_rollback_sop.md +198 -0
- package/.6aspec/rules/green/6A_status_sop.md +275 -0
- package/.6aspec/rules/green/6A_tasks_sop.md +181 -0
- package/.6aspec/rules/green/6A_visual_logic_sop.md +121 -0
- package/.6aspec/rules/green/green_status_schema.md +293 -0
- package/.6aspec/script/create_entities_from_markdown.py +688 -0
- package/.claude/commands/6aspec/brown/archive.md +11 -0
- package/.claude/commands/6aspec/brown/continue.md +11 -0
- package/.claude/commands/6aspec/brown/design.md +11 -0
- package/.claude/commands/6aspec/brown/ff.md +11 -0
- package/.claude/commands/6aspec/brown/impact.md +11 -0
- package/.claude/commands/6aspec/brown/implement.md +11 -0
- package/.claude/commands/6aspec/brown/list.md +11 -0
- package/.claude/commands/6aspec/brown/new.md +11 -0
- package/.claude/commands/6aspec/brown/proposal.md +11 -0
- package/.claude/commands/6aspec/brown/quick.md +11 -0
- package/.claude/commands/6aspec/brown/review.md +11 -0
- package/.claude/commands/6aspec/brown/rollback.md +11 -0
- package/.claude/commands/6aspec/brown/specs.md +11 -0
- package/.claude/commands/6aspec/brown/status.md +11 -0
- package/.claude/commands/6aspec/brown/tasks.md +11 -0
- package/.claude/commands/6aspec/brown/understand.md +11 -0
- package/.claude/commands/6aspec/brown/verify.md +11 -0
- package/.claude/commands/6aspec/green/archive.md +8 -0
- package/.claude/commands/6aspec/green/clarify.md +13 -0
- package/.claude/commands/6aspec/green/continue.md +8 -0
- package/.claude/commands/6aspec/green/design.md +8 -0
- package/.claude/commands/6aspec/green/execute-task.md +20 -0
- package/.claude/commands/6aspec/green/import-model-table.md +8 -0
- package/.claude/commands/6aspec/green/init.md +14 -0
- package/.claude/commands/6aspec/green/model.md +12 -0
- package/.claude/commands/6aspec/green/new.md +13 -0
- package/.claude/commands/6aspec/green/rollback.md +8 -0
- package/.claude/commands/6aspec/green/status.md +8 -0
- package/.claude/commands/6aspec/green/tasks.md +8 -0
- package/.claude/commands/6aspec/green/visual-logic.md +9 -0
- package/.claude/settings.local.json +8 -0
- package/.cursor/commands/6aspec/brown/archive.md +9 -0
- package/.cursor/commands/6aspec/brown/continue.md +9 -0
- package/.cursor/commands/6aspec/brown/design.md +9 -0
- package/.cursor/commands/6aspec/brown/ff.md +9 -0
- package/.cursor/commands/6aspec/brown/impact.md +9 -0
- package/.cursor/commands/6aspec/brown/implement.md +9 -0
- package/.cursor/commands/6aspec/brown/list.md +9 -0
- package/.cursor/commands/6aspec/brown/new.md +9 -0
- package/.cursor/commands/6aspec/brown/proposal.md +9 -0
- package/.cursor/commands/6aspec/brown/quick.md +9 -0
- package/.cursor/commands/6aspec/brown/review.md +9 -0
- package/.cursor/commands/6aspec/brown/rollback.md +9 -0
- package/.cursor/commands/6aspec/brown/specs.md +9 -0
- package/.cursor/commands/6aspec/brown/status.md +9 -0
- package/.cursor/commands/6aspec/brown/tasks.md +9 -0
- package/.cursor/commands/6aspec/brown/understand.md +9 -0
- package/.cursor/commands/6aspec/brown/verify.md +9 -0
- package/.cursor/commands/6aspec/green/archive.md +9 -0
- package/.cursor/commands/6aspec/green/clarify.md +14 -0
- package/.cursor/commands/6aspec/green/continue.md +9 -0
- package/.cursor/commands/6aspec/green/design.md +9 -0
- package/.cursor/commands/6aspec/green/execute-task.md +21 -0
- package/.cursor/commands/6aspec/green/import-model-table.md +9 -0
- package/.cursor/commands/6aspec/green/init.md +15 -0
- package/.cursor/commands/6aspec/green/model.md +13 -0
- package/.cursor/commands/6aspec/green/new.md +14 -0
- package/.cursor/commands/6aspec/green/rollback.md +9 -0
- package/.cursor/commands/6aspec/green/status.md +9 -0
- package/.cursor/commands/6aspec/green/tasks.md +9 -0
- package/.cursor/commands/6aspec/green/visual-logic.md +10 -0
- package/README.en.md +36 -0
- package/README.md +146 -0
- package/bin/6a-spec-install +54 -0
- package/bin/6aspec +48 -0
- package/lib/cli.js +318 -0
- package/lib/installer.js +333 -0
- package/package.json +62 -0
package/lib/cli.js
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const readline = require('readline');
|
|
4
|
+
const installer = require('./installer');
|
|
5
|
+
|
|
6
|
+
const CYAN = '\x1b[36m';
|
|
7
|
+
const GREEN = '\x1b[32m';
|
|
8
|
+
const YELLOW = '\x1b[33m';
|
|
9
|
+
const DIM = '\x1b[2m';
|
|
10
|
+
const BOLD = '\x1b[1m';
|
|
11
|
+
const RESET = '\x1b[0m';
|
|
12
|
+
const WHITE = '\x1b[37m';
|
|
13
|
+
const MAGENTA = '\x1b[35m';
|
|
14
|
+
|
|
15
|
+
const CONFIG_FILE = '.6aspec/init.json';
|
|
16
|
+
|
|
17
|
+
const TOOLS = [
|
|
18
|
+
{ id: 'cursor', name: 'Cursor', desc: 'Cursor IDE 配置' },
|
|
19
|
+
{ id: 'claude', name: 'Claude Code', desc: 'Claude Code CLI 配置' },
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
// ─── Welcome Screen ──────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
function buildWelcomeScreen() {
|
|
25
|
+
const logo = [
|
|
26
|
+
`${CYAN} ████ ${RESET}`,
|
|
27
|
+
`${CYAN} ░░ ░░ ${RESET}`,
|
|
28
|
+
`${CYAN} ████ ${RESET}`,
|
|
29
|
+
`${CYAN} ████ ${RESET}`,
|
|
30
|
+
`${CYAN} ████ ${RESET}`,
|
|
31
|
+
`${CYAN} ░░ ░░ ${RESET}`,
|
|
32
|
+
`${CYAN} ████ ${RESET}`,
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const right = [
|
|
36
|
+
`${BOLD}${WHITE}Welcome to 6Aspec${RESET}`,
|
|
37
|
+
`${DIM}A lightweight spec-driven framework${RESET}`,
|
|
38
|
+
``,
|
|
39
|
+
`${YELLOW}This setup will configure:${RESET}`,
|
|
40
|
+
` ${GREEN}•${RESET} Agent Skills for AI tools`,
|
|
41
|
+
` ${GREEN}•${RESET} /6aspec:* slash commands`,
|
|
42
|
+
``,
|
|
43
|
+
`${YELLOW}Quick start after setup:${RESET}`,
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const brownCmds = [
|
|
47
|
+
` ${GREEN}/6aspec:brown:new${RESET} Create a change`,
|
|
48
|
+
` ${GREEN}/6aspec:brown:continue${RESET} Next artifact`,
|
|
49
|
+
` ${GREEN}/6aspec:brown:implement${RESET} Implement tasks`,
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
const greenCmds = [
|
|
53
|
+
` ${GREEN}/6aspec:green:new${RESET} Create a module`,
|
|
54
|
+
` ${GREEN}/6aspec:green:continue${RESET} Next artifact`,
|
|
55
|
+
` ${GREEN}/6aspec:green:execute-task${RESET} Execute tasks`,
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
const footer = `${DIM}Press Enter to select tools...${RESET}`;
|
|
59
|
+
|
|
60
|
+
const lines = [];
|
|
61
|
+
lines.push('');
|
|
62
|
+
|
|
63
|
+
const totalRight = [...right, ``, ` ${MAGENTA}Brownfield (existing project):${RESET}`, ...brownCmds, ``, ` ${MAGENTA}Greenfield (new project):${RESET}`, ...greenCmds];
|
|
64
|
+
|
|
65
|
+
const maxLines = Math.max(logo.length, totalRight.length);
|
|
66
|
+
|
|
67
|
+
for (let i = 0; i < maxLines; i++) {
|
|
68
|
+
const l = i < logo.length ? logo[i] : ' ';
|
|
69
|
+
const r = i < totalRight.length ? totalRight[i] : '';
|
|
70
|
+
lines.push(` ${l} ${r}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
lines.push('');
|
|
74
|
+
lines.push(` ${' '.repeat(20)} ${footer}`);
|
|
75
|
+
lines.push('');
|
|
76
|
+
|
|
77
|
+
return lines.join('\n');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ─── Interactive Tool Selection (pure readline, no deps) ────────────
|
|
81
|
+
|
|
82
|
+
function waitForEnter() {
|
|
83
|
+
return new Promise((resolve) => {
|
|
84
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
85
|
+
rl.question('', () => {
|
|
86
|
+
rl.close();
|
|
87
|
+
resolve();
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function selectTools(preSelected) {
|
|
93
|
+
return new Promise((resolve) => {
|
|
94
|
+
const selected = TOOLS.map((t) => preSelected.includes(t.id));
|
|
95
|
+
let cursor = 0;
|
|
96
|
+
|
|
97
|
+
const raw = process.stdin.isTTY;
|
|
98
|
+
if (raw) {
|
|
99
|
+
process.stdin.setRawMode(true);
|
|
100
|
+
}
|
|
101
|
+
process.stdin.resume();
|
|
102
|
+
process.stdin.setEncoding('utf8');
|
|
103
|
+
|
|
104
|
+
function render() {
|
|
105
|
+
// Move cursor up to re-render (clear previous lines)
|
|
106
|
+
const totalLines = TOOLS.length + 4;
|
|
107
|
+
process.stdout.write(`\x1b[${totalLines}A\x1b[J`);
|
|
108
|
+
draw();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function draw() {
|
|
112
|
+
console.log('');
|
|
113
|
+
console.log(`${BOLD} Select tools to initialize:${RESET} ${DIM}(↑↓ move, Enter select, Tab confirm)${RESET}`);
|
|
114
|
+
console.log('');
|
|
115
|
+
TOOLS.forEach((tool, i) => {
|
|
116
|
+
const check = selected[i] ? `${GREEN}◉${RESET}` : `○`;
|
|
117
|
+
const pointer = i === cursor ? `${CYAN}❯${RESET}` : ' ';
|
|
118
|
+
const locked = preSelected.includes(tool.id) && selected[i] ? ` ${DIM}(installed)${RESET}` : '';
|
|
119
|
+
console.log(` ${pointer} ${check} ${tool.name} ${DIM}- ${tool.desc}${RESET}${locked}`);
|
|
120
|
+
});
|
|
121
|
+
console.log('');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
draw();
|
|
125
|
+
|
|
126
|
+
function onKey(key) {
|
|
127
|
+
if (key === '\u001b[A') {
|
|
128
|
+
// Up
|
|
129
|
+
cursor = (cursor - 1 + TOOLS.length) % TOOLS.length;
|
|
130
|
+
render();
|
|
131
|
+
} else if (key === '\u001b[B') {
|
|
132
|
+
// Down
|
|
133
|
+
cursor = (cursor + 1) % TOOLS.length;
|
|
134
|
+
render();
|
|
135
|
+
} else if (key === '\r' || key === '\n') {
|
|
136
|
+
// Enter = toggle select
|
|
137
|
+
selected[cursor] = !selected[cursor];
|
|
138
|
+
render();
|
|
139
|
+
} else if (key === '\t') {
|
|
140
|
+
// Tab = confirm
|
|
141
|
+
cleanup();
|
|
142
|
+
const result = TOOLS.filter((_, i) => selected[i]).map((t) => t.id);
|
|
143
|
+
resolve(result);
|
|
144
|
+
} else if (key === '\u0003') {
|
|
145
|
+
// Ctrl+C
|
|
146
|
+
cleanup();
|
|
147
|
+
process.exit(0);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function cleanup() {
|
|
152
|
+
process.stdin.removeListener('data', onKey);
|
|
153
|
+
if (raw) {
|
|
154
|
+
process.stdin.setRawMode(false);
|
|
155
|
+
}
|
|
156
|
+
process.stdin.pause();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
process.stdin.on('data', onKey);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ─── Config Management ──────────────────────────────────────────────
|
|
164
|
+
|
|
165
|
+
function readConfig(targetDir) {
|
|
166
|
+
const configPath = path.join(targetDir, CONFIG_FILE);
|
|
167
|
+
if (fs.existsSync(configPath)) {
|
|
168
|
+
try {
|
|
169
|
+
return JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
170
|
+
} catch {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function writeConfig(targetDir, tools) {
|
|
178
|
+
const configPath = path.join(targetDir, CONFIG_FILE);
|
|
179
|
+
const dir = path.dirname(configPath);
|
|
180
|
+
if (!fs.existsSync(dir)) {
|
|
181
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
182
|
+
}
|
|
183
|
+
const config = {
|
|
184
|
+
version: require('../package.json').version,
|
|
185
|
+
tools,
|
|
186
|
+
updatedAt: new Date().toISOString(),
|
|
187
|
+
};
|
|
188
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
189
|
+
return config;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ─── Parse non-interactive --tool flag ──────────────────────────────
|
|
193
|
+
|
|
194
|
+
function parseToolFlag(args) {
|
|
195
|
+
for (let i = 0; i < args.length; i++) {
|
|
196
|
+
if ((args[i] === '--tool' || args[i] === '-t') && args[i + 1]) {
|
|
197
|
+
return args[i + 1].split(',').map((s) => s.trim().toLowerCase());
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ─── Commands ───────────────────────────────────────────────────────
|
|
204
|
+
|
|
205
|
+
async function init(args) {
|
|
206
|
+
const targetDir = process.cwd();
|
|
207
|
+
const config = readConfig(targetDir);
|
|
208
|
+
const preSelected = config ? config.tools : [];
|
|
209
|
+
|
|
210
|
+
const toolFlag = parseToolFlag(args);
|
|
211
|
+
|
|
212
|
+
let selectedTools;
|
|
213
|
+
|
|
214
|
+
if (toolFlag) {
|
|
215
|
+
// Non-interactive mode
|
|
216
|
+
const valid = toolFlag.filter((t) => TOOLS.some((tt) => tt.id === t));
|
|
217
|
+
if (valid.length === 0) {
|
|
218
|
+
throw new Error(`无效的工具: ${toolFlag.join(', ')}。支持: ${TOOLS.map((t) => t.id).join(', ')}`);
|
|
219
|
+
}
|
|
220
|
+
selectedTools = [...new Set([...preSelected, ...valid])];
|
|
221
|
+
console.log(`\n${GREEN}[INFO]${RESET} 非交互模式:初始化 ${selectedTools.join(', ')}`);
|
|
222
|
+
} else {
|
|
223
|
+
// Interactive mode
|
|
224
|
+
process.stdout.write(buildWelcomeScreen());
|
|
225
|
+
await waitForEnter();
|
|
226
|
+
selectedTools = await selectTools(preSelected);
|
|
227
|
+
|
|
228
|
+
if (selectedTools.length === 0) {
|
|
229
|
+
console.log(`\n${YELLOW}[WARN]${RESET} 未选择任何工具,退出。`);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
console.log('');
|
|
235
|
+
|
|
236
|
+
// Determine what's new vs existing
|
|
237
|
+
const newTools = selectedTools.filter((t) => !preSelected.includes(t));
|
|
238
|
+
const existingTools = selectedTools.filter((t) => preSelected.includes(t));
|
|
239
|
+
|
|
240
|
+
if (existingTools.length > 0) {
|
|
241
|
+
console.log(`${DIM} Already installed: ${existingTools.join(', ')} (will merge update)${RESET}`);
|
|
242
|
+
}
|
|
243
|
+
if (newTools.length > 0) {
|
|
244
|
+
console.log(`${GREEN} New install: ${newTools.join(', ')}${RESET}`);
|
|
245
|
+
}
|
|
246
|
+
console.log('');
|
|
247
|
+
|
|
248
|
+
// Always install shared .6aspec config (merge for existing, copy for new)
|
|
249
|
+
const isFirstTime = !config;
|
|
250
|
+
installer.targetDir = targetDir;
|
|
251
|
+
installer.mergeMode = !isFirstTime;
|
|
252
|
+
installer.install6aspec();
|
|
253
|
+
|
|
254
|
+
// Install each selected tool
|
|
255
|
+
for (const tool of selectedTools) {
|
|
256
|
+
const isNew = newTools.includes(tool);
|
|
257
|
+
installer.mergeMode = !isNew;
|
|
258
|
+
|
|
259
|
+
switch (tool) {
|
|
260
|
+
case 'cursor':
|
|
261
|
+
installer.installCursor();
|
|
262
|
+
break;
|
|
263
|
+
case 'claude':
|
|
264
|
+
installer.installClaude();
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Save config
|
|
270
|
+
const savedConfig = writeConfig(targetDir, selectedTools);
|
|
271
|
+
console.log(`\n${GREEN}[SUCCESS]${RESET} 初始化完成!配置已保存到 ${CONFIG_FILE}`);
|
|
272
|
+
console.log(`${DIM} 工具: ${selectedTools.join(', ')}${RESET}`);
|
|
273
|
+
console.log(`${DIM} 版本: ${savedConfig.version}${RESET}\n`);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async function update(args) {
|
|
277
|
+
const targetDir = process.cwd();
|
|
278
|
+
const config = readConfig(targetDir);
|
|
279
|
+
|
|
280
|
+
if (!config || !config.tools || config.tools.length === 0) {
|
|
281
|
+
throw new Error(
|
|
282
|
+
'未找到初始化配置。请先运行 6aspec init 进行初始化。'
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const tools = config.tools;
|
|
287
|
+
const pkgVersion = require('../package.json').version;
|
|
288
|
+
|
|
289
|
+
console.log(`\n${CYAN}${BOLD}6Aspec Update${RESET}`);
|
|
290
|
+
console.log(`${DIM} 当前配置版本: ${config.version || 'unknown'}${RESET}`);
|
|
291
|
+
console.log(`${DIM} 包版本: ${pkgVersion}${RESET}`);
|
|
292
|
+
console.log(`${DIM} 已安装工具: ${tools.join(', ')}${RESET}`);
|
|
293
|
+
console.log('');
|
|
294
|
+
|
|
295
|
+
// Always merge mode for update
|
|
296
|
+
installer.targetDir = targetDir;
|
|
297
|
+
installer.mergeMode = true;
|
|
298
|
+
|
|
299
|
+
installer.install6aspec();
|
|
300
|
+
|
|
301
|
+
for (const tool of tools) {
|
|
302
|
+
switch (tool) {
|
|
303
|
+
case 'cursor':
|
|
304
|
+
installer.installCursor();
|
|
305
|
+
break;
|
|
306
|
+
case 'claude':
|
|
307
|
+
installer.installClaude();
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Update config version
|
|
313
|
+
const savedConfig = writeConfig(targetDir, tools);
|
|
314
|
+
console.log(`\n${GREEN}[SUCCESS]${RESET} 更新完成!`);
|
|
315
|
+
console.log(`${DIM} 版本: ${savedConfig.version}${RESET}\n`);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
module.exports = { init, update };
|
package/lib/installer.js
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
class Installer {
|
|
5
|
+
constructor() {
|
|
6
|
+
// 获取包目录(安装后)
|
|
7
|
+
this.packageDir = this.findPackageDir();
|
|
8
|
+
this.targetDir = process.cwd();
|
|
9
|
+
this.mergeMode = false; // 合并模式标志
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// 查找包目录(支持本地开发和 npm 安装)
|
|
13
|
+
findPackageDir() {
|
|
14
|
+
// 如果是本地开发,直接使用当前目录
|
|
15
|
+
if (fs.existsSync(path.join(__dirname, '../.6aspec')) ||
|
|
16
|
+
fs.existsSync(path.join(__dirname, '../.cursor'))) {
|
|
17
|
+
return path.join(__dirname, '..');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// 如果是 npm 安装,查找 node_modules
|
|
21
|
+
let current = __dirname;
|
|
22
|
+
while (current !== path.dirname(current)) {
|
|
23
|
+
const sixaspecPath = path.join(current, '.6aspec');
|
|
24
|
+
const cursorPath = path.join(current, '.cursor');
|
|
25
|
+
if (fs.existsSync(sixaspecPath) || fs.existsSync(cursorPath)) {
|
|
26
|
+
return current;
|
|
27
|
+
}
|
|
28
|
+
current = path.dirname(current);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
throw new Error('未找到 .6aspec 或 .cursor 目录,请确保包已正确安装');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 颜色输出
|
|
35
|
+
log(message, type = 'info') {
|
|
36
|
+
const colors = {
|
|
37
|
+
info: '\x1b[32m[INFO]\x1b[0m',
|
|
38
|
+
warn: '\x1b[33m[WARN]\x1b[0m',
|
|
39
|
+
error: '\x1b[31m[ERROR]\x1b[0m',
|
|
40
|
+
success: '\x1b[32m[SUCCESS]\x1b[0m'
|
|
41
|
+
};
|
|
42
|
+
console.log(`${colors[type]} ${message}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 备份目录
|
|
46
|
+
backupDir(targetPath) {
|
|
47
|
+
if (fs.existsSync(targetPath)) {
|
|
48
|
+
const backupPath = `${targetPath}.backup.${Date.now()}`;
|
|
49
|
+
this.log(`备份现有配置到: ${backupPath}`, 'warn');
|
|
50
|
+
this.copyDir(targetPath, backupPath);
|
|
51
|
+
return backupPath;
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 递归删除目录
|
|
57
|
+
removeDir(dirPath) {
|
|
58
|
+
if (fs.existsSync(dirPath)) {
|
|
59
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
60
|
+
for (const entry of entries) {
|
|
61
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
62
|
+
if (entry.isDirectory()) {
|
|
63
|
+
this.removeDir(fullPath);
|
|
64
|
+
} else {
|
|
65
|
+
fs.unlinkSync(fullPath);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
fs.rmdirSync(dirPath);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 递归复制目录(完全覆盖模式)
|
|
73
|
+
copyDir(src, dest) {
|
|
74
|
+
// 如果目标目录存在,先完全删除(确保完全覆盖)
|
|
75
|
+
if (fs.existsSync(dest)) {
|
|
76
|
+
this.removeDir(dest);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 重新创建目录并复制
|
|
80
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
81
|
+
|
|
82
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
83
|
+
|
|
84
|
+
for (const entry of entries) {
|
|
85
|
+
const srcPath = path.join(src, entry.name);
|
|
86
|
+
const destPath = path.join(dest, entry.name);
|
|
87
|
+
|
|
88
|
+
if (entry.isDirectory()) {
|
|
89
|
+
this.copyDir(srcPath, destPath);
|
|
90
|
+
} else {
|
|
91
|
+
fs.copyFileSync(srcPath, destPath);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 智能合并目录(只更新包中存在的文件,保留用户自定义文件)
|
|
97
|
+
mergeDir(src, dest) {
|
|
98
|
+
if (!fs.existsSync(dest)) {
|
|
99
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
103
|
+
let updatedCount = 0;
|
|
104
|
+
let addedCount = 0;
|
|
105
|
+
|
|
106
|
+
for (const entry of entries) {
|
|
107
|
+
const srcPath = path.join(src, entry.name);
|
|
108
|
+
const destPath = path.join(dest, entry.name);
|
|
109
|
+
|
|
110
|
+
if (entry.isDirectory()) {
|
|
111
|
+
const result = this.mergeDir(srcPath, destPath);
|
|
112
|
+
updatedCount += result.updated;
|
|
113
|
+
addedCount += result.added;
|
|
114
|
+
} else {
|
|
115
|
+
const exists = fs.existsSync(destPath);
|
|
116
|
+
fs.copyFileSync(srcPath, destPath);
|
|
117
|
+
if (exists) {
|
|
118
|
+
updatedCount++;
|
|
119
|
+
} else {
|
|
120
|
+
addedCount++;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return { updated: updatedCount, added: addedCount };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// 获取目录中所有文件的相对路径列表
|
|
129
|
+
getAllFiles(dir, baseDir = dir) {
|
|
130
|
+
const files = [];
|
|
131
|
+
if (!fs.existsSync(dir)) {
|
|
132
|
+
return files;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
136
|
+
for (const entry of entries) {
|
|
137
|
+
const fullPath = path.join(dir, entry.name);
|
|
138
|
+
const relativePath = path.relative(baseDir, fullPath);
|
|
139
|
+
|
|
140
|
+
if (entry.isDirectory()) {
|
|
141
|
+
files.push(...this.getAllFiles(fullPath, baseDir));
|
|
142
|
+
} else {
|
|
143
|
+
files.push(relativePath);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return files;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 安装公共配置 (.6aspec)
|
|
151
|
+
install6aspec() {
|
|
152
|
+
this.log('开始安装公共配置 (.6aspec)...');
|
|
153
|
+
|
|
154
|
+
const sourceDir = path.join(this.packageDir, '.6aspec');
|
|
155
|
+
const targetDir = path.join(this.targetDir, '.6aspec');
|
|
156
|
+
|
|
157
|
+
if (!fs.existsSync(sourceDir)) {
|
|
158
|
+
this.log('未找到 .6aspec 源目录,跳过', 'warn');
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const targetExists = fs.existsSync(targetDir);
|
|
163
|
+
|
|
164
|
+
if (this.mergeMode && targetExists) {
|
|
165
|
+
// 智能合并模式
|
|
166
|
+
this.log('使用智能合并模式(保留用户自定义文件)...');
|
|
167
|
+
|
|
168
|
+
// 统计用户自定义文件
|
|
169
|
+
const sourceFiles = new Set(this.getAllFiles(sourceDir, sourceDir));
|
|
170
|
+
const targetFiles = this.getAllFiles(targetDir, targetDir);
|
|
171
|
+
const customFiles = targetFiles.filter(f => !sourceFiles.has(f));
|
|
172
|
+
|
|
173
|
+
if (customFiles.length > 0) {
|
|
174
|
+
this.log(`检测到 ${customFiles.length} 个用户自定义文件,将保留`, 'info');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const result = this.mergeDir(sourceDir, targetDir);
|
|
178
|
+
this.log(`更新了 ${result.updated} 个文件,新增了 ${result.added} 个文件`, 'info');
|
|
179
|
+
|
|
180
|
+
if (customFiles.length > 0) {
|
|
181
|
+
this.log(`保留了 ${customFiles.length} 个用户自定义文件`, 'info');
|
|
182
|
+
}
|
|
183
|
+
} else {
|
|
184
|
+
// 完全覆盖模式
|
|
185
|
+
if (targetExists) {
|
|
186
|
+
this.backupDir(targetDir);
|
|
187
|
+
}
|
|
188
|
+
this.log('复制公共配置文件...');
|
|
189
|
+
this.copyDir(sourceDir, targetDir);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
this.log('✓ 公共配置安装完成!', 'success');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// 安装 Cursor 配置
|
|
196
|
+
installCursor() {
|
|
197
|
+
this.log('开始安装 Cursor 配置...');
|
|
198
|
+
|
|
199
|
+
const sourceDir = path.join(this.packageDir, '.cursor');
|
|
200
|
+
const targetDir = path.join(this.targetDir, '.cursor');
|
|
201
|
+
|
|
202
|
+
if (!fs.existsSync(sourceDir)) {
|
|
203
|
+
throw new Error('未找到 .cursor 源目录');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const targetExists = fs.existsSync(targetDir);
|
|
207
|
+
|
|
208
|
+
if (this.mergeMode && targetExists) {
|
|
209
|
+
// 智能合并模式
|
|
210
|
+
this.log('使用智能合并模式(保留用户自定义文件)...');
|
|
211
|
+
|
|
212
|
+
// 只复制 commands 目录
|
|
213
|
+
const commandsSourceDir = path.join(sourceDir, 'commands');
|
|
214
|
+
if (fs.existsSync(commandsSourceDir)) {
|
|
215
|
+
const commandsTargetDir = path.join(targetDir, 'commands');
|
|
216
|
+
const result = this.mergeDir(commandsSourceDir, commandsTargetDir);
|
|
217
|
+
this.log(`commands: 更新了 ${result.updated} 个文件,新增了 ${result.added} 个文件`, 'info');
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
// 完全覆盖模式
|
|
221
|
+
if (targetExists) {
|
|
222
|
+
this.backupDir(targetDir);
|
|
223
|
+
}
|
|
224
|
+
this.log('复制 Cursor 配置文件...');
|
|
225
|
+
|
|
226
|
+
// 创建目标目录
|
|
227
|
+
if (!fs.existsSync(targetDir)) {
|
|
228
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// 只复制 commands 目录
|
|
232
|
+
const commandsSourceDir = path.join(sourceDir, 'commands');
|
|
233
|
+
if (fs.existsSync(commandsSourceDir)) {
|
|
234
|
+
const commandsTargetDir = path.join(targetDir, 'commands');
|
|
235
|
+
this.copyDir(commandsSourceDir, commandsTargetDir);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
this.log('✓ Cursor 配置安装完成!', 'success');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// 安装 Claude 配置
|
|
243
|
+
installClaude() {
|
|
244
|
+
this.log('开始安装 Claude 配置...');
|
|
245
|
+
|
|
246
|
+
const sourceDir = path.join(this.packageDir, '.claude');
|
|
247
|
+
const targetDir = path.join(this.targetDir, '.claude');
|
|
248
|
+
|
|
249
|
+
if (!fs.existsSync(sourceDir)) {
|
|
250
|
+
this.log('未找到 .claude 源目录,跳过', 'warn');
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const targetExists = fs.existsSync(targetDir);
|
|
255
|
+
|
|
256
|
+
// 创建目标目录结构
|
|
257
|
+
if (!fs.existsSync(targetDir)) {
|
|
258
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (this.mergeMode && targetExists) {
|
|
262
|
+
// 智能合并模式
|
|
263
|
+
this.log('使用智能合并模式(保留用户自定义文件)...');
|
|
264
|
+
|
|
265
|
+
// 复制 commands
|
|
266
|
+
const commandsDir = path.join(sourceDir, 'commands');
|
|
267
|
+
if (fs.existsSync(commandsDir)) {
|
|
268
|
+
const targetCommandsDir = path.join(targetDir, 'commands');
|
|
269
|
+
const result = this.mergeDir(commandsDir, targetCommandsDir);
|
|
270
|
+
if (result.updated > 0 || result.added > 0) {
|
|
271
|
+
this.log(`commands: 更新了 ${result.updated} 个文件,新增了 ${result.added} 个文件`, 'info');
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
} else {
|
|
275
|
+
// 完全覆盖模式
|
|
276
|
+
if (targetExists) {
|
|
277
|
+
this.backupDir(targetDir);
|
|
278
|
+
}
|
|
279
|
+
this.log('复制 Claude 配置文件...');
|
|
280
|
+
|
|
281
|
+
// 复制 commands
|
|
282
|
+
const commandsDir = path.join(sourceDir, 'commands');
|
|
283
|
+
if (fs.existsSync(commandsDir)) {
|
|
284
|
+
const targetCommandsDir = path.join(targetDir, 'commands');
|
|
285
|
+
this.copyDir(commandsDir, targetCommandsDir);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
this.log('✓ Claude 配置安装完成!', 'success');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// 主安装方法
|
|
293
|
+
async install(tool = 'cursor', mergeMode = false) {
|
|
294
|
+
this.mergeMode = mergeMode;
|
|
295
|
+
|
|
296
|
+
this.log(`6A-spec 安装工具`);
|
|
297
|
+
this.log(`目标目录: ${this.targetDir}`);
|
|
298
|
+
this.log(`工具: ${tool}`);
|
|
299
|
+
if (mergeMode) {
|
|
300
|
+
this.log(`模式: 智能合并(保留用户自定义文件)`, 'info');
|
|
301
|
+
} else {
|
|
302
|
+
this.log(`模式: 完全覆盖(会先备份现有配置)`, 'info');
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
try {
|
|
306
|
+
// 无论安装哪个工具,都要先安装公共配置
|
|
307
|
+
this.install6aspec();
|
|
308
|
+
|
|
309
|
+
switch (tool.toLowerCase()) {
|
|
310
|
+
case 'cursor':
|
|
311
|
+
this.installCursor();
|
|
312
|
+
break;
|
|
313
|
+
case 'claude':
|
|
314
|
+
this.installClaude();
|
|
315
|
+
break;
|
|
316
|
+
case 'all':
|
|
317
|
+
this.installCursor();
|
|
318
|
+
this.installClaude();
|
|
319
|
+
break;
|
|
320
|
+
default:
|
|
321
|
+
throw new Error(`未知工具: ${tool}\n支持的工具: cursor, claude, all`);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
this.log('安装完成!', 'success');
|
|
325
|
+
} catch (error) {
|
|
326
|
+
this.log(error.message, 'error');
|
|
327
|
+
throw error;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
module.exports = new Installer();
|
|
333
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "6aspec",
|
|
3
|
+
"version": "2.0.0-dev.2",
|
|
4
|
+
"description": "6Aspec - 轻量级 spec 驱动开发框架,支持 Cursor 和 Claude Code",
|
|
5
|
+
"main": "lib/installer.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"6aspec": "./bin/6aspec"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node bin/6aspec --help",
|
|
11
|
+
"version:dev": "npm version prerelease --preid=dev --no-git-tag-version",
|
|
12
|
+
"version:beta": "npm version prerelease --preid=beta --no-git-tag-version",
|
|
13
|
+
"version:rc": "npm version prerelease --preid=rc --no-git-tag-version",
|
|
14
|
+
"version:patch": "npm version patch --no-git-tag-version",
|
|
15
|
+
"version:minor": "npm version minor --no-git-tag-version",
|
|
16
|
+
"version:major": "npm version major --no-git-tag-version",
|
|
17
|
+
"version:dev:git": "npm version prerelease --preid=dev",
|
|
18
|
+
"version:beta:git": "npm version prerelease --preid=beta",
|
|
19
|
+
"version:rc:git": "npm version prerelease --preid=rc",
|
|
20
|
+
"version:patch:git": "npm version patch",
|
|
21
|
+
"version:minor:git": "npm version minor",
|
|
22
|
+
"version:major:git": "npm version major",
|
|
23
|
+
"publish:dev": "npm run version:dev && npm publish --tag dev",
|
|
24
|
+
"publish:beta": "npm run version:beta && npm publish --tag beta",
|
|
25
|
+
"publish:rc": "npm run version:rc && npm publish --tag rc",
|
|
26
|
+
"publish:latest": "npm run version:patch && npm publish",
|
|
27
|
+
"publish:dev:otp": "npm run version:dev && npm publish --tag dev --otp",
|
|
28
|
+
"publish:beta:otp": "npm run version:beta && npm publish --tag beta --otp",
|
|
29
|
+
"publish:rc:otp": "npm run version:rc && npm publish --tag rc --otp",
|
|
30
|
+
"publish:latest:otp": "npm run version:patch && npm publish --otp",
|
|
31
|
+
"publish:dev:git": "npm run version:dev:git && npm publish --tag dev",
|
|
32
|
+
"publish:beta:git": "npm run version:beta:git && npm publish --tag beta",
|
|
33
|
+
"publish:rc:git": "npm run version:rc:git && npm publish --tag rc",
|
|
34
|
+
"publish:latest:git": "npm run version:patch:git && npm publish"
|
|
35
|
+
},
|
|
36
|
+
"keywords": [
|
|
37
|
+
"cursor",
|
|
38
|
+
"claude",
|
|
39
|
+
"ai",
|
|
40
|
+
"prompts",
|
|
41
|
+
"spec-driven",
|
|
42
|
+
"development",
|
|
43
|
+
"6aspec"
|
|
44
|
+
],
|
|
45
|
+
"author": "yanghuijava@gmail.com",
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"files": [
|
|
48
|
+
"bin/",
|
|
49
|
+
"lib/",
|
|
50
|
+
".cursor/",
|
|
51
|
+
".claude/",
|
|
52
|
+
".6aspec/",
|
|
53
|
+
"README.md"
|
|
54
|
+
],
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=14.0.0"
|
|
57
|
+
},
|
|
58
|
+
"repository": {
|
|
59
|
+
"type": "git",
|
|
60
|
+
"url": "https://gitee.com/yanghuijava/6-a-spec.git"
|
|
61
|
+
}
|
|
62
|
+
}
|