@minko-ai/minko 0.1.0 → 0.1.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.
Files changed (189) hide show
  1. package/README.md +577 -2
  2. package/assets/mascot-preview.png +0 -0
  3. package/assets/mascot-preview.txt +46 -0
  4. package/assets/mascot-source.png +0 -0
  5. package/assets/skills/README.md +21 -0
  6. package/assets/skills/brainstorming/LICENSE +21 -0
  7. package/assets/skills/brainstorming/SKILL.md +316 -0
  8. package/assets/skills/buddhist-method/LICENSE +21 -0
  9. package/assets/skills/buddhist-method/SKILL.md +137 -0
  10. package/assets/skills/buddhist-method/references/ariyasacca-debug.md +57 -0
  11. package/assets/skills/buddhist-method/references/extended-principles.md +100 -0
  12. package/assets/skills/code-humanizer/LICENSE +21 -0
  13. package/assets/skills/code-humanizer/SKILL.md +174 -0
  14. package/assets/skills/code-review/LICENSE +21 -0
  15. package/assets/skills/code-review/SKILL.md +232 -0
  16. package/assets/skills/code-review/reference/angular.md +768 -0
  17. package/assets/skills/code-review/reference/architecture-review-guide.md +472 -0
  18. package/assets/skills/code-review/reference/c.md +890 -0
  19. package/assets/skills/code-review/reference/code-quality-universal.md +488 -0
  20. package/assets/skills/code-review/reference/code-review-best-practices.md +136 -0
  21. package/assets/skills/code-review/reference/common-bugs-checklist.md +302 -0
  22. package/assets/skills/code-review/reference/cpp.md +893 -0
  23. package/assets/skills/code-review/reference/cross-cutting/async-concurrency-patterns.md +515 -0
  24. package/assets/skills/code-review/reference/cross-cutting/error-handling-principles.md +492 -0
  25. package/assets/skills/code-review/reference/cross-cutting/n-plus-one-queries.md +309 -0
  26. package/assets/skills/code-review/reference/cross-cutting/sql-injection-prevention.md +308 -0
  27. package/assets/skills/code-review/reference/cross-cutting/xss-prevention.md +264 -0
  28. package/assets/skills/code-review/reference/csharp.md +525 -0
  29. package/assets/skills/code-review/reference/css-less-sass.md +661 -0
  30. package/assets/skills/code-review/reference/dart.md +670 -0
  31. package/assets/skills/code-review/reference/django.md +985 -0
  32. package/assets/skills/code-review/reference/fastapi.md +580 -0
  33. package/assets/skills/code-review/reference/go.md +993 -0
  34. package/assets/skills/code-review/reference/java.md +409 -0
  35. package/assets/skills/code-review/reference/java8.md +586 -0
  36. package/assets/skills/code-review/reference/kotlin.md +1018 -0
  37. package/assets/skills/code-review/reference/nestjs.md +593 -0
  38. package/assets/skills/code-review/reference/performance-review-guide.md +816 -0
  39. package/assets/skills/code-review/reference/php.md +684 -0
  40. package/assets/skills/code-review/reference/python.md +1073 -0
  41. package/assets/skills/code-review/reference/qt.md +757 -0
  42. package/assets/skills/code-review/reference/react.md +871 -0
  43. package/assets/skills/code-review/reference/ruby.md +964 -0
  44. package/assets/skills/code-review/reference/rust.md +846 -0
  45. package/assets/skills/code-review/reference/security-review-guide.md +494 -0
  46. package/assets/skills/code-review/reference/svelte.md +1064 -0
  47. package/assets/skills/code-review/reference/swift.md +936 -0
  48. package/assets/skills/code-review/reference/typescript.md +1016 -0
  49. package/assets/skills/code-review/reference/vue.md +924 -0
  50. package/assets/skills/code-review/reference/zig.md +440 -0
  51. package/assets/skills/cyclomatic-complexity/LICENSE +201 -0
  52. package/assets/skills/cyclomatic-complexity/SKILL.md +69 -0
  53. package/assets/skills/five-whys/LICENSE +21 -0
  54. package/assets/skills/five-whys/SKILL.md +247 -0
  55. package/assets/skills/five-whys/examples/performance-regression.md +161 -0
  56. package/assets/skills/five-whys/examples/production-incident.md +134 -0
  57. package/assets/skills/five-whys/references/software-patterns.md +284 -0
  58. package/assets/skills/five-whys/references/toyota-origins.md +257 -0
  59. package/assets/skills/systematic-debugging/LICENSE +21 -0
  60. package/assets/skills/systematic-debugging/SKILL.md +283 -0
  61. package/assets/skills/test-driven-development/LICENSE +21 -0
  62. package/assets/skills/test-driven-development/SKILL.md +330 -0
  63. package/assets/skills/verification-before-completion/LICENSE +21 -0
  64. package/assets/skills/verification-before-completion/SKILL.md +120 -0
  65. package/assets/skills/vibe-sec/LICENSE +201 -0
  66. package/assets/skills/vibe-sec/SKILL.md +758 -0
  67. package/assets/skills/writing-plans/LICENSE +21 -0
  68. package/assets/skills/writing-plans/SKILL.md +204 -0
  69. package/bin/minko.js +2 -2
  70. package/dist/agent/approvalTokens.js +224 -0
  71. package/dist/agent/branchLock.js +315 -0
  72. package/dist/agent/compact.js +182 -0
  73. package/dist/agent/context.js +183 -0
  74. package/dist/agent/cron.js +326 -0
  75. package/dist/agent/hooks.js +211 -0
  76. package/dist/agent/lanes.js +511 -0
  77. package/dist/agent/loop.js +337 -0
  78. package/dist/agent/memory.js +159 -0
  79. package/dist/agent/permissions.js +71 -0
  80. package/dist/agent/plan.js +118 -0
  81. package/dist/agent/prompt.js +120 -0
  82. package/dist/agent/subagent.js +67 -0
  83. package/dist/agent/tasks.js +449 -0
  84. package/dist/agent/transcript.js +62 -0
  85. package/dist/agent/workerStore.js +172 -0
  86. package/dist/agent/workerTypes.js +1 -0
  87. package/dist/agent/workers.js +503 -0
  88. package/dist/cli/app.js +261 -0
  89. package/dist/cli/argv.js +206 -0
  90. package/dist/cli/commandsCommand.js +12 -0
  91. package/dist/cli/configCommand.js +170 -0
  92. package/dist/cli/doctorCommand.js +112 -0
  93. package/dist/cli/exportCommand.js +85 -0
  94. package/dist/cli/forkCommand.js +24 -0
  95. package/dist/cli/linkCommand.js +272 -0
  96. package/dist/cli/loginCommand.js +69 -0
  97. package/dist/cli/mcpLoginCommand.js +68 -0
  98. package/dist/cli/pluginCommand.js +113 -0
  99. package/dist/cli/powerCommand.js +174 -0
  100. package/dist/cli/promptFooter.js +74 -0
  101. package/dist/cli/relayCommand.js +38 -0
  102. package/dist/cli/sandboxCommand.js +31 -0
  103. package/dist/cli/serveCommand.js +104 -0
  104. package/dist/cli/statusCommand.js +120 -0
  105. package/dist/cli/tui.js +1104 -0
  106. package/dist/commands/registry.js +99 -0
  107. package/dist/config/paths.js +81 -0
  108. package/dist/config/store.js +605 -0
  109. package/dist/index.js +199 -0
  110. package/dist/link/protocol.js +159 -0
  111. package/dist/link/relay.js +184 -0
  112. package/dist/link/server.js +566 -0
  113. package/dist/link/tunnel.js +90 -0
  114. package/dist/lsp/client.js +340 -0
  115. package/dist/lsp/tool.js +177 -0
  116. package/dist/mcp/client.js +469 -0
  117. package/dist/mcp/httpClient.js +191 -0
  118. package/dist/mcp/server.js +206 -0
  119. package/dist/mcp/tools.js +173 -0
  120. package/dist/oauth/device.js +111 -0
  121. package/dist/oauth/store.js +56 -0
  122. package/dist/plugins/loader.js +291 -0
  123. package/dist/plugins/market.js +98 -0
  124. package/dist/providers/anthropic.js +279 -0
  125. package/dist/providers/factory.js +109 -0
  126. package/dist/providers/openai.js +182 -0
  127. package/dist/providers/retry.js +95 -0
  128. package/dist/providers/sse.js +67 -0
  129. package/dist/providers/types.js +10 -0
  130. package/dist/rag/embeddings.js +171 -0
  131. package/dist/rag/index.js +373 -0
  132. package/dist/rag/server.js +167 -0
  133. package/dist/rag/tool.js +148 -0
  134. package/dist/sandbox/index.js +55 -0
  135. package/dist/sdk/server.js +150 -0
  136. package/dist/sdk/session.js +104 -0
  137. package/dist/session/liveness.js +148 -0
  138. package/dist/session/recorder.js +49 -0
  139. package/dist/session/store.js +171 -0
  140. package/dist/skills/index.js +428 -0
  141. package/dist/theme.js +30 -0
  142. package/dist/tools/fs.js +195 -0
  143. package/dist/tools/notebook.js +109 -0
  144. package/dist/tools/registry.js +32 -0
  145. package/dist/tools/search.js +130 -0
  146. package/dist/tools/shell.js +200 -0
  147. package/dist/tools/structured.js +83 -0
  148. package/dist/tools/types.js +1 -0
  149. package/dist/ui/ansi.js +135 -0
  150. package/dist/ui/banner.js +103 -0
  151. package/dist/ui/input.js +49 -0
  152. package/dist/ui/inputModel.js +113 -0
  153. package/dist/ui/keyboard.js +142 -0
  154. package/dist/ui/mascot.js +34 -0
  155. package/dist/ui/mascotData.js +96 -0
  156. package/dist/ui/md.js +183 -0
  157. package/dist/ui/screen.js +49 -0
  158. package/dist/ui/spinner.js +52 -0
  159. package/dist/ui/transcript.js +287 -0
  160. package/dist/util/clipboard.js +53 -0
  161. package/dist/util/errors.js +50 -0
  162. package/dist/util/version.js +12 -0
  163. package/dist/web/fetch.js +168 -0
  164. package/dist/web/search.js +97 -0
  165. package/package.json +45 -36
  166. package/src/agent/loop.ts +0 -159
  167. package/src/agent/prompt.ts +0 -45
  168. package/src/cli/app.ts +0 -396
  169. package/src/cli/argv.ts +0 -139
  170. package/src/cli/configCommand.ts +0 -175
  171. package/src/config/paths.ts +0 -20
  172. package/src/config/store.ts +0 -149
  173. package/src/index.ts +0 -120
  174. package/src/providers/anthropic.ts +0 -215
  175. package/src/providers/openai.ts +0 -158
  176. package/src/providers/sse.ts +0 -57
  177. package/src/providers/types.ts +0 -59
  178. package/src/session/store.ts +0 -125
  179. package/src/theme.ts +0 -41
  180. package/src/tools/fs.ts +0 -143
  181. package/src/tools/registry.ts +0 -19
  182. package/src/tools/shell.ts +0 -120
  183. package/src/tools/types.ts +0 -26
  184. package/src/ui/ansi.ts +0 -110
  185. package/src/ui/banner.ts +0 -39
  186. package/src/ui/input.ts +0 -54
  187. package/src/ui/md.ts +0 -160
  188. package/src/ui/spinner.ts +0 -58
  189. package/src/util/errors.ts +0 -48
