Skip to content

Commit b95b015

Browse files
committed
mods: a noun's types live in its own types folder and dependents read them from there
1 parent 748e0c4 commit b95b015

32 files changed

Lines changed: 204 additions & 248 deletions

File tree

‎mods/README.md‎

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,36 @@ crosses it. `$.ui.press({ plugin, key })` presses a `Button` the test
7676
rendered, as a click in the terminal does.
7777

7878
`tsc -p mods/tsconfig.json` typechecks every mod's hooks and tests against
79-
`types/`.
79+
`types/` and each mod's own `types/` contract.
80+
81+
## Composing mods: noun contracts
82+
83+
A mod that adds a noun to `$` in the `engine.create` fold owns that noun's
84+
types, and keeps them in one place: its `types/index.d.ts`, an ambient file
85+
with no imports that merges into `claude-code`, declaring the noun on
86+
`EngineInterface` and exporting the types it is made of, each named for the
87+
noun (`telemetry/types/index.d.ts` declares `$.telemetry` and exports
88+
`Telemetry`, `TelemetryLogEntry`, `TelemetryMarkEntry` and the rest).
89+
90+
- The contract is the only declaration of the noun. The mod's own hooks
91+
import its types from `claude-code` (`import type { Telemetry } from
92+
'claude-code'`), and the value its `engine.create` hook returns is checked
93+
against `EngineInterface['telemetry']`, so the implementation cannot drift
94+
from what callers read.
95+
- A mod that calls another's noun reads the same file and never copies it:
96+
`mods/tsconfig.json` includes `*/types/**/*.d.ts`, so `$.telemetry.log(…)`
97+
in `diff` types against `telemetry`'s contract as it stands.
98+
- A test of a mod that calls another's noun seats a provider for it, an inline
99+
plugin whose `engine.create` hook adds the noun, and answers the calls the
100+
way it answers the engine's: `on('telemetry.log', ($, e) => ({ value:
101+
undefined }))` runs above the provider's own method, its `e` typed by the
102+
contract. With no provider loaded the `$` build refuses the hook, naming the
103+
noun nobody provides.
104+
105+
A plugin outside this repository that depends on a mod's noun points its
106+
tsconfig `include` at that mod's `types/` folder for now; once the engine
107+
writes the contracts of the plugins a session has installed, `/plugin-types`
108+
will put them beside `claude-code.d.ts` and the include goes away.
80109

81110
Early access: hooks modules load only where function hooks are enabled, and
82111
the API these mods are written against may change between releases without

‎mods/diff/hooks/telemetry-noun.d.ts‎

Lines changed: 0 additions & 57 deletions
This file was deleted.

‎mods/telemetry/.claude-plugin/plugin.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@
44
"description": "Plugin analytics: adds $.telemetry in the engine.create fold, so a plugin logs an event or marks a feature's use as one first-party row per call, sent with the session's own credential.",
55
"author": {
66
"name": "Anthropic"
7-
}
7+
},
8+
"types": "types/index.d.ts"
89
}

‎mods/telemetry/README.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ string named together with the list it is chosen from), under `log` and
2828
the last two and refused on the first. An entry that breaks a rule is
2929
refused before anything is sent.
3030

31-
`hooks/register.ts` is the module; `hooks/telemetry-types/` is the noun's
32-
type as a caller sees it.
31+
`hooks/register.ts` is the module; `types/index.d.ts` is the noun's contract,
32+
the one declaration of `$.telemetry` that this mod's hooks, a mod calling the
33+
noun and a test answering it all read.
3334

3435
## What it hooks
3536

‎mods/telemetry/hooks/entries/checked-props/checked-props.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import type TelemetryTypes from '../../telemetry-types'
21
import { checkedValue } from '../checked-value'
32
import { isRecord } from '../is-record'
3+
import type { Method } from '../method'
44
import { PROP_LIMIT } from '../prop-limit'
55
import { refusal } from '../refusal'
66
import { TOKEN } from '../token'
@@ -15,7 +15,7 @@ import { TOKEN } from '../token'
1515
*/
1616
export function checkedProps(
1717
props: unknown,
18-
method: TelemetryTypes.Method,
18+
method: Method,
1919
): Record<string, string | number | boolean> {
2020
if (!isRecord(props)) {
2121
throw refusal('props: an object of properties by key', method)

‎mods/telemetry/hooks/entries/checked-value/checked-value.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import type TelemetryTypes from '../../telemetry-types'
21
import { CHOICE_TOKEN } from '../choice-token'
32
import { CHOICES_LIMIT } from '../choices-limit'
43
import { isRecord } from '../is-record'
4+
import type { Method } from '../method'
55
import { refusal } from '../refusal'
66

77
/**
@@ -19,7 +19,7 @@ import { refusal } from '../refusal'
1919
export function checkedValue(
2020
key: string,
2121
value: unknown,
22-
method: TelemetryTypes.Method = 'log',
22+
method: Method = 'log',
2323
): string | number | boolean {
2424
if (typeof value === 'boolean') {
2525
return value

‎mods/telemetry/hooks/entries/index.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export * from './is-record'
1616
export * from './mark'
1717
export * from './mark-fields-of'
1818
export * from './mark-kinds'
19+
export * from './method'
1920
export * from './prop-limit'
2021
export * from './refusal'
2122
export * from './row-session'
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import type TelemetryTypes from '../../telemetry-types'
1+
import type { TelemetryMarkKind } from 'claude-code'
2+
23
import { MARK_KINDS } from '../mark-kinds'
34

45
/**
@@ -7,5 +8,5 @@ import { MARK_KINDS } from '../mark-kinds'
78
* @param value what the caller passed as `kind`
89
* @returns whether value names one of the three mark kinds
910
*/
10-
export const isMarkKind = (value: unknown): value is TelemetryTypes.MarkKind =>
11+
export const isMarkKind = (value: unknown): value is TelemetryMarkKind =>
1112
MARK_KINDS.some(kind => kind === value)
Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
1-
import type TelemetryTypes from '../../telemetry-types'
1+
import type { TelemetryMarkKind } from 'claude-code'
22

33
/**
44
* The three kinds a mark may be, in the order the feature events name them.
55
*/
6-
export const MARK_KINDS: readonly TelemetryTypes.MarkKind[] = [
7-
'ok',
8-
'sad',
9-
'bad',
10-
]
6+
export const MARK_KINDS: readonly TelemetryMarkKind[] = ['ok', 'sad', 'bad']

‎mods/telemetry/hooks/entries/mark/mark.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
import type TelemetryTypes from '../../telemetry-types'
1+
import type { TelemetryMarkKind } from 'claude-code'
2+
23
import type { Fields } from '../fields'
34

45
/**
56
* One mark past every check: the feature, how it went, why when not ok,
67
* and its properties as they go into the row.
78
*/
89
export type Mark = {
9-
kind: TelemetryTypes.MarkKind
10+
kind: TelemetryMarkKind
1011
feature: string
1112
reason?: string
1213
props: Fields['props']

0 commit comments

Comments
 (0)