Showing preview only (864K chars total). Download the full file or copy to clipboard to get everything.
Repository: JocysCom/FocusLogger
Branch: main
Commit: bd497bb745a6
Files: 88
Total size: 826.0 KB
Directory structure:
gitextract_pkit4a3b/
├── .ai/
│ ├── ReadMe.md
│ ├── coding-guideline.instructions.md
│ ├── instructions.md
│ ├── repository-analysis.instructions.md
│ └── skills/
│ └── ai-self-improvement/
│ ├── SKILL.md
│ └── scripts/
│ └── Sync-AgentAssets.ps1
├── .claude/
│ ├── coding-guideline.instructions.md
│ ├── instructions.md
│ ├── repository-analysis.instructions.md
│ └── skills/
│ └── ai-self-improvement/
│ ├── SKILL.md
│ └── scripts/
│ └── Sync-AgentAssets.ps1
├── .editorconfig
├── .github/
│ ├── copilot-instructions.md
│ ├── instructions/
│ │ ├── coding-guideline.instructions.md
│ │ └── repository-analysis.instructions.md
│ └── skills/
│ └── ai-self-improvement/
│ ├── SKILL.md
│ └── scripts/
│ └── Sync-AgentAssets.ps1
├── .gitignore
├── .markdownlint.json
├── Documents/
│ ├── App_1_Sign.ps1
│ ├── App_2_Zip.ps1
│ ├── Take_Screenshot.ps1
│ └── Take_Screenshot.ps1.cs
├── FocusLogger/
│ ├── App.xaml
│ ├── App.xaml.cs
│ ├── AssemblyInfo.cs
│ ├── Common/
│ │ ├── DataItem.cs
│ │ ├── DataItemType.cs
│ │ └── NativeMethods.cs
│ ├── Controls/
│ │ ├── DataListControl.xaml
│ │ └── DataListControl.xaml.cs
│ ├── JocysCom/
│ │ ├── Collections/
│ │ │ └── CollectionsHelper.cs
│ │ ├── Common/
│ │ │ └── Helper.cs
│ │ ├── ComponentModel/
│ │ │ ├── BindingListInvoked.cs
│ │ │ ├── NotifyPropertyChanged.cs
│ │ │ ├── PropertyComparer.cs
│ │ │ └── SortableBindingList.cs
│ │ ├── Configuration/
│ │ │ ├── Arguments.cs
│ │ │ ├── AssemblyInfo.cs
│ │ │ ├── ISettingsData.cs
│ │ │ ├── ISettingsFileItem.cs
│ │ │ ├── ISettingsItem.cs
│ │ │ ├── ISettingsListFileItem.cs
│ │ │ ├── SettingsData.cs
│ │ │ ├── SettingsHelper.cs
│ │ │ ├── SettingsItem.cs
│ │ │ └── SettingsParser.cs
│ │ ├── Controls/
│ │ │ ├── ControlsHelper.WPF.UseWindowsForms.cs
│ │ │ ├── ControlsHelper.WPF.cs
│ │ │ ├── ControlsHelper.cs
│ │ │ ├── InfoControl.xaml
│ │ │ ├── InfoControl.xaml.cs
│ │ │ ├── InfoHelpProvider.cs
│ │ │ ├── InitHelper.cs
│ │ │ ├── ItemFormattingConverter.cs
│ │ │ ├── MessageBoxWindow.xaml
│ │ │ ├── MessageBoxWindow.xaml.cs
│ │ │ ├── TabIndexConverter.cs
│ │ │ ├── Themes/
│ │ │ │ ├── Convert_SVG_to_XAML.ps1
│ │ │ │ ├── Default.xaml
│ │ │ │ ├── Default_MultiReplace.ps1
│ │ │ │ ├── Icons.xaml
│ │ │ │ └── Icons.xaml.cs
│ │ │ └── ToolStripBorderlessRenderer.cs
│ │ ├── Data/
│ │ │ └── SqlHelper.Types.cs
│ │ ├── IO/
│ │ │ └── PathHelper.cs
│ │ ├── MakeLinks_Ref.ps1
│ │ ├── Runtime/
│ │ │ ├── RuntimeHelper.cs
│ │ │ └── Serializer.cs
│ │ └── Text/
│ │ └── Helper.cs
│ ├── JocysCom.FocusLogger.csproj
│ ├── MainWindow.xaml
│ ├── MainWindow.xaml.cs
│ └── Resources/
│ ├── AiAnalysisPrompt.md
│ └── Icons/
│ ├── Convert_SVG_to_XAML.ps1
│ ├── IconExperience.License.txt
│ ├── Icons_Default.xaml
│ └── Icons_Default.xaml.cs
├── FocusLogger.Tests/
│ ├── CsvExportTests.cs
│ ├── JocysCom.FocusLogger.Tests.csproj
│ └── UIAutomationTests.cs
├── JocysCom.FocusLogger.slnx
├── LICENSE
├── README.md
├── Resources/
│ └── ZipFiles.ps1
├── SECURITY.md
├── Settings.XamlStyler
└── Solution_Cleanup.ps1
================================================
FILE CONTENTS
================================================
================================================
FILE: .ai/ReadMe.md
================================================
## Files
- `developer-info.md` - Developer information about the solution, which will be used when creating the repository-analysis.instructions.md file.
- `instructions.md` - Main AI Agent System Message instructions used by AI Agents, like CLINE or CoPilot.
- `repository-analysis.instructions.md` - Autogenerated Repository Analysis file that is supplied to the AI Agent as a System message after instructions.md.
- `repository-analysis.prompt.md` - User Message prompt that instructs AI to create repository-analysis.instructions.md.
- `Update-AgentInstructions.ps1` - Sets all `*instruction.md` files as AI agent system message instruction files. Used by AI agents, like CLINE or Copilot.
## Custom instructions: GitHub Copilot
With GitHub Copilot, you can receive chat responses tailored to your team'ss workflow, preferred tools, and project specifics.
Instead of adding this contextual detail to each chat query, you can create a file that supplies this information automatically.
While this additional context won't appear in the chat, it is available to GitHub Copilot, allowing it to generate more accurate and relevant responses.
**How to Enable Custom Instructions**
Enable the feature via Tools > Options > GitHub > Copilot > and check (Preview) Enable custom instructions to be loaded from .github/copilot-instructions.md files and added to requests.
Add copilot-instruction.md in the root of your respository inside the .github file, create the file if it doesn't already exist.
GitHub Copilot Enable Custom Instructions
Learn more about creating custom instructions:
https://docs.github.com/en/enterprise-cloud@latest/copilot/customizing-copilot/adding-custom-instructions-for-github-copilot
## Custom instructions: CLINE
All CLINE instructions are stored inside the .clinerules\ folder.
All \*.md files will be loaded as system messages.
## Custom instructions: OpenAI Codex
Instructions are stored in `%USERPROFILE%\.codex\instructions.md` file.
## User Instructions to create `repository-analysis.md`
Note: Switch AI agent to `Plan` mode before submitting this message. This will yield better results.
### LLM Selection Guideline
If your deployment environment allows, prefer a newer, higher-capacity model—such as Anthropic Claude Sonnet 4 or Google Gemini 2.5 Pro - instead of the current OpenAI GPT 4 lineup. These frontier models typically deliver stronger reasoning, broader context windows, and more consistent output quality on complex tasks.
### Install the Mermaid preview plug-in
1. Open Extensions (Ctrl + Shift + X).
2. Search: Markdown Preview Mermaid Support (publisher: Matt Bierner).
3. Click Install – or hit Ctrl + P.
### Open Preview
1. Open the menu on the tab header of the file.
2. Click "Open Preview" – or hit Ctrl + Shift + V.
### Re Open Preview
1. Open the menu on the tab header of the file.
2. Click "Reopen Editor With..."
3. Click "Markdown Preview (Built-In)"
================================================
FILE: .ai/coding-guideline.instructions.md
================================================
# Coding Guidelines
- If the qdrant-mcp-server is running, use it for all permanent memory operations (e.g. storing user information).
- After making changes, ALWAYS start a new server for testing.
- Kill all existing related servers from previous testing before starting a new server.
- Prefer the simplest viable solution; avoid over-engineering.
- Do not add broad try/catch or wrapper layers unless required by a failing test or explicit requirement; if you catch, rethrow to preserve the stack.
- Before writing new code, actively look for existing utilities or functions that can be reused instead of duplicated.
- New helper methods or classes must be justified with a clear, documented need for functionality that is unavailable elsewhere in the codebase.
- Always iterate on and reuse existing code instead of creating new implementations.
- Avoid adding layers of abstraction that do not deliver clear value.
- Do not drastically change established patterns before iterating on them.
- No duplication / SSOT: update or move existing code instead of adding parallel implementations. If you introduce a replacement, remove the old one **in the same change**.
- Write code that accounts for different environments (dev, test, and prod).
- Only modify what is explicitly requested or clearly necessary; do **not** create new files or modules unless explicitly requested.
- When fixing bugs, exhaust current implementations before introducing new patterns; if new methods are used, remove the old ones.
- Keep the codebase clean and organized.
- Avoid one-off scripts unless absolutely necessary.
- Use mocks only for tests, not for dev or prod.
- Never add stubbing or fake data in dev or prod environments.
- Never overwrite the .env file without explicit confirmation.
- Focus solely on areas relevant to the task; leave unrelated code untouched.
- Write thorough tests for all major functionality.
- Avoid major changes to the existing architecture unless explicitly instructed.
- Always consider the impact on other methods and areas of the code.
- Prefer to wrap long lines for better readability.
- Preserve existing formatting; limit formatting to lines you changed and match surrounding style. Also remove any unused imports/usings or dead code **introduced by your edits**.
- Stability primitives (repeatability > cleverness): when the repo provides an established way to perform an operation (repo-owned script, documented command snippet, standard PS1 under .ai/Scripts), treat it as the single source of truth. Use it verbatim instead of synthesizing “equivalent” commands. Only deviate when explicitly asked or when the primitive is proven broken in this environment (and then fix the primitive, not invent a parallel path).
- No code file that you **create or modify** may exceed **6000 tokens (~24 KB)** once your changes are applied.
- If your changes alone would push the file past this limit, either trim the change or ask for explicit permission to refactor; do **not** alter unrelated code solely to meet the limit.
- Existing oversized files are left untouched unless the user explicitly requests a refactor.
## Source Control Conventions
Naming conventions for issues, branches, and pull requests.
### Categories
| Category | Use for |
|----------|---------|
| `FEAT` | New features |
| `FIX` | Bug fixes |
| `TECH` | Infrastructure, dependencies, refactoring |
| `DOCS` | Documentation |
### Naming Patterns
| Item | Pattern | Example |
|------------|---------|---------|
| **Issue** | `{CATEGORY}: {Description}` | `FEAT: Download logs to CSV` |
| **Branch** | `{CATEGORY}-{issue#}-{lowercase-dashed-name}` | `FEAT-21-download-logs-to-csv` |
| **PR** | `PR: #{issue#}: {CATEGORY}: {Description}` | `PR: #21: FEAT: Download logs to CSV` |
- Branch names: lowercase, words separated by dashes, non-ASCII replaced with a single dash, derived from issue title but may be shortened.
- PR body must reference the issue with `Closes #{issue#}`.
- Merge via PR only — no direct pushes to `main` or `master`.
- Feature branches are always created from `main` or `master`. Never merge one feature branch into another — only merge `main` if you need to catch up.
- Always create branches from latest origin/main or origin/main, never merge sub-branches, confirm before risky git ops.
- Do not add Co-Authored-By {AI model}/{AI company} lines to commits.
- Never close issues before a release is published with the fix.
Use the following guidelines:
1. Doc Comment Enhancement for IntelliSense
- Replace or augment simple comments with relevant doc comment syntax that is supported by IntelliSense as needed.
- Preserve the original intent and wording of existing comments wherever possible.
2. Code Layout for Clarity
- Place the most important or user-editable sections at the top if logically appropriate.
- Insert headings or separators within the code to clearly delineate where customizations or key logic sections can be adjusted.
3. No Extraneous Code Comments
- Do not include "one-off" or user-directed commentary in the code.
- Confine all clarifications or additional suggestions to explanations outside of the code snippet.
4. Avoid Outdated or Deprecated Methods
- Refrain from introducing or relying on obsolete or deprecated methods and libraries.
- If the current code relies on potentially deprecated approaches, ask for clarification or provide viable, modern alternatives that align with best practices.
5. Testing and Validation
- Suggest running unit tests or simulations on the modified segments to confirm that the changes fix the issue without impacting overall functionality.
- Ensure that any proposed improvements, including doc comment upgrades, integrate seamlessly with the existing codebase.
- After all code modifications, navigate to the affected project directory and build C# then Angular to confirm the application compiles without errors:
cd {PROJECT} && dotnet build {PROJECT}.csproj
cd {PROJECT}/ClientApp && ng build
- Run relevant unit tests if code changes affect core logic.
- If the developer certificate is not trusted, then execute: dotnet dev-certs https --trust
- To launch project use: dotnet watch run --project {PROJECT}/{PROJECT}.csproj --launch-profile "{PROJECT} (NG Build)"
6. Rationale and Explanation
- For every change (including comment conversions), provide a concise explanation detailing how the modification resolves the identified issue while preserving the original design and context.
- Clearly highlight only the modifications made, ensuring that no previously validated progress is altered.
- NOTE: Summarize reasoning for the user, but do NOT expose full chain-of-thought. Keep internal deliberations internal; surface only the concise rationale needed to justify each change.
7. Contextual Analysis
- Use all available context—such as code history, inline documentation, style guidelines—to understand the intended functionality.
- When inspecting an existing file for understanding, prefer reading the whole file in a
single `read_file` call when it comfortably fits in context; switch to targeted slices
only when the file is too large, the tool truncates it, or a specific anchor line is
already known.
- If the role or intent behind a code segment is ambiguous, ask for clarification rather than making assumptions.
8. Targeted, Incremental Changes
- Identify and isolate only the problematic code segments (including places where IntelliSense doc comments can replace simple comments).
- Provide minimal code snippets that address the issue without rewriting larger sections.
- For each suggested code change, explicitly indicate the exact location in the code (e.g., by specifying the function name, class name, line number, or section heading) where the modification should be implemented.
9. Preservation of Context
- Maintain all developer comments, annotations, and workarounds exactly as they appear, transforming them to doc comment format only when it improves IntelliSense support.
- Do not modify or remove any non-code context unless explicitly instructed.
- Avoid introducing new, irrelevant comments in the code.
10. Launching {PROJECT} Correctly:
- Navigate to the {PROJECT} project folder.
- Run the following command to launch the project with live reload and proper debugging configuration:
dotnet watch run --launch-profile "{PROJECT} (NG Build)" --project {PROJECT}/{PROJECT}.csproj
- This command will start the {PROJECT} project on the designated debugging session URL.
- Ensure that any previous {PROJECT} instances are terminated before running this command.
================================================
FILE: .ai/instructions.md
================================================
## Role
Your role is to analyze and improve code by making only localized, targeted changes. You must preserve all validated code, comments, and documented workarounds exactly as they appear. Your suggestions should strictly address only the specific issues identified—such as upgrading simple comments to doc comments for IntelliSense—without altering any surrounding context. Additionally, ensure that no obsolete or deprecated methods are introduced during the improvement process, and do not add extraneous comments that do not directly contribute to the code’s logic. Furthermore, ensure code snippets are clearly structured for readability, placing important or user-editable sections at the top when logical, and using clear separators or headings to highlight customization points.
Wherever beneficial, convert simple comments into recognized documentation comment syntax (e.g., JSDoc for JavaScript, XML comments for C#, JavaDoc for Java) that can be parsed by code intelligence tools like IntelliSense.
Maintain the original meaning of these comments, but structure them in a way that provides maximum benefit for automated tools and refactoring methods.
Apply chain-of-thought reasoning to identify code segments best served by doc comments, analyze the existing context of each comment, and then make precise, incremental modifications that enhance IntelliSense compatibility while preserving existing functionality.
## Output
Wrap any and all code—including regular code snippets, inline code segments, outputs, pseudocode, or any text that represents code—in Markdown code blocks with a language identifier (e.g., ```typescript, ```powershell).
================================================
FILE: .ai/repository-analysis.instructions.md
================================================
# Repository Analysis
## 1. Repository Overview
This document provides a factual reference for the Jocys.com FocusLogger repository, aimed at developers and AI coding agents working on the codebase.
**FocusLogger** is a Windows desktop utility that monitors and logs which process or program takes window focus. It targets users (especially gamers and power users) who experience unexpected focus stealing — where a background process briefly grabs foreground focus, interrupting gameplay or work. The tool logs every focus change with timestamps, process details, window class names, and focus-state flags, allowing users to identify the culprit.
- **Repository:** https://github.com/JocysCom/FocusLogger
- **License:** GNU General Public License v3.0
- **Current version:** 1.2.6
- **Target platform:** Windows 10+ with .NET 8.0
- **Primary audiences:** Gamers, power users, IT support personnel diagnosing focus-stealing issues.
## 2. Top-Level Structure
This section maps every top-level directory and file to help navigate the repository quickly.
| Path | Purpose |
|------|---------|
| `FocusLogger/` | Main application project (WPF, .NET 8.0). Contains all app source code, shared library, and resources. |
| `FocusLogger.Tests/` | MSTest test project. Unit tests for CSV export and UI automation tests. |
| `Documents/` | Release engineering: signing scripts, zip packaging scripts, screenshot tooling, and pre-built release files. |
| `Resources/` | Solution-level shared scripts (currently `ZipFiles.ps1` for checksum-aware zip packaging). |
| `.ai/` | AI agent instructions, coding guidelines, repository analysis, and skills. |
| `JocysCom.FocusLogger.slnx` | Solution file (XML-based `.slnx` format) referencing the two projects. |
| `README.md` | Project overview, download link, system requirements, screenshot. |
| `LICENSE` | GPLv3 license text. |
| `SECURITY.md` | Security vulnerability reporting policy (support@jocys.com). |
| `Settings.XamlStyler` | XamlStyler configuration for consistent XAML formatting. |
| `Solution_Cleanup.ps1` | PowerShell script for cleaning build artifacts. |
## 3. Technology Stack & Key Dependencies
This section lists verified technologies and versions drawn from project files.
| Technology | Version / Detail | Evidence |
|------------|-----------------|----------|
| .NET | 8.0 (`net8.0-windows`) | `JocysCom.FocusLogger.csproj` TargetFramework |
| C# | Implicit (SDK default for .NET 8) | SDK-style project |
| WPF | `<UseWPF>true</UseWPF>` | csproj |
| Windows Forms interop | `<UseWindowsForms>true</UseWindowsForms>` | csproj — used for P/Invoke helpers and DPI awareness |
| MSTest | v3.x (`MSTest.TestFramework 3.*`, `MSTest.TestAdapter 3.*`) | Test csproj PackageReference |
| Microsoft.NET.Test.Sdk | 17.x | Test csproj PackageReference |
| Windows API (user32.dll) | P/Invoke | `NativeMethods.cs` |
| PowerShell | Scripts for build, sign, zip, cleanup | `Documents/`, `Resources/`, root |
| XamlStyler | Config present | `Settings.XamlStyler` |
**No NuGet package dependencies** in the main application project — all functionality comes from .NET SDK and the embedded `JocysCom.ClassLibrary`.
## 4. Architecture & Runtime Model
This section describes how the application is structured and how it operates at runtime.
FocusLogger is a **single-executable WPF desktop application** that polls Windows API functions to detect focus changes. It does not persist log data between sessions (in-memory only) but provides CSV export for offline analysis.
### Architectural layers
```mermaid
graph TD
subgraph UI["UI Layer (WPF)"]
App["App.xaml.cs<br/>Entry point, DPI aware"]
MW["MainWindow.xaml.cs<br/>Main window frame"]
DLC["DataListControl.xaml.cs<br/>Core logging UI + logic"]
end
subgraph Core["Core Logic"]
DI["DataItem.cs<br/>Log entry model"]
DIT["DataItemType.cs<br/>Entry type enum"]
NM["NativeMethods.cs<br/>P/Invoke declarations"]
CSV["CSV Export<br/>BuildCsvContent, CsvEscape"]
end
subgraph Shared["JocysCom.ClassLibrary (embedded)"]
Config["Configuration<br/>SettingsData, SettingsItem, AssemblyInfo"]
CompModel["ComponentModel<br/>SortableBindingList, BindingListInvoked"]
Controls["Controls<br/>ControlsHelper, ItemFormattingConverter,<br/>InfoControl, MessageBoxWindow"]
Other["Collections, IO, Text, Runtime, Data"]
end
subgraph WinAPI["Windows OS"]
User32["user32.dll"]
end
App --> MW --> DLC
DLC --> DI
DLC --> NM
DLC --> CSV
NM --> User32
DI --> Config
DLC --> CompModel
DLC --> Controls
MW --> Controls
```
### Key architectural decisions
- **Polling via timer:** A `System.Timers.Timer` with 1ms interval (non-auto-reset) continuously polls `GetActiveWindow()` and `GetForegroundWindow()`. Duplicate events are suppressed via `DataItem.IsSame()`.
- **Thread safety:** Timer fires on a thread-pool thread; UI updates are marshalled via `ControlsHelper.BeginInvoke()`. A `lock(AddLock)` synchronizes the polling logic.
- **Embedded shared library:** `JocysCom.ClassLibrary` files are included directly in `FocusLogger/JocysCom/` rather than as a compiled DLL or NuGet package.
- **No MVVM framework:** Code-behind pattern with data binding. `DataListControl.xaml.cs` contains both view-model-like logic and model interaction.
## 5. Project Inventory
This section lists each project in the solution with its key metadata.
### 5.1 JocysCom.FocusLogger (main application)
| Property | Value |
|----------|-------|
| Path | `FocusLogger/JocysCom.FocusLogger.csproj` |
| Output type | `WinExe` |
| Target framework | `net8.0-windows` |
| Assembly name | `JocysCom.FocusLogger` |
| Description | Find out which process or program is taking the window focus. In game, mouse and keyboard could temporarily stop responding if another program takes the focus. This tool could help diagnose which program is stealing the focus. |
| Version | 1.2.6 |
| NuGet dependencies | None |
| Embedded resources | `Resources/BuildDate.txt` (auto-generated), `Resources/AiAnalysisPrompt.md` |
**Source structure:**
| Directory | Contents |
|-----------|----------|
| `FocusLogger/` (root) | `App.xaml(.cs)`, `MainWindow.xaml(.cs)`, `AssemblyInfo.cs`, `App.ico` |
| `FocusLogger/Common/` | `DataItem.cs`, `DataItemType.cs`, `NativeMethods.cs` |
| `FocusLogger/Controls/` | `DataListControl.xaml(.cs)` — core logging control |
| `FocusLogger/JocysCom/` | Embedded `JocysCom.ClassLibrary` (~30 files across Collections, Common, ComponentModel, Configuration, Controls, Data, IO, Runtime, Text) |
| `FocusLogger/Resources/` | `AiAnalysisPrompt.md`, `BuildDate.txt`, `Icons/` (SVG sources, XAML icons, conversion scripts) |
| `FocusLogger/Properties/` | Publish profiles |
### 5.2 JocysCom.FocusLogger.Tests (test project)
| Property | Value |
|----------|-------|
| Path | `FocusLogger.Tests/JocysCom.FocusLogger.Tests.csproj` |
| Target framework | `net8.0-windows` |
| Test framework | MSTest v3.x |
| Project reference | `FocusLogger/JocysCom.FocusLogger.csproj` |
**Test files:**
| File | Purpose |
|------|---------|
| `CsvExportTests.cs` | Unit tests for `CsvEscape` and `BuildCsvContent` methods |
| `UIAutomationTests.cs` | UI automation tests using `System.Windows.Automation` — launches the built app and interacts with controls by AutomationId |
Run tests with: `dotnet test FocusLogger.Tests/JocysCom.FocusLogger.Tests.csproj`
## 6. Dependency & Data Flow
This section explains how the projects and components relate to each other and how data moves through the system.
### Project dependency graph
```mermaid
graph LR
Tests["FocusLogger.Tests<br/>(MSTest v3)"] -->|ProjectReference| App["JocysCom.FocusLogger<br/>(WPF App)"]
App -->|embedded source| Shared["JocysCom.ClassLibrary<br/>(in FocusLogger/JocysCom/)"]
App -->|P/Invoke| WinAPI["user32.dll"]
```
### Runtime data flow
1. `System.Timers.Timer` fires (1ms interval, non-auto-reset).
2. `DataListControl.UpdateInfo()` acquires `AddLock`.
3. Calls `NativeMethods.GetActiveWindow()` and `NativeMethods.GetForegroundWindow()`.
4. For each handle, `GetItemFromHandle()` creates a `DataItem` with timestamp, focus flags (mouse/keyboard/caret), window title, and window class name.
5. `IsSame()` checks if the event differs from the previous one; if not, it is skipped.
6. `UpdateFromProcess()` enriches the `DataItem` with process name and path (with error handling for restricted processes).
7. The item is inserted at position 0 of `SortableBindingList<DataItem>` via `ControlsHelper.BeginInvoke()` (UI thread dispatch).
8. The WPF `DataGrid` updates via data binding. `ItemFormattingConverter` translates boolean flags to icons.
### CSV export flow
1. User clicks "Save CSV" button.
2. `SaveFileDialog` prompts for file location.
3. `BuildCsvContent()` iterates all `DataItem` entries, writing CSV with headers: Date, PID, Process Name, Active, Mouse, Keyboard, Caret, Window Title, Window Class, Path.
4. File is written as UTF-8.
5. "Explore" button opens the saved file location in Explorer.
6. "AI Prompt Example" button shows the embedded `AiAnalysisPrompt.md` in a `MessageBoxWindow` for users to copy and paste into an AI assistant along with their CSV.
## 7. Build, Test, CI/CD & Operational Workflows
This section documents how the project is built, tested, and released based on repository evidence.
### Build
```bash
dotnet build JocysCom.FocusLogger.slnx
```
- **Pre-build event:** Generates `Resources/BuildDate.txt` with the current ISO 8601 timestamp via PowerShell.
- **Output:** Single `JocysCom.FocusLogger.exe` in `bin/{Configuration}/net8.0-windows/`.
- **Debug configuration:** Embedded PDB symbols (`DebugType: embedded`).
### Test
```bash
dotnet test FocusLogger.Tests/JocysCom.FocusLogger.Tests.csproj
```
- **Framework:** MSTest v3.x with Microsoft.NET.Test.Sdk 17.x.
- **Unit tests:** `CsvExportTests` — validates CSV escaping and content generation.
- **UI automation tests:** `UIAutomationTests` — launches the built application and interacts via `System.Windows.Automation`. Requires a prior build of the main project.
### Release / packaging scripts
| Script | Purpose |
|--------|---------|
| `Documents/App_1_Sign.ps1` | Code-signs the application executable. |
| `Documents/App_2_Zip.ps1` | Packages the signed executable into a release ZIP. |
| `Resources/ZipFiles.ps1` | Shared utility for checksum-aware ZIP creation (compares source/dest checksums before rebuilding). |
| `Documents/Take_Screenshot.ps1` | Captures application screenshot for documentation. |
| `Documents/Take_Screenshot.ps1.cs` | C# helper compiled by the screenshot script. |
| `Solution_Cleanup.ps1` | Cleans `bin/`, `obj/`, and other build artifacts. |
### Icon workflow
SVG icon sources are stored in `FocusLogger/Resources/Icons/Icons_Default/`. The script `Convert_SVG_to_XAML.ps1` converts them to XAML resource dictionaries (`Icons_Default.xaml`).
### CI/CD
No CI/CD workflow files were found under `.github/workflows/`. Builds and releases appear to be performed locally.
## 8. Documentation Map
This section identifies where documentation lives in the repository.
| Location | Audience | Content |
|----------|----------|---------|
| `README.md` | End users, contributors | Project overview, download link, system requirements, screenshot |
| `SECURITY.md` | Security researchers | Vulnerability reporting policy |
| `LICENSE` | All | GPLv3 full text |
| `.ai/ReadMe.md` | AI agents, developers | Explains the purpose of each file in the `.ai/` directory and how custom instructions work for Copilot, CLINE, and Codex |
| `.ai/instructions.md` | AI agents | Role definition and output formatting rules for AI-assisted edits |
| `.ai/coding-guideline.instructions.md` | AI agents | Detailed coding guidelines, source control conventions (branch/PR naming), testing workflow, and constraints for AI agents |
| `.ai/repository-analysis.instructions.md` | AI agents, developers | This file — comprehensive repository reference |
| `.ai/skills/` | AI agents | Skill definitions (e.g., `ai-self-improvement`) for agent-assisted workflows |
| `FocusLogger/Resources/AiAnalysisPrompt.md` | End users | Prompt template for users to paste into AI assistants alongside exported CSV logs |
| `Documents/Images/` | README, users | Application screenshot |
| `Settings.XamlStyler` | Developers | XamlStyler formatting configuration |
### Documentation taxonomy
```mermaid
graph TD
subgraph EndUsers["End-User Documentation"]
README["README.md<br/>Overview, download, requirements"]
SECURITY["SECURITY.md<br/>Vulnerability reporting"]
LICENSE["LICENSE<br/>GPLv3"]
AiPrompt["FocusLogger/Resources/<br/>AiAnalysisPrompt.md<br/>Prompt template for CSV analysis"]
Screenshot["Documents/Images/<br/>Application screenshot"]
end
subgraph AIAgents["AI Agent Instructions"]
AiReadMe[".ai/ReadMe.md<br/>Directory guide"]
AiInstr[".ai/instructions.md<br/>Role & output rules"]
AiCoding[".ai/coding-guideline<br/>.instructions.md<br/>Coding & SCM conventions"]
AiRepo[".ai/repository-analysis<br/>.instructions.md<br/>This file"]
AiSkills[".ai/skills/<br/>Agent skill definitions"]
end
subgraph DevConfig["Developer Configuration"]
XamlStyler["Settings.XamlStyler<br/>XAML formatting"]
end
```
## 9. AI-Agent-Relevant Conventions and Constraints
This section captures rules and patterns that materially affect automated edits. The full set of coding and source-control conventions is defined in `.ai/coding-guideline.instructions.md`; the highlights below are the items most likely to cause mistakes if overlooked.
1. **Coding style:** Follow Microsoft C# conventions. PascalCase for public members, camelCase for locals. Some private fields use `_PascalCase` (e.g., `_Date`). Preserve existing naming patterns in each file. Prefer the simplest viable solution; avoid over-engineering.
2. **Doc comments:** Per `.ai/instructions.md`, convert simple comments to XML documentation comments where beneficial for IntelliSense. Do not alter surrounding code when doing so.
3. **Shared library files (`FocusLogger/JocysCom/`):** These are embedded from a shared `JocysCom.ClassLibrary`. Exercise caution when editing — changes here may diverge from the upstream library.
4. **XAML formatting:** The repository uses XamlStyler (see `Settings.XamlStyler`). XAML edits should conform to the configured style.
5. **No NuGet packages in the main app:** All dependencies are framework-provided or embedded source. Do not introduce NuGet package dependencies without explicit approval.
6. **Test project uses MSTest v3:** New tests should follow MSTest v3 patterns (`[TestClass]`, `[TestMethod]`, `Assert.*`).
7. **UI automation tests depend on a built executable:** `UIAutomationTests` locate the app at a relative path from the test output. Building the main project before running these tests is required.
8. **Pre-build event:** The csproj generates `Resources/BuildDate.txt` via PowerShell. This file should not be manually edited or committed.
9. **Solution format:** Uses `.slnx` (XML-based solution format), not the older `.sln` text format.
10. **No CI/CD pipelines:** All build and release steps are manual/local. Scripts in `Documents/` handle signing and packaging.
11. **File size limit:** No code file that is created or modified may exceed 6000 tokens (~24 KB).
12. **Source control conventions:** Issues use `{CATEGORY}: {Description}` (FEAT, FIX, TECH, DOCS). Branches use `{CATEGORY}-{issue#}-{lowercase-dashed-name}`. PRs use `PR: #{issue#}: {CATEGORY}: {Description}` and must reference the issue with `Closes #{issue#}`. Merge via PR only — no direct pushes to `main`.
13. **No Co-Authored-By lines:** Do not add `Co-Authored-By` AI model/company lines to commits.
14. **Issue closure:** Never close issues before a release is published with the fix.
15. **No duplication / SSOT:** Update or move existing code instead of adding parallel implementations. If introducing a replacement, remove the old one in the same change.
16. **Stability primitives:** When the repo provides an established script or command, use it verbatim. Only deviate when it is proven broken, and then fix the primitive rather than inventing a parallel path.
================================================
FILE: .ai/skills/ai-self-improvement/SKILL.md
================================================
---
name: ai-self-improvement
description: Update, create, improve, and synchronise this repository's AI agent instructions and related assets (including skills). Use when the user asks to create or edit a skill/SKILL.md, modify the agent's own instructions/processes, restructure instruction governance, migrate instruction content into skills, or run/adjust the sync pipeline that publishes `.ai/` sources into agent-specific folders. Load this skill before writing any SKILL.md, .instructions.md, or touching any skills/ folder (.ai/, .claude/, .roo/, .github/). It tells you the correct location (.ai/) and the sync step, so files end up in the right place.
---
# AI Self-Improvement (Instructions + Skills)
## Critical: `.ai/` is the Primary Source for ALL Agents
The `.ai/` folder is the **single source of truth** for all AI agent configurations in this repository. This applies to:
- **CLINE / Roo Code** — synced to `.roo/rules/` and `.roo/skills/`
- **GitHub Copilot** — synced to `.github/copilot-instructions.md`
- **OpenAI Codex / AGENTS.md** — synced to `AGENTS.md` at repo root
- **Claude Code** — synced to `.claude/*.instructions.md` and `.claude/skills/`
**IMPORTANT:** When asked to modify skills, instructions, or perform any AI self-improvement task, you MUST:
1. Locate the source file under `.ai/` (not the agent-specific output)
2. Make changes to the `.ai/` source
3. Run the sync script to propagate changes to all agents
## Path Mapping Reference
When you encounter a path in an agent-specific folder, map it to `.ai/`:
| Agent-Specific Path | Source Path (Edit Here) |
|---------------------|------------------------|
| `.roo/rules/*.md` | `.ai/*.instructions.md` |
| `.roo/skills/<name>/SKILL.md` | `.ai/skills/<name>/SKILL.md` |
| `.github/copilot-instructions.md` | `.ai/instructions.md` (generated) |
| `AGENTS.md` | `.ai/instructions.md` (generated) |
| `.claude/*.instructions.md` | `.ai/*.instructions.md` |
| `.claude/skills/<name>/SKILL.md` | `.ai/skills/<name>/SKILL.md` |
**Example:** If asked to update `.roo/skills/ai-self-improvement/SKILL.md`, you must edit `.ai/skills/ai-self-improvement/SKILL.md` instead.
## Editable instruction files (sources of truth)
You can update your own instruction files under `.ai/`:
- `.ai/instructions.md` — the main system instructions file
- `.ai/*instructions.md` — additional instruction files (auto-included)
- `.ai/*instructions-detail.md` — detailed instruction files (read only when needed)
- `.ai/skills/<name>/SKILL.md` — skill definition files
## Workflow
1. Treat `.ai/` as the **single source of truth** for agent instructions **and skills**.
2. When creating or migrating a skill, create/update it under `.ai/skills/`.
3. Make instruction changes in `.ai/instructions.md` and related `*.instructions.md` / `*.instructions-detail.md` files.
4. Do **not** edit generated outputs directly (they are produced by the sync script):
- `.roo/rules/`
- `.roo/skills/`
- `.github/copilot-instructions.md`
- `AGENTS.md`
- `.claude/`
5. **Test changes before syncing** — verify scripts execute correctly and changes work as expected.
6. After testing, run the sync script to apply to all agents.
## Testing Before Sync
Before running the sync script, always verify your changes work correctly:
- **For script changes**: Execute the modified script and verify output is correct
- **For instruction changes**: Review the markdown renders properly and instructions are clear
- **For skill changes**: Test any bundled tools or scripts included in the skill
**Example**: If you modify a PowerShell script in a skill, run it directly from `.ai/skills/<name>/scripts/` to confirm it works before syncing.
## Activation process
After editing instruction files (or master skills), run from repository root:
```powershell
.\.ai\skills\ai-self-improvement\scripts\Sync-AgentAssets.ps1 AUTO
```
This script synchronizes changes from `.ai/` to all agent-specific folders.
## Single source of truth
**Never embed template content in instructions — reference template files instead.**
Example:
- ✅ "Template maintained in `pr/checklist.template.md`"
- ❌ Pasting template content into instructions
## Bundled scripts
- Sync entrypoint (instructions + skills): `.ai/skills/ai-self-improvement/scripts/Sync-AgentAssets.ps1`
================================================
FILE: .ai/skills/ai-self-improvement/scripts/Sync-AgentAssets.ps1
================================================
# Script: Sync-AgentAssets.ps1
# Location: .ai/skills/ai-self-improvement/scripts/Sync-AgentAssets.ps1
# Description:
# Synchronises AI agent instruction files and skills from master sources under `.ai/`.
# - Instructions: copies `*.instructions.md` from `.ai/` into agent-specific outputs.
# - Skills: mirrors `.ai/skills/*` into agent skill folders (e.g. `.roo/skills/*`).
#
# Options for Mode:
# ALL - update all known agent outputs
# AUTO - update only agents that exist in this repository (default usage)
# Or a specific agent name: CLINE, ROO CODE, GitHub CoPilot, OpenAI Codex, Claude Code
param(
[Parameter(Position = 0)]
[string]$Mode,
[switch]$NoClear
)
# Combine remaining args so Windows PowerShell (-File) invocations like:
# Sync-AgentAssets.ps1 GitHub CoPilot
# work the same as:
# Sync-AgentAssets.ps1 "GitHub CoPilot"
if ($args.Count -gt 0) {
$ModeFromArgs = ($args -join ' ')
if (-not $Mode -or $Mode -eq '') {
$Mode = $ModeFromArgs
}
}
# Allow calling via the old filename (if invoked through a copied/renamed script).
# This only affects displayed script name in prompts/logs.
$scriptName = [System.IO.Path]::GetFileName($MyInvocation.MyCommand.Path)
# Strict mode
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
function Ensure-Directory {
param(
[Parameter(Mandatory = $true)]
[string]$Path
)
if (-not (Test-Path -Path $Path -PathType Container)) {
New-Item -ItemType Directory -Force -Path $Path | Out-Null
}
}
# Function to check if instruction files exist in a directory
function Test-HasInstructionFiles {
param(
[Parameter(Mandatory = $true)]
[string]$Path,
[string]$Filter = '*instructions.md'
)
if (Test-Path $Path -PathType Container) {
$files = @(Get-ChildItem $Path -Filter $Filter -File -ErrorAction SilentlyContinue)
return ($files.Length -gt 0)
}
return $false
}
# Function to pause at the end (unless -NoWait is specified)
function Invoke-Pause {
Write-Host "Pausing for 2 seconds..."
Start-Sleep -Seconds 2
}
function Copy-FileIfDifferent {
param(
[Parameter(Mandatory = $true)]
[string]$SourcePath,
[Parameter(Mandatory = $true)]
[string]$TargetPath
)
$targetDir = Split-Path -Path $TargetPath -Parent
Ensure-Directory -Path $targetDir
if (-not (Test-Path -Path $TargetPath -PathType Leaf)) {
Copy-Item -LiteralPath $SourcePath -Destination $TargetPath -Force
$relative = $TargetPath.Substring($repoRoot.Length + 1)
Write-Host "Created: $relative"
return
}
$srcBytes = [System.IO.File]::ReadAllBytes($SourcePath)
$dstBytes = [System.IO.File]::ReadAllBytes($TargetPath)
if ($srcBytes.Length -eq $dstBytes.Length) {
$same = $true
for ($i = 0; $i -lt $srcBytes.Length; $i++) {
if ($srcBytes[$i] -ne $dstBytes[$i]) { $same = $false; break }
}
if ($same) {
$relative = $TargetPath.Substring($repoRoot.Length + 1)
Write-Host "Up-to-date: $relative"
return
}
}
Copy-Item -LiteralPath $SourcePath -Destination $TargetPath -Force
$relative = $TargetPath.Substring($repoRoot.Length + 1)
Write-Host "Updated: $relative"
}
function Get-TextAuto {
param(
[Parameter(Mandatory = $true)]
[string]$Path
)
# .NET StreamReader detects BOM for UTF-8/UTF-16/UTF-32 automatically.
$sr = New-Object System.IO.StreamReader($Path, $true)
try {
return $sr.ReadToEnd()
}
finally {
$sr.Dispose()
}
}
function Write-Utf8NoBom {
param(
[Parameter(Mandatory = $true)]
[string]$Path,
[Parameter(Mandatory = $true)]
[string]$Content
)
$dir = Split-Path -Path $Path -Parent
Ensure-Directory -Path $dir
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
[System.IO.File]::WriteAllText($Path, $Content, $utf8NoBom)
}
function Assert-InstructionSync {
param(
[Parameter(Mandatory = $true)]
[string]$SourceDirectory,
[Parameter(Mandatory = $true)]
[string]$TargetDirectory,
[Parameter(Mandatory = $true)]
[System.IO.FileSystemInfo[]]$SourceFiles
)
$srcDir = Join-Path $repoRoot $SourceDirectory
$dstDir = Join-Path $repoRoot $TargetDirectory
foreach ($sourceFile in $SourceFiles) {
$srcPath = Join-Path $srcDir $sourceFile.Name
$dstPath = Join-Path $dstDir $sourceFile.Name
if (-not (Test-Path $dstPath -PathType Leaf)) {
throw "Binary comparison failed. Destination file missing: $dstPath"
}
$srcBytes = [System.IO.File]::ReadAllBytes($srcPath)
$dstBytes = [System.IO.File]::ReadAllBytes($dstPath)
if ($srcBytes.Length -ne $dstBytes.Length) {
throw "Binary comparison failed. Source and target size mismatch in binary: Source: $srcPath Target: $dstPath"
}
for ($i = 0; $i -lt $srcBytes.Length; $i++) {
if ($srcBytes[$i] -ne $dstBytes[$i]) {
throw "Binary comparison failed. Source and target content mismatch in binary: Source: $srcPath Target: $dstPath"
}
}
}
}
# Function to update agents that use multiple separate instruction files
function Update-MultipleFileAgent {
param(
[Parameter(Mandatory = $true)]
[string]$AgentName,
[Parameter(Mandatory = $true)]
[string]$TargetDirectory,
[Parameter(Mandatory = $true)]
[System.IO.FileSystemInfo[]]$SourceFiles,
[Parameter(Mandatory = $true)]
[string]$RepoRoot
)
Write-Host "`r`n--- Updating $AgentName Instructions ---"
$targetDir = Join-Path $RepoRoot $TargetDirectory
foreach ($sourceFile in $SourceFiles) {
$targetFile = Join-Path $targetDir $sourceFile.Name
Copy-FileIfDifferent -SourcePath $sourceFile.FullName -TargetPath $targetFile
}
Assert-InstructionSync -SourceDirectory ".ai" -TargetDirectory $TargetDirectory -SourceFiles $SourceFiles
}
# Function to update agents that use a single combined instruction file
function Update-SingleFileAgent {
param(
[Parameter(Mandatory = $true)]
[string]$AgentName,
[Parameter(Mandatory = $true)]
[string]$TargetFilePath,
[Parameter(Mandatory = $true)]
[System.IO.FileSystemInfo[]]$SourceFiles,
[Parameter(Mandatory = $true)]
[string]$RepoRoot
)
Write-Host "`r`n--- Updating $AgentName Instructions ---"
$targetFile = Join-Path $RepoRoot $TargetFilePath
$relativeTarget = $targetFile.Substring($repoRoot.Length + 1)
$allInstructionsContent = New-Object System.Text.StringBuilder
$firstFile = $true
foreach ($sourceFile in $SourceFiles) {
$sourceContent = Get-TextAuto -Path $sourceFile.FullName
if ([string]::IsNullOrWhiteSpace($sourceContent)) {
Write-Warning "Skipping empty file: $($sourceFile.Name)"
continue
}
if (-not $firstFile) {
[void]$allInstructionsContent.AppendLine("")
}
[void]$allInstructionsContent.AppendLine("==== START OF INSTRUCTIONS FROM: $($sourceFile.Name) ====")
[void]$allInstructionsContent.AppendLine("")
[void]$allInstructionsContent.AppendLine("# Instructions from: $($sourceFile.Name)")
[void]$allInstructionsContent.AppendLine("")
[void]$allInstructionsContent.AppendLine($sourceContent.Trim())
[void]$allInstructionsContent.AppendLine("")
[void]$allInstructionsContent.AppendLine("==== END OF INSTRUCTIONS FROM: $($sourceFile.Name) ====")
$firstFile = $false
}
$finalContent = $allInstructionsContent.ToString()
$existing = if (Test-Path -Path $targetFile -PathType Leaf) { Get-TextAuto -Path $targetFile } else { $null }
if ($null -ne $existing -and $existing -eq $finalContent) {
Write-Host "Up-to-date: $relativeTarget"
return
}
Write-Utf8NoBom -Path $targetFile -Content $finalContent
Write-Host "Updated: $relativeTarget"
}
function Invoke-RoboCopyMirror {
param(
[Parameter(Mandatory = $true)]
[string]$SourceDirectory,
[Parameter(Mandatory = $true)]
[string]$DestinationDirectory,
[Parameter(Mandatory = $true)]
[string]$Label
)
if (-not (Test-Path $SourceDirectory -PathType Container)) {
Write-Host "No skills folder found at: $SourceDirectory"
return
}
Ensure-Directory -Path $DestinationDirectory
Write-Host "`r`n--- Mirroring skills to $Label ---"
Write-Host "Source: $SourceDirectory"
Write-Host "Destination: $DestinationDirectory"
# /MIR = mirror (copy + delete removed)
# /FFT = tolerate 2s timestamp granularity
# /R:1 /W:1 = retry quickly
# /NFL/NDL = no file/dir listing (keep output compact)
# /NJH/NJS = no job header/summary
# /NP = no progress
# /XD = exclude version control/build dirs
$excludedDirs = @('.git', '.vs', 'bin', 'obj')
$args = @(
$SourceDirectory,
$DestinationDirectory,
'/MIR',
'/FFT',
'/R:1',
'/W:1',
'/NFL',
'/NDL',
'/NJH',
'/NJS',
'/NP'
)
foreach ($d in $excludedDirs) {
$args += '/XD'
$args += $d
}
$exe = 'robocopy'
# Do not echo the full robocopy command; it is noisy and can wrap in some terminals.
Write-Host "robocopy <source> <destination> /MIR /NFL /NDL /NJH /NJS /NP ..."
& $exe @args | Out-Null
$exitCode = $LASTEXITCODE
# Robocopy uses bitmask exit codes.
# 0-7 are success with various flags; >= 8 indicates failure.
if ($exitCode -ge 8) {
throw "Robocopy failed with exit code $exitCode. Command: $cmd"
}
# IMPORTANT: robocopy returns 1+ for successful copies.
# Ensure PowerShell script does not propagate a non-zero exit code for success cases.
$global:LASTEXITCODE = 0
Write-Host "Mirrored skills to $Label (robocopy exit code $exitCode)."
}
function Sync-SkillsToRoo {
param(
[Parameter(Mandatory = $true)]
[string]$RepoRoot
)
$srcSkillsRoot = Join-Path $RepoRoot ".ai\skills"
$rooSkillsRoot = Join-Path $RepoRoot ".roo\skills"
Invoke-RoboCopyMirror -SourceDirectory $srcSkillsRoot -DestinationDirectory $rooSkillsRoot -Label "Roo (.roo\\skills)"
}
function Sync-SkillsToGitHub {
param(
[Parameter(Mandatory = $true)]
[string]$RepoRoot
)
$srcSkillsRoot = Join-Path $RepoRoot ".ai\skills"
$githubSkillsRoot = Join-Path $RepoRoot ".github\skills"
Invoke-RoboCopyMirror -SourceDirectory $srcSkillsRoot -DestinationDirectory $githubSkillsRoot -Label "GitHub (.github\\skills)"
}
function Sync-SkillsToClaude {
param(
[Parameter(Mandatory = $true)]
[string]$RepoRoot
)
$srcSkillsRoot = Join-Path $RepoRoot ".ai\skills"
$claudeSkillsRoot = Join-Path $RepoRoot ".claude\skills"
Invoke-RoboCopyMirror -SourceDirectory $srcSkillsRoot -DestinationDirectory $claudeSkillsRoot -Label "Claude Code (.claude\\skills)"
}
# --- Main Script ---
if (-not $NoClear) {
Clear-Host
}
# We are located under `.ai/skills/<skill>/tools`. Find repo root by going up 4 levels.
$scriptDir = $PSScriptRoot
$repoRoot = (Join-Path -Path $scriptDir -ChildPath "..\..\..\.." | Resolve-Path).Path
# `.ai` folder path
$aiDir = Join-Path $repoRoot ".ai"
# Discover source files matching *instructions.md in the .ai folder
[System.IO.FileSystemInfo[]]$sourceInstructionFiles = Get-ChildItem -Path $aiDir -Filter "*instructions.md" -File | Sort-Object Name
if ($null -eq $sourceInstructionFiles -or $sourceInstructionFiles.Length -eq 0) {
Write-Warning "No '*instructions.md' files found in '$aiDir'. Nothing to process."
exit 0
}
Write-Host "Found the following source instruction files in '$aiDir':"
$sourceInstructionFiles | ForEach-Object { Write-Host "- $($_.Name)" }
# Mode parameter handling: if 'ALL' or 'AUTO', skip interactive prompt
if ($Mode -eq 'ALL') {
Write-Host "Selected: ALL (parameter mode)"
$updateCline = $true
$updateCopilot = $true
$updateRooCode = $true
$updateCodex = $true
$updateClaude = $true
}
elseif ($Mode -eq 'AUTO') {
Write-Host "Selected: AUTO (parameter mode)"
# Determine available agents based on instruction files
$updateCline = Test-HasInstructionFiles -Path (Join-Path $repoRoot '.clinerules')
$updateRooCode = Test-HasInstructionFiles -Path (Join-Path $repoRoot '.roo\rules')
$updateCopilot = Test-Path (Join-Path $repoRoot '.github\copilot-instructions.md') -PathType Leaf
$updateCodex = Test-Path (Join-Path $repoRoot 'AGENTS.md') -PathType Leaf
$updateClaude = Test-HasInstructionFiles -Path (Join-Path $repoRoot '.claude')
Write-Host "Agents to update based on available instruction files:"
if ($updateCline) { Write-Host "- CLINE" }
if ($updateRooCode) { Write-Host "- ROO CODE" }
if ($updateCopilot) { Write-Host "- GitHub CoPilot" }
if ($updateCodex) { Write-Host "- OpenAI Codex" }
if ($updateClaude) { Write-Host "- Claude Code" }
}
elseif ($Mode -and $Mode -ne '') {
# Specific agent mode (e.g., CLINE, "ROO CODE", etc.)
$updateCline = ($Mode -eq 'CLINE')
$updateCopilot = ($Mode -eq 'GitHub CoPilot')
$updateRooCode = ($Mode -eq 'ROO CODE')
$updateCodex = ($Mode -eq 'OpenAI Codex')
$updateClaude = ($Mode -eq 'Claude Code')
Write-Host "Selected: $Mode (parameter mode)"
}
else {
# User prompt for agent selection
# Detect available agents for interactive menu
$hasCline = Test-HasInstructionFiles -Path (Join-Path $repoRoot '.clinerules')
$hasRooCode = Test-HasInstructionFiles -Path (Join-Path $repoRoot '.roo\rules')
$hasCopilot = Test-Path (Join-Path $repoRoot '.github\copilot-instructions.md') -PathType Leaf
$hasCodex = Test-Path (Join-Path $repoRoot 'AGENTS.md') -PathType Leaf
$hasClaude = Test-HasInstructionFiles -Path (Join-Path $repoRoot '.claude')
Write-Host "`r`nDetected AI agents with instruction files:"
if ($hasCline) { Write-Host "- CLINE" }
if ($hasRooCode) { Write-Host "- ROO CODE" }
if ($hasCopilot) { Write-Host "- GitHub CoPilot" }
if ($hasCodex) { Write-Host "- OpenAI Codex" }
if ($hasClaude) { Write-Host "- Claude Code" }
Write-Host ""
Write-Host "=============================================================="
Write-Host "Select Agent Instruction Set to Update"
Write-Host "--------------------------------------------------------------"
Write-Host "1. AUTO - Update only agents with instruction files (default)"
Write-Host "2. ALL - Update instructions for all AI agents"
Write-Host "3. CLINE - Update instructions for CLINE"
Write-Host "4. ROO CODE - Update instructions for ROO CODE"
Write-Host "5. GitHub CoPilot - Update instructions for GitHub CoPilot"
Write-Host "6. OpenAI Codex - Update instructions for OpenAI Codex"
Write-Host "7. Claude Code - Update instructions for Claude Code"
Write-Host "0. Exit"
Write-Host "=============================================================="
$selection = Read-Host "Enter the number of your choice (0-7)"
# Initialize flags
$updateCline = $false
$updateCopilot = $false
$updateRooCode = $false
$updateCodex = $false
$updateClaude = $false
switch ($selection) {
'1' {
$updateCline = Test-HasInstructionFiles -Path (Join-Path $repoRoot '.clinerules')
$updateRooCode = Test-HasInstructionFiles -Path (Join-Path $repoRoot '.roo\rules')
$updateCopilot = Test-Path (Join-Path $repoRoot '.github\copilot-instructions.md') -PathType Leaf
$updateCodex = Test-Path (Join-Path $repoRoot 'AGENTS.md') -PathType Leaf
$updateClaude = Test-HasInstructionFiles -Path (Join-Path $repoRoot '.claude')
Write-Host "Selected: AUTO"
}
'2' {
$updateCline = $true
$updateCopilot = $true
$updateRooCode = $true
$updateCodex = $true
$updateClaude = $true
Write-Host "Selected: ALL"
}
'3' { $updateCline = $true; Write-Host "Selected: CLINE" }
'4' { $updateRooCode = $true; Write-Host "Selected: ROO CODE" }
'5' { $updateCopilot = $true; Write-Host "Selected: GitHub CoPilot" }
'6' { $updateCodex = $true; Write-Host "Selected: OpenAI Codex" }
'7' { $updateClaude = $true; Write-Host "Selected: Claude Code" }
'0' { Write-Host "Operation cancelled by user."; exit 0 }
default { throw "Invalid selection. Exiting." }
}
}
# --- Multiple-File Agent Updates ---
if ($updateCline) {
Update-MultipleFileAgent -AgentName "CLINE" -TargetDirectory ".clinerules" -SourceFiles $sourceInstructionFiles -RepoRoot $repoRoot
}
if ($updateRooCode) {
Update-MultipleFileAgent -AgentName "ROO CODE" -TargetDirectory ".roo\rules" -SourceFiles $sourceInstructionFiles -RepoRoot $repoRoot
}
# --- Single-File Agent Updates ---
if ($updateCopilot) {
$copilotTarget = ".github\copilot-instructions.md"
$githubInstructionsDir = Join-Path $repoRoot ".github\instructions"
if (Test-Path $githubInstructionsDir -PathType Container) {
Write-Host "`r`n--- Updating GitHub CoPilot Instructions (folder-based) ---"
$mainName = "instructions.md"
$mainSource = $sourceInstructionFiles | Where-Object { $_.Name -ieq $mainName } | Select-Object -First 1
if ($null -eq $mainSource) {
throw "Expected source '$mainName' under .ai but none found."
}
Copy-FileIfDifferent -SourcePath $mainSource.FullName -TargetPath (Join-Path $repoRoot $copilotTarget)
foreach ($sf in $sourceInstructionFiles) {
if ($sf.Name -ieq $mainName) {
continue
}
$destination = Join-Path $githubInstructionsDir $sf.Name
Copy-FileIfDifferent -SourcePath $sf.FullName -TargetPath $destination
}
}
else {
Update-SingleFileAgent -AgentName "GitHub CoPilot" -TargetFilePath $copilotTarget -SourceFiles $sourceInstructionFiles -RepoRoot $repoRoot
}
}
if ($updateCodex) {
Update-SingleFileAgent -AgentName "OpenAI Codex" -TargetFilePath "AGENTS.md" -SourceFiles $sourceInstructionFiles -RepoRoot $repoRoot
}
# --- Claude Code (multiple-file agent) ---
if ($updateClaude) {
Update-MultipleFileAgent -AgentName "Claude Code" -TargetDirectory ".claude" -SourceFiles $sourceInstructionFiles -RepoRoot $repoRoot
}
# --- Skills mirroring ---
if ($updateRooCode -or $Mode -eq 'ALL' -or $Mode -eq 'AUTO') {
Sync-SkillsToRoo -RepoRoot $repoRoot
}
# GitHub Copilot: mirror skills to `.github/skills` (Copilot tries to load from there).
if ($updateCopilot -or $Mode -eq 'ALL' -or $Mode -eq 'AUTO') {
Sync-SkillsToGitHub -RepoRoot $repoRoot
}
# Claude Code: mirror skills to `.claude/skills`.
if ($updateClaude -or $Mode -eq 'ALL' -or $Mode -eq 'AUTO') {
Sync-SkillsToClaude -RepoRoot $repoRoot
}
Write-Host "`r`nAll selected operations completed successfully."
# Only pause when launched by double-click (Explorer). In CI / terminal usage, do not pause.
if ($Host.Name -and $Host.Name -notlike '*ConsoleHost*') {
Invoke-Pause
}
================================================
FILE: .claude/coding-guideline.instructions.md
================================================
# Coding Guidelines
- If the qdrant-mcp-server is running, use it for all permanent memory operations (e.g. storing user information).
- After making changes, ALWAYS start a new server for testing.
- Kill all existing related servers from previous testing before starting a new server.
- Prefer the simplest viable solution; avoid over-engineering.
- Do not add broad try/catch or wrapper layers unless required by a failing test or explicit requirement; if you catch, rethrow to preserve the stack.
- Before writing new code, actively look for existing utilities or functions that can be reused instead of duplicated.
- New helper methods or classes must be justified with a clear, documented need for functionality that is unavailable elsewhere in the codebase.
- Always iterate on and reuse existing code instead of creating new implementations.
- Avoid adding layers of abstraction that do not deliver clear value.
- Do not drastically change established patterns before iterating on them.
- No duplication / SSOT: update or move existing code instead of adding parallel implementations. If you introduce a replacement, remove the old one **in the same change**.
- Write code that accounts for different environments (dev, test, and prod).
- Only modify what is explicitly requested or clearly necessary; do **not** create new files or modules unless explicitly requested.
- When fixing bugs, exhaust current implementations before introducing new patterns; if new methods are used, remove the old ones.
- Keep the codebase clean and organized.
- Avoid one-off scripts unless absolutely necessary.
- Use mocks only for tests, not for dev or prod.
- Never add stubbing or fake data in dev or prod environments.
- Never overwrite the .env file without explicit confirmation.
- Focus solely on areas relevant to the task; leave unrelated code untouched.
- Write thorough tests for all major functionality.
- Avoid major changes to the existing architecture unless explicitly instructed.
- Always consider the impact on other methods and areas of the code.
- Prefer to wrap long lines for better readability.
- Preserve existing formatting; limit formatting to lines you changed and match surrounding style. Also remove any unused imports/usings or dead code **introduced by your edits**.
- Stability primitives (repeatability > cleverness): when the repo provides an established way to perform an operation (repo-owned script, documented command snippet, standard PS1 under .ai/Scripts), treat it as the single source of truth. Use it verbatim instead of synthesizing “equivalent” commands. Only deviate when explicitly asked or when the primitive is proven broken in this environment (and then fix the primitive, not invent a parallel path).
- No code file that you **create or modify** may exceed **6000 tokens (~24 KB)** once your changes are applied.
- If your changes alone would push the file past this limit, either trim the change or ask for explicit permission to refactor; do **not** alter unrelated code solely to meet the limit.
- Existing oversized files are left untouched unless the user explicitly requests a refactor.
## Source Control Conventions
Naming conventions for issues, branches, and pull requests.
### Categories
| Category | Use for |
|----------|---------|
| `FEAT` | New features |
| `FIX` | Bug fixes |
| `TECH` | Infrastructure, dependencies, refactoring |
| `DOCS` | Documentation |
### Naming Patterns
| Item | Pattern | Example |
|------------|---------|---------|
| **Issue** | `{CATEGORY}: {Description}` | `FEAT: Download logs to CSV` |
| **Branch** | `{CATEGORY}-{issue#}-{lowercase-dashed-name}` | `FEAT-21-download-logs-to-csv` |
| **PR** | `PR: #{issue#}: {CATEGORY}: {Description}` | `PR: #21: FEAT: Download logs to CSV` |
- Branch names: lowercase, words separated by dashes, non-ASCII replaced with a single dash, derived from issue title but may be shortened.
- PR body must reference the issue with `Closes #{issue#}`.
- Merge via PR only — no direct pushes to `main` or `master`.
- Feature branches are always created from `main` or `master`. Never merge one feature branch into another — only merge `main` if you need to catch up.
- Always create branches from latest origin/main or origin/main, never merge sub-branches, confirm before risky git ops.
- Do not add Co-Authored-By {AI model}/{AI company} lines to commits.
- Never close issues before a release is published with the fix.
Use the following guidelines:
1. Doc Comment Enhancement for IntelliSense
- Replace or augment simple comments with relevant doc comment syntax that is supported by IntelliSense as needed.
- Preserve the original intent and wording of existing comments wherever possible.
2. Code Layout for Clarity
- Place the most important or user-editable sections at the top if logically appropriate.
- Insert headings or separators within the code to clearly delineate where customizations or key logic sections can be adjusted.
3. No Extraneous Code Comments
- Do not include "one-off" or user-directed commentary in the code.
- Confine all clarifications or additional suggestions to explanations outside of the code snippet.
4. Avoid Outdated or Deprecated Methods
- Refrain from introducing or relying on obsolete or deprecated methods and libraries.
- If the current code relies on potentially deprecated approaches, ask for clarification or provide viable, modern alternatives that align with best practices.
5. Testing and Validation
- Suggest running unit tests or simulations on the modified segments to confirm that the changes fix the issue without impacting overall functionality.
- Ensure that any proposed improvements, including doc comment upgrades, integrate seamlessly with the existing codebase.
- After all code modifications, navigate to the affected project directory and build C# then Angular to confirm the application compiles without errors:
cd {PROJECT} && dotnet build {PROJECT}.csproj
cd {PROJECT}/ClientApp && ng build
- Run relevant unit tests if code changes affect core logic.
- If the developer certificate is not trusted, then execute: dotnet dev-certs https --trust
- To launch project use: dotnet watch run --project {PROJECT}/{PROJECT}.csproj --launch-profile "{PROJECT} (NG Build)"
6. Rationale and Explanation
- For every change (including comment conversions), provide a concise explanation detailing how the modification resolves the identified issue while preserving the original design and context.
- Clearly highlight only the modifications made, ensuring that no previously validated progress is altered.
- NOTE: Summarize reasoning for the user, but do NOT expose full chain-of-thought. Keep internal deliberations internal; surface only the concise rationale needed to justify each change.
7. Contextual Analysis
- Use all available context—such as code history, inline documentation, style guidelines—to understand the intended functionality.
- When inspecting an existing file for understanding, prefer reading the whole file in a
single `read_file` call when it comfortably fits in context; switch to targeted slices
only when the file is too large, the tool truncates it, or a specific anchor line is
already known.
- If the role or intent behind a code segment is ambiguous, ask for clarification rather than making assumptions.
8. Targeted, Incremental Changes
- Identify and isolate only the problematic code segments (including places where IntelliSense doc comments can replace simple comments).
- Provide minimal code snippets that address the issue without rewriting larger sections.
- For each suggested code change, explicitly indicate the exact location in the code (e.g., by specifying the function name, class name, line number, or section heading) where the modification should be implemented.
9. Preservation of Context
- Maintain all developer comments, annotations, and workarounds exactly as they appear, transforming them to doc comment format only when it improves IntelliSense support.
- Do not modify or remove any non-code context unless explicitly instructed.
- Avoid introducing new, irrelevant comments in the code.
10. Launching {PROJECT} Correctly:
- Navigate to the {PROJECT} project folder.
- Run the following command to launch the project with live reload and proper debugging configuration:
dotnet watch run --launch-profile "{PROJECT} (NG Build)" --project {PROJECT}/{PROJECT}.csproj
- This command will start the {PROJECT} project on the designated debugging session URL.
- Ensure that any previous {PROJECT} instances are terminated before running this command.
================================================
FILE: .claude/instructions.md
================================================
## Role
Your role is to analyze and improve code by making only localized, targeted changes. You must preserve all validated code, comments, and documented workarounds exactly as they appear. Your suggestions should strictly address only the specific issues identified—such as upgrading simple comments to doc comments for IntelliSense—without altering any surrounding context. Additionally, ensure that no obsolete or deprecated methods are introduced during the improvement process, and do not add extraneous comments that do not directly contribute to the code’s logic. Furthermore, ensure code snippets are clearly structured for readability, placing important or user-editable sections at the top when logical, and using clear separators or headings to highlight customization points.
Wherever beneficial, convert simple comments into recognized documentation comment syntax (e.g., JSDoc for JavaScript, XML comments for C#, JavaDoc for Java) that can be parsed by code intelligence tools like IntelliSense.
Maintain the original meaning of these comments, but structure them in a way that provides maximum benefit for automated tools and refactoring methods.
Apply chain-of-thought reasoning to identify code segments best served by doc comments, analyze the existing context of each comment, and then make precise, incremental modifications that enhance IntelliSense compatibility while preserving existing functionality.
## Output
Wrap any and all code—including regular code snippets, inline code segments, outputs, pseudocode, or any text that represents code—in Markdown code blocks with a language identifier (e.g., ```typescript, ```powershell).
================================================
FILE: .claude/repository-analysis.instructions.md
================================================
# Repository Analysis
## 1. Repository Overview
This document provides a factual reference for the Jocys.com FocusLogger repository, aimed at developers and AI coding agents working on the codebase.
**FocusLogger** is a Windows desktop utility that monitors and logs which process or program takes window focus. It targets users (especially gamers and power users) who experience unexpected focus stealing — where a background process briefly grabs foreground focus, interrupting gameplay or work. The tool logs every focus change with timestamps, process details, window class names, and focus-state flags, allowing users to identify the culprit.
- **Repository:** https://github.com/JocysCom/FocusLogger
- **License:** GNU General Public License v3.0
- **Current version:** 1.2.6
- **Target platform:** Windows 10+ with .NET 8.0
- **Primary audiences:** Gamers, power users, IT support personnel diagnosing focus-stealing issues.
## 2. Top-Level Structure
This section maps every top-level directory and file to help navigate the repository quickly.
| Path | Purpose |
|------|---------|
| `FocusLogger/` | Main application project (WPF, .NET 8.0). Contains all app source code, shared library, and resources. |
| `FocusLogger.Tests/` | MSTest test project. Unit tests for CSV export and UI automation tests. |
| `Documents/` | Release engineering: signing scripts, zip packaging scripts, screenshot tooling, and pre-built release files. |
| `Resources/` | Solution-level shared scripts (currently `ZipFiles.ps1` for checksum-aware zip packaging). |
| `.ai/` | AI agent instructions, coding guidelines, repository analysis, and skills. |
| `JocysCom.FocusLogger.slnx` | Solution file (XML-based `.slnx` format) referencing the two projects. |
| `README.md` | Project overview, download link, system requirements, screenshot. |
| `LICENSE` | GPLv3 license text. |
| `SECURITY.md` | Security vulnerability reporting policy (support@jocys.com). |
| `Settings.XamlStyler` | XamlStyler configuration for consistent XAML formatting. |
| `Solution_Cleanup.ps1` | PowerShell script for cleaning build artifacts. |
## 3. Technology Stack & Key Dependencies
This section lists verified technologies and versions drawn from project files.
| Technology | Version / Detail | Evidence |
|------------|-----------------|----------|
| .NET | 8.0 (`net8.0-windows`) | `JocysCom.FocusLogger.csproj` TargetFramework |
| C# | Implicit (SDK default for .NET 8) | SDK-style project |
| WPF | `<UseWPF>true</UseWPF>` | csproj |
| Windows Forms interop | `<UseWindowsForms>true</UseWindowsForms>` | csproj — used for P/Invoke helpers and DPI awareness |
| MSTest | v3.x (`MSTest.TestFramework 3.*`, `MSTest.TestAdapter 3.*`) | Test csproj PackageReference |
| Microsoft.NET.Test.Sdk | 17.x | Test csproj PackageReference |
| Windows API (user32.dll) | P/Invoke | `NativeMethods.cs` |
| PowerShell | Scripts for build, sign, zip, cleanup | `Documents/`, `Resources/`, root |
| XamlStyler | Config present | `Settings.XamlStyler` |
**No NuGet package dependencies** in the main application project — all functionality comes from .NET SDK and the embedded `JocysCom.ClassLibrary`.
## 4. Architecture & Runtime Model
This section describes how the application is structured and how it operates at runtime.
FocusLogger is a **single-executable WPF desktop application** that polls Windows API functions to detect focus changes. It does not persist log data between sessions (in-memory only) but provides CSV export for offline analysis.
### Architectural layers
```mermaid
graph TD
subgraph UI["UI Layer (WPF)"]
App["App.xaml.cs<br/>Entry point, DPI aware"]
MW["MainWindow.xaml.cs<br/>Main window frame"]
DLC["DataListControl.xaml.cs<br/>Core logging UI + logic"]
end
subgraph Core["Core Logic"]
DI["DataItem.cs<br/>Log entry model"]
DIT["DataItemType.cs<br/>Entry type enum"]
NM["NativeMethods.cs<br/>P/Invoke declarations"]
CSV["CSV Export<br/>BuildCsvContent, CsvEscape"]
end
subgraph Shared["JocysCom.ClassLibrary (embedded)"]
Config["Configuration<br/>SettingsData, SettingsItem, AssemblyInfo"]
CompModel["ComponentModel<br/>SortableBindingList, BindingListInvoked"]
Controls["Controls<br/>ControlsHelper, ItemFormattingConverter,<br/>InfoControl, MessageBoxWindow"]
Other["Collections, IO, Text, Runtime, Data"]
end
subgraph WinAPI["Windows OS"]
User32["user32.dll"]
end
App --> MW --> DLC
DLC --> DI
DLC --> NM
DLC --> CSV
NM --> User32
DI --> Config
DLC --> CompModel
DLC --> Controls
MW --> Controls
```
### Key architectural decisions
- **Polling via timer:** A `System.Timers.Timer` with 1ms interval (non-auto-reset) continuously polls `GetActiveWindow()` and `GetForegroundWindow()`. Duplicate events are suppressed via `DataItem.IsSame()`.
- **Thread safety:** Timer fires on a thread-pool thread; UI updates are marshalled via `ControlsHelper.BeginInvoke()`. A `lock(AddLock)` synchronizes the polling logic.
- **Embedded shared library:** `JocysCom.ClassLibrary` files are included directly in `FocusLogger/JocysCom/` rather than as a compiled DLL or NuGet package.
- **No MVVM framework:** Code-behind pattern with data binding. `DataListControl.xaml.cs` contains both view-model-like logic and model interaction.
## 5. Project Inventory
This section lists each project in the solution with its key metadata.
### 5.1 JocysCom.FocusLogger (main application)
| Property | Value |
|----------|-------|
| Path | `FocusLogger/JocysCom.FocusLogger.csproj` |
| Output type | `WinExe` |
| Target framework | `net8.0-windows` |
| Assembly name | `JocysCom.FocusLogger` |
| Description | Find out which process or program is taking the window focus. In game, mouse and keyboard could temporarily stop responding if another program takes the focus. This tool could help diagnose which program is stealing the focus. |
| Version | 1.2.6 |
| NuGet dependencies | None |
| Embedded resources | `Resources/BuildDate.txt` (auto-generated), `Resources/AiAnalysisPrompt.md` |
**Source structure:**
| Directory | Contents |
|-----------|----------|
| `FocusLogger/` (root) | `App.xaml(.cs)`, `MainWindow.xaml(.cs)`, `AssemblyInfo.cs`, `App.ico` |
| `FocusLogger/Common/` | `DataItem.cs`, `DataItemType.cs`, `NativeMethods.cs` |
| `FocusLogger/Controls/` | `DataListControl.xaml(.cs)` — core logging control |
| `FocusLogger/JocysCom/` | Embedded `JocysCom.ClassLibrary` (~30 files across Collections, Common, ComponentModel, Configuration, Controls, Data, IO, Runtime, Text) |
| `FocusLogger/Resources/` | `AiAnalysisPrompt.md`, `BuildDate.txt`, `Icons/` (SVG sources, XAML icons, conversion scripts) |
| `FocusLogger/Properties/` | Publish profiles |
### 5.2 JocysCom.FocusLogger.Tests (test project)
| Property | Value |
|----------|-------|
| Path | `FocusLogger.Tests/JocysCom.FocusLogger.Tests.csproj` |
| Target framework | `net8.0-windows` |
| Test framework | MSTest v3.x |
| Project reference | `FocusLogger/JocysCom.FocusLogger.csproj` |
**Test files:**
| File | Purpose |
|------|---------|
| `CsvExportTests.cs` | Unit tests for `CsvEscape` and `BuildCsvContent` methods |
| `UIAutomationTests.cs` | UI automation tests using `System.Windows.Automation` — launches the built app and interacts with controls by AutomationId |
Run tests with: `dotnet test FocusLogger.Tests/JocysCom.FocusLogger.Tests.csproj`
## 6. Dependency & Data Flow
This section explains how the projects and components relate to each other and how data moves through the system.
### Project dependency graph
```mermaid
graph LR
Tests["FocusLogger.Tests<br/>(MSTest v3)"] -->|ProjectReference| App["JocysCom.FocusLogger<br/>(WPF App)"]
App -->|embedded source| Shared["JocysCom.ClassLibrary<br/>(in FocusLogger/JocysCom/)"]
App -->|P/Invoke| WinAPI["user32.dll"]
```
### Runtime data flow
1. `System.Timers.Timer` fires (1ms interval, non-auto-reset).
2. `DataListControl.UpdateInfo()` acquires `AddLock`.
3. Calls `NativeMethods.GetActiveWindow()` and `NativeMethods.GetForegroundWindow()`.
4. For each handle, `GetItemFromHandle()` creates a `DataItem` with timestamp, focus flags (mouse/keyboard/caret), window title, and window class name.
5. `IsSame()` checks if the event differs from the previous one; if not, it is skipped.
6. `UpdateFromProcess()` enriches the `DataItem` with process name and path (with error handling for restricted processes).
7. The item is inserted at position 0 of `SortableBindingList<DataItem>` via `ControlsHelper.BeginInvoke()` (UI thread dispatch).
8. The WPF `DataGrid` updates via data binding. `ItemFormattingConverter` translates boolean flags to icons.
### CSV export flow
1. User clicks "Save CSV" button.
2. `SaveFileDialog` prompts for file location.
3. `BuildCsvContent()` iterates all `DataItem` entries, writing CSV with headers: Date, PID, Process Name, Active, Mouse, Keyboard, Caret, Window Title, Window Class, Path.
4. File is written as UTF-8.
5. "Explore" button opens the saved file location in Explorer.
6. "AI Prompt Example" button shows the embedded `AiAnalysisPrompt.md` in a `MessageBoxWindow` for users to copy and paste into an AI assistant along with their CSV.
## 7. Build, Test, CI/CD & Operational Workflows
This section documents how the project is built, tested, and released based on repository evidence.
### Build
```bash
dotnet build JocysCom.FocusLogger.slnx
```
- **Pre-build event:** Generates `Resources/BuildDate.txt` with the current ISO 8601 timestamp via PowerShell.
- **Output:** Single `JocysCom.FocusLogger.exe` in `bin/{Configuration}/net8.0-windows/`.
- **Debug configuration:** Embedded PDB symbols (`DebugType: embedded`).
### Test
```bash
dotnet test FocusLogger.Tests/JocysCom.FocusLogger.Tests.csproj
```
- **Framework:** MSTest v3.x with Microsoft.NET.Test.Sdk 17.x.
- **Unit tests:** `CsvExportTests` — validates CSV escaping and content generation.
- **UI automation tests:** `UIAutomationTests` — launches the built application and interacts via `System.Windows.Automation`. Requires a prior build of the main project.
### Release / packaging scripts
| Script | Purpose |
|--------|---------|
| `Documents/App_1_Sign.ps1` | Code-signs the application executable. |
| `Documents/App_2_Zip.ps1` | Packages the signed executable into a release ZIP. |
| `Resources/ZipFiles.ps1` | Shared utility for checksum-aware ZIP creation (compares source/dest checksums before rebuilding). |
| `Documents/Take_Screenshot.ps1` | Captures application screenshot for documentation. |
| `Documents/Take_Screenshot.ps1.cs` | C# helper compiled by the screenshot script. |
| `Solution_Cleanup.ps1` | Cleans `bin/`, `obj/`, and other build artifacts. |
### Icon workflow
SVG icon sources are stored in `FocusLogger/Resources/Icons/Icons_Default/`. The script `Convert_SVG_to_XAML.ps1` converts them to XAML resource dictionaries (`Icons_Default.xaml`).
### CI/CD
No CI/CD workflow files were found under `.github/workflows/`. Builds and releases appear to be performed locally.
## 8. Documentation Map
This section identifies where documentation lives in the repository.
| Location | Audience | Content |
|----------|----------|---------|
| `README.md` | End users, contributors | Project overview, download link, system requirements, screenshot |
| `SECURITY.md` | Security researchers | Vulnerability reporting policy |
| `LICENSE` | All | GPLv3 full text |
| `.ai/ReadMe.md` | AI agents, developers | Explains the purpose of each file in the `.ai/` directory and how custom instructions work for Copilot, CLINE, and Codex |
| `.ai/instructions.md` | AI agents | Role definition and output formatting rules for AI-assisted edits |
| `.ai/coding-guideline.instructions.md` | AI agents | Detailed coding guidelines, source control conventions (branch/PR naming), testing workflow, and constraints for AI agents |
| `.ai/repository-analysis.instructions.md` | AI agents, developers | This file — comprehensive repository reference |
| `.ai/skills/` | AI agents | Skill definitions (e.g., `ai-self-improvement`) for agent-assisted workflows |
| `FocusLogger/Resources/AiAnalysisPrompt.md` | End users | Prompt template for users to paste into AI assistants alongside exported CSV logs |
| `Documents/Images/` | README, users | Application screenshot |
| `Settings.XamlStyler` | Developers | XamlStyler formatting configuration |
### Documentation taxonomy
```mermaid
graph TD
subgraph EndUsers["End-User Documentation"]
README["README.md<br/>Overview, download, requirements"]
SECURITY["SECURITY.md<br/>Vulnerability reporting"]
LICENSE["LICENSE<br/>GPLv3"]
AiPrompt["FocusLogger/Resources/<br/>AiAnalysisPrompt.md<br/>Prompt template for CSV analysis"]
Screenshot["Documents/Images/<br/>Application screenshot"]
end
subgraph AIAgents["AI Agent Instructions"]
AiReadMe[".ai/ReadMe.md<br/>Directory guide"]
AiInstr[".ai/instructions.md<br/>Role & output rules"]
AiCoding[".ai/coding-guideline<br/>.instructions.md<br/>Coding & SCM conventions"]
AiRepo[".ai/repository-analysis<br/>.instructions.md<br/>This file"]
AiSkills[".ai/skills/<br/>Agent skill definitions"]
end
subgraph DevConfig["Developer Configuration"]
XamlStyler["Settings.XamlStyler<br/>XAML formatting"]
end
```
## 9. AI-Agent-Relevant Conventions and Constraints
This section captures rules and patterns that materially affect automated edits. The full set of coding and source-control conventions is defined in `.ai/coding-guideline.instructions.md`; the highlights below are the items most likely to cause mistakes if overlooked.
1. **Coding style:** Follow Microsoft C# conventions. PascalCase for public members, camelCase for locals. Some private fields use `_PascalCase` (e.g., `_Date`). Preserve existing naming patterns in each file. Prefer the simplest viable solution; avoid over-engineering.
2. **Doc comments:** Per `.ai/instructions.md`, convert simple comments to XML documentation comments where beneficial for IntelliSense. Do not alter surrounding code when doing so.
3. **Shared library files (`FocusLogger/JocysCom/`):** These are embedded from a shared `JocysCom.ClassLibrary`. Exercise caution when editing — changes here may diverge from the upstream library.
4. **XAML formatting:** The repository uses XamlStyler (see `Settings.XamlStyler`). XAML edits should conform to the configured style.
5. **No NuGet packages in the main app:** All dependencies are framework-provided or embedded source. Do not introduce NuGet package dependencies without explicit approval.
6. **Test project uses MSTest v3:** New tests should follow MSTest v3 patterns (`[TestClass]`, `[TestMethod]`, `Assert.*`).
7. **UI automation tests depend on a built executable:** `UIAutomationTests` locate the app at a relative path from the test output. Building the main project before running these tests is required.
8. **Pre-build event:** The csproj generates `Resources/BuildDate.txt` via PowerShell. This file should not be manually edited or committed.
9. **Solution format:** Uses `.slnx` (XML-based solution format), not the older `.sln` text format.
10. **No CI/CD pipelines:** All build and release steps are manual/local. Scripts in `Documents/` handle signing and packaging.
11. **File size limit:** No code file that is created or modified may exceed 6000 tokens (~24 KB).
12. **Source control conventions:** Issues use `{CATEGORY}: {Description}` (FEAT, FIX, TECH, DOCS). Branches use `{CATEGORY}-{issue#}-{lowercase-dashed-name}`. PRs use `PR: #{issue#}: {CATEGORY}: {Description}` and must reference the issue with `Closes #{issue#}`. Merge via PR only — no direct pushes to `main`.
13. **No Co-Authored-By lines:** Do not add `Co-Authored-By` AI model/company lines to commits.
14. **Issue closure:** Never close issues before a release is published with the fix.
15. **No duplication / SSOT:** Update or move existing code instead of adding parallel implementations. If introducing a replacement, remove the old one in the same change.
16. **Stability primitives:** When the repo provides an established script or command, use it verbatim. Only deviate when it is proven broken, and then fix the primitive rather than inventing a parallel path.
================================================
FILE: .claude/skills/ai-self-improvement/SKILL.md
================================================
---
name: ai-self-improvement
description: Update, create, improve, and synchronise this repository's AI agent instructions and related assets (including skills). Use when the user asks to create or edit a skill/SKILL.md, modify the agent's own instructions/processes, restructure instruction governance, migrate instruction content into skills, or run/adjust the sync pipeline that publishes `.ai/` sources into agent-specific folders. Load this skill before writing any SKILL.md, .instructions.md, or touching any skills/ folder (.ai/, .claude/, .roo/, .github/). It tells you the correct location (.ai/) and the sync step, so files end up in the right place.
---
# AI Self-Improvement (Instructions + Skills)
## Critical: `.ai/` is the Primary Source for ALL Agents
The `.ai/` folder is the **single source of truth** for all AI agent configurations in this repository. This applies to:
- **CLINE / Roo Code** — synced to `.roo/rules/` and `.roo/skills/`
- **GitHub Copilot** — synced to `.github/copilot-instructions.md`
- **OpenAI Codex / AGENTS.md** — synced to `AGENTS.md` at repo root
- **Claude Code** — synced to `.claude/*.instructions.md` and `.claude/skills/`
**IMPORTANT:** When asked to modify skills, instructions, or perform any AI self-improvement task, you MUST:
1. Locate the source file under `.ai/` (not the agent-specific output)
2. Make changes to the `.ai/` source
3. Run the sync script to propagate changes to all agents
## Path Mapping Reference
When you encounter a path in an agent-specific folder, map it to `.ai/`:
| Agent-Specific Path | Source Path (Edit Here) |
|---------------------|------------------------|
| `.roo/rules/*.md` | `.ai/*.instructions.md` |
| `.roo/skills/<name>/SKILL.md` | `.ai/skills/<name>/SKILL.md` |
| `.github/copilot-instructions.md` | `.ai/instructions.md` (generated) |
| `AGENTS.md` | `.ai/instructions.md` (generated) |
| `.claude/*.instructions.md` | `.ai/*.instructions.md` |
| `.claude/skills/<name>/SKILL.md` | `.ai/skills/<name>/SKILL.md` |
**Example:** If asked to update `.roo/skills/ai-self-improvement/SKILL.md`, you must edit `.ai/skills/ai-self-improvement/SKILL.md` instead.
## Editable instruction files (sources of truth)
You can update your own instruction files under `.ai/`:
- `.ai/instructions.md` — the main system instructions file
- `.ai/*instructions.md` — additional instruction files (auto-included)
- `.ai/*instructions-detail.md` — detailed instruction files (read only when needed)
- `.ai/skills/<name>/SKILL.md` — skill definition files
## Workflow
1. Treat `.ai/` as the **single source of truth** for agent instructions **and skills**.
2. When creating or migrating a skill, create/update it under `.ai/skills/`.
3. Make instruction changes in `.ai/instructions.md` and related `*.instructions.md` / `*.instructions-detail.md` files.
4. Do **not** edit generated outputs directly (they are produced by the sync script):
- `.roo/rules/`
- `.roo/skills/`
- `.github/copilot-instructions.md`
- `AGENTS.md`
- `.claude/`
5. **Test changes before syncing** — verify scripts execute correctly and changes work as expected.
6. After testing, run the sync script to apply to all agents.
## Testing Before Sync
Before running the sync script, always verify your changes work correctly:
- **For script changes**: Execute the modified script and verify output is correct
- **For instruction changes**: Review the markdown renders properly and instructions are clear
- **For skill changes**: Test any bundled tools or scripts included in the skill
**Example**: If you modify a PowerShell script in a skill, run it directly from `.ai/skills/<name>/scripts/` to confirm it works before syncing.
## Activation process
After editing instruction files (or master skills), run from repository root:
```powershell
.\.ai\skills\ai-self-improvement\scripts\Sync-AgentAssets.ps1 AUTO
```
This script synchronizes changes from `.ai/` to all agent-specific folders.
## Single source of truth
**Never embed template content in instructions — reference template files instead.**
Example:
- ✅ "Template maintained in `pr/checklist.template.md`"
- ❌ Pasting template content into instructions
## Bundled scripts
- Sync entrypoint (instructions + skills): `.ai/skills/ai-self-improvement/scripts/Sync-AgentAssets.ps1`
================================================
FILE: .claude/skills/ai-self-improvement/scripts/Sync-AgentAssets.ps1
================================================
# Script: Sync-AgentAssets.ps1
# Location: .ai/skills/ai-self-improvement/scripts/Sync-AgentAssets.ps1
# Description:
# Synchronises AI agent instruction files and skills from master sources under `.ai/`.
# - Instructions: copies `*.instructions.md` from `.ai/` into agent-specific outputs.
# - Skills: mirrors `.ai/skills/*` into agent skill folders (e.g. `.roo/skills/*`).
#
# Options for Mode:
# ALL - update all known agent outputs
# AUTO - update only agents that exist in this repository (default usage)
# Or a specific agent name: CLINE, ROO CODE, GitHub CoPilot, OpenAI Codex, Claude Code
param(
[Parameter(Position = 0)]
[string]$Mode,
[switch]$NoClear
)
# Combine remaining args so Windows PowerShell (-File) invocations like:
# Sync-AgentAssets.ps1 GitHub CoPilot
# work the same as:
# Sync-AgentAssets.ps1 "GitHub CoPilot"
if ($args.Count -gt 0) {
$ModeFromArgs = ($args -join ' ')
if (-not $Mode -or $Mode -eq '') {
$Mode = $ModeFromArgs
}
}
# Allow calling via the old filename (if invoked through a copied/renamed script).
# This only affects displayed script name in prompts/logs.
$scriptName = [System.IO.Path]::GetFileName($MyInvocation.MyCommand.Path)
# Strict mode
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
function Ensure-Directory {
param(
[Parameter(Mandatory = $true)]
[string]$Path
)
if (-not (Test-Path -Path $Path -PathType Container)) {
New-Item -ItemType Directory -Force -Path $Path | Out-Null
}
}
# Function to check if instruction files exist in a directory
function Test-HasInstructionFiles {
param(
[Parameter(Mandatory = $true)]
[string]$Path,
[string]$Filter = '*instructions.md'
)
if (Test-Path $Path -PathType Container) {
$files = @(Get-ChildItem $Path -Filter $Filter -File -ErrorAction SilentlyContinue)
return ($files.Length -gt 0)
}
return $false
}
# Function to pause at the end (unless -NoWait is specified)
function Invoke-Pause {
Write-Host "Pausing for 2 seconds..."
Start-Sleep -Seconds 2
}
function Copy-FileIfDifferent {
param(
[Parameter(Mandatory = $true)]
[string]$SourcePath,
[Parameter(Mandatory = $true)]
[string]$TargetPath
)
$targetDir = Split-Path -Path $TargetPath -Parent
Ensure-Directory -Path $targetDir
if (-not (Test-Path -Path $TargetPath -PathType Leaf)) {
Copy-Item -LiteralPath $SourcePath -Destination $TargetPath -Force
$relative = $TargetPath.Substring($repoRoot.Length + 1)
Write-Host "Created: $relative"
return
}
$srcBytes = [System.IO.File]::ReadAllBytes($SourcePath)
$dstBytes = [System.IO.File]::ReadAllBytes($TargetPath)
if ($srcBytes.Length -eq $dstBytes.Length) {
$same = $true
for ($i = 0; $i -lt $srcBytes.Length; $i++) {
if ($srcBytes[$i] -ne $dstBytes[$i]) { $same = $false; break }
}
if ($same) {
$relative = $TargetPath.Substring($repoRoot.Length + 1)
Write-Host "Up-to-date: $relative"
return
}
}
Copy-Item -LiteralPath $SourcePath -Destination $TargetPath -Force
$relative = $TargetPath.Substring($repoRoot.Length + 1)
Write-Host "Updated: $relative"
}
function Get-TextAuto {
param(
[Parameter(Mandatory = $true)]
[string]$Path
)
# .NET StreamReader detects BOM for UTF-8/UTF-16/UTF-32 automatically.
$sr = New-Object System.IO.StreamReader($Path, $true)
try {
return $sr.ReadToEnd()
}
finally {
$sr.Dispose()
}
}
function Write-Utf8NoBom {
param(
[Parameter(Mandatory = $true)]
[string]$Path,
[Parameter(Mandatory = $true)]
[string]$Content
)
$dir = Split-Path -Path $Path -Parent
Ensure-Directory -Path $dir
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
[System.IO.File]::WriteAllText($Path, $Content, $utf8NoBom)
}
function Assert-InstructionSync {
param(
[Parameter(Mandatory = $true)]
[string]$SourceDirectory,
[Parameter(Mandatory = $true)]
[string]$TargetDirectory,
[Parameter(Mandatory = $true)]
[System.IO.FileSystemInfo[]]$SourceFiles
)
$srcDir = Join-Path $repoRoot $SourceDirectory
$dstDir = Join-Path $repoRoot $TargetDirectory
foreach ($sourceFile in $SourceFiles) {
$srcPath = Join-Path $srcDir $sourceFile.Name
$dstPath = Join-Path $dstDir $sourceFile.Name
if (-not (Test-Path $dstPath -PathType Leaf)) {
throw "Binary comparison failed. Destination file missing: $dstPath"
}
$srcBytes = [System.IO.File]::ReadAllBytes($srcPath)
$dstBytes = [System.IO.File]::ReadAllBytes($dstPath)
if ($srcBytes.Length -ne $dstBytes.Length) {
throw "Binary comparison failed. Source and target size mismatch in binary: Source: $srcPath Target: $dstPath"
}
for ($i = 0; $i -lt $srcBytes.Length; $i++) {
if ($srcBytes[$i] -ne $dstBytes[$i]) {
throw "Binary comparison failed. Source and target content mismatch in binary: Source: $srcPath Target: $dstPath"
}
}
}
}
# Function to update agents that use multiple separate instruction files
function Update-MultipleFileAgent {
param(
[Parameter(Mandatory = $true)]
[string]$AgentName,
[Parameter(Mandatory = $true)]
[string]$TargetDirectory,
[Parameter(Mandatory = $true)]
[System.IO.FileSystemInfo[]]$SourceFiles,
[Parameter(Mandatory = $true)]
[string]$RepoRoot
)
Write-Host "`r`n--- Updating $AgentName Instructions ---"
$targetDir = Join-Path $RepoRoot $TargetDirectory
foreach ($sourceFile in $SourceFiles) {
$targetFile = Join-Path $targetDir $sourceFile.Name
Copy-FileIfDifferent -SourcePath $sourceFile.FullName -TargetPath $targetFile
}
Assert-InstructionSync -SourceDirectory ".ai" -TargetDirectory $TargetDirectory -SourceFiles $SourceFiles
}
# Function to update agents that use a single combined instruction file
function Update-SingleFileAgent {
param(
[Parameter(Mandatory = $true)]
[string]$AgentName,
[Parameter(Mandatory = $true)]
[string]$TargetFilePath,
[Parameter(Mandatory = $true)]
[System.IO.FileSystemInfo[]]$SourceFiles,
[Parameter(Mandatory = $true)]
[string]$RepoRoot
)
Write-Host "`r`n--- Updating $AgentName Instructions ---"
$targetFile = Join-Path $RepoRoot $TargetFilePath
$relativeTarget = $targetFile.Substring($repoRoot.Length + 1)
$allInstructionsContent = New-Object System.Text.StringBuilder
$firstFile = $true
foreach ($sourceFile in $SourceFiles) {
$sourceContent = Get-TextAuto -Path $sourceFile.FullName
if ([string]::IsNullOrWhiteSpace($sourceContent)) {
Write-Warning "Skipping empty file: $($sourceFile.Name)"
continue
}
if (-not $firstFile) {
[void]$allInstructionsContent.AppendLine("")
}
[void]$allInstructionsContent.AppendLine("==== START OF INSTRUCTIONS FROM: $($sourceFile.Name) ====")
[void]$allInstructionsContent.AppendLine("")
[void]$allInstructionsContent.AppendLine("# Instructions from: $($sourceFile.Name)")
[void]$allInstructionsContent.AppendLine("")
[void]$allInstructionsContent.AppendLine($sourceContent.Trim())
[void]$allInstructionsContent.AppendLine("")
[void]$allInstructionsContent.AppendLine("==== END OF INSTRUCTIONS FROM: $($sourceFile.Name) ====")
$firstFile = $false
}
$finalContent = $allInstructionsContent.ToString()
$existing = if (Test-Path -Path $targetFile -PathType Leaf) { Get-TextAuto -Path $targetFile } else { $null }
if ($null -ne $existing -and $existing -eq $finalContent) {
Write-Host "Up-to-date: $relativeTarget"
return
}
Write-Utf8NoBom -Path $targetFile -Content $finalContent
Write-Host "Updated: $relativeTarget"
}
function Invoke-RoboCopyMirror {
param(
[Parameter(Mandatory = $true)]
[string]$SourceDirectory,
[Parameter(Mandatory = $true)]
[string]$DestinationDirectory,
[Parameter(Mandatory = $true)]
[string]$Label
)
if (-not (Test-Path $SourceDirectory -PathType Container)) {
Write-Host "No skills folder found at: $SourceDirectory"
return
}
Ensure-Directory -Path $DestinationDirectory
Write-Host "`r`n--- Mirroring skills to $Label ---"
Write-Host "Source: $SourceDirectory"
Write-Host "Destination: $DestinationDirectory"
# /MIR = mirror (copy + delete removed)
# /FFT = tolerate 2s timestamp granularity
# /R:1 /W:1 = retry quickly
# /NFL/NDL = no file/dir listing (keep output compact)
# /NJH/NJS = no job header/summary
# /NP = no progress
# /XD = exclude version control/build dirs
$excludedDirs = @('.git', '.vs', 'bin', 'obj')
$args = @(
$SourceDirectory,
$DestinationDirectory,
'/MIR',
'/FFT',
'/R:1',
'/W:1',
'/NFL',
'/NDL',
'/NJH',
'/NJS',
'/NP'
)
foreach ($d in $excludedDirs) {
$args += '/XD'
$args += $d
}
$exe = 'robocopy'
# Do not echo the full robocopy command; it is noisy and can wrap in some terminals.
Write-Host "robocopy <source> <destination> /MIR /NFL /NDL /NJH /NJS /NP ..."
& $exe @args | Out-Null
$exitCode = $LASTEXITCODE
# Robocopy uses bitmask exit codes.
# 0-7 are success with various flags; >= 8 indicates failure.
if ($exitCode -ge 8) {
throw "Robocopy failed with exit code $exitCode. Command: $cmd"
}
# IMPORTANT: robocopy returns 1+ for successful copies.
# Ensure PowerShell script does not propagate a non-zero exit code for success cases.
$global:LASTEXITCODE = 0
Write-Host "Mirrored skills to $Label (robocopy exit code $exitCode)."
}
function Sync-SkillsToRoo {
param(
[Parameter(Mandatory = $true)]
[string]$RepoRoot
)
$srcSkillsRoot = Join-Path $RepoRoot ".ai\skills"
$rooSkillsRoot = Join-Path $RepoRoot ".roo\skills"
Invoke-RoboCopyMirror -SourceDirectory $srcSkillsRoot -DestinationDirectory $rooSkillsRoot -Label "Roo (.roo\\skills)"
}
function Sync-SkillsToGitHub {
param(
[Parameter(Mandatory = $true)]
[string]$RepoRoot
)
$srcSkillsRoot = Join-Path $RepoRoot ".ai\skills"
$githubSkillsRoot = Join-Path $RepoRoot ".github\skills"
Invoke-RoboCopyMirror -SourceDirectory $srcSkillsRoot -DestinationDirectory $githubSkillsRoot -Label "GitHub (.github\\skills)"
}
function Sync-SkillsToClaude {
param(
[Parameter(Mandatory = $true)]
[string]$RepoRoot
)
$srcSkillsRoot = Join-Path $RepoRoot ".ai\skills"
$claudeSkillsRoot = Join-Path $RepoRoot ".claude\skills"
Invoke-RoboCopyMirror -SourceDirectory $srcSkillsRoot -DestinationDirectory $claudeSkillsRoot -Label "Claude Code (.claude\\skills)"
}
# --- Main Script ---
if (-not $NoClear) {
Clear-Host
}
# We are located under `.ai/skills/<skill>/tools`. Find repo root by going up 4 levels.
$scriptDir = $PSScriptRoot
$repoRoot = (Join-Path -Path $scriptDir -ChildPath "..\..\..\.." | Resolve-Path).Path
# `.ai` folder path
$aiDir = Join-Path $repoRoot ".ai"
# Discover source files matching *instructions.md in the .ai folder
[System.IO.FileSystemInfo[]]$sourceInstructionFiles = Get-ChildItem -Path $aiDir -Filter "*instructions.md" -File | Sort-Object Name
if ($null -eq $sourceInstructionFiles -or $sourceInstructionFiles.Length -eq 0) {
Write-Warning "No '*instructions.md' files found in '$aiDir'. Nothing to process."
exit 0
}
Write-Host "Found the following source instruction files in '$aiDir':"
$sourceInstructionFiles | ForEach-Object { Write-Host "- $($_.Name)" }
# Mode parameter handling: if 'ALL' or 'AUTO', skip interactive prompt
if ($Mode -eq 'ALL') {
Write-Host "Selected: ALL (parameter mode)"
$updateCline = $true
$updateCopilot = $true
$updateRooCode = $true
$updateCodex = $true
$updateClaude = $true
}
elseif ($Mode -eq 'AUTO') {
Write-Host "Selected: AUTO (parameter mode)"
# Determine available agents based on instruction files
$updateCline = Test-HasInstructionFiles -Path (Join-Path $repoRoot '.clinerules')
$updateRooCode = Test-HasInstructionFiles -Path (Join-Path $repoRoot '.roo\rules')
$updateCopilot = Test-Path (Join-Path $repoRoot '.github\copilot-instructions.md') -PathType Leaf
$updateCodex = Test-Path (Join-Path $repoRoot 'AGENTS.md') -PathType Leaf
$updateClaude = Test-HasInstructionFiles -Path (Join-Path $repoRoot '.claude')
Write-Host "Agents to update based on available instruction files:"
if ($updateCline) { Write-Host "- CLINE" }
if ($updateRooCode) { Write-Host "- ROO CODE" }
if ($updateCopilot) { Write-Host "- GitHub CoPilot" }
if ($updateCodex) { Write-Host "- OpenAI Codex" }
if ($updateClaude) { Write-Host "- Claude Code" }
}
elseif ($Mode -and $Mode -ne '') {
# Specific agent mode (e.g., CLINE, "ROO CODE", etc.)
$updateCline = ($Mode -eq 'CLINE')
$updateCopilot = ($Mode -eq 'GitHub CoPilot')
$updateRooCode = ($Mode -eq 'ROO CODE')
$updateCodex = ($Mode -eq 'OpenAI Codex')
$updateClaude = ($Mode -eq 'Claude Code')
Write-Host "Selected: $Mode (parameter mode)"
}
else {
# User prompt for agent selection
# Detect available agents for interactive menu
$hasCline = Test-HasInstructionFiles -Path (Join-Path $repoRoot '.clinerules')
$hasRooCode = Test-HasInstructionFiles -Path (Join-Path $repoRoot '.roo\rules')
$hasCopilot = Test-Path (Join-Path $repoRoot '.github\copilot-instructions.md') -PathType Leaf
$hasCodex = Test-Path (Join-Path $repoRoot 'AGENTS.md') -PathType Leaf
$hasClaude = Test-HasInstructionFiles -Path (Join-Path $repoRoot '.claude')
Write-Host "`r`nDetected AI agents with instruction files:"
if ($hasCline) { Write-Host "- CLINE" }
if ($hasRooCode) { Write-Host "- ROO CODE" }
if ($hasCopilot) { Write-Host "- GitHub CoPilot" }
if ($hasCodex) { Write-Host "- OpenAI Codex" }
if ($hasClaude) { Write-Host "- Claude Code" }
Write-Host ""
Write-Host "=============================================================="
Write-Host "Select Agent Instruction Set to Update"
Write-Host "--------------------------------------------------------------"
Write-Host "1. AUTO - Update only agents with instruction files (default)"
Write-Host "2. ALL - Update instructions for all AI agents"
Write-Host "3. CLINE - Update instructions for CLINE"
Write-Host "4. ROO CODE - Update instructions for ROO CODE"
Write-Host "5. GitHub CoPilot - Update instructions for GitHub CoPilot"
Write-Host "6. OpenAI Codex - Update instructions for OpenAI Codex"
Write-Host "7. Claude Code - Update instructions for Claude Code"
Write-Host "0. Exit"
Write-Host "=============================================================="
$selection = Read-Host "Enter the number of your choice (0-7)"
# Initialize flags
$updateCline = $false
$updateCopilot = $false
$updateRooCode = $false
$updateCodex = $false
$updateClaude = $false
switch ($selection) {
'1' {
$updateCline = Test-HasInstructionFiles -Path (Join-Path $repoRoot '.clinerules')
$updateRooCode = Test-HasInstructionFiles -Path (Join-Path $repoRoot '.roo\rules')
$updateCopilot = Test-Path (Join-Path $repoRoot '.github\copilot-instructions.md') -PathType Leaf
$updateCodex = Test-Path (Join-Path $repoRoot 'AGENTS.md') -PathType Leaf
$updateClaude = Test-HasInstructionFiles -Path (Join-Path $repoRoot '.claude')
Write-Host "Selected: AUTO"
}
'2' {
$updateCline = $true
$updateCopilot = $true
$updateRooCode = $true
$updateCodex = $true
$updateClaude = $true
Write-Host "Selected: ALL"
}
'3' { $updateCline = $true; Write-Host "Selected: CLINE" }
'4' { $updateRooCode = $true; Write-Host "Selected: ROO CODE" }
'5' { $updateCopilot = $true; Write-Host "Selected: GitHub CoPilot" }
'6' { $updateCodex = $true; Write-Host "Selected: OpenAI Codex" }
'7' { $updateClaude = $true; Write-Host "Selected: Claude Code" }
'0' { Write-Host "Operation cancelled by user."; exit 0 }
default { throw "Invalid selection. Exiting." }
}
}
# --- Multiple-File Agent Updates ---
if ($updateCline) {
Update-MultipleFileAgent -AgentName "CLINE" -TargetDirectory ".clinerules" -SourceFiles $sourceInstructionFiles -RepoRoot $repoRoot
}
if ($updateRooCode) {
Update-MultipleFileAgent -AgentName "ROO CODE" -TargetDirectory ".roo\rules" -SourceFiles $sourceInstructionFiles -RepoRoot $repoRoot
}
# --- Single-File Agent Updates ---
if ($updateCopilot) {
$copilotTarget = ".github\copilot-instructions.md"
$githubInstructionsDir = Join-Path $repoRoot ".github\instructions"
if (Test-Path $githubInstructionsDir -PathType Container) {
Write-Host "`r`n--- Updating GitHub CoPilot Instructions (folder-based) ---"
$mainName = "instructions.md"
$mainSource = $sourceInstructionFiles | Where-Object { $_.Name -ieq $mainName } | Select-Object -First 1
if ($null -eq $mainSource) {
throw "Expected source '$mainName' under .ai but none found."
}
Copy-FileIfDifferent -SourcePath $mainSource.FullName -TargetPath (Join-Path $repoRoot $copilotTarget)
foreach ($sf in $sourceInstructionFiles) {
if ($sf.Name -ieq $mainName) {
continue
}
$destination = Join-Path $githubInstructionsDir $sf.Name
Copy-FileIfDifferent -SourcePath $sf.FullName -TargetPath $destination
}
}
else {
Update-SingleFileAgent -AgentName "GitHub CoPilot" -TargetFilePath $copilotTarget -SourceFiles $sourceInstructionFiles -RepoRoot $repoRoot
}
}
if ($updateCodex) {
Update-SingleFileAgent -AgentName "OpenAI Codex" -TargetFilePath "AGENTS.md" -SourceFiles $sourceInstructionFiles -RepoRoot $repoRoot
}
# --- Claude Code (multiple-file agent) ---
if ($updateClaude) {
Update-MultipleFileAgent -AgentName "Claude Code" -TargetDirectory ".claude" -SourceFiles $sourceInstructionFiles -RepoRoot $repoRoot
}
# --- Skills mirroring ---
if ($updateRooCode -or $Mode -eq 'ALL' -or $Mode -eq 'AUTO') {
Sync-SkillsToRoo -RepoRoot $repoRoot
}
# GitHub Copilot: mirror skills to `.github/skills` (Copilot tries to load from there).
if ($updateCopilot -or $Mode -eq 'ALL' -or $Mode -eq 'AUTO') {
Sync-SkillsToGitHub -RepoRoot $repoRoot
}
# Claude Code: mirror skills to `.claude/skills`.
if ($updateClaude -or $Mode -eq 'ALL' -or $Mode -eq 'AUTO') {
Sync-SkillsToClaude -RepoRoot $repoRoot
}
Write-Host "`r`nAll selected operations completed successfully."
# Only pause when launched by double-click (Explorer). In CI / terminal usage, do not pause.
if ($Host.Name -and $Host.Name -notlike '*ConsoleHost*') {
Invoke-Pause
}
================================================
FILE: .editorconfig
================================================
root = true
[*]
charset = utf-8
# Line endings: Let Git handle normalization via .gitattributes and core.autocrlf
# - Repository stores LF (normalized by Git)
# - Working tree uses OS-native line endings (CRLF on Windows, LF on Unix)
# - Uncomment below to force editors to use LF (useful for LF-everywhere workflows):
# end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
# Spaces at the end can be meaningful in Markdown (hard line breaks)
[*.md]
trim_trailing_whitespace = false
# Windows PowerShell 5.1 can misinterpret UTF-8 without BOM when non-ASCII exists.
# If you are PowerShell 7+ only, you may prefer charset = utf-8 here.
[*.ps1]
charset = utf-8-bom
================================================
FILE: .github/copilot-instructions.md
================================================
## Role
Your role is to analyze and improve code by making only localized, targeted changes. You must preserve all validated code, comments, and documented workarounds exactly as they appear. Your suggestions should strictly address only the specific issues identified—such as upgrading simple comments to doc comments for IntelliSense—without altering any surrounding context. Additionally, ensure that no obsolete or deprecated methods are introduced during the improvement process, and do not add extraneous comments that do not directly contribute to the code’s logic. Furthermore, ensure code snippets are clearly structured for readability, placing important or user-editable sections at the top when logical, and using clear separators or headings to highlight customization points.
Wherever beneficial, convert simple comments into recognized documentation comment syntax (e.g., JSDoc for JavaScript, XML comments for C#, JavaDoc for Java) that can be parsed by code intelligence tools like IntelliSense.
Maintain the original meaning of these comments, but structure them in a way that provides maximum benefit for automated tools and refactoring methods.
Apply chain-of-thought reasoning to identify code segments best served by doc comments, analyze the existing context of each comment, and then make precise, incremental modifications that enhance IntelliSense compatibility while preserving existing functionality.
## Output
Wrap any and all code—including regular code snippets, inline code segments, outputs, pseudocode, or any text that represents code—in Markdown code blocks with a language identifier (e.g., ```typescript, ```powershell).
================================================
FILE: .github/instructions/coding-guideline.instructions.md
================================================
# Coding Guidelines
- If the qdrant-mcp-server is running, use it for all permanent memory operations (e.g. storing user information).
- After making changes, ALWAYS start a new server for testing.
- Kill all existing related servers from previous testing before starting a new server.
- Prefer the simplest viable solution; avoid over-engineering.
- Do not add broad try/catch or wrapper layers unless required by a failing test or explicit requirement; if you catch, rethrow to preserve the stack.
- Before writing new code, actively look for existing utilities or functions that can be reused instead of duplicated.
- New helper methods or classes must be justified with a clear, documented need for functionality that is unavailable elsewhere in the codebase.
- Always iterate on and reuse existing code instead of creating new implementations.
- Avoid adding layers of abstraction that do not deliver clear value.
- Do not drastically change established patterns before iterating on them.
- No duplication / SSOT: update or move existing code instead of adding parallel implementations. If you introduce a replacement, remove the old one **in the same change**.
- Write code that accounts for different environments (dev, test, and prod).
- Only modify what is explicitly requested or clearly necessary; do **not** create new files or modules unless explicitly requested.
- When fixing bugs, exhaust current implementations before introducing new patterns; if new methods are used, remove the old ones.
- Keep the codebase clean and organized.
- Avoid one-off scripts unless absolutely necessary.
- Use mocks only for tests, not for dev or prod.
- Never add stubbing or fake data in dev or prod environments.
- Never overwrite the .env file without explicit confirmation.
- Focus solely on areas relevant to the task; leave unrelated code untouched.
- Write thorough tests for all major functionality.
- Avoid major changes to the existing architecture unless explicitly instructed.
- Always consider the impact on other methods and areas of the code.
- Prefer to wrap long lines for better readability.
- Preserve existing formatting; limit formatting to lines you changed and match surrounding style. Also remove any unused imports/usings or dead code **introduced by your edits**.
- Stability primitives (repeatability > cleverness): when the repo provides an established way to perform an operation (repo-owned script, documented command snippet, standard PS1 under .ai/Scripts), treat it as the single source of truth. Use it verbatim instead of synthesizing “equivalent” commands. Only deviate when explicitly asked or when the primitive is proven broken in this environment (and then fix the primitive, not invent a parallel path).
- No code file that you **create or modify** may exceed **6000 tokens (~24 KB)** once your changes are applied.
- If your changes alone would push the file past this limit, either trim the change or ask for explicit permission to refactor; do **not** alter unrelated code solely to meet the limit.
- Existing oversized files are left untouched unless the user explicitly requests a refactor.
## Source Control Conventions
Naming conventions for issues, branches, and pull requests.
### Categories
| Category | Use for |
|----------|---------|
| `FEAT` | New features |
| `FIX` | Bug fixes |
| `TECH` | Infrastructure, dependencies, refactoring |
| `DOCS` | Documentation |
### Naming Patterns
| Item | Pattern | Example |
|------------|---------|---------|
| **Issue** | `{CATEGORY}: {Description}` | `FEAT: Download logs to CSV` |
| **Branch** | `{CATEGORY}-{issue#}-{lowercase-dashed-name}` | `FEAT-21-download-logs-to-csv` |
| **PR** | `PR: #{issue#}: {CATEGORY}: {Description}` | `PR: #21: FEAT: Download logs to CSV` |
- Branch names: lowercase, words separated by dashes, non-ASCII replaced with a single dash, derived from issue title but may be shortened.
- PR body must reference the issue with `Closes #{issue#}`.
- Merge via PR only — no direct pushes to `main` or `master`.
- Feature branches are always created from `main` or `master`. Never merge one feature branch into another — only merge `main` if you need to catch up.
- Always create branches from latest origin/main or origin/main, never merge sub-branches, confirm before risky git ops.
- Do not add Co-Authored-By {AI model}/{AI company} lines to commits.
- Never close issues before a release is published with the fix.
Use the following guidelines:
1. Doc Comment Enhancement for IntelliSense
- Replace or augment simple comments with relevant doc comment syntax that is supported by IntelliSense as needed.
- Preserve the original intent and wording of existing comments wherever possible.
2. Code Layout for Clarity
- Place the most important or user-editable sections at the top if logically appropriate.
- Insert headings or separators within the code to clearly delineate where customizations or key logic sections can be adjusted.
3. No Extraneous Code Comments
- Do not include "one-off" or user-directed commentary in the code.
- Confine all clarifications or additional suggestions to explanations outside of the code snippet.
4. Avoid Outdated or Deprecated Methods
- Refrain from introducing or relying on obsolete or deprecated methods and libraries.
- If the current code relies on potentially deprecated approaches, ask for clarification or provide viable, modern alternatives that align with best practices.
5. Testing and Validation
- Suggest running unit tests or simulations on the modified segments to confirm that the changes fix the issue without impacting overall functionality.
- Ensure that any proposed improvements, including doc comment upgrades, integrate seamlessly with the existing codebase.
- After all code modifications, navigate to the affected project directory and build C# then Angular to confirm the application compiles without errors:
cd {PROJECT} && dotnet build {PROJECT}.csproj
cd {PROJECT}/ClientApp && ng build
- Run relevant unit tests if code changes affect core logic.
- If the developer certificate is not trusted, then execute: dotnet dev-certs https --trust
- To launch project use: dotnet watch run --project {PROJECT}/{PROJECT}.csproj --launch-profile "{PROJECT} (NG Build)"
6. Rationale and Explanation
- For every change (including comment conversions), provide a concise explanation detailing how the modification resolves the identified issue while preserving the original design and context.
- Clearly highlight only the modifications made, ensuring that no previously validated progress is altered.
- NOTE: Summarize reasoning for the user, but do NOT expose full chain-of-thought. Keep internal deliberations internal; surface only the concise rationale needed to justify each change.
7. Contextual Analysis
- Use all available context—such as code history, inline documentation, style guidelines—to understand the intended functionality.
- When inspecting an existing file for understanding, prefer reading the whole file in a
single `read_file` call when it comfortably fits in context; switch to targeted slices
only when the file is too large, the tool truncates it, or a specific anchor line is
already known.
- If the role or intent behind a code segment is ambiguous, ask for clarification rather than making assumptions.
8. Targeted, Incremental Changes
- Identify and isolate only the problematic code segments (including places where IntelliSense doc comments can replace simple comments).
- Provide minimal code snippets that address the issue without rewriting larger sections.
- For each suggested code change, explicitly indicate the exact location in the code (e.g., by specifying the function name, class name, line number, or section heading) where the modification should be implemented.
9. Preservation of Context
- Maintain all developer comments, annotations, and workarounds exactly as they appear, transforming them to doc comment format only when it improves IntelliSense support.
- Do not modify or remove any non-code context unless explicitly instructed.
- Avoid introducing new, irrelevant comments in the code.
10. Launching {PROJECT} Correctly:
- Navigate to the {PROJECT} project folder.
- Run the following command to launch the project with live reload and proper debugging configuration:
dotnet watch run --launch-profile "{PROJECT} (NG Build)" --project {PROJECT}/{PROJECT}.csproj
- This command will start the {PROJECT} project on the designated debugging session URL.
- Ensure that any previous {PROJECT} instances are terminated before running this command.
================================================
FILE: .github/instructions/repository-analysis.instructions.md
================================================
# Repository Analysis
## 1. Repository Overview
This document provides a factual reference for the Jocys.com FocusLogger repository, aimed at developers and AI coding agents working on the codebase.
**FocusLogger** is a Windows desktop utility that monitors and logs which process or program takes window focus. It targets users (especially gamers and power users) who experience unexpected focus stealing — where a background process briefly grabs foreground focus, interrupting gameplay or work. The tool logs every focus change with timestamps, process details, window class names, and focus-state flags, allowing users to identify the culprit.
- **Repository:** https://github.com/JocysCom/FocusLogger
- **License:** GNU General Public License v3.0
- **Current version:** 1.2.6
- **Target platform:** Windows 10+ with .NET 8.0
- **Primary audiences:** Gamers, power users, IT support personnel diagnosing focus-stealing issues.
## 2. Top-Level Structure
This section maps every top-level directory and file to help navigate the repository quickly.
| Path | Purpose |
|------|---------|
| `FocusLogger/` | Main application project (WPF, .NET 8.0). Contains all app source code, shared library, and resources. |
| `FocusLogger.Tests/` | MSTest test project. Unit tests for CSV export and UI automation tests. |
| `Documents/` | Release engineering: signing scripts, zip packaging scripts, screenshot tooling, and pre-built release files. |
| `Resources/` | Solution-level shared scripts (currently `ZipFiles.ps1` for checksum-aware zip packaging). |
| `.ai/` | AI agent instructions, coding guidelines, repository analysis, and skills. |
| `JocysCom.FocusLogger.slnx` | Solution file (XML-based `.slnx` format) referencing the two projects. |
| `README.md` | Project overview, download link, system requirements, screenshot. |
| `LICENSE` | GPLv3 license text. |
| `SECURITY.md` | Security vulnerability reporting policy (support@jocys.com). |
| `Settings.XamlStyler` | XamlStyler configuration for consistent XAML formatting. |
| `Solution_Cleanup.ps1` | PowerShell script for cleaning build artifacts. |
## 3. Technology Stack & Key Dependencies
This section lists verified technologies and versions drawn from project files.
| Technology | Version / Detail | Evidence |
|------------|-----------------|----------|
| .NET | 8.0 (`net8.0-windows`) | `JocysCom.FocusLogger.csproj` TargetFramework |
| C# | Implicit (SDK default for .NET 8) | SDK-style project |
| WPF | `<UseWPF>true</UseWPF>` | csproj |
| Windows Forms interop | `<UseWindowsForms>true</UseWindowsForms>` | csproj — used for P/Invoke helpers and DPI awareness |
| MSTest | v3.x (`MSTest.TestFramework 3.*`, `MSTest.TestAdapter 3.*`) | Test csproj PackageReference |
| Microsoft.NET.Test.Sdk | 17.x | Test csproj PackageReference |
| Windows API (user32.dll) | P/Invoke | `NativeMethods.cs` |
| PowerShell | Scripts for build, sign, zip, cleanup | `Documents/`, `Resources/`, root |
| XamlStyler | Config present | `Settings.XamlStyler` |
**No NuGet package dependencies** in the main application project — all functionality comes from .NET SDK and the embedded `JocysCom.ClassLibrary`.
## 4. Architecture & Runtime Model
This section describes how the application is structured and how it operates at runtime.
FocusLogger is a **single-executable WPF desktop application** that polls Windows API functions to detect focus changes. It does not persist log data between sessions (in-memory only) but provides CSV export for offline analysis.
### Architectural layers
```mermaid
graph TD
subgraph UI["UI Layer (WPF)"]
App["App.xaml.cs<br/>Entry point, DPI aware"]
MW["MainWindow.xaml.cs<br/>Main window frame"]
DLC["DataListControl.xaml.cs<br/>Core logging UI + logic"]
end
subgraph Core["Core Logic"]
DI["DataItem.cs<br/>Log entry model"]
DIT["DataItemType.cs<br/>Entry type enum"]
NM["NativeMethods.cs<br/>P/Invoke declarations"]
CSV["CSV Export<br/>BuildCsvContent, CsvEscape"]
end
subgraph Shared["JocysCom.ClassLibrary (embedded)"]
Config["Configuration<br/>SettingsData, SettingsItem, AssemblyInfo"]
CompModel["ComponentModel<br/>SortableBindingList, BindingListInvoked"]
Controls["Controls<br/>ControlsHelper, ItemFormattingConverter,<br/>InfoControl, MessageBoxWindow"]
Other["Collections, IO, Text, Runtime, Data"]
end
subgraph WinAPI["Windows OS"]
User32["user32.dll"]
end
App --> MW --> DLC
DLC --> DI
DLC --> NM
DLC --> CSV
NM --> User32
DI --> Config
DLC --> CompModel
DLC --> Controls
MW --> Controls
```
### Key architectural decisions
- **Polling via timer:** A `System.Timers.Timer` with 1ms interval (non-auto-reset) continuously polls `GetActiveWindow()` and `GetForegroundWindow()`. Duplicate events are suppressed via `DataItem.IsSame()`.
- **Thread safety:** Timer fires on a thread-pool thread; UI updates are marshalled via `ControlsHelper.BeginInvoke()`. A `lock(AddLock)` synchronizes the polling logic.
- **Embedded shared library:** `JocysCom.ClassLibrary` files are included directly in `FocusLogger/JocysCom/` rather than as a compiled DLL or NuGet package.
- **No MVVM framework:** Code-behind pattern with data binding. `DataListControl.xaml.cs` contains both view-model-like logic and model interaction.
## 5. Project Inventory
This section lists each project in the solution with its key metadata.
### 5.1 JocysCom.FocusLogger (main application)
| Property | Value |
|----------|-------|
| Path | `FocusLogger/JocysCom.FocusLogger.csproj` |
| Output type | `WinExe` |
| Target framework | `net8.0-windows` |
| Assembly name | `JocysCom.FocusLogger` |
| Description | Find out which process or program is taking the window focus. In game, mouse and keyboard could temporarily stop responding if another program takes the focus. This tool could help diagnose which program is stealing the focus. |
| Version | 1.2.6 |
| NuGet dependencies | None |
| Embedded resources | `Resources/BuildDate.txt` (auto-generated), `Resources/AiAnalysisPrompt.md` |
**Source structure:**
| Directory | Contents |
|-----------|----------|
| `FocusLogger/` (root) | `App.xaml(.cs)`, `MainWindow.xaml(.cs)`, `AssemblyInfo.cs`, `App.ico` |
| `FocusLogger/Common/` | `DataItem.cs`, `DataItemType.cs`, `NativeMethods.cs` |
| `FocusLogger/Controls/` | `DataListControl.xaml(.cs)` — core logging control |
| `FocusLogger/JocysCom/` | Embedded `JocysCom.ClassLibrary` (~30 files across Collections, Common, ComponentModel, Configuration, Controls, Data, IO, Runtime, Text) |
| `FocusLogger/Resources/` | `AiAnalysisPrompt.md`, `BuildDate.txt`, `Icons/` (SVG sources, XAML icons, conversion scripts) |
| `FocusLogger/Properties/` | Publish profiles |
### 5.2 JocysCom.FocusLogger.Tests (test project)
| Property | Value |
|----------|-------|
| Path | `FocusLogger.Tests/JocysCom.FocusLogger.Tests.csproj` |
| Target framework | `net8.0-windows` |
| Test framework | MSTest v3.x |
| Project reference | `FocusLogger/JocysCom.FocusLogger.csproj` |
**Test files:**
| File | Purpose |
|------|---------|
| `CsvExportTests.cs` | Unit tests for `CsvEscape` and `BuildCsvContent` methods |
| `UIAutomationTests.cs` | UI automation tests using `System.Windows.Automation` — launches the built app and interacts with controls by AutomationId |
Run tests with: `dotnet test FocusLogger.Tests/JocysCom.FocusLogger.Tests.csproj`
## 6. Dependency & Data Flow
This section explains how the projects and components relate to each other and how data moves through the system.
### Project dependency graph
```mermaid
graph LR
Tests["FocusLogger.Tests<br/>(MSTest v3)"] -->|ProjectReference| App["JocysCom.FocusLogger<br/>(WPF App)"]
App -->|embedded source| Shared["JocysCom.ClassLibrary<br/>(in FocusLogger/JocysCom/)"]
App -->|P/Invoke| WinAPI["user32.dll"]
```
### Runtime data flow
1. `System.Timers.Timer` fires (1ms interval, non-auto-reset).
2. `DataListControl.UpdateInfo()` acquires `AddLock`.
3. Calls `NativeMethods.GetActiveWindow()` and `NativeMethods.GetForegroundWindow()`.
4. For each handle, `GetItemFromHandle()` creates a `DataItem` with timestamp, focus flags (mouse/keyboard/caret), window title, and window class name.
5. `IsSame()` checks if the event differs from the previous one; if not, it is skipped.
6. `UpdateFromProcess()` enriches the `DataItem` with process name and path (with error handling for restricted processes).
7. The item is inserted at position 0 of `SortableBindingList<DataItem>` via `ControlsHelper.BeginInvoke()` (UI thread dispatch).
8. The WPF `DataGrid` updates via data binding. `ItemFormattingConverter` translates boolean flags to icons.
### CSV export flow
1. User clicks "Save CSV" button.
2. `SaveFileDialog` prompts for file location.
3. `BuildCsvContent()` iterates all `DataItem` entries, writing CSV with headers: Date, PID, Process Name, Active, Mouse, Keyboard, Caret, Window Title, Window Class, Path.
4. File is written as UTF-8.
5. "Explore" button opens the saved file location in Explorer.
6. "AI Prompt Example" button shows the embedded `AiAnalysisPrompt.md` in a `MessageBoxWindow` for users to copy and paste into an AI assistant along with their CSV.
## 7. Build, Test, CI/CD & Operational Workflows
This section documents how the project is built, tested, and released based on repository evidence.
### Build
```bash
dotnet build JocysCom.FocusLogger.slnx
```
- **Pre-build event:** Generates `Resources/BuildDate.txt` with the current ISO 8601 timestamp via PowerShell.
- **Output:** Single `JocysCom.FocusLogger.exe` in `bin/{Configuration}/net8.0-windows/`.
- **Debug configuration:** Embedded PDB symbols (`DebugType: embedded`).
### Test
```bash
dotnet test FocusLogger.Tests/JocysCom.FocusLogger.Tests.csproj
```
- **Framework:** MSTest v3.x with Microsoft.NET.Test.Sdk 17.x.
- **Unit tests:** `CsvExportTests` — validates CSV escaping and content generation.
- **UI automation tests:** `UIAutomationTests` — launches the built application and interacts via `System.Windows.Automation`. Requires a prior build of the main project.
### Release / packaging scripts
| Script | Purpose |
|--------|---------|
| `Documents/App_1_Sign.ps1` | Code-signs the application executable. |
| `Documents/App_2_Zip.ps1` | Packages the signed executable into a release ZIP. |
| `Resources/ZipFiles.ps1` | Shared utility for checksum-aware ZIP creation (compares source/dest checksums before rebuilding). |
| `Documents/Take_Screenshot.ps1` | Captures application screenshot for documentation. |
| `Documents/Take_Screenshot.ps1.cs` | C# helper compiled by the screenshot script. |
| `Solution_Cleanup.ps1` | Cleans `bin/`, `obj/`, and other build artifacts. |
### Icon workflow
SVG icon sources are stored in `FocusLogger/Resources/Icons/Icons_Default/`. The script `Convert_SVG_to_XAML.ps1` converts them to XAML resource dictionaries (`Icons_Default.xaml`).
### CI/CD
No CI/CD workflow files were found under `.github/workflows/`. Builds and releases appear to be performed locally.
## 8. Documentation Map
This section identifies where documentation lives in the repository.
| Location | Audience | Content |
|----------|----------|---------|
| `README.md` | End users, contributors | Project overview, download link, system requirements, screenshot |
| `SECURITY.md` | Security researchers | Vulnerability reporting policy |
| `LICENSE` | All | GPLv3 full text |
| `.ai/ReadMe.md` | AI agents, developers | Explains the purpose of each file in the `.ai/` directory and how custom instructions work for Copilot, CLINE, and Codex |
| `.ai/instructions.md` | AI agents | Role definition and output formatting rules for AI-assisted edits |
| `.ai/coding-guideline.instructions.md` | AI agents | Detailed coding guidelines, source control conventions (branch/PR naming), testing workflow, and constraints for AI agents |
| `.ai/repository-analysis.instructions.md` | AI agents, developers | This file — comprehensive repository reference |
| `.ai/skills/` | AI agents | Skill definitions (e.g., `ai-self-improvement`) for agent-assisted workflows |
| `FocusLogger/Resources/AiAnalysisPrompt.md` | End users | Prompt template for users to paste into AI assistants alongside exported CSV logs |
| `Documents/Images/` | README, users | Application screenshot |
| `Settings.XamlStyler` | Developers | XamlStyler formatting configuration |
### Documentation taxonomy
```mermaid
graph TD
subgraph EndUsers["End-User Documentation"]
README["README.md<br/>Overview, download, requirements"]
SECURITY["SECURITY.md<br/>Vulnerability reporting"]
LICENSE["LICENSE<br/>GPLv3"]
AiPrompt["FocusLogger/Resources/<br/>AiAnalysisPrompt.md<br/>Prompt template for CSV analysis"]
Screenshot["Documents/Images/<br/>Application screenshot"]
end
subgraph AIAgents["AI Agent Instructions"]
AiReadMe[".ai/ReadMe.md<br/>Directory guide"]
AiInstr[".ai/instructions.md<br/>Role & output rules"]
AiCoding[".ai/coding-guideline<br/>.instructions.md<br/>Coding & SCM conventions"]
AiRepo[".ai/repository-analysis<br/>.instructions.md<br/>This file"]
AiSkills[".ai/skills/<br/>Agent skill definitions"]
end
subgraph DevConfig["Developer Configuration"]
XamlStyler["Settings.XamlStyler<br/>XAML formatting"]
end
```
## 9. AI-Agent-Relevant Conventions and Constraints
This section captures rules and patterns that materially affect automated edits. The full set of coding and source-control conventions is defined in `.ai/coding-guideline.instructions.md`; the highlights below are the items most likely to cause mistakes if overlooked.
1. **Coding style:** Follow Microsoft C# conventions. PascalCase for public members, camelCase for locals. Some private fields use `_PascalCase` (e.g., `_Date`). Preserve existing naming patterns in each file. Prefer the simplest viable solution; avoid over-engineering.
2. **Doc comments:** Per `.ai/instructions.md`, convert simple comments to XML documentation comments where beneficial for IntelliSense. Do not alter surrounding code when doing so.
3. **Shared library files (`FocusLogger/JocysCom/`):** These are embedded from a shared `JocysCom.ClassLibrary`. Exercise caution when editing — changes here may diverge from the upstream library.
4. **XAML formatting:** The repository uses XamlStyler (see `Settings.XamlStyler`). XAML edits should conform to the configured style.
5. **No NuGet packages in the main app:** All dependencies are framework-provided or embedded source. Do not introduce NuGet package dependencies without explicit approval.
6. **Test project uses MSTest v3:** New tests should follow MSTest v3 patterns (`[TestClass]`, `[TestMethod]`, `Assert.*`).
7. **UI automation tests depend on a built executable:** `UIAutomationTests` locate the app at a relative path from the test output. Building the main project before running these tests is required.
8. **Pre-build event:** The csproj generates `Resources/BuildDate.txt` via PowerShell. This file should not be manually edited or committed.
9. **Solution format:** Uses `.slnx` (XML-based solution format), not the older `.sln` text format.
10. **No CI/CD pipelines:** All build and release steps are manual/local. Scripts in `Documents/` handle signing and packaging.
11. **File size limit:** No code file that is created or modified may exceed 6000 tokens (~24 KB).
12. **Source control conventions:** Issues use `{CATEGORY}: {Description}` (FEAT, FIX, TECH, DOCS). Branches use `{CATEGORY}-{issue#}-{lowercase-dashed-name}`. PRs use `PR: #{issue#}: {CATEGORY}: {Description}` and must reference the issue with `Closes #{issue#}`. Merge via PR only — no direct pushes to `main`.
13. **No Co-Authored-By lines:** Do not add `Co-Authored-By` AI model/company lines to commits.
14. **Issue closure:** Never close issues before a release is published with the fix.
15. **No duplication / SSOT:** Update or move existing code instead of adding parallel implementations. If introducing a replacement, remove the old one in the same change.
16. **Stability primitives:** When the repo provides an established script or command, use it verbatim. Only deviate when it is proven broken, and then fix the primitive rather than inventing a parallel path.
================================================
FILE: .github/skills/ai-self-improvement/SKILL.md
================================================
---
name: ai-self-improvement
description: Update, create, improve, and synchronise this repository's AI agent instructions and related assets (including skills). Use when the user asks to create or edit a skill/SKILL.md, modify the agent's own instructions/processes, restructure instruction governance, migrate instruction content into skills, or run/adjust the sync pipeline that publishes `.ai/` sources into agent-specific folders. Load this skill before writing any SKILL.md, .instructions.md, or touching any skills/ folder (.ai/, .claude/, .roo/, .github/). It tells you the correct location (.ai/) and the sync step, so files end up in the right place.
---
# AI Self-Improvement (Instructions + Skills)
## Critical: `.ai/` is the Primary Source for ALL Agents
The `.ai/` folder is the **single source of truth** for all AI agent configurations in this repository. This applies to:
- **CLINE / Roo Code** — synced to `.roo/rules/` and `.roo/skills/`
- **GitHub Copilot** — synced to `.github/copilot-instructions.md`
- **OpenAI Codex / AGENTS.md** — synced to `AGENTS.md` at repo root
- **Claude Code** — synced to `.claude/*.instructions.md` and `.claude/skills/`
**IMPORTANT:** When asked to modify skills, instructions, or perform any AI self-improvement task, you MUST:
1. Locate the source file under `.ai/` (not the agent-specific output)
2. Make changes to the `.ai/` source
3. Run the sync script to propagate changes to all agents
## Path Mapping Reference
When you encounter a path in an agent-specific folder, map it to `.ai/`:
| Agent-Specific Path | Source Path (Edit Here) |
|---------------------|------------------------|
| `.roo/rules/*.md` | `.ai/*.instructions.md` |
| `.roo/skills/<name>/SKILL.md` | `.ai/skills/<name>/SKILL.md` |
| `.github/copilot-instructions.md` | `.ai/instructions.md` (generated) |
| `AGENTS.md` | `.ai/instructions.md` (generated) |
| `.claude/*.instructions.md` | `.ai/*.instructions.md` |
| `.claude/skills/<name>/SKILL.md` | `.ai/skills/<name>/SKILL.md` |
**Example:** If asked to update `.roo/skills/ai-self-improvement/SKILL.md`, you must edit `.ai/skills/ai-self-improvement/SKILL.md` instead.
## Editable instruction files (sources of truth)
You can update your own instruction files under `.ai/`:
- `.ai/instructions.md` — the main system instructions file
- `.ai/*instructions.md` — additional instruction files (auto-included)
- `.ai/*instructions-detail.md` — detailed instruction files (read only when needed)
- `.ai/skills/<name>/SKILL.md` — skill definition files
## Workflow
1. Treat `.ai/` as the **single source of truth** for agent instructions **and skills**.
2. When creating or migrating a skill, create/update it under `.ai/skills/`.
3. Make instruction changes in `.ai/instructions.md` and related `*.instructions.md` / `*.instructions-detail.md` files.
4. Do **not** edit generated outputs directly (they are produced by the sync script):
- `.roo/rules/`
- `.roo/skills/`
- `.github/copilot-instructions.md`
- `AGENTS.md`
- `.claude/`
5. **Test changes before syncing** — verify scripts execute correctly and changes work as expected.
6. After testing, run the sync script to apply to all agents.
## Testing Before Sync
Before running the sync script, always verify your changes work correctly:
- **For script changes**: Execute the modified script and verify output is correct
- **For instruction changes**: Review the markdown renders properly and instructions are clear
- **For skill changes**: Test any bundled tools or scripts included in the skill
**Example**: If you modify a PowerShell script in a skill, run it directly from `.ai/skills/<name>/scripts/` to confirm it works before syncing.
## Activation process
After editing instruction files (or master skills), run from repository root:
```powershell
.\.ai\skills\ai-self-improvement\scripts\Sync-AgentAssets.ps1 AUTO
```
This script synchronizes changes from `.ai/` to all agent-specific folders.
## Single source of truth
**Never embed template content in instructions — reference template files instead.**
Example:
- ✅ "Template maintained in `pr/checklist.template.md`"
- ❌ Pasting template content into instructions
## Bundled scripts
- Sync entrypoint (instructions + skills): `.ai/skills/ai-self-improvement/scripts/Sync-AgentAssets.ps1`
================================================
FILE: .github/skills/ai-self-improvement/scripts/Sync-AgentAssets.ps1
================================================
# Script: Sync-AgentAssets.ps1
# Location: .ai/skills/ai-self-improvement/scripts/Sync-AgentAssets.ps1
# Description:
# Synchronises AI agent instruction files and skills from master sources under `.ai/`.
# - Instructions: copies `*.instructions.md` from `.ai/` into agent-specific outputs.
# - Skills: mirrors `.ai/skills/*` into agent skill folders (e.g. `.roo/skills/*`).
#
# Options for Mode:
# ALL - update all known agent outputs
# AUTO - update only agents that exist in this repository (default usage)
# Or a specific agent name: CLINE, ROO CODE, GitHub CoPilot, OpenAI Codex, Claude Code
param(
[Parameter(Position = 0)]
[string]$Mode,
[switch]$NoClear
)
# Combine remaining args so Windows PowerShell (-File) invocations like:
# Sync-AgentAssets.ps1 GitHub CoPilot
# work the same as:
# Sync-AgentAssets.ps1 "GitHub CoPilot"
if ($args.Count -gt 0) {
$ModeFromArgs = ($args -join ' ')
if (-not $Mode -or $Mode -eq '') {
$Mode = $ModeFromArgs
}
}
# Allow calling via the old filename (if invoked through a copied/renamed script).
# This only affects displayed script name in prompts/logs.
$scriptName = [System.IO.Path]::GetFileName($MyInvocation.MyCommand.Path)
# Strict mode
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
function Ensure-Directory {
param(
[Parameter(Mandatory = $true)]
[string]$Path
)
if (-not (Test-Path -Path $Path -PathType Container)) {
New-Item -ItemType Directory -Force -Path $Path | Out-Null
}
}
# Function to check if instruction files exist in a directory
function Test-HasInstructionFiles {
param(
[Parameter(Mandatory = $true)]
[string]$Path,
[string]$Filter = '*instructions.md'
)
if (Test-Path $Path -PathType Container) {
$files = @(Get-ChildItem $Path -Filter $Filter -File -ErrorAction SilentlyContinue)
return ($files.Length -gt 0)
}
return $false
}
# Function to pause at the end (unless -NoWait is specified)
function Invoke-Pause {
Write-Host "Pausing for 2 seconds..."
Start-Sleep -Seconds 2
}
function Copy-FileIfDifferent {
param(
[Parameter(Mandatory = $true)]
[string]$SourcePath,
[Parameter(Mandatory = $true)]
[string]$TargetPath
)
$targetDir = Split-Path -Path $TargetPath -Parent
Ensure-Directory -Path $targetDir
if (-not (Test-Path -Path $TargetPath -PathType Leaf)) {
Copy-Item -LiteralPath $SourcePath -Destination $TargetPath -Force
$relative = $TargetPath.Substring($repoRoot.Length + 1)
Write-Host "Created: $relative"
return
}
$srcBytes = [System.IO.File]::ReadAllBytes($SourcePath)
$dstBytes = [System.IO.File]::ReadAllBytes($TargetPath)
if ($srcBytes.Length -eq $dstBytes.Length) {
$same = $true
for ($i = 0; $i -lt $srcBytes.Length; $i++) {
if ($srcBytes[$i] -ne $dstBytes[$i]) { $same = $false; break }
}
if ($same) {
$relative = $TargetPath.Substring($repoRoot.Length + 1)
Write-Host "Up-to-date: $relative"
return
}
}
Copy-Item -LiteralPath $SourcePath -Destination $TargetPath -Force
$relative = $TargetPath.Substring($repoRoot.Length + 1)
Write-Host "Updated: $relative"
}
function Get-TextAuto {
param(
[Parameter(Mandatory = $true)]
[string]$Path
)
# .NET StreamReader detects BOM for UTF-8/UTF-16/UTF-32 automatically.
$sr = New-Object System.IO.StreamReader($Path, $true)
try {
return $sr.ReadToEnd()
}
finally {
$sr.Dispose()
}
}
function Write-Utf8NoBom {
param(
[Parameter(Mandatory = $true)]
[string]$Path,
[Parameter(Mandatory = $true)]
[string]$Content
)
$dir = Split-Path -Path $Path -Parent
Ensure-Directory -Path $dir
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
[System.IO.File]::WriteAllText($Path, $Content, $utf8NoBom)
}
function Assert-InstructionSync {
param(
[Parameter(Mandatory = $true)]
[string]$SourceDirectory,
[Parameter(Mandatory = $true)]
[string]$TargetDirectory,
[Parameter(Mandatory = $true)]
[System.IO.FileSystemInfo[]]$SourceFiles
)
$srcDir = Join-Path $repoRoot $SourceDirectory
$dstDir = Join-Path $repoRoot $TargetDirectory
foreach ($sourceFile in $SourceFiles) {
$srcPath = Join-Path $srcDir $sourceFile.Name
$dstPath = Join-Path $dstDir $sourceFile.Name
if (-not (Test-Path $dstPath -PathType Leaf)) {
throw "Binary comparison failed. Destination file missing: $dstPath"
}
$srcBytes = [System.IO.File]::ReadAllBytes($srcPath)
$dstBytes = [System.IO.File]::ReadAllBytes($dstPath)
if ($srcBytes.Length -ne $dstBytes.Length) {
throw "Binary comparison failed. Source and target size mismatch in binary: Source: $srcPath Target: $dstPath"
}
for ($i = 0; $i -lt $srcBytes.Length; $i++) {
if ($srcBytes[$i] -ne $dstBytes[$i]) {
throw "Binary comparison failed. Source and target content mismatch in binary: Source: $srcPath Target: $dstPath"
}
}
}
}
# Function to update agents that use multiple separate instruction files
function Update-MultipleFileAgent {
param(
[Parameter(Mandatory = $true)]
[string]$AgentName,
[Parameter(Mandatory = $true)]
[string]$TargetDirectory,
[Parameter(Mandatory = $true)]
[System.IO.FileSystemInfo[]]$SourceFiles,
[Parameter(Mandatory = $true)]
[string]$RepoRoot
)
Write-Host "`r`n--- Updating $AgentName Instructions ---"
$targetDir = Join-Path $RepoRoot $TargetDirectory
foreach ($sourceFile in $SourceFiles) {
$targetFile = Join-Path $targetDir $sourceFile.Name
Copy-FileIfDifferent -SourcePath $sourceFile.FullName -TargetPath $targetFile
}
Assert-InstructionSync -SourceDirectory ".ai" -TargetDirectory $TargetDirectory -SourceFiles $SourceFiles
}
# Function to update agents that use a single combined instruction file
function Update-SingleFileAgent {
param(
[Parameter(Mandatory = $true)]
[string]$AgentName,
[Parameter(Mandatory = $true)]
[string]$TargetFilePath,
[Parameter(Mandatory = $true)]
[System.IO.FileSystemInfo[]]$SourceFiles,
[Parameter(Mandatory = $true)]
[string]$RepoRoot
)
Write-Host "`r`n--- Updating $AgentName Instructions ---"
$targetFile = Join-Path $RepoRoot $TargetFilePath
$relativeTarget = $targetFile.Substring($repoRoot.Length + 1)
$allInstructionsContent = New-Object System.Text.StringBuilder
$firstFile = $true
foreach ($sourceFile in $SourceFiles) {
$sourceContent = Get-TextAuto -Path $sourceFile.FullName
if ([string]::IsNullOrWhiteSpace($sourceContent)) {
Write-Warning "Skipping empty file: $($sourceFile.Name)"
continue
}
if (-not $firstFile) {
[void]$allInstructionsContent.AppendLine("")
}
[void]$allInstructionsContent.AppendLine("==== START OF INSTRUCTIONS FROM: $($sourceFile.Name) ====")
[void]$allInstructionsContent.AppendLine("")
[void]$allInstructionsContent.AppendLine("# Instructions from: $($sourceFile.Name)")
[void]$allInstructionsContent.AppendLine("")
[void]$allInstructionsContent.AppendLine($sourceContent.Trim())
[void]$allInstructionsContent.AppendLine("")
[void]$allInstructionsContent.AppendLine("==== END OF INSTRUCTIONS FROM: $($sourceFile.Name) ====")
$firstFile = $false
}
$finalContent = $allInstructionsContent.ToString()
$existing = if (Test-Path -Path $targetFile -PathType Leaf) { Get-TextAuto -Path $targetFile } else { $null }
if ($null -ne $existing -and $existing -eq $finalContent) {
Write-Host "Up-to-date: $relativeTarget"
return
}
Write-Utf8NoBom -Path $targetFile -Content $finalContent
Write-Host "Updated: $relativeTarget"
}
function Invoke-RoboCopyMirror {
param(
[Parameter(Mandatory = $true)]
[string]$SourceDirectory,
[Parameter(Mandatory = $true)]
[string]$DestinationDirectory,
[Parameter(Mandatory = $true)]
[string]$Label
)
if (-not (Test-Path $SourceDirectory -PathType Container)) {
Write-Host "No skills folder found at: $SourceDirectory"
return
}
Ensure-Directory -Path $DestinationDirectory
Write-Host "`r`n--- Mirroring skills to $Label ---"
Write-Host "Source: $SourceDirectory"
Write-Host "Destination: $DestinationDirectory"
# /MIR = mirror (copy + delete removed)
# /FFT = tolerate 2s timestamp granularity
# /R:1 /W:1 = retry quickly
# /NFL/NDL = no file/dir listing (keep output compact)
# /NJH/NJS = no job header/summary
# /NP = no progress
# /XD = exclude version control/build dirs
$excludedDirs = @('.git', '.vs', 'bin', 'obj')
$args = @(
$SourceDirectory,
$DestinationDirectory,
'/MIR',
'/FFT',
'/R:1',
'/W:1',
'/NFL',
'/NDL',
'/NJH',
'/NJS',
'/NP'
)
foreach ($d in $excludedDirs) {
$args += '/XD'
$args += $d
}
$exe = 'robocopy'
# Do not echo the full robocopy command; it is noisy and can wrap in some terminals.
Write-Host "robocopy <source> <destination> /MIR /NFL /NDL /NJH /NJS /NP ..."
& $exe @args | Out-Null
$exitCode = $LASTEXITCODE
# Robocopy uses bitmask exit codes.
# 0-7 are success with various flags; >= 8 indicates failure.
if ($exitCode -ge 8) {
throw "Robocopy failed with exit code $exitCode. Command: $cmd"
}
# IMPORTANT: robocopy returns 1+ for successful copies.
# Ensure PowerShell script does not propagate a non-zero exit code for success cases.
$global:LASTEXITCODE = 0
Write-Host "Mirrored skills to $Label (robocopy exit code $exitCode)."
}
function Sync-SkillsToRoo {
param(
[Parameter(Mandatory = $true)]
[string]$RepoRoot
)
$srcSkillsRoot = Join-Path $RepoRoot ".ai\skills"
$rooSkillsRoot = Join-Path $RepoRoot ".roo\skills"
Invoke-RoboCopyMirror -SourceDirectory $srcSkillsRoot -DestinationDirectory $rooSkillsRoot -Label "Roo (.roo\\skills)"
}
function Sync-SkillsToGitHub {
param(
[Parameter(Mandatory = $true)]
[string]$RepoRoot
)
$srcSkillsRoot = Join-Path $RepoRoot ".ai\skills"
$githubSkillsRoot = Join-Path $RepoRoot ".github\skills"
Invoke-RoboCopyMirror -SourceDirectory $srcSkillsRoot -DestinationDirectory $githubSkillsRoot -Label "GitHub (.github\\skills)"
}
function Sync-SkillsToClaude {
param(
[Parameter(Mandatory = $true)]
[string]$RepoRoot
)
$srcSkillsRoot = Join-Path $RepoRoot ".ai\skills"
$claudeSkillsRoot = Join-Path $RepoRoot ".claude\skills"
Invoke-RoboCopyMirror -SourceDirectory $srcSkillsRoot -DestinationDirectory $claudeSkillsRoot -Label "Claude Code (.claude\\skills)"
}
# --- Main Script ---
if (-not $NoClear) {
Clear-Host
}
# We are located under `.ai/skills/<skill>/tools`. Find repo root by going up 4 levels.
$scriptDir = $PSScriptRoot
$repoRoot = (Join-Path -Path $scriptDir -ChildPath "..\..\..\.." | Resolve-Path).Path
# `.ai` folder path
$aiDir = Join-Path $repoRoot ".ai"
# Discover source files matching *instructions.md in the .ai folder
[System.IO.FileSystemInfo[]]$sourceInstructionFiles = Get-ChildItem -Path $aiDir -Filter "*instructions.md" -File | Sort-Object Name
if ($null -eq $sourceInstructionFiles -or $sourceInstructionFiles.Length -eq 0) {
Write-Warning "No '*instructions.md' files found in '$aiDir'. Nothing to process."
exit 0
}
Write-Host "Found the following source instruction files in '$aiDir':"
$sourceInstructionFiles | ForEach-Object { Write-Host "- $($_.Name)" }
# Mode parameter handling: if 'ALL' or 'AUTO', skip interactive prompt
if ($Mode -eq 'ALL') {
Write-Host "Selected: ALL (parameter mode)"
$updateCline = $true
$updateCopilot = $true
$updateRooCode = $true
$updateCodex = $true
$updateClaude = $true
}
elseif ($Mode -eq 'AUTO') {
Write-Host "Selected: AUTO (parameter mode)"
# Determine available agents based on instruction files
$updateCline = Test-HasInstructionFiles -Path (Join-Path $repoRoot '.clinerules')
$updateRooCode = Test-HasInstructionFiles -Path (Join-Path $repoRoot '.roo\rules')
$updateCopilot = Test-Path (Join-Path $repoRoot '.github\copilot-instructions.md') -PathType Leaf
$updateCodex = Test-Path (Join-Path $repoRoot 'AGENTS.md') -PathType Leaf
$updateClaude = Test-HasInstructionFiles -Path (Join-Path $repoRoot '.claude')
Write-Host "Agents to update based on available instruction files:"
if ($updateCline) { Write-Host "- CLINE" }
if ($updateRooCode) { Write-Host "- ROO CODE" }
if ($updateCopilot) { Write-Host "- GitHub CoPilot" }
if ($updateCodex) { Write-Host "- OpenAI Codex" }
if ($updateClaude) { Write-Host "- Claude Code" }
}
elseif ($Mode -and $Mode -ne '') {
# Specific agent mode (e.g., CLINE, "ROO CODE", etc.)
$updateCline = ($Mode -eq 'CLINE')
$updateCopilot = ($Mode -eq 'GitHub CoPilot')
$updateRooCode = ($Mode -eq 'ROO CODE')
$updateCodex = ($Mode -eq 'OpenAI Codex')
$updateClaude = ($Mode -eq 'Claude Code')
Write-Host "Selected: $Mode (parameter mode)"
}
else {
# User prompt for agent selection
# Detect available agents for interactive menu
$hasCline = Test-HasInstructionFiles -Path (Join-Path $repoRoot '.clinerules')
$hasRooCode = Test-HasInstructionFiles -Path (Join-Path $repoRoot '.roo\rules')
$hasCopilot = Test-Path (Join-Path $repoRoot '.github\copilot-instructions.md') -PathType Leaf
$hasCodex = Test-Path (Join-Path $repoRoot 'AGENTS.md') -PathType Leaf
$hasClaude = Test-HasInstructionFiles -Path (Join-Path $repoRoot '.claude')
Write-Host "`r`nDetected AI agents with instruction files:"
if ($hasCline) { Write-Host "- CLINE" }
if ($hasRooCode) { Write-Host "- ROO CODE" }
if ($hasCopilot) { Write-Host "- GitHub CoPilot" }
if ($hasCodex) { Write-Host "- OpenAI Codex" }
if ($hasClaude) { Write-Host "- Claude Code" }
Write-Host ""
Write-Host "=============================================================="
Write-Host "Select Agent Instruction Set to Update"
Write-Host "--------------------------------------------------------------"
Write-Host "1. AUTO - Update only agents with instruction files (default)"
Write-Host "2. ALL - Update instructions for all AI agents"
Write-Host "3. CLINE - Update instructions for CLINE"
Write-Host "4. ROO CODE - Update instructions for ROO CODE"
Write-Host "5. GitHub CoPilot - Update instructions for GitHub CoPilot"
Write-Host "6. OpenAI Codex - Update instructions for OpenAI Codex"
Write-Host "7. Claude Code - Update instructions for Claude Code"
Write-Host "0. Exit"
Write-Host "=============================================================="
$selection = Read-Host "Enter the number of your choice (0-7)"
# Initialize flags
$updateCline = $false
$updateCopilot = $false
$updateRooCode = $false
$updateCodex = $false
$updateClaude = $false
switch ($selection) {
'1' {
$updateCline = Test-HasInstructionFiles -Path (Join-Path $repoRoot '.clinerules')
$updateRooCode = Test-HasInstructionFiles -Path (Join-Path $repoRoot '.roo\rules')
$updateCopilot = Test-Path (Join-Path $repoRoot '.github\copilot-instructions.md') -PathType Leaf
$updateCodex = Test-Path (Join-Path $repoRoot 'AGENTS.md') -PathType Leaf
$updateClaude = Test-HasInstructionFiles -Path (Join-Path $repoRoot '.claude')
Write-Host "Selected: AUTO"
}
'2' {
$updateCline = $true
$updateCopilot = $true
$updateRooCode = $true
$updateCodex = $true
$updateClaude = $true
Write-Host "Selected: ALL"
}
'3' { $updateCline = $true; Write-Host "Selected: CLINE" }
'4' { $updateRooCode = $true; Write-Host "Selected: ROO CODE" }
'5' { $updateCopilot = $true; Write-Host "Selected: GitHub CoPilot" }
'6' { $updateCodex = $true; Write-Host "Selected: OpenAI Codex" }
'7' { $updateClaude = $true; Write-Host "Selected: Claude Code" }
'0' { Write-Host "Operation cancelled by user."; exit 0 }
default { throw "Invalid selection. Exiting." }
}
}
# --- Multiple-File Agent Updates ---
if ($updateCline) {
Update-MultipleFileAgent -AgentName "CLINE" -TargetDirectory ".clinerules" -SourceFiles $sourceInstructionFiles -RepoRoot $repoRoot
}
if ($updateRooCode) {
Update-MultipleFileAgent -AgentName "ROO CODE" -TargetDirectory ".roo\rules" -SourceFiles $sourceInstructionFiles -RepoRoot $repoRoot
}
# --- Single-File Agent Updates ---
if ($updateCopilot) {
$copilotTarget = ".github\copilot-instructions.md"
$githubInstructionsDir = Join-Path $repoRoot ".github\instructions"
if (Test-Path $githubInstructionsDir -PathType Container) {
Write-Host "`r`n--- Updating GitHub CoPilot Instructions (folder-based) ---"
$mainName = "instructions.md"
$mainSource = $sourceInstructionFiles | Where-Object { $_.Name -ieq $mainName } | Select-Object -First 1
if ($null -eq $mainSource) {
throw "Expected source '$mainName' under .ai but none found."
}
Copy-FileIfDifferent -SourcePath $mainSource.FullName -TargetPath (Join-Path $repoRoot $copilotTarget)
foreach ($sf in $sourceInstructionFiles) {
if ($sf.Name -ieq $mainName) {
continue
}
$destination = Join-Path $githubInstructionsDir $sf.Name
Copy-FileIfDifferent -SourcePath $sf.FullName -TargetPath $destination
}
}
else {
Update-SingleFileAgent -AgentName "GitHub CoPilot" -TargetFilePath $copilotTarget -SourceFiles $sourceInstructionFiles -RepoRoot $repoRoot
}
}
if ($updateCodex) {
Update-SingleFileAgent -AgentName "OpenAI Codex" -TargetFilePath "AGENTS.md" -SourceFiles $sourceInstructionFiles -RepoRoot $repoRoot
}
# --- Claude Code (multiple-file agent) ---
if ($updateClaude) {
Update-MultipleFileAgent -AgentName "Claude Code" -TargetDirectory ".claude" -SourceFiles $sourceInstructionFiles -RepoRoot $repoRoot
}
# --- Skills mirroring ---
if ($updateRooCode -or $Mode -eq 'ALL' -or $Mode -eq 'AUTO') {
Sync-SkillsToRoo -RepoRoot $repoRoot
}
# GitHub Copilot: mirror skills to `.github/skills` (Copilot tries to load from there).
if ($updateCopilot -or $Mode -eq 'ALL' -or $Mode -eq 'AUTO') {
Sync-SkillsToGitHub -RepoRoot $repoRoot
}
# Claude Code: mirror skills to `.claude/skills`.
if ($updateClaude -or $Mode -eq 'ALL' -or $Mode -eq 'AUTO') {
Sync-SkillsToClaude -RepoRoot $repoRoot
}
Write-Host "`r`nAll selected operations completed successfully."
# Only pause when launched by double-click (Explorer). In CI / terminal usage, do not pause.
if ($Host.Name -and $Host.Name -notlike '*ConsoleHost*') {
Invoke-Pause
}
================================================
FILE: .gitignore
================================================
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
/FocusLogger/Resources/BuildDate.txt
/FocusLogger/Documents/Files
/.claude/settings.local.json
Documents/Files
================================================
FILE: .markdownlint.json
================================================
// https://github.com/DavidAnson/markdownlint
{
"default": true,
"MD013": false, // There are lines that are longer than the configured line_length (default: 80 characters).
"MD033": false, // Raw HTML is used in a markdown document.
"MD041": false, // The first line in a document is not a top-level heading.
}
================================================
FILE: Documents/App_1_Sign.ps1
================================================
Import-Module "d:\_Backup\Configuration\SSL\Tools\app_signModule.ps1" -Force
[string[]]$appFiles = @(
"..\FocusLogger\bin\Release\publish\JocysCom.FocusLogger.exe"
)
[string]$appName = "Jocys.com Focus Logger"
[string]$appLink = "https://www.jocys.com"
ProcessFiles $appName $appLink $appFiles
pause
================================================
FILE: Documents/App_2_Zip.ps1
================================================
# Make sure the output directories exist
$filesDir = Join-Path $PSScriptRoot "Files"
$binDir = Join-Path $PSScriptRoot "..\Resources"
$file1="JocysCom.FocusLogger.exe"
if (-not [System.IO.File]::Exists([System.IO.Path]::Combine($filesDir, $file1))){
[System.IO.File]::Copy([System.IO.Path]::Combine($PSScriptRoot, "..\App\bin\Release\publish\", $file1), [System.IO.Path]::Combine($filesDir, $file1))
}
& "$binDir\ZipFiles.ps1" $filesDir "$filesDir\JocysCom.FocusLogger.zip" $file1 $true
================================================
FILE: Documents/Take_Screenshot.ps1
================================================
# Take_Screenshot.ps1
# Launches FocusLogger, Notepad, and Explorer, switches between them
# to generate focus log entries, then captures a screenshot using PrintWindow.
#
# Usage: Right-click > Run with PowerShell, or run from terminal:
# powershell -ExecutionPolicy Bypass -File Take_Screenshot.ps1
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
# Load C# helper class (supports re-running in the same session).
$csFilePath = Join-Path (Split-Path -Parent $MyInvocation.MyCommand.Path) "Take_Screenshot.ps1.cs"
$csFileContent = Get-Content -Path $csFilePath -Raw
$fileHash = Get-FileHash -InputStream ([System.IO.MemoryStream]::new([System.Text.Encoding]::UTF8.GetBytes($csFileContent))) -Algorithm SHA256
$className = "TakeScreenshot"
if (-not $script:loadedClasses) { $script:loadedClasses = @{} }
if (-not $script:loadedClasses.ContainsKey($fileHash.Hash)) {
$className += (Get-Date -Format "yyyyMMddHHmmss")
$csCode = $csFileContent -replace "TakeScreenshot", $className
Add-Type -TypeDefinition $csCode -ReferencedAssemblies System.Windows.Forms, System.Drawing
$script:loadedClasses[$fileHash.Hash] = $className
} else {
$className = $script:loadedClasses[$fileHash.Hash]
}
$helper = [Type]$className
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$screenshotPath = Join-Path $scriptDir "Images\JocysCom.FocusLogger.png"
$tempFile = Join-Path $env:TEMP "My Document.txt"
# Find FocusLogger executable.
$appPath = Join-Path $scriptDir "..\FocusLogger\bin\Debug\net8.0-windows\JocysCom.FocusLogger.exe"
if (-not (Test-Path $appPath)) {
$appPath = Join-Path $scriptDir "..\FocusLogger\bin\Release\net8.0-windows\JocysCom.FocusLogger.exe"
}
if (-not (Test-Path $appPath)) {
Write-Error "FocusLogger not found. Build the project first."
exit 1
}
$appPath = Resolve-Path $appPath
$focusLogger = $null
$notepad = $null
$explorerProc = $null
try {
# 1. Start FocusLogger (centered).
Write-Host "1. Starting FocusLogger..."
$focusLogger = Start-Process -FilePath $appPath -PassThru
Start-Sleep -Seconds 2
$focusLogger.Refresh()
$flHwnd = $focusLogger.MainWindowHandle
$helper::CenterAndResize($flHwnd, 920, 480)
Start-Sleep -Milliseconds 500
# 2. Start Notepad with a document.
Write-Host "2. Starting Notepad..."
"Sample document for Focus Logger screenshot." | Out-File -FilePath $tempFile -Encoding UTF8
$notepadPidsBefore = @(Get-Process -Name "Notepad" -ErrorAction SilentlyContinue | ForEach-Object { $_.Id })
Start-Process -FilePath "notepad.exe" -ArgumentList "`"$tempFile`""
Start-Sleep -Seconds 2
$notepad = Get-Process -Name "Notepad" -ErrorAction SilentlyContinue |
Where-Object { $notepadPidsBefore -notcontains $_.Id -and $_.MainWindowHandle -ne [IntPtr]::Zero } |
Select-Object -First 1
if (-not $notepad) {
$notepad = Get-Process -Name "Notepad" -ErrorAction SilentlyContinue |
Where-Object { $_.MainWindowHandle -ne [IntPtr]::Zero } |
Select-Object -First 1
}
$npHwnd = if ($notepad) { $notepad.MainWindowHandle } else { [IntPtr]::Zero }
if ($npHwnd -ne [IntPtr]::Zero) {
$helper::CenterAndResize($npHwnd, 1040, 680, -50)
$helper::SetForegroundWindow($npHwnd) | Out-Null
Start-Sleep -Milliseconds 500
}
# 3. Start Explorer.
Write-Host "3. Starting Explorer..."
$explorerHwndsBefore = @(Get-Process explorer -ErrorAction SilentlyContinue |
Where-Object { $_.MainWindowHandle -ne [IntPtr]::Zero } |
ForEach-Object { $_.MainWindowHandle })
Start-Process "explorer.exe" -ArgumentList "C:\Windows"
Start-Sleep -Seconds 2
$explorerProc = Get-Process explorer -ErrorAction SilentlyContinue |
Where-Object { $_.MainWindowHandle -ne [IntPtr]::Zero -and $explorerHwndsBefore -notcontains $_.MainWindowHandle } |
Select-Object -First 1
if (-not $explorerProc) {
$explorerProc = Get-Process explorer -ErrorAction SilentlyContinue |
Where-Object { $_.MainWindowTitle -match "Windows" -and $_.MainWindowHandle -ne [IntPtr]::Zero } |
Select-Object -First 1
}
$exHwnd = if ($explorerProc) { $explorerProc.MainWindowHandle } else { [IntPtr]::Zero }
if ($exHwnd -ne [IntPtr]::Zero) {
$helper::CenterAndResize($exHwnd, 700, 400)
$helper::SetForegroundWindow($exHwnd) | Out-Null
Start-Sleep -Milliseconds 500
}
# 4. Switch focus between windows to generate log entries.
Write-Host "4. Switching focus..."
if ($npHwnd -ne [IntPtr]::Zero) { $helper::SetForegroundWindow($npHwnd) | Out-Null; Start-Sleep -Milliseconds 800 }
if ($exHwnd -ne [IntPtr]::Zero) { $helper::SetForegroundWindow($exHwnd) | Out-Null; Start-Sleep -Milliseconds 800 }
if ($npHwnd -ne [IntPtr]::Zero) { $helper::SetForegroundWindow($npHwnd) | Out-Null; Start-Sleep -Milliseconds 800 }
if ($exHwnd -ne [IntPtr]::Zero) { $helper::SetForegroundWindow($exHwnd) | Out-Null; Start-Sleep -Milliseconds 800 }
if ($npHwnd -ne [IntPtr]::Zero) { $helper::SetForegroundWindow($npHwnd) | Out-Null; Start-Sleep -Milliseconds 800 }
# 5. Move Notepad behind FocusLogger as white background, then capture.
Write-Host "5. Taking screenshot..."
if ($npHwnd -ne [IntPtr]::Zero) {
$helper::CenterAndResize($npHwnd, 1040, 680, -50)
$helper::SetForegroundWindow($npHwnd) | Out-Null
Start-Sleep -Milliseconds 300
}
$helper::SetForegroundWindow($flHwnd) | Out-Null
Start-Sleep -Seconds 1
$helper::CaptureWindow($flHwnd, $screenshotPath)
Write-Host " Screenshot saved to: $screenshotPath"
} finally {
Write-Host "6. Cleaning up..."
if ($notepad -and -not $notepad.HasExited) { $notepad.Kill(); $notepad.WaitForExit(3000) | Out-Null }
if ($focusLogger -and -not $focusLogger.HasExited) { $focusLogger.Kill(); $focusLogger.WaitForExit(3000) | Out-Null }
if ($explorerProc -and -not $explorerProc.HasExited) { $explorerProc.CloseMainWindow() | Out-Null }
Start-Sleep -Milliseconds 500
Remove-Item $tempFile -ErrorAction SilentlyContinue
Write-Host "Done."
}
================================================
FILE: Documents/Take_Screenshot.ps1.cs
================================================
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class TakeScreenshot
{
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll")]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
[DllImport("user32.dll")]
public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
[DllImport("user32.dll")]
public static extern bool PrintWindow(IntPtr hWnd, IntPtr hdcBlt, uint nFlags);
[DllImport("dwmapi.dll")]
public static extern int DwmGetWindowAttribute(IntPtr hwnd, int dwAttribute, out RECT pvAttribute, int cbAttribute);
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left, Top, Right, Bottom;
}
public static RECT GetExtendedFrameBounds(IntPtr hwnd)
{
RECT rect;
DwmGetWindowAttribute(hwnd, 9, out rect, Marshal.SizeOf(typeof(RECT)));
return rect;
}
public static void CenterAndResize(IntPtr hwnd, int w, int h, int yOffset = 0)
{
var screen = Screen.PrimaryScreen.WorkingArea;
int x = (screen.Width - w) / 2 + screen.Left;
int y = (screen.Height - h) / 2 + screen.Top + yOffset;
SetWindowPos(hwnd, IntPtr.Zero, x, y, w, h, 0x0040);
}
/// <summary>
/// Captures a window using CopyFromScreen with DWM extended frame bounds
/// for accurate visible area (excludes invisible shadow border).
/// </summary>
public static void CaptureWindow(IntPtr hwnd, string filePath)
{
var rect = GetExtendedFrameBounds(hwnd);
int w = rect.Right - rect.Left;
int h = rect.Bottom - rect.Top;
using (var bitmap = new Bitmap(w, h, PixelFormat.Format32bppArgb))
{
using (var graphics = Graphics.FromImage(bitmap))
{
graphics.CopyFromScreen(rect.Left, rect.Top, 0, 0, new Size(w, h));
}
bitmap.Save(filePath, ImageFormat.Png);
}
}
}
================================================
FILE: FocusLogger/App.xaml
================================================
<Application
x:Class="JocysCom.FocusLogger.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:JocysCom.FocusLogger"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="JocysCom/Controls/Themes/Default.xaml" />
<ResourceDictionary Source="JocysCom/Controls/Themes/Icons.xaml" />
<ResourceDictionary Source="Resources/Icons/Icons_Default.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
================================================
FILE: FocusLogger/App.xaml.cs
================================================
using System;
using System.Windows;
namespace JocysCom.FocusLogger
{
public partial class App : Application
{
public App()
{
SetDPIAware();
}
internal class NativeMethods
{
[System.Runtime.InteropServices.DllImport("user32.dll")]
internal static extern bool SetProcessDPIAware();
}
public static void SetDPIAware()
{
// DPI aware property must be set before application window is created.
if (Environment.OSVersion.Version.Major >= 6)
NativeMethods.SetProcessDPIAware();
}
}
}
================================================
FILE: FocusLogger/AssemblyInfo.cs
================================================
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]
================================================
FILE: FocusLogger/Common/DataItem.cs
================================================
using JocysCom.ClassLibrary.Configuration;
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace JocysCom.FocusLogger
{
public class DataItem : SettingsItem
{
public DateTime Date { get => _Date; set => SetProperty(ref _Date, value); }
DateTime _Date;
public int ProcessId { get => _ProcessId; set => SetProperty(ref _ProcessId, value); }
int _ProcessId;
public string ProcessName { get => _ProcessName; set => SetProperty(ref _ProcessName, value); }
string _ProcessName;
public string ProcessPath { get => _ProcessPath; set => SetProperty(ref _ProcessPath, value); }
string _ProcessPath;
public string WindowTitle { get => _WindowTitle; set => SetProperty(ref _WindowTitle, value); }
string _WindowTitle;
public string WindowClassName { get => _WindowClassName; set => SetProperty(ref _WindowClassName, value); }
string _WindowClassName;
public bool HasMouse { get => _HasMouse; set => SetProperty(ref _HasMouse, value); }
bool _HasMouse;
public bool HasKeyboard { get => _HasKeyboard; set => SetProperty(ref _HasKeyboard, value); }
bool _HasKeyboard;
public bool HasCaret { get => _HasCaret; set => SetProperty(ref _HasCaret, value); }
bool _HasCaret;
public bool IsActive { get => _IsActive; set => SetProperty(ref _IsActive, value); }
bool _IsActive;
public bool NonPath { get => _IsError; set => SetProperty(ref _IsError, value); }
bool _IsError;
public bool IsSame(DataItem item)
{
return
item.ProcessId == ProcessId &&
item.HasMouse == HasMouse &&
item.HasKeyboard == HasKeyboard &&
item.HasCaret == HasCaret &&
item.IsActive == IsActive;
}
public System.Windows.MessageBoxImage StatusCode { get => _StatusCode; set => SetProperty(ref _StatusCode, value); }
System.Windows.MessageBoxImage _StatusCode;
}
}
================================================
FILE: FocusLogger/Common/DataItemType.cs
================================================
namespace JocysCom.FocusLogger
{
public enum DataItemType
{
None = 0,
}
}
================================================
FILE: FocusLogger/Common/NativeMethods.cs
================================================
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace JocysCom.FocusLogger
{
internal class NativeMethods
{
// https://docs.microsoft.com/en-gb/windows/win32/api/winuser/
/// <summary>
/// Get handle to the window with the keyboard focus.
/// </summary>
/// <returns>
/// The return value is the handle to the window with the keyboard focus.
/// If the calling thread's message queue does not have an associated window with the keyboard focus, the return value is NULL.
/// </returns>
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
internal static extern IntPtr GetFocus();
/// <summary>
/// Window which is active.
/// </summary>
/// <returns>
/// The return value is the handle to the active window attached to the calling thread's message queue.
/// Otherwise, the return value is NULL.
/// </returns>
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
internal static extern IntPtr GetActiveWindow();
/// <summary>
/// Get handle to the child window at the top of the Z order.
/// </summary>
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
internal static extern IntPtr GetTopWindow(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
internal static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
internal static extern int GetWindowTextLengthW(IntPtr hWnd);
/// <summary>
/// Copies the text of the specified window's title bar (if it has one) into a buffer.
/// </summary>
/// <param name="hWnd">A handle to the window or control containing the text.</param>
/// <param name="lpString">The buffer that will receive the text.</param>
/// <param name="nMaxCount">The maximum number of characters to copy to the buffer, including the null character.</param>
/// <returns>If the function succeeds, the return value is the length, in characters, of the copied string, not including the terminating null character.</returns>
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
/// <summary>
/// A handle to the window that will receive the keyboard input.
/// </summary>
/// <returns>
/// The return value is a handle to the foreground window.
/// The foreground window can be NULL in certain circumstances, such as when a window is losing activation.
/// </returns>
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
internal static extern IntPtr GetForegroundWindow();
/// <summary>
/// Retrieves the identifier of the thread that created the specified window and,
/// optionally, the identifier of the process that created the window.
/// </summary>
/// <param name="hWnd">A handle to the window.</param>
/// <param name="lpdwProcessId">A pointer to a variable that receives the process identifier</param>
/// <returns>Identifier of the thread that created the window.</returns>
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
/// <summary>
/// Retrieves information about the active window or a specified GUI thread.
/// </summary>
/// <param name="idThread">
/// The identifier for the thread for which information is to be retrieved.
/// To retrieve this value, use the GetWindowThreadProcessId function.
/// If this parameter is NULL, the function returns information for the foreground thread.
/// </param>
/// <param name="pgui">
/// A pointer to a GUITHREADINFO structure that receives information describing the thread.
/// </param>
/// <returns>If the function succeeds, the return value is nonzero.</returns>
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern bool GetGUIThreadInfo(int idThread, ref GUITHREADINFO pgui);
[StructLayout(LayoutKind.Sequential)]
internal struct RECT
{
public int iLeft;
public int iTop;
public int iRight;
public int iBottom;
}
[Flags]
internal enum GUI
{
/// <summary>The caret's blink state. This bit is set if the caret is visible.</summary>
GUI_CARETBLINKING = 0x00000001,
/// <summary>The thread's menu state. This bit is set if the thread is in menu mode.</summary>
GUI_INMENUMODE = 0x00000004,
/// <summary>The thread's move state. This bit is set if the thread is in a move or size loop.</summary>
GUI_INMOVESIZE = 0x00000002,
/// <summary>The thread's pop-up menu state. This bit is set if the thread has an active pop-up menu.</summary>
GUI_POPUPMENUMODE = 0x00000010,
/// <summary>The thread's system menu state. This bit is set if the thread is in a system menu mode.</summary>
GUI_SYSTEMMENUMODE = 0x00000008,
}
[StructLayout(LayoutKind.Sequential)]
internal struct GUITHREADINFO
{
/// <summary>The size of this structure, in bytes.</summary>
public int cbSize;
/// <summary>The thread state.</summary>
public GUI flags;
/// <summary>A handle to the active window within the thread.</summary>
public IntPtr hwndActive;
/// <summary>A handle to the window that has the keyboard focus.</summary>
public IntPtr hwndFocus;
/// <summary>A handle to the window that has captured the mouse.</summary>
public IntPtr hwndCapture;
/// <summary>A handle to the window that owns any active menus.</summary>
public IntPtr hwndMenuOwner;
/// <summary>A handle to the window in a move or size loop.</summary>
public IntPtr hwndMoveSize;
/// <summary>A handle to the window that is displaying the caret.</summary>
public IntPtr hwndCaret;
/// <summary>The caret's bounding rectangle, in client coordinates, relative to the window specified by the hwndCaret member.</summary>
public RECT rectCaret;
}
internal static GUITHREADINFO? GetInfo(IntPtr hWnd)
{
int lpdwProcessId;
int threadId = GetWindowThreadProcessId(hWnd, out lpdwProcessId);
var pgui = new GUITHREADINFO();
pgui.cbSize = Marshal.SizeOf(pgui);
if (GetGUIThreadInfo(threadId, ref pgui))
return pgui;
return null;
}
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
internal static string GetWindowClassName(IntPtr hWnd)
{
var sb = new StringBuilder(256);
var length = GetClassName(hWnd, sb, sb.Capacity);
return length > 0 ? sb.ToString() : "";
}
internal static string GetWindowText(IntPtr hWnd)
{
int textLength = GetWindowTextLengthW(hWnd);
var lpString = new StringBuilder(textLength + 1);
var length = GetWindowText(hWnd, lpString, lpString.Capacity);
return lpString.ToString();
}
}
}
================================================
FILE: FocusLogger/Controls/DataListControl.xaml
================================================
<UserControl
x:Class=" JocysCom.FocusLogger.Controls.DataListControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="clr-namespace:JocysCom.ClassLibrary.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="This"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
d:DesignHeight="270"
d:DesignWidth="480"
Loaded="UserControl_Loaded"
Unloaded="UserControl_Unloaded"
mc:Ignorable="d">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel
Grid.Row="0"
Margin="3,3,0,0"
HorizontalAlignment="Left"
Orientation="Horizontal">
<Button
Name="SaveCsvButton"
Margin="2"
Padding="3,0,3,0"
AutomationProperties.AutomationId="SaveCsvButton"
Background="Transparent"
Click="SaveCsvButton_Click">
<StackPanel Style="{StaticResource ButtonStackPanel}">
<ContentControl Content="{StaticResource Icon_Save}" Focusable="False" />
<Label Content="Save CSV" Style="{StaticResource ButtonLabel}" />
</StackPanel>
</Button>
<Button
Name="ExploreCsvButton"
Margin="2"
Padding="3,0,3,0"
AutomationProperties.AutomationId="ExploreCsvButton"
Background="Transparent"
Click="ExploreCsvButton_Click">
<StackPanel Style="{StaticResource ButtonStackPanel}">
<ContentControl Content="{StaticResource Icon_FolderOpen}" Focusable="False" />
<Label Content="Explore" Style="{StaticResource ButtonLabel}" />
</StackPanel>
</Button>
<Button
Name="CopyAiPromptButton"
Margin="2"
Padding="3,0,3,0"
AutomationProperties.AutomationId="CopyAiPromptButton"
Background="Transparent"
Click="CopyAiPromptButton_Click">
<StackPanel Style="{StaticResource ButtonStackPanel}">
<ContentControl Content="{StaticResource Icon_list}" Focusable="False" />
<Label Content="AI Prompt Example" Style="{StaticResource ButtonLabel}" />
</StackPanel>
</Button>
</StackPanel>
<ToolBarPanel Grid.Row="0" HorizontalAlignment="Right" Style="{StaticResource MainToolBarPanel}">
<Button
Name="ClearButton"
Margin="2"
Padding="3,0,3,0"
AutomationProperties.AutomationId="ClearButton"
Background="Transparent"
Click="ClearButton_Click">
<StackPanel Style="{StaticResource ButtonStackPanel}">
<ContentControl Content="{StaticResource Icon_Delete}" Focusable="False" />
<Label Content="Clear" Style="{StaticResource ButtonLabel}" />
</StackPanel>
</Button>
</ToolBarPanel>
<DataGrid
x:Name="MainDataGrid"
Grid.Row="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
x:FieldModifier="public"
AutoGenerateColumns="False"
AutomationProperties.AutomationId="MainDataGrid"
Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
BorderThickness="0"
IsReadOnly="True"
ScrollViewer.CanContentScroll="True"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
SelectionMode="Extended">
<DataGrid.Resources>
<converters:ItemFormattingConverter x:Key="_MainDataGridFormattingConverter" />
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn
x:Name="DateColumn"
Width="Auto"
EditingElementStyle="{StaticResource TextBlockCell}"
ElementStyle="{StaticResource TextBlockCell}"
Header="Date">
<DataGridTextColumn.Binding>
<MultiBinding Converter="{StaticResource _MainDataGridFormattingConverter}">
<Binding RelativeSource="{RelativeSource Self}" />
<Binding RelativeSource="{RelativeSource TemplatedParent}" />
<Binding Path="Date" />
</MultiBinding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
<DataGridTextColumn
x:Name="ProcessIdColumn"
Binding="{Binding ProcessId}"
EditingElementStyle="{StaticResource TextBoxCell}"
ElementStyle="{StaticResource TextBlockRightCell}"
Header="PID" />
<DataGridTextColumn
x:Name="ProcessNameColumn"
Binding="{Binding ProcessName}"
EditingElementStyle="{StaticResource TextBoxCell}"
ElementStyle="{StaticResource TextBlockCell}"
Header="Process Name" />
<DataGridTemplateColumn
x:Name="IsActiveImageColumn"
Width="SizeToCells"
CanUserResize="False"
IsReadOnly="True">
<DataGridTemplateColumn.Header>
<TextBlock HorizontalAlignment="Center" Text="A" ToolTip="Window is Active" />
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentControl x:Name="IsActiveIcon" Width="12" Height="12">
<ContentControl.Content>
<MultiBinding Converter="{StaticResource _MainDataGridFormattingConverter}">
<Binding RelativeSource="{RelativeSource Self}" />
<Binding RelativeSource="{RelativeSource TemplatedParent}" />
<Binding Path="IsActive" />
</MultiBinding>
</ContentControl.Content>
</ContentControl>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn
x:Name="IsActiveColumn"
EditingElementStyle="{StaticResource TextBoxCell}"
ElementStyle="{StaticResource TextBlockCell}"
Header="Active"
Visibility="Collapsed">
<DataGridTextColumn.Binding>
<MultiBinding Converter="{StaticResource _MainDataGridFormattingConverter}">
<Binding RelativeSource="{RelativeSource Self}" />
<Binding RelativeSource="{RelativeSource TemplatedParent}" />
<Binding Path="IsActive" />
</MultiBinding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
<DataGridTemplateColumn
x:Name="HasMouseImageColumn"
Width="SizeToCells"
CanUserResize="False"
IsReadOnly="True">
<DataGridTemplateColumn.Header>
<TextBlock HorizontalAlignment="Center" Text="M" ToolTip="Window has Mouse" />
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentControl x:Name="HasMouseIcon" Width="12" Height="12">
<ContentControl.Content>
<MultiBinding Converter="{StaticResource _MainDataGridFormattingConverter}">
<Binding RelativeSource="{RelativeSource Self}" />
<Binding RelativeSource="{RelativeSource TemplatedParent}" />
<Binding Path="HasMouse" />
</MultiBinding>
</ContentControl.Content>
</ContentControl>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn
x:Name="HasMouseColumn"
EditingElementStyle="{StaticResource TextBoxCell}"
ElementStyle="{StaticResource TextBlockCell}"
Header="Mouse"
Visibility="Collapsed">
<DataGridTextColumn.Binding>
<MultiBinding Converter="{StaticResource _MainDataGridFormattingConverter}">
<Binding RelativeSource="{RelativeSource Self}" />
<Binding RelativeSource="{RelativeSource TemplatedParent}" />
<Binding Path="HasMouse" />
</MultiBinding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
<DataGridTemplateColumn
x:Name="HasKeyboardImageColumn"
Width="SizeToCells"
CanUserResize="False"
IsReadOnly="True">
<DataGridTemplateColumn.Header>
<TextBlock Text="K" ToolTip="Window has Keyboard" />
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentControl x:Name="HasKeyboardIcon" Width="12" Height="12">
<ContentControl.Content>
<MultiBinding Converter="{StaticResource _MainDataGridFormattingConverter}">
<Binding RelativeSource="{RelativeSource Self}" />
<Binding RelativeSource="{RelativeSource TemplatedParent}" />
<Binding Path="HasKeyboard" />
</MultiBinding>
</ContentControl.Content>
</ContentControl>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn
x:Name="HasKeyboardColumn"
EditingElementStyle="{StaticResource TextBoxCell}"
ElementStyle="{StaticResource TextBlockCell}"
Header="Keyboard"
Visibility="Collapsed">
<DataGridTextColumn.Binding>
<MultiBinding Converter="{StaticResource _MainDataGridFormattingConverter}">
<Binding RelativeSource="{RelativeSource Self}" />
<Binding RelativeSource="{RelativeSource TemplatedParent}" />
<Binding Path="HasKeyboard" />
</MultiBinding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
<DataGridTemplateColumn
x:Name="HasCaretImageColumn"
Width="SizeToCells"
CanUserResize="False"
IsReadOnly="True">
<DataGridTemplateColumn.Header>
<TextBlock HorizontalAlignment="Center" Text="C" ToolTip="Window has Caret" />
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentControl x:Name="HasCaretIcon" Width="12" Height="12">
<ContentControl.Content>
<MultiBinding Converter="{StaticResource _MainDataGridFormattingConverter}">
<Binding RelativeSource="{RelativeSource Self}" />
<Binding RelativeSource="{RelativeSource TemplatedParent}" />
<Binding Path="HasCaret" />
</MultiBinding>
</ContentControl.Content>
</ContentControl>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn
x:Name="HasCaretColumn"
EditingElementStyle="{StaticResource TextBoxCell}"
ElementStyle="{StaticResource TextBlockCell}"
Header="Caret"
Visibility="Collapsed">
<DataGridTextColumn.Binding>
<MultiBinding Converter="{StaticResource _MainDataGridFormattingConverter}">
<Binding RelativeSource="{RelativeSource Self}" />
<Binding RelativeSource="{RelativeSource TemplatedParent}" />
<Binding Path="HasCaret" />
</MultiBinding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
<DataGridTextColumn
x:Name="WindowTiteColumn"
Binding="{Binding WindowTitle}"
EditingElementStyle="{StaticResource TextBoxCell}"
ElementStyle="{StaticResource TextBlockCell}"
Header="Window Title" />
<DataGridTextColumn
x:Name="ProcessPathColumn"
gitextract_pkit4a3b/ ├── .ai/ │ ├── ReadMe.md │ ├── coding-guideline.instructions.md │ ├── instructions.md │ ├── repository-analysis.instructions.md │ └── skills/ │ └── ai-self-improvement/ │ ├── SKILL.md │ └── scripts/ │ └── Sync-AgentAssets.ps1 ├── .claude/ │ ├── coding-guideline.instructions.md │ ├── instructions.md │ ├── repository-analysis.instructions.md │ └── skills/ │ └── ai-self-improvement/ │ ├── SKILL.md │ └── scripts/ │ └── Sync-AgentAssets.ps1 ├── .editorconfig ├── .github/ │ ├── copilot-instructions.md │ ├── instructions/ │ │ ├── coding-guideline.instructions.md │ │ └── repository-analysis.instructions.md │ └── skills/ │ └── ai-self-improvement/ │ ├── SKILL.md │ └── scripts/ │ └── Sync-AgentAssets.ps1 ├── .gitignore ├── .markdownlint.json ├── Documents/ │ ├── App_1_Sign.ps1 │ ├── App_2_Zip.ps1 │ ├── Take_Screenshot.ps1 │ └── Take_Screenshot.ps1.cs ├── FocusLogger/ │ ├── App.xaml │ ├── App.xaml.cs │ ├── AssemblyInfo.cs │ ├── Common/ │ │ ├── DataItem.cs │ │ ├── DataItemType.cs │ │ └── NativeMethods.cs │ ├── Controls/ │ │ ├── DataListControl.xaml │ │ └── DataListControl.xaml.cs │ ├── JocysCom/ │ │ ├── Collections/ │ │ │ └── CollectionsHelper.cs │ │ ├── Common/ │ │ │ └── Helper.cs │ │ ├── ComponentModel/ │ │ │ ├── BindingListInvoked.cs │ │ │ ├── NotifyPropertyChanged.cs │ │ │ ├── PropertyComparer.cs │ │ │ └── SortableBindingList.cs │ │ ├── Configuration/ │ │ │ ├── Arguments.cs │ │ │ ├── AssemblyInfo.cs │ │ │ ├── ISettingsData.cs │ │ │ ├── ISettingsFileItem.cs │ │ │ ├── ISettingsItem.cs │ │ │ ├── ISettingsListFileItem.cs │ │ │ ├── SettingsData.cs │ │ │ ├── SettingsHelper.cs │ │ │ ├── SettingsItem.cs │ │ │ └── SettingsParser.cs │ │ ├── Controls/ │ │ │ ├── ControlsHelper.WPF.UseWindowsForms.cs │ │ │ ├── ControlsHelper.WPF.cs │ │ │ ├── ControlsHelper.cs │ │ │ ├── InfoControl.xaml │ │ │ ├── InfoControl.xaml.cs │ │ │ ├── InfoHelpProvider.cs │ │ │ ├── InitHelper.cs │ │ │ ├── ItemFormattingConverter.cs │ │ │ ├── MessageBoxWindow.xaml │ │ │ ├── MessageBoxWindow.xaml.cs │ │ │ ├── TabIndexConverter.cs │ │ │ ├── Themes/ │ │ │ │ ├── Convert_SVG_to_XAML.ps1 │ │ │ │ ├── Default.xaml │ │ │ │ ├── Default_MultiReplace.ps1 │ │ │ │ ├── Icons.xaml │ │ │ │ └── Icons.xaml.cs │ │ │ └── ToolStripBorderlessRenderer.cs │ │ ├── Data/ │ │ │ └── SqlHelper.Types.cs │ │ ├── IO/ │ │ │ └── PathHelper.cs │ │ ├── MakeLinks_Ref.ps1 │ │ ├── Runtime/ │ │ │ ├── RuntimeHelper.cs │ │ │ └── Serializer.cs │ │ └── Text/ │ │ └── Helper.cs │ ├── JocysCom.FocusLogger.csproj │ ├── MainWindow.xaml │ ├── MainWindow.xaml.cs │ └── Resources/ │ ├── AiAnalysisPrompt.md │ └── Icons/ │ ├── Convert_SVG_to_XAML.ps1 │ ├── IconExperience.License.txt │ ├── Icons_Default.xaml │ └── Icons_Default.xaml.cs ├── FocusLogger.Tests/ │ ├── CsvExportTests.cs │ ├── JocysCom.FocusLogger.Tests.csproj │ └── UIAutomationTests.cs ├── JocysCom.FocusLogger.slnx ├── LICENSE ├── README.md ├── Resources/ │ └── ZipFiles.ps1 ├── SECURITY.md ├── Settings.XamlStyler └── Solution_Cleanup.ps1
SYMBOL INDEX (474 symbols across 42 files)
FILE: Documents/Take_Screenshot.ps1.cs
class TakeScreenshot (line 7) | public class TakeScreenshot
method SetForegroundWindow (line 9) | [DllImport("user32.dll")]
method ShowWindow (line 11) | [DllImport("user32.dll")]
method SetWindowPos (line 13) | [DllImport("user32.dll")]
method GetWindowRect (line 15) | [DllImport("user32.dll")]
method PrintWindow (line 17) | [DllImport("user32.dll")]
method DwmGetWindowAttribute (line 19) | [DllImport("dwmapi.dll")]
type RECT (line 22) | [StructLayout(LayoutKind.Sequential)]
method GetExtendedFrameBounds (line 28) | public static RECT GetExtendedFrameBounds(IntPtr hwnd)
method CenterAndResize (line 35) | public static void CenterAndResize(IntPtr hwnd, int w, int h, int yOff...
method CaptureWindow (line 47) | public static void CaptureWindow(IntPtr hwnd, string filePath)
FILE: FocusLogger.Tests/CsvExportTests.cs
class CsvExportTests (line 11) | [TestClass]
method CsvEscape_PlainText_ReturnsUnchanged (line 14) | [TestMethod]
method CsvEscape_TextWithComma_WrapsInQuotes (line 20) | [TestMethod]
method CsvEscape_TextWithQuotes_EscapesQuotes (line 26) | [TestMethod]
method CsvEscape_TextWithNewline_WrapsInQuotes (line 32) | [TestMethod]
method CsvEscape_NullOrEmpty_ReturnsEmpty (line 38) | [TestMethod]
method BuildCsvContent_WithItems_ProducesValidCsv (line 45) | [TestMethod]
method BuildCsvContent_Empty_ReturnsHeaderOnly (line 86) | [TestMethod]
FILE: FocusLogger.Tests/UIAutomationTests.cs
class UIAutomationTests (line 10) | [TestClass]
method GetAppPath (line 13) | private static string GetAppPath()
method FindDescendant (line 21) | private static AutomationElement FindDescendant(AutomationElement pare...
method App_LaunchAndDetectNotepadFocus (line 35) | [TestMethod]
method FindMainWindow (line 92) | private static AutomationElement FindMainWindow(int processId, int tim...
FILE: FocusLogger/App.xaml.cs
class App (line 7) | public partial class App : Application
method App (line 9) | public App()
class NativeMethods (line 14) | internal class NativeMethods
method SetProcessDPIAware (line 16) | [System.Runtime.InteropServices.DllImport("user32.dll")]
method SetDPIAware (line 20) | public static void SetDPIAware()
FILE: FocusLogger/Common/DataItem.cs
class DataItem (line 8) | public class DataItem : SettingsItem
method IsSame (line 44) | public bool IsSame(DataItem item)
FILE: FocusLogger/Common/DataItemType.cs
type DataItemType (line 3) | public enum DataItemType
FILE: FocusLogger/Common/NativeMethods.cs
class NativeMethods (line 7) | internal class NativeMethods
method GetFocus (line 18) | [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
method GetActiveWindow (line 28) | [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
method GetTopWindow (line 34) | [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
method IsWindowVisible (line 37) | [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
method GetWindowTextLengthW (line 40) | [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
method GetWindowText (line 50) | [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
method GetForegroundWindow (line 60) | [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
method GetWindowThreadProcessId (line 70) | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
method GetGUIThreadInfo (line 85) | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
type RECT (line 88) | [StructLayout(LayoutKind.Sequential)]
type GUI (line 97) | [Flags]
type GUITHREADINFO (line 112) | [StructLayout(LayoutKind.Sequential)]
method GetInfo (line 135) | internal static GUITHREADINFO? GetInfo(IntPtr hWnd)
method GetClassName (line 146) | [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
method GetWindowClassName (line 149) | internal static string GetWindowClassName(IntPtr hWnd)
method GetWindowText (line 156) | internal static string GetWindowText(IntPtr hWnd)
FILE: FocusLogger/Controls/DataListControl.xaml.cs
class DataListControl (line 20) | public partial class DataListControl : UserControl
method DataListControl (line 22) | public DataListControl()
method _MainDataGridFormattingConverter_Convert (line 33) | object _MainDataGridFormattingConverter_Convert(object[] values, Type ...
method UserControl_Loaded (line 80) | private void UserControl_Loaded(object sender, RoutedEventArgs e)
method SaveCsvButton_Click (line 89) | private void SaveCsvButton_Click(object sender, RoutedEventArgs e)
method ExploreCsvButton_Click (line 109) | private void ExploreCsvButton_Click(object sender, RoutedEventArgs e)
method BuildCsvContent (line 119) | public static string BuildCsvContent(System.Collections.Generic.IEnume...
method CsvEscape (line 141) | public static string CsvEscape(string value)
method CopyAiPromptButton_Click (line 150) | private void CopyAiPromptButton_Click(object sender, RoutedEventArgs e)
method ClearButton_Click (line 158) | private void ClearButton_Click(object sender, RoutedEventArgs e)
method UserControl_Unloaded (line 163) | private void UserControl_Unloaded(object sender, RoutedEventArgs e)
method UpdateFromProcess (line 180) | public void UpdateFromProcess(DataItem item)
method InitTimer (line 212) | void InitTimer()
method _Timer_Elapsed (line 221) | private void _Timer_Elapsed(object sender, System.Timers.ElapsedEventA...
method UpdateInfo (line 232) | public void UpdateInfo()
method GetItemFromHandle (line 266) | DataItem GetItemFromHandle(IntPtr hWnd, bool isActive = false)
FILE: FocusLogger/JocysCom/Collections/CollectionsHelper.cs
class CollectionsHelper (line 7) | public static partial class CollectionsHelper
method Synchronize (line 24) | public static void Synchronize<T>(IList<T> source, IList<T> target, IE...
FILE: FocusLogger/JocysCom/Common/Helper.cs
class Helper (line 14) | public partial class Helper : IDisposable
method WriteAppHeader (line 21) | public static void WriteAppHeader()
method WriteAppHeader (line 27) | public static void WriteAppHeader(Assembly assembly)
method WriteAppHelp (line 41) | public static void WriteAppHelp()
method FindResource (line 49) | public static T FindResource<T>(string name, object o)
method FindResource (line 61) | public static T FindResource<T>(string name, params Assembly[] assembl...
method GetResourceKeys (line 84) | public static string[] GetResourceKeys(Assembly assembly)
method GetResourceValue (line 95) | public static Stream GetResourceValue(string name, Assembly assembly)
method GetResource (line 114) | public static T GetResource<TSource, T>(string name)
method GetResource (line 123) | public static T GetResource<T>(string name, params Assembly[] assemblies)
method ConvertResource (line 144) | static T ConvertResource<T>(Stream stream)
method GetAssemblies (line 176) | static Assembly[] GetAssemblies()
method LongDelay (line 210) | public static async Task LongDelay(
method LongDelay (line 217) | public static async Task LongDelay(
class DebounceData (line 241) | class DebounceData
method Delay (line 247) | [Obsolete("Use `async Task Debounce(Action action, int? delay = null, ...
method Delay (line 251) | [Obsolete("Use `async Task Debounce(Func<Task> action, int? delay = nu...
method Debounce (line 262) | public static async Task Debounce(Action action, int? delay = null)
method Debounce (line 272) | public static async Task Debounce<T>(Action<T> action, T arg, int? del...
method Debounce (line 282) | public static async Task Debounce(Func<Task> action, int? delay = null)
method _Debounce (line 294) | public static async Task _Debounce(Delegate action, int? delay = null,...
method ExecuteWithUIThreadMarshaling (line 325) | private static async Task ExecuteWithUIThreadMarshaling(Delegate actio...
method GetWpfDispatcher (line 354) | private static Dispatcher GetWpfDispatcher()
method GetCounterValue (line 391) | private static double GetCounterValue(PerformanceCounter pc, string ca...
type DiskData (line 400) | public enum DiskData { ReadAndWrite, Read, Write };
method GetDiskData (line 403) | public double GetDiskData(DiskData dd)
method IsGuid (line 440) | public static bool IsGuid(string s)
method IsOverlap (line 450) | public static bool IsOverlap<T>(
method RunSynchronously (line 484) | public static void RunSynchronously(Func<Task> asyncFunc)
method RunSynchronously (line 516) | public static TResult RunSynchronously<TResult>(Func<Task<TResult>> as...
method Dispose (line 541) | public void Dispose()
method Dispose (line 548) | protected virtual void Dispose(bool disposing)
FILE: FocusLogger/JocysCom/ComponentModel/BindingListInvoked.cs
class BindingListInvoked (line 15) | public class BindingListInvoked<T> : BindingList<T>
method BindingListInvoked (line 17) | public BindingListInvoked() : base() { }
method BindingListInvoked (line 19) | public BindingListInvoked(IList<T> list)
method BindingListInvoked (line 22) | public BindingListInvoked(IEnumerable<T> enumeration)
method AddRange (line 25) | public void AddRange(IEnumerable<T> list)
method Invoke (line 42) | void Invoke(Delegate method, params object[] args)
method DynamicInvoke (line 83) | void DynamicInvoke(Delegate method, params object[] args)
method RemoveItem (line 103) | protected override void RemoveItem(int index)
method InsertItem (line 108) | protected override void InsertItem(int index, T item)
method SetItem (line 113) | protected override void SetItem(int index, T item)
method OnListChanged (line 118) | protected override void OnListChanged(ListChangedEventArgs e)
method OnAddingNew (line 123) | protected override void OnAddingNew(AddingNewEventArgs e)
FILE: FocusLogger/JocysCom/ComponentModel/NotifyPropertyChanged.cs
class NotifyPropertyChanged (line 14) | public class NotifyPropertyChanged : INotifyPropertyChanged
method OnPropertyChanged (line 33) | protected virtual void OnPropertyChanged([CallerMemberName] string pro...
method SetProperty (line 61) | protected void SetProperty<T>(ref T property, T value, [CallerMemberNa...
FILE: FocusLogger/JocysCom/ComponentModel/PropertyComparer.cs
class PropertyComparer (line 13) | public class PropertyComparer<T> : IComparer<T>
method PropertyComparer (line 19) | public PropertyComparer(PropertyDescriptor propDesc, ListSortDirection...
method PropertyComparer (line 25) | public PropertyComparer(ListSortDescriptionCollection sortCollection)
method Compare (line 30) | int IComparer<T>.Compare(T x, T y)
method Compare (line 40) | protected int Compare(T x, T y)
method CompareValues (line 57) | private int CompareValues(object xValue, object yValue, ListSortDirect...
method RecursiveCompareInternal (line 76) | private int RecursiveCompareInternal(T x, T y, int index)
FILE: FocusLogger/JocysCom/ComponentModel/SortableBindingList.cs
class SortableBindingList (line 12) | [Serializable]
method SortableBindingList (line 15) | public SortableBindingList() : base() { }
method SortableBindingList (line 17) | public SortableBindingList(IList<T> list)
method SortableBindingList (line 20) | public SortableBindingList(IEnumerable<T> enumeration)
method From (line 23) | public static SortableBindingList<T> From(IEnumerable<T> list)
method CheckReadOnly (line 59) | private bool CheckReadOnly() { return !_Sorted && !_Filtered; }
method FindCore (line 61) | protected override int FindCore(PropertyDescriptor property, object key)
method ApplySortCore (line 73) | protected override void ApplySortCore(PropertyDescriptor property, Lis...
method ApplySort (line 81) | void IBindingListView.ApplySort(ListSortDescriptionCollection sorts)
method ApplySort (line 86) | protected void ApplySort(ListSortDescriptionCollection sorts)
method ApplySortInternal (line 94) | private void ApplySortInternal(PropertyComparer<T> comparer)
method RemoveSortCore (line 106) | protected override void RemoveSortCore()
method RemoveFilter (line 132) | void IBindingListView.RemoveFilter() { RemoveFilter(); }
method RemoveFilter (line 133) | protected void RemoveFilter()
method UpdateFilter (line 148) | protected virtual void UpdateFilter()
method InsertItem (line 172) | protected override void InsertItem(int index, T item)
method RemoveItem (line 182) | protected override void RemoveItem(int index)
method OnItemChanged (line 194) | private void OnItemChanged(object sender, EventArgs args)
method RemoveAll (line 200) | public void RemoveAll(Func<object, bool> value)
FILE: FocusLogger/JocysCom/Configuration/Arguments.cs
class Arguments (line 16) | public class Arguments : Dictionary<string, string>
method Arguments (line 24) | public Arguments(string[] args, StringComparer comparer = null) : base...
method GetValue (line 110) | public string GetValue(string key, bool ignoreCase = false)
method ContainsKey (line 122) | public bool ContainsKey(string key, bool ignoreCase)
FILE: FocusLogger/JocysCom/Configuration/AssemblyInfo.cs
class AssemblyInfo (line 13) | public partial class AssemblyInfo
method AssemblyInfo (line 19) | public AssemblyInfo()
method AssemblyInfo (line 54) | public AssemblyInfo(string strValFile)
method AssemblyInfo (line 59) | public AssemblyInfo(Assembly assembly)
class EntryAssemblyAttribute (line 71) | [AttributeUsage(AttributeTargets.Assembly)]
method FindEntryAssembly1 (line 78) | Assembly FindEntryAssembly1()
method FindEntryAssembly2 (line 97) | Assembly FindEntryAssembly2()
method GetTitle (line 172) | public string GetTitle(bool showBuild = true, bool showRunMode = true,...
class NativeMethods (line 249) | internal partial class NativeMethods
method WTSQuerySessionInformationW (line 251) | [DllImport("wtsapi32.dll")]
method GetWindowsDomainName (line 265) | public string GetWindowsDomainName() { return GetInformation(7); }
method GetWindowsUserName (line 270) | public string GetWindowsUserName() { return GetInformation(5); }
method GetInformation (line 275) | private static string GetInformation(int WTSInfoClass)
method GetBuildDateTime (line 303) | public static DateTime GetBuildDateTime(string filePath)
method GetBuildDateTime (line 356) | public static DateTime GetBuildDateTime(Assembly assembly, TimeZoneInf...
method GetDateTime (line 396) | static DateTime GetDateTime(int secondsSince1970, TimeZoneInfo tzi = n...
method GetAttribute (line 435) | string GetAttribute<T>(Func<T, string> value) where T : Attribute
method GetAppDataPath (line 446) | public string GetAppDataPath(bool userLevel = false, string format = "...
method GetAppDataFile (line 465) | public FileInfo GetAppDataFile(bool userLevel = false, string format =...
FILE: FocusLogger/JocysCom/Configuration/ISettingsData.cs
type ISettingsData (line 8) | public interface ISettingsData
method ResetToDefault (line 12) | bool ResetToDefault();
method Save (line 15) | void Save(object[] items = null);
method SaveAs (line 19) | void SaveAs(string fileName, object[] items = null);
method Load (line 22) | void Load();
method LoadFrom (line 26) | void LoadFrom(string fileName);
method DeleteItem (line 43) | string DeleteItem(ISettingsFileItem itemFile);
method RenameItem (line 49) | string RenameItem(ISettingsFileItem itemFile, string newName);
FILE: FocusLogger/JocysCom/Configuration/ISettingsFileItem.cs
type ISettingsFileItem (line 8) | public interface ISettingsFileItem : ISettingsItem
FILE: FocusLogger/JocysCom/Configuration/ISettingsItem.cs
type ISettingsItem (line 7) | public interface ISettingsItem : INotifyPropertyChanged
FILE: FocusLogger/JocysCom/Configuration/ISettingsListFileItem.cs
type ISettingsListFileItem (line 12) | public interface ISettingsListFileItem : ISettingsFileItem
method SetIcon (line 28) | void SetIcon(string contents, string type = ".svg");
FILE: FocusLogger/JocysCom/Configuration/SettingsData.cs
class SettingsData (line 25) | [Serializable, XmlRoot("Data"), DataContract]
method SettingsData (line 31) | public SettingsData()
method SettingsData (line 50) | public SettingsData(string overrideFileName = null, bool? userLevel = ...
method GetAppDataPath (line 59) | private string GetAppDataPath(bool userLevel = false)
method Initialize (line 75) | private void Initialize(string overrideFileName, bool? userLevel, stri...
method GetLocalSettingsDirectory (line 118) | public string GetLocalSettingsDirectory()
method ItemsToArraySynchronized (line 203) | public T[] ItemsToArraySynchronized()
method SaveAs (line 236) | public void SaveAs(string path, object[] items = null)
method RemoveInvalidPathChars (line 307) | public static string RemoveInvalidPathChars(string name)
method RemoveInvalidFileNameChars (line 313) | public static string RemoveInvalidFileNameChars(string name)
method Save (line 323) | public void Save(object[] items = null)
method Add (line 340) | public void Add(params T[] items)
method Remove (line 351) | public void Remove(params T[] items)
class SettingsDataEventArgs (line 358) | public class SettingsDataEventArgs : EventArgs
method SettingsDataEventArgs (line 360) | public SettingsDataEventArgs(IList<T> items)
class ItemPropertyChangedEventArgs (line 368) | public class ItemPropertyChangedEventArgs : PropertyChangedEventArgs
method ItemPropertyChangedEventArgs (line 374) | public ItemPropertyChangedEventArgs(string propertyName, object oldV...
method SetLastWriteTime (line 403) | private void SetLastWriteTime(FileInfo fi)
method IsNewerOnDisk (line 414) | private bool IsNewerOnDisk(FileInfo fi)
method AllowWriteFile (line 425) | private bool AllowWriteFile(FileInfo fi)
method Load (line 438) | public void Load()
method GetRootDirectory (line 443) | static DirectoryInfo GetRootDirectory(FileInfo fi)
method LoadFrom (line 458) | public void LoadFrom(string fileName)
method SortList (line 594) | public void SortList<T1>(IList<T1> items) where T1 : ISettingsFileItem
method GetFileItemFullName (line 610) | public string GetFileItemFullName(ISettingsFileItem fileItem, string o...
method GetFileItemFullBaseName (line 619) | public string GetFileItemFullBaseName(ISettingsFileItem fileItem)
method GetFileItemFullName (line 636) | public static string GetFileItemFullName(string rootPath, ISettingsFil...
method RenameFolder (line 657) | public string RenameFolder(string currentPath, string newFolderName)
method RenameItem (line 699) | public string RenameItem(ISettingsFileItem fileItem, string newName)
method DeleteItem (line 765) | public string DeleteItem(ISettingsFileItem fileItem)
method LoadAndValidateData (line 806) | void LoadAndValidateData(IList<T> data)
method Synchronize (line 862) | public static void Synchronize(IList<T> source, IList<T> target)
method GetHashValues (line 893) | Dictionary<T, byte[]> GetHashValues(IList<T> items)
method ResetToDefault (line 912) | public bool ResetToDefault()
method Serialize (line 952) | byte[] Serialize(object fileItem)
method DeserializeData (line 963) | public SettingsData<T> DeserializeData(byte[] bytes, bool compressed)
method DeserializeItem (line 977) | public T DeserializeItem(byte[] bytes, bool compressed)
method SetFileMonitoring (line 993) | public void SetFileMonitoring(bool enabled)
method SetFileMonitoring (line 1023) | public void SetFileMonitoring(bool enabled, string folderPath, string ...
method OnChanged (line 1058) | private void OnChanged(object sender, FileSystemEventArgs e)
method OnCreated (line 1061) | private void OnCreated(object sender, FileSystemEventArgs e)
method OnDeleted (line 1064) | private void OnDeleted(object sender, FileSystemEventArgs e)
method OnRenamed (line 1067) | private void OnRenamed(object sender, RenamedEventArgs e)
method FilesChangedDebounced (line 1070) | private void FilesChangedDebounced()
FILE: FocusLogger/JocysCom/Configuration/SettingsHelper.cs
class SettingsHelper (line 14) | public static partial class SettingsHelper
method Compress (line 23) | public static byte[] Compress(byte[] bytes)
method Decompress (line 47) | public static byte[] Decompress(byte[] bytes)
method IsDifferent (line 76) | public static bool IsDifferent(string path, byte[] bytes)
method WriteIfDifferent (line 102) | public static bool WriteIfDifferent(string path, byte[] bytes)
method ReadFileContent (line 143) | public static string ReadFileContent(string name, out Encoding encoding)
method GetFileContentBytes (line 158) | public static byte[] GetFileContentBytes(string content, Encoding enco...
method IsEnoughSpaceAvailable (line 176) | public static bool IsEnoughSpaceAvailable(string path, long requiredBy...
method SaveFileWithChecksum (line 202) | public static FileInfo SaveFileWithChecksum(string name, byte[] bytes)
method ComputeCRC32Checksum (line 229) | public static uint ComputeCRC32Checksum(byte[] bytes)
FILE: FocusLogger/JocysCom/Configuration/SettingsItem.cs
class SettingsItem (line 12) | public class SettingsItem : NotifyPropertyChanged, ISettingsItem
FILE: FocusLogger/JocysCom/Configuration/SettingsParser.cs
class SettingsParser (line 16) | public partial class SettingsParser
method SettingsParser (line 19) | public SettingsParser(string configPrefix = "")
method Parse (line 36) | public T Parse<T>(string name, T defaultValue = default(T))
method ParseValue (line 54) | public static object ParseValue(Type t, string v, object defaultValue ...
method TryParseValue (line 83) | public static T TryParseValue<T>(string v, T defaultValue = default(T))
method ParseValue (line 95) | public static T ParseValue<T>(string v, T defaultValue = default(T))
FILE: FocusLogger/JocysCom/Controls/ControlsHelper.WPF.UseWindowsForms.cs
class ControlsHelper (line 10) | public partial class ControlsHelper
method CheckTopMost (line 17) | public static void CheckTopMost(Window win)
method AutoSizeByOpenForms (line 25) | public static void AutoSizeByOpenForms(Window win, int addSize = -64)
method GetImageSource (line 43) | public static ImageSource GetImageSource(System.Drawing.Bitmap bitmap)
method CenterWindowOnApplication (line 62) | public static void CenterWindowOnApplication(Window window)
method TransformToPixels (line 111) | private static void TransformToPixels(double unitX, double unitY, out ...
method TransformToUnits (line 123) | private static void TransformToUnits(int pixelX, int pixelY, out doubl...
method GetMainFormTopMost (line 132) | public static bool GetMainFormTopMost()
FILE: FocusLogger/JocysCom/Controls/ControlsHelper.WPF.cs
class ControlsHelper (line 17) | public partial class ControlsHelper
method EnableWithDelay (line 19) | public static void EnableWithDelay(UIElement control)
method IsDesignMode (line 31) | public static bool IsDesignMode(UIElement component)
method IsDesignMode1 (line 38) | private static bool IsDesignMode1(UIElement component)
method Clone (line 54) | public static T Clone<T>(T o)
method SetText (line 83) | public static void SetText(Label control, string format, params object...
method SetText (line 94) | public static void SetText(PasswordBox control, string format, params ...
method SetText (line 109) | public static void SetText(GroupBox control, string format, params obj...
method SetText (line 124) | public static void SetText(TextBox control, string format, params obje...
method SetText (line 139) | public static void SetText(TextBlock control, string format, params ob...
method SetTextFromResource (line 150) | public static void SetTextFromResource(RichTextBox box, byte[] rtf)
method SetChecked (line 165) | public static void SetChecked(System.Windows.Controls.Primitives.Toggl...
method SetEnabled (line 177) | public static void SetEnabled(UIElement control, bool enabled)
method SetVisible (line 189) | public static void SetVisible(UIElement control, bool enabled)
method SetItemsSource (line 199) | public static void SetItemsSource(DataGridComboBoxColumn grid, IBindin...
method SetItemsSource (line 219) | public static void SetItemsSource(ItemsControl grid, IBindingList list)
method HookHyperlinks (line 240) | private static void HookHyperlinks(object sender, TextChangedEventArgs e)
method link_RequestNavigate (line 268) | private static void link_RequestNavigate(object sender, System.Windows...
method GetPoints (line 277) | public static Point[] GetPoints(Control control, bool relative = false)
method GetParent (line 334) | public static T GetParent<T>(DependencyObject control, bool includeTop...
method AddWeakHandlerOnWindowClosing (line 351) | public static void AddWeakHandlerOnWindowClosing(DependencyObject cont...
method RemoveFromParent (line 359) | public static void RemoveFromParent(FrameworkElement element)
method GetAll (line 396) | public static Dictionary<string, DependencyObject> GetAll(string path,...
method _GetAll (line 409) | private static Dictionary<string, DependencyObject> _GetAll(string pat...
method GetAll (line 464) | public static IEnumerable<DependencyObject> GetAll(DependencyObject co...
method GetAll (line 472) | public static T[] GetAll<T>(FrameworkElement control, bool includeTop ...
method GetActiveControl (line 479) | public static void GetActiveControl(FrameworkElement control, out Fram...
method ApplyBorderStyle (line 514) | public static void ApplyBorderStyle(DataGrid grid)
method GetSelection (line 686) | public static List<T> GetSelection<T>(DataGrid grid, string keyPropert...
method RestoreSelection (line 707) | [Obsolete("Use `bool SetSelection<T>(DataGrid grid, string keyProperty...
method RestoreSelection (line 711) | [Obsolete("Use `bool SetSelection<T>(DataGrid grid, string keyProperty...
method SetSelection (line 715) | public static bool SetSelection<T>(DataGrid grid, string keyPropertyNa...
method GetScrollVerticalAlignment (line 760) | public static VerticalAlignment GetScrollVerticalAlignment(ScrollViewe...
method AutoScroll (line 777) | public static void AutoScroll(Control control)
method EnableAutoScroll (line 796) | public static void EnableAutoScroll(TextBoxBase control, bool enable =...
method TextBoxBase_Unloaded (line 809) | private static void TextBoxBase_Unloaded(object sender, RoutedEventArg...
method TextBoxBase_TextChanged (line 812) | private static void TextBoxBase_TextChanged(object sender, TextChanged...
method TextBoxBase_IsVisibleChanged (line 815) | private static void TextBoxBase_IsVisibleChanged(object sender, Depend...
method AppendText (line 822) | public static void AppendText(TextBox control, string text, int maxSiz...
method AppInvoke (line 859) | public static void AppInvoke(Action action)
method AppBeginInvoke (line 874) | public static void AppBeginInvoke(Action action)
method AllowLoad (line 899) | public static bool AllowLoad(FrameworkElement control)
method AllowUnload (line 913) | public static bool AllowUnload(FrameworkElement control)
method IsLoaded (line 927) | public static bool IsLoaded(FrameworkElement control)
method GetParent (line 967) | static T GetParent<T>(DependencyObject source) where T : class
method FileExplorer_DataGrid_CheckBox_PreviewMouseDown (line 977) | public static void FileExplorer_DataGrid_CheckBox_PreviewMouseDown(obj...
method IsTabItemSelected (line 995) | public static bool IsTabItemSelected(FrameworkElement control)
method EnsureTabItemSelected (line 1026) | public static void EnsureTabItemSelected(FrameworkElement control)
FILE: FocusLogger/JocysCom/Controls/ControlsHelper.cs
class ControlsHelper (line 13) | public static partial class ControlsHelper
method InitInvokeContext (line 20) | public static void InitInvokeContext()
method BeginInvoke (line 101) | public static Task BeginInvoke(Action action, int? millisecondsDelay =...
method BeginInvoke (line 122) | public static Task BeginInvoke(Delegate method, params object[] args)
method Invoke (line 131) | public static void Invoke(Action action)
method Invoke (line 150) | public static object Invoke(Delegate method, params object[] args)
method OpenUrl (line 171) | public static void OpenUrl(string url)
method MessageBoxShow (line 193) | private static void MessageBoxShow(string message)
method OpenPath (line 207) | public static void OpenPath(string path, string arguments = null)
method GetPrimaryKeyPropertyInfo (line 240) | public static PropertyInfo GetPrimaryKeyPropertyInfo(object item)
method GetValue (line 275) | private static T GetValue<T>(object item, string keyPropertyName, Prop...
method GetPropertyInfo (line 295) | private static PropertyInfo GetPropertyInfo(string keyPropertyName, ob...
method IsOnCooldown (line 317) | public static bool IsOnCooldown(object control, int? milliseconds = null)
FILE: FocusLogger/JocysCom/Controls/InfoControl.xaml.cs
class InfoControl (line 16) | public partial class InfoControl : UserControl
method InfoControl (line 20) | public InfoControl()
method CreateBusyIconAnimation (line 42) | private void CreateBusyIconAnimation()
method HelpProvider_OnMouseEnter (line 55) | private void HelpProvider_OnMouseEnter(object sender, EventArgs e)
method HelpProvider_OnMouseLeave (line 66) | private void HelpProvider_OnMouseLeave(object sender, EventArgs e)
method SetImage (line 86) | public void SetImage(object resource)
method Reset (line 96) | public void Reset()
method SetTitle (line 102) | public void SetTitle(string format, params object[] args)
method SetHead (line 112) | public void SetHead(string format, params object[] args)
method SetBodyError (line 123) | public void SetBodyError(string content, params object[] args)
method SetBodyInfo (line 134) | public void SetBodyInfo(string content, params object[] args)
method SetWithTimeout (line 145) | public async void SetWithTimeout(MessageBoxImage image, string content...
method SetBody (line 160) | public void SetBody(MessageBoxImage image, string content = null, para...
method AddTask (line 197) | public void AddTask(object name)
method RemoveTask (line 208) | public void RemoveTask(object name)
method UpdateIcon (line 218) | public void UpdateIcon()
method UserControl_Loaded (line 240) | private void UserControl_Loaded(object sender, RoutedEventArgs e)
method UserControl_Unloaded (line 246) | private void UserControl_Unloaded(object sender, RoutedEventArgs e)
FILE: FocusLogger/JocysCom/Controls/InfoHelpProvider.cs
class InfoHelpProvider (line 8) | public class InfoHelpProvider
method GetHelpHead (line 25) | public string GetHelpHead(UIElement control)
method GetHelpImage (line 32) | public MessageBoxImage GetHelpImage(UIElement control)
method GetHelpBody (line 39) | public string GetHelpBody(UIElement control, int? maxLength = null, bo...
method Add (line 52) | public void Add(UIElement control, string helpHead, string helpBody = ...
method Remove (line 74) | public void Remove(UIElement control)
method CropText (line 104) | public static string CropText(object so, int? maxLength = 0)
method RemoveMultispace (line 128) | public static string RemoveMultispace(string s)
FILE: FocusLogger/JocysCom/Controls/InitHelper.cs
class InitHelper (line 12) | public class InitHelper
method InitHelper (line 15) | public InitHelper()
method WriteLine (line 29) | public void WriteLine(string prefix)
method InitTimer (line 61) | public static void InitTimer(FrameworkElement control, Action Initiali...
method Control_Loaded (line 85) | private static void Control_Loaded(object sender, RoutedEventArgs e)
method Control_Unloaded (line 94) | private static void Control_Unloaded(object sender, RoutedEventArgs e)
method Control_IsVisibleChanged (line 107) | private static void Control_IsVisibleChanged(object sender, System.Win...
method Control_PropertyChanged (line 115) | private static void Control_PropertyChanged(object sender, EventArgs e)
method RestartTimer (line 121) | private static void RestartTimer(object sender)
method _Timer_Elapsed (line 137) | private static void _Timer_Elapsed(object sender, System.Timers.Elapse...
FILE: FocusLogger/JocysCom/Controls/ItemFormattingConverter.cs
class ItemFormattingConverter (line 7) | public class ItemFormattingConverter : IMultiValueConverter
method Convert (line 13) | public object Convert(object[] values, Type targetType, object paramet...
method ConvertBack (line 16) | public object[] ConvertBack(object value, Type[] targetTypes, object p...
FILE: FocusLogger/JocysCom/Controls/MessageBoxWindow.xaml.cs
class MessageBoxWindow (line 13) | public partial class MessageBoxWindow : Window
method MessageBoxWindow (line 18) | public MessageBoxWindow()
method Show (line 38) | public static MessageBoxResult Show(
method ShowDialog (line 58) | public MessageBoxResult ShowDialog(
method ShowPrompt (line 95) | public MessageBoxResult ShowPrompt(
method FocusMessageTextBox (line 128) | private void FocusMessageTextBox(object sender, RoutedEventArgs e)
method SetSize (line 136) | public void SetSize(double width = 0, double height = 0)
method MessageBoxWindow_Loaded (line 150) | private void MessageBoxWindow_Loaded(object sender, RoutedEventArgs e)
method EnableButtons (line 174) | void EnableButtons(MessageBoxResult r1, MessageBoxResult r2 = MessageB...
method _SwitchButton (line 189) | private void _SwitchButton(MessageBoxButton button, MessageBoxResult d...
method _SwitchIcon (line 218) | private void _SwitchIcon(MessageBoxImage icon)
method CopyMessage_Click (line 237) | private void CopyMessage_Click(object sender, RoutedEventArgs e)
method Button_Click (line 246) | private void Button_Click(object sender, RoutedEventArgs e)
method SetLink (line 252) | public void SetLink(string text = null, Uri uri = null)
method MainHyperLink_RequestNavigate (line 261) | private void MainHyperLink_RequestNavigate(object sender, System.Windo...
method OpenUrl (line 268) | public static void OpenUrl(string url)
method ApplyAspectRatio (line 285) | private static Size ApplyAspectRatio(Size s, double w = 16, double h = 9)
method MeasureString (line 293) | private static Size MeasureString(string candidate, TextBlock control)
method MessageTextBox_KeyUp (line 311) | private void MessageTextBox_KeyUp(object sender, System.Windows.Input....
method UpdateSizeLabel (line 316) | void UpdateSizeLabel()
method Window_Loaded (line 323) | private void Window_Loaded(object sender, RoutedEventArgs e)
method Window_Unloaded (line 332) | private void Window_Unloaded(object sender, RoutedEventArgs e)
method Window_Closed (line 338) | private void Window_Closed(object sender, EventArgs e)
FILE: FocusLogger/JocysCom/Controls/TabIndexConverter.cs
class TabIndexConverter (line 10) | public class TabIndexConverter : IValueConverter
method Convert (line 12) | public object Convert(object value, Type targetType, object parameter,...
method ConvertBack (line 29) | public object ConvertBack(object value, Type targetType, object parame...
FILE: FocusLogger/JocysCom/Controls/Themes/Icons.xaml.cs
class Icons (line 5) | partial class Icons : ResourceDictionary
method Icons (line 7) | public Icons()
FILE: FocusLogger/JocysCom/Controls/ToolStripBorderlessRenderer.cs
class ToolStripBorderlessRenderer (line 10) | public class ToolStripBorderlessRenderer : ToolStripSystemRenderer
method OnRenderToolStripBorder (line 12) | protected override void OnRenderToolStripBorder(ToolStripRenderEventAr...
FILE: FocusLogger/JocysCom/Data/SqlHelper.Types.cs
class SqlHelper (line 9) | public partial class SqlHelper
method HaveSize (line 12) | public static bool HaveSize(string sqlType)
method IsUnicode (line 19) | public static bool IsUnicode(string sqlType)
method ToSystemTypeVB (line 26) | public static string ToSystemTypeVB(string sqlType)
method ToSystemTypeCS (line 40) | public static string ToSystemTypeCS(string sqlType)
method ToSystemType (line 48) | public static Type ToSystemType(string sqlTypeSring)
method GetSqlDataType (line 128) | public static SqlDataType GetSqlDataType(TypeCode code, int min = 0, i...
type SqlDataType (line 170) | public enum SqlDataType
FILE: FocusLogger/JocysCom/IO/PathHelper.cs
class PathHelper (line 11) | public static partial class PathHelper
method ValidateFileName (line 15) | public static void ValidateFileName(string name)
method ValidatePath (line 30) | public static void ValidatePath(string path)
method IsPathVirtual (line 45) | public static bool IsPathVirtual(string path)
method IsPathPhysical (line 60) | public static bool IsPathPhysical(string path)
method GetPathPhysical (line 73) | public static string GetPathPhysical(string relativeTo, string path)
method GetPathRooted (line 98) | public static string GetPathRooted(string relativeTo, string path)
method GetRelativePath (line 129) | public static string GetRelativePath(string relativeTo, string path, b...
method GetPathVirtual (line 152) | public static string GetPathVirtual(string relativeTo, string path)
method GetParentFolderPhysical (line 169) | public static string GetParentFolderPhysical(string relativeTo, string...
method GetParentFolderVirtual (line 182) | public static string GetParentFolderVirtual(string relativeTo, string ...
method GetFileName (line 194) | public static string GetFileName(string relativeTo, string path)
FILE: FocusLogger/JocysCom/Runtime/RuntimeHelper.cs
class RuntimeHelper (line 14) | public static partial class RuntimeHelper
method IsNumeric (line 38) | public static bool IsNumeric(Type type)
method GetBuiltInTypeNameOrAlias (line 64) | public static string GetBuiltInTypeNameOrAlias(Type type)
method GetFirstArgumentOfGenericType (line 91) | private static Type GetFirstArgumentOfGenericType(Type type)
method IsNullableType (line 96) | public static bool IsNullableType(Type type)
method GetItersectingFields (line 112) | private static FieldInfo[] GetItersectingFields(object source, object ...
method GetFields (line 127) | private static FieldInfo[] GetFields(Type t, bool cache = true)
method GetProperties (line 140) | private static PropertyInfo[] GetProperties(Type t, bool cache = true)
method CopyFields (line 148) | public static void CopyFields(object source, object target, bool onlyN...
method GetJsonSerializer (line 191) | static DataContractJsonSerializer GetJsonSerializer(Type type, DataCon...
method Serialize (line 198) | private static string Serialize(object o)
method Deserialize (line 210) | private static object Deserialize(string json, Type type)
method GetPropertyDiffInfo (line 230) | public static string GetPropertyDiffInfo<TSource, TTarget>()
method CanCopy (line 277) | public static bool CanCopy(Type source, Type target, bool onlyNonByRef...
method CopyProperties (line 294) | public static void CopyProperties(object source, object target, bool o...
method EqualProperties (line 339) | public static bool EqualProperties(object source, object target, bool ...
method ObjectToBytes (line 385) | public static byte[] ObjectToBytes<T>(T o)
method BytesToObject (line 441) | public static T BytesToObject<T>(byte[] bytes)
method StructureToBytes (line 524) | public static byte[] StructureToBytes<T>(T value) where T : struct
method BytesToStructure (line 542) | public static T BytesToStructure<T>(byte[] bytes) where T : struct
method BytesToStructure (line 550) | public static object BytesToStructure(byte[] bytes, Type type)
method TryParse (line 582) | public static bool TryParse(object value, Type t, out object result)
method TryParse (line 622) | public static bool TryParse<T>(string value, out T result)
method TryParse (line 635) | public static T TryParse<T>(string value, T defaultValue = default(T))
method CanParse (line 647) | public static bool CanParse<T>(string value)
method IsNullable (line 653) | public static bool IsNullable(Type t)
method ToFourDigitYear (line 683) | public static int ToFourDigitYear(int year, int? twoDigitYearMax = null)
FILE: FocusLogger/JocysCom/Runtime/Serializer.cs
class Serializer (line 15) | public static class Serializer
method ReadFile (line 27) | public static byte[] ReadFile(string path, int attempts = 2, int waitT...
method WriteFile (line 57) | public static void WriteFile(string path, byte[] bytes, int attempts =...
method GetJsonSerializer (line 91) | static DataContractJsonSerializer GetJsonSerializer(Type type, DataCon...
method GetJsonOptions (line 103) | private static System.Text.Json.JsonSerializerOptions GetJsonOptions()
method SerializeToJson (line 122) | public static string SerializeToJson(object o, Encoding encoding = null)
method DeserializeFromJson (line 151) | public static object DeserializeFromJson(string json, Type type, Encod...
method DeserializeFromJson (line 180) | public static T DeserializeFromJson<T>(string json, Encoding encoding ...
method FormatJson (line 187) | public static string FormatJson(string json, string indent = "\t")
method XmlFormat (line 225) | public static string XmlFormat(string xml)
method GetXmlSerializer (line 246) | public static XmlSerializer GetXmlSerializer(Type type)
method SerializeToXml (line 270) | public static XmlDocument SerializeToXml(object o)
method SeriallizeToXml (line 294) | static T SeriallizeToXml<T>(object o, Encoding encoding = null, bool o...
method SerializeToXmlString (line 380) | public static string SerializeToXmlString(object o, Encoding encoding ...
method SerializeToXmlFile (line 395) | public static void SerializeToXmlFile(object o, string path, Encoding ...
method SerializeToXmlBytes (line 412) | public static byte[] SerializeToXmlBytes(object o, Encoding encoding =...
method DeserializeFromXml (line 430) | public static object DeserializeFromXml(XmlDocument doc, Type type)
method DeserializeFromXmlBytes (line 444) | public static object DeserializeFromXmlBytes(byte[] bytes, Type type, ...
method DeserializeFromXmlString (line 485) | public static object DeserializeFromXmlString(string xml, Type type, X...
method DeserializeFromXmlFile (line 527) | public static object DeserializeFromXmlFile(string filename, Type type...
method DeserializeFromXml (line 541) | public static T DeserializeFromXml<T>(XmlDocument doc)
method DeserializeFromXmlString (line 551) | public static T DeserializeFromXmlString<T>(string xml)
method DeserializeFromXmlFile (line 562) | public static T DeserializeFromXmlFile<T>(string filename, Encoding en...
method DeserializeFromXmlBytes (line 573) | public static T DeserializeFromXmlBytes<T>(byte[] bytes, Encoding enco...
method SerializeToXsdFile (line 588) | public static void SerializeToXsdFile(object o, string path, Encoding ...
FILE: FocusLogger/JocysCom/Text/Helper.cs
class Helper (line 11) | public static class Helper
method Replace (line 31) | public static string Replace<T>(string s, T o, bool usePrefix = true, ...
method ReplaceDictionary (line 74) | public static string ReplaceDictionary(string s, Dictionary<string, ob...
method GetReplaceMacros (line 101) | public static List<string> GetReplaceMacros<T>(bool usePrefix = true, ...
method Replace (line 122) | public static string Replace<T>(T o, string s, bool usePrefix = true, ...
method Replace (line 153) | public static string Replace(string s, string oldValue, string newValu...
method IndexOf (line 185) | public static int IndexOf(byte[] input, byte[] value, int startIndex = 0)
method GetValue (line 215) | public static string GetValue(string name, string s, string defaultVal...
method ToLiteral (line 229) | public static string ToLiteral(string input)
method FormatLiteral (line 235) | private static string FormatLiteral(string value, bool quote)
method WrapText (line 301) | public static string WrapText(string text, int width, bool useSpaces =...
method BreakLine (line 361) | private static int BreakLine(string text, int pos, int max)
method IdentText (line 385) | public static string IdentText(string s, int tabs = 1, string ident = ...
method RemoveIdent (line 423) | public static string RemoveIdent(string s)
method BytesToStringBlock (line 444) | public static string BytesToStringBlock(string s, bool addIndex, bool ...
method BytesToStringBlock (line 450) | public static string BytesToStringBlock(byte[] bytes, bool addIndex, b...
method CropLines (line 502) | public static string CropLines(string s, int maxLines = 8)
method IBM437ToUTF8 (line 519) | public static string IBM437ToUTF8(byte[] bytes)
method IBM437ToUTF8 (line 528) | public static string IBM437ToUTF8(string text)
method TimeSpanToString (line 546) | public static string TimeSpanToString(TimeSpan ts, bool includeMillise...
method ConvertTimeSpanStandardToISO8601 (line 613) | public static string ConvertTimeSpanStandardToISO8601(string jsonString)
method ConvertTimeSpanISO8601ToStandard (line 635) | public static string ConvertTimeSpanISO8601ToStandard(string jsonString)
FILE: FocusLogger/MainWindow.xaml.cs
class MainWindow (line 7) | public partial class MainWindow : Window
method MainWindow (line 9) | public MainWindow()
method LoadHelpAndInfo (line 16) | void LoadHelpAndInfo()
method Window_Closing (line 27) | private void Window_Closing(object sender, System.ComponentModel.Cance...
FILE: FocusLogger/Resources/Icons/Icons_Default.xaml.cs
class Icons_Default (line 5) | partial class Icons_Default : ResourceDictionary
method Icons_Default (line 7) | public Icons_Default()
Condensed preview — 88 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (960K chars).
[
{
"path": ".ai/ReadMe.md",
"chars": 2945,
"preview": "## Files\n\n- `developer-info.md` - Developer information about the solution, which will be used when creating the reposit"
},
{
"path": ".ai/coding-guideline.instructions.md",
"chars": 8904,
"preview": "# Coding Guidelines\r\n\r\n- If the qdrant-mcp-server is running, use it for all permanent memory operations (e.g. storing "
},
{
"path": ".ai/instructions.md",
"chars": 1663,
"preview": "## Role\n\nYour role is to analyze and improve code by making only localized, targeted changes. You must preserve all val"
},
{
"path": ".ai/repository-analysis.instructions.md",
"chars": 16417,
"preview": "# Repository Analysis\n\n## 1. Repository Overview\n\nThis document provides a factual reference for the Jocys.com FocusLogg"
},
{
"path": ".ai/skills/ai-self-improvement/SKILL.md",
"chars": 4419,
"preview": "---\r\nname: ai-self-improvement\r\ndescription: Update, create, improve, and synchronise this repository's AI agent instruc"
},
{
"path": ".ai/skills/ai-self-improvement/scripts/Sync-AgentAssets.ps1",
"chars": 20137,
"preview": "# Script: Sync-AgentAssets.ps1\r\n# Location: .ai/skills/ai-self-improvement/scripts/Sync-AgentAssets.ps1\r\n# Description:\r"
},
{
"path": ".claude/coding-guideline.instructions.md",
"chars": 8904,
"preview": "# Coding Guidelines\r\n\r\n- If the qdrant-mcp-server is running, use it for all permanent memory operations (e.g. storing "
},
{
"path": ".claude/instructions.md",
"chars": 1663,
"preview": "## Role\n\nYour role is to analyze and improve code by making only localized, targeted changes. You must preserve all val"
},
{
"path": ".claude/repository-analysis.instructions.md",
"chars": 16417,
"preview": "# Repository Analysis\n\n## 1. Repository Overview\n\nThis document provides a factual reference for the Jocys.com FocusLogg"
},
{
"path": ".claude/skills/ai-self-improvement/SKILL.md",
"chars": 4419,
"preview": "---\r\nname: ai-self-improvement\r\ndescription: Update, create, improve, and synchronise this repository's AI agent instruc"
},
{
"path": ".claude/skills/ai-self-improvement/scripts/Sync-AgentAssets.ps1",
"chars": 20137,
"preview": "# Script: Sync-AgentAssets.ps1\r\n# Location: .ai/skills/ai-self-improvement/scripts/Sync-AgentAssets.ps1\r\n# Description:\r"
},
{
"path": ".editorconfig",
"chars": 691,
"preview": "root = true\n\n[*]\ncharset = utf-8\n# Line endings: Let Git handle normalization via .gitattributes and core.autocrlf\n# - R"
},
{
"path": ".github/copilot-instructions.md",
"chars": 1663,
"preview": "## Role\n\nYour role is to analyze and improve code by making only localized, targeted changes. You must preserve all val"
},
{
"path": ".github/instructions/coding-guideline.instructions.md",
"chars": 8904,
"preview": "# Coding Guidelines\r\n\r\n- If the qdrant-mcp-server is running, use it for all permanent memory operations (e.g. storing "
},
{
"path": ".github/instructions/repository-analysis.instructions.md",
"chars": 16417,
"preview": "# Repository Analysis\n\n## 1. Repository Overview\n\nThis document provides a factual reference for the Jocys.com FocusLogg"
},
{
"path": ".github/skills/ai-self-improvement/SKILL.md",
"chars": 4419,
"preview": "---\r\nname: ai-self-improvement\r\ndescription: Update, create, improve, and synchronise this repository's AI agent instruc"
},
{
"path": ".github/skills/ai-self-improvement/scripts/Sync-AgentAssets.ps1",
"chars": 20137,
"preview": "# Script: Sync-AgentAssets.ps1\r\n# Location: .ai/skills/ai-self-improvement/scripts/Sync-AgentAssets.ps1\r\n# Description:\r"
},
{
"path": ".gitignore",
"chars": 6467,
"preview": "## Ignore Visual Studio temporary files, build results, and\r\n## files generated by popular Visual Studio add-ons.\r\n##\r\n#"
},
{
"path": ".markdownlint.json",
"chars": 327,
"preview": "// https://github.com/DavidAnson/markdownlint\n{\n \"default\": true,\n \"MD013\": false, // There are lines that are lon"
},
{
"path": "Documents/App_1_Sign.ps1",
"chars": 316,
"preview": "Import-Module \"d:\\_Backup\\Configuration\\SSL\\Tools\\app_signModule.ps1\" -Force\r\n\r\n[string[]]$appFiles = @(\r\n \"..\\FocusL"
},
{
"path": "Documents/App_2_Zip.ps1",
"chars": 505,
"preview": "\r\n# Make sure the output directories exist\r\n$filesDir = Join-Path $PSScriptRoot \"Files\"\r\n$binDir = Join-Path $PSScriptRo"
},
{
"path": "Documents/Take_Screenshot.ps1",
"chars": 6185,
"preview": "# Take_Screenshot.ps1\n# Launches FocusLogger, Notepad, and Explorer, switches between them\n# to generate focus log entri"
},
{
"path": "Documents/Take_Screenshot.ps1.cs",
"chars": 2023,
"preview": "using System;\nusing System.Drawing;\nusing System.Drawing.Imaging;\nusing System.Runtime.InteropServices;\nusing System.Win"
},
{
"path": "FocusLogger/App.xaml",
"chars": 687,
"preview": "<Application\r\n\tx:Class=\"JocysCom.FocusLogger.App\"\r\n\txmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\r\n"
},
{
"path": "FocusLogger/App.xaml.cs",
"chars": 523,
"preview": "using System;\nusing System.Windows;\n\nnamespace JocysCom.FocusLogger\n{\n\n\tpublic partial class App : Application\n\t{\n\t\tpub"
},
{
"path": "FocusLogger/AssemblyInfo.cs",
"chars": 479,
"preview": "using System.Windows;\r\n\r\n[assembly: ThemeInfo(\r\n\tResourceDictionaryLocation.None, //where theme specific resource dictio"
},
{
"path": "FocusLogger/Common/DataItem.cs",
"chars": 1900,
"preview": "using JocysCom.ClassLibrary.Configuration;\r\nusing System;\r\nusing System.ComponentModel;\r\nusing System.Runtime.CompilerS"
},
{
"path": "FocusLogger/Common/DataItemType.cs",
"chars": 87,
"preview": "namespace JocysCom.FocusLogger\r\n{\r\n\tpublic enum DataItemType\r\n\t{\r\n\t\tNone = 0,\r\n\t}\r\n}\r\n"
},
{
"path": "FocusLogger/Common/NativeMethods.cs",
"chars": 7098,
"preview": "using System;\r\nusing System.Runtime.InteropServices;\r\nusing System.Text;\r\n\r\nnamespace JocysCom.FocusLogger\r\n{\r\n\tinterna"
},
{
"path": "FocusLogger/Controls/DataListControl.xaml",
"chars": 11930,
"preview": "<UserControl\r\n\tx:Class=\" JocysCom.FocusLogger.Controls.DataListControl\"\r\n\txmlns=\"http://schemas.microsoft.com/winfx/2006"
},
{
"path": "FocusLogger/Controls/DataListControl.xaml.cs",
"chars": 9120,
"preview": "using JocysCom.ClassLibrary.ComponentModel;\r\nusing JocysCom.ClassLibrary.Controls;\r\nusing JocysCom.FocusLogger.Resource"
},
{
"path": "FocusLogger/JocysCom/Collections/CollectionsHelper.cs",
"chars": 3062,
"preview": "using System.Collections.Generic;\r\nusing System.Collections.ObjectModel;\r\nusing System.Linq;\r\n\r\nnamespace JocysCom.Class"
},
{
"path": "FocusLogger/JocysCom/Common/Helper.cs",
"chars": 20737,
"preview": "using System;\r\nusing System.Collections.Concurrent;\r\nusing System.Diagnostics;\r\nusing System.IO;\r\nusing System.Linq;\r\nus"
},
{
"path": "FocusLogger/JocysCom/ComponentModel/BindingListInvoked.cs",
"chars": 4890,
"preview": "using System;\r\nusing System.Collections.Generic;\r\nusing System.ComponentModel;\r\nusing System.Reflection;\r\nusing System.T"
},
{
"path": "FocusLogger/JocysCom/ComponentModel/NotifyPropertyChanged.cs",
"chars": 2870,
"preview": "using System;\r\nusing System.ComponentModel;\r\nusing System.Runtime.CompilerServices;\r\nusing System.Threading;\r\n\r\nnamespac"
},
{
"path": "FocusLogger/JocysCom/ComponentModel/PropertyComparer.cs",
"chars": 3818,
"preview": "using System;\r\nusing System.Collections.Generic;\r\nusing System.ComponentModel;\r\n\r\nnamespace JocysCom.ClassLibrary.Compon"
},
{
"path": "FocusLogger/JocysCom/ComponentModel/SortableBindingList.cs",
"chars": 6135,
"preview": "using System;\r\nusing System.Collections.Generic;\r\nusing System.ComponentModel;\r\n\r\nnamespace JocysCom.ClassLibrary.Compo"
},
{
"path": "FocusLogger/JocysCom/Configuration/Arguments.cs",
"chars": 5857,
"preview": "using JocysCom.ClassLibrary.Runtime;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Tex"
},
{
"path": "FocusLogger/JocysCom/Configuration/AssemblyInfo.cs",
"chars": 15744,
"preview": "using System;\r\nusing System.Diagnostics;\r\nusing System.IO;\r\nusing System.Reflection;\r\nusing System.Runtime.InteropServic"
},
{
"path": "FocusLogger/JocysCom/Configuration/ISettingsData.cs",
"chars": 2280,
"preview": "using System;\r\nusing System.ComponentModel;\r\nusing System.IO;\r\n\r\nnamespace JocysCom.ClassLibrary.Configuration\r\n{\r\n\t/// "
},
{
"path": "FocusLogger/JocysCom/Configuration/ISettingsFileItem.cs",
"chars": 1410,
"preview": "using System;\r\nusing System.ComponentModel;\r\nusing System.Xml.Serialization;\r\n\r\nnamespace JocysCom.ClassLibrary.Configur"
},
{
"path": "FocusLogger/JocysCom/Configuration/ISettingsItem.cs",
"chars": 561,
"preview": "using System.ComponentModel;\r\n\r\nnamespace JocysCom.ClassLibrary.Configuration\r\n{\r\n\t\r\n\t/// <summary>Defines a settings it"
},
{
"path": "FocusLogger/JocysCom/Configuration/ISettingsListFileItem.cs",
"chars": 1505,
"preview": "using System;\r\nusing System.Windows.Media;\r\n\r\nnamespace JocysCom.ClassLibrary.Configuration\r\n{\r\n\t/// <summary>Defines a "
},
{
"path": "FocusLogger/JocysCom/Configuration/SettingsData.cs",
"chars": 36716,
"preview": "using JocysCom.ClassLibrary.Collections;\r\nusing JocysCom.ClassLibrary.ComponentModel;\r\nusing JocysCom.ClassLibrary.Runt"
},
{
"path": "FocusLogger/JocysCom/Configuration/SettingsHelper.cs",
"chars": 9871,
"preview": "using System;\r\nusing System.IO;\r\nusing System.IO.Compression;\r\nusing System.Linq;\r\nusing System.Reflection;\r\nusing Syste"
},
{
"path": "FocusLogger/JocysCom/Configuration/SettingsItem.cs",
"chars": 735,
"preview": "using JocysCom.ClassLibrary.ComponentModel;\r\nusing System.ComponentModel;\r\n\r\nnamespace JocysCom.ClassLibrary.Configurati"
},
{
"path": "FocusLogger/JocysCom/Configuration/SettingsParser.cs",
"chars": 5011,
"preview": "using System;\r\nusing System.Linq;\r\n#if NETFRAMEWORK // .NET Framework...\r\nusing System.Configuration;\r\n#endif\r\n#if __MOB"
},
{
"path": "FocusLogger/JocysCom/Controls/ControlsHelper.WPF.UseWindowsForms.cs",
"chars": 4574,
"preview": "using System.Windows;\r\nusing System.Linq;\r\nusing System;\r\nusing System.Windows.Media;\r\nusing System.IO;\r\nusing System.Da"
},
{
"path": "FocusLogger/JocysCom/Controls/ControlsHelper.WPF.cs",
"chars": 35995,
"preview": "using System;\r\nusing System.Collections.Generic;\r\nusing System.ComponentModel;\r\nusing System.IO;\r\nusing System.Linq;\r\nu"
},
{
"path": "FocusLogger/JocysCom/Controls/ControlsHelper.cs",
"chars": 10896,
"preview": "using System;\r\nusing System.Collections.Concurrent;\r\nusing System.Data;\r\nusing System.Diagnostics;\r\nusing System.Linq;\r"
},
{
"path": "FocusLogger/JocysCom/Controls/InfoControl.xaml",
"chars": 3023,
"preview": "<UserControl\r\n\tx:Class=\"JocysCom.ClassLibrary.Controls.InfoControl\"\r\n\txmlns=\"http://schemas.microsoft.com/winfx/2006/xa"
},
{
"path": "FocusLogger/JocysCom/Controls/InfoControl.xaml.cs",
"chars": 7562,
"preview": "using JocysCom.ClassLibrary.Controls.Themes;\r\nusing System;\r\nusing System.ComponentModel;\r\nusing System.Reflection;\r\nus"
},
{
"path": "FocusLogger/JocysCom/Controls/InfoHelpProvider.cs",
"chars": 3932,
"preview": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Text.RegularExpressions;\r\nusing System.Windows;\r\n\r\nnames"
},
{
"path": "FocusLogger/JocysCom/Controls/InitHelper.cs",
"chars": 4440,
"preview": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Diagnostics;\r\nusing System.Windows;\r\nusing System.Linq;\r"
},
{
"path": "FocusLogger/JocysCom/Controls/ItemFormattingConverter.cs",
"chars": 711,
"preview": "using System;\r\nusing System.Globalization;\r\nusing System.Windows.Data;\r\n\r\nnamespace JocysCom.ClassLibrary.Controls\r\n{\r\n"
},
{
"path": "FocusLogger/JocysCom/Controls/MessageBoxWindow.xaml",
"chars": 4020,
"preview": "<Window\r\n\tx:Class=\"JocysCom.ClassLibrary.Controls.MessageBoxWindow\"\r\n\txmlns=\"http://schemas.microsoft.com/winfx/2006/xa"
},
{
"path": "FocusLogger/JocysCom/Controls/MessageBoxWindow.xaml.cs",
"chars": 11881,
"preview": "using System;\r\nusing System.Globalization;\r\nusing System.Windows;\r\nusing System.Windows.Controls;\r\nusing System.Windows"
},
{
"path": "FocusLogger/JocysCom/Controls/TabIndexConverter.cs",
"chars": 1174,
"preview": "using System;\r\nusing System.Globalization;\r\nusing System.Linq;\r\nusing System.Windows;\r\nusing System.Windows.Controls;\r\n"
},
{
"path": "FocusLogger/JocysCom/Controls/Themes/Convert_SVG_to_XAML.ps1",
"chars": 14616,
"preview": "<#\r\n.SYNOPSIS\r\n Convert Folder with SVG image files into XAML Resource file.\r\n.NOTES\r\n Author: Evaldas Jocys <"
},
{
"path": "FocusLogger/JocysCom/Controls/Themes/Default.xaml",
"chars": 80938,
"preview": "<ResourceDictionary\r\n\txmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\r\n\txmlns:x=\"http://schemas.micro"
},
{
"path": "FocusLogger/JocysCom/Controls/Themes/Default_MultiReplace.ps1",
"chars": 2205,
"preview": "<#\r\n.SYNOPSIS\r\n Replace in multiple files.\r\n.NOTES\r\n Author: Evaldas Jocys <evaldas@jocys.com>\r\n Modified: "
},
{
"path": "FocusLogger/JocysCom/Controls/Themes/Icons.xaml",
"chars": 105040,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n<ResourceDictionary\r\n\tx:Class=\"JocysCom.ClassLibrary.Controls.Themes.Icons\"\r\n\t"
},
{
"path": "FocusLogger/JocysCom/Controls/Themes/Icons.xaml.cs",
"chars": 2356,
"preview": "using System.Windows;\r\n\r\nnamespace JocysCom.ClassLibrary.Controls.Themes\r\n{\r\n\tpartial class Icons : ResourceDictionary\r\n"
},
{
"path": "FocusLogger/JocysCom/Controls/ToolStripBorderlessRenderer.cs",
"chars": 579,
"preview": "using System.Windows.Forms;\r\n\r\nnamespace JocysCom.ClassLibrary.Controls\r\n{\r\n\t/// <summary>\r\n\t/// Remove white borders f"
},
{
"path": "FocusLogger/JocysCom/Data/SqlHelper.Types.cs",
"chars": 6515,
"preview": "using System;\r\n#if NETFRAMEWORK\r\nusing System.Data.SqlClient;\r\n#else\r\n#endif\r\n\r\nnamespace JocysCom.ClassLibrary.Data\r\n{\r"
},
{
"path": "FocusLogger/JocysCom/IO/PathHelper.cs",
"chars": 8101,
"preview": "using System;\r\nusing System.Collections.Generic;\r\nusing System.IO;\r\nusing System.Linq;\r\n\r\nnamespace JocysCom.ClassLibrar"
},
{
"path": "FocusLogger/JocysCom/MakeLinks_Ref.ps1",
"chars": 510,
"preview": "<#\r\n.SYNOPSIS\r\n Thin wrapper that calls the original MakeLinks.ps1 from the Core library.\r\n Place this file in ext"
},
{
"path": "FocusLogger/JocysCom/Runtime/RuntimeHelper.cs",
"chars": 22382,
"preview": "using System;\r\nusing System.Collections.Concurrent;\r\nusing System.Collections.Generic;\r\nusing System.Data;\r\nusing Syste"
},
{
"path": "FocusLogger/JocysCom/Runtime/Serializer.cs",
"chars": 23580,
"preview": "using System;\r\nusing System.Collections.Concurrent;\r\nusing System.Collections.Generic;\r\nusing System.IO;\r\nusing System.L"
},
{
"path": "FocusLogger/JocysCom/Text/Helper.cs",
"chars": 21806,
"preview": "using System;\r\nusing System.Collections.Generic;\r\nusing System.IO;\r\nusing System.Linq;\r\nusing System.Text;\r\nusing System"
},
{
"path": "FocusLogger/JocysCom.FocusLogger.csproj",
"chars": 2072,
"preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\r\n\r\n <PropertyGroup>\r\n <OutputType>WinExe</OutputType>\r\n <TargetFramework>net8."
},
{
"path": "FocusLogger/MainWindow.xaml",
"chars": 995,
"preview": "<Window\r\n\tx:Class=\"JocysCom.FocusLogger.MainWindow\"\r\n\txmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\""
},
{
"path": "FocusLogger/MainWindow.xaml.cs",
"chars": 666,
"preview": "using JocysCom.ClassLibrary.Controls;\nusing System.Reflection;\nusing System.Windows;\n\nnamespace JocysCom.FocusLogger\n{\n"
},
{
"path": "FocusLogger/Resources/AiAnalysisPrompt.md",
"chars": 1178,
"preview": "I have a Focus Logger CSV file that records which Windows process or program takes window focus.\nEach row contains: Date"
},
{
"path": "FocusLogger/Resources/Icons/Convert_SVG_to_XAML.ps1",
"chars": 14533,
"preview": "<#\r\n.SYNOPSIS\r\n Convert Folder with SVG image files into XAML Resource file.\r\n.NOTES\r\n Author: Evaldas Jocys <"
},
{
"path": "FocusLogger/Resources/Icons/IconExperience.License.txt",
"chars": 1676,
"preview": "License Agreement (G-Collection v2.0)\r\n\r\nThis is a legal agreement between you, the purchaser, and INCORS GmbH (\"INCORS\""
},
{
"path": "FocusLogger/Resources/Icons/Icons_Default.xaml",
"chars": 50973,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n<ResourceDictionary\r\n\tx:Class=\"JocysCom.FocusLogger.Resources.Icons.Icons_Defau"
},
{
"path": "FocusLogger/Resources/Icons/Icons_Default.xaml.cs",
"chars": 693,
"preview": "using System.Windows;\r\n\r\nnamespace JocysCom.FocusLogger.Resources.Icons\r\n{\r\n\tpartial class Icons_Default : ResourceDicti"
},
{
"path": "FocusLogger.Tests/CsvExportTests.cs",
"chars": 2770,
"preview": "using JocysCom.FocusLogger;\nusing JocysCom.FocusLogger.Controls;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusi"
},
{
"path": "FocusLogger.Tests/JocysCom.FocusLogger.Tests.csproj",
"chars": 588,
"preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n <PropertyGroup>\n <TargetFramework>net8.0-windows</TargetFramework>\n <UseWPF>t"
},
{
"path": "FocusLogger.Tests/UIAutomationTests.cs",
"chars": 3613,
"preview": "using Microsoft.VisualStudio.TestTools.UnitTesting;\nusing System;\nusing System.Diagnostics;\nusing System.IO;\nusing Syste"
},
{
"path": "JocysCom.FocusLogger.slnx",
"chars": 161,
"preview": "<Solution>\r\n <Project Path=\"FocusLogger/JocysCom.FocusLogger.csproj\" />\r\n <Project Path=\"FocusLogger.Tests/JocysCom.Fo"
},
{
"path": "LICENSE",
"chars": 35149,
"preview": " GNU GENERAL PUBLIC LICENSE\n Version 3, 29 June 2007\n\n Copyright (C) 2007 Free "
},
{
"path": "README.md",
"chars": 796,
"preview": "# Jocys.com Focus Logger\n\nFind out which process or program is taking the window focus.\n\nIn game, mouse and keyboard cou"
},
{
"path": "Resources/ZipFiles.ps1",
"chars": 12425,
"preview": "param (\r\n [Parameter(Mandatory = $true, Position = 0)]\r\n [string] $sourceDir,\r\n\r\n [Parameter(Mandatory = $true,"
},
{
"path": "SECURITY.md",
"chars": 153,
"preview": "# Security Policy\n\n## Reporting a Vulnerability\n\nPlease report (suspected) security vulnerabilities to\n**[support@jocys."
},
{
"path": "Settings.XamlStyler",
"chars": 1850,
"preview": "{\r\n \"AttributesTolerance\": 3,\r\n \"KeepFirstAttributeOnSameLine\": false,\r\n \"MaxAttributeCharactersPerLine\": 0,\r\n \"MaxA"
},
{
"path": "Solution_Cleanup.ps1",
"chars": 11594,
"preview": "<#\r\n.SYNOPSIS\r\n\t\tRemoves temporary bin and obj folders.\r\n\t\tKill and clear IIS Express configuration.\r\n\t\tRemoves temporar"
}
]
About this extraction
This page contains the full source code of the JocysCom/FocusLogger GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 88 files (826.0 KB), approximately 228.1k tokens, and a symbol index with 474 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.