@minimalcorp/tsunagi 0.1.2 → 0.1.4

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 (77) hide show
  1. package/.next/standalone/apps/web/.next/BUILD_ID +1 -1
  2. package/.next/standalone/apps/web/.next/app-path-routes-manifest.json +1 -0
  3. package/.next/standalone/apps/web/.next/build-manifest.json +3 -3
  4. package/.next/standalone/apps/web/.next/prerender-manifest.json +3 -3
  5. package/.next/standalone/apps/web/.next/routes-manifest.json +6 -0
  6. package/.next/standalone/apps/web/.next/server/app/_global-error.html +1 -1
  7. package/.next/standalone/apps/web/.next/server/app/_global-error.rsc +1 -1
  8. package/.next/standalone/apps/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  9. package/.next/standalone/apps/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  10. package/.next/standalone/apps/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  11. package/.next/standalone/apps/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  12. package/.next/standalone/apps/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/.next/standalone/apps/web/.next/server/app/_not-found.html +1 -1
  14. package/.next/standalone/apps/web/.next/server/app/_not-found.rsc +1 -1
  15. package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  16. package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  17. package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  18. package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  19. package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  20. package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  21. package/.next/standalone/apps/web/.next/server/app/health/route/app-paths-manifest.json +3 -0
  22. package/.next/standalone/apps/web/.next/server/app/health/route/build-manifest.json +9 -0
  23. package/.next/standalone/apps/web/.next/server/app/health/route/server-reference-manifest.json +4 -0
  24. package/.next/standalone/apps/web/.next/server/app/health/route.js +6 -0
  25. package/.next/standalone/apps/web/.next/server/app/health/route.js.map +5 -0
  26. package/.next/standalone/apps/web/.next/server/app/health/route.js.nft.json +1 -0
  27. package/.next/standalone/apps/web/.next/server/app/health/route_client-reference-manifest.js +3 -0
  28. package/.next/standalone/apps/web/.next/server/app/icon.png/route.js +2 -1
  29. package/.next/standalone/apps/web/.next/server/app/icon.png/route.js.nft.json +1 -1
  30. package/.next/standalone/apps/web/.next/server/app/index.html +1 -1
  31. package/.next/standalone/apps/web/.next/server/app/index.rsc +2 -2
  32. package/.next/standalone/apps/web/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  33. package/.next/standalone/apps/web/.next/server/app/index.segments/_full.segment.rsc +2 -2
  34. package/.next/standalone/apps/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  35. package/.next/standalone/apps/web/.next/server/app/index.segments/_index.segment.rsc +1 -1
  36. package/.next/standalone/apps/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  37. package/.next/standalone/apps/web/.next/server/app/page_client-reference-manifest.js +1 -1
  38. package/.next/standalone/apps/web/.next/server/app/settings.html +1 -1
  39. package/.next/standalone/apps/web/.next/server/app/settings.rsc +1 -1
  40. package/.next/standalone/apps/web/.next/server/app/settings.segments/_full.segment.rsc +1 -1
  41. package/.next/standalone/apps/web/.next/server/app/settings.segments/_head.segment.rsc +1 -1
  42. package/.next/standalone/apps/web/.next/server/app/settings.segments/_index.segment.rsc +1 -1
  43. package/.next/standalone/apps/web/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
  44. package/.next/standalone/apps/web/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +1 -1
  45. package/.next/standalone/apps/web/.next/server/app/settings.segments/settings.segment.rsc +1 -1
  46. package/.next/standalone/apps/web/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
  47. package/.next/standalone/apps/web/.next/server/app-paths-manifest.json +1 -0
  48. package/.next/standalone/apps/web/.next/server/chunks/[root-of-the-server]__03yk.z3._.js +3 -0
  49. package/.next/standalone/apps/web/.next/server/chunks/[root-of-the-server]__0op9af~._.js +3 -0
  50. package/.next/standalone/apps/web/.next/server/chunks/apps_web__next-internal_server_app_health_route_actions_08pjq78.js +3 -0
  51. package/.next/standalone/apps/web/.next/server/chunks/node_modules_next_06p.d_a._.js +13 -0
  52. package/.next/standalone/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0qw6wy3._.js +2 -2
  53. package/.next/standalone/apps/web/.next/server/chunks/ssr/[root-of-the-server]__12yece_._.js +2 -2
  54. package/.next/standalone/apps/web/.next/server/chunks/ssr/_0g.cd6a._.js +1 -1
  55. package/.next/standalone/apps/web/.next/server/middleware-build-manifest.js +3 -3
  56. package/.next/standalone/apps/web/.next/server/pages/404.html +1 -1
  57. package/.next/standalone/apps/web/.next/server/pages/500.html +1 -1
  58. package/.next/standalone/apps/web/.next/server/server-reference-manifest.js +1 -1
  59. package/.next/standalone/apps/web/.next/server/server-reference-manifest.json +1 -1
  60. package/.next/standalone/apps/web/.next/static/chunks/{0l0s3fn6xga.t.js → 0bulq7i0gcc02.js} +2 -2
  61. package/.next/standalone/apps/web/.next/static/chunks/{0ew.-lnxoet~x.js → 0lvd32c5czli~.js} +2 -2
  62. package/.next/standalone/apps/web/.next/static/chunks/{0hx_sdyz0yyfb.js → 0x7qsr~cuotr_.js} +1 -1
  63. package/dist/auto-migrate.js +6 -7
  64. package/dist/cli.js +120 -10
  65. package/dist/plugin-lifecycle.d.ts +4 -1
  66. package/dist/plugin-lifecycle.js +11 -4
  67. package/dist/server/index.js +6 -1
  68. package/dist/server/routes/hooks.js +24 -12
  69. package/dist/server/routes/terminal.js +3 -2
  70. package/package.json +2 -1
  71. package/prisma/migrations/20260410000000_clear_stale_tab_todos/migration.sql +4 -0
  72. package/scripts/monaco-editor.sh +59 -0
  73. package/tsunagi-marketplace/plugins/tsunagi-plugin/skills/tsunagi-task/SKILL.md +21 -0
  74. package/.next/standalone/apps/web/.next/server/chunks/[root-of-the-server]__0jam776._.js +0 -13
  75. /package/.next/standalone/apps/web/.next/static/{QhaROxwd-I-6St9I2Yhbg → WKHKhVSNPtIYKtVKANGvu}/_buildManifest.js +0 -0
  76. /package/.next/standalone/apps/web/.next/static/{QhaROxwd-I-6St9I2Yhbg → WKHKhVSNPtIYKtVKANGvu}/_clientMiddlewareManifest.js +0 -0
  77. /package/.next/standalone/apps/web/.next/static/{QhaROxwd-I-6St9I2Yhbg → WKHKhVSNPtIYKtVKANGvu}/_ssgManifest.js +0 -0
