@aiready/cli 0.13.0 → 0.13.2

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.
@@ -1,6 +1,6 @@
1
1
 
2
2
  
3
- > @aiready/cli@0.12.23 build /Users/pengcao/projects/aiready/packages/cli
3
+ > @aiready/cli@0.13.2 build /Users/pengcao/projects/aiready/packages/cli
4
4
  > tsup src/index.ts src/cli.ts --format cjs,esm
5
5
 
6
6
  CLI Building entry: src/cli.ts, src/index.ts
@@ -10,7 +10,7 @@
10
10
  CJS Build start
11
11
  ESM Build start
12
12
 
13
-  WARN  ▲ [WARNING] "import.meta" is not available with the "cjs" output format and will be empty [empty-import-meta] 6:35:40 pm
13
+  WARN  ▲ [WARNING] "import.meta" is not available with the "cjs" output format and will be empty [empty-import-meta] 7:51:15 pm
14
14
 
15
15
  src/cli.ts:32:31:
16
16
   32 │ return dirname(fileURLToPath(import.meta.url));
@@ -20,10 +20,10 @@
20
20
 
21
21
 
22
22
 
23
- CJS dist/index.js 10.62 KB
24
- CJS dist/cli.js 80.29 KB
25
- CJS ⚡️ Build success in 34ms
26
- ESM dist/cli.mjs 66.94 KB
27
- ESM dist/chunk-VOKP7FGM.mjs 9.52 KB
28
23
  ESM dist/index.mjs 170.00 B
29
- ESM ⚡️ Build success in 34ms
24
+ ESM dist/chunk-VOKP7FGM.mjs 9.52 KB
25
+ ESM dist/cli.mjs 68.68 KB
26
+ ESM ⚡️ Build success in 30ms
27
+ CJS dist/index.js 10.62 KB
28
+ CJS dist/cli.js 82.21 KB
29
+ CJS ⚡️ Build success in 30ms
@@ -1,19 +1,20 @@
1
1
 
2
2
  
3
- > @aiready/cli@0.12.23 test /Users/pengcao/projects/aiready/packages/cli
3
+ > @aiready/cli@0.13.0 test /Users/pengcao/projects/aiready/packages/cli
4
4
  > vitest run
5
5
 
