mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-25 08:48:08 +02:00
feat(telemetry): anonymous posthog usage telemetry across node cli and python daemon (#205)
* feat: add telemetry phase 1
* feat: add node telemetry event catalog
* feat: add telemetry event helpers
* feat: emit setup and connection telemetry
* feat: emit connection and stack telemetry
* feat: emit ingest and scan telemetry
* feat: emit query telemetry
* feat: emit sampled mcp telemetry
* docs: expand telemetry event catalog
* feat: add telemetry schema sync artifact
* feat: pass telemetry project id to semantic daemon
* feat: add daemon telemetry foundation
* feat: emit semantic daemon telemetry
* feat: emit daemon lifecycle telemetry
* docs: document full telemetry event catalog
* feat(telemetry): dim first-run notice
* feat(telemetry): show first-run notice before command output
* feat(telemetry): wire ktx PostHog project for live ingestion
* docs(telemetry): drop posthog project name and host from storage section
* docs(telemetry): trim to general overview and disclaimer
* docs(agents): add short telemetry guidelines
* feat(telemetry): enable posthog geoip enrichment
* docs(telemetry): drop ip-geoip note from public overview
* refactor(telemetry): drop no-op groupIdentify, rely on capture groups field
* fix(telemetry): respect CI kill switch in python daemon identity
* fix(sql): route table-count analysis to existing analyze-batch endpoint
* fix(telemetry): emit install_first_run from notice path and derive flagsPresent from commander
* fix(telemetry): read package info via getKtxCliPackageInfo to satisfy boundary check
* fix(telemetry): make python identity env={} bypass os.environ and unset CI in tests
* fix(telemetry): unset CI kill switch in cli-program-telemetry tests
This commit is contained in:
parent
c87d14a554
commit
b0dd13ce7c
73 changed files with 6576 additions and 48 deletions
|
|
@ -3,7 +3,7 @@ import { tmpdir } from 'node:os';
|
|||
import { join } from 'node:path';
|
||||
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
||||
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { createLocalProjectMemoryIngest } from '../../context/memory/local-memory.js';
|
||||
import { detectCaptureSignals } from '../../context/memory/capture-signals.js';
|
||||
import type { MemoryAgentInput } from '../../context/memory/types.js';
|
||||
|
|
@ -47,6 +47,19 @@ function makeFakeServer() {
|
|||
};
|
||||
}
|
||||
|
||||
function makeIo() {
|
||||
let stderr = '';
|
||||
return {
|
||||
stdout: { isTTY: true, write() {} },
|
||||
stderr: {
|
||||
write(chunk: string) {
|
||||
stderr += chunk;
|
||||
},
|
||||
},
|
||||
stderrText: () => stderr,
|
||||
};
|
||||
}
|
||||
|
||||
function getTool(tools: RegisteredTool[], name: string): RegisteredTool {
|
||||
const found = tools.find((tool) => tool.name === name);
|
||||
if (!found) {
|
||||
|
|
@ -153,6 +166,11 @@ async function listToolsThroughSdk(contextTools: KtxMcpContextPorts) {
|
|||
}
|
||||
|
||||
describe('createKtxMcpServer', () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('registers annotations and output schemas for every retained tool', async () => {
|
||||
const fake = makeFakeServer();
|
||||
createKtxMcpServer({
|
||||
|
|
@ -227,6 +245,37 @@ describe('createKtxMcpServer', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('emits sampled debug telemetry for MCP tool requests', async () => {
|
||||
vi.spyOn(Math, 'random').mockReturnValue(0);
|
||||
vi.stubEnv('KTX_TELEMETRY_DEBUG', '1');
|
||||
vi.stubEnv('CI', '');
|
||||
const fake = makeFakeServer();
|
||||
const io = makeIo();
|
||||
const projectDir = '/tmp/ktx-mcp-telemetry';
|
||||
|
||||
createKtxMcpServer({
|
||||
server: fake.server,
|
||||
userContext: { userId: 'local-user' },
|
||||
projectDir,
|
||||
io,
|
||||
contextTools: {
|
||||
knowledge: {
|
||||
search: vi.fn<KtxKnowledgeMcpPort['search']>().mockResolvedValue({ results: [], totalFound: 0 }),
|
||||
read: vi.fn<KtxKnowledgeMcpPort['read']>().mockResolvedValue(null),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await expect(getTool(fake.tools, 'wiki_search').handler({ query: 'revenue recognition', limit: 5 })).resolves.toMatchObject({
|
||||
structuredContent: { results: [], totalFound: 0 },
|
||||
});
|
||||
|
||||
expect(io.stderrText()).toContain('"event":"mcp_request_completed"');
|
||||
expect(io.stderrText()).toContain('"toolName":"wiki_search"');
|
||||
expect(io.stderrText()).toContain('"sampleRate":0.1');
|
||||
expect(io.stderrText()).not.toContain(projectDir);
|
||||
});
|
||||
|
||||
it('registers parser-gated sql_execution when the host provides a SQL execution port', async () => {
|
||||
const fake = makeFakeServer();
|
||||
const response: KtxSqlExecutionResponse = {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue