Repository navigation
KeylessAgent: full agent stack — parser hardening, browser reliability, WPF UI, snapshot budgets, NuGet publishing - #2
Merged
Conversation
… tooling - KeylessAgent.slnx with Abstractions, Core, Cli, Playground, and Tests projects - Directory.Build.props (net10.0, analyzers, package metadata) and Directory.Packages.props (CPM) - All NuGet packages installed via dotnet CLI (ImanSoftware.* set, Playwright, Spectre, xunit, FluentAssertions 7.x) - nuget.config with package source mapping, publish.ps1, build-and-test workflow - .editorconfig, .gitattributes, .gitignore, global.json, LICENSE.txt, tool icon
- CommandKind with Description attribute per AgentScript v1 command name - CommandStatus, ApprovalMode, SnapshotMode, and AgentEventType enums
- Line-based state-machine parser: all 10 commands, case-insensitive keywords, multiline payloads, nonce markers (<<<CONTENT:x7), code-fence stripping, line-numbered problems for every grammar violation - AgentScriptValidator: response/file-content size limits and path shape rules - ResponseFormatGuard: single gate producing FormatGuardResult (valid script or problems) - ErrorCodes catalog (KA_*), configuration options model, format-correction.txt template - 28 xunit tests covering all parser and guard paths
…ckups - GitService via short-lived git processes (info, branch, stash, stage, commit, push -u, log, .git/info/exclude upkeep); never force or reset - BranchGuard enforcing protected branch patterns (main/master/release*) in code - BranchNameBuilder: agent/<yyyyMMdd-HHmm>-<slug> with request-derived kebab slug - PathSandbox: workspace-relative only, no .. or absolute paths, .git/.agent blocked, symlink escape detection - CommandPolicy: deny list (format/shutdown/force-push/global-config/pipe-to-shell) plus configurable allow list for AutoSafe - Per-command executors (say/newfile/updatefile/editfile/deletefile/readfile/ runcommand/ask/commit/done) behind WorkspaceCommandExecutor with dry-run, Ask-mode diffs/confirmations, BOM preservation, and backup-before-change - BackupService into .agent/backups with undo-latest restore
- Tests for path sandbox (.., absolute, .git/.agent, junction escape), command policy (deny list, segments, allowlist), branch guard (main/MASTER/release*/releases vs working branches), branch name builder (fake clock, empty/non-Latin/symbol/long slugs), and all executors (new/update/edit/delete/read/run/ask/commit incl. BOM preservation, backups, dry-run, declines, push failure, non-zero exits) - Fix sandbox: symlink check no longer rejects paths whose parent folders do not exist yet - Slug: whitespace collapses to dashes, repeated dashes collapse - Relax analyzers for the test project (not shipped)
- SnapshotService runs tree-view.bat/code-dump.bat via cmd /c without a window, stdin closed and hard timeout so the trailing pause can never hang; staleness check ignores the snapshot outputs themselves - SnapshotFilter: tree lines filtered by folder name including nested blocks; dump sections dropped by heading; chunking only at ### section boundaries - ResultFormatter builds [KEYLESSAGENT RESULTS] messages mapping every result to command number/name/target with head+tail output capping and the skipped-commands note - Tests: filter/chunk, bat-run-without-hanging, staleness, attachments, formatter ids/truncation/skipped
…extractor, fake - PlaywrightChatProvider: ConnectOverCDP to the user's Chrome (default) with LaunchPersistentContext fallback, manual-login wait, new-chat click, and DeepThink/Search toggle enforcement via aria-pressed (warn and continue when a toggle cannot be found) - PlaywrightChatSession: rate-limited sends, whole-text insert (never per-character typing), attachment upload via file input with paste fallback, reply waiting on stop-indicator + stable final-answer read, debug screenshots - Default selectors.json built from the real DOM sample (structural selectors over hashed classes) with fallback lists, editable without recompiling, auto-written to the workspace - ReplyExtractor: tag-scanner returning only the final answer, skipping .ds-think-content and code-block banners, preserving pre indentation - FakeChatProvider (scripted replies) + local RateLimiter - Playground harness running one scripted conversation end to end - 139 tests green; solution builds with zero warnings
- AgentLoop orchestrating one turn in numbered steps: config load, git preconditions (repo check, dirty-tree continue/stash/cancel), protected- branch guard with agent-branch creation, session resume/new chat, stale-snapshot refresh, first message with system prompt + attachments, the conversation loop (wait -> guard -> execute with batch limits and skipped reporting -> results message), and an always-on snapshot refresh in finally - Automatic format re-ask with correction template up to MaxFormatRetries, raw reply shown to the user when exhausted - ConfigurationService: tolerant keyless.json (comments, case, trailing commas, string enums), default file written when missing, range validation - SessionStore persisting chat URL/branch/turn in .agent/session.json (no-session is a KA_SESSION_NOT_FOUND failure, matching the Outcome package's rule that Data is never null) - SystemPromptBuilder injecting limits into the embedded system-prompt.md - KeylessAgentServiceFactory wiring everything by hand (no DI container) - TurnLogger JSON-lines transcript, FileLoggerProvider for debug diagnostics - 156 tests green including end-to-end loop tests with the fake provider
…ckaging - SpectreAgentUserInterface: say messages as panels, progress through the status spinner, prompts for confirm/ask, colored +/- diffs before changes - Commands: 'run <request>' (--dry-run, -v), interactive default command (prompt-per-turn session), 'resume <request>' (reuses .agent/session.json chat), 'undo' (restores newest backup batch), 'config' (prints effective keyless.json, writing defaults when missing) - AgentCliRunner: shared spinner + branch/commit summary at turn end - KeylessAgentServiceFactory.CreateSessionStore/CreateFileLoggerFactory; SingleProviderLoggerFactory for verbose diagnostics logging - publish.bat calling the root publish.ps1; tool verified to pack (nupkg+snupkg)
- Full README (what/why, requirements, install, usage, Mermaid agent-loop flowchart, AgentScript summary, safety model, known limitations, roadmap incl. WinForms UI and official-API provider, ToS/account-risk notice, not-affiliated statement) and the short tool README - docs/: architecture.md, agentscript-spec.md (grammar, commands, limits, full KA_ error-code catalog), configuration.md, browser-automation.md (selectors table + manual verification checklist), security.md, git-workflow.md - Checklist review: inject IDateTimeProvider into the browser classes so Core has no direct DateTime.Now/UtcNow; verified zero warnings, zero csproj package versions, exactly six ImanSoftware packages, no Spectre/console usage in Core — 156 tests green
…nt directory - All commands now take the workspace path as their first positional argument: 'keyless run <workspace> <request>', 'resume <workspace> <request>', 'undo <workspace>', 'config <workspace>' - When omitted, the very first prompt asks for it, validating that the directory exists (current directory shown only as the default, never silently assumed); run/resume also prompt for a missing request - Interactive default command prompts for the workspace before the first request; -w removed in favor of the positional argument - README (root + tool) usage examples updated to workspace-first form
…-fatal Bug: wrapping a whole agent turn in AnsiConsole.Status made every Say/ Confirm/Ask during the turn throw Spectre's 'Trying to run one or more interactive functions concurrently' and kill the process with exit 1. - IAgentUserInterface gains BeginWait(message): Core now marks only actual waiting phases (chat reply, sends, runcommand process, snapshot scripts) with a disposable wait scope — never the whole turn - New ConsoleUserInterface owns all console access through one semaphore; the spinner is a background task cancelled+joined before ANY other UI call (Say/Ask/Confirm/ShowDiff/ShowProgress), overlapping BeginWait is safe (previous spinner stopped first), scopes tolerate double stops - AgentCliRunner no longer wraps turns in Status; a failing or cancelled turn is printed (message + .agent/logs pointer) instead of crashing - InteractiveSession loop: turn failures return to the keyless> prompt, Ctrl+C cancels only the current turn (fresh linked token per turn); exit code 1 reserved for startup errors - Console Output/InputEncoding set to UTF-8 at startup when not redirected; console markers switched to plain ASCII ([ok] [!] [x] [i]) with proper markup escaping - Tests: nested-exclusivity reproduction of the crash, BeginWait+Confirm stops spinner first, Say during a wait, overlapping waits, Ask after a disposed wait, and failing/cancelled turns returning to the prompt (Spectre.Console.Testing 0.57.2 via CPM); 164 tests green
Before every send the newest assistant message is fingerprinted (data-virtual-list-item-key, else a hash of the final-answer text, plus the visible count); the send itself must be confirmed within Browser.SendConfirmSeconds (new, default 15: new message item, or empty input + generating indicator) with one retry (click then Enter) before failing KA_SEND_NOT_CONFIRMED; WaitForReplyAsync accepts only a reply whose fingerprint differs from the pre-send one (identical text valid only with a new message key), whose final answer exists and is stable for two reads, and once the stop indicator is gone. Attachment sends wait for chips + enabled send (Browser.UploadWaitSeconds, default 60) and fall back to pasting on timeout. Session logic now sits behind the IChatPage seam (PlaywrightChatPage = real DOM; FakeChatPage in tests), enabling scenario tests (a)-(d). Docs updated; 168 tests green.
- LineEndings helper: detect dominant ending (mixed keeps the majority, tie means LF), normalize to LF, apply back; new files resolve their ending from .editorconfig (end_of_line, section globs), then .gitattributes (eol=), then the dominant ending of sibling source files (dot/config files excluded), then Environment.NewLine - editfile: match and count FIND/REPLACE on LF-normalized text (the 'matched N times' message included) and write back with the file's own ending; updatefile converts model content to the existing ending; newfile applies the resolved ending; BOM handling unchanged - FileChange diffs are LF-normalized (no stray \r in the CLI diff) and MaxCommandOutputChars counts normalized characters in result messages - Tests: CRLF+LF FIND matches once and stays CRLF, multi-line find, mixed-ending file, BOM+CRLF, newfile obeys .editorconfig and Windows NewLine, updatefile keeps endings, editorconfig globs, gitattributes, sibling dominance, diff without \r; 185 tests green
- deletefile now asks for confirmation in Ask AND AutoSafe modes; only Auto skips it (a backup is always made in every mode) - AutoSafe/Auto turns show a one-time warning at turn start: 'AutoSafe is not a security boundary' (dotnet/node/python can run arbitrary code) - docs/configuration.md, docs/security.md and the README approval table now state exactly what the code does - Tests: deletefile asked+declined in Ask and AutoSafe, not asked in Auto, and the AutoSafe boundary warning; 186 tests green
- BeginWait returns only after the Status display actually started (ManualResetEventSlim set from inside the display), so a prompt can never start in the gap; StopSpinner no longer gives up after 3s — it joins fully (30s upper bound with a warning), so any prompt/write always runs after the spinner is really gone - ConsoleWriter becomes an instance over one IAnsiConsole (Shared for command output); commands and the runner route everything through the same console surface as the agent UI - Turn failures print exception type + message; with -v the full stack trace is shown and appended to .agent/logs/diagnostics.log; the log path is always printed; WorkspacePrompt uses the same ASCII [x] marker - Tests: prompt immediately after BeginWait never throws, slow spinner shutdown still lets the following prompt run, failing turns show the exception type and return to the keyless> prompt; 190 tests green
- SendButton fallback order per the sample: icon button in the textarea's sibling container, iconLabelPrimary, filled-circle, filled next to the file input, submit — each candidate checked for visibility and aria-disabled before clicking (a disabled send is skipped, never the target); Enter stays the last fallback - Every selector loop logs (Debug) which fallback INDEX matched, so a broken selector is one log line away - Toggle and MessageItems selectors made pure-CSS first (:has-text kept as a Playwright-only later fallback); AttachmentChips added earlier - Fragment tests with AngleSharp run the default catalog against real fragments of deepseek-dom-sample-trimmed.html: input placeholder, file input multiple, toggles aria-pressed, virtualized item keys, final answer vs reasoning, send fallbacks matching and skipping the disabled button; 198 tests green
- /workspace <path> validates the directory, refuses while the loop is not ready (a failing switch keeps the old workspace), re-runs the git preconditions and clears the target workspace's .agent/session.json (fresh chat session), then prints the new header; every turn and callback receives the workspace explicitly — no CurrentDirectory - /help lists the slash commands; any other /-prefixed input is rejected with a hint instead of being sent to the model - InteractiveSession takes the workspace as a parameter and a switchWorkspace callback; InteractiveCommand wires preconditions via the git service and session store - Tests: help listed without a model send, unknown /command rejected, switch recorded + used for subsequent turns, missing directory refused, failed switch keeps the old workspace, usage message; 204 tests green
- Send retry happens only when the input STILL holds the text; a cleared input means the message was sent even when the virtualized item count did not change (treated as sent, then WaitForReplyAsync). Retry exactly once, and each attempt is the send button when clickable, otherwise Enter — never both in the same attempt (first attempt included) - Attachment delivery judged by several signals: SetInputFiles result, the hidden file input's reported file names, composer-near elements showing a file name, chips visibility. Paste only when SetInputFiles itself failed or there is NO sign of the files after Browser.UploadWaitSeconds; uncertainty counts as attached with a warning — the same content is never both attached and pasted - FakeChatPage gains FailClick, InputFileNames, VisibleFileNames and LastInsertedText; tests (a)-(e) cover all five scenarios; 209 tests green
- IChatDiagnostics (Core, GUI-reusable) + ChatDiagnosticsService over the IChatPageInspector seam: probes every selectors.json key through the fallback lists and classifies MATCHED (index + count) / NOT FOUND / NOT APPLICABLE NOW (Stop idle, no chips, no reasoning, fresh chat, login); reports login state, DeepThink/Search aria-pressed, input editability and the page URL; exit 0 only when all required keys matched; never sends a message - PlaywrightChatInspector (real DOM) with the shared connection logic from PlaywrightChatProvider; a new tab, never touching the user's chat - --capture <dir>: trimmed HTML snapshots (composer, newest message, sidebar, chips) via HtmlTrimmer — scripts/styles/svg removed, long text replaced with '...' so no personal content leaves the machine - --watch: waits for a manually sent prompt, captures generating and finished states once each - keyless doctor CLI command with a state table; docs section 'How to verify selectors: keyless doctor'; 5 tests with a scripted inspector; 214 tests green
- All IChatPage operations are Task-returning with a CancellationToken; Playwright's waits have no token support, so each call races the token (WithCancellationAsync) — Ctrl+C interrupts any in-flight wait promptly instead of waiting out its timeout; FakeChatPage and all session tests updated - KA_SEND_NOT_CONFIRMED and KA_REPLY_TIMEOUT errors now carry a pointer: the debug screenshot path under .agent/logs when Browser.Debug is on (the page writes to the exact path), otherwise a hint to run 'keyless doctor'; screenshot path bug fixed (the destination path, not the name, is passed to the page) - docs/browser-automation.md: send/confirm and attachment decision tables, the still-unverified selector list (Stop, New Chat, chips), and the cancellation note - 216 tests green; build with zero warnings
…ibutes - HtmlTrimmer rewritten: removal tags became HTML comments (the old '<script removed>' literal parsed as a real element); attribute values (href/src/title/alt/value/placeholder/aria-label/data-*) are masked to '[masked]' unless they are short allowlisted UI labels; structural attributes (class/role/aria-pressed/aria-disabled/type/accept/multiple) survive; data-virtual-list-item-key is replaced with a stable '[key:hash]' - Text runs longer than the cap no longer swallow the following '<' (old '>...<' bug) — text nodes are masked per-node instead of one greedy run - Allowlist: DeepThink, Search, New chat, Stop, Message DeepSeek, 'Thought for N seconds' pattern, Copy, Download, Regenerate - Tests with AngleSharp: sidebar titles/hrefs, user messages and attachment file names never appear; UI labels + structural attributes survive; trimmed HTML parses to the same element structure (minus the removed script/style/svg); 223 tests green
… readiness - IsSendEnabledAsync is tri-state now: true/false when a send control matched, null when no send-control selector matches at all (fake page and real page agree); a null state never blocks the send - After SetInputFiles succeeds, the session waits (up to Browser.UploadWaitSeconds) for BOTH a positive upload sign (files registered in the input, a chip showing a file name, chips visible) AND an enabled send control, whenever that control can be detected; an upload registered while the control stays disabled is sent anyway with a warning after the timeout - Tests: send enabled only after N polls -> the click happens after enablement; undetectable send control -> no deadlock (returns well before the timeout); 225 tests green
- SelectorsCatalog.LoadAsync, PlaywrightChatInspector (ProbeAsync/ GetToggleStateAsync/IsInputEditableAsync/CaptureTrimmedAsync) and the whole diagnostics pipeline are async with CancellationToken - FileLoggerProvider no longer blocks inside ILogger.Log: Log enqueues into a Channel<string> and one background task performs all storage I/O; the provider constructor does no I/O at all - TurnLogger.StartAsync/AppendAsync/AppendResultAsync; AgentLoop awaits them; /workspace switch flow is async end to end (git preconditions and session clearing happen on the async path with the turn's token) - New NoSyncOverAsyncRuleTests greps all src/ production sources for GetAwaiter().GetResult()/.Result/.Wait() and fails on any violation (tests are exempt); 225 tests green, zero warnings
…tab rules Observed bug: a separate logged-out Chrome window opened (never focused) showing the DeepSeek login page instead of the user's session. Root causes fixed: - Chrome 136 ignores --remote-debugging-port for the DEFAULT user-data-dir: the docs claiming a daily-profile attach were wrong and are corrected everywhere (README requirements, browser-automation Connection section, configuration.md). A dedicated profile (Browser.UserDataDir, default %LOCALAPPDATA%\KeylessAgent\chrome-profile) is now mandatory and the login persists in it - The silent LaunchPersistentContext fallback is removed; it is opt-in (Browser.AllowPlaywrightLaunch=false) with an explicit warning - pages[last] navigation (which could destroy a user's tab) is gone: an existing chat.deepseek.com tab is reused (Browser.ReuseExistingTab), otherwise a new TAB opens via CDP Target.createTarget newWindow:false, matched by URL polling; the tab is focused (BringToFrontAsync + Target.activateTarget), again when login is required - Connection is now IBrowserConnector in Core (probe -> attach; auto-launch the REAL Chrome via IBrowserEnvironment: port probe, chrome.exe locate via config/registry/paths, Process.Start, wait 30s, attach; actionable KA_BROWSER_CONNECTION_FAILED with the exact manual command line; port-open-but-attach-failed reports the reason instead of falling through), shared by run, doctor and the new 'keyless chrome <workspace>' command (dedicated Chrome + one-time login) - Dispose policy: only the tab the agent opened is closed; the Chrome window stays running (Browser.CloseChromeOnExit=false default) - New config keys: UserDataDir, AutoLaunchChrome, ChromePath, ReuseExistingTab, AllowPlaywrightLaunch, CloseChromeOnExit - Tests with faked environment: port open -> attach (no relaunch), port closed -> launch then attach, Chrome missing -> actionable failure, auto-launch off -> fail instead of fall through, Playwright fallback opt-in surfaces as failure without a real browser, profile default resolution; 232 tests green
- IAgentUserInterface.ChooseAsync with ChoiceRequest/ChoiceOption/ChoiceResult (UI-agnostic); ConsoleUserInterface numbered menu: arrows move, digits 1-9 pick immediately, Enter confirms the highlighted (default marked), Esc/Ctrl+C cancels; non-interactive input returns the default and prints it; runs inside the console gate (spinner stopped first) - AgentLoop preconditions rewritten as one combined flow: at most ONE menu per turn, none when the tree is clean and the branch is protected (agent branch auto-created); dirty menu carry/stash/here/cancel (here omitted on protected branches); branch menu new/stay/cancel on clean non-protected branches; the typed 'type continue/stash/cancel' and 'press Enter or type keep' prompts removed - Auto git init (Git.AutoInit default true): git missing -> KA_GIT_NOT_INSTALLED with download link + manual steps; git init -b main (fallback init + branch -M main); .NET .gitignore via dotnet new gitignore or built-in template when the workspace has .sln/.slnx/.csproj; initial commit 'Initial commit (created by KeylessAgent)' with one-shot -c user.name/user.email identity fallback - CommitExecutor: no 'origin' remote -> commit locally and skip push with a notice in the result sent to the model; origin present + push failure reported as before; KA_GIT_INIT_FAILED for init/commit failures; AgentTurnResult.ErrorCode - ConfirmAsync shows the bracketed default, Enter accepts, no empty-input loop - 233 tests green
…back
- Browser.ReplySource = Auto | Copy | Dom (default Auto); Auto tries the
Copy capture, validates, falls back to Dom; Copy never falls back
silently (KA_REPLY_TIMEOUT); Dom is the original behavior
- New selector key MessageCopyButton (message-level Copy copies ONLY the
final answer; bar may be hover-only — the newest message is hovered
first, and only the NEWEST message's control is ever clicked)
- IChatPage capture members + Playwright implementation: ExposeBinding
interception wrapping navigator.clipboard.writeText, execCommand('copy')
and the copy event, gated on an agent-only flag (user's manual copying
and the OS clipboard are never affected); clipboard fallback grants
clipboard-read/write, saves the previous text, reads readText() and
ALWAYS restores it in a finally block
- ReplyReader: strategy + validation (normalized markdown/punctuation-
stripped comparison: containment or similarity >= 0.9; reasoning start
must be absent; copy >= 50% of DOM length; numbers logged on rejection)
- Completion detection unchanged (fingerprint + final answer + stop +
stable); IChatSession.LastReplyOrigin surfaces Copy/Dom/Dom (copy
rejected) and the AgentLoop logs reply-source per reply
- Parser fence tolerance: outer fence stripped only when the first AND
last non-blank lines are fences of the same or greater length (3 or 4
backticks); inner fences inside CONTENT payloads untouched; system
prompt tells DeepSeek not to wrap replies in a code fence
- doctor: MessageCopyButton probe + ReplyCapture check (strategy,
clipboardUntouched, copiedLength, validation result)
- 247 tests green (14 new: ReplyReader validation/strategies, parser
fences, interception/clipboard/none flows)
…ODO removed
- README rewritten: tagline, requirements (.NET 10, Windows, git, Chrome with
the Chrome 136 dedicated-profile rule), install, first run, usage with the
numbered git menus and auto-init notice, Mermaid agent loop, AgentScript
summary, reply sources table (Copy/Dom), safety model ('AutoSafe is not a
security boundary'), known limitations, roadmap, layout, ToS/not-affiliated
- docs: git-workflow.md (auto init, numbered menus, at-most-one-menu flow,
no-origin push skip), security.md (menu note), configuration.md
(ReplySource + Git.AutoInit), browser-automation.md (reply sources,
capture strategies, validation rules), agentscript-spec.md
(KA_GIT_NOT_INSTALLED, KA_GIT_INIT_FAILED)
- CLI README updated (chrome/doctor commands, menus, auto init)
- DocsConsistencyTests: every ErrorCodes KA_ code must appear in
agentscript-spec.md; every Browser/Limits/Git/Commands default must appear
in configuration.md; the documented keyless.json block must deserialize to
values equal to the default options object
- DOCS-TODO.md deleted; 250 tests green
…ne, and the Missing-tools rule
…ules, verified result lines, step-mode iteration raise
…ols wired into the executor chain, e2e hardening scenarios
…tips, TextMuted contrast, inverse-visibility converter
…ved; consistency tests extended
…ent sends, doctor chip-candidate output
… diagnostic Priority 1 — schema version and migration: KeylessOptions gains SchemaVersion (null = pre-versioning v1), CurrentSchemaVersion = 2 and CreateDefault(); LoadAsync migrates stale files (backs up the raw file as keyless.json.v<N>.bak, preserves Mode/ReplyTimeoutSeconds/ChromeDebugPort/ Allowlist, replaces the rest with current defaults) and AgentLoop announces the migration. A fresh keyless.json is now written at the current version so it is never migrated pointlessly. Fixed stale defaults that overrode the new C# timings (MinDelayMs 400, MaxDelayMs 1500, Commands.TimeoutSeconds 180); docs/configuration.md documents the new values plus SchemaVersion. Priority 2 — per-message reminder no longer contradicts the CONTENT fence rule: the whole reply is plain AgentScript, but every CONTENT payload must be wrapped in its own triple-backtick fence (stripped by the program), with a worked example; system-prompt.md says the same right after the CORRECT example. Priority 3 — every Playwright call carries an explicit timeout (clicks 5s/3s, fills 5s, hovers 2s, reload 10s, combined waits 5s, goto commit 15s, CDP minimize race 5s, session insert 15s / attempt-1 click 10s via linked CTS) so nothing rides on the 30s library default. Priority 4 — copy-button diagnostic: PlaywrightChatInspector.DumpButtonsAsync dumps tag/role/label/title/text/classes of every button on the newest assistant message; keyless doctor appends the first 400 chars to the MessageCopyButton detail; a third label-scan strategy clicks any button whose label contains 'copy'.
…istence A1 — the 30 s Playwright default in selector probes (the 60/120/180 s send/results/upload multiples): EvaluateAsync is hard-capped at 1.5 s; IsSendEnabledAsync skips absent selectors with an instant CountAsync (no element resolution at all) while keeping the paperclip skip and the null=undetectable contract; SetInputFiles gets 5 s, the accessibility hover 2 s; page.SetDefaultTimeout(5000) lands on every connection path (attach, auto-launch, Playwright fallback, doctor) as the safety net; IsSendEnabledAsync and ResetComposerAsync log a warning above 2 s with the slowest selector index. A2 — the ~10 s copy wait per message: first wait capped at 3 s, and a per-session circuit breaker skips the whole copy path after the first miss (origin 'copy disabled after the first miss this session'); a new session or keyless doctor --capture re-opens it; Browser.ReplySource=Dom still short-circuits with zero waits. B — the Chrome window popping back up: Target.createTarget now sends background=true (Chrome activated the new tab and restored the minimized window); the minimize runs immediately after the tab opens (Minimized + MinimizeAfterLogin) and again after login; CDP verifies the state via Browser.getWindowBounds read-back with a normal->minimized recovery (up to 3 attempts); the Win32 fallback is real now — ChromeWindowMinimizer enumerates Chrome_WidgetWin_1 windows owned by our chrome.exe processes (launched PID's descendant tree, or the --user-data-dir / debug-port command-line match when attaching) and ShowWindow(SW_MINIMIZE)s them, because the launcher PID's MainWindowHandle is typically 0. BringToFront stays login-only. Tests: absent send-button selectors do zero EvaluateAsync calls; the copy circuit breaker (one wait per session, 3 s cap) and the Dom short-circuit; createTarget background/newWindow parameters. DOCS-TODO entries mark the minimize and copy behavior UNVERIFIED until a live run.
…guard Turn-9/10 evidence: a minimized Chrome window gets throttled by Windows 11 native occlusion detection, the DeepSeek stream stops mid-reply, the Stop indicator blinks off between chunks, and WaitForReplyAsync accepted the truncated AgentScript reply. CHANGE 1 — ComposeChromeArguments gains --disable-features=CalculateNativeWinOcclusion,IntensiveWakeUpThrottling, --disable-ipc-flooding-protection and --disable-hang-monitor (existing flags untouched). The window still minimizes visually; the renderer keeps running at full speed. CHANGE 2 — WaitForReplyAsync never accepts an AgentScript reply whose tail lacks @@eof: it logs, keeps polling (the stream may resume), and after 5 truncation detections fires a ONE-SHOT FocusTabAsync recovery (counter reset, never routine); 5 more detections even with the window visible fail with KA_REPLY_TRUNCATED ('cut off repeatedly even with the window visible. The chat session stays valid. Resend the same request.'). MinimizeAfterLogin and WindowMode defaults are untouched; no un-minimize outside this recovery. CHANGE 3 — every detection logs 'Truncated reply detected (attempt N): length=.. last60=..' and appends a 'reply-truncated' turn event with Length/Tail/Attempt/FocusTriggered. Tests: truncated-then-completed accepted only after @@eof arrives; 5 → one focus recovery → 5 → failure with 10 events and the FocusTriggered flags; the occlusion flags theory. 601 core + 201 WPF tests green.
- WaitForReplyAsync no longer focuses the tab on truncation by default:
20 silent detections (reset stableReply, Warning log, reply-truncated
event with FocusTriggered:false) then KA_REPLY_TRUNCATED with the resend
hint ('Reply stream was cut off 20 times in this turn. The chat session
stays valid. Resend the same request. If this repeats, set
Browser.AutoFocusOnTruncation to true in keyless.json, or use
WindowMode:Offscreen.'). No FocusTabAsync call site remains in
SendMessageAsync (verified: there never was one in this session file).
- BrowserOptions.AutoFocusOnTruncation (default false): when true, ONE
FocusTabAsync per WaitForReplyAsync at the fifth detection — the only
focus call site in the session. docs/configuration.md documents it;
MinimizeAfterLogin, WindowMode and the Chrome flags untouched.
- CLI confirmation prompt rings the terminal bell ('\a') so the taskbar
flashes without raising a window.
- Tests rewritten: 20-detection default path never focuses and carries the
exact failure message; opt-in path focuses once at the 5th detection
(deterministic swap in the 5th event's callback) and still accepts the
completed reply. 602 core + 201 WPF tests green.
New IChromeWindowController (ChromeWindowController.cs): ConnectOverCDP 10s, first context/page (creates one when empty), Browser.getWindowForTarget -> windowId, Show = setWindowBounds normal @100,100 1280x900 + Target.activateTarget, Hide = setWindowBounds normal @-32000,-32000 (a moved window keeps rendering; a minimized one is throttled), --peek N shows then restores offscreen after N s, closed port fails with 'Chrome is not running on port {port}. Run keyless chrome first.', session detached in finally, every CDP call logged. Factory: CreateChromeWindowController(loggerFactory). CLI: keyless show [workspace] [--peek N] and keyless hide [workspace], following the DoctorCommand pattern; exact success strings per spec. 'keyless chrome' shows the window right after ConnectAsync so an offscreen login page is visible. The login flow (WaitForLoginIfNeeded) shows the window instead of BringToFront — FocusTabAsync stays only as the unused IChatPage contract there. WPF: ChromeViewModel gains IChromeWindowController + ShowChromeCommand (disabled without a workspace), DoctorView adds a 'Show Chrome' GhostButton next to Start Chrome, ShellWindow wires the factory. Test ctors updated. Tests: ChromeWindowControllerTests (closed-port message, exact show/hide CDP payloads, empty-context page creation, CDP failure -> Outcome), provider login tests assert ShowAsync(peek 0) instead of FocusedTabs, WPF ShowChrome tests. 607 core + 204 WPF tests green. WindowMode/MinimizeAfterLogin/flags untouched.
…ide Chrome button
Turn-16 proved Chrome was TRULY minimized (throttled renderer, 20 reply
truncations), not hidden:
- WindowMode default 'Minimized' → 'Offscreen' ('Minimized' kept as a
backward-compatible alias that also means offscreen: launch flags
--window-position=-32000,-32000 --window-size=1280,900; --start-minimized
is gone — a minimized window is throttled by Chrome no matter what the
startup position was).
- PlaywrightChatProvider.MinimizeWindowAsync → HideWindowAsync: the CDP
call now sends {left:-32000, top:-32000, width:1280, height:900,
windowState:normal} and VERIFIES via Browser.getWindowBounds read-back
(left/top == -32000) with a normal→offscreen recovery (3 attempts); the
Win32 fallback ShowWindow(SW_MINIMIZE) → SW_HIDE. Early-hide and
post-login hide both cover every non-Normal mode. Recovery: keyless show
/ WPF Show Chrome.
- Doctor page: Start Chrome / Show Chrome / Hide Chrome (GhostButton,
tooltips, cross-disable while busy). ChromeViewModel gains
HideChromeCommand + IsHiding; all three commands cross-notify.
- keyless config now prints windowMode/minimizeAfterLogin and escapes the
bracket-joined list cells (the '[main, master, release*]' cell parsed as
Spectre markup and crashed the command).
- Tests: WindowMode theory rewritten (no --start-minimized anywhere except
Normal's absence), provider login tests unchanged, WPF hide/cross-disable
tests, config-table fix. 607 core + 208 WPF tests green; WPF Release
build clean; fresh keyless.json contains WindowMode Offscreen.
…me diagnostics Turn-17 (WindowMode=Offscreen, -32000,-32000) truncated 20 times WITH --disable-features=CalculateNativeWinOcclusion present; turn-18 succeeded only while the window was visible and truncated again when hidden. Conclusion: Chrome treats fully offscreen windows as occluded and throttles the renderer regardless of the startup position. STEP 1: launch command line is logged by BrowserEnvironment.StartChromeAsync and forwarded by the connector as a 'chrome-launched' event into the turn log (provider wires ChatSessionOptions.LogEvent into the new internal BrowserConnector.LogEventSink). STEP 2: WindowModeFlag gains TinyCorner (--window-position=w-80,h-80 --window-size=80,80) and EdgePeek (10x10 sliver), computed from the REAL primary screen bounds (GetSystemMetrics SM_CXSCREEN/SM_CYSCREEN — the same values as System.Windows.Forms.Screen.PrimaryScreen.Bounds without the WinForms dependency; fallback 1920x1080). STEP 3: every hide path — ChromeWindowMinimizer.HideAsync (post-login/ early-hide) and ChromeWindowController.HideAsync (keyless hide / WPF / peek return) — now moves to the same tiny 80x80 corner square with windowState:normal instead of -32000,-32000, verified by a full getWindowBounds read-back. DEFAULT FLIP: WindowMode default is now 'TinyCorner' per the task's conditional — Offscreen is proven broken (turn-17) with the flag present. docs/configuration.md documents the visible-on-screen >=80x80 rule. STEP 5: 'keyless diagnostics chrome <workspace>' prints every running chrome.exe command line (Get-CimInstance Win32_Process via PowerShell), the disable-features/window-position lines, and a verdict on CalculateNativeWinOcclusion presence. Tests: TinyCorner/EdgePeek argument theory (explicit 1920x1080), screen bounds non-hardcoded, chrome-launched wiring through the connector, controller hide bounds = tiny corner. 611 core + 208 WPF tests green.
GetSystemMetrics(SM_CXSCREEN/SM_CYSCREEN) returns the FULL monitor resolution, so the 80x80 corner square's bottom edge landed exactly at the physical screen corner — under the always-on-top taskbar: technically on-screen, actually invisible, and not distinguishable in the taskbar either. PrimaryScreenSize() now uses SystemParametersInfo(SPI_GETWORKAREA), which returns the primary monitor's work area RECT (taskbar excluded), keeping the (int Width, int Height) shape and the 1920x1080 fallback. Both consumers — TinyCornerBounds() and WindowModeFlag()/ComposeChromeArguments() (tinycorner/edgepeek) — feed from it unchanged, so the launch flags and the CDP hide/show geometry always agree and stay inside the visible desktop. TinyCornerBounds' public signature is unchanged; only what feeds it changed. Parameter names now say workAreaWidth/workAreaHeight. Tests: with a fabricated 48px taskbar (work area 1920x1032) the square's edges satisfy Top+Height <= 1032 and Left+Width <= 1920; without an offset (1920x1080) it returns exactly (1840,1000,80,80). 613 core + 208 WPF tests green. Live check pending: chrome-launched event shows work-area-based --window-position, and the 80x80 square is visible on the desktop during a turn.
…onest failures IChromeWindowController (keyless show/hide, WPF buttons, login flow) was a weaker duplicate of ChromeWindowMinimizer: one blind setWindowBounds, no read-back, no Win32 fallback, wrong window with several tabs open, and false successes. ChromeWindowMover (new, internal static) is now the single engine: - TryMoveViaCdpAsync: setWindowBounds + Browser.getWindowBounds read-back compared against the EXACT target bounds/windowState, forced-normal recovery between attempts, up to 3 tries. - ShowWindowsViaWin32: EnumWindows over OUR chrome.exe processes (launched PID tree or --user-data-dir/--remote-debugging-port command-line match), ShowWindow with any command, then a VISIBILITY read-back (IsWindowVisible/ IsIconic) — works for BOTH directions now (hide: SW_HIDE; show: SW_SHOWNOACTIVATE so no focus steal). A test-only override hook keeps unit tests away from real windows. - PickChatPage: prefers the page whose URL is chat.deepseek.com, falls back to FirstOrDefault, null lets the caller create one. ChromeWindowMinimizer keeps its public behavior (best-effort, never fails a turn) and orchestrates the mover with the 5s CDP race. ChromeWindowController now verifies (3-attempt read-back), falls back to Win32 in BOTH directions, picks the chat page, and returns Outcome.Failure 'Chrome window could not be located or moved - it may have been closed.' when neither path can verify — no more false 'shown'/'hidden' notes. Win32 hide logic that used to be private in the minimizer moved to the mover; nothing duplicated. Tests: the CDP fake now echoes applied bounds (read-back semantics) with mismatch/throw switches; new tests for the honest-failure message, the Win32-rescued success, and PickChatPage preference. 615 core + 208 WPF tests green.
… defaults again
CreateDefault() serializes every field explicitly, so once a workspace's
keyless.json reaches the current SchemaVersion, MigrateIfStaleAsync never
runs again - and a literal like "WindowMode": "Minimized" written under an
old shipped default deserializes forever, silently defeating every later
default fix (the exact reason Offscreen/Minimized kept truncating turns).
ConfigurationService now upgrades known-stale default literals on EVERY
load, orthogonal to the SchemaVersion-gated migration:
- KnownStaleDefaults: a single static table of (FieldPath, OldDefault,
NewDefault, Read, With) entries - one line per future default change,
with a comment spelling out the pattern. Seeded with Browser.WindowMode
Minimized->TinyCorner and Offscreen->TinyCorner.
- Only values EXACTLY matching a known old default are rewritten (strings
OrdinalIgnoreCase, so case variants of a shipped default count as the
default, not a customization); anything else is left untouched.
- Upgrades back up the raw file as keyless.json.pre-defaults-upgrade.bak
(cannot collide with the real .v{N}.bak schema backups), rewrite the file
with the upgraded options (all other loaded values - user
customizations - survive), and log one informational line per field.
- LastLoadUpgradedFields (concrete property) exposes what fired for tests
and diagnostics; ConfigurationService gains an optional ILoggerFactory
(factory CreateConfigurationService(loggerFactory) passes one through).
Tests: Minimized literal at current SchemaVersion -> TinyCorner + backup +
log line; Offscreen -> TinyCorner; 'Normal' untouched (no backup, no
rewrite); idempotent second load (no re-upgrade, backup preserved); the
rewrite preserves user customizations (ReplyTimeoutSeconds 120, Mode Auto).
620 core + 208 WPF tests green.
…electors fixed selectors.json had NO versioning: written once from the embedded default, read verbatim forever - so improved MessageCopyButton selectors never reached existing workspaces and ReplySource=Auto permanently fell back to Dom via the circuit breaker. - Embedded default carries CatalogVersion: 2; SelectorsCatalog exposes CatalogVersion (absent = 0 = pre-versioning file). - SelectorsCatalog.MergeAsync: when the file's version is older (or forced), back up as selectors.json.v<N>.bak, then MERGE - a key whose workspace value matches ANY version ever shipped for it (ShippedValues table, generated from the resource's git history) is refreshed to the current embedded value; genuine customizations and workspace-only keys survive; keys added by later releases are picked up. Key matching is case- insensitive (tolerant parser); comparisons canonical through System.Text.Json so hand-reformatting still compares equal. One informational log names every refreshed key (provider passes its logger factory). - keyless selectors refresh <workspace>: forces the merge unconditionally with the same backup, printing exactly which keys were refreshed. - The MessageCopyButton miss warning now points at 'keyless selectors refresh' as the first remedy. Tests: old-version file (no CatalogVersion + old MessageCopyButton + old shipped SendButton + hand-edited InputBox) -> copy-button/SendButton refreshed to embedded, InputBox preserved, backup written, log line present; current file byte-identical and no backup; forced refresh no-ops with a backup; missing keys added from the embedded default. 624 core + 208 WPF tests green.
The fixed 20-attempt cap burned out in ~2-4s: each truncation retry
continued without the poll delay, so detections ran back-to-back (~150ms
apart) while real throttled streams took 8+ seconds to resume - a stream
that eventually succeeded was killed anyway.
- New BrowserOptions.TruncationBudgetSeconds (default 20, validated >= 5):
WaitForReplyAsync tracks the time since the FIRST truncation detection
and fails with KA_REPLY_TRUNCATED only once that budget is exceeded.
Failure message names the elapsed seconds: 'Reply stream was cut off
repeatedly for {N}s in this turn. ...'
- The truncation branch now awaits the same Task.Delay(_pollDelayMs) as
the normal poll path - no more busy-looping.
- The reply-truncated turn event gains ElapsedSeconds (real timing in
turn-N.jsonl); the Warning keeps its format.
- AutoFocusOnTruncation still fires once, but at 25% of the budget
(proportional) instead of a hardcoded attempt 5.
- Catalog/doc wording updated to the time-based semantics.
Tests rewritten for the budget semantics (1s/8s budgets to keep them fast:
>=10 spaced detections, ElapsedSeconds ascending and past the budget,
focus at >= quarter, exactly one post-focus detection). 624 core + 208 WPF
tests green across repeated runs.
A live chrome-launched log showed --window-position=1840,1000 (raw screen) despite the SPI_GETWORKAREA fix: the process was not DPI-aware, so at a non-100% display scale GetSystemMetrics/SPI_GETWORKAREA return OS-VIRTUALIZED pixel values that do not match the physical grid a DPI-aware Chrome renders into. 1. DPI awareness: the CLI embeds app.manifest with <dpiAwareness>PerMonitorV2</dpiAwareness> (ApplicationManifest wired in the csproj - the CLI is the process that launches Chrome and computes the geometry; verified the manifest is embedded in the built exe); the WPF csproj sets <ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode> for the Show/Hide buttons' host. 2. Every Chrome launch logs raw screen size vs work area side by side (BrowserEnvironment.RawScreenSize is back), and the chrome-launched turn event gains RawScreenSize/WorkAreaSize fields - future discrepancies are visible in turn logs without manual reproduction. 3. Post-launch verification: after the launch attach, the connector locates the just-launched window's real HWND (ChromeWindowMover.FindOurChromeWindows made internal, polled up to 3s because the port can open before the window exists), GetWindowRects it (ChromeWindowMover.GetWindowBoundsWin32), and logs actual-vs-requested against RequestedWindowBounds(mode) with an 8px tolerance - a MISMATCH warning + chrome-window-bounds event expose Chrome silently clamping/repositioning the window (e.g. minimum-size enforcement), a root cause previously invisible. Read-only; guarded off on the test seam. Tests: RequestedWindowBounds theory (TinyCorner/EdgePeek/Offscreen/Minimized/ Normal-null), RawScreenSize sanity, chrome-launched payload carries both size fields. 630 core + 208 WPF tests green.
…ded nothing) Verified at the binary level: <ApplicationHighDpiMode>PerMonitorV2</...> left ZERO PerMonitorV2 bytes in the built WPF exe, while the CLI's explicit app.manifest approach embedded it. The WPF csproj now uses the same guaranteed mechanism (app.manifest + ApplicationManifest); re-verified the setting is present in the exe. WPF tests green.
…p.OnExit net There was no way to truly CLOSE the Chrome the agent launched - only Show/Hide window moves - and the TinyCorner window is invisible by design, so it could silently outlive the session. IChromeWindowController.CloseAsync(options, allowAttached = false): - ownership matched from the RUNNING processes' command lines via ChromeWindowMover.GetChromeProcessInfos (extracted; CollectOurChromeProcessIds now internal and rebuilt on top of it) - profile-dir match = ours-by-profile, port-only match = the user's own browser on our port; - a Chrome matched only by the port is REFUSED unless allowAttached (the AttachedToRunningChrome ownership rule; message says it was NOT closed); - graceful CDP Browser.close first (whole browser, profile flushed), then the debug port polled (~3s) to confirm it actually went down; - only if it stayed up: Process.Kill(entireProcessTree: true) on every owned root pid - helper processes included - then a final port+process check; - honest KA_BROWSER_CONNECTION_FAILED failure when even the kill cannot be verified. Test seams (ProcessInfosOverride/KillOverride) keep unit tests away from live processes and real kills. WPF: Close Chrome button on the Doctor page (4th ChromeViewModel command, same IsBusy cross-disabling, IsClosing flag); a successful close clears the stale Chrome-ready/visible status and notes 'Chrome closed. Use Start Chrome to open it again.' App.OnExit: ALWAYS-ON safety net closing the Chrome the app itself launched on normal exit - bounded to 4s (worker task + ManualResetEventSlim wait, no sync-over-async: the NoSyncOverAsync rule forbids GetResult/.Result/.Wait), never lets an exception escape. Browser.CloseChromeOnExit keeps its exact meaning (per-turn session disposal); both sites carry comments explaining the distinction. Tests: graceful close confirmed by port-down; kill fallback reached when the port stays up (kill recorded via the seam) with honest failure when it cannot verify; attached-only Chrome refused without any CDP traffic and allowed with the explicit flag; nothing-running failure; WPF close-command success/failure/ disabled. 635 core + 211 WPF tests green.
… caps
Large workspaces still hit DeepSeek's length limit: the default mode was
Full, the TreeAndChanged no-baseline fallback attached the same unlimited
full dump, ChunkDump only capped per-part size (five 240k parts still
arrived together), and nothing filtered huge generated files by name or size.
A) SnapshotOptions.Mode defaults to TreeAndChanged (Full fully supported).
B) SnapshotOptions.MaxTotalAttachmentChars (400,000 = chars/4 tokens): when
tree + dump exceed it, the tree stays complete and the dump keeps only
files REFERENCED by the turn's request text (file name or path,
case-insensitive); everything else becomes a per-folder omission note
('[omitted: N files under src/Core/ (182 KB) - use the file-read tool…').
A snapshot-budget-exceeded turn event records the original total, the
ceiling and the omissions. BuildAttachmentsAsync gained requestText +
logEvent parameters (wired from AgentLoop; feedback path too).
C) The TreeAndChanged no-baseline/empty-diff fallback routes through the
same budget path - a first turn is never an unlimited full dump. Also
fixed a pre-existing flaw: the changed-files diff was computed AFTER the
baseline was overwritten (always vacuously empty), so it is now stashed
at refresh time (.agent/snapshot-changes.json) BEFORE the baseline update
- the second turn's changed-files.txt finally lists real changes.
D) Snapshot.Filter.ExcludeFilePatterns (min.js/min.css/lock files/designer/
g.cs/ModelSnapshot wildcards) drop generated files from the attached dump;
Snapshot.MaxDumpedFileChars (50,000) replaces oversized files with their
heading + '[file too large to attach: N KB - use the file-read tool…]'.
Disk copies stay untouched in both cases.
Tests: budget keeps tree + referenced files with the omission note and the
turn event; second turn uses the stashed diff; oversized file shows heading +
placeholder; denylisted files vanish from the attachment but not from disk;
small workspaces behave exactly as before. 641 core + 211 WPF tests green.
…ject READMEs The DepLens publishing flow is ported: publish.bat in the CLI root runs publish.ps1 from the solution root, which packs the CLI into artifacts/ImanSoftware.KeylessAgent.Cli (auto-bumping the patch version in Directory.Build.props when an artifact already exists) and pushes the nupkg + snupkg to NuGet.org with a 30s timeout, cleaning up on failure. - Icons: the attached logo (665x665 RGBA PNG) goes to icon.png in both the CLI and the WPF project (PackageIcon); the WPF exe also gets icon.ico (256x256 PNG-in-ICO, decoded/resized/re-encoded in pure python - no PIL on this machine) wired as <ApplicationIcon>. - Packaging metadata (Directory.Build.props already carried the Authors/ Company/Copyright/MIT/URL/symbols/PackageReadmeFile block): the CLI csproj adds PackAsTool + ToolCommandName keyless + PackageId ImanSoftware.KeylessAgent.Cli + Description + tags + PackageIcon + the icon/readme pack items. The WPF csproj adds the same pack items. Per this repo's test-enforced convention the <Version> stays ONLY in Directory.Build.props, so publish.ps1 was adapted to bump it THERE (not the csproj like DepLens) - the VersionMatchesDirectoryBuildProps test stays green. - READMEs: the CLI README gains a full command reference (run/resume/undo/ config/chrome/doctor/show/hide/diagnostics chrome/selectors refresh with examples, interactive slash commands, Ctrl+C, exit codes) plus the versioning/publishing section; the WPF project gets its own README (pages, Chrome window control incl. Close Chrome, requirements); the root README (319 lines, written from the final code) remains the general project documentation and keeps every token the consistency tests require. Verified: dotnet pack produces ImanSoftware.KeylessAgent.Cli.1.0.0.nupkg + snupkg with icon.png/README.md/keyless tool metadata inside; manifest + DPI settings intact; 641 core + 211 WPF tests green. First real publish needs NUGET_API_KEY (setx NUGET_API_KEY ...).
1. icon.ico rebuilt WITHOUT any hand-written pixel code: a one-off WPF converter decodes icon.png, resizes to 256x256 with the SDK's TransformedBitmap/PngBitmapEncoder, and the script only wraps those encoder-produced PNG bytes verbatim in the documented 6+16-byte PNG-compressed ICO entry (planes=1, bpp=32). Verified VISUALLY: the .ico was decoded back with IconBitmapDecoder and rendered to a PNG - clean cyan-on-black diamond logo, colors and shape identical to icon.png, no scrambling (the broken render came from the old hand-rolled decode/ re-encode path, now deleted). 2. WPF csproj: <IsPackable>false</IsPackable> (the pack items from the previous round removed as dead weight). Verified: dotnet pack on the WPF project produces NO package content. 3. publish.ps1 version-bump path exercised end-to-end WITHOUT a real push (-SkipPush, dummy NUGET_API_KEY): run 1 packed 1.0.0; run 2 detected the 1.0.0 artifact, auto-bumped 1.0.0 -> 1.0.1 in Directory.Build.props (csproj untouched - zero <Version> elements there), packed 1.0.1, and the summary shows 'Directory.Build.props updated'. DBP restored to 1.0.0 afterwards, artifacts/ gitignored (already) and the local artifacts folder removed; VersionMatchesDirectoryBuildProps passes and the full suites stay green (641 core + 211 WPF).
main's PR #1 was a squash of an EARLIER snapshot of this same branch, so every overlapping file conflicted add/add; each resolves to the feature version (the newest state - main's README, for example, is byte-identical to the feature's pre-restructure copy). Everything main added that the feature never touched (.editorconfig, .gitattributes, .github workflow, global.json, nuget.config, LICENSE.txt, Directory.Packages.props, src/Playground) carries over unchanged. Merged tree: build clean, 641 core + 211 WPF tests green.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Merges the complete feature branch into main (main PR #1 was an early squash of this same branch).
Highlights since PR #1
Merged tree verified: build clean, 641 core + 211 WPF tests green, dotnet pack smoke-tested.