@dommaker/harness 0.7.2 → 0.7.4

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.
Files changed (121) hide show
  1. package/README.md +390 -5
  2. package/dist/architecture/constraint-engine.d.ts +49 -0
  3. package/dist/architecture/constraint-engine.d.ts.map +1 -0
  4. package/dist/architecture/constraint-engine.js +207 -0
  5. package/dist/architecture/constraint-engine.js.map +1 -0
  6. package/dist/architecture/cross-project-checker.d.ts +34 -0
  7. package/dist/architecture/cross-project-checker.d.ts.map +1 -0
  8. package/dist/architecture/cross-project-checker.js +260 -0
  9. package/dist/architecture/cross-project-checker.js.map +1 -0
  10. package/dist/context/progressive-loader.d.ts +110 -0
  11. package/dist/context/progressive-loader.d.ts.map +1 -0
  12. package/dist/context/progressive-loader.js +183 -0
  13. package/dist/context/progressive-loader.js.map +1 -0
  14. package/dist/context/token-budget.d.ts +144 -0
  15. package/dist/context/token-budget.d.ts.map +1 -0
  16. package/dist/context/token-budget.js +264 -0
  17. package/dist/context/token-budget.js.map +1 -0
  18. package/dist/core/constraints/definitions.d.ts.map +1 -1
  19. package/dist/core/constraints/definitions.js +33 -0
  20. package/dist/core/constraints/definitions.js.map +1 -1
  21. package/dist/core/index.d.ts +2 -0
  22. package/dist/core/index.d.ts.map +1 -1
  23. package/dist/core/index.js +4 -0
  24. package/dist/core/index.js.map +1 -1
  25. package/dist/failure/classifier.d.ts +63 -0
  26. package/dist/failure/classifier.d.ts.map +1 -0
  27. package/dist/failure/classifier.js +123 -0
  28. package/dist/failure/classifier.js.map +1 -0
  29. package/dist/failure/index.d.ts +10 -0
  30. package/dist/failure/index.d.ts.map +1 -0
  31. package/dist/failure/index.js +36 -0
  32. package/dist/failure/index.js.map +1 -0
  33. package/dist/failure/recorder.d.ts +78 -0
  34. package/dist/failure/recorder.d.ts.map +1 -0
  35. package/dist/failure/recorder.js +193 -0
  36. package/dist/failure/recorder.js.map +1 -0
  37. package/dist/failure/types.d.ts +89 -0
  38. package/dist/failure/types.d.ts.map +1 -0
  39. package/dist/failure/types.js +121 -0
  40. package/dist/failure/types.js.map +1 -0
  41. package/dist/gates/acceptance.d.ts +159 -0
  42. package/dist/gates/acceptance.d.ts.map +1 -0
  43. package/dist/gates/acceptance.js +368 -0
  44. package/dist/gates/acceptance.js.map +1 -0
  45. package/dist/gates/contract.d.ts +41 -0
  46. package/dist/gates/contract.d.ts.map +1 -0
  47. package/dist/gates/contract.js +264 -0
  48. package/dist/gates/contract.js.map +1 -0
  49. package/dist/gates/index.d.ts +23 -0
  50. package/dist/gates/index.d.ts.map +1 -0
  51. package/dist/gates/index.js +46 -0
  52. package/dist/gates/index.js.map +1 -0
  53. package/dist/gates/performance.d.ts +81 -0
  54. package/dist/gates/performance.d.ts.map +1 -0
  55. package/dist/gates/performance.js +336 -0
  56. package/dist/gates/performance.js.map +1 -0
  57. package/dist/gates/review.d.ts +37 -0
  58. package/dist/gates/review.d.ts.map +1 -0
  59. package/dist/gates/review.js +149 -0
  60. package/dist/gates/review.js.map +1 -0
  61. package/dist/gates/security.d.ts +45 -0
  62. package/dist/gates/security.d.ts.map +1 -0
  63. package/dist/gates/security.js +221 -0
  64. package/dist/gates/security.js.map +1 -0
  65. package/dist/gates/types.d.ts +115 -0
  66. package/dist/gates/types.d.ts.map +1 -0
  67. package/dist/gates/types.js +6 -0
  68. package/dist/gates/types.js.map +1 -0
  69. package/dist/index.d.ts +15 -1
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.js +30 -5
  72. package/dist/index.js.map +1 -1
  73. package/dist/monitoring/index.d.ts +6 -0
  74. package/dist/monitoring/index.d.ts.map +1 -1
  75. package/dist/monitoring/index.js +6 -0
  76. package/dist/monitoring/index.js.map +1 -1
  77. package/dist/monitoring/performance-analyzer.d.ts +104 -0
  78. package/dist/monitoring/performance-analyzer.d.ts.map +1 -0
  79. package/dist/monitoring/performance-analyzer.js +417 -0
  80. package/dist/monitoring/performance-analyzer.js.map +1 -0
  81. package/dist/monitoring/performance-collector.d.ts +111 -0
  82. package/dist/monitoring/performance-collector.d.ts.map +1 -0
  83. package/dist/monitoring/performance-collector.js +319 -0
  84. package/dist/monitoring/performance-collector.js.map +1 -0
  85. package/dist/monitoring/trace-analyzer.js +1 -1
  86. package/dist/monitoring/trace-analyzer.js.map +1 -1
  87. package/dist/monitoring/traces.js +1 -1
  88. package/dist/monitoring/traces.js.map +1 -1
  89. package/dist/presets/index.d.ts +0 -1
  90. package/dist/presets/index.d.ts.map +1 -1
  91. package/dist/presets/index.js +1 -6
  92. package/dist/presets/index.js.map +1 -1
  93. package/dist/spec/annotation-checker.d.ts +47 -0
  94. package/dist/spec/annotation-checker.d.ts.map +1 -0
  95. package/dist/spec/annotation-checker.js +299 -0
  96. package/dist/spec/annotation-checker.js.map +1 -0
  97. package/dist/types/index.d.ts +2 -0
  98. package/dist/types/index.d.ts.map +1 -1
  99. package/dist/types/index.js +9 -0
  100. package/dist/types/index.js.map +1 -1
  101. package/dist/types/performance.d.ts +204 -0
  102. package/dist/types/performance.d.ts.map +1 -0
  103. package/dist/types/performance.js +12 -0
  104. package/dist/types/performance.js.map +1 -0
  105. package/package.json +3 -1
  106. package/dist/extensions/long-running/constraints.d.ts +0 -38
  107. package/dist/extensions/long-running/constraints.d.ts.map +0 -1
  108. package/dist/extensions/long-running/constraints.js +0 -153
  109. package/dist/extensions/long-running/constraints.js.map +0 -1
  110. package/dist/extensions/long-running/index.d.ts +0 -30
  111. package/dist/extensions/long-running/index.d.ts.map +0 -1
  112. package/dist/extensions/long-running/index.js +0 -50
  113. package/dist/extensions/long-running/index.js.map +0 -1
  114. package/dist/extensions/long-running/types.d.ts +0 -154
  115. package/dist/extensions/long-running/types.d.ts.map +0 -1
  116. package/dist/extensions/long-running/types.js +0 -9
  117. package/dist/extensions/long-running/types.js.map +0 -1
  118. package/dist/presets/long-running.d.ts +0 -33
  119. package/dist/presets/long-running.d.ts.map +0 -1
  120. package/dist/presets/long-running.js +0 -52
  121. package/dist/presets/long-running.js.map +0 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @dommaker/harness
