Skip to content

[DEV-1573] Spectre.Console UX for setup wizard + history import - #30

Merged
alexeyzimarev merged 12 commits into
mainfrom
feature/dev-1573-spectre-console
Apr 23, 2026
Merged

alexeyzimarev merged 12 commits into
mainfrom
feature/dev-1573-spectre-console

Conversation

@alexeyzimarev

Copy link
Copy Markdown
Member

Summary

  • Adopt Spectre.Console for the two most human-facing commands — setup and history — replacing ad-hoc Console.Write* with prompts, status spinner, rules, grids, and pinned progress.
  • Thread an optional IProgress<ImportProgress> through SessionImporter so the importer stays console-agnostic while HistoryCommand drives a live footer that survives a 1.5K-session scrollback without a table.
  • Gate TTY vs piped output via Console.IsOutputRedirected; the non-TTY path preserves today's plain line-per-session format so scripts and CI logs don't break.

What changed

  • setup — Rule section headers, Status spinner around the reachability probe, TextPrompt / SelectionPrompt / ConfirmationPrompt replace the numbered 1/2/3 menus, final summary as a Grid. --no-prompt path behaviourally unchanged.
  • history — single pinned ProgressTask footer (one task, not per-session — 1.5K tasks would be hostile); footer description mutates to show Importing {done}/{total} · {sid8}: {lines}/{total} ↳ subagent {agentId8}. Subagent completions stream above the footer as ↳ imported subagent … (N lines) live, instead of the old trailing summary line. Background phase (titles / summaries) gets a separate Rule + Progress block; individual failures stream as ✗ title failed for … / ✗ summary failed for ….
  • SessionImporter — gains optional IProgress<ImportProgress>? parameter on ImportSessionAsync, SendTranscriptBatches, and SendAgentLifecycle. New events: BatchFlushed(int), SubagentStarted(string), SubagentFinished(string, int). SendAgentLifecycle return type widened from Task to Task<int> (line count).
  • Not adopted: Spectre.Console.Cli (reflection-heavy; kept hand-rolled ArgParsing). No shared IConsoleUx abstraction. Hook commands untouched.

Design & plan

  • Spec: docs/superpowers/specs/2026-04-23-spectre-console-cli-ux-design.md
  • Plan: docs/superpowers/plans/2026-04-23-spectre-console-cli-ux.md

Test plan

  • Unit tests: 292/292 passing (added 2 TDD tests for IProgress wiring).
  • Integration tests: 4/4 passing.
  • AOT publish gate clean: dotnet publish -c Release 2>&1 | grep -E 'IL[23][01][0-9]{2}' returns zero lines.
  • Smoke test: published AOT binary renders Rule / Status / ✓ markup correctly on setup --no-prompt.
  • Manual TTY smoke: kapacitor setup (interactive) — reviewer check.
  • Manual TTY smoke: kapacitor history --min-lines 10 against a real server — reviewer check.
  • Manual piped smoke: kapacitor history --min-lines 10 > out.log — reviewer check.

🤖 Generated with Claude Code

alexeyzimarev and others added 11 commits April 23, 2026 19:40
Design document for narrowing Spectre.Console adoption to the two most
human-facing commands (setup, history), with explicit non-TTY fallbacks
and no Spectre.Console.Cli dependency to keep the AOT/trim build clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implementation plan for DEV-1573. Ten bite-sized tasks covering package
addition + AOT gate, ImportProgress event hierarchy, TDD-driven importer
wiring, setup wizard overhaul, HistoryDisplay abstraction with TTY vs
piped gate, and background-phase progress with streaming failure lines.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@linear

linear Bot commented Apr 23, 2026

Copy link
Copy Markdown

@qodo-code-review

Copy link
Copy Markdown

Review Summary by Qodo

Adopt Spectre.Console for setup wizard and history import with live progress

✨ Enhancement 🧪 Tests 📝 Documentation

Grey Divider

Walkthroughs

Description
• Adopted Spectre.Console for the setup and history commands, replacing ad-hoc console output
  with rich prompts, spinners, rules, grids, and live progress tracking
• **setup command** — Replaced numbered menus with SelectionPrompt, TextPrompt, and
  ConfirmationPrompt; wrapped server reachability probe in Status spinner; replaced final summary
  with two-column Grid layout; added Rule section headers
