@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.
Files changed (120) hide show
  1. package/bin/tlc-cli.ts +20 -5
  2. package/dist/compact-before.mjs +86 -8
  3. package/dist/doctor.mjs +116 -10
  4. package/dist/init-project.mjs +13 -7
  5. package/dist/lessons-cli.mjs +86 -8
  6. package/dist/obs-cli.mjs +86 -8
  7. package/dist/prompt-submit.mjs +86 -8
  8. package/dist/response-after.mjs +86 -8
  9. package/dist/run.mjs +86 -8
  10. package/dist/session-end.mjs +86 -8
  11. package/dist/session-start.mjs +86 -8
  12. package/dist/shim.mjs +6976 -15
  13. package/dist/stop.mjs +86 -8
  14. package/dist/subagent-start.mjs +86 -8
  15. package/dist/subagent-stop.mjs +86 -8
  16. package/dist/support.mjs +86 -8
  17. package/dist/tlc-cli.mjs +106 -12
  18. package/dist/tool-after.mjs +86 -8
  19. package/dist/tool-before.mjs +86 -8
  20. package/dist/tool-failure.mjs +86 -8
  21. package/docs/log.md +5 -0
  22. package/package.json +3 -2
  23. package/src/core/core.facade.ts +11 -0
  24. package/src/core/index.ts +2 -0
  25. package/src/core/release/release.version.ts +147 -0
  26. package/src/core/shim/shim.precedence.ts +72 -0
  27. package/src/core/skill/skill.link.ts +81 -0
  28. package/src/entrypoints/shim.ts +41 -9
  29. package/src/platform/fs-atomic.ts +61 -23
  30. package/src/platform/paths.ts +27 -0
  31. package/tools/doctor.ts +50 -3
  32. package/tools/init-project.ts +27 -7
  33. package/CHANGELOG.md +0 -95
  34. package/docs/decisions/ad-001.md +0 -32
  35. package/docs/decisions/ad-002.md +0 -51
  36. package/docs/decisions/ad-003.md +0 -30
  37. package/docs/decisions/ad-004.md +0 -37
  38. package/docs/decisions/ad-005.md +0 -36
  39. package/docs/decisions/ad-006.md +0 -49
  40. package/docs/decisions/ad-007.md +0 -36
  41. package/docs/decisions/ad-008.md +0 -54
  42. package/docs/decisions/ad-009.md +0 -61
  43. package/docs/decisions/ad-010.md +0 -45
  44. package/docs/decisions/ad-011.md +0 -59
  45. package/docs/decisions/ad-012.md +0 -71
  46. package/docs/decisions/ad-013.md +0 -87
  47. package/docs/decisions/ad-014.md +0 -56
  48. package/docs/decisions/ad-015.md +0 -33
  49. package/docs/decisions/ad-016.md +0 -98
  50. package/docs/decisions/ad-017.md +0 -65
  51. package/docs/decisions/ad-018.md +0 -77
  52. package/docs/decisions/ad-019.md +0 -75
  53. package/docs/decisions/ad-020.md +0 -88
  54. package/docs/decisions/ad-021.md +0 -57
  55. package/docs/decisions/ad-022.md +0 -120
  56. package/docs/decisions/ad-023.md +0 -87
  57. package/docs/decisions/ad-024.md +0 -85
  58. package/docs/decisions/ad-025.md +0 -92
  59. package/docs/decisions/ad-026.md +0 -131
  60. package/docs/decisions/ad-027.md +0 -90
  61. package/docs/decisions/ad-028.md +0 -90
  62. package/docs/decisions/ad-029.md +0 -73
  63. package/docs/decisions/ad-030.md +0 -97
  64. package/docs/decisions/ad-031.md +0 -94
  65. package/docs/decisions/ad-032.md +0 -83
  66. package/docs/decisions/ad-033.md +0 -89
  67. package/docs/decisions/ad-034.md +0 -86
  68. package/docs/decisions/ad-035.md +0 -86
  69. package/docs/decisions/ad-036.md +0 -68
  70. package/docs/decisions/ad-037.md +0 -47
  71. package/docs/decisions/ad-038.md +0 -52
  72. package/docs/decisions/ad-039.md +0 -69
  73. package/docs/decisions/ad-040.md +0 -89
  74. package/docs/decisions/ad-041.md +0 -98
  75. package/docs/decisions/ad-042.md +0 -82
  76. package/docs/decisions/ad-043.md +0 -79
  77. package/docs/decisions/ad-044.md +0 -61
  78. package/docs/decisions/ad-045.md +0 -94
  79. package/docs/decisions/ad-046.md +0 -111
  80. package/docs/decisions/ad-047.md +0 -96
  81. package/docs/decisions/ad-048.md +0 -85
  82. package/docs/decisions/ad-049.md +0 -66
  83. package/docs/decisions/ad-050.md +0 -94
  84. package/docs/decisions/ad-051.md +0 -69
  85. package/docs/decisions/ad-052.md +0 -69
  86. package/docs/decisions/ad-053.md +0 -78
  87. package/docs/decisions/ad-054.md +0 -98
  88. package/docs/decisions/ad-055.md +0 -74
  89. package/docs/decisions/ad-056.md +0 -85
  90. package/docs/decisions/ad-057.md +0 -68
  91. package/docs/decisions/ad-058.md +0 -97
  92. package/docs/decisions/ad-059.md +0 -82
  93. package/docs/decisions/ad-060.md +0 -75
  94. package/docs/decisions/ad-061.md +0 -68
  95. package/docs/decisions/ad-062.md +0 -72
  96. package/docs/decisions/ad-063.md +0 -84
  97. package/docs/decisions/ad-064.md +0 -79
  98. package/docs/decisions/ad-065.md +0 -81
  99. package/docs/decisions/ad-066.md +0 -111
  100. package/docs/decisions/ad-067.md +0 -64
  101. package/docs/decisions/ad-068.md +0 -79
  102. package/docs/decisions/ad-069.md +0 -74
  103. package/docs/decisions/ad-070.md +0 -86
  104. package/docs/decisions/ad-071.md +0 -93
  105. package/docs/decisions/ad-072.md +0 -82
  106. package/docs/decisions/ad-073.md +0 -102
  107. package/docs/decisions/ad-074.md +0 -91
  108. package/docs/decisions/ad-075.md +0 -79
  109. package/docs/decisions/ad-076.md +0 -102
  110. package/docs/decisions/ad-077.md +0 -94
  111. package/docs/decisions/ad-078.md +0 -84
  112. package/docs/decisions/ad-079.md +0 -73
  113. package/docs/decisions/ad-080.md +0 -86
  114. package/docs/decisions/ad-081.md +0 -70
  115. package/docs/decisions/ad-082.md +0 -79
  116. package/docs/decisions/ad-083.md +0 -88
  117. package/docs/decisions/index.md +0 -111
  118. package/tools/test-env.mjs +0 -28
  119. package/tools/test-env.names.d.mts +0 -1
  120. 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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