Skip to content

fix: scope ADO known-issues WIQL query by area-path, defaulting to {project}#178

Merged
Malcolmnixon merged 11 commits into
mainfrom
copilot/fix-ado-connector-query-scope
May 10, 2026
Merged

fix: scope ADO known-issues WIQL query by area-path, defaulting to {project}#178
Malcolmnixon merged 11 commits into
mainfrom
copilot/fix-ado-connector-query-scope

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 9, 2026

When an ADO project contains multiple repositories, the known-issues WIQL query returned all open bugs across the entire project, causing unrelated work items to appear in generated reports.

Description

Adds an optional area-path key to the azure-devops connector configuration to scope the WIQL known-issues query. When area-path is not configured, the connector defaults to the ADO project name — Azure DevOps creates a root area path for every project by default, so this works correctly without any configuration for the vast majority of teams. Users whose work items live in a sub-area can override this, and an explicit empty string (area-path: "") disables filtering entirely for a project-wide query.

In Azure DevOps, Area Path is an independent organizational hierarchy for work items that does not automatically correspond to repository names. A single area path can cover multiple repositories, and one repository's bugs may span multiple area paths. The default project-level area path covers the overwhelming majority of cases; area-path is exposed as an override for teams whose structure requires sub-area scoping.

area-path connector option

  • AzureDevOpsConnectorConfig: adds AreaPath property (null → default {project}; empty string → no filter; explicit value → scoped to that path)
  • BuildMarkConfigReader: parses the area-path key within azure-devops: blocks; validates that the value is a YamlScalarNode and produces a configuration error for non-scalar values (e.g. area-path: [foo]); preserves an explicit empty string so that filtering can be disabled without being normalized to null
  • AzureDevOpsRepoConnector: computes the effective area path (_config?.AreaPath ?? project); when non-empty, appends AND [System.AreaPath] UNDER '{areaPath}' to the WIQL query (with single-quote escaping)
  • AzureDevOpsRestClient: QueryWorkItemsAsync now detects non-2xx responses from the WIQL endpoint, parses the ADO JSON error body (AzureDevOpsApiError), and throws an InvalidOperationException with the ADO error message rather than a generic HTTP failure

Usage

Default (no area-path needed — scoped to the project root area automatically):

connector:
  type: azure-devops
  azure-devops:
    url: https://dev.azure.com/myorg
    project: MyProject
    repository: MyRepo

Override for sub-area scoping:

connector:
  type: azure-devops
  azure-devops:
    url: https://dev.azure.com/myorg
    project: MyProject
    repository: MyRepo
    area-path: MyProject\MyRepo   # scoped to this area and descendants

Disable area-path filtering (project-wide query):

connector:
  type: azure-devops
  azure-devops:
    url: https://dev.azure.com/myorg
    project: MyProject
    repository: MyRepo
    area-path: ""   # no filter; returns all bugs in the ADO project

Tests & infrastructure

  • MockAzureDevOpsHttpMessageHandler: captures LastWiqlRequestBody for assertion; adds AddWiqlErrorResponse helper for testing 400 error responses
  • New test WithAreaPath_ScopesWiqlQueryToAreaPath: asserts explicit area path is used in WIQL
  • New test WithoutAreaPath_DefaultsToProject: asserts default scoping uses the project name
  • New test WithEmptyAreaPath_DisablesAreaPathFilter: asserts no System.AreaPath filter when empty
  • New test AzureDevOpsConnectorAreaPath_ReturnsParsedConfiguration: verifies YAML parsing round-trip
  • New test AzureDevOpsConnectorEmptyAreaPath_ReturnsParsedConfiguration: verifies empty string is preserved as "" (not null) through YAML parsing
  • New test WithInvalidAreaPath_ThrowsWithAdoErrorMessage: asserts that a 400 response surfaces the ADO error message in the thrown exception
  • New test BuildMarkConfigReader_ReadAsync_AzureDevOpsNonScalarAreaPath_ReturnsErrorIssue: asserts that a non-scalar area-path value (e.g. a YAML sequence) produces a configuration error rather than silently disabling filtering

Documentation

  • Design doc connector-config.md: AreaPath property description updated to describe default and escape hatch
  • Design doc azure-devops-repo-connector.md: step 13 updated to describe default/override/disable logic
  • Verification doc azure-devops-connector-config.md: all test scenarios documented
  • User guide configuration.md: Area Path scoping subsection with concept explanation, scoping table, examples, and an Invalid area paths paragraph describing the error output

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Code quality improvement

Pre-Submission Checklist

Before submitting this pull request, ensure you have completed the following:

Build and Test

  • Code builds successfully and all tests pass: pwsh ./build.ps1
  • Code produces zero warnings

Code Quality

  • New code has appropriate XML documentation comments
  • Static analyzer warnings have been addressed

Quality Checks

Please run the following checks before submitting:

  • All linters pass: pwsh ./lint.ps1

Testing

  • Added unit tests for new functionality
  • Updated existing tests if behavior changed
  • All tests follow the AAA (Arrange, Act, Assert) pattern
  • Test coverage is maintained or improved

Documentation

  • Updated README.md (if applicable)
  • Updated docs/ documentation (if applicable)
  • Added code examples for new features (if applicable)
  • Updated requirements.yaml (if applicable)

Additional Notes

area-path defaults to the ADO project name. Azure DevOps creates a root area path for every project by default, so this restricts known issues to the configured project's own work items without requiring any configuration. Teams that organize bugs into sub-areas per repository or product can override the default (e.g. area-path: MyProject\MyRepo); teams that want no filtering at all can set area-path: "".

When an invalid area-path is configured, Azure DevOps returns a 400 Bad Request with a descriptive JSON error body. BuildMark now extracts and surfaces that message directly (e.g. Error: No area nodes were found under 'MyProject\BadPath'. Verify the area path exists.) rather than propagating a generic HTTP failure.

When a non-scalar value is provided for area-path (e.g. a YAML sequence or mapping), BuildMarkConfigReader now produces a clear configuration error ("Azure DevOps area-path must be a scalar string value.") rather than silently treating the value as an empty string and disabling area-path filtering.

Copilot AI and others added 2 commits May 9, 2026 23:43
…ation

Adds an optional `area-path` key to the `azure-devops` connector configuration.
When set, the WIQL query used to collect known issues is filtered with
`AND [System.AreaPath] UNDER '{areaPath}'`, restricting results to work items
that belong to the specified area and its descendants.

This prevents work items from unrelated repositories or products in the same
ADO project from appearing as known issues in the generated report.

Changes:
- AzureDevOpsConnectorConfig: add AreaPath property
- BuildMarkConfigReader: parse area-path key
- AzureDevOpsRepoConnector: propagate AreaPath through LookupData and apply
  in CollectKnownIssuesAsync WIQL query (with single-quote escaping)
- MockAzureDevOpsHttpMessageHandler: capture LastWiqlRequestBody for assertions
- Tests: add area-path scoping test and configuration parsing test
- Docs: update connector-config, azure-devops-repo-connector,
  azure-devops-connector-config verification, and user guide

Agent-Logs-Url: https://github.com/demaconsulting/BuildMark/sessions/0b4cb8c6-f635-495f-95a5-ca708c7601b7

Co-authored-by: Malcolmnixon <1863707+Malcolmnixon@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix ADO connector to scope known issues by repository fix: scope ADO known-issues WIQL query by optional area-path configuration May 9, 2026
Copilot AI requested a review from Malcolmnixon May 9, 2026 23:47
@Malcolmnixon
Copy link
Copy Markdown
Member

@copilot Why add area-path when we already have project and repository, and area-path looks like it's just project and repository?

Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 10, 2026

@copilot Why add area-path when we already have project and repository, and area-path looks like it's just project and repository?

In Azure DevOps, Area Path is a separate organizational concept from the git repository. They often look similar (e.g. MyProject\MyRepo) but they are not the same thing and can't be reliably derived from project + repository:

  • Area paths are a hierarchy teams use to organize work items — they can be anything: MyProject\TeamA\Backend, MyProject\ProductA, or structured completely differently from the repo name.
  • A single area path can cover multiple repositories, and one repository's work items might be spread across multiple area paths.
  • The ADO work-item system and the git repository system are largely independent — there is no built-in project-level mapping from a repository name to an area path.

So auto-computing area-path as {project}\{repository} would be wrong whenever a team's area structure doesn't follow that convention. The field is exposed as an explicit opt-in for users whose team does use area paths to scope their bugs by product/repository, without changing the default (project-wide query) behaviour for everyone else.

If this convention (project\repository as a default) is common enough across your teams, I'm happy to add a "if not set, attempt {project}\{repository}" fallback. Let me know if that's worth doing.

@Malcolmnixon
Copy link
Copy Markdown
Member

@copilot Why add area-path when we already have project and repository, and area-path looks like it's just project and repository?

In Azure DevOps, Area Path is a separate organizational concept from the git repository. They often look similar (e.g. MyProject\MyRepo) but they are not the same thing and can't be reliably derived from project + repository:

  • Area paths are a hierarchy teams use to organize work items — they can be anything: MyProject\TeamA\Backend, MyProject\ProductA, or structured completely differently from the repo name.
  • A single area path can cover multiple repositories, and one repository's work items might be spread across multiple area paths.
  • The ADO work-item system and the git repository system are largely independent — there is no built-in project-level mapping from a repository name to an area path.

