drtrace 0.1.0 → 0.3.0

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 (43) hide show
  1. package/README.md +29 -0
  2. package/agents/CONTRIBUTING.md +296 -0
  3. package/agents/README.md +174 -0
  4. package/agents/daemon-method-selection.md +298 -0
  5. package/agents/integration-guides/cpp-best-practices.md +218 -0
  6. package/agents/integration-guides/cpp-ros-integration.md +88 -0
  7. package/agents/log-analysis.md +217 -0
  8. package/agents/log-help.md +226 -0
  9. package/agents/log-init.md +933 -0
  10. package/agents/log-it.md +1126 -0
  11. package/bin/init.js +4 -4
  12. package/dist/bin/init.js +31 -0
  13. package/dist/config-schema.d.ts +2 -2
  14. package/dist/init.d.ts +49 -4
  15. package/dist/init.js +494 -35
  16. package/dist/resources/agents/CONTRIBUTING.md +296 -0
  17. package/dist/resources/agents/README.md +174 -0
  18. package/dist/resources/agents/daemon-method-selection.md +298 -0
  19. package/dist/resources/agents/integration-guides/cpp-best-practices.md +218 -0
  20. package/dist/resources/agents/integration-guides/cpp-ros-integration.md +88 -0
  21. package/dist/resources/agents/log-analysis.md +217 -0
  22. package/dist/resources/agents/log-help.md +226 -0
  23. package/dist/resources/agents/log-init.md +933 -0
  24. package/dist/resources/agents/log-it.md +1126 -0
  25. package/dist/resources/cpp/drtrace_sink.hpp +1248 -0
  26. package/package.json +9 -2
  27. package/.eslintrc.js +0 -20
  28. package/jest.config.js +0 -11
  29. package/src/client.ts +0 -68
  30. package/src/config-schema.ts +0 -115
  31. package/src/config.ts +0 -326
  32. package/src/index.ts +0 -3
  33. package/src/init.ts +0 -410
  34. package/src/logger.ts +0 -56
  35. package/src/queue.ts +0 -105
  36. package/src/transport.ts +0 -60
  37. package/src/types.ts +0 -20
  38. package/tests/client.test.ts +0 -66
  39. package/tests/config-schema.test.ts +0 -198
  40. package/tests/config.test.ts +0 -456
  41. package/tests/queue.test.ts +0 -72
  42. package/tests/transport.test.ts +0 -52
  43. package/tsconfig.json +0 -18
