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

## 0.10.0 — 2026-10-26

Environments per repository, code in another project, a tidier installer page.

- Environment map keys `Repository:branch` ("ERP:staging=UAT (sbx03)"): a repository with keys of its own uses only
those, so the same branch name can mean different environments - or none - in different repositories. `ado_trace`
no longer reports "NOT merged yet" for branches a repository does not use. Plain keys (`staging=UAT`) work as before.
- Detecting the flow (installer, `adolens setup`, `ado_settings`): with several repositories each branch is named with
its repository; repositories without a commit in the last half year and work branches (`000000_TestBranch`,
`123456_LoginForm_…`) are left out.
- `adolens config --code-project <name>` / `ado_settings codeProject=<name>`: the project with the code, pull requests
and pipelines when it is not the work items' one; `ado_file`, the code part of `ado_search`, `ado_pr`,
`ado_pr_update`, `ado_pipeline`, `ado_changes` and `ado_label` look there.
- Installer, environments page: a larger wizard, as many rows as fit (the rest is named, to set later in the settings),
the repository shown next to each branch, and one drop-down for where to save instead of three options that did not
fit.

## 0.9.5 — 2026-10-26

- `ado_file` by name works without Code Search (not installed or failing): the file lists of the TFVC Metadata
Expand All @@ -14,7 +31,7 @@
- `ado_trace`: a branch earlier in the environment flow than the one a change was merged into is not reported as
"NOT merged yet" (a change merged straight into master of a library repository says nothing about its dev branch).
- Before "not yet", one question per source branch: completed PRs from the same source branch into that branch that
are not linked to the item ("merged by PRs not linked to the item: PR 31002 (2021-09-01, staging <- 243623_…)").
are not linked to the item ("merged by PRs not linked to the item: PR 31002 (2021-09-01, staging <- 123456_…)").

## 0.9.3 — 2026-10-19

Expand Down Expand Up @@ -96,7 +113,7 @@ Fixes from testing on a real project.
setup EXE already did; `uninstall.ps1` takes it off.

- Tool arguments in another JSON shape are read instead of failing with a bare "An error occurred invoking":
`ids=714800` (a number for text), `workItems=[1,2]`, `file=1`, `top="20"`, `draft="true"`. This was behind the failing
`ids=123456` (a number for text), `workItems=[1,2]`, `file=1`, `top="20"`, `draft="true"`. This was behind the failing
`ado_update` (even with only a comment), `ado_pr_update action=create` with work items and `ado_attachments file=<n>`.
An argument that still cannot be read says which and why.
- `ado_attachments`: two files with the same name are listed by number to pick one.
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,19 @@ adolens config --connection fabrikam --env "Main-Deploy/Stage 1=UAT + SCM"
`ado_trace` then lists the stages in that order with their labels ("UAT (sbx03): YES") and, for Git, also checks whether
the work is merged into each mapped branch even when no release pipeline deploys it.

**Several repositories.** The same branch name can mean different things in different repositories — or nothing (a
library merged straight into `master`). A key `Repository:branch` belongs to one repository; a repository with keys of
its own uses only those, the others use the plain branch keys. The installer and `adolens setup` name branches this way
when a project has more than one active repository, and leave out work branches (`000000_TestBranch`, `123456_…`):

```powershell
adolens config --connection contoso --env "ERP:dev=DEV (sbx01)" --env "ERP:staging=UAT (sbx03)" --env "ERP:master=PROD" --env "Common:master=PROD"
```

**Code in another project.** When the work items live in one project and the code, pull requests and pipelines in
another, set it once: `adolens config --connection contoso --code-project ERP` (or ask Claude: `ado_settings
codeProject=ERP`). `ado_file`, `ado_search` (code), `ado_pr`, `ado_pipeline` and `ado_label` then look there.

