@dommaker/harness 1.9.0 → 1.10.0
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 +6 -0
- package/README.md +9 -12
- package/dist/cli/commands/check.d.ts +3 -3
- package/dist/cli/commands/check.d.ts.map +1 -1
- package/dist/cli/commands/check.js +41 -78
- package/dist/cli/commands/check.js.map +1 -1
- package/dist/cli/commands/constraints-report.d.ts +2 -10
- package/dist/cli/commands/constraints-report.d.ts.map +1 -1
- package/dist/cli/commands/constraints-report.js +4 -57
- package/dist/cli/commands/constraints-report.js.map +1 -1
- package/dist/cli/commands/constraints-retire.d.ts +9 -23
- package/dist/cli/commands/constraints-retire.d.ts.map +1 -1
- package/dist/cli/commands/constraints-retire.js +39 -121
- package/dist/cli/commands/constraints-retire.js.map +1 -1
- package/dist/cli/commands/constraints.d.ts +3 -8
- package/dist/cli/commands/constraints.d.ts.map +1 -1
- package/dist/cli/commands/constraints.js +8 -23
- package/dist/cli/commands/constraints.js.map +1 -1
- package/dist/cli/commands/definitions.js +2 -2
- package/dist/cli/commands/init.d.ts +0 -32
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +7 -149
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/report.d.ts.map +1 -1
- package/dist/cli/commands/report.js +14 -17
- package/dist/cli/commands/report.js.map +1 -1
- package/dist/cli/commands/scaffold-templates.d.ts +0 -2
- package/dist/cli/commands/scaffold-templates.d.ts.map +1 -1
- package/dist/cli/commands/scaffold-templates.js +2 -37
- package/dist/cli/commands/scaffold-templates.js.map +1 -1
- package/dist/cli/commands/scaffold.d.ts +0 -2
- package/dist/cli/commands/scaffold.d.ts.map +1 -1
- package/dist/cli/commands/scaffold.js +0 -10
- package/dist/cli/commands/scaffold.js.map +1 -1
- package/dist/cli/commands/status.js +10 -10
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/sync-docs/agents-syncer.d.ts +0 -2
- package/dist/cli/commands/sync-docs/agents-syncer.d.ts.map +1 -1
- package/dist/cli/commands/sync-docs/agents-syncer.js +4 -23
- package/dist/cli/commands/sync-docs/agents-syncer.js.map +1 -1
- package/dist/core/constraints/capabilities-parser.d.ts.map +1 -1
- package/dist/core/constraints/capabilities-parser.js +16 -4
- package/dist/core/constraints/capabilities-parser.js.map +1 -1
- package/dist/core/constraints/checker.d.ts +15 -24
- package/dist/core/constraints/checker.d.ts.map +1 -1
- package/dist/core/constraints/checker.js +33 -72
- package/dist/core/constraints/checker.js.map +1 -1
- package/dist/core/constraints/checkers/docs-freshness.js +1 -1
- package/dist/core/constraints/checkers/docs-freshness.js.map +1 -1
- package/dist/core/constraints/checkers/governance-presence.d.ts +2 -1
- package/dist/core/constraints/checkers/governance-presence.d.ts.map +1 -1
- package/dist/core/constraints/checkers/governance-presence.js +4 -3
- package/dist/core/constraints/checkers/governance-presence.js.map +1 -1
- package/dist/core/constraints/checkers/index.d.ts +1 -1
- package/dist/core/constraints/checkers/index.d.ts.map +1 -1
- package/dist/core/constraints/checkers/index.js +4 -8
- package/dist/core/constraints/checkers/index.js.map +1 -1
- package/dist/core/constraints/checkers/iron-flags.d.ts +3 -4
- package/dist/core/constraints/checkers/iron-flags.d.ts.map +1 -1
- package/dist/core/constraints/checkers/iron-flags.js +4 -5
- package/dist/core/constraints/checkers/iron-flags.js.map +1 -1
- package/dist/core/constraints/checkers/no-test-simplification.js +1 -1
- package/dist/core/constraints/checkers/no-test-simplification.js.map +1 -1
- package/dist/core/constraints/checkers/types.d.ts +1 -1
- package/dist/core/constraints/checkers/types.js +1 -1
- package/dist/core/constraints/definitions/guidelines.d.ts +4 -4
- package/dist/core/constraints/definitions/guidelines.d.ts.map +1 -1
- package/dist/core/constraints/definitions/guidelines.js +9 -25
- package/dist/core/constraints/definitions/guidelines.js.map +1 -1
- package/dist/core/constraints/definitions/iron-laws.d.ts +4 -4
- package/dist/core/constraints/definitions/iron-laws.d.ts.map +1 -1
- package/dist/core/constraints/definitions/iron-laws.js +10 -81
- package/dist/core/constraints/definitions/iron-laws.js.map +1 -1
- package/dist/core/constraints/definitions.d.ts +7 -9
- package/dist/core/constraints/definitions.d.ts.map +1 -1
- package/dist/core/constraints/definitions.js +13 -16
- package/dist/core/constraints/definitions.js.map +1 -1
- package/dist/core/constraints/index.d.ts +2 -5
- package/dist/core/constraints/index.d.ts.map +1 -1
- package/dist/core/constraints/index.js +3 -13
- package/dist/core/constraints/index.js.map +1 -1
- package/dist/core/constraints/injection-writer.d.ts +10 -38
- package/dist/core/constraints/injection-writer.d.ts.map +1 -1
- package/dist/core/constraints/injection-writer.js +17 -54
- package/dist/core/constraints/injection-writer.js.map +1 -1
- package/dist/core/constraints/run-env.d.ts +1 -10
- package/dist/core/constraints/run-env.d.ts.map +1 -1
- package/dist/core/constraints/run-env.js +1 -22
- package/dist/core/constraints/run-env.js.map +1 -1
- package/dist/core/constraints/usage-report.d.ts +5 -7
- package/dist/core/constraints/usage-report.d.ts.map +1 -1
- package/dist/core/constraints/usage-report.js +5 -10
- package/dist/core/constraints/usage-report.js.map +1 -1
- package/dist/core/effective-constraints.d.ts +12 -22
- package/dist/core/effective-constraints.d.ts.map +1 -1
- package/dist/core/effective-constraints.js +13 -30
- package/dist/core/effective-constraints.js.map +1 -1
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +3 -9
- package/dist/core/index.js.map +1 -1
- package/dist/core/project-config-loader.d.ts +4 -42
- package/dist/core/project-config-loader.d.ts.map +1 -1
- package/dist/core/project-config-loader.js +16 -125
- package/dist/core/project-config-loader.js.map +1 -1
- package/dist/core/validators/passes-gate.d.ts +2 -2
- package/dist/core/validators/passes-gate.js +6 -6
- package/dist/core/validators/passes-gate.js.map +1 -1
- package/dist/index.d.ts +7 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -18
- package/dist/index.js.map +1 -1
- package/dist/monitoring/trace-analyzer.d.ts.map +1 -1
- package/dist/monitoring/trace-analyzer.js +11 -12
- package/dist/monitoring/trace-analyzer.js.map +1 -1
- package/dist/monitoring/traces.d.ts +4 -4
- package/dist/monitoring/traces.d.ts.map +1 -1
- package/dist/monitoring/traces.js +8 -8
- package/dist/monitoring/traces.js.map +1 -1
- package/dist/presets/standard.d.ts +8 -10
- package/dist/presets/standard.d.ts.map +1 -1
- package/dist/presets/standard.js +10 -16
- package/dist/presets/standard.js.map +1 -1
- package/dist/types/constraint.d.ts +24 -35
- package/dist/types/constraint.d.ts.map +1 -1
- package/dist/types/constraint.js +8 -7
- package/dist/types/constraint.js.map +1 -1
- package/dist/types/passes-gate.d.ts +5 -5
- package/dist/types/passes-gate.d.ts.map +1 -1
- package/dist/types/project-config.d.ts +8 -66
- package/dist/types/project-config.d.ts.map +1 -1
- package/dist/types/project-config.js +3 -2
- package/dist/types/project-config.js.map +1 -1
- package/dist/types/trace.d.ts +8 -10
- package/dist/types/trace.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/CONTEXT.md +2 -2
- package/src/__tests__/checker-extra.test.ts +37 -61
- package/src/__tests__/checker.test.ts +10 -115
- package/src/__tests__/effective-constraints.test.ts +18 -83
- package/src/__tests__/iron-laws.test.ts +71 -90
- package/src/__tests__/passes-gate-check.test.ts +2 -2
- package/src/__tests__/project-config-loader-extra.test.ts +22 -123
- package/src/__tests__/project-config-loader.test.ts +5 -33
- package/src/__tests__/public-exports.test.ts +1 -7
- package/src/__tests__/public-type-surface.test.ts +2 -5
- package/src/__tests__/public-value-surface.test.ts +1 -7
- package/src/__tests__/skip-semantics.test.ts +21 -37
- package/src/__tests__/trace-analyzer-extra.test.ts +14 -14
- package/src/__tests__/trace-analyzer.test.ts +21 -21
- package/src/__tests__/trace-collector.test.ts +22 -22
- package/src/__tests__/traces.test.ts +27 -28
- package/src/cli/commands/CONTEXT.md +9 -9
- package/src/cli/commands/__tests__/check-read-count.test.ts +4 -15
- package/src/cli/commands/__tests__/check-skip-output.test.ts +14 -14
- package/src/cli/commands/__tests__/check.test.ts +14 -31
- package/src/cli/commands/__tests__/constraints-report.test.ts +12 -80
- package/src/cli/commands/__tests__/constraints-retire.test.ts +42 -231
- package/src/cli/commands/__tests__/init-governance-io.test.ts +12 -58
- package/src/cli/commands/__tests__/init-ondisk.test.ts +12 -20
- package/src/cli/commands/__tests__/init.test.ts +0 -11
- package/src/cli/commands/__tests__/project-path-anchoring.test.ts +2 -2
- package/src/cli/commands/__tests__/report-extra.test.ts +4 -4
- package/src/cli/commands/__tests__/report.test.ts +1 -1
- package/src/cli/commands/__tests__/scaffold.test.ts +4 -14
- package/src/cli/commands/__tests__/status-analysis-wiring.test.ts +3 -3
- package/src/cli/commands/__tests__/status-extra.test.ts +14 -14
- package/src/cli/commands/__tests__/status.test.ts +12 -12
- package/src/cli/commands/__tests__/sync-docs-agents.test.ts +1 -1
- package/src/cli/commands/check.ts +43 -82
- package/src/cli/commands/constraints-report.ts +4 -59
- package/src/cli/commands/constraints-retire.ts +49 -154
- package/src/cli/commands/constraints.ts +11 -26
- package/src/cli/commands/definitions.ts +2 -2
- package/src/cli/commands/init.ts +8 -179
- package/src/cli/commands/report.ts +19 -23
- package/src/cli/commands/scaffold-templates.ts +1 -37
- package/src/cli/commands/scaffold.ts +0 -11
- package/src/cli/commands/status.ts +10 -10
- package/src/cli/commands/sync-docs/agents-syncer.ts +4 -25
- package/src/core/CONTEXT.md +9 -9
- package/src/core/constraints/__tests__/capability-counts.test.ts +5 -5
- package/src/core/constraints/__tests__/collect-constraints.test.ts +13 -8
- package/src/core/constraints/__tests__/git-evidence.test.ts +4 -7
- package/src/core/constraints/__tests__/injection-writer.test.ts +8 -61
- package/src/core/constraints/__tests__/run-env.test.ts +5 -34
- package/src/core/constraints/__tests__/trace-injection.test.ts +1 -1
- package/src/core/constraints/capabilities-parser.ts +17 -5
- package/src/core/constraints/checker.ts +39 -88
- package/src/core/constraints/checkers/__tests__/check-outcome.test.ts +5 -8
- package/src/core/constraints/checkers/__tests__/governance-presence.test.ts +1 -53
- package/src/core/constraints/checkers/__tests__/iron-flags.test.ts +5 -25
- package/src/core/constraints/checkers/docs-freshness.ts +1 -1
- package/src/core/constraints/checkers/governance-presence.ts +4 -3
- package/src/core/constraints/checkers/index.ts +4 -10
- package/src/core/constraints/checkers/iron-flags.ts +3 -12
- package/src/core/constraints/checkers/no-test-simplification.ts +1 -1
- package/src/core/constraints/checkers/types.ts +1 -1
- package/src/core/constraints/definitions/guidelines.ts +8 -25
- package/src/core/constraints/definitions/iron-laws.ts +9 -82
- package/src/core/constraints/definitions.ts +14 -15
- package/src/core/constraints/index.ts +4 -19
- package/src/core/constraints/injection-writer.ts +16 -61
- package/src/core/constraints/run-env.ts +1 -33
- package/src/core/constraints/usage-report.ts +8 -15
- package/src/core/effective-constraints.ts +14 -45
- package/src/core/index.ts +3 -10
- package/src/core/project-config-loader.ts +20 -145
- package/src/core/validators/passes-gate.ts +6 -6
- package/src/failure/CONTEXT.md +1 -1
- package/src/hooks/__tests__/bootstrap.test.ts +3 -5
- package/src/index.ts +8 -18
- package/src/monitoring/__tests__/trace-file-anchoring.test.ts +1 -1
- package/src/monitoring/__tests__/traces.test.ts +22 -22
- package/src/monitoring/trace-analyzer.ts +12 -13
- package/src/monitoring/traces.ts +8 -8
- package/src/presets/__tests__/standard.test.ts +12 -16
- package/src/presets/standard.ts +14 -23
- package/src/types/__tests__/types.test.ts +3 -3
- package/src/types/constraint.ts +26 -40
- package/src/types/passes-gate.ts +5 -5
- package/src/types/project-config.ts +8 -77
- package/src/types/trace.ts +8 -11
- package/dist/core/constraints/agent-prompt-renderer.d.ts +0 -32
- package/dist/core/constraints/agent-prompt-renderer.d.ts.map +0 -1
- package/dist/core/constraints/agent-prompt-renderer.js +0 -55
- package/dist/core/constraints/agent-prompt-renderer.js.map +0 -1
- package/dist/core/constraints/checkers/no-bypass-checkpoint.d.ts +0 -6
- package/dist/core/constraints/checkers/no-bypass-checkpoint.d.ts.map +0 -1
- package/dist/core/constraints/checkers/no-bypass-checkpoint.js +0 -40
- package/dist/core/constraints/checkers/no-bypass-checkpoint.js.map +0 -1
- package/dist/core/constraints/definitions/prompts.d.ts +0 -12
- package/dist/core/constraints/definitions/prompts.d.ts.map +0 -1
- package/dist/core/constraints/definitions/prompts.js +0 -400
- package/dist/core/constraints/definitions/prompts.js.map +0 -1
- package/dist/core/constraints/injection-drift.d.ts +0 -61
- package/dist/core/constraints/injection-drift.d.ts.map +0 -1
- package/dist/core/constraints/injection-drift.js +0 -131
- package/dist/core/constraints/injection-drift.js.map +0 -1
- package/dist/core/constraints/injection-renderer.d.ts +0 -23
- package/dist/core/constraints/injection-renderer.d.ts.map +0 -1
- package/dist/core/constraints/injection-renderer.js +0 -44
- package/dist/core/constraints/injection-renderer.js.map +0 -1
- package/src/cli/commands/__tests__/check-drift.test.ts +0 -158
- package/src/cli/commands/__tests__/init-injection.test.ts +0 -466
- package/src/core/constraints/__tests__/agent-prompt-renderer.test.ts +0 -129
- package/src/core/constraints/__tests__/injection-drift.test.ts +0 -247
- package/src/core/constraints/agent-prompt-renderer.ts +0 -67
- package/src/core/constraints/checkers/__tests__/no-bypass-checkpoint.test.ts +0 -76
- package/src/core/constraints/checkers/no-bypass-checkpoint.ts +0 -40
- package/src/core/constraints/definitions/prompts.ts +0 -414
- package/src/core/constraints/injection-drift.ts +0 -142
- package/src/core/constraints/injection-renderer.ts +0 -44
|
@@ -6,15 +6,14 @@
|
|
|
6
6
|
* ADR-0023 决策 2),一次运行内各至多读一次。
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type { Constraint
|
|
9
|
+
import type { Constraint } from '../types/constraint';
|
|
10
10
|
import type {
|
|
11
11
|
ProjectConfig,
|
|
12
|
-
CustomConstraintDefinition,
|
|
13
12
|
MergedConstraintsConfig,
|
|
14
13
|
CapabilitiesConfig,
|
|
15
14
|
GovernanceConfig,
|
|
16
15
|
} from '../types/project-config';
|
|
17
|
-
import {
|
|
16
|
+
import { CONSTRAINTS } from './constraints/definitions';
|
|
18
17
|
import { PRESETS_BY_NAME, STANDARD_PRESET } from '../presets';
|
|
19
18
|
import { filterEnabledEntries } from './effective-set';
|
|
20
19
|
import { resolveRunEnv, type RunEnv, type RunTarget } from './constraints/run-env';
|
|
@@ -24,7 +23,6 @@ import { resolveRunEnv, type RunEnv, type RunTarget } from './constraints/run-en
|
|
|
24
23
|
*/
|
|
25
24
|
const DEFAULT_CONFIG: ProjectConfig = {
|
|
26
25
|
preset: 'standard',
|
|
27
|
-
custom_constraints_file: 'custom-constraints.yml',
|
|
28
26
|
};
|
|
29
27
|
|
|
30
28
|
/**
|
|
@@ -103,25 +101,6 @@ export function getCapabilitiesMode(target: RunTarget): CapabilitiesMode {
|
|
|
103
101
|
return 'file';
|
|
104
102
|
}
|
|
105
103
|
|
|
106
|
-
/**
|
|
107
|
-
* 退役判定单点(studio#82 D6 一处真相)
|
|
108
|
-
*
|
|
109
|
-
* 两个落点合成一条规则:禁用(config.yml `constraints.<id>.enabled: false`,内置约束的
|
|
110
|
-
* 退役落点)或 custom 条目自带 `retired` 元数据(custom 约束的退役落点,不拆 config.yml
|
|
111
|
-
* 第二处)。`mergeConstraints` 的 custom 追加跳过与 `harness constraints retire` 的
|
|
112
|
-
* already_retired 检查共用本函数——后者此前是这条规则的手写复印(harness#137 收口)。
|
|
113
|
-
*
|
|
114
|
-
* `disabled` 由调用方给:mergeConstraints 传的是含 preset 裁剪的完整禁用集,
|
|
115
|
-
* retire 传的是 config.yml 单点(退役落点只有这一处,preset 语义不掺进退役判定)。
|
|
116
|
-
* `retired` 按 truthiness 判,与 mergeConstraints 历史口径逐字一致。
|
|
117
|
-
*/
|
|
118
|
-
export function isConstraintRetired(
|
|
119
|
-
customDef: { retired?: unknown } | undefined,
|
|
120
|
-
disabled: boolean
|
|
121
|
-
): boolean {
|
|
122
|
-
return disabled || Boolean(customDef?.retired);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
104
|
/**
|
|
126
105
|
* 项目配置加载器
|
|
127
106
|
*/
|
|
@@ -129,7 +108,6 @@ export class ProjectConfigLoader {
|
|
|
129
108
|
private env: RunEnv;
|
|
130
109
|
private projectPath: string;
|
|
131
110
|
private config: ProjectConfig;
|
|
132
|
-
private customConstraints: Record<string, CustomConstraintDefinition>;
|
|
133
111
|
|
|
134
112
|
/**
|
|
135
113
|
* @param target 项目根路径,或本 run 已构造的运行级观察面(ADR-0023 决策 2)
|
|
@@ -139,51 +117,23 @@ export class ProjectConfigLoader {
|
|
|
139
117
|
this.env = resolveRunEnv(target);
|
|
140
118
|
this.projectPath = this.env.projectPath;
|
|
141
119
|
this.config = { ...DEFAULT_CONFIG };
|
|
142
|
-
this.customConstraints = {};
|
|
143
120
|
}
|
|
144
121
|
|
|
145
122
|
/**
|
|
146
|
-
*
|
|
123
|
+
* 加载项目配置(run 内 memo 在观察面上,同一份解析结果供全部消费方共用)
|
|
147
124
|
*/
|
|
148
125
|
load(): ProjectConfig {
|
|
149
|
-
// 1. 加载主配置(run 内 memo 在观察面上,同一份解析结果供全部消费方共用)
|
|
150
126
|
const raw = this.env.rawConfig();
|
|
151
127
|
if (raw) {
|
|
152
128
|
this.config = { ...DEFAULT_CONFIG, ...(raw as Partial<ProjectConfig>) };
|
|
153
129
|
}
|
|
154
|
-
|
|
155
|
-
// 2. 加载自定义约束
|
|
156
|
-
this.loadCustomConstraints();
|
|
157
|
-
|
|
158
130
|
return this.config;
|
|
159
131
|
}
|
|
160
132
|
|
|
161
133
|
/**
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
* 文件读取经观察面(ADR-0023 决策 2):同一次运行内多个加载器共用同一份内容,
|
|
165
|
-
* 「哪个文件名算数」与合并顺序仍由本函数决定(方式 1 文件 → 方式 2 主配置内联,后者覆盖)。
|
|
166
|
-
*/
|
|
167
|
-
private loadCustomConstraints(): void {
|
|
168
|
-
// 方式 1:从单独文件加载
|
|
169
|
-
if (this.config.custom_constraints_file) {
|
|
170
|
-
const defs = this.env.customConstraints(this.config.custom_constraints_file);
|
|
171
|
-
this.customConstraints = { ...this.customConstraints, ...defs };
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// 方式 2:从主配置文件中加载
|
|
175
|
-
if (this.config.custom_constraints) {
|
|
176
|
-
this.customConstraints = { ...this.customConstraints, ...this.config.custom_constraints };
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* 合并内置约束和自定义约束(ADR-0001 生效集完整链路)
|
|
134
|
+
* 合并内置约束(生效集完整链路,ADR-0029)
|
|
182
135
|
*
|
|
183
|
-
* 合并顺序:内置 → preset 裁剪 → config.yml `constraints.<id>.enabled:false`
|
|
184
|
-
* 删除(对内置与 custom 同效)→ custom-constraints 追加
|
|
185
|
-
* (禁用/已退役的 custom 不追加)→ scenes 过滤
|
|
186
|
-
* (带 appliesTo 的 prompt 仅当 scenes 交集非空时保留)。
|
|
136
|
+
* 合并顺序:内置 → preset 裁剪 → config.yml `constraints.<id>.enabled:false` 删除。
|
|
187
137
|
*
|
|
188
138
|
* config.yml 中未知约束 id(如已移除约束的禁用残留)静默忽略,
|
|
189
139
|
* 记录在结果 unknownIds 中供诊断。
|
|
@@ -199,6 +149,9 @@ export class ProjectConfigLoader {
|
|
|
199
149
|
console.error(`[harness] 未知预设 "${presetName}",已回落 standard`);
|
|
200
150
|
preset = STANDARD_PRESET;
|
|
201
151
|
}
|
|
152
|
+
// preset 按 severity 分键;展开为「id → 是否启用」的单桶视图
|
|
153
|
+
const bySeverity = (severity: 'error' | 'warning'): Record<string, Constraint> =>
|
|
154
|
+
Object.fromEntries(Object.entries(CONSTRAINTS).filter(([, c]) => c.severity === severity));
|
|
202
155
|
const pick = (
|
|
203
156
|
source: Record<string, Constraint>,
|
|
204
157
|
ids: string[] | null
|
|
@@ -215,104 +168,36 @@ export class ProjectConfigLoader {
|
|
|
215
168
|
ids: string[] | null
|
|
216
169
|
): string[] => (ids === null ? [] : Object.keys(source).filter(id => !ids.includes(id)));
|
|
217
170
|
|
|
171
|
+
const errorConstraints = bySeverity('error');
|
|
172
|
+
const warningConstraints = bySeverity('warning');
|
|
173
|
+
|
|
218
174
|
const unknownIds: string[] = [];
|
|
219
175
|
const result: MergedConstraintsConfig = {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
176
|
+
constraints: {
|
|
177
|
+
...pick(errorConstraints, preset.errors),
|
|
178
|
+
...pick(warningConstraints, preset.warnings),
|
|
179
|
+
},
|
|
223
180
|
disabled: [
|
|
224
|
-
...presetDisabled(
|
|
225
|
-
...presetDisabled(
|
|
226
|
-
...presetDisabled(PROMPTS, preset.prompts),
|
|
181
|
+
...presetDisabled(errorConstraints, preset.errors),
|
|
182
|
+
...presetDisabled(warningConstraints, preset.warnings),
|
|
227
183
|
],
|
|
228
|
-
custom: [],
|
|
229
184
|
unknownIds,
|
|
230
185
|
};
|
|
231
186
|
|
|
232
187
|
// 1. 处理启用/禁用配置(未知 id 静默忽略,记录供诊断)
|
|
233
188
|
if (this.config.constraints) {
|
|
234
|
-
const knownIds = new Set(
|
|
235
|
-
...Object.keys(IRON_LAWS),
|
|
236
|
-
...Object.keys(GUIDELINES),
|
|
237
|
-
...Object.keys(PROMPTS),
|
|
238
|
-
...Object.keys(this.customConstraints),
|
|
239
|
-
]);
|
|
189
|
+
const knownIds = new Set(Object.keys(CONSTRAINTS));
|
|
240
190
|
const filtered = filterEnabledEntries(knownIds, this.config.constraints);
|
|
241
191
|
unknownIds.push(...filtered.unknownIds);
|
|
242
192
|
for (const constraintId of filtered.disabledIds) {
|
|
243
193
|
result.disabled.push(constraintId);
|
|
244
|
-
|
|
245
|
-
delete result.ironLaws[constraintId];
|
|
246
|
-
delete result.guidelines[constraintId];
|
|
247
|
-
delete result.prompts![constraintId];
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// 2. 添加自定义约束(config.yml 禁用的 id 不追加:step 1 已将其
|
|
252
|
-
// 收集进 disabled,custom 在 step 1 时尚未入桶,须在此兜底跳过;
|
|
253
|
-
// 条目带 retired 元数据的同样不追加——studio#82 D6 退役落点在条目自身)
|
|
254
|
-
for (const [id, customDef] of Object.entries(this.customConstraints)) {
|
|
255
|
-
if (isConstraintRetired(customDef, result.disabled.includes(id))) {
|
|
256
|
-
continue;
|
|
257
|
-
}
|
|
258
|
-
const constraint = this.toConstraint(customDef, id);
|
|
259
|
-
|
|
260
|
-
result.custom.push(id);
|
|
261
|
-
|
|
262
|
-
const level = customDef.level || 'guideline';
|
|
263
|
-
switch (level) {
|
|
264
|
-
case 'iron_law':
|
|
265
|
-
result.ironLaws[id] = constraint;
|
|
266
|
-
break;
|
|
267
|
-
case 'guideline':
|
|
268
|
-
result.guidelines[id] = constraint;
|
|
269
|
-
break;
|
|
270
|
-
case 'prompt':
|
|
271
|
-
result.prompts![id] = constraint;
|
|
272
|
-
break;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// 3. scenes 过滤(ADR-0001):带 appliesTo 标签的 prompt 仅当项目 scenes
|
|
277
|
-
// 与其交集非空时保留;无 appliesTo 的条目不受影响。缺省 scenes=[] 即
|
|
278
|
-
// 场景专属 prompt 默认不进入生效集。
|
|
279
|
-
const scenes = this.config.scenes ?? [];
|
|
280
|
-
for (const [id, constraint] of Object.entries(result.prompts!)) {
|
|
281
|
-
if (
|
|
282
|
-
constraint.appliesTo &&
|
|
283
|
-
constraint.appliesTo.length > 0 &&
|
|
284
|
-
!constraint.appliesTo.some(scene => scenes.includes(scene))
|
|
285
|
-
) {
|
|
286
|
-
delete result.prompts![id];
|
|
194
|
+
delete result.constraints[constraintId];
|
|
287
195
|
}
|
|
288
196
|
}
|
|
289
197
|
|
|
290
198
|
return result;
|
|
291
199
|
}
|
|
292
200
|
|
|
293
|
-
/**
|
|
294
|
-
* 将自定义约束定义转换为 Constraint
|
|
295
|
-
*
|
|
296
|
-
* kind 推导(ADR-0001):自定义约束没有注册表中的 checker,统一归为
|
|
297
|
-
* kind='prompt'(不执行 checker,check() 短路通过),level 仅决定其所在
|
|
298
|
-
* 桶与严重性行为,保持与历史"未注册默认通过"相同的执行语义。
|
|
299
|
-
*/
|
|
300
|
-
private toConstraint(def: CustomConstraintDefinition, defaultId: string): Constraint {
|
|
301
|
-
const level = def.level || 'guideline';
|
|
302
|
-
return {
|
|
303
|
-
id: def.id || defaultId,
|
|
304
|
-
kind: 'prompt',
|
|
305
|
-
level,
|
|
306
|
-
rule: def.rule || '',
|
|
307
|
-
message: def.message || '',
|
|
308
|
-
trigger: (def.trigger || 'manual') as ConstraintTrigger | ConstraintTrigger[],
|
|
309
|
-
description: def.description,
|
|
310
|
-
promptInjection: def.promptInjection,
|
|
311
|
-
enabled: def.enabled !== false,
|
|
312
|
-
enforcement: 'custom',
|
|
313
|
-
};
|
|
314
|
-
}
|
|
315
|
-
|
|
316
201
|
/**
|
|
317
202
|
* 获取当前配置
|
|
318
203
|
*/
|
|
@@ -320,20 +205,10 @@ export class ProjectConfigLoader {
|
|
|
320
205
|
return this.config;
|
|
321
206
|
}
|
|
322
207
|
|
|
323
|
-
/**
|
|
324
|
-
* 获取自定义约束
|
|
325
|
-
*/
|
|
326
|
-
getCustomConstraints(): Record<string, CustomConstraintDefinition> {
|
|
327
|
-
return this.customConstraints;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
208
|
/**
|
|
331
209
|
* 检查是否有自定义配置
|
|
332
210
|
*/
|
|
333
211
|
hasCustomConfig(): boolean {
|
|
334
|
-
return (
|
|
335
|
-
Object.keys(this.customConstraints).length > 0 ||
|
|
336
|
-
(this.config.constraints !== undefined && Object.keys(this.config.constraints).length > 0)
|
|
337
|
-
);
|
|
212
|
+
return this.config.constraints !== undefined && Object.keys(this.config.constraints).length > 0;
|
|
338
213
|
}
|
|
339
214
|
}
|
|
@@ -97,7 +97,7 @@ export class PassesGate {
|
|
|
97
97
|
* - 业务层自己运行测试,传入结果
|
|
98
98
|
*
|
|
99
99
|
* @param testResult 测试结果(由业务层运行测试后传入)
|
|
100
|
-
* @returns 验证结果(是否允许标记完成 +
|
|
100
|
+
* @returns 验证结果(是否允许标记完成 + 阻断级违规)
|
|
101
101
|
*
|
|
102
102
|
* @example
|
|
103
103
|
* ```typescript
|
|
@@ -107,7 +107,7 @@ export class PassesGate {
|
|
|
107
107
|
*
|
|
108
108
|
* if (!passesResult.allowed) {
|
|
109
109
|
* return res.status(400).json({
|
|
110
|
-
* error: '
|
|
110
|
+
* error: '约束违规',
|
|
111
111
|
* violations: passesResult.violations,
|
|
112
112
|
* });
|
|
113
113
|
* }
|
|
@@ -116,23 +116,23 @@ export class PassesGate {
|
|
|
116
116
|
check(testResult: TestResult): PassesGateCheckResult {
|
|
117
117
|
const violations: PassesGateViolation[] = [];
|
|
118
118
|
|
|
119
|
-
// 1. 测试未通过 →
|
|
119
|
+
// 1. 测试未通过 → NO SELF APPROVAL WITHOUT TEST EVIDENCE
|
|
120
120
|
if (!testResult.passed) {
|
|
121
121
|
violations.push({
|
|
122
122
|
id: 'no_self_approval',
|
|
123
123
|
rule: 'NO SELF APPROVAL WITHOUT TEST EVIDENCE',
|
|
124
124
|
message: '测试未通过',
|
|
125
|
-
|
|
125
|
+
severity: 'error',
|
|
126
126
|
});
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
// 2. 缺少证据 →
|
|
129
|
+
// 2. 缺少证据 → NO COMPLETION CLAIMS WITHOUT FRESH VERIFICATION
|
|
130
130
|
if (this.config.requireEvidence && !testResult.evidence) {
|
|
131
131
|
violations.push({
|
|
132
132
|
id: 'no_completion_without_verification',
|
|
133
133
|
rule: 'NO COMPLETION CLAIMS WITHOUT FRESH VERIFICATION',
|
|
134
134
|
message: '缺少测试证据',
|
|
135
|
-
|
|
135
|
+
severity: 'error',
|
|
136
136
|
});
|
|
137
137
|
}
|
|
138
138
|
|
package/src/failure/CONTEXT.md
CHANGED
|
@@ -22,6 +22,6 @@
|
|
|
22
22
|
- 分类器基于规则匹配,不调用 LLM
|
|
23
23
|
- S4 约束违规处理策略模块(ConstraintViolationHandler / executeWithBlock /
|
|
24
24
|
executeWithCollect / executeWithSafeBoolean)已删除(架构评审候选1):
|
|
25
|
-
COLLECT 对真实 checker 结构上不可交付(checker
|
|
25
|
+
COLLECT 对真实 checker 结构上不可交付(checker 首个 error 级违规即 throw,
|
|
26
26
|
handler 的 catch 只能吞证据造假绿)。语义归宿——阻断 = `checkConstraints` 本体,
|
|
27
27
|
收集 = `checker.collectConstraints`(report 直调);BLOCK/SAFE_BOOLEAN 零生产消费者随迁
|
|
@@ -22,7 +22,6 @@ function setupTempDir(dir: string): string {
|
|
|
22
22
|
fs.mkdirSync(harnessDir, { recursive: true });
|
|
23
23
|
const config = {
|
|
24
24
|
preset: 'standard',
|
|
25
|
-
custom_constraints_file: 'custom-constraints.yml',
|
|
26
25
|
};
|
|
27
26
|
fs.writeFileSync(path.join(harnessDir, 'config.yml'), yaml.dump(config), 'utf-8');
|
|
28
27
|
return harnessDir;
|
|
@@ -85,7 +84,7 @@ describe('bootstrapHarness', () => {
|
|
|
85
84
|
const result = await bootstrapHarness(noConfigDir);
|
|
86
85
|
expect(result).toHaveProperty('checker');
|
|
87
86
|
expect(result).toHaveProperty('projectPath', noConfigDir);
|
|
88
|
-
expect(result.mergedConstraints).toHaveProperty('
|
|
87
|
+
expect(result.mergedConstraints).toHaveProperty('constraints');
|
|
89
88
|
} finally {
|
|
90
89
|
fs.rmSync(noConfigDir, { recursive: true, force: true });
|
|
91
90
|
}
|
|
@@ -121,9 +120,8 @@ describe('bootstrapHarnessSync', () => {
|
|
|
121
120
|
it('initializes ConstraintChecker with merged constraints', () => {
|
|
122
121
|
const result = bootstrapHarnessSync(tempDir);
|
|
123
122
|
// Check that mergedConstraints has the expected structure
|
|
124
|
-
expect(result.mergedConstraints).toHaveProperty('
|
|
125
|
-
|
|
126
|
-
});
|
|
123
|
+
expect(result.mergedConstraints).toHaveProperty('constraints');
|
|
124
|
+
});
|
|
127
125
|
|
|
128
126
|
it('uses process.cwd() when no project path is given', () => {
|
|
129
127
|
// We cannot easily test process.cwd() fallback without mocking cwd,
|
package/src/index.ts
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 通用工程约束框架
|
|
5
5
|
*
|
|
6
|
-
* 约束体系(ADR-
|
|
7
|
-
* - check:可执行检查(
|
|
8
|
-
* -
|
|
6
|
+
* 约束体系(ADR-0029 severity 显式模型):
|
|
7
|
+
* - 全部约束 kind='check':可执行检查(error 阻断 / warning 告警)
|
|
8
|
+
* - 纯文本提示层已整体关停(ADR-0029)
|
|
9
9
|
*
|
|
10
10
|
* 门禁系统:
|
|
11
11
|
* - PassesGate:测试门控
|
|
@@ -16,13 +16,13 @@
|
|
|
16
16
|
* - CheckpointValidator:检查点验证
|
|
17
17
|
*
|
|
18
18
|
* 公共导出(ADR-0003):显式清单,禁 export *。
|
|
19
|
-
* 收录标准:属 harness 定位(约束数据 / 执行引擎 /
|
|
19
|
+
* 收录标准:属 harness 定位(约束数据 / 执行引擎 / 知识基建)、
|
|
20
20
|
* 实现真实可用、无同名冲突。内部 seam
|
|
21
21
|
* (ConstraintChecker / constraintChecker / ProjectConfigLoader)不进公共清单。
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
// ========================================
|
|
25
|
-
//
|
|
25
|
+
// 约束类型(severity 显式模型)
|
|
26
26
|
// ========================================
|
|
27
27
|
export {
|
|
28
28
|
ConstraintViolationError,
|
|
@@ -31,7 +31,7 @@ export type {
|
|
|
31
31
|
Constraint,
|
|
32
32
|
ConstraintId,
|
|
33
33
|
ConstraintKind,
|
|
34
|
-
|
|
34
|
+
ConstraintSeverity,
|
|
35
35
|
ConstraintTrigger,
|
|
36
36
|
ConstraintContext,
|
|
37
37
|
ConstraintResult,
|
|
@@ -43,9 +43,7 @@ export type {
|
|
|
43
43
|
// 约束数据(内置定义 + 生效集)
|
|
44
44
|
// ========================================
|
|
45
45
|
export {
|
|
46
|
-
|
|
47
|
-
GUIDELINES,
|
|
48
|
-
PROMPTS,
|
|
46
|
+
CONSTRAINTS,
|
|
49
47
|
getAllConstraints,
|
|
50
48
|
getConstraint,
|
|
51
49
|
findConstraintsByTrigger,
|
|
@@ -67,17 +65,10 @@ export {
|
|
|
67
65
|
export type { CheckConstraintsOptions } from './core/constraints/checker';
|
|
68
66
|
|
|
69
67
|
// ========================================
|
|
70
|
-
//
|
|
68
|
+
// 约束缓存
|
|
71
69
|
// ========================================
|
|
72
70
|
export { CheckCache } from './core/constraints/check-cache';
|
|
73
71
|
export type { CheckCacheConfig, CheckSamplingConfig } from './core/constraints/check-cache';
|
|
74
|
-
export { renderConstraintsByTrigger } from './core/constraints/agent-prompt-renderer';
|
|
75
|
-
export type { RenderConstraintsByTriggerOptions } from './core/constraints/agent-prompt-renderer';
|
|
76
|
-
export {
|
|
77
|
-
CONSTRAINTS_START_MARKER,
|
|
78
|
-
CONSTRAINTS_END_MARKER,
|
|
79
|
-
renderConstraintsSection,
|
|
80
|
-
} from './core/constraints/injection-renderer';
|
|
81
72
|
|
|
82
73
|
// ========================================
|
|
83
74
|
// 检查点与验证器
|
|
@@ -373,7 +364,6 @@ export type {
|
|
|
373
364
|
export type {
|
|
374
365
|
ProjectConfig,
|
|
375
366
|
MergedConstraintsConfig,
|
|
376
|
-
CustomConstraintDefinition,
|
|
377
367
|
GovernanceConfig,
|
|
378
368
|
CapabilitiesConfig,
|
|
379
369
|
ChangelogConfig,
|
|
@@ -42,7 +42,7 @@ describe('TraceCollector', () => {
|
|
|
42
42
|
const disabledCollector = new TraceCollector({ enabled: false });
|
|
43
43
|
disabledCollector.record({
|
|
44
44
|
constraintId: 'test',
|
|
45
|
-
|
|
45
|
+
severity: 'error',
|
|
46
46
|
timestamp: Date.now(),
|
|
47
47
|
result: 'pass',
|
|
48
48
|
});
|
|
@@ -54,7 +54,7 @@ describe('TraceCollector', () => {
|
|
|
54
54
|
it('should append trace to file', () => {
|
|
55
55
|
collector.record({
|
|
56
56
|
constraintId: 'test',
|
|
57
|
-
|
|
57
|
+
severity: 'error',
|
|
58
58
|
timestamp: Date.now(),
|
|
59
59
|
result: 'pass',
|
|
60
60
|
});
|
|
@@ -64,7 +64,7 @@ describe('TraceCollector', () => {
|
|
|
64
64
|
|
|
65
65
|
describe('recordPass()', () => {
|
|
66
66
|
it('should record pass trace', () => {
|
|
67
|
-
collector.recordPass('test_constraint', '
|
|
67
|
+
collector.recordPass('test_constraint', 'error');
|
|
68
68
|
const call = mockFs.appendFileSync.mock.calls[0];
|
|
69
69
|
const content = JSON.parse(call[1] as string);
|
|
70
70
|
expect(content.result).toBe('pass');
|
|
@@ -73,7 +73,7 @@ describe('TraceCollector', () => {
|
|
|
73
73
|
|
|
74
74
|
describe('recordFail()', () => {
|
|
75
75
|
it('should record fail trace', () => {
|
|
76
|
-
collector.recordFail('test_constraint', '
|
|
76
|
+
collector.recordFail('test_constraint', 'error');
|
|
77
77
|
const call = mockFs.appendFileSync.mock.calls[0];
|
|
78
78
|
const content = JSON.parse(call[1] as string);
|
|
79
79
|
expect(content.result).toBe('fail');
|
|
@@ -91,8 +91,8 @@ describe('TraceCollector', () => {
|
|
|
91
91
|
|
|
92
92
|
it('should parse all traces from file', () => {
|
|
93
93
|
mockFs.readFileSync.mockReturnValueOnce(
|
|
94
|
-
'{"constraintId":"a","
|
|
95
|
-
'{"constraintId":"b","
|
|
94
|
+
'{"constraintId":"a","severity":"error","timestamp":100,"result":"pass"}\n' +
|
|
95
|
+
'{"constraintId":"b","severity":"warning","timestamp":200,"result":"fail"}\n'
|
|
96
96
|
);
|
|
97
97
|
const traces = collector.read();
|
|
98
98
|
expect(traces.length).toBe(2);
|
|
@@ -100,19 +100,19 @@ describe('TraceCollector', () => {
|
|
|
100
100
|
|
|
101
101
|
it('should apply constraintId filter', () => {
|
|
102
102
|
mockFs.readFileSync.mockReturnValueOnce(
|
|
103
|
-
'{"constraintId":"a","
|
|
104
|
-
'{"constraintId":"b","
|
|
103
|
+
'{"constraintId":"a","severity":"error","timestamp":100,"result":"pass"}\n' +
|
|
104
|
+
'{"constraintId":"b","severity":"warning","timestamp":200,"result":"fail"}\n'
|
|
105
105
|
);
|
|
106
106
|
const traces = collector.read({ constraintId: 'a' });
|
|
107
107
|
expect(traces.length).toBe(1);
|
|
108
108
|
});
|
|
109
109
|
|
|
110
|
-
it('should apply
|
|
110
|
+
it('should apply severity filter', () => {
|
|
111
111
|
mockFs.readFileSync.mockReturnValueOnce(
|
|
112
|
-
'{"constraintId":"a","
|
|
113
|
-
'{"constraintId":"b","
|
|
112
|
+
'{"constraintId":"a","severity":"error","timestamp":100,"result":"pass"}\n' +
|
|
113
|
+
'{"constraintId":"b","severity":"warning","timestamp":200,"result":"fail"}\n'
|
|
114
114
|
);
|
|
115
|
-
const traces = collector.read({
|
|
115
|
+
const traces = collector.read({ severity: 'warning' });
|
|
116
116
|
expect(traces.length).toBe(1);
|
|
117
117
|
});
|
|
118
118
|
});
|
|
@@ -121,7 +121,7 @@ describe('TraceCollector', () => {
|
|
|
121
121
|
it('should read recent traces', () => {
|
|
122
122
|
const now = Date.now();
|
|
123
123
|
mockFs.readFileSync.mockReturnValueOnce(
|
|
124
|
-
`{"constraintId":"new","
|
|
124
|
+
`{"constraintId":"new","severity":"error","timestamp":${now - 1000},"result":"pass"}\n`
|
|
125
125
|
);
|
|
126
126
|
const traces = collector.readRecent(1);
|
|
127
127
|
expect(traces.length).toBe(1);
|
|
@@ -131,7 +131,7 @@ describe('TraceCollector', () => {
|
|
|
131
131
|
describe('readByConstraint()', () => {
|
|
132
132
|
it('should read by constraint', () => {
|
|
133
133
|
mockFs.readFileSync.mockReturnValueOnce(
|
|
134
|
-
'{"constraintId":"a","
|
|
134
|
+
'{"constraintId":"a","severity":"error","timestamp":100,"result":"pass"}\n'
|
|
135
135
|
);
|
|
136
136
|
const traces = collector.readByConstraint('a');
|
|
137
137
|
expect(traces.length).toBe(1);
|
|
@@ -143,7 +143,7 @@ describe('TraceCollector', () => {
|
|
|
143
143
|
mockFs.statSync.mockReturnValueOnce({ size: 11 * 1024 * 1024 } as any);
|
|
144
144
|
collector.record({
|
|
145
145
|
constraintId: 'test',
|
|
146
|
-
|
|
146
|
+
severity: 'error',
|
|
147
147
|
timestamp: Date.now(),
|
|
148
148
|
result: 'pass',
|
|
149
149
|
});
|
|
@@ -155,7 +155,7 @@ describe('TraceCollector', () => {
|
|
|
155
155
|
it('should return stats', () => {
|
|
156
156
|
mockFs.existsSync.mockReturnValueOnce(true);
|
|
157
157
|
mockFs.readFileSync.mockReturnValueOnce(
|
|
158
|
-
'{"constraintId":"a","
|
|
158
|
+
'{"constraintId":"a","severity":"error","timestamp":100,"result":"pass"}\n'
|
|
159
159
|
);
|
|
160
160
|
mockFs.statSync.mockReturnValueOnce({ size: 1024 } as any);
|
|
161
161
|
const stats = collector.getStats();
|
|
@@ -166,9 +166,9 @@ describe('TraceCollector', () => {
|
|
|
166
166
|
describe('坏行容错(harness#82 裁决 4:原「抛」改 skip,签名与返回类型不变)', () => {
|
|
167
167
|
it('read() 单行损坏只丢该行,合法 trace 照常返回', () => {
|
|
168
168
|
mockFs.readFileSync.mockReturnValueOnce(
|
|
169
|
-
'{"constraintId":"a","
|
|
169
|
+
'{"constraintId":"a","severity":"error","timestamp":100,"result":"pass"}\n' +
|
|
170
170
|
'{"constraintId":"b","broken"\n' +
|
|
171
|
-
'{"constraintId":"c","
|
|
171
|
+
'{"constraintId":"c","severity":"warning","timestamp":200,"result":"fail"}\n'
|
|
172
172
|
);
|
|
173
173
|
const traces = collector.read();
|
|
174
174
|
expect(traces.map(t => t.constraintId)).toEqual(['a', 'c']);
|
|
@@ -176,9 +176,9 @@ describe('TraceCollector', () => {
|
|
|
176
176
|
|
|
177
177
|
it('read() 坏行不阻断过滤', () => {
|
|
178
178
|
mockFs.readFileSync.mockReturnValueOnce(
|
|
179
|
-
'{"constraintId":"a","
|
|
179
|
+
'{"constraintId":"a","severity":"error","timestamp":100,"result":"pass"}\n' +
|
|
180
180
|
'not-json\n' +
|
|
181
|
-
'{"constraintId":"a","
|
|
181
|
+
'{"constraintId":"a","severity":"error","timestamp":200,"result":"fail"}\n'
|
|
182
182
|
);
|
|
183
183
|
const traces = collector.read({ constraintId: 'a' });
|
|
184
184
|
expect(traces).toHaveLength(2);
|
|
@@ -187,8 +187,8 @@ describe('TraceCollector', () => {
|
|
|
187
187
|
it('getStats() 首末行坏时不抛:totalLines 保持原始行数,时间戳取合法记录', () => {
|
|
188
188
|
mockFs.readFileSync.mockReturnValueOnce(
|
|
189
189
|
'not-json\n' +
|
|
190
|
-
'{"constraintId":"a","
|
|
191
|
-
'{"constraintId":"b","
|
|
190
|
+
'{"constraintId":"a","severity":"error","timestamp":100,"result":"pass"}\n' +
|
|
191
|
+
'{"constraintId":"b","severity":"warning","timestamp":200,"result":"fail"}\n' +
|
|
192
192
|
'also-bad\n'
|
|
193
193
|
);
|
|
194
194
|
mockFs.statSync.mockReturnValueOnce({ size: 2048 } as any);
|
|
@@ -94,8 +94,8 @@ export function summarizeTraces(traces: ExecutionTrace[]): TraceSummary[] {
|
|
|
94
94
|
const summaries: TraceSummary[] = [];
|
|
95
95
|
|
|
96
96
|
for (const [constraintId, group] of grouped) {
|
|
97
|
-
//
|
|
98
|
-
const
|
|
97
|
+
// 提取严重性(从第一条 trace)
|
|
98
|
+
const severity = group[0].severity;
|
|
99
99
|
|
|
100
100
|
// 计算时间范围
|
|
101
101
|
const timeRange = timeRangeOf(group.map(t => t.timestamp));
|
|
@@ -120,7 +120,7 @@ export function summarizeTraces(traces: ExecutionTrace[]): TraceSummary[] {
|
|
|
120
120
|
|
|
121
121
|
summaries.push({
|
|
122
122
|
constraintId,
|
|
123
|
-
|
|
123
|
+
severity,
|
|
124
124
|
timeRange,
|
|
125
125
|
totalChecks,
|
|
126
126
|
passCount,
|
|
@@ -155,7 +155,7 @@ export function detectTraceAnomalies(
|
|
|
155
155
|
anomalies.push({
|
|
156
156
|
type: 'rising_fail_rate',
|
|
157
157
|
constraintId: summary.constraintId,
|
|
158
|
-
|
|
158
|
+
severity: summary.severity,
|
|
159
159
|
message: `约束 ${summary.constraintId} 失败率 ${Math.round(summary.failRate * 100)}% 且趋势上升`,
|
|
160
160
|
data: {
|
|
161
161
|
currentRate: summary.failRate,
|
|
@@ -172,7 +172,7 @@ export function detectTraceAnomalies(
|
|
|
172
172
|
anomalies.push({
|
|
173
173
|
type: 'low_pass_rate',
|
|
174
174
|
constraintId: summary.constraintId,
|
|
175
|
-
|
|
175
|
+
severity: summary.severity,
|
|
176
176
|
message: `约束 ${summary.constraintId} 通过率 ${Math.round(summary.passRate * 100)}%,低于 30%`,
|
|
177
177
|
data: {
|
|
178
178
|
currentRate: summary.passRate,
|
|
@@ -358,14 +358,13 @@ export class TraceAnalyzer {
|
|
|
358
358
|
lines.push('');
|
|
359
359
|
|
|
360
360
|
for (const summary of summaries) {
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
lines.push(`${levelEmoji} **${summary.constraintId}**`);
|
|
361
|
+
const severityEmoji = {
|
|
362
|
+
error: '🔴',
|
|
363
|
+
warning: '🟡',
|
|
364
|
+
info: '🔵',
|
|
365
|
+
}[summary.severity];
|
|
366
|
+
|
|
367
|
+
lines.push(`${severityEmoji} **${summary.constraintId}**`);
|
|
369
368
|
lines.push(` - Checks: ${summary.totalChecks}`);
|
|
370
369
|
lines.push(` - Pass: ${Math.round(summary.passRate * 100)}%`);
|
|
371
370
|
lines.push(` - Fail: ${Math.round(summary.failRate * 100)}%`);
|