peertable 0.3.2 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.ja.md CHANGED
@@ -79,7 +79,9 @@ claude --mcp-config .team/mcp.json \
79
79
 
80
80
  > 円卓を立てて
81
81
 
82
- 聞き取り・命名・`.team/` の scaffold(プロジェクト本体を汚さない)・Lattice plan 投入(単独モードなら読み取り専用の `.team/tasks.md` 生成)・メンバー起動・親の着卓まで一続き。teardown で diff ゼロに戻る。
82
+ 聞き取り・命名・`.team/` の scaffold(プロジェクト本体を汚さない)・Lattice plan 投入(単独モードなら読み取り専用の `.team/tasks.md` 生成)・メンバー起動・親の着卓まで一続き。
83
+
84
+ **teardown は既定で「解散」**——席を畳んでメンバー登録を外し、`.team/` と `.mcp.json` を撤去する。**部屋と過去ログは残る**(部屋は場所であり、次の卓も同じ部屋で続く。過去ログはその部屋の履歴として繋がる)。工程正本 `.lattice/` も残す。**痕跡ゼロに戻したいなら `--purge`**——部屋ごと削除してプロジェクトを diff ゼロへ返す(ゲストのプロジェクトで試した時はこちら)。
83
85
 
84
86
  ## 状態
85
87
 
package/README.md CHANGED
@@ -76,7 +76,7 @@ Standalone gives up machine-guaranteed scheduling across tasks — nothing else.
76
76
 
77
77
  ```
78
78
  room/ room server (zero-dependency Node) + per-session MCP channel client
79
- skill/ "peertable" skill for Claude Code: setup / teardown of a full table
79
+ skill/ "peertable" skill for Claude Code: setup / disband (teardown) of a full table
80
80
  docs/ plan.md — the living design document & decision log (Japanese)
81
81
  experiments/ verification harnesses (V1 channels, V2 Lattice concurrency, V3 full loop)
82
82
  ```
@@ -111,7 +111,7 @@ The `room` entry in `.team/mcp.json` is just `{ "command": "peertable-client" }`
111
111
 
112
112
  > 円卓を立てて / "set up a peertable for this project"
113
113
 
114
- It interviews you, names the members, scaffolds `.team/` (charter + roles, isolated from your project, `.git/info/exclude`d), seeds the Lattice plan — or writes the read-only `.team/tasks.md` agenda if you chose standalone — launches the member sessions, and seats itself beside the table. `teardown` restores your project to a zero diff.
114
+ It interviews you, names the members, scaffolds `.team/` (charter + roles, isolated from your project, `.git/info/exclude`d), seeds the Lattice plan — or writes the read-only `.team/tasks.md` agenda if you chose standalone — launches the member sessions, and seats itself beside the table. `teardown` disbands by default: it closes the seats, removes the member registrations, and clears `.team/` — **the room and its history stay** (a room is a place; the next table continues in the same room, so past logs read as that room's history), and the `.lattice/` plan store is kept. Pass `--purge` to delete the room too and restore your project to a zero diff.
115
115
 
116
116
  ## Status
117
117
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peertable",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "A round table of peer agents. No orchestrator at the head. Turn Claude Code sessions into a team of equal, long-lived peers.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/room/client.mjs CHANGED
@@ -10,7 +10,7 @@ import { fileURLToPath } from 'node:url'
10
10
 
11
11
  // client.mjs 側のハードコード版数。package.json の version と一致していることを
12
12
  // diagnostics の version_consistency が見る(2 つの版数源の drift 検出。決定45)
13
- const MCP_VERSION = '0.3.2'
13
+ const MCP_VERSION = '0.3.3'
14
14
  const PKG_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
15
15
 
16
16
  const USAGE = `usage:
@@ -222,6 +222,9 @@ async function runDiagnostics(asJson) {
222
222
  'scripts/make-plan-input.mjs',
223
223
  'scripts/parent-join.sh',
224
224
  'scripts/wakeup-bridge.mjs',
225
+ 'scripts/seat-status-bridge.mjs',
226
+ // teardown の archive(=解散・既定)が呼ぶ。欠けるとログの写しが取れない
227
+ 'scripts/archive-room-log.py',
225
228
  'templates/gen-plan.mjs',
226
229
  'templates/done.sh',
227
230
  'templates/charter.md',
package/skill/SKILL.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: peertable
3
- description: 任意プロジェクトに Peertable チーム(対等メンバー並列型のマルチエージェント作業システム)を導入・撤去する。setup でメンバーセッション群と room を立ち上げ、teardown diff ゼロに戻す。「チームで作業して」「円卓を立てて」「peertable setup / teardown」で使う。
3
+ description: 任意プロジェクトに Peertable チーム(対等メンバー並列型のマルチエージェント作業システム)を導入・解散する。setup でメンバーセッション群と room を立ち上げ、teardown で席と足場を撤去する(既定は解散——部屋と過去ログは残り、次の卓も同じ部屋で続く。痕跡ゼロにするなら --purge)。「チームで作業して」「円卓を立てて」「peertable setup / teardown」で使う。
4
4
  ---
5
5
 
6
6
  # Peertable — setup / teardown
@@ -57,7 +57,22 @@ description: 任意プロジェクトに Peertable チーム(対等メンバ
57
57
 
58
58
  ## teardown
59
59
 
60
- `scripts/teardown.sh <project>` が機械部分を行う(room 名・server URL・作成記録は `.team/setup-state.json` から読むので引数は project だけ。書込トークンは環境変数 `PEERTABLE_POST_TOKEN`): tmux セッション終了(先に殺す。`.team/` 消失後の参照事故防止)→ サーバー room 削除 → **外部ペインの復元**(`.lattice/project.json` を setup 前へ戻す。既存文書があったなら `.team/project.json.bak` から書き戻し、無かったなら削除する。`.team/` を消す前にやる——退避先がその中にある)→ `.team/` 削除 → `.git/info/exclude` の追記行を戻す → setup が作った `.lattice/` なら削除。実行後 `git status` で diff ゼロを確認して報告する。
60
+ `scripts/teardown.sh <project> [--purge]` が機械部分を**全部**行う(room 名・server URL・作成記録は `.team/setup-state.json` から読むので引数は project だけ。書込トークンは環境変数 `PEERTABLE_POST_TOKEN`)。**席の終了も本 script が行う**——AI が事前に `pty_close` して回る必要はない。
61
+
62
+ **既定は archive(=解散)、`--purge` が痕跡ゼロ**(決定61・オーナー裁定 2026-08-09)。**円卓の解散は「部屋を畳む」ではなく「集まりが散る」**——**部屋は場所であって、次の卓も同じ部屋で続く**。過去ログはその部屋の履歴としてそのまま残り、**部屋は常に一つに見える**。ゲスト project を汚さない不可侵原則は `--purge` が担う。
63
+
64
+ 段の順序(**前の段が後の段の前提**):
65
+ 1. **room ログの写し**(archive のみ)→ `docs/archive/room-log_<room>_<日時>.md`。**原本は room に残る**ので、これは repo 側の控え(失敗しても撤去は続行する)
66
+ 2. **席の終了** → **この room の member 一覧から `peer-<名前>` だけ**を畳む(`peer-*` を全部畳むと同じマシンの別の卓を巻き込む)。他卓の席が残っていれば注記だけ出す
67
+ 3. **ブリッジの停止**(起床・稼働状態)→ `.team/` を消す前(pid 記録がその中にある)
68
+ 4. **解散**(archive): **履歴へ解散の区切りを1行投稿してから、メンバー登録だけ外す**。**部屋も過去ログも消さない**——区切りが無いと、次の卓の発言が前の卓と地続きに読める/**`--purge`**: room ごと削除(トークンを要する唯一の段)
69
+ 5. **外部ペインの復元** → `.team/project.json.bak` が退避先なので `.team/` を消す前
70
+ 6. `.team/` 削除 → `.mcp.json` → `.git/info/exclude` の追記行を戻す
71
+ 7. **`.lattice/`**: archive では**残す**(`lattice todo status` と `gantt serve` が読む)。`--purge` かつ setup が作ったものなら削除。**残しても git 追跡外なら次の clone に残らない**ので、残すなら commit する(script は注記を出すだけ——他人の repo へ勝手に commit しない)
72
+
73
+ **次の卓を同じ部屋で立てる時は、setup の room 名を前と同じにする**。member は席が戻れば再登録され、履歴は続く。
74
+
75
+ 実行後は **`git status`(archive なら `docs/archive/` と `.lattice/` が増えているのが正・`--purge` なら diff ゼロ)**、**公開 UI に部屋と過去ログが残っていること(archive)** と、**`tmux -S <socket> list-sessions | grep peer-`** の残存ゼロを確認して報告する。
61
76
 
62
77
  各段は `[実施] / [スキップ] / [未実施]` を1行ずつ出す。**トークンを要するのは room 削除だけ**なので、そこが失敗しても残りの撤去は続行し、未実施を明示して非ゼロで終わる(黙って中断しない・決定58)。未実施が出ても**撤去そのものは済んでいる**。残りは表示された **[手当] の curl を手で叩く**だけで、`.team/` は既に消えているので **teardown.sh の再実行はできない**(2026-08-08 実測。再実行すると `setup-state.json` が読めず落ちる)。
63
78
 
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env python3
2
+ """room の会話ログを Markdown へ書き出す(teardown の archive モード用)。
3
+
4
+ usage:
5
+ archive-room-log.py <server_url> <room> <out.md> ログを書き出す
6
+ archive-room-log.py --members <server_url> <room> member 名を空白区切りで出す(席を畳む相手)
7
+
8
+ 本文は投稿時の Markdown をそのまま置く(引用ブロックで包むと表とコードが崩れる)。
9
+ 本文の無い発言(過去に本文なし POST が着地した分)は、欠落と分かる形で残す——
10
+ 消すと「そこに発言があった」ことまで消える。
11
+ """
12
+ import json
13
+ import sys
14
+ import urllib.request
15
+
16
+
17
+ def fetch(url, path):
18
+ with urllib.request.urlopen(f"{url.rstrip('/')}{path}", timeout=15) as r:
19
+ return json.load(r)
20
+
21
+
22
+ def main(argv):
23
+ if argv[:1] == ["--members"]:
24
+ url, room = argv[1], argv[2]
25
+ members = fetch(url, f"/api/{room}/members")["members"]
26
+ print(" ".join(m["name"] for m in members))
27
+ return 0
28
+
29
+ url, room, out = argv[0], argv[1], argv[2]
30
+ messages = fetch(url, f"/api/{room}/messages")["messages"]
31
+ lines = [
32
+ f"# 円卓ログ — room `{room}`(全{len(messages)}発言)",
33
+ "",
34
+ "teardown(archive モード)が書き出した正史。room サーバー側は削除済みなので、"
35
+ "この文書が唯一の記録である。",
36
+ "",
37
+ "---",
38
+ "",
39
+ ]
40
+ for m in messages:
41
+ body = m.get("body")
42
+ if body is None:
43
+ body = "(本文欠落——本文なし POST が着地した発言。欠落そのものを記録として残す)"
44
+ lines += [f"## [{m['seq']}] {m['from']} → {m.get('to', 'all')} ・ {m['ts']}", "", body, ""]
45
+ with open(out, "w", encoding="utf-8") as f:
46
+ f.write("\n".join(lines))
47
+ return 0
48
+
49
+
50
+ if __name__ == "__main__":
51
+ # 生の traceback を出さない。teardown の画面へ出るのは「何が起きたか」の1行であるべきで、
52
+ # stack trace は読む側に原因を伝えない(呼び出し側は非ゼロだけを見る)
53
+ try:
54
+ sys.exit(main(sys.argv[1:]))
55
+ except Exception as exc: # noqa: BLE001 — 入口なので型を絞らない
56
+ print(f"ARCHIVE_ROOM_LOG_FAILED: {type(exc).__name__}: {exc}", file=sys.stderr)
57
+ sys.exit(1)
@@ -1,12 +1,26 @@
1
1
  #!/bin/bash
2
- # Peertable teardown の機械部分。実行前にメンバーセッションを終了しておくこと(AI pty_close で行う)。
3
- # usage: teardown.sh <project_dir>
2
+ # Peertable teardown の機械部分。席(tmux)も本 script が畳む。
3
+ # usage: teardown.sh <project_dir> [--purge]
4
+ # 既定(archive): room のログを `docs/archive/` へ書き出し、`.lattice/` は残して畳む
5
+ # --purge : 何も残さず全部消す(従来の痕跡ゼロ・ゲスト project 向け)
4
6
  # 書込トークンは環境変数 PEERTABLE_POST_TOKEN から取る(`~/.config/peertable.env` は export 付きで定義すること)。
5
7
  #
6
8
  # 撤去は「何が実施され、何が実施されなかったか」を1行ずつ出す。room 削除だけがトークンを要するので、
7
9
  # そこが失敗しても残りの撤去は続行し、未実施を明示して非ゼロで終わる(黙って中断しない・決定58)。
10
+ #
11
+ # **既定が archive なのはオーナー裁定(2026-08-09・決定61)**。痕跡ゼロを既定にしていた時代は、
12
+ # 畳んだ瞬間に room の会話ログ(server 側の正本)と Lattice store が消えていた——**卓の議論と
13
+ # 工程の記録は、卓そのものより寿命が長い**。ゲスト project を汚さない不可侵原則は `--purge` が持つ。
8
14
  set -e
9
15
  proj="$1"
16
+ mode=archive
17
+ for arg in "${@:2}"; do
18
+ case "$arg" in
19
+ --purge) mode=purge ;;
20
+ --archive) mode=archive ;;
21
+ *) echo "ERROR: 未知の引数: ${arg}(受けるのは --purge / --archive だけ)" >&2; exit 1 ;;
22
+ esac
23
+ done
10
24
  state="$proj/.team/setup-state.json"
11
25
  room=$(python3 -c "import json;print(json.load(open('$state'))['room'])")
12
26
  url=$(python3 -c "import json;print(json.load(open('$state'))['server_url'])")
@@ -22,6 +36,42 @@ skip() { echo "teardown: [スキップ] $*"; }
22
36
  miss() { echo "teardown: [未実施] $*" >&2; fail=1; }
23
37
  yes_() { [ "$1" = "True" ] || [ "$1" = "true" ]; }
24
38
 
39
+ echo "teardown: mode=${mode}(archive=ログとstoreを残す/purge=痕跡ゼロ)"
40
+
41
+ # ---- ログの控え(archive だけ)。room 自体は残るので、これは repo 側の写し ----
42
+ # 失敗しても撤去は続ける(room に原本があるため)。--purge の時だけ「消す前の最後の機会」になる
43
+ log_saved=skip
44
+ if [ "$mode" = archive ]; then
45
+ arc="$proj/docs/archive"
46
+ out="$arc/room-log_${room}_$(date +%Y%m%d-%H%M%S).md"
47
+ mkdir -p "$arc"
48
+ if python3 "$(dirname "$0")/archive-room-log.py" "$url" "$room" "$out"; then
49
+ did "room ログの写しを ${out#"$proj/"} へ(原本は room に残る)"
50
+ log_saved=yes
51
+ else
52
+ miss "room ログの写しに失敗($url/api/$room が読めない)— **原本は room に残っている**ので撤去は続行する"
53
+ log_saved=no
54
+ fi
55
+ fi
56
+
57
+ # ---- 席(tmux)の終了。**`.team/` を消す前**に、この room の member だけを畳む ----
58
+ # `peer-*` を全部畳むと、同じマシンの別の卓を巻き込む(bridge が members 起点にしているのと同じ理由)
59
+ sock="${PEERTABLE_TMUX_SOCKET:-${TMPDIR}claude-tmux-sockets/claude.sock}"
60
+ seats=$(python3 "$(dirname "$0")/archive-room-log.py" --members "$url" "$room" 2>/dev/null || true)
61
+ if [ -z "$seats" ]; then
62
+ miss "席の終了 — member 一覧が取れず、畳む相手を特定できない。手で確認: tmux -S \"$sock\" list-sessions | grep peer-"
63
+ else
64
+ closed=0
65
+ for name in $seats; do
66
+ if tmux -S "$sock" has-session -t "peer-$name" 2>/dev/null; then
67
+ tmux -S "$sock" kill-session -t "peer-$name" && closed=$((closed + 1))
68
+ fi
69
+ done
70
+ if [ "$closed" -gt 0 ]; then did "席の終了(${closed}席)"; else skip "席の終了(生きている席なし)"; fi
71
+ left=$(tmux -S "$sock" list-sessions 2>/dev/null | grep -c '^peer-' || true)
72
+ [ "${left:-0}" -eq 0 ] || echo "teardown: [注記] 他の卓の peer-* が ${left}件 残っている(この卓のものではないので畳まない)"
73
+ fi
74
+
25
75
  # Codex 席の起床ブリッジ(決定54)。常駐 process なので、`.team/` を消す前に確実に止める
26
76
  if [ -f "$proj/.team/wakeup-bridge.json" ]; then
27
77
  # 停止に失敗しても **ここで止まらない**。`set -e` で落ちると、t6 の契約(各段の実施・未実施を
@@ -54,8 +104,38 @@ fi
54
104
  ext=$(python3 -c "import json;print(json.load(open('$state')).get('external_pane', False))")
55
105
  pj_pre=$(python3 -c "import json;print(json.load(open('$state')).get('project_json_preexisting', False))")
56
106
 
57
- # room 削除。トークンを要する唯一の段で、ここだけが外部サービスへの依存境界
58
- if [ -z "${PEERTABLE_POST_TOKEN:-}" ]; then
107
+ # ---- 解散(archive): **部屋は残し、メンバー登録だけ外す** ----
108
+ # 円卓の解散は「部屋を畳む」ではなく「集まりが散る」。部屋は場所であって、次の campaign
109
+ # 同じ部屋で続く——**過去ログが同じ部屋の履歴として繋がり、部屋は常に一つに見える**
110
+ # (オーナー裁定 2026-08-09・決定61)。参加者一覧だけ空にして、席が戻れば再登録される
111
+ if [ "$mode" = archive ]; then
112
+ if [ -z "${PEERTABLE_POST_TOKEN:-}" ]; then
113
+ miss "メンバー登録の解除 — TOKEN_MISSING: \`~/.config/peertable.env\` の定義が \`export\` 付きでないと子 process へ渡らない"
114
+ echo "teardown: [手当] 参加者は次で外せる: curl -X DELETE \"$url/api/$room/members/<名前>\" -H \"X-Peertable-Token: \$PEERTABLE_POST_TOKEN\"" >&2
115
+ elif [ -z "$seats" ]; then
116
+ skip "メンバー登録の解除(一覧が取れていない)"
117
+ else
118
+ # 履歴に解散の区切りを残す。**部屋が続く以上、どこで卓が変わったかが読めないと
119
+ # 過去ログが一続きの会話に見えてしまう**(次の campaign の発言と地続きになる)
120
+ body="解散。この卓はここまで。参加者: ${seats}。部屋と過去ログはこのまま残り、次の卓も同じ部屋で続く。"
121
+ python3 -c "
122
+ import json,sys,urllib.request
123
+ req=urllib.request.Request('$url/api/$room/messages', method='POST',
124
+ data=json.dumps({'from':'system','to':'all','body':'''$body'''}).encode(),
125
+ headers={'Content-Type':'application/json','X-Peertable-Token':'$PEERTABLE_POST_TOKEN'})
126
+ urllib.request.urlopen(req, timeout=10).read()
127
+ " 2>/dev/null && did "解散の区切りを履歴へ" || skip "解散の区切り(投稿できず・撤去は続行)"
128
+ n=0
129
+ for name in $seats; do
130
+ c=$(curl -s -o /dev/null -w '%{http_code}' -X DELETE "$url/api/$room/members/$name" -H "X-Peertable-Token: $PEERTABLE_POST_TOKEN" || true)
131
+ [ "$c" = 200 ] && n=$((n + 1))
132
+ done
133
+ did "メンバー登録の解除(${n}名)— **部屋と過去ログは残す**($url/$room)"
134
+ fi
135
+ # room 削除は --purge だけ。トークンを要する唯一の段で、ここだけが外部サービスへの依存境界
136
+ elif [ "$log_saved" = no ]; then
137
+ miss "room 削除 $room — ログを保全できていないので消さない(保全より先に消すと会話は二度と戻らない)"
138
+ elif [ -z "${PEERTABLE_POST_TOKEN:-}" ]; then
59
139
  miss "room 削除 $room — TOKEN_MISSING: PEERTABLE_POST_TOKEN が空。\`~/.config/peertable.env\` の定義が \`export\` 付きでないと子 process へ渡らない"
60
140
  else
61
141
  code=$(curl -s -o /dev/null -w '%{http_code}' -X DELETE "$url/api/$room" -H "X-Peertable-Token: $PEERTABLE_POST_TOKEN" || true)
@@ -107,11 +187,15 @@ else
107
187
  skip "exclude の .team/(setup が足していない)"
108
188
  fi
109
189
 
110
- if [ "$lat_pre" = "False" ] || [ "$lat_pre" = "false" ]; then
111
- rm -rf "$proj/.lattice"
112
- did ".lattice/ 削除(setup が作ったもの)"
113
- else
190
+ if [ "$lat_pre" = "True" ] || [ "$lat_pre" = "true" ]; then
114
191
  skip ".lattice/(setup 以前から存在)"
192
+ elif [ "$mode" = archive ]; then
193
+ # 工程正本を残すのが archive の本体。**残すだけでは git が知らない**ので、追跡へ入れるのは人の判断
194
+ did ".lattice/ を残す(工程正本・\`lattice todo status\` と \`gantt serve\` が読む)"
195
+ echo "teardown: [注記] .lattice/ は git 追跡外のままなら次の clone に残らない。残すなら commit すること"
196
+ else
197
+ rm -rf "$proj/.lattice"
198
+ did ".lattice/ 削除(setup が作ったもの・--purge)"
115
199
  fi
116
200
 
117
201
  if [ "$fail" -ne 0 ]; then