@diyor28/context 1.0.0

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 (280) hide show
  1. package/README.md +270 -0
  2. package/dist/__tests__/attachment-selector.test.d.ts +11 -0
  3. package/dist/__tests__/attachment-selector.test.d.ts.map +1 -0
  4. package/dist/__tests__/attachment-selector.test.js +449 -0
  5. package/dist/__tests__/attachment-selector.test.js.map +1 -0
  6. package/dist/__tests__/cache-breakpoints.test.d.ts +11 -0
  7. package/dist/__tests__/cache-breakpoints.test.d.ts.map +1 -0
  8. package/dist/__tests__/cache-breakpoints.test.js +398 -0
  9. package/dist/__tests__/cache-breakpoints.test.js.map +1 -0
  10. package/dist/__tests__/codecs.test.d.ts +7 -0
  11. package/dist/__tests__/codecs.test.d.ts.map +1 -0
  12. package/dist/__tests__/codecs.test.js +331 -0
  13. package/dist/__tests__/codecs.test.js.map +1 -0
  14. package/dist/__tests__/compactor.test.d.ts +11 -0
  15. package/dist/__tests__/compactor.test.d.ts.map +1 -0
  16. package/dist/__tests__/compactor.test.js +519 -0
  17. package/dist/__tests__/compactor.test.js.map +1 -0
  18. package/dist/__tests__/context-graph.test.d.ts +7 -0
  19. package/dist/__tests__/context-graph.test.d.ts.map +1 -0
  20. package/dist/__tests__/context-graph.test.js +262 -0
  21. package/dist/__tests__/context-graph.test.js.map +1 -0
  22. package/dist/__tests__/hash.test.d.ts +7 -0
  23. package/dist/__tests__/hash.test.d.ts.map +1 -0
  24. package/dist/__tests__/hash.test.js +228 -0
  25. package/dist/__tests__/hash.test.js.map +1 -0
  26. package/dist/__tests__/integration.test.d.ts +15 -0
  27. package/dist/__tests__/integration.test.d.ts.map +1 -0
  28. package/dist/__tests__/integration.test.js +728 -0
  29. package/dist/__tests__/integration.test.js.map +1 -0
  30. package/dist/__tests__/kind-order.test.d.ts +7 -0
  31. package/dist/__tests__/kind-order.test.d.ts.map +1 -0
  32. package/dist/__tests__/kind-order.test.js +243 -0
  33. package/dist/__tests__/kind-order.test.js.map +1 -0
  34. package/dist/__tests__/phase2-integration.test.d.ts +5 -0
  35. package/dist/__tests__/phase2-integration.test.d.ts.map +1 -0
  36. package/dist/__tests__/phase2-integration.test.js +222 -0
  37. package/dist/__tests__/phase2-integration.test.js.map +1 -0
  38. package/dist/__tests__/queries.test.d.ts +7 -0
  39. package/dist/__tests__/queries.test.d.ts.map +1 -0
  40. package/dist/__tests__/queries.test.js +254 -0
  41. package/dist/__tests__/queries.test.js.map +1 -0
  42. package/dist/__tests__/token-estimator.test.d.ts +7 -0
  43. package/dist/__tests__/token-estimator.test.d.ts.map +1 -0
  44. package/dist/__tests__/token-estimator.test.js +267 -0
  45. package/dist/__tests__/token-estimator.test.js.map +1 -0
  46. package/dist/adapters/anthropic-estimator.d.ts +38 -0
  47. package/dist/adapters/anthropic-estimator.d.ts.map +1 -0
  48. package/dist/adapters/anthropic-estimator.js +108 -0
  49. package/dist/adapters/anthropic-estimator.js.map +1 -0
  50. package/dist/adapters/attachment-resolver.d.ts +96 -0
  51. package/dist/adapters/attachment-resolver.d.ts.map +1 -0
  52. package/dist/adapters/attachment-resolver.js +176 -0
  53. package/dist/adapters/attachment-resolver.js.map +1 -0
  54. package/dist/adapters/attachment-selector.d.ts +59 -0
  55. package/dist/adapters/attachment-selector.d.ts.map +1 -0
  56. package/dist/adapters/attachment-selector.js +163 -0
  57. package/dist/adapters/attachment-selector.js.map +1 -0
  58. package/dist/adapters/gemini-estimator.d.ts +27 -0
  59. package/dist/adapters/gemini-estimator.d.ts.map +1 -0
  60. package/dist/adapters/gemini-estimator.js +80 -0
  61. package/dist/adapters/gemini-estimator.js.map +1 -0
  62. package/dist/adapters/index.d.ts +12 -0
  63. package/dist/adapters/index.d.ts.map +1 -0
  64. package/dist/adapters/index.js +28 -0
  65. package/dist/adapters/index.js.map +1 -0
  66. package/dist/adapters/memory-store.d.ts +139 -0
  67. package/dist/adapters/memory-store.d.ts.map +1 -0
  68. package/dist/adapters/memory-store.js +187 -0
  69. package/dist/adapters/memory-store.js.map +1 -0
  70. package/dist/adapters/openai-estimator.d.ts +35 -0
  71. package/dist/adapters/openai-estimator.d.ts.map +1 -0
  72. package/dist/adapters/openai-estimator.js +89 -0
  73. package/dist/adapters/openai-estimator.js.map +1 -0
  74. package/dist/adapters/summarizer.d.ts +121 -0
  75. package/dist/adapters/summarizer.d.ts.map +1 -0
  76. package/dist/adapters/summarizer.js +121 -0
  77. package/dist/adapters/summarizer.js.map +1 -0
  78. package/dist/adapters/token-estimator.d.ts +63 -0
  79. package/dist/adapters/token-estimator.d.ts.map +1 -0
  80. package/dist/adapters/token-estimator.js +37 -0
  81. package/dist/adapters/token-estimator.js.map +1 -0
  82. package/dist/builder/context-builder.d.ts +186 -0
  83. package/dist/builder/context-builder.d.ts.map +1 -0
  84. package/dist/builder/context-builder.js +305 -0
  85. package/dist/builder/context-builder.js.map +1 -0
  86. package/dist/builder/context-fork.d.ts +166 -0
  87. package/dist/builder/context-fork.d.ts.map +1 -0
  88. package/dist/builder/context-fork.js +282 -0
  89. package/dist/builder/context-fork.js.map +1 -0
  90. package/dist/builder/index.d.ts +6 -0
  91. package/dist/builder/index.d.ts.map +1 -0
  92. package/dist/builder/index.js +22 -0
  93. package/dist/builder/index.js.map +1 -0
  94. package/dist/codecs/base.d.ts +18 -0
  95. package/dist/codecs/base.d.ts.map +1 -0
  96. package/dist/codecs/base.js +39 -0
  97. package/dist/codecs/base.js.map +1 -0
  98. package/dist/codecs/conversation-history.codec.d.ts +81 -0
  99. package/dist/codecs/conversation-history.codec.d.ts.map +1 -0
  100. package/dist/codecs/conversation-history.codec.js +89 -0
  101. package/dist/codecs/conversation-history.codec.js.map +1 -0
  102. package/dist/codecs/index.d.ts +31 -0
  103. package/dist/codecs/index.d.ts.map +1 -0
  104. package/dist/codecs/index.js +71 -0
  105. package/dist/codecs/index.js.map +1 -0
  106. package/dist/codecs/redacted-stub.codec.d.ts +32 -0
  107. package/dist/codecs/redacted-stub.codec.d.ts.map +1 -0
  108. package/dist/codecs/redacted-stub.codec.js +64 -0
  109. package/dist/codecs/redacted-stub.codec.js.map +1 -0
  110. package/dist/codecs/structured-reference.codec.d.ts +40 -0
  111. package/dist/codecs/structured-reference.codec.d.ts.map +1 -0
  112. package/dist/codecs/structured-reference.codec.js +81 -0
  113. package/dist/codecs/structured-reference.codec.js.map +1 -0
  114. package/dist/codecs/system-rules.codec.d.ts +32 -0
  115. package/dist/codecs/system-rules.codec.d.ts.map +1 -0
  116. package/dist/codecs/system-rules.codec.js +62 -0
  117. package/dist/codecs/system-rules.codec.js.map +1 -0
  118. package/dist/codecs/tool-output.codec.d.ts +66 -0
  119. package/dist/codecs/tool-output.codec.d.ts.map +1 -0
  120. package/dist/codecs/tool-output.codec.js +95 -0
  121. package/dist/codecs/tool-output.codec.js.map +1 -0
  122. package/dist/codecs/tool-schema.codec.d.ts +36 -0
  123. package/dist/codecs/tool-schema.codec.d.ts.map +1 -0
  124. package/dist/codecs/tool-schema.codec.js +74 -0
  125. package/dist/codecs/tool-schema.codec.js.map +1 -0
  126. package/dist/codecs/unsafe-text.codec.d.ts +28 -0
  127. package/dist/codecs/unsafe-text.codec.d.ts.map +1 -0
  128. package/dist/codecs/unsafe-text.codec.js +63 -0
  129. package/dist/codecs/unsafe-text.codec.js.map +1 -0
  130. package/dist/graph/context-graph.d.ts +121 -0
  131. package/dist/graph/context-graph.d.ts.map +1 -0
  132. package/dist/graph/context-graph.js +166 -0
  133. package/dist/graph/context-graph.js.map +1 -0
  134. package/dist/graph/index.d.ts +8 -0
  135. package/dist/graph/index.d.ts.map +1 -0
  136. package/dist/graph/index.js +24 -0
  137. package/dist/graph/index.js.map +1 -0
  138. package/dist/graph/kind-order.d.ts +60 -0
  139. package/dist/graph/kind-order.d.ts.map +1 -0
  140. package/dist/graph/kind-order.js +113 -0
  141. package/dist/graph/kind-order.js.map +1 -0
  142. package/dist/graph/queries.d.ts +68 -0
  143. package/dist/graph/queries.d.ts.map +1 -0
  144. package/dist/graph/queries.js +240 -0
  145. package/dist/graph/queries.js.map +1 -0
  146. package/dist/graph/views.d.ts +90 -0
  147. package/dist/graph/views.d.ts.map +1 -0
  148. package/dist/graph/views.js +173 -0
  149. package/dist/graph/views.js.map +1 -0
  150. package/dist/index.d.ts +16 -0
  151. package/dist/index.d.ts.map +1 -0
  152. package/dist/index.js +40 -0
  153. package/dist/index.js.map +1 -0
  154. package/dist/pipeline/compactor.d.ts +128 -0
  155. package/dist/pipeline/compactor.d.ts.map +1 -0
  156. package/dist/pipeline/compactor.js +346 -0
  157. package/dist/pipeline/compactor.js.map +1 -0
  158. package/dist/pipeline/index.d.ts +6 -0
  159. package/dist/pipeline/index.d.ts.map +1 -0
  160. package/dist/pipeline/index.js +22 -0
  161. package/dist/pipeline/index.js.map +1 -0
  162. package/dist/pipeline/summarizer.d.ts +18 -0
  163. package/dist/pipeline/summarizer.d.ts.map +1 -0
  164. package/dist/pipeline/summarizer.js +68 -0
  165. package/dist/pipeline/summarizer.js.map +1 -0
  166. package/dist/policies/default-policy.d.ts +29 -0
  167. package/dist/policies/default-policy.d.ts.map +1 -0
  168. package/dist/policies/default-policy.js +58 -0
  169. package/dist/policies/default-policy.js.map +1 -0
  170. package/dist/policies/index.d.ts +5 -0
  171. package/dist/policies/index.d.ts.map +1 -0
  172. package/dist/policies/index.js +21 -0
  173. package/dist/policies/index.js.map +1 -0
  174. package/dist/providers/anthropic-compiler.d.ts +58 -0
  175. package/dist/providers/anthropic-compiler.d.ts.map +1 -0
  176. package/dist/providers/anthropic-compiler.js +182 -0
  177. package/dist/providers/anthropic-compiler.js.map +1 -0
  178. package/dist/providers/capabilities.d.ts +54 -0
  179. package/dist/providers/capabilities.d.ts.map +1 -0
  180. package/dist/providers/capabilities.js +87 -0
  181. package/dist/providers/capabilities.js.map +1 -0
  182. package/dist/providers/gemini-compiler.d.ts +51 -0
  183. package/dist/providers/gemini-compiler.d.ts.map +1 -0
  184. package/dist/providers/gemini-compiler.js +206 -0
  185. package/dist/providers/gemini-compiler.js.map +1 -0
  186. package/dist/providers/index.d.ts +8 -0
  187. package/dist/providers/index.d.ts.map +1 -0
  188. package/dist/providers/index.js +24 -0
  189. package/dist/providers/index.js.map +1 -0
  190. package/dist/providers/openai-compiler.d.ts +46 -0
  191. package/dist/providers/openai-compiler.d.ts.map +1 -0
  192. package/dist/providers/openai-compiler.js +149 -0
  193. package/dist/providers/openai-compiler.js.map +1 -0
  194. package/dist/types/attachment.d.ts +62 -0
  195. package/dist/types/attachment.d.ts.map +1 -0
  196. package/dist/types/attachment.js +6 -0
  197. package/dist/types/attachment.js.map +1 -0
  198. package/dist/types/block.d.ts +61 -0
  199. package/dist/types/block.d.ts.map +1 -0
  200. package/dist/types/block.js +8 -0
  201. package/dist/types/block.js.map +1 -0
  202. package/dist/types/codec.d.ts +58 -0
  203. package/dist/types/codec.d.ts.map +1 -0
  204. package/dist/types/codec.js +6 -0
  205. package/dist/types/codec.js.map +1 -0
  206. package/dist/types/compiled.d.ts +91 -0
  207. package/dist/types/compiled.d.ts.map +1 -0
  208. package/dist/types/compiled.js +6 -0
  209. package/dist/types/compiled.js.map +1 -0
  210. package/dist/types/hash.d.ts +24 -0
  211. package/dist/types/hash.d.ts.map +1 -0
  212. package/dist/types/hash.js +49 -0
  213. package/dist/types/hash.js.map +1 -0
  214. package/dist/types/index.d.ts +10 -0
  215. package/dist/types/index.d.ts.map +1 -0
  216. package/dist/types/index.js +26 -0
  217. package/dist/types/index.js.map +1 -0
  218. package/dist/types/policy.d.ts +128 -0
  219. package/dist/types/policy.d.ts.map +1 -0
  220. package/dist/types/policy.js +55 -0
  221. package/dist/types/policy.js.map +1 -0
  222. package/package.json +55 -0
  223. package/postcss.config.js +4 -0
  224. package/src/__tests__/attachment-selector.test.ts +559 -0
  225. package/src/__tests__/cache-breakpoints.test.ts +566 -0
  226. package/src/__tests__/codecs.test.ts +417 -0
  227. package/src/__tests__/compactor.test.ts +608 -0
  228. package/src/__tests__/context-graph.test.ts +383 -0
  229. package/src/__tests__/hash.test.ts +274 -0
  230. package/src/__tests__/integration.test.ts +866 -0
  231. package/src/__tests__/kind-order.test.ts +312 -0
  232. package/src/__tests__/phase2-integration.test.ts +253 -0
  233. package/src/__tests__/queries.test.ts +387 -0
  234. package/src/__tests__/token-estimator.test.ts +326 -0
  235. package/src/adapters/anthropic-estimator.ts +125 -0
  236. package/src/adapters/attachment-resolver.ts +295 -0
  237. package/src/adapters/attachment-selector.ts +218 -0
  238. package/src/adapters/gemini-estimator.ts +93 -0
  239. package/src/adapters/index.ts +12 -0
  240. package/src/adapters/memory-store.ts +299 -0
  241. package/src/adapters/openai-estimator.ts +105 -0
  242. package/src/adapters/summarizer.ts +250 -0
  243. package/src/adapters/token-estimator.ts +74 -0
  244. package/src/builder/context-builder.ts +467 -0
  245. package/src/builder/context-fork.ts +471 -0
  246. package/src/builder/index.ts +6 -0
  247. package/src/codecs/base.ts +36 -0
  248. package/src/codecs/conversation-history.codec.ts +108 -0
  249. package/src/codecs/index.ts +57 -0
  250. package/src/codecs/redacted-stub.codec.ts +76 -0
  251. package/src/codecs/structured-reference.codec.ts +96 -0
  252. package/src/codecs/system-rules.codec.ts +74 -0
  253. package/src/codecs/tool-output.codec.ts +109 -0
  254. package/src/codecs/tool-schema.codec.ts +87 -0
  255. package/src/codecs/unsafe-text.codec.ts +74 -0
  256. package/src/graph/context-graph.ts +205 -0
  257. package/src/graph/index.ts +8 -0
  258. package/src/graph/kind-order.ts +125 -0
  259. package/src/graph/queries.ts +306 -0
  260. package/src/graph/views.ts +255 -0
  261. package/src/index.ts +31 -0
  262. package/src/pipeline/compactor.ts +563 -0
  263. package/src/pipeline/index.ts +6 -0
  264. package/src/pipeline/summarizer.ts +76 -0
  265. package/src/policies/default-policy.ts +69 -0
  266. package/src/policies/index.ts +5 -0
  267. package/src/providers/anthropic-compiler.ts +294 -0
  268. package/src/providers/capabilities.ts +144 -0
  269. package/src/providers/gemini-compiler.ts +272 -0
  270. package/src/providers/index.ts +8 -0
  271. package/src/providers/openai-compiler.ts +191 -0
  272. package/src/types/attachment.ts +86 -0
  273. package/src/types/block.ts +84 -0
  274. package/src/types/codec.ts +68 -0
  275. package/src/types/compiled.ts +109 -0
  276. package/src/types/hash.ts +58 -0
  277. package/src/types/index.ts +10 -0
  278. package/src/types/policy.ts +194 -0
  279. package/tsconfig.json +21 -0
  280. package/vitest.config.ts +21 -0
