upd framework, added projects

This commit is contained in:
beo3000 2026-02-15 19:56:18 +01:00
parent 2d93e188ec
commit 12ab9cea00
18 changed files with 461 additions and 4 deletions

1
.gitignore vendored
View File

@ -1,6 +1,7 @@
# OS
Thumbs.db
.DS_Store
nul
# Temp
*.bak

View File

@ -50,7 +50,6 @@
],
"defaultMode": "acceptEdits"
},
"model": "sonnet",
"enabledPlugins": {
"csharp-lsp@claude-plugins-official": true
},

View File

@ -1,6 +1,16 @@
{
"model": { "name": "gemini-2.5-pro" },
"model": {
"name": "gemini-2.5-pro"
},
"context": {
"includeDirectories": ["C:/work/claude-meta/memory"]
"includeDirectories": [
"C:/work/claude-meta/memory"
]
},
"hasSeenIdeIntegrationNudge": true,
"security": {
"auth": {
"selectedType": "oauth-personal"
}
}
}
}

View File

View File

@ -0,0 +1,20 @@
# GoodWood App Notes
## Architecture
- Blazor Server (.NET 9), Clean Architecture: Domain → Application → Infrastructure → Web
- Fluxor (Redux): Store/{Feature}State/{Actions,Reducers,Effects,State}.cs pattern
- AutoMapper profiles in Application/Mapping/
- EF migrations: `dotnet ef migrations add [Name] --project src/GoodWood.Infrastructure --startup-project src/GoodWood.Web --context AppDbContext --output-dir Data/Migrations`
- Dual DbContext: AppDbContext (app schema), AppIdentityDbContext (auth schema)
## Code-behind pattern
- Partial classes: Component.razor + Component.razor.cs
- [Inject] needs `using Microsoft.AspNetCore.Components;` in .cs files (not auto-imported unlike .razor)
## SEPA Export (added 2026-02-08)
- pain.008.003.02 XML generated by SepaXmlBuilder (System.Xml, no external lib)
- Download via JS: `window.downloadFile` in wwwroot/js/fileDownload.js
- SepaExport entity links CashBookEntries via nullable FK SepaExportId
- PaymentStatus enum on CashBookEntry: Open/DirectDebitCollected/Paid
- Person gets: Iban, Bic, SepaMandateReference, SepaMandateDate
- Club gets: BankIban, BankBic, SepaCreditorId

View File

