@grafema/util 0.3.0-beta

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 (324) hide show
  1. package/LICENSE +190 -0
  2. package/dist/api/GraphAPI.d.ts +87 -0
  3. package/dist/api/GraphAPI.d.ts.map +1 -0
  4. package/dist/api/GraphAPI.js +212 -0
  5. package/dist/api/GraphAPI.js.map +1 -0
  6. package/dist/api/GuaranteeAPI.d.ts +147 -0
  7. package/dist/api/GuaranteeAPI.d.ts.map +1 -0
  8. package/dist/api/GuaranteeAPI.js +290 -0
  9. package/dist/api/GuaranteeAPI.js.map +1 -0
  10. package/dist/config/ConfigLoader.d.ts +214 -0
  11. package/dist/config/ConfigLoader.d.ts.map +1 -0
  12. package/dist/config/ConfigLoader.js +441 -0
  13. package/dist/config/ConfigLoader.js.map +1 -0
  14. package/dist/config/index.d.ts +6 -0
  15. package/dist/config/index.d.ts.map +1 -0
  16. package/dist/config/index.js +5 -0
  17. package/dist/config/index.js.map +1 -0
  18. package/dist/core/CoverageAnalyzer.d.ts +65 -0
  19. package/dist/core/CoverageAnalyzer.d.ts.map +1 -0
  20. package/dist/core/CoverageAnalyzer.js +199 -0
  21. package/dist/core/CoverageAnalyzer.js.map +1 -0
  22. package/dist/core/FileExplainer.d.ts +101 -0
  23. package/dist/core/FileExplainer.d.ts.map +1 -0
  24. package/dist/core/FileExplainer.js +140 -0
  25. package/dist/core/FileExplainer.js.map +1 -0
  26. package/dist/core/FileOverview.d.ts +124 -0
  27. package/dist/core/FileOverview.d.ts.map +1 -0
  28. package/dist/core/FileOverview.js +279 -0
  29. package/dist/core/FileOverview.js.map +1 -0
  30. package/dist/core/GrafemaUri.d.ts +66 -0
  31. package/dist/core/GrafemaUri.d.ts.map +1 -0
  32. package/dist/core/GrafemaUri.js +191 -0
  33. package/dist/core/GrafemaUri.js.map +1 -0
  34. package/dist/core/GraphBackend.d.ts +158 -0
  35. package/dist/core/GraphBackend.d.ts.map +1 -0
  36. package/dist/core/GraphBackend.js +85 -0
  37. package/dist/core/GraphBackend.js.map +1 -0
  38. package/dist/core/GraphFreshnessChecker.d.ts +33 -0
  39. package/dist/core/GraphFreshnessChecker.d.ts.map +1 -0
  40. package/dist/core/GraphFreshnessChecker.js +104 -0
  41. package/dist/core/GraphFreshnessChecker.js.map +1 -0
  42. package/dist/core/GuaranteeManager.d.ts +254 -0
  43. package/dist/core/GuaranteeManager.d.ts.map +1 -0
  44. package/dist/core/GuaranteeManager.js +447 -0
  45. package/dist/core/GuaranteeManager.js.map +1 -0
  46. package/dist/core/HashUtils.d.ts +24 -0
  47. package/dist/core/HashUtils.d.ts.map +1 -0
  48. package/dist/core/HashUtils.js +46 -0
  49. package/dist/core/HashUtils.js.map +1 -0
  50. package/dist/core/IncrementalReanalyzer.d.ts +33 -0
  51. package/dist/core/IncrementalReanalyzer.d.ts.map +1 -0
  52. package/dist/core/IncrementalReanalyzer.js +67 -0
  53. package/dist/core/IncrementalReanalyzer.js.map +1 -0
  54. package/dist/core/ResourceRegistry.d.ts +17 -0
  55. package/dist/core/ResourceRegistry.d.ts.map +1 -0
  56. package/dist/core/ResourceRegistry.js +32 -0
  57. package/dist/core/ResourceRegistry.js.map +1 -0
  58. package/dist/core/SemanticId.d.ts +159 -0
  59. package/dist/core/SemanticId.d.ts.map +1 -0
  60. package/dist/core/SemanticId.js +291 -0
  61. package/dist/core/SemanticId.js.map +1 -0
  62. package/dist/core/VersionManager.d.ts +166 -0
  63. package/dist/core/VersionManager.d.ts.map +1 -0
  64. package/dist/core/VersionManager.js +239 -0
  65. package/dist/core/VersionManager.js.map +1 -0
  66. package/dist/core/brandNodeInternal.d.ts +14 -0
  67. package/dist/core/brandNodeInternal.d.ts.map +1 -0
  68. package/dist/core/brandNodeInternal.js +4 -0
  69. package/dist/core/brandNodeInternal.js.map +1 -0
  70. package/dist/core/nodes/GuaranteeNode.d.ts +76 -0
  71. package/dist/core/nodes/GuaranteeNode.d.ts.map +1 -0
  72. package/dist/core/nodes/GuaranteeNode.js +118 -0
  73. package/dist/core/nodes/GuaranteeNode.js.map +1 -0
  74. package/dist/core/nodes/IssueNode.d.ts +73 -0
  75. package/dist/core/nodes/IssueNode.d.ts.map +1 -0
  76. package/dist/core/nodes/IssueNode.js +130 -0
  77. package/dist/core/nodes/IssueNode.js.map +1 -0
  78. package/dist/core/nodes/NodeKind.d.ts +104 -0
  79. package/dist/core/nodes/NodeKind.d.ts.map +1 -0
  80. package/dist/core/nodes/NodeKind.js +166 -0
  81. package/dist/core/nodes/NodeKind.js.map +1 -0
  82. package/dist/diagnostics/DiagnosticCollector.d.ts +103 -0
  83. package/dist/diagnostics/DiagnosticCollector.d.ts.map +1 -0
  84. package/dist/diagnostics/DiagnosticCollector.js +133 -0
  85. package/dist/diagnostics/DiagnosticCollector.js.map +1 -0
  86. package/dist/diagnostics/DiagnosticReporter.d.ts +122 -0
  87. package/dist/diagnostics/DiagnosticReporter.d.ts.map +1 -0
  88. package/dist/diagnostics/DiagnosticReporter.js +300 -0
  89. package/dist/diagnostics/DiagnosticReporter.js.map +1 -0
  90. package/dist/diagnostics/DiagnosticWriter.d.ts +31 -0
  91. package/dist/diagnostics/DiagnosticWriter.d.ts.map +1 -0
  92. package/dist/diagnostics/DiagnosticWriter.js +44 -0
  93. package/dist/diagnostics/DiagnosticWriter.js.map +1 -0
  94. package/dist/diagnostics/categories.d.ts +57 -0
  95. package/dist/diagnostics/categories.d.ts.map +1 -0
  96. package/dist/diagnostics/categories.js +71 -0
  97. package/dist/diagnostics/categories.js.map +1 -0
  98. package/dist/diagnostics/index.d.ts +17 -0
  99. package/dist/diagnostics/index.d.ts.map +1 -0
  100. package/dist/diagnostics/index.js +15 -0
  101. package/dist/diagnostics/index.js.map +1 -0
  102. package/dist/errors/GrafemaError.d.ts +200 -0
  103. package/dist/errors/GrafemaError.d.ts.map +1 -0
  104. package/dist/errors/GrafemaError.js +209 -0
  105. package/dist/errors/GrafemaError.js.map +1 -0
  106. package/dist/index.d.ts +75 -0
  107. package/dist/index.d.ts.map +1 -0
  108. package/dist/index.js +76 -0
  109. package/dist/index.js.map +1 -0
  110. package/dist/instructions/index.d.ts +8 -0
  111. package/dist/instructions/index.d.ts.map +1 -0
  112. package/dist/instructions/index.js +20 -0
  113. package/dist/instructions/index.js.map +1 -0
  114. package/dist/instructions/onboarding.md +133 -0
  115. package/dist/knowledge/KnowledgeBase.d.ts +113 -0
  116. package/dist/knowledge/KnowledgeBase.d.ts.map +1 -0
  117. package/dist/knowledge/KnowledgeBase.js +420 -0
  118. package/dist/knowledge/KnowledgeBase.js.map +1 -0
  119. package/dist/knowledge/SemanticAddressResolver.d.ts +59 -0
  120. package/dist/knowledge/SemanticAddressResolver.d.ts.map +1 -0
  121. package/dist/knowledge/SemanticAddressResolver.js +160 -0
  122. package/dist/knowledge/SemanticAddressResolver.js.map +1 -0
  123. package/dist/knowledge/git-ingest.d.ts +58 -0
  124. package/dist/knowledge/git-ingest.d.ts.map +1 -0
  125. package/dist/knowledge/git-ingest.js +301 -0
  126. package/dist/knowledge/git-ingest.js.map +1 -0
  127. package/dist/knowledge/git-queries.d.ts +86 -0
  128. package/dist/knowledge/git-queries.d.ts.map +1 -0
  129. package/dist/knowledge/git-queries.js +177 -0
  130. package/dist/knowledge/git-queries.js.map +1 -0
  131. package/dist/knowledge/index.d.ts +14 -0
  132. package/dist/knowledge/index.d.ts.map +1 -0
  133. package/dist/knowledge/index.js +10 -0
  134. package/dist/knowledge/index.js.map +1 -0
  135. package/dist/knowledge/parser.d.ts +39 -0
  136. package/dist/knowledge/parser.d.ts.map +1 -0
  137. package/dist/knowledge/parser.js +292 -0
  138. package/dist/knowledge/parser.js.map +1 -0
  139. package/dist/knowledge/types.d.ts +133 -0
  140. package/dist/knowledge/types.d.ts.map +1 -0
  141. package/dist/knowledge/types.js +8 -0
  142. package/dist/knowledge/types.js.map +1 -0
  143. package/dist/logging/Logger.d.ts +98 -0
  144. package/dist/logging/Logger.d.ts.map +1 -0
  145. package/dist/logging/Logger.js +274 -0
  146. package/dist/logging/Logger.js.map +1 -0
  147. package/dist/notation/archetypes.d.ts +36 -0
  148. package/dist/notation/archetypes.d.ts.map +1 -0
  149. package/dist/notation/archetypes.js +173 -0
  150. package/dist/notation/archetypes.js.map +1 -0
  151. package/dist/notation/fold.d.ts +25 -0
  152. package/dist/notation/fold.d.ts.map +1 -0
  153. package/dist/notation/fold.js +598 -0
  154. package/dist/notation/fold.js.map +1 -0
  155. package/dist/notation/index.d.ts +18 -0
  156. package/dist/notation/index.d.ts.map +1 -0
  157. package/dist/notation/index.js +16 -0
  158. package/dist/notation/index.js.map +1 -0
  159. package/dist/notation/lodExtractor.d.ts +32 -0
  160. package/dist/notation/lodExtractor.d.ts.map +1 -0
  161. package/dist/notation/lodExtractor.js +149 -0
  162. package/dist/notation/lodExtractor.js.map +1 -0
  163. package/dist/notation/nameShortener.d.ts +22 -0
  164. package/dist/notation/nameShortener.d.ts.map +1 -0
  165. package/dist/notation/nameShortener.js +24 -0
  166. package/dist/notation/nameShortener.js.map +1 -0
  167. package/dist/notation/perspectives.d.ts +11 -0
  168. package/dist/notation/perspectives.d.ts.map +1 -0
  169. package/dist/notation/perspectives.js +16 -0
  170. package/dist/notation/perspectives.js.map +1 -0
  171. package/dist/notation/renderer.d.ts +31 -0
  172. package/dist/notation/renderer.d.ts.map +1 -0
  173. package/dist/notation/renderer.js +315 -0
  174. package/dist/notation/renderer.js.map +1 -0
  175. package/dist/notation/traceRenderer.d.ts +39 -0
  176. package/dist/notation/traceRenderer.d.ts.map +1 -0
  177. package/dist/notation/traceRenderer.js +358 -0
  178. package/dist/notation/traceRenderer.js.map +1 -0
  179. package/dist/notation/types.d.ts +66 -0
  180. package/dist/notation/types.d.ts.map +1 -0
  181. package/dist/notation/types.js +10 -0
  182. package/dist/notation/types.js.map +1 -0
  183. package/dist/queries/NodeContext.d.ts +81 -0
  184. package/dist/queries/NodeContext.d.ts.map +1 -0
  185. package/dist/queries/NodeContext.js +196 -0
  186. package/dist/queries/NodeContext.js.map +1 -0
  187. package/dist/queries/findCallsInFunction.d.ts +62 -0
  188. package/dist/queries/findCallsInFunction.d.ts.map +1 -0
  189. package/dist/queries/findCallsInFunction.js +169 -0
  190. package/dist/queries/findCallsInFunction.js.map +1 -0
  191. package/dist/queries/findContainingFunction.d.ts +57 -0
  192. package/dist/queries/findContainingFunction.d.ts.map +1 -0
  193. package/dist/queries/findContainingFunction.js +91 -0
  194. package/dist/queries/findContainingFunction.js.map +1 -0
  195. package/dist/queries/index.d.ts +18 -0
  196. package/dist/queries/index.d.ts.map +1 -0
  197. package/dist/queries/index.js +14 -0
  198. package/dist/queries/index.js.map +1 -0
  199. package/dist/queries/traceDataflow.d.ts +65 -0
  200. package/dist/queries/traceDataflow.d.ts.map +1 -0
  201. package/dist/queries/traceDataflow.js +754 -0
  202. package/dist/queries/traceDataflow.js.map +1 -0
  203. package/dist/queries/traceValues.d.ts +70 -0
  204. package/dist/queries/traceValues.d.ts.map +1 -0
  205. package/dist/queries/traceValues.js +373 -0
  206. package/dist/queries/traceValues.js.map +1 -0
  207. package/dist/queries/types.d.ts +166 -0
  208. package/dist/queries/types.d.ts.map +1 -0
  209. package/dist/queries/types.js +10 -0
  210. package/dist/queries/types.js.map +1 -0
  211. package/dist/schema/GraphSchemaExtractor.d.ts +53 -0
  212. package/dist/schema/GraphSchemaExtractor.d.ts.map +1 -0
  213. package/dist/schema/GraphSchemaExtractor.js +125 -0
  214. package/dist/schema/GraphSchemaExtractor.js.map +1 -0
  215. package/dist/schema/InterfaceSchemaExtractor.d.ts +73 -0
  216. package/dist/schema/InterfaceSchemaExtractor.d.ts.map +1 -0
  217. package/dist/schema/InterfaceSchemaExtractor.js +113 -0
  218. package/dist/schema/InterfaceSchemaExtractor.js.map +1 -0
  219. package/dist/schema/index.d.ts +5 -0
  220. package/dist/schema/index.d.ts.map +1 -0
  221. package/dist/schema/index.js +3 -0
  222. package/dist/schema/index.js.map +1 -0
  223. package/dist/storage/backends/RFDBServerBackend.d.ts +356 -0
  224. package/dist/storage/backends/RFDBServerBackend.d.ts.map +1 -0
  225. package/dist/storage/backends/RFDBServerBackend.js +748 -0
  226. package/dist/storage/backends/RFDBServerBackend.js.map +1 -0
  227. package/dist/storage/backends/typeValidation.d.ts +47 -0
  228. package/dist/storage/backends/typeValidation.d.ts.map +1 -0
  229. package/dist/storage/backends/typeValidation.js +141 -0
  230. package/dist/storage/backends/typeValidation.js.map +1 -0
  231. package/dist/utils/findRfdbBinary.d.ts +67 -0
  232. package/dist/utils/findRfdbBinary.d.ts.map +1 -0
  233. package/dist/utils/findRfdbBinary.js +261 -0
  234. package/dist/utils/findRfdbBinary.js.map +1 -0
  235. package/dist/utils/lazyDownload.d.ts +43 -0
  236. package/dist/utils/lazyDownload.d.ts.map +1 -0
  237. package/dist/utils/lazyDownload.js +175 -0
  238. package/dist/utils/lazyDownload.js.map +1 -0
  239. package/dist/utils/moduleResolution.d.ts +134 -0
  240. package/dist/utils/moduleResolution.d.ts.map +1 -0
  241. package/dist/utils/moduleResolution.js +189 -0
  242. package/dist/utils/moduleResolution.js.map +1 -0
  243. package/dist/utils/resolveNodeFile.d.ts +13 -0
  244. package/dist/utils/resolveNodeFile.d.ts.map +1 -0
  245. package/dist/utils/resolveNodeFile.js +18 -0
  246. package/dist/utils/resolveNodeFile.js.map +1 -0
  247. package/dist/utils/startRfdbServer.d.ts +63 -0
  248. package/dist/utils/startRfdbServer.d.ts.map +1 -0
  249. package/dist/utils/startRfdbServer.js +142 -0
  250. package/dist/utils/startRfdbServer.js.map +1 -0
  251. package/dist/validation/PathValidator.d.ts +80 -0
  252. package/dist/validation/PathValidator.d.ts.map +1 -0
  253. package/dist/validation/PathValidator.js +252 -0
  254. package/dist/validation/PathValidator.js.map +1 -0
  255. package/dist/version.d.ts +11 -0
  256. package/dist/version.d.ts.map +1 -0
  257. package/dist/version.js +26 -0
  258. package/dist/version.js.map +1 -0
  259. package/package.json +50 -0
  260. package/src/api/GraphAPI.ts +307 -0
  261. package/src/api/GuaranteeAPI.ts +402 -0
  262. package/src/config/ConfigLoader.ts +653 -0
  263. package/src/config/index.ts +13 -0
  264. package/src/core/CoverageAnalyzer.ts +243 -0
  265. package/src/core/FileExplainer.ts +179 -0
  266. package/src/core/FileOverview.ts +397 -0
  267. package/src/core/GrafemaUri.ts +216 -0
  268. package/src/core/GraphBackend.ts +266 -0
  269. package/src/core/GraphFreshnessChecker.ts +145 -0
  270. package/src/core/GuaranteeManager.ts +684 -0
  271. package/src/core/HashUtils.ts +48 -0
  272. package/src/core/IncrementalReanalyzer.ts +106 -0
  273. package/src/core/ResourceRegistry.ts +39 -0
  274. package/src/core/SemanticId.ts +423 -0
  275. package/src/core/VersionManager.ts +405 -0
  276. package/src/core/brandNodeInternal.ts +16 -0
  277. package/src/core/nodes/GuaranteeNode.ts +162 -0
  278. package/src/core/nodes/IssueNode.ts +177 -0
  279. package/src/core/nodes/NodeKind.ts +192 -0
  280. package/src/diagnostics/DiagnosticCollector.ts +170 -0
  281. package/src/diagnostics/DiagnosticReporter.ts +395 -0
  282. package/src/diagnostics/DiagnosticWriter.ts +50 -0
  283. package/src/diagnostics/categories.ts +104 -0
  284. package/src/diagnostics/index.ts +30 -0
  285. package/src/errors/GrafemaError.ts +297 -0
  286. package/src/index.ts +261 -0
  287. package/src/instructions/index.ts +21 -0
  288. package/src/instructions/onboarding.md +133 -0
  289. package/src/knowledge/KnowledgeBase.ts +486 -0
  290. package/src/knowledge/SemanticAddressResolver.ts +191 -0
  291. package/src/knowledge/git-ingest.ts +402 -0
  292. package/src/knowledge/git-queries.ts +269 -0
  293. package/src/knowledge/index.ts +29 -0
  294. package/src/knowledge/parser.ts +294 -0
  295. package/src/knowledge/types.ts +146 -0
  296. package/src/logging/Logger.ts +303 -0
  297. package/src/notation/archetypes.ts +189 -0
  298. package/src/notation/fold.ts +696 -0
  299. package/src/notation/index.ts +27 -0
  300. package/src/notation/lodExtractor.ts +177 -0
  301. package/src/notation/nameShortener.ts +24 -0
  302. package/src/notation/perspectives.ts +18 -0
  303. package/src/notation/renderer.ts +394 -0
  304. package/src/notation/traceRenderer.ts +458 -0
  305. package/src/notation/types.ts +79 -0
  306. package/src/queries/NodeContext.ts +280 -0
  307. package/src/queries/findCallsInFunction.ts +249 -0
  308. package/src/queries/findContainingFunction.ts +124 -0
  309. package/src/queries/index.ts +44 -0
  310. package/src/queries/traceDataflow.ts +838 -0
  311. package/src/queries/traceValues.ts +531 -0
  312. package/src/queries/types.ts +191 -0
  313. package/src/schema/GraphSchemaExtractor.ts +177 -0
  314. package/src/schema/InterfaceSchemaExtractor.ts +173 -0
  315. package/src/schema/index.ts +5 -0
  316. package/src/storage/backends/RFDBServerBackend.ts +895 -0
  317. package/src/storage/backends/typeValidation.ts +154 -0
  318. package/src/utils/findRfdbBinary.ts +288 -0
  319. package/src/utils/lazyDownload.ts +206 -0
  320. package/src/utils/moduleResolution.ts +271 -0
  321. package/src/utils/resolveNodeFile.ts +18 -0
  322. package/src/utils/startRfdbServer.ts +197 -0
  323. package/src/validation/PathValidator.ts +334 -0
  324. package/src/version.ts +28 -0
