@calcit/procs 0.13.14 → 0.13.16

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 (28) hide show
  1. package/.yarn/install-state.gz +0 -0
  2. package/RFCs/07-26-agent-machine-protocol-rfc.md +113 -35
  3. package/RFCs/07-28-persistent-tree-cursor-rfc.md +22 -4
  4. package/RFCs/08-05-systematic-nil-reduction-rfc.md +2 -2
  5. package/RFCs/08-14-architecture-scaffold-rfc.md +534 -0
  6. package/RFCs/08-14-todo-placeholder-rfc.md +177 -0
  7. package/RFCs/README.md +3 -1
  8. package/editing-history/20260814-1412-architecture-scaffold-rfc-and-cursor-envelope.md +46 -0
  9. package/editing-history/20260814-1640-architecture-scaffold-review-fixes.md +21 -0
  10. package/editing-history/20260814-1700-fix-bare-empty-map-js-codegen.md +10 -0
  11. package/editing-history/20260814-1710-map-codegen-callee-context.md +9 -0
  12. package/editing-history/20260814-1720-option-membership-method-guidance.md +11 -0
  13. package/editing-history/20260814-1746-pr-352-review-fix.md +8 -0
  14. package/editing-history/20260814-1858-map-wrapped-data-definitions.md +11 -0
  15. package/editing-history/20260814-1910-assert-type-struct-narrowing.md +6 -0
  16. package/editing-history/20260814-1937-assert-type-direct-definitions.md +5 -0
  17. package/editing-history/20260814-1945-applied-qualified-type-references.md +5 -0
  18. package/editing-history/20260815-0043-pr-352-assert-type-nominal-guard.md +12 -0
  19. package/history/202608131607-runtime-map-decoder.md +13 -0
  20. package/history/202608131625-core-decode-map-as-metadata.md +12 -0
  21. package/history/202608131650-release-0.13.15.md +10 -0
  22. package/lib/calcit.procs.d.mts +6 -1
  23. package/lib/calcit.procs.mjs +133 -0
  24. package/lib/package.json +1 -1
  25. package/notes/202608132206-repo-sync.md +3 -0
  26. package/notes/repos.md +177 -0
  27. package/package.json +1 -1
  28. package/ts-src/calcit.procs.mts +132 -1
