onbuzz 4.9.13 → 4.10.0
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/node_modules/glob/README.md +31 -5
- package/node_modules/glob/dist/commonjs/glob.d.ts +8 -0
- package/node_modules/glob/dist/commonjs/glob.d.ts.map +1 -1
- package/node_modules/glob/dist/commonjs/glob.js +2 -1
- package/node_modules/glob/dist/commonjs/glob.js.map +1 -1
- package/node_modules/glob/dist/commonjs/index.min.js +3 -3
- package/node_modules/glob/dist/commonjs/index.min.js.map +4 -4
- package/node_modules/glob/dist/commonjs/pattern.d.ts +3 -0
- package/node_modules/glob/dist/commonjs/pattern.d.ts.map +1 -1
- package/node_modules/glob/dist/commonjs/pattern.js +4 -0
- package/node_modules/glob/dist/commonjs/pattern.js.map +1 -1
- package/node_modules/glob/dist/esm/glob.d.ts +8 -0
- package/node_modules/glob/dist/esm/glob.d.ts.map +1 -1
- package/node_modules/glob/dist/esm/glob.js +2 -1
- package/node_modules/glob/dist/esm/glob.js.map +1 -1
- package/node_modules/glob/dist/esm/index.min.js +3 -3
- package/node_modules/glob/dist/esm/index.min.js.map +4 -4
- package/node_modules/glob/dist/esm/pattern.d.ts +3 -0
- package/node_modules/glob/dist/esm/pattern.d.ts.map +1 -1
- package/node_modules/glob/dist/esm/pattern.js +4 -0
- package/node_modules/glob/dist/esm/pattern.js.map +1 -1
- package/node_modules/{@isaacs → glob/node_modules}/balanced-match/README.md +7 -10
- package/node_modules/{@isaacs → glob/node_modules}/balanced-match/package.json +7 -18
- package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/README.md +3 -6
- package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/commonjs/index.js +6 -4
- package/node_modules/glob/node_modules/brace-expansion/dist/commonjs/index.js.map +1 -0
- package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/esm/index.js +6 -4
- package/node_modules/glob/node_modules/brace-expansion/dist/esm/index.js.map +1 -0
- package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/package.json +11 -7
- package/node_modules/glob/node_modules/minimatch/README.md +76 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.d.ts +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.d.ts +4 -2
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.js +309 -55
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.js +2 -4
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.d.ts +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.js +4 -4
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.d.ts +81 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.js +232 -134
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.d.ts +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.js +8 -8
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.d.ts +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/ast.d.ts +4 -2
- package/node_modules/glob/node_modules/minimatch/dist/esm/ast.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/ast.js +309 -55
- package/node_modules/glob/node_modules/minimatch/dist/esm/ast.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.js +2 -4
- package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/escape.d.ts +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/escape.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/escape.js +4 -4
- package/node_modules/glob/node_modules/minimatch/dist/esm/escape.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/index.d.ts +81 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/index.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/index.js +232 -134
- package/node_modules/glob/node_modules/minimatch/dist/esm/index.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.d.ts +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.js +8 -8
- package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/package.json +17 -11
- package/node_modules/glob/package.json +10 -13
- package/node_modules/minipass/LICENSE.md +55 -0
- package/node_modules/minipass/dist/commonjs/index.d.ts +12 -16
- package/node_modules/minipass/dist/commonjs/index.d.ts.map +1 -1
- package/node_modules/minipass/dist/commonjs/index.js +13 -3
- package/node_modules/minipass/dist/commonjs/index.js.map +1 -1
- package/node_modules/minipass/dist/esm/index.d.ts +12 -16
- package/node_modules/minipass/dist/esm/index.d.ts.map +1 -1
- package/node_modules/minipass/dist/esm/index.js +3 -1
- package/node_modules/minipass/dist/esm/index.js.map +1 -1
- package/node_modules/minipass/package.json +9 -14
- package/node_modules/path-scurry/node_modules/lru-cache/README.md +96 -10
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel-browser.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel-browser.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel.d.ts +5 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel.js +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.d.ts +1400 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.js +1726 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.min.js +2 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.min.js.map +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.d.ts +12 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.js +10 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel-cjs.cjs.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel-cjs.d.cts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel.d.ts +5 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel.js +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.d.ts +109 -32
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.d.ts.map +1 -1
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.js +334 -197
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.js.map +1 -1
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.min.js +1 -1
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.min.js.map +4 -4
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel-node.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel-node.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel.d.ts +5 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel.js +9 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.d.ts +1400 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.js +1726 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.min.js +2 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.min.js.map +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.d.ts +12 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.js +10 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.d.ts +12 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.js +10 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel-browser.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel-browser.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel.d.ts +5 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel.js +4 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.d.ts +1400 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.js +1722 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.min.js +2 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.min.js.map +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.d.ts +12 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.js +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel-esm.d.mts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel-esm.mjs.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel.d.ts +5 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel.js +19 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.d.ts +109 -32
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.d.ts.map +1 -1
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.js +333 -196
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.js.map +1 -1
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.min.js +1 -1
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.min.js.map +4 -4
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel-node.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel-node.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel.d.ts +5 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel.js +6 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.d.ts +1400 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.js +1722 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.min.js +2 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.min.js.map +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.d.ts +12 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.js +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.d.ts +12 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.js +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/package.json +71 -18
- package/node_modules/path-scurry/package.json +8 -24
- package/package.json +1 -1
- package/scripts/debug-balance-probe.mjs +35 -35
- package/scripts/push-image.sh +43 -43
- package/scripts/setup-acr.sh +65 -65
- package/scripts/verify-optional-deps.js +96 -1
- package/src/__tests__/composioCliFlags.test.js +239 -239
- package/src/analyzers/CSSAnalyzer.js +298 -297
- package/src/analyzers/ConfigValidator.js +691 -690
- package/src/analyzers/ESLintAnalyzer.js +320 -320
- package/src/analyzers/JavaScriptAnalyzer.js +260 -261
- package/src/analyzers/PrettierFormatter.js +246 -247
- package/src/analyzers/PythonAnalyzer.js +283 -283
- package/src/analyzers/SecurityAnalyzer.js +729 -729
- package/src/analyzers/SparrowAnalyzer.js +341 -341
- package/src/analyzers/TypeScriptAnalyzer.js +247 -247
- package/src/analyzers/__tests__/CSSAnalyzer.test.js +41 -41
- package/src/analyzers/__tests__/ConfigValidator.test.js +362 -362
- package/src/analyzers/__tests__/JavaScriptAnalyzer.test.js +40 -40
- package/src/analyzers/__tests__/PythonAnalyzer.test.js +205 -208
- package/src/analyzers/__tests__/SecurityAnalyzer.test.js +303 -303
- package/src/analyzers/__tests__/TypeScriptAnalyzer.test.js +187 -187
- package/src/analyzers/codeCloneDetector/analyzer.js +344 -344
- package/src/analyzers/codeCloneDetector/detector.js +250 -250
- package/src/analyzers/codeCloneDetector/index.js +194 -192
- package/src/analyzers/codeCloneDetector/parser.js +199 -199
- package/src/core/__tests__/agentPool.test.js +866 -866
- package/src/core/__tests__/agentPoolAutoResume.test.js +209 -209
- package/src/core/__tests__/agentPoolWakeOnMessage.test.js +315 -315
- package/src/core/__tests__/agentScheduler.emptyResponseChatStall.test.js +213 -213
- package/src/core/__tests__/agentScheduler.errorCategorisation.test.js +246 -246
- package/src/core/__tests__/agentScheduler.firstChunkTimeout.test.js +138 -138
- package/src/core/__tests__/agentScheduler.modeTransitions.test.js +233 -233
- package/src/core/__tests__/agentScheduler.nativePromptPick.test.js +319 -319
- package/src/core/__tests__/agentScheduler.taskLifecycleInstruction.test.js +78 -78
- package/src/core/__tests__/agentScheduler.visualizer.test.js +258 -258
- package/src/core/__tests__/flowCheckpointStore.test.js +140 -140
- package/src/core/__tests__/flowEndToEnd.test.js +565 -565
- package/src/core/__tests__/flowFieldMapping.test.js +188 -189
- package/src/core/__tests__/flowLintClientMirror.test.js +96 -98
- package/src/core/__tests__/flowSavePayload.test.js +170 -169
- package/src/core/__tests__/flowTemplates.test.js +311 -311
- package/src/core/__tests__/flowVersionStore.test.js +123 -123
- package/src/core/__tests__/messageProcessor.test.js +669 -669
- package/src/core/__tests__/stateManager.test.js +0 -1
- package/src/core/agentPool.js +2474 -2475
- package/src/core/agentScheduler.js +1 -4
- package/src/core/contextManager.js +708 -708
- package/src/core/flowExecutor.js +1510 -1510
- package/src/core/flowFieldMapping.js +136 -138
- package/src/core/messageProcessor.js +953 -954
- package/src/core/orchestrator.js +593 -595
- package/src/core/stateManager.js +1765 -1752
- package/src/index.js +1221 -1221
- package/src/interfaces/__tests__/archivedAgentDelete.test.js +207 -207
- package/src/interfaces/__tests__/bulkAgentRoute.test.js +361 -361
- package/src/interfaces/__tests__/imageServing.test.js +228 -228
- package/src/interfaces/__tests__/remoteSessionAuth.test.js +308 -308
- package/src/interfaces/__tests__/videoJobsRoutes.test.js +178 -179
- package/src/interfaces/__tests__/webServer.marketplace.test.js +629 -629
- package/src/interfaces/schedulerRoutes.js +50 -50
- package/src/interfaces/terminal/__tests__/smoke/connection.test.js +341 -350
- package/src/interfaces/terminal/__tests__/smoke/enhancements.test.js +156 -156
- package/src/interfaces/terminal/__tests__/smoke/imports.test.js +325 -330
- package/src/interfaces/terminal/__tests__/smoke/tools.test.js +385 -388
- package/src/interfaces/terminal/api/session.js +265 -266
- package/src/interfaces/terminal/api/websocket.js +496 -497
- package/src/interfaces/terminal/components/AgentCreator.js +691 -705
- package/src/interfaces/terminal/components/AgentEditor.js +676 -678
- package/src/interfaces/terminal/components/AgentSwitcher.js +331 -330
- package/src/interfaces/terminal/components/ErrorPanel.js +263 -264
- package/src/interfaces/terminal/components/Header.js +28 -28
- package/src/interfaces/terminal/components/Layout.js +598 -603
- package/src/interfaces/terminal/components/MessageList.js +280 -281
- package/src/interfaces/terminal/components/SettingsPanel.js +410 -415
- package/src/interfaces/terminal/components/StatusBar.js +2 -0
- package/src/interfaces/terminal/index.js +168 -168
- package/src/interfaces/terminal/state/useAgentControl.js +496 -496
- package/src/interfaces/terminal/state/useAgents.js +537 -537
- package/src/interfaces/terminal/state/useMessages.js +629 -630
- package/src/interfaces/terminal/state/useTools.js +554 -554
- package/src/interfaces/terminal/utils/debugLogger.js +44 -44
- package/src/interfaces/terminal/utils/settingsStorage.js +232 -232
- package/src/interfaces/webServer.js +7578 -7579
- package/src/interfaces/webServer.js.bak +7046 -7046
- package/src/modules/fileExplorer/__tests__/zipDownload.test.js +237 -237
- package/src/modules/fileExplorer/controller.js +470 -469
- package/src/modules/fileExplorer/routes.js +285 -286
- package/src/modules/widget/__tests__/isDisabled.test.js +41 -41
- package/src/modules/widget/__tests__/routes.test.js +677 -678
- package/src/modules/widget/__tests__/runtime.test.js +401 -401
- package/src/modules/widget/__tests__/versioning.test.js +309 -309
- package/src/modules/widget/__tests__/webComponentRuntime.test.js +565 -565
- package/src/modules/widget/__tests__/widgetTool.test.js +316 -316
- package/src/modules/widget/routes.js +435 -435
- package/src/modules/widget/runtime/bundle.js +640 -640
- package/src/modules/widget/runtime/webComponentBundle.js +470 -470
- package/src/modules/widget/schema.js +182 -181
- package/src/modules/widget/widgetTool.js +1389 -1389
- package/src/services/__tests__/agentActivityService.test.js +401 -402
- package/src/services/__tests__/benchmarkService.test.js +184 -184
- package/src/services/__tests__/contextInjectionService.test.js +246 -246
- package/src/services/__tests__/conversationQuery.test.js +721 -723
- package/src/services/__tests__/credentialVault.test.js +469 -469
- package/src/services/__tests__/discordService.integration.test.js +638 -639
- package/src/services/__tests__/flowContextService.test.js +590 -590
- package/src/services/__tests__/memoryService.test.js +1 -1
- package/src/services/__tests__/messageSource.test.js +380 -380
- package/src/services/__tests__/modelRouterNaming.test.js +111 -111
- package/src/services/__tests__/projectDetector.test.js +34 -34
- package/src/services/__tests__/promptService.test.js +242 -242
- package/src/services/__tests__/telegramService.test.js +941 -941
- package/src/services/__tests__/tokenCountingService.test.js +48 -48
- package/src/services/agentActivityService.js +419 -420
- package/src/services/aiService.js +2997 -3001
- package/src/services/apiKeyManager.js +359 -359
- package/src/services/benchmarkService.js +196 -196
- package/src/services/codebaseKnowledgeService.js +2 -2
- package/src/services/composioService.js +738 -738
- package/src/services/conversationCompactionService.js +1258 -1257
- package/src/services/credentialVault.js +685 -685
- package/src/services/discordService.js +792 -793
- package/src/services/embeddings/__tests__/azureCustomProvider.test.js +232 -232
- package/src/services/embeddings/__tests__/embeddingService.test.js +417 -417
- package/src/services/embeddings/__tests__/localProvider.test.js +263 -263
- package/src/services/embeddings/autoRecall.js +218 -219
- package/src/services/embeddings/indexers/__tests__/agentIndexer.test.js +232 -232
- package/src/services/embeddings/indexers/__tests__/memoryIndexer.test.js +418 -418
- package/src/services/embeddings/indexers/__tests__/reminisceIndexer.test.js +356 -357
- package/src/services/embeddings/indexers/__tests__/skillsIndexer.test.js +145 -145
- package/src/services/embeddings/indexers/__tests__/taskIndexer.test.js +146 -146
- package/src/services/embeddings/indexers/composioIndexer.js +279 -279
- package/src/services/embeddings/providerInterface.js +206 -206
- package/src/services/embeddings/providers/localProvider.js +11 -7
- package/src/services/embeddings/providers/openaiProvider.js +101 -101
- package/src/services/embeddings/vectorStore/inMemoryJsonStore.js +356 -356
- package/src/services/errorHandler.js +809 -809
- package/src/services/flowContextService.js +586 -586
- package/src/services/grounding/MockAdapter.js +125 -125
- package/src/services/modelRouterService.js +26 -31
- package/src/services/modelsService.js +322 -322
- package/src/services/ollamaService.js +452 -452
- package/src/services/projectDetector.js +403 -404
- package/src/services/promptService.js +418 -418
- package/src/services/qualityInspector.js +795 -795
- package/src/services/scheduleService.js +726 -726
- package/src/services/serviceRegistry.js +386 -386
- package/src/services/telegrafBot.js +174 -174
- package/src/services/telegramService.js +1972 -1972
- package/src/services/visualEditorBridge.js +1033 -1033
- package/src/services/visualEditorServer.js +1769 -1774
- package/src/services/whatsappService.js +667 -668
- package/src/tools/__tests__/agentCommunicationTool.findAgent.test.js +226 -226
- package/src/tools/__tests__/agentCommunicationTool.test.js +3 -3
- package/src/tools/__tests__/agentDelayTool.test.js +342 -342
- package/src/tools/__tests__/baseTool.test.js +3 -3
- package/src/tools/__tests__/codeMapTool.test.js +915 -915
- package/src/tools/__tests__/fileContentReplaceTool.test.js +309 -309
- package/src/tools/__tests__/fileTreeTool.test.js +274 -274
- package/src/tools/__tests__/filesystemTool.test.js +815 -815
- package/src/tools/__tests__/foundryWebSearchTool.test.js +252 -252
- package/src/tools/__tests__/imageTool.validator.test.js +194 -194
- package/src/tools/__tests__/jobDoneTool.test.js +580 -581
- package/src/tools/__tests__/memoryTool.forgetStale.test.js +272 -272
- package/src/tools/__tests__/memoryTool.reminisce.test.js +2 -2
- package/src/tools/__tests__/memoryTool.reminisceSemanticSearch.test.js +301 -301
- package/src/tools/__tests__/memoryTool.semanticSearch.test.js +405 -405
- package/src/tools/__tests__/memoryTool.teamPool.test.js +293 -293
- package/src/tools/__tests__/memoryTool.test.js +1 -1
- package/src/tools/__tests__/seekTool.test.js +282 -282
- package/src/tools/__tests__/skillsTool.search.test.js +164 -164
- package/src/tools/__tests__/skillsTool.test.js +226 -226
- package/src/tools/__tests__/staticAnalysisTool.test.js +509 -509
- package/src/tools/__tests__/taskManagerTool.discipline.test.js +137 -137
- package/src/tools/__tests__/taskManagerTool.search.test.js +143 -143
- package/src/tools/__tests__/taskManagerTool.test.js +866 -866
- package/src/tools/__tests__/terminalTool.test.js +448 -448
- package/src/tools/__tests__/toolShapeForgiveness.test.js +259 -260
- package/src/tools/__tests__/userPromptTool.test.js +297 -297
- package/src/tools/__tests__/videoTool.jobs.test.js +147 -147
- package/src/tools/__tests__/webTool.e2e.test.js +609 -603
- package/src/tools/__tests__/webTool.unit.test.js +195 -195
- package/src/tools/__tests__/webTool.visionModel.test.js +75 -75
- package/src/tools/agentCommunicationTool.js +8 -10
- package/src/tools/agentDelayTool.js +496 -497
- package/src/tools/asyncToolManager.js +602 -603
- package/src/tools/baseTool.js +12 -11
- package/src/tools/cloneDetectionTool.js +576 -581
- package/src/tools/codeMapTool.js +0 -6
- package/src/tools/composioTool.js +617 -617
- package/src/tools/dependencyResolverTool.js +1211 -1212
- package/src/tools/desktop/DesktopTool.js +629 -638
- package/src/tools/desktop/__tests__/DesktopTool.e2e.test.js +306 -306
- package/src/tools/desktop/__tests__/DesktopTool.test.js +507 -507
- package/src/tools/desktop/__tests__/osController.test.js +364 -364
- package/src/tools/desktop/osController.js +491 -491
- package/src/tools/docxTool.js +623 -623
- package/src/tools/excelTool.js +636 -636
- package/src/tools/fileContentReplaceTool.js +5 -7
- package/src/tools/fileSystemTool.js +12 -19
- package/src/tools/fileTreeTool.js +840 -840
- package/src/tools/foundryWebSearchTool.js +273 -273
- package/src/tools/helpTool.js +198 -198
- package/src/tools/imageTool.js +1397 -1397
- package/src/tools/importAnalyzerTool.js +1056 -1056
- package/src/tools/jobDoneTool.js +495 -495
- package/src/tools/memoryTool.js +1 -1
- package/src/tools/office/pres/__tests__/presSystem.test.js +365 -365
- package/src/tools/office/pres/archetypes/agenda.js +61 -61
- package/src/tools/office/pres/archetypes/bentoGrid.js +218 -219
- package/src/tools/office/pres/archetypes/bigStat.js +140 -142
- package/src/tools/office/pres/archetypes/closing.js +70 -70
- package/src/tools/office/pres/archetypes/hero.js +70 -70
- package/src/tools/office/pres/archetypes/productHero.js +93 -94
- package/src/tools/office/pres/archetypes/table.js +73 -74
- package/src/tools/office/pres/backgrounds/orb.js +66 -66
- package/src/tools/office/pres/components.js +422 -423
- package/src/tools/officeTool.js +441 -441
- package/src/tools/pdfTool.js +625 -627
- package/src/tools/platformControlTool.js +1081 -1081
- package/src/tools/seekTool.js +917 -918
- package/src/tools/skillsTool.js +1 -1
- package/src/tools/staticAnalysisTool.js +2143 -2146
- package/src/tools/taskManagerTool.js +3324 -3324
- package/src/tools/terminalTool.js +2615 -2618
- package/src/tools/videoTool.js +1303 -1303
- package/src/tools/visionTool.js +508 -508
- package/src/tools/visualEditorTool.js +1289 -1290
- package/src/tools/webTool.js +3368 -3368
- package/src/tools/whatsappTool.js +464 -464
- package/src/types/__tests__/agent.test.js +499 -499
- package/src/types/__tests__/contextReference.test.js +606 -606
- package/src/types/__tests__/conversation.test.js +555 -555
- package/src/types/__tests__/toolCommand.test.js +584 -584
- package/src/types/contextReference.js +974 -971
- package/src/types/conversation.js +729 -729
- package/src/types/toolCommand.js +746 -746
- package/src/utilities/__tests__/attachmentValidator.test.js +80 -80
- package/src/utilities/__tests__/auditReport.test.js +328 -328
- package/src/utilities/__tests__/directoryAccessManager.test.js +388 -388
- package/src/utilities/__tests__/jsonRepair.test.js +103 -104
- package/src/utilities/__tests__/modeTransitionReasons.test.js +105 -105
- package/src/utilities/__tests__/platformUtils.test.js +80 -87
- package/src/utilities/__tests__/structuredFileValidator.test.js +261 -263
- package/src/utilities/__tests__/toolConstants.test.js +92 -94
- package/src/utilities/__tests__/useIsTouchDevice.detect.test.js +114 -114
- package/src/utilities/__tests__/webUiUtilSync.test.js +117 -117
- package/src/utilities/attachmentValidator.js +284 -288
- package/src/utilities/authCache.js.backup-1779570472481 +121 -121
- package/src/utilities/browserStealth.js +631 -630
- package/src/utilities/configManager.js +616 -617
- package/src/utilities/directoryAccessManager.js +564 -565
- package/src/utilities/fileProcessor.js +308 -307
- package/src/utilities/humanBehavior.js +454 -453
- package/src/utilities/logger.js +479 -479
- package/src/utilities/structuredFileValidator.js +696 -699
- package/src/utilities/tagParser.js +5 -10
- package/src/utilities/userDataDir.js +308 -308
- package/node_modules/@isaacs/brace-expansion/dist/commonjs/index.js.map +0 -1
- package/node_modules/@isaacs/brace-expansion/dist/esm/index.js.map +0 -1
- package/node_modules/minipass/LICENSE +0 -15
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/LICENSE.md +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/index.d.ts +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/index.d.ts.map +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/index.js +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/index.js.map +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/package.json +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/index.d.ts +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/index.d.ts.map +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/index.js +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/index.js.map +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/package.json +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/LICENSE +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/commonjs/index.d.ts +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/commonjs/index.d.ts.map +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/commonjs/package.json +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/esm/index.d.ts +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/esm/index.d.ts.map +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/esm/package.json +0 -0
|
@@ -1,915 +1,915 @@
|
|
|
1
|
-
import { jest, describe, test, expect, beforeEach } from '@jest/globals';
|
|
2
|
-
import { createMockLogger
|
|
3
|
-
|
|
4
|
-
// Mock fs before import
|
|
5
|
-
const mockFsPromises = {
|
|
6
|
-
stat: jest.fn(),
|
|
7
|
-
readFile: jest.fn(),
|
|
8
|
-
readdir: jest.fn()
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
jest.unstable_mockModule('fs', () => ({
|
|
12
|
-
default: { promises: mockFsPromises, readFileSync: jest.fn(() => { throw new Error('no file'); }) },
|
|
13
|
-
promises: mockFsPromises,
|
|
14
|
-
readFileSync: jest.fn(() => { throw new Error('no file'); })
|
|
15
|
-
}));
|
|
16
|
-
|
|
17
|
-
// Mock constants
|
|
18
|
-
jest.unstable_mockModule('../../utilities/constants.js', () => ({
|
|
19
|
-
TOOL_STATUS: { PENDING: 'pending', EXECUTING: 'executing', COMPLETED: 'completed', FAILED: 'failed' },
|
|
20
|
-
OPERATION_STATUS: { NOT_FOUND: 'not_found' },
|
|
21
|
-
ERROR_TYPES: {},
|
|
22
|
-
SYSTEM_DEFAULTS: { MAX_TOOL_EXECUTION_TIME: 300000 }
|
|
23
|
-
}));
|
|
24
|
-
|
|
25
|
-
const { default: CodeMapTool } = await import('../codeMapTool.js');
|
|
26
|
-
|
|
27
|
-
describe('CodeMapTool', () => {
|
|
28
|
-
let tool;
|
|
29
|
-
let logger;
|
|
30
|
-
const context = { projectDir: '/project', agentId: 'agent-1' };
|
|
31
|
-
|
|
32
|
-
beforeEach(() => {
|
|
33
|
-
jest.clearAllMocks();
|
|
34
|
-
logger = createMockLogger();
|
|
35
|
-
tool = new CodeMapTool({}, logger);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test('constructor sets metadata correctly', () => {
|
|
39
|
-
expect(tool.id).toBe('code-map');
|
|
40
|
-
expect(tool.requiresProject).toBe(true);
|
|
41
|
-
expect(tool.isAsync).toBe(true);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test('getDescription mentions skeleton and read-range', () => {
|
|
45
|
-
const desc = tool.getDescription();
|
|
46
|
-
expect(desc).toContain('skeleton');
|
|
47
|
-
expect(desc).toContain('read-range');
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test('getRequiredParameters returns action', () => {
|
|
51
|
-
expect(tool.getRequiredParameters()).toEqual(['action']);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test('parseParameters parses JSON content', () => {
|
|
55
|
-
const content = JSON.stringify({
|
|
56
|
-
action: 'skeleton',
|
|
57
|
-
path: 'src/',
|
|
58
|
-
level: 'B.0'
|
|
59
|
-
});
|
|
60
|
-
const result = tool.parseParameters(content);
|
|
61
|
-
expect(result.action).toBe('skeleton');
|
|
62
|
-
expect(result.path).toBe('src/');
|
|
63
|
-
expect(result.level).toBe('B.0');
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
test('parseParameters parses nested parameters JSON', () => {
|
|
67
|
-
const content = JSON.stringify({
|
|
68
|
-
parameters: { action: 'read-range', filePath: 'index.js', startLine: 1, endLine: 10 }
|
|
69
|
-
});
|
|
70
|
-
const result = tool.parseParameters(content);
|
|
71
|
-
expect(result.action).toBe('read-range');
|
|
72
|
-
expect(result.filePath).toBe('index.js');
|
|
73
|
-
expect(result.startLine).toBe(1);
|
|
74
|
-
expect(result.endLine).toBe(10);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test('parseParameters parses XML content', () => {
|
|
78
|
-
const content = '<action>skeleton</action><path>src/</path><level>A.0</level>';
|
|
79
|
-
const result = tool.parseParameters(content);
|
|
80
|
-
expect(result.action).toBe('skeleton');
|
|
81
|
-
expect(result.path).toBe('src/');
|
|
82
|
-
expect(result.level).toBe('A.0');
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
test('parseParameters returns parseError on bad JSON', () => {
|
|
86
|
-
const result = tool.parseParameters('{ broken');
|
|
87
|
-
expect(result).toHaveProperty('parseError');
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test('customValidateParameters rejects missing action', () => {
|
|
91
|
-
const result = tool.customValidateParameters({});
|
|
92
|
-
expect(result.valid).toBe(false);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
test('customValidateParameters rejects invalid action', () => {
|
|
96
|
-
const result = tool.customValidateParameters({ action: 'invalid' });
|
|
97
|
-
expect(result.valid).toBe(false);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test('customValidateParameters requires path for skeleton', () => {
|
|
101
|
-
const result = tool.customValidateParameters({ action: 'skeleton' });
|
|
102
|
-
expect(result.valid).toBe(false);
|
|
103
|
-
expect(result.errors.some(e => e.includes('path'))).toBe(true);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
test('customValidateParameters rejects invalid level', () => {
|
|
107
|
-
const result = tool.customValidateParameters({ action: 'skeleton', path: 'src/', level: 'X.9' });
|
|
108
|
-
expect(result.valid).toBe(false);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
test('customValidateParameters requires filePath/startLine/endLine for read-range', () => {
|
|
112
|
-
const result = tool.customValidateParameters({ action: 'read-range' });
|
|
113
|
-
expect(result.valid).toBe(false);
|
|
114
|
-
expect(result.errors.length).toBeGreaterThanOrEqual(3);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
test('customValidateParameters rejects endLine < startLine', () => {
|
|
118
|
-
const result = tool.customValidateParameters({
|
|
119
|
-
action: 'read-range', filePath: 'a.js', startLine: 10, endLine: 5
|
|
120
|
-
});
|
|
121
|
-
expect(result.valid).toBe(false);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
test('customValidateParameters rejects range exceeding max', () => {
|
|
125
|
-
const result = tool.customValidateParameters({
|
|
126
|
-
action: 'read-range', filePath: 'a.js', startLine: 1, endLine: 600
|
|
127
|
-
});
|
|
128
|
-
expect(result.valid).toBe(false);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
test('customValidateParameters accepts valid skeleton params', () => {
|
|
132
|
-
const result = tool.customValidateParameters({ action: 'skeleton', path: 'src/' });
|
|
133
|
-
expect(result.valid).toBe(true);
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
test('execute skeleton on single JS file', async () => {
|
|
137
|
-
const jsContent = [
|
|
138
|
-
'import express from "express";',
|
|
139
|
-
'',
|
|
140
|
-
'export class App {',
|
|
141
|
-
' constructor() {}',
|
|
142
|
-
' start() {',
|
|
143
|
-
' console.log("started");',
|
|
144
|
-
' }',
|
|
145
|
-
'}',
|
|
146
|
-
'',
|
|
147
|
-
'export function main() {',
|
|
148
|
-
' return new App();',
|
|
149
|
-
'}'
|
|
150
|
-
].join('\n');
|
|
151
|
-
|
|
152
|
-
mockFsPromises.stat.mockResolvedValue({
|
|
153
|
-
isFile: () => true,
|
|
154
|
-
isDirectory: () => false,
|
|
155
|
-
size: jsContent.length
|
|
156
|
-
});
|
|
157
|
-
mockFsPromises.readFile.mockResolvedValue(jsContent);
|
|
158
|
-
|
|
159
|
-
const result = await tool.execute(
|
|
160
|
-
{ action: 'skeleton', path: 'src/app.js', level: 'B.0' },
|
|
161
|
-
context
|
|
162
|
-
);
|
|
163
|
-
|
|
164
|
-
expect(result.success).toBe(true);
|
|
165
|
-
expect(result.action).toBe('skeleton');
|
|
166
|
-
expect(result.totalFiles).toBeGreaterThanOrEqual(1);
|
|
167
|
-
expect(result.totalEntries).toBeGreaterThanOrEqual(1);
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
test('execute skeleton on directory with JS files', async () => {
|
|
171
|
-
// First stat: directory check
|
|
172
|
-
mockFsPromises.stat
|
|
173
|
-
.mockResolvedValueOnce({ isFile: () => false, isDirectory: () => true }) // path stat
|
|
174
|
-
.mockResolvedValueOnce({ size: 100 }); // file stat
|
|
175
|
-
|
|
176
|
-
// Discover files - readdir for root
|
|
177
|
-
mockFsPromises.readdir.mockResolvedValueOnce([
|
|
178
|
-
{ name: 'index.js', isDirectory: () => false, isFile: () => true, isSymbolicLink: () => false }
|
|
179
|
-
]);
|
|
180
|
-
|
|
181
|
-
mockFsPromises.readFile
|
|
182
|
-
.mockRejectedValueOnce(new Error('no .gitignore')) // _loadGitignoreRules
|
|
183
|
-
.mockResolvedValueOnce('export function hello() { return 1; }\n'); // file content
|
|
184
|
-
|
|
185
|
-
const result = await tool.execute(
|
|
186
|
-
{ action: 'skeleton', path: 'src/', level: 'A.0' },
|
|
187
|
-
context
|
|
188
|
-
);
|
|
189
|
-
|
|
190
|
-
expect(result.success).toBe(true);
|
|
191
|
-
expect(result.action).toBe('skeleton');
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
test('execute skeleton returns empty when no supported files', async () => {
|
|
195
|
-
mockFsPromises.stat.mockResolvedValue({ isFile: () => false, isDirectory: () => true });
|
|
196
|
-
mockFsPromises.readdir.mockResolvedValue([]);
|
|
197
|
-
mockFsPromises.readFile.mockRejectedValue(new Error('no file'));
|
|
198
|
-
|
|
199
|
-
const result = await tool.execute(
|
|
200
|
-
{ action: 'skeleton', path: 'empty/' },
|
|
201
|
-
context
|
|
202
|
-
);
|
|
203
|
-
|
|
204
|
-
expect(result.success).toBe(true);
|
|
205
|
-
expect(result.totalFiles).toBe(0);
|
|
206
|
-
expect(result.message).toContain('No supported files');
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
test('execute skeleton throws for non-existent path', async () => {
|
|
210
|
-
mockFsPromises.stat.mockRejectedValue(new Error('ENOENT'));
|
|
211
|
-
|
|
212
|
-
await expect(tool.execute(
|
|
213
|
-
{ action: 'skeleton', path: 'missing/' },
|
|
214
|
-
context
|
|
215
|
-
)).rejects.toThrow('Path not found');
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
test('execute read-range returns formatted lines', async () => {
|
|
219
|
-
const content = 'line1\nline2\nline3\nline4\nline5\n';
|
|
220
|
-
mockFsPromises.readFile.mockResolvedValue(content);
|
|
221
|
-
|
|
222
|
-
const result = await tool.execute(
|
|
223
|
-
{ action: 'read-range', filePath: 'src/index.js', startLine: 2, endLine: 4 },
|
|
224
|
-
context
|
|
225
|
-
);
|
|
226
|
-
|
|
227
|
-
expect(result.success).toBe(true);
|
|
228
|
-
expect(result.action).toBe('read-range');
|
|
229
|
-
expect(result.linesReturned).toBe(3);
|
|
230
|
-
expect(result.content).toContain('line2');
|
|
231
|
-
expect(result.content).toContain('line3');
|
|
232
|
-
expect(result.content).toContain('line4');
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
test('execute read-range throws when startLine exceeds file length', async () => {
|
|
236
|
-
mockFsPromises.readFile.mockResolvedValue('line1\nline2\n');
|
|
237
|
-
|
|
238
|
-
await expect(tool.execute(
|
|
239
|
-
{ action: 'read-range', filePath: 'a.js', startLine: 100, endLine: 110 },
|
|
240
|
-
context
|
|
241
|
-
)).rejects.toThrow('exceeds file length');
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
test('execute read-range throws for missing file', async () => {
|
|
245
|
-
mockFsPromises.readFile.mockRejectedValue(new Error('ENOENT'));
|
|
246
|
-
|
|
247
|
-
await expect(tool.execute(
|
|
248
|
-
{ action: 'read-range', filePath: 'missing.js', startLine: 1, endLine: 5 },
|
|
249
|
-
context
|
|
250
|
-
)).rejects.toThrow('File not found');
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
test('execute throws on unknown action', async () => {
|
|
254
|
-
await expect(tool.execute(
|
|
255
|
-
{ action: 'unknown' },
|
|
256
|
-
context
|
|
257
|
-
)).rejects.toThrow('Unknown action');
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
test('_langOf detects python files', () => {
|
|
261
|
-
expect(tool._langOf('script.py')).toBe('python');
|
|
262
|
-
expect(tool._langOf('app.js')).toBe('js');
|
|
263
|
-
expect(tool._langOf('component.tsx')).toBe('js');
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
test('_parseJS extracts exported functions', () => {
|
|
267
|
-
const lines = [
|
|
268
|
-
'export function hello() {',
|
|
269
|
-
' return 1;',
|
|
270
|
-
'}'
|
|
271
|
-
];
|
|
272
|
-
const entries = tool._parseJS(lines, { publicOnly: true, withComments: false, includeImports: false });
|
|
273
|
-
expect(entries.length).toBeGreaterThanOrEqual(1);
|
|
274
|
-
expect(entries[0].kind).toBe('signature');
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
test('_parseJS extracts imports when includeImports is true', () => {
|
|
278
|
-
const lines = [
|
|
279
|
-
'import express from "express";',
|
|
280
|
-
'const x = require("path");',
|
|
281
|
-
'export function hello() {}'
|
|
282
|
-
];
|
|
283
|
-
const entries = tool._parseJS(lines, { publicOnly: false, withComments: false, includeImports: true });
|
|
284
|
-
const imports = entries.filter(e => e.kind === 'import');
|
|
285
|
-
expect(imports.length).toBe(2);
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
test('_parseJS includes comments when withComments is true', () => {
|
|
289
|
-
const lines = [
|
|
290
|
-
'/** My doc */',
|
|
291
|
-
'export function hello() {}'
|
|
292
|
-
];
|
|
293
|
-
const entries = tool._parseJS(lines, { publicOnly: false, withComments: true, includeImports: false });
|
|
294
|
-
const comments = entries.filter(e => e.kind === 'comment');
|
|
295
|
-
expect(comments.length).toBeGreaterThanOrEqual(1);
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
test('_parsePython extracts def and class', () => {
|
|
299
|
-
const lines = [
|
|
300
|
-
'class MyClass:',
|
|
301
|
-
' def __init__(self):',
|
|
302
|
-
' pass',
|
|
303
|
-
'',
|
|
304
|
-
'def public_func():',
|
|
305
|
-
' return 1'
|
|
306
|
-
];
|
|
307
|
-
const entries = tool._parsePython(lines, { publicOnly: false, withComments: false, includeImports: false });
|
|
308
|
-
const sigs = entries.filter(e => e.kind === 'signature');
|
|
309
|
-
expect(sigs.length).toBeGreaterThanOrEqual(2);
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
test('_parsePython respects publicOnly', () => {
|
|
313
|
-
const lines = [
|
|
314
|
-
'def public_func():',
|
|
315
|
-
' pass',
|
|
316
|
-
'def _private_func():',
|
|
317
|
-
' pass'
|
|
318
|
-
];
|
|
319
|
-
const entries = tool._parsePython(lines, { publicOnly: true, withComments: false, includeImports: false });
|
|
320
|
-
const sigs = entries.filter(e => e.kind === 'signature');
|
|
321
|
-
expect(sigs.length).toBe(1);
|
|
322
|
-
expect(sigs[0].text).toContain('public_func');
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
test('_parseGitignore parses rules', () => {
|
|
326
|
-
const content = '# comment\nnode_modules/\n*.log\n!important.log';
|
|
327
|
-
const rules = tool._parseGitignore(content, '');
|
|
328
|
-
expect(rules.length).toBe(3);
|
|
329
|
-
expect(rules[2].negate).toBe(true);
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
test('_gitignorePatternToRegex handles ** patterns', () => {
|
|
333
|
-
const re = tool._gitignorePatternToRegex('**/test');
|
|
334
|
-
expect(re).toContain('(.+/)?');
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
// ─────────────────────────────────────────────────────────────────
|
|
338
|
-
// Browser-JS / pre-ES-module patterns. Before this set of regexes,
|
|
339
|
-
// CodeMap on a vanilla client-side .js file produced an empty map
|
|
340
|
-
// because the parser only matched ESM exports + class methods.
|
|
341
|
-
// These tests pin the new patterns. Each test exercises ONE pattern
|
|
342
|
-
// in isolation so a future regression on any one of them surfaces
|
|
343
|
-
// a precise failure.
|
|
344
|
-
// ─────────────────────────────────────────────────────────────────
|
|
345
|
-
describe('_parseJS — browser-JS patterns', () => {
|
|
346
|
-
const opts = { publicOnly: false, withComments: false, includeImports: false };
|
|
347
|
-
const signatures = (entries) =>
|
|
348
|
-
entries.filter(e => e.kind === 'signature').map(e => e.text.trim());
|
|
349
|
-
|
|
350
|
-
test('top-level IIFE is captured', () => {
|
|
351
|
-
const entries = tool._parseJS([
|
|
352
|
-
'(function () {',
|
|
353
|
-
' var x = 1;',
|
|
354
|
-
'})();',
|
|
355
|
-
], opts);
|
|
356
|
-
expect(signatures(entries)).toContain('(function () {');
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
test('IIFE with leading ! or + prefix (Webpack-UMD style)', () => {
|
|
360
|
-
const entries = tool._parseJS([
|
|
361
|
-
'!function () { return 1; }();',
|
|
362
|
-
'+function () { return 2; }();',
|
|
363
|
-
], opts);
|
|
364
|
-
expect(signatures(entries)).toEqual(expect.arrayContaining([
|
|
365
|
-
'!function () { return 1; }();',
|
|
366
|
-
'+function () { return 2; }();',
|
|
367
|
-
]));
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
test('window.X = function — global assignment', () => {
|
|
371
|
-
const entries = tool._parseJS([
|
|
372
|
-
'window.MyApp = function () {',
|
|
373
|
-
' return 1;',
|
|
374
|
-
'};',
|
|
375
|
-
], opts);
|
|
376
|
-
expect(signatures(entries).join('\n')).toMatch(/window\.MyApp\s*=\s*function/);
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
test('globalThis.foo = () => — global arrow assignment', () => {
|
|
380
|
-
const entries = tool._parseJS([
|
|
381
|
-
'globalThis.greet = (name) => `hi ${name}`;',
|
|
382
|
-
], opts);
|
|
383
|
-
expect(signatures(entries).join('\n')).toMatch(/globalThis\.greet/);
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
test('Namespace.utils.foo = function — namespaced global assignment', () => {
|
|
387
|
-
const entries = tool._parseJS([
|
|
388
|
-
'MyApp.utils.formatDate = function (d) {',
|
|
389
|
-
' return d.toISOString();',
|
|
390
|
-
'};',
|
|
391
|
-
], opts);
|
|
392
|
-
expect(signatures(entries).join('\n')).toMatch(/MyApp\.utils\.formatDate/);
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
test('Foo.prototype.bar = function — prototype method', () => {
|
|
396
|
-
const entries = tool._parseJS([
|
|
397
|
-
'Foo.prototype.bar = function () {',
|
|
398
|
-
' return this.x;',
|
|
399
|
-
'};',
|
|
400
|
-
], opts);
|
|
401
|
-
expect(signatures(entries).join('\n')).toMatch(/Foo\.prototype\.bar/);
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
test('Foo.prototype.bar = () => — prototype arrow assignment', () => {
|
|
405
|
-
// (Real-world prototype-as-arrow is unusual but the regex
|
|
406
|
-
// covers it for completeness.)
|
|
407
|
-
const entries = tool._parseJS([
|
|
408
|
-
'Foo.prototype.bar = () => 42;',
|
|
409
|
-
], opts);
|
|
410
|
-
expect(signatures(entries).join('\n')).toMatch(/Foo\.prototype\.bar/);
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
test('jQuery $.fn.plugin = function — jQuery plugin pattern', () => {
|
|
414
|
-
const entries = tool._parseJS([
|
|
415
|
-
'$.fn.myPlugin = function (opts) {',
|
|
416
|
-
' return this.each(function () {});',
|
|
417
|
-
'};',
|
|
418
|
-
], opts);
|
|
419
|
-
expect(signatures(entries).join('\n')).toMatch(/\$\.fn\.myPlugin/);
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
test('jQuery.fn.plugin = function — full jQuery prefix', () => {
|
|
423
|
-
const entries = tool._parseJS([
|
|
424
|
-
'jQuery.fn.banner = function () { return this; };',
|
|
425
|
-
], opts);
|
|
426
|
-
expect(signatures(entries).join('\n')).toMatch(/jQuery\.fn\.banner/);
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
test('document.addEventListener("DOMContentLoaded", …) — page lifecycle', () => {
|
|
430
|
-
const entries = tool._parseJS([
|
|
431
|
-
'document.addEventListener("DOMContentLoaded", () => {',
|
|
432
|
-
' init();',
|
|
433
|
-
'});',
|
|
434
|
-
], opts);
|
|
435
|
-
expect(signatures(entries).join('\n')).toMatch(/document\.addEventListener/);
|
|
436
|
-
});
|
|
437
|
-
|
|
438
|
-
test('window.addEventListener("load", …) — page lifecycle (alt)', () => {
|
|
439
|
-
const entries = tool._parseJS([
|
|
440
|
-
'window.addEventListener("load", function () { boot(); });',
|
|
441
|
-
], opts);
|
|
442
|
-
expect(signatures(entries).join('\n')).toMatch(/window\.addEventListener/);
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
test('REGRESSION: a vanilla browser file (IIFE wrapping jQuery plugins) now produces a non-empty map', () => {
|
|
446
|
-
// This is the original failure case: pure client-side .js with
|
|
447
|
-
// none of the new patterns produced zero entries. Pre-fix this
|
|
448
|
-
// file's signatures.length would have been 0; post-fix it's > 0.
|
|
449
|
-
const entries = tool._parseJS([
|
|
450
|
-
'(function ($) {',
|
|
451
|
-
' $.fn.tooltip = function (options) {',
|
|
452
|
-
' return this.each(function () {});',
|
|
453
|
-
' };',
|
|
454
|
-
' $.fn.popover = function (options) {',
|
|
455
|
-
' return this.each(function () {});',
|
|
456
|
-
' };',
|
|
457
|
-
'})(jQuery);',
|
|
458
|
-
], opts);
|
|
459
|
-
expect(signatures(entries).length).toBeGreaterThanOrEqual(3);
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
test('REGRESSION: file unaffected by changes — modern ESM still matches', () => {
|
|
463
|
-
// Make sure adding the new regexes didn't regress the original
|
|
464
|
-
// matcher set.
|
|
465
|
-
const entries = tool._parseJS([
|
|
466
|
-
'export function hello() {}',
|
|
467
|
-
'export class Greeter {}',
|
|
468
|
-
'export const sum = (a, b) => a + b;',
|
|
469
|
-
], opts);
|
|
470
|
-
expect(signatures(entries).length).toBe(3);
|
|
471
|
-
});
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
// ─────────────────────────────────────────────────────────────────
|
|
475
|
-
// TypeScript / TSX coverage. The JS parser is the same parser used
|
|
476
|
-
// for .ts / .tsx / .mjs / .cjs (see _langOf); these tests pin which
|
|
477
|
-
// TypeScript-specific patterns the no-regex parser captures TODAY
|
|
478
|
-
// and which it MISSES, so a future tree-sitter migration (see the
|
|
479
|
-
// file-header comment in codeMapTool.js) has an explicit baseline
|
|
480
|
-
// to preserve / improve against.
|
|
481
|
-
// ─────────────────────────────────────────────────────────────────
|
|
482
|
-
describe('_parseJS — TypeScript / TSX coverage', () => {
|
|
483
|
-
const opts = { publicOnly: false, withComments: false, includeImports: false };
|
|
484
|
-
const sigs = (lines) =>
|
|
485
|
-
tool._parseJS(lines, opts).filter(e => e.kind === 'signature').map(e => e.text.trim());
|
|
486
|
-
|
|
487
|
-
// ── Captures we rely on (regressions here would break TS skeletons) ──
|
|
488
|
-
|
|
489
|
-
test('export interface — one-line', () => {
|
|
490
|
-
const out = sigs(['export interface User { id: string; name: string; }']);
|
|
491
|
-
expect(out.join('\n')).toMatch(/export interface User/);
|
|
492
|
-
});
|
|
493
|
-
|
|
494
|
-
test('export type alias', () => {
|
|
495
|
-
const out = sigs(['export type ID = string | number;']);
|
|
496
|
-
expect(out.join('\n')).toMatch(/export type ID/);
|
|
497
|
-
});
|
|
498
|
-
|
|
499
|
-
test('export enum', () => {
|
|
500
|
-
const out = sigs(['export enum Color { Red, Green, Blue }']);
|
|
501
|
-
expect(out.join('\n')).toMatch(/export enum Color/);
|
|
502
|
-
});
|
|
503
|
-
|
|
504
|
-
test('export interface multi-line opening', () => {
|
|
505
|
-
const out = sigs([
|
|
506
|
-
'export interface User {',
|
|
507
|
-
' id: string;',
|
|
508
|
-
' name: string;',
|
|
509
|
-
'}',
|
|
510
|
-
]);
|
|
511
|
-
expect(out.join('\n')).toMatch(/export interface User \{/);
|
|
512
|
-
});
|
|
513
|
-
|
|
514
|
-
test('abstract class — declaration + abstract method', () => {
|
|
515
|
-
const out = sigs([
|
|
516
|
-
'abstract class Animal {',
|
|
517
|
-
' abstract sound(): string;',
|
|
518
|
-
'}',
|
|
519
|
-
]);
|
|
520
|
-
expect(out.join('\n')).toMatch(/abstract class Animal/);
|
|
521
|
-
expect(out.join('\n')).toMatch(/abstract sound\(\): string/);
|
|
522
|
-
});
|
|
523
|
-
|
|
524
|
-
test('generic function: identity<T>(x: T): T', () => {
|
|
525
|
-
const out = sigs(['function identity<T>(x: T): T { return x; }']);
|
|
526
|
-
expect(out.join('\n')).toMatch(/function identity<T>\(x: T\): T/);
|
|
527
|
-
});
|
|
528
|
-
|
|
529
|
-
test('generic class: Container<T>', () => {
|
|
530
|
-
const out = sigs([
|
|
531
|
-
'class Container<T> {',
|
|
532
|
-
' value: T;',
|
|
533
|
-
'}',
|
|
534
|
-
]);
|
|
535
|
-
expect(out.join('\n')).toMatch(/class Container<T>/);
|
|
536
|
-
});
|
|
537
|
-
|
|
538
|
-
test('class method with TS return type annotation', () => {
|
|
539
|
-
const out = sigs([
|
|
540
|
-
'class C {',
|
|
541
|
-
' foo(x: number): string { return String(x); }',
|
|
542
|
-
'}',
|
|
543
|
-
]);
|
|
544
|
-
expect(out.join('\n')).toMatch(/foo\(x: number\): string/);
|
|
545
|
-
});
|
|
546
|
-
|
|
547
|
-
test('TSX function component: () => JSX.Element', () => {
|
|
548
|
-
const out = sigs([
|
|
549
|
-
'function App(): JSX.Element {',
|
|
550
|
-
' return <div />;',
|
|
551
|
-
'}',
|
|
552
|
-
]);
|
|
553
|
-
expect(out.join('\n')).toMatch(/function App\(\): JSX\.Element/);
|
|
554
|
-
});
|
|
555
|
-
|
|
556
|
-
test('ESM re-export: export { foo } from "./bar"', () => {
|
|
557
|
-
const out = sigs([`export { foo } from './bar';`]);
|
|
558
|
-
expect(out.join('\n')).toMatch(/export \{ foo \} from/);
|
|
559
|
-
});
|
|
560
|
-
|
|
561
|
-
test('ESM aliased re-export: export { foo as bar } from "./baz"', () => {
|
|
562
|
-
const out = sigs([`export { foo as bar } from './baz';`]);
|
|
563
|
-
expect(out.join('\n')).toMatch(/export \{ foo as bar \} from/);
|
|
564
|
-
});
|
|
565
|
-
|
|
566
|
-
test('decorator above class — class is still captured (decorator dropped is acceptable)', () => {
|
|
567
|
-
const out = sigs([
|
|
568
|
-
'@Component({ selector: "x" })',
|
|
569
|
-
'class Foo {}',
|
|
570
|
-
]);
|
|
571
|
-
expect(out.join('\n')).toMatch(/class Foo/);
|
|
572
|
-
});
|
|
573
|
-
|
|
574
|
-
test('literal-union return type', () => {
|
|
575
|
-
const out = sigs([`function getKind(): "a" | "b" { return "a"; }`]);
|
|
576
|
-
expect(out.join('\n')).toMatch(/function getKind\(\):/);
|
|
577
|
-
});
|
|
578
|
-
|
|
579
|
-
test('export const generic arrow: <T>(x: T): T => x', () => {
|
|
580
|
-
const out = sigs(['export const fn = <T>(x: T): T => x;']);
|
|
581
|
-
expect(out.join('\n')).toMatch(/export const fn/);
|
|
582
|
-
});
|
|
583
|
-
|
|
584
|
-
// ── Language detection on all declared TS/TSX/MJS/CJS extensions ──
|
|
585
|
-
|
|
586
|
-
test('_langOf maps .ts / .tsx / .mjs / .cjs / .jsx → "js"', () => {
|
|
587
|
-
expect(tool._langOf('a.ts')).toBe('js');
|
|
588
|
-
expect(tool._langOf('a.tsx')).toBe('js');
|
|
589
|
-
expect(tool._langOf('a.mjs')).toBe('js');
|
|
590
|
-
expect(tool._langOf('a.cjs')).toBe('js');
|
|
591
|
-
expect(tool._langOf('a.jsx')).toBe('js');
|
|
592
|
-
// Case-insensitive on the extension.
|
|
593
|
-
expect(tool._langOf('a.TS')).toBe('js');
|
|
594
|
-
});
|
|
595
|
-
|
|
596
|
-
// ── Known gaps. These tests pin CURRENT (limited) behavior so an
|
|
597
|
-
// improvement to the parser fails them — at which point you
|
|
598
|
-
// update the assertion. Each gap is real and worth fixing in a
|
|
599
|
-
// tree-sitter migration. ─────────────────────────────────────
|
|
600
|
-
|
|
601
|
-
describe('KNOWN GAPS — pin current limitations', () => {
|
|
602
|
-
test('GAP: bare `interface` (no `export`) is NOT captured', () => {
|
|
603
|
-
const out = sigs(['interface User { id: string; name: string; }']);
|
|
604
|
-
expect(out).toEqual([]);
|
|
605
|
-
});
|
|
606
|
-
|
|
607
|
-
test('GAP: bare `type` alias (no `export`) is NOT captured', () => {
|
|
608
|
-
const out = sigs(['type ID = string | number;']);
|
|
609
|
-
expect(out).toEqual([]);
|
|
610
|
-
});
|
|
611
|
-
|
|
612
|
-
test('GAP: bare `enum` (no `export`) is NOT captured', () => {
|
|
613
|
-
const out = sigs(['enum Color { Red, Green, Blue }']);
|
|
614
|
-
expect(out).toEqual([]);
|
|
615
|
-
});
|
|
616
|
-
|
|
617
|
-
test('GAP: bare multi-line `interface` (no `export`) is NOT captured', () => {
|
|
618
|
-
const out = sigs([
|
|
619
|
-
'interface User {',
|
|
620
|
-
' id: string;',
|
|
621
|
-
'}',
|
|
622
|
-
]);
|
|
623
|
-
expect(out).toEqual([]);
|
|
624
|
-
});
|
|
625
|
-
|
|
626
|
-
test('GAP: typed arrow component `const App: React.FC = () => <div />` is NOT captured', () => {
|
|
627
|
-
// The `: React.FC` annotation between the identifier and `=`
|
|
628
|
-
// breaks the parser's "ident = arrow" recognition. Common in
|
|
629
|
-
// older React+TS codebases.
|
|
630
|
-
const out = sigs(['const App: React.FC = () => <div />;']);
|
|
631
|
-
expect(out).toEqual([]);
|
|
632
|
-
});
|
|
633
|
-
|
|
634
|
-
test('GAP: async generator `async function* foo()` is NOT captured', () => {
|
|
635
|
-
const out = sigs([
|
|
636
|
-
'async function* stream(): AsyncIterableIterator<number> {',
|
|
637
|
-
' yield 1;',
|
|
638
|
-
'}',
|
|
639
|
-
]);
|
|
640
|
-
expect(out).toEqual([]);
|
|
641
|
-
});
|
|
642
|
-
|
|
643
|
-
test('GAP: destructured-arg arrow with type annotation is NOT captured', () => {
|
|
644
|
-
// `const fn = ({ name }: { name: string }): string => …`
|
|
645
|
-
// The destructured + typed parameter list trips the
|
|
646
|
-
// ident = arrow recognition.
|
|
647
|
-
const out = sigs([`const greet = ({ name }: { name: string }): string => \`hi \${name}\`;`]);
|
|
648
|
-
expect(out).toEqual([]);
|
|
649
|
-
});
|
|
650
|
-
});
|
|
651
|
-
});
|
|
652
|
-
|
|
653
|
-
// ─────────────────────────────────────────────────────────────────
|
|
654
|
-
// C / C++ — _parseC. Same approach as the JS path; we lock the
|
|
655
|
-
// patterns the regex needs to handle on real-world C/CPP files so
|
|
656
|
-
// a future refactor (e.g. swapping for tree-sitter) has explicit
|
|
657
|
-
// behavior to preserve.
|
|
658
|
-
// ─────────────────────────────────────────────────────────────────
|
|
659
|
-
describe('_parseC — C/C++ skeleton extraction', () => {
|
|
660
|
-
const opts = { publicOnly: false, withComments: false, includeImports: true };
|
|
661
|
-
const sig = (lines) => tool._parseC(lines, opts).filter(e => e.kind === 'signature').map(e => e.text.trim());
|
|
662
|
-
const imp = (lines) => tool._parseC(lines, opts).filter(e => e.kind === 'import').map(e => e.text.trim());
|
|
663
|
-
|
|
664
|
-
test('#include with angle brackets and quotes', () => {
|
|
665
|
-
const lines = [
|
|
666
|
-
'#include <stdio.h>',
|
|
667
|
-
'#include "myheader.h"',
|
|
668
|
-
];
|
|
669
|
-
expect(imp(lines)).toEqual([
|
|
670
|
-
'#include <stdio.h>',
|
|
671
|
-
'#include "myheader.h"',
|
|
672
|
-
]);
|
|
673
|
-
});
|
|
674
|
-
|
|
675
|
-
test('#define captured as signature', () => {
|
|
676
|
-
expect(sig(['#define MAX_BUF 1024', '#define PI 3.14'])).toEqual([
|
|
677
|
-
'#define MAX_BUF 1024',
|
|
678
|
-
'#define PI 3.14',
|
|
679
|
-
]);
|
|
680
|
-
});
|
|
681
|
-
|
|
682
|
-
test('class / struct / union / enum / namespace headers', () => {
|
|
683
|
-
const lines = [
|
|
684
|
-
'class Foo {',
|
|
685
|
-
'};',
|
|
686
|
-
'struct Bar {',
|
|
687
|
-
'};',
|
|
688
|
-
'union Baz { int a; };',
|
|
689
|
-
'enum class Color { RED, GREEN };',
|
|
690
|
-
'namespace loxia {',
|
|
691
|
-
'}',
|
|
692
|
-
];
|
|
693
|
-
const found = sig(lines).join('\n');
|
|
694
|
-
expect(found).toMatch(/class Foo/);
|
|
695
|
-
expect(found).toMatch(/struct Bar/);
|
|
696
|
-
expect(found).toMatch(/union Baz/);
|
|
697
|
-
expect(found).toMatch(/enum class Color/);
|
|
698
|
-
expect(found).toMatch(/namespace loxia/);
|
|
699
|
-
});
|
|
700
|
-
|
|
701
|
-
test('typedef and using alias', () => {
|
|
702
|
-
const lines = [
|
|
703
|
-
'typedef unsigned long u64;',
|
|
704
|
-
'using IntList = std::vector<int>;',
|
|
705
|
-
];
|
|
706
|
-
const found = sig(lines).join('\n');
|
|
707
|
-
expect(found).toMatch(/typedef unsigned long u64/);
|
|
708
|
-
expect(found).toMatch(/using IntList/);
|
|
709
|
-
});
|
|
710
|
-
|
|
711
|
-
test('free function definition with simple return type', () => {
|
|
712
|
-
const lines = [
|
|
713
|
-
'int add(int a, int b) {',
|
|
714
|
-
' return a + b;',
|
|
715
|
-
'}',
|
|
716
|
-
];
|
|
717
|
-
expect(sig(lines).join('\n')).toMatch(/int add\(int a, int b\)/);
|
|
718
|
-
});
|
|
719
|
-
|
|
720
|
-
test('static + inline + const-qualified function definitions', () => {
|
|
721
|
-
const lines = [
|
|
722
|
-
'static inline int square(int x) { return x * x; }',
|
|
723
|
-
'const char* greet(const char* name) {',
|
|
724
|
-
' return name;',
|
|
725
|
-
'}',
|
|
726
|
-
];
|
|
727
|
-
const found = sig(lines).join('\n');
|
|
728
|
-
expect(found).toMatch(/static inline int square/);
|
|
729
|
-
expect(found).toMatch(/const char\* greet/);
|
|
730
|
-
});
|
|
731
|
-
|
|
732
|
-
test('method, constructor, and destructor', () => {
|
|
733
|
-
const lines = [
|
|
734
|
-
'Foo::Foo(int x) : x_(x) {',
|
|
735
|
-
'}',
|
|
736
|
-
'Foo::~Foo() {',
|
|
737
|
-
'}',
|
|
738
|
-
'int Foo::compute(int n) const {',
|
|
739
|
-
' return n * x_;',
|
|
740
|
-
'}',
|
|
741
|
-
];
|
|
742
|
-
const found = sig(lines).join('\n');
|
|
743
|
-
expect(found).toMatch(/Foo::Foo\(int x\)/);
|
|
744
|
-
expect(found).toMatch(/Foo::~Foo\(\)/);
|
|
745
|
-
expect(found).toMatch(/int Foo::compute/);
|
|
746
|
-
});
|
|
747
|
-
|
|
748
|
-
test('REGRESSION: function CALLS are NOT misidentified as definitions', () => {
|
|
749
|
-
// Without the call-rejection heuristic, the regex would happily
|
|
750
|
-
// grab `printf("hi");` as a signature. Lock that it doesn't.
|
|
751
|
-
const lines = [
|
|
752
|
-
'int main(void) {',
|
|
753
|
-
' printf("hi");',
|
|
754
|
-
' foo();',
|
|
755
|
-
' return 0;',
|
|
756
|
-
'}',
|
|
757
|
-
];
|
|
758
|
-
// Only main(void) should be a signature; printf/foo are calls.
|
|
759
|
-
const found = sig(lines);
|
|
760
|
-
expect(found).toEqual(['int main(void) {']);
|
|
761
|
-
});
|
|
762
|
-
|
|
763
|
-
test('REGRESSION: a tiny realistic C file produces a useful skeleton', () => {
|
|
764
|
-
const lines = [
|
|
765
|
-
'#include <stdio.h>',
|
|
766
|
-
'',
|
|
767
|
-
'#define BUF_SIZE 256',
|
|
768
|
-
'',
|
|
769
|
-
'typedef struct {',
|
|
770
|
-
' int x;',
|
|
771
|
-
' int y;',
|
|
772
|
-
'} Point;',
|
|
773
|
-
'',
|
|
774
|
-
'static int distance_sq(Point p) {',
|
|
775
|
-
' return p.x * p.x + p.y * p.y;',
|
|
776
|
-
'}',
|
|
777
|
-
'',
|
|
778
|
-
'int main(int argc, char** argv) {',
|
|
779
|
-
' Point p = { 3, 4 };',
|
|
780
|
-
' printf("d² = %d\\n", distance_sq(p));',
|
|
781
|
-
' return 0;',
|
|
782
|
-
'}',
|
|
783
|
-
];
|
|
784
|
-
const all = tool._parseC(lines, opts);
|
|
785
|
-
// Includes count as imports; the rest are signatures.
|
|
786
|
-
const imports = all.filter(e => e.kind === 'import').map(e => e.text.trim());
|
|
787
|
-
const signatures = all.filter(e => e.kind === 'signature').map(e => e.text.trim());
|
|
788
|
-
expect(imports).toEqual(['#include <stdio.h>']);
|
|
789
|
-
expect(signatures).toEqual(expect.arrayContaining([
|
|
790
|
-
'#define BUF_SIZE 256',
|
|
791
|
-
'static int distance_sq(Point p) {',
|
|
792
|
-
'int main(int argc, char** argv) {',
|
|
793
|
-
]));
|
|
794
|
-
});
|
|
795
|
-
});
|
|
796
|
-
|
|
797
|
-
test('_langOf detects C/C++ files', () => {
|
|
798
|
-
expect(tool._langOf('foo.c')).toBe('c');
|
|
799
|
-
expect(tool._langOf('foo.h')).toBe('c');
|
|
800
|
-
expect(tool._langOf('foo.cpp')).toBe('c');
|
|
801
|
-
expect(tool._langOf('foo.cxx')).toBe('c');
|
|
802
|
-
expect(tool._langOf('foo.hpp')).toBe('c');
|
|
803
|
-
expect(tool._langOf('foo.cc')).toBe('c');
|
|
804
|
-
expect(tool._langOf('app.js')).toBe('js'); // sanity
|
|
805
|
-
expect(tool._langOf('script.py')).toBe('python'); // sanity
|
|
806
|
-
});
|
|
807
|
-
|
|
808
|
-
test('execute skeleton on unsupported file type throws', async () => {
|
|
809
|
-
mockFsPromises.stat.mockResolvedValue({
|
|
810
|
-
isFile: () => true,
|
|
811
|
-
isDirectory: () => false
|
|
812
|
-
});
|
|
813
|
-
|
|
814
|
-
await expect(tool.execute(
|
|
815
|
-
{ action: 'skeleton', path: 'data.json' },
|
|
816
|
-
context
|
|
817
|
-
)).rejects.toThrow('Unsupported file type');
|
|
818
|
-
});
|
|
819
|
-
|
|
820
|
-
// ─────────────────────────────────────────────────────────────────
|
|
821
|
-
// Regex-free parser regressions — these cases are exactly the
|
|
822
|
-
// kind of pattern that brittle regex would silently mis-classify.
|
|
823
|
-
// They lock down the new string-utility implementation against
|
|
824
|
-
// the most common false-positive risks.
|
|
825
|
-
// ─────────────────────────────────────────────────────────────────
|
|
826
|
-
describe('REGRESSION: no-regex parsers — false-positive guards', () => {
|
|
827
|
-
const opts = { publicOnly: false, withComments: false, includeImports: true };
|
|
828
|
-
|
|
829
|
-
// JS
|
|
830
|
-
test('JS: `class` keyword does NOT match `classroom = …`', () => {
|
|
831
|
-
const entries = tool._parseJS(['classroom = 1;'], opts);
|
|
832
|
-
// No signature should be emitted — `classroom` is a plain var, not a class.
|
|
833
|
-
expect(entries.filter(e => e.kind === 'signature').length).toBe(0);
|
|
834
|
-
});
|
|
835
|
-
test('JS: string literal containing `class` does NOT push a class frame', () => {
|
|
836
|
-
// Pre-fix the original regex `(stripped.match(/\{/g) || []).length` could
|
|
837
|
-
// be tripped by content inside strings if not pre-masked. The no-regex
|
|
838
|
-
// implementation masks strings before counting braces.
|
|
839
|
-
const entries = tool._parseJS([
|
|
840
|
-
'const x = "class Foo {";',
|
|
841
|
-
], opts);
|
|
842
|
-
// Only the `const` line should produce something; no class signature.
|
|
843
|
-
const sigs = entries.filter(e => e.kind === 'signature').map(e => e.text);
|
|
844
|
-
expect(sigs.some(s => s.startsWith('class '))).toBe(false);
|
|
845
|
-
});
|
|
846
|
-
test('JS: `// foo` and `/* foo */` line/block comments still suppress signatures', () => {
|
|
847
|
-
const entries = tool._parseJS([
|
|
848
|
-
'// export function fake() {}',
|
|
849
|
-
'/* export function alsoFake() {} */',
|
|
850
|
-
'export function real() {}',
|
|
851
|
-
], opts);
|
|
852
|
-
const sigs = entries.filter(e => e.kind === 'signature').map(e => e.text.trim());
|
|
853
|
-
expect(sigs).toEqual(['export function real() {}']);
|
|
854
|
-
});
|
|
855
|
-
test('JS: `require` as a substring inside another identifier is NOT an import', () => {
|
|
856
|
-
// E.g. `prerequires = []` or `xrequireY()` — neither should be classified
|
|
857
|
-
// as an import line by the require-call detector.
|
|
858
|
-
const entries = tool._parseJS([
|
|
859
|
-
'const xrequireY = makeIt();',
|
|
860
|
-
], opts);
|
|
861
|
-
expect(entries.filter(e => e.kind === 'import').length).toBe(0);
|
|
862
|
-
});
|
|
863
|
-
test('JS: `==` after a global LHS does NOT trigger an assignment match', () => {
|
|
864
|
-
const entries = tool._parseJS([
|
|
865
|
-
'window.foo == bar;',
|
|
866
|
-
], opts);
|
|
867
|
-
expect(entries.filter(e => e.kind === 'signature').length).toBe(0);
|
|
868
|
-
});
|
|
869
|
-
|
|
870
|
-
// C
|
|
871
|
-
test('C: `class` inside a string literal does NOT push a class signature', () => {
|
|
872
|
-
const entries = tool._parseC([
|
|
873
|
-
'const char* msg = "class is a keyword in C++";',
|
|
874
|
-
], opts);
|
|
875
|
-
// The C parser is line-based and doesn't deeply track string state;
|
|
876
|
-
// this asserts the realistic case where the assignment isn't a class.
|
|
877
|
-
// The line shouldn't surface as a "class …" signature.
|
|
878
|
-
const sigs = entries.filter(e => e.kind === 'signature').map(e => e.text);
|
|
879
|
-
expect(sigs.some(s => s.trim().startsWith('class '))).toBe(false);
|
|
880
|
-
});
|
|
881
|
-
test('C: bare function CALL `foo(x, y);` is NOT a definition', () => {
|
|
882
|
-
const entries = tool._parseC([' foo(1, 2);'], opts);
|
|
883
|
-
expect(entries.filter(e => e.kind === 'signature').length).toBe(0);
|
|
884
|
-
});
|
|
885
|
-
|
|
886
|
-
// Python
|
|
887
|
-
test('Python: `class` keyword does NOT match `classroom = 1`', () => {
|
|
888
|
-
const entries = tool._parsePython(['classroom = 1'], opts);
|
|
889
|
-
// It's a top-level data assignment, NOT a class signature.
|
|
890
|
-
const sigs = entries.filter(e => e.kind === 'signature').map(e => e.text);
|
|
891
|
-
expect(sigs.length).toBe(0);
|
|
892
|
-
const datas = entries.filter(e => e.kind === 'data');
|
|
893
|
-
expect(datas.length).toBe(1);
|
|
894
|
-
});
|
|
895
|
-
test('Python: `def` keyword on a line where it is NOT the leading token (e.g. `nodef(x):`) is not a function', () => {
|
|
896
|
-
// The word-boundary check in startsWithKeyword catches this case.
|
|
897
|
-
const entries = tool._parsePython(['nodef(x):'], opts);
|
|
898
|
-
expect(entries.filter(e => e.kind === 'signature').length).toBe(0);
|
|
899
|
-
});
|
|
900
|
-
test('Python: relative import `from . import foo` is captured', () => {
|
|
901
|
-
const entries = tool._parsePython(['from . import foo'], opts);
|
|
902
|
-
expect(entries.filter(e => e.kind === 'import').length).toBe(1);
|
|
903
|
-
});
|
|
904
|
-
test('Python: docstring closes correctly on a one-liner', () => {
|
|
905
|
-
const entries = tool._parsePython([
|
|
906
|
-
'def hello():',
|
|
907
|
-
' """One-liner docstring."""',
|
|
908
|
-
' return 1',
|
|
909
|
-
], { ...opts, withComments: true });
|
|
910
|
-
const kinds = entries.map(e => e.kind);
|
|
911
|
-
expect(kinds).toContain('signature');
|
|
912
|
-
expect(kinds).toContain('comment');
|
|
913
|
-
});
|
|
914
|
-
});
|
|
915
|
-
});
|
|
1
|
+
import { jest, describe, test, expect, beforeEach } from '@jest/globals';
|
|
2
|
+
import { createMockLogger } from '../../__test-utils__/mockFactories.js';
|
|
3
|
+
|
|
4
|
+
// Mock fs before import
|
|
5
|
+
const mockFsPromises = {
|
|
6
|
+
stat: jest.fn(),
|
|
7
|
+
readFile: jest.fn(),
|
|
8
|
+
readdir: jest.fn()
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
jest.unstable_mockModule('fs', () => ({
|
|
12
|
+
default: { promises: mockFsPromises, readFileSync: jest.fn(() => { throw new Error('no file'); }) },
|
|
13
|
+
promises: mockFsPromises,
|
|
14
|
+
readFileSync: jest.fn(() => { throw new Error('no file'); })
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
// Mock constants
|
|
18
|
+
jest.unstable_mockModule('../../utilities/constants.js', () => ({
|
|
19
|
+
TOOL_STATUS: { PENDING: 'pending', EXECUTING: 'executing', COMPLETED: 'completed', FAILED: 'failed' },
|
|
20
|
+
OPERATION_STATUS: { NOT_FOUND: 'not_found' },
|
|
21
|
+
ERROR_TYPES: {},
|
|
22
|
+
SYSTEM_DEFAULTS: { MAX_TOOL_EXECUTION_TIME: 300000 }
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
const { default: CodeMapTool } = await import('../codeMapTool.js');
|
|
26
|
+
|
|
27
|
+
describe('CodeMapTool', () => {
|
|
28
|
+
let tool;
|
|
29
|
+
let logger;
|
|
30
|
+
const context = { projectDir: '/project', agentId: 'agent-1' };
|
|
31
|
+
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
jest.clearAllMocks();
|
|
34
|
+
logger = createMockLogger();
|
|
35
|
+
tool = new CodeMapTool({}, logger);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('constructor sets metadata correctly', () => {
|
|
39
|
+
expect(tool.id).toBe('code-map');
|
|
40
|
+
expect(tool.requiresProject).toBe(true);
|
|
41
|
+
expect(tool.isAsync).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('getDescription mentions skeleton and read-range', () => {
|
|
45
|
+
const desc = tool.getDescription();
|
|
46
|
+
expect(desc).toContain('skeleton');
|
|
47
|
+
expect(desc).toContain('read-range');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('getRequiredParameters returns action', () => {
|
|
51
|
+
expect(tool.getRequiredParameters()).toEqual(['action']);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('parseParameters parses JSON content', () => {
|
|
55
|
+
const content = JSON.stringify({
|
|
56
|
+
action: 'skeleton',
|
|
57
|
+
path: 'src/',
|
|
58
|
+
level: 'B.0'
|
|
59
|
+
});
|
|
60
|
+
const result = tool.parseParameters(content);
|
|
61
|
+
expect(result.action).toBe('skeleton');
|
|
62
|
+
expect(result.path).toBe('src/');
|
|
63
|
+
expect(result.level).toBe('B.0');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('parseParameters parses nested parameters JSON', () => {
|
|
67
|
+
const content = JSON.stringify({
|
|
68
|
+
parameters: { action: 'read-range', filePath: 'index.js', startLine: 1, endLine: 10 }
|
|
69
|
+
});
|
|
70
|
+
const result = tool.parseParameters(content);
|
|
71
|
+
expect(result.action).toBe('read-range');
|
|
72
|
+
expect(result.filePath).toBe('index.js');
|
|
73
|
+
expect(result.startLine).toBe(1);
|
|
74
|
+
expect(result.endLine).toBe(10);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('parseParameters parses XML content', () => {
|
|
78
|
+
const content = '<action>skeleton</action><path>src/</path><level>A.0</level>';
|
|
79
|
+
const result = tool.parseParameters(content);
|
|
80
|
+
expect(result.action).toBe('skeleton');
|
|
81
|
+
expect(result.path).toBe('src/');
|
|
82
|
+
expect(result.level).toBe('A.0');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('parseParameters returns parseError on bad JSON', () => {
|
|
86
|
+
const result = tool.parseParameters('{ broken');
|
|
87
|
+
expect(result).toHaveProperty('parseError');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('customValidateParameters rejects missing action', () => {
|
|
91
|
+
const result = tool.customValidateParameters({});
|
|
92
|
+
expect(result.valid).toBe(false);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('customValidateParameters rejects invalid action', () => {
|
|
96
|
+
const result = tool.customValidateParameters({ action: 'invalid' });
|
|
97
|
+
expect(result.valid).toBe(false);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('customValidateParameters requires path for skeleton', () => {
|
|
101
|
+
const result = tool.customValidateParameters({ action: 'skeleton' });
|
|
102
|
+
expect(result.valid).toBe(false);
|
|
103
|
+
expect(result.errors.some(e => e.includes('path'))).toBe(true);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('customValidateParameters rejects invalid level', () => {
|
|
107
|
+
const result = tool.customValidateParameters({ action: 'skeleton', path: 'src/', level: 'X.9' });
|
|
108
|
+
expect(result.valid).toBe(false);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('customValidateParameters requires filePath/startLine/endLine for read-range', () => {
|
|
112
|
+
const result = tool.customValidateParameters({ action: 'read-range' });
|
|
113
|
+
expect(result.valid).toBe(false);
|
|
114
|
+
expect(result.errors.length).toBeGreaterThanOrEqual(3);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('customValidateParameters rejects endLine < startLine', () => {
|
|
118
|
+
const result = tool.customValidateParameters({
|
|
119
|
+
action: 'read-range', filePath: 'a.js', startLine: 10, endLine: 5
|
|
120
|
+
});
|
|
121
|
+
expect(result.valid).toBe(false);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test('customValidateParameters rejects range exceeding max', () => {
|
|
125
|
+
const result = tool.customValidateParameters({
|
|
126
|
+
action: 'read-range', filePath: 'a.js', startLine: 1, endLine: 600
|
|
127
|
+
});
|
|
128
|
+
expect(result.valid).toBe(false);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('customValidateParameters accepts valid skeleton params', () => {
|
|
132
|
+
const result = tool.customValidateParameters({ action: 'skeleton', path: 'src/' });
|
|
133
|
+
expect(result.valid).toBe(true);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('execute skeleton on single JS file', async () => {
|
|
137
|
+
const jsContent = [
|
|
138
|
+
'import express from "express";',
|
|
139
|
+
'',
|
|
140
|
+
'export class App {',
|
|
141
|
+
' constructor() {}',
|
|
142
|
+
' start() {',
|
|
143
|
+
' console.log("started");',
|
|
144
|
+
' }',
|
|
145
|
+
'}',
|
|
146
|
+
'',
|
|
147
|
+
'export function main() {',
|
|
148
|
+
' return new App();',
|
|
149
|
+
'}'
|
|
150
|
+
].join('\n');
|
|
151
|
+
|
|
152
|
+
mockFsPromises.stat.mockResolvedValue({
|
|
153
|
+
isFile: () => true,
|
|
154
|
+
isDirectory: () => false,
|
|
155
|
+
size: jsContent.length
|
|
156
|
+
});
|
|
157
|
+
mockFsPromises.readFile.mockResolvedValue(jsContent);
|
|
158
|
+
|
|
159
|
+
const result = await tool.execute(
|
|
160
|
+
{ action: 'skeleton', path: 'src/app.js', level: 'B.0' },
|
|
161
|
+
context
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
expect(result.success).toBe(true);
|
|
165
|
+
expect(result.action).toBe('skeleton');
|
|
166
|
+
expect(result.totalFiles).toBeGreaterThanOrEqual(1);
|
|
167
|
+
expect(result.totalEntries).toBeGreaterThanOrEqual(1);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test('execute skeleton on directory with JS files', async () => {
|
|
171
|
+
// First stat: directory check
|
|
172
|
+
mockFsPromises.stat
|
|
173
|
+
.mockResolvedValueOnce({ isFile: () => false, isDirectory: () => true }) // path stat
|
|
174
|
+
.mockResolvedValueOnce({ size: 100 }); // file stat
|
|
175
|
+
|
|
176
|
+
// Discover files - readdir for root
|
|
177
|
+
mockFsPromises.readdir.mockResolvedValueOnce([
|
|
178
|
+
{ name: 'index.js', isDirectory: () => false, isFile: () => true, isSymbolicLink: () => false }
|
|
179
|
+
]);
|
|
180
|
+
|
|
181
|
+
mockFsPromises.readFile
|
|
182
|
+
.mockRejectedValueOnce(new Error('no .gitignore')) // _loadGitignoreRules
|
|
183
|
+
.mockResolvedValueOnce('export function hello() { return 1; }\n'); // file content
|
|
184
|
+
|
|
185
|
+
const result = await tool.execute(
|
|
186
|
+
{ action: 'skeleton', path: 'src/', level: 'A.0' },
|
|
187
|
+
context
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
expect(result.success).toBe(true);
|
|
191
|
+
expect(result.action).toBe('skeleton');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test('execute skeleton returns empty when no supported files', async () => {
|
|
195
|
+
mockFsPromises.stat.mockResolvedValue({ isFile: () => false, isDirectory: () => true });
|
|
196
|
+
mockFsPromises.readdir.mockResolvedValue([]);
|
|
197
|
+
mockFsPromises.readFile.mockRejectedValue(new Error('no file'));
|
|
198
|
+
|
|
199
|
+
const result = await tool.execute(
|
|
200
|
+
{ action: 'skeleton', path: 'empty/' },
|
|
201
|
+
context
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
expect(result.success).toBe(true);
|
|
205
|
+
expect(result.totalFiles).toBe(0);
|
|
206
|
+
expect(result.message).toContain('No supported files');
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test('execute skeleton throws for non-existent path', async () => {
|
|
210
|
+
mockFsPromises.stat.mockRejectedValue(new Error('ENOENT'));
|
|
211
|
+
|
|
212
|
+
await expect(tool.execute(
|
|
213
|
+
{ action: 'skeleton', path: 'missing/' },
|
|
214
|
+
context
|
|
215
|
+
)).rejects.toThrow('Path not found');
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test('execute read-range returns formatted lines', async () => {
|
|
219
|
+
const content = 'line1\nline2\nline3\nline4\nline5\n';
|
|
220
|
+
mockFsPromises.readFile.mockResolvedValue(content);
|
|
221
|
+
|
|
222
|
+
const result = await tool.execute(
|
|
223
|
+
{ action: 'read-range', filePath: 'src/index.js', startLine: 2, endLine: 4 },
|
|
224
|
+
context
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
expect(result.success).toBe(true);
|
|
228
|
+
expect(result.action).toBe('read-range');
|
|
229
|
+
expect(result.linesReturned).toBe(3);
|
|
230
|
+
expect(result.content).toContain('line2');
|
|
231
|
+
expect(result.content).toContain('line3');
|
|
232
|
+
expect(result.content).toContain('line4');
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test('execute read-range throws when startLine exceeds file length', async () => {
|
|
236
|
+
mockFsPromises.readFile.mockResolvedValue('line1\nline2\n');
|
|
237
|
+
|
|
238
|
+
await expect(tool.execute(
|
|
239
|
+
{ action: 'read-range', filePath: 'a.js', startLine: 100, endLine: 110 },
|
|
240
|
+
context
|
|
241
|
+
)).rejects.toThrow('exceeds file length');
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test('execute read-range throws for missing file', async () => {
|
|
245
|
+
mockFsPromises.readFile.mockRejectedValue(new Error('ENOENT'));
|
|
246
|
+
|
|
247
|
+
await expect(tool.execute(
|
|
248
|
+
{ action: 'read-range', filePath: 'missing.js', startLine: 1, endLine: 5 },
|
|
249
|
+
context
|
|
250
|
+
)).rejects.toThrow('File not found');
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test('execute throws on unknown action', async () => {
|
|
254
|
+
await expect(tool.execute(
|
|
255
|
+
{ action: 'unknown' },
|
|
256
|
+
context
|
|
257
|
+
)).rejects.toThrow('Unknown action');
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test('_langOf detects python files', () => {
|
|
261
|
+
expect(tool._langOf('script.py')).toBe('python');
|
|
262
|
+
expect(tool._langOf('app.js')).toBe('js');
|
|
263
|
+
expect(tool._langOf('component.tsx')).toBe('js');
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test('_parseJS extracts exported functions', () => {
|
|
267
|
+
const lines = [
|
|
268
|
+
'export function hello() {',
|
|
269
|
+
' return 1;',
|
|
270
|
+
'}'
|
|
271
|
+
];
|
|
272
|
+
const entries = tool._parseJS(lines, { publicOnly: true, withComments: false, includeImports: false });
|
|
273
|
+
expect(entries.length).toBeGreaterThanOrEqual(1);
|
|
274
|
+
expect(entries[0].kind).toBe('signature');
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test('_parseJS extracts imports when includeImports is true', () => {
|
|
278
|
+
const lines = [
|
|
279
|
+
'import express from "express";',
|
|
280
|
+
'const x = require("path");',
|
|
281
|
+
'export function hello() {}'
|
|
282
|
+
];
|
|
283
|
+
const entries = tool._parseJS(lines, { publicOnly: false, withComments: false, includeImports: true });
|
|
284
|
+
const imports = entries.filter(e => e.kind === 'import');
|
|
285
|
+
expect(imports.length).toBe(2);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
test('_parseJS includes comments when withComments is true', () => {
|
|
289
|
+
const lines = [
|
|
290
|
+
'/** My doc */',
|
|
291
|
+
'export function hello() {}'
|
|
292
|
+
];
|
|
293
|
+
const entries = tool._parseJS(lines, { publicOnly: false, withComments: true, includeImports: false });
|
|
294
|
+
const comments = entries.filter(e => e.kind === 'comment');
|
|
295
|
+
expect(comments.length).toBeGreaterThanOrEqual(1);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test('_parsePython extracts def and class', () => {
|
|
299
|
+
const lines = [
|
|
300
|
+
'class MyClass:',
|
|
301
|
+
' def __init__(self):',
|
|
302
|
+
' pass',
|
|
303
|
+
'',
|
|
304
|
+
'def public_func():',
|
|
305
|
+
' return 1'
|
|
306
|
+
];
|
|
307
|
+
const entries = tool._parsePython(lines, { publicOnly: false, withComments: false, includeImports: false });
|
|
308
|
+
const sigs = entries.filter(e => e.kind === 'signature');
|
|
309
|
+
expect(sigs.length).toBeGreaterThanOrEqual(2);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
test('_parsePython respects publicOnly', () => {
|
|
313
|
+
const lines = [
|
|
314
|
+
'def public_func():',
|
|
315
|
+
' pass',
|
|
316
|
+
'def _private_func():',
|
|
317
|
+
' pass'
|
|
318
|
+
];
|
|
319
|
+
const entries = tool._parsePython(lines, { publicOnly: true, withComments: false, includeImports: false });
|
|
320
|
+
const sigs = entries.filter(e => e.kind === 'signature');
|
|
321
|
+
expect(sigs.length).toBe(1);
|
|
322
|
+
expect(sigs[0].text).toContain('public_func');
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
test('_parseGitignore parses rules', () => {
|
|
326
|
+
const content = '# comment\nnode_modules/\n*.log\n!important.log';
|
|
327
|
+
const rules = tool._parseGitignore(content, '');
|
|
328
|
+
expect(rules.length).toBe(3);
|
|
329
|
+
expect(rules[2].negate).toBe(true);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
test('_gitignorePatternToRegex handles ** patterns', () => {
|
|
333
|
+
const re = tool._gitignorePatternToRegex('**/test');
|
|
334
|
+
expect(re).toContain('(.+/)?');
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// ─────────────────────────────────────────────────────────────────
|
|
338
|
+
// Browser-JS / pre-ES-module patterns. Before this set of regexes,
|
|
339
|
+
// CodeMap on a vanilla client-side .js file produced an empty map
|
|
340
|
+
// because the parser only matched ESM exports + class methods.
|
|
341
|
+
// These tests pin the new patterns. Each test exercises ONE pattern
|
|
342
|
+
// in isolation so a future regression on any one of them surfaces
|
|
343
|
+
// a precise failure.
|
|
344
|
+
// ─────────────────────────────────────────────────────────────────
|
|
345
|
+
describe('_parseJS — browser-JS patterns', () => {
|
|
346
|
+
const opts = { publicOnly: false, withComments: false, includeImports: false };
|
|
347
|
+
const signatures = (entries) =>
|
|
348
|
+
entries.filter(e => e.kind === 'signature').map(e => e.text.trim());
|
|
349
|
+
|
|
350
|
+
test('top-level IIFE is captured', () => {
|
|
351
|
+
const entries = tool._parseJS([
|
|
352
|
+
'(function () {',
|
|
353
|
+
' var x = 1;',
|
|
354
|
+
'})();',
|
|
355
|
+
], opts);
|
|
356
|
+
expect(signatures(entries)).toContain('(function () {');
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
test('IIFE with leading ! or + prefix (Webpack-UMD style)', () => {
|
|
360
|
+
const entries = tool._parseJS([
|
|
361
|
+
'!function () { return 1; }();',
|
|
362
|
+
'+function () { return 2; }();',
|
|
363
|
+
], opts);
|
|
364
|
+
expect(signatures(entries)).toEqual(expect.arrayContaining([
|
|
365
|
+
'!function () { return 1; }();',
|
|
366
|
+
'+function () { return 2; }();',
|
|
367
|
+
]));
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
test('window.X = function — global assignment', () => {
|
|
371
|
+
const entries = tool._parseJS([
|
|
372
|
+
'window.MyApp = function () {',
|
|
373
|
+
' return 1;',
|
|
374
|
+
'};',
|
|
375
|
+
], opts);
|
|
376
|
+
expect(signatures(entries).join('\n')).toMatch(/window\.MyApp\s*=\s*function/);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test('globalThis.foo = () => — global arrow assignment', () => {
|
|
380
|
+
const entries = tool._parseJS([
|
|
381
|
+
'globalThis.greet = (name) => `hi ${name}`;',
|
|
382
|
+
], opts);
|
|
383
|
+
expect(signatures(entries).join('\n')).toMatch(/globalThis\.greet/);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
test('Namespace.utils.foo = function — namespaced global assignment', () => {
|
|
387
|
+
const entries = tool._parseJS([
|
|
388
|
+
'MyApp.utils.formatDate = function (d) {',
|
|
389
|
+
' return d.toISOString();',
|
|
390
|
+
'};',
|
|
391
|
+
], opts);
|
|
392
|
+
expect(signatures(entries).join('\n')).toMatch(/MyApp\.utils\.formatDate/);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
test('Foo.prototype.bar = function — prototype method', () => {
|
|
396
|
+
const entries = tool._parseJS([
|
|
397
|
+
'Foo.prototype.bar = function () {',
|
|
398
|
+
' return this.x;',
|
|
399
|
+
'};',
|
|
400
|
+
], opts);
|
|
401
|
+
expect(signatures(entries).join('\n')).toMatch(/Foo\.prototype\.bar/);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
test('Foo.prototype.bar = () => — prototype arrow assignment', () => {
|
|
405
|
+
// (Real-world prototype-as-arrow is unusual but the regex
|
|
406
|
+
// covers it for completeness.)
|
|
407
|
+
const entries = tool._parseJS([
|
|
408
|
+
'Foo.prototype.bar = () => 42;',
|
|
409
|
+
], opts);
|
|
410
|
+
expect(signatures(entries).join('\n')).toMatch(/Foo\.prototype\.bar/);
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
test('jQuery $.fn.plugin = function — jQuery plugin pattern', () => {
|
|
414
|
+
const entries = tool._parseJS([
|
|
415
|
+
'$.fn.myPlugin = function (opts) {',
|
|
416
|
+
' return this.each(function () {});',
|
|
417
|
+
'};',
|
|
418
|
+
], opts);
|
|
419
|
+
expect(signatures(entries).join('\n')).toMatch(/\$\.fn\.myPlugin/);
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
test('jQuery.fn.plugin = function — full jQuery prefix', () => {
|
|
423
|
+
const entries = tool._parseJS([
|
|
424
|
+
'jQuery.fn.banner = function () { return this; };',
|
|
425
|
+
], opts);
|
|
426
|
+
expect(signatures(entries).join('\n')).toMatch(/jQuery\.fn\.banner/);
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
test('document.addEventListener("DOMContentLoaded", …) — page lifecycle', () => {
|
|
430
|
+
const entries = tool._parseJS([
|
|
431
|
+
'document.addEventListener("DOMContentLoaded", () => {',
|
|
432
|
+
' init();',
|
|
433
|
+
'});',
|
|
434
|
+
], opts);
|
|
435
|
+
expect(signatures(entries).join('\n')).toMatch(/document\.addEventListener/);
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
test('window.addEventListener("load", …) — page lifecycle (alt)', () => {
|
|
439
|
+
const entries = tool._parseJS([
|
|
440
|
+
'window.addEventListener("load", function () { boot(); });',
|
|
441
|
+
], opts);
|
|
442
|
+
expect(signatures(entries).join('\n')).toMatch(/window\.addEventListener/);
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
test('REGRESSION: a vanilla browser file (IIFE wrapping jQuery plugins) now produces a non-empty map', () => {
|
|
446
|
+
// This is the original failure case: pure client-side .js with
|
|
447
|
+
// none of the new patterns produced zero entries. Pre-fix this
|
|
448
|
+
// file's signatures.length would have been 0; post-fix it's > 0.
|
|
449
|
+
const entries = tool._parseJS([
|
|
450
|
+
'(function ($) {',
|
|
451
|
+
' $.fn.tooltip = function (options) {',
|
|
452
|
+
' return this.each(function () {});',
|
|
453
|
+
' };',
|
|
454
|
+
' $.fn.popover = function (options) {',
|
|
455
|
+
' return this.each(function () {});',
|
|
456
|
+
' };',
|
|
457
|
+
'})(jQuery);',
|
|
458
|
+
], opts);
|
|
459
|
+
expect(signatures(entries).length).toBeGreaterThanOrEqual(3);
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
test('REGRESSION: file unaffected by changes — modern ESM still matches', () => {
|
|
463
|
+
// Make sure adding the new regexes didn't regress the original
|
|
464
|
+
// matcher set.
|
|
465
|
+
const entries = tool._parseJS([
|
|
466
|
+
'export function hello() {}',
|
|
467
|
+
'export class Greeter {}',
|
|
468
|
+
'export const sum = (a, b) => a + b;',
|
|
469
|
+
], opts);
|
|
470
|
+
expect(signatures(entries).length).toBe(3);
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
// ─────────────────────────────────────────────────────────────────
|
|
475
|
+
// TypeScript / TSX coverage. The JS parser is the same parser used
|
|
476
|
+
// for .ts / .tsx / .mjs / .cjs (see _langOf); these tests pin which
|
|
477
|
+
// TypeScript-specific patterns the no-regex parser captures TODAY
|
|
478
|
+
// and which it MISSES, so a future tree-sitter migration (see the
|
|
479
|
+
// file-header comment in codeMapTool.js) has an explicit baseline
|
|
480
|
+
// to preserve / improve against.
|
|
481
|
+
// ─────────────────────────────────────────────────────────────────
|
|
482
|
+
describe('_parseJS — TypeScript / TSX coverage', () => {
|
|
483
|
+
const opts = { publicOnly: false, withComments: false, includeImports: false };
|
|
484
|
+
const sigs = (lines) =>
|
|
485
|
+
tool._parseJS(lines, opts).filter(e => e.kind === 'signature').map(e => e.text.trim());
|
|
486
|
+
|
|
487
|
+
// ── Captures we rely on (regressions here would break TS skeletons) ──
|
|
488
|
+
|
|
489
|
+
test('export interface — one-line', () => {
|
|
490
|
+
const out = sigs(['export interface User { id: string; name: string; }']);
|
|
491
|
+
expect(out.join('\n')).toMatch(/export interface User/);
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
test('export type alias', () => {
|
|
495
|
+
const out = sigs(['export type ID = string | number;']);
|
|
496
|
+
expect(out.join('\n')).toMatch(/export type ID/);
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
test('export enum', () => {
|
|
500
|
+
const out = sigs(['export enum Color { Red, Green, Blue }']);
|
|
501
|
+
expect(out.join('\n')).toMatch(/export enum Color/);
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
test('export interface multi-line opening', () => {
|
|
505
|
+
const out = sigs([
|
|
506
|
+
'export interface User {',
|
|
507
|
+
' id: string;',
|
|
508
|
+
' name: string;',
|
|
509
|
+
'}',
|
|
510
|
+
]);
|
|
511
|
+
expect(out.join('\n')).toMatch(/export interface User \{/);
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
test('abstract class — declaration + abstract method', () => {
|
|
515
|
+
const out = sigs([
|
|
516
|
+
'abstract class Animal {',
|
|
517
|
+
' abstract sound(): string;',
|
|
518
|
+
'}',
|
|
519
|
+
]);
|
|
520
|
+
expect(out.join('\n')).toMatch(/abstract class Animal/);
|
|
521
|
+
expect(out.join('\n')).toMatch(/abstract sound\(\): string/);
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
test('generic function: identity<T>(x: T): T', () => {
|
|
525
|
+
const out = sigs(['function identity<T>(x: T): T { return x; }']);
|
|
526
|
+
expect(out.join('\n')).toMatch(/function identity<T>\(x: T\): T/);
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
test('generic class: Container<T>', () => {
|
|
530
|
+
const out = sigs([
|
|
531
|
+
'class Container<T> {',
|
|
532
|
+
' value: T;',
|
|
533
|
+
'}',
|
|
534
|
+
]);
|
|
535
|
+
expect(out.join('\n')).toMatch(/class Container<T>/);
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
test('class method with TS return type annotation', () => {
|
|
539
|
+
const out = sigs([
|
|
540
|
+
'class C {',
|
|
541
|
+
' foo(x: number): string { return String(x); }',
|
|
542
|
+
'}',
|
|
543
|
+
]);
|
|
544
|
+
expect(out.join('\n')).toMatch(/foo\(x: number\): string/);
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
test('TSX function component: () => JSX.Element', () => {
|
|
548
|
+
const out = sigs([
|
|
549
|
+
'function App(): JSX.Element {',
|
|
550
|
+
' return <div />;',
|
|
551
|
+
'}',
|
|
552
|
+
]);
|
|
553
|
+
expect(out.join('\n')).toMatch(/function App\(\): JSX\.Element/);
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
test('ESM re-export: export { foo } from "./bar"', () => {
|
|
557
|
+
const out = sigs([`export { foo } from './bar';`]);
|
|
558
|
+
expect(out.join('\n')).toMatch(/export \{ foo \} from/);
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
test('ESM aliased re-export: export { foo as bar } from "./baz"', () => {
|
|
562
|
+
const out = sigs([`export { foo as bar } from './baz';`]);
|
|
563
|
+
expect(out.join('\n')).toMatch(/export \{ foo as bar \} from/);
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
test('decorator above class — class is still captured (decorator dropped is acceptable)', () => {
|
|
567
|
+
const out = sigs([
|
|
568
|
+
'@Component({ selector: "x" })',
|
|
569
|
+
'class Foo {}',
|
|
570
|
+
]);
|
|
571
|
+
expect(out.join('\n')).toMatch(/class Foo/);
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
test('literal-union return type', () => {
|
|
575
|
+
const out = sigs([`function getKind(): "a" | "b" { return "a"; }`]);
|
|
576
|
+
expect(out.join('\n')).toMatch(/function getKind\(\):/);
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
test('export const generic arrow: <T>(x: T): T => x', () => {
|
|
580
|
+
const out = sigs(['export const fn = <T>(x: T): T => x;']);
|
|
581
|
+
expect(out.join('\n')).toMatch(/export const fn/);
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
// ── Language detection on all declared TS/TSX/MJS/CJS extensions ──
|
|
585
|
+
|
|
586
|
+
test('_langOf maps .ts / .tsx / .mjs / .cjs / .jsx → "js"', () => {
|
|
587
|
+
expect(tool._langOf('a.ts')).toBe('js');
|
|
588
|
+
expect(tool._langOf('a.tsx')).toBe('js');
|
|
589
|
+
expect(tool._langOf('a.mjs')).toBe('js');
|
|
590
|
+
expect(tool._langOf('a.cjs')).toBe('js');
|
|
591
|
+
expect(tool._langOf('a.jsx')).toBe('js');
|
|
592
|
+
// Case-insensitive on the extension.
|
|
593
|
+
expect(tool._langOf('a.TS')).toBe('js');
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
// ── Known gaps. These tests pin CURRENT (limited) behavior so an
|
|
597
|
+
// improvement to the parser fails them — at which point you
|
|
598
|
+
// update the assertion. Each gap is real and worth fixing in a
|
|
599
|
+
// tree-sitter migration. ─────────────────────────────────────
|
|
600
|
+
|
|
601
|
+
describe('KNOWN GAPS — pin current limitations', () => {
|
|
602
|
+
test('GAP: bare `interface` (no `export`) is NOT captured', () => {
|
|
603
|
+
const out = sigs(['interface User { id: string; name: string; }']);
|
|
604
|
+
expect(out).toEqual([]);
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
test('GAP: bare `type` alias (no `export`) is NOT captured', () => {
|
|
608
|
+
const out = sigs(['type ID = string | number;']);
|
|
609
|
+
expect(out).toEqual([]);
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
test('GAP: bare `enum` (no `export`) is NOT captured', () => {
|
|
613
|
+
const out = sigs(['enum Color { Red, Green, Blue }']);
|
|
614
|
+
expect(out).toEqual([]);
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
test('GAP: bare multi-line `interface` (no `export`) is NOT captured', () => {
|
|
618
|
+
const out = sigs([
|
|
619
|
+
'interface User {',
|
|
620
|
+
' id: string;',
|
|
621
|
+
'}',
|
|
622
|
+
]);
|
|
623
|
+
expect(out).toEqual([]);
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
test('GAP: typed arrow component `const App: React.FC = () => <div />` is NOT captured', () => {
|
|
627
|
+
// The `: React.FC` annotation between the identifier and `=`
|
|
628
|
+
// breaks the parser's "ident = arrow" recognition. Common in
|
|
629
|
+
// older React+TS codebases.
|
|
630
|
+
const out = sigs(['const App: React.FC = () => <div />;']);
|
|
631
|
+
expect(out).toEqual([]);
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
test('GAP: async generator `async function* foo()` is NOT captured', () => {
|
|
635
|
+
const out = sigs([
|
|
636
|
+
'async function* stream(): AsyncIterableIterator<number> {',
|
|
637
|
+
' yield 1;',
|
|
638
|
+
'}',
|
|
639
|
+
]);
|
|
640
|
+
expect(out).toEqual([]);
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
test('GAP: destructured-arg arrow with type annotation is NOT captured', () => {
|
|
644
|
+
// `const fn = ({ name }: { name: string }): string => …`
|
|
645
|
+
// The destructured + typed parameter list trips the
|
|
646
|
+
// ident = arrow recognition.
|
|
647
|
+
const out = sigs([`const greet = ({ name }: { name: string }): string => \`hi \${name}\`;`]);
|
|
648
|
+
expect(out).toEqual([]);
|
|
649
|
+
});
|
|
650
|
+
});
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
// ─────────────────────────────────────────────────────────────────
|
|
654
|
+
// C / C++ — _parseC. Same approach as the JS path; we lock the
|
|
655
|
+
// patterns the regex needs to handle on real-world C/CPP files so
|
|
656
|
+
// a future refactor (e.g. swapping for tree-sitter) has explicit
|
|
657
|
+
// behavior to preserve.
|
|
658
|
+
// ─────────────────────────────────────────────────────────────────
|
|
659
|
+
describe('_parseC — C/C++ skeleton extraction', () => {
|
|
660
|
+
const opts = { publicOnly: false, withComments: false, includeImports: true };
|
|
661
|
+
const sig = (lines) => tool._parseC(lines, opts).filter(e => e.kind === 'signature').map(e => e.text.trim());
|
|
662
|
+
const imp = (lines) => tool._parseC(lines, opts).filter(e => e.kind === 'import').map(e => e.text.trim());
|
|
663
|
+
|
|
664
|
+
test('#include with angle brackets and quotes', () => {
|
|
665
|
+
const lines = [
|
|
666
|
+
'#include <stdio.h>',
|
|
667
|
+
'#include "myheader.h"',
|
|
668
|
+
];
|
|
669
|
+
expect(imp(lines)).toEqual([
|
|
670
|
+
'#include <stdio.h>',
|
|
671
|
+
'#include "myheader.h"',
|
|
672
|
+
]);
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
test('#define captured as signature', () => {
|
|
676
|
+
expect(sig(['#define MAX_BUF 1024', '#define PI 3.14'])).toEqual([
|
|
677
|
+
'#define MAX_BUF 1024',
|
|
678
|
+
'#define PI 3.14',
|
|
679
|
+
]);
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
test('class / struct / union / enum / namespace headers', () => {
|
|
683
|
+
const lines = [
|
|
684
|
+
'class Foo {',
|
|
685
|
+
'};',
|
|
686
|
+
'struct Bar {',
|
|
687
|
+
'};',
|
|
688
|
+
'union Baz { int a; };',
|
|
689
|
+
'enum class Color { RED, GREEN };',
|
|
690
|
+
'namespace loxia {',
|
|
691
|
+
'}',
|
|
692
|
+
];
|
|
693
|
+
const found = sig(lines).join('\n');
|
|
694
|
+
expect(found).toMatch(/class Foo/);
|
|
695
|
+
expect(found).toMatch(/struct Bar/);
|
|
696
|
+
expect(found).toMatch(/union Baz/);
|
|
697
|
+
expect(found).toMatch(/enum class Color/);
|
|
698
|
+
expect(found).toMatch(/namespace loxia/);
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
test('typedef and using alias', () => {
|
|
702
|
+
const lines = [
|
|
703
|
+
'typedef unsigned long u64;',
|
|
704
|
+
'using IntList = std::vector<int>;',
|
|
705
|
+
];
|
|
706
|
+
const found = sig(lines).join('\n');
|
|
707
|
+
expect(found).toMatch(/typedef unsigned long u64/);
|
|
708
|
+
expect(found).toMatch(/using IntList/);
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
test('free function definition with simple return type', () => {
|
|
712
|
+
const lines = [
|
|
713
|
+
'int add(int a, int b) {',
|
|
714
|
+
' return a + b;',
|
|
715
|
+
'}',
|
|
716
|
+
];
|
|
717
|
+
expect(sig(lines).join('\n')).toMatch(/int add\(int a, int b\)/);
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
test('static + inline + const-qualified function definitions', () => {
|
|
721
|
+
const lines = [
|
|
722
|
+
'static inline int square(int x) { return x * x; }',
|
|
723
|
+
'const char* greet(const char* name) {',
|
|
724
|
+
' return name;',
|
|
725
|
+
'}',
|
|
726
|
+
];
|
|
727
|
+
const found = sig(lines).join('\n');
|
|
728
|
+
expect(found).toMatch(/static inline int square/);
|
|
729
|
+
expect(found).toMatch(/const char\* greet/);
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
test('method, constructor, and destructor', () => {
|
|
733
|
+
const lines = [
|
|
734
|
+
'Foo::Foo(int x) : x_(x) {',
|
|
735
|
+
'}',
|
|
736
|
+
'Foo::~Foo() {',
|
|
737
|
+
'}',
|
|
738
|
+
'int Foo::compute(int n) const {',
|
|
739
|
+
' return n * x_;',
|
|
740
|
+
'}',
|
|
741
|
+
];
|
|
742
|
+
const found = sig(lines).join('\n');
|
|
743
|
+
expect(found).toMatch(/Foo::Foo\(int x\)/);
|
|
744
|
+
expect(found).toMatch(/Foo::~Foo\(\)/);
|
|
745
|
+
expect(found).toMatch(/int Foo::compute/);
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
test('REGRESSION: function CALLS are NOT misidentified as definitions', () => {
|
|
749
|
+
// Without the call-rejection heuristic, the regex would happily
|
|
750
|
+
// grab `printf("hi");` as a signature. Lock that it doesn't.
|
|
751
|
+
const lines = [
|
|
752
|
+
'int main(void) {',
|
|
753
|
+
' printf("hi");',
|
|
754
|
+
' foo();',
|
|
755
|
+
' return 0;',
|
|
756
|
+
'}',
|
|
757
|
+
];
|
|
758
|
+
// Only main(void) should be a signature; printf/foo are calls.
|
|
759
|
+
const found = sig(lines);
|
|
760
|
+
expect(found).toEqual(['int main(void) {']);
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
test('REGRESSION: a tiny realistic C file produces a useful skeleton', () => {
|
|
764
|
+
const lines = [
|
|
765
|
+
'#include <stdio.h>',
|
|
766
|
+
'',
|
|
767
|
+
'#define BUF_SIZE 256',
|
|
768
|
+
'',
|
|
769
|
+
'typedef struct {',
|
|
770
|
+
' int x;',
|
|
771
|
+
' int y;',
|
|
772
|
+
'} Point;',
|
|
773
|
+
'',
|
|
774
|
+
'static int distance_sq(Point p) {',
|
|
775
|
+
' return p.x * p.x + p.y * p.y;',
|
|
776
|
+
'}',
|
|
777
|
+
'',
|
|
778
|
+
'int main(int argc, char** argv) {',
|
|
779
|
+
' Point p = { 3, 4 };',
|
|
780
|
+
' printf("d² = %d\\n", distance_sq(p));',
|
|
781
|
+
' return 0;',
|
|
782
|
+
'}',
|
|
783
|
+
];
|
|
784
|
+
const all = tool._parseC(lines, opts);
|
|
785
|
+
// Includes count as imports; the rest are signatures.
|
|
786
|
+
const imports = all.filter(e => e.kind === 'import').map(e => e.text.trim());
|
|
787
|
+
const signatures = all.filter(e => e.kind === 'signature').map(e => e.text.trim());
|
|
788
|
+
expect(imports).toEqual(['#include <stdio.h>']);
|
|
789
|
+
expect(signatures).toEqual(expect.arrayContaining([
|
|
790
|
+
'#define BUF_SIZE 256',
|
|
791
|
+
'static int distance_sq(Point p) {',
|
|
792
|
+
'int main(int argc, char** argv) {',
|
|
793
|
+
]));
|
|
794
|
+
});
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
test('_langOf detects C/C++ files', () => {
|
|
798
|
+
expect(tool._langOf('foo.c')).toBe('c');
|
|
799
|
+
expect(tool._langOf('foo.h')).toBe('c');
|
|
800
|
+
expect(tool._langOf('foo.cpp')).toBe('c');
|
|
801
|
+
expect(tool._langOf('foo.cxx')).toBe('c');
|
|
802
|
+
expect(tool._langOf('foo.hpp')).toBe('c');
|
|
803
|
+
expect(tool._langOf('foo.cc')).toBe('c');
|
|
804
|
+
expect(tool._langOf('app.js')).toBe('js'); // sanity
|
|
805
|
+
expect(tool._langOf('script.py')).toBe('python'); // sanity
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
test('execute skeleton on unsupported file type throws', async () => {
|
|
809
|
+
mockFsPromises.stat.mockResolvedValue({
|
|
810
|
+
isFile: () => true,
|
|
811
|
+
isDirectory: () => false
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
await expect(tool.execute(
|
|
815
|
+
{ action: 'skeleton', path: 'data.json' },
|
|
816
|
+
context
|
|
817
|
+
)).rejects.toThrow('Unsupported file type');
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
// ─────────────────────────────────────────────────────────────────
|
|
821
|
+
// Regex-free parser regressions — these cases are exactly the
|
|
822
|
+
// kind of pattern that brittle regex would silently mis-classify.
|
|
823
|
+
// They lock down the new string-utility implementation against
|
|
824
|
+
// the most common false-positive risks.
|
|
825
|
+
// ─────────────────────────────────────────────────────────────────
|
|
826
|
+
describe('REGRESSION: no-regex parsers — false-positive guards', () => {
|
|
827
|
+
const opts = { publicOnly: false, withComments: false, includeImports: true };
|
|
828
|
+
|
|
829
|
+
// JS
|
|
830
|
+
test('JS: `class` keyword does NOT match `classroom = …`', () => {
|
|
831
|
+
const entries = tool._parseJS(['classroom = 1;'], opts);
|
|
832
|
+
// No signature should be emitted — `classroom` is a plain var, not a class.
|
|
833
|
+
expect(entries.filter(e => e.kind === 'signature').length).toBe(0);
|
|
834
|
+
});
|
|
835
|
+
test('JS: string literal containing `class` does NOT push a class frame', () => {
|
|
836
|
+
// Pre-fix the original regex `(stripped.match(/\{/g) || []).length` could
|
|
837
|
+
// be tripped by content inside strings if not pre-masked. The no-regex
|
|
838
|
+
// implementation masks strings before counting braces.
|
|
839
|
+
const entries = tool._parseJS([
|
|
840
|
+
'const x = "class Foo {";',
|
|
841
|
+
], opts);
|
|
842
|
+
// Only the `const` line should produce something; no class signature.
|
|
843
|
+
const sigs = entries.filter(e => e.kind === 'signature').map(e => e.text);
|
|
844
|
+
expect(sigs.some(s => s.startsWith('class '))).toBe(false);
|
|
845
|
+
});
|
|
846
|
+
test('JS: `// foo` and `/* foo */` line/block comments still suppress signatures', () => {
|
|
847
|
+
const entries = tool._parseJS([
|
|
848
|
+
'// export function fake() {}',
|
|
849
|
+
'/* export function alsoFake() {} */',
|
|
850
|
+
'export function real() {}',
|
|
851
|
+
], opts);
|
|
852
|
+
const sigs = entries.filter(e => e.kind === 'signature').map(e => e.text.trim());
|
|
853
|
+
expect(sigs).toEqual(['export function real() {}']);
|
|
854
|
+
});
|
|
855
|
+
test('JS: `require` as a substring inside another identifier is NOT an import', () => {
|
|
856
|
+
// E.g. `prerequires = []` or `xrequireY()` — neither should be classified
|
|
857
|
+
// as an import line by the require-call detector.
|
|
858
|
+
const entries = tool._parseJS([
|
|
859
|
+
'const xrequireY = makeIt();',
|
|
860
|
+
], opts);
|
|
861
|
+
expect(entries.filter(e => e.kind === 'import').length).toBe(0);
|
|
862
|
+
});
|
|
863
|
+
test('JS: `==` after a global LHS does NOT trigger an assignment match', () => {
|
|
864
|
+
const entries = tool._parseJS([
|
|
865
|
+
'window.foo == bar;',
|
|
866
|
+
], opts);
|
|
867
|
+
expect(entries.filter(e => e.kind === 'signature').length).toBe(0);
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
// C
|
|
871
|
+
test('C: `class` inside a string literal does NOT push a class signature', () => {
|
|
872
|
+
const entries = tool._parseC([
|
|
873
|
+
'const char* msg = "class is a keyword in C++";',
|
|
874
|
+
], opts);
|
|
875
|
+
// The C parser is line-based and doesn't deeply track string state;
|
|
876
|
+
// this asserts the realistic case where the assignment isn't a class.
|
|
877
|
+
// The line shouldn't surface as a "class …" signature.
|
|
878
|
+
const sigs = entries.filter(e => e.kind === 'signature').map(e => e.text);
|
|
879
|
+
expect(sigs.some(s => s.trim().startsWith('class '))).toBe(false);
|
|
880
|
+
});
|
|
881
|
+
test('C: bare function CALL `foo(x, y);` is NOT a definition', () => {
|
|
882
|
+
const entries = tool._parseC([' foo(1, 2);'], opts);
|
|
883
|
+
expect(entries.filter(e => e.kind === 'signature').length).toBe(0);
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
// Python
|
|
887
|
+
test('Python: `class` keyword does NOT match `classroom = 1`', () => {
|
|
888
|
+
const entries = tool._parsePython(['classroom = 1'], opts);
|
|
889
|
+
// It's a top-level data assignment, NOT a class signature.
|
|
890
|
+
const sigs = entries.filter(e => e.kind === 'signature').map(e => e.text);
|
|
891
|
+
expect(sigs.length).toBe(0);
|
|
892
|
+
const datas = entries.filter(e => e.kind === 'data');
|
|
893
|
+
expect(datas.length).toBe(1);
|
|
894
|
+
});
|
|
895
|
+
test('Python: `def` keyword on a line where it is NOT the leading token (e.g. `nodef(x):`) is not a function', () => {
|
|
896
|
+
// The word-boundary check in startsWithKeyword catches this case.
|
|
897
|
+
const entries = tool._parsePython(['nodef(x):'], opts);
|
|
898
|
+
expect(entries.filter(e => e.kind === 'signature').length).toBe(0);
|
|
899
|
+
});
|
|
900
|
+
test('Python: relative import `from . import foo` is captured', () => {
|
|
901
|
+
const entries = tool._parsePython(['from . import foo'], opts);
|
|
902
|
+
expect(entries.filter(e => e.kind === 'import').length).toBe(1);
|
|
903
|
+
});
|
|
904
|
+
test('Python: docstring closes correctly on a one-liner', () => {
|
|
905
|
+
const entries = tool._parsePython([
|
|
906
|
+
'def hello():',
|
|
907
|
+
' """One-liner docstring."""',
|
|
908
|
+
' return 1',
|
|
909
|
+
], { ...opts, withComments: true });
|
|
910
|
+
const kinds = entries.map(e => e.kind);
|
|
911
|
+
expect(kinds).toContain('signature');
|
|
912
|
+
expect(kinds).toContain('comment');
|
|
913
|
+
});
|
|
914
|
+
});
|
|
915
|
+
});
|