@hummer98/cmux-team 3.0.0
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/.claude-plugin/marketplace.json +21 -0
- package/.claude-plugin/plugin.json +15 -0
- package/CHANGELOG.md +279 -0
- package/LICENSE +21 -0
- package/README.ja.md +238 -0
- package/README.md +158 -0
- package/bin/cmux-team.js +29 -0
- package/bin/postinstall.js +26 -0
- package/commands/master.md +8 -0
- package/commands/start.md +42 -0
- package/commands/team-archive.md +63 -0
- package/commands/team-design.md +199 -0
- package/commands/team-disband.md +79 -0
- package/commands/team-impl.md +201 -0
- package/commands/team-research.md +182 -0
- package/commands/team-review.md +222 -0
- package/commands/team-spec.md +133 -0
- package/commands/team-status.md +24 -0
- package/commands/team-sync-docs.md +127 -0
- package/commands/team-task.md +158 -0
- package/commands/team-test.md +230 -0
- package/package.json +51 -0
- package/skills/cmux-agent-role/SKILL.md +166 -0
- package/skills/cmux-team/SKILL.md +568 -0
- package/skills/cmux-team/manager/bun.lock +118 -0
- package/skills/cmux-team/manager/cmux.ts +134 -0
- package/skills/cmux-team/manager/conductor.ts +347 -0
- package/skills/cmux-team/manager/daemon.ts +373 -0
- package/skills/cmux-team/manager/e2e.ts +550 -0
- package/skills/cmux-team/manager/logger.ts +13 -0
- package/skills/cmux-team/manager/main.ts +756 -0
- package/skills/cmux-team/manager/master.ts +51 -0
- package/skills/cmux-team/manager/package.json +18 -0
- package/skills/cmux-team/manager/proxy.ts +219 -0
- package/skills/cmux-team/manager/queue.ts +73 -0
- package/skills/cmux-team/manager/schema.ts +84 -0
- package/skills/cmux-team/manager/task.ts +160 -0
- package/skills/cmux-team/manager/template.ts +92 -0
- package/skills/cmux-team/templates/architect.md +25 -0
- package/skills/cmux-team/templates/common-header.md +12 -0
- package/skills/cmux-team/templates/conductor-role.md +244 -0
- package/skills/cmux-team/templates/conductor-task.md +37 -0
- package/skills/cmux-team/templates/conductor.md +275 -0
- package/skills/cmux-team/templates/dockeeper.md +23 -0
- package/skills/cmux-team/templates/implementer.md +24 -0
- package/skills/cmux-team/templates/manager.md +199 -0
- package/skills/cmux-team/templates/master.md +94 -0
- package/skills/cmux-team/templates/researcher.md +24 -0
- package/skills/cmux-team/templates/reviewer.md +28 -0
- package/skills/cmux-team/templates/task-manager.md +22 -0
- package/skills/cmux-team/templates/tester.md +27 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lockfileVersion": 1,
|
|
3
|
+
"configVersion": 1,
|
|
4
|
+
"workspaces": {
|
|
5
|
+
"": {
|
|
6
|
+
"name": "manager",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"ink": "^6.8.0",
|
|
9
|
+
"react": "^19.2.4",
|
|
10
|
+
"zod": "^4.3.6",
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@types/bun": "latest",
|
|
14
|
+
"@types/react": "^19.2.14",
|
|
15
|
+
},
|
|
16
|
+
"peerDependencies": {
|
|
17
|
+
"typescript": "^5",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
"packages": {
|
|
22
|
+
"@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.2.5", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw=="],
|
|
23
|
+
|
|
24
|
+
"@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="],
|
|
25
|
+
|
|
26
|
+
"@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="],
|
|
27
|
+
|
|
28
|
+
"@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
|
|
29
|
+
|
|
30
|
+
"ansi-escapes": ["ansi-escapes@7.3.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg=="],
|
|
31
|
+
|
|
32
|
+
"ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
|
|
33
|
+
|
|
34
|
+
"ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
|
35
|
+
|
|
36
|
+
"auto-bind": ["auto-bind@5.0.1", "", {}, "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg=="],
|
|
37
|
+
|
|
38
|
+
"bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="],
|
|
39
|
+
|
|
40
|
+
"chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
|
|
41
|
+
|
|
42
|
+
"cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="],
|
|
43
|
+
|
|
44
|
+
"cli-cursor": ["cli-cursor@4.0.0", "", { "dependencies": { "restore-cursor": "^4.0.0" } }, "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg=="],
|
|
45
|
+
|
|
46
|
+
"cli-truncate": ["cli-truncate@5.2.0", "", { "dependencies": { "slice-ansi": "^8.0.0", "string-width": "^8.2.0" } }, "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw=="],
|
|
47
|
+
|
|
48
|
+
"code-excerpt": ["code-excerpt@4.0.0", "", { "dependencies": { "convert-to-spaces": "^2.0.1" } }, "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA=="],
|
|
49
|
+
|
|
50
|
+
"convert-to-spaces": ["convert-to-spaces@2.0.1", "", {}, "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ=="],
|
|
51
|
+
|
|
52
|
+
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
|
53
|
+
|
|
54
|
+
"emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
|
|
55
|
+
|
|
56
|
+
"environment": ["environment@1.1.0", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="],
|
|
57
|
+
|
|
58
|
+
"es-toolkit": ["es-toolkit@1.45.1", "", {}, "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw=="],
|
|
59
|
+
|
|
60
|
+
"escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="],
|
|
61
|
+
|
|
62
|
+
"get-east-asian-width": ["get-east-asian-width@1.5.0", "", {}, "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA=="],
|
|
63
|
+
|
|
64
|
+
"indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="],
|
|
65
|
+
|
|
66
|
+
"ink": ["ink@6.8.0", "", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.2.4", "ansi-escapes": "^7.3.0", "ansi-styles": "^6.2.1", "auto-bind": "^5.0.1", "chalk": "^5.6.0", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", "cli-truncate": "^5.1.1", "code-excerpt": "^4.0.0", "es-toolkit": "^1.39.10", "indent-string": "^5.0.0", "is-in-ci": "^2.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.33.0", "scheduler": "^0.27.0", "signal-exit": "^3.0.7", "slice-ansi": "^8.0.0", "stack-utils": "^2.0.6", "string-width": "^8.1.1", "terminal-size": "^4.0.1", "type-fest": "^5.4.1", "widest-line": "^6.0.0", "wrap-ansi": "^9.0.0", "ws": "^8.18.0", "yoga-layout": "~3.2.1" }, "peerDependencies": { "@types/react": ">=19.0.0", "react": ">=19.0.0", "react-devtools-core": ">=6.1.2" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA=="],
|
|
67
|
+
|
|
68
|
+
"is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="],
|
|
69
|
+
|
|
70
|
+
"is-in-ci": ["is-in-ci@2.0.0", "", { "bin": { "is-in-ci": "cli.js" } }, "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w=="],
|
|
71
|
+
|
|
72
|
+
"mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
|
|
73
|
+
|
|
74
|
+
"onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
|
|
75
|
+
|
|
76
|
+
"patch-console": ["patch-console@2.0.0", "", {}, "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA=="],
|
|
77
|
+
|
|
78
|
+
"react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="],
|
|
79
|
+
|
|
80
|
+
"react-reconciler": ["react-reconciler@0.33.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA=="],
|
|
81
|
+
|
|
82
|
+
"restore-cursor": ["restore-cursor@4.0.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg=="],
|
|
83
|
+
|
|
84
|
+
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
|
|
85
|
+
|
|
86
|
+
"signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
|
|
87
|
+
|
|
88
|
+
"slice-ansi": ["slice-ansi@8.0.0", "", { "dependencies": { "ansi-styles": "^6.2.3", "is-fullwidth-code-point": "^5.1.0" } }, "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg=="],
|
|
89
|
+
|
|
90
|
+
"stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="],
|
|
91
|
+
|
|
92
|
+
"string-width": ["string-width@8.2.0", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw=="],
|
|
93
|
+
|
|
94
|
+
"strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
|
|
95
|
+
|
|
96
|
+
"tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="],
|
|
97
|
+
|
|
98
|
+
"terminal-size": ["terminal-size@4.0.1", "", {}, "sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ=="],
|
|
99
|
+
|
|
100
|
+
"type-fest": ["type-fest@5.5.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-PlBfpQwiUvGViBNX84Yxwjsdhd1TUlXr6zjX7eoirtCPIr08NAmxwa+fcYBTeRQxHo9YC9wwF3m9i700sHma8g=="],
|
|
101
|
+
|
|
102
|
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
103
|
+
|
|
104
|
+
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
|
|
105
|
+
|
|
106
|
+
"widest-line": ["widest-line@6.0.0", "", { "dependencies": { "string-width": "^8.1.0" } }, "sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA=="],
|
|
107
|
+
|
|
108
|
+
"wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="],
|
|
109
|
+
|
|
110
|
+
"ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="],
|
|
111
|
+
|
|
112
|
+
"yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="],
|
|
113
|
+
|
|
114
|
+
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
|
115
|
+
|
|
116
|
+
"wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cmux コマンドラッパー — シェルスクリプト不要でペイン操作
|
|
3
|
+
*/
|
|
4
|
+
import { execFile as execFileCb } from "child_process";
|
|
5
|
+
import { promisify } from "util";
|
|
6
|
+
|
|
7
|
+
const execFile = promisify(execFileCb);
|
|
8
|
+
|
|
9
|
+
export async function newSplit(
|
|
10
|
+
direction: "left" | "right" | "up" | "down",
|
|
11
|
+
opts?: { surface?: string }
|
|
12
|
+
): Promise<string> {
|
|
13
|
+
const args = ["new-split", direction];
|
|
14
|
+
if (opts?.surface) args.push("--surface", opts.surface);
|
|
15
|
+
const { stdout } = await execFile("cmux", args);
|
|
16
|
+
const surface = stdout.trim().split(/\s+/)[1];
|
|
17
|
+
if (!surface?.startsWith("surface:")) {
|
|
18
|
+
throw new Error(`Failed to create split: ${stdout}`);
|
|
19
|
+
}
|
|
20
|
+
return surface;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function newSurface(paneId: string): Promise<string> {
|
|
24
|
+
const args = ["new-surface", "--pane", paneId];
|
|
25
|
+
const { stdout } = await execFile("cmux", args);
|
|
26
|
+
const surface = stdout.trim().split(/\s+/)[1];
|
|
27
|
+
if (!surface?.startsWith("surface:")) {
|
|
28
|
+
throw new Error(`Failed to create surface: ${stdout}`);
|
|
29
|
+
}
|
|
30
|
+
return surface;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function listPaneSurfaces(paneId: string): Promise<string[]> {
|
|
34
|
+
const { stdout } = await execFile("cmux", ["list-pane-surfaces", "--pane", paneId]);
|
|
35
|
+
return stdout.trim().split(/\s+/).filter(s => s.startsWith("surface:"));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function send(
|
|
39
|
+
surface: string,
|
|
40
|
+
text: string,
|
|
41
|
+
opts?: { workspace?: string }
|
|
42
|
+
): Promise<void> {
|
|
43
|
+
const args = ["send"];
|
|
44
|
+
if (opts?.workspace) args.push("--workspace", opts.workspace);
|
|
45
|
+
args.push("--surface", surface, text);
|
|
46
|
+
await execFile("cmux", args);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function sendKey(
|
|
50
|
+
surface: string,
|
|
51
|
+
key: string,
|
|
52
|
+
opts?: { workspace?: string }
|
|
53
|
+
): Promise<void> {
|
|
54
|
+
const args = ["send-key"];
|
|
55
|
+
if (opts?.workspace) args.push("--workspace", opts.workspace);
|
|
56
|
+
args.push("--surface", surface, key);
|
|
57
|
+
await execFile("cmux", args);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function readScreen(
|
|
61
|
+
surface: string,
|
|
62
|
+
lines: number = 10,
|
|
63
|
+
opts?: { workspace?: string }
|
|
64
|
+
): Promise<string> {
|
|
65
|
+
const args = ["read-screen", "--surface", surface, "--lines", String(lines)];
|
|
66
|
+
if (opts?.workspace) args.push("--workspace", opts.workspace);
|
|
67
|
+
const { stdout } = await execFile("cmux", args, { timeout: 10_000 });
|
|
68
|
+
return stdout;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function closeSurface(surface: string): Promise<void> {
|
|
72
|
+
await execFile("cmux", ["close-surface", "--surface", surface]).catch(
|
|
73
|
+
() => {}
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function renameTab(
|
|
78
|
+
surface: string,
|
|
79
|
+
title: string
|
|
80
|
+
): Promise<void> {
|
|
81
|
+
await execFile("cmux", ["rename-tab", "--surface", surface, title]).catch(
|
|
82
|
+
() => {}
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function tree(): Promise<string> {
|
|
87
|
+
const { stdout } = await execFile("cmux", ["tree"]);
|
|
88
|
+
return stdout;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function validateSurface(surface: string): Promise<boolean> {
|
|
92
|
+
try {
|
|
93
|
+
const output = await tree();
|
|
94
|
+
return output.includes(surface);
|
|
95
|
+
} catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function waitForTrust(
|
|
101
|
+
surface: string,
|
|
102
|
+
maxAttempts: number = 10
|
|
103
|
+
): Promise<void> {
|
|
104
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
105
|
+
await sleep(3000);
|
|
106
|
+
try {
|
|
107
|
+
const screen = await readScreen(surface, 10);
|
|
108
|
+
if (screen.includes("Yes, I trust")) {
|
|
109
|
+
await sendKey(surface, "return");
|
|
110
|
+
await sleep(3000);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (/Thinking|Reading|❯/.test(screen)) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
} catch {
|
|
117
|
+
// surface がまだ準備できていない
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export async function getCallerSurface(): Promise<string> {
|
|
123
|
+
const { stdout } = await execFile("cmux", ["identify"]);
|
|
124
|
+
const data = JSON.parse(stdout);
|
|
125
|
+
const surface = data?.caller?.surface_ref;
|
|
126
|
+
if (!surface?.startsWith("surface:")) {
|
|
127
|
+
throw new Error(`Failed to get caller surface: ${stdout}`);
|
|
128
|
+
}
|
|
129
|
+
return surface;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function sleep(ms: number): Promise<void> {
|
|
133
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
134
|
+
}
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conductor の初期化・タスク割り当て・監視・結果回収・リセット
|
|
3
|
+
*/
|
|
4
|
+
import { execFile as execFileCb } from "child_process";
|
|
5
|
+
import { promisify } from "util";
|
|
6
|
+
import { existsSync } from "fs";
|
|
7
|
+
import { readFile, writeFile, mkdir, readdir } from "fs/promises";
|
|
8
|
+
import { join, dirname } from "path";
|
|
9
|
+
import { loadTaskState } from "./task";
|
|
10
|
+
import * as cmux from "./cmux";
|
|
11
|
+
import { generateConductorRolePrompt, generateConductorTaskPrompt } from "./template";
|
|
12
|
+
import { log } from "./logger";
|
|
13
|
+
import type { ConductorState } from "./schema";
|
|
14
|
+
|
|
15
|
+
const execFile = promisify(execFileCb);
|
|
16
|
+
|
|
17
|
+
function sleep(ms: number): Promise<void> {
|
|
18
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// --- paneId 取得ヘルパー ---
|
|
22
|
+
|
|
23
|
+
async function getPaneIdForSurface(surface: string): Promise<string | undefined> {
|
|
24
|
+
// cmux tree をパースして surface が属する pane を特定
|
|
25
|
+
try {
|
|
26
|
+
const output = await cmux.tree();
|
|
27
|
+
// tree 出力形式: pane:N の行の後に surface:M が続く
|
|
28
|
+
const lines = output.split("\n");
|
|
29
|
+
let currentPane: string | undefined;
|
|
30
|
+
for (const line of lines) {
|
|
31
|
+
const paneMatch = line.match(/(pane:\d+)/);
|
|
32
|
+
if (paneMatch) currentPane = paneMatch[1];
|
|
33
|
+
if (line.includes(surface) && currentPane) return currentPane;
|
|
34
|
+
}
|
|
35
|
+
} catch {}
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// --- initializeConductorSlots ---
|
|
40
|
+
|
|
41
|
+
export async function initializeConductorSlots(
|
|
42
|
+
projectRoot: string,
|
|
43
|
+
count: number = 3,
|
|
44
|
+
daemonSurface?: string
|
|
45
|
+
): Promise<ConductorState[]> {
|
|
46
|
+
const slots: ConductorState[] = [];
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
// 1. daemon を右に split → Conductor-1
|
|
50
|
+
const surface1 = await cmux.newSplit("right", daemonSurface ? { surface: daemonSurface } : undefined);
|
|
51
|
+
await log("conductor_slot_created", `slot=1 surface=${surface1}`);
|
|
52
|
+
|
|
53
|
+
// 2. daemon を下に split → Conductor-2
|
|
54
|
+
const surface2 = await cmux.newSplit("down", daemonSurface ? { surface: daemonSurface } : undefined);
|
|
55
|
+
await log("conductor_slot_created", `slot=2 surface=${surface2}`);
|
|
56
|
+
|
|
57
|
+
// 3. Conductor-1 を下に split → Conductor-3
|
|
58
|
+
const surface3 = await cmux.newSplit("down", { surface: surface1 });
|
|
59
|
+
await log("conductor_slot_created", `slot=3 surface=${surface3}`);
|
|
60
|
+
|
|
61
|
+
const surfaces = [surface1, surface2, surface3].slice(0, count);
|
|
62
|
+
|
|
63
|
+
// ロールプロンプトファイル生成(全スロットで共有)
|
|
64
|
+
const rolePromptFile = await generateConductorRolePrompt(projectRoot);
|
|
65
|
+
|
|
66
|
+
for (let i = 0; i < surfaces.length; i++) {
|
|
67
|
+
const surface = surfaces[i]!;
|
|
68
|
+
const slotId = `conductor-slot-${i + 1}`;
|
|
69
|
+
|
|
70
|
+
// 環境変数を export してから Claude を起動(子プロセスに自動継承させる)
|
|
71
|
+
const exports: string[] = [`export PROJECT_ROOT=${projectRoot}`];
|
|
72
|
+
// ANTHROPIC_BASE_URL は Claude Max 認証を無効化するため設定しない
|
|
73
|
+
|
|
74
|
+
await cmux.send(
|
|
75
|
+
surface,
|
|
76
|
+
`${exports.join(" && ")} && claude --dangerously-skip-permissions --append-system-prompt-file ${rolePromptFile} 'あなたは Conductor スロットです。Manager が /clear + プロンプト送信でタスクを割り当てるまで、何もせず ❯ プロンプトで待機してください。タスクの検索・読み取り・実行は一切行わないこと。'\n`
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
// Trust 承認
|
|
80
|
+
await cmux.waitForTrust(surface);
|
|
81
|
+
|
|
82
|
+
// タブ名設定
|
|
83
|
+
const num = surface.replace("surface:", "");
|
|
84
|
+
await cmux.renameTab(surface, `[${num}] ♦ idle`);
|
|
85
|
+
|
|
86
|
+
// paneId 取得
|
|
87
|
+
const paneId = await getPaneIdForSurface(surface);
|
|
88
|
+
|
|
89
|
+
const state: ConductorState = {
|
|
90
|
+
conductorId: slotId,
|
|
91
|
+
surface,
|
|
92
|
+
startedAt: new Date().toISOString(),
|
|
93
|
+
agents: [],
|
|
94
|
+
doneCandidate: false,
|
|
95
|
+
status: "idle",
|
|
96
|
+
paneId,
|
|
97
|
+
};
|
|
98
|
+
slots.push(state);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
await log("conductor_slots_initialized", `count=${slots.length}`);
|
|
102
|
+
} catch (e: any) {
|
|
103
|
+
await log("error", `initializeConductorSlots failed: ${e.message}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return slots;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// --- assignTask ---
|
|
110
|
+
|
|
111
|
+
export async function assignTask(
|
|
112
|
+
conductor: ConductorState,
|
|
113
|
+
taskId: string,
|
|
114
|
+
projectRoot: string
|
|
115
|
+
): Promise<ConductorState | null> {
|
|
116
|
+
try {
|
|
117
|
+
const taskRunId = `run-${Math.floor(Date.now() / 1000)}`;
|
|
118
|
+
|
|
119
|
+
// --- 1. タスクファイル検索 ---
|
|
120
|
+
const tasksDir = join(projectRoot, ".team/tasks");
|
|
121
|
+
const files = await readdir(tasksDir);
|
|
122
|
+
const taskFile = files.find((f) => {
|
|
123
|
+
const id = f.match(/^0*(\d+)/)?.[1];
|
|
124
|
+
return id === taskId || id === taskId.replace(/^0+/, "");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
if (!taskFile) {
|
|
128
|
+
await log("error", `Task file not found for ID=${taskId}`);
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const taskContent = await readFile(join(tasksDir, taskFile), "utf-8");
|
|
133
|
+
const taskTitle = taskContent.match(/^title:\s*(.+)/m)?.[1]?.trim() || taskFile.replace(/^\d+-/, "").replace(/\.md$/, "");
|
|
134
|
+
|
|
135
|
+
// --- 2. git worktree 作成 ---
|
|
136
|
+
const worktreePath = join(projectRoot, ".worktrees", taskRunId);
|
|
137
|
+
const branch = `${taskRunId}/task`;
|
|
138
|
+
|
|
139
|
+
await execFile("git", ["worktree", "add", worktreePath, "-b", branch], {
|
|
140
|
+
cwd: projectRoot,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// worktree ブートストラップ
|
|
144
|
+
if (existsSync(join(worktreePath, "package.json"))) {
|
|
145
|
+
await execFile("npm", ["install"], { cwd: worktreePath }).catch(() => {});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// --- 3. Conductor プロンプト生成 ---
|
|
149
|
+
const outputDir = `.team/output/${taskRunId}`;
|
|
150
|
+
await mkdir(join(projectRoot, outputDir), { recursive: true });
|
|
151
|
+
|
|
152
|
+
const promptFile = await generateConductorTaskPrompt(
|
|
153
|
+
projectRoot,
|
|
154
|
+
taskRunId,
|
|
155
|
+
taskId,
|
|
156
|
+
taskContent,
|
|
157
|
+
worktreePath,
|
|
158
|
+
outputDir
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
// --- 4. 既存セッションをリセットして新プロンプトを送信 ---
|
|
162
|
+
// /clear + Enter でセッションリセット
|
|
163
|
+
await cmux.send(conductor.surface, "/clear");
|
|
164
|
+
await sleep(500);
|
|
165
|
+
await cmux.sendKey(conductor.surface, "return");
|
|
166
|
+
await sleep(2000);
|
|
167
|
+
|
|
168
|
+
// 新しいプロンプトを送信
|
|
169
|
+
await cmux.send(
|
|
170
|
+
conductor.surface,
|
|
171
|
+
`${promptFile} を読んで指示に従って作業してください。`
|
|
172
|
+
);
|
|
173
|
+
await sleep(500);
|
|
174
|
+
await cmux.sendKey(conductor.surface, "return");
|
|
175
|
+
|
|
176
|
+
// --- 5. タブ名更新 ---
|
|
177
|
+
const num = conductor.surface.replace("surface:", "");
|
|
178
|
+
const shortTitle = taskTitle.length > 30 ? taskTitle.slice(0, 30) + "…" : taskTitle;
|
|
179
|
+
await cmux.renameTab(conductor.surface, `[${num}] ♦ #${taskId} ${shortTitle}`);
|
|
180
|
+
|
|
181
|
+
// --- 6. ConductorState 更新 ---
|
|
182
|
+
conductor.taskRunId = taskRunId;
|
|
183
|
+
conductor.taskId = taskId;
|
|
184
|
+
conductor.taskTitle = taskTitle;
|
|
185
|
+
conductor.worktreePath = worktreePath;
|
|
186
|
+
conductor.outputDir = outputDir;
|
|
187
|
+
conductor.startedAt = new Date().toISOString();
|
|
188
|
+
conductor.agents = [];
|
|
189
|
+
conductor.doneCandidate = false;
|
|
190
|
+
conductor.status = "running";
|
|
191
|
+
|
|
192
|
+
await log(
|
|
193
|
+
"conductor_started",
|
|
194
|
+
`task_id=${taskId} conductor_id=${conductor.conductorId} task_run_id=${taskRunId} surface=${conductor.surface} title=${taskTitle}`
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
return conductor;
|
|
198
|
+
} catch (e: any) {
|
|
199
|
+
await log("error", `assignTask failed for task ${taskId}: ${e.message}`);
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// --- resetConductor ---
|
|
205
|
+
|
|
206
|
+
export async function resetConductor(
|
|
207
|
+
conductor: ConductorState,
|
|
208
|
+
projectRoot: string
|
|
209
|
+
): Promise<void> {
|
|
210
|
+
try {
|
|
211
|
+
// 1. タブ内のサブ surface を閉じる
|
|
212
|
+
if (conductor.paneId) {
|
|
213
|
+
try {
|
|
214
|
+
const surfaces = await cmux.listPaneSurfaces(conductor.paneId);
|
|
215
|
+
for (const s of surfaces) {
|
|
216
|
+
if (s !== conductor.surface) {
|
|
217
|
+
await cmux.closeSurface(s);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
} catch {}
|
|
221
|
+
} else {
|
|
222
|
+
// paneId なし → agents の surface を個別に閉じる
|
|
223
|
+
for (const agent of conductor.agents) {
|
|
224
|
+
await cmux.closeSurface(agent.surface);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// 2. worktree 削除
|
|
229
|
+
if (conductor.worktreePath && existsSync(conductor.worktreePath)) {
|
|
230
|
+
try {
|
|
231
|
+
await execFile("git", ["worktree", "remove", conductor.worktreePath, "--force"], {
|
|
232
|
+
cwd: projectRoot,
|
|
233
|
+
});
|
|
234
|
+
} catch {}
|
|
235
|
+
// ブランチ削除
|
|
236
|
+
if (conductor.taskRunId) {
|
|
237
|
+
const branch = `${conductor.taskRunId}/task`;
|
|
238
|
+
try {
|
|
239
|
+
await execFile("git", ["branch", "-d", branch], { cwd: projectRoot });
|
|
240
|
+
} catch {}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// 3. タブ名をリセット
|
|
245
|
+
const num = conductor.surface.replace("surface:", "");
|
|
246
|
+
await cmux.renameTab(conductor.surface, `[${num}] ♦ idle`);
|
|
247
|
+
|
|
248
|
+
// 4. ConductorState リセット
|
|
249
|
+
conductor.status = "idle";
|
|
250
|
+
conductor.taskRunId = undefined;
|
|
251
|
+
conductor.taskId = undefined;
|
|
252
|
+
conductor.taskTitle = undefined;
|
|
253
|
+
conductor.worktreePath = undefined;
|
|
254
|
+
conductor.outputDir = undefined;
|
|
255
|
+
conductor.agents = [];
|
|
256
|
+
conductor.doneCandidate = false;
|
|
257
|
+
|
|
258
|
+
await log("conductor_reset", `conductor_id=${conductor.conductorId} surface=${conductor.surface}`);
|
|
259
|
+
} catch (e: any) {
|
|
260
|
+
await log("error", `resetConductor failed: ${e.message}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// --- checkConductorStatus ---
|
|
265
|
+
|
|
266
|
+
export async function checkConductorStatus(
|
|
267
|
+
conductor: ConductorState
|
|
268
|
+
): Promise<"idle" | "running" | "done" | "crashed"> {
|
|
269
|
+
if (conductor.status === "idle") return "idle";
|
|
270
|
+
|
|
271
|
+
// done マーカーファイルのみで完了判定(画面判定はしない)
|
|
272
|
+
if (conductor.outputDir && existsSync(join(conductor.outputDir, "done"))) {
|
|
273
|
+
return "done";
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// surface 消失 → クラッシュ
|
|
277
|
+
if (!(await cmux.validateSurface(conductor.surface))) return "crashed";
|
|
278
|
+
|
|
279
|
+
return "running";
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// --- collectResults ---
|
|
283
|
+
|
|
284
|
+
export async function collectResults(
|
|
285
|
+
conductor: ConductorState,
|
|
286
|
+
projectRoot: string
|
|
287
|
+
): Promise<{ journalSummary?: string }> {
|
|
288
|
+
const result: { journalSummary?: string } = {};
|
|
289
|
+
|
|
290
|
+
// Journal サマリーを task-state.json から読み取る
|
|
291
|
+
try {
|
|
292
|
+
if (conductor.taskId) {
|
|
293
|
+
const taskState = await loadTaskState(projectRoot);
|
|
294
|
+
const state = taskState[conductor.taskId];
|
|
295
|
+
if (state?.journal) {
|
|
296
|
+
result.journalSummary = state.journal;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
} catch {}
|
|
300
|
+
|
|
301
|
+
return result;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// --- spawnConductor(後方互換ラッパー)---
|
|
305
|
+
|
|
306
|
+
export async function spawnConductor(
|
|
307
|
+
taskId: string,
|
|
308
|
+
projectRoot: string
|
|
309
|
+
): Promise<ConductorState | null> {
|
|
310
|
+
// 新しい idle Conductor を作成してタスクを割り当てる(フォールバック)
|
|
311
|
+
try {
|
|
312
|
+
const surface = await cmux.newSplit("down");
|
|
313
|
+
|
|
314
|
+
if (!(await cmux.validateSurface(surface))) {
|
|
315
|
+
await log("error", `spawnConductor: surface ${surface} validation failed`);
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const paneId = await getPaneIdForSurface(surface);
|
|
320
|
+
const conductor: ConductorState = {
|
|
321
|
+
conductorId: `conductor-fallback-${Math.floor(Date.now() / 1000)}`,
|
|
322
|
+
surface,
|
|
323
|
+
startedAt: new Date().toISOString(),
|
|
324
|
+
agents: [],
|
|
325
|
+
doneCandidate: false,
|
|
326
|
+
status: "idle",
|
|
327
|
+
paneId,
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
// ロールプロンプトファイル生成
|
|
331
|
+
const rolePromptFile = await generateConductorRolePrompt(projectRoot);
|
|
332
|
+
|
|
333
|
+
// 環境変数を export してから Claude を起動(子プロセスに自動継承させる)
|
|
334
|
+
const exports: string[] = [`export PROJECT_ROOT=${projectRoot}`];
|
|
335
|
+
// ANTHROPIC_BASE_URL は Claude Max 認証を無効化するため設定しない
|
|
336
|
+
await cmux.send(
|
|
337
|
+
surface,
|
|
338
|
+
`${exports.join(" && ")} && claude --dangerously-skip-permissions --append-system-prompt-file ${rolePromptFile} 'あなたは Conductor スロットです。Manager が /clear + プロンプト送信でタスクを割り当てるまで、何もせず ❯ プロンプトで待機してください。タスクの検索・読み取り・実行は一切行わないこと。'\n`
|
|
339
|
+
);
|
|
340
|
+
await cmux.waitForTrust(surface);
|
|
341
|
+
|
|
342
|
+
return await assignTask(conductor, taskId, projectRoot);
|
|
343
|
+
} catch (e: any) {
|
|
344
|
+
await log("error", `spawnConductor failed for task ${taskId}: ${e.message}`);
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
}
|