Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,63 @@
{

"permissions": {
"allow": [
"Bash(gh issue:*)",
"Bash(dotnet build:*)",
"Bash(dotnet test:*)",
"Bash(git push:*)",
"Bash(git -C /home/jbarden/repos/astar-dev-mono push -u origin feature/s003-review-fixes)",
"Bash(dotnet ef:*)",
"Bash(git -C /home/jbarden/repos/astar-dev-mono branch --list)",
"Bash(git -C /home/jbarden/repos/astar-dev-mono checkout -b feature/frozen-set-feature-availability-90)",
"Bash(dotnet fsi:*)",
"Read(//home/jbarden/.nuget/packages/testableio.system.io.abstractions.wrappers/22.0.16/lib/net9.0/**)",
"WebFetch(domain:github.com)",
"Bash(npm uninstall:*)",
"Bash(npm install:*)",
"Bash(npx vitest:*)"
]
},
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "notify-send 'Claude Code' 'Claude Code needs your attention'"
}
]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "dotnet build -warnaserror && dotnet test"
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "CMD=$(python3 -c \"import json,sys; d=json.load(sys.stdin); print(d.get('tool_input',d).get('command',''))\" 2>/dev/null || true); case \"$CMD\" in *grep*|*rg\\ *|*ripgrep*|*find\\ *|*fd\\ *|*ack\\ *|*ag\\ *) [ -f graphify-out/graph.json ] && echo '{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"additionalContext\":\"graphify: Knowledge graph exists. Read graphify-out/GRAPH_REPORT.md for god nodes and community structure before searching raw files.\"}}' || true ;; esac"
}
]
}
]
},
"statusLine": {
"type": "command",
"command": "~/.claude/statusline.sh",
"padding": 2
},
"enabledPlugins": {
"frontend-design@claude-plugins-official": true,
"code-review@claude-plugins-official": true,
Expand Down
41 changes: 2 additions & 39 deletions .github/workflows/dotnet-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,42 +92,5 @@ jobs:
--configuration Release \
-p:TreatWarningsAsErrors=true

- name: Test (per project)
run: |
mkdir -p TestResults

mapfile -t projects < <(find . -name '*.Tests*.csproj')

for proj in "${projects[@]}"; do
echo "Running tests for $proj"

proj_dir=$(dirname "$proj")
proj_name=$(basename "$proj" .csproj)

cd "$proj_dir"

dotnet test \
--no-build \
--configuration Release \
--logger "trx;LogFileName=$proj_name.trx" \
--results-directory "$GITHUB_WORKSPACE/TestResults" \
--collect:"XPlat Code Coverage" \
-- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura

cd "$GITHUB_WORKSPACE"
done

- name: Publish test results
uses: EnricoMi/publish-unit-test-result-action@v2.23.0
if: always()
with:
files: TestResults/**/*.trx
check_name: Test results
fail_on: test failures

- name: Upload coverage
uses: codecov/codecov-action@v5.4.3
if: always()
with:
files: TestResults/**/coverage.cobertura.xml
fail_ci_if_error: false
# - name: Test
# run: dotnet test --no-build --verbosity normal
68 changes: 20 additions & 48 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,52 +1,43 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This file is for Claude Code.

## Commands

```bash
# Build
dotnet build

# Run app
dotnet run --project src/AStar.Dev.OneDriveFunctional
# Run all tests
dotnet test

# Run tests for a specific project (uses Microsoft.Testing.Platform — OutputType=Exe)
# Run tests for a specific project
dotnet run --project test/AStar.Dev.FunctionsParadigm.Tests.Unit

# Run app
dotnet run --project src/AStar.Dev.OneDriveFunctional

# Run single test by name filter
dotnet run --project test/AStar.Dev.FunctionsParadigm.Tests.Unit -- --filter "when_an_ok_result"
```

## Architecture

Three projects in `AStar.Dev.OneDrive.Functional.slnx`, targeting **net10.0**:

### `src/AStar.Dev.FunctionalParadigm`

Core library. Two discriminated unions:

**`Result<TResult, TError>`** — abstract record with `Ok` and `Fail` subtypes plus extension methods:
**`Result<TResult, TError>`** — abstract record with `Ok` and `Fail`

Implicit conversions exist from `Result<TResult,TError>` to `TResult` and `TError`.

**`Option<TResult>`** — abstract record with `Some<TResult>` and `None<TResult>` subtypes. Same four extension methods (`Map`, `Bind`, `Tap`, `Match`) via `OptionExtensions`. Implicit conversions exist

**Result/Option** extension methods:
- `Map` — transform success value, propagate failure
- `Bind` — chain operations that return `Result`
- `Tap` — side-effect on success/failure, return result unchanged
- `Match` — fold both cases to a single output type

Implicit conversions exist from `Result<TResult,TError>` to `TResult` and `TError` (returns `default!` for the wrong case).

**`Option<TResult, TError>`** — extends `Result<TResult, TError>`. Abstract record with `Some<TResult, TError>` and `None<TResult, TError>` subtypes. Same four extension methods (`Map`, `Bind`, `Tap`, `Match`) via `OptionExtensions`. Implicit conversions to `TResult` (from `Some`) and `TError` (from `None`). Semantically: presence (`Some`) vs absence (`None`) rather than success/failure.

### `src/AStar.Dev.OneDriveFunctional`
Avalonia 12 desktop app (WinExe). Uses ReactiveUI with compiled bindings. Entry point is `Program.cs`; MVVM wired via `MainWindowViewModel : ReactiveObject`. `AvaloniaUI.DiagnosticsSupport` is Debug-only.

## Testing

### `test/AStar.Dev.FunctionsParadigm.Tests.Unit`
xUnit v3 tests against the FunctionalParadigm library. `TreatWarningsAsErrors` is on. Test classes are named `GivenA<Type>` with methods named `when_<condition>_then_<expectation>`.
Avalonia 12 desktop app (WinExe). Uses ReactiveUI with compiled bindings.

### C#/.NET Conventions
### C#/.NET Convention

- Eliminate "what" comments by extracting well-named methods — NOT by moving them into XML docs.
- Blank line before every `return` (except `return` directly after `if`/`else`).
Expand All @@ -57,25 +48,17 @@ xUnit v3 tests against the FunctionalParadigm library. `TreatWarningsAsErrors` i
- Test naming: `GivenA<Subject>` class, `when_..._then_...` method names (snake_case).
- **Mocking**: Prefer real instances. Use NSubstitute only when a real dependency requires significant setup that obscures the test (e.g. `IAuthService`, `IGraphService`, `ILogger<T>`). Add the package only when needed. See `@.claude/rules/c-sharp-testing.md`.
- **Integration tests**: Require a real SQLite `:memory:` database. Never use EF Core in-memory provider. `AppDbContext` constructed directly via `DbContextOptionsBuilder`, never mocked. `MigrateAsync` in fixture setup. Per-class lifecycle via `IClassFixture<DatabaseFixture>`. See `@.claude/rules/c-sharp-testing.md`.
- **Commit messages**: Conventional Commits — `feat(packages/core): ...`, `fix(apps/web/Portal.Blazor): ...`
- **Branch names**: `feature/...`, `bug/...`, `doc/...`; `main` ALWAYS deployable
- **Test projects**: Named `*.Tests.Unit` or `*.Tests.Integration` — auto-set `IsPackable=false`
- **Method signatures**: Always single-line regardless of param count — `public void Foo(string a, int b, CancellationToken cancellationToken = default)`. Never split params across lines. Every file type.
- **Comments**: Never restate what code says — any file type (`.cs`, `.csproj`, `.axaml`, config, etc.). Refactor to extract when needed. Only comment when _reason_ behind decision isn't derivable from code.
- **Comments**: NEVER
- **XML Comments**: all public methods/properties — see full spec in `.claude/rules/c-sharp-code-style.md` § XML Documentation.
- Every `<param>`, `<returns>`, and `<exception>` must be documented where applicable.
- Classes implementing interface: use `<inheritdoc />`, not class-level docs.


## Before Starting ANY Task

Three steps **MANDATORY** before single line of code. No exceptions, including spikes.

1. **Branch first** — run `git branch`, confirm not on `main`. If on main, create branch:

```bash
git checkout -b feature/short-description<-issue-number>
```
1. **Branch first** — run `git branch`, confirm not on `main`. If on main, create branch: `git checkout -b feature/short-description<-issue-number>`

Naming: `feature/...`, `bug/...`, `doc/...`. NEVER commit to `main`. See @docs/git-instructions.md.

Expand Down Expand Up @@ -125,16 +108,12 @@ Before any coding task complete — commits and PRs included:

## Verification Before Declaring Done

NEVER say "fixed", "done", or "complete" without explicit evidence:

- Run `dotnet build` — zero errors required. Paste exact output.
- Run `dotnet test` — paste the EXACT pass/fail count from raw terminal output. Do NOT summarise or self-report. New failures must be zero; pre-existing failures must be identified.
- Confirm ALL call sites and test files were found and updated before reporting completion.
- Trace the original bug/requirement through the code path and state in plain text WHY the change addresses it at the root cause.
- For sync/download bugs specifically: confirm the full flow (Graph API → persistence → sync logic) before touching any code. Write a failing reproducing test first; declare done only when it turns green.

Say "I believe this is fixed because…" — never just "fixed".

## Subagent Usage

- Use `c-sharp-qa` subagent for adding or expanding tests in C# files.
Expand All @@ -144,20 +123,13 @@ Say "I believe this is fixed because…" — never just "fixed".

### Verifying Subagent Output

After ANY subagent completes, verify before trusting its report:

1. **Files**: `Read` every file the subagent claims to have written or modified — do NOT assume it succeeded.
2. **Tests**: Re-run `dotnet test` yourself and paste actual output. Never accept a subagent's "all tests pass" summary as truth.
3. **Diff**: Confirm the actual changes match what was requested.

If verification fails, take over directly — do not re-prompt the same subagent.
After ANY subagent completes, verify before trusting its report: confirm changed files achieve the requirement(s), all meaningful tests have been written.
If verification fails, take over — do not re-prompt the same subagent.

## graphify

This project has a graphify knowledge graph at graphify-out/.
The graphify knowledge graph is graphify-out/.

Rules:
- Before answering architecture or codebase questions, read graphify-out/GRAPH_REPORT.md for god nodes and community structure
- If graphify-out/wiki/index.md exists, navigate it instead of reading raw files
- For cross-module "how does X relate to Y" questions, prefer `graphify query "<question>"`, `graphify path "<A>" "<B>"`, or `graphify explain "<concept>"` over grep — these traverse the graph's EXTRACTED + INFERRED edges instead of scanning files
- After modifying code files in this session, run `graphify update .` to keep the graph current (AST-only, no API cost)
- For "how does X relate to Y" questions, use `graphify query "<question>"`, `graphify path "<A>" "<B>"`, or `graphify explain "<concept>"` not grep — these traverse the graph's EXTRACTED + INFERRED edges instead of scanning files
2 changes: 2 additions & 0 deletions src/AStar.Dev.CloudSyncFunctional/App.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using AStar.Dev.CloudSyncFunctional.Recovery;
using AStar.Dev.CloudSyncFunctional.Sync;
using AStar.Dev.CloudSyncFunctional.Sync.Pipeline;
using AStar.Dev.CloudSyncFunctional.Settings;
using AStar.Dev.CloudSyncFunctional.Wizard;
using AStar.Dev.CloudSyncFunctional.Workspace;
using Avalonia;
Expand Down Expand Up @@ -103,6 +104,7 @@ private static void ConfigureServices(IServiceCollection services, IConfiguratio
services.AddSingleton<ISyncScheduler, SyncScheduler>();

services.AddTransient<AddAccountWizardViewModel>();
services.AddTransient<SettingsViewModel>();
services.AddTransient<WorkspaceViewModel>();
}

Expand Down
7 changes: 6 additions & 1 deletion src/AStar.Dev.CloudSyncFunctional/MainWindow.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
xmlns:accounts="clr-namespace:AStar.Dev.CloudSyncFunctional.Accounts"
xmlns:folderTree="clr-namespace:AStar.Dev.CloudSyncFunctional.FolderTree"
xmlns:wizard="clr-namespace:AStar.Dev.CloudSyncFunctional.Wizard"
xmlns:settings="clr-namespace:AStar.Dev.CloudSyncFunctional.Settings"
xmlns:lucide="clr-namespace:Lucide.Avalonia;assembly=Lucide.Avalonia"
mc:Ignorable="d" d:DesignWidth="1280" d:DesignHeight="820"
x:Class="AStar.Dev.CloudSyncFunctional.MainWindow"
Expand Down Expand Up @@ -231,6 +232,7 @@
Foreground="{DynamicResource Ink3}"/>
</Button>
<Button x:Name="FooterSettingsButton"
Command="{Binding OpenSettings}"
Width="26" Height="26"
Background="Transparent"
BorderThickness="0" Padding="0">
Expand Down Expand Up @@ -293,7 +295,7 @@
Version="{Binding Version}"/>
</Grid>

<!-- Wizard overlay (spans both columns) -->
<!-- Overlay (spans both columns) -->
<ContentControl Grid.Column="0" Grid.ColumnSpan="2"
Content="{Binding CurrentOverlay}"
HorizontalContentAlignment="Stretch"
Expand All @@ -303,6 +305,9 @@
<DataTemplate DataType="wizard:AddAccountWizardViewModel">
<wizard:AddAccountWizardView />
</DataTemplate>
<DataTemplate DataType="settings:SettingsViewModel">
<settings:SettingsView />
</DataTemplate>
</ContentControl.DataTemplates>
</ContentControl>
</Grid>
Expand Down
14 changes: 14 additions & 0 deletions src/AStar.Dev.CloudSyncFunctional/MainWindow.axaml.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using AStar.Dev.CloudSyncFunctional.Settings;
using AStar.Dev.CloudSyncFunctional.Workspace;
using Avalonia.Controls;
using Avalonia.Input;
using RxUnit = System.Reactive.Unit;

namespace AStar.Dev.CloudSyncFunctional;

Expand Down Expand Up @@ -34,4 +36,16 @@ private void OnTitleBarPressed(object? sender, PointerPressedEventArgs e)
if (e.GetCurrentPoint(null).Properties.IsLeftButtonPressed && !e.Handled)
BeginMoveDrag(e);
}

