@dot-ai/cli 0.5.2 → 0.7.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.
@@ -2,14 +2,6 @@ import { describe, it, expect, beforeEach } from 'vitest';
2
2
  import { mkdtemp, mkdir, writeFile, readFile, access } from 'node:fs/promises';
3
3
  import { tmpdir } from 'node:os';
4
4
  import { join } from 'node:path';
5
- import {
6
- loadConfig,
7
- registerDefaults,
8
- clearProviders,
9
- createProviders,
10
- boot,
11
- enrich,
12
- } from '@dot-ai/core';
13
5
 
14
6
  // ── init command logic ───────────────────────────────────────────────────────
15
7
 
@@ -18,8 +10,8 @@ async function runInit(root: string): Promise<string> {
18
10
  const lines: string[] = [];
19
11
 
20
12
  try {
21
- await access(join(aiDir, 'dot-ai.yml'));
22
- lines.push('.ai/dot-ai.yml already exists. Nothing to do.');
13
+ await access(join(aiDir, 'settings.json'));
14
+ lines.push('.ai/settings.json already exists. Nothing to do.');
23
15
  return lines.join('\n');
24
16
  } catch {
25
17
  // Doesn't exist, create it
@@ -27,21 +19,10 @@ async function runInit(root: string): Promise<string> {
27
19
 
28
20
  await mkdir(aiDir, { recursive: true });
29
21
 
30
- await writeFile(join(aiDir, 'dot-ai.yml'), [
31
- '# dot-ai configuration',
32
- '# Uncomment and customize providers as needed.',
33
- '# Default: file-based providers reading from .ai/ directory.',
34
- '#',
35
- '# memory:',
36
- '# use: "@dot-ai/provider-file-memory"',
37
- '#',
38
- '# skills:',
39
- '# use: "@dot-ai/provider-file-skills"',
40
- '#',
41
- '# routing:',
42
- '# use: "@dot-ai/provider-rules-routing"',
43
- '',
44
- ].join('\n'));
22
+ await writeFile(join(aiDir, 'settings.json'), JSON.stringify({
23
+ extensions: [],
24
+ packages: [],
25
+ }, null, 2));
45
26
 
46
27
  await writeFile(join(aiDir, 'AGENTS.md'), [
47
28
  '# AGENTS.md',
@@ -55,82 +36,12 @@ async function runInit(root: string): Promise<string> {
55
36
  ].join('\n'));
56
37
 
57
38
  lines.push('Created:');
58
- lines.push(' .ai/dot-ai.yml (config)');
39
+ lines.push(' .ai/settings.json (config)');
59
40
  lines.push(' .ai/AGENTS.md (template)');
60
- lines.push('\nNext: add SOUL.md, USER.md, skills/, memory/ as needed.');
41
+ lines.push('\nNext: add extensions to .ai/extensions/ or settings.json as needed.');
61
42
  return lines.join('\n');
62
43
  }
63
44
 
64
- // ── helpers ──────────────────────────────────────────────────────────────────
65
-
66
- async function setupAiDir(root: string): Promise<void> {
67
- const ai = join(root, '.ai');
68
- await mkdir(ai, { recursive: true });
69
- await mkdir(join(ai, 'memory'), { recursive: true });
70
- await mkdir(join(ai, 'skills', 'ts-standards'), { recursive: true });
71
- await mkdir(join(ai, 'skills', 'code-review'), { recursive: true });
72
- await mkdir(join(ai, 'tools'), { recursive: true });
73
-
74
- await writeFile(join(ai, 'AGENTS.md'), '# AGENTS.md\n\nYou are Kiwi.');
75
- await writeFile(join(ai, 'SOUL.md'), '# SOUL.md\n\nBe genuine.');
76
- await writeFile(join(ai, 'USER.md'), '# USER.md\n\nJo, developer.');
77
- await writeFile(join(ai, 'IDENTITY.md'), '# IDENTITY.md\n\nName: Kiwi');
78
-
79
- await writeFile(join(ai, 'skills', 'ts-standards', 'SKILL.md'), [
80
- '---',
81
- 'description: TypeScript coding standards',
82
- 'labels: [typescript, code, standards]',
83
- 'triggers: [auto]',
84
- '---',
85
- '',
86
- '## TypeScript Standards',
87
- '- Use strict mode',
88
- ].join('\n'));
89
-
90
- await writeFile(join(ai, 'skills', 'code-review', 'SKILL.md'), [
91
- '---',
92
- 'description: Code review guidelines',
93
- 'labels: [review, code-fix, bug]',
94
- 'triggers: [auto]',
95
- '---',
96
- '',
97
- '## Code Review',
98
- '- Check for edge cases',
99
- ].join('\n'));
100
-
101
- await writeFile(join(ai, 'tools', 'eslint.yaml'), [
102
- 'name: eslint',
103
- 'description: TypeScript linter',
104
- 'labels: [typescript, lint, code]',
105
- ].join('\n'));
106
-
107
- await writeFile(join(ai, 'memory', '2026-03-01.md'), [
108
- '- Fixed auth middleware N+1 query bug',
109
- '- Decided to use JWT for auth tokens',
110
- ].join('\n'));
111
-
112
- await writeFile(join(ai, 'dot-ai.yml'), [
113
- '# dot-ai config',
114
- 'memory:',
115
- ' use: @dot-ai/provider-file-memory',
116
- ` root: ${root}`,
117
- 'skills:',
118
- ' use: @dot-ai/provider-file-skills',
119
- ` root: ${root}`,
120
- 'identity:',
121
- ' use: @dot-ai/provider-file-identity',
122
- ` root: ${root}`,
123
- 'routing:',
124
- ' use: @dot-ai/provider-rules-routing',
125
- 'tasks:',
126
- ' use: @dot-ai/provider-file-tasks',
127
- ` root: ${root}`,
128
- 'tools:',
129
- ' use: @dot-ai/provider-file-tools',
130
- ` root: ${root}`,
131
- ].join('\n'));
132
- }
133
-
134
45
  // ── init tests ───────────────────────────────────────────────────────────────
135
46
 
136
47
  describe('init command', () => {
@@ -140,10 +51,12 @@ describe('init command', () => {
140
51
  root = await mkdtemp(join(tmpdir(), 'dot-ai-cli-init-'));
141
52
  });
142
53
 
143
- it('creates .ai/dot-ai.yml in empty directory', async () => {
54
+ it('creates .ai/settings.json in empty directory', async () => {
144
55
  await runInit(root);
145
- const content = await readFile(join(root, '.ai', 'dot-ai.yml'), 'utf8');
146
- expect(content).toContain('# dot-ai configuration');
56
+ const content = await readFile(join(root, '.ai', 'settings.json'), 'utf8');
57
+ const parsed = JSON.parse(content);
58
+ expect(parsed.extensions).toEqual([]);
59
+ expect(parsed.packages).toEqual([]);
147
60
  });
148
61
 
149
62
  it('creates .ai/AGENTS.md in empty directory', async () => {
@@ -152,195 +65,31 @@ describe('init command', () => {
152
65
  expect(content).toContain('# AGENTS.md');
153
66
  });
154
67
 
155
- it('dot-ai.yml contains commented provider examples', async () => {
156
- await runInit(root);
157
- const content = await readFile(join(root, '.ai', 'dot-ai.yml'), 'utf8');
158
- expect(content).toContain('@dot-ai/provider-file-memory');
159
- expect(content).toContain('@dot-ai/provider-file-skills');
160
- expect(content).toContain('@dot-ai/provider-rules-routing');
161
- });
162
-
163
68
  it('AGENTS.md is a template with ## Rules section', async () => {
164
69
  await runInit(root);
165
70
  const content = await readFile(join(root, '.ai', 'AGENTS.md'), 'utf8');
166
71
  expect(content).toContain('## Rules');
167
72
  });
168
73
 
169
- it('does nothing if dot-ai.yml already exists', async () => {
74
+ it('does nothing if settings.json already exists', async () => {
170
75
  await mkdir(join(root, '.ai'), { recursive: true });
171
- await writeFile(join(root, '.ai', 'dot-ai.yml'), '# existing config\n');
76
+ await writeFile(join(root, '.ai', 'settings.json'), '{"existing": true}\n');
172
77
 
173
78
  const output = await runInit(root);
174
79
 
175
80
  expect(output).toContain('already exists');
176
- // File should be unchanged
177
- const content = await readFile(join(root, '.ai', 'dot-ai.yml'), 'utf8');
178
- expect(content).toBe('# existing config\n');
81
+ const content = await readFile(join(root, '.ai', 'settings.json'), 'utf8');
82
+ expect(content).toBe('{"existing": true}\n');
179
83
  });
180
84
 
181
- it('works if .ai/ directory already exists without dot-ai.yml', async () => {
85
+ it('works if .ai/ directory already exists without settings.json', async () => {
182
86
  await mkdir(join(root, '.ai'), { recursive: true });
183
87
  await writeFile(join(root, '.ai', 'AGENTS.md'), '# existing\n');
184
88
 
185
89
  await runInit(root);
186
90
 
187
- // dot-ai.yml should be created
188
- const content = await readFile(join(root, '.ai', 'dot-ai.yml'), 'utf8');
189
- expect(content).toContain('# dot-ai configuration');
190
- });
191
- });
192
-
193
- // ── boot command logic ────────────────────────────────────────────────────────
194
-
195
- describe('boot command (core pipeline)', () => {
196
- let root: string;
197
-
198
- beforeEach(async () => {
199
- clearProviders();
200
- registerDefaults();
201
- root = await mkdtemp(join(tmpdir(), 'dot-ai-cli-boot-'));
202
- await setupAiDir(root);
203
- });
204
-
205
- it('loads config and creates providers', async () => {
206
- const config = await loadConfig(root);
207
- const providers = await createProviders(config);
208
- expect(providers.memory).toBeDefined();
209
- expect(providers.skills).toBeDefined();
210
- expect(providers.identity).toBeDefined();
211
- expect(providers.routing).toBeDefined();
212
- expect(providers.tools).toBeDefined();
213
- });
214
-
215
- it('boot returns identities, skills, vocabulary', async () => {
216
- const config = await loadConfig(root);
217
- const providers = await createProviders(config);
218
- const cache = await boot(providers);
219
-
220
- expect(cache.identities.length).toBe(4); // AGENTS, SOUL, USER, IDENTITY
221
- expect(cache.skills.length).toBe(2); // ts-standards, code-review
222
- expect(cache.vocabulary.length).toBeGreaterThan(0);
223
- });
224
-
225
- it('vocabulary contains labels from skills and tools', async () => {
226
- const config = await loadConfig(root);
227
- const providers = await createProviders(config);
228
- const cache = await boot(providers);
229
-
230
- expect(cache.vocabulary).toContain('typescript');
231
- expect(cache.vocabulary).toContain('code');
232
- expect(cache.vocabulary).toContain('bug');
233
- });
234
-
235
- it('identities have correct types', async () => {
236
- const config = await loadConfig(root);
237
- const providers = await createProviders(config);
238
- const cache = await boot(providers);
239
-
240
- const types = cache.identities.map(i => i.type);
241
- expect(types).toContain('agents');
242
- expect(types).toContain('soul');
243
- expect(types).toContain('user');
244
- expect(types).toContain('identity');
245
- });
246
- });
247
-
248
- // ── trace command logic ───────────────────────────────────────────────────────
249
-
250
- describe('trace command (enrich pipeline)', () => {
251
- let root: string;
252
-
253
- beforeEach(async () => {
254
- clearProviders();
255
- registerDefaults();
256
- root = await mkdtemp(join(tmpdir(), 'dot-ai-cli-trace-'));
257
- await setupAiDir(root);
258
- });
259
-
260
- it('enrich returns labels matching prompt vocabulary', async () => {
261
- const config = await loadConfig(root);
262
- const providers = await createProviders(config);
263
- const cache = await boot(providers);
264
-
265
- const ctx = await enrich('Fix the TypeScript bug', providers, cache);
266
-
267
- expect(ctx.labels.some(l => l.name === 'typescript')).toBe(true);
268
- expect(ctx.labels.some(l => l.name === 'bug')).toBe(true);
269
- });
270
-
271
- it('enrich returns matched skills for typescript prompt', async () => {
272
- const config = await loadConfig(root);
273
- const providers = await createProviders(config);
274
- const cache = await boot(providers);
275
-
276
- const ctx = await enrich('Fix the TypeScript code', providers, cache);
277
-
278
- expect(ctx.skills.some(s => s.name === 'ts-standards')).toBe(true);
279
- });
280
-
281
- it('enrich returns matched tools for typescript prompt', async () => {
282
- const config = await loadConfig(root);
283
- const providers = await createProviders(config);
284
- const cache = await boot(providers);
285
-
286
- const ctx = await enrich('Fix the TypeScript bug', providers, cache);
287
-
288
- expect(ctx.tools.some(t => t.name === 'eslint')).toBe(true);
289
- });
290
-
291
- it('enrich returns memories related to prompt', async () => {
292
- const config = await loadConfig(root);
293
- const providers = await createProviders(config);
294
- const cache = await boot(providers);
295
-
296
- const ctx = await enrich('Fix the auth bug', providers, cache);
297
-
298
- expect(ctx.memories.some(m => m.content.includes('auth'))).toBe(true);
299
- });
300
-
301
- it('enrich returns routing result', async () => {
302
- const config = await loadConfig(root);
303
- const providers = await createProviders(config);
304
- const cache = await boot(providers);
305
-
306
- const ctx = await enrich('Fix the bug', providers, cache);
307
-
308
- expect(ctx.routing.model).toBeDefined();
309
- expect(ctx.routing.reason).toBeDefined();
310
- });
311
-
312
- it('enrich returns identities from boot cache', async () => {
313
- const config = await loadConfig(root);
314
- const providers = await createProviders(config);
315
- const cache = await boot(providers);
316
-
317
- const ctx = await enrich('anything', providers, cache);
318
-
319
- expect(ctx.identities.length).toBe(4);
320
- });
321
-
322
- it('enrich with unrelated prompt returns no matched skills', async () => {
323
- const config = await loadConfig(root);
324
- const providers = await createProviders(config);
325
- const cache = await boot(providers);
326
-
327
- // "hello world" doesn't match any known label
328
- const ctx = await enrich('hello world', providers, cache);
329
-
330
- expect(ctx.labels.length).toBe(0);
331
- expect(ctx.skills.length).toBe(0);
332
- expect(ctx.tools.length).toBe(0);
333
- });
334
-
335
- it('different prompts match different skills', async () => {
336
- const config = await loadConfig(root);
337
- const providers = await createProviders(config);
338
- const cache = await boot(providers);
339
-
340
- const tsCtx = await enrich('Fix TypeScript code', providers, cache);
341
- const reviewCtx = await enrich('Review this code for bugs', providers, cache);
342
-
343
- expect(tsCtx.skills.some(s => s.name === 'ts-standards')).toBe(true);
344
- expect(reviewCtx.skills.some(s => s.name === 'code-review')).toBe(true);
91
+ const content = await readFile(join(root, '.ai', 'settings.json'), 'utf8');
92
+ const parsed = JSON.parse(content);
93
+ expect(parsed.extensions).toEqual([]);
345
94
  });
346
95
  });