diff --git a/README.md b/README.md index fa0f9b1..75790a9 100644 --- a/README.md +++ b/README.md @@ -287,31 +287,52 @@ Structured filters available: `framework`, `language`, `componentType`, `layer` !.codebase-context/memory.json ``` -## CLI Access (Vendor-Neutral) +## CLI Reference -You can manage team memory directly from the terminal without any AI agent: +All MCP tools are available as CLI commands — no AI agent required. Useful for scripting, debugging, and CI workflows. + +Set `CODEBASE_ROOT` to your project root, or run from the project directory. ```bash -# List all memories -npx codebase-context memory list +# Search the indexed codebase +npx codebase-context search --query "authentication middleware" +npx codebase-context search --query "auth" --intent edit --limit 5 -# Filter by category or type -npx codebase-context memory list --category conventions --type convention +# Project structure, frameworks, and dependencies +npx codebase-context metadata -# Search memories -npx codebase-context memory list --query "auth" +# Index state and progress +npx codebase-context status -# Add a memory -npx codebase-context memory add --type convention --category tooling --memory "Use pnpm, not npm" --reason "Workspace support and speed" +# Re-index the codebase +npx codebase-context reindex +npx codebase-context reindex --incremental --reason "added new service" -# Remove a memory -npx codebase-context memory remove +# Style guide rules +npx codebase-context style-guide +npx codebase-context style-guide --query "naming" --category patterns + +# Team patterns (DI, state, testing, etc.) +npx codebase-context patterns +npx codebase-context patterns --category testing -# JSON output for scripting -npx codebase-context memory list --json +# Symbol references +npx codebase-context refs --symbol "UserService" +npx codebase-context refs --symbol "handleLogin" --limit 20 + +# Circular dependency detection +npx codebase-context cycles +npx codebase-context cycles --scope src/features + +# Memory management +npx codebase-context memory list +npx codebase-context memory list --category conventions --type convention +npx codebase-context memory list --query "auth" --json +npx codebase-context memory add --type convention --category tooling --memory "Use pnpm, not npm" --reason "Workspace support and speed" +npx codebase-context memory remove ``` -Set `CODEBASE_ROOT` to point to your project, or run from the project directory. +All commands accept `--json` for raw JSON output suitable for piping and scripting. ## Tip: Ensuring your AI Agent recalls memory: diff --git a/docs/capabilities.md b/docs/capabilities.md index ed88b8f..9714562 100644 --- a/docs/capabilities.md +++ b/docs/capabilities.md @@ -2,6 +2,35 @@ Technical reference for what `codebase-context` ships today. For the user-facing overview, see [README.md](../README.md). +## CLI Reference + +All 10 MCP tools are exposed as CLI subcommands. Set `CODEBASE_ROOT` or run from the project directory. + +| Command | Flags | Maps to | +|---|---|---| +| `search --query ` | `--intent explore\|edit\|refactor\|migrate`, `--limit `, `--lang `, `--framework `, `--layer ` | `search_codebase` | +| `metadata` | — | `get_codebase_metadata` | +| `status` | — | `get_indexing_status` | +| `reindex` | `--incremental`, `--reason ` | `refresh_index` | +| `style-guide` | `--query `, `--category ` | `get_style_guide` | +| `patterns` | `--category all\|di\|state\|testing\|libraries` | `get_team_patterns` | +| `refs --symbol ` | `--limit ` | `get_symbol_references` | +| `cycles` | `--scope ` | `detect_circular_dependencies` | +| `memory list` | `--category`, `--type`, `--query`, `--json` | — | +| `memory add` | `--type`, `--category`, `--memory`, `--reason` | `remember` | +| `memory remove ` | — | — | + +All commands accept `--json` for raw JSON output. Errors go to stderr with exit code 1. + +```bash +# Quick examples +npx codebase-context status +npx codebase-context search --query "auth middleware" --intent edit +npx codebase-context refs --symbol "UserService" --limit 10 +npx codebase-context cycles --scope src/features +npx codebase-context reindex --incremental +``` + ## Tool Surface 10 MCP tools + 1 optional resource (`codebase://context`). **Migration:** `get_component_usage` was removed; use `get_symbol_references` for symbol usage evidence. diff --git a/package.json b/package.json index 4a3ce4c..409475b 100644 --- a/package.json +++ b/package.json @@ -128,7 +128,7 @@ "@typescript-eslint/typescript-estree": "^7.0.0", "fuse.js": "^7.0.0", "glob": "^10.3.10", - "hono": "4.11.7", + "hono": "^4.11.10", "ignore": "^5.3.1", "typescript": "^5.3.3", "uuid": "^9.0.1", @@ -142,7 +142,7 @@ "@types/uuid": "^9.0.8", "@typescript-eslint/eslint-plugin": "^8.51.0", "@typescript-eslint/parser": "^8.51.0", - "eslint": "^10.0.1", + "eslint": "^9.0.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-import": "^2.32.0", "globals": "^17.0.0", @@ -160,7 +160,8 @@ "sharp" ], "overrides": { - "@modelcontextprotocol/sdk>ajv": "8.18.0" + "@modelcontextprotocol/sdk>ajv": "8.18.0", + "minimatch": ">=10.2.1" } } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 80077f8..07ae007 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,6 +6,7 @@ settings: overrides: '@modelcontextprotocol/sdk>ajv': 8.18.0 + minimatch: '>=10.2.1' importers: @@ -30,8 +31,8 @@ importers: specifier: ^10.3.10 version: 10.5.0 hono: - specifier: 4.11.7 - version: 4.11.7 + specifier: ^4.11.10 + version: 4.12.1 ignore: specifier: ^5.3.1 version: 5.3.2 @@ -62,19 +63,19 @@ importers: version: 9.0.8 '@typescript-eslint/eslint-plugin': specifier: ^8.51.0 - version: 8.51.0(@typescript-eslint/parser@8.51.0(eslint@10.0.1)(typescript@5.9.3))(eslint@10.0.1)(typescript@5.9.3) + version: 8.51.0(@typescript-eslint/parser@8.51.0(eslint@9.39.3)(typescript@5.9.3))(eslint@9.39.3)(typescript@5.9.3) '@typescript-eslint/parser': specifier: ^8.51.0 - version: 8.51.0(eslint@10.0.1)(typescript@5.9.3) + version: 8.51.0(eslint@9.39.3)(typescript@5.9.3) eslint: - specifier: ^10.0.1 - version: 10.0.1 + specifier: ^9.0.0 + version: 9.39.3 eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@10.0.1) + version: 10.1.8(eslint@9.39.3) eslint-plugin-import: specifier: ^2.32.0 - version: 2.32.0(@typescript-eslint/parser@8.51.0(eslint@10.0.1)(typescript@5.9.3))(eslint@10.0.1) + version: 2.32.0(@typescript-eslint/parser@8.51.0(eslint@9.39.3)(typescript@5.9.3))(eslint@9.39.3) globals: specifier: ^17.0.0 version: 17.0.0 @@ -89,7 +90,7 @@ importers: version: 4.21.0 typescript-eslint: specifier: ^8.51.0 - version: 8.51.0(eslint@10.0.1)(typescript@5.9.3) + version: 8.51.0(eslint@9.39.3)(typescript@5.9.3) vitest: specifier: ^4.0.16 version: 4.0.16(@types/node@20.19.25)(tsx@4.21.0) @@ -265,29 +266,37 @@ packages: resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/config-array@0.23.2': - resolution: {integrity: sha512-YF+fE6LV4v5MGWRGj7G404/OZzGNepVF8fxk7jqmqo3lrza7a0uUcDnROGRBG1WFC1omYUS/Wp1f42i0M+3Q3A==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@eslint/config-array@0.21.1': + resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/config-helpers@0.5.2': - resolution: {integrity: sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/core@1.1.0': - resolution: {integrity: sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.3': + resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/js@9.39.2': resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/object-schema@3.0.2': - resolution: {integrity: sha512-HOy56KJt48Bx8KmJ+XGQNSUMT/6dZee/M54XyUyuvTvPXJmsERRvBchsUVx1UMe1WwIH49XLAczNC7V2INsuUw==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@eslint/js@9.39.3': + resolution: {integrity: sha512-1B1VkCq6FuUNlQvlBYb+1jDu/gV297TIs/OeiaSR9l1H27SVW55ONE1e1Vp16NqP683+xEGzxYtv4XCiDPaQiw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.6.0': - resolution: {integrity: sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@hono/node-server@1.19.9': resolution: {integrity: sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==} @@ -689,9 +698,6 @@ packages: '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - '@types/esrecurse@4.3.1': - resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} - '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -880,6 +886,9 @@ packages: resolution: {integrity: sha512-RvwlFxLRpO405PLGffx4N2PYLiF7FD86Q1hHl6J2XCWiq+tTCzpb9ngFw0apFDcXZBMpCzMuwAvA7hjyL1/73A==} hasBin: true + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + array-back@3.1.0: resolution: {integrity: sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==} engines: {node: '>=6'} @@ -931,9 +940,6 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - balanced-match@4.0.3: resolution: {integrity: sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==} engines: {node: 20 || >=22} @@ -946,12 +952,6 @@ packages: resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. - brace-expansion@1.1.12: - resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} - - brace-expansion@2.0.2: - resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} - brace-expansion@5.0.2: resolution: {integrity: sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==} engines: {node: 20 || >=22} @@ -976,6 +976,10 @@ packages: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} @@ -1011,9 +1015,6 @@ packages: resolution: {integrity: sha512-PqMLy5+YGwhMh1wS04mVG44oqDsgyLRSKJBdOo1bnYhMKBW65gZF1dRp2OZRhiTjgUHljy99qkO7bsctLaw35Q==} engines: {node: '>=12.20.0'} - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - content-disposition@1.0.1: resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} engines: {node: '>=18'} @@ -1207,9 +1208,9 @@ packages: '@typescript-eslint/parser': optional: true - eslint-scope@9.1.1: - resolution: {integrity: sha512-GaUN0sWim5qc8KVErfPBWmc31LEsOkrUJbvJZV+xuL3u2phMUK4HIvXlWAakfC8W4nzlK+chPEAkYOYb5ZScIw==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} @@ -1219,13 +1220,9 @@ packages: resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint-visitor-keys@5.0.1: - resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - - eslint@10.0.1: - resolution: {integrity: sha512-20MV9SUdeN6Jd84xESsKhRly+/vxI+hwvpBMA93s+9dAcjdCuCojn4IqUGS3lvVaqjVYGYHSRMCpeFtF2rQYxQ==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} + eslint@9.39.3: + resolution: {integrity: sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: jiti: '*' @@ -1233,9 +1230,9 @@ packages: jiti: optional: true - espree@11.1.1: - resolution: {integrity: sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} esquery@1.7.0: resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} @@ -1429,6 +1426,10 @@ packages: resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} engines: {node: '>=10.0'} + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + globals@17.0.0: resolution: {integrity: sha512-gv5BeD2EssA793rlFWVPMMCqefTlpusw6/2TbAVMy0FzcG8wKJn4O+NqJ4+XWmmwrayJgw5TzrmWjFgmz1XPqw==} engines: {node: '>=18'} @@ -1475,8 +1476,8 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} - hono@4.11.7: - resolution: {integrity: sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw==} + hono@4.12.1: + resolution: {integrity: sha512-hi9afu8g0lfJVLolxElAZGANCTTl6bewIdsRNhaywfP9K8BPf++F2z6OLrYGIinUwpRKzbZHMhPwvc0ZEpAwGw==} engines: {node: '>=16.9.0'} http-errors@2.0.1: @@ -1498,6 +1499,10 @@ packages: resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} engines: {node: '>= 4'} + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -1636,6 +1641,10 @@ packages: jose@6.1.3: resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==} + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + json-bignum@0.0.3: resolution: {integrity: sha512-2WHyXj3OfHSgNyuzDbSxI1w2jgw5gkWSWhS7Qg4bWXx1nLk3jnbwfUeS0PSba3IzpTUWdHxBieELUzXRjQB2zg==} engines: {node: '>=0.8'} @@ -1676,6 +1685,9 @@ packages: lodash.camelcase@4.3.0: resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + long@5.3.2: resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} @@ -1729,13 +1741,6 @@ packages: resolution: {integrity: sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==} engines: {node: 18 || 20 || >=22} - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} - engines: {node: '>=16 || 14 >=14.17'} - minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -1858,6 +1863,10 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -1961,6 +1970,10 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} @@ -2135,6 +2148,10 @@ packages: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -2514,41 +2531,57 @@ snapshots: '@esbuild/win32-x64@0.27.2': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@10.0.1)': + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.3)': dependencies: - eslint: 10.0.1 + eslint: 9.39.3 eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} - '@eslint/config-array@0.23.2': + '@eslint/config-array@0.21.1': dependencies: - '@eslint/object-schema': 3.0.2 + '@eslint/object-schema': 2.1.7 debug: 4.4.3 minimatch: 10.2.2 transitivePeerDependencies: - supports-color - '@eslint/config-helpers@0.5.2': + '@eslint/config-helpers@0.4.2': dependencies: - '@eslint/core': 1.1.0 + '@eslint/core': 0.17.0 - '@eslint/core@1.1.0': + '@eslint/core@0.17.0': dependencies: '@types/json-schema': 7.0.15 + '@eslint/eslintrc@3.3.3': + dependencies: + ajv: 6.14.0 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 10.2.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + '@eslint/js@9.39.2': {} - '@eslint/object-schema@3.0.2': {} + '@eslint/js@9.39.3': {} + + '@eslint/object-schema@2.1.7': {} - '@eslint/plugin-kit@0.6.0': + '@eslint/plugin-kit@0.4.1': dependencies: - '@eslint/core': 1.1.0 + '@eslint/core': 0.17.0 levn: 0.4.1 - '@hono/node-server@1.19.9(hono@4.11.7)': + '@hono/node-server@1.19.9(hono@4.12.1)': dependencies: - hono: 4.11.7 + hono: 4.12.1 '@huggingface/jinja@0.5.5': {} @@ -2713,7 +2746,7 @@ snapshots: '@modelcontextprotocol/sdk@1.26.0(zod@4.3.4)': dependencies: - '@hono/node-server': 1.19.9(hono@4.11.7) + '@hono/node-server': 1.19.9(hono@4.12.1) ajv: 8.18.0 ajv-formats: 3.0.1(ajv@8.18.0) content-type: 1.0.5 @@ -2723,7 +2756,7 @@ snapshots: eventsource-parser: 3.0.6 express: 5.2.1 express-rate-limit: 8.2.1(express@5.2.1) - hono: 4.11.7 + hono: 4.12.1 jose: 6.1.3 json-schema-typed: 8.0.2 pkce-challenge: 5.0.1 @@ -2856,8 +2889,6 @@ snapshots: '@types/deep-eql@4.0.2': {} - '@types/esrecurse@4.3.1': {} - '@types/estree@1.0.8': {} '@types/glob@8.1.0': @@ -2886,15 +2917,15 @@ snapshots: '@types/uuid@9.0.8': {} - '@typescript-eslint/eslint-plugin@8.51.0(@typescript-eslint/parser@8.51.0(eslint@10.0.1)(typescript@5.9.3))(eslint@10.0.1)(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.51.0(@typescript-eslint/parser@8.51.0(eslint@9.39.3)(typescript@5.9.3))(eslint@9.39.3)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.51.0(eslint@10.0.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.51.0(eslint@9.39.3)(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.51.0 - '@typescript-eslint/type-utils': 8.51.0(eslint@10.0.1)(typescript@5.9.3) - '@typescript-eslint/utils': 8.51.0(eslint@10.0.1)(typescript@5.9.3) + '@typescript-eslint/type-utils': 8.51.0(eslint@9.39.3)(typescript@5.9.3) + '@typescript-eslint/utils': 8.51.0(eslint@9.39.3)(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.51.0 - eslint: 10.0.1 + eslint: 9.39.3 ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.3.0(typescript@5.9.3) @@ -2902,14 +2933,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.51.0(eslint@10.0.1)(typescript@5.9.3)': + '@typescript-eslint/parser@8.51.0(eslint@9.39.3)(typescript@5.9.3)': dependencies: '@typescript-eslint/scope-manager': 8.51.0 '@typescript-eslint/types': 8.51.0 '@typescript-eslint/typescript-estree': 8.51.0(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.51.0 debug: 4.4.3 - eslint: 10.0.1 + eslint: 9.39.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -2932,13 +2963,13 @@ snapshots: dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.51.0(eslint@10.0.1)(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.51.0(eslint@9.39.3)(typescript@5.9.3)': dependencies: '@typescript-eslint/types': 8.51.0 '@typescript-eslint/typescript-estree': 8.51.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.51.0(eslint@10.0.1)(typescript@5.9.3) + '@typescript-eslint/utils': 8.51.0(eslint@9.39.3)(typescript@5.9.3) debug: 4.4.3 - eslint: 10.0.1 + eslint: 9.39.3 ts-api-utils: 2.3.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: @@ -2955,7 +2986,7 @@ snapshots: debug: 4.4.3 globby: 11.1.0 is-glob: 4.0.3 - minimatch: 9.0.5 + minimatch: 10.2.2 semver: 7.7.3 ts-api-utils: 1.4.3(typescript@5.9.3) optionalDependencies: @@ -2970,7 +3001,7 @@ snapshots: '@typescript-eslint/types': 8.51.0 '@typescript-eslint/visitor-keys': 8.51.0 debug: 4.4.3 - minimatch: 9.0.5 + minimatch: 10.2.2 semver: 7.7.3 tinyglobby: 0.2.15 ts-api-utils: 2.3.0(typescript@5.9.3) @@ -2978,13 +3009,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.51.0(eslint@10.0.1)(typescript@5.9.3)': + '@typescript-eslint/utils@8.51.0(eslint@9.39.3)(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.1) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.3) '@typescript-eslint/scope-manager': 8.51.0 '@typescript-eslint/types': 8.51.0 '@typescript-eslint/typescript-estree': 8.51.0(typescript@5.9.3) - eslint: 10.0.1 + eslint: 9.39.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -3097,6 +3128,8 @@ snapshots: json-bignum: 0.0.3 tslib: 2.8.1 + argparse@2.0.1: {} + array-back@3.1.0: {} array-back@6.2.2: {} @@ -3163,8 +3196,6 @@ snapshots: dependencies: possible-typed-array-names: 1.1.0 - balanced-match@1.0.2: {} - balanced-match@4.0.3: {} body-parser@2.2.2: @@ -3183,15 +3214,6 @@ snapshots: boolean@3.2.0: {} - brace-expansion@1.1.12: - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - - brace-expansion@2.0.2: - dependencies: - balanced-match: 1.0.2 - brace-expansion@5.0.2: dependencies: balanced-match: 4.0.3 @@ -3219,6 +3241,8 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 + callsites@3.1.0: {} + chai@6.2.2: {} chalk-template@0.4.0: @@ -3256,8 +3280,6 @@ snapshots: table-layout: 4.1.1 typical: 7.3.0 - concat-map@0.0.1: {} - content-disposition@1.0.1: {} content-type@1.0.5: {} @@ -3468,9 +3490,9 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-prettier@10.1.8(eslint@10.0.1): + eslint-config-prettier@10.1.8(eslint@9.39.3): dependencies: - eslint: 10.0.1 + eslint: 9.39.3 eslint-import-resolver-node@0.3.9: dependencies: @@ -3480,17 +3502,17 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.51.0(eslint@10.0.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@10.0.1): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.51.0(eslint@9.39.3)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.3): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.51.0(eslint@10.0.1)(typescript@5.9.3) - eslint: 10.0.1 + '@typescript-eslint/parser': 8.51.0(eslint@9.39.3)(typescript@5.9.3) + eslint: 9.39.3 eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.51.0(eslint@10.0.1)(typescript@5.9.3))(eslint@10.0.1): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.51.0(eslint@9.39.3)(typescript@5.9.3))(eslint@9.39.3): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -3499,13 +3521,13 @@ snapshots: array.prototype.flatmap: 1.3.3 debug: 3.2.7 doctrine: 2.1.0 - eslint: 10.0.1 + eslint: 9.39.3 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.51.0(eslint@10.0.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@10.0.1) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.51.0(eslint@9.39.3)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.3) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 - minimatch: 3.1.2 + minimatch: 10.2.2 object.fromentries: 2.0.8 object.groupby: 1.0.3 object.values: 1.2.1 @@ -3513,16 +3535,14 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.51.0(eslint@10.0.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.51.0(eslint@9.39.3)(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-scope@9.1.1: + eslint-scope@8.4.0: dependencies: - '@types/esrecurse': 4.3.1 - '@types/estree': 1.0.8 esrecurse: 4.3.0 estraverse: 5.3.0 @@ -3530,27 +3550,28 @@ snapshots: eslint-visitor-keys@4.2.1: {} - eslint-visitor-keys@5.0.1: {} - - eslint@10.0.1: + eslint@9.39.3: dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.1) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.3) '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.23.2 - '@eslint/config-helpers': 0.5.2 - '@eslint/core': 1.1.0 - '@eslint/plugin-kit': 0.6.0 + '@eslint/config-array': 0.21.1 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.3 + '@eslint/js': 9.39.3 + '@eslint/plugin-kit': 0.4.1 '@humanfs/node': 0.16.7 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 '@types/estree': 1.0.8 ajv: 6.14.0 + chalk: 4.1.2 cross-spawn: 7.0.6 debug: 4.4.3 escape-string-regexp: 4.0.0 - eslint-scope: 9.1.1 - eslint-visitor-keys: 5.0.1 - espree: 11.1.1 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 esquery: 1.7.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 @@ -3561,17 +3582,18 @@ snapshots: imurmurhash: 0.1.4 is-glob: 4.0.3 json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 minimatch: 10.2.2 natural-compare: 1.4.0 optionator: 0.9.4 transitivePeerDependencies: - supports-color - espree@11.1.1: + espree@10.4.0: dependencies: acorn: 8.16.0 acorn-jsx: 5.3.2(acorn@8.16.0) - eslint-visitor-keys: 5.0.1 + eslint-visitor-keys: 4.2.1 esquery@1.7.0: dependencies: @@ -3790,7 +3812,7 @@ snapshots: dependencies: foreground-child: 3.3.1 jackspeak: 3.4.3 - minimatch: 9.0.5 + minimatch: 10.2.2 minipass: 7.1.2 package-json-from-dist: 1.0.1 path-scurry: 1.11.1 @@ -3804,6 +3826,8 @@ snapshots: semver: 7.7.3 serialize-error: 7.0.1 + globals@14.0.0: {} + globals@17.0.0: {} globalthis@1.0.4: @@ -3846,7 +3870,7 @@ snapshots: dependencies: function-bind: 1.1.2 - hono@4.11.7: {} + hono@4.12.1: {} http-errors@2.0.1: dependencies: @@ -3868,6 +3892,11 @@ snapshots: ignore@7.0.5: {} + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + imurmurhash@0.1.4: {} inherits@2.0.4: {} @@ -4006,6 +4035,10 @@ snapshots: jose@6.1.3: {} + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + json-bignum@0.0.3: {} json-buffer@3.0.1: {} @@ -4039,6 +4072,8 @@ snapshots: lodash.camelcase@4.3.0: {} + lodash.merge@4.6.2: {} + long@5.3.2: {} lru-cache@10.4.3: {} @@ -4080,14 +4115,6 @@ snapshots: dependencies: brace-expansion: 5.0.2 - minimatch@3.1.2: - dependencies: - brace-expansion: 1.1.12 - - minimatch@9.0.5: - dependencies: - brace-expansion: 2.0.2 - minimist@1.2.8: {} minipass@7.1.2: {} @@ -4213,6 +4240,10 @@ snapshots: package-json-from-dist@1.0.1: {} + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + parseurl@1.3.3: {} path-exists@4.0.0: {} @@ -4313,6 +4344,8 @@ snapshots: require-from-string@2.0.2: {} + resolve-from@4.0.0: {} + resolve-pkg-maps@1.0.0: {} resolve@1.22.11: @@ -4585,6 +4618,8 @@ snapshots: strip-bom@3.0.0: {} + strip-json-comments@3.1.1: {} + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -4694,13 +4729,13 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript-eslint@8.51.0(eslint@10.0.1)(typescript@5.9.3): + typescript-eslint@8.51.0(eslint@9.39.3)(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.51.0(@typescript-eslint/parser@8.51.0(eslint@10.0.1)(typescript@5.9.3))(eslint@10.0.1)(typescript@5.9.3) - '@typescript-eslint/parser': 8.51.0(eslint@10.0.1)(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.51.0(@typescript-eslint/parser@8.51.0(eslint@9.39.3)(typescript@5.9.3))(eslint@9.39.3)(typescript@5.9.3) + '@typescript-eslint/parser': 8.51.0(eslint@9.39.3)(typescript@5.9.3) '@typescript-eslint/typescript-estree': 8.51.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.51.0(eslint@10.0.1)(typescript@5.9.3) - eslint: 10.0.1 + '@typescript-eslint/utils': 8.51.0(eslint@9.39.3)(typescript@5.9.3) + eslint: 9.39.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color diff --git a/src/cli.ts b/src/cli.ts index 91b7268..d0cb2cd 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,11 +1,19 @@ /** * CLI subcommands for codebase-context. * Memory list/add/remove — vendor-neutral access without any AI agent. + * search/metadata/status/reindex/style-guide/patterns/refs/cycles — all MCP tools. */ import path from 'path'; +import { promises as fs } from 'fs'; import type { Memory, MemoryCategory, MemoryType } from './types/index.js'; -import { CODEBASE_CONTEXT_DIRNAME, MEMORY_FILENAME } from './constants/codebase-context.js'; +import { + CODEBASE_CONTEXT_DIRNAME, + MEMORY_FILENAME, + INTELLIGENCE_FILENAME, + KEYWORD_INDEX_FILENAME, + VECTOR_DB_DIRNAME +} from './constants/codebase-context.js'; import { appendMemoryFile, readMemoriesFile, @@ -13,6 +21,270 @@ import { filterMemories, withConfidence } from './memory/store.js'; +import { CodebaseIndexer } from './core/indexer.js'; +import { dispatchTool } from './tools/index.js'; +import type { ToolContext } from './tools/index.js'; +import type { IndexState } from './tools/types.js'; +import { analyzerRegistry } from './core/analyzer-registry.js'; +import { AngularAnalyzer } from './analyzers/angular/index.js'; +import { GenericAnalyzer } from './analyzers/generic/index.js'; + +analyzerRegistry.register(new AngularAnalyzer()); +analyzerRegistry.register(new GenericAnalyzer()); + +const _CLI_COMMANDS = [ + 'memory', + 'search', + 'metadata', + 'status', + 'reindex', + 'style-guide', + 'patterns', + 'refs', + 'cycles' +] as const; + +type CliCommand = (typeof _CLI_COMMANDS)[number]; + +function printUsage(): void { + console.log('codebase-context [options]'); + console.log(''); + console.log('Commands:'); + console.log(' memory Memory CRUD'); + console.log(' search --query Search the indexed codebase'); + console.log(' [--intent explore|edit|refactor|migrate]'); + console.log(' [--limit ] [--lang ] [--framework ] [--layer ]'); + console.log(' metadata Project structure, frameworks, deps'); + console.log(' status Index state and progress'); + console.log(' reindex [--incremental] [--reason ] Re-index the codebase'); + console.log(' style-guide [--query ] [--category ] Style guide rules'); + console.log(' patterns [--category all|di|state|testing|libraries] Team patterns'); + console.log(' refs --symbol [--limit ] Symbol references'); + console.log(' cycles [--scope ] Circular dependency detection'); + console.log(''); + console.log('Global flags:'); + console.log(' --json Output raw JSON (default: human-readable)'); + console.log(' --help Show this help'); + console.log(''); + console.log('Environment:'); + console.log(' CODEBASE_ROOT Project root path (default: cwd)'); +} + +async function initToolContext(): Promise { + const rootPath = path.resolve(process.env.CODEBASE_ROOT || process.cwd()); + + const paths = { + baseDir: path.join(rootPath, CODEBASE_CONTEXT_DIRNAME), + memory: path.join(rootPath, CODEBASE_CONTEXT_DIRNAME, MEMORY_FILENAME), + intelligence: path.join(rootPath, CODEBASE_CONTEXT_DIRNAME, INTELLIGENCE_FILENAME), + keywordIndex: path.join(rootPath, CODEBASE_CONTEXT_DIRNAME, KEYWORD_INDEX_FILENAME), + vectorDb: path.join(rootPath, CODEBASE_CONTEXT_DIRNAME, VECTOR_DB_DIRNAME) + }; + + // Check if index exists to determine initial status + let indexExists = false; + try { + await fs.access(paths.keywordIndex); + indexExists = true; + } catch { + // no index on disk + } + + const indexState: IndexState = { + status: indexExists ? 'ready' : 'idle' + }; + + const performIndexing = async (incrementalOnly?: boolean): Promise => { + indexState.status = 'indexing'; + const mode = incrementalOnly ? 'incremental' : 'full'; + console.error(`Indexing (${mode}): ${rootPath}`); + + try { + let lastLoggedProgress = { phase: '', percentage: -1 }; + const indexer = new CodebaseIndexer({ + rootPath, + incrementalOnly, + onProgress: (progress) => { + const shouldLog = + progress.phase !== lastLoggedProgress.phase || + (progress.percentage % 10 === 0 && + progress.percentage !== lastLoggedProgress.percentage); + if (shouldLog) { + console.error(`[${progress.phase}] ${progress.percentage}%`); + lastLoggedProgress = { phase: progress.phase, percentage: progress.percentage }; + } + } + }); + + indexState.indexer = indexer; + const stats = await indexer.index(); + indexState.status = 'ready'; + indexState.lastIndexed = new Date(); + indexState.stats = stats; + + console.error( + `Complete: ${stats.indexedFiles} files, ${stats.totalChunks} chunks in ${( + stats.duration / 1000 + ).toFixed(2)}s` + ); + } catch (error) { + indexState.status = 'error'; + indexState.error = error instanceof Error ? error.message : String(error); + console.error('Indexing failed:', indexState.error); + } + }; + + return { indexState, paths, rootPath, performIndexing }; +} + +function extractText(result: { content?: Array<{ type: string; text: string }> }): string { + return result.content?.[0]?.text ?? ''; +} + +function formatJson(json: string, useJson: boolean): void { + if (useJson) { + console.log(json); + return; + } + // Pretty-print already-formatted JSON as-is (it's already readable) + console.log(json); +} + +export async function handleCliCommand(argv: string[]): Promise { + const command = argv[0] as CliCommand | '--help' | undefined; + + if (!command || command === '--help') { + printUsage(); + return; + } + + if (command === 'memory') { + return handleMemoryCli(argv.slice(1)); + } + + const useJson = argv.includes('--json'); + + // Parse flags into a map + const flags: Record = {}; + for (let i = 1; i < argv.length; i++) { + const arg = argv[i]; + if (arg === '--json') continue; + if (arg.startsWith('--')) { + const key = arg.slice(2); + const next = argv[i + 1]; + if (next && !next.startsWith('--')) { + flags[key] = next; + i++; + } else { + flags[key] = true; + } + } + } + + const ctx = await initToolContext(); + + let toolName: string; + let toolArgs: Record = {}; + + switch (command) { + case 'search': { + if (!flags['query']) { + console.error('Error: --query is required'); + console.error('Usage: codebase-context search --query [--intent ] [--limit ]'); + process.exit(1); + } + toolName = 'search_codebase'; + toolArgs = { + query: flags['query'], + ...(flags['intent'] ? { intent: flags['intent'] } : {}), + ...(flags['limit'] ? { limit: Number(flags['limit']) } : {}), + ...(flags['lang'] || flags['framework'] || flags['layer'] + ? { + filters: { + ...(flags['lang'] ? { language: flags['lang'] } : {}), + ...(flags['framework'] ? { framework: flags['framework'] } : {}), + ...(flags['layer'] ? { layer: flags['layer'] } : {}) + } + } + : {}) + }; + break; + } + case 'metadata': { + toolName = 'get_codebase_metadata'; + break; + } + case 'status': { + toolName = 'get_indexing_status'; + break; + } + case 'reindex': { + toolName = 'refresh_index'; + toolArgs = { + ...(flags['incremental'] ? { incrementalOnly: true } : {}), + ...(flags['reason'] ? { reason: flags['reason'] } : {}) + }; + // For CLI, reindex must be awaited (fire-and-forget won't work in a process that exits) + await ctx.performIndexing(Boolean(flags['incremental'])); + const statusResult = await dispatchTool('get_indexing_status', {}, ctx); + formatJson(extractText(statusResult), useJson); + return; + } + case 'style-guide': { + toolName = 'get_style_guide'; + toolArgs = { + ...(flags['query'] ? { query: flags['query'] } : {}), + ...(flags['category'] ? { category: flags['category'] } : {}) + }; + break; + } + case 'patterns': { + toolName = 'get_team_patterns'; + toolArgs = { + ...(flags['category'] ? { category: flags['category'] } : {}) + }; + break; + } + case 'refs': { + if (!flags['symbol']) { + console.error('Error: --symbol is required'); + console.error('Usage: codebase-context refs --symbol [--limit ]'); + process.exit(1); + } + toolName = 'get_symbol_references'; + toolArgs = { + symbol: flags['symbol'], + ...(flags['limit'] ? { limit: Number(flags['limit']) } : {}) + }; + break; + } + case 'cycles': { + toolName = 'detect_circular_dependencies'; + toolArgs = { + ...(flags['scope'] ? { scope: flags['scope'] } : {}) + }; + break; + } + default: { + console.error(`Unknown command: ${command}`); + console.error(''); + printUsage(); + process.exit(1); + } + } + + try { + const result = await dispatchTool(toolName, toolArgs, ctx); + if (result.isError) { + console.error(extractText(result)); + process.exit(1); + } + formatJson(extractText(result), useJson); + } catch (error) { + console.error('Error:', error instanceof Error ? error.message : String(error)); + process.exit(1); + } +} export async function handleMemoryCli(args: string[]): Promise { // Resolve project root: use CODEBASE_ROOT env or cwd (argv[2] is "memory", not a path) diff --git a/src/index.ts b/src/index.ts index 64e3947..aee4844 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,7 +10,6 @@ import { promises as fs } from 'fs'; import path from 'path'; -import { glob } from 'glob'; import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { @@ -18,23 +17,13 @@ import { ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, - Tool, Resource } from '@modelcontextprotocol/sdk/types.js'; import { CodebaseIndexer } from './core/indexer.js'; -import type { - IndexingStats, - SearchResult, - RelationshipData, - Memory, - MemoryCategory, - MemoryType -} from './types/index.js'; -import { CodebaseSearcher } from './core/search.js'; +import type { IndexingStats } from './types/index.js'; import { analyzerRegistry } from './core/analyzer-registry.js'; import { AngularAnalyzer } from './analyzers/angular/index.js'; import { GenericAnalyzer } from './analyzers/generic/index.js'; -import { InternalFileGraph } from './utils/usage-tracker.js'; import { IndexCorruptedError } from './errors/index.js'; import { CODEBASE_CONTEXT_DIRNAME, @@ -43,25 +32,14 @@ import { KEYWORD_INDEX_FILENAME, VECTOR_DB_DIRNAME } from './constants/codebase-context.js'; -import { - appendMemoryFile, - readMemoriesFile, - filterMemories, - applyUnfilteredLimit, - withConfidence -} from './memory/store.js'; -import { handleMemoryCli } from './cli.js'; +import { appendMemoryFile } from './memory/store.js'; +import { handleCliCommand } from './cli.js'; import { parseGitLogLineToMemory } from './memory/git-memory.js'; -import { buildEvidenceLock } from './preflight/evidence-lock.js'; -import { shouldIncludePatternConflictCategory } from './preflight/query-scope.js'; import { isComplementaryPatternCategory, - isComplementaryPatternConflict, shouldSkipLegacyTestingFrameworkCategory } from './patterns/semantics.js'; import { CONTEXT_RESOURCE_URI, isContextResourceUri } from './resources/uri.js'; -import { assessSearchQuality } from './core/search-quality.js'; -import { findSymbolReferences } from './core/symbol-references.js'; import { readIndexMeta, validateIndexArtifacts } from './core/index-meta.js'; import { TOOLS, dispatchTool, type ToolContext } from './tools/index.js'; @@ -756,10 +734,22 @@ const isDirectRun = process.argv[1]?.replace(/\\/g, '/').endsWith('index.js') || process.argv[1]?.replace(/\\/g, '/').endsWith('index.ts'); +const CLI_SUBCOMMANDS = [ + 'memory', + 'search', + 'metadata', + 'status', + 'reindex', + 'style-guide', + 'patterns', + 'refs', + 'cycles' +]; + if (isDirectRun) { - // CLI subcommand: memory list/add/remove - if (process.argv[2] === 'memory') { - handleMemoryCli(process.argv.slice(3)).catch((error) => { + const subcommand = process.argv[2]; + if (CLI_SUBCOMMANDS.includes(subcommand) || subcommand === '--help') { + handleCliCommand(process.argv.slice(2)).catch((error) => { console.error('Error:', error instanceof Error ? error.message : String(error)); process.exit(1); }); diff --git a/src/preflight/evidence-lock.ts b/src/preflight/evidence-lock.ts index 167ef7c..f5eddcf 100644 --- a/src/preflight/evidence-lock.ts +++ b/src/preflight/evidence-lock.ts @@ -165,7 +165,7 @@ export function buildEvidenceLock(input: BuildEvidenceLockInput): EvidenceLock { stressTriggers.push('Insufficient evidence: most evidence sources are empty'); } - // Trigger: low caller coverage + // Trigger: low caller coverage if ( input.impactCoverage && input.impactCoverage.total > 3 && @@ -214,9 +214,7 @@ export function buildEvidenceLock(input: BuildEvidenceLockInput): EvidenceLock { if (!readyToEdit) { // Code evidence weak/missing if (codeStrength === 'weak' || codeStrength === 'missing') { - whatWouldHelp.push( - 'Search with a more specific query targeting the implementation files' - ); + whatWouldHelp.push('Search with a more specific query targeting the implementation files'); } // Pattern evidence missing diff --git a/src/tools/search-codebase.ts b/src/tools/search-codebase.ts index 363aa59..1fb5550 100644 --- a/src/tools/search-codebase.ts +++ b/src/tools/search-codebase.ts @@ -9,7 +9,6 @@ import type { SearchResult } from '../types/index.js'; import { buildEvidenceLock } from '../preflight/evidence-lock.js'; import { shouldIncludePatternConflictCategory } from '../preflight/query-scope.js'; import { - isComplementaryPatternCategory, isComplementaryPatternConflict, shouldSkipLegacyTestingFrameworkCategory } from '../patterns/semantics.js'; @@ -176,8 +175,9 @@ export async function handle( text: JSON.stringify( { status: 'error', - message: `Auto-heal retry failed: ${retryError instanceof Error ? retryError.message : String(retryError) - }` + message: `Auto-heal retry failed: ${ + retryError instanceof Error ? retryError.message : String(retryError) + }` }, null, 2 @@ -313,7 +313,8 @@ export async function handle( function buildRelationshipHints(result: SearchResult): RelationshipHints { const rPath = result.filePath; // Graph keys are relative paths with forward slashes; normalize for comparison - const rPathNorm = path.relative(ctx.rootPath, rPath).replace(/\\/g, '/') || rPath.replace(/\\/g, '/'); + const rPathNorm = + path.relative(ctx.rootPath, rPath).replace(/\\/g, '/') || rPath.replace(/\\/g, '/'); // importedBy: files that import this result (reverse lookup), collect with counts const importedByMap = new Map(); @@ -395,13 +396,6 @@ export async function handle( const resultPaths = results.map((r) => r.filePath); const impactCandidates = computeImpactCandidates(resultPaths); - let riskLevel: 'low' | 'medium' | 'high' = 'low'; - if (impactCandidates.length > 10) { - riskLevel = 'high'; - } else if (impactCandidates.length > 3) { - riskLevel = 'medium'; - } - // Use existing pattern intelligence for evidenceLock scoring, but keep the output payload lite. const preferredPatternsForEvidence: Array<{ pattern: string; example?: string }> = []; const patterns = intelligence.patterns || {}; @@ -417,6 +411,13 @@ export async function handle( } } + let riskLevel: 'low' | 'medium' | 'high' = 'low'; + if (impactCandidates.length > 10) { + riskLevel = 'high'; + } else if (impactCandidates.length > 3) { + riskLevel = 'medium'; + } + editPreflight = { mode: 'lite', riskLevel, @@ -498,7 +499,8 @@ export async function handle( callersTotal > 0 ? { covered: callersCovered, total: callersTotal } : undefined; // --- Risk level (based on circular deps + impact breadth) --- - let riskLevel: 'low' | 'medium' | 'high' = 'low'; + //TODO: Review this risk level calculation + let _riskLevel: 'low' | 'medium' | 'high' = 'low'; let cycleCount = 0; const graphDataSource = relationships?.graph || intelligence?.internalFileGraph; if (graphDataSource) { @@ -521,9 +523,9 @@ export async function handle( } } if (cycleCount > 0 || impactCandidates.length > 10) { - riskLevel = 'high'; + _riskLevel = 'high'; } else if (impactCandidates.length > 3) { - riskLevel = 'medium'; + _riskLevel = 'medium'; } // --- Golden files (exemplar code) --- @@ -533,7 +535,8 @@ export async function handle( })); // --- Confidence (index freshness) --- - const confidence = computeIndexConfidence(); + // TODO: Review this confidence calculation + //const confidence = computeIndexConfidence(); // --- Failure memories (1.5x relevance boost) --- const failureWarnings = relatedMemories @@ -617,8 +620,12 @@ export async function handle( } // Add patterns (do/avoid, capped at 3 each, with adoption %) - const doPatterns = preferredPatternsForOutput.slice(0, 3).map((p) => `${p.pattern} — ${p.adoption ? ` ${p.adoption}% adoption` : ''}`); - const avoidPatterns = avoidPatternsForOutput.slice(0, 3).map((p) => `${p.pattern} — ${p.adoption ? ` ${p.adoption}% adoption` : ''} (declining)`); + const doPatterns = preferredPatternsForOutput + .slice(0, 3) + .map((p) => `${p.pattern} — ${p.adoption ? ` ${p.adoption}% adoption` : ''}`); + const avoidPatterns = avoidPatternsForOutput + .slice(0, 3) + .map((p) => `${p.pattern} — ${p.adoption ? ` ${p.adoption}% adoption` : ''} (declining)`); if (doPatterns.length > 0 || avoidPatterns.length > 0) { decisionCard.patterns = { ...(doPatterns.length > 0 && { do: doPatterns }), @@ -717,8 +724,8 @@ export async function handle( confidence: searchQuality.confidence, ...(searchQuality.status === 'low_confidence' && searchQuality.nextSteps?.[0] && { - hint: searchQuality.nextSteps[0] - }) + hint: searchQuality.nextSteps[0] + }) }, ...(preflightPayload && { preflight: preflightPayload }), results: results.map((r) => { @@ -734,7 +741,9 @@ export async function handle( ...(r.componentType && r.layer && { type: `${r.componentType}:${r.layer}` }), ...(r.trend && r.trend !== 'Stable' && { trend: r.trend }), ...(r.patternWarning && { patternWarning: r.patternWarning }), - ...(relationshipsAndHints.relationships && { relationships: relationshipsAndHints.relationships }), + ...(relationshipsAndHints.relationships && { + relationships: relationshipsAndHints.relationships + }), ...(relationshipsAndHints.hints && { hints: relationshipsAndHints.hints }), ...(enrichedSnippet && { snippet: enrichedSnippet }) };