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
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
# Changelog

## 0.9.0 — 2026-10-18

Improvements from testing on a real project.

- `ado_pr_update action=update`: change the title or description of a pull request and link work items to it after it
was created (branch policies often require a linked work item), instead of abandoning it and opening a new one.
- People are found by name, e-mail or a display name with a company suffix ("Anna Nowak (Contoso)"); when the identity
search finds nobody, the people picker of the web is asked too.
- Fields required by rules of an inherited process ("required when moving to Resolved") are checked before writing
and listed in `ado_fields type=<type>`, instead of a 400 from Azure DevOps.
- `ado_sprint detail=full`: who has what - open items, tasks and hours left per person.
- `ado_file` on a table, table extension, view or map: a skeleton of properties, fields (type, label, mandatory),
indexes, relations and field groups instead of only the methods.
- `ado_work_item`: links to release stages name the release, the stage, its status and date; commit links give the
full commit id.
- `ado_trace`: with no code linked, an item that speaks of configuration (ER, parameters, setup…) is reported as a
probable change in the system's setup that can be checked only there.

## 0.8.2 — 2026-10-18

Fewer false alarms in sprints and checks.
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ What each MCP tool answers, when Claude should reach for it, and the CLI command
| `ado_trace` | was it done as asked, and where is it: the acceptance criteria (from the parent for a task) and whether they changed after coding started; changesets, commits and pull requests linked to the item and its tasks with the X++ objects they touch; builds (also those deleted by retention); every release stage of the code's branches with **YES / NOT YET**, labelled by the environment map (`staging` → UAT); for Git, whether the change is merged into each mapped branch; tests, tasks and warning signals | "did they do what I asked", "is it on UAT yet" | `adolens trace 123` |
| `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); `merged=<branch>`: those completed into a branch (`since`, `top`, `repo`) with their work items; 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_pr_update` | `create` (repo, source, target, title, markdown text, work items, reviewers, draft, tags), `update` (title, description, link work items - for branch policies), `comment` (general or on a file and line), `reply`, `resolve`, `vote`, `tag`, `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); `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"` |
Expand All @@ -232,7 +232,7 @@ What each MCP tool answers, when Claude should reach for it, and the CLI command
| `ado_review` | checks of a change, or of every change of a work item and its tasks, before testing: work item linked, labels added or used exist in every required language (`config --label-languages`), X++ messages from labels, no TODO left | before moving to test, PR review | `adolens review 523`, `adolens review --item 123` |
| `ado_my_day` | since the previous working day (or `since`): where you were mentioned (all projects), your items and followed items changed by others, pull requests waiting for your review | the start of the day | `adolens myday --since 3d` |
| `ado_search` | one search over work items (full text, with the field that matched), wiki pages and code (Git and TFVC, X++ objects named), grouped; a part that is not available (e.g. Code Search not installed) is reported and the rest still answers | "how does X work", "where is Y" | `adolens search "credit limit" --scope work;wiki` |
| `ado_file` | a file by TFVC path, Git path + repo, or just its name (found through Code Search); X++ objects as their list of methods, `part` shows one method; `history=true`: who changed it, when, through which pull request and for which work items | "what does this object do", "why is it like this" | `adolens file CreditCheck.xml --part validate`, `adolens file CreditCheck.xml --history` |
| `ado_file` | a file by TFVC path, Git path + repo, or just its name (found through Code Search); X++ objects as their list of methods (tables, extensions, views also as fields, indexes and relations), `part` shows one method; without `version` it names the branch read; `history=true`: who changed it, when, through which pull request and for which work items | "what does this object do", "why is it like this" | `adolens file CreditCheck.xml --part validate`, `adolens file CreditCheck.xml --history` |
| `ado_health` | backlog hygiene of a team's open items (newest first): no acceptance criteria, description, estimate or parent, no change for 14+ days while in progress, left in a finished sprint, look-alike titles (series like "Sprint 202 / 203" excluded); items done in the last 30 days without any linked code | refinement, keeping the backlog clean | `adolens health --team "Demo Team"` |
| `ado_release_notes` | markdown release notes: requirements and bugs finished in a sprint or between dates, requirements grouped under their feature | sprint review, a release page on the wiki | `adolens release-notes previous`, `adolens release-notes --since 14d` |
| `ado_undo` | reverts the last change ADO Lens made: field values back (fields someone changed since are left and named), comment deleted, link removed or added back, a created item set to its removed state; always a preview first; `list=true` shows recent changes | "undo that", a mistaken mass change | `adolens undo --list`, `adolens undo` |
Expand Down
4 changes: 2 additions & 2 deletions README.pl.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ Na co odpowiada każde narzędzie MCP, kiedy Claude powinien po nie sięgnąć i
| `ado_trace` | czy zrobiono to, o co proszono, i gdzie to jest: kryteria akceptacji (dla zadania — z elementu nadrzędnego) i to, czy zmieniły się po rozpoczęciu prac; changesety, commity i pull requesty podpięte pod element i jego zadania, z obiektami X++, których dotyczą; buildy (także usunięte przez retencję); każdy etap release'u gałęzi tego kodu z **TAK / JESZCZE NIE** i etykietą z mapy środowisk (`staging` → UAT); dla Gita — czy zmiana jest scalona do każdej zmapowanej gałęzi; testy, zadania i sygnały ostrzegawcze | „czy zrobili to, o co prosiłem”, „czy to już jest na UAT” | `adolens trace 123` |
| `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` | `merged=<gałąź>`: pull requesty scalone do gałęzi (`since`, `top`, `repo`) z ich work itemami; 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_pr_update` | `create` (repo, źródło, cel, tytuł, opis w markdownie, work itemy, recenzenci, szkic, tagi), `update` (tytuł, opis, podpięcie work itemów - pod polityki gałęzi), `comment` (ogólny albo na pliku i linii), `reply`, `resolve`, `vote`, `tag`, `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); `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"` |
Expand All @@ -233,7 +233,7 @@ Na co odpowiada każde narzędzie MCP, kiedy Claude powinien po nie sięgnąć i
| `ado_review` | sprawdzenie zmiany albo wszystkich zmian work itemu i jego tasków przed testami: powiązany work item, etykiety we wszystkich wymaganych językach (`config --label-languages`), komunikaty X++ z etykiet, brak TODO | przed przekazaniem do testów, przegląd PR | `adolens review 523`, `adolens review --item 123` |
| `ado_my_day` | od poprzedniego dnia roboczego (albo `since`): gdzie cię oznaczono (wszystkie projekty), twoje i obserwowane elementy zmienione przez innych, pull requesty czekające na twój przegląd | początek dnia | `adolens myday --since 3d` |
| `ado_search` | jedno wyszukiwanie po work itemach (pełny tekst, z polem, w którym znaleziono), stronach wiki i kodzie (Git i TFVC, z nazwami obiektów X++), pogrupowane; niedostępna część (np. brak Code Search) jest zgłaszana, reszta odpowiada | „jak działa X”, „gdzie jest Y” | `adolens search "limit kredytowy" --scope work;wiki` |
| `ado_file` | plik po ścieżce TFVC, ścieżce Git + repo albo samej nazwie (szukanej przez Code Search); obiekty X++ jako lista metod, `part` pokazuje jedną metodę; `history=true`: kto zmieniał, kiedy, w którym pull requeście i dla jakich work itemów | „co robi ten obiekt”, „dlaczego tak działa” | `adolens file CreditCheck.xml --part validate`, `adolens file CreditCheck.xml --history` |
| `ado_file` | plik po ścieżce TFVC, ścieżce Git + repo albo samej nazwie (szukanej przez Code Search); obiekty X++ jako lista metod (tabele, rozszerzenia, widoki także jako pola, indeksy i relacje), `part` pokazuje jedną metodę; bez `version` podaje, z której gałęzi czyta; `history=true`: kto zmieniał, kiedy, w którym pull requeście i dla jakich work itemów | „co robi ten obiekt”, „dlaczego tak działa” | `adolens file CreditCheck.xml --part validate`, `adolens file CreditCheck.xml --history` |
| `ado_health` | higiena otwartych elementów zespołu (od najnowszych): brak kryteriów akceptacji, opisu, szacunku albo rodzica, w toku bez zmian od 14+ dni, zostawione w zakończonym sprincie, podobne tytuły (serie typu „Sprint 202 / 203” pominięte); elementy zakończone w ostatnich 30 dniach bez powiązanego kodu | refinement, porządek w backlogu | `adolens health --team "Demo Team"` |
| `ado_release_notes` | notatki wydania w markdownie: wymagania i błędy zakończone w sprincie albo między datami, wymagania pogrupowane pod feature'ami | przegląd sprintu, strona wydania na wiki | `adolens release-notes previous`, `adolens release-notes --since 14d` |
| `ado_undo` | cofa ostatnią zmianę zrobioną przez ADO Lens: przywraca wartości pól (pola zmienione w międzyczasie przez innych zostają i są wymienione), usuwa komentarz, usuwa lub przywraca link, utworzony element przestawia w stan usunięty; zawsze najpierw podgląd; `list=true` pokazuje ostatnie zmiany | „cofnij to”, pomyłka przy zmianie wsadowej | `adolens undo --list`, `adolens undo` |
Expand Down
2 changes: 1 addition & 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.8.2</Version>
<Version>0.9.0</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
15 changes: 15 additions & 0 deletions src/AdoLens/Boards.cs
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,21 @@ public static async Task<string> SprintAsync(Lens lens, string? connection, stri
}
if (detail == "summary") return sb.ToString().TrimEnd();