2
2
 
3
- > 通用工程约束框架 - 铁律系统、检查点验证、测试门控、拦截器
3
+ > 通用工程约束框架 - 铁律系统、门禁系统、检查点验证、拦截器
4
4
 
5
5
  ## 简介
6
6
 
@@ -11,12 +11,15 @@
11
11
  | 功能 | 说明 |
12
12
  |------|------|
13
13
  | **铁律系统** | 16 条内置约束(4 Iron Laws + 10 Guidelines + 2 Tips) |
14
+ | **门禁系统** | 7 种门禁(测试、审查、安全、性能、契约、检查点、验收标准) |
14
15
  | **检查点验证** | 验证工作流步骤的结果是否符合预期 |
15
- | **测试门控** | 禁止自评通过,必须通过真实测试 |
16
- | **拦截器** | 抽象拦截框架,自动执行 enforcement(v0.7+) |
16
+ | **拦截器** | 抽象拦截框架,自动执行 enforcement |
17
17
  | **Session 管理** | 启动检查点 + 结束状态管理 |
18
18
  | **预设系统** | 提供 strict/standard/relaxed 三种预设 |
19
19
  | **Execution Trace** | 轻量记录约束检查,异常检测,诊断系统 |
20
+ | **Performance Trace** | 性能日志收集,统计分析,异常检测(🆕) |
21
+ | **Failure Classification** | 错误分类,失败记录,等级映射(🆕) |
22
+ | **Spec 验证** | 验证架构文档、模块定义、API 定义 |
20
23
  | **项目级自定义约束** | 扩展/覆盖内置约束,无需 fork |
