@tech-leads-club/harness-toolkit 0.2.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 +95 -0
- package/LICENSE +84 -0
- package/NOTICE +9 -0
- package/README.md +480 -0
- package/bin/tlc +10 -0
- package/bin/tlc-build +80 -0
- package/bin/tlc-cli.ts +1509 -0
- package/bin/tlc-exec +10 -0
- package/bin/tlc-exec.cmd +4 -0
- package/bin/tlc-exec.d.mts +60 -0
- package/bin/tlc-exec.mjs +248 -0
- package/bin/tlc.cmd +4 -0
- package/bin/tlc.mjs +7 -0
- package/bin/write-user-hooks.d.mts +40 -0
- package/bin/write-user-hooks.mjs +123 -0
- package/capabilities/catalog.json +418 -0
- package/config.example.json +51 -0
- package/dist/compact-before.mjs +7839 -0
- package/dist/doctor.mjs +8263 -0
- package/dist/help-topic.mjs +82 -0
- package/dist/init-project.mjs +993 -0
- package/dist/install-runtime.mjs +969 -0
- package/dist/lessons-cli.mjs +7258 -0
- package/dist/obs-cli.mjs +7025 -0
- package/dist/price-lookup.mjs +221 -0
- package/dist/prompt-submit.mjs +7860 -0
- package/dist/refresh-model-prices.mjs +158 -0
- package/dist/response-after.mjs +7879 -0
- package/dist/run.mjs +7830 -0
- package/dist/session-end.mjs +7937 -0
- package/dist/session-start.mjs +8056 -0
- package/dist/shim.mjs +52 -0
- package/dist/stop.mjs +8494 -0
- package/dist/subagent-start.mjs +7861 -0
- package/dist/subagent-stop.mjs +7851 -0
- package/dist/support.mjs +7045 -0
- package/dist/tlc-cli.mjs +8063 -0
- package/dist/tool-after.mjs +8113 -0
- package/dist/tool-before.mjs +7985 -0
- package/dist/tool-failure.mjs +7850 -0
- package/dist/uninstall-runtime.mjs +1307 -0
- package/docs/architecture.md +270 -0
- package/docs/concepts.md +562 -0
- package/docs/coverage.md +55 -0
- package/docs/decisions/ad-001.md +32 -0
- package/docs/decisions/ad-002.md +51 -0
- package/docs/decisions/ad-003.md +30 -0
- package/docs/decisions/ad-004.md +37 -0
- package/docs/decisions/ad-005.md +36 -0
- package/docs/decisions/ad-006.md +49 -0
- package/docs/decisions/ad-007.md +36 -0
- package/docs/decisions/ad-008.md +54 -0
- package/docs/decisions/ad-009.md +61 -0
- package/docs/decisions/ad-010.md +45 -0
- package/docs/decisions/ad-011.md +59 -0
- package/docs/decisions/ad-012.md +71 -0
- package/docs/decisions/ad-013.md +87 -0
- package/docs/decisions/ad-014.md +56 -0
- package/docs/decisions/ad-015.md +33 -0
- package/docs/decisions/ad-016.md +98 -0
- package/docs/decisions/ad-017.md +65 -0
- package/docs/decisions/ad-018.md +77 -0
- package/docs/decisions/ad-019.md +75 -0
- package/docs/decisions/ad-020.md +88 -0
- package/docs/decisions/ad-021.md +57 -0
- package/docs/decisions/ad-022.md +120 -0
- package/docs/decisions/ad-023.md +87 -0
- package/docs/decisions/ad-024.md +85 -0
- package/docs/decisions/ad-025.md +92 -0
- package/docs/decisions/ad-026.md +131 -0
- package/docs/decisions/ad-027.md +90 -0
- package/docs/decisions/ad-028.md +90 -0
- package/docs/decisions/ad-029.md +73 -0
- package/docs/decisions/ad-030.md +97 -0
- package/docs/decisions/ad-031.md +94 -0
- package/docs/decisions/ad-032.md +83 -0
- package/docs/decisions/ad-033.md +89 -0
- package/docs/decisions/ad-034.md +86 -0
- package/docs/decisions/ad-035.md +86 -0
- package/docs/decisions/ad-036.md +68 -0
- package/docs/decisions/ad-037.md +47 -0
- package/docs/decisions/ad-038.md +52 -0
- package/docs/decisions/ad-039.md +69 -0
- package/docs/decisions/ad-040.md +89 -0
- package/docs/decisions/ad-041.md +98 -0
- package/docs/decisions/ad-042.md +82 -0
- package/docs/decisions/ad-043.md +79 -0
- package/docs/decisions/ad-044.md +61 -0
- package/docs/decisions/ad-045.md +94 -0
- package/docs/decisions/ad-046.md +111 -0
- package/docs/decisions/ad-047.md +96 -0
- package/docs/decisions/ad-048.md +85 -0
- package/docs/decisions/ad-049.md +66 -0
- package/docs/decisions/ad-050.md +94 -0
- package/docs/decisions/ad-051.md +69 -0
- package/docs/decisions/ad-052.md +69 -0
- package/docs/decisions/ad-053.md +78 -0
- package/docs/decisions/ad-054.md +98 -0
- package/docs/decisions/ad-055.md +74 -0
- package/docs/decisions/ad-056.md +85 -0
- package/docs/decisions/ad-057.md +68 -0
- package/docs/decisions/ad-058.md +97 -0
- package/docs/decisions/ad-059.md +82 -0
- package/docs/decisions/ad-060.md +75 -0
- package/docs/decisions/ad-061.md +68 -0
- package/docs/decisions/ad-062.md +72 -0
- package/docs/decisions/ad-063.md +84 -0
- package/docs/decisions/ad-064.md +79 -0
- package/docs/decisions/ad-065.md +81 -0
- package/docs/decisions/ad-066.md +111 -0
- package/docs/decisions/ad-067.md +64 -0
- package/docs/decisions/ad-068.md +79 -0
- package/docs/decisions/ad-069.md +74 -0
- package/docs/decisions/ad-070.md +86 -0
- package/docs/decisions/ad-071.md +93 -0
- package/docs/decisions/ad-072.md +82 -0
- package/docs/decisions/ad-073.md +102 -0
- package/docs/decisions/ad-074.md +91 -0
- package/docs/decisions/ad-075.md +79 -0
- package/docs/decisions/ad-076.md +102 -0
- package/docs/decisions/ad-077.md +94 -0
- package/docs/decisions/ad-078.md +84 -0
- package/docs/decisions/ad-079.md +73 -0
- package/docs/decisions/ad-080.md +86 -0
- package/docs/decisions/ad-081.md +70 -0
- package/docs/decisions/ad-082.md +79 -0
- package/docs/decisions/ad-083.md +88 -0
- package/docs/decisions/index.md +111 -0
- package/docs/diagnose.md +248 -0
- package/docs/index.md +52 -0
- package/docs/init.md +101 -0
- package/docs/lessons.md +294 -0
- package/docs/log.md +139 -0
- package/docs/measure.md +178 -0
- package/docs/providers/claude-code.md +118 -0
- package/docs/providers/cursor.md +110 -0
- package/docs/providers/index.md +71 -0
- package/docs/troubleshooting.md +138 -0
- package/model-aliases.json +12 -0
- package/model-prices.cursor.json +410 -0
- package/model-prices.json +1 -0
- package/package.json +57 -0
- package/skills/harness-init/SKILL.md +241 -0
- package/skills/harness-init/evals/trigger_evals.json +86 -0
- package/skills/harness-init/references/capabilities.md +113 -0
- package/src/contracts/capabilities.ts +30 -0
- package/src/contracts/decision.ts +21 -0
- package/src/contracts/effort.ts +15 -0
- package/src/contracts/harness-event.ts +87 -0
- package/src/contracts/index.ts +6 -0
- package/src/contracts/policy-defaults.ts +14 -0
- package/src/contracts/wiring.ts +20 -0
- package/src/core/attest/attest.service.ts +0 -0
- package/src/core/capability/capability.service.ts +62 -0
- package/src/core/capability/capability.store.ts +51 -0
- package/src/core/capability/capability.types.ts +56 -0
- package/src/core/comment-policy/comment-policy.service.ts +307 -0
- package/src/core/comment-policy/comment-policy.types.ts +6 -0
- package/src/core/comment-policy/comment-resolvability.ts +134 -0
- package/src/core/comment-policy/comment-syntax.catalog.ts +310 -0
- package/src/core/comment-policy/comment-syntax.store.ts +65 -0
- package/src/core/comment-policy/comment-syntax.types.ts +24 -0
- package/src/core/core.facade.ts +461 -0
- package/src/core/duplication/duplication.service.ts +219 -0
- package/src/core/floor/floor.catalog.ts +54 -0
- package/src/core/floor/floor.paths.ts +90 -0
- package/src/core/floor/floor.policy-surface.ts +311 -0
- package/src/core/floor/floor.service.ts +306 -0
- package/src/core/floor/floor.tokenize.ts +179 -0
- package/src/core/floor/floor.verb.ts +28 -0
- package/src/core/floor/index.ts +1 -0
- package/src/core/gate/gate.artifact.ts +157 -0
- package/src/core/gate/gate.command.ts +130 -0
- package/src/core/gate/gate.findings.ts +201 -0
- package/src/core/gate/gate.inputs.ts +0 -0
- package/src/core/gate/gate.lock.ts +283 -0
- package/src/core/gate/gate.service.ts +28 -0
- package/src/core/gate/gate.types.ts +67 -0
- package/src/core/handoff/handoff.service.ts +43 -0
- package/src/core/handoff/handoff.store.ts +59 -0
- package/src/core/handoff/handoff.types.ts +89 -0
- package/src/core/index.ts +52 -0
- package/src/core/integrity/state-seal.ts +81 -0
- package/src/core/lesson/lesson.authored.ts +96 -0
- package/src/core/lesson/lesson.credit.ts +51 -0
- package/src/core/lesson/lesson.garden.ts +283 -0
- package/src/core/lesson/lesson.link.ts +93 -0
- package/src/core/lesson/lesson.score.ts +61 -0
- package/src/core/lesson/lesson.select.ts +209 -0
- package/src/core/lesson/lesson.service.ts +110 -0
- package/src/core/lesson/lesson.store.ts +317 -0
- package/src/core/lesson/lesson.sync.ts +70 -0
- package/src/core/lesson/lesson.types.ts +92 -0
- package/src/core/lesson/lesson.validity.ts +44 -0
- package/src/core/observability/observability.report.ts +326 -0
- package/src/core/observability/observability.service.ts +328 -0
- package/src/core/observability/observability.store.ts +269 -0
- package/src/core/observability/observability.types.ts +193 -0
- package/src/core/observability/observability.why.ts +206 -0
- package/src/core/observe/observe.service.ts +76 -0
- package/src/core/plan/plan.detect.ts +47 -0
- package/src/core/plan/plan.service.ts +81 -0
- package/src/core/plan/plan.types.ts +25 -0
- package/src/core/policy/policy.defaults.ts +103 -0
- package/src/core/policy/policy.guard.ts +34 -0
- package/src/core/policy/policy.integrity.ts +238 -0
- package/src/core/policy/policy.loader.ts +118 -0
- package/src/core/policy/policy.operator.ts +82 -0
- package/src/core/policy/policy.posture.ts +79 -0
- package/src/core/policy/policy.rails.ts +45 -0
- package/src/core/policy/policy.types.ts +157 -0
- package/src/core/presence/presence.service.ts +111 -0
- package/src/core/presence/presence.store.ts +59 -0
- package/src/core/presence/presence.types.ts +9 -0
- package/src/core/release/release.decisions.ts +147 -0
- package/src/core/release/release.seen.ts +38 -0
- package/src/core/shell-policy/shell-policy.service.ts +215 -0
- package/src/core/shell-policy/shell-policy.stall.ts +57 -0
- package/src/core/shell-policy/shell-policy.types.ts +26 -0
- package/src/core/ship/ship.ledger.ts +76 -0
- package/src/core/ship/ship.service.ts +137 -0
- package/src/core/ship/ship.types.ts +19 -0
- package/src/core/stagnation/stagnation.resolution.ts +87 -0
- package/src/core/stagnation/stagnation.service.ts +20 -0
- package/src/core/stagnation/stagnation.store.ts +49 -0
- package/src/core/stagnation/stagnation.types.ts +12 -0
- package/src/core/subagent-policy/subagent-policy.parent-model.ts +202 -0
- package/src/core/subagent-policy/subagent-policy.service.ts +139 -0
- package/src/core/subagent-policy/subagent-policy.types.ts +22 -0
- package/src/core/supply-chain/supply-chain.catalog.ts +51 -0
- package/src/core/supply-chain/supply-chain.service.ts +217 -0
- package/src/core/turn/turn.activity.ts +94 -0
- package/src/core/turn/turn.autopilot.ts +131 -0
- package/src/core/turn/turn.failure-signals.ts +193 -0
- package/src/core/turn/turn.loop-counter.ts +71 -0
- package/src/core/turn/turn.types.ts +14 -0
- package/src/core/untrusted/untrusted.detect.ts +53 -0
- package/src/core/untrusted/untrusted.recall.ts +87 -0
- package/src/core/untrusted/untrusted.service.ts +129 -0
- package/src/core/untrusted/untrusted.store.ts +75 -0
- package/src/core/untrusted/untrusted.types.ts +42 -0
- package/src/entrypoints/compact-before.ts +14 -0
- package/src/entrypoints/prompt-submit.ts +25 -0
- package/src/entrypoints/response-after.ts +60 -0
- package/src/entrypoints/run.ts +179 -0
- package/src/entrypoints/session-end.ts +58 -0
- package/src/entrypoints/session-start.ts +172 -0
- package/src/entrypoints/shim.ts +46 -0
- package/src/entrypoints/stop.ts +876 -0
- package/src/entrypoints/subagent-start.ts +16 -0
- package/src/entrypoints/subagent-stop.ts +32 -0
- package/src/entrypoints/support.ts +182 -0
- package/src/entrypoints/tool-after.ts +111 -0
- package/src/entrypoints/tool-before.ts +186 -0
- package/src/entrypoints/tool-failure.ts +26 -0
- package/src/platform/backoff.ts +49 -0
- package/src/platform/cli-output.ts +30 -0
- package/src/platform/env-scope.ts +22 -0
- package/src/platform/fs-atomic.ts +142 -0
- package/src/platform/fs-jsonl.ts +24 -0
- package/src/platform/git.ts +187 -0
- package/src/platform/paths.ts +80 -0
- package/src/platform/pricing.ts +229 -0
- package/src/platform/process.ts +64 -0
- package/src/platform/sanitize.ts +23 -0
- package/src/platform/screen.ts +46 -0
- package/src/platform/style.ts +103 -0
- package/src/providers/claude/claude.capabilities.ts +25 -0
- package/src/providers/claude/claude.detect.ts +14 -0
- package/src/providers/claude/claude.inbound.ts +252 -0
- package/src/providers/claude/claude.lessons-view.ts +33 -0
- package/src/providers/claude/claude.outbound.ts +65 -0
- package/src/providers/claude/claude.policy-defaults.ts +9 -0
- package/src/providers/claude/claude.transcript.ts +62 -0
- package/src/providers/claude/claude.wiring.ts +245 -0
- package/src/providers/claude/index.ts +17 -0
- package/src/providers/cursor/cursor.capabilities.ts +28 -0
- package/src/providers/cursor/cursor.detect.ts +14 -0
- package/src/providers/cursor/cursor.inbound.ts +203 -0
- package/src/providers/cursor/cursor.lessons-view.ts +30 -0
- package/src/providers/cursor/cursor.outbound.ts +43 -0
- package/src/providers/cursor/cursor.policy-defaults.ts +9 -0
- package/src/providers/cursor/cursor.wiring.ts +214 -0
- package/src/providers/cursor/index.ts +17 -0
- package/src/providers/index.ts +9 -0
- package/src/providers/provider.degrade.ts +152 -0
- package/src/providers/provider.port.ts +20 -0
- package/src/providers/provider.registry.ts +28 -0
- package/tools/doctor.ts +631 -0
- package/tools/help-topic.ts +74 -0
- package/tools/init-project.ts +277 -0
- package/tools/install-runtime.ts +164 -0
- package/tools/lessons-cli.ts +474 -0
- package/tools/obs-cli.ts +175 -0
- package/tools/price-lookup.ts +57 -0
- package/tools/refresh-model-prices.ts +210 -0
- package/tools/test-env.mjs +28 -0
- package/tools/test-env.names.d.mts +1 -0
- package/tools/test-env.names.mjs +14 -0
- package/tools/uninstall-runtime.ts +463 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Provider
|
|
3
|
+
title: "Claude Code provider"
|
|
4
|
+
description: "The Claude Code adapter — capability descriptor, event mapping, and wiring target for the settings.json hooks block in Claude Code's resolved config directory."
|
|
5
|
+
tags: [provider, claude-code]
|
|
6
|
+
timestamp: "2026-07-29"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Claude Code provider
|
|
10
|
+
|
|
11
|
+
Source: `src/providers/claude/`.
|
|
12
|
+
|
|
13
|
+
## Detection
|
|
14
|
+
|
|
15
|
+
`claude.detect.ts`: a raw hook payload is Claude Code's when `hook_event_name` is PascalCase (e.g.
|
|
16
|
+
`PreToolUse`) and either `cwd` or `transcript_path` is present.
|
|
17
|
+
|
|
18
|
+
## Capability descriptor
|
|
19
|
+
|
|
20
|
+
`claude.capabilities.ts`:
|
|
21
|
+
|
|
22
|
+
| Capability | Value |
|
|
23
|
+
| --- | --- |
|
|
24
|
+
| `enforcesHooks` | `true` |
|
|
25
|
+
| `askSupportedOn` | `["tool.before", "shell.before", "mcp.before", "read.before"]` — includes `tool.before`, unlike Cursor (see [/decisions/ad-009.md](/decisions/ad-009.md), note) |
|
|
26
|
+
| `sessionEnv` | `false` |
|
|
27
|
+
| `nativeLoopCounter` | `false` — `Stop` carries no loop counter; `core/turn` supplies it instead (see [/decisions/ad-014.md](/decisions/ad-014.md)) |
|
|
28
|
+
| `dedicatedShellEvent` | `false` — shell is `PreToolUse`/`PostToolUse` with `tool_name: "Bash"` |
|
|
29
|
+
| `toolInputRewrite` | `true` |
|
|
30
|
+
| `toolOutputRewrite` | `true` |
|
|
31
|
+
| `contextAtToolBefore` | `true` |
|
|
32
|
+
| `contextAtToolAfter` | `true` |
|
|
33
|
+
| `contextAtStop` | `true` — `Stop` accepts `hookSpecificOutput.additionalContext` for feedback that continues the turn |
|
|
34
|
+
| `sessionStartContextReliable` | `true` — `SessionStart` delivers `hookSpecificOutput.additionalContext`, capped at 10,000 characters |
|
|
35
|
+
| `usageInPayload` | `false` — cost comes from the transcript, not the hook payload |
|
|
36
|
+
| `effortSignal` | `true` — `effort.level` (`low\|medium\|high\|xhigh\|max`) |
|
|
37
|
+
| `thoughtEvent` | `false` |
|
|
38
|
+
|
|
39
|
+
## Policy defaults
|
|
40
|
+
|
|
41
|
+
`claude.policy-defaults.ts` supplies no model allowlist and no blocked patterns — see
|
|
42
|
+
[/decisions/ad-011.md](/decisions/ad-011.md).
|
|
43
|
+
|
|
44
|
+
It used to ship three model slugs, and an empty project list fell back to them, so a spawn could be refused by a
|
|
45
|
+
list that appears nowhere in the project. This page named a fourth that the code did not, which is what a shipped
|
|
46
|
+
catalogue does over time. `subagents.allowedModels` is the operator's and has no other source; an empty one
|
|
47
|
+
enforces nothing and `doctor` says so ([/decisions/ad-053.md](/decisions/ad-053.md)).
|
|
48
|
+
|
|
49
|
+
## Event mapping
|
|
50
|
+
|
|
51
|
+
`claude.inbound.ts` maps Claude's PascalCase hook names to `HarnessEventKind`. Unlike Cursor, `PreToolUse`
|
|
52
|
+
and `PostToolUse` are single dispatcher hooks that fan out by `tool_name`:
|
|
53
|
+
|
|
54
|
+
| Claude hook | Fan-out rule | `HarnessEventKind` |
|
|
55
|
+
| --- | --- | --- |
|
|
56
|
+
| `SessionStart` | — | `session.start` |
|
|
57
|
+
| `SessionEnd` | — | `session.end` |
|
|
58
|
+
| `UserPromptSubmit` | — | `prompt.submit` |
|
|
59
|
+
| `PreToolUse` | `tool_name === "Bash"` | `shell.before` |
|
|
60
|
+
| `PreToolUse` | `tool_name` matches `mcp__*` | `mcp.before` |
|
|
61
|
+
| `PreToolUse` | `tool_name === "Read"` | `read.before` |
|
|
62
|
+
| `PreToolUse` | otherwise | `tool.before` |
|
|
63
|
+
| `PostToolUse` | `tool_name === "Bash"` | `shell.after` |
|
|
64
|
+
| `PostToolUse` | `tool_name` matches `mcp__*` | `mcp.after` |
|
|
65
|
+
| `PostToolUse` | `tool_name` is `Edit`/`Write` | `edit.after` |
|
|
66
|
+
| `PostToolUse` | otherwise | `tool.after` |
|
|
67
|
+
| `PostToolUseFailure` | — | `tool.failure` |
|
|
68
|
+
| `SubagentStart` | — | `subagent.start` |
|
|
69
|
+
| `SubagentStop` | — | `subagent.stop` |
|
|
70
|
+
| `Stop` | — | `stop` |
|
|
71
|
+
| `PreCompact` | — | `compact.before` |
|
|
72
|
+
| `MessageDisplay` | — | `response.after` |
|
|
73
|
+
|
|
74
|
+
Claude has no `thought.after` equivalent (`thoughtEvent: false`).
|
|
75
|
+
|
|
76
|
+
## Field paths
|
|
77
|
+
|
|
78
|
+
Exact JSON field paths this adapter reads are pinned in
|
|
79
|
+
[/decisions/ad-014.md](/decisions/ad-014.md) — including the one inferred rather than documented
|
|
80
|
+
(`tool_input.file_path` for Edit/Write/Read).
|
|
81
|
+
|
|
82
|
+
## Wiring target
|
|
83
|
+
|
|
84
|
+
`claude.wiring.ts` merges (`strategy: "merge"`) into `~/.claude/settings.json`'s `hooks` block —
|
|
85
|
+
never replacing the file wholesale, since a user's own Claude settings may already exist. Every entry uses
|
|
86
|
+
exec form (`command: "node"`, `args: [launcherPath, handler]`) on every platform, bypassing shell
|
|
87
|
+
tokenization so there is no quoting variant to get wrong. Handler names are the
|
|
88
|
+
`src/entrypoints/<name>.ts` filenames (see [/decisions/ad-015.md](/decisions/ad-015.md)):
|
|
89
|
+
`session-start`, `session-end`, `prompt-submit`, `tool-before`, `tool-after`, `tool-failure`,
|
|
90
|
+
`subagent-start`, `subagent-stop`, `stop`, `compact-before`, `response-after`.
|
|
91
|
+
|
|
92
|
+
The merge is idempotent and deep-equality-checked per hook group, so re-running it never duplicates an
|
|
93
|
+
already-present entry.
|
|
94
|
+
|
|
95
|
+
## Lessons view
|
|
96
|
+
|
|
97
|
+
`claude.lessons-view.ts` appends a single `@.tlc/harness/lessons.md` import line to the project's `CLAUDE.md` when it
|
|
98
|
+
is not already present. It is a pointer, not a copy — the content lives in one file.
|
|
99
|
+
|
|
100
|
+
It is written under `intelligence.lessons.syncRulesFile: "always"` and **not** under the default `"auto"`, because
|
|
101
|
+
this adapter declares `sessionStartContextReliable: true`: `SessionStart` delivers
|
|
102
|
+
`hookSpecificOutput.additionalContext`, so lessons arrive without a durable file and the pointer would be a second
|
|
103
|
+
copy of a working route. Set `always` to have it anyway — it survives a restart, which the injected context does not
|
|
104
|
+
(see [/decisions/ad-050.md](/decisions/ad-050.md), and [/decisions/ad-011.md](/decisions/ad-011.md) item 4).
|
|
105
|
+
|
|
106
|
+
Only `hookSpecificOutput.additionalContext` is emitted. Claude Code reads a top-level `additional_context` as well
|
|
107
|
+
and does not deduplicate the two, so emitting both would inject the same text twice.
|
|
108
|
+
|
|
109
|
+
## Doctor / status
|
|
110
|
+
|
|
111
|
+
`tlc harness doctor` reports Claude wiring as `wired` when merging the current entries into the existing
|
|
112
|
+
`~/.claude/settings.json` would produce no change, `detected-but-unwired` otherwise, and `not-installed`
|
|
113
|
+
when `~/.claude` does not exist.
|
|
114
|
+
|
|
115
|
+
## See also
|
|
116
|
+
|
|
117
|
+
- [/providers/index.md](/providers/index.md)
|
|
118
|
+
- [/providers/cursor.md](/providers/cursor.md)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Provider
|
|
3
|
+
title: "Cursor provider"
|
|
4
|
+
description: "The Cursor adapter — capability descriptor, event mapping, and wiring target for Cursor's hooks.json."
|
|
5
|
+
tags: [provider, cursor]
|
|
6
|
+
timestamp: "2026-07-29"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Cursor provider
|
|
10
|
+
|
|
11
|
+
Source: `src/providers/cursor/`.
|
|
12
|
+
|
|
13
|
+
## Detection
|
|
14
|
+
|
|
15
|
+
`cursor.detect.ts`: a raw hook payload is Cursor's when `hook_event_name` is camelCase (e.g.
|
|
16
|
+
`beforeShellExecution`) and `workspace_roots` is an array.
|
|
17
|
+
|
|
18
|
+
## Capability descriptor
|
|
19
|
+
|
|
20
|
+
`cursor.capabilities.ts`:
|
|
21
|
+
|
|
22
|
+
| Capability | Value |
|
|
23
|
+
| --- | --- |
|
|
24
|
+
| `enforcesHooks` | `true` |
|
|
25
|
+
| `askSupportedOn` | `["shell.before", "mcp.before"]` — **not** `tool.before` (see [/decisions/ad-009.md](/decisions/ad-009.md), note) |
|
|
26
|
+
| `sessionEnv` | `true` |
|
|
27
|
+
| `nativeLoopCounter` | `true` |
|
|
28
|
+
| `dedicatedShellEvent` | `true` |
|
|
29
|
+
| `toolInputRewrite` | `true` |
|
|
30
|
+
| `toolOutputRewrite` | `true` |
|
|
31
|
+
| `contextAtToolBefore` | `false` |
|
|
32
|
+
| `contextAtToolAfter` | `true` |
|
|
33
|
+
| `contextAtStop` | `false` — the `stop` output schema carries `followup_message` and nothing else |
|
|
34
|
+
| `sessionStartContextReliable` | `false` — Cursor accepts `additional_context` at `sessionStart`, logs it as merged, and drops it (see [Lessons view](#lessons-view)) |
|
|
35
|
+
| `usageInPayload` | `true` |
|
|
36
|
+
| `effortSignal` | `false` |
|
|
37
|
+
| `thoughtEvent` | `true` |
|
|
38
|
+
|
|
39
|
+
## Policy defaults
|
|
40
|
+
|
|
41
|
+
`cursor.policy-defaults.ts` supplies the blocked-pattern list (`-fast(?:$|[^a-z0-9])`, `/fast(?:$|[^a-z0-9])`,
|
|
42
|
+
`composer-2\.5-fast`) — see [/decisions/ad-011.md](/decisions/ad-011.md).
|
|
43
|
+
|
|
44
|
+
**It supplies no model allowlist.** It used to, and an empty project list fell back to it, so a spawn could be
|
|
45
|
+
refused by five slugs that appear nowhere in the project and had already gone stale. `subagents.allowedModels` is
|
|
46
|
+
the operator's and has no other source; an empty one enforces nothing and `doctor` says so
|
|
47
|
+
([/decisions/ad-053.md](/decisions/ad-053.md)). The blocked patterns stay because they are **added** to the
|
|
48
|
+
project's rather than replacing them.
|
|
49
|
+
|
|
50
|
+
## Event mapping
|
|
51
|
+
|
|
52
|
+
`cursor.inbound.ts` maps Cursor's own camelCase hook names to the shared `HarnessEventKind`:
|
|
53
|
+
|
|
54
|
+
| Cursor hook | `HarnessEventKind` |
|
|
55
|
+
| --- | --- |
|
|
56
|
+
| `sessionStart` | `session.start` |
|
|
57
|
+
| `sessionEnd` | `session.end` |
|
|
58
|
+
| `beforeSubmitPrompt` | `prompt.submit` |
|
|
59
|
+
| `preToolUse` | `tool.before` |
|
|
60
|
+
| `postToolUse` | `tool.after` |
|
|
61
|
+
| `postToolUseFailure` | `tool.failure` |
|
|
62
|
+
| `beforeShellExecution` | `shell.before` |
|
|
63
|
+
| `afterShellExecution` | `shell.after` |
|
|
64
|
+
| `beforeMCPExecution` | `mcp.before` |
|
|
65
|
+
| `afterMCPExecution` | `mcp.after` |
|
|
66
|
+
| `beforeReadFile` | `read.before` |
|
|
67
|
+
| `afterFileEdit` | `edit.after` |
|
|
68
|
+
| `subagentStart` | `subagent.start` |
|
|
69
|
+
| `subagentStop` | `subagent.stop` |
|
|
70
|
+
| `stop` | `stop` |
|
|
71
|
+
| `preCompact` | `compact.before` |
|
|
72
|
+
| `afterAgentResponse` | `response.after` |
|
|
73
|
+
| `afterAgentThought` | `thought.after` |
|
|
74
|
+
|
|
75
|
+
Cursor has a dedicated event per tool class (`beforeShellExecution`, `beforeMCPExecution`,
|
|
76
|
+
`beforeReadFile`), unlike Claude's single `PreToolUse`/`PostToolUse` fan-out.
|
|
77
|
+
|
|
78
|
+
## Wiring target
|
|
79
|
+
|
|
80
|
+
`cursor.wiring.ts` writes (`strategy: "replace"`) the user-level `~/.cursor/hooks.json`, one entry per
|
|
81
|
+
`(hookEvent, handler)` pair, dispatching through the launcher: `node <launcherPath> <handler>` on
|
|
82
|
+
Unix/macOS, `cmd /c node <launcherPath> <handler>` on Windows. Handler names are the
|
|
83
|
+
`src/entrypoints/<name>.ts` filenames (see [/decisions/ad-015.md](/decisions/ad-015.md)):
|
|
84
|
+
`session-bootstrap`, `persist-handoff`, `obs-session-end`, `obs-passive`, `guard-subagent`,
|
|
85
|
+
`pre-tool-use`, `guard-shell`, `audit-event`, `guard-mcp`, `guard-read`, `format`, `verify-gates`,
|
|
86
|
+
`obs-stop`, `track-response`.
|
|
87
|
+
|
|
88
|
+
## Lessons view
|
|
89
|
+
|
|
90
|
+
`cursor.lessons-view.ts` renders `.tlc/harness/lessons.md` into `.cursor/rules/harness-lessons.mdc`
|
|
91
|
+
(`alwaysApply: true`). This is not a second copy of a working route — it is **the** route on this host. Cursor
|
|
92
|
+
accepts `additional_context` returned from `sessionStart`, logs it as merged, and drops it; its own staff called that
|
|
93
|
+
"a bug on our side… a timing issue between when the hook runs and when the composer handle is created" (forum thread
|
|
94
|
+
158452, 2026-04-20), and it was reported again against 3.14.7 on 2026-08-02. `env` on the same payload arrives,
|
|
95
|
+
because that is a different code path — which is why `HARNESS_ACTIVE` works while the prose does not.
|
|
96
|
+
|
|
97
|
+
The adapter therefore declares `sessionStartContextReliable: false`, and the default `syncRulesFile: "auto"` writes
|
|
98
|
+
the view here for that reason rather than because an operator guessed. `never` declines it; `always` forces it (see
|
|
99
|
+
[/decisions/ad-050.md](/decisions/ad-050.md), and [/decisions/ad-011.md](/decisions/ad-011.md) item 4 for the
|
|
100
|
+
original reasoning).
|
|
101
|
+
|
|
102
|
+
## Doctor / status
|
|
103
|
+
|
|
104
|
+
`tlc harness doctor` reports Cursor wiring as `wired`, `detected-but-unwired`, or `not-installed` by
|
|
105
|
+
diffing the live `~/.cursor/hooks.json` against the entries this adapter would write.
|
|
106
|
+
|
|
107
|
+
## See also
|
|
108
|
+
|
|
109
|
+
- [/providers/index.md](/providers/index.md)
|
|
110
|
+
- [/providers/claude-code.md](/providers/claude-code.md)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Aggregate
|
|
3
|
+
title: "Providers index"
|
|
4
|
+
description: "Index of the provider adapters — Cursor and Claude Code — and the port they both implement."
|
|
5
|
+
tags: [providers, index, architecture]
|
|
6
|
+
timestamp: "2026-07-29"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Providers
|
|
10
|
+
|
|
11
|
+
Core steering logic never imports a provider adapter and never reads a provider name. Each provider is an
|
|
12
|
+
anti-corruption-layer adapter implementing `ProviderPort`
|
|
13
|
+
(`src/providers/provider.port.ts`):
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
type ProviderPort = {
|
|
17
|
+
readonly name: string;
|
|
18
|
+
detect(raw: unknown): boolean;
|
|
19
|
+
capabilities(): ProviderCapabilities;
|
|
20
|
+
policyDefaults(): ProviderPolicyDefaults;
|
|
21
|
+
toEvent(raw: Record<string, unknown>): HarnessEvent | null;
|
|
22
|
+
render(decision: Decision, event: HarnessEvent): Rendered;
|
|
23
|
+
wiring(runtime: RuntimePaths): ProviderWiring;
|
|
24
|
+
};
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
- `detect` — does this raw hook payload belong to this provider?
|
|
28
|
+
- `capabilities` — a declarative `ProviderCapabilities` descriptor (see
|
|
29
|
+
[/architecture.md](/architecture.md)); core degrades on this data, never on `name`.
|
|
30
|
+
- `policyDefaults` — this provider's own model allowlist / blocked patterns / minimum effort (see
|
|
31
|
+
[/decisions/ad-011.md](/decisions/ad-011.md)).
|
|
32
|
+
- `toEvent` — parses a raw hook payload into the shared `HarnessEvent` shape.
|
|
33
|
+
- `render` — turns a core `Decision` back into this provider's wire format.
|
|
34
|
+
- `wiring` — describes which hooks this provider needs registered, and where.
|
|
35
|
+
|
|
36
|
+
## Registered providers
|
|
37
|
+
|
|
38
|
+
Detection runs in registry order (`src/providers/provider.registry.ts`), first match wins, and multiple
|
|
39
|
+
matches are reported as ambiguous rather than silently resolved:
|
|
40
|
+
|
|
41
|
+
| Provider | Detected by | Docs |
|
|
42
|
+
| --- | --- | --- |
|
|
43
|
+
| Cursor | camelCase `hook_event_name` + `workspace_roots` array | [/providers/cursor.md](/providers/cursor.md) |
|
|
44
|
+
| Claude Code | PascalCase `hook_event_name` + `cwd` or `transcript_path` | [/providers/claude-code.md](/providers/claude-code.md) |
|
|
45
|
+
|
|
46
|
+
## Event kinds
|
|
47
|
+
|
|
48
|
+
Both adapters translate into the same 18-member `HarnessEventKind` union (see
|
|
49
|
+
[/decisions/ad-009.md](/decisions/ad-009.md) item 1): `session.start`, `session.end`, `prompt.submit`,
|
|
50
|
+
`tool.before`, `tool.after`, `tool.failure`, `shell.before`, `shell.after`, `mcp.before`, `mcp.after`,
|
|
51
|
+
`read.before`, `edit.after`, `subagent.start`, `subagent.stop`, `stop`, `compact.before`, `response.after`,
|
|
52
|
+
`thought.after`. A provider that cannot produce a kind is gated by its capability descriptor, not by the
|
|
53
|
+
kind's absence.
|
|
54
|
+
|
|
55
|
+
## Caller identity vs. spawn target
|
|
56
|
+
|
|
57
|
+
`HarnessEvent` splits two fields that an earlier design conflated (see
|
|
58
|
+
[/decisions/ad-016.md](/decisions/ad-016.md) item 1):
|
|
59
|
+
|
|
60
|
+
| Field | Meaning |
|
|
61
|
+
| --- | --- |
|
|
62
|
+
| `subagentType` | the identity of the agent **currently running** (the caller) |
|
|
63
|
+
| `spawnSubagentType` | the type of subagent a Task/spawn call **targets** |
|
|
64
|
+
| `model` | the current/parent model |
|
|
65
|
+
| `spawnModel` | the model of the child being spawned |
|
|
66
|
+
|
|
67
|
+
## Adding a provider
|
|
68
|
+
|
|
69
|
+
A new provider is a new directory under `src/providers/<name>/` plus one line in
|
|
70
|
+
`src/providers/provider.registry.ts` (see [/decisions/ad-004.md](/decisions/ad-004.md) and
|
|
71
|
+
[/decisions/ad-009.md](/decisions/ad-009.md) item 7) — never a change to `core/`.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Runbook
|
|
3
|
+
title: "Was that the harness?"
|
|
4
|
+
description: "How to tell a harness decision from model behaviour, in the moment and after the fact: the one command that answers it, what each rule name means, and the four symptoms that are not the harness at all."
|
|
5
|
+
tags: [troubleshooting, rules, observability]
|
|
6
|
+
timestamp: "2026-08-12"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Was that the harness?
|
|
10
|
+
|
|
11
|
+
> "eu to meio perdido tentando entender o que é comportamento do harness toolkit, o que é loucura do modelo"
|
|
12
|
+
|
|
13
|
+
Hooks are invisible by construction: the harness answers the editor, and the editor decides whether to show
|
|
14
|
+
you. This page is how you find out anyway.
|
|
15
|
+
|
|
16
|
+
## The one command
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
tlc harness why
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The last ten decisions the tool made, newest first, each with the rule behind it:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
Last 4 harness decision(s), newest first:
|
|
26
|
+
|
|
27
|
+
19:23:05 shell deny rule=policy-surface-write
|
|
28
|
+
python3 -c "open('.tlc/harness/config.json','w')"
|
|
29
|
+
19:23:05 shell ask rule=shell-catastrophic
|
|
30
|
+
dd if=/dev/zero of=/dev/sda
|
|
31
|
+
19:23:05 tool.before deny rule=subagent-allowlist
|
|
32
|
+
Task
|
|
33
|
+
19:23:05 session start context rule=unattributed
|
|
34
|
+
4210 chars injected
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**And when it was not the harness, it says so:**
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
No harness decision in this window.
|
|
41
|
+
Whatever you just saw was the model, not a rail — the harness allowed everything it was asked about.
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
That sentence is the point of the command. `tlc harness why 30` widens the window; `--json` gives the same
|
|
45
|
+
records as data.
|
|
46
|
+
|
|
47
|
+
## The rule names
|
|
48
|
+
|
|
49
|
+
Every refusal names one. The name is the pointer — the reasoning lives in the decision record it cites.
|
|
50
|
+
|
|
51
|
+
| Rule | What refused, and why |
|
|
52
|
+
| --- | --- |
|
|
53
|
+
| `outside-project-destruction` | a destructive command aimed outside the repo and outside the temp directory |
|
|
54
|
+
| `unprovable-destruction` | a destructive verb whose target is built at runtime, so the harness cannot see what it would delete |
|
|
55
|
+
| `unprovable-execution` | a program fetched over the network and handed to a shell, so the harness cannot read what would run |
|
|
56
|
+
| `secret-access` | a read that would copy credentials into the transcript, from a file **or** from the instance metadata service |
|
|
57
|
+
| `history-rewrite` | `git push --force`. `--force-with-lease` is allowed |
|
|
58
|
+
| `machine-control` | `shutdown`, `reboot`, `halt`, `poweroff` |
|
|
59
|
+
| `policy-surface-write` | any route an agent has to harness policy or state |
|
|
60
|
+
| `policy-baseline-divergence` | a policy file changed mid-session with no `tlc harness` command behind it |
|
|
61
|
+
| `untrusted-command` | a shell command that appears verbatim in content this session fetched — put to you before it runs |
|
|
62
|
+
| `edit-collision` | another live session touched this file recently |
|
|
63
|
+
| `shell-catastrophic` | a shell command that can destroy data outside the workspace |
|
|
64
|
+
| `shell-posture-paired` | the `paired` posture asks before a command that leaves the machine or can overwrite a path |
|
|
65
|
+
| `shell-stall` | the same shell command repeated past the threshold |
|
|
66
|
+
| `subagent-allowlist` | the model is not on `subagents.allowedModels` |
|
|
67
|
+
| `subagent-parent-fast` | the parent chat is in Fast mode |
|
|
68
|
+
| `subagent-blocked-pattern` | the model matched a blocked shape, `*-fast` by default |
|
|
69
|
+
| `subagent-model-required` | the spawn named no model and `requireModel` is on |
|
|
70
|
+
| `subagent-min-effort` | the spawn's effort is below `minEffort` |
|
|
71
|
+
| `subagent-read-only` | a read-only subagent type reached for a writing tool |
|
|
72
|
+
| `rewrite-unavailable` | the provider cannot rewrite tool input, so the harness asked instead |
|
|
73
|
+
| `unattributed` | a record written before rules were required. It will not appear for new decisions |
|
|
74
|
+
|
|
75
|
+
## Four things that are **not** the harness
|
|
76
|
+
|
|
77
|
+
**A message with no `rule=`, no `BLOCKED:` and no `FLOOR:`.** Every harness decision carries one of the three.
|
|
78
|
+
|
|
79
|
+
**A subagent model changing.** The harness does not change models. It refuses a spawn, with
|
|
80
|
+
`rule=subagent-allowlist`, and the refusal goes to the model as `agent_message` and to you as `user_message`.
|
|
81
|
+
If your editor renders neither, `tlc harness why` still shows it.
|
|
82
|
+
|
|
83
|
+
**A gate failing here but passing in your terminal.** From the second attempt the follow-up says so itself, and
|
|
84
|
+
names the variables the hook set. It is usually the project's gate command differing from the one your suite
|
|
85
|
+
needs — which only you can change:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
tlc harness gate test-command <your real test command>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Everything, when the harness is off for this repo.** `tlc harness doctor` lists which rails are on.
|
|
92
|
+
|
|
93
|
+
## After the fact
|
|
94
|
+
|
|
95
|
+
| Command | Answers |
|
|
96
|
+
| --- | --- |
|
|
97
|
+
| `tlc harness why [n]` | the last n decisions, with rules — **start here** |
|
|
98
|
+
| `tlc harness obs report` | this session: gate outcomes, refusals by rule, interruptions by rule, cost |
|
|
99
|
+
| `tlc harness obs live` | the same signal as it happens |
|
|
100
|
+
| `tlc harness doctor` | which rails are on, and which are on but enforcing nothing |
|
|
101
|
+
| `tlc harness handoff` | what the last turn left open |
|
|
102
|
+
| `tlc harness attest` | one hash-chained record per session, for a reviewer |
|
|
103
|
+
|
|
104
|
+
## When a refusal is wrong
|
|
105
|
+
|
|
106
|
+
Say so in your reply and let the operator decide. Working around a floor rule is not a fix — the six of them
|
|
107
|
+
read no configuration precisely so that nothing in a session can clear them
|
|
108
|
+
([/decisions/ad-016.md](/decisions/ad-016.md)).
|
|
109
|
+
|
|
110
|
+
For a rail, the operator changes it from their own terminal, outside the agent session
|
|
111
|
+
([/decisions/ad-022.md](/decisions/ad-022.md)):
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
tlc harness mode solo # leave the paired posture
|
|
115
|
+
tlc harness pause # disable stop checks while exploring
|
|
116
|
+
tlc harness policy accept --all # after you edited config mid-session
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## When you want it off entirely
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
tlc harness uninstall # the plan, and nothing else
|
|
123
|
+
tlc harness uninstall --yes # apply it
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
It un-merges the hook groups out of `settings.json` and leaves every other key alone, so this is the
|
|
127
|
+
supported alternative to editing that file by hand. `config.json` and `state/` survive unless you add
|
|
128
|
+
`--purge`.
|
|
129
|
+
|
|
130
|
+
**An agent cannot do this for you**, and the refusal is not a defect. The runtime home sits outside any
|
|
131
|
+
project and `state/` is a policy surface, so a delegated uninstall meets `outside-project-destruction` or
|
|
132
|
+
`policy-surface-write` ([/decisions/ad-066.md](/decisions/ad-066.md)). Run it from your own terminal.
|
|
133
|
+
|
|
134
|
+
## See also
|
|
135
|
+
|
|
136
|
+
- [/concepts.md](/concepts.md) — every rail from the operator's side
|
|
137
|
+
- [/diagnose.md](/diagnose.md) — hooks not firing, stale runtime, cost showing null
|
|
138
|
+
- [/decisions/index.md](/decisions/index.md) — why each rule exists
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"composer-2.5": "composer-2.5",
|
|
3
|
+
"cursor-grok-4.5-high": "grok-4.5",
|
|
4
|
+
"cursor-grok-4.5": "grok-4.5",
|
|
5
|
+
"glm-5.2-high": "glm-5.2",
|
|
6
|
+
"glm-5.2": "glm-5.2",
|
|
7
|
+
"kimi-k2.7-code": "kimi-k2.7-code",
|
|
8
|
+
"gpt-5.3-codex-high": "gpt-5.3-codex",
|
|
9
|
+
"gpt-5.3-codex": "gpt-5.3-codex",
|
|
10
|
+
"auto": "auto-cost",
|
|
11
|
+
"auto-cost": "auto-cost"
|
|
12
|
+
}
|