speccore 8.3.340 → 8.3.341
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/.agents/skills/speccore-router/SKILL.md +0 -2
- package/README.en.md +3 -11
- package/README.md +3 -13
- package/dist/cli.js +1 -187
- package/dist/cli.js.map +1 -1
- package/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +530 -481
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/completion.d.ts.map +1 -1
- package/dist/commands/completion.js +9 -7
- package/dist/commands/completion.js.map +1 -1
- package/dist/commands/doctor.js +3 -3
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/done.js +1 -5
- package/dist/commands/done.js.map +1 -1
- package/dist/commands/execute.js +2 -2
- package/dist/commands/execute.js.map +1 -1
- package/dist/commands/help.js +5 -54
- package/dist/commands/help.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +0 -7
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/pr.d.ts.map +1 -1
- package/dist/commands/pr.js +3 -5
- package/dist/commands/pr.js.map +1 -1
- package/dist/commands/search.js +1 -1
- package/dist/commands/search.js.map +1 -1
- package/dist/commands/update-env-configs.d.ts.map +1 -1
- package/dist/commands/update-env-configs.js +2 -1
- package/dist/commands/update-env-configs.js.map +1 -1
- package/dist/commands/update.d.ts +4 -0
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +41 -1
- package/dist/commands/update.js.map +1 -1
- package/dist/core/analyze-engine.js +3 -3
- package/dist/core/analyze-engine.js.map +1 -1
- package/dist/core/arbitration/l3-human-arbiter.js +1 -1
- package/dist/core/arbitration/l3-human-arbiter.js.map +1 -1
- package/dist/core/arbitration/verdict-generator.js +1 -1
- package/dist/core/arbitration/verdict-generator.js.map +1 -1
- package/dist/core/ask-engine.d.ts.map +1 -1
- package/dist/core/ask-engine.js +16 -42
- package/dist/core/ask-engine.js.map +1 -1
- package/dist/core/code-graph/reporter.d.ts.map +1 -1
- package/dist/core/code-graph/reporter.js +3 -12
- package/dist/core/code-graph/reporter.js.map +1 -1
- package/dist/core/code-graph/requirement-linker.js +1 -1
- package/dist/core/code-graph/requirement-linker.js.map +1 -1
- package/dist/core/code-scanner.js +1 -1
- package/dist/core/code-scanner.js.map +1 -1
- package/dist/core/deploy/engine.d.ts.map +1 -1
- package/dist/core/deploy/engine.js +58 -44
- package/dist/core/deploy/engine.js.map +1 -1
- package/dist/core/environment-config.d.ts +2 -0
- package/dist/core/environment-config.d.ts.map +1 -1
- package/dist/core/environment-config.js +6 -1
- package/dist/core/environment-config.js.map +1 -1
- package/dist/core/error-diagnosis.js +2 -2
- package/dist/core/error-diagnosis.js.map +1 -1
- package/dist/core/git-integration.d.ts +6 -0
- package/dist/core/git-integration.d.ts.map +1 -1
- package/dist/core/git-integration.js +11 -0
- package/dist/core/git-integration.js.map +1 -1
- package/dist/core/global-knowledge.js +1 -1
- package/dist/core/index-guard.d.ts.map +1 -1
- package/dist/core/index-guard.js +4 -26
- package/dist/core/index-guard.js.map +1 -1
- package/dist/core/intent-recognition.d.ts +1 -1
- package/dist/core/intent-recognition.d.ts.map +1 -1
- package/dist/core/intent-recognition.js +5 -63
- package/dist/core/intent-recognition.js.map +1 -1
- package/dist/core/prompt-builder.js +1 -1
- package/dist/core/prompt-builder.js.map +1 -1
- package/dist/core/prompt-plugins/analyze-graph-plugin.js +1 -1
- package/dist/core/prompt-plugins/analyze-graph-plugin.js.map +1 -1
- package/dist/core/structured-extractor.d.ts +6 -0
- package/dist/core/structured-extractor.d.ts.map +1 -1
- package/dist/core/structured-extractor.js +6 -0
- package/dist/core/structured-extractor.js.map +1 -1
- package/dist/core/unified-config.d.ts.map +1 -1
- package/dist/core/unified-config.js +29 -19
- package/dist/core/unified-config.js.map +1 -1
- package/dist/core/usage-analytics.js +1 -1
- package/dist/core/usage-analytics.js.map +1 -1
- package/package.json +1 -1
- package/templates/html/speccore-ask-guide.html +1 -1
- package/templates/html/speccore-ask-pipeline.html +1 -1
- package/templates/html/speccore-ask-result.html +1 -1
- package/templates/html/speccore-help.html +2 -2
- package/templates/speccore-yml-example.yml +24 -24
- package/dist/commands/audit.d.ts +0 -12
- package/dist/commands/audit.d.ts.map +0 -1
- package/dist/commands/audit.js +0 -540
- package/dist/commands/audit.js.map +0 -1
- package/dist/commands/backup.d.ts +0 -18
- package/dist/commands/backup.d.ts.map +0 -1
- package/dist/commands/backup.js +0 -102
- package/dist/commands/backup.js.map +0 -1
- package/dist/commands/baseline.d.ts +0 -13
- package/dist/commands/baseline.d.ts.map +0 -1
- package/dist/commands/baseline.js +0 -227
- package/dist/commands/baseline.js.map +0 -1
- package/dist/commands/chain.d.ts +0 -55
- package/dist/commands/chain.d.ts.map +0 -1
- package/dist/commands/chain.js +0 -501
- package/dist/commands/chain.js.map +0 -1
- package/dist/commands/clarify.d.ts +0 -17
- package/dist/commands/clarify.d.ts.map +0 -1
- package/dist/commands/clarify.js +0 -351
- package/dist/commands/clarify.js.map +0 -1
- package/dist/commands/code-index.d.ts +0 -21
- package/dist/commands/code-index.d.ts.map +0 -1
- package/dist/commands/code-index.js +0 -193
- package/dist/commands/code-index.js.map +0 -1
- package/dist/commands/context.d.ts +0 -10
- package/dist/commands/context.d.ts.map +0 -1
- package/dist/commands/context.js +0 -213
- package/dist/commands/context.js.map +0 -1
- package/dist/commands/current.d.ts +0 -10
- package/dist/commands/current.d.ts.map +0 -1
- package/dist/commands/current.js +0 -53
- package/dist/commands/current.js.map +0 -1
- package/dist/commands/dev.d.ts +0 -18
- package/dist/commands/dev.d.ts.map +0 -1
- package/dist/commands/dev.js +0 -854
- package/dist/commands/dev.js.map +0 -1
- package/dist/commands/diff.d.ts +0 -10
- package/dist/commands/diff.d.ts.map +0 -1
- package/dist/commands/diff.js +0 -160
- package/dist/commands/diff.js.map +0 -1
- package/dist/commands/global-status.d.ts +0 -9
- package/dist/commands/global-status.d.ts.map +0 -1
- package/dist/commands/global-status.js +0 -135
- package/dist/commands/global-status.js.map +0 -1
- package/dist/commands/graph.d.ts +0 -25
- package/dist/commands/graph.d.ts.map +0 -1
- package/dist/commands/graph.js +0 -523
- package/dist/commands/graph.js.map +0 -1
- package/dist/commands/handover.d.ts +0 -11
- package/dist/commands/handover.d.ts.map +0 -1
- package/dist/commands/handover.js +0 -170
- package/dist/commands/handover.js.map +0 -1
- package/dist/commands/health.d.ts +0 -7
- package/dist/commands/health.d.ts.map +0 -1
- package/dist/commands/health.js +0 -80
- package/dist/commands/health.js.map +0 -1
- package/dist/commands/history.d.ts +0 -9
- package/dist/commands/history.d.ts.map +0 -1
- package/dist/commands/history.js +0 -117
- package/dist/commands/history.js.map +0 -1
- package/dist/commands/impact.d.ts +0 -12
- package/dist/commands/impact.d.ts.map +0 -1
- package/dist/commands/impact.js +0 -238
- package/dist/commands/impact.js.map +0 -1
- package/dist/commands/import.d.ts +0 -19
- package/dist/commands/import.d.ts.map +0 -1
- package/dist/commands/import.js +0 -673
- package/dist/commands/import.js.map +0 -1
- package/dist/commands/knowledge.d.ts +0 -6
- package/dist/commands/knowledge.d.ts.map +0 -1
- package/dist/commands/knowledge.js +0 -94
- package/dist/commands/knowledge.js.map +0 -1
- package/dist/commands/lifecycle.d.ts +0 -9
- package/dist/commands/lifecycle.d.ts.map +0 -1
- package/dist/commands/lifecycle.js +0 -288
- package/dist/commands/lifecycle.js.map +0 -1
- package/dist/commands/merge-check.d.ts +0 -10
- package/dist/commands/merge-check.d.ts.map +0 -1
- package/dist/commands/merge-check.js +0 -183
- package/dist/commands/merge-check.js.map +0 -1
- package/dist/commands/notify.d.ts +0 -13
- package/dist/commands/notify.d.ts.map +0 -1
- package/dist/commands/notify.js +0 -63
- package/dist/commands/notify.js.map +0 -1
- package/dist/commands/rag-index.d.ts +0 -19
- package/dist/commands/rag-index.d.ts.map +0 -1
- package/dist/commands/rag-index.js +0 -286
- package/dist/commands/rag-index.js.map +0 -1
- package/dist/commands/recommend.d.ts +0 -13
- package/dist/commands/recommend.d.ts.map +0 -1
- package/dist/commands/recommend.js +0 -37
- package/dist/commands/recommend.js.map +0 -1
- package/dist/commands/refresh.d.ts +0 -21
- package/dist/commands/refresh.d.ts.map +0 -1
- package/dist/commands/refresh.js +0 -211
- package/dist/commands/refresh.js.map +0 -1
- package/dist/commands/rollback.d.ts +0 -8
- package/dist/commands/rollback.d.ts.map +0 -1
- package/dist/commands/rollback.js +0 -113
- package/dist/commands/rollback.js.map +0 -1
- package/dist/commands/status.d.ts +0 -7
- package/dist/commands/status.d.ts.map +0 -1
- package/dist/commands/status.js +0 -262
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/trace.d.ts +0 -12
- package/dist/commands/trace.d.ts.map +0 -1
- package/dist/commands/trace.js +0 -163
- package/dist/commands/trace.js.map +0 -1
- package/dist/commands/tracker.d.ts +0 -2
- package/dist/commands/tracker.d.ts.map +0 -1
- package/dist/commands/tracker.js +0 -28
- package/dist/commands/tracker.js.map +0 -1
- package/dist/commands/verdict.d.ts +0 -16
- package/dist/commands/verdict.d.ts.map +0 -1
- package/dist/commands/verdict.js +0 -189
- package/dist/commands/verdict.js.map +0 -1
- package/dist/commands/workspace.d.ts +0 -20
- package/dist/commands/workspace.d.ts.map +0 -1
- package/dist/commands/workspace.js +0 -142
- package/dist/commands/workspace.js.map +0 -1
- package/dist/core/constraint-set.d.ts +0 -67
- package/dist/core/constraint-set.d.ts.map +0 -1
- package/dist/core/constraint-set.js +0 -151
- package/dist/core/constraint-set.js.map +0 -1
- package/dist/core/smart-recommend.d.ts +0 -16
- package/dist/core/smart-recommend.d.ts.map +0 -1
- package/dist/core/smart-recommend.js +0 -149
- package/dist/core/smart-recommend.js.map +0 -1
- package/dist/core/task-type-registry.d.ts +0 -110
- package/dist/core/task-type-registry.d.ts.map +0 -1
- package/dist/core/task-type-registry.js +0 -334
- package/dist/core/task-type-registry.js.map +0 -1
- package/dist/utils/mermaid-render.d.ts +0 -39
- package/dist/utils/mermaid-render.d.ts.map +0 -1
- package/dist/utils/mermaid-render.js +0 -284
- package/dist/utils/mermaid-render.js.map +0 -1
package/dist/commands/import.js
DELETED
|
@@ -1,673 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* import - 多项目导入命令
|
|
4
|
-
* 将存量项目导入到全量层(GLOBAL/platforms/),填充全量需求和索引
|
|
5
|
-
*/
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.importCommand = importCommand;
|
|
8
|
-
const fs_extra_1 = require("fs-extra");
|
|
9
|
-
const capabilities_1 = require("../core/capabilities");
|
|
10
|
-
const path_1 = require("path");
|
|
11
|
-
const logger_1 = require("../utils/logger");
|
|
12
|
-
const task_utils_1 = require("../utils/task-utils");
|
|
13
|
-
const global_layer_1 = require("../core/global-layer");
|
|
14
|
-
const readline_1 = require("readline");
|
|
15
|
-
function promptUser(question) {
|
|
16
|
-
const rl = (0, readline_1.createInterface)({ input: process.stdin, output: process.stdout });
|
|
17
|
-
return new Promise(resolve => {
|
|
18
|
-
rl.question(`${question} `, answer => { rl.close(); resolve(answer.trim()); });
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
async function importCommand(options) {
|
|
22
|
-
const spinner = new logger_1.Spinner('Importing project to global layer');
|
|
23
|
-
spinner.start();
|
|
24
|
-
try {
|
|
25
|
-
// 新逻辑:多项目导入到全量层
|
|
26
|
-
if (options.project) {
|
|
27
|
-
const projectName = options.project;
|
|
28
|
-
const projectPath = options.path || `./${projectName}`;
|
|
29
|
-
if (!options.type) {
|
|
30
|
-
logger_1.logger.error('请通过 --type 指定项目类型(backend/frontend/web/h5/miniapp)');
|
|
31
|
-
logger_1.logger.error('示例: speccore import --project=xxx --type=backend --path=.');
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
const projectType = options.type;
|
|
35
|
-
await importToGlobalLayer(projectName, projectPath, projectType, options);
|
|
36
|
-
spinner.stop('Project imported to global layer');
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
// 导入逻辑
|
|
40
|
-
const sources = (options.source || 'all').split(',');
|
|
41
|
-
const results = [];
|
|
42
|
-
for (const source of sources) {
|
|
43
|
-
switch (source.trim()) {
|
|
44
|
-
case 'code':
|
|
45
|
-
results.push(await importCode(options.path || './'));
|
|
46
|
-
break;
|
|
47
|
-
case 'prd':
|
|
48
|
-
results.push(await importPRD(options.path || './PRD.md'));
|
|
49
|
-
break;
|
|
50
|
-
case 'prototype':
|
|
51
|
-
results.push(await importPrototype(options.url || ''));
|
|
52
|
-
break;
|
|
53
|
-
case 'all':
|
|
54
|
-
results.push(await autoDetectImport(options.path || './'));
|
|
55
|
-
break;
|
|
56
|
-
default:
|
|
57
|
-
logger_1.logger.warn(`Unknown source type: ${source}`);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
spinner.stop('Import completed');
|
|
61
|
-
for (const result of results) {
|
|
62
|
-
logger_1.logger.info(result);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
catch (error) {
|
|
66
|
-
spinner.fail(`Import failed: ${error}`);
|
|
67
|
-
throw error;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* 多项目导入到全量层
|
|
72
|
-
*/
|
|
73
|
-
async function importToGlobalLayer(projectName, projectPath, projectType, options) {
|
|
74
|
-
// 备份追踪
|
|
75
|
-
let backupPath = null;
|
|
76
|
-
// 1. 检查路径
|
|
77
|
-
if (!(await (0, fs_extra_1.pathExists)(projectPath))) {
|
|
78
|
-
throw new Error(`Project path not found: ${projectPath}`);
|
|
79
|
-
}
|
|
80
|
-
// 2. 检查全局层是否已初始化
|
|
81
|
-
const globalDir = (0, path_1.join)(process.cwd(), '.speccore', 'GLOBAL');
|
|
82
|
-
if (!(await (0, fs_extra_1.pathExists)(globalDir))) {
|
|
83
|
-
throw new Error('Global layer not initialized. Run: speccore init');
|
|
84
|
-
}
|
|
85
|
-
// 检测是否已导入(覆盖/增量判断)
|
|
86
|
-
const existingDir = (0, path_1.join)(globalDir, 'PROJECTS', projectName);
|
|
87
|
-
if (await (0, fs_extra_1.pathExists)(existingDir)) {
|
|
88
|
-
if (options.force) {
|
|
89
|
-
// 备份旧目录为时间戳格式
|
|
90
|
-
backupPath = await (0, task_utils_1.backupDirWithTimestamp)(existingDir);
|
|
91
|
-
if (backupPath) {
|
|
92
|
-
logger_1.logger.info(`🔁 强制覆盖模式:已备份为 ${backupPath.split('/').pop()}`);
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
95
|
-
logger_1.logger.info('🔁 强制覆盖模式:已存在项目将被重新扫描替换');
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
else if (options.update) {
|
|
99
|
-
logger_1.logger.info('🔄 增量更新模式:追加新 API,保留已有');
|
|
100
|
-
}
|
|
101
|
-
else if (options.interactive) {
|
|
102
|
-
logger_1.logger.info(`⚠️ 项目 ${projectName} 已存在!`);
|
|
103
|
-
const answer = await promptUser('选择 [o]覆盖/[u]增量/[c]取消:');
|
|
104
|
-
if (answer === 'c') {
|
|
105
|
-
logger_1.logger.info('已取消');
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
options.force = answer === 'o';
|
|
109
|
-
options.update = answer === 'u';
|
|
110
|
-
}
|
|
111
|
-
else {
|
|
112
|
-
logger_1.logger.warn(`⚠️ 项目 ${projectName} 已存在,使用 --update 增量或 --force 覆盖`);
|
|
113
|
-
logger_1.logger.info('将跳过已有项目,使用 --update 追加新 API');
|
|
114
|
-
options.update = true;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
// 3. 读取当前全量索引
|
|
118
|
-
const index = await (0, global_layer_1.readGlobalIndex)();
|
|
119
|
-
// 3.5 检测 Excel/CSV 文件 → 直接解析为需求
|
|
120
|
-
let scanResult = { apis: [], models: [], techStack: '', repoUrl: '' };
|
|
121
|
-
let fromFile = false;
|
|
122
|
-
const ext = projectPath.split('.').pop()?.toLowerCase();
|
|
123
|
-
if (ext === 'xlsx' || ext === 'csv') {
|
|
124
|
-
logger_1.logger.info(`📊 从 ${ext.toUpperCase()} 文件导入需求...`);
|
|
125
|
-
try {
|
|
126
|
-
if (ext === 'xlsx') {
|
|
127
|
-
const XLSX = require('xlsx');
|
|
128
|
-
const wb = XLSX.readFile((0, path_1.join)(process.cwd(), projectPath));
|
|
129
|
-
const ws = wb.Sheets[wb.SheetNames[0]];
|
|
130
|
-
const rows = XLSX.utils.sheet_to_json(ws, { header: 1 });
|
|
131
|
-
scanResult.apis = rows.slice(1)
|
|
132
|
-
.filter((r) => r.some((c) => c))
|
|
133
|
-
.map((r) => ({
|
|
134
|
-
method: 'REQ',
|
|
135
|
-
path: String(r[0] || ''),
|
|
136
|
-
name: String(r[1] || r[0] || ''),
|
|
137
|
-
description: r.slice(1).filter((c) => c).join(' — '),
|
|
138
|
-
sourceFile: projectPath,
|
|
139
|
-
}));
|
|
140
|
-
}
|
|
141
|
-
else {
|
|
142
|
-
const content = await (0, fs_extra_1.readFile)((0, path_1.join)(process.cwd(), projectPath), 'utf-8');
|
|
143
|
-
const lines = content.split('\n').filter(l => l.trim());
|
|
144
|
-
scanResult.apis = lines.map(l => {
|
|
145
|
-
const parts = l.split(',').map(p => p.trim());
|
|
146
|
-
return {
|
|
147
|
-
method: 'REQ',
|
|
148
|
-
path: parts[0] || '',
|
|
149
|
-
name: parts[1] || parts[0] || '',
|
|
150
|
-
description: parts.join(' — '),
|
|
151
|
-
sourceFile: projectPath,
|
|
152
|
-
};
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
fromFile = true;
|
|
156
|
-
logger_1.logger.info(` 解析到 ${scanResult.apis.length} 条需求`);
|
|
157
|
-
}
|
|
158
|
-
catch (e) {
|
|
159
|
-
throw new Error(`文件解析失败: ${e.message}`);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
// 4. 扫描项目代码(非文件模式)
|
|
163
|
-
if (!fromFile) {
|
|
164
|
-
logger_1.logger.info(`🔍 Scanning project: ${projectName} (${projectType})`);
|
|
165
|
-
scanResult = await scanProject(projectPath, projectType, options);
|
|
166
|
-
logger_1.logger.info(` Found ${scanResult.apis.length} API endpoints, ${scanResult.models.length} data models`);
|
|
167
|
-
// ── Interactive preview: 预览扫描结果 → 用户确认/调整 ──
|
|
168
|
-
if (options.interactive && scanResult.apis.length > 0) {
|
|
169
|
-
logger_1.logger.info('');
|
|
170
|
-
logger_1.logger.info('📋 扫描结果预览:');
|
|
171
|
-
logger_1.logger.info(` 项目: ${projectName} | 类型: ${projectType}`);
|
|
172
|
-
logger_1.logger.info(` 技术栈: ${scanResult.techStack || '未检测到'}`);
|
|
173
|
-
logger_1.logger.info(` API 端点: ${scanResult.apis.length} 个`);
|
|
174
|
-
logger_1.logger.info('');
|
|
175
|
-
for (const api of scanResult.apis) {
|
|
176
|
-
logger_1.logger.info(` ${api.method.padEnd(8)} ${api.path.padEnd(30)} → ${api.sourceFile}`);
|
|
177
|
-
}
|
|
178
|
-
logger_1.logger.info('');
|
|
179
|
-
logger_1.logger.info('💡 [y] 确认导入 [a] 新增遗漏 [s] 跳过某个 [q] 取消');
|
|
180
|
-
const answer = await promptUser('确认导入?');
|
|
181
|
-
if (answer?.toLowerCase() === 'q') {
|
|
182
|
-
logger_1.logger.info('已取消');
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
if (answer?.toLowerCase() === 'a') {
|
|
186
|
-
logger_1.logger.info('请在 REQUIREMENT.md 生成后手动补充遗漏的 API 端点');
|
|
187
|
-
}
|
|
188
|
-
if (answer?.toLowerCase() === 's') {
|
|
189
|
-
const resp = await promptUser('请输入要跳过的 API 路径(逗号分隔,留空则全部保留):');
|
|
190
|
-
if (resp) {
|
|
191
|
-
const skipPaths = resp.split(',').map(s => s.trim());
|
|
192
|
-
scanResult.apis = scanResult.apis.filter(a => !skipPaths.includes(a.path));
|
|
193
|
-
logger_1.logger.info(`已跳过 ${skipPaths.length} 个 API,保留 ${scanResult.apis.length} 个`);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
} // close if (!fromFile)
|
|
198
|
-
// 5. 生成需求条目
|
|
199
|
-
const requirements = [];
|
|
200
|
-
let nextId = parseInt((0, global_layer_1.getNextReqId)(index).replace('REQ-', ''), 10);
|
|
201
|
-
for (const api of scanResult.apis) {
|
|
202
|
-
const reqId = `REQ-${String(nextId++).padStart(3, '0')}`;
|
|
203
|
-
requirements.push({
|
|
204
|
-
id: reqId,
|
|
205
|
-
name: api.name,
|
|
206
|
-
description: `API: ${api.method} ${api.path}${api.sourceFile ? ` (${api.sourceFile})` : ''}\n<!-- AI-ANALYZE: 分析 ${api.path} 的功能职责、输入输出、业务规则 -->\n${api.description || '从代码扫描提取的 API 端点,待 AI 分析补充'}`,
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
// 如果没有扫描到 API,生成一个占位需求
|
|
210
|
-
if (requirements.length === 0) {
|
|
211
|
-
const reqId = `REQ-${String(nextId++).padStart(3, '0')}`;
|
|
212
|
-
requirements.push({
|
|
213
|
-
id: reqId,
|
|
214
|
-
name: `${projectName} 项目导入`,
|
|
215
|
-
description: `从 ${projectPath} 导入的项目,类型: ${projectType}`,
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
// 6. 创建项目目录和文件
|
|
219
|
-
await (0, global_layer_1.ensureProjectDir)(projectName);
|
|
220
|
-
const entries = await (0, global_layer_1.writeProjectRequirements)(projectName, projectType, requirements, scanResult.techStack);
|
|
221
|
-
await (0, global_layer_1.writeProjectMetadata)(projectName, projectType, scanResult.techStack, scanResult.repoUrl);
|
|
222
|
-
// 7. 更新全量索引
|
|
223
|
-
for (const entry of entries) {
|
|
224
|
-
await (0, global_layer_1.appendReqToIndex)(entry);
|
|
225
|
-
}
|
|
226
|
-
await (0, global_layer_1.upsertProjectInIndex)({
|
|
227
|
-
name: projectName,
|
|
228
|
-
type: projectType,
|
|
229
|
-
reqCount: entries.length,
|
|
230
|
-
implemented: entries.filter((e) => e.status === '📦 已有实现').length,
|
|
231
|
-
inProgress: 0,
|
|
232
|
-
pending: 0,
|
|
233
|
-
lastImport: new Date().toISOString().split('T')[0],
|
|
234
|
-
});
|
|
235
|
-
// 8. 更新版本
|
|
236
|
-
const newVersion = (0, global_layer_1.bumpGlobalVersion)(index.version);
|
|
237
|
-
await (0, global_layer_1.updateIndexVersion)(newVersion);
|
|
238
|
-
// 9. 更新 OVERVIEW.md 和 CHANGELOG.md
|
|
239
|
-
await updateGlobalOverview(projectName, projectType);
|
|
240
|
-
await updateGlobalChangelog(`导入项目 ${projectName}`, newVersion);
|
|
241
|
-
// 10. 检测并建议更新宪法(CONSTITUTION.md)
|
|
242
|
-
await suggestConstitutionUpdate(projectName, projectType, scanResult.techStack);
|
|
243
|
-
// 11. 生成 AI 分析指引(供 Slash Command 使用)
|
|
244
|
-
await generateAnalysisPrompt(projectName, projectType, scanResult, projectPath);
|
|
245
|
-
// 12. 同步能力注册表
|
|
246
|
-
await (0, capabilities_1.syncCapabilities)({
|
|
247
|
-
importProject: projectName,
|
|
248
|
-
importType: projectType,
|
|
249
|
-
importApis: scanResult.apis.length,
|
|
250
|
-
});
|
|
251
|
-
// 10. 输出报告
|
|
252
|
-
logger_1.logger.info('');
|
|
253
|
-
logger_1.logger.info('✅ 项目导入完成!');
|
|
254
|
-
logger_1.logger.info('');
|
|
255
|
-
logger_1.logger.info('📊 导入摘要:');
|
|
256
|
-
logger_1.logger.info(` 项目名称: ${projectName}`);
|
|
257
|
-
logger_1.logger.info(` 项目类型: ${projectType}`);
|
|
258
|
-
logger_1.logger.info(` 识别接口: ${scanResult.apis.length} 个`);
|
|
259
|
-
logger_1.logger.info(` 数据模型: ${scanResult.models.length} 个`);
|
|
260
|
-
logger_1.logger.info(` 生成需求: ${entries.map((e) => e.id).join(', ')}`);
|
|
261
|
-
logger_1.logger.info('');
|
|
262
|
-
logger_1.logger.info('📁 已创建:');
|
|
263
|
-
logger_1.logger.info(` GLOBAL/platforms/${projectName}/`);
|
|
264
|
-
logger_1.logger.info('');
|
|
265
|
-
logger_1.logger.info('📋 已更新:');
|
|
266
|
-
logger_1.logger.info(' GLOBAL/INDEX.md(需求映射 + 项目列表)');
|
|
267
|
-
logger_1.logger.info('');
|
|
268
|
-
logger_1.logger.info('📋 下一步:');
|
|
269
|
-
logger_1.logger.info(' speccore global-status 查看全量层状态');
|
|
270
|
-
logger_1.logger.info(' speccore iteration-from-global 从全量层生成迭代');
|
|
271
|
-
// 备份汇总
|
|
272
|
-
if (backupPath) {
|
|
273
|
-
logger_1.logger.info('');
|
|
274
|
-
logger_1.logger.info('📦 备份文件:');
|
|
275
|
-
logger_1.logger.info(` ${backupPath}`);
|
|
276
|
-
logger_1.logger.info(' 💡 如不再需要可手动删除');
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
async function scanProject(projectPath, projectType, options) {
|
|
280
|
-
const result = {
|
|
281
|
-
apis: [],
|
|
282
|
-
models: [],
|
|
283
|
-
techStack: '',
|
|
284
|
-
repoUrl: '',
|
|
285
|
-
};
|
|
286
|
-
const absPath = (0, path_1.join)(process.cwd(), projectPath);
|
|
287
|
-
// Handle --scope and --ignore
|
|
288
|
-
const scope = options?.scope || 'all';
|
|
289
|
-
const ignores = (options?.ignore || '').split(',').filter(Boolean).map(s => s.trim());
|
|
290
|
-
if (projectType === 'backend') {
|
|
291
|
-
// 扫描后端项目
|
|
292
|
-
// 尝试读取 pom.xml (Java/Maven)
|
|
293
|
-
const pomPath = (0, path_1.join)(absPath, 'pom.xml');
|
|
294
|
-
if (await (0, fs_extra_1.pathExists)(pomPath)) {
|
|
295
|
-
const pom = await (0, fs_extra_1.readFile)(pomPath, 'utf-8');
|
|
296
|
-
const groupMatch = pom.match(/<groupId>([^<]+)<\/groupId>/);
|
|
297
|
-
const artifactMatch = pom.match(/<artifactId>([^<]+)<\/artifactId>/);
|
|
298
|
-
if (groupMatch && artifactMatch) {
|
|
299
|
-
result.techStack = `Java Maven: ${groupMatch[1]}:${artifactMatch[1]}`;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
// 扫描 package.json (Node.js)
|
|
303
|
-
const pkgPath = (0, path_1.join)(absPath, 'package.json');
|
|
304
|
-
if (await (0, fs_extra_1.pathExists)(pkgPath)) {
|
|
305
|
-
const pkg = JSON.parse(await (0, fs_extra_1.readFile)(pkgPath, 'utf-8'));
|
|
306
|
-
result.techStack = `Node.js: ${pkg.name || 'unnamed'}`;
|
|
307
|
-
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
308
|
-
if (deps['@nestjs/core'])
|
|
309
|
-
result.techStack += ' (NestJS)';
|
|
310
|
-
else if (deps['express'])
|
|
311
|
-
result.techStack += ' (Express)';
|
|
312
|
-
}
|
|
313
|
-
// 扫描 src/ 目录寻找 Controller/路由
|
|
314
|
-
const srcDir = (0, path_1.join)(absPath, 'src');
|
|
315
|
-
if (await (0, fs_extra_1.pathExists)(srcDir)) {
|
|
316
|
-
result.apis = await scanApiEndpoints(srcDir, ignores, scope);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
else if (['web', 'h5', 'miniapp'].includes(projectType)) {
|
|
320
|
-
// 扫描前端项目
|
|
321
|
-
const pkgPath = (0, path_1.join)(absPath, 'package.json');
|
|
322
|
-
if (await (0, fs_extra_1.pathExists)(pkgPath)) {
|
|
323
|
-
const pkg = JSON.parse(await (0, fs_extra_1.readFile)(pkgPath, 'utf-8'));
|
|
324
|
-
result.techStack = `${projectType}: ${pkg.name || 'unnamed'}`;
|
|
325
|
-
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
326
|
-
if (deps['vue'])
|
|
327
|
-
result.techStack += ' (Vue)';
|
|
328
|
-
else if (deps['react'])
|
|
329
|
-
result.techStack += ' (React)';
|
|
330
|
-
else if (deps['@angular/core'])
|
|
331
|
-
result.techStack += ' (Angular)';
|
|
332
|
-
// 扫描 pages/ 目录
|
|
333
|
-
const pagesDir = (0, path_1.join)(absPath, 'src', 'pages');
|
|
334
|
-
if (await (0, fs_extra_1.pathExists)(pagesDir)) {
|
|
335
|
-
const pages = await (0, fs_extra_1.readdir)(pagesDir, { withFileTypes: true });
|
|
336
|
-
for (const page of pages.filter((p) => p.isDirectory())) {
|
|
337
|
-
result.apis.push({
|
|
338
|
-
method: 'PAGE',
|
|
339
|
-
path: `/${page.name}`,
|
|
340
|
-
name: `${page.name} 页面`,
|
|
341
|
-
description: `${projectType} 页面`,
|
|
342
|
-
sourceFile: `src/pages/${page.name}`,
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
return result;
|
|
349
|
-
}
|
|
350
|
-
/**
|
|
351
|
-
* 递归扫描 API 端点
|
|
352
|
-
*/
|
|
353
|
-
async function scanApiEndpoints(srcDir, ignores = [], scope = 'all') {
|
|
354
|
-
const apis = [];
|
|
355
|
-
async function scan(dir) {
|
|
356
|
-
if (!(await (0, fs_extra_1.pathExists)(dir)))
|
|
357
|
-
return;
|
|
358
|
-
const entries = await (0, fs_extra_1.readdir)(dir, { withFileTypes: true });
|
|
359
|
-
for (const entry of entries) {
|
|
360
|
-
const fullPath = (0, path_1.join)(dir, entry.name);
|
|
361
|
-
// 跳过忽略的包
|
|
362
|
-
if (ignores.some(ign => fullPath.includes(ign)))
|
|
363
|
-
continue;
|
|
364
|
-
if (entry.isDirectory()) {
|
|
365
|
-
await scan(fullPath);
|
|
366
|
-
}
|
|
367
|
-
else if ([
|
|
368
|
-
'.java', '.ts', '.js', '.go', '.py',
|
|
369
|
-
].includes((0, path_1.extname)(entry.name))) {
|
|
370
|
-
// scope 过滤
|
|
371
|
-
if (scope === 'core' && !fullPath.includes('controller') && !fullPath.includes('service'))
|
|
372
|
-
continue;
|
|
373
|
-
if (scope === 'api' && !fullPath.includes('controller') && !fullPath.includes('route'))
|
|
374
|
-
continue;
|
|
375
|
-
const content = await (0, fs_extra_1.readFile)(fullPath, 'utf-8');
|
|
376
|
-
const relativePath = fullPath.replace(process.cwd() + '/', '');
|
|
377
|
-
// 类级路径前缀 (Spring @RequestMapping, NestJS @Controller)
|
|
378
|
-
let classPrefix = '';
|
|
379
|
-
const classMapping = content.match(/@(?:RequestMapping|Controller)\s*\(\s*(?:value\s*=\s*)?["']([^"']+)["']/);
|
|
380
|
-
if (classMapping)
|
|
381
|
-
classPrefix = classMapping[1];
|
|
382
|
-
// Java Spring
|
|
383
|
-
const javaMatches = content.matchAll(/@(GetMapping|PostMapping|PutMapping|DeleteMapping|PatchMapping|RequestMapping)\s*(?:\([^)]*\))?\s*(?:@[^(\n]*\s*)*(?:public\s+\S+\s+)?(\w+)\s*\(/g);
|
|
384
|
-
for (const match of javaMatches) {
|
|
385
|
-
const annMethod = match[1];
|
|
386
|
-
const funcName = match[2];
|
|
387
|
-
const pathMatch = content.substring(match.index || 0, (match.index || 0) + 200).match(/@\w+Mapping\s*\(\s*(?:value\s*=\s*)?["']([^"']+)["']/);
|
|
388
|
-
let apiPath = pathMatch ? pathMatch[1] : '/';
|
|
389
|
-
apiPath = classPrefix + apiPath; // 合并类级路径
|
|
390
|
-
apis.push({
|
|
391
|
-
method: annMethod.replace('Mapping', '').toUpperCase(),
|
|
392
|
-
path: apiPath,
|
|
393
|
-
name: funcName ? funcName.replace(/([A-Z])/g, ' $1').trim() : apiPath,
|
|
394
|
-
description: `从 ${entry.name} 扫描到的 API 端点`,
|
|
395
|
-
sourceFile: relativePath,
|
|
396
|
-
});
|
|
397
|
-
}
|
|
398
|
-
// TypeScript NestJS
|
|
399
|
-
const tsMatches = content.matchAll(/@(Get|Post|Put|Delete|Patch)\s*\(\s*(?:['"]([^'"]*)['"]\s*)?\)/g);
|
|
400
|
-
for (const match of tsMatches) {
|
|
401
|
-
apis.push({
|
|
402
|
-
method: match[1],
|
|
403
|
-
path: classPrefix + (match[2] || '/'),
|
|
404
|
-
name: match[2] ? match[2].replace(/^\//, '').replace(/\//g, ' ') : 'API',
|
|
405
|
-
description: `从 ${entry.name} 扫描到的 NestJS 端点`,
|
|
406
|
-
sourceFile: relativePath,
|
|
407
|
-
});
|
|
408
|
-
}
|
|
409
|
-
// Express routes
|
|
410
|
-
const expressMatches = content.matchAll(/(?:app|router)\.(get|post|put|delete|patch)\s*\(\s*['"]([^'"]+)['"]/g);
|
|
411
|
-
for (const match of expressMatches) {
|
|
412
|
-
apis.push({
|
|
413
|
-
method: match[1].toUpperCase(),
|
|
414
|
-
path: classPrefix + match[2],
|
|
415
|
-
name: match[2].replace(/^\//, '').replace(/\//g, ' '),
|
|
416
|
-
description: `从 ${entry.name} 扫描到的 Express 路由`,
|
|
417
|
-
sourceFile: relativePath,
|
|
418
|
-
});
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
await scan(srcDir);
|
|
424
|
-
return apis;
|
|
425
|
-
}
|
|
426
|
-
// ============================================================
|
|
427
|
-
// 全局文件更新
|
|
428
|
-
// ============================================================
|
|
429
|
-
async function updateGlobalOverview(projectName, projectType) {
|
|
430
|
-
const overviewPath = (0, path_1.join)(process.cwd(), '.speccore', 'GLOBAL', 'OVERVIEW.md');
|
|
431
|
-
if (!(await (0, fs_extra_1.pathExists)(overviewPath)))
|
|
432
|
-
return;
|
|
433
|
-
let content = await (0, fs_extra_1.readFile)(overviewPath, 'utf-8');
|
|
434
|
-
const today = new Date().toISOString().split('T')[0];
|
|
435
|
-
const newEntry = `| ${projectName} | ${projectType} | 已导入 | - |`;
|
|
436
|
-
if (content.includes('_待导入_')) {
|
|
437
|
-
content = content.replace('| _待导入_ | - | - | - |', newEntry);
|
|
438
|
-
}
|
|
439
|
-
await (0, fs_extra_1.writeFile)(overviewPath, content);
|
|
440
|
-
}
|
|
441
|
-
async function updateGlobalChangelog(description, version) {
|
|
442
|
-
const changelogPath = (0, path_1.join)(process.cwd(), '.speccore', 'GLOBAL', 'CHANGELOG.md');
|
|
443
|
-
if (!(await (0, fs_extra_1.pathExists)(changelogPath)))
|
|
444
|
-
return;
|
|
445
|
-
let content = await (0, fs_extra_1.readFile)(changelogPath, 'utf-8');
|
|
446
|
-
const today = new Date().toISOString().split('T')[0];
|
|
447
|
-
const newEntry = `| ${today} | ${version} | 导入 | ${description} | SpecCore |`;
|
|
448
|
-
if (content.includes('_暂无记录_')) {
|
|
449
|
-
content = content.replace('| _暂无记录_ | v1.0 | 创建 | 全量层模板初始化 | - |', newEntry);
|
|
450
|
-
}
|
|
451
|
-
else {
|
|
452
|
-
// 在变更记录表后面追加
|
|
453
|
-
const firstEntry = content.indexOf('|', content.indexOf('## 变更记录'));
|
|
454
|
-
const endOfLine = content.indexOf('\n', firstEntry);
|
|
455
|
-
if (endOfLine > 0) {
|
|
456
|
-
content = content.slice(0, endOfLine + 1) + newEntry + '\n' + content.slice(endOfLine + 1);
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
await (0, fs_extra_1.writeFile)(changelogPath, content);
|
|
460
|
-
}
|
|
461
|
-
// ============================================================
|
|
462
|
-
// 兼容函数
|
|
463
|
-
// ============================================================
|
|
464
|
-
async function importCode(sourcePath) {
|
|
465
|
-
if (!(await (0, fs_extra_1.pathExists)(sourcePath))) {
|
|
466
|
-
throw new Error(`Path not found: ${sourcePath}`);
|
|
467
|
-
}
|
|
468
|
-
const statsObj = await (0, fs_extra_1.stat)(sourcePath);
|
|
469
|
-
if (!statsObj.isDirectory()) {
|
|
470
|
-
throw new Error(`Path must be a directory: ${sourcePath}`);
|
|
471
|
-
}
|
|
472
|
-
const entries = await (0, fs_extra_1.readdir)(sourcePath, { withFileTypes: true });
|
|
473
|
-
const files = entries.filter((e) => e.isFile() && ['.java', '.ts', '.js', '.py', '.go'].includes((0, path_1.extname)(e.name)));
|
|
474
|
-
logger_1.logger.info(`Legacy mode: Found ${files.length} source files.`);
|
|
475
|
-
logger_1.logger.info('💡 Tip: Use speccore import --project=<name> --path=<path> --type=<type> for global layer import.');
|
|
476
|
-
return `Found ${files.length} source files in ${sourcePath}`;
|
|
477
|
-
}
|
|
478
|
-
async function importPRD(prdPath) {
|
|
479
|
-
if (!(await (0, fs_extra_1.pathExists)(prdPath))) {
|
|
480
|
-
throw new Error(`PRD file not found: ${prdPath}`);
|
|
481
|
-
}
|
|
482
|
-
const content = await (0, fs_extra_1.readFile)(prdPath, 'utf-8');
|
|
483
|
-
const requirements = extractRequirements(content);
|
|
484
|
-
logger_1.logger.info(`Extracted ${requirements.length} requirements (legacy mode)`);
|
|
485
|
-
return `Imported PRD from ${prdPath}: ${requirements.length} requirements`;
|
|
486
|
-
}
|
|
487
|
-
function extractRequirements(content) {
|
|
488
|
-
const requirements = [];
|
|
489
|
-
const patterns = [
|
|
490
|
-
/(?:需求|Requirement)\s*[::]\s*(.+)/g,
|
|
491
|
-
/(?:功能|Feature)\s*[::]\s*(.+)/g,
|
|
492
|
-
/\d+\.\s+(.+)/g,
|
|
493
|
-
];
|
|
494
|
-
for (const pattern of patterns) {
|
|
495
|
-
const matches = content.matchAll(pattern);
|
|
496
|
-
for (const match of matches) {
|
|
497
|
-
requirements.push(match[1].trim());
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
return requirements;
|
|
501
|
-
}
|
|
502
|
-
async function importPrototype(url) {
|
|
503
|
-
if (!url) {
|
|
504
|
-
throw new Error('Prototype URL is required');
|
|
505
|
-
}
|
|
506
|
-
logger_1.logger.info(`Importing prototype from ${url}`);
|
|
507
|
-
return `Prototype imported from ${url}`;
|
|
508
|
-
}
|
|
509
|
-
async function autoDetectImport(sourcePath) {
|
|
510
|
-
logger_1.logger.info('💡 Tip: Use speccore import --project=<name> --path=<path> --type=<type> for global layer import.');
|
|
511
|
-
logger_1.logger.info('Auto-detecting project structure...');
|
|
512
|
-
const results = [];
|
|
513
|
-
if (await (0, fs_extra_1.pathExists)((0, path_1.join)(sourcePath, 'src'))) {
|
|
514
|
-
results.push(await importCode(sourcePath));
|
|
515
|
-
}
|
|
516
|
-
const prdFiles = ['PRD.md', 'README.md', 'docs/PRD.md'];
|
|
517
|
-
for (const prdFile of prdFiles) {
|
|
518
|
-
if (await (0, fs_extra_1.pathExists)((0, path_1.join)(sourcePath, prdFile))) {
|
|
519
|
-
results.push(await importPRD((0, path_1.join)(sourcePath, prdFile)));
|
|
520
|
-
break;
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
return results.join('\n');
|
|
524
|
-
}
|
|
525
|
-
// ============================================================
|
|
526
|
-
// 宪法建议更新
|
|
527
|
-
// ============================================================
|
|
528
|
-
async function suggestConstitutionUpdate(projectName, projectType, techStack) {
|
|
529
|
-
const constPath = (0, path_1.join)(process.cwd(), '.speccore', 'CONSTITUTION.md');
|
|
530
|
-
if (!(await (0, fs_extra_1.pathExists)(constPath)))
|
|
531
|
-
return;
|
|
532
|
-
let content = await (0, fs_extra_1.readFile)(constPath, 'utf-8');
|
|
533
|
-
// 构建建议内容
|
|
534
|
-
const suggestions = [];
|
|
535
|
-
// 检测已存在的技术栈,避免重复
|
|
536
|
-
if (!content.includes(projectName)) {
|
|
537
|
-
suggestions.push(`### ${projectName} (${projectType})`);
|
|
538
|
-
suggestions.push(`- 来源: 自动检测自项目导入`);
|
|
539
|
-
suggestions.push(`- 技术栈: ${techStack}`);
|
|
540
|
-
}
|
|
541
|
-
// 框架检测 → 规则建议
|
|
542
|
-
if (techStack.includes('NestJS') && !content.includes('NestJS')) {
|
|
543
|
-
suggestions.push('- 框架: NestJS → 推荐 JWT + Passport 认证,DTO 校验用 class-validator');
|
|
544
|
-
}
|
|
545
|
-
else if (techStack.includes('Spring') && !content.includes('Spring Boot')) {
|
|
546
|
-
suggestions.push('- 框架: Spring Boot → 推荐统一异常 @ControllerAdvice');
|
|
547
|
-
}
|
|
548
|
-
else if (techStack.includes('Vue') && !content.includes('Vue')) {
|
|
549
|
-
suggestions.push('- 框架: Vue → 推荐 Composition API + Pinia 状态管理');
|
|
550
|
-
}
|
|
551
|
-
else if (techStack.includes('React') && !content.includes('React')) {
|
|
552
|
-
suggestions.push('- 框架: React → 推荐 Hooks + zustand 状态管理');
|
|
553
|
-
}
|
|
554
|
-
if (suggestions.length === 0)
|
|
555
|
-
return;
|
|
556
|
-
// 追加到宪法末尾
|
|
557
|
-
content += `\n\n<!-- 自动检测自 import ${projectName} (${new Date().toISOString().split('T')[0]}) -->\n`;
|
|
558
|
-
content += suggestions.join('\n') + '\n';
|
|
559
|
-
await (0, fs_extra_1.writeFile)(constPath, content);
|
|
560
|
-
logger_1.logger.info(' 📋 建议已追加到 CONSTITUTION.md(框架自动检测)');
|
|
561
|
-
}
|
|
562
|
-
// ============================================================
|
|
563
|
-
// AI 分析指引(供 Slash Command 使用)
|
|
564
|
-
// ============================================================
|
|
565
|
-
async function generateAnalysisPrompt(projectName, projectType, scanResult, projectPath) {
|
|
566
|
-
const projectDir = (0, path_1.join)(process.cwd(), '.speccore', 'GLOBAL', 'PROJECTS', projectName);
|
|
567
|
-
if (!(await (0, fs_extra_1.pathExists)(projectDir)))
|
|
568
|
-
return;
|
|
569
|
-
const apis = scanResult.apis;
|
|
570
|
-
const promptPath = (0, path_1.join)(projectDir, 'ANALYSIS_PROMPT.md');
|
|
571
|
-
// 扫描工程文件树(前 3 层)
|
|
572
|
-
const absSrcPath = (0, path_1.join)(process.cwd(), projectPath, 'src');
|
|
573
|
-
const fileTree = await buildFileTree(absSrcPath);
|
|
574
|
-
const content = `# AI 反工程分析任务: ${projectName}
|
|
575
|
-
|
|
576
|
-
> 本文档由 \`speccore import\` 自动生成。
|
|
577
|
-
> **目标:从源码倒推出完整的需求文档、技术方案、编码规范。**
|
|
578
|
-
> 用户触发 \`/spec-import-analyze\`,AI 按此指引完成。
|
|
579
|
-
|
|
580
|
-
## 项目信息
|
|
581
|
-
- 名称: ${projectName}
|
|
582
|
-
- 类型: ${projectType}
|
|
583
|
-
- 技术栈: ${scanResult.techStack || '未检测到(请检查 package.json/pom.xml)'}
|
|
584
|
-
- 源码路径: \`${projectPath}\`
|
|
585
|
-
|
|
586
|
-
## 工程文件树(src/ 前 3 层)
|
|
587
|
-
\`\`\`
|
|
588
|
-
${fileTree}
|
|
589
|
-
\`\`\`
|
|
590
|
-
|
|
591
|
-
---
|
|
592
|
-
## 第一步:深入源码反推需求
|
|
593
|
-
|
|
594
|
-
对每个扫描到的 API,**不仅要看 Controller,还要追溯 Service / Repository / Entity**:
|
|
595
|
-
|
|
596
|
-
${apis.map((api, i) => `\n### API ${i + 1}: ${api.method} ${api.path}
|
|
597
|
-
- **入口文件**: \`${api.sourceFile}\`
|
|
598
|
-
- **分析方式**:
|
|
599
|
-
1. 读取入口文件的完整代码
|
|
600
|
-
2. 如果 Controller 调用了 Service → 跳转到 Service 实现
|
|
601
|
-
3. 如果 Service 操作了数据库 → 读取 Repository/DAO 和 Entity/Model
|
|
602
|
-
4. 完整还原数据流转: 请求 → 校验 → 业务逻辑 → 持久化 → 响应`).join('\n')}
|
|
603
|
-
|
|
604
|
-
---
|
|
605
|
-
## 第二步:输出目标文件
|
|
606
|
-
|
|
607
|
-
### 2.1 需求文档 → \`PROJECTS/${projectName}/REQUIREMENT.md\`
|
|
608
|
-
${apis.map(api => `- **${api.method} ${api.path}**: 补充功能职责、参数校验、响应结构、业务规则`).join('\n')}
|
|
609
|
-
|
|
610
|
-
### 2.2 数据模型 → \`PROJECTS/${projectName}/SCHEMA.md\`
|
|
611
|
-
- 从 Entity/Model 文件提取所有数据表和字段
|
|
612
|
-
- 标注主键、索引、外键关系
|
|
613
|
-
- 绘制实体关系图(文字版)
|
|
614
|
-
|
|
615
|
-
### 2.3 技术方案 → \`PROJECTS/${projectName}/TECH.md\`
|
|
616
|
-
- 架构分层(Controller → Service → Repository)
|
|
617
|
-
- 关键设计决策(为什么要这么分层)
|
|
618
|
-
- 外部依赖(Redis/消息队列/第三方服务)
|
|
619
|
-
- 🚀 仅生成骨架,建议后续增强或选择性覆盖
|
|
620
|
-
|
|
621
|
-
### 2.4 编码规则 → \`RULES/\`
|
|
622
|
-
| 文件 | 检测重点 |
|
|
623
|
-
| :--- | :--- |
|
|
624
|
-
| API_CONVENTIONS.md | URL 前缀、版本号、RESTful 风格、统一响应格式 |
|
|
625
|
-
| EXCEPTION_HANDLING.md | 异常基类、@ControllerAdvice、错误码枚举 |
|
|
626
|
-
| NAMING.md | 包名规则、类名/方法名模式 |
|
|
627
|
-
| AUTH.md | 认证方式(JWT/Session/OAuth)、权限模型 |
|
|
628
|
-
|
|
629
|
-
### 2.5 宪法更新 → \`CONSTITUTION.md\`
|
|
630
|
-
补充全局约束:数据库版本、ORM 框架、缓存策略、日志框架
|
|
631
|
-
|
|
632
|
-
---
|
|
633
|
-
## 第三步:结果验证
|
|
634
|
-
|
|
635
|
-
分析完成后,在 REQUIREMENT.md 末尾追加:
|
|
636
|
-
\`\`\`
|
|
637
|
-
✅ AI 反工程分析完成 (${new Date().toISOString().split('T')[0]})
|
|
638
|
-
- 分析 API: ${apis.length} 个
|
|
639
|
-
- 生成规则: RULES/ 目录下 4 个文件
|
|
640
|
-
- 分析模式: 从源码倒推(无原始需求文档)
|
|
641
|
-
\`\`\`
|
|
642
|
-
`;
|
|
643
|
-
await (0, fs_extra_1.writeFile)(promptPath, content);
|
|
644
|
-
logger_1.logger.info(' 📋 生成 AI 分析指引: ANALYSIS_PROMPT.md');
|
|
645
|
-
logger_1.logger.info(' 💡 在 AI IDE 中输入 /spec-import-analyze 即可开始 AI 分析');
|
|
646
|
-
}
|
|
647
|
-
/**
|
|
648
|
-
* 构建工程文件树(前 3 层)
|
|
649
|
-
*/
|
|
650
|
-
async function buildFileTree(srcDir, depth = 0, maxDepth = 3) {
|
|
651
|
-
if (depth > maxDepth || !(await (0, fs_extra_1.pathExists)(srcDir)))
|
|
652
|
-
return '';
|
|
653
|
-
const lines = [];
|
|
654
|
-
const entries = await (0, fs_extra_1.readdir)(srcDir, { withFileTypes: true });
|
|
655
|
-
for (const entry of entries.slice(0, 20)) { // 限制每层 20 个
|
|
656
|
-
const indent = ' '.repeat(depth);
|
|
657
|
-
if (entry.isDirectory()) {
|
|
658
|
-
lines.push(`${indent}📁 ${entry.name}/`);
|
|
659
|
-
const subtree = await buildFileTree((0, path_1.join)(srcDir, entry.name), depth + 1, maxDepth);
|
|
660
|
-
if (subtree)
|
|
661
|
-
lines.push(subtree);
|
|
662
|
-
}
|
|
663
|
-
else {
|
|
664
|
-
const ext = (0, path_1.extname)(entry.name);
|
|
665
|
-
const emoji = ['.java', '.ts', '.js', '.go', '.py'].includes(ext) ? '📄' : '📎';
|
|
666
|
-
lines.push(`${indent}${emoji} ${entry.name}`);
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
if (depth > 0 && lines.length === 0)
|
|
670
|
-
return '';
|
|
671
|
-
return lines.join('\n');
|
|
672
|
-
}
|
|
673
|
-
//# sourceMappingURL=import.js.map
|