@ -0,0 +1,29 @@
# ISMS-DB Project Memory
## Skill Triggers
- "aktualisiere unser ISMS", "ISMS aktualisieren", "ISMS updaten" → ALWAYS invoke Skill `isms-update`
- This skill runs: Fetch-Data.ps1 → edit JSON → Upload-Data.ps1
- NEVER edit tenant data (Data/default/*.json) without running fetch first
- NEVER skip upload after editing
## Auth Stack
- Microsoft.Identity.Web 3.x (resolved 3.14.1)
- Use `AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme).AddMicrosoftIdentityWebApp(...)` — NOT `services.AddMicrosoftIdentityWebApp(...)` (IServiceCollection overload removed in 3.x)
- `using Microsoft.Identity.Web.UI;` required in Program.cs for `AddMicrosoftIdentityUI()`
- Use `PostConfigure<OpenIdConnectOptions>` to hook `OnTokenValidated` without clobbering MSIW's own handler
## SecurePageModel Pattern
- Inherits `PageModel`, uses `override` on `OnPageHandlerSelectionAsync` / `OnPageHandlerExecutionAsync`
- Do NOT declare `IAsyncPageFilter` explicitly — `PageModel` already has virtual methods
- Resolves `IUserService` + `TenantService` via `ctx.HttpContext.RequestServices`
## Audit Result Writing
- NEVER delegate JSON creation to a Task subagent — too slow (33 calls, 17 min)
- Build the JSON object in main context, then pipe to `append-audit-result.py`:
`python3 append-audit-result.py src/IsmsApp/Data/default/ <<'JSONEOF' ... JSONEOF`
- Script validates: .lock, control exists, Id unique, Iteration unique
- For the JSON heredoc: use python3 inline to generate UUIDs + timestamps, then echo
## Razor Gotcha
- `RZ1031`: cannot put free-standing `@(expr)` in `<option>` attribute area
- Fix: use `@if (cond) { <option selected> } else { <option> }` pattern instead

View File

View File

View File

View File

@ -0,0 +1,15 @@
# Claude Meta Memory
## claude-meta Framework
- Repo: `C:\work\claude-meta\`
- Verlinkt nach `~/.claude/` via Symlinks (Dateien) + Junctions (Verzeichnisse)
- NIE `~/.claude/` selbst symlinken (Bug #764)
- Junctions brauchen kein Admin, aber gleiches Volume
- settings.json Permissions gelten global für alle Projekte
- @imports in CLAUDE.md werden on-demand geladen (token-effizient)
- `docs/FRAMEWORK.md` enthält vollständige Doku
## Windows Symlinks
- File-Symlinks brauchen Developer Mode
- Junctions (`mklink /J`) brauchen KEIN Admin, aber gleiches Volume
- Claude Code Issue #3575: symlinked settings.json hatte Bugs (reportedly fixed)

View File

View File

View File

@ -0,0 +1,34 @@
# KbsV3 Memory
## Architecture Guide
- See [kbsv3-architecture.md](kbsv3-architecture.md) for: Job system, SAP RFC calls, gRPC communication, DB read/write patterns
## Plotly.Blazor (v7.0.0)
- `ModeFlag` namespace: `Plotly.Blazor.Traces.ScatterLib`
- `XAxis.Type` needs `Plotly.Blazor.LayoutLib.XAxisLib.TypeEnum.Date` (not string)
- `Title` is ambiguous between `LayoutLib` and `LayoutLib.XAxisLib` — use fully qualified
- Events (relayout) not implemented in library — use JS interop via `el.on('plotly_relayout', ...)`
## Service Layer Pattern
- Services use `JobDbContext` injected directly
- Interface in `Contract/`, implementation in `Services/`
- Register in `Program.cs` as `builder.Services.AddScoped<IFoo, Foo>()`
## SAP NCo3 IRfcFunction
- `IRfcFunction` does NOT implement `IDisposable``using var fnc` causes CS1674 compile error
- NCo3 manages memory via GC; `fnc` vars in method scope are eligible for collection after method returns
- Fix 1 from memory-optimization plan is NOT applicable via `using`; no explicit disposal mechanism available
## Job Worker Patterns (IKbsJob vs ILongRunningJobService)
- **All KnownJobs** in Kbs3SqlService use `IKbsJob` (NOT `ILongRunningJobService`)
- `IKbsJob` runs via `LongRunningJobServiceHost` with `JobType.CallServiceHost`
- Auto-registered as keyed service by class name (e.g. `"SyncOpexPurchaseOrdersService"`)
- `IKnownJob.ServiceName` format: `"modulename.ClassName"` → host splits on `.` and resolves last segment
- Do NOT add new `JobType` enum entries for `IKbsJob` workers — they all share `CallServiceHost`
- `ILongRunningJobService` is for services like AD sync, Hydra — they have dedicated JobType enum entries
## HydraArchiveData
- Entity: HydraArchiveReloadId, AcquisitionTime, PdatrefPparamDatasupId, DecimalValue
- Covering index: (HydraArchiveReloadId, AcquisitionTime, PdatrefPparamDatasupId)
- SQL bucket aggregation: DATEDIFF/DATEADD pattern for time-bucket grouping
- Raw SQL via `_dbContext.Database.SqlQueryRaw<T>(sql, params)`

View File

@ -0,0 +1,122 @@
# KbsV3 Architecture Patterns
## 1. Job System (IKnownJob → Worker)
### Creating a new Job (full checklist)
1. **JobType enum**`GrpcProtos/GrpcServices/Contracts/JobType.cs` — add `[StringValue("X")] X`
2. **IKnownJob**`GrpcProtos/GrpcServices/KnownJobs/MyJob.cs` — metadata class
- `ServiceName` format: `"modulename.WorkerClassName"` (e.g. `"sqlsync.UpdateCalcPriceService"`)
- Module names: `helpdesk`, `sqlsync`, `ad`, `azure`, `docuware`, `sap`, `scheduler`
3. **Worker**`Kbs3XxxService/Services/MyWorker.cs` — implements `ILongRunningJobService`
- Constructor: `ProtocolLogger`, `IApiComposer`, plus custom DI
- `JobType` property returns `GetJobType()` (static method using `JobType.X.GetStringValue()`)
- `Run(JobDataRequest, CancellationToken)` → business logic
- `GetHeadline()` → section title
4. **Auto-registration**`IntegrationServiceBase.AutoInjectServices()` discovers workers automatically
5. **No changes needed** in Program.cs of the microservice
### Worker capabilities
- `ProtocolLogger.LogHeadline/LogInfo/LogWarn/LogError` — streams to UI in real-time
- `apiComposer` — callback to server (see section 3)
- `request.InputData` — string input (usually username or JSON payload)
### IKnownJob with Payload
- `Payload` property returns a typed object (serialized as InputData)
- Example: `UpdateCalculationPriceDatabase.cs` returns `UpdateCalcPriceRequest`
## 2. SAP Data Access (RFC Calls)
### Where RFC calls live
- **Kbs3SapService/SapServices/** — e.g. `OrderService.cs`, `FiService.cs`, `TableService.cs`
- Uses SAP NCo3: `RfcDestinationManager.GetDestination(systemName)``repos.CreateFunction("Z_FUNC")``fnc.Invoke(dest)`
- `IRfcFunction` does NOT implement IDisposable
### RFC call pattern
```csharp
var dest = RfcDestinationManager.GetDestination(request.SystemName);
var repos = dest.Repository;
var fnc = repos.CreateFunction("Z_RFC_NAME");
fnc.SetValue("PARAM", value); // importing params
fnc.Invoke(dest);
var itResult = fnc.GetTable("ET_TABLE"); // exporting table
for (var i = 0; i < itResult.RowCount; i++) {
itResult.CurrentIndex = i;
var val = itResult.GetString("FIELD");
}
```
### Extension methods (Kbs3SapService/Extensions/)
- `DateTime.ToSapDate()` → SAP date string
- `itResult.GetDateTimeOrNull("FIELD")` → nullable DateTime
- `itResult.GetDecimal("FIELD")`, `GetBoolean("FIELD")`, etc.
### gRPC service wrapper
- `Kbs3SapService/GrpcServices/SapOrderService.cs` — thin proxy implementing `ISapOrderService`
- Delegates to the actual `OrderService` (SapServices layer)
## 3. gRPC Communication Between Modules
### Contract interfaces
- **Location:** `GrpcProtos/GrpcServices/Contracts/SapService/` (e.g. `ISapOrderService.cs`)
- Attributes: `[ServiceContract]`, `[OperationContract]`
- DTOs: `[DataContract]` + `[DataMember(Order = N)]`
### DTO conventions
- Namespace: `GrpcProtos.GrpcServices.Model` (data classes) or `Contracts/SapService/` (request/response)
- Collections: `Type[] prop = []`
- Nullable refs: `string?`, `DateTime?`
- Sequential `[DataMember(Order = N)]` starting at 1
- `BapiMessage[]` for error handling
### Server → Microservice (gRPC Client)
- **Location:** `Kbs3ServerService/GrpcClients/` (e.g. `SapOrderServiceClient.cs`)
- Inherits `GrpcClientBase<ISapOrderService>`
- Constructor: `(GrpcChannel, ServiceRegistry, IServiceScopeFactory)`
- Methods: `return Client?.MethodName(request);`
### Microservice → Server (IApiComposer callback)
- **Interface:** `GrpcProtos/GrpcServices/Contracts/Server/IApiComposer.cs`
- **Implementation:** in Kbs3ServerService (ApiComposer class)
- Workers use `apiComposer.GetUser()`, `apiComposer.GetCalcPriceInfos()`, etc.
- To add new callback: add method to IApiComposer → implement in server ApiComposer
## 4. Database Read/Write
### Server-side (EF Core)
- **DbContext:** `Kbs3ServerService.Repository/Contexts/JobDbContext.cs` (inherits `ThreadSafeDbContext`)
- **Entities:** `Kbs3ServerService.Repository/Entities/`
- **Base classes:** `AuditableEntity` (manual audit) or `KbEntity` (auto SQL defaults via GETUTCDATE())
- **Global decimal precision:** 18,4
- **Configurations:** `IEntityTypeConfiguration<T>` via `ApplyAllConfigurations()`
- **Service layer:** `Kbs3ServerService.Service/Services/` — inject `JobDbContext`, use EF Core CRUD
### Migrations
```bash
dotnet ef migrations add Name --project Kbs3ServerService.Repository --startup-project Kbs3ServerService
dotnet ef database update -- Development --project Kbs3ServerService.Repository --startup-project Kbs3ServerService
```
### Microservice-side (Raw ADO.NET) — Kbs3SqlService pattern
- `IOptions<DatabaseOptions>` → connection strings for multiple DBs
- `ConnectionStringKbs` = main KBS app DB
- `ConnectionStringSapCalcData` = external SAP calc DB
- Uses `SqlConnection` + `SqlCommand` with parameterized queries
- Pattern: `new SqlConnection(connectionString)``OpenAsync()``ExecuteNonQueryAsync()``CloseAsync()`
- Example: `UpdateCalcPriceService.cs` — fetches via `apiComposer`, writes via raw SQL
### Key difference
| Location | Technology | Use case |
|----------|-----------|----------|
| Kbs3ServerService | EF Core (JobDbContext) | Main app CRUD, entities, migrations |
| Kbs3SqlService | Raw ADO.NET (SqlConnection) | Bulk sync to external/app DBs from workers |
| Kbs3SapService | None | No direct DB access — SAP only |
## 5. Service Registration
### Server services
- Interface in `Contract/`, implementation in `Services/`
- `builder.Services.AddScoped<IFoo, Foo>()` in `Program.cs`
### Microservice workers
- Auto-discovered by `IntegrationServiceBase.AutoInjectServices()`
- Keyed service: `services.AddKeyedScoped<ILongRunningJobService>(jobType, workerType)`

View File

@ -0,0 +1,12 @@
{
"C--Users-d-chrka--claude": "global",
"C--work-chrka-GoodWood": "GoodWood",
"C--work-chrka-GoodWood-GoodWoodApp": "GoodWoodApp",
"C--work-chrka-Koogle2025-KoogleApp": "Koogle2025",
"C--work-claude": "claude",
"C--work-claude-meta": "claude-meta",
"C--work-hello-world": "hello-world",
"C--work-KRAH-aims": "aims",
"C--work-KRAH-ISMS-DB": "ISMS-DB",
"C--work-KRAH-kbsV3": "kbsV3"
}

View File

@ -231,6 +231,70 @@ if (-not (Test-Path $geminiSourceDir)) {
}
}
# ============================================================
# PROJECT MEMORY JUNCTIONS
# ============================================================
$projectMapFile = Join-Path $repoRoot "memory\projects\project-map.json"
$projectsDir = Join-Path $claudeDir "projects"
Write-Host ""
Write-Host "Installing project memory junctions..." -ForegroundColor Cyan
if (Test-Path $projectMapFile) {
$projectMap = Get-Content $projectMapFile -Raw | ConvertFrom-Json
foreach ($prop in $projectMap.PSObject.Properties) {
$encodedName = $prop.Name
$friendlyName = $prop.Value
$memorySrc = Join-Path $repoRoot "memory\projects\$friendlyName"
$projectDir = Join-Path $projectsDir $encodedName
$memoryDst = Join-Path $projectDir "memory"
if (-not (Test-Path $projectDir)) {
Write-Host " SKIP: $encodedName (project dir not found)" -ForegroundColor Yellow
continue
}
if (-not (Test-Path $memorySrc)) {
Write-Host " SKIP: $friendlyName (repo dir not found)" -ForegroundColor Yellow
continue
}
# Check if already a junction to the correct target
if (Test-Path $memoryDst) {
$item = Get-Item $memoryDst -Force
if ($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) {
Write-Host " OK: $friendlyName (already linked)" -ForegroundColor Green
continue
}
# Move existing memory files to repo before replacing
$existing = Get-ChildItem $memoryDst -File -ErrorAction SilentlyContinue
foreach ($f in $existing) {
$destFile = Join-Path $memorySrc $f.Name
if (-not (Test-Path $destFile)) {
Move-Item $f.FullName $destFile
Write-Host " MOVED: $($f.Name) -> $memorySrc" -ForegroundColor Cyan
}
}
Remove-Item $memoryDst -Recurse -Force
}
if ($useJunctions) {
cmd /c "mklink /J `"$memoryDst`" `"$memorySrc`"" | Out-Null
if ($LASTEXITCODE -eq 0) {
Write-Host " JUNCTION: $friendlyName -> $memorySrc" -ForegroundColor Green
} else {
Write-Host " FAIL: $friendlyName junction creation failed" -ForegroundColor Red
$success = $false
}
} else {
Copy-Item $memorySrc $memoryDst -Recurse -Force
Write-Host " COPY: $friendlyName (different volume)" -ForegroundColor Yellow
$success = $false
}
}
} else {
Write-Host " SKIP: project-map.json not found" -ForegroundColor Yellow
}
# --- Validation ---
Write-Host ""
Write-Host "Validating..." -ForegroundColor Cyan
@ -288,6 +352,20 @@ if (Test-Path $geminiSourceDir) {
}
}
# Project memory validation
if (Test-Path $projectMapFile) {
$projectMap = Get-Content $projectMapFile -Raw | ConvertFrom-Json
foreach ($prop in $projectMap.PSObject.Properties) {
$memoryDst = Join-Path $projectsDir "$($prop.Name)\memory"
if (Test-Path $memoryDst) {
$item = Get-Item $memoryDst -Force
$isJunction = $item.Attributes -band [System.IO.FileAttributes]::ReparsePoint
$label = if ($isJunction) { "junction" } else { "copy" }
Write-Host " OK: $($prop.Value) memory/ ($label)" -ForegroundColor Green
}
}
}
# --- Summary ---
Write-Host ""
if ($valid) {

View File

@ -161,5 +161,71 @@ else
fi
fi
# ============================================================
# PROJECT MEMORY SYMLINKS
# ============================================================
PROJECT_MAP="$REPO_ROOT/memory/projects/project-map.json"
PROJECTS_DIR="$CLAUDE_DIR/projects"
echo ""
echo "Installing project memory symlinks..."
if [ -f "$PROJECT_MAP" ] && command -v python3 &>/dev/null; then
python3 -c "
import json, sys
with open('$PROJECT_MAP') as f:
data = json.load(f)
for k, v in data.items():
print(f'{k} {v}')
" | while read encoded friendly; do
memory_src="$REPO_ROOT/memory/projects/$friendly"
project_dir="$PROJECTS_DIR/$encoded"
memory_dst="$project_dir/memory"
[ ! -d "$project_dir" ] && echo " SKIP: $encoded (project dir not found)" && continue
[ ! -d "$memory_src" ] && echo " SKIP: $friendly (repo dir not found)" && continue
if [ -L "$memory_dst" ]; then
echo " OK: $friendly (already linked)"
continue
fi
# Move existing memory files to repo before replacing
if [ -d "$memory_dst" ]; then
for f in "$memory_dst"/*; do
[ -f "$f" ] || continue
base=$(basename "$f")
[ ! -f "$memory_src/$base" ] && mv "$f" "$memory_src/" && echo " MOVED: $base -> $memory_src"
done
rm -rf "$memory_dst"
fi
ln -s "$memory_src" "$memory_dst"
echo " SYMLINK: $friendly -> $memory_src"
done
elif [ -f "$PROJECT_MAP" ]; then
echo " SKIP: python3 not found (needed to parse JSON)"
fi
# Project memory validation
if [ -f "$PROJECT_MAP" ] && command -v python3 &>/dev/null; then
echo ""
echo "Validating project memory..."
python3 -c "
import json
with open('$PROJECT_MAP') as f:
data = json.load(f)
for k, v in data.items():
print(f'{k} {v}')
" | while read encoded friendly; do
memory_dst="$PROJECTS_DIR/$encoded/memory"
if [ -L "$memory_dst" ]; then
echo " OK: $friendly memory/ (symlink)"
elif [ -d "$memory_dst" ]; then
echo " OK: $friendly memory/ (copy)"
fi
done
fi
echo ""
echo "Installation complete!"

View File

@ -0,0 +1,71 @@
# link-project-memory.ps1 — Add a new project to the memory backup map and create junction
param(
[Parameter(Mandatory)][string]$EncodedName,
[Parameter(Mandatory)][string]$FriendlyName
)
$ErrorActionPreference = "Stop"
$repoRoot = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
$projectMapFile = Join-Path $repoRoot "memory\projects\project-map.json"
$memorySrc = Join-Path $repoRoot "memory\projects\$FriendlyName"
$claudeDir = Join-Path $env:USERPROFILE ".claude"
$projectDir = Join-Path $claudeDir "projects\$EncodedName"
$memoryDst = Join-Path $projectDir "memory"
# Validate project dir exists
if (-not (Test-Path $projectDir)) {
Write-Host "ERROR: Project dir not found: $projectDir" -ForegroundColor Red
exit 1
}
# Create repo memory dir
if (-not (Test-Path $memorySrc)) {
New-Item -ItemType Directory -Path $memorySrc -Force | Out-Null
New-Item -ItemType File -Path (Join-Path $memorySrc ".gitkeep") -Force | Out-Null
Write-Host "Created: $memorySrc" -ForegroundColor Cyan
}
# Update project-map.json
$map = @{}
if (Test-Path $projectMapFile) {
$json = Get-Content $projectMapFile -Raw | ConvertFrom-Json
foreach ($prop in $json.PSObject.Properties) {
$map[$prop.Name] = $prop.Value
}
}
$map[$EncodedName] = $FriendlyName
$sorted = [ordered]@{}
$map.GetEnumerator() | Sort-Object Key | ForEach-Object { $sorted[$_.Key] = $_.Value }
$sorted | ConvertTo-Json | Set-Content $projectMapFile -Encoding UTF8
Write-Host "Updated: project-map.json" -ForegroundColor Cyan
# Move existing memory files
if (Test-Path $memoryDst) {
$item = Get-Item $memoryDst -Force
if ($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) {
Write-Host "Already a junction: $memoryDst" -ForegroundColor Green
exit 0
}
$existing = Get-ChildItem $memoryDst -File -ErrorAction SilentlyContinue
foreach ($f in $existing) {
$destFile = Join-Path $memorySrc $f.Name
if (-not (Test-Path $destFile)) {
Move-Item $f.FullName $destFile
Write-Host "Moved: $($f.Name)" -ForegroundColor Cyan
}
}
Remove-Item $memoryDst -Recurse -Force
}
# Create junction
cmd /c "mklink /J `"$memoryDst`" `"$memorySrc`"" | Out-Null
if ($LASTEXITCODE -eq 0) {
Write-Host "JUNCTION: $memoryDst -> $memorySrc" -ForegroundColor Green
} else {
Write-Host "ERROR: Junction creation failed" -ForegroundColor Red
exit 1
}
Write-Host ""
Write-Host "Done! Memory for '$FriendlyName' is now backed up in the repo." -ForegroundColor Green