upd framework, added projects
This commit is contained in:
parent
2d93e188ec
commit
12ab9cea00
|
|
@ -1,6 +1,7 @@
|
|||
# OS
|
||||
Thumbs.db
|
||||
.DS_Store
|
||||
nul
|
||||
|
||||
# Temp
|
||||
*.bak
|
||||
|
|
|
|||
|
|
@ -50,7 +50,6 @@
|
|||
],
|
||||
"defaultMode": "acceptEdits"
|
||||
},
|
||||
"model": "sonnet",
|
||||
"enabledPlugins": {
|
||||
"csharp-lsp@claude-plugins-official": true
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)`
|
||||
|
|
@ -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)`
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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!"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
Loading…
Reference in New Issue