@fragments-sdk/cli 0.2.2

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 (259) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +106 -0
  3. package/dist/bin.d.ts +1 -0
  4. package/dist/bin.js +4783 -0
  5. package/dist/bin.js.map +1 -0
  6. package/dist/chunk-4FDQSGKX.js +786 -0
  7. package/dist/chunk-4FDQSGKX.js.map +1 -0
  8. package/dist/chunk-7H2MMGYG.js +369 -0
  9. package/dist/chunk-7H2MMGYG.js.map +1 -0
  10. package/dist/chunk-BSCG3IP7.js +619 -0
  11. package/dist/chunk-BSCG3IP7.js.map +1 -0
  12. package/dist/chunk-LY2CFFPY.js +898 -0
  13. package/dist/chunk-LY2CFFPY.js.map +1 -0
  14. package/dist/chunk-MUZ6CM66.js +6636 -0
  15. package/dist/chunk-MUZ6CM66.js.map +1 -0
  16. package/dist/chunk-OAENNG3G.js +1489 -0
  17. package/dist/chunk-OAENNG3G.js.map +1 -0
  18. package/dist/chunk-XHNKNI6J.js +235 -0
  19. package/dist/chunk-XHNKNI6J.js.map +1 -0
  20. package/dist/core-DWKLGY4N.js +68 -0
  21. package/dist/core-DWKLGY4N.js.map +1 -0
  22. package/dist/generate-4LQNJ7SX.js +249 -0
  23. package/dist/generate-4LQNJ7SX.js.map +1 -0
  24. package/dist/index.d.ts +775 -0
  25. package/dist/index.js +41 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/init-EMVI47QG.js +416 -0
  28. package/dist/init-EMVI47QG.js.map +1 -0
  29. package/dist/mcp-bin.d.ts +1 -0
  30. package/dist/mcp-bin.js +1117 -0
  31. package/dist/mcp-bin.js.map +1 -0
  32. package/dist/scan-4YPRF7FV.js +12 -0
  33. package/dist/scan-4YPRF7FV.js.map +1 -0
  34. package/dist/service-QSZMZJBJ.js +208 -0
  35. package/dist/service-QSZMZJBJ.js.map +1 -0
  36. package/dist/static-viewer-MIPGZ4Z7.js +12 -0
  37. package/dist/static-viewer-MIPGZ4Z7.js.map +1 -0
  38. package/dist/test-SQ5ZHXWU.js +1067 -0
  39. package/dist/test-SQ5ZHXWU.js.map +1 -0
  40. package/dist/tokens-HSGMYK64.js +173 -0
  41. package/dist/tokens-HSGMYK64.js.map +1 -0
  42. package/dist/viewer-YRF4SQE4.js +11101 -0
  43. package/dist/viewer-YRF4SQE4.js.map +1 -0
  44. package/package.json +107 -0
  45. package/src/ai.ts +266 -0
  46. package/src/analyze.ts +265 -0
  47. package/src/bin.ts +916 -0
  48. package/src/build.ts +248 -0
  49. package/src/commands/a11y.ts +302 -0
  50. package/src/commands/add.ts +313 -0
  51. package/src/commands/audit.ts +195 -0
  52. package/src/commands/baseline.ts +221 -0
  53. package/src/commands/build.ts +144 -0
  54. package/src/commands/compare.ts +337 -0
  55. package/src/commands/context.ts +107 -0
  56. package/src/commands/dev.ts +107 -0
  57. package/src/commands/enhance.ts +858 -0
  58. package/src/commands/generate.ts +391 -0
  59. package/src/commands/init.ts +531 -0
  60. package/src/commands/link/figma.ts +645 -0
  61. package/src/commands/link/index.ts +10 -0
  62. package/src/commands/link/storybook.ts +267 -0
  63. package/src/commands/list.ts +49 -0
  64. package/src/commands/metrics.ts +114 -0
  65. package/src/commands/reset.ts +242 -0
  66. package/src/commands/scan.ts +537 -0
  67. package/src/commands/storygen.ts +207 -0
  68. package/src/commands/tokens.ts +251 -0
  69. package/src/commands/validate.ts +93 -0
  70. package/src/commands/verify.ts +215 -0
  71. package/src/core/composition.test.ts +262 -0
  72. package/src/core/composition.ts +255 -0
  73. package/src/core/config.ts +84 -0
  74. package/src/core/constants.ts +111 -0
  75. package/src/core/context.ts +380 -0
  76. package/src/core/defineSegment.ts +137 -0
  77. package/src/core/discovery.ts +337 -0
  78. package/src/core/figma.ts +263 -0
  79. package/src/core/fragment-types.ts +214 -0
  80. package/src/core/generators/context.ts +389 -0
  81. package/src/core/generators/index.ts +23 -0
  82. package/src/core/generators/registry.ts +364 -0
  83. package/src/core/generators/typescript-extractor.ts +374 -0
  84. package/src/core/importAnalyzer.ts +217 -0
  85. package/src/core/index.ts +149 -0
  86. package/src/core/loader.ts +155 -0
  87. package/src/core/node.ts +63 -0
  88. package/src/core/parser.ts +551 -0
  89. package/src/core/previewLoader.ts +172 -0
  90. package/src/core/schema/fragment.schema.json +189 -0
  91. package/src/core/schema/registry.schema.json +137 -0
  92. package/src/core/schema.ts +182 -0
  93. package/src/core/storyAdapter.test.ts +571 -0
  94. package/src/core/storyAdapter.ts +761 -0
  95. package/src/core/token-types.ts +287 -0
  96. package/src/core/types.ts +754 -0
  97. package/src/diff.ts +323 -0
  98. package/src/index.ts +43 -0
  99. package/src/mcp/__tests__/projectFields.test.ts +130 -0
  100. package/src/mcp/bin.ts +36 -0
  101. package/src/mcp/index.ts +8 -0
  102. package/src/mcp/server.ts +1310 -0
  103. package/src/mcp/utils.ts +54 -0
  104. package/src/mcp-bin.ts +36 -0
  105. package/src/migrate/__tests__/argTypes/argTypes.test.ts +189 -0
  106. package/src/migrate/__tests__/args/args.test.ts +452 -0
  107. package/src/migrate/__tests__/meta/meta.test.ts +198 -0
  108. package/src/migrate/__tests__/stories/stories.test.ts +278 -0
  109. package/src/migrate/__tests__/utils/utils.test.ts +371 -0
  110. package/src/migrate/__tests__/values/values.test.ts +303 -0
  111. package/src/migrate/bin.ts +108 -0
  112. package/src/migrate/converter.ts +658 -0
  113. package/src/migrate/detect.ts +196 -0
  114. package/src/migrate/index.ts +45 -0
  115. package/src/migrate/migrate.ts +163 -0
  116. package/src/migrate/parser.ts +1136 -0
  117. package/src/migrate/report.ts +624 -0
  118. package/src/migrate/types.ts +169 -0
  119. package/src/screenshot.ts +249 -0
  120. package/src/service/__tests__/ast-utils.test.ts +426 -0
  121. package/src/service/__tests__/enhance-scanner.test.ts +200 -0
  122. package/src/service/__tests__/figma/figma.test.ts +652 -0
  123. package/src/service/__tests__/metrics-store.test.ts +409 -0
  124. package/src/service/__tests__/patch-generator.test.ts +186 -0
  125. package/src/service/__tests__/props-extractor.test.ts +365 -0
  126. package/src/service/__tests__/token-registry.test.ts +267 -0
  127. package/src/service/analytics.ts +659 -0
  128. package/src/service/ast-utils.ts +444 -0
  129. package/src/service/browser-pool.ts +339 -0
  130. package/src/service/capture.ts +267 -0
  131. package/src/service/diff.ts +279 -0
  132. package/src/service/enhance/aggregator.ts +489 -0
  133. package/src/service/enhance/cache.ts +275 -0
  134. package/src/service/enhance/codebase-scanner.ts +357 -0
  135. package/src/service/enhance/context-generator.ts +529 -0
  136. package/src/service/enhance/doc-extractor.ts +523 -0
  137. package/src/service/enhance/index.ts +131 -0
  138. package/src/service/enhance/props-extractor.ts +665 -0
  139. package/src/service/enhance/scanner.ts +445 -0
  140. package/src/service/enhance/storybook-parser.ts +552 -0
  141. package/src/service/enhance/types.ts +346 -0
  142. package/src/service/enhance/variant-renderer.ts +479 -0
  143. package/src/service/figma.ts +1008 -0
  144. package/src/service/index.ts +249 -0
  145. package/src/service/metrics-store.ts +333 -0
  146. package/src/service/patch-generator.ts +349 -0
  147. package/src/service/report.ts +854 -0
  148. package/src/service/storage.ts +401 -0
  149. package/src/service/token-fixes.ts +281 -0
  150. package/src/service/token-parser.ts +504 -0
  151. package/src/service/token-registry.ts +721 -0
  152. package/src/service/utils.ts +172 -0
  153. package/src/setup.ts +241 -0
  154. package/src/shared/command-wrapper.ts +81 -0
  155. package/src/shared/dev-server-client.ts +199 -0
  156. package/src/shared/index.ts +8 -0
  157. package/src/shared/segment-loader.ts +59 -0
  158. package/src/shared/types.ts +147 -0
  159. package/src/static-viewer.ts +715 -0
  160. package/src/test/discovery.ts +172 -0
  161. package/src/test/index.ts +281 -0
  162. package/src/test/reporters/console.ts +194 -0
  163. package/src/test/reporters/json.ts +190 -0
  164. package/src/test/reporters/junit.ts +186 -0
  165. package/src/test/runner.ts +598 -0
  166. package/src/test/types.ts +245 -0
  167. package/src/test/watch.ts +200 -0
  168. package/src/validators.ts +152 -0
  169. package/src/viewer/__tests__/jsx-parser.test.ts +502 -0
  170. package/src/viewer/__tests__/render-utils.test.ts +232 -0
  171. package/src/viewer/__tests__/style-utils.test.ts +404 -0
  172. package/src/viewer/bin.ts +86 -0
  173. package/src/viewer/cli/health.ts +256 -0
  174. package/src/viewer/cli/index.ts +33 -0
  175. package/src/viewer/cli/scan.ts +124 -0
  176. package/src/viewer/cli/utils.ts +174 -0
  177. package/src/viewer/components/AccessibilityPanel.tsx +1404 -0
  178. package/src/viewer/components/ActionCapture.tsx +172 -0
  179. package/src/viewer/components/ActionsPanel.tsx +371 -0
  180. package/src/viewer/components/App.tsx +638 -0
  181. package/src/viewer/components/BottomPanel.tsx +224 -0
  182. package/src/viewer/components/CodePanel.tsx +589 -0
  183. package/src/viewer/components/CommandPalette.tsx +336 -0
  184. package/src/viewer/components/ComponentGraph.tsx +394 -0
  185. package/src/viewer/components/ComponentHeader.tsx +85 -0
  186. package/src/viewer/components/ContractPanel.tsx +234 -0
  187. package/src/viewer/components/ErrorBoundary.tsx +85 -0
  188. package/src/viewer/components/FigmaEmbed.tsx +231 -0
  189. package/src/viewer/components/FragmentEditor.tsx +485 -0
  190. package/src/viewer/components/HealthDashboard.tsx +452 -0
  191. package/src/viewer/components/HmrStatusIndicator.tsx +71 -0
  192. package/src/viewer/components/Icons.tsx +417 -0
  193. package/src/viewer/components/InteractionsPanel.tsx +720 -0
  194. package/src/viewer/components/IsolatedPreviewFrame.tsx +321 -0
  195. package/src/viewer/components/IsolatedRender.tsx +111 -0
  196. package/src/viewer/components/KeyboardShortcutsHelp.tsx +89 -0
  197. package/src/viewer/components/LandingPage.tsx +441 -0
  198. package/src/viewer/components/Layout.tsx +22 -0
  199. package/src/viewer/components/LeftSidebar.tsx +391 -0
  200. package/src/viewer/components/MultiViewportPreview.tsx +429 -0
  201. package/src/viewer/components/PreviewArea.tsx +404 -0
  202. package/src/viewer/components/PreviewFrameHost.tsx +310 -0
  203. package/src/viewer/components/PreviewPane.tsx +150 -0
  204. package/src/viewer/components/PreviewToolbar.tsx +176 -0
  205. package/src/viewer/components/PropsEditor.tsx +512 -0
  206. package/src/viewer/components/PropsTable.tsx +98 -0
  207. package/src/viewer/components/RelationsSection.tsx +57 -0
  208. package/src/viewer/components/ResizablePanel.tsx +328 -0
  209. package/src/viewer/components/RightSidebar.tsx +118 -0
  210. package/src/viewer/components/ScreenshotButton.tsx +90 -0
  211. package/src/viewer/components/Sidebar.tsx +169 -0
  212. package/src/viewer/components/SkeletonLoader.tsx +156 -0
  213. package/src/viewer/components/StoryRenderer.tsx +128 -0
  214. package/src/viewer/components/ThemeProvider.tsx +96 -0
  215. package/src/viewer/components/Toast.tsx +67 -0
  216. package/src/viewer/components/TokenStylePanel.tsx +708 -0
  217. package/src/viewer/components/UsageSection.tsx +95 -0
  218. package/src/viewer/components/VariantMatrix.tsx +350 -0
  219. package/src/viewer/components/VariantRenderer.tsx +131 -0
  220. package/src/viewer/components/VariantTabs.tsx +84 -0
  221. package/src/viewer/components/ViewportSelector.tsx +165 -0
  222. package/src/viewer/components/_future/CreatePage.tsx +836 -0
  223. package/src/viewer/composition-renderer.ts +381 -0
  224. package/src/viewer/constants/index.ts +1 -0
  225. package/src/viewer/constants/ui.ts +185 -0
  226. package/src/viewer/entry.tsx +299 -0
  227. package/src/viewer/hooks/index.ts +2 -0
  228. package/src/viewer/hooks/useA11yCache.ts +383 -0
  229. package/src/viewer/hooks/useA11yService.ts +498 -0
  230. package/src/viewer/hooks/useActions.ts +138 -0
  231. package/src/viewer/hooks/useAppState.ts +124 -0
  232. package/src/viewer/hooks/useFigmaIntegration.ts +132 -0
  233. package/src/viewer/hooks/useHmrStatus.ts +109 -0
  234. package/src/viewer/hooks/useKeyboardShortcuts.ts +222 -0
  235. package/src/viewer/hooks/usePreviewBridge.ts +347 -0
  236. package/src/viewer/hooks/useScrollSpy.ts +78 -0
  237. package/src/viewer/hooks/useUrlState.ts +330 -0
  238. package/src/viewer/hooks/useViewSettings.ts +125 -0
  239. package/src/viewer/index.html +28 -0
  240. package/src/viewer/index.ts +14 -0
  241. package/src/viewer/intelligence/healthReport.ts +505 -0
  242. package/src/viewer/intelligence/styleDrift.ts +340 -0
  243. package/src/viewer/intelligence/usageScanner.ts +309 -0
  244. package/src/viewer/jsx-parser.ts +485 -0
  245. package/src/viewer/postcss.config.js +6 -0
  246. package/src/viewer/preview-frame-entry.tsx +25 -0
  247. package/src/viewer/preview-frame.html +109 -0
  248. package/src/viewer/render-template.html +68 -0
  249. package/src/viewer/render-utils.ts +170 -0
  250. package/src/viewer/server.ts +276 -0
  251. package/src/viewer/style-utils.ts +414 -0
  252. package/src/viewer/styles/globals.css +355 -0
  253. package/src/viewer/tailwind.config.js +37 -0
  254. package/src/viewer/types/a11y.ts +197 -0
  255. package/src/viewer/utils/a11y-fixes.ts +471 -0
  256. package/src/viewer/utils/actionExport.ts +372 -0
  257. package/src/viewer/utils/colorSchemes.ts +201 -0
  258. package/src/viewer/utils/detectRelationships.ts +256 -0
  259. package/src/viewer/vite-plugin.ts +2143 -0
