@dommaker/harness 0.14.0 → 0.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/bin/harness.js +71 -0
- 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 +140 -6
- 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 +80 -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/core/constraints/checker.js +1 -1
- package/dist/core/constraints/checker.js.map +1 -1
- package/dist/core/constraints/definitions.d.ts.map +1 -1
- package/dist/core/constraints/definitions.js +30 -24
- package/dist/core/constraints/definitions.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 +60 -8
- package/dist/knowledge/audit.js.map +1 -1
- package/dist/knowledge/doctor.d.ts +1 -1
- package/dist/knowledge/doctor.d.ts.map +1 -1
- package/dist/knowledge/import.d.ts +1 -1
- package/dist/knowledge/import.d.ts.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 +4 -1
- package/dist/knowledge/index.d.ts.map +1 -1
- package/dist/knowledge/index.js +4 -2
- package/dist/knowledge/index.js.map +1 -1
- package/dist/knowledge/ingest.d.ts +1 -1
- package/dist/knowledge/ingest.d.ts.map +1 -1
- package/dist/knowledge/ingest.js +2 -2
- package/dist/knowledge/ingest.js.map +1 -1
- package/dist/knowledge/lifecycle-hooks.d.ts +1 -1
- package/dist/knowledge/lifecycle-hooks.d.ts.map +1 -1
- package/dist/knowledge/lifecycle.d.ts +16 -1
- 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.d.ts +1 -1
- package/dist/knowledge/lint.d.ts.map +1 -1
- package/dist/knowledge/lint.js +4 -4
- 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/query.d.ts +1 -1
- package/dist/knowledge/query.d.ts.map +1 -1
- package/dist/knowledge/reference-tracker.d.ts +1 -1
- package/dist/knowledge/reference-tracker.d.ts.map +1 -1
- package/dist/knowledge/store.d.ts +29 -2
- package/dist/knowledge/store.d.ts.map +1 -1
- package/dist/knowledge/store.js +10 -5
- package/dist/knowledge/store.js.map +1 -1
- package/dist/knowledge/types.d.ts +17 -1
- package/dist/knowledge/types.d.ts.map +1 -1
- package/dist/knowledge/types.js.map +1 -1
- package/dist/monitoring/knowledge-evolver.d.ts +1 -1
- package/dist/monitoring/knowledge-evolver.d.ts.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/dist/types/constraint.d.ts +1 -1
- package/dist/types/constraint.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/checker.test.ts +4 -4
- package/src/cli/commands/__tests__/doc-freshness-check.test.ts +168 -0
- package/src/cli/commands/__tests__/knowledge.test.ts +70 -2
- package/src/cli/commands/__tests__/spec-baseline-check.test.ts +235 -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 +151 -1
- package/src/cli/commands/release.ts +86 -12
- package/src/cli/commands/sdd.ts +44 -0
- package/src/cli/commands/spec-baseline-check.ts +384 -0
- package/src/context/__tests__/knowledge-injector.test.ts +1 -1
- package/src/core/constraints/checker.ts +1 -1
- package/src/core/constraints/definitions.ts +31 -25
- package/src/knowledge/__tests__/audit.test.ts +57 -1
- package/src/knowledge/__tests__/doctor.test.ts +1 -1
- package/src/knowledge/__tests__/import.test.ts +1 -1
- package/src/knowledge/__tests__/index-generator.test.ts +335 -0
- package/src/knowledge/__tests__/ingest.test.ts +1 -1
- package/src/knowledge/__tests__/lifecycle-hooks.test.ts +1 -1
- package/src/knowledge/__tests__/lifecycle.test.ts +1 -1
- package/src/knowledge/__tests__/lint.test.ts +1 -1
- package/src/knowledge/__tests__/query.test.ts +1 -1
- package/src/knowledge/__tests__/reference-tracker.test.ts +1 -1
- package/src/knowledge/__tests__/store.test.ts +1 -1
- package/src/knowledge/audit.ts +67 -10
- package/src/knowledge/doctor.ts +1 -1
- package/src/knowledge/import.ts +1 -1
- package/src/knowledge/index-generator.ts +233 -0
- package/src/knowledge/index.ts +4 -1
- package/src/knowledge/ingest.ts +3 -3
- package/src/knowledge/lifecycle-hooks.ts +1 -1
- package/src/knowledge/lifecycle.ts +72 -1
- package/src/knowledge/lint.ts +5 -5
- package/src/knowledge/primitives/__tests__/code-structure.test.ts +120 -0
- package/src/knowledge/primitives/code-structure.ts +161 -0
- package/src/knowledge/query.ts +1 -1
- package/src/knowledge/reference-tracker.ts +1 -1
- package/src/knowledge/store.ts +27 -4
- package/src/knowledge/types.ts +21 -1
- package/src/monitoring/knowledge-evolver.ts +1 -1
- package/src/sdd/__tests__/index-generator.test.ts +150 -0
- package/src/sdd/index-generator.ts +109 -0
- package/src/types/constraint.ts +1 -1
- package/dist/cli/commands/diagnose.d.ts +0 -16
- package/dist/cli/commands/diagnose.d.ts.map +0 -1
- package/dist/cli/commands/diagnose.js +0 -195
- package/dist/cli/commands/diagnose.js.map +0 -1
- package/dist/cli/commands/propose.d.ts +0 -18
- package/dist/cli/commands/propose.d.ts.map +0 -1
- package/dist/cli/commands/propose.js +0 -331
- package/dist/cli/commands/propose.js.map +0 -1
- package/dist/cli/commands/traces.d.ts +0 -15
- package/dist/cli/commands/traces.d.ts.map +0 -1
- package/dist/cli/commands/traces.js +0 -167
- package/dist/cli/commands/traces.js.map +0 -1
- package/dist/constraints/definitions.d.ts +0 -39
- package/dist/constraints/definitions.d.ts.map +0 -1
- package/dist/constraints/definitions.js +0 -71
- package/dist/constraints/definitions.js.map +0 -1
- package/dist/constraints/quality.d.ts +0 -11
- package/dist/constraints/quality.d.ts.map +0 -1
- package/dist/constraints/quality.js +0 -257
- package/dist/constraints/quality.js.map +0 -1
- package/dist/constraints/safety.d.ts +0 -13
- package/dist/constraints/safety.d.ts.map +0 -1
- package/dist/constraints/safety.js +0 -103
- package/dist/constraints/safety.js.map +0 -1
- package/dist/extensions/long-running/constraints.d.ts +0 -38
- package/dist/extensions/long-running/constraints.d.ts.map +0 -1
- package/dist/extensions/long-running/constraints.js +0 -153
- package/dist/extensions/long-running/constraints.js.map +0 -1
- package/dist/extensions/long-running/index.d.ts +0 -30
- package/dist/extensions/long-running/index.d.ts.map +0 -1
- package/dist/extensions/long-running/index.js +0 -50
- package/dist/extensions/long-running/index.js.map +0 -1
- package/dist/extensions/long-running/types.d.ts +0 -154
- package/dist/extensions/long-running/types.d.ts.map +0 -1
- package/dist/extensions/long-running/types.js +0 -9
- package/dist/extensions/long-running/types.js.map +0 -1
- package/dist/presets/long-running.d.ts +0 -33
- package/dist/presets/long-running.d.ts.map +0 -1
- package/dist/presets/long-running.js +0 -52
- package/dist/presets/long-running.js.map +0 -1
|
@@ -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
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { KnowledgeInjector } from '../knowledge-injector';
|
|
6
6
|
import { KnowledgeQuery } from '../../knowledge/query';
|
|
7
|
-
import { KnowledgeStore } from '../../knowledge/store';
|
|
7
|
+
import { FileKnowledgeStore as KnowledgeStore } from '../../knowledge/store';
|
|
8
8
|
import * as fs from 'fs';
|
|
9
9
|
import * as path from 'path';
|
|
10
10
|
import * as os from 'os';
|
|
@@ -238,31 +238,6 @@ https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agent
|
|
|
238
238
|
promptInjection: '开始编写代码前,必须确认:需求来源明确(Spec/Issue/Roadmap/用户指令)、验收标准(AC)已定义、边界情况已明确。不要凭假设或猜测开始实现。',
|
|
239
239
|
},
|
|
240
240
|
|
|
241
|
-
/**
|
|
242
|
-
* 必须在 worktree 中开发
|
|
243
|
-
* 原因:隔离工作区,安全回滚,并行开发
|
|
244
|
-
* 来源:Superpowers must_use_worktree
|
|
245
|
-
*/
|
|
246
|
-
must_use_worktree: {
|
|
247
|
-
id: 'must_use_worktree',
|
|
248
|
-
rule: 'CODE CHANGES MUST BE ISOLATED IN A WORKTREE',
|
|
249
|
-
message: '代码开发必须在隔离的 worktree 中进行',
|
|
250
|
-
level: 'iron_law',
|
|
251
|
-
trigger: ['code_implementation'],
|
|
252
|
-
enforcement: 'check-worktree',
|
|
253
|
-
description: `所有代码修改必须在隔离的 worktree 中进行,不要在原始仓库目录直接编辑。
|
|
254
|
-
|
|
255
|
-
【原因】
|
|
256
|
-
- 隔离风险:worktree 中的改动不会影响主工作区
|
|
257
|
-
- 安全回滚:删除 worktree 即可放弃改动
|
|
258
|
-
- 并行开发:多个任务可同时在不同 worktree 中进行
|
|
259
|
-
|
|
260
|
-
【禁止】
|
|
261
|
-
- 在原始仓库目录直接编辑文件
|
|
262
|
-
- 跳过 worktree 创建直接执行 Agent`,
|
|
263
|
-
promptInjection: '所有代码修改必须在隔离的 worktree 中进行。不要在原始仓库目录直接编辑文件。worktree 提供了安全回滚和并行开发能力。如果你发现不在 worktree 中,立即停止并要求创建 worktree。',
|
|
264
|
-
},
|
|
265
|
-
|
|
266
241
|
/**
|
|
267
242
|
* 禁止模糊完成声明
|
|
268
243
|
* 原因:质量底线,必须有可量化证据
|
|
@@ -371,6 +346,37 @@ https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agent
|
|
|
371
346
|
// ========================================
|
|
372
347
|
|
|
373
348
|
export const GUIDELINES: Record<string, Constraint> = {
|
|
349
|
+
/**
|
|
350
|
+
* 高风险改动优先使用 worktree
|
|
351
|
+
* 原因:隔离工作区,安全回滚,并行开发
|
|
352
|
+
* 来源:Superpowers prefer_worktree
|
|
353
|
+
*/
|
|
354
|
+
prefer_worktree: {
|
|
355
|
+
id: 'prefer_worktree',
|
|
356
|
+
rule: 'USE WORKTREE FOR HIGH-RISK CHANGES',
|
|
357
|
+
message: '高风险改动应在 worktree 中进行',
|
|
358
|
+
level: 'guideline',
|
|
359
|
+
trigger: ['code_implementation'],
|
|
360
|
+
enforcement: 'check-worktree',
|
|
361
|
+
description: `高风险改动应在隔离的 worktree 中进行。
|
|
362
|
+
|
|
363
|
+
【何时需要 worktree】
|
|
364
|
+
- 新功能开发(新增模块/API)
|
|
365
|
+
- 跨模块改动(>3 文件)
|
|
366
|
+
- 基础设施变更(CI/CD、依赖)
|
|
367
|
+
|
|
368
|
+
【何时不需要】
|
|
369
|
+
- 配置文件修改
|
|
370
|
+
- 单文件 bug fix
|
|
371
|
+
- 外科手术式修复(typo、missing import)
|
|
372
|
+
|
|
373
|
+
【原因】
|
|
374
|
+
- 隔离风险:worktree 中的改动不会影响主工作区
|
|
375
|
+
- 安全回滚:删除 worktree 即可放弃改动
|
|
376
|
+
- 并行开发:多个任务可同时在不同 worktree 中进行`,
|
|
377
|
+
promptInjection: '高风险改动(新功能、跨模块、基础设施)应在 worktree 中进行。配置修改、单文件 fix 可直接编辑。',
|
|
378
|
+
},
|
|
379
|
+
|
|
374
380
|
/**
|
|
375
381
|
* 禁止无调查修复 bug
|
|
376
382
|
* 例外:简单 typo、配置错误
|
|
@@ -14,7 +14,7 @@ import * as fs from 'fs';
|
|
|
14
14
|
import * as path from 'path';
|
|
15
15
|
import * as os from 'os';
|
|
16
16
|
import { KnowledgeAudit } from '../audit';
|
|
17
|
-
import { KnowledgeStore } from '../store';
|
|
17
|
+
import { FileKnowledgeStore as KnowledgeStore } from '../store';
|
|
18
18
|
import type { KnowledgeEntry } from '../types';
|
|
19
19
|
|
|
20
20
|
function makeTmpDir(): string {
|
|
@@ -558,6 +558,62 @@ describe('auto-fix', () => {
|
|
|
558
558
|
});
|
|
559
559
|
});
|
|
560
560
|
|
|
561
|
+
// ── D2b: deprecated-domain ──────────────────────────────
|
|
562
|
+
|
|
563
|
+
describe('D2b: deprecated-domain', () => {
|
|
564
|
+
it('flags entry with "pipeline" tag', () => {
|
|
565
|
+
const dir = makeTmpDir();
|
|
566
|
+
const audit = new KnowledgeAudit({ baseDir: dir });
|
|
567
|
+
const entry = makeEntry({ tags: ['pipeline', 'deployment'] });
|
|
568
|
+
const issues = audit.validate(entry);
|
|
569
|
+
const dd = issues.filter(i => i.rule === 'deprecated-domain');
|
|
570
|
+
expect(dd.length).toBe(1);
|
|
571
|
+
expect(dd[0].action).toBe('archive');
|
|
572
|
+
expect(dd[0].severity).toBe('high');
|
|
573
|
+
fs.rmSync(dir, { recursive: true });
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
it('flags entry with "pipeline" in title', () => {
|
|
577
|
+
const dir = makeTmpDir();
|
|
578
|
+
const audit = new KnowledgeAudit({ baseDir: dir });
|
|
579
|
+
const entry = makeEntry({ title: 'Pipeline 工作单元不可见导致协作效率低' });
|
|
580
|
+
const issues = audit.validate(entry);
|
|
581
|
+
const dd = issues.filter(i => i.rule === 'deprecated-domain');
|
|
582
|
+
expect(dd.length).toBe(1);
|
|
583
|
+
fs.rmSync(dir, { recursive: true });
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
it('flags entry with "管线" in title', () => {
|
|
587
|
+
const dir = makeTmpDir();
|
|
588
|
+
const audit = new KnowledgeAudit({ baseDir: dir });
|
|
589
|
+
const entry = makeEntry({ title: 'B51 管线端到端验证常量与测试' });
|
|
590
|
+
const issues = audit.validate(entry);
|
|
591
|
+
const dd = issues.filter(i => i.rule === 'deprecated-domain');
|
|
592
|
+
expect(dd.length).toBe(1);
|
|
593
|
+
fs.rmSync(dir, { recursive: true });
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
it('does not flag entry without deprecated domain references', () => {
|
|
597
|
+
const dir = makeTmpDir();
|
|
598
|
+
const audit = new KnowledgeAudit({ baseDir: dir });
|
|
599
|
+
const entry = makeEntry({ title: 'Agent Network 核心概念', tags: ['agent', 'architecture'] });
|
|
600
|
+
const issues = audit.validate(entry);
|
|
601
|
+
const dd = issues.filter(i => i.rule === 'deprecated-domain');
|
|
602
|
+
expect(dd.length).toBe(0);
|
|
603
|
+
fs.rmSync(dir, { recursive: true });
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
it('skips already archived entries', () => {
|
|
607
|
+
const dir = makeTmpDir();
|
|
608
|
+
const audit = new KnowledgeAudit({ baseDir: dir });
|
|
609
|
+
const entry = makeEntry({ title: 'Pipeline legacy entry', maturity: 'archived', tags: ['pipeline'] });
|
|
610
|
+
const issues = audit.validate(entry);
|
|
611
|
+
const dd = issues.filter(i => i.rule === 'deprecated-domain');
|
|
612
|
+
expect(dd.length).toBe(0);
|
|
613
|
+
fs.rmSync(dir, { recursive: true });
|
|
614
|
+
});
|
|
615
|
+
});
|
|
616
|
+
|
|
561
617
|
// ── run() report structure ─────────────────────────────
|
|
562
618
|
|
|
563
619
|
describe('run() report', () => {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import * as fs from 'fs';
|
|
6
6
|
import * as path from 'path';
|
|
7
|
-
import { KnowledgeStore } from '../store';
|
|
7
|
+
import { FileKnowledgeStore as KnowledgeStore } from '../store';
|
|
8
8
|
import { KnowledgeLinter, type LintReport } from '../lint';
|
|
9
9
|
import { ReferenceTracker } from '../reference-tracker';
|
|
10
10
|
import { KnowledgeHealthScorer } from '../doctor';
|