@oculum/scanner 1.0.11 → 1.0.12

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 (309) hide show
  1. package/dist/ai-context/index.d.ts +6 -0
  2. package/dist/ai-context/index.d.ts.map +1 -0
  3. package/dist/ai-context/index.js +13 -0
  4. package/dist/ai-context/index.js.map +1 -0
  5. package/dist/ai-context/manager.d.ts +67 -0
  6. package/dist/ai-context/manager.d.ts.map +1 -0
  7. package/dist/ai-context/manager.js +104 -0
  8. package/dist/ai-context/manager.js.map +1 -0
  9. package/dist/category-filter.d.ts +125 -0
  10. package/dist/category-filter.d.ts.map +1 -0
  11. package/dist/category-filter.js +360 -0
  12. package/dist/category-filter.js.map +1 -0
  13. package/dist/filtering/context-adjustments.d.ts +23 -0
  14. package/dist/filtering/context-adjustments.d.ts.map +1 -0
  15. package/dist/filtering/context-adjustments.js +100 -0
  16. package/dist/filtering/context-adjustments.js.map +1 -0
  17. package/dist/filtering/index.d.ts +3 -0
  18. package/dist/filtering/index.d.ts.map +1 -0
  19. package/dist/filtering/index.js +8 -0
  20. package/dist/filtering/index.js.map +1 -0
  21. package/dist/filtering/pipeline.d.ts +48 -0
  22. package/dist/filtering/pipeline.d.ts.map +1 -0
  23. package/dist/filtering/pipeline.js +76 -0
  24. package/dist/filtering/pipeline.js.map +1 -0
  25. package/dist/formatters/ai-context.d.ts +23 -0
  26. package/dist/formatters/ai-context.d.ts.map +1 -0
  27. package/dist/formatters/ai-context.js +238 -0
  28. package/dist/formatters/ai-context.js.map +1 -0
  29. package/dist/formatters/github-comment.d.ts +1 -1
  30. package/dist/formatters/github-comment.d.ts.map +1 -1
  31. package/dist/formatters/github-comment.js +2 -2
  32. package/dist/formatters/github-comment.js.map +1 -1
  33. package/dist/formatters/ide/claude-code.d.ts +17 -0
  34. package/dist/formatters/ide/claude-code.d.ts.map +1 -0
  35. package/dist/formatters/ide/claude-code.js +94 -0
  36. package/dist/formatters/ide/claude-code.js.map +1 -0
  37. package/dist/formatters/ide/cursor.d.ts +13 -0
  38. package/dist/formatters/ide/cursor.d.ts.map +1 -0
  39. package/dist/formatters/ide/cursor.js +125 -0
  40. package/dist/formatters/ide/cursor.js.map +1 -0
  41. package/dist/formatters/ide/index.d.ts +62 -0
  42. package/dist/formatters/ide/index.d.ts.map +1 -0
  43. package/dist/formatters/ide/index.js +184 -0
  44. package/dist/formatters/ide/index.js.map +1 -0
  45. package/dist/formatters/ide/windsurf.d.ts +13 -0
  46. package/dist/formatters/ide/windsurf.d.ts.map +1 -0
  47. package/dist/formatters/ide/windsurf.js +117 -0
  48. package/dist/formatters/ide/windsurf.js.map +1 -0
  49. package/dist/formatters/index.d.ts +2 -0
  50. package/dist/formatters/index.d.ts.map +1 -1
  51. package/dist/formatters/index.js +17 -1
  52. package/dist/formatters/index.js.map +1 -1
  53. package/dist/index.d.ts +4 -0
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +272 -44
  56. package/dist/index.js.map +1 -1
  57. package/dist/layer1/comments.d.ts +4 -1
  58. package/dist/layer1/comments.d.ts.map +1 -1
  59. package/dist/layer1/comments.js +1 -1
  60. package/dist/layer1/comments.js.map +1 -1
  61. package/dist/layer1/config-audit.d.ts +4 -1
  62. package/dist/layer1/config-audit.d.ts.map +1 -1
  63. package/dist/layer1/config-audit.js +45 -11
  64. package/dist/layer1/config-audit.js.map +1 -1
  65. package/dist/layer1/config-mcp-audit.d.ts +4 -1
  66. package/dist/layer1/config-mcp-audit.d.ts.map +1 -1
  67. package/dist/layer1/config-mcp-audit.js +2 -2
  68. package/dist/layer1/config-mcp-audit.js.map +1 -1
  69. package/dist/layer1/entropy.d.ts +4 -1
  70. package/dist/layer1/entropy.d.ts.map +1 -1
  71. package/dist/layer1/entropy.js +212 -1
  72. package/dist/layer1/entropy.js.map +1 -1
  73. package/dist/layer1/file-flags.d.ts +4 -1
  74. package/dist/layer1/file-flags.d.ts.map +1 -1
  75. package/dist/layer1/file-flags.js +12 -5
  76. package/dist/layer1/file-flags.js.map +1 -1
  77. package/dist/layer1/index.d.ts.map +1 -1
  78. package/dist/layer1/index.js +14 -19
  79. package/dist/layer1/index.js.map +1 -1
  80. package/dist/layer1/patterns.d.ts +4 -1
  81. package/dist/layer1/patterns.d.ts.map +1 -1
  82. package/dist/layer1/patterns.js +34 -4
  83. package/dist/layer1/patterns.js.map +1 -1
  84. package/dist/layer1/urls.d.ts +4 -1
  85. package/dist/layer1/urls.d.ts.map +1 -1
  86. package/dist/layer1/urls.js +162 -14
  87. package/dist/layer1/urls.js.map +1 -1
  88. package/dist/layer1/weak-crypto.d.ts +4 -1
  89. package/dist/layer1/weak-crypto.d.ts.map +1 -1
  90. package/dist/layer1/weak-crypto.js +144 -7
  91. package/dist/layer1/weak-crypto.js.map +1 -1
  92. package/dist/layer2/ai-agent-tools.d.ts +4 -1
  93. package/dist/layer2/ai-agent-tools.d.ts.map +1 -1
  94. package/dist/layer2/ai-agent-tools.js +661 -2
  95. package/dist/layer2/ai-agent-tools.js.map +1 -1
  96. package/dist/layer2/ai-endpoint-protection.d.ts +2 -0
  97. package/dist/layer2/ai-endpoint-protection.d.ts.map +1 -1
  98. package/dist/layer2/ai-endpoint-protection.js +1 -1
  99. package/dist/layer2/ai-endpoint-protection.js.map +1 -1
  100. package/dist/layer2/ai-execution-sinks.d.ts +4 -1
  101. package/dist/layer2/ai-execution-sinks.d.ts.map +1 -1
  102. package/dist/layer2/ai-execution-sinks.js +252 -43
  103. package/dist/layer2/ai-execution-sinks.js.map +1 -1
  104. package/dist/layer2/ai-fingerprinting.d.ts +4 -1
  105. package/dist/layer2/ai-fingerprinting.d.ts.map +1 -1
  106. package/dist/layer2/ai-fingerprinting.js +25 -32
  107. package/dist/layer2/ai-fingerprinting.js.map +1 -1
  108. package/dist/layer2/ai-mcp-security.d.ts +4 -1
  109. package/dist/layer2/ai-mcp-security.d.ts.map +1 -1
  110. package/dist/layer2/ai-mcp-security.js +200 -2
  111. package/dist/layer2/ai-mcp-security.js.map +1 -1
  112. package/dist/layer2/ai-package-hallucination.d.ts +4 -1
  113. package/dist/layer2/ai-package-hallucination.d.ts.map +1 -1
  114. package/dist/layer2/ai-package-hallucination.js +136 -4
  115. package/dist/layer2/ai-package-hallucination.js.map +1 -1
  116. package/dist/layer2/ai-prompt-hygiene.d.ts +4 -1
  117. package/dist/layer2/ai-prompt-hygiene.d.ts.map +1 -1
  118. package/dist/layer2/ai-prompt-hygiene.js +342 -28
  119. package/dist/layer2/ai-prompt-hygiene.js.map +1 -1
  120. package/dist/layer2/ai-rag-safety.d.ts +4 -1
  121. package/dist/layer2/ai-rag-safety.d.ts.map +1 -1
  122. package/dist/layer2/ai-rag-safety.js +82 -2
  123. package/dist/layer2/ai-rag-safety.js.map +1 -1
  124. package/dist/layer2/ai-schema-validation.d.ts +4 -1
  125. package/dist/layer2/ai-schema-validation.d.ts.map +1 -1
  126. package/dist/layer2/ai-schema-validation.js +2 -2
  127. package/dist/layer2/ai-schema-validation.js.map +1 -1
  128. package/dist/layer2/auth-antipatterns.d.ts +2 -0
  129. package/dist/layer2/auth-antipatterns.d.ts.map +1 -1
  130. package/dist/layer2/auth-antipatterns.js +205 -20
  131. package/dist/layer2/auth-antipatterns.js.map +1 -1
  132. package/dist/layer2/byok-patterns.d.ts +4 -1
  133. package/dist/layer2/byok-patterns.d.ts.map +1 -1
  134. package/dist/layer2/byok-patterns.js +2 -2
  135. package/dist/layer2/byok-patterns.js.map +1 -1
  136. package/dist/layer2/dangerous-functions/dom-xss.d.ts +9 -4
  137. package/dist/layer2/dangerous-functions/dom-xss.d.ts.map +1 -1
  138. package/dist/layer2/dangerous-functions/dom-xss.js +73 -22
  139. package/dist/layer2/dangerous-functions/dom-xss.js.map +1 -1
  140. package/dist/layer2/dangerous-functions/index.d.ts +4 -1
  141. package/dist/layer2/dangerous-functions/index.d.ts.map +1 -1
  142. package/dist/layer2/dangerous-functions/index.js +551 -20
  143. package/dist/layer2/dangerous-functions/index.js.map +1 -1
  144. package/dist/layer2/dangerous-functions/math-random.d.ts +54 -4
  145. package/dist/layer2/dangerous-functions/math-random.d.ts.map +1 -1
  146. package/dist/layer2/dangerous-functions/math-random.js +241 -16
  147. package/dist/layer2/dangerous-functions/math-random.js.map +1 -1
  148. package/dist/layer2/dangerous-functions/patterns.d.ts.map +1 -1
  149. package/dist/layer2/dangerous-functions/patterns.js +3 -1
  150. package/dist/layer2/dangerous-functions/patterns.js.map +1 -1
  151. package/dist/layer2/dangerous-functions/utils/control-flow.d.ts +3 -2
  152. package/dist/layer2/dangerous-functions/utils/control-flow.d.ts.map +1 -1
  153. package/dist/layer2/dangerous-functions/utils/control-flow.js +41 -120
  154. package/dist/layer2/dangerous-functions/utils/control-flow.js.map +1 -1
  155. package/dist/layer2/dangerous-functions/utils/helpers.d.ts.map +1 -1
  156. package/dist/layer2/dangerous-functions/utils/helpers.js +26 -3
  157. package/dist/layer2/dangerous-functions/utils/helpers.js.map +1 -1
  158. package/dist/layer2/dangerous-functions/utils/schema-validation.d.ts.map +1 -1
  159. package/dist/layer2/dangerous-functions/utils/schema-validation.js +14 -1
  160. package/dist/layer2/dangerous-functions/utils/schema-validation.js.map +1 -1
  161. package/dist/layer2/data-exposure.d.ts +4 -1
  162. package/dist/layer2/data-exposure.d.ts.map +1 -1
  163. package/dist/layer2/data-exposure.js +11 -38
  164. package/dist/layer2/data-exposure.js.map +1 -1
  165. package/dist/layer2/framework-checks.d.ts +4 -1
  166. package/dist/layer2/framework-checks.d.ts.map +1 -1
  167. package/dist/layer2/framework-checks.js +2 -2
  168. package/dist/layer2/framework-checks.js.map +1 -1
  169. package/dist/layer2/index.d.ts +9 -1
  170. package/dist/layer2/index.d.ts.map +1 -1
  171. package/dist/layer2/index.js +57 -51
  172. package/dist/layer2/index.js.map +1 -1
  173. package/dist/layer2/logic-gates.d.ts +4 -1
  174. package/dist/layer2/logic-gates.d.ts.map +1 -1
  175. package/dist/layer2/logic-gates.js +54 -20
  176. package/dist/layer2/logic-gates.js.map +1 -1
  177. package/dist/layer2/model-supply-chain.d.ts +4 -1
  178. package/dist/layer2/model-supply-chain.d.ts.map +1 -1
  179. package/dist/layer2/model-supply-chain.js +72 -4
  180. package/dist/layer2/model-supply-chain.js.map +1 -1
  181. package/dist/layer2/risky-imports.d.ts +4 -1
  182. package/dist/layer2/risky-imports.d.ts.map +1 -1
  183. package/dist/layer2/risky-imports.js +2 -2
  184. package/dist/layer2/risky-imports.js.map +1 -1
  185. package/dist/layer2/variables.d.ts +4 -1
  186. package/dist/layer2/variables.d.ts.map +1 -1
  187. package/dist/layer2/variables.js +2 -2
  188. package/dist/layer2/variables.js.map +1 -1
  189. package/dist/layer3/anthropic/auto-dismiss.d.ts.map +1 -1
  190. package/dist/layer3/anthropic/auto-dismiss.js +11 -0
  191. package/dist/layer3/anthropic/auto-dismiss.js.map +1 -1
  192. package/dist/modes/incremental.js +1 -1
  193. package/dist/tiers.d.ts +2 -2
  194. package/dist/tiers.d.ts.map +1 -1
  195. package/dist/tiers.js +7 -7
  196. package/dist/tiers.js.map +1 -1
  197. package/dist/types.d.ts +78 -8
  198. package/dist/types.d.ts.map +1 -1
  199. package/dist/types.js +34 -0
  200. package/dist/types.js.map +1 -1
  201. package/dist/utils/code-analysis.d.ts +39 -0
  202. package/dist/utils/code-analysis.d.ts.map +1 -0
  203. package/dist/utils/code-analysis.js +159 -0
  204. package/dist/utils/code-analysis.js.map +1 -0
  205. package/dist/utils/comment-analyzer.d.ts +38 -0
  206. package/dist/utils/comment-analyzer.d.ts.map +1 -0
  207. package/dist/utils/comment-analyzer.js +218 -0
  208. package/dist/utils/comment-analyzer.js.map +1 -0
  209. package/dist/utils/context-helpers.d.ts +108 -1
  210. package/dist/utils/context-helpers.d.ts.map +1 -1
  211. package/dist/utils/context-helpers.js +351 -2
  212. package/dist/utils/context-helpers.js.map +1 -1
  213. package/dist/utils/environment-context.d.ts +76 -0
  214. package/dist/utils/environment-context.d.ts.map +1 -0
  215. package/dist/utils/environment-context.js +271 -0
  216. package/dist/utils/environment-context.js.map +1 -0
  217. package/dist/utils/intent-detector.d.ts +66 -0
  218. package/dist/utils/intent-detector.d.ts.map +1 -0
  219. package/dist/utils/intent-detector.js +282 -0
  220. package/dist/utils/intent-detector.js.map +1 -0
  221. package/dist/utils/parsed-file.d.ts +51 -0
  222. package/dist/utils/parsed-file.d.ts.map +1 -0
  223. package/dist/utils/parsed-file.js +95 -0
  224. package/dist/utils/parsed-file.js.map +1 -0
  225. package/dist/utils/route-hierarchy.d.ts +50 -0
  226. package/dist/utils/route-hierarchy.d.ts.map +1 -0
  227. package/dist/utils/route-hierarchy.js +226 -0
  228. package/dist/utils/route-hierarchy.js.map +1 -0
  229. package/dist/utils/schema-semantics.d.ts +45 -0
  230. package/dist/utils/schema-semantics.d.ts.map +1 -0
  231. package/dist/utils/schema-semantics.js +193 -0
  232. package/dist/utils/schema-semantics.js.map +1 -0
  233. package/package.json +1 -1
  234. package/src/__tests__/benchmark/fixtures/layer2/index.ts +12 -0
  235. package/src/__tests__/benchmark/fixtures/layer2/phase5-excessive-agency.ts +580 -0
  236. package/src/__tests__/benchmark/fixtures/layer2/sprint6-ai-enhancements.ts +515 -0
  237. package/src/__tests__/benchmark/run-depth-validation.ts +9 -9
  238. package/src/__tests__/category-filter.test.ts +478 -0
  239. package/src/__tests__/regression/known-false-positives.test.ts +490 -0
  240. package/src/__tests__/snapshots/__snapshots__/anthropic-validation-refactor.test.ts.snap +18 -14
  241. package/src/__tests__/snapshots/__snapshots__/scan-depth.test.ts.snap +0 -9
  242. package/src/__tests__/snapshots/anthropic-validation-refactor.test.ts +1 -1
  243. package/src/__tests__/validation/run-validation.ts +7 -7
  244. package/src/ai-context/__tests__/manager.test.ts +193 -0
  245. package/src/ai-context/index.ts +15 -0
  246. package/src/ai-context/manager.ts +145 -0
  247. package/src/baseline/__tests__/manager.test.ts +2 -2
  248. package/src/category-filter.ts +400 -0
  249. package/src/filtering/__tests__/pipeline.test.ts +134 -0
  250. package/src/filtering/context-adjustments.ts +111 -0
  251. package/src/filtering/index.ts +10 -0
  252. package/src/filtering/pipeline.ts +130 -0
  253. package/src/formatters/__tests__/ai-context.test.ts +254 -0
  254. package/src/formatters/ai-context.ts +302 -0
  255. package/src/formatters/github-comment.ts +3 -3
  256. package/src/formatters/ide/__tests__/ide.test.ts +319 -0
  257. package/src/formatters/ide/claude-code.ts +110 -0
  258. package/src/formatters/ide/cursor.ts +147 -0
  259. package/src/formatters/ide/index.ts +216 -0
  260. package/src/formatters/ide/windsurf.ts +135 -0
  261. package/src/formatters/index.ts +24 -0
  262. package/src/index.ts +312 -34
  263. package/src/layer1/comments.ts +3 -1
  264. package/src/layer1/config-audit.ts +50 -11
  265. package/src/layer1/config-mcp-audit.ts +4 -2
  266. package/src/layer1/entropy.ts +234 -1
  267. package/src/layer1/file-flags.ts +17 -6
  268. package/src/layer1/index.ts +14 -18
  269. package/src/layer1/patterns.ts +42 -4
  270. package/src/layer1/urls.ts +188 -14
  271. package/src/layer1/weak-crypto.ts +168 -16
  272. package/src/layer2/ai-agent-tools.ts +707 -2
  273. package/src/layer2/ai-endpoint-protection.ts +3 -1
  274. package/src/layer2/ai-execution-sinks.ts +265 -43
  275. package/src/layer2/ai-fingerprinting.ts +28 -32
  276. package/src/layer2/ai-mcp-security.ts +206 -3
  277. package/src/layer2/ai-package-hallucination.ts +153 -4
  278. package/src/layer2/ai-prompt-hygiene.ts +369 -26
  279. package/src/layer2/ai-rag-safety.ts +85 -2
  280. package/src/layer2/ai-schema-validation.ts +4 -2
  281. package/src/layer2/auth-antipatterns.ts +230 -20
  282. package/src/layer2/byok-patterns.ts +4 -2
  283. package/src/layer2/dangerous-functions/dom-xss.ts +94 -22
  284. package/src/layer2/dangerous-functions/index.ts +635 -51
  285. package/src/layer2/dangerous-functions/math-random.ts +268 -16
  286. package/src/layer2/dangerous-functions/patterns.ts +3 -1
  287. package/src/layer2/dangerous-functions/utils/control-flow.ts +8 -135
  288. package/src/layer2/dangerous-functions/utils/schema-validation.ts +16 -1
  289. package/src/layer2/data-exposure.ts +13 -38
  290. package/src/layer2/framework-checks.ts +4 -2
  291. package/src/layer2/index.ts +69 -50
  292. package/src/layer2/logic-gates.ts +59 -22
  293. package/src/layer2/model-supply-chain.ts +79 -4
  294. package/src/layer2/risky-imports.ts +4 -2
  295. package/src/layer2/variables.ts +4 -2
  296. package/src/layer3/anthropic/auto-dismiss.ts +11 -0
  297. package/src/modes/incremental.ts +1 -1
  298. package/src/tiers.ts +9 -9
  299. package/src/types.ts +122 -8
  300. package/src/utils/__tests__/code-analysis.test.ts +165 -0
  301. package/src/utils/__tests__/parsed-file.test.ts +124 -0
  302. package/src/utils/code-analysis.ts +179 -0
  303. package/src/utils/comment-analyzer.ts +249 -0
  304. package/src/utils/context-helpers.ts +408 -2
  305. package/src/utils/environment-context.ts +304 -0
  306. package/src/utils/intent-detector.ts +318 -0
  307. package/src/utils/parsed-file.ts +103 -0
  308. package/src/utils/route-hierarchy.ts +250 -0
  309. package/src/utils/schema-semantics.ts +233 -0
