@skillsmith/core 0.5.3 → 0.5.5

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 (276) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/src/activation/ActivationManager.d.ts +7 -0
  4. package/dist/src/activation/ActivationManager.d.ts.map +1 -1
  5. package/dist/src/activation/ActivationManager.js +13 -4
  6. package/dist/src/activation/ActivationManager.js.map +1 -1
  7. package/dist/src/analysis/adapters/python.d.ts +16 -11
  8. package/dist/src/analysis/adapters/python.d.ts.map +1 -1
  9. package/dist/src/analysis/adapters/python.js +46 -61
  10. package/dist/src/analysis/adapters/python.js.map +1 -1
  11. package/dist/src/analysis/router.test.d.ts +2 -0
  12. package/dist/src/analysis/router.test.d.ts.map +1 -0
  13. package/dist/src/analysis/router.test.js +411 -0
  14. package/dist/src/analysis/router.test.js.map +1 -0
  15. package/dist/src/analysis/tree-sitter/manager.d.ts.map +1 -1
  16. package/dist/src/analysis/tree-sitter/manager.js +12 -5
  17. package/dist/src/analysis/tree-sitter/manager.js.map +1 -1
  18. package/dist/src/analysis/tree-sitter/pythonExtractor.d.ts +45 -0
  19. package/dist/src/analysis/tree-sitter/pythonExtractor.d.ts.map +1 -0
  20. package/dist/src/analysis/tree-sitter/pythonExtractor.js +264 -0
  21. package/dist/src/analysis/tree-sitter/pythonExtractor.js.map +1 -0
  22. package/dist/src/analysis/tree-sitter/pythonExtractor.test.d.ts +12 -0
  23. package/dist/src/analysis/tree-sitter/pythonExtractor.test.d.ts.map +1 -0
  24. package/dist/src/analysis/tree-sitter/pythonExtractor.test.js +74 -0
  25. package/dist/src/analysis/tree-sitter/pythonExtractor.test.js.map +1 -0
  26. package/dist/src/analysis/tree-sitter/pythonIncremental.d.ts +93 -0
  27. package/dist/src/analysis/tree-sitter/pythonIncremental.d.ts.map +1 -0
  28. package/dist/src/analysis/tree-sitter/pythonIncremental.hardening.test.d.ts +22 -0
  29. package/dist/src/analysis/tree-sitter/pythonIncremental.hardening.test.d.ts.map +1 -0
  30. package/dist/src/analysis/tree-sitter/pythonIncremental.hardening.test.js +229 -0
  31. package/dist/src/analysis/tree-sitter/pythonIncremental.hardening.test.js.map +1 -0
  32. package/dist/src/analysis/tree-sitter/pythonIncremental.js +287 -0
  33. package/dist/src/analysis/tree-sitter/pythonIncremental.js.map +1 -0
  34. package/dist/src/analysis/tree-sitter/pythonIncremental.test.d.ts +17 -0
  35. package/dist/src/analysis/tree-sitter/pythonIncremental.test.d.ts.map +1 -0
  36. package/dist/src/analysis/tree-sitter/pythonIncremental.test.js +142 -0
  37. package/dist/src/analysis/tree-sitter/pythonIncremental.test.js.map +1 -0
  38. package/dist/src/analysis/tree-sitter/queries/python.d.ts +43 -0
  39. package/dist/src/analysis/tree-sitter/queries/python.d.ts.map +1 -0
  40. package/dist/src/analysis/tree-sitter/queries/python.js +88 -0
  41. package/dist/src/analysis/tree-sitter/queries/python.js.map +1 -0
  42. package/dist/src/analysis/tree-sitter/queryExtractionMatchesOrExceedsRegex.test.d.ts +13 -0
  43. package/dist/src/analysis/tree-sitter/queryExtractionMatchesOrExceedsRegex.test.d.ts.map +1 -0
  44. package/dist/src/analysis/tree-sitter/queryExtractionMatchesOrExceedsRegex.test.js +174 -0
  45. package/dist/src/analysis/tree-sitter/queryExtractionMatchesOrExceedsRegex.test.js.map +1 -0
  46. package/dist/src/analytics/ROIDashboardService.csv.d.ts +11 -0
  47. package/dist/src/analytics/ROIDashboardService.csv.d.ts.map +1 -0
  48. package/dist/src/analytics/ROIDashboardService.csv.js +43 -0
  49. package/dist/src/analytics/ROIDashboardService.csv.js.map +1 -0
  50. package/dist/src/analytics/ROIDashboardService.d.ts +64 -3
  51. package/dist/src/analytics/ROIDashboardService.d.ts.map +1 -1
  52. package/dist/src/analytics/ROIDashboardService.js +116 -45
  53. package/dist/src/analytics/ROIDashboardService.js.map +1 -1
  54. package/dist/src/api/client.d.ts +4 -66
  55. package/dist/src/api/client.d.ts.map +1 -1
  56. package/dist/src/api/client.events.d.ts.map +1 -1
  57. package/dist/src/api/client.events.js +10 -1
  58. package/dist/src/api/client.events.js.map +1 -1
  59. package/dist/src/api/client.js +40 -66
  60. package/dist/src/api/client.js.map +1 -1
  61. package/dist/src/api/client.token-refresh.d.ts +3 -0
  62. package/dist/src/api/client.token-refresh.d.ts.map +1 -0
  63. package/dist/src/api/client.token-refresh.js +19 -0
  64. package/dist/src/api/client.token-refresh.js.map +1 -0
  65. package/dist/src/api/client.token-refresh.test.d.ts +2 -0
  66. package/dist/src/api/client.token-refresh.test.d.ts.map +1 -0
  67. package/dist/src/api/client.token-refresh.test.js +73 -0
  68. package/dist/src/api/client.token-refresh.test.js.map +1 -0
  69. package/dist/src/api/client.types.d.ts +2 -0
  70. package/dist/src/api/client.types.d.ts.map +1 -1
  71. package/dist/src/api/index.d.ts +1 -1
  72. package/dist/src/api/index.d.ts.map +1 -1
  73. package/dist/src/api/index.js +1 -1
  74. package/dist/src/api/index.js.map +1 -1
  75. package/dist/src/api/schemas.d.ts +60 -60
  76. package/dist/src/api/schemas.d.ts.map +1 -1
  77. package/dist/src/benchmarks/incrementalParseBenchmark.d.ts +18 -0
  78. package/dist/src/benchmarks/incrementalParseBenchmark.d.ts.map +1 -0
  79. package/dist/src/benchmarks/incrementalParseBenchmark.js +121 -0
  80. package/dist/src/benchmarks/incrementalParseBenchmark.js.map +1 -0
  81. package/dist/src/billing/GDPRComplianceService.test.d.ts +2 -0
  82. package/dist/src/billing/GDPRComplianceService.test.d.ts.map +1 -0
  83. package/dist/src/billing/GDPRComplianceService.test.js +405 -0
  84. package/dist/src/billing/GDPRComplianceService.test.js.map +1 -0
  85. package/dist/src/config/index.d.ts +4 -0
  86. package/dist/src/config/index.d.ts.map +1 -1
  87. package/dist/src/config/index.js +6 -0
  88. package/dist/src/config/index.js.map +1 -1
  89. package/dist/src/config/token-credentials.d.ts +13 -0
  90. package/dist/src/config/token-credentials.d.ts.map +1 -0
  91. package/dist/src/config/token-credentials.js +126 -0
  92. package/dist/src/config/token-credentials.js.map +1 -0
  93. package/dist/src/config/token-credentials.test.d.ts +10 -0
  94. package/dist/src/config/token-credentials.test.d.ts.map +1 -0
  95. package/dist/src/config/token-credentials.test.js +91 -0
  96. package/dist/src/config/token-credentials.test.js.map +1 -0
  97. package/dist/src/index.d.ts +7 -4
  98. package/dist/src/index.d.ts.map +1 -1
  99. package/dist/src/index.js +6 -3
  100. package/dist/src/index.js.map +1 -1
  101. package/dist/src/indexer/SkillParser.coverage.test.d.ts +10 -0
  102. package/dist/src/indexer/SkillParser.coverage.test.d.ts.map +1 -0
  103. package/dist/src/indexer/SkillParser.coverage.test.js +76 -0
  104. package/dist/src/indexer/SkillParser.coverage.test.js.map +1 -0
  105. package/dist/src/indexer/SkillParser.test.d.ts +2 -0
  106. package/dist/src/indexer/SkillParser.test.d.ts.map +1 -0
  107. package/dist/src/indexer/SkillParser.test.js +375 -0
  108. package/dist/src/indexer/SkillParser.test.js.map +1 -0
  109. package/dist/src/scripts/__tests__/scan-imported-skills.test.js +34 -5
  110. package/dist/src/scripts/__tests__/scan-imported-skills.test.js.map +1 -1
  111. package/dist/src/scripts/github-import/blocklist.d.ts +65 -0
  112. package/dist/src/scripts/github-import/blocklist.d.ts.map +1 -0
  113. package/dist/src/scripts/github-import/blocklist.js +124 -0
  114. package/dist/src/scripts/github-import/blocklist.js.map +1 -0
  115. package/dist/src/scripts/github-import/index.d.ts +1 -0
  116. package/dist/src/scripts/github-import/index.d.ts.map +1 -1
  117. package/dist/src/scripts/github-import/index.js +3 -0
  118. package/dist/src/scripts/github-import/index.js.map +1 -1
  119. package/dist/src/scripts/github-import/signal-of-intent.d.ts +87 -0
  120. package/dist/src/scripts/github-import/signal-of-intent.d.ts.map +1 -0
  121. package/dist/src/scripts/github-import/signal-of-intent.js +213 -0
  122. package/dist/src/scripts/github-import/signal-of-intent.js.map +1 -0
  123. package/dist/src/scripts/github-import/types.d.ts +22 -0
  124. package/dist/src/scripts/github-import/types.d.ts.map +1 -1
  125. package/dist/src/scripts/github-import/types.js.map +1 -1
  126. package/dist/src/scripts/import-github-skills.js +73 -3
  127. package/dist/src/scripts/import-github-skills.js.map +1 -1
  128. package/dist/src/scripts/skill-scanner/allowlist.d.ts +38 -0
  129. package/dist/src/scripts/skill-scanner/allowlist.d.ts.map +1 -0
  130. package/dist/src/scripts/skill-scanner/allowlist.js +178 -0
  131. package/dist/src/scripts/skill-scanner/allowlist.js.map +1 -0
  132. package/dist/src/scripts/skill-scanner/scanner.d.ts +10 -2
  133. package/dist/src/scripts/skill-scanner/scanner.d.ts.map +1 -1
  134. package/dist/src/scripts/skill-scanner/scanner.js +15 -3
  135. package/dist/src/scripts/skill-scanner/scanner.js.map +1 -1
  136. package/dist/src/scripts/skill-scanner/trust-scorer.d.ts +20 -6
  137. package/dist/src/scripts/skill-scanner/trust-scorer.d.ts.map +1 -1
  138. package/dist/src/scripts/skill-scanner/trust-scorer.js +28 -9
  139. package/dist/src/scripts/skill-scanner/trust-scorer.js.map +1 -1
  140. package/dist/src/scripts/skill-scanner/types.d.ts +50 -0
  141. package/dist/src/scripts/skill-scanner/types.d.ts.map +1 -1
  142. package/dist/src/scripts/validation/types.d.ts +14 -24
  143. package/dist/src/scripts/validation/types.d.ts.map +1 -1
  144. package/dist/src/security/scanner/SecurityScanner.helpers.d.ts +18 -0
  145. package/dist/src/security/scanner/SecurityScanner.helpers.d.ts.map +1 -1
  146. package/dist/src/security/scanner/SecurityScanner.helpers.js +54 -6
  147. package/dist/src/security/scanner/SecurityScanner.helpers.js.map +1 -1
  148. package/dist/src/security/scanner/patterns.d.ts.map +1 -1
  149. package/dist/src/security/scanner/patterns.js +45 -5
  150. package/dist/src/security/scanner/patterns.js.map +1 -1
  151. package/dist/src/services/skill-config-schema.d.ts +4 -36
  152. package/dist/src/services/skill-config-schema.d.ts.map +1 -1
  153. package/dist/src/sources/LocalFilesystemAdapter.d.ts +104 -10
  154. package/dist/src/sources/LocalFilesystemAdapter.d.ts.map +1 -1
  155. package/dist/src/sources/LocalFilesystemAdapter.helpers.d.ts +92 -0
  156. package/dist/src/sources/LocalFilesystemAdapter.helpers.d.ts.map +1 -0
  157. package/dist/src/sources/LocalFilesystemAdapter.helpers.js +157 -0
  158. package/dist/src/sources/LocalFilesystemAdapter.helpers.js.map +1 -0
  159. package/dist/src/sources/LocalFilesystemAdapter.js +218 -159
  160. package/dist/src/sources/LocalFilesystemAdapter.js.map +1 -1
  161. package/dist/src/sources/LocalFilesystemAdapter.scan.d.ts +78 -0
  162. package/dist/src/sources/LocalFilesystemAdapter.scan.d.ts.map +1 -0
  163. package/dist/src/sources/LocalFilesystemAdapter.scan.js +118 -0
  164. package/dist/src/sources/LocalFilesystemAdapter.scan.js.map +1 -0
  165. package/dist/src/sources/index.d.ts +1 -1
  166. package/dist/src/sources/index.d.ts.map +1 -1
  167. package/dist/src/sources/index.js.map +1 -1
  168. package/dist/src/sources/types.d.ts +28 -0
  169. package/dist/src/sources/types.d.ts.map +1 -1
  170. package/dist/src/telemetry/tracer-imports.d.ts +13 -0
  171. package/dist/src/telemetry/tracer-imports.d.ts.map +1 -0
  172. package/dist/src/telemetry/tracer-imports.js +26 -0
  173. package/dist/src/telemetry/tracer-imports.js.map +1 -0
  174. package/dist/src/telemetry/tracer.d.ts.map +1 -1
  175. package/dist/src/telemetry/tracer.js +18 -21
  176. package/dist/src/telemetry/tracer.js.map +1 -1
  177. package/dist/src/utils/rate-limit.d.ts +39 -0
  178. package/dist/src/utils/rate-limit.d.ts.map +1 -0
  179. package/dist/src/utils/rate-limit.js +48 -0
  180. package/dist/src/utils/rate-limit.js.map +1 -0
  181. package/dist/src/utils/rate-limit.test.d.ts +11 -0
  182. package/dist/src/utils/rate-limit.test.d.ts.map +1 -0
  183. package/dist/src/utils/rate-limit.test.js +86 -0
  184. package/dist/src/utils/rate-limit.test.js.map +1 -0
  185. package/dist/src/webhooks/WebhookDeadLetterRepository.d.ts +178 -0
  186. package/dist/src/webhooks/WebhookDeadLetterRepository.d.ts.map +1 -0
  187. package/dist/src/webhooks/WebhookDeadLetterRepository.js +196 -0
  188. package/dist/src/webhooks/WebhookDeadLetterRepository.js.map +1 -0
  189. package/dist/src/webhooks/WebhookQueue.d.ts +1 -0
  190. package/dist/src/webhooks/WebhookQueue.d.ts.map +1 -1
  191. package/dist/src/webhooks/WebhookQueue.js +19 -0
  192. package/dist/src/webhooks/WebhookQueue.js.map +1 -1
  193. package/dist/src/webhooks/WebhookQueue.types.d.ts +11 -0
  194. package/dist/src/webhooks/WebhookQueue.types.d.ts.map +1 -1
  195. package/dist/src/webhooks/index.d.ts +1 -0
  196. package/dist/src/webhooks/index.d.ts.map +1 -1
  197. package/dist/src/webhooks/index.js +2 -0
  198. package/dist/src/webhooks/index.js.map +1 -1
  199. package/dist/src/webhooks/webhook-schemas.d.ts +117 -1212
  200. package/dist/src/webhooks/webhook-schemas.d.ts.map +1 -1
  201. package/dist/tests/ActivationManager.test.d.ts +13 -0
  202. package/dist/tests/ActivationManager.test.d.ts.map +1 -0
  203. package/dist/tests/ActivationManager.test.js +218 -0
  204. package/dist/tests/ActivationManager.test.js.map +1 -0
  205. package/dist/tests/LocalFilesystemAdapter.coverage.test.d.ts +13 -0
  206. package/dist/tests/LocalFilesystemAdapter.coverage.test.d.ts.map +1 -0
  207. package/dist/tests/LocalFilesystemAdapter.coverage.test.js +314 -0
  208. package/dist/tests/LocalFilesystemAdapter.coverage.test.js.map +1 -0
  209. package/dist/tests/LocalFilesystemAdapter.security.test.d.ts +18 -0
  210. package/dist/tests/LocalFilesystemAdapter.security.test.d.ts.map +1 -0
  211. package/dist/tests/LocalFilesystemAdapter.security.test.js +344 -0
  212. package/dist/tests/LocalFilesystemAdapter.security.test.js.map +1 -0
  213. package/dist/tests/LocalFilesystemAdapter.test.d.ts +12 -0
  214. package/dist/tests/LocalFilesystemAdapter.test.d.ts.map +1 -0
  215. package/dist/tests/LocalFilesystemAdapter.test.js +301 -0
  216. package/dist/tests/LocalFilesystemAdapter.test.js.map +1 -0
  217. package/dist/tests/ROIDashboardService.coverage.test.d.ts +9 -0
  218. package/dist/tests/ROIDashboardService.coverage.test.d.ts.map +1 -0
  219. package/dist/tests/ROIDashboardService.coverage.test.js +118 -0
  220. package/dist/tests/ROIDashboardService.coverage.test.js.map +1 -0
  221. package/dist/tests/ROIDashboardService.test.js +87 -0
  222. package/dist/tests/ROIDashboardService.test.js.map +1 -1
  223. package/dist/tests/ScraperAdapters.gitlab-coverage.test.d.ts +14 -0
  224. package/dist/tests/ScraperAdapters.gitlab-coverage.test.d.ts.map +1 -0
  225. package/dist/tests/ScraperAdapters.gitlab-coverage.test.js +169 -0
  226. package/dist/tests/ScraperAdapters.gitlab-coverage.test.js.map +1 -0
  227. package/dist/tests/ScraperAdapters.test.d.ts +5 -1
  228. package/dist/tests/ScraperAdapters.test.d.ts.map +1 -1
  229. package/dist/tests/ScraperAdapters.test.js +6 -336
  230. package/dist/tests/ScraperAdapters.test.js.map +1 -1
  231. package/dist/tests/WebhookDeadLetterRepository.test.d.ts +2 -0
  232. package/dist/tests/WebhookDeadLetterRepository.test.d.ts.map +1 -0
  233. package/dist/tests/WebhookDeadLetterRepository.test.js +333 -0
  234. package/dist/tests/WebhookDeadLetterRepository.test.js.map +1 -0
  235. package/dist/tests/WebhookHandler.test.js +93 -1
  236. package/dist/tests/WebhookHandler.test.js.map +1 -1
  237. package/dist/tests/WebhookQueue.coverage.test.d.ts +19 -0
  238. package/dist/tests/WebhookQueue.coverage.test.d.ts.map +1 -0
  239. package/dist/tests/WebhookQueue.coverage.test.js +190 -0
  240. package/dist/tests/WebhookQueue.coverage.test.js.map +1 -0
  241. package/dist/tests/api/client.events.test.d.ts +10 -0
  242. package/dist/tests/api/client.events.test.d.ts.map +1 -0
  243. package/dist/tests/api/client.events.test.js +85 -0
  244. package/dist/tests/api/client.events.test.js.map +1 -0
  245. package/dist/tests/billing/GDPRCompliance.test.d.ts +2 -2
  246. package/dist/tests/billing/GDPRCompliance.test.js +221 -36
  247. package/dist/tests/billing/GDPRCompliance.test.js.map +1 -1
  248. package/dist/tests/github-import/blocklist.test.d.ts +15 -0
  249. package/dist/tests/github-import/blocklist.test.d.ts.map +1 -0
  250. package/dist/tests/github-import/blocklist.test.js +182 -0
  251. package/dist/tests/github-import/blocklist.test.js.map +1 -0
  252. package/dist/tests/github-import/signal-of-intent.test.d.ts +15 -0
  253. package/dist/tests/github-import/signal-of-intent.test.d.ts.map +1 -0
  254. package/dist/tests/github-import/signal-of-intent.test.js +171 -0
  255. package/dist/tests/github-import/signal-of-intent.test.js.map +1 -0
  256. package/dist/tests/security/scanner-regression-guard.test.d.ts +12 -0
  257. package/dist/tests/security/scanner-regression-guard.test.d.ts.map +1 -1
  258. package/dist/tests/security/scanner-regression-guard.test.js +15 -3
  259. package/dist/tests/security/scanner-regression-guard.test.js.map +1 -1
  260. package/dist/tests/security/scanner-wave2-fixtures.test.d.ts +12 -0
  261. package/dist/tests/security/scanner-wave2-fixtures.test.d.ts.map +1 -0
  262. package/dist/tests/security/scanner-wave2-fixtures.test.js +173 -0
  263. package/dist/tests/security/scanner-wave2-fixtures.test.js.map +1 -0
  264. package/dist/tests/security.test.js +1 -0
  265. package/dist/tests/security.test.js.map +1 -1
  266. package/dist/tests/skill-scanner/allowlist.test.d.ts +16 -0
  267. package/dist/tests/skill-scanner/allowlist.test.d.ts.map +1 -0
  268. package/dist/tests/skill-scanner/allowlist.test.js +332 -0
  269. package/dist/tests/skill-scanner/allowlist.test.js.map +1 -0
  270. package/dist/tests/telemetry.test.js +126 -0
  271. package/dist/tests/telemetry.test.js.map +1 -1
  272. package/dist/tests/webhooks/WebhookDeadLetterRepository.test.d.ts +10 -0
  273. package/dist/tests/webhooks/WebhookDeadLetterRepository.test.d.ts.map +1 -0
  274. package/dist/tests/webhooks/WebhookDeadLetterRepository.test.js +109 -0
  275. package/dist/tests/webhooks/WebhookDeadLetterRepository.test.js.map +1 -0
  276. package/package.json +8 -3
