@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.
- package/dist/ai-context/index.d.ts +6 -0
- package/dist/ai-context/index.d.ts.map +1 -0
- package/dist/ai-context/index.js +13 -0
- package/dist/ai-context/index.js.map +1 -0
- package/dist/ai-context/manager.d.ts +67 -0
- package/dist/ai-context/manager.d.ts.map +1 -0
- package/dist/ai-context/manager.js +104 -0
- package/dist/ai-context/manager.js.map +1 -0
- package/dist/category-filter.d.ts +125 -0
- package/dist/category-filter.d.ts.map +1 -0
- package/dist/category-filter.js +360 -0
- package/dist/category-filter.js.map +1 -0
- package/dist/filtering/context-adjustments.d.ts +23 -0
- package/dist/filtering/context-adjustments.d.ts.map +1 -0
- package/dist/filtering/context-adjustments.js +100 -0
- package/dist/filtering/context-adjustments.js.map +1 -0
- package/dist/filtering/index.d.ts +3 -0
- package/dist/filtering/index.d.ts.map +1 -0
- package/dist/filtering/index.js +8 -0
- package/dist/filtering/index.js.map +1 -0
- package/dist/filtering/pipeline.d.ts +48 -0
- package/dist/filtering/pipeline.d.ts.map +1 -0
- package/dist/filtering/pipeline.js +76 -0
- package/dist/filtering/pipeline.js.map +1 -0
- package/dist/formatters/ai-context.d.ts +23 -0
- package/dist/formatters/ai-context.d.ts.map +1 -0
- package/dist/formatters/ai-context.js +238 -0
- package/dist/formatters/ai-context.js.map +1 -0
- package/dist/formatters/github-comment.d.ts +1 -1
- package/dist/formatters/github-comment.d.ts.map +1 -1
- package/dist/formatters/github-comment.js +2 -2
- package/dist/formatters/github-comment.js.map +1 -1
- package/dist/formatters/ide/claude-code.d.ts +17 -0
- package/dist/formatters/ide/claude-code.d.ts.map +1 -0
- package/dist/formatters/ide/claude-code.js +94 -0
- package/dist/formatters/ide/claude-code.js.map +1 -0
- package/dist/formatters/ide/cursor.d.ts +13 -0
- package/dist/formatters/ide/cursor.d.ts.map +1 -0
- package/dist/formatters/ide/cursor.js +125 -0
- package/dist/formatters/ide/cursor.js.map +1 -0
- package/dist/formatters/ide/index.d.ts +62 -0
- package/dist/formatters/ide/index.d.ts.map +1 -0
- package/dist/formatters/ide/index.js +184 -0
- package/dist/formatters/ide/index.js.map +1 -0
- package/dist/formatters/ide/windsurf.d.ts +13 -0
- package/dist/formatters/ide/windsurf.d.ts.map +1 -0
- package/dist/formatters/ide/windsurf.js +117 -0
- package/dist/formatters/ide/windsurf.js.map +1 -0
- package/dist/formatters/index.d.ts +2 -0
- package/dist/formatters/index.d.ts.map +1 -1
- package/dist/formatters/index.js +17 -1
- package/dist/formatters/index.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +272 -44
- package/dist/index.js.map +1 -1
- package/dist/layer1/comments.d.ts +4 -1
- package/dist/layer1/comments.d.ts.map +1 -1
- package/dist/layer1/comments.js +1 -1
- package/dist/layer1/comments.js.map +1 -1
- package/dist/layer1/config-audit.d.ts +4 -1
- package/dist/layer1/config-audit.d.ts.map +1 -1
- package/dist/layer1/config-audit.js +45 -11
- package/dist/layer1/config-audit.js.map +1 -1
- package/dist/layer1/config-mcp-audit.d.ts +4 -1
- package/dist/layer1/config-mcp-audit.d.ts.map +1 -1
- package/dist/layer1/config-mcp-audit.js +2 -2
- package/dist/layer1/config-mcp-audit.js.map +1 -1
- package/dist/layer1/entropy.d.ts +4 -1
- package/dist/layer1/entropy.d.ts.map +1 -1
- package/dist/layer1/entropy.js +212 -1
- package/dist/layer1/entropy.js.map +1 -1
- package/dist/layer1/file-flags.d.ts +4 -1
- package/dist/layer1/file-flags.d.ts.map +1 -1
- package/dist/layer1/file-flags.js +12 -5
- package/dist/layer1/file-flags.js.map +1 -1
- package/dist/layer1/index.d.ts.map +1 -1
- package/dist/layer1/index.js +14 -19
- package/dist/layer1/index.js.map +1 -1
- package/dist/layer1/patterns.d.ts +4 -1
- package/dist/layer1/patterns.d.ts.map +1 -1
- package/dist/layer1/patterns.js +34 -4
- package/dist/layer1/patterns.js.map +1 -1
- package/dist/layer1/urls.d.ts +4 -1
- package/dist/layer1/urls.d.ts.map +1 -1
- package/dist/layer1/urls.js +162 -14
- package/dist/layer1/urls.js.map +1 -1
- package/dist/layer1/weak-crypto.d.ts +4 -1
- package/dist/layer1/weak-crypto.d.ts.map +1 -1
- package/dist/layer1/weak-crypto.js +144 -7
- package/dist/layer1/weak-crypto.js.map +1 -1
- package/dist/layer2/ai-agent-tools.d.ts +4 -1
- package/dist/layer2/ai-agent-tools.d.ts.map +1 -1
- package/dist/layer2/ai-agent-tools.js +661 -2
- package/dist/layer2/ai-agent-tools.js.map +1 -1
- package/dist/layer2/ai-endpoint-protection.d.ts +2 -0
- package/dist/layer2/ai-endpoint-protection.d.ts.map +1 -1
- package/dist/layer2/ai-endpoint-protection.js +1 -1
- package/dist/layer2/ai-endpoint-protection.js.map +1 -1
- package/dist/layer2/ai-execution-sinks.d.ts +4 -1
- package/dist/layer2/ai-execution-sinks.d.ts.map +1 -1
- package/dist/layer2/ai-execution-sinks.js +252 -43
- package/dist/layer2/ai-execution-sinks.js.map +1 -1
- package/dist/layer2/ai-fingerprinting.d.ts +4 -1
- package/dist/layer2/ai-fingerprinting.d.ts.map +1 -1
- package/dist/layer2/ai-fingerprinting.js +25 -32
- package/dist/layer2/ai-fingerprinting.js.map +1 -1
- package/dist/layer2/ai-mcp-security.d.ts +4 -1
- package/dist/layer2/ai-mcp-security.d.ts.map +1 -1
- package/dist/layer2/ai-mcp-security.js +200 -2
- package/dist/layer2/ai-mcp-security.js.map +1 -1
- package/dist/layer2/ai-package-hallucination.d.ts +4 -1
- package/dist/layer2/ai-package-hallucination.d.ts.map +1 -1
- package/dist/layer2/ai-package-hallucination.js +136 -4
- package/dist/layer2/ai-package-hallucination.js.map +1 -1
- package/dist/layer2/ai-prompt-hygiene.d.ts +4 -1
- package/dist/layer2/ai-prompt-hygiene.d.ts.map +1 -1
- package/dist/layer2/ai-prompt-hygiene.js +342 -28
- package/dist/layer2/ai-prompt-hygiene.js.map +1 -1
- package/dist/layer2/ai-rag-safety.d.ts +4 -1
- package/dist/layer2/ai-rag-safety.d.ts.map +1 -1
- package/dist/layer2/ai-rag-safety.js +82 -2
- package/dist/layer2/ai-rag-safety.js.map +1 -1
- package/dist/layer2/ai-schema-validation.d.ts +4 -1
- package/dist/layer2/ai-schema-validation.d.ts.map +1 -1
- package/dist/layer2/ai-schema-validation.js +2 -2
- package/dist/layer2/ai-schema-validation.js.map +1 -1
- package/dist/layer2/auth-antipatterns.d.ts +2 -0
- package/dist/layer2/auth-antipatterns.d.ts.map +1 -1
- package/dist/layer2/auth-antipatterns.js +205 -20
- package/dist/layer2/auth-antipatterns.js.map +1 -1
- package/dist/layer2/byok-patterns.d.ts +4 -1
- package/dist/layer2/byok-patterns.d.ts.map +1 -1
- package/dist/layer2/byok-patterns.js +2 -2
- package/dist/layer2/byok-patterns.js.map +1 -1
- package/dist/layer2/dangerous-functions/dom-xss.d.ts +9 -4
- package/dist/layer2/dangerous-functions/dom-xss.d.ts.map +1 -1
- package/dist/layer2/dangerous-functions/dom-xss.js +73 -22
- package/dist/layer2/dangerous-functions/dom-xss.js.map +1 -1
- package/dist/layer2/dangerous-functions/index.d.ts +4 -1
- package/dist/layer2/dangerous-functions/index.d.ts.map +1 -1
- package/dist/layer2/dangerous-functions/index.js +551 -20
- package/dist/layer2/dangerous-functions/index.js.map +1 -1
- package/dist/layer2/dangerous-functions/math-random.d.ts +54 -4
- package/dist/layer2/dangerous-functions/math-random.d.ts.map +1 -1
- package/dist/layer2/dangerous-functions/math-random.js +241 -16
- package/dist/layer2/dangerous-functions/math-random.js.map +1 -1
- package/dist/layer2/dangerous-functions/patterns.d.ts.map +1 -1
- package/dist/layer2/dangerous-functions/patterns.js +3 -1
- package/dist/layer2/dangerous-functions/patterns.js.map +1 -1
- package/dist/layer2/dangerous-functions/utils/control-flow.d.ts +3 -2
- package/dist/layer2/dangerous-functions/utils/control-flow.d.ts.map +1 -1
- package/dist/layer2/dangerous-functions/utils/control-flow.js +41 -120
- package/dist/layer2/dangerous-functions/utils/control-flow.js.map +1 -1
- package/dist/layer2/dangerous-functions/utils/helpers.d.ts.map +1 -1
- package/dist/layer2/dangerous-functions/utils/helpers.js +26 -3
- package/dist/layer2/dangerous-functions/utils/helpers.js.map +1 -1
- package/dist/layer2/dangerous-functions/utils/schema-validation.d.ts.map +1 -1
- package/dist/layer2/dangerous-functions/utils/schema-validation.js +14 -1
- package/dist/layer2/dangerous-functions/utils/schema-validation.js.map +1 -1
- package/dist/layer2/data-exposure.d.ts +4 -1
- package/dist/layer2/data-exposure.d.ts.map +1 -1
- package/dist/layer2/data-exposure.js +11 -38
- package/dist/layer2/data-exposure.js.map +1 -1
- package/dist/layer2/framework-checks.d.ts +4 -1
- package/dist/layer2/framework-checks.d.ts.map +1 -1
- package/dist/layer2/framework-checks.js +2 -2
- package/dist/layer2/framework-checks.js.map +1 -1
- package/dist/layer2/index.d.ts +9 -1
- package/dist/layer2/index.d.ts.map +1 -1
- package/dist/layer2/index.js +57 -51
- package/dist/layer2/index.js.map +1 -1
- package/dist/layer2/logic-gates.d.ts +4 -1
- package/dist/layer2/logic-gates.d.ts.map +1 -1
- package/dist/layer2/logic-gates.js +54 -20
- package/dist/layer2/logic-gates.js.map +1 -1
- package/dist/layer2/model-supply-chain.d.ts +4 -1
- package/dist/layer2/model-supply-chain.d.ts.map +1 -1
- package/dist/layer2/model-supply-chain.js +72 -4
- package/dist/layer2/model-supply-chain.js.map +1 -1
- package/dist/layer2/risky-imports.d.ts +4 -1
- package/dist/layer2/risky-imports.d.ts.map +1 -1
- package/dist/layer2/risky-imports.js +2 -2
- package/dist/layer2/risky-imports.js.map +1 -1
- package/dist/layer2/variables.d.ts +4 -1
- package/dist/layer2/variables.d.ts.map +1 -1
- package/dist/layer2/variables.js +2 -2
- package/dist/layer2/variables.js.map +1 -1
- package/dist/layer3/anthropic/auto-dismiss.d.ts.map +1 -1
- package/dist/layer3/anthropic/auto-dismiss.js +11 -0
- package/dist/layer3/anthropic/auto-dismiss.js.map +1 -1
- package/dist/modes/incremental.js +1 -1
- package/dist/tiers.d.ts +2 -2
- package/dist/tiers.d.ts.map +1 -1
- package/dist/tiers.js +7 -7
- package/dist/tiers.js.map +1 -1
- package/dist/types.d.ts +78 -8
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +34 -0
- package/dist/types.js.map +1 -1
- package/dist/utils/code-analysis.d.ts +39 -0
- package/dist/utils/code-analysis.d.ts.map +1 -0
- package/dist/utils/code-analysis.js +159 -0
- package/dist/utils/code-analysis.js.map +1 -0
- package/dist/utils/comment-analyzer.d.ts +38 -0
- package/dist/utils/comment-analyzer.d.ts.map +1 -0
- package/dist/utils/comment-analyzer.js +218 -0
- package/dist/utils/comment-analyzer.js.map +1 -0
- package/dist/utils/context-helpers.d.ts +108 -1
- package/dist/utils/context-helpers.d.ts.map +1 -1
- package/dist/utils/context-helpers.js +351 -2
- package/dist/utils/context-helpers.js.map +1 -1
- package/dist/utils/environment-context.d.ts +76 -0
- package/dist/utils/environment-context.d.ts.map +1 -0
- package/dist/utils/environment-context.js +271 -0
- package/dist/utils/environment-context.js.map +1 -0
- package/dist/utils/intent-detector.d.ts +66 -0
- package/dist/utils/intent-detector.d.ts.map +1 -0
- package/dist/utils/intent-detector.js +282 -0
- package/dist/utils/intent-detector.js.map +1 -0
- package/dist/utils/parsed-file.d.ts +51 -0
- package/dist/utils/parsed-file.d.ts.map +1 -0
- package/dist/utils/parsed-file.js +95 -0
- package/dist/utils/parsed-file.js.map +1 -0
- package/dist/utils/route-hierarchy.d.ts +50 -0
- package/dist/utils/route-hierarchy.d.ts.map +1 -0
- package/dist/utils/route-hierarchy.js +226 -0
- package/dist/utils/route-hierarchy.js.map +1 -0
- package/dist/utils/schema-semantics.d.ts +45 -0
- package/dist/utils/schema-semantics.d.ts.map +1 -0
- package/dist/utils/schema-semantics.js +193 -0
- package/dist/utils/schema-semantics.js.map +1 -0
- package/package.json +1 -1
- package/src/__tests__/benchmark/fixtures/layer2/index.ts +12 -0
- package/src/__tests__/benchmark/fixtures/layer2/phase5-excessive-agency.ts +580 -0
- package/src/__tests__/benchmark/fixtures/layer2/sprint6-ai-enhancements.ts +515 -0
- package/src/__tests__/benchmark/run-depth-validation.ts +9 -9
- package/src/__tests__/category-filter.test.ts +478 -0
- package/src/__tests__/regression/known-false-positives.test.ts +490 -0
- package/src/__tests__/snapshots/__snapshots__/anthropic-validation-refactor.test.ts.snap +18 -14
- package/src/__tests__/snapshots/__snapshots__/scan-depth.test.ts.snap +0 -9
- package/src/__tests__/snapshots/anthropic-validation-refactor.test.ts +1 -1
- package/src/__tests__/validation/run-validation.ts +7 -7
- package/src/ai-context/__tests__/manager.test.ts +193 -0
- package/src/ai-context/index.ts +15 -0
- package/src/ai-context/manager.ts +145 -0
- package/src/baseline/__tests__/manager.test.ts +2 -2
- package/src/category-filter.ts +400 -0
- package/src/filtering/__tests__/pipeline.test.ts +134 -0
- package/src/filtering/context-adjustments.ts +111 -0
- package/src/filtering/index.ts +10 -0
- package/src/filtering/pipeline.ts +130 -0
- package/src/formatters/__tests__/ai-context.test.ts +254 -0
- package/src/formatters/ai-context.ts +302 -0
- package/src/formatters/github-comment.ts +3 -3
- package/src/formatters/ide/__tests__/ide.test.ts +319 -0
- package/src/formatters/ide/claude-code.ts +110 -0
- package/src/formatters/ide/cursor.ts +147 -0
- package/src/formatters/ide/index.ts +216 -0
- package/src/formatters/ide/windsurf.ts +135 -0
- package/src/formatters/index.ts +24 -0
- package/src/index.ts +312 -34
- package/src/layer1/comments.ts +3 -1
- package/src/layer1/config-audit.ts +50 -11
- package/src/layer1/config-mcp-audit.ts +4 -2
- package/src/layer1/entropy.ts +234 -1
- package/src/layer1/file-flags.ts +17 -6
- package/src/layer1/index.ts +14 -18
- package/src/layer1/patterns.ts +42 -4
- package/src/layer1/urls.ts +188 -14
- package/src/layer1/weak-crypto.ts +168 -16
- package/src/layer2/ai-agent-tools.ts +707 -2
- package/src/layer2/ai-endpoint-protection.ts +3 -1
- package/src/layer2/ai-execution-sinks.ts +265 -43
- package/src/layer2/ai-fingerprinting.ts +28 -32
- package/src/layer2/ai-mcp-security.ts +206 -3
- package/src/layer2/ai-package-hallucination.ts +153 -4
- package/src/layer2/ai-prompt-hygiene.ts +369 -26
- package/src/layer2/ai-rag-safety.ts +85 -2
- package/src/layer2/ai-schema-validation.ts +4 -2
- package/src/layer2/auth-antipatterns.ts +230 -20
- package/src/layer2/byok-patterns.ts +4 -2
- package/src/layer2/dangerous-functions/dom-xss.ts +94 -22
- package/src/layer2/dangerous-functions/index.ts +635 -51
- package/src/layer2/dangerous-functions/math-random.ts +268 -16
- package/src/layer2/dangerous-functions/patterns.ts +3 -1
- package/src/layer2/dangerous-functions/utils/control-flow.ts +8 -135
- package/src/layer2/dangerous-functions/utils/schema-validation.ts +16 -1
- package/src/layer2/data-exposure.ts +13 -38
- package/src/layer2/framework-checks.ts +4 -2
- package/src/layer2/index.ts +69 -50
- package/src/layer2/logic-gates.ts +59 -22
- package/src/layer2/model-supply-chain.ts +79 -4
- package/src/layer2/risky-imports.ts +4 -2
- package/src/layer2/variables.ts +4 -2
- package/src/layer3/anthropic/auto-dismiss.ts +11 -0
- package/src/modes/incremental.ts +1 -1
- package/src/tiers.ts +9 -9
- package/src/types.ts +122 -8
- package/src/utils/__tests__/code-analysis.test.ts +165 -0
- package/src/utils/__tests__/parsed-file.test.ts +124 -0
- package/src/utils/code-analysis.ts +179 -0
- package/src/utils/comment-analyzer.ts +249 -0
- package/src/utils/context-helpers.ts +408 -2
- package/src/utils/environment-context.ts +304 -0
- package/src/utils/intent-detector.ts +318 -0
- package/src/utils/parsed-file.ts +103 -0
- package/src/utils/route-hierarchy.ts +250 -0
- 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: '
|
|
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('
|
|
158
|
+
expect(baseline.scanDepth).toBe('local')
|
|
159
159
|
})
|
|
160
160
|
|
|
161
161
|
it('should create .oculum directory if it does not exist', () => {
|