if (detail == "full")
{
// Per person: their open items and tasks and the hours left on them.
var people = open.GroupBy(w => WorkItems.Field(w, "System.AssignedTo") ?? "(unassigned)")
.Select(g => (Who: g.Key, Items: g.Count(w => Level(s, w) != Levels.Task), Tasks: g.Count(w => Level(s, w) == Levels.Task),
Left: g.Sum(w => Number(w, RemainingRef) ?? 0)))
.OrderByDescending(p => p.Items + p.Tasks).ToList();
if (people.Count > 0)
{
sb.AppendLine("by person (open):");
foreach (var p in people)
sb.AppendLine($" {p.Who}: {p.Items} item(s), {p.Tasks} task(s){(p.Left > 0 ? $", {Num(p.Left)} h left" : "")}");
}
}

foreach (var g in new[] { "in progress", "not started", "done" })
{
if (!groups.TryGetValue(g, out var list)) continue;
Expand Down
49 changes: 49 additions & 0 deletions src/AdoLens/Diff.cs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,55 @@ public static Dictionary<string, string> Methods(XDocument? doc)
}

/// <summary>The XML without method bodies: what is left are fields, controls and properties.</summary>
/// <summary>Collections shown in a skeleton, with the properties worth a word for their items.</summary>
static readonly Dictionary<string, string[]> SkeletonParts = new(StringComparer.OrdinalIgnoreCase)
{
["Fields"] = ["ExtendedDataType", "EnumType", "Label", "Mandatory", "StringSize"],
["FieldModifications"] = ["Label", "Mandatory", "ExtendedDataType"],
["FieldGroups"] = [],
["FieldGroupExtensions"] = [],
["Indexes"] = ["AllowDuplicates", "AlternateKey"],
["Relations"] = ["RelatedTable", "Cardinality", "RelationshipType"],
["RelationExtensions"] = [],
["DataSources"] = ["Table"],
["Mappings"] = [],
};

