@dommaker/harness 1.4.0 → 1.6.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 +23 -0
- package/README.md +0 -2
- package/dist/cli/commands/check.d.ts.map +1 -1
- package/dist/cli/commands/check.js +28 -0
- package/dist/cli/commands/check.js.map +1 -1
- package/dist/cli/commands/definitions.d.ts.map +1 -1
- package/dist/cli/commands/definitions.js +1 -39
- package/dist/cli/commands/definitions.js.map +1 -1
- package/dist/cli/commands/knowledge.d.ts +0 -19
- package/dist/cli/commands/knowledge.d.ts.map +1 -1
- package/dist/cli/commands/knowledge.js +0 -105
- package/dist/cli/commands/knowledge.js.map +1 -1
- package/dist/cli/commands/passes-gate.d.ts +0 -2
- package/dist/cli/commands/passes-gate.d.ts.map +1 -1
- package/dist/cli/commands/passes-gate.js +0 -1
- package/dist/cli/commands/passes-gate.js.map +1 -1
- package/dist/cli/commands/performance.d.ts +0 -4
- package/dist/cli/commands/performance.d.ts.map +1 -1
- package/dist/cli/commands/performance.js +10 -20
- package/dist/cli/commands/performance.js.map +1 -1
- package/dist/cli/commands/report.d.ts +1 -1
- package/dist/cli/commands/report.d.ts.map +1 -1
- package/dist/cli/commands/report.js +2 -55
- 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 +4 -8
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/context/index.d.ts +2 -5
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +2 -8
- package/dist/context/index.js.map +1 -1
- package/dist/context/token-budget.d.ts +0 -29
- package/dist/context/token-budget.d.ts.map +1 -1
- package/dist/context/token-budget.js +1 -65
- package/dist/context/token-budget.js.map +1 -1
- package/dist/context/types.d.ts +0 -1
- package/dist/context/types.d.ts.map +1 -1
- package/dist/context/types.js +0 -9
- package/dist/context/types.js.map +1 -1
- package/dist/core/constraints/checker.d.ts +14 -0
- package/dist/core/constraints/checker.d.ts.map +1 -1
- package/dist/core/constraints/checker.js +27 -5
- package/dist/core/constraints/checker.js.map +1 -1
- package/dist/core/constraints/checkers/capability-sync.d.ts +17 -6
- package/dist/core/constraints/checkers/capability-sync.d.ts.map +1 -1
- package/dist/core/constraints/checkers/capability-sync.js +48 -19
- package/dist/core/constraints/checkers/capability-sync.js.map +1 -1
- package/dist/core/constraints/checkers/docs-freshness.d.ts.map +1 -1
- package/dist/core/constraints/checkers/docs-freshness.js +15 -6
- package/dist/core/constraints/checkers/docs-freshness.js.map +1 -1
- package/dist/core/constraints/checkers/index.d.ts +2 -2
- package/dist/core/constraints/checkers/index.d.ts.map +1 -1
- package/dist/core/constraints/checkers/index.js +2 -1
- package/dist/core/constraints/checkers/index.js.map +1 -1
- package/dist/core/constraints/checkers/types.d.ts +41 -2
- package/dist/core/constraints/checkers/types.d.ts.map +1 -1
- package/dist/core/constraints/checkers/types.js +33 -0
- package/dist/core/constraints/checkers/types.js.map +1 -1
- package/dist/core/constraints/git-evidence.d.ts +6 -3
- package/dist/core/constraints/git-evidence.d.ts.map +1 -1
- package/dist/core/constraints/git-evidence.js +6 -3
- package/dist/core/constraints/git-evidence.js.map +1 -1
- package/dist/core/index.d.ts +1 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -9
- package/dist/core/index.js.map +1 -1
- package/dist/core/spec/validator.d.ts +2 -0
- package/dist/core/spec/validator.d.ts.map +1 -1
- package/dist/core/spec/validator.js +9 -11
- package/dist/core/spec/validator.js.map +1 -1
- package/dist/core/validators/index.d.ts +1 -1
- package/dist/core/validators/index.d.ts.map +1 -1
- package/dist/core/validators/passes-gate.d.ts +1 -15
- package/dist/core/validators/passes-gate.d.ts.map +1 -1
- package/dist/core/validators/passes-gate.js +0 -125
- package/dist/core/validators/passes-gate.js.map +1 -1
- package/dist/failure/index.d.ts +0 -1
- package/dist/failure/index.d.ts.map +1 -1
- package/dist/failure/index.js +1 -7
- package/dist/failure/index.js.map +1 -1
- package/dist/gates/checker-gate.d.ts +2 -1
- package/dist/gates/checker-gate.d.ts.map +1 -1
- package/dist/gates/checker-gate.js +10 -7
- package/dist/gates/checker-gate.js.map +1 -1
- package/dist/gates/definitions.d.ts.map +1 -1
- package/dist/gates/definitions.js +0 -4
- package/dist/gates/definitions.js.map +1 -1
- package/dist/gates/performance.d.ts +7 -22
- package/dist/gates/performance.d.ts.map +1 -1
- package/dist/gates/performance.js +7 -89
- package/dist/gates/performance.js.map +1 -1
- package/dist/gates/types.d.ts +2 -8
- package/dist/gates/types.d.ts.map +1 -1
- package/dist/index.d.ts +6 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -22
- package/dist/index.js.map +1 -1
- package/dist/knowledge/index.d.ts +0 -1
- package/dist/knowledge/index.d.ts.map +1 -1
- package/dist/knowledge/index.js +1 -3
- package/dist/knowledge/index.js.map +1 -1
- package/dist/monitoring/trace-analyzer.d.ts +25 -10
- package/dist/monitoring/trace-analyzer.d.ts.map +1 -1
- package/dist/monitoring/trace-analyzer.js +147 -116
- package/dist/monitoring/trace-analyzer.js.map +1 -1
- package/dist/types/constraint.d.ts +8 -0
- package/dist/types/constraint.d.ts.map +1 -1
- package/dist/types/constraint.js +6 -1
- package/dist/types/constraint.js.map +1 -1
- package/dist/types/passes-gate.d.ts +0 -19
- package/dist/types/passes-gate.d.ts.map +1 -1
- package/dist/types/trace.d.ts +9 -0
- package/dist/types/trace.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/CONTEXT.md +1 -0
- package/src/__tests__/passes-gate-check.test.ts +20 -4
- package/src/__tests__/passes-gate.test.ts +18 -119
- package/src/__tests__/performance-gate.test.ts +3 -11
- package/src/__tests__/public-exports.test.ts +46 -16
- package/src/__tests__/trace-analyzer.test.ts +52 -2
- package/src/cli/commands/CONTEXT.md +5 -4
- package/src/cli/commands/__tests__/bin-exit-mapping.test.ts +6 -0
- package/src/cli/commands/__tests__/check.test.ts +74 -0
- package/src/cli/commands/__tests__/passes-gate-dead-options.test.ts +70 -0
- package/src/cli/commands/__tests__/passes-gate.test.ts +7 -3
- package/src/cli/commands/__tests__/performance.test.ts +5 -16
- package/src/cli/commands/__tests__/project-path-convention.test.ts +85 -23
- package/src/cli/commands/__tests__/registry.test.ts +52 -3
- package/src/cli/commands/__tests__/report-extra.test.ts +0 -19
- package/src/cli/commands/__tests__/report-false-green.test.ts +46 -0
- package/src/cli/commands/__tests__/report.test.ts +0 -20
- package/src/cli/commands/__tests__/status-analysis-wiring.test.ts +68 -0
- package/src/cli/commands/__tests__/status-extra.test.ts +63 -116
- package/src/cli/commands/__tests__/status.test.ts +66 -148
- package/src/cli/commands/check.ts +39 -1
- package/src/cli/commands/definitions.ts +1 -39
- package/src/cli/commands/knowledge.ts +0 -119
- package/src/cli/commands/passes-gate.ts +0 -3
- package/src/cli/commands/performance.ts +13 -27
- package/src/cli/commands/report.ts +3 -61
- package/src/cli/commands/status.ts +5 -10
- package/src/context/CONTEXT.md +2 -3
- package/src/context/__tests__/token-budget.test.ts +1 -81
- package/src/context/index.ts +2 -7
- package/src/context/token-budget.ts +0 -73
- package/src/context/types.ts +0 -9
- package/src/core/CONTEXT.md +6 -6
- package/src/core/constraints/__tests__/collect-constraints.test.ts +66 -0
- package/src/core/constraints/checker.ts +41 -6
- package/src/core/constraints/checkers/__tests__/capability-sync.test.ts +90 -31
- package/src/core/constraints/checkers/__tests__/check-outcome.test.ts +176 -0
- package/src/core/constraints/checkers/__tests__/docs-freshness.test.ts +18 -9
- package/src/core/constraints/checkers/__tests__/three-state-consistency.test.ts +12 -4
- package/src/core/constraints/checkers/capability-sync.ts +56 -18
- package/src/core/constraints/checkers/docs-freshness.ts +18 -5
- package/src/core/constraints/checkers/index.ts +9 -2
- package/src/core/constraints/checkers/types.ts +58 -2
- package/src/core/constraints/git-evidence.ts +6 -3
- package/src/core/index.ts +0 -11
- package/src/core/spec/__tests__/validator.test.ts +24 -0
- package/src/core/spec/validator.ts +9 -10
- package/src/core/validators/__tests__/passes-gate.test.ts +2 -9
- package/src/core/validators/index.ts +0 -1
- package/src/core/validators/passes-gate.ts +1 -149
- package/src/failure/CONTEXT.md +8 -6
- package/src/failure/index.ts +0 -10
- package/src/gates/CONTEXT.md +4 -3
- package/src/gates/__tests__/checker-gate.test.ts +24 -0
- package/src/gates/__tests__/performance.test.ts +6 -74
- package/src/gates/checker-gate.ts +11 -15
- package/src/gates/definitions.ts +0 -4
- package/src/gates/performance.ts +9 -111
- package/src/gates/types.ts +2 -8
- package/src/index.ts +1 -36
- package/src/knowledge/CONTEXT.md +1 -2
- package/src/knowledge/index.ts +0 -1
- package/src/monitoring/CONTEXT.md +2 -2
- package/src/monitoring/trace-analyzer.ts +160 -126
- package/src/types/constraint.ts +16 -2
- package/src/types/passes-gate.ts +0 -26
- package/src/types/trace.ts +10 -0
- package/src/utils/__tests__/jsonl-skip-disposition.test.ts +0 -1
- package/dist/cli/commands/analyze-sessions.d.ts +0 -20
- package/dist/cli/commands/analyze-sessions.d.ts.map +0 -1
- package/dist/cli/commands/analyze-sessions.js +0 -290
- package/dist/cli/commands/analyze-sessions.js.map +0 -1
- package/dist/cli/commands/update-user-model.d.ts +0 -25
- package/dist/cli/commands/update-user-model.d.ts.map +0 -1
- package/dist/cli/commands/update-user-model.js +0 -397
- package/dist/cli/commands/update-user-model.js.map +0 -1
- package/dist/cli/session-mining/corrections.d.ts +0 -17
- package/dist/cli/session-mining/corrections.d.ts.map +0 -1
- package/dist/cli/session-mining/corrections.js +0 -58
- package/dist/cli/session-mining/corrections.js.map +0 -1
- package/dist/cli/session-mining/index.d.ts +0 -13
- package/dist/cli/session-mining/index.d.ts.map +0 -1
- package/dist/cli/session-mining/index.js +0 -29
- package/dist/cli/session-mining/index.js.map +0 -1
- package/dist/cli/session-mining/text.d.ts +0 -31
- package/dist/cli/session-mining/text.d.ts.map +0 -1
- package/dist/cli/session-mining/text.js +0 -126
- package/dist/cli/session-mining/text.js.map +0 -1
- package/dist/cli/session-mining/transcript.d.ts +0 -34
- package/dist/cli/session-mining/transcript.d.ts.map +0 -1
- package/dist/cli/session-mining/transcript.js +0 -130
- package/dist/cli/session-mining/transcript.js.map +0 -1
- package/dist/context/compaction.d.ts +0 -57
- package/dist/context/compaction.d.ts.map +0 -1
- package/dist/context/compaction.js +0 -165
- package/dist/context/compaction.js.map +0 -1
- package/dist/core/session/clean-state.d.ts +0 -17
- package/dist/core/session/clean-state.d.ts.map +0 -1
- package/dist/core/session/clean-state.js +0 -192
- package/dist/core/session/clean-state.js.map +0 -1
- package/dist/core/session/index.d.ts +0 -6
- package/dist/core/session/index.d.ts.map +0 -1
- package/dist/core/session/index.js +0 -15
- package/dist/core/session/index.js.map +0 -1
- package/dist/core/session/startup.d.ts +0 -47
- package/dist/core/session/startup.d.ts.map +0 -1
- package/dist/core/session/startup.js +0 -263
- package/dist/core/session/startup.js.map +0 -1
- package/dist/failure/constraint-handler.d.ts +0 -83
- package/dist/failure/constraint-handler.d.ts.map +0 -1
- package/dist/failure/constraint-handler.js +0 -138
- package/dist/failure/constraint-handler.js.map +0 -1
- package/dist/knowledge/lifecycle-hooks.d.ts +0 -62
- package/dist/knowledge/lifecycle-hooks.d.ts.map +0 -1
- package/dist/knowledge/lifecycle-hooks.js +0 -143
- package/dist/knowledge/lifecycle-hooks.js.map +0 -1
- package/dist/types/session.d.ts +0 -113
- package/dist/types/session.d.ts.map +0 -1
- package/dist/types/session.js +0 -6
- package/dist/types/session.js.map +0 -1
- package/src/__tests__/clean-state-extra.test.ts +0 -301
- package/src/__tests__/clean-state.test.ts +0 -169
- package/src/__tests__/constraint-handler.test.ts +0 -132
- package/src/__tests__/passes-gate-extra.test.ts +0 -93
- package/src/__tests__/session-startup.test.ts +0 -334
- package/src/cli/commands/__tests__/analyze-sessions.test.ts +0 -184
- package/src/cli/commands/__tests__/update-user-model.test.ts +0 -234
- package/src/cli/commands/analyze-sessions.ts +0 -335
- package/src/cli/commands/update-user-model.ts +0 -461
- package/src/cli/session-mining/__tests__/transcript.test.ts +0 -118
- package/src/cli/session-mining/corrections.ts +0 -56
- package/src/cli/session-mining/index.ts +0 -13
- package/src/cli/session-mining/text.ts +0 -117
- package/src/cli/session-mining/transcript.ts +0 -125
- package/src/context/__tests__/compaction.test.ts +0 -169
- package/src/context/compaction.ts +0 -201
- package/src/core/session/__tests__/startup.test.ts +0 -281
- package/src/core/session/clean-state.ts +0 -178
- package/src/core/session/index.ts +0 -15
- package/src/core/session/startup.ts +0 -253
- package/src/failure/constraint-handler.ts +0 -171
- package/src/knowledge/__tests__/lifecycle-hooks.test.ts +0 -140
- package/src/knowledge/lifecycle-hooks.ts +0 -202
- package/src/types/session.ts +0 -132
|
@@ -6,17 +6,21 @@
|
|
|
6
6
|
* 项目根由 src/test-setup/project-fixture 声明式构造(回收仍走 mkdtemp-cleanup)。
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { describe, it, expect
|
|
9
|
+
import { describe, it, expect } from '@jest/globals';
|
|
10
10
|
import * as fs from 'fs';
|
|
11
11
|
import * as path from 'path';
|
|
12
12
|
import { docsFreshness } from '../docs-freshness';
|
|
13
|
-
import { buildCheckEnv } from '../types';
|
|
13
|
+
import { buildCheckEnv, normalizeCheckOutcome, type CheckOutcome } from '../types';
|
|
14
14
|
import { collectSourceFiles } from '../../capabilities-reconcile';
|
|
15
15
|
import { createProjectFixture } from '../../../../test-setup/project-fixture';
|
|
16
16
|
import type { ConstraintContext } from '../../../../types/constraint';
|
|
17
17
|
|
|
18
18
|
const TABLE_HEAD = '| 模块 | 文件 | 说明 |\n|------|------|------|\n';
|
|
19
19
|
|
|
20
|
+
/** 证据行拼成单串,便于按路径断言(harness#119) */
|
|
21
|
+
const evidenceText = (outcome: CheckOutcome): string =>
|
|
22
|
+
normalizeCheckOutcome(outcome).evidence.join('\n');
|
|
23
|
+
|
|
20
24
|
function setupProject(name: string, files: string[]): string {
|
|
21
25
|
return createProjectFixture({
|
|
22
26
|
name: `docs-fresh-${name}`,
|
|
@@ -66,10 +70,9 @@ describe('docs_freshness — 幽灵判定(文档→代码方向)', () => {
|
|
|
66
70
|
dir,
|
|
67
71
|
`${TABLE_HEAD}| old | src/old.ts | old |\n| deleted | src/deleted.ts | deleted |`
|
|
68
72
|
);
|
|
69
|
-
const
|
|
70
|
-
expect(
|
|
71
|
-
expect(
|
|
72
|
-
errSpy.mockRestore();
|
|
73
|
+
const outcome = await docsFreshness.evaluate(makeEnv(dir));
|
|
74
|
+
expect(normalizeCheckOutcome(outcome).satisfied).toBe(false);
|
|
75
|
+
expect(evidenceText(outcome)).toContain('src/deleted.ts');
|
|
73
76
|
});
|
|
74
77
|
|
|
75
78
|
it('登记的目录条目已消失 → 失败(ADR-0009 口径从严,文件+目录都查)', async () => {
|
|
@@ -78,7 +81,9 @@ describe('docs_freshness — 幽灵判定(文档→代码方向)', () => {
|
|
|
78
81
|
dir,
|
|
79
82
|
`${TABLE_HEAD}| 核心 | src/core/ | 核心 |\n| 遗魂 | src/gone/ | 已删目录 |`
|
|
80
83
|
);
|
|
81
|
-
|
|
84
|
+
const outcome = await docsFreshness.evaluate(makeEnv(dir));
|
|
85
|
+
expect(normalizeCheckOutcome(outcome).satisfied).toBe(false);
|
|
86
|
+
expect(evidenceText(outcome)).toContain('src/gone/');
|
|
82
87
|
});
|
|
83
88
|
|
|
84
89
|
it('登记的目录条目仍存在 → 通过', async () => {
|
|
@@ -93,7 +98,9 @@ describe('docs_freshness — 幽灵判定(文档→代码方向)', () => {
|
|
|
93
98
|
dir,
|
|
94
99
|
`${TABLE_HEAD}| routes | src/agents/routes.ts | 活 |\n| 旧路由 | src/agent-configs/routes.ts | 已删 |`
|
|
95
100
|
);
|
|
96
|
-
|
|
101
|
+
const outcome = await docsFreshness.evaluate(makeEnv(dir));
|
|
102
|
+
expect(normalizeCheckOutcome(outcome).satisfied).toBe(false);
|
|
103
|
+
expect(evidenceText(outcome)).toContain('src/agent-configs/routes.ts');
|
|
97
104
|
});
|
|
98
105
|
});
|
|
99
106
|
|
|
@@ -112,6 +119,8 @@ describe('docs_freshness — 内置 Runner 检查(changelog/context)', () =>
|
|
|
112
119
|
JSON.stringify({ name: 'x', version: '2.0.0' })
|
|
113
120
|
);
|
|
114
121
|
fs.writeFileSync(path.join(dir, 'CHANGELOG.md'), '## [1.0.0] - 2026-01-01\n\n- old\n');
|
|
115
|
-
|
|
122
|
+
const outcome = await docsFreshness.evaluate(makeEnv(dir));
|
|
123
|
+
expect(normalizeCheckOutcome(outcome).satisfied).toBe(false);
|
|
124
|
+
expect(evidenceText(outcome)).toContain('1.0.0 !== package.json 版本 2.0.0');
|
|
116
125
|
});
|
|
117
126
|
});
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import { describe, it, expect } from '@jest/globals';
|
|
15
15
|
import { contextDocSync } from '../context-doc-sync';
|
|
16
16
|
import { docsFreshness } from '../docs-freshness';
|
|
17
|
-
import { buildCheckEnv } from '../types';
|
|
17
|
+
import { buildCheckEnv, normalizeCheckOutcome, type CheckOutcome } from '../types';
|
|
18
18
|
import { collectSourceFiles } from '../../capabilities-reconcile';
|
|
19
19
|
import { createProjectFixture } from '../../../../test-setup/project-fixture';
|
|
20
20
|
import type { ConstraintContext } from '../../../../types/constraint';
|
|
@@ -62,10 +62,16 @@ function evalDocsFreshness(dir: string) {
|
|
|
62
62
|
);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
/** 三态判定(比较判定结论本身,不比较返回形状——harness#119 后 checker 可带回证据) */
|
|
66
|
+
function verdictOf(outcome: CheckOutcome): true | false | 'skip' {
|
|
67
|
+
const normalized = normalizeCheckOutcome(outcome);
|
|
68
|
+
return normalized.skipped ? 'skip' : normalized.satisfied;
|
|
69
|
+
}
|
|
70
|
+
|
|
65
71
|
/** 同一 fixture 上两条约束的判定必须一致(对照断言) */
|
|
66
72
|
async function expectBoth(dir: string, verdict: true | false | 'skip'): Promise<void> {
|
|
67
|
-
expect(await evalContextDocSync(dir)).toBe(verdict);
|
|
68
|
-
expect(await evalDocsFreshness(dir)).toBe(verdict);
|
|
73
|
+
expect(verdictOf(await evalContextDocSync(dir))).toBe(verdict);
|
|
74
|
+
expect(verdictOf(await evalDocsFreshness(dir))).toBe(verdict);
|
|
69
75
|
}
|
|
70
76
|
|
|
71
77
|
describe('context_files 三态对照 — context_doc_sync vs docs_freshness(工单 84)', () => {
|
|
@@ -140,6 +146,8 @@ describe('docs_freshness 评估段 skip 行为变更(工单 84:约定已立
|
|
|
140
146
|
files: ['src/old.ts'],
|
|
141
147
|
capabilitiesBody: `${TABLE_HEAD}| old | src/old.ts | 活 |\n| 遗魂 | src/gone/ | 已删 |`,
|
|
142
148
|
});
|
|
143
|
-
|
|
149
|
+
const outcome = await evalDocsFreshness(dir);
|
|
150
|
+
expect(verdictOf(outcome)).toBe(false);
|
|
151
|
+
expect(normalizeCheckOutcome(outcome).evidence.join('\n')).toContain('src/gone/');
|
|
144
152
|
});
|
|
145
153
|
});
|
|
@@ -1,17 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* capability_sync:CAPABILITIES.md 与代码同步检查(工单 21)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* 1. Git diff 增量 —
|
|
6
|
-
* 2.
|
|
4
|
+
* 两步验证,归因不同(harness#119):
|
|
5
|
+
* 1. Git diff 增量 — 本次变更的非测试文件是否在文档中有记录 → **判违规**(有因果)
|
|
6
|
+
* 2. 全量扫描(T-058)— 源码根下所有源文件是否都在 CAPABILITIES.md 中 → **只出提示**
|
|
7
|
+
*
|
|
8
|
+
* Step 2 不判违规的理由:它是仓库级完备性不变量,与正在被评估的那次变更无因果——
|
|
9
|
+
* 一处历史漏登会让此后每次 module_* 评估恒红(studio 实测 22/22 fail,且因 fail
|
|
10
|
+
* 零证据而无人能定位)。本处保留缺口输出是为了让人看得见、能自己修,而不是为了拦。
|
|
11
|
+
*
|
|
12
|
+
* 已知缺口(ADR-0016「影响」①):降级后仓库级漏登**没有自动拦截点**。`sync-docs --check`
|
|
13
|
+
* 单独跑会点名并 exit 1,但 CI/ship 都按「先 sync-docs 写入、后 --check」的顺序跑
|
|
14
|
+
* (2026-08-08 CI 4 连红固化的顺序),而 file 模式的写入正给缺登记自动补行
|
|
15
|
+
* (`capabilities-syncer` 的 `mode !== 'module'` 分支)、那份写入又不回提——于是流水线里
|
|
16
|
+
* 永远查不到漏登。补这个牙齿归流水线侧(sync 后 git diff 非空即失败),不在本约束里做。
|
|
17
|
+
*
|
|
18
|
+
* 例外:「有表格但零条目」= 文档被清空,判违规(历史门,此前靠 Step 2 兜,现显式化)。
|
|
7
19
|
*
|
|
8
20
|
* 覆盖/漏登判定不在本文件实现,统一走 capabilities-reconcile(ADR-0009):
|
|
9
21
|
* 目录条目(以 / 结尾)前缀匹配,文件条目精确匹配或路径边界后缀匹配;
|
|
10
22
|
* module 模式下目录条目参与覆盖,未覆盖文件按源码根下第一级子目录聚合。
|
|
11
23
|
*
|
|
12
24
|
* 清单格式(计数行,mode=listing 或嗅探命中)无文件表可核对,直接放行;
|
|
13
|
-
*
|
|
14
|
-
* 检查异常时 fail-open 但输出 console.warn 保留可观测性。
|
|
25
|
+
* 无表格的散文文档保持历史放行;检查异常时 fail-open 但输出 console.warn 保留可观测性。
|
|
15
26
|
*
|
|
16
27
|
* ADR-0001 存在性探测:项目根无 CAPABILITIES.md(未采用该约定)时返回 'skip',
|
|
17
28
|
* 不计 pass/fail,避免在未采用约定的项目上全量误报。
|
|
@@ -23,7 +34,7 @@ import { isCapabilityListingFormat } from '../capabilities-parser';
|
|
|
23
34
|
import { getCapabilitiesMode } from '../../project-config-loader';
|
|
24
35
|
import { detectSourceRoots } from '../../../utils/detect-source-roots';
|
|
25
36
|
import { reconcileCapabilities, significantCodeChanges } from '../capabilities-reconcile';
|
|
26
|
-
import type
|
|
37
|
+
import { formatEvidence, type ConstraintCheck } from './types';
|
|
27
38
|
|
|
28
39
|
export const capabilitySync: ConstraintCheck = {
|
|
29
40
|
id: 'capability_sync',
|
|
@@ -59,25 +70,52 @@ export const capabilitySync: ConstraintCheck = {
|
|
|
59
70
|
sourceRoots,
|
|
60
71
|
});
|
|
61
72
|
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
if (gateOpen) return true;
|
|
73
|
+
// 无表格的散文文档:历史放行语义
|
|
74
|
+
if (!verdict.hasTable) return true;
|
|
65
75
|
|
|
66
|
-
//
|
|
67
|
-
|
|
76
|
+
// 文档退化门:有表格却什么都没登记(限定「源码根下确有文件」——空仓 + 空表没有可登记
|
|
77
|
+
// 对象,历史行为是放行,本票不顺手扩大 fail 面)
|
|
78
|
+
if (verdict.coverageEntries.length === 0 && population.length > 0) {
|
|
79
|
+
return {
|
|
80
|
+
pass: false,
|
|
81
|
+
evidence: ['CAPABILITIES.md 有表格但零条目登记,运行 harness sync-docs 或直接补登记'],
|
|
82
|
+
};
|
|
83
|
+
}
|
|
68
84
|
|
|
69
|
-
// ── Step
|
|
70
|
-
if (
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
85
|
+
// ── Step 1: Git diff 增量检查(与本次变更有因果,每个变更文件都必须被覆盖)──
|
|
86
|
+
if (verdict.uncoveredChanges.length > 0) {
|
|
87
|
+
return {
|
|
88
|
+
pass: false,
|
|
89
|
+
evidence: formatEvidence(
|
|
90
|
+
`本次变更有 ${verdict.uncoveredChanges.length} 个文件未登记进 CAPABILITIES.md`,
|
|
91
|
+
verdict.uncoveredChanges
|
|
92
|
+
),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ── Step 2: 全量源码根扫描(T-058)——仓库级漂移,只提示不判违规,见文件头 ──
|
|
97
|
+
const gaps = capabilitiesMode === 'module' ? verdict.uncoveredDirs : verdict.uncoveredFiles;
|
|
98
|
+
if (gaps.length > 0) {
|
|
99
|
+
const kind = capabilitiesMode === 'module' ? '未登记模块目录' : '未登记源文件';
|
|
100
|
+
return {
|
|
101
|
+
pass: true,
|
|
102
|
+
evidence: formatEvidence(
|
|
103
|
+
`CAPABILITIES.md ${kind} ${gaps.length} 项(仓库级漂移,与本次变更无关;修复: harness sync-docs)`,
|
|
104
|
+
gaps
|
|
105
|
+
),
|
|
106
|
+
};
|
|
74
107
|
}
|
|
75
108
|
|
|
76
109
|
return true;
|
|
77
110
|
} catch (err) {
|
|
78
|
-
// fail-open
|
|
111
|
+
// fail-open 语义保留,但异常放行也要落地可见:warn 只进本地 stderr,
|
|
112
|
+
// 进不了 trace——静默吞错会让解析 bug 变成「永远通过」(harness#119 同族问题)
|
|
113
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
79
114
|
console.warn('[capability_sync] 检查执行异常,默认放行:', err);
|
|
80
|
-
return
|
|
115
|
+
return {
|
|
116
|
+
pass: true,
|
|
117
|
+
evidence: [`检查异常,按 fail-open 放行: ${reason}(本次结果不代表文档已同步)`],
|
|
118
|
+
};
|
|
81
119
|
}
|
|
82
120
|
},
|
|
83
121
|
};
|
|
@@ -15,6 +15,7 @@ import { detectSourceRoots } from '../../../utils/detect-source-roots';
|
|
|
15
15
|
import { reconcileCapabilities } from '../capabilities-reconcile';
|
|
16
16
|
import type { DocFreshnessCheck } from '../../../types/project-config';
|
|
17
17
|
import type { ConstraintCheck, CheckEnv } from './types';
|
|
18
|
+
import { formatEvidence } from './types';
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* 内置默认文档新鲜度检查配置
|
|
@@ -95,10 +96,13 @@ export const docsFreshness: ConstraintCheck = {
|
|
|
95
96
|
// Step 1: 文件表格式 — 登记的条目(文件+目录)是否仍存在(ADR-0009)
|
|
96
97
|
const deadEntries = findDeadCapabilityEntries(projectPath, env);
|
|
97
98
|
if (deadEntries.length > 0) {
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
|
|
101
|
-
return
|
|
99
|
+
// 证据通道点名条目(harness#119):此前是 console.error 侧信道(2026-08-08 studio
|
|
100
|
+
// CI 4 连红时的临时办法),CLI 结构化输出与 trace 两头都拿不到,
|
|
101
|
+
// 铁律拦截时用户只看到一句通用提示,basename 碰撞下无从定位
|
|
102
|
+
return {
|
|
103
|
+
pass: false,
|
|
104
|
+
evidence: formatEvidence('CAPABILITIES.md 登记的条目不存在', deadEntries),
|
|
105
|
+
};
|
|
102
106
|
}
|
|
103
107
|
|
|
104
108
|
// Step 2: 能力清单格式 + CLAUDE.md + CHANGELOG — 通过 FreshnessRunner
|
|
@@ -120,7 +124,16 @@ export const docsFreshness: ConstraintCheck = {
|
|
|
120
124
|
results = runner.runAll({ checks: getBuiltInDocFreshnessConfig() }, projectPath, { requiredDirs });
|
|
121
125
|
}
|
|
122
126
|
|
|
123
|
-
|
|
127
|
+
const failed = results.filter(r => !r.pass);
|
|
128
|
+
if (failed.length > 0) {
|
|
129
|
+
return {
|
|
130
|
+
pass: false,
|
|
131
|
+
evidence: formatEvidence(
|
|
132
|
+
'文档新鲜度检查未通过',
|
|
133
|
+
failed.map(r => `${r.label}: ${r.message ?? '未通过'}`)
|
|
134
|
+
),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
124
137
|
} catch {
|
|
125
138
|
// FreshnessRunner 失败不影响整体
|
|
126
139
|
}
|
|
@@ -80,5 +80,12 @@ export function registeredCheckCount(): number {
|
|
|
80
80
|
return registry.size;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
export { buildCheckEnv } from './types';
|
|
84
|
-
export type {
|
|
83
|
+
export { buildCheckEnv, normalizeCheckOutcome } from './types';
|
|
84
|
+
export type {
|
|
85
|
+
ConstraintCheck,
|
|
86
|
+
CheckEnv,
|
|
87
|
+
CheckOutcome,
|
|
88
|
+
CheckDetail,
|
|
89
|
+
NormalizedOutcome,
|
|
90
|
+
EvidenceProviders,
|
|
91
|
+
} from './types';
|
|
@@ -73,8 +73,64 @@ export function buildCheckEnv(
|
|
|
73
73
|
* - true = 满足(pass)
|
|
74
74
|
* - false = 违反(fail)
|
|
75
75
|
* - 'skip' = 未评估(项目未采用对应约定,或证据 flag 未接线),不计 pass/fail
|
|
76
|
+
* - CheckDetail = 判定 + 证据行(harness#119)
|
|
76
77
|
*/
|
|
77
|
-
export type CheckOutcome = boolean | 'skip';
|
|
78
|
+
export type CheckOutcome = boolean | 'skip' | CheckDetail;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 带证据的检查结果(harness#119)
|
|
82
|
+
*
|
|
83
|
+
* 动机:只回布尔时,fail 的具体原因(哪个文件没登记)在 CLI 与 trace 两头都落脚不了,
|
|
84
|
+
* 一条恒红的约束无法被诊断。证据行由 checker 自行措辞(自描述、可直接给人看),
|
|
85
|
+
* 编排层只负责透传与打印,不参与解释内容。
|
|
86
|
+
*
|
|
87
|
+
* `pass: true` + 非空 evidence = 「满足但有提示」:不进 pass/fail 统计口径,
|
|
88
|
+
* 仅作为可观测输出(如仓库级文档漂移——与本次变更无因果,不该判违规)。
|
|
89
|
+
*/
|
|
90
|
+
export interface CheckDetail {
|
|
91
|
+
/** false = 违反;true = 满足 */
|
|
92
|
+
pass: boolean;
|
|
93
|
+
/** 证据行(每条自描述,如「变更文件未登记: src/foo.ts」) */
|
|
94
|
+
evidence?: string[];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** 归一后的检查结果:编排层 / gate 唯一消费形状 */
|
|
98
|
+
export interface NormalizedOutcome {
|
|
99
|
+
satisfied: boolean;
|
|
100
|
+
skipped: boolean;
|
|
101
|
+
evidence: string[];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** 证据条数上限:trace 是 JSONL,缺口上百项时不能整仓落盘 */
|
|
105
|
+
export const MAX_EVIDENCE_ITEMS = 10;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 组装证据行:一行说明 + 逐条依据(超限截断)
|
|
109
|
+
*
|
|
110
|
+
* 每行自描述且不带缩进——缩进由消费端(CLI / gate 理由 / 铁律异常文案)决定,trace 原样存。
|
|
111
|
+
* 修复入口不写在这里:那是各约束自己的事,写进各自的 summary 行(docs_freshness 的
|
|
112
|
+
* 幽灵条目与 sync-docs 无关,共用一个指针会误导)。
|
|
113
|
+
*/
|
|
114
|
+
export function formatEvidence(summary: string, items: string[]): string[] {
|
|
115
|
+
const shown = items.slice(0, MAX_EVIDENCE_ITEMS);
|
|
116
|
+
const hidden = items.length - shown.length;
|
|
117
|
+
return [...(summary ? [summary] : []), ...shown, ...(hidden > 0 ? [`…另 ${hidden} 项`] : [])];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* 归一 CheckOutcome(boolean | 'skip' | CheckDetail)为统一形状
|
|
122
|
+
*
|
|
123
|
+
* skip 恒不携带证据:未评估的约束不得留下证据行(否则统计侧会把「没查」读成「查出问题」)。
|
|
124
|
+
*/
|
|
125
|
+
export function normalizeCheckOutcome(outcome: CheckOutcome): NormalizedOutcome {
|
|
126
|
+
if (outcome === 'skip') return { satisfied: true, skipped: true, evidence: [] };
|
|
127
|
+
if (typeof outcome === 'boolean') return { satisfied: outcome, skipped: false, evidence: [] };
|
|
128
|
+
return {
|
|
129
|
+
satisfied: outcome.pass,
|
|
130
|
+
skipped: false,
|
|
131
|
+
evidence: outcome.evidence ?? [],
|
|
132
|
+
};
|
|
133
|
+
}
|
|
78
134
|
|
|
79
135
|
/**
|
|
80
136
|
* 单条约束检查实现
|
|
@@ -82,7 +138,7 @@ export type CheckOutcome = boolean | 'skip';
|
|
|
82
138
|
export interface ConstraintCheck {
|
|
83
139
|
/** 约束 ID(与 definitions 一致) */
|
|
84
140
|
id: string;
|
|
85
|
-
/** 检查主体:true = 满足;false = 违反;'skip' =
|
|
141
|
+
/** 检查主体:true = 满足;false = 违反;'skip' = 未评估;CheckDetail = 判定 + 证据行 */
|
|
86
142
|
evaluate(env: CheckEnv): Promise<CheckOutcome> | CheckOutcome;
|
|
87
143
|
}
|
|
88
144
|
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* git 证据适配器(架构评审 #87
|
|
2
|
+
* git 证据适配器(架构评审 #87,适用范围按 ADR-0021 收窄)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* check 链路的唯一 git 取证点:context-builder(触发推断)与 checker 层(CheckEnv 证据)
|
|
5
5
|
* 共用同一实例 = 共用同一证据来源。此前两处各自抓真 git,一次 CLI check 里
|
|
6
6
|
* `git diff --cached --name-only` 实跑两遍。
|
|
7
7
|
*
|
|
8
|
+
* 链外站点(写操作 / 发布编排 / 审查流 / 降级容忍)不并入本 seam,记名豁免清单见
|
|
9
|
+
* `core/CONTEXT.md` 约定段;同型站点(spec/validator 的 staged 列表)经此取证据。
|
|
10
|
+
*
|
|
8
11
|
* seam 上的两个真 adapter:
|
|
9
12
|
* - 缺省 realGitCommandRunner:真 git 子进程
|
|
10
13
|
* - 注入 runner:委托/计数/命令表(测试与定制场景)
|
|
@@ -77,7 +80,7 @@ export function parseHeadDirs(treeOutput: string): Set<string> {
|
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
/**
|
|
80
|
-
* 构造 git 证据 adapter
|
|
83
|
+
* 构造 git 证据 adapter(GitEvidence 实例的唯一构造点:check 链路与 spec/validator 的 staged 取证都经此)
|
|
81
84
|
*
|
|
82
85
|
* @param projectPath 取证目录
|
|
83
86
|
* @param run 命令执行器,缺省真 git;测试注入计数/替身执行器即可脱离子进程
|
package/src/core/index.ts
CHANGED
|
@@ -59,21 +59,10 @@ export type {
|
|
|
59
59
|
CheckType,
|
|
60
60
|
CheckConfig,
|
|
61
61
|
PassesGateConfig,
|
|
62
|
-
PassesGateResult,
|
|
63
62
|
TaskTestResult,
|
|
64
63
|
DynamicTask,
|
|
65
64
|
} from './validators';
|
|
66
65
|
|
|
67
|
-
// 会话启动 / 净室状态
|
|
68
|
-
export {
|
|
69
|
-
SessionStartup,
|
|
70
|
-
createSessionStartup,
|
|
71
|
-
DEFAULT_CODE_CHECKPOINTS,
|
|
72
|
-
MINIMAL_CHECKPOINTS,
|
|
73
|
-
CleanStateManager,
|
|
74
|
-
createCleanStateManager,
|
|
75
|
-
} from './session';
|
|
76
|
-
|
|
77
66
|
// Spec 验证器
|
|
78
67
|
export { SpecValidator } from './spec/validator';
|
|
79
68
|
export type {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { SpecValidator, validateSpec, validateAllSpecs } from '../validator';
|
|
6
|
+
import type { GitCommandRunner } from '../../constraints/git-evidence';
|
|
6
7
|
import * as fs from 'fs/promises';
|
|
7
8
|
|
|
8
9
|
// Mock fs
|
|
@@ -263,6 +264,29 @@ describe('SpecValidator', () => {
|
|
|
263
264
|
expect(result).toBeDefined();
|
|
264
265
|
expect(result.total).toBeGreaterThanOrEqual(0);
|
|
265
266
|
});
|
|
267
|
+
|
|
268
|
+
it('staged 取证经 GitEvidence adapter(注入 runner,不落 child_process)', async () => {
|
|
269
|
+
const calls: Array<{ command: string; cwd: string }> = [];
|
|
270
|
+
const runner: GitCommandRunner = (command, cwd) => {
|
|
271
|
+
calls.push({ command, cwd });
|
|
272
|
+
return 'specs/test.yml\nARCHITECTURE.md\nsrc/utils/helper.ts';
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const files: string[] = await (validator as any).getStagedFiles('/repo', runner);
|
|
276
|
+
|
|
277
|
+
expect(calls).toEqual([{ command: 'git diff --cached --name-only', cwd: '/repo' }]);
|
|
278
|
+
expect(files).toEqual(['/repo/specs/test.yml', '/repo/ARCHITECTURE.md']);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('runner 抛错 → 空列表(取证失败降级语义与迁移前一致)', async () => {
|
|
282
|
+
const runner: GitCommandRunner = () => {
|
|
283
|
+
throw new Error('not a git repository');
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const files: string[] = await (validator as any).getStagedFiles('/repo', runner);
|
|
287
|
+
|
|
288
|
+
expect(files).toEqual([]);
|
|
289
|
+
});
|
|
266
290
|
});
|
|
267
291
|
|
|
268
292
|
describe('validate with custom schema', () => {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import * as fs from 'fs/promises';
|
|
13
13
|
import * as path from 'path';
|
|
14
14
|
import * as glob from 'fast-glob';
|
|
15
|
-
import {
|
|
15
|
+
import { createGitEvidence, splitFileNames, type GitCommandRunner } from '../constraints/git-evidence';
|
|
16
16
|
import type {
|
|
17
17
|
SpecValidatorConfig,
|
|
18
18
|
SpecValidationResult,
|
|
@@ -243,7 +243,7 @@ export class SpecValidator {
|
|
|
243
243
|
let files: string[];
|
|
244
244
|
|
|
245
245
|
if (staged) {
|
|
246
|
-
files = await this.getStagedFiles();
|
|
246
|
+
files = await this.getStagedFiles(cwd);
|
|
247
247
|
} else {
|
|
248
248
|
files = await this.getSpecFiles(cwd);
|
|
249
249
|
}
|
|
@@ -291,15 +291,14 @@ export class SpecValidator {
|
|
|
291
291
|
|
|
292
292
|
/**
|
|
293
293
|
* 获取暂存的 Spec 文件
|
|
294
|
+
*
|
|
295
|
+
* git 事实经 GitEvidence adapter(ADR-0021);`run` 缺省真 git,测试注入替身。
|
|
294
296
|
*/
|
|
295
|
-
private async getStagedFiles(): Promise<string[]> {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
} catch {
|
|
301
|
-
return [];
|
|
302
|
-
}
|
|
297
|
+
private async getStagedFiles(cwd: string, run?: GitCommandRunner): Promise<string[]> {
|
|
298
|
+
const evidence = createGitEvidence(cwd, run);
|
|
299
|
+
return splitFileNames(evidence.changedFileNames(true))
|
|
300
|
+
.filter(f => this.isSpecFile(f))
|
|
301
|
+
.map(f => path.resolve(cwd, f));
|
|
303
302
|
}
|
|
304
303
|
|
|
305
304
|
/**
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Tests for passes-gate.ts
|
|
3
3
|
*
|
|
4
4
|
* Priority: test pure logic methods (check)
|
|
5
|
-
* Skip methods that do real file I/O / exec (
|
|
5
|
+
* Skip methods that do real file I/O / exec (runTests, runTest, detectTestCommand, etc.)
|
|
6
6
|
* 测试产物解析判定不在此面:见 test-output.test.ts(ADR-0012)
|
|
7
7
|
*/
|
|
8
8
|
|
|
@@ -16,7 +16,7 @@ describe('PassesGate', () => {
|
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
it('creates a PassesGate instance with custom config', () => {
|
|
19
|
-
const gate = createPassesGate({ enabled: false,
|
|
19
|
+
const gate = createPassesGate({ enabled: false, allowPartialPass: true });
|
|
20
20
|
expect(gate).toBeInstanceOf(PassesGate);
|
|
21
21
|
});
|
|
22
22
|
});
|
|
@@ -67,11 +67,4 @@ describe('PassesGate', () => {
|
|
|
67
67
|
expect(result.testResult).toEqual(testResult);
|
|
68
68
|
});
|
|
69
69
|
});
|
|
70
|
-
|
|
71
|
-
describe('getTestResult', () => {
|
|
72
|
-
it('returns undefined for unknown task', () => {
|
|
73
|
-
const gate = createPassesGate();
|
|
74
|
-
expect(gate.getTestResult('unknown')).toBeUndefined();
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
70
|
});
|