#
tokens: 46519/50000 6/281 files (page 7/8)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 7 of 8. Use http://codebase.md/tiberriver256/azure-devops-mcp?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .clinerules
├── .env.example
├── .eslintrc.json
├── .github
│   ├── FUNDING.yml
│   ├── release-please-config.json
│   ├── release-please-manifest.json
│   └── workflows
│       ├── main.yml
│       └── release-please.yml
├── .gitignore
├── .husky
│   ├── commit-msg
│   └── pre-commit
├── .kilocode
│   └── mcp.json
├── .prettierrc
├── .vscode
│   └── settings.json
├── CHANGELOG.md
├── commitlint.config.js
├── CONTRIBUTING.md
├── create_branch.sh
├── docs
│   ├── authentication.md
│   ├── azure-identity-authentication.md
│   ├── ci-setup.md
│   ├── examples
│   │   ├── azure-cli-authentication.env
│   │   ├── azure-identity-authentication.env
│   │   ├── pat-authentication.env
│   │   └── README.md
│   ├── testing
│   │   ├── README.md
│   │   └── setup.md
│   └── tools
│       ├── core-navigation.md
│       ├── organizations.md
│       ├── pipelines.md
│       ├── projects.md
│       ├── pull-requests.md
│       ├── README.md
│       ├── repositories.md
│       ├── resources.md
│       ├── search.md
│       ├── user-tools.md
│       ├── wiki.md
│       └── work-items.md
├── finish_task.sh
├── jest.e2e.config.js
├── jest.int.config.js
├── jest.unit.config.js
├── LICENSE
├── memory
│   └── tasks_memory_2025-05-26T16-18-03.json
├── package-lock.json
├── package.json
├── project-management
│   ├── planning
│   │   ├── architecture-guide.md
│   │   ├── azure-identity-authentication-design.md
│   │   ├── project-plan.md
│   │   ├── project-structure.md
│   │   ├── tech-stack.md
│   │   └── the-dream-team.md
│   ├── startup.xml
│   ├── tdd-cycle.xml
│   └── troubleshooter.xml
├── README.md
├── setup_env.sh
├── shrimp-rules.md
├── src
│   ├── clients
│   │   └── azure-devops.ts
│   ├── features
│   │   ├── organizations
│   │   │   ├── __test__
│   │   │   │   └── test-helpers.ts
│   │   │   ├── index.spec.unit.ts
│   │   │   ├── index.ts
│   │   │   ├── list-organizations
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── schemas.ts
│   │   │   ├── tool-definitions.ts
│   │   │   └── types.ts
│   │   ├── pipelines
│   │   │   ├── get-pipeline
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── index.spec.unit.ts
│   │   │   ├── index.ts
│   │   │   ├── list-pipelines
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── tool-definitions.ts
│   │   │   ├── trigger-pipeline
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   └── types.ts
│   │   ├── projects
│   │   │   ├── __test__
│   │   │   │   └── test-helpers.ts
│   │   │   ├── get-project
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── get-project-details
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── index.spec.unit.ts
│   │   │   ├── index.ts
│   │   │   ├── list-projects
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── schemas.ts
│   │   │   ├── tool-definitions.ts
│   │   │   └── types.ts
│   │   ├── pull-requests
│   │   │   ├── add-pull-request-comment
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   └── index.ts
│   │   │   ├── create-pull-request
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── get-pull-request-comments
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   └── index.ts
│   │   │   ├── index.spec.unit.ts
│   │   │   ├── index.ts
│   │   │   ├── list-pull-requests
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── schemas.ts
│   │   │   ├── tool-definitions.ts
│   │   │   ├── types.ts
│   │   │   └── update-pull-request
│   │   │       ├── feature.spec.int.ts
│   │   │       ├── feature.spec.unit.ts
│   │   │       ├── feature.ts
│   │   │       └── index.ts
│   │   ├── repositories
│   │   │   ├── __test__
│   │   │   │   └── test-helpers.ts
│   │   │   ├── get-all-repositories-tree
│   │   │   │   ├── __snapshots__
│   │   │   │   │   └── feature.spec.unit.ts.snap
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── get-file-content
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── get-repository
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── get-repository-details
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── index.spec.unit.ts
│   │   │   ├── index.ts
│   │   │   ├── list-repositories
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── schemas.ts
│   │   │   ├── tool-definitions.ts
│   │   │   └── types.ts
│   │   ├── search
│   │   │   ├── index.spec.unit.ts
│   │   │   ├── index.ts
│   │   │   ├── schemas.ts
│   │   │   ├── search-code
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   └── index.ts
│   │   │   ├── search-wiki
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   └── index.ts
│   │   │   ├── search-work-items
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   └── index.ts
│   │   │   ├── tool-definitions.ts
│   │   │   └── types.ts
│   │   ├── users
│   │   │   ├── get-me
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── index.spec.unit.ts
│   │   │   ├── index.ts
│   │   │   ├── schemas.ts
│   │   │   ├── tool-definitions.ts
│   │   │   └── types.ts
│   │   ├── wikis
│   │   │   ├── create-wiki
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── create-wiki-page
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── get-wiki-page
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── get-wikis
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── index.spec.unit.ts
│   │   │   ├── index.ts
│   │   │   ├── list-wiki-pages
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── tool-definitions.ts
│   │   │   └── update-wiki-page
│   │   │       ├── feature.spec.int.ts
│   │   │       ├── feature.ts
│   │   │       ├── index.ts
│   │   │       └── schema.ts
│   │   └── work-items
│   │       ├── __test__
│   │       │   ├── fixtures.ts
│   │       │   ├── test-helpers.ts
│   │       │   └── test-utils.ts
│   │       ├── create-work-item
│   │       │   ├── feature.spec.int.ts
│   │       │   ├── feature.spec.unit.ts
│   │       │   ├── feature.ts
│   │       │   ├── index.ts
│   │       │   └── schema.ts
│   │       ├── get-work-item
│   │       │   ├── feature.spec.int.ts
│   │       │   ├── feature.spec.unit.ts
│   │       │   ├── feature.ts
│   │       │   ├── index.ts
│   │       │   └── schema.ts
│   │       ├── index.spec.unit.ts
│   │       ├── index.ts
│   │       ├── list-work-items
│   │       │   ├── feature.spec.int.ts
│   │       │   ├── feature.spec.unit.ts
│   │       │   ├── feature.ts
│   │       │   ├── index.ts
│   │       │   └── schema.ts
│   │       ├── manage-work-item-link
│   │       │   ├── feature.spec.int.ts
│   │       │   ├── feature.spec.unit.ts
│   │       │   ├── feature.ts
│   │       │   ├── index.ts
│   │       │   └── schema.ts
│   │       ├── schemas.ts
│   │       ├── tool-definitions.ts
│   │       ├── types.ts
│   │       └── update-work-item
│   │           ├── feature.spec.int.ts
│   │           ├── feature.spec.unit.ts
│   │           ├── feature.ts
│   │           ├── index.ts
│   │           └── schema.ts
│   ├── index.spec.unit.ts
│   ├── index.ts
│   ├── server.spec.e2e.ts
│   ├── server.ts
│   ├── shared
│   │   ├── api
│   │   │   ├── client.ts
│   │   │   └── index.ts
│   │   ├── auth
│   │   │   ├── auth-factory.ts
│   │   │   ├── client-factory.ts
│   │   │   └── index.ts
│   │   ├── config
│   │   │   ├── index.ts
│   │   │   └── version.ts
│   │   ├── enums
│   │   │   ├── index.spec.unit.ts
│   │   │   └── index.ts
│   │   ├── errors
│   │   │   ├── azure-devops-errors.ts
│   │   │   ├── handle-request-error.ts
│   │   │   └── index.ts
│   │   ├── test
│   │   │   └── test-helpers.ts
│   │   └── types
│   │       ├── config.ts
│   │       ├── index.ts
│   │       ├── request-handler.ts
│   │       └── tool-definition.ts
│   └── utils
│       ├── environment.spec.unit.ts
│       └── environment.ts
├── tasks.json
├── tests
│   └── setup.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/tasks.json:
--------------------------------------------------------------------------------

```json
  1 | {
  2 |   "tasks": [
  3 |     {
  4 |       "id": "42e6533a-f407-4286-be04-4d76fdfd8734",
  5 |       "name": "Create list-wiki-pages directory structure and schema",
  6 |       "description": "Create the folder structure src/features/wikis/list-wiki-pages/ with schema.ts and index.ts files. Implement Zod schema validation for ListWikiPagesSchema with organizationId, projectId, wikiId, path, and recursionLevel parameters following existing wiki patterns.",
  7 |       "status": "completed",
  8 |       "dependencies": [],
  9 |       "createdAt": "2025-05-26T16:18:03.641Z",
 10 |       "updatedAt": "2025-05-26T16:18:03.641Z",
 11 |       "relatedFiles": [
 12 |         {
 13 |           "path": "src/features/wikis/list-wiki-pages/schema.ts",
 14 |           "type": "CREATE",
 15 |           "description": "Zod schema for list wiki pages parameters"
 16 |         },
 17 |         {
 18 |           "path": "src/features/wikis/list-wiki-pages/index.ts",
 19 |           "type": "CREATE",
 20 |           "description": "Export file for list wiki pages feature"
 21 |         },
 22 |         {
 23 |           "path": "src/features/wikis/get-wikis/schema.ts",
 24 |           "type": "REFERENCE",
 25 |           "description": "Reference pattern for schema structure"
 26 |         },
 27 |         {
 28 |           "path": "src/utils/environment.ts",
 29 |           "type": "REFERENCE",
 30 |           "description": "Default organization and project utilities"
 31 |         }
 32 |       ],
 33 |       "implementationGuide": "1. Create directory: src/features/wikis/list-wiki-pages/\n2. Create schema.ts with ListWikiPagesSchema using z.object():\n   - organizationId: z.string().optional().describe()\n   - projectId: z.string().optional().describe()\n   - wikiId: z.string().describe()\n   - path: z.string().optional().describe()\n   - recursionLevel: z.number().int().min(1).max(50).optional().describe()\n3. Import defaultOrg, defaultProject from utils/environment\n4. Create index.ts with exports for schema and future feature function\n5. Follow exact patterns from src/features/wikis/get-wikis/schema.ts",
 34 |       "verificationCriteria": "Schema compiles without errors, exports are properly defined, follows existing naming conventions, includes proper TypeScript types and Zod validation",
 35 |       "analysisResult": "Implement list_wiki_pages tool for Azure DevOps MCP server following GitHub issue #184 requirements. The implementation extends existing WikiClient with Azure DevOps Pages Batch API support, includes comprehensive pagination handling, and maintains consistency with established codebase patterns for schema validation, error handling, and testing."
 36 |     },
 37 |     {
 38 |       "id": "6b895c15-b337-444b-908a-e50a5ae07da3",
 39 |       "name": "Extend WikiClient with listWikiPages method",
 40 |       "description": "Add listWikiPages method to WikiClient class in src/clients/azure-devops.ts. Implement Azure DevOps Pages Batch API call with POST request, pagination loop using continuationToken, and proper error handling.",
 41 |       "status": "completed",
 42 |       "dependencies": [],
 43 |       "createdAt": "2025-05-26T16:18:03.641Z",
 44 |       "updatedAt": "2025-05-26T21:57:06.473Z",
 45 |       "relatedFiles": [
 46 |         {
 47 |           "path": "src/clients/azure-devops.ts",
 48 |           "type": "TO_MODIFY",
 49 |           "description": "Add listWikiPages method to WikiClient class",
 50 |           "lineStart": 45,
 51 |           "lineEnd": 532
 52 |         }
 53 |       ],
 54 |       "implementationGuide": "1. Add listWikiPages method to WikiClient class\n2. Method signature: async listWikiPages(projectId: string, wikiId: string, options?: {path?: string, recursionLevel?: number})\n3. Implement POST request to: {baseUrl}/{project}/_apis/wiki/wikis/{wikiId}/pagesbatch?api-version=7.1\n4. Request body: {top: 1000, continuationToken?, path?, recursionLevel?}\n5. Pagination loop: while continuationToken exists, make subsequent requests\n6. Concatenate all results from response.data.value arrays\n7. Error handling: 404 -> AzureDevOpsResourceNotFoundError, 401/403 -> AzureDevOpsPermissionError\n8. Return WikiPageSummary[] with {id, path, url, order} fields\n9. Sort results by order then path",
 55 |       "verificationCriteria": "Method compiles without errors, implements proper pagination loop, handles all error cases, returns correctly typed results, follows existing WikiClient method patterns",
 56 |       "analysisResult": "Implement list_wiki_pages tool for Azure DevOps MCP server following GitHub issue #184 requirements. The implementation extends existing WikiClient with Azure DevOps Pages Batch API support, includes comprehensive pagination handling, and maintains consistency with established codebase patterns for schema validation, error handling, and testing.",
 57 |       "summary": "Successfully implemented the listWikiPages method in WikiClient class. The implementation includes: 1) Added WikiPageSummary interface with id, path, url, and order fields as required. 2) Implemented POST request to Azure DevOps Pages Batch API with proper pagination using continuationToken. 3) Added comprehensive error handling for 404 (AzureDevOpsResourceNotFoundError) and 401/403 (AzureDevOpsPermissionError) status codes. 4) Implemented sorting by order then path as specified. 5) Method signature matches requirements with optional path and recursionLevel parameters. 6) Code compiles without errors and follows existing WikiClient patterns. 7) All TypeScript types are properly defined and exported.",
 58 |       "completedAt": "2025-05-26T21:57:06.472Z"
 59 |     },
 60 |     {
 61 |       "id": "6f042d63-fa61-42c9-b7b0-820495aec9ba",
 62 |       "name": "Implement list-wiki-pages feature function",
 63 |       "description": "Create feature.ts with listWikiPages function that uses the WikiClient method. Define WikiPageSummary interface and implement the main feature logic with proper error handling and type safety.",
 64 |       "status": "completed",
 65 |       "dependencies": [
 66 |         {
 67 |           "taskId": "42e6533a-f407-4286-be04-4d76fdfd8734"
 68 |         },
 69 |         {
 70 |           "taskId": "6b895c15-b337-444b-908a-e50a5ae07da3"
 71 |         }
 72 |       ],
 73 |       "createdAt": "2025-05-26T16:18:03.641Z",
 74 |       "updatedAt": "2025-05-26T22:44:06.001Z",
 75 |       "relatedFiles": [
 76 |         {
 77 |           "path": "src/features/wikis/list-wiki-pages/feature.ts",
 78 |           "type": "CREATE",
 79 |           "description": "Main feature implementation"
 80 |         }
 81 |       ],
 82 |       "implementationGuide": "1. Create src/features/wikis/list-wiki-pages/feature.ts\n2. Define WikiPageSummary interface: {id: number, path: string, url: string, order?: number}\n3. Define ListWikiPagesOptions interface matching schema\n4. Implement listWikiPages function:\n   - Import WikiClient from clients/azure-devops\n   - Use organizationId || defaultOrg, projectId || defaultProject\n   - Call wikiClient.listWikiPages() with proper parameters\n   - Handle errors with try/catch and proper error type conversion\n   - Return WikiPageSummary[] array\n5. Follow patterns from src/features/wikis/get-wiki-page/feature.ts",
 83 |       "verificationCriteria": "Feature function compiles and exports correctly, proper error handling, type safety maintained, follows existing feature patterns, integrates properly with WikiClient",
 84 |       "analysisResult": "Implement list_wiki_pages tool for Azure DevOps MCP server following GitHub issue #184 requirements. The implementation extends existing WikiClient with Azure DevOps Pages Batch API support, includes comprehensive pagination handling, and maintains consistency with established codebase patterns for schema validation, error handling, and testing.",
 85 |       "summary": "Successfully implemented the list-wiki-pages feature function with all required components: Created src/features/wikis/list-wiki-pages/feature.ts with WikiPageSummary interface {id: number, path: string, url: string, order?: number}, imported ListWikiPagesOptions from schema, implemented listWikiPages function using WikiClient.listWikiPages() method with proper error handling, default organization/project handling, and type conversion from client's string id to number id. The implementation follows established patterns from get-wiki-page feature, compiles without TypeScript errors, and integrates properly with the existing WikiClient.",
 86 |       "completedAt": "2025-05-26T22:44:06.000Z"
 87 |     },
 88 |     {
 89 |       "id": "29f527f5-a069-4c2d-900b-eb3c7ac478d2",
 90 |       "name": "Add tool definition and update wikis module exports",
 91 |       "description": "Add list_wiki_pages tool definition to tool-definitions.ts and update the main wikis index.ts to include the new feature exports and request handler case.",
 92 |       "status": "completed",
 93 |       "dependencies": [
 94 |         {
 95 |           "taskId": "42e6533a-f407-4286-be04-4d76fdfd8734"
 96 |         },
 97 |         {
 98 |           "taskId": "6f042d63-fa61-42c9-b7b0-820495aec9ba"
 99 |         }
100 |       ],
101 |       "createdAt": "2025-05-26T16:18:03.641Z",
102 |       "updatedAt": "2025-05-26T22:52:55.297Z",
103 |       "relatedFiles": [
104 |         {
105 |           "path": "src/features/wikis/tool-definitions.ts",
106 |           "type": "TO_MODIFY",
107 |           "description": "Add list_wiki_pages tool definition"
108 |         },
109 |         {
110 |           "path": "src/features/wikis/index.ts",
111 |           "type": "TO_MODIFY",
112 |           "description": "Add exports and request handler case"
113 |         }
114 |       ],
115 |       "implementationGuide": "1. Update src/features/wikis/tool-definitions.ts:\n   - Import ListWikiPagesSchema\n   - Add tool definition: {name: 'list_wiki_pages', description: 'List pages within an Azure DevOps wiki', inputSchema: zodToJsonSchema(ListWikiPagesSchema)}\n2. Update src/features/wikis/index.ts:\n   - Add exports: export {listWikiPages, ListWikiPagesSchema} from './list-wiki-pages'\n   - Add 'list_wiki_pages' to isWikisRequest array\n   - Add case in handleWikisRequest switch statement\n   - Parse args with ListWikiPagesSchema.parse()\n   - Call listWikiPages with proper parameters\n   - Return JSON.stringify(result, null, 2) in content array",
116 |       "verificationCriteria": "Tool definition is properly added, exports are correct, request handler case works, follows existing patterns for tool registration and handling",
117 |       "analysisResult": "Implement list_wiki_pages tool for Azure DevOps MCP server following GitHub issue #184 requirements. The implementation extends existing WikiClient with Azure DevOps Pages Batch API support, includes comprehensive pagination handling, and maintains consistency with established codebase patterns for schema validation, error handling, and testing.",
118 |       "summary": "Successfully implemented the list_wiki_pages tool definition and updated wikis module exports. Added ListWikiPagesSchema import to tool-definitions.ts, created the tool definition with proper name, description, and schema. Updated main wikis index.ts to export listWikiPages and ListWikiPagesSchema, added 'list_wiki_pages' to the request identifier array, and implemented the request handler case with proper argument parsing and function call. Also fixed the missing export in list-wiki-pages/index.ts. All changes follow existing patterns and the build compiles successfully without errors.",
119 |       "completedAt": "2025-05-26T22:52:55.296Z"
120 |     },
121 |     {
122 |       "id": "457c0d1b-3635-49d7-916e-0e9aeb4f370f",
123 |       "name": "Implement comprehensive integration tests",
124 |       "description": "Create feature.spec.int.ts with comprehensive integration tests that test against real Azure DevOps API. This is the primary testing approach, covering happy path, error scenarios, and edge cases with real API responses.",
125 |       "status": "completed",
126 |       "dependencies": [
127 |         {
128 |           "taskId": "6f042d63-fa61-42c9-b7b0-820495aec9ba"
129 |         }
130 |       ],
131 |       "createdAt": "2025-05-26T16:18:03.641Z",
132 |       "updatedAt": "2025-05-26T23:24:09.973Z",
133 |       "relatedFiles": [
134 |         {
135 |           "path": "src/features/wikis/list-wiki-pages/feature.spec.int.ts",
136 |           "type": "CREATE",
137 |           "description": "Integration tests for list wiki pages feature"
138 |         }
139 |       ],
140 |       "implementationGuide": "1. Create src/features/wikis/list-wiki-pages/feature.spec.int.ts\n2. Add environment guard: process.env.AZDO_INT_TESTS === 'true'\n3. Comprehensive test cases with real Azure DevOps API:\n   - List pages in real test wiki (happy path)\n   - Handle invalid wikiId (expect 404 error)\n   - Test path filtering with real wiki structure\n   - Test recursionLevel parameter with various values\n   - Test pagination with large wiki structures\n   - Verify returned data structure matches WikiPageSummary interface\n   - Test edge cases like empty wikis, deeply nested paths\n   - Error scenarios: permission errors, network issues\n4. Follow patterns from src/features/wikis/get-wikis/feature.spec.int.ts\n5. Use real Azure DevOps connection and test data\n6. Include proper cleanup and comprehensive error handling",
141 |       "verificationCriteria": "Integration tests provide comprehensive coverage with real Azure DevOps API, proper environment guards, tests validate real data structure and all major scenarios, follows existing integration test patterns",
142 |       "analysisResult": "Implement list_wiki_pages tool for Azure DevOps MCP server following GitHub issue #184 requirements. The implementation extends existing WikiClient with Azure DevOps Pages Batch API support, includes comprehensive pagination handling, and maintains consistency with established codebase patterns for schema validation, error handling, and testing.",
143 |       "summary": "Successfully implemented comprehensive integration tests for list-wiki-pages feature. Created src/features/wikis/list-wiki-pages/feature.spec.int.ts with complete test coverage including: environment guard (AZDO_INT_TESTS === 'true'), happy path tests with real Azure DevOps API, error scenarios for invalid wikiId/projectId/organizationId, edge cases for empty wikis and deeply nested paths, data structure validation matching WikiPageSummary interface, performance tests for large wiki structures, path filtering tests, recursionLevel parameter testing with boundary values (1-50), and proper cleanup with comprehensive error handling. All tests follow existing integration test patterns from get-wikis and get-wiki-page features.",
144 |       "completedAt": "2025-05-26T23:24:09.973Z"
145 |     },
146 |     {
147 |       "id": "68804833-8bdd-4dda-ab6b-dc22f540a0e3",
148 |       "name": "Implement unit tests for coverage gaps",
149 |       "description": "Create feature.spec.unit.ts with unit tests to fill coverage gaps not covered by integration tests. Use mocks only when absolutely necessary for scenarios that cannot be tested with real Azure DevOps API.",
150 |       "status": "completed",
151 |       "dependencies": [
152 |         {
153 |           "taskId": "457c0d1b-3635-49d7-916e-0e9aeb4f370f"
154 |         }
155 |       ],
156 |       "createdAt": "2025-05-26T16:18:03.641Z",
157 |       "updatedAt": "2025-05-26T23:31:32.356Z",
158 |       "relatedFiles": [
159 |         {
160 |           "path": "src/features/wikis/list-wiki-pages/feature.spec.unit.ts",
161 |           "type": "CREATE",
162 |           "description": "Unit tests for list wiki pages feature"
163 |         }
164 |       ],
165 |       "implementationGuide": "1. Create src/features/wikis/list-wiki-pages/feature.spec.unit.ts\n2. Mock WikiClient and its listWikiPages method only for scenarios not covered by integration tests\n3. Focus on edge cases and error scenarios that are difficult to reproduce with real API:\n   - Network failures and timeouts\n   - Malformed API responses\n   - Edge cases in pagination logic\n   - Input validation edge cases\n4. Follow patterns from src/features/wikis/get-wikis/feature.spec.unit.ts\n5. Use jest.mock() for WikiClient only when necessary\n6. Complement integration tests rather than duplicate coverage",
166 |       "verificationCriteria": "Unit tests fill gaps in integration test coverage, minimal use of mocks, tests focus on scenarios that cannot be tested with real API, follows existing test patterns",
167 |       "analysisResult": "Implement list_wiki_pages tool for Azure DevOps MCP server following GitHub issue #184 requirements. The implementation extends existing WikiClient with Azure DevOps Pages Batch API support, includes comprehensive pagination handling, and maintains consistency with established codebase patterns for schema validation, error handling, and testing.",
168 |       "summary": "Successfully implemented comprehensive unit tests for list-wiki-pages feature. Created src/features/wikis/list-wiki-pages/feature.spec.unit.ts with 394 lines of focused unit tests that complement integration tests. Tests cover scenarios not easily testable with real API: network failures/timeouts, malformed API responses, edge cases in pagination logic, input validation edge cases, large datasets (10,000 pages), special characters in paths, boundary recursionLevel values, client creation failures, and data transformation scenarios. Used minimal mocking (only WikiClient when necessary) following patterns from existing unit tests. All tests focus on scenarios that cannot be reliably tested with real Azure DevOps API while avoiding duplication of integration test coverage.",
169 |       "completedAt": "2025-05-26T23:31:32.355Z"
170 |     }
171 |   ]
172 | }
```