static readonly string[] SkeletonRoot = ["Label", "TableGroup", "TableType", "Extends", "ConfigurationKey", "PrimaryIndex", "ClusteredIndex", "TitleField1", "TitleField2"];

/// <summary>
/// The metadata of an AOT object in a few lines (table, table extension, view, map): properties, then each field with
/// its type and label, indexes with their fields, relations with their table - instead of the raw XML.
/// </summary>
public static string Skeleton(XDocument? doc)
{
if (doc?.Root is not { } root) return "";
var sb = new StringBuilder();
string? Value(XElement e, string name) => e.Elements().FirstOrDefault(x => x.Name.LocalName == name)?.Value.Trim() is { Length: > 0 } v ? v : null;
var props = SkeletonRoot.Select(p => (p, v: Value(root, p))).Where(x => x.v is not null).Select(x => $"{x.p} {x.v}").ToList();
if (props.Count > 0) sb.AppendLine(string.Join("; ", props));
foreach (var part in root.Elements().Where(e => SkeletonParts.ContainsKey(e.Name.LocalName)))
{
var items = part.Elements().ToList();
if (items.Count == 0) continue;
var keys = SkeletonParts[part.Name.LocalName];
sb.AppendLine($"{part.Name.LocalName} ({items.Count}):");
foreach (var item in items.Take(80))
{
var name = Value(item, "Name") ?? item.Name.LocalName;
var extra = keys.Select(k => (k, v: Value(item, k))).Where(x => x.v is not null)
.Select(x => x.k is "ExtendedDataType" or "EnumType" or "RelatedTable" or "Table" or "Label" ? x.v! : $"{x.k} {x.v}").ToList();
// Index and group members, relation constraints: their field names.
var members = item.Descendants().Where(d => d.Name.LocalName is "DataField" or "Field" && d.Parent != item)
.Select(d => d.Value.Trim()).Where(v => v.Length > 0 && !v.Contains('\n')).Distinct().Take(8).ToList();
if (members.Count > 0) extra.Add("(" + string.Join(", ", members) + ")");
sb.AppendLine($"- {name}{(extra.Count > 0 ? ": " + string.Join(", ", extra) : "")}");
}
if (items.Count > 80) sb.AppendLine($"- … {items.Count - 80} more (detail=full)");
}
return sb.ToString().TrimEnd();
}

public static string Metadata(XDocument? doc)
{
if (doc?.Root is null) return "";
Expand Down
Loading