add-skill-kit 3.2.4 → 3.2.6
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.
- package/README.md +179 -119
- package/bin/lib/commands/help.js +0 -4
- package/bin/lib/commands/install.js +129 -9
- package/bin/lib/ui.js +1 -1
- package/lib/agent-cli/__tests__/adaptive_engine.test.js +190 -0
- package/lib/agent-cli/__tests__/integration/cross_script.test.js +222 -0
- package/lib/agent-cli/__tests__/integration/full_cycle.test.js +230 -0
- package/lib/agent-cli/__tests__/pattern_analyzer.test.js +173 -0
- package/lib/agent-cli/__tests__/pre_execution_check.test.js +167 -0
- package/lib/agent-cli/__tests__/skill_injector.test.js +191 -0
- package/lib/agent-cli/bin/{ag-smart.js → agent.js} +48 -15
- package/lib/agent-cli/dashboard/dashboard_server.js +340 -0
- package/lib/agent-cli/dashboard/index.html +538 -0
- package/lib/agent-cli/lib/audit.js +2 -2
- package/lib/agent-cli/lib/auto-learn.js +8 -8
- package/lib/agent-cli/lib/eslint-fix.js +1 -1
- package/lib/agent-cli/lib/fix.js +5 -5
- package/lib/agent-cli/lib/hooks/install-hooks.js +4 -4
- package/lib/agent-cli/lib/hooks/lint-learn.js +4 -4
- package/lib/agent-cli/lib/learn.js +10 -10
- package/lib/agent-cli/lib/recall.js +1 -1
- package/lib/agent-cli/lib/settings.js +24 -0
- package/lib/agent-cli/lib/skill-learn.js +2 -2
- package/lib/agent-cli/lib/stats.js +3 -3
- package/lib/agent-cli/lib/ui/dashboard-ui.js +103 -4
- package/lib/agent-cli/lib/ui/index.js +36 -6
- package/lib/agent-cli/lib/watcher.js +2 -2
- package/lib/agent-cli/package.json +4 -4
- package/lib/agent-cli/scripts/adaptive_engine.js +381 -0
- package/lib/agent-cli/scripts/dashboard_server.js +224 -0
- package/lib/agent-cli/scripts/error_sensor.js +565 -0
- package/lib/agent-cli/scripts/learn_from_failure.js +225 -0
- package/lib/agent-cli/scripts/pattern_analyzer.js +781 -0
- package/lib/agent-cli/scripts/pre_execution_check.js +623 -0
- package/lib/agent-cli/scripts/rule_sharing.js +374 -0
- package/lib/agent-cli/scripts/skill_injector.js +387 -0
- package/lib/agent-cli/scripts/success_sensor.js +500 -0
- package/lib/agent-cli/scripts/user_correction_sensor.js +426 -0
- package/lib/agent-cli/services/auto-learn-service.js +247 -0
- package/lib/agent-cli/src/MIGRATION.md +418 -0
- package/lib/agent-cli/src/README.md +367 -0
- package/lib/agent-cli/src/core/evolution/evolution-signal.js +42 -0
- package/lib/agent-cli/src/core/evolution/index.js +17 -0
- package/lib/agent-cli/src/core/evolution/review-gate.js +40 -0
- package/lib/agent-cli/src/core/evolution/signal-detector.js +137 -0
- package/lib/agent-cli/src/core/evolution/signal-queue.js +79 -0
- package/lib/agent-cli/src/core/evolution/threshold-checker.js +79 -0
- package/lib/agent-cli/src/core/index.js +15 -0
- package/lib/agent-cli/src/core/learning/cognitive-enhancer.js +282 -0
- package/lib/agent-cli/src/core/learning/index.js +12 -0
- package/lib/agent-cli/src/core/learning/lesson-synthesizer.js +83 -0
- package/lib/agent-cli/src/core/scanning/index.js +14 -0
- package/lib/agent-cli/src/data/index.js +13 -0
- package/lib/agent-cli/src/data/repositories/index.js +8 -0
- package/lib/agent-cli/src/data/repositories/lesson-repository.js +130 -0
- package/lib/agent-cli/src/data/repositories/signal-repository.js +119 -0
- package/lib/agent-cli/src/data/storage/index.js +8 -0
- package/lib/agent-cli/src/data/storage/json-storage.js +64 -0
- package/lib/agent-cli/src/data/storage/yaml-storage.js +66 -0
- package/lib/agent-cli/src/infrastructure/index.js +13 -0
- package/lib/agent-cli/src/presentation/formatters/skill-formatter.js +232 -0
- package/lib/agent-cli/src/services/export-service.js +162 -0
- package/lib/agent-cli/src/services/index.js +13 -0
- package/lib/agent-cli/src/services/learning-service.js +99 -0
- package/lib/agent-cli/types/index.d.ts +343 -0
- package/lib/agent-cli/utils/benchmark.js +269 -0
- package/lib/agent-cli/utils/logger.js +303 -0
- package/lib/agent-cli/utils/ml_patterns.js +300 -0
- package/lib/agent-cli/utils/recovery.js +312 -0
- package/lib/agent-cli/utils/telemetry.js +290 -0
- package/lib/agentskillskit-cli/ag-smart.js +15 -15
- package/lib/agentskillskit-cli/package.json +3 -3
- package/package.json +12 -6
- package/lib/agent-cli/lib/auto_preview.py +0 -148
- package/lib/agent-cli/lib/checklist.py +0 -222
- package/lib/agent-cli/lib/session_manager.py +0 -120
- package/lib/agent-cli/lib/verify_all.py +0 -327
- /package/bin/{cli.js → kit.js} +0 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit Tests for pattern_analyzer.js
|
|
3
|
+
*
|
|
4
|
+
* Tests core pattern analysis functions:
|
|
5
|
+
* - analyzeErrorPatterns
|
|
6
|
+
* - analyzeCorrectionPatterns
|
|
7
|
+
* - analyzeSuccessPatterns
|
|
8
|
+
* - calculateSuccessFailureRatio
|
|
9
|
+
* - calculateTrends
|
|
10
|
+
* - findHighFrequencyPatterns
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
14
|
+
import path from 'path';
|
|
15
|
+
import { fileURLToPath } from 'url';
|
|
16
|
+
|
|
17
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
18
|
+
const __dirname = path.dirname(__filename);
|
|
19
|
+
|
|
20
|
+
// Mock fs module
|
|
21
|
+
vi.mock('fs', async () => {
|
|
22
|
+
const actual = await vi.importActual('fs');
|
|
23
|
+
return {
|
|
24
|
+
...actual,
|
|
25
|
+
default: {
|
|
26
|
+
...actual,
|
|
27
|
+
existsSync: vi.fn(() => true),
|
|
28
|
+
readFileSync: vi.fn(),
|
|
29
|
+
writeFileSync: vi.fn(),
|
|
30
|
+
mkdirSync: vi.fn()
|
|
31
|
+
},
|
|
32
|
+
existsSync: vi.fn(() => true),
|
|
33
|
+
readFileSync: vi.fn(),
|
|
34
|
+
writeFileSync: vi.fn(),
|
|
35
|
+
mkdirSync: vi.fn()
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Test fixtures
|
|
40
|
+
const mockErrors = [
|
|
41
|
+
{ id: 'ERR-1', type: 'test', severity: 'HIGH', timestamp: new Date().toISOString() },
|
|
42
|
+
{ id: 'ERR-2', type: 'test', severity: 'HIGH', timestamp: new Date().toISOString() },
|
|
43
|
+
{ id: 'ERR-3', type: 'build', severity: 'CRITICAL', timestamp: new Date().toISOString() }
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const mockCorrections = [
|
|
47
|
+
{ category: 'import-fix', timestamp: new Date().toISOString() },
|
|
48
|
+
{ category: 'import-fix', timestamp: new Date().toISOString() },
|
|
49
|
+
{ category: 'type-fix', timestamp: new Date().toISOString() }
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
const mockSuccesses = [
|
|
53
|
+
{ pattern: 'async-await', type: 'pattern', detectedAt: new Date().toISOString() },
|
|
54
|
+
{ pattern: 'typescript-types', type: 'pattern', detectedAt: new Date().toISOString() },
|
|
55
|
+
{ pattern: 'async-await', type: 'pattern', detectedAt: new Date().toISOString() }
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
describe('Pattern Analyzer', () => {
|
|
59
|
+
describe('analyzeErrorPatterns', () => {
|
|
60
|
+
it('should group errors by type', () => {
|
|
61
|
+
const patterns = {
|
|
62
|
+
byType: {},
|
|
63
|
+
bySeverity: {},
|
|
64
|
+
byFile: {},
|
|
65
|
+
byTime: {},
|
|
66
|
+
total: mockErrors.length
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
for (const err of mockErrors) {
|
|
70
|
+
patterns.byType[err.type] = (patterns.byType[err.type] || 0) + 1;
|
|
71
|
+
patterns.bySeverity[err.severity] = (patterns.bySeverity[err.severity] || 0) + 1;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
expect(patterns.byType.test).toBe(2);
|
|
75
|
+
expect(patterns.byType.build).toBe(1);
|
|
76
|
+
expect(patterns.bySeverity.HIGH).toBe(2);
|
|
77
|
+
expect(patterns.bySeverity.CRITICAL).toBe(1);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('analyzeCorrectionPatterns', () => {
|
|
82
|
+
it('should group corrections by category', () => {
|
|
83
|
+
const patterns = { byCategory: {} };
|
|
84
|
+
|
|
85
|
+
for (const corr of mockCorrections) {
|
|
86
|
+
patterns.byCategory[corr.category] = (patterns.byCategory[corr.category] || 0) + 1;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
expect(patterns.byCategory['import-fix']).toBe(2);
|
|
90
|
+
expect(patterns.byCategory['type-fix']).toBe(1);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('analyzeSuccessPatterns', () => {
|
|
95
|
+
it('should group successes by pattern', () => {
|
|
96
|
+
const patterns = { byPattern: {} };
|
|
97
|
+
|
|
98
|
+
for (const s of mockSuccesses) {
|
|
99
|
+
patterns.byPattern[s.pattern] = (patterns.byPattern[s.pattern] || 0) + 1;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
expect(patterns.byPattern['async-await']).toBe(2);
|
|
103
|
+
expect(patterns.byPattern['typescript-types']).toBe(1);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe('calculateSuccessFailureRatio', () => {
|
|
108
|
+
it('should return EXCELLENT for 70%+ success rate', () => {
|
|
109
|
+
const errors = [{ id: 1 }];
|
|
110
|
+
const corrections = [{ id: 1 }];
|
|
111
|
+
const successes = Array(8).fill({ id: 1 }); // 8 successes, 2 failures = 80%
|
|
112
|
+
|
|
113
|
+
const totalFailures = errors.length + corrections.length;
|
|
114
|
+
const totalSuccesses = successes.length;
|
|
115
|
+
const total = totalFailures + totalSuccesses;
|
|
116
|
+
const successRate = totalSuccesses / total;
|
|
117
|
+
|
|
118
|
+
expect(successRate).toBeGreaterThan(0.7);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should return NEEDS_ATTENTION for <30% success rate', () => {
|
|
122
|
+
const errors = Array(7).fill({ id: 1 });
|
|
123
|
+
const corrections = [];
|
|
124
|
+
const successes = [{ id: 1 }]; // 1 success, 7 failures = ~12%
|
|
125
|
+
|
|
126
|
+
const totalFailures = errors.length + corrections.length;
|
|
127
|
+
const totalSuccesses = successes.length;
|
|
128
|
+
const total = totalFailures + totalSuccesses;
|
|
129
|
+
const successRate = totalSuccesses / total;
|
|
130
|
+
|
|
131
|
+
expect(successRate).toBeLessThan(0.3);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should handle empty data', () => {
|
|
135
|
+
const total = 0;
|
|
136
|
+
expect(total).toBe(0);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe('calculateTrends', () => {
|
|
141
|
+
it('should calculate weekly comparison', () => {
|
|
142
|
+
const now = new Date();
|
|
143
|
+
const oneWeekAgo = new Date(now - 7 * 24 * 60 * 60 * 1000);
|
|
144
|
+
const twoWeeksAgo = new Date(now - 14 * 24 * 60 * 60 * 1000);
|
|
145
|
+
|
|
146
|
+
const thisWeekErrors = mockErrors.filter(e => new Date(e.timestamp) > oneWeekAgo);
|
|
147
|
+
const lastWeekErrors = mockErrors.filter(e => {
|
|
148
|
+
const d = new Date(e.timestamp);
|
|
149
|
+
return d > twoWeeksAgo && d <= oneWeekAgo;
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
expect(thisWeekErrors.length).toBeGreaterThanOrEqual(0);
|
|
153
|
+
expect(lastWeekErrors.length).toBeGreaterThanOrEqual(0);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe('findHighFrequencyPatterns', () => {
|
|
158
|
+
it('should find patterns occurring 3+ times', () => {
|
|
159
|
+
const threshold = 3;
|
|
160
|
+
const errorPatterns = {
|
|
161
|
+
byType: { 'test': 5, 'build': 2, 'lint': 4 }
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const highFrequency = Object.entries(errorPatterns.byType)
|
|
165
|
+
.filter(([, count]) => count >= threshold)
|
|
166
|
+
.map(([type, count]) => ({ type, count }));
|
|
167
|
+
|
|
168
|
+
expect(highFrequency.length).toBe(2);
|
|
169
|
+
expect(highFrequency.find(p => p.type === 'test')?.count).toBe(5);
|
|
170
|
+
expect(highFrequency.find(p => p.type === 'lint')?.count).toBe(4);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
});
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit Tests for pre_execution_check.js
|
|
3
|
+
*
|
|
4
|
+
* Tests:
|
|
5
|
+
* - detectIntent
|
|
6
|
+
* - checkIntent
|
|
7
|
+
* - generateAdvice
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
11
|
+
|
|
12
|
+
// Mock fs module
|
|
13
|
+
vi.mock('fs', async () => {
|
|
14
|
+
const actual = await vi.importActual('fs');
|
|
15
|
+
return {
|
|
16
|
+
...actual,
|
|
17
|
+
default: {
|
|
18
|
+
...actual,
|
|
19
|
+
existsSync: vi.fn(() => false),
|
|
20
|
+
readFileSync: vi.fn(() => '{}'),
|
|
21
|
+
writeFileSync: vi.fn()
|
|
22
|
+
},
|
|
23
|
+
existsSync: vi.fn(() => false),
|
|
24
|
+
readFileSync: vi.fn(() => '{}')
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('Pre-Execution Check', () => {
|
|
29
|
+
describe('detectIntent', () => {
|
|
30
|
+
it('should detect delete operations as CRITICAL', () => {
|
|
31
|
+
const input = 'delete file test.js';
|
|
32
|
+
const intents = [];
|
|
33
|
+
|
|
34
|
+
if (/\b(delete|remove|unlink|rm)\b/i.test(input)) {
|
|
35
|
+
intents.push({ action: 'delete', target: 'file', risk: 'CRITICAL' });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
expect(intents.length).toBe(1);
|
|
39
|
+
expect(intents[0].risk).toBe('CRITICAL');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should detect rename operations as HIGH risk', () => {
|
|
43
|
+
const input = 'rename component to new name';
|
|
44
|
+
const intents = [];
|
|
45
|
+
|
|
46
|
+
if (/\b(rename|mv|move)\b/i.test(input)) {
|
|
47
|
+
intents.push({ action: 'rename', target: 'file', risk: 'HIGH' });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
expect(intents.length).toBe(1);
|
|
51
|
+
expect(intents[0].risk).toBe('HIGH');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should detect create operations as LOW risk', () => {
|
|
55
|
+
const input = 'create new component file';
|
|
56
|
+
const intents = [];
|
|
57
|
+
|
|
58
|
+
if (/\b(create|new|add|write)\b.*\b(file|component|module)\b/i.test(input)) {
|
|
59
|
+
intents.push({ action: 'create', target: 'file', risk: 'LOW' });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
expect(intents.length).toBe(1);
|
|
63
|
+
expect(intents[0].risk).toBe('LOW');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should detect async operations', () => {
|
|
67
|
+
const input = 'create async function to fetch API';
|
|
68
|
+
const intents = [];
|
|
69
|
+
|
|
70
|
+
if (/\b(async|await|promise|fetch)\b/i.test(input)) {
|
|
71
|
+
intents.push({ action: 'async', target: 'code', risk: 'MEDIUM' });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
expect(intents.length).toBe(1);
|
|
75
|
+
expect(intents[0].action).toBe('async');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should detect database operations as HIGH risk', () => {
|
|
79
|
+
const input = 'query database for users';
|
|
80
|
+
const intents = [];
|
|
81
|
+
|
|
82
|
+
if (/\b(database|db|query|sql|prisma)\b/i.test(input)) {
|
|
83
|
+
intents.push({ action: 'database', target: 'data', risk: 'HIGH' });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
expect(intents.length).toBe(1);
|
|
87
|
+
expect(intents[0].risk).toBe('HIGH');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should detect completion as CRITICAL', () => {
|
|
91
|
+
const input = 'complete task and notify user';
|
|
92
|
+
const intents = [];
|
|
93
|
+
|
|
94
|
+
if (/\b(complete|finish|done|notify|submit)\b/i.test(input)) {
|
|
95
|
+
intents.push({ action: 'complete', target: 'task', risk: 'CRITICAL' });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
expect(intents.length).toBe(1);
|
|
99
|
+
expect(intents[0].risk).toBe('CRITICAL');
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('checkIntent', () => {
|
|
104
|
+
it('should return violations for CRITICAL intents', () => {
|
|
105
|
+
const violations = [];
|
|
106
|
+
const intent = 'delete all files';
|
|
107
|
+
|
|
108
|
+
if (/\b(delete)\b/i.test(intent)) {
|
|
109
|
+
violations.push({
|
|
110
|
+
id: 'INTENT-DELETE',
|
|
111
|
+
severity: 'CRITICAL',
|
|
112
|
+
source: 'intent-detection'
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
expect(violations.some(v => v.severity === 'CRITICAL')).toBe(true);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should match builtin rules', () => {
|
|
120
|
+
const BUILTIN_RULES = [
|
|
121
|
+
{
|
|
122
|
+
id: 'BUILTIN-001',
|
|
123
|
+
name: 'TypeScript type safety',
|
|
124
|
+
check: (intent) => intent.toLowerCase().includes('function'),
|
|
125
|
+
severity: 'HIGH'
|
|
126
|
+
}
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
const intent = 'create function handler';
|
|
130
|
+
const warnings = [];
|
|
131
|
+
|
|
132
|
+
for (const rule of BUILTIN_RULES) {
|
|
133
|
+
if (rule.check(intent)) {
|
|
134
|
+
warnings.push({ id: rule.id, severity: rule.severity });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
expect(warnings.length).toBe(1);
|
|
139
|
+
expect(warnings[0].id).toBe('BUILTIN-001');
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe('generateAdvice', () => {
|
|
144
|
+
it('should prioritize CRITICAL first', () => {
|
|
145
|
+
const all = [
|
|
146
|
+
{ severity: 'MEDIUM', prevention: 'Medium advice' },
|
|
147
|
+
{ severity: 'CRITICAL', prevention: 'Critical advice' },
|
|
148
|
+
{ severity: 'HIGH', prevention: 'High advice' }
|
|
149
|
+
];
|
|
150
|
+
|
|
151
|
+
const critical = all.filter(v => v.severity === 'CRITICAL');
|
|
152
|
+
const high = all.filter(v => v.severity === 'HIGH');
|
|
153
|
+
const other = all.filter(v => v.severity !== 'CRITICAL' && v.severity !== 'HIGH');
|
|
154
|
+
|
|
155
|
+
expect(critical.length).toBe(1);
|
|
156
|
+
expect(high.length).toBe(1);
|
|
157
|
+
expect(other.length).toBe(1);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should block execution for CRITICAL violations', () => {
|
|
161
|
+
const violations = [{ severity: 'CRITICAL' }];
|
|
162
|
+
const hasCritical = violations.some(v => v.severity === 'CRITICAL');
|
|
163
|
+
|
|
164
|
+
expect(hasCritical).toBe(true);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
});
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit Tests for skill_injector.js
|
|
3
|
+
*
|
|
4
|
+
* Tests:
|
|
5
|
+
* - analyzeLessons
|
|
6
|
+
* - getSkillCandidates
|
|
7
|
+
* - generateSkillContent
|
|
8
|
+
* - generateSkills
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
12
|
+
|
|
13
|
+
// Mock fs module
|
|
14
|
+
vi.mock('fs', async () => {
|
|
15
|
+
const actual = await vi.importActual('fs');
|
|
16
|
+
return {
|
|
17
|
+
...actual,
|
|
18
|
+
default: {
|
|
19
|
+
...actual,
|
|
20
|
+
existsSync: vi.fn(() => false),
|
|
21
|
+
readFileSync: vi.fn(() => '{}'),
|
|
22
|
+
writeFileSync: vi.fn(),
|
|
23
|
+
mkdirSync: vi.fn(),
|
|
24
|
+
readdirSync: vi.fn(() => [])
|
|
25
|
+
},
|
|
26
|
+
existsSync: vi.fn(() => false),
|
|
27
|
+
readFileSync: vi.fn(() => '{}'),
|
|
28
|
+
writeFileSync: vi.fn(),
|
|
29
|
+
mkdirSync: vi.fn(),
|
|
30
|
+
readdirSync: vi.fn(() => [])
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Category config constant
|
|
35
|
+
const CATEGORY_CONFIG = {
|
|
36
|
+
'SAFE': { name: 'safety-rules', description: 'Auto-learned safety rules' },
|
|
37
|
+
'CODE': { name: 'code-patterns', description: 'Auto-learned code patterns' },
|
|
38
|
+
'FLOW': { name: 'workflow-rules', description: 'Auto-learned workflow rules' },
|
|
39
|
+
'INT': { name: 'integration-rules', description: 'Auto-learned integration rules' },
|
|
40
|
+
'PERF': { name: 'performance-rules', description: 'Auto-learned performance rules' },
|
|
41
|
+
'TEST': { name: 'testing-rules', description: 'Auto-learned testing rules' },
|
|
42
|
+
'LEARN': { name: 'general-lessons', description: 'General lessons learned' }
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
describe('Skill Injector', () => {
|
|
46
|
+
describe('analyzeLessons', () => {
|
|
47
|
+
it('should group lessons by category', () => {
|
|
48
|
+
const lessons = [
|
|
49
|
+
{ id: 'SAFE-001', pattern: 'delete check' },
|
|
50
|
+
{ id: 'SAFE-002', pattern: 'file backup' },
|
|
51
|
+
{ id: 'CODE-001', pattern: 'typescript' },
|
|
52
|
+
{ id: 'TEST-001', pattern: 'unit test' }
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
const categories = {};
|
|
56
|
+
|
|
57
|
+
for (const lesson of lessons) {
|
|
58
|
+
const category = lesson.id?.split('-')[0] || 'LEARN';
|
|
59
|
+
if (!categories[category]) {
|
|
60
|
+
categories[category] = [];
|
|
61
|
+
}
|
|
62
|
+
categories[category].push(lesson);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
expect(categories['SAFE'].length).toBe(2);
|
|
66
|
+
expect(categories['CODE'].length).toBe(1);
|
|
67
|
+
expect(categories['TEST'].length).toBe(1);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should default to LEARN for unknown categories', () => {
|
|
71
|
+
const lessons = [{ id: 'UNKNOWN-001', pattern: 'something' }];
|
|
72
|
+
|
|
73
|
+
const categories = {};
|
|
74
|
+
for (const lesson of lessons) {
|
|
75
|
+
const category = lesson.id?.split('-')[0] || 'LEARN';
|
|
76
|
+
if (!categories[category]) {
|
|
77
|
+
categories[category] = [];
|
|
78
|
+
}
|
|
79
|
+
categories[category].push(lesson);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
expect(categories['UNKNOWN'].length).toBe(1);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('getSkillCandidates', () => {
|
|
87
|
+
it('should require minimum 3 lessons for skill generation', () => {
|
|
88
|
+
const categories = {
|
|
89
|
+
'SAFE': [{ id: 1 }, { id: 2 }, { id: 3 }], // 3 - eligible
|
|
90
|
+
'CODE': [{ id: 1 }, { id: 2 }], // 2 - not eligible
|
|
91
|
+
'TEST': [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }] // 4 - eligible
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const candidates = [];
|
|
95
|
+
for (const [category, lessons] of Object.entries(categories)) {
|
|
96
|
+
if (lessons.length >= 3) {
|
|
97
|
+
candidates.push({
|
|
98
|
+
category,
|
|
99
|
+
count: lessons.length,
|
|
100
|
+
config: CATEGORY_CONFIG[category] || {
|
|
101
|
+
name: `${category.toLowerCase()}-rules`,
|
|
102
|
+
description: `Auto-learned ${category.toLowerCase()} rules`
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
expect(candidates.length).toBe(2);
|
|
109
|
+
expect(candidates.find(c => c.category === 'SAFE')).toBeDefined();
|
|
110
|
+
expect(candidates.find(c => c.category === 'TEST')).toBeDefined();
|
|
111
|
+
expect(candidates.find(c => c.category === 'CODE')).toBeUndefined();
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('generateSkillContent', () => {
|
|
116
|
+
it('should generate valid SKILL.md content', () => {
|
|
117
|
+
const category = 'SAFE';
|
|
118
|
+
const lessons = [
|
|
119
|
+
{ id: 'SAFE-001', pattern: 'delete check', severity: 'CRITICAL', message: 'Check before delete' },
|
|
120
|
+
{ id: 'SAFE-002', pattern: 'backup first', severity: 'HIGH', message: 'Backup before modify' }
|
|
121
|
+
];
|
|
122
|
+
const config = CATEGORY_CONFIG['SAFE'];
|
|
123
|
+
|
|
124
|
+
// Generate YAML frontmatter
|
|
125
|
+
const content = `---
|
|
126
|
+
name: ${config.name}
|
|
127
|
+
description: ${config.description}
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
# ${config.name}
|
|
131
|
+
`;
|
|
132
|
+
|
|
133
|
+
expect(content).toContain('name: safety-rules');
|
|
134
|
+
expect(content).toContain('Auto-learned safety rules');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should include severity icons', () => {
|
|
138
|
+
const severityIcons = {
|
|
139
|
+
'CRITICAL': '🔴',
|
|
140
|
+
'HIGH': '🟠',
|
|
141
|
+
'MEDIUM': '🟡',
|
|
142
|
+
'LOW': '🟢'
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
expect(severityIcons['CRITICAL']).toBe('🔴');
|
|
146
|
+
expect(severityIcons['HIGH']).toBe('🟠');
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe('generateSkills', () => {
|
|
151
|
+
it('should generate skills for eligible categories', () => {
|
|
152
|
+
const candidates = [
|
|
153
|
+
{ category: 'SAFE', count: 5, config: CATEGORY_CONFIG['SAFE'] },
|
|
154
|
+
{ category: 'CODE', count: 3, config: CATEGORY_CONFIG['CODE'] }
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
const generated = candidates.map(c => ({
|
|
158
|
+
category: c.category,
|
|
159
|
+
name: c.config.name,
|
|
160
|
+
lessonCount: c.count
|
|
161
|
+
}));
|
|
162
|
+
|
|
163
|
+
expect(generated.length).toBe(2);
|
|
164
|
+
expect(generated[0].name).toBe('safety-rules');
|
|
165
|
+
expect(generated[1].name).toBe('code-patterns');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('should support dry-run mode', () => {
|
|
169
|
+
const dryRun = true;
|
|
170
|
+
const writeOperations = [];
|
|
171
|
+
|
|
172
|
+
if (!dryRun) {
|
|
173
|
+
writeOperations.push('write');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
expect(writeOperations.length).toBe(0);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe('Category Mapping', () => {
|
|
181
|
+
it('should have all standard categories', () => {
|
|
182
|
+
expect(CATEGORY_CONFIG['SAFE']).toBeDefined();
|
|
183
|
+
expect(CATEGORY_CONFIG['CODE']).toBeDefined();
|
|
184
|
+
expect(CATEGORY_CONFIG['FLOW']).toBeDefined();
|
|
185
|
+
expect(CATEGORY_CONFIG['INT']).toBeDefined();
|
|
186
|
+
expect(CATEGORY_CONFIG['PERF']).toBeDefined();
|
|
187
|
+
expect(CATEGORY_CONFIG['TEST']).toBeDefined();
|
|
188
|
+
expect(CATEGORY_CONFIG['LEARN']).toBeDefined();
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
});
|
|
@@ -24,6 +24,8 @@ const ARGS = process.argv.slice(2);
|
|
|
24
24
|
const COMMAND = ARGS[0];
|
|
25
25
|
const SCRIPTS_DIR = path.join(__dirname, "..", "lib");
|
|
26
26
|
const HOOKS_DIR = path.join(SCRIPTS_DIR, "hooks");
|
|
27
|
+
const DASHBOARD_DIR = path.join(__dirname, "..", "dashboard");
|
|
28
|
+
const AUTO_LEARN_DIR = path.join(__dirname, "..", "scripts");
|
|
27
29
|
|
|
28
30
|
/**
|
|
29
31
|
* Run a script with given arguments
|
|
@@ -52,46 +54,54 @@ function printHelp() {
|
|
|
52
54
|
console.log(`
|
|
53
55
|
🤖 Agent Skill Kit CLI v${VERSION}
|
|
54
56
|
|
|
55
|
-
Usage:
|
|
57
|
+
Usage: agent <command> [options]
|
|
56
58
|
|
|
57
59
|
${"─".repeat(50)}
|
|
58
60
|
|
|
59
61
|
📚 CORE COMMANDS:
|
|
60
62
|
|
|
61
63
|
learn Teach a new lesson to the memory
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
agent learn --add --pattern "var " --message "Use let/const"
|
|
65
|
+
agent learn --list
|
|
66
|
+
agent learn --remove LEARN-001
|
|
65
67
|
|
|
66
68
|
recall Check file(s) against learned patterns
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
agent recall src/app.js
|
|
70
|
+
agent recall ./src
|
|
69
71
|
|
|
70
72
|
audit Run full compliance audit
|
|
71
|
-
|
|
73
|
+
agent audit [directory]
|
|
72
74
|
|
|
73
75
|
${"─".repeat(50)}
|
|
74
76
|
|
|
75
77
|
🚀 PRODUCTION FEATURES:
|
|
76
78
|
|
|
77
79
|
watch Real-time file monitoring
|
|
78
|
-
|
|
80
|
+
agent watch [directory]
|
|
79
81
|
|
|
80
82
|
stats Knowledge base statistics
|
|
81
|
-
|
|
83
|
+
agent stats
|
|
82
84
|
|
|
83
85
|
install-hooks Install git pre-commit hook
|
|
84
|
-
|
|
85
|
-
|
|
86
|
+
agent install-hooks
|
|
87
|
+
agent install-hooks --remove
|
|
86
88
|
|
|
87
89
|
lint-learn Auto-learn from ESLint JSON output
|
|
88
|
-
npx eslint . --format json |
|
|
90
|
+
npx eslint . --format json | agent lint-learn
|
|
89
91
|
|
|
90
92
|
fix 🆕 Auto-fix violations
|
|
91
|
-
|
|
93
|
+
agent fix <file|dir> [--mode safe|aggressive]
|
|
92
94
|
|
|
93
95
|
sync-skills 🆕 Sync hot patterns to SKILL.md
|
|
94
|
-
|
|
96
|
+
agent sync-skills
|
|
97
|
+
|
|
98
|
+
dashboard 🆕 Start Auto-Learn dashboard
|
|
99
|
+
agent dashboard
|
|
100
|
+
|
|
101
|
+
auto-learn 🆕 Run auto-learn scripts
|
|
102
|
+
agent auto-learn --scan
|
|
103
|
+
agent auto-learn --analyze
|
|
104
|
+
agent auto-learn --check "intent"
|
|
95
105
|
|
|
96
106
|
${"─".repeat(50)}
|
|
97
107
|
|
|
@@ -137,6 +147,29 @@ switch (COMMAND) {
|
|
|
137
147
|
run("skill-learn.js", ARGS.slice(1));
|
|
138
148
|
break;
|
|
139
149
|
|
|
150
|
+
// Auto-Learn features
|
|
151
|
+
case "dashboard":
|
|
152
|
+
run("dashboard_server.js", ARGS.slice(1), DASHBOARD_DIR);
|
|
153
|
+
break;
|
|
154
|
+
case "auto-learn":
|
|
155
|
+
const subCmd = ARGS[1];
|
|
156
|
+
if (subCmd === "--scan" || subCmd === "-s") {
|
|
157
|
+
run("error_sensor.js", ARGS.slice(2), AUTO_LEARN_DIR);
|
|
158
|
+
} else if (subCmd === "--success") {
|
|
159
|
+
run("success_sensor.js", ARGS.slice(2), AUTO_LEARN_DIR);
|
|
160
|
+
} else if (subCmd === "--analyze" || subCmd === "-a") {
|
|
161
|
+
run("pattern_analyzer.js", ARGS.slice(2), AUTO_LEARN_DIR);
|
|
162
|
+
} else if (subCmd === "--check" || subCmd === "-c") {
|
|
163
|
+
run("pre_execution_check.js", ARGS.slice(2), AUTO_LEARN_DIR);
|
|
164
|
+
} else if (subCmd === "--adapt") {
|
|
165
|
+
run("adaptive_engine.js", ARGS.slice(2), AUTO_LEARN_DIR);
|
|
166
|
+
} else if (subCmd === "--inject") {
|
|
167
|
+
run("skill_injector.js", ARGS.slice(2), AUTO_LEARN_DIR);
|
|
168
|
+
} else {
|
|
169
|
+
console.log("Usage: agent auto-learn [--scan|--success|--analyze|--check|--adapt|--inject]");
|
|
170
|
+
}
|
|
171
|
+
break;
|
|
172
|
+
|
|
140
173
|
// Meta
|
|
141
174
|
case "--version":
|
|
142
175
|
case "-v":
|
|
@@ -153,6 +186,6 @@ switch (COMMAND) {
|
|
|
153
186
|
break;
|
|
154
187
|
default:
|
|
155
188
|
console.log(`❌ Unknown command: ${COMMAND}`);
|
|
156
|
-
console.log(" Run '
|
|
189
|
+
console.log(" Run 'agent help' for available commands.\n");
|
|
157
190
|
process.exit(1);
|
|
158
191
|
}
|