@handsupmin/gc-tree 0.1.4 → 0.2.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/README.zh.md CHANGED
@@ -19,213 +19,229 @@
19
19
 
20
20
  </div>
21
21
 
22
- 这是为那些日常工作横跨多个仓库、产品、客户和工作流的开发者准备的。
23
-
24
- `gc-tree` 给 AI 编码工具补上了一层 **仓库之上的可复用上下文层**。该长期保留的上下文可以留下来,只在相关仓库里生效,不相关的时候就安静退场。
25
-
26
22
  ---
27
23
 
28
- ## 为什么是 gc-tree?
24
+ ## 问题所在
29
25
 
30
- 只要你开始认真把 AI 代理用进日常开发,repo-local context 很快就不够用了。
26
+ 你每天都在用 Claude Code Codex。但你的实际工作横跨多个仓库、产品和客户——而 AI 工具只认识当前文件。
31
27
 
32
- 一旦工作同时分布在多个仓库和多个工作流上,常见问题就会一起出现:
28
+ 于是你不得不一遍遍地做这些事:
33
29
 
34
- - 长期上下文被不断塞进提示词里
35
- - 不相关的上下文泄漏到别的仓库
36
- - 每开一个新会话都要重新讲一遍背景
37
- - 客户或产品知识只藏在聊天记录里
38
- - 每次切换工作流,都得靠人脑手动切换上下文
30
+ - 重新解释哪些仓库属于同一套系统
31
+ - 把同一份架构文档反复粘贴进提示词
32
+ - 提醒 AI 它上周"已经知道"的编码规范
33
+ - 手动剔除与当前仓库无关的上下文
39
34
 
40
- `gc-tree` 是给那些已经深度使用 Codex、Claude Code 等 AI 编码工具、但不想再手动维护上下文的人准备的。
35
+ 这不是 AI 的问题,而是**上下文管理的问题**。
41
36
 
42
37
  ---
43
38
 
44
- ## 它能直接带来什么
39
+ ## gc-tree 做什么
45
40
 
46
- - **多条可长期维护的上下文**
47
- 你可以按产品、客户或工作流,分别维护自己的上下文线。
41
+ `gc-tree` 工作在**仓库之上**。它把上下文存储为结构化的 Markdown 文件,让 AI 工具在每次会话前自动拉取相关内容。
48
42
 
49
- - **按仓库控制相关性**
50
- 可以明确指定某条上下文只该作用于哪些仓库。
43
+ ```bash
44
+ gctree resolve --query "auth token rotation policy"
45
+ ```
51
46
 
52
- - **更聪明的范围保护**
53
- 当你进入一个尚未映射的仓库时,可以选择只继续这一次、以后都在这里启用,或者在这里忽略它。
47
+ ```json
48
+ {
49
+ "gc_branch": "main",
50
+ "matches": [
51
+ {
52
+ "title": "认证与 Session 规范",
53
+ "score": 4,
54
+ "summary": "每次请求都做 JWT rotation,refresh token 存在 httpOnly cookie 里,access token TTL 15 分钟",
55
+ "excerpt": "## 认证流程\nAccess token:15 分钟 TTL,每次认证请求都做 rotation..."
56
+ }
57
+ ]
58
+ }
59
+ ```
54
60
 
55
- - **带引导的 onboarding 与更新**
56
- 可以用 Codex、Claude Code,或者两者一起,把上下文建立起来并持续维护。
61
+ AI 工具拿到的是正确的上下文——不是整个知识库,而是恰好相关的那一片。
57
62
 
58
- - **summary-first markdown 知识结构**
59
- 知识留在文件里,而不是藏在隐式记忆里;工具可以先读摘要,再决定要不要展开全文。
63
+ **实测:每次查询只注入约 4% 的总上下文。** 其余 96% 留在磁盘上,真正需要时才进入 token 窗口。
60
64
 
61
65
  ---
62
66
 