@@ -0,0 +1,177 @@
1
+ # RFC: `todo!` 未实现占位表达式与静态提醒
2
+
3
+ 状态:Draft
4
+ 日期:2026-08-14
5
+ 关联:`08-14-architecture-scaffold-rfc.md`、`03-05-function-schema-dual-track-rfc.md`、`07-26-static-semantic-analysis-rfc.md`
6
+
7
+ ## 1. 概要
8
+
9
+ 新增与 Rust `todo!()` 对应的 Calcit 占位表达式:
10
+
11
+ ```cirru
12
+ defn validate-order (order)
13
+ todo! "|implement order validation"
14
+ ```
15
+
16
+ `todo!` 表示“此路径刻意尚未实现”,同时满足三类契约:
17
+
18
+ - 类型检查:可出现在任意期望返回类型的位置,不产生返回类型 mismatch;
19
+ - 静态诊断:产生带精确 definition/path 的 `W_TODO`,提醒 Agent 仍有未完成工作;
20
+ - 运行时:一旦执行到该路径,native/JS 明确抛出 TODO error,WASM 执行 `unreachable` trap。
21
+
22
+ 它不是 `raise` 的别名。`raise` 表示程序设计中的普通异常路径,静态分析不应据此推断代码未完成;`todo!` 表示开发状态,必须能被结构化查询和完成门禁识别。
23
+
24
+ ## 2. 表面语法
25
+
26
+ 第一版支持零或一个 String 参数:
27
+
28
+ ```cirru.no-check
29
+ todo!
30
+ todo! "|implement app.order/validate-order"
31
+ ```
32
+
33
+ 约束:
34
+
35
+ - 名称使用 `todo!`;`!` 是 Calcit 命名约定的一部分,不采用 Rust 括号宏语法;
36
+ - message 省略时使用稳定默认文本 `TODO: implementation is pending`;
37
+ - message 必须是静态 String literal,保证 analyzer 无需执行代码即可输出原因;
38
+ - 多参数、Tag、动态拼接或 metadata map 第一版拒绝,避免占位表达式演变成日志 API;
39
+ - scaffold 需要携带 planned calls 时,将它们格式化进静态 message,同时仍以 architecture graph 作为机器可读来源。
40
+
41
+ ## 3. 类型语义
42
+
43
+ ### 3.1 Never/bottom
44
+
45
+ 目标语义中,`todo!` 的表达式类型是内部 `Never`(bottom type),不是 `Unit` 或任意伪造的业务类型:
46
+
47
+ ```cirru
48
+ defn load-user (id)
49
+ ; schema return: User
50
+ todo! "|load user"
51
+ ```
52
+
53
+ 规则:
54
+
55
+ - actual `Never` 满足任意 expected type,因此不产生 `W_FN_RETURN_TYPE_MISMATCH`;
56
+ - `if` / `match` 的一个分支为 `Never`、另一分支为 `T` 时,整体推断为 `T`;
57
+ - `Never` 不参与泛型变量绑定,不把 `T` 推断成 `Never`;
58
+ - `Never` 没有运行时 value,也不能构造或存入 collection;
59
+ - 第一阶段只作为 compiler internal annotation;不承诺用户可在 schema 中显式写 `'Never`。
60
+
61
+ 当前基础实现以 compiler-known diverging proc 的 `Dynamic` 返回签名避免伪造的返回类型 mismatch,同时由 `W_TODO` 保留“未完成”信息;它尚未改变 `if`/泛型的类型 join。完整 `Never` 是下一阶段的类型系统工作,届时替换该内部表示,而不改变表面语法或诊断协议。
62
+
63
+ ### 3.2 与控制流的关系
64
+
65
+ `todo!` 是 diverging expression。preprocessor 在它之后仍可保留源码用于展示,但控制流和类型 join 不要求该表达式返回。第一版不借此实现完整的 unreachable-code warning;这可以在未来与 `raise`、`quit!` 的控制流分析统一处理。
66
+
67
+ ## 4. 静态诊断
68
+
69
+ 每个 `todo!` 产生:
70
+
71
+ ```text
72
+ [W_TODO] TODO remains in `app.order/validate-order`: implement order validation
73
+ at app.order/validate-order @3
74
+ ```
75
+
76
+ Cirru EDN diagnostic 至少包含:
77
+
78
+ ```cirru
79
+ {}
80
+ :code :W_TODO
81
+ :severity :warning
82
+ :namespace 'app.order
83
+ :definition 'validate-order
84
+ :path $ [] 3
85
+ :message "|implement order validation"
86
+ ```
87
+
88
+ 诊断策略:
89
+
90
+ - `W_TODO` 是 completion warning,不是 type warning;同一位置不得再产生伪造的返回类型 mismatch;
91
+ - scaffold dry-run/apply 把它列入 expected warnings,不把新 stub 当成 apply conflict;
92
+ - `cr --check-only` 遵循当前严格 warning 策略:可创建 scaffold,但仍有 reachable TODO 时检查返回非零,Agent 不能宣称功能完成;
93
+ - `cr analyze check-types` 扫描所选 Snapshot definition,将 TODO 数量和 diagnostics 纳入 human/Cirru EDN 报告,即使节点暂时不从 entry 可达;
94
+ - 后续若引入 warning severity/allow-list,`W_TODO` 的默认 completion gate 仍应为 deny,显式探索性运行才允许降级;
95
+ - Cirru EDN stdout 保持单个 value;JSON 只作为现有工具兼容投影,human warning 与普通命令提示走 stderr。
96
+
97
+ 参数契约也由 compiler-known proc 统一执行:`todo!` 只接受零个参数,或一个
98
+ 静态 String literal;非 literal 参数和多余参数在 preprocessing 阶段分别报告
99
+ Type/Arity 错误并阻止 codegen。native、JavaScript、WASM backend 仍保留同样的
100
+ 校验作为防御性边界,不把非法调用静默降级为 unconditional trap。
101
+
102
+ ## 5. 运行时与 codegen
103
+
104
+ 即使静态门禁通常会先发现 TODO,各后端仍必须定义一致的防御行为:
105
+
106
+ | 后端 | 行为 |
107
+ |------|------|
108
+ | native | 返回 `CalcitErrKind::Effect`,message 以 `TODO:` 为前缀并附 call stack |
109
+ | JavaScript | 生成 `throw new Error("TODO: ...")` |
110
+ | WASM | 在可选 host log 后执行 `unreachable`;第一版至少保证 trap,不返回伪值 |
111
+ | IR | 保留明确的 todo/diverge 节点,不能降为 `nil` |
112
+
113
+ `try` 是否能捕获 native/JS TODO 第一版沿用普通 runtime error 机制;静态 `W_TODO` 不因外层存在 `try` 而消失,因为捕获异常不代表实现已经完成。
114
+
115
+ ## 6. 实现形态
116
+
117
+ 推荐将 `todo!` 作为 compiler-known builtin/syntax,而不是 calcit-core 普通函数:
118
+
119
+ - parser/name resolution 能稳定识别,不受局部同名 binding 影响;
120
+ - type inference 可直接返回 internal `Never`;
121
+ - preprocessor 能在表达式位置产生精确 `W_TODO`;
122
+ - JS/WASM codegen 可直接生成 diverging operation;IR 的显式 todo/diverge 节点留在后续实现;
123
+ - runtime 不需要用普通 `raise` 猜测某段字符串是否以 TODO 开头。
124
+
125
+ 当前实现采用 `CalcitProc::Todo`:preprocessor 对它生成精确 `W_TODO`,Dynamic signature 仅作为完整 `Never` 前的临时内部兼容表示,不能把 warning 当作可忽略的普通动态边界。
126
+
127
+ ## 7. 与 scaffold 的关系
128
+
129
+ architecture scaffold 默认生成:
130
+
131
+ ```cirru
132
+ defn validate-order (order)
133
+ todo! "|implement app.order/validate-order; planned calls: app.order/order-total"
134
+ ```
135
+
136
+ - doc/schema/params 正常写入 `CodeEntry`;
137
+ - `:scaffold` tag 与 `W_TODO` 分工:tag 表示 definition 来源,warning 表示代码中仍存在未实现路径;
138
+ - 完成实现时必须移除对应 `todo!`;是否同时自动移除 `:scaffold` tag 由 scaffold completion check 决定;
139
+ - planned edges 仍来自 architecture graph,不把 message 当成可解析协议;
140
+ - 实际 call graph 在实现前可以没有 planned calls,drift report 应把这种缺失标记为 `pending`,而不是伪造不可执行调用。
141
+
142
+ ## 8. 分阶段实现
143
+
144
+ ### Phase A:基础可用版本(已实现)
145
+
146
+ - 注册 `todo!`;
147
+ - native runtime TODO effect;
148
+ - 精确 `W_TODO` preprocessing diagnostic;
149
+ - function schema return context 测试;
150
+ - 完整 internal Never/bottom、branch/generic 规则留给后续控制流分析。
151
+
152
+ ### Phase B:分析与后端
153
+
154
+ - `analyze check-types` 全 Snapshot TODO 扫描及 Cirru EDN machine result;
155
+ - JS/WASM codegen 已实现;IR 的显式 todo/diverge 节点仍待实现;
156
+ - `--check-only`、eval、test 和 codegen warning 行为测试;
157
+ - Agent 文档与 error-handling 文档。
158
+
159
+ ### Phase C:scaffold 接入
160
+
161
+ - scaffold 默认 `--stub todo`;
162
+ - generated/expected warning 分类;
163
+ - TODO 清零和 `:scaffold` tag 完成检查;
164
+ - architecture drift 的 pending edge 表达。
165
+
166
+ ## 9. 验收
167
+
168
+ - `todo!` 可作为 `'Number`、`'String`、Struct、Enum、generic 和 `Unit` 函数的返回表达式,不产生类型 mismatch;
169
+ - `if condition (todo! "|left") 1` 推断为 Number;
170
+ - 每处 TODO 都产生一个带稳定 `W_TODO`、FQN、path 和 message 的 diagnostic;
171
+ - `analyze check-types` 能发现当前 entry 不可达 definition 内的 TODO;
172
+ - scaffold apply 可成功创建 TODO stub,但完成门禁不会在 `W_TODO` 尚存时通过;
173
+ - native、JS、WASM 执行到 TODO 时均中止,不返回 `nil` 或默认值;
174
+ - `raise "|TODO..."` 不产生 `W_TODO`,普通异常语义保持不变;
175
+ - shadowing 或同名用户 definition 不会被误判为 compiler TODO;
176
+ - Cirru EDN stdout 保持单个 value,兼容 JSON renderer 不改变该纯净度契约;
177
+ - 全部核心、CLI、JS 和 WASM 回归通过。
package/RFCs/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # RFC 整理索引
2
2
 
