@skillsmith/core 0.10.0 → 0.11.1

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 (162) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +7 -5
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/src/analysis/tree-sitter/pythonIncremental.d.ts +23 -0
  5. package/dist/src/analysis/tree-sitter/pythonIncremental.d.ts.map +1 -1
  6. package/dist/src/analysis/tree-sitter/pythonIncremental.hardening.test.js +137 -0
  7. package/dist/src/analysis/tree-sitter/pythonIncremental.hardening.test.js.map +1 -1
  8. package/dist/src/analysis/tree-sitter/pythonIncremental.js +83 -10
  9. package/dist/src/analysis/tree-sitter/pythonIncremental.js.map +1 -1
  10. package/dist/src/analysis/tree-sitter/queryExtractionMatchesOrExceedsRegex.test.js +6 -1
  11. package/dist/src/analysis/tree-sitter/queryExtractionMatchesOrExceedsRegex.test.js.map +1 -1
  12. package/dist/src/config/audit-notify-state.d.ts +46 -0
  13. package/dist/src/config/audit-notify-state.d.ts.map +1 -0
  14. package/dist/src/config/audit-notify-state.js +55 -0
  15. package/dist/src/config/audit-notify-state.js.map +1 -0
  16. package/dist/src/config/audit-notify-state.test.d.ts +9 -0
  17. package/dist/src/config/audit-notify-state.test.d.ts.map +1 -0
  18. package/dist/src/config/audit-notify-state.test.js +63 -0
  19. package/dist/src/config/audit-notify-state.test.js.map +1 -0
  20. package/dist/src/config/index.d.ts +10 -0
  21. package/dist/src/config/index.d.ts.map +1 -1
  22. package/dist/src/config/index.js.map +1 -1
  23. package/dist/src/exports/services.d.ts +1 -1
  24. package/dist/src/exports/services.d.ts.map +1 -1
  25. package/dist/src/exports/services.js +3 -1
  26. package/dist/src/exports/services.js.map +1 -1
  27. package/dist/src/index.d.ts +4 -1
  28. package/dist/src/index.d.ts.map +1 -1
  29. package/dist/src/index.js +7 -1
  30. package/dist/src/index.js.map +1 -1
  31. package/dist/src/logging/context.d.ts +58 -0
  32. package/dist/src/logging/context.d.ts.map +1 -0
  33. package/dist/src/logging/context.js +62 -0
  34. package/dist/src/logging/context.js.map +1 -0
  35. package/dist/src/logging/context.test.d.ts +14 -0
  36. package/dist/src/logging/context.test.d.ts.map +1 -0
  37. package/dist/src/logging/context.test.js +88 -0
  38. package/dist/src/logging/context.test.js.map +1 -0
  39. package/dist/src/logging/index.d.ts +13 -0
  40. package/dist/src/logging/index.d.ts.map +1 -0
  41. package/dist/src/logging/index.js +12 -0
  42. package/dist/src/logging/index.js.map +1 -0
  43. package/dist/src/logging/logger.d.ts +71 -0
  44. package/dist/src/logging/logger.d.ts.map +1 -0
  45. package/dist/src/logging/logger.js +264 -0
  46. package/dist/src/logging/logger.js.map +1 -0
  47. package/dist/src/logging/logger.test.d.ts +9 -0
  48. package/dist/src/logging/logger.test.d.ts.map +1 -0
  49. package/dist/src/logging/logger.test.js +320 -0
  50. package/dist/src/logging/logger.test.js.map +1 -0
  51. package/dist/src/logging/redact.d.ts +35 -0
  52. package/dist/src/logging/redact.d.ts.map +1 -0
  53. package/dist/src/logging/redact.js +152 -0
  54. package/dist/src/logging/redact.js.map +1 -0
  55. package/dist/src/logging/redact.test.d.ts +8 -0
  56. package/dist/src/logging/redact.test.d.ts.map +1 -0
  57. package/dist/src/logging/redact.test.js +330 -0
  58. package/dist/src/logging/redact.test.js.map +1 -0
  59. package/dist/src/logging/rotation.d.ts +75 -0
  60. package/dist/src/logging/rotation.d.ts.map +1 -0
  61. package/dist/src/logging/rotation.js +284 -0
  62. package/dist/src/logging/rotation.js.map +1 -0
  63. package/dist/src/logging/rotation.test.d.ts +11 -0
  64. package/dist/src/logging/rotation.test.d.ts.map +1 -0
  65. package/dist/src/logging/rotation.test.js +132 -0
  66. package/dist/src/logging/rotation.test.js.map +1 -0
  67. package/dist/src/logging/types.d.ts +69 -0
  68. package/dist/src/logging/types.d.ts.map +1 -0
  69. package/dist/src/logging/types.js +9 -0
  70. package/dist/src/logging/types.js.map +1 -0
  71. package/dist/src/security/index.d.ts +2 -0
  72. package/dist/src/security/index.d.ts.map +1 -1
  73. package/dist/src/security/index.js +4 -0
  74. package/dist/src/security/index.js.map +1 -1
  75. package/dist/src/security/scanner/SecurityScanner.hostile-update.d.ts +46 -0
  76. package/dist/src/security/scanner/SecurityScanner.hostile-update.d.ts.map +1 -0
  77. package/dist/src/security/scanner/SecurityScanner.hostile-update.js +209 -0
  78. package/dist/src/security/scanner/SecurityScanner.hostile-update.js.map +1 -0
  79. package/dist/src/security/scanner/index.d.ts +2 -1
  80. package/dist/src/security/scanner/index.d.ts.map +1 -1
  81. package/dist/src/security/scanner/index.js +2 -0
  82. package/dist/src/security/scanner/index.js.map +1 -1
  83. package/dist/src/security/scanner/types.d.ts +21 -0
  84. package/dist/src/security/scanner/types.d.ts.map +1 -1
  85. package/dist/src/services/skill-installation.helpers.d.ts +13 -1
  86. package/dist/src/services/skill-installation.helpers.d.ts.map +1 -1
  87. package/dist/src/services/skill-installation.helpers.js +14 -1
  88. package/dist/src/services/skill-installation.helpers.js.map +1 -1
  89. package/dist/src/services/skill-installation.io.d.ts.map +1 -1
  90. package/dist/src/services/skill-installation.io.js +19 -2
  91. package/dist/src/services/skill-installation.io.js.map +1 -1
  92. package/dist/src/sync/BackgroundSyncService.d.ts +10 -2
  93. package/dist/src/sync/BackgroundSyncService.d.ts.map +1 -1
  94. package/dist/src/sync/BackgroundSyncService.js +46 -4
  95. package/dist/src/sync/BackgroundSyncService.js.map +1 -1
  96. package/dist/src/sync/SyncEngine.d.ts +9 -0
  97. package/dist/src/sync/SyncEngine.d.ts.map +1 -1
  98. package/dist/src/sync/SyncEngine.js +23 -4
  99. package/dist/src/sync/SyncEngine.js.map +1 -1
  100. package/dist/src/sync/access-token.d.ts +27 -0
  101. package/dist/src/sync/access-token.d.ts.map +1 -0
  102. package/dist/src/sync/access-token.js +40 -0
  103. package/dist/src/sync/access-token.js.map +1 -0
  104. package/dist/src/sync/audit-notify-client.d.ts +83 -0
  105. package/dist/src/sync/audit-notify-client.d.ts.map +1 -0
  106. package/dist/src/sync/audit-notify-client.js +126 -0
  107. package/dist/src/sync/audit-notify-client.js.map +1 -0
  108. package/dist/src/sync/audit-notify-client.test.d.ts +14 -0
  109. package/dist/src/sync/audit-notify-client.test.d.ts.map +1 -0
  110. package/dist/src/sync/audit-notify-client.test.js +133 -0
  111. package/dist/src/sync/audit-notify-client.test.js.map +1 -0
  112. package/dist/src/sync/index.d.ts +1 -0
  113. package/dist/src/sync/index.d.ts.map +1 -1
  114. package/dist/src/sync/index.js +2 -0
  115. package/dist/src/sync/index.js.map +1 -1
  116. package/dist/src/sync/inventory-client.d.ts.map +1 -1
  117. package/dist/src/sync/inventory-client.js +5 -16
  118. package/dist/src/sync/inventory-client.js.map +1 -1
  119. package/dist/src/telemetry/posthog.d.ts +15 -0
  120. package/dist/src/telemetry/posthog.d.ts.map +1 -1
  121. package/dist/src/telemetry/posthog.js +6 -1
  122. package/dist/src/telemetry/posthog.js.map +1 -1
  123. package/dist/src/telemetry/wrap.bench.js +5 -0
  124. package/dist/src/telemetry/wrap.bench.js.map +1 -1
  125. package/dist/src/telemetry/wrap.correlation.test.d.ts +20 -0
  126. package/dist/src/telemetry/wrap.correlation.test.d.ts.map +1 -0
  127. package/dist/src/telemetry/wrap.correlation.test.js +174 -0
  128. package/dist/src/telemetry/wrap.correlation.test.js.map +1 -0
  129. package/dist/src/telemetry/wrap.d.ts.map +1 -1
  130. package/dist/src/telemetry/wrap.gate.test.js +5 -0
  131. package/dist/src/telemetry/wrap.gate.test.js.map +1 -1
  132. package/dist/src/telemetry/wrap.js +111 -52
  133. package/dist/src/telemetry/wrap.js.map +1 -1
  134. package/dist/src/telemetry/wrap.marker.test.js +5 -0
  135. package/dist/src/telemetry/wrap.marker.test.js.map +1 -1
  136. package/dist/src/telemetry/wrap.test.js +10 -0
  137. package/dist/src/telemetry/wrap.test.js.map +1 -1
  138. package/dist/src/types.d.ts +6 -0
  139. package/dist/src/types.d.ts.map +1 -1
  140. package/dist/tests/security/scanner-regression-guard.test.js +179 -1
  141. package/dist/tests/security/scanner-regression-guard.test.js.map +1 -1
  142. package/dist/tests/sync/BackgroundSyncService.abort.test.d.ts +17 -0
  143. package/dist/tests/sync/BackgroundSyncService.abort.test.d.ts.map +1 -0
  144. package/dist/tests/sync/BackgroundSyncService.abort.test.js +182 -0
  145. package/dist/tests/sync/BackgroundSyncService.abort.test.js.map +1 -0
  146. package/dist/tests/sync/BackgroundSyncService.test.d.ts +9 -1
  147. package/dist/tests/sync/BackgroundSyncService.test.d.ts.map +1 -1
  148. package/dist/tests/sync/BackgroundSyncService.test.js +8 -3
  149. package/dist/tests/sync/BackgroundSyncService.test.js.map +1 -1
  150. package/dist/tests/sync/SyncEngine.abort.test.d.ts +15 -0
  151. package/dist/tests/sync/SyncEngine.abort.test.d.ts.map +1 -0
  152. package/dist/tests/sync/SyncEngine.abort.test.js +100 -0
  153. package/dist/tests/sync/SyncEngine.abort.test.js.map +1 -0
  154. package/dist/tests/sync/SyncEngine.status.test.d.ts +10 -0
  155. package/dist/tests/sync/SyncEngine.status.test.d.ts.map +1 -0
  156. package/dist/tests/sync/SyncEngine.status.test.js +97 -0
  157. package/dist/tests/sync/SyncEngine.status.test.js.map +1 -0
  158. package/dist/tests/sync/SyncEngine.test.d.ts +22 -1
  159. package/dist/tests/sync/SyncEngine.test.d.ts.map +1 -1
  160. package/dist/tests/sync/SyncEngine.test.js +3 -69
  161. package/dist/tests/sync/SyncEngine.test.js.map +1 -1
  162. package/package.json +7 -2
