@dommaker/harness 0.6.1 → 0.7.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 +122 -6
- package/bin/harness.js +30 -2
- package/dist/cli/commands/index.d.ts +1 -0
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +4 -1
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/spec.d.ts +31 -0
- package/dist/cli/commands/spec.d.ts.map +1 -0
- package/dist/cli/commands/spec.js +169 -0
- package/dist/cli/commands/spec.js.map +1 -0
- package/dist/core/constraints/definitions.d.ts.map +1 -1
- package/dist/core/constraints/definitions.js +5 -2
- package/dist/core/constraints/definitions.js.map +1 -1
- package/dist/core/constraints/index.d.ts +2 -0
- package/dist/core/constraints/index.d.ts.map +1 -1
- package/dist/core/constraints/index.js +7 -1
- package/dist/core/constraints/index.js.map +1 -1
- package/dist/core/constraints/interceptor.d.ts +37 -0
- package/dist/core/constraints/interceptor.d.ts.map +1 -0
- package/dist/core/constraints/interceptor.js +176 -0
- package/dist/core/constraints/interceptor.js.map +1 -0
- package/dist/core/spec/index.d.ts +7 -0
- package/dist/core/spec/index.d.ts.map +1 -0
- package/dist/core/spec/index.js +23 -0
- package/dist/core/spec/index.js.map +1 -0
- package/dist/core/spec/validator.d.ts +78 -0
- package/dist/core/spec/validator.d.ts.map +1 -0
- package/dist/core/spec/validator.js +372 -0
- package/dist/core/spec/validator.js.map +1 -0
- package/dist/core/validators/passes-gate.d.ts +51 -1
- package/dist/core/validators/passes-gate.d.ts.map +1 -1
- package/dist/core/validators/passes-gate.js +90 -0
- package/dist/core/validators/passes-gate.js.map +1 -1
- package/dist/extensions/long-running/constraints.d.ts +38 -0
- package/dist/extensions/long-running/constraints.d.ts.map +1 -0
- package/dist/extensions/long-running/constraints.js +153 -0
- package/dist/extensions/long-running/constraints.js.map +1 -0
- package/dist/extensions/long-running/index.d.ts +30 -0
- package/dist/extensions/long-running/index.d.ts.map +1 -0
- package/dist/extensions/long-running/index.js +50 -0
- package/dist/extensions/long-running/index.js.map +1 -0
- package/dist/extensions/long-running/types.d.ts +154 -0
- package/dist/extensions/long-running/types.d.ts.map +1 -0
- package/dist/extensions/long-running/types.js +9 -0
- package/dist/extensions/long-running/types.js.map +1 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -1
- package/dist/presets/index.d.ts +1 -0
- package/dist/presets/index.d.ts.map +1 -1
- package/dist/presets/index.js +6 -1
- package/dist/presets/index.js.map +1 -1
- package/dist/presets/long-running.d.ts +33 -0
- package/dist/presets/long-running.d.ts.map +1 -0
- package/dist/presets/long-running.js +52 -0
- package/dist/presets/long-running.js.map +1 -0
- package/dist/types/constraint.d.ts +1 -1
- package/dist/types/constraint.d.ts.map +1 -1
- package/dist/types/constraint.js.map +1 -1
- package/dist/types/enforcement.d.ts +42 -0
- package/dist/types/enforcement.d.ts.map +1 -0
- package/dist/types/enforcement.js +6 -0
- package/dist/types/enforcement.js.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +4 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/passes-gate.d.ts +20 -0
- package/dist/types/passes-gate.d.ts.map +1 -1
- package/dist/types/project-config.d.ts +11 -0
- package/dist/types/project-config.d.ts.map +1 -1
- package/dist/types/spec.d.ts +85 -0
- package/dist/types/spec.d.ts.map +1 -0
- package/dist/types/spec.js +8 -0
- package/dist/types/spec.js.map +1 -0
- package/package.json +8 -7
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ConstraintInterceptor - 约束拦截器
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.constraintInterceptor = exports.ConstraintInterceptor = void 0;
|
|
7
|
+
exports.interceptOperation = interceptOperation;
|
|
8
|
+
exports.claimOperation = claimOperation;
|
|
9
|
+
const constraint_1 = require("../../types/constraint");
|
|
10
|
+
const checker_1 = require("./checker");
|
|
11
|
+
class ConstraintInterceptor {
|
|
12
|
+
static instance;
|
|
13
|
+
executors = new Map();
|
|
14
|
+
enabled = true;
|
|
15
|
+
skipEnforcements = new Set();
|
|
16
|
+
constructor() { }
|
|
17
|
+
static getInstance() {
|
|
18
|
+
if (!ConstraintInterceptor.instance) {
|
|
19
|
+
ConstraintInterceptor.instance = new ConstraintInterceptor();
|
|
20
|
+
}
|
|
21
|
+
return ConstraintInterceptor.instance;
|
|
22
|
+
}
|
|
23
|
+
setEnabled(enabled) {
|
|
24
|
+
this.enabled = enabled;
|
|
25
|
+
}
|
|
26
|
+
register(enforcementId, executor, source) {
|
|
27
|
+
this.executors.set(enforcementId, executor);
|
|
28
|
+
}
|
|
29
|
+
registerBatch(registrations) {
|
|
30
|
+
for (const { enforcementId, executor } of registrations) {
|
|
31
|
+
this.register(enforcementId, executor);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
unregister(enforcementId) {
|
|
35
|
+
return this.executors.delete(enforcementId);
|
|
36
|
+
}
|
|
37
|
+
hasExecutor(enforcementId) {
|
|
38
|
+
return this.executors.has(enforcementId);
|
|
39
|
+
}
|
|
40
|
+
getExecutor(enforcementId) {
|
|
41
|
+
return this.executors.get(enforcementId);
|
|
42
|
+
}
|
|
43
|
+
getRegistrations() {
|
|
44
|
+
return Array.from(this.executors.entries()).map(([id, executor]) => ({
|
|
45
|
+
id,
|
|
46
|
+
executor,
|
|
47
|
+
registeredAt: new Date(),
|
|
48
|
+
source: undefined,
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
skip(enforcementIds) {
|
|
52
|
+
for (const id of enforcementIds) {
|
|
53
|
+
this.skipEnforcements.add(id);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
clearSkips() {
|
|
57
|
+
this.skipEnforcements.clear();
|
|
58
|
+
}
|
|
59
|
+
async intercept(trigger, context) {
|
|
60
|
+
if (!this.enabled) {
|
|
61
|
+
return {
|
|
62
|
+
passed: true,
|
|
63
|
+
constraints: [],
|
|
64
|
+
violations: [],
|
|
65
|
+
message: '拦截器已禁用',
|
|
66
|
+
interceptedAt: new Date(),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const result = {
|
|
70
|
+
passed: true,
|
|
71
|
+
constraints: [],
|
|
72
|
+
violations: [],
|
|
73
|
+
interceptedAt: new Date(),
|
|
74
|
+
};
|
|
75
|
+
const constraints = checker_1.constraintChecker.getConstraints();
|
|
76
|
+
const allConstraints = [
|
|
77
|
+
...Object.values(constraints.ironLaws),
|
|
78
|
+
...Object.values(constraints.guidelines),
|
|
79
|
+
...Object.values(constraints.tips),
|
|
80
|
+
];
|
|
81
|
+
const applicableConstraints = allConstraints.filter(constraint => {
|
|
82
|
+
const triggers = Array.isArray(constraint.trigger)
|
|
83
|
+
? constraint.trigger
|
|
84
|
+
: [constraint.trigger];
|
|
85
|
+
return triggers.includes(trigger);
|
|
86
|
+
});
|
|
87
|
+
const order = { iron_law: 0, guideline: 1, tip: 2 };
|
|
88
|
+
const ordered = applicableConstraints.sort((a, b) => order[a.level] - order[b.level]);
|
|
89
|
+
for (const constraint of ordered) {
|
|
90
|
+
const enforcementId = constraint.enforcement;
|
|
91
|
+
if (this.skipEnforcements.has(enforcementId)) {
|
|
92
|
+
result.constraints.push({ constraint, skipped: true, skipReason: 'configured to skip' });
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const executor = this.executors.get(enforcementId);
|
|
96
|
+
if (!executor) {
|
|
97
|
+
result.constraints.push({ constraint, skipped: true, skipReason: `no executor for '${enforcementId}'` });
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const enforcementContext = { ...context, enforcementId, constraint };
|
|
101
|
+
const start = Date.now();
|
|
102
|
+
try {
|
|
103
|
+
const enforcementResult = await executor.execute(enforcementContext);
|
|
104
|
+
enforcementResult.duration = Date.now() - start;
|
|
105
|
+
result.constraints.push({ constraint, enforcementResult });
|
|
106
|
+
if (!enforcementResult.passed) {
|
|
107
|
+
if (constraint.level === 'iron_law') {
|
|
108
|
+
result.passed = false;
|
|
109
|
+
result.violations.push(constraint);
|
|
110
|
+
throw new constraint_1.ConstraintViolationError({
|
|
111
|
+
id: constraint.id,
|
|
112
|
+
level: constraint.level,
|
|
113
|
+
satisfied: false,
|
|
114
|
+
constraint,
|
|
115
|
+
message: constraint.message,
|
|
116
|
+
requiredAction: constraint.enforcement,
|
|
117
|
+
checkedAt: new Date(),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
if (constraint.level === 'guideline') {
|
|
121
|
+
result.violations.push(constraint);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
if (error instanceof constraint_1.ConstraintViolationError)
|
|
127
|
+
throw error;
|
|
128
|
+
result.constraints.push({ constraint, enforcementResult: { passed: false, error: error.message, duration: Date.now() - start } });
|
|
129
|
+
if (constraint.level === 'iron_law') {
|
|
130
|
+
result.passed = false;
|
|
131
|
+
result.violations.push(constraint);
|
|
132
|
+
throw new constraint_1.ConstraintViolationError({
|
|
133
|
+
id: constraint.id,
|
|
134
|
+
level: constraint.level,
|
|
135
|
+
satisfied: false,
|
|
136
|
+
constraint,
|
|
137
|
+
message: `Enforcement failed: ${error.message}`,
|
|
138
|
+
requiredAction: constraint.enforcement,
|
|
139
|
+
checkedAt: new Date(),
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
result.message = result.violations.length === 0 ? '✅ 拦截通过' : `❌ ${result.violations.length} 个约束违规`;
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
async claim(trigger, context) {
|
|
148
|
+
const result = await this.intercept(trigger, context);
|
|
149
|
+
if (!result.passed) {
|
|
150
|
+
throw new constraint_1.ConstraintViolationError({
|
|
151
|
+
id: result.violations[0]?.id || 'unknown',
|
|
152
|
+
level: 'iron_law',
|
|
153
|
+
satisfied: false,
|
|
154
|
+
message: result.message,
|
|
155
|
+
checkedAt: new Date(),
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async canProceed(trigger, context) {
|
|
160
|
+
try {
|
|
161
|
+
return (await this.intercept(trigger, context)).passed;
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
exports.ConstraintInterceptor = ConstraintInterceptor;
|
|
169
|
+
exports.constraintInterceptor = ConstraintInterceptor.getInstance();
|
|
170
|
+
async function interceptOperation(trigger, context) {
|
|
171
|
+
return exports.constraintInterceptor.intercept(trigger, context);
|
|
172
|
+
}
|
|
173
|
+
async function claimOperation(trigger, context) {
|
|
174
|
+
return exports.constraintInterceptor.claim(trigger, context);
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=interceptor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interceptor.js","sourceRoot":"","sources":["../../../src/core/constraints/interceptor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/spec/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,aAAa,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Spec 验证模块
|
|
4
|
+
*
|
|
5
|
+
* 框架提供验证机制,项目定义自己的 Spec Schema
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
19
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
20
|
+
};
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
__exportStar(require("./validator"), exports);
|
|
23
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/spec/index.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;;;;;;;;;;;;;;AAEH,8CAA4B"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spec 验证器
|
|
3
|
+
*
|
|
4
|
+
* 框架提供验证机制,项目定义自己的 Spec Schema
|
|
5
|
+
*
|
|
6
|
+
* 设计原则:
|
|
7
|
+
* - 框架不包含具体 Schema 定义
|
|
8
|
+
* - 动态加载项目的 Schema
|
|
9
|
+
* - 支持 Zod / JSON Schema / 自定义验证器
|
|
10
|
+
*/
|
|
11
|
+
import type { SpecValidatorConfig, SpecValidationResult, BatchSpecValidationResult, SpecSchemaDefinition, SpecType } from '../../types/spec';
|
|
12
|
+
/**
|
|
13
|
+
* Spec 验证器
|
|
14
|
+
*/
|
|
15
|
+
export declare class SpecValidator {
|
|
16
|
+
private static instance;
|
|
17
|
+
private config;
|
|
18
|
+
private schemaCache;
|
|
19
|
+
private constructor();
|
|
20
|
+
/**
|
|
21
|
+
* 获取单例实例
|
|
22
|
+
*/
|
|
23
|
+
static getInstance(config?: Partial<SpecValidatorConfig>): SpecValidator;
|
|
24
|
+
/**
|
|
25
|
+
* 更新配置
|
|
26
|
+
*/
|
|
27
|
+
setConfig(config: Partial<SpecValidatorConfig>): void;
|
|
28
|
+
/**
|
|
29
|
+
* 加载项目的 Schema
|
|
30
|
+
*
|
|
31
|
+
* 支持两种方式:
|
|
32
|
+
* 1. 指定 schemaPath 目录,自动加载 index.ts/js
|
|
33
|
+
* 2. 使用 --schema 参数指定路径
|
|
34
|
+
*/
|
|
35
|
+
loadSchema(schemaPath: string): Promise<SpecSchemaDefinition | null>;
|
|
36
|
+
/**
|
|
37
|
+
* 检测 Spec 类型
|
|
38
|
+
*/
|
|
39
|
+
detectSpecType(filePath: string): SpecType;
|
|
40
|
+
/**
|
|
41
|
+
* 验证单个文件
|
|
42
|
+
*/
|
|
43
|
+
validateFile(filePath: string, schema?: SpecSchemaDefinition): Promise<SpecValidationResult>;
|
|
44
|
+
/**
|
|
45
|
+
* 基础验证(无 Schema 时)
|
|
46
|
+
*/
|
|
47
|
+
private basicValidation;
|
|
48
|
+
/**
|
|
49
|
+
* 批量验证
|
|
50
|
+
*/
|
|
51
|
+
validateAll(projectPath?: string, staged?: boolean): Promise<BatchSpecValidationResult>;
|
|
52
|
+
/**
|
|
53
|
+
* 获取 Spec 文件
|
|
54
|
+
*/
|
|
55
|
+
private getSpecFiles;
|
|
56
|
+
/**
|
|
57
|
+
* 获取暂存的 Spec 文件
|
|
58
|
+
*/
|
|
59
|
+
private getStagedFiles;
|
|
60
|
+
/**
|
|
61
|
+
* 判断是否为 Spec 文件
|
|
62
|
+
*/
|
|
63
|
+
private isSpecFile;
|
|
64
|
+
/**
|
|
65
|
+
* 检查文件是否存在
|
|
66
|
+
*/
|
|
67
|
+
private fileExists;
|
|
68
|
+
/**
|
|
69
|
+
* 动态导入模块
|
|
70
|
+
*/
|
|
71
|
+
private dynamicImport;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* 便捷函数
|
|
75
|
+
*/
|
|
76
|
+
export declare function validateSpec(filePath: string, schemaPath?: string): Promise<SpecValidationResult>;
|
|
77
|
+
export declare function validateAllSpecs(projectPath?: string, staged?: boolean): Promise<BatchSpecValidationResult>;
|
|
78
|
+
//# sourceMappingURL=validator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../../src/core/spec/validator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,KAAK,EACV,mBAAmB,EACnB,oBAAoB,EACpB,yBAAyB,EACzB,oBAAoB,EACpB,QAAQ,EACT,MAAM,kBAAkB,CAAC;AAY1B;;GAEG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAgB;IACvC,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,WAAW,CAAgD;IAEnE,OAAO;IAIP;;OAEG;IACH,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,GAAG,aAAa;IAOxE;;OAEG;IACH,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,mBAAmB,CAAC,GAAG,IAAI;IAKrD;;;;;;OAMG;IACG,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IAwC1E;;OAEG;IACH,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ;IAkB1C;;OAEG;IACG,YAAY,CAChB,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,oBAAoB,GAC5B,OAAO,CAAC,oBAAoB,CAAC;IA+ChC;;OAEG;YACW,eAAe;IAwC7B;;OAEG;IACG,WAAW,CACf,WAAW,CAAC,EAAE,MAAM,EACpB,MAAM,CAAC,EAAE,OAAO,GACf,OAAO,CAAC,yBAAyB,CAAC;IAwCrC;;OAEG;YACW,YAAY;IAe1B;;OAEG;YACW,cAAc;IAc5B;;OAEG;IACH,OAAO,CAAC,UAAU;IAoBlB;;OAEG;YACW,UAAU;IASxB;;OAEG;YACW,aAAa;CAiB5B;AAED;;GAEG;AACH,wBAAsB,YAAY,CAChC,QAAQ,EAAE,MAAM,EAChB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,oBAAoB,CAAC,CAO/B;AAED,wBAAsB,gBAAgB,CACpC,WAAW,CAAC,EAAE,MAAM,EACpB,MAAM,CAAC,EAAE,OAAO,GACf,OAAO,CAAC,yBAAyB,CAAC,CAGpC"}
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Spec 验证器
|
|
4
|
+
*
|
|
5
|
+
* 框架提供验证机制,项目定义自己的 Spec Schema
|
|
6
|
+
*
|
|
7
|
+
* 设计原则:
|
|
8
|
+
* - 框架不包含具体 Schema 定义
|
|
9
|
+
* - 动态加载项目的 Schema
|
|
10
|
+
* - 支持 Zod / JSON Schema / 自定义验证器
|
|
11
|
+
*/
|
|
12
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
15
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
16
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
17
|
+
}
|
|
18
|
+
Object.defineProperty(o, k2, desc);
|
|
19
|
+
}) : (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
o[k2] = m[k];
|
|
22
|
+
}));
|
|
23
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
24
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
25
|
+
}) : function(o, v) {
|
|
26
|
+
o["default"] = v;
|
|
27
|
+
});
|
|
28
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
29
|
+
var ownKeys = function(o) {
|
|
30
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
31
|
+
var ar = [];
|
|
32
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
33
|
+
return ar;
|
|
34
|
+
};
|
|
35
|
+
return ownKeys(o);
|
|
36
|
+
};
|
|
37
|
+
return function (mod) {
|
|
38
|
+
if (mod && mod.__esModule) return mod;
|
|
39
|
+
var result = {};
|
|
40
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
41
|
+
__setModuleDefault(result, mod);
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
})();
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.SpecValidator = void 0;
|
|
47
|
+
exports.validateSpec = validateSpec;
|
|
48
|
+
exports.validateAllSpecs = validateAllSpecs;
|
|
49
|
+
const fs = __importStar(require("fs/promises"));
|
|
50
|
+
const path = __importStar(require("path"));
|
|
51
|
+
const glob = __importStar(require("fast-glob"));
|
|
52
|
+
/**
|
|
53
|
+
* 默认配置
|
|
54
|
+
*/
|
|
55
|
+
const DEFAULT_CONFIG = {
|
|
56
|
+
enabled: true,
|
|
57
|
+
schemaPath: './specs/schemas',
|
|
58
|
+
files: ['ARCHITECTURE.md', 'specs/**/*.yml', 'specs/**/*.yaml'],
|
|
59
|
+
failureLevel: 'error',
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Spec 验证器
|
|
63
|
+
*/
|
|
64
|
+
class SpecValidator {
|
|
65
|
+
static instance;
|
|
66
|
+
config;
|
|
67
|
+
schemaCache = new Map();
|
|
68
|
+
constructor(config) {
|
|
69
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* 获取单例实例
|
|
73
|
+
*/
|
|
74
|
+
static getInstance(config) {
|
|
75
|
+
if (!SpecValidator.instance) {
|
|
76
|
+
SpecValidator.instance = new SpecValidator(config);
|
|
77
|
+
}
|
|
78
|
+
return SpecValidator.instance;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* 更新配置
|
|
82
|
+
*/
|
|
83
|
+
setConfig(config) {
|
|
84
|
+
this.config = { ...this.config, ...config };
|
|
85
|
+
this.schemaCache.clear();
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* 加载项目的 Schema
|
|
89
|
+
*
|
|
90
|
+
* 支持两种方式:
|
|
91
|
+
* 1. 指定 schemaPath 目录,自动加载 index.ts/js
|
|
92
|
+
* 2. 使用 --schema 参数指定路径
|
|
93
|
+
*/
|
|
94
|
+
async loadSchema(schemaPath) {
|
|
95
|
+
const cached = this.schemaCache.get(schemaPath);
|
|
96
|
+
if (cached)
|
|
97
|
+
return cached;
|
|
98
|
+
const absolutePath = path.resolve(schemaPath);
|
|
99
|
+
try {
|
|
100
|
+
// 尝试加载 TypeScript/JavaScript 模块
|
|
101
|
+
const indexPath = path.join(absolutePath, 'index.ts');
|
|
102
|
+
const indexPathJs = path.join(absolutePath, 'index.js');
|
|
103
|
+
let schemaModule;
|
|
104
|
+
if (await this.fileExists(indexPath)) {
|
|
105
|
+
// 动态导入 TypeScript 模块(需要 tsx 或编译后的 .js)
|
|
106
|
+
schemaModule = await this.dynamicImport(indexPath);
|
|
107
|
+
}
|
|
108
|
+
else if (await this.fileExists(indexPathJs)) {
|
|
109
|
+
schemaModule = await this.dynamicImport(indexPathJs);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
// 尝试直接加载指定文件
|
|
113
|
+
schemaModule = await this.dynamicImport(absolutePath);
|
|
114
|
+
}
|
|
115
|
+
if (schemaModule && schemaModule.validate) {
|
|
116
|
+
const schema = {
|
|
117
|
+
name: schemaModule.name || 'custom',
|
|
118
|
+
version: schemaModule.version,
|
|
119
|
+
validate: schemaModule.validate,
|
|
120
|
+
};
|
|
121
|
+
this.schemaCache.set(schemaPath, schema);
|
|
122
|
+
return schema;
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
// Schema 加载失败是正常的(项目可能没有自定义 Schema)
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* 检测 Spec 类型
|
|
133
|
+
*/
|
|
134
|
+
detectSpecType(filePath) {
|
|
135
|
+
const basename = path.basename(filePath).toLowerCase();
|
|
136
|
+
if (basename === 'architecture.md') {
|
|
137
|
+
return 'architecture';
|
|
138
|
+
}
|
|
139
|
+
if (filePath.includes('module') || filePath.includes('modules')) {
|
|
140
|
+
return 'module';
|
|
141
|
+
}
|
|
142
|
+
if (filePath.includes('api') || filePath.includes('apis')) {
|
|
143
|
+
return 'api';
|
|
144
|
+
}
|
|
145
|
+
return 'custom';
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* 验证单个文件
|
|
149
|
+
*/
|
|
150
|
+
async validateFile(filePath, schema) {
|
|
151
|
+
const specType = this.detectSpecType(filePath);
|
|
152
|
+
const absolutePath = path.resolve(filePath);
|
|
153
|
+
// 检查文件是否存在
|
|
154
|
+
if (!(await this.fileExists(absolutePath))) {
|
|
155
|
+
return {
|
|
156
|
+
valid: false,
|
|
157
|
+
file: filePath,
|
|
158
|
+
type: specType,
|
|
159
|
+
errors: [{ path: '', message: `文件不存在: ${filePath}`, severity: 'error' }],
|
|
160
|
+
warnings: [],
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
// 如果没有传入 Schema,尝试加载项目的 Schema
|
|
164
|
+
let schemaToUse = schema;
|
|
165
|
+
if (!schemaToUse) {
|
|
166
|
+
const loadedSchema = await this.loadSchema(this.config.schemaPath);
|
|
167
|
+
schemaToUse = loadedSchema ?? undefined;
|
|
168
|
+
}
|
|
169
|
+
// 如果没有 Schema,使用基础验证
|
|
170
|
+
if (!schemaToUse) {
|
|
171
|
+
return this.basicValidation(filePath, specType);
|
|
172
|
+
}
|
|
173
|
+
// 使用项目的 Schema 验证
|
|
174
|
+
try {
|
|
175
|
+
const content = await fs.readFile(absolutePath, 'utf-8');
|
|
176
|
+
const result = await schemaToUse.validate(content, filePath);
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
return {
|
|
181
|
+
valid: false,
|
|
182
|
+
file: filePath,
|
|
183
|
+
type: specType,
|
|
184
|
+
errors: [{
|
|
185
|
+
path: '',
|
|
186
|
+
message: `验证失败: ${error instanceof Error ? error.message : String(error)}`,
|
|
187
|
+
severity: 'error',
|
|
188
|
+
}],
|
|
189
|
+
warnings: [],
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* 基础验证(无 Schema 时)
|
|
195
|
+
*/
|
|
196
|
+
async basicValidation(filePath, specType) {
|
|
197
|
+
const absolutePath = path.resolve(filePath);
|
|
198
|
+
const content = await fs.readFile(absolutePath, 'utf-8');
|
|
199
|
+
const errors = [];
|
|
200
|
+
const warnings = [];
|
|
201
|
+
// ARCHITECTURE.md 基础检查
|
|
202
|
+
if (specType === 'architecture') {
|
|
203
|
+
if (!content.includes('# ') && !content.includes('## ')) {
|
|
204
|
+
errors.push({
|
|
205
|
+
path: '',
|
|
206
|
+
message: 'ARCHITECTURE.md 应包含标题和章节',
|
|
207
|
+
severity: 'warning',
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// YAML 文件基础检查
|
|
212
|
+
if (filePath.endsWith('.yml') || filePath.endsWith('.yaml')) {
|
|
213
|
+
try {
|
|
214
|
+
const yaml = await Promise.resolve().then(() => __importStar(require('js-yaml')));
|
|
215
|
+
yaml.load(content);
|
|
216
|
+
}
|
|
217
|
+
catch (e) {
|
|
218
|
+
errors.push({
|
|
219
|
+
path: '',
|
|
220
|
+
message: `YAML 解析失败: ${e instanceof Error ? e.message : String(e)}`,
|
|
221
|
+
severity: 'error',
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
valid: errors.length === 0,
|
|
227
|
+
file: filePath,
|
|
228
|
+
type: specType,
|
|
229
|
+
errors,
|
|
230
|
+
warnings,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* 批量验证
|
|
235
|
+
*/
|
|
236
|
+
async validateAll(projectPath, staged) {
|
|
237
|
+
const cwd = projectPath || process.cwd();
|
|
238
|
+
const results = [];
|
|
239
|
+
// 加载项目的 Schema
|
|
240
|
+
const loadedSchema = await this.loadSchema(this.config.schemaPath);
|
|
241
|
+
const schema = loadedSchema ?? undefined;
|
|
242
|
+
// 获取要验证的文件
|
|
243
|
+
let files;
|
|
244
|
+
if (staged) {
|
|
245
|
+
files = await this.getStagedFiles();
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
files = await this.getSpecFiles(cwd);
|
|
249
|
+
}
|
|
250
|
+
// 过滤只保留 Spec 相关文件
|
|
251
|
+
const specFiles = files.filter(f => this.isSpecFile(f));
|
|
252
|
+
// 验证每个文件
|
|
253
|
+
for (const file of specFiles) {
|
|
254
|
+
const result = await this.validateFile(file, schema);
|
|
255
|
+
results.push(result);
|
|
256
|
+
}
|
|
257
|
+
// 统计结果
|
|
258
|
+
const passed = results.filter(r => r.valid).length;
|
|
259
|
+
const failed = results.filter(r => !r.valid).length;
|
|
260
|
+
const warnings = results.reduce((sum, r) => sum + r.warnings.length, 0);
|
|
261
|
+
return {
|
|
262
|
+
total: specFiles.length,
|
|
263
|
+
passed,
|
|
264
|
+
failed,
|
|
265
|
+
warnings,
|
|
266
|
+
results,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* 获取 Spec 文件
|
|
271
|
+
*/
|
|
272
|
+
async getSpecFiles(cwd) {
|
|
273
|
+
const allFiles = [];
|
|
274
|
+
for (const pattern of this.config.files) {
|
|
275
|
+
const files = await glob.glob(pattern, {
|
|
276
|
+
cwd,
|
|
277
|
+
absolute: true,
|
|
278
|
+
ignore: ['node_modules/**', 'dist/**', '.git/**'],
|
|
279
|
+
});
|
|
280
|
+
allFiles.push(...files);
|
|
281
|
+
}
|
|
282
|
+
return [...new Set(allFiles)];
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* 获取暂存的 Spec 文件
|
|
286
|
+
*/
|
|
287
|
+
async getStagedFiles() {
|
|
288
|
+
const { exec } = await Promise.resolve().then(() => __importStar(require('child_process')));
|
|
289
|
+
const { promisify } = await Promise.resolve().then(() => __importStar(require('util')));
|
|
290
|
+
const execAsync = promisify(exec);
|
|
291
|
+
try {
|
|
292
|
+
const { stdout } = await execAsync('git diff --cached --name-only');
|
|
293
|
+
const allFiles = stdout.trim().split('\n').filter(Boolean);
|
|
294
|
+
return allFiles.filter(f => this.isSpecFile(f));
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
return [];
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* 判断是否为 Spec 文件
|
|
302
|
+
*/
|
|
303
|
+
isSpecFile(filePath) {
|
|
304
|
+
const basename = path.basename(filePath).toLowerCase();
|
|
305
|
+
// ARCHITECTURE.md
|
|
306
|
+
if (basename === 'architecture.md')
|
|
307
|
+
return true;
|
|
308
|
+
// specs 目录下的文件
|
|
309
|
+
if (filePath.includes('specs/') || filePath.includes('spec/'))
|
|
310
|
+
return true;
|
|
311
|
+
// 符合配置的文件模式
|
|
312
|
+
for (const pattern of this.config.files) {
|
|
313
|
+
if (pattern.startsWith('specs/')) {
|
|
314
|
+
// 检查路径是否匹配
|
|
315
|
+
if (filePath.includes('/specs/'))
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* 检查文件是否存在
|
|
323
|
+
*/
|
|
324
|
+
async fileExists(filePath) {
|
|
325
|
+
try {
|
|
326
|
+
await fs.access(filePath);
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
catch {
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* 动态导入模块
|
|
335
|
+
*/
|
|
336
|
+
async dynamicImport(modulePath) {
|
|
337
|
+
try {
|
|
338
|
+
// 尝试直接导入
|
|
339
|
+
return await Promise.resolve(`${modulePath}`).then(s => __importStar(require(s)));
|
|
340
|
+
}
|
|
341
|
+
catch {
|
|
342
|
+
// 如果是 TypeScript 文件,尝试加载编译后的 JS
|
|
343
|
+
if (modulePath.endsWith('.ts')) {
|
|
344
|
+
const jsPath = modulePath.replace(/\.ts$/, '.js');
|
|
345
|
+
try {
|
|
346
|
+
return await Promise.resolve(`${jsPath}`).then(s => __importStar(require(s)));
|
|
347
|
+
}
|
|
348
|
+
catch {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
exports.SpecValidator = SpecValidator;
|
|
357
|
+
/**
|
|
358
|
+
* 便捷函数
|
|
359
|
+
*/
|
|
360
|
+
async function validateSpec(filePath, schemaPath) {
|
|
361
|
+
const validator = SpecValidator.getInstance();
|
|
362
|
+
if (schemaPath) {
|
|
363
|
+
const loadedSchema = await validator.loadSchema(schemaPath);
|
|
364
|
+
return validator.validateFile(filePath, loadedSchema ?? undefined);
|
|
365
|
+
}
|
|
366
|
+
return validator.validateFile(filePath);
|
|
367
|
+
}
|
|
368
|
+
async function validateAllSpecs(projectPath, staged) {
|
|
369
|
+
const validator = SpecValidator.getInstance();
|
|
370
|
+
return validator.validateAll(projectPath, staged);
|
|
371
|
+
}
|
|
372
|
+
//# sourceMappingURL=validator.js.map
|