3
- 更新时间:2026-08-08
3
+ 更新时间:2026-08-14
4
4
 
5
5
  ## 目录原则
6
6
 
@@ -39,6 +39,8 @@
39
39
  | `07-28-git-module-store-rfc.md` | Draft | 保持 `deps.cirru` 与 Git 模块路径,以 tag 为最佳实践并使用 pnpm 式全局目录存储;不引入 registry、lockfile、workspace 或多版本。 |
40
40
  | `07-28-project-tooling-contract-rfc.md` | Draft | 在既有 `cr` 子命令上补强单项目工具契约,保持 EDN 树形事实来源。 |
41
41
  | `07-28-persistent-tree-cursor-rfc.md` | Draft | `.calcit/` 本地状态、虚拟 cursor、region/marks/last-query、结构化 clipboard 与 path 迁移。 |
42
+ | `08-14-architecture-scaffold-rfc.md` | Implemented | Cirru EDN architecture graph、existing-definition reconciliation、atomic scaffold apply、work items 与多 Agent 分工边界。 |
43
+ | `08-14-todo-placeholder-rfc.md` | Partial | compiler-known `todo!`、`W_TODO` 与 native/JS/WASM 中止行为已落地;完整 Never/control-flow inference 后续实现。 |
42
44
  | `08-04-strict-cirru-edn-decoding-rfc.md` | Implemented | Phase 1:`parse-cirru-edn-as` 严格类型化反序列化、无 Dynamic 的 `EdnDecoderGraph`、名义身份与 Native/JS 一致性。 |
43
45
  | `08-05-systematic-nil-reduction-rfc.md` | Partial | 类型驱动减少 nil:先拆分可省略参数与 nullable 值,再迁移至 Option/Result 并逐步收紧 typed code。 |
44
46
  | `08-08-cross-backend-host-ffi-contracts-rfc.md` | Draft | 统一 JS/native/WASM/WASI 的逻辑 FFI 契约与诊断,ABI transport 保持 backend-specific;首个完整 shape consumer 为 JS/DOM。 |