package/src/init.ts DELETED
@@ -1,410 +0,0 @@
1
- /**
2
- * Interactive project initialization for DrTrace (JavaScript/TypeScript)
3
- * Mirrors Python init_project.py with interactive prompts via prompts library
4
- */
5
-
6
- import * as fs from "fs";
7
- import * as path from "path";
8
- import prompts from "prompts";
9
- import { ConfigSchema, DrTraceConfig } from "./config-schema";
10
-
11
- export interface InitOptions {
12
- projectRoot?: string;
13
- nonInteractive?: boolean;
14
- config?: Partial<DrTraceConfig>;
15
- }
16
-
17
- export class ProjectInitializer {
18
- private projectRoot: string;
19
- private drtraceDir: string;
20
- private configPath: string;
21
-
22
- constructor(projectRoot: string = process.cwd()) {
23
- this.projectRoot = projectRoot;
24
- this.drtraceDir = path.join(projectRoot, "_drtrace");
25
- this.configPath = path.join(this.drtraceDir, "config.json");
26
- }
27
-
28
- /**
29
- * Run the interactive initialization workflow
30
- */
31
- async runInteractive(): Promise<boolean> {
32
- console.log("\nšŸš€ DrTrace Project Initialization\n");
33
- console.log("=".repeat(50));
34
-
35
- // Collect project information
36
- console.log("\nšŸ“‹ Project Information:");
37
- const projectName = await this.promptText(
38
- "Project name",
39
- "my-app"
40
- );
41
- const applicationId = await this.promptText(
42
- "Application ID",
43
- projectName.toLowerCase().replace(/\s+/g, "-")
44
- );
45
-
46
- // Language selection
47
- console.log("\nšŸ”§ Technology Stack:");
48
- const language = await this.promptChoice(
49
- "Select language/runtime:",
50
- ["python", "javascript", "both"],
51
- "javascript"
52
- );
53
-
54
- // Daemon configuration
55
- console.log("\nšŸ“” DrTrace Daemon Configuration:");
56
- const daemonUrl = await this.promptText(
57
- "Daemon URL",
58
- "http://localhost:8001"
59
- );
60
- const enabled = await this.promptYesNo("Enable DrTrace by default?", true);
61
-
62
- // Environment selection
63
- console.log("\nšŸŒ Environments:");
64
- const selectedEnvs = await this.promptMultiSelect(
65
- "Which environments to configure?",
66
- ["development", "staging", "production", "ci"]
67
- );
68
- const environments = selectedEnvs.length > 0 ? selectedEnvs : ["development"];
69
-
70
- // Agent configuration
71
- console.log("\nšŸ¤– Agent Integration (Optional):");
72
- const agentEnabled = await this.promptYesNo(
73
- "Enable agent interface?",
74
- false
75
- );
76
-
77
- let agentFramework = "bmad";
78
- if (agentEnabled) {
79
- agentFramework = await this.promptChoice(
80
- "Select agent framework:",
81
- ["bmad", "langchain", "other"],
82
- "bmad"
83
- );
84
- }
85
-
86
- // Check for existing config
87
- if (!this.handleExistingConfig()) {
88
- console.log("\nāŒ Initialization cancelled.");
89
- return false;
90
- }
91
-
92
- // Create directory structure
93
- this.createDirectoryStructure();
94
-
95
- // Generate and save main config
96
- const config = ConfigSchema.getDefaultConfig({
97
- project_name: projectName,
98
- application_id: applicationId,
99
- language: language as "python" | "javascript" | "both",
100
- daemon_url: daemonUrl,
101
- enabled,
102
- environments,
103
- agent_enabled: agentEnabled,
104
- agent_framework: agentFramework as "bmad" | "langchain" | "other",
105
- });
106
-
107
- ConfigSchema.save(config, this.configPath);
108
- console.log(`\nāœ“ Main config created: ${this.configPath}`);
109
-
110
- // Generate environment-specific configs
111
- this.generateEnvironmentConfigs(config);
112
-
113
- // Copy default agent spec
114
- if (agentEnabled) {
115
- this.copyAgentSpec();
116
- }
117
-
118
- // Generate .env.example
119
- this.generateEnvExample(config);
120
-
121
- // Generate README
122
- this.generateReadme();
123
-
124
- // Summary and next steps
125
- this.printSummary(config);
126
-
127
- return true;
128
- }
129
-
130
- /**
131
- * Prompt for text input
132
- */
133
- private async promptText(
134
- question: string,
135
- defaultValue?: string
136
- ): Promise<string> {
137
- const response = await prompts({
138
- type: "text",
139
- name: "value",
140
- message: question,
141
- initial: defaultValue,
142
- });
143
- return response.value || defaultValue || "";
144
- }
145
-
146
- /**
147
- * Prompt for yes/no
148
- */
149
- private async promptYesNo(
150
- question: string,
151
- defaultValue: boolean = true
152
- ): Promise<boolean> {
153
- const response = await prompts({
154
- type: "confirm",
155
- name: "value",
156
- message: question,
157
- initial: defaultValue,
158
- });
159
- return response.value;
160
- }
161
-
162
- /**
163
- * Prompt for single choice
164
- */
165
- private async promptChoice(
166
- question: string,
167
- choices: string[],
168
- defaultValue?: string
169
- ): Promise<string> {
170
- const response = await prompts({
171
- type: "select",
172
- name: "value",
173
- message: question,
174
- choices: choices.map((c) => ({ title: c, value: c })),
175
- initial: defaultValue ? choices.indexOf(defaultValue) : 0,
176
- });
177
- return response.value;
178
- }
179
-
180
- /**
181
- * Prompt for multiple selections
182
- */
183
- private async promptMultiSelect(
184
- question: string,
185
- choices: string[]
186
- ): Promise<string[]> {
187
- const response = await prompts({
188
- type: "multiselect",
189
- name: "value",
190
- message: question,
191
- choices: choices.map((c) => ({ title: c, value: c })),
192
- initial: 0, // Development selected by default
193
- });
194
- return response.value || [];
195
- }
196
-
197
- /**
198
- * Handle existing config
199
- */
200
- private handleExistingConfig(): boolean {
201
- if (!fs.existsSync(this.configPath)) {
202
- return true;
203
- }
204
-
205
- console.log(
206
- `\nāš ļø Configuration already exists at ${this.configPath}`
207
- );
208
- // In interactive mode, we would prompt here
209
- // For now, we'll require confirmation via prompts
210
- return true;
211
- }
212
-
213
- /**
214
- * Create _drtrace directory structure
215
- */
216
- private createDirectoryStructure(): void {
217
- const agentsDir = path.join(this.drtraceDir, "agents");
218
- fs.mkdirSync(agentsDir, { recursive: true });
219
- console.log(`āœ“ Created directory: ${this.drtraceDir}`);
220
- }
221
-
222
- /**
223
- * Generate environment-specific configs
224
- */
225
- private generateEnvironmentConfigs(baseConfig: DrTraceConfig): void {
226
- const environments = baseConfig.environments || ["development"];
227
-
228
- for (const env of environments) {
229
- const envConfig = { ...baseConfig };
230
- const envConfigPath = path.join(this.drtraceDir, `config.${env}.json`);
231
- ConfigSchema.save(envConfig, envConfigPath);
232
- console.log(`āœ“ Generated: ${envConfigPath}`);
233
- }
234
- }
235
-
236
- /**
237
- * Copy agent spec to _drtrace/agents/
238
- */
239
- private copyAgentSpec(): void {
240
- try {
241
- const agentPath = path.join(
242
- this.drtraceDir,
243
- "agents",
244
- "log-analysis.md"
245
- );
246
- const defaultSpec = this.getDefaultAgentSpec();
247
- fs.writeFileSync(agentPath, defaultSpec);
248
- console.log(`āœ“ Copied agent spec: ${agentPath}`);
249
- } catch (error) {
250
- console.warn(`āš ļø Could not copy agent spec: ${error}`);
251
- }
252
- }
253
-
254
- /**
255
- * Get default agent spec
256
- */
257
- private getDefaultAgentSpec(): string {
258
- return `# DrTrace Log Analysis Agent
259
-
260
- This is the default agent spec for log analysis.
261
-
262
- ## Purpose
263
-
264
- Analyze logs and provide root cause analysis for errors.
265
-
266
- ## Capabilities
267
-
268
- - Parse log entries and extract key information
269
- - Identify error patterns
270
- - Suggest remediation steps
271
-
272
- ## Configuration
273
-
274
- Environment-specific overrides can be set in config files.
275
- `;
276
- }
277
-
278
- /**
279
- * Generate .env.example file
280
- */
281
- private generateEnvExample(config: DrTraceConfig): void {
282
- const envFile = path.join(this.drtraceDir, ".env.example");
283
- const content = `# DrTrace Configuration - Copy to .env and customize
284
-
285
- # Basic Configuration
286
- DRTRACE_APPLICATION_ID=${config.application_id}
287
- DRTRACE_DAEMON_URL=${config.daemon_url || "http://localhost:8001"}
288
- DRTRACE_ENABLED=${config.enabled !== false ? "true" : "false"}
289
-
290
- # Environment-specific overrides
291
- # Uncomment and modify for your environment
292
- # DRTRACE_DAEMON_HOST=localhost
293
- # DRTRACE_DAEMON_PORT=8001
294
- # DRTRACE_RETENTION_DAYS=7
295
-
296
- # Agent configuration
297
- # DRTRACE_AGENT_ENABLED=false
298
- # DRTRACE_AGENT_FRAMEWORK=bmad
299
- `;
300
- fs.writeFileSync(envFile, content);
301
- console.log(`āœ“ Generated: ${envFile}`);
302
- }
303
-
304
- /**
305
- * Generate README.md
306
- */
307
- private generateReadme(): void {
308
- const readmeFile = path.join(this.drtraceDir, "README.md");
309
- const content = `# DrTrace Configuration Guide
310
-
311
- This directory contains configuration files for the DrTrace (Web Workflow Integration) system.
312
-
313
- ## Files
314
-
315
- - **config.json** - Main project configuration
316
- - **config.{environment}.json** - Environment-specific overrides
317
- - **.env.example** - Environment variable template
318
- - **agents/** - Agent specifications and custom rules
319
-
320
- ## Configuration
321
-
322
- ### Basic Setup
323
-
324
- 1. Review and customize \`config.json\`
325
- 2. For environment-specific settings, edit \`config.{environment}.json\`
326
- 3. Create \`.env\` from \`.env.example\` and set your environment variables
327
-
328
- ### Environment Variables
329
-
330
- - \`DRTRACE_APPLICATION_ID\` - Unique application identifier
331
- - \`DRTRACE_DAEMON_URL\` - URL of the DrTrace daemon
332
- - \`DRTRACE_ENABLED\` - Enable/disable DrTrace globally (true/false)
333
- - \`DRTRACE_RETENTION_DAYS\` - How long to retain logs (days)
334
-
335
- ### Environments
336
-
337
- Configure separate settings for:
338
- - **development** - Local development setup
339
- - **staging** - Pre-production testing
340
- - **production** - Live environment
341
- - **ci** - Continuous integration/testing
342
-
343
- ## Usage
344
-
345
- Load configuration based on your environment:
346
-
347
- \`\`\`javascript
348
- import { ConfigSchema } from 'drtrace';
349
- const config = ConfigSchema.load('./_drtrace/config.json');
350
- \`\`\`
351
-
352
- ## Further Reading
353
-
354
- - See \`docs/\` for complete documentation
355
- - Check \`agents/\` for agent specifications
356
- `;
357
- fs.writeFileSync(readmeFile, content);
358
- console.log(`āœ“ Generated: ${readmeFile}`);
359
- }
360
-
361
- /**
362
- * Print initialization summary
363
- */
364
- private printSummary(config: DrTraceConfig): void {
365
- console.log("\n" + "=".repeat(50));
366
- console.log("āœ… Project Initialization Complete!\n");
367
-
368
- console.log(`šŸ“ Configuration Location: ${this.drtraceDir}\n`);
369
-
370
- console.log("šŸ“‹ Generated Files:");
371
- console.log(` • ${this.configPath}`);
372
- for (const env of config.environments || []) {
373
- console.log(` • ${path.join(this.drtraceDir, `config.${env}.json`)}`);
374
- }
375
- console.log(` • ${path.join(this.drtraceDir, ".env.example")}`);
376
- console.log(` • ${path.join(this.drtraceDir, "README.md")}`);
377
-
378
- if (config.agent?.enabled) {
379
- console.log(
380
- ` • ${path.join(this.drtraceDir, "agents", "log-analysis.md")}`
381
- );
382
- }
383
-
384
- console.log("\nšŸ“– Next Steps:");
385
- console.log(` 1. Review ${this.configPath}`);
386
- console.log(` 2. Create .env: cp ${path.join(this.drtraceDir, ".env.example")} .env`);
387
- console.log(` 3. Start the daemon: drtrace daemon start`);
388
- console.log(
389
- ` 4. Read ${path.join(this.drtraceDir, "README.md")} for more details`
390
- );
391
-
392
- console.log("\n" + "=".repeat(50) + "\n");
393
- }
394
- }
395
-
396
- /**
397
- * Entry point for init command
398
- */
399
- export async function runInitProject(
400
- projectRoot?: string
401
- ): Promise<number> {
402
- try {
403
- const initializer = new ProjectInitializer(projectRoot);
404
- const success = await initializer.runInteractive();
405
- return success ? 0 : 1;
406
- } catch (error) {
407
- console.error(`\nāŒ Error during initialization:`, error);
408
- return 1;
409
- }
410
- }
package/src/logger.ts DELETED
@@ -1,56 +0,0 @@
1
- import type { LogEvent, LogLevel } from './types';
2
- import { LogQueue } from './queue';
3
-
4
- export class DrTraceLogger {
5
- private queue: LogQueue;
6
- private applicationId: string;
7
- private logLevel: LogLevel;
8
- private originalConsole?: { log: typeof console.log; error: typeof console.error };
9
-
10
- constructor(opts: { queue: LogQueue; applicationId: string; logLevel: LogLevel }) {
11
- this.queue = opts.queue;
12
- this.applicationId = opts.applicationId;
13
- this.logLevel = opts.logLevel || 'info';
14
- }
15
-
16
- attachToConsole(): void {
17
- if (this.originalConsole) return;
18
- this.originalConsole = { log: console.log, error: console.error };
19
-
20
- console.log = (...args: any[]) => {
21
- try {
22
- this.log('info', args.map(String).join(' '));
23
- } catch {
24
- // Silently ignore logging errors
25
- }
26
- this.originalConsole!.log.apply(console, args);
27
- };
28
-
29
- console.error = (...args: any[]) => {
30
- try {
31
- this.log('error', args.map(String).join(' '));
32
- } catch {
33
- // Silently ignore logging errors
34
- }
35
- this.originalConsole!.error.apply(console, args);
36
- };
37
- }
38
-
39
- detachFromConsole(): void {
40
- if (!this.originalConsole) return;
41
- console.log = this.originalConsole.log;
42
- console.error = this.originalConsole.error;
43
- this.originalConsole = undefined;
44
- }
45
-
46
- log(level: LogLevel, message: string, context?: Record<string, unknown>): void {
47
- const event: LogEvent = {
48
- timestamp: new Date().toISOString(),
49
- applicationId: this.applicationId,
50
- level,
51
- message,
52
- context,
53
- };
54
- this.queue.push(event);
55
- }
56
- }
package/src/queue.ts DELETED
@@ -1,105 +0,0 @@
1
- import type { LogEvent } from './types';
2
- import { Transport } from './transport';
3
-
4
- export class LogQueue {
5
- private buffer: LogEvent[] = [];
6
- private transport: Transport;
7
- private batchSize: number;
8
- private flushIntervalMs: number;
9
- private timer: NodeJS.Timeout | null = null;
10
- private running = false;
11
- private maxQueueSize: number;
12
- private exitHandlerBound = false;
13
- private exitHandler?: () => Promise<void>;
14
-
15
- constructor(opts: { transport: Transport; batchSize: number; flushIntervalMs: number; maxQueueSize?: number }) {
16
- this.transport = opts.transport;
17
- this.batchSize = Math.max(1, opts.batchSize);
18
- this.flushIntervalMs = Math.max(100, opts.flushIntervalMs);
19
- this.maxQueueSize = Math.max(1, opts.maxQueueSize ?? 10000);
20
- }
21
-
22
- start(): void {
23
- if (this.running) return;
24
- this.running = true;
25
- this.scheduleFlush();
26
- this.registerExitHandlers();
27
- }
28
-
29
- stop(): void {
30
- this.running = false;
31
- if (this.timer) {
32
- clearTimeout(this.timer);
33
- this.timer = null;
34
- }
35
- this.unregisterExitHandlers();
36
- }
37
-
38
- push(event: LogEvent): void {
39
- this.buffer.push(event);
40
- if (this.buffer.length > this.maxQueueSize) {
41
- // Drop oldest to enforce limit
42
- const dropCount = this.buffer.length - this.maxQueueSize;
43
- this.buffer.splice(0, dropCount);
44
- console.warn?.(`DrTrace: maxQueueSize exceeded, dropped ${dropCount} log(s)`);
45
- }
46
- if (this.buffer.length >= this.batchSize) {
47
- this.flush();
48
- }
49
- }
50
-
51
- async flush(): Promise<void> {
52
- if (this.buffer.length === 0) {
53
- this.scheduleFlush();
54
- return;
55
- }
56
- const toSend = this.buffer.splice(0, this.buffer.length);
57
- try {
58
- await this.transport.sendBatch(toSend);
59
- } finally {
60
- this.scheduleFlush();
61
- }
62
- }
63
-
64
- private scheduleFlush(): void {
65
- if (!this.running) return;
66
- if (this.timer) {
67
- clearTimeout(this.timer);
68
- this.timer = null;
69
- }
70
- this.timer = setTimeout(() => {
71
- this.flush();
72
- }, this.flushIntervalMs);
73
- this.timer.unref?.();
74
- }
75
-
76
- private registerExitHandlers(): void {
77
- if (this.exitHandlerBound) return;
78
- const handler = async () => {
79
- try {
80
- const flushPromise = this.flush();
81
- const timeout = new Promise((resolve) => {
82
- const to = setTimeout(resolve, 2000);
83
- (to as any).unref?.();
84
- });
85
- await Promise.race([flushPromise, timeout]);
86
- } catch {
87
- // ignore
88
- }
89
- };
90
- this.exitHandler = handler;
91
- process.on('SIGINT', handler);
92
- process.on('SIGTERM', handler);
93
- this.exitHandlerBound = true;
94
- }
95
-
96
- private unregisterExitHandlers(): void {
97
- if (!this.exitHandlerBound) return;
98
- if (this.exitHandler) {
99
- process.removeListener('SIGINT', this.exitHandler);
100
- process.removeListener('SIGTERM', this.exitHandler);
101
- this.exitHandler = undefined;
102
- }
103
- this.exitHandlerBound = false;
104
- }
105
- }
package/src/transport.ts DELETED
@@ -1,60 +0,0 @@
1
- import type { LogEvent } from './types';
2
-
3
- const DEFAULT_TIMEOUT_MS = 5000;
4
- const DEFAULT_MAX_RETRIES = 3;
5
- const BACKOFF_MS = [100, 200, 400];
6
- const USER_AGENT = 'drtrace-client-js/0.1.0';
7
-
8
- export class Transport {
9
- private daemonUrl: string;
10
- private timeoutMs: number;
11
- private maxRetries: number;
12
-
13
- constructor(opts: { daemonUrl: string; timeoutMs?: number; maxRetries?: number }) {
14
- this.daemonUrl = opts.daemonUrl.replace(/\/$/, '');
15
- this.timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
16
- this.maxRetries = Math.max(0, opts.maxRetries ?? DEFAULT_MAX_RETRIES);
17
- }
18
-
19
- async sendBatch(events: LogEvent[]): Promise<void> {
20
- if (events.length === 0) return;
21
- const url = `${this.daemonUrl}/logs/ingest`;
22
- const payload = { logs: events };
23
-
24
- const fetchImpl: typeof fetch | undefined = (globalThis as any).fetch;
25
- if (!fetchImpl) return; // Non-blocking skip
26
-
27
- for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
28
- const controller = new AbortController();
29
- const to = setTimeout(() => controller.abort(), this.timeoutMs);
30
- to.unref?.();
31
- try {
32
- const res = await fetchImpl(url, {
33
- method: 'POST',
34
- headers: {
35
- 'Content-Type': 'application/json',
36
- 'User-Agent': USER_AGENT,
37
- },
38
- body: JSON.stringify(payload),
39
- signal: controller.signal,
40
- });
41
- // Consume body to free resources
42
- res.body && (await res.text().catch(() => ''));
43
- // Treat non-2xx as retryable
44
- if (res.ok) return;
45
- } catch (_err) {
46
- // swallow and retry
47
- } finally {
48
- clearTimeout(to);
49
- }
50
-
51
- if (attempt < this.maxRetries) {
52
- const delay = BACKOFF_MS[Math.min(attempt, BACKOFF_MS.length - 1)];
53
- await new Promise((resolve) => {
54
- const to = setTimeout(resolve, delay);
55
- to.unref?.();
56
- });
57
- }
58
- }
59
- }
60
- }
package/src/types.ts DELETED
@@ -1,20 +0,0 @@
1
- export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
2
-
3
- export interface LogEvent {
4
- timestamp: string;
5
- applicationId: string;
6
- level: LogLevel;
7
- message: string;
8
- context?: Record<string, unknown>;
9
- }
10
-
11
- export interface ClientOptions {
12
- applicationId: string;
13
- daemonUrl: string;
14
- enabled?: boolean;
15
- logLevel?: LogLevel;
16
- batchSize?: number;
17
- flushIntervalMs?: number;
18
- maxRetries?: number;
19
- maxQueueSize?: number;
20
- }