@a5c-ai/krate 5.0.1-staging.04a3db697

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 (246) hide show
  1. package/Dockerfile +31 -0
  2. package/README.md +183 -0
  3. package/bin/krate-demo.mjs +23 -0
  4. package/bin/krate-server.mjs +14 -0
  5. package/dist/krate-controller-ui.json +3067 -0
  6. package/dist/krate-lifecycle.json +201 -0
  7. package/dist/krate-runtime-snapshot.json +2955 -0
  8. package/dist/krate-summary.json +722 -0
  9. package/docs/README.md +61 -0
  10. package/docs/agents/README.md +83 -0
  11. package/docs/agents/acceptance-test-matrix.md +193 -0
  12. package/docs/agents/agent-mux-adapter-contract.md +167 -0
  13. package/docs/agents/agent-mux-source-map.md +310 -0
  14. package/docs/agents/agent-run-memory-import-spec.md +256 -0
  15. package/docs/agents/agent-stack-management-spec.md +421 -0
  16. package/docs/agents/api-contract-spec.md +309 -0
  17. package/docs/agents/artifacts-writeback-spec.md +145 -0
  18. package/docs/agents/chart-packaging-spec.md +128 -0
  19. package/docs/agents/ci-orchestration-spec.md +140 -0
  20. package/docs/agents/context-assembly-spec.md +219 -0
  21. package/docs/agents/controller-reconciliation-spec.md +255 -0
  22. package/docs/agents/crd-schema-spec.md +315 -0
  23. package/docs/agents/decision-log-open-questions.md +169 -0
  24. package/docs/agents/developer-implementation-checklist.md +329 -0
  25. package/docs/agents/dispatching-design.md +262 -0
  26. package/docs/agents/gaps-agent-mux-to-krate-crds.md +298 -0
  27. package/docs/agents/glossary.md +66 -0
  28. package/docs/agents/implementation-blueprint.md +324 -0
  29. package/docs/agents/implementation-rollout-slices.md +251 -0
  30. package/docs/agents/memory-context-integration-spec.md +194 -0
  31. package/docs/agents/memory-ontology-schema-spec.md +253 -0
  32. package/docs/agents/memory-operations-runbook.md +121 -0
  33. package/docs/agents/mvp-vertical-slice-spec.md +146 -0
  34. package/docs/agents/observability-audit-spec.md +265 -0
  35. package/docs/agents/operator-runbook.md +174 -0
  36. package/docs/agents/org-memory-api-payload-examples.md +333 -0
  37. package/docs/agents/org-memory-controller-sequence-spec.md +181 -0
  38. package/docs/agents/org-memory-e2e-fixture-plan.md +161 -0
  39. package/docs/agents/org-memory-ui-implementation-map.md +114 -0
  40. package/docs/agents/org-memory-vertical-slice-spec.md +168 -0
  41. package/docs/agents/org-resource-model-delta-spec.md +111 -0
  42. package/docs/agents/org-route-resource-model-spec.md +183 -0
  43. package/docs/agents/org-scoping-namespace-spec.md +114 -0
  44. package/docs/agents/rbac-secrets-management-spec.md +406 -0
  45. package/docs/agents/repository-page-integration-spec.md +255 -0
  46. package/docs/agents/resource-contract-examples.md +808 -0
  47. package/docs/agents/resource-relationship-map.md +190 -0
  48. package/docs/agents/security-threat-model.md +188 -0
  49. package/docs/agents/shared-memory-company-brain-spec.md +358 -0
  50. package/docs/agents/storage-migration-spec.md +168 -0
  51. package/docs/agents/subagent-orchestration-spec.md +152 -0
  52. package/docs/agents/system-overview.md +88 -0
  53. package/docs/agents/tools-mcp-skills-spec.md +189 -0
  54. package/docs/agents/traceability-matrix.md +79 -0
  55. package/docs/agents/ui-flow-spec.md +211 -0
  56. package/docs/agents/ui-ux-system-spec.md +426 -0
  57. package/docs/agents/workspace-lifecycle-spec.md +166 -0
  58. package/docs/architecture-spec.md +78 -0
  59. package/docs/components/control-plane.md +78 -0
  60. package/docs/components/data-plane.md +69 -0
  61. package/docs/components/hooks-events.md +67 -0
  62. package/docs/components/identity-rbac-policy.md +73 -0
  63. package/docs/components/kubevela-oam.md +70 -0
  64. package/docs/components/operations-publishing.md +81 -0
  65. package/docs/components/runners-ci.md +66 -0
  66. package/docs/components/web-ui.md +94 -0
  67. package/docs/external/README.md +47 -0
  68. package/docs/external/bidirectional-sync-design.md +134 -0
  69. package/docs/external/cicd-interface.md +64 -0
  70. package/docs/external/external-backend-controllers.md +170 -0
  71. package/docs/external/external-backend-crds.md +234 -0
  72. package/docs/external/external-backend-ui-spec.md +151 -0
  73. package/docs/external/external-backend-ux-flows.md +115 -0
  74. package/docs/external/external-object-mapping.md +125 -0
  75. package/docs/external/git-forge-interface.md +68 -0
  76. package/docs/external/github-integration-design.md +151 -0
  77. package/docs/external/issue-tracking-interface.md +66 -0
  78. package/docs/external/provider-capability-manifests.md +204 -0
  79. package/docs/external/provider-catalog.md +139 -0
  80. package/docs/external/provider-rollout-testing.md +78 -0
  81. package/docs/external/research-results.md +48 -0
  82. package/docs/external/security-auth-permissions.md +81 -0
  83. package/docs/external/sync-state-machines.md +108 -0
  84. package/docs/external/unified-external-backend-model.md +107 -0
  85. package/docs/external/user-facing-changes.md +67 -0
  86. package/docs/gaps.md +161 -0
  87. package/docs/install.md +94 -0
  88. package/docs/krate-design.md +334 -0
  89. package/docs/local-minikube.md +55 -0
  90. package/docs/ontology/README.md +32 -0
  91. package/docs/ontology/bounded-contexts.md +29 -0
  92. package/docs/ontology/events-and-hooks.md +32 -0
  93. package/docs/ontology/oam-kubevela.md +32 -0
  94. package/docs/ontology/operations-and-release.md +25 -0
  95. package/docs/ontology/personas-and-actors.md +32 -0
  96. package/docs/ontology/policies-and-invariants.md +33 -0
  97. package/docs/ontology/problem-space.md +30 -0
  98. package/docs/ontology/resource-contracts.md +40 -0
  99. package/docs/ontology/resource-taxonomy.md +42 -0
  100. package/docs/ontology/runners-and-ci.md +29 -0
  101. package/docs/ontology/solution-space.md +24 -0
  102. package/docs/ontology/storage-and-data-boundaries.md +29 -0
  103. package/docs/ontology/validation-matrix.md +24 -0
  104. package/docs/ontology/web-ui-excellent-flows.md +32 -0
  105. package/docs/ontology/workflows.md +39 -0
  106. package/docs/ontology/world.md +35 -0
  107. package/docs/product-requirements.md +62 -0
  108. package/docs/roadmap-mvp.md +87 -0
  109. package/docs/system-requirements.md +90 -0
  110. package/docs/tests/README.md +53 -0
  111. package/docs/tests/agent-qa-plan.md +63 -0
  112. package/docs/tests/browser-ui-tests.md +62 -0
  113. package/docs/tests/ci-quality-gates.md +48 -0
  114. package/docs/tests/coverage-model.md +64 -0
  115. package/docs/tests/e2e-scenario-tests.md +53 -0
  116. package/docs/tests/fixtures-test-data.md +63 -0
  117. package/docs/tests/observability-reliability-tests.md +54 -0
  118. package/docs/tests/product-test-matrix.md +145 -0
  119. package/docs/tests/qa-adoption-roadmap.md +130 -0
  120. package/docs/tests/qa-automation-plan.md +101 -0
  121. package/docs/tests/security-compliance-tests.md +57 -0
  122. package/docs/tests/test-framework-tools.md +88 -0
  123. package/docs/tests/test-suite-layout.md +121 -0
  124. package/docs/tests/unit-integration-tests.md +48 -0
  125. package/docs/todo-kyverno +714 -0
  126. package/docs/todos.md +4 -0
  127. package/docs/user-stories.md +78 -0
  128. package/examples/minikube-demo.yaml +190 -0
  129. package/examples/oam-application.yaml +23 -0
  130. package/examples/policy-kyverno-pr-title.yaml +18 -0
  131. package/package.json +63 -0
  132. package/scripts/build.mjs +29 -0
  133. package/scripts/setup-minikube.mjs +65 -0
  134. package/scripts/smoke.mjs +37 -0
  135. package/scripts/validate-doc-coverage.mjs +152 -0
  136. package/scripts/validate-package.mjs +93 -0
  137. package/scripts/validate-ui.mjs +236 -0
  138. package/src/agent-adapter-controller.js +169 -0
  139. package/src/agent-approval-controller.js +170 -0
  140. package/src/agent-context-bundles.js +242 -0
  141. package/src/agent-dispatch-controller.js +209 -0
  142. package/src/agent-gateway-config-controller.js +147 -0
  143. package/src/agent-memory-controller.js +357 -0
  144. package/src/agent-memory-import.js +327 -0
  145. package/src/agent-memory-query.js +292 -0
  146. package/src/agent-memory-repository-source-controller.js +255 -0
  147. package/src/agent-mux-client.js +280 -0
  148. package/src/agent-permission-review.js +250 -0
  149. package/src/agent-project-controller.js +117 -0
  150. package/src/agent-provider-config-controller.js +150 -0
  151. package/src/agent-secret-config-grant-controller.js +282 -0
  152. package/src/agent-session-transcript-controller.js +189 -0
  153. package/src/agent-stack-controller.js +347 -0
  154. package/src/agent-subagent-controller.js +160 -0
  155. package/src/agent-transport-binding-controller.js +121 -0
  156. package/src/agent-trigger-controller.js +321 -0
  157. package/src/agent-workspace-controller.js +447 -0
  158. package/src/agent-writeback-controller.js +302 -0
  159. package/src/api-controller.js +541 -0
  160. package/src/argocd-gitops.js +43 -0
  161. package/src/async-controller.js +207 -0
  162. package/src/audit-controller.js +191 -0
  163. package/src/auth.js +307 -0
  164. package/src/component-catalog.js +41 -0
  165. package/src/control-plane.js +136 -0
  166. package/src/controller-client.js +50 -0
  167. package/src/controller-ui.js +551 -0
  168. package/src/data-plane.js +178 -0
  169. package/src/event-bus.js +61 -0
  170. package/src/external/conflict-controller.js +225 -0
  171. package/src/external/github/auth.js +96 -0
  172. package/src/external/github/cicd.js +180 -0
  173. package/src/external/github/git-forge.js +240 -0
  174. package/src/external/github/index.js +144 -0
  175. package/src/external/github/issue-tracking.js +163 -0
  176. package/src/external/provider-adapter.js +161 -0
  177. package/src/external/provider-resource-factory.js +161 -0
  178. package/src/external/sync-controller.js +235 -0
  179. package/src/external/webhook-controller.js +144 -0
  180. package/src/external/write-controller.js +283 -0
  181. package/src/gitea-backend.js +95 -0
  182. package/src/gitea-service.js +173 -0
  183. package/src/handoff.js +98 -0
  184. package/src/hooks-events.js +63 -0
  185. package/src/http-server.js +377 -0
  186. package/src/identity-policy.js +86 -0
  187. package/src/index.js +55 -0
  188. package/src/kubernetes-controller-async.js +511 -0
  189. package/src/kubernetes-controller.js +878 -0
  190. package/src/kubernetes-resource-gateway.js +48 -0
  191. package/src/operations.js +112 -0
  192. package/src/org-scoping.js +5 -0
  193. package/src/resource-model.js +221 -0
  194. package/src/runners-ci.js +48 -0
  195. package/src/runtime.js +196 -0
  196. package/src/snapshot-cache.js +157 -0
  197. package/src/web-ui.js +40 -0
  198. package/tests/agent-adapter-controller.test.js +361 -0
  199. package/tests/agent-approval-controller.test.js +173 -0
  200. package/tests/agent-context-bundles.test.js +278 -0
  201. package/tests/agent-dispatch-controller.test.js +315 -0
  202. package/tests/agent-gateway-config-controller.test.js +386 -0
  203. package/tests/agent-memory-controller.test.js +308 -0
  204. package/tests/agent-memory-import-snapshot.test.js +477 -0
  205. package/tests/agent-memory-query.test.js +404 -0
  206. package/tests/agent-memory-repository-source.test.js +514 -0
  207. package/tests/agent-mux-client.test.js +204 -0
  208. package/tests/agent-permission-review-v2.test.js +317 -0
  209. package/tests/agent-permission-review.test.js +209 -0
  210. package/tests/agent-project-controller.test.js +302 -0
  211. package/tests/agent-provider-config-controller.test.js +376 -0
  212. package/tests/agent-resources.test.js +228 -0
  213. package/tests/agent-secret-config-grant.test.js +231 -0
  214. package/tests/agent-session-transcript-controller.test.js +499 -0
  215. package/tests/agent-stack-controller.test.js +221 -0
  216. package/tests/agent-subagent-controller.test.js +201 -0
  217. package/tests/agent-transport-binding-controller.test.js +294 -0
  218. package/tests/agent-trigger-controller.test.js +211 -0
  219. package/tests/agent-trigger-routes.test.js +190 -0
  220. package/tests/agent-trigger-sources.test.js +245 -0
  221. package/tests/agent-workspace-controller.test.js +181 -0
  222. package/tests/agent-writeback.test.js +292 -0
  223. package/tests/approval-persistence.test.js +171 -0
  224. package/tests/async-controller.test.js +252 -0
  225. package/tests/audit-controller.test.js +227 -0
  226. package/tests/deployment.test.js +396 -0
  227. package/tests/e2e/lifecycle.test.js +117 -0
  228. package/tests/external-github-forge.test.js +560 -0
  229. package/tests/external-github-issues-cicd.test.js +520 -0
  230. package/tests/external-integration.test.js +470 -0
  231. package/tests/external-persistence.test.js +340 -0
  232. package/tests/external-provider-adapter.test.js +365 -0
  233. package/tests/external-resource-model.test.js +215 -0
  234. package/tests/external-webhook-sync.test.js +287 -0
  235. package/tests/external-write-conflict.test.js +353 -0
  236. package/tests/gitea-service.test.js +253 -0
  237. package/tests/health-check-real.test.js +165 -0
  238. package/tests/integration/full-flow.test.js +266 -0
  239. package/tests/krate.test.js +727 -0
  240. package/tests/memory-search-wiring.test.js +270 -0
  241. package/tests/org-scoping.test.js +687 -0
  242. package/tests/session-cookie-hmac.test.js +151 -0
  243. package/tests/snapshot-performance.test.js +247 -0
  244. package/tests/sse-events.test.js +107 -0
  245. package/tests/workspace-volumes.test.js +312 -0
  246. package/tests/writeback-persistence.test.js +207 -0