• **history command** — Implemented single pinned ProgressTask footer showing `Importing
  {done}/{total} · {sid8}: {lines}/{total}` with live subagent completion streaming; added separate
  background-phase progress block for titles/summaries with individual failure streaming
• **SessionImporter** — Added optional IProgress<ImportProgress>? parameter to
  ImportSessionAsync, SendTranscriptBatches, and SendAgentLifecycle; defined three progress
  events: BatchFlushed(int), SubagentStarted(string), SubagentFinished(string, int); changed
  SendAgentLifecycle return type from Task to Task<int>
• **TTY vs piped output** — Gated rich output via Console.IsOutputRedirected; non-TTY path
  preserves plain line-per-session format for scripts and CI logs
• **Testing** — Added 2 TDD tests for IProgress wiring; all 292 unit tests and 4 integration tests
  passing; AOT publish gate clean
• **Documentation** — Comprehensive design spec and implementation plan covering architecture, UX
  flows, and testing strategy
Diagram
flowchart LR
  A["setup/history<br/>commands"] -->|"Console.IsOutputRedirected"| B{"TTY or<br/>piped?"}
  B -->|"TTY"| C["Spectre.Console<br/>rich output"]
  C --> D["Rules, Prompts,<br/>Spinners, Grids"]
  B -->|"piped"| E["Plain text<br/>output"]
  F["SessionImporter"] -->|"IProgress&lt;ImportProgress&gt;"| G["Progress events"]
  G --> H["BatchFlushed<br/>SubagentStarted<br/>SubagentFinished"]
  H --> C
Loading

Grey Divider

File Changes

1. src/kapacitor/Commands/HistoryCommand.cs ✨ Enhancement +381/-223

Spectre.Console UX for history import with live progress footer

• Introduced HistoryDisplay struct to abstract output rendering with TTY vs piped branching via
 Console.IsOutputRedirected
• Added IProgress<ImportProgress> parameter threading through ImportSessionAsync and
 SendTranscriptBatches calls
• Implemented per-session progress callback tracking line counts and subagent lifecycle events
• Wrapped main import loop in Spectre Progress block for TTY mode with pinned footer showing
 session count and per-session line progress
• Added background-phase progress block with two separate task bars for titles and summaries,
 streaming individual failure lines as they occur
• Replaced all user-facing Console.Write* calls with display.Line() method that routes to
 AnsiConsole.MarkupLine() (TTY) or Console.WriteLine() (piped)

src/kapacitor/Commands/HistoryCommand.cs


2. src/kapacitor/Commands/SetupCommand.cs ✨ Enhancement +60/-79

Spectre.Console UX for setup wizard with prompts and grid

• Added Spectre.Console using and replaced welcome banner with centered Rule
• Replaced numbered menu prompts with SelectionPrompt for visibility and plugin scope selection
• Replaced Console.Write/ReadLine with TextPrompt for server URL and daemon name inputs
• Wrapped server reachability probe in Status() spinner with animated dots
• Replaced final summary output with two-column Grid layout showing server, visibility, daemon,
 auth, and config path
• Replaced all section headers with Rule elements using yellow color and step numbering
• Added ConfirmationPrompt for re-run confirmation with proper markup escaping

src/kapacitor/Commands/SetupCommand.cs


3. src/kapacitor/Commands/SessionImporter.cs ✨ Enhancement +46/-31

Thread IProgress callbacks through session importer for live UI updates

• Added optional IProgress<ImportProgress>? parameter to ImportSessionAsync,
 SendTranscriptBatches, and SendAgentLifecycle methods
• Fire BatchFlushed events after each transcript batch is posted to the server
• Fire SubagentStarted before streaming a subagent's transcript and SubagentFinished after
 completion with line count
• Changed SendAgentLifecycle return type from Task to Task<int> to return the number of lines
 sent
• Threaded progress reporting through all batch flush points in both main session and agent
 transcript processing

src/kapacitor/Commands/SessionImporter.cs


View more (5)
4. src/kapacitor/Commands/ImportProgress.cs ✨ Enhancement +17/-0

Define ImportProgress event hierarchy for importer callbacks

• Created new abstract record ImportProgress as base type for importer progress events
• Defined BatchFlushed(int LinesAdded) event fired after each transcript batch is flushed to
 server