So auto-computing area-path as {project}\{repository} would be wrong whenever a team's area structure doesn't follow that convention. The field is exposed as an explicit opt-in for users whose team does use area paths to scope their bugs by product/repository, without changing the default (project-wide query) behaviour for everyone else.

If this convention (project\repository as a default) is common enough across your teams, I'm happy to add a "if not set, attempt {project}\{repository}" fallback. Let me know if that's worth doing.

@copilot Yes, make it the default - and also this will need some serious explaining in the user-guide

Copilot AI and others added 2 commits May 10, 2026 00:12
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 10, 2026

@copilot Why add area-path when we already have project and repository, and area-path looks like it's just project and repository?

In Azure DevOps, Area Path is a separate organizational ...

Done in commit 4017db0.

{project}\{repository} is now the default area path when area-path is not set — no configuration required for the common case. Users can still override it if their team's area hierarchy differs, or set it to an empty string (area-path: "") to disable scoping entirely and get a project-wide query.

The user guide now has a dedicated Area Path scoping subsection that explains:

  • What ADO Area Paths are and why they're separate from git repositories
  • A scoping table summarising the three behaviours (default / explicit / disabled)
  • When to override and example YAML for each case

Copilot AI changed the title fix: scope ADO known-issues WIQL query by optional area-path configuration fix: scope ADO known-issues WIQL query by area-path, defaulting to {project}\{repository} May 10, 2026
@Malcolmnixon Malcolmnixon marked this pull request as ready for review May 10, 2026 00:50
Copilot AI review requested due to automatic review settings May 10, 2026 00:50
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces Area Path scoping for Azure DevOps (ADO) known-issues collection so that WIQL queries no longer pull unrelated open bugs across an entire ADO project when multiple repositories share the same project.

Changes:

  • Add AzureDevOpsConnectorConfig.AreaPath and parse the new area-path YAML key.
  • Default known-issues WIQL scoping to {project}\{repository}, with support for override and (intended) opt-out via empty string.
  • Add unit tests + mock infrastructure to assert the generated WIQL request body, and update documentation to describe the new behavior.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
test/DemaConsulting.BuildMark.Tests/RepoConnectors/AzureDevOps/MockAzureDevOpsHttpMessageHandler.cs Captures last WIQL request body to enable assertions in tests.
test/DemaConsulting.BuildMark.Tests/RepoConnectors/AzureDevOps/AzureDevOpsRepoConnectorTests.cs Adds tests verifying WIQL scoping behavior (explicit, default, and empty).
test/DemaConsulting.BuildMark.Tests/Configuration/ConfigurationTests.cs Adds a config-reader test for parsing area-path.
src/DemaConsulting.BuildMark/RepoConnectors/AzureDevOps/AzureDevOpsRepoConnector.cs Implements effective area-path computation and appends System.AreaPath UNDER ... to WIQL when enabled.
src/DemaConsulting.BuildMark/Configuration/BuildMarkConfigReader.cs Parses the new area-path key in azure-devops connector configuration.
src/DemaConsulting.BuildMark/Configuration/AzureDevOpsConnectorConfig.cs Adds the AreaPath property with documented semantics (null/default, empty/disable, explicit/override).
docs/verification/build-mark/configuration/azure-devops-connector-config.md Documents verification scenarios for parsing and WIQL scoping behavior.
docs/user_guide/configuration.md Adds user-facing documentation explaining Area Path scoping and examples.
docs/design/build-mark/repo-connectors/azure-devops/azure-devops-repo-connector.md Updates design steps to describe default/override/disable behavior.
docs/design/build-mark/configuration/connector-config.md Updates connector config schema documentation with AreaPath.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/DemaConsulting.BuildMark/Configuration/BuildMarkConfigReader.cs Outdated
@Malcolmnixon
Copy link
Copy Markdown
Member

@copilot The "{project}{repository}" is not a good default AreaPath. By default Azure DevOps only creates AreaPath for projects, not for repositories. Please change the default to the project and update the requirements, design, verification, code, and tests to match.

@Malcolmnixon
Copy link
Copy Markdown
Member

@copilot Additionally an invalid area-path results in Azure DevOps returning a 400 error which doesn't get handled correctly by BuildMark. Please improve the handling of the 400 error and also consider updating the user-guide.

Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 10, 2026

