@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
package/README.md
ADDED
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
# harness-toolkit
|
|
2
|
+
|
|
3
|
+
Steers Cursor and Claude Code agents with **gates → follow-up → handoff → policy**.
|
|
4
|
+
|
|
5
|
+
Hooks fire on the editor's own events. The harness answers each one with a decision — allow, ask, deny, or
|
|
6
|
+
text injected into the turn — and writes a record of what it decided and why.
|
|
7
|
+
|
|
8
|
+
- **[Everything it validates](#everything-it-validates)** — the whole list, one row per check
|
|
9
|
+
- **[How to see any of it](#how-to-see-any-of-it)** — the command behind each row
|
|
10
|
+
- **[How to explain a decision](#how-to-explain-a-decision)** — from a message on screen back to the rule
|
|
11
|
+
|
|
12
|
+
## Start here
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm i -g @tech-leads-club/harness-toolkit
|
|
16
|
+
tlc harness install
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Then restart Cursor or Claude Code. That is the whole setup — the installer finds which of the two you
|
|
20
|
+
have and wires only those, and the harness works in every repository right away with a safe baseline.
|
|
21
|
+
|
|
22
|
+
To give one project its own rules, open it and say **"setup harness"** to the agent, or run
|
|
23
|
+
`tlc harness init --minimal`. To check anything, run `tlc harness doctor`.
|
|
24
|
+
|
|
25
|
+
## Table of contents
|
|
26
|
+
|
|
27
|
+
1. [Start here](#start-here)
|
|
28
|
+
2. [Everything it validates](#everything-it-validates)
|
|
29
|
+
- [Tier 1 — the floor](#tier-1--the-floor-no-configuration-reaches-it)
|
|
30
|
+
- [Tier 2 — always on, no switch](#tier-2--always-on-no-switch)
|
|
31
|
+
- [Tier 3 — the rails you choose](#tier-3--the-rails-you-choose)
|
|
32
|
+
3. [How to see any of it](#how-to-see-any-of-it)
|
|
33
|
+
4. [How to explain a decision](#how-to-explain-a-decision)
|
|
34
|
+
5. [Providers](#providers)
|
|
35
|
+
6. [Requirements](#requirements)
|
|
36
|
+
7. [Install](#install)
|
|
37
|
+
8. [Update](#update)
|
|
38
|
+
9. [Quick start](#quick-start)
|
|
39
|
+
10. [How it works](#how-it-works)
|
|
40
|
+
11. [Commands](#commands)
|
|
41
|
+
12. [Connect a project](#connect-a-project)
|
|
42
|
+
13. [Paths and shared state](#paths-and-shared-state)
|
|
43
|
+
14. [Ship claims](#ship-claims)
|
|
44
|
+
15. [Price catalogs](#price-catalogs)
|
|
45
|
+
16. [Windows](#windows)
|
|
46
|
+
17. [Troubleshooting](#troubleshooting)
|
|
47
|
+
18. [Documentation](#documentation)
|
|
48
|
+
19. [Contributing](#contributing)
|
|
49
|
+
20. [License](#license)
|
|
50
|
+
|
|
51
|
+
## Everything it validates
|
|
52
|
+
|
|
53
|
+
Three tiers, and which tier a check is in decides whether you can turn it off.
|
|
54
|
+
|
|
55
|
+
| Tier | Count | Configurable | Runs |
|
|
56
|
+
|------|-------|--------------|------|
|
|
57
|
+
| [Floor](#tier-1--the-floor-no-configuration-reaches-it) | 7 rules | Never | Before any policy is loaded, on every tool call, shell command and read |
|
|
58
|
+
| [Always on](#tier-2--always-on-no-switch) | 3 checks | Never | After the floor, on every acting event |
|
|
59
|
+
| [Rails](#tier-3--the-rails-you-choose) | 23 capabilities | Each one, individually | Where the table says |
|
|
60
|
+
|
|
61
|
+
Nothing else runs. If a message on your screen is not from one of the thirty-three rows below, it is not the
|
|
62
|
+
harness.
|
|
63
|
+
|
|
64
|
+
### Tier 1 — the floor, no configuration reaches it
|
|
65
|
+
|
|
66
|
+
Evaluated before the policy file is read, so no setting and no edit by an agent can clear one. Every denial
|
|
67
|
+
prints `rule=<name>`, and the name is the first column here.
|
|
68
|
+
|
|
69
|
+
<!-- generated:floor -->
|
|
70
|
+
|
|
71
|
+
| Rule | Denies | Allowed anyway |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| `outside-project-destruction` | a destructive command whose target resolves outside the repository and outside the OS temp directory | the same command inside the repository, or inside the temp directory |
|
|
74
|
+
| `unprovable-destruction` | a destructive verb whose target is a variable, a command substitution, or otherwise built at runtime — the harness cannot see what it would delete | a literal path it can resolve and check |
|
|
75
|
+
| `secret-access` | a read that would copy a credential into the transcript — `.env`, `~/.ssh`, `~/.aws`, `*.pem` and similar through a shell reader or the editor's own read tool, and the instance metadata service through any verb that speaks to the network | searching local files for the literal address, because `grep` and its kin make no request |
|
|
76
|
+
| `history-rewrite` | `git push --force` | `--force-with-lease`, which refuses on its own when the remote moved |
|
|
77
|
+
| `machine-control` | `shutdown`, `reboot`, `halt`, `poweroff` | — |
|
|
78
|
+
| `unprovable-execution` | a program fetched over the network and handed to a shell — piped, process-substituted, or inside a shell's `-c`/`eval` substitution. The gate cannot read what would run | a fetch with no shell downstream, and a shell fed a local file the gate can read |
|
|
79
|
+
| `policy-surface-write` | every route an agent has to harness policy and state — a shell redirect, an interpreter, a heredoc program, or a write tool — in the project and under the runtime home, plus the mutating `tlc harness` subcommands from inside a session | reading them with a proven reader (`cat`, `head`, `grep`, `jq`, `ls`, `stat`, `test`), and `tlc harness handoff` for the handoff state |
|
|
80
|
+
|
|
81
|
+
<!-- /generated -->
|
|
82
|
+
|
|
83
|
+
Policy changes are the operator's, from a terminal outside the agent session:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
tlc harness gate test-command node --test 'src/**/__test__/*.test.ts'
|
|
87
|
+
tlc harness gate lint-command npx biome check .
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Tier 2 — always on, no switch
|
|
91
|
+
|
|
92
|
+
Not floor rules, and equally unconfigurable — each one detects a condition that a config field could
|
|
93
|
+
otherwise switch off.
|
|
94
|
+
|
|
95
|
+
| Check | Fires on | Verdict | What it checks | How to see it |
|
|
96
|
+
|---|---|---|---|---|
|
|
97
|
+
| `policy-baseline-divergence` | every acting event | `deny` | Every policy source is hashed at session start. If one changes mid-session with no `tlc harness` command behind it, the next acting call is refused and the path named. Reads still pass, so the agent can investigate and report | `tlc harness policy` lists what changed; `tlc harness policy accept <path>` clears it |
|
|
98
|
+
| `policy-surface-write` (tool half) | `tool.before` | `deny` | An agent write to policy or state through Write, Edit, Delete, MultiEdit or NotebookEdit — the same paths the floor's shell half covers | `tlc harness obs report` — refusals by rule |
|
|
99
|
+
| `edit-collision` | `tool.before` | `ask` | Another live session in the same working tree touched this file recently | `tlc harness status` lists the live sessions |
|
|
100
|
+
|
|
101
|
+
### Tier 3 — the rails you choose
|
|
102
|
+
|
|
103
|
+
All 23 are off unless the **default** column says `on`, and each was presented with its benefit and its
|
|
104
|
+
trade-off when you ran the init wizard. `configPath` is the key in `.tlc/harness/config.json`.
|
|
105
|
+
|
|
106
|
+
<!-- generated:validates -->
|
|
107
|
+
|
|
108
|
+
| Rail · key · default | What it checks | Fires on | Verdict | How to see it |
|
|
109
|
+
|---|---|---|---|---|
|
|
110
|
+
| **Grind (lint/test on stop)**<br>`grind.enabled` · off | Runs your lint and test commands against the files the turn changed, and sends the agent back until they pass. | `stop` | `follow-up` | tlc harness obs report — runs, wall-clock and total; the last verdict is in the project state directory as last-gate.json |
|
|
111
|
+
| **Ship gate**<br>`shipGate.enabled` · off | Checks a declared ship claim against recent PASS evidence for the runtime paths the turn touched. | `stop` | `block-stop` | tlc harness obs report; the ship ledger in the project state directory records every claim, challenge and pass |
|
|
112
|
+
| **Empty-diff anti-ship**<br>`shipGate.emptyDiffAntiShip` · off | Checks that a ship claim has a non-empty diff behind it. | `stop` | `block-stop` | the ship ledger in the project state directory — the challenge row names the empty diff |
|
|
113
|
+
| **Comment gate (agent-added comments)**<br>`comments.enabled` · off | Checks the comment lines this turn added against the commit the turn started from: by reason, by resolvability, or none. | `stop` | `block-stop` | tlc harness obs report — the comments gate appears among the gate outcomes |
|
|
114
|
+
| **Duplication gate (agent-added copies)**<br>`duplication.enabled` · off | Checks whether the runs of code this turn added already exist somewhere else in the project. | `stop` | `block-stop` | tlc harness obs report — the duplication gate appears among the gate outcomes |
|
|
115
|
+
| **Supply-chain gate (dependencies this turn added)**<br>`supplyChain.enabled` · off | Checks what this turn added to the dependency graph: a manifest that moved without its lock, or an unpinned version. | `stop` | `block-stop` | tlc harness obs report — the supply-chain gate appears among the gate outcomes |
|
|
116
|
+
| **Subagent allowlist**<br>`subagents.enforceAllowlist` · off | Checks a subagent's model against the list you wrote, and against the blocked *-fast shapes. | `tool.before`<br>`subagent.start` | `deny` | tlc harness obs report — refusals attributed by rule; the denial text names subagents.allowedModels and lists what is permitted |
|
|
117
|
+
| **Block parent Fast mode for Task spawns**<br>`subagents.blockParentFast` · off | Checks whether the parent chat is in Fast mode before letting it spawn a subagent. | `tool.before`<br>`subagent.start` | `deny` | tlc harness obs report — refusals by rule; tlc harness status shows the sticky parent model it read |
|
|
118
|
+
| **Shell stall detection**<br>`shell.stallDetection` · off | Counts identical shell commands in a row and stops the loop at your threshold. | `shell.before` | `deny` | tlc harness obs report — interruptions attributed to the shell-stall rule |
|
|
119
|
+
| **Catastrophic shell ask**<br>`shell.catastrophicAsk` · **on** | Checks a shell command for destruction that reaches outside the workspace. | `shell.before` | `ask` | tlc harness obs report — interruptions attributed to the shell-catastrophic rule |
|
|
120
|
+
| **Lessons**<br>`intelligence.lessons.enabled` · off | Records what a repeated gate failure taught, ranks it, and injects it into the next session and retry. | `session.start`<br>`stop`<br>`session.end` | `context` | tlc harness lessons list — every tier with staleness and effectiveness; obs report shows the characters each injection cost |
|
|
121
|
+
| **Budget continue**<br>`intelligence.budgetContinue` · off | Checks for unfinished handoff work under context pressure and says keep going rather than wrap up. | `stop` | `follow-up` | tlc harness handoff — the follow-up fires only with unfinished work recorded there |
|
|
122
|
+
| **Gap feedback**<br>`intelligence.gapFeedback` · **on** | Turns a gate's output into a numbered list of gaps the retry has to close. | `stop` | `follow-up` | tlc harness handoff — the gaps it injects are the ones stored as previous_gaps |
|
|
123
|
+
| **Failure classification**<br>`intelligence.failureClassification` · **on** | Labels each gate failure with a category and stores it on the handoff. | `stop` | `record` | tlc harness handoff — last_failure_category |
|
|
124
|
+
| **Progressive handoff**<br>`intelligence.progressiveHandoff` · **on** | Reads the gaps the previous session ended with back out at the next session's start. | `session.start` | `context` | tlc harness handoff — the gaps it reads back out are previous_gaps |
|
|
125
|
+
| **Progressive context**<br>`intelligence.progressiveContext` · **on** | Raises the detail in the follow-up on each stop retry, so a repeat attempt is not given the same prompt. | `stop` | `follow-up` | tlc harness obs report — the retry count for a stop is the escalation level it reached |
|
|
126
|
+
| **Autopilot**<br>`intelligence.autopilot` · **on** | Emits ordered steps after a gate failure, computed by the runtime rather than invented by the model. | `stop` | `follow-up` | the AUTOPILOT block is in the follow-up text itself; obs report counts the failing stops that produced one |
|
|
127
|
+
| **Idle-turn gate (asked instead of acting)**<br>`intelligence.idleTurnGate` · off | Checks whether a turn that ended with open work recorded any tool call or file change at all. | `stop` | `block-stop` | tlc harness obs report for the block; tlc harness handoff shows the open work that armed it |
|
|
128
|
+
| **Docs staleness gate**<br>`docs.command` · off | Runs the repository's own documentation staleness tool on stop, like a lint command. | `stop` | `block-stop` | tlc harness obs report; the docs gate writes the same last-gate.json artifact the lint and test gates do |
|
|
129
|
+
| **Global observability spool**<br>`obs.globalSpool` · off | Copies every record into one file under the runtime home, so cost is readable across repositories. | `tool.after`<br>`tool.failure` | `record` | the spool file under the runtime home; tlc harness obs prune reports how many records it dropped |
|
|
130
|
+
| **Untrusted-content framing and enforcement**<br>`untrustedContent.enabled` · off | Frames outside content as data, and in enforce mode asks before a command that appears verbatim in it. | `tool.after` | `context` | tlc harness obs report — one framing injection per turn, with the characters it cost |
|
|
131
|
+
| **Plan gate (declared scope vs diff)**<br>`planGate.enabled` · off | Checks the files the turn changed against the scope it declared, and against any stated deviation. | `response.after`<br>`stop` | `block-stop` | tlc harness handoff — plan_paths, plan_at and plan_deviations |
|
|
132
|
+
| **Observation mode (measure a rail with its rule off)**<br>`observe.enabled` · off | Runs a rail's checker while that rail is not enforcing, and records the reading without acting on it. | `stop`<br>`session.end` | `record` | tlc harness obs report — the observation readings, held apart from the refusal counters so those stay honest |
|
|
133
|
+
|
|
134
|
+
<!-- /generated -->
|
|
135
|
+
|
|
136
|
+
The two tables above are generated from [`capabilities/catalog.json`](capabilities/catalog.json) and
|
|
137
|
+
`src/core/floor/floor.catalog.ts`. `tlc harness test` fails when they drift, so a rail that exists and is
|
|
138
|
+
not listed here is a build failure rather than a documentation gap.
|
|
139
|
+
|
|
140
|
+
Each rail's full benefit and trade-off — the long form, as the init wizard reads them out — is in
|
|
141
|
+
[`docs/architecture.md`](docs/architecture.md) and [`docs/concepts.md`](docs/concepts.md).
|
|
142
|
+
|
|
143
|
+
**One thing is not in any table: operator posture.** `tlc harness mode paired|solo|focus` changes how much
|
|
144
|
+
the agent surfaces and what earns an interruption. It switches no gate on and weakens no verification —
|
|
145
|
+
the evidence bar is identical at all three ([`docs/decisions/ad-025.md`](docs/decisions/ad-025.md)).
|
|
146
|
+
|
|
147
|
+
## How to see any of it
|
|
148
|
+
|
|
149
|
+
Every row above names a command in its last column. These are those commands.
|
|
150
|
+
|
|
151
|
+
| Command | Answers |
|
|
152
|
+
|---------|---------|
|
|
153
|
+
| `tlc harness status` | Which posture, which rails are on, which sessions are live, whether policy diverged |
|
|
154
|
+
| `tlc harness doctor` | Whether the install is healthy, and every rail that is off or misconfigured — including a rail switched on with nothing to enforce |
|
|
155
|
+
| `tlc harness obs report` | Per session: gate outcomes, refusals attributed by rule, interruptions by rule, characters injected, cost |
|
|
156
|
+
| `tlc harness obs live` | The same signal as it happens |
|
|
157
|
+
| `tlc harness handoff` | What the turn left open: gaps, blockers, next action, plan scope, failure category |
|
|
158
|
+
| `tlc harness lessons list` | Every lesson in all three tiers, with staleness, validity and whether it ever helped |
|
|
159
|
+
| `tlc harness why [n]` | The last n decisions this tool made, with the rule behind each — and a plain sentence when it made none |
|
|
160
|
+
| `tlc harness attest` | One hash-chained record per session: policy in force, rails active, refusals by rule, gate outcomes |
|
|
161
|
+
| `tlc harness policy` | Which policy source changed mid-session, changing nothing |
|
|
162
|
+
| `--json` on any of them | The same content, machine-readable |
|
|
163
|
+
|
|
164
|
+
All of these read. None of them changes a decision.
|
|
165
|
+
|
|
166
|
+
## How to explain a decision
|
|
167
|
+
|
|
168
|
+
**Start with `tlc harness why`** — the last ten decisions this tool made, each with the rule behind it. When the
|
|
169
|
+
harness did nothing, it says that in words, which is the answer no other command gives. Full guide:
|
|
170
|
+
[`docs/troubleshooting.md`](docs/troubleshooting.md).
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
You saw a message and want to know which rule produced it.
|
|
174
|
+
|
|
175
|
+
1. **The message names its rule.** A floor denial ends in `rule=<name>` — look it up in
|
|
176
|
+
[tier 1](#tier-1--the-floor-no-configuration-reaches-it). A rail's block names the gate.
|
|
177
|
+
2. **`tlc harness obs report`** attributes every refusal and interruption in the session to a rule, so
|
|
178
|
+
"seven interruptions" becomes "six from the posture, one from the catastrophic rule".
|
|
179
|
+
3. **`tlc harness attest`** is the same thing for a reviewer: which policy the session ran under, whether
|
|
180
|
+
it changed mid-session, and every gate outcome, hash-chained so a removed record is detectable.
|
|
181
|
+
4. **`tlc harness doctor`** explains the absence of a decision — a rail you expected to fire and did not
|
|
182
|
+
is usually one that is off, or on with nothing configured to enforce.
|
|
183
|
+
|
|
184
|
+
Two limits worth stating. The harness records the decisions it made; it never learns your answer to an
|
|
185
|
+
`ask`, so it reports a rate and its attribution, never a precision or an accuracy. And the attestation is
|
|
186
|
+
chained, not signed: it detects a rewritten record and proves nothing about authorship.
|
|
187
|
+
|
|
188
|
+
### What it covers, and what it does not
|
|
189
|
+
|
|
190
|
+
[docs/coverage.md](docs/coverage.md) assesses the harness against a published agentic-risk taxonomy — four risks
|
|
191
|
+
covered, five partial, one not applicable — and states what each row leaves open. It is a self-assessment, and the
|
|
192
|
+
control names in it are generated from the same catalogs this README's tables come from, so a rail that is renamed
|
|
193
|
+
or removed fails the build rather than leaving a claim standing.
|
|
194
|
+
|
|
195
|
+
## Providers
|
|
196
|
+
|
|
197
|
+
Both providers share one runtime, one project policy file, and one on-disk state directory. Core steering
|
|
198
|
+
logic never imports a provider adapter and never branches on a provider's name — see
|
|
199
|
+
[`docs/architecture.md`](docs/architecture.md) and [`docs/providers/index.md`](docs/providers/index.md).
|
|
200
|
+
|
|
201
|
+
| Provider | Detected by | User-level wiring | Docs |
|
|
202
|
+
|----------|-------------|--------------------|------|
|
|
203
|
+
| **Cursor** | `CURSOR_CONFIG_DIR`, else `~/.cursor` | `<resolved>/hooks.json` (replaced) | [`docs/providers/cursor.md`](docs/providers/cursor.md) |
|
|
204
|
+
| **Claude Code** | `CLAUDE_CONFIG_DIR`, else `~/.claude` | `<resolved>/settings.json` `hooks` block (merged) | [`docs/providers/claude-code.md`](docs/providers/claude-code.md) |
|
|
205
|
+
|
|
206
|
+
The installer and `tlc harness init` detect which of these are present and wire only those — neither
|
|
207
|
+
assumes Cursor.
|
|
208
|
+
|
|
209
|
+
A rail fires only where the provider can express it. `ask` on an event a provider cannot ask about becomes
|
|
210
|
+
`deny`, and injected context on an event a provider ignores is withheld rather than rendered into a field
|
|
211
|
+
nothing reads — `src/providers/provider.degrade.ts`.
|
|
212
|
+
|
|
213
|
+
## Requirements
|
|
214
|
+
|
|
215
|
+
| Dependency | Notes |
|
|
216
|
+
|------------|--------|
|
|
217
|
+
| **Bun** *or* **Node.js 24+** | Either one is enough. Bun runs every hook directly with no build step (~1 ms/hook); Node needs 24 LTS or 26 and the shipped `dist/` (~27 ms/hook). With neither, the installer stops and names both fixes |
|
|
218
|
+
| **npm** | Delivers the package on every platform |
|
|
219
|
+
| **esbuild** (only for the Node path) | Needed once to recompile `dist/`; the published `dist/` already works |
|
|
220
|
+
|
|
221
|
+
## Install
|
|
222
|
+
|
|
223
|
+
The same two commands on every platform.
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
npm i -g @tech-leads-club/harness-toolkit
|
|
227
|
+
tlc harness install
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
The package is the delivery vehicle and `tlc harness install` is what puts the runtime in place. They are
|
|
231
|
+
separate because a directory npm replaces wholesale cannot hold state — `config.json`, `state/` and `flags/`
|
|
232
|
+
would be deleted by an ordinary update ([`docs/decisions/ad-056.md`](docs/decisions/ad-056.md)).
|
|
233
|
+
|
|
234
|
+
Install target: `~/.tlc/harness` (runtime). The init skill is linked into the skills directory of
|
|
235
|
+
each provider it finds, because a provider only reads its own.
|
|
236
|
+
|
|
237
|
+
`tlc harness install`:
|
|
238
|
+
|
|
239
|
+
1. Materialises the runtime at `~/.tlc/harness` from the installed package
|
|
240
|
+
2. Creates `config.json` from `config.example.json` when missing
|
|
241
|
+
3. Adds `tlc` to `~/.local/bin`
|
|
242
|
+
4. Links the init skill into each detected provider's `skills/harness-init`
|
|
243
|
+
5. Wires user-level hooks for every provider it detects installed, in that provider's resolved config
|
|
244
|
+
directory
|
|
245
|
+
|
|
246
|
+
Update with `tlc harness update`, which bumps the package and re-materialises the runtime.
|
|
247
|
+
|
|
248
|
+
Overrides: `TLC_HOME`, `TLC_REPO_URL`, `TLC_BIN_DIR`.
|
|
249
|
+
|
|
250
|
+
Provider config directories are resolved, not assumed: `CLAUDE_CONFIG_DIR` and `CURSOR_CONFIG_DIR` are
|
|
251
|
+
honoured when set, so a relocated config is wired correctly. `tlc harness doctor` prints the resolved
|
|
252
|
+
target for each provider.
|
|
253
|
+
|
|
254
|
+
### Uninstall
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
tlc harness uninstall # print the plan; change nothing
|
|
258
|
+
tlc harness uninstall --yes # apply exactly that plan
|
|
259
|
+
tlc harness uninstall --purge --yes # and delete config.json, state/ and flags/ too
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
The plan is the confirmation — there is no prompt, so it works the same in CI and in a shell that is
|
|
263
|
+
already broken. What it does, and what it deliberately does not:
|
|
264
|
+
|
|
265
|
+
| | |
|
|
266
|
+
| --- | --- |
|
|
267
|
+
| `settings.json` | un-merged, never deleted. Every key and every hook that is not ours stays exactly where it is |
|
|
268
|
+
| `hooks.json` | deleted when every entry was ours, rewritten when it holds somebody else's |
|
|
269
|
+
| the `tlc` link and the skill links | removed |
|
|
270
|
+
| the runtime payload at `~/.tlc/harness` | removed — **unless** it is a symlink to a checkout, which is unlinked and never followed |
|
|
271
|
+
| `config.json`, `state/`, `flags/` | **kept**, unless you add `--purge` |
|
|
272
|
+
| the global npm package | reported with the exact command, never run for you |
|
|
273
|
+
| `.tlc/` inside your repositories | reported, never hunted for |
|
|
274
|
+
|
|
275
|
+
Running it twice is safe: the second run reports nothing to undo and exits 0.
|
|
276
|
+
|
|
277
|
+
An agent cannot run this for you. `~/.tlc/harness/state` is a policy surface and the runtime home is
|
|
278
|
+
outside any project, so the floor answers a delegated uninstall with `policy-surface-write` or
|
|
279
|
+
`outside-project-destruction`. That is deliberate — the supervised does not switch off the supervisor.
|
|
280
|
+
|
|
281
|
+
Restart or reload the provider session after install.
|
|
282
|
+
|
|
283
|
+
**From a git clone** — that is a contributor install, and it is described in
|
|
284
|
+
[`CONTRIBUTING.md`](CONTRIBUTING.md). Update never writes into a clone.
|
|
285
|
+
|
|
286
|
+
## Update
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
tlc harness update
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Moves the runtime to upstream, refreshes CLI + init skill + provider wiring, then runs doctor.
|
|
293
|
+
Reload/restart the provider session afterward if hooks or the init skill should refresh.
|
|
294
|
+
|
|
295
|
+
**The runtime path is an artifact the harness owns**, and update never touches anything else
|
|
296
|
+
([AD-046](docs/decisions/ad-046.md)):
|
|
297
|
+
|
|
298
|
+
| `tlc harness doctor` says | What update writes |
|
|
299
|
+
| --- | --- |
|
|
300
|
+
| `installed from npm` | bumps the package to `@latest` and re-materialises the runtime. No git command runs |
|
|
301
|
+
| `managed checkout` | moves it to upstream with a hard reset. Do not develop there — a local change is discarded |
|
|
302
|
+
| `link to a working clone` | nothing in the clone. That is a contributor install; you pull it yourself |
|
|
303
|
+
|
|
304
|
+
`dist/` is rebuilt only when a bundle is missing. Rebuilding a complete `dist/` is what used to dirty the checkout
|
|
305
|
+
and break every later update, because Bun and esbuild emit different bytes for the same source.
|
|
306
|
+
|
|
307
|
+
**If `update` aborts on `dist/`, install the package again.** A stuck install cannot deliver its own fix — the fix
|
|
308
|
+
lives in the revision `update` has to fetch — so `npm i -g @tech-leads-club/harness-toolkit@latest` followed by
|
|
309
|
+
`tlc harness install` is the recovery route, because the registry serves it independently of what is installed. It
|
|
310
|
+
leaves `config.json`, `state/` and any linked clone untouched ([AD-048](docs/decisions/ad-048.md)). There is no `--force`.
|
|
311
|
+
|
|
312
|
+
After a successful pull, prints a short digest of **optional catalog capabilities this project has not
|
|
313
|
+
enabled yet** (benefit + trade-off + how to enable). Nothing is auto-enabled — use the harness-init skill or
|
|
314
|
+
edit `.tlc/harness/config.json`.
|
|
315
|
+
|
|
316
|
+
`tlc harness doctor` emits non-blocking `WARN:` lines for the same off/missing opt-ins (and for default-on
|
|
317
|
+
features you explicitly set to `false`).
|
|
318
|
+
|
|
319
|
+
## Quick start
|
|
320
|
+
|
|
321
|
+
```bash
|
|
322
|
+
tlc harness doctor
|
|
323
|
+
tlc harness help
|
|
324
|
+
tlc harness status
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Healthy install checklist:
|
|
328
|
+
|
|
329
|
+
- Bun on PATH, or Node 24+ for the `dist/` fallback path
|
|
330
|
+
- `~/.tlc/harness` present with `dist/*.mjs`
|
|
331
|
+
- At least one provider's user-level hooks invoke `tlc-exec`
|
|
332
|
+
- `tlc` on PATH (open a new shell if needed)
|
|
333
|
+
|
|
334
|
+
## How it works
|
|
335
|
+
|
|
336
|
+
```mermaid
|
|
337
|
+
graph LR
|
|
338
|
+
EV["provider event<br/><i>Cursor or Claude Code hook</i>"]
|
|
339
|
+
HK["user-level hook file"]
|
|
340
|
+
LX["bin/tlc-exec.mjs <handler><br/><i>Bun first, Node + dist fallback</i>"]
|
|
341
|
+
EP["src/entrypoints/<handler>.ts<br/><i>core + resolved adapter</i>"]
|
|
342
|
+
OUT["follow-up · handoff · observability<br/><i>under project policy</i>"]
|
|
343
|
+
EV --> HK --> LX --> EP --> OUT
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
| Layer | Location |
|
|
347
|
+
|-------|----------|
|
|
348
|
+
| Runtime | `~/.tlc/harness` |
|
|
349
|
+
| Cursor user hooks | `<cursor config>/hooks.json` |
|
|
350
|
+
| Claude Code user hooks | `<claude config>/settings.json` (`hooks` block) |
|
|
351
|
+
| Project policy | `<repo>/.tlc/harness/config.json` |
|
|
352
|
+
| Project shim (per provider) | `<repo>/.cursor/hooks.json`, `<repo>/.claude/settings.json` |
|
|
353
|
+
|
|
354
|
+
Entrypoint: `bin/tlc-exec.mjs`.
|
|
355
|
+
Wrappers: `bin/tlc`, `bin/tlc-exec` (Unix); `bin/tlc.cmd`, `bin/tlc-exec.cmd` (Windows).
|
|
356
|
+
|
|
357
|
+
See `tlc harness help architecture` or [`docs/architecture.md`](docs/architecture.md).
|
|
358
|
+
|
|
359
|
+
## Commands
|
|
360
|
+
|
|
361
|
+
| Command | Purpose |
|
|
362
|
+
|---------|---------|
|
|
363
|
+
| `tlc harness status` | Mode, grind, gates |
|
|
364
|
+
| `--json` on any read command | Machine-readable output: `status`, `doctor`, `obs`, `lessons`, `prices lookup` |
|
|
365
|
+
| `tlc harness update` | Pull runtime + refresh skill/CLI/wiring + doctor |
|
|
366
|
+
| `tlc harness doctor` | Health checklist |
|
|
367
|
+
| `tlc harness help [topic]` | Docs |
|
|
368
|
+
| `tlc harness build` | Compile `dist/` for the Node fallback path |
|
|
369
|
+
| `tlc harness test` | Run the full local gate |
|
|
370
|
+
| `tlc harness grind [on\|off]` | Lint/test follow-ups on stop |
|
|
371
|
+
| `tlc harness pause` / `resume` | Disable / enable stop checks |
|
|
372
|
+
| `tlc harness mode solo\|paired\|focus` | Operator posture |
|
|
373
|
+
| `tlc harness attest` | Tamper-evident record of what each session ran under |
|
|
374
|
+
| `tlc harness handoff` | Handoff state between turns and sessions — the sanctioned reader |
|
|
375
|
+
| `tlc harness obs live` / `obs report` | Signal / session rollup |
|
|
376
|
+
| `tlc harness prices refresh` / `lookup` | Cost catalogs |
|
|
377
|
+
| `tlc harness lessons list` | Lessons across the three tiers, with staleness and effectiveness |
|
|
378
|
+
| `tlc harness lessons add "…" [--ref path:symbol] [--global] [--pin]` | Write a lesson; `--ref` retires it when that stops resolving, `--pin` puts it ahead of ranking |
|
|
379
|
+
| `tlc harness policy` / `policy accept <path>` | List a mid-session policy change; accept exactly those paths |
|
|
380
|
+
| `tlc harness init --minimal` | Project stub |
|
|
381
|
+
|
|
382
|
+
## Connect a project
|
|
383
|
+
|
|
384
|
+
1. Open the repository in Cursor and/or Claude Code.
|
|
385
|
+
2. Run `tlc harness init --minimal`, or ask the agent to run the harness-init skill.
|
|
386
|
+
3. Confirm `.tlc/harness/config.json` and the shim hooks for whichever provider(s) you use.
|
|
387
|
+
4. Run `tlc harness doctor` from the project root.
|
|
388
|
+
|
|
389
|
+
Details: `tlc harness help init` or [`docs/init.md`](docs/init.md).
|
|
390
|
+
|
|
391
|
+
## Paths and shared state
|
|
392
|
+
|
|
393
|
+
Both providers read and write the **same** project state — there is one `.tlc/harness/state/`, not one per
|
|
394
|
+
provider. Records inside it (signal, debug, audit) carry a `provider` field per event.
|
|
395
|
+
|
|
396
|
+
| Path | Contents |
|
|
397
|
+
|------|----------|
|
|
398
|
+
| `~/.tlc/harness` | Runtime |
|
|
399
|
+
| `~/.tlc/harness/state/lessons.json` | Global lesson tier — this machine, every product ([AD-040](docs/decisions/ad-040.md)) |
|
|
400
|
+
| `<cursor config>/hooks.json` | Cursor user hooks (if Cursor installed) |
|
|
401
|
+
| `<claude config>/settings.json` | Claude Code user hooks, `hooks` block (if Claude Code installed) |
|
|
402
|
+
| `<provider config>/skills/harness-init` | Init skill, linked per detected provider from runtime `skills/harness-init` |
|
|
403
|
+
| `<repo>/.tlc/harness/config.json` | Project policy (tracked) |
|
|
404
|
+
| `<repo>/.tlc/harness/state/` | Handoff, obs, audit, project-tier `lessons.json`, ship ledger (gitignored) |
|
|
405
|
+
|
|
406
|
+
Do not use `~/.tlc/harness` for anything other than the installed runtime — see
|
|
407
|
+
[`docs/decisions/ad-002.md`](docs/decisions/ad-002.md) for why the layout is namespaced this way.
|
|
408
|
+
|
|
409
|
+
## Ship claims
|
|
410
|
+
|
|
411
|
+
Protocol line (free-form "done/shipped" is ignored):
|
|
412
|
+
|
|
413
|
+
```text
|
|
414
|
+
HARNESS_SHIP_CLAIM: <one-line summary>
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
When `shipGate` is enabled and runtime paths changed, cite recent PASS under `evidenceDir`.
|
|
418
|
+
See `tlc harness help concepts` or [`docs/concepts.md`](docs/concepts.md).
|
|
419
|
+
|
|
420
|
+
The plan gate uses the same shape for scope:
|
|
421
|
+
|
|
422
|
+
```text
|
|
423
|
+
HARNESS_PLAN: src/core/plan/**, src/entrypoints/stop.ts
|
|
424
|
+
HARNESS_PLAN_DEVIATION: src/x.ts — the call site moved with the type
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
Both are protocol lines on purpose. A gate that fires on free-English "done" fires on the word, not the
|
|
428
|
+
claim.
|
|
429
|
+
|
|
430
|
+
## Price catalogs
|
|
431
|
+
|
|
432
|
+
```bash
|
|
433
|
+
tlc harness prices refresh
|
|
434
|
+
tlc harness prices refresh cursor
|
|
435
|
+
tlc harness prices refresh litellm
|
|
436
|
+
tlc harness prices lookup <model-id> [provider]
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
See `tlc harness help prices` or [`docs/measure.md`](docs/measure.md).
|
|
440
|
+
|
|
441
|
+
## Windows
|
|
442
|
+
|
|
443
|
+
Path resolution goes through `os.homedir()` only, hooks use exec form, filenames are sanitized,
|
|
444
|
+
atomic writes retry, and the CLI ships a `.cmd` shim alongside directory junctions
|
|
445
|
+
([`docs/decisions/ad-006.md`](docs/decisions/ad-006.md)).
|
|
446
|
+
|
|
447
|
+
CI runs the full suite and the `dist/` build on `windows-latest` on every push.
|
|
448
|
+
|
|
449
|
+
Outside CI coverage: `install.ps1`, and hooks firing inside a Cursor or Claude Code session on Windows.
|
|
450
|
+
|
|
451
|
+
## Troubleshooting
|
|
452
|
+
|
|
453
|
+
| Symptom | Action |
|
|
454
|
+
|---------|--------|
|
|
455
|
+
| `tlc: command not found` | New shell; ensure `~/.local/bin` on PATH; re-run install |
|
|
456
|
+
| Hooks never fire | Reload/restart the provider session; check the provider's own hook log; confirm `tlc-exec` |
|
|
457
|
+
| Missing `dist/` | `tlc harness build` |
|
|
458
|
+
| Cost `null` | `tlc harness help prices` |
|
|
459
|
+
| Project doctor FAILs | Expected until project policy exists |
|
|
460
|
+
| A rail you enabled never fires | `tlc harness doctor` — a rail switched on with nothing configured to enforce is reported as a fault |
|
|
461
|
+
| Every tool call is refused after you edited the config | `tlc harness policy` then `tlc harness policy accept <path>` |
|
|
462
|
+
|
|
463
|
+
See `tlc harness help diagnose` or [`docs/diagnose.md`](docs/diagnose.md).
|
|
464
|
+
|
|
465
|
+
## Documentation
|
|
466
|
+
|
|
467
|
+
Full OKF v0.1 documentation bundle: [`docs/index.md`](docs/index.md).
|
|
468
|
+
|
|
469
|
+
## Contributing
|
|
470
|
+
|
|
471
|
+
[`CONTRIBUTING.md`](./CONTRIBUTING.md) · [`SECURITY.md`](./SECURITY.md)
|
|
472
|
+
|
|
473
|
+
## License
|
|
474
|
+
|
|
475
|
+
**PolyForm Noncommercial 1.0.0** — [`LICENSE`](./LICENSE), [`NOTICE`](./NOTICE).
|
|
476
|
+
|
|
477
|
+
| Allowed | Requires separate license |
|
|
478
|
+
|---------|---------------------------|
|
|
479
|
+
| Noncommercial use, change, distribute with attribution | Commercial use |
|
|
480
|
+
| Keep `Required Notice` + license terms | Dropping attribution |
|
package/bin/tlc
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
SOURCE="${BASH_SOURCE[0]:-$0}"
|
|
4
|
+
while [[ -L "$SOURCE" ]]; do
|
|
5
|
+
DIR="$(cd "$(dirname "$SOURCE")" && pwd)"
|
|
6
|
+
SOURCE="$(readlink "$SOURCE")"
|
|
7
|
+
[[ "$SOURCE" != /* ]] && SOURCE="$DIR/$SOURCE"
|
|
8
|
+
done
|
|
9
|
+
BIN_DIR="$(cd "$(dirname "$SOURCE")" && pwd)"
|
|
10
|
+
exec node "$BIN_DIR/tlc-exec.mjs" tlc-cli "$@"
|
package/bin/tlc-build
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Build Node-runnable ESM bundles under dist/. Requires Bun OR esbuild on PATH for compile.
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
TLC_HOME="$(cd "$(dirname "$0")/.." && pwd)"
|
|
6
|
+
DIST="$TLC_HOME/dist"
|
|
7
|
+
mkdir -p "$DIST"
|
|
8
|
+
|
|
9
|
+
# Derived from disk, never hardcoded: a fixed list silently stops building a new
|
|
10
|
+
# entrypoint, and the missing bundle only surfaces when a hook fires in production.
|
|
11
|
+
collect() {
|
|
12
|
+
local dir="$1"
|
|
13
|
+
local name
|
|
14
|
+
for path in "$dir"/*.ts; do
|
|
15
|
+
[ -e "$path" ] || continue
|
|
16
|
+
name="$(basename "$path" .ts)"
|
|
17
|
+
case "$name" in *.test) continue ;; esac
|
|
18
|
+
printf '%s\n' "$name"
|
|
19
|
+
done
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
# tools/dev/ holds the checks that validate THIS repository's own architecture, docs and conventions. They must
|
|
23
|
+
# not ship — a user's clone has no src/core to validate and no docs/decisions of ours to render — and the
|
|
24
|
+
# directory is the whole declaration: `collect` reads one level, so nothing under tools/dev is ever a bundle.
|
|
25
|
+
# The list this replaced named four while ten qualified, and the six that were added later shipped for weeks.
|
|
26
|
+
|
|
27
|
+
# macOS ships bash 3.2, which has no mapfile — read into arrays the portable way instead.
|
|
28
|
+
ENTRYPOINTS=()
|
|
29
|
+
while IFS= read -r name; do
|
|
30
|
+
[ -n "$name" ] && ENTRYPOINTS+=("$name")
|
|
31
|
+
done < <(collect "$TLC_HOME/src/entrypoints")
|
|
32
|
+
|
|
33
|
+
TOOLS=()
|
|
34
|
+
while IFS= read -r name; do
|
|
35
|
+
[ -n "$name" ] && TOOLS+=("$name")
|
|
36
|
+
done < <(collect "$TLC_HOME/tools")
|
|
37
|
+
|
|
38
|
+
build_one() {
|
|
39
|
+
local src="$1"
|
|
40
|
+
local out="$2"
|
|
41
|
+
if command -v bun >/dev/null 2>&1; then
|
|
42
|
+
bun build --target=node --format=esm --outfile="$out" "$src"
|
|
43
|
+
return
|
|
44
|
+
fi
|
|
45
|
+
if command -v esbuild >/dev/null 2>&1; then
|
|
46
|
+
esbuild --bundle --platform=node --format=esm --outfile="$out" "$src"
|
|
47
|
+
return
|
|
48
|
+
fi
|
|
49
|
+
echo "tlc-build: need Bun or esbuild to compile TypeScript → dist/" >&2
|
|
50
|
+
echo " Install Node.js 24+ (Active LTS) or 26 Current, then either Bun or: npm i -g esbuild" >&2
|
|
51
|
+
exit 1
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
echo "tlc-build → $DIST"
|
|
55
|
+
for name in "${ENTRYPOINTS[@]}"; do
|
|
56
|
+
build_one "$TLC_HOME/src/entrypoints/${name}.ts" "$DIST/${name}.mjs"
|
|
57
|
+
done
|
|
58
|
+
for name in "${TOOLS[@]}"; do
|
|
59
|
+
build_one "$TLC_HOME/tools/${name}.ts" "$DIST/${name}.mjs"
|
|
60
|
+
done
|
|
61
|
+
|
|
62
|
+
build_one "$TLC_HOME/bin/tlc-cli.ts" "$DIST/tlc-cli.mjs"
|
|
63
|
+
|
|
64
|
+
chmod +x "$TLC_HOME/bin/tlc" "$TLC_HOME/bin/tlc-exec" "$TLC_HOME/bin/tlc-build"
|
|
65
|
+
|
|
66
|
+
# A bundle whose source moved or was deleted is not rebuilt, so it is also never diffed — it simply stays in
|
|
67
|
+
# dist/ and ships. Deriving what to remove from the same disk that decides what to build closes that.
|
|
68
|
+
for bundle in "$DIST"/*.mjs; do
|
|
69
|
+
[ -e "$bundle" ] || continue
|
|
70
|
+
name="$(basename "$bundle" .mjs)"
|
|
71
|
+
if [ "$name" = "tlc-cli" ] ||
|
|
72
|
+
[ -f "$TLC_HOME/src/entrypoints/${name}.ts" ] ||
|
|
73
|
+
[ -f "$TLC_HOME/tools/${name}.ts" ]; then
|
|
74
|
+
continue
|
|
75
|
+
fi
|
|
76
|
+
echo "tlc-build: pruning $name.mjs — no source"
|
|
77
|
+
rm -f "$bundle"
|
|
78
|
+
done
|
|
79
|
+
|
|
80
|
+
echo "tlc-build: ok ($(ls -1 "$DIST"/*.mjs | wc -l) bundles)"
|