• Defined SubagentStarted(string AgentId) event fired when importer begins streaming a subagent's
 transcript
• Defined SubagentFinished(string AgentId, int LinesSent) event fired after subagent transcript is
 fully streamed

src/kapacitor/Commands/ImportProgress.cs


5. test/kapacitor.Tests.Unit/SessionImporterProgressTests.cs 🧪 Tests +105/-0

TDD tests for ImportProgress event wiring in SessionImporter

• Added TDD test SendTranscriptBatches_fires_BatchFlushed_per_100_lines verifying three
 BatchFlushed events for 250 lines (100, 100, 50)
• Added TDD test ImportSessionAsync_fires_subagent_events_around_inline_import verifying
 SubagentStarted and SubagentFinished events bracket agent transcript streaming with correct line
 counts
• Used WireMock.Net to mock HTTP endpoints and Progress<T> to capture emitted events

test/kapacitor.Tests.Unit/SessionImporterProgressTests.cs


6. src/kapacitor/kapacitor.csproj Dependencies +1/-0

Add Spectre.Console package dependency

• Added Spectre.Console NuGet package reference with version constraint 0.*

src/kapacitor/kapacitor.csproj


7. docs/superpowers/plans/2026-04-23-spectre-console-cli-ux.md 📝 Documentation +1368/-0

Detailed implementation plan for Spectre.Console CLI UX adoption

• Comprehensive 1368-line implementation plan with 10 sequential tasks covering package addition,
 event hierarchy, progress threading, setup wizard UX, history display abstraction, TTY vs piped
 branching, and background-phase progress
• Each task includes step-by-step instructions, code snippets, build/test/AOT verification gates,
 and smoke test procedures
• Detailed file map, architecture rationale, and self-review notes covering spec coverage and known
 gaps

docs/superpowers/plans/2026-04-23-spectre-console-cli-ux.md


8. docs/superpowers/specs/2026-04-23-spectre-console-cli-ux-design.md 📝 Documentation +235/-0

Spectre.Console CLI UX design specification for setup and history

• Comprehensive design specification for adopting Spectre.Console in the setup and history CLI
 commands
• Details the setup wizard UX improvements: Rule headers, Status spinners,
 TextPrompt/SelectionPrompt/ConfirmationPrompt replacements, and Grid-based final summary
• Specifies history import progress tracking with a single pinned footer task, streamed completion
 lines, and background phase (titles/summaries) rendering
• Defines SessionImporter interface changes: optional IProgress<ImportProgress> parameter with
 three event types (BatchFlushed, SubagentStarted, SubagentFinished)
• Documents non-TTY fallback behavior via Console.IsOutputRedirected to preserve existing
 plain-text output for piped/CI contexts
• Includes AOT/trim safety constraints, testing strategy, and explicit out-of-scope items

docs/superpowers/specs/2026-04-23-spectre-console-cli-ux-design.md


Grey Divider

Qodo Logo

@qodo-code-review

qodo-code-review Bot commented Apr 23, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Invalid with on struct 🐞 Bug ≡ Correctness
Description
HistoryCommand uses a with { ... } expression on HistoryDisplay, but HistoryDisplay is
declared as a non-record struct, so this code will not compile and will block the build.
Code

src/kapacitor/Commands/HistoryCommand.cs[R459-462]

+                .StartAsync(async ctx => {
+                    var footer = ctx.AddTask("[green]Importing[/]", maxValue: transcriptFiles.Count);
+                    display = display with { Footer = footer };
+
Evidence
The type being copied is declared as a plain readonly struct, but later the code uses a
record-style with expression to copy it with a modified property, which is invalid for non-record
structs.

src/kapacitor/Commands/HistoryCommand.cs[9-36]
src/kapacitor/Commands/HistoryCommand.cs[454-466]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`HistoryDisplay` is a `readonly struct` but is copied using `display = display with { Footer = footer };`, which requires a record type and causes compilation failure.

### Issue Context
You likely intended value-type copy semantics with init-only properties.

### Fix Focus Areas
- src/kapacitor/Commands/HistoryCommand.cs[9-36]
- src/kapacitor/Commands/HistoryCommand.cs[454-466]

### Suggested fix
Either:
1) Change the declaration to `readonly record struct HistoryDisplay` (preferred since you already use `with`), or
2) Replace the `with` usage with an explicit new value:
```csharp
var footer = ctx.AddTask(...);
display = new HistoryDisplay { Tty = display.Tty, Footer = footer };
```
(keep other properties as needed).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Background progress max wrong ☑ 🐞 Bug ≡ Correctness
Description
The background titles/summaries progress block sets both tasks’ maxValue to
backgroundTasks.Count, but their Value comes from separate title-only and summary-only counters,
so when both types exist the bars can’t reach 100%.
Code