@@ -0,0 +1,46 @@
1
+ # 功能架构脚手架 RFC 与 cursor envelope 兼容性
2
+
3
+ ## 背景
4
+
5
+ 单 definition 的 `cr edit def` 无法直接承载一个功能的调用结构、接口约束、已有节点复用和批量生成计划。未来多个 Agent 拆分实现时,还需要在不承诺同 Snapshot 并发写安全的前提下,确保 cursor sidecar 不会丢弃其他 cursor user 的位置数据。
6
+
7
+ ## 本次结论
8
+
9
+ - 新增 `RFCs/08-14-architecture-scaffold-rfc.md`,建议以 `cr edit scaffold` 接收 Cirru EDN desired-state overlay;canonical model 是 Symbol FQN 到 declaration 的平面 map 加 typed anonymous-enum edge set(如 `:: :call from to`),tree 只作为展示或未来输入语法糖。
10
+ - architecture declaration 包含 mode、kind、doc、当前 canonical schema、函数参数和可选 code/examples;已有 definition 默认复用,具体 schema/kind 冲突拒绝整次 apply。旧示例中的 quoted `:: 'Fn` 已修正为 unquoted `:: :fn`,只有 code AST 使用 quote。
11
+ - 已有 definition 不会从 scaffold graph 中消失:planner 同时展示 existing/planned doc、schema、kind 和字段级 diff;apply 不覆盖现有内容,差异通过 info/warning/conflict diagnostics 报告。
12
+ - 函数 `:params` 属于程序内部元数据,architecture Cirru EDN 使用 Symbol(如 `'order`),parser 不把 String/Tag 隐式转换成参数名。
13
+ - scaffold stub 不再用普通 `raise`:新增 `08-14-todo-placeholder-rfc.md`,设计 compiler-known `todo!`、internal Never、`W_TODO` 和各后端的 diverging 行为;scaffold apply 可创建 stub,但 TODO 完成门禁仍失败。
14
+ - scaffold 与 TODO 的规范机器结果改用 Cirru EDN,保留 Symbol/Tag/Set/Quote/schema 的数据身份;JSON 只作为既有 Agent、`jq` 和 LSP/MCP 工具的兼容 renderer。
15
+ - 多 Agent 第一版采用“并行产出 definition 实现、parent/coordinator 串行写回 Snapshot”;CLI 只输出带 target/write-set/schema/doc/planned edges 的 work item,不启动 Agent,也不把 call graph 当成硬任务依赖。
16
+ - reconciliation 区分 `create`、`reuse-pending`、`reuse-complete` 和 `external`;带 scaffold/TODO 的已有节点会在重复 dry-run 时继续产生稳定 work item,保证中断后可恢复。
17
+ - definition-level semantic patch、`ensure-import` 和 SCC/batch 建议留到流程走通后;第一版继续复用 Snapshot revision、现有 edit/transaction 与 staged atomic write。
18
+ - 第一版不暴露只有单一有效值的 `--stub`/`--existing` 策略开关,直接固定为 compatible reuse、hard conflict reject 和 TODO stub。
19
+ - cursor 身份仍为 `--cursor-user` > `CALCIT_CURSOR_USER` > `default`,但 architecture 不再把 work item 绑定到 cursor user。未来多 user 推荐 `.calcit/cursors/<user>.cirru`,source mutation 只立即迁移当前 user,其他 user 下次访问时 lazy revalidate。当前新建 v4 sidecar 的默认 cursor key 先从 `main` 改为 `default`。
20
+ - scaffold 只生成普通 `CodeEntry` stub,不增加运行时架构元数据;后续实现和 drift 检查继续复用现有 call graph、query、tree 和 cursor。
21
+ - cursor v4 文件本来已有 `:active` 与 `:cursors` map,但 Rust 内存模型只保留 active state,任何保存都会把 map 收缩为固定 `:main`。
22
+ - `CursorDocument` 现在保存实际 `active_name` 和其他 cursor state,序列化时合并写回,因此可无损 round-trip named cursors;CLI 行为仍只作用于 active entry。
23
+ - architecture 文件实现期间可约定放在 `docs/architectures/<feature>.cirru` 并用 normalized content hash 形成 plan-id;派生的 status/work items/diagnostics 不回写 plan。`.calcit/` 只承载本地临时 cursor 状态。
24
+ - 本轮没有实现 cursor user 选择、per-user 文件、semantic patch、lease、心跳或并发锁;这些都不阻塞 architecture → scaffold → work items → 串行整合的第一版闭环。
25
+
26
+ ## 验证
27
+
28
+ - `cargo fmt --all -- --check`
29
+ - `cargo clippy -- -D warnings`
30
+ - `cargo test`
31
+ - `yarn compile`
32
+ - `yarn check-agent-interface`(12/12)
33
+ - `yarn check-all`
34
+ - `cargo test --bin cr cli_handlers::cursor::tests`(19/19,包含 `default` key 与 named cursor round-trip)
35
+ - 用 `cr cirru parse-edn` 验证 RFC 中 architecture、machine result 与 work item 示例;带空格 doc 使用双引号保护 pipe string。
36
+ - architecture result、TODO diagnostic、通用 machine envelope 与 Definition Descriptor 的 EDN 示例均通过 `cr cirru parse-edn`。
37
+
38
+ 新增/扩展的 cursor round-trip 测试覆盖非默认 active 名称和非 active cursor 条目的保存与恢复。
39
+
40
+ ## Scaffold 与 `todo!` 基础实现进度
41
+
42
+ - 新增 `cr edit scaffold`;接受平面 architecture Cirru EDN,输出 human、canonical EDN 或 JSON compatibility projection。
43
+ - parser 强制 FQN/params 使用 Symbol,edge 使用匿名 enum(`:: :call from to` / `:: :type from to`),拒绝误用异构 list 的 edge。
44
+ - planner 验证 roots/edge endpoint、schema 与 namespace,reconcile 现有 definition,并输出 create/reuse-pending/reuse-complete/external、预览 operation 和带 plan/base revision/write-set 的 work item。
45
+ - `--dry-run` 保持只读;apply 在 staged Snapshot 中一次创建全部缺失 `:ensure` definition,function 生成 `todo!` stub,写入 doc/schema/`:scaffold`,复核原 revision 后 atomic rename,绝不覆盖已有 definition。apply 成功后触发已有 cursor 后置校验。
46
+ - 新增 compiler-known `todo!` proc:native effect 中断,preprocessor 产生 `W_TODO`,JS/WASM codegen 均显式中断。完整 Never/branch/generic inference、definition-level patch 和 external dependency/core lookup 仍待后续实现。
@@ -0,0 +1,21 @@
1
+ # 2026-08-14 16:40 — architecture scaffold review fixes
2
+
3
+ ## Summary
4
+
5
+ - Processed PR #351 review comments for scaffold reconciliation, metadata preservation, machine-result revisions/diffs, work-item identity, and writer concurrency.
6
+ - Made explicit function code, tags, examples, and data code survive scaffold apply; generated `:scaffold` only for TODO function stubs.
7
+ - Added empty-root validation, external target compatibility validation, per-definition diffs, proposed/applied revisions, and post-scaffold work-item revisions.
8
+ - Added a destination writer lock around staged snapshot replacement and strengthened the named-cursor round-trip fixture with distinct states.
9
+ - Enforced one `todo!` argument contract across preprocessing, JavaScript, WASM, and native boundaries; diagnostics now identify the containing definition and invalid messages use the argument location.
10
+ - Expanded the machine-protocol RFC with canonical JSON tagged projections and stdio framing/negotiation fixtures, and aligned the cursor default example with `:default`.
11
+ - Added a concrete nested EDN/JSON typed-value fixture and equivalent EDN/JSON handshake fixture requirements.
12
+
13
+ ## Validation
14
+
15
+ - `cargo fmt --all`
16
+ - `cargo clippy -- -D warnings`
17
+ - `cargo test`
18
+ - `yarn compile`
19
+ - `yarn check-agent-interface`
20
+ - `yarn check-all`
21
+ - `cr docs check-md` for all modified RFC and static-analysis documents
@@ -0,0 +1,10 @@
1
+ # Bare empty-map JS codegen
2
+
3
+ - A bare Cirru `{}` survives preprocessing as `CalcitProc::NativeMap`, rather
4
+ than as an already-created map value.
5
+ - JS code generation must invoke that runtime constructor when it is used as a
6
+ value. Emitting the escaped proc name alone produced `$clt._$M_`, an alias
7
+ that the runtime does not export.
8
+ - Keep a focused codegen test for the emitted `$clt._$n__$M_()` call. This
9
+ protects empty map values in typed struct constructors and ordinary calls
10
+ such as `merge {} other`.
@@ -0,0 +1,9 @@
1
+ # Empty-map codegen needs callee context
2
+
3
+ - In JS codegen a bare `CalcitProc::NativeMap` must produce an empty map by
4
+ invoking the runtime constructor.
5
+ - The same proc at the head of a list is the constructor callee and must not be
6
+ pre-invoked. Otherwise generated code becomes `map()(...entries)`.
7
+ - Keep separate regression assertions for the bare value and an entry-bearing
8
+ map literal, and run the emitted-JS Node suite because unit output alone
9
+ cannot catch a callee/value context mix-up.
@@ -0,0 +1,11 @@
1
+ # Typed Option membership and method-style guidance
2
+
3
+ - The nominal-enum legacy-absence warning now permits `includes?` / `contains?`
4
+ when the candidate and the checked collection element are statically the same
5
+ nominal enum. This keeps `Set<Option<T>>` membership type-safe without
6
+ treating an `Option` as a nullable payload.
7
+ - Added a regression test covering `includes?` over `Set<Option<Number>>`.
8
+ - Updated Option/Result user-facing documentation to prefer receiver methods
9
+ such as `.and-then`, `.or-else`, `.map`, and `.unwrap-or`; direct
10
+ `option:*` / `result:*` helpers remain implementation-level compatibility
11
+ details rather than the recommended public style.
@@ -0,0 +1,8 @@
1
+ # PR 352 review boundary correction
2
+
3
+ - `List.contains?` tests index presence rather than membership of list values.
4
+ - Restrict the nominal-enum membership warning exemption accordingly: only
5
+ `includes?` uses list elements, while `contains?` applies to Set elements or
6
+ Map keys.
7
+ - Added a regression assertion that `contains?` with `List<Option<T>>` still
8
+ reports the migration warning.
@@ -0,0 +1,11 @@
1
+ # Map-wrapped data-definition macro forms
2
+
3
+ - `defstruct Name $ {}` and `defenum Name $ {}` deliver one map-headed AST
4
+ wrapper through their variadic macro parameters. Normalize that wrapper into
5
+ the contained field or variant forms before detecting generics and where
6
+ bounds.
7
+ - Compare the macro AST head with quoted `'{}`; evaluating `{}` would instead
8
+ create a runtime map and fails to recognize the syntax marker.
9
+ - Added definition-attached end-to-end fixtures for typed Struct field access
10
+ and Enum construction/tag matching, so both wrapper forms remain covered by
11
+ the native test suites.
@@ -0,0 +1,6 @@
1
+ # 2026-08-14 assert-type Struct 收窄
2
+
3
+ - `assert-type` 的限定名(例如 `'app.schema/Store`)在非泛型上下文中必须保留为名义 `TypeRef`,不能误作 type variable;这样后续必填 Struct 字段访问才能解析声明。
4
+ - 断言包裹任意表达式时,类型推断应把声明类型交给外层 `let` binding;局部变量的断言仍由预处理阶段直接收窄。
5
+ - `defstruct` / `defenum` 的 `$ {} ...` map-headed 输入已经由宏归一化,静态类型定义解析也必须在泛型和 `:where` 解析前采用相同的归一化。
6
+ - 回归覆盖包括两种断言收窄路径、map-headed Struct/Enum 静态解析,以及完整 Struct/Enum Snapshot 运行。
@@ -0,0 +1,5 @@
1
+ # 2026-08-14 assert-type 直接类型定义
2
+
3
+ - `assert-type` 的第二参数是类型语境;未加引号的 symbol 现在会解析为可见的 Struct/Enum 定义,例如 `assert-type source Store`。
4
+ - 单引号继续用于 TypeVar(`'T`)和显式名义 TypeRef,不把泛型变量当作运行时定义求值。
5
+ - 两条 Struct 收窄回归测试改为直接定义写法,覆盖语句断言及 `let` 右侧断言。
@@ -0,0 +1,5 @@
1
+ # 2026-08-14 限定名类型应用
2
+
3
+ - 未限定泛型语境中,带 `/` 的 TypeRef 仍是名义类型,不应在 `::` 类型应用时退化为普通值。
4
+ - `(:: 'app.schema/Box 'String)` 现在保留 `app.schema/Box` 及其类型参数;保留现有 TypeVar 行为。
5
+ - 添加针对无 scope 限定名类型应用的回归测试,覆盖 `assert-type` 使用的同一类型解析路径。
@@ -0,0 +1,12 @@
1
+ # PR 352 assert-type direct resolution guard
2
+
3
+ - `assert-type` direct definition resolution now verifies the referenced def
4
+ resolves to a concrete StructDef/EnumDef before treating it as a resolved type.
5
+ - Visible function or value names are kept as-is instead of being passed to
6
+ `parse_type_annotation_form` as resolved runtime values, which could silently
7
+ change the asserted type.
8
+ - Added `code_resolves_to_nominal_type_def` in `type_annotation.rs` reusing the
9
+ existing `resolve_type_def_from_code` peeling logic.
10
+ - Extended the membership regression block with Map<Option<T>, V> key
11
+ membership via `contains?`, Map<K, Option<T>> value membership via `includes?`,
12
+ reversed-direction negatives, and specialized Map membership procs.
@@ -0,0 +1,13 @@
1
+ # Runtime map decoder for typed boundary data
2
+
3
+ ## Change summary
4
+
5
+ - Added `decode-map-as value TypeExpr`, a compile-time-derived decoder for an already-evaluated Calcit Map.
6
+ - The decoder constructs nominal Struct values recursively, rejects unknown keys and missing required fields, and reports nested failure paths.
7
+ - Struct fields declared as `Option<T>` accept raw `T`, preserve an already-wrapped `%some`/`%none`, and become `%none` when the source field is absent.
8
+ - `Dynamic` is allowed only as an explicit runtime-boundary leaf for this decoder; the existing text-based `parse-cirru-edn-as` remains a closed-data decoder and continues to reject it.
9
+ - Native and JavaScript backends share the graph ABI and tests. WASM continues to reject both typed EDN decoder syntaxes explicitly.
10
+
11
+ ## Knowledge point
12
+
13
+ Runtime Maps are not Cirru EDN: they do not carry nominal Struct identity and commonly represent optional data by omitted keys. A typed boundary decoder must therefore perform map-to-Struct construction, enforce required fields, and lift omitted or raw optional values into nominal `Option` variants. Reusing the closed EDN decoder without these rules silently permits invalid Structs or forces callers back to ad-hoc dynamic map readers.
@@ -0,0 +1,12 @@
1
+ # Document `decode-map-as` in calcit-core
2
+
3
+ ## Change summary
4
+
5
+ - Added the `decode-map-as` runtime syntax to the core snapshot metadata with builtin/internal/meta/syntax tags and a typed boundary schema.
6
+ - Added reusable `RuntimeMapMeta` and `RuntimeMapResponse` Struct fixtures for core examples and tests.
7
+ - Added a passing example plus definition-attached unit tests covering recursive Struct decoding through an Option field, omitted `Option` fields becoming `%none`, pre-wrapped `%some`, and explicit Dynamic payload preservation.
8
+ - Documented the native/JavaScript support boundary and kept WASM behavior explicit.
9
+
10
+ ## Knowledge point
11
+
12
+ Compiler syntax and core snapshot metadata are separate surfaces. Adding a Rust/JS builtin is incomplete until `calcit-core.cirru` exposes its documentation, examples, schema, and tests; otherwise `cr docs`, `cr test`, and downstream agents cannot discover or verify the feature.
@@ -0,0 +1,10 @@
1
+ # Release Calcit 0.13.15
2
+
3
+ ## Change summary
4
+
5
+ - Released the merged `decode-map-as` runtime decoder and its core snapshot metadata, examples, and tests.
6
+ - Kept Cargo and npm package versions synchronized at `0.13.15` for the direct-main release workflow.
7
+
8
+ ## Knowledge point
9
+
10
+ Version-only release changes are allowed directly on `main`; the functional implementation was merged and validated before this release commit. The release tag is created from the synchronized `main` commit so Cargo and npm publish the same version.
@@ -247,7 +247,7 @@ export declare let parse_cirru: (code: string) => CalcitCirruQuote;
247
247
  export declare let parse_cirru_list: (code: string) => CalcitList;
