axmap-cli 1.8.0 → 1.9.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/commands/ax-update.md +0 -3
- package/README.ko.md +15 -0
- package/README.md +15 -0
- package/bin/axmap.mjs +149 -88
- package/governance/vote.mjs +6 -69
- package/mcp/README.md +2 -0
- package/mcp/server.mjs +22 -7
- package/package.json +1 -1
- package/src/bridge.mjs +168 -56
- package/src/bridge.mts +104 -12
- package/src/managed-session.mjs +4 -3
- package/src/managed-session.mts +4 -3
- package/src/session-mailbox.mjs +21 -11
- package/src/session-mailbox.mts +13 -8
- package/src/session-types.mjs +3 -0
- package/src/session-types.mts +9 -1
- package/src/terminal-bridge.mjs +13 -7
- package/src/terminal-bridge.mts +12 -7
- package/src/update.mjs +3 -20
- package/src/workspace.mjs +247 -0
- package/src/workspace.mts +220 -0
- package/tools/build.mjs +1 -1
- package/tools/bus.mjs +1 -1
- package/tools/run.mjs +8 -1
- package/src/closure.mjs +0 -343
- package/tools/vendor.mjs +0 -382
|
@@ -14,6 +14,3 @@ description: axMap — 새 버전이 나왔는지 보고, 받을지는 사람이
|
|
|
14
14
|
|
|
15
15
|
3. 종료 코드가 `1` 이면 **확인 자체를 못 한 것**이다 (네트워크, 또는 아직 배포된
|
|
16
16
|
적이 없는 꾸러미). "최신입니다" 로 옮기지 마라 — 모르는 것과 최신인 것은 다르다.
|
|
17
|
-
|
|
18
|
-
팀 저장소 안의 `ci/axmap/` 사본에서 이 명령을 부르면 "사본입니다" 라고 답한다.
|
|
19
|
-
그건 정상이다. 사본은 npm 이 아니라 axMap 저장소의 `tools/vendor.mjs` 로 갱신한다.
|
package/README.ko.md
CHANGED
|
@@ -31,6 +31,21 @@ axmap setup
|
|
|
31
31
|
|
|
32
32
|
최신 변경부터 정리합니다.
|
|
33
33
|
|
|
34
|
+
### 1.9.0
|
|
35
|
+
|
|
36
|
+
<sub>2026.10.29</sub>
|
|
37
|
+
|
|
38
|
+
**작업 폴더에서 저장소를 찾고, 꺼진 로컬 세션을 알리며, 저장소 안 axMap 사본을 폐기합니다.**
|
|
39
|
+
|
|
40
|
+
- 작업 폴더가 Git 밖이면 `.axmap.json`(`{ "repo": "<경로>" }`) 수동 지정 → 하위 폴더 자동 탐색(모두 같은 저장소, worktree 포함) 순으로 대상을 정합니다. 서로 다른 저장소가 섞여 있으면 추측하지 않고 후보를 보여 줍니다. `axmap workspace show`로 판정 이유를 보고 `axmap workspace use <저장소>`로 기록합니다. 호출마다 다시 판정하므로 재시작이 필요 없습니다.
|
|
41
|
+
- 로컬 파이프가 사라진 관리형 세션은 대기 중이 아니라 `closed`로 표시합니다. 확인하지 못한 세션은 `liveness: unverified`로 구별하고 대기 중으로 보고하지 않습니다.
|
|
42
|
+
- 로컬 파이프에 연결조차 못 했거나 보내기 전에 거절된 요청은 `failed`로 기록해 다음 요청을 막지 않습니다. 이런 받은 편지함 묶음의 쪽지는 대기열로 돌아갑니다. 시간 초과와 연결 뒤 오류는 지금처럼 `unknown`이며 자동 재전송하지 않습니다.
|
|
43
|
+
- 팀 저장소에 axMap을 복사하던 방식(`tools/vendor.mjs` → `ci/axmap/`)을 폐기합니다. 커밋 훅은 사본으로 되돌아가지 않고, 사본만 남은 저장소에서는 `axmap hook install` 안내와 함께 멈춥니다. `axmap doctor`가 옛 훅을 표시합니다.
|
|
44
|
+
|
|
45
|
+
[결정 D30–D31 →](docs/DECISIONS.md)
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
34
49
|
### 1.7.0
|
|
35
50
|
|
|
36
51
|
<sub>2026.10.27</sub>
|
package/README.md
CHANGED
|
@@ -31,6 +31,21 @@ View pipeline state inside compatible MCP Apps hosts, or add it to the existing
|
|
|
31
31
|
|
|
32
32
|
Newest changes first.
|
|
33
33
|
|
|
34
|
+
### 1.9.0
|
|
35
|
+
|
|
36
|
+
<sub>October 29, 2026</sub>
|
|
37
|
+
|
|
38
|
+
**Find the repository from a workspace folder, report dead local sessions, and retire in-repository copies of axMap.**
|
|
39
|
+
|
|
40
|
+
- When the working folder is not inside Git, axMap picks the target repository in this order: a manual `.axmap.json` (`{ "repo": "<path>" }`), then automatic discovery of child folders that all belong to one repository (worktrees included). If child folders belong to different repositories, axMap does not guess; it lists the candidates. `axmap workspace show` explains the choice and `axmap workspace use <repo>` records it. The choice is re-evaluated on every call, so no restart is needed.
|
|
41
|
+
- Managed sessions whose local pipe no longer exists are reported as `closed` instead of idle. Sessions that cannot be checked are marked `liveness: unverified` and are not reported as idle.
|
|
42
|
+
- Requests that could not even connect to the local pipe, or were rejected before sending, are recorded as `failed` and no longer block the next request. Mailbox batches in that state return their messages to the queue. Timeouts and errors after connecting remain `unknown` and are never retried automatically.
|
|
43
|
+
- Copying axMap into a team repository (`tools/vendor.mjs` → `ci/axmap/`) is retired. Commit hooks no longer fall back to such a copy; if only a copy is present, the hook stops with instructions to run `axmap hook install`. `axmap doctor` flags old hooks.
|
|
44
|
+
|
|
45
|
+
[Decisions D30–D31 →](docs/DECISIONS.md)
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
34
49
|
### 1.7.0
|
|
35
50
|
|
|
36
51
|
<sub>October 27, 2026</sub>
|
package/bin/axmap.mjs
CHANGED
|
@@ -36,7 +36,6 @@ import { auditLedger, formatAudit, planAudit, snapshotTime } from '../src/invari
|
|
|
36
36
|
import {
|
|
37
37
|
PACKAGE_NAME,
|
|
38
38
|
fetchLatest,
|
|
39
|
-
isVendored,
|
|
40
39
|
checkDisabled,
|
|
41
40
|
readCache,
|
|
42
41
|
writeCache,
|
|
@@ -48,11 +47,27 @@ import { homeRegistrations } from '../src/mcpstate.mjs'
|
|
|
48
47
|
import { compareBranchFor, formatOverlap } from '../src/overlap.mjs'
|
|
49
48
|
import { withRepoLock } from '../src/repolock.mjs'
|
|
50
49
|
import { readProfile, syncConfiguredProfile } from '../src/identity.mjs'
|
|
50
|
+
import { inspectTarget, ambiguityMessage, describeTarget, writeWorkspaceChoice } from '../src/workspace.mjs'
|
|
51
51
|
|
|
52
52
|
/** 이 CLI 가 들어 있는 axMap 폴더. 갱신 확인과 setup 이 자기 위치를 알아야 한다. */
|
|
53
53
|
const SELF_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
54
54
|
|
|
55
|
-
/**
|
|
55
|
+
/**
|
|
56
|
+
* 이 폴더가 axMap 원본(소스 저장소 또는 npm 꾸러미)인가. `package.json` 의 이름으로 가른다.
|
|
57
|
+
*
|
|
58
|
+
* 팀 저장소 안에 복사해 두던 옛 사본(`ci/axmap` 등)에는 `package.json` 이 없었다.
|
|
59
|
+
* 그래서 훅이 "되돌아가도 되는 자리" 와 "걷어낸 사본" 을 이것 하나로 가른다 (D31).
|
|
60
|
+
* 못 읽으면 `false` — 원본이라고 판정하지 못했으면 원본으로 쓰지 않는다.
|
|
61
|
+
*/
|
|
62
|
+
function isAxmapSourceRoot(dir) {
|
|
63
|
+
try {
|
|
64
|
+
return JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8'))?.name === PACKAGE_NAME
|
|
65
|
+
} catch {
|
|
66
|
+
return false
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** 지금 도는 axMap 의 버전. 못 읽으면 `null` — 판을 지어내지 않는다. */
|
|
56
71
|
function selfVersion() {
|
|
57
72
|
try {
|
|
58
73
|
return JSON.parse(fs.readFileSync(path.join(SELF_ROOT, 'package.json'), 'utf8'))?.version ?? null
|
|
@@ -523,8 +538,8 @@ function markBusSeen(root, me, id) {
|
|
|
523
538
|
/**
|
|
524
539
|
* 쪽지를 읽는 명령. **경로를 문자열로 적지 않고 계산한다.**
|
|
525
540
|
*
|
|
526
|
-
* 🔴 `node tools/bus.mjs …` 라고 적혀 있었다.
|
|
527
|
-
* 틀린다** —
|
|
541
|
+
* 🔴 `node tools/bus.mjs …` 라고 적혀 있었다. axMap 저장소에서는 맞고 **설치본이나
|
|
542
|
+
* 다른 저장소에서는 틀린다** — 그 파일은 도구가 놓인 자리에 있다.
|
|
528
543
|
* 안내대로 치면 `MODULE_NOT_FOUND` 가 난다. 알림이 "쪽지가 있다" 고 말한 직후에
|
|
529
544
|
* 읽는 방법을 틀리게 알려주는 것이라, 받은 사람은 도구가 고장 났다고 결론짓는다.
|
|
530
545
|
*/
|
|
@@ -2396,15 +2411,24 @@ function cmdDoctor(flags) {
|
|
|
2396
2411
|
const hookAbs = path.isAbsolute(hookPath) ? hookPath : path.join(root, hookPath)
|
|
2397
2412
|
if (fs.existsSync(hookAbs) && fs.readFileSync(hookAbs, 'utf8').includes('axmap')) {
|
|
2398
2413
|
// 훅은 절대 경로를 담는다. 폴더 이름이 바뀌면 사라진 곳을 가리킨다.
|
|
2399
|
-
// 다만 요즘 훅은 그 경로가 없을 때 저장소
|
|
2400
|
-
// 그래서 박아둔 경로만 보고 빨간불을 켜면 **거짓 경보**가 된다.
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2414
|
+
// 다만 요즘 훅은 그 경로가 없을 때 axMap 저장소 자신(<루트>/bin)을 한 번 더 본다
|
|
2415
|
+
// (cmdHookInstall 참조). 그래서 박아둔 경로만 보고 빨간불을 켜면 **거짓 경보**가 된다.
|
|
2416
|
+
//
|
|
2417
|
+
// 🔴 저장소 안 사본(ci/axmap · axmap)은 2026-10-29 에 걷어냈다 (DECISIONS D31).
|
|
2418
|
+
// 그 전에 심은 훅은 박아둔 경로가 없으면 그 사본으로 되돌아가 **옛 규칙으로**
|
|
2419
|
+
// 판정한다. 겉으로는 커밋이 잘 되므로 여기서 말하지 않으면 아무도 모른다.
|
|
2420
|
+
const hookText = fs.readFileSync(hookAbs, 'utf8')
|
|
2421
|
+
const target = (hookText.match(/"([^"]+axmap\.mjs)"/) ?? [])[1]
|
|
2422
|
+
// 옛 훅은 둘 중 하나다: 사본을 직접 박았거나(`node ci/axmap/bin/axmap.mjs hook install`),
|
|
2423
|
+
// 되돌아갈 자리 목록(`for p in ...`)에 사본을 넣었다. 지금 훅도 "사본이 있으면 막는다"
|
|
2424
|
+
// 는 검사에 같은 경로 글자를 쓰므로, 글자가 들어 있다는 것만으로는 가르지 않는다.
|
|
2425
|
+
const pinsCopy = /[\\/](ci[\\/])?axmap[\\/]bin[\\/]axmap\.mjs$/.test(target ?? '') && !isAxmapSourceRoot(path.resolve(target, '..', '..'))
|
|
2426
|
+
const legacy = pinsCopy || /for p in "\$ROOT\/ci\/axmap\/bin\/axmap\.mjs"/.test(hookText)
|
|
2427
|
+
const selfFallback = isAxmapSourceRoot(root) && fs.existsSync(path.join(root, 'bin', 'axmap.mjs'))
|
|
2428
|
+
if (legacy) no('커밋 훅', '옛 훅입니다 — 저장소 안 axMap 사본(ci/axmap)을 부르거나 그리로 되돌아갑니다.\n 사본은 옛 규칙으로 판정하고, axMap 은 더 이상 사본을 쓰지 않습니다.\n axmap hook install 로 다시 심으세요')
|
|
2429
|
+
else if (!target || fs.existsSync(target)) ok('커밋 훅', '심겨 있습니다')
|
|
2430
|
+
else if (selfFallback) ok('커밋 훅', '심겨 있습니다 (박아둔 경로 대신 bin/axmap.mjs 를 씁니다)')
|
|
2431
|
+
else no('커밋 훅', `가리키는 파일이 없습니다: ${target}\n axmap hook install 로 다시 심으세요`)
|
|
2408
2432
|
} else {
|
|
2409
2433
|
no('커밋 훅', `없습니다 — claim 하지 않은 파일도 그냥 커밋됩니다\n axmap hook install`)
|
|
2410
2434
|
}
|
|
@@ -2456,15 +2480,11 @@ function cmdDoctor(flags) {
|
|
|
2456
2480
|
// doctor 는 "지금 도는가" 를 재는 자리이고, 레지스트리를 기다리게 만들면
|
|
2457
2481
|
// 네트워크가 느린 날 점검 자체가 안 끝난다.
|
|
2458
2482
|
const ver = selfVersion()
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
if (notice) hm('새 버전', notice.split('\n').join('\n '))
|
|
2465
|
-
else if (cache) ok('버전', `${ver ?? '(모름)'} — 최신입니다 (확인: ${new Date(cache.checkedAt).toISOString()})`)
|
|
2466
|
-
else ok('버전', `${ver ?? '(모름)'} — 새 버전이 있는지 물어본 적이 없습니다 (axmap update)`)
|
|
2467
|
-
}
|
|
2483
|
+
const cache = readCache()
|
|
2484
|
+
const notice = noticeLine(ver, cache?.latest)
|
|
2485
|
+
if (notice) hm('새 버전', notice.split('\n').join('\n '))
|
|
2486
|
+
else if (cache) ok('버전', `${ver ?? '(모름)'} — 최신입니다 (확인: ${new Date(cache.checkedAt).toISOString()})`)
|
|
2487
|
+
else ok('버전', `${ver ?? '(모름)'} — 새 버전이 있는지 물어본 적이 없습니다 (axmap update)`)
|
|
2468
2488
|
|
|
2469
2489
|
return finishDoctor(lines, bad, warn, flags)
|
|
2470
2490
|
}
|
|
@@ -2498,57 +2518,55 @@ function cmdHookInstall() {
|
|
|
2498
2518
|
* 🔴 박아둔 경로를 **1순위로 두되 유일한 길로 두지 않는다.**
|
|
2499
2519
|
*
|
|
2500
2520
|
* 위 주석의 의도(바깥 설치 대응)는 옳다. 그래서 `self` 를 그대로 첫 번째로 둔다.
|
|
2501
|
-
*
|
|
2502
|
-
*
|
|
2503
|
-
*
|
|
2504
|
-
* 문제는 사라졌다는 사실이 아니라 **죽는 방식**이다. 훅은 "검사 실패" 가 아니라
|
|
2505
|
-
* `MODULE_NOT_FOUND` 로 죽고, 0 이 아닌 종료 코드는 곧 커밋 차단이다. 즉
|
|
2506
|
-
* **선점과 무관한 커밋까지 전부 막힌다.** 막히면 사람은 계속 일해야 하므로
|
|
2507
|
-
* `--no-verify` 를 쓰기 시작하고, **그 습관은 훅이 고쳐진 뒤에도 남는다.**
|
|
2508
|
-
* 지금의 고장보다 그것이 비싸다.
|
|
2521
|
+
* 박아둔 경로가 사라지면 훅은 "검사 실패" 가 아니라 `MODULE_NOT_FOUND` 로 죽고,
|
|
2522
|
+
* 0 이 아닌 종료 코드는 곧 커밋 차단이다 — **선점과 무관한 커밋까지 전부 막힌다.**
|
|
2523
|
+
* 막히면 사람은 `--no-verify` 를 쓰기 시작하고, 그 습관은 훅이 고쳐진 뒤에도 남는다.
|
|
2509
2524
|
*
|
|
2510
|
-
* 그래서
|
|
2511
|
-
* (2026-08-27 · `git cat-file -e <브랜치>:<경로>`):
|
|
2525
|
+
* 그래서 둘을 차례로 본다.
|
|
2512
2526
|
*
|
|
2513
|
-
* 1. 박아둔 절대 경로 (바깥 설치)
|
|
2514
|
-
* 2. <루트>/
|
|
2515
|
-
* 3. <루트>/axmap/bin chore/axmap-bootstrap · backup/*
|
|
2516
|
-
* 4. <루트>/bin back/dev · front/dev ← 옛 배치
|
|
2527
|
+
* 1. 박아둔 절대 경로 (npm 설치본 · 바깥 설치)
|
|
2528
|
+
* 2. <루트>/bin/axmap.mjs **axMap 저장소 자신일 때만** (폴더를 옮긴 경우)
|
|
2517
2529
|
*
|
|
2518
|
-
* 🔴
|
|
2519
|
-
*
|
|
2520
|
-
*
|
|
2530
|
+
* 🔴 **저장소 안 사본(`ci/axmap` · `axmap`)은 보지 않는다 (2026-10-29, DECISIONS D31).**
|
|
2531
|
+
* 예전에는 그 둘도 되돌아갈 자리였다. 그런데 사본은 만든 날의 규칙에 멈춰 있어서,
|
|
2532
|
+
* 박아둔 경로가 사라진 날 훅이 **옛 규칙으로 판정**했다 (GIT-AS-DB 사고 3).
|
|
2533
|
+
* axMap 은 이제 장부와 코드를 한 곳에만 둔다. 2번이 "axMap 저장소 자신" 인지는
|
|
2534
|
+
* `package.json` 의 꾸러미 이름으로 가른다 — 팀 저장소의 옛 `bin/` 사본에는 그
|
|
2535
|
+
* 파일이 없다.
|
|
2521
2536
|
*
|
|
2522
|
-
* 🔴
|
|
2537
|
+
* 🔴 사본은 있는데 진짜 도구를 못 찾으면 **막는다(exit 1).** 사본으로 되돌아가던
|
|
2538
|
+
* 사람에게 그냥 통과시키면 어제까지 막히던 커밋이 오늘 조용히 통과한다 — 판정을
|
|
2539
|
+
* 못 했는데 통과시키는 것이다. 무엇을 하면 되는지(`hook install`)를 같이 말한다.
|
|
2523
2540
|
*
|
|
2524
|
-
*
|
|
2525
|
-
*
|
|
2526
|
-
*
|
|
2527
|
-
*
|
|
2528
|
-
* `ax_inbox` 의 "없는 것과 못 읽은 것은 다르다" 와 같은 결이다.
|
|
2529
|
-
*
|
|
2530
|
-
* ⚠ 다만 **CI 가 대신 잡아 준다고 말하지 않는다.** 팀 저장소의 `claims` 잡은
|
|
2531
|
-
* `merge_request_event` 에서만 돌고 `changes:` 목록에 든 폴더만 본다.
|
|
2532
|
-
* dev 브랜치 직접 push 와 목록 밖 폴더는 그물 밖이다. 그래서 여기서 할 수
|
|
2533
|
-
* 있는 것은 **조용히 넘어가지 않는 것**뿐이고, 문구는 그 사실만 말한다.
|
|
2534
|
-
* (그물을 넓히는 것은 `.gitlab-ci.yml` 쪽 일이라 여기서 처리하지 않는다.)
|
|
2541
|
+
* 🔴 사본도 도구도 없으면 **시끄럽게 경고하고 통과시킨다(exit 0).** 이건 설치 문제다.
|
|
2542
|
+
* 설치 문제로 선점과 무관한 커밋까지 전부 막으면 위의 `--no-verify` 습관이 생긴다.
|
|
2543
|
+
* ⚠ 다만 **CI 가 대신 잡아 준다고 말하지 않는다.** 여기서 할 수 있는 것은
|
|
2544
|
+
* **조용히 넘어가지 않는 것**뿐이고, 문구는 그 사실만 말한다.
|
|
2535
2545
|
*/
|
|
2536
2546
|
const body = `#!/bin/sh
|
|
2537
2547
|
# axMap - claim 하지 않은 파일의 커밋을 막는다.
|
|
2538
|
-
#
|
|
2539
|
-
# (ci/axmap
|
|
2548
|
+
# 박아둔 경로가 없으면 axMap 저장소 자신(<루트>/bin)만 한 번 더 본다.
|
|
2549
|
+
# 저장소 안 사본(ci/axmap)은 쓰지 않는다. 자세한 근거는 bin/axmap.mjs 의 cmdHookInstall.
|
|
2540
2550
|
AX="${self}"
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
if [ -f "$p" ]; then AX="$p"; break; fi
|
|
2545
|
-
done
|
|
2551
|
+
ROOT=$(git rev-parse --show-toplevel 2>/dev/null) || ROOT=""
|
|
2552
|
+
if [ ! -f "$AX" ] && [ -n "$ROOT" ] && [ -f "$ROOT/bin/axmap.mjs" ] && grep -q '"name": *"${PACKAGE_NAME}"' "$ROOT/package.json" 2>/dev/null; then
|
|
2553
|
+
AX="$ROOT/bin/axmap.mjs"
|
|
2546
2554
|
fi
|
|
2547
2555
|
if [ ! -f "$AX" ]; then
|
|
2556
|
+
if [ -n "$ROOT" ] && { [ -f "$ROOT/ci/axmap/bin/axmap.mjs" ] || [ -f "$ROOT/axmap/bin/axmap.mjs" ]; }; then
|
|
2557
|
+
echo "" >&2
|
|
2558
|
+
echo " !! axMap: 이 저장소 안의 axMap 사본(ci/axmap)은 더 이상 쓰지 않습니다." >&2
|
|
2559
|
+
echo " 사본은 옛 규칙으로 판정하므로 검사를 맡기지 않았고, 이 커밋을 멈췄습니다." >&2
|
|
2560
|
+
echo " 고치려면 - 이 저장소에서 axmap hook install" >&2
|
|
2561
|
+
echo " (axmap 명령이 없으면 먼저 npm install -g ${PACKAGE_NAME}@latest)" >&2
|
|
2562
|
+
echo "" >&2
|
|
2563
|
+
exit 1
|
|
2564
|
+
fi
|
|
2548
2565
|
echo "" >&2
|
|
2549
2566
|
echo " !! axMap: 검사 도구를 찾지 못했습니다." >&2
|
|
2550
2567
|
echo " 이 커밋은 선점 검사를 거치지 않았습니다." >&2
|
|
2551
|
-
echo " 고치려면 -
|
|
2568
|
+
echo " 고치려면 - 이 저장소에서 axmap hook install" >&2
|
|
2569
|
+
echo " (axmap 명령이 없으면 먼저 npm install -g ${PACKAGE_NAME}@latest)" >&2
|
|
2552
2570
|
echo "" >&2
|
|
2553
2571
|
exit 0
|
|
2554
2572
|
fi
|
|
@@ -2600,14 +2618,10 @@ exec node "$AX" verify
|
|
|
2600
2618
|
* 🔴 **import 하지 않고 spawn 한다.** 이유가 둘이다.
|
|
2601
2619
|
*
|
|
2602
2620
|
* 1. `tools/setup.mjs` 는 설치기를 부르느라 `src/agentcli.mjs` 계열까지
|
|
2603
|
-
* 끌고 온다.
|
|
2604
|
-
* import 그래프에 잡혀 **팀 저장소의 `ci/axmap/` 사본이 통째로 불어난다.**
|
|
2605
|
-
* 사본은 CI 가 판정에 쓰는 파일만 담기로 한 자리다.
|
|
2621
|
+
* 끌고 온다. static import 를 걸면 CLI 를 띄울 때마다 그 전부를 읽는다.
|
|
2606
2622
|
* 2. 설치는 판정이 아니다. 실패해도 CLI 의 나머지가 멈출 이유가 없다.
|
|
2607
2623
|
*
|
|
2608
|
-
*
|
|
2609
|
-
* 무엇을 대신 하면 되는지를 말한다 — 이 저장소가 `tools/bus.mjs` 로 한 번 겪은
|
|
2610
|
-
* 실패다(사본에 빠져 있는데 오류만 났다).
|
|
2624
|
+
* 파일이 없을 때는 조용히 실패하지 않고, 꾸러미가 온전하지 않다고 말한다.
|
|
2611
2625
|
*/
|
|
2612
2626
|
/**
|
|
2613
2627
|
* 딸린 프로그램들. **이름을 경로로 바꿔 주는 것이 전부다.**
|
|
@@ -2617,6 +2631,7 @@ exec node "$AX" verify
|
|
|
2617
2631
|
* **npm 으로 설치하면 그 경로가 기계마다 다르다.** 그래서 팀의 CI 설정과
|
|
2618
2632
|
* 슬래시 명령이 경로를 박아 둘 수밖에 없었고, 경로가 박혀 있는 한 사본을
|
|
2619
2633
|
* 지울 수 없었다. 여기 이름이 하나 생길 때마다 박힌 경로가 하나 사라진다.
|
|
2634
|
+
* 이 이름들이 자리를 잡은 뒤 사본은 2026-10-29 에 걷어냈다 (DECISIONS D31).
|
|
2620
2635
|
*
|
|
2621
2636
|
* 값은 [파일, 한 줄 설명] 이다. 설명은 HELP 와 오류 메시지가 같이 쓴다 —
|
|
2622
2637
|
* 두 군데 적으면 한 쪽만 고쳐지는 날이 온다.
|
|
@@ -2643,11 +2658,7 @@ function cmdRun(name) {
|
|
|
2643
2658
|
if (!fs.existsSync(script)) {
|
|
2644
2659
|
die(
|
|
2645
2660
|
`이 axMap 에는 ${name}(${what})이 들어 있지 않습니다: ${script}\n\n` +
|
|
2646
|
-
|
|
2647
|
-
? ` 여기는 팀 저장소 안의 **벤더링된 사본**입니다(옆에 SOURCE.json 이 있습니다).\n` +
|
|
2648
|
-
` 사본에는 CI 가 판정에 쓰는 파일만 들어 있습니다.\n\n` +
|
|
2649
|
-
` 배포판으로 부르세요:\n npx -y ${PACKAGE_NAME}@latest ${name}`
|
|
2650
|
-
: ` 꾸러미가 온전하지 않습니다. 다시 받으세요:\n npx -y ${PACKAGE_NAME}@latest ${name}`),
|
|
2661
|
+
` 꾸러미가 온전하지 않습니다. 다시 받으세요:\n npx -y ${PACKAGE_NAME}@latest ${name}`,
|
|
2651
2662
|
)
|
|
2652
2663
|
}
|
|
2653
2664
|
// 인자는 그대로 넘긴다. 여기서 다시 해석하면 두 곳의 플래그 목록이 어긋난다.
|
|
@@ -2690,20 +2701,6 @@ function cmdRun(name) {
|
|
|
2690
2701
|
async function cmdUpdate(flags) {
|
|
2691
2702
|
const current = selfVersion()
|
|
2692
2703
|
|
|
2693
|
-
// 사본은 npm 이 아니라 tools/vendor.mjs 가 갱신한다. 여기서 레지스트리를 치면
|
|
2694
|
-
// 팀 CI 가 매번 바깥 네트워크에 의존하게 되고, 안내를 받은 팀원이 할 수 있는
|
|
2695
|
-
// 일도 없다 (사본은 axMap 저장소에서 vendor.mjs 를 다시 돌려야 바뀐다).
|
|
2696
|
-
if (isVendored(SELF_ROOT)) {
|
|
2697
|
-
console.log('')
|
|
2698
|
-
console.log('여기는 팀 저장소 안의 벤더링된 사본입니다 (옆에 SOURCE.json 이 있습니다).')
|
|
2699
|
-
console.log('사본은 npm 이 아니라 axMap 저장소의 tools/vendor.mjs 가 갱신합니다.')
|
|
2700
|
-
console.log('')
|
|
2701
|
-
console.log(' 사본이 온전한지: node ci/verify-vendor.mjs')
|
|
2702
|
-
console.log(' 어느 커밋의 사본인지: ci/axmap/SOURCE.json')
|
|
2703
|
-
console.log('')
|
|
2704
|
-
process.exit(0)
|
|
2705
|
-
}
|
|
2706
|
-
|
|
2707
2704
|
const registry = registryUrl()
|
|
2708
2705
|
console.log('')
|
|
2709
2706
|
console.log(`지금 버전: ${current ?? '(모름)'}`)
|
|
@@ -2774,8 +2771,8 @@ async function cmdUpdate(flags) {
|
|
|
2774
2771
|
* 출력이 **돌리는 사람의 PC 상태에 따라 달라지고**, 그러면 시험이 사람마다
|
|
2775
2772
|
* 다른 답을 낸다 (실제로 그렇게 깨졌다).
|
|
2776
2773
|
*
|
|
2777
|
-
* 🔴 못 읽으면 `null` 을 돌려 **아무 말도 안 한다.** 사본을 안 쓰는 설치도
|
|
2778
|
-
*
|
|
2774
|
+
* 🔴 못 읽으면 `null` 을 돌려 **아무 말도 안 한다.** 사본을 안 쓰는 설치도 있고,
|
|
2775
|
+
* 없는 것을 낡았다고
|
|
2779
2776
|
* 말하면 그 경고는 곧 아무도 안 읽는 잡음이 된다.
|
|
2780
2777
|
*/
|
|
2781
2778
|
function installedAppVersion() {
|
|
@@ -2798,13 +2795,69 @@ function installedAppVersion() {
|
|
|
2798
2795
|
* 글을 섞으면 그 출력을 파싱하는 쪽이 조용히 깨진다.
|
|
2799
2796
|
*/
|
|
2800
2797
|
function warnIfStale() {
|
|
2801
|
-
if (checkDisabled()
|
|
2798
|
+
if (checkDisabled()) return
|
|
2802
2799
|
const cache = readCache()
|
|
2803
2800
|
if (!cache) return
|
|
2804
2801
|
const notice = noticeLine(selfVersion(), cache.latest)
|
|
2805
2802
|
if (notice) console.error(`\n${notice}\n`)
|
|
2806
2803
|
}
|
|
2807
2804
|
|
|
2805
|
+
// ---------------------------------------------------------------------------
|
|
2806
|
+
// 명령: workspace (Git 저장소가 아닌 작업 폴더에서 대상 저장소를 보고 정한다)
|
|
2807
|
+
// ---------------------------------------------------------------------------
|
|
2808
|
+
|
|
2809
|
+
/**
|
|
2810
|
+
* 장부를 쓰는 명령은 Git 저장소 안에서 돈다. 터미널이 여러 저장소(worktree)를 담은
|
|
2811
|
+
* 작업 폴더에서 열렸으면 MCP 서버·로컬 세션 연결과 **같은 규칙**(src/workspace.mjs)으로
|
|
2812
|
+
* 대상 저장소를 정하고 그 최상위로 옮겨서 실행한다. 선점 경로는 저장소 기준 경로다.
|
|
2813
|
+
*
|
|
2814
|
+
* 🔴 빠른 길: 지금 폴더나 조상에 `.git` 이 있으면 git 을 한 번도 더 띄우지 않고
|
|
2815
|
+
* 지금까지와 똑같이 돈다. 거의 모든 명령이 여기를 지나므로 Git 안의 사용자에게
|
|
2816
|
+
* 비용을 늘리지 않는다 (ledgerlock 테스트의 시간 창이 그 비용에 민감하다).
|
|
2817
|
+
*/
|
|
2818
|
+
const WORKSPACE_COMMANDS = new Set(['init', 'bus-repair', 'users-repair', 'claim', 'release', 'renew', 'status', 'audit', 'doctor', 'hook', 'bus'])
|
|
2819
|
+
|
|
2820
|
+
function insideGitMarker(dir) {
|
|
2821
|
+
for (let at = path.resolve(dir); ; at = path.dirname(at)) {
|
|
2822
|
+
if (fs.existsSync(path.join(at, '.git'))) return true
|
|
2823
|
+
if (path.dirname(at) === at) return false
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
|
|
2827
|
+
function enterWorkspaceTarget(name) {
|
|
2828
|
+
if (!WORKSPACE_COMMANDS.has(name) || process.env.GIT_DIR || insideGitMarker(process.cwd())) return
|
|
2829
|
+
let r
|
|
2830
|
+
try { r = inspectTarget(process.cwd()) } catch (e) { die(e.message) }
|
|
2831
|
+
if (r.kind === 'ambiguous') die(ambiguityMessage(r))
|
|
2832
|
+
if (r.kind !== 'manual' && r.kind !== 'auto') return // 저장소 없음 → 기존 안내(repoRoot)
|
|
2833
|
+
console.error(`axMap: 작업 폴더가 Git 저장소가 아니라 ${r.kind === 'manual' ? '수동 설정' : '자동 탐색'}으로 ${r.repo} 를 대상으로 합니다.`)
|
|
2834
|
+
process.chdir(r.repo)
|
|
2835
|
+
}
|
|
2836
|
+
|
|
2837
|
+
function cmdWorkspace(rest, flags) {
|
|
2838
|
+
const [sub, target] = rest
|
|
2839
|
+
if (sub === 'show') {
|
|
2840
|
+
let r
|
|
2841
|
+
try { r = inspectTarget(target ? path.resolve(target) : process.cwd()) } catch (e) { die(e.message) }
|
|
2842
|
+
if (boolFlag(flags.json, 'json')) {
|
|
2843
|
+
const out = { kind: r.kind, from: r.from, repo: r.repo ?? null, config: r.config ?? null,
|
|
2844
|
+
candidates: r.kind === 'ambiguous' ? r.groups.map(g => ({ repo: g.target, folders: g.members.map(m => m.name) }))
|
|
2845
|
+
: r.kind === 'auto' ? [{ repo: r.group.target, folders: r.group.members.map(m => m.name) }] : [] }
|
|
2846
|
+
console.log(JSON.stringify(out, null, 2))
|
|
2847
|
+
} else console.log(describeTarget(r))
|
|
2848
|
+
// 모호함은 "아직 정하지 않았다" 는 결과라 성공으로 세지 않는다.
|
|
2849
|
+
process.exit(r.kind === 'ambiguous' ? 2 : 0)
|
|
2850
|
+
}
|
|
2851
|
+
if (sub === 'use') {
|
|
2852
|
+
if (!target) die('사용법: axmap workspace use <저장소 폴더>')
|
|
2853
|
+
let w
|
|
2854
|
+
try { w = writeWorkspaceChoice(process.cwd(), target) } catch (e) { die(e.message) }
|
|
2855
|
+
console.log(`${w.config} 에 저장했습니다: { "repo": "${w.value}" }\n대상 저장소: ${w.repo}\nMCP 서버는 다음 도구 호출부터 따릅니다. 다시 켤 필요가 없습니다.`)
|
|
2856
|
+
return
|
|
2857
|
+
}
|
|
2858
|
+
die('사용법: axmap workspace show [폴더] [--json] | axmap workspace use <저장소 폴더>')
|
|
2859
|
+
}
|
|
2860
|
+
|
|
2808
2861
|
// ---------------------------------------------------------------------------
|
|
2809
2862
|
|
|
2810
2863
|
const HELP = `axmap - AI 에이전트 작업 선점 프로토콜
|
|
@@ -2827,6 +2880,9 @@ const HELP = `axmap - AI 에이전트 작업 선점 프로토콜
|
|
|
2827
2880
|
[--checkpoint 통과하면 "여기까지 봤다" 를 남긴다]
|
|
2828
2881
|
axmap doctor 이 PC 에서 선점이 실제로 도는지 점검 [--json]
|
|
2829
2882
|
axmap hook install verify 를 pre-commit 훅으로 설치
|
|
2883
|
+
axmap workspace show [폴더] Git 저장소가 아닌 작업 폴더에서 어느 저장소를 겨누는지와 이유
|
|
2884
|
+
[--json] (수동 설정 · 자동 탐색 · 모호함 + 후보)
|
|
2885
|
+
axmap workspace use <저장소 폴더> 그 선택을 이 폴더의 .axmap.json 에 적는다
|
|
2830
2886
|
axmap update 새 버전이 있는지 묻는다 (바꾸지는 않는다)
|
|
2831
2887
|
axmap --version 지금 도는 판 번호를 찍는다 (아래 version 명령과 다르다)
|
|
2832
2888
|
|
|
@@ -2873,12 +2929,17 @@ const [cmd, ...rest] = positional
|
|
|
2873
2929
|
*/
|
|
2874
2930
|
if (boolFlag(flags.version, 'version') && !cmd) {
|
|
2875
2931
|
const v = selfVersion()
|
|
2876
|
-
if (!v) die('버전을 알 수 없습니다 - package.json 이
|
|
2932
|
+
if (!v) die('버전을 알 수 없습니다 - 이 axMap 옆에 package.json 이 없습니다.')
|
|
2877
2933
|
console.log(v)
|
|
2878
2934
|
process.exit(0)
|
|
2879
2935
|
}
|
|
2880
2936
|
|
|
2937
|
+
enterWorkspaceTarget(cmd)
|
|
2938
|
+
|
|
2881
2939
|
switch (cmd) {
|
|
2940
|
+
case 'workspace':
|
|
2941
|
+
cmdWorkspace(rest, flags)
|
|
2942
|
+
break
|
|
2882
2943
|
// 이름을 경로로 바꿔 주는 것들 (RUNNERS). 인자는 손대지 않고 그대로 간다.
|
|
2883
2944
|
case 'setup':
|
|
2884
2945
|
case 'bridge':
|
package/governance/vote.mjs
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* `agent.summary` AI 가 적는다 (`--agent-summary`)
|
|
20
20
|
* `agent.id` 부르는 쪽이 적는다 (`--agent`, 없으면 `"none"`)
|
|
21
21
|
* `agent.shown` 🔴 **아무도 못 적는다. 이 프로그램이 직접 잰다** —
|
|
22
|
-
* 변경량·경로·그 시점의
|
|
22
|
+
* 변경량·경로·그 시점의 판정.
|
|
23
23
|
* `committerEmail` 을 못 적게 한 것과 같은 이유다 (아래).
|
|
24
24
|
*
|
|
25
25
|
* GitLab 의 Approve 버튼을 쓰지 않는 이유는 [GOVERNANCE.md](GOVERNANCE.md) 와
|
|
@@ -77,36 +77,11 @@ import {
|
|
|
77
77
|
const VOTES_BRANCH = 'axmap/votes'
|
|
78
78
|
const VOTES_REL = path.join('.axmap', 'votes')
|
|
79
79
|
|
|
80
|
-
/** 이 파일이 놓인 자리.
|
|
80
|
+
/** 이 파일이 놓인 자리. 옆의 gate.mjs 를 부를 때 쓴다. */
|
|
81
81
|
const HERE = path.dirname(fileURLToPath(import.meta.url))
|
|
82
82
|
|
|
83
|
-
/**
|
|
84
|
-
* 사본이 놓이는 자리. **저장소 루트 기준**이고 `tools/vendor.mjs` 의 `VENDOR_REL`
|
|
85
|
-
* 과 같은 값이어야 한다. 여기가 갈리면 표에 적히는 출처가 조용히 비게 된다.
|
|
86
|
-
*/
|
|
87
|
-
const VENDOR_REL = 'ci/axmap'
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* 벤더링(**남의 저장소 코드를 내 저장소 안에 복사해 두는 것**) 출처가 적힌 파일을
|
|
91
|
-
* 찾는 순서.
|
|
92
|
-
*
|
|
93
|
-
* 🔴 **저장소 루트 쪽을 먼저 본다.** 이 칸이 말하는 것은 "어느 스크립트가 돌았나"
|
|
94
|
-
* 가 아니라 **"표를 던지는 이 저장소가 어느 사본을 쓰고 있나"** 이기 때문이다.
|
|
95
|
-
* 개발 중에 axMap 저장소의 스크립트를 팀 저장소에서 직접 돌리더라도, 표에는
|
|
96
|
-
* 그 팀 저장소의 사본 출처가 적혀야 한다.
|
|
97
|
-
*
|
|
98
|
-
* 둘 다 없으면 `{ commit: null, behind: null }` — axMap 저장소 자신에서 돌릴 때가
|
|
99
|
-
* 그렇다. 사본이 아니니 출처도 없다.
|
|
100
|
-
*/
|
|
101
|
-
const axmapSourceCandidates = () => [
|
|
102
|
-
path.join(ROOT ?? '.', ...VENDOR_REL.split('/'), 'SOURCE.json'),
|
|
103
|
-
path.join(HERE, '..', 'SOURCE.json'),
|
|
104
|
-
]
|
|
105
|
-
|
|
106
83
|
/** 게이트를 미리 한 번 돌려 보는 데 주는 시간. 넘기면 "잴 수 없음" 으로 적는다. */
|
|
107
84
|
const GATE_TIMEOUT_MS = 60_000
|
|
108
|
-
/** 원격에 axMap 이 몇 커밋 앞서 있는지 물어보는 데 주는 시간. */
|
|
109
|
-
const LS_REMOTE_TIMEOUT_MS = 15_000
|
|
110
85
|
|
|
111
86
|
/** push 가 거부될 때(= 누가 먼저 도착) 다시 해보는 횟수. bin/axmap.mjs 와 같은 값. */
|
|
112
87
|
const MAX_CAS_RETRIES = 5
|
|
@@ -240,7 +215,7 @@ const HELP = `표를 던진다 — 저장소 안의 파일 하나로
|
|
|
240
215
|
다만 판정문에는 반드시 보인다.
|
|
241
216
|
|
|
242
217
|
표에는 던질 때 실제로 보였던 것(agent.shown)이 함께 적힌다 — 변경량·경로·
|
|
243
|
-
그 시점의
|
|
218
|
+
그 시점의 판정. 🔴 이 칸은 아무도 못 적는다.
|
|
244
219
|
이 프로그램이 직접 재서 채운다.
|
|
245
220
|
`
|
|
246
221
|
|
|
@@ -687,44 +662,6 @@ function measureGate(branch, targetName, policyFlag) {
|
|
|
687
662
|
return `${verdict.ok ? '충족' : '미달'} ${verdict.approvals}/${verdict.threshold}`
|
|
688
663
|
}
|
|
689
664
|
|
|
690
|
-
/**
|
|
691
|
-
* 이 저장소에 복사돼 있는 axMap 이 **어느 커밋의 사본이고 몇 커밋 뒤처졌는가.**
|
|
692
|
-
*
|
|
693
|
-
* 🔴 못 닿으면 `behind: null` 이고 **표는 그대로 던져진다.** axMap 이 잠깐
|
|
694
|
-
* 죽거나 접근 권한이 만료된 날 팀의 투표가 멈추면 안 된다 — 사본을 두는
|
|
695
|
-
* 이유(벤더링)와 정확히 같은 이유다.
|
|
696
|
-
*
|
|
697
|
-
* 🔴 **여기서 fetch 하지 않는다.** 표를 던지는 명령이 남의 저장소를 통째로
|
|
698
|
-
* 내려받아 이 저장소의 오브젝트를 불리는 것은 아무도 예상하지 않는 부수효과다.
|
|
699
|
-
* 그래서 `ls-remote` 로 끝을 물어보고, 셀 수 있을 때만(= 그 이력이 이미 이
|
|
700
|
-
* 저장소에 있을 때만) 센다. 못 세면 `null` 이다.
|
|
701
|
-
* ⚠ 그 결과 "원격에 못 닿음" 과 "닿았지만 셀 수 없음" 이 둘 다 `null` 이다.
|
|
702
|
-
* `behind: null` 은 **"뒤처지지 않았다" 가 아니라 "못 쟀다"** 로 읽는다.
|
|
703
|
-
*/
|
|
704
|
-
function measureAxmap() {
|
|
705
|
-
let src = null
|
|
706
|
-
for (const p of axmapSourceCandidates()) {
|
|
707
|
-
try { src = JSON.parse(fs.readFileSync(p, 'utf8')); break } catch { /* 다음 자리 */ }
|
|
708
|
-
}
|
|
709
|
-
if (!src) return { commit: null, behind: null }
|
|
710
|
-
|
|
711
|
-
const commit = str(src?.source?.commit)
|
|
712
|
-
const url = str(src?.source?.repository)
|
|
713
|
-
const branch = str(src?.source?.branch) ?? 'main'
|
|
714
|
-
if (!commit) return { commit: null, behind: null }
|
|
715
|
-
if (!url) return { commit, behind: null }
|
|
716
|
-
|
|
717
|
-
const ls = git(['ls-remote', url, branch], { env: NO_PROMPT_ENV, timeout: LS_REMOTE_TIMEOUT_MS })
|
|
718
|
-
if (ls.code !== 0 || !ls.out) return { commit, behind: null }
|
|
719
|
-
const tip = ls.out.split('\n')[0].split('\t')[0].trim()
|
|
720
|
-
if (!/^[0-9a-f]{40}$/.test(tip)) return { commit, behind: null }
|
|
721
|
-
if (tip === commit) return { commit, behind: 0 }
|
|
722
|
-
|
|
723
|
-
const n = git(['rev-list', '--count', `${commit}..${tip}`])
|
|
724
|
-
if (n.code !== 0 || !/^\d+$/.test(n.out)) return { commit, behind: null }
|
|
725
|
-
return { commit, behind: Number(n.out) }
|
|
726
|
-
}
|
|
727
|
-
|
|
728
665
|
// ---------------------------------------------------------------------------
|
|
729
666
|
// 본체
|
|
730
667
|
// ---------------------------------------------------------------------------
|
|
@@ -886,7 +823,8 @@ const relPath = `votes/${branch}/${voter}-${sha.slice(0, 8)}.json`
|
|
|
886
823
|
const shown = {
|
|
887
824
|
...measureDiff(targetName, branch),
|
|
888
825
|
gate: measureGate(branch, targetName, str(flags.policy)),
|
|
889
|
-
axmap
|
|
826
|
+
// 🔴 예전에는 `axmap` 칸(저장소 안 사본의 출처 커밋·뒤처진 정도)도 적었다.
|
|
827
|
+
// 사본을 걷어낸 뒤(D31)로는 잴 것이 없다. 옛 표에 남은 그 칸은 판정에 안 쓰인다.
|
|
890
828
|
}
|
|
891
829
|
|
|
892
830
|
// 시각(`at`)은 위에서 한 번 정했다. CAS 재시도마다 바뀌면 같은 표가 매번 다른
|
|
@@ -940,8 +878,7 @@ for (let attempt = 1; attempt <= MAX_CAS_RETRIES; attempt++) {
|
|
|
940
878
|
// 무엇이 적혔는지 모르고, 모르는 칸은 아무도 안 읽는다.
|
|
941
879
|
console.log(
|
|
942
880
|
` 본 것: ${shown.files === null ? '못 쟀음' : `${shown.files}파일 +${shown.insertions}/-${shown.deletions}`}`
|
|
943
|
-
+ ` · 던지기 직전 판정: ${shown.gate}
|
|
944
|
-
+ ` · axMap 사본: ${shown.axmap.behind === null ? '뒤처짐 못 쟀음' : `${shown.axmap.behind}커밋 뒤`}`,
|
|
881
|
+
+ ` · 던지기 직전 판정: ${shown.gate}`,
|
|
945
882
|
)
|
|
946
883
|
if (voteValue === 'reject') {
|
|
947
884
|
console.log(' reject 는 정족수 계산에 들어가지 않습니다 — 판정문에 보이기만 합니다.')
|
package/mcp/README.md
CHANGED
|
@@ -148,6 +148,8 @@ CLI 를 부를 때마다 넘긴다. 앞 두 칸은 CLI 와 순서가 같아서,
|
|
|
148
148
|
|
|
149
149
|
서버는 도구를 부를 때마다 대상 저장소를 다시 정한다. 순서는 `AXMAP_REPO` → 도구의 절대 경로 `cwd` → 클라이언트 작업 폴더(roots) → 시작 폴더의 Git 루트다. 근거는 [DECISIONS.md의 D22](../docs/DECISIONS.md).
|
|
150
150
|
|
|
151
|
+
그 폴더가 Git 저장소가 아니면(예: 한 저장소의 worktree 여러 개를 담은 작업 폴더) `.axmap.json`의 `repo` 수동 설정 → 바로 아래 폴더의 저장소 자동 탐색(같은 저장소의 worktree뿐이면 메인 체크아웃) 순으로 정한다. 서로 다른 저장소가 둘 이상이면 고르지 않고 후보를 오류로 알린다. 도구의 `cwd`를 주거나 그 폴더에서 `axmap workspace use <저장소 폴더>`로 `.axmap.json`을 쓰면 서버를 다시 켜지 않아도 다음 호출부터 따른다. `axmap workspace show`는 지금 무엇을 왜 고르는지 보여 준다. 근거는 D30.
|
|
152
|
+
|
|
151
153
|
장부가 없는 repo도 첫 조회·선점 때 자동으로 준비한다. 기존 원격 장부가 있으면 이어 사용하고, 원격 접근 실패는 빈 장부로 바꾸지 않는다. roots가 여러 repo를 가리켜 대상을 고를 수 없으면 도구의 `cwd`를 지정한다. roots 미지원 클라이언트에서 실행 중 폴더를 바꿀 때도 `cwd`를 전달한다. 서로 다른 MCP 세션의 타겟은 독립적이다.
|
|
152
154
|
|
|
153
155
|
전역 등록인데 `AXMAP_REPO` 에 한 폴더를 박아 두면 **등록은 전역인데 대상은 한 곳으로
|
package/mcp/server.mjs
CHANGED
|
@@ -46,6 +46,7 @@ import path from 'node:path'
|
|
|
46
46
|
import readline from 'node:readline'
|
|
47
47
|
import { fileURLToPath } from 'node:url'
|
|
48
48
|
import { syncConfiguredProfile } from '../src/identity.mjs'
|
|
49
|
+
import { resolveTarget } from '../src/workspace.mjs'
|
|
49
50
|
|
|
50
51
|
const HERE = path.dirname(fileURLToPath(import.meta.url))
|
|
51
52
|
|
|
@@ -77,14 +78,26 @@ let rootsTask = null
|
|
|
77
78
|
let rootsGeneration = 0
|
|
78
79
|
let rootRequestId = 0
|
|
79
80
|
const pendingRoots = new Map()
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
/**
|
|
82
|
+
* 🔴 시작 폴더가 여러 저장소를 담은 작업 폴더라 모호하면 여기서 죽지 않는다.
|
|
83
|
+
* 서버가 떠야 도구 호출이 그 모호함과 고르는 방법을 에이전트에게 알려 줄 수 있다.
|
|
84
|
+
* 대상은 도구를 부를 때마다 다시 정하므로, 사람이 `.axmap.json` 을 쓰면 다음
|
|
85
|
+
* 호출부터 바로 따른다.
|
|
86
|
+
*/
|
|
87
|
+
let REPO
|
|
88
|
+
try { REPO = resolveRepo() } catch (e) {
|
|
89
|
+
REPO = process.cwd()
|
|
90
|
+
process.stderr.write(`axMap MCP: 대상 저장소를 아직 정하지 못했습니다.\n${e.message}\n`)
|
|
86
91
|
}
|
|
87
92
|
|
|
93
|
+
/**
|
|
94
|
+
* 폴더 → 대상 저장소. 위로 찾기(Git 안)를 먼저 하고, Git 밖이면 작업 폴더 규칙
|
|
95
|
+
* (`.axmap.json` 수동 설정 → 바로 아래 저장소 자동 탐색 → 모호하면 오류)을 쓴다.
|
|
96
|
+
* 규칙은 `src/workspace.mjs` 한 곳에 있다 — CLI·로컬 세션 연결과 같은 답을 내야 한다.
|
|
97
|
+
* 저장소가 없으면 null, 모호하거나 판정하지 못하면 예외다.
|
|
98
|
+
*/
|
|
99
|
+
function gitRoot(cwd) { return resolveTarget(cwd) }
|
|
100
|
+
|
|
88
101
|
/** Explicit pin > per-call cwd > client roots > launch cwd.
|
|
89
102
|
* A global desktop selection must never redirect another CLI session. */
|
|
90
103
|
function resolveRepo(args = {}) {
|
|
@@ -111,7 +124,9 @@ function resolveRepo(args = {}) {
|
|
|
111
124
|
return top
|
|
112
125
|
}))]
|
|
113
126
|
if (dirs.length === 1) return dirs[0]
|
|
114
|
-
|
|
127
|
+
// 시작 폴더는 여러 roots 중 하나를 고르는 근거로만 쓴다. 판정하지 못하면 근거가 없는 것이다.
|
|
128
|
+
let launched = null
|
|
129
|
+
try { launched = gitRoot(process.cwd()) } catch { /* 고를 근거 없음 → 아래 오류 */ }
|
|
115
130
|
if (dirs.includes(launched)) return launched
|
|
116
131
|
throw new Error('작업 저장소를 하나로 정할 수 없습니다. 도구의 cwd 에 작업 폴더 절대 경로를 지정하세요.')
|
|
117
132
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"//name": "🔴 npm 의 'axmap' 은 2022년부터 남이 쓰고 있는 다른 꾸러미다(leozin_ 의 Map 라이브러리, 1.1.2). 그래서 `npx axmap` 은 우리 것이 아니고, 누구에게도 그렇게 안내하면 안 된다. 2026-08-28 에 axmap-cli 로 정했다 — 스코프(@사용자/이름)를 쓰지 않는 이유는 스코프가 npm 계정 이름에 묶여서 나중에 팀이나 조직으로 옮길 때 이름이 통째로 바뀌기 때문이다. 사람이 치는 **명령** 이름은 그대로 axmap 이다 (아래 bin) — 꾸러미 이름과 명령 이름은 달라도 된다.",
|
|
3
3
|
"name": "axmap-cli",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.9.0",
|
|
5
5
|
"//private": "🔴 여기 있던 \"private\": true 를 2026-08-28 에 지웠다. 그 줄이 있는 동안 npm publish 는 거부됐다. 되돌리려면 다시 넣으면 되지만, 이미 올라간 버전은 그래도 안 사라진다 — npm 은 같은 번호를 덮어쓰지 못하고 삭제도 72시간 안에만 된다. 즉 이 줄을 되살리는 것은 '앞으로 안 올린다' 는 뜻이지 '올린 것을 없앤다' 는 뜻이 아니다.",
|
|
6
6
|
"//publishConfig": "axmap-cli 는 스코프가 없어서 기본이 이미 공개다. 즉 지금은 없어도 된다. 그래도 남겨 두는 이유: 나중에 @조직/axmap 처럼 스코프를 붙이는 날 이 줄이 없으면 publish 가 '유료 플랜이 필요하다'는 엉뚱한 말로 실패한다.",
|
|
7
7
|
"publishConfig": {
|