@dommaker/harness 1.9.0 → 1.11.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 +17 -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.d.ts.map +1 -1
- package/dist/cli/commands/definitions.js +7 -12
- package/dist/cli/commands/definitions.js.map +1 -1
- 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 +1 -3
- package/dist/cli/commands/scaffold-templates.d.ts.map +1 -1
- package/dist/cli/commands/scaffold-templates.js +2 -53
- 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 +6 -8
- package/dist/core/constraints/usage-report.d.ts.map +1 -1
- package/dist/core/constraints/usage-report.js +6 -11
- 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 +9 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -18
- package/dist/index.js.map +1 -1
- package/dist/knowledge/lint.d.ts +17 -0
- package/dist/knowledge/lint.d.ts.map +1 -1
- package/dist/knowledge/lint.js +52 -0
- package/dist/knowledge/lint.js.map +1 -1
- package/dist/knowledge/store.d.ts +7 -0
- package/dist/knowledge/store.d.ts.map +1 -1
- package/dist/knowledge/store.js +48 -13
- package/dist/knowledge/store.js.map +1 -1
- package/dist/knowledge/types.d.ts +5 -1
- package/dist/knowledge/types.d.ts.map +1 -1
- package/dist/knowledge/types.js +5 -1
- package/dist/knowledge/types.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 +8 -7
- package/src/__tests__/public-type-surface.test.ts +7 -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/__tests__/usage-report-public-export.test.ts +21 -0
- package/src/cli/commands/CONTEXT.md +12 -12
- 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-json-flag.test.ts +101 -0
- 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 +13 -21
- package/src/cli/commands/__tests__/init.test.ts +1 -16
- package/src/cli/commands/__tests__/numeric-flag-assembly.test.ts +1 -1
- package/src/cli/commands/__tests__/passes-gate-dead-options.test.ts +1 -1
- package/src/cli/commands/__tests__/project-path-anchoring.test.ts +2 -2
- package/src/cli/commands/__tests__/registry.test.ts +1 -1
- 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 +7 -12
- package/src/cli/commands/init.ts +8 -179
- package/src/cli/commands/report.ts +19 -23
- package/src/cli/commands/scaffold-templates.ts +1 -53
- 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 +9 -16
- 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 +28 -18
- package/src/knowledge/CONTEXT.md +4 -3
- package/src/knowledge/__tests__/lint.test.ts +84 -0
- package/src/knowledge/__tests__/store.test.ts +70 -0
- package/src/knowledge/lint.ts +60 -1
- package/src/knowledge/store.ts +48 -11
- package/src/knowledge/types.ts +7 -1
- package/src/monitoring/CONTEXT.md +1 -1
- 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/cli/commands/posteval-plan.d.ts +0 -15
- package/dist/cli/commands/posteval-plan.d.ts.map +0 -1
- package/dist/cli/commands/posteval-plan.js +0 -86
- package/dist/cli/commands/posteval-plan.js.map +0 -1
- 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/cli/commands/__tests__/posteval-plan.test.ts +0 -204
- package/src/cli/commands/posteval-plan.ts +0 -98
- 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,
|
|
@@ -56,6 +54,26 @@ export {
|
|
|
56
54
|
} from './core/effective-constraints';
|
|
57
55
|
export type { EffectiveConfigLint } from './core/effective-constraints';
|
|
58
56
|
|
|
57
|
+
// ========================================
|
|
58
|
+
// 约束使用报告(退役候选诊断数据层,studio#602 E1 (a) 链路消费)
|
|
59
|
+
// ========================================
|
|
60
|
+
export {
|
|
61
|
+
buildConstraintsUsageReport,
|
|
62
|
+
diagnoseRetireCandidates,
|
|
63
|
+
collectUsageByConstraint,
|
|
64
|
+
readProjectTraces,
|
|
65
|
+
readProjectTracesReport,
|
|
66
|
+
CANDIDATE_KIND_LABEL,
|
|
67
|
+
DEFAULT_DIAGNOSE_THRESHOLDS,
|
|
68
|
+
} from './core/constraints/usage-report';
|
|
69
|
+
export type {
|
|
70
|
+
ConstraintUsageStats,
|
|
71
|
+
RetireCandidate,
|
|
72
|
+
RetireCandidateKind,
|
|
73
|
+
DiagnoseThresholds,
|
|
74
|
+
ConstraintsUsageReport,
|
|
75
|
+
} from './core/constraints/usage-report';
|
|
76
|
+
|
|
59
77
|
// ========================================
|
|
60
78
|
// 约束检查引擎(便捷 API;options 统一签名,ADR-0003)
|
|
61
79
|
// ========================================
|
|
@@ -67,17 +85,10 @@ export {
|
|
|
67
85
|
export type { CheckConstraintsOptions } from './core/constraints/checker';
|
|
68
86
|
|
|
69
87
|
// ========================================
|
|
70
|
-
//
|
|
88
|
+
// 约束缓存
|
|
71
89
|
// ========================================
|
|
72
90
|
export { CheckCache } from './core/constraints/check-cache';
|
|
73
91
|
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
92
|
|
|
82
93
|
// ========================================
|
|
83
94
|
// 检查点与验证器
|
|
@@ -373,7 +384,6 @@ export type {
|
|
|
373
384
|
export type {
|
|
374
385
|
ProjectConfig,
|
|
375
386
|
MergedConstraintsConfig,
|
|
376
|
-
CustomConstraintDefinition,
|
|
377
387
|
GovernanceConfig,
|
|
378
388
|
CapabilitiesConfig,
|
|
379
389
|
ChangelogConfig,
|
package/src/knowledge/CONTEXT.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
- 所有模式支持 `decayAt` 硬过期
|
|
18
18
|
|
|
19
19
|
## 核心导出
|
|
20
|
-
- `KnowledgeStore`(类型面)/ `FileKnowledgeStore`(`store.ts`,barrel 与包根交出的**值**符号——store 的文件系统实现,构造收 `{ baseDir }`;仓内构造点两处:`cli/commands/knowledge-view.ts` 的 `openKnowledgeStore()`(knowledge 各子命令的取数口)与 `cli/commands/constraints-retire.ts` 的退役沉淀写入)— 知识条目 CRUD + 结构化存储;`saveAll()` 全量批量写入(循环内只更新内存索引、结束一次 writeIndex,harness#107)、`applyAll()` 按 id 部分更新的批量出口(`update(id, partial)` 的批量形,语义含未知 id 逐条跳过与空批零读写,harness#134)、`getConsumptionStats()` 读 `.consumption-stats.json` 的当日消费计数(打分核心唯一的消费统计取数口,harness#134
|
|
20
|
+
- `KnowledgeStore`(类型面)/ `FileKnowledgeStore`(`store.ts`,barrel 与包根交出的**值**符号——store 的文件系统实现,构造收 `{ baseDir }`;仓内构造点两处:`cli/commands/knowledge-view.ts` 的 `openKnowledgeStore()`(knowledge 各子命令的取数口)与 `cli/commands/constraints-retire.ts` 的退役沉淀写入)— 知识条目 CRUD + 结构化存储;`saveAll()` 全量批量写入(循环内只更新内存索引、结束一次 writeIndex,harness#107)、`applyAll()` 按 id 部分更新的批量出口(`update(id, partial)` 的批量形,语义含未知 id 逐条跳过与空批零读写,harness#134)、`getConsumptionStats()` 读 `.consumption-stats.json` 的当日消费计数(打分核心唯一的消费统计取数口,harness#134)。**写入闸(E1 复盘修正 M1)**:save/saveAll/applyAll 落盘前校验 maturity/layer 枚举(update 经 save 继承),未声明值拒写抛错不静默降级;值域正本 = `types.ts` 的 `MATURITY_LEVELS`/`STORAGE_LAYERS` 常量(不进 barrel)。批量路径先整批预校验再落盘(任一脏值全批拒写);applyAll 批内同 id 多条按序累积合并语义不变
|
|
21
21
|
- `KnowledgeQuery` — 语义搜索 + 类型/标签过滤 + `queryByMode()` + `consume(taskContext)`;`query()` 是 budget 截断管线(仅用于 prompt 注入),`search()` 是全语料文本搜索(先匹配→排序→limit,交互搜索专用,勿用 query() 代替)
|
|
22
22
|
- `KnowledgeLifecycle` — per-mode 生命周期管理;缺省阈值常量 `DEFAULT_DECAY_CONFIG`(`provenDecayMonths: 12` / `verifiedDecayMonths: 6` / `draftDecayMonths: 3` / `autoPromoteSources: []`,正本 `types.ts`)经构造器 `{...DEFAULT_DECAY_CONFIG, ...config}` 合并,barrel 与包根一并交出
|
|
23
23
|
- `extractCodeStructure(dir)`(`primitives/code-structure.ts`,配 `CodeStructure`/`DeclarationInfo`/`ImportInfo` 三类型对外)— 目录代码结构提取;仓内零调用方,是留给下游的公开面
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
- `tree-walker`(包内,不进导出面)— 知识树排除口径单点(harness#134):`isEntryFile()` / `isInfraDir()` / `INDEX_MD_FILE` / `SNAPSHOTS_DIR`,由 store 顶层扫描、migration 顶层扫描、index-generator 递归扫描三处共同消费
|
|
30
30
|
- `flywheel-metrics`(包内,不进导出面)— 知识飞轮指标唯一实现:`evaluateFlywheel(env)` 出 canonical 比例、`genuineRefs()` 出 synthetic 过滤口径,audit D6 / `knowledge stats` / `knowledge health` 三处共消费(ADR-0013)
|
|
31
31
|
- `ReferenceTracker` — 知识引用关系图谱
|
|
32
|
-
- `KnowledgeLinter` — 知识质量检查(完整性/一致性/时效性)
|
|
32
|
+
- `KnowledgeLinter` — 知识质量检查(完整性/一致性/时效性);maturity/layer 枚举校验两个入口共用判定正本 `invalidEnumIssues`:`run()` 经 `checkInvalidEnums()` 扫存量(缺字段即脏、issue 带 entryId,`invalid_enum` 无 autoFix 分支——映射需人工裁定),`validateEntry()` 管摄入前(字段缺省跳过以兼容只传四字段的 pre-ingest 调用方)
|
|
33
33
|
- `ColdStartImporter` — 冷启动知识导入
|
|
34
34
|
- `KnowledgeHealthScorer` — 知识健康评分(doctor.ts)
|
|
35
35
|
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
- 知识条目文件的 frontmatter 语法只由 `src/utils/frontmatter` 定义(harness#89):缺头/空 meta = `absent`(合法输入,按非条目静默处理、不上报——migration 对 `absent` 不计 `errors`、不迁移,只计 `skipped` 保证 `total` 计数闭环,harness#161 对齐 #89 裁决 2);未闭合/YAML 非法 = `malformed`(必须显式上报后按消费方语义恢复——migration 落 `errors`、store 与 index-generator 打一行 stderr 后跳过或走 best-effort),禁止静默丢条目;canonical 字段序是 `store.toFrontmatter` 的私有策略,`join` 只管包裹格式
|
|
44
44
|
- **知识树的排除口径只有一个正本 `tree-walker`**(harness#134):`_index.md`(索引生成物)、`.snapshots`、`.archive` / `archived`、`resolutions` 是树基建、不是条目人口。store 与 migration 的顶层扫描、index-generator 的递归扫描三处都走它;新增 walker 禁止另立排除清单。统一的是**排除口径**不是遍历深度(store/migration 顶层、index-generator 递归)。例外须原地记名理由:`import.ts` 的 docs 扫描吃的是**项目文档树**,本口径在它那里没有对应物
|
|
45
45
|
- **循环内禁止逐条 `store.update()`**(harness#134):`update()` 的形状是 get→save、`save()` 每次全量重写 index.json,N 条修复 = N 次全量重写。批量形是 `applyAll(id → partial)`;一次 `audit --fix`、一轮 `runDecayCycle()`、一次 `updateReferencedBy()` 各只重写一次索引,计数闸见 `audit-write-count.test.ts` 与 `lifecycle.test.ts` / `reference-tracker.test.ts` 的 stringify 计数项
|
|
46
|
+
- **写入闸不管读**(E1 复盘修正 M1):枚举校验只在 save/saveAll/applyAll/update 落盘前执行,读取(parseFile)不校验——盘上脏条目可读可列,但任一写入路径触及即抛错(含「只改其他字段」的部分更新),先修脏值再写;绕过本 store 直写文件的外部写入方不受闸约束
|
|
46
47
|
- **审计判定脱离文件系统可测**(harness#134):规则表与 D1–D7 打分住 `audit-scoring.ts`,零 fs(源形状闸钉在 `audit-scoring.test.ts`);引擎 `audit.ts` 只做 store 装配,环境数据经 `store.getConsumptionStats()` / `getSurvivalRate()` 取好喂入
|
|
47
48
|
- 知识条目有明确的生命周期状态(按 consumptionMode 分化)
|
|
48
49
|
- 约束退役(`harness constraints retire`,人确认)时写入 KnowledgeStore:规则原文 + 退役原因 + 历史统计
|
|
@@ -56,7 +57,7 @@
|
|
|
56
57
|
## 注意事项
|
|
57
58
|
- Phase 1+4 实现的知识引擎核心
|
|
58
59
|
- 约束"退役不删除"——retire 落盘 config.yml `enabled: false` + retired 元数据,保留规则原文 + 退役原因 + 历史统计(可回滚)
|
|
59
|
-
- `MaturityLevel`
|
|
60
|
+
- `MaturityLevel` / `StorageLayer` 的值域不抄清单——运行时正本是 `types.ts` 的 `MATURITY_LEVELS` / `STORAGE_LAYERS` 常量(写入闸与 lint 枚举校验共用)
|
|
60
61
|
- `excludeArchived` 同时排除 archived 和 deprecated
|
|
61
62
|
- 仍有两处按**原始** `referencedBy.length` 判定,不属飞轮指标、ADR-0013 明确列为范围外:`lifecycle.ts` 的 signal 饱和 / reference 激活(退役阈值调整另票)、`knowledge health` 的 D1「verified 零引用」线索提示(逐条 issue 线索,非聚合分子)
|
|
62
63
|
- #134 收口后仍在的逐条写点(同型问题,本票裁决点名的循环之外,需要时另票):`lint.ts` 的三处 autoFix `store.update()`、`lifecycle.recordReference()`(逐事件调用,返回更新后条目并触发回调,批量化会改它的契约)、`ingest.mergeEntries()`;`store.list()` 每条一次 `findFile()` 线性扫索引的 O(N²) 按 #134 裁决**未动**,待把知识树实际规模重新量一次再判是否单开票
|
|
@@ -57,6 +57,7 @@ describe('KnowledgeLinter', () => {
|
|
|
57
57
|
expect(report.fixed).toBe(0);
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
+
|
|
60
61
|
it('应该检测所有问题类型', () => {
|
|
61
62
|
// 孤儿条目
|
|
62
63
|
saveEntry({ id: 'orphan-1', title: 'Orphan', maturity: 'draft', contributors: [], projects: [] });
|
|
@@ -79,6 +80,39 @@ describe('KnowledgeLinter', () => {
|
|
|
79
80
|
});
|
|
80
81
|
});
|
|
81
82
|
|
|
83
|
+
// M1 收口:run() 存量扫描必须报出 maturity/layer 脏值(spec:lint 能报出存量脏条目)
|
|
84
|
+
describe('run 存量枚举校验', () => {
|
|
85
|
+
/** 绕过写入闸直写脏条目(模拟实盘直写路径) */
|
|
86
|
+
function writeDirtyFile(name: string, fm: string) {
|
|
87
|
+
const dir = path.join(tmpDir, 'knowledge');
|
|
88
|
+
fs.writeFileSync(path.join(dir, name), `---\n${fm}\n---\n\nbody content long enough.\n`, 'utf-8');
|
|
89
|
+
store.rebuildIndex();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
it('盘上脏 maturity / 脏 layer 的存量条目 → run() 报 invalid_enum', () => {
|
|
93
|
+
writeDirtyFile('resolution-res_a.md',
|
|
94
|
+
'id: res_a\ntype: resolution\ntitle: Dirty M\nmaturity: pending\nlayer: project\ncreated: 2026-09-01T00:00:00.000Z');
|
|
95
|
+
writeDirtyFile('resolution-res_b.md',
|
|
96
|
+
'id: res_b\ntype: resolution\ntitle: Dirty L\nmaturity: draft\nlayer: L3_tool_behavior\ncreated: 2026-09-01T00:00:00.000Z');
|
|
97
|
+
writeDirtyFile('resolution-res_c.md',
|
|
98
|
+
'id: res_c\ntype: resolution\ntitle: Missing Layer\nmaturity: draft\ncreated: 2026-09-01T00:00:00.000Z');
|
|
99
|
+
|
|
100
|
+
const report = linter.run();
|
|
101
|
+
const enumIssues = report.issues.filter(i => i.type === 'invalid_enum');
|
|
102
|
+
expect(enumIssues).toHaveLength(3);
|
|
103
|
+
expect(enumIssues.map(i => i.entryId).sort()).toEqual(['res_a', 'res_b', 'res_c']);
|
|
104
|
+
expect(report.summary.invalid_enum).toBe(3);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('全合法条目库 → 零 invalid_enum(存量合法条目无新噪音)', () => {
|
|
108
|
+
saveEntry({ id: 'clean-1', maturity: 'verified', layer: 'project' });
|
|
109
|
+
saveEntry({ id: 'clean-2', maturity: 'active', layer: 'tech', title: 'Another' });
|
|
110
|
+
|
|
111
|
+
const report = linter.run();
|
|
112
|
+
expect(report.summary.invalid_enum).toBe(0);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
82
116
|
describe('checkIndexConsistency', () => {
|
|
83
117
|
it('应该检测索引中有但文件不存在的条目', () => {
|
|
84
118
|
saveEntry({ id: 'entry-1' });
|
|
@@ -370,6 +404,56 @@ describe('KnowledgeLinter', () => {
|
|
|
370
404
|
});
|
|
371
405
|
expect(issues.some(i => i.type === 'duplicate')).toBe(true);
|
|
372
406
|
});
|
|
407
|
+
|
|
408
|
+
// E1 复盘修正 M1:maturity/layer 枚举值域校验(实盘脏值:pending/canonical/L3_tool_behavior)
|
|
409
|
+
it('flags undeclared maturity value', () => {
|
|
410
|
+
const issues = linter.validateEntry({
|
|
411
|
+
title: 'Specific Entry With Valid Title',
|
|
412
|
+
content: 'This is a sufficiently long content for the enum check test case.',
|
|
413
|
+
tags: [],
|
|
414
|
+
type: 'decision',
|
|
415
|
+
maturity: 'pending',
|
|
416
|
+
});
|
|
417
|
+
const issue = issues.find(i => i.type === 'invalid_enum');
|
|
418
|
+
expect(issue).toBeDefined();
|
|
419
|
+
expect(issue!.severity).toBe('high');
|
|
420
|
+
expect(issue!.description).toContain('pending');
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it('flags undeclared layer value', () => {
|
|
424
|
+
const issues = linter.validateEntry({
|
|
425
|
+
title: 'Specific Entry With Valid Title',
|
|
426
|
+
content: 'This is a sufficiently long content for the enum check test case.',
|
|
427
|
+
tags: [],
|
|
428
|
+
type: 'decision',
|
|
429
|
+
layer: 'L3_tool_behavior',
|
|
430
|
+
});
|
|
431
|
+
const issue = issues.find(i => i.type === 'invalid_enum');
|
|
432
|
+
expect(issue).toBeDefined();
|
|
433
|
+
expect(issue!.description).toContain('L3_tool_behavior');
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
it('accepts declared maturity/layer values', () => {
|
|
437
|
+
const issues = linter.validateEntry({
|
|
438
|
+
title: 'Specific Entry With Valid Title',
|
|
439
|
+
content: 'This is a sufficiently long content for the enum check test case.',
|
|
440
|
+
tags: [],
|
|
441
|
+
type: 'decision',
|
|
442
|
+
maturity: 'verified',
|
|
443
|
+
layer: 'project',
|
|
444
|
+
});
|
|
445
|
+
expect(issues.filter(i => i.type === 'invalid_enum')).toHaveLength(0);
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
it('skips enum check when maturity/layer are not provided (pre-ingest caller compat)', () => {
|
|
449
|
+
const issues = linter.validateEntry({
|
|
450
|
+
title: 'Specific Entry With Valid Title',
|
|
451
|
+
content: 'This is a sufficiently long content for the enum check test case.',
|
|
452
|
+
tags: [],
|
|
453
|
+
type: 'decision',
|
|
454
|
+
});
|
|
455
|
+
expect(issues.filter(i => i.type === 'invalid_enum')).toHaveLength(0);
|
|
456
|
+
});
|
|
373
457
|
});
|
|
374
458
|
|
|
375
459
|
describe('autoFix', () => {
|
|
@@ -174,6 +174,76 @@ describe('KnowledgeStore', () => {
|
|
|
174
174
|
});
|
|
175
175
|
});
|
|
176
176
|
|
|
177
|
+
// 写入闸(E1 复盘修正 M1):maturity/layer 是声明枚举,未声明值拒写报错,不静默降级
|
|
178
|
+
describe('schema gate: maturity/layer 枚举校验', () => {
|
|
179
|
+
it('save 拒绝未声明的 maturity 值,不落盘', () => {
|
|
180
|
+
expect(() => store.save(makeEntry({ maturity: 'pending' as KnowledgeEntry['maturity'] })))
|
|
181
|
+
.toThrow(/maturity.*pending/);
|
|
182
|
+
expect(store.get('DEC-001')).toBeUndefined();
|
|
183
|
+
expect(store.readIndex()).toHaveLength(0);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('save 拒绝未声明的 layer 值,不落盘', () => {
|
|
187
|
+
expect(() => store.save(makeEntry({ layer: 'L3_tool_behavior' as KnowledgeEntry['layer'] })))
|
|
188
|
+
.toThrow(/layer.*L3_tool_behavior/);
|
|
189
|
+
expect(store.get('DEC-001')).toBeUndefined();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('save 拒绝缺失 layer 的条目', () => {
|
|
193
|
+
const entry = makeEntry();
|
|
194
|
+
delete (entry as Partial<KnowledgeEntry>).layer;
|
|
195
|
+
expect(() => store.save(entry)).toThrow(/layer/);
|
|
196
|
+
expect(store.get('DEC-001')).toBeUndefined();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('update 传入脏 maturity 值 → 拒写,既有条目不变', () => {
|
|
200
|
+
store.save(makeEntry());
|
|
201
|
+
expect(() => store.update('DEC-001', { maturity: 'canonical' as KnowledgeEntry['maturity'] }))
|
|
202
|
+
.toThrow(/maturity.*canonical/);
|
|
203
|
+
expect(store.get('DEC-001')!.maturity).toBe('draft');
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('存量脏条目(绕过闸直写文件)经 update 修补其他字段 → 拒写报错', () => {
|
|
207
|
+
// 模拟实盘脏条目:resolution.service 直写文件的形状(maturity/layer 均未声明)
|
|
208
|
+
fs.writeFileSync(
|
|
209
|
+
path.join(tempDir, 'resolution-res_1.md'),
|
|
210
|
+
'---\nid: res_1\ntype: resolution\ntitle: Dirty\nmaturity: pending\nlayer: L3_tool_behavior\ncreated: 2026-09-01T00:00:00.000Z\n---\n\nbody\n',
|
|
211
|
+
'utf-8'
|
|
212
|
+
);
|
|
213
|
+
store.rebuildIndex();
|
|
214
|
+
expect(() => store.update('res_1', { tags: ['touched'] })).toThrow(/maturity.*pending/);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('存量脏条目经 update 把脏字段修成合法值 → 放行(清洗路径)', () => {
|
|
218
|
+
fs.writeFileSync(
|
|
219
|
+
path.join(tempDir, 'resolution-res_2.md'),
|
|
220
|
+
'---\nid: res_2\ntype: resolution\ntitle: Dirty\nmaturity: pending\nlayer: L3_tool_behavior\ncreated: 2026-09-01T00:00:00.000Z\n---\n\nbody\n',
|
|
221
|
+
'utf-8'
|
|
222
|
+
);
|
|
223
|
+
store.rebuildIndex();
|
|
224
|
+
const updated = store.update('res_2', { maturity: 'draft', layer: 'project' });
|
|
225
|
+
expect(updated!.maturity).toBe('draft');
|
|
226
|
+
expect(store.get('res_2')!.layer).toBe('project');
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('saveAll 含一条脏值 → 整批拒写(合法条目也不落盘)', () => {
|
|
230
|
+
expect(() => store.saveAll([
|
|
231
|
+
makeEntry({ id: 'DEC-001' }),
|
|
232
|
+
makeEntry({ id: 'DEC-002', layer: 'process' as KnowledgeEntry['layer'] }),
|
|
233
|
+
])).toThrow(/layer.*process/);
|
|
234
|
+
expect(store.get('DEC-001')).toBeUndefined();
|
|
235
|
+
expect(store.readIndex()).toHaveLength(0);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('applyAll 部分更新把 maturity 改成脏值 → 拒写', () => {
|
|
239
|
+
store.save(makeEntry());
|
|
240
|
+
expect(() => store.applyAll([
|
|
241
|
+
{ id: 'DEC-001', partial: { maturity: 'pending' as KnowledgeEntry['maturity'] } },
|
|
242
|
+
])).toThrow(/maturity.*pending/);
|
|
243
|
+
expect(store.get('DEC-001')!.maturity).toBe('draft');
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
177
247
|
describe('rebuildIndex', () => {
|
|
178
248
|
it('should rebuild index from files', () => {
|
|
179
249
|
store.save(makeEntry({ id: 'DEC-001' }));
|