63
- ## 安装与快速开始
67
+ ## 适合哪些人
64
68
 
65
- ```bash
66
- npm install -g @handsupmin/gc-tree
67
- gctree init
68
- ```
69
+ 如果以下几条符合你的情况,gc-tree 会很适合你:
69
70
 
70
- 这样就可以开工了。
71
- 之后照你原来的开发方式继续就行,`gc-tree` 只是把一层可复用的全局上下文加在你现有流程外面。
71
+ - 工作横跨**多个仓库**(单体仓库团队、平台 + 客户仓库、后端 + 前端技术栈)
72
+ - 同一周内要在**多个产品或客户**之间来回切换
73
+ - 每次开 AI 会话都要**重复解释相同的上下文**
74
+ - 希望 AI 工具不只了解当前文件,还能理解**编码规范、架构和领域知识**
72
75
 
73
- - **CLI 命令:** `gctree`
74
- - **要求:** Node.js 20+
75
-
76
- 如果你想从源码开发,请看 [docs/local-development.zh.md](https://github.com/handsupmin/gc-tree/blob/main/docs/local-development.zh.md)。
76
+ 如果你只在一个仓库、一个产品里工作,不需要这个工具。`CLAUDE.md` `.cursorrules` 就够了。
77
77
 
78
78
  ---
79
79
 
80
- ## 常用操作
80
+ ## 和 CLAUDE.md 或 cursor rules 有什么区别?
81
81
 
82
- ### 当一个工作流值得拥有自己的上下文时,先开一条新分支
82
+ `CLAUDE.md` 很好用——在单个仓库里。
83
83
 
84
- ```bash
85
- gctree checkout -b client-b
86
- gctree onboard
87
- ```
84
+ 一旦你有了多个仓库、客户或工作线:
88
85
 
89
- 不管是客户项目、产品线、迁移工作,还是某个阶段性的专项,只要值得单独维护背景,就给它单开一个 gc-branch。
86
+ | | `CLAUDE.md` / cursor rules | `gc-tree` |
87
+ |---|---|---|
88
+ | 作用范围 | 单个仓库 | 多个仓库,一套上下文 |
89
+ | 持久化方式 | 仓库内的文件 | 存储在仓库外,跨会话复用 |
90
+ | 切换上下文 | 手动编辑文件 | `gctree checkout client-b` |
91
+ | 相关性过滤 | 全部或全无 | 只注入匹配的文档(约 4%) |
92
+ | 初始化方式 | 手写 | 由 AI 工具引导完成 |
93
+ | 支持 Codex | ✅ | ✅ |
94
+ | 支持 Claude Code | ✅ | ✅ |
90
95
 
91
- ### 之后再把长期上下文补进去
96
+ ---
92
97
 
93
- ```bash
94
- gctree update-global-context
95
- ```
98
+ ## 经过验证的性能
96
99
 
97
- 随着工作推进,把真正值得长期保留的信息补到当前激活的 gc-branch 里。
100
+ 基于真实内部文档测试(4 Notion 导出,中英文混合查询):
98
101
 
99
- 简短别名:
102
+ | 指标 | 结果 |
103
+ |---|---|
104
+ | Recall——相关查询找到正确文档的比例 | **100%**(16/16) |
105
+ | Precision——无关查询返回空结果的比例 | **80%**(4/5) |
106
+ | F1 分数 | **88.9%** |
107
+ | 每次查询注入的 token 占总上下文的比例 | **约 4%** |
108
+ | 支持中英文混合查询 | ✅ |
100
109
 
101
- ```bash
102
- gctree update-gc
103
- gctree ugc
104
- ```
110
+ ---
105
111
 
106
- ### 需要的时候再把上下文取出来
112
+ ## Claude Code 和 Codex 均已验证可用
107
113
 
108
114
  ```bash
109
- gctree resolve --query "auth token rotation"
115
+ gctree scaffold --host claude-code # 安装 CLAUDE.md 片段 + /gc-onboard、/gc-update-global-context
116
+ gctree scaffold --host codex # 安装 AGENTS.md 片段 + $gc-onboard、$gc-update-global-context
117
+ gctree scaffold --host both # 两者同时安装
110
118
  ```
111
119
 
112
- 等你真的需要某段背景时,再把它拉回当前时刻。
120
+ 两个 provider 使用同一个底层上下文存储。onboard 一次,两个工具都能用。
113
121
 
114
- ---
115
-
116
- ## 为什么它用起来很顺手
122
+ **Claude Code** — 使用 `/gc-resolve-context`、`/gc-onboard`、`/gc-update-global-context` 斜杠命令。
117
123
 
118
- **你可以像管理 Git 分支一样保留多条上下文,但不用像管理 Git 分支那样时时盯着。**
124
+ **Codex** 使用 `$gc-resolve-context`、`$gc-onboard`、`$gc-update-global-context` skill。已通过 `codex exec` 实际验证:
119
125
 
120
- 你可以把上下文拆成这些维度:
126
+ ```
127
+ gctree status → gc_branch: main,doc_count: 2
128
+ gctree resolve --query 'NestJS DTO plainToInstance'
129
+ → 匹配到"后端编码规范"(score: 3)
130
+ → DTO:class-transformer plainToInstance,class-validator 必填
131
+ → 错误处理:基于 HttpException 的自定义异常,禁止直接抛出 raw Error
132
+ ```
121
133
 
122
- - 客户
123
- - 产品线
124
- - 平台团队
125
- - 一起演进的后端 + 前端技术栈
126
- - 临时专项或迁移项目
134
+ ---
127
135
 
128
- 切换方式也很熟悉:
136
+ ## 安装与快速开始
129
137
 
130
138
  ```bash
131
- gctree checkout -b client-b
132
- gctree checkout main
139
+ npm install -g @handsupmin/gc-tree
140
+ gctree init
133
141
  ```
134
142
 
135
- 但和 Git 不一样的是,这种切换不需要你一直手动维护。
143
+ `gctree init` 会引导你:
144
+ 1. 选择 provider:`claude-code`、`codex` 或 `both`
145
+ 2. 在当前仓库安装集成文件
146
+ 3. 为 `main` gc-branch 完成引导式 onboarding
136
147
 
137
- 如果你当前所在的仓库根本不在这条上下文的作用范围内,`gc-tree` 可以把它判断为“此处无关”,而不是硬塞进当前会话里。
148
+ 之后,AI 工具会自动学会在规划或实现前调用 `gctree resolve`。
138
149
 
139
- 这样一来,你可以长期保留多条上下文,却不用把所有背景都拖进每一次工具会话里。
150
+ - **CLI:** `gctree`
151
+ - **要求:** Node.js 20+
140
152
 
141
153
  ---
142
154
 
143
- ## 一个更贴近现实的工作流
144
-
145
- 假设你平时同时在这些地方工作:
155
+ ## 常用操作
146
156
 
147
- - 一个共享平台仓库
148
- - 两个客户项目仓库
149
- - 一个内部工具仓库
157
+ ### 为不同工作线创建独立上下文
150
158
 
151
- 没有 `gc-tree` 的话,每次开新的 AI 会话都得重新交代:
159
+ ```bash
160
+ gctree checkout -b client-b
161
+ gctree onboard
162
+ ```
152
163
 
153
- - 现在讨论的是哪个客户
154
- - 哪些仓库其实是一组
155
- - 当前最重要的工作流是什么
156
- - 哪些上下文放进来反而会干扰
164
+ 每个 gc-branch 都是完全独立的上下文通道,像 Git 分支一样自由切换。
157
165
 
158
- 有了 `gc-tree`,你就可以按工作线维护不同上下文,在会话之间复用,并用 repo scope 规则挡住那些不该混进来的背景。
166
+ ### 按需拉取相关上下文
159
167
 
160
- 它真正解决的不是“多存一点提示词”,而是:
168
+ ```bash
169
+ gctree resolve --query "billing retry policy"
170
+ ```
161
171
 
162
- > **在对的工作层级上,管理对的上下文。**
172
+ 只返回匹配的文档——标题、摘要和摘录。摘要不够用时,工具才会读完整文档。
163
173
 
164
- ---
174
+ ### 保持上下文与时俱进
165
175
 
166
- ## 核心概念
176
+ ```bash
177
+ gctree update-global-context # 或:gctree update-gc / gctree ugc
178
+ ```
167
179
 
168
- - **gc-branch**
169
- 面向某个产品、客户、工作流或领域的一条长期上下文线。
180
+ 引导式更新流程——AI 工具询问发生了哪些变化,并将新上下文写回 gc-branch
170
181
 
171
- - **repo scope**
172
- 决定这条上下文应该在哪些仓库里生效的规则。
182
+ ### 限定上下文作用的仓库范围
173
183
 
174
- - **provider-guided flow**
175
- 不手写 JSON,而是借助你熟悉的 AI 编码工具完成 onboarding 和更新。
184
+ ```bash
185
+ gctree set-repo-scope --branch client-b --include # 包含当前仓库
186
+ gctree set-repo-scope --branch client-b --exclude # 排除当前仓库
187
+ ```
176
188
 
177
- - **context tree**
178
- 在实现上,`gc-tree` 把上下文组织成一棵按分支感知、由文件承载的知识树。
179
- 对用户来说,核心价值就是:项目之外也能复用的上下文。
189
+ `gc-tree` 不会向不相关的仓库注入上下文。
180
190
 
181
191
  ---
182
192
 
183
- ## 运行时里可见的 provider 命令
193
+ ## 上下文的存储结构
184
194
 
185
- scaffold 完成后,运行时可见的命令是:
186
-
187
- - **Codex:** `$gc-onboard`, `$gc-update-global-context`
188
- - **Claude Code:** `/gc-onboard`, `/gc-update-global-context`
195
+ ```
196
+ ~/.gctree/
197
+ branches/
198
+ main/
199
+ index.md ← 压缩索引,≤2000 字符,优先加载
200
+ docs/
201
+ auth.md ← 完整文档,按需读取
202
+ architecture.md
203
+ client-b/
204
+ index.md
205
+ docs/
206
+ ...
207
+ branch-repo-map.json ← 哪些仓库属于哪个 gc-branch
208
+ settings.json ← 首选 provider、语言
209
+ ```
189
210
 
190
- 这些命令在开始收集或更新长期上下文之前,都应该先说明当前激活的是哪个 gc-branch,并且除非用户明确要求切换,否则应持续使用已经保存的语言。
211
+ 上下文存储在仓库之外——无需 `.gitignore` 规则,不会误提交,使用同一 gc-branch 的所有项目都可复用。
191
212
 
192
213
  ---
193
214
 
194
- ## 核心命令速览
215
+ ## 核心命令
195
216
 
196
217
  | 目标 | 命令 |
197
- | --- | --- |
218
+ |---|---|
198
219
  | 初始化 gc-tree 并选择 provider | `gctree init` |
199
- | 确认当前 gc-branch | `gctree status` |
200
- | 搜索当前激活的上下文 | `gctree resolve --query "..."` |
201
- | 查看仓库范围规则 | `gctree repo-map` |
202
- | 为 gc-branch 显式设置仓库包含 / 排除 | `gctree set-repo-scope --branch <name> --include` / `--exclude` |
220
+ | 确认当前激活的 gc-branch | `gctree status` |
221
+ | 搜索当前上下文 | `gctree resolve --query "..."` |
203
222
  | 创建或切换 gc-branch | `gctree checkout <branch>` / `gctree checkout -b <branch>` |
204
- | 为一个空的 gc-branch 执行 onboarding | `gctree onboard` |
205
- | 为当前 gc-branch 执行长期更新 | `gctree update-global-context` / `gctree update-gc` / `gctree ugc` |
223
+ | 列出所有 gc-branch | `gctree branches` |
224
+ | 对空 gc-branch 进行引导式 onboarding | `gctree onboard` |
225
+ | 对当前 gc-branch 进行引导式更新 | `gctree update-global-context` / `gctree ugc` |
226
+ | 查看仓库范围规则 | `gctree repo-map` |
227
+ | 为 gc-branch 包含或排除当前仓库 | `gctree set-repo-scope --branch <name> --include` / `--exclude` |
206
228
  | 重新 onboarding 前重置 gc-branch | `gctree reset-gc-branch --branch <name> --yes` |
207
- | 手动为其他环境安装 scaffold | `gctree scaffold --host codex --target /path/to/repo` |
229
+ | 在新环境中安装 scaffold | `gctree scaffold --host codex --target /path/to/repo` |
208
230
 
209
231
  ---
210
232
 
211
233
  ## 文档
212
234
 
213
- 详细文档位于 [`docs/`](https://github.com/handsupmin/gc-tree/tree/main/docs) 目录下。
214
-
215
235
  - **概念** — [`docs/concept.zh.md`](https://github.com/handsupmin/gc-tree/blob/main/docs/concept.zh.md)
216
- 说明 `gctree` 是什么、解决什么问题,以及全局上下文层的边界。
217
236
  - **原理** — [`docs/principles.zh.md`](https://github.com/handsupmin/gc-tree/blob/main/docs/principles.zh.md)
218
- 介绍 gc-branch、仓库范围、summary-first 文档和引导式更新等原则。
219
237
  - **使用方法** — [`docs/usage.zh.md`](https://github.com/handsupmin/gc-tree/blob/main/docs/usage.zh.md)
220
- 介绍标准 CLI 流程、provider 命令、仓库范围行为和集成方式。
221
- - **本地运行方法** — [`docs/local-development.zh.md`](https://github.com/handsupmin/gc-tree/blob/main/docs/local-development.zh.md)
222
- 说明依赖安装、本地运行 CLI 与提交前验证方式。
238
+ - **本地开发** [`docs/local-development.zh.md`](https://github.com/handsupmin/gc-tree/blob/main/docs/local-development.zh.md)
223
239
 
224
240
  ---
225
241
 
226
242
  ## 贡献
227
243
 
228
- 欢迎贡献。开发流程与 PR 检查清单位于英文文档 [CONTRIBUTING.md](https://github.com/handsupmin/gc-tree/blob/main/CONTRIBUTING.md)。
244
+ 欢迎贡献。开发流程与 PR 检查清单见 [CONTRIBUTING.md](https://github.com/handsupmin/gc-tree/blob/main/CONTRIBUTING.md)。
229
245
 
230
246
  ---
231
247
 
@@ -2,13 +2,44 @@ import { readFile } from 'node:fs/promises';
2
2
  import { join } from 'node:path';
3
3
  import { extractExcerpt, extractSummary, parseIndexEntries } from './markdown.js';
4
4
  import { branchDir, branchIndexPath } from './paths.js';
5
- function scoreText(text, query) {
6
- const tokens = String(query || '')
5
+ // Common English stop words that are too short or generic to be meaningful query signals.
6
+ const STOP_WORDS = new Set([
7
+ 'a', 'an', 'the', 'and', 'or', 'but', 'if', 'in', 'on', 'at', 'to', 'for',
8
+ 'of', 'with', 'by', 'as', 'is', 'it', 'its', 'be', 'was', 'are', 'were',
9
+ 'do', 'did', 'has', 'had', 'not', 'no', 'so', 'up', 'out', 'off', 'via',
10
+ 'vs', 'per', 'set', 'get', 'run', 'add', 'use', 'new', 'old', 'all', 'any',
11
+ ]);
12
+ function tokenize(text) {
13
+ // Split on whitespace, punctuation, and symbols — preserving Unicode letters
14
+ // and digits (including Korean, Japanese, CJK, etc.).
15
+ return String(text || '')
7
16
  .toLowerCase()
8
- .split(/[^a-z0-9]+/)
9
- .filter((token) => token.length >= 2);
17
+ .split(/[^\p{L}\p{N}]+/u)
18
+ .filter((t) => t.length >= 2 && !STOP_WORDS.has(t));
19
+ }
20
+ function makeTokenRegex(token) {
21
+ // ASCII-only token: use \b word boundary (prevents "store" → "storefront").
22
+ // Non-ASCII token (Korean, CJK, etc.): use Unicode letter lookbehind/lookahead
23
+ // because \b doesn't work for non-ASCII characters.
24
+ const isAscii = /^[a-z0-9]+$/.test(token);
25
+ return isAscii
26
+ ? new RegExp(`\\b${token}\\b`)
27
+ : new RegExp(`(?<!\\p{L})${token}(?!\\p{L})`, 'u');
28
+ }
29
+ function scoreText(text, query) {
30
+ const tokens = tokenize(query);
31
+ if (tokens.length === 0)
32
+ return 0;
10
33
  const haystack = String(text || '').toLowerCase();
11
- return tokens.reduce((sum, token) => sum + (haystack.includes(token) ? 1 : 0), 0);
34
+ return tokens.reduce((sum, token) => {
35
+ try {
36
+ return sum + (makeTokenRegex(token).test(haystack) ? 1 : 0);
37
+ }
38
+ catch {
39
+ // Regex construction failed (e.g. special chars in token) — fall back to substring
40
+ return sum + (haystack.includes(token) ? 1 : 0);
41
+ }
42
+ }, 0);
12
43
  }
13
44
  export async function resolveContext({ home, branch, query, }) {
14
45
  const indexRaw = await readFile(branchIndexPath(home, branch), 'utf8');
@@ -19,7 +50,10 @@ export async function resolveContext({ home, branch, query, }) {
19
50
  const raw = await readFile(fullPath, 'utf8');
20
51
  const summary = extractSummary(raw);
21
52
  const combined = `${entry.title}\n${summary}\n${raw}`;
22
- const score = scoreText(combined, query);
53
+ // Title matches count double (higher signal density)
54
+ const titleScore = scoreText(entry.title, query);
55
+ const bodyScore = scoreText(`${summary}\n${raw}`, query);
56
+ const score = titleScore * 2 + bodyScore;
23
57
  if (score <= 0)
24
58
  continue;
25
59
  matches.push({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@handsupmin/gc-tree",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "description": "Global Context Tree, a lightweight branch-aware global context orchestrator for AI coding tools",
5
5
  "type": "module",
6
6
  "private": false,
@@ -18,7 +18,14 @@
18
18
  "scripts": {
19
19
  "build": "tsc -p tsconfig.json",
20
20
  "test": "node --import tsx --test tests/*.test.ts",
21
- "prepack": "npm run build"
21
+ "prepack": "npm run build",
22
+ "eval": "node --import tsx tests/eval/index.ts",
23
+ "eval:verbose": "node --import tsx tests/eval/index.ts --verbose",
24
+ "eval:multi-repo": "node --import tsx tests/eval/multi-repo.ts",
25
+ "eval:multi-repo:verbose": "node --import tsx tests/eval/multi-repo.ts --verbose",
26
+ "eval:autoresearch": "node --import tsx tests/eval/autoresearch.ts",
27
+ "eval:real-docs": "node --import tsx tests/eval/real-docs.ts",
28
+ "eval:real-docs:verbose": "node --import tsx tests/eval/real-docs.ts --verbose"
22
29
  },
23
30
  "engines": {
24
31
  "node": ">=20"