openmatrix 0.2.31 → 0.2.34
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 +154 -154
- package/dist/cli/commands/approve.js +35 -1
- package/dist/cli/commands/auto.js +2 -2
- package/dist/cli/commands/check-gitignore.js +34 -30
- package/dist/cli/commands/check.js +1 -1
- package/dist/cli/commands/complete.js +35 -7
- package/dist/cli/commands/debug.js +2 -1
- package/dist/cli/commands/deploy.js +1 -1
- package/dist/cli/commands/install-skills.js +3 -0
- package/dist/cli/commands/meeting.js +37 -1
- package/dist/cli/commands/report.js +1 -1
- package/dist/cli/commands/resume.js +35 -1
- package/dist/cli/commands/retry.js +130 -56
- package/dist/cli/commands/start.js +1 -1
- package/dist/cli/commands/status.js +32 -29
- package/dist/cli/commands/step.js +4 -1
- package/dist/orchestrator/ai-reviewer.d.ts +5 -0
- package/dist/orchestrator/ai-reviewer.js +9 -2
- package/dist/orchestrator/context-collector.js +17 -5
- package/dist/orchestrator/executor.d.ts +8 -0
- package/dist/orchestrator/executor.js +24 -5
- package/dist/orchestrator/phase-executor.d.ts +4 -0
- package/dist/orchestrator/phase-executor.js +21 -4
- package/dist/storage/file-store.js +8 -0
- package/dist/storage/state-manager.js +52 -19
- package/dist/test/generator.js +113 -113
- package/dist/utils/error-handler.d.ts +18 -0
- package/dist/utils/error-handler.js +32 -0
- package/dist/utils/worktree-sync.js +24 -3
- package/package.json +61 -61
- package/skills/auto.md +410 -413
- package/skills/brainstorm.md +19 -12
- package/skills/debug.md +694 -691
- package/skills/deploy.md +658 -658
- package/skills/feature.md +713 -686
- package/skills/{SKILL.md → openmatrix-overview.md} +52 -53
- package/skills/plan.md +298 -296
- package/skills/report.md +9 -5
- package/skills/resume.md +292 -287
- package/skills/start.md +32 -20
- package/skills/status.md +5 -4
- package/skills/test.md +875 -875
- package/dist/agents/base-agent.d.ts +0 -46
- package/dist/agents/base-agent.js +0 -17
- package/dist/cli/commands/analyze.d.ts +0 -2
- package/dist/cli/commands/analyze.js +0 -50
- package/dist/orchestrator/smart-question-analyzer.d.ts +0 -90
- package/dist/orchestrator/smart-question-analyzer.js +0 -512
package/dist/test/generator.js
CHANGED
|
@@ -150,17 +150,17 @@ function generateTestContent(source, config) {
|
|
|
150
150
|
// 测试块
|
|
151
151
|
const testBlocks = source.exports.map(exp => generateTestBlock(exp, framework, usesDescribeIt, source.fileType));
|
|
152
152
|
// 组装内容
|
|
153
|
-
const content = `${needsViImport ? `import { vi } from 'vitest';\n` : ''}${imports}
|
|
154
|
-
${needsLifecycle ? `
|
|
155
|
-
beforeEach(() => {
|
|
156
|
-
vi.clearAllMocks();
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
afterEach(() => {
|
|
160
|
-
vi.restoreAllMocks();
|
|
161
|
-
});
|
|
162
|
-
` : ''}
|
|
163
|
-
${testBlocks.join('\n\n')}
|
|
153
|
+
const content = `${needsViImport ? `import { vi } from 'vitest';\n` : ''}${imports}
|
|
154
|
+
${needsLifecycle ? `
|
|
155
|
+
beforeEach(() => {
|
|
156
|
+
vi.clearAllMocks();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
afterEach(() => {
|
|
160
|
+
vi.restoreAllMocks();
|
|
161
|
+
});
|
|
162
|
+
` : ''}
|
|
163
|
+
${testBlocks.join('\n\n')}
|
|
164
164
|
`;
|
|
165
165
|
return content;
|
|
166
166
|
}
|
|
@@ -186,14 +186,14 @@ function generateTestBlock(exportName, framework, usesDescribeIt, fileType) {
|
|
|
186
186
|
// 根据文件类型生成不同的测试场景
|
|
187
187
|
const testCases = generateBusinessTestCases(exportName, fileType);
|
|
188
188
|
const testBlocks = testCases.map(tc => generateTestCaseBlock(tc, exportName));
|
|
189
|
-
return `describe('${getDescribeTitle(exportName, fileType)}', () => {
|
|
190
|
-
${testBlocks.join('\n\n')}
|
|
189
|
+
return `describe('${getDescribeTitle(exportName, fileType)}', () => {
|
|
190
|
+
${testBlocks.join('\n\n')}
|
|
191
191
|
});`;
|
|
192
192
|
}
|
|
193
193
|
else {
|
|
194
|
-
return `test('${exportName}', () => {
|
|
195
|
-
expect(${exportName}).toBeDefined();
|
|
196
|
-
expect(${exportName}(null)).toBeDefined();
|
|
194
|
+
return `test('${exportName}', () => {
|
|
195
|
+
expect(${exportName}).toBeDefined();
|
|
196
|
+
expect(${exportName}(null)).toBeDefined();
|
|
197
197
|
});`;
|
|
198
198
|
}
|
|
199
199
|
}
|
|
@@ -206,10 +206,10 @@ function generateBusinessTestCases(exportName, fileType) {
|
|
|
206
206
|
return [
|
|
207
207
|
{
|
|
208
208
|
name: 'should return valid result on successful execution',
|
|
209
|
-
arrange: `// Arrange - 准备有效的输入参数
|
|
210
|
-
const params = { id: 'test-id', data: {} };
|
|
209
|
+
arrange: `// Arrange - 准备有效的输入参数
|
|
210
|
+
const params = { id: 'test-id', data: {} };
|
|
211
211
|
const spy = vi.spyOn({ ${exportName} }, '${exportName}');`,
|
|
212
|
-
act: `// Act - 执行服务方法
|
|
212
|
+
act: `// Act - 执行服务方法
|
|
213
213
|
const result = await ${exportName}(params);`,
|
|
214
214
|
assert: [
|
|
215
215
|
'expect(result).toBeDefined();',
|
|
@@ -221,9 +221,9 @@ function generateBusinessTestCases(exportName, fileType) {
|
|
|
221
221
|
},
|
|
222
222
|
{
|
|
223
223
|
name: 'should handle invalid input gracefully',
|
|
224
|
-
arrange: `// Arrange - 准备无效输入
|
|
224
|
+
arrange: `// Arrange - 准备无效输入
|
|
225
225
|
const invalidParams = { id: null, data: undefined };`,
|
|
226
|
-
act: `// Act - 使用无效参数调用
|
|
226
|
+
act: `// Act - 使用无效参数调用
|
|
227
227
|
const result = await ${exportName}(invalidParams);`,
|
|
228
228
|
assert: [
|
|
229
229
|
'expect(result).toBeDefined();',
|
|
@@ -233,7 +233,7 @@ function generateBusinessTestCases(exportName, fileType) {
|
|
|
233
233
|
{
|
|
234
234
|
name: 'should handle empty parameters',
|
|
235
235
|
arrange: `// Arrange - 空参数测试`,
|
|
236
|
-
act: `// Act & Assert - 验证空参数处理
|
|
236
|
+
act: `// Act & Assert - 验证空参数处理
|
|
237
237
|
const result = await ${exportName}({});`,
|
|
238
238
|
assert: [
|
|
239
239
|
'expect(result).toBeDefined();',
|
|
@@ -254,11 +254,11 @@ function generateBusinessTestCases(exportName, fileType) {
|
|
|
254
254
|
return [
|
|
255
255
|
{
|
|
256
256
|
name: 'should process valid input correctly',
|
|
257
|
-
arrange: `// Arrange - 准备有效输入
|
|
258
|
-
const input = 'test-value';
|
|
259
|
-
const expected = 'expected-result';
|
|
257
|
+
arrange: `// Arrange - 准备有效输入
|
|
258
|
+
const input = 'test-value';
|
|
259
|
+
const expected = 'expected-result';
|
|
260
260
|
const spy = vi.spyOn({ ${exportName} }, '${exportName}');`,
|
|
261
|
-
act: `// Act
|
|
261
|
+
act: `// Act
|
|
262
262
|
const result = ${exportName}(input);`,
|
|
263
263
|
assert: [
|
|
264
264
|
'expect(result).toBeDefined();',
|
|
@@ -280,7 +280,7 @@ function generateBusinessTestCases(exportName, fileType) {
|
|
|
280
280
|
{
|
|
281
281
|
name: 'should handle empty string input',
|
|
282
282
|
arrange: `// Arrange - 空字符串输入`,
|
|
283
|
-
act: `// Act
|
|
283
|
+
act: `// Act
|
|
284
284
|
const result = ${exportName}('');`,
|
|
285
285
|
assert: [
|
|
286
286
|
'expect(result).toBeDefined();',
|
|
@@ -305,9 +305,9 @@ function generateBusinessTestCases(exportName, fileType) {
|
|
|
305
305
|
return [
|
|
306
306
|
{
|
|
307
307
|
name: 'should render without crashing',
|
|
308
|
-
arrange: `// Arrange - 准备组件 props
|
|
308
|
+
arrange: `// Arrange - 准备组件 props
|
|
309
309
|
const props = { title: \'Test Title\', onClick: vi.fn() };`,
|
|
310
|
-
act: `// Act - 渲染组件
|
|
310
|
+
act: `// Act - 渲染组件
|
|
311
311
|
const { container } = render(<${exportName} {...props} />);`,
|
|
312
312
|
assert: [
|
|
313
313
|
'expect(container).toBeDefined();',
|
|
@@ -316,11 +316,11 @@ function generateBusinessTestCases(exportName, fileType) {
|
|
|
316
316
|
},
|
|
317
317
|
{
|
|
318
318
|
name: 'should handle user interaction',
|
|
319
|
-
arrange: `// Arrange - 准备交互测试
|
|
320
|
-
const handleClick = vi.fn();
|
|
319
|
+
arrange: `// Arrange - 准备交互测试
|
|
320
|
+
const handleClick = vi.fn();
|
|
321
321
|
const props = { onClick: handleClick };`,
|
|
322
|
-
act: `// Act - 模拟用户点击
|
|
323
|
-
const { getByRole } = render(<${exportName} {...props} />);
|
|
322
|
+
act: `// Act - 模拟用户点击
|
|
323
|
+
const { getByRole } = render(<${exportName} {...props} />);
|
|
324
324
|
fireEvent.click(getByRole(\'button\'));`,
|
|
325
325
|
assert: [
|
|
326
326
|
'expect(handleClick).toHaveBeenCalledTimes(1);'
|
|
@@ -328,10 +328,10 @@ function generateBusinessTestCases(exportName, fileType) {
|
|
|
328
328
|
},
|
|
329
329
|
{
|
|
330
330
|
name: 'should display correct content with props',
|
|
331
|
-
arrange: `// Arrange - 准备显示内容测试
|
|
332
|
-
const testContent = \'Hello World\';
|
|
331
|
+
arrange: `// Arrange - 准备显示内容测试
|
|
332
|
+
const testContent = \'Hello World\';
|
|
333
333
|
const props = { children: testContent };`,
|
|
334
|
-
act: `// Act - 渲染并检查内容
|
|
334
|
+
act: `// Act - 渲染并检查内容
|
|
335
335
|
const { getByText } = render(<${exportName} {...props} />);`,
|
|
336
336
|
assert: [
|
|
337
337
|
'expect(getByText(testContent)).toBeInTheDocument();'
|
|
@@ -340,7 +340,7 @@ function generateBusinessTestCases(exportName, fileType) {
|
|
|
340
340
|
{
|
|
341
341
|
name: 'should handle missing optional props',
|
|
342
342
|
arrange: `// Arrange - 无可选 props`,
|
|
343
|
-
act: `// Act - 仅传递必需 props
|
|
343
|
+
act: `// Act - 仅传递必需 props
|
|
344
344
|
const { container } = render(<${exportName} />);`,
|
|
345
345
|
assert: [
|
|
346
346
|
'expect(container).toBeDefined();',
|
|
@@ -352,11 +352,11 @@ function generateBusinessTestCases(exportName, fileType) {
|
|
|
352
352
|
return [
|
|
353
353
|
{
|
|
354
354
|
name: 'should return success response for valid request',
|
|
355
|
-
arrange: `// Arrange - 准备有效的 API 请求参数
|
|
356
|
-
const request = { method: \'GET\', path: \'/api/test\' };
|
|
357
|
-
const mockResponse = { status: 200, data: { id: 1 } };
|
|
355
|
+
arrange: `// Arrange - 准备有效的 API 请求参数
|
|
356
|
+
const request = { method: \'GET\', path: \'/api/test\' };
|
|
357
|
+
const mockResponse = { status: 200, data: { id: 1 } };
|
|
358
358
|
const spy = vi.spyOn({ ${exportName} }, '${exportName}').mockResolvedValue(mockResponse);`,
|
|
359
|
-
act: `// Act - 执行 API 调用
|
|
359
|
+
act: `// Act - 执行 API 调用
|
|
360
360
|
const response = await ${exportName}(request);`,
|
|
361
361
|
assert: [
|
|
362
362
|
'expect(response).toBeDefined();',
|
|
@@ -368,11 +368,11 @@ function generateBusinessTestCases(exportName, fileType) {
|
|
|
368
368
|
},
|
|
369
369
|
{
|
|
370
370
|
name: 'should handle 404 not found',
|
|
371
|
-
arrange: `// Arrange - 准备不存在的资源请求
|
|
372
|
-
const request = { method: \'GET\', path: \'/api/nonexistent\' };
|
|
373
|
-
const mockResponse = { status: 404, error: \'Not Found\' };
|
|
371
|
+
arrange: `// Arrange - 准备不存在的资源请求
|
|
372
|
+
const request = { method: \'GET\', path: \'/api/nonexistent\' };
|
|
373
|
+
const mockResponse = { status: 404, error: \'Not Found\' };
|
|
374
374
|
vi.spyOn({ ${exportName} }, '${exportName}').mockResolvedValue(mockResponse);`,
|
|
375
|
-
act: `// Act - 请求不存在的资源
|
|
375
|
+
act: `// Act - 请求不存在的资源
|
|
376
376
|
const response = await ${exportName}(request);`,
|
|
377
377
|
assert: [
|
|
378
378
|
'expect(response.status).toBe(404);',
|
|
@@ -381,11 +381,11 @@ function generateBusinessTestCases(exportName, fileType) {
|
|
|
381
381
|
},
|
|
382
382
|
{
|
|
383
383
|
name: 'should handle invalid request body',
|
|
384
|
-
arrange: `// Arrange - 准备无效的请求体
|
|
385
|
-
const request = { method: \'POST\', path: \'/api/test\', body: null };
|
|
386
|
-
const mockResponse = { status: 400, error: \'invalid request\' };
|
|
384
|
+
arrange: `// Arrange - 准备无效的请求体
|
|
385
|
+
const request = { method: \'POST\', path: \'/api/test\', body: null };
|
|
386
|
+
const mockResponse = { status: 400, error: \'invalid request\' };
|
|
387
387
|
vi.spyOn({ ${exportName} }, '${exportName}').mockResolvedValue(mockResponse);`,
|
|
388
|
-
act: `// Act - 发送无效请求
|
|
388
|
+
act: `// Act - 发送无效请求
|
|
389
389
|
const response = await ${exportName}(request);`,
|
|
390
390
|
assert: [
|
|
391
391
|
'expect(response.status).toBe(400);',
|
|
@@ -394,8 +394,8 @@ function generateBusinessTestCases(exportName, fileType) {
|
|
|
394
394
|
},
|
|
395
395
|
{
|
|
396
396
|
name: 'should handle network errors gracefully',
|
|
397
|
-
arrange: `// Arrange - 模拟网络错误
|
|
398
|
-
const request = { method: \'GET\', path: \'/api/error\' };
|
|
397
|
+
arrange: `// Arrange - 模拟网络错误
|
|
398
|
+
const request = { method: \'GET\', path: \'/api/error\' };
|
|
399
399
|
vi.spyOn({ ${exportName} }, '${exportName}').mockRejectedValue(new Error('Network Error'));`,
|
|
400
400
|
act: `// Act - 处理错误响应`,
|
|
401
401
|
assert: [
|
|
@@ -409,9 +409,9 @@ function generateBusinessTestCases(exportName, fileType) {
|
|
|
409
409
|
return [
|
|
410
410
|
{
|
|
411
411
|
name: 'should return defined result for valid input',
|
|
412
|
-
arrange: `// Arrange - 准备有效输入
|
|
412
|
+
arrange: `// Arrange - 准备有效输入
|
|
413
413
|
const input = {};`,
|
|
414
|
-
act: `// Act - 执行函数
|
|
414
|
+
act: `// Act - 执行函数
|
|
415
415
|
const result = ${exportName}(input);`,
|
|
416
416
|
assert: [
|
|
417
417
|
'expect(result).toBeDefined();'
|
|
@@ -443,10 +443,10 @@ function generateTestCaseBlock(tc, exportName) {
|
|
|
443
443
|
const indent = ' ';
|
|
444
444
|
const actIndent = tc.act.includes('\n') ? indent : indent + ' ';
|
|
445
445
|
const assertLines = tc.assert.map(a => `${indent} ${a}`).join('\n');
|
|
446
|
-
return `${indent}it('${tc.name}', () => {
|
|
447
|
-
${indent} ${tc.arrange}
|
|
448
|
-
${actIndent}${tc.act}
|
|
449
|
-
${assertLines}
|
|
446
|
+
return `${indent}it('${tc.name}', () => {
|
|
447
|
+
${indent} ${tc.arrange}
|
|
448
|
+
${actIndent}${tc.act}
|
|
449
|
+
${assertLines}
|
|
450
450
|
${indent}});`;
|
|
451
451
|
}
|
|
452
452
|
/**
|
|
@@ -518,21 +518,21 @@ function generateMockFiles(sources, config, projectRoot) {
|
|
|
518
518
|
*/
|
|
519
519
|
function generateMockContent(source, config) {
|
|
520
520
|
const mockExports = source.exports.map(exp => {
|
|
521
|
-
return `export const ${exp} = vi.fn(() => ({
|
|
522
|
-
// Default mock implementation
|
|
523
|
-
data: null,
|
|
524
|
-
error: null
|
|
521
|
+
return `export const ${exp} = vi.fn(() => ({
|
|
522
|
+
// Default mock implementation
|
|
523
|
+
data: null,
|
|
524
|
+
error: null
|
|
525
525
|
}));`;
|
|
526
526
|
});
|
|
527
|
-
return `// Mock for ${source.path}
|
|
528
|
-
import { vi } from 'vitest';
|
|
529
|
-
|
|
530
|
-
${mockExports.join('\n\n')}
|
|
531
|
-
|
|
532
|
-
// Reset all mocks before each test
|
|
533
|
-
beforeEach(() => {
|
|
534
|
-
vi.clearAllMocks();
|
|
535
|
-
});
|
|
527
|
+
return `// Mock for ${source.path}
|
|
528
|
+
import { vi } from 'vitest';
|
|
529
|
+
|
|
530
|
+
${mockExports.join('\n\n')}
|
|
531
|
+
|
|
532
|
+
// Reset all mocks before each test
|
|
533
|
+
beforeEach(() => {
|
|
534
|
+
vi.clearAllMocks();
|
|
535
|
+
});
|
|
536
536
|
`;
|
|
537
537
|
}
|
|
538
538
|
/**
|
|
@@ -562,52 +562,52 @@ function generateE2ETestFiles(scanResult, config, uiComponents) {
|
|
|
562
562
|
*/
|
|
563
563
|
function generateE2EContent(component, framework) {
|
|
564
564
|
if (framework === 'playwright') {
|
|
565
|
-
return `// E2E test for ${component.path}
|
|
566
|
-
import { test, expect } from '@playwright/test';
|
|
567
|
-
|
|
568
|
-
test.describe('${component.exports[0] || 'Component'} E2E', () => {
|
|
569
|
-
test.beforeEach(async ({ page }) => {
|
|
570
|
-
await page.goto('/');
|
|
571
|
-
});
|
|
572
|
-
|
|
573
|
-
test('should render correctly', async ({ page }) => {
|
|
574
|
-
// Navigate to component page
|
|
575
|
-
await page.waitForSelector('[data-testid="${component.exports[0]?.toLowerCase() || 'component'}"]');
|
|
576
|
-
|
|
577
|
-
// Take screenshot
|
|
578
|
-
await page.screenshot({ path: 'screenshots/${component.exports[0]?.toLowerCase() || 'component'}.png' });
|
|
579
|
-
});
|
|
580
|
-
|
|
581
|
-
test('should handle user interaction', async ({ page }) => {
|
|
582
|
-
const element = await page.locator('[data-testid="${component.exports[0]?.toLowerCase() || 'component'}"]');
|
|
583
|
-
await element.click();
|
|
584
|
-
|
|
585
|
-
// Verify state change
|
|
586
|
-
await expect(element).toHaveAttribute('data-active', 'true');
|
|
587
|
-
});
|
|
588
|
-
});
|
|
565
|
+
return `// E2E test for ${component.path}
|
|
566
|
+
import { test, expect } from '@playwright/test';
|
|
567
|
+
|
|
568
|
+
test.describe('${component.exports[0] || 'Component'} E2E', () => {
|
|
569
|
+
test.beforeEach(async ({ page }) => {
|
|
570
|
+
await page.goto('/');
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
test('should render correctly', async ({ page }) => {
|
|
574
|
+
// Navigate to component page
|
|
575
|
+
await page.waitForSelector('[data-testid="${component.exports[0]?.toLowerCase() || 'component'}"]');
|
|
576
|
+
|
|
577
|
+
// Take screenshot
|
|
578
|
+
await page.screenshot({ path: 'screenshots/${component.exports[0]?.toLowerCase() || 'component'}.png' });
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
test('should handle user interaction', async ({ page }) => {
|
|
582
|
+
const element = await page.locator('[data-testid="${component.exports[0]?.toLowerCase() || 'component'}"]');
|
|
583
|
+
await element.click();
|
|
584
|
+
|
|
585
|
+
// Verify state change
|
|
586
|
+
await expect(element).toHaveAttribute('data-active', 'true');
|
|
587
|
+
});
|
|
588
|
+
});
|
|
589
589
|
`;
|
|
590
590
|
}
|
|
591
591
|
// Default Cypress style
|
|
592
|
-
return `// E2E test for ${component.path}
|
|
593
|
-
describe('${component.exports[0] || 'Component'} E2E', () => {
|
|
594
|
-
beforeEach(() => {
|
|
595
|
-
cy.visit('/');
|
|
596
|
-
});
|
|
597
|
-
|
|
598
|
-
it('should render correctly', () => {
|
|
599
|
-
cy.get('[data-testid="${component.exports[0]?.toLowerCase() || 'component'}"]')
|
|
600
|
-
.should('be.visible');
|
|
601
|
-
|
|
602
|
-
cy.screenshot('${component.exports[0]?.toLowerCase() || 'component'}');
|
|
603
|
-
});
|
|
604
|
-
|
|
605
|
-
it('should handle user interaction', () => {
|
|
606
|
-
cy.get('[data-testid="${component.exports[0]?.toLowerCase() || 'component'}"]')
|
|
607
|
-
.click()
|
|
608
|
-
.should('have.attr', 'data-active', 'true');
|
|
609
|
-
});
|
|
610
|
-
});
|
|
592
|
+
return `// E2E test for ${component.path}
|
|
593
|
+
describe('${component.exports[0] || 'Component'} E2E', () => {
|
|
594
|
+
beforeEach(() => {
|
|
595
|
+
cy.visit('/');
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
it('should render correctly', () => {
|
|
599
|
+
cy.get('[data-testid="${component.exports[0]?.toLowerCase() || 'component'}"]')
|
|
600
|
+
.should('be.visible');
|
|
601
|
+
|
|
602
|
+
cy.screenshot('${component.exports[0]?.toLowerCase() || 'component'}');
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
it('should handle user interaction', () => {
|
|
606
|
+
cy.get('[data-testid="${component.exports[0]?.toLowerCase() || 'component'}"]')
|
|
607
|
+
.click()
|
|
608
|
+
.should('have.attr', 'data-active', 'true');
|
|
609
|
+
});
|
|
610
|
+
});
|
|
611
611
|
`;
|
|
612
612
|
}
|
|
613
613
|
/**
|
|
@@ -44,3 +44,21 @@ export declare function isRecoverableError(error: unknown): boolean;
|
|
|
44
44
|
* 判断错误是否为文件系统错误
|
|
45
45
|
*/
|
|
46
46
|
export declare function isFileSystemError(error: unknown): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* ID 验证正则表达式
|
|
49
|
+
*/
|
|
50
|
+
export declare const ID_PATTERNS: {
|
|
51
|
+
taskId: RegExp;
|
|
52
|
+
approvalId: RegExp;
|
|
53
|
+
meetingId: RegExp;
|
|
54
|
+
runId: RegExp;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* 验证 ID 格式
|
|
58
|
+
* @returns true if valid, false otherwise
|
|
59
|
+
*/
|
|
60
|
+
export declare function validateId(id: string, type: keyof typeof ID_PATTERNS): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* 验证并返回 ID,如果无效则抛出错误
|
|
63
|
+
*/
|
|
64
|
+
export declare function validateIdOrThrow(id: string, type: keyof typeof ID_PATTERNS): void;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ID_PATTERNS = void 0;
|
|
3
4
|
exports.toError = toError;
|
|
4
5
|
exports.logError = logError;
|
|
5
6
|
exports.wrapError = wrapError;
|
|
@@ -9,6 +10,8 @@ exports.safeExecuteSync = safeExecuteSync;
|
|
|
9
10
|
exports.executeWithError = executeWithError;
|
|
10
11
|
exports.isRecoverableError = isRecoverableError;
|
|
11
12
|
exports.isFileSystemError = isFileSystemError;
|
|
13
|
+
exports.validateId = validateId;
|
|
14
|
+
exports.validateIdOrThrow = validateIdOrThrow;
|
|
12
15
|
// src/utils/error-handler.ts
|
|
13
16
|
const logger_js_1 = require("./logger.js");
|
|
14
17
|
/**
|
|
@@ -121,3 +124,32 @@ function isFileSystemError(error) {
|
|
|
121
124
|
];
|
|
122
125
|
return fsPatterns.some(p => p.test(err.message));
|
|
123
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* ID 验证正则表达式
|
|
129
|
+
*/
|
|
130
|
+
exports.ID_PATTERNS = {
|
|
131
|
+
taskId: /^TASK-\d{3,}$/,
|
|
132
|
+
approvalId: /^APPR-[A-Z0-9]+$/,
|
|
133
|
+
meetingId: /^meeting-[a-z0-9]+$/,
|
|
134
|
+
runId: /^run-\d{8}-[a-z0-9]{4}$/
|
|
135
|
+
};
|
|
136
|
+
/**
|
|
137
|
+
* 验证 ID 格式
|
|
138
|
+
* @returns true if valid, false otherwise
|
|
139
|
+
*/
|
|
140
|
+
function validateId(id, type) {
|
|
141
|
+
if (!id || typeof id !== 'string')
|
|
142
|
+
return false;
|
|
143
|
+
// 防止路径遍历攻击:拒绝包含 .. 或 / 或 \ 的 ID
|
|
144
|
+
if (id.includes('..') || id.includes('/') || id.includes('\\'))
|
|
145
|
+
return false;
|
|
146
|
+
return exports.ID_PATTERNS[type].test(id);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* 验证并返回 ID,如果无效则抛出错误
|
|
150
|
+
*/
|
|
151
|
+
function validateIdOrThrow(id, type) {
|
|
152
|
+
if (!validateId(id, type)) {
|
|
153
|
+
throw new Error(`Invalid ${type} format: ${id}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -301,15 +301,36 @@ class WorktreeSyncManager {
|
|
|
301
301
|
async cleanupWorktree(worktreePath) {
|
|
302
302
|
try {
|
|
303
303
|
const gitRoot = await this.getGitRoot();
|
|
304
|
+
// 0. 检查是否有未提交的改动(防止丢失工作)
|
|
305
|
+
try {
|
|
306
|
+
const { stdout: statusOutput } = await execAsync(`git status --porcelain`, { cwd: worktreePath });
|
|
307
|
+
if (statusOutput.trim().length > 0) {
|
|
308
|
+
// 有未提交改动,记录警告但不强制移除
|
|
309
|
+
console.warn(`⚠️ Worktree ${worktreePath} 有未提交改动,跳过清理`);
|
|
310
|
+
console.warn(` 未提交文件: ${statusOutput.trim().split('\n').length} 个`);
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
// status 检查失败,继续尝试清理(可能 worktree 已损坏)
|
|
316
|
+
}
|
|
304
317
|
// 1. 移除 worktree
|
|
305
|
-
await execAsync(`git worktree remove "${worktreePath}"
|
|
318
|
+
await execAsync(`git worktree remove "${worktreePath}"`, { cwd: gitRoot });
|
|
306
319
|
// 2. 清理可能的残留分支(如果是临时分支)
|
|
307
320
|
// 注意:不自动删除分支,因为可能还需要
|
|
308
321
|
return true;
|
|
309
322
|
}
|
|
310
323
|
catch (error) {
|
|
311
|
-
|
|
312
|
-
|
|
324
|
+
// 如果普通移除失败,尝试强制移除(但已确认无未提交改动)
|
|
325
|
+
try {
|
|
326
|
+
const gitRoot = await this.getGitRoot();
|
|
327
|
+
await execAsync(`git worktree remove "${worktreePath}" --force`, { cwd: gitRoot });
|
|
328
|
+
return true;
|
|
329
|
+
}
|
|
330
|
+
catch (forceError) {
|
|
331
|
+
(0, error_handler_js_1.logError)(forceError, { operation: 'cleanupWorktreeForce', file: worktreePath });
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
313
334
|
}
|
|
314
335
|
}
|
|
315
336
|
/**
|
package/package.json
CHANGED
|
@@ -1,61 +1,61 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "openmatrix",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "AI Agent task orchestration system with Claude Code Skills integration",
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"types": "dist/index.d.ts",
|
|
7
|
-
"bin": {
|
|
8
|
-
"openmatrix": "dist/cli/index.js"
|
|
9
|
-
},
|
|
10
|
-
"files": [
|
|
11
|
-
"dist",
|
|
12
|
-
"skills",
|
|
13
|
-
"scripts",
|
|
14
|
-
"README.md"
|
|
15
|
-
],
|
|
16
|
-
"scripts": {
|
|
17
|
-
"build": "tsc",
|
|
18
|
-
"dev": "tsx src/cli/index.ts",
|
|
19
|
-
"test": "vitest",
|
|
20
|
-
"lint": "eslint src --ext .ts",
|
|
21
|
-
"typecheck": "tsc --noEmit",
|
|
22
|
-
"postinstall": "node scripts/install-skills.js"
|
|
23
|
-
},
|
|
24
|
-
"keywords": [
|
|
25
|
-
"claude",
|
|
26
|
-
"claude-code",
|
|
27
|
-
"ai-agent",
|
|
28
|
-
"task-orchestration",
|
|
29
|
-
"automation",
|
|
30
|
-
"multi-agent"
|
|
31
|
-
],
|
|
32
|
-
"author": "",
|
|
33
|
-
"license": "MIT",
|
|
34
|
-
"type": "commonjs",
|
|
35
|
-
"repository": {
|
|
36
|
-
"type": "git",
|
|
37
|
-
"url": "git+https://github.com/bigfish1913/openmatrix.git"
|
|
38
|
-
},
|
|
39
|
-
"homepage": "https://github.com/bigfish1913/openmatrix#readme",
|
|
40
|
-
"bugs": {
|
|
41
|
-
"url": "https://github.com/bigfish1913/openmatrix/issues"
|
|
42
|
-
},
|
|
43
|
-
"dependencies": {
|
|
44
|
-
"chalk": "^5.6.2",
|
|
45
|
-
"chokidar": "^5.0.0",
|
|
46
|
-
"commander": "^14.0.3",
|
|
47
|
-
"winston": "^3.19.0"
|
|
48
|
-
},
|
|
49
|
-
"devDependencies": {
|
|
50
|
-
"@types/node": "^22.0.0",
|
|
51
|
-
"@typescript-eslint/eslint-plugin": "^8.58.1",
|
|
52
|
-
"@typescript-eslint/parser": "^8.58.1",
|
|
53
|
-
"@vitest/coverage-v8": "^1.6.1",
|
|
54
|
-
"eslint": "^10.2.0",
|
|
55
|
-
"typescript": "^5.3.3",
|
|
56
|
-
"vitest": "^1.6.0"
|
|
57
|
-
},
|
|
58
|
-
"engines": {
|
|
59
|
-
"node": ">=18.0.0"
|
|
60
|
-
}
|
|
61
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "openmatrix",
|
|
3
|
+
"version": "0.2.34",
|
|
4
|
+
"description": "AI Agent task orchestration system with Claude Code Skills integration",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"openmatrix": "dist/cli/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"skills",
|
|
13
|
+
"scripts",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"dev": "tsx src/cli/index.ts",
|
|
19
|
+
"test": "vitest",
|
|
20
|
+
"lint": "eslint src --ext .ts",
|
|
21
|
+
"typecheck": "tsc --noEmit",
|
|
22
|
+
"postinstall": "node scripts/install-skills.js"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"claude",
|
|
26
|
+
"claude-code",
|
|
27
|
+
"ai-agent",
|
|
28
|
+
"task-orchestration",
|
|
29
|
+
"automation",
|
|
30
|
+
"multi-agent"
|
|
31
|
+
],
|
|
32
|
+
"author": "",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"type": "commonjs",
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/bigfish1913/openmatrix.git"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/bigfish1913/openmatrix#readme",
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/bigfish1913/openmatrix/issues"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"chalk": "^5.6.2",
|
|
45
|
+
"chokidar": "^5.0.0",
|
|
46
|
+
"commander": "^14.0.3",
|
|
47
|
+
"winston": "^3.19.0"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/node": "^22.0.0",
|
|
51
|
+
"@typescript-eslint/eslint-plugin": "^8.58.1",
|
|
52
|
+
"@typescript-eslint/parser": "^8.58.1",
|
|
53
|
+
"@vitest/coverage-v8": "^1.6.1",
|
|
54
|
+
"eslint": "^10.2.0",
|
|
55
|
+
"typescript": "^5.3.3",
|
|
56
|
+
"vitest": "^1.6.0"
|
|
57
|
+
},
|
|
58
|
+
"engines": {
|
|
59
|
+
"node": ">=18.0.0"
|
|
60
|
+
}
|
|
61
|
+
}
|