grga 0.2.0__tar.gz

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 (86) hide show
  1. grga-0.2.0/CHANGELOG.md +416 -0
  2. grga-0.2.0/LICENSE +21 -0
  3. grga-0.2.0/MANIFEST.in +9 -0
  4. grga-0.2.0/PKG-INFO +192 -0
  5. grga-0.2.0/README.md +129 -0
  6. grga-0.2.0/pyproject.toml +65 -0
  7. grga-0.2.0/setup.cfg +4 -0
  8. grga-0.2.0/src/grga/__init__.py +1 -0
  9. grga-0.2.0/src/grga/__main__.py +5 -0
  10. grga-0.2.0/src/grga/cli.py +1759 -0
  11. grga-0.2.0/src/grga/deep_search.py +2157 -0
  12. grga-0.2.0/src/grga/env_loader.py +243 -0
  13. grga-0.2.0/src/grga/exit_codes.py +88 -0
  14. grga-0.2.0/src/grga/expand.py +145 -0
  15. grga-0.2.0/src/grga/file_read.py +339 -0
  16. grga-0.2.0/src/grga/graph/__init__.py +30 -0
  17. grga-0.2.0/src/grga/graph/build.py +2606 -0
  18. grga-0.2.0/src/grga/graph/ids.py +314 -0
  19. grga-0.2.0/src/grga/graph/inspect.py +579 -0
  20. grga-0.2.0/src/grga/graph/promote.py +242 -0
  21. grga-0.2.0/src/grga/graph/schema.py +1511 -0
  22. grga-0.2.0/src/grga/graph/votes.py +278 -0
  23. grga-0.2.0/src/grga/gui.py +1187 -0
  24. grga-0.2.0/src/grga/meta.py +236 -0
  25. grga-0.2.0/src/grga/models.py +114 -0
  26. grga-0.2.0/src/grga/paths.py +92 -0
  27. grga-0.2.0/src/grga/probe.py +390 -0
  28. grga-0.2.0/src/grga/prompts/__init__.py +32 -0
  29. grga-0.2.0/src/grga/prompts/suggest_expand.txt +25 -0
  30. grga-0.2.0/src/grga/prompts/suggest_reformulate.txt +24 -0
  31. grga-0.2.0/src/grga/prompts/suggest_rerank.txt +23 -0
  32. grga-0.2.0/src/grga/related.py +704 -0
  33. grga-0.2.0/src/grga/runtime_env.py +69 -0
  34. grga-0.2.0/src/grga/search.py +1036 -0
  35. grga-0.2.0/src/grga/skill_install.py +82 -0
  36. grga-0.2.0/src/grga/skills/grga/SKILL.md +311 -0
  37. grga-0.2.0/src/grga/skills/grga/examples/probe-search-reflect.md +107 -0
  38. grga-0.2.0/src/grga/skills/grga/examples/search-expand-chapter.md +79 -0
  39. grga-0.2.0/src/grga/skills/grga/examples/search-related-graph.md +134 -0
  40. grga-0.2.0/src/grga/skills/grga/examples/search-suggest.md +98 -0
  41. grga-0.2.0/src/grga/skills/grga/references/anti-patterns.md +105 -0
  42. grga-0.2.0/src/grga/skills/grga/references/reflection-loop.md +307 -0
  43. grga-0.2.0/src/grga/skills/grga/references/storage-model.md +99 -0
  44. grga-0.2.0/src/grga/skills/grga/references/troubleshooting.md +57 -0
  45. grga-0.2.0/src/grga/skills/grga/references/verbs.md +181 -0
  46. grga-0.2.0/src/grga/structure/__init__.py +0 -0
  47. grga-0.2.0/src/grga/structure/md.py +262 -0
  48. grga-0.2.0/src/grga/structure/sidecar.py +84 -0
  49. grga-0.2.0/src/grga/suggest.py +424 -0
  50. grga-0.2.0/src/grga/toolchain.py +1119 -0
  51. grga-0.2.0/src/grga.egg-info/PKG-INFO +192 -0
  52. grga-0.2.0/src/grga.egg-info/SOURCES.txt +84 -0
  53. grga-0.2.0/src/grga.egg-info/dependency_links.txt +1 -0
  54. grga-0.2.0/src/grga.egg-info/entry_points.txt +3 -0
  55. grga-0.2.0/src/grga.egg-info/requires.txt +10 -0
  56. grga-0.2.0/src/grga.egg-info/top_level.txt +2 -0
  57. grga-0.2.0/tests/test_cli_expand.py +74 -0
  58. grga-0.2.0/tests/test_cli_json.py +192 -0
  59. grga-0.2.0/tests/test_cli_smoke.py +109 -0
  60. grga-0.2.0/tests/test_ensure_tools.py +91 -0
  61. grga-0.2.0/tests/test_env_loader.py +116 -0
  62. grga-0.2.0/tests/test_exit_codes.py +79 -0
  63. grga-0.2.0/tests/test_expand.py +154 -0
  64. grga-0.2.0/tests/test_graph_build.py +955 -0
  65. grga-0.2.0/tests/test_graph_schema.py +497 -0
  66. grga-0.2.0/tests/test_incremental_build.py +578 -0
  67. grga-0.2.0/tests/test_md_parser_eval.py +49 -0
  68. grga-0.2.0/tests/test_meta.py +211 -0
  69. grga-0.2.0/tests/test_models.py +89 -0
  70. grga-0.2.0/tests/test_paths.py +124 -0
  71. grga-0.2.0/tests/test_promote.py +183 -0
  72. grga-0.2.0/tests/test_related.py +164 -0
  73. grga-0.2.0/tests/test_related_keywords.py +283 -0
  74. grga-0.2.0/tests/test_related_signals.py +334 -0
  75. grga-0.2.0/tests/test_scaffold.py +28 -0
  76. grga-0.2.0/tests/test_schema_v5_migration.py +399 -0
  77. grga-0.2.0/tests/test_schema_v8_migration.py +511 -0
  78. grga-0.2.0/tests/test_scope_stopwords.py +351 -0
  79. grga-0.2.0/tests/test_search_cap.py +221 -0
  80. grga-0.2.0/tests/test_sidecar.py +101 -0
  81. grga-0.2.0/tests/test_skill_install_cli.py +76 -0
  82. grga-0.2.0/tests/test_structure_md.py +142 -0
  83. grga-0.2.0/tests/test_suggest.py +221 -0
  84. grga-0.2.0/tests/test_toolchain_dependencies.py +66 -0
  85. grga-0.2.0/tests/test_toolchain_sha256_verify.py +540 -0
  86. grga-0.2.0/tests/test_votes.py +174 -0
