kirograph 0.27.2 → 0.28.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -7
- package/dist/bin/commands/annotations.js +56 -0
- package/dist/bin/commands/annotations.js.map +7 -0
- package/dist/bin/commands/ast-rewrite.js +55 -0
- package/dist/bin/commands/ast-rewrite.js.map +7 -0
- package/dist/bin/commands/bench.js +103 -0
- package/dist/bin/commands/bench.js.map +7 -0
- package/dist/bin/commands/branch.js +133 -0
- package/dist/bin/commands/branch.js.map +7 -0
- package/dist/bin/commands/changelog.js +55 -0
- package/dist/bin/commands/changelog.js.map +7 -0
- package/dist/bin/commands/commit-context.js +55 -0
- package/dist/bin/commands/commit-context.js.map +7 -0
- package/dist/bin/commands/complexity.js +60 -0
- package/dist/bin/commands/complexity.js.map +7 -0
- package/dist/bin/commands/cost.js +146 -0
- package/dist/bin/commands/cost.js.map +7 -0
- package/dist/bin/commands/dependency-depth.js +55 -0
- package/dist/bin/commands/dependency-depth.js.map +7 -0
- package/dist/bin/commands/diff-context.js +55 -0
- package/dist/bin/commands/diff-context.js.map +7 -0
- package/dist/bin/commands/distribution.js +56 -0
- package/dist/bin/commands/distribution.js.map +7 -0
- package/dist/bin/commands/doc-coverage.js +56 -0
- package/dist/bin/commands/doc-coverage.js.map +7 -0
- package/dist/bin/commands/doctor.js +202 -0
- package/dist/bin/commands/doctor.js.map +7 -0
- package/dist/bin/commands/dsm.js +56 -0
- package/dist/bin/commands/dsm.js.map +7 -0
- package/dist/bin/commands/gini.js +55 -0
- package/dist/bin/commands/gini.js.map +7 -0
- package/dist/bin/commands/god-class.js +56 -0
- package/dist/bin/commands/god-class.js.map +7 -0
- package/dist/bin/commands/health.js +55 -0
- package/dist/bin/commands/health.js.map +7 -0
- package/dist/bin/commands/help.js +224 -45
- package/dist/bin/commands/help.js.map +2 -2
- package/dist/bin/commands/inheritance-depth.js +56 -0
- package/dist/bin/commands/inheritance-depth.js.map +7 -0
- package/dist/bin/commands/insert-at.js +55 -0
- package/dist/bin/commands/insert-at.js.map +7 -0
- package/dist/bin/commands/largest.js +56 -0
- package/dist/bin/commands/largest.js.map +7 -0
- package/dist/bin/commands/manifest.js +158 -0
- package/dist/bin/commands/manifest.js.map +7 -0
- package/dist/bin/commands/module-api.js +57 -0
- package/dist/bin/commands/module-api.js.map +7 -0
- package/dist/bin/commands/monitor.js +91 -0
- package/dist/bin/commands/monitor.js.map +7 -0
- package/dist/bin/commands/multi-replace.js +66 -0
- package/dist/bin/commands/multi-replace.js.map +7 -0
- package/dist/bin/commands/pr-context.js +55 -0
- package/dist/bin/commands/pr-context.js.map +7 -0
- package/dist/bin/commands/query.js +19 -1
- package/dist/bin/commands/query.js.map +3 -3
- package/dist/bin/commands/rank.js +56 -0
- package/dist/bin/commands/rank.js.map +7 -0
- package/dist/bin/commands/read.js +1 -1
- package/dist/bin/commands/read.js.map +2 -2
- package/dist/bin/commands/recursion.js +56 -0
- package/dist/bin/commands/recursion.js.map +7 -0
- package/dist/bin/commands/rename-preview.js +56 -0
- package/dist/bin/commands/rename-preview.js.map +7 -0
- package/dist/bin/commands/session.js +60 -0
- package/dist/bin/commands/session.js.map +7 -0
- package/dist/bin/commands/simplify-scan.js +56 -0
- package/dist/bin/commands/simplify-scan.js.map +7 -0
- package/dist/bin/commands/str-replace.js +55 -0
- package/dist/bin/commands/str-replace.js.map +7 -0
- package/dist/bin/commands/test-coverage.js +59 -0
- package/dist/bin/commands/test-coverage.js.map +7 -0
- package/dist/bin/commands/test-map.js +56 -0
- package/dist/bin/commands/test-map.js.map +7 -0
- package/dist/bin/commands/test-risk.js +59 -0
- package/dist/bin/commands/test-risk.js.map +7 -0
- package/dist/bin/commands/unused-imports.js +55 -0
- package/dist/bin/commands/unused-imports.js.map +7 -0
- package/dist/bin/commands/upgrade.js +78 -0
- package/dist/bin/commands/upgrade.js.map +7 -0
- package/dist/bin/installer/common.js +5 -1
- package/dist/bin/installer/common.js.map +2 -2
- package/dist/bin/installer/config-prompt.js +239 -93
- package/dist/bin/installer/config-prompt.js.map +3 -3
- package/dist/bin/installer/index.js +38 -6
- package/dist/bin/installer/index.js.map +2 -2
- package/dist/bin/installer/instructions.js +7 -11
- package/dist/bin/installer/instructions.js.map +2 -2
- package/dist/bin/installer/mcp.js.map +2 -2
- package/dist/bin/installer/steering.js +531 -100
- package/dist/bin/installer/steering.js.map +2 -2
- package/dist/bin/installer/targets/kiro.js +7 -3
- package/dist/bin/installer/targets/kiro.js.map +2 -2
- package/dist/bin/kirograph.js +73 -1
- package/dist/bin/kirograph.js.map +3 -3
- package/dist/config.js +26 -8
- package/dist/config.js.map +2 -2
- package/dist/core/branch-manager.js +111 -0
- package/dist/core/branch-manager.js.map +7 -0
- package/dist/core/pipeline.js +3 -3
- package/dist/core/pipeline.js.map +2 -2
- package/dist/db/database.js +34 -7
- package/dist/db/database.js.map +2 -2
- package/dist/db/schema.sql +7 -1
- package/dist/extraction/complexity.js +116 -0
- package/dist/extraction/complexity.js.map +7 -0
- package/dist/extraction/extractor.js +52 -1
- package/dist/extraction/extractor.js.map +2 -2
- package/dist/graph/git-context.js +229 -0
- package/dist/graph/git-context.js.map +7 -0
- package/dist/index.js +4 -2
- package/dist/index.js.map +2 -2
- package/dist/mcp/handler.js +446 -0
- package/dist/mcp/handler.js.map +7 -0
- package/dist/mcp/handlers/architecture.js +297 -0
- package/dist/mcp/handlers/architecture.js.map +7 -0
- package/dist/mcp/handlers/branch.js +175 -0
- package/dist/mcp/handlers/branch.js.map +7 -0
- package/dist/mcp/handlers/code-health.js +563 -0
- package/dist/mcp/handlers/code-health.js.map +7 -0
- package/dist/mcp/handlers/complexity.js +267 -0
- package/dist/mcp/handlers/complexity.js.map +7 -0
- package/dist/mcp/handlers/core.js +611 -0
- package/dist/mcp/handlers/core.js.map +7 -0
- package/dist/mcp/handlers/data.js +337 -0
- package/dist/mcp/handlers/data.js.map +7 -0
- package/dist/mcp/handlers/docs.js +159 -0
- package/dist/mcp/handlers/docs.js.map +7 -0
- package/dist/mcp/handlers/edit-primitives.js +200 -0
- package/dist/mcp/handlers/edit-primitives.js.map +7 -0
- package/dist/mcp/handlers/git-context.js +286 -0
- package/dist/mcp/handlers/git-context.js.map +7 -0
- package/dist/mcp/handlers/memory.js +363 -0
- package/dist/mcp/handlers/memory.js.map +7 -0
- package/dist/mcp/handlers/patterns.js +310 -0
- package/dist/mcp/handlers/patterns.js.map +7 -0
- package/dist/mcp/handlers/security.js +699 -0
- package/dist/mcp/handlers/security.js.map +7 -0
- package/dist/mcp/handlers/utils.js +97 -0
- package/dist/mcp/handlers/utils.js.map +7 -0
- package/dist/mcp/handlers/watchmen.js +104 -0
- package/dist/mcp/handlers/watchmen.js.map +7 -0
- package/dist/mcp/handlers/wiki.js +242 -0
- package/dist/mcp/handlers/wiki.js.map +7 -0
- package/dist/mcp/server.js +72 -3
- package/dist/mcp/server.js.map +2 -2
- package/dist/mcp/tool-names.js +94 -7
- package/dist/mcp/tool-names.js.map +2 -2
- package/dist/mcp/tools.js +445 -3210
- package/dist/mcp/tools.js.map +3 -3
- package/dist/types.js.map +2 -2
- package/package.json +36 -10
- package/dist/architecture/index.d.ts +0 -11
- package/dist/architecture/index.d.ts.map +0 -1
- package/dist/architecture/layers/csharp.d.ts +0 -6
- package/dist/architecture/layers/csharp.d.ts.map +0 -1
- package/dist/architecture/layers/dart.d.ts +0 -10
- package/dist/architecture/layers/dart.d.ts.map +0 -1
- package/dist/architecture/layers/elixir.d.ts +0 -6
- package/dist/architecture/layers/elixir.d.ts.map +0 -1
- package/dist/architecture/layers/go.d.ts +0 -7
- package/dist/architecture/layers/go.d.ts.map +0 -1
- package/dist/architecture/layers/index.d.ts +0 -30
- package/dist/architecture/layers/index.d.ts.map +0 -1
- package/dist/architecture/layers/java.d.ts +0 -7
- package/dist/architecture/layers/java.d.ts.map +0 -1
- package/dist/architecture/layers/ocaml.d.ts +0 -6
- package/dist/architecture/layers/ocaml.d.ts.map +0 -1
- package/dist/architecture/layers/python.d.ts +0 -7
- package/dist/architecture/layers/python.d.ts.map +0 -1
- package/dist/architecture/layers/ruby.d.ts +0 -6
- package/dist/architecture/layers/ruby.d.ts.map +0 -1
- package/dist/architecture/layers/rust.d.ts +0 -6
- package/dist/architecture/layers/rust.d.ts.map +0 -1
- package/dist/architecture/layers/scala.d.ts +0 -6
- package/dist/architecture/layers/scala.d.ts.map +0 -1
- package/dist/architecture/layers/solidity.d.ts +0 -6
- package/dist/architecture/layers/solidity.d.ts.map +0 -1
- package/dist/architecture/layers/types.d.ts +0 -2
- package/dist/architecture/layers/types.d.ts.map +0 -1
- package/dist/architecture/layers/typescript.d.ts +0 -9
- package/dist/architecture/layers/typescript.d.ts.map +0 -1
- package/dist/architecture/layers/vue.d.ts +0 -6
- package/dist/architecture/layers/vue.d.ts.map +0 -1
- package/dist/architecture/manifest/cargo.d.ts +0 -3
- package/dist/architecture/manifest/cargo.d.ts.map +0 -1
- package/dist/architecture/manifest/csproj.d.ts +0 -3
- package/dist/architecture/manifest/csproj.d.ts.map +0 -1
- package/dist/architecture/manifest/elm.d.ts +0 -3
- package/dist/architecture/manifest/elm.d.ts.map +0 -1
- package/dist/architecture/manifest/go.d.ts +0 -3
- package/dist/architecture/manifest/go.d.ts.map +0 -1
- package/dist/architecture/manifest/gradle.d.ts +0 -3
- package/dist/architecture/manifest/gradle.d.ts.map +0 -1
- package/dist/architecture/manifest/index.d.ts +0 -12
- package/dist/architecture/manifest/index.d.ts.map +0 -1
- package/dist/architecture/manifest/maven.d.ts +0 -3
- package/dist/architecture/manifest/maven.d.ts.map +0 -1
- package/dist/architecture/manifest/npm.d.ts +0 -3
- package/dist/architecture/manifest/npm.d.ts.map +0 -1
- package/dist/architecture/manifest/ocaml.d.ts +0 -3
- package/dist/architecture/manifest/ocaml.d.ts.map +0 -1
- package/dist/architecture/manifest/python.d.ts +0 -3
- package/dist/architecture/manifest/python.d.ts.map +0 -1
- package/dist/architecture/manifest/scala.d.ts +0 -3
- package/dist/architecture/manifest/scala.d.ts.map +0 -1
- package/dist/architecture/manifest/types.d.ts +0 -2
- package/dist/architecture/manifest/types.d.ts.map +0 -1
- package/dist/architecture/types.d.ts +0 -91
- package/dist/architecture/types.d.ts.map +0 -1
- package/dist/banner.d.ts +0 -6
- package/dist/banner.d.ts.map +0 -1
- package/dist/banner.js +0 -67
- package/dist/banner.js.map +0 -1
- package/dist/bin/banner.d.ts +0 -6
- package/dist/bin/banner.d.ts.map +0 -1
- package/dist/bin/commands/affected.d.ts +0 -3
- package/dist/bin/commands/affected.d.ts.map +0 -1
- package/dist/bin/commands/architecture.d.ts +0 -3
- package/dist/bin/commands/architecture.d.ts.map +0 -1
- package/dist/bin/commands/attack-surface.d.ts +0 -3
- package/dist/bin/commands/attack-surface.d.ts.map +0 -1
- package/dist/bin/commands/benchmark.d.ts +0 -9
- package/dist/bin/commands/benchmark.d.ts.map +0 -1
- package/dist/bin/commands/budget.d.ts +0 -6
- package/dist/bin/commands/budget.d.ts.map +0 -1
- package/dist/bin/commands/caveman.d.ts +0 -7
- package/dist/bin/commands/caveman.d.ts.map +0 -1
- package/dist/bin/commands/communities.d.ts +0 -3
- package/dist/bin/commands/communities.d.ts.map +0 -1
- package/dist/bin/commands/compression.d.ts +0 -7
- package/dist/bin/commands/compression.d.ts.map +0 -1
- package/dist/bin/commands/configure-mcp.js +0 -104
- package/dist/bin/commands/configure-mcp.js.map +0 -7
- package/dist/bin/commands/context.d.ts +0 -3
- package/dist/bin/commands/context.d.ts.map +0 -1
- package/dist/bin/commands/coupling.d.ts +0 -3
- package/dist/bin/commands/coupling.d.ts.map +0 -1
- package/dist/bin/commands/dashboard.d.ts +0 -3
- package/dist/bin/commands/dashboard.d.ts.map +0 -1
- package/dist/bin/commands/data.d.ts +0 -3
- package/dist/bin/commands/data.d.ts.map +0 -1
- package/dist/bin/commands/dead-code.d.ts +0 -3
- package/dist/bin/commands/dead-code.d.ts.map +0 -1
- package/dist/bin/commands/dep-confusion.d.ts +0 -3
- package/dist/bin/commands/dep-confusion.d.ts.map +0 -1
- package/dist/bin/commands/docs.d.ts +0 -3
- package/dist/bin/commands/docs.d.ts.map +0 -1
- package/dist/bin/commands/exec.d.ts +0 -11
- package/dist/bin/commands/exec.d.ts.map +0 -1
- package/dist/bin/commands/export.d.ts +0 -3
- package/dist/bin/commands/export.d.ts.map +0 -1
- package/dist/bin/commands/files.d.ts +0 -3
- package/dist/bin/commands/files.d.ts.map +0 -1
- package/dist/bin/commands/flows.d.ts +0 -3
- package/dist/bin/commands/flows.d.ts.map +0 -1
- package/dist/bin/commands/gain.d.ts +0 -6
- package/dist/bin/commands/gain.d.ts.map +0 -1
- package/dist/bin/commands/help.d.ts +0 -11
- package/dist/bin/commands/help.d.ts.map +0 -1
- package/dist/bin/commands/hotspots.d.ts +0 -3
- package/dist/bin/commands/hotspots.d.ts.map +0 -1
- package/dist/bin/commands/index.d.ts +0 -3
- package/dist/bin/commands/index.d.ts.map +0 -1
- package/dist/bin/commands/init.d.ts +0 -3
- package/dist/bin/commands/init.d.ts.map +0 -1
- package/dist/bin/commands/install.d.ts +0 -3
- package/dist/bin/commands/install.d.ts.map +0 -1
- package/dist/bin/commands/licenses.d.ts +0 -3
- package/dist/bin/commands/licenses.d.ts.map +0 -1
- package/dist/bin/commands/mark-dirty.d.ts +0 -3
- package/dist/bin/commands/mark-dirty.d.ts.map +0 -1
- package/dist/bin/commands/memory.d.ts +0 -16
- package/dist/bin/commands/memory.d.ts.map +0 -1
- package/dist/bin/commands/package.d.ts +0 -3
- package/dist/bin/commands/package.d.ts.map +0 -1
- package/dist/bin/commands/path.d.ts +0 -3
- package/dist/bin/commands/path.d.ts.map +0 -1
- package/dist/bin/commands/pattern.d.ts +0 -3
- package/dist/bin/commands/pattern.d.ts.map +0 -1
- package/dist/bin/commands/qdrant.d.ts +0 -3
- package/dist/bin/commands/qdrant.d.ts.map +0 -1
- package/dist/bin/commands/qdrant.js +0 -159
- package/dist/bin/commands/qdrant.js.map +0 -1
- package/dist/bin/commands/query.d.ts +0 -3
- package/dist/bin/commands/query.d.ts.map +0 -1
- package/dist/bin/commands/reachability.d.ts +0 -3
- package/dist/bin/commands/reachability.d.ts.map +0 -1
- package/dist/bin/commands/read.d.ts +0 -6
- package/dist/bin/commands/read.d.ts.map +0 -1
- package/dist/bin/commands/refactor.d.ts +0 -3
- package/dist/bin/commands/refactor.d.ts.map +0 -1
- package/dist/bin/commands/remediation.d.ts +0 -3
- package/dist/bin/commands/remediation.d.ts.map +0 -1
- package/dist/bin/commands/sbom.d.ts +0 -3
- package/dist/bin/commands/sbom.d.ts.map +0 -1
- package/dist/bin/commands/security-ci-report.d.ts +0 -9
- package/dist/bin/commands/security-ci-report.d.ts.map +0 -1
- package/dist/bin/commands/security-export.d.ts +0 -2
- package/dist/bin/commands/security-export.d.ts.map +0 -1
- package/dist/bin/commands/security-flows.d.ts +0 -3
- package/dist/bin/commands/security-flows.d.ts.map +0 -1
- package/dist/bin/commands/security-secrets.d.ts +0 -3
- package/dist/bin/commands/security-secrets.d.ts.map +0 -1
- package/dist/bin/commands/security.d.ts +0 -3
- package/dist/bin/commands/security.d.ts.map +0 -1
- package/dist/bin/commands/serve.d.ts +0 -3
- package/dist/bin/commands/serve.d.ts.map +0 -1
- package/dist/bin/commands/snapshot.d.ts +0 -3
- package/dist/bin/commands/snapshot.d.ts.map +0 -1
- package/dist/bin/commands/staleness.d.ts +0 -3
- package/dist/bin/commands/staleness.d.ts.map +0 -1
- package/dist/bin/commands/status.d.ts +0 -3
- package/dist/bin/commands/status.d.ts.map +0 -1
- package/dist/bin/commands/stop.d.ts +0 -3
- package/dist/bin/commands/stop.d.ts.map +0 -1
- package/dist/bin/commands/stop.js +0 -81
- package/dist/bin/commands/stop.js.map +0 -1
- package/dist/bin/commands/supply-chain.d.ts +0 -3
- package/dist/bin/commands/supply-chain.d.ts.map +0 -1
- package/dist/bin/commands/surprising.d.ts +0 -3
- package/dist/bin/commands/surprising.d.ts.map +0 -1
- package/dist/bin/commands/sync-if-dirty.d.ts +0 -3
- package/dist/bin/commands/sync-if-dirty.d.ts.map +0 -1
- package/dist/bin/commands/sync.d.ts +0 -3
- package/dist/bin/commands/sync.d.ts.map +0 -1
- package/dist/bin/commands/typesense.d.ts +0 -3
- package/dist/bin/commands/typesense.d.ts.map +0 -1
- package/dist/bin/commands/typesense.js +0 -126
- package/dist/bin/commands/typesense.js.map +0 -1
- package/dist/bin/commands/uninit.d.ts +0 -4
- package/dist/bin/commands/uninit.d.ts.map +0 -1
- package/dist/bin/commands/unlock.d.ts +0 -3
- package/dist/bin/commands/unlock.d.ts.map +0 -1
- package/dist/bin/commands/utils.d.ts +0 -17
- package/dist/bin/commands/utils.d.ts.map +0 -1
- package/dist/bin/commands/vex.d.ts +0 -3
- package/dist/bin/commands/vex.d.ts.map +0 -1
- package/dist/bin/commands/vuln-suppress.d.ts +0 -3
- package/dist/bin/commands/vuln-suppress.d.ts.map +0 -1
- package/dist/bin/commands/vulns.d.ts +0 -3
- package/dist/bin/commands/vulns.d.ts.map +0 -1
- package/dist/bin/installer/archive.d.ts +0 -47
- package/dist/bin/installer/archive.d.ts.map +0 -1
- package/dist/bin/installer/auto-detect.d.ts +0 -16
- package/dist/bin/installer/auto-detect.d.ts.map +0 -1
- package/dist/bin/installer/caveman.d.ts +0 -12
- package/dist/bin/installer/caveman.d.ts.map +0 -1
- package/dist/bin/installer/cli-agent.d.ts +0 -20
- package/dist/bin/installer/cli-agent.d.ts.map +0 -1
- package/dist/bin/installer/common.d.ts +0 -36
- package/dist/bin/installer/common.d.ts.map +0 -1
- package/dist/bin/installer/config-prompt.d.ts +0 -13
- package/dist/bin/installer/config-prompt.d.ts.map +0 -1
- package/dist/bin/installer/dashboard.d.ts +0 -3
- package/dist/bin/installer/dashboard.d.ts.map +0 -1
- package/dist/bin/installer/detect.d.ts +0 -30
- package/dist/bin/installer/detect.d.ts.map +0 -1
- package/dist/bin/installer/hooks.d.ts +0 -8
- package/dist/bin/installer/hooks.d.ts.map +0 -1
- package/dist/bin/installer/index.d.ts +0 -12
- package/dist/bin/installer/index.d.ts.map +0 -1
- package/dist/bin/installer/instructions.d.ts +0 -14
- package/dist/bin/installer/instructions.d.ts.map +0 -1
- package/dist/bin/installer/mcp.d.ts +0 -5
- package/dist/bin/installer/mcp.d.ts.map +0 -1
- package/dist/bin/installer/prompts.d.ts +0 -45
- package/dist/bin/installer/prompts.d.ts.map +0 -1
- package/dist/bin/installer/qdrant-dashboard.d.ts +0 -4
- package/dist/bin/installer/qdrant-dashboard.d.ts.map +0 -1
- package/dist/bin/installer/steering.d.ts +0 -17
- package/dist/bin/installer/steering.d.ts.map +0 -1
- package/dist/bin/installer/targets/aider.d.ts +0 -6
- package/dist/bin/installer/targets/aider.d.ts.map +0 -1
- package/dist/bin/installer/targets/amp.d.ts +0 -6
- package/dist/bin/installer/targets/amp.d.ts.map +0 -1
- package/dist/bin/installer/targets/antigravity.d.ts +0 -13
- package/dist/bin/installer/targets/antigravity.d.ts.map +0 -1
- package/dist/bin/installer/targets/augment.d.ts +0 -6
- package/dist/bin/installer/targets/augment.d.ts.map +0 -1
- package/dist/bin/installer/targets/claude.d.ts +0 -6
- package/dist/bin/installer/targets/claude.d.ts.map +0 -1
- package/dist/bin/installer/targets/cline.d.ts +0 -13
- package/dist/bin/installer/targets/cline.d.ts.map +0 -1
- package/dist/bin/installer/targets/codex.d.ts +0 -6
- package/dist/bin/installer/targets/codex.d.ts.map +0 -1
- package/dist/bin/installer/targets/continue.d.ts +0 -12
- package/dist/bin/installer/targets/continue.d.ts.map +0 -1
- package/dist/bin/installer/targets/copilot-cli.d.ts +0 -12
- package/dist/bin/installer/targets/copilot-cli.d.ts.map +0 -1
- package/dist/bin/installer/targets/copilot.d.ts +0 -6
- package/dist/bin/installer/targets/copilot.d.ts.map +0 -1
- package/dist/bin/installer/targets/cursor.d.ts +0 -6
- package/dist/bin/installer/targets/cursor.d.ts.map +0 -1
- package/dist/bin/installer/targets/devin.d.ts +0 -13
- package/dist/bin/installer/targets/devin.d.ts.map +0 -1
- package/dist/bin/installer/targets/gemini-cli.d.ts +0 -16
- package/dist/bin/installer/targets/gemini-cli.d.ts.map +0 -1
- package/dist/bin/installer/targets/generic.d.ts +0 -59
- package/dist/bin/installer/targets/generic.d.ts.map +0 -1
- package/dist/bin/installer/targets/goose.d.ts +0 -6
- package/dist/bin/installer/targets/goose.d.ts.map +0 -1
- package/dist/bin/installer/targets/index.d.ts +0 -11
- package/dist/bin/installer/targets/index.d.ts.map +0 -1
- package/dist/bin/installer/targets/junie.d.ts +0 -13
- package/dist/bin/installer/targets/junie.d.ts.map +0 -1
- package/dist/bin/installer/targets/kilo.d.ts +0 -12
- package/dist/bin/installer/targets/kilo.d.ts.map +0 -1
- package/dist/bin/installer/targets/kiro.d.ts +0 -5
- package/dist/bin/installer/targets/kiro.d.ts.map +0 -1
- package/dist/bin/installer/targets/opencode.d.ts +0 -13
- package/dist/bin/installer/targets/opencode.d.ts.map +0 -1
- package/dist/bin/installer/targets/openhands.d.ts +0 -6
- package/dist/bin/installer/targets/openhands.d.ts.map +0 -1
- package/dist/bin/installer/targets/qoder.d.ts +0 -12
- package/dist/bin/installer/targets/qoder.d.ts.map +0 -1
- package/dist/bin/installer/targets/qwen.d.ts +0 -12
- package/dist/bin/installer/targets/qwen.d.ts.map +0 -1
- package/dist/bin/installer/targets/replit.d.ts +0 -6
- package/dist/bin/installer/targets/replit.d.ts.map +0 -1
- package/dist/bin/installer/targets/roo.d.ts +0 -13
- package/dist/bin/installer/targets/roo.d.ts.map +0 -1
- package/dist/bin/installer/targets/tabnine.d.ts +0 -6
- package/dist/bin/installer/targets/tabnine.d.ts.map +0 -1
- package/dist/bin/installer/targets/trae.d.ts +0 -6
- package/dist/bin/installer/targets/trae.d.ts.map +0 -1
- package/dist/bin/installer/targets/warp.d.ts +0 -12
- package/dist/bin/installer/targets/warp.d.ts.map +0 -1
- package/dist/bin/installer/targets/windsurf.d.ts +0 -13
- package/dist/bin/installer/targets/windsurf.d.ts.map +0 -1
- package/dist/bin/kirograph.d.ts +0 -6
- package/dist/bin/kirograph.d.ts.map +0 -1
- package/dist/bin/progress.d.ts +0 -29
- package/dist/bin/progress.d.ts.map +0 -1
- package/dist/bin/ui.d.ts +0 -11
- package/dist/bin/ui.d.ts.map +0 -1
- package/dist/compression/filters/aws.d.ts +0 -9
- package/dist/compression/filters/aws.d.ts.map +0 -1
- package/dist/compression/filters/docker.d.ts +0 -6
- package/dist/compression/filters/docker.d.ts.map +0 -1
- package/dist/compression/filters/files.d.ts +0 -6
- package/dist/compression/filters/files.d.ts.map +0 -1
- package/dist/compression/filters/generic.d.ts +0 -7
- package/dist/compression/filters/generic.d.ts.map +0 -1
- package/dist/compression/filters/git.d.ts +0 -6
- package/dist/compression/filters/git.d.ts.map +0 -1
- package/dist/compression/filters/github.d.ts +0 -6
- package/dist/compression/filters/github.d.ts.map +0 -1
- package/dist/compression/filters/lint.d.ts +0 -6
- package/dist/compression/filters/lint.d.ts.map +0 -1
- package/dist/compression/filters/misc.d.ts +0 -6
- package/dist/compression/filters/misc.d.ts.map +0 -1
- package/dist/compression/filters/package.d.ts +0 -6
- package/dist/compression/filters/package.d.ts.map +0 -1
- package/dist/compression/filters/test.d.ts +0 -6
- package/dist/compression/filters/test.d.ts.map +0 -1
- package/dist/compression/index.d.ts +0 -29
- package/dist/compression/index.d.ts.map +0 -1
- package/dist/compression/naive-cost.d.ts +0 -26
- package/dist/compression/naive-cost.d.ts.map +0 -1
- package/dist/compression/tracker.d.ts +0 -155
- package/dist/compression/tracker.d.ts.map +0 -1
- package/dist/compression/types.d.ts +0 -49
- package/dist/compression/types.d.ts.map +0 -1
- package/dist/config.d.ts +0 -138
- package/dist/config.d.ts.map +0 -1
- package/dist/context/index.d.ts +0 -61
- package/dist/context/index.d.ts.map +0 -1
- package/dist/core/file-tree.d.ts +0 -15
- package/dist/core/file-tree.d.ts.map +0 -1
- package/dist/core/lock-manager.d.ts +0 -22
- package/dist/core/lock-manager.d.ts.map +0 -1
- package/dist/core/pipeline.d.ts +0 -40
- package/dist/core/pipeline.d.ts.map +0 -1
- package/dist/core/snapshot.d.ts +0 -58
- package/dist/core/snapshot.d.ts.map +0 -1
- package/dist/data/filters.d.ts +0 -23
- package/dist/data/filters.d.ts.map +0 -1
- package/dist/data/indexer.d.ts +0 -25
- package/dist/data/indexer.d.ts.map +0 -1
- package/dist/data/linker.d.ts +0 -37
- package/dist/data/linker.d.ts.map +0 -1
- package/dist/data/linker.test.js +0 -60
- package/dist/data/linker.test.js.map +0 -7
- package/dist/data/lint.d.ts +0 -27
- package/dist/data/lint.d.ts.map +0 -1
- package/dist/data/parsers/__fixtures__/pdf-inspector-stub.js +0 -41
- package/dist/data/parsers/__fixtures__/pdf-inspector-stub.js.map +0 -7
- package/dist/data/parsers/csv.d.ts +0 -11
- package/dist/data/parsers/csv.d.ts.map +0 -1
- package/dist/data/parsers/excel.d.ts +0 -10
- package/dist/data/parsers/excel.d.ts.map +0 -1
- package/dist/data/parsers/index.d.ts +0 -28
- package/dist/data/parsers/index.d.ts.map +0 -1
- package/dist/data/parsers/json-array.d.ts +0 -9
- package/dist/data/parsers/json-array.d.ts.map +0 -1
- package/dist/data/parsers/jsonl.d.ts +0 -9
- package/dist/data/parsers/jsonl.d.ts.map +0 -1
- package/dist/data/parsers/parquet.d.ts +0 -10
- package/dist/data/parsers/parquet.d.ts.map +0 -1
- package/dist/data/parsers/pdf.test.js +0 -125
- package/dist/data/parsers/pdf.test.js.map +0 -7
- package/dist/data/profiler.d.ts +0 -29
- package/dist/data/profiler.d.ts.map +0 -1
- package/dist/data/queries.d.ts +0 -95
- package/dist/data/queries.d.ts.map +0 -1
- package/dist/data/types.d.ts +0 -103
- package/dist/data/types.d.ts.map +0 -1
- package/dist/db/database.d.ts +0 -190
- package/dist/db/database.d.ts.map +0 -1
- package/dist/docs/formats/asciidoc.d.ts +0 -9
- package/dist/docs/formats/asciidoc.d.ts.map +0 -1
- package/dist/docs/formats/html.d.ts +0 -10
- package/dist/docs/formats/html.d.ts.map +0 -1
- package/dist/docs/formats/index.d.ts +0 -31
- package/dist/docs/formats/index.d.ts.map +0 -1
- package/dist/docs/formats/markdown.d.ts +0 -11
- package/dist/docs/formats/markdown.d.ts.map +0 -1
- package/dist/docs/formats/openapi.d.ts +0 -17
- package/dist/docs/formats/openapi.d.ts.map +0 -1
- package/dist/docs/formats/org.d.ts +0 -9
- package/dist/docs/formats/org.d.ts.map +0 -1
- package/dist/docs/formats/plaintext.d.ts +0 -11
- package/dist/docs/formats/plaintext.d.ts.map +0 -1
- package/dist/docs/formats/rdoc.d.ts +0 -10
- package/dist/docs/formats/rdoc.d.ts.map +0 -1
- package/dist/docs/formats/rst.d.ts +0 -11
- package/dist/docs/formats/rst.d.ts.map +0 -1
- package/dist/docs/indexer.d.ts +0 -49
- package/dist/docs/indexer.d.ts.map +0 -1
- package/dist/docs/linker.d.ts +0 -30
- package/dist/docs/linker.d.ts.map +0 -1
- package/dist/docs/lint.d.ts +0 -27
- package/dist/docs/lint.d.ts.map +0 -1
- package/dist/docs/queries.d.ts +0 -78
- package/dist/docs/queries.d.ts.map +0 -1
- package/dist/docs/section-id.d.ts +0 -48
- package/dist/docs/section-id.d.ts.map +0 -1
- package/dist/docs/summarizer.d.ts +0 -13
- package/dist/docs/summarizer.d.ts.map +0 -1
- package/dist/docs/types.d.ts +0 -71
- package/dist/docs/types.d.ts.map +0 -1
- package/dist/docs/vectors.d.ts +0 -54
- package/dist/docs/vectors.d.ts.map +0 -1
- package/dist/errors.d.ts +0 -49
- package/dist/errors.d.ts.map +0 -1
- package/dist/extraction/extractor.d.ts +0 -29
- package/dist/extraction/extractor.d.ts.map +0 -1
- package/dist/extraction/grammars.d.ts +0 -54
- package/dist/extraction/grammars.d.ts.map +0 -1
- package/dist/extraction/languages.d.ts +0 -9
- package/dist/extraction/languages.d.ts.map +0 -1
- package/dist/extraction/notebook.d.ts +0 -14
- package/dist/extraction/notebook.d.ts.map +0 -1
- package/dist/frameworks/amplify.d.ts +0 -9
- package/dist/frameworks/amplify.d.ts.map +0 -1
- package/dist/frameworks/angular.d.ts +0 -9
- package/dist/frameworks/angular.d.ts.map +0 -1
- package/dist/frameworks/ansible.d.ts +0 -8
- package/dist/frameworks/ansible.d.ts.map +0 -1
- package/dist/frameworks/cloudformation.d.ts +0 -9
- package/dist/frameworks/cloudformation.d.ts.map +0 -1
- package/dist/frameworks/csharp.d.ts +0 -8
- package/dist/frameworks/csharp.d.ts.map +0 -1
- package/dist/frameworks/docker.d.ts +0 -9
- package/dist/frameworks/docker.d.ts.map +0 -1
- package/dist/frameworks/elixir.d.ts +0 -6
- package/dist/frameworks/elixir.d.ts.map +0 -1
- package/dist/frameworks/express.d.ts +0 -8
- package/dist/frameworks/express.d.ts.map +0 -1
- package/dist/frameworks/flutter.d.ts +0 -12
- package/dist/frameworks/flutter.d.ts.map +0 -1
- package/dist/frameworks/go.d.ts +0 -8
- package/dist/frameworks/go.d.ts.map +0 -1
- package/dist/frameworks/iac.d.ts +0 -10
- package/dist/frameworks/iac.d.ts.map +0 -1
- package/dist/frameworks/index.d.ts +0 -44
- package/dist/frameworks/index.d.ts.map +0 -1
- package/dist/frameworks/java.d.ts +0 -8
- package/dist/frameworks/java.d.ts.map +0 -1
- package/dist/frameworks/kubernetes.d.ts +0 -9
- package/dist/frameworks/kubernetes.d.ts.map +0 -1
- package/dist/frameworks/laravel.d.ts +0 -9
- package/dist/frameworks/laravel.d.ts.map +0 -1
- package/dist/frameworks/pulumi.d.ts +0 -9
- package/dist/frameworks/pulumi.d.ts.map +0 -1
- package/dist/frameworks/python.d.ts +0 -10
- package/dist/frameworks/python.d.ts.map +0 -1
- package/dist/frameworks/react.d.ts +0 -9
- package/dist/frameworks/react.d.ts.map +0 -1
- package/dist/frameworks/ruby.d.ts +0 -8
- package/dist/frameworks/ruby.d.ts.map +0 -1
- package/dist/frameworks/rust.d.ts +0 -8
- package/dist/frameworks/rust.d.ts.map +0 -1
- package/dist/frameworks/scala.d.ts +0 -6
- package/dist/frameworks/scala.d.ts.map +0 -1
- package/dist/frameworks/solidity.d.ts +0 -6
- package/dist/frameworks/solidity.d.ts.map +0 -1
- package/dist/frameworks/svelte.d.ts +0 -8
- package/dist/frameworks/svelte.d.ts.map +0 -1
- package/dist/frameworks/swift.d.ts +0 -10
- package/dist/frameworks/swift.d.ts.map +0 -1
- package/dist/frameworks/terraform.d.ts +0 -14
- package/dist/frameworks/terraform.d.ts.map +0 -1
- package/dist/frameworks/types.d.ts +0 -37
- package/dist/frameworks/types.d.ts.map +0 -1
- package/dist/frameworks/vue.d.ts +0 -7
- package/dist/frameworks/vue.d.ts.map +0 -1
- package/dist/graph/communities.d.ts +0 -43
- package/dist/graph/communities.d.ts.map +0 -1
- package/dist/graph/flows.d.ts +0 -55
- package/dist/graph/flows.d.ts.map +0 -1
- package/dist/graph/queries.d.ts +0 -53
- package/dist/graph/queries.d.ts.map +0 -1
- package/dist/graph/refactor.d.ts +0 -43
- package/dist/graph/refactor.d.ts.map +0 -1
- package/dist/graph/traversal.d.ts +0 -35
- package/dist/graph/traversal.d.ts.map +0 -1
- package/dist/index.d.ts +0 -134
- package/dist/index.d.ts.map +0 -1
- package/dist/installer/index.d.ts +0 -10
- package/dist/installer/index.d.ts.map +0 -1
- package/dist/installer/index.js +0 -526
- package/dist/installer/index.js.map +0 -1
- package/dist/mcp/cache.d.ts +0 -69
- package/dist/mcp/cache.d.ts.map +0 -1
- package/dist/mcp/read-modes.d.ts +0 -31
- package/dist/mcp/read-modes.d.ts.map +0 -1
- package/dist/mcp/server.d.ts +0 -17
- package/dist/mcp/server.d.ts.map +0 -1
- package/dist/mcp/tool-names.d.ts +0 -2
- package/dist/mcp/tool-names.d.ts.map +0 -1
- package/dist/mcp/tools.d.ts +0 -49
- package/dist/mcp/tools.d.ts.map +0 -1
- package/dist/mcp/transport.d.ts +0 -29
- package/dist/mcp/transport.d.ts.map +0 -1
- package/dist/memory/compress.d.ts +0 -19
- package/dist/memory/compress.d.ts.map +0 -1
- package/dist/memory/database.d.ts +0 -92
- package/dist/memory/database.d.ts.map +0 -1
- package/dist/memory/index.d.ts +0 -71
- package/dist/memory/index.d.ts.map +0 -1
- package/dist/memory/symbols.d.ts +0 -21
- package/dist/memory/symbols.d.ts.map +0 -1
- package/dist/memory/types.d.ts +0 -78
- package/dist/memory/types.d.ts.map +0 -1
- package/dist/memory/vectors.d.ts +0 -43
- package/dist/memory/vectors.d.ts.map +0 -1
- package/dist/patterns/graph.d.ts +0 -16
- package/dist/patterns/graph.d.ts.map +0 -1
- package/dist/patterns/index.d.ts +0 -4
- package/dist/patterns/index.d.ts.map +0 -1
- package/dist/patterns/indexer.d.ts +0 -15
- package/dist/patterns/indexer.d.ts.map +0 -1
- package/dist/patterns/loader.d.ts +0 -18
- package/dist/patterns/loader.d.ts.map +0 -1
- package/dist/patterns/runner.d.ts +0 -42
- package/dist/patterns/runner.d.ts.map +0 -1
- package/dist/patterns/types.d.ts +0 -27
- package/dist/patterns/types.d.ts.map +0 -1
- package/dist/resolution/bridges/android-rn.d.ts +0 -13
- package/dist/resolution/bridges/android-rn.d.ts.map +0 -1
- package/dist/resolution/bridges/expo-modules.d.ts +0 -10
- package/dist/resolution/bridges/expo-modules.d.ts.map +0 -1
- package/dist/resolution/bridges/flutter-channel.d.ts +0 -14
- package/dist/resolution/bridges/flutter-channel.d.ts.map +0 -1
- package/dist/resolution/bridges/index.d.ts +0 -40
- package/dist/resolution/bridges/index.d.ts.map +0 -1
- package/dist/resolution/bridges/native-events.d.ts +0 -10
- package/dist/resolution/bridges/native-events.d.ts.map +0 -1
- package/dist/resolution/bridges/native-views.d.ts +0 -13
- package/dist/resolution/bridges/native-views.d.ts.map +0 -1
- package/dist/resolution/bridges/react-native.d.ts +0 -9
- package/dist/resolution/bridges/react-native.d.ts.map +0 -1
- package/dist/resolution/bridges/swift-objc.d.ts +0 -14
- package/dist/resolution/bridges/swift-objc.d.ts.map +0 -1
- package/dist/resolution/bridges/turbomodules.d.ts +0 -10
- package/dist/resolution/bridges/turbomodules.d.ts.map +0 -1
- package/dist/resolution/index.d.ts +0 -81
- package/dist/resolution/index.d.ts.map +0 -1
- package/dist/resolution/name-matcher.d.ts +0 -25
- package/dist/resolution/name-matcher.d.ts.map +0 -1
- package/dist/scripts/postinstall.js +0 -64
- package/dist/search/query-utils.d.ts +0 -21
- package/dist/search/query-utils.d.ts.map +0 -1
- package/dist/search/searcher.d.ts +0 -15
- package/dist/search/searcher.d.ts.map +0 -1
- package/dist/security/attack-surface.d.ts +0 -46
- package/dist/security/attack-surface.d.ts.map +0 -1
- package/dist/security/context-warnings.d.ts +0 -40
- package/dist/security/context-warnings.d.ts.map +0 -1
- package/dist/security/data-flows.d.ts +0 -33
- package/dist/security/data-flows.d.ts.map +0 -1
- package/dist/security/dep-confusion.d.ts +0 -23
- package/dist/security/dep-confusion.d.ts.map +0 -1
- package/dist/security/errors.d.ts +0 -16
- package/dist/security/errors.d.ts.map +0 -1
- package/dist/security/export/fix-suggestions.d.ts +0 -21
- package/dist/security/export/fix-suggestions.d.ts.map +0 -1
- package/dist/security/export/sbom.d.ts +0 -33
- package/dist/security/export/sbom.d.ts.map +0 -1
- package/dist/security/export/serialization.d.ts +0 -62
- package/dist/security/export/serialization.d.ts.map +0 -1
- package/dist/security/export/vex.d.ts +0 -58
- package/dist/security/export/vex.d.ts.map +0 -1
- package/dist/security/index.d.ts +0 -21
- package/dist/security/index.d.ts.map +0 -1
- package/dist/security/integrator.d.ts +0 -63
- package/dist/security/integrator.d.ts.map +0 -1
- package/dist/security/license.d.ts +0 -33
- package/dist/security/license.d.ts.map +0 -1
- package/dist/security/manifest/adapter.d.ts +0 -92
- package/dist/security/manifest/adapter.d.ts.map +0 -1
- package/dist/security/manifest/parser.d.ts +0 -73
- package/dist/security/manifest/parser.d.ts.map +0 -1
- package/dist/security/manifest/plugins/cargo.d.ts +0 -11
- package/dist/security/manifest/plugins/cargo.d.ts.map +0 -1
- package/dist/security/manifest/plugins/composer.d.ts +0 -11
- package/dist/security/manifest/plugins/composer.d.ts.map +0 -1
- package/dist/security/manifest/plugins/go.d.ts +0 -12
- package/dist/security/manifest/plugins/go.d.ts.map +0 -1
- package/dist/security/manifest/plugins/gradle.d.ts +0 -12
- package/dist/security/manifest/plugins/gradle.d.ts.map +0 -1
- package/dist/security/manifest/plugins/hex.d.ts +0 -11
- package/dist/security/manifest/plugins/hex.d.ts.map +0 -1
- package/dist/security/manifest/plugins/maven.d.ts +0 -11
- package/dist/security/manifest/plugins/maven.d.ts.map +0 -1
- package/dist/security/manifest/plugins/npm.d.ts +0 -11
- package/dist/security/manifest/plugins/npm.d.ts.map +0 -1
- package/dist/security/manifest/plugins/nuget.d.ts +0 -12
- package/dist/security/manifest/plugins/nuget.d.ts.map +0 -1
- package/dist/security/manifest/plugins/pip.d.ts +0 -11
- package/dist/security/manifest/plugins/pip.d.ts.map +0 -1
- package/dist/security/manifest/plugins/pubspec.d.ts +0 -11
- package/dist/security/manifest/plugins/pubspec.d.ts.map +0 -1
- package/dist/security/manifest/plugins/pyproject.d.ts +0 -3
- package/dist/security/manifest/plugins/pyproject.d.ts.map +0 -1
- package/dist/security/manifest/plugins/rubygems.d.ts +0 -11
- package/dist/security/manifest/plugins/rubygems.d.ts.map +0 -1
- package/dist/security/manifest/plugins/swift.d.ts +0 -11
- package/dist/security/manifest/plugins/swift.d.ts.map +0 -1
- package/dist/security/owasp.d.ts +0 -16
- package/dist/security/owasp.d.ts.map +0 -1
- package/dist/security/pipeline.d.ts +0 -37
- package/dist/security/pipeline.d.ts.map +0 -1
- package/dist/security/reachability.d.ts +0 -67
- package/dist/security/reachability.d.ts.map +0 -1
- package/dist/security/remediation.d.ts +0 -51
- package/dist/security/remediation.d.ts.map +0 -1
- package/dist/security/secrets.d.ts +0 -38
- package/dist/security/secrets.d.ts.map +0 -1
- package/dist/security/staleness.d.ts +0 -43
- package/dist/security/staleness.d.ts.map +0 -1
- package/dist/security/supply-chain.d.ts +0 -34
- package/dist/security/supply-chain.d.ts.map +0 -1
- package/dist/security/suppressions.d.ts +0 -18
- package/dist/security/suppressions.d.ts.map +0 -1
- package/dist/security/types.d.ts +0 -144
- package/dist/security/types.d.ts.map +0 -1
- package/dist/security/vuln/client.d.ts +0 -90
- package/dist/security/vuln/client.d.ts.map +0 -1
- package/dist/security/vuln/epss-client.d.ts +0 -26
- package/dist/security/vuln/epss-client.d.ts.map +0 -1
- package/dist/security/vuln/index.d.ts +0 -6
- package/dist/security/vuln/index.d.ts.map +0 -1
- package/dist/security/vuln/osv-adapter.d.ts +0 -58
- package/dist/security/vuln/osv-adapter.d.ts.map +0 -1
- package/dist/security/vuln/types.d.ts +0 -29
- package/dist/security/vuln/types.d.ts.map +0 -1
- package/dist/sync/index.d.ts +0 -35
- package/dist/sync/index.d.ts.map +0 -1
- package/dist/types.d.ts +0 -137
- package/dist/types.d.ts.map +0 -1
- package/dist/utils.d.ts +0 -52
- package/dist/utils.d.ts.map +0 -1
- package/dist/vectors/index.d.ts +0 -79
- package/dist/vectors/index.d.ts.map +0 -1
- package/dist/vectors/lancedb-index.d.ts +0 -50
- package/dist/vectors/lancedb-index.d.ts.map +0 -1
- package/dist/vectors/orama-index.d.ts +0 -54
- package/dist/vectors/orama-index.d.ts.map +0 -1
- package/dist/vectors/pglite-index.d.ts +0 -53
- package/dist/vectors/pglite-index.d.ts.map +0 -1
- package/dist/vectors/qdrant-index.d.ts +0 -77
- package/dist/vectors/qdrant-index.d.ts.map +0 -1
- package/dist/vectors/typesense-index.d.ts +0 -75
- package/dist/vectors/typesense-index.d.ts.map +0 -1
- package/dist/vectors/vec-index.d.ts +0 -52
- package/dist/vectors/vec-index.d.ts.map +0 -1
package/dist/mcp/tools.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
2
|
var __defProp = Object.defineProperty;
|
|
4
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
6
|
var __export = (target, all) => {
|
|
9
7
|
for (var name in all)
|
|
@@ -17,76 +15,17 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
15
|
}
|
|
18
16
|
return to;
|
|
19
17
|
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
19
|
var tools_exports = {};
|
|
30
20
|
__export(tools_exports, {
|
|
31
21
|
KIROGRAPH_TOOL_NAMES: () => import_tool_names.KIROGRAPH_TOOL_NAMES,
|
|
32
22
|
LIVE_SEARCH_TOOL_DEFINITION: () => LIVE_SEARCH_TOOL_DEFINITION,
|
|
33
|
-
ToolHandler: () => ToolHandler,
|
|
23
|
+
ToolHandler: () => import_handler.ToolHandler,
|
|
34
24
|
tools: () => tools
|
|
35
25
|
});
|
|
36
26
|
module.exports = __toCommonJS(tools_exports);
|
|
37
|
-
var fs = __toESM(require("fs"));
|
|
38
|
-
var path = __toESM(require("path"));
|
|
39
|
-
var crypto = __toESM(require("crypto"));
|
|
40
|
-
var import_child_process = require("child_process");
|
|
41
|
-
var import__ = __toESM(require("../index"));
|
|
42
|
-
var import_errors = require("../errors");
|
|
43
|
-
var import_compression = require("../compression/index");
|
|
44
|
-
var import_tracker = require("../compression/tracker");
|
|
45
27
|
var import_tool_names = require("./tool-names");
|
|
46
|
-
|
|
47
|
-
function truncate(s) {
|
|
48
|
-
return s.length > MAX_OUTPUT ? s.slice(0, MAX_OUTPUT) + "\n\u2026[truncated]" : s;
|
|
49
|
-
}
|
|
50
|
-
function estimateFileTokens(projectRoot, filePaths) {
|
|
51
|
-
let total = 0;
|
|
52
|
-
for (const fp of filePaths) {
|
|
53
|
-
try {
|
|
54
|
-
const fullPath = path.isAbsolute(fp) ? fp : path.join(projectRoot, fp);
|
|
55
|
-
const stat = fs.statSync(fullPath);
|
|
56
|
-
total += Math.round(stat.size / 4);
|
|
57
|
-
} catch {
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
return total;
|
|
61
|
-
}
|
|
62
|
-
function clampLimit(value, defaultValue) {
|
|
63
|
-
const n = typeof value === "number" ? value : defaultValue;
|
|
64
|
-
return Math.max(1, Math.min(100, Math.round(n)));
|
|
65
|
-
}
|
|
66
|
-
function mapKind(kind) {
|
|
67
|
-
if (kind === "type_alias") return "type";
|
|
68
|
-
return kind;
|
|
69
|
-
}
|
|
70
|
-
function writeSessionMarker(projectRoot) {
|
|
71
|
-
try {
|
|
72
|
-
const hash = crypto.createHash("sha256").update(projectRoot).digest("hex").slice(0, 16);
|
|
73
|
-
fs.writeFileSync(`/tmp/kirograph-consulted-${hash}`, String(Date.now()));
|
|
74
|
-
} catch {
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
function formatAge(timestamp) {
|
|
78
|
-
const diff = Date.now() - timestamp;
|
|
79
|
-
const minutes = Math.floor(diff / 6e4);
|
|
80
|
-
if (minutes < 60) return `${minutes}m ago`;
|
|
81
|
-
const hours = Math.floor(minutes / 60);
|
|
82
|
-
if (hours < 24) return `${hours}h ago`;
|
|
83
|
-
const days = Math.floor(hours / 24);
|
|
84
|
-
if (days < 7) return `${days}d ago`;
|
|
85
|
-
const weeks = Math.floor(days / 7);
|
|
86
|
-
if (weeks < 5) return `${weeks}w ago`;
|
|
87
|
-
const months = Math.floor(days / 30);
|
|
88
|
-
return `${months}mo ago`;
|
|
89
|
-
}
|
|
28
|
+
var import_handler = require("./handler");
|
|
90
29
|
const tools = [
|
|
91
30
|
{
|
|
92
31
|
name: "kirograph_search",
|
|
@@ -95,6 +34,12 @@ const tools = [
|
|
|
95
34
|
type: "object",
|
|
96
35
|
properties: {
|
|
97
36
|
query: { type: "string", description: 'Symbol name or partial name (e.g., "auth", "signIn", "UserService")' },
|
|
37
|
+
mode: {
|
|
38
|
+
type: "string",
|
|
39
|
+
description: 'Search mode: "name" (default, FTS prefix match) or "similar" (fuzzy substring match, broader results)',
|
|
40
|
+
enum: ["name", "similar"],
|
|
41
|
+
default: "name"
|
|
42
|
+
},
|
|
98
43
|
kind: {
|
|
99
44
|
type: "string",
|
|
100
45
|
description: "Filter by node kind",
|
|
@@ -171,7 +116,8 @@ const tools = [
|
|
|
171
116
|
inputSchema: {
|
|
172
117
|
type: "object",
|
|
173
118
|
properties: {
|
|
174
|
-
symbol: { type: "string", description: "Symbol name to look up" },
|
|
119
|
+
symbol: { type: "string", description: "Symbol name or fully-qualified name to look up" },
|
|
120
|
+
qualified: { type: "boolean", description: "When true, treat symbol as a fully-qualified name (exact match, bypasses FTS \u2014 use when you have the exact qualifiedName from a previous result)", default: false },
|
|
175
121
|
includeCode: { type: "boolean", description: "Include full source code. Deprecated \u2014 prefer detail.", default: false },
|
|
176
122
|
detail: {
|
|
177
123
|
type: "string",
|
|
@@ -295,6 +241,19 @@ const tools = [
|
|
|
295
241
|
required: ["package"]
|
|
296
242
|
}
|
|
297
243
|
},
|
|
244
|
+
{
|
|
245
|
+
name: "kirograph_manifest",
|
|
246
|
+
description: "Workspace manifest summary: list all packages across ecosystems with versions, external dep counts, and license. Pass package= to drill into one package. Pass ecosystem= to filter (npm, cargo, go, python, maven, gradle, csproj). Pass showDrift=true to surface packages that appear in multiple manifest files with differing versions.",
|
|
247
|
+
inputSchema: {
|
|
248
|
+
type: "object",
|
|
249
|
+
properties: {
|
|
250
|
+
package: { type: "string", description: "Package name or path for a focused view (partial match accepted)" },
|
|
251
|
+
ecosystem: { type: "string", description: 'Filter by ecosystem / language (e.g. "npm", "cargo", "go", "python")' },
|
|
252
|
+
showDrift: { type: "boolean", description: "Highlight packages declared in multiple manifests with different versions (default: false)", default: false },
|
|
253
|
+
projectPath: { type: "string" }
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
},
|
|
298
257
|
{
|
|
299
258
|
name: "kirograph_hotspots",
|
|
300
259
|
description: "Find the most-connected symbols in the codebase by total edge degree (incoming + outgoing). Useful for identifying load-bearing code, core abstractions, or blast-radius hot zones.",
|
|
@@ -367,6 +326,179 @@ const tools = [
|
|
|
367
326
|
required: ["symbol"]
|
|
368
327
|
}
|
|
369
328
|
},
|
|
329
|
+
{
|
|
330
|
+
name: "kirograph_module_api",
|
|
331
|
+
description: "List all exported symbols in a file or directory \u2014 the public API surface.",
|
|
332
|
+
inputSchema: {
|
|
333
|
+
type: "object",
|
|
334
|
+
properties: {
|
|
335
|
+
path: { type: "string", description: 'File path (e.g. "src/auth/service.ts") or directory prefix (e.g. "src/auth/"). Omit for the whole project.' },
|
|
336
|
+
limit: { type: "number", description: "Max results (default: 100)", default: 100 },
|
|
337
|
+
projectPath: { type: "string" }
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
name: "kirograph_rename_preview",
|
|
343
|
+
description: "Show every reference site for a symbol \u2014 blast-radius preview before a rename.",
|
|
344
|
+
inputSchema: {
|
|
345
|
+
type: "object",
|
|
346
|
+
properties: {
|
|
347
|
+
symbol: { type: "string", description: "Symbol name to find all references for" },
|
|
348
|
+
projectPath: { type: "string" }
|
|
349
|
+
},
|
|
350
|
+
required: ["symbol"]
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
name: "kirograph_doc_coverage",
|
|
355
|
+
description: "Find exported symbols that are missing docstrings.",
|
|
356
|
+
inputSchema: {
|
|
357
|
+
type: "object",
|
|
358
|
+
properties: {
|
|
359
|
+
limit: { type: "number", description: "Max results (default: 50)", default: 50 },
|
|
360
|
+
projectPath: { type: "string" }
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
name: "kirograph_god_class",
|
|
366
|
+
description: "List classes ranked by member count to identify god classes.",
|
|
367
|
+
inputSchema: {
|
|
368
|
+
type: "object",
|
|
369
|
+
properties: {
|
|
370
|
+
threshold: { type: "number", description: "Member count threshold for flagging (default: 10)", default: 10 },
|
|
371
|
+
limit: { type: "number", description: "Max results (default: 20)", default: 20 },
|
|
372
|
+
projectPath: { type: "string" }
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
name: "kirograph_inheritance_depth",
|
|
378
|
+
description: "Show the deepest inheritance chains (BFS on extends/implements edges).",
|
|
379
|
+
inputSchema: {
|
|
380
|
+
type: "object",
|
|
381
|
+
properties: {
|
|
382
|
+
limit: { type: "number", description: "Max results (default: 20)", default: 20 },
|
|
383
|
+
projectPath: { type: "string" }
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
name: "kirograph_recursion",
|
|
389
|
+
description: "Find recursive and mutually-recursive functions (SCC on call edges).",
|
|
390
|
+
inputSchema: {
|
|
391
|
+
type: "object",
|
|
392
|
+
properties: {
|
|
393
|
+
limit: { type: "number", description: "Max results (default: 30)", default: 30 },
|
|
394
|
+
projectPath: { type: "string" }
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
name: "kirograph_largest",
|
|
400
|
+
description: "List symbols ranked by lines of code (end_line \u2212 start_line).",
|
|
401
|
+
inputSchema: {
|
|
402
|
+
type: "object",
|
|
403
|
+
properties: {
|
|
404
|
+
limit: { type: "number", description: "Max results (default: 30)", default: 30 },
|
|
405
|
+
projectPath: { type: "string" }
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
name: "kirograph_rank",
|
|
411
|
+
description: "Rank symbols by fan-in (callers) or fan-out (callees) edge count.",
|
|
412
|
+
inputSchema: {
|
|
413
|
+
type: "object",
|
|
414
|
+
properties: {
|
|
415
|
+
by: { type: "string", description: 'Ranking dimension: "fan-in" (default) or "fan-out"', enum: ["fan-in", "fan-out"], default: "fan-in" },
|
|
416
|
+
limit: { type: "number", description: "Max results (default: 30)", default: 30 },
|
|
417
|
+
projectPath: { type: "string" }
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
name: "kirograph_distribution",
|
|
423
|
+
description: "Symbol-kind breakdown (function, class, method\u2026) for a file, directory, or the whole project.",
|
|
424
|
+
inputSchema: {
|
|
425
|
+
type: "object",
|
|
426
|
+
properties: {
|
|
427
|
+
path: { type: "string", description: "Directory or file path prefix to scope. Omit for whole project." },
|
|
428
|
+
projectPath: { type: "string" }
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
name: "kirograph_annotations",
|
|
434
|
+
description: "Decorator/annotation histogram for the whole project, or list of symbols using a specific decorator.",
|
|
435
|
+
inputSchema: {
|
|
436
|
+
type: "object",
|
|
437
|
+
properties: {
|
|
438
|
+
decorator: { type: "string", description: 'Decorator name to filter (e.g. "Injectable"). Omit for full histogram.' },
|
|
439
|
+
limit: { type: "number", description: "Max results (default: 50)", default: 50 },
|
|
440
|
+
projectPath: { type: "string" }
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
name: "kirograph_session_start",
|
|
446
|
+
description: "Save the current graph metrics as a session baseline.",
|
|
447
|
+
inputSchema: {
|
|
448
|
+
type: "object",
|
|
449
|
+
properties: {
|
|
450
|
+
projectPath: { type: "string" }
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
name: "kirograph_session_end",
|
|
456
|
+
description: "Diff current graph metrics against the session baseline and report per-dimension deltas.",
|
|
457
|
+
inputSchema: {
|
|
458
|
+
type: "object",
|
|
459
|
+
properties: {
|
|
460
|
+
projectPath: { type: "string" }
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
name: "kirograph_unused_imports",
|
|
466
|
+
description: "Find import nodes with zero resolved downstream edges (i.e. nothing in the codebase references the imported symbol).",
|
|
467
|
+
inputSchema: {
|
|
468
|
+
type: "object",
|
|
469
|
+
properties: {
|
|
470
|
+
limit: { type: "number", description: "Max results (default: 50)" },
|
|
471
|
+
projectPath: { type: "string" }
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
name: "kirograph_gini",
|
|
477
|
+
description: "Compute the Gini inequality coefficient for a size metric (loc, fan-in, or fan-out) across all function/method nodes. Returns the coefficient plus top/bottom 5.",
|
|
478
|
+
inputSchema: {
|
|
479
|
+
type: "object",
|
|
480
|
+
properties: {
|
|
481
|
+
metric: {
|
|
482
|
+
type: "string",
|
|
483
|
+
description: 'Metric to measure: "loc" (lines of code), "fan-in" (incoming calls), "fan-out" (outgoing calls)',
|
|
484
|
+
enum: ["loc", "fan-in", "fan-out"],
|
|
485
|
+
default: "loc"
|
|
486
|
+
},
|
|
487
|
+
projectPath: { type: "string" }
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
name: "kirograph_dependency_depth",
|
|
493
|
+
description: "Run a Kahn topological sort on the file import graph and report each file's longest path from a root (depth). Useful for spotting deeply layered dependency chains.",
|
|
494
|
+
inputSchema: {
|
|
495
|
+
type: "object",
|
|
496
|
+
properties: {
|
|
497
|
+
limit: { type: "number", description: "Max files to show sorted by depth DESC (default: 20)" },
|
|
498
|
+
projectPath: { type: "string" }
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
},
|
|
370
502
|
{
|
|
371
503
|
name: "kirograph_exec",
|
|
372
504
|
description: "Run a shell command with token-optimized, noise-filtered output.",
|
|
@@ -490,6 +622,93 @@ const tools = [
|
|
|
490
622
|
}
|
|
491
623
|
}
|
|
492
624
|
},
|
|
625
|
+
// ── Complexity tools (require enableComplexity=true) ──────────────────────────
|
|
626
|
+
{
|
|
627
|
+
name: "kirograph_complexity",
|
|
628
|
+
description: "Rank symbols by cyclomatic complexity, cognitive complexity, or maintainability index. Requires enableComplexity: true and a re-index.",
|
|
629
|
+
inputSchema: {
|
|
630
|
+
type: "object",
|
|
631
|
+
properties: {
|
|
632
|
+
metric: { type: "string", description: 'Ranking metric: "cyclomatic" (default), "cognitive", or "maintainability"', enum: ["cyclomatic", "cognitive", "maintainability"], default: "cyclomatic" },
|
|
633
|
+
threshold: { type: "number", description: "Flag symbols above (CC/cog) or below (MI) this threshold" },
|
|
634
|
+
limit: { type: "number", description: "Max results (default: 30)", default: 30 },
|
|
635
|
+
projectPath: { type: "string" }
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
name: "kirograph_simplify_scan",
|
|
641
|
+
description: "Find functions/methods that exceed complexity, size, or maintainability thresholds \u2014 simplification candidates.",
|
|
642
|
+
inputSchema: {
|
|
643
|
+
type: "object",
|
|
644
|
+
properties: {
|
|
645
|
+
thresholdCC: { type: "number", description: "Cyclomatic complexity threshold (default: 10)", default: 10 },
|
|
646
|
+
thresholdMI: { type: "number", description: "Maintainability index lower bound \u2014 flag below this (default: 50)", default: 50 },
|
|
647
|
+
thresholdLOC: { type: "number", description: "Lines of code threshold (default: 50)", default: 50 },
|
|
648
|
+
limit: { type: "number", description: "Max results (default: 30)", default: 30 },
|
|
649
|
+
projectPath: { type: "string" }
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
},
|
|
653
|
+
// ── Git context tools (require enableGitContext=true) ─────────────────────────
|
|
654
|
+
{
|
|
655
|
+
name: "kirograph_diff_context",
|
|
656
|
+
description: "Identify symbols changed in the working tree (or staged), with their callers and callees for immediate blast-radius context.",
|
|
657
|
+
inputSchema: {
|
|
658
|
+
type: "object",
|
|
659
|
+
properties: {
|
|
660
|
+
staged: { type: "boolean", description: "If true, show staged changes (git diff --cached) instead of unstaged", default: false },
|
|
661
|
+
projectPath: { type: "string" }
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
},
|
|
665
|
+
{
|
|
666
|
+
name: "kirograph_commit_context",
|
|
667
|
+
description: "Structured summary of staged changes \u2014 affected symbols, diff stat, staged files. Use to craft precise commit messages.",
|
|
668
|
+
inputSchema: {
|
|
669
|
+
type: "object",
|
|
670
|
+
properties: {
|
|
671
|
+
projectPath: { type: "string" }
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
},
|
|
675
|
+
{
|
|
676
|
+
name: "kirograph_pr_context",
|
|
677
|
+
description: "Semantic diff between two git refs \u2014 changed symbols with callers/callees. Use to write PR descriptions.",
|
|
678
|
+
inputSchema: {
|
|
679
|
+
type: "object",
|
|
680
|
+
properties: {
|
|
681
|
+
base: { type: "string", description: "Base ref (branch name, tag, or commit hash)" },
|
|
682
|
+
head: { type: "string", description: "Head ref (default: HEAD)", default: "HEAD" },
|
|
683
|
+
projectPath: { type: "string" }
|
|
684
|
+
},
|
|
685
|
+
required: ["base"]
|
|
686
|
+
}
|
|
687
|
+
},
|
|
688
|
+
{
|
|
689
|
+
name: "kirograph_changelog",
|
|
690
|
+
description: "Human-readable semantic changelog between two git refs: commit log + affected symbols.",
|
|
691
|
+
inputSchema: {
|
|
692
|
+
type: "object",
|
|
693
|
+
properties: {
|
|
694
|
+
ref1: { type: "string", description: "Start ref (older)" },
|
|
695
|
+
ref2: { type: "string", description: "End ref (default: HEAD)", default: "HEAD" },
|
|
696
|
+
projectPath: { type: "string" }
|
|
697
|
+
},
|
|
698
|
+
required: ["ref1"]
|
|
699
|
+
}
|
|
700
|
+
},
|
|
701
|
+
{
|
|
702
|
+
name: "kirograph_test_map",
|
|
703
|
+
description: "Find test files covering a symbol, or list exported symbols with no test coverage.",
|
|
704
|
+
inputSchema: {
|
|
705
|
+
type: "object",
|
|
706
|
+
properties: {
|
|
707
|
+
symbol: { type: "string", description: "Symbol name to find tests for. Omit to list all uncovered exported symbols." },
|
|
708
|
+
projectPath: { type: "string" }
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
},
|
|
493
712
|
{
|
|
494
713
|
name: "kirograph_refactor",
|
|
495
714
|
description: "Rename preview (all references) or function summarization.",
|
|
@@ -504,6 +723,74 @@ const tools = [
|
|
|
504
723
|
required: ["mode"]
|
|
505
724
|
}
|
|
506
725
|
},
|
|
726
|
+
// ── Edit primitives (require enableEditPrimitives=true) ───────────────────────
|
|
727
|
+
{
|
|
728
|
+
name: "kirograph_str_replace",
|
|
729
|
+
description: "Replace a unique string anchor in a file; fails if the anchor appears 0 or >1 times.",
|
|
730
|
+
inputSchema: {
|
|
731
|
+
type: "object",
|
|
732
|
+
properties: {
|
|
733
|
+
file: { type: "string", description: "File path relative to project root" },
|
|
734
|
+
old_str: { type: "string", description: "Exact string to find (must be unique in the file)" },
|
|
735
|
+
new_str: { type: "string", description: "Replacement string" },
|
|
736
|
+
projectPath: { type: "string" }
|
|
737
|
+
},
|
|
738
|
+
required: ["file", "old_str", "new_str"]
|
|
739
|
+
}
|
|
740
|
+
},
|
|
741
|
+
{
|
|
742
|
+
name: "kirograph_multi_str_replace",
|
|
743
|
+
description: "Apply N replacements as an all-or-nothing transaction; aborts if any anchor is ambiguous or missing.",
|
|
744
|
+
inputSchema: {
|
|
745
|
+
type: "object",
|
|
746
|
+
properties: {
|
|
747
|
+
file: { type: "string", description: "File path relative to project root" },
|
|
748
|
+
pairs: {
|
|
749
|
+
type: "array",
|
|
750
|
+
description: "Array of {old_str, new_str} replacement pairs applied in order",
|
|
751
|
+
items: {
|
|
752
|
+
type: "object",
|
|
753
|
+
properties: {
|
|
754
|
+
old_str: { type: "string", description: "Exact string to find (must be unique)" },
|
|
755
|
+
new_str: { type: "string", description: "Replacement string" }
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
},
|
|
759
|
+
projectPath: { type: "string" }
|
|
760
|
+
},
|
|
761
|
+
required: ["file", "pairs"]
|
|
762
|
+
}
|
|
763
|
+
},
|
|
764
|
+
{
|
|
765
|
+
name: "kirograph_insert_at",
|
|
766
|
+
description: "Insert content before or after a unique anchor string or line number.",
|
|
767
|
+
inputSchema: {
|
|
768
|
+
type: "object",
|
|
769
|
+
properties: {
|
|
770
|
+
file: { type: "string", description: "File path relative to project root" },
|
|
771
|
+
anchor: { type: "string", description: "Unique string anchor to insert relative to (mutually exclusive with line)" },
|
|
772
|
+
line: { type: "number", description: "1-based line number to insert relative to (mutually exclusive with anchor)" },
|
|
773
|
+
content: { type: "string", description: "Content to insert (include newline if needed)" },
|
|
774
|
+
position: { type: "string", description: '"before" or "after" the anchor/line (default: "after")', enum: ["before", "after"], default: "after" },
|
|
775
|
+
projectPath: { type: "string" }
|
|
776
|
+
},
|
|
777
|
+
required: ["file", "content"]
|
|
778
|
+
}
|
|
779
|
+
},
|
|
780
|
+
{
|
|
781
|
+
name: "kirograph_ast_grep_rewrite",
|
|
782
|
+
description: "Structural AST rewrite via ast-grep. Skipped if ast-grep is not installed.",
|
|
783
|
+
inputSchema: {
|
|
784
|
+
type: "object",
|
|
785
|
+
properties: {
|
|
786
|
+
file: { type: "string", description: "File path relative to project root" },
|
|
787
|
+
pattern: { type: "string", description: "ast-grep pattern to match" },
|
|
788
|
+
rewrite: { type: "string", description: "ast-grep rewrite template" },
|
|
789
|
+
projectPath: { type: "string" }
|
|
790
|
+
},
|
|
791
|
+
required: ["file", "pattern", "rewrite"]
|
|
792
|
+
}
|
|
793
|
+
},
|
|
507
794
|
// ── Memory tools (require enableMemory=true) ────────────────────────────────
|
|
508
795
|
{
|
|
509
796
|
name: "kirograph_mem_search",
|
|
@@ -1252,10 +1539,10 @@ const tools = [
|
|
|
1252
1539
|
}
|
|
1253
1540
|
}
|
|
1254
1541
|
},
|
|
1255
|
-
// ──
|
|
1542
|
+
// ── Phase 3 complexity tools ──────────────────────────────────────────────────
|
|
1256
1543
|
{
|
|
1257
|
-
name: "
|
|
1258
|
-
description: "
|
|
1544
|
+
name: "kirograph_health",
|
|
1545
|
+
description: "Composite graph health score (0\u201310000) across complexity, dead code, coupling, and circular dependencies. Higher is better.",
|
|
1259
1546
|
inputSchema: {
|
|
1260
1547
|
type: "object",
|
|
1261
1548
|
properties: {
|
|
@@ -1264,3179 +1551,127 @@ const tools = [
|
|
|
1264
1551
|
}
|
|
1265
1552
|
},
|
|
1266
1553
|
{
|
|
1267
|
-
name: "
|
|
1268
|
-
description: "
|
|
1554
|
+
name: "kirograph_dsm",
|
|
1555
|
+
description: "Design Structure Matrix \u2014 dependency counts between top-level modules. Reveals architectural coupling and layering violations.",
|
|
1269
1556
|
inputSchema: {
|
|
1270
1557
|
type: "object",
|
|
1271
1558
|
properties: {
|
|
1272
|
-
|
|
1559
|
+
limit: { type: "number", description: "Max module groups to include (default 15)", default: 15 },
|
|
1273
1560
|
projectPath: { type: "string" }
|
|
1274
1561
|
}
|
|
1275
1562
|
}
|
|
1276
1563
|
},
|
|
1277
1564
|
{
|
|
1278
|
-
name: "
|
|
1279
|
-
description: "
|
|
1565
|
+
name: "kirograph_test_risk",
|
|
1566
|
+
description: "Risk-ranked list of functions: complexity \xD7 fan-in. Highest-risk functions are most likely to break when changed.",
|
|
1280
1567
|
inputSchema: {
|
|
1281
1568
|
type: "object",
|
|
1282
1569
|
properties: {
|
|
1283
|
-
|
|
1570
|
+
limit: { type: "number", description: "Max results (default 20)", default: 20 },
|
|
1571
|
+
threshold: { type: "number", description: "Min risk score to include (default 0)", default: 0 },
|
|
1284
1572
|
projectPath: { type: "string" }
|
|
1285
1573
|
}
|
|
1286
1574
|
}
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
limit: { type: "number", description: "Max results (default 20, max 100)", default: 20 },
|
|
1299
|
-
projectPath: { type: "string" }
|
|
1300
|
-
}
|
|
1301
|
-
}
|
|
1302
|
-
};
|
|
1303
|
-
class ToolHandler {
|
|
1304
|
-
constructor(cg) {
|
|
1305
|
-
this.connections = /* @__PURE__ */ new Map();
|
|
1306
|
-
/** Anti-loop: track recent data_query calls per dataset for pagination detection. */
|
|
1307
|
-
this.queryTracker = /* @__PURE__ */ new Map();
|
|
1308
|
-
this.defaultCg = cg;
|
|
1309
|
-
}
|
|
1310
|
-
setDefaultKiroGraph(cg) {
|
|
1311
|
-
this.defaultCg = cg;
|
|
1312
|
-
}
|
|
1313
|
-
/** Close all cached cross-project connections. */
|
|
1314
|
-
closeAll() {
|
|
1315
|
-
for (const cg of this.connections.values()) {
|
|
1316
|
-
try {
|
|
1317
|
-
cg.close();
|
|
1318
|
-
} catch {
|
|
1319
|
-
}
|
|
1320
|
-
}
|
|
1321
|
-
this.connections.clear();
|
|
1322
|
-
}
|
|
1323
|
-
/** Anti-loop: detect pagination patterns and warn the agent. */
|
|
1324
|
-
checkPaginationLoop(dataset, offset, response) {
|
|
1325
|
-
const now = Date.now();
|
|
1326
|
-
const key = dataset;
|
|
1327
|
-
const currentOffset = offset ?? 0;
|
|
1328
|
-
for (const [k, v] of this.queryTracker) {
|
|
1329
|
-
if (now - v.lastCall > 6e4) this.queryTracker.delete(k);
|
|
1330
|
-
}
|
|
1331
|
-
const entry = this.queryTracker.get(key) ?? { offsets: [], lastCall: 0 };
|
|
1332
|
-
entry.offsets.push(currentOffset);
|
|
1333
|
-
entry.lastCall = now;
|
|
1334
|
-
if (entry.offsets.length > 10) entry.offsets = entry.offsets.slice(-10);
|
|
1335
|
-
this.queryTracker.set(key, entry);
|
|
1336
|
-
if (entry.offsets.length > 5) {
|
|
1337
|
-
const recent = entry.offsets.slice(-6);
|
|
1338
|
-
let isIncrementing = true;
|
|
1339
|
-
for (let i = 1; i < recent.length; i++) {
|
|
1340
|
-
if (recent[i] <= recent[i - 1]) {
|
|
1341
|
-
isIncrementing = false;
|
|
1342
|
-
break;
|
|
1343
|
-
}
|
|
1344
|
-
}
|
|
1345
|
-
if (isIncrementing) {
|
|
1346
|
-
return response + "\n\n\u26A0 Pagination detected. Consider using kirograph_data_aggregate for summary statistics instead of paginating through all rows.";
|
|
1347
|
-
}
|
|
1348
|
-
}
|
|
1349
|
-
return response;
|
|
1350
|
-
}
|
|
1351
|
-
async getConnection(projectPath) {
|
|
1352
|
-
if (!projectPath) return this.defaultCg;
|
|
1353
|
-
const resolved = path.resolve(projectPath);
|
|
1354
|
-
if (!path.isAbsolute(resolved)) return null;
|
|
1355
|
-
if (this.connections.has(resolved)) return this.connections.get(resolved);
|
|
1356
|
-
try {
|
|
1357
|
-
const cg = await import__.default.open(resolved);
|
|
1358
|
-
this.connections.set(resolved, cg);
|
|
1359
|
-
return cg;
|
|
1360
|
-
} catch {
|
|
1361
|
-
return null;
|
|
1362
|
-
}
|
|
1363
|
-
}
|
|
1364
|
-
async handle(toolName, args) {
|
|
1365
|
-
try {
|
|
1366
|
-
const text = await this.dispatch(toolName, args);
|
|
1367
|
-
const truncated = truncate(text);
|
|
1368
|
-
if (toolName !== "kirograph_exec" && toolName !== "kirograph_gain") {
|
|
1369
|
-
try {
|
|
1370
|
-
const projectRoot = args.projectPath || this.defaultCg?.getProjectRoot() || process.cwd();
|
|
1371
|
-
const { estimateNaiveCost } = await Promise.resolve().then(() => require("../compression/naive-cost.js"));
|
|
1372
|
-
const { estimateTokens: estimateTokens2 } = await Promise.resolve().then(() => require("../compression/index.js"));
|
|
1373
|
-
const outputTokens = estimateTokens2(truncated);
|
|
1374
|
-
const naiveCost = estimateNaiveCost(toolName, outputTokens, args);
|
|
1375
|
-
if (naiveCost !== null && naiveCost > outputTokens) {
|
|
1376
|
-
const tracker = new import_tracker.TokenTracker(projectRoot);
|
|
1377
|
-
if (toolName.startsWith("kirograph_mem_")) {
|
|
1378
|
-
tracker.recordMemorySaving(toolName, outputTokens, naiveCost);
|
|
1379
|
-
} else if (toolName.startsWith("kirograph_docs_")) {
|
|
1380
|
-
tracker.recordDocsSaving(toolName, outputTokens, naiveCost);
|
|
1381
|
-
} else if (toolName.startsWith("kirograph_data_")) {
|
|
1382
|
-
tracker.recordDataSaving(toolName, outputTokens, naiveCost);
|
|
1383
|
-
} else {
|
|
1384
|
-
tracker.recordGraphSaving(toolName, outputTokens, naiveCost);
|
|
1385
|
-
}
|
|
1386
|
-
}
|
|
1387
|
-
} catch {
|
|
1388
|
-
}
|
|
1575
|
+
},
|
|
1576
|
+
// ── Phase 3 git context tools ─────────────────────────────────────────────────
|
|
1577
|
+
{
|
|
1578
|
+
name: "kirograph_test_coverage",
|
|
1579
|
+
description: "Parse lcov/Istanbul/Cobertura coverage files and show per-file coverage percentage. Sorted worst-first by default.",
|
|
1580
|
+
inputSchema: {
|
|
1581
|
+
type: "object",
|
|
1582
|
+
properties: {
|
|
1583
|
+
sortBy: { type: "string", description: "Sort order: asc (worst first) or desc (best first)", enum: ["asc", "desc"], default: "asc" },
|
|
1584
|
+
limit: { type: "number", description: "Max results (default 30)", default: 30 },
|
|
1585
|
+
projectPath: { type: "string" }
|
|
1389
1586
|
}
|
|
1390
|
-
return { content: [{ type: "text", text: truncated }] };
|
|
1391
|
-
} catch (err) {
|
|
1392
|
-
(0, import_errors.logError)("MCP tool error", { tool: toolName, error: err });
|
|
1393
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1394
|
-
return { content: [{ type: "text", text: `Error: ${message}` }], isError: true };
|
|
1395
1587
|
}
|
|
1396
|
-
}
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
1406
|
-
const config = await loadConfig(projectRoot);
|
|
1407
|
-
if (config.shellCompressionLevel && config.shellCompressionLevel !== "off") {
|
|
1408
|
-
defaultLevel = config.shellCompressionLevel;
|
|
1409
|
-
}
|
|
1410
|
-
} catch {
|
|
1411
|
-
}
|
|
1412
|
-
const level = args.level ?? defaultLevel;
|
|
1413
|
-
const timeout = (args.timeout ?? 60) * 1e3;
|
|
1414
|
-
let rawOutput;
|
|
1415
|
-
let exitCode = 0;
|
|
1416
|
-
try {
|
|
1417
|
-
rawOutput = (0, import_child_process.execSync)(cmd, {
|
|
1418
|
-
cwd: execCwd,
|
|
1419
|
-
timeout,
|
|
1420
|
-
encoding: "utf8",
|
|
1421
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
1422
|
-
maxBuffer: 10 * 1024 * 1024
|
|
1423
|
-
});
|
|
1424
|
-
} catch (err) {
|
|
1425
|
-
rawOutput = (err.stdout || "") + (err.stderr || "");
|
|
1426
|
-
exitCode = err.status ?? 1;
|
|
1588
|
+
},
|
|
1589
|
+
// ── Phase 4 branch tools ──────────────────────────────────────────────────────
|
|
1590
|
+
{
|
|
1591
|
+
name: "kirograph_branch_list",
|
|
1592
|
+
description: "List tracked branches with their DB sizes and last sync times.",
|
|
1593
|
+
inputSchema: {
|
|
1594
|
+
type: "object",
|
|
1595
|
+
properties: {
|
|
1596
|
+
projectPath: { type: "string" }
|
|
1427
1597
|
}
|
|
1428
|
-
const result = (0, import_compression.compress)(cmd, rawOutput, { level, preserveErrors: exitCode !== 0 });
|
|
1429
|
-
const tracker = new import_tracker.TokenTracker(projectRoot);
|
|
1430
|
-
tracker.record(cmd, result.originalTokens, result.compressedTokens, result.strategy);
|
|
1431
|
-
const header = exitCode !== 0 ? `[exit ${exitCode}] ` : "";
|
|
1432
|
-
const footer = result.savings > 5 ? `
|
|
1433
|
-
|
|
1434
|
-
[${result.savings}% tokens saved | ${result.originalTokens}\u2192${result.compressedTokens} | ${result.strategy}]` : "";
|
|
1435
|
-
return `${header}${result.output}${footer}`;
|
|
1436
1598
|
}
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
` Tokens without KiroGraph: ~${stats.totalOriginal.toLocaleString()}`,
|
|
1449
|
-
` Tokens with KiroGraph: ~${stats.totalCompressed.toLocaleString()}`,
|
|
1450
|
-
` Saved: ${stats.totalSaved.toLocaleString()} tokens (${stats.savingsPercent}%)`
|
|
1451
|
-
];
|
|
1452
|
-
if (stats.bySource.exec.count > 0 || stats.bySource.graph.count > 0 || stats.bySource.memory.count > 0 || stats.bySource.docs.count > 0) {
|
|
1453
|
-
lines.push("", "By source:");
|
|
1454
|
-
if (stats.bySource.graph.count > 0) {
|
|
1455
|
-
lines.push(` Graph tools: ${stats.bySource.graph.count} calls, ~${stats.bySource.graph.saved.toLocaleString()} tokens saved (vs file reads/grep)`);
|
|
1456
|
-
}
|
|
1457
|
-
if (stats.bySource.docs.count > 0) {
|
|
1458
|
-
lines.push(` Docs tools: ${stats.bySource.docs.count} calls, ~${stats.bySource.docs.saved.toLocaleString()} tokens saved (vs reading full doc files)`);
|
|
1459
|
-
}
|
|
1460
|
-
if (stats.bySource.data.count > 0) {
|
|
1461
|
-
lines.push(` Data tools: ${stats.bySource.data.count} calls, ~${stats.bySource.data.saved.toLocaleString()} tokens saved (vs loading raw data files)`);
|
|
1462
|
-
}
|
|
1463
|
-
if (stats.bySource.exec.count > 0) {
|
|
1464
|
-
lines.push(` Compression: ${stats.bySource.exec.count} calls, ~${stats.bySource.exec.saved.toLocaleString()} tokens saved (vs raw output)`);
|
|
1465
|
-
}
|
|
1466
|
-
if (stats.bySource.memory.count > 0) {
|
|
1467
|
-
lines.push(` Memory: ${stats.bySource.memory.count} calls, ~${stats.bySource.memory.saved.toLocaleString()} tokens saved (vs re-discovering context)`);
|
|
1468
|
-
}
|
|
1469
|
-
}
|
|
1470
|
-
if (Object.keys(stats.byFamily).length > 0) {
|
|
1471
|
-
lines.push("", "Top families:");
|
|
1472
|
-
for (const [family, data] of Object.entries(stats.byFamily).slice(0, 7)) {
|
|
1473
|
-
lines.push(` ${family}: ${data.count} calls, ${data.savings}% avg savings`);
|
|
1474
|
-
}
|
|
1475
|
-
}
|
|
1476
|
-
if (stats.recentCommands.length > 0) {
|
|
1477
|
-
lines.push("", "Recent:");
|
|
1478
|
-
for (const cmd of stats.recentCommands.slice(0, 5)) {
|
|
1479
|
-
const tag = cmd.source === "graph" ? "\u{1F4CA}" : "\u26A1";
|
|
1480
|
-
lines.push(` ${tag} ${cmd.command.slice(0, 40)} \u2192 ${cmd.savings}% saved`);
|
|
1481
|
-
}
|
|
1599
|
+
},
|
|
1600
|
+
{
|
|
1601
|
+
name: "kirograph_branch_diff",
|
|
1602
|
+
description: "Symbols added, removed, or changed between two tracked branches.",
|
|
1603
|
+
inputSchema: {
|
|
1604
|
+
type: "object",
|
|
1605
|
+
required: ["branchA"],
|
|
1606
|
+
properties: {
|
|
1607
|
+
branchA: { type: "string", description: "First branch name (required)" },
|
|
1608
|
+
branchB: { type: "string", description: "Second branch name (default: main)", default: "main" },
|
|
1609
|
+
projectPath: { type: "string" }
|
|
1482
1610
|
}
|
|
1483
|
-
return lines.join("\n");
|
|
1484
1611
|
}
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
const cache = getFileReadCache();
|
|
1498
|
-
if (mode !== "full") {
|
|
1499
|
-
let cg2 = null;
|
|
1500
|
-
try {
|
|
1501
|
-
cg2 = await this.getConnection(args.projectPath);
|
|
1502
|
-
} catch {
|
|
1503
|
-
}
|
|
1504
|
-
const result2 = executeReadMode({
|
|
1505
|
-
mode,
|
|
1506
|
-
filePath: resolvedPath,
|
|
1507
|
-
start: args.start,
|
|
1508
|
-
end: args.end,
|
|
1509
|
-
cg: cg2
|
|
1510
|
-
});
|
|
1511
|
-
cache.read(resolvedPath, true);
|
|
1512
|
-
return result2.content;
|
|
1513
|
-
}
|
|
1514
|
-
const result = cache.read(resolvedPath, noCache);
|
|
1515
|
-
if (result.cached) {
|
|
1516
|
-
return result.content;
|
|
1517
|
-
}
|
|
1518
|
-
if (result.changed) {
|
|
1519
|
-
return `[file changed since last read]
|
|
1520
|
-
|
|
1521
|
-
${result.content}`;
|
|
1612
|
+
},
|
|
1613
|
+
{
|
|
1614
|
+
name: "kirograph_branch_search",
|
|
1615
|
+
description: "Search for symbols in a specific tracked branch graph.",
|
|
1616
|
+
inputSchema: {
|
|
1617
|
+
type: "object",
|
|
1618
|
+
required: ["query", "branch"],
|
|
1619
|
+
properties: {
|
|
1620
|
+
query: { type: "string", description: "Search query (symbol name, partial match)" },
|
|
1621
|
+
branch: { type: "string", description: "Branch name to search in" },
|
|
1622
|
+
limit: { type: "number", description: "Max results (default 20)", default: 20 },
|
|
1623
|
+
projectPath: { type: "string" }
|
|
1522
1624
|
}
|
|
1523
|
-
return result.content;
|
|
1524
1625
|
}
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
return cached;
|
|
1535
|
-
}
|
|
1536
|
-
if (!fs.existsSync(resolvedPath)) {
|
|
1537
|
-
return `Error: File not found: ${resolvedPath}`;
|
|
1626
|
+
},
|
|
1627
|
+
// ── Pattern tools (require enablePatterns=true) ─────────────────────────────
|
|
1628
|
+
{
|
|
1629
|
+
name: "kirograph_pattern_coverage",
|
|
1630
|
+
description: "OWASP Top 10 coverage report: categories covered and match counts.",
|
|
1631
|
+
inputSchema: {
|
|
1632
|
+
type: "object",
|
|
1633
|
+
properties: {
|
|
1634
|
+
projectPath: { type: "string" }
|
|
1538
1635
|
}
|
|
1539
|
-
const result = cache.read(resolvedPath, true);
|
|
1540
|
-
return result.content;
|
|
1541
1636
|
}
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
if (command) {
|
|
1552
|
-
const shellLevel = rawLevel === "ultra" ? "ultra" : rawLevel === "lite" || rawLevel === "normal" ? "normal" : "aggressive";
|
|
1553
|
-
const { compress: compress2 } = await Promise.resolve().then(() => require("../compression/index.js"));
|
|
1554
|
-
const result = compress2(command, text, { level: shellLevel });
|
|
1555
|
-
compressed = result.output;
|
|
1556
|
-
strategy = `rtk:${result.commandFamily}:${shellLevel}`;
|
|
1557
|
-
} else {
|
|
1558
|
-
const cavemanLevel = rawLevel === "normal" || rawLevel === "lite" ? "lite" : rawLevel === "aggressive" ? "full" : rawLevel;
|
|
1559
|
-
const { compressObservation } = await Promise.resolve().then(() => require("../memory/compress.js"));
|
|
1560
|
-
const result = compressObservation(text, cavemanLevel);
|
|
1561
|
-
compressed = result.compressed;
|
|
1562
|
-
strategy = `caveman:${cavemanLevel}`;
|
|
1637
|
+
},
|
|
1638
|
+
{
|
|
1639
|
+
name: "kirograph_pattern_save_baseline",
|
|
1640
|
+
description: "Save current pattern match counts as a baseline.",
|
|
1641
|
+
inputSchema: {
|
|
1642
|
+
type: "object",
|
|
1643
|
+
properties: {
|
|
1644
|
+
label: { type: "string", description: 'Baseline label (default: "default")', default: "default" },
|
|
1645
|
+
projectPath: { type: "string" }
|
|
1563
1646
|
}
|
|
1564
|
-
const compressedTokens = est(compressed);
|
|
1565
|
-
const savings = originalTokens > 0 ? Math.round((originalTokens - compressedTokens) / originalTokens * 100) : 0;
|
|
1566
|
-
const footer = savings > 5 ? `
|
|
1567
|
-
|
|
1568
|
-
[${savings}% tokens saved | ${originalTokens}\u2192${compressedTokens} | ${strategy}]` : "";
|
|
1569
|
-
return compressed + footer;
|
|
1570
1647
|
}
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
const status = budget.getStatus();
|
|
1581
|
-
const lines = [
|
|
1582
|
-
"Context Budget:",
|
|
1583
|
-
` Tokens consumed: ${status.consumed.toLocaleString()}`,
|
|
1584
|
-
` Budget limit: ${status.limit > 0 ? status.limit.toLocaleString() : "unlimited"}`,
|
|
1585
|
-
` Remaining: ${status.limit > 0 ? status.remaining.toLocaleString() : "\u221E"}`,
|
|
1586
|
-
` Utilization: ${status.utilization}%`
|
|
1587
|
-
];
|
|
1588
|
-
if (status.warning) {
|
|
1589
|
-
lines.push(`
|
|
1590
|
-
\u26A0 ${status.warning}`);
|
|
1648
|
+
},
|
|
1649
|
+
{
|
|
1650
|
+
name: "kirograph_pattern_diff",
|
|
1651
|
+
description: "Diff pattern matches against a saved baseline.",
|
|
1652
|
+
inputSchema: {
|
|
1653
|
+
type: "object",
|
|
1654
|
+
properties: {
|
|
1655
|
+
label: { type: "string", description: 'Baseline label (default: "default")', default: "default" },
|
|
1656
|
+
projectPath: { type: "string" }
|
|
1591
1657
|
}
|
|
1592
|
-
return lines.join("\n");
|
|
1593
1658
|
}
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
(r) => `${mapKind(r.node.kind)} ${r.node.name}
|
|
1608
|
-
File: ${r.node.filePath}:${r.node.startLine}
|
|
1609
|
-
Qualified: ${r.node.qualifiedName}`
|
|
1610
|
-
).join("\n\n");
|
|
1611
|
-
}
|
|
1612
|
-
case "kirograph_context": {
|
|
1613
|
-
const detail = args.detail ?? (args.includeCode === false ? "summary" : "full");
|
|
1614
|
-
const ctx = await cg.buildContext(args.task, {
|
|
1615
|
-
maxNodes: args.maxNodes ?? 20,
|
|
1616
|
-
includeCode: detail === "full"
|
|
1617
|
-
});
|
|
1618
|
-
const lines = [ctx.summary, ""];
|
|
1619
|
-
if (ctx.entryPoints.length === 0) {
|
|
1620
|
-
lines.push("No matching symbols found. If this is a new feature, consider using kirograph_files to explore the codebase structure.");
|
|
1621
|
-
} else {
|
|
1622
|
-
lines.push("## Entry Points");
|
|
1623
|
-
for (const n of ctx.entryPoints) {
|
|
1624
|
-
lines.push(`- ${mapKind(n.kind)} \`${n.name}\` \u2014 ${n.filePath}:${n.startLine}`);
|
|
1625
|
-
if (detail === "full" && ctx.codeSnippets.has(n.id)) {
|
|
1626
|
-
lines.push("```", ctx.codeSnippets.get(n.id), "```");
|
|
1627
|
-
} else if (detail === "signatures") {
|
|
1628
|
-
if (n.signature) lines.push(` Signature: ${n.signature}`);
|
|
1629
|
-
if (n.docstring) lines.push(` Docs: ${n.docstring}`);
|
|
1630
|
-
}
|
|
1631
|
-
}
|
|
1632
|
-
if (ctx.relatedNodes.length > 0) {
|
|
1633
|
-
lines.push("", "## Related Symbols");
|
|
1634
|
-
for (const n of ctx.relatedNodes.slice(0, 10)) {
|
|
1635
|
-
lines.push(`- ${mapKind(n.kind)} \`${n.name}\` \u2014 ${n.filePath}:${n.startLine}`);
|
|
1636
|
-
}
|
|
1637
|
-
}
|
|
1638
|
-
}
|
|
1639
|
-
try {
|
|
1640
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
1641
|
-
const projectRoot = cg.getProjectRoot();
|
|
1642
|
-
const config = await loadConfig(projectRoot);
|
|
1643
|
-
if (config.enableMemory) {
|
|
1644
|
-
const { MemoryManager } = await Promise.resolve().then(() => require("../memory/index.js"));
|
|
1645
|
-
const db = cg.getDatabase();
|
|
1646
|
-
db.applyMemorySchema();
|
|
1647
|
-
const mem = new MemoryManager(config, db.getRawDb());
|
|
1648
|
-
mem.initialize();
|
|
1649
|
-
const qualifiedNames = [
|
|
1650
|
-
...ctx.entryPoints.map((n) => n.qualifiedName),
|
|
1651
|
-
...ctx.relatedNodes.slice(0, 5).map((n) => n.qualifiedName)
|
|
1652
|
-
].filter(Boolean);
|
|
1653
|
-
const contextLimit = config.memoryContextLimit ?? 3;
|
|
1654
|
-
const contextThreshold = config.memoryContextThreshold ?? 0.3;
|
|
1655
|
-
let memResults = mem.getLinkedObservations(qualifiedNames, contextLimit, contextThreshold);
|
|
1656
|
-
if (memResults.length === 0) {
|
|
1657
|
-
memResults = (await mem.search(args.task, { limit: contextLimit })).filter((r) => r.score >= contextThreshold);
|
|
1658
|
-
}
|
|
1659
|
-
if (memResults.length > 0) {
|
|
1660
|
-
lines.push("", "## Related Memory");
|
|
1661
|
-
for (const r of memResults.slice(0, contextLimit)) {
|
|
1662
|
-
lines.push(`- [${r.observation.kind}] ${r.observation.content}`);
|
|
1663
|
-
}
|
|
1664
|
-
}
|
|
1665
|
-
}
|
|
1666
|
-
} catch {
|
|
1667
|
-
}
|
|
1668
|
-
try {
|
|
1669
|
-
const projectRoot2 = cg.getProjectRoot();
|
|
1670
|
-
const config2 = await (await Promise.resolve().then(() => require("../config.js"))).loadConfig(projectRoot2);
|
|
1671
|
-
if (config2.enableDocs && config2.docsContextLimit > 0) {
|
|
1672
|
-
const db2 = cg.getDatabase();
|
|
1673
|
-
db2.applyDocsSchema();
|
|
1674
|
-
const { DocsQueries } = await Promise.resolve().then(() => require("../docs/queries.js"));
|
|
1675
|
-
const docsQueries = new DocsQueries(db2.getRawDb(), projectRoot2);
|
|
1676
|
-
const qNames = ctx.entryPoints.map((n) => n.qualifiedName).filter(Boolean);
|
|
1677
|
-
if (qNames.length > 0) {
|
|
1678
|
-
const docRefs = docsQueries.getRefs({ qualifiedName: qNames[0] });
|
|
1679
|
-
const additionalRefs = qNames.slice(1, 5).flatMap((qn) => docsQueries.getRefs({ qualifiedName: qn }));
|
|
1680
|
-
const allDocRefs = [...docRefs, ...additionalRefs];
|
|
1681
|
-
const seenSections = /* @__PURE__ */ new Set();
|
|
1682
|
-
const uniqueRefs = allDocRefs.filter((r) => {
|
|
1683
|
-
if (seenSections.has(r.sectionId)) return false;
|
|
1684
|
-
seenSections.add(r.sectionId);
|
|
1685
|
-
return r.confidence >= config2.docsContextThreshold;
|
|
1686
|
-
}).slice(0, config2.docsContextLimit);
|
|
1687
|
-
if (uniqueRefs.length > 0) {
|
|
1688
|
-
lines.push("", "## Related Documentation");
|
|
1689
|
-
for (const ref of uniqueRefs) {
|
|
1690
|
-
const section = docsQueries.getSection(ref.sectionId);
|
|
1691
|
-
if (section) {
|
|
1692
|
-
const summary = section.section.summary ?? section.section.title;
|
|
1693
|
-
lines.push(`- [${ref.refType}] ${summary} \u2014 ${section.section.filePath} (ID: ${ref.sectionId})`);
|
|
1694
|
-
}
|
|
1695
|
-
}
|
|
1696
|
-
}
|
|
1697
|
-
}
|
|
1698
|
-
}
|
|
1699
|
-
} catch {
|
|
1700
|
-
}
|
|
1701
|
-
try {
|
|
1702
|
-
const projectRoot3 = cg.getProjectRoot();
|
|
1703
|
-
const config3 = await (await Promise.resolve().then(() => require("../config.js"))).loadConfig(projectRoot3);
|
|
1704
|
-
if (config3.enableData && config3.dataContextLimit > 0) {
|
|
1705
|
-
const db3 = cg.getDatabase();
|
|
1706
|
-
db3.applyDataSchema();
|
|
1707
|
-
const entryFiles = ctx.entryPoints.map((n) => n.filePath).filter(Boolean);
|
|
1708
|
-
if (entryFiles.length > 0) {
|
|
1709
|
-
const placeholders = entryFiles.map(() => "?").join(", ");
|
|
1710
|
-
const dataRefs = db3.getRawDb().all(
|
|
1711
|
-
`SELECT DISTINCT d.id, d.file_path, d.row_count, d.column_count
|
|
1712
|
-
FROM data_code_refs r JOIN data_datasets d ON r.dataset_id = d.id
|
|
1713
|
-
WHERE r.qualified_name IN (${placeholders})`,
|
|
1714
|
-
entryFiles
|
|
1715
|
-
);
|
|
1716
|
-
if (dataRefs.length > 0) {
|
|
1717
|
-
const { DataQueries } = await Promise.resolve().then(() => require("../data/queries.js"));
|
|
1718
|
-
const dq = new DataQueries(db3.getRawDb());
|
|
1719
|
-
const limit = config3.dataContextLimit;
|
|
1720
|
-
lines.push("", "## Related Data");
|
|
1721
|
-
for (const ref of dataRefs.slice(0, limit)) {
|
|
1722
|
-
const info = dq.describeDataset(ref.id);
|
|
1723
|
-
if (info) {
|
|
1724
|
-
const colSummary = info.columns.map((c) => `${c.name}:${c.inferredType}`).join(", ");
|
|
1725
|
-
lines.push(`- **${ref.id}** (${ref.file_path}) \u2014 ${ref.row_count} rows, ${ref.column_count} cols`);
|
|
1726
|
-
lines.push(` Schema: ${colSummary}`);
|
|
1727
|
-
}
|
|
1728
|
-
}
|
|
1729
|
-
}
|
|
1730
|
-
}
|
|
1731
|
-
}
|
|
1732
|
-
} catch {
|
|
1733
|
-
}
|
|
1734
|
-
try {
|
|
1735
|
-
const projectRootSec = cg.getProjectRoot();
|
|
1736
|
-
const configSec = await (await Promise.resolve().then(() => require("../config.js"))).loadConfig(projectRootSec);
|
|
1737
|
-
if (configSec.enableSecurity) {
|
|
1738
|
-
const dbSec = cg.getDatabase();
|
|
1739
|
-
dbSec.applySecuritySchema();
|
|
1740
|
-
const rawDbSec = dbSec.getRawDb();
|
|
1741
|
-
const contextNodeIds = [
|
|
1742
|
-
...ctx.entryPoints.map((n) => n.id),
|
|
1743
|
-
...ctx.relatedNodes.map((n) => n.id)
|
|
1744
|
-
].filter(Boolean);
|
|
1745
|
-
if (contextNodeIds.length > 0) {
|
|
1746
|
-
const { getSecurityWarningsForNodes, formatSecurityWarnings } = await Promise.resolve().then(() => require("../security/context-warnings.js"));
|
|
1747
|
-
const warnings = getSecurityWarningsForNodes(rawDbSec, contextNodeIds);
|
|
1748
|
-
if (warnings.length > 0) {
|
|
1749
|
-
const nodeNames = /* @__PURE__ */ new Map();
|
|
1750
|
-
for (const n of ctx.entryPoints) {
|
|
1751
|
-
nodeNames.set(n.id, n.name);
|
|
1752
|
-
}
|
|
1753
|
-
for (const n of ctx.relatedNodes) {
|
|
1754
|
-
nodeNames.set(n.id, n.name);
|
|
1755
|
-
}
|
|
1756
|
-
const secSection = formatSecurityWarnings(warnings, nodeNames);
|
|
1757
|
-
if (secSection) {
|
|
1758
|
-
lines.push(secSection);
|
|
1759
|
-
}
|
|
1760
|
-
}
|
|
1761
|
-
}
|
|
1762
|
-
}
|
|
1763
|
-
} catch {
|
|
1764
|
-
}
|
|
1765
|
-
try {
|
|
1766
|
-
const projectRootPat = cg.getProjectRoot();
|
|
1767
|
-
const configPat = await (await Promise.resolve().then(() => require("../config.js"))).loadConfig(projectRootPat);
|
|
1768
|
-
if (configPat.enablePatterns) {
|
|
1769
|
-
const dbPat = cg.getDatabase();
|
|
1770
|
-
dbPat.applyPatternsSchema();
|
|
1771
|
-
const rawDbPat = dbPat.getRawDb();
|
|
1772
|
-
const tableExists = rawDbPat.get("SELECT name FROM sqlite_master WHERE type='table' AND name='pattern_matches'");
|
|
1773
|
-
if (tableExists) {
|
|
1774
|
-
const contextFiles = [
|
|
1775
|
-
...ctx.entryPoints.map((n) => n.filePath),
|
|
1776
|
-
...ctx.relatedNodes.map((n) => n.filePath)
|
|
1777
|
-
].filter(Boolean);
|
|
1778
|
-
const uniqueContextFiles = [...new Set(contextFiles)];
|
|
1779
|
-
if (uniqueContextFiles.length > 0) {
|
|
1780
|
-
const placeholders = uniqueContextFiles.map(() => "?").join(",");
|
|
1781
|
-
const patternRows = rawDbPat.all(
|
|
1782
|
-
`SELECT file_path, pattern_id, severity, owasp_category, line FROM pattern_matches WHERE file_path IN (${placeholders}) ORDER BY severity DESC LIMIT 5`,
|
|
1783
|
-
uniqueContextFiles
|
|
1784
|
-
);
|
|
1785
|
-
if (patternRows.length > 0) {
|
|
1786
|
-
const patternWarnings = patternRows.map(
|
|
1787
|
-
(p) => `- **${p.pattern_id}** [${p.severity.toUpperCase()}/${p.owasp_category}]: ${p.file_path}:${p.line}`
|
|
1788
|
-
).join("\n");
|
|
1789
|
-
lines.push("", "## \u26A0 Pattern Findings", "", patternWarnings);
|
|
1790
|
-
if (patternRows.length === 5) lines.push("", "More findings \u2014 run `kirograph pattern --list` to see all rules.");
|
|
1791
|
-
}
|
|
1792
|
-
}
|
|
1793
|
-
}
|
|
1794
|
-
}
|
|
1795
|
-
} catch {
|
|
1796
|
-
}
|
|
1797
|
-
const graphTokens = lines.join("\n").length / 4;
|
|
1798
|
-
const uniqueFiles = /* @__PURE__ */ new Set([
|
|
1799
|
-
...ctx.entryPoints.map((n) => n.filePath),
|
|
1800
|
-
...ctx.relatedNodes.map((n) => n.filePath)
|
|
1801
|
-
]);
|
|
1802
|
-
if (uniqueFiles.size > 0) {
|
|
1803
|
-
const naiveTokens = estimateFileTokens(cg.getProjectRoot(), [...uniqueFiles]);
|
|
1804
|
-
if (naiveTokens > 0) {
|
|
1805
|
-
const savingsPct = Math.round((1 - graphTokens / naiveTokens) * 100);
|
|
1806
|
-
if (savingsPct > 0) {
|
|
1807
|
-
lines.push("", `---`, `Context savings: ~${Math.round(graphTokens)} tokens (graph) vs ~${naiveTokens} tokens (full files) \u2014 ${savingsPct}% reduction`);
|
|
1808
|
-
}
|
|
1809
|
-
}
|
|
1810
|
-
}
|
|
1811
|
-
return lines.join("\n");
|
|
1812
|
-
}
|
|
1813
|
-
case "kirograph_callers": {
|
|
1814
|
-
const limit = clampLimit(args.limit, 20);
|
|
1815
|
-
const results = cg.searchNodes(args.symbol, void 0, 5);
|
|
1816
|
-
if (results.length === 0) return `Symbol "${args.symbol}" not found in index.`;
|
|
1817
|
-
const node = results[0].node;
|
|
1818
|
-
const callers = await cg.getCallers(node.id, limit);
|
|
1819
|
-
if (callers.length === 0) return `No callers found for \`${node.name}\`.`;
|
|
1820
|
-
return `Callers of \`${node.name}\`:
|
|
1821
|
-
` + callers.map(
|
|
1822
|
-
(n) => `- ${mapKind(n.kind)} \`${n.name}\` \u2014 ${n.filePath}:${n.startLine}`
|
|
1823
|
-
).join("\n");
|
|
1824
|
-
}
|
|
1825
|
-
case "kirograph_callees": {
|
|
1826
|
-
const limit = clampLimit(args.limit, 20);
|
|
1827
|
-
const results = cg.searchNodes(args.symbol, void 0, 5);
|
|
1828
|
-
if (results.length === 0) return `Symbol "${args.symbol}" not found in index.`;
|
|
1829
|
-
const node = results[0].node;
|
|
1830
|
-
const callees = await cg.getCallees(node.id, limit);
|
|
1831
|
-
if (callees.length === 0) return `\`${node.name}\` doesn't call any indexed symbols.`;
|
|
1832
|
-
return `\`${node.name}\` calls:
|
|
1833
|
-
` + callees.map(
|
|
1834
|
-
(n) => `- ${mapKind(n.kind)} \`${n.name}\` \u2014 ${n.filePath}:${n.startLine}`
|
|
1835
|
-
).join("\n");
|
|
1836
|
-
}
|
|
1837
|
-
case "kirograph_impact": {
|
|
1838
|
-
const results = cg.searchNodes(args.symbol, void 0, 5);
|
|
1839
|
-
if (results.length === 0) return `Symbol "${args.symbol}" not found in index.`;
|
|
1840
|
-
const node = results[0].node;
|
|
1841
|
-
const affected = await cg.getImpactRadius(node.id, args.depth ?? 2);
|
|
1842
|
-
if (affected.length === 0) return `No dependents found for \`${node.name}\`.`;
|
|
1843
|
-
let output = `Changing \`${node.name}\` may affect ${affected.length} symbol(s):
|
|
1844
|
-
` + affected.map((n) => `- ${mapKind(n.kind)} \`${n.name}\` \u2014 ${n.filePath}:${n.startLine}`).join("\n");
|
|
1845
|
-
try {
|
|
1846
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
1847
|
-
const projectRoot = cg.getProjectRoot();
|
|
1848
|
-
const config = await loadConfig(projectRoot);
|
|
1849
|
-
if (config.enableMemory) {
|
|
1850
|
-
const { MemoryManager } = await Promise.resolve().then(() => require("../memory/index.js"));
|
|
1851
|
-
const db = cg.getDatabase();
|
|
1852
|
-
db.applyMemorySchema();
|
|
1853
|
-
const mem = new MemoryManager(config, db.getRawDb());
|
|
1854
|
-
mem.initialize();
|
|
1855
|
-
const contextLimit = config.memoryContextLimit ?? 3;
|
|
1856
|
-
const contextThreshold = config.memoryContextThreshold ?? 0.3;
|
|
1857
|
-
const memResults = mem.getLinkedObservations(
|
|
1858
|
-
[node.qualifiedName],
|
|
1859
|
-
contextLimit,
|
|
1860
|
-
contextThreshold
|
|
1861
|
-
);
|
|
1862
|
-
if (memResults.length > 0) {
|
|
1863
|
-
output += "\n\nRelated Memory:";
|
|
1864
|
-
for (const r of memResults) {
|
|
1865
|
-
const age = formatAge(r.observation.createdAt);
|
|
1866
|
-
output += `
|
|
1867
|
-
- [${r.observation.kind}] ${r.observation.content} (${age})`;
|
|
1868
|
-
}
|
|
1869
|
-
}
|
|
1870
|
-
}
|
|
1871
|
-
} catch {
|
|
1872
|
-
}
|
|
1873
|
-
try {
|
|
1874
|
-
const rawDbImp = cg.getDatabase().getRawDb();
|
|
1875
|
-
const tableExistsImp = rawDbImp.get("SELECT name FROM sqlite_master WHERE type='table' AND name='pattern_matches'");
|
|
1876
|
-
if (tableExistsImp) {
|
|
1877
|
-
const symNode = rawDbImp.get("SELECT id FROM nodes WHERE name = ? LIMIT 1", [args.symbol]);
|
|
1878
|
-
if (symNode) {
|
|
1879
|
-
const patternMatches = rawDbImp.all(
|
|
1880
|
-
"SELECT pattern_id, severity, line FROM pattern_matches WHERE symbol_node_id = ?",
|
|
1881
|
-
[symNode.id]
|
|
1882
|
-
);
|
|
1883
|
-
if (patternMatches.length > 0) {
|
|
1884
|
-
output += "\n\n\u26A0 This symbol has pattern matches:";
|
|
1885
|
-
for (const pm of patternMatches) {
|
|
1886
|
-
output += `
|
|
1887
|
-
[${pm.severity.toUpperCase()}] ${pm.pattern_id} at line ${pm.line}`;
|
|
1888
|
-
}
|
|
1889
|
-
}
|
|
1890
|
-
}
|
|
1891
|
-
}
|
|
1892
|
-
} catch {
|
|
1893
|
-
}
|
|
1894
|
-
return output;
|
|
1895
|
-
}
|
|
1896
|
-
case "kirograph_node": {
|
|
1897
|
-
const results = cg.searchNodes(args.symbol, void 0, 5);
|
|
1898
|
-
if (results.length === 0) return `Symbol "${args.symbol}" not found in index.`;
|
|
1899
|
-
const node = results[0].node;
|
|
1900
|
-
const nodeDetail = args.detail ?? (args.includeCode ? "full" : "summary");
|
|
1901
|
-
const lines = [
|
|
1902
|
-
`${mapKind(node.kind)} \`${node.name}\``,
|
|
1903
|
-
`File: ${node.filePath}:${node.startLine}-${node.endLine}`,
|
|
1904
|
-
`Qualified: ${node.qualifiedName}`
|
|
1905
|
-
];
|
|
1906
|
-
if (nodeDetail === "signatures" || nodeDetail === "full") {
|
|
1907
|
-
if (node.signature) lines.push(`Signature: ${node.signature}`);
|
|
1908
|
-
if (node.docstring) lines.push(`Docs: ${node.docstring}`);
|
|
1909
|
-
}
|
|
1910
|
-
if (nodeDetail === "full") {
|
|
1911
|
-
const src = cg.getNodeSource(node);
|
|
1912
|
-
if (src) lines.push("", "```", src, "```");
|
|
1913
|
-
}
|
|
1914
|
-
return lines.join("\n");
|
|
1915
|
-
}
|
|
1916
|
-
case "kirograph_status": {
|
|
1917
|
-
const stats = await cg.getStats();
|
|
1918
|
-
const langLine = Object.entries(stats.filesByLanguage).sort((a, b) => b[1] - a[1]).map(([k, v]) => `${k}=${v}`).join(", ");
|
|
1919
|
-
const dbMb = (stats.dbSizeBytes / 1024 / 1024).toFixed(2);
|
|
1920
|
-
const semanticLines = stats.embeddingsEnabled ? [
|
|
1921
|
-
` Semantic search: enabled`,
|
|
1922
|
-
` Semantic model: ${stats.embeddingModel}`,
|
|
1923
|
-
` Semantic engine: ${stats.semanticEngine === "turboquant" ? `turboquant (${stats.vecIndexCount} entries \xB7 ANN)` : stats.semanticEngine === "turbovec" ? `turbovec (${stats.vecIndexCount} entries \xB7 ${stats.turbovecBits ?? 4} bits \xB7 Rust/SIMD)` : stats.semanticEngine === "sqlite-vec" ? `sqlite-vec (${stats.vecIndexCount} entries in ANN index)` : stats.semanticEngine === "orama" ? `orama hybrid (${stats.vecIndexCount} docs in index)` : stats.semanticEngine === "pglite" ? `pglite+pgvector (${stats.vecIndexCount} rows in DB)` : stats.semanticEngine === "lancedb" ? `lancedb (${stats.vecIndexCount} entries in ANN index)` : stats.semanticEngine === "qdrant" ? `qdrant (${stats.vecIndexCount} points in collection)` : stats.semanticEngine === "typesense" ? `typesense (${stats.vecIndexCount} documents in collection)` : "in-process cosine"}`,
|
|
1924
|
-
` Embeddings: ${stats.embeddingCount} / ${stats.embeddableNodeCount || stats.nodes} embeddable symbols`,
|
|
1925
|
-
...stats.engineFallback ? [` \u26A0 Engine fallback: ${stats.engineFallback}`] : []
|
|
1926
|
-
] : [` Semantic search: disabled`];
|
|
1927
|
-
const frameworkLine = stats.frameworks.length > 0 ? ` Frameworks: ${stats.frameworks.join(", ")}` : ` Frameworks: none detected`;
|
|
1928
|
-
const archLine = stats.architectureEnabled ? stats.architectureStats ? ` Architecture: enabled \u2014 ${stats.architectureStats.packages} packages, ${stats.architectureStats.layers} layers, ${stats.architectureStats.packageDeps} deps` : ` Architecture: enabled (not yet analyzed \u2014 run kirograph index)` : ` Architecture: disabled`;
|
|
1929
|
-
let docsLine = " Documentation: disabled";
|
|
1930
|
-
try {
|
|
1931
|
-
const { loadConfig: loadCfg } = await Promise.resolve().then(() => require("../config.js"));
|
|
1932
|
-
const cfg = await loadCfg(cg.getProjectRoot());
|
|
1933
|
-
if (cfg.enableDocs) {
|
|
1934
|
-
const db = cg.getDatabase();
|
|
1935
|
-
db.applyDocsSchema();
|
|
1936
|
-
const rawDb = db.getRawDb();
|
|
1937
|
-
const docFiles = rawDb.get("SELECT COUNT(DISTINCT file_path) as cnt FROM doc_sections")?.cnt ?? 0;
|
|
1938
|
-
const docSections = rawDb.get("SELECT COUNT(*) as cnt FROM doc_sections")?.cnt ?? 0;
|
|
1939
|
-
const docRefs = rawDb.get("SELECT COUNT(*) as cnt FROM doc_code_refs")?.cnt ?? 0;
|
|
1940
|
-
docsLine = ` Documentation: enabled \u2014 ${docFiles} files, ${docSections} sections, ${docRefs} code refs`;
|
|
1941
|
-
}
|
|
1942
|
-
} catch {
|
|
1943
|
-
}
|
|
1944
|
-
let dataLine = " Data: disabled";
|
|
1945
|
-
try {
|
|
1946
|
-
const { loadConfig: loadCfg2 } = await Promise.resolve().then(() => require("../config.js"));
|
|
1947
|
-
const cfg2 = await loadCfg2(cg.getProjectRoot());
|
|
1948
|
-
if (cfg2.enableData) {
|
|
1949
|
-
const rawDb2 = cg.getDatabase().getRawDb();
|
|
1950
|
-
cg.getDatabase().applyDataSchema();
|
|
1951
|
-
const datasetCount = rawDb2.get("SELECT COUNT(*) as cnt FROM data_datasets")?.cnt ?? 0;
|
|
1952
|
-
if (datasetCount > 0) {
|
|
1953
|
-
const totalRows = rawDb2.get("SELECT SUM(row_count) as total FROM data_datasets")?.total ?? 0;
|
|
1954
|
-
const totalCols = rawDb2.get("SELECT SUM(column_count) as total FROM data_datasets")?.total ?? 0;
|
|
1955
|
-
const totalSize = rawDb2.get("SELECT SUM(file_size) as total FROM data_datasets")?.total ?? 0;
|
|
1956
|
-
const sizeMb = (totalSize / 1024 / 1024).toFixed(2);
|
|
1957
|
-
dataLine = ` Data: enabled \u2014 ${datasetCount} datasets, ${totalRows.toLocaleString()} rows, ${totalCols} columns (${sizeMb} MB source)`;
|
|
1958
|
-
} else {
|
|
1959
|
-
dataLine = ` Data: enabled (no datasets indexed yet \u2014 run kirograph index)`;
|
|
1960
|
-
}
|
|
1961
|
-
}
|
|
1962
|
-
} catch {
|
|
1963
|
-
}
|
|
1964
|
-
let securityLine = " Security: disabled";
|
|
1965
|
-
try {
|
|
1966
|
-
const { loadConfig: loadCfgSec } = await Promise.resolve().then(() => require("../config.js"));
|
|
1967
|
-
const cfgSec = await loadCfgSec(cg.getProjectRoot());
|
|
1968
|
-
if (cfgSec.enableSecurity) {
|
|
1969
|
-
const db = cg.getDatabase();
|
|
1970
|
-
db.applySecuritySchema();
|
|
1971
|
-
const rawDb = db.getRawDb();
|
|
1972
|
-
const depCount = rawDb.get("SELECT COUNT(*) as cnt FROM sec_dependencies")?.cnt ?? 0;
|
|
1973
|
-
const vulnCount = rawDb.get("SELECT COUNT(*) as cnt FROM sec_vulnerabilities")?.cnt ?? 0;
|
|
1974
|
-
const affectedCount = rawDb.get("SELECT COUNT(*) as cnt FROM sec_reachability WHERE verdict = 'affected'")?.cnt ?? 0;
|
|
1975
|
-
const staleCount = rawDb.get("SELECT COUNT(*) as cnt FROM sec_dependencies WHERE vuln_data_stale = 1")?.cnt ?? 0;
|
|
1976
|
-
const staleNote = staleCount > 0 ? ` \u26A0 ${staleCount} stale` : "";
|
|
1977
|
-
securityLine = ` Security: enabled \u2014 ${depCount} deps, ${vulnCount} vulns (${affectedCount} affected)${staleNote}`;
|
|
1978
|
-
}
|
|
1979
|
-
} catch {
|
|
1980
|
-
}
|
|
1981
|
-
let patternsLine = " Patterns: disabled";
|
|
1982
|
-
try {
|
|
1983
|
-
const { loadConfig: loadCfgPat } = await Promise.resolve().then(() => require("../config.js"));
|
|
1984
|
-
const cfgP = await loadCfgPat(cg.getProjectRoot());
|
|
1985
|
-
if (cfgP.enablePatterns) {
|
|
1986
|
-
const rawDbP = cg.getDatabase().getRawDb();
|
|
1987
|
-
const tableExistsP = rawDbP.get("SELECT name FROM sqlite_master WHERE type='table' AND name='pattern_matches'");
|
|
1988
|
-
if (tableExistsP) {
|
|
1989
|
-
const matchCount = rawDbP.get("SELECT COUNT(*) as cnt FROM pattern_matches")?.cnt ?? 0;
|
|
1990
|
-
const fileCount = rawDbP.get("SELECT COUNT(DISTINCT file_path) as cnt FROM pattern_matches")?.cnt ?? 0;
|
|
1991
|
-
const ruleCount = rawDbP.get("SELECT COUNT(DISTINCT pattern_id) as cnt FROM pattern_matches")?.cnt ?? 0;
|
|
1992
|
-
patternsLine = ` Patterns: enabled \u2014 ${matchCount} matches, ${ruleCount} rules triggered, ${fileCount} files`;
|
|
1993
|
-
} else {
|
|
1994
|
-
patternsLine = ` Patterns: enabled (no data yet \u2014 run kirograph index)`;
|
|
1995
|
-
}
|
|
1996
|
-
}
|
|
1997
|
-
} catch {
|
|
1998
|
-
}
|
|
1999
|
-
const threshold = stats.syncWarningThreshold ?? 10;
|
|
2000
|
-
const pendingFiles = stats.pendingFiles ?? 0;
|
|
2001
|
-
const syncRunning = stats.syncRunning ?? false;
|
|
2002
|
-
const syncLines = [];
|
|
2003
|
-
if (syncRunning) {
|
|
2004
|
-
syncLines.push(` \u26A0 Sync is currently running in the background.`);
|
|
2005
|
-
}
|
|
2006
|
-
if (threshold > 0 && pendingFiles >= threshold) {
|
|
2007
|
-
syncLines.push(
|
|
2008
|
-
` \u26A0 Index may be incomplete \u2014 ${pendingFiles} file${pendingFiles !== 1 ? "s" : ""} pending sync.` + (syncRunning ? " Sync is running in background." : " Run `kirograph sync` to update.") + ` Would you like to wait before proceeding?`
|
|
2009
|
-
);
|
|
2010
|
-
}
|
|
2011
|
-
const tracker = new import_tracker.TokenTracker(cg.getProjectRoot());
|
|
2012
|
-
const gainStats = tracker.getStats("session");
|
|
2013
|
-
const gainLines = [];
|
|
2014
|
-
if (gainStats.totalCommands > 0) {
|
|
2015
|
-
gainLines.push(` Compression: ${gainStats.totalCommands} commands, ${gainStats.savingsPercent}% avg savings (${gainStats.totalSaved.toLocaleString()} tokens saved this session)`);
|
|
2016
|
-
}
|
|
2017
|
-
return [
|
|
2018
|
-
`KiroGraph Status`,
|
|
2019
|
-
` Project: ${cg.getProjectRoot()}`,
|
|
2020
|
-
` Files indexed: ${stats.files}`,
|
|
2021
|
-
` Symbols: ${stats.nodes}`,
|
|
2022
|
-
` Relationships: ${stats.edges}`,
|
|
2023
|
-
` By kind: ${Object.entries(stats.nodesByKind).map(([k, v]) => `${k}=${v}`).join(", ")}`,
|
|
2024
|
-
langLine ? ` By language: ${langLine}` : "",
|
|
2025
|
-
frameworkLine,
|
|
2026
|
-
archLine,
|
|
2027
|
-
docsLine,
|
|
2028
|
-
dataLine,
|
|
2029
|
-
securityLine,
|
|
2030
|
-
patternsLine,
|
|
2031
|
-
` DB size: ${dbMb} MB`,
|
|
2032
|
-
...semanticLines,
|
|
2033
|
-
...syncLines,
|
|
2034
|
-
...gainLines
|
|
2035
|
-
].filter(Boolean).join("\n");
|
|
2036
|
-
}
|
|
2037
|
-
case "kirograph_files": {
|
|
2038
|
-
let renderTree2 = function(nodes, prefix) {
|
|
2039
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
2040
|
-
const node = nodes[i];
|
|
2041
|
-
const isLast = i === nodes.length - 1;
|
|
2042
|
-
const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
2043
|
-
const childPrefix = prefix + (isLast ? " " : "\u2502 ");
|
|
2044
|
-
const meta = includeMetadata && node.type === "file" && node.language ? ` [${node.language}${node.symbolCount ? ` \xB7 ${node.symbolCount} symbols` : ""}]` : "";
|
|
2045
|
-
lines.push(`${prefix}${connector}${node.name}${meta}`);
|
|
2046
|
-
if (node.children?.length) renderTree2(node.children, childPrefix);
|
|
2047
|
-
}
|
|
2048
|
-
};
|
|
2049
|
-
var renderTree = renderTree2;
|
|
2050
|
-
const format = args.format ?? "tree";
|
|
2051
|
-
const includeMetadata = args.includeMetadata !== false;
|
|
2052
|
-
const tree = cg.getFiles({
|
|
2053
|
-
filterPath: args.filterPath,
|
|
2054
|
-
pattern: args.pattern,
|
|
2055
|
-
maxDepth: args.maxDepth
|
|
2056
|
-
});
|
|
2057
|
-
if (format === "flat") {
|
|
2058
|
-
let flattenTree2 = function(nodes) {
|
|
2059
|
-
for (const node of nodes) {
|
|
2060
|
-
if (node.type === "file") {
|
|
2061
|
-
const meta = includeMetadata && node.language ? ` [${node.language}${node.symbolCount ? ` \xB7 ${node.symbolCount}` : ""}]` : "";
|
|
2062
|
-
flat.push(`${node.path}${meta}`);
|
|
2063
|
-
}
|
|
2064
|
-
if (node.children?.length) flattenTree2(node.children);
|
|
2065
|
-
}
|
|
2066
|
-
};
|
|
2067
|
-
var flattenTree = flattenTree2;
|
|
2068
|
-
const flat = [];
|
|
2069
|
-
flattenTree2(tree);
|
|
2070
|
-
return flat.length > 0 ? flat.join("\n") : "No indexed files found.";
|
|
2071
|
-
}
|
|
2072
|
-
if (format === "grouped") {
|
|
2073
|
-
let groupTree2 = function(nodes) {
|
|
2074
|
-
for (const node of nodes) {
|
|
2075
|
-
if (node.type === "file") {
|
|
2076
|
-
const dir = node.path.includes("/") ? node.path.slice(0, node.path.lastIndexOf("/")) : ".";
|
|
2077
|
-
if (!groups.has(dir)) groups.set(dir, []);
|
|
2078
|
-
groups.get(dir).push(node);
|
|
2079
|
-
}
|
|
2080
|
-
if (node.children?.length) groupTree2(node.children);
|
|
2081
|
-
}
|
|
2082
|
-
};
|
|
2083
|
-
var groupTree = groupTree2;
|
|
2084
|
-
const groups = /* @__PURE__ */ new Map();
|
|
2085
|
-
groupTree2(tree);
|
|
2086
|
-
const lines2 = [];
|
|
2087
|
-
for (const [dir, files] of [...groups.entries()].sort()) {
|
|
2088
|
-
lines2.push(`${dir}/`);
|
|
2089
|
-
for (const f of files) {
|
|
2090
|
-
const meta = includeMetadata && f.language ? ` [${f.language}${f.symbolCount ? ` \xB7 ${f.symbolCount}` : ""}]` : "";
|
|
2091
|
-
lines2.push(` ${f.name}${meta}`);
|
|
2092
|
-
}
|
|
2093
|
-
}
|
|
2094
|
-
return lines2.length > 0 ? lines2.join("\n") : "No indexed files found.";
|
|
2095
|
-
}
|
|
2096
|
-
if (format === "compact") {
|
|
2097
|
-
let compactTree2 = function(nodes) {
|
|
2098
|
-
for (const node of nodes) {
|
|
2099
|
-
if (node.type === "file") {
|
|
2100
|
-
const dir = node.path.includes("/") ? node.path.slice(0, node.path.lastIndexOf("/")) : ".";
|
|
2101
|
-
const stat = dirStats.get(dir) || { files: 0, symbols: 0, langs: /* @__PURE__ */ new Map() };
|
|
2102
|
-
stat.files++;
|
|
2103
|
-
stat.symbols += node.symbolCount || 0;
|
|
2104
|
-
if (node.language) stat.langs.set(node.language, (stat.langs.get(node.language) || 0) + 1);
|
|
2105
|
-
dirStats.set(dir, stat);
|
|
2106
|
-
}
|
|
2107
|
-
if (node.children?.length) compactTree2(node.children);
|
|
2108
|
-
}
|
|
2109
|
-
};
|
|
2110
|
-
var compactTree = compactTree2;
|
|
2111
|
-
const dirStats = /* @__PURE__ */ new Map();
|
|
2112
|
-
compactTree2(tree);
|
|
2113
|
-
const totalFiles = [...dirStats.values()].reduce((s, d) => s + d.files, 0);
|
|
2114
|
-
const totalSymbols = [...dirStats.values()].reduce((s, d) => s + d.symbols, 0);
|
|
2115
|
-
const lines2 = [`${totalFiles} files, ${totalSymbols} symbols in ${dirStats.size} directories:
|
|
2116
|
-
`];
|
|
2117
|
-
for (const [dir, stat] of [...dirStats.entries()].sort()) {
|
|
2118
|
-
const langSummary = [...stat.langs.entries()].map(([l, c]) => `${l}:${c}`).join(" ");
|
|
2119
|
-
lines2.push(`${dir}/ (${stat.files} files, ${stat.symbols} symbols) ${langSummary}`);
|
|
2120
|
-
}
|
|
2121
|
-
return lines2.join("\n");
|
|
2122
|
-
}
|
|
2123
|
-
const lines = [];
|
|
2124
|
-
renderTree2(tree, "");
|
|
2125
|
-
return lines.length > 0 ? lines.join("\n") : "No indexed files found.";
|
|
2126
|
-
}
|
|
2127
|
-
case "kirograph_dead_code": {
|
|
2128
|
-
const limit = clampLimit(args.limit, 50);
|
|
2129
|
-
const dead = cg.findDeadCode(limit);
|
|
2130
|
-
if (dead.length === 0) return "No dead code detected.";
|
|
2131
|
-
return `Potential dead code (${dead.length} unexported symbols with no incoming references):
|
|
2132
|
-
` + dead.map((n) => `- ${mapKind(n.kind)} \`${n.name}\` \u2014 ${n.filePath}:${n.startLine}`).join("\n");
|
|
2133
|
-
}
|
|
2134
|
-
case "kirograph_circular_deps": {
|
|
2135
|
-
const cycles = cg.findCircularDependencies();
|
|
2136
|
-
if (cycles.length === 0) return "No circular dependencies found.";
|
|
2137
|
-
return `Found ${cycles.length} circular dependency cycle(s):
|
|
2138
|
-
` + cycles.map((cycle, i) => `Cycle ${i + 1}: ${cycle.join(" \u2192 ")}`).join("\n");
|
|
2139
|
-
}
|
|
2140
|
-
case "kirograph_path": {
|
|
2141
|
-
const fromResults = cg.searchNodes(args.from, void 0, 3);
|
|
2142
|
-
const toResults = cg.searchNodes(args.to, void 0, 3);
|
|
2143
|
-
if (fromResults.length === 0) return `Symbol "${args.from}" not found in index.`;
|
|
2144
|
-
if (toResults.length === 0) return `Symbol "${args.to}" not found in index.`;
|
|
2145
|
-
const fromNode = fromResults[0].node;
|
|
2146
|
-
const toNode = toResults[0].node;
|
|
2147
|
-
const pathNodes = await cg.findPath(fromNode.id, toNode.id);
|
|
2148
|
-
if (pathNodes.length === 0) return `No path found between \`${fromNode.name}\` and \`${toNode.name}\`.`;
|
|
2149
|
-
return `Path from \`${fromNode.name}\` to \`${toNode.name}\` (${pathNodes.length} nodes):
|
|
2150
|
-
` + pathNodes.map((n, i) => `${i + 1}. ${mapKind(n.kind)} \`${n.name}\` \u2014 ${n.filePath}:${n.startLine}`).join("\n");
|
|
2151
|
-
}
|
|
2152
|
-
case "kirograph_type_hierarchy": {
|
|
2153
|
-
const results = cg.searchNodes(args.symbol, void 0, 5);
|
|
2154
|
-
if (results.length === 0) return `Symbol "${args.symbol}" not found in index.`;
|
|
2155
|
-
const node = results[0].node;
|
|
2156
|
-
const direction = args.direction ?? "both";
|
|
2157
|
-
const hierarchy = cg.getTypeHierarchy(node.id, direction);
|
|
2158
|
-
if (hierarchy.length === 0) return `No type hierarchy found for \`${node.name}\`.`;
|
|
2159
|
-
return `Type hierarchy for \`${node.name}\` (direction: ${direction}):
|
|
2160
|
-
` + hierarchy.map((n) => `- ${mapKind(n.kind)} \`${n.name}\` \u2014 ${n.filePath}:${n.startLine}`).join("\n");
|
|
2161
|
-
}
|
|
2162
|
-
case "kirograph_architecture": {
|
|
2163
|
-
if (!cg.isArchitectureEnabled()) {
|
|
2164
|
-
return "Architecture analysis is disabled. Set enableArchitecture=true in .kirograph/config.json and re-index.";
|
|
2165
|
-
}
|
|
2166
|
-
const level = args.level ?? "both";
|
|
2167
|
-
const includeFiles = args.includeFiles === true;
|
|
2168
|
-
const arch = cg.getArchitecture();
|
|
2169
|
-
const lines = ["# Architecture"];
|
|
2170
|
-
if ((level === "packages" || level === "both") && arch.packages.length > 0) {
|
|
2171
|
-
lines.push("\n## Packages");
|
|
2172
|
-
for (const pkg of arch.packages) {
|
|
2173
|
-
const meta = [pkg.language, pkg.version].filter(Boolean).join(", ");
|
|
2174
|
-
lines.push(`- **${pkg.name}** (${pkg.path}) [${pkg.source}${meta ? " \xB7 " + meta : ""}]`);
|
|
2175
|
-
}
|
|
2176
|
-
if (arch.packageDeps.length > 0) {
|
|
2177
|
-
lines.push("\n## Package Dependencies");
|
|
2178
|
-
for (const dep of arch.packageDeps) {
|
|
2179
|
-
const src = arch.packages.find((p) => p.id === dep.sourcePkg)?.name ?? dep.sourcePkg;
|
|
2180
|
-
const tgt = arch.packages.find((p) => p.id === dep.targetPkg)?.name ?? dep.targetPkg;
|
|
2181
|
-
lines.push(`- ${src} \u2192 ${tgt} (${dep.depCount} import${dep.depCount !== 1 ? "s" : ""})`);
|
|
2182
|
-
}
|
|
2183
|
-
}
|
|
2184
|
-
}
|
|
2185
|
-
if ((level === "layers" || level === "both") && arch.layers.length > 0) {
|
|
2186
|
-
lines.push("\n## Layers");
|
|
2187
|
-
for (const layer of arch.layers) {
|
|
2188
|
-
const fileCount = Object.values(arch.fileLayers).filter((fl) => fl.some((l) => l.layerId === layer.id)).length;
|
|
2189
|
-
lines.push(`- **${layer.name}** [${layer.source}] \u2014 ${fileCount} file${fileCount !== 1 ? "s" : ""}`);
|
|
2190
|
-
}
|
|
2191
|
-
if (arch.layerDeps.length > 0) {
|
|
2192
|
-
lines.push("\n## Layer Dependencies");
|
|
2193
|
-
for (const dep of arch.layerDeps) {
|
|
2194
|
-
const src = dep.sourceLayer.replace("layer:", "");
|
|
2195
|
-
const tgt = dep.targetLayer.replace("layer:", "");
|
|
2196
|
-
lines.push(`- ${src} \u2192 ${tgt} (${dep.depCount})`);
|
|
2197
|
-
}
|
|
2198
|
-
}
|
|
2199
|
-
}
|
|
2200
|
-
if (includeFiles && (level === "packages" || level === "both")) {
|
|
2201
|
-
lines.push("\n## File \u2192 Package");
|
|
2202
|
-
for (const [file, pkgIds] of Object.entries(arch.filePackages).slice(0, 50)) {
|
|
2203
|
-
const names = pkgIds.map((id) => arch.packages.find((p) => p.id === id)?.name ?? id).join(", ");
|
|
2204
|
-
lines.push(`- ${file}: ${names}`);
|
|
2205
|
-
}
|
|
2206
|
-
if (Object.keys(arch.filePackages).length > 50) lines.push(" \u2026(truncated)");
|
|
2207
|
-
}
|
|
2208
|
-
if (arch.packages.length === 0 && arch.layers.length === 0) {
|
|
2209
|
-
return "No architecture data found. Run `kirograph index` with enableArchitecture=true.";
|
|
2210
|
-
}
|
|
2211
|
-
return lines.join("\n");
|
|
2212
|
-
}
|
|
2213
|
-
case "kirograph_coupling": {
|
|
2214
|
-
if (!cg.isArchitectureEnabled()) {
|
|
2215
|
-
return "Architecture analysis is disabled. Set enableArchitecture=true in .kirograph/config.json and re-index.";
|
|
2216
|
-
}
|
|
2217
|
-
const sortBy = args.sortBy ?? "instability";
|
|
2218
|
-
const limit = clampLimit(args.limit, 20);
|
|
2219
|
-
const arch = cg.getArchitecture();
|
|
2220
|
-
if (arch.coupling.length === 0) {
|
|
2221
|
-
return "No coupling data. Run `kirograph index` with enableArchitecture=true.";
|
|
2222
|
-
}
|
|
2223
|
-
const sorted = [...arch.coupling].sort((a, b) => {
|
|
2224
|
-
if (sortBy === "afferent") return b.afferent - a.afferent;
|
|
2225
|
-
if (sortBy === "efferent") return b.efferent - a.efferent;
|
|
2226
|
-
return b.instability - a.instability;
|
|
2227
|
-
}).slice(0, limit);
|
|
2228
|
-
const lines = [
|
|
2229
|
-
`Coupling Metrics (sorted by ${sortBy}):`,
|
|
2230
|
-
"",
|
|
2231
|
-
"Package Ca Ce I",
|
|
2232
|
-
"\u2500".repeat(52)
|
|
2233
|
-
];
|
|
2234
|
-
for (const c of sorted) {
|
|
2235
|
-
const pkg = arch.packages.find((p) => p.id === c.packageId);
|
|
2236
|
-
const name = (pkg?.name ?? c.packageId).slice(0, 32).padEnd(32);
|
|
2237
|
-
const ca = String(c.afferent).padStart(4);
|
|
2238
|
-
const ce = String(c.efferent).padStart(4);
|
|
2239
|
-
const inst = c.instability.toFixed(2).padStart(5);
|
|
2240
|
-
lines.push(`${name} ${ca} ${ce} ${inst}`);
|
|
2241
|
-
}
|
|
2242
|
-
lines.push("", "Ca=afferent (depended on by), Ce=efferent (depends on), I=instability (Ce/(Ca+Ce))");
|
|
2243
|
-
return lines.join("\n");
|
|
2244
|
-
}
|
|
2245
|
-
case "kirograph_package": {
|
|
2246
|
-
if (!cg.isArchitectureEnabled()) {
|
|
2247
|
-
return "Architecture analysis is disabled. Set enableArchitecture=true in .kirograph/config.json and re-index.";
|
|
2248
|
-
}
|
|
2249
|
-
const query = args.package.toLowerCase();
|
|
2250
|
-
const includeFiles = args.includeFiles !== false;
|
|
2251
|
-
const arch = cg.getArchitecture();
|
|
2252
|
-
const pkg = arch.packages.find(
|
|
2253
|
-
(p) => p.name.toLowerCase().includes(query) || p.path.toLowerCase().includes(query) || p.id.toLowerCase().includes(query)
|
|
2254
|
-
);
|
|
2255
|
-
if (!pkg) return `Package "${args.package}" not found. Use kirograph_architecture to list all packages.`;
|
|
2256
|
-
const lines = [
|
|
2257
|
-
`## Package: ${pkg.name}`,
|
|
2258
|
-
`Path: ${pkg.path}`,
|
|
2259
|
-
`Source: ${pkg.source}${pkg.manifestPath ? ` (${pkg.manifestPath})` : ""}`,
|
|
2260
|
-
...pkg.version ? [`Version: ${pkg.version}`] : [],
|
|
2261
|
-
...pkg.language ? [`Language: ${pkg.language}`] : []
|
|
2262
|
-
];
|
|
2263
|
-
const deps = arch.packageDeps.filter((d) => d.sourcePkg === pkg.id);
|
|
2264
|
-
const dependents = arch.packageDeps.filter((d) => d.targetPkg === pkg.id);
|
|
2265
|
-
const coupling = arch.coupling.find((c) => c.packageId === pkg.id);
|
|
2266
|
-
if (coupling) {
|
|
2267
|
-
lines.push("", `Coupling: Ca=${coupling.afferent} Ce=${coupling.efferent} I=${coupling.instability.toFixed(2)}`);
|
|
2268
|
-
}
|
|
2269
|
-
if (deps.length > 0) {
|
|
2270
|
-
lines.push("", `Depends on (${deps.length}):`);
|
|
2271
|
-
for (const dep of deps) {
|
|
2272
|
-
const name = arch.packages.find((p) => p.id === dep.targetPkg)?.name ?? dep.targetPkg;
|
|
2273
|
-
lines.push(` \u2192 ${name} (${dep.depCount} import${dep.depCount !== 1 ? "s" : ""})`);
|
|
2274
|
-
}
|
|
2275
|
-
}
|
|
2276
|
-
if (dependents.length > 0) {
|
|
2277
|
-
lines.push("", `Depended on by (${dependents.length}):`);
|
|
2278
|
-
for (const dep of dependents) {
|
|
2279
|
-
const name = arch.packages.find((p) => p.id === dep.sourcePkg)?.name ?? dep.sourcePkg;
|
|
2280
|
-
lines.push(` \u2190 ${name} (${dep.depCount} import${dep.depCount !== 1 ? "s" : ""})`);
|
|
2281
|
-
}
|
|
2282
|
-
}
|
|
2283
|
-
if (pkg.externalDeps && pkg.externalDeps.length > 0) {
|
|
2284
|
-
lines.push("", `External deps (${pkg.externalDeps.length}): ${pkg.externalDeps.slice(0, 10).join(", ")}${pkg.externalDeps.length > 10 ? "\u2026" : ""}`);
|
|
2285
|
-
}
|
|
2286
|
-
if (includeFiles) {
|
|
2287
|
-
const files = Object.entries(arch.filePackages).filter(([, ids]) => ids.includes(pkg.id)).map(([f]) => f).sort();
|
|
2288
|
-
if (files.length > 0) {
|
|
2289
|
-
lines.push("", `Files (${files.length}):`);
|
|
2290
|
-
for (const f of files.slice(0, 30)) lines.push(` ${f}`);
|
|
2291
|
-
if (files.length > 30) lines.push(` \u2026and ${files.length - 30} more`);
|
|
2292
|
-
}
|
|
2293
|
-
}
|
|
2294
|
-
return lines.join("\n");
|
|
2295
|
-
}
|
|
2296
|
-
case "kirograph_hotspots": {
|
|
2297
|
-
const limit = clampLimit(args.limit, 20);
|
|
2298
|
-
const hotspots = cg.findHotspots(limit);
|
|
2299
|
-
if (hotspots.length === 0) return "No symbols found in index.";
|
|
2300
|
-
const lines = [`Top ${hotspots.length} most-connected symbols (by edge degree):
|
|
2301
|
-
`];
|
|
2302
|
-
for (const n of hotspots) {
|
|
2303
|
-
lines.push(`${mapKind(n.kind)} \`${n.name}\` \u2014 degree ${n.degree} (in: ${n.inDegree}, out: ${n.outDegree})`);
|
|
2304
|
-
lines.push(` File: ${n.filePath}:${n.startLine}`);
|
|
2305
|
-
}
|
|
2306
|
-
return lines.join("\n");
|
|
2307
|
-
}
|
|
2308
|
-
case "kirograph_surprising": {
|
|
2309
|
-
const limit = clampLimit(args.limit, 20);
|
|
2310
|
-
const connections = cg.findSurprisingConnections(limit);
|
|
2311
|
-
if (connections.length === 0) return "No surprising cross-file connections found.";
|
|
2312
|
-
const lines = [`Top ${connections.length} surprising cross-file connections:
|
|
2313
|
-
`];
|
|
2314
|
-
for (const c of connections) {
|
|
2315
|
-
lines.push(`${mapKind(c.source.kind)} \`${c.source.name}\` ${c.kind}\u2192 ${mapKind(c.target.kind)} \`${c.target.name}\` (score: ${c.score.toFixed(2)})`);
|
|
2316
|
-
lines.push(` ${c.source.filePath} \u2192 ${c.target.filePath}`);
|
|
2317
|
-
}
|
|
2318
|
-
return lines.join("\n");
|
|
2319
|
-
}
|
|
2320
|
-
case "kirograph_diff": {
|
|
2321
|
-
const sm = cg.createSnapshotManager();
|
|
2322
|
-
const snapshot = args.snapshot ? sm.load(args.snapshot) : sm.loadLatest();
|
|
2323
|
-
if (!snapshot) {
|
|
2324
|
-
return args.snapshot ? `Snapshot "${args.snapshot}" not found. Use \`kirograph snapshot list\` to see available snapshots.` : "No snapshots found. Run `kirograph snapshot` to save one first.";
|
|
2325
|
-
}
|
|
2326
|
-
const diff = sm.diff(snapshot, sm.currentSnapshot());
|
|
2327
|
-
const fromDate = new Date(diff.from.timestamp).toISOString().slice(0, 19).replace("T", " ");
|
|
2328
|
-
const lines = [
|
|
2329
|
-
`Graph diff: "${diff.from.label}" (${fromDate}) \u2192 current`,
|
|
2330
|
-
``,
|
|
2331
|
-
`Symbols: +${diff.addedNodes.length} added, -${diff.removedNodes.length} removed`,
|
|
2332
|
-
`Edges: +${diff.addedEdges.length} added, -${diff.removedEdges.length} removed`
|
|
2333
|
-
];
|
|
2334
|
-
if (diff.addedNodes.length > 0) {
|
|
2335
|
-
lines.push(`
|
|
2336
|
-
## Added symbols (${diff.addedNodes.length})`);
|
|
2337
|
-
for (const n of diff.addedNodes.slice(0, 30)) {
|
|
2338
|
-
lines.push(`+ ${mapKind(n.kind)} \`${n.name}\` \u2014 ${n.filePath}`);
|
|
2339
|
-
}
|
|
2340
|
-
if (diff.addedNodes.length > 30) lines.push(` \u2026and ${diff.addedNodes.length - 30} more`);
|
|
2341
|
-
}
|
|
2342
|
-
if (diff.removedNodes.length > 0) {
|
|
2343
|
-
lines.push(`
|
|
2344
|
-
## Removed symbols (${diff.removedNodes.length})`);
|
|
2345
|
-
for (const n of diff.removedNodes.slice(0, 30)) {
|
|
2346
|
-
lines.push(`- ${mapKind(n.kind)} \`${n.name}\` \u2014 ${n.filePath}`);
|
|
2347
|
-
}
|
|
2348
|
-
if (diff.removedNodes.length > 30) lines.push(` \u2026and ${diff.removedNodes.length - 30} more`);
|
|
2349
|
-
}
|
|
2350
|
-
return lines.join("\n");
|
|
2351
|
-
}
|
|
2352
|
-
case "kirograph_snapshot_save": {
|
|
2353
|
-
const sm = cg.createSnapshotManager();
|
|
2354
|
-
const snapshot = sm.save(args.label);
|
|
2355
|
-
return `Snapshot saved: "${snapshot.label}" \u2014 ${snapshot.nodeCount} symbols, ${snapshot.edgeCount} edges`;
|
|
2356
|
-
}
|
|
2357
|
-
case "kirograph_snapshot_list": {
|
|
2358
|
-
const sm = cg.createSnapshotManager();
|
|
2359
|
-
const snapshots = sm.list();
|
|
2360
|
-
if (snapshots.length === 0) return "No snapshots yet. Use kirograph_snapshot_save to save one first.";
|
|
2361
|
-
const lines = [`Saved snapshots (${snapshots.length}):
|
|
2362
|
-
`];
|
|
2363
|
-
for (const s of snapshots) {
|
|
2364
|
-
const date = new Date(s.timestamp).toISOString().slice(0, 19).replace("T", " ");
|
|
2365
|
-
lines.push(`- ${s.label} (${date}, ${s.nodeCount} symbols, ${s.edgeCount} edges)`);
|
|
2366
|
-
}
|
|
2367
|
-
return lines.join("\n");
|
|
2368
|
-
}
|
|
2369
|
-
case "kirograph_flows": {
|
|
2370
|
-
const { getExecutionFlows, traceFlow, detectEntryPoints } = await Promise.resolve().then(() => require("../graph/flows.js"));
|
|
2371
|
-
const db = cg.getDatabase();
|
|
2372
|
-
if (args.entryPoint) {
|
|
2373
|
-
const results = cg.searchNodes(args.entryPoint, void 0, 5);
|
|
2374
|
-
if (results.length === 0) return `Symbol "${args.entryPoint}" not found in index.`;
|
|
2375
|
-
const hops = traceFlow(db, results[0].node.id, args.maxDepth ?? 10);
|
|
2376
|
-
if (hops.length < 2) return `No outgoing call chain found from "${args.entryPoint}".`;
|
|
2377
|
-
const lines2 = [`## Execution flow from \`${args.entryPoint}\``, ""];
|
|
2378
|
-
for (let i = 0; i < hops.length; i++) {
|
|
2379
|
-
const hop = hops[i];
|
|
2380
|
-
const indent = " ".repeat(i);
|
|
2381
|
-
const arrow = i === 0 ? "\u2192" : "\u21B3";
|
|
2382
|
-
const conf = hop.confidence && hop.confidence !== "extracted" ? ` [${hop.confidence}]` : "";
|
|
2383
|
-
lines2.push(`${indent}${arrow} ${hop.kind} \`${hop.symbol}\` \u2014 ${hop.filePath}:${hop.line}${conf}`);
|
|
2384
|
-
}
|
|
2385
|
-
return lines2.join("\n");
|
|
2386
|
-
}
|
|
2387
|
-
const flows = getExecutionFlows(db, {
|
|
2388
|
-
maxFlows: args.maxFlows ?? 10,
|
|
2389
|
-
maxDepth: args.maxDepth ?? 10
|
|
2390
|
-
});
|
|
2391
|
-
if (flows.length === 0) return "No execution flows detected. The graph may be too small or have no call edges.";
|
|
2392
|
-
const lines = [`## Execution Flows (${flows.length} detected)`, ""];
|
|
2393
|
-
for (const flow of flows) {
|
|
2394
|
-
lines.push(`### \`${flow.entryPoint}\` (${flow.entryPointKind}) \u2014 criticality: ${flow.criticality.toFixed(2)}`);
|
|
2395
|
-
lines.push(`File: ${flow.entryPointFile}`, "");
|
|
2396
|
-
for (let i = 0; i < flow.hops.length; i++) {
|
|
2397
|
-
const hop = flow.hops[i];
|
|
2398
|
-
const indent = " ".repeat(Math.min(i, 5));
|
|
2399
|
-
const arrow = i === 0 ? "\u2192" : "\u21B3";
|
|
2400
|
-
const conf = hop.confidence && hop.confidence !== "extracted" ? ` [${hop.confidence}]` : "";
|
|
2401
|
-
lines.push(`${indent}${arrow} \`${hop.symbol}\` (${hop.kind}) \u2014 ${hop.filePath}:${hop.line}${conf}`);
|
|
2402
|
-
}
|
|
2403
|
-
lines.push("");
|
|
2404
|
-
}
|
|
2405
|
-
return lines.join("\n");
|
|
2406
|
-
}
|
|
2407
|
-
case "kirograph_communities": {
|
|
2408
|
-
const { detectCommunities } = await Promise.resolve().then(() => require("../graph/communities.js"));
|
|
2409
|
-
const db = cg.getDatabase();
|
|
2410
|
-
const result = detectCommunities(db, {
|
|
2411
|
-
resolution: args.resolution ?? 1
|
|
2412
|
-
});
|
|
2413
|
-
if (result.communities.length === 0) return "No communities detected. The graph may be too small or have no edges.";
|
|
2414
|
-
const limit = Math.min(args.limit ?? 15, result.communities.length);
|
|
2415
|
-
const lines = [
|
|
2416
|
-
`## Communities (${result.communities.length} detected, modularity: ${result.modularity.toFixed(3)})`,
|
|
2417
|
-
`Graph: ${result.totalNodes} nodes, ${result.totalEdges} edges`,
|
|
2418
|
-
""
|
|
2419
|
-
];
|
|
2420
|
-
for (const c of result.communities.slice(0, limit)) {
|
|
2421
|
-
lines.push(`### ${c.label} (${c.memberCount} symbols)`);
|
|
2422
|
-
lines.push(`- Directory: \`${c.dominantDirectory}\``);
|
|
2423
|
-
lines.push(`- Language: ${c.dominantLanguage}`);
|
|
2424
|
-
lines.push(`- Inter-community edges: ${c.interCommunityEdges}`);
|
|
2425
|
-
lines.push(`- Top members:`);
|
|
2426
|
-
for (const m of c.members.slice(0, 8)) {
|
|
2427
|
-
lines.push(` - ${m.kind} \`${m.name}\` \u2014 ${m.filePath}`);
|
|
2428
|
-
}
|
|
2429
|
-
if (c.memberCount > 8) lines.push(` - \u2026and ${c.memberCount - 8} more`);
|
|
2430
|
-
lines.push("");
|
|
2431
|
-
}
|
|
2432
|
-
return lines.join("\n");
|
|
2433
|
-
}
|
|
2434
|
-
case "kirograph_refactor": {
|
|
2435
|
-
const { renamePreview, suggestRefactorings } = await Promise.resolve().then(() => require("../graph/refactor.js"));
|
|
2436
|
-
const db = cg.getDatabase();
|
|
2437
|
-
const mode = args.mode;
|
|
2438
|
-
if (mode === "rename") {
|
|
2439
|
-
if (!args.symbol) return 'Error: "symbol" parameter is required for rename mode.';
|
|
2440
|
-
const preview = renamePreview(db, args.symbol);
|
|
2441
|
-
if (!preview) return `Symbol "${args.symbol}" not found in index.`;
|
|
2442
|
-
const lines = [
|
|
2443
|
-
`## Rename Preview: \`${preview.symbol}\``,
|
|
2444
|
-
`Kind: ${preview.kind}`,
|
|
2445
|
-
`Defined at: ${preview.filePath}:${preview.line}`,
|
|
2446
|
-
`Total references: ${preview.totalReferences}`,
|
|
2447
|
-
""
|
|
2448
|
-
];
|
|
2449
|
-
if (preview.references.length === 0) {
|
|
2450
|
-
lines.push("No references found \u2014 this symbol can be safely renamed without affecting other code.");
|
|
2451
|
-
} else {
|
|
2452
|
-
const byFile = /* @__PURE__ */ new Map();
|
|
2453
|
-
for (const ref of preview.references) {
|
|
2454
|
-
if (!byFile.has(ref.filePath)) byFile.set(ref.filePath, []);
|
|
2455
|
-
byFile.get(ref.filePath).push(ref);
|
|
2456
|
-
}
|
|
2457
|
-
for (const [file, refs] of byFile) {
|
|
2458
|
-
lines.push(`### ${file} (${refs.length} references)`);
|
|
2459
|
-
for (const ref of refs.slice(0, 10)) {
|
|
2460
|
-
lines.push(`- Line ${ref.line}: \`${ref.context}\` (${ref.edgeKind})`);
|
|
2461
|
-
}
|
|
2462
|
-
if (refs.length > 10) lines.push(` \u2026and ${refs.length - 10} more`);
|
|
2463
|
-
lines.push("");
|
|
2464
|
-
}
|
|
2465
|
-
}
|
|
2466
|
-
return lines.join("\n");
|
|
2467
|
-
}
|
|
2468
|
-
if (mode === "suggest") {
|
|
2469
|
-
const suggestions = suggestRefactorings(db, args.limit ?? 10);
|
|
2470
|
-
if (suggestions.length === 0) return "No refactoring suggestions \u2014 the codebase structure looks clean.";
|
|
2471
|
-
const lines = [`## Refactoring Suggestions (${suggestions.length})`, ""];
|
|
2472
|
-
for (const s of suggestions) {
|
|
2473
|
-
const icon = s.priority === "high" ? "\u{1F534}" : s.priority === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
2474
|
-
lines.push(`${icon} **${s.type}** [${s.priority}]: ${s.description}`);
|
|
2475
|
-
lines.push(` Rationale: ${s.rationale}`);
|
|
2476
|
-
if (s.symbols.length > 0) {
|
|
2477
|
-
lines.push(` Symbols: ${s.symbols.slice(0, 3).join(", ")}`);
|
|
2478
|
-
}
|
|
2479
|
-
lines.push("");
|
|
2480
|
-
}
|
|
2481
|
-
return lines.join("\n");
|
|
2482
|
-
}
|
|
2483
|
-
return 'Unknown mode. Use "rename" or "suggest".';
|
|
2484
|
-
}
|
|
2485
|
-
// ── Memory tools ────────────────────────────────────────────────────────
|
|
2486
|
-
case "kirograph_mem_search": {
|
|
2487
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2488
|
-
const projectRoot = cg.getProjectRoot();
|
|
2489
|
-
const config = await loadConfig(projectRoot);
|
|
2490
|
-
if (!config.enableMemory) return "Memory is not enabled. Set enableMemory: true in .kirograph/config.json";
|
|
2491
|
-
const { MemoryManager } = await Promise.resolve().then(() => require("../memory/index.js"));
|
|
2492
|
-
const db = cg.getDatabase();
|
|
2493
|
-
db.applyMemorySchema();
|
|
2494
|
-
const mem = new MemoryManager(config, db.getRawDb());
|
|
2495
|
-
mem.initialize();
|
|
2496
|
-
const results = await mem.search(args.query, {
|
|
2497
|
-
limit: args.limit ?? 10,
|
|
2498
|
-
kind: args.kind,
|
|
2499
|
-
sessionId: args.sessionId,
|
|
2500
|
-
asOf: args.asOf
|
|
2501
|
-
});
|
|
2502
|
-
if (results.length === 0) return `No memory observations found for "${args.query}".`;
|
|
2503
|
-
return results.map((r, i) => {
|
|
2504
|
-
const age = formatAge(r.observation.createdAt);
|
|
2505
|
-
const lines = [`${i + 1}. [${r.observation.kind}] ${r.observation.content} (${age})`];
|
|
2506
|
-
if (r.relations && r.relations.length > 0) {
|
|
2507
|
-
for (const rel of r.relations) {
|
|
2508
|
-
const other = rel.observationA === r.observation.id ? rel.observationB : rel.observationA;
|
|
2509
|
-
const icon = rel.relation === "conflicts_with" ? "\u26A1" : rel.relation === "supersedes" ? "\u21A9" : "~";
|
|
2510
|
-
lines.push(` ${icon} ${rel.relation} ${other} (confidence: ${rel.confidence})${rel.judgmentStatus === "judged" ? " (judged)" : ""}`);
|
|
2511
|
-
}
|
|
2512
|
-
}
|
|
2513
|
-
return lines.join("\n");
|
|
2514
|
-
}).join("\n");
|
|
2515
|
-
}
|
|
2516
|
-
case "kirograph_mem_store": {
|
|
2517
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2518
|
-
const projectRoot = cg.getProjectRoot();
|
|
2519
|
-
const config = await loadConfig(projectRoot);
|
|
2520
|
-
if (!config.enableMemory) return "Memory is not enabled. Set enableMemory: true in .kirograph/config.json";
|
|
2521
|
-
const { MemoryManager } = await Promise.resolve().then(() => require("../memory/index.js"));
|
|
2522
|
-
const db = cg.getDatabase();
|
|
2523
|
-
db.applyMemorySchema();
|
|
2524
|
-
const mem = new MemoryManager(config, db.getRawDb());
|
|
2525
|
-
mem.initialize();
|
|
2526
|
-
const id = await mem.store({
|
|
2527
|
-
content: args.content,
|
|
2528
|
-
kind: args.kind ?? "note",
|
|
2529
|
-
source: "agent",
|
|
2530
|
-
topicKey: args.topicKey,
|
|
2531
|
-
reviewAfter: args.reviewAfter
|
|
2532
|
-
});
|
|
2533
|
-
if (!id) return "Observation already exists (duplicate content).";
|
|
2534
|
-
return `Stored observation ${id} [${args.kind ?? "note"}]`;
|
|
2535
|
-
}
|
|
2536
|
-
case "kirograph_mem_timeline": {
|
|
2537
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2538
|
-
const projectRoot = cg.getProjectRoot();
|
|
2539
|
-
const config = await loadConfig(projectRoot);
|
|
2540
|
-
if (!config.enableMemory) return "Memory is not enabled. Set enableMemory: true in .kirograph/config.json";
|
|
2541
|
-
const { MemoryManager } = await Promise.resolve().then(() => require("../memory/index.js"));
|
|
2542
|
-
const db = cg.getDatabase();
|
|
2543
|
-
db.applyMemorySchema();
|
|
2544
|
-
const mem = new MemoryManager(config, db.getRawDb());
|
|
2545
|
-
mem.initialize();
|
|
2546
|
-
const { sessions, observations } = mem.timeline({
|
|
2547
|
-
limit: args.limit ?? 5,
|
|
2548
|
-
sessionId: args.sessionId
|
|
2549
|
-
});
|
|
2550
|
-
if (sessions.length === 0) return "No memory sessions found.";
|
|
2551
|
-
const lines = [];
|
|
2552
|
-
for (const session of sessions) {
|
|
2553
|
-
const start = new Date(session.startedAt).toISOString().slice(0, 16).replace("T", " ");
|
|
2554
|
-
const status = session.endedAt ? "ended" : "active";
|
|
2555
|
-
const obs = observations.get(session.id) ?? [];
|
|
2556
|
-
lines.push(`## ${start} [${session.ide ?? "unknown"}] (${status}, ${obs.length} observations)`);
|
|
2557
|
-
for (const o of obs.slice(0, 5)) {
|
|
2558
|
-
lines.push(` - [${o.kind}] ${o.content.slice(0, 120)}`);
|
|
2559
|
-
}
|
|
2560
|
-
if (obs.length > 5) lines.push(` \u2026and ${obs.length - 5} more`);
|
|
2561
|
-
}
|
|
2562
|
-
return lines.join("\n");
|
|
2563
|
-
}
|
|
2564
|
-
case "kirograph_mem_status": {
|
|
2565
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2566
|
-
const projectRoot = cg.getProjectRoot();
|
|
2567
|
-
const config = await loadConfig(projectRoot);
|
|
2568
|
-
if (!config.enableMemory) return "Memory is not enabled. Set enableMemory: true in .kirograph/config.json";
|
|
2569
|
-
const { MemoryManager } = await Promise.resolve().then(() => require("../memory/index.js"));
|
|
2570
|
-
const db = cg.getDatabase();
|
|
2571
|
-
db.applyMemorySchema();
|
|
2572
|
-
const mem = new MemoryManager(config, db.getRawDb());
|
|
2573
|
-
mem.initialize();
|
|
2574
|
-
const stats = mem.getStats();
|
|
2575
|
-
const lines = [
|
|
2576
|
-
"KiroGraph Memory Status",
|
|
2577
|
-
` Sessions: ${stats.sessions} (${stats.activeSessions} active)`,
|
|
2578
|
-
` Observations: ${stats.observations}`,
|
|
2579
|
-
` Symbol links: ${stats.links}`,
|
|
2580
|
-
` Embeddings: ${stats.vectors} / ${stats.embeddableCount}`,
|
|
2581
|
-
` Model mismatch: ${stats.modelMismatch ? "\u26A0 yes \u2014 run kirograph mem reembed" : "no"}`,
|
|
2582
|
-
` Relations: ${stats.relations} (${stats.pendingConflicts} pending)`,
|
|
2583
|
-
` Caveman compression: ${config.cavemanMode !== "off" ? config.cavemanMode : "off (storing raw)"}`
|
|
2584
|
-
];
|
|
2585
|
-
return lines.join("\n");
|
|
2586
|
-
}
|
|
2587
|
-
case "kirograph_mem_review": {
|
|
2588
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2589
|
-
const projectRoot = args.projectPath ?? cg.getProjectRoot();
|
|
2590
|
-
const config = await loadConfig(projectRoot);
|
|
2591
|
-
if (!config.enableMemory) return "Memory is not enabled.";
|
|
2592
|
-
const { MemoryManager } = await Promise.resolve().then(() => require("../memory/index.js"));
|
|
2593
|
-
const db = cg.getDatabase();
|
|
2594
|
-
db.applyMemorySchema();
|
|
2595
|
-
const mem = new MemoryManager(config, db.getRawDb());
|
|
2596
|
-
mem.initialize();
|
|
2597
|
-
const obs = mem.getObservationsForReview(args.limit ?? 20);
|
|
2598
|
-
if (obs.length === 0) return "No overdue observations \u2014 all review_after dates are in the future.";
|
|
2599
|
-
const now = Date.now();
|
|
2600
|
-
return obs.map((o, i) => {
|
|
2601
|
-
const daysOverdue = Math.floor((now - (o.reviewAfter ?? now)) / 864e5);
|
|
2602
|
-
return `${i + 1}. [${o.kind}]${o.topicKey ? ` (${o.topicKey})` : ""} ${o.content.slice(0, 120)} \u2014 overdue by ${daysOverdue}d`;
|
|
2603
|
-
}).join("\n");
|
|
2604
|
-
}
|
|
2605
|
-
case "kirograph_mem_mark_reviewed": {
|
|
2606
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2607
|
-
const projectRoot = args.projectPath ?? cg.getProjectRoot();
|
|
2608
|
-
const config = await loadConfig(projectRoot);
|
|
2609
|
-
if (!config.enableMemory) return "Memory is not enabled.";
|
|
2610
|
-
const { MemoryManager } = await Promise.resolve().then(() => require("../memory/index.js"));
|
|
2611
|
-
const db = cg.getDatabase();
|
|
2612
|
-
db.applyMemorySchema();
|
|
2613
|
-
const mem = new MemoryManager(config, db.getRawDb());
|
|
2614
|
-
mem.initialize();
|
|
2615
|
-
mem.markReviewed(args.id);
|
|
2616
|
-
return `Observation ${args.id} marked as reviewed.`;
|
|
2617
|
-
}
|
|
2618
|
-
case "kirograph_mem_compare": {
|
|
2619
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2620
|
-
const projectRoot = args.projectPath ?? cg.getProjectRoot();
|
|
2621
|
-
const config = await loadConfig(projectRoot);
|
|
2622
|
-
if (!config.enableMemory) return "Memory is not enabled.";
|
|
2623
|
-
const { MemoryManager } = await Promise.resolve().then(() => require("../memory/index.js"));
|
|
2624
|
-
const db = cg.getDatabase();
|
|
2625
|
-
db.applyMemorySchema();
|
|
2626
|
-
const mem = new MemoryManager(config, db.getRawDb());
|
|
2627
|
-
mem.initialize();
|
|
2628
|
-
const relationId = mem.compareObservations({
|
|
2629
|
-
observationA: args.observationA,
|
|
2630
|
-
observationB: args.observationB,
|
|
2631
|
-
relation: args.relation,
|
|
2632
|
-
confidence: args.confidence ?? 1,
|
|
2633
|
-
reason: args.reason,
|
|
2634
|
-
evidence: args.evidence
|
|
2635
|
-
});
|
|
2636
|
-
return `Relation ${relationId} created (${args.relation}, confidence: ${args.confidence ?? 1}). Use kirograph_mem_judge to finalize.`;
|
|
2637
|
-
}
|
|
2638
|
-
case "kirograph_mem_judge": {
|
|
2639
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2640
|
-
const projectRoot = args.projectPath ?? cg.getProjectRoot();
|
|
2641
|
-
const config = await loadConfig(projectRoot);
|
|
2642
|
-
if (!config.enableMemory) return "Memory is not enabled.";
|
|
2643
|
-
const { MemoryManager } = await Promise.resolve().then(() => require("../memory/index.js"));
|
|
2644
|
-
const db = cg.getDatabase();
|
|
2645
|
-
db.applyMemorySchema();
|
|
2646
|
-
const mem = new MemoryManager(config, db.getRawDb());
|
|
2647
|
-
mem.initialize();
|
|
2648
|
-
mem.judgeRelation(
|
|
2649
|
-
args.relationId,
|
|
2650
|
-
args.relation,
|
|
2651
|
-
args.confidence,
|
|
2652
|
-
args.reason,
|
|
2653
|
-
args.evidence
|
|
2654
|
-
);
|
|
2655
|
-
return `Relation ${args.relationId} judged as [${args.relation}] (confidence: ${args.confidence}).`;
|
|
2656
|
-
}
|
|
2657
|
-
case "kirograph_mem_capture": {
|
|
2658
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2659
|
-
const projectRoot = args.projectPath ?? cg.getProjectRoot();
|
|
2660
|
-
const config = await loadConfig(projectRoot);
|
|
2661
|
-
if (!config.enableMemory) return "Memory is not enabled.";
|
|
2662
|
-
const { MemoryManager } = await Promise.resolve().then(() => require("../memory/index.js"));
|
|
2663
|
-
const db = cg.getDatabase();
|
|
2664
|
-
db.applyMemorySchema();
|
|
2665
|
-
const mem = new MemoryManager(config, db.getRawDb());
|
|
2666
|
-
mem.initialize();
|
|
2667
|
-
const extracted = mem.capturePassive(args.content);
|
|
2668
|
-
if (extracted.length === 0) return "No structured sections found. Use ## Key Learnings, ## Observations, ## Decisions, or ## Key Changes headings.";
|
|
2669
|
-
return `Captured ${extracted.length} observation(s):
|
|
2670
|
-
` + extracted.map((e, i) => `${i + 1}. [${e.kind}] ${e.content.slice(0, 100)}${e.id ? "" : " (duplicate)"}`).join("\n");
|
|
2671
|
-
}
|
|
2672
|
-
case "kirograph_mem_save_prompt": {
|
|
2673
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2674
|
-
const projectRoot = args.projectPath ?? cg.getProjectRoot();
|
|
2675
|
-
const config = await loadConfig(projectRoot);
|
|
2676
|
-
if (!config.enableMemory) return "Memory is not enabled.";
|
|
2677
|
-
const { MemoryManager } = await Promise.resolve().then(() => require("../memory/index.js"));
|
|
2678
|
-
const db = cg.getDatabase();
|
|
2679
|
-
db.applyMemorySchema();
|
|
2680
|
-
const mem = new MemoryManager(config, db.getRawDb());
|
|
2681
|
-
mem.initialize();
|
|
2682
|
-
const id = await mem.savePrompt(args.content);
|
|
2683
|
-
return `Prompt saved as ${id}.`;
|
|
2684
|
-
}
|
|
2685
|
-
case "kirograph_mem_suggest_topic_key": {
|
|
2686
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2687
|
-
const projectRoot = args.projectPath ?? cg.getProjectRoot();
|
|
2688
|
-
const config = await loadConfig(projectRoot);
|
|
2689
|
-
if (!config.enableMemory) return "Memory is not enabled.";
|
|
2690
|
-
const { MemoryManager } = await Promise.resolve().then(() => require("../memory/index.js"));
|
|
2691
|
-
const db = cg.getDatabase();
|
|
2692
|
-
db.applyMemorySchema();
|
|
2693
|
-
const mem = new MemoryManager(config, db.getRawDb());
|
|
2694
|
-
mem.initialize();
|
|
2695
|
-
const key = mem.suggestTopicKey(args.kind, args.title);
|
|
2696
|
-
return `Suggested topic_key: ${key}`;
|
|
2697
|
-
}
|
|
2698
|
-
case "kirograph_mem_conflicts_scan": {
|
|
2699
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2700
|
-
const projectRoot = args.projectPath ?? cg.getProjectRoot();
|
|
2701
|
-
const config = await loadConfig(projectRoot);
|
|
2702
|
-
if (!config.enableMemory) return "Memory is not enabled.";
|
|
2703
|
-
const { MemoryManager } = await Promise.resolve().then(() => require("../memory/index.js"));
|
|
2704
|
-
const db = cg.getDatabase();
|
|
2705
|
-
db.applyMemorySchema();
|
|
2706
|
-
const mem = new MemoryManager(config, db.getRawDb());
|
|
2707
|
-
mem.initialize();
|
|
2708
|
-
const candidates = mem.scanConflicts(args.limit ?? 50);
|
|
2709
|
-
if (candidates.length === 0) return "No potential conflicts found among recent observations.";
|
|
2710
|
-
return `Found ${candidates.length} potential conflict(s):
|
|
2711
|
-
` + candidates.map(
|
|
2712
|
-
(c, i) => `${i + 1}. [${c.observationA.kind}] "${c.observationA.content.slice(0, 80)}" \u2194 [${c.observationB.kind}] "${c.observationB.content.slice(0, 80)}" (similarity: ${c.similarity.toFixed(2)})`
|
|
2713
|
-
).join("\n") + "\n\nUse kirograph_mem_compare to establish relations.";
|
|
2714
|
-
}
|
|
2715
|
-
case "kirograph_mem_prune": {
|
|
2716
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2717
|
-
const projectRoot = args.projectPath ?? cg.getProjectRoot();
|
|
2718
|
-
const config = await loadConfig(projectRoot);
|
|
2719
|
-
if (!config.enableMemory) return "Memory is not enabled.";
|
|
2720
|
-
const { MemoryManager } = await Promise.resolve().then(() => require("../memory/index.js"));
|
|
2721
|
-
const db = cg.getDatabase();
|
|
2722
|
-
db.applyMemorySchema();
|
|
2723
|
-
const mem = new MemoryManager(config, db.getRawDb());
|
|
2724
|
-
mem.initialize();
|
|
2725
|
-
const olderThan = args.olderThan ?? "90d";
|
|
2726
|
-
const durationMs = (() => {
|
|
2727
|
-
const m = olderThan.match(/^(\d+)(d|m|w|h)$/);
|
|
2728
|
-
if (!m) return 90 * 864e5;
|
|
2729
|
-
const v = parseInt(m[1]);
|
|
2730
|
-
switch (m[2]) {
|
|
2731
|
-
case "h":
|
|
2732
|
-
return v * 36e5;
|
|
2733
|
-
case "d":
|
|
2734
|
-
return v * 864e5;
|
|
2735
|
-
case "w":
|
|
2736
|
-
return v * 7 * 864e5;
|
|
2737
|
-
case "m":
|
|
2738
|
-
return v * 30 * 864e5;
|
|
2739
|
-
default:
|
|
2740
|
-
return 90 * 864e5;
|
|
2741
|
-
}
|
|
2742
|
-
})();
|
|
2743
|
-
const deleted = mem.prune(durationMs);
|
|
2744
|
-
return `Pruned ${deleted} observation(s) older than ${olderThan}.`;
|
|
2745
|
-
}
|
|
2746
|
-
case "kirograph_mem_lint": {
|
|
2747
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2748
|
-
const projectRoot = args.projectPath ?? cg.getProjectRoot();
|
|
2749
|
-
const config = await loadConfig(projectRoot);
|
|
2750
|
-
if (!config.enableMemory) return "Memory is not enabled.";
|
|
2751
|
-
const { MemoryManager } = await Promise.resolve().then(() => require("../memory/index.js"));
|
|
2752
|
-
const db = cg.getDatabase();
|
|
2753
|
-
db.applyMemorySchema();
|
|
2754
|
-
const mem = new MemoryManager(config, db.getRawDb());
|
|
2755
|
-
mem.initialize();
|
|
2756
|
-
const result = mem.lint();
|
|
2757
|
-
const lines = ["Memory Lint Results:"];
|
|
2758
|
-
lines.push(` Stale links: ${result.staleLinks}`);
|
|
2759
|
-
lines.push(` Model mismatch: ${result.modelMismatch ? "yes" : "no"}`);
|
|
2760
|
-
lines.push(` Stale sessions: ${result.staleSessions} (auto-closed)`);
|
|
2761
|
-
if (args.fix && result.staleLinks > 0) {
|
|
2762
|
-
const removed = mem.removeStaleLinks();
|
|
2763
|
-
lines.push(`
|
|
2764
|
-
\u2713 Removed ${removed} stale link(s)`);
|
|
2765
|
-
} else if (result.staleLinks > 0) {
|
|
2766
|
-
lines.push(`
|
|
2767
|
-
Pass fix: true to remove stale links.`);
|
|
2768
|
-
}
|
|
2769
|
-
if (result.modelMismatch) {
|
|
2770
|
-
lines.push(`
|
|
2771
|
-
Run 'kirograph mem reembed' to fix model mismatch.`);
|
|
2772
|
-
}
|
|
2773
|
-
return lines.join("\n");
|
|
2774
|
-
}
|
|
2775
|
-
case "kirograph_mem_conflicts_list": {
|
|
2776
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2777
|
-
const projectRoot = args.projectPath ?? cg.getProjectRoot();
|
|
2778
|
-
const config = await loadConfig(projectRoot);
|
|
2779
|
-
if (!config.enableMemory) return "Memory is not enabled.";
|
|
2780
|
-
const { MemoryManager } = await Promise.resolve().then(() => require("../memory/index.js"));
|
|
2781
|
-
const db = cg.getDatabase();
|
|
2782
|
-
db.applyMemorySchema();
|
|
2783
|
-
const mem = new MemoryManager(config, db.getRawDb());
|
|
2784
|
-
mem.initialize();
|
|
2785
|
-
const limit = clampLimit(args.limit, 20);
|
|
2786
|
-
const pending = mem.getPendingRelations(limit);
|
|
2787
|
-
if (pending.length === 0) return "No pending conflict relations.";
|
|
2788
|
-
return `Pending relations (${pending.length}):
|
|
2789
|
-
|
|
2790
|
-
` + pending.map(
|
|
2791
|
-
(r) => `[${r.id}] ${r.relation}
|
|
2792
|
-
A: ${r.observationA}
|
|
2793
|
-
B: ${r.observationB}${r.reason ? `
|
|
2794
|
-
Reason: ${r.reason}` : ""}`
|
|
2795
|
-
).join("\n\n") + "\n\nUse kirograph_mem_conflicts_ignore to dismiss, or kirograph_mem_judge to finalize.";
|
|
2796
|
-
}
|
|
2797
|
-
case "kirograph_mem_conflicts_ignore": {
|
|
2798
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2799
|
-
const projectRoot = args.projectPath ?? cg.getProjectRoot();
|
|
2800
|
-
const config = await loadConfig(projectRoot);
|
|
2801
|
-
if (!config.enableMemory) return "Memory is not enabled.";
|
|
2802
|
-
const { MemoryManager } = await Promise.resolve().then(() => require("../memory/index.js"));
|
|
2803
|
-
const db = cg.getDatabase();
|
|
2804
|
-
db.applyMemorySchema();
|
|
2805
|
-
const mem = new MemoryManager(config, db.getRawDb());
|
|
2806
|
-
mem.initialize();
|
|
2807
|
-
const relationId = args.relationId;
|
|
2808
|
-
mem.ignoreRelation(relationId);
|
|
2809
|
-
return `Relation "${relationId}" ignored.`;
|
|
2810
|
-
}
|
|
2811
|
-
// ── Docs tools ────────────────────────────────────────────────────────────
|
|
2812
|
-
case "kirograph_docs_toc": {
|
|
2813
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2814
|
-
const projectRoot = cg.getProjectRoot();
|
|
2815
|
-
const config = await loadConfig(projectRoot);
|
|
2816
|
-
if (!config.enableDocs) return "Documentation indexing is not enabled. Set enableDocs: true in .kirograph/config.json and run kirograph index.";
|
|
2817
|
-
const { DocsQueries } = await Promise.resolve().then(() => require("../docs/queries.js"));
|
|
2818
|
-
const db = cg.getDatabase();
|
|
2819
|
-
db.applyDocsSchema();
|
|
2820
|
-
const docs = new DocsQueries(db.getRawDb(), projectRoot);
|
|
2821
|
-
const toc = docs.getToc({ file: args.file, tree: args.tree });
|
|
2822
|
-
if (toc.length === 0) return args.file ? `No sections found in "${args.file}".` : "No documentation indexed. Run kirograph index.";
|
|
2823
|
-
const lines = [];
|
|
2824
|
-
const renderEntry = (entry, indent) => {
|
|
2825
|
-
const prefix = "#".repeat(entry.level || 1);
|
|
2826
|
-
const summary = entry.summary ? ` \u2014 ${entry.summary}` : "";
|
|
2827
|
-
lines.push(`${indent}${prefix} ${entry.title}${summary}`);
|
|
2828
|
-
lines.push(`${indent} ID: ${entry.id}`);
|
|
2829
|
-
if (entry.children?.length) {
|
|
2830
|
-
for (const child of entry.children) renderEntry(child, indent + " ");
|
|
2831
|
-
}
|
|
2832
|
-
};
|
|
2833
|
-
if (args.tree) {
|
|
2834
|
-
for (const entry of toc) renderEntry(entry, "");
|
|
2835
|
-
} else {
|
|
2836
|
-
for (const entry of toc) {
|
|
2837
|
-
const prefix = "#".repeat(entry.level || 1);
|
|
2838
|
-
const summary = entry.summary ? ` \u2014 ${entry.summary}` : "";
|
|
2839
|
-
lines.push(`${prefix} ${entry.title} [${entry.filePath}]${summary}`);
|
|
2840
|
-
lines.push(` ID: ${entry.id}`);
|
|
2841
|
-
}
|
|
2842
|
-
}
|
|
2843
|
-
return lines.join("\n");
|
|
2844
|
-
}
|
|
2845
|
-
case "kirograph_docs_search": {
|
|
2846
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2847
|
-
const projectRoot = cg.getProjectRoot();
|
|
2848
|
-
const config = await loadConfig(projectRoot);
|
|
2849
|
-
if (!config.enableDocs) return "Documentation indexing is not enabled. Set enableDocs: true in .kirograph/config.json and run kirograph index.";
|
|
2850
|
-
const { DocsQueries } = await Promise.resolve().then(() => require("../docs/queries.js"));
|
|
2851
|
-
const db = cg.getDatabase();
|
|
2852
|
-
db.applyDocsSchema();
|
|
2853
|
-
const docs = new DocsQueries(db.getRawDb(), projectRoot, config);
|
|
2854
|
-
const results = await docs.searchSections(args.query, {
|
|
2855
|
-
file: args.file,
|
|
2856
|
-
limit: args.limit ?? 10
|
|
2857
|
-
});
|
|
2858
|
-
if (results.length === 0) return `No documentation sections found matching "${args.query}".`;
|
|
2859
|
-
return results.map((r, i) => {
|
|
2860
|
-
const summary = r.section.summary ? `
|
|
2861
|
-
${r.section.summary}` : "";
|
|
2862
|
-
return `${i + 1}. ${r.section.title} [${r.section.filePath}]${summary}
|
|
2863
|
-
ID: ${r.section.id}`;
|
|
2864
|
-
}).join("\n\n");
|
|
2865
|
-
}
|
|
2866
|
-
case "kirograph_docs_section": {
|
|
2867
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2868
|
-
const projectRoot = cg.getProjectRoot();
|
|
2869
|
-
const config = await loadConfig(projectRoot);
|
|
2870
|
-
if (!config.enableDocs) return "Documentation indexing is not enabled. Set enableDocs: true in .kirograph/config.json and run kirograph index.";
|
|
2871
|
-
const { DocsQueries } = await Promise.resolve().then(() => require("../docs/queries.js"));
|
|
2872
|
-
const db = cg.getDatabase();
|
|
2873
|
-
db.applyDocsSchema();
|
|
2874
|
-
const docs = new DocsQueries(db.getRawDb(), projectRoot);
|
|
2875
|
-
const result = docs.getSection(args.id, { context: args.context });
|
|
2876
|
-
if (!result) return `Section "${args.id}" not found.`;
|
|
2877
|
-
const lines = [];
|
|
2878
|
-
if (result.ancestors?.length) {
|
|
2879
|
-
lines.push("Breadcrumb: " + result.ancestors.map((a) => a.title).join(" > ") + " > " + result.section.title);
|
|
2880
|
-
lines.push("");
|
|
2881
|
-
}
|
|
2882
|
-
lines.push(result.content);
|
|
2883
|
-
if (result.children?.length) {
|
|
2884
|
-
lines.push("", "## Child sections:");
|
|
2885
|
-
for (const child of result.children) {
|
|
2886
|
-
const summary = child.summary ? ` \u2014 ${child.summary}` : "";
|
|
2887
|
-
lines.push(` - ${child.title}${summary} (ID: ${child.id})`);
|
|
2888
|
-
}
|
|
2889
|
-
}
|
|
2890
|
-
return lines.join("\n");
|
|
2891
|
-
}
|
|
2892
|
-
case "kirograph_docs_outline": {
|
|
2893
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2894
|
-
const projectRoot = cg.getProjectRoot();
|
|
2895
|
-
const config = await loadConfig(projectRoot);
|
|
2896
|
-
if (!config.enableDocs) return "Documentation indexing is not enabled. Set enableDocs: true in .kirograph/config.json and run kirograph index.";
|
|
2897
|
-
const { DocsQueries } = await Promise.resolve().then(() => require("../docs/queries.js"));
|
|
2898
|
-
const db = cg.getDatabase();
|
|
2899
|
-
db.applyDocsSchema();
|
|
2900
|
-
const docs = new DocsQueries(db.getRawDb(), projectRoot);
|
|
2901
|
-
const outline = docs.getOutline(args.file);
|
|
2902
|
-
if (outline.length === 0) return `No sections found in "${args.file}". Is the file indexed?`;
|
|
2903
|
-
const lines = [`Outline: ${args.file}`, ""];
|
|
2904
|
-
const renderOutline = (entries, indent) => {
|
|
2905
|
-
for (const entry of entries) {
|
|
2906
|
-
const summary = entry.summary ? ` \u2014 ${entry.summary}` : "";
|
|
2907
|
-
lines.push(`${indent}${"#".repeat(entry.level || 1)} ${entry.title}${summary}`);
|
|
2908
|
-
if (entry.children?.length) renderOutline(entry.children, indent + " ");
|
|
2909
|
-
}
|
|
2910
|
-
};
|
|
2911
|
-
renderOutline(outline, "");
|
|
2912
|
-
return lines.join("\n");
|
|
2913
|
-
}
|
|
2914
|
-
case "kirograph_docs_refs": {
|
|
2915
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2916
|
-
const projectRoot = cg.getProjectRoot();
|
|
2917
|
-
const config = await loadConfig(projectRoot);
|
|
2918
|
-
if (!config.enableDocs) return "Documentation indexing is not enabled. Set enableDocs: true in .kirograph/config.json and run kirograph index.";
|
|
2919
|
-
const { DocsQueries } = await Promise.resolve().then(() => require("../docs/queries.js"));
|
|
2920
|
-
const db = cg.getDatabase();
|
|
2921
|
-
db.applyDocsSchema();
|
|
2922
|
-
const docs = new DocsQueries(db.getRawDb(), projectRoot);
|
|
2923
|
-
const refs = docs.getRefs({
|
|
2924
|
-
sectionId: args.sectionId,
|
|
2925
|
-
qualifiedName: args.nodeId
|
|
2926
|
-
});
|
|
2927
|
-
if (refs.length === 0) {
|
|
2928
|
-
if (args.sectionId) return `No code references found in section "${args.sectionId}".`;
|
|
2929
|
-
if (args.nodeId) return `No documentation sections reference "${args.nodeId}".`;
|
|
2930
|
-
return "Provide either sectionId or nodeId to look up cross-references.";
|
|
2931
|
-
}
|
|
2932
|
-
return refs.map((r) => {
|
|
2933
|
-
const direction = args.sectionId ? `\u2192 ${r.qualifiedName}` : `\u2190 ${r.sectionTitle ?? r.sectionId}`;
|
|
2934
|
-
return `[${r.refType}] ${direction} (confidence: ${r.confidence.toFixed(2)})`;
|
|
2935
|
-
}).join("\n");
|
|
2936
|
-
}
|
|
2937
|
-
// ── Data tools ────────────────────────────────────────────────────────────
|
|
2938
|
-
case "kirograph_data_list": {
|
|
2939
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2940
|
-
const projectRoot = cg.getProjectRoot();
|
|
2941
|
-
const config = await loadConfig(projectRoot);
|
|
2942
|
-
if (!config.enableData) return "Data indexing is not enabled. Set enableData: true in .kirograph/config.json and run kirograph index.";
|
|
2943
|
-
const { DataQueries } = await Promise.resolve().then(() => require("../data/queries.js"));
|
|
2944
|
-
const db = cg.getDatabase();
|
|
2945
|
-
db.applyDataSchema();
|
|
2946
|
-
const dq = new DataQueries(db.getRawDb());
|
|
2947
|
-
const datasets = dq.listDatasets();
|
|
2948
|
-
if (datasets.length === 0) return "No datasets indexed. Run kirograph index or kirograph data reindex.";
|
|
2949
|
-
return datasets.map((ds) => {
|
|
2950
|
-
const sizeMb = (ds.fileSize / 1024 / 1024).toFixed(2);
|
|
2951
|
-
return `${ds.id} (${ds.format})
|
|
2952
|
-
File: ${ds.filePath}
|
|
2953
|
-
Rows: ${ds.rowCount.toLocaleString()} | Columns: ${ds.columnCount} | Size: ${sizeMb} MB`;
|
|
2954
|
-
}).join("\n\n");
|
|
2955
|
-
}
|
|
2956
|
-
case "kirograph_data_describe": {
|
|
2957
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2958
|
-
const projectRoot = cg.getProjectRoot();
|
|
2959
|
-
const config = await loadConfig(projectRoot);
|
|
2960
|
-
if (!config.enableData) return "Data indexing is not enabled. Set enableData: true in .kirograph/config.json and run kirograph index.";
|
|
2961
|
-
const { DataQueries } = await Promise.resolve().then(() => require("../data/queries.js"));
|
|
2962
|
-
const db = cg.getDatabase();
|
|
2963
|
-
db.applyDataSchema();
|
|
2964
|
-
const dq = new DataQueries(db.getRawDb());
|
|
2965
|
-
if (args.column) {
|
|
2966
|
-
const col = dq.describeColumn(args.dataset, args.column);
|
|
2967
|
-
if (!col) return `Column "${args.column}" not found in dataset "${args.dataset}".`;
|
|
2968
|
-
return [
|
|
2969
|
-
`Column: ${col.name}`,
|
|
2970
|
-
`Type: ${col.inferredType}`,
|
|
2971
|
-
`Nullable: ${col.nullable} (${(col.nullPct * 100).toFixed(1)}% null)`,
|
|
2972
|
-
`Cardinality: ${col.cardinality}`,
|
|
2973
|
-
col.minValue ? `Min: ${col.minValue}` : "",
|
|
2974
|
-
col.maxValue ? `Max: ${col.maxValue}` : "",
|
|
2975
|
-
col.meanValue !== null ? `Mean: ${col.meanValue.toFixed(2)}` : "",
|
|
2976
|
-
`Samples: ${col.sampleValues.join(", ")}`
|
|
2977
|
-
].filter(Boolean).join("\n");
|
|
2978
|
-
}
|
|
2979
|
-
const result = dq.describeDataset(args.dataset);
|
|
2980
|
-
if (!result) return `Dataset "${args.dataset}" not found. Use kirograph_data_list to see available datasets.`;
|
|
2981
|
-
const lines = [
|
|
2982
|
-
`Dataset: ${result.dataset.id} (${result.dataset.format})`,
|
|
2983
|
-
`File: ${result.dataset.filePath}`,
|
|
2984
|
-
`Rows: ${result.dataset.rowCount.toLocaleString()} | Columns: ${result.dataset.columnCount}`,
|
|
2985
|
-
"",
|
|
2986
|
-
"Columns:"
|
|
2987
|
-
];
|
|
2988
|
-
for (const col of result.columns) {
|
|
2989
|
-
const nullInfo = col.nullable ? ` (${(col.nullPct * 100).toFixed(0)}% null)` : "";
|
|
2990
|
-
const samples = col.sampleValues.length > 0 ? ` \u2014 samples: ${col.sampleValues.slice(0, 3).join(", ")}` : "";
|
|
2991
|
-
const summary = col.summary ? ` [${col.summary}]` : "";
|
|
2992
|
-
lines.push(` ${col.name}: ${col.inferredType}${nullInfo} [${col.cardinality} distinct]${samples}${summary}`);
|
|
2993
|
-
}
|
|
2994
|
-
const rules = dq.validationRules(args.dataset);
|
|
2995
|
-
if (rules && rules.length > 0) {
|
|
2996
|
-
lines.push("", "Validation rules:");
|
|
2997
|
-
for (const r of rules.slice(0, 10)) {
|
|
2998
|
-
lines.push(` ${r.column}: ${r.rules.join("; ")}`);
|
|
2999
|
-
}
|
|
3000
|
-
}
|
|
3001
|
-
const hints = dq.sampleHints(args.dataset);
|
|
3002
|
-
if (hints && hints.length > 0) {
|
|
3003
|
-
lines.push("", "Sample data hints:");
|
|
3004
|
-
for (const h of hints.slice(0, 10)) {
|
|
3005
|
-
lines.push(` ${h.column}: ${h.hint}`);
|
|
3006
|
-
}
|
|
3007
|
-
}
|
|
3008
|
-
return lines.join("\n");
|
|
3009
|
-
}
|
|
3010
|
-
case "kirograph_data_query": {
|
|
3011
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3012
|
-
const projectRoot = cg.getProjectRoot();
|
|
3013
|
-
const config = await loadConfig(projectRoot);
|
|
3014
|
-
if (!config.enableData) return "Data indexing is not enabled. Set enableData: true in .kirograph/config.json and run kirograph index.";
|
|
3015
|
-
const { DataQueries } = await Promise.resolve().then(() => require("../data/queries.js"));
|
|
3016
|
-
const db = cg.getDatabase();
|
|
3017
|
-
db.applyDataSchema();
|
|
3018
|
-
const dq = new DataQueries(db.getRawDb());
|
|
3019
|
-
const result = dq.queryRows(args.dataset, {
|
|
3020
|
-
filters: args.filters,
|
|
3021
|
-
columns: args.columns,
|
|
3022
|
-
limit: args.limit ?? 100,
|
|
3023
|
-
offset: args.offset ?? 0
|
|
3024
|
-
});
|
|
3025
|
-
if (!result) return `Dataset "${args.dataset}" not found.`;
|
|
3026
|
-
if (result.rows.length === 0) return `No rows match the given filters (${result.totalMatching} total in dataset).`;
|
|
3027
|
-
const header = `${result.rows.length} rows returned (${result.totalMatching} total matching):
|
|
3028
|
-
`;
|
|
3029
|
-
const rowStrs = result.rows.slice(0, 50).map((row, i) => {
|
|
3030
|
-
const vals = Object.entries(row).map(([k, v]) => `${k}=${v ?? "null"}`).join(", ");
|
|
3031
|
-
return ` ${i + 1}. ${vals}`;
|
|
3032
|
-
});
|
|
3033
|
-
if (result.rows.length > 50) rowStrs.push(` \u2026and ${result.rows.length - 50} more rows`);
|
|
3034
|
-
let response = header + rowStrs.join("\n");
|
|
3035
|
-
const maxChars = config.dataMaxResponseTokens * 4;
|
|
3036
|
-
if (response.length > maxChars) {
|
|
3037
|
-
response = response.slice(0, maxChars) + "\n\n[truncated: response exceeded token budget]";
|
|
3038
|
-
}
|
|
3039
|
-
response = this.checkPaginationLoop(args.dataset, args.offset, response);
|
|
3040
|
-
return response;
|
|
3041
|
-
}
|
|
3042
|
-
case "kirograph_data_aggregate": {
|
|
3043
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3044
|
-
const projectRoot = cg.getProjectRoot();
|
|
3045
|
-
const config = await loadConfig(projectRoot);
|
|
3046
|
-
if (!config.enableData) return "Data indexing is not enabled. Set enableData: true in .kirograph/config.json and run kirograph index.";
|
|
3047
|
-
const { DataQueries } = await Promise.resolve().then(() => require("../data/queries.js"));
|
|
3048
|
-
const db = cg.getDatabase();
|
|
3049
|
-
db.applyDataSchema();
|
|
3050
|
-
const dq = new DataQueries(db.getRawDb());
|
|
3051
|
-
const result = dq.aggregate(args.dataset, {
|
|
3052
|
-
groupBy: args.groupBy ?? [],
|
|
3053
|
-
metrics: args.metrics ?? [],
|
|
3054
|
-
filters: args.filters
|
|
3055
|
-
});
|
|
3056
|
-
if (!result) return `Dataset "${args.dataset}" not found.`;
|
|
3057
|
-
if (result.rows.length === 0) return "No results (empty dataset or all rows filtered out).";
|
|
3058
|
-
const keys = Object.keys(result.rows[0]);
|
|
3059
|
-
const header = keys.join(" | ");
|
|
3060
|
-
const separator = keys.map(() => "---").join(" | ");
|
|
3061
|
-
const rows = result.rows.slice(0, 100).map((row) => keys.map((k) => row[k] ?? "null").join(" | "));
|
|
3062
|
-
let response = `${header}
|
|
3063
|
-
${separator}
|
|
3064
|
-
${rows.join("\n")}${result.rows.length > 100 ? `
|
|
3065
|
-
\u2026and ${result.rows.length - 100} more groups` : ""}`;
|
|
3066
|
-
const maxChars = config.dataMaxResponseTokens * 4;
|
|
3067
|
-
if (response.length > maxChars) {
|
|
3068
|
-
response = response.slice(0, maxChars) + "\n\n[truncated: response exceeded token budget]";
|
|
3069
|
-
}
|
|
3070
|
-
return response;
|
|
3071
|
-
}
|
|
3072
|
-
case "kirograph_data_search": {
|
|
3073
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3074
|
-
const projectRoot = cg.getProjectRoot();
|
|
3075
|
-
const config = await loadConfig(projectRoot);
|
|
3076
|
-
if (!config.enableData) return "Data indexing is not enabled. Set enableData: true in .kirograph/config.json and run kirograph index.";
|
|
3077
|
-
const { DataQueries } = await Promise.resolve().then(() => require("../data/queries.js"));
|
|
3078
|
-
const db = cg.getDatabase();
|
|
3079
|
-
db.applyDataSchema();
|
|
3080
|
-
const dq = new DataQueries(db.getRawDb());
|
|
3081
|
-
const cols = dq.searchColumns(args.dataset, args.query);
|
|
3082
|
-
if (cols.length === 0) return `No columns matching "${args.query}" in dataset "${args.dataset}".`;
|
|
3083
|
-
return cols.map((c) => {
|
|
3084
|
-
const samples = c.sampleValues.length > 0 ? ` \u2014 samples: ${c.sampleValues.slice(0, 3).join(", ")}` : "";
|
|
3085
|
-
return `${c.name}: ${c.inferredType} [${c.cardinality} distinct]${samples}`;
|
|
3086
|
-
}).join("\n");
|
|
3087
|
-
}
|
|
3088
|
-
case "kirograph_data_join": {
|
|
3089
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3090
|
-
const projectRoot = cg.getProjectRoot();
|
|
3091
|
-
const config = await loadConfig(projectRoot);
|
|
3092
|
-
if (!config.enableData) return "Data indexing is not enabled. Set enableData: true in .kirograph/config.json and run kirograph index.";
|
|
3093
|
-
const { DataQueries } = await Promise.resolve().then(() => require("../data/queries.js"));
|
|
3094
|
-
const db = cg.getDatabase();
|
|
3095
|
-
db.applyDataSchema();
|
|
3096
|
-
const dq = new DataQueries(db.getRawDb());
|
|
3097
|
-
try {
|
|
3098
|
-
const result = dq.join({
|
|
3099
|
-
left: args.left,
|
|
3100
|
-
right: args.right,
|
|
3101
|
-
leftColumn: args.leftColumn,
|
|
3102
|
-
rightColumn: args.rightColumn,
|
|
3103
|
-
type: args.type ?? "inner",
|
|
3104
|
-
columns: args.columns,
|
|
3105
|
-
limit: args.limit
|
|
3106
|
-
});
|
|
3107
|
-
if (!result) return `Dataset not found. Verify both dataset IDs with kirograph_data_list.`;
|
|
3108
|
-
const joinTypeStr = String(args.type ?? "inner").toUpperCase();
|
|
3109
|
-
const header = `Join: ${args.left}.${args.leftColumn} ${joinTypeStr} JOIN ${args.right}.${args.rightColumn}
|
|
3110
|
-
Matching rows: ${result.totalMatching} (showing ${result.rows.length})`;
|
|
3111
|
-
if (result.rows.length === 0) return `${header}
|
|
3112
|
-
|
|
3113
|
-
No matching rows.`;
|
|
3114
|
-
const lines = result.rows.map((r) => JSON.stringify(r));
|
|
3115
|
-
let response = `${header}
|
|
3116
|
-
|
|
3117
|
-
${lines.join("\n")}`;
|
|
3118
|
-
const maxChars = config.dataMaxResponseTokens * 4;
|
|
3119
|
-
if (response.length > maxChars) {
|
|
3120
|
-
response = response.slice(0, maxChars) + "\n\n[truncated: response exceeded token budget]";
|
|
3121
|
-
}
|
|
3122
|
-
return response;
|
|
3123
|
-
} catch (err) {
|
|
3124
|
-
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
3125
|
-
}
|
|
3126
|
-
}
|
|
3127
|
-
case "kirograph_data_correlations": {
|
|
3128
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3129
|
-
const projectRoot = cg.getProjectRoot();
|
|
3130
|
-
const config = await loadConfig(projectRoot);
|
|
3131
|
-
if (!config.enableData) return "Data indexing is not enabled. Set enableData: true in .kirograph/config.json and run kirograph index.";
|
|
3132
|
-
const { DataQueries } = await Promise.resolve().then(() => require("../data/queries.js"));
|
|
3133
|
-
const db = cg.getDatabase();
|
|
3134
|
-
db.applyDataSchema();
|
|
3135
|
-
const dq = new DataQueries(db.getRawDb());
|
|
3136
|
-
const pairs = dq.correlations(args.dataset, args.threshold);
|
|
3137
|
-
if (pairs === null) return `Dataset "${args.dataset}" not found.`;
|
|
3138
|
-
if (pairs.length === 0) return `No correlations above threshold ${args.threshold ?? 0.3} found. The dataset may have fewer than 2 numeric columns or no significant correlations.`;
|
|
3139
|
-
const lines = pairs.map(
|
|
3140
|
-
(p) => `${p.column1} \u2194 ${p.column2}: ${p.correlation > 0 ? "+" : ""}${p.correlation.toFixed(4)} (${p.strength})`
|
|
3141
|
-
);
|
|
3142
|
-
return `Correlations for "${args.dataset}" (threshold: ${args.threshold ?? 0.3}):
|
|
3143
|
-
|
|
3144
|
-
${lines.join("\n")}`;
|
|
3145
|
-
}
|
|
3146
|
-
case "kirograph_data_quality": {
|
|
3147
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3148
|
-
const projectRoot = cg.getProjectRoot();
|
|
3149
|
-
const config = await loadConfig(projectRoot);
|
|
3150
|
-
if (!config.enableData) return "Data indexing is not enabled. Set enableData: true in .kirograph/config.json and run kirograph index.";
|
|
3151
|
-
const { DataQueries } = await Promise.resolve().then(() => require("../data/queries.js"));
|
|
3152
|
-
const db = cg.getDatabase();
|
|
3153
|
-
db.applyDataSchema();
|
|
3154
|
-
const dq = new DataQueries(db.getRawDb());
|
|
3155
|
-
const quality = dq.quality(args.dataset);
|
|
3156
|
-
if (quality === null) return `Dataset "${args.dataset}" not found.`;
|
|
3157
|
-
if (quality.length === 0) return `No quality issues detected in "${args.dataset}". All columns look healthy.`;
|
|
3158
|
-
const lines = quality.map(
|
|
3159
|
-
(q) => `${q.column} (risk: ${(q.riskScore * 100).toFixed(0)}%): ${q.issues.join("; ")}`
|
|
3160
|
-
);
|
|
3161
|
-
return `Quality report for "${args.dataset}" (${quality.length} columns with issues):
|
|
3162
|
-
|
|
3163
|
-
${lines.join("\n")}`;
|
|
3164
|
-
}
|
|
3165
|
-
case "kirograph_data_drift": {
|
|
3166
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3167
|
-
const projectRoot = cg.getProjectRoot();
|
|
3168
|
-
const config = await loadConfig(projectRoot);
|
|
3169
|
-
if (!config.enableData) return "Data indexing is not enabled. Set enableData: true in .kirograph/config.json and run kirograph index.";
|
|
3170
|
-
const { DataQueries } = await Promise.resolve().then(() => require("../data/queries.js"));
|
|
3171
|
-
const db = cg.getDatabase();
|
|
3172
|
-
db.applyDataSchema();
|
|
3173
|
-
const dq = new DataQueries(db.getRawDb());
|
|
3174
|
-
const drift = dq.detectDrift(args.dataset);
|
|
3175
|
-
if (drift === null) return `Dataset "${args.dataset}" not found.`;
|
|
3176
|
-
if (!drift.hasDrift) {
|
|
3177
|
-
const rowDelta = drift.rowCountDelta !== 0 ? ` (rows: ${drift.rowCountDelta > 0 ? "+" : ""}${drift.rowCountDelta})` : "";
|
|
3178
|
-
return `No schema drift detected for "${args.dataset}".${rowDelta}`;
|
|
3179
|
-
}
|
|
3180
|
-
const driftLines = [`Schema drift detected for "${args.dataset}":`];
|
|
3181
|
-
if (drift.addedColumns?.length) driftLines.push(` Added columns: ${drift.addedColumns.join(", ")}`);
|
|
3182
|
-
if (drift.removedColumns?.length) driftLines.push(` Removed columns: ${drift.removedColumns.join(", ")}`);
|
|
3183
|
-
if (drift.typeChanges?.length) {
|
|
3184
|
-
for (const tc of drift.typeChanges) {
|
|
3185
|
-
driftLines.push(` Type change: ${tc.column} ${tc.from} \u2192 ${tc.to}`);
|
|
3186
|
-
}
|
|
3187
|
-
}
|
|
3188
|
-
if (drift.rowCountDelta !== 0) driftLines.push(` Row count delta: ${drift.rowCountDelta > 0 ? "+" : ""}${drift.rowCountDelta}`);
|
|
3189
|
-
return driftLines.join("\n");
|
|
3190
|
-
}
|
|
3191
|
-
case "kirograph_data_history": {
|
|
3192
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3193
|
-
const projectRoot = cg.getProjectRoot();
|
|
3194
|
-
const config = await loadConfig(projectRoot);
|
|
3195
|
-
if (!config.enableData) return "Data indexing is not enabled. Set enableData: true in .kirograph/config.json and run kirograph index.";
|
|
3196
|
-
const { DataQueries } = await Promise.resolve().then(() => require("../data/queries.js"));
|
|
3197
|
-
const db = cg.getDatabase();
|
|
3198
|
-
db.applyDataSchema();
|
|
3199
|
-
const dq = new DataQueries(db.getRawDb());
|
|
3200
|
-
const limit = Math.min(Number(args.limit ?? 10), 50);
|
|
3201
|
-
const history = dq.getHistory(args.dataset, limit);
|
|
3202
|
-
if (history === null) return `Dataset "${args.dataset}" not found.`;
|
|
3203
|
-
if (history.length === 0) return `No history snapshots for "${args.dataset}".`;
|
|
3204
|
-
const histLines = [`Schema history for "${args.dataset}" (${history.length} snapshot(s)):
|
|
3205
|
-
`];
|
|
3206
|
-
for (const snap of history) {
|
|
3207
|
-
const date = new Date(snap.snapshotAt).toISOString().replace("T", " ").slice(0, 19);
|
|
3208
|
-
const colNames = snap.columns.map((c) => c.name).join(", ");
|
|
3209
|
-
histLines.push(`${date} rows: ${snap.rowCount.toLocaleString()} cols: ${snap.columnCount} schema: ${colNames}`);
|
|
3210
|
-
}
|
|
3211
|
-
return histLines.join("\n");
|
|
3212
|
-
}
|
|
3213
|
-
// ── Watchmen tools (require enableMemory + enableWatchmen) ────────────────
|
|
3214
|
-
case "kirograph_watchmen_status": {
|
|
3215
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3216
|
-
const projectRoot = args.projectPath ?? cg.getProjectRoot();
|
|
3217
|
-
const config = await loadConfig(projectRoot);
|
|
3218
|
-
if (!config.enableMemory) return "Memory is not enabled. Set enableMemory: true in .kirograph/config.json";
|
|
3219
|
-
if (!config.enableWatchmen) return "Watchmen is not enabled. Set enableWatchmen: true in .kirograph/config.json";
|
|
3220
|
-
const { WatchmenChecker } = await Promise.resolve().then(() => require("../watchmen/index.js"));
|
|
3221
|
-
const { MemoryDatabase } = await Promise.resolve().then(() => require("../memory/database.js"));
|
|
3222
|
-
const db = cg.getDatabase();
|
|
3223
|
-
db.applyMemorySchema();
|
|
3224
|
-
const memDb = new MemoryDatabase(db.getRawDb());
|
|
3225
|
-
memDb.initialize();
|
|
3226
|
-
const checker = new WatchmenChecker(config.watchmenThreshold);
|
|
3227
|
-
const { ready, pendingCount } = checker.shouldSynthesize(memDb);
|
|
3228
|
-
const { targetFiles } = checker.buildReadyResponse("", pendingCount, projectRoot);
|
|
3229
|
-
const statusLines = [
|
|
3230
|
-
`Watchmen Status:`,
|
|
3231
|
-
` Pending observations: ${pendingCount} / ${config.watchmenThreshold} threshold`,
|
|
3232
|
-
` Ready to synthesize: ${ready ? "yes" : "no"}`,
|
|
3233
|
-
` Target files:`
|
|
3234
|
-
];
|
|
3235
|
-
for (const f of targetFiles) statusLines.push(` \xB7 ${f}`);
|
|
3236
|
-
return statusLines.join("\n");
|
|
3237
|
-
}
|
|
3238
|
-
case "kirograph_watchmen_synthesize": {
|
|
3239
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3240
|
-
const projectRoot = args.projectPath ?? cg.getProjectRoot();
|
|
3241
|
-
const config = await loadConfig(projectRoot);
|
|
3242
|
-
if (!config.enableMemory || !config.enableWatchmen) return "Watchmen is not enabled (enableMemory + enableWatchmen required).";
|
|
3243
|
-
if (config.watchmenSynthesisMode !== "local") return 'watchmenSynthesisMode is not "local" \u2014 use the agent hook instead.';
|
|
3244
|
-
const { WatchmenChecker } = await Promise.resolve().then(() => require("../watchmen/index.js"));
|
|
3245
|
-
const { MemoryDatabase } = await Promise.resolve().then(() => require("../memory/database.js"));
|
|
3246
|
-
const { runLocalSynthesis } = await Promise.resolve().then(() => require("../watchmen/synthesize.js"));
|
|
3247
|
-
const { MemoryManager } = await Promise.resolve().then(() => require("../memory/index.js"));
|
|
3248
|
-
const db = cg.getDatabase();
|
|
3249
|
-
db.applyMemorySchema();
|
|
3250
|
-
const memDb = new MemoryDatabase(db.getRawDb());
|
|
3251
|
-
memDb.initialize();
|
|
3252
|
-
const checker = new WatchmenChecker(config.watchmenThreshold);
|
|
3253
|
-
const { ready, pendingCount } = checker.shouldSynthesize(memDb);
|
|
3254
|
-
if (!ready && !args.force) {
|
|
3255
|
-
return `Watchmen: ${pendingCount}/${config.watchmenThreshold} observations \u2014 threshold not reached. Pass force: true to run anyway.`;
|
|
3256
|
-
}
|
|
3257
|
-
const observations = memDb.getObservationsSinceLastSummary(50);
|
|
3258
|
-
if (observations.length === 0) return "No observations to synthesize.";
|
|
3259
|
-
const readyResult = checker.buildReadyResponse("", pendingCount, projectRoot);
|
|
3260
|
-
const result = await runLocalSynthesis(observations, readyResult, config.watchmenLocalModel, projectRoot, true);
|
|
3261
|
-
const mgr = new MemoryManager(config, db.getRawDb(), projectRoot);
|
|
3262
|
-
mgr.initialize();
|
|
3263
|
-
await mgr.store({ content: result.summaryObservation, kind: "summary", source: "agent" });
|
|
3264
|
-
const synthLines = [`Watchmen synthesis complete.`];
|
|
3265
|
-
if (result.filesWritten.length) synthLines.push(`Brief written to: ${result.filesWritten.join(", ")}`);
|
|
3266
|
-
if (result.skillsWritten.length) synthLines.push(`Skills written: ${result.skillsWritten.join(", ")}`);
|
|
3267
|
-
if (result.skillsPruned.length) synthLines.push(`Pruned stale: ${result.skillsPruned.join(", ")}`);
|
|
3268
|
-
return synthLines.join("\n");
|
|
3269
|
-
}
|
|
3270
|
-
case "kirograph_watchmen_reset": {
|
|
3271
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3272
|
-
const projectRoot = args.projectPath ?? cg.getProjectRoot();
|
|
3273
|
-
const config = await loadConfig(projectRoot);
|
|
3274
|
-
if (!config.enableMemory) return "Memory is not enabled.";
|
|
3275
|
-
if (!config.enableWatchmen) return "Watchmen is not enabled.";
|
|
3276
|
-
const { MemoryManager } = await Promise.resolve().then(() => require("../memory/index.js"));
|
|
3277
|
-
const db = cg.getDatabase();
|
|
3278
|
-
db.applyMemorySchema();
|
|
3279
|
-
const mgr = new MemoryManager(config, db.getRawDb(), projectRoot);
|
|
3280
|
-
mgr.initialize();
|
|
3281
|
-
const id = await mgr.store({ content: `Watchmen counter reset via MCP at ${(/* @__PURE__ */ new Date()).toISOString()}.`, kind: "summary", source: "manual" });
|
|
3282
|
-
return id ? "Watchmen counter reset \u2014 pending observations set back to 0." : "Already at zero \u2014 nothing to reset.";
|
|
3283
|
-
}
|
|
3284
|
-
// ── Affected tests ────────────────────────────────────────────────────────
|
|
3285
|
-
case "kirograph_affected": {
|
|
3286
|
-
const files = args.files ?? [];
|
|
3287
|
-
if (files.length === 0) return 'No files provided. Pass file paths in the "files" array.';
|
|
3288
|
-
const affected = cg.getAffectedTests(files, {
|
|
3289
|
-
depth: args.depth ?? 5,
|
|
3290
|
-
testPattern: args.testPattern
|
|
3291
|
-
});
|
|
3292
|
-
if (affected.length === 0) return `No affected test files found for the provided ${files.length} changed file(s).`;
|
|
3293
|
-
return `Affected test files (${affected.length}) for ${files.length} changed file(s):
|
|
3294
|
-
|
|
3295
|
-
` + affected.map((f) => `- ${f}`).join("\n");
|
|
3296
|
-
}
|
|
3297
|
-
// ── Security tools (require enableSecurity=true) ──────────────────────────
|
|
3298
|
-
case "kirograph_security": {
|
|
3299
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3300
|
-
const projectRoot = cg.getProjectRoot();
|
|
3301
|
-
const config = await loadConfig(projectRoot);
|
|
3302
|
-
if (!config.enableSecurity) return "Security analysis is not enabled. Set enableSecurity: true in .kirograph/config.json and run kirograph index.";
|
|
3303
|
-
if (!config.enableArchitecture) return "Security requires architecture analysis. Set enableArchitecture: true in .kirograph/config.json and run kirograph index.";
|
|
3304
|
-
const db = cg.getDatabase();
|
|
3305
|
-
db.applySecuritySchema();
|
|
3306
|
-
const rawDb = db.getRawDb();
|
|
3307
|
-
const depCount = rawDb.get(
|
|
3308
|
-
`SELECT COUNT(*) as count FROM sec_dependencies`
|
|
3309
|
-
) ?? { count: 0 };
|
|
3310
|
-
const vulnCount = rawDb.get(
|
|
3311
|
-
`SELECT COUNT(*) as count FROM sec_vulnerabilities`
|
|
3312
|
-
) ?? { count: 0 };
|
|
3313
|
-
const verdictRows = rawDb.all(
|
|
3314
|
-
`SELECT verdict, COUNT(*) as count FROM sec_reachability GROUP BY verdict`
|
|
3315
|
-
);
|
|
3316
|
-
const verdicts = {};
|
|
3317
|
-
for (const row of verdictRows) {
|
|
3318
|
-
verdicts[row.verdict] = row.count;
|
|
3319
|
-
}
|
|
3320
|
-
const staleCount = rawDb.get(
|
|
3321
|
-
`SELECT COUNT(*) as count FROM sec_dependencies WHERE vuln_data_stale = 1`
|
|
3322
|
-
) ?? { count: 0 };
|
|
3323
|
-
const { SuppressionManager: SecSuppressionManager } = await Promise.resolve().then(() => require("../security/suppressions.js"));
|
|
3324
|
-
const secSuppressions = new SecSuppressionManager(projectRoot);
|
|
3325
|
-
const allCveIds = rawDb.all(`SELECT cve_id FROM sec_vulnerabilities`);
|
|
3326
|
-
const suppressedCveCount = allCveIds.filter((r) => secSuppressions.isSuppressed(r.cve_id)).length;
|
|
3327
|
-
const visibleVulnCount = vulnCount.count - suppressedCveCount;
|
|
3328
|
-
const lines = [
|
|
3329
|
-
"# Security Overview",
|
|
3330
|
-
"",
|
|
3331
|
-
`Dependencies: ${depCount.count}`,
|
|
3332
|
-
`Vulnerabilities: ${visibleVulnCount}${suppressedCveCount > 0 ? ` (${suppressedCveCount} suppressed)` : ""}`
|
|
3333
|
-
];
|
|
3334
|
-
if (visibleVulnCount > 0) {
|
|
3335
|
-
const affected = verdicts["affected"] ?? 0;
|
|
3336
|
-
const notAffected = verdicts["not_affected"] ?? 0;
|
|
3337
|
-
const underInvestigation = verdicts["under_investigation"] ?? 0;
|
|
3338
|
-
const pending = visibleVulnCount - affected - notAffected - underInvestigation;
|
|
3339
|
-
lines.push("", "## Reachability Verdicts", "");
|
|
3340
|
-
if (affected > 0) lines.push(`\u25CF Affected: ${affected}`);
|
|
3341
|
-
if (notAffected > 0) lines.push(`\u25CF Not affected: ${notAffected}`);
|
|
3342
|
-
if (underInvestigation > 0) lines.push(`\u25CF Under investigation: ${underInvestigation}`);
|
|
3343
|
-
if (pending > 0) lines.push(`\u25CF Pending analysis: ${pending}`);
|
|
3344
|
-
}
|
|
3345
|
-
if (staleCount.count > 0) {
|
|
3346
|
-
lines.push("", `\u26A0 ${staleCount.count} dependenc${staleCount.count === 1 ? "y has" : "ies have"} stale vulnerability data. Use kirograph_vulns with refresh=true to update.`);
|
|
3347
|
-
}
|
|
3348
|
-
const lastVulnCheck = rawDb.get(
|
|
3349
|
-
`SELECT MIN(last_vuln_check) as oldest FROM sec_dependencies WHERE last_vuln_check IS NOT NULL`
|
|
3350
|
-
)?.oldest;
|
|
3351
|
-
if (lastVulnCheck != null) {
|
|
3352
|
-
const ageMs = Date.now() - lastVulnCheck;
|
|
3353
|
-
const ageDays = ageMs / (1e3 * 60 * 60 * 24);
|
|
3354
|
-
const maxAge = config.securityEnrichMaxAgeDays ?? 7;
|
|
3355
|
-
if (ageDays > maxAge) {
|
|
3356
|
-
lines.push("", `\u26A0 Vulnerability data is ${Math.floor(ageDays)} days old (max: ${maxAge}). Run kirograph vulns --refresh to update.`);
|
|
3357
|
-
}
|
|
3358
|
-
}
|
|
3359
|
-
try {
|
|
3360
|
-
const rawDbSec = db.getRawDb();
|
|
3361
|
-
const tableExistsSec = rawDbSec.get("SELECT name FROM sqlite_master WHERE type='table' AND name='pattern_matches'");
|
|
3362
|
-
if (tableExistsSec) {
|
|
3363
|
-
const patternCount = rawDbSec.get("SELECT COUNT(*) as cnt FROM pattern_matches")?.cnt ?? 0;
|
|
3364
|
-
if (patternCount > 0) {
|
|
3365
|
-
const critCount = rawDbSec.get("SELECT COUNT(*) as cnt FROM pattern_matches WHERE severity = 'critical'")?.cnt ?? 0;
|
|
3366
|
-
lines.push(`SAST findings: ${patternCount} pattern matches (${critCount} critical)`);
|
|
3367
|
-
}
|
|
3368
|
-
}
|
|
3369
|
-
} catch {
|
|
3370
|
-
}
|
|
3371
|
-
return lines.join("\n");
|
|
3372
|
-
}
|
|
3373
|
-
case "kirograph_vulns": {
|
|
3374
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3375
|
-
const projectRoot = cg.getProjectRoot();
|
|
3376
|
-
const config = await loadConfig(projectRoot);
|
|
3377
|
-
if (!config.enableSecurity) return "Security analysis is not enabled. Set enableSecurity: true in .kirograph/config.json and run kirograph index.";
|
|
3378
|
-
const db = cg.getDatabase();
|
|
3379
|
-
db.applySecuritySchema();
|
|
3380
|
-
const rawDb = db.getRawDb();
|
|
3381
|
-
if (args.refresh === true) {
|
|
3382
|
-
const { OsvAdapter } = await Promise.resolve().then(() => require("../security/vuln/osv-adapter.js"));
|
|
3383
|
-
const { VulnerabilityDatabaseClient } = await Promise.resolve().then(() => require("../security/vuln/client.js"));
|
|
3384
|
-
const adapters = config.securityDatabases.map((dbName) => {
|
|
3385
|
-
if (dbName.toUpperCase() === "OSV") return new OsvAdapter();
|
|
3386
|
-
return null;
|
|
3387
|
-
}).filter(Boolean);
|
|
3388
|
-
const client = new VulnerabilityDatabaseClient(adapters, db);
|
|
3389
|
-
await client.enrichAll();
|
|
3390
|
-
}
|
|
3391
|
-
let query = `
|
|
3392
|
-
SELECT
|
|
3393
|
-
v.node_id, v.cve_id, v.severity_score, v.fixed_version, v.summary,
|
|
3394
|
-
v.epss_score, v.epss_percentile, v.risk_score,
|
|
3395
|
-
d.package_name, d.ecosystem, d.resolved_version, d.declared_constraint,
|
|
3396
|
-
r.verdict
|
|
3397
|
-
FROM sec_vulnerabilities v
|
|
3398
|
-
LEFT JOIN edges e ON e.target = v.node_id AND e.kind = 'has_vulnerability'
|
|
3399
|
-
LEFT JOIN sec_dependencies d ON d.node_id = e.source
|
|
3400
|
-
LEFT JOIN sec_reachability r ON r.vulnerability_node_id = v.node_id
|
|
3401
|
-
WHERE 1=1
|
|
3402
|
-
`;
|
|
3403
|
-
const params = [];
|
|
3404
|
-
if (args.severity) {
|
|
3405
|
-
const severityRanges = {
|
|
3406
|
-
critical: [9, 10],
|
|
3407
|
-
high: [7, 8.9],
|
|
3408
|
-
medium: [4, 6.9],
|
|
3409
|
-
low: [0.1, 3.9]
|
|
3410
|
-
};
|
|
3411
|
-
const range = severityRanges[args.severity.toLowerCase()];
|
|
3412
|
-
if (!range) return `Invalid severity: ${args.severity}. Use: critical, high, medium, low`;
|
|
3413
|
-
query += ` AND v.severity_score >= ? AND v.severity_score <= ?`;
|
|
3414
|
-
params.push(range[0], range[1]);
|
|
3415
|
-
}
|
|
3416
|
-
if (args.verdict) {
|
|
3417
|
-
const validVerdicts = ["affected", "not_affected", "under_investigation"];
|
|
3418
|
-
if (!validVerdicts.includes(args.verdict)) {
|
|
3419
|
-
return `Invalid verdict: ${args.verdict}. Use: affected, not_affected, under_investigation`;
|
|
3420
|
-
}
|
|
3421
|
-
query += ` AND r.verdict = ?`;
|
|
3422
|
-
params.push(args.verdict);
|
|
3423
|
-
}
|
|
3424
|
-
const limit = clampLimit(args.limit, 20);
|
|
3425
|
-
query += ` ORDER BY v.risk_score DESC NULLS LAST LIMIT ?`;
|
|
3426
|
-
params.push(limit);
|
|
3427
|
-
const rows = rawDb.all(query, params);
|
|
3428
|
-
const verdictRankMcp = (v) => v === "affected" ? 3 : v === "under_investigation" ? 2 : v === "not_affected" ? 1 : 0;
|
|
3429
|
-
const dedupMapMcp = /* @__PURE__ */ new Map();
|
|
3430
|
-
for (const row of rows) {
|
|
3431
|
-
const key = `${row.cve_id}::${row.package_name ?? ""}::${row.ecosystem ?? ""}`;
|
|
3432
|
-
const ex = dedupMapMcp.get(key);
|
|
3433
|
-
if (!ex || verdictRankMcp(row.verdict) > verdictRankMcp(ex.verdict)) dedupMapMcp.set(key, row);
|
|
3434
|
-
}
|
|
3435
|
-
const dedupedRowsMcp = [...dedupMapMcp.values()];
|
|
3436
|
-
const { SuppressionManager } = await Promise.resolve().then(() => require("../security/suppressions.js"));
|
|
3437
|
-
const suppressionMgr = new SuppressionManager(projectRoot);
|
|
3438
|
-
const suppressedCount = dedupedRowsMcp.filter((row) => suppressionMgr.isSuppressed(row.cve_id)).length;
|
|
3439
|
-
const filteredRows = dedupedRowsMcp.filter((row) => !suppressionMgr.isSuppressed(row.cve_id));
|
|
3440
|
-
if (filteredRows.length === 0) {
|
|
3441
|
-
const noMatch = "No vulnerabilities found" + (args.severity || args.verdict ? " matching filters." : ".");
|
|
3442
|
-
return suppressedCount > 0 ? `${noMatch} (${suppressedCount} suppressed)` : noMatch;
|
|
3443
|
-
}
|
|
3444
|
-
const { formatFixSuggestion } = await Promise.resolve().then(() => require("../security/export/fix-suggestions.js"));
|
|
3445
|
-
const lines = [`Vulnerabilities (${filteredRows.length}${suppressedCount > 0 ? `, ${suppressedCount} suppressed` : ""}):
|
|
3446
|
-
`];
|
|
3447
|
-
for (const row of filteredRows) {
|
|
3448
|
-
const score = row.severity_score;
|
|
3449
|
-
let severityLabel;
|
|
3450
|
-
if (score == null) severityLabel = "UNKNOWN";
|
|
3451
|
-
else if (score >= 9) severityLabel = "CRITICAL";
|
|
3452
|
-
else if (score >= 7) severityLabel = "HIGH";
|
|
3453
|
-
else if (score >= 4) severityLabel = "MEDIUM";
|
|
3454
|
-
else severityLabel = "LOW";
|
|
3455
|
-
let verdictLabel;
|
|
3456
|
-
if (!row.verdict) verdictLabel = "pending";
|
|
3457
|
-
else if (row.verdict === "affected") verdictLabel = "affected";
|
|
3458
|
-
else if (row.verdict === "not_affected") verdictLabel = "not affected";
|
|
3459
|
-
else verdictLabel = "investigating";
|
|
3460
|
-
const pkg = row.package_name ? `${row.package_name}@${row.resolved_version || row.declared_constraint || "?"}` : "unknown package";
|
|
3461
|
-
const epssNote = row.epss_score != null ? ` [EPSS: ${row.epss_score.toFixed(2)}]` : "";
|
|
3462
|
-
const riskNote = row.risk_score != null ? ` [Risk: ${row.risk_score.toFixed(1)}]` : "";
|
|
3463
|
-
lines.push(`${severityLabel} ${row.cve_id} ${pkg} [${verdictLabel}]${epssNote}${riskNote}`);
|
|
3464
|
-
if (row.summary && row.summary !== "Manually registered") {
|
|
3465
|
-
const truncSummary = row.summary.length > 120 ? row.summary.slice(0, 120) + "\u2026" : row.summary;
|
|
3466
|
-
lines.push(` ${truncSummary}`);
|
|
3467
|
-
}
|
|
3468
|
-
if (row.fixed_version && row.ecosystem && row.package_name) {
|
|
3469
|
-
const fix = formatFixSuggestion(row.ecosystem, row.package_name, row.fixed_version);
|
|
3470
|
-
if (fix) lines.push(` ${fix}`);
|
|
3471
|
-
}
|
|
3472
|
-
}
|
|
3473
|
-
return truncate(lines.join("\n"));
|
|
3474
|
-
}
|
|
3475
|
-
case "kirograph_vuln_add": {
|
|
3476
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3477
|
-
const projectRoot = cg.getProjectRoot();
|
|
3478
|
-
const config = await loadConfig(projectRoot);
|
|
3479
|
-
if (!config.enableSecurity) return "Security analysis is not enabled. Set enableSecurity: true in .kirograph/config.json and run kirograph index.";
|
|
3480
|
-
const cveId = args.cveId;
|
|
3481
|
-
const pkgName = args.package;
|
|
3482
|
-
if (!cveId) return "Error: cveId is required.";
|
|
3483
|
-
if (!pkgName) return "Error: package is required.";
|
|
3484
|
-
const db = cg.getDatabase();
|
|
3485
|
-
db.applySecuritySchema();
|
|
3486
|
-
const rawDb = db.getRawDb();
|
|
3487
|
-
const depRow = rawDb.get(
|
|
3488
|
-
`SELECT node_id, ecosystem FROM sec_dependencies WHERE package_name = ?`,
|
|
3489
|
-
[pkgName]
|
|
3490
|
-
);
|
|
3491
|
-
if (!depRow) {
|
|
3492
|
-
return `No dependency found matching "${pkgName}". Run kirograph index first to discover dependencies.`;
|
|
3493
|
-
}
|
|
3494
|
-
const vulnNodeId = `vuln:${cveId}`;
|
|
3495
|
-
const now = Date.now();
|
|
3496
|
-
const severity = args.severity;
|
|
3497
|
-
const summary = args.summary ?? "Manually registered";
|
|
3498
|
-
const fixedVersion = args.fixedVersion ?? null;
|
|
3499
|
-
rawDb.run(
|
|
3500
|
-
`INSERT OR REPLACE INTO nodes
|
|
3501
|
-
(id, kind, name, qualified_name, file_path, language,
|
|
3502
|
-
start_line, end_line, start_column, end_column,
|
|
3503
|
-
is_exported, is_async, is_static, is_abstract, updated_at)
|
|
3504
|
-
VALUES (?, 'vulnerability', ?, ?, '', 'unknown', 0, 0, 0, 0, 0, 0, 0, 0, ?)`,
|
|
3505
|
-
[vulnNodeId, cveId, cveId, now]
|
|
3506
|
-
);
|
|
3507
|
-
rawDb.run(
|
|
3508
|
-
`INSERT OR REPLACE INTO sec_vulnerabilities
|
|
3509
|
-
(node_id, cve_id, severity_score, affected_ranges, fixed_version, summary, source_database)
|
|
3510
|
-
VALUES (?, ?, ?, '[]', ?, ?, 'manual')`,
|
|
3511
|
-
[vulnNodeId, cveId, severity ?? null, fixedVersion, summary]
|
|
3512
|
-
);
|
|
3513
|
-
rawDb.run(
|
|
3514
|
-
`INSERT OR IGNORE INTO edges (source, target, kind, confidence, confidence_score)
|
|
3515
|
-
VALUES (?, ?, 'has_vulnerability', 'extracted', 1.0)`,
|
|
3516
|
-
[depRow.node_id, vulnNodeId]
|
|
3517
|
-
);
|
|
3518
|
-
return `Registered ${cveId} against ${pkgName}.`;
|
|
3519
|
-
}
|
|
3520
|
-
case "kirograph_vuln_suppress": {
|
|
3521
|
-
const projectRoot = cg.getProjectRoot();
|
|
3522
|
-
const cveId = args.cveId;
|
|
3523
|
-
if (!cveId) return "Error: cveId is required.";
|
|
3524
|
-
const { SuppressionManager } = await Promise.resolve().then(() => require("../security/suppressions.js"));
|
|
3525
|
-
const manager = new SuppressionManager(projectRoot);
|
|
3526
|
-
manager.add(cveId, args.reason, args.expires);
|
|
3527
|
-
const reasonNote = args.reason ? ` Reason: ${args.reason}.` : "";
|
|
3528
|
-
const expiresNote = args.expires ? ` Expires: ${args.expires}.` : "";
|
|
3529
|
-
return `${cveId} suppressed.${reasonNote}${expiresNote}`;
|
|
3530
|
-
}
|
|
3531
|
-
case "kirograph_sbom": {
|
|
3532
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3533
|
-
const projectRoot = cg.getProjectRoot();
|
|
3534
|
-
const config = await loadConfig(projectRoot);
|
|
3535
|
-
if (!config.enableSecurity) return "Security analysis is not enabled. Set enableSecurity: true in .kirograph/config.json and run kirograph index.";
|
|
3536
|
-
const db = cg.getDatabase();
|
|
3537
|
-
db.applySecuritySchema();
|
|
3538
|
-
const { SBOMExporter } = await Promise.resolve().then(() => require("../security/export/sbom.js"));
|
|
3539
|
-
const exporter = new SBOMExporter(db, projectRoot);
|
|
3540
|
-
const json = exporter.exportJSON();
|
|
3541
|
-
return truncate(json);
|
|
3542
|
-
}
|
|
3543
|
-
case "kirograph_vex": {
|
|
3544
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3545
|
-
const projectRoot = cg.getProjectRoot();
|
|
3546
|
-
const config = await loadConfig(projectRoot);
|
|
3547
|
-
if (!config.enableSecurity) return "Security analysis is not enabled. Set enableSecurity: true in .kirograph/config.json and run kirograph index.";
|
|
3548
|
-
const db = cg.getDatabase();
|
|
3549
|
-
db.applySecuritySchema();
|
|
3550
|
-
const { VEXExporter } = await Promise.resolve().then(() => require("../security/export/vex.js"));
|
|
3551
|
-
const exporter = new VEXExporter(db, projectRoot);
|
|
3552
|
-
const json = exporter.exportJSON();
|
|
3553
|
-
return truncate(json);
|
|
3554
|
-
}
|
|
3555
|
-
case "kirograph_reachability": {
|
|
3556
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3557
|
-
const projectRoot = cg.getProjectRoot();
|
|
3558
|
-
const config = await loadConfig(projectRoot);
|
|
3559
|
-
if (!config.enableSecurity) return "Security analysis is not enabled. Set enableSecurity: true in .kirograph/config.json and run kirograph index.";
|
|
3560
|
-
const target = args.target;
|
|
3561
|
-
if (!target) return "Error: target is required (dependency name or CVE ID).";
|
|
3562
|
-
const db = cg.getDatabase();
|
|
3563
|
-
db.applySecuritySchema();
|
|
3564
|
-
const rawDb = db.getRawDb();
|
|
3565
|
-
let vulnerabilityNodeId = null;
|
|
3566
|
-
let targetLabel = target;
|
|
3567
|
-
const vulnRow = rawDb.get(
|
|
3568
|
-
`SELECT node_id FROM sec_vulnerabilities WHERE cve_id = ?`,
|
|
3569
|
-
[target]
|
|
3570
|
-
);
|
|
3571
|
-
if (vulnRow) {
|
|
3572
|
-
vulnerabilityNodeId = vulnRow.node_id;
|
|
3573
|
-
} else {
|
|
3574
|
-
const depRow = rawDb.get(
|
|
3575
|
-
`SELECT node_id, package_name FROM sec_dependencies WHERE package_name = ?`,
|
|
3576
|
-
[target]
|
|
3577
|
-
);
|
|
3578
|
-
if (depRow) {
|
|
3579
|
-
targetLabel = depRow.package_name;
|
|
3580
|
-
const vulnEdge = rawDb.get(
|
|
3581
|
-
`SELECT target FROM edges WHERE source = ? AND kind = 'has_vulnerability' LIMIT 1`,
|
|
3582
|
-
[depRow.node_id]
|
|
3583
|
-
);
|
|
3584
|
-
if (vulnEdge) {
|
|
3585
|
-
vulnerabilityNodeId = vulnEdge.target;
|
|
3586
|
-
} else {
|
|
3587
|
-
return `No vulnerabilities found for dependency "${target}". The dependency exists but has no known vulnerabilities.`;
|
|
3588
|
-
}
|
|
3589
|
-
} else {
|
|
3590
|
-
return `Target "${target}" not found. Provide a valid CVE ID or dependency package name.`;
|
|
3591
|
-
}
|
|
3592
|
-
}
|
|
3593
|
-
const { ReachabilityAnalyzer } = await Promise.resolve().then(() => require("../security/reachability.js"));
|
|
3594
|
-
const analyzer = new ReachabilityAnalyzer(db, config);
|
|
3595
|
-
const result = await analyzer.analyze(vulnerabilityNodeId);
|
|
3596
|
-
const lines = [
|
|
3597
|
-
`# Reachability: ${targetLabel}`,
|
|
3598
|
-
"",
|
|
3599
|
-
`Verdict: ${result.verdict}`,
|
|
3600
|
-
`Reaching entry points: ${result.reachingEntryPointCount}`
|
|
3601
|
-
];
|
|
3602
|
-
if (result.paths.length > 0) {
|
|
3603
|
-
lines.push("", "## Paths");
|
|
3604
|
-
for (const p of result.paths.slice(0, 5)) {
|
|
3605
|
-
lines.push(`- From ${p.entryPoint}: ${p.path.join(" \u2192 ")}`);
|
|
3606
|
-
}
|
|
3607
|
-
if (result.paths.length > 5) {
|
|
3608
|
-
lines.push(` \u2026and ${result.paths.length - 5} more paths`);
|
|
3609
|
-
}
|
|
3610
|
-
}
|
|
3611
|
-
if (result.unresolvedSymbols.length > 0) {
|
|
3612
|
-
lines.push("", "## Unresolved Symbols");
|
|
3613
|
-
for (const sym of result.unresolvedSymbols.slice(0, 10)) {
|
|
3614
|
-
lines.push(`- ${sym}`);
|
|
3615
|
-
}
|
|
3616
|
-
if (result.unresolvedSymbols.length > 10) {
|
|
3617
|
-
lines.push(` \u2026and ${result.unresolvedSymbols.length - 10} more`);
|
|
3618
|
-
}
|
|
3619
|
-
}
|
|
3620
|
-
if (result.verdict === "affected") {
|
|
3621
|
-
const impact = await analyzer.getImpactSummary(vulnerabilityNodeId);
|
|
3622
|
-
if (impact) {
|
|
3623
|
-
lines.push("", "## Impact Summary");
|
|
3624
|
-
if (impact.affectedLayers.length > 0) {
|
|
3625
|
-
lines.push(`Affected layers: ${impact.affectedLayers.join(", ")}`);
|
|
3626
|
-
}
|
|
3627
|
-
lines.push(`Affected entry points: ${impact.affectedEntryPoints.length}`);
|
|
3628
|
-
lines.push(`Distinct paths: ${impact.distinctPathCount}`);
|
|
3629
|
-
}
|
|
3630
|
-
}
|
|
3631
|
-
return truncate(lines.join("\n"));
|
|
3632
|
-
}
|
|
3633
|
-
case "kirograph_staleness": {
|
|
3634
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3635
|
-
const projectRoot = cg.getProjectRoot();
|
|
3636
|
-
const config = await loadConfig(projectRoot);
|
|
3637
|
-
if (!config.enableSecurity) return "Security analysis is not enabled. Set enableSecurity: true in .kirograph/config.json and run kirograph index.";
|
|
3638
|
-
const db = cg.getDatabase();
|
|
3639
|
-
db.applySecuritySchema();
|
|
3640
|
-
const rawDb = db.getRawDb();
|
|
3641
|
-
const threshold = typeof args.threshold === "number" ? args.threshold : 0.3;
|
|
3642
|
-
if (args.refresh === true) {
|
|
3643
|
-
const { StalenessChecker } = await Promise.resolve().then(() => require("../security/staleness.js"));
|
|
3644
|
-
const checker = new StalenessChecker(db);
|
|
3645
|
-
await checker.checkAll();
|
|
3646
|
-
}
|
|
3647
|
-
const rows = rawDb.all(
|
|
3648
|
-
`SELECT package_name, ecosystem, resolved_version, declared_constraint,
|
|
3649
|
-
latest_version, latest_published, staleness_score
|
|
3650
|
-
FROM sec_dependencies
|
|
3651
|
-
WHERE staleness_score >= ?
|
|
3652
|
-
ORDER BY staleness_score DESC`,
|
|
3653
|
-
[threshold]
|
|
3654
|
-
);
|
|
3655
|
-
if (rows.length === 0) {
|
|
3656
|
-
return `No dependencies found with staleness_score >= ${threshold}.` + (args.refresh ? "" : " Use refresh=true to fetch latest version data from registries.");
|
|
3657
|
-
}
|
|
3658
|
-
const lines = [`Stale Dependencies (threshold: ${threshold}):
|
|
3659
|
-
`];
|
|
3660
|
-
for (const row of rows) {
|
|
3661
|
-
const resolved = row.resolved_version ?? row.declared_constraint ?? "?";
|
|
3662
|
-
const latest = row.latest_version ?? "?";
|
|
3663
|
-
const score = row.staleness_score ?? 0;
|
|
3664
|
-
const months = row.latest_published ? Math.round((Date.now() - row.latest_published) / (1e3 * 60 * 60 * 24 * 30)) : null;
|
|
3665
|
-
const bar = "\u2588".repeat(Math.round(score * 10)) + "\u2591".repeat(10 - Math.round(score * 10));
|
|
3666
|
-
const monthsStr = months !== null ? `, ${months}mo since latest` : "";
|
|
3667
|
-
lines.push(`${row.package_name} (${row.ecosystem}): ${resolved} \u2192 ${latest}${monthsStr}`);
|
|
3668
|
-
lines.push(` ${bar} ${score.toFixed(2)}`);
|
|
3669
|
-
}
|
|
3670
|
-
const totalCount = rawDb.get(`SELECT COUNT(*) as count FROM sec_dependencies`) ?? { count: 0 };
|
|
3671
|
-
lines.push("", `${rows.length} of ${totalCount.count} dependencies are stale (score >= ${threshold})`);
|
|
3672
|
-
return truncate(lines.join("\n"));
|
|
3673
|
-
}
|
|
3674
|
-
case "kirograph_licenses": {
|
|
3675
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3676
|
-
const projectRoot = cg.getProjectRoot();
|
|
3677
|
-
const config = await loadConfig(projectRoot);
|
|
3678
|
-
if (!config.enableSecurity) return "Security analysis is not enabled. Set enableSecurity: true in .kirograph/config.json and run kirograph index.";
|
|
3679
|
-
const db = cg.getDatabase();
|
|
3680
|
-
db.applySecuritySchema();
|
|
3681
|
-
const rawDb = db.getRawDb();
|
|
3682
|
-
const deps = rawDb.all(
|
|
3683
|
-
`SELECT package_name, ecosystem, license FROM sec_dependencies ORDER BY ecosystem, package_name`
|
|
3684
|
-
);
|
|
3685
|
-
const { checkLicensePolicy } = await Promise.resolve().then(() => require("../security/license.js"));
|
|
3686
|
-
const policy = config.securityLicensePolicy;
|
|
3687
|
-
const violations = checkLicensePolicy(deps, policy);
|
|
3688
|
-
if (args.policy === true) {
|
|
3689
|
-
if (violations.length === 0) {
|
|
3690
|
-
return "No license policy violations found.";
|
|
3691
|
-
}
|
|
3692
|
-
const lines2 = ["# License Policy Violations\n"];
|
|
3693
|
-
for (const v of violations) {
|
|
3694
|
-
lines2.push(`${v.severity.toUpperCase()} ${v.packageName} [${v.ecosystem}] ${v.license}`);
|
|
3695
|
-
}
|
|
3696
|
-
const denyCount2 = violations.filter((v) => v.severity === "deny").length;
|
|
3697
|
-
const warnCount2 = violations.filter((v) => v.severity === "warn").length;
|
|
3698
|
-
lines2.push("");
|
|
3699
|
-
if (denyCount2 > 0) lines2.push(`${denyCount2} denied license${denyCount2 !== 1 ? "s" : ""}`);
|
|
3700
|
-
if (warnCount2 > 0) lines2.push(`${warnCount2} license warning${warnCount2 !== 1 ? "s" : ""}`);
|
|
3701
|
-
return truncate(lines2.join("\n"));
|
|
3702
|
-
}
|
|
3703
|
-
if (deps.length === 0) {
|
|
3704
|
-
return "No dependencies found. Run kirograph index first.";
|
|
3705
|
-
}
|
|
3706
|
-
const violationMap = /* @__PURE__ */ new Map();
|
|
3707
|
-
for (const v of violations) {
|
|
3708
|
-
violationMap.set(`${v.ecosystem}:${v.packageName}`, v.severity);
|
|
3709
|
-
}
|
|
3710
|
-
const lines = [`# License Report (${deps.length} dependencies)
|
|
3711
|
-
`];
|
|
3712
|
-
if (violations.length > 0) {
|
|
3713
|
-
lines.push("## Policy Violations\n");
|
|
3714
|
-
for (const v of violations) {
|
|
3715
|
-
lines.push(`${v.severity.toUpperCase()} ${v.packageName} [${v.ecosystem}] ${v.license}`);
|
|
3716
|
-
}
|
|
3717
|
-
lines.push("");
|
|
3718
|
-
}
|
|
3719
|
-
lines.push("## All Dependencies\n");
|
|
3720
|
-
lines.push("package | ecosystem | license | status");
|
|
3721
|
-
lines.push("------- | --------- | ------- | ------");
|
|
3722
|
-
for (const dep of deps) {
|
|
3723
|
-
const key = `${dep.ecosystem}:${dep.package_name}`;
|
|
3724
|
-
const violation = violationMap.get(key);
|
|
3725
|
-
const license = dep.license ?? "(unknown)";
|
|
3726
|
-
const status = violation ?? (dep.license ? "ok" : "unknown");
|
|
3727
|
-
lines.push(`${dep.package_name} | ${dep.ecosystem} | ${license} | ${status}`);
|
|
3728
|
-
}
|
|
3729
|
-
const denyCount = violations.filter((v) => v.severity === "deny").length;
|
|
3730
|
-
const warnCount = violations.filter((v) => v.severity === "warn").length;
|
|
3731
|
-
const unknownCount = deps.filter((d) => !d.license).length;
|
|
3732
|
-
lines.push("");
|
|
3733
|
-
if (denyCount > 0) lines.push(`${denyCount} denied license${denyCount !== 1 ? "s" : ""}`);
|
|
3734
|
-
if (warnCount > 0) lines.push(`${warnCount} license warning${warnCount !== 1 ? "s" : ""}`);
|
|
3735
|
-
if (unknownCount > 0) lines.push(`${unknownCount} unknown license${unknownCount !== 1 ? "s" : ""}`);
|
|
3736
|
-
if (denyCount === 0 && warnCount === 0) lines.push("No policy violations");
|
|
3737
|
-
return truncate(lines.join("\n"));
|
|
3738
|
-
}
|
|
3739
|
-
case "kirograph_attack_surface": {
|
|
3740
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3741
|
-
const projectRoot = cg.getProjectRoot();
|
|
3742
|
-
const config = await loadConfig(projectRoot);
|
|
3743
|
-
if (!config.enableSecurity) return "Security analysis is not enabled. Set enableSecurity: true in .kirograph/config.json and run kirograph index.";
|
|
3744
|
-
const db = cg.getDatabase();
|
|
3745
|
-
db.applySecuritySchema();
|
|
3746
|
-
const { AttackSurfaceAnalyzer } = await Promise.resolve().then(() => require("../security/attack-surface.js"));
|
|
3747
|
-
const analyzer = new AttackSurfaceAnalyzer(db);
|
|
3748
|
-
const result = await analyzer.analyze();
|
|
3749
|
-
if (result.totalRoutes === 0) {
|
|
3750
|
-
return "No route nodes found in the graph. Ensure the project has been indexed with architecture analysis enabled.";
|
|
3751
|
-
}
|
|
3752
|
-
const limit = clampLimit(args.limit, 20);
|
|
3753
|
-
const routes = args.publicOnly === true ? result.criticalPaths.filter((r) => r.exposureLevel === "public") : result.criticalPaths;
|
|
3754
|
-
const lines = [
|
|
3755
|
-
"# Attack Surface",
|
|
3756
|
-
"",
|
|
3757
|
-
`Total routes: ${result.totalRoutes} Public: ${result.publicRoutes} Authenticated: ${result.authenticatedRoutes} Routes with vulns: ${result.routesWithVulns}`,
|
|
3758
|
-
""
|
|
3759
|
-
];
|
|
3760
|
-
const displayed = routes.slice(0, limit);
|
|
3761
|
-
if (displayed.length === 0) {
|
|
3762
|
-
lines.push(args.publicOnly === true ? "No public routes with vulnerable dependencies found." : "No routes with vulnerable dependencies found.");
|
|
3763
|
-
} else {
|
|
3764
|
-
for (const entry of displayed) {
|
|
3765
|
-
const authTag = entry.isAuthenticated ? "[auth]" : "[public]";
|
|
3766
|
-
const riskTag = entry.riskScore > 0 ? ` risk=${entry.riskScore.toFixed(1)}` : "";
|
|
3767
|
-
lines.push(`${authTag} ${entry.route} (${entry.exposureLevel})${riskTag} ${entry.filePath}`);
|
|
3768
|
-
for (const dep of entry.vulnerableDeps.slice(0, 3)) {
|
|
3769
|
-
lines.push(` \u2514 ${dep.cveId} via ${dep.packageName} (${dep.hopCount} hop${dep.hopCount !== 1 ? "s" : ""}${dep.verdict ? `, ${dep.verdict}` : ""})`);
|
|
3770
|
-
}
|
|
3771
|
-
if (entry.vulnerableDeps.length > 3) {
|
|
3772
|
-
lines.push(` \u2514 \u2026and ${entry.vulnerableDeps.length - 3} more vulns`);
|
|
3773
|
-
}
|
|
3774
|
-
}
|
|
3775
|
-
if (routes.length > limit) {
|
|
3776
|
-
lines.push("", `\u2026and ${routes.length - limit} more routes (increase limit to see all)`);
|
|
3777
|
-
}
|
|
3778
|
-
}
|
|
3779
|
-
return truncate(lines.join("\n"));
|
|
3780
|
-
}
|
|
3781
|
-
case "kirograph_secrets": {
|
|
3782
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3783
|
-
const projectRoot = cg.getProjectRoot();
|
|
3784
|
-
const config = await loadConfig(projectRoot);
|
|
3785
|
-
if (!config.enableSecurity) return "Security analysis is not enabled. Set enableSecurity: true in .kirograph/config.json and run kirograph index.";
|
|
3786
|
-
const db = cg.getDatabase();
|
|
3787
|
-
db.applySecuritySchema();
|
|
3788
|
-
const { SecretsScanner } = await Promise.resolve().then(() => require("../security/secrets.js"));
|
|
3789
|
-
const scanner = new SecretsScanner(db, projectRoot);
|
|
3790
|
-
const result = await scanner.scan({ includeTests: args.includeTests === true });
|
|
3791
|
-
if (result.totalFindings === 0) {
|
|
3792
|
-
return `No secrets found. Scanned ${result.filesScanned} file${result.filesScanned !== 1 ? "s" : ""}.`;
|
|
3793
|
-
}
|
|
3794
|
-
let findings = result.findings;
|
|
3795
|
-
if (args.severity) {
|
|
3796
|
-
findings = findings.filter((f) => f.severity === args.severity);
|
|
3797
|
-
}
|
|
3798
|
-
if (findings.length === 0) {
|
|
3799
|
-
return `No secrets found matching severity "${args.severity}". Total findings (all severities): ${result.totalFindings}.`;
|
|
3800
|
-
}
|
|
3801
|
-
const lines = [
|
|
3802
|
-
`# Secrets Scan (${result.filesScanned} files scanned)`,
|
|
3803
|
-
"",
|
|
3804
|
-
`Found: ${result.totalFindings} Critical: ${result.criticalCount} High: ${result.highCount}`,
|
|
3805
|
-
""
|
|
3806
|
-
];
|
|
3807
|
-
for (const f of findings) {
|
|
3808
|
-
lines.push(`${f.severity.toUpperCase()} ${f.type}`);
|
|
3809
|
-
lines.push(` ${f.filePath}:${f.line}:${f.column} snippet: ${f.snippet}`);
|
|
3810
|
-
if (f.nodeName) {
|
|
3811
|
-
lines.push(` in function: ${f.nodeName}`);
|
|
3812
|
-
}
|
|
3813
|
-
if (f.entryPointCount > 0) {
|
|
3814
|
-
lines.push(` reachable from ${f.entryPointCount} entry point${f.entryPointCount !== 1 ? "s" : ""}`);
|
|
3815
|
-
}
|
|
3816
|
-
}
|
|
3817
|
-
return truncate(lines.join("\n"));
|
|
3818
|
-
}
|
|
3819
|
-
case "kirograph_security_flows": {
|
|
3820
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3821
|
-
const projectRoot = cg.getProjectRoot();
|
|
3822
|
-
const config = await loadConfig(projectRoot);
|
|
3823
|
-
if (!config.enableSecurity) return "Security analysis is not enabled. Set enableSecurity: true in .kirograph/config.json and run kirograph index.";
|
|
3824
|
-
const db = cg.getDatabase();
|
|
3825
|
-
db.applySecuritySchema();
|
|
3826
|
-
const { DataFlowAnalyzer } = await Promise.resolve().then(() => require("../security/data-flows.js"));
|
|
3827
|
-
const analyzer = new DataFlowAnalyzer(db);
|
|
3828
|
-
let findings = await analyzer.analyze();
|
|
3829
|
-
const typeFilter = args.type ?? "all";
|
|
3830
|
-
const TYPE_MAP = {
|
|
3831
|
-
sql: "sql-injection",
|
|
3832
|
-
eval: "dangerous-eval",
|
|
3833
|
-
deserialize: "unsafe-deserialize",
|
|
3834
|
-
path: "path-traversal",
|
|
3835
|
-
crypto: "hardcoded-crypto"
|
|
3836
|
-
};
|
|
3837
|
-
if (typeFilter !== "all") {
|
|
3838
|
-
const mapped = TYPE_MAP[typeFilter];
|
|
3839
|
-
if (!mapped) return `Invalid type filter "${typeFilter}". Use: sql, eval, deserialize, path, crypto, all`;
|
|
3840
|
-
findings = findings.filter((f) => f.type === mapped);
|
|
3841
|
-
}
|
|
3842
|
-
if (findings.length === 0) {
|
|
3843
|
-
return typeFilter === "all" ? "No dangerous data flows detected." : `No "${typeFilter}" findings detected.`;
|
|
3844
|
-
}
|
|
3845
|
-
const lines = [
|
|
3846
|
-
`# Security Flows (${findings.length} finding${findings.length !== 1 ? "s" : ""})`,
|
|
3847
|
-
""
|
|
3848
|
-
];
|
|
3849
|
-
for (const f of findings) {
|
|
3850
|
-
lines.push(`${f.severity.toUpperCase()} [${f.owaspCategory}] ${f.type}`);
|
|
3851
|
-
lines.push(` ${f.filePath}:${f.line} symbol: ${f.symbol}`);
|
|
3852
|
-
lines.push(` ${f.description}`);
|
|
3853
|
-
lines.push(` Fix: ${f.recommendation}`);
|
|
3854
|
-
lines.push("");
|
|
3855
|
-
}
|
|
3856
|
-
return truncate(lines.join("\n"));
|
|
3857
|
-
}
|
|
3858
|
-
case "kirograph_supply_chain": {
|
|
3859
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3860
|
-
const projectRoot = cg.getProjectRoot();
|
|
3861
|
-
const config = await loadConfig(projectRoot);
|
|
3862
|
-
if (!config.enableSecurity) return "Security analysis is not enabled. Set enableSecurity: true in .kirograph/config.json and run kirograph index.";
|
|
3863
|
-
const db = cg.getDatabase();
|
|
3864
|
-
db.applySecuritySchema();
|
|
3865
|
-
const { SupplyChainChecker } = await Promise.resolve().then(() => require("../security/supply-chain.js"));
|
|
3866
|
-
const checker = new SupplyChainChecker(db);
|
|
3867
|
-
const { results, errors } = await checker.checkAll();
|
|
3868
|
-
const RISK_ORDER = { critical: 0, high: 1, medium: 2, low: 3, unknown: 4 };
|
|
3869
|
-
const thresholdArg = args.threshold;
|
|
3870
|
-
const thresholdOrder = thresholdArg ? RISK_ORDER[thresholdArg] ?? 4 : 2;
|
|
3871
|
-
const filtered = results.filter((r) => (RISK_ORDER[r.riskLevel] ?? 4) <= thresholdOrder).sort((a, b) => (RISK_ORDER[a.riskLevel] ?? 4) - (RISK_ORDER[b.riskLevel] ?? 4));
|
|
3872
|
-
if (filtered.length === 0) {
|
|
3873
|
-
return `No supply chain risks found at threshold "${thresholdArg ?? "medium"}" or above. Checked ${results.length} dependencies.`;
|
|
3874
|
-
}
|
|
3875
|
-
const lines = [
|
|
3876
|
-
`# Supply Chain Health (${filtered.length} risks, threshold: ${thresholdArg ?? "medium"})`,
|
|
3877
|
-
""
|
|
3878
|
-
];
|
|
3879
|
-
for (const r of filtered) {
|
|
3880
|
-
const scoreStr = r.scorecardScore !== null ? ` scorecard=${r.scorecardScore.toFixed(1)}/10` : "";
|
|
3881
|
-
const maintainerStr = r.maintainerCount !== null ? ` maintainers=${r.maintainerCount}` : "";
|
|
3882
|
-
lines.push(`${r.riskLevel.toUpperCase()} ${r.packageName} (${r.ecosystem})${scoreStr}${maintainerStr}`);
|
|
3883
|
-
for (const reason of r.riskReasons) {
|
|
3884
|
-
lines.push(` \u2022 ${reason}`);
|
|
3885
|
-
}
|
|
3886
|
-
}
|
|
3887
|
-
if (errors.length > 0) {
|
|
3888
|
-
lines.push("", `\u26A0 ${errors.length} package${errors.length !== 1 ? "s" : ""} could not be checked (network errors)`);
|
|
3889
|
-
}
|
|
3890
|
-
return truncate(lines.join("\n"));
|
|
3891
|
-
}
|
|
3892
|
-
case "kirograph_dep_confusion": {
|
|
3893
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3894
|
-
const projectRoot = cg.getProjectRoot();
|
|
3895
|
-
const config = await loadConfig(projectRoot);
|
|
3896
|
-
if (!config.enableSecurity) return "Security analysis is not enabled. Set enableSecurity: true in .kirograph/config.json and run kirograph index.";
|
|
3897
|
-
const db = cg.getDatabase();
|
|
3898
|
-
db.applySecuritySchema();
|
|
3899
|
-
const { DepConfusionChecker } = await Promise.resolve().then(() => require("../security/dep-confusion.js"));
|
|
3900
|
-
const checker = new DepConfusionChecker(db);
|
|
3901
|
-
const findings = await checker.check();
|
|
3902
|
-
if (findings.length === 0) {
|
|
3903
|
-
return "No dependency confusion vulnerabilities detected.";
|
|
3904
|
-
}
|
|
3905
|
-
const RISK_ORDER = { critical: 0, high: 1, medium: 2 };
|
|
3906
|
-
const sorted = [...findings].sort((a, b) => (RISK_ORDER[a.riskLevel] ?? 3) - (RISK_ORDER[b.riskLevel] ?? 3));
|
|
3907
|
-
const lines = [
|
|
3908
|
-
`# Dependency Confusion (${findings.length} finding${findings.length !== 1 ? "s" : ""})`,
|
|
3909
|
-
""
|
|
3910
|
-
];
|
|
3911
|
-
for (const f of sorted) {
|
|
3912
|
-
lines.push(`${f.riskLevel.toUpperCase()} ${f.packageName} (${f.ecosystem})`);
|
|
3913
|
-
lines.push(` ${f.explanation}`);
|
|
3914
|
-
if (f.publicExists && f.publicVersion) {
|
|
3915
|
-
lines.push(` Public version: ${f.publicVersion}${f.publicPublishedAt ? ` published: ${f.publicPublishedAt}` : ""}`);
|
|
3916
|
-
}
|
|
3917
|
-
}
|
|
3918
|
-
return truncate(lines.join("\n"));
|
|
3919
|
-
}
|
|
3920
|
-
case "kirograph_remediation": {
|
|
3921
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3922
|
-
const projectRoot = cg.getProjectRoot();
|
|
3923
|
-
const config = await loadConfig(projectRoot);
|
|
3924
|
-
if (!config.enableSecurity) return "Security analysis is not enabled. Set enableSecurity: true in .kirograph/config.json and run kirograph index.";
|
|
3925
|
-
const db = cg.getDatabase();
|
|
3926
|
-
db.applySecuritySchema();
|
|
3927
|
-
const { RemediationTracker } = await Promise.resolve().then(() => require("../security/remediation.js"));
|
|
3928
|
-
const tracker = new RemediationTracker(db);
|
|
3929
|
-
let statuses = tracker.getStatus();
|
|
3930
|
-
if (args.overdueOnly === true) {
|
|
3931
|
-
statuses = statuses.filter((s) => s.isOverdue);
|
|
3932
|
-
}
|
|
3933
|
-
if (statuses.length === 0) {
|
|
3934
|
-
return args.overdueOnly === true ? "No overdue vulnerabilities found." : "No open vulnerabilities with SLA tracking data found.";
|
|
3935
|
-
}
|
|
3936
|
-
const SLA_ORDER = { overdue: 0, warning: 1, no_fix: 2, ok: 3 };
|
|
3937
|
-
statuses.sort((a, b) => {
|
|
3938
|
-
const slaA = SLA_ORDER[a.slaStatus] ?? 3;
|
|
3939
|
-
const slaB = SLA_ORDER[b.slaStatus] ?? 3;
|
|
3940
|
-
if (slaA !== slaB) return slaA - slaB;
|
|
3941
|
-
return (b.severity ?? 0) - (a.severity ?? 0);
|
|
3942
|
-
});
|
|
3943
|
-
const overdueCount = statuses.filter((s) => s.isOverdue).length;
|
|
3944
|
-
const warningCount = statuses.filter((s) => s.slaStatus === "warning").length;
|
|
3945
|
-
const lines = [
|
|
3946
|
-
`# Remediation SLA (${statuses.length} open${overdueCount > 0 ? `, ${overdueCount} overdue` : ""}${warningCount > 0 ? `, ${warningCount} warning` : ""})`,
|
|
3947
|
-
""
|
|
3948
|
-
];
|
|
3949
|
-
for (const s of statuses) {
|
|
3950
|
-
const severityLabel = s.severity == null ? "UNKNOWN" : s.severity >= 9 ? "CRITICAL" : s.severity >= 7 ? "HIGH" : s.severity >= 4 ? "MEDIUM" : "LOW";
|
|
3951
|
-
const slaTag = s.slaStatus === "overdue" ? "[OVERDUE]" : s.slaStatus === "warning" ? "[WARNING]" : s.slaStatus === "no_fix" ? "[NO_FIX]" : "[OK]";
|
|
3952
|
-
lines.push(`${slaTag} ${severityLabel} ${s.cveId} ${s.packageName}`);
|
|
3953
|
-
if (s.daysOpen !== null) lines.push(` Open for ${s.daysOpen} day${s.daysOpen !== 1 ? "s" : ""}`);
|
|
3954
|
-
if (s.daysWithFixAvailable !== null) lines.push(` Fix available for ${s.daysWithFixAvailable} day${s.daysWithFixAvailable !== 1 ? "s" : ""}`);
|
|
3955
|
-
if (s.slaDeadline !== null) {
|
|
3956
|
-
const deadlineDate = new Date(s.slaDeadline).toISOString().slice(0, 10);
|
|
3957
|
-
lines.push(` SLA deadline: ${deadlineDate}`);
|
|
3958
|
-
}
|
|
3959
|
-
}
|
|
3960
|
-
return truncate(lines.join("\n"));
|
|
3961
|
-
}
|
|
3962
|
-
case "kirograph_live_search": {
|
|
3963
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3964
|
-
const projectRoot = cg.getProjectRoot();
|
|
3965
|
-
const config = await loadConfig(projectRoot);
|
|
3966
|
-
if (!config.enablePatterns) {
|
|
3967
|
-
return "kirograph_live_search requires enablePatterns: true in .kirograph/config.json";
|
|
3968
|
-
}
|
|
3969
|
-
const { PatternRunner } = await Promise.resolve().then(() => require("../patterns/runner.js"));
|
|
3970
|
-
const runner = new PatternRunner();
|
|
3971
|
-
if (!runner.isAvailable()) {
|
|
3972
|
-
return "kirograph_live_search requires @ast-grep/napi. Run: npm install @ast-grep/napi";
|
|
3973
|
-
}
|
|
3974
|
-
const pattern = args.pattern;
|
|
3975
|
-
const language = args.language;
|
|
3976
|
-
const rawLimit = typeof args.limit === "number" ? args.limit : 20;
|
|
3977
|
-
const limit = Math.max(1, Math.min(100, Math.round(rawLimit)));
|
|
3978
|
-
const db = cg.getDatabase();
|
|
3979
|
-
const rawDb = db.getRawDb();
|
|
3980
|
-
const files = rawDb.all(
|
|
3981
|
-
"SELECT path FROM files WHERE language = ? LIMIT 5000",
|
|
3982
|
-
[language]
|
|
3983
|
-
);
|
|
3984
|
-
const results = [];
|
|
3985
|
-
let truncated = false;
|
|
3986
|
-
for (const file of files) {
|
|
3987
|
-
if (results.length >= limit) {
|
|
3988
|
-
truncated = true;
|
|
3989
|
-
break;
|
|
3990
|
-
}
|
|
3991
|
-
let content;
|
|
3992
|
-
try {
|
|
3993
|
-
const fullPath = path.isAbsolute(file.path) ? file.path : path.join(projectRoot, file.path);
|
|
3994
|
-
content = fs.readFileSync(fullPath, "utf8");
|
|
3995
|
-
} catch {
|
|
3996
|
-
continue;
|
|
3997
|
-
}
|
|
3998
|
-
const matches = await runner.runInline(pattern, language, content);
|
|
3999
|
-
for (const m of matches) {
|
|
4000
|
-
if (results.length >= limit) {
|
|
4001
|
-
truncated = true;
|
|
4002
|
-
break;
|
|
4003
|
-
}
|
|
4004
|
-
results.push({ filePath: file.path, line: m.line, matchText: m.matchText });
|
|
4005
|
-
}
|
|
4006
|
-
}
|
|
4007
|
-
if (results.length === 0) {
|
|
4008
|
-
return `0 matches for '${pattern}' in ${language} files`;
|
|
4009
|
-
}
|
|
4010
|
-
const lines = [
|
|
4011
|
-
`${results.length} match${results.length !== 1 ? "es" : ""} for '${pattern}' in ${language} files`,
|
|
4012
|
-
""
|
|
4013
|
-
];
|
|
4014
|
-
for (const r of results) {
|
|
4015
|
-
lines.push(` ${r.filePath}:${r.line}`);
|
|
4016
|
-
lines.push(` ${r.matchText}`);
|
|
4017
|
-
lines.push("");
|
|
4018
|
-
}
|
|
4019
|
-
if (truncated) {
|
|
4020
|
-
lines.push(`[truncated \u2014 showing first ${limit} results; refine your pattern or use --lang to narrow scope]`);
|
|
4021
|
-
}
|
|
4022
|
-
return truncate(lines.join("\n"));
|
|
4023
|
-
}
|
|
4024
|
-
// ── Pattern tools ──────────────────────────────────────────────────────────
|
|
4025
|
-
case "kirograph_pattern_coverage": {
|
|
4026
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
4027
|
-
const projectRoot = cg.getProjectRoot();
|
|
4028
|
-
const config = await loadConfig(projectRoot);
|
|
4029
|
-
if (!config.enablePatterns) {
|
|
4030
|
-
return "Pattern analysis is not enabled. Set enablePatterns: true in .kirograph/config.json and run kirograph index.";
|
|
4031
|
-
}
|
|
4032
|
-
const { PatternLibraryLoader } = await Promise.resolve().then(() => require("../patterns/loader.js"));
|
|
4033
|
-
const loader = new PatternLibraryLoader();
|
|
4034
|
-
const builtinPath = path.join(__dirname, "../patterns/library");
|
|
4035
|
-
const customPath = config.patternLibraryPath;
|
|
4036
|
-
let rules = [];
|
|
4037
|
-
try {
|
|
4038
|
-
rules = loader.load(builtinPath, customPath);
|
|
4039
|
-
} catch {
|
|
4040
|
-
}
|
|
4041
|
-
const rulesByCategory = /* @__PURE__ */ new Map();
|
|
4042
|
-
for (const rule of rules) {
|
|
4043
|
-
const prefix = rule.owaspCategory.match(/^(A\d{2})/)?.[1] ?? rule.owaspCategory;
|
|
4044
|
-
if (!rulesByCategory.has(prefix)) rulesByCategory.set(prefix, []);
|
|
4045
|
-
rulesByCategory.get(prefix).push(rule);
|
|
4046
|
-
}
|
|
4047
|
-
const db = cg.getDatabase();
|
|
4048
|
-
db.applyPatternsSchema();
|
|
4049
|
-
const rawDb = db.getRawDb();
|
|
4050
|
-
const matchCountsByCategory = /* @__PURE__ */ new Map();
|
|
4051
|
-
try {
|
|
4052
|
-
const tableExists = rawDb.get("SELECT name FROM sqlite_master WHERE type='table' AND name='pattern_matches'");
|
|
4053
|
-
if (tableExists) {
|
|
4054
|
-
const rows = rawDb.all(
|
|
4055
|
-
"SELECT owasp_category, COUNT(*) as cnt FROM pattern_matches GROUP BY owasp_category"
|
|
4056
|
-
);
|
|
4057
|
-
for (const row of rows) {
|
|
4058
|
-
const prefix = row.owasp_category.match(/^(A\d{2})/)?.[1] ?? row.owasp_category;
|
|
4059
|
-
matchCountsByCategory.set(prefix, (matchCountsByCategory.get(prefix) ?? 0) + row.cnt);
|
|
4060
|
-
}
|
|
4061
|
-
}
|
|
4062
|
-
} catch {
|
|
4063
|
-
}
|
|
4064
|
-
const owaspTop10 = [
|
|
4065
|
-
"A01",
|
|
4066
|
-
"A02",
|
|
4067
|
-
"A03",
|
|
4068
|
-
"A04",
|
|
4069
|
-
"A05",
|
|
4070
|
-
"A06",
|
|
4071
|
-
"A07",
|
|
4072
|
-
"A08",
|
|
4073
|
-
"A09",
|
|
4074
|
-
"A10"
|
|
4075
|
-
];
|
|
4076
|
-
const owaspNames = {
|
|
4077
|
-
A01: "Broken Access Control",
|
|
4078
|
-
A02: "Cryptographic Failures",
|
|
4079
|
-
A03: "Injection",
|
|
4080
|
-
A04: "Insecure Design",
|
|
4081
|
-
A05: "Security Misconfiguration",
|
|
4082
|
-
A06: "Vulnerable and Outdated Components",
|
|
4083
|
-
A07: "Identification and Authentication Failures",
|
|
4084
|
-
A08: "Software and Data Integrity Failures",
|
|
4085
|
-
A09: "Security Logging and Monitoring Failures",
|
|
4086
|
-
A10: "Server-Side Request Forgery"
|
|
4087
|
-
};
|
|
4088
|
-
const covered = [];
|
|
4089
|
-
const uncovered = [];
|
|
4090
|
-
const lines = [
|
|
4091
|
-
"# OWASP Top 10 Pattern Coverage",
|
|
4092
|
-
"",
|
|
4093
|
-
`Total rules loaded: ${rules.length}`,
|
|
4094
|
-
"",
|
|
4095
|
-
"Category | Rules | Matches",
|
|
4096
|
-
"-------- | ----- | -------"
|
|
4097
|
-
];
|
|
4098
|
-
for (const cat of owaspTop10) {
|
|
4099
|
-
const catRules = rulesByCategory.get(cat) ?? [];
|
|
4100
|
-
const matchCount = matchCountsByCategory.get(cat) ?? 0;
|
|
4101
|
-
const label = `${cat}: ${owaspNames[cat] ?? cat}`;
|
|
4102
|
-
if (catRules.length > 0) {
|
|
4103
|
-
covered.push(label);
|
|
4104
|
-
lines.push(`${label} | ${catRules.length} | ${matchCount}`);
|
|
4105
|
-
} else {
|
|
4106
|
-
uncovered.push(label);
|
|
4107
|
-
}
|
|
4108
|
-
}
|
|
4109
|
-
for (const [cat, catRules] of rulesByCategory.entries()) {
|
|
4110
|
-
if (!owaspTop10.includes(cat)) {
|
|
4111
|
-
const matchCount = matchCountsByCategory.get(cat) ?? 0;
|
|
4112
|
-
lines.push(`${cat} | ${catRules.length} | ${matchCount}`);
|
|
4113
|
-
}
|
|
4114
|
-
}
|
|
4115
|
-
if (uncovered.length > 0) {
|
|
4116
|
-
lines.push("", `Uncovered categories (${uncovered.length}):`, ...uncovered.map((c) => ` - ${c}`));
|
|
4117
|
-
}
|
|
4118
|
-
lines.push("", `Coverage: ${covered.length} / ${owaspTop10.length} OWASP Top 10 categories`);
|
|
4119
|
-
return lines.join("\n");
|
|
4120
|
-
}
|
|
4121
|
-
case "kirograph_pattern_save_baseline": {
|
|
4122
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
4123
|
-
const projectRoot = cg.getProjectRoot();
|
|
4124
|
-
const config = await loadConfig(projectRoot);
|
|
4125
|
-
if (!config.enablePatterns) {
|
|
4126
|
-
return "Pattern analysis is not enabled. Set enablePatterns: true in .kirograph/config.json and run kirograph index.";
|
|
4127
|
-
}
|
|
4128
|
-
const label = args.label ?? "default";
|
|
4129
|
-
const db = cg.getDatabase();
|
|
4130
|
-
db.applyPatternsSchema();
|
|
4131
|
-
const rawDb = db.getRawDb();
|
|
4132
|
-
const tableExists = rawDb.get("SELECT name FROM sqlite_master WHERE type='table' AND name='pattern_matches'");
|
|
4133
|
-
if (!tableExists) {
|
|
4134
|
-
return "No pattern_matches table found. Run kirograph index first to populate pattern data.";
|
|
4135
|
-
}
|
|
4136
|
-
const rows = rawDb.all(
|
|
4137
|
-
"SELECT pattern_id, COUNT(*) as cnt FROM pattern_matches GROUP BY pattern_id"
|
|
4138
|
-
);
|
|
4139
|
-
const baseline = {};
|
|
4140
|
-
for (const row of rows) {
|
|
4141
|
-
baseline[row.pattern_id] = row.cnt;
|
|
4142
|
-
}
|
|
4143
|
-
const kirographDir = path.join(projectRoot, ".kirograph");
|
|
4144
|
-
if (!fs.existsSync(kirographDir)) fs.mkdirSync(kirographDir, { recursive: true });
|
|
4145
|
-
const baselinePath = path.join(kirographDir, `pattern-baseline-${label}.json`);
|
|
4146
|
-
fs.writeFileSync(baselinePath, JSON.stringify({ label, savedAt: Date.now(), counts: baseline }, null, 2));
|
|
4147
|
-
const totalMatches = Object.values(baseline).reduce((s, c) => s + c, 0);
|
|
4148
|
-
return `Baseline "${label}" saved: ${Object.keys(baseline).length} patterns, ${totalMatches} total matches \u2192 ${baselinePath}`;
|
|
4149
|
-
}
|
|
4150
|
-
case "kirograph_pattern_diff": {
|
|
4151
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
4152
|
-
const projectRoot = cg.getProjectRoot();
|
|
4153
|
-
const config = await loadConfig(projectRoot);
|
|
4154
|
-
if (!config.enablePatterns) {
|
|
4155
|
-
return "Pattern analysis is not enabled. Set enablePatterns: true in .kirograph/config.json and run kirograph index.";
|
|
4156
|
-
}
|
|
4157
|
-
const label = args.label ?? "default";
|
|
4158
|
-
const baselinePath = path.join(projectRoot, ".kirograph", `pattern-baseline-${label}.json`);
|
|
4159
|
-
if (!fs.existsSync(baselinePath)) {
|
|
4160
|
-
return `Baseline "${label}" not found. Run kirograph_pattern_save_baseline first.`;
|
|
4161
|
-
}
|
|
4162
|
-
let baseline;
|
|
4163
|
-
try {
|
|
4164
|
-
baseline = JSON.parse(fs.readFileSync(baselinePath, "utf8"));
|
|
4165
|
-
} catch {
|
|
4166
|
-
return `Failed to read baseline file: ${baselinePath}`;
|
|
4167
|
-
}
|
|
4168
|
-
const db = cg.getDatabase();
|
|
4169
|
-
db.applyPatternsSchema();
|
|
4170
|
-
const rawDb = db.getRawDb();
|
|
4171
|
-
const tableExists = rawDb.get("SELECT name FROM sqlite_master WHERE type='table' AND name='pattern_matches'");
|
|
4172
|
-
const currentCounts = {};
|
|
4173
|
-
if (tableExists) {
|
|
4174
|
-
const rows = rawDb.all(
|
|
4175
|
-
"SELECT pattern_id, COUNT(*) as cnt FROM pattern_matches GROUP BY pattern_id"
|
|
4176
|
-
);
|
|
4177
|
-
for (const row of rows) {
|
|
4178
|
-
currentCounts[row.pattern_id] = row.cnt;
|
|
4179
|
-
}
|
|
4180
|
-
}
|
|
4181
|
-
const allPatternIds = /* @__PURE__ */ new Set([...Object.keys(baseline.counts), ...Object.keys(currentCounts)]);
|
|
4182
|
-
const newFindings = [];
|
|
4183
|
-
const resolvedFindings = [];
|
|
4184
|
-
const unchanged = [];
|
|
4185
|
-
const changed = [];
|
|
4186
|
-
for (const id of allPatternIds) {
|
|
4187
|
-
const before = baseline.counts[id] ?? 0;
|
|
4188
|
-
const after = currentCounts[id] ?? 0;
|
|
4189
|
-
if (before === 0 && after > 0) {
|
|
4190
|
-
newFindings.push({ id, count: after });
|
|
4191
|
-
} else if (before > 0 && after === 0) {
|
|
4192
|
-
resolvedFindings.push({ id, count: before });
|
|
4193
|
-
} else if (before === after) {
|
|
4194
|
-
unchanged.push({ id, count: after });
|
|
4195
|
-
} else {
|
|
4196
|
-
changed.push({ id, before, after, delta: after - before });
|
|
4197
|
-
}
|
|
4198
|
-
}
|
|
4199
|
-
const savedDate = new Date(baseline.savedAt).toISOString().slice(0, 16).replace("T", " ");
|
|
4200
|
-
const lines = [
|
|
4201
|
-
`# Pattern Diff: "${label}" (saved ${savedDate}) \u2192 current`,
|
|
4202
|
-
"",
|
|
4203
|
-
`NEW: ${newFindings.length} RESOLVED: ${resolvedFindings.length} CHANGED: ${changed.length} UNCHANGED: ${unchanged.length}`
|
|
4204
|
-
];
|
|
4205
|
-
if (newFindings.length > 0) {
|
|
4206
|
-
lines.push("", "## New Findings (not in baseline)");
|
|
4207
|
-
for (const f of newFindings.sort((a, b) => b.count - a.count)) {
|
|
4208
|
-
lines.push(` + ${f.id}: ${f.count} matches`);
|
|
4209
|
-
}
|
|
4210
|
-
}
|
|
4211
|
-
if (resolvedFindings.length > 0) {
|
|
4212
|
-
lines.push("", "## Resolved (in baseline, not in current)");
|
|
4213
|
-
for (const f of resolvedFindings.sort((a, b) => b.count - a.count)) {
|
|
4214
|
-
lines.push(` - ${f.id}: was ${f.count} matches`);
|
|
4215
|
-
}
|
|
4216
|
-
}
|
|
4217
|
-
if (changed.length > 0) {
|
|
4218
|
-
lines.push("", "## Changed");
|
|
4219
|
-
for (const f of changed.sort((a, b) => Math.abs(b.delta) - Math.abs(a.delta))) {
|
|
4220
|
-
const sign = f.delta > 0 ? "+" : "";
|
|
4221
|
-
lines.push(` ~ ${f.id}: ${f.before} \u2192 ${f.after} (${sign}${f.delta})`);
|
|
4222
|
-
}
|
|
4223
|
-
}
|
|
4224
|
-
return lines.join("\n");
|
|
4225
|
-
}
|
|
4226
|
-
// ── Wiki tools ────────────────────────────────────────────────────────────
|
|
4227
|
-
case "kirograph_wiki_ingest": {
|
|
4228
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
4229
|
-
const projectRoot = cg.getProjectRoot();
|
|
4230
|
-
const config = await loadConfig(projectRoot);
|
|
4231
|
-
if (!config.enableWiki) return "Wiki is not enabled. Set enableWiki: true in .kirograph/config.json";
|
|
4232
|
-
const { KiroGraphWiki } = await Promise.resolve().then(() => require("../wiki/index.js"));
|
|
4233
|
-
const db = cg.getDatabase();
|
|
4234
|
-
db.applyWikiSchema();
|
|
4235
|
-
const wiki = new KiroGraphWiki(db.getRawDb(), projectRoot + "/.kirograph", {
|
|
4236
|
-
autoResolveConflicts: config.wikiAutoResolveConflicts ?? false
|
|
4237
|
-
});
|
|
4238
|
-
wiki.initialize();
|
|
4239
|
-
const source = args.source ?? "";
|
|
4240
|
-
const sourceName = args.sourceName ?? "source";
|
|
4241
|
-
if (!source) return "source is required";
|
|
4242
|
-
if (config.wikiSynthesisMode === "local") {
|
|
4243
|
-
wiki.queueSource(source, sourceName);
|
|
4244
|
-
const count = wiki.getQueueCount();
|
|
4245
|
-
return `Source "${sourceName}" queued for local wiki synthesis (${count} in queue). The agentStop hook will run "kirograph wiki synthesize" automatically.`;
|
|
4246
|
-
}
|
|
4247
|
-
const prompt = wiki.getIngestPrompt(source, sourceName);
|
|
4248
|
-
return `WIKI_INGEST_PROMPT
|
|
4249
|
-
${prompt}
|
|
4250
|
-
|
|
4251
|
-
Produce WIKI_DIFF blocks following SCHEMA.md, then call kirograph_wiki_apply_diff with the diff string.`;
|
|
4252
|
-
}
|
|
4253
|
-
case "kirograph_wiki_apply_diff": {
|
|
4254
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
4255
|
-
const projectRoot = cg.getProjectRoot();
|
|
4256
|
-
const config = await loadConfig(projectRoot);
|
|
4257
|
-
if (!config.enableWiki) return "Wiki is not enabled. Set enableWiki: true in .kirograph/config.json";
|
|
4258
|
-
const { KiroGraphWiki } = await Promise.resolve().then(() => require("../wiki/index.js"));
|
|
4259
|
-
const db = cg.getDatabase();
|
|
4260
|
-
db.applyWikiSchema();
|
|
4261
|
-
const wiki = new KiroGraphWiki(db.getRawDb(), projectRoot + "/.kirograph", {
|
|
4262
|
-
autoResolveConflicts: config.wikiAutoResolveConflicts ?? false
|
|
4263
|
-
});
|
|
4264
|
-
wiki.initialize();
|
|
4265
|
-
const diff = args.diff ?? "";
|
|
4266
|
-
if (!diff) return "diff is required";
|
|
4267
|
-
const result = wiki.applyDiff(diff);
|
|
4268
|
-
const lines = ["\u2713 Wiki diff applied"];
|
|
4269
|
-
if (result.created.length) lines.push(` Created: ${result.created.join(", ")}`);
|
|
4270
|
-
if (result.updated.length) lines.push(` Updated: ${result.updated.join(", ")}`);
|
|
4271
|
-
if (result.conflictsResolved.length) lines.push(` Conflicts auto-resolved: ${result.conflictsResolved.join(", ")}`);
|
|
4272
|
-
if (result.conflictsPending.length) {
|
|
4273
|
-
lines.push(` \u26A1 Conflicts pending (${result.conflictsPending.length}):`);
|
|
4274
|
-
for (const c of result.conflictsPending) {
|
|
4275
|
-
lines.push(` ${c.page} \xA7${c.section}: "${c.existing}" vs "${c.incoming}" (source: ${c.source})`);
|
|
4276
|
-
}
|
|
4277
|
-
}
|
|
4278
|
-
return lines.join("\n");
|
|
4279
|
-
}
|
|
4280
|
-
case "kirograph_wiki_search": {
|
|
4281
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
4282
|
-
const projectRoot = cg.getProjectRoot();
|
|
4283
|
-
const config = await loadConfig(projectRoot);
|
|
4284
|
-
if (!config.enableWiki) return "Wiki is not enabled. Set enableWiki: true in .kirograph/config.json";
|
|
4285
|
-
const { KiroGraphWiki } = await Promise.resolve().then(() => require("../wiki/index.js"));
|
|
4286
|
-
const db = cg.getDatabase();
|
|
4287
|
-
db.applyWikiSchema();
|
|
4288
|
-
const wiki = new KiroGraphWiki(db.getRawDb(), projectRoot + "/.kirograph");
|
|
4289
|
-
wiki.initialize();
|
|
4290
|
-
const query = args.query ?? "";
|
|
4291
|
-
if (!query) return "query is required";
|
|
4292
|
-
const limit = Math.min(Number(args.limit ?? 5), 20);
|
|
4293
|
-
const results = wiki.search(query, limit);
|
|
4294
|
-
if (results.length === 0) return `No wiki pages found for "${query}".`;
|
|
4295
|
-
const lines = [`Wiki Search: ${query}`, ""];
|
|
4296
|
-
for (const { page, score } of results) {
|
|
4297
|
-
lines.push(`[${page.slug}] ${page.title} (score: ${score.toFixed(3)})`);
|
|
4298
|
-
const preview = page.content.split("\n").find((l) => l.trim() && !l.startsWith("#"));
|
|
4299
|
-
if (preview) lines.push(` ${preview.trim().slice(0, 120)}`);
|
|
4300
|
-
lines.push("");
|
|
4301
|
-
}
|
|
4302
|
-
return lines.join("\n").trim();
|
|
4303
|
-
}
|
|
4304
|
-
case "kirograph_wiki_page": {
|
|
4305
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
4306
|
-
const projectRoot = cg.getProjectRoot();
|
|
4307
|
-
const config = await loadConfig(projectRoot);
|
|
4308
|
-
if (!config.enableWiki) return "Wiki is not enabled. Set enableWiki: true in .kirograph/config.json";
|
|
4309
|
-
const { KiroGraphWiki } = await Promise.resolve().then(() => require("../wiki/index.js"));
|
|
4310
|
-
const db = cg.getDatabase();
|
|
4311
|
-
db.applyWikiSchema();
|
|
4312
|
-
const wiki = new KiroGraphWiki(db.getRawDb(), projectRoot + "/.kirograph");
|
|
4313
|
-
wiki.initialize();
|
|
4314
|
-
const slug = args.slug ?? "";
|
|
4315
|
-
if (!slug) return "slug is required";
|
|
4316
|
-
const page = wiki.getPage(slug);
|
|
4317
|
-
if (!page) return `Wiki page "${slug}" not found. Use kirograph_wiki_list to see available pages.`;
|
|
4318
|
-
return page.content;
|
|
4319
|
-
}
|
|
4320
|
-
case "kirograph_wiki_lint": {
|
|
4321
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
4322
|
-
const projectRoot = cg.getProjectRoot();
|
|
4323
|
-
const config = await loadConfig(projectRoot);
|
|
4324
|
-
if (!config.enableWiki) return "Wiki is not enabled. Set enableWiki: true in .kirograph/config.json";
|
|
4325
|
-
const { KiroGraphWiki } = await Promise.resolve().then(() => require("../wiki/index.js"));
|
|
4326
|
-
const db = cg.getDatabase();
|
|
4327
|
-
db.applyWikiSchema();
|
|
4328
|
-
const wiki = new KiroGraphWiki(db.getRawDb(), projectRoot + "/.kirograph");
|
|
4329
|
-
wiki.initialize();
|
|
4330
|
-
const issues = wiki.lint();
|
|
4331
|
-
if (issues.length === 0) return "\u2713 Wiki lint passed \u2014 no issues found.";
|
|
4332
|
-
const lines = [`Wiki Lint \u2014 ${issues.length} issue(s)`, ""];
|
|
4333
|
-
for (const issue of issues) {
|
|
4334
|
-
const icon = issue.kind === "contradiction" ? "\u26A1" : issue.kind === "orphan" ? "\u25CB" : issue.kind === "broken_link" ? "\u{1F517}" : "\u26A0";
|
|
4335
|
-
lines.push(`${icon} [${issue.kind}] ${issue.slug}`);
|
|
4336
|
-
lines.push(` ${issue.detail}`);
|
|
4337
|
-
lines.push("");
|
|
4338
|
-
}
|
|
4339
|
-
return lines.join("\n").trim();
|
|
4340
|
-
}
|
|
4341
|
-
case "kirograph_wiki_list": {
|
|
4342
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
4343
|
-
const projectRoot = cg.getProjectRoot();
|
|
4344
|
-
const config = await loadConfig(projectRoot);
|
|
4345
|
-
if (!config.enableWiki) return "Wiki is not enabled. Set enableWiki: true in .kirograph/config.json";
|
|
4346
|
-
const { KiroGraphWiki } = await Promise.resolve().then(() => require("../wiki/index.js"));
|
|
4347
|
-
const db = cg.getDatabase();
|
|
4348
|
-
db.applyWikiSchema();
|
|
4349
|
-
const wiki = new KiroGraphWiki(db.getRawDb(), projectRoot + "/.kirograph");
|
|
4350
|
-
wiki.initialize();
|
|
4351
|
-
const pages = wiki.listPages();
|
|
4352
|
-
if (pages.length === 0) return "Wiki is empty. Use kirograph_wiki_ingest to add pages.";
|
|
4353
|
-
const stats = wiki.getStats();
|
|
4354
|
-
const lines = [
|
|
4355
|
-
`Wiki \u2014 ${stats.pageCount} page(s), ${stats.totalSources} total sources`,
|
|
4356
|
-
""
|
|
4357
|
-
];
|
|
4358
|
-
for (const page of pages) {
|
|
4359
|
-
const date = new Date(page.updatedAt).toISOString().slice(0, 10);
|
|
4360
|
-
lines.push(` ${page.slug.padEnd(32)} ${page.title} (${page.sourceCount} src, ${date})`);
|
|
4361
|
-
}
|
|
4362
|
-
return lines.join("\n");
|
|
4363
|
-
}
|
|
4364
|
-
case "kirograph_wiki_synthesize": {
|
|
4365
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
4366
|
-
const projectRoot = args.projectPath ?? cg.getProjectRoot();
|
|
4367
|
-
const config = await loadConfig(projectRoot);
|
|
4368
|
-
if (!config.enableWiki) return "Wiki is not enabled. Set enableWiki: true in .kirograph/config.json";
|
|
4369
|
-
if (config.wikiSynthesisMode !== "local") return 'wikiSynthesisMode is not "local". This tool is for local-model synthesis only.';
|
|
4370
|
-
const { KiroGraphWiki } = await Promise.resolve().then(() => require("../wiki/index.js"));
|
|
4371
|
-
const db = cg.getDatabase();
|
|
4372
|
-
db.applyWikiSchema();
|
|
4373
|
-
const wiki = new KiroGraphWiki(db.getRawDb(), projectRoot + "/.kirograph", {
|
|
4374
|
-
autoResolveConflicts: config.wikiAutoResolveConflicts ?? false
|
|
4375
|
-
});
|
|
4376
|
-
wiki.initialize();
|
|
4377
|
-
const queueCount = wiki.getQueueCount();
|
|
4378
|
-
if (queueCount === 0) return "Wiki queue is empty \u2014 nothing to synthesize.";
|
|
4379
|
-
const result = await wiki.synthesize(config.wikiLocalModel, true);
|
|
4380
|
-
const synthLines = [`Wiki synthesis complete \u2014 processed ${result.processed} source(s).`];
|
|
4381
|
-
if (result.created.length) synthLines.push(`Created: ${result.created.join(", ")}`);
|
|
4382
|
-
if (result.updated.length) synthLines.push(`Updated: ${result.updated.join(", ")}`);
|
|
4383
|
-
if (result.errors.length) synthLines.push(`Errors: ${result.errors.join("; ")}`);
|
|
4384
|
-
return synthLines.join("\n");
|
|
4385
|
-
}
|
|
4386
|
-
case "kirograph_wiki_init": {
|
|
4387
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
4388
|
-
const projectRoot = args.projectPath ?? cg.getProjectRoot();
|
|
4389
|
-
const config = await loadConfig(projectRoot);
|
|
4390
|
-
if (!config.enableWiki) return "Wiki is not enabled. Set enableWiki: true in .kirograph/config.json";
|
|
4391
|
-
const { KiroGraphWiki } = await Promise.resolve().then(() => require("../wiki/index.js"));
|
|
4392
|
-
const db = cg.getDatabase();
|
|
4393
|
-
db.applyWikiSchema();
|
|
4394
|
-
const wiki = new KiroGraphWiki(db.getRawDb(), projectRoot + "/.kirograph", {
|
|
4395
|
-
autoResolveConflicts: config.wikiAutoResolveConflicts ?? false
|
|
4396
|
-
});
|
|
4397
|
-
wiki.initWiki();
|
|
4398
|
-
return "Wiki initialized. SCHEMA.md and MANIFEST.md created in .kirograph/wiki/.";
|
|
4399
|
-
}
|
|
4400
|
-
case "kirograph_wiki_reindex": {
|
|
4401
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
4402
|
-
const projectRoot = args.projectPath ?? cg.getProjectRoot();
|
|
4403
|
-
const config = await loadConfig(projectRoot);
|
|
4404
|
-
if (!config.enableWiki) return "Wiki is not enabled. Set enableWiki: true in .kirograph/config.json";
|
|
4405
|
-
const { KiroGraphWiki } = await Promise.resolve().then(() => require("../wiki/index.js"));
|
|
4406
|
-
const db = cg.getDatabase();
|
|
4407
|
-
db.applyWikiSchema();
|
|
4408
|
-
const wiki = new KiroGraphWiki(db.getRawDb(), projectRoot + "/.kirograph");
|
|
4409
|
-
wiki.initialize();
|
|
4410
|
-
const count = wiki.reindex();
|
|
4411
|
-
return `Reindexed ${count} wiki page(s) from .kirograph/wiki/*.md.`;
|
|
4412
|
-
}
|
|
4413
|
-
case "kirograph_wiki_status": {
|
|
4414
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
4415
|
-
const projectRoot = args.projectPath ?? cg.getProjectRoot();
|
|
4416
|
-
const config = await loadConfig(projectRoot);
|
|
4417
|
-
if (!config.enableWiki) return "Wiki is not enabled. Set enableWiki: true in .kirograph/config.json";
|
|
4418
|
-
const { KiroGraphWiki } = await Promise.resolve().then(() => require("../wiki/index.js"));
|
|
4419
|
-
const db = cg.getDatabase();
|
|
4420
|
-
db.applyWikiSchema();
|
|
4421
|
-
const wiki = new KiroGraphWiki(db.getRawDb(), projectRoot + "/.kirograph");
|
|
4422
|
-
wiki.initialize();
|
|
4423
|
-
const stats = wiki.getStats();
|
|
4424
|
-
const oldest = stats.oldestPage ? new Date(stats.oldestPage).toISOString().slice(0, 10) : "n/a";
|
|
4425
|
-
const newest = stats.newestPage ? new Date(stats.newestPage).toISOString().slice(0, 10) : "n/a";
|
|
4426
|
-
return [
|
|
4427
|
-
"Wiki Status:",
|
|
4428
|
-
` Pages: ${stats.pageCount}`,
|
|
4429
|
-
` Total sources: ${stats.totalSources}`,
|
|
4430
|
-
` Oldest page: ${oldest}`,
|
|
4431
|
-
` Newest page: ${newest}`,
|
|
4432
|
-
` Wiki dir: .kirograph/wiki/`
|
|
4433
|
-
].join("\n");
|
|
4434
|
-
}
|
|
4435
|
-
default:
|
|
4436
|
-
return `Unknown tool: ${toolName}`;
|
|
1659
|
+
}
|
|
1660
|
+
];
|
|
1661
|
+
const LIVE_SEARCH_TOOL_DEFINITION = {
|
|
1662
|
+
name: "kirograph_live_search",
|
|
1663
|
+
description: "Run a live AST pattern search against the indexed file list using @ast-grep/napi. Finds structural code patterns (SQL injection, eval, path traversal, etc.) without needing the codebase to be re-indexed. Only available when enablePatterns: true.",
|
|
1664
|
+
inputSchema: {
|
|
1665
|
+
type: "object",
|
|
1666
|
+
required: ["pattern", "language"],
|
|
1667
|
+
properties: {
|
|
1668
|
+
pattern: { type: "string", description: 'ast-grep inline pattern (e.g. "eval($X)")' },
|
|
1669
|
+
language: { type: "string", description: "Language to search: javascript, typescript, python, go, rust, java, etc." },
|
|
1670
|
+
limit: { type: "number", description: "Max results (default 20, max 100)", default: 20 },
|
|
1671
|
+
projectPath: { type: "string" }
|
|
4437
1672
|
}
|
|
4438
1673
|
}
|
|
4439
|
-
}
|
|
1674
|
+
};
|
|
4440
1675
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4441
1676
|
0 && (module.exports = {
|
|
4442
1677
|
KIROGRAPH_TOOL_NAMES,
|