aggroot 1.5.0 → 1.5.1

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.
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aggroot",
3
- "version": "1.5.0",
3
+ "version": "1.5.1",
4
4
  "description": "AggRoot - AI Assistant (Node.js/TypeScript Version)",
5
5
  "keywords": [
6
6
  "ai",
@@ -0,0 +1,238 @@
1
+ /**
2
+ * 生成示例 Skills 到桌面(带分类目录 + DESCRIPTION.md + 索引缓存)
3
+ *
4
+ * 运行: npx tsx scripts/generate-example-skills.ts
5
+ */
6
+
7
+ import * as fs from 'node:fs';
8
+ import * as path from 'node:path';
9
+ import * as os from 'node:os';
10
+ import { createSkillManageTool } from '../src/domain/skill/skill-manager-tool.js';
11
+ import { createSkillRegistry, loadSkillsWithCache, invalidateIndexCache } from '../src/domain/skill/skill-loader.js';
12
+
13
+ const outputDir = path.join(os.homedir(), 'Desktop', 'generated-skills');
14
+ fs.mkdirSync(outputDir, { recursive: true });
15
+ process.env.AGGROOT_HOME = outputDir;
16
+
17
+ const registry = createSkillRegistry();
18
+ const tool = createSkillManageTool(registry);
19
+
20
+ const skills: Array<{ name: string; category?: string; content: string }> = [
21
+ // ---- devops 分类 ----
22
+ {
23
+ name: 'deploy-to-staging',
24
+ category: 'devops',
25
+ content: `---
26
+ name: deploy-to-staging
27
+ description: Deploy the project to staging environment using docker
28
+ whenToUse: When you need to deploy the current project to the staging server
29
+ ---
30
+
31
+ # Deploy to Staging
32
+
33
+ 1. Run tests: \`npm test\`
34
+ 2. Build Docker image: \`docker build -t myapp:staging .\`
35
+ 3. Push to registry: \`docker push registry.example.com/myapp:staging\`
36
+ 4. SSH to staging server: \`ssh deploy@staging.example.com\`
37
+ 5. Pull and restart: \`docker-compose pull && docker-compose up -d\`
38
+ 6. Verify: \`curl -f https://staging.example.com/health\`
39
+
40
+ ## Pitfalls
41
+ - Always run tests before deploying
42
+ - Check that .env.staging has the correct DATABASE_URL
43
+ - If health check fails, rollback with \`docker-compose down && docker-compose up -d --no-pull\`
44
+ `,
45
+ },
46
+ {
47
+ name: 'rollback',
48
+ category: 'devops',
49
+ content: `---
50
+ name: rollback
51
+ description: Rollback a deployment to the previous version
52
+ whenToUse: When a deployment fails or causes issues in production
53
+ ---
54
+
55
+ # Rollback Deployment
56
+
57
+ 1. Check current version: \`docker-compose ps\`
58
+ 2. Rollback to previous image: \`docker-compose down && docker-compose up -d --no-pull\`
59
+ 3. Verify health: \`curl -f https://staging.example.com/health\`
60
+ 4. Notify team in Slack
61
+
62
+ ## Pitfalls
63
+ - Always verify the previous image is still available
64
+ - Don't rollback if database migration already ran
65
+ `,
66
+ },
67
+ // ---- debugging 分类 ----
68
+ {
69
+ name: 'debug-auth-401',
70
+ category: 'debugging',
71
+ content: `---
72
+ name: debug-auth-401
73
+ description: Debug and fix 401 authentication errors in test suites
74
+ whenToUse: When auth tests fail with 401 status code
75
+ ---
76
+
77
+ # Debug Auth 401 Errors
78
+
79
+ 1. Run the failing test with verbose output to identify which test and assertion failed
80
+ 2. Read the auth middleware source code to understand the token validation logic
81
+ 3. Check if the middleware correctly handles token formats (e.g., Bearer prefix stripping)
82
+ 4. Fix the middleware to properly extract the token
83
+ 5. Re-run tests to verify the fix
84
+
85
+ ## Common Causes
86
+ - Middleware not stripping "Bearer " prefix from Authorization header
87
+ - Missing or incorrect token extraction logic
88
+ - Token not being passed in test fixtures
89
+ `,
90
+ },
91
+ {
92
+ name: 'debug-memory-leak',
93
+ category: 'debugging',
94
+ content: `---
95
+ name: debug-memory-leak
96
+ description: Investigate and fix memory leaks in Node.js applications
97
+ whenToUse: When the application shows increasing memory usage over time
98
+ ---
99
+
100
+ # Debug Memory Leak
101
+
102
+ 1. Take heap snapshot: \`node --inspect app.js\` then Chrome DevTools → Memory → Take Snapshot
103
+ 2. Compare snapshots over time to find growing objects
104
+ 3. Check for common patterns: event listeners not removed, closures holding references, global caches
105
+ 4. Fix the leak and verify with load testing
106
+
107
+ ## Common Causes
108
+ - Event listeners not removed on cleanup
109
+ - Closures capturing large objects
110
+ - Unbounded caches without TTL
111
+ `,
112
+ },
113
+ // ---- git 分类 ----
114
+ {
115
+ name: 'smart-commit',
116
+ category: 'git',
117
+ content: `---
118
+ name: smart-commit
119
+ description: Create a well-structured git commit with proper message
120
+ whenToUse: When you need to commit changes to git
121
+ ---
122
+
123
+ # Smart Commit
124
+
125
+ 1. Run \`git status\` to see all changes
126
+ 2. Run \`git diff\` to understand what changed
127
+ 3. Stage appropriate files with \`git add\`
128
+ 4. Create commit with conventional commit format:
129
+ - feat: new feature
130
+ - fix: bug fix
131
+ - refactor: code refactoring
132
+
133
+ ## Pitfalls
134
+ - Always review the diff before committing
135
+ - Never commit .env files or secrets
136
+ `,
137
+ },
138
+ // ---- 根级 skill(无分类)----
139
+ {
140
+ name: 'explain',
141
+ content: `---
142
+ name: explain
143
+ description: Explain code or concept in detail
144
+ whenToUse: When you need to understand how something works
145
+ ---
146
+
147
+ # Explain
148
+
149
+ 1. Read the relevant source files
150
+ 2. Trace the execution flow
151
+ 3. Summarize the key concepts
152
+ 4. Provide examples if helpful
153
+
154
+ Provide explanation at the requested detail level.
155
+ `,
156
+ },
157
+ ];
158
+
159
+ console.log('Generating skills with category structure...\n');
160
+
161
+ for (const skill of skills) {
162
+ const result = await tool.execute({
163
+ action: 'create',
164
+ name: skill.name,
165
+ content: skill.content,
166
+ category: skill.category,
167
+ });
168
+
169
+ if (result.success) {
170
+ console.log(` ✓ ${skill.name}${skill.category ? ` (category: ${skill.category})` : ''}`);
171
+ } else {
172
+ console.log(` ✗ ${skill.name}: ${result.stderr}`);
173
+ }
174
+ }
175
+
176
+ // 手动写 DESCRIPTION.md 文件到分类目录
177
+ const skillsDir = path.join(outputDir, 'skills');
178
+ const descriptions: Record<string, string> = {
179
+ devops: 'DevOps tools — deployment, rollback, and infrastructure management',
180
+ debugging: 'Debugging workflows — systematic approaches to finding and fixing bugs',
181
+ git: 'Git workflows — commit, branch, and version control best practices',
182
+ };
183
+
184
+ for (const [cat, desc] of Object.entries(descriptions)) {
185
+ const descPath = path.join(skillsDir, cat, 'DESCRIPTION.md');
186
+ if (fs.existsSync(path.join(skillsDir, cat))) {
187
+ fs.writeFileSync(descPath, `---\ndescription: ${desc}\n---\n`, 'utf-8');
188
+ console.log(` ✓ ${cat}/DESCRIPTION.md`);
189
+ }
190
+ }
191
+
192
+ // 删除旧缓存并重新加载以生成 .index.json
193
+ await invalidateIndexCache(skillsDir);
194
+
195
+ console.log('\nLoading skills with cache...');
196
+ const { skills: loadedSkills, categories } = await loadSkillsWithCache(skillsDir, 'user');
197
+
198
+ console.log(`\nLoaded ${loadedSkills.length} skills, ${categories.size} categories:`);
199
+ for (const [cat, desc] of categories) {
200
+ console.log(` ${cat}: ${desc}`);
201
+ }
202
+
203
+ // 验证 .index.json
204
+ const indexPath = path.join(skillsDir, '.index.json');
205
+ if (fs.existsSync(indexPath)) {
206
+ const cache = JSON.parse(fs.readFileSync(indexPath, 'utf-8'));
207
+ console.log(`\n.index.json created:`);
208
+ console.log(` version: ${cache.version}`);
209
+ console.log(` skills: ${cache.skills.length}`);
210
+ console.log(` categories: ${Object.keys(cache.categories).join(', ')}`);
211
+ console.log(` manifest entries: ${Object.keys(cache.manifest).length}`);
212
+ for (const entry of cache.skills) {
213
+ console.log(` - ${entry.name} (category: ${entry.category || 'none'}, path: ${entry.filePath})`);
214
+ }
215
+ }
216
+
217
+ // 列出生成的文件结构
218
+ function listFiles(dir: string, prefix = ''): void {
219
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
220
+ if (entry.name.startsWith('.') && entry.isFile()) {
221
+ // Show dot files (like .index.json) but with indicator
222
+ console.log(`${prefix}${entry.name} (cache)`);
223
+ continue;
224
+ }
225
+ if (entry.name.startsWith('.')) continue;
226
+ if (entry.isDirectory()) {
227
+ console.log(`${prefix}${entry.name}/`);
228
+ listFiles(path.join(dir, entry.name), `${prefix} `);
229
+ } else {
230
+ console.log(`${prefix}${entry.name}`);
231
+ }
232
+ }
233
+ }
234
+
235
+ console.log('\nGenerated file structure:');
236
+ listFiles(skillsDir);
237
+
238
+ console.log(`\nOutput directory: ${outputDir}`);
@@ -0,0 +1,37 @@
1
+ import { parseSource } from '../src/shared/utils/bash-parser.ts';
2
+ import { isSafeShellCommand } from '../src/domain/agent/services/approval.ts';
3
+ import { getToolRiskLevel, ToolRiskLevel } from '../src/domain/agent/services/approval.ts';
4
+
5
+ const commands = [
6
+ 'cd /c/Users/zhuoz/Desktop/gui && ls -la build* 2>/dev/null || echo "No build directory found"',
7
+ 'cd C:/Users/zhuoz/Desktop/gui && find build -name "libAutoJdv*" -o -name "AutoJdv.dll" 2>/dev/null | head -5',
8
+ 'ls -la',
9
+ 'echo hello',
10
+ ];
11
+
12
+ for (const cmd of commands) {
13
+ console.log(`\n=== Testing: ${cmd.slice(0, 80)}... ===`);
14
+
15
+ const t0 = Date.now();
16
+ let astResult;
17
+ try {
18
+ astResult = parseSource(cmd);
19
+ } catch (e) {
20
+ console.log('parseSource THREW:', e);
21
+ continue;
22
+ }
23
+ const parseTime = Date.now() - t0;
24
+ console.log(`parseSource: type=${astResult?.type} children=${astResult?.children?.length} time=${parseTime}ms`);
25
+
26
+ const t1 = Date.now();
27
+ const safe = isSafeShellCommand(cmd);
28
+ const safeTime = Date.now() - t1;
29
+ console.log(`isSafeShellCommand: ${safe} time=${safeTime}ms`);
30
+
31
+ const t2 = Date.now();
32
+ const risk = getToolRiskLevel('Shell', { command: cmd });
33
+ const riskTime = Date.now() - t2;
34
+ console.log(`getToolRiskLevel: ${risk} time=${riskTime}ms`);
35
+ }
36
+
37
+ console.log('\nAll tests completed.');
@@ -0,0 +1,24 @@
1
+ import { parseSource } from '../src/shared/utils/bash-parser.ts';
2
+
3
+ const cmd = 'cd /c/Users/zhuoz/Desktop/gui && ls -la build* 2>/dev/null || echo "No build directory found"';
4
+
5
+ // Test 1: just parseSource with timeout guard
6
+ console.log('Testing parseSource...');
7
+ const start = Date.now();
8
+
9
+ // Use a worker-like approach: run in a separate microtask with a hard timeout
10
+ let result: any = 'TIMEOUT';
11
+ let done = false;
12
+
13
+ // We can't truly timeout sync code in JS, but let's at least see if it returns
14
+ try {
15
+ result = parseSource(cmd);
16
+ done = true;
17
+ } catch (e) {
18
+ result = 'ERROR: ' + e;
19
+ done = true;
20
+ }
21
+
22
+ const elapsed = Date.now() - start;
23
+ console.log('parseSource returned:', done ? 'yes' : 'no', 'time:', elapsed, 'ms');
24
+ console.log('result type:', result?.type, 'children:', result?.children?.length);
@@ -0,0 +1,4 @@
1
+ // Test: find where the bash parser infinite loop is
2
+ // by testing parseSource with a simpler version of the command
3
+
4
+ const { parseSource } = require('../dist/shared/utils/bash-parser.cjs');
@@ -0,0 +1,148 @@
1
+ // Test just the tokenizer - inline the key parts
2
+ function makeLexer(src: string) {
3
+ return { src, len: src.length, i: 0 }
4
+ }
5
+
6
+ function peek(L: any, off = 0): string {
7
+ return L.i + off < L.len ? L.src[L.i + off] : ''
8
+ }
9
+
10
+ function advance(L: any): void {
11
+ L.i++
12
+ }
13
+
14
+ function skipBlanks(L: any): void {
15
+ while (L.i < L.len) {
16
+ const c = L.src[L.i]
17
+ if (c === ' ' || c === '\t' || c === '\r') {
18
+ advance(L)
19
+ } else if (c === '\\' && (L.src[L.i + 1] === '\n' || (L.src[L.i + 1] === '\r' && L.src[L.i + 2] === '\n'))) {
20
+ advance(L); advance(L)
21
+ if (L.src[L.i - 1] === '\r') advance(L)
22
+ } else {
23
+ break
24
+ }
25
+ }
26
+ }
27
+
28
+ function isWordChar(c: string): boolean {
29
+ return (
30
+ (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
31
+ (c >= '0' && c <= '9') || c === '_' || c === '/' || c === '.' ||
32
+ c === '-' || c === '+' || c === ':' || c === '@' || c === '%' ||
33
+ c === ',' || c === '~' || c === '^' || c === '?' || c === '*' ||
34
+ c === '!' || c === '=' || c === '[' || c === ']'
35
+ )
36
+ }
37
+
38
+ function isWordStart(c: string): boolean {
39
+ return isWordChar(c) || c === '\\'
40
+ }
41
+
42
+ function nextToken(L: any, ctx: string = 'arg'): any {
43
+ skipBlanks(L)
44
+ const start = L.i
45
+ if (L.i >= L.len) return { type: 'EOF', value: '', start, end: start }
46
+
47
+ const c = L.src[L.i]
48
+ const c1 = peek(L, 1)
49
+ const c2 = peek(L, 2)
50
+
51
+ if (c === '\n') { advance(L); return { type: 'NEWLINE', value: '\n', start, end: L.i } }
52
+ if (c === '#') {
53
+ const si = L.i
54
+ while (L.i < L.len && L.src[L.i] !== '\n') advance(L)
55
+ return { type: 'COMMENT', value: L.src.slice(si, L.i), start, end: L.i }
56
+ }
57
+
58
+ // Multi-char operators (longest match first)
59
+ if (c === '&' && c1 === '&') { advance(L); advance(L); return { type: 'OP', value: '&&', start, end: L.i } }
60
+ if (c === '|' && c1 === '|') { advance(L); advance(L); return { type: 'OP', value: '||', start, end: L.i } }
61
+ if (c === '|' && c1 === '&') { advance(L); advance(L); return { type: 'OP', value: '|&', start, end: L.i } }
62
+ if (c === '>' && c1 === '>') { advance(L); advance(L); return { type: 'OP', value: '>>', start, end: L.i } }
63
+ if (c === '>' && c1 === '&') { advance(L); advance(L); return { type: 'OP', value: '>&', start, end: L.i } }
64
+ if (c === '>' && c1 === '|') { advance(L); advance(L); return { type: 'OP', value: '>|', start, end: L.i } }
65
+ if (c === '&' && c1 === '>' && c2 === '>') { advance(L); advance(L); advance(L); return { type: 'OP', value: '&>>', start, end: L.i } }
66
+ if (c === '&' && c1 === '>') { advance(L); advance(L); return { type: 'OP', value: '&>', start, end: L.i } }
67
+ if (c === '<' && c1 === '<' && c2 === '<') { advance(L); advance(L); advance(L); return { type: 'OP', value: '<<<', start, end: L.i } }
68
+ if (c === '<' && c1 === '<' && c2 === '-') { advance(L); advance(L); advance(L); return { type: 'OP', value: '<<-', start, end: L.i } }
69
+ if (c === '<' && c1 === '<') { advance(L); advance(L); return { type: 'OP', value: '<<', start, end: L.i } }
70
+ if (c === '<' && c1 === '&') { advance(L); advance(L); return { type: 'OP', value: '<&', start, end: L.i } }
71
+ if (c === '<' && c1 === '(') { advance(L); advance(L); return { type: 'LT_PAREN', value: '<(', start, end: L.i } }
72
+ if (c === '>' && c1 === '(') { advance(L); advance(L); return { type: 'GT_PAREN', value: '>(', start, end: L.i } }
73
+ if (c === '(' && c1 === '(') { advance(L); advance(L); return { type: 'OP', value: '((', start, end: L.i } }
74
+ if (c === ')' && c1 === ')') { advance(L); advance(L); return { type: 'OP', value: '))', start, end: L.i } }
75
+
76
+ if (c === '|' || c === '&' || c === ';' || c === '>' || c === '<') {
77
+ advance(L); return { type: 'OP', value: c, start, end: L.i }
78
+ }
79
+ if (c === '(' || c === ')') {
80
+ advance(L); return { type: 'OP', value: c, start, end: L.i }
81
+ }
82
+
83
+ // In cmd position, [ [[ { start test/group
84
+ if (ctx === 'cmd') {
85
+ if (c === '[' && c1 === '[') { advance(L); advance(L); return { type: 'OP', value: '[[', start, end: L.i } }
86
+ if (c === '[') { advance(L); return { type: 'OP', value: '[', start, end: L.i } }
87
+ if (c === '{' && (c1 === ' ' || c1 === '\t' || c1 === '\n')) { advance(L); return { type: 'OP', value: '{', start, end: L.i } }
88
+ if (c === '}') { advance(L); return { type: 'OP', value: '}', start, end: L.i } }
89
+ if (c === '!' && (c1 === ' ' || c1 === '\t')) { advance(L); return { type: 'OP', value: '!', start, end: L.i } }
90
+ }
91
+
92
+ // Quotes
93
+ if (c === '"') { advance(L); return { type: 'DQUOTE', value: '"', start, end: L.i } }
94
+ if (c === "'") {
95
+ const si = L.i; advance(L)
96
+ while (L.i < L.len && L.src[L.i] !== "'") advance(L)
97
+ if (L.i < L.len) advance(L)
98
+ return { type: 'SQUOTE', value: L.src.slice(si, L.i), start, end: L.i }
99
+ }
100
+ if (c === '$') {
101
+ if (c1 === '(' && c2 === '(') { advance(L); advance(L); advance(L); return { type: 'DOLLAR_DPAREN', value: '$((', start, end: L.i } }
102
+ if (c1 === '(') { advance(L); advance(L); return { type: 'DOLLAR_PAREN', value: '$(', start, end: L.i } }
103
+ if (c1 === '{') { advance(L); advance(L); return { type: 'DOLLAR_BRACE', value: '${', start, end: L.i } }
104
+ advance(L); return { type: 'DOLLAR', value: '$', start, end: L.i }
105
+ }
106
+ if (c === '`') {
107
+ const si = L.i; advance(L)
108
+ while (L.i < L.len && L.src[L.i] !== '`') advance(L)
109
+ if (L.i < L.len) advance(L)
110
+ return { type: 'BACKTICK', value: L.src.slice(si, L.i), start, end: L.i }
111
+ }
112
+
113
+ // Word
114
+ if (isWordStart(c)) {
115
+ const si = L.i
116
+ while (L.i < L.len && isWordChar(L.src[L.i]!)) advance(L)
117
+ return { type: 'WORD', value: L.src.slice(si, L.i), start, end: L.i }
118
+ }
119
+
120
+ // Fallback: single char as word
121
+ advance(L)
122
+ return { type: 'WORD', value: c, start, end: L.i }
123
+ }
124
+
125
+ // Test: tokenize the whole command
126
+ const cmd = 'cd /c/Users/zhuoz/Desktop/gui && ls -la build* 2>/dev/null || echo "No build directory found"';
127
+ const L = makeLexer(cmd);
128
+ const tokens: any[] = [];
129
+ let count = 0;
130
+ const start = Date.now();
131
+
132
+ while (true) {
133
+ const tok = nextToken(L);
134
+ tokens.push(tok);
135
+ count++;
136
+ if (tok.type === 'EOF' || count > 100) break;
137
+ if (Date.now() - start > 5000) {
138
+ console.log('TOKENIZER TIMEOUT at position', L.i, 'of', L.len);
139
+ console.log('Last token:', JSON.stringify(tok));
140
+ console.log('Remaining:', JSON.stringify(cmd.slice(L.i, L.i + 50)));
141
+ process.exit(1);
142
+ }
143
+ }
144
+
145
+ console.log('Tokenization complete:', count, 'tokens in', Date.now() - start, 'ms');
146
+ for (const t of tokens) {
147
+ console.log(' ', t.type, JSON.stringify(t.value));
148
+ }