@@ -0,0 +1,193 @@
1
+ /**
2
+ * AIContextManager Tests
3
+ */
4
+
5
+ import { existsSync, mkdirSync, rmSync, writeFileSync, readFileSync } from 'fs'
6
+ import { join } from 'path'
7
+ import { tmpdir } from 'os'
8
+ import { AIContextManager } from '../manager'
9
+
10
+ describe('AIContextManager', () => {
11
+ const testDir = join(tmpdir(), 'oculum-ai-context-test-' + Date.now())
12
+
13
+ beforeAll(() => {
14
+ // Create test directory
15
+ mkdirSync(testDir, { recursive: true })
16
+ })
17
+
18
+ afterAll(() => {
19
+ // Clean up test directory
20
+ try {
21
+ rmSync(testDir, { recursive: true, force: true })
22
+ } catch {
23
+ // Ignore cleanup errors
24
+ }
25
+ })
26
+
27
+ beforeEach(() => {
28
+ // Clean up .oculum directory before each test
29
+ const oculumDir = join(testDir, '.oculum')
30
+ try {
31
+ rmSync(oculumDir, { recursive: true, force: true })
32
+ } catch {
33
+ // Ignore errors
34
+ }
35
+ })
36
+
37
+ describe('saveContext', () => {
38
+ it('should create .oculum directory if not exists', () => {
39
+ const manager = new AIContextManager({ projectPath: testDir })
40
+ const content = '# AI Context\n\nTest content'
41
+
42
+ const result = manager.saveContext(content)
43
+
44
+ expect(result.success).toBe(true)
45
+ expect(existsSync(join(testDir, '.oculum'))).toBe(true)
46
+ expect(existsSync(join(testDir, '.oculum', 'ai-context.md'))).toBe(true)
47
+ })
48
+
49
+ it('should save ai-context.md and return { success, path }', () => {
50
+ const manager = new AIContextManager({ projectPath: testDir })
51
+ const content = '# Security Issues\n\n1. SQL Injection in users.ts:42'
52
+
53
+ const result = manager.saveContext(content)
54
+
55
+ expect(result.success).toBe(true)
56
+ expect(result.path).toBe(join(testDir, '.oculum', 'ai-context.md'))
57
+
58
+ const savedContent = readFileSync(result.path, 'utf-8')
59
+ expect(savedContent).toBe(content)
60
+ })
61
+
62
+ it('should overwrite existing ai-context.md', () => {
63
+ const manager = new AIContextManager({ projectPath: testDir })
64
+
65
+ // Save initial content
66
+ manager.saveContext('Initial content')
67
+
68
+ // Save new content
69
+ const newContent = 'Updated content'
70
+ const result = manager.saveContext(newContent)
71
+
72
+ expect(result.success).toBe(true)
73
+ const savedContent = readFileSync(result.path, 'utf-8')
74
+ expect(savedContent).toBe(newContent)
75
+ })
76
+
77
+ it('should return { success: false, error } on permission error', () => {
78
+ // Use an invalid path that will cause an error
79
+ const manager = new AIContextManager({ projectPath: '/nonexistent/path/that/does/not/exist' })
80
+
81
+ const result = manager.saveContext('test content')
82
+
83
+ expect(result.success).toBe(false)
84
+ expect(result.error).toBeDefined()
85
+ expect(result.error).toContain('Failed to save')
86
+ })
87
+ })
88
+
89
+ describe('loadContext', () => {
90
+ it('should load existing context file', () => {
91
+ const manager = new AIContextManager({ projectPath: testDir })
92
+ const content = '# AI Context\n\nSome security findings'
93
+
94
+ // Save first
95
+ manager.saveContext(content)
96
+
97
+ // Load
98
+ const result = manager.loadContext()
99
+
100
+ expect(result.found).toBe(true)
101
+ expect(result.content).toBe(content)
102
+ })
103
+
104
+ it('should return { found: false } when file does not exist', () => {
105
+ const manager = new AIContextManager({ projectPath: testDir })
106
+
107
+ const result = manager.loadContext()
108
+
109
+ expect(result.found).toBe(false)
110
+ expect(result.content).toBeUndefined()
111
+ })
112
+
113
+ it('should return error if file cannot be read', () => {
114
+ // Create a directory with the same name as the file (to cause read error)
115
+ const oculumDir = join(testDir, '.oculum')
116
+ const contextPath = join(oculumDir, 'ai-context.md')
117
+
118
+ mkdirSync(oculumDir, { recursive: true })
119
+ mkdirSync(contextPath, { recursive: true }) // Create as directory
120
+
121
+ const manager = new AIContextManager({ projectPath: testDir })
122
+ const result = manager.loadContext()
123
+
124
+ expect(result.found).toBe(false)
125
+ expect(result.error).toBeDefined()
126
+ })
127
+ })
128
+
129
+ describe('clearContext', () => {
130
+ it('should clear context file and return { success: true, existed: true }', () => {
131
+ const manager = new AIContextManager({ projectPath: testDir })
132
+
133
+ // Save first
134
+ manager.saveContext('Some content')
135
+ expect(manager.hasContext()).toBe(true)
136
+
137
+ // Clear
138
+ const result = manager.clearContext()
139
+
140
+ expect(result.success).toBe(true)
141
+ expect(result.existed).toBe(true)
142
+ expect(manager.hasContext()).toBe(false)
143
+ })
144
+
145
+ it('should return { success: true, existed: false } when no context exists', () => {
146
+ const manager = new AIContextManager({ projectPath: testDir })
147
+
148
+ const result = manager.clearContext()
149
+
150
+ expect(result.success).toBe(true)
151
+ expect(result.existed).toBe(false)
152
+ })
153
+ })
154
+
155
+ describe('hasContext', () => {
156
+ it('should return true when ai-context.md exists', () => {
157
+ const manager = new AIContextManager({ projectPath: testDir })
158
+ manager.saveContext('test')
159
+
160
+ expect(manager.hasContext()).toBe(true)
161
+ })
162
+
163
+ it('should return false when ai-context.md does not exist', () => {
164
+ const manager = new AIContextManager({ projectPath: testDir })
165
+
166
+ expect(manager.hasContext()).toBe(false)
167
+ })
168
+ })
169
+
170
+ describe('getContextPath', () => {
171
+ it('should return the full path to ai-context.md', () => {
172
+ const manager = new AIContextManager({ projectPath: testDir })
173
+
174
+ const path = manager.getContextPath()
175
+
176
+ expect(path).toBe(join(testDir, '.oculum', 'ai-context.md'))
177
+ })
178
+ })
179
+
180
+ describe('constructor', () => {
181
+ it('should support string argument for backward compatibility', () => {
182
+ const manager = new AIContextManager(testDir)
183
+
184
+ expect(manager.getContextPath()).toBe(join(testDir, '.oculum', 'ai-context.md'))
185
+ })
186
+
187
+ it('should support options object', () => {
188
+ const manager = new AIContextManager({ projectPath: testDir })
189
+
190
+ expect(manager.getContextPath()).toBe(join(testDir, '.oculum', 'ai-context.md'))
191
+ })
192
+ })
193
+ })
@@ -0,0 +1,15 @@
1
+ /**
2
+ * AI Context Module
3
+ * Exports for AI context management functionality
4
+ */
5
+
6
+ export {
7
+ AIContextManager,
8
+ AI_CONTEXT_FILE,
9
+ AI_CONTEXT_PATH,
10
+ OCULUM_DIR,
11
+ type AIContextManagerOptions,
12
+ type SaveContextResult,
13
+ type LoadContextResult,
14
+ type ClearContextResult,
15
+ } from './manager'
@@ -0,0 +1,145 @@
1
+ /**
2
+ * AI Context Manager
3
+ * Handles loading, saving, and clearing AI context files
4
+ */
5
+
6
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from 'fs'
7
+ import { join } from 'path'
8
+
9
+ /** AI context file name */
10
+ export const AI_CONTEXT_FILE = 'ai-context.md'
11
+
12
+ /** Directory for oculum files */
13
+ export const OCULUM_DIR = '.oculum'
14
+
15
+ /** Full path to AI context file (relative to project root) */
16
+ export const AI_CONTEXT_PATH = `${OCULUM_DIR}/${AI_CONTEXT_FILE}`
17
+
18
+ export interface AIContextManagerOptions {
19
+ /** Project root path */
20
+ projectPath: string
21
+ }
22
+
23
+ export interface SaveContextResult {
24
+ /** Whether the save was successful */
25
+ success: boolean
26
+ /** Path where context was saved */
27
+ path: string
28
+ /** Error message (if failed) */
29
+ error?: string
30
+ }
31
+
32
+ export interface LoadContextResult {
33
+ /** Whether a context file was found */
34
+ found: boolean
35
+ /** The context content (if found) */
36
+ content?: string
37
+ /** Error message (if failed to load) */
38
+ error?: string
39
+ }
40
+
41
+ export interface ClearContextResult {
42
+ /** Whether the clear was successful */
43
+ success: boolean
44
+ /** Whether a context file existed before clearing */
45
+ existed: boolean
46
+ /** Error message (if failed) */
47
+ error?: string
48
+ }
49
+
50
+ /**
51
+ * Manages AI context files for IDE consumption
52
+ */
53
+ export class AIContextManager {
54
+ private projectPath: string
55
+ private contextPath: string
56
+
57
+ constructor(options: AIContextManagerOptions | string) {
58
+ // Support both old string arg and new options object
59
+ if (typeof options === 'string') {
60
+ this.projectPath = options
61
+ } else {
62
+ this.projectPath = options.projectPath
63
+ }
64
+ this.contextPath = join(this.projectPath, OCULUM_DIR, AI_CONTEXT_FILE)
65
+ }
66
+
67
+ /**
68
+ * Get the full path to the AI context file
69
+ */
70
+ getContextPath(): string {
71
+ return this.contextPath
72
+ }
73
+
74
+ /**
75
+ * Save AI context to .oculum/ai-context.md
76
+ */
77
+ saveContext(content: string): SaveContextResult {
78
+ try {
79
+ // Ensure .oculum directory exists
80
+ const oculumDir = join(this.projectPath, OCULUM_DIR)
81
+ if (!existsSync(oculumDir)) {
82
+ mkdirSync(oculumDir, { recursive: true })
83
+ }
84
+
85
+ // Write content to file
86
+ writeFileSync(this.contextPath, content)
87
+
88
+ return { success: true, path: this.contextPath }
89
+ } catch (err) {
90
+ return {
91
+ success: false,
92
+ path: this.contextPath,
93
+ error: `Failed to save AI context: ${err instanceof Error ? err.message : 'Unknown error'}`,
94
+ }
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Load AI context from .oculum/ai-context.md
100
+ */
101
+ loadContext(): LoadContextResult {
102
+ if (!existsSync(this.contextPath)) {
103
+ return { found: false }
104
+ }
105
+
106
+ try {
107
+ const content = readFileSync(this.contextPath, 'utf-8')
108
+ return { found: true, content }
109
+ } catch (err) {
110
+ return {
111
+ found: false,
112
+ error: `Failed to read AI context: ${err instanceof Error ? err.message : 'Unknown error'}`,
113
+ }
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Clear (delete) the AI context file
119
+ */
120
+ clearContext(): ClearContextResult {
121
+ const existed = existsSync(this.contextPath)
122
+
123
+ if (!existed) {
124
+ return { success: true, existed: false }
125
+ }
126
+
127
+ try {
128
+ unlinkSync(this.contextPath)
129
+ return { success: true, existed: true }
130
+ } catch (err) {
131
+ return {
132
+ success: false,
133
+ existed: true,
134
+ error: `Failed to clear AI context: ${err instanceof Error ? err.message : 'Unknown error'}`,
135
+ }
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Check if an AI context file exists
141
+ */
142
+ hasContext(): boolean {
143
+ return existsSync(this.contextPath)
144
+ }
145
+ }
@@ -142,7 +142,7 @@ describe('BaselineManager', () => {
142
142
  timestamp: '2024-01-15T10:00:00.000Z',
143
143
  }
144
144
 
145
- const result = manager.saveBaseline(scanResult, { scanDepth: 'cheap' })
145
+ const result = manager.saveBaseline(scanResult, { scanDepth: 'local' })
146
146
 
147
147
  expect(result.success).toBe(true)
148
148
  expect(existsSync(result.path)).toBe(true)
@@ -155,7 +155,7 @@ describe('BaselineManager', () => {
155
155
  expect(baseline.findings).toHaveLength(1)
156
156
  expect(baseline.findings[0].title).toBe('Hardcoded API key')
157
157
  expect(baseline.stats.critical).toBe(1)
158
- expect(baseline.scanDepth).toBe('cheap')
158
+ expect(baseline.scanDepth).toBe('local')
159
159
  })
160
160
 
161
161
  it('should create .oculum directory if it does not exist', () => {