@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,269 @@
1
+ /**
2
+ * Git Query Functions for KnowledgeBase
3
+ *
4
+ * Provides analytical queries over git data (COMMIT, AUTHOR nodes)
5
+ * stored in the KnowledgeBase. Operates on KBNode arrays loaded from
6
+ * YAML files into the KB index.
7
+ *
8
+ * COMMIT nodes carry raw YAML fields: hash, message, author_ref, date, files.
9
+ * AUTHOR nodes carry: name, emails, aliases.
10
+ * These are accessed via type assertion since they're extra properties
11
+ * on KBNode objects loaded from YAML frontmatter.
12
+ */
13
+
14
+ import type { KBNode } from './types.js';
15
+ import type { KnowledgeBase } from './KnowledgeBase.js';
16
+
17
+ // -- Public result types --
18
+
19
+ export interface ChurnEntry {
20
+ path: string;
21
+ changeCount: number;
22
+ totalAdded: number;
23
+ totalRemoved: number;
24
+ }
25
+
26
+ export interface CoChangeEntry {
27
+ path: string;
28
+ coChangeCount: number;
29
+ support: number;
30
+ }
31
+
32
+ export interface OwnershipEntry {
33
+ authorId: string;
34
+ authorName: string;
35
+ commits: number;
36
+ linesAdded: number;
37
+ linesRemoved: number;
38
+ }
39
+
40
+ export interface ArchaeologyEntry {
41
+ path: string;
42
+ lastCommitHash: string;
43
+ lastCommitDate: string;
44
+ lastAuthor: string;
45
+ firstCommitDate: string;
46
+ firstAuthor: string;
47
+ }
48
+
49
+ // -- Internal raw-data accessors --
50
+
51
+ interface RawCommitFile {
52
+ path: string;
53
+ added: number;
54
+ removed: number;
55
+ }
56
+
57
+ function getCommitHash(node: KBNode): string {
58
+ return (node as any).hash ?? '';
59
+ }
60
+
61
+ function getCommitDate(node: KBNode): string {
62
+ return (node as any).date ?? '';
63
+ }
64
+
65
+ function getCommitFiles(node: KBNode): RawCommitFile[] {
66
+ return (node as any).files ?? [];
67
+ }
68
+
69
+ function getCommitAuthorRef(node: KBNode): string {
70
+ return (node as any).author_ref ?? '';
71
+ }
72
+
73
+ function getAuthorName(node: KBNode): string {
74
+ return (node as any).name ?? '';
75
+ }
76
+
77
+ // -- Query functions --
78
+
79
+ /**
80
+ * Compute file churn: how many commits touch each file and total lines added/removed.
81
+ *
82
+ * @param kb - KnowledgeBase instance (must be loaded)
83
+ * @param opts.limit - Return only the top N entries
84
+ * @param opts.since - ISO date string; only consider commits on or after this date
85
+ * @returns Churn entries sorted by changeCount descending
86
+ */
87
+ export async function getChurn(
88
+ kb: KnowledgeBase,
89
+ opts?: { limit?: number; since?: string },
90
+ ): Promise<ChurnEntry[]> {
91
+ const commits = await kb.queryNodes({ type: 'COMMIT' });
92
+
93
+ const filtered = opts?.since
94
+ ? commits.filter(c => getCommitDate(c) >= opts.since!)
95
+ : commits;
96
+
97
+ const map = new Map<string, ChurnEntry>();
98
+
99
+ for (const commit of filtered) {
100
+ for (const file of getCommitFiles(commit)) {
101
+ let entry = map.get(file.path);
102
+ if (!entry) {
103
+ entry = { path: file.path, changeCount: 0, totalAdded: 0, totalRemoved: 0 };
104
+ map.set(file.path, entry);
105
+ }
106
+ entry.changeCount += 1;
107
+ entry.totalAdded += file.added;
108
+ entry.totalRemoved += file.removed;
109
+ }
110
+ }
111
+
112
+ const sorted = Array.from(map.values()).sort((a, b) => b.changeCount - a.changeCount);
113
+
114
+ return opts?.limit ? sorted.slice(0, opts.limit) : sorted;
115
+ }
116
+
117
+ /**
118
+ * Find files that frequently change together with the given file.
119
+ *
120
+ * For every commit that touches `filePath`, counts how many of those commits
121
+ * also touch each other file. `support` is the ratio of co-change count to
122
+ * the total number of commits touching `filePath`.
123
+ *
124
+ * @param kb - KnowledgeBase instance (must be loaded)
125
+ * @param filePath - The file to find co-changes for
126
+ * @param opts.minSupport - Minimum support threshold (default 0.1 = 10%)
127
+ * @returns Co-change entries sorted by coChangeCount descending
128
+ */
129
+ export async function getCoChanges(
130
+ kb: KnowledgeBase,
131
+ filePath: string,
132
+ opts?: { minSupport?: number },
133
+ ): Promise<CoChangeEntry[]> {
134
+ const commits = await kb.queryNodes({ type: 'COMMIT' });
135
+ const minSupport = opts?.minSupport ?? 0.1;
136
+
137
+ // Find commits that touch the target file
138
+ const touchingCommits = commits.filter(c =>
139
+ getCommitFiles(c).some(f => f.path === filePath),
140
+ );
141
+
142
+ const totalTouching = touchingCommits.length;
143
+ if (totalTouching === 0) return [];
144
+
145
+ // Count co-occurrences of other files
146
+ const coMap = new Map<string, number>();
147
+
148
+ for (const commit of touchingCommits) {
149
+ for (const file of getCommitFiles(commit)) {
150
+ if (file.path === filePath) continue;
151
+ coMap.set(file.path, (coMap.get(file.path) ?? 0) + 1);
152
+ }
153
+ }
154
+
155
+ const entries: CoChangeEntry[] = [];
156
+
157
+ for (const [path, count] of coMap) {
158
+ const support = count / totalTouching;
159
+ if (support >= minSupport) {
160
+ entries.push({ path, coChangeCount: count, support });
161
+ }
162
+ }
163
+
164
+ return entries.sort((a, b) => b.coChangeCount - a.coChangeCount);
165
+ }
166
+
167
+ /**
168
+ * Compute ownership of a file: who committed changes and how much.
169
+ *
170
+ * Groups commits touching `filePath` by author_ref, sums commit count and
171
+ * lines added/removed. Looks up author names from AUTHOR nodes in the KB.
172
+ *
173
+ * @param kb - KnowledgeBase instance (must be loaded)
174
+ * @param filePath - The file to compute ownership for
175
+ * @returns Ownership entries sorted by commits descending
176
+ */
177
+ export async function getOwnership(
178
+ kb: KnowledgeBase,
179
+ filePath: string,
180
+ ): Promise<OwnershipEntry[]> {
181
+ const commits = await kb.queryNodes({ type: 'COMMIT' });
182
+ const authors = await kb.queryNodes({ type: 'AUTHOR' });
183
+
184
+ // Build author name lookup: id -> name
185
+ const authorNameById = new Map<string, string>();
186
+ for (const author of authors) {
187
+ authorNameById.set(author.id, getAuthorName(author));
188
+ }
189
+
190
+ // Accumulate per-author stats
191
+ const map = new Map<string, { commits: number; linesAdded: number; linesRemoved: number }>();
192
+
193
+ for (const commit of commits) {
194
+ const files = getCommitFiles(commit);
195
+ const fileEntry = files.find(f => f.path === filePath);
196
+ if (!fileEntry) continue;
197
+
198
+ const authorRef = getCommitAuthorRef(commit);
199
+ let stats = map.get(authorRef);
200
+ if (!stats) {
201
+ stats = { commits: 0, linesAdded: 0, linesRemoved: 0 };
202
+ map.set(authorRef, stats);
203
+ }
204
+ stats.commits += 1;
205
+ stats.linesAdded += fileEntry.added;
206
+ stats.linesRemoved += fileEntry.removed;
207
+ }
208
+
209
+ const entries: OwnershipEntry[] = [];
210
+
211
+ for (const [authorId, stats] of map) {
212
+ entries.push({
213
+ authorId,
214
+ authorName: authorNameById.get(authorId) ?? authorId,
215
+ commits: stats.commits,
216
+ linesAdded: stats.linesAdded,
217
+ linesRemoved: stats.linesRemoved,
218
+ });
219
+ }
220
+
221
+ return entries.sort((a, b) => b.commits - a.commits);
222
+ }
223
+
224
+ /**
225
+ * Dig into the history of a file: when it was first created, last modified, and by whom.
226
+ *
227
+ * @param kb - KnowledgeBase instance (must be loaded)
228
+ * @param filePath - The file to investigate
229
+ * @returns Archaeology entry with first/last commit info, or empty strings if no commits found
230
+ */
231
+ export async function getArchaeology(
232
+ kb: KnowledgeBase,
233
+ filePath: string,
234
+ ): Promise<ArchaeologyEntry> {
235
+ const commits = await kb.queryNodes({ type: 'COMMIT' });
236
+
237
+ // Filter to commits touching this file
238
+ const touching = commits.filter(c =>
239
+ getCommitFiles(c).some(f => f.path === filePath),
240
+ );
241
+
242
+ if (touching.length === 0) {
243
+ return {
244
+ path: filePath,
245
+ lastCommitHash: '',
246
+ lastCommitDate: '',
247
+ lastAuthor: '',
248
+ firstCommitDate: '',
249
+ firstAuthor: '',
250
+ };
251
+ }
252
+
253
+ // Sort by date ascending
254
+ const sorted = touching.sort((a, b) =>
255
+ getCommitDate(a).localeCompare(getCommitDate(b)),
256
+ );
257
+
258
+ const first = sorted[0];
259
+ const last = sorted[sorted.length - 1];
260
+
261
+ return {
262
+ path: filePath,
263
+ lastCommitHash: getCommitHash(last),
264
+ lastCommitDate: getCommitDate(last),
265
+ lastAuthor: getCommitAuthorRef(last),
266
+ firstCommitDate: getCommitDate(first),
267
+ firstAuthor: getCommitAuthorRef(first),
268
+ };
269
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Knowledge Base — persistent knowledge layer for Grafema
3
+ */
4
+
5
+ export { KnowledgeBase } from './KnowledgeBase.js';
6
+ export { SemanticAddressResolver, parseSemanticAddress } from './SemanticAddressResolver.js';
7
+ export type { ResolverBackend } from './SemanticAddressResolver.js';
8
+ export { parseFrontmatter, parseKBNode, serializeKBNode, parseEdgesFile, appendEdge } from './parser.js';
9
+ export type {
10
+ KBNodeType,
11
+ KBLifecycle,
12
+ KBScope,
13
+ KBNodeBase,
14
+ KBDecision,
15
+ KBFact,
16
+ KBSession,
17
+ KBNode,
18
+ KBEdge,
19
+ KBStats,
20
+ KBQueryFilter,
21
+ ParsedSemanticAddress,
22
+ ResolvedAddress,
23
+ DanglingCodeRef,
24
+ } from './types.js';
25
+ export { parseYamlArrayFile } from './parser.js';
26
+ export { GitIngest, parseGitLog, normalizeAuthors } from './git-ingest.js';
27
+ export type { RawCommit, FileChange, AuthorEntry, CommitEntry, IngestResult, Meta } from './git-ingest.js';
28
+ export { getChurn, getCoChanges, getOwnership, getArchaeology } from './git-queries.js';
29
+ export type { ChurnEntry, CoChangeEntry, OwnershipEntry, ArchaeologyEntry } from './git-queries.js';
@@ -0,0 +1,294 @@
1
+ /**
2
+ * Knowledge Base Parser
3
+ *
4
+ * Parses markdown files with YAML frontmatter into KBNode objects.
5
+ * Serializes KBNode objects back to markdown format.
6
+ */
7
+
8
+ import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
9
+ import { readFileSync, appendFileSync, mkdirSync, existsSync } from 'fs';
10
+ import { dirname } from 'path';
11
+ import type { KBNode, KBNodeType, KBEdge, KBLifecycle, KBDecision, KBFact, KBSession, KBScope } from './types.js';
12
+
13
+ const VALID_TYPES: KBNodeType[] = ['DECISION', 'FACT', 'SESSION', 'COMMIT', 'FILE_CHANGE', 'AUTHOR', 'TICKET', 'INCIDENT'];
14
+ const ID_PATTERN = /^kb:[a-z_]+:[a-z0-9_-]+$/;
15
+
16
+ /**
17
+ * Split markdown content into YAML frontmatter and body.
18
+ */
19
+ export function parseFrontmatter(content: string): { frontmatter: Record<string, unknown>; body: string } {
20
+ const trimmed = content.trimStart();
21
+ if (!trimmed.startsWith('---\n') && !trimmed.startsWith('---\r\n')) {
22
+ throw new Error('Missing frontmatter: file must start with ---');
23
+ }
24
+
25
+ // Find closing --- delimiter
26
+ const firstNewline = trimmed.indexOf('\n');
27
+ const rest = trimmed.slice(firstNewline + 1);
28
+ const closingIndex = rest.indexOf('\n---');
29
+
30
+ if (closingIndex === -1) {
31
+ throw new Error('Missing frontmatter: no closing --- delimiter found');
32
+ }
33
+
34
+ const yamlContent = rest.slice(0, closingIndex);
35
+ const body = rest.slice(closingIndex + 4).replace(/^\r?\n/, ''); // skip \n--- and leading newline
36
+
37
+ const frontmatter = parseYaml(yamlContent) as Record<string, unknown>;
38
+ if (!frontmatter || typeof frontmatter !== 'object') {
39
+ throw new Error('Invalid frontmatter: YAML must be an object');
40
+ }
41
+
42
+ return { frontmatter, body };
43
+ }
44
+
45
+ /**
46
+ * Derive lifecycle from file path.
47
+ * declared/ → declared, derived/ → derived, synced/ → synced
48
+ */
49
+ function deriveLifecycle(filePath: string): KBLifecycle {
50
+ if (filePath.includes('/derived/') || filePath.includes('\\derived\\')) return 'derived';
51
+ if (filePath.includes('/synced/') || filePath.includes('\\synced\\')) return 'synced';
52
+ return 'declared'; // default
53
+ }
54
+
55
+ /**
56
+ * Validate and construct a typed KBNode from parsed frontmatter and body.
57
+ */
58
+ export function parseKBNode(frontmatter: Record<string, unknown>, body: string, filePath: string): KBNode {
59
+ // Required: id
60
+ const id = frontmatter.id;
61
+ if (typeof id !== 'string' || !id) {
62
+ throw new Error(`Missing required field "id" in ${filePath}`);
63
+ }
64
+ if (!ID_PATTERN.test(id)) {
65
+ throw new Error(`Invalid ID format "${id}" in ${filePath}. Must match kb:<type>:<slug> (lowercase, hyphens, underscores, digits)`);
66
+ }
67
+
68
+ // Required: type
69
+ const type = frontmatter.type;
70
+ if (typeof type !== 'string' || !VALID_TYPES.includes(type as KBNodeType)) {
71
+ throw new Error(`Invalid or missing type "${type}" in ${filePath}. Must be one of: ${VALID_TYPES.join(', ')}`);
72
+ }
73
+
74
+ const lifecycle = deriveLifecycle(filePath);
75
+
76
+ // Base node
77
+ const base: KBNode = {
78
+ id: id as string,
79
+ type: type as KBNodeType,
80
+ projections: Array.isArray(frontmatter.projections) ? frontmatter.projections as string[] : [],
81
+ created: String(frontmatter.created ?? ''),
82
+ content: body.trim(),
83
+ filePath,
84
+ lifecycle,
85
+ };
86
+
87
+ // Optional base fields
88
+ if (frontmatter.subtype) base.subtype = String(frontmatter.subtype);
89
+ if (frontmatter.scope) base.scope = frontmatter.scope as KBScope;
90
+ if (frontmatter.source) base.source = String(frontmatter.source);
91
+ if (Array.isArray(frontmatter.relates_to)) base.relates_to = frontmatter.relates_to as string[];
92
+
93
+ // Type-specific fields
94
+ switch (type) {
95
+ case 'DECISION': {
96
+ const decision = base as KBDecision;
97
+ decision.status = (frontmatter.status as KBDecision['status']) ?? 'proposed';
98
+ if (frontmatter.effective_from) decision.effective_from = String(frontmatter.effective_from);
99
+ if (frontmatter.effective_until) decision.effective_until = String(frontmatter.effective_until);
100
+ if (Array.isArray(frontmatter.applies_to)) decision.applies_to = frontmatter.applies_to as string[];
101
+ if (frontmatter.superseded_by) decision.superseded_by = String(frontmatter.superseded_by);
102
+ return decision;
103
+ }
104
+ case 'FACT': {
105
+ const fact = base as KBFact;
106
+ if (frontmatter.confidence) fact.confidence = frontmatter.confidence as KBFact['confidence'];
107
+ if (frontmatter.superseded_by) fact.superseded_by = String(frontmatter.superseded_by);
108
+ return fact;
109
+ }
110
+ case 'SESSION': {
111
+ const session = base as KBSession;
112
+ if (frontmatter.task_id) session.task_id = String(frontmatter.task_id);
113
+ if (frontmatter.session_path) session.session_path = String(frontmatter.session_path);
114
+ if (Array.isArray(frontmatter.produced)) session.produced = frontmatter.produced as string[];
115
+ return session;
116
+ }
117
+ default:
118
+ return base;
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Serialize a KBNode back to markdown with YAML frontmatter.
124
+ */
125
+ export function serializeKBNode(node: KBNode): string {
126
+ // Build frontmatter object (exclude computed/internal fields)
127
+ const fm: Record<string, unknown> = {
128
+ id: node.id,
129
+ type: node.type,
130
+ };
131
+
132
+ // Decision-specific
133
+ if (node.type === 'DECISION') {
134
+ const d = node as KBDecision;
135
+ fm.status = d.status;
136
+ if (d.applies_to?.length) fm.applies_to = d.applies_to;
137
+ if (d.effective_from) fm.effective_from = d.effective_from;
138
+ if (d.effective_until) fm.effective_until = d.effective_until;
139
+ if (d.superseded_by) fm.superseded_by = d.superseded_by;
140
+ }
141
+
142
+ // Fact-specific
143
+ if (node.type === 'FACT') {
144
+ const f = node as KBFact;
145
+ if (f.confidence) fm.confidence = f.confidence;
146
+ if (f.superseded_by) fm.superseded_by = f.superseded_by;
147
+ }
148
+
149
+ // Session-specific
150
+ if (node.type === 'SESSION') {
151
+ const s = node as KBSession;
152
+ if (s.task_id) fm.task_id = s.task_id;
153
+ if (s.session_path) fm.session_path = s.session_path;
154
+ if (s.produced?.length) fm.produced = s.produced;
155
+ }
156
+
157
+ // Common optional fields
158
+ if (node.subtype) fm.subtype = node.subtype;
159
+ if (node.scope) fm.scope = node.scope;
160
+ if (node.projections.length > 0) fm.projections = node.projections;
161
+ if (node.source) fm.source = node.source;
162
+ if (node.relates_to?.length) fm.relates_to = node.relates_to;
163
+ fm.created = node.created;
164
+
165
+ const yamlStr = stringifyYaml(fm, { lineWidth: 0 }).trimEnd();
166
+ const body = node.content ? `\n${node.content}\n` : '\n';
167
+
168
+ return `---\n${yamlStr}\n---\n${body}`;
169
+ }
170
+
171
+ /**
172
+ * Parse edges.yaml file into KBEdge array.
173
+ */
174
+ export function parseEdgesFile(filePath: string): KBEdge[] {
175
+ if (!existsSync(filePath)) return [];
176
+
177
+ const content = readFileSync(filePath, 'utf-8');
178
+ const parsed = parseYaml(content);
179
+
180
+ if (!parsed) return [];
181
+ if (!Array.isArray(parsed)) {
182
+ throw new Error(`edges.yaml must be a YAML array, got ${typeof parsed}`);
183
+ }
184
+
185
+ return parsed.map((entry: unknown, i: number) => {
186
+ if (!entry || typeof entry !== 'object') {
187
+ throw new Error(`Edge at index ${i} is not an object`);
188
+ }
189
+ const e = entry as Record<string, unknown>;
190
+ if (!e.type || !e.from || !e.to) {
191
+ throw new Error(`Edge at index ${i} missing required fields (type, from, to)`);
192
+ }
193
+ const edge: KBEdge = {
194
+ type: String(e.type),
195
+ from: String(e.from),
196
+ to: String(e.to),
197
+ };
198
+ if (e.evidence) edge.evidence = String(e.evidence);
199
+ return edge;
200
+ });
201
+ }
202
+
203
+ /**
204
+ * Parse a YAML file containing an array of node objects (e.g., commits/2026-03.yaml, authors.yaml).
205
+ * Each entry must have at least a `type` field. ID is generated based on type:
206
+ * - COMMIT → kb:commit:<short-hash>
207
+ * - AUTHOR → kb:author:<slug>
208
+ * - Others → kb:<type>:<index>
209
+ */
210
+ export function parseYamlArrayFile(filePath: string): KBNode[] {
211
+ const content = readFileSync(filePath, 'utf-8');
212
+ const parsed = parseYaml(content);
213
+
214
+ if (!Array.isArray(parsed)) {
215
+ throw new Error(`YAML array file must contain an array, got ${typeof parsed} in ${filePath}`);
216
+ }
217
+
218
+ return parsed.map((entry: unknown, index: number) => {
219
+ if (!entry || typeof entry !== 'object') {
220
+ throw new Error(`Entry at index ${index} is not an object in ${filePath}`);
221
+ }
222
+ const e = entry as Record<string, unknown>;
223
+
224
+ const type = e.type;
225
+ if (typeof type !== 'string' || !VALID_TYPES.includes(type as KBNodeType)) {
226
+ throw new Error(`Invalid or missing type "${type}" at index ${index} in ${filePath}. Must be one of: ${VALID_TYPES.join(', ')}`);
227
+ }
228
+
229
+ // Generate ID based on type
230
+ let id: string;
231
+ switch (type) {
232
+ case 'COMMIT':
233
+ id = `kb:commit:${String(e.hash ?? '').slice(0, 8)}`;
234
+ break;
235
+ case 'AUTHOR': {
236
+ if (typeof e.id === 'string' && e.id.startsWith('kb:author:')) {
237
+ id = e.id;
238
+ } else if (typeof e.id === 'string') {
239
+ id = `kb:author:${e.id}`;
240
+ } else {
241
+ const name = String(e.name ?? '');
242
+ id = `kb:author:${name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')}`;
243
+ }
244
+ break;
245
+ }
246
+ default:
247
+ id = `kb:${type.toLowerCase()}:${index}`;
248
+ break;
249
+ }
250
+
251
+ const base: KBNode = {
252
+ id,
253
+ type: type as KBNodeType,
254
+ projections: Array.isArray(e.projections) ? e.projections as string[] : [],
255
+ created: type === 'COMMIT' ? (String(e.date ?? '').split('T')[0]) : '',
256
+ content: '',
257
+ filePath,
258
+ lifecycle: 'derived' as KBLifecycle,
259
+ };
260
+
261
+ // Spread all entry fields onto the node, preserving base fields
262
+ const node: KBNode = { ...e, ...base } as KBNode;
263
+
264
+ return node;
265
+ });
266
+ }
267
+
268
+ /**
269
+ * Append an edge to the edges.yaml file.
270
+ */
271
+ export function appendEdge(filePath: string, edge: KBEdge): void {
272
+ const dir = dirname(filePath);
273
+ if (!existsSync(dir)) {
274
+ mkdirSync(dir, { recursive: true });
275
+ }
276
+
277
+ const entry: Record<string, string> = {
278
+ type: edge.type,
279
+ from: edge.from,
280
+ to: edge.to,
281
+ };
282
+ if (edge.evidence) entry.evidence = edge.evidence;
283
+
284
+ const yamlEntry = stringifyYaml([entry], { lineWidth: 0 }).trimEnd();
285
+
286
+ if (!existsSync(filePath)) {
287
+ // New file — write with comment header
288
+ const header = '# Knowledge Graph edges\n# Format: {type, from, to, evidence?}\n\n';
289
+ appendFileSync(filePath, header + yamlEntry + '\n', 'utf-8');
290
+ } else {
291
+ // Append to existing file
292
+ appendFileSync(filePath, '\n' + yamlEntry + '\n', 'utf-8');
293
+ }
294
+ }