@@ -0,0 +1,142 @@
1
+ /**
2
+ * SMI-4293: PythonIncrementalParser unit tests.
3
+ *
4
+ * Covers the six cases called out in the plan:
5
+ * 1. First parse caches the tree.
6
+ * 2. Unchanged content re-parse hits the cache (no re-parse).
7
+ * 3. Incremental edits re-use the previous tree via tree.edit().
8
+ * 4. LRU eviction at the configured max.
9
+ * 5. Corrupted cached tree falls back gracefully (returns null, adapter
10
+ * then falls back to regex).
11
+ * 6. Unsupported / unavailable grammar surfaces `isReady === false` and
12
+ * `parseSync` returns null.
13
+ *
14
+ * @see docs/internal/implementation/github-wave-5c-tree-sitter-incremental.md
15
+ */
16
+ import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
17
+ // Silence warn output from the SMI-4316 hardening paths; behavioural
18
+ // assertions live in pythonIncremental.hardening.test.ts.
19
+ vi.mock('../../utils/logger.js', () => ({
20
+ createLogger: () => ({
21
+ warn: vi.fn(),
22
+ error: vi.fn(),
23
+ info: vi.fn(),
24
+ debug: vi.fn(),
25
+ auditLog: vi.fn(),
26
+ securityLog: vi.fn(),
27
+ }),
28
+ }));
29
+ import { PythonIncrementalParser } from './pythonIncremental.js';
30
+ describe('PythonIncrementalParser', () => {
31
+ const parsers = [];
32
+ beforeAll(() => {
33
+ // No-op — each test creates its own parser so disposal is explicit.
34
+ });
35
+ function trackedParser(options = {}, loader) {
36
+ const p = loader
37
+ ? new PythonIncrementalParser(options, loader)
38
+ : new PythonIncrementalParser(options);
39
+ parsers.push(p);
40
+ return p;
41
+ }
42
+ afterAll(() => {
43
+ for (const p of parsers)
44
+ p.dispose();
45
+ });
46
+ // ------------------------------------------------------------------
47
+ // 1. First parse caches the tree
48
+ // ------------------------------------------------------------------
49
+ it('caches a tree after the first parse', async () => {
50
+ const parser = trackedParser();
51
+ const result = await parser.parse('def foo():\n pass\n', 'first.py');
52
+ expect(result).not.toBeNull();
53
+ expect(parser.cacheSize).toBe(1);
54
+ expect(result?.functions.map((f) => f.name)).toContain('foo');
55
+ });
56
+ // ------------------------------------------------------------------
57
+ // 2. Unchanged content hits the cache and still returns results
58
+ // ------------------------------------------------------------------
59
+ it('reuses the cached tree when content is unchanged', async () => {
60
+ const parser = trackedParser();
61
+ const src = 'def foo():\n return 42\n';
62
+ const r1 = await parser.parse(src, 'unchanged.py');
63
+ const r2 = await parser.parse(src, 'unchanged.py');
64
+ expect(r1).toEqual(r2);
65
+ // Still only one entry; parse didn't create a duplicate.
66
+ expect(parser.cacheSize).toBe(1);
67
+ });
68
+ // ------------------------------------------------------------------
69
+ // 3. Incremental edit re-parses using the previous tree
70
+ // ------------------------------------------------------------------
71
+ it('applies incremental edits and observes the change', async () => {
72
+ const parser = trackedParser();
73
+ const filePath = 'edit.py';
74
+ const v1 = 'def foo():\n return 1\n';
75
+ const v2 = 'def foo():\n return 2\n';
76
+ await parser.parse(v1, filePath);
77
+ const r2 = await parser.parse(v2, filePath);
78
+ expect(r2).not.toBeNull();
79
+ expect(r2?.functions).toHaveLength(1);
80
+ expect(r2?.functions[0].name).toBe('foo');
81
+ });
82
+ it('detects newly added functions across an edit', async () => {
83
+ const parser = trackedParser();
84
+ const filePath = 'add.py';
85
+ await parser.parse('def a():\n pass\n', filePath);
86
+ const r = await parser.parse('def a():\n pass\n\ndef b():\n pass\n', filePath);
87
+ expect(r).not.toBeNull();
88
+ const names = r?.functions.map((f) => f.name).sort();
89
+ expect(names).toEqual(['a', 'b']);
90
+ });
91
+ // ------------------------------------------------------------------
92
+ // 4. LRU eviction at max
93
+ // ------------------------------------------------------------------
94
+ it('evicts the least-recently-used tree when the cache is full', async () => {
95
+ const parser = trackedParser({ maxTrees: 3 });
96
+ await parser.parse('def a(): pass\n', 'a.py');
97
+ await parser.parse('def b(): pass\n', 'b.py');
98
+ await parser.parse('def c(): pass\n', 'c.py');
99
+ expect(parser.cacheSize).toBe(3);
100
+ // Touch a.py and b.py so c.py becomes LRU.
101
+ await parser.parse('def a(): pass\n', 'a.py');
102
+ await parser.parse('def b(): pass\n', 'b.py');
103
+ // Adding a new file should evict c.py.
104
+ await parser.parse('def d(): pass\n', 'd.py');
105
+ expect(parser.cacheSize).toBe(3);
106
+ });
107
+ // ------------------------------------------------------------------
108
+ // 5. Corrupted cached tree falls back gracefully
109
+ // (simulated by forcing the cached tree's .edit to throw)
110
+ // ------------------------------------------------------------------
111
+ it('invalidates and returns null when the cached tree is corrupted', async () => {
112
+ const parser = trackedParser();
113
+ const filePath = 'corrupt.py';
114
+ await parser.parse('def ok(): pass\n', filePath);
115
+ // Reach into the private cache to sabotage the tree.
116
+ const cache = parser
117
+ .cache;
118
+ const entry = cache.get(filePath);
119
+ if (!entry)
120
+ throw new Error('expected cache entry');
121
+ entry.tree.edit = () => {
122
+ throw new Error('simulated corruption');
123
+ };
124
+ const r = await parser.parse('def ok(): return 1\n', filePath);
125
+ expect(r).toBeNull();
126
+ expect(parser.cacheSize).toBe(0);
127
+ });
128
+ // ------------------------------------------------------------------
129
+ // 6. Grammar unavailable (loader rejects) → isReady stays false
130
+ // ------------------------------------------------------------------
131
+ it('flags init failure when the WASM loader rejects', async () => {
132
+ const failingLoader = async () => {
133
+ throw new Error('module not found');
134
+ };
135
+ const parser = trackedParser({}, failingLoader);
136
+ const result = await parser.parse('def x(): pass\n', 'fail.py');
137
+ expect(result).toBeNull();
138
+ expect(parser.isReady).toBe(false);
139
+ expect(parser.hasFailedInit).toBe(true);
140
+ });
141
+ });
142
+ //# sourceMappingURL=pythonIncremental.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pythonIncremental.test.js","sourceRoot":"","sources":["../../../../src/analysis/tree-sitter/pythonIncremental.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * SMI-4293: Python Tree-Sitter Query Strings
3
+ *
4
+ * TypeScript-consumable tree-sitter queries used by the PythonAdapter
5
+ * when the WASM parser is available. Paired with python.scm for reference
6
+ * but this module is what runtime code imports (loadable without fs reads).
7
+ *
8
+ * These queries drive the query-based extraction path that replaces the
9
+ * regex fallback. The extraction must match-or-exceed the regex baseline;
10
+ * see queryExtractionMatchesOrExceedsRegex.test.ts (finding H3).
11
+ *
12
+ * @see docs/internal/implementation/github-wave-5c-tree-sitter-incremental.md
13
+ * @module analysis/tree-sitter/queries/python
14
+ */
15
+ /**
16
+ * Query capturing `import x`, `import x as y`, `import x.y.z`.
17
+ */
18
+ export declare const PYTHON_IMPORT_QUERY = "\n(import_statement\n name: (dotted_name) @import.module)\n\n(import_statement\n name: (aliased_import\n name: (dotted_name) @import.module\n alias: (identifier) @import.alias))\n";
19
+ /**
20
+ * Query capturing `from module import name`, aliased imports, and wildcard.
21
+ * Relative imports (`from .x import y`) capture the dotted_name portion as
22
+ * the module name; the leading dots are preserved via relative_import wrapping.
23
+ */
24
+ export declare const PYTHON_FROM_IMPORT_QUERY = "\n(import_from_statement\n module_name: (dotted_name) @from.module\n name: (dotted_name) @from.name)\n\n(import_from_statement\n module_name: (dotted_name) @from.module\n name: (aliased_import\n name: (dotted_name) @from.name\n alias: (identifier) @from.alias))\n\n(import_from_statement\n module_name: (dotted_name) @from.module\n (wildcard_import) @from.wildcard)\n\n(import_from_statement\n module_name: (relative_import) @from.module\n name: (_) @from.name)\n";
25
+ /**
26
+ * Query capturing top-level and nested function definitions.
27
+ * `async` is a child marker when present; we detect it via node type.
28
+ */
29
+ export declare const PYTHON_FUNCTION_QUERY = "\n(function_definition\n name: (identifier) @function.name\n parameters: (parameters) @function.params) @function.def\n";
30
+ /**
31
+ * Query capturing class definitions.
32
+ */
33
+ export declare const PYTHON_CLASS_QUERY = "\n(class_definition\n name: (identifier) @class.name) @class.def\n";
34
+ /**
35
+ * Query capturing `__all__ = [...]` export declarations.
36
+ */
37
+ export declare const PYTHON_ALL_EXPORT_QUERY = "\n(assignment\n left: (identifier) @all.var\n right: (list\n (string) @all.name)\n (#eq? @all.var \"__all__\"))\n";
38
+ /**
39
+ * Combined query used for a single pass over the tree.
40
+ * Keeps capture names namespaced so extraction can partition results.
41
+ */
42
+ export declare const PYTHON_COMBINED_QUERY: string;
43
+ //# sourceMappingURL=python.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"python.d.ts","sourceRoot":"","sources":["../../../../../src/analysis/tree-sitter/queries/python.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH;;GAEG;AACH,eAAO,MAAM,mBAAmB,gMAQ/B,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,keAkBpC,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,qBAAqB,8HAIjC,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,kBAAkB,wEAG9B,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,uBAAuB,4HAMnC,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,qBAAqB,QAMtB,CAAA"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * SMI-4293: Python Tree-Sitter Query Strings
3
+ *
4
+ * TypeScript-consumable tree-sitter queries used by the PythonAdapter
5
+ * when the WASM parser is available. Paired with python.scm for reference
6
+ * but this module is what runtime code imports (loadable without fs reads).
7
+ *
8
+ * These queries drive the query-based extraction path that replaces the
9
+ * regex fallback. The extraction must match-or-exceed the regex baseline;
10
+ * see queryExtractionMatchesOrExceedsRegex.test.ts (finding H3).
11
+ *
12
+ * @see docs/internal/implementation/github-wave-5c-tree-sitter-incremental.md
13
+ * @module analysis/tree-sitter/queries/python
14
+ */
15
+ /**
16
+ * Query capturing `import x`, `import x as y`, `import x.y.z`.
17
+ */
18
+ export const PYTHON_IMPORT_QUERY = `
19
+ (import_statement
20
+ name: (dotted_name) @import.module)
21
+
22
+ (import_statement
23
+ name: (aliased_import
24
+ name: (dotted_name) @import.module
25
+ alias: (identifier) @import.alias))
26
+ `;
27
+ /**
28
+ * Query capturing `from module import name`, aliased imports, and wildcard.
29
+ * Relative imports (`from .x import y`) capture the dotted_name portion as
30
+ * the module name; the leading dots are preserved via relative_import wrapping.
31
+ */
32
+ export const PYTHON_FROM_IMPORT_QUERY = `
33
+ (import_from_statement
34
+ module_name: (dotted_name) @from.module
35
+ name: (dotted_name) @from.name)
36
+
37
+ (import_from_statement
38
+ module_name: (dotted_name) @from.module
39
+ name: (aliased_import
40
+ name: (dotted_name) @from.name
41
+ alias: (identifier) @from.alias))
42
+
43
+ (import_from_statement
44
+ module_name: (dotted_name) @from.module
45
+ (wildcard_import) @from.wildcard)
46
+
47
+ (import_from_statement
48
+ module_name: (relative_import) @from.module
49
+ name: (_) @from.name)
50
+ `;
51
+ /**
52
+ * Query capturing top-level and nested function definitions.
53
+ * `async` is a child marker when present; we detect it via node type.
54
+ */
55
+ export const PYTHON_FUNCTION_QUERY = `
56
+ (function_definition
57
+ name: (identifier) @function.name
58
+ parameters: (parameters) @function.params) @function.def
59
+ `;
60
+ /**
61
+ * Query capturing class definitions.
62
+ */
63
+ export const PYTHON_CLASS_QUERY = `
64
+ (class_definition
65
+ name: (identifier) @class.name) @class.def
66
+ `;
67
+ /**
68
+ * Query capturing `__all__ = [...]` export declarations.
69
+ */
70
+ export const PYTHON_ALL_EXPORT_QUERY = `
71
+ (assignment
72
+ left: (identifier) @all.var
73
+ right: (list
74
+ (string) @all.name)
75
+ (#eq? @all.var "__all__"))
76
+ `;
77
+ /**
78
+ * Combined query used for a single pass over the tree.
79
+ * Keeps capture names namespaced so extraction can partition results.
80
+ */
81
+ export const PYTHON_COMBINED_QUERY = [
82
+ PYTHON_IMPORT_QUERY,
83
+ PYTHON_FROM_IMPORT_QUERY,
84
+ PYTHON_FUNCTION_QUERY,
85
+ PYTHON_CLASS_QUERY,
86
+ PYTHON_ALL_EXPORT_QUERY,
87
+ ].join('\n');
88
+ //# sourceMappingURL=python.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"python.js","sourceRoot":"","sources":["../../../../../src/analysis/tree-sitter/queries/python.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH;;GAEG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG;;;;;;;;CAQlC,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG;;;;;;;;;;;;;;;;;;CAkBvC,CAAA;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG;;;;CAIpC,CAAA;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG;;;CAGjC,CAAA;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG;;;;;;CAMtC,CAAA;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,mBAAmB;IACnB,wBAAwB;IACxB,qBAAqB;IACrB,kBAAkB;IAClB,uBAAuB;CACxB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * SMI-4293 / finding H3: Regression guard for query-based extraction.
3
+ *
4
+ * Before the WASM query path replaces the regex path, this test asserts
5
+ * that for every Python fixture, the query-based extraction produces a
6
+ * SUPERSET OR EQUAL SET of constructs relative to the regex baseline.
7
+ * A missing construct in the query path blocks the PR — queries must be
8
+ * extended until parity is reached.
9
+ *
10
+ * @see docs/internal/implementation/github-wave-5c-tree-sitter-incremental.md
11
+ */
12
+ export {};
13
+ //# sourceMappingURL=queryExtractionMatchesOrExceedsRegex.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queryExtractionMatchesOrExceedsRegex.test.d.ts","sourceRoot":"","sources":["../../../../src/analysis/tree-sitter/queryExtractionMatchesOrExceedsRegex.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG"}
@@ -0,0 +1,174 @@
1
+ /**
2
+ * SMI-4293 / finding H3: Regression guard for query-based extraction.
3
+ *
4
+ * Before the WASM query path replaces the regex path, this test asserts
5
+ * that for every Python fixture, the query-based extraction produces a
6
+ * SUPERSET OR EQUAL SET of constructs relative to the regex baseline.
7
+ * A missing construct in the query path blocks the PR — queries must be
8
+ * extended until parity is reached.
9
+ *
10
+ * @see docs/internal/implementation/github-wave-5c-tree-sitter-incremental.md
11
+ */
12
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
13
+ import { PythonAdapter } from '../adapters/python.js';
14
+ import { PythonIncrementalParser } from './pythonIncremental.js';
15
+ // Fixtures span every construct the regex extractor recognises, plus a
16
+ // handful of edge cases the original Python test suite exercises.
17
+ const FIXTURES = [
18
+ {
19
+ name: 'simple-imports',
20
+ source: `
21
+ import os
22
+ import sys
23
+ import json
24
+ `,
25
+ },
26
+ {
27
+ name: 'aliased-imports',
28
+ source: `
29
+ import numpy as np
30
+ import pandas as pd
31
+ `,
32
+ },
33
+ {
34
+ name: 'from-imports',
35
+ source: `
36
+ from os import path, getcwd
37
+ from typing import Optional, List
38
+ `,
39
+ },
40
+ {
41
+ name: 'from-aliased-and-wildcard',
42
+ source: `
43
+ from django.http import HttpResponse as Resp
44
+ from utils import *
45
+ `,
46
+ },
47
+ {
48
+ name: 'top-level-functions-classes',
49
+ source: `
50
+ def top_fn():
51
+ pass
52
+
53
+ async def async_fn(a, b):
54
+ return a + b
55
+
56
+ class PublicClass:
57
+ def method(self):
58
+ pass
59
+
60
+ class _Private:
61
+ pass
62
+
63
+ def _hidden():
64
+ pass
65
+ `,
66
+ },
67
+ {
68
+ name: 'all-export-list',
69
+ source: `
70
+ __all__ = ["ExportedFn", "ExportedCls"]
71
+
72
+ def ExportedFn():
73
+ pass
74
+
75
+ class ExportedCls:
76
+ pass
77
+
78
+ def not_exported_but_public():
79
+ pass
80
+ `,
81
+ },
82
+ {
83
+ name: 'nested-functions',
84
+ source: `
85
+ def outer():
86
+ def middle():
87
+ def inner():
88
+ pass
89
+ return inner
90
+ return middle
91
+ `,
92
+ },
93
+ {
94
+ name: 'decorators',
95
+ source: `
96
+ @decorator
97
+ def decorated():
98
+ pass
99
+
100
+ @decorator1
101
+ @decorator2
102
+ def multi_decorated():
103
+ pass
104
+
105
+ @module.attr_decorator
106
+ def attr_decorated():
107
+ pass
108
+ `,
109
+ },
110
+ {
111
+ name: 'type-hinted-function',
112
+ source: `
113
+ def typed(a: int, b: str, c: Optional[List[int]]) -> Dict[str, Any]:
114
+ pass
115
+ `,
116
+ },
117
+ {
118
+ name: 'empty-module',
119
+ source: '',
120
+ },
121
+ {
122
+ name: 'comments-only',
123
+ source: `
124
+ # just a comment
125
+ # nothing else
126
+ `,
127
+ },
128
+ ];
129
+ function normalize(result) {
130
+ return {
131
+ imports: new Set(result.imports.map((i) => `${i.module}::${[...i.namedImports].sort().join(',')}`)),
132
+ exports: new Set(result.exports.map((e) => `${e.name}::${e.kind}`)),
133
+ functions: new Set(result.functions.map((f) => `${f.name}::${f.isAsync}`)),
134
+ };
135
+ }
136
+ function isSuperset(candidate, baseline) {
137
+ const missing = [];
138
+ for (const item of baseline)
139
+ if (!candidate.has(item))
140
+ missing.push(item);
141
+ return missing.length === 0 ? { ok: true } : { ok: false, missing };
142
+ }
143
+ describe('Python query extraction vs regex baseline (finding H3)', () => {
144
+ const regexAdapter = new PythonAdapter();
145
+ const queryParser = new PythonIncrementalParser();
146
+ beforeAll(async () => {
147
+ await queryParser.ensureReady();
148
+ });
149
+ afterAll(() => {
150
+ regexAdapter.dispose();
151
+ queryParser.dispose();
152
+ });
153
+ it('boots the WASM parser for the regression guard', () => {
154
+ expect(queryParser.isReady).toBe(true);
155
+ });
156
+ for (const fixture of FIXTURES) {
157
+ it(`query extraction ⊇ regex baseline for fixture "${fixture.name}"`, () => {
158
+ const regexResult = regexAdapter.parseFile(fixture.source, `${fixture.name}.py`);
159
+ const queryResult = queryParser.parseSync(fixture.source, `${fixture.name}.py`);
160
+ expect(queryResult).not.toBeNull();
161
+ if (!queryResult)
162
+ return; // keep type narrowing happy
163
+ const regex = normalize(regexResult);
164
+ const query = normalize(queryResult);
165
+ const importCheck = isSuperset(query.imports, regex.imports);
166
+ const exportCheck = isSuperset(query.exports, regex.exports);
167
+ const functionCheck = isSuperset(query.functions, regex.functions);
168
+ expect(importCheck, `imports missing vs regex baseline: ${'missing' in importCheck ? JSON.stringify(importCheck.missing) : '[]'}`).toMatchObject({ ok: true });
169
+ expect(exportCheck, `exports missing vs regex baseline: ${'missing' in exportCheck ? JSON.stringify(exportCheck.missing) : '[]'}`).toMatchObject({ ok: true });
170
+ expect(functionCheck, `functions missing vs regex baseline: ${'missing' in functionCheck ? JSON.stringify(functionCheck.missing) : '[]'}`).toMatchObject({ ok: true });
171
+ });
172
+ }
173
+ });
174
+ //# sourceMappingURL=queryExtractionMatchesOrExceedsRegex.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queryExtractionMatchesOrExceedsRegex.test.js","sourceRoot":"","sources":["../../../../src/analysis/tree-sitter/queryExtractionMatchesOrExceedsRegex.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * ROI dashboard CSV export helpers — extracted from ROIDashboardService to keep
3
+ * the main service file under the 500-line governance limit.
4
+ */
5
+ import type { ROIDashboard } from './types.js';
6
+ /**
7
+ * Render a partial ROI dashboard as CSV.
8
+ * Handles user-only, stakeholder-only, or combined payloads.
9
+ */
10
+ export declare function convertROIToCSV(data: Partial<ROIDashboard>): string;
11
+ //# sourceMappingURL=ROIDashboardService.csv.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ROIDashboardService.csv.d.ts","sourceRoot":"","sources":["../../../src/analytics/ROIDashboardService.csv.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE9C;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,MAAM,CAsCnE"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * ROI dashboard CSV export helpers — extracted from ROIDashboardService to keep
3
+ * the main service file under the 500-line governance limit.
4
+ */
5
+ /**
6
+ * Render a partial ROI dashboard as CSV.
7
+ * Handles user-only, stakeholder-only, or combined payloads.
8
+ */
9
+ export function convertROIToCSV(data) {
10
+ const lines = [];
11
+ if (data.user) {
12
+ lines.push('User ROI Dashboard');
13
+ lines.push('');
14
+ lines.push('Metric,Value');
15
+ lines.push(`User ID,${data.user.userId}`);
16
+ lines.push(`Total Time Saved (min),${data.user.totalTimeSaved.toFixed(1)}`);
17
+ lines.push(`Estimated Value (USD),${data.user.estimatedValueUsd.toFixed(2)}`);
18
+ lines.push('');
19
+ lines.push('Top Skills');
20
+ lines.push('Skill ID,Skill Name,Time Saved (min)');
21
+ for (const skill of data.user.topSkills) {
22
+ lines.push(`${skill.skillId},${skill.skillName},${skill.timeSaved.toFixed(1)}`);
23
+ }
24
+ }
25
+ if (data.stakeholder) {
26
+ lines.push('Stakeholder ROI Dashboard');
27
+ lines.push('');
28
+ lines.push('Metric,Value');
29
+ lines.push(`Total Users,${data.stakeholder.totalUsers}`);
30
+ lines.push(`Total Activations,${data.stakeholder.totalActivations}`);
31
+ lines.push(`Avg Time Saved Per User (min),${data.stakeholder.avgTimeSavedPerUser.toFixed(1)}`);
32
+ lines.push(`Total Estimated Value (USD),${data.stakeholder.totalEstimatedValue.toFixed(2)}`);
33
+ lines.push(`Adoption Rate (%),${(data.stakeholder.adoptionRate * 100).toFixed(1)}`);
34
+ lines.push('');
35
+ lines.push('Skill Leaderboard');
36
+ lines.push('Skill ID,Skill Name,User Count,Total Value (USD)');
37
+ for (const skill of data.stakeholder.skillLeaderboard) {
38
+ lines.push(`${skill.skillId},${skill.skillName},${skill.userCount},${skill.totalValue.toFixed(2)}`);
39
+ }
40
+ }
41
+ return lines.join('\n');
42
+ }
43
+ //# sourceMappingURL=ROIDashboardService.csv.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ROIDashboardService.csv.js","sourceRoot":"","sources":["../../../src/analytics/ROIDashboardService.csv.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,IAA2B;IACzD,MAAM,KAAK,GAAa,EAAE,CAAA;IAE1B,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,KAAK,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAA;QAChC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACd,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAA;QAC1B,KAAK,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAA;QACzC,KAAK,CAAC,IAAI,CAAC,0BAA0B,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;QAC3E,KAAK,CAAC,IAAI,CAAC,yBAAyB,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;QAC7E,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACd,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;QACxB,KAAK,CAAC,IAAI,CAAC,sCAAsC,CAAC,CAAA;QAClD,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACxC,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;QACjF,CAAC;IACH,CAAC;IAED,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;QACrB,KAAK,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAA;QACvC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACd,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAA;QAC1B,KAAK,CAAC,IAAI,CAAC,eAAe,IAAI,CAAC,WAAW,CAAC,UAAU,EAAE,CAAC,CAAA;QACxD,KAAK,CAAC,IAAI,CAAC,qBAAqB,IAAI,CAAC,WAAW,CAAC,gBAAgB,EAAE,CAAC,CAAA;QACpE,KAAK,CAAC,IAAI,CAAC,iCAAiC,IAAI,CAAC,WAAW,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;QAC9F,KAAK,CAAC,IAAI,CAAC,+BAA+B,IAAI,CAAC,WAAW,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;QAC5F,KAAK,CAAC,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,WAAW,CAAC,YAAY,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;QACnF,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACd,KAAK,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAA;QAC/B,KAAK,CAAC,IAAI,CAAC,kDAAkD,CAAC,CAAA;QAC9D,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,WAAW,CAAC,gBAAgB,EAAE,CAAC;YACtD,KAAK,CAAC,IAAI,CACR,GAAG,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CACxF,CAAA;QACH,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACzB,CAAC"}
@@ -15,19 +15,63 @@ export interface ROIComputeOptions {
15
15
  startDate?: string;
16
16
  endDate?: string;
17
17
  }
