@skillsmith/core 0.5.3 → 0.5.4
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/CHANGELOG.md +10 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/src/activation/ActivationManager.d.ts +7 -0
- package/dist/src/activation/ActivationManager.d.ts.map +1 -1
- package/dist/src/activation/ActivationManager.js +13 -4
- package/dist/src/activation/ActivationManager.js.map +1 -1
- package/dist/src/analysis/adapters/python.d.ts +16 -11
- package/dist/src/analysis/adapters/python.d.ts.map +1 -1
- package/dist/src/analysis/adapters/python.js +46 -61
- package/dist/src/analysis/adapters/python.js.map +1 -1
- package/dist/src/analysis/router.test.d.ts +2 -0
- package/dist/src/analysis/router.test.d.ts.map +1 -0
- package/dist/src/analysis/router.test.js +411 -0
- package/dist/src/analysis/router.test.js.map +1 -0
- package/dist/src/analysis/tree-sitter/manager.d.ts.map +1 -1
- package/dist/src/analysis/tree-sitter/manager.js +12 -5
- package/dist/src/analysis/tree-sitter/manager.js.map +1 -1
- package/dist/src/analysis/tree-sitter/pythonExtractor.d.ts +45 -0
- package/dist/src/analysis/tree-sitter/pythonExtractor.d.ts.map +1 -0
- package/dist/src/analysis/tree-sitter/pythonExtractor.js +264 -0
- package/dist/src/analysis/tree-sitter/pythonExtractor.js.map +1 -0
- package/dist/src/analysis/tree-sitter/pythonExtractor.test.d.ts +12 -0
- package/dist/src/analysis/tree-sitter/pythonExtractor.test.d.ts.map +1 -0
- package/dist/src/analysis/tree-sitter/pythonExtractor.test.js +74 -0
- package/dist/src/analysis/tree-sitter/pythonExtractor.test.js.map +1 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.d.ts +93 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.d.ts.map +1 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.hardening.test.d.ts +22 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.hardening.test.d.ts.map +1 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.hardening.test.js +229 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.hardening.test.js.map +1 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.js +287 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.js.map +1 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.test.d.ts +17 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.test.d.ts.map +1 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.test.js +142 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.test.js.map +1 -0
- package/dist/src/analysis/tree-sitter/queries/python.d.ts +43 -0
- package/dist/src/analysis/tree-sitter/queries/python.d.ts.map +1 -0
- package/dist/src/analysis/tree-sitter/queries/python.js +88 -0
- package/dist/src/analysis/tree-sitter/queries/python.js.map +1 -0
- package/dist/src/analysis/tree-sitter/queryExtractionMatchesOrExceedsRegex.test.d.ts +13 -0
- package/dist/src/analysis/tree-sitter/queryExtractionMatchesOrExceedsRegex.test.d.ts.map +1 -0
- package/dist/src/analysis/tree-sitter/queryExtractionMatchesOrExceedsRegex.test.js +174 -0
- package/dist/src/analysis/tree-sitter/queryExtractionMatchesOrExceedsRegex.test.js.map +1 -0
- package/dist/src/analytics/ROIDashboardService.csv.d.ts +11 -0
- package/dist/src/analytics/ROIDashboardService.csv.d.ts.map +1 -0
- package/dist/src/analytics/ROIDashboardService.csv.js +43 -0
- package/dist/src/analytics/ROIDashboardService.csv.js.map +1 -0
- package/dist/src/analytics/ROIDashboardService.d.ts +64 -3
- package/dist/src/analytics/ROIDashboardService.d.ts.map +1 -1
- package/dist/src/analytics/ROIDashboardService.js +116 -45
- package/dist/src/analytics/ROIDashboardService.js.map +1 -1
- package/dist/src/api/schemas.d.ts +70 -319
- package/dist/src/api/schemas.d.ts.map +1 -1
- package/dist/src/benchmarks/incrementalParseBenchmark.d.ts +18 -0
- package/dist/src/benchmarks/incrementalParseBenchmark.d.ts.map +1 -0
- package/dist/src/benchmarks/incrementalParseBenchmark.js +121 -0
- package/dist/src/benchmarks/incrementalParseBenchmark.js.map +1 -0
- package/dist/src/billing/GDPRComplianceService.test.d.ts +2 -0
- package/dist/src/billing/GDPRComplianceService.test.d.ts.map +1 -0
- package/dist/src/billing/GDPRComplianceService.test.js +405 -0
- package/dist/src/billing/GDPRComplianceService.test.js.map +1 -0
- package/dist/src/index.d.ts +4 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/indexer/SkillParser.coverage.test.d.ts +10 -0
- package/dist/src/indexer/SkillParser.coverage.test.d.ts.map +1 -0
- package/dist/src/indexer/SkillParser.coverage.test.js +76 -0
- package/dist/src/indexer/SkillParser.coverage.test.js.map +1 -0
- package/dist/src/indexer/SkillParser.test.d.ts +2 -0
- package/dist/src/indexer/SkillParser.test.d.ts.map +1 -0
- package/dist/src/indexer/SkillParser.test.js +375 -0
- package/dist/src/indexer/SkillParser.test.js.map +1 -0
- package/dist/src/scripts/validation/types.d.ts +14 -24
- package/dist/src/scripts/validation/types.d.ts.map +1 -1
- package/dist/src/services/skill-config-schema.d.ts +4 -36
- package/dist/src/services/skill-config-schema.d.ts.map +1 -1
- package/dist/src/sources/LocalFilesystemAdapter.d.ts +104 -10
- package/dist/src/sources/LocalFilesystemAdapter.d.ts.map +1 -1
- package/dist/src/sources/LocalFilesystemAdapter.helpers.d.ts +92 -0
- package/dist/src/sources/LocalFilesystemAdapter.helpers.d.ts.map +1 -0
- package/dist/src/sources/LocalFilesystemAdapter.helpers.js +157 -0
- package/dist/src/sources/LocalFilesystemAdapter.helpers.js.map +1 -0
- package/dist/src/sources/LocalFilesystemAdapter.js +218 -159
- package/dist/src/sources/LocalFilesystemAdapter.js.map +1 -1
- package/dist/src/sources/LocalFilesystemAdapter.scan.d.ts +78 -0
- package/dist/src/sources/LocalFilesystemAdapter.scan.d.ts.map +1 -0
- package/dist/src/sources/LocalFilesystemAdapter.scan.js +118 -0
- package/dist/src/sources/LocalFilesystemAdapter.scan.js.map +1 -0
- package/dist/src/sources/index.d.ts +1 -1
- package/dist/src/sources/index.d.ts.map +1 -1
- package/dist/src/sources/index.js.map +1 -1
- package/dist/src/sources/types.d.ts +28 -0
- package/dist/src/sources/types.d.ts.map +1 -1
- package/dist/src/telemetry/tracer-imports.d.ts +13 -0
- package/dist/src/telemetry/tracer-imports.d.ts.map +1 -0
- package/dist/src/telemetry/tracer-imports.js +26 -0
- package/dist/src/telemetry/tracer-imports.js.map +1 -0
- package/dist/src/telemetry/tracer.d.ts.map +1 -1
- package/dist/src/telemetry/tracer.js +18 -21
- package/dist/src/telemetry/tracer.js.map +1 -1
- package/dist/src/utils/rate-limit.d.ts +39 -0
- package/dist/src/utils/rate-limit.d.ts.map +1 -0
- package/dist/src/utils/rate-limit.js +48 -0
- package/dist/src/utils/rate-limit.js.map +1 -0
- package/dist/src/utils/rate-limit.test.d.ts +11 -0
- package/dist/src/utils/rate-limit.test.d.ts.map +1 -0
- package/dist/src/utils/rate-limit.test.js +86 -0
- package/dist/src/utils/rate-limit.test.js.map +1 -0
- package/dist/src/webhooks/WebhookDeadLetterRepository.d.ts +178 -0
- package/dist/src/webhooks/WebhookDeadLetterRepository.d.ts.map +1 -0
- package/dist/src/webhooks/WebhookDeadLetterRepository.js +196 -0
- package/dist/src/webhooks/WebhookDeadLetterRepository.js.map +1 -0
- package/dist/src/webhooks/WebhookQueue.d.ts +1 -0
- package/dist/src/webhooks/WebhookQueue.d.ts.map +1 -1
- package/dist/src/webhooks/WebhookQueue.js +19 -0
- package/dist/src/webhooks/WebhookQueue.js.map +1 -1
- package/dist/src/webhooks/WebhookQueue.types.d.ts +11 -0
- package/dist/src/webhooks/WebhookQueue.types.d.ts.map +1 -1
- package/dist/src/webhooks/index.d.ts +1 -0
- package/dist/src/webhooks/index.d.ts.map +1 -1
- package/dist/src/webhooks/index.js +2 -0
- package/dist/src/webhooks/index.js.map +1 -1
- package/dist/src/webhooks/webhook-schemas.d.ts +117 -1212
- package/dist/src/webhooks/webhook-schemas.d.ts.map +1 -1
- package/dist/tests/ActivationManager.test.d.ts +13 -0
- package/dist/tests/ActivationManager.test.d.ts.map +1 -0
- package/dist/tests/ActivationManager.test.js +218 -0
- package/dist/tests/ActivationManager.test.js.map +1 -0
- package/dist/tests/LocalFilesystemAdapter.coverage.test.d.ts +13 -0
- package/dist/tests/LocalFilesystemAdapter.coverage.test.d.ts.map +1 -0
- package/dist/tests/LocalFilesystemAdapter.coverage.test.js +314 -0
- package/dist/tests/LocalFilesystemAdapter.coverage.test.js.map +1 -0
- package/dist/tests/LocalFilesystemAdapter.security.test.d.ts +18 -0
- package/dist/tests/LocalFilesystemAdapter.security.test.d.ts.map +1 -0
- package/dist/tests/LocalFilesystemAdapter.security.test.js +344 -0
- package/dist/tests/LocalFilesystemAdapter.security.test.js.map +1 -0
- package/dist/tests/LocalFilesystemAdapter.test.d.ts +12 -0
- package/dist/tests/LocalFilesystemAdapter.test.d.ts.map +1 -0
- package/dist/tests/LocalFilesystemAdapter.test.js +301 -0
- package/dist/tests/LocalFilesystemAdapter.test.js.map +1 -0
- package/dist/tests/ROIDashboardService.coverage.test.d.ts +9 -0
- package/dist/tests/ROIDashboardService.coverage.test.d.ts.map +1 -0
- package/dist/tests/ROIDashboardService.coverage.test.js +118 -0
- package/dist/tests/ROIDashboardService.coverage.test.js.map +1 -0
- package/dist/tests/ROIDashboardService.test.js +87 -0
- package/dist/tests/ROIDashboardService.test.js.map +1 -1
- package/dist/tests/ScraperAdapters.gitlab-coverage.test.d.ts +14 -0
- package/dist/tests/ScraperAdapters.gitlab-coverage.test.d.ts.map +1 -0
- package/dist/tests/ScraperAdapters.gitlab-coverage.test.js +169 -0
- package/dist/tests/ScraperAdapters.gitlab-coverage.test.js.map +1 -0
- package/dist/tests/ScraperAdapters.test.d.ts +5 -1
- package/dist/tests/ScraperAdapters.test.d.ts.map +1 -1
- package/dist/tests/ScraperAdapters.test.js +6 -336
- package/dist/tests/ScraperAdapters.test.js.map +1 -1
- package/dist/tests/WebhookDeadLetterRepository.test.d.ts +2 -0
- package/dist/tests/WebhookDeadLetterRepository.test.d.ts.map +1 -0
- package/dist/tests/WebhookDeadLetterRepository.test.js +333 -0
- package/dist/tests/WebhookDeadLetterRepository.test.js.map +1 -0
- package/dist/tests/WebhookHandler.test.js +93 -1
- package/dist/tests/WebhookHandler.test.js.map +1 -1
- package/dist/tests/WebhookQueue.coverage.test.d.ts +19 -0
- package/dist/tests/WebhookQueue.coverage.test.d.ts.map +1 -0
- package/dist/tests/WebhookQueue.coverage.test.js +190 -0
- package/dist/tests/WebhookQueue.coverage.test.js.map +1 -0
- package/dist/tests/billing/GDPRCompliance.test.d.ts +2 -2
- package/dist/tests/billing/GDPRCompliance.test.js +221 -36
- package/dist/tests/billing/GDPRCompliance.test.js.map +1 -1
- package/dist/tests/telemetry.test.js +126 -0
- package/dist/tests/telemetry.test.js.map +1 -1
- package/dist/tests/webhooks/WebhookDeadLetterRepository.test.d.ts +10 -0
- package/dist/tests/webhooks/WebhookDeadLetterRepository.test.d.ts.map +1 -0
- package/dist/tests/webhooks/WebhookDeadLetterRepository.test.js +109 -0
- package/dist/tests/webhooks/WebhookDeadLetterRepository.test.js.map +1 -0
- 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
|
-
*
|
|
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
|