@dommaker/harness 1.0.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -13
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/status.js +0 -9
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/sync-docs/agents-syncer.d.ts.map +1 -1
- package/dist/cli/commands/sync-docs/agents-syncer.js +25 -4
- package/dist/cli/commands/sync-docs/agents-syncer.js.map +1 -1
- package/dist/completion-checkers/classify.d.ts +23 -0
- package/dist/completion-checkers/classify.d.ts.map +1 -0
- package/dist/completion-checkers/classify.js +37 -0
- package/dist/completion-checkers/classify.js.map +1 -0
- package/dist/completion-checkers/contract-presence.d.ts +12 -0
- package/dist/completion-checkers/contract-presence.d.ts.map +1 -0
- package/dist/completion-checkers/contract-presence.js +36 -0
- package/dist/completion-checkers/contract-presence.js.map +1 -0
- package/dist/completion-checkers/glob-match.d.ts +13 -0
- package/dist/completion-checkers/glob-match.d.ts.map +1 -0
- package/dist/completion-checkers/glob-match.js +58 -0
- package/dist/completion-checkers/glob-match.js.map +1 -0
- package/dist/completion-checkers/index.d.ts +19 -0
- package/dist/completion-checkers/index.d.ts.map +1 -0
- package/dist/completion-checkers/index.js +47 -0
- package/dist/completion-checkers/index.js.map +1 -0
- package/dist/completion-checkers/phase-format.d.ts +15 -0
- package/dist/completion-checkers/phase-format.d.ts.map +1 -0
- package/dist/completion-checkers/phase-format.js +42 -0
- package/dist/completion-checkers/phase-format.js.map +1 -0
- package/dist/completion-checkers/tdd-chain.d.ts +16 -0
- package/dist/completion-checkers/tdd-chain.d.ts.map +1 -0
- package/dist/completion-checkers/tdd-chain.js +57 -0
- package/dist/completion-checkers/tdd-chain.js.map +1 -0
- package/dist/completion-checkers/types.d.ts +77 -0
- package/dist/completion-checkers/types.d.ts.map +1 -0
- package/dist/completion-checkers/types.js +10 -0
- package/dist/completion-checkers/types.js.map +1 -0
- package/dist/core/constraints/checker.d.ts +3 -1
- package/dist/core/constraints/checker.d.ts.map +1 -1
- package/dist/core/constraints/checker.js +4 -2
- package/dist/core/constraints/checker.js.map +1 -1
- package/dist/core/constraints/interceptor.d.ts +3 -2
- package/dist/core/constraints/interceptor.d.ts.map +1 -1
- package/dist/core/constraints/interceptor.js +4 -4
- package/dist/core/constraints/interceptor.js.map +1 -1
- package/dist/index.d.ts +10 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -5
- package/dist/index.js.map +1 -1
- package/dist/monitoring/constraint-doctor.d.ts +2 -8
- package/dist/monitoring/constraint-doctor.d.ts.map +1 -1
- package/dist/monitoring/constraint-doctor.js +2 -4
- package/dist/monitoring/constraint-doctor.js.map +1 -1
- package/dist/monitoring/traces.d.ts +3 -3
- package/dist/monitoring/traces.d.ts.map +1 -1
- package/dist/monitoring/traces.js.map +1 -1
- package/dist/pretool-use-hook.d.ts +16 -0
- package/dist/pretool-use-hook.d.ts.map +1 -0
- package/dist/pretool-use-hook.js +68 -0
- package/dist/pretool-use-hook.js.map +1 -0
- package/dist/types/trace.d.ts +4 -4
- package/dist/types/trace.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/CONTEXT.md +2 -2
- package/src/__tests__/checker-extra.test.ts +47 -0
- package/src/__tests__/pretool-use-hook.test.ts +82 -0
- package/src/cli/commands/__tests__/status-extra.test.ts +0 -22
- package/src/cli/commands/__tests__/status.test.ts +0 -15
- package/src/cli/commands/__tests__/sync-docs-agents.test.ts +55 -0
- package/src/cli/commands/status.ts +0 -10
- package/src/cli/commands/sync-docs/agents-syncer.ts +26 -6
- package/src/completion-checkers/CONTEXT.md +25 -0
- package/src/completion-checkers/__tests__/contract-presence.test.ts +44 -0
- package/src/completion-checkers/__tests__/phase-format.test.ts +60 -0
- package/src/completion-checkers/__tests__/tdd-chain.test.ts +99 -0
- package/src/completion-checkers/classify.ts +47 -0
- package/src/completion-checkers/contract-presence.ts +40 -0
- package/src/completion-checkers/glob-match.ts +54 -0
- package/src/completion-checkers/index.ts +19 -0
- package/src/completion-checkers/phase-format.ts +48 -0
- package/src/completion-checkers/tdd-chain.ts +63 -0
- package/src/completion-checkers/types.ts +84 -0
- package/src/core/CONTEXT.md +1 -1
- package/src/core/constraints/__tests__/interceptor.test.ts +14 -0
- package/src/core/constraints/checker.ts +5 -2
- package/src/core/constraints/interceptor.ts +7 -4
- package/src/index.ts +24 -6
- package/src/monitoring/constraint-doctor.ts +4 -15
- package/src/monitoring/traces.ts +3 -3
- package/src/pretool-use-hook.ts +68 -0
- package/src/types/trace.ts +4 -4
package/src/index.ts
CHANGED
|
@@ -66,6 +66,13 @@ export * from './safety';
|
|
|
66
66
|
// ========================================
|
|
67
67
|
export * from './verification';
|
|
68
68
|
|
|
69
|
+
// ========================================
|
|
70
|
+
// Completion Checkers 导出(T7-E1,studio#160)
|
|
71
|
+
// WU 收尾软观测三纯判定函数:tdd-chain / phase-format / contract-presence。
|
|
72
|
+
// 纯函数直接 export,不进 ConstraintCheck 闭环注册表。
|
|
73
|
+
// ========================================
|
|
74
|
+
export * from './completion-checkers';
|
|
75
|
+
|
|
69
76
|
// ========================================
|
|
70
77
|
// Dashboard 数据导出
|
|
71
78
|
// ========================================
|
|
@@ -106,6 +113,7 @@ export type { RenderConstraintsByTriggerOptions } from './core/constraints/agent
|
|
|
106
113
|
import { constraintChecker } from './core/constraints/checker';
|
|
107
114
|
import { getTraceCollector } from './monitoring/traces';
|
|
108
115
|
import type { ConstraintContext, ConstraintCheckResult, ConstraintTrigger } from './types/constraint';
|
|
116
|
+
import type { MergedConstraintsConfig } from './types/project-config';
|
|
109
117
|
import { constraintInterceptor } from './core/constraints/interceptor';
|
|
110
118
|
import type { EnforcementExecutor, EnforcementId, InterceptionResult } from './types/enforcement';
|
|
111
119
|
|
|
@@ -119,13 +127,15 @@ export const interceptor = constraintInterceptor;
|
|
|
119
127
|
* 拦截操作 — 在操作执行前检查约束
|
|
120
128
|
* @param trigger - 触发条件
|
|
121
129
|
* @param context - 约束上下文
|
|
130
|
+
* @param customConfig - 可选,per-request 自定义约束配置;不传则用内置约束集
|
|
122
131
|
* @returns 拦截结果(是否通过、违规列表)
|
|
123
132
|
*/
|
|
124
133
|
export async function interceptOperation(
|
|
125
134
|
trigger: ConstraintTrigger,
|
|
126
|
-
context: ConstraintContext
|
|
135
|
+
context: ConstraintContext,
|
|
136
|
+
customConfig?: MergedConstraintsConfig | null
|
|
127
137
|
): Promise<InterceptionResult> {
|
|
128
|
-
return constraintInterceptor.intercept(trigger, context);
|
|
138
|
+
return constraintInterceptor.intercept(trigger, context, customConfig);
|
|
129
139
|
}
|
|
130
140
|
|
|
131
141
|
/**
|
|
@@ -159,12 +169,16 @@ export function registerExecutor(
|
|
|
159
169
|
*
|
|
160
170
|
* @param context - 约束上下文
|
|
161
171
|
* @param options.onTrace - 每条约束检查后的回调(用于记录 trace 到外部存储)
|
|
172
|
+
* @param options.customConfig - 可选,per-request 自定义约束配置;不传则用内置约束集
|
|
162
173
|
*/
|
|
163
174
|
export async function checkConstraints(
|
|
164
175
|
context: ConstraintContext,
|
|
165
|
-
options?: {
|
|
176
|
+
options?: {
|
|
177
|
+
onTrace?: (result: import('./types/constraint').ConstraintResult) => void;
|
|
178
|
+
customConfig?: MergedConstraintsConfig | null;
|
|
179
|
+
}
|
|
166
180
|
): Promise<ConstraintCheckResult> {
|
|
167
|
-
const result = await constraintChecker.checkConstraints(context);
|
|
181
|
+
const result = await constraintChecker.checkConstraints(context, options?.customConfig ?? null);
|
|
168
182
|
if (options?.onTrace) {
|
|
169
183
|
for (const r of result.ironLaws) options.onTrace(r);
|
|
170
184
|
for (const r of result.guidelines) options.onTrace(r);
|
|
@@ -174,9 +188,13 @@ export async function checkConstraints(
|
|
|
174
188
|
|
|
175
189
|
/**
|
|
176
190
|
* 执行前检查(仅 Iron Laws)
|
|
191
|
+
*
|
|
192
|
+
* @param context - 约束上下文
|
|
193
|
+
* @param customConfig - 可选,per-request 自定义约束配置;不传则用内置约束集
|
|
177
194
|
*/
|
|
178
195
|
export async function checkBeforeExecution(
|
|
179
|
-
context: ConstraintContext
|
|
196
|
+
context: ConstraintContext,
|
|
197
|
+
customConfig?: MergedConstraintsConfig | null
|
|
180
198
|
): Promise<void> {
|
|
181
|
-
return constraintChecker.beforeExecution(context);
|
|
199
|
+
return constraintChecker.beforeExecution(context, customConfig);
|
|
182
200
|
}
|
|
@@ -19,17 +19,8 @@ export type { Diagnosis };
|
|
|
19
19
|
* 诊断配置
|
|
20
20
|
*/
|
|
21
21
|
export interface ConstraintDoctorConfig {
|
|
22
|
-
/**
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
/** Agent 类型(预留,后续对接) */
|
|
26
|
-
agentType?: 'claude-code' | 'codex' | 'openai';
|
|
27
|
-
|
|
28
|
-
/** 最大 traces 数量(用于构造 prompt) */
|
|
29
|
-
maxTracesInPrompt?: number;
|
|
30
|
-
|
|
31
|
-
/** 是否自动生成提案 */
|
|
32
|
-
autoGenerateProposal?: boolean;
|
|
22
|
+
/** 最大 traces 数量(用于构造诊断证据) */
|
|
23
|
+
maxTraces?: number;
|
|
33
24
|
}
|
|
34
25
|
|
|
35
26
|
/**
|
|
@@ -51,9 +42,7 @@ export class ConstraintDoctor {
|
|
|
51
42
|
|
|
52
43
|
constructor(config?: ConstraintDoctorConfig) {
|
|
53
44
|
this.config = {
|
|
54
|
-
|
|
55
|
-
maxTracesInPrompt: 20,
|
|
56
|
-
autoGenerateProposal: false,
|
|
45
|
+
maxTraces: 20,
|
|
57
46
|
...config,
|
|
58
47
|
};
|
|
59
48
|
this.traces = [];
|
|
@@ -95,7 +84,7 @@ export class ConstraintDoctor {
|
|
|
95
84
|
rootCause: {
|
|
96
85
|
primary: '',
|
|
97
86
|
secondary: [],
|
|
98
|
-
evidence: traces.slice(0, this.config.
|
|
87
|
+
evidence: traces.slice(0, this.config.maxTraces!),
|
|
99
88
|
},
|
|
100
89
|
impact: {
|
|
101
90
|
severity: 'medium',
|
package/src/monitoring/traces.ts
CHANGED
|
@@ -88,7 +88,7 @@ export class TraceCollector {
|
|
|
88
88
|
*/
|
|
89
89
|
recordPass(
|
|
90
90
|
constraintId: string,
|
|
91
|
-
level: 'iron_law' | 'guideline'
|
|
91
|
+
level: 'iron_law' | 'guideline',
|
|
92
92
|
options?: Partial<ExecutionTrace>
|
|
93
93
|
): void {
|
|
94
94
|
this.record({
|
|
@@ -105,7 +105,7 @@ export class TraceCollector {
|
|
|
105
105
|
*/
|
|
106
106
|
recordFail(
|
|
107
107
|
constraintId: string,
|
|
108
|
-
level: 'iron_law' | 'guideline'
|
|
108
|
+
level: 'iron_law' | 'guideline',
|
|
109
109
|
options?: Partial<ExecutionTrace>
|
|
110
110
|
): void {
|
|
111
111
|
this.record({
|
|
@@ -122,7 +122,7 @@ export class TraceCollector {
|
|
|
122
122
|
*/
|
|
123
123
|
recordBypass(
|
|
124
124
|
constraintId: string,
|
|
125
|
-
level: 'iron_law' | 'guideline'
|
|
125
|
+
level: 'iron_law' | 'guideline',
|
|
126
126
|
bypassReason?: string,
|
|
127
127
|
options?: Partial<ExecutionTrace>
|
|
128
128
|
): void {
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PreToolUse 执法脚本 — command-gate hook 固化版(studio#153)
|
|
3
|
+
*
|
|
4
|
+
* 内容 = studio-agent provider-hooks.buildHookScriptContent 生成脚本(#147)的固化版,
|
|
5
|
+
* 归属归位:谁的东西谁发货——CommandGate 是 harness 的,hook 脚本随 harness 包出厂。
|
|
6
|
+
* 编译产物 = 包内 dist/pretool-use-hook.js,provider hook 配置(codex hooks.json /
|
|
7
|
+
* kimi config.toml)直接指向 require.resolve('@dommaker/harness') 同目录的该文件,
|
|
8
|
+
* 不再由 studio-agent 按 worktree 生成、不再内嵌绝对路径。
|
|
9
|
+
*
|
|
10
|
+
* 语义(与生成版一致):
|
|
11
|
+
* - stdin 收 provider PreToolUse JSON(tool_input.command);
|
|
12
|
+
* - CommandGate.isAllowed 判定 block 级黑名单,命中 → stderr 写原因 + exit 2
|
|
13
|
+
* (codex/kimi 阻断语义:exit 2 + stderr 阻断,其余 exit code fail-open);
|
|
14
|
+
* - fail-open:stdin 非 JSON / 缺字段 / CommandGate 异常一律放行——宁可漏拦
|
|
15
|
+
* 也不全体 Bash 秒断,拦截层只是纵深防御的一道;
|
|
16
|
+
* - warn/audit 级命中同样放行(isAllowed 只看 block 级),与生成版行为一致。
|
|
17
|
+
*/
|
|
18
|
+
import { CommandGate } from './gates/command';
|
|
19
|
+
|
|
20
|
+
/** hook 标识:与 studio-agent 生成版同一 marker(配置幂等检测共用口径) */
|
|
21
|
+
export const HOOK_MARKER = 'harness-command-gate';
|
|
22
|
+
|
|
23
|
+
interface PreToolUseInput {
|
|
24
|
+
tool_input?: { command?: string };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 判定一次 PreToolUse 是否放行。
|
|
29
|
+
* block 级命中 → allowed=false;warn/audit/干净命令/坏输入一律 allowed=true。
|
|
30
|
+
*/
|
|
31
|
+
export function decidePreToolUse(rawStdin: string): { allowed: boolean; command: string } {
|
|
32
|
+
let input: PreToolUseInput = {};
|
|
33
|
+
try {
|
|
34
|
+
input = JSON.parse(rawStdin || '{}') as PreToolUseInput;
|
|
35
|
+
} catch {
|
|
36
|
+
// 非 JSON stdin:放行
|
|
37
|
+
}
|
|
38
|
+
const command = (input.tool_input && input.tool_input.command) || '';
|
|
39
|
+
try {
|
|
40
|
+
const gate = new CommandGate();
|
|
41
|
+
return { allowed: gate.isAllowed(command), command };
|
|
42
|
+
} catch {
|
|
43
|
+
// CommandGate 加载/判定异常:fail-open
|
|
44
|
+
return { allowed: true, command };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 执行一次 hook:返回进程 exit code(2 = 阻断,0 = 放行)。
|
|
50
|
+
* 阻断时原因写 stderr(provider 把 stderr 作为阻断原因回填模型)。
|
|
51
|
+
*/
|
|
52
|
+
export function runPreToolUseHook(rawStdin: string): number {
|
|
53
|
+
const { allowed, command } = decidePreToolUse(rawStdin);
|
|
54
|
+
if (!allowed) {
|
|
55
|
+
console.error(`[${HOOK_MARKER}] blocked: ${command}`);
|
|
56
|
+
return 2;
|
|
57
|
+
}
|
|
58
|
+
return 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* istanbul ignore next -- 进程入口仅做 stdin 收集 + exit,逻辑由 runPreToolUseHook 单测覆盖 */
|
|
62
|
+
if (require.main === module) {
|
|
63
|
+
let raw = '';
|
|
64
|
+
process.stdin.on('data', (c) => { raw += c; });
|
|
65
|
+
process.stdin.on('end', () => {
|
|
66
|
+
process.exit(runPreToolUseHook(raw));
|
|
67
|
+
});
|
|
68
|
+
}
|
package/src/types/trace.ts
CHANGED
|
@@ -29,7 +29,7 @@ export interface ExecutionTrace {
|
|
|
29
29
|
constraintId: string;
|
|
30
30
|
|
|
31
31
|
/** 约束层级 */
|
|
32
|
-
level: 'iron_law' | 'guideline' | 'prompt'
|
|
32
|
+
level: 'iron_law' | 'guideline' | 'prompt';
|
|
33
33
|
|
|
34
34
|
/** 检查时间(Unix timestamp) */
|
|
35
35
|
timestamp: number;
|
|
@@ -77,7 +77,7 @@ export interface TraceSummary {
|
|
|
77
77
|
constraintId: string;
|
|
78
78
|
|
|
79
79
|
/** 约束层级 */
|
|
80
|
-
level: 'iron_law' | 'guideline' | 'prompt'
|
|
80
|
+
level: 'iron_law' | 'guideline' | 'prompt';
|
|
81
81
|
|
|
82
82
|
/** 统计时间范围 */
|
|
83
83
|
timeRange: {
|
|
@@ -161,7 +161,7 @@ export interface TraceAnomaly {
|
|
|
161
161
|
constraintId: string;
|
|
162
162
|
|
|
163
163
|
/** 约束层级 */
|
|
164
|
-
level: 'iron_law' | 'guideline' | 'prompt'
|
|
164
|
+
level: 'iron_law' | 'guideline' | 'prompt';
|
|
165
165
|
|
|
166
166
|
/** 异常描述 */
|
|
167
167
|
message: string;
|
|
@@ -188,7 +188,7 @@ export interface TraceFilter {
|
|
|
188
188
|
constraintId?: string;
|
|
189
189
|
|
|
190
190
|
/** 约束层级(可选) */
|
|
191
|
-
level?: 'iron_law' | 'guideline' | 'prompt'
|
|
191
|
+
level?: 'iron_law' | 'guideline' | 'prompt';
|
|
192
192
|
|
|
193
193
|
/** 结果类型(可选) */
|
|
194
194
|
result?: 'pass' | 'fail' | 'bypassed' | 'skip';
|