@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,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Decision
|
|
3
|
+
title: "AD-002 — Provider-neutral naming and layout"
|
|
4
|
+
description: "The CLI binary is tlc with a harness subcommand group, the runtime home is ~/.tlc/harness/, and the repository is github.com/felipfr/tlc-agent-harness."
|
|
5
|
+
tags: [decision, naming, cli, layout]
|
|
6
|
+
timestamp: "2026-07-29"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# AD-002 — Provider-neutral naming and layout
|
|
10
|
+
|
|
11
|
+
- **status**: active
|
|
12
|
+
- **date**: 2026-07-29
|
|
13
|
+
|
|
14
|
+
## Decision
|
|
15
|
+
|
|
16
|
+
CLI binary is `tlc` with a `harness` subcommand group. Runtime home `~/.tlc/harness/`. Project source of
|
|
17
|
+
truth `.tlc/harness/config.json` (tracked) + `.tlc/harness/state/` (ignored). Repository is
|
|
18
|
+
`github.com/felipfr/tlc-agent-harness`; the predecessor keeps its own name.
|
|
19
|
+
|
|
20
|
+
> **The repository clause is superseded by [/decisions/ad-052.md](/decisions/ad-052.md)**: the project lives at
|
|
21
|
+
> `github.com/tech-leads-club/harness-toolkit`. Everything else in this decision — the binary, the runtime home and
|
|
22
|
+
> the project policy path — is unchanged, and AD-052 says why moving them was rejected.
|
|
23
|
+
|
|
24
|
+
## Why
|
|
25
|
+
|
|
26
|
+
`harness` is the primary binary of Harness.io CLI 3.0 (public beta) — a PATH-order collision that fails
|
|
27
|
+
silently. `.tlc/` namespaces the suite so future `.tlc/skills/` needs no new root dir. A Claude-only user
|
|
28
|
+
must not install into `~/.cursor/`.
|
|
29
|
+
|
|
30
|
+
## Applies to
|
|
31
|
+
|
|
32
|
+
- installers (`install.sh`, `install.ps1`)
|
|
33
|
+
- `bin/`
|
|
34
|
+
- all path resolution (`src/platform/paths.ts`)
|
|
35
|
+
- docs
|
|
36
|
+
- the harness-init skill
|
|
37
|
+
|
|
38
|
+
## Repo hygiene — `.specs/` stays local
|
|
39
|
+
|
|
40
|
+
`.specs/` holds working specs and task ledgers for whoever is building, which is not part of the product
|
|
41
|
+
tree and goes stale against the code the moment a task lands. The durable *why* lives in this decision
|
|
42
|
+
bundle instead, which is versioned and validated by `tools/check-docs-bundle.ts`.
|
|
43
|
+
|
|
44
|
+
`.gitignore` also dropped the stale blanket `.cursor/` and `state/` entries (predecessor layout) in favour
|
|
45
|
+
of `.tlc/harness/state/`, since under this decision the provider adapters (`.cursor/hooks.json`, the
|
|
46
|
+
`hooks` block of `.claude/settings.json`) and the project policy are meant to be tracked.
|
|
47
|
+
|
|
48
|
+
## See also
|
|
49
|
+
|
|
50
|
+
- [/decisions/ad-003.md](/decisions/ad-003.md) — no backward-compatible dual read of the old layout
|
|
51
|
+
- [/decisions/ad-006.md](/decisions/ad-006.md) — Windows path handling under the new layout
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Decision
|
|
3
|
+
title: "AD-003 — No backward compatibility"
|
|
4
|
+
description: "No dual-read of .cursor/harness*, no migrate command, no compatibility symlinks — single code path only."
|
|
5
|
+
tags: [decision, migration, layout]
|
|
6
|
+
timestamp: "2026-07-29"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# AD-003 — No backward compatibility
|
|
10
|
+
|
|
11
|
+
- **status**: active
|
|
12
|
+
- **date**: 2026-07-29
|
|
13
|
+
|
|
14
|
+
## Decision
|
|
15
|
+
|
|
16
|
+
No dual-read of `.cursor/harness*`, no migrate command, no compatibility symlinks. Single code path only.
|
|
17
|
+
|
|
18
|
+
## Why
|
|
19
|
+
|
|
20
|
+
Two repos, single operator, hand-adjusted. Compatibility layers would be permanent dead weight.
|
|
21
|
+
|
|
22
|
+
## Applies to
|
|
23
|
+
|
|
24
|
+
- path resolution
|
|
25
|
+
- config loading
|
|
26
|
+
- installers
|
|
27
|
+
|
|
28
|
+
## See also
|
|
29
|
+
|
|
30
|
+
- [/decisions/ad-002.md](/decisions/ad-002.md) — the new layout this decision refuses to bridge to the old one
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Decision
|
|
3
|
+
title: "AD-004 — Ports and adapters with an anti-corruption layer per provider"
|
|
4
|
+
description: "Core steering logic is provider-agnostic; each provider is an ACL adapter exposing detect, capabilities, toEvent, render, and core degrades on a declarative capability descriptor, never on provider identity."
|
|
5
|
+
tags: [decision, architecture, providers, core]
|
|
6
|
+
timestamp: "2026-07-29"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# AD-004 — Ports and adapters with an anti-corruption layer per provider
|
|
10
|
+
|
|
11
|
+
- **status**: active
|
|
12
|
+
- **date**: 2026-07-29
|
|
13
|
+
|
|
14
|
+
## Decision
|
|
15
|
+
|
|
16
|
+
Core steering logic is provider-agnostic and organized flat-by-aggregate under `core/`. Each provider is an
|
|
17
|
+
ACL adapter under `providers/<name>/` exposing `detect`, `capabilities`, `toEvent`, `render`. Core degrades
|
|
18
|
+
on a declarative `ProviderCapabilities` descriptor, never on provider identity. Enforced by a registry
|
|
19
|
+
contract test plus a lint asserting zero vendor identifiers inside `core/`.
|
|
20
|
+
|
|
21
|
+
## Why
|
|
22
|
+
|
|
23
|
+
The hook IO contract is the expensive coupling. A capability descriptor turns provider asymmetry into
|
|
24
|
+
data, which is what makes a hookless provider (all capabilities false → instruction rendering) a new file
|
|
25
|
+
rather than a refactor.
|
|
26
|
+
|
|
27
|
+
## Applies to
|
|
28
|
+
|
|
29
|
+
- `src/core/`
|
|
30
|
+
- `src/providers/`
|
|
31
|
+
- all entrypoint handlers
|
|
32
|
+
|
|
33
|
+
## See also
|
|
34
|
+
|
|
35
|
+
- [/architecture.md](/architecture.md) — the port shape in practice
|
|
36
|
+
- [/providers/index.md](/providers/index.md) — the two adapters that implement this port today
|
|
37
|
+
- [/decisions/ad-010.md](/decisions/ad-010.md) — where the shared vocabulary lives
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Decision
|
|
3
|
+
title: "AD-005 — Local test runner is the gate; CI matrix runs on every push"
|
|
4
|
+
description: "tlc harness test is the per-task gate; .github/workflows/ci.yml runs the ubuntu|macos|windows matrix on push and pull_request, free on standard runners because the repository is public."
|
|
5
|
+
tags: [decision, ci, gate, testing]
|
|
6
|
+
timestamp: "2026-07-29"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# AD-005 — Local test runner is the gate; CI matrix runs on every push
|
|
10
|
+
|
|
11
|
+
- **status**: active
|
|
12
|
+
- **date**: 2026-07-29
|
|
13
|
+
|
|
14
|
+
## Decision
|
|
15
|
+
|
|
16
|
+
`tlc harness test` runs the full suite (golden-file wire formats, registry contract, core-purity lint,
|
|
17
|
+
concurrency, platform units) and is the per-task gate. `.github/workflows/ci.yml` carries the
|
|
18
|
+
`ubuntu|macos|windows` matrix on `push` to main, on `pull_request`, and on demand.
|
|
19
|
+
|
|
20
|
+
## Why
|
|
21
|
+
|
|
22
|
+
A task cannot be called done on inspection, so the gate must be runnable locally and must be the
|
|
23
|
+
per-task gate. CI is the second, independent pass, and the only place the Windows leg runs.
|
|
24
|
+
|
|
25
|
+
Standard GitHub-hosted runners are free and unlimited on public repositories, across Linux, macOS and
|
|
26
|
+
Windows. Larger runners bill even there, so the matrix stays on standard ones.
|
|
27
|
+
|
|
28
|
+
## Applies to
|
|
29
|
+
|
|
30
|
+
- `.github/workflows/ci.yml`
|
|
31
|
+
- `bin/tlc-cli.ts`
|
|
32
|
+
- every task's definition of done
|
|
33
|
+
|
|
34
|
+
## See also
|
|
35
|
+
|
|
36
|
+
- [/decisions/ad-008.md](/decisions/ad-008.md) — what the local gate actually runs
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Decision
|
|
3
|
+
title: "AD-006 — Windows ships in scope, with CI covering the suite and the build"
|
|
4
|
+
description: "Windows path resolution, exec-form hooks and the .cmd shim are in scope; the CI windows-latest leg runs the suite and the dist build; install.ps1 and the editor end sit outside CI coverage."
|
|
5
|
+
tags: [decision, windows, platform]
|
|
6
|
+
timestamp: "2026-07-29"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# AD-006 — Windows ships in scope, with CI covering the suite and the build
|
|
10
|
+
|
|
11
|
+
- **status**: active
|
|
12
|
+
- **date**: 2026-07-29
|
|
13
|
+
|
|
14
|
+
## Decision
|
|
15
|
+
|
|
16
|
+
Windows stays in scope for design and code (`os.homedir()` only, exec-form hooks, sanitized filenames,
|
|
17
|
+
atomic-write retry, junctions + `.cmd` shim). The CI `windows-latest` leg runs the suite and the `dist/`
|
|
18
|
+
build on every push. `install.ps1` and hooks firing inside an editor session sit outside that coverage
|
|
19
|
+
hardware.
|
|
20
|
+
|
|
21
|
+
## Why
|
|
22
|
+
|
|
23
|
+
The portable choice is the correct choice independently of Windows, so deferring would relocate work rather
|
|
24
|
+
than remove it — and would reopen every call site later. But the operator develops on WSL and cannot prove
|
|
25
|
+
Windows behavior; claiming validation would be dishonest.
|
|
26
|
+
|
|
27
|
+
## Amended 2026-08-03 — `install.sh` is covered on POSIX
|
|
28
|
+
|
|
29
|
+
"Installers sit outside coverage" was read as *no installer test at all*, and that gap shipped a broken
|
|
30
|
+
primary install path: `curl … | bash` aborted on `${BASH_SOURCE[0]}` under `set -u`, because a piped script has
|
|
31
|
+
no file on disk. The README's first command could not run on any platform, and nothing failed to say so.
|
|
32
|
+
|
|
33
|
+
`install.sh` now has a POSIX suite (`tools/__test__/install-sh.test.ts`) covering the piped invocation, the
|
|
34
|
+
checkout invocation, and a guard against reading `BASH_SOURCE` without a default. It skips on `win32`, so the
|
|
35
|
+
original decision is unchanged where it was actually about hardware: `install.ps1` and hooks firing inside an
|
|
36
|
+
editor session remain uncovered, because the operator still cannot prove them.
|
|
37
|
+
|
|
38
|
+
The distinction the first wording missed: *the shell installer is testable anywhere bash exists*, and only
|
|
39
|
+
the PowerShell installer and the editor end depend on Windows.
|
|
40
|
+
|
|
41
|
+
## Applies to
|
|
42
|
+
|
|
43
|
+
- `src/platform/`
|
|
44
|
+
- installers
|
|
45
|
+
- README
|
|
46
|
+
|
|
47
|
+
## See also
|
|
48
|
+
|
|
49
|
+
- `README.md` (repo root, outside this bundle) — states this caveat to users directly
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Decision
|
|
3
|
+
title: "AD-007 — Vendor check applies to core tests; absence checks do not"
|
|
4
|
+
description: "tools/check-boundaries.ts scans src/core/**/__test__/ for vendor identifiers but excludes __test__ from the HOME/.cursor absence checks, so core tests must use opaque provider names."
|
|
5
|
+
tags: [decision, boundaries, testing, core]
|
|
6
|
+
timestamp: "2026-07-29"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# AD-007 — Vendor check applies to core tests; absence checks do not
|
|
10
|
+
|
|
11
|
+
- **status**: active
|
|
12
|
+
- **date**: 2026-07-29
|
|
13
|
+
|
|
14
|
+
## Decision
|
|
15
|
+
|
|
16
|
+
`tools/check-boundaries.ts` scans `src/core/**/__test__/` for vendor identifiers, but excludes `__test__`
|
|
17
|
+
from the `process.env.HOME` and `.cursor/…` absence checks. Consequence: **core tests must use opaque
|
|
18
|
+
provider names** (`provider-a`, `provider-b`), never `cursor`/`claude`.
|
|
19
|
+
|
|
20
|
+
## Why
|
|
21
|
+
|
|
22
|
+
A test asserting a string is absent must contain that string as the literal it searches for — so excluding
|
|
23
|
+
tests is correct for absence checks. But a core test that *needs* the literal `cursor` or `claude` is itself
|
|
24
|
+
the smell the vendor rule exists to catch; writing core tests with opaque names is what proves core treats
|
|
25
|
+
a provider name as an opaque string. Excluding tests there would have made the strongest guarantee opt-in.
|
|
26
|
+
|
|
27
|
+
## Applies to
|
|
28
|
+
|
|
29
|
+
- `tools/check-boundaries.ts`
|
|
30
|
+
- every `src/core/**/__test__/` file
|
|
31
|
+
- design guarantees
|
|
32
|
+
|
|
33
|
+
## See also
|
|
34
|
+
|
|
35
|
+
- [/decisions/ad-004.md](/decisions/ad-004.md) — the boundary this check enforces
|
|
36
|
+
- [/decisions/ad-011.md](/decisions/ad-011.md) — real vendor literals the check caught
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Decision
|
|
3
|
+
title: "AD-008 — Biome + TypeScript in the gate; @types/node pinned to the declared floor"
|
|
4
|
+
description: "devDependencies add @biomejs/biome@2.5.6 and typescript@7.0.2; the gate becomes biome check && tsc --noEmit && node --test && check-boundaries, and @types/node is pinned to ^24, not the latest 26.x."
|
|
5
|
+
tags: [decision, gate, typescript, biome]
|
|
6
|
+
timestamp: "2026-07-29"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# AD-008 — Biome + TypeScript in the gate; `@types/node` pinned to the declared floor
|
|
10
|
+
|
|
11
|
+
- **status**: active
|
|
12
|
+
- **date**: 2026-07-29
|
|
13
|
+
|
|
14
|
+
## Decision
|
|
15
|
+
|
|
16
|
+
devDependencies `@biomejs/biome@2.5.6` (lint + format) and `typescript@7.0.2` (`tsc --noEmit`). Gate becomes
|
|
17
|
+
`biome check && tsc --noEmit && node --test && check-boundaries`. `@types/node` pinned to `^24`, **not** the
|
|
18
|
+
latest 26.x. Both configs are scoped to the new module trees; a later widening task covers the rest of the
|
|
19
|
+
tree.
|
|
20
|
+
|
|
21
|
+
## Why
|
|
22
|
+
|
|
23
|
+
The repo had no linter, no formatter, and no typechecker — `node --test`, esbuild, and Bun all strip types
|
|
24
|
+
without validating, so a "no TypeScript errors" criterion was unverifiable. This matters most where
|
|
25
|
+
`Decision` / `HarnessEvent` / `ProviderCapabilities` are discriminated unions that deliver nothing without
|
|
26
|
+
checking. Biome does **not** substitute for `tsc`: its type engine is deliberately independent of the
|
|
27
|
+
compiler and rule-scoped, which is fine for a lint rule and not for a build gate. `@types/node` is a
|
|
28
|
+
compatibility contract, not a tool — typing against Node 26 while `engines` declares `>=24` would let
|
|
29
|
+
Node-26-only APIs pass a gate that promises Node 24 support.
|
|
30
|
+
|
|
31
|
+
`noUncheckedIndexedAccess` stays on. It produces most of the diagnostic noise but catches exactly the class
|
|
32
|
+
of bug that appears when parsing vendor payloads (`match[1]`, `args[0]`, `parts[2]`) — the highest-value
|
|
33
|
+
flag for an adapter layer.
|
|
34
|
+
|
|
35
|
+
## Applies to
|
|
36
|
+
|
|
37
|
+
- `tsconfig.json`
|
|
38
|
+
- `biome.json`
|
|
39
|
+
- `package.json`
|
|
40
|
+
- `CONTRIBUTING.md`
|
|
41
|
+
- every task's gate
|
|
42
|
+
|
|
43
|
+
## Historical note — local Node below the declared floor
|
|
44
|
+
|
|
45
|
+
Earlier in the port, local `node` was **v22.22.3** while `engines.node` and the launcher required **24+**
|
|
46
|
+
(the launcher exits fatally below it), and `npm install` confirmed with `EBADENGINE`. Tests ran fine (type
|
|
47
|
+
stripping is unflagged since Node 22.18), but the harness itself could not run end-to-end locally until Node
|
|
48
|
+
was upgraded to 24 or 26. This was never a build blocker — only a blocker for a local end-to-end smoke of
|
|
49
|
+
the installed harness — and has since been resolved by upgrading the local Node runtime.
|
|
50
|
+
|
|
51
|
+
## See also
|
|
52
|
+
|
|
53
|
+
- [/decisions/ad-005.md](/decisions/ad-005.md) — the gate this decision defines the contents of
|
|
54
|
+
- `CONTRIBUTING.md` (repo root, outside this bundle) — the checks a contributor runs
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Decision
|
|
3
|
+
title: "AD-009 — Event kinds are provider-agnostic; capabilities are data, not flags"
|
|
4
|
+
description: "The event-kind union covers what any provider emits, askSupportedOn is a list of kinds rather than a pair of booleans, and EffortLevel has five levels."
|
|
5
|
+
tags: [decision, design, cursor, contracts]
|
|
6
|
+
timestamp: "2026-07-29"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# AD-009 — Event kinds are provider-agnostic; capabilities are data, not flags
|
|
10
|
+
|
|
11
|
+
- **status**: active
|
|
12
|
+
- **date**: 2026-07-29
|
|
13
|
+
|
|
14
|
+
## Decision
|
|
15
|
+
|
|
16
|
+
Seven corrections to the event-kind union and the capability descriptor, each one settling how provider
|
|
17
|
+
asymmetry is represented.
|
|
18
|
+
|
|
19
|
+
1. **18 event kinds, not 16.** Added `mcp.after` and `thought.after`. Both are wired to observability
|
|
20
|
+
already — returning `null` for them would silently drop signal that already exists. A kind exists when
|
|
21
|
+
*any* provider produces it; a provider that cannot is gated by its descriptor, not by the kind's absence.
|
|
22
|
+
2. **`askAtToolLevel` + `askAtShellLevel` → `askSupportedOn: HarnessEventKind[]`.** A boolean pair cannot
|
|
23
|
+
express Cursor's real behavior (`ask` on `beforeShellExecution` and `beforeMCPExecution`, but not
|
|
24
|
+
`preToolUse`). Data instead of flags; a hookless provider gets `[]`.
|
|
25
|
+
3. **`contextAtToolTime` → `contextAtToolBefore` + `contextAtToolAfter`.** The table listed `partial` for
|
|
26
|
+
Cursor — a non-boolean in a boolean field. The split is what `partial` meant: `postToolUse` carries
|
|
27
|
+
`additional_context`, `preToolUse` does not.
|
|
28
|
+
4. **`HarnessEvent.text?: string` added.** `response.after` ship-claim detection needs the response body,
|
|
29
|
+
and `raw` is adapter-only by contract. Also serves `prompt.submit` and `thought.after`.
|
|
30
|
+
5. **`EffortLevel` is five levels** — `low | medium | high | xhigh | max`, per Claude Code's documented
|
|
31
|
+
`effort.level`. A three-value type makes `isEffortLevel` reject `xhigh`/`max`, so `minEffort` would skip
|
|
32
|
+
silently on exactly the high-effort sessions it governs.
|
|
33
|
+
6. **`continue` under `enforcesHooks: false` keeps the `ADVISORY —` prefix.** Unenforced guidance must
|
|
34
|
+
announce itself.
|
|
35
|
+
7. **Provider registration is part of each provider's final task.** `providers/<name>/index.ts` composing
|
|
36
|
+
the pieces + one line in `provider.registry.ts`.
|
|
37
|
+
|
|
38
|
+
## Why
|
|
39
|
+
|
|
40
|
+
The tables were written from vendor docs without being executed. Building Cursor against them — with
|
|
41
|
+
goldens captured from the real pre-refactor handlers — surfaced what documentation review could not. Items
|
|
42
|
+
1, 3, and 5 are outright defects that would have shipped as silent signal loss; the rest are precision.
|
|
43
|
+
|
|
44
|
+
## Applies to
|
|
45
|
+
|
|
46
|
+
- `src/providers/provider.types.ts` / `src/contracts/`
|
|
47
|
+
- `provider.degrade.ts`
|
|
48
|
+
- `cursor.capabilities.ts`
|
|
49
|
+
- the Claude adapter
|
|
50
|
+
- `minEffort` / ship-claim detection
|
|
51
|
+
|
|
52
|
+
## Note — `ask` at `tool.before` is provider-specific
|
|
53
|
+
|
|
54
|
+
Cursor's `askSupportedOn` excludes `tool.before`, so its golden asserts `ask` never reaches the renderer
|
|
55
|
+
there. Claude's **includes** it (see [/providers/claude-code.md](/providers/claude-code.md)). That invariant
|
|
56
|
+
is Cursor's, not general — do not generalize it from reading only Cursor's tests.
|
|
57
|
+
|
|
58
|
+
## See also
|
|
59
|
+
|
|
60
|
+
- [/providers/cursor.md](/providers/cursor.md), [/providers/claude-code.md](/providers/claude-code.md)
|
|
61
|
+
- [/decisions/ad-010.md](/decisions/ad-010.md) — where these contract types now live
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Decision
|
|
3
|
+
title: "AD-010 — Shared vocabulary moves to src/contracts/"
|
|
4
|
+
description: "HarnessEvent, Decision, Rendered, ProviderCapabilities, EffortLevel, and ProviderWiring live in a new src/contracts/ module holding types and pure helpers only; core and providers both import from it, neither imports the other."
|
|
5
|
+
tags: [decision, architecture, contracts]
|
|
6
|
+
timestamp: "2026-07-29"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# AD-010 — Shared vocabulary moves to `src/contracts/`
|
|
10
|
+
|
|
11
|
+
- **status**: active
|
|
12
|
+
- **date**: 2026-07-29
|
|
13
|
+
|
|
14
|
+
## Decision
|
|
15
|
+
|
|
16
|
+
`HarnessEvent`, `HarnessEventKind`, `Decision`, `Rendered`, `ProviderCapabilities`, `EffortLevel`,
|
|
17
|
+
`ProviderWiring` move into a **`src/contracts/`** module holding types and pure helpers only — zero
|
|
18
|
+
behavior, zero IO. Both `core/` and `providers/` import from it; neither imports the other.
|
|
19
|
+
`check-boundaries` forbids `core ↔ providers` and additionally forbids
|
|
20
|
+
`contracts → {core, providers, platform, entrypoints}`.
|
|
21
|
+
|
|
22
|
+
## Why
|
|
23
|
+
|
|
24
|
+
The design's dependency diagram said core never imports providers, and separately that core returns
|
|
25
|
+
`Decision` values and that degradation consumes `HarnessEvent`. Those two statements are only compatible if
|
|
26
|
+
the vocabulary lives somewhere both can reach. It never said where.
|
|
27
|
+
|
|
28
|
+
## Why a third module rather than putting the types in `core/`
|
|
29
|
+
|
|
30
|
+
Classic hexagonal would place port types in the domain and have adapters depend inward, which also works. A
|
|
31
|
+
separate contracts module was chosen because it keeps the boundary rule symmetric and mechanically
|
|
32
|
+
checkable (neither side may import the other, full stop), makes the shared surface explicit and small
|
|
33
|
+
enough to review, and makes behavior coupling impossible rather than merely discouraged — an adapter cannot
|
|
34
|
+
reach a core service even by accident.
|
|
35
|
+
|
|
36
|
+
## Applies to
|
|
37
|
+
|
|
38
|
+
- `src/contracts/` (new)
|
|
39
|
+
- `src/providers/**` (imports)
|
|
40
|
+
- `tools/check-boundaries.ts`
|
|
41
|
+
|
|
42
|
+
## See also
|
|
43
|
+
|
|
44
|
+
- [/architecture.md](/architecture.md) — how contracts sits between core and providers
|
|
45
|
+
- [/decisions/ad-004.md](/decisions/ad-004.md) — the ports-and-adapters shape this vocabulary serves
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Decision
|
|
3
|
+
title: "AD-011 — Vendor-specific data belongs to the provider, not to core"
|
|
4
|
+
description: "Model catalogs, cost pool names, and lessons rendering all move out of core and into each provider — four corrections surfaced by the vendor-identifier check hitting real code."
|
|
5
|
+
tags: [decision, providers, core, policy, pricing]
|
|
6
|
+
timestamp: "2026-07-29"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# AD-011 — Vendor-specific data belongs to the provider, not to core
|
|
10
|
+
|
|
11
|
+
- **status**: active
|
|
12
|
+
- **date**: 2026-07-29
|
|
13
|
+
|
|
14
|
+
## Decision
|
|
15
|
+
|
|
16
|
+
Four corrections, all surfaced by the vendor check hitting real code.
|
|
17
|
+
|
|
18
|
+
1. **Model catalogs move to the provider.** `ProviderPort` gains
|
|
19
|
+
`policyDefaults(): { allowedModels, blockedPatterns, minEffort }`. Cursor supplies its own model list and
|
|
20
|
+
`-fast` patterns; Claude supplies its own. `core/policy` keeps only the *shape* and provider-neutral
|
|
21
|
+
patterns (`-fast`, `/fast`). `core/subagent-policy` receives the effective list as an argument, exactly
|
|
22
|
+
as it already receives `ProviderCapabilities`. Resolution at the entrypoint: project/user config →
|
|
23
|
+
provider defaults → core generic defaults.
|
|
24
|
+
2. **Cost pool names in core become neutral**: `provider_native | other | auto | unknown`. Vendor-named
|
|
25
|
+
pools (e.g. a Cursor-specific catalog key) move to `platform/pricing.ts`, which is correctly outside the
|
|
26
|
+
vendor scan because pricing must name real vendors. `cost_source` is `"provider"`, not a vendor name.
|
|
27
|
+
3. **`FailureCategory` and `GateGap` get one home** — `core/gate/gate.types.ts`. `core/handoff` and
|
|
28
|
+
`core/lesson` import the types from there instead of each hand-copying them. Sibling type imports inside
|
|
29
|
+
`core/` are fine; only cross-*module* imports are barred.
|
|
30
|
+
4. **Provider-side lessons rendering is assigned.** `.tlc/harness/lessons.md` is the source of truth.
|
|
31
|
+
Rendering the provider-native view is each provider's own concern: Cursor's
|
|
32
|
+
`.cursor/rules/harness-lessons.mdc` and Claude's `@.tlc/harness/lessons.md` line in `CLAUDE.md` each
|
|
33
|
+
belong to that provider's wiring.
|
|
34
|
+
|
|
35
|
+
## Why
|
|
36
|
+
|
|
37
|
+
The vendor check did its job — it caught that a Cursor model catalog was sitting in what is supposed to be
|
|
38
|
+
provider-agnostic core. Two vendor literals were also passing only because `_` suppresses the `\b` word
|
|
39
|
+
boundary in the scan regex, i.e. tolerated by a regex accident rather than by design. Fixing them properly
|
|
40
|
+
puts vendor knowledge where the anti-corruption layer says it goes.
|
|
41
|
+
|
|
42
|
+
## Note
|
|
43
|
+
|
|
44
|
+
An early interim shipped `allowedModels: []`, which is functionally inert while `enforceAllowlist` defaults
|
|
45
|
+
`false` but becomes a footgun the moment a user enables it. Item 1 removes that footgun.
|
|
46
|
+
|
|
47
|
+
## Applies to
|
|
48
|
+
|
|
49
|
+
- `contracts/` (port shape)
|
|
50
|
+
- `providers/cursor/`, `providers/claude/`
|
|
51
|
+
- `core/policy`, `core/subagent-policy`
|
|
52
|
+
- `core/observability`, `core/gate`, `core/handoff`, `core/lesson`
|
|
53
|
+
- `platform/pricing.ts`
|
|
54
|
+
|
|
55
|
+
## See also
|
|
56
|
+
|
|
57
|
+
- [/measure.md](/measure.md) — the neutral cost pool names in the observability record shape
|
|
58
|
+
- [/providers/index.md](/providers/index.md) — `policyDefaults()` on each provider
|
|
59
|
+
- [/decisions/ad-007.md](/decisions/ad-007.md) — the check that caught this
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Decision
|
|
3
|
+
title: "AD-012 — Prefer Bun at runtime, keep dist/ for the Node fallback, ship no binary"
|
|
4
|
+
description: "tlc-exec tries Bun first, falling back to Node + dist/*.mjs; dist/ stays tracked since the installer is a git clone with no build step, and no compiled single-file binary ships."
|
|
5
|
+
tags: [decision, runtime, bun, performance]
|
|
6
|
+
timestamp: "2026-07-29"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# AD-012 — Prefer Bun at runtime, keep `dist/` for the Node fallback, ship no binary
|
|
10
|
+
|
|
11
|
+
- **status**: active
|
|
12
|
+
- **date**: 2026-07-29
|
|
13
|
+
|
|
14
|
+
## Decision
|
|
15
|
+
|
|
16
|
+
`tlc-exec` tries **Bun first** when it is on PATH, falling back to Node + `dist/*.mjs`. `dist/` stays
|
|
17
|
+
tracked (the installer is `git clone` with no build step). No compiled single-file binary.
|
|
18
|
+
|
|
19
|
+
## Measured
|
|
20
|
+
|
|
21
|
+
Same handler, 30 warm invocations each, on the development machine:
|
|
22
|
+
|
|
23
|
+
| Path | Per invocation |
|
|
24
|
+
| --- | --- |
|
|
25
|
+
| `bun run src/*.ts` | ~1 ms |
|
|
26
|
+
| `bun run dist/*.mjs` | ~1 ms |
|
|
27
|
+
| `node dist/*.mjs` | 27 ms |
|
|
28
|
+
| `node src/*.ts` | 63 ms |
|
|
29
|
+
|
|
30
|
+
## Why
|
|
31
|
+
|
|
32
|
+
Hooks are inner-loop — they fire on every tool call, shell command, read, and stop. At ~30 fires per agent
|
|
33
|
+
turn that is ~810 ms of pure process startup on Node versus ~30 ms on Bun. Output is byte-identical across
|
|
34
|
+
runtimes — verified on `guard-shell` (`{"permission":"allow"}`).
|
|
35
|
+
|
|
36
|
+
`dist/` still earns its keep only for the Node path, where it is 2.3× faster than stripping types at
|
|
37
|
+
runtime (27 ms vs 63 ms). Under Bun it buys nothing, which is why it is a fallback artifact rather than the
|
|
38
|
+
primary one.
|
|
39
|
+
|
|
40
|
+
No compiled binary because a Bun-compiled executable is 50–100 MB per platform and this repo both installs
|
|
41
|
+
by `git clone` and tracks its build output — four platform binaries in git history is untenable. The gain
|
|
42
|
+
over `bun run` is also ~zero at this scale; the published latency case for binaries is against a *cold Node*
|
|
43
|
+
baseline, which the Bun-first path already removes.
|
|
44
|
+
|
|
45
|
+
Using the harness never requires Bun — Bun is the fast path, Node stays the guaranteed path.
|
|
46
|
+
|
|
47
|
+
**Surface the fallback, never fall back silently.** A user on the 27× slower path must know they are on it
|
|
48
|
+
and how to leave it. `tlc harness doctor` and `tlc harness status` both report the resolved runtime with the
|
|
49
|
+
measured cost of the gap and the one-line fix (install Bun). Absent Bun is a `WARN`, not a `FAIL` — Node is
|
|
50
|
+
supported, just slower.
|
|
51
|
+
|
|
52
|
+
**Detection must not cost a spawn per hook.** `spawnSync(bun, ["--version"])` on every invocation would burn
|
|
53
|
+
more than the 26 ms it saves. Resolve by testing for the executable on `PATH` without executing it, and
|
|
54
|
+
cache the result in `state/` keyed by the resolved path; refresh on `doctor` / `status` / `update`.
|
|
55
|
+
|
|
56
|
+
## Context
|
|
57
|
+
|
|
58
|
+
Claude Code itself is TypeScript on Bun, shipped as a Bun-compiled binary; other CLI agent tools are
|
|
59
|
+
TS/Node or have migrated to a compiled systems language. TS-on-Bun is the incumbent pattern for this exact
|
|
60
|
+
problem domain.
|
|
61
|
+
|
|
62
|
+
## Applies to
|
|
63
|
+
|
|
64
|
+
- `bin/tlc-exec.mjs`
|
|
65
|
+
- `bin/tlc-build`
|
|
66
|
+
- [/architecture.md](/architecture.md)
|
|
67
|
+
|
|
68
|
+
## See also
|
|
69
|
+
|
|
70
|
+
- [/measure.md](/measure.md) — how `doctor`/`status` surface the resolved runtime
|
|
71
|
+
- [/init.md](/init.md) — Step 1b of the init skill, which presents this trade-off to the user
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Decision
|
|
3
|
+
title: "AD-013 — Documentation follows the Open Knowledge Format (OKF v0.1)"
|
|
4
|
+
description: "docs/ becomes an OKF v0.1 bundle — markdown with YAML frontmatter, index.md and log.md as reserved files, absolute bundle-relative cross-links, and a closed type vocabulary."
|
|
5
|
+
tags: [decision, docs, okf]
|
|
6
|
+
timestamp: "2026-07-29"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# AD-013 — Documentation follows the Open Knowledge Format (OKF v0.1)
|
|
10
|
+
|
|
11
|
+
- **status**: active
|
|
12
|
+
- **date**: 2026-07-29
|
|
13
|
+
|
|
14
|
+
## Decision
|
|
15
|
+
|
|
16
|
+
`docs/` becomes an OKF v0.1 bundle — markdown with YAML frontmatter, `index.md` and `log.md` as reserved
|
|
17
|
+
files, absolute bundle-relative cross-links.
|
|
18
|
+
|
|
19
|
+
## Layout
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
docs/
|
|
23
|
+
├── index.md okf_version: "0.1", entries from concept descriptions
|
|
24
|
+
├── log.md chronological, ISO 8601
|
|
25
|
+
├── architecture.md type: Concept
|
|
26
|
+
├── concepts.md type: Concept
|
|
27
|
+
├── diagnose.md type: Runbook
|
|
28
|
+
├── init.md type: Runbook
|
|
29
|
+
├── lessons.md type: Concept
|
|
30
|
+
├── measure.md type: Runbook
|
|
31
|
+
├── providers/{index,cursor,claude-code}.md type: Aggregate / Provider
|
|
32
|
+
└── decisions/{index,ad-001…ad-016}.md type: Aggregate / Decision
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## `type` vocabulary
|
|
36
|
+
|
|
37
|
+
Closed, small: `Concept`, `Runbook`, `Provider`, `Decision`, `Capability`, `Aggregate`. OKF has no central
|
|
38
|
+
registry, so the constraint is ours to keep.
|
|
39
|
+
|
|
40
|
+
## Frontmatter
|
|
41
|
+
|
|
42
|
+
`type` is the only field the spec requires. This bundle also always sets `title`, `description`, `tags`,
|
|
43
|
+
`timestamp`. Unknown keys must be preserved on round-trip; broken links are valid and mean unrealized
|
|
44
|
+
knowledge.
|
|
45
|
+
|
|
46
|
+
## Links
|
|
47
|
+
|
|
48
|
+
Absolute (`/decisions/ad-010.md`), not relative — they survive a document moving between subdirectories.
|
|
49
|
+
|
|
50
|
+
## Citations
|
|
51
|
+
|
|
52
|
+
Any doc asserting something from an external source carries a numbered `# Citations` section, per the spec.
|
|
53
|
+
|
|
54
|
+
## Why
|
|
55
|
+
|
|
56
|
+
This repo is a harness *for coding agents*; its own documentation should be machine-consumable by the
|
|
57
|
+
agents it steers. OKF is markdown with structured metadata, needs no SDK, no registry and no service, and
|
|
58
|
+
the spec explicitly targets keeping knowledge beside the code so an agent reads both from one clone.
|
|
59
|
+
|
|
60
|
+
## Why this also splits `STATE.md`
|
|
61
|
+
|
|
62
|
+
`AD-001`…`AD-016` were sixteen-plus sections in one growing file. As OKF concepts — one decision per file,
|
|
63
|
+
`type: Decision`, cross-linked — they become addressable: an agent asking "why does `contracts/` exist"
|
|
64
|
+
resolves `/decisions/ad-010.md` directly instead of scanning one long document.
|
|
65
|
+
|
|
66
|
+
## Note on the sibling repo
|
|
67
|
+
|
|
68
|
+
Some projects use OKF for **runtime knowledge** (an agent's own `knowledge/` namespace), not for repo
|
|
69
|
+
docs — its `docs/*.md` carry no frontmatter. Applying it to docs here is a different and additional use, not
|
|
70
|
+
a copy of that setup.
|
|
71
|
+
|
|
72
|
+
## Applies to
|
|
73
|
+
|
|
74
|
+
- `docs/**`
|
|
75
|
+
- `.specs/STATE.md` (split)
|
|
76
|
+
- `tools/check-docs-bundle.ts`
|
|
77
|
+
|
|
78
|
+
## See also
|
|
79
|
+
|
|
80
|
+
- [/index.md](/index.md), [/log.md](/log.md)
|
|
81
|
+
- [/decisions/index.md](/decisions/index.md)
|
|
82
|
+
|
|
83
|
+
# Citations
|
|
84
|
+
|
|
85
|
+
1. Open Knowledge Format annotated specification — https://okf.md/spec/
|
|
86
|
+
2. GitBook, *What is OKF? Understanding Google's Open Knowledge Format* — https://www.gitbook.com/blog/what-is-okf-open-knowledge-format
|
|
87
|
+
3. Reference implementation, `GoogleCloudPlatform/knowledge-catalog` (Apache 2.0)
|