@dommaker/harness 0.16.0 → 0.16.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/README.md +1 -0
- package/bin/harness.js +73 -1
- package/dist/cli/commands/doc-freshness-check.d.ts +53 -0
- package/dist/cli/commands/doc-freshness-check.d.ts.map +1 -0
- package/dist/cli/commands/doc-freshness-check.js +366 -0
- package/dist/cli/commands/doc-freshness-check.js.map +1 -0
- package/dist/cli/commands/index.d.ts +4 -1
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +13 -1
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/knowledge.d.ts +12 -0
- package/dist/cli/commands/knowledge.d.ts.map +1 -1
- package/dist/cli/commands/knowledge.js +134 -0
- package/dist/cli/commands/knowledge.js.map +1 -1
- package/dist/cli/commands/release.d.ts +11 -0
- package/dist/cli/commands/release.d.ts.map +1 -1
- package/dist/cli/commands/release.js +43 -12
- package/dist/cli/commands/release.js.map +1 -1
- package/dist/cli/commands/sdd.d.ts +8 -0
- package/dist/cli/commands/sdd.d.ts.map +1 -0
- package/dist/cli/commands/sdd.js +68 -0
- package/dist/cli/commands/sdd.js.map +1 -0
- package/dist/cli/commands/spec-baseline-check.d.ts +30 -0
- package/dist/cli/commands/spec-baseline-check.d.ts.map +1 -0
- package/dist/cli/commands/spec-baseline-check.js +366 -0
- package/dist/cli/commands/spec-baseline-check.js.map +1 -0
- package/dist/cli/commands/sync-docs.d.ts +2 -0
- package/dist/cli/commands/sync-docs.d.ts.map +1 -1
- package/dist/cli/commands/sync-docs.js +320 -1
- package/dist/cli/commands/sync-docs.js.map +1 -1
- package/dist/knowledge/audit.d.ts +1 -1
- package/dist/knowledge/audit.d.ts.map +1 -1
- package/dist/knowledge/audit.js +59 -7
- package/dist/knowledge/audit.js.map +1 -1
- package/dist/knowledge/index-generator.d.ts +36 -0
- package/dist/knowledge/index-generator.d.ts.map +1 -0
- package/dist/knowledge/index-generator.js +241 -0
- package/dist/knowledge/index-generator.js.map +1 -0
- package/dist/knowledge/index.d.ts +2 -0
- package/dist/knowledge/index.d.ts.map +1 -1
- package/dist/knowledge/index.js +3 -1
- package/dist/knowledge/index.js.map +1 -1
- package/dist/knowledge/ingest.js +2 -2
- package/dist/knowledge/ingest.js.map +1 -1
- package/dist/knowledge/lifecycle.d.ts +15 -0
- package/dist/knowledge/lifecycle.d.ts.map +1 -1
- package/dist/knowledge/lifecycle.js +70 -0
- package/dist/knowledge/lifecycle.js.map +1 -1
- package/dist/knowledge/lint.js +3 -3
- package/dist/knowledge/lint.js.map +1 -1
- package/dist/knowledge/primitives/code-structure.d.ts +37 -0
- package/dist/knowledge/primitives/code-structure.d.ts.map +1 -0
- package/dist/knowledge/primitives/code-structure.js +168 -0
- package/dist/knowledge/primitives/code-structure.js.map +1 -0
- package/dist/knowledge/types.d.ts +3 -1
- package/dist/knowledge/types.d.ts.map +1 -1
- package/dist/knowledge/types.js.map +1 -1
- package/dist/sdd/index-generator.d.ts +22 -0
- package/dist/sdd/index-generator.d.ts.map +1 -0
- package/dist/sdd/index-generator.js +118 -0
- package/dist/sdd/index-generator.js.map +1 -0
- package/package.json +1 -1
- package/src/cli/commands/CONTEXT.md +3 -3
- package/src/cli/commands/__tests__/doc-freshness-check.test.ts +168 -0
- package/src/cli/commands/__tests__/knowledge.test.ts +69 -1
- package/src/cli/commands/__tests__/spec-baseline-check.test.ts +235 -0
- package/src/cli/commands/__tests__/sync-docs-agents.test.ts +226 -0
- package/src/cli/commands/doc-freshness-check.ts +397 -0
- package/src/cli/commands/index.ts +4 -1
- package/src/cli/commands/knowledge.ts +150 -0
- package/src/cli/commands/release.ts +48 -12
- package/src/cli/commands/sdd.ts +44 -0
- package/src/cli/commands/spec-baseline-check.ts +384 -0
- package/src/cli/commands/sync-docs.ts +355 -1
- package/src/knowledge/__tests__/audit.test.ts +56 -0
- package/src/knowledge/__tests__/index-generator.test.ts +335 -0
- package/src/knowledge/audit.ts +64 -8
- package/src/knowledge/index-generator.ts +233 -0
- package/src/knowledge/index.ts +2 -0
- package/src/knowledge/ingest.ts +2 -2
- package/src/knowledge/lifecycle.ts +71 -0
- package/src/knowledge/lint.ts +3 -3
- package/src/knowledge/primitives/__tests__/code-structure.test.ts +120 -0
- package/src/knowledge/primitives/code-structure.ts +161 -0
- package/src/knowledge/types.ts +4 -1
- package/src/sdd/__tests__/index-generator.test.ts +150 -0
- package/src/sdd/index-generator.ts +109 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* harness spec-baseline-check 命令
|
|
3
|
+
*
|
|
4
|
+
* 验证 spec 文件的前置条件(Baseline / 前置条件)是否满足。
|
|
5
|
+
* 纯代码操作,零 LLM 调用。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import * as fs from 'fs';
|
|
10
|
+
import * as path from 'path';
|
|
11
|
+
|
|
12
|
+
export interface SpecBaselineCheckOptions {
|
|
13
|
+
/** 项目路径 */
|
|
14
|
+
projectPath?: string;
|
|
15
|
+
/** 输出 JSON 格式 */
|
|
16
|
+
json?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** 前置条件验证结果 */
|
|
20
|
+
export interface PrerequisiteResult {
|
|
21
|
+
/** 前置条件描述 */
|
|
22
|
+
prerequisite: string;
|
|
23
|
+
/** 是否满足 */
|
|
24
|
+
satisfied: boolean;
|
|
25
|
+
/** 证据 */
|
|
26
|
+
evidence: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ============================================
|
|
30
|
+
// 解析器
|
|
31
|
+
// ============================================
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 从 spec 内容中提取 Baseline/前置条件 section
|
|
35
|
+
*/
|
|
36
|
+
export function extractBaselineSection(content: string): string[] {
|
|
37
|
+
const lines = content.split('\n');
|
|
38
|
+
const prerequisites: string[] = [];
|
|
39
|
+
|
|
40
|
+
// 查找 ## Baseline 或 ## 前置条件 section
|
|
41
|
+
let inSection = false;
|
|
42
|
+
let sectionLevel = 0;
|
|
43
|
+
|
|
44
|
+
for (const line of lines) {
|
|
45
|
+
const headerMatch = line.match(/^(#{1,6})\s+(.+)/);
|
|
46
|
+
|
|
47
|
+
if (headerMatch) {
|
|
48
|
+
const level = headerMatch[1].length;
|
|
49
|
+
const title = headerMatch[2].trim();
|
|
50
|
+
|
|
51
|
+
// 检查是否是目标 section
|
|
52
|
+
if (/^(Baseline|前置条件|Prerequisites|Prereqs)$/i.test(title)) {
|
|
53
|
+
inSection = true;
|
|
54
|
+
sectionLevel = level;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 同级或更高级的标题结束 section
|
|
59
|
+
if (inSection && level <= sectionLevel) {
|
|
60
|
+
inSection = false;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 在 section 中提取列表项
|
|
66
|
+
if (inSection) {
|
|
67
|
+
const listMatch = line.match(/^\s*[-*+]\s+(.+)/);
|
|
68
|
+
if (listMatch) {
|
|
69
|
+
prerequisites.push(listMatch[1].trim());
|
|
70
|
+
}
|
|
71
|
+
// 也支持编号列表
|
|
72
|
+
const numberedMatch = line.match(/^\s*\d+\.\s+(.+)/);
|
|
73
|
+
if (numberedMatch) {
|
|
74
|
+
prerequisites.push(numberedMatch[1].trim());
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return prerequisites;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ============================================
|
|
83
|
+
// 验证器
|
|
84
|
+
// ============================================
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 检查文件/目录是否存在
|
|
88
|
+
*/
|
|
89
|
+
function checkFileExists(pattern: string, projectPath: string): { exists: boolean; evidence: string } {
|
|
90
|
+
// 提取路径引用(反引号中的路径、引号中的路径、或直接的路径模式)
|
|
91
|
+
const pathPatterns = [
|
|
92
|
+
/`([^`]+\.[a-z]+)`/g, // `src/foo.ts`
|
|
93
|
+
/["']([^"']+\.[a-z]+)["']/g, // "src/foo.ts"
|
|
94
|
+
/`([^`]+\/)`/g, // `src/foo/`
|
|
95
|
+
/(\S+\/\S+\.[a-z]+)/g, // src/foo.ts
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
const paths: string[] = [];
|
|
99
|
+
for (const p of pathPatterns) {
|
|
100
|
+
p.lastIndex = 0;
|
|
101
|
+
let m;
|
|
102
|
+
while ((m = p.exec(pattern)) !== null) {
|
|
103
|
+
paths.push(m[1]);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (paths.length === 0) {
|
|
108
|
+
return { exists: false, evidence: '无法从描述中提取路径' };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const found: string[] = [];
|
|
112
|
+
const missing: string[] = [];
|
|
113
|
+
|
|
114
|
+
for (const p of paths) {
|
|
115
|
+
const fullPath = path.resolve(projectPath, p);
|
|
116
|
+
if (fs.existsSync(fullPath)) {
|
|
117
|
+
found.push(p);
|
|
118
|
+
} else {
|
|
119
|
+
missing.push(p);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (missing.length === 0 && found.length > 0) {
|
|
124
|
+
return { exists: true, evidence: `文件存在: ${found.join(', ')}` };
|
|
125
|
+
}
|
|
126
|
+
if (found.length > 0) {
|
|
127
|
+
return { exists: false, evidence: `部分存在: ${found.join(', ')}; 缺失: ${missing.join(', ')}` };
|
|
128
|
+
}
|
|
129
|
+
return { exists: false, evidence: `文件不存在: ${missing.join(', ')}` };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 检查代码中是否存在特定模式
|
|
134
|
+
*/
|
|
135
|
+
function checkCodePattern(pattern: string, projectPath: string): { exists: boolean; evidence: string } {
|
|
136
|
+
// 从描述中提取关键词
|
|
137
|
+
const keywords: string[] = [];
|
|
138
|
+
|
|
139
|
+
// 提取反引号中的代码
|
|
140
|
+
const codePatterns = pattern.match(/`([^`]+)`/g);
|
|
141
|
+
if (codePatterns) {
|
|
142
|
+
for (const cp of codePatterns) {
|
|
143
|
+
const inner = cp.slice(1, -1);
|
|
144
|
+
if (inner.length >= 2) {
|
|
145
|
+
keywords.push(inner);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 提取 "implemented" / "exists" / "已实现" 等关键词后的内容
|
|
151
|
+
const implMatch = pattern.match(/(?:implemented|exists|已实现|已完成|已存在)[\s::]+(.+)/i);
|
|
152
|
+
if (implMatch) {
|
|
153
|
+
keywords.push(implMatch[1].trim());
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (keywords.length === 0) {
|
|
157
|
+
return { exists: false, evidence: '无法从描述中提取搜索关键词' };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const found: string[] = [];
|
|
161
|
+
const notFound: string[] = [];
|
|
162
|
+
|
|
163
|
+
for (const kw of keywords) {
|
|
164
|
+
let count = 0;
|
|
165
|
+
function walkDir(dir: string) {
|
|
166
|
+
let entries: fs.Dirent[];
|
|
167
|
+
try {
|
|
168
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
169
|
+
} catch {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
for (const entry of entries) {
|
|
173
|
+
if (entry.name.startsWith('.') || entry.name === 'node_modules' || entry.name === 'dist') continue;
|
|
174
|
+
const fullPath = path.join(dir, entry.name);
|
|
175
|
+
if (entry.isDirectory()) {
|
|
176
|
+
walkDir(fullPath);
|
|
177
|
+
} else if (entry.isFile() && (fullPath.endsWith('.ts') || fullPath.endsWith('.js'))) {
|
|
178
|
+
try {
|
|
179
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
180
|
+
if (content.includes(kw)) count++;
|
|
181
|
+
} catch { /* skip */ }
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
walkDir(projectPath);
|
|
186
|
+
if (count > 0) {
|
|
187
|
+
found.push(`${kw} (${count} files)`);
|
|
188
|
+
} else {
|
|
189
|
+
notFound.push(kw);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (notFound.length === 0 && found.length > 0) {
|
|
194
|
+
return { exists: true, evidence: `代码中找到: ${found.join(', ')}` };
|
|
195
|
+
}
|
|
196
|
+
if (found.length > 0) {
|
|
197
|
+
return { exists: false, evidence: `找到: ${found.join(', ')}; 未找到: ${notFound.join(', ')}` };
|
|
198
|
+
}
|
|
199
|
+
return { exists: false, evidence: `代码中未找到: ${notFound.join(', ')}` };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* 检查包依赖是否安装
|
|
204
|
+
*/
|
|
205
|
+
function checkDependency(pattern: string, projectPath: string): { exists: boolean; evidence: string } {
|
|
206
|
+
// 提取包名
|
|
207
|
+
const pkgPatterns = [
|
|
208
|
+
/`(@?[\w-]+\/[\w-]+)`/g, // `@scope/pkg` or `pkg-name`
|
|
209
|
+
/`([\w-]+)`/g, // `pkg`
|
|
210
|
+
/install(?:ed)?\s+(\S+)/gi, // installed pkg-name
|
|
211
|
+
];
|
|
212
|
+
|
|
213
|
+
const packages: string[] = [];
|
|
214
|
+
for (const p of pkgPatterns) {
|
|
215
|
+
p.lastIndex = 0;
|
|
216
|
+
let m;
|
|
217
|
+
while ((m = p.exec(pattern)) !== null) {
|
|
218
|
+
const pkg = m[1];
|
|
219
|
+
if (pkg.length >= 2 && !pkg.includes('/') && !pkg.includes('.')) {
|
|
220
|
+
packages.push(pkg);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (packages.length === 0) {
|
|
226
|
+
return { exists: false, evidence: '无法从描述中提取包名' };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const found: string[] = [];
|
|
230
|
+
const missing: string[] = [];
|
|
231
|
+
|
|
232
|
+
const pkgJsonPath = path.join(projectPath, 'package.json');
|
|
233
|
+
let deps: Record<string, string> = {};
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
|
|
237
|
+
deps = { ...pkgJson.dependencies, ...pkgJson.devDependencies };
|
|
238
|
+
} catch {
|
|
239
|
+
return { exists: false, evidence: '无法读取 package.json' };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
for (const pkg of packages) {
|
|
243
|
+
if (deps[pkg] || deps[`@types/${pkg}`]) {
|
|
244
|
+
found.push(pkg);
|
|
245
|
+
} else {
|
|
246
|
+
// 检查 node_modules
|
|
247
|
+
const nmPath = path.join(projectPath, 'node_modules', pkg);
|
|
248
|
+
if (fs.existsSync(nmPath)) {
|
|
249
|
+
found.push(pkg);
|
|
250
|
+
} else {
|
|
251
|
+
missing.push(pkg);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (missing.length === 0 && found.length > 0) {
|
|
257
|
+
return { exists: true, evidence: `依赖已安装: ${found.join(', ')}` };
|
|
258
|
+
}
|
|
259
|
+
if (found.length > 0) {
|
|
260
|
+
return { exists: false, evidence: `已安装: ${found.join(', ')}; 缺失: ${missing.join(', ')}` };
|
|
261
|
+
}
|
|
262
|
+
return { exists: false, evidence: `依赖缺失: ${missing.join(', ')}` };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* 综合验证一条前置条件
|
|
267
|
+
*/
|
|
268
|
+
function verifyPrerequisite(prereq: string, projectPath: string): PrerequisiteResult {
|
|
269
|
+
const lower = prereq.toLowerCase();
|
|
270
|
+
|
|
271
|
+
// 路径/文件存在性检查
|
|
272
|
+
if (lower.includes('文件') || lower.includes('file') || lower.includes('目录') || lower.includes('directory')
|
|
273
|
+
|| /`[^`]+\.[a-z]+`/.test(prereq) || /\//.test(prereq)) {
|
|
274
|
+
const { exists, evidence } = checkFileExists(prereq, projectPath);
|
|
275
|
+
if (exists || evidence.includes('文件不存在')) {
|
|
276
|
+
return { prerequisite: prereq, satisfied: exists, evidence };
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// 依赖检查
|
|
281
|
+
if (lower.includes('依赖') || lower.includes('install') || lower.includes('package') || lower.includes('npm')) {
|
|
282
|
+
const { exists, evidence } = checkDependency(prereq, projectPath);
|
|
283
|
+
return { prerequisite: prereq, satisfied: exists, evidence };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// 代码模式检查
|
|
287
|
+
if (lower.includes('实现') || lower.includes('implement') || lower.includes('exist')
|
|
288
|
+
|| lower.includes('已') || lower.includes('必须')
|
|
289
|
+
|| /`[^`]+`/.test(prereq)) {
|
|
290
|
+
const { exists, evidence } = checkCodePattern(prereq, projectPath);
|
|
291
|
+
return { prerequisite: prereq, satisfied: exists, evidence };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// 默认:无法自动验证
|
|
295
|
+
return {
|
|
296
|
+
prerequisite: prereq,
|
|
297
|
+
satisfied: true,
|
|
298
|
+
evidence: '无法自动验证(需人工确认)',
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ============================================
|
|
303
|
+
// 输出格式化
|
|
304
|
+
// ============================================
|
|
305
|
+
|
|
306
|
+
function formatTable(results: PrerequisiteResult[]): string {
|
|
307
|
+
if (results.length === 0) return chalk.yellow('未找到 Baseline / 前置条件 section');
|
|
308
|
+
|
|
309
|
+
const lines: string[] = [];
|
|
310
|
+
lines.push(chalk.blue(`前置条件检查 (${results.length} 条)\n`));
|
|
311
|
+
|
|
312
|
+
const satisfied = results.filter(r => r.satisfied).length;
|
|
313
|
+
const failed = results.filter(r => !r.satisfied);
|
|
314
|
+
|
|
315
|
+
lines.push(chalk.bold(` 满足: ${satisfied}/${results.length}`));
|
|
316
|
+
if (failed.length > 0) {
|
|
317
|
+
lines.push(chalk.red(` 未满足: ${failed.length}\n`));
|
|
318
|
+
} else {
|
|
319
|
+
lines.push(chalk.green(' 全部满足\n'));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
for (const r of results) {
|
|
323
|
+
const icon = r.satisfied ? chalk.green('✓') : chalk.red('✗');
|
|
324
|
+
lines.push(` ${icon} ${r.prerequisite}`);
|
|
325
|
+
if (!r.satisfied) {
|
|
326
|
+
lines.push(` ${chalk.yellow(r.evidence)}`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return lines.join('\n');
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// ============================================
|
|
334
|
+
// 主入口
|
|
335
|
+
// ============================================
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* spec-baseline-check CLI 命令
|
|
339
|
+
*/
|
|
340
|
+
export async function specBaselineCheck(
|
|
341
|
+
specPath: string,
|
|
342
|
+
options: SpecBaselineCheckOptions = {},
|
|
343
|
+
): Promise<void> {
|
|
344
|
+
// 验证文件存在
|
|
345
|
+
const resolvedPath = path.resolve(specPath);
|
|
346
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
347
|
+
console.error(chalk.red(`文件不存在: ${resolvedPath}`));
|
|
348
|
+
process.exitCode = 1;
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const projectPath = options.projectPath || path.dirname(resolvedPath);
|
|
353
|
+
|
|
354
|
+
// 读取 spec
|
|
355
|
+
const content = fs.readFileSync(resolvedPath, 'utf-8');
|
|
356
|
+
|
|
357
|
+
// 提取前置条件
|
|
358
|
+
const prerequisites = extractBaselineSection(content);
|
|
359
|
+
|
|
360
|
+
if (prerequisites.length === 0) {
|
|
361
|
+
if (options.json) {
|
|
362
|
+
console.log(JSON.stringify({ prerequisites: [], message: '未找到 Baseline / 前置条件 section' }, null, 2));
|
|
363
|
+
} else {
|
|
364
|
+
console.log(chalk.yellow('未找到 ## Baseline 或 ## 前置条件 section'));
|
|
365
|
+
}
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// 验证每条前置条件
|
|
370
|
+
const results = prerequisites.map(p => verifyPrerequisite(p, projectPath));
|
|
371
|
+
|
|
372
|
+
// 输出
|
|
373
|
+
if (options.json) {
|
|
374
|
+
console.log(JSON.stringify(results, null, 2));
|
|
375
|
+
} else {
|
|
376
|
+
console.log(formatTable(results));
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// 有未满足的前置条件时 exit 1
|
|
380
|
+
const failed = results.filter(r => !r.satisfied);
|
|
381
|
+
if (failed.length > 0) {
|
|
382
|
+
process.exitCode = 1;
|
|
383
|
+
}
|
|
384
|
+
}
|