--------------------------------------------------------------------------------
/docs/tools/repositories.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Azure DevOps Repositories Tools
  2 | 
  3 | This document describes the tools available for working with Azure DevOps Git repositories.
  4 | 
  5 | ## get_repository_details
  6 | 
  7 | Gets detailed information about a specific Git repository, including optional branch statistics and refs.
  8 | 
  9 | ### Description
 10 | 
 11 | The `get_repository_details` tool retrieves comprehensive information about a specific Git repository in Azure DevOps. It can optionally include branch statistics (ahead/behind counts, commit information) and repository refs (branches, tags). This is useful for tasks like branch management, policy configuration, and repository statistics tracking.
 12 | 
 13 | ### Parameters
 14 | 
 15 | ```json
 16 | {
 17 |   "projectId": "MyProject", // Required: The ID or name of the project
 18 |   "repositoryId": "MyRepo", // Required: The ID or name of the repository
 19 |   "includeStatistics": true, // Optional: Whether to include branch statistics (default: false)
 20 |   "includeRefs": true, // Optional: Whether to include repository refs (default: false)
 21 |   "refFilter": "heads/", // Optional: Filter for refs (e.g., "heads/" or "tags/")
 22 |   "branchName": "main" // Optional: Name of specific branch to get statistics for
 23 | }
 24 | ```
 25 | 
 26 | | Parameter | Type | Required | Description |
 27 | | --------- | ---- | -------- | ----------- |
 28 | | `projectId` | string | Yes | The ID or name of the project containing the repository |
 29 | | `repositoryId` | string | Yes | The ID or name of the repository to get details for |
 30 | | `includeStatistics` | boolean | No | Whether to include branch statistics (default: false) |
 31 | | `includeRefs` | boolean | No | Whether to include repository refs (default: false) |
 32 | | `refFilter` | string | No | Optional filter for refs (e.g., "heads/" or "tags/") |
 33 | | `branchName` | string | No | Name of specific branch to get statistics for (if includeStatistics is true) |
 34 | 
 35 | ### Response
 36 | 
 37 | The tool returns a `RepositoryDetails` object containing:
 38 | 
 39 | - `repository`: The basic repository information (same as returned by `get_repository`)
 40 | - `statistics` (optional): Branch statistics if requested
 41 | - `refs` (optional): Repository refs if requested
 42 | 
 43 | Example response:
 44 | 
 45 | ```json
 46 | {
 47 |   "repository": {
 48 |     "id": "repo-guid",
 49 |     "name": "MyRepository",
 50 |     "url": "https://dev.azure.com/organization/MyProject/_apis/git/repositories/MyRepository",
 51 |     "project": {
 52 |       "id": "project-guid",
 53 |       "name": "MyProject",
 54 |       "url": "https://dev.azure.com/organization/_apis/projects/project-guid"
 55 |     },
 56 |     "defaultBranch": "refs/heads/main",
 57 |     "size": 25478,
 58 |     "remoteUrl": "https://dev.azure.com/organization/MyProject/_git/MyRepository",
 59 |     "sshUrl": "[email protected]:v3/organization/MyProject/MyRepository",
 60 |     "webUrl": "https://dev.azure.com/organization/MyProject/_git/MyRepository"
 61 |   },
 62 |   "statistics": {
 63 |     "branches": [
 64 |       {
 65 |         "name": "refs/heads/main",
 66 |         "aheadCount": 0,
 67 |         "behindCount": 0,
 68 |         "isBaseVersion": true,
 69 |         "commit": {
 70 |           "commitId": "commit-guid",
 71 |           "author": {
 72 |             "name": "John Doe",
 73 |             "email": "[email protected]",
 74 |             "date": "2023-01-01T12:00:00Z"
 75 |           },
 76 |           "committer": {
 77 |             "name": "John Doe",
 78 |             "email": "[email protected]",
 79 |             "date": "2023-01-01T12:00:00Z"
 80 |           },
 81 |           "comment": "Initial commit"
 82 |         }
 83 |       }
 84 |     ]
 85 |   },
 86 |   "refs": {
 87 |     "value": [
 88 |       {
 89 |         "name": "refs/heads/main",
 90 |         "objectId": "commit-guid",
 91 |         "creator": {
 92 |           "displayName": "John Doe",
 93 |           "id": "user-guid"
 94 |         },
 95 |         "url": "https://dev.azure.com/organization/MyProject/_apis/git/repositories/repo-guid/refs/heads/main"
 96 |       }
 97 |     ],
 98 |     "count": 1
 99 |   }
100 | }
101 | ```
102 | 
103 | ### Error Handling
104 | 
105 | The tool may throw the following errors:
106 | 
107 | - General errors: If the API call fails or other unexpected errors occur
108 | - Authentication errors: If the authentication credentials are invalid or expired
109 | - Permission errors: If the authenticated user doesn't have permission to access the repository
110 | - ResourceNotFound errors: If the specified project or repository doesn't exist
111 | 
112 | Error messages will be formatted as text and provide details about what went wrong.
113 | 
114 | ### Example Usage
115 | 
116 | ```typescript
117 | // Basic example - just repository info
118 | const repoDetails = await mcpClient.callTool('get_repository_details', {
119 |   projectId: 'MyProject',
120 |   repositoryId: 'MyRepo'
121 | });
122 | console.log(repoDetails);
123 | 
124 | // Example with branch statistics
125 | const repoWithStats = await mcpClient.callTool('get_repository_details', {
126 |   projectId: 'MyProject',
127 |   repositoryId: 'MyRepo',
128 |   includeStatistics: true
129 | });
130 | console.log(repoWithStats);
131 | 
132 | // Example with refs filtered to branches
133 | const repoWithBranches = await mcpClient.callTool('get_repository_details', {
134 |   projectId: 'MyProject',
135 |   repositoryId: 'MyRepo',
136 |   includeRefs: true,
137 |   refFilter: 'heads/'
138 | });
139 | console.log(repoWithBranches);
140 | 
141 | // Example with all options
142 | const fullRepoDetails = await mcpClient.callTool('get_repository_details', {
143 |   projectId: 'MyProject',
144 |   repositoryId: 'MyRepo',
145 |   includeStatistics: true,
146 |   includeRefs: true,
147 |   refFilter: 'heads/',
148 |   branchName: 'main'
149 | });
150 | console.log(fullRepoDetails);
151 | ```
152 | 
153 | ### Implementation Details
154 | 
155 | This tool uses the Azure DevOps Node API's Git API to retrieve repository details:
156 | 
157 | 1. It gets a connection to the Azure DevOps WebApi client
158 | 2. It calls the `getGitApi()` method to get a handle to the Git API
159 | 3. It retrieves the basic repository information using `getRepository()`
160 | 4. If requested, it retrieves branch statistics using `getBranches()`
161 | 5. If requested, it retrieves repository refs using `getRefs()`
162 | 6. The combined results are returned to the caller
163 | 
164 | ## list_repositories
165 | 
166 | Lists all Git repositories in a specific project.
167 | 
168 | ### Description
169 | 
170 | The `list_repositories` tool retrieves all Git repositories within a specified Azure DevOps project. This is useful for discovering which repositories are available for cloning, accessing files, or creating branches and pull requests.
171 | 
172 | This tool uses the Azure DevOps WebApi client to interact with the Git API.
173 | 
174 | ### Parameters
175 | 
176 | ```json
177 | {
178 |   "projectId": "MyProject", // Required: The ID or name of the project
179 |   "includeLinks": true // Optional: Whether to include reference links
180 | }
181 | ```
182 | 
183 | | Parameter      | Type    | Required | Description                                                  |
184 | | -------------- | ------- | -------- | ------------------------------------------------------------ |
185 | | `projectId`    | string  | Yes      | The ID or name of the project containing the repositories    |
186 | | `includeLinks` | boolean | No       | Whether to include reference links in the repository objects |
187 | 
188 | ### Response
189 | 
190 | The tool returns an array of `GitRepository` objects, each containing:
191 | 
192 | - `id`: The unique identifier of the repository
193 | - `name`: The name of the repository
194 | - `url`: The URL of the repository
195 | - `project`: Object containing basic project information
196 | - `defaultBranch`: The default branch of the repository (e.g., "refs/heads/main")
197 | - `size`: The size of the repository
198 | - `remoteUrl`: The remote URL for cloning the repository
199 | - `sshUrl`: The SSH URL for cloning the repository
200 | - `webUrl`: The web URL for browsing the repository in browser
201 | - ... and potentially other repository properties
202 | 
203 | Example response:
204 | 
205 | ```json
206 | [
207 |   {
208 |     "id": "repo-guid-1",
209 |     "name": "FirstRepository",
210 |     "url": "https://dev.azure.com/organization/MyProject/_apis/git/repositories/FirstRepository",
211 |     "project": {
212 |       "id": "project-guid",
213 |       "name": "MyProject",
214 |       "url": "https://dev.azure.com/organization/_apis/projects/project-guid"
215 |     },
216 |     "defaultBranch": "refs/heads/main",
217 |     "size": 25478,
218 |     "remoteUrl": "https://dev.azure.com/organization/MyProject/_git/FirstRepository",
219 |     "sshUrl": "[email protected]:v3/organization/MyProject/FirstRepository",
220 |     "webUrl": "https://dev.azure.com/organization/MyProject/_git/FirstRepository"
221 |   },
222 |   {
223 |     "id": "repo-guid-2",
224 |     "name": "SecondRepository",
225 |     "url": "https://dev.azure.com/organization/MyProject/_apis/git/repositories/SecondRepository",
226 |     "project": {
227 |       "id": "project-guid",
228 |       "name": "MyProject",
229 |       "url": "https://dev.azure.com/organization/_apis/projects/project-guid"
230 |     },
231 |     "defaultBranch": "refs/heads/main",
232 |     "size": 15789,
233 |     "remoteUrl": "https://dev.azure.com/organization/MyProject/_git/SecondRepository",
234 |     "sshUrl": "[email protected]:v3/organization/MyProject/SecondRepository",
235 |     "webUrl": "https://dev.azure.com/organization/MyProject/_git/SecondRepository"
236 |   }
237 | ]
238 | ```
239 | 
240 | ### Error Handling
241 | 
242 | The tool may throw the following errors:
243 | 
244 | - General errors: If the API call fails or other unexpected errors occur
245 | - Authentication errors: If the authentication credentials are invalid or expired
246 | - Permission errors: If the authenticated user doesn't have permission to list repositories
247 | - ResourceNotFound errors: If the specified project doesn't exist
248 | 
249 | Error messages will be formatted as text and provide details about what went wrong.
250 | 
251 | ### Example Usage
252 | 
253 | ```typescript
254 | // Basic example
255 | const repositories = await mcpClient.callTool('list_repositories', {
256 |   projectId: 'MyProject',
257 | });
258 | console.log(repositories);
259 | 
260 | // Example with includeLinks parameter
261 | const repositoriesWithLinks = await mcpClient.callTool('list_repositories', {
262 |   projectId: 'MyProject',
263 |   includeLinks: true,
264 | });
265 | console.log(repositoriesWithLinks);
266 | ```
267 | 
268 | ### Implementation Details
269 | 
270 | This tool uses the Azure DevOps Node API's Git API to retrieve repositories:
271 | 
272 | 1. It gets a connection to the Azure DevOps WebApi client
273 | 2. It calls the `getGitApi()` method to get a handle to the Git API
274 | 3. It then calls `getRepositories()` with the specified project ID and optional include links parameter
275 | 4. The results are returned directly to the caller
276 | 
277 | ### Related Tools
278 | 
279 | - `get_repository`: Get details of a specific repository
280 | - `get_repository_details`: Get detailed information about a repository including statistics and refs
281 | - `list_projects`: List all projects in the organization (to find project IDs)
282 | 
283 | ## get_file_content
284 | 
285 | Retrieves the content of a file or directory from a Git repository.
286 | 
287 | ### Description
288 | 
289 | The `get_file_content` tool allows you to access the contents of files and directories within a Git repository. This is useful for examining code, documentation, or other files stored in repositories without having to clone the entire repository. It supports fetching file content from the default branch or from specific branches, tags, or commits.
290 | 
291 | ### Parameters
292 | 
293 | ```json
294 | {
295 |   "projectId": "MyProject", // Required: The ID or name of the project
296 |   "repositoryId": "MyRepo", // Required: The ID or name of the repository
297 |   "path": "/src/index.ts", // Required: The path to the file or directory
298 |   "versionType": "branch", // Optional: The type of version (branch, tag, or commit)
299 |   "version": "main" // Optional: The name of the branch/tag, or commit ID
300 | }
301 | ```
302 | 
303 | | Parameter | Type | Required | Description |
304 | | --------- | ---- | -------- | ----------- |
305 | | `projectId` | string | Yes | The ID or name of the project containing the repository |
306 | | `repositoryId` | string | Yes | The ID or name of the repository |
307 | | `path` | string | Yes | The path to the file or directory (starting with "/") |
308 | | `versionType` | enum | No | The type of version: "branch", "tag", or "commit" (GitVersionType) |
309 | | `version` | string | No | The name of the branch/tag, or the commit ID |
310 | 
311 | ### Response
312 | 
313 | The tool returns a `FileContentResponse` object containing:
314 | 
315 | - `content`: The content of the file as a string, or a JSON string of items for directories
316 | - `isDirectory`: Boolean indicating whether the path refers to a directory
317 | 
318 | Example response for a file:
319 | 
320 | ```json
321 | {
322 |   "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-root',\n  templateUrl: './app.component.html',\n  styleUrls: ['./app.component.css']\n})\nexport class AppComponent {\n  title = 'My App';\n}\n",
323 |   "isDirectory": false
324 | }
325 | ```
326 | 
327 | Example response for a directory:
328 | 
329 | ```json
330 | {
331 |   "content": "[{\"objectId\":\"c7be24d3\",\"gitObjectType\":\"blob\",\"commitId\":\"d5b8e757\",\"path\":\"/src/app/app.component.ts\",\"contentMetadata\":{\"fileName\":\"app.component.ts\"}},{\"objectId\":\"a8c2e5f1\",\"gitObjectType\":\"blob\",\"commitId\":\"d5b8e757\",\"path\":\"/src/app/app.module.ts\",\"contentMetadata\":{\"fileName\":\"app.module.ts\"}}]",
332 |   "isDirectory": true
333 | }
334 | ```
335 | 
336 | ### Error Handling
337 | 
338 | The tool may throw the following errors:
339 | 
340 | - General errors: If the API call fails or other unexpected errors occur
341 | - Authentication errors: If the authentication credentials are invalid or expired
342 | - Permission errors: If the authenticated user doesn't have permission to access the repository
343 | - ResourceNotFound errors: If the specified project, repository, or path doesn't exist
344 | 
345 | Error messages will be formatted as text and provide details about what went wrong.
346 | 
347 | ### Example Usage
348 | 
349 | ```typescript
350 | // Basic example - get file from default branch
351 | const fileContent = await mcpClient.callTool('get_file_content', {
352 |   projectId: 'MyProject',
353 |   repositoryId: 'MyRepo',
354 |   path: '/src/index.ts'
355 | });
356 | console.log(fileContent.content);
357 | 
358 | // Get directory content
359 | const directoryContent = await mcpClient.callTool('get_file_content', {
360 |   projectId: 'MyProject',
361 |   repositoryId: 'MyRepo',
362 |   path: '/src'
363 | });
364 | if (directoryContent.isDirectory) {
365 |   const items = JSON.parse(directoryContent.content);
366 |   console.log(`Directory contains ${items.length} items`);
367 | }
368 | 
369 | // Get file from specific branch
370 | const branchFileContent = await mcpClient.callTool('get_file_content', {
371 |   projectId: 'MyProject',
372 |   repositoryId: 'MyRepo',
373 |   path: '/src/index.ts',
374 |   versionType: 'branch',
375 |   version: 'feature/new-ui'
376 | });
377 | console.log(branchFileContent.content);
378 | 
379 | // Get file from specific commit
380 | const commitFileContent = await mcpClient.callTool('get_file_content', {
381 |   projectId: 'MyProject',
382 |   repositoryId: 'MyRepo',
383 |   path: '/src/index.ts',
384 |   versionType: 'commit',
385 |   version: 'a1b2c3d4e5f6g7h8i9j0'
386 | });
387 | console.log(commitFileContent.content);
388 | ```
389 | 
390 | ### Implementation Details
391 | 
392 | This tool uses the Azure DevOps Node API's Git API to retrieve file or directory content:
393 | 
394 | 1. It gets a connection to the Azure DevOps WebApi client
395 | 2. It calls the `getGitApi()` method to get a handle to the Git API
396 | 3. It determines if the path is a file or directory by attempting to fetch items
397 | 4. For directories, it returns the list of items as a JSON string
398 | 5. For files, it fetches the file content and returns it as a string
399 | 6. The results are wrapped in a `FileContentResponse` object with the appropriate `isDirectory` flag
400 | 
401 | ### Resource URI Access
402 | 
403 | In addition to using this tool, file content can also be accessed via resource URIs with the following patterns:
404 | 
405 | - Default branch: `ado://{organization}/{project}/{repo}/contents/{path}`
406 | - Specific branch: `ado://{organization}/{project}/{repo}/branches/{branch}/contents/{path}`
407 | - Specific commit: `ado://{organization}/{project}/{repo}/commits/{commit}/contents/{path}`
408 | - Specific tag: `ado://{organization}/{project}/{repo}/tags/{tag}/contents/{path}`
409 | - Pull request: `ado://{organization}/{project}/{repo}/pullrequests/{prId}/contents/{path}`
410 | 
411 | ### Related Tools
412 | 
413 | - `list_repositories`: List all repositories in a project
414 | - `get_repository`: Get details of a specific repository
415 | - `get_repository_details`: Get detailed information about a repository including statistics and refs
416 | - `search_code`: Search for code across repositories in a project
417 | 
418 | ## get_all_repositories_tree
419 | 
420 | Displays a hierarchical tree view of files and directories across multiple Azure DevOps repositories within a project, based on their default branches.
421 | 
422 | ### Description
423 | 
424 | The `get_all_repositories_tree` tool provides a broad overview of file and directory structure across multiple repositories in a project. It uses a tree-like structure similar to the Unix `tree` command, with each repository's tree displayed sequentially.
425 | 
426 | Key features:
427 | - Views multiple repositories at once
428 | - Filter repositories by name pattern
429 | - Filter files by pattern
430 | - Control depth to balance performance and detail
431 | - Shows directories and files in a hierarchical view
432 | - Provides statistics (count of files and directories)
433 | - Works with the default branch of each repository
434 | - Handles errors gracefully
435 | 
436 | ### Parameters
437 | 
438 | ```json
439 | {
440 |   "organizationId": "MyOrg",
441 |   "projectId": "MyProject",
442 |   "repositoryPattern": "API*",
443 |   "depth": 0, 
444 |   "pattern": "*.yaml"
445 | }
446 | ```
447 | 
448 | - `organizationId` (string, required): The ID or name of the Azure DevOps organization.
449 | - `projectId` (string, required): The ID or name of the project containing the repositories.
450 | - `repositoryPattern` (string, optional): Pattern to filter repositories by name (PowerShell wildcard).
451 | - `depth` (number, optional, default: 0): Maximum depth to traverse in each repository's file hierarchy. Use 0 for unlimited depth (more efficient server-side recursion), or a specific number (1-10) for limited depth.
452 | - `pattern` (string, optional): Pattern to filter files by name (PowerShell wildcard). Note: Directories are always shown regardless of this filter.
453 | 
454 | ### Response
455 | 
456 | The response is a formatted ASCII tree showing the file and directory structure of each repository:
457 | 
458 | ```
459 | Repo-API-1/
460 |   |-- src/
461 |   |   |-- config.yaml
462 |   |   `-- utils/
463 |   `-- deploy.yaml
464 | 1 directory, 2 files
465 | 
466 | Repo-API-Gateway/
467 |   |-- charts/
468 |   |   `-- values.yaml
469 |   `-- README.md
470 | 1 directory, 2 files
471 | 
472 | Repo-Data-Service/
473 |   (Repository is empty or default branch not found)
474 | 0 directories, 0 files
475 | ```
476 | 
477 | ### Examples
478 | 
479 | #### Basic Example - View All Repositories with Maximum Depth
480 | 
481 | ```javascript
482 | const result = await mcpClient.callTool('get_all_repositories_tree', {
483 |   organizationId: 'MyOrg',
484 |   projectId: 'MyProject'
485 | });
486 | console.log(result);
487 | ```
488 | 
489 | #### Filter Repositories by Name Pattern
490 | 
491 | ```javascript
492 | const result = await mcpClient.callTool('get_all_repositories_tree', {
493 |   organizationId: 'MyOrg',
494 |   projectId: 'MyProject',
495 |   repositoryPattern: 'API*'
496 | });
497 | console.log(result);
498 | ```
499 | 
500 | #### Limited Depth and File Pattern Filter
501 | 
502 | ```javascript
503 | const result = await mcpClient.callTool('get_all_repositories_tree', {
504 |   organizationId: 'MyOrg',
505 |   projectId: 'MyProject',
506 |   depth: 1,  // Only one level deep
507 |   pattern: '*.yaml'
508 | });
509 | console.log(result);
510 | ```
511 | 
512 | ### Performance Considerations
513 | 
514 | - For maximum depth (depth=0), the tool uses server-side recursion (VersionControlRecursionType.Full) which is more efficient for retrieving deep directory structures.
515 | - For limited depth (depth=1 to 10), the tool uses client-side recursion which is better for controlled exploration.
516 | - When viewing very large repositories, consider using a limited depth or file pattern to reduce response time.
517 | 
518 | ### Related Tools
519 | 
520 | - `list_repositories`: Lists all repositories in a project (summary only)
521 | - `get_repository_details`: Gets detailed info about a single repository
522 | - `get_repository_tree`: Explores structure within a single repository (more detailed)
523 | - `get_file_content`: Gets content of a specific file
524 | 
```

--------------------------------------------------------------------------------
/shrimp-rules.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Development Guidelines for AI Agents - mcp-server-azure-devops
  2 | 
  3 | **This document is exclusively for AI Agent operational use. DO NOT include general development knowledge.**
  4 | 
  5 | ## 1. Project Overview
  6 | 
  7 | ### Purpose
  8 | - This project, `@tiberriver256/mcp-server-azure-devops`, is an MCP (Model Context Protocol) server.
  9 | - Its primary function is to provide tools for interacting with Azure DevOps services.
 10 | 
 11 | ### Technology Stack
 12 | - **Core**: TypeScript, Node.js
 13 | - **Key Libraries**:
 14 |     - `@modelcontextprotocol/sdk`: For MCP server and type definitions.
 15 |     - `azure-devops-node-api`: For interacting with Azure DevOps.
 16 |     - `@azure/identity`: For Azure authentication.
 17 |     - `zod`: For schema definition and validation.
 18 |     - `zod-to-json-schema`: For converting Zod schemas to JSON schemas for MCP tools.
 19 | - **Testing**: Jest (for unit, integration, and e2e tests).
 20 | - **Linting/Formatting**: ESLint, Prettier.
 21 | - **Environment Management**: `dotenv`.
 22 | 
 23 | ### Core Functionality
 24 | - Provides MCP tools to interact with Azure DevOps features including, but not limited to:
 25 |     - Organizations
 26 |     - Projects (list, get, get details)
 27 |     - Repositories (list, get, get content, get tree)
 28 |     - Work Items (list, get, create, update, manage links)
 29 |     - Pull Requests (list, get, create, update, add/get comments)
 30 |     - Pipelines (list, trigger)
 31 |     - Search (code, wiki, work items)
 32 |     - Users (get current user)
 33 |     - Wikis (list, get page, create, update page)
 34 | 
 35 | ## 2. Project Architecture
 36 | 
 37 | ### Main Directory Structure
 38 | - **`./` (Root)**:
 39 |     - [`package.json`](package.json:0): Project metadata, dependencies, and NPM scripts. **REFER** to this for available commands and dependencies.
 40 |     - [`tsconfig.json`](tsconfig.json:0): TypeScript compiler configuration. **ADHERE** to its settings.
 41 |     - [`.eslintrc.json`](.eslintrc.json:0): ESLint configuration for code linting. **ADHERE** to its rules.
 42 |     - [`README.md`](README.md:0): General project information.
 43 |     - `setup_env.sh`: Shell script for environment setup.
 44 |     - `CHANGELOG.md` (if present): Tracks changes between versions.
 45 | - **`src/`**: Contains all TypeScript source code.
 46 |     - **`src/features/`**: Core application logic. Each subdirectory represents a distinct Azure DevOps feature set (e.g., `projects`, `repositories`).
 47 |         - `src/features/[feature-name]/`: Contains all files related to a specific feature.
 48 |             - `src/features/[feature-name]/index.ts`: Main export file for the feature. Exports request handlers (`isFeatureRequest`, `handleFeatureRequest`), tool definitions array (`featureTools`), schemas, types, and individual tool implementation functions. **MODIFY** this file when adding new tools or functionalities to the feature.
 49 |             - `src/features/[feature-name]/schemas.ts`: Defines Zod input/output schemas for all tools within this feature. **DEFINE** new schemas here.
 50 |             - `src/features/[feature-name]/tool-definitions.ts`: Defines MCP tools for the feature using `@modelcontextprotocol/sdk` and `zodToJsonSchema`. **ADD** new tool definitions here.
 51 |             - `src/features/[feature-name]/types.ts`: Contains TypeScript type definitions specific to this feature. **DEFINE** feature-specific types here.
 52 |             - `src/features/[feature-name]/[tool-name]/`: Subdirectory for a specific tool/action within the feature.
 53 |                 - `src/features/[feature-name]/[tool-name]/feature.ts`: Implements the core logic for the specific tool (e.g., API calls, data transformation). **IMPLEMENT** tool logic here.
 54 |                 - `src/features/[feature-name]/[tool-name]/index.ts`: Exports the `feature.ts` logic and potentially tool-specific schemas/types if not in the parent feature files.
 55 |                 - `src/features/[feature-name]/[tool-name]/schema.ts` (optional, often re-exports from feature-level `schemas.ts`): Defines or re-exports Zod schemas for this specific tool.
 56 |         - `src/features/organizations/`, `src/features/pipelines/`, `src/features/projects/`, `src/features/pull-requests/`, `src/features/repositories/`, `src/features/search/`, `src/features/users/`, `src/features/wikis/`, `src/features/work-items/`: Existing feature modules. **REFER** to these for patterns.
 57 |     - **`src/shared/`**: Contains shared modules and utilities used across features.
 58 |         - `src/shared/api/`: Azure DevOps API client setup (e.g., `client.ts`).
 59 |         - `src/shared/auth/`: Authentication logic for Azure DevOps (e.g., `auth-factory.ts`, `client-factory.ts`). **USE** these factories; DO NOT implement custom auth.
 60 |         - `src/shared/config/`: Configuration management (e.g., `version.ts`).
 61 |         - `src/shared/errors/`: Shared error handling classes and utilities (e.g., `azure-devops-errors.ts`, `handle-request-error.ts`). **USE** these for consistent error handling.
 62 |         - `src/shared/types/`: Global TypeScript type definitions (e.g., `config.ts`, `request-handler.ts`, `tool-definition.ts`).
 63 |     - **`src/utils/`**: General utility functions.
 64 |         - `src/utils/environment.ts`: Provides default values for environment variables (e.g., `defaultProject`, `defaultOrg`).
 65 |     - [`src/index.ts`](src/index.ts:1): Main application entry point. Handles environment variable loading and server initialization. **Exports** server components.
 66 |     - [`src/server.ts`](src/server.ts:1): MCP server core logic. Initializes the server, registers all tool handlers from features, and sets up request routing. **MODIFY** this file to register new feature modules (their `isFeatureRequest` and `handleFeatureRequest` handlers, and `featureTools` array).
 67 | - **`docs/`**: Currently empty. If documentation is added, **MAINTAIN** it in sync with code changes.
 68 | - **`project-management/`**: Contains project planning and design documents. **REFER** to `architecture-guide.md` for high-level design.
 69 | - **`tests/`**: Directory for global test setup or utilities if any. Most tests are co-located with source files (e.g., `*.spec.unit.ts`, `*.spec.int.ts`, `*.spec.e2e.ts`).
 70 | 
 71 | ## 3. Code Standards
 72 | 
 73 | ### Naming Conventions
 74 | - **Files and Directories**: USE kebab-case (e.g., `my-feature`, `get-project-details.ts`).
 75 | - **Variables and Functions**: USE camelCase (e.g., `projectId`, `listProjects`).
 76 | - **Classes, Interfaces, Enums, Types**: USE PascalCase (e.g., `AzureDevOpsClient`, `TeamProject`, `AuthenticationMethod`).
 77 | - **Test Files**:
 78 |     - Unit tests: `[filename].spec.unit.ts` (e.g., [`get-project.spec.unit.ts`](src/features/projects/get-project/feature.spec.unit.ts:0)).
 79 |     - Integration tests: `[filename].spec.int.ts` (e.g., [`get-project.spec.int.ts`](src/features/projects/get-project/feature.spec.int.ts:0)).
 80 |     - E2E tests: `[filename].spec.e2e.ts` (e.g., [`server.spec.e2e.ts`](src/server.spec.e2e.ts:0)).
 81 | - **Feature Modules**: Place under `src/features/[feature-name]/`.
 82 | - **Tool Logic**: Place in `src/features/[feature-name]/[tool-name]/feature.ts`.
 83 | - **Schemas**: Define in `src/features/[feature-name]/schemas.ts`.
 84 | - **Tool Definitions (MCP)**: Define in `src/features/[feature-name]/tool-definitions.ts`.
 85 | - **Types**: Feature-specific types in `src/features/[feature-name]/types.ts`; global types in `src/shared/types/`.
 86 | 
 87 | ### Formatting
 88 | - **Prettier**: Enforced via ESLint and lint-staged.
 89 | - **Rule**: ADHERE to formatting rules defined by Prettier (implicitly via [`.eslintrc.json`](.eslintrc.json:1) which extends `prettier`).
 90 | - **Action**: ALWAYS run `npm run format` (or rely on lint-staged) before committing.
 91 | 
 92 | ### Linting
 93 | - **ESLint**: Configuration in [`.eslintrc.json`](.eslintrc.json:1).
 94 | - **Rule**: ADHERE to linting rules.
 95 | - **Action**: ALWAYS run `npm run lint` (or `npm run lint:fix`) and RESOLVE all errors/warnings before committing.
 96 | - **Key Lint Rules (from [`.eslintrc.json`](.eslintrc.json:1))**:
 97 |     - `prettier/prettier: "error"` (Prettier violations are ESLint errors).
 98 |     - `@typescript-eslint/no-explicit-any: "warn"` (Avoid `any` where possible; it's "off" for `*.spec.unit.ts` and `tests/**/*.ts`).
 99 |     - `@typescript-eslint/no-unused-vars: ["error", { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }]` (No unused variables, allowing `_` prefix for ignored ones).
100 | 
101 | ### Comments
102 | - **TSDoc**: USE TSDoc for documenting public functions, classes, interfaces, and types (e.g., `/** ... */`).
103 | - **Inline Comments**: For complex logic blocks, ADD inline comments (`// ...`) explaining the purpose.
104 | 
105 | ### TypeScript Specifics (from [`tsconfig.json`](tsconfig.json:1))
106 | - `strict: true`: ADHERE to strict mode.
107 | - `noImplicitAny: true`: DO NOT use implicit `any`. Explicitly type all entities.
108 | - `noUnusedLocals: true`, `noUnusedParameters: true`: ENSURE no unused local variables or parameters.
109 | - `moduleResolution: "Node16"`: Be aware of Node.js ESM module resolution specifics.
110 | - `paths: { "@/*": ["src/*"] }`: USE path alias `@/*` for imports from `src/`.
111 | 
112 | ## 4. Functionality Implementation Standards
113 | 
114 | ### Adding a New Tool/Functionality to an Existing Feature
115 | 1.  **Identify Feature**: Determine the relevant feature directory in `src/features/[feature-name]/`.
116 | 2.  **Create Tool Directory**: Inside the feature directory, CREATE a new subdirectory for your tool, e.g., `src/features/[feature-name]/[new-tool-name]/`.
117 | 3.  **Implement Logic**: CREATE `[new-tool-name]/feature.ts`. Implement the core Azure DevOps interaction logic here.
118 |     - USE `getClient()` from `src/shared/api/client.ts` or `getConnection()` from [`src/server.ts`](src/server.ts:1) to get `WebApi`.
119 |     - USE error handling from `src/shared/errors/`.
120 | 4.  **Define Schema**:
121 |     - ADD Zod schema for the tool's input to `src/features/[feature-name]/schemas.ts`.
122 |     - EXPORT it.
123 |     - If needed, CREATE `[new-tool-name]/schema.ts` and re-export the specific schema from the feature-level `schemas.ts`.
124 | 5.  **Define MCP Tool**:
125 |     - ADD tool definition to `src/features/[feature-name]/tool-definitions.ts`.
126 |     - Import the Zod schema and use `zodToJsonSchema` for `inputSchema`.
127 |     - Ensure `name` matches the intended tool name for MCP.
128 | 6.  **Update Feature Index**:
129 |     - In `src/features/[feature-name]/index.ts`:
130 |         - EXPORT your new tool's logic function (from `[new-tool-name]/feature.ts` or its `index.ts`).
131 |         - ADD your new tool's name to the `includes()` check in `isFeatureRequest` function.
132 |         - ADD a `case` for your new tool in the `handleFeatureRequest` function to call your logic. Parse arguments using the Zod schema.
133 | 7.  **Update Server**: No changes usually needed in [`src/server.ts`](src/server.ts:1) if the feature module is already registered. The feature's `tool-definitions.ts` and `handleFeatureRequest` will be picked up.
134 | 8.  **Add Tests**: CREATE `[new-tool-name]/feature.spec.unit.ts` and `[new-tool-name]/feature.spec.int.ts`.
135 | 
136 | ### Adding a New Feature Module (e.g., for a new Azure DevOps Service Area)
137 | 1.  **Create Feature Directory**: CREATE `src/features/[new-feature-module-name]/`.
138 | 2.  **Implement Tools**: Follow "Adding a New Tool" steps above for each tool within this new feature module. This includes creating `schemas.ts`, `tool-definitions.ts`, `types.ts` (if needed), and subdirectories for each tool's `feature.ts`.
139 | 3.  **Create Feature Index**: CREATE `src/features/[new-feature-module-name]/index.ts`.
140 |     - EXPORT all schemas, types, tool logic functions.
141 |     - EXPORT the `[new-feature-module-name]Tools` array from `tool-definitions.ts`.
142 |     - CREATE and EXPORT `is[NewFeatureModuleName]Request` (e.g., `isMyNewFeatureRequest`) type guard.
143 |     - CREATE and EXPORT `handle[NewFeatureModuleName]Request` (e.g., `handleMyNewFeatureRequest`) request handler function.
144 | 4.  **Register Feature in Server**:
145 |     - In [`src/server.ts`](src/server.ts:1):
146 |         - IMPORT `[new-feature-module-name]Tools`, `is[NewFeatureModuleName]Request`, and `handle[NewFeatureModuleName]Request` from your new feature's `index.ts`.
147 |         - ADD `...[new-feature-module-name]Tools` to the `tools` array in the `ListToolsRequestSchema` handler.
148 |         - ADD an `if (is[NewFeatureModuleName]Request(request)) { return await handle[NewFeatureModuleName]Request(connection, request); }` block in the `CallToolRequestSchema` handler.
149 | 5.  **Add Tests**: Ensure comprehensive tests for the new feature module.
150 | 
151 | ## 5. Framework/Plugin/Third-party Library Usage Standards
152 | 
153 | - **`@modelcontextprotocol/sdk`**:
154 |     - USE `Server` class from `@modelcontextprotocol/sdk/server/index.js` to create the MCP server ([`src/server.ts`](src/server.ts:1)).
155 |     - USE `StdioServerTransport` for transport ([`src/index.ts`](src/index.ts:1)).
156 |     - USE schema types like `CallToolRequestSchema` from `@modelcontextprotocol/sdk/types.js`.
157 |     - DEFINE tools as `ToolDefinition[]` (see `src/shared/types/tool-definition.ts` and feature `tool-definitions.ts` files).
158 | - **`azure-devops-node-api`**:
159 |     - This is the primary library for Azure DevOps interactions.
160 |     - OBTAIN `WebApi` connection object via `getConnection()` from [`src/server.ts`](src/server.ts:1) or `AzureDevOpsClient` from `src/shared/auth/client-factory.ts`.
161 |     - USE specific APIs from the connection (e.g., `connection.getCoreApi()`, `connection.getWorkItemTrackingApi()`).
162 | - **`@azure/identity`**:
163 |     - Used for Azure authentication (e.g., `DefaultAzureCredential`).
164 |     - Primarily abstracted via `AzureDevOpsClient` in `src/shared/auth/`. PREFER using this abstraction.
165 | - **`zod`**:
166 |     - USE for all input/output schema definition and validation.
167 |     - DEFINE schemas in `src/features/[feature-name]/schemas.ts`.
168 |     - USE `z.object({...})`, `z.string()`, `z.boolean()`, etc.
169 |     - USE `.optional()`, `.default()`, `.describe()` for schema fields.
170 | - **`zod-to-json-schema`**:
171 |     - USE to convert Zod schemas to JSON schemas for MCP `inputSchema` in `tool-definitions.ts`.
172 | - **`dotenv`**:
173 |     - Used in [`src/index.ts`](src/index.ts:1) to load environment variables from a `.env` file.
174 | - **Jest**:
175 |     - Test files co-located with source files or in feature-specific `__test__` directories.
176 |     - Configuration in `jest.unit.config.js`, `jest.int.config.js`, `jest.e2e.config.js`.
177 | - **ESLint/Prettier**: See "Code Standards".
178 | 
179 | ## 6. Workflow Standards
180 | 
181 | ### Development Workflow
182 | 1.  **Branch**: CREATE or CHECKOUT a feature/bugfix branch from `main` (or relevant development branch).
183 | 2.  **Implement**: WRITE code and corresponding tests.
184 | 3.  **Test**:
185 |     - RUN unit tests: `npm run test:unit`.
186 |     - RUN integration tests: `npm run test:int`.
187 |     - RUN E2E tests: `npm run test:e2e`.
188 |     - Or run all tests: `npm test`.
189 |     - ENSURE all tests pass.
190 | 4.  **Lint & Format**:
191 |     - RUN `npm run lint` (or `npm run lint:fix`). RESOLVE all issues.
192 |     - RUN `npm run format`.
193 | 5.  **Commit**:
194 |     - USE Conventional Commits specification (e.g., `feat: ...`, `fix: ...`).
195 |     - RECOMMENDED: Use `npm run commit` (uses `cz-conventional-changelog`) for guided commit messages.
196 | 6.  **Pull Request**: PUSH branch and CREATE Pull Request against `main` (or relevant development branch).
197 | 
198 | ### NPM Scripts (from [`package.json`](package.json:1))
199 | - `build`: `tsc` (Compiles TypeScript to `dist/`).
200 | - `dev`: `ts-node-dev --respawn --transpile-only src/index.ts` (Runs server in development with auto-restart).
201 | - `start`: `node dist/index.js` (Runs compiled server).
202 | - `inspector`: `npm run build && npx @modelcontextprotocol/inspector node dist/index.js` (Runs server with MCP Inspector).
203 | - `test:unit`, `test:int`, `test:e2e`, `test`: Run respective test suites.
204 | - `lint`, `lint:fix`: Run ESLint.
205 | - `format`: Run Prettier.
206 | - `prepare`: `husky install` (Sets up Git hooks).
207 | - `commit`: `cz` (Interactive commitizen).
208 | 
209 | ### CI/CD
210 | - No explicit CI/CD pipeline configuration files (e.g., `azure-pipelines.yml`, `.github/workflows/`) were found in the file listing. If added, **REFER** to them.
211 | 
212 | ## 7. Key File Interaction Standards
213 | 
214 | - **Adding/Modifying a Tool**:
215 |     - TOUCH `src/features/[feature-name]/[tool-name]/feature.ts` (logic).
216 |     - TOUCH `src/features/[feature-name]/schemas.ts` (Zod schema).
217 |     - TOUCH `src/features/[feature-name]/tool-definitions.ts` (MCP tool definition).
218 |     - TOUCH `src/features/[feature-name]/index.ts` (export logic, update request handler and guard).
219 |     - TOUCH corresponding `*.spec.unit.ts` and `*.spec.int.ts` files.
220 | - **Adding a New Feature Module**:
221 |     - CREATE files within `src/features/[new-feature-module-name]/` as per "Functionality Implementation Standards".
222 |     - MODIFY [`src/server.ts`](src/server.ts:1) to import and register the new feature module's tools and handlers.
223 | - **Configuration Changes**:
224 |     - Environment variables: Managed via `.env` file (loaded by `dotenv` in [`src/index.ts`](src/index.ts:1)).
225 |     - TypeScript config: [`tsconfig.json`](tsconfig.json:1).
226 |     - Linting config: [`.eslintrc.json`](.eslintrc.json:1).
227 | - **Dependency Management**:
228 |     - MODIFY [`package.json`](package.json:1) to add/update dependencies.
229 |     - RUN `npm install` or `npm ci`.
230 | - **Documentation**:
231 |     - `docs/` directory is currently empty. If project documentation is added (e.g., `docs/feature-x.md`), **UPDATE** it when the corresponding feature `src/features/feature-x/` is modified.
232 |     - [`README.md`](README.md:0): UPDATE for significant high-level changes.
233 | 
234 | ## 8. AI Decision-making Standards
235 | 
236 | ### When Adding a New Azure DevOps API Interaction:
237 | 1.  **Goal**: To expose a new Azure DevOps API endpoint as an MCP tool.
238 | 2.  **Decision: New or Existing Feature?**
239 |     - IF the API relates to an existing service area (e.g., adding a new work item query type to `work-items` feature), MODIFY the existing feature module.
240 |     - ELSE (e.g., interacting with Azure DevOps Audit Logs, a new service area), CREATE a new feature module. (See "Functionality Implementation Standards").
241 | 3.  **Pattern Adherence**:
242 |     - FOLLOW the established pattern:
243 |         - `src/features/[feature]/[tool]/feature.ts` for logic.
244 |         - `src/features/[feature]/schemas.ts` for Zod schemas.
245 |         - `src/features/[feature]/tool-definitions.ts` for MCP tool definitions.
246 |         - `src/features/[feature]/index.ts` for feature-level exports, request guard (`isFeatureRequest`), and request handler (`handleFeatureRequest`).
247 |     - **Example**: To add `get_pipeline_run_logs` to `pipelines` feature:
248 |         - CREATE `src/features/pipelines/get-pipeline-run-logs/feature.ts`.
249 |         - ADD `GetPipelineRunLogsSchema` to `src/features/pipelines/schemas.ts`.
250 |         - ADD `get_pipeline_run_logs` definition to `src/features/pipelines/tool-definitions.ts`.
251 |         - UPDATE `src/features/pipelines/index.ts` to export the new function, add to `isPipelinesRequest`, and handle in `handlePipelinesRequest`.
252 | 4.  **Error Handling**:
253 |     - ALWAYS use custom error classes from `src/shared/errors/azure-devops-errors.ts` (e.g., `AzureDevOpsResourceNotFoundError`).
254 |     - WRAP external API calls in try/catch blocks.
255 |     - USE `handleResponseError` from `src/shared/errors/handle-request-error.ts` in the top-level request handler in [`src/server.ts`](src/server.ts:1) (already done for existing features). Feature-specific handlers should re-throw custom errors.
256 | 5.  **Testing**:
257 |     - ALWAYS write unit tests for the new logic in `[tool-name]/feature.spec.unit.ts`.
258 |     - ALWAYS write integration tests (NEVER mocking anything) in `[tool-name]/feature.spec.int.ts`. Prefer integration tests over unit tests.
259 | 
260 | ### When Modifying Existing Functionality:
261 | 1.  **Identify Impact**: DETERMINE all files affected by the change (logic, schemas, tool definitions, tests, potentially documentation).
262 | 2.  **Maintain Consistency**: ENSURE changes are consistent with existing patterns within that feature module.
263 | 3.  **Update Tests**: MODIFY existing tests or ADD new ones to cover the changes. ENSURE all tests pass.
264 | 4.  **Version Bumping**: For significant changes, consider if a version bump in [`package.json`](package.json:1) is warranted (usually handled by `release-please`).
265 | 
266 | ## 9. Prohibited Actions
267 | 
268 | - **DO NOT** include general development knowledge or LLM-known facts in this `shrimp-rules.md` document. This document is for project-specific operational rules for AI.
269 | - **DO NOT** explain project functionality in terms of *what it does for an end-user*. Focus on *how to modify or add to it* for an AI developer.
270 | - **DO NOT** use `any` type implicitly. [`tsconfig.json`](tsconfig.json:1) enforces `noImplicitAny: true`. [`.eslintrc.json`](.eslintrc.json:1) warns on explicit `any` (`@typescript-eslint/no-explicit-any: "warn"`), except in unit tests. MINIMIZE explicit `any`.
271 | - **DO NOT** bypass linting (`npm run lint`) or formatting (`npm run format`) checks. Code MUST adhere to these standards.
272 | - **DO NOT** commit code that fails tests (`npm test`).
273 | - **DO NOT** implement custom Azure DevOps authentication logic. USE the provided `AzureDevOpsClient` from `src/shared/auth/`.
274 | - **DO NOT** hardcode configuration values (like PATs, Org URLs, Project IDs). These should come from environment variables (see [`src/index.ts`](src/index.ts:1) `getConfig` and `src/utils/environment.ts`).
275 | - **DO NOT** directly call Azure DevOps REST APIs if a corresponding function already exists in the `azure-devops-node-api` library or in shared project code (e.g., `src/shared/api/`).
276 | - **DO NOT** modify files in `dist/` directory directly. This directory is auto-generated by `npm run build`.
277 | - **DO NOT** ignore the `project-management/` directory for understanding architectural guidelines, but DO NOT replicate its content here.
278 | - **DO NOT** use mocks within integration tests.
```

--------------------------------------------------------------------------------
/src/clients/azure-devops.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import axios, { AxiosError } from 'axios';
  2 | import { DefaultAzureCredential, AzureCliCredential } from '@azure/identity';
  3 | import {
  4 |   AzureDevOpsError,
  5 |   AzureDevOpsResourceNotFoundError,
  6 |   AzureDevOpsValidationError,
  7 |   AzureDevOpsPermissionError,
  8 | } from '../shared/errors';
  9 | import { defaultOrg, defaultProject } from '../utils/environment';
 10 | 
 11 | interface AzureDevOpsApiErrorResponse {
 12 |   message?: string;
 13 |   typeKey?: string;
 14 |   errorCode?: number;
 15 |   eventId?: number;
 16 | }
 17 | 
 18 | interface ClientOptions {
 19 |   organizationId?: string;
 20 | }
 21 | 
 22 | interface WikiCreateParameters {
 23 |   name: string;
 24 |   projectId: string;
 25 |   type: 'projectWiki' | 'codeWiki';
 26 |   repositoryId?: string;
 27 |   mappedPath?: string;
 28 |   version?: {
 29 |     version: string;
 30 |     versionType?: 'branch' | 'tag' | 'commit';
 31 |   };
 32 | }
 33 | 
 34 | interface WikiPageContent {
 35 |   content: string;
 36 | }
 37 | 
 38 | export interface WikiPageSummary {
 39 |   id: number;
 40 |   path: string;
 41 |   url?: string;
 42 |   order?: number;
 43 | }
 44 | 
 45 | interface WikiPagesBatchRequest {
 46 |   top: number;
 47 |   continuationToken?: string;
 48 | }
 49 | 
 50 | interface WikiPagesBatchResponse {
 51 |   value: WikiPageSummary[];
 52 |   continuationToken?: string;
 53 | }
 54 | 
 55 | interface PageUpdateOptions {
 56 |   comment?: string;
 57 |   versionDescriptor?: {
 58 |     version?: string;
 59 |   };
 60 | }
 61 | 
 62 | export class WikiClient {
 63 |   private baseUrl: string;
 64 |   private organizationId: string;
 65 | 
 66 |   constructor(organizationId: string) {
 67 |     this.organizationId = organizationId || defaultOrg;
 68 |     this.baseUrl = `https://dev.azure.com/${this.organizationId}`;
 69 |   }
 70 | 
 71 |   /**
 72 |    * Gets a project's ID from its name or verifies a project ID
 73 |    * @param projectNameOrId - Project name or ID
 74 |    * @returns The project ID
 75 |    */
 76 |   private async getProjectId(projectNameOrId: string): Promise<string> {
 77 |     try {
 78 |       // Try to get project details using the provided name or ID
 79 |       const url = `${this.baseUrl}/_apis/projects/${projectNameOrId}`;
 80 |       const authHeader = await getAuthorizationHeader();
 81 | 
 82 |       const response = await axios.get(url, {
 83 |         params: {
 84 |           'api-version': '7.1',
 85 |         },
 86 |         headers: {
 87 |           Authorization: authHeader,
 88 |           'Content-Type': 'application/json',
 89 |         },
 90 |       });
 91 | 
 92 |       // Return the project ID from the response
 93 |       return response.data.id;
 94 |     } catch (error) {
 95 |       const axiosError = error as AxiosError;
 96 | 
 97 |       if (axiosError.response) {
 98 |         const status = axiosError.response.status;
 99 |         const errorMessage =
100 |           typeof axiosError.response.data === 'object' &&
101 |           axiosError.response.data
102 |             ? (axiosError.response.data as AzureDevOpsApiErrorResponse)
103 |                 .message || axiosError.message
104 |             : axiosError.message;
105 | 
106 |         if (status === 404) {
107 |           throw new AzureDevOpsResourceNotFoundError(
108 |             `Project not found: ${projectNameOrId}`,
109 |           );
110 |         }
111 | 
112 |         if (status === 401 || status === 403) {
113 |           throw new AzureDevOpsPermissionError(
114 |             `Permission denied to access project: ${projectNameOrId}`,
115 |           );
116 |         }
117 | 
118 |         throw new AzureDevOpsError(
119 |           `Failed to get project details: ${errorMessage}`,
120 |         );
121 |       }
122 | 
123 |       throw new AzureDevOpsError(
124 |         `Network error when getting project details: ${axiosError.message}`,
125 |       );
126 |     }
127 |   }
128 | 
129 |   /**
130 |    * Creates a new wiki in Azure DevOps
131 |    * @param projectId - Project ID or name
132 |    * @param params - Parameters for creating the wiki
133 |    * @returns The created wiki
134 |    */
135 |   async createWiki(projectId: string, params: WikiCreateParameters) {
136 |     // Use the default project if not provided
137 |     const project = projectId || defaultProject;
138 | 
139 |     try {
140 |       // Get the actual project ID (whether the input was a name or ID)
141 |       const actualProjectId = await this.getProjectId(project);
142 | 
143 |       // Construct the URL to create the wiki
144 |       const url = `${this.baseUrl}/${project}/_apis/wiki/wikis`;
145 | 
146 |       // Get authorization header
147 |       const authHeader = await getAuthorizationHeader();
148 | 
149 |       // Make the API request
150 |       const response = await axios.post(
151 |         url,
152 |         {
153 |           name: params.name,
154 |           type: params.type,
155 |           projectId: actualProjectId,
156 |           ...(params.type === 'codeWiki' && {
157 |             repositoryId: params.repositoryId,
158 |             mappedPath: params.mappedPath,
159 |             version: params.version,
160 |           }),
161 |         },
162 |         {
163 |           params: {
164 |             'api-version': '7.1',
165 |           },
166 |           headers: {
167 |             Authorization: authHeader,
168 |             'Content-Type': 'application/json',
169 |           },
170 |         },
171 |       );
172 | 
173 |       return response.data;
174 |     } catch (error) {
175 |       const axiosError = error as AxiosError;
176 | 
177 |       // Handle specific error cases
178 |       if (axiosError.response) {
179 |         const status = axiosError.response.status;
180 |         const errorMessage =
181 |           typeof axiosError.response.data === 'object' &&
182 |           axiosError.response.data
183 |             ? (axiosError.response.data as AzureDevOpsApiErrorResponse)
184 |                 .message || axiosError.message
185 |             : axiosError.message;
186 | 
187 |         // Handle 404 Not Found
188 |         if (status === 404) {
189 |           throw new AzureDevOpsResourceNotFoundError(
190 |             `Project not found: ${projectId}`,
191 |           );
192 |         }
193 | 
194 |         // Handle 401 Unauthorized or 403 Forbidden
195 |         if (status === 401 || status === 403) {
196 |           throw new AzureDevOpsPermissionError(
197 |             `Permission denied to create wiki in project: ${projectId}`,
198 |           );
199 |         }
200 | 
201 |         // Handle validation errors
202 |         if (status === 400) {
203 |           throw new AzureDevOpsValidationError(
204 |             `Invalid wiki creation parameters: ${errorMessage}`,
205 |           );
206 |         }
207 | 
208 |         // Handle other error statuses
209 |         throw new AzureDevOpsError(`Failed to create wiki: ${errorMessage}`);
210 |       }
211 | 
212 |       // Handle network errors
213 |       throw new AzureDevOpsError(
214 |         `Network error when creating wiki: ${axiosError.message}`,
215 |       );
216 |     }
217 |   }
218 | 
219 |   /**
220 |    * Gets a wiki page's content
221 |    * @param projectId - Project ID or name
222 |    * @param wikiId - Wiki ID or name
223 |    * @param pagePath - Path of the wiki page
224 |    * @param options - Additional options like version
225 |    * @returns The wiki page content and ETag
226 |    */
227 |   async getPage(projectId: string, wikiId: string, pagePath: string) {
228 |     // Use the default project if not provided
229 |     const project = projectId || defaultProject;
230 | 
231 |     // Ensure pagePath starts with a forward slash
232 |     const normalizedPath = pagePath.startsWith('/') ? pagePath : `/${pagePath}`;
233 | 
234 |     // Construct the URL to get the wiki page
235 |     const url = `${this.baseUrl}/${project}/_apis/wiki/wikis/${wikiId}/pages`;
236 |     const params: Record<string, string> = {
237 |       'api-version': '7.1',
238 |       path: normalizedPath,
239 |     };
240 | 
241 |     try {
242 |       // Get authorization header
243 |       const authHeader = await getAuthorizationHeader();
244 | 
245 |       // Make the API request for plain text content
246 |       const response = await axios.get(url, {
247 |         params,
248 |         headers: {
249 |           Authorization: authHeader,
250 |           Accept: 'text/plain',
251 |           'Content-Type': 'application/json',
252 |         },
253 |         responseType: 'text',
254 |       });
255 | 
256 |       // Return both the content and the ETag
257 |       return {
258 |         content: response.data,
259 |         eTag: response.headers.etag?.replace(/"/g, ''), // Remove quotes from ETag
260 |       };
261 |     } catch (error) {
262 |       const axiosError = error as AxiosError;
263 | 
264 |       // Handle specific error cases
265 |       if (axiosError.response) {
266 |         const status = axiosError.response.status;
267 |         const errorMessage =
268 |           typeof axiosError.response.data === 'object' &&
269 |           axiosError.response.data
270 |             ? (axiosError.response.data as AzureDevOpsApiErrorResponse)
271 |                 .message || axiosError.message
272 |             : axiosError.message;
273 | 
274 |         // Handle 404 Not Found
275 |         if (status === 404) {
276 |           throw new AzureDevOpsResourceNotFoundError(
277 |             `Wiki page not found: ${pagePath} in wiki ${wikiId}`,
278 |           );
279 |         }
280 | 
281 |         // Handle 401 Unauthorized or 403 Forbidden
282 |         if (status === 401 || status === 403) {
283 |           throw new AzureDevOpsPermissionError(
284 |             `Permission denied to access wiki page: ${pagePath}`,
285 |           );
286 |         }
287 | 
288 |         // Handle other error statuses
289 |         throw new AzureDevOpsError(
290 |           `Failed to get wiki page: ${errorMessage} ${axiosError.response?.data}`,
291 |         );
292 |       }
293 | 
294 |       // Handle network errors
295 |       throw new AzureDevOpsError(
296 |         `Network error when getting wiki page: ${axiosError.message}`,
297 |       );
298 |     }
299 |   }
300 | 
301 |   /**
302 |    * Creates a new wiki page with the provided content
303 |    * @param content - Content for the new wiki page
304 |    * @param projectId - Project ID or name
305 |    * @param wikiId - Wiki ID or name
306 |    * @param pagePath - Path of the wiki page to create
307 |    * @param options - Additional options like comment
308 |    * @returns The created wiki page
309 |    */
310 |   async createPage(
311 |     content: string,
312 |     projectId: string,
313 |     wikiId: string,
314 |     pagePath: string,
315 |     options?: { comment?: string },
316 |   ) {
317 |     // Use the default project if not provided
318 |     const project = projectId || defaultProject;
319 | 
320 |     // Encode the page path, handling forward slashes properly
321 |     const encodedPagePath = encodeURIComponent(pagePath).replace(/%2F/g, '/');
322 | 
323 |     // Construct the URL to create the wiki page
324 |     const url = `${this.baseUrl}/${project}/_apis/wiki/wikis/${wikiId}/pages`;
325 | 
326 |     const params: Record<string, string> = {
327 |       'api-version': '7.1',
328 |       path: encodedPagePath,
329 |     };
330 | 
331 |     // Prepare the request payload
332 |     const payload: Record<string, string> = {
333 |       content,
334 |     };
335 | 
336 |     // Add comment if provided
337 |     if (options?.comment) {
338 |       payload.comment = options.comment;
339 |     }
340 | 
341 |     try {
342 |       // Get authorization header
343 |       const authHeader = await getAuthorizationHeader();
344 | 
345 |       // Make the API request
346 |       const response = await axios.put(url, payload, {
347 |         params,
348 |         headers: {
349 |           Authorization: authHeader,
350 |           'Content-Type': 'application/json',
351 |           Accept: 'application/json',
352 |         },
353 |       });
354 | 
355 |       // The ETag header contains the version
356 |       const eTag = response.headers.etag;
357 | 
358 |       // Return the page content along with metadata
359 |       return {
360 |         ...response.data,
361 |         version: eTag ? eTag.replace(/"/g, '') : undefined, // Remove quotes from ETag
362 |       };
363 |     } catch (error) {
364 |       const axiosError = error as AxiosError;
365 | 
366 |       // Handle specific error cases
367 |       if (axiosError.response) {
368 |         const status = axiosError.response.status;
369 |         const errorMessage =
370 |           typeof axiosError.response.data === 'object' &&
371 |           axiosError.response.data
372 |             ? (axiosError.response.data as AzureDevOpsApiErrorResponse)
373 |                 .message || axiosError.message
374 |             : axiosError.message;
375 | 
376 |         // Handle 404 Not Found - usually means the parent path doesn't exist
377 |         if (status === 404) {
378 |           throw new AzureDevOpsResourceNotFoundError(
379 |             `Cannot create wiki page: parent path for ${pagePath} does not exist`,
380 |           );
381 |         }
382 | 
383 |         // Handle 401 Unauthorized or 403 Forbidden
384 |         if (status === 401 || status === 403) {
385 |           throw new AzureDevOpsPermissionError(
386 |             `Permission denied to create wiki page: ${pagePath}`,
387 |           );
388 |         }
389 | 
390 |         // Handle 412 Precondition Failed - page might already exist
391 |         if (status === 412) {
392 |           throw new AzureDevOpsValidationError(
393 |             `Wiki page already exists: ${pagePath}`,
394 |           );
395 |         }
396 | 
397 |         // Handle 400 Bad Request - usually validation errors
398 |         if (status === 400) {
399 |           throw new AzureDevOpsValidationError(
400 |             `Invalid request when creating wiki page: ${errorMessage}`,
401 |           );
402 |         }
403 | 
404 |         // Handle other error statuses
405 |         throw new AzureDevOpsError(
406 |           `Failed to create wiki page: ${errorMessage}`,
407 |         );
408 |       }
409 | 
410 |       // Handle network errors
411 |       throw new AzureDevOpsError(
412 |         `Network error when creating wiki page: ${axiosError.message}`,
413 |       );
414 |     }
415 |   }
416 | 
417 |   /**
418 |    * Updates a wiki page with the provided content
419 |    * @param content - Content for the wiki page
420 |    * @param projectId - Project ID or name
421 |    * @param wikiId - Wiki ID or name
422 |    * @param pagePath - Path of the wiki page
423 |    * @param options - Additional options like comment and version
424 |    * @returns The updated wiki page
425 |    */
426 |   async updatePage(
427 |     content: WikiPageContent,
428 |     projectId: string,
429 |     wikiId: string,
430 |     pagePath: string,
431 |     options?: PageUpdateOptions,
432 |   ) {
433 |     // Use the default project if not provided
434 |     const project = projectId || defaultProject;
435 | 
436 |     // First get the current page version
437 |     let currentETag;
438 |     try {
439 |       const currentPage = await this.getPage(project, wikiId, pagePath);
440 |       currentETag = currentPage.eTag;
441 |     } catch (error) {
442 |       if (error instanceof AzureDevOpsResourceNotFoundError) {
443 |         // If page doesn't exist, we'll create it (no If-Match header needed)
444 |         currentETag = undefined;
445 |       } else {
446 |         throw error;
447 |       }
448 |     }
449 | 
450 |     // Encode the page path, handling forward slashes properly
451 |     const encodedPagePath = encodeURIComponent(pagePath).replace(/%2F/g, '/');
452 | 
453 |     // Construct the URL to update the wiki page
454 |     const url = `${this.baseUrl}/${project}/_apis/wiki/wikis/${wikiId}/pages`;
455 |     const params: Record<string, string> = {
456 |       'api-version': '7.1',
457 |       path: encodedPagePath,
458 |     };
459 | 
460 |     // Add optional comment parameter if provided
461 |     if (options?.comment) {
462 |       params.comment = options.comment;
463 |     }
464 | 
465 |     try {
466 |       // Get authorization header
467 |       const authHeader = await getAuthorizationHeader();
468 | 
469 |       // Prepare request headers
470 |       const headers: Record<string, string> = {
471 |         Authorization: authHeader,
472 |         'Content-Type': 'application/json',
473 |       };
474 | 
475 |       // Add If-Match header if we have an ETag (for updates)
476 |       if (currentETag) {
477 |         headers['If-Match'] = `"${currentETag}"`; // Wrap in quotes as required by API
478 |       }
479 | 
480 |       // Create a properly typed payload
481 |       const payload: Record<string, string> = {
482 |         content: content.content,
483 |       };
484 | 
485 |       // Make the API request
486 |       const response = await axios.put(url, payload, {
487 |         params,
488 |         headers,
489 |       });
490 | 
491 |       // The ETag header contains the version
492 |       const eTag = response.headers.etag;
493 | 
494 |       // Return the page content along with metadata
495 |       return {
496 |         ...response.data,
497 |         version: eTag ? eTag.replace(/"/g, '') : undefined, // Remove quotes from ETag
498 |         message:
499 |           response.status === 201
500 |             ? 'Page created successfully'
501 |             : 'Page updated successfully',
502 |       };
503 |     } catch (error) {
504 |       const axiosError = error as AxiosError;
505 | 
506 |       // Handle specific error cases
507 |       if (axiosError.response) {
508 |         const status = axiosError.response.status;
509 |         const errorMessage =
510 |           typeof axiosError.response.data === 'object' &&
511 |           axiosError.response.data
512 |             ? (axiosError.response.data as AzureDevOpsApiErrorResponse)
513 |                 .message || axiosError.message
514 |             : axiosError.message;
515 | 
516 |         // Handle 404 Not Found
517 |         if (status === 404) {
518 |           throw new AzureDevOpsResourceNotFoundError(
519 |             `Wiki page not found: ${pagePath} in wiki ${wikiId}`,
520 |           );
521 |         }
522 | 
523 |         // Handle 401 Unauthorized or 403 Forbidden
524 |         if (status === 401 || status === 403) {
525 |           throw new AzureDevOpsPermissionError(
526 |             `Permission denied to update wiki page: ${pagePath}`,
527 |           );
528 |         }
529 | 
530 |         // Handle 412 Precondition Failed (version conflict)
531 |         if (status === 412) {
532 |           throw new AzureDevOpsValidationError(
533 |             `Version conflict: The wiki page has been modified since you retrieved it. Please get the latest version and try again.`,
534 |           );
535 |         }
536 | 
537 |         // Handle other error statuses
538 |         throw new AzureDevOpsError(
539 |           `Failed to update wiki page: ${errorMessage}`,
540 |         );
541 |       }
542 | 
543 |       // Handle network errors
544 |       throw new AzureDevOpsError(
545 |         `Network error when updating wiki page: ${axiosError.message}`,
546 |       );
547 |     }
548 |   }
549 | 
550 |   /**
551 |    * Lists wiki pages from a wiki using the Pages Batch API
552 |    * @param projectId - Project ID or name
553 |    * @param wikiId - Wiki ID or name
554 |    * @returns Array of wiki page summaries sorted by order then path
555 |    */
556 |   async listWikiPages(
557 |     projectId: string,
558 |     wikiId: string,
559 |   ): Promise<WikiPageSummary[]> {
560 |     // Use the default project if not provided
561 |     const project = projectId || defaultProject;
562 | 
563 |     // Construct the URL for the Pages Batch API
564 |     const url = `${this.baseUrl}/${project}/_apis/wiki/wikis/${wikiId}/pagesbatch`;
565 | 
566 |     const allPages: WikiPageSummary[] = [];
567 |     let continuationToken: string | undefined;
568 | 
569 |     try {
570 |       // Get authorization header
571 |       const authHeader = await getAuthorizationHeader();
572 | 
573 |       do {
574 |         // Prepare the request body
575 |         const requestBody: WikiPagesBatchRequest = {
576 |           top: 100,
577 |           ...(continuationToken && { continuationToken }),
578 |         };
579 | 
580 |         // Make the API request
581 |         const response = await axios.post<WikiPagesBatchResponse>(
582 |           url,
583 |           requestBody,
584 |           {
585 |             params: {
586 |               'api-version': '7.1',
587 |             },
588 |             headers: {
589 |               Authorization: authHeader,
590 |               'Content-Type': 'application/json',
591 |             },
592 |           },
593 |         );
594 | 
595 |         // Add the pages from this batch to our collection
596 |         if (response.data.value && Array.isArray(response.data.value)) {
597 |           allPages.push(...response.data.value);
598 |         }
599 | 
600 |         // Update continuation token for next iteration
601 |         continuationToken = response.data.continuationToken;
602 |       } while (continuationToken);
603 | 
604 |       // Sort results by order then path
605 |       return allPages.sort((a, b) => {
606 |         // Handle optional order field
607 |         const aOrder = a.order ?? Number.MAX_SAFE_INTEGER;
608 |         const bOrder = b.order ?? Number.MAX_SAFE_INTEGER;
609 | 
610 |         if (aOrder !== bOrder) {
611 |           return aOrder - bOrder;
612 |         }
613 |         return a.path.localeCompare(b.path);
614 |       });
615 |     } catch (error) {
616 |       const axiosError = error as AxiosError;
617 | 
618 |       // Handle specific error cases
619 |       if (axiosError.response) {
620 |         const status = axiosError.response.status;
621 |         const errorMessage =
622 |           typeof axiosError.response.data === 'object' &&
623 |           axiosError.response.data
624 |             ? (axiosError.response.data as AzureDevOpsApiErrorResponse)
625 |                 .message || axiosError.message
626 |             : axiosError.message;
627 | 
628 |         // Handle 404 Not Found
629 |         if (status === 404) {
630 |           throw new AzureDevOpsResourceNotFoundError(
631 |             `Wiki not found: ${wikiId} in project ${projectId}`,
632 |           );
633 |         }
634 | 
635 |         // Handle 401 Unauthorized or 403 Forbidden
636 |         if (status === 401 || status === 403) {
637 |           throw new AzureDevOpsPermissionError(
638 |             `Permission denied to list wiki pages in wiki: ${wikiId}`,
639 |           );
640 |         }
641 | 
642 |         // Handle other error statuses
643 |         throw new AzureDevOpsError(
644 |           `Failed to list wiki pages: ${errorMessage}`,
645 |         );
646 |       }
647 | 
648 |       // Handle network errors
649 |       throw new AzureDevOpsError(
650 |         `Network error when listing wiki pages: ${axiosError.message}`,
651 |       );
652 |     }
653 |   }
654 | }
655 | 
656 | /**
657 |  * Creates a Wiki client for Azure DevOps operations
658 |  * @param options - Options for creating the client
659 |  * @returns A Wiki client instance
660 |  */
661 | export async function getWikiClient(
662 |   options: ClientOptions,
663 | ): Promise<WikiClient> {
664 |   const { organizationId } = options;
665 | 
666 |   return new WikiClient(organizationId || defaultOrg);
667 | }
668 | 
669 | /**
670 |  * Get the authorization header for Azure DevOps API requests
671 |  * @returns The authorization header
672 |  */
673 | export async function getAuthorizationHeader(): Promise<string> {
674 |   try {
675 |     // For PAT authentication, we can construct the header directly
676 |     if (
677 |       process.env.AZURE_DEVOPS_AUTH_METHOD?.toLowerCase() === 'pat' &&
678 |       process.env.AZURE_DEVOPS_PAT
679 |     ) {
680 |       // For PAT auth, we can construct the Basic auth header directly
681 |       const token = process.env.AZURE_DEVOPS_PAT;
682 |       const base64Token = Buffer.from(`:${token}`).toString('base64');
683 |       return `Basic ${base64Token}`;
684 |     }
685 | 
686 |     // For Azure Identity / Azure CLI auth, we need to get a token
687 |     // using the Azure DevOps resource ID
688 |     // Choose the appropriate credential based on auth method
689 |     const credential =
690 |       process.env.AZURE_DEVOPS_AUTH_METHOD?.toLowerCase() === 'azure-cli'
691 |         ? new AzureCliCredential()
692 |         : new DefaultAzureCredential();
693 | 
694 |     // Azure DevOps resource ID for token acquisition
695 |     const AZURE_DEVOPS_RESOURCE_ID = '499b84ac-1321-427f-aa17-267ca6975798';
696 | 
697 |     // Get token for Azure DevOps
698 |     const token = await credential.getToken(
699 |       `${AZURE_DEVOPS_RESOURCE_ID}/.default`,
700 |     );
701 | 
702 |     if (!token || !token.token) {
703 |       throw new Error('Failed to acquire token for Azure DevOps');
704 |     }
705 | 
706 |     return `Bearer ${token.token}`;
707 |   } catch (error) {
708 |     throw new AzureDevOpsValidationError(
709 |       `Failed to get authorization header: ${error instanceof Error ? error.message : String(error)}`,
710 |     );
711 |   }
712 | }
713 | 
```

--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Changelog
  2 | 
  3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
  4 | 
  5 | ## [0.1.42](https://github.com/Tiberriver256/mcp-server-azure-devops/compare/mcp-server-azure-devops-v0.1.41...mcp-server-azure-devops-v0.1.42) (2025-07-15)
  6 | 
  7 | 
  8 | ### Features
  9 | 
 10 | * implement human-readable string enums for Azure DevOps API responses ([8168bcb](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/8168bcbe8e4957e9632927f57ecbe9632c911735))
 11 | 
 12 | ## [0.1.41](https://github.com/Tiberriver256/mcp-server-azure-devops/compare/mcp-server-azure-devops-v0.1.40...mcp-server-azure-devops-v0.1.41) (2025-07-14)
 13 | 
 14 | 
 15 | ### Features
 16 | 
 17 | * **pull-requests:** enhance get_pull_request_comments response with … ([#229](https://github.com/Tiberriver256/mcp-server-azure-devops/issues/229)) ([6997a04](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/6997a04e92b4fe453354b8fd9f0f25c974fcad2b))
 18 | 
 19 | 
 20 | ### Bug Fixes
 21 | 
 22 | * **work-items:** make expand enum compatible with Gemini CLI ([#240](https://github.com/Tiberriver256/mcp-server-azure-devops/issues/240)) ([ac1dcac](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/ac1dcace4cd6f63d5decd4820307b52a4d0d431d))
 23 | 
 24 | ## [0.1.40](https://github.com/Tiberriver256/mcp-server-azure-devops/compare/mcp-server-azure-devops-v0.1.39...mcp-server-azure-devops-v0.1.40) (2025-06-20)
 25 | 
 26 | 
 27 | ### Bug Fixes
 28 | 
 29 | * simplify listWikiPages API by removing unused parameters ([fff7238](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/fff72384f69433942ee8439de0dda90d7fc85c38))
 30 | 
 31 | ## [0.1.39](https://github.com/Tiberriver256/mcp-server-azure-devops/compare/mcp-server-azure-devops-v0.1.38...mcp-server-azure-devops-v0.1.39) (2025-06-03)
 32 | 
 33 | 
 34 | ### Features
 35 | 
 36 | * add listWikiPages functionality to Azure DevOps wiki client ([bb9ddc0](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/bb9ddc077e80be0caeda106a7e75dc336a62c9ae))
 37 | * implement create_wiki_page feature for Azure DevOps wiki API integration ([#225](https://github.com/Tiberriver256/mcp-server-azure-devops/issues/225)) ([7e3294d](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/7e3294d1f1b6e82d5ca34cf86f3eaa51579dad02))
 38 | 
 39 | 
 40 | ### Bug Fixes
 41 | 
 42 | * enhanced the get-pull-request-comments tool to include path and line number ([f6017e5](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/f6017e5dd352b63b189e761c9cf27d103dd24b9d))
 43 | * remove uuid() validator to resolve unknown format error ([b251252](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/b251252c7c455ee11d8076380b70a546ebf40a6e))
 44 | 
 45 | ## [0.1.38](https://github.com/Tiberriver256/mcp-server-azure-devops/compare/mcp-server-azure-devops-v0.1.37...mcp-server-azure-devops-v0.1.38) (2025-05-25)
 46 | 
 47 | 
 48 | ### Bug Fixes
 49 | 
 50 | * improve org name extraction from url ([496abc7](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/496abc7a9c5fd0867cf8484f8c33c47a3cc42edf))
 51 | 
 52 | ## [0.1.37](https://github.com/Tiberriver256/mcp-server-azure-devops/compare/mcp-server-azure-devops-v0.1.36...mcp-server-azure-devops-v0.1.37) (2025-05-14)
 53 | 
 54 | 
 55 | ### Bug Fixes
 56 | 
 57 | * get_me support for visualstudio.com urls ([ffd3c8a](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/ffd3c8a34bcee1856911e5eed7b719d524c25fef))
 58 | 
 59 | ## [0.1.36](https://github.com/Tiberriver256/mcp-server-azure-devops/compare/mcp-server-azure-devops-v0.1.35...mcp-server-azure-devops-v0.1.36) (2025-05-07)
 60 | 
 61 | 
 62 | ### Bug Fixes
 63 | 
 64 | * implement pagination for list-pull-requests to prevent infinite loop ([#196](https://github.com/Tiberriver256/mcp-server-azure-devops/issues/196)) ([e3d7f32](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/e3d7f321f11241bd7b45a4f0e6810509cc8c01c1))
 65 | 
 66 | ## [0.1.35](https://github.com/Tiberriver256/mcp-server-azure-devops/compare/mcp-server-azure-devops-v0.1.34...mcp-server-azure-devops-v0.1.35) (2025-05-07)
 67 | 
 68 | 
 69 | ### Bug Fixes
 70 | 
 71 | * update creatorId and reviewerId to require UUIDs instead of allowing emails ([09e82ef](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/09e82ef5e7dfdcd07d1851450ea8c488ea8bb82a))
 72 | * use default project for code search when no projectId is specified ([#202](https://github.com/Tiberriver256/mcp-server-azure-devops/issues/202)) ([3bf118f](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/3bf118f45b4222bbfaf888deb67d546b87afc2fe))
 73 | 
 74 | ## [0.1.34](https://github.com/Tiberriver256/mcp-server-azure-devops/compare/mcp-server-azure-devops-v0.1.33...mcp-server-azure-devops-v0.1.34) (2025-05-02)
 75 | 
 76 | 
 77 | ### Features
 78 | 
 79 | * add update-pull-request tool to tool-definitions and implement reviewer management ([b7b5398](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/b7b539813baadb84e15022fdb93d24f440491d94))
 80 | 
 81 | ## [0.1.33](https://github.com/Tiberriver256/mcp-server-azure-devops/compare/mcp-server-azure-devops-v0.1.32...mcp-server-azure-devops-v0.1.33) (2025-04-28)
 82 | 
 83 | 
 84 | ### Bug Fixes
 85 | 
 86 | * add guidance for HTML formatting in multi-line text fields ([#188](https://github.com/Tiberriver256/mcp-server-azure-devops/issues/188)) ([25751cd](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/25751cd0d7cb8919a7bca80d0796784935f0fbed)), closes [#179](https://github.com/Tiberriver256/mcp-server-azure-devops/issues/179)
 87 | 
 88 | ## [0.1.32](https://github.com/Tiberriver256/mcp-server-azure-devops/compare/mcp-server-azure-devops-v0.1.31...mcp-server-azure-devops-v0.1.32) (2025-04-26)
 89 | 
 90 | 
 91 | ### Features
 92 | 
 93 | * add get_pull_request_comments ([2b7fb3a](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/2b7fb3a885466c633d2d2dfdd8906cc9573483d9))
 94 | * add_pull_request_comment ([1df6161](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/1df616161835e11c0039bc344ccdc57742f79507))
 95 | 
 96 | ## [0.1.31](https://github.com/Tiberriver256/mcp-server-azure-devops/compare/mcp-server-azure-devops-v0.1.30...mcp-server-azure-devops-v0.1.31) (2025-04-23)
 97 | 
 98 | 
 99 | ### Features
100 | 
101 | * **pull-requests:** implement list-pull-requests functionality ([3f8cac4](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/3f8cac448e2adacaddeb069bc0116b8526577624))
102 | * **wikis:** add create and update wiki functionalities ([27edd6d](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/27edd6d7786748548f4a0123ff19be43b30265c4))
103 | 
104 | 
105 | ### Bug Fixes
106 | 
107 | * **pull-requests:** update repository name environment variable ([d2fde5f](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/d2fde5f94280f08056f94b613a673d4bbe9c0192))
108 | 
109 | ## [0.1.30](https://github.com/Tiberriver256/mcp-server-azure-devops/compare/mcp-server-azure-devops-v0.1.29...mcp-server-azure-devops-v0.1.30) (2025-04-21)
110 | 
111 | 
112 | ### Features
113 | 
114 | * **wikis:** implement `get_wiki_page` tool ([7ba5fd7](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/7ba5fd7830fefa17c014aa2b80f0bad04d8fcbf7))
115 | * **wikis:** implement `get_wikis` tool ([3120479](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/3120479b5c31bfaeb50a507791056066f33b6534))
116 | 
117 | ## [0.1.29](https://github.com/Tiberriver256/mcp-server-azure-devops/compare/mcp-server-azure-devops-v0.1.28...mcp-server-azure-devops-v0.1.29) (2025-04-19)
118 | 
119 | 
120 | ### Features
121 | 
122 | * **pipelines:** implement trigger-pipeline functionality ([e9ba71b](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/e9ba71bfeb2c3a2dc0e1e314698a453d5995d099))
123 | 
124 | ## [0.1.28](https://github.com/Tiberriver256/mcp-server-azure-devops/compare/mcp-server-azure-devops-v0.1.27...mcp-server-azure-devops-v0.1.28) (2025-04-17)
125 | 
126 | 
127 | ### Features
128 | 
129 | * **pipeline:** implement get-pipeline functionality ([#166](https://github.com/Tiberriver256/mcp-server-azure-devops/issues/166)) ([e307340](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/e3073401e141b566191be16ed4f9b7925c2849eb))
130 | 
131 | ## [0.1.27](https://github.com/Tiberriver256/mcp-server-azure-devops/compare/mcp-server-azure-devops-v0.1.26...mcp-server-azure-devops-v0.1.27) (2025-04-16)
132 | 
133 | 
134 | ### Features
135 | 
136 | * **pipeline:** implement list-pipelines ([#161](https://github.com/Tiberriver256/mcp-server-azure-devops/issues/161)) ([89ce473](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/89ce4732ba754632540ffb45ceae323f9675c023)), closes [#94](https://github.com/Tiberriver256/mcp-server-azure-devops/issues/94)
137 | 
138 | ## [0.1.26](https://github.com/Tiberriver256/mcp-server-azure-devops/compare/mcp-server-azure-devops-v0.1.25...mcp-server-azure-devops-v0.1.26) (2025-04-15)
139 | 
140 | 
141 | ### Features
142 | 
143 | * **getWorkItem:** enhance get_work_item to include all available fields ([3810660](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/38106600f04842a44e5e5b6e824716ebb6f69e61))
144 | * support default project and organization in all tools ([5beca06](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/5beca063057bdbc2dd869c865fb01e0d311c8917))
145 | 
146 | 
147 | ### Bug Fixes
148 | 
149 | * return actual field information from get_project_details tool ([64a030a](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/64a030a8c14fd1f9e7f871ae409f0dded23dbe98))
150 | 
151 | ## [0.1.25](https://github.com/Tiberriver256/mcp-server-azure-devops/compare/mcp-server-azure-devops-v0.1.24...mcp-server-azure-devops-v0.1.25) (2025-04-11)
152 | 
153 | 
154 | ### Features
155 | 
156 | * create pull request ([ab9c255](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/ab9c2554ea82a497dead8131a6479ba6fe7c5ba8))
157 | 
158 | ## [0.1.24](https://github.com/Tiberriver256/mcp-server-azure-devops/compare/mcp-server-azure-devops-v0.1.23...mcp-server-azure-devops-v0.1.24) (2025-04-10)
159 | 
160 | 
161 | ### Bug Fixes
162 | 
163 | * add missing minimatch module ([ee1ffa3](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/ee1ffa34afb0da9cdac31da140c17dbd9c589c2b))
164 | 
165 | ## [0.1.23](https://github.com/Tiberriver256/mcp-server-azure-devops/compare/mcp-server-azure-devops-v0.1.22...mcp-server-azure-devops-v0.1.23) (2025-04-10)
166 | 
167 | 
168 | ### Features
169 | 
170 | * **repositories:** add get_all_repositories_tree tool for viewing multi-repository file structure ([adbe206](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/adbe206300d55ba06063c675492b3a8153b688f7))
171 | * support default project and organization in all tools ([96d61bd](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/96d61bd1098146dfafd1faf7dade1a37725cd7b7))
172 | 
173 | ## [0.1.22](https://github.com/Tiberriver256/mcp-server-azure-devops/compare/mcp-server-azure-devops-v0.1.21...mcp-server-azure-devops-v0.1.22) (2025-04-08)
174 | 
175 | 
176 | ### Bug Fixes
177 | 
178 | * allow parameterless tools to be called without arguments ([9ce88c3](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/9ce88c3afd4454b8a65392a98e7e2ffb45192584))
179 | 
180 | ## [0.1.21](https://github.com/Tiberriver256/mcp-server-azure-devops/compare/mcp-server-azure-devops-v0.1.20...mcp-server-azure-devops-v0.1.21) (2025-04-08)
181 | 
182 | 
183 | ### Features
184 | 
185 | * add get-file-content feature to access repository content ([a282f75](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/a282f75383ffc362e5b2d1ecbccebb0047e21571))
186 | * restore get_file_content tool and update documentation ([f71013a](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/f71013a962fb5fbe5d121eaf7f1901e58cf70482))
187 | 
188 | ## [0.1.20](https://github.com/Tiberriver256/mcp-server-azure-devops/compare/mcp-server-azure-devops-v0.1.19...mcp-server-azure-devops-v0.1.20) (2025-04-06)
189 | 
190 | 
191 | ### Bug Fixes
192 | 
193 | * add explicit permissions to workflow file ([ae85b95](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/ae85b953d2467538e42d8c6853b93e1af3c8ed51))
194 | * refine WIQL query in integration test ([eb32e43](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/eb32e43a9064485d29661bcae99a987e3b863464))
195 | * remove schema validation for parameterless tools ([031a71d](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/031a71d71083649216e5b67eb6d67c18c78702bd))
196 | 
197 | ## [0.1.19](https://github.com/Tiberriver256/mcp-server-azure-devops/compare/mcp-server-azure-devops-v0.1.18...mcp-server-azure-devops-v0.1.19) (2025-04-05)
198 | 
199 | 
200 | ### Bug Fixes
201 | 
202 | * package.json & package-lock.json to reduce vulnerabilities ([2fb1e72](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/2fb1e725120edc75c9897bc81f57381c20ad880a))
203 | 
204 | ## [0.1.18](https://github.com/Tiberriver256/mcp-server-azure-devops/compare/mcp-server-azure-devops-v0.1.17...mcp-server-azure-devops-v0.1.18) (2025-04-05)
205 | 
206 | 
207 | ### Bug Fixes
208 | 
209 | * getMe profile bug ([ceca909](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/ceca909beaa74b0dd150ce1688a498281fd0b9e8))
210 | 
211 | ## [0.1.17](https://github.com/Tiberriver256/mcp-server-azure-devops/compare/mcp-server-azure-devops-v0.1.16...mcp-server-azure-devops-v0.1.17) (2025-04-05)
212 | 
213 | 
214 | ### Features
215 | 
216 | * implement get_me tool ([2a3849d](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/2a3849da063f6ce0877dd672992a8bc19f88230e))
217 | 
218 | ## [0.1.16](https://github.com/Tiberriver256/mcp-server-azure-devops/compare/mcp-server-azure-devops-v0.1.15...mcp-server-azure-devops-v0.1.16) (2025-04-05)
219 | 
220 | 
221 | ### Features
222 | 
223 | * limit search results to 10 when includeContent is true ([827e4e6](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/827e4e65be353125f5ae595b7e68d80f614f8c07))
224 | * make projectId optional in search features for organization-wide search ([1ca1e0e](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/1ca1e0e146bf880d367078b02a2ddaebf6f54a2a))
225 | 
226 | 
227 | ### Bug Fixes
228 | 
229 | * correct [Object Object] display in search_code includeContent ([bdabd6b](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/bdabd6bbeb3f60347c37499bdcb621f5c206dfe0))
230 | * resolve parameter conflict in getItemContent function ([38d624c](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/38d624c10dcfad26bab6d04a9290ad05097f5052))
231 | * simplify content handling in search_code to properly process ReadableStream ([136a90a](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/136a90a94f446e2c4227d87286b8d71ef8223212))
232 | 
233 | 
234 | ### Performance Improvements
235 | 
236 | * optimize git hooks with lint-staged ([ba953d8](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/ba953d84706893d56a82573c8d9e8ecdf3b09591))
237 | 
238 | ## [0.1.15](https://github.com/Tiberriver256/mcp-server-azure-devops/compare/mcp-server-azure-devops-v0.1.14...mcp-server-azure-devops-v0.1.15) (2025-04-02)
239 | 
240 | 
241 | ### Bug Fixes
242 | 
243 | * search_work_items authentication with Azure Identity ([cdb2e72](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/cdb2e722ee3abf6be465adcad7dc294f7c623103))
244 | 
245 | ## [0.1.14](https://github.com/Tiberriver256/mcp-server-azure-devops/compare/mcp-server-azure-devops-v0.1.13...mcp-server-azure-devops-v0.1.14) (2025-04-02)
246 | 
247 | 
248 | ### Bug Fixes
249 | 
250 | * add zod-to-json-schema dependency and remove unused packages from package-lock.json ([c9c117f](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/c9c117fd388e228c1116d9249698d931557877b7))
251 | 
252 | ## [0.1.13](https://github.com/Tiberriver256/mcp-server-azure-devops/compare/mcp-server-azure-devops-v0.1.12...mcp-server-azure-devops-v0.1.13) (2025-04-02)
253 | 
254 | 
255 | ### Features
256 | 
257 | * add 'expand' option to get_work_item ([6bee365](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/6bee365d9b37f7e197eaff03065e713ab0ee1c5f))
258 | * Add npm publish to release.yml ([50d0368](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/50d0368c090adc39a9b3ece67d198cabcd18c6ce))
259 | * add pre-commit hook for prettier and eslint ([1b4ddff](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/1b4ddff90e3c3ab9954d041398d224f03c632f63))
260 | * enhance GitHub release notes with changelog content ([2fb275d](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/2fb275d38acbc9c092584573a549466ccd5482bc))
261 | * implement automated release workflow ([9e5a5df](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/9e5a5dfacdd87ca933ed02efbd0aa8035239332d))
262 | * implement get_project_details core functionality ([6d93d98](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/6d93d9820c4bd3ce8bc257d05ff04b39d1370a19)), closes [#101](https://github.com/Tiberriver256/mcp-server-azure-devops/issues/101)
263 | * implement get_repository_details core functionality ([dcef80b](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/dcef80b922ef338f6d3704ab30f59c1b126c70ee))
264 | * implement manage work item link handler ([72cd641](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/72cd6419cf804eb0d72d5ba7763ad5b46bc35650))
265 | * implement search_wiki handler with tests ([286598c](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/286598c47052ade3b6a524938046b3e3b9341b3a))
266 | * implement search_work_items handler with tests ([e244658](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/e2446587e6f82fb7e2dbfe47d2d034ecfdfc3189))
267 | * **search:** add code search functionality for Azure DevOps repos ([0680102](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/068010236b10d8ed444ec01bd6820b27c5c9dcdc))
268 | 
269 | 
270 | ### Bug Fixes
271 | 
272 | * add bin field to make package executable with npx ([2d3d5fa](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/2d3d5fa31a9ba741c4a85d7ef21d72ff46270695))
273 | * add build step to workflow and ensure dist files are included in package ([6e12d3c](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/6e12d3ca666937c7b24c7c5d8b161fbb8e34798c))
274 | * add parent-child relationship support for createWorkItem ([31d5efe](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/31d5efef49c162772e64eabd1e4012d8143dc270))
275 | * add tag_name parameter to GitHub release action ([68cfa43](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/68cfa43839c5975cdf9c2ec8a5348ace6138d1c2))
276 | * improve cross-platform CLI compatibility for Windows ([0f6ed3f](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/0f6ed3fe7c72ba63ec5485047ce52e06278457ab))
277 | * make AZURE_DEVOPS_AUTH_METHOD parameter case-insensitive ([9bbf53f](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/9bbf53ffcc1a9170e6ba038fee182da0621be777))
278 | * only request max 200 by default ([296de35](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/296de3584346bd05c14dec3b39dff9a5ec0036a5))
279 | * resolve npm publish authentication and package content issues ([96e91d0](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/96e91d04ec620ad77fc35fea31c2b7795fb73d9e))
280 | * restore tests/setup.ts to fix test suite ([5e23eab](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/5e23eab1228f3949c431f1b8509ad5fbf829e528))
281 | * revert to direct execution of index.js to fix main module detection ([82efa90](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/82efa90852f56db3a0b028ec50eb5230072da88a))
282 | * Typo in release.yaml workflow ([e0de15f](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/e0de15fd220ef2141466cf0530383921ed99253d))
283 | 
284 | ## [0.1.12](https://github.com/Tiberriver256/mcp-server-azure-devops/compare/mcp-server-azure-devops-v0.1.11...mcp-server-azure-devops-v0.1.12) (2025-04-02)
285 | 
286 | 
287 | ### Features
288 | 
289 | * add 'expand' option to get_work_item ([6bee365](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/6bee365d9b37f7e197eaff03065e713ab0ee1c5f))
290 | * Add npm publish to release.yml ([50d0368](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/50d0368c090adc39a9b3ece67d198cabcd18c6ce))
291 | * add pre-commit hook for prettier and eslint ([1b4ddff](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/1b4ddff90e3c3ab9954d041398d224f03c632f63))
292 | * enhance GitHub release notes with changelog content ([2fb275d](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/2fb275d38acbc9c092584573a549466ccd5482bc))
293 | * implement automated release workflow ([9e5a5df](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/9e5a5dfacdd87ca933ed02efbd0aa8035239332d))
294 | * implement get_project_details core functionality ([6d93d98](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/6d93d9820c4bd3ce8bc257d05ff04b39d1370a19)), closes [#101](https://github.com/Tiberriver256/mcp-server-azure-devops/issues/101)
295 | * implement get_repository_details core functionality ([dcef80b](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/dcef80b922ef338f6d3704ab30f59c1b126c70ee))
296 | * implement manage work item link handler ([72cd641](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/72cd6419cf804eb0d72d5ba7763ad5b46bc35650))
297 | * implement search_wiki handler with tests ([286598c](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/286598c47052ade3b6a524938046b3e3b9341b3a))
298 | * implement search_work_items handler with tests ([e244658](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/e2446587e6f82fb7e2dbfe47d2d034ecfdfc3189))
299 | * **search:** add code search functionality for Azure DevOps repos ([0680102](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/068010236b10d8ed444ec01bd6820b27c5c9dcdc))
300 | 
301 | 
302 | ### Bug Fixes
303 | 
304 | * add bin field to make package executable with npx ([2d3d5fa](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/2d3d5fa31a9ba741c4a85d7ef21d72ff46270695))
305 | * add build step to workflow and ensure dist files are included in package ([6e12d3c](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/6e12d3ca666937c7b24c7c5d8b161fbb8e34798c))
306 | * add parent-child relationship support for createWorkItem ([31d5efe](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/31d5efef49c162772e64eabd1e4012d8143dc270))
307 | * add tag_name parameter to GitHub release action ([68cfa43](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/68cfa43839c5975cdf9c2ec8a5348ace6138d1c2))
308 | * improve cross-platform CLI compatibility for Windows ([0f6ed3f](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/0f6ed3fe7c72ba63ec5485047ce52e06278457ab))
309 | * make AZURE_DEVOPS_AUTH_METHOD parameter case-insensitive ([9bbf53f](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/9bbf53ffcc1a9170e6ba038fee182da0621be777))
310 | * only request max 200 by default ([296de35](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/296de3584346bd05c14dec3b39dff9a5ec0036a5))
311 | * resolve npm publish authentication and package content issues ([96e91d0](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/96e91d04ec620ad77fc35fea31c2b7795fb73d9e))
312 | * restore tests/setup.ts to fix test suite ([5e23eab](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/5e23eab1228f3949c431f1b8509ad5fbf829e528))
313 | * revert to direct execution of index.js to fix main module detection ([82efa90](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/82efa90852f56db3a0b028ec50eb5230072da88a))
314 | * Typo in release.yaml workflow ([e0de15f](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/e0de15fd220ef2141466cf0530383921ed99253d))
315 | 
316 | ### [0.1.11](https://github.com/Tiberriver256/mcp-server-azure-devops/compare/v0.1.10...v0.1.11) (2025-04-01)
317 | 
318 | 
319 | ### Features
320 | 
321 | * **search:** add code search functionality for Azure DevOps repos ([0680102](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/068010236b10d8ed444ec01bd6820b27c5c9dcdc))
322 | 
323 | ### [0.1.10](https://github.com/Tiberriver256/mcp-server-azure-devops/compare/v0.1.9...v0.1.10) (2025-04-01)
324 | 
325 | 
326 | ### Features
327 | 
328 | * add 'expand' option to get_work_item ([6bee365](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/6bee365d9b37f7e197eaff03065e713ab0ee1c5f))
329 | 
330 | 
331 | ### Bug Fixes
332 | 
333 | * only request max 200 by default ([296de35](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/296de3584346bd05c14dec3b39dff9a5ec0036a5))
334 | 
335 | ### [0.1.9](https://github.com/Tiberriver256/mcp-server-azure-devops/compare/v0.1.8...v0.1.9) (2025-03-31)
336 | 
337 | 
338 | ### Features
339 | 
340 | * add pre-commit hook for prettier and eslint ([1b4ddff](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/1b4ddff90e3c3ab9954d041398d224f03c632f63))
341 | * implement manage work item link handler ([72cd641](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/72cd6419cf804eb0d72d5ba7763ad5b46bc35650))
342 | 
343 | 
344 | ### Bug Fixes
345 | 
346 | * add parent-child relationship support for createWorkItem ([31d5efe](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/31d5efef49c162772e64eabd1e4012d8143dc270))
347 | * make AZURE_DEVOPS_AUTH_METHOD parameter case-insensitive ([9bbf53f](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/9bbf53ffcc1a9170e6ba038fee182da0621be777))
348 | * restore tests/setup.ts to fix test suite ([5e23eab](https://github.com/Tiberriver256/mcp-server-azure-devops/commit/5e23eab1228f3949c431f1b8509ad5fbf829e528))
349 | 
350 | ### [0.1.8](https://github.com/Tiberriver256/azure-devops-mcp/compare/v0.1.7...v0.1.8) (2025-03-26)
351 | 
352 | 
353 | ### Bug Fixes
354 | 
355 | * revert to direct execution of index.js to fix main module detection ([82efa90](https://github.com/Tiberriver256/azure-devops-mcp/commit/82efa90852f56db3a0b028ec50eb5230072da88a))
356 | 
357 | ### [0.1.7](https://github.com/Tiberriver256/azure-devops-mcp/compare/v0.1.6...v0.1.7) (2025-03-26)
358 | 
359 | 
360 | ### Bug Fixes
361 | 
362 | * add build step to workflow and ensure dist files are included in package ([6e12d3c](https://github.com/Tiberriver256/azure-devops-mcp/commit/6e12d3ca666937c7b24c7c5d8b161fbb8e34798c))
363 | 
364 | ### [0.1.6](https://github.com/Tiberriver256/azure-devops-mcp/compare/v0.1.5...v0.1.6) (2025-03-26)
365 | 
366 | 
367 | ### Bug Fixes
368 | 
369 | * improve cross-platform CLI compatibility for Windows ([0f6ed3f](https://github.com/Tiberriver256/azure-devops-mcp/commit/0f6ed3fe7c72ba63ec5485047ce52e06278457ab))
370 | 
371 | ### [0.1.5](https://github.com/Tiberriver256/azure-devops-mcp/compare/v0.1.4...v0.1.5) (2025-03-26)
372 | 
373 | 
374 | ### Bug Fixes
375 | 
376 | * add bin field to make package executable with npx ([2d3d5fa](https://github.com/Tiberriver256/azure-devops-mcp/commit/2d3d5fa31a9ba741c4a85d7ef21d72ff46270695))
377 | 
378 | ### [0.1.4](https://github.com/Tiberriver256/azure-devops-mcp/compare/v0.1.3...v0.1.4) (2025-03-26)
379 | 
380 | 
381 | ### Bug Fixes
382 | 
383 | * resolve npm publish authentication and package content issues ([96e91d0](https://github.com/Tiberriver256/azure-devops-mcp/commit/96e91d04ec620ad77fc35fea31c2b7795fb73d9e))
384 | 
385 | ### [0.1.3](https://github.com/Tiberriver256/azure-devops-mcp/compare/v0.1.2...v0.1.3) (2025-03-26)
386 | 
387 | 
388 | ### Features
389 | 
390 | * Add npm publish to release.yml ([50d0368](https://github.com/Tiberriver256/azure-devops-mcp/commit/50d0368c090adc39a9b3ece67d198cabcd18c6ce))
391 | 
392 | 
393 | ### Bug Fixes
394 | 
395 | * Typo in release.yaml workflow ([e0de15f](https://github.com/Tiberriver256/azure-devops-mcp/commit/e0de15fd220ef2141466cf0530383921ed99253d))
396 | 
397 | ### [0.1.2](https://github.com/Tiberriver256/azure-devops-mcp/compare/v0.1.1...v0.1.2) (2025-03-26)
398 | 
399 | 
400 | ### Bug Fixes
401 | 
402 | * add tag_name parameter to GitHub release action ([68cfa43](https://github.com/Tiberriver256/azure-devops-mcp/commit/68cfa43839c5975cdf9c2ec8a5348ace6138d1c2))
403 | 
404 | ### 0.1.1 (2025-03-26)
405 | 
406 | 
407 | ### Features
408 | 
409 | * enhance GitHub release notes with changelog content ([2fb275d](https://github.com/Tiberriver256/azure-devops-mcp/commit/2fb275d38acbc9c092584573a549466ccd5482bc))
410 | * implement automated release workflow ([9e5a5df](https://github.com/Tiberriver256/azure-devops-mcp/commit/9e5a5dfacdd87ca933ed02efbd0aa8035239332d))
411 | 
412 | ## 0.1.0 (2025-03-26)
413 | 
414 | 
415 | ### Features
416 | 
417 | * enhance GitHub release notes with changelog content ([dcaf554](https://github.com/Tiberriver256/azure-devops-mcp/commit/dcaf5542fc08cbb9bd665623d305ae7879758f4e))
418 | * implement automated release workflow ([6fbf41e](https://github.com/Tiberriver256/azure-devops-mcp/commit/6fbf41e5a52c4db054355d4aced33744f6b1a6eb))
419 | 
```

--------------------------------------------------------------------------------
/src/features/search/search-code/feature.spec.unit.ts:
--------------------------------------------------------------------------------

```typescript
   1 | import axios from 'axios';
   2 | import { searchCode } from './feature';
   3 | import { WebApi } from 'azure-devops-node-api';
   4 | import { AzureDevOpsError } from '../../../shared/errors';
   5 | import { GitVersionType } from 'azure-devops-node-api/interfaces/GitInterfaces';
   6 | 
   7 | // Mock Azure Identity
   8 | jest.mock('@azure/identity', () => {
   9 |   const mockGetToken = jest.fn().mockResolvedValue({ token: 'mock-token' });
  10 |   return {
  11 |     DefaultAzureCredential: jest.fn().mockImplementation(() => ({
  12 |       getToken: mockGetToken,
  13 |     })),
  14 |     AzureCliCredential: jest.fn().mockImplementation(() => ({
  15 |       getToken: mockGetToken,
  16 |     })),
  17 |   };
  18 | });
  19 | 
  20 | // Mock axios
  21 | jest.mock('axios');
  22 | const mockedAxios = axios as jest.Mocked<typeof axios>;
  23 | 
  24 | describe('searchCode unit', () => {
  25 |   // Mock WebApi connection
  26 |   const mockConnection = {
  27 |     getGitApi: jest.fn().mockImplementation(() => ({
  28 |       getItemContent: jest.fn().mockImplementation((_repoId, path) => {
  29 |         // Return different content based on the path to simulate different files
  30 |         if (path === '/src/example.ts') {
  31 |           return Buffer.from('export function example() { return "test"; }');
  32 |         }
  33 |         return Buffer.from('// Empty file');
  34 |       }),
  35 |     })),
  36 |     _getHttpClient: jest.fn().mockReturnValue({
  37 |       getAuthorizationHeader: jest.fn().mockReturnValue('Bearer mock-token'),
  38 |     }),
  39 |     getCoreApi: jest.fn().mockImplementation(() => ({
  40 |       getProjects: jest
  41 |         .fn()
  42 |         .mockResolvedValue([{ name: 'TestProject', id: 'project-id' }]),
  43 |     })),
  44 |     serverUrl: 'https://dev.azure.com/testorg',
  45 |   } as unknown as WebApi;
  46 | 
  47 |   // Store original console.error
  48 |   const originalConsoleError = console.error;
  49 | 
  50 |   beforeEach(() => {
  51 |     jest.clearAllMocks();
  52 |     // Mock console.error to prevent error messages from being displayed during tests
  53 |     console.error = jest.fn();
  54 |   });
  55 | 
  56 |   afterEach(() => {
  57 |     // Restore original console.error
  58 |     console.error = originalConsoleError;
  59 |   });
  60 | 
  61 |   test('should return search results with content', async () => {
  62 |     // Arrange
  63 |     const mockSearchResponse = {
  64 |       data: {
  65 |         count: 1,
  66 |         results: [
  67 |           {
  68 |             fileName: 'example.ts',
  69 |             path: '/src/example.ts',
  70 |             matches: {
  71 |               content: [
  72 |                 {
  73 |                   charOffset: 17,
  74 |                   length: 7,
  75 |                 },
  76 |               ],
  77 |             },
  78 |             collection: {
  79 |               name: 'DefaultCollection',
  80 |             },
  81 |             project: {
  82 |               name: 'TestProject',
  83 |               id: 'project-id',
  84 |             },
  85 |             repository: {
  86 |               name: 'TestRepo',
  87 |               id: 'repo-id',
  88 |               type: 'git',
  89 |             },
  90 |             versions: [
  91 |               {
  92 |                 branchName: 'main',
  93 |                 changeId: 'commit-hash',
  94 |               },
  95 |             ],
  96 |             contentId: 'content-hash',
  97 |           },
  98 |         ],
  99 |       },
 100 |     };
 101 | 
 102 |     mockedAxios.post.mockResolvedValueOnce(mockSearchResponse);
 103 | 
 104 |     // Create a mock stream with content
 105 |     const fileContent = 'export function example() { return "test"; }';
 106 |     const mockStream = {
 107 |       on: jest.fn().mockImplementation((event, callback) => {
 108 |         if (event === 'data') {
 109 |           // Call the callback with the data
 110 |           callback(Buffer.from(fileContent));
 111 |         } else if (event === 'end') {
 112 |           // Call the end callback asynchronously
 113 |           setTimeout(callback, 0);
 114 |         }
 115 |         return mockStream; // Return this for chaining
 116 |       }),
 117 |     };
 118 | 
 119 |     // Mock Git API to return content
 120 |     const mockGitApi = {
 121 |       getItemContent: jest.fn().mockResolvedValue(mockStream),
 122 |     };
 123 | 
 124 |     const mockConnectionWithContent = {
 125 |       ...mockConnection,
 126 |       getGitApi: jest.fn().mockResolvedValue(mockGitApi),
 127 |       serverUrl: 'https://dev.azure.com/testorg',
 128 |     } as unknown as WebApi;
 129 | 
 130 |     // Act
 131 |     const result = await searchCode(mockConnectionWithContent, {
 132 |       searchText: 'example',
 133 |       projectId: 'TestProject',
 134 |       includeContent: true,
 135 |     });
 136 | 
 137 |     // Assert
 138 |     expect(result).toBeDefined();
 139 |     expect(result.count).toBe(1);
 140 |     expect(result.results).toHaveLength(1);
 141 |     expect(result.results[0].fileName).toBe('example.ts');
 142 |     expect(result.results[0].content).toBe(
 143 |       'export function example() { return "test"; }',
 144 |     );
 145 |     expect(mockedAxios.post).toHaveBeenCalledTimes(1);
 146 |     expect(mockGitApi.getItemContent).toHaveBeenCalledTimes(1);
 147 |     expect(mockGitApi.getItemContent).toHaveBeenCalledWith(
 148 |       'repo-id',
 149 |       '/src/example.ts',
 150 |       'TestProject',
 151 |       undefined,
 152 |       undefined,
 153 |       undefined,
 154 |       undefined,
 155 |       false,
 156 |       {
 157 |         version: 'commit-hash',
 158 |         versionType: GitVersionType.Commit,
 159 |       },
 160 |       true,
 161 |     );
 162 |   });
 163 | 
 164 |   test('should not fetch content when includeContent is false', async () => {
 165 |     // Arrange
 166 |     const mockSearchResponse = {
 167 |       data: {
 168 |         count: 1,
 169 |         results: [
 170 |           {
 171 |             fileName: 'example.ts',
 172 |             path: '/src/example.ts',
 173 |             matches: {
 174 |               content: [
 175 |                 {
 176 |                   charOffset: 17,
 177 |                   length: 7,
 178 |                 },
 179 |               ],
 180 |             },
 181 |             collection: {
 182 |               name: 'DefaultCollection',
 183 |             },
 184 |             project: {
 185 |               name: 'TestProject',
 186 |               id: 'project-id',
 187 |             },
 188 |             repository: {
 189 |               name: 'TestRepo',
 190 |               id: 'repo-id',
 191 |               type: 'git',
 192 |             },
 193 |             versions: [
 194 |               {
 195 |                 branchName: 'main',
 196 |                 changeId: 'commit-hash',
 197 |               },
 198 |             ],
 199 |             contentId: 'content-hash',
 200 |           },
 201 |         ],
 202 |       },
 203 |     };
 204 | 
 205 |     mockedAxios.post.mockResolvedValueOnce(mockSearchResponse);
 206 | 
 207 |     // Act
 208 |     const result = await searchCode(mockConnection, {
 209 |       searchText: 'example',
 210 |       projectId: 'TestProject',
 211 |       includeContent: false,
 212 |     });
 213 | 
 214 |     // Assert
 215 |     expect(result).toBeDefined();
 216 |     expect(result.count).toBe(1);
 217 |     expect(result.results).toHaveLength(1);
 218 |     expect(result.results[0].fileName).toBe('example.ts');
 219 |     expect(result.results[0].content).toBeUndefined();
 220 |     expect(mockConnection.getGitApi).not.toHaveBeenCalled();
 221 |   });
 222 | 
 223 |   test('should handle empty search results', async () => {
 224 |     // Arrange
 225 |     const mockSearchResponse = {
 226 |       data: {
 227 |         count: 0,
 228 |         results: [],
 229 |       },
 230 |     };
 231 | 
 232 |     mockedAxios.post.mockResolvedValueOnce(mockSearchResponse);
 233 | 
 234 |     // Act
 235 |     const result = await searchCode(mockConnection, {
 236 |       searchText: 'nonexistent',
 237 |       projectId: 'TestProject',
 238 |     });
 239 | 
 240 |     // Assert
 241 |     expect(result).toBeDefined();
 242 |     expect(result.count).toBe(0);
 243 |     expect(result.results).toHaveLength(0);
 244 |   });
 245 | 
 246 |   test('should handle API errors', async () => {
 247 |     // Arrange
 248 |     const axiosError = new Error('API Error');
 249 |     (axiosError as any).isAxiosError = true;
 250 |     (axiosError as any).response = {
 251 |       status: 404,
 252 |       data: {
 253 |         message: 'Project not found',
 254 |       },
 255 |     };
 256 | 
 257 |     mockedAxios.post.mockRejectedValueOnce(axiosError);
 258 | 
 259 |     // Act & Assert
 260 |     await expect(
 261 |       searchCode(mockConnection, {
 262 |         searchText: 'example',
 263 |         projectId: 'NonExistentProject',
 264 |       }),
 265 |     ).rejects.toThrow(AzureDevOpsError);
 266 |   });
 267 | 
 268 |   test('should propagate custom errors when thrown internally', async () => {
 269 |     // Arrange
 270 |     const customError = new AzureDevOpsError('Custom error');
 271 | 
 272 |     // Mock axios to properly return the custom error
 273 |     mockedAxios.post.mockImplementationOnce(() => {
 274 |       throw customError;
 275 |     });
 276 | 
 277 |     // Act & Assert
 278 |     await expect(
 279 |       searchCode(mockConnection, {
 280 |         searchText: 'example',
 281 |         projectId: 'TestProject',
 282 |       }),
 283 |     ).rejects.toThrow(AzureDevOpsError);
 284 | 
 285 |     // Reset mock and set it up again for the second test
 286 |     mockedAxios.post.mockReset();
 287 |     mockedAxios.post.mockImplementationOnce(() => {
 288 |       throw customError;
 289 |     });
 290 | 
 291 |     await expect(
 292 |       searchCode(mockConnection, {
 293 |         searchText: 'example',
 294 |         projectId: 'TestProject',
 295 |       }),
 296 |     ).rejects.toThrow('Custom error');
 297 |   });
 298 | 
 299 |   test('should apply filters when provided', async () => {
 300 |     // Arrange
 301 |     const mockSearchResponse = {
 302 |       data: {
 303 |         count: 1,
 304 |         results: [
 305 |           {
 306 |             fileName: 'example.ts',
 307 |             path: '/src/example.ts',
 308 |             matches: {
 309 |               content: [
 310 |                 {
 311 |                   charOffset: 17,
 312 |                   length: 7,
 313 |                 },
 314 |               ],
 315 |             },
 316 |             collection: {
 317 |               name: 'DefaultCollection',
 318 |             },
 319 |             project: {
 320 |               name: 'TestProject',
 321 |               id: 'project-id',
 322 |             },
 323 |             repository: {
 324 |               name: 'TestRepo',
 325 |               id: 'repo-id',
 326 |               type: 'git',
 327 |             },
 328 |             versions: [
 329 |               {
 330 |                 branchName: 'main',
 331 |                 changeId: 'commit-hash',
 332 |               },
 333 |             ],
 334 |             contentId: 'content-hash',
 335 |           },
 336 |         ],
 337 |       },
 338 |     };
 339 | 
 340 |     mockedAxios.post.mockResolvedValueOnce(mockSearchResponse);
 341 | 
 342 |     // Act
 343 |     await searchCode(mockConnection, {
 344 |       searchText: 'example',
 345 |       projectId: 'TestProject',
 346 |       filters: {
 347 |         Repository: ['TestRepo'],
 348 |         Path: ['/src'],
 349 |         Branch: ['main'],
 350 |         CodeElement: ['function'],
 351 |       },
 352 |     });
 353 | 
 354 |     // Assert
 355 |     expect(mockedAxios.post).toHaveBeenCalledWith(
 356 |       expect.any(String),
 357 |       expect.objectContaining({
 358 |         filters: {
 359 |           Project: ['TestProject'],
 360 |           Repository: ['TestRepo'],
 361 |           Path: ['/src'],
 362 |           Branch: ['main'],
 363 |           CodeElement: ['function'],
 364 |         },
 365 |       }),
 366 |       expect.any(Object),
 367 |     );
 368 |   });
 369 | 
 370 |   test('should handle pagination parameters', async () => {
 371 |     // Arrange
 372 |     const mockSearchResponse = {
 373 |       data: {
 374 |         count: 100,
 375 |         results: Array(10)
 376 |           .fill(0)
 377 |           .map((_, i) => ({
 378 |             fileName: `example${i}.ts`,
 379 |             path: `/src/example${i}.ts`,
 380 |             matches: {
 381 |               content: [
 382 |                 {
 383 |                   charOffset: 17,
 384 |                   length: 7,
 385 |                 },
 386 |               ],
 387 |             },
 388 |             collection: {
 389 |               name: 'DefaultCollection',
 390 |             },
 391 |             project: {
 392 |               name: 'TestProject',
 393 |               id: 'project-id',
 394 |             },
 395 |             repository: {
 396 |               name: 'TestRepo',
 397 |               id: 'repo-id',
 398 |               type: 'git',
 399 |             },
 400 |             versions: [
 401 |               {
 402 |                 branchName: 'main',
 403 |                 changeId: 'commit-hash',
 404 |               },
 405 |             ],
 406 |             contentId: `content-hash-${i}`,
 407 |           })),
 408 |       },
 409 |     };
 410 | 
 411 |     mockedAxios.post.mockResolvedValueOnce(mockSearchResponse);
 412 | 
 413 |     // Act
 414 |     await searchCode(mockConnection, {
 415 |       searchText: 'example',
 416 |       projectId: 'TestProject',
 417 |       top: 10,
 418 |       skip: 20,
 419 |     });
 420 | 
 421 |     // Assert
 422 |     expect(mockedAxios.post).toHaveBeenCalledWith(
 423 |       expect.any(String),
 424 |       expect.objectContaining({
 425 |         $top: 10,
 426 |         $skip: 20,
 427 |       }),
 428 |       expect.any(Object),
 429 |     );
 430 |   });
 431 | 
 432 |   test('should handle errors when fetching file content', async () => {
 433 |     // Arrange
 434 |     const mockSearchResponse = {
 435 |       data: {
 436 |         count: 1,
 437 |         results: [
 438 |           {
 439 |             fileName: 'example.ts',
 440 |             path: '/src/example.ts',
 441 |             matches: {
 442 |               content: [
 443 |                 {
 444 |                   charOffset: 17,
 445 |                   length: 7,
 446 |                 },
 447 |               ],
 448 |             },
 449 |             collection: {
 450 |               name: 'DefaultCollection',
 451 |             },
 452 |             project: {
 453 |               name: 'TestProject',
 454 |               id: 'project-id',
 455 |             },
 456 |             repository: {
 457 |               name: 'TestRepo',
 458 |               id: 'repo-id',
 459 |               type: 'git',
 460 |             },
 461 |             versions: [
 462 |               {
 463 |                 branchName: 'main',
 464 |                 changeId: 'commit-hash',
 465 |               },
 466 |             ],
 467 |             contentId: 'content-hash',
 468 |           },
 469 |         ],
 470 |       },
 471 |     };
 472 | 
 473 |     mockedAxios.post.mockResolvedValueOnce(mockSearchResponse);
 474 | 
 475 |     // Mock Git API to throw an error
 476 |     const mockGitApi = {
 477 |       getItemContent: jest
 478 |         .fn()
 479 |         .mockRejectedValue(new Error('Failed to fetch content')),
 480 |     };
 481 |     const mockConnectionWithError = {
 482 |       ...mockConnection,
 483 |       getGitApi: jest.fn().mockResolvedValue(mockGitApi),
 484 |     } as unknown as WebApi;
 485 | 
 486 |     // Act
 487 |     const result = await searchCode(mockConnectionWithError, {
 488 |       searchText: 'example',
 489 |       projectId: 'TestProject',
 490 |       includeContent: true,
 491 |     });
 492 | 
 493 |     // Assert
 494 |     expect(result).toBeDefined();
 495 |     expect(result.count).toBe(1);
 496 |     expect(result.results).toHaveLength(1);
 497 |     // Content should be undefined when there's an error fetching it
 498 |     expect(result.results[0].content).toBeUndefined();
 499 |   });
 500 | 
 501 |   test('should use default project when projectId is not provided', async () => {
 502 |     // Arrange
 503 |     // Set up environment variable for default project
 504 |     const originalEnv = process.env.AZURE_DEVOPS_DEFAULT_PROJECT;
 505 |     process.env.AZURE_DEVOPS_DEFAULT_PROJECT = 'DefaultProject';
 506 | 
 507 |     const mockSearchResponse = {
 508 |       data: {
 509 |         count: 2,
 510 |         results: [
 511 |           {
 512 |             fileName: 'example1.ts',
 513 |             path: '/src/example1.ts',
 514 |             matches: {
 515 |               content: [
 516 |                 {
 517 |                   charOffset: 17,
 518 |                   length: 7,
 519 |                 },
 520 |               ],
 521 |             },
 522 |             collection: {
 523 |               name: 'DefaultCollection',
 524 |             },
 525 |             project: {
 526 |               name: 'DefaultProject',
 527 |               id: 'default-project-id',
 528 |             },
 529 |             repository: {
 530 |               name: 'Repo1',
 531 |               id: 'repo-id-1',
 532 |               type: 'git',
 533 |             },
 534 |             versions: [
 535 |               {
 536 |                 branchName: 'main',
 537 |                 changeId: 'commit-hash-1',
 538 |               },
 539 |             ],
 540 |             contentId: 'content-hash-1',
 541 |           },
 542 |           {
 543 |             fileName: 'example2.ts',
 544 |             path: '/src/example2.ts',
 545 |             matches: {
 546 |               content: [
 547 |                 {
 548 |                   charOffset: 17,
 549 |                   length: 7,
 550 |                 },
 551 |               ],
 552 |             },
 553 |             collection: {
 554 |               name: 'DefaultCollection',
 555 |             },
 556 |             project: {
 557 |               name: 'DefaultProject',
 558 |               id: 'default-project-id',
 559 |             },
 560 |             repository: {
 561 |               name: 'Repo2',
 562 |               id: 'repo-id-2',
 563 |               type: 'git',
 564 |             },
 565 |             versions: [
 566 |               {
 567 |                 branchName: 'main',
 568 |                 changeId: 'commit-hash-2',
 569 |               },
 570 |             ],
 571 |             contentId: 'content-hash-2',
 572 |           },
 573 |         ],
 574 |       },
 575 |     };
 576 | 
 577 |     mockedAxios.post.mockResolvedValueOnce(mockSearchResponse);
 578 | 
 579 |     try {
 580 |       // Act
 581 |       const result = await searchCode(mockConnection, {
 582 |         searchText: 'example',
 583 |         includeContent: false,
 584 |       });
 585 | 
 586 |       // Assert
 587 |       expect(result).toBeDefined();
 588 |       expect(result.count).toBe(2);
 589 |       expect(result.results).toHaveLength(2);
 590 |       expect(result.results[0].project.name).toBe('DefaultProject');
 591 |       expect(result.results[1].project.name).toBe('DefaultProject');
 592 |       expect(mockedAxios.post).toHaveBeenCalledTimes(1);
 593 |       expect(mockedAxios.post).toHaveBeenCalledWith(
 594 |         expect.stringContaining(
 595 |           'https://almsearch.dev.azure.com/testorg/DefaultProject/_apis/search/codesearchresults',
 596 |         ),
 597 |         expect.objectContaining({
 598 |           filters: expect.objectContaining({
 599 |             Project: ['DefaultProject'],
 600 |           }),
 601 |         }),
 602 |         expect.any(Object),
 603 |       );
 604 |     } finally {
 605 |       // Restore original environment variable
 606 |       process.env.AZURE_DEVOPS_DEFAULT_PROJECT = originalEnv;
 607 |     }
 608 |   });
 609 | 
 610 |   test('should throw error when no projectId is provided and no default project is set', async () => {
 611 |     // Arrange
 612 |     // Ensure no default project is set
 613 |     const originalEnv = process.env.AZURE_DEVOPS_DEFAULT_PROJECT;
 614 |     process.env.AZURE_DEVOPS_DEFAULT_PROJECT = '';
 615 | 
 616 |     try {
 617 |       // Act & Assert
 618 |       await expect(
 619 |         searchCode(mockConnection, {
 620 |           searchText: 'example',
 621 |           includeContent: false,
 622 |         }),
 623 |       ).rejects.toThrow('Project ID is required');
 624 |     } finally {
 625 |       // Restore original environment variable
 626 |       process.env.AZURE_DEVOPS_DEFAULT_PROJECT = originalEnv;
 627 |     }
 628 |   });
 629 | 
 630 |   test('should handle includeContent for different content types', async () => {
 631 |     // Arrange
 632 |     const mockSearchResponse = {
 633 |       data: {
 634 |         count: 4,
 635 |         results: [
 636 |           // Result 1 - Buffer content
 637 |           {
 638 |             fileName: 'example1.ts',
 639 |             path: '/src/example1.ts',
 640 |             matches: {
 641 |               content: [
 642 |                 {
 643 |                   charOffset: 17,
 644 |                   length: 7,
 645 |                 },
 646 |               ],
 647 |             },
 648 |             collection: {
 649 |               name: 'DefaultCollection',
 650 |             },
 651 |             project: {
 652 |               name: 'TestProject',
 653 |               id: 'project-id',
 654 |             },
 655 |             repository: {
 656 |               name: 'TestRepo',
 657 |               id: 'repo-id-1',
 658 |               type: 'git',
 659 |             },
 660 |             versions: [
 661 |               {
 662 |                 branchName: 'main',
 663 |                 changeId: 'commit-hash-1',
 664 |               },
 665 |             ],
 666 |             contentId: 'content-hash-1',
 667 |           },
 668 |           // Result 2 - String content
 669 |           {
 670 |             fileName: 'example2.ts',
 671 |             path: '/src/example2.ts',
 672 |             matches: {
 673 |               content: [
 674 |                 {
 675 |                   charOffset: 17,
 676 |                   length: 7,
 677 |                 },
 678 |               ],
 679 |             },
 680 |             collection: {
 681 |               name: 'DefaultCollection',
 682 |             },
 683 |             project: {
 684 |               name: 'TestProject',
 685 |               id: 'project-id',
 686 |             },
 687 |             repository: {
 688 |               name: 'TestRepo',
 689 |               id: 'repo-id-2',
 690 |               type: 'git',
 691 |             },
 692 |             versions: [
 693 |               {
 694 |                 branchName: 'main',
 695 |                 changeId: 'commit-hash-2',
 696 |               },
 697 |             ],
 698 |             contentId: 'content-hash-2',
 699 |           },
 700 |           // Result 3 - Object content
 701 |           {
 702 |             fileName: 'example3.ts',
 703 |             path: '/src/example3.ts',
 704 |             matches: {
 705 |               content: [
 706 |                 {
 707 |                   charOffset: 17,
 708 |                   length: 7,
 709 |                 },
 710 |               ],
 711 |             },
 712 |             collection: {
 713 |               name: 'DefaultCollection',
 714 |             },
 715 |             project: {
 716 |               name: 'TestProject',
 717 |               id: 'project-id',
 718 |             },
 719 |             repository: {
 720 |               name: 'TestRepo',
 721 |               id: 'repo-id-3',
 722 |               type: 'git',
 723 |             },
 724 |             versions: [
 725 |               {
 726 |                 branchName: 'main',
 727 |                 changeId: 'commit-hash-3',
 728 |               },
 729 |             ],
 730 |             contentId: 'content-hash-3',
 731 |           },
 732 |           // Result 4 - Uint8Array content
 733 |           {
 734 |             fileName: 'example4.ts',
 735 |             path: '/src/example4.ts',
 736 |             matches: {
 737 |               content: [
 738 |                 {
 739 |                   charOffset: 17,
 740 |                   length: 7,
 741 |                 },
 742 |               ],
 743 |             },
 744 |             collection: {
 745 |               name: 'DefaultCollection',
 746 |             },
 747 |             project: {
 748 |               name: 'TestProject',
 749 |               id: 'project-id',
 750 |             },
 751 |             repository: {
 752 |               name: 'TestRepo',
 753 |               id: 'repo-id-4',
 754 |               type: 'git',
 755 |             },
 756 |             versions: [
 757 |               {
 758 |                 branchName: 'main',
 759 |                 changeId: 'commit-hash-4',
 760 |               },
 761 |             ],
 762 |             contentId: 'content-hash-4',
 763 |           },
 764 |         ],
 765 |       },
 766 |     };
 767 | 
 768 |     mockedAxios.post.mockResolvedValueOnce(mockSearchResponse);
 769 | 
 770 |     // Create mock contents for each type - all as streams, since that's what getItemContent returns
 771 |     // These are all streams but with different content to demonstrate handling different data types from the stream
 772 |     const createMockStream = (content: string) => ({
 773 |       on: jest.fn().mockImplementation((event, callback) => {
 774 |         if (event === 'data') {
 775 |           callback(Buffer.from(content));
 776 |         } else if (event === 'end') {
 777 |           setTimeout(callback, 0);
 778 |         }
 779 |         return createMockStream(content); // Return this for chaining
 780 |       }),
 781 |     });
 782 | 
 783 |     // Create four different mock streams with different content
 784 |     const mockStream1 = createMockStream('Buffer content');
 785 |     const mockStream2 = createMockStream('String content');
 786 |     const mockStream3 = createMockStream(
 787 |       JSON.stringify({ foo: 'bar', baz: 42 }),
 788 |     );
 789 |     const mockStream4 = createMockStream('hello');
 790 | 
 791 |     // Mock Git API to return our different mock streams for each repository
 792 |     const mockGitApi = {
 793 |       getItemContent: jest
 794 |         .fn()
 795 |         .mockImplementationOnce(() => Promise.resolve(mockStream1))
 796 |         .mockImplementationOnce(() => Promise.resolve(mockStream2))
 797 |         .mockImplementationOnce(() => Promise.resolve(mockStream3))
 798 |         .mockImplementationOnce(() => Promise.resolve(mockStream4)),
 799 |     };
 800 | 
 801 |     const mockConnectionWithStreams = {
 802 |       ...mockConnection,
 803 |       getGitApi: jest.fn().mockResolvedValue(mockGitApi),
 804 |       serverUrl: 'https://dev.azure.com/testorg',
 805 |     } as unknown as WebApi;
 806 | 
 807 |     // Act
 808 |     const result = await searchCode(mockConnectionWithStreams, {
 809 |       searchText: 'example',
 810 |       projectId: 'TestProject',
 811 |       includeContent: true,
 812 |     });
 813 | 
 814 |     // Assert
 815 |     expect(result).toBeDefined();
 816 |     expect(result.count).toBe(4);
 817 |     expect(result.results).toHaveLength(4);
 818 | 
 819 |     // Check each result has appropriate content from the streams
 820 |     // Result 1 - Buffer content stream
 821 |     expect(result.results[0].content).toBe('Buffer content');
 822 | 
 823 |     // Result 2 - String content stream
 824 |     expect(result.results[1].content).toBe('String content');
 825 | 
 826 |     // Result 3 - JSON object content stream
 827 |     expect(result.results[2].content).toBe('{"foo":"bar","baz":42}');
 828 | 
 829 |     // Result 4 - Text content stream
 830 |     expect(result.results[3].content).toBe('hello');
 831 | 
 832 |     // Git API should have been called 4 times
 833 |     expect(mockGitApi.getItemContent).toHaveBeenCalledTimes(4);
 834 |     // Verify the parameters for the first call
 835 |     expect(mockGitApi.getItemContent.mock.calls[0]).toEqual([
 836 |       'repo-id-1',
 837 |       '/src/example1.ts',
 838 |       'TestProject',
 839 |       undefined,
 840 |       undefined,
 841 |       undefined,
 842 |       undefined,
 843 |       false,
 844 |       {
 845 |         version: 'commit-hash-1',
 846 |         versionType: GitVersionType.Commit,
 847 |       },
 848 |       true,
 849 |     ]);
 850 |   });
 851 | 
 852 |   test('should properly convert content stream to string', async () => {
 853 |     // Arrange
 854 |     const mockSearchResponse = {
 855 |       data: {
 856 |         count: 1,
 857 |         results: [
 858 |           {
 859 |             fileName: 'example.ts',
 860 |             path: '/src/example.ts',
 861 |             matches: {
 862 |               content: [
 863 |                 {
 864 |                   charOffset: 17,
 865 |                   length: 7,
 866 |                 },
 867 |               ],
 868 |             },
 869 |             collection: {
 870 |               name: 'DefaultCollection',
 871 |             },
 872 |             project: {
 873 |               name: 'TestProject',
 874 |               id: 'project-id',
 875 |             },
 876 |             repository: {
 877 |               name: 'TestRepo',
 878 |               id: 'repo-id',
 879 |               type: 'git',
 880 |             },
 881 |             versions: [
 882 |               {
 883 |                 branchName: 'main',
 884 |                 changeId: 'commit-hash',
 885 |               },
 886 |             ],
 887 |             contentId: 'content-hash',
 888 |           },
 889 |         ],
 890 |       },
 891 |     };
 892 | 
 893 |     mockedAxios.post.mockResolvedValueOnce(mockSearchResponse);
 894 | 
 895 |     // Create a mock ReadableStream
 896 |     const mockContent = 'This is the file content';
 897 | 
 898 |     // Create a simplified mock stream that emits the content
 899 |     const mockStream = {
 900 |       on: jest.fn().mockImplementation((event, callback) => {
 901 |         if (event === 'data') {
 902 |           // Call the callback with the data
 903 |           callback(Buffer.from(mockContent));
 904 |         } else if (event === 'end') {
 905 |           // Call the end callback asynchronously
 906 |           setTimeout(callback, 0);
 907 |         }
 908 |         return mockStream; // Return this for chaining
 909 |       }),
 910 |     };
 911 | 
 912 |     // Mock Git API to return our mock stream
 913 |     const mockGitApi = {
 914 |       getItemContent: jest.fn().mockResolvedValue(mockStream),
 915 |     };
 916 | 
 917 |     const mockConnectionWithStream = {
 918 |       ...mockConnection,
 919 |       getGitApi: jest.fn().mockResolvedValue(mockGitApi),
 920 |       serverUrl: 'https://dev.azure.com/testorg',
 921 |     } as unknown as WebApi;
 922 | 
 923 |     // Act
 924 |     const result = await searchCode(mockConnectionWithStream, {
 925 |       searchText: 'example',
 926 |       projectId: 'TestProject',
 927 |       includeContent: true,
 928 |     });
 929 | 
 930 |     // Assert
 931 |     expect(result).toBeDefined();
 932 |     expect(result.count).toBe(1);
 933 |     expect(result.results).toHaveLength(1);
 934 | 
 935 |     // Check that the content was properly converted from stream to string
 936 |     expect(result.results[0].content).toBe(mockContent);
 937 | 
 938 |     // Verify the stream event handlers were attached
 939 |     expect(mockStream.on).toHaveBeenCalledWith('data', expect.any(Function));
 940 |     expect(mockStream.on).toHaveBeenCalledWith('end', expect.any(Function));
 941 |     expect(mockStream.on).toHaveBeenCalledWith('error', expect.any(Function));
 942 | 
 943 |     // Verify the parameters for getItemContent
 944 |     expect(mockGitApi.getItemContent).toHaveBeenCalledWith(
 945 |       'repo-id',
 946 |       '/src/example.ts',
 947 |       'TestProject',
 948 |       undefined,
 949 |       undefined,
 950 |       undefined,
 951 |       undefined,
 952 |       false,
 953 |       {
 954 |         version: 'commit-hash',
 955 |         versionType: GitVersionType.Commit,
 956 |       },
 957 |       true,
 958 |     );
 959 |   });
 960 | 
 961 |   test('should limit top to 10 when includeContent is true', async () => {
 962 |     // Arrange
 963 |     const mockSearchResponse = {
 964 |       data: {
 965 |         count: 10,
 966 |         results: Array(10)
 967 |           .fill(0)
 968 |           .map((_, i) => ({
 969 |             fileName: `example${i}.ts`,
 970 |             path: `/src/example${i}.ts`,
 971 |             matches: {
 972 |               content: [
 973 |                 {
 974 |                   charOffset: 17,
 975 |                   length: 7,
 976 |                 },
 977 |               ],
 978 |             },
 979 |             collection: {
 980 |               name: 'DefaultCollection',
 981 |             },
 982 |             project: {
 983 |               name: 'TestProject',
 984 |               id: 'project-id',
 985 |             },
 986 |             repository: {
 987 |               name: 'TestRepo',
 988 |               id: 'repo-id',
 989 |               type: 'git',
 990 |             },
 991 |             versions: [
 992 |               {
 993 |                 branchName: 'main',
 994 |                 changeId: 'commit-hash',
 995 |               },
 996 |             ],
 997 |             contentId: `content-hash-${i}`,
 998 |           })),
 999 |       },
1000 |     };
1001 | 
1002 |     mockedAxios.post.mockResolvedValueOnce(mockSearchResponse);
1003 | 
1004 |     // For this test, we don't need to mock the Git API since we're only testing the top parameter
1005 |     // We'll create a connection that doesn't have includeContent functionality
1006 |     const mockConnectionWithoutContent = {
1007 |       ...mockConnection,
1008 |       getGitApi: jest.fn().mockImplementation(() => {
1009 |         throw new Error('Git API not available');
1010 |       }),
1011 |       serverUrl: 'https://dev.azure.com/testorg',
1012 |     } as unknown as WebApi;
1013 | 
1014 |     // Act
1015 |     await searchCode(mockConnectionWithoutContent, {
1016 |       searchText: 'example',
1017 |       projectId: 'TestProject',
1018 |       top: 50, // User tries to get 50 results
1019 |       includeContent: true, // But includeContent is true
1020 |     });
1021 | 
1022 |     // Assert
1023 |     expect(mockedAxios.post).toHaveBeenCalledWith(
1024 |       expect.any(String),
1025 |       expect.objectContaining({
1026 |         $top: 10, // Should be limited to 10
1027 |       }),
1028 |       expect.any(Object),
1029 |     );
1030 |   });
1031 | 
1032 |   test('should not limit top when includeContent is false', async () => {
1033 |     // Arrange
1034 |     const mockSearchResponse = {
1035 |       data: {
1036 |         count: 50,
1037 |         results: Array(50)
1038 |           .fill(0)
1039 |           .map((_, i) => ({
1040 |             // ... simplified result object
1041 |             fileName: `example${i}.ts`,
1042 |           })),
1043 |       },
1044 |     };
1045 | 
1046 |     mockedAxios.post.mockResolvedValueOnce(mockSearchResponse);
1047 | 
1048 |     // Act
1049 |     await searchCode(mockConnection, {
1050 |       searchText: 'example',
1051 |       projectId: 'TestProject',
1052 |       top: 50, // User wants 50 results
1053 |       includeContent: false, // includeContent is false
1054 |     });
1055 | 
1056 |     // Assert
1057 |     expect(mockedAxios.post).toHaveBeenCalledWith(
1058 |       expect.any(String),
1059 |       expect.objectContaining({
1060 |         $top: 50, // Should use requested value
1061 |       }),
1062 |       expect.any(Object),
1063 |     );
1064 |   });
1065 | });
1066 | 
```
Page 7/8FirstPrevNextLast