@@ -0,0 +1,416 @@
1
+ # Changelog
2
+
3
+ All notable changes to `grga` are recorded here.
4
+
5
+ ## [Unreleased]
6
+
7
+ ### 依赖清理 · 删除已死的 `perf` extra + `markdown-it-py` 提升为核心依赖
8
+
9
+ - **删除** `pyproject.toml` 的 `[project.optional-dependencies].perf =
10
+ ["numpy>=1.20"]`。这个 extra 是 v8 时代给 PPR 幂迭代的 numpy CSR +
11
+ bincount SpMV 后端准备的(详见 `docs/design/plan-2026-07-12-related-perf.md`
12
+ §2.3 / §P3)。v9 keyword-primary 已整体移除 PPR(`graph/ppr.py` /
13
+ `graph/adjacency_cache.py` 都删了,`related_keywords` 走 index-only 早
14
+ 停查 `keyword_pairs`,无矩阵运算)。运行时代码里没有任何 `import numpy`,
15
+ `perf` extra 装了也没有加速目标。用户端 `pip install grga[perf]` 现在
16
+ 会安装失败 —— 一次性砍掉,避免 pyproject 里保留死符号误导。
17
+ - **合并** `[project.optional-dependencies].v2 = ["markdown-it-py>=3.0"]`
18
+ → 核心 `dependencies`。`markdown-it-py` 是 `structure/md.py` 的主
19
+ Markdown 解析器(ADR-0001),`expand --level chapter` 与 `heading_path`
20
+ 元数据必需。v2 分组当初是为了"v1 打包不污染"而设的临时缓冲,v2 早已
21
+ 是默认路径,分组抽象无实际意义。核心 install 直接带上,避免 caller
22
+ 忘记 `pip install grga[v2]` 后 heading 静默降级为空。
23
+ - **不变**:`scripts/build-onefile.py` 的 `EXCLUDED_MODULES` 里仍列了
24
+ `numpy`(防御性排除,避免任何传递依赖把 numpy 拖进单文件),无需改动。
25
+
26
+ ### build 期写入门槛自适应 · Gate 1 独立性 + Gate 2 阶梯(docs/design/decisions.md §2026-07-13)
27
+
28
+ `min_pair_count` 固定为 5 对小语料过严:一本中篇(几百 chunk)的真实
29
+ 概念对常只共现 2-3 次,全被砍空,`related --mode graph` 频繁空结果。
30
+ 但直接降门槛会放进 npmi 虚高的噪声(两个词各出现 1 次恰好同现 → npmi
31
+ 满分 1.0)。改为两条正交门槛:
32
+
33
+ - **新增**:写入期 **Gate 1 · 独立性判据**(无参、恒开、严格三元相等)——
34
+ `chunk_df_a == chunk_df_b == cooccur_count`。挡掉"两个词完全绑定"
35
+ (npmi=1.0 artefact);保留"单向依附"(rare 词只跟 common 词同现)
36
+ 的真信号。用严格三元相等(不用 `co >= max`)是因为 `mode='build'`
37
+ 下 `cooccur_count` 是 grow-only(D6),可以出现 `co > min(df_a, df_b)`
38
+ 的合法单向依附——`co >= max` 会误杀,三元相等不会。
39
+ `_finalize_pairs_from_temp` 与 `_prune_low_score_pairs` 同步实现。
40
+ - **变更**:`min_pair_count` 默认从固定 `5` 改为 **scope 自适应阶梯**
41
+ `resolve_min_pair_count(n_chunks)`:`≤200→2` / `≤2000→3` / `≤20000→4`
42
+ / `>20000→5`。`grga index build/rebuild --min-pair-count` 默认值
43
+ `5` → 未设置(哨兵 `None` = 自适应);显式传 int 覆盖阶梯。
44
+ - **变更**:`grga.graph.build.build_graph_index` 的 `min_pair_count`
45
+ 形参类型 `int` → `Optional[int]`(`None` = 自适应)。
46
+ - **变更**:`_prune_low_score_pairs`(v7→v8 迁移的存储契约执行者)改为
47
+ per-scope 查 `scope_stats.n_chunks` 求 floor + Gate 1;无 scope_stats
48
+ 时回退固定 5。
49
+ - **不变**:Gate 3(`min_npmi=0.2`)、大语料(>20000 chunks)行为、
50
+ `related --json` 契约、BuildStats / CLI JSON 形状。
51
+ - 小语料 `related` 覆盖率显著提升;大语料零回归。
52
+
53
+ ## [Unreleased] · related 信号契约 v3.1 · `resolved_seeds` 多 seed 探针(docs/design/related-signals-v3.md)
54
+
55
+ 多 seed `related --mode graph` 的空结果原本二义——分不清"seed 未索引"
56
+ 和"seed 已索引但孤立(边被写入期/读侧 npmi 门槛滤空)"。这两种情况
57
+ caller 从 `results` 派生不出来(信息天然重合),下一步动作却相反。新增
58
+ 唯一一个 envelope 级探针补这一 bit。
59
+
60
+ - **新增**:`grga related --json --mode graph` envelope 加
61
+ `resolved_seeds: [{seed, in_index}]` — 每个去重(casefold + 保序)输入
62
+ seed 一条,`in_index` 报告是否命中 `keywords` 词表(与 scope 无关)。
63
+ - **变更**:`grga.related.related_keywords()` 返回类型 `List[RelatedKeyword]`
64
+ → `RelatedKeywordsResult{results, resolved_seeds}`。新增 `SeedResolution`
65
+ dataclass。
66
+ - **变更**:graph 模式非 JSON 输出把 dead seed 以 stderr 两行提示呈现
67
+ (`unknown seeds ...` / `indexed but no candidates ...`),不再在空结果时
68
+ 提前 `Exit`;exit code 仍为 0,stdout 候选流不受影响。
69
+ - **不变**:`RelatedKeyword` 六字段、排序 `(via_seed, -npmi, kw)`、多 seed
70
+ 不融合(D4)、heading 模式、热路径动词 `--json`。
71
+ - caller 一段 5 行即可分派:`in_index=false` → 换词 / `suggest reformulate`;
72
+ `in_index=true` 但缺席于 `{results[].via_seed}` → 换更具体 seed 或松
73
+ `--min-npmi`。免去逐 seed 的 `grga graph inspect` round-trip。
74
+ - schema:`schemas/related-signals-v3.json` graph 分支加 `resolved_seeds`
75
+ (required)+ `seed_resolution` $def。
76
+
77
+ ## [Unreleased] · 历史条目
78
+
79
+ ### BREAKING · related 信号契约 v2 / v2.1 / v2.2(docs/design/related-signals-v2.md)
80
+
81
+ 同日三步落地:v2.0 撤 trace-signals v1("图游走可视化"),v2.1 把
82
+ `graph_signals` 折叠进顶层 `expansion`(去 `neighbor_edges` 里 `a=seed`
83
+ 重复),v2.2 进一步扁平化 `edges_to_seeds` 列表包装(单 seed 查询里
84
+ "数组包一个字典包一个 seed 回显" 的三层嵌套 → 顶层内联 `npmi` /
85
+ `cooccur_count`)。动机:生产实测稀有种子经 PPR 扩展会拉入通用词命中
86
+ 的假阳性文档(`苍坤` seed 拉进《三体》),旧 `graph_actions` op log
87
+ 无法帮 caller 识别;同时每一次 caller 反馈都指向"信号一处收,去嵌套
88
+ 去回显"。
89
+
90
+ - **移除**:`trace_signals_version` + `graph_actions` 从
91
+ `grga related --json` 与 `grga suggest --json` 撤除。
92
+ - **移除(v2.1)**:`Related.graph_signals` 整块(含 `seed_chunk_df` /
93
+ `neighbor_edges` / 里面的 `total_chunks_in_scope`),`--graph-signals-limit`
94
+ CLI 参数。
95
+ - **移除**:`src/grga/trace.py`、`schemas/trace-signals-v1.json`、
96
+ `schemas/trace-viz-session-v1.json`,以及依赖 `graph_actions` 的两个内置
97
+ skill 工件(`skills/grga-trace-viz/`、`skills/grga/workflows/trace-viewer/`)。
98
+ - **新增(v2.1)**:`grga related --json --mode graph` 顶层 `expansion` 一体化承载:
99
+ * `seeds: List[{kw, chunk_df?}]` — 每个 seed 附加 in-scope chunk df。
100
+ * `expanded_keywords: List[{kw, ppr_score, hops_from_seed?, chunk_df?,
101
+ ...}]` — 每个 PPR 邻居一处出现,无跨数组 join。
102
+ * `total_chunks_in_scope: int` — `chunk_df` 的分母。
103
+ - **变更(v2.2)**:`expanded_keywords[i]` 里最强直接边扁平化,dropping
104
+ `edges_to_seeds` 列表包装:
105
+ * `npmi` / `cooccur_count`:内联在 entry 自身(两字段一起出现或一起
106
+ 缺席)。存在条件:该邻居在 `keyword_pairs` 有直接 seed 边(与
107
+ `hops_from_seed` 解耦,允许"低 npmi 直接边被 PPR 绕开"的信号透过)。
108
+ * `source`:只在 != `"chunk_cooccur"` 时 emit(即 `llm_promoted`)。
109
+ * `via_seeds`:只在**多 seed 查询里 + 邻居有 >=2 条直接边**时出现,
110
+ 按 npmi desc 列出被桥接的 seed 名。
111
+ - **新增**:graph 模式每条 `results[]` 内嵌 `keyword_contributions`
112
+ (`[{kw, occurrences}]`,按 `(-occurrences, kw)` 排序)。
113
+ - **变更**:`related --mode dir|heading` 与 `suggest --json` 去 envelope,
114
+ 现返回 `{"results": [...]}` 对象。
115
+ - **不变**:`search` / `probe` / `expand` / `deep-search` 的 `--json` 输出。
116
+ - caller 用 `expansion.seeds` + `keyword_contributions` 即可 ~5 行过滤
117
+ "仅由 PPR 扩展词命中" 的假阳性;加 `expanded_keywords[].chunk_df` 阈值
118
+ 可再叠一层 hub-word 剪枝。契约 schema:`schemas/related-signals-v2.json`。
119
+ 旧规范归档于 `docs/design/deprecated/trace-signals.md`。
120
+
121
+ ### 性能 · `related --mode graph` 优化 (docs/design/plan-2026-07-12-related-perf.md)
122
+
123
+ 7 项独立优化叠加,把冷启动从 ~110s 压到 ~3s、生产热启动预期 ~200ms。
124
+ top-20 结果 top-20 doc Jaccard=1.0(vs 旧行为),numpy 后端与纯 Python
125
+ 逐位一致。实验证据:`experiments/related_perf_2026_07_12/`。
126
+
127
+ - **P2.3 doc 聚合 id-native**:`aggregate_docs` 用 `kw_id IN (int)` 替换
128
+ `k.kw IN (text)`,直接命中 `keyword_chunks` 复合主键
129
+ `(kw_id, doc_id, chunk_id)`。doc 聚合 SQL 从 ~11s → ~53ms(-99.5%)。
130
+ 设 `GRGA_DOC_AGG_LEGACY=1` 走旧路径。
131
+ - **P3 numpy CSR + `np.bincount` backend**:`_ppr_numpy` SpMV 幂迭代,PPR
132
+ 从 ~35s → ~96ms(-99.7%)。numpy 是**可选依赖**:`pip install grga[perf]`
133
+ 安装;缺失时自动退回纯 Python `_ppr_python`。`GRGA_PPR_BACKEND=py|numpy|auto`
134
+ 可强制选后端,`GRGA_PPR_NUMPY_MIN_EDGES`(默认 50000)控制 auto 切换阈值。
135
+ - **P2.2 id-native 图构建**:新增 `load_edges_indexed` 跳过 JOIN + casefold,
136
+ `IndexedGraph.node_ids` 仍填字符串(契约兼容)。
137
+ - **P2.1 删热路径 `build_adjacency`**:`adjacency_cache` 不再构建从不使用的
138
+ dict adjacency;`get_adjacency` 返回 3-tuple 兼容语法但 `adj` 位恒为 `None`。
139
+ `build_adjacency` / `personalized_pagerank`(dict 版)函数本身保留供 legacy
140
+ caller / 单元测试使用。
141
+ - **P1.2 `min_npmi` 默认 0.2**:`related --min-npmi FLOAT` 参数化;
142
+ `GRGA_PPR_MIN_NPMI` env 覆盖。SQL WHERE 过滤 `chunk_cooccur` 边使 npmi <
143
+ 阈值的边不参与 PPR。`neighbor_edges` schema 不变但**门槛语义微变**(详见
144
+ `docs/design/trace-signals.md` §7.1)。
145
+ - **P1.4a `--edge-quantile` 便捷入口**:`grga related --edge-quantile 0.1`
146
+ 保留 top 10% 边(按 npmi 采样),内部换算为等效 `min_npmi`;与
147
+ `--min-npmi` 互斥(同给报 exit 2)。
148
+ - **P1.1 stopword ∪ core_boundaries**:建图层 `_INDEX_STOPWORDS` 复用已有
149
+ 的 26 字 `_CORE_STOP_BOUNDARIES` 结构虚词集,索引层不再放行 `在/是/也/就/但/
150
+ 这/有/都/后/上/…` 等高频功能词。需要 `grga index build --rebuild` 生效;detect
151
+ 层的 `_CORE_STOP_BOUNDARIES` 语义分层不变(构词后缀如 面壁**者** 仍安全)。
152
+
153
+ ### Changed
154
+
155
+ - `adjacency_cache` 的缓存键增加 `min_npmi` 维度,不同阈值不串扰。
156
+ - `edge_weight` 中 `chunk_cooccur` 边的 `0.01 * log1p(count)` 小地板**保留**
157
+ 作为小语料下 npmi=0 的防御性 fallback(与 P1.2 的 SQL WHERE 过滤解耦,
158
+ 见 `src/grga/graph/ppr.py::edge_weight` docstring)。
159
+
160
+ ### Added
161
+
162
+ - `pyproject.toml [project.optional-dependencies].perf = ["numpy>=1.20"]`
163
+ —— 一体化开关 `pip install grga[perf]` 启用 numpy 加速后端。
164
+
165
+ ## [0.2.0] - 2026-07-09
166
+
167
+ ### BREAKING
168
+
169
+ - **Wheel no longer bundles binaries.** `rg`, `rga`, `pandoc`, `fzf`, and
170
+ `pdftotext` are downloaded at first use (or on demand via
171
+ `grga tools install`). This trims the wheel from ~187 MB to under 1 MB
172
+ and makes distribution license-compliant (upstream AGPL/GPL binaries
173
+ are fetched at runtime, not redistributed).
174
+ - **Managed tools cache moved out of `site-packages`.** New default:
175
+ `$XDG_DATA_HOME/grga/tools/<platform>` on Linux/macOS,
176
+ `%LOCALAPPDATA%\grga\tools\<platform>` on Windows. Override with
177
+ `$GRGA_TOOLS_DIR`. Legacy `site-packages/grga/data/tools/` is still
178
+ discovered as a fallback; run `grga tools migrate` to copy it into the
179
+ new location.
180
+ - **Stable exit-code contract frozen.** See `docs/exit-codes.md`. Codes
181
+ may only be *added* in future releases, never removed or renumbered.
182
+ `--json` errors now use a fixed schema on stdout so agents can pipe
183
+ results reliably.
184
+
185
+ ### Added
186
+
187
+ - `grga tools migrate` — copy legacy site-packages binaries into the
188
+ XDG cache with `--dry-run` support.
189
+ - `grga tools install --from <dir>` — install from a local mirror
190
+ directory (offline / air-gapped installs). Optional `--fallback-online`
191
+ falls back to GitHub when a local asset is missing.
192
+ - `grga tools install --no-verify` and `GRGA_INSECURE=1` — bypass sha256
193
+ verification (with a loud warning) for mirror scenarios.
194
+ - `tools.json` now supports optional `sha256` per tool; `install_tool`
195
+ verifies the downloaded archive when present.
196
+ - `grga skill install --to <dir>` (+ `list` / `path`) — copy bundled
197
+ agent skills into any workspace.
198
+ - `--offline` / `GRGA_OFFLINE=1` — fail fast instead of prompting to
199
+ download missing tools.
200
+ - `MIN_VERSIONS` map (empty by default) lets future releases pin minimum
201
+ versions per tool without more code.
202
+ - `GRGA_PREFER_SYSTEM=1` — invert tool resolution to prefer system-PATH
203
+ binaries over managed ones.
204
+
205
+ ### Changed
206
+
207
+ - `find_tool` now honors `GRGA_PREFER_SYSTEM` and applies `MIN_VERSIONS`.
208
+ - `check_tools` recognizes bundled binaries in both the XDG cache and
209
+ the legacy site-packages location.
210
+ - `scripts/download-tools.py` extended with `--platform`, `--out`,
211
+ `--write-hashes`, `--yes`. It is a *developer* tool only; wheel
212
+ builds no longer invoke it.
213
+ - Project metadata (license, urls, classifiers, `Development Status`)
214
+ hardened for PyPI publishing.
215
+
216
+ ### Fixed
217
+
218
+ - Downloads write to a `.part` file then atomically rename on success,
219
+ so aborted downloads no longer leave half-installed tools.
220
+
221
+ ## Unreleased
222
+
223
+ ### Breaking changes (chunk-level graph migration, schema v4)
224
+
225
+ - **The L2 graph moved from document-level to segment (chunk) level.**
226
+ `related --mode graph` now runs Personalized PageRank over segment
227
+ co-occurrence instead of whole-document co-occurrence. The doc-level graph
228
+ was structurally unable to produce signal on large single-file corpora
229
+ (a 22 MB novel collapsed every keyword into one `doc_path`, giving PPR no
230
+ real edges). See `docs/design/plan-2026-07-11-chunk-migration.md`.
231
+
232
+ The CLI surface is unchanged: `related --mode graph|dir|heading` keeps its
233
+ name and parameters, so existing caller skills need no edits.
234
+
235
+ - **`keyword_docs` table dropped.** The doc-level inverted index is gone.
236
+ `related` / `graph inspect` now read the new `keyword_chunks` table.
237
+
238
+ - **`keyword_pairs` schema rebuilt.** Primary key is now
239
+ `(directory, keyword_a, keyword_b, source)`. New columns: `source`
240
+ (`'chunk_cooccur'` | `'llm_promoted'`), `cooccur_count`, `npmi`. The old
241
+ `(scope, directory, keyword_a, keyword_b)` / `count` shape is gone.
242
+
243
+ - **One-shot v3 → v4 migration.** `ensure_schema` detects a legacy database
244
+ and, in a single atomic transaction: drops `keyword_docs`; rebuilds
245
+ `keyword_pairs`; salvages old promotion rows (`scope='llm_promoted'`) as
246
+ `source='llm_promoted'`; clears every other legacy (doc-level) pair — the
247
+ segment build recomputes it. `PRAGMA user_version` bumps 3 → 4. Failure
248
+ rolls back; no half-migrated state is left on disk.
249
+
250
+ - **`grga index build` gains chunk knobs.** `--chunk-mode {line,chars}`
251
+ (default `line`), `--chunk-max-chars` (600), `--chunk-min-chars` (300),
252
+ `--min-pair-count` (3). The old `--min-count` / `--max-terms` flags are
253
+ removed. `chars` mode splits on `heading > blank line > hard char cut`.
254
+
255
+ - **`deep_search` LLM pairs route through votes.** `KeywordLibrary` no
256
+ longer writes `keyword_pairs` directly; deep-search co-mentions accumulate
257
+ in `llm_extension_votes` and graduate via `grga index promote` — the same
258
+ anti-pollution gate the `suggest` flow already used. `keyword_counts`
259
+ (self-evolving heat table) is unchanged.
260
+
261
+ ### Added (chunk migration)
262
+
263
+ - **`graph_signals` metadata on `related --mode graph --json`.** Each result
264
+ carries `seed_chunk_df` (per-seed segment df), `total_chunks_in_scope`, and
265
+ `neighbor_edges` (PPR neighbour edges with `npmi` / `cooccur_count` /
266
+ `source`). Callers can classify seeds (hub / domain / rare) and re-rank
267
+ neighbours themselves — grga does no filtering / rerank / warning on top.
268
+ `--graph-signals-limit N` caps `neighbor_edges` (default 30). The block is
269
+ emitted only under `--json`; human-readable output stays terse.
270
+
271
+ - **`chunks` / `keyword_chunks` tables.** `chunks` records each chunk's line
272
+ range + char count; `keyword_chunks` is the keyword → chunk inverted index
273
+ (segment df is derivable via `COUNT(*) GROUP BY keyword`, no extra column).
274
+
275
+ ### Performance
276
+
277
+ - **`related --mode graph` is 2–5× faster across every corpus size.** Five
278
+ stacked optimisations (PERF plan §P0-1 through §P1-2), each with bench
279
+ data in its own commit:
280
+ - **P0-1** LRU cache of ``(edges, adjacency, indexed_graph)`` keyed on
281
+ ``(db_path, mtime, scope)`` (cap 8, mtime-invalidated by `index build`
282
+ / `suggest` / `promote`).
283
+ - **P0-2** Preview reuses the main PPR result — a single unrestricted
284
+ PPR now feeds both the scope-limited result list and the cross-scope
285
+ `crossed_scopes_preview` annotation. `--preview` no longer costs a
286
+ second full PPR.
287
+ - **P0-3** PPR internals: hoisted ``sum(neighbours.values())`` into an
288
+ ``out_degree`` map computed once per graph, and switched
289
+ ``seed_in_graph`` to a set for O(1) exclusion. Semantics pinned by a
290
+ reference-implementation diff test (score drift < 1e-9).
291
+ - **P1-1** `IndexedGraph` — integer-indexed adjacency (``list[list[(int,
292
+ float)]]``) with `personalized_pagerank_indexed`. The legacy dict API
293
+ now delegates. Eliminates the 2.33M `dict.get` calls that dominated
294
+ the profile.
295
+ - **P1-2** ``keyword_docs.keyword`` stored casefolded (schema v3, one-
296
+ shot migration bumps ``PRAGMA user_version`` 2→3, merging any
297
+ same-fold collisions in place). Every read path drops the non-
298
+ sargable ``LOWER(keyword)`` wrapper. The self-join in ``load_edges``
299
+ keeps a ``+column`` optimiser barrier so SQLite still joins on
300
+ ``(directory, doc_path)`` instead of range-scanning ``(directory,
301
+ keyword > ?)``.
302
+
303
+ Bench (600 docs / 3 scopes / 7414 keyword_docs rows, 5-call avg):
304
+
305
+ | | v2.2 baseline | v2.3 target |
306
+ | ------------- | ------------- | ----------- |
307
+ | scoped | 151 ms | **42 ms** |
308
+ | global | 350 ms | **94 ms** |
309
+ | cross-scope | 354 ms | **72 ms** |
310
+ | preview=True | 527 ms (3.8×) | **96 ms** |
311
+ | PPR own time (3 calls) | 1.83 s | **0.18 s** |
312
+
313
+ Numpy fallback is intentionally not shipped (PERF plan §P2-1); the pure-
314
+ Python indexed path lands well inside the 100 ms perception threshold
315
+ for personal-corpus graphs.
316
+
317
+ ### Changed
318
+
319
+ - **Storage model reframed as user-scoped.** grga's DB is a single per-user
320
+ knowledge graph (`~/.config/grga/deep_keywords.db`). All `index build`
321
+ scopes write into it; `--directory` is a query-time filter, not a
322
+ partition key. See "Storage model (user-scoped)" in SKILL.md.
323
+
324
+ ### Breaking changes
325
+
326
+ - **`related --mode heading` now searches across scopes by default.** Pass
327
+ `--same-scope` to restore the old scope-limited behaviour. Heading paths
328
+ are structural filters (`Install > macOS` means the same thing across
329
+ corpora), so cross-scope is the more useful default.
330
+
331
+ - **`grga search` multi-word semantics: space now means AND, not OR.**
332
+ Adjacent terms with no explicit operator (e.g. `grga search "韩立 师傅"`)
333
+ used to compile to `韩立 || 师傅` (union of matches). They now compile to
334
+ `韩立 && 师傅` (both terms on the same line), matching the Google / ripgrep
335
+ intuition and the existing skill docs. When implicit AND fires, grga prints
336
+ a one-line notice to stderr:
337
+
338
+ ```
339
+ [grga] multi-term query treated as AND: "韩立 师傅" → "韩立" AND "师傅"
340
+ ```
341
+
342
+ Explicit `||` / `OR` and `&&` / `AND` are unchanged. Pass `--implicit-or`
343
+ to restore the v1 space-is-OR behaviour, or `--implicit-and` to force the
344
+ new default and suppress the notice. Python callers can pass
345
+ `search_directory(..., implicit_operator="or")` for the same effect.
346
+
347
+ Deep-search subprocess invocations of `grga search` continue to use
348
+ `--implicit-or` internally so the deep-search planner's expressions keep
349
+ their previous semantics.
350
+
351
+ ### Added
352
+
353
+ - **`related --cross-scope` flag** (`mode=graph` only): keep seeds in
354
+ `--directory` but let PPR expansion cross into every other scope in the
355
+ shared DB. Off by default — cross-corpus association is an *escalation*,
356
+ not the default retrieval path.
357
+
358
+ - **`related --same-scope` flag** (`mode=heading` only): force heading
359
+ matches to stay within `--directory` (the previous default).
360
+
361
+ - **`crossed_scopes_preview` metadata on `related --mode graph --json`.**
362
+ Opt-in via `--preview`: the graph JSON envelope then carries a
363
+ `crossed_scopes_preview` object (`would_add`, `top_other_scopes`,
364
+ `top_bridge_keywords`) so a caller can see what opening cross-scope would
365
+ pull in *before* committing to it. It is opt-in because the preview runs
366
+ a full-graph PPR whose cost scales with every other scope in the shared
367
+ DB. The graph `--json` output is now an envelope
368
+ `{results, crossed_scopes_preview}` rather than a bare array (`dir` /
369
+ `heading` modes still return arrays).
370
+
371
+ - **`Related` objects now carry `directory` and `qualified_path`.** Every
372
+ related result records the indexed scope root it came from, so
373
+ cross-scope / global queries no longer conflate identically-named files
374
+ (`README.md`, `docs/install.md`) from different scopes.
375
+
376
+ - **`grga graph inspect <keyword>` command.** Reports a keyword's
377
+ `docs_by_scope` distribution, `top_cooccurring_keywords`, and a
378
+ `suspected_polysemy` heuristic (per-scope neighbour sets with pairwise
379
+ Jaccard < 0.2) so callers can spot same-name-different-meaning collisions
380
+ before enabling `--cross-scope`.
381
+
382
+ - **`--max-total / -N` global result cap on `grga search`.** Defaults to
383
+ `1000` (`0` disables). grga now streams rga's JSON output, and once the
384
+ cap is hit it stops reading, kills the rga subprocess, and returns a
385
+ partial result with `truncated: true` in the envelope. Layer 2 metadata
386
+ (`occurrence_index` / `total_occurrences` / `line_ratio` / `nearby_hits`)
387
+ is computed *after* truncation, so the counts reflect the returned slice
388
+ rather than the untrimmed rga output.
389
+
390
+ This closes the "22 MB single novel + two-word query returned 111 525 hits
391
+ in 40 s" pathology that previously wedged caller agents against their
392
+ default 60 s tool timeout.
393
+
394
+ Deep-search runs its own generous cap (`DEEP_SEARCH_MAX_TOTAL = 20000` per
395
+ iteration, exposed as the `max_total` parameter on
396
+ `_run_grga_search`) because its planner needs many raw hits per query for
397
+ keyword ranking. Set `max_total=None`/`0` on that helper to disable.
398
+
399
+ - **`--strict-cap` on `grga search`.** Instead of truncating, run a cheap
400
+ `rga --count-matches` pre-check and return
401
+ `{"ok": false, "error": "too_many_matches", "total_hits": N, "message": …}`
402
+ when the cap is exceeded. Useful when callers would rather fail loudly
403
+ than reason about partial data.
404
+
405
+ - `search_directory(...)` gains `max_total`, `strict_cap`, and
406
+ `implicit_operator` keyword arguments. `build_rga_command_preview(...)`
407
+ gains `implicit_operator`.
408
+
409
+ - `grga search --json` output now always includes a `truncated: bool` field
410
+ and, when adjacency was treated as implicit AND, an `implicit_notice`
411
+ string. See `docs/api.md`.
412
+
413
+ ### Fixed
414
+
415
+ - Very large rga result sets no longer force grga to buffer the entire
416
+ stdout (24 MB in the pathology case) before parsing.
grga-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024-2026 grga maintainers
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
grga-0.2.0/MANIFEST.in ADDED
@@ -0,0 +1,9 @@
1
+ include LICENSE
2
+ include README.md
3
+ include CHANGELOG.md
4
+ recursive-include src/grga/prompts *.txt
5
+ recursive-include src/grga/skills *
6
+
7
+ global-exclude __pycache__
8
+ global-exclude *.py[cod]
9
+ global-exclude .DS_Store
grga-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,192 @@
1
+ Metadata-Version: 2.4
2
+ Name: grga
3
+ Version: 0.2.0
4
+ Summary: A CLI and Tkinter GUI for ripgrep-all search with L2 graph index and LLM-assisted deep search
5
+ Author: grga maintainers
6
+ License: MIT License
7
+
8
+ Copyright (c) 2024-2026 grga maintainers
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/grga-tools/grga
29
+ Project-URL: Repository, https://github.com/grga-tools/grga
30
+ Project-URL: Documentation, https://github.com/grga-tools/grga#readme
31
+ Project-URL: Changelog, https://github.com/grga-tools/grga/blob/main/CHANGELOG.md
32
+ Project-URL: Issues, https://github.com/grga-tools/grga/issues
33
+ Keywords: ripgrep,rga,search,gui,cli,llm,graph
34
+ Classifier: Development Status :: 4 - Beta
35
+ Classifier: Environment :: Console
36
+ Classifier: Environment :: X11 Applications
37
+ Classifier: Intended Audience :: Developers
38
+ Classifier: Intended Audience :: End Users/Desktop
39
+ Classifier: License :: OSI Approved :: MIT License
40
+ Classifier: Operating System :: OS Independent
41
+ Classifier: Programming Language :: Python :: 3
42
+ Classifier: Programming Language :: Python :: 3 :: Only
43
+ Classifier: Programming Language :: Python :: 3.8
44
+ Classifier: Programming Language :: Python :: 3.9
45
+ Classifier: Programming Language :: Python :: 3.10
46
+ Classifier: Programming Language :: Python :: 3.11
47
+ Classifier: Programming Language :: Python :: 3.12
48
+ Classifier: Topic :: Desktop Environment
49
+ Classifier: Topic :: Text Processing :: Indexing
50
+ Classifier: Topic :: Utilities
51
+ Requires-Python: >=3.8
52
+ Description-Content-Type: text/markdown
53
+ License-File: LICENSE
54
+ Requires-Dist: typer>=0.9
55
+ Requires-Dist: openai>=1.0
56
+ Requires-Dist: jieba>=0.42
57
+ Requires-Dist: markdown-it-py>=3.0
58
+ Provides-Extra: standalone
59
+ Requires-Dist: pyinstaller>=6.0; extra == "standalone"
60
+ Provides-Extra: test
61
+ Requires-Dist: pytest>=7.0; extra == "test"
62
+ Dynamic: license-file
63
+
64
+ # grga
65
+
66
+ > **一款向文档学习、却不依赖文档,可迁移的0成本检索器。** — 从你的文档中自动发现概念连接,学习到的知识可直接用于同领域文档的检索。完全本地运行,不依赖 Embedding 和 LLM。
67
+
68
+ `grga` 是一个基于 `ripgrep-all` 的 Agent 友好全文搜索工具,提供命令行入口和 Tkinter 图形界面。它封装 `rga`、`rg` 等底层工具,支持在普通文本、Markdown、PDF、Office 文档等文件中搜索内容,也支持只按文件路径搜索。
69
+
70
+ 项目目标是把常用搜索能力、工具链检查和内置工具管理集成到一个 Python 包中,并支持打包成 standalone 可执行文件,尽量减少用户手动配置系统命令的成本。
71
+
72
+ ### 与普通 grep 有什么不同?
73
+
74
+ grga 是"grep 家族的最外层封装":底层复用 `ripgrep` 的速度和 `ripgrep-all`
75
+ 的文档适配器,再往上叠**上下文扩展**、**共现关系图**、**LLM 深度搜索**几层能力。下表按"从 grep 一路加码到 grga"的顺序对比各工具的支持程度,并给出
76
+ grga 侧的具体命令与依赖:
77
+
78
+ | 能力 | grep | `rg` | `rga` | grga 命令 / 用法 | 依赖 / 配置 |
79
+ |---|:---:|:---:|:---:|---|---|
80
+ | 纯文本 / 源码搜索 | ✅ | ✅ | ✅ | `grga search 关键词` | 无 |
81
+ | 大仓库速度(并发 + `.gitignore`) | ❌ 单线程 | ✅ 多线程 | ✅ 继承 rg | 同上,自动透传 `rg` | `rg`(首次 `grga tools install` 自动下载) |
82
+ | PDF / Office / EPUB / 归档 / sqlite | ❌ | ❌ | ✅ 内建适配器 | 同上,自动透传 `rga` | `rga` + `rga-preproc` + `pandoc` + `pdftotext`(自动下载) |
83
+ | 查询表达式(短语 / 正则 / glob / `\|\|` / `&&` / 括号) | ⚠️ 仅正则 | ⚠️ 仅正则 | ⚠️ 仅正则 | 默认多词 **AND**,`--implicit-or` 恢复 v1 OR 语义 | 无 |
84
+ | 路径搜索(只匹配文件名,不读内容) | ⚠️ 需配合 `find` | ⚠️ 需 `--files \| rg` | ⚠️ 同 rg | `grga search --path-only` | 无 |
85
+ | 命令预览与试探(先看规模再决定读不读) | ❌ | ❌ | ❌ | `grga preview` / `grga probe` | 无 |
86
+ | Markdown / PDF 的段落 / 章节上下文扩展 | ❌ | ❌ | ❌ | `grga expand`(按行 / 段落 / 章节逐级) | |
87
+ | 关键词共现关系图(离线索引 + 亚毫秒查询) | ❌ | ❌ | ❌ | `grga index build` → `grga related` / `grga graph inspect` | |
88
+ | LLM 深度搜索、查询改写、结果重排 | ❌ | ❌ | ❌ | `grga deep-search` / `grga suggest` | OpenAI 兼容 API;至少 `GRGA_OPENAI_API_KEY` + `GRGA_OPENAI_MODEL`(详见 [docs/env-vars.md](docs/env-vars.md)) |
89
+ | Tk 图形界面 | ❌ | ❌ | ❌ | `grga gui` 或 `grga-gui` | 系统自带 Tk |
90
+ | Agent 友好(`--json` + 稳定退出码契约) | ❌ | ⚠️ 部分 | ⚠️ 部分 | `grga skill install` 分发内置 agent 技能包 | 错误契约见 [docs/exit-codes.md](docs/exit-codes.md) |
91
+
92
+ 所有环境变量都可以写进 `$XDG_CONFIG_HOME/grga/.env`(首次运行会自动生成
93
+ 一份注释模板),完整清单见 [docs/env-vars.md](docs/env-vars.md)。更多 CLI
94
+ 参数细节见 [docs/CLI.md](docs/CLI.md)。
95
+
96
+ > **外部工具运行时下载**:自 0.2.0 起,wheel 不再内嵌 `rg` / `rga` / `pandoc` / `fzf` / `pdftotext` 等二进制文件。首次使用时会按需下载到用户缓存目录(见下文),也可提前运行 `grga tools install`。
97
+ >
98
+ > **许可证**:`grga` 本身以 **MIT** 许可证发布(见 [LICENSE](LICENSE))。它调用的外部工具各自遵循其上游许可证(`rg`:MIT/Unlicense;`rga`:AGPL;`pandoc`:GPL;`fzf`:MIT),请分别遵守。
99
+
100
+
101
+ ### 与其它 GraphRAG 有什么不同
102
+ - 不依赖 LLM 抽实体
103
+ - 不依赖 Embedding 和向量库
104
+ - 仅依赖统计学从语料中学习短语以及进行短语的自连接,学习到的内容存入 Sqlite,与原始语料库解绑。
105
+ - 一方面可以只选取部分代表性语料进行学习;另一方面可以直接应用于其它同领域文档库。代价是理论上的精度漂移,实践中完全可接受。如果漂移到不可接受,在新语料上再增量学习一遍即可。
106
+ - 不过分追求 Recall 的准确度,提供5类原语动词,依赖 Agent 进行多轮循环检索补齐证据。
107
+
108
+ ## 快速上手
109
+
110
+ ```bash
111
+ # 安装 grga
112
+ pip install grga
113
+ ```
114
+
115
+ 安装后即可**直接搜索纯文本类文件**(`.txt` / `.md` / 源码 / 日志 / JSON / CSV
116
+ 等),无需额外配置:
117
+
118
+ ```bash
119
+ cd /path/to/your/documents
120
+ grga search "面壁计划" # 全文搜索
121
+ grga search "面壁计划" --path-only # 只按文件名搜
122
+ grga gui # 图形界面(或 grga-gui)
123
+ grga --help # 查看命令帮助
124
+ ```
125
+
126
+ **给你的 AI Agent 装 skill**(可选,一行搞定,让 Kilo / Claude Code / QwenPaw 等直接学会用 grga):
127
+
128
+ ```bash
129
+ grga skill install --to ~/.kilocode/skills # 换成你的 agent skill 目录即可
130
+
131
+ # 预学习(可选,视语料规模需要花费一定时间,但可以提供图扩展能力)
132
+ cd /path/to/your/documents
133
+ grga index build
134
+ ```
135
+ `docs/search-demo`下提供了“纯LLM回答”、“简单文本检索回答”、“图扩展回答”三种搜索结果,可以看到明显的差别。
136
+
137
+ ### 按场景选用
138
+
139
+ **搜索 PDF / Office / 电子书** —— 首次需装一次工具链(`rga` / `pandoc` /
140
+ `pdftotext` 等,之后自动复用):
141
+
142
+ ```bash
143
+ grga tools install
144
+ grga search "面壁计划" -g "*.pdf"
145
+ ```
146
+
147
+ **上下文扩展** —— 把命中扩展成段落 / 章节:
148
+
149
+ ```bash
150
+ grga expand ... # 按行 / 段落 / 章节逐级扩展
151
+ ```
152
+
153
+ **关键词共现关系图**(不依赖 LLM,先建索引再查):
154
+
155
+ ```bash
156
+ cd /path/to/your/documents
157
+ grga index build
158
+ grga related 面壁计划
159
+ grga graph inspect 面壁计划
160
+ ```
161
+
162
+ **LLM 深度搜索**(需配置 OpenAI 兼容凭据,见 [docs/env-vars.md](docs/env-vars.md)):
163
+
164
+ ```bash
165
+ export GRGA_OPENAI_API_KEY=sk-...
166
+ export GRGA_OPENAI_MODEL=gpt-4o-mini
167
+ # or write them to $HOME/.config/grga/.env
168
+
169
+ grga deep-search "面壁计划"
170
+ ```
171
+
172
+ > 注:作者到现在还没用过深度搜索功能,基本靠文本检索+图扩展就解决问题了。
173
+
174
+
175
+ **打包成可执行文件 / 离线部署 / 作为 agent 原语接入** —— 见
176
+ [docs/install-and-usage.md](docs/install-and-usage.md):
177
+
178
+ ```bash
179
+ python -m build # 构建 wheel
180
+ python scripts/build-onefile.py --clean # 构建单文件可执行程序
181
+ ```
182
+
183
+ > 完整安装、工具下载、镜像/打包构建、面向 AI Agent 的进阶说明都在
184
+ > **[docs/install-and-usage.md](docs/install-and-usage.md)**;环境变量清单见
185
+ > [docs/env-vars.md](docs/env-vars.md),CLI 参数见 [docs/CLI.md](docs/CLI.md)。
186
+
187
+ ## 致谢
188
+
189
+ `grga` 的诞生离不开这两个工具在日常开发中的持续加成:
190
+
191
+ - **[QwenPaw](https://github.com/liunux4odoo/qwenpaw)** —— 本地多智能体框架。`grga` 从"检索能力"演进到"Agent-Native 检索契约",很多设计决策(skill 分发、退出码契约、`--json` schema、caller 反思循环)都是先在 QwenPaw 的 agent 场景里被反复打磨,才沉淀成规格的。
192
+ - **[Kilo Code](https://kilocode.ai)** —— IDE 里的 AI 编程助手。`grga` 的大部分实装、测试、重构都由它落地;作者只写"目标 / 约束 / 边界",实现细节交给它——这套"规格给约束,实现给自由"的分工正是这个项目能小而快演进的关键。