@@ -0,0 +1,249 @@
1
+ /**
2
+ * @fragments/service
3
+ *
4
+ * Screenshot capture and verification service for Segments.
5
+ * Provides browser pool management, screenshot capture, and visual diff capabilities.
6
+ */
7
+
8
+ // Browser pool
9
+ export {
10
+ BrowserPool,
11
+ BrowserPoolError,
12
+ getSharedPool,
13
+ shutdownSharedPool,
14
+ type BrowserPoolConfig,
15
+ } from "./browser-pool.js";
16
+
17
+ // Capture engine
18
+ export {
19
+ CaptureEngine,
20
+ CaptureError,
21
+ createCaptureEngine,
22
+ type CaptureOptions,
23
+ } from "./capture.js";
24
+
25
+ // Storage manager
26
+ export {
27
+ StorageManager,
28
+ StorageError,
29
+ createStorageManager,
30
+ type StorageConfig,
31
+ } from "./storage.js";
32
+
33
+ // Diff engine
34
+ export {
35
+ DiffEngine,
36
+ DiffError,
37
+ createDiffEngine,
38
+ type DiffOptions,
39
+ } from "./diff.js";
40
+
41
+ // Figma client
42
+ export {
43
+ FigmaClient,
44
+ FigmaError,
45
+ createFigmaClient,
46
+ type FigmaClientConfig,
47
+ type FigmaImageResult,
48
+ type FigmaUrlParts,
49
+ type FigmaComponent,
50
+ type FigmaFileComponents,
51
+ type FigmaVariant,
52
+ type FigmaComponentSetWithVariants,
53
+ // Design property types (Phase 3)
54
+ type FigmaColor,
55
+ type FigmaFill,
56
+ type FigmaStroke,
57
+ type FigmaEffect,
58
+ type FigmaTypography,
59
+ type FigmaDesignProperties,
60
+ type CSSDesignProperties,
61
+ type StyleDiffResult,
62
+ } from "./figma.js";
63
+
64
+ // Utilities
65
+ export {
66
+ ServiceError,
67
+ Timer,
68
+ computeHash,
69
+ createDeferred,
70
+ sleep,
71
+ bufferToBase64Url,
72
+ base64UrlToBuffer,
73
+ sanitizeFilename,
74
+ buildScreenshotPath,
75
+ formatMs,
76
+ retry,
77
+ } from "./utils.js";
78
+
79
+ // Analytics engine
80
+ export {
81
+ analyzeDesignSystem,
82
+ getGrade,
83
+ getScoreColor,
84
+ type DesignSystemAnalytics,
85
+ type ComponentInventory,
86
+ type ComponentSummary,
87
+ type CoverageAnalytics,
88
+ type QualityAnalytics,
89
+ type DistributionAnalytics,
90
+ type Recommendation,
91
+ } from "./analytics.js";
92
+
93
+ // HTML Report generator
94
+ export { generateHtmlReport } from "./report.js";
95
+
96
+ // Token parser and registry
97
+ export {
98
+ parseTokenFile,
99
+ parseTokenFiles,
100
+ hexToRgb,
101
+ rgbToHex,
102
+ parseRgb,
103
+ normalizeColor,
104
+ } from "./token-parser.js";
105
+
106
+ export {
107
+ TokenRegistryManager,
108
+ getSharedTokenRegistry,
109
+ initializeSharedRegistry,
110
+ clearSharedRegistry,
111
+ createTokenRegistry,
112
+ } from "./token-registry.js";
113
+
114
+ // Token fix suggestions
115
+ export {
116
+ generateTokenFixes,
117
+ generateAIFixContext,
118
+ type TokenFixContext,
119
+ } from "./token-fixes.js";
120
+
121
+ // Patch generator
122
+ export {
123
+ generateTokenPatches,
124
+ generateCSSInJSPatches,
125
+ type Patch,
126
+ type PatchGenerationResult,
127
+ type PatchGenerationOptions,
128
+ } from "./patch-generator.js";
129
+
130
+ // AST utilities for style extraction and patching
131
+ export {
132
+ extractStyleLocations,
133
+ applyPatch,
134
+ applyPatches,
135
+ findStyleOccurrences,
136
+ type StyleLocation,
137
+ type PatchResult,
138
+ } from "./ast-utils.js";
139
+
140
+ // Metrics storage for compliance trends
141
+ export {
142
+ MetricsStore,
143
+ createMetricsStore,
144
+ type ComplianceSnapshot,
145
+ type MetricsTrend,
146
+ type MetricsQueryOptions,
147
+ } from "./metrics-store.js";
148
+
149
+ // Enhancement module - codebase analysis for AI documentation
150
+ export {
151
+ // Scanner
152
+ scanFileForImports,
153
+ scanFileForUsages,
154
+ scanFile,
155
+ scanCodebase,
156
+ incrementalScan,
157
+ getScanStats,
158
+ hasCachedAnalysis,
159
+ // Aggregator
160
+ aggregateComponentUsages,
161
+ aggregateAllUsages,
162
+ findCommonPropCombinations,
163
+ summarizePatternsForPrompt,
164
+ inferRelations,
165
+ inferAllRelations,
166
+ type ComponentRelation,
167
+ // Cache
168
+ loadCache,
169
+ saveCache,
170
+ createEmptyCache,
171
+ computeFileHash,
172
+ detectFileChanges,
173
+ getCacheStats,
174
+ // Doc Extractor
175
+ extractComponentDocs,
176
+ extractDocsFromSource,
177
+ findComponentSource,
178
+ extractAllComponentDocs,
179
+ // Storybook Parser
180
+ parseStoryFile,
181
+ parseStorySource,
182
+ findStoryFiles,
183
+ parseAllStories,
184
+ mergeStorybookIntoDoc,
185
+ type StoryExample,
186
+ type StorybookMeta,
187
+ type ArgType,
188
+ type ParsedStoryFile,
189
+ // AI Context Generator
190
+ generateComponentContext,
191
+ generateAIContextPackage,
192
+ generatePromptContext,
193
+ generateEnhancementSuggestions,
194
+ generateSystemPrompt,
195
+ generateUserPrompt,
196
+ isBoilerplate,
197
+ filterBoilerplate,
198
+ type ComponentContext,
199
+ type AIContextPackage,
200
+ type EnhancedProp,
201
+ // Props Extractor
202
+ extractPropsFromFile,
203
+ extractPropsFromSource,
204
+ extractPropsForComponent,
205
+ extractAllComponentProps,
206
+ convertToSegmentProps,
207
+ type ExtractedProp,
208
+ type PropsExtractionResult,
209
+ type PropsExtractionOptions,
210
+ // Variant Renderer
211
+ renderVariants,
212
+ renderAllComponentVariants,
213
+ checkStorybookRunning,
214
+ getStorybookStoryIds,
215
+ shutdownSharedPool as shutdownVariantPool,
216
+ type RenderedVariant,
217
+ type VariantRenderResult,
218
+ type VariantRenderOptions,
219
+ // Types
220
+ type ComponentUsage,
221
+ type UsageProps,
222
+ type ComponentImport,
223
+ type UsagePattern,
224
+ type FileContext,
225
+ type ComponentAnalysis,
226
+ type UsageAnalysis,
227
+ type AnalysisCache,
228
+ type ScanProgress,
229
+ type ScanOptions,
230
+ type FileChanges,
231
+ type ExtractedDocs,
232
+ type ComponentEnhancement,
233
+ type EnhancementResult,
234
+ } from "./enhance/index.js";
235
+
236
+ // Re-export types and constants from core for convenience
237
+ export { BRAND, DEFAULTS } from "../core/index.js";
238
+ export type {
239
+ Screenshot,
240
+ ScreenshotMetadata,
241
+ DiffResult,
242
+ BoundingBox,
243
+ BaselineInfo,
244
+ Manifest,
245
+ VerifyRequest,
246
+ VerifyResult,
247
+ Viewport,
248
+ Theme,
249
+ } from "../core/index.js";
@@ -0,0 +1,333 @@
1
+ /**
2
+ * Metrics Store
3
+ *
4
+ * Persists compliance metrics over time for trend analysis.
5
+ * Stores snapshots in .segments/metrics/ directory.
6
+ */
7
+
8
+ import { readFile, writeFile, mkdir, readdir } from "node:fs/promises";
9
+ import { join, dirname } from "node:path";
10
+ import { BRAND } from "../core/index.js";
11
+
12
+ /**
13
+ * A single compliance snapshot for a component
14
+ */
15
+ export interface ComplianceSnapshot {
16
+ /** ISO timestamp when snapshot was taken */
17
+ timestamp: string;
18
+
19
+ /** Component name */
20
+ component: string;
21
+
22
+ /** Compliance percentage (0-100) */
23
+ compliance: number;
24
+
25
+ /** Number of violations */
26
+ violations: number;
27
+
28
+ /** Detailed violation info */
29
+ violationDetails?: Array<{
30
+ property: string;
31
+ issue: string;
32
+ severity: "error" | "warning";
33
+ }>;
34
+
35
+ /** Git commit hash if available */
36
+ commitHash?: string;
37
+
38
+ /** Branch name if available */
39
+ branch?: string;
40
+ }
41
+
42
+ /**
43
+ * Aggregated metrics for trend analysis
44
+ */
45
+ export interface MetricsTrend {
46
+ /** Component name or 'system' for aggregate */
47
+ component: string;
48
+
49
+ /** Time period */
50
+ period: "day" | "week" | "month";
51
+
52
+ /** Data points */
53
+ dataPoints: Array<{
54
+ date: string;
55
+ compliance: number;
56
+ violations: number;
57
+ }>;
58
+
59
+ /** Trend direction */
60
+ trend: "improving" | "stable" | "declining";
61
+
62
+ /** Average compliance over period */
63
+ averageCompliance: number;
64
+ }
65
+
66
+ /**
67
+ * Options for metrics queries
68
+ */
69
+ export interface MetricsQueryOptions {
70
+ /** Number of days to look back (default: 30) */
71
+ days?: number;
72
+
73
+ /** Filter by component */
74
+ component?: string;
75
+
76
+ /** Group by period */
77
+ groupBy?: "day" | "week" | "month";
78
+ }
79
+
80
+ /**
81
+ * Metrics Store for persisting compliance data
82
+ */
83
+ export class MetricsStore {
84
+ private readonly projectRoot: string;
85
+ private readonly metricsDir: string;
86
+
87
+ constructor(projectRoot: string) {
88
+ this.projectRoot = projectRoot;
89
+ this.metricsDir = join(projectRoot, BRAND.dataDir, "metrics");
90
+ }
91
+
92
+ /**
93
+ * Save a compliance snapshot
94
+ */
95
+ async saveSnapshot(snapshot: ComplianceSnapshot): Promise<string> {
96
+ // Ensure metrics directory exists
97
+ await mkdir(this.metricsDir, { recursive: true });
98
+
99
+ // Create filename with timestamp and component
100
+ const date = new Date(snapshot.timestamp);
101
+ const dateStr = date.toISOString().split("T")[0]; // YYYY-MM-DD
102
+ const filename = `${dateStr}_${snapshot.component}.json`;
103
+ const filepath = join(this.metricsDir, filename);
104
+
105
+ // Append to existing file for the day or create new
106
+ let snapshots: ComplianceSnapshot[] = [];
107
+ try {
108
+ const existing = await readFile(filepath, "utf-8");
109
+ snapshots = JSON.parse(existing);
110
+ } catch {
111
+ // File doesn't exist, start fresh
112
+ }
113
+
114
+ snapshots.push(snapshot);
115
+
116
+ await writeFile(filepath, JSON.stringify(snapshots, null, 2));
117
+
118
+ return filepath;
119
+ }
120
+
121
+ /**
122
+ * Get compliance history for a component
123
+ */
124
+ async getHistory(
125
+ component: string,
126
+ options: MetricsQueryOptions = {}
127
+ ): Promise<ComplianceSnapshot[]> {
128
+ const { days = 30 } = options;
129
+
130
+ const snapshots: ComplianceSnapshot[] = [];
131
+ const cutoffDate = new Date();
132
+ cutoffDate.setDate(cutoffDate.getDate() - days);
133
+
134
+ try {
135
+ const files = await readdir(this.metricsDir);
136
+
137
+ for (const file of files) {
138
+ if (!file.endsWith(".json")) continue;
139
+
140
+ // Check if file is within date range
141
+ const dateStr = file.split("_")[0];
142
+ const fileDate = new Date(dateStr);
143
+ if (fileDate < cutoffDate) continue;
144
+
145
+ // Check if file matches component
146
+ if (component !== "all" && !file.includes(`_${component}.json`)) {
147
+ continue;
148
+ }
149
+
150
+ try {
151
+ const content = await readFile(join(this.metricsDir, file), "utf-8");
152
+ const fileSnapshots: ComplianceSnapshot[] = JSON.parse(content);
153
+
154
+ // Filter to matching component
155
+ const matching = component === "all"
156
+ ? fileSnapshots
157
+ : fileSnapshots.filter((s) => s.component === component);
158
+
159
+ snapshots.push(...matching);
160
+ } catch {
161
+ // Skip malformed files
162
+ }
163
+ }
164
+ } catch {
165
+ // Metrics directory doesn't exist
166
+ return [];
167
+ }
168
+
169
+ // Sort by timestamp
170
+ snapshots.sort(
171
+ (a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
172
+ );
173
+
174
+ return snapshots;
175
+ }
176
+
177
+ /**
178
+ * Get system-wide compliance history
179
+ */
180
+ async getSystemHistory(options: MetricsQueryOptions = {}): Promise<ComplianceSnapshot[]> {
181
+ return this.getHistory("all", options);
182
+ }
183
+
184
+ /**
185
+ * Calculate compliance trend for a component
186
+ */
187
+ async getTrend(
188
+ component: string,
189
+ options: MetricsQueryOptions = {}
190
+ ): Promise<MetricsTrend> {
191
+ const { days = 30, groupBy = "day" } = options;
192
+
193
+ const snapshots = await this.getHistory(component, { days });
194
+
195
+ // Group by period
196
+ const groups = new Map<string, ComplianceSnapshot[]>();
197
+
198
+ for (const snapshot of snapshots) {
199
+ const date = new Date(snapshot.timestamp);
200
+ let key: string;
201
+
202
+ switch (groupBy) {
203
+ case "week":
204
+ // Get ISO week
205
+ const weekStart = new Date(date);
206
+ weekStart.setDate(date.getDate() - date.getDay());
207
+ key = weekStart.toISOString().split("T")[0];
208
+ break;
209
+ case "month":
210
+ key = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}`;
211
+ break;
212
+ case "day":
213
+ default:
214
+ key = date.toISOString().split("T")[0];
215
+ break;
216
+ }
217
+
218
+ const existing = groups.get(key) || [];
219
+ existing.push(snapshot);
220
+ groups.set(key, existing);
221
+ }
222
+
223
+ // Calculate aggregates for each period
224
+ const dataPoints: MetricsTrend["dataPoints"] = [];
225
+
226
+ for (const [date, periodSnapshots] of groups) {
227
+ const avgCompliance =
228
+ periodSnapshots.reduce((sum, s) => sum + s.compliance, 0) /
229
+ periodSnapshots.length;
230
+ const totalViolations = periodSnapshots.reduce(
231
+ (sum, s) => sum + s.violations,
232
+ 0
233
+ );
234
+
235
+ dataPoints.push({
236
+ date,
237
+ compliance: Math.round(avgCompliance * 100) / 100,
238
+ violations: totalViolations,
239
+ });
240
+ }
241
+
242
+ // Sort by date
243
+ dataPoints.sort((a, b) => a.date.localeCompare(b.date));
244
+
245
+ // Determine trend
246
+ let trend: MetricsTrend["trend"] = "stable";
247
+ if (dataPoints.length >= 2) {
248
+ const first = dataPoints.slice(0, Math.ceil(dataPoints.length / 2));
249
+ const second = dataPoints.slice(Math.ceil(dataPoints.length / 2));
250
+
251
+ const firstAvg =
252
+ first.reduce((sum, d) => sum + d.compliance, 0) / first.length;
253
+ const secondAvg =
254
+ second.reduce((sum, d) => sum + d.compliance, 0) / second.length;
255
+
256
+ const diff = secondAvg - firstAvg;
257
+ if (diff > 2) trend = "improving";
258
+ else if (diff < -2) trend = "declining";
259
+ }
260
+
261
+ const averageCompliance =
262
+ dataPoints.length > 0
263
+ ? dataPoints.reduce((sum, d) => sum + d.compliance, 0) / dataPoints.length
264
+ : 0;
265
+
266
+ return {
267
+ component,
268
+ period: groupBy,
269
+ dataPoints,
270
+ trend,
271
+ averageCompliance: Math.round(averageCompliance * 100) / 100,
272
+ };
273
+ }
274
+
275
+ /**
276
+ * Generate an ASCII sparkline for terminal display
277
+ */
278
+ generateSparkline(dataPoints: Array<{ compliance: number }>): string {
279
+ if (dataPoints.length === 0) return "─";
280
+
281
+ const chars = " ▁▂▃▄▅▆▇█";
282
+ const values = dataPoints.map((d) => d.compliance);
283
+ const min = Math.min(...values);
284
+ const max = Math.max(...values);
285
+ const range = max - min || 1;
286
+
287
+ return values
288
+ .map((v) => {
289
+ const normalized = (v - min) / range;
290
+ const index = Math.round(normalized * (chars.length - 1));
291
+ return chars[index];
292
+ })
293
+ .join("");
294
+ }
295
+
296
+ /**
297
+ * Delete old metrics beyond retention period
298
+ */
299
+ async cleanup(retentionDays: number = 90): Promise<number> {
300
+ const cutoffDate = new Date();
301
+ cutoffDate.setDate(cutoffDate.getDate() - retentionDays);
302
+
303
+ let deleted = 0;
304
+
305
+ try {
306
+ const files = await readdir(this.metricsDir);
307
+ const { unlink } = await import("node:fs/promises");
308
+
309
+ for (const file of files) {
310
+ if (!file.endsWith(".json")) continue;
311
+
312
+ const dateStr = file.split("_")[0];
313
+ const fileDate = new Date(dateStr);
314
+
315
+ if (fileDate < cutoffDate) {
316
+ await unlink(join(this.metricsDir, file));
317
+ deleted++;
318
+ }
319
+ }
320
+ } catch {
321
+ // Metrics directory doesn't exist
322
+ }
323
+
324
+ return deleted;
325
+ }
326
+ }
327
+
328
+ /**
329
+ * Create a metrics store instance
330
+ */
331
+ export function createMetricsStore(projectRoot: string): MetricsStore {
332
+ return new MetricsStore(projectRoot);
333
+ }