21
24
  | **CLI 工具** | 命令行工具执行检查 |
22
25
 
@@ -170,7 +173,112 @@ const cleaner = new CleanStateManager();
170
173
  const cleanResult = await cleaner.onSessionEnd(workDir, sessionInfo);
171
174
  ```
172
175
 
173
- ### 5. 使用拦截器(v0.7+)
176
+ ### 7. 门禁系统
177
+
178
+ harness 提供完整的门禁系统,支持多种门禁类型:
179
+
180
+ | 门禁 | 类 | 说明 |
181
+ |------|-----|------|
182
+ | 测试门控 | `PassesGate` | 禁止自评通过,必须通过真实测试 |
183
+ | 审查门禁 | `ReviewGate` | 检查 GitHub PR 审查状态 |
184
+ | 安全门禁 | `SecurityGate` | npm audit 安全漏洞扫描 |
185
+ | 性能门禁 | `PerformanceGate` | 响应时间、覆盖率、打包大小检查 |
186
+ | 契约门禁 | `ContractGate` | OpenAPI 契约验证 |
187
+ | 检查点验证 | `CheckpointValidator` | 验证工作流步骤结果 |
188
+ | 验收标准门禁 | `SpecAcceptanceGate` | 验证任务是否满足验收标准 |
189
+
190
+ **使用门禁**:
191
+
192
+ ```typescript
193
+ import {
194
+ PassesGate,
195
+ ReviewGate,
196
+ SecurityGate,
197
+ PerformanceGate,
198
+ ContractGate,
199
+ CheckpointValidator,
200
+ SpecAcceptanceGate,
201
+ } from '@dommaker/harness';
202
+
203
+ // 测试门控
204
+ const passesGate = new PassesGate({ requireEvidence: true });
205
+ const testResult = await passesGate.runTests();
206
+
207
+ // 审查门禁
208
+ const reviewGate = new ReviewGate({ minReviewers: 2 });
209
+ const reviewResult = await reviewGate.check({
210
+ projectId: 'my-project',
211
+ projectPath: '/path/to/project',
212
+ prNumber: 123,
213
+ });
214
+
215
+ // 安全门禁
216
+ const securityGate = new SecurityGate({ severityThreshold: 'high' });
217
+ const securityResult = await securityGate.scan({
218
+ projectId: 'my-project',
219
+ projectPath: '/path/to/project',
220
+ });
221
+
222
+ // 性能门禁(带超时)
223
+ const performanceGate = new PerformanceGate({
224
+ thresholds: {
225
+ maxResponseTime: 500,
226
+ minCoverage: 80,
227
+ maxBundleSize: 1024,
228
+ },
229
+ coverageTimeout: 120000, // 2分钟超时
230
+ });
231
+ const perfResult = await performanceGate.check({
232
+ projectId: 'my-project',
233
+ projectPath: '/path/to/project',
234
+ });
235
+
236
+ // 契约门禁
237
+ const contractGate = new ContractGate({ strict: true });
238
+ const contractResult = await contractGate.check({
239
+ projectId: 'my-project',
240
+ projectPath: '/path/to/project',
241
+ newContractPath: '/path/to/openapi.yaml',
242
+ });
243
+
244
+ // 检查点验证
245
+ const checkpointValidator = CheckpointValidator.getInstance();
246
+ const checkpointResult = await checkpointValidator.validate(checkpoints, {
247
+ workdir: '/path/to/project',
248
+ });
249
+ ```
250
+
251
+ **门禁结果**:
252
+
253
+ ```typescript
254
+ interface GateResult {
255
+ gate: string; // 门禁类型
256
+ passed: boolean; // 是否通过
257
+ message: string; // 结果消息
258
+ details?: { // 详细信息
259
+ metrics?: object; // 性能指标
260
+ failures?: string[]; // 失败项
261
+ warnings?: string[]; // 警告项
262
+ };
263
+ timestamp: string; // 时间戳
264
+ duration?: number; // 执行时长(毫秒)
265
+ }
266
+ ```
267
+
268
+ **PerformanceGate 超时配置**:
269
+
270
+ ```typescript
271
+ const gate = new PerformanceGate({
272
+ thresholds: { minCoverage: 80 },
273
+ coverageTimeout: 60000, // 覆盖率测试超时(毫秒)
274
+ benchmarkTimeout: 30000, // 基准测试超时(毫秒)
275
+ });
276
+
277
+ // 动态设置超时
278
+ gate.setTimeouts({ coverage: 120000 });
279
+ ```
280
+
281
+ ### 8. 使用拦截器
174
282
 
175
283
  拦截器自动执行 enforcement,无需手动调用检查 API:
176
284
 
@@ -371,6 +479,104 @@ class PassesGate {
371
479
  }
372
480
  ```
373
481
 
482
+ ### ReviewGate
483
+
484
+ ```typescript
485
+ class ReviewGate {
486
+ constructor(config: ReviewGateConfig);
487
+
488
+ check(context: GateContext): Promise<GateResult>;
489
+ setMinReviewers(count: number): void;
490
+ }
491
+ ```
492
+
493
+ ### SecurityGate
494
+
495
+ ```typescript
496
+ class SecurityGate {
497
+ constructor(config: SecurityGateConfig);
498
+
499
+ scan(context: GateContext): Promise<GateResult>;
500
+ }
501
+ ```
502
+
503
+ ### PerformanceGate
504
+
505
+ ```typescript
506
+ class PerformanceGate {
507
+ constructor(config: PerformanceGateConfig);
508
+
509
+ check(context: GateContext): Promise<GateResult>;
510
+ runBenchmark(context: GateContext): Promise<BenchmarkResult>;
511
+ setThresholds(thresholds: Partial<PerformanceThresholds>): void;
512
+ setTimeouts(options: { coverage?: number; benchmark?: number }): void;
513
+ }
514
+ ```
515
+
516
+ ### ContractGate
517
+
518
+ ```typescript
519
+ class ContractGate {
520
+ constructor(config: ContractGateConfig);
521
+
522
+ check(context: GateContext): Promise<GateResult>;
523
+ }
524
+ ```
525
+
526
+ ### SpecAcceptanceGate
527
+
528
+ 验收标准门禁,用于验证任务是否满足验收标准:
529
+
530
+ ```typescript
531
+ import { SpecAcceptanceGate } from '@dommaker/harness';
532
+
533
+ // 创建验收门禁
534
+ const acceptanceGate = new SpecAcceptanceGate({
535
+ tasksPath: './tasks.yml',
536
+ checkAllTasks: false,
537
+ });
538
+
539
+ // 检查单个任务
540
+ const result = await acceptanceGate.check({
541
+ projectPath: '/path/to/project',
542
+ taskId: 'task-001',
543
+ tasksPath: './tasks.yml',
544
+ });
545
+
546
+ // 检查所有任务
547
+ const allResults = await acceptanceGate.check({
548
+ projectPath: '/path/to/project',
549
+ checkAllTasks: true,
550
+ });
551
+
552
+ // 自定义验收条件
553
+ const customGate = new SpecAcceptanceGate({
554
+ customAcceptanceCriteria: {
555
+ 'has-tests': async (task) => {
556
+ // 自定义验证逻辑
557
+ return true;
558
+ },
559
+ },
560
+ });
561
+ ```
562
+
563
+ **验收标准格式(tasks.yml)**:
564
+
565
+ ```yaml
566
+ tasks:
567
+ - id: task-001
568
+ title: 实现用户登录
569
+ acceptance_criteria:
570
+ - id: ac-1
571
+ description: 用户可以使用邮箱登录
572
+ type: automated
573
+ required: true
574
+ - id: ac-2
575
+ description: 登录失败显示错误提示
576
+ type: manual
577
+ required: true
578
+ ```
579
+
374
580
  ### SessionStartup
375
581
 
