@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
package/src/cli/commands/init.ts
CHANGED
|
@@ -11,7 +11,12 @@ import * as path from 'path';
|
|
|
11
11
|
import * as yaml from 'js-yaml';
|
|
12
12
|
import { createExampleCheckpoint, createExampleResolutions } from './validate';
|
|
13
13
|
import { detectSourceRoots } from '../../utils/detect-source-roots';
|
|
14
|
-
import {
|
|
14
|
+
import { getEffectiveConstraints } from '../../core/effective-constraints';
|
|
15
|
+
import {
|
|
16
|
+
CONSTRAINTS_START_MARKER,
|
|
17
|
+
CONSTRAINTS_END_MARKER,
|
|
18
|
+
renderConstraintsSection,
|
|
19
|
+
} from '../../core/constraints/injection-renderer';
|
|
15
20
|
|
|
16
21
|
export interface InitOptions {
|
|
17
22
|
/** 项目路径 */
|
|
@@ -507,18 +512,15 @@ custom_constraints:
|
|
|
507
512
|
}
|
|
508
513
|
|
|
509
514
|
/**
|
|
510
|
-
*
|
|
511
|
-
*
|
|
512
|
-
* - 如果 CLAUDE.md 不存在,创建并写入完整约束段
|
|
513
|
-
* - 如果存在 HARNESS_CONSTRAINTS_START/END 标记,替换标记间内容
|
|
514
|
-
* - 如果不存在标记,在文件末尾追加约束段
|
|
515
|
-
*
|
|
516
|
-
/**
|
|
517
|
-
* 在 CLAUDE.md 顶部写入 Output Style 段(仅首次创建,后续不覆盖)
|
|
515
|
+
* Output Style 段标记(ADR-0001:init 只写标记区间内,标记外只读)
|
|
518
516
|
*/
|
|
519
|
-
const
|
|
520
|
-
|
|
521
|
-
|
|
517
|
+
const OUTPUT_STYLE_START = '<!-- HARNESS_OUTPUT_STYLE_START -->';
|
|
518
|
+
const OUTPUT_STYLE_END = '<!-- HARNESS_OUTPUT_STYLE_END -->';
|
|
519
|
+
|
|
520
|
+
/** 旧版 harness 无标记 Output Style 段的特征串(用于区分 harness 写入 vs 用户自写) */
|
|
521
|
+
const LEGACY_OUTPUT_STYLE_FINGERPRINT = 'Terse like caveman';
|
|
522
|
+
|
|
523
|
+
const OUTPUT_STYLE_BODY = [
|
|
522
524
|
'Terse like caveman. Technical substance exact. Only fluff die.',
|
|
523
525
|
'Drop: articles, filler (just/really/basically), pleasantries (sure/certainly/happy to), hedging.',
|
|
524
526
|
'Fragments OK. Short synonyms. Code blocks unchanged. Error messages quoted exact.',
|
|
@@ -527,25 +529,87 @@ const OUTPUT_STYLE_SECTION = [
|
|
|
527
529
|
'Read existing files before writing. Don\'t re-read unless changed.',
|
|
528
530
|
'Skip files over 100KB unless required.',
|
|
529
531
|
'Don\'t guess APIs, versions, flags, commit SHAs, or package names. Verify before asserting.',
|
|
530
|
-
'',
|
|
531
532
|
].join('\n');
|
|
532
533
|
|
|
533
|
-
|
|
534
|
+
/** 渲染带标记的 Output Style 段(含 `## Output Style` 标题,以换行结尾) */
|
|
535
|
+
function renderOutputStyleSection(): string {
|
|
536
|
+
return `${OUTPUT_STYLE_START}\n## Output Style\n\n${OUTPUT_STYLE_BODY}\n${OUTPUT_STYLE_END}\n`;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* 在 CLAUDE.md 顶部写入 Output Style 段(标记化、幂等)
|
|
541
|
+
*
|
|
542
|
+
* - 已有 HARNESS_OUTPUT_STYLE 标记:替换标记间内容
|
|
543
|
+
* - 无标记但存在旧版 harness 写入的无标记段(特征串匹配):原位置换为标记版
|
|
544
|
+
* - 无标记且 `## Output Style` 段为用户自写(特征串不匹配):跳过并提示,不重复追加
|
|
545
|
+
* - 完全没有该段:在文件顶部插入标记版
|
|
546
|
+
*/
|
|
547
|
+
export async function setupClaudeMdOutputStyle(projectPath: string): Promise<void> {
|
|
534
548
|
const claudeMdPath = path.join(projectPath, 'CLAUDE.md');
|
|
549
|
+
let content: string;
|
|
535
550
|
try {
|
|
536
|
-
|
|
537
|
-
if (content.includes('## Output Style')) return; // 已存在,不覆盖
|
|
538
|
-
const newContent = OUTPUT_STYLE_SECTION + '\n' + content;
|
|
539
|
-
await fs.writeFile(claudeMdPath, newContent, 'utf-8');
|
|
551
|
+
content = await fs.readFile(claudeMdPath, 'utf-8');
|
|
540
552
|
} catch {
|
|
541
553
|
// CLAUDE.md 不存在——setupClaudeMdConstraints 会创建它
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const section = renderOutputStyleSection();
|
|
558
|
+
const startIdx = content.indexOf(OUTPUT_STYLE_START);
|
|
559
|
+
const endIdx = content.indexOf(OUTPUT_STYLE_END);
|
|
560
|
+
|
|
561
|
+
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
562
|
+
// 替换标记间内容(标记本身也替换,保持最新文本)。
|
|
563
|
+
// 尾部换行规范化:标记后恰好保留一个换行分隔,保证重复运行幂等。
|
|
564
|
+
const after = content.slice(endIdx + OUTPUT_STYLE_END.length).replace(/^\n+/, '');
|
|
565
|
+
const newContent = content.slice(0, startIdx) + section + (after ? '\n' + after : '');
|
|
566
|
+
if (newContent !== content) {
|
|
567
|
+
await fs.writeFile(claudeMdPath, newContent, 'utf-8');
|
|
568
|
+
console.log(chalk.green('✅ 已更新 CLAUDE.md Output Style 段'));
|
|
569
|
+
}
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// 检测旧版无标记 `## Output Style` 段
|
|
574
|
+
const legacyMatch = /^## Output Style[ \t]*$/m.exec(content);
|
|
575
|
+
if (legacyMatch) {
|
|
576
|
+
const sectionStart = legacyMatch.index;
|
|
577
|
+
const afterHeading = sectionStart + legacyMatch[0].length;
|
|
578
|
+
const nextHeadingOffset = content.slice(afterHeading).search(/^#{1,6} /m);
|
|
579
|
+
const sectionEnd = nextHeadingOffset === -1 ? content.length : afterHeading + nextHeadingOffset;
|
|
580
|
+
const legacySection = content.slice(sectionStart, sectionEnd);
|
|
581
|
+
|
|
582
|
+
if (!legacySection.includes(LEGACY_OUTPUT_STYLE_FINGERPRINT)) {
|
|
583
|
+
// 用户自写的同名 section:不动,也不追加(避免重复)
|
|
584
|
+
console.log(chalk.yellow('⚠️ CLAUDE.md 已存在自定义 "## Output Style" 段,跳过 harness Output Style 注入'));
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// 旧版 harness 写入:原位置换为标记版
|
|
589
|
+
const before = content.slice(0, sectionStart);
|
|
590
|
+
const after = content.slice(sectionEnd);
|
|
591
|
+
const newContent = before + section + (after.length > 0 ? '\n' + after : '');
|
|
592
|
+
await fs.writeFile(claudeMdPath, newContent, 'utf-8');
|
|
593
|
+
console.log(chalk.green('✅ 已将 CLAUDE.md Output Style 段迁移为标记化管理'));
|
|
594
|
+
return;
|
|
542
595
|
}
|
|
596
|
+
|
|
597
|
+
// 完全没有该段:插入文件顶部
|
|
598
|
+
await fs.writeFile(claudeMdPath, section + '\n' + content, 'utf-8');
|
|
599
|
+
console.log(chalk.green('✅ 已在 CLAUDE.md 顶部写入 Output Style 段'));
|
|
543
600
|
}
|
|
544
601
|
|
|
545
602
|
/**
|
|
546
|
-
*
|
|
603
|
+
* 在 CLAUDE.md 中写入/更新 Governance Rules 约束段
|
|
604
|
+
*
|
|
605
|
+
* - 约束集来自 getEffectiveConstraints(ADR-0001):preset 裁剪、config.yml
|
|
606
|
+
* 禁用、custom 追加、scenes 过滤全部反映在注入文本里
|
|
607
|
+
* - 期望段文本由纯函数 renderConstraintsSection 渲染(P6 漂移校验复用)
|
|
608
|
+
* - 如果 CLAUDE.md 不存在,创建并写入完整约束段
|
|
609
|
+
* - 如果存在 HARNESS_CONSTRAINTS_START/END 标记,替换标记间内容
|
|
610
|
+
* - 如果不存在标记,在文件末尾追加约束段
|
|
547
611
|
*/
|
|
548
|
-
async function setupClaudeMdConstraints(projectPath: string): Promise<void> {
|
|
612
|
+
export async function setupClaudeMdConstraints(projectPath: string): Promise<void> {
|
|
549
613
|
const claudeMdPath = path.join(projectPath, 'CLAUDE.md');
|
|
550
614
|
|
|
551
615
|
// 读取 harness 版本
|
|
@@ -558,44 +622,10 @@ async function setupClaudeMdConstraints(projectPath: string): Promise<void> {
|
|
|
558
622
|
// fallback to 'unknown'
|
|
559
623
|
}
|
|
560
624
|
|
|
561
|
-
//
|
|
562
|
-
const
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
// Iron Laws
|
|
567
|
-
const ironLawItems = Object.entries(IRON_LAWS).filter(([, c]) => c.promptInjection);
|
|
568
|
-
if (ironLawItems.length > 0) {
|
|
569
|
-
bodyLines.push('### Iron Laws (违反将阻断)');
|
|
570
|
-
for (const [id, c] of ironLawItems) {
|
|
571
|
-
bodyLines.push(`- **${id}**: ${c.promptInjection}`);
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
// Guidelines
|
|
576
|
-
const guidelineItems = Object.entries(GUIDELINES).filter(([, c]) => c.promptInjection);
|
|
577
|
-
if (guidelineItems.length > 0) {
|
|
578
|
-
bodyLines.push('');
|
|
579
|
-
bodyLines.push('### Guidelines (应遵循)');
|
|
580
|
-
for (const [id, c] of guidelineItems) {
|
|
581
|
-
bodyLines.push(`- **${id}**: ${c.promptInjection}`);
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
// Tips
|
|
586
|
-
const tipItems = Object.entries(TIPS).filter(([, c]) => c.promptInjection);
|
|
587
|
-
if (tipItems.length > 0) {
|
|
588
|
-
bodyLines.push('');
|
|
589
|
-
bodyLines.push('### Tips');
|
|
590
|
-
for (const [id, c] of tipItems) {
|
|
591
|
-
bodyLines.push(`- **${id}**: ${c.promptInjection}`);
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
bodyLines.push('<!-- HARNESS_CONSTRAINTS_END -->');
|
|
596
|
-
|
|
597
|
-
const fullSection = '## Governance Rules\n' + bodyLines.join('\n') + '\n';
|
|
598
|
-
const bodyOnly = bodyLines.join('\n') + '\n';
|
|
625
|
+
// 生效约束集 → 渲染期望段(纯函数,与写文件分离)
|
|
626
|
+
const constraints = getEffectiveConstraints(projectPath);
|
|
627
|
+
const bodyOnly = renderConstraintsSection(constraints, version);
|
|
628
|
+
const fullSection = '## Governance Rules\n' + bodyOnly;
|
|
599
629
|
|
|
600
630
|
// 检查 CLAUDE.md 是否存在
|
|
601
631
|
let existingContent: string;
|
|
@@ -607,9 +637,6 @@ async function setupClaudeMdConstraints(projectPath: string): Promise<void> {
|
|
|
607
637
|
existingContent = '';
|
|
608
638
|
}
|
|
609
639
|
|
|
610
|
-
const startMarker = '<!-- HARNESS_CONSTRAINTS_START -->';
|
|
611
|
-
const endMarker = '<!-- HARNESS_CONSTRAINTS_END -->';
|
|
612
|
-
|
|
613
640
|
if (!fileExists) {
|
|
614
641
|
// 创建新文件
|
|
615
642
|
await fs.writeFile(claudeMdPath, fullSection, 'utf-8');
|
|
@@ -617,14 +644,15 @@ async function setupClaudeMdConstraints(projectPath: string): Promise<void> {
|
|
|
617
644
|
return;
|
|
618
645
|
}
|
|
619
646
|
|
|
620
|
-
const startIdx = existingContent.indexOf(
|
|
621
|
-
const endIdx = existingContent.indexOf(
|
|
647
|
+
const startIdx = existingContent.indexOf(CONSTRAINTS_START_MARKER);
|
|
648
|
+
const endIdx = existingContent.indexOf(CONSTRAINTS_END_MARKER);
|
|
622
649
|
|
|
623
650
|
if (startIdx !== -1 && endIdx !== -1) {
|
|
624
|
-
//
|
|
651
|
+
// 替换标记间内容(标记本身也替换,保持包括最新版本号)。
|
|
652
|
+
// 尾部换行规范化:标记后恰好保留一个换行分隔,保证重复运行幂等。
|
|
625
653
|
const before = existingContent.slice(0, startIdx);
|
|
626
|
-
const after = existingContent.slice(endIdx +
|
|
627
|
-
const newContent = before + bodyOnly + after;
|
|
654
|
+
const after = existingContent.slice(endIdx + CONSTRAINTS_END_MARKER.length).replace(/^\n+/, '');
|
|
655
|
+
const newContent = before + bodyOnly + (after ? '\n' + after : '');
|
|
628
656
|
await fs.writeFile(claudeMdPath, newContent, 'utf-8');
|
|
629
657
|
console.log(chalk.green(`✅ 已更新 CLAUDE.md 治理约束 (v${version})`));
|
|
630
658
|
} else {
|
|
@@ -8,7 +8,7 @@ import chalk from 'chalk';
|
|
|
8
8
|
import * as fs from 'fs/promises';
|
|
9
9
|
import { ConstraintChecker } from '../../core/constraints/checker';
|
|
10
10
|
import { getTraceCollector } from '../../monitoring/traces';
|
|
11
|
-
import { IRON_LAWS, GUIDELINES,
|
|
11
|
+
import { IRON_LAWS, GUIDELINES, PROMPTS } from '../../core/constraints/definitions';
|
|
12
12
|
import { executeWithCollect } from '../../failure/constraint-handler';
|
|
13
13
|
import type { ConstraintContext } from '../../types/constraint';
|
|
14
14
|
|
|
@@ -28,7 +28,7 @@ interface ReportData {
|
|
|
28
28
|
total: number;
|
|
29
29
|
ironLaws: number;
|
|
30
30
|
guidelines: number;
|
|
31
|
-
|
|
31
|
+
prompts: number;
|
|
32
32
|
passed: number;
|
|
33
33
|
failed: number;
|
|
34
34
|
warnings: number;
|
|
@@ -50,7 +50,7 @@ export async function report(options: ReportOptions): Promise<void> {
|
|
|
50
50
|
const checker = ConstraintChecker.getInstance();
|
|
51
51
|
checker.setTraceRecorder(getTraceCollector());
|
|
52
52
|
|
|
53
|
-
const allConstraints = { ...IRON_LAWS, ...GUIDELINES, ...
|
|
53
|
+
const allConstraints = { ...IRON_LAWS, ...GUIDELINES, ...PROMPTS };
|
|
54
54
|
const totalConstraints = Object.keys(allConstraints).length;
|
|
55
55
|
|
|
56
56
|
const context: ConstraintContext = {
|
|
@@ -81,7 +81,7 @@ export async function report(options: ReportOptions): Promise<void> {
|
|
|
81
81
|
total: totalConstraints,
|
|
82
82
|
ironLaws: Object.keys(IRON_LAWS).length,
|
|
83
83
|
guidelines: Object.keys(GUIDELINES).length,
|
|
84
|
-
|
|
84
|
+
prompts: Object.keys(PROMPTS).length,
|
|
85
85
|
passed: result.passed ? totalConstraints : totalConstraints - violations.length,
|
|
86
86
|
failed: failedIronLaws.length,
|
|
87
87
|
warnings: failedGuidelines.length,
|
|
@@ -129,7 +129,7 @@ function generateMarkdownReport(data: ReportData): string {
|
|
|
129
129
|
`| 总约束 | ${data.constraints.total} |`,
|
|
130
130
|
`| Iron Laws | ${data.constraints.ironLaws} |`,
|
|
131
131
|
`| Guidelines | ${data.constraints.guidelines} |`,
|
|
132
|
-
`|
|
|
132
|
+
`| Prompts | ${data.constraints.prompts} |`,
|
|
133
133
|
`| 通过 | ${data.constraints.passed} |`,
|
|
134
134
|
`| 失败 (error) | ${data.constraints.failed} |`,
|
|
135
135
|
`| 警告 (warning) | ${data.constraints.warnings} |`,
|
|
@@ -184,7 +184,7 @@ function generateHtmlReport(data: ReportData): string {
|
|
|
184
184
|
<tr><td>总约束</td><td>${data.constraints.total}</td></tr>
|
|
185
185
|
<tr><td>Iron Laws</td><td>${data.constraints.ironLaws}</td></tr>
|
|
186
186
|
<tr><td>Guidelines</td><td>${data.constraints.guidelines}</td></tr>
|
|
187
|
-
<tr><td>
|
|
187
|
+
<tr><td>Prompts</td><td>${data.constraints.prompts}</td></tr>
|
|
188
188
|
<tr><td>通过</td><td class="passed">${data.constraints.passed}</td></tr>
|
|
189
189
|
<tr><td>失败</td><td class="failed">${data.constraints.failed}</td></tr>
|
|
190
190
|
<tr><td>警告</td><td>${data.constraints.warnings}</td></tr>
|
|
@@ -9,6 +9,7 @@ import * as fs from 'fs';
|
|
|
9
9
|
import * as path from 'path';
|
|
10
10
|
import { TraceCollector } from '../../monitoring/traces';
|
|
11
11
|
import { TraceAnalyzer } from '../../monitoring/trace-analyzer';
|
|
12
|
+
import { DEFAULT_TRACE_FILE } from '../../types/trace';
|
|
12
13
|
import type { TraceSummary, TraceAnomaly } from '../../types/trace';
|
|
13
14
|
|
|
14
15
|
export interface StatusOptions {
|
|
@@ -28,7 +29,7 @@ export interface StatusOptions {
|
|
|
28
29
|
export async function status(options: StatusOptions): Promise<void> {
|
|
29
30
|
const projectPath = options.projectPath || process.cwd();
|
|
30
31
|
const harnessDir = path.join(projectPath, '.harness');
|
|
31
|
-
const tracesPath = path.join(
|
|
32
|
+
const tracesPath = path.join(projectPath, DEFAULT_TRACE_FILE);
|
|
32
33
|
const statePath = path.join(harnessDir, '.state.json');
|
|
33
34
|
|
|
34
35
|
console.log(chalk.blue('📊 Harness 状态'));
|
|
@@ -91,8 +92,7 @@ export async function status(options: StatusOptions): Promise<void> {
|
|
|
91
92
|
|
|
92
93
|
// 下一步建议
|
|
93
94
|
console.log(chalk.blue('💡 下一步建议:'));
|
|
94
|
-
console.log(chalk.gray('
|
|
95
|
-
console.log(chalk.gray(' 2. 运行 harness status --detail 查看详情'));
|
|
95
|
+
console.log(chalk.gray(' 运行 harness status --detail 查看详情'));
|
|
96
96
|
}
|
|
97
97
|
return;
|
|
98
98
|
}
|
|
@@ -159,7 +159,6 @@ export async function status(options: StatusOptions): Promise<void> {
|
|
|
159
159
|
// 下一步建议
|
|
160
160
|
console.log(chalk.blue('💡 下一步建议:'));
|
|
161
161
|
if (anomalies.length > 0) {
|
|
162
|
-
console.log(chalk.gray(' harness flow # 一键执行诊断+提案'));
|
|
163
162
|
console.log(chalk.gray(' harness status --detail # 查看详细状态'));
|
|
164
163
|
} else if (traceCount >= 100) {
|
|
165
164
|
console.log(chalk.gray(' • 状态良好,继续保持!'));
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import * as fs from 'fs/promises';
|
|
10
10
|
import * as path from 'path';
|
|
11
11
|
import { IRON_LAWS, GUIDELINES, TIPS } from '../../../core/constraints/definitions';
|
|
12
|
-
import { readCapabilitiesEntries } from '../../../core/constraints/capabilities-parser';
|
|
12
|
+
import { isCapabilityListingFormat, readCapabilitiesEntries } from '../../../core/constraints/capabilities-parser';
|
|
13
13
|
import { FreshnessRunner } from '../../../core/constraints/doc-freshness/runner';
|
|
14
14
|
import type { CapabilitiesMode } from '../../../core/project-config-loader';
|
|
15
15
|
import type { DocFreshnessCheck } from '../../../types/project-config';
|
|
@@ -35,13 +35,11 @@ export async function parseCapabilitiesFiles(capabilitiesPath: string): Promise<
|
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* 检测 CAPABILITIES.md 是否使用能力清单格式(计数行),而非文件表格格式
|
|
38
|
+
*
|
|
39
|
+
* @deprecated 实现已收敛到 core/constraints/capabilities-parser(与工单 19-B 同方向),
|
|
40
|
+
* 此处保留 re-export 兼容现有调用方
|
|
38
41
|
*/
|
|
39
|
-
export
|
|
40
|
-
// 能力清单格式特征:包含 "CLI Commands (N)" / "Iron Laws (N)" 等计数行
|
|
41
|
-
return /CLI Commands\s*\(\d+\)/.test(content) ||
|
|
42
|
-
/Iron Laws?\s*\(\d+\)/.test(content) ||
|
|
43
|
-
/Guidelines?\s*\(\d+\)/.test(content);
|
|
44
|
-
}
|
|
42
|
+
export { isCapabilityListingFormat };
|
|
45
43
|
|
|
46
44
|
/**
|
|
47
45
|
* 构建 CAPABILITIES.md 能力清单格式的检查配置
|
|
@@ -163,7 +163,7 @@ export async function createExampleCheckpoint(projectPath: string): Promise<void
|
|
|
163
163
|
* 默认 Resolutions(RKB — 约束 → 已知解法映射)
|
|
164
164
|
*/
|
|
165
165
|
const DEFAULT_RESOLUTIONS = {
|
|
166
|
-
|
|
166
|
+
no_fuzzy_completion_claim: {
|
|
167
167
|
title: 'commit message 缺少验证证据',
|
|
168
168
|
fix: '在 commit message body 中附上验证输出:\n`npx harness check --staged` | `npx harness validate` | `npm test -- --coverage`\n确认全部通过后重新 commit。',
|
|
169
169
|
},
|
|
@@ -175,10 +175,6 @@ const DEFAULT_RESOLUTIONS = {
|
|
|
175
175
|
title: '关键目录缺少 CONTEXT.md',
|
|
176
176
|
fix: '在 required_dirs 目录下创建 CONTEXT.md。运行 `npx harness sync-docs` 可自动生成模板。',
|
|
177
177
|
},
|
|
178
|
-
no_coverage_decrease: {
|
|
179
|
-
title: '测试覆盖率下降',
|
|
180
|
-
fix: '运行 `npm test -- --coverage` 查看当前覆盖率。新增文件必须有对应的测试文件。',
|
|
181
|
-
},
|
|
182
178
|
};
|
|
183
179
|
|
|
184
180
|
/**
|
package/src/core/CONTEXT.md
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# core/
|
|
2
2
|
|
|
3
3
|
## 职责
|
|
4
|
-
|
|
4
|
+
约束引擎核心:check/prompt 二元约束系统(ADR-0001)、生效集合并(effective-constraints)、检查点验证器(CSO/passes-gate)、会话管理、Spec 验证器、项目配置加载。
|
|
5
5
|
|
|
6
6
|
## 核心导出
|
|
7
|
-
- `constraints/` — 约束定义(IRON_LAWS/GUIDELINES/TIPS) + 检查引擎(ConstraintChecker) + 拦截器(ConstraintInterceptor) + 缓存(CheckCache)
|
|
7
|
+
- `constraints/` — 约束定义(IRON_LAWS/GUIDELINES/PROMPTS;TIPS 已退役为空表) + 检查引擎(ConstraintChecker) + 拦截器(ConstraintInterceptor) + 缓存(CheckCache) + 注入渲染(injection-renderer)/漂移校验(injection-drift)/使用统计(usage-report)
|
|
8
|
+
- `effective-constraints.ts` — `getEffectiveConstraints(projectRoot)`:全仓唯一生效集来源(内置 → preset → config.yml 禁用 → custom 追加 → scenes 过滤);`lintEffectiveConfig` 配置诊断
|
|
8
9
|
- `validators/` — checkpoints、passes-gate、CSO 验证器
|
|
9
10
|
- `session/` — 会话管理
|
|
10
11
|
- `spec/validator` — SpecValidator
|
|
@@ -17,10 +18,12 @@
|
|
|
17
18
|
- 依赖 `src/monitoring/traces` TraceCollector
|
|
18
19
|
|
|
19
20
|
## 约定
|
|
20
|
-
- 约束定义在 `constraints/definitions.ts`,不在运行时代码中定义
|
|
21
|
+
- 约束定义在 `constraints/definitions/{iron-laws,guidelines,prompts}.ts`,不在运行时代码中定义
|
|
22
|
+
- check 层必须带真实 checker(注册表闭环,引用未注册 checker 构建报错)
|
|
21
23
|
- Iron Law 违规必须 throw ConstraintViolationError
|
|
22
24
|
- 检查器使用单例模式(ConstraintChecker.getInstance())
|
|
23
25
|
|
|
24
26
|
## 注意事项
|
|
25
27
|
- 零 Token 成本:所有分析纯文件操作,无 LLM 调用
|
|
26
|
-
- 约束配置支持 .harness/config.yml
|
|
28
|
+
- 约束配置支持 .harness/config.yml 自定义合并(preset 真实生效)
|
|
29
|
+
- 存在性探测约束(capability_sync/docs_freshness/context_doc_sync)在约定文件缺失时 skip,不计 pass/fail
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 注入漂移校验测试(ADR-0001 决策 7,P6)
|
|
3
|
+
*
|
|
4
|
+
* - 三类漂移各自检出:版本 / 内容(条目级 missing/extra)/ 重复章节
|
|
5
|
+
* - 无漂移静默;无标记段 = 未注入不算漂移;CLAUDE.md 不存在不算漂移
|
|
6
|
+
* - config.yml 变更后未重跑 init → 内容漂移(extra)
|
|
7
|
+
*
|
|
8
|
+
* 使用真实临时目录(getEffectiveConstraints 读真实 fs)。
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import * as fs from 'fs';
|
|
12
|
+
import * as os from 'os';
|
|
13
|
+
import * as path from 'path';
|
|
14
|
+
import { detectInjectionDrift, INJECTION_DRIFT_FIX_HINT } from '../injection-drift';
|
|
15
|
+
import {
|
|
16
|
+
renderConstraintsSection,
|
|
17
|
+
CONSTRAINTS_START_MARKER,
|
|
18
|
+
} from '../injection-renderer';
|
|
19
|
+
import { getEffectiveConstraints } from '../../effective-constraints';
|
|
20
|
+
|
|
21
|
+
const TEST_VERSION = '9.9.9-test';
|
|
22
|
+
|
|
23
|
+
function makeTmpProject(): string {
|
|
24
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), 'harness-drift-test-'));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** 写入与当前生效集完全一致的注入段(版本 TEST_VERSION) */
|
|
28
|
+
function writeSyncedClaudeMd(root: string, extra = ''): string {
|
|
29
|
+
const section =
|
|
30
|
+
'## Governance Rules\n' + renderConstraintsSection(getEffectiveConstraints(root), TEST_VERSION);
|
|
31
|
+
const content = `# Test Project\n\n${section}${extra}`;
|
|
32
|
+
fs.writeFileSync(path.join(root, 'CLAUDE.md'), content, 'utf-8');
|
|
33
|
+
return content;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
describe('detectInjectionDrift', () => {
|
|
37
|
+
it('无漂移:注入段与期望渲染一致 → hasDrift=false', () => {
|
|
38
|
+
const root = makeTmpProject();
|
|
39
|
+
writeSyncedClaudeMd(root);
|
|
40
|
+
|
|
41
|
+
const drift = detectInjectionDrift(root, TEST_VERSION);
|
|
42
|
+
|
|
43
|
+
expect(drift.notInjected).toBe(false);
|
|
44
|
+
expect(drift.hasDrift).toBe(false);
|
|
45
|
+
expect(drift.versionDrift).toBeUndefined();
|
|
46
|
+
expect(drift.contentDrift).toBeUndefined();
|
|
47
|
+
expect(drift.duplicateHeading).toBe(false);
|
|
48
|
+
expect(drift.fixHint).toContain('npx harness init');
|
|
49
|
+
expect(drift.fixHint).toBe(INJECTION_DRIFT_FIX_HINT);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('版本漂移:标记段版本号改旧 → versionDrift', () => {
|
|
53
|
+
const root = makeTmpProject();
|
|
54
|
+
const synced = writeSyncedClaudeMd(root);
|
|
55
|
+
fs.writeFileSync(
|
|
56
|
+
path.join(root, 'CLAUDE.md'),
|
|
57
|
+
synced.replace(`<!-- version: ${TEST_VERSION} -->`, '<!-- version: 0.0.1-old -->'),
|
|
58
|
+
'utf-8'
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const drift = detectInjectionDrift(root, TEST_VERSION);
|
|
62
|
+
|
|
63
|
+
expect(drift.hasDrift).toBe(true);
|
|
64
|
+
expect(drift.versionDrift).toEqual({ expected: TEST_VERSION, actual: '0.0.1-old' });
|
|
65
|
+
expect(drift.contentDrift).toBeUndefined();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('内容漂移:手改标记段内一条 → 原条目 missing、改后条目 extra', () => {
|
|
69
|
+
const root = makeTmpProject();
|
|
70
|
+
const synced = writeSyncedClaudeMd(root);
|
|
71
|
+
const originalLine = synced.split('\n').find(l => l.startsWith('- **'))!;
|
|
72
|
+
const editedLine = originalLine.replace(/: .+$/, ': 手工篡改的注入文本');
|
|
73
|
+
fs.writeFileSync(
|
|
74
|
+
path.join(root, 'CLAUDE.md'),
|
|
75
|
+
synced.replace(originalLine, editedLine),
|
|
76
|
+
'utf-8'
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const drift = detectInjectionDrift(root, TEST_VERSION);
|
|
80
|
+
|
|
81
|
+
expect(drift.hasDrift).toBe(true);
|
|
82
|
+
expect(drift.versionDrift).toBeUndefined();
|
|
83
|
+
expect(drift.contentDrift).toBeDefined();
|
|
84
|
+
expect(drift.contentDrift!.missing).toEqual([originalLine]);
|
|
85
|
+
expect(drift.contentDrift!.extra).toEqual([editedLine]);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('内容漂移:config.yml 变更后未重跑 init → 被禁条目成为 extra', () => {
|
|
89
|
+
const root = makeTmpProject();
|
|
90
|
+
writeSyncedClaudeMd(root);
|
|
91
|
+
// 注入后禁用一条 prompt,但不重跑 init
|
|
92
|
+
fs.mkdirSync(path.join(root, '.harness'), { recursive: true });
|
|
93
|
+
fs.writeFileSync(
|
|
94
|
+
path.join(root, '.harness', 'config.yml'),
|
|
95
|
+
'constraints:\n no_fuzzy_completion_claim:\n enabled: false\n',
|
|
96
|
+
'utf-8'
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const drift = detectInjectionDrift(root, TEST_VERSION);
|
|
100
|
+
|
|
101
|
+
expect(drift.hasDrift).toBe(true);
|
|
102
|
+
expect(drift.contentDrift).toBeDefined();
|
|
103
|
+
expect(drift.contentDrift!.missing).toEqual([]);
|
|
104
|
+
expect(drift.contentDrift!.extra).toHaveLength(1);
|
|
105
|
+
expect(drift.contentDrift!.extra[0]).toContain('no_fuzzy_completion_claim');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('重复章节:标记段之外还有一个 ## Governance Rules 标题 → duplicateHeading', () => {
|
|
109
|
+
const root = makeTmpProject();
|
|
110
|
+
writeSyncedClaudeMd(root, '\n## Governance Rules\n\n旧版遗留的同名章节\n');
|
|
111
|
+
|
|
112
|
+
const drift = detectInjectionDrift(root, TEST_VERSION);
|
|
113
|
+
|
|
114
|
+
expect(drift.duplicateHeading).toBe(true);
|
|
115
|
+
expect(drift.hasDrift).toBe(true);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('无标记段 = 未注入,不算漂移', () => {
|
|
119
|
+
const root = makeTmpProject();
|
|
120
|
+
fs.writeFileSync(path.join(root, 'CLAUDE.md'), '# Test Project\n\n## Governance Rules\n\n用户自写内容\n', 'utf-8');
|
|
121
|
+
|
|
122
|
+
const drift = detectInjectionDrift(root, TEST_VERSION);
|
|
123
|
+
|
|
124
|
+
expect(drift.notInjected).toBe(true);
|
|
125
|
+
expect(drift.hasDrift).toBe(false);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('CLAUDE.md 不存在 = 未注入,不算漂移', () => {
|
|
129
|
+
const root = makeTmpProject();
|
|
130
|
+
|
|
131
|
+
const drift = detectInjectionDrift(root, TEST_VERSION);
|
|
132
|
+
|
|
133
|
+
expect(drift.notInjected).toBe(true);
|
|
134
|
+
expect(drift.hasDrift).toBe(false);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('默认版本参数读 package.json:与真实版本一致的注入段无版本漂移', () => {
|
|
138
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
139
|
+
const realVersion = require('../../../../package.json').version as string;
|
|
140
|
+
const root = makeTmpProject();
|
|
141
|
+
const section =
|
|
142
|
+
'## Governance Rules\n' + renderConstraintsSection(getEffectiveConstraints(root), realVersion);
|
|
143
|
+
fs.writeFileSync(path.join(root, 'CLAUDE.md'), section, 'utf-8');
|
|
144
|
+
|
|
145
|
+
const drift = detectInjectionDrift(root);
|
|
146
|
+
|
|
147
|
+
expect(drift.versionDrift).toBeUndefined();
|
|
148
|
+
expect(drift.hasDrift).toBe(false);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('标记段含 ' + CONSTRAINTS_START_MARKER + ' 但版本行缺失 → versionDrift actual=(缺失)', () => {
|
|
152
|
+
const root = makeTmpProject();
|
|
153
|
+
const synced = writeSyncedClaudeMd(root);
|
|
154
|
+
fs.writeFileSync(
|
|
155
|
+
path.join(root, 'CLAUDE.md'),
|
|
156
|
+
synced.replace(`<!-- version: ${TEST_VERSION} -->\n`, ''),
|
|
157
|
+
'utf-8'
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
const drift = detectInjectionDrift(root, TEST_VERSION);
|
|
161
|
+
|
|
162
|
+
expect(drift.versionDrift).toEqual({ expected: TEST_VERSION, actual: '(缺失)' });
|
|
163
|
+
});
|
|
164
|
+
});
|
|
@@ -24,6 +24,7 @@ describe('ConstraintInterceptor', () => {
|
|
|
24
24
|
|
|
25
25
|
const mockIronLaw = {
|
|
26
26
|
id: 'test_iron_law',
|
|
27
|
+
kind: 'check' as const,
|
|
27
28
|
rule: 'Test Iron Law',
|
|
28
29
|
message: 'Test iron law message',
|
|
29
30
|
level: 'iron_law' as const,
|
|
@@ -34,6 +35,7 @@ describe('ConstraintInterceptor', () => {
|
|
|
34
35
|
|
|
35
36
|
const mockGuideline = {
|
|
36
37
|
id: 'test_guideline',
|
|
38
|
+
kind: 'check' as const,
|
|
37
39
|
rule: 'Test Guideline',
|
|
38
40
|
message: 'Test guideline message',
|
|
39
41
|
level: 'guideline' as const,
|
|
@@ -63,6 +65,7 @@ describe('ConstraintInterceptor', () => {
|
|
|
63
65
|
ironLaws: { test_iron_law: mockIronLaw },
|
|
64
66
|
guidelines: { test_guideline: mockGuideline },
|
|
65
67
|
tips: {},
|
|
68
|
+
prompts: {},
|
|
66
69
|
});
|
|
67
70
|
});
|
|
68
71
|
|
|
@@ -200,6 +203,7 @@ describe('ConstraintInterceptor', () => {
|
|
|
200
203
|
ironLaws: {},
|
|
201
204
|
guidelines: {},
|
|
202
205
|
tips: {},
|
|
206
|
+
prompts: {},
|
|
203
207
|
});
|
|
204
208
|
|
|
205
209
|
const result = await interceptor.intercept('code_implementation', mockContext);
|
|
@@ -69,6 +69,19 @@ export function readCapabilitiesEntries(capabilitiesPath: string, options: Capab
|
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
/**
|
|
73
|
+
* 检测 CAPABILITIES.md 是否使用能力清单格式(计数行),而非文件表格格式
|
|
74
|
+
*
|
|
75
|
+
* 清单格式没有文件表可核对,计数由 sync-docs 自动维护,
|
|
76
|
+
* checker 与 sync-docs 都应按此跳过条目级比对。
|
|
77
|
+
*/
|
|
78
|
+
export function isCapabilityListingFormat(content: string): boolean {
|
|
79
|
+
// 能力清单格式特征:包含 "CLI Commands (N)" / "Iron Laws (N)" 等计数行
|
|
80
|
+
return /CLI Commands\s*\(\d+\)/.test(content) ||
|
|
81
|
+
/Iron Laws?\s*\(\d+\)/.test(content) ||
|
|
82
|
+
/Guidelines?\s*\(\d+\)/.test(content);
|
|
83
|
+
}
|
|
84
|
+
|
|
72
85
|
/**
|
|
73
86
|
* 未覆盖文件的聚合目录(module 模式)
|
|
74
87
|
*
|