gsd-pi 2.78.0 → 2.78.1-dev.0fdacd524
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/README.md +60 -23
- package/dist/bundled-resource-path.d.ts +7 -0
- package/dist/bundled-resource-path.js +34 -2
- package/dist/claude-cli-check.js +104 -33
- package/dist/cli-policy.d.ts +13 -0
- package/dist/cli-policy.js +17 -0
- package/dist/cli.js +95 -55
- package/dist/headless-query.d.ts +22 -0
- package/dist/headless-query.js +43 -8
- package/dist/headless.d.ts +10 -0
- package/dist/headless.js +16 -1
- package/dist/loader.js +9 -13
- package/dist/onboarding.d.ts +10 -0
- package/dist/onboarding.js +2 -2
- package/dist/provider-migrations.d.ts +2 -2
- package/dist/provider-migrations.js +5 -2
- package/dist/resource-loader.d.ts +5 -2
- package/dist/resource-loader.js +30 -13
- package/dist/resources/.managed-resources-content-hash +1 -0
- package/dist/resources/extensions/claude-code-cli/readiness.js +128 -32
- package/dist/resources/extensions/google-search/index.js +2 -6
- package/dist/resources/extensions/gsd/auto/loop.js +23 -0
- package/dist/resources/extensions/gsd/auto/phases.js +5 -13
- package/dist/resources/extensions/gsd/auto/run-unit.js +26 -12
- package/dist/resources/extensions/gsd/auto/session.js +5 -6
- package/dist/resources/extensions/gsd/auto-dashboard.js +3 -2
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +55 -21
- package/dist/resources/extensions/gsd/auto-dispatch.js +18 -6
- package/dist/resources/extensions/gsd/auto-prompts.js +69 -2
- package/dist/resources/extensions/gsd/auto-recovery.js +43 -4
- package/dist/resources/extensions/gsd/auto-runtime-state.js +31 -0
- package/dist/resources/extensions/gsd/auto-start.js +1 -1
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +2 -2
- package/dist/resources/extensions/gsd/auto-worktree.js +60 -13
- package/dist/resources/extensions/gsd/auto.js +39 -14
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +14 -2
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +7 -5
- package/dist/resources/extensions/gsd/bootstrap/query-tools.js +2 -2
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +5 -4
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +112 -31
- package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +11 -6
- package/dist/resources/extensions/gsd/bootstrap/subagent-input.js +22 -0
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +45 -8
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +121 -3
- package/dist/resources/extensions/gsd/commands/catalog.js +76 -5
- package/dist/resources/extensions/gsd/commands/handlers/core.js +23 -1
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +8 -0
- package/dist/resources/extensions/gsd/commands-config.js +3 -2
- package/dist/resources/extensions/gsd/commands-extensions.js +46 -3
- package/dist/resources/extensions/gsd/commands-handlers.js +3 -2
- package/dist/resources/extensions/gsd/commands-mcp-status.js +3 -1
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +10 -1
- package/dist/resources/extensions/gsd/commands-worktree.js +309 -0
- package/dist/resources/extensions/gsd/dashboard-overlay.js +1 -1
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +10 -0
- package/dist/resources/extensions/gsd/doctor-providers.js +2 -1
- package/dist/resources/extensions/gsd/doctor-runtime-checks.js +39 -1
- package/dist/resources/extensions/gsd/error-classifier.js +1 -1
- package/dist/resources/extensions/gsd/forensics.js +10 -8
- package/dist/resources/extensions/gsd/git-service.js +12 -5
- package/dist/resources/extensions/gsd/gsd-db.js +11 -2
- package/dist/resources/extensions/gsd/guided-flow.js +25 -24
- package/dist/resources/extensions/gsd/home-dir.js +16 -0
- package/dist/resources/extensions/gsd/key-manager.js +2 -1
- package/dist/resources/extensions/gsd/memory-store.js +66 -31
- package/dist/resources/extensions/gsd/migrate/command.js +3 -2
- package/dist/resources/extensions/gsd/milestone-id-reservation.js +36 -0
- package/dist/resources/extensions/gsd/model-router.js +114 -9
- package/dist/resources/extensions/gsd/native-git-bridge.js +7 -1
- package/dist/resources/extensions/gsd/preferences-models.js +91 -15
- package/dist/resources/extensions/gsd/preferences-types.js +2 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +32 -0
- package/dist/resources/extensions/gsd/preferences.js +5 -3
- package/dist/resources/extensions/gsd/prompt-loader.js +23 -12
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +10 -0
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +10 -0
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
- package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +2 -0
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +10 -0
- package/dist/resources/extensions/gsd/prompts/refine-slice.md +10 -0
- package/dist/resources/extensions/gsd/prompts/rewrite-docs.md +2 -0
- package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +9 -3
- package/dist/resources/extensions/gsd/state.js +42 -0
- package/dist/resources/extensions/gsd/templates/PREFERENCES.md +1 -0
- package/dist/resources/extensions/gsd/tools/memory-tools.js +18 -1
- package/dist/resources/extensions/gsd/unit-context-manifest.js +29 -4
- package/dist/resources/extensions/gsd/visualizer-overlay.js +1 -1
- package/dist/resources/extensions/gsd/watch/header-renderer.js +3 -1
- package/dist/resources/extensions/gsd/worktree-command.js +26 -46
- package/dist/resources/extensions/gsd/worktree-manager.js +20 -1
- package/dist/resources/extensions/gsd/worktree-resolver.js +4 -13
- package/dist/resources/extensions/gsd/worktree-root.js +124 -0
- package/dist/resources/extensions/gsd/worktree-session-state.js +33 -0
- package/dist/resources/extensions/gsd/worktree.js +4 -115
- package/dist/resources/extensions/mcp-client/index.js +6 -9
- package/dist/resources/extensions/ollama/index.js +15 -2
- package/dist/resources/extensions/ollama/model-capabilities.js +31 -0
- package/dist/resources/extensions/ollama/ollama-client.js +40 -4
- package/dist/resources/extensions/slash-commands/create-extension.js +36 -22
- package/dist/resources/extensions/subagent/index.js +324 -178
- package/dist/resources/skills/create-gsd-extension/SKILL.md +9 -5
- package/dist/resources/skills/create-gsd-extension/references/custom-commands.md +1 -1
- package/dist/resources/skills/create-gsd-extension/references/custom-rendering.md +5 -5
- package/dist/resources/skills/create-gsd-extension/references/custom-tools.md +4 -4
- package/dist/resources/skills/create-gsd-extension/references/custom-ui.md +6 -6
- package/dist/resources/skills/create-gsd-extension/references/events-reference.md +3 -3
- package/dist/resources/skills/create-gsd-extension/references/packaging-distribution.md +1 -1
- package/dist/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +3 -3
- package/dist/resources/skills/create-gsd-extension/workflows/create-extension.md +32 -12
- package/dist/resources/skills/lint/SKILL.md +4 -0
- package/dist/resources/skills/review/SKILL.md +4 -0
- package/dist/resources/skills/test/SKILL.md +3 -0
- package/dist/rtk-shared.d.ts +3 -0
- package/dist/rtk-shared.js +17 -0
- package/dist/rtk.d.ts +2 -5
- package/dist/rtk.js +3 -20
- package/dist/runtime-checks.d.ts +27 -0
- package/dist/runtime-checks.js +38 -0
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
- package/dist/web/standalone/.next/build-manifest.json +4 -4
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +44 -4
- package/dist/web/standalone/.next/required-server-files.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
- package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js +4 -2
- package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/page.js +2 -2
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
- package/dist/web/standalone/.next/server/chunks/63.js +3 -3
- package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
- package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware.js +2 -2
- package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
- package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/server/webpack-runtime.js +1 -1
- package/dist/web/standalone/.next/static/chunks/2556.0527fea66e123b7f.js +1 -0
- package/dist/web/standalone/.next/static/chunks/2824.08296bc2f9654698.js +1 -0
- package/dist/web/standalone/.next/static/chunks/3026.3af53b279375f082.js +1 -0
- package/dist/web/standalone/.next/static/chunks/315.6f68ae79b67d25cf.js +1 -0
- package/dist/web/standalone/.next/static/chunks/3497.4bfc60a3b3dea717.js +1 -0
- package/dist/web/standalone/.next/static/chunks/5516.4a07c872b5c3a663.js +1 -0
- package/dist/web/standalone/.next/static/chunks/8336.31b019697882acfb.js +10 -0
- package/dist/web/standalone/.next/static/chunks/8845.c9702695e8c5a9c5.js +2 -0
- package/dist/web/standalone/.next/static/chunks/9058.01ef3a463bda88f1.js +20 -0
- package/dist/web/standalone/.next/static/chunks/9441.1081da1125d1764f.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/page-9bf2e0c50fb2ca05.js +1 -0
- package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
- package/dist/web/standalone/.next/static/chunks/webpack-f9f0dc45e4f3ac10.js +1 -0
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
- package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
- package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
- package/dist/web/standalone/package.json +2 -1
- package/dist/web/standalone/server.js +1 -1
- package/dist/welcome-screen.js +27 -1
- package/dist/worktree-cli.d.ts +1 -0
- package/dist/worktree-cli.js +9 -3
- package/dist/worktree-status-banner.d.ts +1 -0
- package/dist/worktree-status-banner.js +132 -0
- package/package.json +1 -3
- package/packages/daemon/package.json +2 -2
- package/packages/mcp-server/dist/alias-telemetry.d.ts +8 -0
- package/packages/mcp-server/dist/alias-telemetry.d.ts.map +1 -0
- package/packages/mcp-server/dist/alias-telemetry.js +30 -0
- package/packages/mcp-server/dist/alias-telemetry.js.map +1 -0
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +74 -46
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +2 -2
- package/packages/mcp-server/src/alias-telemetry.test.ts +78 -0
- package/packages/mcp-server/src/alias-telemetry.ts +30 -0
- package/packages/mcp-server/src/workflow-tools.test.ts +78 -0
- package/packages/mcp-server/src/workflow-tools.ts +93 -58
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/native/package.json +1 -1
- package/packages/native/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.cache-breakpoint.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.cache-breakpoint.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.cache-breakpoint.test.js +231 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.cache-breakpoint.test.js.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.js +48 -19
- package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +13 -0
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/dist/utils/repair-tool-json.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/repair-tool-json.js +24 -3
- package/packages/pi-ai/dist/utils/repair-tool-json.js.map +1 -1
- package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js +26 -0
- package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-ai/src/providers/anthropic-shared.cache-breakpoint.test.ts +289 -0
- package/packages/pi-ai/src/providers/anthropic-shared.ts +52 -20
- package/packages/pi-ai/src/types.ts +13 -0
- package/packages/pi-ai/src/utils/repair-tool-json.ts +24 -3
- package/packages/pi-ai/src/utils/tests/repair-tool-json.test.ts +32 -0
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +6 -0
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/messages.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/messages.js +4 -0
- package/packages/pi-coding-agent/dist/core/messages.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +19 -2
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts +10 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +18 -0
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +13 -0
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +20 -16
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/token-telemetry.d.ts +37 -0
- package/packages/pi-coding-agent/dist/core/token-telemetry.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/token-telemetry.js +49 -0
- package/packages/pi-coding-agent/dist/core/token-telemetry.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js +133 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +14 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/tests/system-prompt-cache-stability.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/tests/system-prompt-cache-stability.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/tests/system-prompt-cache-stability.test.js +78 -0
- package/packages/pi-coding-agent/dist/tests/system-prompt-cache-stability.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/tests/token-telemetry.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/tests/token-telemetry.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/tests/token-telemetry.test.js +181 -0
- package/packages/pi-coding-agent/dist/tests/token-telemetry.test.js.map +1 -0
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +7 -0
- package/packages/pi-coding-agent/src/core/messages.ts +4 -0
- package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +32 -2
- package/packages/pi-coding-agent/src/core/model-registry.ts +21 -0
- package/packages/pi-coding-agent/src/core/system-prompt.ts +33 -15
- package/packages/pi-coding-agent/src/core/token-telemetry.ts +77 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.ts +212 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +17 -1
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +1 -1
- package/packages/pi-coding-agent/src/tests/system-prompt-cache-stability.test.ts +102 -0
- package/packages/pi-coding-agent/src/tests/token-telemetry.test.ts +200 -0
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-tui/dist/__tests__/autocomplete.test.js +17 -3
- package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
- package/packages/pi-tui/dist/components/__tests__/leak-fixes-runtime.test.d.ts +2 -0
- package/packages/pi-tui/dist/components/__tests__/leak-fixes-runtime.test.d.ts.map +1 -0
- package/packages/pi-tui/dist/components/__tests__/leak-fixes-runtime.test.js +161 -0
- package/packages/pi-tui/dist/components/__tests__/leak-fixes-runtime.test.js.map +1 -0
- package/packages/pi-tui/package.json +1 -1
- package/packages/pi-tui/src/__tests__/autocomplete.test.ts +20 -3
- package/packages/pi-tui/src/components/__tests__/leak-fixes-runtime.test.ts +219 -0
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/packages/rpc-client/package.json +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/claude-code-cli/readiness.ts +130 -30
- package/src/resources/extensions/google-search/index.ts +2 -9
- package/src/resources/extensions/gsd/auto/loop.ts +24 -2
- package/src/resources/extensions/gsd/auto/phases.ts +6 -14
- package/src/resources/extensions/gsd/auto/run-unit.ts +26 -12
- package/src/resources/extensions/gsd/auto/session.ts +5 -6
- package/src/resources/extensions/gsd/auto/types.ts +1 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +3 -2
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +60 -24
- package/src/resources/extensions/gsd/auto-dispatch.ts +18 -6
- package/src/resources/extensions/gsd/auto-prompts.ts +66 -2
- package/src/resources/extensions/gsd/auto-recovery.ts +46 -8
- package/src/resources/extensions/gsd/auto-runtime-state.ts +51 -0
- package/src/resources/extensions/gsd/auto-start.ts +1 -1
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +2 -4
- package/src/resources/extensions/gsd/auto-worktree.ts +82 -12
- package/src/resources/extensions/gsd/auto.ts +37 -10
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +15 -13
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +8 -7
- package/src/resources/extensions/gsd/bootstrap/query-tools.ts +2 -2
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +10 -9
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +121 -31
- package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +12 -6
- package/src/resources/extensions/gsd/bootstrap/subagent-input.ts +20 -0
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +50 -8
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +141 -11
- package/src/resources/extensions/gsd/commands/catalog.ts +82 -5
- package/src/resources/extensions/gsd/commands/handlers/core.ts +23 -1
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +10 -0
- package/src/resources/extensions/gsd/commands-config.ts +3 -2
- package/src/resources/extensions/gsd/commands-extensions.ts +43 -3
- package/src/resources/extensions/gsd/commands-handlers.ts +3 -2
- package/src/resources/extensions/gsd/commands-mcp-status.ts +3 -1
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +15 -1
- package/src/resources/extensions/gsd/commands-worktree.ts +383 -0
- package/src/resources/extensions/gsd/dashboard-overlay.ts +1 -1
- package/src/resources/extensions/gsd/docs/preferences-reference.md +10 -0
- package/src/resources/extensions/gsd/doctor-providers.ts +2 -1
- package/src/resources/extensions/gsd/doctor-runtime-checks.ts +39 -1
- package/src/resources/extensions/gsd/doctor-types.ts +3 -1
- package/src/resources/extensions/gsd/error-classifier.ts +1 -1
- package/src/resources/extensions/gsd/forensics.ts +12 -7
- package/src/resources/extensions/gsd/git-service.ts +13 -5
- package/src/resources/extensions/gsd/gsd-db.ts +12 -2
- package/src/resources/extensions/gsd/guided-flow.ts +27 -26
- package/src/resources/extensions/gsd/home-dir.ts +19 -0
- package/src/resources/extensions/gsd/journal.ts +4 -1
- package/src/resources/extensions/gsd/key-manager.ts +2 -1
- package/src/resources/extensions/gsd/memory-store.ts +81 -28
- package/src/resources/extensions/gsd/migrate/command.ts +3 -2
- package/src/resources/extensions/gsd/milestone-id-reservation.ts +47 -0
- package/src/resources/extensions/gsd/model-router.ts +172 -9
- package/src/resources/extensions/gsd/native-git-bridge.ts +7 -1
- package/src/resources/extensions/gsd/preferences-models.ts +101 -15
- package/src/resources/extensions/gsd/preferences-types.ts +6 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +35 -0
- package/src/resources/extensions/gsd/preferences.ts +16 -2
- package/src/resources/extensions/gsd/prompt-loader.ts +26 -12
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +10 -0
- package/src/resources/extensions/gsd/prompts/complete-slice.md +10 -0
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
- package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +2 -0
- package/src/resources/extensions/gsd/prompts/plan-slice.md +10 -0
- package/src/resources/extensions/gsd/prompts/refine-slice.md +10 -0
- package/src/resources/extensions/gsd/prompts/rewrite-docs.md +2 -0
- package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +9 -3
- package/src/resources/extensions/gsd/state.ts +42 -0
- package/src/resources/extensions/gsd/templates/PREFERENCES.md +1 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +179 -1
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +24 -5
- package/src/resources/extensions/gsd/tests/auto-supervisor.test.mjs +21 -4
- package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/budget-prediction.test.ts +138 -211
- package/src/resources/extensions/gsd/tests/bundled-skill-triggers.test.ts +50 -27
- package/src/resources/extensions/gsd/tests/commands-extensions-version-compare.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/commands-worktree-clean.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +142 -59
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +7 -4
- package/src/resources/extensions/gsd/tests/completed-at-reconcile.test.ts +89 -32
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +41 -23
- package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +3 -43
- package/src/resources/extensions/gsd/tests/debug-logger.test.ts +5 -3
- package/src/resources/extensions/gsd/tests/deferred-milestone-dir-4996.test.ts +116 -0
- package/src/resources/extensions/gsd/tests/discuss-empty-db-fallback.test.ts +22 -87
- package/src/resources/extensions/gsd/tests/discuss-queued-milestones.test.ts +7 -118
- package/src/resources/extensions/gsd/tests/discuss-tool-scope-leak.test.ts +18 -60
- package/src/resources/extensions/gsd/tests/doctor-orphan-milestone-4996.test.ts +100 -0
- package/src/resources/extensions/gsd/tests/double-merge-guard.test.ts +14 -76
- package/src/resources/extensions/gsd/tests/ensure-preconditions-guard-4996.test.ts +93 -0
- package/src/resources/extensions/gsd/tests/false-degraded-mode-warning.test.ts +22 -83
- package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +1 -63
- package/src/resources/extensions/gsd/tests/find-missing-summaries-closed-runtime.test.ts +47 -0
- package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +26 -1
- package/src/resources/extensions/gsd/tests/gitignore-bg-shell-runtime.test.ts +63 -0
- package/src/resources/extensions/gsd/tests/google-search-stub.test.ts +25 -65
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/gsd-no-project-error-runtime.test.ts +81 -0
- package/src/resources/extensions/gsd/tests/headless-answers.test.ts +14 -4
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +22 -12
- package/src/resources/extensions/gsd/tests/help-menu-coverage.test.ts +57 -0
- package/src/resources/extensions/gsd/tests/home-dir.test.ts +52 -0
- package/src/resources/extensions/gsd/tests/import-done-milestones-runtime.test.ts +145 -0
- package/src/resources/extensions/gsd/tests/init-prefs-routing.test.ts +64 -1
- package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +72 -1
- package/src/resources/extensions/gsd/tests/integration/token-savings.test.ts +0 -23
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +128 -0
- package/src/resources/extensions/gsd/tests/memory-tools.test.ts +33 -1
- package/src/resources/extensions/gsd/tests/merge-self-branch-guard.test.ts +124 -0
- package/src/resources/extensions/gsd/tests/milestone-id-gap-reuse-4996.test.ts +152 -0
- package/src/resources/extensions/gsd/tests/milestone-report-path.test.ts +18 -1
- package/src/resources/extensions/gsd/tests/model-router.test.ts +169 -8
- package/src/resources/extensions/gsd/tests/native-git-infra-errors.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +32 -43
- package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +4 -10
- package/src/resources/extensions/gsd/tests/preferences.test.ts +127 -0
- package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +16 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/quick-turn-end-cleanup.test.ts +6 -6
- package/src/resources/extensions/gsd/tests/register-hooks-compaction-checkpoint.test.ts +93 -0
- package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +168 -19
- package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +23 -1
- package/src/resources/extensions/gsd/tests/steer-worktree-path.test.ts +17 -1
- package/src/resources/extensions/gsd/tests/system-context-message-routing.test.ts +101 -0
- package/src/resources/extensions/gsd/tests/token-profile.test.ts +51 -4
- package/src/resources/extensions/gsd/tests/turn-epoch.test.ts +7 -16
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +38 -3
- package/src/resources/extensions/gsd/tests/unstructured-continue-context-injection.test.ts +5 -7
- package/src/resources/extensions/gsd/tests/uok-gitops-turn-action.test.ts +15 -1
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +6 -6
- package/src/resources/extensions/gsd/tests/worktree-path-injection.test.ts +235 -0
- package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +34 -33
- package/src/resources/extensions/gsd/tests/worktree.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +131 -1
- package/src/resources/extensions/gsd/tools/memory-tools.ts +17 -1
- package/src/resources/extensions/gsd/unit-context-manifest.ts +44 -12
- package/src/resources/extensions/gsd/visualizer-overlay.ts +1 -1
- package/src/resources/extensions/gsd/watch/header-renderer.ts +3 -1
- package/src/resources/extensions/gsd/workflow-logger.ts +1 -0
- package/src/resources/extensions/gsd/worktree-command.ts +31 -44
- package/src/resources/extensions/gsd/worktree-manager.ts +40 -1
- package/src/resources/extensions/gsd/worktree-resolver.ts +4 -14
- package/src/resources/extensions/gsd/worktree-root.ts +144 -0
- package/src/resources/extensions/gsd/worktree-session-state.ts +35 -0
- package/src/resources/extensions/gsd/worktree.ts +8 -119
- package/src/resources/extensions/mcp-client/index.ts +6 -10
- package/src/resources/extensions/mcp-client/tests/global-config.test.ts +91 -0
- package/src/resources/extensions/ollama/index.ts +16 -2
- package/src/resources/extensions/ollama/model-capabilities.ts +34 -0
- package/src/resources/extensions/ollama/ollama-client.ts +41 -4
- package/src/resources/extensions/ollama/tests/model-capabilities.test.ts +96 -0
- package/src/resources/extensions/ollama/tests/ollama-client-timeout-env.test.ts +147 -0
- package/src/resources/extensions/slash-commands/create-extension.ts +38 -24
- package/src/resources/extensions/subagent/index.ts +165 -7
- package/src/resources/skills/create-gsd-extension/SKILL.md +9 -5
- package/src/resources/skills/create-gsd-extension/references/custom-commands.md +1 -1
- package/src/resources/skills/create-gsd-extension/references/custom-rendering.md +5 -5
- package/src/resources/skills/create-gsd-extension/references/custom-tools.md +4 -4
- package/src/resources/skills/create-gsd-extension/references/custom-ui.md +6 -6
- package/src/resources/skills/create-gsd-extension/references/events-reference.md +3 -3
- package/src/resources/skills/create-gsd-extension/references/packaging-distribution.md +1 -1
- package/src/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +3 -3
- package/src/resources/skills/create-gsd-extension/templates/extension-skeleton.ts +2 -2
- package/src/resources/skills/create-gsd-extension/templates/stateful-tool-skeleton.ts +3 -3
- package/src/resources/skills/create-gsd-extension/templates/templates.test.ts +58 -0
- package/src/resources/skills/create-gsd-extension/workflows/create-extension.md +32 -12
- package/src/resources/skills/lint/SKILL.md +4 -0
- package/src/resources/skills/review/SKILL.md +4 -0
- package/src/resources/skills/test/SKILL.md +3 -0
- package/dist/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +0 -601
- package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +0 -651
- package/dist/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +0 -91
- package/dist/resources/extensions/gsd/tests/auto-supervisor.test.mjs +0 -53
- package/dist/resources/extensions/gsd/tests/dist-redirect.mjs +0 -112
- package/dist/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +0 -23
- package/dist/resources/extensions/gsd/tests/resolve-ts.mjs +0 -5
- package/dist/resources/skills/github-workflows/references/gh/tests/__init__.py +0 -0
- package/dist/resources/skills/github-workflows/references/gh/tests/test_github_project_setup.py +0 -608
- package/dist/web/standalone/.next/static/chunks/2826.e9f5195e91f9cad2.js +0 -11
- package/dist/web/standalone/.next/static/chunks/3621.fc7480022c972438.js +0 -20
- package/dist/web/standalone/.next/static/chunks/app/page-151349214571e2b6.js +0 -1
- package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
- package/dist/web/standalone/.next/static/chunks/webpack-2e68521d7c82f7c2.js +0 -1
- package/src/resources/extensions/gsd/tests/copy-planning-artifacts-samepath.test.ts +0 -22
- package/src/resources/extensions/gsd/tests/discuss-slice-structured-questions.test.ts +0 -47
- package/src/resources/extensions/gsd/tests/empty-content-abort-loop.test.ts +0 -75
- /package/dist/web/standalone/.next/static/{C1zT2kEfoLhDdbWPWKrXd → 4iu6IYeYfxOq8OidlDqp6}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{C1zT2kEfoLhDdbWPWKrXd → 4iu6IYeYfxOq8OidlDqp6}/_ssgManifest.js +0 -0
|
@@ -23,9 +23,12 @@ describe("CombinedAutocompleteProvider — slash commands", () => {
|
|
|
23
23
|
const provider = makeProvider(sampleCommands);
|
|
24
24
|
const result = provider.getSuggestions(["/se"], 0, 3);
|
|
25
25
|
assert.ok(result);
|
|
26
|
-
|
|
26
|
+
// /se matches settings and session — name the values, then assert exact
|
|
27
|
+
// count as the contract for this prefix. Asserting count alone would
|
|
28
|
+
// pass even if the matches were the wrong commands.
|
|
27
29
|
assert.ok(result.items.some((i) => i.value === "settings"));
|
|
28
30
|
assert.ok(result.items.some((i) => i.value === "session"));
|
|
31
|
+
assert.equal(result.items.length, 2);
|
|
29
32
|
});
|
|
30
33
|
it("returns null when no commands match", () => {
|
|
31
34
|
const provider = makeProvider(sampleCommands);
|
|
@@ -36,7 +39,11 @@ describe("CombinedAutocompleteProvider — slash commands", () => {
|
|
|
36
39
|
const provider = makeProvider(sampleCommands);
|
|
37
40
|
const result = provider.getSuggestions(["/mod"], 0, 4);
|
|
38
41
|
assert.ok(result);
|
|
39
|
-
|
|
42
|
+
// Verify the matched command is present and every item carries a
|
|
43
|
+
// description — avoids `[0]` positional coupling that would silently
|
|
44
|
+
// pass if list ordering changed.
|
|
45
|
+
assert.ok(result.items.some((i) => i.value === "model"));
|
|
46
|
+
assert.ok(result.items.every((i) => typeof i.description === "string" && i.description.length > 0), "every suggestion must have a non-empty description");
|
|
40
47
|
});
|
|
41
48
|
it("does not offer slash command suggestions mid-line", () => {
|
|
42
49
|
const sentinelCommands = [
|
|
@@ -77,8 +84,10 @@ describe("CombinedAutocompleteProvider — argument completions", () => {
|
|
|
77
84
|
const provider = makeProvider(commands);
|
|
78
85
|
const result = provider.getSuggestions(["/thinking m"], 0, 11);
|
|
79
86
|
assert.ok(result);
|
|
87
|
+
// /thinking m matches only "medium" — verify the expected value is
|
|
88
|
+
// present (by name, not index) and then assert exact count.
|
|
89
|
+
assert.ok(result.items.some((i) => i.value === "medium"));
|
|
80
90
|
assert.equal(result.items.length, 1);
|
|
81
|
-
assert.equal(result.items[0]?.value, "medium");
|
|
82
91
|
});
|
|
83
92
|
it("returns null for commands without argument completions", () => {
|
|
84
93
|
const provider = makeProvider(sampleCommands);
|
|
@@ -102,6 +111,11 @@ describe("CombinedAutocompleteProvider — argument completions", () => {
|
|
|
102
111
|
const provider = makeProvider(commands);
|
|
103
112
|
const result = provider.getSuggestions(["/test "], 0, 6);
|
|
104
113
|
assert.ok(result);
|
|
114
|
+
// Empty prefix returns all 3 subcommands — verify each is present
|
|
115
|
+
// by name. Bare count would pass for any 3-element list.
|
|
116
|
+
assert.ok(result.items.some((i) => i.value === "start"));
|
|
117
|
+
assert.ok(result.items.some((i) => i.value === "stop"));
|
|
118
|
+
assert.ok(result.items.some((i) => i.value === "status"));
|
|
105
119
|
assert.equal(result.items.length, 3);
|
|
106
120
|
});
|
|
107
121
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"autocomplete.test.js","sourceRoot":"","sources":["../../src/__tests__/autocomplete.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,4BAA4B,EAAE,MAAM,oBAAoB,CAAC;AAGlE,SAAS,YAAY,CAAC,WAA2B,EAAE,EAAE,WAAmB,MAAM;IAC7E,OAAO,IAAI,4BAA4B,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAC7D,CAAC;AAED,MAAM,cAAc,GAAmB;IACtC,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,oBAAoB,EAAE;IACvD,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE;IAC9C,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,mBAAmB,EAAE;IACrD,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,gBAAgB,EAAE;IACjD,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,oBAAoB,EAAE;CACvD,CAAC;AAEF,QAAQ,CAAC,+CAA+C,EAAE,GAAG,EAAE;IAC9D,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QAC1C,MAAM,QAAQ,GAAG,YAAY,CAAC,cAAc,CAAC,CAAC;QAC9C,MAAM,MAAM,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QACpD,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,2BAA2B,CAAC,CAAC;QAC/C,MAAM,CAAC,KAAK,CAAC,MAAO,CAAC,KAAK,CAAC,MAAM,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;QAC1D,MAAM,CAAC,KAAK,CAAC,MAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC3C,MAAM,QAAQ,GAAG,YAAY,CAAC,cAAc,CAAC,CAAC;QAC9C,MAAM,MAAM,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QACtD,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;QAClB,MAAM,CAAC,KAAK,CAAC,MAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,oBAAoB;QAC3D,MAAM,CAAC,EAAE,CAAC,MAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC;QAC7D,MAAM,CAAC,EAAE,CAAC,MAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC9C,MAAM,QAAQ,GAAG,YAAY,CAAC,cAAc,CAAC,CAAC;QAC9C,MAAM,MAAM,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QACvD,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC9C,MAAM,QAAQ,GAAG,YAAY,CAAC,cAAc,CAAC,CAAC;QAC9C,MAAM,MAAM,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QACvD,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;QAClB,MAAM,CAAC,KAAK,CAAC,MAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,cAAc,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC5D,MAAM,gBAAgB,GAAmB;YACxC,EAAE,IAAI,EAAE,qBAAqB,EAAE,WAAW,EAAE,wBAAwB,EAAE;SACtE,CAAC;QACF,MAAM,QAAQ,GAAG,YAAY,CAAC,gBAAgB,CAAC,CAAC;QAChD,MAAM,IAAI,GAAG,iBAAiB,CAAC;QAC/B,MAAM,MAAM,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QAE/D,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACrB,OAAO;QACR,CAAC;QAED,MAAM,CAAC,EAAE,CACR,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,KAAK,qBAAqB,CAAC,EAClE,sEAAsE,CACtE,CAAC;QACF,MAAM,CAAC,EAAE,CACR,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,KAAK,wBAAwB,CAAC,EAC3E,mEAAmE,CACnE,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC3D,MAAM,QAAQ,GAAG,YAAY,CAAC,cAAc,CAAC,CAAC;QAC9C,MAAM,MAAM,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QACxD,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;QAClB,MAAM,CAAC,KAAK,CAAC,MAAO,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QACpC,MAAM,CAAC,EAAE,CAAC,MAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,qDAAqD,EAAE,GAAG,EAAE;IACpE,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;QACtE,MAAM,QAAQ,GAAmB;YAChC;gBACC,IAAI,EAAE,UAAU;gBAChB,WAAW,EAAE,oBAAoB;gBACjC,sBAAsB,EAAE,CAAC,MAAM,EAAE,EAAE;oBAClC,MAAM,MAAM,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;oBAChD,MAAM,QAAQ,GAAG,MAAM;yBACrB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;yBAC1C,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;oBACvC,OAAO,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC;gBAC9C,CAAC;aACD;SACD,CAAC;QACF,MAAM,QAAQ,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;QACxC,MAAM,MAAM,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAC,aAAa,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QAC/D,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;QAClB,MAAM,CAAC,KAAK,CAAC,MAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QACtC,MAAM,CAAC,KAAK,CAAC,MAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;QACjE,MAAM,QAAQ,GAAG,YAAY,CAAC,cAAc,CAAC,CAAC;QAC9C,MAAM,MAAM,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAC,eAAe,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QACjE,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QACnE,MAAM,QAAQ,GAAmB;YAChC;gBACC,IAAI,EAAE,MAAM;gBACZ,WAAW,EAAE,cAAc;gBAC3B,sBAAsB,EAAE,CAAC,MAAM,EAAE,EAAE;oBAClC,MAAM,IAAI,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;oBACzC,MAAM,QAAQ,GAAG,IAAI;yBACnB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;yBAC1C,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;oBACvC,OAAO,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC;gBAC9C,CAAC;aACD;SACD,CAAC;QACF,MAAM,QAAQ,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;QACxC,MAAM,MAAM,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QACzD,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;QAClB,MAAM,CAAC,KAAK,CAAC,MAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,yDAAyD,EAAE,GAAG,EAAE;IACxE,EAAE,CAAC,iEAAiE,EAAE,GAAG,EAAE;QAC1E,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;QAChC,MAAM,MAAM,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAC,kBAAkB,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QACpE,gFAAgF;QAChF,yEAAyE;QACzE,qEAAqE;QACrE,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACrB,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,+BAA+B,CAAC,CAAC;YACxE,kEAAkE;YAClE,8CAA8C;YAC9C,MAAM,CAAC,KAAK,CAAC,OAAO,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,yBAAyB,CAAC,CAAC;YACxE,MAAM,CAAC,EAAE,CACR,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,EAC9B,iDAAiD,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAChF,CAAC;QACH,CAAC;IACF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4DAA4D,EAAE,GAAG,EAAE;QACrE,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;QAChC,MAAM,MAAM,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAC,wBAAwB,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QAC1E,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACrB,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,+BAA+B,CAAC,CAAC;YACxE,MAAM,CAAC,KAAK,CAAC,OAAO,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,yBAAyB,CAAC,CAAC;YACxE,uEAAuE;YACvE,MAAM,CAAC,EAAE,CACR,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,EAChC,mDAAmD,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAClF,CAAC;QACH,CAAC;IACF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uEAAuE,EAAE,GAAG,EAAE;QAChF,MAAM,QAAQ,GAAG,YAAY,CAAC,EAAE,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QACjD,kEAAkE;QAClE,oEAAoE;QACpE,wEAAwE;QACxE,MAAM,MAAM,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QACpD,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,EAAE,6CAA6C,CAAC,CAAC;IAC3E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAC/D,MAAM,QAAQ,GAAG,YAAY,CAAC,EAAE,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QACjD,MAAM,MAAM,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAC5D,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,EAAE,kEAAkE,CAAC,CAAC;IAChG,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,gDAAgD,EAAE,GAAG,EAAE;IAC/D,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAC/D,MAAM,QAAQ,GAAG,YAAY,CAAC,cAAc,CAAC,CAAC;QAC9C,MAAM,MAAM,GAAG,QAAQ,CAAC,eAAe,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,KAAK,CAAC,CAAC;QACxG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC;QAC5C,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC,qBAAqB;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qEAAqE,EAAE,GAAG,EAAE;QAC9E,MAAM,QAAQ,GAAG,YAAY,CAAC,cAAc,CAAC,CAAC;QAC9C,MAAM,MAAM,GAAG,QAAQ,CAAC,eAAe,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,KAAK,CAAC,CAAC;QAC1G,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC;QAC9C,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACpD,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;QAChC,MAAM,MAAM,GAAG,QAAQ,CAAC,eAAe,CACtC,CAAC,OAAO,CAAC,EACT,CAAC,EACD,CAAC,EACD,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,UAAU,EAAE,EAC7C,OAAO,CACP,CAAC;QACF,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC9D,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;QAChC,MAAM,MAAM,GAAG,QAAQ,CAAC,eAAe,CACtC,CAAC,KAAK,CAAC,EACP,CAAC,EACD,CAAC,EACD,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,EACjC,KAAK,CACL,CAAC;QACF,wEAAwE;QACxE,MAAM,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACtC,MAAM,QAAQ,GAAG,YAAY,CAAC,cAAc,CAAC,CAAC;QAC9C,MAAM,MAAM,GAAG,QAAQ,CAAC,eAAe,CACtC,CAAC,mBAAmB,CAAC,EACrB,CAAC,EACD,CAAC,EACD,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,EACxC,KAAK,CACL,CAAC;QACF,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,uDAAuD,EAAE,GAAG,EAAE;IACtE,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC9C,MAAM,QAAQ,GAAG,YAAY,CAAC,cAAc,CAAC,CAAC;QAC9C,MAAM,MAAM,GAAG,QAAQ,CAAC,uBAAuB,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAChE,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8DAA8D,EAAE,GAAG,EAAE;QACvE,MAAM,QAAQ,GAAG,YAAY,CAAC,cAAc,CAAC,CAAC;QAC9C,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,2BAA2B,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IAC3E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACpE,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;QAChC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,2BAA2B,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IAC/E,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC","sourcesContent":["import { describe, it } from \"node:test\";\nimport assert from \"node:assert/strict\";\nimport { CombinedAutocompleteProvider } from \"../autocomplete.js\";\nimport type { SlashCommand } from \"../autocomplete.js\";\n\nfunction makeProvider(commands: SlashCommand[] = [], basePath: string = \"/tmp\") {\n\treturn new CombinedAutocompleteProvider(commands, basePath);\n}\n\nconst sampleCommands: SlashCommand[] = [\n\t{ name: \"settings\", description: \"Open settings menu\" },\n\t{ name: \"model\", description: \"Select model\" },\n\t{ name: \"session\", description: \"Show session info\" },\n\t{ name: \"export\", description: \"Export session\" },\n\t{ name: \"thinking\", description: \"Set thinking level\" },\n];\n\ndescribe(\"CombinedAutocompleteProvider — slash commands\", () => {\n\tit(\"returns all commands for bare /\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getSuggestions([\"/\"], 0, 1);\n\t\tassert.ok(result, \"should return suggestions\");\n\t\tassert.equal(result!.items.length, sampleCommands.length);\n\t\tassert.equal(result!.prefix, \"/\");\n\t});\n\n\tit(\"filters commands by typed prefix\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getSuggestions([\"/se\"], 0, 3);\n\t\tassert.ok(result);\n\t\tassert.equal(result!.items.length, 2); // settings, session\n\t\tassert.ok(result!.items.some((i) => i.value === \"settings\"));\n\t\tassert.ok(result!.items.some((i) => i.value === \"session\"));\n\t});\n\n\tit(\"returns null when no commands match\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getSuggestions([\"/zzz\"], 0, 4);\n\t\tassert.equal(result, null);\n\t});\n\n\tit(\"includes description in suggestions\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getSuggestions([\"/mod\"], 0, 4);\n\t\tassert.ok(result);\n\t\tassert.equal(result!.items[0]?.description, \"Select model\");\n\t});\n\n\tit(\"does not offer slash command suggestions mid-line\", () => {\n\t\tconst sentinelCommands: SlashCommand[] = [\n\t\t\t{ name: \"codexmidlinecommand\", description: \"Sentinel slash command\" },\n\t\t];\n\t\tconst provider = makeProvider(sentinelCommands);\n\t\tconst line = \"hello /codexmid\";\n\t\tconst result = provider.getSuggestions([line], 0, line.length);\n\n\t\tif (result === null) {\n\t\t\treturn;\n\t\t}\n\n\t\tassert.ok(\n\t\t\tresult.items.every((item) => item.value !== \"codexmidlinecommand\"),\n\t\t\t\"mid-line slash-like text should not return slash command completions\",\n\t\t);\n\t\tassert.ok(\n\t\t\tresult.items.every((item) => item.description !== \"Sentinel slash command\"),\n\t\t\t\"mid-line slash-like text should not return slash command metadata\",\n\t\t);\n\t});\n\n\tit(\"triggers slash commands after leading whitespace\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getSuggestions([\" /se\"], 0, 5);\n\t\tassert.ok(result);\n\t\tassert.equal(result!.prefix, \"/se\");\n\t\tassert.ok(result!.items.some((item) => item.value === \"settings\"));\n\t});\n});\n\ndescribe(\"CombinedAutocompleteProvider — argument completions\", () => {\n\tit(\"returns argument completions for commands that support them\", () => {\n\t\tconst commands: SlashCommand[] = [\n\t\t\t{\n\t\t\t\tname: \"thinking\",\n\t\t\t\tdescription: \"Set thinking level\",\n\t\t\t\tgetArgumentCompletions: (prefix) => {\n\t\t\t\t\tconst levels = [\"off\", \"low\", \"medium\", \"high\"];\n\t\t\t\t\tconst filtered = levels\n\t\t\t\t\t\t.filter((l) => l.startsWith(prefix.trim()))\n\t\t\t\t\t\t.map((l) => ({ value: l, label: l }));\n\t\t\t\t\treturn filtered.length > 0 ? filtered : null;\n\t\t\t\t},\n\t\t\t},\n\t\t];\n\t\tconst provider = makeProvider(commands);\n\t\tconst result = provider.getSuggestions([\"/thinking m\"], 0, 11);\n\t\tassert.ok(result);\n\t\tassert.equal(result!.items.length, 1);\n\t\tassert.equal(result!.items[0]?.value, \"medium\");\n\t});\n\n\tit(\"returns null for commands without argument completions\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getSuggestions([\"/settings foo\"], 0, 13);\n\t\tassert.equal(result, null);\n\t});\n\n\tit(\"returns all arg completions for empty prefix after space\", () => {\n\t\tconst commands: SlashCommand[] = [\n\t\t\t{\n\t\t\t\tname: \"test\",\n\t\t\t\tdescription: \"Test command\",\n\t\t\t\tgetArgumentCompletions: (prefix) => {\n\t\t\t\t\tconst subs = [\"start\", \"stop\", \"status\"];\n\t\t\t\t\tconst filtered = subs\n\t\t\t\t\t\t.filter((s) => s.startsWith(prefix.trim()))\n\t\t\t\t\t\t.map((s) => ({ value: s, label: s }));\n\t\t\t\t\treturn filtered.length > 0 ? filtered : null;\n\t\t\t\t},\n\t\t\t},\n\t\t];\n\t\tconst provider = makeProvider(commands);\n\t\tconst result = provider.getSuggestions([\"/test \"], 0, 6);\n\t\tassert.ok(result);\n\t\tassert.equal(result!.items.length, 3);\n\t});\n});\n\ndescribe(\"CombinedAutocompleteProvider — @ file prefix extraction\", () => {\n\tit(\"detects @ at start of line and returns a valid suggestion shape\", () => {\n\t\tconst provider = makeProvider();\n\t\tconst result = provider.getSuggestions([\"@nonexistent_xyz\"], 0, 16);\n\t\t// Either null (nothing matched) or a well-formed {items: Array, prefix: string}\n\t\t// shape. Previous version's `result.items.length >= 0` was a tautology —\n\t\t// array length is always ≥ 0; the whole expression could never fail.\n\t\tif (result !== null) {\n\t\t\tassert.ok(Array.isArray(result.items), \"result.items must be an array\");\n\t\t\t// The @-prefix extraction strips the leading @ — prefix should be\n\t\t\t// the raw text without the trigger character.\n\t\t\tassert.equal(typeof result.prefix, \"string\", \"prefix must be a string\");\n\t\t\tassert.ok(\n\t\t\t\t!result.prefix.startsWith(\"@\"),\n\t\t\t\t`prefix must have the @ trigger stripped, got: ${JSON.stringify(result.prefix)}`,\n\t\t\t);\n\t\t}\n\t});\n\n\tit(\"detects @ after space and returns a valid suggestion shape\", () => {\n\t\tconst provider = makeProvider();\n\t\tconst result = provider.getSuggestions([\"check @nonexistent_xyz\"], 0, 22);\n\t\tif (result !== null) {\n\t\t\tassert.ok(Array.isArray(result.items), \"result.items must be an array\");\n\t\t\tassert.equal(typeof result.prefix, \"string\", \"prefix must be a string\");\n\t\t\t// The prefix must NOT include the word \"check\" that came before the @.\n\t\t\tassert.ok(\n\t\t\t\t!result.prefix.includes(\"check\"),\n\t\t\t\t`prefix must not include text before the @, got: ${JSON.stringify(result.prefix)}`,\n\t\t\t);\n\t\t}\n\t});\n\n\tit(\"returns null for bare @ with no query to avoid full tree walk (#1824)\", () => {\n\t\tconst provider = makeProvider([], process.cwd());\n\t\t// A bare \"@\" produces an empty rawPrefix after stripping the \"@\".\n\t\t// This must return null to avoid a synchronous full filesystem walk\n\t\t// via the native fuzzyFind addon, which freezes the TUI on large repos.\n\t\tconst result = provider.getSuggestions([\"@\"], 0, 1);\n\t\tassert.equal(result, null, \"bare @ should not trigger fuzzy file search\");\n\t});\n\n\tit(\"returns null for @ after space with no query (#1824)\", () => {\n\t\tconst provider = makeProvider([], process.cwd());\n\t\tconst result = provider.getSuggestions([\"look at @\"], 0, 9);\n\t\tassert.equal(result, null, \"@ after space with no query should not trigger fuzzy file search\");\n\t});\n});\n\ndescribe(\"CombinedAutocompleteProvider — applyCompletion\", () => {\n\tit(\"applies slash command completion with trailing space\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.applyCompletion([\"/se\"], 0, 3, { value: \"settings\", label: \"settings\" }, \"/se\");\n\t\tassert.equal(result.lines[0], \"/settings \");\n\t\tassert.equal(result.cursorCol, 10); // after \"/settings \"\n\t});\n\n\tit(\"preserves leading whitespace when applying slash command completion\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.applyCompletion([\" /se\"], 0, 5, { value: \"settings\", label: \"settings\" }, \"/se\");\n\t\tassert.equal(result.lines[0], \" /settings \");\n\t\tassert.equal(result.cursorCol, 12);\n\t});\n\n\tit(\"applies file path completion for @ prefix\", () => {\n\t\tconst provider = makeProvider();\n\t\tconst result = provider.applyCompletion(\n\t\t\t[\"@src/\"],\n\t\t\t0,\n\t\t\t5,\n\t\t\t{ value: \"@src/index.ts\", label: \"index.ts\" },\n\t\t\t\"@src/\",\n\t\t);\n\t\tassert.equal(result.lines[0], \"@src/index.ts \");\n\t});\n\n\tit(\"applies directory completion without trailing space\", () => {\n\t\tconst provider = makeProvider();\n\t\tconst result = provider.applyCompletion(\n\t\t\t[\"@sr\"],\n\t\t\t0,\n\t\t\t3,\n\t\t\t{ value: \"@src/\", label: \"src/\" },\n\t\t\t\"@sr\",\n\t\t);\n\t\t// Directories should not get trailing space so user can continue typing\n\t\tassert.ok(!result.lines[0]!.endsWith(\" \"));\n\t});\n\n\tit(\"preserves text after cursor\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.applyCompletion(\n\t\t\t[\"/se and more text\"],\n\t\t\t0,\n\t\t\t3,\n\t\t\t{ value: \"settings\", label: \"settings\" },\n\t\t\t\"/se\",\n\t\t);\n\t\tassert.ok(result.lines[0]!.includes(\"and more text\"));\n\t});\n});\n\ndescribe(\"CombinedAutocompleteProvider — force file suggestions\", () => {\n\tit(\"does not trigger for slash commands\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getForceFileSuggestions([\"/set\"], 0, 4);\n\t\tassert.equal(result, null);\n\t});\n\n\tit(\"shouldTriggerFileCompletion returns false for slash commands\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tassert.equal(provider.shouldTriggerFileCompletion([\"/set\"], 0, 4), false);\n\t});\n\n\tit(\"shouldTriggerFileCompletion returns true for regular text\", () => {\n\t\tconst provider = makeProvider();\n\t\tassert.equal(provider.shouldTriggerFileCompletion([\"some text\"], 0, 9), true);\n\t});\n});\n"]}
|
|
1
|
+
{"version":3,"file":"autocomplete.test.js","sourceRoot":"","sources":["../../src/__tests__/autocomplete.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it } from \"node:test\";\nimport assert from \"node:assert/strict\";\nimport { CombinedAutocompleteProvider } from \"../autocomplete.js\";\nimport type { SlashCommand } from \"../autocomplete.js\";\n\nfunction makeProvider(commands: SlashCommand[] = [], basePath: string = \"/tmp\") {\n\treturn new CombinedAutocompleteProvider(commands, basePath);\n}\n\nconst sampleCommands: SlashCommand[] = [\n\t{ name: \"settings\", description: \"Open settings menu\" },\n\t{ name: \"model\", description: \"Select model\" },\n\t{ name: \"session\", description: \"Show session info\" },\n\t{ name: \"export\", description: \"Export session\" },\n\t{ name: \"thinking\", description: \"Set thinking level\" },\n];\n\ndescribe(\"CombinedAutocompleteProvider — slash commands\", () => {\n\tit(\"returns all commands for bare /\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getSuggestions([\"/\"], 0, 1);\n\t\tassert.ok(result, \"should return suggestions\");\n\t\tassert.equal(result!.items.length, sampleCommands.length);\n\t\tassert.equal(result!.prefix, \"/\");\n\t});\n\n\tit(\"filters commands by typed prefix\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getSuggestions([\"/se\"], 0, 3);\n\t\tassert.ok(result);\n\t\t// /se matches settings and session — name the values, then assert exact\n\t\t// count as the contract for this prefix. Asserting count alone would\n\t\t// pass even if the matches were the wrong commands.\n\t\tassert.ok(result!.items.some((i) => i.value === \"settings\"));\n\t\tassert.ok(result!.items.some((i) => i.value === \"session\"));\n\t\tassert.equal(result!.items.length, 2);\n\t});\n\n\tit(\"returns null when no commands match\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getSuggestions([\"/zzz\"], 0, 4);\n\t\tassert.equal(result, null);\n\t});\n\n\tit(\"includes description in suggestions\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getSuggestions([\"/mod\"], 0, 4);\n\t\tassert.ok(result);\n\t\t// Verify the matched command is present and every item carries a\n\t\t// description — avoids `[0]` positional coupling that would silently\n\t\t// pass if list ordering changed.\n\t\tassert.ok(result!.items.some((i) => i.value === \"model\"));\n\t\tassert.ok(\n\t\t\tresult!.items.every((i) => typeof i.description === \"string\" && i.description.length > 0),\n\t\t\t\"every suggestion must have a non-empty description\",\n\t\t);\n\t});\n\n\tit(\"does not offer slash command suggestions mid-line\", () => {\n\t\tconst sentinelCommands: SlashCommand[] = [\n\t\t\t{ name: \"codexmidlinecommand\", description: \"Sentinel slash command\" },\n\t\t];\n\t\tconst provider = makeProvider(sentinelCommands);\n\t\tconst line = \"hello /codexmid\";\n\t\tconst result = provider.getSuggestions([line], 0, line.length);\n\n\t\tif (result === null) {\n\t\t\treturn;\n\t\t}\n\n\t\tassert.ok(\n\t\t\tresult.items.every((item) => item.value !== \"codexmidlinecommand\"),\n\t\t\t\"mid-line slash-like text should not return slash command completions\",\n\t\t);\n\t\tassert.ok(\n\t\t\tresult.items.every((item) => item.description !== \"Sentinel slash command\"),\n\t\t\t\"mid-line slash-like text should not return slash command metadata\",\n\t\t);\n\t});\n\n\tit(\"triggers slash commands after leading whitespace\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getSuggestions([\" /se\"], 0, 5);\n\t\tassert.ok(result);\n\t\tassert.equal(result!.prefix, \"/se\");\n\t\tassert.ok(result!.items.some((item) => item.value === \"settings\"));\n\t});\n});\n\ndescribe(\"CombinedAutocompleteProvider — argument completions\", () => {\n\tit(\"returns argument completions for commands that support them\", () => {\n\t\tconst commands: SlashCommand[] = [\n\t\t\t{\n\t\t\t\tname: \"thinking\",\n\t\t\t\tdescription: \"Set thinking level\",\n\t\t\t\tgetArgumentCompletions: (prefix) => {\n\t\t\t\t\tconst levels = [\"off\", \"low\", \"medium\", \"high\"];\n\t\t\t\t\tconst filtered = levels\n\t\t\t\t\t\t.filter((l) => l.startsWith(prefix.trim()))\n\t\t\t\t\t\t.map((l) => ({ value: l, label: l }));\n\t\t\t\t\treturn filtered.length > 0 ? filtered : null;\n\t\t\t\t},\n\t\t\t},\n\t\t];\n\t\tconst provider = makeProvider(commands);\n\t\tconst result = provider.getSuggestions([\"/thinking m\"], 0, 11);\n\t\tassert.ok(result);\n\t\t// /thinking m matches only \"medium\" — verify the expected value is\n\t\t// present (by name, not index) and then assert exact count.\n\t\tassert.ok(result!.items.some((i) => i.value === \"medium\"));\n\t\tassert.equal(result!.items.length, 1);\n\t});\n\n\tit(\"returns null for commands without argument completions\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getSuggestions([\"/settings foo\"], 0, 13);\n\t\tassert.equal(result, null);\n\t});\n\n\tit(\"returns all arg completions for empty prefix after space\", () => {\n\t\tconst commands: SlashCommand[] = [\n\t\t\t{\n\t\t\t\tname: \"test\",\n\t\t\t\tdescription: \"Test command\",\n\t\t\t\tgetArgumentCompletions: (prefix) => {\n\t\t\t\t\tconst subs = [\"start\", \"stop\", \"status\"];\n\t\t\t\t\tconst filtered = subs\n\t\t\t\t\t\t.filter((s) => s.startsWith(prefix.trim()))\n\t\t\t\t\t\t.map((s) => ({ value: s, label: s }));\n\t\t\t\t\treturn filtered.length > 0 ? filtered : null;\n\t\t\t\t},\n\t\t\t},\n\t\t];\n\t\tconst provider = makeProvider(commands);\n\t\tconst result = provider.getSuggestions([\"/test \"], 0, 6);\n\t\tassert.ok(result);\n\t\t// Empty prefix returns all 3 subcommands — verify each is present\n\t\t// by name. Bare count would pass for any 3-element list.\n\t\tassert.ok(result!.items.some((i) => i.value === \"start\"));\n\t\tassert.ok(result!.items.some((i) => i.value === \"stop\"));\n\t\tassert.ok(result!.items.some((i) => i.value === \"status\"));\n\t\tassert.equal(result!.items.length, 3);\n\t});\n});\n\ndescribe(\"CombinedAutocompleteProvider — @ file prefix extraction\", () => {\n\tit(\"detects @ at start of line and returns a valid suggestion shape\", () => {\n\t\tconst provider = makeProvider();\n\t\tconst result = provider.getSuggestions([\"@nonexistent_xyz\"], 0, 16);\n\t\t// Either null (nothing matched) or a well-formed {items: Array, prefix: string}\n\t\t// shape. Previous version's `result.items.length >= 0` was a tautology —\n\t\t// array length is always ≥ 0; the whole expression could never fail.\n\t\tif (result !== null) {\n\t\t\tassert.ok(Array.isArray(result.items), \"result.items must be an array\");\n\t\t\t// The @-prefix extraction strips the leading @ — prefix should be\n\t\t\t// the raw text without the trigger character.\n\t\t\tassert.equal(typeof result.prefix, \"string\", \"prefix must be a string\");\n\t\t\tassert.ok(\n\t\t\t\t!result.prefix.startsWith(\"@\"),\n\t\t\t\t`prefix must have the @ trigger stripped, got: ${JSON.stringify(result.prefix)}`,\n\t\t\t);\n\t\t}\n\t});\n\n\tit(\"detects @ after space and returns a valid suggestion shape\", () => {\n\t\tconst provider = makeProvider();\n\t\tconst result = provider.getSuggestions([\"check @nonexistent_xyz\"], 0, 22);\n\t\tif (result !== null) {\n\t\t\tassert.ok(Array.isArray(result.items), \"result.items must be an array\");\n\t\t\tassert.equal(typeof result.prefix, \"string\", \"prefix must be a string\");\n\t\t\t// The prefix must NOT include the word \"check\" that came before the @.\n\t\t\tassert.ok(\n\t\t\t\t!result.prefix.includes(\"check\"),\n\t\t\t\t`prefix must not include text before the @, got: ${JSON.stringify(result.prefix)}`,\n\t\t\t);\n\t\t}\n\t});\n\n\tit(\"returns null for bare @ with no query to avoid full tree walk (#1824)\", () => {\n\t\tconst provider = makeProvider([], process.cwd());\n\t\t// A bare \"@\" produces an empty rawPrefix after stripping the \"@\".\n\t\t// This must return null to avoid a synchronous full filesystem walk\n\t\t// via the native fuzzyFind addon, which freezes the TUI on large repos.\n\t\tconst result = provider.getSuggestions([\"@\"], 0, 1);\n\t\tassert.equal(result, null, \"bare @ should not trigger fuzzy file search\");\n\t});\n\n\tit(\"returns null for @ after space with no query (#1824)\", () => {\n\t\tconst provider = makeProvider([], process.cwd());\n\t\tconst result = provider.getSuggestions([\"look at @\"], 0, 9);\n\t\tassert.equal(result, null, \"@ after space with no query should not trigger fuzzy file search\");\n\t});\n});\n\ndescribe(\"CombinedAutocompleteProvider — applyCompletion\", () => {\n\tit(\"applies slash command completion with trailing space\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.applyCompletion([\"/se\"], 0, 3, { value: \"settings\", label: \"settings\" }, \"/se\");\n\t\tassert.equal(result.lines[0], \"/settings \");\n\t\tassert.equal(result.cursorCol, 10); // after \"/settings \"\n\t});\n\n\tit(\"preserves leading whitespace when applying slash command completion\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.applyCompletion([\" /se\"], 0, 5, { value: \"settings\", label: \"settings\" }, \"/se\");\n\t\tassert.equal(result.lines[0], \" /settings \");\n\t\tassert.equal(result.cursorCol, 12);\n\t});\n\n\tit(\"applies file path completion for @ prefix\", () => {\n\t\tconst provider = makeProvider();\n\t\tconst result = provider.applyCompletion(\n\t\t\t[\"@src/\"],\n\t\t\t0,\n\t\t\t5,\n\t\t\t{ value: \"@src/index.ts\", label: \"index.ts\" },\n\t\t\t\"@src/\",\n\t\t);\n\t\tassert.equal(result.lines[0], \"@src/index.ts \");\n\t});\n\n\tit(\"applies directory completion without trailing space\", () => {\n\t\tconst provider = makeProvider();\n\t\tconst result = provider.applyCompletion(\n\t\t\t[\"@sr\"],\n\t\t\t0,\n\t\t\t3,\n\t\t\t{ value: \"@src/\", label: \"src/\" },\n\t\t\t\"@sr\",\n\t\t);\n\t\t// Directories should not get trailing space so user can continue typing\n\t\tassert.ok(!result.lines[0]!.endsWith(\" \"));\n\t});\n\n\tit(\"preserves text after cursor\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.applyCompletion(\n\t\t\t[\"/se and more text\"],\n\t\t\t0,\n\t\t\t3,\n\t\t\t{ value: \"settings\", label: \"settings\" },\n\t\t\t\"/se\",\n\t\t);\n\t\tassert.ok(result.lines[0]!.includes(\"and more text\"));\n\t});\n});\n\ndescribe(\"CombinedAutocompleteProvider — force file suggestions\", () => {\n\tit(\"does not trigger for slash commands\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getForceFileSuggestions([\"/set\"], 0, 4);\n\t\tassert.equal(result, null);\n\t});\n\n\tit(\"shouldTriggerFileCompletion returns false for slash commands\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tassert.equal(provider.shouldTriggerFileCompletion([\"/set\"], 0, 4), false);\n\t});\n\n\tit(\"shouldTriggerFileCompletion returns true for regular text\", () => {\n\t\tconst provider = makeProvider();\n\t\tassert.equal(provider.shouldTriggerFileCompletion([\"some text\"], 0, 9), true);\n\t});\n});\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"leak-fixes-runtime.test.d.ts","sourceRoot":"","sources":["../../../src/components/__tests__/leak-fixes-runtime.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// Runtime regression tests for the long-running-session leak fixes.
|
|
2
|
+
//
|
|
3
|
+
// Replaces the source-grep file `src/tests/session-memory-leaks.test.ts`
|
|
4
|
+
// (deleted in #4875, tracked as #4873). The previous tests asserted on
|
|
5
|
+
// identifier presence (`_prevRender`, `_lastMessage`, `MAX_CHAT_COMPONENTS`)
|
|
6
|
+
// in source — a regression that set `MAX_CHAT_COMPONENTS = Number.MAX_SAFE_INTEGER`
|
|
7
|
+
// or replaced `setText` with an inline mutation would still pass.
|
|
8
|
+
//
|
|
9
|
+
// Each test below drives the actual component through a long-running scenario
|
|
10
|
+
// and asserts on observable behaviour.
|
|
11
|
+
import { describe, it, mock, beforeEach, afterEach } from "node:test";
|
|
12
|
+
import assert from "node:assert/strict";
|
|
13
|
+
import { Container } from "../../tui.js";
|
|
14
|
+
import { Loader } from "../loader.js";
|
|
15
|
+
import { Text } from "../text.js";
|
|
16
|
+
function makeMockTUI() {
|
|
17
|
+
return { requestRender: mock.fn() };
|
|
18
|
+
}
|
|
19
|
+
// ─── Container render-skip ──────────────────────────────────────────────
|
|
20
|
+
describe("Container.render skips work when output is unchanged", () => {
|
|
21
|
+
it("returns the SAME array reference across two renders with no changes", () => {
|
|
22
|
+
// The container caches `_prevRender` so doRender() can short-circuit
|
|
23
|
+
// the post-processing (image-line scan, applyLineResets, line diffs).
|
|
24
|
+
// The contract for the optimization is reference equality of the
|
|
25
|
+
// returned array — that's what the consumer in tui.ts checks.
|
|
26
|
+
const c = new Container();
|
|
27
|
+
c.addChild(new Text("hello", 0, 0));
|
|
28
|
+
const first = c.render(20);
|
|
29
|
+
const second = c.render(20);
|
|
30
|
+
assert.strictEqual(second, first, "Container must return the same array reference when content is unchanged " +
|
|
31
|
+
"(reference equality is the consumer-visible contract for the skip)");
|
|
32
|
+
});
|
|
33
|
+
it("returns a DIFFERENT array reference after addChild invalidates the cache", () => {
|
|
34
|
+
// If a regression caused `_prevRender` to never reset, downstream
|
|
35
|
+
// rendering would skip post-processing for new components — visible
|
|
36
|
+
// as missing/stale UI. Behaviourally we observe that adding a child
|
|
37
|
+
// breaks the reference-equality contract on the next render.
|
|
38
|
+
const c = new Container();
|
|
39
|
+
c.addChild(new Text("a", 0, 0));
|
|
40
|
+
const first = c.render(20);
|
|
41
|
+
c.addChild(new Text("b", 0, 0));
|
|
42
|
+
const second = c.render(20);
|
|
43
|
+
assert.notStrictEqual(second, first, "adding a child must invalidate the cached render reference");
|
|
44
|
+
});
|
|
45
|
+
it("returns a different reference after content changes", () => {
|
|
46
|
+
const t = new Text("hello", 0, 0);
|
|
47
|
+
const c = new Container();
|
|
48
|
+
c.addChild(t);
|
|
49
|
+
const first = c.render(20);
|
|
50
|
+
t.setText("world");
|
|
51
|
+
const second = c.render(20);
|
|
52
|
+
assert.notStrictEqual(second, first, "changing a child's text must produce a fresh array (not the cached one)");
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
// ─── Loader frame isolation ─────────────────────────────────────────────
|
|
56
|
+
describe("Loader does not mutate Text cache on every spinner tick", () => {
|
|
57
|
+
let tui;
|
|
58
|
+
let loader;
|
|
59
|
+
beforeEach(() => {
|
|
60
|
+
tui = makeMockTUI();
|
|
61
|
+
mock.timers.enable({ apis: ["setInterval"] });
|
|
62
|
+
});
|
|
63
|
+
afterEach(() => {
|
|
64
|
+
try {
|
|
65
|
+
loader?.stop();
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
/* best-effort */
|
|
69
|
+
}
|
|
70
|
+
mock.timers.reset();
|
|
71
|
+
});
|
|
72
|
+
it("does not invalidate Text's render cache across N spinner ticks", () => {
|
|
73
|
+
// Loader extends Text. Text caches its rendered lines keyed by
|
|
74
|
+
// (text, width). The leak was: the old Loader called setText()
|
|
75
|
+
// on every 80ms tick, which always cleared the cache, forcing a
|
|
76
|
+
// re-wrap of the message text.
|
|
77
|
+
// Behavioural test: render the loader, capture the cached array
|
|
78
|
+
// reference returned by Text.render, advance many ticks, and
|
|
79
|
+
// assert the underlying Text cache was NOT invalidated (we observe
|
|
80
|
+
// this via reference equality of the cached lines slice in the
|
|
81
|
+
// returned array — the second `result[*]` segment from super.render
|
|
82
|
+
// stays identity-stable when the cache survives).
|
|
83
|
+
loader = new Loader(tui, (s) => s, (s) => s, "the-message");
|
|
84
|
+
const before = loader.render(40);
|
|
85
|
+
// Run 50 frame intervals — the spinner should rotate, but the
|
|
86
|
+
// underlying message text wrapping must not change.
|
|
87
|
+
mock.timers.tick(80 * 50);
|
|
88
|
+
const after = loader.render(40);
|
|
89
|
+
// The message portion (everything after the first `""` padding line
|
|
90
|
+
// and after the spinner glyph + space prefix on the first content
|
|
91
|
+
// line) must be byte-identical across renders.
|
|
92
|
+
assert.equal(before.length, after.length, "render shape stable across ticks");
|
|
93
|
+
// Trailing message lines (index 2 onwards) come straight from Text's
|
|
94
|
+
// cache without any spinner mutation. They must be reference-equal
|
|
95
|
+
// substrings — same string contents byte-for-byte.
|
|
96
|
+
for (let i = 2; i < before.length; i++) {
|
|
97
|
+
assert.equal(after[i], before[i], `message line ${i} must remain byte-identical across spinner ticks ` +
|
|
98
|
+
`(cache must not be invalidated by frame rotation)`);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
it("setMessage invalidates Text cache and a new message is reflected", () => {
|
|
102
|
+
// Quiescence is conditional on the message being unchanged — we
|
|
103
|
+
// must still see updates when it actually changes. This is the
|
|
104
|
+
// counter-test that prevents a false-positive "cache always stable"
|
|
105
|
+
// fix that would freeze the loader text.
|
|
106
|
+
loader = new Loader(tui, (s) => s, (s) => s, "first");
|
|
107
|
+
const beforeFirstRender = loader.render(40).join("\n");
|
|
108
|
+
loader.setMessage("second");
|
|
109
|
+
const afterChange = loader.render(40).join("\n");
|
|
110
|
+
assert.ok(afterChange.includes("second"), "new message must render");
|
|
111
|
+
assert.ok(!afterChange.includes("first") || beforeFirstRender !== afterChange, "render output must change when message changes");
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
// ─── Text.setText early-return guard ────────────────────────────────────
|
|
115
|
+
describe("Text.setText returns early when value is unchanged", () => {
|
|
116
|
+
it("does not invalidate the cached render when setText receives the same value", () => {
|
|
117
|
+
// The setText early-return is the runtime guard that keeps the
|
|
118
|
+
// Loader-and-Text cache stable. We observe it via reference equality
|
|
119
|
+
// of the rendered output across a setText(SAME) call.
|
|
120
|
+
const t = new Text("identical", 0, 0);
|
|
121
|
+
const first = t.render(30);
|
|
122
|
+
t.setText("identical"); // same value — must NOT clear the cache
|
|
123
|
+
const second = t.render(30);
|
|
124
|
+
assert.strictEqual(second, first, "setText with an unchanged value must leave the render cache intact");
|
|
125
|
+
});
|
|
126
|
+
it("invalidates cache when the value actually changes", () => {
|
|
127
|
+
const t = new Text("one", 0, 0);
|
|
128
|
+
const first = t.render(30);
|
|
129
|
+
t.setText("two");
|
|
130
|
+
const second = t.render(30);
|
|
131
|
+
assert.notStrictEqual(second, first, "setText with a new value must produce a fresh render result");
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
// ─── Heap growth bound (gated on --expose-gc) ───────────────────────────
|
|
135
|
+
describe("Long-running render loop does not leak heap (forced-GC bound)", () => {
|
|
136
|
+
const hasGc = typeof globalThis.gc === "function";
|
|
137
|
+
it("renders a Container 5000 times with stable heapUsed (within 2x baseline)", { skip: !hasGc ? "requires --expose-gc to drive deterministic GC between snapshots" : false }, () => {
|
|
138
|
+
// This is the on-the-wire memory-soak test. The naive version
|
|
139
|
+
// (no forced GC) is flaky because Node's GC is opportunistic;
|
|
140
|
+
// any background allocation can shift heapUsed past an absolute
|
|
141
|
+
// bound. We instead force GC between snapshots and assert a
|
|
142
|
+
// RELATIVE bound: post-iteration heap must be within 2x of the
|
|
143
|
+
// post-warmup baseline.
|
|
144
|
+
const gc = globalThis.gc;
|
|
145
|
+
const c = new Container();
|
|
146
|
+
const t = new Text("steady-state", 0, 0);
|
|
147
|
+
c.addChild(t);
|
|
148
|
+
// Warm up so the JIT and any lazy initial allocations settle.
|
|
149
|
+
for (let i = 0; i < 200; i++)
|
|
150
|
+
c.render(40);
|
|
151
|
+
gc();
|
|
152
|
+
const baseline = process.memoryUsage().heapUsed;
|
|
153
|
+
for (let i = 0; i < 5000; i++)
|
|
154
|
+
c.render(40);
|
|
155
|
+
gc();
|
|
156
|
+
const after = process.memoryUsage().heapUsed;
|
|
157
|
+
assert.ok(after < baseline * 2, `heapUsed must stay within 2x baseline after 5000 renders ` +
|
|
158
|
+
`(baseline=${baseline}B, after=${after}B, ratio=${(after / baseline).toFixed(2)})`);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
//# sourceMappingURL=leak-fixes-runtime.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"leak-fixes-runtime.test.js","sourceRoot":"","sources":["../../../src/components/__tests__/leak-fixes-runtime.test.ts"],"names":[],"mappings":"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","sourcesContent":["// Runtime regression tests for the long-running-session leak fixes.\n//\n// Replaces the source-grep file `src/tests/session-memory-leaks.test.ts`\n// (deleted in #4875, tracked as #4873). The previous tests asserted on\n// identifier presence (`_prevRender`, `_lastMessage`, `MAX_CHAT_COMPONENTS`)\n// in source — a regression that set `MAX_CHAT_COMPONENTS = Number.MAX_SAFE_INTEGER`\n// or replaced `setText` with an inline mutation would still pass.\n//\n// Each test below drives the actual component through a long-running scenario\n// and asserts on observable behaviour.\n\nimport { describe, it, mock, beforeEach, afterEach } from \"node:test\";\nimport assert from \"node:assert/strict\";\nimport { Container } from \"../../tui.js\";\nimport { Loader } from \"../loader.js\";\nimport { Text } from \"../text.js\";\n\ninterface MockTui {\n\trequestRender: ReturnType<typeof mock.fn>;\n}\n\nfunction makeMockTUI(): MockTui {\n\treturn { requestRender: mock.fn() };\n}\n\n// ─── Container render-skip ──────────────────────────────────────────────\n\ndescribe(\"Container.render skips work when output is unchanged\", () => {\n\tit(\"returns the SAME array reference across two renders with no changes\", () => {\n\t\t// The container caches `_prevRender` so doRender() can short-circuit\n\t\t// the post-processing (image-line scan, applyLineResets, line diffs).\n\t\t// The contract for the optimization is reference equality of the\n\t\t// returned array — that's what the consumer in tui.ts checks.\n\t\tconst c = new Container();\n\t\tc.addChild(new Text(\"hello\", 0, 0));\n\n\t\tconst first = c.render(20);\n\t\tconst second = c.render(20);\n\t\tassert.strictEqual(\n\t\t\tsecond,\n\t\t\tfirst,\n\t\t\t\"Container must return the same array reference when content is unchanged \" +\n\t\t\t\t\"(reference equality is the consumer-visible contract for the skip)\",\n\t\t);\n\t});\n\n\tit(\"returns a DIFFERENT array reference after addChild invalidates the cache\", () => {\n\t\t// If a regression caused `_prevRender` to never reset, downstream\n\t\t// rendering would skip post-processing for new components — visible\n\t\t// as missing/stale UI. Behaviourally we observe that adding a child\n\t\t// breaks the reference-equality contract on the next render.\n\t\tconst c = new Container();\n\t\tc.addChild(new Text(\"a\", 0, 0));\n\t\tconst first = c.render(20);\n\t\tc.addChild(new Text(\"b\", 0, 0));\n\t\tconst second = c.render(20);\n\t\tassert.notStrictEqual(\n\t\t\tsecond,\n\t\t\tfirst,\n\t\t\t\"adding a child must invalidate the cached render reference\",\n\t\t);\n\t});\n\n\tit(\"returns a different reference after content changes\", () => {\n\t\tconst t = new Text(\"hello\", 0, 0);\n\t\tconst c = new Container();\n\t\tc.addChild(t);\n\n\t\tconst first = c.render(20);\n\t\tt.setText(\"world\");\n\t\tconst second = c.render(20);\n\t\tassert.notStrictEqual(\n\t\t\tsecond,\n\t\t\tfirst,\n\t\t\t\"changing a child's text must produce a fresh array (not the cached one)\",\n\t\t);\n\t});\n});\n\n// ─── Loader frame isolation ─────────────────────────────────────────────\n\ndescribe(\"Loader does not mutate Text cache on every spinner tick\", () => {\n\tlet tui: MockTui;\n\tlet loader: Loader;\n\n\tbeforeEach(() => {\n\t\ttui = makeMockTUI();\n\t\tmock.timers.enable({ apis: [\"setInterval\"] });\n\t});\n\n\tafterEach(() => {\n\t\ttry {\n\t\t\tloader?.stop();\n\t\t} catch {\n\t\t\t/* best-effort */\n\t\t}\n\t\tmock.timers.reset();\n\t});\n\n\tit(\"does not invalidate Text's render cache across N spinner ticks\", () => {\n\t\t// Loader extends Text. Text caches its rendered lines keyed by\n\t\t// (text, width). The leak was: the old Loader called setText()\n\t\t// on every 80ms tick, which always cleared the cache, forcing a\n\t\t// re-wrap of the message text.\n\t\t// Behavioural test: render the loader, capture the cached array\n\t\t// reference returned by Text.render, advance many ticks, and\n\t\t// assert the underlying Text cache was NOT invalidated (we observe\n\t\t// this via reference equality of the cached lines slice in the\n\t\t// returned array — the second `result[*]` segment from super.render\n\t\t// stays identity-stable when the cache survives).\n\t\tloader = new Loader(tui as never, (s) => s, (s) => s, \"the-message\");\n\n\t\tconst before = loader.render(40);\n\t\t// Run 50 frame intervals — the spinner should rotate, but the\n\t\t// underlying message text wrapping must not change.\n\t\tmock.timers.tick(80 * 50);\n\t\tconst after = loader.render(40);\n\n\t\t// The message portion (everything after the first `\"\"` padding line\n\t\t// and after the spinner glyph + space prefix on the first content\n\t\t// line) must be byte-identical across renders.\n\t\tassert.equal(before.length, after.length, \"render shape stable across ticks\");\n\t\t// Trailing message lines (index 2 onwards) come straight from Text's\n\t\t// cache without any spinner mutation. They must be reference-equal\n\t\t// substrings — same string contents byte-for-byte.\n\t\tfor (let i = 2; i < before.length; i++) {\n\t\t\tassert.equal(\n\t\t\t\tafter[i],\n\t\t\t\tbefore[i],\n\t\t\t\t`message line ${i} must remain byte-identical across spinner ticks ` +\n\t\t\t\t\t`(cache must not be invalidated by frame rotation)`,\n\t\t\t);\n\t\t}\n\t});\n\n\tit(\"setMessage invalidates Text cache and a new message is reflected\", () => {\n\t\t// Quiescence is conditional on the message being unchanged — we\n\t\t// must still see updates when it actually changes. This is the\n\t\t// counter-test that prevents a false-positive \"cache always stable\"\n\t\t// fix that would freeze the loader text.\n\t\tloader = new Loader(tui as never, (s) => s, (s) => s, \"first\");\n\t\tconst beforeFirstRender = loader.render(40).join(\"\\n\");\n\t\tloader.setMessage(\"second\");\n\t\tconst afterChange = loader.render(40).join(\"\\n\");\n\t\tassert.ok(afterChange.includes(\"second\"), \"new message must render\");\n\t\tassert.ok(!afterChange.includes(\"first\") || beforeFirstRender !== afterChange,\n\t\t\t\"render output must change when message changes\");\n\t});\n});\n\n// ─── Text.setText early-return guard ────────────────────────────────────\n\ndescribe(\"Text.setText returns early when value is unchanged\", () => {\n\tit(\"does not invalidate the cached render when setText receives the same value\", () => {\n\t\t// The setText early-return is the runtime guard that keeps the\n\t\t// Loader-and-Text cache stable. We observe it via reference equality\n\t\t// of the rendered output across a setText(SAME) call.\n\t\tconst t = new Text(\"identical\", 0, 0);\n\t\tconst first = t.render(30);\n\t\tt.setText(\"identical\"); // same value — must NOT clear the cache\n\t\tconst second = t.render(30);\n\t\tassert.strictEqual(\n\t\t\tsecond,\n\t\t\tfirst,\n\t\t\t\"setText with an unchanged value must leave the render cache intact\",\n\t\t);\n\t});\n\n\tit(\"invalidates cache when the value actually changes\", () => {\n\t\tconst t = new Text(\"one\", 0, 0);\n\t\tconst first = t.render(30);\n\t\tt.setText(\"two\");\n\t\tconst second = t.render(30);\n\t\tassert.notStrictEqual(\n\t\t\tsecond,\n\t\t\tfirst,\n\t\t\t\"setText with a new value must produce a fresh render result\",\n\t\t);\n\t});\n});\n\n// ─── Heap growth bound (gated on --expose-gc) ───────────────────────────\n\ndescribe(\"Long-running render loop does not leak heap (forced-GC bound)\", () => {\n\tconst hasGc = typeof (globalThis as any).gc === \"function\";\n\n\tit(\n\t\t\"renders a Container 5000 times with stable heapUsed (within 2x baseline)\",\n\t\t{ skip: !hasGc ? \"requires --expose-gc to drive deterministic GC between snapshots\" : false },\n\t\t() => {\n\t\t\t// This is the on-the-wire memory-soak test. The naive version\n\t\t\t// (no forced GC) is flaky because Node's GC is opportunistic;\n\t\t\t// any background allocation can shift heapUsed past an absolute\n\t\t\t// bound. We instead force GC between snapshots and assert a\n\t\t\t// RELATIVE bound: post-iteration heap must be within 2x of the\n\t\t\t// post-warmup baseline.\n\t\t\tconst gc = (globalThis as any).gc as () => void;\n\n\t\t\tconst c = new Container();\n\t\t\tconst t = new Text(\"steady-state\", 0, 0);\n\t\t\tc.addChild(t);\n\n\t\t\t// Warm up so the JIT and any lazy initial allocations settle.\n\t\t\tfor (let i = 0; i < 200; i++) c.render(40);\n\t\t\tgc();\n\t\t\tconst baseline = process.memoryUsage().heapUsed;\n\n\t\t\tfor (let i = 0; i < 5000; i++) c.render(40);\n\t\t\tgc();\n\t\t\tconst after = process.memoryUsage().heapUsed;\n\n\t\t\tassert.ok(\n\t\t\t\tafter < baseline * 2,\n\t\t\t\t`heapUsed must stay within 2x baseline after 5000 renders ` +\n\t\t\t\t\t`(baseline=${baseline}B, after=${after}B, ratio=${(after / baseline).toFixed(2)})`,\n\t\t\t);\n\t\t},\n\t);\n});\n"]}
|
|
@@ -28,9 +28,12 @@ describe("CombinedAutocompleteProvider — slash commands", () => {
|
|
|
28
28
|
const provider = makeProvider(sampleCommands);
|
|
29
29
|
const result = provider.getSuggestions(["/se"], 0, 3);
|
|
30
30
|
assert.ok(result);
|
|
31
|
-
|
|
31
|
+
// /se matches settings and session — name the values, then assert exact
|
|
32
|
+
// count as the contract for this prefix. Asserting count alone would
|
|
33
|
+
// pass even if the matches were the wrong commands.
|
|
32
34
|
assert.ok(result!.items.some((i) => i.value === "settings"));
|
|
33
35
|
assert.ok(result!.items.some((i) => i.value === "session"));
|
|
36
|
+
assert.equal(result!.items.length, 2);
|
|
34
37
|
});
|
|
35
38
|
|
|
36
39
|
it("returns null when no commands match", () => {
|
|
@@ -43,7 +46,14 @@ describe("CombinedAutocompleteProvider — slash commands", () => {
|
|
|
43
46
|
const provider = makeProvider(sampleCommands);
|
|
44
47
|
const result = provider.getSuggestions(["/mod"], 0, 4);
|
|
45
48
|
assert.ok(result);
|
|
46
|
-
|
|
49
|
+
// Verify the matched command is present and every item carries a
|
|
50
|
+
// description — avoids `[0]` positional coupling that would silently
|
|
51
|
+
// pass if list ordering changed.
|
|
52
|
+
assert.ok(result!.items.some((i) => i.value === "model"));
|
|
53
|
+
assert.ok(
|
|
54
|
+
result!.items.every((i) => typeof i.description === "string" && i.description.length > 0),
|
|
55
|
+
"every suggestion must have a non-empty description",
|
|
56
|
+
);
|
|
47
57
|
});
|
|
48
58
|
|
|
49
59
|
it("does not offer slash command suggestions mid-line", () => {
|
|
@@ -95,8 +105,10 @@ describe("CombinedAutocompleteProvider — argument completions", () => {
|
|
|
95
105
|
const provider = makeProvider(commands);
|
|
96
106
|
const result = provider.getSuggestions(["/thinking m"], 0, 11);
|
|
97
107
|
assert.ok(result);
|
|
108
|
+
// /thinking m matches only "medium" — verify the expected value is
|
|
109
|
+
// present (by name, not index) and then assert exact count.
|
|
110
|
+
assert.ok(result!.items.some((i) => i.value === "medium"));
|
|
98
111
|
assert.equal(result!.items.length, 1);
|
|
99
|
-
assert.equal(result!.items[0]?.value, "medium");
|
|
100
112
|
});
|
|
101
113
|
|
|
102
114
|
it("returns null for commands without argument completions", () => {
|
|
@@ -122,6 +134,11 @@ describe("CombinedAutocompleteProvider — argument completions", () => {
|
|
|
122
134
|
const provider = makeProvider(commands);
|
|
123
135
|
const result = provider.getSuggestions(["/test "], 0, 6);
|
|
124
136
|
assert.ok(result);
|
|
137
|
+
// Empty prefix returns all 3 subcommands — verify each is present
|
|
138
|
+
// by name. Bare count would pass for any 3-element list.
|
|
139
|
+
assert.ok(result!.items.some((i) => i.value === "start"));
|
|
140
|
+
assert.ok(result!.items.some((i) => i.value === "stop"));
|
|
141
|
+
assert.ok(result!.items.some((i) => i.value === "status"));
|
|
125
142
|
assert.equal(result!.items.length, 3);
|
|
126
143
|
});
|
|
127
144
|
});
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
// Runtime regression tests for the long-running-session leak fixes.
|
|
2
|
+
//
|
|
3
|
+
// Replaces the source-grep file `src/tests/session-memory-leaks.test.ts`
|
|
4
|
+
// (deleted in #4875, tracked as #4873). The previous tests asserted on
|
|
5
|
+
// identifier presence (`_prevRender`, `_lastMessage`, `MAX_CHAT_COMPONENTS`)
|
|
6
|
+
// in source — a regression that set `MAX_CHAT_COMPONENTS = Number.MAX_SAFE_INTEGER`
|
|
7
|
+
// or replaced `setText` with an inline mutation would still pass.
|
|
8
|
+
//
|
|
9
|
+
// Each test below drives the actual component through a long-running scenario
|
|
10
|
+
// and asserts on observable behaviour.
|
|
11
|
+
|
|
12
|
+
import { describe, it, mock, beforeEach, afterEach } from "node:test";
|
|
13
|
+
import assert from "node:assert/strict";
|
|
14
|
+
import { Container } from "../../tui.js";
|
|
15
|
+
import { Loader } from "../loader.js";
|
|
16
|
+
import { Text } from "../text.js";
|
|
17
|
+
|
|
18
|
+
interface MockTui {
|
|
19
|
+
requestRender: ReturnType<typeof mock.fn>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function makeMockTUI(): MockTui {
|
|
23
|
+
return { requestRender: mock.fn() };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ─── Container render-skip ──────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
describe("Container.render skips work when output is unchanged", () => {
|
|
29
|
+
it("returns the SAME array reference across two renders with no changes", () => {
|
|
30
|
+
// The container caches `_prevRender` so doRender() can short-circuit
|
|
31
|
+
// the post-processing (image-line scan, applyLineResets, line diffs).
|
|
32
|
+
// The contract for the optimization is reference equality of the
|
|
33
|
+
// returned array — that's what the consumer in tui.ts checks.
|
|
34
|
+
const c = new Container();
|
|
35
|
+
c.addChild(new Text("hello", 0, 0));
|
|
36
|
+
|
|
37
|
+
const first = c.render(20);
|
|
38
|
+
const second = c.render(20);
|
|
39
|
+
assert.strictEqual(
|
|
40
|
+
second,
|
|
41
|
+
first,
|
|
42
|
+
"Container must return the same array reference when content is unchanged " +
|
|
43
|
+
"(reference equality is the consumer-visible contract for the skip)",
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("returns a DIFFERENT array reference after addChild invalidates the cache", () => {
|
|
48
|
+
// If a regression caused `_prevRender` to never reset, downstream
|
|
49
|
+
// rendering would skip post-processing for new components — visible
|
|
50
|
+
// as missing/stale UI. Behaviourally we observe that adding a child
|
|
51
|
+
// breaks the reference-equality contract on the next render.
|
|
52
|
+
const c = new Container();
|
|
53
|
+
c.addChild(new Text("a", 0, 0));
|
|
54
|
+
const first = c.render(20);
|
|
55
|
+
c.addChild(new Text("b", 0, 0));
|
|
56
|
+
const second = c.render(20);
|
|
57
|
+
assert.notStrictEqual(
|
|
58
|
+
second,
|
|
59
|
+
first,
|
|
60
|
+
"adding a child must invalidate the cached render reference",
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("returns a different reference after content changes", () => {
|
|
65
|
+
const t = new Text("hello", 0, 0);
|
|
66
|
+
const c = new Container();
|
|
67
|
+
c.addChild(t);
|
|
68
|
+
|
|
69
|
+
const first = c.render(20);
|
|
70
|
+
t.setText("world");
|
|
71
|
+
const second = c.render(20);
|
|
72
|
+
assert.notStrictEqual(
|
|
73
|
+
second,
|
|
74
|
+
first,
|
|
75
|
+
"changing a child's text must produce a fresh array (not the cached one)",
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// ─── Loader frame isolation ─────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
describe("Loader does not mutate Text cache on every spinner tick", () => {
|
|
83
|
+
let tui: MockTui;
|
|
84
|
+
let loader: Loader;
|
|
85
|
+
|
|
86
|
+
beforeEach(() => {
|
|
87
|
+
tui = makeMockTUI();
|
|
88
|
+
mock.timers.enable({ apis: ["setInterval"] });
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
afterEach(() => {
|
|
92
|
+
try {
|
|
93
|
+
loader?.stop();
|
|
94
|
+
} catch {
|
|
95
|
+
/* best-effort */
|
|
96
|
+
}
|
|
97
|
+
mock.timers.reset();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("does not invalidate Text's render cache across N spinner ticks", () => {
|
|
101
|
+
// Loader extends Text. Text caches its rendered lines keyed by
|
|
102
|
+
// (text, width). The leak was: the old Loader called setText()
|
|
103
|
+
// on every 80ms tick, which always cleared the cache, forcing a
|
|
104
|
+
// re-wrap of the message text.
|
|
105
|
+
// Behavioural test: render the loader, capture the cached array
|
|
106
|
+
// reference returned by Text.render, advance many ticks, and
|
|
107
|
+
// assert the underlying Text cache was NOT invalidated (we observe
|
|
108
|
+
// this via reference equality of the cached lines slice in the
|
|
109
|
+
// returned array — the second `result[*]` segment from super.render
|
|
110
|
+
// stays identity-stable when the cache survives).
|
|
111
|
+
loader = new Loader(tui as never, (s) => s, (s) => s, "the-message");
|
|
112
|
+
|
|
113
|
+
const before = loader.render(40);
|
|
114
|
+
// Run 50 frame intervals — the spinner should rotate, but the
|
|
115
|
+
// underlying message text wrapping must not change.
|
|
116
|
+
mock.timers.tick(80 * 50);
|
|
117
|
+
const after = loader.render(40);
|
|
118
|
+
|
|
119
|
+
// The message portion (everything after the first `""` padding line
|
|
120
|
+
// and after the spinner glyph + space prefix on the first content
|
|
121
|
+
// line) must be byte-identical across renders.
|
|
122
|
+
assert.equal(before.length, after.length, "render shape stable across ticks");
|
|
123
|
+
// Trailing message lines (index 2 onwards) come straight from Text's
|
|
124
|
+
// cache without any spinner mutation. They must be reference-equal
|
|
125
|
+
// substrings — same string contents byte-for-byte.
|
|
126
|
+
for (let i = 2; i < before.length; i++) {
|
|
127
|
+
assert.equal(
|
|
128
|
+
after[i],
|
|
129
|
+
before[i],
|
|
130
|
+
`message line ${i} must remain byte-identical across spinner ticks ` +
|
|
131
|
+
`(cache must not be invalidated by frame rotation)`,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("setMessage invalidates Text cache and a new message is reflected", () => {
|
|
137
|
+
// Quiescence is conditional on the message being unchanged — we
|
|
138
|
+
// must still see updates when it actually changes. This is the
|
|
139
|
+
// counter-test that prevents a false-positive "cache always stable"
|
|
140
|
+
// fix that would freeze the loader text.
|
|
141
|
+
loader = new Loader(tui as never, (s) => s, (s) => s, "first");
|
|
142
|
+
const beforeFirstRender = loader.render(40).join("\n");
|
|
143
|
+
loader.setMessage("second");
|
|
144
|
+
const afterChange = loader.render(40).join("\n");
|
|
145
|
+
assert.ok(afterChange.includes("second"), "new message must render");
|
|
146
|
+
assert.ok(!afterChange.includes("first") || beforeFirstRender !== afterChange,
|
|
147
|
+
"render output must change when message changes");
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// ─── Text.setText early-return guard ────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
describe("Text.setText returns early when value is unchanged", () => {
|
|
154
|
+
it("does not invalidate the cached render when setText receives the same value", () => {
|
|
155
|
+
// The setText early-return is the runtime guard that keeps the
|
|
156
|
+
// Loader-and-Text cache stable. We observe it via reference equality
|
|
157
|
+
// of the rendered output across a setText(SAME) call.
|
|
158
|
+
const t = new Text("identical", 0, 0);
|
|
159
|
+
const first = t.render(30);
|
|
160
|
+
t.setText("identical"); // same value — must NOT clear the cache
|
|
161
|
+
const second = t.render(30);
|
|
162
|
+
assert.strictEqual(
|
|
163
|
+
second,
|
|
164
|
+
first,
|
|
165
|
+
"setText with an unchanged value must leave the render cache intact",
|
|
166
|
+
);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("invalidates cache when the value actually changes", () => {
|
|
170
|
+
const t = new Text("one", 0, 0);
|
|
171
|
+
const first = t.render(30);
|
|
172
|
+
t.setText("two");
|
|
173
|
+
const second = t.render(30);
|
|
174
|
+
assert.notStrictEqual(
|
|
175
|
+
second,
|
|
176
|
+
first,
|
|
177
|
+
"setText with a new value must produce a fresh render result",
|
|
178
|
+
);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// ─── Heap growth bound (gated on --expose-gc) ───────────────────────────
|
|
183
|
+
|
|
184
|
+
describe("Long-running render loop does not leak heap (forced-GC bound)", () => {
|
|
185
|
+
const hasGc = typeof (globalThis as any).gc === "function";
|
|
186
|
+
|
|
187
|
+
it(
|
|
188
|
+
"renders a Container 5000 times with stable heapUsed (within 2x baseline)",
|
|
189
|
+
{ skip: !hasGc ? "requires --expose-gc to drive deterministic GC between snapshots" : false },
|
|
190
|
+
() => {
|
|
191
|
+
// This is the on-the-wire memory-soak test. The naive version
|
|
192
|
+
// (no forced GC) is flaky because Node's GC is opportunistic;
|
|
193
|
+
// any background allocation can shift heapUsed past an absolute
|
|
194
|
+
// bound. We instead force GC between snapshots and assert a
|
|
195
|
+
// RELATIVE bound: post-iteration heap must be within 2x of the
|
|
196
|
+
// post-warmup baseline.
|
|
197
|
+
const gc = (globalThis as any).gc as () => void;
|
|
198
|
+
|
|
199
|
+
const c = new Container();
|
|
200
|
+
const t = new Text("steady-state", 0, 0);
|
|
201
|
+
c.addChild(t);
|
|
202
|
+
|
|
203
|
+
// Warm up so the JIT and any lazy initial allocations settle.
|
|
204
|
+
for (let i = 0; i < 200; i++) c.render(40);
|
|
205
|
+
gc();
|
|
206
|
+
const baseline = process.memoryUsage().heapUsed;
|
|
207
|
+
|
|
208
|
+
for (let i = 0; i < 5000; i++) c.render(40);
|
|
209
|
+
gc();
|
|
210
|
+
const after = process.memoryUsage().heapUsed;
|
|
211
|
+
|
|
212
|
+
assert.ok(
|
|
213
|
+
after < baseline * 2,
|
|
214
|
+
`heapUsed must stay within 2x baseline after 5000 renders ` +
|
|
215
|
+
`(baseline=${baseline}B, after=${after}B, ratio=${(after / baseline).toFixed(2)})`,
|
|
216
|
+
);
|
|
217
|
+
},
|
|
218
|
+
);
|
|
219
|
+
});
|