**One map for the whole team.** The map can live in Azure DevOps itself: a page "ADO Lens" in the project wiki with a
two-column table (branch or release stage | environment), in flow order. Everyone using ADO Lens on the project reads
it, so Product Owners configure nothing; a local map (`--env`) overrides it for one person. The installer, `adolens
Expand Down
13 changes: 13 additions & 0 deletions README.pl.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,19 @@ adolens config --connection fabrikam --env "Main-Deploy/Stage 1=UAT + SCM"
Wtedy `ado_trace` wypisuje etapy w tej kolejności z ich etykietami („UAT (sbx03): YES”), a dla Gita sprawdza też, czy
praca jest scalona do każdej zmapowanej gałęzi, nawet gdy żaden pipeline jej nie wdraża.

**Kilka repozytoriów.** Ta sama nazwa gałęzi może znaczyć co innego w różnych repozytoriach — albo nic (biblioteka
scalana prosto do `master`). Klucz `Repozytorium:gałąź` należy do jednego repozytorium; repozytorium z własnymi kluczami
używa tylko ich, pozostałe — zwykłych kluczy gałęzi. Instalator i `adolens setup` nazywają tak gałęzie, gdy projekt ma
więcej niż jedno aktywne repozytorium, i pomijają gałęzie robocze (`000000_TestBranch`, `123456_…`):

```powershell
adolens config --connection contoso --env "ERP:dev=DEV (sbx01)" --env "ERP:staging=UAT (sbx03)" --env "ERP:master=PROD" --env "Common:master=PROD"
```

**Kod w innym projekcie.** Gdy work itemy są w jednym projekcie, a kod, pull requesty i pipeline'y w innym, ustaw to
raz: `adolens config --connection contoso --code-project ERP` (albo poproś Claude'a: `ado_settings codeProject=ERP`).
Wtedy `ado_file`, `ado_search` (kod), `ado_pr`, `ado_pipeline` i `ado_label` szukają tam.

**Jedna mapa dla całego zespołu.** Mapa może mieszkać w samym Azure DevOps: strona „ADO Lens” w wiki projektu z tabelą
w dwóch kolumnach (gałąź albo etap release'u | środowisko), w kolejności przepływu. Każdy, kto używa ADO Lens w projekcie,
ją czyta, więc Product Ownerzy niczego nie konfigurują; lokalna mapa (`--env`) nadpisuje ją dla jednej osoby. Zapisują
Expand Down
78 changes: 48 additions & 30 deletions packaging/inno/ado-lens.iss
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ OutputBaseFilename=ado-lens-setup-{#Version}
Compression=lzma2/max
SolidCompression=yes
WizardStyle=modern
; Taller and wider than the default: the environments page lists release stages and branches.
WizardSizePercent=120,130
UninstallDisplayName=ADO Lens
UninstallDisplayIcon={app}\bin\adolens.exe
LicenseFile={#Source}\LICENSE
Expand Down Expand Up @@ -88,6 +90,7 @@ en.EnvLater=Later: Start menu > ADO Lens - settings > Environments (change, add,
en.EnvMine=Save for me only (this computer)
en.EnvTeam=Save for the whole team (wiki page "ADO Lens" of the project)
en.EnvSkip=Skip - Claude asks when needed
en.EnvMore=... and %1 more: Start menu > ADO Lens - settings > Environments.
en.EnvFailed=Saving the environments did not work:%n%n%1
en.ReadmeUrl=https://github.com/willowdev-code/ado-lens#readme
en.IconSettings=ADO Lens - settings
Expand All @@ -104,6 +107,7 @@ pl.EnvLater=Później: menu Start > ADO Lens - ustawienia > Środowiska (zmiana,
pl.EnvMine=Zapisz tylko dla mnie (ten komputer)
pl.EnvTeam=Zapisz dla całego zespołu (strona wiki "ADO Lens" w projekcie)
pl.EnvSkip=Pomiń - Claude zapyta, gdy będzie trzeba
pl.EnvMore=... i jeszcze %1: menu Start > ADO Lens - ustawienia > Środowiska.
pl.EnvFailed=Nie udało się zapisać środowisk:%n%n%1
pl.ReadmeUrl=https://github.com/willowdev-code/ado-lens/blob/main/README.pl.md
pl.IconSettings=ADO Lens - ustawienia
Expand Down Expand Up @@ -170,7 +174,8 @@ var
EnvKeys: TArrayOfString;
EnvLabels: array of TNewStaticText;
EnvEdits: array of TNewEdit;
EnvMine, EnvTeam, EnvSkip: TNewRadioButton;
EnvScope: TNewComboBox;
EnvMore: TNewStaticText;
EnvDetected: Boolean;

function SetEnvironmentVariable(lpName: String; lpValue: String): Boolean;
Expand Down Expand Up @@ -384,22 +389,7 @@ begin
EnvInfo.Width := EnvPage.SurfaceWidth;
EnvInfo.Height := ScaleY(40);
EnvInfo.Caption := CustomMessage('EnvDetecting');
EnvMine := TNewRadioButton.Create(EnvPage);
EnvTeam := TNewRadioButton.Create(EnvPage);
EnvSkip := TNewRadioButton.Create(EnvPage);
EnvMine.Parent := EnvPage.Surface;
EnvTeam.Parent := EnvPage.Surface;
EnvSkip.Parent := EnvPage.Surface;
EnvMine.Caption := CustomMessage('EnvMine');
EnvTeam.Caption := CustomMessage('EnvTeam');
EnvSkip.Caption := CustomMessage('EnvSkip');
EnvMine.Width := EnvPage.SurfaceWidth;
EnvTeam.Width := EnvPage.SurfaceWidth;
EnvSkip.Width := EnvPage.SurfaceWidth;
EnvMine.Checked := True;
EnvMine.Visible := False;
EnvTeam.Visible := False;
EnvSkip.Visible := False;
{ One drop-down for where to save, at the bottom above the hint: always visible, whatever the number of rows. }
EnvLater := TNewStaticText.Create(EnvPage);
EnvLater.Parent := EnvPage.Surface;
EnvLater.AutoSize := False;
Expand All @@ -408,17 +398,37 @@ begin
EnvLater.Height := ScaleY(30);
EnvLater.Caption := CustomMessage('EnvLater');
EnvLater.Top := EnvPage.SurfaceHeight - EnvLater.Height;
EnvScope := TNewComboBox.Create(EnvPage);
EnvScope.Parent := EnvPage.Surface;
EnvScope.Style := csDropDownList;
EnvScope.Width := EnvPage.SurfaceWidth;
EnvScope.Items.Add(CustomMessage('EnvMine'));
EnvScope.Items.Add(CustomMessage('EnvTeam'));
EnvScope.Items.Add(CustomMessage('EnvSkip'));
EnvScope.ItemIndex := 0;
EnvScope.Top := EnvLater.Top - EnvScope.Height - ScaleY(6);
EnvScope.Visible := False;
EnvMore := TNewStaticText.Create(EnvPage);
EnvMore.Parent := EnvPage.Surface;
EnvMore.AutoSize := False;
EnvMore.Width := EnvPage.SurfaceWidth;
EnvMore.Height := ScaleY(16);
EnvMore.Top := EnvScope.Top - EnvMore.Height - ScaleY(2);
EnvMore.Visible := False;
end;

{ Fills the environment page from "adolens setup --detect": the map already in use, or one row per stage or branch
found with a suggested name. }
procedure DetectEnvironments;
var
Exe, OutFile, Line, Key, Guess: String;
Exe, OutFile, Line, Key, Guess, Caption: String;
Lines: TArrayOfString;
I, P, N, Top: Integer;
I, P, N, Top, MaxRows, Skipped: Integer;
Code: Integer;
begin
{ As many rows as fit between the text and the drop-down; the rest is named and can be set in the settings later. }
MaxRows := (EnvMore.Top - (EnvInfo.Top + EnvInfo.Height + ScaleY(4))) div ScaleY(26);
Skipped := 0;
EnvDetected := True;
Exe := ExpandConstant('{tmp}\adolens.exe');
OutFile := ExpandConstant('{tmp}\adolens-envs.txt');
Expand All @@ -437,7 +447,9 @@ begin
EnvInfo.Caption := FmtMessage(CustomMessage('EnvExisting'), [Copy(Line, 1, P - 1), Copy(Line, P + 1, Length(Line))]);
Exit;
end;
if (Copy(Line, 1, 4) = 'row' + #9) and (N < 8) then
if (Copy(Line, 1, 4) = 'row' + #9) and (N >= MaxRows) then
Skipped := Skipped + 1
else if Copy(Line, 1, 4) = 'row' + #9 then
begin
Delete(Line, 1, 4);
P := Pos(#9, Line);
Expand All @@ -459,7 +471,14 @@ begin
Top := EnvInfo.Top + EnvInfo.Height + ScaleY(4) + (N - 1) * ScaleY(26);
EnvLabels[N - 1] := TNewStaticText.Create(EnvPage);
EnvLabels[N - 1].Parent := EnvPage.Surface;
EnvLabels[N - 1].Caption := Key;
{ "D365FO:staging" is the branch staging of the repository D365FO. }
Caption := Key;
P := Pos(':', Key);
if (P > 1) and (Pos('/', Key) = 0) then
Caption := Copy(Key, P + 1, Length(Key)) + ' (' + Copy(Key, 1, P - 1) + ')';
EnvLabels[N - 1].Caption := Caption;
EnvLabels[N - 1].AutoSize := False;
EnvLabels[N - 1].Height := ScaleY(18);
EnvLabels[N - 1].Top := Top + ScaleY(3);
EnvLabels[N - 1].Width := EnvPage.SurfaceWidth div 2 - ScaleX(8);
EnvEdits[N - 1] := TNewEdit.Create(EnvPage);
Expand All @@ -476,13 +495,12 @@ begin
Exit;
end;
EnvInfo.Caption := CustomMessage('EnvText');
Top := EnvInfo.Top + EnvInfo.Height + ScaleY(10) + N * ScaleY(26);
EnvMine.Top := Top;
EnvTeam.Top := Top + ScaleY(20);
EnvSkip.Top := Top + ScaleY(40);
EnvMine.Visible := True;
EnvTeam.Visible := True;
EnvSkip.Visible := True;
if Skipped > 0 then
begin
EnvMore.Caption := FmtMessage(CustomMessage('EnvMore'), [IntToStr(Skipped)]);
EnvMore.Visible := True;
end;
EnvScope.Visible := True;
end;

{ Saves the names typed on the environment page; returns False to stay on the page when it failed. }
Expand All @@ -492,7 +510,7 @@ var
I, Code: Integer;
begin
Result := True;
if EnvSkip.Checked or (GetArrayLength(EnvKeys) = 0) then Exit;
if (EnvScope.ItemIndex = 2) or (GetArrayLength(EnvKeys) = 0) then Exit;
Env := '';
for I := 0 to GetArrayLength(EnvKeys) - 1 do
begin
Expand All @@ -506,7 +524,7 @@ begin
end;
end;
if Env = '' then Exit;
if EnvTeam.Checked then Scope := 'team' else Scope := 'local';
if EnvScope.ItemIndex = 1 then Scope := 'team' else Scope := 'local';
Code := RunAdoLens(ExpandConstant('{tmp}\adolens.exe'), 'setup --env "' + Env + '" --scope ' + Scope, Output);
if Code <> 0 then
begin
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.9.5</Version>
<Version>0.10.0</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
4 changes: 3 additions & 1 deletion src/AdoLens/Cli.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ logout remove the stored PAT of a connection --connection <name>
connections list the connections
config show or change settings
--connection <name> --project <name> --team <name> (defaults of a connection)
--code-project <name> (code, pull requests and pipelines in another project; "" clears)
--default-connection <name> --mode po|dev|full --usage-log true|false
--show-tokens true|false --tool-loading auto|demand|all --add-toolset <set> --remove-toolset <set> --confirm-above <n>
--language pl|en|auto (settings console and installer texts)
Expand Down Expand Up @@ -243,7 +244,7 @@ public int Connections()
{
var pat = CredentialStore.Read(name) is not null ? "PAT stored" : "no stored PAT";
Console.WriteLine($"{(name.Equals(settings.DefaultConnection, StringComparison.OrdinalIgnoreCase) ? "*" : " ")} {name,-16} " +
$"https://dev.azure.com/{c.Organization} project {c.Project ?? "-"} team {c.Team ?? "-"} ({pat})" +
$"https://dev.azure.com/{c.Organization} project {c.Project ?? "-"}{(c.CodeProject is { } cp ? $" code in {cp}" : "")} team {c.Team ?? "-"} ({pat})" +
(c.Environments.Count > 0 ? $"\n {"",-16} environments: {string.Join(", ", c.Environments.Select(e => $"{e.Key} = {e.Value}"))}" : "") +
(c.LabelLanguages.Count > 0 ? $"\n {"",-16} label languages: {string.Join(", ", c.LabelLanguages)}" : ""));
}
Expand All @@ -262,6 +263,7 @@ public int Config()
throw new ArgumentException($"no connection named '{name}' - add it with 'adolens login --connection {name}'");
if (cl.Opt("project") is { } p) { conn.Project = NullIfEmpty(p); changed = true; }
if (cl.Opt("team") is { } t) { conn.Team = NullIfEmpty(t); changed = true; }
if (cl.Opt("code-project") is { } cp) { conn.CodeProject = NullIfEmpty(cp); changed = true; }
while (cl.Opt("env") is { } e)
{
var eq = e.IndexOf('=');
Expand Down
2 changes: 1 addition & 1 deletion src/AdoLens/CommandLine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public sealed partial class CommandLine
["pr"] = ["connection=", "project=", "repo=", "list=", "detail=", "merged=", "since=", "top="],
["pipeline"] = ["connection=", "project=", "definition=", "branch=", "top=", "detail=", "artifacts", "artifact=", "part=", "save", "to=", "deployments", "stage="],
["changes"] = ["connection=", "repo=", "file=", "detail="],
["config"] = ["env=", "remove-env=", "connection=", "default-connection=", "project=", "team=", "mode=", "usage-log=", "add-toolset=", "remove-toolset=", "confirm-above=", "show-tokens=", "tool-loading=", "language=", "label-languages=", "download-dir="],
["config"] = ["env=", "remove-env=", "connection=", "default-connection=", "project=", "code-project=", "team=", "mode=", "usage-log=", "add-toolset=", "remove-toolset=", "confirm-above=", "show-tokens=", "tool-loading=", "language=", "label-languages=", "download-dir="],
["item"] = ["connection=", "include=", "detail="],
["query"] = ["connection=", "project=", "wiql=", "query=", "top="],
["create"] = ["connection=", "project=", "type=", "title=", "description=", "criteria=", "assign=", "parent=", "iteration=", "area=", "tags=", "state=", "field=", "template="],
Expand Down
Loading