@calcit/procs 0.12.48 → 0.12.50

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.
@@ -0,0 +1,223 @@
1
+ # RFC: `:features` 函数能力标记 + `:js-object` 类型收敛
2
+
3
+ 状态:Draft
4
+ 日期:2026-07-08
5
+
6
+ ---
7
+
8
+ ## 1. 概要
9
+
10
+ 在函数 schema 中新增 `:features` 属性(HashSet),标记当前函数可使用的语言能力子集。默认空集合。使用 JS interop 语法(`aget`、`&js-object`、`new` 等)要求函数显式标记 `:features $ #{} :js-ffi`,从类型层面将 FFI 隔离在有限的 binding 函数中。
11
+
12
+ 同步新增 `:js-object` 类型注解,表示 JavaScript FFI 操作产生的外部 opaque 数据,阻断 JS 对象向纯 Calcit 函数的无意识泄露。
13
+
14
+ ## 2. 动机
15
+
16
+ ### 问题
17
+
18
+ 当前 JS FFI 函数(`is_js_syntax_procs` 收录 21 个)在 `codegen::codegen_mode()` 下全局放行,任何函数都可以随意调用:
19
+
20
+ ```cirru
21
+ ;; 任意函数都能直接调用 FFI,无任何约束
22
+ defn my-pure-fn (x)
23
+ aget x |someProp ;; 静默通过
24
+ &js-object $ {} (:a 1) ;; 静默通过
25
+ ```
26
+
27
+ 这导致:
28
+
29
+ - **边界模糊**:无法从代码中区分"纯 Calcit 函数"和"FFI binding 函数"
30
+ - **审查困难**:排查 JS interop 使用点需要全文搜索 21 个函数名
31
+ - **类型不安全**:JS 对象可以自由流经所有函数,静态分析无法追踪外部数据
32
+ - **重构风险**:修改 FFI 层时无法确定影响范围
33
+
34
+ ### 目标
35
+
36
+ | 目标 | 说明 |
37
+ | -------- | ------------------------------------------------------------------------ |
38
+ | 权限收敛 | 只有 schema 中声明 `:js-ffi` 的函数才能调用 JS FFI |
39
+ | 类型隔离 | `:js-object` 类型标注 JS 对象,跨非 FFI 边界时产生告警 |
40
+ | 渐进迁移 | 先 warning 后 error,给生态适配时间 |
41
+ | 可扩展 | `:features` 设计预留 `:side-effects`、`:async`、`:unsafe` 等未来能力标记 |
42
+
43
+ ## 3. 设计
44
+
45
+ ### 3.1 `:features` — Schema 属性
46
+
47
+ 在 `CalcitFnTypeAnnotation` 中新增 `features` 字段:
48
+
49
+ ```rust
50
+ pub struct CalcitFnTypeAnnotation {
51
+ pub generics: Arc<Vec<Arc<str>>>,
52
+ pub where_bounds: Arc<Vec<CalcitGenericBound>>,
53
+ pub arg_types: Vec<Arc<CalcitTypeAnnotation>>,
54
+ pub return_type: Arc<CalcitTypeAnnotation>,
55
+ pub fn_kind: SchemaKind,
56
+ pub rest_type: Option<Arc<CalcitTypeAnnotation>>,
57
+ /// Feature flags declared in schema, e.g. `:features $ #{} :js-ffi`
58
+ pub features: Arc<HashSet<EdnTag>>,
59
+ }
60
+ ```
61
+
62
+ ### 3.2 `:js-object` — 类型注解
63
+
64
+ 在 `CalcitTypeAnnotation` 枚举中新增变体:
65
+
66
+ ```rust
67
+ pub enum CalcitTypeAnnotation {
68
+ // ... 现有变体不变 ...
69
+ /// JavaScript FFI external object (opaque to Calcit type system)
70
+ JsObject,
71
+ }
72
+ ```
73
+
74
+ EDN 序列化:tag 名为 `:js-object`。
75
+
76
+ ### 3.3 Canonical Cirru EDN 形态
77
+
78
+ ```cirru
79
+ %{} :CodeEntry
80
+ :doc "|FFI binding for DOM event handling"
81
+ :code $ quote
82
+ defn handle-click (event)
83
+ let
84
+ target $ aget event |target
85
+ value $ aget target |value
86
+ println value
87
+ :schema $ :: :fn
88
+ {}
89
+ :args $ [] :js-object
90
+ :return :unit
91
+ :features $ #{} :js-ffi
92
+ ```
93
+
94
+ 一行 Cirru EDN 示例:
95
+
96
+ ```cirru
97
+ (:: :fn ({} (:args ([] :js-object)) (:return :unit) (:features (#{} :js-ffi))))
98
+ ```
99
+
100
+ ### 3.4 校验流程
101
+
102
+ ```mermaid
103
+ flowchart TD
104
+ A[preprocess_expr 符号解析] --> B{调用目标是 JS FFI 函数?}
105
+ B -->|否| C[正常预处理]
106
+ B -->|是| D{当前正在编译的 def 有 :js-ffi?}
107
+ D -->|是| C
108
+ D -->|否| E[产生 Error/Warning]
109
+
110
+ F[函数 A 返回 JsObject] --> G[函数 B 接收 JsObject 作为参数]
111
+ G --> H{B 的 schema 有 :js-ffi?}
112
+ H -->|否| I[类型告警: JsObject 流入非 FFI 函数]
113
+ H -->|是| J[放行]
114
+ ```
115
+
116
+ ### 3.5 校验位置
117
+
118
+ | 校验点 | 位置 | 触发条件 |
119
+ | ----------------- | ------------------------------------------ | ----------------------------------------------------------------------------------- |
120
+ | FFI 函数调用 | `preprocess_expr` → `preprocess_list_call` | head 解析为 `is_js_syntax_procs` 且当前编译上下文中 `defn` 无 `:js-ffi` |
121
+ | `ns js/...` 语法 | `preprocess_expr` 中 `RawCode(Js, _)` 分支 | 同上的 features 检查 |
122
+ | `JsObject` 跨边界 | `check_user_fn_arg_types` | 实参类型为 `JsObject` 且形参 schema 非 `JsObject` 且函数无 `:js-ffi` |
123
+ | `JsObject` 返回值 | `check_function_return_type` | 返回值推断为 `JsObject` 且函数 schema 的 `:return` 非 `JsObject` 且函数无 `:js-ffi` |
124
+
125
+ ### 3.6 默认值与兼容
126
+
127
+ - `:features` 默认 `Arc::new(HashSet::new())`,即空集合 = 无任何特殊能力
128
+ - `:js-object` 类型的默认行为:在无 `:js-ffi` 的函数中作为 `:dynamic` 等效处理(宽松模式),或产生类型告警(严格模式)
129
+ - 第一阶段以 **warning** 形式上线,通过 `--warn-ffi` flag 控制,后续升级为 error
130
+
131
+ ## 4. FFI 函数清单
132
+
133
+ 当前 `is_js_syntax_procs()` 收录的函数,全部受 `:js-ffi` 管控:
134
+
135
+ | 函数 | 用途 | 返回类型建议 |
136
+ | --------------------------------------- | -------------------- | -------------------------- |
137
+ | `aget` / `js-get` | JS 属性读取 | `:js-object` 或 `:dynamic` |
138
+ | `aset` / `js-set` | JS 属性写入 | `:unit` |
139
+ | `js-delete` | JS 属性删除 | `:unit` |
140
+ | `exists?` | JS 属性存在性检查 | `:bool` |
141
+ | `instance?` | JS instanceof | `:bool` |
142
+ | `&js-object` | 创建 JS 对象 | `:js-object` |
143
+ | `js-array` | 创建 JS 数组 | `:js-object` |
144
+ | `js-await` / `js-for-await` | Promise/异步迭代 | `:js-object` |
145
+ | `new` | JS new 构造 | `:js-object` |
146
+ | `set!` | JS 赋值 | `:unit` |
147
+ | `to-js-data` | Calcit → JS 转换 | `:js-object` |
148
+ | `to-calcit-data` | JS → Calcit 转换 | `:dynamic` |
149
+ | `extract-cirru-edn` / `to-cirru-edn` | EDN 序列化 | `:string` / `:dynamic` |
150
+ | `&raw-code` | 原始 JS 代码 | `:js-object` |
151
+ | `timeout-call` | 异步定时 | `:js-object` |
152
+ | `foldl` | fold-left(JS 实现) | 依赖元素类型 |
153
+ | `load-console-formatter!` / `printable` | 控制台输出 | `:unit` |
154
+
155
+ > **注**:`foldl` 虽然在 JS FFI 列表中,但它是纯函数式的 fold 操作,未来可考虑移出 FFI 列表或单独归类。
156
+
157
+ ## 5. 实现计划
158
+
159
+ ### Phase 1: 数据层(~2h)
160
+
161
+ - [ ] `CalcitFnTypeAnnotation.features` 字段新增
162
+ - [ ] EDN 序列化 / 反序列化(`to_schema_edn` / `parse_loaded_schema_annotation`)
163
+ - [ ] `CalcitTypeAnnotation::JsObject` 变体新增 + 全量 match 补齐
164
+ - [ ] `JsObject` 的 EDN tag 解析(`:js-object`)
165
+
166
+ ### Phase 2: 校验层(~4h)
167
+
168
+ - [ ] `preprocess_list_call` 中 FFI 调用权限校验
169
+ - [ ] `preprocess_expr` 中 `ns js/...` 语法的 FFI 权限校验
170
+ - [ ] 编译上下文传递:在 `ensure_ns_def_preprocessed` 链路中追踪当前 `defn` 的 features
171
+ - [ ] `check_user_fn_arg_types` 中 `JsObject` 跨边界告警
172
+ - [ ] `check_function_return_type` 中 `JsObject` 返回值告警
173
+ - [ ] `--warn-ffi` CLI flag(默认开启 warning 模式)
174
+
175
+ ### Phase 3: 类型推断(~2h)
176
+
177
+ - [ ] `&js-object`、`js-array`、`new` 等 FFI 函数的返回值推断为 `JsObject`
178
+ - [ ] `aget` 对 `JsObject` 参数的接收校验
179
+ - [ ] `to-calcit-data` 作为 `JsObject → Dynamic` 的转换边界
180
+
181
+ ### Phase 4: 迁移与测试(~3h)
182
+
183
+ - [ ] 现有 `calcit/` 下使用 JS FFI 的测试文件 schema 迁移
184
+ - [ ] 新增 `calcit/test-ffi-features.cirru` 测试用例:
185
+ - 无 `:js-ffi` 调用 `aget` → 预期 warning/error
186
+ - 有 `:js-ffi` 调用 `aget` → 预期通过
187
+ - 无 `:js-ffi` 接收 `JsObject` 参数 → 预期类型告警
188
+ - 有 `:js-ffi` 接收 `JsObject` → 预期通过
189
+ - `to-calcit-data` 边界转换后不再需要 `:js-ffi`
190
+ - [ ] 文档更新(`docs/features.md`)
191
+
192
+ ### Phase 5: 严格模式(后续版本)
193
+
194
+ - [ ] 缺少 `:js-ffi` 的 FFI 调用从 warning 升级为 hard error
195
+ - [ ] `:js-object` 可能扩展到区分 `:js-object` vs `:js-array` vs `:js-promise`
196
+
197
+ ## 6. 风险与缓解
198
+
199
+ | 风险 | 影响 | 缓解 |
200
+ | --------------------------------------- | ------------------ | ---------------------------------------------------- |
201
+ | 现有 Cirru 代码大量使用 FFI 但无 schema | 迁移工作量大 | 先 warning 后 error;提供迁移脚本 |
202
+ | `foldl` 列入 FFI 引起误报 | 纯函数被标记为 FFI | 考虑将 `foldl` 移出 `is_js_syntax_procs` 或单独分类 |
203
+ | 宏生成的函数自动获取 `:js-ffi` | 权限继承不明确 | 宏生成函数的 features 从宏定义处显式声明,不自动继承 |
204
+ | 第三方库适配周期长 | 生态断裂 | 预留至少一个大版本的 warning-only 过渡期 |
205
+
206
+ ## 7. 未来扩展
207
+
208
+ `:features` 设计为 `HashSet<EdnTag>`,天然支持未来新增能力标记:
209
+
210
+ | 标记 | 含义 |
211
+ | --------------- | ------------------------------------------------ |
212
+ | `:js-ffi` | 可使用 JS interop 语法(本次实现) |
213
+ | `:side-effects` | 函数可能产生副作用(未来:配合 effects graph) |
214
+ | `:async` | 函数内部使用异步操作(未来:配合 effect 系统) |
215
+ | `:unsafe` | 函数绕过了类型检查(未来:配合 unsafe block) |
216
+ | `:host` | 函数需要宿主环境 API(未来:配合多 target 编译) |
217
+
218
+ ## 8. 参考资料
219
+
220
+ - 现有 JS FFI 函数定义:`src/builtins.rs:649` `is_js_syntax_procs()`
221
+ - 现有 Schema 结构:`src/calcit/type_annotation.rs:3826` `CalcitFnTypeAnnotation`
222
+ - 现有预处理入口:`src/runner/preprocess/mod.rs` `preprocess_expr()`
223
+ - 相关 RFC:[Type Slot 机制](./04-13-type-slot-mechanism-rfc.md)、[泛型 `:where` 约束](./05-31-generic-where-bounds-mfs.md)
@@ -0,0 +1,195 @@
1
+ # RFC: 文档知识节点与增量关系索引
2
+
3
+ 状态:Draft
4
+ 日期:2026-07-19
5
+ 关联:`docs/docs-indexing.md`、`07-06-semantic-tree-navigation-rfc.md`
6
+
7
+ ## 1. 概要
8
+
9
+ 在保持 Markdown 可读、可 Git 管理的前提下,为 `cr docs` 增加一层结构化的文档知识索引:
10
+
11
+ ```text
12
+ Markdown / Calcit snapshot
13
+
14
+ 文档节点与关系解析
15
+
16
+ ~/.config/calcit/docs-cache/
17
+
18
+ 搜索、跳转、LLM context、缺失检查
19
+ ```
20
+
21
+ Markdown 和 Calcit 源码仍然是事实来源;缓存只是可以删除并重新生成的派生数据。
22
+
23
+ ## 2. 目标
24
+
25
+ 1. 文档可以表达稳定的知识节点,而不要求一次性拆分所有 Markdown。
26
+ 2. 通过 `id`、`parent`、`related`、`requires`、`leads_to` 组织树与图关系。
27
+ 3. 通过 `namespace/definition` 将文档节点关联到真实 Calcit 定义。
28
+ 4. 使用用户级 JSON 文本缓存,不引入 SQLite 或其他二进制数据库。
29
+ 5. 文件内容变化时增量失效;解析器或 schema 变化时全量失效。
30
+ 6. 能发现孤立节点、断链、未文档化定义和缺少必要章节。
31
+
32
+ ## 3. 非目标
33
+
34
+ - 当前不引入 RDF、SPARQL、Neo4j 或 SurrealDB。
35
+ - 当前不强制把每个章节拆成独立文件。
36
+ - 当前不把缓存当作用户需要手工维护的知识源。
37
+ - 当前不改变既有 `cr docs search/read` 的默认输出语义。
38
+
39
+ ## 4. 文档元数据
40
+
41
+ 现有字段继续保留:`title`、`summary`、`scope`、`kind`、`category`、`aliases`、`entry_for`。
42
+
43
+ 新增字段采用稳定 ID 和关系字段:
44
+
45
+ ```yaml
46
+ id: api/list/nth
47
+ code_refs:
48
+ - calcit.core/nth
49
+ parent: structure/list
50
+ related:
51
+ - api/list/get
52
+ requires:
53
+ - concept/indexing
54
+ leads_to:
55
+ - example/list-access
56
+ ```
57
+
58
+ 实现阶段允许使用 `x-calcit` / `extensions` 命名空间保存项目专属字段,避免污染通用 frontmatter。
59
+
60
+ `id` 是语义身份;`namespace/definition` 只是代码引用,不能互相替代。
61
+
62
+ ## 5. 缓存模型
63
+
64
+ 默认位置:
65
+
66
+ ```text
67
+ ~/.config/calcit/docs-cache/v1/<project-id>/
68
+ ```
69
+
70
+ 项目身份至少包含项目根目录,避免不同项目的相对路径发生冲突。缓存还记录解析器/schema 版本和内置 Calcit core snapshot 的定义指纹;任一输入变化都会触发重建。
71
+
72
+ 缓存由两部分组成:
73
+
74
+ ```json
75
+ {
76
+ "nodes": [],
77
+ "edges": []
78
+ }
79
+ ```
80
+
81
+ 每个文件还需要记录:
82
+
83
+ ```json
84
+ {
85
+ "path": "docs/structures/list.md",
86
+ "content_hash": "...",
87
+ "parser_version": 1,
88
+ "schema_version": 1,
89
+ "node_ids": ["structure/list"]
90
+ }
91
+ ```
92
+
93
+ 内容 hash 不变时可以复用文件级解析结果;parser/schema 版本变化时必须重新解析。
94
+
95
+ ## 6. 关系模型
96
+
97
+ 树关系:
98
+
99
+ - `parent`
100
+ - `contains`
101
+ - `leads_to`
102
+
103
+ 语义关系:
104
+
105
+ - `related`
106
+ - `requires`
107
+ - `documents`
108
+ - `implements`
109
+ - `example_of`
110
+
111
+ 节点采用树形入口,关系采用独立边表,避免把循环关系硬塞进嵌套结构。
112
+
113
+ ## 7. 分阶段计划
114
+
115
+ ### Phase A:元数据兼容与增量缓存基础
116
+
117
+ - 扩展 frontmatter 解析器,保留未知扩展字段的兼容空间;
118
+ - 定义节点、边、文件缓存的 JSON schema;
119
+ - 增加内容 hash、parser version、schema version;
120
+ - 保持现有搜索和读取命令兼容;
121
+ - 添加解析和失效测试。
122
+
123
+ ### Phase B:导航查询
124
+
125
+ 已完成第一版面向知识节点的查询:
126
+
127
+ ```bash
128
+ cr docs graph build
129
+ cr docs graph check
130
+ cr docs graph children <node>
131
+ cr docs graph related <node>
132
+ cr docs graph path <from> <to>
133
+ ```
134
+
135
+ 查询当前从 Markdown 构建 JSON cache,并通过双向 BFS 查找关系路径:
136
+
137
+ ```bash
138
+ cr docs graph path core/features/list core/run/edit-tree
139
+ # core/features/list -> core/run/query -> core/run/edit-tree
140
+ ```
141
+
142
+ 当前已支持通过 `code_refs` 反查关联文档节点;后续再增加源码定义、示例和正文聚合:
143
+
144
+ ```bash
145
+ cr docs graph explain <definition>
146
+ ```
147
+
148
+ ### Phase C:完整性检查
149
+
150
+ ```bash
151
+ cr docs graph check
152
+ cr docs graph orphans
153
+ cr docs graph missing
154
+ ```
155
+
156
+ 检查断链、孤立节点、无正文定义、缺少示例和缺少必要章节。
157
+
158
+ 当前已实现:
159
+
160
+ - `cr docs graph check`:检查关系边是否指向已知文档节点;
161
+ - `cr docs graph missing [--ns <prefix>] [--limit <n>]`:按 namespace 分批检查带有 snapshot 文档说明、但没有 `code_refs` 的定义;
162
+ - `cr docs graph orphans`:检查没有任何关系的文档节点。
163
+
164
+ ## 8. 验证标准
165
+
166
+ - 删除缓存后,查询结果与重新构建结果一致;
167
+ - 只修改一个 Markdown 文件时,不重新解析其他未变化文件;
168
+ - 修改 parser/schema 版本时会全量失效;
169
+ - 不带新增元数据的旧 Markdown 行为保持不变;
170
+ - 缓存只包含 JSON/文本,不提交到 Git;
171
+ - `cargo fmt`、`cargo clippy -- -D warnings`、`cargo test` 通过。
172
+
173
+ ### 当前验证矩阵
174
+
175
+ Phase A 当前已验证:
176
+
177
+ | 案例 | 结果 |
178
+ | --- | --- |
179
+ | 没有 frontmatter 的旧文档 | 保持空知识元数据,不影响旧搜索 |
180
+ | 只有 `id` / `parent` | 正确解析树入口 |
181
+ | 单值和多值关系混用 | 保持声明顺序并正确合并 |
182
+ | `related` / `requires` / `leads_to` 交叉关系 | 可表达树外关系 |
183
+ | 旧的 `title` / `aliases` / `entry_for` | 不会误解析为知识关系 |
184
+ | 未闭合 frontmatter | 丢弃知识元数据,避免部分解析污染索引 |
185
+ | 文件内容变化 | 内容 hash 失效 |
186
+ | parser/schema 版本变化 | 缓存失效 |
187
+ | `code_refs` 反查 definition | 返回关联文档节点 |
188
+ | 不同项目根目录 | 缓存路径隔离 |
189
+ | nodes/edges/files JSON 往返 | 保持结构和关系信息 |
190
+
191
+ 当前仍未接入的能力:
192
+
193
+ - 从 Calcit snapshot 自动生成 `code_refs`(当前已加载内置 core snapshot,并校验引用是否可解析;应用/模块 snapshot 仍未接入);
194
+ - 自动根据 definition 的 `:examples` 检查文档示例覆盖;
195
+ - 从 graph 节点聚合正文、schema、examples 的 LLM context。
@@ -0,0 +1,73 @@
1
+ # RFC: 类型自省(Type Introspection)一致性改进
2
+
3
+ 状态:Implemented(1-3 项已落地,第 4 项按计划延后)
4
+ 日期:2026-07-19
5
+
6
+ ---
7
+
8
+ ## 1. 概要
9
+
10
+ 调研 Calcit 当前"查询一个类型能怎么交互"的能力——有哪些字段、有哪些方法(无论是内建还是通过 trait/impl 附加的)——发现相关能力**分散在多个不对齐的 API 里**,且存在几处"数据已经在运行时结构里,但没有暴露给 Calcit 代码"的具体 gap。本 RFC 记录调研结论,并给出可落地的修复项清单。
11
+
12
+ ## 2. 调研结论
13
+
14
+ ### 2.1 方法(methods)查询——统一性最好的部分
15
+
16
+ `&methods-of` / `&inspect-methods`([meta.rs](../src/builtins/meta.rs))把"内建方法"(list/map/string/number/set/fn 各自的 `&core-*-impls`)和"用户 `defimpl`/`impl-traits`"统一表示成 `CalcitImpl`,按优先级顺序去重合并,`&methods-of` 对内建/自定义一视同仁。
17
+
18
+ 局限:`collect_impl_records_for_value` 只接受**实例值**(`Tuple`/`Record`/`List`/`Map`/`Number`/`Str`/`Set`/`Fn`/`Proc`),不接受裸的 `Calcit::Struct`/`Calcit::Enum`/`Calcit::Trait`(类型定义本身)。也就是说 `&methods-of Person`(还没造实例)会直接报错,必须先构造一个实例才能查询"这个类型将来会有哪些方法"。
19
+
20
+ `NativeMethodsOf` 的类型签名是 `arg_types: vec![dynamic_tag()]`([proc_name.rs](../src/calcit/proc_name.rs)),说明这一限制纯粹是**运行时实现**的限制,不是类型检查器强加的——修复它不需要碰类型系统。
21
+
22
+ ### 2.2 字段(fields)查询——能用,但类型签名与实现脱节
23
+
24
+ `to-pairs`/`keys`/`&record:to-map` 在运行时(`to_pairs` in [maps.rs](../src/builtins/maps.rs))原生支持 `Calcit::Record`,能拿到字段名(tag)。
25
+
26
+ 但 `ToPairs` 的类型签名声明 `arg_types: vec![some_tag("map")]`,而 `matches_with_bindings`([type_annotation.rs](../src/calcit/type_annotation.rs))里 `(TypeRef("map"), Record(base))` 这一分支是拿 `"map"` 去匹配 `base.name`(即结构体自己的名字,比如 `"Person"`),显然不相等 → **对 record 调用 `to-pairs`/`keys` 会在类型检查阶段被判定为类型不匹配并产生告警**,即便运行时完全正确。这是文档/类型签名与实际能力脱节的具体例子。
27
+
28
+ 没有实例、只有裸类型定义时(刚 `defstruct Person ...` 还没构造实例),**没有**可编程 API 能拿到字段名列表当数据用;唯一能看到字段的方式是 `println`/`str` 对 struct 值做格式化输出人肉读。
29
+
30
+ ### 2.3 `Display`(`println`/`str`)在三种类型定义值之间不对称
31
+
32
+ `impl fmt::Display for Calcit`([calcit.rs](../src/calcit.rs)):
33
+
34
+ | 类型定义值 | 输出示例 | 包含信息 |
35
+ | --- | --- | --- |
36
+ | `Struct` | `(%struct :Person (:name string) (:age number))` | 字段名 + 类型 |
37
+ | `Trait` | `(trait Name :m1 :m2)` | 仅方法名,**没有**方法签名类型 |
38
+ | `Enum` | `(%enum :Name)` | **仅名字**,连 variant 列表都没有 |
39
+
40
+ `CalcitEnum` 内部其实完整保存着 `variants()`(tag + payload_types,见 [sum_type.rs](../src/calcit/sum_type.rs)),数据都在,只是没接到 `Display` 里——这是一处"能力已具备却未暴露"的明显 gap,且没有任何测试覆盖这个输出格式(`grep "%struct"`/`"%enum"` 在 `calcit/*.cirru` 测试里零命中)。
41
+
42
+ ### 2.4 结论
43
+
44
+ - 没有一个统一的"describe 类型"入口能同时列出 fields + methods(不管来源);需要组合调用不同风格的 API。
45
+ - 对"类型定义本身"(还未实例化)的支持最弱:`&methods-of` 不接受、字段列表没有编程接口、`Enum`/`Trait` 的打印格式也比 `Struct` 缺信息。
46
+
47
+ ## 3. 修复计划(按优先级/风险排序)
48
+
49
+ 1. ✅ **`Enum` 的 `Display` 补上 variants**(最小、最安全,纯展示层修复,无 API 变化)。
50
+ - 实现:[src/calcit.rs](../src/calcit.rs) `impl fmt::Display for Calcit` 的 `Enum` 分支,现在遍历 `enum_def.variants()` 输出 `(:tag type1 type2 ...)`。
51
+ - 测试:`enum_display_includes_variants`(同文件 `#[cfg(test)] mod tests`)。
52
+ 2. ✅ **`&methods-of` 接受裸 `Struct`/`Enum`/`Trait` 值**(直接读它们自带的 `impls` 字段),不再强制要求实例。
53
+ - 实现:[src/builtins/meta.rs](../src/builtins/meta.rs) `collect_impl_records_for_value` 新增 `Struct`/`Enum` 分支;`iter_impls_in_precedence_order` 把 `Struct`/`Enum` 纳入与 `Tuple`/`Record` 相同的“后定义优先”倒序规则;`Trait` 单独处理(直接读 `trait_def.methods`,因为 trait 本身没有 `impls` 字段,是直接声明方法名)。`methods_of`/`inspect_methods` 都做了对应分支。
54
+ - JS 同步:[ts-src/calcit.procs.mts](../ts-src/calcit.procs.mts) 的 `lookup_impls`/`_$n_methods_of`/`_$n_inspect_methods` 同步补齐 `CalcitStruct`/`CalcitEnum`/`CalcitTrait` 分支。
55
+ - 测试:[calcit/test-traits.cirru](../calcit/test-traits.cirru) 的 `test-method-introspection` 新增对 `impl-traits Person0 MyFooImpl`(裸 struct)、`DemoBar`(裸 enum)、`MyFoo`(裸 trait)调用 `&methods-of` 的断言,`yarn check-all`(rs/js/ir/wasm 四目标)全部通过。
56
+ 3. ✅ **修正 `to-pairs`/`keys` 的类型签名**,让 record 也能匹配,消除虚假类型告警。
57
+ - 实现:[src/calcit/type_annotation.rs](../src/calcit/type_annotation.rs) `matches_with_bindings` 里 `(TypeRef, Record)` 分支新增:当 `TypeRef` 名字就是泛化的 `"map"` 时,结构性地匹配任意 record(不要求 record 名字等于 `"map"`)。
58
+ - 测试:新增单元测试 `generic_map_type_ref_accepts_records_structurally`,直接验证 `TypeRef("map")` 与 `Record(Person)` 现在双向匹配,同时确认无关的 `TypeRef` 名字仍然不会误匹配。
59
+ - 备注:实测 `cr --check-only` 在当前仓库测试集里并未因这个 gap 产生可观察的告警(`test-record.cirru` 里 `keys p2` 这行本来就没有触发过告警,猜测是这条路径上的静态类型推断没有把 `p2` 识别为具体的 `Record` 类型,所以没有触发 `check_proc_arg_types` 这条检查分支)。但 `matches_with_bindings` 的错误比较逻辑本身是真实存在的 bug,属于防御性修复:一旦未来静态推断能力增强(例如给变量加显式类型标注后传入 `to-pairs`/`keys`),就不会再误报。
60
+ 4. ⏸️ **(可选,视时间,本轮未实现)** 新增 `&struct:fields`/`&enum:variants` 之类的编程接口,让“裸类型定义”的字段/variant 列表也能被程序消费,而不仅仅是打印文本。
61
+ - 未实现原因:新增一个 proc 需要贯穿 `proc_name.rs`(注册+类型签名)、`builtins.rs`(分发)、`builtins/records.rs`或`meta.rs`(实现)、以及 JS/IR/WASM 三个 codegen 目标的同步实现,工作量与收益相比前三项更低(前三项已经解决了运行时能力不对齐的核心问题;字段/variant 名字目前仍可通过 `Display`/`println` 人肉获取,只是没有编程接口)。留作后续独立迭代。
62
+
63
+ ## 4. 兼容性 / 风险
64
+
65
+ - 第 1、2、3 项都已实现并通过 `cargo test`、`cargo run --bin cr -- calcit/test.cirru`、`yarn check-all`、`cargo clippy -- -D warnings`、`cargo fmt` 验证,纯粹的能力扩展/告警范围放宽,不改变现有行为,向后兼容。
66
+ - 第 4 项延后,不影响现有行为。
67
+
68
+ ## 5. 验证方式(已执行)
69
+
70
+ - `cargo test`:新增/更新的 Rust 单元测试全部通过,覆盖 `Display for Enum`、`&methods-of` 对裸类型定义的调用、`to-pairs`/`keys` 的类型匹配放宽。
71
+ - `cargo run --bin cr -- calcit/test.cirru`:Cirru 集成测试套件全部通过,包括新增的裸类型 `&methods-of` 断言。
72
+ - `yarn check-all`:JS/IR/WASM 三个目标同步验证通过(`&methods-of` 的 JS 实现已同步补齐)。
73
+ - `cargo clippy -- -D warnings` / `cargo fmt`:均无告警。
package/RFCs/README.md CHANGED
@@ -29,6 +29,9 @@
29
29
  | `05-31-generic-where-bounds-mfs.md` | Active | 函数 schema 泛型 `:where` 约束的最小功能规格,先作为主链路开发基线。 |
30
30
  | `06-15-effects-graph-rfc.md` | Draft | `cr analyze effects-graph`:State/Transform/Effect 语义分解图与类型驱动 effect 标注路线。 |
31
31
  | `06-29-cr-exec-cli-builtins-rfc.md` | **Active** | `cr exec` + `calcit.cli/*` 内建函数:绕过 Shell 转义的 Cirru 函数调用方案。 |
32
+ | `07-06-semantic-tree-navigation-rfc.md` | Draft | 语义化树形导航与编辑:路径标注、多候选交互、锚点搜索替换、结构化查询语言。 |
33
+ | `07-19-doc-knowledge-index-rfc.md` | Draft | Markdown/Calcit snapshot 的知识节点、关系索引与用户级增量缓存方案。 |
34
+ | `07-19-type-introspection-consistency-rfc.md` | Implemented | 类型自省一致性改进:`&methods-of` 支持裸类型定义、`Enum` Display 补 variants、`to-pairs`/`keys` 类型签名修正(第 4 项可选新增 proc 延后)。 |
32
35
 
33
36
  ## 已执行的清理
34
37
 
package/build.rs CHANGED
@@ -305,10 +305,8 @@ fn parse_code_entry(edn: Edn, owner: &str) -> Result<CodeEntry, String> {
305
305
  "examples" => examples = from_edn(value.clone()).map_err(|e| format!("{owner}: invalid `:examples`: {e}"))?,
306
306
  "tags" => tags = parse_tags_from_edn(value, owner)?,
307
307
  "code" => code = Some(from_edn(value.clone()).map_err(|e| format!("{owner}: invalid `:code`: {e}"))?),
308
- "schema" => {
309
- if !matches!(value, Edn::Nil) {
310
- schema = Some(parse_schema_from_edn(value, owner).map_err(|e| format!("{owner}: invalid `:schema`: {e}"))?);
311
- }
308
+ "schema" if !matches!(value, Edn::Nil) => {
309
+ schema = Some(parse_schema_from_edn(value, owner).map_err(|e| format!("{owner}: invalid `:schema`: {e}"))?);
312
310
  }
313
311
  _ => {}
314
312
  }
@@ -0,0 +1,40 @@
1
+ ## Path format migration: `@` prefix for display paths
2
+
3
+ ### What changed
4
+
5
+ All display-level Cirru tree paths now use `@` prefix notation when rendered to user.
6
+
7
+ ### Display format change
8
+ - `1.2.3.4` → `@1.2.3.4` (bare path)
9
+ - `[1.2.3.4]` → `[@1.2.3.4]` (bracketed path)
10
+ - `NodeLocation::Display` now outputs `ns/def [@1.2.3]` instead of `ns/def [1.2.3]`
11
+
12
+ ### Backward compatibility
13
+ - `parse_path()` strips leading `@` if present, so CLI input still accepts both `@1.2.3` and `1.2.3`
14
+ - Error messages and help text updated to show `@` format
15
+
16
+ ### Files modified
17
+
18
+ **Core functions:**
19
+ - `src/bin/cli_handlers/common.rs`: `format_path()` returns `@1.2.3`; `parse_path()` strips `@` prefix; test assertions updated
20
+ - `src/calcit.rs`: `NodeLocation::Display` outputs `[@1.2.3]`; test assertion updated
21
+ - `src/bin/cli_handlers/cirru_validator.rs`: local `format_path()` updated
22
+ - `src/type_coverage.rs`: `format_cirru_path()` updated
23
+ - `src/bin/cli_handlers/chunk_display.rs`: `compare_coords()` strips `@`; test `fragment()` helper uses `@`
24
+ - `src/runner/preprocess/mod.rs`: `[&inspect-type]` debug output uses `@`
25
+
26
+ **Tree/edit handlers:**
27
+ - `src/bin/cli_handlers/tree.rs`: inline path formatting in swap handler
28
+ - `src/bin/cli_handlers/edit.rs`: error message example
29
+ - `src/cli_args.rs`: help text examples (3 locations)
30
+
31
+ **Documentation:**
32
+ - `docs/CalcitAgent.md`: 19 path examples updated
33
+ - `docs/run/agent-advanced.md`: 23 path examples updated
34
+ - `docs/run/edit-tree.md`: 6 path examples updated
35
+ - `docs/Respo-Agent.md` (respo project): 26 path examples updated
36
+
37
+ ### Why
38
+ - `@` prefix helps LLMs distinguish path coordinates from other dotted numeric patterns (like version numbers or floats)
39
+ - `@` is safe in shell (no escaping needed)
40
+ - All display output now consistent with `@` prefix
@@ -0,0 +1,54 @@
1
+ # 语义化树形导航实现 (2026-07-07)
2
+
3
+ ## 变更概要
4
+
5
+ 实现 RFC `07-06-semantic-tree-navigation-rfc.md` 全部四个 Phase。
6
+
7
+ ## 实现功能
8
+
9
+ ### L1: `--path-annotations`
10
+
11
+ - `cr tree show` 新增 `--path-annotations` flag
12
+ - 开启后递归为所有嵌套 `Cirru::List` 末尾追加 `; "previous node path: X.Y.Z"` 注释节点
13
+ - AST 层面操作:`children.push(Cirru::List([Cirru::Leaf(";"), Cirru::Leaf(path_string)]))`
14
+ - 子节点 > 8 时底部 tip 提示可开启
15
+
16
+ ### L2: `--pick` 多候选选择
17
+
18
+ - `search-replace` 多匹配时展示带路径、上下文的候选列表
19
+ - `--pick <N>` 直接选择第 N 个候选执行替换
20
+
21
+ ### L4: 语义路径表达式
22
+
23
+ - `cr query path <ns> --selector 'path heading ... nth ...'`
24
+ - 三个选择器:裸叶子(精确匹配)、`heading`(前缀匹配)、`nth`(位置导航)
25
+ - `resolve_path_expression` 函数解析并返回数字路径
26
+
27
+ ### L4: `--selector` 限定搜索范围
28
+
29
+ - `search-replace --selector 'path ...'` 在语义路径定位的子树内搜索替换
30
+ - 与 `cr query path` 共用同一套选择器语法
31
+
32
+ ### Phase 4: `cr query anchors`
33
+
34
+ - 遍历 AST 中 `noted @anchor:<name>` 调用,输出锚点名称和路径
35
+
36
+ ## 修改文件
37
+
38
+ | 文件 | 变更 |
39
+ | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
40
+ | `src/cli_args.rs` | 新增 `path_annotations`, `pick`, `selector` 字段;新增 `QueryPathCommand`, `QueryAnchorsCommand` |
41
+ | `src/bin/cli_handlers/tree.rs` | `annotate_paths` 函数;`handle_show` 路径标注 + tip;`handle_search_replace` 多候选 + selector |
42
+ | `src/bin/cli_handlers/query.rs` | `resolve_path_expression`(pub), `starts_with_pattern`, `handle_query_path`, `handle_query_anchors`, `find_anchors` |
43
+ | `src/bin/cli_handlers/command_echo.rs` | Path/Anchors 子命令 echo 支持 |
44
+ | `docs/CalcitAgent.md` | 精简引用新功能 |
45
+ | `docs/run/agent-advanced.md` | 详细示例文档 |
46
+ | `RFCs/07-06-semantic-tree-navigation-rfc.md` | 新增 RFC |
47
+
48
+ ## 设计决策
49
+
50
+ - `--at`/`--child` 链式语法被 `--selector` 替代,代码中已移除
51
+ - `exact` 选择器不纳入——链式导航模型不需要独立精确匹配,`heading` 无多余子节点时等同
52
+ - `sub-path` 选择器不纳入——L1 路径标注让 `nth` 足够
53
+ - 路径注释放在 list 末尾(不破坏已有子节点索引)
54
+ - `Cirru::Leaf` 存原始内容,格式化时自动加引号
@@ -0,0 +1,27 @@
1
+ # `:features` function schema + `:js-object` type + CLI simplification
2
+
3
+ ## Changes
4
+
5
+ ### Feature: `:features` and `:js-object` type system
6
+
7
+ - Added `features: Arc<HashSet<EdnTag>>` to `CalcitFnTypeAnnotation` for marking function capabilities
8
+ - Added `CalcitTypeAnnotation::JsObject` variant for opaque JS FFI data
9
+ - EDN round-trip for both (`features_to_edn` + `parse_fn_features_from_form`)
10
+ - Manually implemented `PartialOrd`/`Ord`/`Hash` for `CalcitFnTypeAnnotation`
11
+ - `CURRENT_FN_FEATURES` thread-local + `lookup_def_schema` fallback for FFI checking
12
+ - `as_fn()` helper on `CalcitTypeAnnotation`
13
+ - `#{}` hashset support in `schema_cirru_to_edn`
14
+ - `:features` field validation in `validate_schema_for_write`
15
+ - All direct FFI callers marked with `:features $ #{} :js-ffi`
16
+
17
+ ### Refactor: CLI simplification
18
+
19
+ - Removed `--json` option and `--json-input` switch from all edit/tree subcommands
20
+ - Auto-detect JSON (`[` prefix) vs Cirru EDN (`quote` prefix) in `parse_input_to_cirru`
21
+ - Simplified `read_code_input` to `(file, code)` params; stdin fallback
22
+ - Removed `json()`/`json_input()` from `InsertOperation` trait
23
+ - Simplified `CodeInputParts` to only carry `file`+`code`
24
+ - Added stdin mention to all edit subcommand help texts
25
+ - Updated agent docs (CalcitAgent.md, agent-advanced.md, cirru-syntax.md, edit-tree.md)
26
+ - Cleaned up stale short flag mentions (`-j`, `-J`, `-e`)
27
+ - Changed js-interop doc blocks to `no-check` to avoid FFI warnings
@@ -0,0 +1,8 @@
1
+ # Edit History - Concise Method Representation and Introspection Refactoring
2
+
3
+ - **Objective**: Improve the representation of methods inside the VM to return first-class `Calcit::Method` values rather than strings from `&methods-of`, and provide a concise, user-facing rendering suitable for REPL and dynamic inspection rather than verbose list expressions, all while keeping internal preprocessing unaffected.
4
+ - **Actions**:
5
+ - Refactored `methods_of` in `src/builtins/meta.rs` to return native `Calcit::Method` values directly with leading dots stripped.
6
+ - Implemented custom `turn_string` in `src/calcit.rs` for `Calcit::Method` to format method values concisely (e.g., `.add`, `.-field`, `.!native`) for `str` conversions, prints, and dynamic assertions.
7
+ - Updated `format_to_lisp` in `src/calcit.rs` to keep `format_to_lisp` compatible with internal macros (like `deftrait` which dynamically slices `format-to-lisp` output starting from the 9th character).
8
+ - Cleaned up assertions inside `calcit/test-traits.cirru`'s `test-method-introspection` to map `&methods-of` outputs to string using `str` rather than raw assertion of methods list, ensuring absolute compatibility and safety.