@dommaker/harness 1.5.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 +11 -0
- package/README.md +0 -2
- package/dist/cli/commands/definitions.d.ts.map +1 -1
- package/dist/cli/commands/definitions.js +1 -32
- package/dist/cli/commands/definitions.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 +22 -2
- package/dist/core/constraints/checker.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/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/passes-gate.d.ts +0 -19
- package/dist/types/passes-gate.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/passes-gate-check.test.ts +20 -4
- package/src/__tests__/passes-gate.test.ts +5 -99
- 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 +3 -2
- 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__/registry.test.ts +46 -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/definitions.ts +1 -32
- 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 +5 -6
- package/src/core/constraints/__tests__/collect-constraints.test.ts +66 -0
- package/src/core/constraints/checker.ts +33 -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 +3 -2
- package/src/gates/__tests__/performance.test.ts +6 -74
- 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/passes-gate.ts +0 -26
- 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 -30
- package/dist/cli/commands/update-user-model.d.ts.map +0 -1
- package/dist/cli/commands/update-user-model.js +0 -406
- 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 -268
- package/src/cli/commands/analyze-sessions.ts +0 -335
- package/src/cli/commands/update-user-model.ts +0 -476
- 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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* TokenBudget 和 TokenEstimator 测试
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { TokenBudget, TokenEstimator
|
|
5
|
+
import { TokenBudget, TokenEstimator } from '../token-budget';
|
|
6
6
|
|
|
7
7
|
describe('TokenEstimator', () => {
|
|
8
8
|
describe('estimateText()', () => {
|
|
@@ -256,83 +256,3 @@ describe('TokenBudget', () => {
|
|
|
256
256
|
});
|
|
257
257
|
});
|
|
258
258
|
});
|
|
259
|
-
|
|
260
|
-
describe('AdaptiveTokenBudget', () => {
|
|
261
|
-
describe('recordActualUsage()', () => {
|
|
262
|
-
it('should record usage history', () => {
|
|
263
|
-
const budget = new AdaptiveTokenBudget(1000);
|
|
264
|
-
budget.recordActualUsage(500);
|
|
265
|
-
budget.recordActualUsage(600);
|
|
266
|
-
expect(budget.getAverageUsage()).toBe(550);
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
it('should trim history when exceeding max size', () => {
|
|
270
|
-
const budget = new AdaptiveTokenBudget(1000);
|
|
271
|
-
for (let i = 0; i < 15; i++) {
|
|
272
|
-
budget.recordActualUsage(100 + i);
|
|
273
|
-
}
|
|
274
|
-
expect(budget.getAverageUsage()).toBeGreaterThan(0);
|
|
275
|
-
});
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
describe('getAverageUsage()', () => {
|
|
279
|
-
it('should return 0 when no history', () => {
|
|
280
|
-
const budget = new AdaptiveTokenBudget(1000);
|
|
281
|
-
expect(budget.getAverageUsage()).toBe(0);
|
|
282
|
-
});
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
describe('predictNeed()', () => {
|
|
286
|
-
it('should return total when no history', () => {
|
|
287
|
-
const budget = new AdaptiveTokenBudget(1000);
|
|
288
|
-
expect(budget.predictNeed()).toBe(1000);
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
it('should predict based on history', () => {
|
|
292
|
-
const budget = new AdaptiveTokenBudget(1000);
|
|
293
|
-
budget.recordActualUsage(400);
|
|
294
|
-
budget.recordActualUsage(500);
|
|
295
|
-
budget.recordActualUsage(600);
|
|
296
|
-
const predicted = budget.predictNeed(0.9);
|
|
297
|
-
expect(predicted).toBeGreaterThan(0);
|
|
298
|
-
});
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
describe('suggestBudgetAdjustment()', () => {
|
|
302
|
-
it('should suggest increase when ratio > 0.9', () => {
|
|
303
|
-
const budget = new AdaptiveTokenBudget(1000);
|
|
304
|
-
for (let i = 0; i < 5; i++) {
|
|
305
|
-
budget.recordActualUsage(950);
|
|
306
|
-
}
|
|
307
|
-
const suggestion = budget.suggestBudgetAdjustment();
|
|
308
|
-
expect(suggestion.action).toBe('increase');
|
|
309
|
-
expect(suggestion.suggestedBudget).toBeGreaterThan(1000);
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
it('should suggest decrease when ratio < 0.5 and history >= 5', () => {
|
|
313
|
-
const budget = new AdaptiveTokenBudget(1000);
|
|
314
|
-
for (let i = 0; i < 5; i++) {
|
|
315
|
-
budget.recordActualUsage(100);
|
|
316
|
-
}
|
|
317
|
-
const suggestion = budget.suggestBudgetAdjustment();
|
|
318
|
-
expect(suggestion.action).toBe('decrease');
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
it('should maintain when ratio is moderate', () => {
|
|
322
|
-
const budget = new AdaptiveTokenBudget(1000);
|
|
323
|
-
for (let i = 0; i < 5; i++) {
|
|
324
|
-
budget.recordActualUsage(600);
|
|
325
|
-
}
|
|
326
|
-
const suggestion = budget.suggestBudgetAdjustment();
|
|
327
|
-
expect(suggestion.action).toBe('maintain');
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
it('should maintain when history < 5 even if low usage', () => {
|
|
331
|
-
const budget = new AdaptiveTokenBudget(1000);
|
|
332
|
-
budget.recordActualUsage(100);
|
|
333
|
-
budget.recordActualUsage(100);
|
|
334
|
-
const suggestion = budget.suggestBudgetAdjustment();
|
|
335
|
-
expect(suggestion.action).toBe('maintain');
|
|
336
|
-
});
|
|
337
|
-
});
|
|
338
|
-
});
|
package/src/context/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 上下文管理模块(ADR-0003:显式清单,禁 export *)
|
|
3
3
|
*
|
|
4
|
-
* Token 预算 +
|
|
4
|
+
* Token 预算 + 会话管理 + 知识注入
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
// 类型
|
|
@@ -17,14 +17,9 @@ export type {
|
|
|
17
17
|
SessionHandle,
|
|
18
18
|
SessionMessage,
|
|
19
19
|
} from './types';
|
|
20
|
-
export { DEFAULT_COMPACTION_CONFIG } from './types';
|
|
21
20
|
|
|
22
21
|
// Token 预算
|
|
23
|
-
export {
|
|
24
|
-
|
|
25
|
-
// 会话压缩
|
|
26
|
-
export { SessionCompaction } from './compaction';
|
|
27
|
-
export type { CompactionResult } from './compaction';
|
|
22
|
+
export { TokenBudget, TokenEstimator } from './token-budget';
|
|
28
23
|
|
|
29
24
|
// 会话管理
|
|
30
25
|
export { SessionManager } from './session-manager';
|
|
@@ -220,76 +220,3 @@ export class TokenBudget {
|
|
|
220
220
|
};
|
|
221
221
|
}
|
|
222
222
|
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* 自适应预算管理器
|
|
226
|
-
*
|
|
227
|
-
* 根据历史使用情况动态调整预算
|
|
228
|
-
*/
|
|
229
|
-
export class AdaptiveTokenBudget extends TokenBudget {
|
|
230
|
-
private history: number[] = [];
|
|
231
|
-
private maxHistorySize: number = 10;
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* 记录实际使用情况
|
|
235
|
-
*/
|
|
236
|
-
recordActualUsage(actualTokens: number): void {
|
|
237
|
-
this.history.push(actualTokens);
|
|
238
|
-
if (this.history.length > this.maxHistorySize) {
|
|
239
|
-
this.history.shift();
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* 获取平均使用量
|
|
245
|
-
*/
|
|
246
|
-
getAverageUsage(): number {
|
|
247
|
-
if (this.history.length === 0) return 0;
|
|
248
|
-
return this.history.reduce((a, b) => a + b, 0) / this.history.length;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* 预测未来需求
|
|
253
|
-
*/
|
|
254
|
-
predictNeed(confidence: number = 0.9): number {
|
|
255
|
-
if (this.history.length === 0) return this.total;
|
|
256
|
-
|
|
257
|
-
const avg = this.getAverageUsage();
|
|
258
|
-
const max = Math.max(...this.history);
|
|
259
|
-
|
|
260
|
-
// 简单预测:平均值 + 一定比例的最大值
|
|
261
|
-
return Math.ceil(avg + (max - avg) * confidence);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* 建议预算调整
|
|
266
|
-
*/
|
|
267
|
-
suggestBudgetAdjustment(): {
|
|
268
|
-
action: 'increase' | 'decrease' | 'maintain';
|
|
269
|
-
suggestedBudget: number;
|
|
270
|
-
reason: string;
|
|
271
|
-
} {
|
|
272
|
-
const avg = this.getAverageUsage();
|
|
273
|
-
const ratio = avg / this.total;
|
|
274
|
-
|
|
275
|
-
if (ratio > 0.9) {
|
|
276
|
-
return {
|
|
277
|
-
action: 'increase',
|
|
278
|
-
suggestedBudget: Math.ceil(avg * 1.2),
|
|
279
|
-
reason: '平均使用量接近预算上限,建议增加 20%'
|
|
280
|
-
};
|
|
281
|
-
} else if (ratio < 0.5 && this.history.length >= 5) {
|
|
282
|
-
return {
|
|
283
|
-
action: 'decrease',
|
|
284
|
-
suggestedBudget: Math.ceil(avg * 1.2),
|
|
285
|
-
reason: '平均使用量远低于预算,可减少预算'
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
return {
|
|
290
|
-
action: 'maintain',
|
|
291
|
-
suggestedBudget: this.total,
|
|
292
|
-
reason: '预算使用合理'
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
}
|
package/src/context/types.ts
CHANGED
|
@@ -19,15 +19,6 @@ export interface CompactionConfig {
|
|
|
19
19
|
fallbackStrategy: 'truncate-middle' | 'head-drop' | 'retry-with-clamp';
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
export const DEFAULT_COMPACTION_CONFIG: CompactionConfig = {
|
|
23
|
-
triggerRatio: 0.8,
|
|
24
|
-
level: 'eviction',
|
|
25
|
-
preserveToolCallPairs: true,
|
|
26
|
-
structuredSummary: true,
|
|
27
|
-
maxSummaryTokens: 2000,
|
|
28
|
-
fallbackStrategy: 'truncate-middle',
|
|
29
|
-
};
|
|
30
|
-
|
|
31
22
|
export interface SessionMessage {
|
|
32
23
|
role: 'user' | 'assistant' | 'tool';
|
|
33
24
|
content: string;
|
package/src/core/CONTEXT.md
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
# core/
|
|
2
2
|
|
|
3
3
|
## 职责
|
|
4
|
-
约束引擎核心:check/prompt 二元约束系统(ADR-0001)、生效集合并(effective-constraints)、检查点验证器(CSO/passes-gate)
|
|
4
|
+
约束引擎核心:check/prompt 二元约束系统(ADR-0001)、生效集合并(effective-constraints)、检查点验证器(CSO/passes-gate)、Spec 验证器、项目配置加载。
|
|
5
5
|
|
|
6
6
|
## 核心导出
|
|
7
|
-
- `constraints/` — 约束定义(IRON_LAWS/GUIDELINES/PROMPTS) + 检查引擎(ConstraintChecker,拦截统一由 checkBeforeExecution 承担,ADR-0004) + 缓存(CheckCache:TTL 缓存 + 计数采样,H6/G5 起公开导出) + 注入渲染(injection-renderer) + 落点写入器(injection-writer:「声明→对照→幂等替换」中 marker-range 替换的唯一 writer——replaceStandaloneRange/replaceEnclosedRange/cutMarkerBlock + 半标记守护;落点路由读写两侧同住此模块 resolveInjectionTarget(CLAUDE.md 有标记段优先、否则 AGENTS.md PRESERVE:governance,studio #307)/resolveGovernanceLanding(init 写侧,含旧版无标记 Governance Rules 块豁免),ADR-0011) + Agent prompt 渲染(agent-prompt-renderer:trigger 参数化分组渲染,role 路由留 studio,H6/G6)/漂移校验(injection-drift:纯检测只读,路由消费 injection-writer)/使用统计(usage-report:constraints report·retire 的只读数据层,读 trace 两个入口——`readProjectTracesReport()` 带 `skippedLines`、`readProjectTraces()` 兼容包装丢计数;报告模型 `ConstraintsUsageReport` 的降级位 = `traceFileExists` + `skippedLines`,harness#100)/清单对照(capabilities-reconcile:CAPABILITIES.md 与代码实况的覆盖/幽灵判定唯一实现,capability_sync·docs_freshness·sync-docs 共消费,ADR-0009);CheckEnv 生产侧唯一构造点 `buildCheckEnv(context, providers|'none')`(checkers/types.ts:providers = 证据接线,git 两项绑 GitEvidence 实例,'none' = 显式不接 → evidence flag checker 按契约 skip);判定接缝 `CheckOutcome = boolean | 'skip' | CheckDetail{pass, evidence}`(harness#119/ADR-0016)——编排层与 `gates/checker-gate` 一律经唯一归一点 `normalizeCheckOutcome()` 取三态 + 证据,不各自 `=== false`(对新形状会误判);证据行由 checker 自行措辞(自描述、不带缩进),组装与截断策略单点在 `formatEvidence()`/`MAX_EVIDENCE_ITEMS`(同住 checkers/types.ts);证据四出口 = `ConstraintResult.evidence` → CLI 结论块/提示块、`ExecutionTrace.evidence`、铁律 `ConstraintViolationError.message`(铁律违规直接 throw
|
|
8
|
-
- `git-evidence.ts` —
|
|
7
|
+
- `constraints/` — 约束定义(IRON_LAWS/GUIDELINES/PROMPTS) + 检查引擎(ConstraintChecker,拦截统一由 checkBeforeExecution 承担,ADR-0004) + 缓存(CheckCache:TTL 缓存 + 计数采样,H6/G5 起公开导出) + 注入渲染(injection-renderer) + 落点写入器(injection-writer:「声明→对照→幂等替换」中 marker-range 替换的唯一 writer——replaceStandaloneRange/replaceEnclosedRange/cutMarkerBlock + 半标记守护;落点路由读写两侧同住此模块 resolveInjectionTarget(CLAUDE.md 有标记段优先、否则 AGENTS.md PRESERVE:governance,studio #307)/resolveGovernanceLanding(init 写侧,含旧版无标记 Governance Rules 块豁免),ADR-0011) + Agent prompt 渲染(agent-prompt-renderer:trigger 参数化分组渲染,role 路由留 studio,H6/G6)/漂移校验(injection-drift:纯检测只读,路由消费 injection-writer)/使用统计(usage-report:constraints report·retire 的只读数据层,读 trace 两个入口——`readProjectTracesReport()` 带 `skippedLines`、`readProjectTraces()` 兼容包装丢计数;报告模型 `ConstraintsUsageReport` 的降级位 = `traceFileExists` + `skippedLines`,harness#100)/清单对照(capabilities-reconcile:CAPABILITIES.md 与代码实况的覆盖/幽灵判定唯一实现,capability_sync·docs_freshness·sync-docs 共消费,ADR-0009);CheckEnv 生产侧唯一构造点 `buildCheckEnv(context, providers|'none')`(checkers/types.ts:providers = 证据接线,git 两项绑 GitEvidence 实例,'none' = 显式不接 → evidence flag checker 按契约 skip);判定接缝 `CheckOutcome = boolean | 'skip' | CheckDetail{pass, evidence}`(harness#119/ADR-0016)——编排层与 `gates/checker-gate` 一律经唯一归一点 `normalizeCheckOutcome()` 取三态 + 证据,不各自 `=== false`(对新形状会误判);证据行由 checker 自行措辞(自描述、不带缩进),组装与截断策略单点在 `formatEvidence()`/`MAX_EVIDENCE_ITEMS`(同住 checkers/types.ts);证据四出口 = `ConstraintResult.evidence` → CLI 结论块/提示块、`ExecutionTrace.evidence`、铁律 `ConstraintViolationError.message`(铁律违规直接 throw,异常文案是其证据唯一外溢面);检查引擎双出口——`checkConstraints`(block:首个铁律违规即抛,throw 契约不动)与 `collectConstraints`(collect:共享检查体、不抛、全量收集,report 的不抛出口,架构评审候选1)
|
|
8
|
+
- `git-evidence.ts` — check 链路的 git 取证唯一接缝(#87,范围按 ADR-0021 收窄;`spec/validator` 的 staged 列表同型复用):`createGitEvidence(projectPath, run?)` 返回 `GitEvidence{stagedDiff, changedFileNames(staged), headDirs}`,seam 上两个真 adapter(缺省 `realGitCommandRunner` 走真 git / 注入 runner 供计数与替身)。命令级 memo 落在此实例(含失败结果),实例生命周期 = 一次 check run,故 context-builder 与 ConstraintChecker 传同一实例即共用证据;判定语义与迁移前一致(diff 取证失败 → 空串,`git ls-tree` 失败 → null = 所有目录视为新);`parseHeadDirs`/`splitFileNames` 是配套纯函数
|
|
9
9
|
- `constraints/context-builder.ts` — 只装配不取证:`buildConstraintContext(options.evidence?)` 与 `detectTrigger(options.evidence?)` 经 GitEvidence 取 git 事实(不传 = 自建真 adapter),本模块无 child_process import;其余证据标志(traces/文档标记)仍为本地探测
|
|
10
10
|
- `effective-constraints.ts` — `getEffectiveConstraints(projectRoot, {preset?})`:全仓唯一生效集来源(内置 → preset → config.yml 禁用(内置与 custom 同效)→ custom 追加(禁用/已退役的不追加)→ scenes 过滤);`getMergedConstraintsConfig(projectRoot, {preset?})`:同链路的完整 MergedConstraintsConfig 形状(含 disabled/custom/unknownIds),内置工单 23 优先级规则(--preset 仅在无项目自定义配置时生效),check 命令经此入口;`lintEffectiveConfig` 配置诊断
|
|
11
11
|
- `effective-set.ts` — `filterEnabledEntries(knownIds, entries, {onUnknownId})`:约束侧(collect)与门禁侧(throw)共用的 config 条目筛选器
|
|
12
|
-
- `validators/` — checkpoints、passes-gate、CSO 验证器;`test-output.ts` —「测试产物解读」唯一实现:`extractFailures` 提取函数 + `judgeTestRun` 判定入口(退出码为主 + 行首锚定的文本失败信号交叉否决;文本救不回非零退出,`allowPartialPass` 只作用于文本否决那一维),passes-gate 与 `gates/acceptance` 的结论一律从它取、各自体内不写判定 `if`(解析收口 ADR-0012,判定依据 ADR-0014);**不读覆盖率**——harness 核心判定对覆盖率不取数不执法,判覆盖率的两处(CLI `passes-gate --coverage`、`gates/performance`)各自读 json-summary,`extractCoverage` 与 `TaskTestResult.coverage` 已随 ADR-0015 删除;`PassesGate.runTests(workDir)` 的执行与证据落盘根是**必传形参**,本层不再取 cwd(harness#95,约定正本见 `src/cli/commands/CONTEXT.md`);`detectTestCommand` 是测试命令探测唯一正本(模块级导出:test:ci → test 排 echo 占位 → test:e2e → test:coverage → Python 标记 pyproject.toml/pytest.ini → go.mod,探不到返回 undefined,兜底归调用方——CLI 映射 skip、PassesGate 内部 fail-closed,不再兜底 'npm test';CLI 本地版已删,架构评审 A2);PassesGate 扩展注册表(registerExtension/unregisterExtension/getExtensionNames/runAllTests/checkAllPasses + PassesGateExtension/ExtensionTestResult 两类型与包根导出)已整段删除——零 adapter 假想 seam 且扩展结果本就不参与 allowed 判定,需要扩展测试类型时按 ADR-0014 判例在 `judgeTestRun` 层加,不恢复旧 seam(harness#104,架构评审 B1
|
|
13
|
-
- `session/` — 会话管理
|
|
12
|
+
- `validators/` — checkpoints、passes-gate、CSO 验证器;`test-output.ts` —「测试产物解读」唯一实现:`extractFailures` 提取函数 + `judgeTestRun` 判定入口(退出码为主 + 行首锚定的文本失败信号交叉否决;文本救不回非零退出,`allowPartialPass` 只作用于文本否决那一维),passes-gate 与 `gates/acceptance` 的结论一律从它取、各自体内不写判定 `if`(解析收口 ADR-0012,判定依据 ADR-0014);**不读覆盖率**——harness 核心判定对覆盖率不取数不执法,判覆盖率的两处(CLI `passes-gate --coverage`、`gates/performance`)各自读 json-summary,`extractCoverage` 与 `TaskTestResult.coverage` 已随 ADR-0015 删除;`PassesGate.runTests(workDir)` 的执行与证据落盘根是**必传形参**,本层不再取 cwd(harness#95,约定正本见 `src/cli/commands/CONTEXT.md`);`detectTestCommand` 是测试命令探测唯一正本(模块级导出:test:ci → test 排 echo 占位 → test:e2e → test:coverage → Python 标记 pyproject.toml/pytest.ini → go.mod,探不到返回 undefined,兜底归调用方——CLI 映射 skip、PassesGate 内部 fail-closed,不再兜底 'npm test';CLI 本地版已删,架构评审 A2);PassesGate 扩展注册表(registerExtension/unregisterExtension/getExtensionNames/runAllTests/checkAllPasses + PassesGateExtension/ExtensionTestResult 两类型与包根导出)已整段删除——零 adapter 假想 seam 且扩展结果本就不参与 allowed 判定,需要扩展测试类型时按 ADR-0014 判例在 `judgeTestRun` 层加,不恢复旧 seam(harness#104,架构评审 B1);PassesGate 公开面 = `check()` + `runTests()` 两个真实消费点,`setPasses`/`getTestResult`/`checkTestFileChanges` 三方法与死数据 `PROTECTED_TEST_PATTERNS`、私有 `testResults` 缓存已随 ADR-0022(双仓零消费者)删除,历史 AC-007「保留 setPasses 向后兼容」同案推翻;包内模块,不进 `src/index.ts` 导出面
|
|
14
13
|
- `spec/validator` — SpecValidator
|
|
15
14
|
- `project-config-loader` — 项目配置加载 + 约束合并(mergeConstraints 内含 preset 裁剪:presets/ 纯数据 + 未知名回落 standard,禁用到未知 id 经 effective-set 共享筛选器 collect 模式);`getGovernanceConfig` 是全仓唯一允许手写钻取 `config.governance` 的点(工单 84),消费方一律经它或经 `resolveContextFiles`(三态:unconfigured / enabled-empty / enabled,dirs 元素类型在此收口)取数据,不得再自己 cast `Record<string, unknown>`;checker 对前两态一律 skip,自动探测回落只属于 init/扫描类工具流调用方
|
|
16
15
|
|
|
@@ -25,7 +24,7 @@
|
|
|
25
24
|
- check 层必须带真实 checker(注册表闭环,引用未注册 checker 构建报错)
|
|
26
25
|
- Iron Law 违规必须 throw ConstraintViolationError
|
|
27
26
|
- 上行数据一律注入,不在 core 内 require 上层(harness#88):trace 记录器经 `ConstraintChecker` 构造参数注入(缺省 no-op,`getInstance()`/`constraintChecker` 即未接线的默认实例,只评估不写 trace),真实收集器由组合根接线(CLI check/report、`bootstrapHarness`);CAPABILITIES.md 能力清单计数经 `capabilities-parser` 的 `CapabilityDefinitionSource` 由 cli 侧注入定义表
|
|
28
|
-
- git
|
|
27
|
+
- git **取证**(读判定证据)在 check 链路内只能经 `constraints/git-evidence.ts` 的 adapter 取(#87,适用范围按 ADR-0021 收窄为「check 链路唯一」,不再声称全仓唯一):`context-builder` / `checker` / `CheckEnv` / `validators` 这条判定链内不得出现 `execSync`/`execAsync`/`execFileAsync` 跑 git;`spec/validator` 的 staged 文件列表与 `changedFileNames(staged:true)` 同型,已收口(就地 `createGitEvidence(cwd)`,测试注入 runner);一次 run 一份实例、沿调用链显式传递(CLI check → buildConstraintContext → checkConstraints),memo 既不做成模块级全局也不做成单例字段。**链外站点记名豁免**(语义异质:不要 memo、不要 once 语义、失败处理各异,硬并入会造出宽 interface)——`cli/commands/release.ts`(发布流程编排,非判定证据)、`gates/review.ts` 与 `cli/commands/review.ts`(PR 审查流,gh/日志语义)、`knowledge/import.ts`(导入启发式,失败记 ImportError 降级)。豁免清单外新增 git 站点先回答「在不在判定链内」;`validators/passes-gate.ts` 的 `git diff --name-only` 站点已随 ADR-0022 删 `checkTestFileChanges` 消失,判定链内当下无站外 git 调用点
|
|
29
28
|
|
|
30
29
|
## 注意事项
|
|
31
30
|
- 零 Token 成本:所有分析纯文件操作,无 LLM 调用
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* collectConstraints — 收集模式(架构评审候选1)
|
|
3
|
+
*
|
|
4
|
+
* 与 checkConstraints 共享检查体,唯一差别是不 throw:
|
|
5
|
+
* 铁律违规不抛、全量收进 ironLaws、guidelines 照常、passed 如实。
|
|
6
|
+
* block 模式的 throw 契约由 checker-extra / iron-laws 既有用例钉住,这里只测 collect 侧。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, it, expect } from '@jest/globals';
|
|
10
|
+
import { ConstraintChecker } from '../checker';
|
|
11
|
+
import type { ConstraintContext, ConstraintResult } from '../../../types/constraint';
|
|
12
|
+
|
|
13
|
+
// 缺省 no-op trace 记录器:本套件不验 trace 面(trace 面由 trace-injection 套件钉住)
|
|
14
|
+
const checker = new ConstraintChecker();
|
|
15
|
+
|
|
16
|
+
/** 只违 no_completion_without_verification,其余铁律满足(flag 口径同 checker-extra) */
|
|
17
|
+
const VIOLATING: ConstraintContext = {
|
|
18
|
+
operation: 'code_implementation',
|
|
19
|
+
hasTest: false,
|
|
20
|
+
hasVerificationEvidence: false,
|
|
21
|
+
hasRequirement: true,
|
|
22
|
+
hasSingleTask: true,
|
|
23
|
+
taskDescription: 'single focused change',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const CLEAN: ConstraintContext = {
|
|
27
|
+
operation: 'code_implementation',
|
|
28
|
+
hasTest: true,
|
|
29
|
+
hasVerificationEvidence: true,
|
|
30
|
+
hasRequirement: true,
|
|
31
|
+
hasSingleTask: true,
|
|
32
|
+
taskDescription: 'single focused change',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
describe('collectConstraints(收集模式,不抛)', () => {
|
|
36
|
+
it('铁律违规不抛 —— resolves 而非 rejects', async () => {
|
|
37
|
+
await expect(checker.collectConstraints(VIOLATING)).resolves.toBeDefined();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('违规铁律如实进 ironLaws:satisfied=false 且 passed=false', async () => {
|
|
41
|
+
const result = await checker.collectConstraints(VIOLATING);
|
|
42
|
+
const violated = result.ironLaws.find((r: ConstraintResult) => r.id === 'no_completion_without_verification');
|
|
43
|
+
expect(violated).toBeDefined();
|
|
44
|
+
expect(violated!.satisfied).toBe(false);
|
|
45
|
+
expect(result.passed).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('首个违规不截断:触发域内后续铁律照常评估', async () => {
|
|
49
|
+
const result = await checker.collectConstraints(VIOLATING);
|
|
50
|
+
const ids = result.ironLaws.map((r: ConstraintResult) => r.id);
|
|
51
|
+
expect(ids).toContain('incremental_progress');
|
|
52
|
+
expect(ids).toContain('no_implementation_without_requirement');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('guidelines 照常执行,warningCount 与不满意条目同口径', async () => {
|
|
56
|
+
const result = await checker.collectConstraints(VIOLATING);
|
|
57
|
+
expect(result.guidelines.length).toBeGreaterThan(0);
|
|
58
|
+
expect(result.warningCount).toBe(result.guidelines.filter((g: ConstraintResult) => !g.satisfied).length);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('干净上下文对照:passed=true、ironLaws 全满意', async () => {
|
|
62
|
+
const result = await checker.collectConstraints(CLEAN);
|
|
63
|
+
expect(result.passed).toBe(true);
|
|
64
|
+
expect(result.ironLaws.every((r: ConstraintResult) => r.satisfied)).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -263,6 +263,35 @@ export class ConstraintChecker {
|
|
|
263
263
|
context: ConstraintContext,
|
|
264
264
|
customConfig?: MergedConstraintsConfig | null,
|
|
265
265
|
evidence?: GitEvidence
|
|
266
|
+
): Promise<ConstraintCheckResult> {
|
|
267
|
+
return this.runAllConstraints(context, customConfig, evidence, 'block');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* 收集模式执行约束检查(harness report 的不抛出口,架构评审候选1)
|
|
272
|
+
*
|
|
273
|
+
* 与 checkConstraints 共享同一检查体,唯一差别是不 throw:
|
|
274
|
+
* 铁律违规照进 result.ironLaws、passed=false,后续铁律与 guidelines 照常执行,
|
|
275
|
+
* trace 逐条照记。报告类消费者拿全量视图;阻断语义只属于 checkConstraints
|
|
276
|
+
* (throw 契约是 #119 判定证据的外溢面,逐字不动)。
|
|
277
|
+
*/
|
|
278
|
+
async collectConstraints(
|
|
279
|
+
context: ConstraintContext,
|
|
280
|
+
customConfig?: MergedConstraintsConfig | null,
|
|
281
|
+
evidence?: GitEvidence
|
|
282
|
+
): Promise<ConstraintCheckResult> {
|
|
283
|
+
return this.runAllConstraints(context, customConfig, evidence, 'collect');
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* checkConstraints / collectConstraints 的共享检查体;
|
|
288
|
+
* mode='block' 首个铁律违规即抛,mode='collect' 全量跑完不抛
|
|
289
|
+
*/
|
|
290
|
+
private async runAllConstraints(
|
|
291
|
+
context: ConstraintContext,
|
|
292
|
+
customConfig: MergedConstraintsConfig | null | undefined,
|
|
293
|
+
evidence: GitEvidence | undefined,
|
|
294
|
+
mode: 'block' | 'collect'
|
|
266
295
|
): Promise<ConstraintCheckResult> {
|
|
267
296
|
// run 起始:重置 src 扫描缓存(S7);git 证据 = 本 run 独占的 adapter 实例(工单 18 → #87)
|
|
268
297
|
this.cache.invalidate();
|
|
@@ -279,7 +308,7 @@ export class ConstraintChecker {
|
|
|
279
308
|
// context.operation 为主触发条件,extraTriggers 为次级推断(ADR-0001),任一命中即匹配
|
|
280
309
|
const operations = [context.operation, ...(context.extraTriggers ?? [])];
|
|
281
310
|
|
|
282
|
-
// 1. Iron Laws:
|
|
311
|
+
// 1. Iron Laws: block 模式首个违规即抛;collect 模式全量收集
|
|
283
312
|
for (const constraint of Object.values(constraints.ironLaws)) {
|
|
284
313
|
if (!matchesTrigger(constraint, operations)) continue;
|
|
285
314
|
|
|
@@ -289,7 +318,9 @@ export class ConstraintChecker {
|
|
|
289
318
|
|
|
290
319
|
if (!checkResult.satisfied) {
|
|
291
320
|
result.passed = false;
|
|
292
|
-
|
|
321
|
+
if (mode === 'block') {
|
|
322
|
+
throw new ConstraintViolationError(checkResult);
|
|
323
|
+
}
|
|
293
324
|
}
|
|
294
325
|
}
|
|
295
326
|
|
|
@@ -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
|
});
|