@cleocode/core 2026.3.74 → 2026.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/agents/agent-schema.d.ts.map +1 -1
- package/dist/agents/retry.js +26 -21
- package/dist/agents/retry.js.map +1 -1
- package/dist/cant/approval.d.ts +110 -0
- package/dist/cant/approval.d.ts.map +1 -0
- package/dist/cant/approval.js +185 -0
- package/dist/cant/approval.js.map +1 -0
- package/dist/cant/context-builder.d.ts +79 -0
- package/dist/cant/context-builder.d.ts.map +1 -0
- package/dist/cant/context-builder.js +117 -0
- package/dist/cant/context-builder.js.map +1 -0
- package/dist/cant/discretion.d.ts +95 -0
- package/dist/cant/discretion.d.ts.map +1 -0
- package/dist/cant/discretion.js +116 -0
- package/dist/cant/discretion.js.map +1 -0
- package/dist/cant/index.d.ts +25 -0
- package/dist/cant/index.d.ts.map +1 -0
- package/dist/cant/index.js +23 -0
- package/dist/cant/index.js.map +1 -0
- package/dist/cant/parallel-runner.d.ts +38 -0
- package/dist/cant/parallel-runner.d.ts.map +1 -0
- package/dist/cant/parallel-runner.js +173 -0
- package/dist/cant/parallel-runner.js.map +1 -0
- package/dist/cant/types.d.ts +127 -0
- package/dist/cant/types.d.ts.map +1 -0
- package/dist/cant/types.js +11 -0
- package/dist/cant/types.js.map +1 -0
- package/dist/cant/workflow-executor.d.ts +105 -0
- package/dist/cant/workflow-executor.d.ts.map +1 -0
- package/dist/cant/workflow-executor.js +440 -0
- package/dist/cant/workflow-executor.js.map +1 -0
- package/dist/cleo.js +21 -1
- package/dist/cleo.js.map +1 -1
- package/dist/code/index.d.ts +10 -0
- package/dist/code/index.d.ts.map +1 -0
- package/dist/code/outline.d.ts +51 -0
- package/dist/code/outline.d.ts.map +1 -0
- package/dist/code/parser.d.ts +30 -0
- package/dist/code/parser.d.ts.map +1 -0
- package/dist/code/search.d.ts +42 -0
- package/dist/code/search.d.ts.map +1 -0
- package/dist/code/unfold.d.ts +44 -0
- package/dist/code/unfold.d.ts.map +1 -0
- package/dist/conduit/conduit-client.d.ts +35 -0
- package/dist/conduit/conduit-client.d.ts.map +1 -0
- package/dist/conduit/conduit-client.js +94 -0
- package/dist/conduit/conduit-client.js.map +1 -0
- package/dist/conduit/factory.d.ts +15 -0
- package/dist/conduit/factory.d.ts.map +1 -0
- package/dist/conduit/factory.js +35 -0
- package/dist/conduit/factory.js.map +1 -0
- package/dist/conduit/http-transport.d.ts +44 -0
- package/dist/conduit/http-transport.d.ts.map +1 -0
- package/dist/conduit/http-transport.js +165 -0
- package/dist/conduit/http-transport.js.map +1 -0
- package/dist/conduit/index.d.ts +15 -0
- package/dist/conduit/index.d.ts.map +1 -0
- package/dist/conduit/index.js +12 -0
- package/dist/conduit/index.js.map +1 -0
- package/dist/conduit/local-transport.d.ts +91 -0
- package/dist/conduit/local-transport.d.ts.map +1 -0
- package/dist/conduit/sse-transport.d.ts +68 -0
- package/dist/conduit/sse-transport.d.ts.map +1 -0
- package/dist/config.js +4 -3
- package/dist/config.js.map +1 -1
- package/dist/crypto/credentials.d.ts +40 -0
- package/dist/crypto/credentials.d.ts.map +1 -0
- package/dist/crypto/credentials.js +144 -0
- package/dist/crypto/credentials.js.map +1 -0
- package/dist/engine-result.d.ts +1 -1
- package/dist/engine-result.d.ts.map +1 -1
- package/dist/error-catalog.d.ts +1 -1
- package/dist/error-catalog.d.ts.map +1 -1
- package/dist/error-registry.d.ts +1 -1
- package/dist/error-registry.d.ts.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/hooks/handlers/agent-hooks.d.ts.map +1 -1
- package/dist/hooks/handlers/agent-hooks.js +106 -0
- package/dist/hooks/handlers/agent-hooks.js.map +1 -0
- package/dist/hooks/handlers/context-hooks.d.ts.map +1 -1
- package/dist/hooks/handlers/context-hooks.js +111 -0
- package/dist/hooks/handlers/context-hooks.js.map +1 -0
- package/dist/hooks/handlers/error-hooks.d.ts +14 -5
- package/dist/hooks/handlers/error-hooks.d.ts.map +1 -1
- package/dist/hooks/handlers/error-hooks.js +15 -6
- package/dist/hooks/handlers/error-hooks.js.map +1 -1
- package/dist/hooks/handlers/file-hooks.d.ts.map +1 -1
- package/dist/hooks/handlers/file-hooks.js +35 -11
- package/dist/hooks/handlers/file-hooks.js.map +1 -1
- package/dist/hooks/handlers/handler-helpers.d.ts +41 -0
- package/dist/hooks/handlers/handler-helpers.d.ts.map +1 -0
- package/dist/hooks/handlers/handler-helpers.js +61 -0
- package/dist/hooks/handlers/handler-helpers.js.map +1 -0
- package/dist/hooks/handlers/index.js +10 -1
- package/dist/hooks/handlers/index.js.map +1 -1
- package/dist/hooks/handlers/mcp-hooks.d.ts.map +1 -1
- package/dist/hooks/handlers/mcp-hooks.js +88 -21
- package/dist/hooks/handlers/mcp-hooks.js.map +1 -1
- package/dist/hooks/handlers/session-hooks.d.ts.map +1 -1
- package/dist/hooks/handlers/session-hooks.js +5 -10
- package/dist/hooks/handlers/session-hooks.js.map +1 -1
- package/dist/hooks/handlers/task-hooks.d.ts.map +1 -1
- package/dist/hooks/handlers/task-hooks.js +5 -10
- package/dist/hooks/handlers/task-hooks.js.map +1 -1
- package/dist/hooks/handlers/work-capture-hooks.d.ts.map +1 -1
- package/dist/hooks/handlers/work-capture-hooks.js +165 -0
- package/dist/hooks/handlers/work-capture-hooks.js.map +1 -0
- package/dist/hooks/payload-schemas.js +83 -26
- package/dist/hooks/payload-schemas.js.map +1 -1
- package/dist/hooks/provider-hooks.js +37 -5
- package/dist/hooks/provider-hooks.js.map +1 -1
- package/dist/hooks/registry.js +76 -23
- package/dist/hooks/registry.js.map +1 -1
- package/dist/hooks/types.js +17 -13
- package/dist/hooks/types.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6452 -3371
- package/dist/index.js.map +4 -4
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +12 -0
- package/dist/init.js.map +1 -1
- package/dist/internal.d.ts +11 -1
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +10 -0
- package/dist/internal.js.map +1 -1
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/tree-sitter-languages.d.ts +29 -0
- package/dist/lib/tree-sitter-languages.d.ts.map +1 -0
- package/dist/memory/brain-links.d.ts.map +1 -1
- package/dist/memory/brain-maintenance.d.ts +13 -0
- package/dist/memory/brain-maintenance.d.ts.map +1 -1
- package/dist/memory/brain-retrieval.d.ts +3 -0
- package/dist/memory/brain-retrieval.d.ts.map +1 -1
- package/dist/memory/brain-retrieval.js +5 -0
- package/dist/memory/brain-retrieval.js.map +1 -1
- package/dist/memory/decisions.d.ts.map +1 -1
- package/dist/mvi-helpers.d.ts +52 -0
- package/dist/mvi-helpers.d.ts.map +1 -0
- package/dist/mvi-helpers.js +74 -0
- package/dist/mvi-helpers.js.map +1 -0
- package/dist/nexus/index.js +2 -0
- package/dist/nexus/index.js.map +1 -1
- package/dist/nexus/workspace.d.ts.map +1 -1
- package/dist/nexus/workspace.js +355 -0
- package/dist/nexus/workspace.js.map +1 -0
- package/dist/orchestration/hierarchy.d.ts +32 -0
- package/dist/orchestration/hierarchy.d.ts.map +1 -0
- package/dist/orchestration/index.d.ts +1 -0
- package/dist/orchestration/index.d.ts.map +1 -1
- package/dist/output.d.ts +2 -2
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +40 -8
- package/dist/output.js.map +1 -1
- package/dist/pagination.d.ts +1 -1
- package/dist/pagination.d.ts.map +1 -1
- package/dist/sessions/find.d.ts +3 -0
- package/dist/sessions/find.d.ts.map +1 -1
- package/dist/sessions/find.js +3 -1
- package/dist/sessions/find.js.map +1 -1
- package/dist/sessions/index.d.ts.map +1 -1
- package/dist/sessions/index.js +11 -4
- package/dist/sessions/index.js.map +1 -1
- package/dist/sessions/snapshot.js +213 -0
- package/dist/sessions/snapshot.js.map +1 -0
- package/dist/store/agent-registry-accessor.d.ts +31 -0
- package/dist/store/agent-registry-accessor.d.ts.map +1 -0
- package/dist/store/agent-registry-accessor.js +169 -0
- package/dist/store/agent-registry-accessor.js.map +1 -0
- package/dist/store/converters.d.ts.map +1 -1
- package/dist/store/converters.js +2 -0
- package/dist/store/converters.js.map +1 -1
- package/dist/store/cross-db-cleanup.d.ts +34 -0
- package/dist/store/cross-db-cleanup.d.ts.map +1 -1
- package/dist/store/db-helpers.d.ts.map +1 -1
- package/dist/store/db-helpers.js +1 -0
- package/dist/store/db-helpers.js.map +1 -1
- package/dist/store/json.js +2 -2
- package/dist/store/safety-data-accessor.d.ts +7 -0
- package/dist/store/safety-data-accessor.d.ts.map +1 -1
- package/dist/store/safety-data-accessor.js +14 -0
- package/dist/store/safety-data-accessor.js.map +1 -1
- package/dist/store/signaldock-sqlite.d.ts +48 -0
- package/dist/store/signaldock-sqlite.d.ts.map +1 -0
- package/dist/store/signaldock-sqlite.js +178 -0
- package/dist/store/signaldock-sqlite.js.map +1 -0
- package/dist/store/sqlite-data-accessor.d.ts.map +1 -1
- package/dist/store/sqlite-data-accessor.js +50 -0
- package/dist/store/sqlite-data-accessor.js.map +1 -1
- package/dist/store/sqlite.d.ts.map +1 -1
- package/dist/store/sqlite.js +30 -1
- package/dist/store/sqlite.js.map +1 -1
- package/dist/store/task-store.d.ts.map +1 -1
- package/dist/store/task-store.js +2 -0
- package/dist/store/task-store.js.map +1 -1
- package/dist/store/tasks-schema.d.ts +16 -0
- package/dist/store/tasks-schema.d.ts.map +1 -1
- package/dist/store/tasks-schema.js +33 -0
- package/dist/store/tasks-schema.js.map +1 -1
- package/dist/store/validation-schemas.d.ts +32 -0
- package/dist/store/validation-schemas.d.ts.map +1 -1
- package/dist/system/health.d.ts +1 -1
- package/dist/system/health.d.ts.map +1 -1
- package/dist/system/health.js +35 -0
- package/dist/system/health.js.map +1 -1
- package/dist/task-work/index.d.ts.map +1 -1
- package/dist/task-work/index.js +8 -4
- package/dist/task-work/index.js.map +1 -1
- package/dist/tasks/complete.js +5 -2
- package/dist/tasks/complete.js.map +1 -1
- package/dist/tasks/find.d.ts +3 -0
- package/dist/tasks/find.d.ts.map +1 -1
- package/dist/tasks/find.js +7 -1
- package/dist/tasks/find.js.map +1 -1
- package/dist/tasks/list.d.ts +5 -2
- package/dist/tasks/list.d.ts.map +1 -1
- package/dist/tasks/list.js +9 -2
- package/dist/tasks/list.js.map +1 -1
- package/dist/tasks/show.d.ts +3 -0
- package/dist/tasks/show.d.ts.map +1 -1
- package/dist/tasks/show.js +2 -0
- package/dist/tasks/show.js.map +1 -1
- package/dist/upgrade.d.ts.map +1 -1
- package/dist/upgrade.js +15 -0
- package/dist/upgrade.js.map +1 -1
- package/migrations/drizzle-tasks/20260324000000_assignee-column/migration.sql +6 -0
- package/migrations/drizzle-tasks/20260324000000_assignee-column/snapshot.json +9 -0
- package/migrations/drizzle-tasks/20260327000000_agent-credentials/migration.sql +23 -0
- package/package.json +17 -7
- package/src/__tests__/cli-parity.test.js +11 -1
- package/src/__tests__/cli-parity.test.js.map +1 -1
- package/src/__tests__/cli-parity.test.ts +17 -1
- package/src/__tests__/human-output.test.js +11 -1
- package/src/__tests__/human-output.test.js.map +1 -1
- package/src/__tests__/human-output.test.ts +18 -1
- package/src/__tests__/injection-chain.test.js +3 -2
- package/src/__tests__/injection-chain.test.js.map +1 -1
- package/src/__tests__/injection-mvi-tiers.test.d.ts +2 -2
- package/src/__tests__/injection-mvi-tiers.test.js +15 -15
- package/src/__tests__/injection-mvi-tiers.test.js.map +1 -1
- package/src/__tests__/lafs-conformance.test.d.ts +1 -1
- package/src/__tests__/lafs-conformance.test.js +2 -2
- package/src/__tests__/sharing.test.js +19 -0
- package/src/__tests__/sharing.test.js.map +1 -1
- package/src/agents/__tests__/agent-registry.test.d.ts +12 -0
- package/src/agents/__tests__/agent-registry.test.d.ts.map +1 -0
- package/src/agents/__tests__/agent-registry.test.js +262 -0
- package/src/agents/__tests__/agent-registry.test.js.map +1 -0
- package/src/agents/__tests__/execution-learning.test.d.ts +14 -0
- package/src/agents/__tests__/execution-learning.test.d.ts.map +1 -0
- package/src/agents/__tests__/execution-learning.test.js +533 -0
- package/src/agents/__tests__/execution-learning.test.js.map +1 -0
- package/src/agents/__tests__/health-monitor.test.d.ts +10 -0
- package/src/agents/__tests__/health-monitor.test.d.ts.map +1 -0
- package/src/agents/__tests__/health-monitor.test.js +259 -0
- package/src/agents/__tests__/health-monitor.test.js.map +1 -0
- package/src/agents/__tests__/registry.test.js +27 -2
- package/src/agents/__tests__/registry.test.js.map +1 -1
- package/src/agents/agent-schema.ts +2 -5
- package/src/cant/__tests__/cant-agent-parse.test.ts +94 -0
- package/src/cant/approval.ts +218 -0
- package/src/cant/context-builder.ts +135 -0
- package/src/cant/discretion.ts +149 -0
- package/src/cant/index.ts +58 -0
- package/src/cant/parallel-runner.ts +205 -0
- package/src/cant/types.ts +158 -0
- package/src/cant/workflow-executor.ts +618 -0
- package/src/code/index.ts +10 -0
- package/src/code/outline.ts +214 -0
- package/src/code/parser.ts +299 -0
- package/src/code/search.ts +173 -0
- package/src/code/unfold.ts +204 -0
- package/src/conduit/__tests__/dual-api-e2e.test.ts +212 -0
- package/src/conduit/__tests__/local-credential-flow.test.ts +230 -0
- package/src/conduit/__tests__/local-transport.test.ts +320 -0
- package/src/conduit/__tests__/sse-transport.test.ts +344 -0
- package/src/conduit/conduit-client.ts +123 -0
- package/src/conduit/factory.ts +49 -0
- package/src/conduit/http-transport.ts +201 -0
- package/src/conduit/index.ts +15 -0
- package/src/conduit/local-transport.ts +309 -0
- package/src/conduit/sse-transport.ts +382 -0
- package/src/crypto/credentials.ts +166 -0
- package/src/engine-result.ts +1 -1
- package/src/error-catalog.ts +1 -1
- package/src/error-registry.ts +1 -1
- package/src/errors.ts +1 -1
- package/src/hooks/handlers/__tests__/hook-automation-e2e.test.d.ts +13 -0
- package/src/hooks/handlers/__tests__/hook-automation-e2e.test.d.ts.map +1 -0
- package/src/hooks/handlers/__tests__/hook-automation-e2e.test.js +501 -0
- package/src/hooks/handlers/__tests__/hook-automation-e2e.test.js.map +1 -0
- package/src/hooks/handlers/agent-hooks.ts +1 -30
- package/src/hooks/handlers/context-hooks.ts +1 -30
- package/src/hooks/handlers/error-hooks.ts +14 -5
- package/src/hooks/handlers/file-hooks.ts +1 -6
- package/src/hooks/handlers/handler-helpers.ts +62 -0
- package/src/hooks/handlers/mcp-hooks.ts +2 -14
- package/src/hooks/handlers/session-hooks.ts +1 -6
- package/src/hooks/handlers/task-hooks.ts +1 -6
- package/src/hooks/handlers/work-capture-hooks.ts +1 -10
- package/src/index.ts +12 -1
- package/src/init.ts +12 -0
- package/src/intelligence/__tests__/adaptive-validation.test.d.ts +11 -0
- package/src/intelligence/__tests__/adaptive-validation.test.d.ts.map +1 -0
- package/src/intelligence/__tests__/adaptive-validation.test.js +517 -0
- package/src/intelligence/__tests__/adaptive-validation.test.js.map +1 -0
- package/src/intelligence/__tests__/impact.test.d.ts +1 -0
- package/src/intelligence/__tests__/impact.test.d.ts.map +1 -1
- package/src/intelligence/__tests__/impact.test.js +132 -1
- package/src/intelligence/__tests__/impact.test.js.map +1 -1
- package/src/internal.ts +22 -0
- package/src/lib/__tests__/retry.test.d.ts +7 -0
- package/src/lib/__tests__/retry.test.d.ts.map +1 -0
- package/src/lib/__tests__/retry.test.js +225 -0
- package/src/lib/__tests__/retry.test.js.map +1 -0
- package/src/lib/index.ts +8 -0
- package/src/lib/tree-sitter-languages.ts +88 -0
- package/src/lifecycle/__tests__/chain-store.test.js +6 -0
- package/src/lifecycle/__tests__/chain-store.test.js.map +1 -1
- package/src/lifecycle/__tests__/tessera-engine.test.js +52 -0
- package/src/lifecycle/__tests__/tessera-engine.test.js.map +1 -1
- package/src/memory/__tests__/brain-automation.test.d.ts +11 -0
- package/src/memory/__tests__/brain-automation.test.d.ts.map +1 -0
- package/src/memory/__tests__/brain-automation.test.js +730 -0
- package/src/memory/__tests__/brain-automation.test.js.map +1 -0
- package/src/memory/__tests__/brain-links.test.ts +14 -0
- package/src/memory/__tests__/brain-retrieval.test.ts +10 -0
- package/src/memory/__tests__/session-memory.test.ts +17 -0
- package/src/memory/brain-links.ts +17 -0
- package/src/memory/brain-maintenance.ts +33 -1
- package/src/memory/brain-retrieval.ts +27 -2
- package/src/memory/decisions.ts +18 -2
- package/src/mvi-helpers.ts +81 -0
- package/src/nexus/workspace.ts +19 -7
- package/src/orchestration/hierarchy.ts +202 -0
- package/src/orchestration/index.ts +1 -0
- package/src/output.ts +43 -10
- package/src/pagination.ts +1 -1
- package/src/sessions/__tests__/session-edge-cases.test.js +20 -1
- package/src/sessions/__tests__/session-edge-cases.test.js.map +1 -1
- package/src/sessions/__tests__/session-find.test.js +1 -1
- package/src/sessions/__tests__/session-find.test.js.map +1 -1
- package/src/sessions/__tests__/session-find.test.ts +1 -1
- package/src/sessions/find.ts +6 -1
- package/src/sessions/index.ts +9 -0
- package/src/store/__tests__/migration-safety.test.js +3 -0
- package/src/store/__tests__/migration-safety.test.js.map +1 -1
- package/src/store/__tests__/session-store.test.js +128 -1
- package/src/store/__tests__/session-store.test.js.map +1 -1
- package/src/store/__tests__/task-store.test.js +18 -1
- package/src/store/__tests__/task-store.test.js.map +1 -1
- package/src/store/__tests__/test-db-helper.d.ts.map +1 -1
- package/src/store/__tests__/test-db-helper.js +12 -0
- package/src/store/__tests__/test-db-helper.js.map +1 -1
- package/src/store/agent-registry-accessor.ts +375 -0
- package/src/store/converters.ts +2 -0
- package/src/store/cross-db-cleanup.ts +175 -1
- package/src/store/db-helpers.ts +1 -0
- package/src/store/safety-data-accessor.ts +23 -0
- package/src/store/signaldock-sqlite.ts +429 -0
- package/src/store/sqlite-data-accessor.ts +72 -0
- package/src/store/sqlite.ts +4 -1
- package/src/store/task-store.ts +9 -1
- package/src/store/tasks-schema.ts +7 -0
- package/src/system/__tests__/health.test.ts +2 -2
- package/src/system/health.ts +54 -2
- package/src/task-work/index.ts +5 -0
- package/src/tasks/__tests__/add.test.js +19 -1
- package/src/tasks/__tests__/add.test.js.map +1 -1
- package/src/tasks/__tests__/assignee.test.d.ts +14 -0
- package/src/tasks/__tests__/assignee.test.d.ts.map +1 -0
- package/src/tasks/__tests__/assignee.test.js +125 -0
- package/src/tasks/__tests__/assignee.test.js.map +1 -0
- package/src/tasks/__tests__/assignee.test.ts +162 -0
- package/src/tasks/__tests__/complete-unblocks.test.js +13 -1
- package/src/tasks/__tests__/complete-unblocks.test.js.map +1 -1
- package/src/tasks/__tests__/complete.test.js +28 -7
- package/src/tasks/__tests__/complete.test.js.map +1 -1
- package/src/tasks/__tests__/epic-enforcement.test.d.ts +15 -0
- package/src/tasks/__tests__/epic-enforcement.test.d.ts.map +1 -0
- package/src/tasks/__tests__/epic-enforcement.test.js +669 -0
- package/src/tasks/__tests__/epic-enforcement.test.js.map +1 -0
- package/src/tasks/__tests__/hierarchy-policy.test.js +5 -0
- package/src/tasks/__tests__/hierarchy-policy.test.js.map +1 -1
- package/src/tasks/__tests__/minimal-test.test.d.ts +2 -0
- package/src/tasks/__tests__/minimal-test.test.d.ts.map +1 -0
- package/src/tasks/__tests__/minimal-test.test.js +25 -0
- package/src/tasks/__tests__/minimal-test.test.js.map +1 -0
- package/src/tasks/__tests__/pipeline-stage.test.d.ts +14 -0
- package/src/tasks/__tests__/pipeline-stage.test.d.ts.map +1 -0
- package/src/tasks/__tests__/pipeline-stage.test.js +277 -0
- package/src/tasks/__tests__/pipeline-stage.test.js.map +1 -0
- package/src/tasks/__tests__/update.test.js +43 -6
- package/src/tasks/__tests__/update.test.js.map +1 -1
- package/src/tasks/find.ts +11 -1
- package/src/tasks/list.ts +14 -3
- package/src/tasks/show.ts +6 -0
- package/src/upgrade.ts +16 -0
- package/dist/tasks/reparent.d.ts +0 -38
- package/dist/tasks/reparent.d.ts.map +0 -1
- package/dist/ui/injection-legacy.d.ts +0 -26
- package/dist/ui/injection-legacy.d.ts.map +0 -1
- package/dist/ui/injection-legacy.js +0 -42
- package/dist/ui/injection-legacy.js.map +0 -1
- package/src/signaldock/__tests__/claude-code-transport.test.d.ts +0 -7
- package/src/signaldock/__tests__/claude-code-transport.test.d.ts.map +0 -1
- package/src/signaldock/__tests__/claude-code-transport.test.js +0 -147
- package/src/signaldock/__tests__/claude-code-transport.test.js.map +0 -1
- package/src/signaldock/__tests__/claude-code-transport.test.ts +0 -180
- package/src/signaldock/__tests__/factory.test.d.ts +0 -7
- package/src/signaldock/__tests__/factory.test.d.ts.map +0 -1
- package/src/signaldock/__tests__/factory.test.js +0 -55
- package/src/signaldock/__tests__/factory.test.js.map +0 -1
- package/src/signaldock/__tests__/factory.test.ts +0 -61
- package/src/signaldock/__tests__/signaldock-transport.test.d.ts +0 -9
- package/src/signaldock/__tests__/signaldock-transport.test.d.ts.map +0 -1
- package/src/signaldock/__tests__/signaldock-transport.test.js +0 -321
- package/src/signaldock/__tests__/signaldock-transport.test.js.map +0 -1
- package/src/signaldock/__tests__/signaldock-transport.test.ts +0 -421
- package/src/signaldock/claude-code-transport.ts +0 -137
- package/src/signaldock/factory.ts +0 -39
- package/src/signaldock/index.ts +0 -28
- package/src/signaldock/signaldock-transport.ts +0 -194
- package/src/signaldock/transport.ts +0 -78
- package/src/signaldock/types.ts +0 -100
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* smart_outline — file structural skeleton via tree-sitter.
|
|
3
|
+
*
|
|
4
|
+
* Parses a source file and returns all top-level and nested symbols
|
|
5
|
+
* with signatures only (bodies collapsed). This gives agents a ~1-2K
|
|
6
|
+
* token overview of a file vs ~12K for a full Read.
|
|
7
|
+
*
|
|
8
|
+
* @task T151
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { readFileSync } from 'node:fs';
|
|
12
|
+
import { relative } from 'node:path';
|
|
13
|
+
import type { CodeSymbol } from '@cleocode/contracts';
|
|
14
|
+
import { detectLanguage } from '../lib/tree-sitter-languages.js';
|
|
15
|
+
import { parseFile } from './parser.js';
|
|
16
|
+
|
|
17
|
+
/** A symbol node in the outline tree, with optional children. */
|
|
18
|
+
export interface OutlineNode {
|
|
19
|
+
/** Symbol name. */
|
|
20
|
+
name: string;
|
|
21
|
+
/** Symbol kind (function, class, method, etc.). */
|
|
22
|
+
kind: string;
|
|
23
|
+
/** Start line (1-based). */
|
|
24
|
+
startLine: number;
|
|
25
|
+
/** End line (1-based). */
|
|
26
|
+
endLine: number;
|
|
27
|
+
/** Signature line(s) — the declaration without the body. */
|
|
28
|
+
signature: string;
|
|
29
|
+
/** Whether this symbol is exported. */
|
|
30
|
+
exported: boolean;
|
|
31
|
+
/** Nested symbols (methods inside classes, etc.). */
|
|
32
|
+
children: OutlineNode[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Result of generating a smart outline for a file. */
|
|
36
|
+
export interface SmartOutlineResult {
|
|
37
|
+
/** Source file path (relative). */
|
|
38
|
+
filePath: string;
|
|
39
|
+
/** Detected language. */
|
|
40
|
+
language: string;
|
|
41
|
+
/** Top-level symbol tree. */
|
|
42
|
+
symbols: OutlineNode[];
|
|
43
|
+
/** Estimated token count for this outline. */
|
|
44
|
+
estimatedTokens: number;
|
|
45
|
+
/** Parse errors (non-fatal). */
|
|
46
|
+
errors: string[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Extract the signature line(s) from source for a symbol.
|
|
51
|
+
*
|
|
52
|
+
* Reads the opening lines of the symbol declaration up to the first
|
|
53
|
+
* opening brace or colon (for Python). This gives context without
|
|
54
|
+
* the full implementation body.
|
|
55
|
+
*/
|
|
56
|
+
function extractSignature(
|
|
57
|
+
lines: string[],
|
|
58
|
+
startLine: number,
|
|
59
|
+
endLine: number,
|
|
60
|
+
language: string,
|
|
61
|
+
): string {
|
|
62
|
+
const start = Math.max(0, startLine - 1); // 1-based to 0-based
|
|
63
|
+
const end = Math.min(lines.length, endLine);
|
|
64
|
+
|
|
65
|
+
// For short symbols (< 3 lines), return the whole thing
|
|
66
|
+
if (end - start <= 3) {
|
|
67
|
+
return lines.slice(start, end).join('\n');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Find the signature boundary (opening brace, colon for Python)
|
|
71
|
+
const sigLines: string[] = [];
|
|
72
|
+
for (let i = start; i < end && sigLines.length < 5; i++) {
|
|
73
|
+
const line = lines[i]!;
|
|
74
|
+
sigLines.push(line);
|
|
75
|
+
|
|
76
|
+
if (language === 'python') {
|
|
77
|
+
if (line.trimEnd().endsWith(':')) break;
|
|
78
|
+
} else {
|
|
79
|
+
if (line.includes('{')) break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const bodyLines = end - start - sigLines.length;
|
|
84
|
+
if (bodyLines > 0) {
|
|
85
|
+
sigLines.push(` // ... ${bodyLines} lines`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return sigLines.join('\n');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Build a tree from flat symbols by nesting methods inside their parent classes.
|
|
93
|
+
*/
|
|
94
|
+
function buildTree(symbols: CodeSymbol[], lines: string[], language: string): OutlineNode[] {
|
|
95
|
+
const nodes: OutlineNode[] = [];
|
|
96
|
+
const classNodes = new Map<string, OutlineNode>();
|
|
97
|
+
|
|
98
|
+
// First pass: create all nodes
|
|
99
|
+
for (const sym of symbols) {
|
|
100
|
+
const node: OutlineNode = {
|
|
101
|
+
name: sym.name,
|
|
102
|
+
kind: sym.kind,
|
|
103
|
+
startLine: sym.startLine,
|
|
104
|
+
endLine: sym.endLine,
|
|
105
|
+
signature: extractSignature(lines, sym.startLine, sym.endLine, language),
|
|
106
|
+
exported: sym.exported ?? false,
|
|
107
|
+
children: [],
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
if (
|
|
111
|
+
sym.kind === 'class' ||
|
|
112
|
+
sym.kind === 'struct' ||
|
|
113
|
+
sym.kind === 'impl' ||
|
|
114
|
+
sym.kind === 'trait'
|
|
115
|
+
) {
|
|
116
|
+
classNodes.set(sym.name, node);
|
|
117
|
+
nodes.push(node);
|
|
118
|
+
} else if (sym.kind === 'method' && sym.parent) {
|
|
119
|
+
const parent = classNodes.get(sym.parent);
|
|
120
|
+
if (parent) {
|
|
121
|
+
parent.children.push(node);
|
|
122
|
+
} else {
|
|
123
|
+
nodes.push(node);
|
|
124
|
+
}
|
|
125
|
+
} else {
|
|
126
|
+
// Check if this symbol is nested inside a class by line range
|
|
127
|
+
let nested = false;
|
|
128
|
+
for (const [, classNode] of classNodes) {
|
|
129
|
+
if (sym.startLine >= classNode.startLine && sym.endLine <= classNode.endLine) {
|
|
130
|
+
classNode.children.push(node);
|
|
131
|
+
nested = true;
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (!nested) nodes.push(node);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return nodes;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Estimate token count for an outline (rough: ~4 chars per token). */
|
|
143
|
+
function estimateTokens(nodes: OutlineNode[]): number {
|
|
144
|
+
let chars = 0;
|
|
145
|
+
for (const node of nodes) {
|
|
146
|
+
chars += node.signature.length + node.name.length + node.kind.length + 20; // overhead
|
|
147
|
+
chars += estimateTokens(node.children);
|
|
148
|
+
}
|
|
149
|
+
return Math.ceil(chars / 4);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Generate a smart outline for a source file.
|
|
154
|
+
*
|
|
155
|
+
* Returns a tree of symbols with signatures only (bodies collapsed),
|
|
156
|
+
* suitable for giving agents a quick structural overview.
|
|
157
|
+
*
|
|
158
|
+
* @param filePath - Absolute path to source file
|
|
159
|
+
* @param projectRoot - Project root for relative path computation
|
|
160
|
+
* @returns Smart outline result with symbol tree and token estimate
|
|
161
|
+
*/
|
|
162
|
+
export function smartOutline(filePath: string, projectRoot?: string): SmartOutlineResult {
|
|
163
|
+
const root = projectRoot ?? process.cwd();
|
|
164
|
+
const relPath = relative(root, filePath);
|
|
165
|
+
const language = detectLanguage(filePath);
|
|
166
|
+
|
|
167
|
+
if (!language) {
|
|
168
|
+
return {
|
|
169
|
+
filePath: relPath,
|
|
170
|
+
language: 'unknown',
|
|
171
|
+
symbols: [],
|
|
172
|
+
estimatedTokens: 0,
|
|
173
|
+
errors: ['Unsupported language'],
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Parse the file for symbols
|
|
178
|
+
const parseResult = parseFile(filePath, root);
|
|
179
|
+
if (parseResult.errors.length > 0 && parseResult.symbols.length === 0) {
|
|
180
|
+
return {
|
|
181
|
+
filePath: relPath,
|
|
182
|
+
language,
|
|
183
|
+
symbols: [],
|
|
184
|
+
estimatedTokens: 0,
|
|
185
|
+
errors: parseResult.errors,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Read source lines for signature extraction
|
|
190
|
+
let lines: string[];
|
|
191
|
+
try {
|
|
192
|
+
lines = readFileSync(filePath, 'utf-8').split('\n');
|
|
193
|
+
} catch (err) {
|
|
194
|
+
return {
|
|
195
|
+
filePath: relPath,
|
|
196
|
+
language,
|
|
197
|
+
symbols: [],
|
|
198
|
+
estimatedTokens: 0,
|
|
199
|
+
errors: [`Failed to read file: ${err instanceof Error ? err.message : String(err)}`],
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Build symbol tree with signatures
|
|
204
|
+
const tree = buildTree(parseResult.symbols, lines, language);
|
|
205
|
+
const estimatedTokens = estimateTokens(tree);
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
filePath: relPath,
|
|
209
|
+
language,
|
|
210
|
+
symbols: tree,
|
|
211
|
+
estimatedTokens,
|
|
212
|
+
errors: parseResult.errors,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tree-sitter AST parser — query execution engine for code analysis.
|
|
3
|
+
*
|
|
4
|
+
* Resolves grammar paths from node_modules, writes S-expression query
|
|
5
|
+
* patterns to temp files, executes tree-sitter CLI, and parses output
|
|
6
|
+
* into structured CodeSymbol objects.
|
|
7
|
+
*
|
|
8
|
+
* @task T149
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { execFileSync } from 'node:child_process';
|
|
12
|
+
import { existsSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
13
|
+
import { tmpdir } from 'node:os';
|
|
14
|
+
import { join, relative } from 'node:path';
|
|
15
|
+
import type {
|
|
16
|
+
BatchParseResult,
|
|
17
|
+
CodeSymbol,
|
|
18
|
+
CodeSymbolKind,
|
|
19
|
+
ParseResult,
|
|
20
|
+
} from '@cleocode/contracts';
|
|
21
|
+
import { detectLanguage, type TreeSitterLanguage } from '../lib/tree-sitter-languages.js';
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Tree-sitter CLI resolution
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
/** Resolve the tree-sitter CLI binary from node_modules. */
|
|
28
|
+
function resolveTreeSitterBin(): string {
|
|
29
|
+
const candidates = [
|
|
30
|
+
join(process.cwd(), 'packages', 'core', 'node_modules', '.bin', 'tree-sitter'),
|
|
31
|
+
join(process.cwd(), 'node_modules', '.bin', 'tree-sitter'),
|
|
32
|
+
];
|
|
33
|
+
for (const p of candidates) {
|
|
34
|
+
if (existsSync(p)) return p;
|
|
35
|
+
}
|
|
36
|
+
throw new Error('tree-sitter CLI not found. Run: pnpm add -F @cleocode/core tree-sitter-cli');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// S-expression query patterns per language family
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
/** Query patterns that match function/method/class/type declarations. */
|
|
44
|
+
const QUERY_PATTERNS: Record<string, string> = {
|
|
45
|
+
// TypeScript / JavaScript
|
|
46
|
+
typescript: `
|
|
47
|
+
(function_declaration name: (identifier) @name) @definition.function
|
|
48
|
+
(method_definition name: (property_identifier) @name) @definition.method
|
|
49
|
+
(class_declaration name: (type_identifier) @name) @definition.class
|
|
50
|
+
(interface_declaration name: (type_identifier) @name) @definition.interface
|
|
51
|
+
(type_alias_declaration name: (type_identifier) @name) @definition.type
|
|
52
|
+
(enum_declaration name: (identifier) @name) @definition.enum
|
|
53
|
+
(lexical_declaration (variable_declarator name: (identifier) @name)) @definition.variable
|
|
54
|
+
(export_statement (function_declaration name: (identifier) @name)) @definition.function
|
|
55
|
+
(export_statement (class_declaration name: (type_identifier) @name)) @definition.class
|
|
56
|
+
(arrow_function) @definition.function
|
|
57
|
+
`,
|
|
58
|
+
javascript: `
|
|
59
|
+
(function_declaration name: (identifier) @name) @definition.function
|
|
60
|
+
(method_definition name: (property_identifier) @name) @definition.method
|
|
61
|
+
(class_declaration name: (identifier) @name) @definition.class
|
|
62
|
+
(lexical_declaration (variable_declarator name: (identifier) @name)) @definition.variable
|
|
63
|
+
(export_statement (function_declaration name: (identifier) @name)) @definition.function
|
|
64
|
+
(export_statement (class_declaration name: (identifier) @name)) @definition.class
|
|
65
|
+
(arrow_function) @definition.function
|
|
66
|
+
`,
|
|
67
|
+
// Python
|
|
68
|
+
python: `
|
|
69
|
+
(function_definition name: (identifier) @name) @definition.function
|
|
70
|
+
(class_definition name: (identifier) @name) @definition.class
|
|
71
|
+
(decorated_definition (function_definition name: (identifier) @name)) @definition.function
|
|
72
|
+
(decorated_definition (class_definition name: (identifier) @name)) @definition.class
|
|
73
|
+
`,
|
|
74
|
+
// Go
|
|
75
|
+
go: `
|
|
76
|
+
(function_declaration name: (identifier) @name) @definition.function
|
|
77
|
+
(method_declaration name: (field_identifier) @name) @definition.method
|
|
78
|
+
(type_declaration (type_spec name: (type_identifier) @name)) @definition.type
|
|
79
|
+
`,
|
|
80
|
+
// Rust
|
|
81
|
+
rust: `
|
|
82
|
+
(function_item name: (identifier) @name) @definition.function
|
|
83
|
+
(impl_item type: (type_identifier) @name) @definition.impl
|
|
84
|
+
(struct_item name: (type_identifier) @name) @definition.struct
|
|
85
|
+
(enum_item name: (type_identifier) @name) @definition.enum
|
|
86
|
+
(trait_item name: (type_identifier) @name) @definition.trait
|
|
87
|
+
(type_item name: (type_identifier) @name) @definition.type
|
|
88
|
+
(mod_item name: (identifier) @name) @definition.module
|
|
89
|
+
`,
|
|
90
|
+
// Ruby
|
|
91
|
+
ruby: `
|
|
92
|
+
(method name: (identifier) @name) @definition.method
|
|
93
|
+
(class name: (constant) @name) @definition.class
|
|
94
|
+
(module name: (constant) @name) @definition.module
|
|
95
|
+
(singleton_method name: (identifier) @name) @definition.method
|
|
96
|
+
`,
|
|
97
|
+
// Java / C / C++
|
|
98
|
+
java: `
|
|
99
|
+
(method_declaration name: (identifier) @name) @definition.method
|
|
100
|
+
(class_declaration name: (identifier) @name) @definition.class
|
|
101
|
+
(interface_declaration name: (identifier) @name) @definition.interface
|
|
102
|
+
(enum_declaration name: (identifier) @name) @definition.enum
|
|
103
|
+
`,
|
|
104
|
+
c: `
|
|
105
|
+
(function_definition declarator: (function_declarator declarator: (identifier) @name)) @definition.function
|
|
106
|
+
(struct_specifier name: (type_identifier) @name) @definition.struct
|
|
107
|
+
(enum_specifier name: (type_identifier) @name) @definition.enum
|
|
108
|
+
(type_definition declarator: (type_identifier) @name) @definition.type
|
|
109
|
+
`,
|
|
110
|
+
cpp: `
|
|
111
|
+
(function_definition declarator: (function_declarator declarator: (qualified_identifier) @name)) @definition.function
|
|
112
|
+
(function_definition declarator: (function_declarator declarator: (identifier) @name)) @definition.function
|
|
113
|
+
(class_specifier name: (type_identifier) @name) @definition.class
|
|
114
|
+
(struct_specifier name: (type_identifier) @name) @definition.struct
|
|
115
|
+
(enum_specifier name: (type_identifier) @name) @definition.enum
|
|
116
|
+
(namespace_definition name: (identifier) @name) @definition.module
|
|
117
|
+
`,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/** Map language to query pattern key. */
|
|
121
|
+
function queryKeyForLanguage(lang: TreeSitterLanguage): string {
|
|
122
|
+
if (lang === 'tsx') return 'typescript';
|
|
123
|
+
return lang;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Map capture name suffix to CodeSymbolKind. */
|
|
127
|
+
function captureToKind(capture: string): CodeSymbolKind {
|
|
128
|
+
const map: Record<string, CodeSymbolKind> = {
|
|
129
|
+
function: 'function',
|
|
130
|
+
method: 'method',
|
|
131
|
+
class: 'class',
|
|
132
|
+
interface: 'interface',
|
|
133
|
+
type: 'type',
|
|
134
|
+
enum: 'enum',
|
|
135
|
+
variable: 'variable',
|
|
136
|
+
constant: 'constant',
|
|
137
|
+
module: 'module',
|
|
138
|
+
import: 'import',
|
|
139
|
+
export: 'export',
|
|
140
|
+
struct: 'struct',
|
|
141
|
+
trait: 'trait',
|
|
142
|
+
impl: 'impl',
|
|
143
|
+
};
|
|
144
|
+
return map[capture] ?? 'function';
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
// Output parsing
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Parse tree-sitter query output into CodeSymbol objects.
|
|
153
|
+
*
|
|
154
|
+
* tree-sitter query output format (one match per block):
|
|
155
|
+
* ```
|
|
156
|
+
* path/to/file.ts
|
|
157
|
+
* pattern: 0
|
|
158
|
+
* capture: 0 - name, start: (5, 9), end: (5, 20), text: `parseFile`
|
|
159
|
+
* capture: 1 - definition.function, start: (5, 0), end: (15, 1)
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
function parseQueryOutput(output: string, filePath: string, language: string): CodeSymbol[] {
|
|
163
|
+
const symbols: CodeSymbol[] = [];
|
|
164
|
+
const lines = output.split('\n');
|
|
165
|
+
|
|
166
|
+
let currentName = '';
|
|
167
|
+
let currentKind: CodeSymbolKind = 'function';
|
|
168
|
+
let endLine = 0;
|
|
169
|
+
|
|
170
|
+
for (const line of lines) {
|
|
171
|
+
const nameMatch = line.match(
|
|
172
|
+
/capture: \d+ - name, start: \(\d+, \d+\), end: \(\d+, \d+\)(?:, text: `([^`]+)`)?/,
|
|
173
|
+
);
|
|
174
|
+
if (nameMatch) {
|
|
175
|
+
currentName = nameMatch[1] ?? '';
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const defMatch = line.match(
|
|
180
|
+
/capture: \d+ - definition\.(\w+), start: \((\d+), \d+\), end: \((\d+), \d+\)/,
|
|
181
|
+
);
|
|
182
|
+
if (defMatch && currentName) {
|
|
183
|
+
currentKind = captureToKind(defMatch[1]!);
|
|
184
|
+
const defStart = Number.parseInt(defMatch[2]!, 10) + 1;
|
|
185
|
+
endLine = Number.parseInt(defMatch[3]!, 10) + 1;
|
|
186
|
+
|
|
187
|
+
symbols.push({
|
|
188
|
+
name: currentName,
|
|
189
|
+
kind: currentKind,
|
|
190
|
+
startLine: defStart,
|
|
191
|
+
endLine,
|
|
192
|
+
filePath,
|
|
193
|
+
language,
|
|
194
|
+
});
|
|
195
|
+
currentName = '';
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return symbols;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
// Public API
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Parse a single file and extract code symbols.
|
|
208
|
+
*
|
|
209
|
+
* @param filePath - Absolute or relative path to source file
|
|
210
|
+
* @param projectRoot - Project root for relative path computation
|
|
211
|
+
* @returns Parse result with symbols and any errors
|
|
212
|
+
*/
|
|
213
|
+
export function parseFile(filePath: string, projectRoot?: string): ParseResult {
|
|
214
|
+
const root = projectRoot ?? process.cwd();
|
|
215
|
+
const relPath = relative(root, filePath);
|
|
216
|
+
const language = detectLanguage(filePath);
|
|
217
|
+
|
|
218
|
+
if (!language) {
|
|
219
|
+
return {
|
|
220
|
+
filePath: relPath,
|
|
221
|
+
language: 'unknown',
|
|
222
|
+
symbols: [],
|
|
223
|
+
errors: ['Unsupported language'],
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const queryKey = queryKeyForLanguage(language);
|
|
228
|
+
const pattern = QUERY_PATTERNS[queryKey];
|
|
229
|
+
if (!pattern) {
|
|
230
|
+
return {
|
|
231
|
+
filePath: relPath,
|
|
232
|
+
language,
|
|
233
|
+
symbols: [],
|
|
234
|
+
errors: [`No query pattern for ${language}`],
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const tmpDir = mkdtempSync(join(tmpdir(), 'cleo-ts-'));
|
|
239
|
+
const queryFile = join(tmpDir, 'query.scm');
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
writeFileSync(queryFile, pattern);
|
|
243
|
+
const bin = resolveTreeSitterBin();
|
|
244
|
+
|
|
245
|
+
const output = execFileSync(bin, ['query', queryFile, filePath], {
|
|
246
|
+
encoding: 'utf-8',
|
|
247
|
+
timeout: 30000,
|
|
248
|
+
maxBuffer: 10 * 1024 * 1024, // 10MB
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const symbols = parseQueryOutput(output, relPath, language);
|
|
252
|
+
return { filePath: relPath, language, symbols, errors: [] };
|
|
253
|
+
} catch (err) {
|
|
254
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
255
|
+
return { filePath: relPath, language, symbols: [], errors: [msg] };
|
|
256
|
+
} finally {
|
|
257
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Batch-parse multiple files, grouping by language for efficiency.
|
|
263
|
+
*
|
|
264
|
+
* Files with the same language share a single query pattern file
|
|
265
|
+
* but each file is parsed individually (tree-sitter CLI limitation).
|
|
266
|
+
*
|
|
267
|
+
* @param filePaths - Array of file paths to parse
|
|
268
|
+
* @param projectRoot - Project root for relative path computation
|
|
269
|
+
* @returns Aggregated results with per-file breakdowns
|
|
270
|
+
*/
|
|
271
|
+
export function batchParse(filePaths: string[], projectRoot?: string): BatchParseResult {
|
|
272
|
+
const root = projectRoot ?? process.cwd();
|
|
273
|
+
const results: ParseResult[] = [];
|
|
274
|
+
const skipped: string[] = [];
|
|
275
|
+
|
|
276
|
+
// Group files by language
|
|
277
|
+
const byLanguage = new Map<string, string[]>();
|
|
278
|
+
for (const fp of filePaths) {
|
|
279
|
+
const lang = detectLanguage(fp);
|
|
280
|
+
if (!lang) {
|
|
281
|
+
skipped.push(relative(root, fp));
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
const key = queryKeyForLanguage(lang);
|
|
285
|
+
const group = byLanguage.get(key) ?? [];
|
|
286
|
+
group.push(fp);
|
|
287
|
+
byLanguage.set(key, group);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Parse each group
|
|
291
|
+
for (const [_queryKey, files] of byLanguage) {
|
|
292
|
+
for (const fp of files) {
|
|
293
|
+
results.push(parseFile(fp, root));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const totalSymbols = results.reduce((sum, r) => sum + r.symbols.length, 0);
|
|
298
|
+
return { results, skipped, totalSymbols };
|
|
299
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* smart_search — cross-codebase symbol search via tree-sitter.
|
|
3
|
+
*
|
|
4
|
+
* Walks a directory tree, batch-parses code files by language, and
|
|
5
|
+
* matches symbols against a query string with relevance scoring.
|
|
6
|
+
*
|
|
7
|
+
* @task T152
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { readdirSync } from 'node:fs';
|
|
11
|
+
import { join, relative } from 'node:path';
|
|
12
|
+
import type { CodeSymbol } from '@cleocode/contracts';
|
|
13
|
+
import { detectLanguage, type TreeSitterLanguage } from '../lib/tree-sitter-languages.js';
|
|
14
|
+
import { batchParse } from './parser.js';
|
|
15
|
+
|
|
16
|
+
/** A search result with relevance score. */
|
|
17
|
+
export interface SmartSearchResult {
|
|
18
|
+
/** The matched symbol. */
|
|
19
|
+
symbol: CodeSymbol;
|
|
20
|
+
/** Relevance score (higher = better match). */
|
|
21
|
+
score: number;
|
|
22
|
+
/** How the query matched (exact, substring, fuzzy, path). */
|
|
23
|
+
matchType: 'exact' | 'substring' | 'fuzzy' | 'path';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Options for smart_search. */
|
|
27
|
+
export interface SmartSearchOptions {
|
|
28
|
+
/** Maximum results to return (default: 20). */
|
|
29
|
+
maxResults?: number;
|
|
30
|
+
/** Glob-like file pattern filter (e.g. "*.ts", "src/**"). */
|
|
31
|
+
filePattern?: string;
|
|
32
|
+
/** Restrict to specific language. */
|
|
33
|
+
language?: TreeSitterLanguage;
|
|
34
|
+
/** Root directory to search (default: cwd). */
|
|
35
|
+
rootDir?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Walk a directory tree and collect source files.
|
|
40
|
+
*
|
|
41
|
+
* Respects common ignore patterns (node_modules, dist, .git, etc.)
|
|
42
|
+
* and optionally filters by file pattern and language.
|
|
43
|
+
*/
|
|
44
|
+
function collectSourceFiles(dir: string, options: SmartSearchOptions): string[] {
|
|
45
|
+
const IGNORE_DIRS = new Set([
|
|
46
|
+
'node_modules',
|
|
47
|
+
'dist',
|
|
48
|
+
'.git',
|
|
49
|
+
'.cleo',
|
|
50
|
+
'target',
|
|
51
|
+
'__pycache__',
|
|
52
|
+
'.next',
|
|
53
|
+
'.nuxt',
|
|
54
|
+
'build',
|
|
55
|
+
'coverage',
|
|
56
|
+
'.turbo',
|
|
57
|
+
'.cache',
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
const files: string[] = [];
|
|
61
|
+
|
|
62
|
+
function walk(currentDir: string): void {
|
|
63
|
+
let entries: { isDirectory(): boolean; isFile(): boolean; name: string }[];
|
|
64
|
+
try {
|
|
65
|
+
entries = readdirSync(currentDir, { withFileTypes: true, encoding: 'utf-8' }) as {
|
|
66
|
+
isDirectory(): boolean;
|
|
67
|
+
isFile(): boolean;
|
|
68
|
+
name: string;
|
|
69
|
+
}[];
|
|
70
|
+
} catch {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
for (const entry of entries) {
|
|
75
|
+
if (entry.isDirectory()) {
|
|
76
|
+
if (!IGNORE_DIRS.has(entry.name) && !entry.name.startsWith('.')) {
|
|
77
|
+
walk(join(currentDir, entry.name));
|
|
78
|
+
}
|
|
79
|
+
} else if (entry.isFile()) {
|
|
80
|
+
const fullPath = join(currentDir, entry.name);
|
|
81
|
+
const lang = detectLanguage(entry.name);
|
|
82
|
+
if (!lang) continue;
|
|
83
|
+
|
|
84
|
+
// Language filter
|
|
85
|
+
if (options.language && lang !== options.language) continue;
|
|
86
|
+
|
|
87
|
+
// File pattern filter (simple glob: *.ext or dir/**)
|
|
88
|
+
if (options.filePattern) {
|
|
89
|
+
const relPath = relative(dir, fullPath);
|
|
90
|
+
if (options.filePattern.startsWith('*.')) {
|
|
91
|
+
const ext = options.filePattern.slice(1);
|
|
92
|
+
if (!entry.name.endsWith(ext)) continue;
|
|
93
|
+
} else if (!relPath.includes(options.filePattern.replace('/**', ''))) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
files.push(fullPath);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
walk(dir);
|
|
104
|
+
return files;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Score a symbol against a query string.
|
|
109
|
+
*
|
|
110
|
+
* Scoring: exact name match (10) > substring in name (5) >
|
|
111
|
+
* path match (3) > fuzzy (1).
|
|
112
|
+
*/
|
|
113
|
+
function scoreSymbol(
|
|
114
|
+
symbol: CodeSymbol,
|
|
115
|
+
query: string,
|
|
116
|
+
): { score: number; matchType: SmartSearchResult['matchType'] } {
|
|
117
|
+
const q = query.toLowerCase();
|
|
118
|
+
const name = symbol.name.toLowerCase();
|
|
119
|
+
const path = symbol.filePath.toLowerCase();
|
|
120
|
+
|
|
121
|
+
// Exact name match
|
|
122
|
+
if (name === q) return { score: 10, matchType: 'exact' };
|
|
123
|
+
|
|
124
|
+
// Substring in name
|
|
125
|
+
if (name.includes(q)) return { score: 5, matchType: 'substring' };
|
|
126
|
+
|
|
127
|
+
// Path match
|
|
128
|
+
if (path.includes(q)) return { score: 3, matchType: 'path' };
|
|
129
|
+
|
|
130
|
+
// Fuzzy: all query chars appear in order in the name
|
|
131
|
+
let qi = 0;
|
|
132
|
+
for (let ni = 0; ni < name.length && qi < q.length; ni++) {
|
|
133
|
+
if (name[ni] === q[qi]) qi++;
|
|
134
|
+
}
|
|
135
|
+
if (qi === q.length) return { score: 1, matchType: 'fuzzy' };
|
|
136
|
+
|
|
137
|
+
return { score: 0, matchType: 'fuzzy' };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Search for symbols across a codebase.
|
|
142
|
+
*
|
|
143
|
+
* Walks the directory tree, batch-parses source files, and returns
|
|
144
|
+
* symbols matching the query ranked by relevance score.
|
|
145
|
+
*
|
|
146
|
+
* @param query - Search string to match against symbol names and paths
|
|
147
|
+
* @param options - Search options (maxResults, filePattern, language, rootDir)
|
|
148
|
+
* @returns Ranked array of search results
|
|
149
|
+
*/
|
|
150
|
+
export function smartSearch(query: string, options: SmartSearchOptions = {}): SmartSearchResult[] {
|
|
151
|
+
const rootDir = options.rootDir ?? process.cwd();
|
|
152
|
+
const maxResults = options.maxResults ?? 20;
|
|
153
|
+
|
|
154
|
+
// Collect and parse source files
|
|
155
|
+
const files = collectSourceFiles(rootDir, options);
|
|
156
|
+
const parsed = batchParse(files, rootDir);
|
|
157
|
+
|
|
158
|
+
// Score all symbols
|
|
159
|
+
const results: SmartSearchResult[] = [];
|
|
160
|
+
for (const fileResult of parsed.results) {
|
|
161
|
+
for (const symbol of fileResult.symbols) {
|
|
162
|
+
const { score, matchType } = scoreSymbol(symbol, query);
|
|
163
|
+
if (score > 0) {
|
|
164
|
+
results.push({ symbol, score, matchType });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Sort by score descending, then by name
|
|
170
|
+
results.sort((a, b) => b.score - a.score || a.symbol.name.localeCompare(b.symbol.name));
|
|
171
|
+
|
|
172
|
+
return results.slice(0, maxResults);
|
|
173
|
+
}
|