@@ -0,0 +1,511 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import crypto from 'node:crypto';
4
+ import { execFileSync } from 'node:child_process';
5
+ import { minkoHome } from "../config/paths.js";
6
+ import { WorkerRegistry } from "./workers.js";
7
+ import { BranchLockStore } from "./branchLock.js";
8
+ /**
9
+ * Git-worktree lanes: each lane is an isolated branch + directory with a
10
+ * writable worker. Main-tree files are never touched by the worker.
11
+ */
12
+ export class LaneRegistry {
13
+ options;
14
+ workers;
15
+ stateDir;
16
+ lanes = new Map();
17
+ sequence = 0;
18
+ locks;
19
+ constructor(options) {
20
+ this.options = options;
21
+ this.stateDir = options.stateDir ?? path.join(minkoHome(), 'lanes');
22
+ this.locks = options.lockStore ?? new BranchLockStore(path.join(this.stateDir, 'locks'));
23
+ this.workers = new WorkerRegistry({
24
+ getProvider: options.getProvider,
25
+ getConfig: options.getConfig,
26
+ cwd: options.cwd,
27
+ approve: options.approve,
28
+ storeDir: path.join(this.stateDir, 'workers'),
29
+ approvalStore: options.approvalStore,
30
+ });
31
+ this.loadIndex();
32
+ }
33
+ create(input) {
34
+ const prompt = input.prompt.trim();
35
+ if (prompt.length === 0) {
36
+ throw new Error('缺少 prompt 参数');
37
+ }
38
+ const repo = git(cwdPath(this.options.cwd), ['rev-parse', '--show-toplevel']);
39
+ if (repo.length === 0) {
40
+ throw new Error('lane 需要在一个 git 仓库里运行');
41
+ }
42
+ this.sequence += 1;
43
+ const id = 'l-' + Date.now().toString(36) + '-' + this.sequence.toString(36);
44
+ const branch = 'minko/lane-' + id;
45
+ const repoKey = crypto.createHash('sha1').update(repo).digest('hex').slice(0, 8);
46
+ const dir = path.join(this.stateDir, repoKey, id);
47
+ fs.mkdirSync(path.dirname(dir), { recursive: true });
48
+ this.locks.acquire(repo, branch, 'lane:' + id);
49
+ try {
50
+ git(repo, ['worktree', 'add', '-b', branch, dir, 'HEAD']);
51
+ }
52
+ catch (error) {
53
+ this.locks.release(repo, branch, 'lane:' + id);
54
+ throw error;
55
+ }
56
+ const record = {
57
+ id,
58
+ name: input.name?.trim() || id,
59
+ repo,
60
+ branch,
61
+ dir,
62
+ createdAt: new Date().toISOString(),
63
+ };
64
+ try {
65
+ const worker = this.workers.create({
66
+ prompt: lanePrompt(record, prompt),
67
+ name: 'lane:' + id,
68
+ cwd: dir,
69
+ allowWrite: true,
70
+ });
71
+ record.workerId = worker.id;
72
+ }
73
+ catch (error) {
74
+ try {
75
+ git(repo, ['worktree', 'remove', '--force', dir]);
76
+ }
77
+ catch { /* best effort */ }
78
+ this.locks.release(repo, branch, 'lane:' + id);
79
+ throw error;
80
+ }
81
+ this.lanes.set(id, record);
82
+ this.saveIndex();
83
+ return this.info(record);
84
+ }
85
+ list() {
86
+ return [...this.lanes.values()]
87
+ .sort((a, b) => b.createdAt.localeCompare(a.createdAt))
88
+ .map((record) => this.info(record));
89
+ }
90
+ get(id) {
91
+ const record = this.lanes.get(id);
92
+ return record ? this.info(record) : null;
93
+ }
94
+ async wait(id, timeoutMs = 300_000) {
95
+ const record = this.require(id);
96
+ if (record.workerId) {
97
+ await this.workers.wait(record.workerId, timeoutMs);
98
+ }
99
+ return this.info(record);
100
+ }
101
+ status(id) {
102
+ const record = this.require(id);
103
+ const info = this.info(record);
104
+ let worktree = '';
105
+ try {
106
+ worktree = git(record.dir, ['status', '--short']);
107
+ }
108
+ catch (error) {
109
+ worktree = '(无法读取 worktree:' + errorMessage(error) + ')';
110
+ }
111
+ return [
112
+ info.id + ' [' + info.state + '] ' + info.name,
113
+ '分支:' + info.branch,
114
+ '目录:' + info.dir,
115
+ info.result ? '结果:\n' + info.result : '',
116
+ info.error ? '错误:' + info.error : '',
117
+ '工作区:\n' + (worktree.trim() || '(干净)'),
118
+ ].filter((line) => line.length > 0).join('\n');
119
+ }
120
+ diff(id) {
121
+ const record = this.require(id);
122
+ const status = git(record.dir, ['status', '--short']);
123
+ const stat = git(record.dir, ['diff', '--stat']);
124
+ const diff = git(record.dir, ['diff']);
125
+ return [
126
+ '状态:\n' + (status.trim() || '(干净)'),
127
+ '差异统计:\n' + (stat.trim() || '(无)'),
128
+ '差异:\n' + (diff.trim() ? cap(diff.trim(), 20_000) : '(无)'),
129
+ '未跟踪文件不会出现在 diff 中,请看状态列表。',
130
+ ].join('\n\n');
131
+ }
132
+ async merge(id, options = {}) {
133
+ const record = this.require(id);
134
+ const worker = record.workerId ? this.workers.get(record.workerId) : null;
135
+ if (worker && (worker.state === 'running' || worker.state === 'queued')) {
136
+ throw new Error('lane worker 还没有完成,先 lane_wait');
137
+ }
138
+ // Commit whatever the worker wrote inside the isolated worktree.
139
+ const laneStatus = git(record.dir, ['status', '--porcelain']);
140
+ if (laneStatus.trim().length > 0) {
141
+ git(record.dir, ['add', '-A']);
142
+ git(record.dir, [
143
+ '-c', 'user.name=Minko',
144
+ '-c', 'user.email=minko@localhost',
145
+ 'commit', '-m', 'minko lane: ' + record.name + ' (' + record.id + ')',
146
+ ]);
147
+ }
148
+ const target = git(record.repo, ['rev-parse', '--abbrev-ref', 'HEAD']);
149
+ const dirty = git(record.repo, ['status', '--porcelain']);
150
+ if (dirty.trim().length > 0 && options.allowDirty !== true) {
151
+ throw new Error('主工作区有未提交改动;先提交,或传 allow_dirty: true');
152
+ }
153
+ const ahead = Number(git(record.repo, ['rev-list', '--count', target + '..' + record.branch]));
154
+ if (ahead === 0) {
155
+ await this.cleanup(record, options.deleteBranch !== false);
156
+ return { branch: record.branch, mergedInto: target, noChanges: true };
157
+ }
158
+ this.locks.acquire(record.repo, target, 'lane-merge:' + record.id);
159
+ try {
160
+ const strategy = options.strategy ?? 'merge';
161
+ if (strategy === 'squash') {
162
+ git(record.repo, ['merge', '--squash', record.branch]);
163
+ git(record.repo, [
164
+ '-c', 'user.name=Minko',
165
+ '-c', 'user.email=minko@localhost',
166
+ 'commit', '-m', 'minko lane: ' + record.name + ' (' + record.id + ')',
167
+ ]);
168
+ }
169
+ else if (strategy === 'ff') {
170
+ git(record.repo, ['merge', '--ff-only', record.branch]);
171
+ }
172
+ else {
173
+ git(record.repo, [
174
+ 'merge', '--no-ff', record.branch,
175
+ '-m', 'minko lane: ' + record.name + ' (' + record.id + ')',
176
+ ]);
177
+ }
178
+ }
179
+ catch (error) {
180
+ try {
181
+ git(record.repo, ['merge', '--abort']);
182
+ }
183
+ catch { /* nothing to abort */ }
184
+ throw new Error('合并失败(已 abort):' + errorMessage(error));
185
+ }
186
+ finally {
187
+ this.locks.release(record.repo, target, 'lane-merge:' + record.id);
188
+ }
189
+ let commit;
190
+ try {
191
+ commit = git(record.repo, ['rev-parse', 'HEAD']);
192
+ }
193
+ catch { /* optional */ }
194
+ await this.cleanup(record, options.deleteBranch !== false);
195
+ return { branch: record.branch, mergedInto: target, commit };
196
+ }
197
+ async cleanup(record, deleteBranch) {
198
+ if (record.workerId) {
199
+ this.workers.stop(record.workerId);
200
+ await this.workers.wait(record.workerId, 2_000).catch(() => null);
201
+ }
202
+ try {
203
+ git(record.repo, ['worktree', 'remove', '--force', record.dir]);
204
+ }
205
+ catch {
206
+ // The merged branch is still usable; a locked worktree can be pruned later.
207
+ }
208
+ if (deleteBranch) {
209
+ try {
210
+ git(record.repo, ['branch', '-D', record.branch]);
211
+ }
212
+ catch { /* keep branch */ }
213
+ }
214
+ this.locks.release(record.repo, record.branch);
215
+ this.lanes.delete(record.id);
216
+ this.saveIndex();
217
+ }
218
+ async close(id, keepBranch = false) {
219
+ const record = this.lanes.get(id);
220
+ if (!record) {
221
+ return false;
222
+ }
223
+ if (record.workerId) {
224
+ this.workers.stop(record.workerId);
225
+ await this.workers.wait(record.workerId, 2_000).catch(() => null);
226
+ }
227
+ try {
228
+ git(record.repo, ['worktree', 'remove', '--force', record.dir]);
229
+ }
230
+ catch {
231
+ return false;
232
+ }
233
+ if (!keepBranch) {
234
+ try {
235
+ git(record.repo, ['branch', '-D', record.branch]);
236
+ }
237
+ catch { /* keep branch */ }
238
+ }
239
+ this.locks.release(record.repo, record.branch);
240
+ this.lanes.delete(id);
241
+ this.saveIndex();
242
+ return true;
243
+ }
244
+ closeAll() {
245
+ this.workers.stopAll();
246
+ }
247
+ info(record) {
248
+ const worker = record.workerId ? this.workers.get(record.workerId) : null;
249
+ return {
250
+ ...record,
251
+ state: worker?.state ?? 'detached',
252
+ result: worker?.result,
253
+ error: worker?.error,
254
+ };
255
+ }
256
+ require(id) {
257
+ const record = this.lanes.get(id);
258
+ if (!record) {
259
+ throw new Error('找不到 lane:' + id);
260
+ }
261
+ return record;
262
+ }
263
+ indexFile() {
264
+ return path.join(this.stateDir, 'index.json');
265
+ }
266
+ loadIndex() {
267
+ try {
268
+ const raw = JSON.parse(fs.readFileSync(this.indexFile(), 'utf8'));
269
+ if (!Array.isArray(raw.lanes)) {
270
+ return;
271
+ }
272
+ for (const item of raw.lanes) {
273
+ if (item === null || typeof item !== 'object') {
274
+ continue;
275
+ }
276
+ const record = item;
277
+ if (typeof record.id !== 'string' || typeof record.repo !== 'string'
278
+ || typeof record.branch !== 'string' || typeof record.dir !== 'string'
279
+ || typeof record.name !== 'string' || typeof record.createdAt !== 'string') {
280
+ continue;
281
+ }
282
+ this.lanes.set(record.id, {
283
+ id: record.id,
284
+ name: record.name,
285
+ repo: record.repo,
286
+ branch: record.branch,
287
+ dir: record.dir,
288
+ createdAt: record.createdAt,
289
+ });
290
+ }
291
+ }
292
+ catch {
293
+ // No index yet, or it is unreadable.
294
+ }
295
+ }
296
+ saveIndex() {
297
+ try {
298
+ fs.mkdirSync(this.stateDir, { recursive: true });
299
+ fs.writeFileSync(this.indexFile(), JSON.stringify({ lanes: [...this.lanes.values()] }, null, 2), 'utf8');
300
+ }
301
+ catch {
302
+ // Lane state is advisory; worktrees remain usable without the index.
303
+ }
304
+ }
305
+ }
306
+ /** Lane tools for the main agent. Writes still go through worker approval. */
307
+ export function createLaneTools(registry) {
308
+ const create = {
309
+ readOnly: true,
310
+ spec: {
311
+ name: 'lane_create',
312
+ description: '在一个独立的 git worktree/branch 里启动写入 worker。不会改动主工作区;用 lane_diff 查看改动。',
313
+ parameters: {
314
+ type: 'object',
315
+ properties: {
316
+ prompt: { type: 'string', description: '交给 lane worker 的任务' },
317
+ name: { type: 'string', description: '便于识别的短名称' },
318
+ },
319
+ required: ['prompt'],
320
+ additionalProperties: false,
321
+ },
322
+ },
323
+ async run(args, ctx) {
324
+ const prompt = typeof args.prompt === 'string' ? args.prompt : '';
325
+ if (ctx.planMode) {
326
+ throw new Error('计划模式不能创建 lane;先提交计划或退出计划模式');
327
+ }
328
+ const name = typeof args.name === 'string' ? args.name : undefined;
329
+ const lane = registry.create({ prompt, name });
330
+ return '已创建 lane ' + lane.id + '(' + lane.name + ')\n'
331
+ + '分支:' + lane.branch + '\n目录:' + lane.dir + '\n'
332
+ + 'worker:' + (lane.workerId ?? '-') + '\n用 lane_wait 等待结果,lane_diff 查看改动。';
333
+ },
334
+ };
335
+ const list = {
336
+ readOnly: true,
337
+ spec: {
338
+ name: 'lane_list',
339
+ description: '列出当前会话的 git worktree lane 及状态。',
340
+ parameters: { type: 'object', properties: {}, additionalProperties: false },
341
+ },
342
+ async run() {
343
+ const lanes = registry.list();
344
+ if (lanes.length === 0) {
345
+ return '还没有 lane。';
346
+ }
347
+ return lanes.map((lane) => lane.id + ' [' + lane.state + '] ' + lane.name
348
+ + ' · ' + lane.branch).join('\n');
349
+ },
350
+ };
351
+ const status = {
352
+ readOnly: true,
353
+ spec: {
354
+ name: 'lane_status',
355
+ description: '查看一个 lane 的 worker 状态、结果和 worktree 文件状态。',
356
+ parameters: {
357
+ type: 'object',
358
+ properties: { id: { type: 'string' } },
359
+ required: ['id'],
360
+ additionalProperties: false,
361
+ },
362
+ },
363
+ async run(args) {
364
+ return registry.status(requiredId(args));
365
+ },
366
+ };
367
+ const wait = {
368
+ readOnly: true,
369
+ spec: {
370
+ name: 'lane_wait',
371
+ description: '等待 lane worker 完成并返回状态与结果。',
372
+ parameters: {
373
+ type: 'object',
374
+ properties: {
375
+ id: { type: 'string' },
376
+ timeout_ms: { type: 'number' },
377
+ },
378
+ required: ['id'],
379
+ additionalProperties: false,
380
+ },
381
+ },
382
+ async run(args) {
383
+ const timeout = typeof args.timeout_ms === 'number' && args.timeout_ms > 0
384
+ ? Math.floor(args.timeout_ms)
385
+ : 120_000;
386
+ const lane = await registry.wait(requiredId(args), timeout);
387
+ return lane.id + ' [' + lane.state + '] ' + lane.name
388
+ + (lane.result ? '\n' + lane.result : '')
389
+ + (lane.error ? '\n错误:' + lane.error : '');
390
+ },
391
+ };
392
+ const diff = {
393
+ readOnly: true,
394
+ spec: {
395
+ name: 'lane_diff',
396
+ description: '查看 lane 里的 git 状态、差异统计和 diff。',
397
+ parameters: {
398
+ type: 'object',
399
+ properties: { id: { type: 'string' } },
400
+ required: ['id'],
401
+ additionalProperties: false,
402
+ },
403
+ },
404
+ async run(args) {
405
+ return registry.diff(requiredId(args));
406
+ },
407
+ };
408
+ const close = {
409
+ readOnly: true,
410
+ spec: {
411
+ name: 'lane_close',
412
+ description: '停止 lane worker 并删除 worktree;默认也删除分支,keep_branch=true 时保留分支供手动合并。',
413
+ parameters: {
414
+ type: 'object',
415
+ properties: {
416
+ id: { type: 'string' },
417
+ keep_branch: { type: 'boolean' },
418
+ },
419
+ required: ['id'],
420
+ additionalProperties: false,
421
+ },
422
+ },
423
+ async run(args) {
424
+ const removed = await registry.close(requiredId(args), args.keep_branch === true);
425
+ if (!removed) {
426
+ throw new Error('删除 worktree 失败;可能有进程仍占用目录');
427
+ }
428
+ return '已关闭 lane ' + requiredId(args);
429
+ },
430
+ };
431
+ const merge = {
432
+ readOnly: false,
433
+ needsApproval: () => true,
434
+ spec: {
435
+ name: 'lane_merge',
436
+ description: '提交 lane 里的改动并合并到当前分支;冲突时自动 abort 并保留 lane。',
437
+ parameters: {
438
+ type: 'object',
439
+ properties: {
440
+ id: { type: 'string' },
441
+ strategy: { type: 'string', enum: ['merge', 'squash', 'ff'] },
442
+ delete_branch: { type: 'boolean', description: '合并后删除 lane 分支,默认 true' },
443
+ allow_dirty: { type: 'boolean', description: '允许主工作区有未提交改动' },
444
+ },
445
+ required: ['id'],
446
+ additionalProperties: false,
447
+ },
448
+ },
449
+ async run(args) {
450
+ const strategy = args.strategy === 'squash' || args.strategy === 'ff'
451
+ ? args.strategy
452
+ : 'merge';
453
+ const result = await registry.merge(requiredId(args), {
454
+ strategy,
455
+ deleteBranch: args.delete_branch !== false,
456
+ allowDirty: args.allow_dirty === true,
457
+ });
458
+ if (result.noChanges) {
459
+ return 'lane ' + result.branch + ' 没有改动可合并,已清理。';
460
+ }
461
+ return '已合并 ' + result.branch + ' → ' + result.mergedInto
462
+ + (result.commit ? ' · ' + result.commit.slice(0, 10) : '');
463
+ },
464
+ };
465
+ return [create, list, status, wait, diff, close, merge];
466
+ }
467
+ function requiredId(args) {
468
+ const id = typeof args.id === 'string' ? args.id.trim() : '';
469
+ if (id.length === 0) {
470
+ throw new Error('缺少 id 参数');
471
+ }
472
+ return id;
473
+ }
474
+ function lanePrompt(record, prompt) {
475
+ return [
476
+ '【Lane 任务】你正在一个独立的 git worktree 中工作。',
477
+ '目录:' + record.dir,
478
+ '分支:' + record.branch,
479
+ '只修改这个目录里的文件,不要碰主工作区。',
480
+ '完成后总结改了哪些文件、怎么验证。',
481
+ '',
482
+ prompt,
483
+ ].join('\n');
484
+ }
485
+ function cwdPath(cwd) {
486
+ return path.resolve(cwd);
487
+ }
488
+ function git(cwd, args) {
489
+ try {
490
+ return execFileSync('git', ['-C', cwd, ...args], {
491
+ encoding: 'utf8',
492
+ stdio: ['ignore', 'pipe', 'pipe'],
493
+ timeout: 20_000,
494
+ windowsHide: true,
495
+ }).trim();
496
+ }
497
+ catch (error) {
498
+ const stderr = error.stderr;
499
+ const detail = stderr ? stderr.toString().trim() : errorMessage(error);
500
+ throw new Error(detail || 'git 命令失败:' + args.join(' '));
501
+ }
502
+ }
503
+ function errorMessage(error) {
504
+ return error instanceof Error ? error.message : String(error);
505
+ }
506
+ function cap(text, max) {
507
+ if (text.length <= max) {
508
+ return text;
509
+ }
510
+ return text.slice(0, max) + '\n…(diff 过长,已截断)';
511
+ }