@@ -0,0 +1,327 @@
1
+ /**
2
+ * Agent Memory Import & Snapshot — Slice 2.3c
3
+ *
4
+ * Provides:
5
+ * - parseJournalForImport: parse a babysitter .a5c journal into a summary-only import payload
6
+ * - validateMemoryImport: validate an AgentRunMemoryImport spec
7
+ * - createMemorySnapshot: create a dispatch-time memory snapshot pinning record refs
8
+ * - validateMemorySnapshot: validate an AgentMemorySnapshot
9
+ * - validateOntology: validate an AgentMemoryOntology spec
10
+ * - getOntologyNodeKinds: return nodeKinds array from an ontology spec
11
+ * - getOntologyEdgeKinds: return edgeKinds array from an ontology spec
12
+ *
13
+ * Boundary: agent-memory-import
14
+ * - owns: journal parsing, summary extraction, snapshot creation, ontology validation
15
+ * - delegatesTo: (none — pure data transformation)
16
+ * - mustNotOwn: persistence, HTTP routing, Kubernetes resources, secret handling, git operations
17
+ */
18
+
19
+ import { randomBytes } from 'node:crypto';
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Journal parsing
23
+ // ---------------------------------------------------------------------------
24
+
25
+ /**
26
+ * Parse a babysitter .a5c journal array into a summary-only import payload.
27
+ * Returns structural metadata only — no raw task content, no arbitrary effect payloads.
28
+ *
29
+ * @param {Array} journal - Array of journal event objects
30
+ * @returns {{ summary: object, keyEvents: Array, effectSummary: object }}
31
+ */
32
+ export function parseJournalForImport(journal) {
33
+ if (!Array.isArray(journal) || journal.length === 0) {
34
+ return {
35
+ summary: {
36
+ runId: null,
37
+ processId: null,
38
+ eventCount: 0,
39
+ durationMs: 0,
40
+ runStatus: null,
41
+ },
42
+ keyEvents: [],
43
+ effectSummary: {
44
+ successCount: 0,
45
+ failureCount: 0,
46
+ effectKinds: [],
47
+ },
48
+ };
49
+ }
50
+
51
+ // Extract run metadata from run_start event
52
+ const runStart = journal.find(e => e.type === 'run_start');
53
+ const runEnd = journal.find(e => e.type === 'run_end');
54
+
55
+ const runId = runStart?.runId ?? null;
56
+ const processId = runStart?.processId ?? null;
57
+ const runStatus = runEnd?.status ?? null;
58
+
59
+ // Compute duration from timestamps
60
+ let durationMs = 0;
61
+ if (runStart?.timestamp && runEnd?.timestamp) {
62
+ const startMs = Date.parse(runStart.timestamp);
63
+ const endMs = Date.parse(runEnd.timestamp);
64
+ if (!isNaN(startMs) && !isNaN(endMs) && endMs >= startMs) {
65
+ durationMs = endMs - startMs;
66
+ }
67
+ }
68
+
69
+ // Extract key events (structural, no raw content)
70
+ const keyEvents = [];
71
+ let successCount = 0;
72
+ let failureCount = 0;
73
+ const effectKindSet = new Set();
74
+
75
+ for (const event of journal) {
76
+ switch (event.type) {
77
+ case 'run_start':
78
+ keyEvents.push({
79
+ type: 'run_start',
80
+ runId: event.runId ?? null,
81
+ processId: event.processId ?? null,
82
+ timestamp: event.timestamp ?? null,
83
+ });
84
+ break;
85
+
86
+ case 'task_completed': {
87
+ const keyEvent = {
88
+ type: 'task_completed',
89
+ taskId: event.taskId ?? null,
90
+ title: event.title ?? null,
91
+ status: event.status ?? null,
92
+ timestamp: event.timestamp ?? null,
93
+ };
94
+ // Strip raw effect to summary-only (kind + result only)
95
+ if (event.effect && typeof event.effect === 'object') {
96
+ keyEvent.effect = {
97
+ kind: event.effect.kind ?? null,
98
+ result: event.effect.result ?? null,
99
+ };
100
+ if (event.effect.kind) effectKindSet.add(event.effect.kind);
101
+ if (event.effect.result === 'success') successCount++;
102
+ else if (event.effect.result === 'failure') failureCount++;
103
+ } else if (event.status === 'success') {
104
+ successCount++;
105
+ } else if (event.status === 'failure') {
106
+ failureCount++;
107
+ }
108
+ keyEvents.push(keyEvent);
109
+ break;
110
+ }
111
+
112
+ case 'breakpoint':
113
+ keyEvents.push({
114
+ type: 'breakpoint',
115
+ reason: event.reason ?? null,
116
+ timestamp: event.timestamp ?? null,
117
+ });
118
+ break;
119
+
120
+ case 'run_end':
121
+ keyEvents.push({
122
+ type: 'run_end',
123
+ status: event.status ?? null,
124
+ timestamp: event.timestamp ?? null,
125
+ });
126
+ break;
127
+
128
+ default:
129
+ // Include other event types with only structural metadata
130
+ keyEvents.push({
131
+ type: event.type,
132
+ timestamp: event.timestamp ?? null,
133
+ });
134
+ break;
135
+ }
136
+ }
137
+
138
+ return {
139
+ summary: {
140
+ runId,
141
+ processId,
142
+ eventCount: journal.length,
143
+ durationMs,
144
+ runStatus,
145
+ },
146
+ keyEvents,
147
+ effectSummary: {
148
+ successCount,
149
+ failureCount,
150
+ effectKinds: Array.from(effectKindSet),
151
+ },
152
+ };
153
+ }
154
+
155
+ // ---------------------------------------------------------------------------
156
+ // validateMemoryImport
157
+ // ---------------------------------------------------------------------------
158
+
159
+ /**
160
+ * Validate an AgentRunMemoryImport spec.
161
+ *
162
+ * @param {object} importSpec
163
+ * @returns {{ valid: boolean, errors: string[] }}
164
+ */
165
+ export function validateMemoryImport(importSpec) {
166
+ const errors = [];
167
+
168
+ if (!importSpec || typeof importSpec !== 'object') {
169
+ return { valid: false, errors: ['importSpec must be a non-null object'] };
170
+ }
171
+
172
+ if (!importSpec.name || typeof importSpec.name !== 'string' || importSpec.name.trim() === '') {
173
+ errors.push('name is required and must be a non-empty string');
174
+ }
175
+
176
+ if (!importSpec.organizationRef || typeof importSpec.organizationRef !== 'string' || importSpec.organizationRef.trim() === '') {
177
+ errors.push('organizationRef is required and must be a non-empty string');
178
+ }
179
+
180
+ if (!importSpec.runId || typeof importSpec.runId !== 'string' || importSpec.runId.trim() === '') {
181
+ errors.push('runId is required and must be a non-empty string');
182
+ }
183
+
184
+ return { valid: errors.length === 0, errors };
185
+ }
186
+
187
+ // ---------------------------------------------------------------------------
188
+ // createMemorySnapshot
189
+ // ---------------------------------------------------------------------------
190
+
191
+ /**
192
+ * Create a dispatch-time memory snapshot pinning record refs.
193
+ *
194
+ * @param {object} params
195
+ * @param {string} params.sessionRef - The agent session reference (required)
196
+ * @param {string} params.organizationRef - Organization reference
197
+ * @param {Array} params.recordRefs - Array of record reference strings
198
+ * @param {object} [params.queryCriteria] - Optional query criteria used to select records
199
+ * @returns {object} snapshot
200
+ */
201
+ export function createMemorySnapshot({ sessionRef, organizationRef, recordRefs = [], queryCriteria }) {
202
+ const snapshotId = `snap-${randomBytes(8).toString('hex')}`;
203
+ const createdAt = new Date().toISOString();
204
+
205
+ const snapshot = {
206
+ snapshotId,
207
+ sessionRef: sessionRef ?? null,
208
+ organizationRef: organizationRef ?? null,
209
+ recordRefs: Array.isArray(recordRefs) ? [...recordRefs] : [],
210
+ createdAt,
211
+ };
212
+
213
+ if (queryCriteria !== undefined && queryCriteria !== null) {
214
+ snapshot.queryCriteria = { ...queryCriteria };
215
+ }
216
+
217
+ return snapshot;
218
+ }
219
+
220
+ // ---------------------------------------------------------------------------
221
+ // validateMemorySnapshot
222
+ // ---------------------------------------------------------------------------
223
+
224
+ /**
225
+ * Validate an AgentMemorySnapshot object.
226
+ *
227
+ * @param {object} snapshot
228
+ * @returns {{ valid: boolean, errors: string[] }}
229
+ */
230
+ export function validateMemorySnapshot(snapshot) {
231
+ const errors = [];
232
+
233
+ if (!snapshot || typeof snapshot !== 'object') {
234
+ return { valid: false, errors: ['snapshot must be a non-null object'] };
235
+ }
236
+
237
+ if (!snapshot.snapshotId || typeof snapshot.snapshotId !== 'string' || snapshot.snapshotId.trim() === '') {
238
+ errors.push('snapshotId is required and must be a non-empty string');
239
+ }
240
+
241
+ if (!snapshot.sessionRef || typeof snapshot.sessionRef !== 'string' || snapshot.sessionRef.trim() === '') {
242
+ errors.push('sessionRef is required and must be a non-empty string');
243
+ }
244
+
245
+ return { valid: errors.length === 0, errors };
246
+ }
247
+
248
+ // ---------------------------------------------------------------------------
249
+ // validateOntology (standalone)
250
+ // ---------------------------------------------------------------------------
251
+
252
+ /**
253
+ * Validate an AgentMemoryOntology spec.
254
+ *
255
+ * @param {object} ontologySpec
256
+ * @returns {{ valid: boolean, errors: string[] }}
257
+ */
258
+ export function validateOntology(ontologySpec) {
259
+ const errors = [];
260
+
261
+ if (!ontologySpec || typeof ontologySpec !== 'object') {
262
+ return { valid: false, errors: ['ontologySpec must be a non-null object'] };
263
+ }
264
+
265
+ if (!ontologySpec.name || typeof ontologySpec.name !== 'string' || ontologySpec.name.trim() === '') {
266
+ errors.push('name is required and must be a non-empty string');
267
+ }
268
+
269
+ if (!ontologySpec.organizationRef || typeof ontologySpec.organizationRef !== 'string' || ontologySpec.organizationRef.trim() === '') {
270
+ errors.push('organizationRef is required and must be a non-empty string');
271
+ }
272
+
273
+ // nodeKinds must be a non-empty array
274
+ const nodeKinds = ontologySpec.nodeKinds;
275
+ if (!Array.isArray(nodeKinds) || nodeKinds.length === 0) {
276
+ errors.push('nodeKinds must be a non-empty array');
277
+ } else {
278
+ // Check for duplicate nodeKind names
279
+ const seen = new Set();
280
+ for (const nk of nodeKinds) {
281
+ const kindName = nk?.name;
282
+ if (kindName !== undefined && kindName !== null) {
283
+ if (seen.has(kindName)) {
284
+ errors.push(`Duplicate nodeKind name: "${kindName}"`);
285
+ } else {
286
+ seen.add(kindName);
287
+ }
288
+ }
289
+ }
290
+ }
291
+
292
+ return { valid: errors.length === 0, errors };
293
+ }
294
+
295
+ // ---------------------------------------------------------------------------
296
+ // getOntologyNodeKinds
297
+ // ---------------------------------------------------------------------------
298
+
299
+ /**
300
+ * Return the nodeKinds array from an ontology spec.
301
+ *
302
+ * @param {object} ontologySpec
303
+ * @returns {Array}
304
+ */
305
+ export function getOntologyNodeKinds(ontologySpec) {
306
+ if (!ontologySpec || !Array.isArray(ontologySpec.nodeKinds)) {
307
+ return [];
308
+ }
309
+ return [...ontologySpec.nodeKinds];
310
+ }
311
+
312
+ // ---------------------------------------------------------------------------
313
+ // getOntologyEdgeKinds
314
+ // ---------------------------------------------------------------------------
315
+
316
+ /**
317
+ * Return the edgeKinds array from an ontology spec.
318
+ *
319
+ * @param {object} ontologySpec
320
+ * @returns {Array}
321
+ */
322
+ export function getOntologyEdgeKinds(ontologySpec) {
323
+ if (!ontologySpec || !Array.isArray(ontologySpec.edgeKinds)) {
324
+ return [];
325
+ }
326
+ return [...ontologySpec.edgeKinds];
327
+ }
@@ -0,0 +1,292 @@
1
+ /**
2
+ * Agent Memory Query Engine
3
+ *
4
+ * Standalone query engine for in-memory graph traversal and document grep.
5
+ * No external dependencies — operates purely on plain JS data structures.
6
+ *
7
+ * Boundary: agent-memory-query
8
+ * - owns: graph traversal, relevance scoring, text grep, context extraction, combined query execution
9
+ * - delegatesTo: (none — pure data transformation)
10
+ * - mustNotOwn: persistence, HTTP routing, Kubernetes resources, secret handling
11
+ */
12
+
13
+ export const AGENT_MEMORY_QUERY_BOUNDARY = {
14
+ role: 'agent-memory-query',
15
+ scope: 'In-memory graph traversal and full-text grep query execution for agent memory documents',
16
+ owns: ['graph traversal', 'nodeKind filtering', 'edge following', 'relevance scoring', 'full-text grep', 'context extraction', 'combined query execution'],
17
+ delegatesTo: [],
18
+ mustNotOwn: ['persistence', 'HTTP routing', 'Kubernetes resources', 'secret handling'],
19
+ };
20
+
21
+ const VALID_MODES = ['graph-only', 'grep-only', 'graph-and-grep'];
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // Graph query
25
+ // ---------------------------------------------------------------------------
26
+
27
+ /**
28
+ * Execute a graph query over a set of records.
29
+ *
30
+ * @param {object} params
31
+ * @param {Array} params.records - Array of graph records. Each record has { id, nodeKind, attributes, edges }.
32
+ * @param {Array} [params.edges] - Optional flat edges array { source, target, kind }. If provided these
33
+ * supplement per-record edges during traversal.
34
+ * @param {string} params.query - Search text (required, must be non-empty).
35
+ * @param {Array} [params.kinds] - nodeKind filter. Empty array means "no filter".
36
+ * @param {number} [params.depth] - Edge-follow depth (default 1).
37
+ * @returns {{ matches: Array, totalMatches: number }}
38
+ */
39
+ export function queryGraph({ records = [], edges = [], query, kinds = [], depth = 1 }) {
40
+ if (query === undefined || query === null) {
41
+ throw new Error('queryGraph: query text is required');
42
+ }
43
+ if (typeof query !== 'string' || query.trim() === '') {
44
+ throw new Error('queryGraph: query text must be a non-empty string');
45
+ }
46
+
47
+ // Build adjacency index from flat edges + per-record edges
48
+ const adjacency = buildAdjacency(records, edges);
49
+
50
+ // Filter by nodeKind
51
+ let candidates = records;
52
+ if (kinds.length > 0) {
53
+ candidates = candidates.filter(r => kinds.includes(r.nodeKind));
54
+ }
55
+
56
+ const lowerQuery = query.toLowerCase();
57
+ const matches = [];
58
+
59
+ for (const record of candidates) {
60
+ const score = scoreRecord(record, lowerQuery);
61
+ if (score === 0) continue;
62
+
63
+ const followedEdges = followEdges(record.id, adjacency, depth);
64
+
65
+ matches.push({
66
+ record: shallowClone(record),
67
+ score,
68
+ edges: followedEdges,
69
+ });
70
+ }
71
+
72
+ // Sort by descending score for deterministic output
73
+ matches.sort((a, b) => b.score - a.score);
74
+
75
+ return { matches, totalMatches: matches.length };
76
+ }
77
+
78
+ /**
79
+ * Execute a grep query over a set of documents.
80
+ *
81
+ * @param {object} params
82
+ * @param {Array} params.documents - Array of { path: string, content: string }.
83
+ * @param {string} params.query - Search text (required, must be non-empty).
84
+ * @param {Array} [params.paths] - Optional glob-style path filters.
85
+ * @param {number} [params.context] - Number of context lines above/below each match (default 1).
86
+ * @param {number} [params.maxMatches] - Maximum number of excerpts to return (default 25).
87
+ * @returns {{ excerpts: Array, totalMatches: number }}
88
+ */
89
+ export function queryGrep({ documents = [], query, paths = [], context = 1, maxMatches = 25 }) {
90
+ if (query === undefined || query === null) {
91
+ throw new Error('queryGrep: query text is required');
92
+ }
93
+ if (typeof query !== 'string' || query.trim() === '') {
94
+ throw new Error('queryGrep: query text must be a non-empty string');
95
+ }
96
+
97
+ // Filter by path globs
98
+ let filtered = documents;
99
+ if (paths.length > 0) {
100
+ filtered = documents.filter(doc => paths.some(p => globMatch(p, doc.path)));
101
+ }
102
+
103
+ const lowerQuery = query.toLowerCase();
104
+ const excerpts = [];
105
+
106
+ for (const doc of filtered) {
107
+ if (excerpts.length >= maxMatches) break;
108
+
109
+ const lines = String(doc.content || '').split('\n');
110
+ for (let i = 0; i < lines.length; i++) {
111
+ if (excerpts.length >= maxMatches) break;
112
+
113
+ const lowerLine = lines[i].toLowerCase();
114
+ if (!lowerLine.includes(lowerQuery)) continue;
115
+
116
+ // Build highlighted line: mark matched span
117
+ const matchStart = lowerLine.indexOf(lowerQuery);
118
+ const matchEnd = matchStart + query.length;
119
+ const highlighted =
120
+ lines[i].slice(0, matchStart) +
121
+ '**' + lines[i].slice(matchStart, matchEnd) + '**' +
122
+ lines[i].slice(matchEnd);
123
+
124
+ // Context lines
125
+ const ctxStart = Math.max(0, i - context);
126
+ const ctxEnd = Math.min(lines.length - 1, i + context);
127
+ const contextLines = lines.slice(ctxStart, ctxEnd + 1);
128
+
129
+ excerpts.push({
130
+ path: doc.path,
131
+ lineNumber: i + 1,
132
+ line: lines[i],
133
+ highlighted,
134
+ context: contextLines.join('\n'),
135
+ contextStart: ctxStart + 1,
136
+ contextEnd: ctxEnd + 1,
137
+ });
138
+ }
139
+ }
140
+
141
+ return { excerpts, totalMatches: excerpts.length };
142
+ }
143
+
144
+ /**
145
+ * Execute a combined query (graph + grep or either alone).
146
+ *
147
+ * @param {object} params
148
+ * @param {string} params.query - Search text (required).
149
+ * @param {string} [params.mode] - 'graph-and-grep' | 'graph-only' | 'grep-only' (default 'graph-and-grep').
150
+ * @param {Array} [params.records] - Graph records.
151
+ * @param {Array} [params.edges] - Flat edge list.
152
+ * @param {Array} [params.documents] - Grep documents.
153
+ * @param {object} [params.graphOptions] - Passed to queryGraph: { kinds, depth }.
154
+ * @param {object} [params.grepOptions] - Passed to queryGrep: { paths, context, maxMatches }.
155
+ * @returns {{ graph: object|null, grep: object|null, stats: object }}
156
+ */
157
+ export function queryMemory({
158
+ query,
159
+ mode = 'graph-and-grep',
160
+ records = [],
161
+ edges = [],
162
+ documents = [],
163
+ graphOptions = {},
164
+ grepOptions = {},
165
+ }) {
166
+ if (!VALID_MODES.includes(mode)) {
167
+ throw new Error(`queryMemory: invalid mode "${mode}". Must be one of: ${VALID_MODES.join(', ')}`);
168
+ }
169
+ if (query === undefined || query === null) {
170
+ throw new Error('queryMemory: query text is required');
171
+ }
172
+ if (typeof query !== 'string' || query.trim() === '') {
173
+ throw new Error('queryMemory: query text must be a non-empty string');
174
+ }
175
+
176
+ let graphResult = null;
177
+ let grepResult = null;
178
+
179
+ if (mode === 'graph-only' || mode === 'graph-and-grep') {
180
+ graphResult = queryGraph({
181
+ records,
182
+ edges,
183
+ query,
184
+ kinds: graphOptions.kinds || [],
185
+ depth: graphOptions.depth ?? 1,
186
+ });
187
+ }
188
+
189
+ if (mode === 'grep-only' || mode === 'graph-and-grep') {
190
+ grepResult = queryGrep({
191
+ documents,
192
+ query,
193
+ paths: grepOptions.paths || [],
194
+ context: grepOptions.context ?? 1,
195
+ maxMatches: grepOptions.maxMatches ?? 25,
196
+ });
197
+ }
198
+
199
+ const graphCount = graphResult ? graphResult.totalMatches : 0;
200
+ const grepCount = grepResult ? grepResult.totalMatches : 0;
201
+ const totalMatches = graphCount + grepCount;
202
+
203
+ return {
204
+ graph: graphResult,
205
+ grep: grepResult,
206
+ stats: {
207
+ mode,
208
+ totalMatches,
209
+ graphCount,
210
+ grepCount,
211
+ },
212
+ };
213
+ }
214
+
215
+ // ---------------------------------------------------------------------------
216
+ // Internal helpers
217
+ // ---------------------------------------------------------------------------
218
+
219
+ /**
220
+ * Build adjacency map: nodeId -> [{ target, kind, source }]
221
+ * Merges per-record edges with a flat edge list.
222
+ */
223
+ function buildAdjacency(records, flatEdges) {
224
+ const adj = new Map();
225
+
226
+ for (const record of records) {
227
+ if (!adj.has(record.id)) adj.set(record.id, []);
228
+ for (const edge of record.edges || []) {
229
+ adj.get(record.id).push({ ...edge, source: record.id });
230
+ }
231
+ }
232
+
233
+ for (const edge of flatEdges) {
234
+ const src = edge.source;
235
+ if (!adj.has(src)) adj.set(src, []);
236
+ adj.get(src).push({ ...edge });
237
+ }
238
+
239
+ return adj;
240
+ }
241
+
242
+ /**
243
+ * Follow edges from a start node up to `maxDepth` hops.
244
+ * Returns a flat array of edge objects encountered.
245
+ */
246
+ function followEdges(startId, adjacency, maxDepth) {
247
+ if (maxDepth <= 0) return [];
248
+
249
+ const visited = new Set([startId]);
250
+ const collected = [];
251
+ let frontier = [startId];
252
+
253
+ for (let d = 0; d < maxDepth; d++) {
254
+ const nextFrontier = [];
255
+ for (const nodeId of frontier) {
256
+ for (const edge of adjacency.get(nodeId) || []) {
257
+ if (visited.has(edge.target)) continue;
258
+ visited.add(edge.target);
259
+ collected.push({ ...edge });
260
+ nextFrontier.push(edge.target);
261
+ }
262
+ }
263
+ frontier = nextFrontier;
264
+ if (frontier.length === 0) break;
265
+ }
266
+
267
+ return collected;
268
+ }
269
+
270
+ /**
271
+ * Score a record against a lowercase query string.
272
+ * Returns 0 (no match), 1 (attribute match), or 2 (id match).
273
+ */
274
+ function scoreRecord(record, lowerQuery) {
275
+ const id = String(record.id || '').toLowerCase();
276
+ const attrs = JSON.stringify(record.attributes || {}).toLowerCase();
277
+
278
+ if (id.includes(lowerQuery)) return 2;
279
+ if (attrs.includes(lowerQuery)) return 1;
280
+ return 0;
281
+ }
282
+
283
+ /** Minimal shallow clone to avoid mutation of inputs */
284
+ function shallowClone(obj) {
285
+ return Object.assign(Object.create(null), obj);
286
+ }
287
+
288
+ /** Simple glob match: * matches any sequence of characters */
289
+ function globMatch(pattern, path) {
290
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*');
291
+ return new RegExp(`^${escaped}$`).test(path);
292
+ }