@@ -1,26 +1,31 @@
1
1
  import { prisma } from '../lib/db.js';
2
2
  export const hookEvents = [];
3
3
  const sessionTasks = new Map();
4
- /** TaskEntryリストをTodo形式に変換 */
4
+ /**
5
+ * TaskEntryリストをTodo形式に変換。
6
+ * 'deleted' を含めて全 status を pass-through し、表示時のフィルタは frontend 側に任せる。
7
+ */
5
8
  function tasksToTodos(tasks) {
6
9
  return Array.from(tasks.values()).map((t) => ({
7
10
  content: t.subject,
8
- status: t.status === 'in_progress' ? 'in_progress' : t.status,
11
+ status: t.status,
9
12
  }));
10
13
  }
11
14
  /** DBでタブのステータス(+ todos)を直接更新する */
12
15
  async function updateTabStatus(sessionId, status, todos) {
13
16
  try {
14
- await prisma.tab.updateMany({
17
+ const result = await prisma.tab.updateMany({
15
18
  where: { tabId: sessionId },
16
19
  data: {
17
20
  status,
18
21
  ...(todos !== undefined && { todos: JSON.stringify(todos) }),
19
22
  },
20
23
  });
24
+ return { count: result.count };
21
25
  }
22
26
  catch (err) {
23
27
  console.warn(`[hooks] Failed to update tab status in DB:`, err);
28
+ return { count: 0 };
24
29
  }
25
30
  }
26
31
  export async function hooksRoutes(fastify) {
@@ -64,7 +69,9 @@ export async function hooksRoutes(fastify) {
64
69
  if (body.tool_name === 'TodoWrite' && body.tool_input) {
65
70
  const todos = body.tool_input.todos;
66
71
  if (Array.isArray(todos)) {
67
- await updateTabStatus(sessionId, 'running', todos);
72
+ fastify.log.info({ sessionId, todosCount: todos.length }, '[hooks] TodoWrite received');
73
+ const { count } = await updateTabStatus(sessionId, 'running', todos);
74
+ fastify.log.info({ sessionId, updatedTabCount: count }, '[hooks] TodoWrite tab updated');
68
75
  io.to(room).emit('todos-updated', { sessionId, todos });
69
76
  todosUpdated = true;
70
77
  }
@@ -87,16 +94,19 @@ export async function hooksRoutes(fastify) {
87
94
  todosUpdated = true;
88
95
  }
89
96
  }
90
- // TaskUpdate: タスクのステータスを更新
97
+ // TaskUpdate: タスクのステータスを更新('deleted' 含む。Mapからは除去せず保持し、表示層で除外)
91
98
  if (body.tool_name === 'TaskUpdate' && body.tool_response && body.tool_input) {
92
99
  const taskId = body.tool_input.taskId;
93
- const statusChange = body.tool_response.statusChange;
100
+ // tool_input.status を優先(直接入力なので確実)、フォールバックで tool_response.statusChange.to
101
+ const newStatus = body.tool_input.status ??
102
+ body.tool_response.statusChange?.to;
94
103
  const tasks = sessionTasks.get(sessionId);
95
- if (tasks && taskId && statusChange?.to) {
104
+ if (tasks && taskId && newStatus) {
96
105
  const entry = tasks.get(taskId);
97
106
  if (entry) {
98
- entry.status = statusChange.to;
107
+ entry.status = newStatus;
99
108
  const todos = tasksToTodos(tasks);
109
+ fastify.log.info({ sessionId, taskId, newStatus, mapSize: tasks.size }, '[hooks] TaskUpdate processed');
100
110
  await updateTabStatus(sessionId, 'running', todos);
101
111
  io.to(room).emit('todos-updated', { sessionId, todos });
102
112
  todosUpdated = true;
@@ -118,16 +128,18 @@ export async function hooksRoutes(fastify) {
118
128
  io.to(room).emit('status-changed', { sessionId, status: 'success' });
119
129
  break;
120
130
  case 'StopFailure':
121
- // APIエラーでターン終了 → failure状態に更新
122
- await updateTabStatus(sessionId, 'error');
131
+ // APIエラーでターン終了 → failure状態に更新、todosをクリア
132
+ await updateTabStatus(sessionId, 'error', []);
123
133
  io.to(room).emit('status-changed', { sessionId, status: 'failure' });
134
+ io.to(room).emit('todos-updated', { sessionId, todos: [] });
124
135
  break;
125
136
  case 'SessionEnd':
126
- // セッション終了(Escキー中断・Ctrl+C・/exit等)→ idle状態に更新
137
+ // セッション終了(Escキー中断・Ctrl+C・/exit等)→ idle状態に更新、todosをクリア
127
138
  // reason: "prompt_input_exit" = ユーザー中断、"other" = プロセスkill等
128
139
  fastify.log.info({ sessionId, reason: body.reason }, 'SessionEnd received');
129
- await updateTabStatus(sessionId, 'idle');
140
+ await updateTabStatus(sessionId, 'idle', []);
130
141
  io.to(room).emit('status-changed', { sessionId, status: 'idle' });
142
+ io.to(room).emit('todos-updated', { sessionId, todos: [] });
131
143
  break;
132
144
  default:
133
145
  fastify.log.debug({ eventName }, 'Unhandled hook event');
@@ -48,12 +48,13 @@ export async function terminalRoutes(fastify) {
48
48
  // PTYプロセス終了 → room全体に通知(全接続クライアントに終了を伝える)+ セッション削除
49
49
  const exitHandler = ptyProcess.onExit(({ exitCode }) => {
50
50
  io.to(room).emit('exit', { exitCode });
51
- // Ctrl+C等でClaudeが強制終了した場合にclaudeStatusをidleにリセット
51
+ // Ctrl+C等でClaudeが強制終了した場合にclaudeStatusをidleにリセット、todosをクリア
52
52
  io.to(room).emit('status-changed', { sessionId, status: 'idle' });
53
+ io.to(room).emit('todos-updated', { sessionId, todos: [] });
53
54
  prisma.tab
54
55
  .updateMany({
55
56
  where: { tabId: sessionId },
56
- data: { status: 'idle' },
57
+ data: { status: 'idle', todos: '[]' },
57
58
  })
58
59
  .catch(() => {
59
60
  /* DB更新失敗は無視 */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minimalcorp/tsunagi",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Multi-repo GitHub project management with Claude AI integration, designed for visualizing and controlling AI-driven development locally.",
5
5
  "keywords": [
6
6
  "tsunagi",
@@ -41,6 +41,7 @@
41
41
  "prisma.config.ts",
42
42
  ".next/standalone/**/*",
43
43
  "scripts/fix-node-pty-permissions.cjs",
44
+ "scripts/monaco-editor.sh",
44
45
  "tsunagi-marketplace/**/*",
45
46
  "LICENSE",
46
47
  "README.md"
@@ -0,0 +1,4 @@
1
+ -- session 終了時の todos クリア漏れで蓄積した stale データを一括リセット
2
+ -- idle/error 状態のタブは次回セッション開始まで参照されないため安全にクリアできる
3
+ -- success 状態(Stop 正常完了)のタブは累積プログレス表示のため意図的に残す
4
+ UPDATE "tabs" SET todos = '[]' WHERE status IN ('idle', 'error');
@@ -0,0 +1,59 @@
1
+ #!/bin/sh
2
+ # monaco-editor.sh
3
+ # $EDITOR として設定することで、Claude Code の Ctrl+G でブラウザ上の
4
+ # Monaco Editor Modal を開いてプロンプトを編集できる。
5
+ #
6
+ # セットアップ:
7
+ # export EDITOR="/path/to/scripts/monaco-editor.sh"
8
+ #
9
+ # 依存: curl のみ
10
+
11
+ set -e
12
+
13
+ TMPFILE="$1"
14
+ API_BASE="http://localhost:2792"
15
+
16
+ if [ -z "$TMPFILE" ]; then
17
+ echo "Usage: monaco-editor.sh <tmpfile>" >&2
18
+ exit 1
19
+ fi
20
+
21
+ # 代替画面 (alt screen) に切り替えて script の出力を claude の描画領域から隔離する。
22
+ # vim/nano 等の通常の $EDITOR と同じ作法。これをやらないと script の stderr 出力が
23
+ # main buffer に積まれて、claude の Ink renderer の位置情報と実画面がズレ、
24
+ # $EDITOR 終了後の描画に余白が生じる。
25
+ printf '\033[?1049h'
26
+ # どんな経路で終了しても alt screen から確実に復帰させる
27
+ trap 'printf "\033[?1049l"' EXIT
28
+
29
+ # エラー報告用: alt screen から抜けてから stderr に出す
30
+ _error_exit() {
31
+ printf '\033[?1049l'
32
+ trap - EXIT
33
+ echo "Error: $1" >&2
34
+ exit 1
35
+ }
36
+
37
+ # ファイルパスを送信。サーバー側がファイルの読み書きを行う。
38
+ # TSUNAGI_SESSION_ID はPTY作成時に設定される環境変数(= tab_id)
39
+ # レスポンスはプレーンテキストの sessionId のみ(JSON パース不要)
40
+ SESSION_ID=$(curl -sf -X POST "$API_BASE/api/editor/session" \
41
+ -H "Content-Type: application/json" \
42
+ -d "{\"filePath\": \"$TMPFILE\", \"tabId\": \"$TSUNAGI_SESSION_ID\"}")
43
+
44
+ if [ -z "$SESSION_ID" ]; then
45
+ _error_exit "Failed to create editor session (is tsunagi running? check monaco-editor.sh setup)"
46
+ fi
47
+
48
+ # 完了までポーリング(無制限、0.1秒間隔。Ctrl+C で中断可能)
49
+ # 速めの polling 間隔にしているのは、Cmd+Enter Submit 後に sh が foreground PG から
50
+ # 抜けるまでの遅延を最小化するため(TerminalView 側の resize nudge が claude に
51
+ # 届くように)。
52
+ # レスポンスはプレーンテキスト "done" or "pending"(JSON パース不要)
53
+ while true; do
54
+ STATUS=$(curl -sf "$API_BASE/api/editor/session/$SESSION_ID" 2>/dev/null || echo "pending")
55
+ if [ "$STATUS" = "done" ]; then
56
+ exit 0
57
+ fi
58
+ sleep 0.1
59
+ done
@@ -17,6 +17,27 @@ description: プロジェクトのタスク管理ツール。タスクの検索
17
17
 
18
18
  遷移は必ず順方向: backlog → planning → coding → reviewing → done
19
19
 
20
+ ## 実装開始時の TODO 作成(必須)
21
+
22
+ `coding` に遷移したら、**即座に** Claude Code 組み込みの `TodoWrite` ツールで実装ステップを TODO として登録する。これにより tsunagi UI にプログレスバーが表示される。
23
+
24
+ ### 手順
25
+
26
+ 1. `tsunagi_update_task(status: "coding")` の直後に `TodoWrite` を呼ぶ
27
+ 2. 各 TODO は完了判断が明確な作業単位にする
28
+ - 例: 「○○ファイルを修正」「マイグレーション作成・適用」「lint/type-check を通す」「動作確認」
29
+ 3. ステップ完了ごとに `TodoWrite` で該当項目の status を `in_progress` → `completed` に更新する
30
+ 4. 作業中に追加ステップが判明した場合、既存 TODO に追記して `TodoWrite` で再登録する
31
+
32
+ ### 語彙の使い分け(混同注意)
33
+
34
+ | 対象 | 使うツール | status 語彙 |
35
+ | --------------- | ---------------------- | -------------------------------------- |
36
+ | tsunagi タスク | `tsunagi_update_task` | backlog/planning/coding/reviewing/done |
37
+ | tab TODO (進捗) | `TodoWrite` (組み込み) | pending/in_progress/completed |
38
+
39
+ tsunagi タスクの status に `in_progress` を渡してはいけない(「よくある間違い」表参照)。逆に TODO の進捗管理は組み込み `TodoWrite` で行う。
40
+
20
41
  ## 現在のタスクを特定する
21
42
 
22
43
  タスク操作には `id`, `session_id`, `cwd` のいずれかでタスクを指定する。