openmatrix 0.2.27 → 0.2.28
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/dist/orchestrator/git-commit-manager.d.ts +13 -0
- package/dist/orchestrator/git-commit-manager.js +57 -0
- package/dist/utils/gitignore.js +2 -0
- package/dist/utils/worktree-sync.d.ts +100 -0
- package/dist/utils/worktree-sync.js +352 -0
- package/package.json +1 -1
- package/skills/auto.md +5 -0
- package/skills/start.md +758 -691
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Task } from '../types/index.js';
|
|
2
|
+
import { WorktreeSyncManager } from '../utils/worktree-sync.js';
|
|
2
3
|
export interface CommitInfo {
|
|
3
4
|
taskId: string;
|
|
4
5
|
taskTitle: string;
|
|
@@ -25,6 +26,7 @@ export declare class GitCommitManager {
|
|
|
25
26
|
private repoPath;
|
|
26
27
|
private gitRoot;
|
|
27
28
|
private enabled;
|
|
29
|
+
private worktreeSyncManager;
|
|
28
30
|
constructor(repoPath?: string);
|
|
29
31
|
/**
|
|
30
32
|
* 获取 git 仓库根目录(支持 .git 在父级目录的情况)
|
|
@@ -107,4 +109,15 @@ export declare class GitCommitManager {
|
|
|
107
109
|
* 提交验收阶段完成
|
|
108
110
|
*/
|
|
109
111
|
commitAcceptComplete(task: Task, runId: string): Promise<CommitResult>;
|
|
112
|
+
/**
|
|
113
|
+
* 同步 worktree 改动到主工作树
|
|
114
|
+
*
|
|
115
|
+
* 当 Agent 在 worktree 中工作时,改动会提交到 worktree 的分支。
|
|
116
|
+
* 此方法将这些改动同步回主工作树,确保提交可见。
|
|
117
|
+
*/
|
|
118
|
+
private syncWorktreeChanges;
|
|
119
|
+
/**
|
|
120
|
+
* 获取 WorktreeSyncManager 实例(供外部调用)
|
|
121
|
+
*/
|
|
122
|
+
getWorktreeSyncManager(): WorktreeSyncManager;
|
|
110
123
|
}
|
|
@@ -41,6 +41,7 @@ const path = __importStar(require("path"));
|
|
|
41
41
|
const fs = __importStar(require("fs/promises"));
|
|
42
42
|
const gitignore_js_1 = require("../utils/gitignore.js");
|
|
43
43
|
const error_handler_js_1 = require("../utils/error-handler.js");
|
|
44
|
+
const worktree_sync_js_1 = require("../utils/worktree-sync.js");
|
|
44
45
|
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
45
46
|
/**
|
|
46
47
|
* GitCommitManager - Git 自动提交管理器
|
|
@@ -54,8 +55,10 @@ class GitCommitManager {
|
|
|
54
55
|
repoPath;
|
|
55
56
|
gitRoot = null;
|
|
56
57
|
enabled = true;
|
|
58
|
+
worktreeSyncManager;
|
|
57
59
|
constructor(repoPath = process.cwd()) {
|
|
58
60
|
this.repoPath = repoPath;
|
|
61
|
+
this.worktreeSyncManager = new worktree_sync_js_1.WorktreeSyncManager(repoPath);
|
|
59
62
|
}
|
|
60
63
|
/**
|
|
61
64
|
* 获取 git 仓库根目录(支持 .git 在父级目录的情况)
|
|
@@ -329,6 +332,8 @@ class GitCommitManager {
|
|
|
329
332
|
}
|
|
330
333
|
// 确保 .gitignore 中包含 .openmatrix(写入到 git 根目录)
|
|
331
334
|
await (0, gitignore_js_1.ensureOpenmatrixGitignore)(this.repoPath);
|
|
335
|
+
// 🔄 同步 worktree 改动到主工作树(关键步骤)
|
|
336
|
+
await this.syncWorktreeChanges();
|
|
332
337
|
// 获取未提交的文件(用于生成 commit message,在 git add 之前获取)
|
|
333
338
|
const files = await this.getUncommittedFiles();
|
|
334
339
|
if (files.length === 0) {
|
|
@@ -449,5 +454,57 @@ class GitCommitManager {
|
|
|
449
454
|
impactScope: []
|
|
450
455
|
});
|
|
451
456
|
}
|
|
457
|
+
/**
|
|
458
|
+
* 同步 worktree 改动到主工作树
|
|
459
|
+
*
|
|
460
|
+
* 当 Agent 在 worktree 中工作时,改动会提交到 worktree 的分支。
|
|
461
|
+
* 此方法将这些改动同步回主工作树,确保提交可见。
|
|
462
|
+
*/
|
|
463
|
+
async syncWorktreeChanges() {
|
|
464
|
+
try {
|
|
465
|
+
// 检查是否有 Agent worktree
|
|
466
|
+
const hasChanges = await this.worktreeSyncManager.hasUnsyncedChanges();
|
|
467
|
+
if (!hasChanges) {
|
|
468
|
+
console.log('✅ 未检测到 worktree 改动,跳过同步');
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
console.log('🔄 检测到 worktree 改动,开始同步...');
|
|
472
|
+
// 执行完整同步
|
|
473
|
+
const { syncResults, cleanupResults } = await this.worktreeSyncManager.fullSync();
|
|
474
|
+
// 输出同步结果
|
|
475
|
+
for (const result of syncResults) {
|
|
476
|
+
if (result.success && result.syncedFiles.length > 0) {
|
|
477
|
+
console.log(`✅ 同步成功: ${result.syncedFiles.length} 个文件`);
|
|
478
|
+
console.log(` 文件: ${result.syncedFiles.slice(0, 5).join(', ')}${result.syncedFiles.length > 5 ? '...' : ''}`);
|
|
479
|
+
if (result.commitHash) {
|
|
480
|
+
console.log(` 来源提交: ${result.commitHash}`);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
else if (result.error) {
|
|
484
|
+
console.log(`⚠️ 同步跳过: ${result.error}`);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
// 输出清理结果
|
|
488
|
+
for (const cleanup of cleanupResults) {
|
|
489
|
+
if (cleanup.success) {
|
|
490
|
+
console.log(`🧹 清理 worktree: ${cleanup.path}`);
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
console.log(`⚠️ 清理失败: ${cleanup.path}`);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
catch (error) {
|
|
498
|
+
// 同步失败不应该阻止提交,但需要记录警告
|
|
499
|
+
console.warn('⚠️ Worktree 同步失败:', error instanceof Error ? error.message : String(error));
|
|
500
|
+
(0, error_handler_js_1.logError)(error, { operation: 'syncWorktreeChanges', file: this.repoPath });
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* 获取 WorktreeSyncManager 实例(供外部调用)
|
|
505
|
+
*/
|
|
506
|
+
getWorktreeSyncManager() {
|
|
507
|
+
return this.worktreeSyncManager;
|
|
508
|
+
}
|
|
452
509
|
}
|
|
453
510
|
exports.GitCommitManager = GitCommitManager;
|
package/dist/utils/gitignore.js
CHANGED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worktree 同步结果
|
|
3
|
+
*/
|
|
4
|
+
export interface WorktreeSyncResult {
|
|
5
|
+
success: boolean;
|
|
6
|
+
syncedFiles: string[];
|
|
7
|
+
commitHash?: string;
|
|
8
|
+
branch?: string;
|
|
9
|
+
error?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Worktree 信息
|
|
13
|
+
*/
|
|
14
|
+
export interface WorktreeInfo {
|
|
15
|
+
path: string;
|
|
16
|
+
branch: string;
|
|
17
|
+
commit: string;
|
|
18
|
+
isMain: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* WorktreeSyncManager - 管理 Git Worktree 同步
|
|
22
|
+
*
|
|
23
|
+
* 当 Agent 在 worktree 中工作时,改动会提交到 worktree 的分支。
|
|
24
|
+
* 此类负责将这些改动同步回主工作树。
|
|
25
|
+
*
|
|
26
|
+
* 同步策略:
|
|
27
|
+
* 1. 检测 Agent 创建的 worktree
|
|
28
|
+
* 2. 获取 worktree 中的提交和改动文件
|
|
29
|
+
* 3. 将改动文件复制到主工作树(或 cherry-pick 提交)
|
|
30
|
+
* 4. 清理临时 worktree
|
|
31
|
+
*/
|
|
32
|
+
export declare class WorktreeSyncManager {
|
|
33
|
+
private repoPath;
|
|
34
|
+
private gitRoot;
|
|
35
|
+
constructor(repoPath?: string);
|
|
36
|
+
/**
|
|
37
|
+
* 获取 git 仓库根目录
|
|
38
|
+
*/
|
|
39
|
+
private getGitRoot;
|
|
40
|
+
/**
|
|
41
|
+
* 列出所有 worktree
|
|
42
|
+
*/
|
|
43
|
+
listWorktrees(): Promise<WorktreeInfo[]>;
|
|
44
|
+
/**
|
|
45
|
+
* 获取 Agent worktree 目录
|
|
46
|
+
*
|
|
47
|
+
* Claude Code 创建的 worktree 通常在 .claude/worktrees/ 目录下
|
|
48
|
+
*/
|
|
49
|
+
getAgentWorktrees(): Promise<WorktreeInfo[]>;
|
|
50
|
+
/**
|
|
51
|
+
* 获取 worktree 相对于主工作树的改动
|
|
52
|
+
*/
|
|
53
|
+
getWorktreeChanges(worktreePath: string): Promise<string[]>;
|
|
54
|
+
/**
|
|
55
|
+
* 获取 worktree 中最新提交的改动文件
|
|
56
|
+
*/
|
|
57
|
+
getLatestCommitChanges(worktreePath: string): Promise<string[]>;
|
|
58
|
+
/**
|
|
59
|
+
* 同步 worktree 改动到主工作树
|
|
60
|
+
*
|
|
61
|
+
* 策略:
|
|
62
|
+
* 1. 获取 worktree 中已提交但未同步的改动
|
|
63
|
+
* 2. 将改动文件复制到主工作树
|
|
64
|
+
* 3. 在主工作树中暂存这些文件
|
|
65
|
+
*
|
|
66
|
+
* @param worktreePath worktree 路径(可选,不提供则自动检测所有 Agent worktree)
|
|
67
|
+
*/
|
|
68
|
+
syncWorktreeToMain(worktreePath?: string): Promise<WorktreeSyncResult[]>;
|
|
69
|
+
/**
|
|
70
|
+
* Cherry-pick worktree 的提交到主工作树
|
|
71
|
+
*
|
|
72
|
+
* 替代方案:直接 cherry-pick worktree 的提交
|
|
73
|
+
* 适用于:改动较大、需要保留完整提交历史的场景
|
|
74
|
+
*/
|
|
75
|
+
cherryPickWorktreeCommit(worktreePath: string): Promise<WorktreeSyncResult>;
|
|
76
|
+
/**
|
|
77
|
+
* 清理已同步的 worktree
|
|
78
|
+
*/
|
|
79
|
+
cleanupWorktree(worktreePath: string): Promise<boolean>;
|
|
80
|
+
/**
|
|
81
|
+
* 完整的同步流程
|
|
82
|
+
*
|
|
83
|
+
* 1. 检测所有 Agent worktree
|
|
84
|
+
* 2. 同步改动到主工作树
|
|
85
|
+
* 3. 清理 worktree
|
|
86
|
+
*
|
|
87
|
+
* @returns 同步结果
|
|
88
|
+
*/
|
|
89
|
+
fullSync(): Promise<{
|
|
90
|
+
syncResults: WorktreeSyncResult[];
|
|
91
|
+
cleanupResults: {
|
|
92
|
+
path: string;
|
|
93
|
+
success: boolean;
|
|
94
|
+
}[];
|
|
95
|
+
}>;
|
|
96
|
+
/**
|
|
97
|
+
* 检查是否有未同步的 worktree 改动
|
|
98
|
+
*/
|
|
99
|
+
hasUnsyncedChanges(): Promise<boolean>;
|
|
100
|
+
}
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.WorktreeSyncManager = void 0;
|
|
37
|
+
// src/utils/worktree-sync.ts
|
|
38
|
+
const child_process_1 = require("child_process");
|
|
39
|
+
const util_1 = require("util");
|
|
40
|
+
const fs = __importStar(require("fs/promises"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const error_handler_js_1 = require("./error-handler.js");
|
|
43
|
+
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
44
|
+
/**
|
|
45
|
+
* WorktreeSyncManager - 管理 Git Worktree 同步
|
|
46
|
+
*
|
|
47
|
+
* 当 Agent 在 worktree 中工作时,改动会提交到 worktree 的分支。
|
|
48
|
+
* 此类负责将这些改动同步回主工作树。
|
|
49
|
+
*
|
|
50
|
+
* 同步策略:
|
|
51
|
+
* 1. 检测 Agent 创建的 worktree
|
|
52
|
+
* 2. 获取 worktree 中的提交和改动文件
|
|
53
|
+
* 3. 将改动文件复制到主工作树(或 cherry-pick 提交)
|
|
54
|
+
* 4. 清理临时 worktree
|
|
55
|
+
*/
|
|
56
|
+
class WorktreeSyncManager {
|
|
57
|
+
repoPath;
|
|
58
|
+
gitRoot = null;
|
|
59
|
+
constructor(repoPath = process.cwd()) {
|
|
60
|
+
this.repoPath = repoPath;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 获取 git 仓库根目录
|
|
64
|
+
*/
|
|
65
|
+
async getGitRoot() {
|
|
66
|
+
if (this.gitRoot)
|
|
67
|
+
return this.gitRoot;
|
|
68
|
+
try {
|
|
69
|
+
const { stdout } = await execAsync('git rev-parse --show-toplevel', { cwd: this.repoPath });
|
|
70
|
+
this.gitRoot = stdout.trim();
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
(0, error_handler_js_1.logError)(error, { operation: 'getGitRoot', file: this.repoPath });
|
|
74
|
+
this.gitRoot = this.repoPath;
|
|
75
|
+
}
|
|
76
|
+
return this.gitRoot;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 列出所有 worktree
|
|
80
|
+
*/
|
|
81
|
+
async listWorktrees() {
|
|
82
|
+
try {
|
|
83
|
+
const { stdout } = await execAsync('git worktree list --porcelain', { cwd: this.repoPath });
|
|
84
|
+
const worktrees = [];
|
|
85
|
+
const lines = stdout.split('\n');
|
|
86
|
+
let current = {};
|
|
87
|
+
for (const line of lines) {
|
|
88
|
+
if (line.startsWith('worktree ')) {
|
|
89
|
+
if (current.path) {
|
|
90
|
+
worktrees.push({
|
|
91
|
+
path: current.path,
|
|
92
|
+
branch: current.branch || '',
|
|
93
|
+
commit: current.commit || '',
|
|
94
|
+
isMain: current.isMain || false
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
current = { path: line.slice(9) };
|
|
98
|
+
// 第一个 worktree 是主工作树
|
|
99
|
+
current.isMain = worktrees.length === 0;
|
|
100
|
+
}
|
|
101
|
+
else if (line.startsWith('HEAD ')) {
|
|
102
|
+
current.commit = line.slice(5);
|
|
103
|
+
}
|
|
104
|
+
else if (line.startsWith('branch ')) {
|
|
105
|
+
current.branch = line.slice(7);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// 添加最后一个
|
|
109
|
+
if (current.path) {
|
|
110
|
+
worktrees.push({
|
|
111
|
+
path: current.path,
|
|
112
|
+
branch: current.branch || '',
|
|
113
|
+
commit: current.commit || '',
|
|
114
|
+
isMain: current.isMain || false
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return worktrees;
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
(0, error_handler_js_1.logError)(error, { operation: 'listWorktrees', file: this.repoPath });
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* 获取 Agent worktree 目录
|
|
126
|
+
*
|
|
127
|
+
* Claude Code 创建的 worktree 通常在 .claude/worktrees/ 目录下
|
|
128
|
+
*/
|
|
129
|
+
async getAgentWorktrees() {
|
|
130
|
+
const worktrees = await this.listWorktrees();
|
|
131
|
+
const gitRoot = await this.getGitRoot();
|
|
132
|
+
// 过滤出 Agent 创建的 worktree(通常以 agent- 开头或在 .claude/worktrees 下)
|
|
133
|
+
return worktrees.filter(w => !w.isMain &&
|
|
134
|
+
(w.path.includes('.claude/worktrees') || w.path.includes('agent-')));
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* 获取 worktree 相对于主工作树的改动
|
|
138
|
+
*/
|
|
139
|
+
async getWorktreeChanges(worktreePath) {
|
|
140
|
+
try {
|
|
141
|
+
const gitRoot = await this.getGitRoot();
|
|
142
|
+
// 获取 worktree 相对于主分支的改动文件
|
|
143
|
+
const { stdout } = await execAsync(`git diff --name-only HEAD "${worktreePath}"`, { cwd: gitRoot });
|
|
144
|
+
return stdout.split('\n').filter(f => f.trim());
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
// 如果上面的方法失败,尝试直接在 worktree 中获取未提交的改动
|
|
148
|
+
try {
|
|
149
|
+
const { stdout } = await execAsync('git diff --name-only HEAD', { cwd: worktreePath });
|
|
150
|
+
return stdout.split('\n').filter(f => f.trim());
|
|
151
|
+
}
|
|
152
|
+
catch (innerError) {
|
|
153
|
+
(0, error_handler_js_1.logError)(innerError, { operation: 'getWorktreeChanges', file: worktreePath });
|
|
154
|
+
return [];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* 获取 worktree 中最新提交的改动文件
|
|
160
|
+
*/
|
|
161
|
+
async getLatestCommitChanges(worktreePath) {
|
|
162
|
+
try {
|
|
163
|
+
// 在 worktree 中获取最新提交改动的文件
|
|
164
|
+
const { stdout } = await execAsync('git diff --name-only HEAD~1 HEAD', { cwd: worktreePath });
|
|
165
|
+
return stdout.split('\n').filter(f => f.trim());
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
// 可能只有一个提交,尝试获取与初始提交的差异
|
|
169
|
+
try {
|
|
170
|
+
const { stdout } = await execAsync('git diff --name-only HEAD', { cwd: worktreePath });
|
|
171
|
+
return stdout.split('\n').filter(f => f.trim());
|
|
172
|
+
}
|
|
173
|
+
catch (innerError) {
|
|
174
|
+
(0, error_handler_js_1.logError)(innerError, { operation: 'getLatestCommitChanges', file: worktreePath });
|
|
175
|
+
return [];
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* 同步 worktree 改动到主工作树
|
|
181
|
+
*
|
|
182
|
+
* 策略:
|
|
183
|
+
* 1. 获取 worktree 中已提交但未同步的改动
|
|
184
|
+
* 2. 将改动文件复制到主工作树
|
|
185
|
+
* 3. 在主工作树中暂存这些文件
|
|
186
|
+
*
|
|
187
|
+
* @param worktreePath worktree 路径(可选,不提供则自动检测所有 Agent worktree)
|
|
188
|
+
*/
|
|
189
|
+
async syncWorktreeToMain(worktreePath) {
|
|
190
|
+
const gitRoot = await this.getGitRoot();
|
|
191
|
+
const worktrees = worktreePath
|
|
192
|
+
? [{ path: worktreePath, branch: '', commit: '', isMain: false }]
|
|
193
|
+
: await this.getAgentWorktrees();
|
|
194
|
+
if (worktrees.length === 0) {
|
|
195
|
+
return [{
|
|
196
|
+
success: true,
|
|
197
|
+
syncedFiles: [],
|
|
198
|
+
error: 'No agent worktrees found'
|
|
199
|
+
}];
|
|
200
|
+
}
|
|
201
|
+
const results = [];
|
|
202
|
+
for (const wt of worktrees) {
|
|
203
|
+
try {
|
|
204
|
+
// 1. 获取改动文件
|
|
205
|
+
const changedFiles = await this.getLatestCommitChanges(wt.path);
|
|
206
|
+
if (changedFiles.length === 0) {
|
|
207
|
+
results.push({
|
|
208
|
+
success: true,
|
|
209
|
+
syncedFiles: [],
|
|
210
|
+
branch: wt.branch,
|
|
211
|
+
error: 'No changes in worktree'
|
|
212
|
+
});
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
// 2. 复制文件到主工作树
|
|
216
|
+
const syncedFiles = [];
|
|
217
|
+
for (const file of changedFiles) {
|
|
218
|
+
const srcPath = path.join(wt.path, file);
|
|
219
|
+
const destPath = path.join(gitRoot, file);
|
|
220
|
+
try {
|
|
221
|
+
// 确保目标目录存在
|
|
222
|
+
const destDir = path.dirname(destPath);
|
|
223
|
+
await fs.mkdir(destDir, { recursive: true });
|
|
224
|
+
// 复制文件
|
|
225
|
+
await fs.copyFile(srcPath, destPath);
|
|
226
|
+
syncedFiles.push(file);
|
|
227
|
+
}
|
|
228
|
+
catch (copyError) {
|
|
229
|
+
(0, error_handler_js_1.logError)(copyError, { operation: 'copyFile', file: srcPath, metadata: { dest: destPath } });
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// 3. 在主工作树中暂存文件
|
|
233
|
+
if (syncedFiles.length > 0) {
|
|
234
|
+
const filesArg = syncedFiles.map(f => `"${f}"`).join(' ');
|
|
235
|
+
await execAsync(`git add ${filesArg}`, { cwd: gitRoot });
|
|
236
|
+
}
|
|
237
|
+
// 4. 获取 worktree 的最新提交 hash
|
|
238
|
+
let commitHash;
|
|
239
|
+
try {
|
|
240
|
+
const { stdout } = await execAsync('git rev-parse HEAD', { cwd: wt.path });
|
|
241
|
+
commitHash = stdout.trim();
|
|
242
|
+
}
|
|
243
|
+
catch { }
|
|
244
|
+
results.push({
|
|
245
|
+
success: true,
|
|
246
|
+
syncedFiles,
|
|
247
|
+
commitHash,
|
|
248
|
+
branch: wt.branch
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
catch (error) {
|
|
252
|
+
results.push({
|
|
253
|
+
success: false,
|
|
254
|
+
syncedFiles: [],
|
|
255
|
+
branch: wt.branch,
|
|
256
|
+
error: error instanceof Error ? error.message : String(error)
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return results;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Cherry-pick worktree 的提交到主工作树
|
|
264
|
+
*
|
|
265
|
+
* 替代方案:直接 cherry-pick worktree 的提交
|
|
266
|
+
* 适用于:改动较大、需要保留完整提交历史的场景
|
|
267
|
+
*/
|
|
268
|
+
async cherryPickWorktreeCommit(worktreePath) {
|
|
269
|
+
const gitRoot = await this.getGitRoot();
|
|
270
|
+
try {
|
|
271
|
+
// 1. 获取 worktree 的最新提交 hash
|
|
272
|
+
const { stdout: commitHash } = await execAsync('git rev-parse HEAD', { cwd: worktreePath });
|
|
273
|
+
const hash = commitHash.trim();
|
|
274
|
+
// 2. 在主工作树中 cherry-pick 这个提交
|
|
275
|
+
await execAsync(`git cherry-pick ${hash} --no-commit`, { cwd: gitRoot });
|
|
276
|
+
// 3. 获取被 cherry-pick 的文件列表
|
|
277
|
+
const { stdout: files } = await execAsync('git diff --name-only --cached', { cwd: gitRoot });
|
|
278
|
+
const syncedFiles = files.split('\n').filter(f => f.trim());
|
|
279
|
+
return {
|
|
280
|
+
success: true,
|
|
281
|
+
syncedFiles,
|
|
282
|
+
commitHash: hash
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
// Cherry-pick 可能失败(冲突),尝试放弃并返回错误
|
|
287
|
+
try {
|
|
288
|
+
await execAsync('git cherry-pick --abort', { cwd: gitRoot });
|
|
289
|
+
}
|
|
290
|
+
catch { }
|
|
291
|
+
return {
|
|
292
|
+
success: false,
|
|
293
|
+
syncedFiles: [],
|
|
294
|
+
error: error instanceof Error ? error.message : String(error)
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* 清理已同步的 worktree
|
|
300
|
+
*/
|
|
301
|
+
async cleanupWorktree(worktreePath) {
|
|
302
|
+
try {
|
|
303
|
+
const gitRoot = await this.getGitRoot();
|
|
304
|
+
// 1. 移除 worktree
|
|
305
|
+
await execAsync(`git worktree remove "${worktreePath}" --force`, { cwd: gitRoot });
|
|
306
|
+
// 2. 清理可能的残留分支(如果是临时分支)
|
|
307
|
+
// 注意:不自动删除分支,因为可能还需要
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
(0, error_handler_js_1.logError)(error, { operation: 'cleanupWorktree', file: worktreePath });
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* 完整的同步流程
|
|
317
|
+
*
|
|
318
|
+
* 1. 检测所有 Agent worktree
|
|
319
|
+
* 2. 同步改动到主工作树
|
|
320
|
+
* 3. 清理 worktree
|
|
321
|
+
*
|
|
322
|
+
* @returns 同步结果
|
|
323
|
+
*/
|
|
324
|
+
async fullSync() {
|
|
325
|
+
// 1. 同步
|
|
326
|
+
const syncResults = await this.syncWorktreeToMain();
|
|
327
|
+
// 2. 清理已成功同步的 worktree
|
|
328
|
+
const cleanupResults = [];
|
|
329
|
+
const worktrees = await this.getAgentWorktrees();
|
|
330
|
+
for (const wt of worktrees) {
|
|
331
|
+
const syncResult = syncResults.find(r => r.branch === wt.branch);
|
|
332
|
+
if (syncResult?.success && syncResult.syncedFiles.length > 0) {
|
|
333
|
+
const cleanupSuccess = await this.cleanupWorktree(wt.path);
|
|
334
|
+
cleanupResults.push({ path: wt.path, success: cleanupSuccess });
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return { syncResults, cleanupResults };
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* 检查是否有未同步的 worktree 改动
|
|
341
|
+
*/
|
|
342
|
+
async hasUnsyncedChanges() {
|
|
343
|
+
const worktrees = await this.getAgentWorktrees();
|
|
344
|
+
for (const wt of worktrees) {
|
|
345
|
+
const changes = await this.getLatestCommitChanges(wt.path);
|
|
346
|
+
if (changes.length > 0)
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
exports.WorktreeSyncManager = WorktreeSyncManager;
|
package/package.json
CHANGED
package/skills/auto.md
CHANGED
|
@@ -285,6 +285,11 @@ Agent({
|
|
|
285
285
|
openmatrix complete TASK-XXX --success
|
|
286
286
|
```
|
|
287
287
|
|
|
288
|
+
**注意**: `openmatrix complete` 会自动执行以下操作:
|
|
289
|
+
1. 🔄 **同步 Worktree 改动** - 如果 Agent 在 worktree 中工作,自动将改动同步到主工作树
|
|
290
|
+
2. 📝 **更新上下文** - 将执行摘要写入全局 context.md
|
|
291
|
+
3. ✅ **标记完成** - 更新任务状态并触发 Git 提交
|
|
292
|
+
|
|
288
293
|
3. **获取下一个任务(必须执行,防止上下文压缩丢失):**
|
|
289
294
|
```bash
|
|
290
295
|
openmatrix step --json
|