Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# Changelog

## 0.7.0 — 2026-10-15

Files from Azure DevOps: read what can be read, save the rest.

- `ado_attachments` reads PDF (text page by page, `pages=2-5`; scanned pages shown as images), PowerPoint, saved e-mails
(.eml) and ZIP files (the list, `part=` one file inside), next to Word, Excel, Outlook e-mails and text.
- The kind of a file comes from its bytes, not its name: binaries (traces, packages) are never sent as text.
- Long documents come as an outline and the start, with their size in tokens; `detail=full` for all.
- `save=true` streams a file to disk at any size; only its path, size and SHA-256 go to Claude. Files over 20 MB or not
readable get a link and the hint to save them, instead of an error.
- Build artifacts: `ado_pipeline build=<id> artifacts=true` lists them, `artifact=<name>` shows the files in one
(`part=` reads a file), `save=true` saves it.
- Download folder: `adolens config --download-dir <folder>` or the settings menu (default Downloads\ADO Lens).
- A required label language with no label file in the model is reported by `ado_review` and `ado_label`.
- THIRD-PARTY-NOTICES.md lists the bundled libraries and their licenses (PdfPig: Apache 2.0); shipped in every package.

## 0.6.0 — 2026-10-15

Screenshots and documents for Claude, D365 labels, checks before testing, ready commands, a settings menu.
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ adolens config --add-toolset pipelines # one more tool set on top of the m
adolens config --confirm-above 3 # updates of more items are previewed first (0: always)
adolens config --usage-log false
adolens config --show-tokens true # end every answer with [~N tokens]
adolens config --download-dir D:\Traces # where save=true puts files (default: Downloads\ADO Lens)
adolens config --connection contoso --label-languages "en-US, pl" # labels must exist in these languages
adolens config --connection contoso --env staging=UAT --env master=PROD # what branches mean (for ado_trace)
```

Expand Down Expand Up @@ -208,13 +210,13 @@ What each MCP tool answers, when Claude should reach for it, and the CLI command
| `ado_changes` | the files of a changeset (`523`), pull request (`PR 482`) or commit (sha + `repo`) with X++ objects and their added / changed / removed methods; `file` gives the diff of one file — for AOT XML per method plus the metadata (fields, controls, properties) without raw XML noise | reading what exactly changed | `adolens changes 523`, `adolens changes 523 --file AxClass/SalesTable_Extension.xml` |
| `ado_pr` | without id: pull requests waiting for my review and mine (`list=active`: all); with id: status, reviewers and votes, checks (build, work item linking, reviewers), linked work items, open comment threads written by people (not system notes) | code review, "what is blocking my PR" | `adolens pr`, `adolens pr 482` |
| `ado_pr_update` | `create` (repo, source, target, title, markdown text, work items, reviewers, draft), `comment` (general or on a file and line), `reply`, `resolve`, `vote`, `publish`, `complete`, `abandon`; `@[name]` notifies | reviewing, opening and finishing PRs | — |
| `ado_pipeline` | recent builds (of a pipeline, a branch); with `build`: the failed stage, job and steps, their errors, and the lines of the log that report errors (MSBuild noise cut) | "why is the build red" | `adolens pipeline`, `adolens pipeline 900` |
| `ado_pipeline` | recent builds (of a pipeline, a branch); with `build`: the failed stage, job and steps, their errors, and the lines of the log that report errors (MSBuild noise cut); `artifacts=true` lists the build's artifacts, `artifact=<name>` the files in one (`part=` reads a file), `save=true` saves it to disk | "why is the build red", getting a package or a log | `adolens pipeline 900`, `adolens pipeline 900 --artifact drop --save` |
| `ado_pipeline_run` | queues a pipeline by name or id, optionally on a branch, with variables | re-running a build | — |
| `ado_wiki` | the project wiki: page tree (`depth`), one page as markdown with its version and subpages, or search across the project's wikis with snippets; long pages are cut unless `detail=full` | "how does X work", finding documentation | `adolens wiki`, `adolens wiki "Team/Setup"`, `adolens wiki --search "dual write"` |
| `ado_wiki_write` | `create` a page, `append` / `prepend` to it, or `replace` it with the version from the last full read — a page changed by someone else in the meantime is never overwritten; optional change comment | release notes, keeping docs current | `adolens wiki-write "Team/Release notes" --file notes.md --mode append` |
| `ado_tests` | the tests of work items (ids) or of a sprint's requirements and bugs: their Test Cases with the latest outcome per plan, suite and configuration (who, when), items without tests; `detail=full` adds the steps | "is it tested", sprint review | `adolens tests 123`, `adolens tests` (current sprint) |
| `ado_test_create` | Test Cases for a work item from markdown (`## title`, steps `1. action => expected`), linked as tested-by, in the item's area and iteration; `plan` + `suite` (id or name) add them to a suite | turning acceptance criteria into tests | `adolens test-create 123 --file cases.md --plan R1 --suite Login` |
| `ado_attachments` | the files of a work item and the screenshots pasted into its text or comments; `file` shows an image to Claude or gives the text of a Word, Excel, Outlook e-mail (.msg) or text file; `add` attaches a local file | specifications and e-mails attached to items, "look at the screenshot" | `adolens attachments 123`, `adolens attachments 123 --file 2` |
| `ado_attachments` | the files of a work item and the screenshots pasted into its text or comments; `file` gives the text of a PDF (`pages=2-5`; scanned pages shown as images), Word, Excel, PowerPoint, e-mail (.msg, .eml), ZIP (list; `part=` one file inside) or text file, or shows an image - the type is read from the bytes, binaries are never sent as text, long documents come as an outline and the start (`detail=full` for all); `save=true` streams the file to disk at any size and returns only its path, size and SHA-256; files over 20 MB or not readable get a link and the hint to save them; `add` attaches a local file | specifications, e-mails and screenshots of items; traces and logs for other tools | `adolens attachments 123 --file 2 --pages 1-3`, `adolens attachments 123 --file 2 --save` |
| `ado_create_tree` | a tree of work items from an indented list (`- Feature: …`, children indented, `\| description`, `@person`), under an existing item if given; previewed first, one undo removes it all | breaking a feature into requirements and tasks | — |
| `ado_metrics` | done items and estimate per finished sprint (velocity), cycle and lead time (median, 85%), what is in progress longest | "are we getting faster", planning | `adolens metrics --sprints 6` |
| `ado_label` | a D365 message pasted as seen (values in place of %1 %2 … are fine) or `@File:Id`: the label, its text in every language (missing ones flagged) and the objects, X++ methods, fields and controls using it | "where does this error come from" | `adolens label "Item 1234: received 12, on hand 5"` |
Expand Down
4 changes: 2 additions & 2 deletions README.pl.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,13 +211,13 @@ Na co odpowiada każde narzędzie MCP, kiedy Claude powinien po nie sięgnąć i
| `ado_changes` | pliki changesetu (`523`), pull requesta (`PR 482`) albo commita (sha + `repo`) z obiektami X++ i ich dodanymi / zmienionymi / usuniętymi metodami; `file` daje diff jednego pliku — dla AOT XML per metoda plus metadane (pola, kontrolki, właściwości) bez szumu surowego XML-a | czytanie, co dokładnie się zmieniło | `adolens changes 523`, `adolens changes 523 --file AxClass/SalesTable_Extension.xml` |
| `ado_pr` | bez id: pull requesty czekające na mój przegląd i moje (`list=active`: wszystkie); z id: stan, recenzenci i głosy, kontrole (build, podpięty work item, recenzenci), podpięte work itemy, otwarte wątki pisane przez ludzi (bez wpisów systemowych) | code review, „co blokuje mój PR” | `adolens pr`, `adolens pr 482` |
| `ado_pr_update` | `create` (repo, źródło, cel, tytuł, opis w markdownie, work itemy, recenzenci, szkic), `comment` (ogólny albo na pliku i linii), `reply`, `resolve`, `vote`, `publish`, `complete`, `abandon`; `@[imię]` wysyła powiadomienie | przegląd, zakładanie i kończenie PR-ów | — |
| `ado_pipeline` | ostatnie buildy (pipeline'u, gałęzi); z `build`: nieudany etap, zadanie i kroki, ich błędy i linie logu zgłaszające błędy (bez szumu MSBuild) | „czemu build jest czerwony” | `adolens pipeline`, `adolens pipeline 900` |
| `ado_pipeline` | ostatnie buildy (pipeline'u, gałęzi); z `build`: nieudany etap, zadanie i kroki, ich błędy i linie logu zgłaszające błędy (bez szumu MSBuild); `artifacts=true` lista artefaktów buildu, `artifact=<nazwa>` pliki w nim (`part=` czyta plik), `save=true` zapisuje go na dysk | „czemu build jest czerwony”, pobranie paczki lub logu | `adolens pipeline 900`, `adolens pipeline 900 --artifact drop --save` |
| `ado_pipeline_run` | uruchamia pipeline po nazwie albo id, opcjonalnie na gałęzi, ze zmiennymi | ponowne puszczenie buildu | — |
| `ado_wiki` | wiki projektu: drzewo stron (`depth`), strona jako markdown z wersją i podstronami albo wyszukiwanie we wszystkich wiki projektu z fragmentami; długie strony są ucinane, chyba że `detail=full` | „jak działa X”, szukanie dokumentacji | `adolens wiki`, `adolens wiki "Team/Setup"`, `adolens wiki --search "dual write"` |
| `ado_wiki_write` | `create` — nowa strona, `append` / `prepend` — dopisanie, `replace` — zastąpienie z wersją z ostatniego pełnego odczytu (strony zmienionej w międzyczasie przez kogoś innego nigdy nie nadpisze); opcjonalny komentarz zmiany | notatki wydania, aktualna dokumentacja | `adolens wiki-write "Team/Release notes" --file notes.md --mode append` |
| `ado_tests` | testy work itemów (id) albo wymagań i błędów sprintu: ich Test Case'y z ostatnim wynikiem w każdym planie, zestawie i konfiguracji (kto, kiedy), elementy bez testów; `detail=full` dodaje kroki | „czy to przetestowane”, przegląd sprintu | `adolens tests 123`, `adolens tests` (bieżący sprint) |
| `ado_test_create` | Test Case'y dla work itemu z markdownu (`## tytuł`, kroki `1. akcja => oczekiwany wynik`), podpięte jako tested-by, w obszarze i iteracji elementu; `plan` + `suite` (id albo nazwa) dodają je do zestawu | kryteria akceptacji zamienione w testy | `adolens test-create 123 --file przypadki.md --plan R1 --suite Logowanie` |
| `ado_attachments` | pliki work itemu i zrzuty ekranu wklejone w jego treść lub komentarze; `file` pokazuje obraz Claude'owi albo daje tekst pliku Word, Excel, maila Outlooka (.msg) lub tekstowego; `add` dołącza plik z dysku | specyfikacje i maile przy elementach, „zobacz zrzut” | `adolens attachments 123`, `adolens attachments 123 --file 2` |
| `ado_attachments` | pliki work itemu i zrzuty ekranu wklejone w jego treść lub komentarze; `file` daje tekst PDF-a (`pages=2-5`; zeskanowane strony pokazane jako obrazy), Worda, Excela, PowerPointa, maila (.msg, .eml), ZIP-a (lista; `part=` jeden plik ze środka) lub pliku tekstowego, albo pokazuje obraz - typ jest rozpoznawany po bajtach, pliki binarne nigdy nie idą jako tekst, długie dokumenty przychodzą jako spis treści i początek (`detail=full` całość); `save=true` zapisuje plik na dysk strumieniowo w dowolnym rozmiarze i zwraca tylko ścieżkę, rozmiar i SHA-256; pliki ponad 20 MB albo nieczytelne dostają link i podpowiedź zapisu; `add` dołącza plik z dysku | specyfikacje, maile i zrzuty przy elementach; trace'y i logi dla innych narzędzi | `adolens attachments 123 --file 2 --pages 1-3`, `adolens attachments 123 --file 2 --save` |
| `ado_create_tree` | drzewo work itemów z listy z wcięciami (`- Feature: …`, dzieci wcięte, `\| opis`, `@osoba`), opcjonalnie pod istniejącym elementem; najpierw podgląd, jedno cofnięcie usuwa całość | rozbicie feature'a na wymagania i taski | — |
| `ado_metrics` | zrobione elementy i szacunek w zakończonych sprintach (prędkość), czas realizacji (mediana, 85%), co najdłużej w toku | „czy przyspieszamy”, planowanie | `adolens metrics --sprints 6` |
| `ado_label` | komunikat D365 wklejony tak, jak widać na ekranie (z wartościami zamiast %1 %2 …) albo `@Plik:Id`: etykieta, jej tekst w każdym języku (brakujące oznaczone) i obiekty, metody X++, pola i kontrolki, które jej używają | „skąd ten błąd” | `adolens label "Towar 1234: przyjęto 12, stan 5"` |
Expand Down
3 changes: 3 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ organization and a `Demo` project) is enough.
Credential Manager still wins. Prefer the installer when Claude Code is used too.
- **Other servers:** `adolens status` reports other MCP entries in the Claude configuration that keep a PAT in plain
text (for example an older Azure DevOps server). It never changes them; remove the PAT there yourself.
- **Saved files:** `save=true` streams a file from Azure DevOps into the download folder (default `Downloads\ADO Lens`,
`adolens config --download-dir`). The file name is cleaned so it cannot point outside that folder, an existing file is
never overwritten, and saved files are never opened or run. Only the path, size and SHA-256 go to Claude.
- **Network:** the server talks only to `https://dev.azure.com/<organization>` (and the `vsrm` / `vssps` hosts of the
same organization) over HTTPS, with the PAT in the `Authorization` header. It opens no network ports.

Expand Down
14 changes: 14 additions & 0 deletions THIRD-PARTY-NOTICES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Third-party notices

ADO Lens is distributed under the MIT license (see LICENSE). The program bundles these libraries; their licenses
apply to them.

| Library | License | Source |
|---|---|---|
| PdfPig (UglyToad.PdfPig and its parts) | Apache License 2.0 | https://github.com/UglyToad/PdfPig |
| ModelContextProtocol (C# SDK) | MIT | https://github.com/modelcontextprotocol/csharp-sdk |
| Microsoft.Data.Sqlite, SQLitePCLRaw | MIT | https://github.com/dotnet/efcore, https://github.com/ericsink/SQLitePCL.raw |
| SQLite | Public domain | https://sqlite.org |
| Microsoft.Extensions.* and the .NET runtime | MIT | https://github.com/dotnet/runtime |

The Apache License 2.0 text: https://www.apache.org/licenses/LICENSE-2.0. PdfPig is used unmodified.
2 changes: 1 addition & 1 deletion install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ else {
}
Copy-Item $f.FullName $dst -Force
}
foreach ($f in 'install.ps1', 'uninstall.ps1', 'README.md', 'README.pl.md', 'CHANGELOG.md', 'SECURITY.md', 'LICENSE') {
foreach ($f in 'install.ps1', 'uninstall.ps1', 'README.md', 'README.pl.md', 'CHANGELOG.md', 'SECURITY.md', 'LICENSE', 'THIRD-PARTY-NOTICES.md') {
if (Test-Path (Join-Path $src $f)) { Copy-Item (Join-Path $src $f) $InstallDir -Force }
}
if (Test-Path (Join-Path $src 'skills')) { Copy-Item (Join-Path $src 'skills') $InstallDir -Recurse -Force }
Expand Down
3 changes: 2 additions & 1 deletion pack.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Write-Host 'publishing (self-contained)...' -ForegroundColor Cyan
if ($LASTEXITCODE -ne 0) { throw 'dotnet publish failed' }

Remove-Item (Join-Path $stage 'bin\*.pdb') -Force -ErrorAction SilentlyContinue
foreach ($f in 'install.ps1', 'uninstall.ps1', 'README.md', 'README.pl.md', 'CHANGELOG.md', 'SECURITY.md', 'LICENSE') {
foreach ($f in 'install.ps1', 'uninstall.ps1', 'README.md', 'README.pl.md', 'CHANGELOG.md', 'SECURITY.md', 'LICENSE', 'THIRD-PARTY-NOTICES.md') {
Copy-Item (Join-Path $PSScriptRoot $f) $stage -Force
}
# Skills for Claude (SKILL.md per folder), installed next to the server.
Expand Down Expand Up @@ -92,6 +92,7 @@ if (-not $NoZip) {
[IO.File]::WriteAllText((Join-Path $mcpbStage 'manifest.json'), $manifest, (New-Object Text.UTF8Encoding $false))
Copy-Item (Join-Path $stage 'bin') $mcpbStage -Recurse
Copy-Item (Join-Path $stage 'LICENSE') $mcpbStage
Copy-Item (Join-Path $stage 'THIRD-PARTY-NOTICES.md') $mcpbStage
Copy-Item (Join-Path $PSScriptRoot 'packaging\icon\icon.png') $mcpbStage
$mcpb = Join-Path $OutDir "ado-lens-$version.mcpb"
New-Zip $mcpbStage $mcpb $false
Expand Down
3 changes: 3 additions & 0 deletions skills/dev-build/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ description: Diagnoses a failed build or release for a developer - the failing s
3. Find the code: the object or file named in the error (`ado_file`, X++: `part=<method>`), and what changed since the
last green build of the same pipeline (`ado_pipeline` lists them; `ado_changes` of the changes between).
4. Who and what: `ado_file history=true` for the last change of that object and its work item.
5. Logs, packages and traces kept as build artifacts: `ado_pipeline build=<id> artifacts=true`, then `artifact=<name>
part=<file>` to read one file, or `save=true` to put it on disk for another tool (a trace parser, a diff tool) -
only the path comes back, not the content.

## Answer

Expand Down
25 changes: 25 additions & 0 deletions src/AdoLens/AdoClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,31 @@ public async Task<byte[]> GetBytesAsync(string url, long maxBytes, CancellationT
}
}

/// <summary>Streams a file into <paramref name="target"/> without holding it in memory; returns the bytes written.</summary>
public async Task<long> DownloadToAsync(string url, Stream target, CancellationToken ct = default)
{
url = WithApiVersion(url);
if (Uri.TryCreate(url, UriKind.Absolute, out var abs) && !IsAzureDevOpsHost(abs))
throw new AdoApiException($"refusing to send the PAT to {abs.Host}: only Azure DevOps hosts are allowed");
for (var attempt = 1; ; attempt++)
{
using var req = new HttpRequestMessage(HttpMethod.Get, url);
req.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/octet-stream"));
req.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/zip"));
using var res = await http.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, ct);
if (attempt < MaxAttempts && RetryDelay(res) is { } delay)
{
await Delay(delay, ct);
continue;
}
if (!res.IsSuccessStatusCode || res.Content.Headers.ContentType?.MediaType == "text/html") await EnsureSuccessAsync(res, ct);
await using var source = await res.Content.ReadAsStreamAsync(ct);
var before = target.CanSeek ? target.Position : 0;
await source.CopyToAsync(target, 1 << 20, ct);
return target.CanSeek ? target.Position - before : res.Content.Headers.ContentLength ?? -1;
}
}

/// <summary>Uploads a file as a work item attachment; returns its URL.</summary>
public async Task<string> UploadAttachmentAsync(string project, string fileName, byte[] content, CancellationToken ct = default)
{
Expand Down
3 changes: 2 additions & 1 deletion src/AdoLens/AdoLens.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<AssemblyTitle>ADO Lens for Azure DevOps</AssemblyTitle>
<Description>Azure DevOps (work items, boards, wiki, Git and TFVC, pipelines) served to AI assistants over MCP</Description>
<Copyright>Copyright © 2026 WillowDev</Copyright>
<Version>0.6.0</Version>
<Version>0.7.0</Version>
</PropertyGroup>

<ItemGroup>
Expand All @@ -34,6 +34,7 @@
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.12" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.12" />
<PackageReference Include="ModelContextProtocol" Version="2.2.0" />
<PackageReference Include="PdfPig" Version="0.1.16" />
</ItemGroup>

</Project>
Loading