@cocorograph/hub-agent 0.8.39 → 0.8.40
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/package.json +1 -1
- package/src/claude-md-sync.mjs +87 -21
- package/src/tmux.mjs +8 -6
package/package.json
CHANGED
package/src/claude-md-sync.mjs
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Hub 正本 (Project.claude_md) と workspace ローカル CLAUDE.md
|
|
2
|
+
* Hub 正本 (Project.claude_md) と workspace ローカル CLAUDE.md の同期。
|
|
3
3
|
*
|
|
4
|
-
* 方針:
|
|
4
|
+
* 方針: **受け取り専用**(Hub → 端末の一方向。唯一の例外が下記「初回登録」)。
|
|
5
5
|
* - Hub 側更新 → 端末 pull(ローカルを `.bak.<ts>` 退避してから全文上書き)
|
|
6
|
-
* - 端末側更新 →
|
|
7
|
-
* -
|
|
8
|
-
*
|
|
6
|
+
* - 端末側更新 → **何もしない**(PUT せず、ローカルも黙って上書きしない)。
|
|
7
|
+
* warn + `config-md-receive-only-drift` で乖離を見える形に残す
|
|
8
|
+
* - 両方変化 → 同上(端末を勝者にしない)
|
|
9
9
|
* - どちらも変化なし → no-op
|
|
10
10
|
*
|
|
11
|
+
* ⚠️ 2026-08-09 までは双方向 last-write-wins(端末側更新は PUT、両方変化は端末勝者で
|
|
12
|
+
* PUT + Hub 本文を `.bak.conflict.<ts>` へ退避)だった。Hub の正本がプレースホルダ入り
|
|
13
|
+
* になり、GET が CLI 別のレンダリング結果を返すようになったため、端末が持つ本文を
|
|
14
|
+
* 送り返すと必ず正本を平坦化する(backend が 400 で拒否し、`.bak.conflict` だけが
|
|
15
|
+
* 溜まる)。**正本を直すのは Hub 側**(AI が MCP / API 経由で更新する)。
|
|
16
|
+
*
|
|
11
17
|
* 初回(メタファイル無し): **端末を勝たせない**(唯一の例外が下記「初回登録」)。
|
|
12
18
|
* - ローカルが空でも非空でも、Hub 本文があれば pull。手元は `.bak` へ退避するので
|
|
13
|
-
*
|
|
19
|
+
* 失われない(受け取り専用にした後は、戻しても push されないので **取り込みは
|
|
20
|
+
* Hub 側の正本を直して行う**)。
|
|
14
21
|
* - 出どころ(自動生成物か手書きか)を判定材料にしない。かつては sidecar
|
|
15
22
|
* (自動生成本文の hash)を置いて見分けていたが、**判定に使う箇所は 1 か所なのに
|
|
16
23
|
* 書く / 消す箇所が 6 か所**あり、「消費できたか」がファイル削除の成否に依存する
|
|
@@ -23,12 +30,19 @@
|
|
|
23
30
|
* 詳細な条件と、なぜ `localFilePreexisted` を永続化しないのかは
|
|
24
31
|
* `syncClaudeMdWithHub` 内の `isInitialRegistration` のコメントを参照。
|
|
25
32
|
*
|
|
26
|
-
*
|
|
27
|
-
* GET は `cli_kind` に応じて CLI
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* -
|
|
31
|
-
*
|
|
33
|
+
* **ローカルの設定ファイルは受け取り専用**(2026-08-09)。Hub の正本は
|
|
34
|
+
* `Project.claude_md` 1 本(プレースホルダ入り)で、GET は `cli_kind` に応じて CLI 別へ
|
|
35
|
+
* レンダリングして返す。端末に置かれる **CLAUDE.md も AGENTS.md もその
|
|
36
|
+
* レンダリング結果**であって正本ではないので、
|
|
37
|
+
* - GET には必ず `cli_kind` を載せる(載せないと Codex が CLAUDE.md 向け本文を掴む)
|
|
38
|
+
* - **PUT は初回登録を除いて一切行わない**(push / conflict のどちらも)。乖離は
|
|
39
|
+
* warn + `config-md-receive-only-drift` で見える形にし、ローカルも黙って上書きしない
|
|
40
|
+
* - 正本を直すのは **Hub 側**(AI が MCP / API 経由で更新する)。端末の編集は次回
|
|
41
|
+
* pull で上書きされる前に `.bak` へ退避されるので失われない
|
|
42
|
+
* ⚠️ 初版(同日)は AGENTS.md だけを対象にしていたが、それは実装漏れだった。CLAUDE.md も
|
|
43
|
+
* 同じ経路を通り、送ると backend の平坦化ガードに 400 `config_md_flattened` で弾かれ、
|
|
44
|
+
* `.bak.conflict` だけが毎セッション積み上がる。詳細は `syncClaudeMdWithHub` 内の
|
|
45
|
+
* 受け取り専用ガードのコメント。
|
|
32
46
|
* `cli_kind` を載せていなかった頃に作られた壊れた同期状態(AGENTS.md の base に
|
|
33
47
|
* CLAUDE.md 本文が入っている)は、sync の冒頭で meta / base を捨てて初回 sync へ戻す
|
|
34
48
|
* (`migrateBrokenAgentsBase`)。
|
|
@@ -1236,6 +1250,11 @@ export async function syncClaudeMdWithHub({
|
|
|
1236
1250
|
|
|
1237
1251
|
// --- 初回登録: 「Hub に無くローカルにある」ものを一度だけ Hub へ取り込む -------
|
|
1238
1252
|
//
|
|
1253
|
+
// ⚠️ 2026-08-09 以降、**これが PUT を発行する唯一の経路**(受け取り専用ガードの
|
|
1254
|
+
// 唯一の例外)。両立する理由は下の受け取り専用ガードのコメントに書いてある
|
|
1255
|
+
// (要約: 正本が空なら端末の本文は「正本のレンダリング結果」ではありえないので、
|
|
1256
|
+
// 送っても正本を平坦化しない = 上書きではなく種付け)。
|
|
1257
|
+
//
|
|
1239
1258
|
// 条件は **すべて** 満たすときだけ:
|
|
1240
1259
|
// - meta が無い(この端末はまだ一度も同期していない)
|
|
1241
1260
|
// - Hub 本文が空(**等値比較のみ**。`updated_at` の有無は問わない)
|
|
@@ -1444,12 +1463,25 @@ export async function syncClaudeMdWithHub({
|
|
|
1444
1463
|
return { action: "pull" }
|
|
1445
1464
|
}
|
|
1446
1465
|
|
|
1447
|
-
// ----
|
|
1466
|
+
// ---- ローカルの設定ファイルは受け取り専用(ここから下は必ず localChanged === true)
|
|
1467
|
+
//
|
|
1468
|
+
// Hub は `Project.claude_md`(プレースホルダ入りの正本)を CLI 別にレンダリングして
|
|
1469
|
+
// 返す。端末の **CLAUDE.md も AGENTS.md もそのレンダリング結果**であって正本では
|
|
1470
|
+
// ないので、端末側で編集されたからといって Hub へ送り返してはいけない(正本に
|
|
1471
|
+
// プレースホルダが残っていても、送るのは展開済みの平坦な本文になる = backend の
|
|
1472
|
+
// 平坦化ガードに 400 `config_md_flattened` で弾かれる)。
|
|
1473
|
+
//
|
|
1474
|
+
// ⚠️ **設定ファイルの種別で分岐しない**。初版(2026-08-09)は
|
|
1475
|
+
// `filename === "AGENTS.md"` で絞っていたが、合意した設計は
|
|
1476
|
+
// 「ローカルは常に受け取るだけ。AI が Hub の正本を更新する」であり、種別を問わない。
|
|
1477
|
+
// CLAUDE.md を放置すると、正本がプレースホルダ化された後に
|
|
1478
|
+
// 「GET はレンダリング結果 → 端末は localChanged と判定 → PUT → 400 で拒否」を
|
|
1479
|
+
// 毎セッション繰り返し、`.bak.conflict` だけが積み上がって収束しない
|
|
1480
|
+
// (AGENTS.md が 2026-08-01 から起こしていた症状の再演)。
|
|
1448
1481
|
//
|
|
1449
|
-
//
|
|
1450
|
-
//
|
|
1451
|
-
//
|
|
1452
|
-
// 送るのは展開済みの平坦な本文になる = backend の平坦化ガードに 400 で弾かれる)。
|
|
1482
|
+
// ⚠️ **この判定を前へ動かさないこと**。pull 分岐の **後ろ** に置いているので、
|
|
1483
|
+
// 「Hub だけが変化した純粋な pull」は従来どおり取り込む。前へ動かすと
|
|
1484
|
+
// 「受け取り専用」ではなく「受け取らない」になり、移行後の初回 pull も止まる。
|
|
1453
1485
|
//
|
|
1454
1486
|
// ⚠️ **push 経路と conflict 経路で別々に判定しない**。ここから下は両方とも
|
|
1455
1487
|
// 「ローカルが(前回同期時点から)変化した」経路で、PUT する / しないの分岐は
|
|
@@ -1459,22 +1491,36 @@ export async function syncClaudeMdWithHub({
|
|
|
1459
1491
|
// 上書き」に倒すと、退避 `.bak` は残っても人は気づかず、端末の編集が事実上消える。
|
|
1460
1492
|
// 乖離は **warn + 専用の reason** で見える形にして残し、取り込みは人に委ねる。
|
|
1461
1493
|
//
|
|
1494
|
+
// 【唯一の例外=初回登録(`isInitialRegistration`)がなぜ受け取り専用と両立するか】
|
|
1495
|
+
// 初回登録が成立するのは **Hub の正本が空**(`hubBody === ""` + meta 無し +
|
|
1496
|
+
// 呼び出し元が「このセッションより前からファイルがあった」と報告)のときだけ。
|
|
1497
|
+
// 正本が空である以上、端末の本文は「正本のレンダリング結果」ではありえない
|
|
1498
|
+
// (レンダリング元が無い)ので、送り返しても正本を平坦化しない。これは
|
|
1499
|
+
// **上書きではなく、未登録の正本を 1 度だけ種付けする**経路であり、
|
|
1500
|
+
// 「レンダリング結果を正本へ書き戻さない」という受け取り専用の禁止事項に触れない。
|
|
1501
|
+
// 実データ 16 件がこの経路を通っており、消すと未登録 workspace の正本が
|
|
1502
|
+
// 永久に空のままになる(=端末には毎回プレースホルダが配られ、Hub 側に
|
|
1503
|
+
// 案件固有の内容が載らない)。種付けが済めば meta と非空の正本が揃うので、
|
|
1504
|
+
// 以降の乖離はすべてこのガードに落ちる。
|
|
1505
|
+
//
|
|
1462
1506
|
// ⚠️ ログに本文を載せないこと(接続情報が混ざりうる)。出してよいのは長さ・行数・
|
|
1463
1507
|
// ハッシュのような、中身を復元できない指標だけ。
|
|
1464
|
-
if (
|
|
1508
|
+
if (!isInitialRegistration) {
|
|
1465
1509
|
logger?.warn?.(
|
|
1466
1510
|
{
|
|
1467
1511
|
dirName,
|
|
1468
|
-
|
|
1512
|
+
// ⚠️ 固定値を書かない。種別を広げた後に「常に AGENTS.md」と出ると、
|
|
1513
|
+
// どちらのファイルで乖離したのかがログから分からなくなる。
|
|
1514
|
+
config_filename: configPaths(configFilename).filename,
|
|
1469
1515
|
hub_changed: hubChanged,
|
|
1470
1516
|
local_bytes: localBody == null ? 0 : Buffer.byteLength(localBody, "utf-8"),
|
|
1471
1517
|
local_lines: localBody == null ? 0 : localBody.split("\n").length,
|
|
1472
1518
|
local_hash: localCurrentHash,
|
|
1473
1519
|
hub_hash: hubHash,
|
|
1474
1520
|
},
|
|
1475
|
-
"syncClaudeMdWithHub: local
|
|
1521
|
+
"syncClaudeMdWithHub: local config md drifted from the rendered hub source of truth; it is receive-only so nothing was pushed and nothing was overwritten (edit the source of truth on Hub, then let the next sync pull)",
|
|
1476
1522
|
)
|
|
1477
|
-
return { action: "skipped", reason: "
|
|
1523
|
+
return { action: "skipped", reason: "config-md-receive-only-drift" }
|
|
1478
1524
|
}
|
|
1479
1525
|
|
|
1480
1526
|
if (!hubChanged && localChanged) {
|
|
@@ -1482,6 +1528,13 @@ export async function syncClaudeMdWithHub({
|
|
|
1482
1528
|
// 個別チェックを再掲しないこと(検査の本体が 2 つになると、片方だけ適用範囲が
|
|
1483
1529
|
// 戻る回帰が起きる。実際に「PUT 経路にだけ掛ける」設計から blocker が出た)。
|
|
1484
1530
|
// push: Hub に PUT。成功時にメタ更新。
|
|
1531
|
+
//
|
|
1532
|
+
// ⚠️ **ここへ来るのは初回登録だけ**(2026-08-09 以降)。上の受け取り専用ガードが
|
|
1533
|
+
// それ以外の `localChanged` を全部返しているため。`isInitialRegistration` は
|
|
1534
|
+
// `hubBody === ""` かつ meta 無しが前提なので、この分岐の `hubChanged` は
|
|
1535
|
+
// 常に false になる(= `!hubChanged` の条件はいま実質的に常に真)。
|
|
1536
|
+
// 条件を消して単純化しないこと: 受け取り専用ガードの例外を将来増やしたときに、
|
|
1537
|
+
// 「Hub が動いているのに端末本文で PUT する」経路が黙って復活する。
|
|
1485
1538
|
const put = await putHubClaudeMd({
|
|
1486
1539
|
hubUrl: url,
|
|
1487
1540
|
accessToken: token,
|
|
@@ -1550,6 +1603,19 @@ export async function syncClaudeMdWithHub({
|
|
|
1550
1603
|
|
|
1551
1604
|
// hubChanged && localChanged → 衝突。端末側を勝者にして Hub に PUT。
|
|
1552
1605
|
//
|
|
1606
|
+
// ⚠️⚠️ **この分岐はいま到達しない**(2026-08-09 以降)。受け取り専用ガードが
|
|
1607
|
+
// `localChanged` の経路を初回登録以外すべて返し、初回登録は `hubBody === ""` が
|
|
1608
|
+
// 前提なので `hubChanged` が false になるため。テストも
|
|
1609
|
+
// 「conflict 相当の入力 → `skipped(config-md-receive-only-drift)`・PUT 0 回・
|
|
1610
|
+
// `.bak.conflict` 0 件」を固定する側へ載せ替えてある。
|
|
1611
|
+
//
|
|
1612
|
+
// ⚠️ **消していないのは意図的**。「受け取り専用」は方針であって backend の制約では
|
|
1613
|
+
// なく(正本にプレースホルダを保ったまま端末から更新する API ができれば覆りうる)、
|
|
1614
|
+
// ガードと同じ commit で衝突解決の機構ごと落とすと、方針を見直すときに
|
|
1615
|
+
// 退避 (.bak.conflict) と楽観ロックの組み合わせを一から書き直すことになる。
|
|
1616
|
+
// ⚠️ 逆に言えば **いまは検査されていないコード**なので、ここを直したくなったら
|
|
1617
|
+
// 先に到達可能性(受け取り専用ガードの例外)を決めること。
|
|
1618
|
+
//
|
|
1553
1619
|
// ⚠️ hash の観測と snapshot の自己整合性は **入口の門で保証済み**(push 経路と同じ
|
|
1554
1620
|
// 理由でここに再掲しない)。壊れた snapshot は入口で返すので、**PUT しない回に
|
|
1555
1621
|
// `.bak.conflict` だけが増える**こともない。
|
package/src/tmux.mjs
CHANGED
|
@@ -828,14 +828,16 @@ export async function createSession(name, cwd, opts = {}) {
|
|
|
828
828
|
}
|
|
829
829
|
}
|
|
830
830
|
// HUB_PROJECTS_BASE 配下の workspace の場合は CLAUDE.md を必要なら生成し、
|
|
831
|
-
// Hub の正本(Project.claude_md
|
|
831
|
+
// Hub の正本(Project.claude_md)から受け取る。
|
|
832
832
|
// - ensureClaudeMd: 既存 CLAUDE.md は触らない。無ければ director frontmatter
|
|
833
833
|
// または placeholder を生成する。
|
|
834
|
-
// - syncClaudeMdWithHub: Hub
|
|
835
|
-
//
|
|
836
|
-
//
|
|
837
|
-
//
|
|
838
|
-
//
|
|
834
|
+
// - syncClaudeMdWithHub: **受け取り専用**。Hub 側更新はローカルへ pull(.bak 退避
|
|
835
|
+
// してから上書き)、端末側で編集されていても Hub へは送り返さない(正本は
|
|
836
|
+
// プレースホルダ入りで、端末にあるのはそのレンダリング結果のため)。乖離は
|
|
837
|
+
// warn + `config-md-receive-only-drift` で見える形に残す。唯一の例外が
|
|
838
|
+
// **初回登録**(Hub の正本が空 + そのファイルが元からあった場合に 1 度だけ push)。
|
|
839
|
+
// Hub が一度も保存されていないプロジェクトでは何もしない(テナント標準
|
|
840
|
+
// CLAUDE.md の運用を尊重する)。
|
|
839
841
|
// どちらも失敗しても session 起動自体は止めない (warn のみ)。
|
|
840
842
|
// ⚠️ 2 つの呼び出しで **1 本の budget を共有**する。1 往復ごとの期限だけでは
|
|
841
843
|
// 往復の本数ぶん足し算されてセッション起動が長時間止まりうるため、Hub 処理に
|