@dommaker/harness 0.16.8 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/README.md +33 -32
- package/bin/harness.js +34 -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 +19 -2
- package/dist/cli/commands/sync-docs/capabilities-syncer.d.ts.map +1 -1
- package/dist/cli/commands/sync-docs/capabilities-syncer.js +79 -19
- package/dist/cli/commands/sync-docs/capabilities-syncer.js.map +1 -1
- package/dist/cli/commands/sync-docs/index.d.ts +2 -0
- package/dist/cli/commands/sync-docs/index.d.ts.map +1 -1
- package/dist/cli/commands/sync-docs/index.js +83 -20
- package/dist/cli/commands/sync-docs/index.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 +14 -0
- package/dist/core/constraints/capabilities-parser.d.ts.map +1 -1
- package/dist/core/constraints/capabilities-parser.js +30 -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 +10 -0
- package/dist/core/constraints/checkers/capability-sync.d.ts.map +1 -1
- package/dist/core/constraints/checkers/capability-sync.js +73 -11
- 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 +33 -574
- 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 +28 -3
- package/dist/core/project-config-loader.d.ts.map +1 -1
- package/dist/core/project-config-loader.js +74 -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 +32 -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 +356 -331
- 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 +4 -4
- package/src/__tests__/project-config-loader.test.ts +40 -1
- 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/__tests__/sync-docs.test.ts +163 -0
- 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 +90 -15
- package/src/cli/commands/sync-docs/index.ts +87 -24
- 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 +28 -0
- package/src/core/constraints/checker.ts +73 -28
- package/src/core/constraints/checkers/capability-sync.ts +89 -18
- 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 +33 -599
- 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 +82 -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 +37 -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(' • 状态良好,继续保持!'));
|
|
@@ -7,9 +7,11 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import * as fs from 'fs/promises';
|
|
10
|
+
import * as path from 'path';
|
|
10
11
|
import { IRON_LAWS, GUIDELINES, TIPS } from '../../../core/constraints/definitions';
|
|
11
|
-
import { readCapabilitiesEntries } from '../../../core/constraints/capabilities-parser';
|
|
12
|
+
import { isCapabilityListingFormat, readCapabilitiesEntries } from '../../../core/constraints/capabilities-parser';
|
|
12
13
|
import { FreshnessRunner } from '../../../core/constraints/doc-freshness/runner';
|
|
14
|
+
import type { CapabilitiesMode } from '../../../core/project-config-loader';
|
|
13
15
|
import type { DocFreshnessCheck } from '../../../types/project-config';
|
|
14
16
|
import type { ModuleInfo, SyncResult } from './project-reader';
|
|
15
17
|
|
|
@@ -33,13 +35,11 @@ export async function parseCapabilitiesFiles(capabilitiesPath: string): Promise<
|
|
|
33
35
|
|
|
34
36
|
/**
|
|
35
37
|
* 检测 CAPABILITIES.md 是否使用能力清单格式(计数行),而非文件表格格式
|
|
38
|
+
*
|
|
39
|
+
* @deprecated 实现已收敛到 core/constraints/capabilities-parser(与工单 19-B 同方向),
|
|
40
|
+
* 此处保留 re-export 兼容现有调用方
|
|
36
41
|
*/
|
|
37
|
-
export
|
|
38
|
-
// 能力清单格式特征:包含 "CLI Commands (N)" / "Iron Laws (N)" 等计数行
|
|
39
|
-
return /CLI Commands\s*\(\d+\)/.test(content) ||
|
|
40
|
-
/Iron Laws?\s*\(\d+\)/.test(content) ||
|
|
41
|
-
/Guidelines?\s*\(\d+\)/.test(content);
|
|
42
|
-
}
|
|
42
|
+
export { isCapabilityListingFormat };
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
45
|
* 构建 CAPABILITIES.md 能力清单格式的检查配置
|
|
@@ -137,19 +137,23 @@ export function updateCapabilityCounts(content: string, projectPath: string): st
|
|
|
137
137
|
|
|
138
138
|
/**
|
|
139
139
|
* 更新 CAPABILITIES.md 文件
|
|
140
|
+
*
|
|
141
|
+
* module 模式(governance.capabilities.mode=module)下不再为新文件自动加表格行
|
|
142
|
+
* (目录条目需人工策划登记),幽灵行剔除与「最后更新」刷新保留。
|
|
140
143
|
*/
|
|
141
144
|
export async function updateCapabilitiesFile(
|
|
142
145
|
capabilitiesPath: string,
|
|
143
146
|
currentModules: ModuleInfo[],
|
|
144
147
|
existingFiles: string[],
|
|
145
148
|
result: SyncResult,
|
|
149
|
+
mode: CapabilitiesMode = 'file',
|
|
146
150
|
): Promise<void> {
|
|
147
151
|
let content: string;
|
|
148
152
|
try {
|
|
149
153
|
content = await fs.readFile(capabilitiesPath, 'utf-8');
|
|
150
154
|
} catch {
|
|
151
|
-
//
|
|
152
|
-
content = generateCapabilitiesContent(currentModules);
|
|
155
|
+
// 文件不存在,创建新的(module 模式生成按目录聚合的模板)
|
|
156
|
+
content = generateCapabilitiesContent(currentModules, mode);
|
|
153
157
|
await fs.writeFile(capabilitiesPath, content, 'utf-8');
|
|
154
158
|
return;
|
|
155
159
|
}
|
|
@@ -164,8 +168,8 @@ export async function updateCapabilitiesFile(
|
|
|
164
168
|
content = content.replace(rowRegex, '');
|
|
165
169
|
}
|
|
166
170
|
|
|
167
|
-
//
|
|
168
|
-
if (result.added.length > 0) {
|
|
171
|
+
// 添加新文件的行(在最后一个表格行之后);module 模式跳过
|
|
172
|
+
if (mode !== 'module' && result.added.length > 0) {
|
|
169
173
|
const getBasenameLocal = (f: string) => f.split('/').pop()!;
|
|
170
174
|
const addedModules = currentModules.filter(m => result.added.includes(getBasenameLocal(m.file)));
|
|
171
175
|
const tableEndRegex = /(^\|[^|]+\|[^|]+\|[^|]+\|\s*$)/gm;
|
|
@@ -186,8 +190,8 @@ export async function updateCapabilitiesFile(
|
|
|
186
190
|
// 清理多余空行
|
|
187
191
|
content = content.replace(/\n{3,}/g, '\n\n');
|
|
188
192
|
} else {
|
|
189
|
-
//
|
|
190
|
-
content += '\n\n' + generateModuleTable(currentModules);
|
|
193
|
+
// 没有表格,追加模块表格(module 模式按目录聚合)
|
|
194
|
+
content += '\n\n' + (mode === 'module' ? generateDirTable(currentModules) : generateModuleTable(currentModules));
|
|
191
195
|
}
|
|
192
196
|
|
|
193
197
|
// 更新最后更新时间
|
|
@@ -203,7 +207,7 @@ export async function updateCapabilitiesFile(
|
|
|
203
207
|
/**
|
|
204
208
|
* 生成 CAPABILITIES.md 内容
|
|
205
209
|
*/
|
|
206
|
-
function generateCapabilitiesContent(modules: ModuleInfo[]): string {
|
|
210
|
+
function generateCapabilitiesContent(modules: ModuleInfo[], mode: CapabilitiesMode = 'file'): string {
|
|
207
211
|
const now = new Date().toISOString().split('T')[0];
|
|
208
212
|
return `# CAPABILITIES.md
|
|
209
213
|
|
|
@@ -211,7 +215,7 @@ function generateCapabilitiesContent(modules: ModuleInfo[]): string {
|
|
|
211
215
|
|
|
212
216
|
---
|
|
213
217
|
|
|
214
|
-
${generateModuleTable(modules)}
|
|
218
|
+
${mode === 'module' ? generateDirTable(modules) : generateModuleTable(modules)}
|
|
215
219
|
`;
|
|
216
220
|
}
|
|
217
221
|
|
|
@@ -227,3 +231,74 @@ function generateModuleTable(modules: ModuleInfo[]): string {
|
|
|
227
231
|
|
|
228
232
|
return `| 模块 | 文件 | 说明 |\n|------|------|------|\n${rows}`;
|
|
229
233
|
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* 生成按目录聚合的模块表格(module 模式模板)
|
|
237
|
+
*
|
|
238
|
+
* 每个源码子目录一行目录条目(| core | src/core/ | core |),
|
|
239
|
+
* 文件直接位于源码根时聚到根目录一行。
|
|
240
|
+
*/
|
|
241
|
+
function generateDirTable(modules: ModuleInfo[]): string {
|
|
242
|
+
if (modules.length === 0) return '';
|
|
243
|
+
|
|
244
|
+
const dirs: string[] = [];
|
|
245
|
+
for (const m of modules) {
|
|
246
|
+
const dir = path.posix.dirname(m.file);
|
|
247
|
+
const key = dir === '.' ? '' : dir + '/';
|
|
248
|
+
if (key && !dirs.includes(key)) dirs.push(key);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const rows = dirs.map(d => {
|
|
252
|
+
const name = d.replace(/\/$/, '').split('/').pop()!;
|
|
253
|
+
return `| ${name} | ${d} | ${name} |`;
|
|
254
|
+
}).join('\n');
|
|
255
|
+
|
|
256
|
+
return `| 模块 | 文件 | 说明 |\n|------|------|------|\n${rows}`;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* 将 CAPABILITIES.md 文件表格折叠为目录条目(--compact 一次性迁移)
|
|
261
|
+
*
|
|
262
|
+
* 按文件条目(第二列)的 dirname 分组:同组 ≥2 个文件条目折叠为一行目录条目
|
|
263
|
+
* (替换组内第一行、删除其余行,说明取组内第一行,为空则用目录名);
|
|
264
|
+
* 单独成组的文件行、表头、非表格文本、PRESERVE 块原样保留。
|
|
265
|
+
* 已折叠的目录条目行不再匹配文件条目,故幂等。
|
|
266
|
+
*/
|
|
267
|
+
export function compactCapabilitiesContent(content: string): string {
|
|
268
|
+
const lines = content.split('\n');
|
|
269
|
+
const fileEntryRegex = /\.(?:ts|tsx|js|jsx)$/;
|
|
270
|
+
const rowRegex = /^\|[^|]+\|[^|]+\|[^|]+\|\s*$/;
|
|
271
|
+
|
|
272
|
+
interface TableRow {
|
|
273
|
+
lineIndex: number;
|
|
274
|
+
file: string;
|
|
275
|
+
desc: string;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// 收集文件条目行,按 dirname 分组(无目录前缀的条目不参与折叠)
|
|
279
|
+
const groups = new Map<string, TableRow[]>();
|
|
280
|
+
for (let i = 0; i < lines.length; i++) {
|
|
281
|
+
if (!rowRegex.test(lines[i])) continue;
|
|
282
|
+
const cells = lines[i].split('|').map(c => c.trim());
|
|
283
|
+
const file = cells[2] ?? '';
|
|
284
|
+
if (!fileEntryRegex.test(file)) continue;
|
|
285
|
+
const dir = path.posix.dirname(file);
|
|
286
|
+
if (dir === '.') continue;
|
|
287
|
+
const group = groups.get(dir) ?? [];
|
|
288
|
+
group.push({ lineIndex: i, file, desc: cells[3] ?? '' });
|
|
289
|
+
groups.set(dir, group);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const deleteLines = new Set<number>();
|
|
293
|
+
for (const [dir, rows] of groups) {
|
|
294
|
+
if (rows.length < 2) continue;
|
|
295
|
+
const name = dir.split('/').pop()!;
|
|
296
|
+
const desc = rows[0].desc || name;
|
|
297
|
+
lines[rows[0].lineIndex] = `| ${name} | ${dir}/ | ${desc} |`;
|
|
298
|
+
for (const row of rows.slice(1)) {
|
|
299
|
+
deleteLines.add(row.lineIndex);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return lines.filter((_, i) => !deleteLines.has(i)).join('\n');
|
|
304
|
+
}
|
|
@@ -16,8 +16,9 @@ import chalk from 'chalk';
|
|
|
16
16
|
import * as fs from 'fs/promises';
|
|
17
17
|
import { existsSync } from 'fs';
|
|
18
18
|
import * as path from 'path';
|
|
19
|
-
import { readCapabilitiesEntries } from '../../../core/constraints/capabilities-parser';
|
|
19
|
+
import { aggregateToSourceSubdir, readCapabilitiesEntries } from '../../../core/constraints/capabilities-parser';
|
|
20
20
|
import { detectSourceRoots } from '../../../utils/detect-source-roots';
|
|
21
|
+
import { getCapabilitiesMode } from '../../../core/project-config-loader';
|
|
21
22
|
import { getSourceDirs, scanSourceModules, getRequiredContextDirs } from './project-reader';
|
|
22
23
|
import type { ModuleInfo, SyncResult } from './project-reader';
|
|
23
24
|
import {
|
|
@@ -26,6 +27,7 @@ import {
|
|
|
26
27
|
checkCapabilityCounts,
|
|
27
28
|
updateCapabilityCounts,
|
|
28
29
|
updateCapabilitiesFile,
|
|
30
|
+
compactCapabilitiesContent,
|
|
29
31
|
} from './capabilities-syncer';
|
|
30
32
|
import { createContextMd, findExistingContextFiles, getLatestTsMtime } from './context-syncer';
|
|
31
33
|
import { buildAgentsMd } from './agents-syncer';
|
|
@@ -42,6 +44,8 @@ export interface SyncDocsOptions {
|
|
|
42
44
|
json?: boolean;
|
|
43
45
|
/** 同步 AGENTS.md(agent 导读);PRESERVE 标记段在重新生成时原样保留 */
|
|
44
46
|
agents?: boolean;
|
|
47
|
+
/** 一次性迁移:将 CAPABILITIES.md 文件表格折叠为目录条目 */
|
|
48
|
+
compact?: boolean;
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
/**
|
|
@@ -51,6 +55,26 @@ export async function syncDocs(options: SyncDocsOptions): Promise<boolean> {
|
|
|
51
55
|
const projectPath = options.projectPath || process.cwd();
|
|
52
56
|
const isCheck = options.check === true;
|
|
53
57
|
const isJson = options.json === true;
|
|
58
|
+
const capsMode = getCapabilitiesMode(projectPath);
|
|
59
|
+
|
|
60
|
+
// --compact:一次性迁移,将文件表格折叠为目录条目后直接返回
|
|
61
|
+
if (options.compact) {
|
|
62
|
+
const capsPath = path.join(projectPath, 'CAPABILITIES.md');
|
|
63
|
+
try {
|
|
64
|
+
const content = await fs.readFile(capsPath, 'utf-8');
|
|
65
|
+
const compacted = compactCapabilitiesContent(content);
|
|
66
|
+
if (compacted !== content) {
|
|
67
|
+
await fs.writeFile(capsPath, compacted, 'utf-8');
|
|
68
|
+
if (!isJson) console.log(chalk.green('✅ 已将 CAPABILITIES.md 文件表格折叠为目录条目'));
|
|
69
|
+
} else {
|
|
70
|
+
if (!isJson) console.log(chalk.green('✅ CAPABILITIES.md 无需折叠'));
|
|
71
|
+
}
|
|
72
|
+
return true;
|
|
73
|
+
} catch {
|
|
74
|
+
if (!isJson) console.log(chalk.red('❌ CAPABILITIES.md 不存在,无法折叠'));
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
54
78
|
|
|
55
79
|
if (!isJson) {
|
|
56
80
|
if (isCheck) {
|
|
@@ -88,7 +112,8 @@ export async function syncDocs(options: SyncDocsOptions): Promise<boolean> {
|
|
|
88
112
|
let capsIsCapabilityListing = false;
|
|
89
113
|
try {
|
|
90
114
|
capsContent = await fs.readFile(capabilitiesPath, 'utf-8');
|
|
91
|
-
|
|
115
|
+
// mode=listing 是清单格式的显式声明,与嗅探结果等价
|
|
116
|
+
if (capsMode === 'listing' || isCapabilityListingFormat(capsContent)) {
|
|
92
117
|
capsIsCapabilityListing = true;
|
|
93
118
|
} else {
|
|
94
119
|
existingFiles = await parseCapabilitiesFiles(capabilitiesPath);
|
|
@@ -116,13 +141,31 @@ export async function syncDocs(options: SyncDocsOptions): Promise<boolean> {
|
|
|
116
141
|
// (basename 列表里永远不会有目录,直接对比会把目录行误报为「已删除的模块」)
|
|
117
142
|
const existingDirs = existingFiles.filter(f => f.endsWith('/'));
|
|
118
143
|
const existingFileNames = existingFiles.filter(f => !f.endsWith('/'));
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
)
|
|
125
|
-
|
|
144
|
+
if (capsMode === 'module') {
|
|
145
|
+
// module 模式:文件条目精确匹配或目录条目前缀覆盖;
|
|
146
|
+
// added 不再是逐文件列表,聚合为「未覆盖目录」(与 capability_sync checker 同规则)
|
|
147
|
+
const entries = readCapabilitiesEntries(capabilitiesPath, { includeDirs: true });
|
|
148
|
+
const fileEntries = entries.filter(e => !e.endsWith('/'));
|
|
149
|
+
const dirEntries = entries.filter(e => e.endsWith('/'));
|
|
150
|
+
const uncoveredDirs = new Set<string>();
|
|
151
|
+
for (const m of currentModules) {
|
|
152
|
+
const covered =
|
|
153
|
+
fileEntries.includes(m.file) || dirEntries.some(d => m.file.startsWith(d));
|
|
154
|
+
if (!covered) {
|
|
155
|
+
const root = srcDirs.find(d => m.file.startsWith(d + '/')) || srcDirs[0] || '';
|
|
156
|
+
uncoveredDirs.add(aggregateToSourceSubdir(root, m.file));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
result.added = [...uncoveredDirs];
|
|
160
|
+
} else {
|
|
161
|
+
result.added = currentModules
|
|
162
|
+
.filter(
|
|
163
|
+
m =>
|
|
164
|
+
!existingDirs.some(d => m.file.startsWith(d)) &&
|
|
165
|
+
!existingFileNames.includes(getBasename(m.file))
|
|
166
|
+
)
|
|
167
|
+
.map(m => getBasename(m.file));
|
|
168
|
+
}
|
|
126
169
|
result.removed = existingFileNames.filter(f => !currentBasenames.includes(f));
|
|
127
170
|
|
|
128
171
|
// 幽灵条目清扫(2026-08-08 studio CI 4 连红事故):上方按 basename 对比,
|
|
@@ -235,18 +278,30 @@ export async function syncDocs(options: SyncDocsOptions): Promise<boolean> {
|
|
|
235
278
|
}
|
|
236
279
|
|
|
237
280
|
if (!capsIsCapabilityListing && hasTableIssues) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
281
|
+
if (capsMode === 'module') {
|
|
282
|
+
// module 模式:added 为聚合后的未覆盖目录,需人工登记目录条目
|
|
283
|
+
jsonOutput.added = result.added.map(d => ({ dir: d }));
|
|
284
|
+
jsonOutput.removed = result.removed.map(f => ({ file: f }));
|
|
285
|
+
(jsonOutput.resolution as Array<Record<string, unknown>>).push({
|
|
286
|
+
action: 'register-capability-dirs',
|
|
287
|
+
details:
|
|
288
|
+
'在 CAPABILITIES.md 中为这些目录登记一行目录条目(如 `| 模块名 | src/xxx/ | 说明 |`)',
|
|
289
|
+
dirs: result.added,
|
|
290
|
+
});
|
|
291
|
+
} else {
|
|
292
|
+
jsonOutput.added = result.added.map(f => {
|
|
293
|
+
const getBasenameLocal = (s: string) => s.split('/').pop()!;
|
|
294
|
+
return {
|
|
295
|
+
file: f,
|
|
296
|
+
module: currentModules.find(m => getBasenameLocal(m.file) === f),
|
|
297
|
+
};
|
|
298
|
+
});
|
|
299
|
+
jsonOutput.removed = result.removed.map(f => ({ file: f }));
|
|
300
|
+
(jsonOutput.resolution as Array<Record<string, unknown>>).push({
|
|
301
|
+
action: 'sync-capabilities',
|
|
302
|
+
command: 'harness sync-docs',
|
|
303
|
+
});
|
|
304
|
+
}
|
|
250
305
|
}
|
|
251
306
|
|
|
252
307
|
if (hasContextIssues) {
|
|
@@ -282,8 +337,16 @@ export async function syncDocs(options: SyncDocsOptions): Promise<boolean> {
|
|
|
282
337
|
}
|
|
283
338
|
|
|
284
339
|
if (result.added.length > 0) {
|
|
285
|
-
|
|
286
|
-
|
|
340
|
+
if (capsMode === 'module') {
|
|
341
|
+
console.log(chalk.yellow(`\n📄 CAPABILITIES.md 未登记以下模块(目录):`));
|
|
342
|
+
result.added.forEach(d => console.log(chalk.gray(` + ${d}`)));
|
|
343
|
+
console.log(
|
|
344
|
+
chalk.gray(' 请在 CAPABILITIES.md 中为这些目录登记一行目录条目(如 `| 模块名 | src/xxx/ | 说明 |`)')
|
|
345
|
+
);
|
|
346
|
+
} else {
|
|
347
|
+
console.log(chalk.yellow(`\n📄 CAPABILITIES.md 缺少以下模块:`));
|
|
348
|
+
result.added.forEach(f => console.log(chalk.gray(` + ${f}`)));
|
|
349
|
+
}
|
|
287
350
|
}
|
|
288
351
|
|
|
289
352
|
if (result.removed.length > 0) {
|
|
@@ -337,7 +400,7 @@ export async function syncDocs(options: SyncDocsOptions): Promise<boolean> {
|
|
|
337
400
|
}
|
|
338
401
|
|
|
339
402
|
if (!capsIsCapabilityListing && hasTableIssues) {
|
|
340
|
-
await updateCapabilitiesFile(capabilitiesPath, currentModules, existingFiles, result);
|
|
403
|
+
await updateCapabilitiesFile(capabilitiesPath, currentModules, existingFiles, result, capsMode);
|
|
341
404
|
console.log(chalk.green(`\n✅ 已更新 CAPABILITIES.md`));
|
|
342
405
|
}
|
|
343
406
|
|
|
@@ -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
|
/**
|