@devtrace-ai/cli 1.0.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.
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.TestRunner = void 0;
37
+ const child_process_1 = require("child_process");
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const config_1 = require("./config");
41
+ class TestRunner {
42
+ /**
43
+ * Auto-detects the test runner and command based on project files.
44
+ */
45
+ static detectTestCommand(projectDir = process.cwd()) {
46
+ // 1. package.json check
47
+ const packageJsonPath = path.join(projectDir, 'package.json');
48
+ if (fs.existsSync(packageJsonPath)) {
49
+ try {
50
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
51
+ if (pkg.scripts && pkg.scripts.test) {
52
+ return 'npm test';
53
+ }
54
+ }
55
+ catch {
56
+ // Ignore JSON parse errors
57
+ }
58
+ }
59
+ // 2. Cargo.toml (Rust)
60
+ if (fs.existsSync(path.join(projectDir, 'Cargo.toml'))) {
61
+ return 'cargo test';
62
+ }
63
+ // 3. python check
64
+ if (fs.existsSync(path.join(projectDir, 'pytest.ini')) ||
65
+ fs.existsSync(path.join(projectDir, 'conftest.py'))) {
66
+ return 'pytest';
67
+ }
68
+ return null;
69
+ }
70
+ /**
71
+ * Runs the test suite synchronously, measures duration, and parses the console outputs.
72
+ */
73
+ static runTests(projectDir = process.cwd()) {
74
+ const config = config_1.ConfigManager.loadConfig(projectDir);
75
+ const command = config.testCommand || this.detectTestCommand(projectDir);
76
+ if (!command) {
77
+ return null;
78
+ }
79
+ const start = Date.now();
80
+ let exitCode = 0;
81
+ let raw = '';
82
+ try {
83
+ raw = (0, child_process_1.execSync)(command, { cwd: projectDir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
84
+ }
85
+ catch (error) {
86
+ exitCode = error.status !== undefined ? error.status : 1;
87
+ raw = (error.stdout || '') + '\n' + (error.stderr || '');
88
+ }
89
+ const duration = Date.now() - start;
90
+ const { passed, failed, skipped } = this.parseTestOutput(raw);
91
+ // Truncate raw logs to first 1000 characters to keep database light
92
+ const rawTruncated = raw.length > 1000 ? raw.substring(0, 997) + '...' : raw;
93
+ return {
94
+ command,
95
+ exitCode,
96
+ passed,
97
+ failed,
98
+ skipped,
99
+ duration,
100
+ raw: rawTruncated
101
+ };
102
+ }
103
+ /**
104
+ * Regex heuristics for parsing test runner outputs.
105
+ */
106
+ static parseTestOutput(output) {
107
+ let passed = 0;
108
+ let failed = 0;
109
+ let skipped = 0;
110
+ // Standard pattern matching for Jest / Vitest / Playwright / Mocha
111
+ // e.g. "Tests: 12 passed, 12 total"
112
+ const jestTestsRegex = /Tests:\s+(?=(?:\d+\s*failed|\d+\s*passed|\d+\s*skipped))(?:(\d+)\s*failed)?\s*,?\s*(?:(\d+)\s*passed)?\s*,?\s*(?:(\d+)\s*skipped)?/;
113
+ const jestMatch = output.match(jestTestsRegex);
114
+ if (jestMatch) {
115
+ failed = parseInt(jestMatch[1] || '0', 10);
116
+ passed = parseInt(jestMatch[2] || '0', 10);
117
+ skipped = parseInt(jestMatch[3] || '0', 10);
118
+ return { passed, failed, skipped };
119
+ }
120
+ // Pytest output: "2 passed, 1 warning in 0.12s" or "1 failed, 2 passed in 1.2s"
121
+ const pytestRegex = /(?=(?:\d+\s*failed|\d+\s*passed|\d+\s*skipped))(?:(\d+)\s*failed)?\s*,?\s*(?:(\d+)\s*passed)?\s*,?\s*(?:(\d+)\s*skipped)?\s*in\s*\d+\.\d+s/;
122
+ const pytestMatch = output.match(pytestRegex);
123
+ if (pytestMatch) {
124
+ failed = parseInt(pytestMatch[1] || '0', 10);
125
+ passed = parseInt(pytestMatch[2] || '0', 10);
126
+ skipped = parseInt(pytestMatch[3] || '0', 10);
127
+ return { passed, failed, skipped };
128
+ }
129
+ // Cargo output: "test result: ok. 4 passed; 0 failed; 0 ignored;"
130
+ const cargoRegex = /test result:.*(\d+)\s+passed;\s+(\d+)\s+failed;\s+(\d+)\s+ignored/;
131
+ const cargoMatch = output.match(cargoRegex);
132
+ if (cargoMatch) {
133
+ passed = parseInt(cargoMatch[1] || '0', 10);
134
+ failed = parseInt(cargoMatch[2] || '0', 10);
135
+ skipped = parseInt(cargoMatch[3] || '0', 10);
136
+ return { passed, failed, skipped };
137
+ }
138
+ // Fallback simple scanner
139
+ // Check if exitCode alone suggests success/failure
140
+ return { passed, failed, skipped };
141
+ }
142
+ }
143
+ exports.TestRunner = TestRunner;
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@devtrace-ai/cli",
3
+ "version": "1.0.0",
4
+ "description": "Measure how much of your AI-generated code survives 30 days — local-first survival telemetry for Claude Code, Cursor, and Copilot",
5
+ "license": "MIT",
6
+ "main": "dist/index.js",
7
+ "type": "commonjs",
8
+ "bin": {
9
+ "devtrace": "dist/index.js"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "!dist/tests",
14
+ "LICENSE",
15
+ "README.md"
16
+ ],
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "keywords": [
21
+ "ai",
22
+ "telemetry",
23
+ "code-survival",
24
+ "claude-code",
25
+ "cursor",
26
+ "copilot",
27
+ "developer-analytics",
28
+ "git",
29
+ "cli"
30
+ ],
31
+ "engines": {
32
+ "node": ">=18"
33
+ },
34
+ "scripts": {
35
+ "build": "tsc",
36
+ "postbuild": "node -e \"require('fs').chmodSync('dist/index.js', 0o755)\"",
37
+ "prepare": "npm run build",
38
+ "trace": "node dist/index.js",
39
+ "test": "node --test dist/tests/*.test.js"
40
+ },
41
+ "devDependencies": {
42
+ "@types/node": "^20.11.0",
43
+ "typescript": "^5.3.3"
44
+ },
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/davidgdb/DevTrace.git"
48
+ },
49
+ "homepage": "https://github.com/davidgdb/DevTrace",
50
+ "bugs": {
51
+ "url": "https://github.com/davidgdb/DevTrace/issues"
52
+ }
53
+ }