File size: 5,485 Bytes
a5d1c1b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61b1da1
a5d1c1b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61b1da1
a5d1c1b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61b1da1
a5d1c1b
 
 
 
 
 
 
 
 
 
 
 
 
 
61b1da1
a5d1c1b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61b1da1
a5d1c1b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import fs from 'node:fs';
import path from 'node:path';
import os from 'node:os';
import { projectConfigManager } from '../src/config/project-config.js';
import { cacheService, computeSha256 } from '../src/services/cache-service.js';
import { syncService } from '../src/services/sync-service.js';
import { handleStatus } from '../src/commands/status.js';
import { handleDiff } from '../src/commands/diff.js';
import { handleReset } from '../src/commands/reset.js';
import { handleKnowledgeList, handleKnowledgeShow } from '../src/commands/knowledge.js';
import { handleSkillsList, handleSkillsShow } from '../src/commands/skills.js';

describe('CLI Commands & Workflows', () => {
  let testDir: string;
  let originalCwd: string;

  beforeEach(() => {
    testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'evb-cmd-test-'));
    originalCwd = process.cwd();
    process.chdir(testDir);

    // Initialize clean mock workspace
    projectConfigManager.writeConfig(testDir, {
      version: 1,
      source: {
        repository: 'https://github.com/acme/project-intelligence',
        branch: 'main',
      },
    });

    const fileContent = '# Authentication Architecture\nDetails about JWT.';
    cacheService.writeCachedFile(testDir, 'knowledge/auth.md', fileContent);
    cacheService.writeCachedFile(
      testDir,
      'skills/deploy/SKILL.md',
      '---\nname: deploy\ndescription: Production deployment runbook\n---\n# Deploy Runbook'
    );

    projectConfigManager.writeState(testDir, {
      last_sync: new Date().toISOString(),
      source_revision: 'main',
      cached_files: 2,
      files: {
        'knowledge/auth.md': computeSha256(fileContent),
        'skills/deploy/SKILL.md': computeSha256(
          '---\nname: deploy\ndescription: Production deployment runbook\n---\n# Deploy Runbook'
        ),
      },
    });
  });

  afterEach(() => {
    process.chdir(originalCwd);
    fs.rmSync(testDir, { recursive: true, force: true });
    vi.restoreAllMocks();
  });

  it('runs evb status in JSON mode', async () => {
    const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
    await handleStatus({ json: true });

    expect(consoleSpy).toHaveBeenCalled();
    const output = JSON.parse(consoleSpy.mock.calls[0][0]);
    expect(output.repository).toBe('https://github.com/acme/project-intelligence');
    expect(output.isDirty).toBe(false);
    expect(output.cachedFilesCount).toBe(2);
    expect(output.knowledgeCount).toBe(1);
    expect(output.skillsCount).toBe(1);
  });

  it('detects dirty state on status and diff after local edit', async () => {
    cacheService.writeCachedFile(testDir, 'knowledge/auth.md', '# Auth v2 with RS256');

    const statusSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
    await handleStatus({ json: true });

    const statusJson = JSON.parse(statusSpy.mock.calls[0][0]);
    expect(statusJson.isDirty).toBe(true);
    expect(statusJson.modifications.modified).toEqual(['knowledge/auth.md']);

    // Check diff
    const diffSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
    await handleDiff(undefined, { json: true });

    const diffJson = JSON.parse(diffSpy.mock.calls[0][0]);
    expect(diffJson.isDirty).toBe(true);
    expect(diffJson.diffs).toHaveLength(1);
    expect(diffJson.diffs[0].file).toBe('knowledge/auth.md');
    expect(diffJson.diffs[0].type).toBe('modified');
  });

  it('runs evb knowledge list and show', async () => {
    const listSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
    await handleKnowledgeList({ json: true });

    const listJson = JSON.parse(listSpy.mock.calls[0][0]);
    expect(listJson.total).toBe(2); // auth.md and deploy/SKILL.md
    expect(listJson.documents.some((d: any) => d.path === 'knowledge/auth.md')).toBe(true);

    const showSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
    await handleKnowledgeShow('knowledge/auth.md', { json: true });

    const showJson = JSON.parse(showSpy.mock.calls[0][0]);
    expect(showJson.content).toContain('Authentication Architecture');
  });

  it('runs evb skills list and show', async () => {
    const listSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
    await handleSkillsList({ json: true });

    const listJson = JSON.parse(listSpy.mock.calls[0][0]);
    expect(listJson.total).toBe(1);
    expect(listJson.skills[0].id).toBe('deploy');
    expect(listJson.skills[0].description).toBe('Production deployment runbook');

    const showSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
    await handleSkillsShow('deploy', { json: true });

    const showJson = JSON.parse(showSpy.mock.calls[0][0]);
    expect(showJson.content).toContain('Deploy Runbook');
  });

  it('resets local modifications with evb reset', async () => {
    cacheService.writeCachedFile(testDir, 'knowledge/auth.md', '# Dirty modifications');
    expect(cacheService.computeDirtyState(testDir).isDirty).toBe(true);

    // Mock syncService.sync
    vi.spyOn(syncService, 'sync').mockResolvedValue({
      updated: 1,
      unchanged: 1,
      removed: 0,
      total: 2,
    });

    const resetSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
    await handleReset({ yes: true, json: true });

    expect(resetSpy).toHaveBeenCalled();
    const result = JSON.parse(resetSpy.mock.calls[0][0]);
    expect(result.status).toBe('reset_complete');
  });
});