18
+ /**
19
+ * Options for the public {@link ROIDashboardService.getDashboard} entrypoint.
20
+ * Both dates must be provided together (or neither, which defaults to the last 30 days).
21
+ */
22
+ export interface GetDashboardOptions {
23
+ userId?: string;
24
+ startDate?: string;
25
+ endDate?: string;
26
+ }
27
+ /**
28
+ * Strict ISO-8601 / RFC-3339 profile matcher (SMI-4317). Accepts `YYYY-MM-DD`
29
+ * and `YYYY-MM-DDTHH:MM:SS(.sss)?(Z|[+-]HH:MM)`; rejects RFC-2822, slash
30
+ * separators, space-as-T, bare date-time without offset, and any trailing
31
+ * content. Syntactic guard only — calendar validity (e.g., `2026-13-01`) is
32
+ * caught by the paired `Date.parse` check. Exported for tests/reuse.
33
+ */
34
+ export declare const ISO_8601_STRICT: RegExp;
18
35
  export declare class ROIDashboardService {
19
36
  private repo;
20
37
  private readonly TIME_SAVED_PER_SUCCESS;
21
38
  private readonly VALUE_PER_MINUTE;
22
39
  constructor(db: DatabaseType);
23
40
  /**
24
- * Get user ROI dashboard data
41
+ * Get user ROI dashboard data for a rolling window (last `days` days).
42
+ * For an explicit ISO-8601 range, use {@link getDashboard}.
25
43
  */
26
44
  getUserROI(userId: string, days?: number): ROIDashboard['user'];
27
45
  /**
28
- * Get stakeholder aggregate ROI dashboard
46
+ * Core per-user ROI computation over an explicit ISO-8601 range.
47
+ * Shared between {@link getUserROI} (days-based) and {@link getDashboard}.
48
+ */
49
+ private buildUserROI;
50
+ /**
51
+ * Get stakeholder aggregate ROI dashboard for a rolling window (last `days` days).
52
+ * For an explicit ISO-8601 range, use {@link getDashboard}.
29
53
  */
30
54
  getStakeholderROI(days?: number): ROIDashboard['stakeholder'];
55
+ /**
56
+ * Core stakeholder ROI computation over an explicit ISO-8601 range.
57
+ * Shared between {@link getStakeholderROI} (days-based) and {@link getDashboard}.
58
+ */
59
+ private buildStakeholderROI;
60
+ /**
61
+ * Public date-range-aware dashboard entrypoint (SMI-1683 / GitHub #603).
62
+ *
63
+ * Behavior:
64
+ * - Both dates omitted: defaults to the last 30 days ending now.
65
+ * - Exactly one date provided: throws {@link ValidationError} (the range is ambiguous).
66
+ * - `startDate >= endDate`: throws {@link ValidationError}.
67
+ * - `userId` provided: returns `{ user }` with date-filtered per-user metrics.
68
+ * - `userId` omitted: returns `{ stakeholder }` aggregated over the range.
69
+ *
70
+ * @param options.userId Optional — when supplied, returns the per-user dashboard.
71
+ * @param options.startDate Optional ISO-8601 timestamp; must be paired with `endDate`.
72
+ * @param options.endDate Optional ISO-8601 timestamp; must be paired with `startDate`.
73
+ */
74
+ getDashboard(options?: GetDashboardOptions): ROIDashboard;
31
75
  /**
32
76
  * Compute and store ROI metrics for a period
33
77
  * This should be run periodically (e.g., daily) to maintain the dashboard
@@ -42,6 +86,24 @@ export declare class ROIDashboardService {
42
86
  */
43
87
  refreshMetrics(): void;
44
88
  private getDateRange;
89
+ /**
90
+ * Resolve the range for {@link getDashboard}: default to last 30 days when both
91
+ * dates are omitted, validate that exactly-one-date was not supplied, and enforce
92
+ * `startDate < endDate`.
93
+ */
94
+ private resolveDashboardRange;
95
+ /**
96
+ * Throw a typed {@link ValidationError} when the supplied range is malformed.
97
+ *
98
+ * Validation layers (SMI-4317):
99
+ * 1. Strict ISO-8601 / RFC-3339 regex — rejects shapes like `2026/01/01`,
100
+ * `2026-01-01 00:00:00` (space), `Jan 1 2026`, or RFC-2822 that
101
+ * `Date.parse` would otherwise accept.
102
+ * 2. `Date.parse` NaN check — catches syntactically valid but semantically
103
+ * invalid dates (e.g., `2026-13-01`) that the regex cannot detect.
104
+ * 3. Ordering — rejects when `startDate >= endDate`.
105
+ */
106
+ private assertValidRange;
45
107
  private calculateTimeSaved;
46
108
  private groupBySkill;
47
109
  private calculateWeeklyTrend;
@@ -50,6 +112,5 @@ export declare class ROIDashboardService {
50
112
  private computeUserMetrics;
51
113
  private computeSkillMetrics;
52
114
  private computeDailyMetrics;
53
- private convertROIToCSV;
54
115
  }
55
116
  //# sourceMappingURL=ROIDashboardService.d.ts.map