248
248
  export declare let parse_cirru_edn: (code: string, options: CalcitValue) => CalcitValue;
249
249
  type DataShapeNode = {
250
- kind: "unit" | "bool" | "number" | "string" | "symbol" | "tag" | "buffer" | "cirru-quote";
250
+ kind: "unit" | "bool" | "number" | "string" | "symbol" | "tag" | "buffer" | "cirru-quote" | "dynamic";
251
251
  } | {
252
252
  kind: "optional" | "list" | "set" | "ref";
253
253
  inner: number;
@@ -255,6 +255,10 @@ type DataShapeNode = {
255
255
  kind: "map";
256
256
  key: number;
257
257
  value: number;
258
+ } | {
259
+ kind: "map-option";
260
+ nominal: CalcitEnumDef;
261
+ inner: number;
258
262
  } | {
259
263
  kind: "struct";
260
264
  nominal: CalcitStructDef;
@@ -274,6 +278,7 @@ type DataShapeGraph = {
274
278
  nodes: DataShapeNode[];
275
279
  };
276
280
  export declare let parse_cirru_edn_as: (code: string, graph: DataShapeGraph) => CalcitValue;
281
+ export declare let decode_map_as: (value: CalcitValue, graph: DataShapeGraph) => CalcitValue;
277
282
  export declare let json_parse: (code: CalcitValue) => CalcitValue;
278
283
  export declare let json_stringify: (value: CalcitValue) => string;
279
284
  export declare let json_pretty: (value: CalcitValue) => string;
@@ -1746,6 +1746,139 @@ export let parse_cirru_edn_as = (code, graph) => {
1746
1746
  }
1747
1747
  return decode_typed_edn_node(graph, graph.root, input, "$", 0);
1748
1748
  };
1749
+ const map_decode_error = (path, message) => {
1750
+ throw new Error(`decode-map-as failed at ${path}: ${message}`);
1751
+ };
1752
+ const decode_runtime_map_node = (graph, nodeId, input, path, depth) => {
1753
+ if (depth > 1024)
1754
+ map_decode_error(path, "decode nesting exceeds 1024");
1755
+ const node = graph.nodes[nodeId];
1756
+ if (node == null)
1757
+ map_decode_error(path, `invalid data shape node #${nodeId}`);
1758
+ switch (node.kind) {
1759
+ case "dynamic":
1760
+ return input;
1761
+ case "map-option": {
1762
+ if (input instanceof CalcitEnumValue &&
1763
+ (input.enumPrototype === node.nominal || input.enumPrototype?.name() === node.nominal.name())) {
1764
+ const tag = input.tag;
1765
+ if (!(tag instanceof CalcitTag))
1766
+ map_decode_error(path, "Option variant is not a tag");
1767
+ const tag_name = tag.value;
1768
+ if (tag_name === "none" && input.extra.length === 0)
1769
+ return input;
1770
+ if (tag_name === "some" && input.extra.length === 1) {
1771
+ return new CalcitEnumValue(tag, [decode_runtime_map_node(graph, node.inner, input.extra[0], path, depth + 1)], node.nominal);
1772
+ }
1773
+ return map_decode_error(path, "invalid Option value");
1774
+ }
1775
+ return new CalcitEnumValue(newTag("some"), [decode_runtime_map_node(graph, node.inner, input, path, depth + 1)], node.nominal);
1776
+ }
1777
+ case "struct": {
1778
+ if (!(input instanceof CalcitMap || input instanceof CalcitSliceMap)) {
1779
+ return map_decode_error(path, `expected map for struct :${node.nominal.name.value}, got ${typed_edn_kind(input)}`);
1780
+ }
1781
+ const entries = new Map();
1782
+ input.pairs().forEach(([key, value]) => {
1783
+ const name = key instanceof CalcitTag ? key.value : typeof key === "string" ? key : null;
1784
+ if (name == null)
1785
+ map_decode_error(path, "struct map keys must be tags or strings");
1786
+ if (!node.fields.some(([field]) => field === name)) {
1787
+ map_decode_error(path, `struct :${node.nominal.name.value} has unknown field :${name}`);
1788
+ }
1789
+ if (entries.has(name))
1790
+ map_decode_error(path, `struct :${node.nominal.name.value} has duplicate field :${name}`);
1791
+ entries.set(name, value);
1792
+ });
1793
+ const decoded = new Map();
1794
+ node.fields.forEach(([field, child]) => {
1795
+ if (entries.has(field)) {
1796
+ decoded.set(field, decode_runtime_map_node(graph, child, entries.get(field), `${path}.${field}`, depth + 1));
1797
+ }
1798
+ else {
1799
+ const fieldNode = graph.nodes[child];
1800
+ if (fieldNode?.kind === "map-option") {
1801
+ decoded.set(field, new CalcitEnumValue(newTag("none"), [], fieldNode.nominal));
1802
+ }
1803
+ else {
1804
+ map_decode_error(path, `struct :${node.nominal.name.value} is missing required field :${field}`);
1805
+ }
1806
+ }
1807
+ });
1808
+ const values = node.nominal.fields.map((field) => decoded.get(field.value));
1809
+ return new CalcitStructValue(node.nominal.name, node.nominal.fields, values, node.nominal);
1810
+ }
1811
+ case "list": {
1812
+ if (!(input instanceof CalcitList || input instanceof CalcitSliceList)) {
1813
+ return map_decode_error(path, `expected list, got ${typed_edn_kind(input)}`);
1814
+ }
1815
+ return new CalcitSliceList(Array.from(input.items()).map((value, idx) => decode_runtime_map_node(graph, node.inner, value, `${path}[${idx}]`, depth + 1)));
1816
+ }
1817
+ case "set": {
1818
+ if (!(input instanceof CalcitSet || input instanceof TypedEdnSetView)) {
1819
+ return map_decode_error(path, `expected set, got ${typed_edn_kind(input)}`);
1820
+ }
1821
+ const values = [];
1822
+ const source = input instanceof TypedEdnSetView ? input.items : input.values();
1823
+ source.forEach((value) => {
1824
+ const decoded = decode_runtime_map_node(graph, node.inner, value, `${path}.item`, depth + 1);
1825
+ if (values.some((item) => _$n__$e_(item, decoded)))
1826
+ map_decode_error(path, "duplicate decoded set value");
1827
+ values.push(decoded);
1828
+ });
1829
+ return new CalcitSet(values);
1830
+ }
1831
+ case "map": {
1832
+ if (!(input instanceof CalcitMap || input instanceof CalcitSliceMap)) {
1833
+ return map_decode_error(path, `expected map, got ${typed_edn_kind(input)}`);
1834
+ }
1835
+ const entries = [];
1836
+ input.pairs().forEach(([key, value]) => {
1837
+ const decodedKey = decode_runtime_map_node(graph, node.key, key, `${path}.key`, depth + 1);
1838
+ if (entries.some(([existing]) => _$n__$e_(existing, decodedKey)))
1839
+ map_decode_error(path, "duplicate decoded map key");
1840
+ entries.push([decodedKey, decode_runtime_map_node(graph, node.value, value, `${path}.value`, depth + 1)]);
1841
+ });
1842
+ return new CalcitSliceMap(entries.flat());
1843
+ }
1844
+ case "optional":
1845
+ return input == null ? null : decode_runtime_map_node(graph, node.inner, input, path, depth + 1);
1846
+ case "ref":
1847
+ if (!(input instanceof CalcitRef))
1848
+ return map_decode_error(path, `expected atom, got ${typed_edn_kind(input)}`);
1849
+ return atom(decode_runtime_map_node(graph, node.inner, input.value, `${path}.value`, depth + 1));
1850
+ case "enum": {
1851
+ if (!(input instanceof CalcitEnumValue) || input.enumPrototype == null) {
1852
+ return map_decode_error(path, `expected enum :${node.nominal.name()}, got ${typed_edn_kind(input)}`);
1853
+ }
1854
+ const actualEnumName = enum_prototype_name(input.enumPrototype);
1855
+ if (actualEnumName !== node.nominal.name()) {
1856
+ return map_decode_error(path, `expected enum :${node.nominal.name()}, got enum :${actualEnumName}`);
1857
+ }
1858
+ if (!(input.tag instanceof CalcitTag))
1859
+ return map_decode_error(path, `enum :${node.nominal.name()} variant must be a tag`);
1860
+ const inputTag = input.tag;
1861
+ const variant = node.variants.find((candidate) => candidate.tag === inputTag.value);
1862
+ if (variant == null)
1863
+ return map_decode_error(path, `enum :${node.nominal.name()} has no variant :${inputTag.value}`);
1864
+ if (variant.payload.length !== input.extra.length) {
1865
+ return map_decode_error(path, `enum :${node.nominal.name()} variant :${variant.tag} expects ${variant.payload.length} payload(s), got ${input.extra.length}`);
1866
+ }
1867
+ const values = variant.payload.map((payloadNode, idx) => decode_runtime_map_node(graph, payloadNode, input.extra[idx], `${path}.payload[${idx}]`, depth + 1));
1868
+ return new CalcitEnumValue(newTag(variant.tag), values, node.nominal);
1869
+ }
1870
+ default:
1871
+ return decode_typed_edn_node(graph, nodeId, input, path, depth);
1872
+ }
1873
+ };
1874
+ export let decode_map_as = (value, graph) => {
1875
+ if (graph.version !== 1)
1876
+ throw new Error(`decode-map-as expected data shape ABI version 1, got ${graph.version}`);
1877
+ if (typeof graph.fingerprint !== "string" || graph.fingerprint.length === 0) {
1878
+ throw new Error("decode-map-as expected a non-empty data shape fingerprint");
1879
+ }
1880
+ return decode_runtime_map_node(graph, graph.root, value, "$", 0);
1881
+ };
1749
1882
  const json_to_calcit = (value) => {
1750
1883
  if (value == null)
1751
1884
  return null;
package/lib/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@calcit/procs",
3
- "version": "0.13.14",
3
+ "version": "0.13.16",
4
4
  "main": "./lib/calcit.procs.mjs",
5
5
  "devDependencies": {
6
6
  "@types/node": "^25.7.0",
@@ -0,0 +1,3 @@
1
+ # Repository sync inventory
2
+
3
+ Recorded the organization repository inventory in `notes/repos.md`. This pass fetches each repository's latest `origin/main`, switches to an open PR head when one exists, and excludes projects whose declared `deps.cirru` Calcit version is `0.13.15`.