@@ -0,0 +1,696 @@
1
+ /**
2
+ * Notation Fold Engine — structural compression of sibling blocks
3
+ *
4
+ * Pure function: NotationBlock[] → NotationBlock[]
5
+ * Fold is a view-layer transform, not part of DSL grammar.
6
+ *
7
+ * Implements 11 rules from notation-folding.md:
8
+ * Rules 1-5: structural (language-agnostic)
9
+ * Rules 6-7: cleanup (dedup, artifacts)
10
+ * Rules 8-11: semantic (derivation/call patterns)
11
+ *
12
+ * Pipeline order matters — chains/dispatch collapse adjacent siblings first,
13
+ * making the remaining set more amenable to group folding.
14
+ *
15
+ * @module notation/fold
16
+ */
17
+
18
+ import type { NotationBlock } from './types.js';
19
+
20
+ /** Minimum group size to trigger folding */
21
+ const FOLD_THRESHOLD = 3;
22
+
23
+ // === Node Rendering Roles ===
24
+
25
+ type NodeRole = 'actor' | 'container' | 'binding' | 'datum' | 'shape' | 'control';
26
+
27
+ const CONTAINER_TYPES = new Set(['MODULE', 'CLASS', 'OBJECT', 'NAMESPACE', 'PROGRAM']);
28
+ const BINDING_TYPES = new Set(['IMPORT', 'IMPORT_BINDING', 'EXPORT', 'EXPORT_BINDING']);
29
+ const SHAPE_TYPES = new Set(['INTERFACE', 'TYPE_ALIAS', 'ENUM', 'ENUM_MEMBER']);
30
+ const CONTROL_TYPES = new Set(['LOOP', 'BRANCH', 'CASE', 'CONDITIONAL', 'SWITCH', 'TRY']);
31
+ const BEHAVIOR_OPERATORS = new Set(['>', '=>', '>x', '~>>']);
32
+
33
+ /**
34
+ * Classify a block's rendering role based on node type + edge content.
35
+ *
36
+ * Roles (determined by graph shape, not AST type alone):
37
+ * Actor — has behavior edges (calls, writes, throws...) → Block
38
+ * Container — MODULE, CLASS, etc. → Block
39
+ * Binding — IMPORT, EXPORT → Line
40
+ * Datum — VARIABLE/CONSTANT with only passive edges → Inline `name < verb source`
41
+ * Shape — INTERFACE, TYPE_ALIAS, ENUM → Block or Name
42
+ * Control — LOOP, BRANCH, CASE → Modifier
43
+ *
44
+ * No suppression: all nodes with edges are preserved (Inv-2 Side Effect Visibility).
45
+ */
46
+ function classifyRole(block: NotationBlock): NodeRole {
47
+ const type = block.nodeType;
48
+
49
+ if (CONTAINER_TYPES.has(type)) return 'container';
50
+ if (BINDING_TYPES.has(type)) return 'binding';
51
+ if (SHAPE_TYPES.has(type)) return 'shape';
52
+ if (CONTROL_TYPES.has(type)) return 'control';
53
+
54
+ // Check for behavior edges
55
+ const hasBehavior = block.lines.some(l => BEHAVIOR_OPERATORS.has(l.operator));
56
+ if (hasBehavior) return 'actor';
57
+
58
+ // VARIABLE/CONSTANT/PARAMETER with only passive edges → datum
59
+ if (type === 'VARIABLE' || type === 'CONSTANT' || type === 'PARAMETER') {
60
+ return 'datum';
61
+ }
62
+
63
+ // FUNCTION/METHOD → actor (even without visible behavior)
64
+ if (type === 'FUNCTION' || type === 'METHOD') return 'actor';
65
+
66
+ return 'actor';
67
+ }
68
+
69
+ /**
70
+ * Apply role-based transforms: datum → inline (preserves archetype operator and verb).
71
+ */
72
+ function applyRoleTransforms(blocks: NotationBlock[]): NotationBlock[] {
73
+ const result: NotationBlock[] = [];
74
+
75
+ for (const block of blocks) {
76
+ if (block.foldMeta) {
77
+ result.push(block);
78
+ continue;
79
+ }
80
+
81
+ const role = classifyRole(block);
82
+
83
+ switch (role) {
84
+ case 'datum': {
85
+ // Inline: render as "name < verb source" — no braces, preserves archetype
86
+ const inlineName = formatDatumInline(block);
87
+ result.push({
88
+ ...block,
89
+ displayName: inlineName,
90
+ lines: [],
91
+ children: [],
92
+ foldMeta: { kind: 'datum-inline', count: 1 },
93
+ });
94
+ break;
95
+ }
96
+
97
+ default:
98
+ result.push(block);
99
+ }
100
+ }
101
+
102
+ return result;
103
+ }
104
+
105
+ /**
106
+ * Format a datum block as a single inline string: "name < verb source"
107
+ * Preserves the archetype operator and verb — no information loss.
108
+ */
109
+ function formatDatumInline(block: NotationBlock): string {
110
+ // Find the first flow_in line
111
+ for (const line of block.lines) {
112
+ if (line.operator && line.targets.length > 0) {
113
+ const mod = line.modifier ? `${line.modifier} ` : '';
114
+ return `${block.displayName} ${mod}${line.operator} ${line.verb} ${line.targets.join(', ')}`;
115
+ }
116
+ }
117
+ return block.displayName;
118
+ }
119
+
120
+ // === Block Signature ===
121
+
122
+ interface BlockSignature {
123
+ nodeType: string;
124
+ lineKeys: string[];
125
+ childCount: number;
126
+ }
127
+
128
+ function computeSignature(block: NotationBlock): BlockSignature {
129
+ return {
130
+ nodeType: block.nodeType,
131
+ lineKeys: block.lines
132
+ .map(l => `${l.modifier ?? ''}|${l.operator}|${l.verb}`)
133
+ .sort(),
134
+ childCount: block.children.length,
135
+ };
136
+ }
137
+
138
+ function signatureKey(sig: BlockSignature): string {
139
+ return `${sig.nodeType}|${sig.lineKeys.join(';')}|${sig.childCount}`;
140
+ }
141
+
142
+ // === Main fold function ===
143
+
144
+ /**
145
+ * Apply all folding rules to a list of sibling blocks.
146
+ * Returns a new list where repetitive structures are compressed.
147
+ *
148
+ * Folding is recursive — children of non-folded blocks are also folded.
149
+ */
150
+ export function foldBlocks(blocks: NotationBlock[]): NotationBlock[] {
151
+ // Rule 4: Dedup by nodeId (per-block cleanup, always applies)
152
+ let current = dedup(blocks);
153
+
154
+ // Rule 6: Target dedup within each block's lines (per-block cleanup, always applies)
155
+ current = current.map(dedupTargets);
156
+
157
+ if (current.length <= 1) {
158
+ // Role transforms still apply to single blocks
159
+ current = applyRoleTransforms(current);
160
+ // Still recurse into children
161
+ return current.map(b => {
162
+ if (b.foldMeta || b.children.length === 0) return b;
163
+ return { ...b, children: foldBlocks(b.children) };
164
+ });
165
+ }
166
+
167
+ // Rules 8/9: Chain detection and linearization (before role transforms — needs REFERENCE blocks)
168
+ const chainResult = detectChains(current);
169
+ current = [...chainResult.chains, ...chainResult.remaining];
170
+
171
+ // Rule 11: Case dispatch detection
172
+ const dispatchResult = detectDispatch(current);
173
+ current = [...dispatchResult.dispatches, ...dispatchResult.remaining];
174
+
175
+ // Rule 10: Repetitive call fold
176
+ const callResult = detectRepetitiveCalls(current);
177
+ current = [...callResult.folds, ...callResult.remaining];
178
+
179
+ // Role transforms: datum → inline, internal → suppress (after semantic rules)
180
+ current = applyRoleTransforms(current);
181
+
182
+ // Separate already-folded from remaining
183
+ const alreadyFolded = current.filter(b => b.foldMeta);
184
+ const unfolded = current.filter(b => !b.foldMeta);
185
+
186
+ // Rule 7: Repeated leaf fold
187
+ const leafResult = repeatedLeafFold(unfolded);
188
+
189
+ // Rule 5: Structural suppression
190
+ const trivialResult = structuralSuppression(leafResult.remaining);
191
+
192
+ // Rules 1-3: Group fold with anomaly preservation and source aggregation
193
+ const groupResult = groupFold(trivialResult.remaining);
194
+
195
+ // Combine: early folds, group folds, leaf folds, trivial groups, anomalies
196
+ const result = [
197
+ ...alreadyFolded,
198
+ ...groupResult.folded,
199
+ ...leafResult.folds,
200
+ ...trivialResult.groups,
201
+ ...groupResult.anomalies,
202
+ ];
203
+
204
+ // Recurse: fold children of non-folded blocks
205
+ return result.map(b => {
206
+ if (b.foldMeta || b.children.length === 0) return b;
207
+ return { ...b, children: foldBlocks(b.children) };
208
+ });
209
+ }
210
+
211
+ // === Rule 4: Dedup ===
212
+
213
+ function dedup(blocks: NotationBlock[]): NotationBlock[] {
214
+ const seen = new Map<string, NotationBlock>();
215
+ for (const block of blocks) {
216
+ const existing = seen.get(block.nodeId);
217
+ if (!existing) {
218
+ seen.set(block.nodeId, block);
219
+ } else {
220
+ // Keep the richer block (more lines, children, and targets)
221
+ const richness = (b: NotationBlock) =>
222
+ b.lines.length + b.children.length + b.lines.reduce((s, l) => s + l.targets.length, 0);
223
+ if (richness(block) > richness(existing)) {
224
+ seen.set(block.nodeId, block);
225
+ }
226
+ }
227
+ }
228
+ return [...seen.values()];
229
+ }
230
+
231
+ // === Rule 6: Target dedup within lines ===
232
+
233
+ function dedupTargets(block: NotationBlock): NotationBlock {
234
+ const hasDupes = block.lines.some(l => l.targets.length !== new Set(l.targets).size);
235
+ if (!hasDupes) return block;
236
+
237
+ return {
238
+ ...block,
239
+ lines: block.lines.map(line => ({
240
+ ...line,
241
+ targets: [...new Set(line.targets)],
242
+ })),
243
+ };
244
+ }
245
+
246
+ // === Rules 8/9: Chain detection and linearization ===
247
+
248
+ function detectChains(
249
+ blocks: NotationBlock[],
250
+ ): { chains: NotationBlock[]; remaining: NotationBlock[] } {
251
+ if (blocks.length < 3) return { chains: [], remaining: blocks };
252
+
253
+ const consumed = new Set<number>();
254
+ const chainBlocks: NotationBlock[] = [];
255
+
256
+ // Find all blocks with "derived from" lines
257
+ const derivesFrom = new Map<number, string>();
258
+ for (let i = 0; i < blocks.length; i++) {
259
+ for (const line of blocks[i].lines) {
260
+ if (line.verb === 'derived from' && line.targets.length > 0) {
261
+ derivesFrom.set(i, line.targets[0]);
262
+ break;
263
+ }
264
+ }
265
+ }
266
+
267
+ if (derivesFrom.size < 2) return { chains: [], remaining: blocks };
268
+
269
+ // Group blocks by location
270
+ const locationGroups = new Map<string, number[]>();
271
+ for (let i = 0; i < blocks.length; i++) {
272
+ const loc = blocks[i].location;
273
+ if (!loc) continue;
274
+ if (!locationGroups.has(loc)) locationGroups.set(loc, []);
275
+ locationGroups.get(loc)!.push(i);
276
+ }
277
+
278
+ for (const [loc, indices] of locationGroups) {
279
+ if (indices.length < 3) continue;
280
+
281
+ // Build name → indices map within this location group
282
+ const nameToIndices = new Map<string, number[]>();
283
+ for (const idx of indices) {
284
+ const name = blocks[idx].displayName;
285
+ if (!nameToIndices.has(name)) nameToIndices.set(name, []);
286
+ nameToIndices.get(name)!.push(idx);
287
+ }
288
+
289
+ // Build name derivation graph: name → derives from name
290
+ const nameDerivesFrom = new Map<string, string>();
291
+ for (const idx of indices) {
292
+ if (derivesFrom.has(idx) && nameToIndices.has(derivesFrom.get(idx)!)) {
293
+ nameDerivesFrom.set(blocks[idx].displayName, derivesFrom.get(idx)!);
294
+ }
295
+ }
296
+
297
+ if (nameDerivesFrom.size < 2) continue;
298
+
299
+ // Find chain roots: names derived FROM but don't derive from anything
300
+ const derivedFromNames = new Set(nameDerivesFrom.values());
301
+ const derivingNames = new Set(nameDerivesFrom.keys());
302
+ const roots = [...derivedFromNames].filter(n => !derivingNames.has(n));
303
+
304
+ for (const root of roots) {
305
+ // Build chain from root forward
306
+ const steps: string[] = [root];
307
+ let current = root;
308
+ const visited = new Set<string>([root]);
309
+
310
+ while (true) {
311
+ let next: string | undefined;
312
+ for (const [name, from] of nameDerivesFrom) {
313
+ if (from === current && !visited.has(name)) {
314
+ next = name;
315
+ break;
316
+ }
317
+ }
318
+ if (!next) break;
319
+ steps.push(next);
320
+ visited.add(next);
321
+ current = next;
322
+ }
323
+
324
+ if (steps.length < 3) continue;
325
+
326
+ // Mark all blocks in this chain as consumed
327
+ for (const name of steps) {
328
+ const idxs = nameToIndices.get(name);
329
+ if (idxs) {
330
+ for (const idx of idxs) consumed.add(idx);
331
+ }
332
+ }
333
+
334
+ // Check if last step has "reads" line for source
335
+ let chainSource: string | undefined;
336
+ const lastIdxs = nameToIndices.get(steps[steps.length - 1]) ?? [];
337
+ for (const idx of lastIdxs) {
338
+ for (const line of blocks[idx].lines) {
339
+ if (line.verb === 'reads' && line.targets.length > 0) {
340
+ chainSource = line.targets[0];
341
+ }
342
+ }
343
+ }
344
+
345
+ chainBlocks.push({
346
+ nodeId: `chain:${steps.join(':')}`,
347
+ displayName: steps.join(' \u2218 ') + (chainSource ? `(${chainSource})` : ''),
348
+ nodeType: blocks[indices[0]].nodeType,
349
+ lines: [],
350
+ children: [],
351
+ location: loc,
352
+ foldMeta: {
353
+ kind: 'chain',
354
+ count: steps.length,
355
+ chainSteps: steps,
356
+ chainSource,
357
+ },
358
+ });
359
+ }
360
+ }
361
+
362
+ const remaining = blocks.filter((_, i) => !consumed.has(i));
363
+ return { chains: chainBlocks, remaining };
364
+ }
365
+
366
+ // === Rule 11: Case dispatch detection ===
367
+
368
+ function detectDispatch(
369
+ blocks: NotationBlock[],
370
+ ): { dispatches: NotationBlock[]; remaining: NotationBlock[] } {
371
+ if (blocks.length < 8) return { dispatches: [], remaining: blocks };
372
+
373
+ // Find blocks with "derived from case" lines
374
+ const caseIndices: number[] = [];
375
+ for (let i = 0; i < blocks.length; i++) {
376
+ for (const line of blocks[i].lines) {
377
+ if (line.verb === 'derived from' && line.targets.includes('case')) {
378
+ caseIndices.push(i);
379
+ break;
380
+ }
381
+ }
382
+ }
383
+
384
+ if (caseIndices.length < FOLD_THRESHOLD) return { dispatches: [], remaining: blocks };
385
+
386
+ // Each case block has a pattern block before it
387
+ const consumed = new Set<number>();
388
+ const branches: Array<{ pattern: string; handler: string; indices: number[] }> = [];
389
+
390
+ for (const idx of caseIndices) {
391
+ if (consumed.has(idx)) continue;
392
+
393
+ const handler = blocks[idx];
394
+ // Pattern node is at idx-1 with no edges/children
395
+ if (idx > 0 && !consumed.has(idx - 1) &&
396
+ blocks[idx - 1].lines.length === 0 && blocks[idx - 1].children.length === 0) {
397
+ const pattern = blocks[idx - 1];
398
+ // Only consume the pattern + handler pair; dup/arg blocks handled by dedup/leaf rules
399
+ branches.push({
400
+ pattern: pattern.displayName,
401
+ handler: handler.displayName,
402
+ indices: [idx - 1, idx],
403
+ });
404
+
405
+ consumed.add(idx - 1);
406
+ consumed.add(idx);
407
+ }
408
+ }
409
+
410
+ if (branches.length < FOLD_THRESHOLD) return { dispatches: [], remaining: blocks };
411
+
412
+ // Exemplar: first branch's blocks as children
413
+ const exemplarBlocks = branches[0].indices.map(i => blocks[i]);
414
+ const summaryBranches = branches.slice(1).map(b => ({
415
+ pattern: b.pattern,
416
+ handler: b.handler,
417
+ }));
418
+
419
+ const dispatch: NotationBlock = {
420
+ nodeId: `dispatch:${branches[0].pattern}`,
421
+ displayName: `${branches[0].pattern} \u2192 ${branches[0].handler}`,
422
+ nodeType: 'DISPATCH',
423
+ lines: [],
424
+ children: exemplarBlocks,
425
+ foldMeta: {
426
+ kind: 'dispatch',
427
+ count: branches.length,
428
+ branches: summaryBranches,
429
+ },
430
+ };
431
+
432
+ // Summary block after exemplar
433
+ const branchList = summaryBranches.slice(0, 3)
434
+ .map(b => `${b.pattern} \u2192 ${b.handler}`)
435
+ .join(', ');
436
+ const suffix = summaryBranches.length > 3
437
+ ? `, ...+${summaryBranches.length - 3} more`
438
+ : '';
439
+
440
+ const summary: NotationBlock = {
441
+ nodeId: `dispatch-summary:${branches[0].pattern}`,
442
+ displayName: `...+${branches.length - 1} more branches: ${branchList}${suffix}`,
443
+ nodeType: 'DISPATCH',
444
+ lines: [],
445
+ children: [],
446
+ foldMeta: {
447
+ kind: 'dispatch',
448
+ count: branches.length - 1,
449
+ branches: summaryBranches,
450
+ },
451
+ };
452
+
453
+ const remaining = blocks.filter((_, i) => !consumed.has(i));
454
+ return { dispatches: [dispatch, summary], remaining };
455
+ }
456
+
457
+ // === Rule 10: Repetitive call fold ===
458
+
459
+ function detectRepetitiveCalls(
460
+ blocks: NotationBlock[],
461
+ ): { folds: NotationBlock[]; remaining: NotationBlock[] } {
462
+ // Group non-leaf blocks by (name + signature) — same function called repeatedly
463
+ const groups = new Map<string, number[]>();
464
+
465
+ for (let i = 0; i < blocks.length; i++) {
466
+ const block = blocks[i];
467
+ if (block.foldMeta) continue;
468
+ if (block.lines.length === 0 && block.children.length === 0) continue; // Skip leaves
469
+
470
+ const sig = computeSignature(block);
471
+ const key = `call:${block.displayName}|${signatureKey(sig)}`;
472
+ if (!groups.has(key)) groups.set(key, []);
473
+ groups.get(key)!.push(i);
474
+ }
475
+
476
+ const consumed = new Set<number>();
477
+ const folds: NotationBlock[] = [];
478
+
479
+ for (const [, indices] of groups) {
480
+ if (indices.length <= FOLD_THRESHOLD) continue;
481
+
482
+ const exemplar = blocks[indices[0]];
483
+
484
+ // Exemplar shown expanded
485
+ folds.push(exemplar);
486
+
487
+ // Fold summary
488
+ folds.push({
489
+ nodeId: `callfold:${exemplar.displayName}`,
490
+ displayName: `...+${indices.length - 1} more ${exemplar.displayName}`,
491
+ nodeType: exemplar.nodeType,
492
+ lines: [],
493
+ children: [],
494
+ foldMeta: {
495
+ kind: 'fold',
496
+ count: indices.length,
497
+ label: exemplar.displayName,
498
+ },
499
+ });
500
+
501
+ for (const idx of indices) consumed.add(idx);
502
+ }
503
+
504
+ const remaining = blocks.filter((_, i) => !consumed.has(i));
505
+ return { folds, remaining };
506
+ }
507
+
508
+ // === Rule 7: Repeated leaf fold ===
509
+
510
+ function repeatedLeafFold(
511
+ blocks: NotationBlock[],
512
+ ): { folds: NotationBlock[]; remaining: NotationBlock[] } {
513
+ // Group leaf blocks (no lines, no children) by name
514
+ const leafGroups = new Map<string, number[]>();
515
+ const nonLeafIndices = new Set<number>();
516
+
517
+ for (let i = 0; i < blocks.length; i++) {
518
+ const block = blocks[i];
519
+ if (block.foldMeta) {
520
+ nonLeafIndices.add(i);
521
+ continue;
522
+ }
523
+ if (block.lines.length === 0 && block.children.length === 0) {
524
+ const name = block.displayName;
525
+ if (!leafGroups.has(name)) leafGroups.set(name, []);
526
+ leafGroups.get(name)!.push(i);
527
+ } else {
528
+ nonLeafIndices.add(i);
529
+ }
530
+ }
531
+
532
+ const foldedLeafIndices = new Set<number>();
533
+ const folds: NotationBlock[] = [];
534
+
535
+ for (const [name, indices] of leafGroups) {
536
+ if (indices.length <= FOLD_THRESHOLD) continue;
537
+
538
+ for (const idx of indices) foldedLeafIndices.add(idx);
539
+
540
+ folds.push({
541
+ nodeId: `leafrepeat:${name}`,
542
+ displayName: `${name} \u00d7${indices.length}`,
543
+ nodeType: blocks[indices[0]].nodeType,
544
+ lines: [],
545
+ children: [],
546
+ location: blocks[indices[0]].location,
547
+ foldMeta: {
548
+ kind: 'leaf-repeat',
549
+ count: indices.length,
550
+ label: name,
551
+ },
552
+ });
553
+ }
554
+
555
+ const remaining = blocks.filter((_, i) =>
556
+ nonLeafIndices.has(i) || (leafGroups.get(blocks[i].displayName)?.length ?? 0) <= FOLD_THRESHOLD,
557
+ );
558
+
559
+ return { folds, remaining };
560
+ }
561
+
562
+ // === Rule 5: Structural suppression ===
563
+
564
+ function structuralSuppression(
565
+ blocks: NotationBlock[],
566
+ ): { groups: NotationBlock[]; remaining: NotationBlock[] } {
567
+ const trivialByType = new Map<string, NotationBlock[]>();
568
+ const nonTrivialBlocks: NotationBlock[] = [];
569
+
570
+ for (const block of blocks) {
571
+ if (block.foldMeta) {
572
+ nonTrivialBlocks.push(block);
573
+ continue;
574
+ }
575
+
576
+ const isTrivial =
577
+ (block.lines.length === 0 && block.children.length === 0) ||
578
+ block.lines.every(l => l.operator === '');
579
+
580
+ if (isTrivial) {
581
+ if (!trivialByType.has(block.nodeType)) trivialByType.set(block.nodeType, []);
582
+ trivialByType.get(block.nodeType)!.push(block);
583
+ } else {
584
+ nonTrivialBlocks.push(block);
585
+ }
586
+ }
587
+
588
+ const groups: NotationBlock[] = [];
589
+
590
+ for (const [type, trivials] of trivialByType) {
591
+ if (trivials.length > FOLD_THRESHOLD) {
592
+ const nameList = trivials.map(b => b.displayName).join(', ');
593
+ groups.push({
594
+ nodeId: `trivial:${type}`,
595
+ displayName: `[${type.toLowerCase()}: ${nameList}]`,
596
+ nodeType: type,
597
+ lines: [],
598
+ children: [],
599
+ foldMeta: {
600
+ kind: 'trivial-group',
601
+ count: trivials.length,
602
+ names: trivials.map(b => b.displayName),
603
+ },
604
+ });
605
+ } else {
606
+ nonTrivialBlocks.push(...trivials);
607
+ }
608
+ }
609
+
610
+ return { groups, remaining: nonTrivialBlocks };
611
+ }
612
+
613
+ // === Rules 1-3: Group fold with anomaly preservation and source aggregation ===
614
+
615
+ function groupFold(
616
+ blocks: NotationBlock[],
617
+ ): { folded: NotationBlock[]; anomalies: NotationBlock[] } {
618
+ const unfoldedBlocks = blocks.filter(b => !b.foldMeta);
619
+ const alreadyFolded = blocks.filter(b => b.foldMeta);
620
+
621
+ if (unfoldedBlocks.length <= FOLD_THRESHOLD) {
622
+ return { folded: alreadyFolded, anomalies: unfoldedBlocks };
623
+ }
624
+
625
+ // Group by signature
626
+ const sigGroups = new Map<string, NotationBlock[]>();
627
+ for (const block of unfoldedBlocks) {
628
+ const sig = computeSignature(block);
629
+ const key = signatureKey(sig);
630
+ if (!sigGroups.has(key)) sigGroups.set(key, []);
631
+ sigGroups.get(key)!.push(block);
632
+ }
633
+
634
+ const folded: NotationBlock[] = [...alreadyFolded];
635
+ const anomalies: NotationBlock[] = [];
636
+
637
+ for (const [, group] of sigGroups) {
638
+ if (group.length > FOLD_THRESHOLD) {
639
+ const exemplar = group[0];
640
+
641
+ // Rule 3: Source aggregation — check if all share same source
642
+ const sourceSummary = findCommonSource(group);
643
+ const label = inferLabel(group);
644
+ const summaryParts = [`...+${group.length - 1} more ${label}`];
645
+ if (sourceSummary) summaryParts.push(`from ${sourceSummary}`);
646
+
647
+ // Exemplar shown expanded
648
+ folded.push(exemplar);
649
+
650
+ // Fold summary
651
+ folded.push({
652
+ nodeId: `fold:${exemplar.nodeId}`,
653
+ displayName: summaryParts.join(' '),
654
+ nodeType: exemplar.nodeType,
655
+ lines: [],
656
+ children: [],
657
+ foldMeta: {
658
+ kind: 'fold',
659
+ count: group.length,
660
+ label,
661
+ sourceSummary,
662
+ },
663
+ });
664
+ } else {
665
+ // Rule 2: Anomaly — too few to fold
666
+ anomalies.push(...group);
667
+ }
668
+ }
669
+
670
+ return { folded, anomalies };
671
+ }
672
+
673
+ // === Helpers ===
674
+
675
+ function findCommonSource(blocks: NotationBlock[]): string | undefined {
676
+ const sources = new Set<string>();
677
+
678
+ for (const block of blocks) {
679
+ for (const line of block.lines) {
680
+ if (line.targets.length > 0) {
681
+ for (const t of line.targets) sources.add(t);
682
+ }
683
+ }
684
+ }
685
+
686
+ if (sources.size === 1) return [...sources][0];
687
+ return undefined;
688
+ }
689
+
690
+ function inferLabel(group: NotationBlock[]): string {
691
+ if (group.length === 0) return '';
692
+
693
+ const type = group[0].nodeType.toLowerCase();
694
+ if (type.endsWith('s')) return type;
695
+ return type + 's';
696
+ }