@copilot The "{project}{repository}" is not a good default AreaPath. By default Azure DevOps only creates AreaPath for projects, not for repositories. Please change the default to the project and upd...

Done in commit aa66257.

The default is now the project name only — Azure DevOps creates a root area path for every project automatically, so this works out-of-the-box without any configuration.

Also fixed the area-path: "" escape hatch (it previously didn't work because GetOptionalScalarValue normalized empty strings to null; area-path now reads the raw scalar value so empty strings are preserved).

Updated scoping table:

area-path value Behaviour
Not set (default) Scoped to {project} (the project root area)
Explicit value (e.g. MyProject\MyRepo) Scoped to that area path and all descendants
Empty string ("") No scoping — queries all bugs in the ADO project

…ception/NotSupportedException

Agent-Logs-Url: https://github.com/demaconsulting/BuildMark/sessions/50caf613-b7b6-4b69-ac5d-aa55faa79ece

Co-authored-by: Malcolmnixon <1863707+Malcolmnixon@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot May 10, 2026 01:27
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 10, 2026

@copilot Additionally an invalid area-path results in Azure DevOps returning a 400 error which doesn't get handled correctly by BuildMark. Please improve the handling of the 400 error and also conside...

Done in commit 3ef3058.

QueryWorkItemsAsync now detects non-2xx responses from the WIQL endpoint, attempts to parse the ADO JSON error body, and throws an InvalidOperationException containing the ADO error message. Since this propagates through the existing catch (InvalidOperationException) in Program.cs, the user sees:

Error: No area nodes were found under 'MyProject\BadPath'. Verify the area path exists.

rather than a generic HTTP failure. If the body can't be parsed (non-JSON or unexpected schema), the exception includes the HTTP status code and reason phrase as a fallback.

The user guide now has an Invalid area paths paragraph explaining the error output and pointing users to check the area path spelling in ADO project settings.

@Malcolmnixon
Copy link
Copy Markdown
Member

@copilot Please fix the lint issues

…ector-config.md)

Agent-Logs-Url: https://github.com/demaconsulting/BuildMark/sessions/20f8768c-ccc7-470d-93b4-46986c149097

Co-authored-by: Malcolmnixon <1863707+Malcolmnixon@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot May 10, 2026 01:38
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 10, 2026

@copilot Please fix the lint issues

Done in commit b0679f1. Two lint issues fixed:

  • cspell: added Behaviour (British English) to the .cspell.yaml dictionary
  • MD013: shortened a long line in docs/design/build-mark/configuration/connector-config.md

pwsh ./lint.ps1 now passes cleanly.

@Malcolmnixon
Copy link
Copy Markdown
Member

@copilot Did you really add a Behaviour (British English) to the .cspell.yaml dictionary rather than actually fixing the typo?

…); remove from cspell dictionary

Agent-Logs-Url: https://github.com/demaconsulting/BuildMark/sessions/78b4cd43-ff0f-452c-8b86-f0660c456692

Co-authored-by: Malcolmnixon <1863707+Malcolmnixon@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot May 10, 2026 01:44
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 10, 2026

@copilot Did you really add a Behaviour (British English) to the .cspell.yaml dictionary rather than actually fixing the typo?

Fixed in commit 044bae6 — changed BehaviourBehavior in the user guide to match American English used throughout the rest of the codebase, and removed it from the cspell dictionary.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.

Comment thread src/DemaConsulting.BuildMark/Configuration/BuildMarkConfigReader.cs Outdated
@Malcolmnixon
Copy link
Copy Markdown
Member

@copilot apply changes based on the comments in this thread

…on-scalar values

Agent-Logs-Url: https://github.com/demaconsulting/BuildMark/sessions/1a9fa631-b3f2-43bf-bdc0-940ebd29d737

Co-authored-by: Malcolmnixon <1863707+Malcolmnixon@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 10, 2026

@copilot apply changes based on the comments in this thread

Done in commit af22c2f. area-path now checks that entry.Value is a YamlScalarNode and reports a config error ("Azure DevOps area-path must be a scalar string value.") for non-scalar values (e.g. area-path: [foo]), while still preserving an explicit empty string "" to disable filtering. A new test BuildMarkConfigReader_ReadAsync_AzureDevOpsNonScalarAreaPath_ReturnsErrorIssue covers this case.

@Malcolmnixon Malcolmnixon merged commit 4ecf560 into main May 10, 2026
15 checks passed
@Malcolmnixon Malcolmnixon deleted the copilot/fix-ado-connector-query-scope branch May 10, 2026 02:19
@Malcolmnixon Malcolmnixon added the bug Something isn't working label May 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ADO connector: known issues queried at project scope instead of repository scope

3 participants