376
582
  ```typescript
@@ -534,4 +740,183 @@ harness propose implement --diagnosis <id>
534
740
  | 异常检测 | 每日 | 0(未触发)~500(触发) |
535
741
  | Agent 诊断 | 按需 | ~2000 |
536
742
 
537
- **对比 Meta-Harness**:百万级 vs 5500/周,成本降低 **180 倍**。
743
+ **对比 Meta-Harness**:百万级 vs 5500/周,成本降低 **180 倍**。
744
+
745
+ ---
746
+
747
+ ## Performance Trace 系统(v0.4+)
748
+
749
+ ### 概念
750
+
751
+ Performance Trace 是性能数据的轻量记录,用于:
752
+ - 监控关键操作耗时
753
+ - 检测性能异常(慢操作、超阈值)
754
+ - 统计 Token 使用和上下文大小
755
+ - 为性能优化提供数据基础
756
+
757
+ ### 设计原则
758
+
759
+ | 原则 | 说明 |
760
+ |------|------|
761
+ | **零 Token 成本** | 记录和统计都不调用 LLM |
762
+ | **文件存储** | 追加写入,单行 JSON 格式 |
763
+ | **异步分析** | 统计每小时执行,异常检测每日执行 |
764
+ | **可配置阈值** | 按操作类型设置不同的性能阈值 |
765
+
766
+ ### Trace 数据结构
767
+
768
+ ```typescript
769
+ interface PerformanceTrace {
770
+ operation: string; // 操作名称
771
+ startTime: number; // 开始时间戳
772
+ duration: number; // 耗时(毫秒)
773
+ status: 'ok' | 'exceeded' | 'error';
774
+ threshold?: number; // 配置的阈值
775
+ metadata?: Record<string, unknown>;
776
+ }
777
+ ```
778
+
779
+ ### API 使用
780
+
781
+ ```typescript
782
+ import { PerformanceCollector, PerformanceAnalyzer } from '@dommaker/harness';
783
+
784
+ // 创建收集器
785
+ const collector = new PerformanceCollector({
786
+ logFile: '.harness/logs/performance.log',
787
+ thresholds: {
788
+ extract: 1000, // 提取操作阈值 1s
789
+ transform: 500, // 转换操作阈值 500ms
790
+ invoke: 10000, // 调用操作阈值 10s
791
+ },
792
+ });
793
+
794
+ // 记录操作
795
+ await collector.recordOk('extract', 150, { contextSize: 5000 });
796
+ await collector.recordExceeded('invoke', 12000, 10000, { agent: 'skill-agent' });
797
+
798
+ // 分析统计
799
+ const analyzer = new PerformanceAnalyzer();
800
+ const summaries = analyzer.summarize(traces);
801
+ const anomalies = analyzer.detectAnomalies(traces);
802
+
803
+ // 生成报告
804
+ const report = analyzer.generateReport(summaries, anomalies);
805
+ ```
806
+
807
+ ### 文件位置
808
+
809
+ ```
810
+ .harness/logs/
811
+ ├── performance.log # 性能日志
812
+ ├── performance-summary.json # 统计汇总
813
+ └── tokens.log # Token 使用日志
814
+ ```
815
+
816
+ ### 与 TraceCollector 对比
817
+
818
+ | 能力 | TraceCollector | PerformanceCollector |
819
+ |------|----------------|---------------------|
820
+ | **监控目标** | 约束检查结果 | 操作耗时 |
821
+ | **监控"什么"** | 对不对 | 快不快 |
822
+ | **异常类型** | 高绕过率、失败率上升 | 慢操作、超阈值 |
823
+ | **存储路径** | `.harness/logs/traces.log` | `.harness/logs/performance.log` |
824
+
825
+ ---
826
+
827
+ ## Failure Classification 系统(v0.4+)
828
+
829
+ ### 概念
830
+
831
+ Failure Classification 提供通用的错误分类和记录能力,用于:
832
+ - 统一错误分类标准
833
+ - 分级处理失败(L1-L4)
834
+ - 记录失败历史供分析
835
+
836
+ ### 设计原则
837
+
838
+ | 原则 | 说明 |
839
+ |------|------|
840
+ | **零业务逻辑** | 只提供类型 + 分类 + 记录能力 |
841
+ | **可扩展规则** | 支持自定义分类规则 |
842
+ | **文件存储** | 追加写入,单行 JSON 格式 |
843
+
844
+ ### 错误类型
845
+
846
+ ```typescript
847
+ enum ErrorType {
848
+ TEST_FAILED = 'TEST_FAILED', // 测试失败
849
+ GATE_FAILED = 'GATE_FAILED', // 门禁检查失败
850
+ DEPENDENCY_BLOCKED = 'DEPENDENCY_BLOCKED', // 依赖阻塞
851
+ CONTEXT_OVERFLOW = 'CONTEXT_OVERFLOW', // 上下文溢出
852
+ TIMEOUT = 'TIMEOUT', // 超时
853
+ NETWORK_ERROR = 'NETWORK_ERROR', // 网络错误
854
+ AGENT_ERROR = 'AGENT_ERROR', // Agent 错误
855
+ TOOL_ERROR = 'TOOL_ERROR', // 工具错误
856
+ VALIDATION_ERROR = 'VALIDATION_ERROR', // 验证错误
857
+ UNKNOWN = 'UNKNOWN', // 未知错误
858
+ }
859
+ ```
860
+
861
+ ### 失败等级
862
+
863
+ | 等级 | 说明 | 处理策略 |
864
+ |------|------|---------|
865
+ | L1 | 自动重试 | 重试、降级 |
866
+ | L2 | 需要干预 | 人工审核 |
867
+ | L3 | 严重问题 | 开会讨论 |
868
+ | L4 | 致命错误 | 回滚 |
869
+
870
+ ### API 使用
871
+
872
+ ```typescript
873
+ import {
874
+ ErrorClassifier,
875
+ FailureRecorder,
876
+ ErrorType,
877
+ FailureLevel,
878
+ classifyError,
879
+ } from '@dommaker/harness';
880
+
881
+ // 快速分类
882
+ const type = classifyError(new Error('test failed'));
883
+ // type === ErrorType.TEST_FAILED
884
+
885
+ // 完整分类器
886
+ const classifier = new ErrorClassifier({
887
+ rules: [
888
+ {
889
+ type: ErrorType.CUSTOM_ERROR,
890
+ keywords: ['custom', 'specific'],
891
+ level: FailureLevel.L2,
892
+ },
893
+ ],
894
+ });
895
+
896
+ const result = classifier.classify(new Error('custom error'));
897
+ // result.type, result.level, result.matchedRule
898
+
899
+ // 失败记录
900
+ const recorder = new FailureRecorder({
901
+ logFile: '.harness/logs/failures.log',
902
+ });
903
+
904
+ await recorder.record({
905
+ type: ErrorType.TEST_FAILED,
906
+ level: FailureLevel.L1,
907
+ message: 'Test failed',
908
+ timestamp: Date.now(),
909
+ });
910
+
911
+ // 获取统计
912
+ const stats = await recorder.getStats();
913
+ // { total, byType, byLevel }
914
+ ```
915
+
916
+ ### 与 PerformanceCollector 对比
917
+
918
+ | 能力 | PerformanceCollector | FailureRecorder |
919
+ |------|---------------------|-----------------|
920
+ | **监控目标** | 操作耗时 | 错误分类 |
921
+ | **存储内容** | 性能指标 | 失败记录 |
922
+ | **分析维度** | 慢操作、超阈值 | 错误类型分布、失败等级 |
@@ -0,0 +1,49 @@
1
+ /**
2
+ * 架构约束引擎
3
+ *
4
+ * 通用能力:加载规则、执行检查、生成报告
5
+ * 规则定义:由项目提供(.architect/rules.yml)
6
+ */
7
+ export interface ArchitectureContext {
8
+ files: string[];
9
+ diff: string;
10
+ pr?: {
11
+ number: number;
12
+ changedFiles: number;
13
+ additions: number;
14
+ deletions: number;
15
+ };
16
+ }
17
+ export interface ArchitectureViolation {
18
+ ruleId: string;
19
+ message: string;
20
+ files?: string[];
21
+ severity: 'error' | 'warning';
22
+ }
23
+ export interface ArchitectureResult {
24
+ passed: boolean;
25
+ violations: ArchitectureViolation[];
26
+ }
27
+ export interface ArchitectureRule {
28
+ id: string;
29
+ type: 'forbidden-pattern' | 'file-count' | 'module-boundary' | 'custom';
30
+ severity?: 'error' | 'warning';
31
+ scope?: string;
32
+ message?: string;
33
+ patterns?: string[];
34
+ threshold?: number;
35
+ script?: string;
36
+ }
37
+ export declare class ArchitectureConstraintEngine {
38
+ private rules;
39
+ constructor(rules: ArchitectureRule[]);
40
+ check(context: ArchitectureContext): Promise<ArchitectureResult>;
41
+ private checkRule;
42
+ private checkForbiddenPattern;
43
+ private checkFileCount;
44
+ private checkModuleBoundary;
45
+ private matchGlob;
46
+ }
47
+ export declare function loadArchitectureRules(configPath: string): Promise<ArchitectureRule[]>;
48
+ export declare function runArchitectureCheck(configPath: string, context: ArchitectureContext): Promise<ArchitectureResult>;
49
+ //# sourceMappingURL=constraint-engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constraint-engine.d.ts","sourceRoot":"","sources":["../../src/architecture/constraint-engine.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,CAAC,EAAE;QACH,MAAM,EAAE,MAAM,CAAC;QACf,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;CAC/B;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,qBAAqB,EAAE,CAAC;CACrC;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,mBAAmB,GAAG,YAAY,GAAG,iBAAiB,GAAG,QAAQ,CAAC;IACxE,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAE/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IAEpB,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,4BAA4B;IAC3B,OAAO,CAAC,KAAK;gBAAL,KAAK,EAAE,gBAAgB,EAAE;IAEvC,KAAK,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC;YAcxD,SAAS;IAgBvB,OAAO,CAAC,qBAAqB;IA6B7B,OAAO,CAAC,cAAc;IAkBtB,OAAO,CAAC,mBAAmB;IAQ3B,OAAO,CAAC,SAAS;CAkBlB;AAGD,wBAAsB,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAgB3F;AAiED,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,kBAAkB,CAAC,CAI7B"}
@@ -0,0 +1,207 @@
1
+ "use strict";
2
+ /**
3
+ * 架构约束引擎
4
+ *
5
+ * 通用能力:加载规则、执行检查、生成报告
6
+ * 规则定义:由项目提供(.architect/rules.yml)
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.ArchitectureConstraintEngine = void 0;
43
+ exports.loadArchitectureRules = loadArchitectureRules;
44
+ exports.runArchitectureCheck = runArchitectureCheck;
45
+ class ArchitectureConstraintEngine {
46
+ rules;
47
+ constructor(rules) {
48
+ this.rules = rules;
49
+ }
50
+ async check(context) {
51
+ const violations = [];
52
+ for (const rule of this.rules) {
53
+ const result = await this.checkRule(rule, context);
54
+ violations.push(...result);
55
+ }
56
+ return {
57
+ passed: violations.filter(v => v.severity === 'error').length === 0,
58
+ violations,
59
+ };
60
+ }
61
+ async checkRule(rule, context) {
62
+ switch (rule.type) {
63
+ case 'forbidden-pattern':
64
+ return this.checkForbiddenPattern(rule, context);
65
+ case 'file-count':
66
+ return this.checkFileCount(rule, context);
67
+ case 'module-boundary':
68
+ return this.checkModuleBoundary(rule, context);
69
+ default:
70
+ return [];
71
+ }
72
+ }
73
+ checkForbiddenPattern(rule, context) {
74
+ const violations = [];
75
+ const patterns = rule.patterns || [];
76
+ for (const file of context.files) {
77
+ // 检查文件路径是否匹配 scope
78
+ if (rule.scope && !this.matchGlob(file, rule.scope)) {
79
+ continue;
80
+ }
81
+ // 检查文件名是否包含禁止模式
82
+ for (const pattern of patterns) {
83
+ if (file.toLowerCase().includes(pattern.toLowerCase())) {
84
+ violations.push({
85
+ ruleId: rule.id,
86
+ message: rule.message || `File contains forbidden pattern: ${pattern}`,
87
+ files: [file],
88
+ severity: rule.severity || 'error',
89
+ });
90
+ }
91
+ }
92
+ }
93
+ return violations;
94
+ }
95
+ checkFileCount(rule, context) {
96
+ const threshold = rule.threshold || 3;
97
+ const count = context.files.length;
98
+ if (count > threshold) {
99
+ return [{
100
+ ruleId: rule.id,
101
+ message: rule.message || `Too many files changed (${count} > ${threshold}). Reuse check required.`,
102
+ severity: rule.severity || 'warning',
103
+ }];
104
+ }
105
+ return [];
106
+ }
107
+ checkModuleBoundary(rule, context) {
108
+ // TODO: 实现模块边界检查
109
+ return [];
110
+ }
111
+ matchGlob(file, pattern) {
112
+ // 简化的 glob 匹配
113
+ let regexPattern = pattern;
114
+ // 处理 **/ 开头(匹配任意前缀路径)
115
+ if (regexPattern.startsWith('**/')) {
116
+ regexPattern = '(?:.*/)?' + regexPattern.slice(3);
117
+ }
118
+ // 处理 /**/(匹配任意中间路径)和 **(匹配任意字符)
119
+ regexPattern = regexPattern
120
+ .replace(/\/\*\*\//g, '/(?:.*/)?') // /**/ -> /(?:.*/)?
121
+ .replace(/\*\*/g, '.*') // ** -> .*
122
+ .replace(/\*/g, '[^/]*'); // * -> [^/]*
123
+ const regex = new RegExp(regexPattern);
124
+ return regex.test(file);
125
+ }
126
+ }
127
+ exports.ArchitectureConstraintEngine = ArchitectureConstraintEngine;
128
+ // 加载规则的工厂函数(支持 YAML 或 JSON)
129
+ async function loadArchitectureRules(configPath) {
130
+ const fs = await Promise.resolve().then(() => __importStar(require('fs/promises')));
131
+ const content = await fs.readFile(configPath, 'utf-8');
132
+ // 简单 YAML 解析(不依赖外部库)
133
+ let config;
134
+ if (configPath.endsWith('.json')) {
135
+ config = JSON.parse(content);
136
+ }
137
+ else {
138
+ // 简单 YAML 解析:提取 rules 数组
139
+ config = parseSimpleYaml(content);
140
+ }
141
+ return config.rules || [];
142
+ }
143
+ // 简单的 YAML 子集解析器
144
+ function parseSimpleYaml(content) {
145
+ const lines = content.split('\n');
146
+ const result = { rules: [] };
147
+ let currentRule = null;
148
+ let currentArray = null;
149
+ let indentLevel = 0;
150
+ for (const line of lines) {
151
+ const trimmed = line.trim();
152
+ // 跳过注释和空行
153
+ if (!trimmed || trimmed.startsWith('#'))
154
+ continue;
155
+ // 检测缩进级别
156
+ const leadingSpace = line.length - line.trimStart().length;
157
+ // 新规则开始
158
+ if (trimmed.startsWith('- id:')) {
159
+ if (currentRule && result.rules) {
160
+ result.rules.push(currentRule);
161
+ }
162
+ currentRule = { id: trimmed.replace('- id:', '').trim() };
163
+ currentArray = null;
164
+ indentLevel = leadingSpace;
165
+ }
166
+ // 规则属性
167
+ else if (currentRule && trimmed.includes(':')) {
168
+ const colonIndex = trimmed.indexOf(':');
169
+ const key = trimmed.slice(0, colonIndex).trim();
170
+ const value = trimmed.slice(colonIndex + 1).trim();
171
+ if (key === 'patterns') {
172
+ currentArray = [];
173
+ currentRule[key] = currentArray;
174
+ }
175
+ else if (value) {
176
+ // 去除引号
177
+ const cleanValue = value.replace(/^["']|["']$/g, '');
178
+ if (key === 'threshold') {
179
+ currentRule[key] = parseInt(cleanValue, 10);
180
+ }
181
+ else if (key === 'severity') {
182
+ currentRule[key] = cleanValue;
183
+ }
184
+ else {
185
+ currentRule[key] = cleanValue;
186
+ }
187
+ }
188
+ }
189
+ // 数组元素
190
+ else if (currentArray && trimmed.startsWith('- ')) {
191
+ const value = trimmed.slice(2).trim().replace(/^["']|["']$/g, '');
192
+ currentArray.push(value);
193
+ }
194
+ }
195
+ // 添加最后一个规则
196
+ if (currentRule && result.rules) {
197
+ result.rules.push(currentRule);
198
+ }
199
+ return result;
200
+ }
201
+ // CLI 入口
202
+ async function runArchitectureCheck(configPath, context) {
203
+ const rules = await loadArchitectureRules(configPath);
204
+ const engine = new ArchitectureConstraintEngine(rules);
205
+ return engine.check(context);
206
+ }
207
+ //# sourceMappingURL=constraint-engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constraint-engine.js","sourceRoot":"","sources":["../../src/architecture/constraint-engine.ts"],"names":[],"mappings":"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"}