Skip to content

[Bug] ADO selective bridge import drops work item fields and generates numeric-only change names #425

@djm81

Description

@djm81

Describe the Bug

Selective Azure DevOps import through specfact project sync bridge fails for valid work items because the ADO adapter returns a reduced summary object instead of the raw work item payload expected by the import path.

Specifically:

  • AdoAdapter.fetch_backlog_item() calls _get_work_item_data().
  • _get_work_item_data() returns only title, state, and description.
  • AdoAdapter.import_artifact() expects the native ADO work item shape with a populated fields object.
  • The import then fails with ADO work item must have fields.

There is also a follow-up usability problem after import succeeds via local hotfix: when the ADO item has no existing OpenSpec metadata footer/comment, the imported OpenSpec change name falls back to the numeric work item ID instead of a meaningful kebab-case slug derived from the work item title.

To Reproduce

Steps to reproduce the behavior:

specfact project sync bridge --adapter ado --mode bidirectional --ado-org my-org --ado-project my-project --repo /path/to/repo --bundle demo-bundle --backlog-ids 123456

Example:

specfact project sync bridge --adapter ado --mode bidirectional --ado-org my-org --ado-project my-project --repo /path/to/repo --bundle demo-bundle --backlog-ids 234567

Expected Behavior

  • Selective ADO backlog import should fetch the full work item payload and import it as an OpenSpec change proposal without error.
  • The imported change should be created with a meaningful kebab-case change ID derived from the work item title when no prior OpenSpec change ID exists in the ADO description/comments.
  • The work item ID should remain in source tracking, not become the primary OpenSpec change name by default.

Actual Behavior

Before local hotfix, the command fails with:

Failed to import backlog item '123456': Failed to import backlog item as change proposal: ADO work item must have fields

Root cause confirmed locally:

  • specfact_cli.adapters.ado.AdoAdapter._get_work_item_data() returned:
{
    "title": fields.get("System.Title", ""),
    "state": fields.get("System.State", ""),
    "description": fields.get("System.Description", ""),
}

instead of the full ADO work item response containing fields.

After applying a local hotfix to return the full work item payload, the same command succeeds and creates OpenSpec changes, but the generated change names still default to the numeric ADO work item ID because ADO import falls back to str(item_data.get("id")) when no OpenSpec metadata is present.

Environment

  • OS: Windows
  • Python Version: 3.13
  • SpecFact CLI Version: 0.42.2
  • Installation Method: Installed CLI via Python Scripts / local Windows Python installation

Command Output

Failing output before local hotfix:

SpecFact CLI - v0.42.2

⏱️  Started: 2026-03-20 11:04:12
Syncing ado artifacts from: .
Selected backlog items (1): 123456
✗ Import failed with 1 errors
  • Failed to import backlog item '123456': Failed to import backlog item as change proposal: ADO work item must have fields

✓ Finished: 2026-03-20 11:04:15 | Duration: 2.67s

Successful output after local hotfix:

SpecFact CLI - v0.42.2

⏱️  Started: 2026-03-20 11:30:31
Syncing ado artifacts from: .
Selected backlog items (1): 123456
✓ Created OpenSpec change: 123456 at /path/to/repo/openspec/changes/123456
✓ Imported 1 backlog item(s)
✓ Exported 0 backlog item(s)

✓ Finished: 2026-03-20 11:30:34 | Duration: 2.80s

Codebase Context (for brownfield issues)

  • Project Type: Plain Python CLI used from a brownfield internal repository with OpenSpec changes
  • Codebase Size: Multi-folder enterprise repository using ADO bridge import workflows
  • Python Version in Target Codebase: 3.12 for the target repo environment, 3.13 for the installed SpecFact CLI runtime

Additional Context

Verified local code paths:

  • specfact_cli.sync.bridge_sync.import_backlog_items_to_bundle() calls adapter.fetch_backlog_item(item_ref) and then adapter.import_artifact(...).
  • specfact_cli.adapters.ado.AdoAdapter.import_artifact() requires a work item dict with fields.
  • specfact_cli.adapters.ado.AdoAdapter._get_work_item_data() currently strips the API response down to summary fields, causing the import contract mismatch.
  • specfact_cli.adapters.ado.AdoAdapter.extract_change_proposal_data() falls back to str(item_data.get("id")) when no OpenSpec footer or comment exists.
  • specfact_cli.adapters.backlog_base.BacklogAdapterBase.import_backlog_item_as_proposal() then uses that fallback as ChangeProposal.name.

Local hotfix that resolved the import failure:

  • Change _get_work_item_data() to return the full ADO work item payload.
  • Preserve top-level compatibility keys like title, state, and description for existing call sites.

Proposed improvement for change naming:

  1. If no existing OpenSpec change ID is found in the ADO description/comments, derive a kebab-case change ID from System.Title instead of using the numeric work item ID.
  2. Add a shared slug-generation helper so all backlog adapters can normalize imported proposal names consistently.
  3. If a generated slug already exists, append a short deterministic suffix such as -123456 instead of falling back to the raw numeric ID as the whole change name.
  4. Keep the numeric ADO work item ID only in source_tracking.source_id and related metadata.

Suggested implementation direction:

  • In AdoAdapter.extract_change_proposal_data(), replace the final fallback change_id = str(item_data.get("id", "unknown")) with title-based slug generation.
  • Or, more generally, in BacklogAdapterBase.import_backlog_item_as_proposal(), if proposal_data["change_id"] is missing or numeric-only, derive a slug from proposal_data["title"] before constructing ChangeProposal.

Optional follow-up:

  • Extend _parse_work_item_reference() to also accept ADO visualstudio.com/.../_workitems/edit/<id> URLs in addition to numeric IDs and dev.azure.com/... URLs.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

Projects

Status

Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions