@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.
Files changed (89) hide show
  1. package/CHANGELOG.md +19 -13
  2. package/dist/cli/commands/status.d.ts.map +1 -1
  3. package/dist/cli/commands/status.js +0 -9
  4. package/dist/cli/commands/status.js.map +1 -1
  5. package/dist/cli/commands/sync-docs/agents-syncer.d.ts.map +1 -1
  6. package/dist/cli/commands/sync-docs/agents-syncer.js +25 -4
  7. package/dist/cli/commands/sync-docs/agents-syncer.js.map +1 -1
  8. package/dist/completion-checkers/classify.d.ts +23 -0
  9. package/dist/completion-checkers/classify.d.ts.map +1 -0
  10. package/dist/completion-checkers/classify.js +37 -0
  11. package/dist/completion-checkers/classify.js.map +1 -0
  12. package/dist/completion-checkers/contract-presence.d.ts +12 -0
  13. package/dist/completion-checkers/contract-presence.d.ts.map +1 -0
  14. package/dist/completion-checkers/contract-presence.js +36 -0
  15. package/dist/completion-checkers/contract-presence.js.map +1 -0
  16. package/dist/completion-checkers/glob-match.d.ts +13 -0
  17. package/dist/completion-checkers/glob-match.d.ts.map +1 -0
  18. package/dist/completion-checkers/glob-match.js +58 -0
  19. package/dist/completion-checkers/glob-match.js.map +1 -0
  20. package/dist/completion-checkers/index.d.ts +19 -0
  21. package/dist/completion-checkers/index.d.ts.map +1 -0
  22. package/dist/completion-checkers/index.js +47 -0
  23. package/dist/completion-checkers/index.js.map +1 -0
  24. package/dist/completion-checkers/phase-format.d.ts +15 -0
  25. package/dist/completion-checkers/phase-format.d.ts.map +1 -0
  26. package/dist/completion-checkers/phase-format.js +42 -0
  27. package/dist/completion-checkers/phase-format.js.map +1 -0
  28. package/dist/completion-checkers/tdd-chain.d.ts +16 -0
  29. package/dist/completion-checkers/tdd-chain.d.ts.map +1 -0
  30. package/dist/completion-checkers/tdd-chain.js +57 -0
  31. package/dist/completion-checkers/tdd-chain.js.map +1 -0
  32. package/dist/completion-checkers/types.d.ts +77 -0
  33. package/dist/completion-checkers/types.d.ts.map +1 -0
  34. package/dist/completion-checkers/types.js +10 -0
  35. package/dist/completion-checkers/types.js.map +1 -0
  36. package/dist/core/constraints/checker.d.ts +3 -1
  37. package/dist/core/constraints/checker.d.ts.map +1 -1
  38. package/dist/core/constraints/checker.js +4 -2
  39. package/dist/core/constraints/checker.js.map +1 -1
  40. package/dist/core/constraints/interceptor.d.ts +3 -2
  41. package/dist/core/constraints/interceptor.d.ts.map +1 -1
  42. package/dist/core/constraints/interceptor.js +4 -4
  43. package/dist/core/constraints/interceptor.js.map +1 -1
  44. package/dist/index.d.ts +10 -2
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +16 -5
  47. package/dist/index.js.map +1 -1
  48. package/dist/monitoring/constraint-doctor.d.ts +2 -8
  49. package/dist/monitoring/constraint-doctor.d.ts.map +1 -1
  50. package/dist/monitoring/constraint-doctor.js +2 -4
  51. package/dist/monitoring/constraint-doctor.js.map +1 -1
  52. package/dist/monitoring/traces.d.ts +3 -3
  53. package/dist/monitoring/traces.d.ts.map +1 -1
  54. package/dist/monitoring/traces.js.map +1 -1
  55. package/dist/pretool-use-hook.d.ts +16 -0
  56. package/dist/pretool-use-hook.d.ts.map +1 -0
  57. package/dist/pretool-use-hook.js +68 -0
  58. package/dist/pretool-use-hook.js.map +1 -0
  59. package/dist/types/trace.d.ts +4 -4
  60. package/dist/types/trace.d.ts.map +1 -1
  61. package/package.json +1 -1
  62. package/src/CONTEXT.md +2 -2
  63. package/src/__tests__/checker-extra.test.ts +47 -0
  64. package/src/__tests__/pretool-use-hook.test.ts +82 -0
  65. package/src/cli/commands/__tests__/status-extra.test.ts +0 -22
  66. package/src/cli/commands/__tests__/status.test.ts +0 -15
  67. package/src/cli/commands/__tests__/sync-docs-agents.test.ts +55 -0
  68. package/src/cli/commands/status.ts +0 -10
  69. package/src/cli/commands/sync-docs/agents-syncer.ts +26 -6
  70. package/src/completion-checkers/CONTEXT.md +25 -0
  71. package/src/completion-checkers/__tests__/contract-presence.test.ts +44 -0
  72. package/src/completion-checkers/__tests__/phase-format.test.ts +60 -0
  73. package/src/completion-checkers/__tests__/tdd-chain.test.ts +99 -0
  74. package/src/completion-checkers/classify.ts +47 -0
  75. package/src/completion-checkers/contract-presence.ts +40 -0
  76. package/src/completion-checkers/glob-match.ts +54 -0
  77. package/src/completion-checkers/index.ts +19 -0
  78. package/src/completion-checkers/phase-format.ts +48 -0
  79. package/src/completion-checkers/tdd-chain.ts +63 -0
  80. package/src/completion-checkers/types.ts +84 -0
  81. package/src/core/CONTEXT.md +1 -1
  82. package/src/core/constraints/__tests__/interceptor.test.ts +14 -0
  83. package/src/core/constraints/checker.ts +5 -2
  84. package/src/core/constraints/interceptor.ts +7 -4
  85. package/src/index.ts +24 -6
  86. package/src/monitoring/constraint-doctor.ts +4 -15
  87. package/src/monitoring/traces.ts +3 -3
  88. package/src/pretool-use-hook.ts +68 -0
  89. 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?: { onTrace?: (result: import('./types/constraint').ConstraintResult) => void }
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
- /** 是否启用 Agent 诊断(默认 false,需要显式启用) */
23
- enabled?: boolean;
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
- enabled: false,
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.maxTracesInPrompt!),
87
+ evidence: traces.slice(0, this.config.maxTraces!),
99
88
  },
100
89
  impact: {
101
90
  severity: 'medium',
@@ -88,7 +88,7 @@ export class TraceCollector {
88
88
  */
89
89
  recordPass(
90
90
  constraintId: string,
91
- level: 'iron_law' | 'guideline' | 'tip',
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' | 'tip',
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' | 'tip',
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
+ }
@@ -29,7 +29,7 @@ export interface ExecutionTrace {
29
29
  constraintId: string;
30
30
 
31
31
  /** 约束层级 */
32
- level: 'iron_law' | 'guideline' | 'prompt' | 'tip';
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' | 'tip';
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' | 'tip';
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' | 'tip';
191
+ level?: 'iron_law' | 'guideline' | 'prompt';
192
192
 
193
193
  /** 结果类型(可选) */
194
194
  result?: 'pass' | 'fail' | 'bypassed' | 'skip';