@aiready/core 0.9.37 → 0.9.38

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,410 @@
1
+ // src/types/language.ts
2
+ var Language = /* @__PURE__ */ ((Language2) => {
3
+ Language2["TypeScript"] = "typescript";
4
+ Language2["JavaScript"] = "javascript";
5
+ Language2["Python"] = "python";
6
+ Language2["Java"] = "java";
7
+ Language2["Go"] = "go";
8
+ Language2["Rust"] = "rust";
9
+ Language2["CSharp"] = "csharp";
10
+ return Language2;
11
+ })(Language || {});
12
+ var LANGUAGE_EXTENSIONS = {
13
+ ".ts": "typescript" /* TypeScript */,
14
+ ".tsx": "typescript" /* TypeScript */,
15
+ ".js": "javascript" /* JavaScript */,
16
+ ".jsx": "javascript" /* JavaScript */,
17
+ ".py": "python" /* Python */,
18
+ ".java": "java" /* Java */,
19
+ ".go": "go" /* Go */,
20
+ ".rs": "rust" /* Rust */,
21
+ ".cs": "csharp" /* CSharp */
22
+ };
23
+ var ParseError = class extends Error {
24
+ constructor(message, filePath, loc) {
25
+ super(message);
26
+ this.filePath = filePath;
27
+ this.loc = loc;
28
+ this.name = "ParseError";
29
+ }
30
+ };
31
+
32
+ // src/scoring.ts
33
+ var DEFAULT_TOOL_WEIGHTS = {
34
+ "pattern-detect": 22,
35
+ "context-analyzer": 19,
36
+ consistency: 14,
37
+ "ai-signal-clarity": 11,
38
+ "agent-grounding": 10,
39
+ testability: 10,
40
+ "doc-drift": 8,
41
+ deps: 6
42
+ };
43
+ var TOOL_NAME_MAP = {
44
+ patterns: "pattern-detect",
45
+ context: "context-analyzer",
46
+ consistency: "consistency",
47
+ "AI signal clarity": "ai-signal-clarity",
48
+ "ai-signal-clarity": "ai-signal-clarity",
49
+ grounding: "agent-grounding",
50
+ "agent-grounding": "agent-grounding",
51
+ testability: "testability",
52
+ tests: "testability",
53
+ "doc-drift": "doc-drift",
54
+ docs: "doc-drift",
55
+ deps: "deps"
56
+ };
57
+ var CONTEXT_TIER_THRESHOLDS = {
58
+ compact: { idealTokens: 3e3, criticalTokens: 1e4, idealDepth: 4 },
59
+ standard: { idealTokens: 5e3, criticalTokens: 15e3, idealDepth: 5 },
60
+ extended: { idealTokens: 15e3, criticalTokens: 5e4, idealDepth: 7 },
61
+ frontier: { idealTokens: 5e4, criticalTokens: 15e4, idealDepth: 10 }
62
+ };
63
+ var SIZE_ADJUSTED_THRESHOLDS = {
64
+ xs: 80,
65
+ // < 50 files
66
+ small: 75,
67
+ // 50-200 files
68
+ medium: 70,
69
+ // 200-500 files
70
+ large: 65,
71
+ // 500-2000 files
72
+ enterprise: 58
73
+ // 2000+ files
74
+ };
75
+ function getProjectSizeTier(fileCount) {
76
+ if (fileCount < 50) return "xs";
77
+ if (fileCount < 200) return "small";
78
+ if (fileCount < 500) return "medium";
79
+ if (fileCount < 2e3) return "large";
80
+ return "enterprise";
81
+ }
82
+ function getRecommendedThreshold(fileCount, modelTier = "standard") {
83
+ const sizeTier = getProjectSizeTier(fileCount);
84
+ const base = SIZE_ADJUSTED_THRESHOLDS[sizeTier];
85
+ const modelBonus = modelTier === "frontier" ? -3 : modelTier === "extended" ? -2 : 0;
86
+ return base + modelBonus;
87
+ }
88
+ function normalizeToolName(shortName) {
89
+ return TOOL_NAME_MAP[shortName] || shortName;
90
+ }
91
+ function getToolWeight(toolName, toolConfig, cliOverride) {
92
+ if (cliOverride !== void 0) {
93
+ return cliOverride;
94
+ }
95
+ if (toolConfig?.scoreWeight !== void 0) {
96
+ return toolConfig.scoreWeight;
97
+ }
98
+ return DEFAULT_TOOL_WEIGHTS[toolName] || 5;
99
+ }
100
+ function parseWeightString(weightStr) {
101
+ const weights = /* @__PURE__ */ new Map();
102
+ if (!weightStr) {
103
+ return weights;
104
+ }
105
+ const pairs = weightStr.split(",");
106
+ for (const pair of pairs) {
107
+ const [toolShortName, weightStr2] = pair.split(":");
108
+ if (toolShortName && weightStr2) {
109
+ const toolName = normalizeToolName(toolShortName.trim());
110
+ const weight = parseInt(weightStr2.trim(), 10);
111
+ if (!isNaN(weight) && weight > 0) {
112
+ weights.set(toolName, weight);
113
+ }
114
+ }
115
+ }
116
+ return weights;
117
+ }
118
+ function calculateOverallScore(toolOutputs, config, cliWeights) {
119
+ if (toolOutputs.size === 0) {
120
+ throw new Error("No tool outputs provided for scoring");
121
+ }
122
+ const weights = /* @__PURE__ */ new Map();
123
+ for (const [toolName] of toolOutputs.entries()) {
124
+ const cliWeight = cliWeights?.get(toolName);
125
+ const configWeight = config?.tools?.[toolName]?.scoreWeight;
126
+ const weight = cliWeight ?? configWeight ?? DEFAULT_TOOL_WEIGHTS[toolName] ?? 5;
127
+ weights.set(toolName, weight);
128
+ }
129
+ let weightedSum = 0;
130
+ let totalWeight = 0;
131
+ const breakdown = [];
132
+ const toolsUsed = [];
133
+ const calculationWeights = {};
134
+ for (const [toolName, output] of toolOutputs.entries()) {
135
+ const weight = weights.get(toolName) || 5;
136
+ const weightedScore = output.score * weight;
137
+ weightedSum += weightedScore;
138
+ totalWeight += weight;
139
+ toolsUsed.push(toolName);
140
+ calculationWeights[toolName] = weight;
141
+ breakdown.push(output);
142
+ }
143
+ const overall = Math.round(weightedSum / totalWeight);
144
+ const rating = getRating(overall);
145
+ const formulaParts = Array.from(toolOutputs.entries()).map(
146
+ ([name, output]) => {
147
+ const w = weights.get(name) || 5;
148
+ return `(${output.score} \xD7 ${w})`;
149
+ }
150
+ );
151
+ const formulaStr = `[${formulaParts.join(" + ")}] / ${totalWeight} = ${overall}`;
152
+ return {
153
+ overall,
154
+ rating,
155
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
156
+ toolsUsed,
157
+ breakdown,
158
+ calculation: {
159
+ formula: formulaStr,
160
+ weights: calculationWeights,
161
+ normalized: formulaStr
162
+ }
163
+ };
164
+ }
165
+ function getRating(score) {
166
+ if (score >= 90) return "Excellent";
167
+ if (score >= 75) return "Good";
168
+ if (score >= 60) return "Fair";
169
+ if (score >= 40) return "Needs Work";
170
+ return "Critical";
171
+ }
172
+ function getRatingWithContext(score, fileCount, modelTier = "standard") {
173
+ const threshold = getRecommendedThreshold(fileCount, modelTier);
174
+ const normalized = score - threshold + 70;
175
+ return getRating(normalized);
176
+ }
177
+ function getRatingDisplay(rating) {
178
+ switch (rating) {
179
+ case "Excellent":
180
+ return { emoji: "\u2705", color: "green" };
181
+ case "Good":
182
+ return { emoji: "\u{1F44D}", color: "blue" };
183
+ case "Fair":
184
+ return { emoji: "\u26A0\uFE0F", color: "yellow" };
185
+ case "Needs Work":
186
+ return { emoji: "\u{1F528}", color: "orange" };
187
+ case "Critical":
188
+ return { emoji: "\u274C", color: "red" };
189
+ }
190
+ }
191
+ function formatScore(result) {
192
+ const { emoji, color } = getRatingDisplay(result.rating);
193
+ return `${result.overall}/100 (${result.rating}) ${emoji}`;
194
+ }
195
+ function formatToolScore(output) {
196
+ let result = ` Score: ${output.score}/100
197
+
198
+ `;
199
+ if (output.factors && output.factors.length > 0) {
200
+ result += ` Factors:
201
+ `;
202
+ output.factors.forEach((factor) => {
203
+ const impactSign = factor.impact > 0 ? "+" : "";
204
+ result += ` \u2022 ${factor.name}: ${impactSign}${factor.impact} - ${factor.description}
205
+ `;
206
+ });
207
+ result += "\n";
208
+ }
209
+ if (output.recommendations && output.recommendations.length > 0) {
210
+ result += ` Recommendations:
211
+ `;
212
+ output.recommendations.forEach((rec, i) => {
213
+ const priorityIcon = rec.priority === "high" ? "\u{1F534}" : rec.priority === "medium" ? "\u{1F7E1}" : "\u{1F535}";
214
+ result += ` ${i + 1}. ${priorityIcon} ${rec.action}
215
+ `;
216
+ result += ` Impact: +${rec.estimatedImpact} points
217
+
218
+ `;
219
+ });
220
+ }
221
+ return result;
222
+ }
223
+
224
+ // src/utils/visualization.ts
225
+ function generateHTML(graph) {
226
+ const payload = JSON.stringify(graph, null, 2);
227
+ return `<!doctype html>
228
+ <html>
229
+ <head>
230
+ <meta charset="utf-8" />
231
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
232
+ <title>AIReady Visualization</title>
233
+ <style>
234
+ html,body { height: 100%; margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0f172a; color: #e2e8f0 }
235
+ #container { display:flex; height:100vh }
236
+ #panel { width: 320px; padding: 16px; background: #071130; box-shadow: -2px 0 8px rgba(0,0,0,0.3); overflow:auto }
237
+ #canvasWrap { flex:1; display:flex; align-items:center; justify-content:center }
238
+ canvas { background: #0b1220; border-radius:8px }
239
+ .stat { margin-bottom:12px }
240
+ </style>
241
+ </head>
242
+ <body>
243
+ <div id="container">
244
+ <div id="canvasWrap"><canvas id="canvas" width="1200" height="800"></canvas></div>
245
+ <div id="panel">
246
+ <h2>AIReady Visualization</h2>
247
+ <div class="stat"><strong>Files:</strong> <span id="stat-files"></span></div>
248
+ <div class="stat"><strong>Dependencies:</strong> <span id="stat-deps"></span></div>
249
+ <div class="stat"><strong>Legend</strong></div>
250
+ <div style="font-size:13px;line-height:1.3;color:#cbd5e1;margin-top:8px">
251
+ <div style="margin-bottom:8px"><span style="display:inline-block;width:12px;height:12px;background:#ff4d4f;margin-right:8px;border:1px solid rgba(255,255,255,0.06)"></span><strong>Critical</strong>: highest severity issues.</div>
252
+ <div style="margin-bottom:8px"><span style="display:inline-block;width:12px;height:12px;background:#ff9900;margin-right:8px;border:1px solid rgba(255,255,255,0.06)"></span><strong>Major</strong>: important issues.</div>
253
+ <div style="margin-bottom:8px"><span style="display:inline-block;width:12px;height:12px;background:#ffd666;margin-right:8px;border:1px solid rgba(255,255,255,0.06)"></span><strong>Minor</strong>: low priority issues.</div>
254
+ <div style="margin-bottom:8px"><span style="display:inline-block;width:12px;height:12px;background:#91d5ff;margin-right:8px;border:1px solid rgba(255,255,255,0.06)"></span><strong>Info</strong>: informational notes.</div>
255
+ <div style="margin-top:10px;color:#94a3b8"><strong>Node size</strong>: larger = higher token cost, more issues or dependency weight.</div>
256
+ <div style="margin-top:6px;color:#94a3b8"><strong>Proximity</strong>: nodes that are spatially close are more contextually related; relatedness is represented by distance and size rather than explicit edges.</div>
257
+ <div style="margin-top:6px;color:#94a3b8"><strong>Edge colors</strong>: <span style="color:#fb7e81">Similarity</span>, <span style="color:#84c1ff">Dependency</span>, <span style="color:#ffa500">Reference</span>, default <span style="color:#334155">Other</span>.</div>
258
+ </div>
259
+ </div>
260
+ </div>
261
+
262
+ <script>
263
+ const graphData = ${payload};
264
+ document.getElementById('stat-files').textContent = graphData.metadata.totalFiles;
265
+ document.getElementById('stat-deps').textContent = graphData.metadata.totalDependencies;
266
+
267
+ const canvas = document.getElementById('canvas');
268
+ const ctx = canvas.getContext('2d');
269
+
270
+ const nodes = graphData.nodes.map((n, i) => ({
271
+ ...n,
272
+ x: canvas.width / 2 + Math.cos(i / graphData.nodes.length * Math.PI * 2) * (Math.min(canvas.width, canvas.height) / 3),
273
+ y: canvas.height / 2 + Math.sin(i / graphData.nodes.length * Math.PI * 2) * (Math.min(canvas.width, canvas.height) / 3),
274
+ }));
275
+
276
+ function draw() {
277
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
278
+
279
+ graphData.edges.forEach(edge => {
280
+ const s = nodes.find(n => n.id === edge.source);
281
+ const t = nodes.find(n => n.id === edge.target);
282
+ if (!s || !t) return;
283
+ if (edge.type === 'related') return;
284
+ if (edge.type === 'similarity') {
285
+ ctx.strokeStyle = '#fb7e81';
286
+ ctx.lineWidth = 1.2;
287
+ } else if (edge.type === 'dependency') {
288
+ ctx.strokeStyle = '#84c1ff';
289
+ ctx.lineWidth = 1.0;
290
+ } else if (edge.type === 'reference') {
291
+ ctx.strokeStyle = '#ffa500';
292
+ ctx.lineWidth = 0.9;
293
+ } else {
294
+ ctx.strokeStyle = '#334155';
295
+ ctx.lineWidth = 0.8;
296
+ }
297
+ ctx.beginPath();
298
+ ctx.moveTo(s.x, s.y);
299
+ ctx.lineTo(t.x, t.y);
300
+ ctx.stroke();
301
+ });
302
+
303
+ const groups = {};
304
+ nodes.forEach(n => {
305
+ const g = n.group || '__default';
306
+ if (!groups[g]) groups[g] = { minX: n.x, minY: n.y, maxX: n.x, maxY: n.y };
307
+ groups[g].minX = Math.min(groups[g].minX, n.x);
308
+ groups[g].minY = Math.min(groups[g].minY, n.y);
309
+ groups[g].maxX = Math.max(groups[g].maxX, n.x);
310
+ groups[g].maxY = Math.max(groups[g].maxY, n.y);
311
+ });
312
+
313
+ const groupRelations = {};
314
+ graphData.edges.forEach(edge => {
315
+ const sNode = nodes.find(n => n.id === edge.source);
316
+ const tNode = nodes.find(n => n.id === edge.target);
317
+ if (!sNode || !tNode) return;
318
+ const g1 = sNode.group || '__default';
319
+ const g2 = tNode.group || '__default';
320
+ if (g1 === g2) return;
321
+ const key = g1 < g2 ? g1 + '::' + g2 : g2 + '::' + g1;
322
+ groupRelations[key] = (groupRelations[key] || 0) + 1;
323
+ });
324
+
325
+ Object.keys(groupRelations).forEach(k => {
326
+ const count = groupRelations[k];
327
+ const [ga, gb] = k.split('::');
328
+ if (!groups[ga] || !groups[gb]) return;
329
+ const ax = (groups[ga].minX + groups[ga].maxX) / 2;
330
+ const ay = (groups[ga].minY + groups[ga].maxY) / 2;
331
+ const bx = (groups[gb].minX + groups[gb].maxX) / 2;
332
+ const by = (groups[gb].minY + groups[gb].maxY) / 2;
333
+ ctx.beginPath();
334
+ ctx.strokeStyle = 'rgba(148,163,184,0.25)';
335
+ ctx.lineWidth = Math.min(6, 0.6 + Math.sqrt(count));
336
+ ctx.moveTo(ax, ay);
337
+ ctx.lineTo(bx, by);
338
+ ctx.stroke();
339
+ });
340
+
341
+ Object.keys(groups).forEach(g => {
342
+ if (g === '__default') return;
343
+ const box = groups[g];
344
+ const pad = 16;
345
+ const x = box.minX - pad;
346
+ const y = box.minY - pad;
347
+ const w = (box.maxX - box.minX) + pad * 2;
348
+ const h = (box.maxY - box.minY) + pad * 2;
349
+ ctx.save();
350
+ ctx.fillStyle = 'rgba(30,64,175,0.04)';
351
+ ctx.strokeStyle = 'rgba(30,64,175,0.12)';
352
+ ctx.lineWidth = 1.2;
353
+ const r = 8;
354
+ ctx.beginPath();
355
+ ctx.moveTo(x + r, y);
356
+ ctx.arcTo(x + w, y, x + w, y + h, r);
357
+ ctx.arcTo(x + w, y + h, x, y + h, r);
358
+ ctx.arcTo(x, y + h, x, y, r);
359
+ ctx.arcTo(x, y, x + w, y, r);
360
+ ctx.closePath();
361
+ ctx.fill();
362
+ ctx.stroke();
363
+ ctx.restore();
364
+ ctx.fillStyle = '#94a3b8';
365
+ ctx.font = '11px sans-serif';
366
+ ctx.fillText(g, x + 8, y + 14);
367
+ });
368
+
369
+ nodes.forEach(n => {
370
+ const sizeVal = (n.size || n.value || 1);
371
+ const r = 6 + (sizeVal / 2);
372
+ ctx.beginPath();
373
+ ctx.fillStyle = n.color || '#60a5fa';
374
+ ctx.arc(n.x, n.y, r, 0, Math.PI * 2);
375
+ ctx.fill();
376
+
377
+ ctx.fillStyle = '#e2e8f0';
378
+ ctx.font = '11px sans-serif';
379
+ ctx.textAlign = 'center';
380
+ ctx.fillText(n.label || n.id.split('/').slice(-1)[0], n.x, n.y + r + 12);
381
+ });
382
+ }
383
+
384
+ draw();
385
+ </script>
386
+ </body>
387
+ </html>`;
388
+ }
389
+
390
+ export {
391
+ Language,
392
+ LANGUAGE_EXTENSIONS,
393
+ ParseError,
394
+ DEFAULT_TOOL_WEIGHTS,
395
+ TOOL_NAME_MAP,
396
+ CONTEXT_TIER_THRESHOLDS,
397
+ SIZE_ADJUSTED_THRESHOLDS,
398
+ getProjectSizeTier,
399
+ getRecommendedThreshold,
400
+ normalizeToolName,
401
+ getToolWeight,
402
+ parseWeightString,
403
+ calculateOverallScore,
404
+ getRating,
405
+ getRatingWithContext,
406
+ getRatingDisplay,
407
+ formatScore,
408
+ formatToolScore,
409
+ generateHTML
410
+ };
@@ -0,0 +1,410 @@
1
+ // src/types/language.ts
2
+ var Language = /* @__PURE__ */ ((Language2) => {
3
+ Language2["TypeScript"] = "typescript";
4
+ Language2["JavaScript"] = "javascript";
5
+ Language2["Python"] = "python";
6
+ Language2["Java"] = "java";
7
+ Language2["Go"] = "go";
8
+ Language2["Rust"] = "rust";
9
+ Language2["CSharp"] = "csharp";
10
+ return Language2;
11
+ })(Language || {});
12
+ var LANGUAGE_EXTENSIONS = {
13
+ ".ts": "typescript" /* TypeScript */,
14
+ ".tsx": "typescript" /* TypeScript */,
15
+ ".js": "javascript" /* JavaScript */,
16
+ ".jsx": "javascript" /* JavaScript */,
17
+ ".py": "python" /* Python */,
18
+ ".java": "java" /* Java */,
19
+ ".go": "go" /* Go */,
20
+ ".rs": "rust" /* Rust */,
21
+ ".cs": "csharp" /* CSharp */
22
+ };
23
+ var ParseError = class extends Error {
24
+ constructor(message, filePath, loc) {
25
+ super(message);
26
+ this.filePath = filePath;
27
+ this.loc = loc;
28
+ this.name = "ParseError";
29
+ }
30
+ };
31
+
32
+ // src/scoring.ts
33
+ var DEFAULT_TOOL_WEIGHTS = {
34
+ "pattern-detect": 22,
35
+ "context-analyzer": 19,
36
+ consistency: 14,
37
+ "ai-signal-clarity": 11,
38
+ "agent-grounding": 10,
39
+ testability: 10,
40
+ "doc-drift": 8,
41
+ deps: 6
42
+ };
43
+ var TOOL_NAME_MAP = {
44
+ patterns: "pattern-detect",
45
+ context: "context-analyzer",
46
+ consistency: "consistency",
47
+ "AI signal clarity": "ai-signal-clarity",
48
+ "ai-signal-clarity": "ai-signal-clarity",
49
+ grounding: "agent-grounding",
50
+ "agent-grounding": "agent-grounding",
51
+ testability: "testability",
52
+ tests: "testability",
53
+ "doc-drift": "doc-drift",
54
+ docs: "doc-drift",
55
+ deps: "deps"
56
+ };
57
+ var CONTEXT_TIER_THRESHOLDS = {
58
+ compact: { idealTokens: 3e3, criticalTokens: 1e4, idealDepth: 4 },
59
+ standard: { idealTokens: 5e3, criticalTokens: 15e3, idealDepth: 5 },
60
+ extended: { idealTokens: 15e3, criticalTokens: 5e4, idealDepth: 7 },
61
+ frontier: { idealTokens: 5e4, criticalTokens: 15e4, idealDepth: 10 }
62
+ };
63
+ var SIZE_ADJUSTED_THRESHOLDS = {
64
+ xs: 80,
65
+ // < 50 files
66
+ small: 75,
67
+ // 50-200 files
68
+ medium: 70,
69
+ // 200-500 files
70
+ large: 65,
71
+ // 500-2000 files
72
+ enterprise: 58
73
+ // 2000+ files
74
+ };
75
+ function getProjectSizeTier(fileCount) {
76
+ if (fileCount < 50) return "xs";
77
+ if (fileCount < 200) return "small";
78
+ if (fileCount < 500) return "medium";
79
+ if (fileCount < 2e3) return "large";
80
+ return "enterprise";
81
+ }
82
+ function getRecommendedThreshold(fileCount, modelTier = "standard") {
83
+ const sizeTier = getProjectSizeTier(fileCount);
84
+ const base = SIZE_ADJUSTED_THRESHOLDS[sizeTier];
85
+ const modelBonus = modelTier === "frontier" ? -3 : modelTier === "extended" ? -2 : 0;
86
+ return base + modelBonus;
87
+ }
88
+ function normalizeToolName(shortName) {
89
+ return TOOL_NAME_MAP[shortName] || shortName;
90
+ }
91
+ function getToolWeight(toolName, toolConfig, cliOverride) {
92
+ if (cliOverride !== void 0) {
93
+ return cliOverride;
94
+ }
95
+ if (toolConfig?.scoreWeight !== void 0) {
96
+ return toolConfig.scoreWeight;
97
+ }
98
+ return DEFAULT_TOOL_WEIGHTS[toolName] || 5;
99
+ }
100
+ function parseWeightString(weightStr) {
101
+ const weights = /* @__PURE__ */ new Map();
102
+ if (!weightStr) {
103
+ return weights;
104
+ }
105
+ const pairs = weightStr.split(",");
106
+ for (const pair of pairs) {
107
+ const [toolShortName, weightStr2] = pair.split(":");
108
+ if (toolShortName && weightStr2) {
109
+ const toolName = normalizeToolName(toolShortName.trim());
110
+ const weight = parseInt(weightStr2.trim(), 10);
111
+ if (!isNaN(weight) && weight > 0) {
112
+ weights.set(toolName, weight);
113
+ }
114
+ }
115
+ }
116
+ return weights;
117
+ }
118
+ function calculateOverallScore(toolOutputs, config, cliWeights) {
119
+ if (toolOutputs.size === 0) {
120
+ throw new Error("No tool outputs provided for scoring");
121
+ }
122
+ const weights = /* @__PURE__ */ new Map();
123
+ for (const [toolName] of toolOutputs.entries()) {
124
+ const cliWeight = cliWeights?.get(toolName);
125
+ const configWeight = config?.tools?.[toolName]?.scoreWeight;
126
+ const weight = cliWeight ?? configWeight ?? DEFAULT_TOOL_WEIGHTS[toolName] ?? 5;
127
+ weights.set(toolName, weight);
128
+ }
129
+ let weightedSum = 0;
130
+ let totalWeight = 0;
131
+ const breakdown = [];
132
+ const toolsUsed = [];
133
+ const calculationWeights = {};
134
+ for (const [toolName, output] of toolOutputs.entries()) {
135
+ const weight = weights.get(toolName) || 5;
136
+ const weightedScore = output.score * weight;
137
+ weightedSum += weightedScore;
138
+ totalWeight += weight;
139
+ toolsUsed.push(toolName);
140
+ calculationWeights[toolName] = weight;
141
+ breakdown.push(output);
142
+ }
143
+ const overall = Math.round(weightedSum / totalWeight);
144
+ const rating = getRating(overall);
145
+ const formulaParts = Array.from(toolOutputs.entries()).map(
146
+ ([name, output]) => {
147
+ const w = weights.get(name) || 5;
148
+ return `(${output.score} \xD7 ${w})`;
149
+ }
150
+ );
151
+ const formulaStr = `[${formulaParts.join(" + ")}] / ${totalWeight} = ${overall}`;
152
+ return {
153
+ overall,
154
+ rating,
155
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
156
+ toolsUsed,
157
+ breakdown,
158
+ calculation: {
159
+ formula: formulaStr,
160
+ weights: calculationWeights,
161
+ normalized: formulaStr
162
+ }
163
+ };
164
+ }
165
+ function getRating(score) {
166
+ if (score >= 90) return "Excellent";
167
+ if (score >= 75) return "Good";
168
+ if (score >= 60) return "Fair";
169
+ if (score >= 40) return "Needs Work";
170
+ return "Critical";
171
+ }
172
+ function getRatingWithContext(score, fileCount, modelTier = "standard") {
173
+ const threshold = getRecommendedThreshold(fileCount, modelTier);
174
+ const normalized = score - threshold + 70;
175
+ return getRating(normalized);
176
+ }
177
+ function getRatingDisplay(rating) {
178
+ switch (rating) {
179
+ case "Excellent":
180
+ return { emoji: "\u2705", color: "green" };
181
+ case "Good":
182
+ return { emoji: "\u{1F44D}", color: "blue" };
183
+ case "Fair":
184
+ return { emoji: "\u26A0\uFE0F", color: "yellow" };
185
+ case "Needs Work":
186
+ return { emoji: "\u{1F528}", color: "orange" };
187
+ case "Critical":
188
+ return { emoji: "\u274C", color: "red" };
189
+ }
190
+ }
191
+ function formatScore(result) {
192
+ const { emoji } = getRatingDisplay(result.rating);
193
+ return `${result.overall}/100 (${result.rating}) ${emoji}`;
194
+ }
195
+ function formatToolScore(output) {
196
+ let result = ` Score: ${output.score}/100
197
+
198
+ `;
199
+ if (output.factors && output.factors.length > 0) {
200
+ result += ` Factors:
201
+ `;
202
+ output.factors.forEach((factor) => {
203
+ const impactSign = factor.impact > 0 ? "+" : "";
204
+ result += ` \u2022 ${factor.name}: ${impactSign}${factor.impact} - ${factor.description}
205
+ `;
206
+ });
207
+ result += "\n";
208
+ }
209
+ if (output.recommendations && output.recommendations.length > 0) {
210
+ result += ` Recommendations:
211
+ `;
212
+ output.recommendations.forEach((rec, i) => {
213
+ const priorityIcon = rec.priority === "high" ? "\u{1F534}" : rec.priority === "medium" ? "\u{1F7E1}" : "\u{1F535}";
214
+ result += ` ${i + 1}. ${priorityIcon} ${rec.action}
215
+ `;
216
+ result += ` Impact: +${rec.estimatedImpact} points
217
+
218
+ `;
219
+ });
220
+ }
221
+ return result;
222
+ }
223
+
224
+ // src/utils/visualization.ts
225
+ function generateHTML(graph) {
226
+ const payload = JSON.stringify(graph, null, 2);
227
+ return `<!doctype html>
228
+ <html>
229
+ <head>
230
+ <meta charset="utf-8" />
231
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
232
+ <title>AIReady Visualization</title>
233
+ <style>
234
+ html,body { height: 100%; margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0f172a; color: #e2e8f0 }
235
+ #container { display:flex; height:100vh }
236
+ #panel { width: 320px; padding: 16px; background: #071130; box-shadow: -2px 0 8px rgba(0,0,0,0.3); overflow:auto }
237
+ #canvasWrap { flex:1; display:flex; align-items:center; justify-content:center }
238
+ canvas { background: #0b1220; border-radius:8px }
239
+ .stat { margin-bottom:12px }
240
+ </style>
241
+ </head>
242
+ <body>
243
+ <div id="container">
244
+ <div id="canvasWrap"><canvas id="canvas" width="1200" height="800"></canvas></div>
245
+ <div id="panel">
246
+ <h2>AIReady Visualization</h2>
247
+ <div class="stat"><strong>Files:</strong> <span id="stat-files"></span></div>
248
+ <div class="stat"><strong>Dependencies:</strong> <span id="stat-deps"></span></div>
249
+ <div class="stat"><strong>Legend</strong></div>
250
+ <div style="font-size:13px;line-height:1.3;color:#cbd5e1;margin-top:8px">
251
+ <div style="margin-bottom:8px"><span style="display:inline-block;width:12px;height:12px;background:#ff4d4f;margin-right:8px;border:1px solid rgba(255,255,255,0.06)"></span><strong>Critical</strong>: highest severity issues.</div>
252
+ <div style="margin-bottom:8px"><span style="display:inline-block;width:12px;height:12px;background:#ff9900;margin-right:8px;border:1px solid rgba(255,255,255,0.06)"></span><strong>Major</strong>: important issues.</div>
253
+ <div style="margin-bottom:8px"><span style="display:inline-block;width:12px;height:12px;background:#ffd666;margin-right:8px;border:1px solid rgba(255,255,255,0.06)"></span><strong>Minor</strong>: low priority issues.</div>
254
+ <div style="margin-bottom:8px"><span style="display:inline-block;width:12px;height:12px;background:#91d5ff;margin-right:8px;border:1px solid rgba(255,255,255,0.06)"></span><strong>Info</strong>: informational notes.</div>
255
+ <div style="margin-top:10px;color:#94a3b8"><strong>Node size</strong>: larger = higher token cost, more issues or dependency weight.</div>
256
+ <div style="margin-top:6px;color:#94a3b8"><strong>Proximity</strong>: nodes that are spatially close are more contextually related; relatedness is represented by distance and size rather than explicit edges.</div>
257
+ <div style="margin-top:6px;color:#94a3b8"><strong>Edge colors</strong>: <span style="color:#fb7e81">Similarity</span>, <span style="color:#84c1ff">Dependency</span>, <span style="color:#ffa500">Reference</span>, default <span style="color:#334155">Other</span>.</div>
258
+ </div>
259
+ </div>
260
+ </div>
261
+
262
+ <script>
263
+ const graphData = ${payload};
264
+ document.getElementById('stat-files').textContent = graphData.metadata.totalFiles;
265
+ document.getElementById('stat-deps').textContent = graphData.metadata.totalDependencies;
266
+
267
+ const canvas = document.getElementById('canvas');
268
+ const ctx = canvas.getContext('2d');
269
+
270
+ const nodes = graphData.nodes.map((n, i) => ({
271
+ ...n,
272
+ x: canvas.width / 2 + Math.cos(i / graphData.nodes.length * Math.PI * 2) * (Math.min(canvas.width, canvas.height) / 3),
273
+ y: canvas.height / 2 + Math.sin(i / graphData.nodes.length * Math.PI * 2) * (Math.min(canvas.width, canvas.height) / 3),
274
+ }));
275
+
276
+ function draw() {
277
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
278
+
279
+ graphData.edges.forEach(edge => {
280
+ const s = nodes.find(n => n.id === edge.source);
281
+ const t = nodes.find(n => n.id === edge.target);
282
+ if (!s || !t) return;
283
+ if (edge.type === 'related') return;
284
+ if (edge.type === 'similarity') {
285
+ ctx.strokeStyle = '#fb7e81';
286
+ ctx.lineWidth = 1.2;
287
+ } else if (edge.type === 'dependency') {
288
+ ctx.strokeStyle = '#84c1ff';
289
+ ctx.lineWidth = 1.0;
290
+ } else if (edge.type === 'reference') {
291
+ ctx.strokeStyle = '#ffa500';
292
+ ctx.lineWidth = 0.9;
293
+ } else {
294
+ ctx.strokeStyle = '#334155';
295
+ ctx.lineWidth = 0.8;
296
+ }
297
+ ctx.beginPath();
298
+ ctx.moveTo(s.x, s.y);
299
+ ctx.lineTo(t.x, t.y);
300
+ ctx.stroke();
301
+ });
302
+
303
+ const groups = {};
304
+ nodes.forEach(n => {
305
+ const g = n.group || '__default';
306
+ if (!groups[g]) groups[g] = { minX: n.x, minY: n.y, maxX: n.x, maxY: n.y };
307
+ groups[g].minX = Math.min(groups[g].minX, n.x);
308
+ groups[g].minY = Math.min(groups[g].minY, n.y);
309
+ groups[g].maxX = Math.max(groups[g].maxX, n.x);
310
+ groups[g].maxY = Math.max(groups[g].maxY, n.y);
311
+ });
312
+
313
+ const groupRelations = {};
314
+ graphData.edges.forEach(edge => {
315
+ const sNode = nodes.find(n => n.id === edge.source);
316
+ const tNode = nodes.find(n => n.id === edge.target);
317
+ if (!sNode || !tNode) return;
318
+ const g1 = sNode.group || '__default';
319
+ const g2 = tNode.group || '__default';
320
+ if (g1 === g2) return;
321
+ const key = g1 < g2 ? g1 + '::' + g2 : g2 + '::' + g1;
322
+ groupRelations[key] = (groupRelations[key] || 0) + 1;
323
+ });
324
+
325
+ Object.keys(groupRelations).forEach(k => {
326
+ const count = groupRelations[k];
327
+ const [ga, gb] = k.split('::');
328
+ if (!groups[ga] || !groups[gb]) return;
329
+ const ax = (groups[ga].minX + groups[ga].maxX) / 2;
330
+ const ay = (groups[ga].minY + groups[ga].maxY) / 2;
331
+ const bx = (groups[gb].minX + groups[gb].maxX) / 2;
332
+ const by = (groups[gb].minY + groups[gb].maxY) / 2;
333
+ ctx.beginPath();
334
+ ctx.strokeStyle = 'rgba(148,163,184,0.25)';
335
+ ctx.lineWidth = Math.min(6, 0.6 + Math.sqrt(count));
336
+ ctx.moveTo(ax, ay);
337
+ ctx.lineTo(bx, by);
338
+ ctx.stroke();
339
+ });
340
+
341
+ Object.keys(groups).forEach(g => {
342
+ if (g === '__default') return;
343
+ const box = groups[g];
344
+ const pad = 16;
345
+ const x = box.minX - pad;
346
+ const y = box.minY - pad;
347
+ const w = (box.maxX - box.minX) + pad * 2;
348
+ const h = (box.maxY - box.minY) + pad * 2;
349
+ ctx.save();
350
+ ctx.fillStyle = 'rgba(30,64,175,0.04)';
351
+ ctx.strokeStyle = 'rgba(30,64,175,0.12)';
352
+ ctx.lineWidth = 1.2;
353
+ const r = 8;
354
+ ctx.beginPath();
355
+ ctx.moveTo(x + r, y);
356
+ ctx.arcTo(x + w, y, x + w, y + h, r);
357
+ ctx.arcTo(x + w, y + h, x, y + h, r);
358
+ ctx.arcTo(x, y + h, x, y, r);
359
+ ctx.arcTo(x, y, x + w, y, r);
360
+ ctx.closePath();
361
+ ctx.fill();
362
+ ctx.stroke();
363
+ ctx.restore();
364
+ ctx.fillStyle = '#94a3b8';
365
+ ctx.font = '11px sans-serif';
366
+ ctx.fillText(g, x + 8, y + 14);
367
+ });
368
+
369
+ nodes.forEach(n => {
370
+ const sizeVal = (n.size || n.value || 1);
371
+ const r = 6 + (sizeVal / 2);
372
+ ctx.beginPath();
373
+ ctx.fillStyle = n.color || '#60a5fa';
374
+ ctx.arc(n.x, n.y, r, 0, Math.PI * 2);
375
+ ctx.fill();
376
+
377
+ ctx.fillStyle = '#e2e8f0';
378
+ ctx.font = '11px sans-serif';
379
+ ctx.textAlign = 'center';
380
+ ctx.fillText(n.label || n.id.split('/').slice(-1)[0], n.x, n.y + r + 12);
381
+ });
382
+ }
383
+
384
+ draw();
385
+ </script>
386
+ </body>
387
+ </html>`;
388
+ }
389
+
390
+ export {
391
+ Language,
392
+ LANGUAGE_EXTENSIONS,
393
+ ParseError,
394
+ DEFAULT_TOOL_WEIGHTS,
395
+ TOOL_NAME_MAP,
396
+ CONTEXT_TIER_THRESHOLDS,
397
+ SIZE_ADJUSTED_THRESHOLDS,
398
+ getProjectSizeTier,
399
+ getRecommendedThreshold,
400
+ normalizeToolName,
401
+ getToolWeight,
402
+ parseWeightString,
403
+ calculateOverallScore,
404
+ getRating,
405
+ getRatingWithContext,
406
+ getRatingDisplay,
407
+ formatScore,
408
+ formatToolScore,
409
+ generateHTML
410
+ };
package/dist/client.js CHANGED
@@ -167,7 +167,7 @@ function calculateOverallScore(toolOutputs, config, cliWeights) {
167
167
  for (const [toolName] of toolOutputs.entries()) {
168
168
  const cliWeight = cliWeights?.get(toolName);
169
169
  const configWeight = config?.tools?.[toolName]?.scoreWeight;
170
- const weight = cliWeight ?? configWeight ?? DEFAULT_TOOL_WEIGHTS[toolName] ?? 10;
170
+ const weight = cliWeight ?? configWeight ?? DEFAULT_TOOL_WEIGHTS[toolName] ?? 5;
171
171
  weights.set(toolName, weight);
172
172
  }
173
173
  let weightedSum = 0;
@@ -176,7 +176,7 @@ function calculateOverallScore(toolOutputs, config, cliWeights) {
176
176
  const toolsUsed = [];
177
177
  const calculationWeights = {};
178
178
  for (const [toolName, output] of toolOutputs.entries()) {
179
- const weight = weights.get(toolName) || 10;
179
+ const weight = weights.get(toolName) || 5;
180
180
  const weightedScore = output.score * weight;
181
181
  weightedSum += weightedScore;
182
182
  totalWeight += weight;
@@ -188,7 +188,7 @@ function calculateOverallScore(toolOutputs, config, cliWeights) {
188
188
  const rating = getRating(overall);
189
189
  const formulaParts = Array.from(toolOutputs.entries()).map(
190
190
  ([name, output]) => {
191
- const w = weights.get(name) || 10;
191
+ const w = weights.get(name) || 5;
192
192
  return `(${output.score} \xD7 ${w})`;
193
193
  }
194
194
  );
@@ -233,7 +233,7 @@ function getRatingDisplay(rating) {
233
233
  }
234
234
  }
235
235
  function formatScore(result) {
236
- const { emoji, color } = getRatingDisplay(result.rating);
236
+ const { emoji } = getRatingDisplay(result.rating);
237
237
  return `${result.overall}/100 (${result.rating}) ${emoji}`;
238
238
  }
239
239
  function formatToolScore(output) {
package/dist/client.mjs CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  getToolWeight,
19
19
  normalizeToolName,
20
20
  parseWeightString
21
- } from "./chunk-HFLFBA6F.mjs";
21
+ } from "./chunk-UQGI67WR.mjs";
22
22
  export {
23
23
  CONTEXT_TIER_THRESHOLDS,
24
24
  DEFAULT_TOOL_WEIGHTS,
package/dist/index.js CHANGED
@@ -117,15 +117,15 @@ __export(index_exports, {
117
117
  module.exports = __toCommonJS(index_exports);
118
118
 
119
119
  // src/types/language.ts
120
- var Language = /* @__PURE__ */ ((Language3) => {
121
- Language3["TypeScript"] = "typescript";
122
- Language3["JavaScript"] = "javascript";
123
- Language3["Python"] = "python";
124
- Language3["Java"] = "java";
125
- Language3["Go"] = "go";
126
- Language3["Rust"] = "rust";
127
- Language3["CSharp"] = "csharp";
128
- return Language3;
120
+ var Language = /* @__PURE__ */ ((Language2) => {
121
+ Language2["TypeScript"] = "typescript";
122
+ Language2["JavaScript"] = "javascript";
123
+ Language2["Python"] = "python";
124
+ Language2["Java"] = "java";
125
+ Language2["Go"] = "go";
126
+ Language2["Rust"] = "rust";
127
+ Language2["CSharp"] = "csharp";
128
+ return Language2;
129
129
  })(Language || {});
130
130
  var LANGUAGE_EXTENSIONS = {
131
131
  ".ts": "typescript" /* TypeScript */,
@@ -901,7 +901,7 @@ function calculateOverallScore(toolOutputs, config, cliWeights) {
901
901
  for (const [toolName] of toolOutputs.entries()) {
902
902
  const cliWeight = cliWeights?.get(toolName);
903
903
  const configWeight = config?.tools?.[toolName]?.scoreWeight;
904
- const weight = cliWeight ?? configWeight ?? DEFAULT_TOOL_WEIGHTS[toolName] ?? 10;
904
+ const weight = cliWeight ?? configWeight ?? DEFAULT_TOOL_WEIGHTS[toolName] ?? 5;
905
905
  weights.set(toolName, weight);
906
906
  }
907
907
  let weightedSum = 0;
@@ -910,7 +910,7 @@ function calculateOverallScore(toolOutputs, config, cliWeights) {
910
910
  const toolsUsed = [];
911
911
  const calculationWeights = {};
912
912
  for (const [toolName, output] of toolOutputs.entries()) {
913
- const weight = weights.get(toolName) || 10;
913
+ const weight = weights.get(toolName) || 5;
914
914
  const weightedScore = output.score * weight;
915
915
  weightedSum += weightedScore;
916
916
  totalWeight += weight;
@@ -922,7 +922,7 @@ function calculateOverallScore(toolOutputs, config, cliWeights) {
922
922
  const rating = getRating(overall);
923
923
  const formulaParts = Array.from(toolOutputs.entries()).map(
924
924
  ([name, output]) => {
925
- const w = weights.get(name) || 10;
925
+ const w = weights.get(name) || 5;
926
926
  return `(${output.score} \xD7 ${w})`;
927
927
  }
928
928
  );
@@ -967,7 +967,7 @@ function getRatingDisplay(rating) {
967
967
  }
968
968
  }
969
969
  function formatScore(result) {
970
- const { emoji, color } = getRatingDisplay(result.rating);
970
+ const { emoji } = getRatingDisplay(result.rating);
971
971
  return `${result.overall}/100 (${result.rating}) ${emoji}`;
972
972
  }
973
973
  function formatToolScore(output) {
@@ -1100,7 +1100,10 @@ function calculateTokenBudget(params) {
1100
1100
  const totalWaste = wastedTokens.duplication + wastedTokens.fragmentation + wastedTokens.chattiness;
1101
1101
  const efficiencyRatio = Math.max(
1102
1102
  0,
1103
- Math.min(1, (totalContextTokens - totalWaste) / Math.max(1, totalContextTokens))
1103
+ Math.min(
1104
+ 1,
1105
+ (totalContextTokens - totalWaste) / Math.max(1, totalContextTokens)
1106
+ )
1104
1107
  );
1105
1108
  return {
1106
1109
  totalContextTokens: Math.round(totalContextTokens),
@@ -1124,7 +1127,8 @@ function estimateCostFromBudget(budget, model, config = {}) {
1124
1127
  const tokensPerDay = wastePerQuery * cfg.queriesPerDevPerDay;
1125
1128
  const tokensPerMonth = tokensPerDay * cfg.daysPerMonth;
1126
1129
  const totalWeight = cfg.developerCount;
1127
- const baseCost = tokensPerMonth / 1e3 * model.pricePer1KInputTokens * totalWeight;
1130
+ const price = config.pricePer1KTokens ?? model.pricePer1KInputTokens;
1131
+ const baseCost = tokensPerMonth / 1e3 * price * totalWeight;
1128
1132
  let confidence = 0.85;
1129
1133
  if (model.contextTier === "frontier") confidence = 0.7;
1130
1134
  const variance = 0.25;
@@ -2004,7 +2008,9 @@ var ParserFactory = class _ParserFactory {
2004
2008
  registerParser(parser) {
2005
2009
  this.parsers.set(parser.language, parser);
2006
2010
  parser.extensions.forEach((ext) => {
2007
- this.extensionMap.set(ext, parser.language);
2011
+ const lang = LANGUAGE_EXTENSIONS[ext] || parser.language;
2012
+ this.extensionMap.set(ext, lang);
2013
+ this.parsers.set(lang, parser);
2008
2014
  });
2009
2015
  }
2010
2016
  /**
package/dist/index.mjs CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  getToolWeight,
19
19
  normalizeToolName,
20
20
  parseWeightString
21
- } from "./chunk-HFLFBA6F.mjs";
21
+ } from "./chunk-UQGI67WR.mjs";
22
22
 
23
23
  // src/utils/file-scanner.ts
24
24
  import { glob } from "glob";
@@ -615,7 +615,10 @@ function calculateTokenBudget(params) {
615
615
  const totalWaste = wastedTokens.duplication + wastedTokens.fragmentation + wastedTokens.chattiness;
616
616
  const efficiencyRatio = Math.max(
617
617
  0,
618
- Math.min(1, (totalContextTokens - totalWaste) / Math.max(1, totalContextTokens))
618
+ Math.min(
619
+ 1,
620
+ (totalContextTokens - totalWaste) / Math.max(1, totalContextTokens)
621
+ )
619
622
  );
620
623
  return {
621
624
  totalContextTokens: Math.round(totalContextTokens),
@@ -639,7 +642,8 @@ function estimateCostFromBudget(budget, model, config = {}) {
639
642
  const tokensPerDay = wastePerQuery * cfg.queriesPerDevPerDay;
640
643
  const tokensPerMonth = tokensPerDay * cfg.daysPerMonth;
641
644
  const totalWeight = cfg.developerCount;
642
- const baseCost = tokensPerMonth / 1e3 * model.pricePer1KInputTokens * totalWeight;
645
+ const price = config.pricePer1KTokens ?? model.pricePer1KInputTokens;
646
+ const baseCost = tokensPerMonth / 1e3 * price * totalWeight;
643
647
  let confidence = 0.85;
644
648
  if (model.contextTier === "frontier") confidence = 0.7;
645
649
  const variance = 0.25;
@@ -1519,7 +1523,9 @@ var ParserFactory = class _ParserFactory {
1519
1523
  registerParser(parser) {
1520
1524
  this.parsers.set(parser.language, parser);
1521
1525
  parser.extensions.forEach((ext) => {
1522
- this.extensionMap.set(ext, parser.language);
1526
+ const lang = LANGUAGE_EXTENSIONS[ext] || parser.language;
1527
+ this.extensionMap.set(ext, lang);
1528
+ this.parsers.set(lang, parser);
1523
1529
  });
1524
1530
  }
1525
1531
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiready/core",
3
- "version": "0.9.37",
3
+ "version": "0.9.38",
4
4
  "description": "Shared utilities for AIReady analysis tools",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -53,6 +53,7 @@
53
53
  "scripts": {
54
54
  "build": "tsup src/index.ts src/client.ts --format cjs,esm --dts",
55
55
  "dev": "tsup src/index.ts src/client.ts --format cjs,esm --watch",
56
+ "test": "vitest run",
56
57
  "lint": "eslint src",
57
58
  "clean": "rm -rf dist",
58
59
  "release": "pnpm build && pnpm publish --no-git-checks"