@@ -0,0 +1,320 @@
1
+ /**
2
+ * SMI-5615: Tests for the shared `logger.ts` factory — crash-proofing,
3
+ * `SKILLSMITH_ERROR_LOG_DISABLE`, `SKILLSMITH_LOG_LEVEL` filtering,
4
+ * redaction-on-disk, and the correlation-ID call-site invariant (F3). All
5
+ * tests point `SKILLSMITH_LOG_DIR` at a temp directory — never the real
6
+ * `~/.skillsmith/logs/`.
7
+ */
8
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
9
+ import { mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
10
+ import { tmpdir } from 'node:os';
11
+ import { join } from 'node:path';
12
+ import { runWithCorrelationId } from './context.js';
13
+ import { createLogger } from './logger.js';
14
+ import { __resetLoggingStateForTests } from './rotation.js';
15
+ let tempDir;
16
+ let originalLogDir;
17
+ let originalDisable;
18
+ let originalLevel;
19
+ beforeEach(() => {
20
+ tempDir = mkdtempSync(join(tmpdir(), 'skillsmith-logger-'));
21
+ originalLogDir = process.env.SKILLSMITH_LOG_DIR;
22
+ originalDisable = process.env.SKILLSMITH_ERROR_LOG_DISABLE;
23
+ originalLevel = process.env.SKILLSMITH_LOG_LEVEL;
24
+ process.env.SKILLSMITH_LOG_DIR = tempDir;
25
+ delete process.env.SKILLSMITH_ERROR_LOG_DISABLE;
26
+ delete process.env.SKILLSMITH_LOG_LEVEL;
27
+ });
28
+ afterEach(async () => {
29
+ await __resetLoggingStateForTests();
30
+ vi.restoreAllMocks();
31
+ const restore = (key, value) => {
32
+ if (value === undefined)
33
+ delete process.env[key];
34
+ else
35
+ process.env[key] = value;
36
+ };
37
+ restore('SKILLSMITH_LOG_DIR', originalLogDir);
38
+ restore('SKILLSMITH_ERROR_LOG_DISABLE', originalDisable);
39
+ restore('SKILLSMITH_LOG_LEVEL', originalLevel);
40
+ rmSync(tempDir, { recursive: true, force: true });
41
+ });
42
+ function findLogFile(surfacePrefix) {
43
+ const files = readdirSync(tempDir).filter((f) => f.startsWith(surfacePrefix));
44
+ expect(files.length).toBeGreaterThan(0);
45
+ return join(tempDir, files[0]);
46
+ }
47
+ async function waitForLogFile(surfacePrefix) {
48
+ return vi.waitFor(() => {
49
+ const files = readdirSync(tempDir).filter((f) => f.startsWith(surfacePrefix));
50
+ expect(files.length).toBeGreaterThan(0);
51
+ return files;
52
+ }, { timeout: 2000, interval: 20 });
53
+ }
54
+ describe('logger.ts — redaction on disk', () => {
55
+ it('writes a redacted message and never persists the raw secret', async () => {
56
+ const logger = createLogger('mcp');
57
+ logger.error('Failed with token sk_live_abcdefghijklmnopqrstuvwx');
58
+ await waitForLogFile('skillsmith-mcp-');
59
+ const content = await vi.waitFor(() => {
60
+ const text = readFileSync(findLogFile('skillsmith-mcp-'), 'utf8');
61
+ expect(text.length).toBeGreaterThan(0);
62
+ return text;
63
+ }, { timeout: 2000, interval: 20 });
64
+ expect(content).toContain('sk_live_[REDACTED]');
65
+ expect(content).not.toContain('abcdefghijklmnopqrstuvwx');
66
+ const record = JSON.parse(content.trim().split('\n')[0]);
67
+ expect(record.level).toBe('error');
68
+ expect(record.surface).toBe('mcp');
69
+ expect(record.msg).toContain('sk_live_[REDACTED]');
70
+ });
71
+ it('redacts secrets inside the `err` detail as well as the message', async () => {
72
+ const logger = createLogger('cli');
73
+ const err = new Error('token=ghp_1234567890abcdefghijklmnopqrstuvwxyz');
74
+ logger.error('tool call failed', { err });
75
+ await waitForLogFile('skillsmith-cli-');
76
+ const content = await vi.waitFor(() => {
77
+ const text = readFileSync(findLogFile('skillsmith-cli-'), 'utf8');
78
+ expect(text.length).toBeGreaterThan(0);
79
+ return text;
80
+ }, { timeout: 2000, interval: 20 });
81
+ const record = JSON.parse(content.trim().split('\n')[0]);
82
+ expect(record.err.message).toContain('ghp_[REDACTED]');
83
+ expect(record.err.message).not.toContain('1234567890abcdefghijklmnopqrstuvwxyz');
84
+ });
85
+ it('strips chalk/ANSI codes from the disk record and redacts a secret that immediately follows one (Mode-B Wave 2 finding)', async () => {
86
+ const logger = createLogger('cli');
87
+ const secret = 'sk_live_' + 'd'.repeat(24);
88
+ // Realistic CLI shape: chalk.red('Error:') directly adjacent to a secret,
89
+ // no space — the exact pattern the diff-audit flagged as a latent gap.
90
+ logger.error(`\x1b[31mError:\x1b[39m${secret} leaked`);
91
+ await waitForLogFile('skillsmith-cli-');
92
+ const content = readFileSync(findLogFile('skillsmith-cli-'), 'utf8');
93
+ const record = JSON.parse(content.trim().split('\n')[0]);
94
+ expect(record.msg).not.toContain('\x1b[');
95
+ expect(record.msg).not.toContain(secret);
96
+ expect(record.msg).toContain('sk_live_[REDACTED]');
97
+ });
98
+ });
99
+ describe('logger.ts — warn/error always mirror to console (plan §1, not just on write failure)', () => {
100
+ // Regression coverage for a Mode-B-adjacent finding caught during Wave 2
101
+ // wire-in: an earlier version of `persistRecord` only called
102
+ // `consoleFallback` when the disk write FAILED. That silently broke the
103
+ // whole point of swapping `console.error`/`warn` call sites in the MCP
104
+ // server and CLI for `logger.error`/`warn` — on the (normal) successful
105
+ // write path, callers would see NOTHING on screen, a regression from
106
+ // today's always-visible `console.error`. The plan is explicit: "the
107
+ // logger writes to disk AND still emits to stderr for warn/error" —
108
+ // unconditionally, not as a failure fallback.
109
+ it('mirrors error to console.error even when the disk write succeeds (writable dir)', async () => {
110
+ const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
111
+ const logger = createLogger('mcp');
112
+ logger.error('boom');
113
+ // console mirror must be synchronous/immediate — a CLI user reading a
114
+ // terminal cannot wait for an async disk write to decide whether to print.
115
+ expect(errorSpy).toHaveBeenCalledWith('boom');
116
+ // ...and the disk record still lands too (both, not either/or).
117
+ await waitForLogFile('skillsmith-mcp-');
118
+ });
119
+ it('mirrors warn to console.warn (not console.error) even when the disk write succeeds', async () => {
120
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
121
+ const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
122
+ const logger = createLogger('cli');
123
+ logger.warn('careful');
124
+ expect(warnSpy).toHaveBeenCalledWith('careful');
125
+ expect(errorSpy).not.toHaveBeenCalled();
126
+ await waitForLogFile('skillsmith-cli-');
127
+ });
128
+ it('redacts the console-mirrored message too, not just the disk record', () => {
129
+ const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
130
+ const logger = createLogger('mcp');
131
+ const secret = 'sk_live_' + 'b'.repeat(24);
132
+ logger.error(`auth failed with ${secret}`);
133
+ const calledWith = errorSpy.mock.calls.map((call) => call[0]).join('\n');
134
+ expect(calledWith).not.toContain(secret);
135
+ expect(calledWith).toContain('sk_live_[REDACTED]');
136
+ });
137
+ it('does NOT mirror info/debug to console on the successful-write path (disk-only by design)', async () => {
138
+ process.env.SKILLSMITH_LOG_LEVEL = 'debug';
139
+ const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
140
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
141
+ const logger = createLogger('mcp');
142
+ logger.info('routine status');
143
+ logger.debug('verbose detail');
144
+ await waitForLogFile('skillsmith-mcp-');
145
+ expect(errorSpy).not.toHaveBeenCalled();
146
+ expect(warnSpy).not.toHaveBeenCalled();
147
+ });
148
+ });
149
+ describe('logger.ts — SKILLSMITH_ERROR_LOG_DISABLE', () => {
150
+ it('accepts "1" and skips writing entirely', async () => {
151
+ process.env.SKILLSMITH_ERROR_LOG_DISABLE = '1';
152
+ const logger = createLogger('mcp');
153
+ logger.error('should not be written');
154
+ // Give any accidental async write a chance to land before asserting absence.
155
+ await new Promise((resolve) => setTimeout(resolve, 100));
156
+ expect(readdirSync(tempDir)).toHaveLength(0);
157
+ });
158
+ it('accepts "true" (case-insensitive)', async () => {
159
+ process.env.SKILLSMITH_ERROR_LOG_DISABLE = 'TRUE';
160
+ const logger = createLogger('mcp');
161
+ logger.error('should not be written');
162
+ await new Promise((resolve) => setTimeout(resolve, 100));
163
+ expect(readdirSync(tempDir)).toHaveLength(0);
164
+ });
165
+ it('logs normally when unset', async () => {
166
+ const logger = createLogger('mcp');
167
+ logger.error('should be written');
168
+ await waitForLogFile('skillsmith-mcp-');
169
+ });
170
+ });
171
+ describe('logger.ts — SKILLSMITH_LOG_LEVEL filtering', () => {
172
+ it('defaults to warn — info/debug are suppressed, warn/error are not', async () => {
173
+ const logger = createLogger('mcp');
174
+ logger.debug('debug message');
175
+ logger.info('info message');
176
+ logger.warn('warn message');
177
+ await waitForLogFile('skillsmith-mcp-');
178
+ const content = readFileSync(findLogFile('skillsmith-mcp-'), 'utf8');
179
+ const lines = content.trim().split('\n').filter(Boolean);
180
+ expect(lines).toHaveLength(1);
181
+ expect(JSON.parse(lines[0]).msg).toBe('warn message');
182
+ });
183
+ it('SKILLSMITH_LOG_LEVEL=debug allows every level through', async () => {
184
+ process.env.SKILLSMITH_LOG_LEVEL = 'debug';
185
+ const logger = createLogger('mcp');
186
+ logger.debug('debug message');
187
+ logger.info('info message');
188
+ logger.warn('warn message');
189
+ logger.error('error message');
190
+ await vi.waitFor(() => {
191
+ const files = readdirSync(tempDir).filter((f) => f.startsWith('skillsmith-mcp-'));
192
+ expect(files.length).toBeGreaterThan(0);
193
+ const content = readFileSync(join(tempDir, files[0]), 'utf8');
194
+ const lines = content.trim().split('\n').filter(Boolean);
195
+ expect(lines).toHaveLength(4);
196
+ return lines;
197
+ }, { timeout: 2000, interval: 20 });
198
+ });
199
+ it('SKILLSMITH_LOG_LEVEL=error suppresses everything but error', async () => {
200
+ process.env.SKILLSMITH_LOG_LEVEL = 'error';
201
+ const logger = createLogger('mcp');
202
+ logger.warn('warn message');
203
+ logger.error('error message');
204
+ await waitForLogFile('skillsmith-mcp-');
205
+ const content = await vi.waitFor(() => {
206
+ const text = readFileSync(findLogFile('skillsmith-mcp-'), 'utf8');
207
+ const lines = text.trim().split('\n').filter(Boolean);
208
+ expect(lines).toHaveLength(1);
209
+ return text;
210
+ }, { timeout: 2000, interval: 20 });
211
+ expect(JSON.parse(content.trim())).toMatchObject({ level: 'error', msg: 'error message' });
212
+ });
213
+ });
214
+ describe('logger.ts — crash-proofing', () => {
215
+ it('never throws and falls back to console when the log directory is unwritable', async () => {
216
+ // A regular FILE at the path where a directory is expected makes
217
+ // `mkdir(..., { recursive: true })` fail with ENOTDIR — portable and
218
+ // permission-independent (unlike relying on running as non-root).
219
+ const blockerFile = join(tempDir, 'blocker');
220
+ writeFileSync(blockerFile, '');
221
+ process.env.SKILLSMITH_LOG_DIR = join(blockerFile, 'logs');
222
+ const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
223
+ const logger = createLogger('mcp');
224
+ expect(() => logger.error('this must not throw')).not.toThrow();
225
+ await vi.waitFor(() => {
226
+ expect(errorSpy).toHaveBeenCalled();
227
+ }, { timeout: 2000, interval: 20 });
228
+ expect(errorSpy.mock.calls.some((call) => call[0] === 'this must not throw')).toBe(true);
229
+ });
230
+ it('routes the warn-level fallback through console.warn, not console.error', async () => {
231
+ const blockerFile = join(tempDir, 'blocker');
232
+ writeFileSync(blockerFile, '');
233
+ process.env.SKILLSMITH_LOG_DIR = join(blockerFile, 'logs');
234
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
235
+ const logger = createLogger('cli');
236
+ expect(() => logger.warn('warn fallback')).not.toThrow();
237
+ await vi.waitFor(() => {
238
+ expect(warnSpy).toHaveBeenCalledWith('warn fallback');
239
+ }, { timeout: 2000, interval: 20 });
240
+ });
241
+ it('never throws even when `details` contains a circular reference, and still persists the record (NEW-2)', async () => {
242
+ const logger = createLogger('mcp');
243
+ const circular = {};
244
+ circular.self = circular;
245
+ expect(() => logger.error('circular details', { blob: circular })).not.toThrow();
246
+ // Before the cycle guard, `redactSensitiveObject` stack-overflowed on
247
+ // `circular`, which aborted the whole record and silently dropped it to
248
+ // the console-only fallback. With the guard, the record — including the
249
+ // correlationId/msg that have nothing to do with the cycle — must still
250
+ // reach disk, with only the offending branch marked `'[Circular]'`.
251
+ await waitForLogFile('skillsmith-mcp-');
252
+ const content = readFileSync(findLogFile('skillsmith-mcp-'), 'utf8');
253
+ const record = JSON.parse(content.trim().split('\n')[0]);
254
+ expect(record.msg).toBe('circular details');
255
+ expect(record.details.blob.self).toBe('[Circular]');
256
+ });
257
+ it('redacts the message before the console fallback, not just the disk record (NEW-1)', async () => {
258
+ const blockerFile = join(tempDir, 'blocker');
259
+ writeFileSync(blockerFile, '');
260
+ process.env.SKILLSMITH_LOG_DIR = join(blockerFile, 'logs');
261
+ const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
262
+ const logger = createLogger('mcp');
263
+ const secret = 'sk_live_' + 'a'.repeat(24);
264
+ expect(() => logger.error(`auth failed with ${secret}`)).not.toThrow();
265
+ await vi.waitFor(() => {
266
+ expect(errorSpy).toHaveBeenCalled();
267
+ }, { timeout: 2000, interval: 20 });
268
+ const calledWith = errorSpy.mock.calls.map((call) => call[0]).join('\n');
269
+ expect(calledWith).not.toContain(secret);
270
+ expect(calledWith).toContain('sk_live_[REDACTED]');
271
+ });
272
+ });
273
+ describe('logger.ts — correlation ID (F3)', () => {
274
+ it('stamps the correlation ID installed for the calling async continuation', async () => {
275
+ const logger = createLogger('mcp');
276
+ await runWithCorrelationId('corr-abc-123', async () => {
277
+ logger.error('inside scope');
278
+ });
279
+ await waitForLogFile('skillsmith-mcp-');
280
+ const content = readFileSync(findLogFile('skillsmith-mcp-'), 'utf8');
281
+ const record = JSON.parse(content.trim().split('\n')[0]);
282
+ expect(record.correlationId).toBe('corr-abc-123');
283
+ });
284
+ it('omits correlationId when called outside any runWithCorrelationId scope', async () => {
285
+ const logger = createLogger('cli');
286
+ logger.error('outside scope');
287
+ await waitForLogFile('skillsmith-cli-');
288
+ const content = readFileSync(findLogFile('skillsmith-cli-'), 'utf8');
289
+ const record = JSON.parse(content.trim().split('\n')[0]);
290
+ expect(record.correlationId).toBeUndefined();
291
+ });
292
+ it('keeps two concurrent, overlapping scopes from observing each others correlation ID', async () => {
293
+ const logger = createLogger('vscode');
294
+ const seen = {};
295
+ await Promise.all([
296
+ runWithCorrelationId('corr-A', async () => {
297
+ await new Promise((resolve) => setTimeout(resolve, 10));
298
+ logger.error('call A');
299
+ }),
300
+ runWithCorrelationId('corr-B', async () => {
301
+ logger.error('call B');
302
+ }),
303
+ ]);
304
+ await vi.waitFor(() => {
305
+ const files = readdirSync(tempDir).filter((f) => f.startsWith('skillsmith-vscode-'));
306
+ expect(files.length).toBeGreaterThan(0);
307
+ const content = readFileSync(join(tempDir, files[0]), 'utf8');
308
+ const lines = content.trim().split('\n').filter(Boolean);
309
+ expect(lines).toHaveLength(2);
310
+ for (const line of lines) {
311
+ const record = JSON.parse(line);
312
+ seen[record.msg] = record.correlationId;
313
+ }
314
+ return lines;
315
+ }, { timeout: 2000, interval: 20 });
316
+ expect(seen['call A']).toBe('corr-A');
317
+ expect(seen['call B']).toBe('corr-B');
318
+ });
319
+ });
320
+ //# sourceMappingURL=logger.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.test.js","sourceRoot":"","sources":["../../../src/logging/logger.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * SMI-883 / SMI-5615: Sensitive-data redaction, ported VERBATIM from the dead
3
+ * `packages/mcp-server/src/logger.ts` (lines 11-107) into the shared
4
+ * `@skillsmith/core` logging module. Same regex patterns, same behavior,
5
+ * same test coverage (see `redact.test.ts`) — this is tested, working code
6
+ * relocated, not rewritten.
7
+ */
8
+ export declare function stripAnsi(text: string): string;
9
+ /**
10
+ * SMI-883: Redact sensitive data from text before logging
11
+ * Exported for testing purposes
12
+ */
13
+ export declare function redactSensitiveData(text: string): string;
14
+ /**
15
+ * SMI-883: Recursively redact sensitive data from objects
16
+ * Exported for testing purposes
17
+ *
18
+ * SMI-5615 Mode-B diff-audit NEW-2: `seen` guards against circular
19
+ * references. The original (dead) `mcp-server/src/logger.ts` version this was
20
+ * ported from had no cycle guard — harmless there since it was never called
21
+ * in production, but this module IS the production write path now, so a
22
+ * circular `details`/`err` object (e.g. a thrown object with a `cause` cycle)
23
+ * must degrade to `'[Circular]'` rather than stack-overflowing, which would
24
+ * otherwise abort the whole record (including the redacted `msg` and
25
+ * `correlationId`) and fall through to the raw-message console fallback.
26
+ *
27
+ * `seen` tracks only the CURRENT recursion path (ancestors), not every object
28
+ * visited overall — each object is removed again once its own subtree
29
+ * finishes (the `finally` below). This is deliberate: the same object
30
+ * appearing twice at sibling positions (a shared, non-cyclic reference — a
31
+ * DAG, not a cycle) must redact normally both times, not get falsely flagged
32
+ * `'[Circular]'` just because it was visited once already elsewhere.
33
+ */
34
+ export declare function redactSensitiveObject(obj: unknown, seen?: WeakSet<object>): unknown;
35
+ //# sourceMappingURL=redact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../../../src/logging/redact.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAqBH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG9C;AAmED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CASxD;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,OAAO,EACZ,IAAI,GAAE,OAAO,CAAC,MAAM,CAAiB,GACpC,OAAO,CAgBT"}
@@ -0,0 +1,152 @@
1
+ /**
2
+ * SMI-883 / SMI-5615: Sensitive-data redaction, ported VERBATIM from the dead
3
+ * `packages/mcp-server/src/logger.ts` (lines 11-107) into the shared
4
+ * `@skillsmith/core` logging module. Same regex patterns, same behavior,
5
+ * same test coverage (see `redact.test.ts`) — this is tested, working code
6
+ * relocated, not rewritten.
7
+ */
8
+ /**
9
+ * Strips ANSI/SGR escape sequences (`\x1b[...m` — the `chalk`-style color
10
+ * codes CLI call sites wrap error text in). SMI-5615 Mode-B diff-audit
11
+ * (Wave 2 pass): the disk-persisted record must not embed raw escape codes
12
+ * as log noise, AND a secret sitting immediately after an escape code's
13
+ * trailing letter (e.g. `\x1b[31msk_live_...`) can defeat
14
+ * `redactSensitiveData`'s leading `\b` word-boundary check — the `m`→`s`
15
+ * transition is word-to-word, not a boundary. Stripping ANSI BEFORE redacting
16
+ * closes that gap. Only applied on the DISK-persisted path (`logger.ts`'s
17
+ * `buildRecord`/`normalizeError`) — deliberately NOT applied to the
18
+ * console-mirrored path, which must keep chalk's colors for CLI terminal UX.
19
+ * Covers chalk's actual output (SGR codes only); not a full ANSI/VT100 strip.
20
+ */
21
+ // \x1b is the intentional ESC byte an SGR sequence always starts with; this
22
+ // is the same pattern the widely-used `strip-ansi` npm package uses (also
23
+ // eslint-disabled there).
24
+ // eslint-disable-next-line no-control-regex
25
+ const ANSI_SGR_PATTERN = /\x1b\[[0-9;]*m/g;
26
+ export function stripAnsi(text) {
27
+ if (!text)
28
+ return text;
29
+ return text.replace(ANSI_SGR_PATTERN, '');
30
+ }
31
+ /**
32
+ * SMI-883: Sensitive data patterns to redact before logging
33
+ * Prevents API keys, tokens, passwords, and secrets from being written to disk
34
+ */
35
+ const SENSITIVE_PATTERNS = [
36
+ // GitHub tokens
37
+ { pattern: /\b(ghp_[a-zA-Z0-9]{36})\b/g, replacement: 'ghp_[REDACTED]' },
38
+ { pattern: /\b(github_pat_[a-zA-Z0-9_]{22,})\b/g, replacement: 'github_pat_[REDACTED]' },
39
+ { pattern: /\b(gho_[a-zA-Z0-9]{36})\b/g, replacement: 'gho_[REDACTED]' },
40
+ { pattern: /\b(ghs_[a-zA-Z0-9]{36})\b/g, replacement: 'ghs_[REDACTED]' },
41
+ { pattern: /\b(ghu_[a-zA-Z0-9]{36})\b/g, replacement: 'ghu_[REDACTED]' },
42
+ { pattern: /\b(ghr_[a-zA-Z0-9]{36})\b/g, replacement: 'ghr_[REDACTED]' },
43
+ // Linear API keys
44
+ { pattern: /\b(lin_api_[a-zA-Z0-9]{32,})\b/g, replacement: 'lin_api_[REDACTED]' },
45
+ // Stripe keys
46
+ { pattern: /\b(sk_live_[a-zA-Z0-9]{24,})\b/g, replacement: 'sk_live_[REDACTED]' },
47
+ { pattern: /\b(sk_test_[a-zA-Z0-9]{24,})\b/g, replacement: 'sk_test_[REDACTED]' },
48
+ { pattern: /\b(pk_live_[a-zA-Z0-9]{24,})\b/g, replacement: 'pk_live_[REDACTED]' },
49
+ { pattern: /\b(pk_test_[a-zA-Z0-9]{24,})\b/g, replacement: 'pk_test_[REDACTED]' },
50
+ // OpenAI API keys
51
+ { pattern: /\b(sk-[a-zA-Z0-9]{48,})\b/g, replacement: 'sk-[REDACTED]' },
52
+ // Anthropic API keys
53
+ { pattern: /\b(sk-ant-[a-zA-Z0-9-]{32,})\b/g, replacement: 'sk-ant-[REDACTED]' },
54
+ // AWS keys
55
+ { pattern: /\b(AKIA[A-Z0-9]{16})\b/g, replacement: 'AKIA[REDACTED]' },
56
+ // Slack tokens
57
+ { pattern: /\b(xox[boaprs]-[a-zA-Z0-9-]{10,})\b/g, replacement: 'xox*-[REDACTED]' },
58
+ // npm tokens
59
+ { pattern: /\b(npm_[a-zA-Z0-9]{36})\b/g, replacement: 'npm_[REDACTED]' },
60
+ // Bearer tokens
61
+ { pattern: /\bBearer\s+([a-zA-Z0-9_\-.]{20,})/gi, replacement: 'Bearer [REDACTED]' },
62
+ // Basic auth
63
+ { pattern: /\bBasic\s+([a-zA-Z0-9+/=]{20,})/gi, replacement: 'Basic [REDACTED]' },
64
+ // JWT tokens
65
+ {
66
+ pattern: /\beyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]+\b/g,
67
+ replacement: '[JWT_REDACTED]',
68
+ },
69
+ // Generic API key patterns
70
+ {
71
+ pattern: /\b(api[_-]?key|apikey)\s*[=:]\s*["']?([a-zA-Z0-9_-]{8,})["']?/gi,
72
+ replacement: 'api_key=[REDACTED]',
73
+ },
74
+ {
75
+ pattern: /\b(token|auth[_-]?token)\s*[=:]\s*["']?([a-zA-Z0-9_-]{8,})["']?/gi,
76
+ replacement: 'token=[REDACTED]',
77
+ },
78
+ {
79
+ pattern: /\b(password|passwd|pwd)\s*[=:]\s*["']?([^"'\s]{4,})["']?/gi,
80
+ replacement: 'password=[REDACTED]',
81
+ },
82
+ {
83
+ pattern: /\b(secret|client[_-]?secret)\s*[=:]\s*["']?([a-zA-Z0-9_-]{8,})["']?/gi,
84
+ replacement: 'secret=[REDACTED]',
85
+ },
86
+ // Connection strings with passwords
87
+ { pattern: /(:\/\/[^:]+:)([^@]+)(@)/gi, replacement: '$1[REDACTED]$3' },
88
+ // Private keys
89
+ {
90
+ pattern: /-----BEGIN\s+(RSA\s+)?PRIVATE\s+KEY-----[\s\S]*?-----END\s+(RSA\s+)?PRIVATE\s+KEY-----/gi,
91
+ replacement: '-----[PRIVATE KEY REDACTED]-----',
92
+ },
93
+ ];
94
+ /**
95
+ * SMI-883: Redact sensitive data from text before logging
96
+ * Exported for testing purposes
97
+ */
98
+ export function redactSensitiveData(text) {
99
+ if (!text)
100
+ return text;
101
+ let redacted = text;
102
+ for (const { pattern, replacement } of SENSITIVE_PATTERNS) {
103
+ // Reset lastIndex for global regex patterns to ensure all matches are found
104
+ pattern.lastIndex = 0;
105
+ redacted = redacted.replace(pattern, replacement);
106
+ }
107
+ return redacted;
108
+ }
109
+ /**
110
+ * SMI-883: Recursively redact sensitive data from objects
111
+ * Exported for testing purposes
112
+ *
113
+ * SMI-5615 Mode-B diff-audit NEW-2: `seen` guards against circular
114
+ * references. The original (dead) `mcp-server/src/logger.ts` version this was
115
+ * ported from had no cycle guard — harmless there since it was never called
116
+ * in production, but this module IS the production write path now, so a
117
+ * circular `details`/`err` object (e.g. a thrown object with a `cause` cycle)
118
+ * must degrade to `'[Circular]'` rather than stack-overflowing, which would
119
+ * otherwise abort the whole record (including the redacted `msg` and
120
+ * `correlationId`) and fall through to the raw-message console fallback.
121
+ *
122
+ * `seen` tracks only the CURRENT recursion path (ancestors), not every object
123
+ * visited overall — each object is removed again once its own subtree
124
+ * finishes (the `finally` below). This is deliberate: the same object
125
+ * appearing twice at sibling positions (a shared, non-cyclic reference — a
126
+ * DAG, not a cycle) must redact normally both times, not get falsely flagged
127
+ * `'[Circular]'` just because it was visited once already elsewhere.
128
+ */
129
+ export function redactSensitiveObject(obj, seen = new WeakSet()) {
130
+ if (obj === null || obj === undefined)
131
+ return obj;
132
+ if (typeof obj === 'string')
133
+ return redactSensitiveData(obj);
134
+ if (typeof obj !== 'object')
135
+ return obj;
136
+ if (seen.has(obj))
137
+ return '[Circular]';
138
+ seen.add(obj);
139
+ try {
140
+ if (Array.isArray(obj))
141
+ return obj.map((item) => redactSensitiveObject(item, seen));
142
+ const result = {};
143
+ for (const [key, value] of Object.entries(obj)) {
144
+ result[key] = redactSensitiveObject(value, seen);
145
+ }
146
+ return result;
147
+ }
148
+ finally {
149
+ seen.delete(obj);
150
+ }
151
+ }
152
+ //# sourceMappingURL=redact.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.js","sourceRoot":"","sources":["../../../src/logging/redact.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;;;GAYG;AACH,4EAA4E;AAC5E,0EAA0E;AAC1E,0BAA0B;AAC1B,4CAA4C;AAC5C,MAAM,gBAAgB,GAAG,iBAAiB,CAAA;AAE1C,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAA;IACtB,OAAO,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAA;AAC3C,CAAC;AAED;;;GAGG;AACH,MAAM,kBAAkB,GAAoD;IAC1E,gBAAgB;IAChB,EAAE,OAAO,EAAE,4BAA4B,EAAE,WAAW,EAAE,gBAAgB,EAAE;IACxE,EAAE,OAAO,EAAE,qCAAqC,EAAE,WAAW,EAAE,uBAAuB,EAAE;IACxF,EAAE,OAAO,EAAE,4BAA4B,EAAE,WAAW,EAAE,gBAAgB,EAAE;IACxE,EAAE,OAAO,EAAE,4BAA4B,EAAE,WAAW,EAAE,gBAAgB,EAAE;IACxE,EAAE,OAAO,EAAE,4BAA4B,EAAE,WAAW,EAAE,gBAAgB,EAAE;IACxE,EAAE,OAAO,EAAE,4BAA4B,EAAE,WAAW,EAAE,gBAAgB,EAAE;IACxE,kBAAkB;IAClB,EAAE,OAAO,EAAE,iCAAiC,EAAE,WAAW,EAAE,oBAAoB,EAAE;IACjF,cAAc;IACd,EAAE,OAAO,EAAE,iCAAiC,EAAE,WAAW,EAAE,oBAAoB,EAAE;IACjF,EAAE,OAAO,EAAE,iCAAiC,EAAE,WAAW,EAAE,oBAAoB,EAAE;IACjF,EAAE,OAAO,EAAE,iCAAiC,EAAE,WAAW,EAAE,oBAAoB,EAAE;IACjF,EAAE,OAAO,EAAE,iCAAiC,EAAE,WAAW,EAAE,oBAAoB,EAAE;IACjF,kBAAkB;IAClB,EAAE,OAAO,EAAE,4BAA4B,EAAE,WAAW,EAAE,eAAe,EAAE;IACvE,qBAAqB;IACrB,EAAE,OAAO,EAAE,iCAAiC,EAAE,WAAW,EAAE,mBAAmB,EAAE;IAChF,WAAW;IACX,EAAE,OAAO,EAAE,yBAAyB,EAAE,WAAW,EAAE,gBAAgB,EAAE;IACrE,eAAe;IACf,EAAE,OAAO,EAAE,sCAAsC,EAAE,WAAW,EAAE,iBAAiB,EAAE;IACnF,aAAa;IACb,EAAE,OAAO,EAAE,4BAA4B,EAAE,WAAW,EAAE,gBAAgB,EAAE;IACxE,gBAAgB;IAChB,EAAE,OAAO,EAAE,qCAAqC,EAAE,WAAW,EAAE,mBAAmB,EAAE;IACpF,aAAa;IACb,EAAE,OAAO,EAAE,mCAAmC,EAAE,WAAW,EAAE,kBAAkB,EAAE;IACjF,aAAa;IACb;QACE,OAAO,EAAE,2DAA2D;QACpE,WAAW,EAAE,gBAAgB;KAC9B;IACD,2BAA2B;IAC3B;QACE,OAAO,EAAE,iEAAiE;QAC1E,WAAW,EAAE,oBAAoB;KAClC;IACD;QACE,OAAO,EAAE,mEAAmE;QAC5E,WAAW,EAAE,kBAAkB;KAChC;IACD;QACE,OAAO,EAAE,4DAA4D;QACrE,WAAW,EAAE,qBAAqB;KACnC;IACD;QACE,OAAO,EAAE,uEAAuE;QAChF,WAAW,EAAE,mBAAmB;KACjC;IACD,oCAAoC;IACpC,EAAE,OAAO,EAAE,2BAA2B,EAAE,WAAW,EAAE,gBAAgB,EAAE;IACvE,eAAe;IACf;QACE,OAAO,EACL,0FAA0F;QAC5F,WAAW,EAAE,kCAAkC;KAChD;CACF,CAAA;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAA;IACtB,IAAI,QAAQ,GAAG,IAAI,CAAA;IACnB,KAAK,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,kBAAkB,EAAE,CAAC;QAC1D,4EAA4E;QAC5E,OAAO,CAAC,SAAS,GAAG,CAAC,CAAA;QACrB,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,WAAW,CAAC,CAAA;IACnD,CAAC;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,qBAAqB,CACnC,GAAY,EACZ,OAAwB,IAAI,OAAO,EAAE;IAErC,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,GAAG,CAAA;IACjD,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,mBAAmB,CAAC,GAAG,CAAC,CAAA;IAC5D,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,GAAG,CAAA;IACvC,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,YAAY,CAAA;IACtC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACb,IAAI,CAAC;QACH,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;YAAE,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,qBAAqB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAA;QACnF,MAAM,MAAM,GAA4B,EAAE,CAAA;QAC1C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YAC/C,MAAM,CAAC,GAAG,CAAC,GAAG,qBAAqB,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;QAClD,CAAC;QACD,OAAO,MAAM,CAAA;IACf,CAAC;YAAS,CAAC;QACT,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IAClB,CAAC;AACH,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * SMI-883 / SMI-5615: Tests for logger sensitive data redaction, ported from
3
+ * `packages/mcp-server/tests/unit/logger.test.ts` — same coverage, new
4
+ * location (`@skillsmith/core`'s `logging/redact.ts`).
5
+ * Ensures API keys, tokens, passwords, and secrets are never written to disk.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=redact.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.test.d.ts","sourceRoot":"","sources":["../../../src/logging/redact.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}