@dommaker/harness 1.1.1 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.md +2 -4
- package/bin/harness.js +12 -37
- package/dist/cli/commands/check.d.ts +2 -2
- package/dist/cli/commands/check.d.ts.map +1 -1
- package/dist/cli/commands/check.js +8 -33
- package/dist/cli/commands/check.js.map +1 -1
- package/dist/cli/commands/constraints-report.js +1 -1
- package/dist/cli/commands/constraints-report.js.map +1 -1
- package/dist/cli/commands/constraints-retire.d.ts +6 -3
- package/dist/cli/commands/constraints-retire.d.ts.map +1 -1
- package/dist/cli/commands/constraints-retire.js +34 -25
- package/dist/cli/commands/constraints-retire.js.map +1 -1
- package/dist/cli/commands/definitions.d.ts +2 -0
- package/dist/cli/commands/definitions.d.ts.map +1 -1
- package/dist/cli/commands/definitions.js.map +1 -1
- package/dist/cli/commands/init.d.ts +21 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +118 -40
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/knowledge.d.ts.map +1 -1
- package/dist/cli/commands/knowledge.js +1 -12
- package/dist/cli/commands/knowledge.js.map +1 -1
- package/dist/cli/commands/report.d.ts.map +1 -1
- package/dist/cli/commands/report.js +0 -2
- package/dist/cli/commands/report.js.map +1 -1
- package/dist/cli/commands/status.js +2 -2
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/context/index.d.ts +9 -6
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +18 -20
- package/dist/context/index.js.map +1 -1
- package/dist/core/constraints/capabilities-parser.d.ts +3 -2
- package/dist/core/constraints/capabilities-parser.d.ts.map +1 -1
- package/dist/core/constraints/capabilities-parser.js +25 -47
- package/dist/core/constraints/capabilities-parser.js.map +1 -1
- package/dist/core/constraints/checker.d.ts +29 -32
- package/dist/core/constraints/checker.d.ts.map +1 -1
- package/dist/core/constraints/checker.js +34 -126
- package/dist/core/constraints/checker.js.map +1 -1
- package/dist/core/constraints/checkers/governance-presence.d.ts +20 -0
- package/dist/core/constraints/checkers/governance-presence.d.ts.map +1 -0
- package/dist/core/constraints/checkers/governance-presence.js +76 -0
- package/dist/core/constraints/checkers/governance-presence.js.map +1 -0
- package/dist/core/constraints/checkers/index.d.ts +2 -1
- package/dist/core/constraints/checkers/index.d.ts.map +1 -1
- package/dist/core/constraints/checkers/index.js +5 -0
- package/dist/core/constraints/checkers/index.js.map +1 -1
- package/dist/core/constraints/checkers/types.d.ts +25 -0
- package/dist/core/constraints/checkers/types.d.ts.map +1 -1
- package/dist/core/constraints/checkers/types.js +24 -0
- package/dist/core/constraints/checkers/types.js.map +1 -1
- package/dist/core/constraints/definitions/guidelines.d.ts.map +1 -1
- package/dist/core/constraints/definitions/guidelines.js +21 -2
- package/dist/core/constraints/definitions/guidelines.js.map +1 -1
- package/dist/core/constraints/definitions/prompts.d.ts.map +1 -1
- package/dist/core/constraints/definitions/prompts.js +0 -5
- package/dist/core/constraints/definitions/prompts.js.map +1 -1
- package/dist/core/constraints/index.d.ts +1 -2
- package/dist/core/constraints/index.d.ts.map +1 -1
- package/dist/core/constraints/index.js +1 -7
- package/dist/core/constraints/index.js.map +1 -1
- package/dist/core/constraints/injection-drift.d.ts +29 -4
- package/dist/core/constraints/injection-drift.d.ts.map +1 -1
- package/dist/core/constraints/injection-drift.js +52 -20
- package/dist/core/constraints/injection-drift.js.map +1 -1
- package/dist/core/constraints/usage-report.d.ts +1 -2
- package/dist/core/constraints/usage-report.d.ts.map +1 -1
- package/dist/core/constraints/usage-report.js +2 -5
- package/dist/core/constraints/usage-report.js.map +1 -1
- package/dist/core/effective-constraints.d.ts +24 -4
- package/dist/core/effective-constraints.d.ts.map +1 -1
- package/dist/core/effective-constraints.js +28 -7
- package/dist/core/effective-constraints.js.map +1 -1
- package/dist/core/effective-set.d.ts +30 -0
- package/dist/core/effective-set.d.ts.map +1 -0
- package/dist/core/effective-set.js +35 -0
- package/dist/core/effective-set.js.map +1 -0
- package/dist/core/index.d.ts +13 -6
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +44 -21
- package/dist/core/index.js.map +1 -1
- package/dist/core/project-config-loader.d.ts +3 -6
- package/dist/core/project-config-loader.d.ts.map +1 -1
- package/dist/core/project-config-loader.js +47 -61
- package/dist/core/project-config-loader.js.map +1 -1
- package/dist/failure/constraint-handler.d.ts +1 -1
- package/dist/failure/constraint-handler.js +1 -1
- package/dist/gates/checker-gate.d.ts +1 -4
- package/dist/gates/checker-gate.d.ts.map +1 -1
- package/dist/gates/checker-gate.js +3 -11
- package/dist/gates/checker-gate.js.map +1 -1
- package/dist/gates/definitions.d.ts +9 -38
- package/dist/gates/definitions.d.ts.map +1 -1
- package/dist/gates/definitions.js +24 -13
- package/dist/gates/definitions.js.map +1 -1
- package/dist/gates/effective-gates.d.ts.map +1 -1
- package/dist/gates/effective-gates.js +14 -18
- package/dist/gates/effective-gates.js.map +1 -1
- package/dist/gates/index.d.ts +1 -1
- package/dist/gates/index.d.ts.map +1 -1
- package/dist/gates/index.js.map +1 -1
- package/dist/hooks/bootstrap.d.ts.map +1 -1
- package/dist/hooks/bootstrap.js +0 -3
- package/dist/hooks/bootstrap.js.map +1 -1
- package/dist/index.d.ts +43 -60
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +180 -143
- package/dist/index.js.map +1 -1
- package/dist/knowledge/query.d.ts +15 -0
- package/dist/knowledge/query.d.ts.map +1 -1
- package/dist/knowledge/query.js +23 -0
- package/dist/knowledge/query.js.map +1 -1
- package/dist/monitoring/analyzer-base.d.ts +0 -4
- package/dist/monitoring/analyzer-base.d.ts.map +1 -1
- package/dist/monitoring/analyzer-base.js +0 -21
- package/dist/monitoring/analyzer-base.js.map +1 -1
- package/dist/monitoring/index.d.ts +6 -12
- package/dist/monitoring/index.d.ts.map +1 -1
- package/dist/monitoring/index.js +13 -29
- package/dist/monitoring/index.js.map +1 -1
- package/dist/monitoring/trace-analyzer.d.ts.map +1 -1
- package/dist/monitoring/trace-analyzer.js +2 -68
- package/dist/monitoring/trace-analyzer.js.map +1 -1
- package/dist/monitoring/traces.d.ts +0 -4
- package/dist/monitoring/traces.d.ts.map +1 -1
- package/dist/monitoring/traces.js +0 -14
- package/dist/monitoring/traces.js.map +1 -1
- package/dist/presets/index.d.ts +2 -2
- package/dist/presets/index.d.ts.map +1 -1
- package/dist/presets/index.js +3 -4
- package/dist/presets/index.js.map +1 -1
- package/dist/presets/standard.d.ts +10 -22
- package/dist/presets/standard.d.ts.map +1 -1
- package/dist/presets/standard.js +15 -75
- package/dist/presets/standard.js.map +1 -1
- package/dist/types/constraint.d.ts +1 -29
- package/dist/types/constraint.d.ts.map +1 -1
- package/dist/types/constraint.js +1 -1
- package/dist/types/constraint.js.map +1 -1
- package/dist/types/index.d.ts +0 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +0 -4
- package/dist/types/index.js.map +1 -1
- package/dist/types/project-config.d.ts +4 -8
- package/dist/types/project-config.d.ts.map +1 -1
- package/dist/types/trace.d.ts +5 -18
- package/dist/types/trace.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/CONTEXT.md +4 -5
- package/src/__tests__/checker-extra.test.ts +33 -139
- package/src/__tests__/checker.test.ts +0 -376
- package/src/__tests__/governance-presence.test.ts +129 -0
- package/src/__tests__/iron-laws.test.ts +7 -7
- package/src/__tests__/project-config-loader-extra.test.ts +42 -29
- package/src/__tests__/public-exports.test.ts +138 -0
- package/src/__tests__/trace-analyzer-extra.test.ts +6 -86
- package/src/__tests__/trace-analyzer.test.ts +4 -5
- package/src/__tests__/trace-collector.test.ts +0 -11
- package/src/__tests__/traces.test.ts +3 -22
- package/src/cli/commands/CONTEXT.md +6 -3
- package/src/cli/commands/__tests__/check-skip-output.test.ts +10 -7
- package/src/cli/commands/__tests__/check.test.ts +29 -69
- package/src/cli/commands/__tests__/constraints-report.test.ts +3 -3
- package/src/cli/commands/__tests__/constraints-retire.test.ts +45 -4
- package/src/cli/commands/__tests__/init-injection.test.ts +202 -0
- package/src/cli/commands/__tests__/knowledge.test.ts +90 -1
- package/src/cli/commands/__tests__/registry.test.ts +8 -6
- package/src/cli/commands/__tests__/status-extra.test.ts +6 -6
- package/src/cli/commands/__tests__/status.test.ts +6 -6
- package/src/cli/commands/__tests__/sync-docs-agents.test.ts +28 -0
- package/src/cli/commands/check.ts +9 -35
- package/src/cli/commands/constraints-report.ts +1 -1
- package/src/cli/commands/constraints-retire.ts +38 -27
- package/src/cli/commands/definitions.ts +2 -0
- package/src/cli/commands/init.ts +129 -40
- package/src/cli/commands/knowledge.ts +1 -16
- package/src/cli/commands/report.ts +0 -2
- package/src/cli/commands/status.ts +2 -2
- package/src/context/index.ts +29 -6
- package/src/core/CONTEXT.md +4 -3
- package/src/core/__tests__/effective-set.test.ts +80 -0
- package/src/core/constraints/__tests__/injection-drift.test.ts +90 -0
- package/src/core/constraints/capabilities-parser.ts +43 -49
- package/src/core/constraints/checker.ts +51 -145
- package/src/core/constraints/checkers/governance-presence.ts +74 -0
- package/src/core/constraints/checkers/index.ts +4 -1
- package/src/core/constraints/checkers/types.ts +42 -0
- package/src/core/constraints/definitions/guidelines.ts +22 -2
- package/src/core/constraints/definitions/prompts.ts +0 -5
- package/src/core/constraints/index.ts +2 -17
- package/src/core/constraints/injection-drift.ts +60 -21
- package/src/core/constraints/usage-report.ts +5 -8
- package/src/core/effective-constraints.ts +35 -7
- package/src/core/effective-set.ts +56 -0
- package/src/core/index.ts +80 -6
- package/src/core/project-config-loader.ts +53 -68
- package/src/failure/CONTEXT.md +1 -1
- package/src/failure/constraint-handler.ts +1 -1
- package/src/gates/CONTEXT.md +3 -3
- package/src/gates/checker-gate.ts +4 -12
- package/src/gates/definitions.ts +27 -50
- package/src/gates/effective-gates.ts +14 -21
- package/src/gates/index.ts +1 -1
- package/src/hooks/bootstrap.ts +0 -3
- package/src/index.ts +384 -148
- package/src/knowledge/CONTEXT.md +2 -2
- package/src/knowledge/__tests__/query.test.ts +64 -0
- package/src/knowledge/query.ts +28 -0
- package/src/monitoring/CONTEXT.md +7 -13
- package/src/monitoring/__tests__/traces.test.ts +0 -10
- package/src/monitoring/analyzer-base.ts +0 -22
- package/src/monitoring/index.ts +6 -12
- package/src/monitoring/trace-analyzer.ts +2 -73
- package/src/monitoring/traces.ts +0 -20
- package/src/presets/__tests__/standard.test.ts +48 -69
- package/src/presets/index.ts +3 -5
- package/src/presets/standard.ts +14 -79
- package/src/spec/CONTEXT.md +5 -13
- package/src/types/constraint.ts +1 -51
- package/src/types/index.ts +0 -12
- package/src/types/project-config.ts +4 -10
- package/src/types/trace.ts +5 -30
- package/dist/core/constraints/interceptor.d.ts +0 -39
- package/dist/core/constraints/interceptor.d.ts.map +0 -1
- package/dist/core/constraints/interceptor.js +0 -166
- package/dist/core/constraints/interceptor.js.map +0 -1
- package/dist/dashboard/data.d.ts +0 -31
- package/dist/dashboard/data.d.ts.map +0 -1
- package/dist/dashboard/data.js +0 -81
- package/dist/dashboard/data.js.map +0 -1
- package/dist/dashboard/index.d.ts +0 -4
- package/dist/dashboard/index.d.ts.map +0 -1
- package/dist/dashboard/index.js +0 -20
- package/dist/dashboard/index.js.map +0 -1
- package/dist/dashboard/stats.d.ts +0 -20
- package/dist/dashboard/stats.d.ts.map +0 -1
- package/dist/dashboard/stats.js +0 -115
- package/dist/dashboard/stats.js.map +0 -1
- package/dist/dashboard/types.d.ts +0 -110
- package/dist/dashboard/types.d.ts.map +0 -1
- package/dist/dashboard/types.js +0 -8
- package/dist/dashboard/types.js.map +0 -1
- package/dist/monitoring/constraint-doctor.d.ts +0 -76
- package/dist/monitoring/constraint-doctor.d.ts.map +0 -1
- package/dist/monitoring/constraint-doctor.js +0 -189
- package/dist/monitoring/constraint-doctor.js.map +0 -1
- package/dist/monitoring/diagnosis-rules.d.ts +0 -18
- package/dist/monitoring/diagnosis-rules.d.ts.map +0 -1
- package/dist/monitoring/diagnosis-rules.js +0 -119
- package/dist/monitoring/diagnosis-rules.js.map +0 -1
- package/dist/monitoring/knowledge-doctor.d.ts +0 -78
- package/dist/monitoring/knowledge-doctor.d.ts.map +0 -1
- package/dist/monitoring/knowledge-doctor.js +0 -266
- package/dist/monitoring/knowledge-doctor.js.map +0 -1
- package/dist/monitoring/knowledge-evolver.d.ts +0 -72
- package/dist/monitoring/knowledge-evolver.d.ts.map +0 -1
- package/dist/monitoring/knowledge-evolver.js +0 -205
- package/dist/monitoring/knowledge-evolver.js.map +0 -1
- package/dist/monitoring/performance-analyzer.d.ts +0 -92
- package/dist/monitoring/performance-analyzer.d.ts.map +0 -1
- package/dist/monitoring/performance-analyzer.js +0 -343
- package/dist/monitoring/performance-analyzer.js.map +0 -1
- package/dist/monitoring/performance-collector.d.ts +0 -103
- package/dist/monitoring/performance-collector.d.ts.map +0 -1
- package/dist/monitoring/performance-collector.js +0 -298
- package/dist/monitoring/performance-collector.js.map +0 -1
- package/dist/safety/index.d.ts +0 -6
- package/dist/safety/index.d.ts.map +0 -1
- package/dist/safety/index.js +0 -22
- package/dist/safety/index.js.map +0 -1
- package/dist/safety/input-guardrail.d.ts +0 -36
- package/dist/safety/input-guardrail.d.ts.map +0 -1
- package/dist/safety/input-guardrail.js +0 -118
- package/dist/safety/input-guardrail.js.map +0 -1
- package/dist/safety/output-guardrail.d.ts +0 -39
- package/dist/safety/output-guardrail.d.ts.map +0 -1
- package/dist/safety/output-guardrail.js +0 -147
- package/dist/safety/output-guardrail.js.map +0 -1
- package/dist/safety/sandbox.d.ts +0 -45
- package/dist/safety/sandbox.d.ts.map +0 -1
- package/dist/safety/sandbox.js +0 -134
- package/dist/safety/sandbox.js.map +0 -1
- package/dist/safety/tool-guardrail.d.ts +0 -51
- package/dist/safety/tool-guardrail.d.ts.map +0 -1
- package/dist/safety/tool-guardrail.js +0 -147
- package/dist/safety/tool-guardrail.js.map +0 -1
- package/dist/safety/types.d.ts +0 -104
- package/dist/safety/types.d.ts.map +0 -1
- package/dist/safety/types.js +0 -6
- package/dist/safety/types.js.map +0 -1
- package/dist/spec/annotation-checker.d.ts +0 -47
- package/dist/spec/annotation-checker.d.ts.map +0 -1
- package/dist/spec/annotation-checker.js +0 -279
- package/dist/spec/annotation-checker.js.map +0 -1
- package/dist/types/enforcement.d.ts +0 -42
- package/dist/types/enforcement.d.ts.map +0 -1
- package/dist/types/enforcement.js +0 -6
- package/dist/types/enforcement.js.map +0 -1
- package/dist/types/monitoring-types.d.ts +0 -53
- package/dist/types/monitoring-types.d.ts.map +0 -1
- package/dist/types/monitoring-types.js +0 -10
- package/dist/types/monitoring-types.js.map +0 -1
- package/dist/types/performance.d.ts +0 -204
- package/dist/types/performance.d.ts.map +0 -1
- package/dist/types/performance.js +0 -12
- package/dist/types/performance.js.map +0 -1
- package/dist/verification/index.d.ts +0 -4
- package/dist/verification/index.d.ts.map +0 -1
- package/dist/verification/index.js +0 -20
- package/dist/verification/index.js.map +0 -1
- package/dist/verification/loop.d.ts +0 -30
- package/dist/verification/loop.d.ts.map +0 -1
- package/dist/verification/loop.js +0 -114
- package/dist/verification/loop.js.map +0 -1
- package/dist/verification/rules-based.d.ts +0 -40
- package/dist/verification/rules-based.d.ts.map +0 -1
- package/dist/verification/rules-based.js +0 -130
- package/dist/verification/rules-based.js.map +0 -1
- package/dist/verification/types.d.ts +0 -69
- package/dist/verification/types.d.ts.map +0 -1
- package/dist/verification/types.js +0 -6
- package/dist/verification/types.js.map +0 -1
- package/src/__tests__/annotation-checker.test.ts +0 -183
- package/src/__tests__/constraint-doctor.test.ts +0 -271
- package/src/__tests__/doctor-extra.test.ts +0 -155
- package/src/__tests__/extend-exceptions.test.ts +0 -149
- package/src/__tests__/interceptor.test.ts +0 -89
- package/src/__tests__/performance-analyzer.test.ts +0 -499
- package/src/__tests__/performance-collector.test.ts +0 -276
- package/src/__tests__/presets.test.ts +0 -65
- package/src/core/constraints/__tests__/interceptor.test.ts +0 -267
- package/src/core/constraints/interceptor.ts +0 -217
- package/src/dashboard/CONTEXT.md +0 -21
- package/src/dashboard/__tests__/data.test.ts +0 -111
- package/src/dashboard/__tests__/stats.test.ts +0 -159
- package/src/dashboard/data.ts +0 -103
- package/src/dashboard/index.ts +0 -3
- package/src/dashboard/stats.ts +0 -127
- package/src/dashboard/types.ts +0 -130
- package/src/monitoring/__tests__/knowledge-doctor.test.ts +0 -278
- package/src/monitoring/__tests__/knowledge-evolver.test.ts +0 -368
- package/src/monitoring/__tests__/performance-analyzer.test.ts +0 -137
- package/src/monitoring/__tests__/performance-collector.test.ts +0 -331
- package/src/monitoring/constraint-doctor.ts +0 -228
- package/src/monitoring/diagnosis-rules.ts +0 -132
- package/src/monitoring/knowledge-doctor.ts +0 -322
- package/src/monitoring/knowledge-evolver.ts +0 -289
- package/src/monitoring/performance-analyzer.ts +0 -400
- package/src/monitoring/performance-collector.ts +0 -326
- package/src/safety/CONTEXT.md +0 -23
- package/src/safety/__tests__/input-guardrail.test.ts +0 -105
- package/src/safety/__tests__/output-guardrail.test.ts +0 -108
- package/src/safety/__tests__/sandbox.test.ts +0 -158
- package/src/safety/__tests__/tool-guardrail.test.ts +0 -127
- package/src/safety/index.ts +0 -5
- package/src/safety/input-guardrail.ts +0 -133
- package/src/safety/output-guardrail.ts +0 -170
- package/src/safety/sandbox.ts +0 -148
- package/src/safety/tool-guardrail.ts +0 -176
- package/src/safety/types.ts +0 -124
- package/src/spec/__tests__/annotation-checker.test.ts +0 -294
- package/src/spec/annotation-checker.ts +0 -320
- package/src/types/enforcement.ts +0 -48
- package/src/types/monitoring-types.ts +0 -68
- package/src/types/performance.ts +0 -295
- package/src/verification/CONTEXT.md +0 -23
- package/src/verification/__tests__/loop.test.ts +0 -303
- package/src/verification/__tests__/rules-based.test.ts +0 -240
- package/src/verification/index.ts +0 -3
- package/src/verification/loop.ts +0 -137
- package/src/verification/rules-based.ts +0 -152
- package/src/verification/types.ts +0 -81
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* harness constraints retire 测试(ADR-0001 P5)
|
|
3
3
|
*
|
|
4
4
|
* 执行逻辑(retireConstraint,纯函数化):config.yml 写入形态、
|
|
5
|
-
* KnowledgeStore
|
|
5
|
+
* KnowledgeStore 记录、治理注入段同步(CLAUDE.md / AGENTS.md 落点路由)、重复 retire / 未知 id 保护。
|
|
6
6
|
* 交互流程(runRetireInteractive):注入 stdin 流测核心分支
|
|
7
7
|
* (候选选择 + iron 二次确认拒绝;无候选手动输入 + 确认执行)。
|
|
8
8
|
*
|
|
@@ -145,7 +145,7 @@ describe('retireConstraint 执行逻辑', () => {
|
|
|
145
145
|
|
|
146
146
|
const result = retireConstraint(root, 'no_bypass_checkpoint', { now: FIXED_NOW });
|
|
147
147
|
expect(result.status).toBe('retired');
|
|
148
|
-
expect(result.
|
|
148
|
+
expect(result.injectionSynced).toBe(true);
|
|
149
149
|
|
|
150
150
|
const after = fs.readFileSync(path.join(root, 'CLAUDE.md'), 'utf-8');
|
|
151
151
|
expect(after).toContain('HARNESS_CONSTRAINTS_START');
|
|
@@ -164,10 +164,51 @@ describe('retireConstraint 执行逻辑', () => {
|
|
|
164
164
|
fs.writeFileSync(path.join(root, 'CLAUDE.md'), '# 用户自写\n', 'utf-8');
|
|
165
165
|
const result = retireConstraint(root, 'capability_sync', { now: FIXED_NOW });
|
|
166
166
|
expect(result.status).toBe('retired');
|
|
167
|
-
expect(result.
|
|
167
|
+
expect(result.injectionSynced).toBe(false);
|
|
168
168
|
expect(fs.readFileSync(path.join(root, 'CLAUDE.md'), 'utf-8')).toBe('# 用户自写\n');
|
|
169
169
|
});
|
|
170
170
|
|
|
171
|
+
it('新模型仓:AGENTS.md PRESERVE:governance 内含标记段时同步重渲染(无 CLAUDE.md)', () => {
|
|
172
|
+
const root = makeTmpProject();
|
|
173
|
+
const before = renderConstraintsSection(getEffectiveConstraints(root), '0.0.0-test');
|
|
174
|
+
expect(before).toContain('no_bypass_checkpoint');
|
|
175
|
+
fs.writeFileSync(
|
|
176
|
+
path.join(root, 'AGENTS.md'),
|
|
177
|
+
`# AGENTS.md\n\n<!-- PRESERVE:governance -->\n## Governance Rules\n${before}<!-- /PRESERVE:governance -->\n\n其他内容\n`,
|
|
178
|
+
'utf-8'
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
const result = retireConstraint(root, 'no_bypass_checkpoint', { now: FIXED_NOW });
|
|
182
|
+
expect(result.status).toBe('retired');
|
|
183
|
+
expect(result.injectionSynced).toBe(true);
|
|
184
|
+
expect(result.injectionFile).toBe('AGENTS.md');
|
|
185
|
+
|
|
186
|
+
const after = fs.readFileSync(path.join(root, 'AGENTS.md'), 'utf-8');
|
|
187
|
+
expect(after).toContain('HARNESS_CONSTRAINTS_START');
|
|
188
|
+
expect(after).toContain('HARNESS_CONSTRAINTS_END');
|
|
189
|
+
expect(after).not.toContain('**no_bypass_checkpoint**');
|
|
190
|
+
expect(after).toContain('PRESERVE:governance');
|
|
191
|
+
expect(after).toContain('其他内容');
|
|
192
|
+
// 不制造 CLAUDE.md
|
|
193
|
+
expect(fs.existsSync(path.join(root, 'CLAUDE.md'))).toBe(false);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('两文件均有标记段时旧模型仓豁免:只同步 CLAUDE.md,AGENTS.md 不动', () => {
|
|
197
|
+
const root = makeTmpProject();
|
|
198
|
+
const before = renderConstraintsSection(getEffectiveConstraints(root), '0.0.0-test');
|
|
199
|
+
fs.writeFileSync(path.join(root, 'CLAUDE.md'), `# 项目\n\n## Governance Rules\n${before}`, 'utf-8');
|
|
200
|
+
const agentsMd = `# AGENTS.md\n\n<!-- PRESERVE:governance -->\n## Governance Rules\n${before}<!-- /PRESERVE:governance -->\n`;
|
|
201
|
+
fs.writeFileSync(path.join(root, 'AGENTS.md'), agentsMd, 'utf-8');
|
|
202
|
+
|
|
203
|
+
const result = retireConstraint(root, 'no_bypass_checkpoint', { now: FIXED_NOW });
|
|
204
|
+
expect(result.status).toBe('retired');
|
|
205
|
+
expect(result.injectionSynced).toBe(true);
|
|
206
|
+
expect(result.injectionFile).toBe('CLAUDE.md');
|
|
207
|
+
|
|
208
|
+
expect(fs.readFileSync(path.join(root, 'CLAUDE.md'), 'utf-8')).not.toContain('**no_bypass_checkpoint**');
|
|
209
|
+
expect(fs.readFileSync(path.join(root, 'AGENTS.md'), 'utf-8')).toBe(agentsMd);
|
|
210
|
+
});
|
|
211
|
+
|
|
171
212
|
it('重复 retire:already_retired,不覆盖原 retired 元数据', () => {
|
|
172
213
|
const root = makeTmpProject();
|
|
173
214
|
const first = retireConstraint(root, 'capability_sync', { reason: '第一次', now: FIXED_NOW });
|
|
@@ -214,7 +255,7 @@ describe('retireConstraint 执行逻辑', () => {
|
|
|
214
255
|
|
|
215
256
|
expect(result.status).toBe('retired');
|
|
216
257
|
expect(result.landing).toBe('custom-constraints.yml');
|
|
217
|
-
expect(result.
|
|
258
|
+
expect(result.injectionSynced).toBe(true);
|
|
218
259
|
// config.yml 不产生该 id 段
|
|
219
260
|
expect(fs.existsSync(path.join(root, '.harness', 'config.yml'))).toBe(false);
|
|
220
261
|
// yml 保留规则原文 + retired 元数据
|
|
@@ -17,6 +17,8 @@ import {
|
|
|
17
17
|
import {
|
|
18
18
|
setupClaudeMdConstraints,
|
|
19
19
|
setupClaudeMdOutputStyle,
|
|
20
|
+
setupAgentsMdConstraints,
|
|
21
|
+
setupGovernanceConstraints,
|
|
20
22
|
} from '../init';
|
|
21
23
|
import { getEffectiveConstraints } from '../../../core/effective-constraints';
|
|
22
24
|
import { getAllConstraints } from '../../../core/constraints/definitions';
|
|
@@ -232,3 +234,203 @@ describe('setupClaudeMdOutputStyle(标记化)', () => {
|
|
|
232
234
|
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('跳过'));
|
|
233
235
|
});
|
|
234
236
|
});
|
|
237
|
+
|
|
238
|
+
const GOVERNANCE_PRESERVE_BEGIN = '<!-- PRESERVE:governance -->';
|
|
239
|
+
const GOVERNANCE_PRESERVE_END = '<!-- /PRESERVE:governance -->';
|
|
240
|
+
|
|
241
|
+
describe('setupAgentsMdConstraints(新落点模型:治理契约 → AGENTS.md PRESERVE:governance 段)', () => {
|
|
242
|
+
let tempDir: string;
|
|
243
|
+
let agentsMdPath: string;
|
|
244
|
+
|
|
245
|
+
beforeEach(() => {
|
|
246
|
+
tempDir = fs.mkdtempSync(path.join(process.cwd(), 'temp-test-init-agents-'));
|
|
247
|
+
agentsMdPath = path.join(tempDir, 'AGENTS.md');
|
|
248
|
+
jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
afterEach(() => {
|
|
252
|
+
jest.restoreAllMocks();
|
|
253
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('AGENTS.md 不存在时创建骨架并写入 PRESERVE:governance 段', async () => {
|
|
257
|
+
await setupAgentsMdConstraints(tempDir);
|
|
258
|
+
|
|
259
|
+
const content = fs.readFileSync(agentsMdPath, 'utf-8');
|
|
260
|
+
expect(content).toContain('# AGENTS.md');
|
|
261
|
+
expect(content).toContain(GOVERNANCE_PRESERVE_BEGIN);
|
|
262
|
+
expect(content).toContain(GOVERNANCE_PRESERVE_END);
|
|
263
|
+
expect(content).toContain('## Governance Rules');
|
|
264
|
+
expect(content).toContain(CONSTRAINTS_START_MARKER);
|
|
265
|
+
expect(content).toContain(CONSTRAINTS_END_MARKER);
|
|
266
|
+
expect(content).toContain('### Iron Laws');
|
|
267
|
+
// PRESERVE 标记在约束段之外(sync-docs 重新生成时整段保留)
|
|
268
|
+
expect(content.indexOf(GOVERNANCE_PRESERVE_BEGIN)).toBeLessThan(content.indexOf(CONSTRAINTS_START_MARKER));
|
|
269
|
+
expect(content.indexOf(CONSTRAINTS_END_MARKER)).toBeLessThan(content.indexOf(GOVERNANCE_PRESERVE_END));
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('连跑两次输出一致(幂等)', async () => {
|
|
273
|
+
await setupAgentsMdConstraints(tempDir);
|
|
274
|
+
const first = fs.readFileSync(agentsMdPath, 'utf-8');
|
|
275
|
+
await setupAgentsMdConstraints(tempDir);
|
|
276
|
+
expect(fs.readFileSync(agentsMdPath, 'utf-8')).toBe(first);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('已有 PRESERVE:governance 段(含标记+段内手写内容):只换标记区间,手写内容原样保留', async () => {
|
|
280
|
+
// 回归:曾把 PRESERVE 段内 HARNESS 标记之外的手写内容(治理契约引言/流程等)清空
|
|
281
|
+
fs.writeFileSync(agentsMdPath, [
|
|
282
|
+
'# AGENTS.md',
|
|
283
|
+
'',
|
|
284
|
+
'机器生成导读(保持不变)',
|
|
285
|
+
'',
|
|
286
|
+
GOVERNANCE_PRESERVE_BEGIN,
|
|
287
|
+
'## Governance Rules',
|
|
288
|
+
CONSTRAINTS_START_MARKER,
|
|
289
|
+
'<!-- version: 0.0.0 -->',
|
|
290
|
+
'过期的旧约束内容',
|
|
291
|
+
CONSTRAINTS_END_MARKER,
|
|
292
|
+
'',
|
|
293
|
+
'治理契约引言(手写,保留)',
|
|
294
|
+
'',
|
|
295
|
+
'### 治理变更流程(手写,保留)',
|
|
296
|
+
GOVERNANCE_PRESERVE_END,
|
|
297
|
+
'',
|
|
298
|
+
'<!-- PRESERVE:other -->',
|
|
299
|
+
'另一个手写段(保持不动)',
|
|
300
|
+
'<!-- /PRESERVE:other -->',
|
|
301
|
+
'',
|
|
302
|
+
].join('\n'));
|
|
303
|
+
|
|
304
|
+
await setupAgentsMdConstraints(tempDir);
|
|
305
|
+
|
|
306
|
+
const content = fs.readFileSync(agentsMdPath, 'utf-8');
|
|
307
|
+
expect(content).toContain('机器生成导读(保持不变)');
|
|
308
|
+
expect(content).toContain('另一个手写段(保持不动)');
|
|
309
|
+
expect(content).not.toContain('过期的旧约束内容');
|
|
310
|
+
expect(content).toContain(CONSTRAINTS_START_MARKER);
|
|
311
|
+
// 段内标记之外的手写内容原样保留
|
|
312
|
+
expect(content).toContain('治理契约引言(手写,保留)');
|
|
313
|
+
expect(content).toContain('### 治理变更流程(手写,保留)');
|
|
314
|
+
expect(content.indexOf(CONSTRAINTS_END_MARKER)).toBeLessThan(content.indexOf('治理契约引言(手写,保留)'));
|
|
315
|
+
expect(content.indexOf('### 治理变更流程(手写,保留)')).toBeLessThan(content.indexOf(GOVERNANCE_PRESERVE_END));
|
|
316
|
+
// 幂等
|
|
317
|
+
await setupAgentsMdConstraints(tempDir);
|
|
318
|
+
expect(fs.readFileSync(agentsMdPath, 'utf-8')).toBe(content);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('已有 PRESERVE:governance 段但无 HARNESS 标记(纯手写段):段尾追加注入段,手写内容不动', async () => {
|
|
322
|
+
fs.writeFileSync(agentsMdPath, [
|
|
323
|
+
'# AGENTS.md',
|
|
324
|
+
'',
|
|
325
|
+
GOVERNANCE_PRESERVE_BEGIN,
|
|
326
|
+
'治理契约引言(手写,保留)',
|
|
327
|
+
'',
|
|
328
|
+
'### 发布纪律(手写,保留)',
|
|
329
|
+
GOVERNANCE_PRESERVE_END,
|
|
330
|
+
'',
|
|
331
|
+
].join('\n'));
|
|
332
|
+
|
|
333
|
+
await setupAgentsMdConstraints(tempDir);
|
|
334
|
+
|
|
335
|
+
const content = fs.readFileSync(agentsMdPath, 'utf-8');
|
|
336
|
+
expect(content).toContain('治理契约引言(手写,保留)');
|
|
337
|
+
expect(content).toContain('### 发布纪律(手写,保留)');
|
|
338
|
+
expect(content).toContain(CONSTRAINTS_START_MARKER);
|
|
339
|
+
// 注入段追加在手写内容之后、PRESERVE 段内
|
|
340
|
+
expect(content.indexOf('### 发布纪律(手写,保留)')).toBeLessThan(content.indexOf(CONSTRAINTS_START_MARKER));
|
|
341
|
+
expect(content.indexOf(CONSTRAINTS_END_MARKER)).toBeLessThan(content.indexOf(GOVERNANCE_PRESERVE_END));
|
|
342
|
+
// 幂等
|
|
343
|
+
await setupAgentsMdConstraints(tempDir);
|
|
344
|
+
expect(fs.readFileSync(agentsMdPath, 'utf-8')).toBe(content);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it('AGENTS.md 存在但无 PRESERVE:governance 段:末尾追加,既有内容不动', async () => {
|
|
348
|
+
fs.writeFileSync(agentsMdPath, '# AGENTS.md\n\n既有导读内容\n');
|
|
349
|
+
|
|
350
|
+
await setupAgentsMdConstraints(tempDir);
|
|
351
|
+
|
|
352
|
+
const content = fs.readFileSync(agentsMdPath, 'utf-8');
|
|
353
|
+
expect(content).toContain('既有导读内容');
|
|
354
|
+
expect(content).toContain(GOVERNANCE_PRESERVE_BEGIN);
|
|
355
|
+
expect(content.indexOf('既有导读内容')).toBeLessThan(content.indexOf(GOVERNANCE_PRESERVE_BEGIN));
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('PRESERVE:governance 标记残缺(只有单边):不写入并告警', async () => {
|
|
359
|
+
const original = `# AGENTS.md\n\n${GOVERNANCE_PRESERVE_BEGIN}\n残缺的段\n`;
|
|
360
|
+
fs.writeFileSync(agentsMdPath, original);
|
|
361
|
+
const logSpy = jest.spyOn(console, 'log');
|
|
362
|
+
|
|
363
|
+
await setupAgentsMdConstraints(tempDir);
|
|
364
|
+
|
|
365
|
+
expect(fs.readFileSync(agentsMdPath, 'utf-8')).toBe(original);
|
|
366
|
+
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('残缺'));
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it('段内注入文本与 renderConstraintsSection(getEffectiveConstraints()) 一致', async () => {
|
|
370
|
+
await setupAgentsMdConstraints(tempDir);
|
|
371
|
+
|
|
372
|
+
const content = fs.readFileSync(agentsMdPath, 'utf-8');
|
|
373
|
+
const start = content.indexOf(CONSTRAINTS_START_MARKER);
|
|
374
|
+
const end = content.indexOf(CONSTRAINTS_END_MARKER) + CONSTRAINTS_END_MARKER.length + 1;
|
|
375
|
+
const actual = content.slice(start, end);
|
|
376
|
+
const version = /<!-- version: ([^ ]+) -->/.exec(content)![1];
|
|
377
|
+
expect(actual).toBe(renderConstraintsSection(getEffectiveConstraints(tempDir), version));
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
describe('setupGovernanceConstraints(落点路由)', () => {
|
|
382
|
+
let tempDir: string;
|
|
383
|
+
|
|
384
|
+
beforeEach(() => {
|
|
385
|
+
tempDir = fs.mkdtempSync(path.join(process.cwd(), 'temp-test-init-routing-'));
|
|
386
|
+
jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
afterEach(() => {
|
|
390
|
+
jest.restoreAllMocks();
|
|
391
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it('新仓(无 CLAUDE.md):治理契约写向 AGENTS.md,不创建 CLAUDE.md', async () => {
|
|
395
|
+
await setupGovernanceConstraints(tempDir);
|
|
396
|
+
|
|
397
|
+
expect(fs.existsSync(path.join(tempDir, 'AGENTS.md'))).toBe(true);
|
|
398
|
+
expect(fs.existsSync(path.join(tempDir, 'CLAUDE.md'))).toBe(false);
|
|
399
|
+
const content = fs.readFileSync(path.join(tempDir, 'AGENTS.md'), 'utf-8');
|
|
400
|
+
expect(content).toContain(GOVERNANCE_PRESERVE_BEGIN);
|
|
401
|
+
expect(content).toContain(CONSTRAINTS_START_MARKER);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it('旧模型仓(CLAUDE.md 已有 HARNESS_CONSTRAINTS 标记):继续写 CLAUDE.md,不动 AGENTS.md', async () => {
|
|
405
|
+
fs.writeFileSync(path.join(tempDir, 'CLAUDE.md'), [
|
|
406
|
+
'# CLAUDE.md',
|
|
407
|
+
'',
|
|
408
|
+
'## Governance Rules',
|
|
409
|
+
CONSTRAINTS_START_MARKER,
|
|
410
|
+
'<!-- version: 0.0.0 -->',
|
|
411
|
+
'旧约束',
|
|
412
|
+
CONSTRAINTS_END_MARKER,
|
|
413
|
+
'',
|
|
414
|
+
].join('\n'));
|
|
415
|
+
fs.writeFileSync(path.join(tempDir, 'AGENTS.md'), '# AGENTS.md\n\n既有导读\n');
|
|
416
|
+
|
|
417
|
+
await setupGovernanceConstraints(tempDir);
|
|
418
|
+
|
|
419
|
+
const claude = fs.readFileSync(path.join(tempDir, 'CLAUDE.md'), 'utf-8');
|
|
420
|
+
expect(claude).not.toContain('旧约束');
|
|
421
|
+
expect(claude).toContain('### Iron Laws');
|
|
422
|
+
// AGENTS.md 不被写入治理段
|
|
423
|
+
expect(fs.readFileSync(path.join(tempDir, 'AGENTS.md'), 'utf-8')).toBe('# AGENTS.md\n\n既有导读\n');
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
it('旧模型仓(CLAUDE.md 有无标记 Governance Rules 块):路由到 CLAUDE.md', async () => {
|
|
427
|
+
fs.writeFileSync(path.join(tempDir, 'CLAUDE.md'), '# CLAUDE.md\n\n## Governance Rules\n\n手写条款\n');
|
|
428
|
+
|
|
429
|
+
await setupGovernanceConstraints(tempDir);
|
|
430
|
+
|
|
431
|
+
const claude = fs.readFileSync(path.join(tempDir, 'CLAUDE.md'), 'utf-8');
|
|
432
|
+
expect(claude).toContain('手写条款');
|
|
433
|
+
expect(claude).toContain(CONSTRAINTS_START_MARKER);
|
|
434
|
+
expect(fs.existsSync(path.join(tempDir, 'AGENTS.md'))).toBe(false);
|
|
435
|
+
});
|
|
436
|
+
});
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* knowledge 命令测试 — knowledgeAudit
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { knowledgeAudit, knowledgeStats, knowledgeHealth } from '../knowledge';
|
|
5
|
+
import { knowledgeAudit, knowledgeStats, knowledgeHealth, knowledgeSearch } from '../knowledge';
|
|
6
6
|
|
|
7
7
|
// Mock chalk
|
|
8
8
|
jest.mock('chalk', () => ({
|
|
@@ -233,3 +233,92 @@ describe('knowledgeHealth CLI', () => {
|
|
|
233
233
|
expect(parsed.summary.staleEntries).toBe(1);
|
|
234
234
|
});
|
|
235
235
|
});
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* knowledgeSearch 回归测试 — harness#63
|
|
239
|
+
* bug:先按 token budget 截断再文本匹配,新条目(lastReferenced 为空、排序队尾)
|
|
240
|
+
* 在语料超过 budget 时永远进不了匹配阶段。
|
|
241
|
+
*/
|
|
242
|
+
describe('knowledgeSearch CLI (harness#63)', () => {
|
|
243
|
+
const fs = require('fs');
|
|
244
|
+
const os = require('os');
|
|
245
|
+
const path = require('path');
|
|
246
|
+
const { FileKnowledgeStore } = require('../../../knowledge/store');
|
|
247
|
+
|
|
248
|
+
let tmpProject: string;
|
|
249
|
+
let knowledgeDir: string;
|
|
250
|
+
let consoleSpy: jest.SpyInstance;
|
|
251
|
+
|
|
252
|
+
const FILLER_COUNT = 25; // 25 × ~800 tokens ≈ 20000 > maxTokens 10000
|
|
253
|
+
const TARGET_ID = 'PIT-020';
|
|
254
|
+
|
|
255
|
+
function makeEntry(overrides: Record<string, any>): any {
|
|
256
|
+
return {
|
|
257
|
+
id: 'X-000',
|
|
258
|
+
type: 'pitfall',
|
|
259
|
+
title: '条目',
|
|
260
|
+
content: '内容',
|
|
261
|
+
maturity: 'proven',
|
|
262
|
+
layer: 'project',
|
|
263
|
+
created: '2026-08-01T00:00:00.000Z',
|
|
264
|
+
lastReferenced: '2026-08-01T00:00:00.000Z',
|
|
265
|
+
contributors: [],
|
|
266
|
+
projects: [],
|
|
267
|
+
tags: [],
|
|
268
|
+
applicablePhases: [],
|
|
269
|
+
sourceReferences: [],
|
|
270
|
+
referencedBy: [],
|
|
271
|
+
executionResults: [],
|
|
272
|
+
consumptionMode: 'reference',
|
|
273
|
+
origin: 'agent',
|
|
274
|
+
...overrides,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
beforeEach(() => {
|
|
279
|
+
tmpProject = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-k63-'));
|
|
280
|
+
knowledgeDir = path.join(tmpProject, '.harness', 'knowledge');
|
|
281
|
+
fs.mkdirSync(knowledgeDir, { recursive: true });
|
|
282
|
+
consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
283
|
+
|
|
284
|
+
const store = new FileKnowledgeStore({ baseDir: knowledgeDir });
|
|
285
|
+
// 高成熟度 + 有 lastReferenced 的填充条目排在前面,吃满 token budget
|
|
286
|
+
for (let i = 0; i < FILLER_COUNT; i++) {
|
|
287
|
+
store.save(makeEntry({
|
|
288
|
+
id: `FILL-${String(i).padStart(3, '0')}`,
|
|
289
|
+
title: `填充条目 ${i}`,
|
|
290
|
+
content: '填'.repeat(399) + '共同词',
|
|
291
|
+
}));
|
|
292
|
+
}
|
|
293
|
+
// 目标条目:新条目,draft + lastReferenced 为空,排在队尾
|
|
294
|
+
store.save(makeEntry({
|
|
295
|
+
id: TARGET_ID,
|
|
296
|
+
title: '新条目',
|
|
297
|
+
maturity: 'draft',
|
|
298
|
+
lastReferenced: '',
|
|
299
|
+
content: 'autolink 相关条目 共同词',
|
|
300
|
+
}));
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
afterEach(() => {
|
|
304
|
+
consoleSpy.mockRestore();
|
|
305
|
+
fs.rmSync(tmpProject, { recursive: true, force: true });
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
async function searchJson(query: string, limit: number): Promise<any> {
|
|
309
|
+
await knowledgeSearch(query, { projectPath: tmpProject, json: true, limit });
|
|
310
|
+
const output = consoleSpy.mock.calls.map((c: any[]) => c[0]).join('\n');
|
|
311
|
+
return JSON.parse(output);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
it('语料超过 token budget 时,排在队尾的新条目仍可被搜到', async () => {
|
|
315
|
+
const result = await searchJson('autolink', 200);
|
|
316
|
+
expect(result.total).toBe(1);
|
|
317
|
+
expect(result.entries[0].id).toBe(TARGET_ID);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('超高频关键词的返回数 = 实际匹配数(截断只发生在匹配之后)', async () => {
|
|
321
|
+
const result = await searchJson('共同词', 200);
|
|
322
|
+
expect(result.total).toBe(FILLER_COUNT + 1);
|
|
323
|
+
});
|
|
324
|
+
});
|
|
@@ -51,11 +51,10 @@ describe('命令注册表闭环', () => {
|
|
|
51
51
|
});
|
|
52
52
|
|
|
53
53
|
it('全部实现引用可解析(module 存在且 export 是函数)', () => {
|
|
54
|
-
const refs = collectRefs(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
54
|
+
const refs = collectRefs([
|
|
55
|
+
...COMMAND_DEFINITIONS,
|
|
56
|
+
...GATE_DEFINITIONS.map(d => d.cli),
|
|
57
|
+
]);
|
|
59
58
|
expect(refs.length).toBeGreaterThan(0);
|
|
60
59
|
|
|
61
60
|
for (const ref of refs) {
|
|
@@ -107,7 +106,10 @@ describe('命令注册表闭环', () => {
|
|
|
107
106
|
}
|
|
108
107
|
for (const child of def.children || []) walk(child);
|
|
109
108
|
};
|
|
110
|
-
for (const def of
|
|
109
|
+
for (const def of [
|
|
110
|
+
...COMMAND_DEFINITIONS,
|
|
111
|
+
...GATE_DEFINITIONS.map(d => d.cli),
|
|
112
|
+
]) walk(def);
|
|
111
113
|
// sync-docs --check 失败路径确实会请求退出
|
|
112
114
|
expect(exitSpy).toHaveBeenCalled();
|
|
113
115
|
} finally {
|
|
@@ -81,7 +81,7 @@ describe('status command - 补充覆盖', () => {
|
|
|
81
81
|
];
|
|
82
82
|
const mockAnalyze = {
|
|
83
83
|
summarize: jest.fn().mockReturnValue([
|
|
84
|
-
{ constraintId: 'no_bypass_checkpoint', level: 'iron_law', passRate: 0.2,
|
|
84
|
+
{ constraintId: 'no_bypass_checkpoint', level: 'iron_law', passRate: 0.2, failRate: 0.8, totalChecks: 10 },
|
|
85
85
|
]),
|
|
86
86
|
detectAnomalies: jest.fn().mockReturnValue(mockAnomalies),
|
|
87
87
|
};
|
|
@@ -119,8 +119,8 @@ describe('status command - 补充覆盖', () => {
|
|
|
119
119
|
mockFs.readFileSync.mockReturnValue(mockTraces.map(t => JSON.stringify(t)).join('\n'));
|
|
120
120
|
|
|
121
121
|
const mockSummaries = [
|
|
122
|
-
{ constraintId: 'no_bypass_checkpoint', level: 'iron_law', passRate: 1,
|
|
123
|
-
{ constraintId: 'no_completion_without_verification', level: 'iron_law', passRate: 0.8,
|
|
122
|
+
{ constraintId: 'no_bypass_checkpoint', level: 'iron_law', passRate: 1, failRate: 0, totalChecks: 10 },
|
|
123
|
+
{ constraintId: 'no_completion_without_verification', level: 'iron_law', passRate: 0.8, failRate: 0.2, totalChecks: 5 },
|
|
124
124
|
];
|
|
125
125
|
const mockAnalyze = {
|
|
126
126
|
summarize: jest.fn().mockReturnValue(mockSummaries),
|
|
@@ -141,7 +141,7 @@ describe('status command - 补充覆盖', () => {
|
|
|
141
141
|
mockFs.readFileSync.mockReturnValue(mockTraces.map(t => JSON.stringify(t)).join('\n'));
|
|
142
142
|
|
|
143
143
|
const mockSummaries = [
|
|
144
|
-
{ constraintId: 'capability_sync', level: 'guideline', passRate: 0.9,
|
|
144
|
+
{ constraintId: 'capability_sync', level: 'guideline', passRate: 0.9, failRate: 0.1, totalChecks: 20 },
|
|
145
145
|
];
|
|
146
146
|
const mockAnalyze = {
|
|
147
147
|
summarize: jest.fn().mockReturnValue(mockSummaries),
|
|
@@ -164,8 +164,8 @@ describe('status command - 补充覆盖', () => {
|
|
|
164
164
|
mockFs.readFileSync.mockReturnValue(mockTraces.map(t => JSON.stringify(t)).join('\n'));
|
|
165
165
|
|
|
166
166
|
const mockSummaries = [
|
|
167
|
-
{ constraintId: 'no_bypass_checkpoint', level: 'iron_law', passRate: 1,
|
|
168
|
-
{ constraintId: 'capability_sync', level: 'guideline', passRate: 0.9,
|
|
167
|
+
{ constraintId: 'no_bypass_checkpoint', level: 'iron_law', passRate: 1, failRate: 0, totalChecks: 10 },
|
|
168
|
+
{ constraintId: 'capability_sync', level: 'guideline', passRate: 0.9, failRate: 0.1, totalChecks: 20 },
|
|
169
169
|
];
|
|
170
170
|
const mockAnalyze = {
|
|
171
171
|
summarize: jest.fn().mockReturnValue(mockSummaries),
|
|
@@ -87,7 +87,7 @@ describe('status command', () => {
|
|
|
87
87
|
mockFs.readFileSync.mockReturnValue(mockTraces.map(t => JSON.stringify(t)).join('\n'));
|
|
88
88
|
|
|
89
89
|
const mockSummarize = jest.fn().mockReturnValue([
|
|
90
|
-
{ constraintId: 'test1', level: 'iron_law', passRate: 1,
|
|
90
|
+
{ constraintId: 'test1', level: 'iron_law', passRate: 1, failRate: 0, totalChecks: 1 },
|
|
91
91
|
]);
|
|
92
92
|
const mockAnalyze = {
|
|
93
93
|
summarize: mockSummarize,
|
|
@@ -106,7 +106,7 @@ describe('status command', () => {
|
|
|
106
106
|
mockFs.readFileSync.mockReturnValue(mockTraces.map(t => JSON.stringify(t)).join('\n'));
|
|
107
107
|
|
|
108
108
|
const mockSummarize = jest.fn().mockReturnValue([
|
|
109
|
-
{ constraintId: 'no_bypass_checkpoint', level: 'iron_law', passRate: 1,
|
|
109
|
+
{ constraintId: 'no_bypass_checkpoint', level: 'iron_law', passRate: 1, failRate: 0, totalChecks: 5 },
|
|
110
110
|
]);
|
|
111
111
|
const mockAnalyze = {
|
|
112
112
|
summarize: mockSummarize,
|
|
@@ -131,7 +131,7 @@ describe('status command', () => {
|
|
|
131
131
|
];
|
|
132
132
|
const mockAnalyze = {
|
|
133
133
|
summarize: jest.fn().mockReturnValue([
|
|
134
|
-
{ constraintId: 'test1', level: 'iron_law', passRate: 0.2,
|
|
134
|
+
{ constraintId: 'test1', level: 'iron_law', passRate: 0.2, failRate: 0.8, totalChecks: 5 },
|
|
135
135
|
]),
|
|
136
136
|
detectAnomalies: jest.fn().mockReturnValue(mockAnomalies),
|
|
137
137
|
};
|
|
@@ -150,7 +150,7 @@ describe('status command', () => {
|
|
|
150
150
|
mockFs.readFileSync.mockReturnValue(mockTraces.map(t => JSON.stringify(t)).join('\n'));
|
|
151
151
|
|
|
152
152
|
const mockSummarize = jest.fn().mockReturnValue([
|
|
153
|
-
{ constraintId: 'test1', level: 'iron_law', passRate: 0.8,
|
|
153
|
+
{ constraintId: 'test1', level: 'iron_law', passRate: 0.8, failRate: 0.2, totalChecks: 10 },
|
|
154
154
|
]);
|
|
155
155
|
const mockAnalyze = {
|
|
156
156
|
summarize: mockSummarize,
|
|
@@ -184,7 +184,7 @@ describe('status command', () => {
|
|
|
184
184
|
|
|
185
185
|
const mockAnalyze = {
|
|
186
186
|
summarize: jest.fn().mockReturnValue([
|
|
187
|
-
{ constraintId: 'test_guide', level: 'guideline', passRate: 0.9,
|
|
187
|
+
{ constraintId: 'test_guide', level: 'guideline', passRate: 0.9, failRate: 0.1, totalChecks: 10 },
|
|
188
188
|
]),
|
|
189
189
|
detectAnomalies: jest.fn().mockReturnValue([]),
|
|
190
190
|
};
|
|
@@ -276,7 +276,7 @@ describe('status command', () => {
|
|
|
276
276
|
|
|
277
277
|
const mockAnalyze = {
|
|
278
278
|
summarize: jest.fn().mockReturnValue([
|
|
279
|
-
{ constraintId: 'test_guide', level: 'guideline', passRate: 0.6,
|
|
279
|
+
{ constraintId: 'test_guide', level: 'guideline', passRate: 0.6, failRate: 0.4, totalChecks: 10 },
|
|
280
280
|
]),
|
|
281
281
|
detectAnomalies: jest.fn().mockReturnValue([]),
|
|
282
282
|
};
|
|
@@ -592,5 +592,33 @@ describe('sync-docs --agents', () => {
|
|
|
592
592
|
|
|
593
593
|
fs.rmSync(testDir, { recursive: true, force: true });
|
|
594
594
|
});
|
|
595
|
+
|
|
596
|
+
it('PRESERVE:governance 治理契约段开箱即用:重新生成保留、--check 通过(studio #302)', async () => {
|
|
597
|
+
const testDir = path.join(tempDir, 'preserve-governance');
|
|
598
|
+
createFixture(testDir, { pnpm: true, withCapabilities: true });
|
|
599
|
+
|
|
600
|
+
const GOVERNANCE_BLOCK = [
|
|
601
|
+
'<!-- PRESERVE:governance -->',
|
|
602
|
+
'## Governance Rules',
|
|
603
|
+
'',
|
|
604
|
+
'<!-- HARNESS_CONSTRAINTS_START -->',
|
|
605
|
+
'- **law_a**: 治理契约条款',
|
|
606
|
+
'<!-- HARNESS_CONSTRAINTS_END -->',
|
|
607
|
+
'<!-- /PRESERVE:governance -->',
|
|
608
|
+
].join('\n');
|
|
609
|
+
|
|
610
|
+
await syncDocs({ projectPath: testDir, agents: true });
|
|
611
|
+
appendBlock(testDir, GOVERNANCE_BLOCK);
|
|
612
|
+
await syncDocs({ projectPath: testDir, agents: true });
|
|
613
|
+
|
|
614
|
+
const content = fs.readFileSync(path.join(testDir, 'AGENTS.md'), 'utf-8');
|
|
615
|
+
expect(content).toContain(GOVERNANCE_BLOCK);
|
|
616
|
+
|
|
617
|
+
// 幂等 + --check 通过(块内含 HARNESS_CONSTRAINTS 标记不影响漂移比对)
|
|
618
|
+
expect(await syncDocs({ projectPath: testDir, agents: true })).toBe(true);
|
|
619
|
+
expect(await syncDocs({ projectPath: testDir, agents: true, check: true })).toBe(true);
|
|
620
|
+
|
|
621
|
+
fs.rmSync(testDir, { recursive: true, force: true });
|
|
622
|
+
});
|
|
595
623
|
});
|
|
596
624
|
});
|
|
@@ -2,17 +2,16 @@
|
|
|
2
2
|
* harness check 命令
|
|
3
3
|
*
|
|
4
4
|
* 检查约束是否满足(check 层:Iron Laws / Guidelines;prompt 层仅注入不检查)
|
|
5
|
-
* 工单 23:触发条件与证据检测迁至 core/constraints/context-builder
|
|
6
|
-
* ADR-0001:约束集统一走
|
|
5
|
+
* 工单 23:触发条件与证据检测迁至 core/constraints/context-builder
|
|
6
|
+
* ADR-0001:约束集统一走 getMergedConstraintsConfig 生效集链路(preset/config 禁用/custom/scenes)
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import chalk from 'chalk';
|
|
10
10
|
import * as fs from 'fs';
|
|
11
11
|
import * as path from 'path';
|
|
12
12
|
import { constraintChecker } from '../../core/constraints/checker';
|
|
13
|
-
import { getTraceCollector } from '../../monitoring/traces';
|
|
14
13
|
import { IRON_LAWS, GUIDELINES, PROMPTS } from '../../core/constraints/definitions';
|
|
15
|
-
import {
|
|
14
|
+
import { getMergedConstraintsConfig } from '../../core/effective-constraints';
|
|
16
15
|
import { buildConstraintContext } from '../../core/constraints/context-builder';
|
|
17
16
|
import { detectInjectionDrift } from '../../core/constraints/injection-drift';
|
|
18
17
|
import { DEFAULT_TRACE_FILE } from '../../types/trace';
|
|
@@ -37,18 +36,12 @@ export async function check(options: CheckOptions): Promise<void> {
|
|
|
37
36
|
console.log(chalk.gray(`预设: ${options.preset}`));
|
|
38
37
|
|
|
39
38
|
try {
|
|
40
|
-
// 加载项目级自定义约束
|
|
41
39
|
const projectPath = options.projectPath || process.cwd();
|
|
42
|
-
const configLoader = new ProjectConfigLoader(projectPath);
|
|
43
|
-
configLoader.load();
|
|
44
|
-
constraintChecker.setTraceRecorder(getTraceCollector());
|
|
45
40
|
|
|
46
41
|
// 生效约束集(ADR-0001):内置 → preset → config.yml 禁用 → custom 追加 → scenes 过滤。
|
|
47
|
-
// --preset 仅在没有项目自定义配置时覆盖 config.yml 的 preset
|
|
48
|
-
// 项目自定义配置优先于 CLI
|
|
49
|
-
const merged =
|
|
50
|
-
? configLoader.mergeConstraints()
|
|
51
|
-
: configLoader.mergeConstraints({ preset: options.preset || 'standard' });
|
|
42
|
+
// --preset 仅在没有项目自定义配置时覆盖 config.yml 的 preset(工单 23 语义:
|
|
43
|
+
// 项目自定义配置优先于 CLI 预设),优先级规则收在 getMergedConstraintsConfig 一处。
|
|
44
|
+
const merged = getMergedConstraintsConfig(projectPath, { preset: options.preset });
|
|
52
45
|
if (merged.custom.length > 0) {
|
|
53
46
|
console.log(chalk.gray(`自定义约束: ${merged.custom.length} 条`));
|
|
54
47
|
}
|
|
@@ -130,7 +123,7 @@ export async function check(options: CheckOptions): Promise<void> {
|
|
|
130
123
|
const drift = detectInjectionDrift(projectPath);
|
|
131
124
|
if (drift.hasDrift) {
|
|
132
125
|
console.log();
|
|
133
|
-
console.log(chalk.yellow(
|
|
126
|
+
console.log(chalk.yellow(`⚠️ 检测到 ${drift.injectionFile ?? '治理文档'} 约束注入漂移(仅警告,不阻断):`));
|
|
134
127
|
if (drift.versionDrift) {
|
|
135
128
|
console.log(chalk.yellow(` ⚠️⚠️ 注入段版本 (${drift.versionDrift.actual}) ≠ 已安装 harness 版本 (${drift.versionDrift.expected}):agent 上下文中的规则与已安装 harness 版本不一致`));
|
|
136
129
|
}
|
|
@@ -199,23 +192,7 @@ async function getSmartHint(projectPath: string): Promise<string | null> {
|
|
|
199
192
|
hints.push('📊 记录已足够,运行 harness status 查看统计');
|
|
200
193
|
state.shownHints.push('trace_50');
|
|
201
194
|
}
|
|
202
|
-
|
|
203
|
-
// 条件 2: 检查异常趋势(简单检查绕过率)
|
|
204
|
-
const bypassCount = lines.filter(line => {
|
|
205
|
-
try {
|
|
206
|
-
const trace = JSON.parse(line);
|
|
207
|
-
return trace.result === 'bypassed';
|
|
208
|
-
} catch {
|
|
209
|
-
return false;
|
|
210
|
-
}
|
|
211
|
-
}).length;
|
|
212
|
-
|
|
213
|
-
const bypassRate = traceCount > 10 ? bypassCount / traceCount : 0;
|
|
214
|
-
if (bypassRate > 0.3 && !state.shownHints.includes('high_bypass')) {
|
|
215
|
-
hints.push('⚠️ 发现异常趋势,建议运行 harness status 查看详情');
|
|
216
|
-
state.shownHints.push('high_bypass');
|
|
217
|
-
}
|
|
218
|
-
|
|
195
|
+
|
|
219
196
|
// 保存状态
|
|
220
197
|
if (hints.length > 0) {
|
|
221
198
|
fs.mkdirSync(path.dirname(statePath), { recursive: true });
|
|
@@ -242,14 +219,11 @@ export function listLaws(): void {
|
|
|
242
219
|
});
|
|
243
220
|
|
|
244
221
|
// Guidelines
|
|
245
|
-
console.log(chalk.yellow('🟡 指导原则 (Guidelines) -
|
|
222
|
+
console.log(chalk.yellow('🟡 指导原则 (Guidelines) - 优先建议,违背发警告但不阻止:\n'));
|
|
246
223
|
Object.values(GUIDELINES).forEach(constraint => {
|
|
247
224
|
console.log(chalk.yellow(` ${constraint.id}`));
|
|
248
225
|
console.log(chalk.gray(` ${constraint.rule}`));
|
|
249
226
|
console.log(chalk.gray(` ${constraint.message}`));
|
|
250
|
-
if (constraint.exceptions && constraint.exceptions.length > 0) {
|
|
251
|
-
console.log(chalk.gray(` 例外: ${constraint.exceptions.join(', ')}`));
|
|
252
|
-
}
|
|
253
227
|
console.log();
|
|
254
228
|
});
|
|
255
229
|
|
|
@@ -118,7 +118,7 @@ export function renderExportMarkdown(
|
|
|
118
118
|
export function renderInjectionDriftLines(drift: InjectionDrift): string[] {
|
|
119
119
|
const lines: string[] = [];
|
|
120
120
|
if (drift.notInjected) {
|
|
121
|
-
lines.push(' CLAUDE.md
|
|
121
|
+
lines.push(' CLAUDE.md / AGENTS.md 均无约束注入标记段(未注入,不算漂移);可运行 npx @dommaker/harness init 注入');
|
|
122
122
|
if (drift.duplicateHeading) {
|
|
123
123
|
lines.push(' 另检测到多个 "## Governance Rules" 标题(历史遗留重复章节),建议手工清理后重跑 npx @dommaker/harness init');
|
|
124
124
|
}
|