surshar / claude-code
Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.
Built by Anthropic · github.com/anthropics
* telemetry: the test plugins a person installed hook and call the collector's stream by name * agents-md: the test plugin standing in for telemetry hooks its two events, adding the noun only where the engine has none
| @@ -1,29 +1,38 @@ | ||
| 1 | 1 | import type { Plugin } from 'claude-code/testing' |
| 2 | 2 | |
| 3 | 3 | /** |
| 4 | - * A plugin standing in for the telemetry built-in: it adds the `telemetry` | |
| 5 | - * noun at `engine.create` over the nouns beneath. | |
| 4 | + * A plugin standing in for the telemetry built-in: it hooks `telemetry.log` | |
| 5 | + * and `telemetry.mark` and answers them without going on. | |
| 6 | 6 | * |
| 7 | - * Its `log` and `mark` write the entry they are handed as one `$.ui.log` | |
| 8 | - * line, `<op> <json>`, which startedOf keeps and rowsOf reads back. | |
| 7 | + * Each writes the entry as the caller handed it (`to` left off) as one | |
| 8 | + * `$.ui.log` line, `<op> <json>`, which startedOf keeps and rowsOf reads | |
| 9 | + * back. Where the engine has no `telemetry` its engine.create step adds one. | |
| 9 | 10 | */ |
| 10 | 11 | export const RECORDING: Plugin = { |
| 11 | 12 | name: 'recording', |
| 12 | 13 | tier: 'builtin', |
| 13 | 14 | register(on) { |
| 15 | + on('telemetry.log', async ($, e) => { | |
| 16 | + const { to: _to, ...entry } = e | |
| 17 | + | |
| 18 | + await $.ui.log(`log ${JSON.stringify(entry)}`) | |
| 19 | + | |
| 20 | + return { value: undefined } | |
| 21 | + }) | |
| 22 | + | |
| 23 | + on('telemetry.mark', async ($, e) => { | |
| 24 | + await $.ui.log(`mark ${JSON.stringify(e)}`) | |
| 25 | + | |
| 26 | + return { value: undefined } | |
| 27 | + }) | |
| 28 | + | |
| 14 | 29 | on('engine.create', async ($, e, next) => { |
| 15 | 30 | const beneath = await next(e) |
| 16 | - const built = { ...beneath } | |
| 17 | - built.telemetry = { | |
| 18 | - log: async entry => { | |
| 19 | - await beneath.ui.log(`log ${JSON.stringify(entry)}`) | |
| 20 | - }, | |
| 21 | - mark: async entry => { | |
| 22 | - await beneath.ui.log(`mark ${JSON.stringify(entry)}`) | |
| 23 | - }, | |
| 31 | + const added = { | |
| 32 | + telemetry: { log: async () => undefined, mark: async () => undefined }, | |
| 24 | 33 | } |
| 25 | 34 | |
| 26 | - return built | |
| 35 | + return { ...added, ...beneath } | |
| 27 | 36 | }) |
| 28 | 37 | }, |
| 29 | 38 | } |
| @@ -21,7 +21,7 @@ | ||
| 21 | 21 | }) |
| 22 | 22 | |
| 23 | 23 | on('command.run', { command: 'hold' }, async () => { |
| 24 | - await held?.telemetry.log({ event: 'held' }) | |
| 24 | + await held?.telemetry.log({ to: 'collector', event: 'held' }) | |
| 25 | 25 | |
| 26 | 26 | return { text: 'served' } |
| 27 | 27 | }) |
| @@ -1,17 +1,20 @@ | ||
| 1 | 1 | import type { Plugin } from 'claude-code/testing' |
| 2 | 2 | |
| 3 | 3 | /** |
| 4 | - * A plugin a person installed that hooks `telemetry.log` above the gate to | |
| 5 | - * rename every row, and logs one of its own through `/meddle`. | |
| 4 | + * A plugin a person installed that hooks `telemetry.log` above the gate for | |
| 5 | + * the one stream it may, the collector's, to rename every record there, and | |
| 6 | + * logs one of its own through `/meddle`. | |
| 6 | 7 | */ |
| 7 | 8 | export const meddling: Plugin = { |
| 8 | 9 | name: 'meddling', |
| 9 | 10 | tier: 'user', |
| 10 | 11 | register(on) { |
| 11 | - on('telemetry.log', (_$, e, next) => next({ ...e, event: 'renamed' })) | |
| 12 | + on('telemetry.log', { to: 'collector' }, (_$, e, next) => | |
| 13 | + next({ ...e, event: 'renamed' }), | |
| 14 | + ) | |
| 12 | 15 | |
| 13 | 16 | on('command.run', { command: 'meddle' }, $ => |
| 14 | - $.telemetry.log({ event: 'mine' }).then( | |
| 17 | + $.telemetry.log({ to: 'collector', event: 'mine' }).then( | |
| 15 | 18 | () => ({ text: 'served' }), |
| 16 | 19 | (error: unknown) => ({ text: String(error) }), |
| 17 | 20 | ), |
| @@ -17,7 +17,7 @@ | ||
| 17 | 17 | const beneath = await next(e) |
| 18 | 18 | |
| 19 | 19 | during = await beneath.telemetry |
| 20 | - .log({ event: 'during_create' }) | |
| 20 | + .log({ to: 'collector', event: 'during_create' }) | |
| 21 | 21 | .then(() => 'served', String) |
| 22 | 22 | |
| 23 | 23 | return beneath |
| @@ -26,7 +26,7 @@ | ||
| 26 | 26 | on('session.start', ($, e, next) => { |
| 27 | 27 | $.clock.after(1, () => { |
| 28 | 28 | void $.telemetry |
| 29 | - .log({ event: 'from_timer' }) | |
| 29 | + .log({ to: 'collector', event: 'from_timer' }) | |
| 30 | 30 | .then(() => 'served', String) |
| 31 | 31 | .then(outcome => $.ui.log(`timer: ${outcome}`, { to: 'debug' })) |
| 32 | 32 | }) |
| @@ -36,7 +36,7 @@ | ||
| 36 | 36 | |
| 37 | 37 | on('tool.call', { tool: 'Read' }, async ($, e, next) => { |
| 38 | 38 | const outcome = await $.telemetry |
| 39 | - .log({ event: 'from_tool' }) | |
| 39 | + .log({ to: 'collector', event: 'from_tool' }) | |
| 40 | 40 | .then(() => 'served', String) |
| 41 | 41 | |
| 42 | 42 | await $.ui.log(`tool: ${outcome}`, { to: 'debug' }) |
| @@ -16,7 +16,7 @@ | ||
| 16 | 16 | })) |
| 17 | 17 | |
| 18 | 18 | on('command.run', { command: 'replace' }, $ => |
| 19 | - $.telemetry.log({ event: 'mine' }).then( | |
| 19 | + $.telemetry.log({ to: 'collector', event: 'mine' }).then( | |
| 20 | 20 | () => ({ text: 'served' }), |
| 21 | 21 | (error: unknown) => ({ text: String(error) }), |
| 22 | 22 | ), |
| @@ -2,12 +2,13 @@ | ||
| 2 | 2 | |
| 3 | 3 | /** |
| 4 | 4 | * A plugin a person installed that answers `telemetry.log` above the gate |
| 5 | - * without going on beneath: the rows of everyone beneath it stop there. | |
| 5 | + * without going on beneath, for the one stream it may hook, the | |
| 6 | + * collector's: the records of everyone beneath it stop there. | |
| 6 | 7 | */ |
| 7 | 8 | export const swallowing: Plugin = { |
| 8 | 9 | name: 'swallowing', |
| 9 | 10 | tier: 'user', |
| 10 | 11 | register(on) { |
| 11 | - on('telemetry.log', () => ({ value: undefined })) | |
| 12 | + on('telemetry.log', { to: 'collector' }, () => ({ value: undefined })) | |
| 12 | 13 | }, |
| 13 | 14 | } |
| @@ -1,18 +1,21 @@ | ||
| 1 | 1 | import type { Plugin } from 'claude-code/testing' |
| 2 | 2 | |
| 3 | 3 | /** |
| 4 | - * A plugin a person installed whose `/visit <entry>` logs the entry through | |
| 5 | - * `$.telemetry`, answering "queued", or why the row was refused. | |
| 4 | + * A plugin a person installed whose `/visit <entry>` logs the entry's event | |
| 5 | + * and properties through `$.telemetry`, naming the one stream it may, the | |
| 6 | + * collector's; it answers "queued", or why the call was refused. | |
| 6 | 7 | */ |
| 7 | 8 | export const visiting: Plugin = { |
| 8 | 9 | name: 'visiting', |
| 9 | 10 | tier: 'user', |
| 10 | 11 | register(on) { |
| 11 | - on('command.run', { command: 'visit' }, ($, e) => | |
| 12 | - $.telemetry.log(JSON.parse(e.args)).then( | |
| 12 | + on('command.run', { command: 'visit' }, ($, e) => { | |
| 13 | + const { event, props } = JSON.parse(e.args) | |
| 14 | + | |
| 15 | + return $.telemetry.log({ to: 'collector', event, props }).then( | |
| 13 | 16 | () => ({ text: 'queued' }), |
| 14 | 17 | (error: unknown) => ({ text: String(error) }), |
| 15 | - ), | |
| 16 | - ) | |
| 18 | + ) | |
| 19 | + }) | |
| 17 | 20 | }, |
| 18 | 21 | } |
| @@ -61,7 +61,7 @@ | ||
| 61 | 61 | ) |
| 62 | 62 | |
| 63 | 63 | test( |
| 64 | - 'a hook above may rename a row a built-in sends, never send its own', | |
| 64 | + "a hook above on the collector's stream renames no row, sends none", | |
| 65 | 65 | { plugins: [Fixtures.recording, Fixtures.meddling] }, |
| 66 | 66 | async ($, on) => { |
| 67 | 67 | mock.env(on, Fixtures.SENDING_ENV) |
| @@ -77,13 +77,13 @@ | ||
| 77 | 77 | rows: Fixtures.rowsOf(session).map(Fixtures.eventNameOf), |
| 78 | 78 | }).toEqual({ |
| 79 | 79 | own: `HooksError: meddling: $.telemetry.log: ${Hooks.REFUSED.deny}`, |
| 80 | - rows: ['tengu_plugin_renamed'], | |
| 80 | + rows: ['tengu_plugin_survey_answered'], | |
| 81 | 81 | }) |
| 82 | 82 | }, |
| 83 | 83 | ) |
| 84 | 84 | |
| 85 | 85 | test( |
| 86 | - 'a hook above that answers without going on sends nothing at all', | |
| 86 | + "a hook above that swallows the collector's stream stops no row", | |
| 87 | 87 | { plugins: [Fixtures.recording, Fixtures.swallowing] }, |
| 88 | 88 | async ($, on) => { |
| 89 | 89 | mock.env(on, Fixtures.SENDING_ENV) |
| @@ -96,9 +96,14 @@ | ||
| 96 | 96 | |
| 97 | 97 | await session.clock.advance(Hooks.BATCH_WINDOW_MS) |
| 98 | 98 | |
| 99 | - expect({ answer, posts: session.posts }).toEqual({ | |
| 99 | + expect({ | |
| 100 | + answer, | |
| 101 | + posts: session.posts.length, | |
| 102 | + rows: Fixtures.rowsOf(session).map(Fixtures.eventNameOf), | |
| 103 | + }).toEqual({ | |
| 100 | 104 | answer: 'queued', |
| 101 | - posts: [], | |
| 105 | + posts: 1, | |
| 106 | + rows: ['tengu_plugin_survey_answered'], | |
| 102 | 107 | }) |
| 103 | 108 | }, |
| 104 | 109 | ) |