src/kapacitor/Commands/HistoryCommand.cs[R479-489]

+                    .StartAsync(async ctx => {
+                        var maxVal      = backgroundTasks.Count;
+                        var titleTask   = ctx.AddTask("[cyan]Titles[/]",    maxValue: maxVal);
+                        var summaryTask = ctx.AddTask("[cyan]Summaries[/]", maxValue: maxVal);
+
+                        var seenTitleFailures   = 0;
+                        var seenSummaryFailures = 0;
+
+                        while (backgroundTasks.Any(t => !t.IsCompleted)) {
+                            titleTask.Value   = titlesGenerated + titlesFailed + titlesSkipped;
+                            summaryTask.Value = summariesGenerated + summariesFailed;
Evidence
backgroundTasks contains both title tasks and (conditionally) summary tasks, but the progress
values are driven by counters that track only one category each. As a result, each bar’s maximum can
be larger than the count it is measuring.

src/kapacitor/Commands/HistoryCommand.cs[381-431]
src/kapacitor/Commands/HistoryCommand.cs[470-529]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The Spectre progress bars for background work use `maxValue = backgroundTasks.Count` for both Titles and Summaries, but their values are computed from separate counters. When both titles and summaries are generated, neither bar reaches 100%.

### Issue Context
`backgroundTasks` is a single list holding multiple task categories.

### Fix Focus Areas
- src/kapacitor/Commands/HistoryCommand.cs[381-431]
- src/kapacitor/Commands/HistoryCommand.cs[470-529]

### Suggested fix
Introduce explicit counters for how many title tasks and summary tasks you enqueue, e.g.:
```csharp
var titleTaskCount = 0;
var summaryTaskCount = 0;
...
backgroundTasks.Add(Task.Run(...));
titleTaskCount++;
...
if (shouldGenerateWhatsDone) { backgroundTasks.Add(Task.Run(...)); summaryTaskCount++; }
```
Then set:
```csharp
var titleTask = ctx.AddTask("[cyan]Titles[/]", maxValue: titleTaskCount);
var summaryTask = ctx.AddTask("[cyan]Summaries[/]", maxValue: summaryTaskCount);
```
and keep `.Value` assignments aligned to those same counts.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Async progress update ordering ☑ 🐞 Bug ☼ Reliability
Description
HistoryCommand uses new Progress<ImportProgress>(...) to update the footer and print subagent
completion lines, but Progress<T> dispatch is asynchronous, so footer/text updates can arrive
after the session loop has advanced (stale/out-of-order output).
Code

src/kapacitor/Commands/HistoryCommand.cs[R208-227]

+                var perSessionProgress = new Progress<ImportProgress>(ev => {
+                        switch (ev) {
+                            case BatchFlushed bf:
+                                linesDone += bf.LinesAdded;
+                                display.AdvanceFooterLines(linesDone, totalLines, sessionIdShort, currentSubagent);
+                                break;
+                            case SubagentStarted ss:
+                                currentSubagent = ss.AgentId.Length >= 8 ? ss.AgentId[..8] : ss.AgentId;
+                                display.AdvanceFooterLines(linesDone, totalLines, sessionIdShort, currentSubagent);
+                                break;
+                            case SubagentFinished sf:
+                                display.Line(
+                                    $"  ↳ imported subagent {sf.AgentId} ({sf.LinesSent} lines)",
+                                    $"  [dim]↳[/] imported subagent [cyan]{Markup.Escape(sf.AgentId)}[/] ({sf.LinesSent} lines)");
+                                currentSubagent = null;
+                                display.AdvanceFooterLines(linesDone, totalLines, sessionIdShort, null);
+                                break;
+                        }
                    }
+                );
Evidence
The unit tests explicitly add Task.Delay(50) because Progress<T> marshals callbacks
asynchronously, demonstrating that event handling isn’t synchronous with Report(). In
HistoryCommand, this means footer mutation and display.Line(...) calls can lag behind the
awaited import and be applied after later sessions start.

test/kapacitor.Tests.Unit/SessionImporterProgressTests.cs[25-43]
src/kapacitor/Commands/HistoryCommand.cs[199-227]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`Progress<ImportProgress>` dispatches callbacks asynchronously, which can reorder/lag UI updates relative to the sequential session import loop.

### Issue Context
The new UX relies on timely, ordered footer updates and streamed lines.

### Fix Focus Areas
- src/kapacitor/Commands/HistoryCommand.cs[199-227]
- test/kapacitor.Tests.Unit/SessionImporterProgressTests.cs[25-43]

### Suggested fix
Replace `new Progress<ImportProgress>(...)` with a synchronous `IProgress<ImportProgress>` implementation so `Report()` updates happen inline:
```csharp
sealed class InlineProgress<T>(Action<T> onReport) : IProgress<T> {
   public void Report(T value) => onReport(value);
}

var perSessionProgress = new InlineProgress<ImportProgress>(ev => { ... });
```
This keeps footer mutations and `display.Line(...)` ordered with the import loop and avoids cross-thread UI updates.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Advisory comments

4. Footer line count inflated ☑ 🐞 Bug ≡ Correctness
Description
The history footer’s {linesDone}/{linesTotal} is updated from BatchFlushed, but BatchFlushed
is reported for both parent and subagent transcript flushes while linesTotal is computed from only
the parent transcript file, so the footer can show linesDone exceeding linesTotal.
Code

src/kapacitor/Commands/HistoryCommand.cs[R199-227]

+                // Count total lines for progress display
+                var totalLines = WatchCommand.CountFileLines(filePath);
+
+                var sessionIdShort = sessionId.Length >= 8 ? sessionId[..8] : sessionId;
+                var linesDone      = 0;
+                string? currentSubagent = null;
+
+                display.SetFooterSession(sessionIdShort, totalLines);
+
+                var perSessionProgress = new Progress<ImportProgress>(ev => {
+                        switch (ev) {
+                            case BatchFlushed bf:
+                                linesDone += bf.LinesAdded;
+                                display.AdvanceFooterLines(linesDone, totalLines, sessionIdShort, currentSubagent);
+                                break;
+                            case SubagentStarted ss:
+                                currentSubagent = ss.AgentId.Length >= 8 ? ss.AgentId[..8] : ss.AgentId;
+                                display.AdvanceFooterLines(linesDone, totalLines, sessionIdShort, currentSubagent);
+                                break;
+                            case SubagentFinished sf:
+                                display.Line(
+                                    $"  ↳ imported subagent {sf.AgentId} ({sf.LinesSent} lines)",
+                                    $"  [dim]↳[/] imported subagent [cyan]{Markup.Escape(sf.AgentId)}[/] ({sf.LinesSent} lines)");
+                                currentSubagent = null;
+                                display.AdvanceFooterLines(linesDone, totalLines, sessionIdShort, null);
+                                break;
+                        }
                    }
+                );
Evidence
HistoryCommand increments linesDone for every BatchFlushed event, and SessionImporter
reports BatchFlushed from SendTranscriptBatches for any transcript (including subagent
transcripts because SendAgentLifecycle passes the same progress instance). Meanwhile linesTotal
comes from CountFileLines(filePath) of the parent transcript only.

src/kapacitor/Commands/HistoryCommand.cs[199-227]
src/kapacitor/Commands/SessionImporter.cs[376-409]
src/kapacitor/Commands/SessionImporter.cs[436-486]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`BatchFlushed(LinesAdded)` doesn’t indicate whether the flushed lines are from the parent session transcript or a subagent transcript. `HistoryCommand` assumes it’s always the parent, so footer line totals can be wrong when subagents are imported inline.

### Issue Context
`SendAgentLifecycle` passes the same `progress` instance into `SendTranscriptBatches` for subagent files, which also emits `BatchFlushed`.

### Fix Focus Areas
- src/kapacitor/Commands/ImportProgress.cs[8-17]
- src/kapacitor/Commands/SessionImporter.cs[376-409]
- src/kapacitor/Commands/SessionImporter.cs[436-486]
- src/kapacitor/Commands/HistoryCommand.cs[199-227]

### Suggested fix options
Option A (most explicit): include `string? AgentId` in `BatchFlushed`:
```csharp
public sealed record BatchFlushed(string? AgentId, int LinesAdded) : ImportProgress;
```
Report it from `SendTranscriptBatches` using the existing `agentId` parameter.

Option B (UI-only workaround): in `HistoryCommand`, ignore `BatchFlushed` while `currentSubagent != null` so parent line progress stays bounded by parent `totalLines`.

Option C: compute `linesTotal` as parent + sum(subagent line counts) when available (requires discovering subagent transcript paths and counting them).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment on lines +459 to +462
.StartAsync(async ctx => {
var footer = ctx.AddTask("[green]Importing[/]", maxValue: transcriptFiles.Count);
display = display with { Footer = footer };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Invalid with on struct 🐞 Bug ≡ Correctness

HistoryCommand uses a with { ... } expression on HistoryDisplay, but HistoryDisplay is
declared as a non-record struct, so this code will not compile and will block the build.
Agent Prompt
### Issue description
`HistoryDisplay` is a `readonly struct` but is copied using `display = display with { Footer = footer };`, which requires a record type and causes compilation failure.

### Issue Context
You likely intended value-type copy semantics with init-only properties.

### Fix Focus Areas
- src/kapacitor/Commands/HistoryCommand.cs[9-36]
- src/kapacitor/Commands/HistoryCommand.cs[454-466]

### Suggested fix
Either:
1) Change the declaration to `readonly record struct HistoryDisplay` (preferred since you already use `with`), or
2) Replace the `with` usage with an explicit new value:
```csharp
var footer = ctx.AddTask(...);
display = new HistoryDisplay { Tty = display.Tty, Footer = footer };
```
(keep other properties as needed).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

False positive — with expressions on non-record structs are legal since C# 10 (the spec allows with on any struct with init/settable members, not just record struct). HistoryDisplay is a readonly struct with init-only properties, which satisfies this. Both the Build and test and AOT publish check CI jobs on this PR are passing, confirming the code compiles cleanly.

…ounting

- Tag BatchFlushed with the owning AgentId so HistoryCommand only counts
  parent-transcript lines against linesTotal (the file it measured with
  CountFileLines). Previously subagent batches could push linesDone past
  linesTotal.
- Switch the per-session progress sink from Progress<T> to a synchronous
  InlineProgress<T>. new Progress<T> marshals to the ThreadPool in a
  console app, so footer mutations and streamed lines could arrive after
  the sequential import loop moved on.
- Track title and summary task counts separately and use them as the
  respective ProgressTask maxValue. Previously both bars used
  backgroundTasks.Count, so with both task types enqueued neither bar
  could reach 100%. Skip the summary bar entirely when no summaries are
  requested.
- Update unit tests to assert AgentId attribution on BatchFlushed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@alexeyzimarev

Copy link
Copy Markdown
Member Author

Addressed findings 2, 3, 4 in 7292054. Finding 1 is a false positive — with on non-record structs has been legal since C# 10 and both CI gates are green on the PR.

2. Background progress max — Titles/Summaries bars now use separate titleTaskCount / summaryTaskCount (incremented each time a task is enqueued), so each can actually reach 100%. The summary bar is skipped entirely when --generate-summaries isn't set (no zero-max noise).

3. Async progress ordering — Swapped new Progress<ImportProgress>(...) for a local InlineProgress<T> : IProgress<T> that runs the handler inline on Report(). Keeps footer mutations and streamed subagent lines ordered with the sequential import loop.

4. Footer line count inflated — BatchFlushed now carries string? AgentId. HistoryCommand only increments linesDone for AgentId is null (parent transcript), so subagent batches no longer push linesDone past linesTotal. Added a test asserting subagent batches arrive with the correct agent id.

All 292 unit tests pass; AOT publish is clean.

@alexeyzimarev
alexeyzimarev merged commit e4e97b2 into main Apr 23, 2026
3 checks passed
@alexeyzimarev
alexeyzimarev deleted the feature/dev-1573-spectre-console branch April 23, 2026 20:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant