axmap-cli 0.0.1

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 (78) hide show
  1. package/.claude/commands/ax-done.md +10 -0
  2. package/.claude/commands/ax-setup.md +31 -0
  3. package/.claude/commands/ax-start.md +15 -0
  4. package/.claude/commands/ax-tell.md +14 -0
  5. package/.claude/commands/ax-update.md +19 -0
  6. package/.claude/commands/ax.md +13 -0
  7. package/CLAUDE.md +309 -0
  8. package/LICENSE +20 -0
  9. package/README.md +207 -0
  10. package/app/README.md +366 -0
  11. package/app/eval/edges.mjs +242 -0
  12. package/app/lib/adjacent.mjs +125 -0
  13. package/app/lib/agentcli.mjs +153 -0
  14. package/app/lib/analyze.mjs +1159 -0
  15. package/app/lib/cochange.mjs +421 -0
  16. package/app/lib/datanodes.mjs +127 -0
  17. package/app/lib/entry.mjs +192 -0
  18. package/app/lib/featuregraph.mjs +389 -0
  19. package/app/lib/features.mjs +645 -0
  20. package/app/lib/fetchrepo-run.mjs +37 -0
  21. package/app/lib/fetchrepo.mjs +164 -0
  22. package/app/lib/flow.mjs +1089 -0
  23. package/app/lib/ladder.mjs +387 -0
  24. package/app/lib/langs.mjs +630 -0
  25. package/app/lib/live.mjs +346 -0
  26. package/app/lib/llm.mjs +594 -0
  27. package/app/lib/newfile.mjs +126 -0
  28. package/app/lib/prdiff.mjs +651 -0
  29. package/app/lib/reveal.mjs +316 -0
  30. package/app/lib/roots.mjs +186 -0
  31. package/app/lib/scope.mjs +342 -0
  32. package/app/lib/session.mjs +389 -0
  33. package/app/lib/slots.mjs +233 -0
  34. package/app/lib/ssot.mjs +277 -0
  35. package/app/lib/teamview.mjs +962 -0
  36. package/app/lib/terms.ko.mjs +169 -0
  37. package/app/server.mjs +1959 -0
  38. package/app/web/shell.css +538 -0
  39. package/app/web/shell.html +197 -0
  40. package/app/web/shell.js +638 -0
  41. package/app/web/stage.js +347 -0
  42. package/app/web/words.js +85 -0
  43. package/bin/axmap.mjs +1918 -0
  44. package/governance/GOVERNANCE.md +433 -0
  45. package/governance/gate.mjs +526 -0
  46. package/governance/vote.mjs +501 -0
  47. package/mcp/README.md +254 -0
  48. package/mcp/SETUP-FOR-AI.md +186 -0
  49. package/mcp/install.ps1 +341 -0
  50. package/mcp/install.sh +339 -0
  51. package/mcp/server.mjs +969 -0
  52. package/package.json +48 -0
  53. package/src/closure.mjs +343 -0
  54. package/src/governance.mjs +839 -0
  55. package/src/invariants.mjs +226 -0
  56. package/src/mrtarget.mjs +284 -0
  57. package/src/promote.mjs +177 -0
  58. package/src/protocol.mjs +423 -0
  59. package/src/repotarget.mjs +81 -0
  60. package/src/update.mjs +177 -0
  61. package/src/version.mjs +186 -0
  62. package/tools/bus.mjs +520 -0
  63. package/tools/cluster-experiment.mjs +256 -0
  64. package/tools/cluster-sweep.mjs +226 -0
  65. package/tools/make-icon.mjs +108 -0
  66. package/tools/mcp-register.mjs +269 -0
  67. package/tools/mr-target.mjs +49 -0
  68. package/tools/persona-bench.mjs +362 -0
  69. package/tools/pick-repo.mjs +229 -0
  70. package/tools/promote.mjs +550 -0
  71. package/tools/reveal-demo.mjs +158 -0
  72. package/tools/run-tests.mjs +42 -0
  73. package/tools/setup.mjs +490 -0
  74. package/tools/shortcut.mjs +121 -0
  75. package/tools/smoke.mjs +166 -0
  76. package/tools/topicgraph.py +154 -0
  77. package/tools/vendor.mjs +382 -0
  78. package/tools/version.mjs +115 -0
@@ -0,0 +1,158 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 그래프 → 트리. `app/lib/reveal.mjs` 를 실제 저장소로 돌려 **화면 없이** 본다.
4
+ *
5
+ * 🔴 왜 따로 도구인가.
6
+ *
7
+ * 점진 공개의 주장은 "한꺼번에 다 보여주지 않으면 벽이 안 생긴다" 이고,
8
+ * 그 주장은 **숫자로만 확인된다** — 몇 번 눌러서 몇 개를 봤나, 한 번에
9
+ * 몇 개가 쏟아졌나, 자리가 안 바뀌었나. 브라우저에서는 이걸 셀 수 없다.
10
+ *
11
+ * 화면(app/web)은 다른 세션이 잡고 있다. 로직은 순수 함수라 화면 없이도
12
+ * 전부 돌릴 수 있으므로, 여기서 먼저 재고 화면은 그 뒤에 붙인다.
13
+ *
14
+ * 사용:
15
+ * node tools/reveal-demo.mjs <저장소> [--steps 5] [--width 100]
16
+ */
17
+
18
+ import fs from 'node:fs'
19
+ import path from 'node:path'
20
+
21
+ import { build, scan } from '../app/lib/analyze.mjs'
22
+ import { entryStarts } from '../app/lib/flow.mjs'
23
+ import { featureGraph } from '../app/lib/featuregraph.mjs'
24
+ import { commitFiles } from '../app/lib/live.mjs'
25
+ import {
26
+ callsOf, emptyReveal, importersOf, layers, preview, reachable, reveal, revealFeature,
27
+ } from '../app/lib/reveal.mjs'
28
+
29
+ const argv = process.argv.slice(2)
30
+ const flag = (name, dflt) => {
31
+ const i = argv.indexOf(`--${name}`)
32
+ return i >= 0 && argv[i + 1] ? argv[i + 1] : dflt
33
+ }
34
+ const ROOT = path.resolve(argv.find((a) => !a.startsWith('--') && argv[argv.indexOf(a) - 1]?.startsWith('--') !== true) ?? '.')
35
+ const STEPS = Number(flag('steps', 5))
36
+
37
+ const short = (p) => (p.length <= 26 ? p : `…${p.slice(-25)}`)
38
+
39
+ /** 층 하나를 한 줄로. 자리(col)가 곧 순서라 왼쪽부터 도착 순이다. */
40
+ function draw(state, opened) {
41
+ const rows = layers(state)
42
+ const out = []
43
+ for (const r of rows) {
44
+ const cells = r.cells.map((c) => {
45
+ const mark = opened.has(c.id) ? '▣' : '□'
46
+ return `${mark} ${short(c.id)}`
47
+ })
48
+ // 한 줄이 너무 길면 접는다 — 화면에서도 줄바꿈이 일어날 자리다.
49
+ const head = `층 ${String(r.depth).padStart(2)} │ `
50
+ const pad = ' '.repeat(head.length)
51
+ let line = head
52
+ const lines = []
53
+ for (const c of cells) {
54
+ if (line.length + c.length + 3 > 96) { lines.push(line); line = pad }
55
+ line += `${c} `
56
+ }
57
+ lines.push(line)
58
+ out.push(...lines.map((l) => l.replace(/\s+$/, '')))
59
+ }
60
+ return out.join('\n')
61
+ }
62
+
63
+ // ── 그래프를 만든다 (지금 화면이 그리는 것과 같은 재료) ────────────────────
64
+ const g = build(scan(ROOT))
65
+ const importers = importersOf(g.edges)
66
+ const calls = callsOf(g.edges)
67
+ const byId = new Map(g.nodes.map((n) => [n.id, n]))
68
+
69
+ console.log(`\n저장소 ${ROOT}`)
70
+ console.log(`━`.repeat(78))
71
+ console.log(`지금 그래프가 한 번에 그리는 것 : 노드 ${g.nodes.length}개 · 엣지 ${g.edges.length}개`)
72
+ console.log(` ← 이것이 "벽" 이다\n`)
73
+
74
+ // ── ① 진입점 하나로 시작 ──────────────────────────────────────────────────
75
+ const starts = entryStarts(ROOT, g.nodes, g.edges)
76
+ if (!starts.length) {
77
+ console.log('진입점을 못 찾았다. 이 저장소로는 시연할 수 없다.')
78
+ process.exit(1)
79
+ }
80
+ let st = reveal(emptyReveal(), [starts[0]], { why: '진입점', importers })
81
+ const opened = new Set()
82
+
83
+ console.log(`┌─ 0번째 ─ 시작`)
84
+ console.log(draw(st, opened))
85
+ let r = reachable(st, calls)
86
+ console.log(`└─ 보이는 것 ${r.shown}개 · 여기서 import 로 더 닿는 것 ${r.more}개\n`)
87
+
88
+ // ── ② 누른다. 누르기 **전에** 몇 개가 나올지 알려준다 ─────────────────────
89
+ for (let step = 1; step <= STEPS; step++) {
90
+ // 아직 안 펼친 것 중 가장 먼저 드러난 것부터 — 사람이 위에서부터 읽는 순서
91
+ const target = st.order.find((id) => !opened.has(id) && preview(st, id, calls).count > 0)
92
+ if (!target) {
93
+ console.log(`더 펼칠 것이 없다 (import 사슬이 ${step - 1}번 만에 말랐다).`)
94
+ console.log(`🔴 이래서 개념 축이 필요하다 — 아래 참조.\n`)
95
+ break
96
+ }
97
+ const pv = preview(st, target, calls)
98
+ // 🔴 예고는 **실제로 뜨는 수**여야 한다. `count` 는 후보 전체이고 한 번에
99
+ // 여는 것은 `ids` 다. 둘이 다른데 후보 수를 적으면 화면이 거짓말을 한다
100
+ // (개념 축에서 "고른 수와 는 수가 다르면 거짓말" 이라고 정한 것과 같다).
101
+ const say = pv.more ? `+${pv.ids.length}개 (후보 ${pv.count}개 중)` : `+${pv.ids.length}개`
102
+ console.log(`┌─ ${step}번째 ─ "${short(target)}" 를 누른다 (누르기 전 예고: ${say})`)
103
+ const before = { ...st.at }
104
+ st = reveal(st, pv.ids, { why: `${path.basename(target)} 가 부른다`, parent: target, importers })
105
+ opened.add(target)
106
+
107
+ // 🔴 불변식 확인 — 한 번 잡은 자리는 다시는 안 움직인다.
108
+ const moved = Object.keys(before).filter(
109
+ (k) => before[k].depth !== st.at[k].depth || before[k].col !== st.at[k].col)
110
+ console.log(draw(st, opened))
111
+ r = reachable(st, calls)
112
+ console.log(`└─ 보이는 것 ${r.shown}개 · 더 닿는 것 ${r.more}개 · 자리가 움직인 노드 ${moved.length}개`
113
+ + (moved.length ? ` ❌ 불변식 위반` : ` ✅`))
114
+ console.log()
115
+ }
116
+
117
+ // ── ③ 개념 축 — import 가 마르면 README 가 이름 붙인 묶음을 연다 ──────────
118
+ let readme = null
119
+ for (const f of ['README.md', 'README.rst', 'readme.md', 'README']) {
120
+ try { readme = fs.readFileSync(path.join(ROOT, f), 'utf8'); break } catch { /* 다음 후보 */ }
121
+ }
122
+ const { available, commits } = commitFiles(ROOT, { since: '10 years ago', maxCommits: 2000 })
123
+ const fg = featureGraph({
124
+ paths: g.nodes.map((n) => n.id),
125
+ commits: available ? commits : [],
126
+ readme,
127
+ edges: g.edges,
128
+ })
129
+ const feats = (fg.nodes ?? []).filter((n) => (n.paths ?? []).length > 1)
130
+ if (!feats.length) {
131
+ console.log('묶음을 못 찾았다 (README 에서 이름을 못 얻었거나 파일이 흩어져 있다).')
132
+ } else {
133
+ const f = feats.sort((a, b) => b.paths.length - a.paths.length)[0]
134
+ console.log(`┌─ 개념 축 ─ 묶음 "${f.name ?? f.id}" 를 누른다 (안에 ${f.paths.length}개)`)
135
+ const before = { ...st.at }
136
+ const shownBefore = st.order.length
137
+ const out = revealFeature(st, f, { edges: g.edges, importers, byId })
138
+ st = out.state
139
+ const moved = Object.keys(before).filter(
140
+ (k) => before[k].depth !== st.at[k].depth || before[k].col !== st.at[k].col)
141
+ console.log(draw(st, opened))
142
+ const grew = st.order.length - shownBefore
143
+ console.log(`└─ ${f.paths.length}개 중 현관문 ${out.door.ids.length}개를 골랐다`)
144
+ console.log(` 근거: ${out.door.why}`)
145
+ for (const id of out.door.ids) {
146
+ const isNew = !before[id]
147
+ console.log(` ${isNew ? '새로' : '이미'} ${short(id).padEnd(28)} 바깥에서 부르는 곳 ${out.door.calledFrom[id]}`)
148
+ }
149
+ // 🔴 고른 수와 실제로 는 수가 다르면 사람은 "눌렀는데 아무 일도 안 났다" 를 본다.
150
+ console.log(` ⇒ 화면에 실제로 는 것 ${grew}개`
151
+ + (grew === out.door.ids.length ? '' : ` ❌ ${out.door.ids.length}개를 골랐는데 ${out.door.ids.length - grew}개는 이미 열려 있던 것`))
152
+ console.log(` 자리가 움직인 노드 ${moved.length}개` + (moved.length ? ' ❌' : ' ✅'))
153
+ }
154
+
155
+ r = reachable(st, calls)
156
+ console.log(`\n${'━'.repeat(78)}`)
157
+ console.log(`한꺼번에 그렸다면 ${g.nodes.length}개. ${STEPS}번 눌러서 본 것은 ${r.shown}개.`)
158
+ console.log(`읽은 흔적이 곧 화면이고, 안 연 것은 화면에 없다.`)
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 테스트를 돌리기 전에 **돌릴 것이 있는지부터 확인한다.**
4
+ *
5
+ * 🔴 `node --test "test/*.test.mjs"` 는 글롭이 아무것도 잡지 못해도
6
+ * 테스트 0개를 돌리고 **exit 0** 을 낸다. 실측:
7
+ *
8
+ * $ node --test "test/nonexistent-*.test.mjs"
9
+ * exit=0
10
+ *
11
+ * 즉 `npm test` 가 초록인데 검증은 하나도 일어나지 않은 상태가 가능하다.
12
+ * 이 저장소가 락에서 금지한 fail-open 과 같은 종류이고, 하필 그것을 막는
13
+ * 검증 계층 자체에 있었다.
14
+ *
15
+ * 파일 목록을 `readdirSync` 로 직접 만들어 `--test` 에 **명시적 경로**로 넘긴다.
16
+ * 글롭 해석을 Node 에게 맡기지 않으므로, 어느 버전에서 돌리든 같은 목록이 돈다.
17
+ * (참고: `node --test test/` 처럼 디렉터리를 넘기는 방식은 Node 24 에서
18
+ * 디렉터리를 모듈로 실행하려다 죽는다. 그래서 이 길은 선택지가 아니었다.)
19
+ */
20
+ import { readdirSync } from 'node:fs'
21
+ import { spawnSync } from 'node:child_process'
22
+ import path from 'node:path'
23
+ import { fileURLToPath } from 'node:url'
24
+
25
+ // 호출 위치에 기대지 않는다. bin/axmap.mjs 가 같은 이유로 쓰는 방식이다.
26
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
27
+ const dir = path.join(root, 'test')
28
+
29
+ const files = readdirSync(dir)
30
+ .filter((f) => f.endsWith('.test.mjs'))
31
+ .sort()
32
+ .map((f) => path.join(dir, f))
33
+
34
+ if (files.length === 0) {
35
+ console.error(`테스트 파일을 하나도 찾지 못했습니다: ${dir}/*.test.mjs`)
36
+ console.error('돌릴 것이 없는 것을 통과로 세지 않습니다.')
37
+ process.exit(1)
38
+ }
39
+
40
+ const r = spawnSync(process.execPath, ['--test', ...files], { stdio: 'inherit', cwd: root })
41
+ // status 가 null 이면 신호로 죽은 것이다. 통과로 세지 않는다.
42
+ process.exit(r.status ?? 1)