6
6
  [?25l
7
7
   RUN  v4.0.18 /Users/pengcao/projects/aiready/packages/cli
8
8
 
9
- ✓ src/commands/__tests__/testability.test.ts (2 tests) 19ms
10
- ✓ src/commands/__tests__/deps-health.test.ts (1 test) 16ms
11
- ✓ src/commands/__tests__/doc-drift.test.ts (1 test) 19ms
12
- ✓ src/commands/__tests__/agent-grounding.test.ts (1 test) 18ms
13
- ✓ src/commands/__tests__/ai-signal-clarity.test.ts (1 test) 8ms
9
+ ✓ src/utils/__tests__/helpers.test.ts (3 tests) 5ms
14
10
  stdout | src/commands/__tests__/visualize.test.ts > Visualize CLI Action > should generate HTML from specified report
15
11
  Building graph from report...
16
12
 
13
+ ✓ src/commands/__tests__/testability.test.ts (2 tests) 14ms
14
+ ✓ src/commands/__tests__/ai-signal-clarity.test.ts (1 test) 13ms
15
+ ✓ src/commands/__tests__/deps-health.test.ts (1 test) 12ms
16
+ ✓ src/commands/__tests__/doc-drift.test.ts (1 test) 25ms
17
+ ✓ src/commands/__tests__/agent-grounding.test.ts (1 test) 25ms
17
18
  stdout | src/commands/__tests__/visualize.test.ts > Visualize CLI Action > should generate HTML from specified report
18
19
  Generating HTML...
19
20
  ✅ Visualization written to: /Users/pengcao/projects/aiready/packages/cli/visualization.html
@@ -41,8 +42,7 @@ Or specify a custom report:
41
42
  Generating HTML...
42
43
  ✅ Visualization written to: /Users/pengcao/projects/aiready/packages/cli/visualization.html
43
44
 
44
- ✓ src/commands/__tests__/visualize.test.ts (4 tests) 30ms
45
- ✓ src/utils/__tests__/helpers.test.ts (3 tests) 6ms
45
+ ✓ src/commands/__tests__/visualize.test.ts (4 tests) 34ms
46
46
  stdout | src/commands/__tests__/upload.test.ts > Upload CLI Action > should fail if API key is missing
47
47
   Set AIREADY_API_KEY environment variable or use --api-key flag.
48
48
  Get an API key from https://platform.getaiready.dev/dashboard
@@ -58,18 +58,18 @@ Or specify a custom report:
58
58
  Score: 80/100
59
59
 
60
60
  ✓ src/commands/__tests__/upload.test.ts (2 tests) 5ms
61
- ✓ src/commands/__tests__/consistency.test.ts (4 tests) 6ms
62
- ✓ src/commands/__tests__/scan.test.ts (6 tests) 140ms
63
- ✓ src/commands/__tests__/extra-commands.test.ts (8 tests) 122ms
61
+ ✓ src/commands/__tests__/consistency.test.ts (4 tests) 4ms
62
+ ✓ src/commands/__tests__/scan.test.ts (6 tests) 77ms
64
63
  ✓ src/__tests__/unified.test.ts (4 tests) 3ms
65
- ✓ src/__tests__/cli.test.ts (3 tests) 3121ms
66
- ✓ should run unified analysis with both tools  3120ms
67
- ✓ src/__tests__/config-shape.test.ts (3 tests) 3122ms
68
- ✓ should generate a strictly portable AIReadyConfig in summary  3121ms
64
+ ✓ src/commands/__tests__/extra-commands.test.ts (8 tests) 93ms
65
+ ✓ src/__tests__/cli.test.ts (3 tests) 3317ms
66
+ ✓ should run unified analysis with both tools  3316ms
67
+ ✓ src/__tests__/config-shape.test.ts (3 tests) 3318ms
68
+ ✓ should generate a strictly portable AIReadyConfig in summary  3316ms
69
69
 
70
70
   Test Files  14 passed (14)
71
71
   Tests  43 passed (43)
72
-  Start at  18:35:57
73
-  Duration  4.61s (transform 3.16s, setup 0ms, import 7.21s, tests 6.64s, environment 2ms)
72
+  Start at  00:58:55
73
+  Duration  4.20s (transform 1.99s, setup 0ms, import 4.11s, tests 6.94s, environment 1ms)
74
74
 
75
75
  [?25h
package/CONTRIBUTING.md CHANGED
@@ -196,7 +196,7 @@ src/
196
196
  Great places to start:
197
197
 
198
198
  - **New commands**: Add new CLI commands
199
- - **Tool integration**: Integrate new analysis tools
199
+ - **Tool integration**: Integrate new analysis tools. Follow the [Spoke Development Guide](./docs/SPOKE_GUIDE.md) for more details.
200
200
  - **Output formats**: Add new output options (XML, CSV, HTML)
201
201
  - **Configuration**: Improve config file handling
202
202
  - **Performance**: Optimize for large codebases
package/README.md CHANGED
@@ -50,10 +50,15 @@ aiready scan .
50
50
 
51
51
  # Run a specific tool
52
52
  aiready patterns . --similarity 0.6
53
- ````
53
+ ```
54
+
55
+ ## 🛠️ Building Your Own Tool (Spokes)
56
+
57
+ Want to build your own analysis tool that integrates with the AIReady ecosystem? Check out our [Spoke Development Guide](./docs/SPOKE_GUIDE.md).
54
58
 
55
59
  ## 🌐 Platform Integration
56
60
 
61
+
57
62
  Connect your local scans to the [AIReady Dashboard](https://platform.getaiready.dev/dashboard).
58
63
 
59
64
  ### Automatic Upload
@@ -85,3 +90,4 @@ MIT
85
90
  ```
86
91
 
87
92
  ```
93
+ ````
package/dist/cli.js CHANGED
@@ -550,6 +550,24 @@ async function scanAction(directory, options) {
550
550
  case "cost":
551
551
  profileTools = [import_core3.ToolName.PatternDetect, import_core3.ToolName.ContextAnalyzer];
552
552
  break;
553
+ case "logic":
554
+ profileTools = [
555
+ import_core3.ToolName.TestabilityIndex,
556
+ import_core3.ToolName.NamingConsistency,
557
+ import_core3.ToolName.ContextAnalyzer,
558
+ import_core3.ToolName.PatternDetect,
559
+ import_core3.ToolName.ChangeAmplification
560
+ ];
561
+ break;
562
+ case "ui":
563
+ profileTools = [
564
+ import_core3.ToolName.NamingConsistency,
565
+ import_core3.ToolName.ContextAnalyzer,
566
+ import_core3.ToolName.PatternDetect,
567
+ import_core3.ToolName.DocDrift,
568
+ import_core3.ToolName.AiSignalClarity
569
+ ];
570
+ break;
553
571
  case "security":
554
572
  profileTools = [
555
573
  import_core3.ToolName.NamingConsistency,
@@ -619,6 +637,7 @@ async function scanAction(directory, options) {
619
637
  );
620
638
  }
621
639
  };
640
+ const scoringProfile = options.profile || baseOptions.scoring?.profile || "default";
622
641
  const results = await analyzeUnified({
623
642
  ...finalOptions,
624
643
  progressCallback,
@@ -635,9 +654,16 @@ async function scanAction(directory, options) {
635
654
  );
636
655
  let scoringResult;
637
656
  if (options.score || finalOptions.scoring?.showBreakdown) {
638
- scoringResult = await scoreUnified(results, finalOptions);
657
+ scoringResult = await scoreUnified(results, {
658
+ ...finalOptions,
659
+ scoring: {
660
+ ...finalOptions.scoring,
661
+ profile: scoringProfile
662
+ }
663
+ });
639
664
  console.log(import_chalk3.default.bold("\n\u{1F4CA} AI Readiness Overall Score"));
640
665
  console.log(` ${(0, import_core3.formatScore)(scoringResult)}`);
666
+ console.log(import_chalk3.default.dim(` (Scoring Profile: ${scoringProfile})`));
641
667
  if (options.compareTo) {
642
668
  try {
643
669
  const prevReport = JSON.parse(
@@ -735,8 +761,26 @@ async function scanAction(directory, options) {
735
761
  console.log(import_chalk3.default.bold("\nTool breakdown:"));
736
762
  scoringResult.breakdown.forEach((tool) => {
737
763
  const rating = (0, import_core3.getRating)(tool.score);
738
- console.log(` - ${tool.toolName}: ${tool.score}/100 (${rating})`);
764
+ const emoji = (0, import_core3.getRatingDisplay)(rating).emoji;
765
+ console.log(
766
+ ` - ${tool.toolName}: ${tool.score}/100 (${rating}) ${emoji}`
767
+ );
739
768
  });
769
+ const allRecs = scoringResult.breakdown.flatMap(
770
+ (t) => (t.recommendations || []).map((r) => ({ ...r, tool: t.toolName }))
771
+ ).sort((a, b) => b.estimatedImpact - a.estimatedImpact).slice(0, 3);
772
+ if (allRecs.length > 0) {
773
+ console.log(import_chalk3.default.bold("\n\u{1F3AF} Top Actionable Recommendations:"));
774
+ allRecs.forEach((rec, i) => {
775
+ const priorityIcon = rec.priority === "high" ? "\u{1F534}" : rec.priority === "medium" ? "\u{1F7E1}" : "\u{1F535}";
776
+ console.log(
777
+ ` ${i + 1}. ${priorityIcon} ${import_chalk3.default.bold(rec.action)}`
778
+ );
779
+ console.log(
780
+ ` Impact: ${import_chalk3.default.green(`+${rec.estimatedImpact} points`)} to ${rec.tool}`
781
+ );
782
+ });
783
+ }
740
784
  }
741
785
  }
742
786
  const mapToUnifiedReport = (res, scoring) => {
@@ -1869,11 +1913,11 @@ program.command("scan").description(
1869
1913
  "Tools to run (comma-separated: patterns,context,consistency,doc-drift,deps-health,aiSignalClarity,grounding,testability,changeAmplification)"
1870
1914
  ).option(
1871
1915
  "--profile <type>",
1872
- "Scan profile to use (agentic, cost, security, onboarding)"
1916
+ "Scan profile to use (agentic, cost, logic, ui, security, onboarding)"
1873
1917
  ).option(
1874
1918
  "--compare-to <path>",
1875
1919
  "Compare results against a previous AIReady report JSON"
1876
- ).option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").option(
1920
+ ).option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").option("--score", "Calculate and display AI Readiness Score (0-100)").option(
1877
1921
  "--no-score",
1878
1922
  "Disable calculating AI Readiness Score (enabled by default)"
1879
1923
  ).option("--weights <weights>", "Custom scoring weights").option("--threshold <score>", "Fail CI/CD if score below threshold (0-100)").option(
package/dist/cli.mjs CHANGED
@@ -23,6 +23,7 @@ import {
23
23
  formatScore,
24
24
  calculateTokenBudget,
25
25
  getRating,
26
+ getRatingDisplay,
26
27
  getRepoMetadata,
27
28
  Severity,
28
29
  ToolName,
@@ -289,6 +290,24 @@ async function scanAction(directory, options) {
289
290
  case "cost":
290
291
  profileTools = [ToolName.PatternDetect, ToolName.ContextAnalyzer];
291
292
  break;
293
+ case "logic":
294
+ profileTools = [
295
+ ToolName.TestabilityIndex,
296
+ ToolName.NamingConsistency,
297
+ ToolName.ContextAnalyzer,
298
+ ToolName.PatternDetect,
299
+ ToolName.ChangeAmplification
300
+ ];
301
+ break;
302
+ case "ui":
303
+ profileTools = [
304
+ ToolName.NamingConsistency,
305
+ ToolName.ContextAnalyzer,
306
+ ToolName.PatternDetect,
307
+ ToolName.DocDrift,
308
+ ToolName.AiSignalClarity
309
+ ];
310
+ break;
292
311
  case "security":
293
312
  profileTools = [
294
313
  ToolName.NamingConsistency,
@@ -358,6 +377,7 @@ async function scanAction(directory, options) {
358
377
  );
359
378
  }
360
379
  };
380
+ const scoringProfile = options.profile || baseOptions.scoring?.profile || "default";
361
381
  const results = await analyzeUnified({
362
382
  ...finalOptions,
363
383
  progressCallback,
@@ -374,9 +394,16 @@ async function scanAction(directory, options) {
374
394
  );
375
395
  let scoringResult;
376
396
  if (options.score || finalOptions.scoring?.showBreakdown) {
377
- scoringResult = await scoreUnified(results, finalOptions);
397
+ scoringResult = await scoreUnified(results, {
398
+ ...finalOptions,
399
+ scoring: {
400
+ ...finalOptions.scoring,
401
+ profile: scoringProfile
402
+ }
403
+ });
378
404
  console.log(chalk3.bold("\n\u{1F4CA} AI Readiness Overall Score"));
379
405
  console.log(` ${formatScore(scoringResult)}`);
406
+ console.log(chalk3.dim(` (Scoring Profile: ${scoringProfile})`));
380
407
  if (options.compareTo) {
381
408
  try {
382
409
  const prevReport = JSON.parse(
@@ -474,8 +501,26 @@ async function scanAction(directory, options) {
474
501
  console.log(chalk3.bold("\nTool breakdown:"));
475
502
  scoringResult.breakdown.forEach((tool) => {
476
503
  const rating = getRating(tool.score);
477
- console.log(` - ${tool.toolName}: ${tool.score}/100 (${rating})`);
504
+ const emoji = getRatingDisplay(rating).emoji;
505
+ console.log(
506
+ ` - ${tool.toolName}: ${tool.score}/100 (${rating}) ${emoji}`
507
+ );
478
508
  });
509
+ const allRecs = scoringResult.breakdown.flatMap(
510
+ (t) => (t.recommendations || []).map((r) => ({ ...r, tool: t.toolName }))
511
+ ).sort((a, b) => b.estimatedImpact - a.estimatedImpact).slice(0, 3);
512
+ if (allRecs.length > 0) {
513
+ console.log(chalk3.bold("\n\u{1F3AF} Top Actionable Recommendations:"));
514
+ allRecs.forEach((rec, i) => {
515
+ const priorityIcon = rec.priority === "high" ? "\u{1F534}" : rec.priority === "medium" ? "\u{1F7E1}" : "\u{1F535}";
516
+ console.log(
517
+ ` ${i + 1}. ${priorityIcon} ${chalk3.bold(rec.action)}`
518
+ );
519
+ console.log(
520
+ ` Impact: ${chalk3.green(`+${rec.estimatedImpact} points`)} to ${rec.tool}`
521
+ );
522
+ });
523
+ }
479
524
  }
480
525
  }
481
526
  const mapToUnifiedReport = (res, scoring) => {
@@ -1630,11 +1675,11 @@ program.command("scan").description(
1630
1675
  "Tools to run (comma-separated: patterns,context,consistency,doc-drift,deps-health,aiSignalClarity,grounding,testability,changeAmplification)"
1631
1676
  ).option(
1632
1677
  "--profile <type>",
1633
- "Scan profile to use (agentic, cost, security, onboarding)"
1678
+ "Scan profile to use (agentic, cost, logic, ui, security, onboarding)"
1634
1679
  ).option(
1635
1680
  "--compare-to <path>",
1636
1681
  "Compare results against a previous AIReady report JSON"
1637
- ).option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").option(
1682
+ ).option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").option("--score", "Calculate and display AI Readiness Score (0-100)").option(
1638
1683
  "--no-score",
1639
1684
  "Disable calculating AI Readiness Score (enabled by default)"
1640
1685
  ).option("--weights <weights>", "Custom scoring weights").option("--threshold <score>", "Fail CI/CD if score below threshold (0-100)").option(
@@ -0,0 +1,184 @@
1
+ # Building a New AIReady Spoke
2
+
3
+ This guide explains how to build a new analysis tool ("spoke") and integrate it into the AIReady ecosystem. AIReady uses a hub-and-spoke architecture where independent tools are coordinated by a central CLI and Hub (@aiready/core).
4
+
5
+ ## 🚀 Getting Started
6
+
7
+ ### 1. Create Package Structure
8
+
9
+ If you are contributing to the monorepo:
10
+
11
+ ```bash
12
+ mkdir -p packages/your-tool/src
13
+ cd packages/your-tool
14
+ ```
15
+
16
+ ### 2. Create `package.json`
17
+
18
+ Your tool should depend on `@aiready/core` for shared types and utilities.
19
+
20
+ ```json
21
+ {
22
+ "name": "@aiready/your-tool",
23
+ "version": "0.1.0",
24
+ "description": "Brief description of what this tool does",
25
+ "main": "./dist/index.js",
26
+ "module": "./dist/index.mjs",
27
+ "types": "./dist/index.d.ts",
28
+ "bin": {
29
+ "aiready-yourtool": "./dist/cli.js"
30
+ },
31
+ "scripts": {
32
+ "build": "tsup src/index.ts src/cli.ts --format cjs,esm --dts",
33
+ "dev": "tsup src/index.ts src/cli.ts --format cjs,esm --dts --watch",
34
+ "test": "vitest run",
35
+ "lint": "eslint src",
36
+ "clean": "rm -rf dist"
37
+ },
38
+ "dependencies": {
39
+ "@aiready/core": "workspace:*",
40
+ "commander": "^12.1.0"
41
+ },
42
+ "devDependencies": {
43
+ "tsup": "^8.3.5"
44
+ },
45
+ "keywords": ["aiready", "your-keywords"],
46
+ "license": "MIT"
47
+ }
48
+ ```
49
+
50
+ ### 3. Implement the Analysis Logic
51
+
52
+ Create `src/analyzer.ts` to contain your core logic.
53
+
54
+ ```typescript
55
+ import { scanFiles, readFileContent } from '@aiready/core';
56
+ import type {
57
+ AnalysisResult,
58
+ Issue,
59
+ ScanOptions,
60
+ SpokeOutput,
61
+ } from '@aiready/core';
62
+
63
+ export async function analyzeYourTool(
64
+ options: ScanOptions
65
+ ): Promise<SpokeOutput> {
66
+ const files = await scanFiles(options);
67
+ const results: AnalysisResult[] = [];
68
+
69
+ // Your analysis logic here
70
+ // 1. Iterate through files
71
+ // 2. Detect issues
72
+ // 3. Return standardized AnalysisResult[]
73
+
74
+ return {
75
+ results,
76
+ summary: {
77
+ totalFiles: files.length,
78
+ totalIssues: results.reduce((acc, r) => acc + r.issues.length, 0),
79
+ // ... other summary stats
80
+ },
81
+ };
82
+ }
83
+ ```
84
+
85
+ ### 4. Implement ToolProvider and Register
86
+
87
+ Every spoke must implement the `ToolProvider` interface and register with the global `ToolRegistry` so that it is automatically discovered by the unified CLI.
88
+
89
+ 1. **Create `src/provider.ts`**:
90
+
91
+ ```typescript
92
+ import {
93
+ ToolProvider,
94
+ ToolName,
95
+ SpokeOutput,
96
+ ScanOptions,
97
+ ToolScoringOutput,
98
+ } from '@aiready/core';
99
+ import { analyzeYourTool } from './analyzer';
100
+
101
+ export const YourToolProvider: ToolProvider = {
102
+ id: ToolName.YourToolID, // Use an existing ToolName or request a new one
103
+ alias: ['your-alias'],
104
+
105
+ async analyze(options: ScanOptions): Promise<SpokeOutput> {
106
+ const output = await analyzeYourTool(options);
107
+ return {
108
+ ...output,
109
+ metadata: { toolName: ToolName.YourToolID, version: '0.1.0' },
110
+ };
111
+ },
112
+
113
+ score(output: SpokeOutput, options: ScanOptions): ToolScoringOutput {
114
+ // Implement scoring logic (0-100)
115
+ return {
116
+ score: 100, // Example
117
+ metrics: output.summary,
118
+ };
119
+ },
120
+
121
+ defaultWeight: 10,
122
+ };
123
+ ```
124
+
125
+ 2. **Register in `src/index.ts`**:
126
+
127
+ ```typescript
128
+ import { ToolRegistry } from '@aiready/core';
129
+ import { YourToolProvider } from './provider';
130
+
131
+ // Register with global registry for automatic CLI discovery
132
+ ToolRegistry.register(YourToolProvider);
133
+
134
+ export { YourToolProvider };
135
+ export * from './analyzer';
136
+ ```
137
+
138
+ ### 5. Create Standalone CLI (`src/cli.ts`)
139
+
140
+ ```typescript
141
+ #!/usr/bin/env node
142
+ import { Command } from 'commander';
143
+ import { analyzeYourTool } from './analyzer';
144
+ import chalk from 'chalk';
145
+
146
+ const program = new Command();
147
+
148
+ program
149
+ .name('aiready-yourtool')
150
+ .description('Description of your tool')
151
+ .version('0.1.0')
152
+ .argument('<directory>', 'Directory to analyze')
153
+ .action(async (directory, options) => {
154
+ console.log(chalk.blue('🔍 Analyzing...\n'));
155
+ const output = await analyzeYourTool({ rootDir: directory });
156
+ console.log(JSON.stringify(output, null, 2));
157
+ });
158
+
159
+ program.parse();
160
+ ```
161
+
162
+ ## 📋 Standard Specs to Follow
163
+
164
+ To ensure your tool integrates perfectly with the AIReady ecosystem, it must follow these rules:
165
+
166
+ 1. **Standard Options**: Support `--include`, `--exclude`, and `--output` (standardized via `ScanOptions`).
167
+ 2. **No Direct Dependencies**: Spokes should never depend on other spokes. Only depend on `@aiready/core`.
168
+ 3. **Standard Issue Types**: Use `IssueType` from `@aiready/core` whenever possible.
169
+ 4. **Severity Levels**: Use `critical`, `major`, `minor`, and `info`.
170
+ 5. **Non-Blocking**: The `analyze` function should be asynchronous and handle large codebases efficiently.
171
+
172
+ ## 🧪 Testing
173
+
174
+ Use `vitest` for unit and integration tests. Ensure you test your `ToolProvider` implementation using the `validateSpokeOutput` utility from `@aiready/core`.
175
+
176
+ ```typescript
177
+ import { validateSpokeOutput } from '@aiready/core/types/contract';
178
+
179
+ test('output matches AIReady contract', async () => {
180
+ const output = await analyzeYourTool({ rootDir: './test' });
181
+ const validation = validateSpokeOutput('your-tool', output);
182
+ expect(validation.valid).toBe(true);
183
+ });
184
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiready/cli",
3
- "version": "0.13.0",
3
+ "version": "0.13.2",
4
4
  "description": "Unified CLI for AIReady analysis tools",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -11,18 +11,18 @@
11
11
  "dependencies": {
12
12
  "chalk": "^5.3.0",
13
13
  "commander": "^14.0.0",
14
+ "@aiready/consistency": "0.19.0",
14
15
  "@aiready/clawmart": "0.1.2",
15
16
  "@aiready/context-analyzer": "0.20.0",
16
17
  "@aiready/agent-grounding": "0.12.0",
17
- "@aiready/consistency": "0.19.0",
18
- "@aiready/core": "0.22.0",
19
- "@aiready/deps": "0.12.0",
18
+ "@aiready/core": "0.22.1",
20
19
  "@aiready/doc-drift": "0.12.0",
21
20
  "@aiready/change-amplification": "0.12.0",
21
+ "@aiready/deps": "0.12.0",
22
22
  "@aiready/ai-signal-clarity": "0.12.0",
23
- "@aiready/pattern-detect": "0.15.0",
24
23
  "@aiready/visualizer": "0.5.0",
25
- "@aiready/testability": "0.5.0"
24
+ "@aiready/testability": "0.5.0",
25
+ "@aiready/pattern-detect": "0.15.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/node": "^24.0.0",