@@ -0,0 +1,205 @@
1
+ /**
2
+ * ContextGraph: Core data structure for managing context blocks and relationships.
3
+ *
4
+ * Holds blocks, tracks derivation/reference edges, supports querying and view creation.
5
+ */
6
+
7
+ import type { ContextBlock, BlockRef } from '../types/block.js';
8
+ import type { BlockQuery } from './queries.js';
9
+ import type { ContextView, ViewOptions } from './views.js';
10
+ import { createContextView } from './views.js';
11
+ import { matchesQuery } from './queries.js';
12
+
13
+ /**
14
+ * Edge types for block relationships.
15
+ */
16
+ export interface ContextGraphEdges {
17
+ /** Derivation edges: blockHash -> parent BlockRefs (provenance tracking) */
18
+ derivedFrom: Map<string, BlockRef[]>;
19
+
20
+ /** Reference edges: blockHash -> referenced blockHashes (lightweight citations) */
21
+ references: Map<string, string[]>;
22
+ }
23
+
24
+ /**
25
+ * ContextGraph: Mutable graph of context blocks with relationship tracking.
26
+ *
27
+ * Responsibilities:
28
+ * - Store blocks by content-addressed hash
29
+ * - Track derivation and reference relationships
30
+ * - Support query-based block selection
31
+ * - Create deterministic views with stable ordering
32
+ */
33
+ export class ContextGraph {
34
+ /** All blocks indexed by blockHash */
35
+ private readonly blocks: Map<string, ContextBlock<unknown>>;
36
+
37
+ /** Block relationship edges */
38
+ private readonly edges: ContextGraphEdges;
39
+
40
+ constructor() {
41
+ this.blocks = new Map();
42
+ this.edges = {
43
+ derivedFrom: new Map(),
44
+ references: new Map(),
45
+ };
46
+ }
47
+
48
+ /**
49
+ * Add a block to the graph.
50
+ * Idempotent: if block already exists (same hash), this is a no-op.
51
+ *
52
+ * @param block - Block to add
53
+ * @param derivedFrom - Optional parent blocks for provenance
54
+ * @param references - Optional referenced block hashes
55
+ */
56
+ addBlock<TPayload>(
57
+ block: ContextBlock<TPayload>,
58
+ derivedFrom?: BlockRef[],
59
+ references?: string[]
60
+ ): void {
61
+ const { blockHash } = block;
62
+
63
+ // Idempotent: skip if already exists
64
+ if (this.blocks.has(blockHash)) {
65
+ return;
66
+ }
67
+
68
+ // Add block
69
+ this.blocks.set(blockHash, block as ContextBlock<unknown>);
70
+
71
+ // Add derivation edges
72
+ if (derivedFrom && derivedFrom.length > 0) {
73
+ this.edges.derivedFrom.set(blockHash, derivedFrom);
74
+ }
75
+
76
+ // Add reference edges
77
+ if (references && references.length > 0) {
78
+ this.edges.references.set(blockHash, references);
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Remove a block from the graph.
84
+ * Also removes associated edges.
85
+ *
86
+ * @param blockHash - Block hash to remove
87
+ * @returns True if block was removed, false if not found
88
+ */
89
+ removeBlock(blockHash: string): boolean {
90
+ const existed = this.blocks.delete(blockHash);
91
+
92
+ // Clean up edges
93
+ this.edges.derivedFrom.delete(blockHash);
94
+ this.edges.references.delete(blockHash);
95
+
96
+ return existed;
97
+ }
98
+
99
+ /**
100
+ * Get a block by hash.
101
+ *
102
+ * @param blockHash - Block hash
103
+ * @returns Block if found, undefined otherwise
104
+ */
105
+ getBlock<TPayload = unknown>(blockHash: string): ContextBlock<TPayload> | undefined {
106
+ return this.blocks.get(blockHash) as ContextBlock<TPayload> | undefined;
107
+ }
108
+
109
+ /**
110
+ * Check if a block exists in the graph.
111
+ *
112
+ * @param blockHash - Block hash
113
+ * @returns True if block exists
114
+ */
115
+ hasBlock(blockHash: string): boolean {
116
+ return this.blocks.has(blockHash);
117
+ }
118
+
119
+ /**
120
+ * Get all blocks in the graph (unordered).
121
+ *
122
+ * @returns Array of all blocks
123
+ */
124
+ getAllBlocks(): ContextBlock<unknown>[] {
125
+ return Array.from(this.blocks.values());
126
+ }
127
+
128
+ /**
129
+ * Get the number of blocks in the graph.
130
+ *
131
+ * @returns Block count
132
+ */
133
+ getBlockCount(): number {
134
+ return this.blocks.size;
135
+ }
136
+
137
+ /**
138
+ * Get parent blocks (provenance) for a given block.
139
+ *
140
+ * @param blockHash - Block hash
141
+ * @returns Array of parent BlockRefs, or empty array if none
142
+ */
143
+ getDerivedFrom(blockHash: string): BlockRef[] {
144
+ return this.edges.derivedFrom.get(blockHash) ?? [];
145
+ }
146
+
147
+ /**
148
+ * Get referenced block hashes for a given block.
149
+ *
150
+ * @param blockHash - Block hash
151
+ * @returns Array of referenced hashes, or empty array if none
152
+ */
153
+ getReferences(blockHash: string): string[] {
154
+ return this.edges.references.get(blockHash) ?? [];
155
+ }
156
+
157
+ /**
158
+ * Select blocks matching a query.
159
+ * Returns blocks in arbitrary order (use createView for deterministic ordering).
160
+ *
161
+ * @param query - Block query
162
+ * @returns Matching blocks
163
+ */
164
+ select(query: BlockQuery): ContextBlock<unknown>[] {
165
+ const allBlocks = this.getAllBlocks();
166
+ return allBlocks.filter((block) => matchesQuery(block, query, this));
167
+ }
168
+
169
+ /**
170
+ * Create a deterministic view of the graph.
171
+ * View is immutable snapshot with stable ordering (KIND_ORDER + lexicographic).
172
+ *
173
+ * @param options - View options (query, token budget, etc.)
174
+ * @returns ContextView with ordered blocks
175
+ */
176
+ async createView(options: ViewOptions): Promise<ContextView> {
177
+ return createContextView(this, options);
178
+ }
179
+
180
+ /**
181
+ * Clear all blocks and edges.
182
+ */
183
+ clear(): void {
184
+ this.blocks.clear();
185
+ this.edges.derivedFrom.clear();
186
+ this.edges.references.clear();
187
+ }
188
+
189
+ /**
190
+ * Get graph statistics.
191
+ *
192
+ * @returns Graph stats
193
+ */
194
+ getStats(): {
195
+ blockCount: number;
196
+ derivationEdgeCount: number;
197
+ referenceEdgeCount: number;
198
+ } {
199
+ return {
200
+ blockCount: this.blocks.size,
201
+ derivationEdgeCount: this.edges.derivedFrom.size,
202
+ referenceEdgeCount: this.edges.references.size,
203
+ };
204
+ }
205
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Graph utilities for @foundry/context
3
+ */
4
+
5
+ export * from './kind-order.js';
6
+ export * from './context-graph.js';
7
+ export * from './queries.js';
8
+ export * from './views.js';
@@ -0,0 +1,125 @@
1
+ /**
2
+ * KIND_ORDER: Deterministic block ordering for context compilation.
3
+ *
4
+ * This is the single source of truth for block ordering.
5
+ * All context compilation MUST respect this order.
6
+ */
7
+
8
+ import type { BlockKind, ContextBlock } from '../types/block.js';
9
+
10
+ /**
11
+ * Immutable block kind ordering (pinned → reference → memory → state → tool_output → history → turn).
12
+ *
13
+ * NEVER modify this array. It is the contract for deterministic compilation.
14
+ */
15
+ export const KIND_ORDER: readonly BlockKind[] = Object.freeze([
16
+ 'pinned', // System rules, always first
17
+ 'reference', // Tool schemas, external docs
18
+ 'memory', // Long-term memory, RAG results
19
+ 'state', // Current workflow/session state
20
+ 'tool_output', // Tool execution results
21
+ 'history', // Conversation history
22
+ 'turn', // Current turn (user message)
23
+ ] as const);
24
+
25
+ /**
26
+ * Get kind index for ordering comparison.
27
+ * Returns -1 if kind is not in KIND_ORDER.
28
+ *
29
+ * @param kind - Block kind
30
+ * @returns Index in KIND_ORDER, or -1 if not found
31
+ */
32
+ export function getKindIndex(kind: BlockKind): number {
33
+ return KIND_ORDER.indexOf(kind);
34
+ }
35
+
36
+ /**
37
+ * Compare two block kinds for ordering.
38
+ * Returns negative if a < b, positive if a > b, zero if equal.
39
+ *
40
+ * @param a - First block kind
41
+ * @param b - Second block kind
42
+ * @returns Comparison result
43
+ */
44
+ export function compareKinds(a: BlockKind, b: BlockKind): number {
45
+ const indexA = getKindIndex(a);
46
+ const indexB = getKindIndex(b);
47
+
48
+ // Throw if either kind is not in KIND_ORDER
49
+ if (indexA === -1) {
50
+ throw new Error(`Invalid block kind: ${a}`);
51
+ }
52
+ if (indexB === -1) {
53
+ throw new Error(`Invalid block kind: ${b}`);
54
+ }
55
+
56
+ return indexA - indexB;
57
+ }
58
+
59
+ /**
60
+ * Sort blocks by KIND_ORDER (stable sort, preserves relative order within same kind).
61
+ *
62
+ * @param blocks - Blocks to sort
63
+ * @returns Sorted blocks (new array)
64
+ */
65
+ export function sortBlocksByKind<TPayload = unknown>(
66
+ blocks: ContextBlock<TPayload>[]
67
+ ): ContextBlock<TPayload>[] {
68
+ return [...blocks].sort((a, b) => compareKinds(a.meta.kind, b.meta.kind));
69
+ }
70
+
71
+ /**
72
+ * Validate that blocks are sorted by KIND_ORDER.
73
+ * Throws if blocks are not sorted correctly.
74
+ *
75
+ * @param blocks - Blocks to validate
76
+ */
77
+ export function validateBlockOrder<TPayload = unknown>(
78
+ blocks: ContextBlock<TPayload>[]
79
+ ): void {
80
+ for (let i = 1; i < blocks.length; i++) {
81
+ const prev = blocks[i - 1];
82
+ const curr = blocks[i];
83
+
84
+ const comparison = compareKinds(prev.meta.kind, curr.meta.kind);
85
+ if (comparison > 0) {
86
+ throw new Error(
87
+ `Blocks not sorted by KIND_ORDER: ${prev.meta.kind} (index ${i - 1}) ` +
88
+ `comes before ${curr.meta.kind} (index ${i})`
89
+ );
90
+ }
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Group blocks by kind (in KIND_ORDER).
96
+ * Returns a map of kind -> blocks.
97
+ *
98
+ * @param blocks - Blocks to group
99
+ * @returns Map of kind to blocks
100
+ */
101
+ export function groupBlocksByKind<TPayload = unknown>(
102
+ blocks: ContextBlock<TPayload>[]
103
+ ): Map<BlockKind, ContextBlock<TPayload>[]> {
104
+ const groups = new Map<BlockKind, ContextBlock<TPayload>[]>();
105
+
106
+ for (const block of blocks) {
107
+ const kind = block.meta.kind;
108
+ if (!groups.has(kind)) {
109
+ groups.set(kind, []);
110
+ }
111
+ groups.get(kind)!.push(block);
112
+ }
113
+
114
+ return groups;
115
+ }
116
+
117
+ /**
118
+ * Check if a kind is valid (exists in KIND_ORDER).
119
+ *
120
+ * @param kind - Block kind to check
121
+ * @returns True if valid
122
+ */
123
+ export function isValidKind(kind: string): kind is BlockKind {
124
+ return getKindIndex(kind as BlockKind) !== -1;
125
+ }
@@ -0,0 +1,306 @@
1
+ /**
2
+ * BlockQuery: Filtering and selection of context blocks.
3
+ *
4
+ * Supports filtering by kind, tags, sensitivity, stability, provenance, and token budget.
5
+ */
6
+
7
+ import type { BlockKind, SensitivityLevel, ContextBlock } from '../types/block.js';
8
+ import type { ContextGraph } from './context-graph.js';
9
+
10
+ /**
11
+ * Block query for filtering blocks in a ContextGraph.
12
+ */
13
+ export interface BlockQuery {
14
+ /** Filter by block kinds (OR logic: match any) */
15
+ kinds?: BlockKind[];
16
+
17
+ /** Filter by tags (AND logic: block must have all tags) */
18
+ tags?: string[];
19
+
20
+ /** Filter by minimum sensitivity level */
21
+ minSensitivity?: SensitivityLevel;
22
+
23
+ /** Filter by maximum sensitivity level */
24
+ maxSensitivity?: SensitivityLevel;
25
+
26
+ /** Filter by source identifier */
27
+ source?: string;
28
+
29
+ /** Filter by minimum creation timestamp (Unix seconds) */
30
+ minCreatedAt?: number;
31
+
32
+ /** Filter by maximum creation timestamp (Unix seconds) */
33
+ maxCreatedAt?: number;
34
+
35
+ /** Filter by provenance: only blocks derived from given hashes */
36
+ derivedFromAny?: string[];
37
+
38
+ /** Filter by provenance: only blocks NOT derived from given hashes */
39
+ notDerivedFromAny?: string[];
40
+
41
+ /** Filter by references: only blocks referencing any of given hashes */
42
+ referencesAny?: string[];
43
+
44
+ /** Exclude blocks with given hashes */
45
+ excludeHashes?: string[];
46
+
47
+ /** Maximum token budget (requires token estimation - applied in views) */
48
+ maxTokens?: number;
49
+ }
50
+
51
+ /**
52
+ * Sensitivity level ordering (public < internal < restricted).
53
+ */
54
+ const SENSITIVITY_ORDER: Record<SensitivityLevel, number> = {
55
+ public: 0,
56
+ internal: 1,
57
+ restricted: 2,
58
+ };
59
+
60
+ /**
61
+ * Compare two sensitivity levels.
62
+ *
63
+ * @param a - First sensitivity level
64
+ * @param b - Second sensitivity level
65
+ * @returns Negative if a < b, positive if a > b, zero if equal
66
+ */
67
+ export function compareSensitivity(a: SensitivityLevel, b: SensitivityLevel): number {
68
+ return SENSITIVITY_ORDER[a] - SENSITIVITY_ORDER[b];
69
+ }
70
+
71
+ /**
72
+ * Check if a block matches a query.
73
+ * Does NOT apply token budget (that's done in view creation).
74
+ *
75
+ * @param block - Block to check
76
+ * @param query - Query to match
77
+ * @param graph - Context graph (for provenance/reference lookups)
78
+ * @returns True if block matches query
79
+ */
80
+ export function matchesQuery(
81
+ block: ContextBlock<unknown>,
82
+ query: BlockQuery,
83
+ graph: ContextGraph
84
+ ): boolean {
85
+ // Filter by kinds (OR logic)
86
+ if (query.kinds && query.kinds.length > 0) {
87
+ if (!query.kinds.includes(block.meta.kind)) {
88
+ return false;
89
+ }
90
+ }
91
+
92
+ // Filter by tags (AND logic: block must have all query tags)
93
+ if (query.tags && query.tags.length > 0) {
94
+ const blockTags = new Set(block.meta.tags ?? []);
95
+ for (const tag of query.tags) {
96
+ if (!blockTags.has(tag)) {
97
+ return false;
98
+ }
99
+ }
100
+ }
101
+
102
+ // Filter by minimum sensitivity
103
+ if (query.minSensitivity !== undefined) {
104
+ if (compareSensitivity(block.meta.sensitivity, query.minSensitivity) < 0) {
105
+ return false;
106
+ }
107
+ }
108
+
109
+ // Filter by maximum sensitivity
110
+ if (query.maxSensitivity !== undefined) {
111
+ if (compareSensitivity(block.meta.sensitivity, query.maxSensitivity) > 0) {
112
+ return false;
113
+ }
114
+ }
115
+
116
+ // Filter by source
117
+ if (query.source !== undefined) {
118
+ if (block.meta.source !== query.source) {
119
+ return false;
120
+ }
121
+ }
122
+
123
+ // Filter by minimum creation timestamp
124
+ if (query.minCreatedAt !== undefined) {
125
+ if (block.meta.createdAt < query.minCreatedAt) {
126
+ return false;
127
+ }
128
+ }
129
+
130
+ // Filter by maximum creation timestamp
131
+ if (query.maxCreatedAt !== undefined) {
132
+ if (block.meta.createdAt > query.maxCreatedAt) {
133
+ return false;
134
+ }
135
+ }
136
+
137
+ // Filter by provenance: derivedFromAny
138
+ if (query.derivedFromAny && query.derivedFromAny.length > 0) {
139
+ const parents = graph.getDerivedFrom(block.blockHash);
140
+ const parentHashes = new Set(parents.map((p) => p.blockHash));
141
+
142
+ const hasMatch = query.derivedFromAny.some((hash) => parentHashes.has(hash));
143
+ if (!hasMatch) {
144
+ return false;
145
+ }
146
+ }
147
+
148
+ // Filter by provenance: notDerivedFromAny
149
+ if (query.notDerivedFromAny && query.notDerivedFromAny.length > 0) {
150
+ const parents = graph.getDerivedFrom(block.blockHash);
151
+ const parentHashes = new Set(parents.map((p) => p.blockHash));
152
+
153
+ const hasMatch = query.notDerivedFromAny.some((hash) => parentHashes.has(hash));
154
+ if (hasMatch) {
155
+ return false;
156
+ }
157
+ }
158
+
159
+ // Filter by references: referencesAny
160
+ if (query.referencesAny && query.referencesAny.length > 0) {
161
+ const refs = graph.getReferences(block.blockHash);
162
+ const refSet = new Set(refs);
163
+
164
+ const hasMatch = query.referencesAny.some((hash) => refSet.has(hash));
165
+ if (!hasMatch) {
166
+ return false;
167
+ }
168
+ }
169
+
170
+ // Exclude specific hashes
171
+ if (query.excludeHashes && query.excludeHashes.length > 0) {
172
+ if (query.excludeHashes.includes(block.blockHash)) {
173
+ return false;
174
+ }
175
+ }
176
+
177
+ // All filters passed
178
+ return true;
179
+ }
180
+
181
+ /**
182
+ * Create an empty query (matches all blocks).
183
+ *
184
+ * @returns Empty query
185
+ */
186
+ export function emptyQuery(): BlockQuery {
187
+ return {};
188
+ }
189
+
190
+ /**
191
+ * Merge multiple queries (AND logic: block must match all queries).
192
+ *
193
+ * @param queries - Queries to merge
194
+ * @returns Merged query
195
+ */
196
+ export function mergeQueries(...queries: BlockQuery[]): BlockQuery {
197
+ const merged: BlockQuery = {};
198
+
199
+ for (const query of queries) {
200
+ // Merge kinds (intersection)
201
+ if (query.kinds) {
202
+ if (merged.kinds) {
203
+ const kindSet = new Set(merged.kinds);
204
+ merged.kinds = query.kinds.filter((k) => kindSet.has(k));
205
+ } else {
206
+ merged.kinds = [...query.kinds];
207
+ }
208
+ }
209
+
210
+ // Merge tags (union - block must have all)
211
+ if (query.tags) {
212
+ merged.tags = [...(merged.tags ?? []), ...query.tags];
213
+ }
214
+
215
+ // Merge sensitivity (most restrictive)
216
+ if (query.minSensitivity !== undefined) {
217
+ if (merged.minSensitivity === undefined) {
218
+ merged.minSensitivity = query.minSensitivity;
219
+ } else {
220
+ // Take the higher minimum
221
+ if (compareSensitivity(query.minSensitivity, merged.minSensitivity) > 0) {
222
+ merged.minSensitivity = query.minSensitivity;
223
+ }
224
+ }
225
+ }
226
+
227
+ if (query.maxSensitivity !== undefined) {
228
+ if (merged.maxSensitivity === undefined) {
229
+ merged.maxSensitivity = query.maxSensitivity;
230
+ } else {
231
+ // Take the lower maximum
232
+ if (compareSensitivity(query.maxSensitivity, merged.maxSensitivity) < 0) {
233
+ merged.maxSensitivity = query.maxSensitivity;
234
+ }
235
+ }
236
+ }
237
+
238
+ // Merge source (must match - conflicting sources = no results)
239
+ if (query.source !== undefined) {
240
+ if (merged.source !== undefined && merged.source !== query.source) {
241
+ // Conflict: return impossible query
242
+ merged.kinds = [];
243
+ } else {
244
+ merged.source = query.source;
245
+ }
246
+ }
247
+
248
+ // Merge timestamps (most restrictive)
249
+ if (query.minCreatedAt !== undefined) {
250
+ if (merged.minCreatedAt === undefined) {
251
+ merged.minCreatedAt = query.minCreatedAt;
252
+ } else {
253
+ merged.minCreatedAt = Math.max(merged.minCreatedAt, query.minCreatedAt);
254
+ }
255
+ }
256
+
257
+ if (query.maxCreatedAt !== undefined) {
258
+ if (merged.maxCreatedAt === undefined) {
259
+ merged.maxCreatedAt = query.maxCreatedAt;
260
+ } else {
261
+ merged.maxCreatedAt = Math.min(merged.maxCreatedAt, query.maxCreatedAt);
262
+ }
263
+ }
264
+
265
+ // Merge provenance (union)
266
+ if (query.derivedFromAny) {
267
+ merged.derivedFromAny = [
268
+ ...(merged.derivedFromAny ?? []),
269
+ ...query.derivedFromAny,
270
+ ];
271
+ }
272
+
273
+ if (query.notDerivedFromAny) {
274
+ merged.notDerivedFromAny = [
275
+ ...(merged.notDerivedFromAny ?? []),
276
+ ...query.notDerivedFromAny,
277
+ ];
278
+ }
279
+
280
+ if (query.referencesAny) {
281
+ merged.referencesAny = [
282
+ ...(merged.referencesAny ?? []),
283
+ ...query.referencesAny,
284
+ ];
285
+ }
286
+
287
+ // Merge excludeHashes (union)
288
+ if (query.excludeHashes) {
289
+ merged.excludeHashes = [
290
+ ...(merged.excludeHashes ?? []),
291
+ ...query.excludeHashes,
292
+ ];
293
+ }
294
+
295
+ // Merge maxTokens (minimum)
296
+ if (query.maxTokens !== undefined) {
297
+ if (merged.maxTokens === undefined) {
298
+ merged.maxTokens = query.maxTokens;
299
+ } else {
300
+ merged.maxTokens = Math.min(merged.maxTokens, query.maxTokens);
301
+ }
302
+ }
303
+ }
304
+
305
+ return merged;
306
+ }