@synth-deploy/server 0.1.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 (317) hide show
  1. package/dist/agent/debrief-retention.d.ts +12 -0
  2. package/dist/agent/debrief-retention.d.ts.map +1 -0
  3. package/dist/agent/debrief-retention.js +27 -0
  4. package/dist/agent/debrief-retention.js.map +1 -0
  5. package/dist/agent/envoy-client.d.ts +216 -0
  6. package/dist/agent/envoy-client.d.ts.map +1 -0
  7. package/dist/agent/envoy-client.js +266 -0
  8. package/dist/agent/envoy-client.js.map +1 -0
  9. package/dist/agent/envoy-registry.d.ts +102 -0
  10. package/dist/agent/envoy-registry.d.ts.map +1 -0
  11. package/dist/agent/envoy-registry.js +319 -0
  12. package/dist/agent/envoy-registry.js.map +1 -0
  13. package/dist/agent/health-checker.d.ts +39 -0
  14. package/dist/agent/health-checker.d.ts.map +1 -0
  15. package/dist/agent/health-checker.js +49 -0
  16. package/dist/agent/health-checker.js.map +1 -0
  17. package/dist/agent/mcp-client-manager.d.ts +36 -0
  18. package/dist/agent/mcp-client-manager.d.ts.map +1 -0
  19. package/dist/agent/mcp-client-manager.js +106 -0
  20. package/dist/agent/mcp-client-manager.js.map +1 -0
  21. package/dist/agent/stale-deployment-detector.d.ts +15 -0
  22. package/dist/agent/stale-deployment-detector.d.ts.map +1 -0
  23. package/dist/agent/stale-deployment-detector.js +50 -0
  24. package/dist/agent/stale-deployment-detector.js.map +1 -0
  25. package/dist/agent/step-runner.d.ts +31 -0
  26. package/dist/agent/step-runner.d.ts.map +1 -0
  27. package/dist/agent/step-runner.js +80 -0
  28. package/dist/agent/step-runner.js.map +1 -0
  29. package/dist/agent/synth-agent.d.ts +168 -0
  30. package/dist/agent/synth-agent.d.ts.map +1 -0
  31. package/dist/agent/synth-agent.js +1195 -0
  32. package/dist/agent/synth-agent.js.map +1 -0
  33. package/dist/api/agent.d.ts +36 -0
  34. package/dist/api/agent.d.ts.map +1 -0
  35. package/dist/api/agent.js +867 -0
  36. package/dist/api/agent.js.map +1 -0
  37. package/dist/api/api-keys.d.ts +4 -0
  38. package/dist/api/api-keys.d.ts.map +1 -0
  39. package/dist/api/api-keys.js +118 -0
  40. package/dist/api/api-keys.js.map +1 -0
  41. package/dist/api/artifacts.d.ts +5 -0
  42. package/dist/api/artifacts.d.ts.map +1 -0
  43. package/dist/api/artifacts.js +142 -0
  44. package/dist/api/artifacts.js.map +1 -0
  45. package/dist/api/auth.d.ts +4 -0
  46. package/dist/api/auth.d.ts.map +1 -0
  47. package/dist/api/auth.js +280 -0
  48. package/dist/api/auth.js.map +1 -0
  49. package/dist/api/deployments.d.ts +11 -0
  50. package/dist/api/deployments.d.ts.map +1 -0
  51. package/dist/api/deployments.js +1098 -0
  52. package/dist/api/deployments.js.map +1 -0
  53. package/dist/api/environments.d.ts +5 -0
  54. package/dist/api/environments.d.ts.map +1 -0
  55. package/dist/api/environments.js +69 -0
  56. package/dist/api/environments.js.map +1 -0
  57. package/dist/api/envoy-reports.d.ts +17 -0
  58. package/dist/api/envoy-reports.d.ts.map +1 -0
  59. package/dist/api/envoy-reports.js +138 -0
  60. package/dist/api/envoy-reports.js.map +1 -0
  61. package/dist/api/envoys.d.ts +5 -0
  62. package/dist/api/envoys.d.ts.map +1 -0
  63. package/dist/api/envoys.js +192 -0
  64. package/dist/api/envoys.js.map +1 -0
  65. package/dist/api/fleet.d.ts +11 -0
  66. package/dist/api/fleet.d.ts.map +1 -0
  67. package/dist/api/fleet.js +394 -0
  68. package/dist/api/fleet.js.map +1 -0
  69. package/dist/api/graph.d.ts +8 -0
  70. package/dist/api/graph.d.ts.map +1 -0
  71. package/dist/api/graph.js +355 -0
  72. package/dist/api/graph.js.map +1 -0
  73. package/dist/api/health.d.ts +20 -0
  74. package/dist/api/health.d.ts.map +1 -0
  75. package/dist/api/health.js +248 -0
  76. package/dist/api/health.js.map +1 -0
  77. package/dist/api/idp-schemas.d.ts +41 -0
  78. package/dist/api/idp-schemas.d.ts.map +1 -0
  79. package/dist/api/idp-schemas.js +17 -0
  80. package/dist/api/idp-schemas.js.map +1 -0
  81. package/dist/api/idp.d.ts +6 -0
  82. package/dist/api/idp.d.ts.map +1 -0
  83. package/dist/api/idp.js +620 -0
  84. package/dist/api/idp.js.map +1 -0
  85. package/dist/api/intake.d.ts +10 -0
  86. package/dist/api/intake.d.ts.map +1 -0
  87. package/dist/api/intake.js +418 -0
  88. package/dist/api/intake.js.map +1 -0
  89. package/dist/api/partitions.d.ts +5 -0
  90. package/dist/api/partitions.d.ts.map +1 -0
  91. package/dist/api/partitions.js +113 -0
  92. package/dist/api/partitions.js.map +1 -0
  93. package/dist/api/progress-event-store.d.ts +62 -0
  94. package/dist/api/progress-event-store.d.ts.map +1 -0
  95. package/dist/api/progress-event-store.js +118 -0
  96. package/dist/api/progress-event-store.js.map +1 -0
  97. package/dist/api/schemas.d.ts +1000 -0
  98. package/dist/api/schemas.d.ts.map +1 -0
  99. package/dist/api/schemas.js +328 -0
  100. package/dist/api/schemas.js.map +1 -0
  101. package/dist/api/security-boundaries.d.ts +4 -0
  102. package/dist/api/security-boundaries.d.ts.map +1 -0
  103. package/dist/api/security-boundaries.js +32 -0
  104. package/dist/api/security-boundaries.js.map +1 -0
  105. package/dist/api/settings.d.ts +4 -0
  106. package/dist/api/settings.d.ts.map +1 -0
  107. package/dist/api/settings.js +99 -0
  108. package/dist/api/settings.js.map +1 -0
  109. package/dist/api/system.d.ts +75 -0
  110. package/dist/api/system.d.ts.map +1 -0
  111. package/dist/api/system.js +558 -0
  112. package/dist/api/system.js.map +1 -0
  113. package/dist/api/telemetry.d.ts +4 -0
  114. package/dist/api/telemetry.d.ts.map +1 -0
  115. package/dist/api/telemetry.js +24 -0
  116. package/dist/api/telemetry.js.map +1 -0
  117. package/dist/api/users.d.ts +4 -0
  118. package/dist/api/users.d.ts.map +1 -0
  119. package/dist/api/users.js +173 -0
  120. package/dist/api/users.js.map +1 -0
  121. package/dist/archive-unpacker.d.ts +24 -0
  122. package/dist/archive-unpacker.d.ts.map +1 -0
  123. package/dist/archive-unpacker.js +239 -0
  124. package/dist/archive-unpacker.js.map +1 -0
  125. package/dist/artifact-analyzer.d.ts +59 -0
  126. package/dist/artifact-analyzer.d.ts.map +1 -0
  127. package/dist/artifact-analyzer.js +334 -0
  128. package/dist/artifact-analyzer.js.map +1 -0
  129. package/dist/auth/idp/index.d.ts +9 -0
  130. package/dist/auth/idp/index.d.ts.map +1 -0
  131. package/dist/auth/idp/index.js +5 -0
  132. package/dist/auth/idp/index.js.map +1 -0
  133. package/dist/auth/idp/ldap.d.ts +56 -0
  134. package/dist/auth/idp/ldap.d.ts.map +1 -0
  135. package/dist/auth/idp/ldap.js +276 -0
  136. package/dist/auth/idp/ldap.js.map +1 -0
  137. package/dist/auth/idp/oidc.d.ts +27 -0
  138. package/dist/auth/idp/oidc.d.ts.map +1 -0
  139. package/dist/auth/idp/oidc.js +97 -0
  140. package/dist/auth/idp/oidc.js.map +1 -0
  141. package/dist/auth/idp/role-mapping.d.ts +9 -0
  142. package/dist/auth/idp/role-mapping.d.ts.map +1 -0
  143. package/dist/auth/idp/role-mapping.js +16 -0
  144. package/dist/auth/idp/role-mapping.js.map +1 -0
  145. package/dist/auth/idp/saml.d.ts +40 -0
  146. package/dist/auth/idp/saml.d.ts.map +1 -0
  147. package/dist/auth/idp/saml.js +117 -0
  148. package/dist/auth/idp/saml.js.map +1 -0
  149. package/dist/auth/idp/types.d.ts +23 -0
  150. package/dist/auth/idp/types.d.ts.map +1 -0
  151. package/dist/auth/idp/types.js +2 -0
  152. package/dist/auth/idp/types.js.map +1 -0
  153. package/dist/fleet/fleet-executor.d.ts +35 -0
  154. package/dist/fleet/fleet-executor.d.ts.map +1 -0
  155. package/dist/fleet/fleet-executor.js +228 -0
  156. package/dist/fleet/fleet-executor.js.map +1 -0
  157. package/dist/fleet/fleet-store.d.ts +13 -0
  158. package/dist/fleet/fleet-store.d.ts.map +1 -0
  159. package/dist/fleet/fleet-store.js +13 -0
  160. package/dist/fleet/fleet-store.js.map +1 -0
  161. package/dist/fleet/index.d.ts +5 -0
  162. package/dist/fleet/index.d.ts.map +1 -0
  163. package/dist/fleet/index.js +4 -0
  164. package/dist/fleet/index.js.map +1 -0
  165. package/dist/fleet/representative-selector.d.ts +15 -0
  166. package/dist/fleet/representative-selector.d.ts.map +1 -0
  167. package/dist/fleet/representative-selector.js +71 -0
  168. package/dist/fleet/representative-selector.js.map +1 -0
  169. package/dist/graph/graph-executor.d.ts +36 -0
  170. package/dist/graph/graph-executor.d.ts.map +1 -0
  171. package/dist/graph/graph-executor.js +348 -0
  172. package/dist/graph/graph-executor.js.map +1 -0
  173. package/dist/graph/graph-inference.d.ts +22 -0
  174. package/dist/graph/graph-inference.d.ts.map +1 -0
  175. package/dist/graph/graph-inference.js +149 -0
  176. package/dist/graph/graph-inference.js.map +1 -0
  177. package/dist/graph/graph-store.d.ts +12 -0
  178. package/dist/graph/graph-store.d.ts.map +1 -0
  179. package/dist/graph/graph-store.js +61 -0
  180. package/dist/graph/graph-store.js.map +1 -0
  181. package/dist/graph/index.d.ts +5 -0
  182. package/dist/graph/index.d.ts.map +1 -0
  183. package/dist/graph/index.js +4 -0
  184. package/dist/graph/index.js.map +1 -0
  185. package/dist/index.d.ts +2 -0
  186. package/dist/index.d.ts.map +1 -0
  187. package/dist/index.js +837 -0
  188. package/dist/index.js.map +1 -0
  189. package/dist/intake/index.d.ts +6 -0
  190. package/dist/intake/index.d.ts.map +1 -0
  191. package/dist/intake/index.js +5 -0
  192. package/dist/intake/index.js.map +1 -0
  193. package/dist/intake/intake-processor.d.ts +17 -0
  194. package/dist/intake/intake-processor.d.ts.map +1 -0
  195. package/dist/intake/intake-processor.js +99 -0
  196. package/dist/intake/intake-processor.js.map +1 -0
  197. package/dist/intake/intake-store.d.ts +7 -0
  198. package/dist/intake/intake-store.d.ts.map +1 -0
  199. package/dist/intake/intake-store.js +7 -0
  200. package/dist/intake/intake-store.js.map +1 -0
  201. package/dist/intake/registry-poller.d.ts +41 -0
  202. package/dist/intake/registry-poller.d.ts.map +1 -0
  203. package/dist/intake/registry-poller.js +202 -0
  204. package/dist/intake/registry-poller.js.map +1 -0
  205. package/dist/intake/webhook-handlers.d.ts +37 -0
  206. package/dist/intake/webhook-handlers.d.ts.map +1 -0
  207. package/dist/intake/webhook-handlers.js +268 -0
  208. package/dist/intake/webhook-handlers.js.map +1 -0
  209. package/dist/logger.d.ts +5 -0
  210. package/dist/logger.d.ts.map +1 -0
  211. package/dist/logger.js +15 -0
  212. package/dist/logger.js.map +1 -0
  213. package/dist/mcp/resources.d.ts +9 -0
  214. package/dist/mcp/resources.d.ts.map +1 -0
  215. package/dist/mcp/resources.js +72 -0
  216. package/dist/mcp/resources.js.map +1 -0
  217. package/dist/mcp/server.d.ts +15 -0
  218. package/dist/mcp/server.d.ts.map +1 -0
  219. package/dist/mcp/server.js +20 -0
  220. package/dist/mcp/server.js.map +1 -0
  221. package/dist/mcp/tools.d.ts +9 -0
  222. package/dist/mcp/tools.d.ts.map +1 -0
  223. package/dist/mcp/tools.js +88 -0
  224. package/dist/mcp/tools.js.map +1 -0
  225. package/dist/middleware/auth.d.ts +29 -0
  226. package/dist/middleware/auth.d.ts.map +1 -0
  227. package/dist/middleware/auth.js +76 -0
  228. package/dist/middleware/auth.js.map +1 -0
  229. package/dist/middleware/permissions.d.ts +13 -0
  230. package/dist/middleware/permissions.d.ts.map +1 -0
  231. package/dist/middleware/permissions.js +32 -0
  232. package/dist/middleware/permissions.js.map +1 -0
  233. package/dist/pattern-store.d.ts +104 -0
  234. package/dist/pattern-store.d.ts.map +1 -0
  235. package/dist/pattern-store.js +299 -0
  236. package/dist/pattern-store.js.map +1 -0
  237. package/package.json +54 -0
  238. package/src/agent/debrief-retention.ts +44 -0
  239. package/src/agent/envoy-client.ts +474 -0
  240. package/src/agent/envoy-registry.ts +384 -0
  241. package/src/agent/health-checker.ts +70 -0
  242. package/src/agent/mcp-client-manager.ts +131 -0
  243. package/src/agent/stale-deployment-detector.ts +79 -0
  244. package/src/agent/step-runner.ts +124 -0
  245. package/src/agent/synth-agent.ts +1567 -0
  246. package/src/api/agent.ts +1075 -0
  247. package/src/api/api-keys.ts +129 -0
  248. package/src/api/artifacts.ts +194 -0
  249. package/src/api/auth.ts +320 -0
  250. package/src/api/deployments.ts +1347 -0
  251. package/src/api/environments.ts +97 -0
  252. package/src/api/envoy-reports.ts +159 -0
  253. package/src/api/envoys.ts +237 -0
  254. package/src/api/fleet.ts +510 -0
  255. package/src/api/graph.ts +516 -0
  256. package/src/api/health.ts +311 -0
  257. package/src/api/idp-schemas.ts +19 -0
  258. package/src/api/idp.ts +735 -0
  259. package/src/api/intake.ts +537 -0
  260. package/src/api/partitions.ts +147 -0
  261. package/src/api/progress-event-store.ts +153 -0
  262. package/src/api/schemas.ts +376 -0
  263. package/src/api/security-boundaries.ts +54 -0
  264. package/src/api/settings.ts +118 -0
  265. package/src/api/system.ts +704 -0
  266. package/src/api/telemetry.ts +32 -0
  267. package/src/api/users.ts +210 -0
  268. package/src/archive-unpacker.ts +271 -0
  269. package/src/artifact-analyzer.ts +438 -0
  270. package/src/auth/idp/index.ts +8 -0
  271. package/src/auth/idp/ldap.ts +340 -0
  272. package/src/auth/idp/oidc.ts +117 -0
  273. package/src/auth/idp/role-mapping.ts +22 -0
  274. package/src/auth/idp/saml.ts +148 -0
  275. package/src/auth/idp/types.ts +22 -0
  276. package/src/fleet/fleet-executor.ts +309 -0
  277. package/src/fleet/fleet-store.ts +13 -0
  278. package/src/fleet/index.ts +4 -0
  279. package/src/fleet/representative-selector.ts +83 -0
  280. package/src/graph/graph-executor.ts +446 -0
  281. package/src/graph/graph-inference.ts +184 -0
  282. package/src/graph/graph-store.ts +75 -0
  283. package/src/graph/index.ts +4 -0
  284. package/src/index.ts +916 -0
  285. package/src/intake/index.ts +5 -0
  286. package/src/intake/intake-processor.ts +111 -0
  287. package/src/intake/intake-store.ts +7 -0
  288. package/src/intake/registry-poller.ts +230 -0
  289. package/src/intake/webhook-handlers.ts +328 -0
  290. package/src/logger.ts +19 -0
  291. package/src/mcp/resources.ts +98 -0
  292. package/src/mcp/server.ts +34 -0
  293. package/src/mcp/tools.ts +117 -0
  294. package/src/middleware/auth.ts +103 -0
  295. package/src/middleware/permissions.ts +35 -0
  296. package/src/pattern-store.ts +409 -0
  297. package/tests/agent-mode.test.ts +536 -0
  298. package/tests/api-handlers.test.ts +1245 -0
  299. package/tests/archive-unpacker.test.ts +179 -0
  300. package/tests/artifact-analyzer.test.ts +240 -0
  301. package/tests/auth-middleware.test.ts +189 -0
  302. package/tests/decision-diary.test.ts +957 -0
  303. package/tests/diary-reader.test.ts +782 -0
  304. package/tests/envoy-client.test.ts +342 -0
  305. package/tests/envoy-reports.test.ts +156 -0
  306. package/tests/mcp-tools.test.ts +213 -0
  307. package/tests/orchestration.test.ts +536 -0
  308. package/tests/partition-deletion.test.ts +143 -0
  309. package/tests/partition-isolation.test.ts +830 -0
  310. package/tests/pattern-store.test.ts +371 -0
  311. package/tests/rbac-enforcement.test.ts +409 -0
  312. package/tests/ssrf-validation.test.ts +56 -0
  313. package/tests/stale-deployment.test.ts +85 -0
  314. package/tests/step-runner.test.ts +308 -0
  315. package/tests/ui-journey.test.ts +330 -0
  316. package/tsconfig.json +11 -0
  317. package/vitest.config.ts +27 -0
