@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,27 @@
1
+ /**
2
+ * Notation — Grafema DSL rendering engine
3
+ *
4
+ * Transforms graph data into compact visual notation.
5
+ * Output-only — Datalog remains the query language.
6
+ *
7
+ * @module notation
8
+ */
9
+
10
+ export type {
11
+ Archetype,
12
+ EdgeMapping,
13
+ DescribeOptions,
14
+ SubgraphData,
15
+ NotationBlock,
16
+ NotationLine,
17
+ FoldMeta,
18
+ } from './types.js';
19
+
20
+ export { EDGE_ARCHETYPE_MAP, lookupEdge, generateLegend } from './archetypes.js';
21
+ export { PERSPECTIVES } from './perspectives.js';
22
+ export { renderNotation } from './renderer.js';
23
+ export { extractSubgraph } from './lodExtractor.js';
24
+ export { shortenName } from './nameShortener.js';
25
+ export { foldBlocks } from './fold.js';
26
+ export { renderTraceNarrative } from './traceRenderer.js';
27
+ export type { TraceDetail, TraceNarrativeOptions } from './traceRenderer.js';
@@ -0,0 +1,177 @@
1
+ /**
2
+ * LOD (Level of Detail) Subgraph Extractor
3
+ *
4
+ * Extracts a SubgraphData from the graph backend using BFS from a root node.
5
+ * Different depth levels control how much of the graph neighborhood is fetched.
6
+ *
7
+ * - LOD 0: Root + direct containment children (names only in renderer)
8
+ * - LOD 1: Same + outgoing non-containment edges for each child, resolved targets
9
+ * - LOD 2: Same + expand children that have their own CONTAINS children
10
+ *
11
+ * @module notation/lodExtractor
12
+ */
13
+
14
+ import type { BaseNodeRecord, EdgeRecord } from '@grafema/types';
15
+ import type { SubgraphData } from './types.js';
16
+ import { lookupEdge } from './archetypes.js';
17
+
18
+ /**
19
+ * Minimal backend interface — matches the project pattern of loose coupling.
20
+ */
21
+ interface GraphBackend {
22
+ getNode(id: string): Promise<BaseNodeRecord | null>;
23
+ getOutgoingEdges(nodeId: string, edgeTypes?: string[] | null): Promise<EdgeRecord[]>;
24
+ getIncomingEdges(nodeId: string, edgeTypes?: string[] | null): Promise<EdgeRecord[]>;
25
+ }
26
+
27
+ /**
28
+ * Containment edge types that define the nesting tree.
29
+ */
30
+ const CONTAINMENT_TYPES = [
31
+ 'CONTAINS', 'HAS_SCOPE', 'HAS_MEMBER', 'HAS_BODY',
32
+ 'HAS_PROPERTY', 'HAS_ELEMENT', 'HAS_INIT', 'HAS_UPDATE',
33
+ 'HAS_CALLBACK', 'HAS_CATCH', 'HAS_FINALLY',
34
+ 'DECLARES', 'DEFINES', 'MOUNTS', 'PROPERTY_KEY', 'PROPERTY_VALUE',
35
+ ];
36
+
37
+ /**
38
+ * Extract a subgraph from the backend, rooted at the given node.
39
+ *
40
+ * @param backend Graph backend
41
+ * @param rootNodeId Semantic ID of the root node
42
+ * @param depth LOD level (0, 1, or 2)
43
+ */
44
+ export async function extractSubgraph(
45
+ backend: GraphBackend,
46
+ rootNodeId: string,
47
+ depth: number = 1,
48
+ ): Promise<SubgraphData> {
49
+ const nodeMap = new Map<string, BaseNodeRecord>();
50
+ const allEdges: EdgeRecord[] = [];
51
+
52
+ const root = await backend.getNode(rootNodeId);
53
+ if (!root) {
54
+ return { rootNodes: [], edges: [], nodeMap };
55
+ }
56
+
57
+ nodeMap.set(root.id, root);
58
+
59
+ // Step 1: Get direct containment children
60
+ const containmentEdges = await backend.getOutgoingEdges(rootNodeId, CONTAINMENT_TYPES);
61
+ const childIds: string[] = [];
62
+
63
+ for (const edge of containmentEdges) {
64
+ allEdges.push(edge);
65
+ childIds.push(edge.dst);
66
+ if (!nodeMap.has(edge.dst)) {
67
+ const child = await backend.getNode(edge.dst);
68
+ if (child) nodeMap.set(child.id, child);
69
+ }
70
+ }
71
+
72
+ // LOD 0: stop here (renderer shows names only)
73
+ if (depth <= 0) {
74
+ return { rootNodes: [root], edges: allEdges, nodeMap };
75
+ }
76
+
77
+ // Step 2: For each child (and root), get outgoing non-containment edges
78
+ const nodesToExpand = [rootNodeId, ...childIds];
79
+ for (const nodeId of nodesToExpand) {
80
+ const outgoing = await backend.getOutgoingEdges(nodeId);
81
+ for (const edge of outgoing) {
82
+ const mapping = lookupEdge(edge.type);
83
+ if (mapping.archetype === 'contains') continue; // already handled
84
+
85
+ allEdges.push(edge);
86
+ // Resolve target nodes
87
+ if (!nodeMap.has(edge.dst)) {
88
+ const target = await backend.getNode(edge.dst);
89
+ if (target) nodeMap.set(target.id, target);
90
+ }
91
+ }
92
+ }
93
+
94
+ // LOD 1: stop here
95
+ if (depth <= 1) {
96
+ return { rootNodes: [root], edges: allEdges, nodeMap };
97
+ }
98
+
99
+ // Step 3 (LOD 2): Expand children that have their own containment children
100
+ for (const childId of childIds) {
101
+ const childContainment = await backend.getOutgoingEdges(childId, CONTAINMENT_TYPES);
102
+ for (const edge of childContainment) {
103
+ allEdges.push(edge);
104
+ if (!nodeMap.has(edge.dst)) {
105
+ const grandchild = await backend.getNode(edge.dst);
106
+ if (grandchild) nodeMap.set(grandchild.id, grandchild);
107
+ }
108
+ }
109
+
110
+ // Also get operator edges for grandchildren
111
+ const grandchildIds = childContainment.map(e => e.dst);
112
+ for (const gcId of grandchildIds) {
113
+ const gcOutgoing = await backend.getOutgoingEdges(gcId);
114
+ for (const edge of gcOutgoing) {
115
+ const mapping = lookupEdge(edge.type);
116
+ if (mapping.archetype === 'contains') continue;
117
+ allEdges.push(edge);
118
+ if (!nodeMap.has(edge.dst)) {
119
+ const target = await backend.getNode(edge.dst);
120
+ if (target) nodeMap.set(target.id, target);
121
+ }
122
+ }
123
+ }
124
+ }
125
+
126
+ // Step 4: Fetch incoming PASSES_ARGUMENT edges for anonymous functions
127
+ // so the renderer can resolve λ names via callback context.
128
+ // CALL nodes are often not in the containment tree, so their outgoing
129
+ // PASSES_ARGUMENT edges don't get picked up by the BFS above.
130
+ await fetchCallbackContext(backend, allEdges, nodeMap);
131
+
132
+ return { rootNodes: [root], edges: allEdges, nodeMap };
133
+ }
134
+
135
+ /**
136
+ * For each anonymous arrow/expression in nodeMap, fetch incoming
137
+ * PASSES_ARGUMENT edges and resolve the CALL source node + sibling args.
138
+ */
139
+ async function fetchCallbackContext(
140
+ backend: GraphBackend,
141
+ allEdges: EdgeRecord[],
142
+ nodeMap: Map<string, BaseNodeRecord>,
143
+ ): Promise<void> {
144
+ const edgeSet = new Set(allEdges.map(e => `${e.src}|${e.dst}|${e.type}`));
145
+
146
+ for (const node of nodeMap.values()) {
147
+ if (node.name !== '<arrow>' && node.name !== '<expression>') continue;
148
+
149
+ const incoming = await backend.getIncomingEdges(node.id, ['PASSES_ARGUMENT']);
150
+ for (const edge of incoming) {
151
+ const key = `${edge.src}|${edge.dst}|${edge.type}`;
152
+ if (edgeSet.has(key)) continue;
153
+ edgeSet.add(key);
154
+ allEdges.push(edge);
155
+
156
+ // Resolve the CALL node (edge source)
157
+ if (!nodeMap.has(edge.src)) {
158
+ const callNode = await backend.getNode(edge.src);
159
+ if (callNode) nodeMap.set(callNode.id, callNode);
160
+ }
161
+
162
+ // Fetch all PASSES_ARGUMENT from same CALL to find sibling args
163
+ const callOutgoing = await backend.getOutgoingEdges(edge.src, ['PASSES_ARGUMENT']);
164
+ for (const sibEdge of callOutgoing) {
165
+ const sibKey = `${sibEdge.src}|${sibEdge.dst}|${sibEdge.type}`;
166
+ if (edgeSet.has(sibKey)) continue;
167
+ edgeSet.add(sibKey);
168
+ allEdges.push(sibEdge);
169
+
170
+ if (!nodeMap.has(sibEdge.dst)) {
171
+ const sibNode = await backend.getNode(sibEdge.dst);
172
+ if (sibNode) nodeMap.set(sibNode.id, sibNode);
173
+ }
174
+ }
175
+ }
176
+ }
177
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Name Shortener — shortest-unambiguous-name within scope
3
+ *
4
+ * Given a set of display names in a subgraph, shorten them to the
5
+ * minimum unique suffix. For example:
6
+ *
7
+ * auth/login.ts -> UserDB.findByEmail → UserDB.findByEmail
8
+ * auth/login.ts -> createToken → createToken
9
+ * auth/utils.ts -> createToken → utils:createToken
10
+ *
11
+ * Phase 4 feature — placeholder for future implementation.
12
+ *
13
+ * @module notation/nameShortener
14
+ */
15
+
16
+ /**
17
+ * Shorten a display name to its shortest unambiguous form
18
+ * within the given set of all names.
19
+ *
20
+ * Current implementation: return name as-is (no shortening).
21
+ */
22
+ export function shortenName(name: string, _allNames: string[]): string {
23
+ return name;
24
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Perspective Presets — archetype filters for describe tool
3
+ *
4
+ * Shared between MCP and CLI to keep perspectives in sync.
5
+ *
6
+ * @module notation/perspectives
7
+ */
8
+
9
+ import type { Archetype } from './types.js';
10
+
11
+ /** Perspective presets map to archetype filters */
12
+ export const PERSPECTIVES: Record<string, Archetype[]> = {
13
+ security: ['write', 'exception'],
14
+ data: ['flow_out', 'flow_in', 'write'],
15
+ errors: ['exception'],
16
+ api: ['flow_out', 'publishes', 'depends'],
17
+ events: ['publishes'],
18
+ };
@@ -0,0 +1,394 @@
1
+ /**
2
+ * Notation Renderer — pure function: SubgraphData → DSL string
3
+ *
4
+ * Pipeline:
5
+ * 1. Classify edges via EDGE_ARCHETYPE_MAP
6
+ * 2. Separate containment from operator edges
7
+ * 3. Group operator edges by source node
8
+ * 4. Sort lines by sortOrder
9
+ * 5. Merge same-operator same-verb targets
10
+ * 6. Serialize to indented DSL text
11
+ *
12
+ * @module notation/renderer
13
+ */
14
+
15
+ import type { EdgeRecord, BaseNodeRecord } from '@grafema/types';
16
+ import type { DescribeOptions, NotationBlock, NotationLine, SubgraphData } from './types.js';
17
+ import { lookupEdge } from './archetypes.js';
18
+ import { getNodeDisplayName } from '../queries/NodeContext.js';
19
+ import { foldBlocks } from './fold.js';
20
+
21
+ /**
22
+ * Render a subgraph as compact DSL notation.
23
+ *
24
+ * Example output:
25
+ * ```
26
+ * login {
27
+ * o- imports bcrypt
28
+ * > calls UserDB.findByEmail, createToken
29
+ * < reads config.auth
30
+ * => writes session
31
+ * >x throws AuthError
32
+ * ~>> emits 'auth:login'
33
+ * }
34
+ * ```
35
+ */
36
+ export function renderNotation(
37
+ input: SubgraphData,
38
+ options: DescribeOptions = {},
39
+ ): string {
40
+ const { depth = 1, archetypeFilter, budget = 7, includeLocations = false } = options;
41
+
42
+ const blocks = buildBlocks(input, depth, archetypeFilter, budget, includeLocations);
43
+ const finalBlocks = depth === 2 ? foldBlocks(blocks) : blocks;
44
+ return serializeBlocks(finalBlocks, 0);
45
+ }
46
+
47
+ // ---------------------------------------------------------------------------
48
+ // Block building
49
+ // ---------------------------------------------------------------------------
50
+
51
+ function buildBlocks(
52
+ input: SubgraphData,
53
+ depth: number,
54
+ archetypeFilter: DescribeOptions['archetypeFilter'],
55
+ budget: number,
56
+ includeLocations: boolean,
57
+ ): NotationBlock[] {
58
+ const { rootNodes, edges, nodeMap } = input;
59
+
60
+ // Index: src → edges, containment edges (src → child node IDs)
61
+ const outgoingBySource = new Map<string, EdgeRecord[]>();
62
+ const childrenOf = new Map<string, string[]>();
63
+
64
+ for (const edge of edges) {
65
+ const mapping = lookupEdge(edge.type);
66
+
67
+ if (mapping.archetype === 'contains') {
68
+ // Containment defines tree structure
69
+ if (!childrenOf.has(edge.src)) childrenOf.set(edge.src, []);
70
+ childrenOf.get(edge.src)!.push(edge.dst);
71
+ } else {
72
+ if (!outgoingBySource.has(edge.src)) outgoingBySource.set(edge.src, []);
73
+ outgoingBySource.get(edge.src)!.push(edge);
74
+ }
75
+ }
76
+
77
+ // Build set of node IDs that are descendants of LOOP nodes in containment tree
78
+ const insideLoopNodes = buildLoopDescendants(childrenOf, nodeMap);
79
+
80
+ const resolvedNames = resolveAnonymousNames(edges, nodeMap);
81
+
82
+ return rootNodes.map(node =>
83
+ buildBlock(node, outgoingBySource, childrenOf, nodeMap, depth, archetypeFilter, budget, includeLocations, insideLoopNodes, resolvedNames),
84
+ );
85
+ }
86
+
87
+ /**
88
+ * Collect all node IDs that are descendants of LOOP-type nodes in the containment tree.
89
+ * Uses BFS from each LOOP node, traversing containment children recursively.
90
+ */
91
+ function buildLoopDescendants(
92
+ childrenOf: Map<string, string[]>,
93
+ nodeMap: Map<string, BaseNodeRecord>,
94
+ ): Set<string> {
95
+ const result = new Set<string>();
96
+
97
+ // Find all LOOP nodes and collect their descendants
98
+ for (const [parentId, children] of childrenOf) {
99
+ const parentNode = nodeMap.get(parentId);
100
+ if (parentNode?.type === 'LOOP') {
101
+ // BFS to collect all descendants of this LOOP node
102
+ const queue = [...children];
103
+ while (queue.length > 0) {
104
+ const id = queue.shift()!;
105
+ if (result.has(id)) continue;
106
+ result.add(id);
107
+ const grandchildren = childrenOf.get(id);
108
+ if (grandchildren) {
109
+ queue.push(...grandchildren);
110
+ }
111
+ }
112
+ }
113
+ }
114
+
115
+ return result;
116
+ }
117
+
118
+ /**
119
+ * Resolve meaningful names for anonymous arrow functions and expressions.
120
+ *
121
+ * Priority:
122
+ * 1. Assignment: `const handler = () => {}` → "handler"
123
+ * 2. Callback: `setRequestHandler(Schema, λ)` → "λ → setRequestHandler(Schema)"
124
+ * 3. Fallback: "λ"
125
+ */
126
+ function resolveAnonymousNames(
127
+ edges: EdgeRecord[],
128
+ nodeMap: Map<string, BaseNodeRecord>,
129
+ ): Map<string, string> {
130
+ const resolved = new Map<string, string>();
131
+
132
+ // Reverse index: dstId → edges (for ASSIGNED_FROM and PASSES_ARGUMENT)
133
+ const incomingByDst = new Map<string, EdgeRecord[]>();
134
+ // Forward index: srcId → PASSES_ARGUMENT edges (for finding siblings)
135
+ const passArgBySrc = new Map<string, EdgeRecord[]>();
136
+
137
+ for (const edge of edges) {
138
+ if (edge.type === 'ASSIGNED_FROM' || edge.type === 'PASSES_ARGUMENT') {
139
+ if (!incomingByDst.has(edge.dst)) incomingByDst.set(edge.dst, []);
140
+ incomingByDst.get(edge.dst)!.push(edge);
141
+ }
142
+ if (edge.type === 'PASSES_ARGUMENT') {
143
+ if (!passArgBySrc.has(edge.src)) passArgBySrc.set(edge.src, []);
144
+ passArgBySrc.get(edge.src)!.push(edge);
145
+ }
146
+ }
147
+
148
+ for (const [nodeId, node] of nodeMap) {
149
+ if (node.name !== '<arrow>' && node.name !== '<expression>') continue;
150
+
151
+ const incoming = incomingByDst.get(nodeId) ?? [];
152
+
153
+ // Priority 1: Assignment — const handler = () => {}
154
+ const assignEdge = incoming.find(e => e.type === 'ASSIGNED_FROM');
155
+ if (assignEdge) {
156
+ const srcNode = nodeMap.get(assignEdge.src);
157
+ if (srcNode?.name && srcNode.name !== '<arrow>' && srcNode.name !== '<expression>') {
158
+ resolved.set(nodeId, srcNode.name);
159
+ continue;
160
+ }
161
+ }
162
+
163
+ // Priority 2: Callback — setRequestHandler(Schema, λ)
164
+ const passEdge = incoming.find(e => e.type === 'PASSES_ARGUMENT');
165
+ if (passEdge) {
166
+ const callNode = nodeMap.get(passEdge.src);
167
+ const callName = callNode ? getNodeDisplayName(callNode) : null;
168
+ if (callName && callName !== '<arrow>' && callName !== '<expression>' && callName !== 'λ') {
169
+ // Named sibling args (skip other anonymous ones)
170
+ const allSiblings = passArgBySrc.get(passEdge.src) ?? [];
171
+ const namedSiblings = allSiblings
172
+ .filter(e => e.dst !== nodeId)
173
+ .map(e => {
174
+ const argNode = nodeMap.get(e.dst);
175
+ if (!argNode?.name) return null;
176
+ if (argNode.name === '<arrow>' || argNode.name === '<expression>') return null;
177
+ return argNode.name;
178
+ })
179
+ .filter((n): n is string => n !== null);
180
+
181
+ // Multiple lambdas in same call → add index to distinguish
182
+ const lambdaCount = allSiblings.filter(e => {
183
+ const n = nodeMap.get(e.dst);
184
+ return n && (n.name === '<arrow>' || n.name === '<expression>');
185
+ }).length;
186
+ const indexSuffix = lambdaCount > 1
187
+ ? String(passEdge.metadata?.index ?? passEdge.metadata?.argIndex ?? '')
188
+ : '';
189
+
190
+ const argStr = namedSiblings.length > 0 ? `(${namedSiblings.join(', ')})` : '';
191
+ resolved.set(nodeId, `λ${indexSuffix} → ${callName}${argStr}`);
192
+ continue;
193
+ }
194
+ }
195
+
196
+ // Priority 3: Fallback
197
+ resolved.set(nodeId, 'λ');
198
+ }
199
+
200
+ return resolved;
201
+ }
202
+
203
+ function buildBlock(
204
+ node: BaseNodeRecord,
205
+ outgoingBySource: Map<string, EdgeRecord[]>,
206
+ childrenOf: Map<string, string[]>,
207
+ nodeMap: Map<string, BaseNodeRecord>,
208
+ depth: number,
209
+ archetypeFilter: DescribeOptions['archetypeFilter'],
210
+ budget: number,
211
+ includeLocations: boolean,
212
+ insideLoopNodes: Set<string>,
213
+ resolvedNames: Map<string, string>,
214
+ ): NotationBlock {
215
+ const displayName = resolvedNames.get(node.id) ?? getNodeDisplayName(node);
216
+ const location = includeLocations && node.file
217
+ ? `${node.file}${node.line ? ':' + node.line : ''}`
218
+ : undefined;
219
+
220
+ // LOD 0: names only — no edges, no children
221
+ if (depth <= 0) {
222
+ return { nodeId: node.id, displayName, nodeType: node.type, lines: [], children: [], location };
223
+ }
224
+
225
+ // LOD 1+: build edge lines
226
+ const nodeEdges = outgoingBySource.get(node.id) ?? [];
227
+ const lines = buildLines(nodeEdges, nodeMap, archetypeFilter, budget, resolvedNames);
228
+
229
+ // Apply [] loop modifier if this node is inside a LOOP
230
+ // Combines with existing modifiers: [] ?? > calls foo
231
+ if (insideLoopNodes.has(node.id)) {
232
+ for (const line of lines) {
233
+ line.modifier = line.modifier ? `[] ${line.modifier}` : '[]';
234
+ }
235
+ }
236
+
237
+ // LOD 2+: recurse into contained children
238
+ let children: NotationBlock[] = [];
239
+ if (depth >= 2) {
240
+ const childIds = childrenOf.get(node.id) ?? [];
241
+ children = childIds
242
+ .map(id => nodeMap.get(id))
243
+ .filter((n): n is BaseNodeRecord => n != null)
244
+ .map(child =>
245
+ buildBlock(child, outgoingBySource, childrenOf, nodeMap, depth - 1, archetypeFilter, budget, includeLocations, insideLoopNodes, resolvedNames),
246
+ );
247
+ }
248
+
249
+ return { nodeId: node.id, displayName, nodeType: node.type, lines, children, location };
250
+ }
251
+
252
+ // ---------------------------------------------------------------------------
253
+ // Line building — group, sort, merge
254
+ // ---------------------------------------------------------------------------
255
+
256
+ function buildLines(
257
+ edges: EdgeRecord[],
258
+ nodeMap: Map<string, BaseNodeRecord>,
259
+ archetypeFilter: DescribeOptions['archetypeFilter'],
260
+ budget: number,
261
+ resolvedNames: Map<string, string>,
262
+ ): NotationLine[] {
263
+ // Group by modifier+operator+verb key (modifier separates certain from uncertain)
264
+ const groups = new Map<string, { operator: string; verb: string; targets: string[]; sortOrder: number; modifier?: string }>();
265
+
266
+ for (const edge of edges) {
267
+ const mapping = lookupEdge(edge.type);
268
+
269
+ // Apply archetype filter
270
+ if (archetypeFilter && !archetypeFilter.includes(mapping.archetype)) continue;
271
+
272
+ const targetNode = nodeMap.get(edge.dst);
273
+ const targetName = resolvedNames.get(edge.dst) ?? (targetNode ? getNodeDisplayName(targetNode) : edge.dst);
274
+
275
+ // Detect dynamic/uncertain edges
276
+ const modifier = isDynamicEdge(edge) ? '??' : undefined;
277
+
278
+ const key = `${modifier ?? ''}|${mapping.operator}|${mapping.verb}`;
279
+ if (!groups.has(key)) {
280
+ groups.set(key, {
281
+ operator: mapping.operator,
282
+ verb: mapping.verb,
283
+ targets: [],
284
+ sortOrder: mapping.sortOrder,
285
+ modifier,
286
+ });
287
+ }
288
+ groups.get(key)!.targets.push(targetName);
289
+ }
290
+
291
+ // Sort by sortOrder
292
+ const sorted = Array.from(groups.values()).sort((a, b) => a.sortOrder - b.sortOrder);
293
+
294
+ // Apply budget: show top-N, summarize rest
295
+ if (sorted.length > budget) {
296
+ const shown = sorted.slice(0, budget);
297
+ const hidden = sorted.slice(budget);
298
+ const hiddenCount = hidden.reduce((sum, g) => sum + g.targets.length, 0);
299
+ shown.push({
300
+ operator: '',
301
+ verb: `...+${hiddenCount} more`,
302
+ targets: [],
303
+ sortOrder: 999,
304
+ });
305
+ return shown.map(g => ({
306
+ operator: g.operator,
307
+ verb: g.verb,
308
+ targets: g.targets,
309
+ sortOrder: g.sortOrder,
310
+ modifier: g.modifier,
311
+ }));
312
+ }
313
+
314
+ return sorted.map(g => ({
315
+ operator: g.operator,
316
+ verb: g.verb,
317
+ targets: g.targets,
318
+ sortOrder: g.sortOrder,
319
+ modifier: g.modifier,
320
+ }));
321
+ }
322
+
323
+ // ---------------------------------------------------------------------------
324
+ // Edge metadata helpers
325
+ // ---------------------------------------------------------------------------
326
+
327
+ /**
328
+ * Check if an edge is dynamic/uncertain based on its metadata.
329
+ *
330
+ * An edge is dynamic/uncertain if any of these hold:
331
+ * - metadata.resolved === false (unresolved reference)
332
+ * - metadata.confidence exists and is < 1.0 (low-confidence inference)
333
+ * - metadata.dynamic === true (dynamic dispatch)
334
+ */
335
+ function isDynamicEdge(edge: EdgeRecord): boolean {
336
+ const meta = edge.metadata;
337
+ if (!meta) return false;
338
+
339
+ if (meta.resolved === false) return true;
340
+ if (typeof meta.confidence === 'number' && meta.confidence < 1.0) return true;
341
+ if (meta.dynamic === true) return true;
342
+
343
+ return false;
344
+ }
345
+
346
+ // ---------------------------------------------------------------------------
347
+ // Serialization
348
+ // ---------------------------------------------------------------------------
349
+
350
+ function serializeBlocks(blocks: NotationBlock[], indentLevel: number): string {
351
+ return blocks.map(block => serializeBlock(block, indentLevel)).join('\n');
352
+ }
353
+
354
+ function serializeBlock(block: NotationBlock, indentLevel: number): string {
355
+ const indent = ' '.repeat(indentLevel);
356
+ const hasContent = block.lines.length > 0 || block.children.length > 0;
357
+
358
+ // Header
359
+ const loc = block.location ? ` (${block.location})` : '';
360
+ let result: string;
361
+
362
+ if (!hasContent) {
363
+ // Leaf node — just name
364
+ result = `${indent}${block.displayName}${loc}`;
365
+ } else {
366
+ result = `${indent}${block.displayName}${loc} {\n`;
367
+
368
+ // Lines
369
+ for (const line of block.lines) {
370
+ result += serializeLine(line, indentLevel + 1);
371
+ }
372
+
373
+ // Children
374
+ if (block.children.length > 0) {
375
+ result += serializeBlocks(block.children, indentLevel + 1) + '\n';
376
+ }
377
+
378
+ result += `${indent}}`;
379
+ }
380
+
381
+ return result;
382
+ }
383
+
384
+ function serializeLine(line: NotationLine, indentLevel: number): string {
385
+ const indent = ' '.repeat(indentLevel);
386
+ const mod = line.modifier ? `${line.modifier} ` : '';
387
+ const prefix = line.operator ? `${line.operator} ` : '';
388
+
389
+ if (line.targets.length === 0) {
390
+ return `${indent}${mod}${prefix}${line.verb}\n`;
391
+ }
392
+
393
+ return `${indent}${mod}${prefix}${line.verb} ${line.targets.join(', ')}\n`;
394
+ }