@dommaker/harness 0.16.9 → 0.17.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 +39 -0
- package/README.md +33 -32
- package/bin/harness.js +33 -20
- package/dist/cli/commands/check.d.ts +3 -2
- package/dist/cli/commands/check.d.ts.map +1 -1
- package/dist/cli/commands/check.js +69 -36
- package/dist/cli/commands/check.js.map +1 -1
- package/dist/cli/commands/constraints-report.d.ts +42 -0
- package/dist/cli/commands/constraints-report.d.ts.map +1 -0
- package/dist/cli/commands/constraints-report.js +233 -0
- package/dist/cli/commands/constraints-report.js.map +1 -0
- package/dist/cli/commands/constraints-retire.d.ts +71 -0
- package/dist/cli/commands/constraints-retire.d.ts.map +1 -0
- package/dist/cli/commands/constraints-retire.js +384 -0
- package/dist/cli/commands/constraints-retire.js.map +1 -0
- package/dist/cli/commands/constraints.d.ts +1 -1
- package/dist/cli/commands/constraints.d.ts.map +1 -1
- package/dist/cli/commands/constraints.js +4 -4
- package/dist/cli/commands/constraints.js.map +1 -1
- package/dist/cli/commands/index.d.ts +2 -1
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +10 -4
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/init.d.ts +20 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +81 -59
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/report.js +4 -4
- package/dist/cli/commands/report.js.map +1 -1
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/status.js +3 -4
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/sync-docs/capabilities-syncer.d.ts +5 -1
- package/dist/cli/commands/sync-docs/capabilities-syncer.d.ts.map +1 -1
- package/dist/cli/commands/sync-docs/capabilities-syncer.js +2 -10
- package/dist/cli/commands/sync-docs/capabilities-syncer.js.map +1 -1
- package/dist/cli/commands/validate.d.ts.map +1 -1
- package/dist/cli/commands/validate.js +1 -5
- package/dist/cli/commands/validate.js.map +1 -1
- package/dist/core/constraints/capabilities-parser.d.ts +7 -0
- package/dist/core/constraints/capabilities-parser.d.ts.map +1 -1
- package/dist/core/constraints/capabilities-parser.js +13 -0
- package/dist/core/constraints/capabilities-parser.js.map +1 -1
- package/dist/core/constraints/checker.d.ts +13 -5
- package/dist/core/constraints/checker.d.ts.map +1 -1
- package/dist/core/constraints/checker.js +62 -25
- package/dist/core/constraints/checker.js.map +1 -1
- package/dist/core/constraints/checkers/capability-sync.d.ts +9 -2
- package/dist/core/constraints/checkers/capability-sync.d.ts.map +1 -1
- package/dist/core/constraints/checkers/capability-sync.js +55 -22
- package/dist/core/constraints/checkers/capability-sync.js.map +1 -1
- package/dist/core/constraints/checkers/context-doc-sync.d.ts +3 -0
- package/dist/core/constraints/checkers/context-doc-sync.d.ts.map +1 -1
- package/dist/core/constraints/checkers/context-doc-sync.js +8 -2
- package/dist/core/constraints/checkers/context-doc-sync.js.map +1 -1
- package/dist/core/constraints/checkers/docs-freshness.d.ts +1 -0
- package/dist/core/constraints/checkers/docs-freshness.d.ts.map +1 -1
- package/dist/core/constraints/checkers/docs-freshness.js +39 -0
- package/dist/core/constraints/checkers/docs-freshness.js.map +1 -1
- package/dist/core/constraints/checkers/index.d.ts +8 -4
- package/dist/core/constraints/checkers/index.d.ts.map +1 -1
- package/dist/core/constraints/checkers/index.js +29 -46
- package/dist/core/constraints/checkers/index.js.map +1 -1
- package/dist/core/constraints/checkers/iron-flags.d.ts +6 -7
- package/dist/core/constraints/checkers/iron-flags.d.ts.map +1 -1
- package/dist/core/constraints/checkers/iron-flags.js +10 -17
- package/dist/core/constraints/checkers/iron-flags.js.map +1 -1
- package/dist/core/constraints/checkers/no-hardcoded-credentials.d.ts +13 -0
- package/dist/core/constraints/checkers/no-hardcoded-credentials.d.ts.map +1 -0
- package/dist/core/constraints/checkers/no-hardcoded-credentials.js +91 -0
- package/dist/core/constraints/checkers/no-hardcoded-credentials.js.map +1 -0
- package/dist/core/constraints/checkers/types.d.ts +15 -4
- package/dist/core/constraints/checkers/types.d.ts.map +1 -1
- package/dist/core/constraints/checkers/types.js +16 -4
- package/dist/core/constraints/checkers/types.js.map +1 -1
- package/dist/core/constraints/context-builder.d.ts +6 -3
- package/dist/core/constraints/context-builder.d.ts.map +1 -1
- package/dist/core/constraints/context-builder.js +29 -20
- package/dist/core/constraints/context-builder.js.map +1 -1
- package/dist/core/constraints/definitions/guidelines.d.ts +6 -2
- package/dist/core/constraints/definitions/guidelines.d.ts.map +1 -1
- package/dist/core/constraints/definitions/guidelines.js +29 -572
- package/dist/core/constraints/definitions/guidelines.js.map +1 -1
- package/dist/core/constraints/definitions/iron-laws.d.ts +6 -2
- package/dist/core/constraints/definitions/iron-laws.d.ts.map +1 -1
- package/dist/core/constraints/definitions/iron-laws.js +48 -231
- package/dist/core/constraints/definitions/iron-laws.js.map +1 -1
- package/dist/core/constraints/definitions/prompts.d.ts +12 -0
- package/dist/core/constraints/definitions/prompts.d.ts.map +1 -0
- package/dist/core/constraints/definitions/prompts.js +405 -0
- package/dist/core/constraints/definitions/prompts.js.map +1 -0
- package/dist/core/constraints/definitions.d.ts +15 -9
- package/dist/core/constraints/definitions.d.ts.map +1 -1
- package/dist/core/constraints/definitions.js +18 -12
- package/dist/core/constraints/definitions.js.map +1 -1
- package/dist/core/constraints/index.d.ts +3 -2
- package/dist/core/constraints/index.d.ts.map +1 -1
- package/dist/core/constraints/index.js +8 -2
- package/dist/core/constraints/index.js.map +1 -1
- package/dist/core/constraints/injection-drift.d.ts +48 -0
- package/dist/core/constraints/injection-drift.d.ts.map +1 -0
- package/dist/core/constraints/injection-drift.js +140 -0
- package/dist/core/constraints/injection-drift.js.map +1 -0
- package/dist/core/constraints/injection-renderer.d.ts +23 -0
- package/dist/core/constraints/injection-renderer.d.ts.map +1 -0
- package/dist/core/constraints/injection-renderer.js +44 -0
- package/dist/core/constraints/injection-renderer.js.map +1 -0
- package/dist/core/constraints/interceptor.d.ts.map +1 -1
- package/dist/core/constraints/interceptor.js +3 -2
- package/dist/core/constraints/interceptor.js.map +1 -1
- package/dist/core/constraints/usage-report.d.ts +95 -0
- package/dist/core/constraints/usage-report.d.ts.map +1 -0
- package/dist/core/constraints/usage-report.js +206 -0
- package/dist/core/constraints/usage-report.js.map +1 -0
- package/dist/core/effective-constraints.d.ts +47 -0
- package/dist/core/effective-constraints.d.ts.map +1 -0
- package/dist/core/effective-constraints.js +60 -0
- package/dist/core/effective-constraints.js.map +1 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +2 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/project-config-loader.d.ts +17 -2
- package/dist/core/project-config-loader.d.ts.map +1 -1
- package/dist/core/project-config-loader.js +55 -8
- package/dist/core/project-config-loader.js.map +1 -1
- package/dist/dashboard/data.d.ts +1 -9
- package/dist/dashboard/data.d.ts.map +1 -1
- package/dist/dashboard/data.js +7 -15
- package/dist/dashboard/data.js.map +1 -1
- package/dist/dashboard/types.d.ts +13 -1
- package/dist/dashboard/types.d.ts.map +1 -1
- package/dist/index.d.ts +0 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -13
- package/dist/index.js.map +1 -1
- package/dist/monitoring/index.d.ts +0 -2
- package/dist/monitoring/index.d.ts.map +1 -1
- package/dist/monitoring/index.js +0 -2
- package/dist/monitoring/index.js.map +1 -1
- package/dist/monitoring/knowledge-evolver.d.ts +1 -1
- package/dist/monitoring/knowledge-evolver.js +1 -1
- package/dist/monitoring/trace-analyzer.d.ts +1 -1
- package/dist/monitoring/trace-analyzer.d.ts.map +1 -1
- package/dist/monitoring/trace-analyzer.js +16 -10
- package/dist/monitoring/trace-analyzer.js.map +1 -1
- package/dist/monitoring/traces.d.ts +2 -1
- package/dist/monitoring/traces.d.ts.map +1 -1
- package/dist/monitoring/traces.js +5 -3
- package/dist/monitoring/traces.js.map +1 -1
- package/dist/presets/standard.d.ts +3 -1
- package/dist/presets/standard.d.ts.map +1 -1
- package/dist/presets/standard.js +18 -8
- package/dist/presets/standard.js.map +1 -1
- package/dist/types/constraint.d.ts +51 -11
- package/dist/types/constraint.d.ts.map +1 -1
- package/dist/types/constraint.js +8 -4
- package/dist/types/constraint.js.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/monitoring-types.d.ts +2 -63
- package/dist/types/monitoring-types.d.ts.map +1 -1
- package/dist/types/monitoring-types.js +2 -2
- package/dist/types/project-config.d.ts +26 -1
- package/dist/types/project-config.d.ts.map +1 -1
- package/dist/types/trace.d.ts +16 -7
- package/dist/types/trace.d.ts.map +1 -1
- package/dist/types/trace.js +8 -0
- package/dist/types/trace.js.map +1 -1
- package/package.json +1 -1
- package/src/CONTEXT.md +2 -2
- package/src/__tests__/checker-extra.test.ts +228 -333
- package/src/__tests__/checker.test.ts +87 -724
- package/src/__tests__/constraint-doctor.test.ts +5 -5
- package/src/__tests__/doctor-extra.test.ts +164 -0
- package/src/__tests__/effective-constraints.test.ts +147 -0
- package/src/__tests__/extend-exceptions.test.ts +3 -3
- package/src/__tests__/iron-laws.test.ts +115 -91
- package/src/__tests__/no-hardcoded-credentials.test.ts +90 -0
- package/src/__tests__/presets.test.ts +7 -7
- package/src/__tests__/project-config-loader-extra.test.ts +41 -4
- package/src/__tests__/skip-semantics.test.ts +322 -0
- package/src/cli/commands/CONTEXT.md +5 -3
- package/src/cli/commands/__tests__/check-drift.test.ts +127 -0
- package/src/cli/commands/__tests__/check-skip-output.test.ts +128 -0
- package/src/cli/commands/__tests__/check.test.ts +14 -25
- package/src/cli/commands/__tests__/constraints-report.test.ts +327 -0
- package/src/cli/commands/__tests__/constraints-retire.test.ts +283 -0
- package/src/cli/commands/__tests__/init-injection.test.ts +234 -0
- package/src/cli/commands/__tests__/status-extra.test.ts +11 -11
- package/src/cli/commands/__tests__/status.test.ts +1 -1
- package/src/cli/commands/check.ts +72 -38
- package/src/cli/commands/constraints-report.ts +234 -0
- package/src/cli/commands/constraints-retire.ts +443 -0
- package/src/cli/commands/constraints.ts +5 -5
- package/src/cli/commands/index.ts +2 -1
- package/src/cli/commands/init.ts +94 -66
- package/src/cli/commands/report.ts +6 -6
- package/src/cli/commands/status.ts +3 -4
- package/src/cli/commands/sync-docs/capabilities-syncer.ts +5 -7
- package/src/cli/commands/validate.ts +1 -5
- package/src/core/CONTEXT.md +7 -4
- package/src/core/constraints/__tests__/injection-drift.test.ts +164 -0
- package/src/core/constraints/__tests__/interceptor.test.ts +4 -0
- package/src/core/constraints/capabilities-parser.ts +13 -0
- package/src/core/constraints/checker.ts +73 -28
- package/src/core/constraints/checkers/capability-sync.ts +72 -29
- package/src/core/constraints/checkers/context-doc-sync.ts +9 -2
- package/src/core/constraints/checkers/docs-freshness.ts +43 -0
- package/src/core/constraints/checkers/index.ts +38 -71
- package/src/core/constraints/checkers/iron-flags.ts +13 -49
- package/src/core/constraints/checkers/no-hardcoded-credentials.ts +93 -0
- package/src/core/constraints/checkers/types.ts +27 -5
- package/src/core/constraints/context-builder.ts +28 -19
- package/src/core/constraints/definitions/guidelines.ts +29 -597
- package/src/core/constraints/definitions/iron-laws.ts +48 -239
- package/src/core/constraints/definitions/prompts.ts +419 -0
- package/src/core/constraints/definitions.ts +18 -11
- package/src/core/constraints/index.ts +10 -1
- package/src/core/constraints/injection-drift.ts +139 -0
- package/src/core/constraints/injection-renderer.ts +44 -0
- package/src/core/constraints/interceptor.ts +3 -2
- package/src/core/constraints/usage-report.ts +254 -0
- package/src/core/effective-constraints.ts +85 -0
- package/src/core/index.ts +4 -1
- package/src/core/project-config-loader.ts +59 -9
- package/src/dashboard/__tests__/data.test.ts +5 -12
- package/src/dashboard/data.ts +9 -19
- package/src/dashboard/types.ts +18 -1
- package/src/index.ts +0 -12
- package/src/knowledge/CONTEXT.md +2 -2
- package/src/monitoring/CONTEXT.md +3 -4
- package/src/monitoring/index.ts +0 -2
- package/src/monitoring/knowledge-evolver.ts +1 -1
- package/src/monitoring/trace-analyzer.ts +15 -10
- package/src/monitoring/traces.ts +10 -6
- package/src/presets/__tests__/standard.test.ts +11 -9
- package/src/presets/standard.ts +24 -15
- package/src/types/constraint.ts +59 -14
- package/src/types/index.ts +1 -3
- package/src/types/monitoring-types.ts +2 -84
- package/src/types/project-config.ts +30 -1
- package/src/types/trace.ts +18 -7
- package/src/verification/CONTEXT.md +1 -1
- package/dist/cli/commands/flow.d.ts +0 -22
- package/dist/cli/commands/flow.d.ts.map +0 -1
- package/dist/cli/commands/flow.js +0 -275
- package/dist/cli/commands/flow.js.map +0 -1
- package/dist/constraints/lifecycle-runner.d.ts +0 -52
- package/dist/constraints/lifecycle-runner.d.ts.map +0 -1
- package/dist/constraints/lifecycle-runner.js +0 -151
- package/dist/constraints/lifecycle-runner.js.map +0 -1
- package/dist/constraints/registry.d.ts +0 -27
- package/dist/constraints/registry.d.ts.map +0 -1
- package/dist/constraints/registry.js +0 -128
- package/dist/constraints/registry.js.map +0 -1
- package/dist/constraints/types.d.ts +0 -53
- package/dist/constraints/types.d.ts.map +0 -1
- package/dist/constraints/types.js +0 -8
- package/dist/constraints/types.js.map +0 -1
- package/dist/core/constraints/checkers/always-pass.d.ts +0 -17
- package/dist/core/constraints/checkers/always-pass.d.ts.map +0 -1
- package/dist/core/constraints/checkers/always-pass.js +0 -23
- package/dist/core/constraints/checkers/always-pass.js.map +0 -1
- package/dist/core/constraints/checkers/guideline-flags.d.ts +0 -9
- package/dist/core/constraints/checkers/guideline-flags.d.ts.map +0 -1
- package/dist/core/constraints/checkers/guideline-flags.js +0 -13
- package/dist/core/constraints/checkers/guideline-flags.js.map +0 -1
- package/dist/core/constraints/checkers/no-any-type.d.ts +0 -6
- package/dist/core/constraints/checkers/no-any-type.d.ts.map +0 -1
- package/dist/core/constraints/checkers/no-any-type.js +0 -42
- package/dist/core/constraints/checkers/no-any-type.js.map +0 -1
- package/dist/core/constraints/checkers/no-excuse-patterns.d.ts +0 -6
- package/dist/core/constraints/checkers/no-excuse-patterns.d.ts.map +0 -1
- package/dist/core/constraints/checkers/no-excuse-patterns.js +0 -25
- package/dist/core/constraints/checkers/no-excuse-patterns.js.map +0 -1
- package/dist/core/constraints/checkers/no-fuzzy-completion-claim.d.ts +0 -6
- package/dist/core/constraints/checkers/no-fuzzy-completion-claim.d.ts.map +0 -1
- package/dist/core/constraints/checkers/no-fuzzy-completion-claim.js +0 -34
- package/dist/core/constraints/checkers/no-fuzzy-completion-claim.js.map +0 -1
- package/dist/core/constraints/checkers/no-performative-agreement.d.ts +0 -8
- package/dist/core/constraints/checkers/no-performative-agreement.d.ts.map +0 -1
- package/dist/core/constraints/checkers/no-performative-agreement.js +0 -23
- package/dist/core/constraints/checkers/no-performative-agreement.js.map +0 -1
- package/dist/core/constraints/checkers/no-simplification-without-approval.d.ts +0 -6
- package/dist/core/constraints/checkers/no-simplification-without-approval.d.ts.map +0 -1
- package/dist/core/constraints/checkers/no-simplification-without-approval.js +0 -30
- package/dist/core/constraints/checkers/no-simplification-without-approval.js.map +0 -1
- package/dist/core/constraints/checkers/test-coverage.d.ts +0 -6
- package/dist/core/constraints/checkers/test-coverage.d.ts.map +0 -1
- package/dist/core/constraints/checkers/test-coverage.js +0 -33
- package/dist/core/constraints/checkers/test-coverage.js.map +0 -1
- package/dist/core/constraints/checkers/yagni.d.ts +0 -8
- package/dist/core/constraints/checkers/yagni.d.ts.map +0 -1
- package/dist/core/constraints/checkers/yagni.js +0 -96
- package/dist/core/constraints/checkers/yagni.js.map +0 -1
- package/dist/core/constraints/definitions/tips.d.ts +0 -7
- package/dist/core/constraints/definitions/tips.d.ts.map +0 -1
- package/dist/core/constraints/definitions/tips.js +0 -62
- package/dist/core/constraints/definitions/tips.js.map +0 -1
- package/dist/evolution/auto-evolve.d.ts +0 -35
- package/dist/evolution/auto-evolve.d.ts.map +0 -1
- package/dist/evolution/auto-evolve.js +0 -64
- package/dist/evolution/auto-evolve.js.map +0 -1
- package/dist/evolution/index.d.ts +0 -2
- package/dist/evolution/index.d.ts.map +0 -1
- package/dist/evolution/index.js +0 -18
- package/dist/evolution/index.js.map +0 -1
- package/dist/monitoring/constraint-evolver.d.ts +0 -120
- package/dist/monitoring/constraint-evolver.d.ts.map +0 -1
- package/dist/monitoring/constraint-evolver.js +0 -410
- package/dist/monitoring/constraint-evolver.js.map +0 -1
- package/src/__tests__/constraint-evolver.test.ts +0 -405
- package/src/__tests__/doctor-evolver.test.ts +0 -381
- package/src/cli/commands/__tests__/flow.test.ts +0 -343
- package/src/cli/commands/flow.ts +0 -277
- package/src/constraints/__tests__/lifecycle-runner.test.ts +0 -246
- package/src/constraints/__tests__/registry.test.ts +0 -196
- package/src/constraints/lifecycle-runner.ts +0 -184
- package/src/constraints/registry.ts +0 -134
- package/src/constraints/types.ts +0 -59
- package/src/core/constraints/checkers/always-pass.ts +0 -22
- package/src/core/constraints/checkers/guideline-flags.ts +0 -30
- package/src/core/constraints/checkers/no-any-type.ts +0 -42
- package/src/core/constraints/checkers/no-excuse-patterns.ts +0 -24
- package/src/core/constraints/checkers/no-fuzzy-completion-claim.ts +0 -33
- package/src/core/constraints/checkers/no-performative-agreement.ts +0 -22
- package/src/core/constraints/checkers/no-simplification-without-approval.ts +0 -29
- package/src/core/constraints/checkers/test-coverage.ts +0 -35
- package/src/core/constraints/checkers/yagni.ts +0 -65
- package/src/core/constraints/definitions/tips.ts +0 -63
- package/src/evolution/CONTEXT.md +0 -22
- package/src/evolution/__tests__/auto-evolve.test.ts +0 -319
- package/src/evolution/auto-evolve.ts +0 -98
- package/src/evolution/index.ts +0 -1
- package/src/monitoring/constraint-evolver.ts +0 -501
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* no_hardcoded_credentials checker 测试(ADR-0001)
|
|
3
|
+
*
|
|
4
|
+
* 注意:测试夹具中的假凭证一律用字符串拼接构造,
|
|
5
|
+
* 避免本文件自身被凭证扫描(含 harness 自身 staged diff)命中。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect } from '@jest/globals';
|
|
9
|
+
import {
|
|
10
|
+
noHardcodedCredentials,
|
|
11
|
+
containsHardcodedCredential,
|
|
12
|
+
} from '../core/constraints/checkers/no-hardcoded-credentials';
|
|
13
|
+
import type { CheckEnv } from '../core/constraints/checkers';
|
|
14
|
+
|
|
15
|
+
function makeEnv(stagedDiff: string): CheckEnv {
|
|
16
|
+
return {
|
|
17
|
+
context: { operation: 'commit', projectPath: '/nonexistent' },
|
|
18
|
+
projectPath: '/nonexistent',
|
|
19
|
+
stagedDiff: async () => stagedDiff,
|
|
20
|
+
stagedDiffNames: async () => '',
|
|
21
|
+
srcScan: () => [],
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const AWS_KEY = 'AKIA' + '1234567890ABCDEF';
|
|
26
|
+
const PEM_HEADER = '-----BEGIN ' + 'RSA PRIVATE KEY-----';
|
|
27
|
+
const PASSWORD_ASSIGN = 'const dbPass' + 'word = "sup3r-s3cret-value";';
|
|
28
|
+
|
|
29
|
+
describe('containsHardcodedCredential', () => {
|
|
30
|
+
it('命中 PEM 私钥头', () => {
|
|
31
|
+
expect(containsHardcodedCredential(PEM_HEADER + '\nMIIE...')).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('命中 AWS Access Key', () => {
|
|
35
|
+
expect(containsHardcodedCredential(`const key = "${AWS_KEY}";`)).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('命中硬编码 password 赋值', () => {
|
|
39
|
+
expect(containsHardcodedCredential(PASSWORD_ASSIGN)).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('命中硬编码 api_key 赋值', () => {
|
|
43
|
+
expect(containsHardcodedCredential('api' + '_key: "abcdef123456789"')).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('豁免环境变量引用', () => {
|
|
47
|
+
expect(
|
|
48
|
+
containsHardcodedCredential('const pass' + 'word = process.env.DB_PASSWORD;')
|
|
49
|
+
).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('豁免占位符值', () => {
|
|
53
|
+
expect(
|
|
54
|
+
containsHardcodedCredential('const pass' + 'word = "your-password-here";')
|
|
55
|
+
).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('普通代码不误报', () => {
|
|
59
|
+
expect(
|
|
60
|
+
containsHardcodedCredential('export function add(a: number, b: number) { return a + b; }')
|
|
61
|
+
).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe('noHardcodedCredentials checker', () => {
|
|
66
|
+
it('staged diff 新增行命中凭证 → fail', async () => {
|
|
67
|
+
const diff = [
|
|
68
|
+
'diff --git a/src/config.ts b/src/config.ts',
|
|
69
|
+
'--- a/src/config.ts',
|
|
70
|
+
'+++ b/src/config.ts',
|
|
71
|
+
'@@ -0,0 +1 @@',
|
|
72
|
+
'+' + PASSWORD_ASSIGN,
|
|
73
|
+
].join('\n');
|
|
74
|
+
expect(await noHardcodedCredentials.evaluate(makeEnv(diff))).toBe(false);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('staged diff 仅删除行含凭证 → pass(删除凭证是好事)', async () => {
|
|
78
|
+
const diff = ['--- a/src/config.ts', '+++ /dev/null', '-' + PASSWORD_ASSIGN].join('\n');
|
|
79
|
+
expect(await noHardcodedCredentials.evaluate(makeEnv(diff))).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('staged diff 无凭证 → pass', async () => {
|
|
83
|
+
const diff = '+++ b/src/a.ts\n+export const x = 1;\n';
|
|
84
|
+
expect(await noHardcodedCredentials.evaluate(makeEnv(diff))).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('staged diff 为空且无可读变更文件 → pass', async () => {
|
|
88
|
+
expect(await noHardcodedCredentials.evaluate(makeEnv(''))).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -11,7 +11,7 @@ describe('Presets', () => {
|
|
|
11
11
|
expect(STRICT_PRESET.name).toBe('strict');
|
|
12
12
|
expect(STRICT_PRESET.ironLaws).toBeNull();
|
|
13
13
|
expect(STRICT_PRESET.guidelines).toBeNull();
|
|
14
|
-
expect(STRICT_PRESET.
|
|
14
|
+
expect(STRICT_PRESET.prompts).toBeNull();
|
|
15
15
|
});
|
|
16
16
|
});
|
|
17
17
|
|
|
@@ -20,7 +20,7 @@ describe('Presets', () => {
|
|
|
20
20
|
expect(STANDARD_PRESET.name).toBe('standard');
|
|
21
21
|
expect(STANDARD_PRESET.ironLaws).toBeNull();
|
|
22
22
|
expect(STANDARD_PRESET.guidelines).toBeNull();
|
|
23
|
-
expect(STANDARD_PRESET.
|
|
23
|
+
expect(STANDARD_PRESET.prompts).toBeNull();
|
|
24
24
|
});
|
|
25
25
|
});
|
|
26
26
|
|
|
@@ -29,15 +29,15 @@ describe('Presets', () => {
|
|
|
29
29
|
expect(RELAXED_PRESET.name).toBe('relaxed');
|
|
30
30
|
expect(RELAXED_PRESET.ironLaws).not.toBeNull();
|
|
31
31
|
expect(RELAXED_PRESET.guidelines).not.toBeNull();
|
|
32
|
-
expect(RELAXED_PRESET.
|
|
32
|
+
expect(RELAXED_PRESET.prompts).toEqual([]);
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
-
it('应该包含
|
|
36
|
-
expect(RELAXED_PRESET.ironLaws).toContain('
|
|
35
|
+
it('应该包含 no_completion_without_verification', () => {
|
|
36
|
+
expect(RELAXED_PRESET.ironLaws).toContain('no_completion_without_verification');
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
-
it('应该包含
|
|
40
|
-
expect(RELAXED_PRESET.
|
|
39
|
+
it('应该包含 no_bypass_checkpoint', () => {
|
|
40
|
+
expect(RELAXED_PRESET.guidelines).toContain('no_bypass_checkpoint');
|
|
41
41
|
});
|
|
42
42
|
});
|
|
43
43
|
|
|
@@ -29,9 +29,9 @@ describe('ProjectConfigLoader - 补充覆盖', () => {
|
|
|
29
29
|
path.join(harnessDir, 'config.yml'),
|
|
30
30
|
`
|
|
31
31
|
constraints:
|
|
32
|
-
|
|
32
|
+
legacy_disabled_a:
|
|
33
33
|
enabled: false
|
|
34
|
-
|
|
34
|
+
legacy_disabled_b:
|
|
35
35
|
enabled: false
|
|
36
36
|
`
|
|
37
37
|
);
|
|
@@ -40,8 +40,8 @@ constraints:
|
|
|
40
40
|
loader.load();
|
|
41
41
|
const merged = loader.mergeConstraints();
|
|
42
42
|
|
|
43
|
-
expect(merged.disabled).toContain('
|
|
44
|
-
expect(merged.disabled).toContain('
|
|
43
|
+
expect(merged.disabled).toContain('legacy_disabled_a');
|
|
44
|
+
expect(merged.disabled).toContain('legacy_disabled_b');
|
|
45
45
|
});
|
|
46
46
|
});
|
|
47
47
|
|
|
@@ -106,6 +106,43 @@ custom_constraints:
|
|
|
106
106
|
});
|
|
107
107
|
});
|
|
108
108
|
|
|
109
|
+
describe('自定义约束 promptInjection', () => {
|
|
110
|
+
it('应该透传 promptInjection 并进入注入段渲染', async () => {
|
|
111
|
+
fs.writeFileSync(
|
|
112
|
+
path.join(harnessDir, 'config.yml'),
|
|
113
|
+
`
|
|
114
|
+
custom_constraints:
|
|
115
|
+
my_prompt_rule:
|
|
116
|
+
rule: MY PROMPT RULE
|
|
117
|
+
message: Prompt rule message
|
|
118
|
+
level: guideline
|
|
119
|
+
trigger: commit
|
|
120
|
+
promptInjection: 我的自定义注入文本
|
|
121
|
+
my_silent_rule:
|
|
122
|
+
rule: MY SILENT RULE
|
|
123
|
+
message: No injection text
|
|
124
|
+
level: guideline
|
|
125
|
+
trigger: commit
|
|
126
|
+
`
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
const loader = new ProjectConfigLoader(tempDir);
|
|
130
|
+
loader.load();
|
|
131
|
+
const merged = loader.mergeConstraints();
|
|
132
|
+
|
|
133
|
+
// 字段透传
|
|
134
|
+
expect(merged.guidelines['my_prompt_rule']?.promptInjection).toBe('我的自定义注入文本');
|
|
135
|
+
// 未定义 promptInjection 的自定义约束保持不注入(与内置无注入文本条目一致)
|
|
136
|
+
expect(merged.guidelines['my_silent_rule']?.promptInjection).toBeUndefined();
|
|
137
|
+
|
|
138
|
+
// 端到端:进入 CLAUDE.md 注入段渲染
|
|
139
|
+
const { renderConstraintsSection } = await import('../core/constraints/injection-renderer');
|
|
140
|
+
const section = renderConstraintsSection(Object.values(merged.guidelines), '0.0.0-test');
|
|
141
|
+
expect(section).toContain('- **my_prompt_rule**: 我的自定义注入文本');
|
|
142
|
+
expect(section).not.toContain('my_silent_rule');
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
109
146
|
describe('约束扩展', () => {
|
|
110
147
|
it('应该扩展例外列表', () => {
|
|
111
148
|
fs.writeFileSync(
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* skip 三态语义测试(ADR-0001 P4)
|
|
3
|
+
*
|
|
4
|
+
* 覆盖:
|
|
5
|
+
* - 三条存在性探测(capability_sync / context_doc_sync / docs_freshness)有/无约定两路
|
|
6
|
+
* - flag 型铁律 undefined → skip(显式 false 仍 fail,true 仍 pass)
|
|
7
|
+
* - Iron Law skip 不阻断 checkConstraints
|
|
8
|
+
* - detectTrigger 代码文件 → 附加 code_implementation 推断
|
|
9
|
+
* - trace 记录 result: 'skip';TraceAnalyzer 的 pass/fail 率分母不计 skip
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { describe, it, expect, beforeAll, afterAll, afterEach } from '@jest/globals';
|
|
13
|
+
import { ConstraintChecker } from '../core/constraints/checker';
|
|
14
|
+
import { detectTrigger, buildConstraintContext } from '../core/constraints/context-builder';
|
|
15
|
+
import { IRON_LAWS } from '../core/constraints/definitions';
|
|
16
|
+
import { TraceAnalyzer } from '../monitoring/trace-analyzer';
|
|
17
|
+
import type { Constraint, ConstraintContext } from '../types/constraint';
|
|
18
|
+
import type { ExecutionTrace } from '../types/trace';
|
|
19
|
+
import * as fs from 'fs';
|
|
20
|
+
import * as path from 'path';
|
|
21
|
+
import { execSync } from 'child_process';
|
|
22
|
+
|
|
23
|
+
const checker = ConstraintChecker.getInstance();
|
|
24
|
+
|
|
25
|
+
function makeConstraint(
|
|
26
|
+
id: string,
|
|
27
|
+
level: 'iron_law' | 'guideline',
|
|
28
|
+
trigger: string = 'commit'
|
|
29
|
+
): Constraint {
|
|
30
|
+
return { id, kind: 'check', level, rule: 'TEST', message: 'test', trigger, enforcement: 'test' };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe('skip 三态语义(ADR-0001)', () => {
|
|
34
|
+
const tempDir = path.join(process.cwd(), 'temp-test-skip-semantics');
|
|
35
|
+
|
|
36
|
+
beforeAll(() => {
|
|
37
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
38
|
+
execSync('git init', { cwd: tempDir });
|
|
39
|
+
execSync('git config user.email "test@test.com"', { cwd: tempDir });
|
|
40
|
+
execSync('git config user.name "Test"', { cwd: tempDir });
|
|
41
|
+
fs.writeFileSync(path.join(tempDir, 'initial.txt'), 'initial');
|
|
42
|
+
execSync('git add .', { cwd: tempDir });
|
|
43
|
+
execSync('git commit -m "init"', { cwd: tempDir });
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
afterAll(() => {
|
|
47
|
+
try {
|
|
48
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
49
|
+
} catch {
|
|
50
|
+
// ignore
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
afterEach(() => {
|
|
55
|
+
// 复位 trace recorder,避免泄漏到其他用例
|
|
56
|
+
checker.setTraceRecorder({ record: () => undefined });
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe('capability_sync 存在性探测', () => {
|
|
60
|
+
it('无 CAPABILITIES.md(未采用约定)→ skip,不计 fail', async () => {
|
|
61
|
+
const dir = path.join(tempDir, 'cap-skip');
|
|
62
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
63
|
+
|
|
64
|
+
const result = await checker.check(makeConstraint('capability_sync', 'guideline'), {
|
|
65
|
+
operation: 'commit',
|
|
66
|
+
projectPath: dir,
|
|
67
|
+
} as ConstraintContext);
|
|
68
|
+
|
|
69
|
+
expect(result.skipped).toBe(true);
|
|
70
|
+
expect(result.satisfied).toBe(true); // fail-open:skip 不产生违规
|
|
71
|
+
|
|
72
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('有 CAPABILITIES.md(采用约定)→ 正常评估,非 skip', async () => {
|
|
76
|
+
const dir = path.join(tempDir, 'cap-adopted');
|
|
77
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
78
|
+
// 散文文档(无表格)历史放行;此处只验证"进入评估"而非 skip
|
|
79
|
+
fs.writeFileSync(path.join(dir, 'CAPABILITIES.md'), '# Capabilities\n\n- Feature: test');
|
|
80
|
+
|
|
81
|
+
const result = await checker.check(makeConstraint('capability_sync', 'guideline'), {
|
|
82
|
+
operation: 'commit',
|
|
83
|
+
projectPath: dir,
|
|
84
|
+
} as ConstraintContext);
|
|
85
|
+
|
|
86
|
+
expect(result.skipped).toBeUndefined();
|
|
87
|
+
expect(result.satisfied).toBe(true);
|
|
88
|
+
|
|
89
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('context_doc_sync 存在性探测', () => {
|
|
94
|
+
it('无 context_files 配置(未采用约定)→ skip', async () => {
|
|
95
|
+
const dir = path.join(tempDir, 'ctx-skip');
|
|
96
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
97
|
+
|
|
98
|
+
const result = await checker.check(makeConstraint('context_doc_sync', 'guideline'), {
|
|
99
|
+
operation: 'module_modification',
|
|
100
|
+
projectPath: dir,
|
|
101
|
+
} as ConstraintContext);
|
|
102
|
+
|
|
103
|
+
expect(result.skipped).toBe(true);
|
|
104
|
+
expect(result.satisfied).toBe(true);
|
|
105
|
+
|
|
106
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('配置了 required_dirs 且缺少 CONTEXT.md → fail(非 skip)', async () => {
|
|
110
|
+
const dir = path.join(tempDir, 'ctx-fail');
|
|
111
|
+
fs.mkdirSync(path.join(dir, '.harness'), { recursive: true });
|
|
112
|
+
fs.mkdirSync(path.join(dir, 'src'), { recursive: true });
|
|
113
|
+
|
|
114
|
+
const yaml = require('js-yaml');
|
|
115
|
+
fs.writeFileSync(
|
|
116
|
+
path.join(dir, '.harness', 'config.yml'),
|
|
117
|
+
yaml.dump({ governance: { context_files: { enabled: true, required_dirs: ['src'] } } })
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const result = await checker.check(makeConstraint('context_doc_sync', 'guideline'), {
|
|
121
|
+
operation: 'module_modification',
|
|
122
|
+
projectPath: dir,
|
|
123
|
+
} as ConstraintContext);
|
|
124
|
+
|
|
125
|
+
expect(result.skipped).toBeUndefined();
|
|
126
|
+
expect(result.satisfied).toBe(false);
|
|
127
|
+
|
|
128
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe('docs_freshness 存在性探测', () => {
|
|
133
|
+
it('无任何 freshness 配置/目标 → skip', async () => {
|
|
134
|
+
const dir = path.join(tempDir, 'fresh-skip');
|
|
135
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
136
|
+
|
|
137
|
+
const result = await checker.check(makeConstraint('docs_freshness', 'iron_law', 'file_modification'), {
|
|
138
|
+
operation: 'file_modification',
|
|
139
|
+
projectPath: dir,
|
|
140
|
+
} as ConstraintContext);
|
|
141
|
+
|
|
142
|
+
expect(result.skipped).toBe(true);
|
|
143
|
+
expect(result.satisfied).toBe(true);
|
|
144
|
+
|
|
145
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('有 CAPABILITIES.md 目标 → 正常评估(幽灵条目仍 fail,非 skip)', async () => {
|
|
149
|
+
const dir = path.join(tempDir, 'fresh-adopted');
|
|
150
|
+
fs.mkdirSync(path.join(dir, 'src'), { recursive: true });
|
|
151
|
+
fs.writeFileSync(
|
|
152
|
+
path.join(dir, 'CAPABILITIES.md'),
|
|
153
|
+
'# Capabilities\n\n| 模块 | 文件 | 说明 |\n|------|------|------|\n| ghost | src/ghost.ts | 不存在 |'
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
const result = await checker.check(makeConstraint('docs_freshness', 'iron_law', 'file_modification'), {
|
|
157
|
+
operation: 'file_modification',
|
|
158
|
+
projectPath: dir,
|
|
159
|
+
} as ConstraintContext);
|
|
160
|
+
|
|
161
|
+
expect(result.skipped).toBeUndefined();
|
|
162
|
+
expect(result.satisfied).toBe(false);
|
|
163
|
+
|
|
164
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe('flag 型铁律:undefined → skip', () => {
|
|
169
|
+
it('hasVerificationEvidence undefined → skip;false → fail;true → pass', async () => {
|
|
170
|
+
const law = IRON_LAWS['no_completion_without_verification'];
|
|
171
|
+
|
|
172
|
+
const skipped = await checker.check(law, { operation: 'code_implementation' });
|
|
173
|
+
expect(skipped.skipped).toBe(true);
|
|
174
|
+
expect(skipped.satisfied).toBe(true);
|
|
175
|
+
|
|
176
|
+
const failed = await checker.check(law, {
|
|
177
|
+
operation: 'code_implementation',
|
|
178
|
+
hasVerificationEvidence: false,
|
|
179
|
+
});
|
|
180
|
+
expect(failed.skipped).toBeUndefined();
|
|
181
|
+
expect(failed.satisfied).toBe(false);
|
|
182
|
+
|
|
183
|
+
const passed = await checker.check(law, {
|
|
184
|
+
operation: 'code_implementation',
|
|
185
|
+
hasVerificationEvidence: true,
|
|
186
|
+
});
|
|
187
|
+
expect(passed.skipped).toBeUndefined();
|
|
188
|
+
expect(passed.satisfied).toBe(true);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('hasSingleTask / hasRequirement undefined → skip', async () => {
|
|
192
|
+
const single = await checker.check(IRON_LAWS['incremental_progress'], {
|
|
193
|
+
operation: 'code_implementation',
|
|
194
|
+
});
|
|
195
|
+
expect(single.skipped).toBe(true);
|
|
196
|
+
|
|
197
|
+
const req = await checker.check(IRON_LAWS['no_implementation_without_requirement'], {
|
|
198
|
+
operation: 'code_implementation',
|
|
199
|
+
});
|
|
200
|
+
expect(req.skipped).toBe(true);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('Iron Law skip 不阻断 checkConstraints', async () => {
|
|
204
|
+
// 三个 flag 全部未接线(CLI pre-commit 路径的典型形态)
|
|
205
|
+
const result = await checker.checkConstraints({
|
|
206
|
+
operation: 'code_implementation',
|
|
207
|
+
projectPath: path.join(tempDir, 'empty'),
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
expect(result.passed).toBe(true);
|
|
211
|
+
const skippedIds = result.ironLaws.filter(r => r.skipped).map(r => r.id);
|
|
212
|
+
expect(skippedIds).toEqual(
|
|
213
|
+
expect.arrayContaining([
|
|
214
|
+
'no_completion_without_verification',
|
|
215
|
+
'incremental_progress',
|
|
216
|
+
'no_implementation_without_requirement',
|
|
217
|
+
])
|
|
218
|
+
);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('显式 false 仍按 fail 阻断(skip 语义不改变 fail-closed)', async () => {
|
|
222
|
+
await expect(
|
|
223
|
+
checker.checkConstraints({
|
|
224
|
+
operation: 'code_implementation',
|
|
225
|
+
hasVerificationEvidence: false,
|
|
226
|
+
hasSingleTask: true,
|
|
227
|
+
hasRequirement: true,
|
|
228
|
+
})
|
|
229
|
+
).rejects.toThrow();
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
describe('detectTrigger 补 code_implementation 推断', () => {
|
|
234
|
+
const triggerDir = path.join(tempDir, 'trigger-probe');
|
|
235
|
+
|
|
236
|
+
beforeAll(() => {
|
|
237
|
+
// src/ 下有已提交 .ts 文件 → detectSourceRoots 命中 'src'
|
|
238
|
+
fs.mkdirSync(path.join(triggerDir, 'src'), { recursive: true });
|
|
239
|
+
fs.writeFileSync(path.join(triggerDir, 'src', 'existing.ts'), 'export const x = 1;');
|
|
240
|
+
execSync('git init', { cwd: triggerDir });
|
|
241
|
+
execSync('git config user.email "test@test.com"', { cwd: triggerDir });
|
|
242
|
+
execSync('git config user.name "Test"', { cwd: triggerDir });
|
|
243
|
+
execSync('git add .', { cwd: triggerDir });
|
|
244
|
+
execSync('git commit -m "init"', { cwd: triggerDir });
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('源码根内代码变更 → [module_modification, code_implementation]', () => {
|
|
248
|
+
const result = detectTrigger(['src/existing.ts'], { projectPath: triggerDir });
|
|
249
|
+
expect(result).toEqual(['module_modification', 'code_implementation']);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('纯文档/配置变更 → 仅 file_modification,不附加 code_implementation', () => {
|
|
253
|
+
const result = detectTrigger(['README.md', 'docs/design.md', '.harness/config.yml'], {
|
|
254
|
+
projectPath: triggerDir,
|
|
255
|
+
});
|
|
256
|
+
expect(result).toBe('file_modification');
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('源码根外的代码文件 → file_modification + code_implementation', () => {
|
|
260
|
+
const result = detectTrigger(['scripts/build.js'], { projectPath: triggerDir });
|
|
261
|
+
expect(result).toEqual(['file_modification', 'code_implementation']);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('仅测试变更 → test_creation + code_implementation', () => {
|
|
265
|
+
const result = detectTrigger(['src/__tests__/existing.test.ts'], { projectPath: triggerDir });
|
|
266
|
+
expect(result).toEqual(['test_creation', 'code_implementation']);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('显式 trigger 优先,不做推断', () => {
|
|
270
|
+
const result = detectTrigger(['src/existing.ts'], {
|
|
271
|
+
trigger: 'design_request',
|
|
272
|
+
projectPath: triggerDir,
|
|
273
|
+
});
|
|
274
|
+
expect(result).toBe('design_request');
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('buildConstraintContext:代码变更 → operation 主推断 + extraTriggers 携带 code_implementation', async () => {
|
|
278
|
+
execSync('git add src/existing.ts', { cwd: triggerDir });
|
|
279
|
+
fs.writeFileSync(path.join(triggerDir, 'src', 'existing.ts'), 'export const x = 2;');
|
|
280
|
+
execSync('git add src/existing.ts', { cwd: triggerDir });
|
|
281
|
+
|
|
282
|
+
const ctx = await buildConstraintContext({ projectPath: triggerDir, staged: true });
|
|
283
|
+
|
|
284
|
+
expect(ctx.operation).toBe('module_modification');
|
|
285
|
+
expect(ctx.extraTriggers).toEqual(['code_implementation']);
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
describe('trace 的 skip 形态', () => {
|
|
290
|
+
it('recordTrace:skip 记录为 result: skip', async () => {
|
|
291
|
+
const records: ExecutionTrace[] = [];
|
|
292
|
+
checker.setTraceRecorder({ record: (t) => records.push(t) });
|
|
293
|
+
|
|
294
|
+
await checker.checkConstraints({
|
|
295
|
+
operation: 'code_implementation',
|
|
296
|
+
projectPath: path.join(tempDir, 'empty'),
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
const skipTraces = records.filter(t => t.result === 'skip');
|
|
300
|
+
expect(skipTraces.map(t => t.constraintId)).toEqual(
|
|
301
|
+
expect.arrayContaining(['incremental_progress', 'no_implementation_without_requirement'])
|
|
302
|
+
);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('TraceAnalyzer:skip 单独计数,不计入 pass/fail 率分母', () => {
|
|
306
|
+
const analyzer = new TraceAnalyzer(null as any);
|
|
307
|
+
const traces: ExecutionTrace[] = [
|
|
308
|
+
{ constraintId: 'c1', level: 'iron_law', timestamp: 1, result: 'pass' },
|
|
309
|
+
{ constraintId: 'c1', level: 'iron_law', timestamp: 2, result: 'skip' },
|
|
310
|
+
{ constraintId: 'c1', level: 'iron_law', timestamp: 3, result: 'skip' },
|
|
311
|
+
{ constraintId: 'c1', level: 'iron_law', timestamp: 4, result: 'fail' },
|
|
312
|
+
];
|
|
313
|
+
|
|
314
|
+
const [summary] = analyzer.summarize(traces);
|
|
315
|
+
expect(summary.totalChecks).toBe(4);
|
|
316
|
+
expect(summary.skipCount).toBe(2);
|
|
317
|
+
// 分母 = 4 - 2 = 2(仅实际评估次数)
|
|
318
|
+
expect(summary.passRate).toBe(0.5);
|
|
319
|
+
expect(summary.failRate).toBe(0.5);
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
});
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# cli/commands/
|
|
2
2
|
|
|
3
3
|
## 职责
|
|
4
|
-
CLI 子命令实现:
|
|
4
|
+
CLI 子命令实现:24 个顶层命令 + constraints 治理子命令 report/retire(共 26 个命令文件),覆盖约束检查/门禁验证/文档同步/知识管理/失败诊断/发布/会话分析等。
|
|
5
5
|
|
|
6
|
-
## 核心导出(
|
|
7
|
-
check / validate / passes-gate / init / report / status /
|
|
6
|
+
## 核心导出(24 顶层命令)
|
|
7
|
+
check / validate / passes-gate / init / report / status / spec / acceptance / performance / security / contract / review / command / sync-docs / knowledge / sdd / failure / posteval-plan / release / analyze-sessions / update-user-model / constraints / doc-freshness-check / spec-baseline-check
|
|
8
|
+
|
|
9
|
+
`constraints` 下挂治理子命令:`constraints report`(使用统计 + 退役候选诊断 + 配置健康 + 注入漂移,`--export` 脱敏)、`constraints retire`(交互选择 + 人确认退役,config.yml retired 元数据 + KnowledgeStore 沉淀 + CLAUDE.md 注入段同步)。
|
|
8
10
|
|
|
9
11
|
## 依赖关系
|
|
10
12
|
- 依赖 `src/core/constraints/` 约束引擎
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* check 命令注入漂移警告测试(ADR-0001 决策 7,P6)
|
|
3
|
+
*
|
|
4
|
+
* 警告不阻断:漂移项目跑 check 仍 0 退出;版本漂移单独显眼警告;
|
|
5
|
+
* 无漂移零输出。使用真实临时目录(漂移检测读真实 fs),
|
|
6
|
+
* 仅 mock checker(直接判过)与 git 调用。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import * as fs from 'fs';
|
|
10
|
+
import * as os from 'os';
|
|
11
|
+
import * as path from 'path';
|
|
12
|
+
import { check } from '../check';
|
|
13
|
+
import { constraintChecker } from '../../../core/constraints/checker';
|
|
14
|
+
import { renderConstraintsSection } from '../../../core/constraints/injection-renderer';
|
|
15
|
+
import { getEffectiveConstraints } from '../../../core/effective-constraints';
|
|
16
|
+
|
|
17
|
+
jest.mock('../../../core/constraints/checker', () => ({
|
|
18
|
+
constraintChecker: {
|
|
19
|
+
setCustomConfig: jest.fn(),
|
|
20
|
+
setTraceRecorder: jest.fn(),
|
|
21
|
+
checkConstraints: jest.fn(),
|
|
22
|
+
},
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
jest.mock('../../../utils/exec', () => ({
|
|
26
|
+
execAsync: jest.fn().mockResolvedValue({ stdout: '', stderr: '' }),
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
jest.mock('child_process', () => ({
|
|
30
|
+
exec: jest.fn(),
|
|
31
|
+
execSync: jest.fn(() => Buffer.from('')),
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
const mockChecker = constraintChecker as jest.Mocked<typeof constraintChecker>;
|
|
35
|
+
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
37
|
+
const REAL_VERSION = require('../../../../package.json').version as string;
|
|
38
|
+
|
|
39
|
+
function makeTmpProject(): string {
|
|
40
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), 'harness-check-drift-test-'));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function writeClaudeMd(root: string, version: string): void {
|
|
44
|
+
const section =
|
|
45
|
+
'## Governance Rules\n' + renderConstraintsSection(getEffectiveConstraints(root), version);
|
|
46
|
+
fs.writeFileSync(path.join(root, 'CLAUDE.md'), `# Test Project\n\n${section}`, 'utf-8');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
describe('check 命令注入漂移警告', () => {
|
|
50
|
+
let consoleSpy: jest.SpyInstance;
|
|
51
|
+
let exitSpy: jest.SpyInstance;
|
|
52
|
+
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
jest.clearAllMocks();
|
|
55
|
+
consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
56
|
+
exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => undefined as never);
|
|
57
|
+
mockChecker.checkConstraints.mockResolvedValue({
|
|
58
|
+
passed: true,
|
|
59
|
+
ironLaws: [],
|
|
60
|
+
guidelines: [],
|
|
61
|
+
tips: [],
|
|
62
|
+
warningCount: 0,
|
|
63
|
+
tipCount: 0,
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
afterEach(() => {
|
|
68
|
+
consoleSpy.mockRestore();
|
|
69
|
+
exitSpy.mockRestore();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const outputText = () => consoleSpy.mock.calls.map(c => String(c[0])).join('\n');
|
|
73
|
+
|
|
74
|
+
it('版本漂移:黄色警告块 + ⚠️⚠️ 版本行,但不阻断(exit 未调用,仍判通过)', async () => {
|
|
75
|
+
const root = makeTmpProject();
|
|
76
|
+
writeClaudeMd(root, '0.0.1-old');
|
|
77
|
+
|
|
78
|
+
await check({ preset: 'standard', staged: false, projectPath: root });
|
|
79
|
+
|
|
80
|
+
const output = outputText();
|
|
81
|
+
expect(output).toContain('约束注入漂移');
|
|
82
|
+
expect(output).toContain('⚠️⚠️');
|
|
83
|
+
expect(output).toContain('agent 上下文中的规则与已安装 harness 版本不一致');
|
|
84
|
+
expect(output).toContain('0.0.1-old');
|
|
85
|
+
expect(output).toContain('npx harness init');
|
|
86
|
+
// 不阻断:检查仍通过、未调用 process.exit
|
|
87
|
+
expect(output).toContain('约束检查通过');
|
|
88
|
+
expect(exitSpy).not.toHaveBeenCalled();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('内容漂移:手改一条 → 警告块含缺失/多余计数,exit 未调用', async () => {
|
|
92
|
+
const root = makeTmpProject();
|
|
93
|
+
writeClaudeMd(root, REAL_VERSION);
|
|
94
|
+
const claudeMdPath = path.join(root, 'CLAUDE.md');
|
|
95
|
+
const content = fs.readFileSync(claudeMdPath, 'utf-8');
|
|
96
|
+
const originalLine = content.split('\n').find(l => l.startsWith('- **'))!;
|
|
97
|
+
fs.writeFileSync(claudeMdPath, content.replace(originalLine, originalLine.replace(/: .+$/, ': 篡改')), 'utf-8');
|
|
98
|
+
|
|
99
|
+
await check({ preset: 'standard', staged: false, projectPath: root });
|
|
100
|
+
|
|
101
|
+
const output = outputText();
|
|
102
|
+
expect(output).toContain('内容漂移: 缺失 1 条 / 多余 1 条');
|
|
103
|
+
expect(output).toContain('约束检查通过');
|
|
104
|
+
expect(exitSpy).not.toHaveBeenCalled();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('无漂移:零警告输出(不增加噪音)', async () => {
|
|
108
|
+
const root = makeTmpProject();
|
|
109
|
+
writeClaudeMd(root, REAL_VERSION);
|
|
110
|
+
|
|
111
|
+
await check({ preset: 'standard', staged: false, projectPath: root });
|
|
112
|
+
|
|
113
|
+
const output = outputText();
|
|
114
|
+
expect(output).not.toContain('注入漂移');
|
|
115
|
+
expect(output).toContain('约束检查通过');
|
|
116
|
+
expect(exitSpy).not.toHaveBeenCalled();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('未注入(无 CLAUDE.md):不警告', async () => {
|
|
120
|
+
const root = makeTmpProject();
|
|
121
|
+
|
|
122
|
+
await check({ preset: 'standard', staged: false, projectPath: root });
|
|
123
|
+
|
|
124
|
+
expect(outputText()).not.toContain('注入漂移');
|
|
125
|
+
expect(exitSpy).not.toHaveBeenCalled();
|
|
126
|
+
});
|
|
127
|
+
});
|