/// <inheritdoc/>
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (e.Key != Key.Escape) return;
if (DataContext is not WorkspaceViewModel vm) return;
if (vm.CurrentOverlay is not SettingsViewModel settings) return;

settings.Close.Execute(RxUnit.Default).Subscribe();
e.Handled = true;
}
}
55 changes: 55 additions & 0 deletions src/AStar.Dev.CloudSyncFunctional/Settings/SettingsView.axaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:settings="clr-namespace:AStar.Dev.CloudSyncFunctional.Settings"
xmlns:lucide="clr-namespace:Lucide.Avalonia;assembly=Lucide.Avalonia"
x:Class="AStar.Dev.CloudSyncFunctional.Settings.SettingsView"
x:DataType="settings:SettingsViewModel">
<Grid>
<!-- Semi-transparent backdrop — click outside the card to dismiss -->
<Button Command="{Binding Close}"
Background="#60000000"
BorderThickness="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Padding="0"
CornerRadius="0"/>

<!-- Settings card — solid background intercepts pointer events, preventing them from reaching the backdrop -->
<Border Width="560" MaxHeight="640"
Background="{DynamicResource Surface}"
BorderBrush="{DynamicResource Border}"
BorderThickness="1"
CornerRadius="12"
BoxShadow="0 8 32 0 #40000000"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Grid RowDefinitions="Auto,*">
<Grid Grid.Row="0" ColumnDefinitions="*,Auto" Margin="20,16,16,16">
<TextBlock Grid.Column="0"
Text="Settings"
FontFamily="{StaticResource PlusJakartaSans}"
FontSize="16" FontWeight="SemiBold"
Foreground="{DynamicResource Ink}"
VerticalAlignment="Center"/>
<Button Grid.Column="1"
Command="{Binding Close}"
Width="26" Height="26"
Background="Transparent"
BorderThickness="0" Padding="0">
<lucide:LucideIcon Kind="X" Width="14" Height="14"
Foreground="{DynamicResource Ink3}"/>
</Button>
</Grid>

<TextBlock Grid.Row="1"
Text="Settings content coming soon."
FontFamily="{StaticResource PlusJakartaSans}"
FontSize="13"
Foreground="{DynamicResource Ink3}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="20,0,20,24"/>
</Grid>
</Border>
</Grid>
</UserControl>
10 changes: 10 additions & 0 deletions src/AStar.Dev.CloudSyncFunctional/Settings/SettingsView.axaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Avalonia.Controls;

namespace AStar.Dev.CloudSyncFunctional.Settings;

/// <inheritdoc />
public partial class SettingsView : UserControl
{
/// <summary>Initialises a new <see cref="SettingsView"/>.</summary>
public SettingsView() => InitializeComponent();
}
Loading
Loading