@cocorograph/hub-agent 0.7.89 → 0.7.90
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 +51 -34
- package/src/claude-md.mjs +35 -28
- package/src/tmux.mjs +19 -3
package/package.json
CHANGED
package/src/claude-md-sync.mjs
CHANGED
|
@@ -26,12 +26,18 @@ import path from "node:path"
|
|
|
26
26
|
import crypto from "node:crypto"
|
|
27
27
|
|
|
28
28
|
const DEFAULT_HUB_API = "https://api.hub.cocorograph.com"
|
|
29
|
-
const TOKEN_PATH = path.join(os.homedir(), ".claude", ".hub_token.json")
|
|
30
29
|
const META_DIR = ".cockpit-sync"
|
|
31
|
-
const META_FILE = "claude-md.json"
|
|
32
|
-
const BASE_FILE = "CLAUDE.md.base"
|
|
33
30
|
const BAK_KEEP = 3 // 通常 .bak と衝突 .bak それぞれ直近 3 件まで保持
|
|
34
31
|
|
|
32
|
+
function configPaths(configFilename = "CLAUDE.md") {
|
|
33
|
+
const filename = configFilename === "AGENTS.md" ? "AGENTS.md" : "CLAUDE.md"
|
|
34
|
+
return {
|
|
35
|
+
filename,
|
|
36
|
+
metaFile: filename === "AGENTS.md" ? "agents-md.json" : "claude-md.json",
|
|
37
|
+
baseFile: `${filename}.base`,
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
35
41
|
async function readJsonOrNull(p) {
|
|
36
42
|
try {
|
|
37
43
|
const text = await fs.readFile(p, "utf-8")
|
|
@@ -41,9 +47,13 @@ async function readJsonOrNull(p) {
|
|
|
41
47
|
}
|
|
42
48
|
}
|
|
43
49
|
|
|
44
|
-
async function loadAccessToken() {
|
|
45
|
-
const
|
|
46
|
-
|
|
50
|
+
async function loadAccessToken(configFilename = "CLAUDE.md") {
|
|
51
|
+
const cliDir = configPaths(configFilename).filename === "AGENTS.md" ? ".codex" : ".claude"
|
|
52
|
+
const primary = await readJsonOrNull(path.join(os.homedir(), cliDir, ".hub_token.json"))
|
|
53
|
+
if (primary && typeof primary.access === "string" && primary.access) return primary.access
|
|
54
|
+
// 既存のhub-agent enrollmentは ~/.claude にtokenを持つため、Codex単独導入前の端末も救済する。
|
|
55
|
+
const legacy = await readJsonOrNull(path.join(os.homedir(), ".claude", ".hub_token.json"))
|
|
56
|
+
return (legacy && typeof legacy.access === "string" && legacy.access) || null
|
|
47
57
|
}
|
|
48
58
|
|
|
49
59
|
function sha256(s) {
|
|
@@ -133,29 +143,29 @@ async function putHubClaudeMd({ hubUrl, accessToken, dirName, body, fetchImpl, l
|
|
|
133
143
|
}
|
|
134
144
|
}
|
|
135
145
|
|
|
136
|
-
async function
|
|
146
|
+
async function readLocalConfigMd(targetDir, configFilename) {
|
|
137
147
|
try {
|
|
138
|
-
return await fs.readFile(path.join(targetDir,
|
|
148
|
+
return await fs.readFile(path.join(targetDir, configPaths(configFilename).filename), "utf-8")
|
|
139
149
|
} catch (err) {
|
|
140
150
|
if (err?.code === "ENOENT") return null
|
|
141
151
|
throw err
|
|
142
152
|
}
|
|
143
153
|
}
|
|
144
154
|
|
|
145
|
-
async function
|
|
146
|
-
await fs.writeFile(path.join(targetDir,
|
|
155
|
+
async function writeLocalConfigMd(targetDir, body, configFilename) {
|
|
156
|
+
await fs.writeFile(path.join(targetDir, configPaths(configFilename).filename), body, "utf-8")
|
|
147
157
|
}
|
|
148
158
|
|
|
149
|
-
async function readMeta(targetDir) {
|
|
150
|
-
const metaPath = path.join(targetDir, META_DIR,
|
|
159
|
+
async function readMeta(targetDir, configFilename) {
|
|
160
|
+
const metaPath = path.join(targetDir, META_DIR, configPaths(configFilename).metaFile)
|
|
151
161
|
return await readJsonOrNull(metaPath)
|
|
152
162
|
}
|
|
153
163
|
|
|
154
|
-
async function writeMeta(targetDir, meta) {
|
|
164
|
+
async function writeMeta(targetDir, meta, configFilename) {
|
|
155
165
|
const metaDir = path.join(targetDir, META_DIR)
|
|
156
166
|
await fs.mkdir(metaDir, { recursive: true })
|
|
157
167
|
await fs.writeFile(
|
|
158
|
-
path.join(metaDir,
|
|
168
|
+
path.join(metaDir, configPaths(configFilename).metaFile),
|
|
159
169
|
JSON.stringify(meta, null, 2),
|
|
160
170
|
"utf-8",
|
|
161
171
|
)
|
|
@@ -171,11 +181,11 @@ async function writeMeta(targetDir, meta) {
|
|
|
171
181
|
* meta(claude-md.json) は hash と updated_at しか持たないため、本文そのものが要る
|
|
172
182
|
* 3-way merge の用途ではこのファイルが必要。
|
|
173
183
|
*/
|
|
174
|
-
async function writeBaseBody(targetDir, body) {
|
|
184
|
+
async function writeBaseBody(targetDir, body, configFilename) {
|
|
175
185
|
if (body == null) return
|
|
176
186
|
const metaDir = path.join(targetDir, META_DIR)
|
|
177
187
|
await fs.mkdir(metaDir, { recursive: true })
|
|
178
|
-
await fs.writeFile(path.join(metaDir,
|
|
188
|
+
await fs.writeFile(path.join(metaDir, configPaths(configFilename).baseFile), body, "utf-8")
|
|
179
189
|
}
|
|
180
190
|
|
|
181
191
|
/**
|
|
@@ -184,11 +194,12 @@ async function writeBaseBody(targetDir, body) {
|
|
|
184
194
|
*
|
|
185
195
|
* @param {'normal'|'conflict'} kind バックアップ種別(命名 prefix)
|
|
186
196
|
*/
|
|
187
|
-
async function
|
|
197
|
+
async function backupLocalConfigMd(targetDir, body, kind, configFilename) {
|
|
188
198
|
if (body == null) return
|
|
189
199
|
const metaDir = path.join(targetDir, META_DIR)
|
|
190
200
|
await fs.mkdir(metaDir, { recursive: true })
|
|
191
|
-
const
|
|
201
|
+
const filename = configPaths(configFilename).filename
|
|
202
|
+
const prefix = kind === "conflict" ? `${filename}.conflict` : filename
|
|
192
203
|
const bakName = makeBakName(prefix)
|
|
193
204
|
await fs.writeFile(path.join(metaDir, bakName), body, "utf-8")
|
|
194
205
|
// 同 prefix の .bak を新しい順に BAK_KEEP 件まで残す。
|
|
@@ -224,12 +235,13 @@ export async function syncClaudeMdWithHub({
|
|
|
224
235
|
accessToken,
|
|
225
236
|
logger,
|
|
226
237
|
fetchImpl,
|
|
238
|
+
configFilename = "CLAUDE.md",
|
|
227
239
|
} = {}) {
|
|
228
240
|
if (!targetDir) throw new Error("syncClaudeMdWithHub requires targetDir")
|
|
229
241
|
if (!dirName) throw new Error("syncClaudeMdWithHub requires dirName")
|
|
230
242
|
|
|
231
243
|
const token =
|
|
232
|
-
accessToken === undefined ? await loadAccessToken() : accessToken || null
|
|
244
|
+
accessToken === undefined ? await loadAccessToken(configFilename) : accessToken || null
|
|
233
245
|
if (!token) {
|
|
234
246
|
logger?.info?.({ dirName }, "syncClaudeMdWithHub: no access token, skip")
|
|
235
247
|
return { action: "skipped", reason: "no-token" }
|
|
@@ -252,11 +264,11 @@ export async function syncClaudeMdWithHub({
|
|
|
252
264
|
const hubUpdatedAt = hub.claude_md_updated_at || null
|
|
253
265
|
|
|
254
266
|
// 2) ローカル CLAUDE.md を読み、現在 hash を計算
|
|
255
|
-
const localBody = await
|
|
267
|
+
const localBody = await readLocalConfigMd(targetDir, configFilename)
|
|
256
268
|
const localCurrentHash = localBody == null ? null : sha256(localBody)
|
|
257
269
|
|
|
258
270
|
// 3) メタファイルを読み、前回 sync の状態を取得
|
|
259
|
-
const meta = await readMeta(targetDir)
|
|
271
|
+
const meta = await readMeta(targetDir, configFilename)
|
|
260
272
|
|
|
261
273
|
// Hub が一度も保存されていない(空本文 + updated_at が null)プロジェクトは
|
|
262
274
|
// 「Hub UI で意図的に未設定にしている」運用(テナント標準 CLAUDE.md に任せる等)の
|
|
@@ -276,7 +288,12 @@ export async function syncClaudeMdWithHub({
|
|
|
276
288
|
if (!meta) {
|
|
277
289
|
// 初回 sync。
|
|
278
290
|
hubChanged = !!hubBody
|
|
279
|
-
if (
|
|
291
|
+
if (configPaths(configFilename).filename === "AGENTS.md") {
|
|
292
|
+
// Codex の AGENTS.md は既存リポジトリにも通常存在し、案件正本とは別の
|
|
293
|
+
// ローカル規約を含む。初回に terminal-wins で Project.claude_md を壊さないよう、
|
|
294
|
+
// Hub 正本を優先して pull(既存 AGENTS.md は .bak に退避)する。
|
|
295
|
+
localChanged = false
|
|
296
|
+
} else if (localBody == null) {
|
|
280
297
|
localChanged = false
|
|
281
298
|
} else {
|
|
282
299
|
localChanged = !!localBody // 非空なら端末側に何かある=衝突候補
|
|
@@ -300,15 +317,15 @@ export async function syncClaudeMdWithHub({
|
|
|
300
317
|
|
|
301
318
|
if (hubChanged && !localChanged) {
|
|
302
319
|
// pull: ローカルを .bak 退避 → Hub の本文で上書き
|
|
303
|
-
await
|
|
304
|
-
await
|
|
320
|
+
await backupLocalConfigMd(targetDir, localBody, "normal", configFilename)
|
|
321
|
+
await writeLocalConfigMd(targetDir, hubBody, configFilename)
|
|
305
322
|
await writeMeta(targetDir, {
|
|
306
323
|
lastSyncedHubUpdatedAt: hubUpdatedAt,
|
|
307
324
|
lastSyncedHash: hubHash || sha256(hubBody),
|
|
308
325
|
lastSyncedAt: nowIso(),
|
|
309
|
-
})
|
|
326
|
+
}, configFilename)
|
|
310
327
|
// 3-way merge 用 base: pull 直後はローカル == Hub なので Hub 本文を base に。
|
|
311
|
-
await writeBaseBody(targetDir, hubBody)
|
|
328
|
+
await writeBaseBody(targetDir, hubBody, configFilename)
|
|
312
329
|
logger?.info?.({ dirName }, "syncClaudeMdWithHub: pull")
|
|
313
330
|
return { action: "pull" }
|
|
314
331
|
}
|
|
@@ -334,17 +351,17 @@ export async function syncClaudeMdWithHub({
|
|
|
334
351
|
lastSyncedHubUpdatedAt: nowIso(),
|
|
335
352
|
lastSyncedHash: localCurrentHash,
|
|
336
353
|
lastSyncedAt: nowIso(),
|
|
337
|
-
})
|
|
354
|
+
}, configFilename)
|
|
338
355
|
// 3-way merge 用 base: push 直後は Hub の正本がローカルと一致するので、
|
|
339
356
|
// 次回の base は今 push した本文 (= localBody)。
|
|
340
|
-
await writeBaseBody(targetDir, localBody || "")
|
|
357
|
+
await writeBaseBody(targetDir, localBody || "", configFilename)
|
|
341
358
|
logger?.info?.({ dirName }, "syncClaudeMdWithHub: push")
|
|
342
359
|
return { action: "push" }
|
|
343
360
|
}
|
|
344
361
|
|
|
345
362
|
// hubChanged && localChanged → 衝突。端末側を勝者にして Hub に PUT。
|
|
346
363
|
// Hub の取り込み内容は .bak.conflict に退避(後で人手マージできるよう)。
|
|
347
|
-
await
|
|
364
|
+
await backupLocalConfigMd(targetDir, hubBody, "conflict", configFilename)
|
|
348
365
|
const ok = await putHubClaudeMd({
|
|
349
366
|
hubUrl: url,
|
|
350
367
|
accessToken: token,
|
|
@@ -361,9 +378,9 @@ export async function syncClaudeMdWithHub({
|
|
|
361
378
|
lastSyncedHubUpdatedAt: nowIso(),
|
|
362
379
|
lastSyncedHash: localCurrentHash,
|
|
363
380
|
lastSyncedAt: nowIso(),
|
|
364
|
-
})
|
|
381
|
+
}, configFilename)
|
|
365
382
|
// 3-way merge 用 base: conflict push 後の Hub 正本は端末側本文 (= localBody)。
|
|
366
|
-
await writeBaseBody(targetDir, localBody || "")
|
|
383
|
+
await writeBaseBody(targetDir, localBody || "", configFilename)
|
|
367
384
|
logger?.warn?.(
|
|
368
385
|
{ dirName },
|
|
369
386
|
"syncClaudeMdWithHub: conflict (terminal wins); hub body saved to .bak.conflict",
|
|
@@ -374,7 +391,7 @@ export async function syncClaudeMdWithHub({
|
|
|
374
391
|
// メタ操作・ハッシュ計算は ensureClaudeMd 側 (Hub pull 初回配信) でも使うため
|
|
375
392
|
// named export する。同じファイルパス・同じスキーマで管理することで、初回配信と
|
|
376
393
|
// 以降の sync で食い違いを起こさない。
|
|
377
|
-
export { sha256, readMeta, writeMeta, writeBaseBody, nowIso }
|
|
394
|
+
export { sha256, readMeta, writeMeta, writeBaseBody, nowIso, configPaths }
|
|
378
395
|
|
|
379
396
|
// 内部テスト用エクスポート
|
|
380
397
|
export const _internals = {
|
|
@@ -382,7 +399,7 @@ export const _internals = {
|
|
|
382
399
|
readMeta,
|
|
383
400
|
writeMeta,
|
|
384
401
|
writeBaseBody,
|
|
385
|
-
|
|
402
|
+
backupLocalConfigMd,
|
|
386
403
|
BAK_KEEP,
|
|
387
|
-
|
|
404
|
+
configPaths,
|
|
388
405
|
}
|
package/src/claude-md.mjs
CHANGED
|
@@ -27,7 +27,6 @@ import {
|
|
|
27
27
|
} from "./claude-md-sync.mjs"
|
|
28
28
|
|
|
29
29
|
const DEFAULT_HUB_API = "https://api.hub.cocorograph.com"
|
|
30
|
-
const TOKEN_PATH = path.join(os.homedir(), ".claude", ".hub_token.json")
|
|
31
30
|
|
|
32
31
|
async function readJsonOrNull(p) {
|
|
33
32
|
try {
|
|
@@ -38,9 +37,12 @@ async function readJsonOrNull(p) {
|
|
|
38
37
|
}
|
|
39
38
|
}
|
|
40
39
|
|
|
41
|
-
async function loadAccessToken() {
|
|
42
|
-
const
|
|
43
|
-
|
|
40
|
+
async function loadAccessToken(configFilename = "CLAUDE.md") {
|
|
41
|
+
const cliDir = configFilename === "AGENTS.md" ? ".codex" : ".claude"
|
|
42
|
+
const primary = await readJsonOrNull(path.join(os.homedir(), cliDir, ".hub_token.json"))
|
|
43
|
+
if (primary && typeof primary.access === "string" && primary.access) return primary.access
|
|
44
|
+
const legacy = await readJsonOrNull(path.join(os.homedir(), ".claude", ".hub_token.json"))
|
|
45
|
+
return (legacy && typeof legacy.access === "string" && legacy.access) || null
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
/**
|
|
@@ -134,7 +136,7 @@ function isGitHubSlug(repoUrl) {
|
|
|
134
136
|
return /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repoUrl)
|
|
135
137
|
}
|
|
136
138
|
|
|
137
|
-
function renderRepositorySection(repositories, dirName = "") {
|
|
139
|
+
function renderRepositorySection(repositories, dirName = "", cliKind = "claude") {
|
|
138
140
|
if (!Array.isArray(repositories) || repositories.length === 0) return ""
|
|
139
141
|
|
|
140
142
|
const lines = ["## Cockpit クローンレス開発フロー", ""]
|
|
@@ -211,7 +213,7 @@ function renderRepositorySection(repositories, dirName = "") {
|
|
|
211
213
|
"# 秘密鍵を ~/.ssh/hub_deploy/ に書き出し、ssh コマンドだけ受け取る",
|
|
212
214
|
)
|
|
213
215
|
lines.push(
|
|
214
|
-
`python3
|
|
216
|
+
`python3 ~/.${cliKind === "codex" ? "codex" : "claude"}/scripts/hub_helper.py deploy_creds ${dirName || "<dir_name>"} --env ${envExample}`,
|
|
215
217
|
)
|
|
216
218
|
lines.push("```")
|
|
217
219
|
lines.push("")
|
|
@@ -235,7 +237,7 @@ function renderRepositorySection(repositories, dirName = "") {
|
|
|
235
237
|
return lines.join("\n")
|
|
236
238
|
}
|
|
237
239
|
|
|
238
|
-
function renderWithDirector(dirName, director, repositorySection = "") {
|
|
240
|
+
function renderWithDirector(dirName, director, repositorySection = "", configFilename = "CLAUDE.md") {
|
|
239
241
|
const fm = director?.frontmatter || {}
|
|
240
242
|
const client = (fm.client || "").trim()
|
|
241
243
|
const domain = (fm.domain || "").trim()
|
|
@@ -245,9 +247,9 @@ function renderWithDirector(dirName, director, repositorySection = "") {
|
|
|
245
247
|
const googleDrive = (fm.google_drive || "").trim()
|
|
246
248
|
|
|
247
249
|
const lines = [
|
|
248
|
-
|
|
250
|
+
`# ${configFilename}`,
|
|
249
251
|
"",
|
|
250
|
-
|
|
252
|
+
`This file provides guidance to ${configFilename === "AGENTS.md" ? "Codex" : "Claude Code"} when working with code in this repository.`,
|
|
251
253
|
"",
|
|
252
254
|
"## プロジェクト概要",
|
|
253
255
|
"",
|
|
@@ -291,17 +293,17 @@ function renderWithDirector(dirName, director, repositorySection = "") {
|
|
|
291
293
|
"開発手順・SSH 接続情報・横断ナレッジへのポインタなど、より詳細な内容は",
|
|
292
294
|
)
|
|
293
295
|
lines.push(
|
|
294
|
-
"
|
|
296
|
+
`このリポジトリで ${configFilename === "AGENTS.md" ? "`codex` を起動して `/init-Codex-md`" : "`claude` を起動して `/init-claude-md`"} スキルを実行してください。`,
|
|
295
297
|
)
|
|
296
298
|
lines.push("")
|
|
297
299
|
return lines.join("\n")
|
|
298
300
|
}
|
|
299
301
|
|
|
300
|
-
function renderPlaceholder(dirName, repositorySection = "") {
|
|
302
|
+
function renderPlaceholder(dirName, repositorySection = "", configFilename = "CLAUDE.md") {
|
|
301
303
|
const sections = [
|
|
302
|
-
|
|
304
|
+
`# ${configFilename}`,
|
|
303
305
|
"",
|
|
304
|
-
|
|
306
|
+
`This file provides guidance to ${configFilename === "AGENTS.md" ? "Codex" : "Claude Code"} when working with code in this repository.`,
|
|
305
307
|
"",
|
|
306
308
|
"## 関連",
|
|
307
309
|
"",
|
|
@@ -315,7 +317,7 @@ function renderPlaceholder(dirName, repositorySection = "") {
|
|
|
315
317
|
"## 初期化",
|
|
316
318
|
"",
|
|
317
319
|
"これは hub-agent の自動生成プレースホルダです。Hub director も未作成です。",
|
|
318
|
-
"
|
|
320
|
+
`詳細を生成するには、このリポジトリで ${configFilename === "AGENTS.md" ? "`codex` を起動して `/init-Codex-md`" : "`claude` を起動して `/init-claude-md`"} スキルを実行してください。`,
|
|
319
321
|
"",
|
|
320
322
|
)
|
|
321
323
|
return sections.join("\n")
|
|
@@ -344,12 +346,16 @@ export async function ensureClaudeMd({
|
|
|
344
346
|
accessToken,
|
|
345
347
|
logger,
|
|
346
348
|
fetchImpl,
|
|
349
|
+
configFilename = "CLAUDE.md",
|
|
347
350
|
} = {}) {
|
|
348
351
|
if (!targetDir) throw new Error("ensureClaudeMd requires targetDir")
|
|
349
352
|
if (!dirName) throw new Error("ensureClaudeMd requires dirName")
|
|
350
|
-
const
|
|
353
|
+
const configMdPath = path.join(
|
|
354
|
+
targetDir,
|
|
355
|
+
configFilename === "AGENTS.md" ? "AGENTS.md" : "CLAUDE.md",
|
|
356
|
+
)
|
|
351
357
|
try {
|
|
352
|
-
await fs.access(
|
|
358
|
+
await fs.access(configMdPath)
|
|
353
359
|
return { written: false, source: "existing" }
|
|
354
360
|
} catch {
|
|
355
361
|
/* not found → continue */
|
|
@@ -358,7 +364,7 @@ export async function ensureClaudeMd({
|
|
|
358
364
|
// accessToken は明示 null (テスト用) で「token を読まない」を選べるようにする。
|
|
359
365
|
// undefined のときだけ ~/.claude/.hub_token.json にフォールバックする。
|
|
360
366
|
const token =
|
|
361
|
-
accessToken === undefined ? await loadAccessToken() : accessToken || null
|
|
367
|
+
accessToken === undefined ? await loadAccessToken(configFilename) : accessToken || null
|
|
362
368
|
const url = hubUrl || DEFAULT_HUB_API
|
|
363
369
|
|
|
364
370
|
// Stage 3: Hub に正本があればそれを最優先で書き込み、メタも初期化する
|
|
@@ -374,17 +380,17 @@ export async function ensureClaudeMd({
|
|
|
374
380
|
const hubBody = hub?.claude_md || ""
|
|
375
381
|
const hubInitialized = !!hub?.claude_md_updated_at
|
|
376
382
|
if (hubInitialized && hubBody) {
|
|
377
|
-
await fs.writeFile(
|
|
383
|
+
await fs.writeFile(configMdPath, hubBody, "utf-8")
|
|
378
384
|
await writeMeta(targetDir, {
|
|
379
385
|
lastSyncedHubUpdatedAt: hub.claude_md_updated_at,
|
|
380
386
|
lastSyncedHash: hub.claude_md_hash || sha256(hubBody),
|
|
381
387
|
lastSyncedAt: nowIso(),
|
|
382
|
-
})
|
|
388
|
+
}, configFilename)
|
|
383
389
|
// 初回 pull 直後は base = Hub 本文。以降の session-end 3-way merge の入力。
|
|
384
|
-
await writeBaseBody(targetDir, hubBody)
|
|
390
|
+
await writeBaseBody(targetDir, hubBody, configFilename)
|
|
385
391
|
logger?.info?.(
|
|
386
|
-
{ dirName,
|
|
387
|
-
"wrote
|
|
392
|
+
{ dirName, configMdPath },
|
|
393
|
+
"wrote project instruction file from Hub project claude_md (initial sync)",
|
|
388
394
|
)
|
|
389
395
|
return { written: true, source: "hub" }
|
|
390
396
|
}
|
|
@@ -399,16 +405,17 @@ export async function ensureClaudeMd({
|
|
|
399
405
|
])
|
|
400
406
|
: [null, []]
|
|
401
407
|
|
|
402
|
-
const
|
|
408
|
+
const cliKind = configFilename === "AGENTS.md" ? "codex" : "claude"
|
|
409
|
+
const repositorySection = renderRepositorySection(repositories, dirName, cliKind)
|
|
403
410
|
const body = director
|
|
404
|
-
? renderWithDirector(dirName, director, repositorySection)
|
|
405
|
-
: renderPlaceholder(dirName, repositorySection)
|
|
411
|
+
? renderWithDirector(dirName, director, repositorySection, configFilename)
|
|
412
|
+
: renderPlaceholder(dirName, repositorySection, configFilename)
|
|
406
413
|
const source = director ? "director" : "placeholder"
|
|
407
414
|
|
|
408
|
-
await fs.writeFile(
|
|
415
|
+
await fs.writeFile(configMdPath, body, "utf-8")
|
|
409
416
|
logger?.info?.(
|
|
410
|
-
{ dirName,
|
|
411
|
-
"wrote
|
|
417
|
+
{ dirName, configMdPath, source },
|
|
418
|
+
"wrote project instruction file for new workspace dir",
|
|
412
419
|
)
|
|
413
420
|
return { written: true, source }
|
|
414
421
|
}
|
package/src/tmux.mjs
CHANGED
|
@@ -78,6 +78,18 @@ export function isUnderHubProjectsBase(p) {
|
|
|
78
78
|
return true
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Hub workspace またはその worktree から、案件指示ファイルを置く親 workspace を返す。
|
|
83
|
+
* worktree 内の AGENTS.md / CLAUDE.md を別管理すると Hub 正本が分岐するため、
|
|
84
|
+
* `<workspace>/.claude/worktrees/<name>` で起動しても常に `<workspace>` を対象にする。
|
|
85
|
+
*/
|
|
86
|
+
export function projectInstructionDir(p) {
|
|
87
|
+
if (!isUnderHubProjectsBase(p)) return path.resolve(p)
|
|
88
|
+
const base = path.resolve(hubProjectsBase())
|
|
89
|
+
const [workspace] = path.relative(base, path.resolve(p)).split(path.sep)
|
|
90
|
+
return path.join(base, workspace)
|
|
91
|
+
}
|
|
92
|
+
|
|
81
93
|
/**
|
|
82
94
|
* git branch 名から worktree dir 名 (= tmux session 名) を導出する。
|
|
83
95
|
* 例: "feature/multi-area" -> "feature-multi-area"
|
|
@@ -788,15 +800,18 @@ export async function createSession(name, cwd, opts = {}) {
|
|
|
788
800
|
// 何もしない(テナント標準 CLAUDE.md の運用を尊重する)。
|
|
789
801
|
// どちらも失敗しても session 起動自体は止めない (warn のみ)。
|
|
790
802
|
if (isUnderHubProjectsBase(resolvedCwd)) {
|
|
791
|
-
const
|
|
803
|
+
const instructionDir = projectInstructionDir(resolvedCwd)
|
|
804
|
+
const dirName = path.basename(instructionDir)
|
|
805
|
+
const configFilename = opts.cliKind === "codex" ? "AGENTS.md" : "CLAUDE.md"
|
|
792
806
|
try {
|
|
793
807
|
await ensureClaudeMd({
|
|
794
|
-
targetDir:
|
|
808
|
+
targetDir: instructionDir,
|
|
795
809
|
dirName,
|
|
796
810
|
hubUrl: opts.hubUrl,
|
|
797
811
|
accessToken: opts.hubAccessToken,
|
|
798
812
|
logger: opts.logger,
|
|
799
813
|
fetchImpl: opts.fetchImpl,
|
|
814
|
+
configFilename,
|
|
800
815
|
})
|
|
801
816
|
} catch (err) {
|
|
802
817
|
opts.logger?.warn?.(
|
|
@@ -806,12 +821,13 @@ export async function createSession(name, cwd, opts = {}) {
|
|
|
806
821
|
}
|
|
807
822
|
try {
|
|
808
823
|
const syncResult = await syncClaudeMdWithHub({
|
|
809
|
-
targetDir:
|
|
824
|
+
targetDir: instructionDir,
|
|
810
825
|
dirName,
|
|
811
826
|
hubUrl: opts.hubUrl,
|
|
812
827
|
accessToken: opts.hubAccessToken,
|
|
813
828
|
logger: opts.logger,
|
|
814
829
|
fetchImpl: opts.fetchImpl,
|
|
830
|
+
configFilename,
|
|
815
831
|
})
|
|
816
832
|
opts.logger?.debug?.(
|
|
817
833
|
{ session: name, dirName, sync: syncResult },
|