@tech-leads-club/harness-toolkit 0.2.1 → 0.2.4
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/bin/tlc-cli.ts +20 -5
- package/dist/compact-before.mjs +86 -8
- package/dist/doctor.mjs +116 -10
- package/dist/init-project.mjs +13 -7
- package/dist/lessons-cli.mjs +86 -8
- package/dist/obs-cli.mjs +86 -8
- package/dist/prompt-submit.mjs +86 -8
- package/dist/response-after.mjs +86 -8
- package/dist/run.mjs +86 -8
- package/dist/session-end.mjs +86 -8
- package/dist/session-start.mjs +86 -8
- package/dist/shim.mjs +6976 -15
- package/dist/stop.mjs +86 -8
- package/dist/subagent-start.mjs +86 -8
- package/dist/subagent-stop.mjs +86 -8
- package/dist/support.mjs +86 -8
- package/dist/tlc-cli.mjs +106 -12
- package/dist/tool-after.mjs +86 -8
- package/dist/tool-before.mjs +86 -8
- package/dist/tool-failure.mjs +86 -8
- package/docs/log.md +5 -0
- package/package.json +3 -2
- package/src/core/core.facade.ts +11 -0
- package/src/core/index.ts +2 -0
- package/src/core/release/release.version.ts +147 -0
- package/src/core/shim/shim.precedence.ts +72 -0
- package/src/core/skill/skill.link.ts +81 -0
- package/src/entrypoints/shim.ts +41 -9
- package/src/platform/fs-atomic.ts +61 -23
- package/src/platform/paths.ts +27 -0
- package/tools/doctor.ts +50 -3
- package/tools/init-project.ts +27 -7
- package/CHANGELOG.md +0 -95
- package/docs/decisions/ad-001.md +0 -32
- package/docs/decisions/ad-002.md +0 -51
- package/docs/decisions/ad-003.md +0 -30
- package/docs/decisions/ad-004.md +0 -37
- package/docs/decisions/ad-005.md +0 -36
- package/docs/decisions/ad-006.md +0 -49
- package/docs/decisions/ad-007.md +0 -36
- package/docs/decisions/ad-008.md +0 -54
- package/docs/decisions/ad-009.md +0 -61
- package/docs/decisions/ad-010.md +0 -45
- package/docs/decisions/ad-011.md +0 -59
- package/docs/decisions/ad-012.md +0 -71
- package/docs/decisions/ad-013.md +0 -87
- package/docs/decisions/ad-014.md +0 -56
- package/docs/decisions/ad-015.md +0 -33
- package/docs/decisions/ad-016.md +0 -98
- package/docs/decisions/ad-017.md +0 -65
- package/docs/decisions/ad-018.md +0 -77
- package/docs/decisions/ad-019.md +0 -75
- package/docs/decisions/ad-020.md +0 -88
- package/docs/decisions/ad-021.md +0 -57
- package/docs/decisions/ad-022.md +0 -120
- package/docs/decisions/ad-023.md +0 -87
- package/docs/decisions/ad-024.md +0 -85
- package/docs/decisions/ad-025.md +0 -92
- package/docs/decisions/ad-026.md +0 -131
- package/docs/decisions/ad-027.md +0 -90
- package/docs/decisions/ad-028.md +0 -90
- package/docs/decisions/ad-029.md +0 -73
- package/docs/decisions/ad-030.md +0 -97
- package/docs/decisions/ad-031.md +0 -94
- package/docs/decisions/ad-032.md +0 -83
- package/docs/decisions/ad-033.md +0 -89
- package/docs/decisions/ad-034.md +0 -86
- package/docs/decisions/ad-035.md +0 -86
- package/docs/decisions/ad-036.md +0 -68
- package/docs/decisions/ad-037.md +0 -47
- package/docs/decisions/ad-038.md +0 -52
- package/docs/decisions/ad-039.md +0 -69
- package/docs/decisions/ad-040.md +0 -89
- package/docs/decisions/ad-041.md +0 -98
- package/docs/decisions/ad-042.md +0 -82
- package/docs/decisions/ad-043.md +0 -79
- package/docs/decisions/ad-044.md +0 -61
- package/docs/decisions/ad-045.md +0 -94
- package/docs/decisions/ad-046.md +0 -111
- package/docs/decisions/ad-047.md +0 -96
- package/docs/decisions/ad-048.md +0 -85
- package/docs/decisions/ad-049.md +0 -66
- package/docs/decisions/ad-050.md +0 -94
- package/docs/decisions/ad-051.md +0 -69
- package/docs/decisions/ad-052.md +0 -69
- package/docs/decisions/ad-053.md +0 -78
- package/docs/decisions/ad-054.md +0 -98
- package/docs/decisions/ad-055.md +0 -74
- package/docs/decisions/ad-056.md +0 -85
- package/docs/decisions/ad-057.md +0 -68
- package/docs/decisions/ad-058.md +0 -97
- package/docs/decisions/ad-059.md +0 -82
- package/docs/decisions/ad-060.md +0 -75
- package/docs/decisions/ad-061.md +0 -68
- package/docs/decisions/ad-062.md +0 -72
- package/docs/decisions/ad-063.md +0 -84
- package/docs/decisions/ad-064.md +0 -79
- package/docs/decisions/ad-065.md +0 -81
- package/docs/decisions/ad-066.md +0 -111
- package/docs/decisions/ad-067.md +0 -64
- package/docs/decisions/ad-068.md +0 -79
- package/docs/decisions/ad-069.md +0 -74
- package/docs/decisions/ad-070.md +0 -86
- package/docs/decisions/ad-071.md +0 -93
- package/docs/decisions/ad-072.md +0 -82
- package/docs/decisions/ad-073.md +0 -102
- package/docs/decisions/ad-074.md +0 -91
- package/docs/decisions/ad-075.md +0 -79
- package/docs/decisions/ad-076.md +0 -102
- package/docs/decisions/ad-077.md +0 -94
- package/docs/decisions/ad-078.md +0 -84
- package/docs/decisions/ad-079.md +0 -73
- package/docs/decisions/ad-080.md +0 -86
- package/docs/decisions/ad-081.md +0 -70
- package/docs/decisions/ad-082.md +0 -79
- package/docs/decisions/ad-083.md +0 -88
- package/docs/decisions/index.md +0 -111
- package/tools/test-env.mjs +0 -28
- package/tools/test-env.names.d.mts +0 -1
- package/tools/test-env.names.mjs +0 -14
package/CHANGELOG.md
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
Generated from `docs/decisions/` — do not edit by hand. Run `node tools/render-changelog.ts`.
|
|
4
|
-
|
|
5
|
-
Each entry is an architectural decision record: what changed, why, what was refused, and what it costs.
|
|
6
|
-
A **Needs your action** line is a change `tlc harness doctor` cannot detect for you; everything else
|
|
7
|
-
doctor reports against your own configuration.
|
|
8
|
-
|
|
9
|
-
## Unreleased
|
|
10
|
-
|
|
11
|
-
- **AD-001** — Optional parent-Fast sticky deny for Task spawns
|
|
12
|
-
- **AD-002** — Provider-neutral naming and layout
|
|
13
|
-
- **AD-003** — No backward compatibility
|
|
14
|
-
- **AD-004** — Ports and adapters with an anti-corruption layer per provider
|
|
15
|
-
- **AD-005** — Local test runner is the gate; CI matrix runs on every push
|
|
16
|
-
- **AD-006** — Windows ships in scope, with CI covering the suite and the build
|
|
17
|
-
- **AD-007** — Vendor check applies to core tests; absence checks do not
|
|
18
|
-
- **AD-008** — Biome + TypeScript in the gate; @types/node pinned to the declared floor
|
|
19
|
-
- **AD-009** — Event kinds are provider-agnostic; capabilities are data, not flags
|
|
20
|
-
- **AD-010** — Shared vocabulary moves to src/contracts/
|
|
21
|
-
- **AD-011** — Vendor-specific data belongs to the provider, not to core
|
|
22
|
-
- **AD-012** — Prefer Bun at runtime, keep dist/ for the Node fallback, ship no binary
|
|
23
|
-
- **AD-013** — Documentation follows the Open Knowledge Format (OKF v0.1)
|
|
24
|
-
- **AD-014** — Claude Code hook payload field paths, pinned
|
|
25
|
-
- **AD-015** — Wiring handler names are the entrypoint filenames
|
|
26
|
-
- **AD-016** — Field semantics, state writers, and the core export surface
|
|
27
|
-
- **AD-017** — The docs gate delegates to the project's tool, and the catalog is the only source of capability metadata
|
|
28
|
-
- **AD-018** — Three rails adopted from an external review, each off by default and declared rather than inferred
|
|
29
|
-
- **AD-019** — A resource is identified by what it resolves to, and a declared capability must be read where it matters
|
|
30
|
-
- **AD-020** — One resolution for the install path, one source for posture, and a config that only advertises what it reads
|
|
31
|
-
- **AD-021** — A gate command that never resolved is a config fault, and a recipe runner does not receive file arguments
|
|
32
|
-
- **AD-022** — The policy surface is a floor rule, detection sits behind interception, and the operator/agent line is structural
|
|
33
|
-
- **AD-023** — One finding per failure, and lesson relevance is recurrence rather than exposure
|
|
34
|
-
- **AD-024** — The gate tells the truth about its own environment, its own lock, and the cause of a failure
|
|
35
|
-
- **AD-025** — Posture governs surfacing only, and each posture has exactly one name
|
|
36
|
-
- **AD-026** — An interruption is worth what it costs: narrower asks, a deadline on questions, and a rate the operator can see
|
|
37
|
-
- **AD-027** — Evidence is ordered against the code, every rail's firing is recorded, and a checker can run with its rule off
|
|
38
|
-
- **Needs your action:** Re-run your verification after the last code change before citing the verdict — the ship gate now refuses evidence written before the code it certifies, so a claim that used to pass can block. Nothing in doctor can see this one; it shows up as a blocked stop.
|
|
39
|
-
- **AD-028** — A resolved failure is kept, a session attests to itself, and provider neutrality becomes a proof
|
|
40
|
-
- **AD-029** — A capability the init skill cannot correctly initialise is not shipped
|
|
41
|
-
- **AD-030** — Clearing a policy divergence is one operator command behind four independent locks, and a refusal never points an agent at a door the floor holds shut
|
|
42
|
-
- **AD-031** — The decisions are the changelog, a breaking change carries its own instruction, and looking never changes anything
|
|
43
|
-
- **AD-032** — A hook is healthy when it can run, not when a marker string is present
|
|
44
|
-
- **AD-033** — The gate says what it costs, appendFiles stops promising what it cannot deliver, and a dead capability leaves
|
|
45
|
-
- **AD-034** — A warning that fires on a healthy install is not a warning, and the author reads the operator's output before anyone else does
|
|
46
|
-
- **AD-035** — A lesson learned by reasoning can be written down, and the harness never learns where lessons come from
|
|
47
|
-
- **AD-036** — A lesson names what makes it true, and stops being injected when that is gone
|
|
48
|
-
- **AD-037** — A lesson can be true for a period, and an unparseable bound withholds it
|
|
49
|
-
- **AD-038** — Promotion counts distinct sessions, because one stuck session is one observation
|
|
50
|
-
- **AD-039** — A lesson is graded by the gate it was injected for, and unproven is not a passing reading
|
|
51
|
-
- **AD-040** — Three lesson tiers, and nothing crosses between products by itself
|
|
52
|
-
- **AD-041** — A member something reads and nothing writes fails the gate
|
|
53
|
-
- **AD-042** — The suite gets an empty runtime home, and one renderer renders a lesson
|
|
54
|
-
- **AD-043** — A standing rule is pinned, not ranked, and the budget says what it dropped
|
|
55
|
-
- **AD-044** — Only an injection a gate could grade can be unproven
|
|
56
|
-
- **AD-045** — A gate verdict is reused when the content hash of its inputs did not change
|
|
57
|
-
- **AD-046** — The runtime path is an artifact, and update never touches what it does not own
|
|
58
|
-
- **AD-047** — An instruction is not an affordance, and a refusal names the route that works
|
|
59
|
-
- **AD-048** — A broken updater cannot deliver its own fix, so the installer is the recovery route
|
|
60
|
-
- **AD-049** — An empty synced file says which of four reasons made it empty
|
|
61
|
-
- **AD-050** — Lesson transport is a provider capability, not an operator preference
|
|
62
|
-
- **AD-051** — A warning fails the gate, and a suppression states what breaks without it
|
|
63
|
-
- **AD-052** — The repository moves to the org and the runtime paths do not
|
|
64
|
-
- **AD-053** — The harness ships no model allowlist, and a list that names nothing enforces nothing
|
|
65
|
-
- **AD-054** — npm is the distribution, and a merged release PR is the only thing that publishes
|
|
66
|
-
- **AD-055** — The changelog is rendered from the decision records, and git says which release each landed in
|
|
67
|
-
- **AD-056** — The package delivers the runtime, and the runtime path stays where hooks already point
|
|
68
|
-
- **AD-057** — The release PR is a mechanism, not a gate, and the bot merges it
|
|
69
|
-
- **AD-058** — Gates diff against the turn's base, and language knowledge is one table
|
|
70
|
-
- **Needs your action:** If a project has the comment gate on, expect it to start firing on turns that commit and on languages it never covered. Nothing changes in your config.
|
|
71
|
-
- **AD-059** — A rail never writes a field it reads, and a counter reads the plane its events land on
|
|
72
|
-
- **AD-060** — The gate records the environment it ran under, and names it only once the cheap explanations are spent
|
|
73
|
-
- **AD-061** — A decision that refuses names its rule, and degrading preserves it
|
|
74
|
-
- **AD-062** — One command answers whether the harness did that, and says so plainly when it did not
|
|
75
|
-
- **AD-063** — One palette for human output, and a checker keeping it out of everything else
|
|
76
|
-
- **AD-064** — A snapshot is assigned, never accumulated, and a table does not list what it cannot count
|
|
77
|
-
- **AD-065** — The obs bus has a contract, and the gate checks both sides of it
|
|
78
|
-
- **AD-066** — Uninstall reads the artefact, and the plan is the confirmation
|
|
79
|
-
- **AD-067** — A reserved file that cannot be retired is rendered, and the gate holds it there
|
|
80
|
-
- **AD-068** — A directory decides what ships, and dist is derived from disk in both directions
|
|
81
|
-
- **AD-069** — A decision record declares its shape, and cites by link so a move cannot break it
|
|
82
|
-
- **AD-070** — A comment has to read for somebody who was not in the session
|
|
83
|
-
- **AD-071** — The turn's added lines are checked against the code the project already has
|
|
84
|
-
- **AD-072** — A record can leave the corpus, and removing is a change worth recording
|
|
85
|
-
- **AD-073** — A neighbour mid-gate is not a reason to block a turn
|
|
86
|
-
- **AD-074** — Code the gate cannot read is refused, and a credential is not always a file
|
|
87
|
-
- **AD-075** — A dependency a turn adds outlives the turn, so two mechanical failures are worth a stop
|
|
88
|
-
- **AD-076** — Goal-hijack enforcement needs the tool's output, and the host does not deliver it
|
|
89
|
-
- **AD-077** — A command that appears verbatim in fetched content is put to the operator
|
|
90
|
-
- **AD-078** — The two files the harness reads aloud are sealed on write and verified on injection
|
|
91
|
-
- **AD-079** — The coverage claim is a generated page with its gaps in it, not a badge in the README
|
|
92
|
-
- **AD-080** — Every hand-written list of our own rules is now checked, including the one that drifted while writing this
|
|
93
|
-
- **AD-081** — The manifest npm publishes is checked here, because the release runner was the only thing reading it
|
|
94
|
-
- **AD-082** — npm is the documented install, and the first version cannot come from CI
|
|
95
|
-
- **AD-083** — Going public switched the branch ruleset on, and an unattended release needs the App to bypass it
|
package/docs/decisions/ad-001.md
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
type: Decision
|
|
3
|
-
title: "AD-001 — Optional parent-Fast sticky deny for Task spawns"
|
|
4
|
-
description: "subagents.blockParentFast denies Task/subagent spawns while the sticky parent model is Fast, closing a gap where Cursor Fast appears on the parent but not on the Task model string."
|
|
5
|
-
tags: [decision, subagents, policy, cursor]
|
|
6
|
-
timestamp: "2026-07-27"
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# AD-001 — Optional parent-Fast sticky deny for Task spawns
|
|
10
|
-
|
|
11
|
-
- **status**: active
|
|
12
|
-
- **date**: 2026-07-27
|
|
13
|
-
|
|
14
|
-
## Decision
|
|
15
|
-
|
|
16
|
-
`subagents.blockParentFast` (default `false`) denies Task / `subagentStart` when sticky parent state is
|
|
17
|
-
Fast; bracket/`model_params` Fast is always treated as blocked in policy helpers.
|
|
18
|
-
|
|
19
|
-
## Why
|
|
20
|
-
|
|
21
|
-
Cursor Fast often appears on the parent (`*-fast` / `model_params.fast=true`) while Task `model` stays
|
|
22
|
-
allowlisted without `-fast`. Without this rail, a Fast parent chat can still spawn full-price subagents.
|
|
23
|
-
|
|
24
|
-
## Applies to
|
|
25
|
-
|
|
26
|
-
- agent-harness hooks
|
|
27
|
-
- harness-init capability 6b (see [/init.md](/init.md))
|
|
28
|
-
|
|
29
|
-
## See also
|
|
30
|
-
|
|
31
|
-
- [/decisions/ad-011.md](/decisions/ad-011.md) — provider model catalogs and blocked patterns
|
|
32
|
-
- [/decisions/ad-016.md](/decisions/ad-016.md) — item 2 fixes the missing writer for this state
|
package/docs/decisions/ad-002.md
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
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
|
package/docs/decisions/ad-003.md
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
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
|
package/docs/decisions/ad-004.md
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
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
|
package/docs/decisions/ad-005.md
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
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
|
package/docs/decisions/ad-006.md
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
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
|
package/docs/decisions/ad-007.md
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
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
|
package/docs/decisions/ad-008.md
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
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
|
package/docs/decisions/ad-009.md
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
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
|
package/docs/decisions/ad-010.md
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
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
|
package/docs/decisions/ad-011.md
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
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
|