@@ -0,0 +1,409 @@
1
+ import crypto from "node:crypto";
2
+ import Database from "better-sqlite3";
3
+
4
+ // ---------------------------------------------------------------------------
5
+ // Types
6
+ // ---------------------------------------------------------------------------
7
+
8
+ export interface CorrectionRecord {
9
+ timestamp: Date;
10
+ field: string;
11
+ from: string;
12
+ to: string;
13
+ artifactId: string;
14
+ }
15
+
16
+ export interface AnalysisPattern {
17
+ id: string;
18
+ source: string;
19
+ artifactType: string;
20
+ namePattern: string;
21
+ corrections: CorrectionRecord[];
22
+ derivedAnalysis: DerivedAnalysis;
23
+ confidence: number;
24
+ appliedCount: number;
25
+ createdAt: Date;
26
+ updatedAt: Date;
27
+ }
28
+
29
+ /**
30
+ * The subset of ArtifactAnalysis fields that can be derived from pattern
31
+ * corrections. Each field is optional — only corrected fields are stored.
32
+ */
33
+ export interface DerivedAnalysis {
34
+ summary?: string;
35
+ dependencies?: string[];
36
+ configurationExpectations?: Record<string, string>;
37
+ deploymentIntent?: string;
38
+ }
39
+
40
+ export interface PatternMatch {
41
+ pattern: AnalysisPattern;
42
+ /** "auto" when confidence >= 0.7 and >= 2 corrections; "suggest" otherwise */
43
+ mode: "auto" | "suggest";
44
+ }
45
+
46
+ // ---------------------------------------------------------------------------
47
+ // Confidence calculation
48
+ // ---------------------------------------------------------------------------
49
+
50
+ const INITIAL_CORRECTION_CONFIDENCE = 0.5;
51
+ const CONSISTENT_CORRECTION_BOOST = 0.15;
52
+ const CONTRADICTION_RESET_CONFIDENCE = 0.5;
53
+ const MAX_CONFIDENCE = 0.95;
54
+
55
+ /**
56
+ * Recomputes confidence from a correction history.
57
+ *
58
+ * - First correction: 0.5
59
+ * - Each subsequent correction that agrees with the current value: +0.15
60
+ * - A contradictory correction (same field, different `to` value as the
61
+ * last correction for that field) resets to 0.5
62
+ * - Capped at 0.95
63
+ */
64
+ export function computeConfidence(corrections: CorrectionRecord[]): number {
65
+ if (corrections.length === 0) return 0;
66
+
67
+ let confidence = INITIAL_CORRECTION_CONFIDENCE;
68
+ // Track the latest `to` value per field to detect contradictions
69
+ const latestByField = new Map<string, string>();
70
+
71
+ for (const c of corrections) {
72
+ const prev = latestByField.get(c.field);
73
+ if (prev !== undefined && prev !== c.to) {
74
+ // Contradictory correction — reset
75
+ confidence = CONTRADICTION_RESET_CONFIDENCE;
76
+ } else if (prev !== undefined) {
77
+ // Consistent — boost
78
+ confidence = Math.min(confidence + CONSISTENT_CORRECTION_BOOST, MAX_CONFIDENCE);
79
+ }
80
+ // First correction for this field — confidence stays at initial
81
+ latestByField.set(c.field, c.to);
82
+ }
83
+
84
+ return Math.round(confidence * 100) / 100; // avoid floating-point noise
85
+ }
86
+
87
+ // ---------------------------------------------------------------------------
88
+ // SQLite row shape
89
+ // ---------------------------------------------------------------------------
90
+
91
+ interface PatternRow {
92
+ id: string;
93
+ source: string;
94
+ artifact_type: string;
95
+ name_pattern: string;
96
+ corrections: string; // JSON
97
+ derived_analysis: string; // JSON
98
+ confidence: number;
99
+ applied_count: number;
100
+ created_at: string;
101
+ updated_at: string;
102
+ }
103
+
104
+ function rowToPattern(row: PatternRow): AnalysisPattern {
105
+ const corrections: CorrectionRecord[] = JSON.parse(row.corrections).map(
106
+ (c: { timestamp: string; field: string; from: string; to: string; artifactId: string }) => ({
107
+ ...c,
108
+ timestamp: new Date(c.timestamp),
109
+ }),
110
+ );
111
+
112
+ return {
113
+ id: row.id,
114
+ source: row.source,
115
+ artifactType: row.artifact_type,
116
+ namePattern: row.name_pattern,
117
+ corrections,
118
+ derivedAnalysis: JSON.parse(row.derived_analysis),
119
+ confidence: row.confidence,
120
+ appliedCount: row.applied_count,
121
+ createdAt: new Date(row.created_at),
122
+ updatedAt: new Date(row.updated_at),
123
+ };
124
+ }
125
+
126
+ // ---------------------------------------------------------------------------
127
+ // PatternStore
128
+ // ---------------------------------------------------------------------------
129
+
130
+ /**
131
+ * SQLite-backed storage for artifact analysis patterns.
132
+ *
133
+ * Patterns capture corrections users make to LLM-generated artifact analyses.
134
+ * When enough consistent corrections accumulate for a given source + type +
135
+ * name combination, the system can auto-apply the learned corrections instead
136
+ * of re-running LLM analysis.
137
+ */
138
+ export class PatternStore {
139
+ private db: Database.Database;
140
+ private stmts: {
141
+ insert: Database.Statement;
142
+ update: Database.Statement;
143
+ getById: Database.Statement;
144
+ findMatches: Database.Statement;
145
+ incrementApplied: Database.Statement;
146
+ listAll: Database.Statement;
147
+ deleteById: Database.Statement;
148
+ };
149
+
150
+ constructor(dbPath: string) {
151
+ this.db = new Database(dbPath);
152
+ this.db.pragma("journal_mode = WAL");
153
+ this.db.pragma("foreign_keys = ON");
154
+
155
+ this.db.exec(`
156
+ CREATE TABLE IF NOT EXISTS analysis_patterns (
157
+ id TEXT PRIMARY KEY,
158
+ source TEXT NOT NULL,
159
+ artifact_type TEXT NOT NULL,
160
+ name_pattern TEXT NOT NULL,
161
+ corrections TEXT NOT NULL DEFAULT '[]',
162
+ derived_analysis TEXT NOT NULL DEFAULT '{}',
163
+ confidence REAL NOT NULL DEFAULT 0,
164
+ applied_count INTEGER NOT NULL DEFAULT 0,
165
+ created_at TEXT NOT NULL,
166
+ updated_at TEXT NOT NULL
167
+ );
168
+
169
+ CREATE INDEX IF NOT EXISTS idx_patterns_source_type
170
+ ON analysis_patterns(source, artifact_type);
171
+ CREATE INDEX IF NOT EXISTS idx_patterns_confidence
172
+ ON analysis_patterns(confidence);
173
+ `);
174
+
175
+ this.stmts = {
176
+ insert: this.db.prepare(`
177
+ INSERT INTO analysis_patterns
178
+ (id, source, artifact_type, name_pattern, corrections, derived_analysis, confidence, applied_count, created_at, updated_at)
179
+ VALUES
180
+ (@id, @source, @artifact_type, @name_pattern, @corrections, @derived_analysis, @confidence, @applied_count, @created_at, @updated_at)
181
+ `),
182
+ update: this.db.prepare(`
183
+ UPDATE analysis_patterns
184
+ SET corrections = @corrections,
185
+ derived_analysis = @derived_analysis,
186
+ confidence = @confidence,
187
+ updated_at = @updated_at
188
+ WHERE id = @id
189
+ `),
190
+ getById: this.db.prepare(`SELECT * FROM analysis_patterns WHERE id = ?`),
191
+ findMatches: this.db.prepare(`
192
+ SELECT * FROM analysis_patterns
193
+ WHERE source = @source AND artifact_type = @artifact_type
194
+ ORDER BY confidence DESC
195
+ `),
196
+ incrementApplied: this.db.prepare(`
197
+ UPDATE analysis_patterns
198
+ SET applied_count = applied_count + 1, updated_at = @updated_at
199
+ WHERE id = @id
200
+ `),
201
+ listAll: this.db.prepare(`SELECT * FROM analysis_patterns ORDER BY updated_at DESC`),
202
+ deleteById: this.db.prepare(`DELETE FROM analysis_patterns WHERE id = ?`),
203
+ };
204
+ }
205
+
206
+ /**
207
+ * Record a correction against a pattern, creating the pattern if it doesn't
208
+ * exist. Returns the updated pattern.
209
+ */
210
+ recordCorrection(
211
+ key: { source: string; artifactType: string; namePattern: string },
212
+ correction: Omit<CorrectionRecord, "timestamp">,
213
+ ): AnalysisPattern {
214
+ const now = new Date();
215
+ const existing = this._findExact(key.source, key.artifactType, key.namePattern);
216
+
217
+ if (existing) {
218
+ const record: CorrectionRecord = { ...correction, timestamp: now };
219
+ const corrections = [...existing.corrections, record];
220
+ const confidence = computeConfidence(corrections);
221
+
222
+ // Rebuild derived analysis from latest corrections
223
+ const derivedAnalysis = this._buildDerivedAnalysis(corrections);
224
+
225
+ this.stmts.update.run({
226
+ id: existing.id,
227
+ corrections: JSON.stringify(corrections),
228
+ derived_analysis: JSON.stringify(derivedAnalysis),
229
+ confidence,
230
+ updated_at: now.toISOString(),
231
+ });
232
+
233
+ return {
234
+ ...existing,
235
+ corrections,
236
+ derivedAnalysis: derivedAnalysis,
237
+ confidence,
238
+ updatedAt: now,
239
+ };
240
+ }
241
+
242
+ // Create new pattern
243
+ const record: CorrectionRecord = { ...correction, timestamp: now };
244
+ const corrections = [record];
245
+ const confidence = computeConfidence(corrections);
246
+ const derivedAnalysis = this._buildDerivedAnalysis(corrections);
247
+ const id = crypto.randomUUID();
248
+
249
+ this.stmts.insert.run({
250
+ id,
251
+ source: key.source,
252
+ artifact_type: key.artifactType,
253
+ name_pattern: key.namePattern,
254
+ corrections: JSON.stringify(corrections),
255
+ derived_analysis: JSON.stringify(derivedAnalysis),
256
+ confidence,
257
+ applied_count: 0,
258
+ created_at: now.toISOString(),
259
+ updated_at: now.toISOString(),
260
+ });
261
+
262
+ return {
263
+ id,
264
+ source: key.source,
265
+ artifactType: key.artifactType,
266
+ namePattern: key.namePattern,
267
+ corrections,
268
+ derivedAnalysis: derivedAnalysis,
269
+ confidence,
270
+ appliedCount: 0,
271
+ createdAt: now,
272
+ updatedAt: now,
273
+ };
274
+ }
275
+
276
+ /**
277
+ * Find patterns matching a given artifact by source + type, then filter by
278
+ * name glob match. Returns patterns sorted by confidence (descending).
279
+ */
280
+ findMatches(source: string, artifactType: string, artifactName: string): PatternMatch[] {
281
+ const rows = this.stmts.findMatches.all({ source, artifact_type: artifactType }) as PatternRow[];
282
+ const matches: PatternMatch[] = [];
283
+
284
+ for (const row of rows) {
285
+ const pattern = rowToPattern(row);
286
+ if (this._globMatch(pattern.namePattern, artifactName)) {
287
+ const autoApply =
288
+ pattern.corrections.length >= 2 && pattern.confidence >= 0.7;
289
+ matches.push({
290
+ pattern,
291
+ mode: autoApply ? "auto" : "suggest",
292
+ });
293
+ }
294
+ }
295
+
296
+ return matches;
297
+ }
298
+
299
+ /**
300
+ * Record that a pattern was applied to an artifact.
301
+ */
302
+ recordApplication(patternId: string): void {
303
+ this.stmts.incrementApplied.run({
304
+ id: patternId,
305
+ updated_at: new Date().toISOString(),
306
+ });
307
+ }
308
+
309
+ /**
310
+ * Get a pattern by ID.
311
+ */
312
+ getById(id: string): AnalysisPattern | undefined {
313
+ const row = this.stmts.getById.get(id) as PatternRow | undefined;
314
+ return row ? rowToPattern(row) : undefined;
315
+ }
316
+
317
+ /**
318
+ * List all patterns, most recently updated first.
319
+ */
320
+ listAll(): AnalysisPattern[] {
321
+ const rows = this.stmts.listAll.all() as PatternRow[];
322
+ return rows.map(rowToPattern);
323
+ }
324
+
325
+ /**
326
+ * Delete a pattern.
327
+ */
328
+ delete(id: string): boolean {
329
+ const result = this.stmts.deleteById.run(id);
330
+ return result.changes > 0;
331
+ }
332
+
333
+ /**
334
+ * Close the database connection.
335
+ */
336
+ close(): void {
337
+ this.db.close();
338
+ }
339
+
340
+ // -------------------------------------------------------------------------
341
+ // Private helpers
342
+ // -------------------------------------------------------------------------
343
+
344
+ private _findExact(
345
+ source: string,
346
+ artifactType: string,
347
+ namePattern: string,
348
+ ): AnalysisPattern | undefined {
349
+ const rows = this.stmts.findMatches.all({ source, artifact_type: artifactType }) as PatternRow[];
350
+ for (const row of rows) {
351
+ if (row.name_pattern === namePattern) {
352
+ return rowToPattern(row);
353
+ }
354
+ }
355
+ return undefined;
356
+ }
357
+
358
+ /**
359
+ * Build a DerivedAnalysis by taking the latest correction `to` value for
360
+ * each known field. Supports: summary, deploymentIntent, dependencies
361
+ * (comma-separated), and configurationExpectations (key=value).
362
+ */
363
+ private _buildDerivedAnalysis(corrections: CorrectionRecord[]): DerivedAnalysis {
364
+ const derived: DerivedAnalysis = {};
365
+ const latestByField = new Map<string, string>();
366
+
367
+ for (const c of corrections) {
368
+ latestByField.set(c.field, c.to);
369
+ }
370
+
371
+ for (const [field, value] of latestByField) {
372
+ switch (field) {
373
+ case "summary":
374
+ derived.summary = value;
375
+ break;
376
+ case "deploymentIntent":
377
+ derived.deploymentIntent = value;
378
+ break;
379
+ case "dependencies":
380
+ derived.dependencies = value.split(",").map((d) => d.trim()).filter(Boolean);
381
+ break;
382
+ default:
383
+ // Treat as a configuration expectation
384
+ if (field.startsWith("config.")) {
385
+ if (!derived.configurationExpectations) {
386
+ derived.configurationExpectations = {};
387
+ }
388
+ derived.configurationExpectations[field.slice("config.".length)] = value;
389
+ }
390
+ break;
391
+ }
392
+ }
393
+
394
+ return derived;
395
+ }
396
+
397
+ /**
398
+ * Simple glob matching: supports `*` (any characters) and `?` (single char).
399
+ * Used to match artifact names against stored name patterns.
400
+ */
401
+ private _globMatch(pattern: string, name: string): boolean {
402
+ // Escape regex special chars except * and ?
403
+ const regexStr = pattern
404
+ .replace(/[.+^${}()|[\]\\]/g, "\\$&")
405
+ .replace(/\*/g, ".*")
406
+ .replace(/\?/g, ".");
407
+ return new RegExp(`^${regexStr}$`).test(name);
408
+ }
409
+ }