koma-miko 0.1.0-alpha.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/README.md +271 -0
- package/README.zh-CN.md +233 -0
- package/dist/claude-code.d.ts +42 -0
- package/dist/claude-code.d.ts.map +1 -0
- package/dist/claude-code.js +135 -0
- package/dist/claude-code.js.map +1 -0
- package/dist/claude-hook-cli.d.ts +3 -0
- package/dist/claude-hook-cli.d.ts.map +1 -0
- package/dist/claude-hook-cli.js +175 -0
- package/dist/claude-hook-cli.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +29 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +15 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +50 -0
- package/dist/config.js.map +1 -0
- package/dist/doctor.d.ts +16 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +128 -0
- package/dist/doctor.js.map +1 -0
- package/dist/index.d.ts +193 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +629 -0
- package/dist/index.js.map +1 -0
- package/evals/audit-demo.mjs +208 -0
- package/evals/claude-hook.mjs +85 -0
- package/evals/claude-live.mjs +191 -0
- package/evals/claude-scale-live.mjs +312 -0
- package/evals/replay.mjs +64 -0
- package/evals/scale.mjs +154 -0
- package/examples/claude-code/contracts.json +20 -0
- package/examples/claude-code/miko.json +24 -0
- package/examples/claude-code/settings.json +62 -0
- package/package.json +61 -0
- package/schema/miko.schema.json +172 -0
- package/src/claude-code.test.ts +133 -0
- package/src/claude-code.ts +196 -0
- package/src/claude-hook-cli.ts +223 -0
- package/src/cli.ts +30 -0
- package/src/config.test.ts +46 -0
- package/src/config.ts +61 -0
- package/src/doctor.test.ts +83 -0
- package/src/doctor.ts +158 -0
- package/src/index.test.ts +404 -0
- package/src/index.ts +905 -0
package/README.md
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# koma-miko (alpha)
|
|
2
|
+
|
|
3
|
+
Deterministic skill and action contract verification for AI agent workflows.
|
|
4
|
+
|
|
5
|
+
Think of each contract as an **Agent Spec**: a developer-owned executable test
|
|
6
|
+
for how an agent prepares, acts, and completes work—not an enterprise policy
|
|
7
|
+
console.
|
|
8
|
+
|
|
9
|
+
Miko checks observable evidence at three points:
|
|
10
|
+
|
|
11
|
+
1. **Prepare** — were the required skills and references loaded?
|
|
12
|
+
2. **Pre-action** — is the proposed tool, risk, and path scope allowed?
|
|
13
|
+
3. **Complete** — did the required tests, reviews, or artifacts actually happen?
|
|
14
|
+
|
|
15
|
+
This source alpha is not published to npm yet. Its API may change before the
|
|
16
|
+
first public release.
|
|
17
|
+
|
|
18
|
+
## Why
|
|
19
|
+
|
|
20
|
+
Agent instructions are useful guidance, but guidance is not enforcement. A skill
|
|
21
|
+
may fail to activate, lose influence in a long session, or be followed initially
|
|
22
|
+
while its completion checklist is skipped later. **Miko does not inspect hidden
|
|
23
|
+
model state, measure whether instructions still influence a near-million-token
|
|
24
|
+
context, or prove that an agent chose correctly among 100 skills.** A host
|
|
25
|
+
records observable events, and Miko checks those events against explicit
|
|
26
|
+
contracts.
|
|
27
|
+
|
|
28
|
+
## Example
|
|
29
|
+
|
|
30
|
+
The developer-facing project file is `miko.json`:
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"$schema": "./node_modules/koma-miko/schema/miko.schema.json",
|
|
35
|
+
"version": 1,
|
|
36
|
+
"specs": [
|
|
37
|
+
{
|
|
38
|
+
"id": "ui-change-v1",
|
|
39
|
+
"appliesWhen": {
|
|
40
|
+
"action": {
|
|
41
|
+
"tools": ["Edit", "Write"],
|
|
42
|
+
"pathPrefixes": ["src/ui"]
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"requires": {
|
|
46
|
+
"skills": [
|
|
47
|
+
{ "name": "product-design", "reloadAfterCompaction": true }
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
"mode": "enforce"
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The TypeScript API consumes the same spec objects directly:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { createMiko } from 'koma-miko';
|
|
60
|
+
|
|
61
|
+
const miko = createMiko({
|
|
62
|
+
contracts: [{
|
|
63
|
+
id: 'ui-change-v1',
|
|
64
|
+
// The host can activate this contract from the actual action, even when
|
|
65
|
+
// the agent forgot to label the task as UI work.
|
|
66
|
+
appliesWhen: {
|
|
67
|
+
action: {
|
|
68
|
+
tools: ['write_file'],
|
|
69
|
+
pathPrefixes: ['src/ui'],
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
requires: {
|
|
73
|
+
skills: [{ name: 'product-design', reloadAfterCompaction: true }],
|
|
74
|
+
references: ['docs/design-system.md'],
|
|
75
|
+
},
|
|
76
|
+
actions: {
|
|
77
|
+
allow: ['read_file', 'write_file', 'run_check'],
|
|
78
|
+
deny: ['delete_file'],
|
|
79
|
+
maxRisk: 'medium',
|
|
80
|
+
scope: {
|
|
81
|
+
tools: ['write_file'],
|
|
82
|
+
allowedPathPrefixes: ['src/ui'],
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
completion: {
|
|
86
|
+
evidence: [
|
|
87
|
+
{ type: 'check_passed', name: 'rendered-ui-review' },
|
|
88
|
+
{ type: 'check_passed', name: 'targeted-tests' },
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
mode: 'review',
|
|
92
|
+
}],
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
miko.startTask({
|
|
96
|
+
sessionId: 'session-7',
|
|
97
|
+
taskId: 'new-settings-page',
|
|
98
|
+
tags: ['ui'],
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Missing preparation is REVIEW in review mode (DENY in enforce mode).
|
|
102
|
+
miko.verifyPreparation('new-settings-page');
|
|
103
|
+
|
|
104
|
+
miko.record({
|
|
105
|
+
taskId: 'new-settings-page',
|
|
106
|
+
type: 'skill_loaded',
|
|
107
|
+
name: 'product-design',
|
|
108
|
+
source: 'observed',
|
|
109
|
+
});
|
|
110
|
+
miko.record({
|
|
111
|
+
taskId: 'new-settings-page',
|
|
112
|
+
type: 'reference_read',
|
|
113
|
+
path: 'docs/design-system.md',
|
|
114
|
+
source: 'observed',
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
miko.verifyAction({
|
|
118
|
+
taskId: 'new-settings-page',
|
|
119
|
+
tool: 'write_file',
|
|
120
|
+
risk: 'medium',
|
|
121
|
+
arguments: { path: 'src/ui/Settings.tsx' },
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// REVIEW until both completion checks have been recorded.
|
|
125
|
+
miko.verifyCompletion('new-settings-page');
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Every result is machine-readable and explainable:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
{
|
|
132
|
+
decision: 'REVIEW',
|
|
133
|
+
checkpoint: 'COMPLETE',
|
|
134
|
+
reasonCode: 'COMPLETION_EVIDENCE_MISSING',
|
|
135
|
+
reason: 'Required completion evidence is missing.',
|
|
136
|
+
contractIds: ['ui-change-v1'],
|
|
137
|
+
missing: ['ui-change-v1:check_passed:rendered-ui-review']
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The plain-text renderer is intentionally developer-facing and bounded even when
|
|
142
|
+
many Agent Specs overlap:
|
|
143
|
+
|
|
144
|
+
```text
|
|
145
|
+
🔴 Miko DENY · PREPARE — PREPARATION_EVIDENCE_MISSING
|
|
146
|
+
Missing evidence:
|
|
147
|
+
- ui-change-v1:skill_loaded:product-design
|
|
148
|
+
Next: load the required skill/reference, then retry the blocked action.
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Claude Code hook mapping
|
|
152
|
+
|
|
153
|
+
`toClaudePreToolUseDecision(result)` maps Miko decisions to Claude Code's
|
|
154
|
+
structured `PreToolUse` output:
|
|
155
|
+
|
|
156
|
+
- `ALLOW` → `allow`
|
|
157
|
+
- `DENY` → `deny`
|
|
158
|
+
- `REVIEW` → `ask`
|
|
159
|
+
|
|
160
|
+
Non-ALLOW results also include a concise `systemMessage` for the user and
|
|
161
|
+
`additionalContext` for the agent. Miko Verifier has no graphical UI: each host
|
|
162
|
+
renders the same structured decision using its native text/approval surface.
|
|
163
|
+
|
|
164
|
+
The included `koma-miko-claude-hook` executable provides a minimal durable
|
|
165
|
+
Claude Code adapter. It observes automatic `Skill` calls, direct `/skill-name`
|
|
166
|
+
expansions, `Read`, `Edit`, and `Write` events; persists a privacy-minimized
|
|
167
|
+
JSONL ledger including non-ALLOW decisions; and can activate a contract from an
|
|
168
|
+
observed tool/path instead of model-supplied tags. See
|
|
169
|
+
[`examples/claude-code`](./examples/claude-code).
|
|
170
|
+
|
|
171
|
+
Skills declared with `reloadAfterCompaction: true` become missing again after a
|
|
172
|
+
Claude `PostCompact` event. The adapter keeps JSONL as the append-only audit
|
|
173
|
+
record and uses a compact materialized snapshot so each Hook only replays events
|
|
174
|
+
written after the latest snapshot. The ledger is auditable, not tamper-proof.
|
|
175
|
+
|
|
176
|
+
To try the source alpha, build/install the package, copy the example `miko.json`
|
|
177
|
+
to the project root, and merge the example hooks into
|
|
178
|
+
`.claude/settings.json`. The example is intentionally not enabled automatically:
|
|
179
|
+
its enforced `frontend-design` skill must actually exist in the target project.
|
|
180
|
+
Miko writes session metadata under `.miko/state/`, which should stay ignored.
|
|
181
|
+
Legacy `.miko/contracts.json` arrays remain readable but are no longer the
|
|
182
|
+
preferred developer interface.
|
|
183
|
+
|
|
184
|
+
Run an entirely offline preflight before spending model credits:
|
|
185
|
+
|
|
186
|
+
```sh
|
|
187
|
+
npx koma-miko doctor
|
|
188
|
+
npx koma-miko doctor --strict --json
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Doctor validates Agent Specs and reports project Skill discovery, required
|
|
192
|
+
Claude Hook coverage, and whether `.miko/state/` is ignored. It never calls a
|
|
193
|
+
model or reads an API key.
|
|
194
|
+
|
|
195
|
+
Claude Code's local CLI, Desktop Code tab, and VS Code/Cursor extension share
|
|
196
|
+
settings, hooks, and skills. Cloud/remote sessions have different configuration
|
|
197
|
+
sources, and managed policies can disable project hooks, so adapters must expose
|
|
198
|
+
their detected capabilities rather than promise identical behavior everywhere.
|
|
199
|
+
See the official [platform overview](https://code.claude.com/docs/en/platforms),
|
|
200
|
+
[Desktop shared configuration](https://code.claude.com/docs/en/desktop), and
|
|
201
|
+
[VS Code settings](https://code.claude.com/docs/en/ide-integrations).
|
|
202
|
+
|
|
203
|
+
## Automated replay
|
|
204
|
+
|
|
205
|
+
`npm run eval:replay -w koma-miko` runs ten simplified skill contracts without
|
|
206
|
+
an API key. For every skill it verifies three cases: missing evidence, an agent
|
|
207
|
+
claim (`asserted`), and a host-observed load (`observed`). Only observed or
|
|
208
|
+
external evidence can satisfy a contract. The first UI case is the narrow
|
|
209
|
+
enforcement demo; the other nine remain review-only.
|
|
210
|
+
`npm run eval:claude-hook -w koma-miko` additionally spawns three independent
|
|
211
|
+
hook processes and verifies an audited `DENY → observed Skill → ALLOW` plus
|
|
212
|
+
ledger privacy.
|
|
213
|
+
`npm run eval:scale -w koma-miko` uses no API key. It benchmarks 100/1,000 Agent
|
|
214
|
+
Specs, 10,000 indexed evidence events, 100 overlapping specs, and snapshot
|
|
215
|
+
restore while checking that terminal output remains bounded.
|
|
216
|
+
`npm run eval:audit-demo -w koma-miko` regenerates the 13-event fixture behind
|
|
217
|
+
the public guided CLI simulation from real Verifier results.
|
|
218
|
+
`eval:audit-demo:check` detects a stale fixture without changing it; the replay
|
|
219
|
+
contains no prompt, code, or model response and needs no backend. The friendly
|
|
220
|
+
terminal story is presentation only; expandable raw events preserve reason
|
|
221
|
+
codes, provenance, and contract IDs for inspection.
|
|
222
|
+
|
|
223
|
+
With `ANTHROPIC_API_KEY` set in the parent process,
|
|
224
|
+
`npm run eval:claude-live -w koma-miko` runs one disposable, real Claude Code
|
|
225
|
+
fixture. It exposes only `Read`, `Edit`, and `Skill`, defaults to Haiku, enforces
|
|
226
|
+
a `$0.10` per-run cap, and checks this sequence:
|
|
227
|
+
|
|
228
|
+
```text
|
|
229
|
+
Read → Miko DENY → Claude loads frontend-design → Miko allows → Edit
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
The fixture is created inside the package workspace so Claude Code treats it as
|
|
233
|
+
project content, then deleted. The runner never reads an env file. Override the
|
|
234
|
+
defaults with `MIKO_LIVE_MODEL` and `MIKO_LIVE_MAX_BUDGET_USD` (capped by the
|
|
235
|
+
runner at `$1`).
|
|
236
|
+
|
|
237
|
+
For the 100-Skill long-context fixture, start without spending credits:
|
|
238
|
+
|
|
239
|
+
```sh
|
|
240
|
+
npm run eval:claude-scale-dry -w koma-miko
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Then run one approximately 20k-token Haiku case with a `$0.12` hard cap:
|
|
244
|
+
|
|
245
|
+
```sh
|
|
246
|
+
MIKO_LIVE_CONTEXT_TOKENS=20000 \
|
|
247
|
+
MIKO_LIVE_MAX_BUDGET_USD=0.12 \
|
|
248
|
+
MIKO_LIVE_CAMPAIGN_BUDGET_USD=0.12 \
|
|
249
|
+
npm run eval:claude-scale-live -w koma-miko
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
The runner accepts at most three comma-separated context sizes from 1,000 to
|
|
253
|
+
190,000 tokens. It creates exactly 100 project Skills, exposes only
|
|
254
|
+
`Read`/`Edit`/`Skill`, runs in a disposable directory, records cache and cost
|
|
255
|
+
metrics, and never reads an env file. See the
|
|
256
|
+
[alpha evaluation record](../../docs/evals/miko-claude-haiku-alpha.md).
|
|
257
|
+
|
|
258
|
+
## Alpha boundaries
|
|
259
|
+
|
|
260
|
+
- **No LLM call or semantic task classifier**
|
|
261
|
+
- **No planner, router, or agent runtime**
|
|
262
|
+
- **No context-window/token monitoring**
|
|
263
|
+
- **One Haiku/100-Skill fixture has passed at approximately 20k tokens; this is not evidence for 100k, 190k, or near-million-token behavior**
|
|
264
|
+
- **No hosted telemetry service** (the Claude adapter uses a local JSONL ledger)
|
|
265
|
+
- **No automatic rewriting of tool calls**
|
|
266
|
+
- **No claim that loading a skill proves the model understood, retained, or followed it**
|
|
267
|
+
|
|
268
|
+
See the [design and discovery notes](../../docs/design/miko.md), including the
|
|
269
|
+
first-hand failure case, public reports used as test discovery data, and the
|
|
270
|
+
post-alpha questions. Product positioning and executable follow-up work live in
|
|
271
|
+
the [developer roadmap](../../docs/design/miko-roadmap.md).
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# koma-miko(alpha)
|
|
2
|
+
|
|
3
|
+
面向 AI agent 工作流的确定性 skill / action 契约验证器。
|
|
4
|
+
|
|
5
|
+
可以把每份 Contract 理解成开发者维护的 **Agent Spec(智能体测试用例)**:
|
|
6
|
+
它像测试代码一样约束 agent 如何准备、行动和完成任务,而不是企业治理控制台。
|
|
7
|
+
|
|
8
|
+
Miko 在三个可观察节点检查证据:
|
|
9
|
+
|
|
10
|
+
1. **准备阶段** — 必需的 skill 和参考文档是否真的被加载;
|
|
11
|
+
2. **动作之前** — 工具、风险等级与路径范围是否符合契约;
|
|
12
|
+
3. **完成之前** — 测试、UI 渲染检查或其他交付义务是否真的执行。
|
|
13
|
+
|
|
14
|
+
当前是源码 alpha,尚未发布到 npm;首次公开发布前 API 仍可能调整。
|
|
15
|
+
|
|
16
|
+
## 为什么
|
|
17
|
+
|
|
18
|
+
Agent 指令是有用的引导,但不等于强制约束。Skill 可能没有被自动触发,
|
|
19
|
+
也可能在长会话中逐渐失去影响,或者 agent 前半段遵守规则、结束时却跳过
|
|
20
|
+
测试清单。**Miko 不检查隐藏的模型状态,无法测量指令在接近百万 token 的
|
|
21
|
+
上下文中是否仍有影响,也无法证明 agent 能从 100 个 Skill 中选对一个。**
|
|
22
|
+
宿主记录可观察事件,Miko 根据明确契约验证这些证据。
|
|
23
|
+
|
|
24
|
+
## 最小示例
|
|
25
|
+
|
|
26
|
+
开发者直接维护项目根目录的 `miko.json`:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"$schema": "./node_modules/koma-miko/schema/miko.schema.json",
|
|
31
|
+
"version": 1,
|
|
32
|
+
"specs": [
|
|
33
|
+
{
|
|
34
|
+
"id": "ui-change-v1",
|
|
35
|
+
"appliesWhen": {
|
|
36
|
+
"action": {
|
|
37
|
+
"tools": ["Edit", "Write"],
|
|
38
|
+
"pathPrefixes": ["src/ui"]
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"requires": {
|
|
42
|
+
"skills": [
|
|
43
|
+
{ "name": "product-design", "reloadAfterCompaction": true }
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
"mode": "enforce"
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
TypeScript API 可以直接使用同一批 Spec 对象:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import { createMiko } from 'koma-miko';
|
|
56
|
+
|
|
57
|
+
const miko = createMiko({
|
|
58
|
+
contracts: [{
|
|
59
|
+
id: 'ui-change-v1',
|
|
60
|
+
appliesWhen: {
|
|
61
|
+
action: {
|
|
62
|
+
tools: ['write_file'],
|
|
63
|
+
pathPrefixes: ['src/ui'],
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
requires: {
|
|
67
|
+
skills: [{ name: 'product-design', reloadAfterCompaction: true }],
|
|
68
|
+
references: ['docs/design-system.md'],
|
|
69
|
+
},
|
|
70
|
+
actions: {
|
|
71
|
+
allow: ['read_file', 'write_file', 'run_check'],
|
|
72
|
+
deny: ['delete_file'],
|
|
73
|
+
maxRisk: 'medium',
|
|
74
|
+
scope: {
|
|
75
|
+
tools: ['write_file'],
|
|
76
|
+
allowedPathPrefixes: ['src/ui'],
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
completion: {
|
|
80
|
+
evidence: [
|
|
81
|
+
{ type: 'check_passed', name: 'rendered-ui-review' },
|
|
82
|
+
{ type: 'check_passed', name: 'targeted-tests' },
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
mode: 'review',
|
|
86
|
+
}],
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
miko.startTask({
|
|
90
|
+
sessionId: 'session-7',
|
|
91
|
+
taskId: 'new-settings-page',
|
|
92
|
+
tags: ['ui'],
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// 未记录 product-design 与 design-system 时返回 REVIEW。
|
|
96
|
+
miko.verifyPreparation('new-settings-page');
|
|
97
|
+
|
|
98
|
+
miko.record({
|
|
99
|
+
taskId: 'new-settings-page',
|
|
100
|
+
type: 'skill_loaded',
|
|
101
|
+
name: 'product-design',
|
|
102
|
+
source: 'observed',
|
|
103
|
+
});
|
|
104
|
+
miko.record({
|
|
105
|
+
taskId: 'new-settings-page',
|
|
106
|
+
type: 'reference_read',
|
|
107
|
+
path: 'docs/design-system.md',
|
|
108
|
+
source: 'observed',
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
miko.verifyAction({
|
|
112
|
+
taskId: 'new-settings-page',
|
|
113
|
+
tool: 'write_file',
|
|
114
|
+
risk: 'medium',
|
|
115
|
+
arguments: { path: 'src/ui/Settings.tsx' },
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// 直到完成证据齐全前都返回 REVIEW。
|
|
119
|
+
miko.verifyCompletion('new-settings-page');
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`mode: 'review'` 会把缺失证据映射为 `REVIEW`;`mode: 'enforce'` 则映射为
|
|
123
|
+
`DENY`。明确越权的工具、风险或路径在两种模式下都会返回 `DENY`。
|
|
124
|
+
|
|
125
|
+
终端文字采用有阶段含义、且长度受限的开发者红绿灯;即使许多 Agent Spec
|
|
126
|
+
重叠,也只展开最重要的前三项:
|
|
127
|
+
|
|
128
|
+
```text
|
|
129
|
+
🔴 Miko DENY · PREPARE — PREPARATION_EVIDENCE_MISSING
|
|
130
|
+
Missing evidence:
|
|
131
|
+
- ui-change-v1:skill_loaded:product-design
|
|
132
|
+
Next: load the required skill/reference, then retry the blocked action.
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`toClaudePreToolUseDecision(result)` 可将 `ALLOW / DENY / REVIEW` 映射到
|
|
136
|
+
Claude Code `PreToolUse` 的 `allow / deny / ask`。非 ALLOW 结果还会同时
|
|
137
|
+
向用户显示精简文字,并把恢复提示交给 agent。Miko Verifier 本身不带图形 UI;
|
|
138
|
+
CLI、桌面端或 IDE 使用各自原生的文字与审批界面渲染同一结构化结果。
|
|
139
|
+
|
|
140
|
+
包内的 `koma-miko-claude-hook` 提供最小可用的持久化 Claude Code 适配器:
|
|
141
|
+
它观察自动 `Skill` 调用、用户直接输入的 `/skill-name`、`Read`、`Edit` 与
|
|
142
|
+
`Write` 事件;使用仅含必要元数据、并记录非 ALLOW 决策的本地 JSONL 账本;
|
|
143
|
+
并可根据真实工具与路径激活合约,不再只相信模型提供的任务标签。配置示例见
|
|
144
|
+
[`examples/claude-code`](./examples/claude-code)。
|
|
145
|
+
|
|
146
|
+
设置 `reloadAfterCompaction: true` 的 Skill 会在 Claude `PostCompact` 后重新
|
|
147
|
+
变为缺失。adapter 保留 append-only JSONL 作为审计记录,同时使用紧凑 snapshot,
|
|
148
|
+
每次 Hook 只需回放 snapshot 之后的尾部事件。该账本便于审计,但并非防篡改账本。
|
|
149
|
+
|
|
150
|
+
试用源码 alpha 时,先构建/安装该包,把示例 `miko.json` 复制到项目根目录,
|
|
151
|
+
再把示例 Hook 合并进 `.claude/settings.json`。
|
|
152
|
+
示例不会自动启用,因为目标项目中必须真实存在被强制要求的
|
|
153
|
+
`frontend-design` Skill。会话元数据写入 `.miko/state/`,该目录应保持忽略。
|
|
154
|
+
旧 `.miko/contracts.json` 数组仍可读取,但不再是推荐的开发者入口。
|
|
155
|
+
|
|
156
|
+
花费模型额度前,先运行完全离线的预检:
|
|
157
|
+
|
|
158
|
+
```sh
|
|
159
|
+
npx koma-miko doctor
|
|
160
|
+
npx koma-miko doctor --strict --json
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Doctor 会验证 Agent Spec,并报告项目 Skill、Claude Hook 覆盖范围以及
|
|
164
|
+
`.miko/state/` 是否已忽略;它不会调用模型,也不会读取 API key。
|
|
165
|
+
|
|
166
|
+
Claude Code 本地 CLI、Desktop Code tab 与 VS Code/Cursor 扩展共享 settings、
|
|
167
|
+
Hooks 和 Skills;云端/远程会话的配置来源不同,企业策略也可能禁用项目 Hook,
|
|
168
|
+
因此适配器应报告实际能力,而不是承诺所有表面完全一致。参见官方
|
|
169
|
+
[平台概览](https://code.claude.com/docs/en/platforms)、
|
|
170
|
+
[Desktop 共享配置](https://code.claude.com/docs/en/desktop) 与
|
|
171
|
+
[VS Code 配置](https://code.claude.com/docs/en/ide-integrations)。
|
|
172
|
+
|
|
173
|
+
## 自动化回放
|
|
174
|
+
|
|
175
|
+
`npm run eval:replay -w koma-miko` 无需 API key,即可运行十个极简 Skill
|
|
176
|
+
合约。每个 Skill 都验证三种情况:没有证据、agent 自述(`asserted`)以及
|
|
177
|
+
宿主观察到的加载(`observed`)。只有 observed 或 external 证据能满足合约。
|
|
178
|
+
第一个 UI 案例是窄范围强制演示,其余九个保持 review-only。
|
|
179
|
+
`npm run eval:claude-hook -w koma-miko` 还会启动三个彼此独立的 Hook 进程,
|
|
180
|
+
验证带审计记录的 `DENY → 观察到 Skill → ALLOW`,并确认账本没有保存代码内容。
|
|
181
|
+
`npm run eval:scale -w koma-miko` 不需要 API key;它会测试 100/1,000 份
|
|
182
|
+
Agent Spec、10,000 条索引证据、100 份重叠 Spec、snapshot 恢复和终端输出上限。
|
|
183
|
+
`npm run eval:audit-demo -w koma-miko` 会用真实 Verifier 结果重新生成公开 CLI
|
|
184
|
+
引导演示背后的 13 个事件;`eval:audit-demo:check` 只检查 fixture 是否过期。
|
|
185
|
+
回放不包含 prompt、代码或模型回复,也不需要后端。终端故事只是展示层;可展开
|
|
186
|
+
的原始事件仍保留 reason code、provenance 与 contract ID,供技术读者检查。
|
|
187
|
+
|
|
188
|
+
父进程设置好 `ANTHROPIC_API_KEY` 后,
|
|
189
|
+
`npm run eval:claude-live -w koma-miko` 会运行一个可自动删除的真实 Claude Code
|
|
190
|
+
fixture。它只暴露 `Read`、`Edit` 与 `Skill`,默认使用 Haiku,单次硬上限为
|
|
191
|
+
`$0.10`,并验证:
|
|
192
|
+
|
|
193
|
+
```text
|
|
194
|
+
Read → Miko DENY → Claude 加载 frontend-design → Miko 放行 → Edit
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
fixture 建在包工作区内,使 Claude Code 把它视为项目内容,结束后删除。runner
|
|
198
|
+
不会自行读取任何 env 文件。可用 `MIKO_LIVE_MODEL` 与
|
|
199
|
+
`MIKO_LIVE_MAX_BUDGET_USD` 覆盖默认值(runner 最高只接受 `$1`)。
|
|
200
|
+
|
|
201
|
+
100 Skills 长上下文 fixture 应先离线检查,不消耗额度:
|
|
202
|
+
|
|
203
|
+
```sh
|
|
204
|
+
npm run eval:claude-scale-dry -w koma-miko
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
然后再运行约 20k-token、硬上限 `$0.12` 的单次 Haiku 测试:
|
|
208
|
+
|
|
209
|
+
```sh
|
|
210
|
+
MIKO_LIVE_CONTEXT_TOKENS=20000 \
|
|
211
|
+
MIKO_LIVE_MAX_BUDGET_USD=0.12 \
|
|
212
|
+
MIKO_LIVE_CAMPAIGN_BUDGET_USD=0.12 \
|
|
213
|
+
npm run eval:claude-scale-live -w koma-miko
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
runner 最多接受三个以逗号分隔、范围为 1,000–190,000 的上下文档位;它会生成
|
|
217
|
+
恰好 100 个项目 Skills,仅暴露 `Read`/`Edit`/`Skill`,在一次性目录运行并记录
|
|
218
|
+
cache、turn 与成本。runner 不会读取 env 文件。结果见
|
|
219
|
+
[alpha 评估记录](../../docs/evals/miko-claude-haiku-alpha.md)。
|
|
220
|
+
|
|
221
|
+
## Alpha 边界
|
|
222
|
+
|
|
223
|
+
- **不调用 LLM,不做语义任务分类;**
|
|
224
|
+
- **不是 planner、router 或 agent runtime;**
|
|
225
|
+
- **不监控 context window 或 token;**
|
|
226
|
+
- **100-Skills fixture 仅在约 20k tokens 的一次 Haiku 测试中通过;这不能代表 100k、190k 或近百万 token 的行为;**
|
|
227
|
+
- **暂无托管遥测和云端策略服务;** Claude 适配器仅使用本地 JSONL 账本;
|
|
228
|
+
- **不自动改写工具调用;**
|
|
229
|
+
- **不声称“加载过 Skill”就等于模型理解、持续记住或遵守了 Skill。**
|
|
230
|
+
|
|
231
|
+
研究案例、契约模型和 alpha 后问题见
|
|
232
|
+
[设计文档](../../docs/design/miko.md);产品定位与可执行 TODO 见
|
|
233
|
+
[开发者路线图](../../docs/design/miko-roadmap.md)。
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { AdvanceContextResult, EvidenceEvent, Miko, RiskLevel, VerificationResult } from './index.js';
|
|
2
|
+
interface ClaudeHookBase {
|
|
3
|
+
session_id: string;
|
|
4
|
+
cwd: string;
|
|
5
|
+
hook_event_name: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ClaudePreToolUseInput extends ClaudeHookBase {
|
|
8
|
+
hook_event_name: 'PreToolUse';
|
|
9
|
+
tool_name: string;
|
|
10
|
+
tool_input: Record<string, unknown>;
|
|
11
|
+
}
|
|
12
|
+
export interface ClaudePostToolUseInput extends ClaudeHookBase {
|
|
13
|
+
hook_event_name: 'PostToolUse';
|
|
14
|
+
tool_name: string;
|
|
15
|
+
tool_input: Record<string, unknown>;
|
|
16
|
+
}
|
|
17
|
+
export interface ClaudeUserPromptExpansionInput extends ClaudeHookBase {
|
|
18
|
+
hook_event_name: 'UserPromptExpansion';
|
|
19
|
+
expansion_type: 'slash_command' | 'mcp_prompt';
|
|
20
|
+
command_name: string;
|
|
21
|
+
}
|
|
22
|
+
export interface ClaudePostCompactInput extends ClaudeHookBase {
|
|
23
|
+
hook_event_name: 'PostCompact';
|
|
24
|
+
trigger?: 'manual' | 'auto';
|
|
25
|
+
}
|
|
26
|
+
export type ClaudeHookInput = ClaudePreToolUseInput | ClaudePostToolUseInput | ClaudeUserPromptExpansionInput | ClaudePostCompactInput | ClaudeHookBase;
|
|
27
|
+
export interface ClaudeHookHandlingResult {
|
|
28
|
+
output?: object;
|
|
29
|
+
evidence: EvidenceEvent[];
|
|
30
|
+
verification?: VerificationResult;
|
|
31
|
+
contextAdvance?: AdvanceContextResult;
|
|
32
|
+
}
|
|
33
|
+
export declare function toProjectRelativePath(value: string, cwd: string): string;
|
|
34
|
+
export declare function riskForClaudeTool(tool: string): RiskLevel;
|
|
35
|
+
export declare function evidenceFromClaudeEvent(input: ClaudeHookInput): EvidenceEvent[];
|
|
36
|
+
/**
|
|
37
|
+
* Handles the host-specific surface while leaving persistence to the caller.
|
|
38
|
+
* No prompt, file contents, Bash command, or model response is copied into evidence.
|
|
39
|
+
*/
|
|
40
|
+
export declare function handleClaudeHookEvent(miko: Miko, taskId: string, input: ClaudeHookInput): ClaudeHookHandlingResult;
|
|
41
|
+
export {};
|
|
42
|
+
//# sourceMappingURL=claude-code.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-code.d.ts","sourceRoot":"","sources":["../src/claude-code.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,oBAAoB,EACpB,aAAa,EACb,IAAI,EACJ,SAAS,EACT,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAGpB,UAAU,cAAc;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,qBAAsB,SAAQ,cAAc;IAC3D,eAAe,EAAE,YAAY,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACrC;AAED,MAAM,WAAW,sBAAuB,SAAQ,cAAc;IAC5D,eAAe,EAAE,aAAa,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACrC;AAED,MAAM,WAAW,8BAA+B,SAAQ,cAAc;IACpE,eAAe,EAAE,qBAAqB,CAAC;IACvC,cAAc,EAAE,eAAe,GAAG,YAAY,CAAC;IAC/C,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,sBAAuB,SAAQ,cAAc;IAC5D,eAAe,EAAE,aAAa,CAAC;IAC/B,OAAO,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;CAC7B;AAED,MAAM,MAAM,eAAe,GACvB,qBAAqB,GACrB,sBAAsB,GACtB,8BAA8B,GAC9B,sBAAsB,GACtB,cAAc,CAAC;AAEnB,MAAM,WAAW,wBAAwB;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,YAAY,CAAC,EAAE,kBAAkB,CAAC;IAClC,cAAc,CAAC,EAAE,oBAAoB,CAAC;CACvC;AAQD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAYxE;AAmCD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAIzD;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,eAAe,GAAG,aAAa,EAAE,CAqC/E;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,eAAe,GACrB,wBAAwB,CAoC1B"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { formatMikoDecision, toClaudePreToolUseDecision } from './index.js';
|
|
3
|
+
const PATH_KEYS = ['file_path', 'path', 'filePath'];
|
|
4
|
+
function nonEmptyString(value) {
|
|
5
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
6
|
+
}
|
|
7
|
+
export function toProjectRelativePath(value, cwd) {
|
|
8
|
+
const normalizedValue = value.replace(/\\/g, '/');
|
|
9
|
+
const normalizedCwd = cwd.replace(/\\/g, '/').replace(/\/$/, '');
|
|
10
|
+
if (normalizedValue === normalizedCwd)
|
|
11
|
+
return '.';
|
|
12
|
+
if (normalizedValue.startsWith(`${normalizedCwd}/`)) {
|
|
13
|
+
return normalizedValue.slice(normalizedCwd.length + 1);
|
|
14
|
+
}
|
|
15
|
+
if (path.isAbsolute(value)) {
|
|
16
|
+
const relative = path.relative(cwd, value).replace(/\\/g, '/');
|
|
17
|
+
return relative.startsWith('../') || relative === '..' ? normalizedValue : relative;
|
|
18
|
+
}
|
|
19
|
+
return normalizedValue.replace(/^\.\//, '');
|
|
20
|
+
}
|
|
21
|
+
function relativeToolInput(input, cwd) {
|
|
22
|
+
const next = { ...input };
|
|
23
|
+
for (const key of PATH_KEYS) {
|
|
24
|
+
if (nonEmptyString(next[key]))
|
|
25
|
+
next[key] = toProjectRelativePath(next[key], cwd);
|
|
26
|
+
}
|
|
27
|
+
return next;
|
|
28
|
+
}
|
|
29
|
+
function privacySafeArguments(input, cwd) {
|
|
30
|
+
const safe = {};
|
|
31
|
+
for (const key of PATH_KEYS) {
|
|
32
|
+
if (nonEmptyString(input[key]))
|
|
33
|
+
safe[key] = toProjectRelativePath(input[key], cwd);
|
|
34
|
+
}
|
|
35
|
+
return Object.keys(safe).length > 0 ? safe : undefined;
|
|
36
|
+
}
|
|
37
|
+
function pathFromInput(input, cwd) {
|
|
38
|
+
const value = PATH_KEYS.map((key) => input[key]).find(nonEmptyString);
|
|
39
|
+
return value ? toProjectRelativePath(value, cwd) : undefined;
|
|
40
|
+
}
|
|
41
|
+
function skillFromToolInput(input) {
|
|
42
|
+
return ['skill', 'name', 'command_name']
|
|
43
|
+
.map((key) => input[key])
|
|
44
|
+
.find(nonEmptyString);
|
|
45
|
+
}
|
|
46
|
+
function skillFromReadPath(filePath) {
|
|
47
|
+
const parts = filePath.replace(/\\/g, '/').split('/').filter(Boolean);
|
|
48
|
+
if (parts.at(-1)?.toLowerCase() !== 'skill.md' || parts.length < 2)
|
|
49
|
+
return undefined;
|
|
50
|
+
return parts.at(-2);
|
|
51
|
+
}
|
|
52
|
+
export function riskForClaudeTool(tool) {
|
|
53
|
+
if (tool === 'Bash' || tool.startsWith('mcp__'))
|
|
54
|
+
return 'high';
|
|
55
|
+
if (tool === 'Edit' || tool === 'Write')
|
|
56
|
+
return 'medium';
|
|
57
|
+
return 'low';
|
|
58
|
+
}
|
|
59
|
+
export function evidenceFromClaudeEvent(input) {
|
|
60
|
+
if (input.hook_event_name === 'UserPromptExpansion') {
|
|
61
|
+
const event = input;
|
|
62
|
+
if (event.expansion_type === 'slash_command' && nonEmptyString(event.command_name)) {
|
|
63
|
+
return [{ type: 'skill_loaded', name: event.command_name, source: 'observed' }];
|
|
64
|
+
}
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
if (input.hook_event_name !== 'PostToolUse')
|
|
68
|
+
return [];
|
|
69
|
+
const event = input;
|
|
70
|
+
const evidence = [];
|
|
71
|
+
if (event.tool_name === 'Skill') {
|
|
72
|
+
const skill = skillFromToolInput(event.tool_input);
|
|
73
|
+
if (skill)
|
|
74
|
+
evidence.push({ type: 'skill_loaded', name: skill, source: 'observed' });
|
|
75
|
+
}
|
|
76
|
+
const filePath = pathFromInput(event.tool_input, event.cwd);
|
|
77
|
+
if (event.tool_name === 'Read' && filePath) {
|
|
78
|
+
evidence.push({ type: 'reference_read', path: filePath, source: 'observed' });
|
|
79
|
+
const skill = skillFromReadPath(filePath);
|
|
80
|
+
if (skill)
|
|
81
|
+
evidence.push({ type: 'skill_loaded', name: skill, source: 'observed' });
|
|
82
|
+
}
|
|
83
|
+
if ((event.tool_name === 'Edit' || event.tool_name === 'Write') && filePath) {
|
|
84
|
+
evidence.push({ type: 'artifact_changed', path: filePath, source: 'observed' });
|
|
85
|
+
}
|
|
86
|
+
evidence.push({
|
|
87
|
+
type: 'tool_succeeded',
|
|
88
|
+
tool: event.tool_name,
|
|
89
|
+
...(privacySafeArguments(event.tool_input, event.cwd)
|
|
90
|
+
? { arguments: privacySafeArguments(event.tool_input, event.cwd) }
|
|
91
|
+
: {}),
|
|
92
|
+
source: 'observed',
|
|
93
|
+
});
|
|
94
|
+
return evidence;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Handles the host-specific surface while leaving persistence to the caller.
|
|
98
|
+
* No prompt, file contents, Bash command, or model response is copied into evidence.
|
|
99
|
+
*/
|
|
100
|
+
export function handleClaudeHookEvent(miko, taskId, input) {
|
|
101
|
+
const evidence = evidenceFromClaudeEvent(input);
|
|
102
|
+
for (const event of evidence)
|
|
103
|
+
miko.record({ taskId, ...event });
|
|
104
|
+
if (input.hook_event_name === 'PostCompact') {
|
|
105
|
+
return {
|
|
106
|
+
evidence,
|
|
107
|
+
contextAdvance: miko.advanceContext(taskId, 'compaction'),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
if (input.hook_event_name === 'PreToolUse') {
|
|
111
|
+
const event = input;
|
|
112
|
+
if (event.tool_name === 'Skill')
|
|
113
|
+
return { evidence };
|
|
114
|
+
const verification = miko.verifyAction({
|
|
115
|
+
taskId,
|
|
116
|
+
tool: event.tool_name,
|
|
117
|
+
risk: riskForClaudeTool(event.tool_name),
|
|
118
|
+
arguments: relativeToolInput(event.tool_input, event.cwd),
|
|
119
|
+
});
|
|
120
|
+
return { output: toClaudePreToolUseDecision(verification), evidence, verification };
|
|
121
|
+
}
|
|
122
|
+
if (input.hook_event_name === 'Stop') {
|
|
123
|
+
const verification = miko.verifyCompletion(taskId);
|
|
124
|
+
if (verification.decision !== 'ALLOW') {
|
|
125
|
+
return {
|
|
126
|
+
output: { systemMessage: formatMikoDecision(verification) },
|
|
127
|
+
evidence,
|
|
128
|
+
verification,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
return { evidence, verification };
|
|
132
|
+
}
|
|
133
|
+
return { evidence };
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=claude-code.js.map
|