diff --git a/.claude/rules/openwolf.md b/.claude/rules/openwolf.md
deleted file mode 100644
index 9785410..0000000
--- a/.claude/rules/openwolf.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-description: OpenWolf protocol enforcement — active on all files
-globs: **/*
----
-
-- Check .wolf/anatomy.md before reading any project file
-- Check .wolf/cerebrum.md Do-Not-Repeat list before generating code
-- After writing or editing files, update .wolf/anatomy.md and append to .wolf/memory.md
-- After receiving a user correction, update .wolf/cerebrum.md immediately (Preferences, Learnings, or Do-Not-Repeat)
-- LEARN from every interaction: if you discover a convention, user preference, or project pattern, add it to .wolf/cerebrum.md. Low threshold — when in doubt, log it.
-- BEFORE fixing any bug or error: read .wolf/buglog.json for known fixes
-- AFTER fixing any bug, error, failed test, failed build, or user-reported problem: ALWAYS log to .wolf/buglog.json with error_message, root_cause, fix, and tags
-- If you edit a file more than twice in a session, that likely indicates a bug — log it to .wolf/buglog.json
-- When the user asks to check/evaluate UI design: run `openwolf designqc` to capture screenshots, then read them from .wolf/designqc-captures/
-- When the user asks to change/pick/migrate UI framework: read .wolf/reframe-frameworks.md, ask decision questions, recommend a framework, then execute with the framework's prompt
diff --git a/.claude/settings.json b/.claude/settings.json
deleted file mode 100644
index b6bf5ad..0000000
--- a/.claude/settings.json
+++ /dev/null
@@ -1,83 +0,0 @@
-{
- "permissions": {
- "allow": [
- "Bash(dotnet restore:*)",
- "Bash(dotnet build:*)",
- "Bash(az webapp:*)",
- "Bash(kill %1)",
- "Bash(curl -s -o /dev/null -w \"%{http_code}\" https://app-talent-ids-dev.azurewebsites.net)",
- "Bash(az sql:*)",
- "Bash(gh workflow:*)"
- ]
- },
- "hooks": {
- "SessionStart": [
- {
- "matcher": "",
- "hooks": [
- {
- "type": "command",
- "command": "node \"$CLAUDE_PROJECT_DIR/.wolf/hooks/session-start.js\"",
- "timeout": 5
- }
- ]
- }
- ],
- "PreToolUse": [
- {
- "matcher": "Read",
- "hooks": [
- {
- "type": "command",
- "command": "node \"$CLAUDE_PROJECT_DIR/.wolf/hooks/pre-read.js\"",
- "timeout": 5
- }
- ]
- },
- {
- "matcher": "Write|Edit|MultiEdit",
- "hooks": [
- {
- "type": "command",
- "command": "node \"$CLAUDE_PROJECT_DIR/.wolf/hooks/pre-write.js\"",
- "timeout": 5
- }
- ]
- }
- ],
- "PostToolUse": [
- {
- "matcher": "Read",
- "hooks": [
- {
- "type": "command",
- "command": "node \"$CLAUDE_PROJECT_DIR/.wolf/hooks/post-read.js\"",
- "timeout": 5
- }
- ]
- },
- {
- "matcher": "Write|Edit|MultiEdit",
- "hooks": [
- {
- "type": "command",
- "command": "node \"$CLAUDE_PROJECT_DIR/.wolf/hooks/post-write.js\"",
- "timeout": 10
- }
- ]
- }
- ],
- "Stop": [
- {
- "matcher": "",
- "hooks": [
- {
- "type": "command",
- "command": "node \"$CLAUDE_PROJECT_DIR/.wolf/hooks/stop.js\"",
- "timeout": 10
- }
- ]
- }
- ]
- }
-}
\ No newline at end of file
diff --git a/.claude/settings.local.json b/.claude/settings.local.json
deleted file mode 100644
index 65ab5d9..0000000
--- a/.claude/settings.local.json
+++ /dev/null
@@ -1,29 +0,0 @@
-{
- "permissions": {
- "allow": [
- "Bash(mkdir:*)",
- "Bash(node -e \":*)",
- "Bash(git add blogs/series-2-dotnet-api/2.1-dotnet-clean-architecture.md)",
- "Bash(git commit -m \"Add brief EasyCaching mention to 2.1 clean architecture article\")",
- "Bash(git push)",
- "Bash(gh pr:*)",
- "Bash(node -e \"console.log\(require\('c:/apps/AngularNetTutotial/node_modules/playwright/package.json'\).version\)\")",
- "Bash(node -e \"console.log\(require\('c:/apps/AngularNetTutotial/Tests/AngularNetTutorial-Playwright/node_modules/playwright/package.json'\).version\)\")",
- "Bash(npx playwright:*)",
- "Bash(npm show:*)",
- "Bash(npm install:*)",
- "Bash(powershell.exe -NoProfile -ExecutionPolicy Bypass -Command \"Get-Content 'c:/apps/AngularNetTutotial/Tests/AngularNetTutorial-Playwright/scripts/speak.ps1' | Select-Object -First 10\")",
- "Bash(powershell.exe -NoProfile -ExecutionPolicy Bypass -File \"c:/apps/AngularNetTutotial/Tests/AngularNetTutorial-Playwright/scripts/speak.ps1\" -Text \"Test audio.\" -OutputPath \"c:/apps/AngularNetTutotial/Tests/AngularNetTutorial-Playwright/scripts/test.wav\")",
- "Bash(node -e \"const p = require\('c:/apps/AngularNetTutotial/node_modules/@playwright/test/package.json'\); console.log\(JSON.stringify\(p.dependencies, null, 2\)\)\")",
- "Bash(xargs grep:*)",
- "Bash(npx ng:*)",
- "Bash(gh pr create --title '6.3 Angular AI Chat Widget: AiService, AiChatComponent, route, and menu' --body ':*)",
- "Bash(gh pr create --title '6.3 Angular AI Chat Widget: blog article and Clients submodule ref' --body ':*)",
- "mcp__MCP_DOCKER__list_directory",
- "mcp__MCP_DOCKER__read_file",
- "Bash(gh pr edit 4 --title '6.5 + 6.6 Natural language search and AI response caching' --body ':*)"
- ],
- "deny": [],
- "ask": []
- }
-}
diff --git a/.dockerignore b/.dockerignore
deleted file mode 100644
index fe1152b..0000000
--- a/.dockerignore
+++ /dev/null
@@ -1,30 +0,0 @@
-**/.classpath
-**/.dockerignore
-**/.env
-**/.git
-**/.gitignore
-**/.project
-**/.settings
-**/.toolstarget
-**/.vs
-**/.vscode
-**/*.*proj.user
-**/*.dbmdl
-**/*.jfm
-**/azds.yaml
-**/bin
-**/charts
-**/docker-compose*
-**/Dockerfile*
-**/node_modules
-**/npm-debug.log
-**/obj
-**/secrets.dev.yaml
-**/values.dev.yaml
-LICENSE
-README.md
-!**/.gitignore
-!.git/HEAD
-!.git/config
-!.git/packed-refs
-!.git/refs/heads/**
\ No newline at end of file
diff --git a/.github/agents/playwright-test-generator.agent.md b/.github/agents/playwright-test-generator.agent.md
deleted file mode 100644
index fb15e6e..0000000
--- a/.github/agents/playwright-test-generator.agent.md
+++ /dev/null
@@ -1,87 +0,0 @@
----
-name: playwright-test-generator
-description: 'Use this agent when you need to create automated browser tests using Playwright Examples: Context: User wants to generate a test for the test plan item. '
-tools:
- - search
- - playwright-test/browser_click
- - playwright-test/browser_drag
- - playwright-test/browser_evaluate
- - playwright-test/browser_file_upload
- - playwright-test/browser_handle_dialog
- - playwright-test/browser_hover
- - playwright-test/browser_navigate
- - playwright-test/browser_press_key
- - playwright-test/browser_select_option
- - playwright-test/browser_snapshot
- - playwright-test/browser_type
- - playwright-test/browser_verify_element_visible
- - playwright-test/browser_verify_list_visible
- - playwright-test/browser_verify_text_visible
- - playwright-test/browser_verify_value
- - playwright-test/browser_wait_for
- - playwright-test/generator_read_log
- - playwright-test/generator_setup_page
- - playwright-test/generator_write_test
-model: Claude Sonnet 4
-mcp-servers:
- playwright-test:
- type: stdio
- command: npx
- args:
- - playwright
- - run-test-mcp-server
- tools:
- - "*"
----
-
-You are a Playwright Test Generator, an expert in browser automation and end-to-end testing.
-Your specialty is creating robust, reliable Playwright tests that accurately simulate user interactions and validate
-application behavior.
-
-# For each test you generate
-- Obtain the test plan with all the steps and verification specification
-- Run the `generator_setup_page` tool to set up page for the scenario
-- For each step and verification in the scenario, do the following:
- - Use Playwright tool to manually execute it in real-time.
- - Use the step description as the intent for each Playwright tool call.
-- Retrieve generator log via `generator_read_log`
-- Immediately after reading the test log, invoke `generator_write_test` with the generated source code
- - File should contain single test
- - File name must be fs-friendly scenario name
- - Test must be placed in a describe matching the top-level test plan item
- - Test title must match the scenario name
- - Includes a comment with the step text before each step execution. Do not duplicate comments if step requires
- multiple actions.
- - Always use best practices from the log when generating tests.
-
-
- For following plan:
-
- ```markdown file=specs/plan.md
- ### 1. Adding New Todos
- **Seed:** `tests/seed.spec.ts`
-
- #### 1.1 Add Valid Todo
- **Steps:**
- 1. Click in the "What needs to be done?" input field
-
- #### 1.2 Add Multiple Todos
- ...
- ```
-
- Following file is generated:
-
- ```ts file=add-valid-todo.spec.ts
- // spec: specs/plan.md
- // seed: tests/seed.spec.ts
-
- test.describe('Adding New Todos', () => {
- test('Add Valid Todo', async { page } => {
- // 1. Click in the "What needs to be done?" input field
- await page.click(...);
-
- ...
- });
- });
- ```
-
diff --git a/.github/agents/playwright-test-healer.agent.md b/.github/agents/playwright-test-healer.agent.md
deleted file mode 100644
index 2e04486..0000000
--- a/.github/agents/playwright-test-healer.agent.md
+++ /dev/null
@@ -1,63 +0,0 @@
----
-name: playwright-test-healer
-description: Use this agent when you need to debug and fix failing Playwright tests
-tools:
- - search
- - edit
- - playwright-test/browser_console_messages
- - playwright-test/browser_evaluate
- - playwright-test/browser_generate_locator
- - playwright-test/browser_network_requests
- - playwright-test/browser_snapshot
- - playwright-test/test_debug
- - playwright-test/test_list
- - playwright-test/test_run
-model: Claude Sonnet 4
-mcp-servers:
- playwright-test:
- type: stdio
- command: npx
- args:
- - playwright
- - run-test-mcp-server
- tools:
- - "*"
----
-
-You are the Playwright Test Healer, an expert test automation engineer specializing in debugging and
-resolving Playwright test failures. Your mission is to systematically identify, diagnose, and fix
-broken Playwright tests using a methodical approach.
-
-Your workflow:
-1. **Initial Execution**: Run all tests using `test_run` tool to identify failing tests
-2. **Debug failed tests**: For each failing test run `test_debug`.
-3. **Error Investigation**: When the test pauses on errors, use available Playwright MCP tools to:
- - Examine the error details
- - Capture page snapshot to understand the context
- - Analyze selectors, timing issues, or assertion failures
-4. **Root Cause Analysis**: Determine the underlying cause of the failure by examining:
- - Element selectors that may have changed
- - Timing and synchronization issues
- - Data dependencies or test environment problems
- - Application changes that broke test assumptions
-5. **Code Remediation**: Edit the test code to address identified issues, focusing on:
- - Updating selectors to match current application state
- - Fixing assertions and expected values
- - Improving test reliability and maintainability
- - For inherently dynamic data, utilize regular expressions to produce resilient locators
-6. **Verification**: Restart the test after each fix to validate the changes
-7. **Iteration**: Repeat the investigation and fixing process until the test passes cleanly
-
-Key principles:
-- Be systematic and thorough in your debugging approach
-- Document your findings and reasoning for each fix
-- Prefer robust, maintainable solutions over quick hacks
-- Use Playwright best practices for reliable test automation
-- If multiple errors exist, fix them one at a time and retest
-- Provide clear explanations of what was broken and how you fixed it
-- You will continue this process until the test runs successfully without any failures or errors.
-- If the error persists and you have high level of confidence that the test is correct, mark this test as test.fixme()
- so that it is skipped during the execution. Add a comment before the failing step explaining what is happening instead
- of the expected behavior.
-- Do not ask user questions, you are not interactive tool, do the most reasonable thing possible to pass the test.
-- Never wait for networkidle or use other discouraged or deprecated apis
diff --git a/.github/agents/playwright-test-planner.agent.md b/.github/agents/playwright-test-planner.agent.md
deleted file mode 100644
index 1f2e788..0000000
--- a/.github/agents/playwright-test-planner.agent.md
+++ /dev/null
@@ -1,81 +0,0 @@
----
-name: playwright-test-planner
-description: Use this agent when you need to create comprehensive test plan for a web application or website
-tools:
- - search
- - playwright-test/browser_click
- - playwright-test/browser_close
- - playwright-test/browser_console_messages
- - playwright-test/browser_drag
- - playwright-test/browser_evaluate
- - playwright-test/browser_file_upload
- - playwright-test/browser_handle_dialog
- - playwright-test/browser_hover
- - playwright-test/browser_navigate
- - playwright-test/browser_navigate_back
- - playwright-test/browser_network_requests
- - playwright-test/browser_press_key
- - playwright-test/browser_run_code
- - playwright-test/browser_select_option
- - playwright-test/browser_snapshot
- - playwright-test/browser_take_screenshot
- - playwright-test/browser_type
- - playwright-test/browser_wait_for
- - playwright-test/planner_setup_page
- - playwright-test/planner_save_plan
-model: Claude Sonnet 4
-mcp-servers:
- playwright-test:
- type: stdio
- command: npx
- args:
- - playwright
- - run-test-mcp-server
- tools:
- - "*"
----
-
-You are an expert web test planner with extensive experience in quality assurance, user experience testing, and test
-scenario design. Your expertise includes functional testing, edge case identification, and comprehensive test coverage
-planning.
-
-You will:
-
-1. **Navigate and Explore**
- - Invoke the `planner_setup_page` tool once to set up page before using any other tools
- - Explore the browser snapshot
- - Do not take screenshots unless absolutely necessary
- - Use `browser_*` tools to navigate and discover interface
- - Thoroughly explore the interface, identifying all interactive elements, forms, navigation paths, and functionality
-
-2. **Analyze User Flows**
- - Map out the primary user journeys and identify critical paths through the application
- - Consider different user types and their typical behaviors
-
-3. **Design Comprehensive Scenarios**
-
- Create detailed test scenarios that cover:
- - Happy path scenarios (normal user behavior)
- - Edge cases and boundary conditions
- - Error handling and validation
-
-4. **Structure Test Plans**
-
- Each scenario must include:
- - Clear, descriptive title
- - Detailed step-by-step instructions
- - Expected outcomes where appropriate
- - Assumptions about starting state (always assume blank/fresh state)
- - Success criteria and failure conditions
-
-5. **Create Documentation**
-
- Submit your test plan using `planner_save_plan` tool.
-
-**Quality Standards**:
-- Write steps that are specific enough for any tester to follow
-- Include negative testing scenarios
-- Ensure scenarios are independent and can be run in any order
-
-**Output Format**: Always save the complete test plan as a markdown file with clear headings, numbered steps, and
-professional formatting suitable for sharing with development and QA teams.
diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml
deleted file mode 100644
index d9b5b71..0000000
--- a/.github/workflows/copilot-setup-steps.yml
+++ /dev/null
@@ -1,34 +0,0 @@
-name: "Copilot Setup Steps"
-
-on:
- workflow_dispatch:
- push:
- paths:
- - .github/workflows/copilot-setup-steps.yml
- pull_request:
- paths:
- - .github/workflows/copilot-setup-steps.yml
-
-jobs:
- copilot-setup-steps:
- runs-on: ubuntu-latest
-
- permissions:
- contents: read
-
- steps:
- - uses: actions/checkout@v4
-
- - uses: actions/setup-node@v4
- with:
- node-version: lts/*
-
- - name: Install dependencies
- run: npm ci
-
- - name: Install Playwright Browsers
- run: npx playwright install --with-deps
-
- # Customize this step as needed
- - name: Build application
- run: npx run build
diff --git a/.github/workflows/deploy-angular.yml b/.github/workflows/deploy-angular.yml
deleted file mode 100644
index 56e8eb2..0000000
--- a/.github/workflows/deploy-angular.yml
+++ /dev/null
@@ -1,79 +0,0 @@
-name: Deploy Angular to Azure Static Web Apps
-
-on:
- push:
- branches:
- - master
- paths:
- - 'Clients/TalentManagement-Angular-Material'
- - '.github/workflows/deploy-angular.yml'
- workflow_dispatch:
-
-permissions:
- id-token: write # Required for OIDC token request
- contents: read
-
-env:
- ANGULAR_APP_DIR: 'Clients/TalentManagement-Angular-Material/talent-management'
- STATIC_WEB_APP_NAME: 'swa-talent-ui-dev'
- RESOURCE_GROUP: 'rg-talent-dev'
-
-jobs:
- build-and-deploy:
- name: Build and Deploy Angular
- runs-on: ubuntu-latest
-
- steps:
- - name: Checkout repository (with submodules)
- uses: actions/checkout@v4
- with:
- submodules: recursive
-
- - name: Set up Node.js
- uses: actions/setup-node@v4
- with:
- node-version: '22.x'
- cache: 'npm'
- cache-dependency-path: '${{ env.ANGULAR_APP_DIR }}/package-lock.json'
-
- - name: Install dependencies
- working-directory: ${{ env.ANGULAR_APP_DIR }}
- run: npm ci
-
- - name: Inject production environment variables
- working-directory: ${{ env.ANGULAR_APP_DIR }}
- env:
- API_URL: ${{ secrets.API_APP_URL }}
- IDENTITY_SERVER_URL: ${{ secrets.IDENTITY_SERVER_URL }}
- run: |
- sed -i "s|https://your-production-api.com/api/v1|${API_URL}/api/v1|g" src/environments/environment.prod.ts
- sed -i "s|https://localhost:44310|${IDENTITY_SERVER_URL}|g" src/environments/environment.prod.ts
-
- - name: Build Angular (production)
- working-directory: ${{ env.ANGULAR_APP_DIR }}
- run: npm run build -- --configuration production
-
- - name: Log in to Azure
- uses: azure/login@v2
- with:
- client-id: ${{ secrets.AZURE_CLIENT_ID }}
- tenant-id: ${{ secrets.AZURE_TENANT_ID }}
- subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
-
- - name: Get Static Web App deployment token
- id: swa-token
- run: |
- SWA_TOKEN=$(az staticwebapp secrets list \
- --name ${{ env.STATIC_WEB_APP_NAME }} \
- --resource-group ${{ env.RESOURCE_GROUP }} \
- --query properties.apiKey \
- --output tsv)
- echo "token=${SWA_TOKEN}" >> $GITHUB_OUTPUT
-
- - name: Deploy to Azure Static Web Apps
- uses: Azure/static-web-apps-deploy@v1
- with:
- azure_static_web_apps_api_token: ${{ steps.swa-token.outputs.token }}
- action: upload
- app_location: 'Clients/TalentManagement-Angular-Material/talent-management/dist/talent-management/browser'
- skip_app_build: true
diff --git a/.github/workflows/deploy-api.yml b/.github/workflows/deploy-api.yml
deleted file mode 100644
index 226e801..0000000
--- a/.github/workflows/deploy-api.yml
+++ /dev/null
@@ -1,90 +0,0 @@
-name: Deploy .NET API to Azure App Service
-
-on:
- push:
- branches:
- - master
- paths:
- - 'ApiResources/TalentManagement-API/**'
- - '.github/workflows/deploy-api.yml'
- workflow_dispatch:
-
-permissions:
- id-token: write # Required for OIDC token request
- contents: read
-
-env:
- DOTNET_VERSION: '10.x'
- PROJECT_PATH: 'ApiResources/TalentManagement-API/TalentManagementAPI.WebApi/TalentManagementAPI.WebApi.csproj'
- SOLUTION_PATH: 'ApiResources/TalentManagement-API/TalentManagementAPI.slnx'
- PUBLISH_DIR: '${{ github.workspace }}/publish/api'
- APP_SERVICE_NAME: 'app-talent-api-dev'
- RESOURCE_GROUP: 'rg-talent-dev'
-
-jobs:
- build-and-deploy:
- name: Build, Test, and Deploy API
- runs-on: ubuntu-latest
-
- steps:
- - name: Checkout repository (with submodules)
- uses: actions/checkout@v4
- with:
- submodules: recursive
-
- - name: Set up .NET ${{ env.DOTNET_VERSION }}
- uses: actions/setup-dotnet@v4
- with:
- dotnet-version: ${{ env.DOTNET_VERSION }}
-
- - name: Restore dependencies
- run: dotnet restore ${{ env.SOLUTION_PATH }}
-
- - name: Build
- run: dotnet build ${{ env.SOLUTION_PATH }} --configuration Release --no-restore
-
- - name: Run unit tests
- run: dotnet test ${{ env.SOLUTION_PATH }} --configuration Release --no-build --verbosity normal
-
- - name: Publish
- run: |
- dotnet publish ${{ env.PROJECT_PATH }} \
- --configuration Release \
- --no-build \
- --output ${{ env.PUBLISH_DIR }}
-
- - name: Log in to Azure
- uses: azure/login@v2
- with:
- client-id: ${{ secrets.AZURE_CLIENT_ID }}
- tenant-id: ${{ secrets.AZURE_TENANT_ID }}
- subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
-
- - name: Configure App Service settings
- env:
- IDS_URL: ${{ secrets.IDENTITY_SERVER_URL }}
- ANGULAR_URL: ${{ secrets.ANGULAR_APP_URL }}
- KV_URI: ${{ secrets.KEY_VAULT_URI }}
- run: |
- az webapp config appsettings set \
- --resource-group ${{ env.RESOURCE_GROUP }} \
- --name ${{ env.APP_SERVICE_NAME }} \
- --settings \
- "ConnectionStrings__DefaultConnection=@Microsoft.KeyVault(SecretUri=${KV_URI}secrets/ConnectionStrings--DefaultConnection)" \
- "JWTSettings__Key=@Microsoft.KeyVault(SecretUri=${KV_URI}secrets/JWTSettings--Key)" \
- "Sts__ServerUrl=$IDS_URL" \
- "Sts__ValidIssuer=$IDS_URL" \
- "Sts__Audience=app.api.talentmanagement" \
- "JWTSettings__Issuer=CoreIdentity" \
- "JWTSettings__Audience=CoreIdentityUser" \
- "JWTSettings__DurationInMinutes=60" \
- "FeatureManagement__AuthEnabled=true" \
- "Cors__AllowedOrigins__0=$ANGULAR_URL" \
- "Cors__AllowedOrigins__1=https://workcontrolgit.github.io" \
- "ASPNETCORE_ENVIRONMENT=Production"
-
- - name: Deploy to Azure App Service
- uses: azure/webapps-deploy@v3
- with:
- app-name: ${{ env.APP_SERVICE_NAME }}
- package: ${{ env.PUBLISH_DIR }}
diff --git a/.github/workflows/deploy-gh-pages.yml b/.github/workflows/deploy-gh-pages.yml
deleted file mode 100644
index cc640d2..0000000
--- a/.github/workflows/deploy-gh-pages.yml
+++ /dev/null
@@ -1,63 +0,0 @@
-name: Deploy Angular to GitHub Pages
-
-on:
- push:
- branches:
- - master
- paths:
- - 'Clients/TalentManagement-Angular-Material'
- - '.github/workflows/deploy-gh-pages.yml'
- workflow_dispatch:
-
-permissions:
- contents: write
-
-env:
- ANGULAR_APP_DIR: 'Clients/TalentManagement-Angular-Material/talent-management'
- BASE_HREF: '/AngularNetTutorial/'
-
-jobs:
- build-and-deploy:
- name: Build and Deploy to GitHub Pages
- runs-on: ubuntu-latest
-
- steps:
- - name: Checkout repository (with submodules)
- uses: actions/checkout@v4
- with:
- submodules: recursive
-
- - name: Set up Node.js
- uses: actions/setup-node@v4
- with:
- node-version: '22.x'
- cache: 'npm'
- cache-dependency-path: '${{ env.ANGULAR_APP_DIR }}/package-lock.json'
-
- - name: Install dependencies
- working-directory: ${{ env.ANGULAR_APP_DIR }}
- run: npm ci
-
- - name: Inject production environment variables
- working-directory: ${{ env.ANGULAR_APP_DIR }}
- env:
- API_URL: ${{ secrets.API_APP_URL }}
- IDENTITY_SERVER_URL: ${{ secrets.IDENTITY_SERVER_URL }}
- run: |
- sed -i "s|https://your-production-api.com/api/v1|${API_URL}/api/v1|g" src/environments/environment.prod.ts
- sed -i "s|https://localhost:44310|${IDENTITY_SERVER_URL}|g" src/environments/environment.prod.ts
-
- - name: Build Angular for GitHub Pages
- working-directory: ${{ env.ANGULAR_APP_DIR }}
- run: npm run build -- --configuration production --base-href "${{ env.BASE_HREF }}"
-
- - name: Add 404.html for SPA deep-link routing
- working-directory: ${{ env.ANGULAR_APP_DIR }}/dist/talent-management/browser
- run: cp index.html 404.html
-
- - name: Deploy to GitHub Pages
- uses: JamesIves/github-pages-deploy-action@v4
- with:
- folder: ${{ env.ANGULAR_APP_DIR }}/dist/talent-management/browser
- branch: gh-pages
- clean: true
diff --git a/.github/workflows/deploy-identityserver.yml b/.github/workflows/deploy-identityserver.yml
deleted file mode 100644
index 6e9532e..0000000
--- a/.github/workflows/deploy-identityserver.yml
+++ /dev/null
@@ -1,128 +0,0 @@
-name: Deploy IdentityServer to Azure App Service
-
-on:
- push:
- branches:
- - master
- paths:
- - 'TokenService/Duende-IdentityServer/**'
- - '.github/workflows/deploy-identityserver.yml'
- workflow_dispatch:
-
-permissions:
- id-token: write # Required for OIDC token request
- contents: read
-
-env:
- DOTNET_VERSION: '8.x'
- STS_PROJECT_PATH: 'TokenService/Duende-IdentityServer/src/DuendeIdentityServer.STS.Identity/DuendeIdentityServer.STS.Identity.csproj'
- ADMIN_PROJECT_PATH: 'TokenService/Duende-IdentityServer/src/DuendeIdentityServer.Admin/DuendeIdentityServer.Admin.csproj'
- STS_PUBLISH_DIR: '${{ github.workspace }}/publish/ids'
- ADMIN_PUBLISH_DIR: '${{ github.workspace }}/publish/admin'
- APP_SERVICE_NAME: 'app-talent-ids-dev'
- ADMIN_APP_SERVICE_NAME: 'app-talent-admin-dev'
- RESOURCE_GROUP: 'rg-talent-dev'
-
-jobs:
- build-and-deploy:
- name: Build and Deploy IdentityServer
- runs-on: ubuntu-latest
-
- steps:
- - name: Checkout repository (with submodules)
- uses: actions/checkout@v4
- with:
- submodules: recursive
-
- - name: Set up .NET ${{ env.DOTNET_VERSION }}
- uses: actions/setup-dotnet@v4
- with:
- dotnet-version: ${{ env.DOTNET_VERSION }}
-
- - name: Restore dependencies
- run: |
- dotnet restore ${{ env.STS_PROJECT_PATH }}
- dotnet restore ${{ env.ADMIN_PROJECT_PATH }}
-
- - name: Build
- run: |
- dotnet build ${{ env.STS_PROJECT_PATH }} --configuration Release --no-restore
- dotnet build ${{ env.ADMIN_PROJECT_PATH }} --configuration Release --no-restore
-
- - name: Publish STS
- run: |
- dotnet publish ${{ env.STS_PROJECT_PATH }} \
- --configuration Release \
- --no-build \
- --output ${{ env.STS_PUBLISH_DIR }}
-
- - name: Publish Admin
- run: |
- dotnet publish ${{ env.ADMIN_PROJECT_PATH }} \
- --configuration Release \
- --no-build \
- --output ${{ env.ADMIN_PUBLISH_DIR }}
-
- - name: Log in to Azure
- uses: azure/login@v2
- with:
- client-id: ${{ secrets.AZURE_CLIENT_ID }}
- tenant-id: ${{ secrets.AZURE_TENANT_ID }}
- subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
-
- - name: Configure STS App Service settings
- env:
- IDS_URL: ${{ secrets.IDENTITY_SERVER_URL }}
- ADMIN_URL: ${{ secrets.IDENTITY_ADMIN_URL }}
- KV_URI: ${{ secrets.KEY_VAULT_URI }}
- run: |
- az webapp config appsettings set \
- --resource-group ${{ env.RESOURCE_GROUP }} \
- --name ${{ env.APP_SERVICE_NAME }} \
- --settings \
- "ConnectionStrings__ConfigurationDbConnection=@Microsoft.KeyVault(SecretUri=${KV_URI}secrets/ConnectionStrings--IdsDbConnection)" \
- "ConnectionStrings__PersistedGrantDbConnection=@Microsoft.KeyVault(SecretUri=${KV_URI}secrets/ConnectionStrings--IdsDbConnection)" \
- "ConnectionStrings__IdentityDbConnection=@Microsoft.KeyVault(SecretUri=${KV_URI}secrets/ConnectionStrings--IdsDbConnection)" \
- "ConnectionStrings__DataProtectionDbConnection=@Microsoft.KeyVault(SecretUri=${KV_URI}secrets/ConnectionStrings--IdsDbConnection)" \
- "AdminConfiguration__IdentityServerBaseUrl=$IDS_URL" \
- "AdminConfiguration__IdentityAdminBaseUrl=$ADMIN_URL" \
- "CspTrustedDomains__0=www.gravatar.com" \
- "CspTrustedDomains__1=fonts.googleapis.com" \
- "CspTrustedDomains__2=fonts.gstatic.com" \
- "CspTrustedDomains__3=workcontrolgit.github.io" \
- "CspTrustedDomains__4=mango-flower-0ced4011e.4.azurestaticapps.net" \
- "ASPNETCORE_ENVIRONMENT=Production"
-
- - name: Configure Admin App Service settings
- env:
- IDS_URL: ${{ secrets.IDENTITY_SERVER_URL }}
- ADMIN_URL: ${{ secrets.IDENTITY_ADMIN_URL }}
- KV_URI: ${{ secrets.KEY_VAULT_URI }}
- run: |
- az webapp config appsettings set \
- --resource-group ${{ env.RESOURCE_GROUP }} \
- --name ${{ env.ADMIN_APP_SERVICE_NAME }} \
- --settings \
- "ConnectionStrings__ConfigurationDbConnection=@Microsoft.KeyVault(SecretUri=${KV_URI}secrets/ConnectionStrings--IdsDbConnection)" \
- "ConnectionStrings__PersistedGrantDbConnection=@Microsoft.KeyVault(SecretUri=${KV_URI}secrets/ConnectionStrings--IdsDbConnection)" \
- "ConnectionStrings__IdentityDbConnection=@Microsoft.KeyVault(SecretUri=${KV_URI}secrets/ConnectionStrings--IdsDbConnection)" \
- "ConnectionStrings__AdminLogDbConnection=@Microsoft.KeyVault(SecretUri=${KV_URI}secrets/ConnectionStrings--IdsDbConnection)" \
- "ConnectionStrings__AdminAuditLogDbConnection=@Microsoft.KeyVault(SecretUri=${KV_URI}secrets/ConnectionStrings--IdsDbConnection)" \
- "ConnectionStrings__DataProtectionDbConnection=@Microsoft.KeyVault(SecretUri=${KV_URI}secrets/ConnectionStrings--IdsDbConnection)" \
- "AdminConfiguration__IdentityServerBaseUrl=$IDS_URL" \
- "AdminConfiguration__IdentityAdminRedirectUri=$ADMIN_URL/signin-oidc" \
- "SeedConfiguration__ApplySeed=false" \
- "DatabaseMigrationsConfiguration__ApplyDatabaseMigrations=false" \
- "ASPNETCORE_ENVIRONMENT=Production"
-
- - name: Deploy STS to Azure App Service
- uses: azure/webapps-deploy@v3
- with:
- app-name: ${{ env.APP_SERVICE_NAME }}
- package: ${{ env.STS_PUBLISH_DIR }}
-
- - name: Deploy Admin to Azure App Service
- uses: azure/webapps-deploy@v3
- with:
- app-name: ${{ env.ADMIN_APP_SERVICE_NAME }}
- package: ${{ env.ADMIN_PUBLISH_DIR }}
diff --git a/.github/workflows/deploy-infra.yml b/.github/workflows/deploy-infra.yml
deleted file mode 100644
index 24af801..0000000
--- a/.github/workflows/deploy-infra.yml
+++ /dev/null
@@ -1,48 +0,0 @@
-name: Deploy Azure Infrastructure (Bicep)
-
-on:
- workflow_dispatch:
- inputs:
- environment:
- description: 'Target environment'
- required: true
- default: 'dev'
- type: choice
- options:
- - dev
-
-permissions:
- id-token: write # Required for OIDC token request
- contents: read
-
-env:
- RESOURCE_GROUP: 'rg-talent-dev'
- BICEP_FILE: 'infra/main.bicep'
- PARAMS_FILE: 'infra/parameters/dev.bicepparam'
-
-jobs:
- deploy-infrastructure:
- name: Deploy Bicep Infrastructure
- runs-on: ubuntu-latest
-
- steps:
- - name: Checkout repository
- uses: actions/checkout@v4
-
- - name: Log in to Azure
- uses: azure/login@v2
- with:
- client-id: ${{ secrets.AZURE_CLIENT_ID }}
- tenant-id: ${{ secrets.AZURE_TENANT_ID }}
- subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
-
- - name: Deploy Bicep
- env:
- # readEnvironmentVariable('SQL_ADMIN_PASSWORD') in dev.bicepparam reads this
- SQL_ADMIN_PASSWORD: ${{ secrets.SQL_ADMIN_PASSWORD }}
- run: |
- az deployment group create \
- --resource-group ${{ env.RESOURCE_GROUP }} \
- --template-file ${{ env.BICEP_FILE }} \
- --parameters ${{ env.PARAMS_FILE }} \
- --output json
diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml
deleted file mode 100644
index 219894e..0000000
--- a/.github/workflows/playwright.yml
+++ /dev/null
@@ -1,40 +0,0 @@
-name: Playwright Tests
-on:
- push:
- branches: [ main, master ]
- pull_request:
- branches: [ main, master ]
-
-env:
- PLAYWRIGHT_DIR: 'Tests/AngularNetTutorial-Playwright'
-
-jobs:
- test:
- timeout-minutes: 60
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- with:
- submodules: recursive
- - uses: actions/setup-node@v4
- with:
- node-version: lts/*
- - name: Install dependencies
- working-directory: ${{ env.PLAYWRIGHT_DIR }}
- run: npm ci
- - name: Install Playwright Browsers
- working-directory: ${{ env.PLAYWRIGHT_DIR }}
- run: npx playwright install --with-deps
- - name: Run Playwright tests
- working-directory: ${{ env.PLAYWRIGHT_DIR }}
- env:
- ANGULAR_APP_URL: ${{ secrets.ANGULAR_APP_URL }}
- API_APP_URL: ${{ secrets.API_APP_URL }}
- IDENTITY_SERVER_URL: ${{ secrets.IDENTITY_SERVER_URL }}
- run: npx playwright test --project=smoke
- - uses: actions/upload-artifact@v4
- if: ${{ !cancelled() }}
- with:
- name: playwright-report
- path: ${{ env.PLAYWRIGHT_DIR }}/playwright-report/
- retention-days: 30
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index 4a9c43e..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,58 +0,0 @@
-# See http://help.github.com/ignore-files/ for more about ignoring files.
-
-# IDE and Editor directories
-.vscode/
-.idea/
-*.swp
-*.swo
-*~
-
-# OS Files
-.DS_Store
-Thumbs.db
-Desktop.ini
-
-# Logs
-logs/
-*.log
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-lerna-debug.log*
-
-# Temporary files
-tmp/
-temp/
-*.tmp
-
-# Build outputs (will be in submodules)
-dist/
-build/
-out/
-
-# Dependencies (will be in submodules)
-node_modules/
-
-# Environment files (these should be in submodules, but add here as safety)
-.env
-.env.local
-.env.*.local
-
-# User-specific files
-*.suo
-*.user
-*.userosscache
-*.sln.docstates
-
-# Scripts with secrets
-infra/scripts/setup-azure.ps1
-
-# OpenWolf
-.wolf/
-
-# Playwright
-/test-results/
-/playwright-report/
-/blob-report/
-/playwright/.cache/
-/playwright/.auth/
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index 5122ca9..0000000
--- a/.gitmodules
+++ /dev/null
@@ -1,12 +0,0 @@
-[submodule "Clients/TalentManagement-Angular-Material"]
- path = Clients/TalentManagement-Angular-Material
- url = https://github.com/workcontrolgit/TalentManagement-Angular-Material.git
-[submodule "ApiResources/TalentManagement-API"]
- path = ApiResources/TalentManagement-API
- url = https://github.com/workcontrolgit/TalentManagement-API.git
-[submodule "TokenService/Duende-IdentityServer"]
- path = TokenService/Duende-IdentityServer
- url = https://github.com/workcontrolgit/Duende-IdentityServer.git
-[submodule "Tests/AngularNetTutorial-Playwright"]
- path = Tests/AngularNetTutorial-Playwright
- url = https://github.com/workcontrolgit/AngularNetTutorial-Playwright.git
diff --git a/.mcp.json b/.mcp.json
deleted file mode 100644
index 6da4077..0000000
--- a/.mcp.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "mcpServers": {
- "playwright-test": {
- "command": "cmd",
- "args": [
- "/c",
- "npx",
- "playwright",
- "run-test-mcp-server"
- ]
- }
- }
-}
\ No newline at end of file
diff --git a/.wolf/OPENWOLF.md b/.wolf/OPENWOLF.md
deleted file mode 100644
index aae2a47..0000000
--- a/.wolf/OPENWOLF.md
+++ /dev/null
@@ -1,135 +0,0 @@
-# OpenWolf Operating Protocol
-
-You are working in an OpenWolf-managed project. These rules apply every turn.
-
-## File Navigation
-
-1. Check `.wolf/anatomy.md` BEFORE reading any file. It has a 2-3 line description and token estimate for every file in the project.
-2. If the description in anatomy.md is sufficient for your task, do NOT read the full file.
-3. If a file is not in anatomy.md, search with Grep/Glob, then update anatomy.md with the new entry.
-
-## Code Generation
-
-1. Before generating code, read `.wolf/cerebrum.md` and respect every entry.
-2. Check the `## Do-Not-Repeat` section — these are past mistakes that must not recur.
-3. Follow all conventions in `## Key Learnings` and `## User Preferences`.
-
-## After Actions
-
-1. After every significant action, append a one-line entry to `.wolf/memory.md`:
- `| HH:MM | description | file(s) | outcome | ~tokens |`
-2. After creating, deleting, or renaming files: update `.wolf/anatomy.md`.
-
-## Cerebrum Learning (MANDATORY — every session)
-
-OpenWolf's value comes from learning across sessions. You MUST update `.wolf/cerebrum.md` whenever you learn something useful. This is not optional.
-
-**Update `## User Preferences` when the user:**
-- Corrects your approach ("no, do it this way instead")
-- Expresses a style preference (naming, structure, formatting)
-- Shows a preferred workflow or tool choice
-- Rejects a suggestion — record what they preferred instead
-- Asks for more/less detail, verbosity, explanation
-
-**Update `## Key Learnings` when you discover:**
-- A project convention not obvious from the code (e.g., "tests go in __tests__/ not test/")
-- A framework-specific pattern this project uses
-- An API behavior that surprised you
-- A dependency quirk or version constraint
-- How modules connect or data flows through the system
-
-**Update `## Do-Not-Repeat` (with date) when:**
-- The user corrects a mistake you made
-- You try something that fails and find the right approach
-- You discover a gotcha that would trip up a fresh session
-
-**Update `## Decision Log` when:**
-- A significant architectural or technical choice is made
-- The user explains why they chose approach A over B
-- A trade-off is explicitly discussed
-
-**The bar is LOW.** If in doubt, add it. A cerebrum entry that's slightly redundant costs nothing. A missing entry means the next session repeats the same discovery process.
-
-## Bug Logging (MANDATORY)
-
-**Log a bug to `.wolf/buglog.json` whenever ANY of these happen:**
-- The user reports an error, bug, or problem
-- A test fails or a command produces an error
-- You fix something that was broken
-- You edit a file more than twice to get it right
-- An import, module, or dependency is missing or wrong
-- A runtime error, type error, or syntax error occurs
-- A build or lint command fails
-- A feature doesn't work as expected
-- You change error handling, try/catch blocks, or validation logic
-- The user says something "doesn't work", "is broken", or "shows wrong X"
-
-**Before fixing:** Read `.wolf/buglog.json` first — the fix may already be known.
-
-**After fixing:** ALWAYS append to `.wolf/buglog.json` with this structure:
-```json
-{
- "id": "bug-NNN",
- "timestamp": "ISO date",
- "error_message": "exact error or user complaint",
- "file": "file that was fixed",
- "root_cause": "why it broke",
- "fix": "what you changed to fix it",
- "tags": ["relevant", "keywords"],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "ISO date"
-}
-```
-
-**The threshold is LOW.** When in doubt, log it. A false positive in the bug log costs nothing. A missed bug means repeating the same mistake later.
-
-## Token Discipline
-
-- Never re-read a file already read this session unless it was modified since.
-- Prefer anatomy.md descriptions over full file reads when possible.
-- Prefer targeted Grep over full file reads when searching for specific code.
-- If appending to a file, do not read the entire file first.
-
-## Design QC
-
-When the user asks you to check, evaluate, or improve the design/UI of their app:
-
-1. Run `openwolf designqc` via Bash to capture screenshots.
- - The command auto-detects a running dev server, or starts one from package.json if needed
- - Use `--url ` only if auto-detection fails
- - The command saves compressed JPEG screenshots to `.wolf/designqc-captures/`
- - Full pages are captured as sectioned viewport-height images (top, section2, ..., bottom)
-2. Read the captured screenshot images from `.wolf/designqc-captures/` using the Read tool.
-3. Evaluate the design against modern standards (Shadcn UI, Tailwind, clean React patterns):
- - Spacing and whitespace consistency
- - Typography hierarchy and readability
- - Color contrast and accessibility (WCAG)
- - Visual hierarchy and focal points
- - Component consistency
- - Whether the design looks "dull" or "white-coded" (generic, no personality)
-4. Provide specific, actionable feedback with fix suggestions.
-5. If the user approves, implement the fixes directly in their code.
-6. After fixes, re-run `openwolf designqc` to capture new screenshots and verify improvement.
-
-**Token awareness:** Each screenshot costs ~2500 tokens. The command compresses images (JPEG quality 70, max width 1200px) to minimize cost. For large apps, use `--routes / /specific-page` to limit captures.
-
-## Reframe — UI Framework Selection
-
-When the user asks to change, pick, migrate, or "reframe" their project's UI framework:
-
-1. Read `.wolf/reframe-frameworks.md` for the full framework knowledge base.
-2. Ask the user the decision questions from the file (current stack, priority, Tailwind usage, theme preference, app type). Stop early once the choice narrows to 1-2 options.
-3. Present a recommendation with reasoning based on the comparison matrix.
-4. Once the user confirms, use the selected framework's prompt from the file — **adapted to the actual project** using `.wolf/anatomy.md` for real file paths, routes, and components.
-5. Execute the migration: install dependencies, update config, refactor components.
-6. After migration, run `openwolf designqc` to verify the new look.
-
-**Do NOT read the entire reframe-frameworks.md into context upfront.** Read the decision questions and comparison matrix first (~50 lines). Only read the specific framework's prompt section after the user chooses.
-
-## Session End
-
-Before ending or when asked to wrap up:
-
-1. Write a session summary to `.wolf/memory.md`.
-2. Review the session: did you learn anything? Did the user correct you? Did you fix a bug? If yes, update `.wolf/cerebrum.md` and/or `.wolf/buglog.json`.
diff --git a/.wolf/anatomy.md b/.wolf/anatomy.md
deleted file mode 100644
index 3892629..0000000
--- a/.wolf/anatomy.md
+++ /dev/null
@@ -1,578 +0,0 @@
-# anatomy.md
-
-> Auto-maintained by OpenWolf. Last scanned: 2026-04-28T02:35:14.407Z
-> Files: 67 tracked | Anatomy hits: 0 | Misses: 0
-
-## ../../Users/Fuji Nguyen/.claude/plans/
-
-- `peppy-conjuring-leaf.md` — Plan: New Standalone Series — DotnetAiAgentMcp (~6061 tok)
-- `toasty-shimmying-haven.md` — Secure Playwright Test Credentials with PKCE Global Auth Setup (~4045 tok)
-
-## ../../Users/Fuji Nguyen/.claude/projects/c--apps-AngularNetTutotial/memory/
-
-
-## ../DotnetMcpTutorial/
-
-- `.gitignore` — Git ignore rules (~92 tok)
-- `README.md` — Project documentation (~357 tok)
-
-## ./
-
-- `.gitignore` — Git ignore rules (~219 tok)
-- `package.json` — Node.js package manifest (~178 tok)
-
-## .claude/
-
-
-## .claude/rules/
-
-
-## .github/agents/
-
-
-## .github/workflows/
-
-
-## .vscode/
-
-- `settings.json` (~94 tok)
-
-## ApiResources/TalentManagement-API/
-
-
-## ApiResources/TalentManagement-API/.dotnet/
-
-
-## ApiResources/TalentManagement-API/.dotnet/.dotnet/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/ardalis.specification.entityframeworkcore/9.3.1/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/ardalis.specification.entityframeworkcore/9.3.1/lib/net8.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/ardalis.specification.entityframeworkcore/9.3.1/lib/net9.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/ardalis.specification/9.3.1/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/ardalis.specification/9.3.1/lib/net8.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/ardalis.specification/9.3.1/lib/net9.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/ardalis.specification/9.3.1/lib/netstandard2.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/asp.versioning.abstractions/8.1.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/asp.versioning.abstractions/8.1.0/lib/net8.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/asp.versioning.abstractions/8.1.0/lib/netstandard1.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/asp.versioning.abstractions/8.1.0/lib/netstandard2.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/asp.versioning.http/8.1.1/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/asp.versioning.http/8.1.1/lib/net8.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/asp.versioning.mvc.apiexplorer/8.1.1/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/asp.versioning.mvc.apiexplorer/8.1.1/lib/net8.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/asp.versioning.mvc/8.1.1/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/asp.versioning.mvc/8.1.1/lib/net8.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/autobogus/2.13.1/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/azure.core/1.46.1/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/azure.core/1.46.1/lib/net462/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/azure.core/1.46.1/lib/net472/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/azure.core/1.46.1/lib/net6.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/azure.core/1.46.1/lib/net8.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/azure.core/1.46.1/lib/netstandard2.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/azure.core/1.47.1/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/azure.core/1.47.1/lib/net462/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/azure.core/1.47.1/lib/net472/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/azure.core/1.47.1/lib/net8.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/azure.core/1.47.1/lib/netstandard2.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/azure.identity/1.14.2/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/azure.identity/1.14.2/lib/net8.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/azure.identity/1.14.2/lib/netstandard2.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/bogus/31.0.3/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/bogus/31.0.3/lib/net40/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/bogus/31.0.3/lib/netstandard1.3/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/bogus/31.0.3/lib/netstandard2.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/bogus/35.6.5/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/bogus/35.6.5/lib/net40/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/bogus/35.6.5/lib/net6.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/bogus/35.6.5/lib/netstandard1.3/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/bogus/35.6.5/lib/netstandard2.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/bouncycastle.cryptography/2.6.1/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/castle.core/5.1.1/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/castle.core/5.1.1/lib/net462/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/castle.core/5.1.1/lib/net6.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/castle.core/5.1.1/lib/netstandard2.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/castle.core/5.1.1/lib/netstandard2.1/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/coverlet.collector/6.0.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/easycaching.core/1.8.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/easycaching.core/1.8.0/lib/net6.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/easycaching.core/1.8.0/lib/netstandard2.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/easycaching.inmemory/1.8.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/easycaching.inmemory/1.8.0/lib/net6.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/easycaching.inmemory/1.8.0/lib/netstandard2.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/easycaching.redis/1.8.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/easycaching.redis/1.8.0/lib/net6.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/easycaching.redis/1.8.0/lib/netstandard2.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/easycaching.serialization.systemtextjson/1.8.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/easycaching.serialization.systemtextjson/1.8.0/lib/net6.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/easycaching.serialization.systemtextjson/1.8.0/lib/netstandard2.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/fluentassertions/6.12.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/fluentassertions/6.12.0/lib/net47/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/fluentassertions/6.12.0/lib/net6.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/fluentassertions/6.12.0/lib/netcoreapp2.1/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/fluentassertions/6.12.0/lib/netcoreapp3.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/fluentassertions/6.12.0/lib/netstandard2.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/fluentassertions/6.12.0/lib/netstandard2.1/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/fluentvalidation.dependencyinjectionextensions/12.1.1/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/fluentvalidation.dependencyinjectionextensions/12.1.1/lib/net8.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/fluentvalidation/12.1.1/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/fluentvalidation/12.1.1/lib/net8.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/humanizer.core/2.14.1/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/humanizer.core/2.14.1/lib/net6.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/humanizer.core/2.14.1/lib/netstandard1.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/humanizer.core/2.14.1/lib/netstandard2.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/mailkit/4.14.1/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/mailkit/4.14.1/docs/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/mailkit/4.14.1/lib/net462/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/mailkit/4.14.1/lib/net47/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/mailkit/4.14.1/lib/net48/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/mailkit/4.14.1/lib/net8.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/mailkit/4.14.1/lib/netstandard2.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/mailkit/4.14.1/lib/netstandard2.1/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/mapster.core/1.2.1/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/mapster.dependencyinjection/1.0.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/mapster/5.0.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/mapster/7.4.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/microsoft.aspnetcore.authentication.jwtbearer/10.0.1/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/microsoft.aspnetcore.authentication.jwtbearer/10.0.1/lib/net10.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/microsoft.bcl.asyncinterfaces/8.0.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/microsoft.bcl.asyncinterfaces/8.0.0/buildTransitive/net461/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/microsoft.bcl.asyncinterfaces/8.0.0/buildTransitive/net462/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/microsoft.bcl.asyncinterfaces/8.0.0/lib/net462/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/microsoft.bcl.asyncinterfaces/8.0.0/lib/netstandard2.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/microsoft.bcl.asyncinterfaces/8.0.0/lib/netstandard2.1/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/microsoft.bcl.cryptography/9.0.4/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/microsoft.bcl.cryptography/9.0.4/buildTransitive/net461/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/microsoft.bcl.cryptography/9.0.4/buildTransitive/net462/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/microsoft.bcl.cryptography/9.0.4/buildTransitive/net8.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/microsoft.bcl.cryptography/9.0.4/buildTransitive/netcoreapp2.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/microsoft.bcl.cryptography/9.0.4/lib/net462/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/microsoft.bcl.cryptography/9.0.4/lib/net8.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/microsoft.bcl.cryptography/9.0.4/lib/net9.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/microsoft.bcl.cryptography/9.0.4/lib/netstandard2.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/microsoft.build.framework/17.11.31/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/microsoft.build.framework/17.11.31/lib/net472/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/microsoft.build.framework/17.11.31/lib/net8.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/microsoft.build.framework/17.11.31/notices/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/microsoft.build.framework/17.11.31/ref/net472/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/microsoft.build.framework/17.11.31/ref/net8.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/microsoft.build.framework/17.11.31/ref/netstandard2.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/microsoft.build.framework/18.0.2/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/microsoft.build.framework/18.0.2/lib/net10.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/microsoft.build.framework/18.0.2/lib/net472/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/microsoft.build.framework/18.0.2/notices/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/microsoft.build.framework/18.0.2/ref/net10.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/microsoft.build.framework/18.0.2/ref/net472/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/microsoft.build.framework/18.0.2/ref/netstandard2.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/microsoft.codeanalysis.analyzers/3.11.0/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/microsoft.codeanalysis.analyzers/3.11.0/buildTransitive/
-
-
-## ApiResources/TalentManagement-API/.nuget/packages/microsoft.codeanalysis.analyzers/3.11.0/buildTransitive/config/
-
-
-## ApiResources/TalentManagement-API/TalentManagementAPI.Infrastructure.Shared/
-
-
-## ApiResources/TalentManagement-API/TalentManagementAPI.Infrastructure.Shared/Services/
-
-
-## ApiResources/TalentManagement-API/TalentManagementAPI.Infrastructure.Tests/Services/
-
-
-## ApiResources/TalentManagement-API/TalentManagementAPI.WebApi.Tests/Controllers/
-
-
-## Clients/TalentManagement-Angular-Material/talent-management/public/data/
-
-
-## Clients/TalentManagement-Angular-Material/talent-management/src/
-
-
-## Clients/TalentManagement-Angular-Material/talent-management/src/app/
-
-- `app.routes.ts` — Exports routes (~1443 tok)
-
-## Clients/TalentManagement-Angular-Material/talent-management/src/app/routes/ai/ai-assistant/
-
-
-## Clients/TalentManagement-Angular-Material/talent-management/src/app/routes/ai/ai-hr-insight/
-
-
-## Clients/TalentManagement-Angular-Material/talent-management/src/app/routes/ai/ai-nl-search/
-
-
-## Clients/TalentManagement-Angular-Material/talent-management/src/app/services/api/
-
-
-## Tests/AngularNetTutorial-Playwright/
-
-- `.gitignore` — Git ignore rules (~152 tok)
-- `package.json` — Node.js package manifest (~250 tok)
-- `playwright.config.ts` — Load environment-specific credentials and URLs. (~1744 tok)
-- `tsconfig.json` — TypeScript configuration (~91 tok)
-
-## Tests/AngularNetTutorial-Playwright/.github/workflows/
-
-- `playwright.yml` — CI: Playwright Tests (~1274 tok)
-
-## Tests/AngularNetTutorial-Playwright/config/
-
-- `environments.json` (~314 tok)
-- `test-config.ts` — Centralized Test Configuration (~2050 tok)
-- `test-users.json` (~267 tok)
-
-## Tests/AngularNetTutorial-Playwright/fixtures/
-
-- `api.fixtures.ts` — API Fixtures (~3016 tok)
-- `auth.fixtures.ts` — Authentication Fixtures (~4316 tok)
-
-## Tests/AngularNetTutorial-Playwright/scripts/
-
-- `fetch-secrets.ts` — Azure Key Vault → .env generator (~927 tok)
-
-## Tests/AngularNetTutorial-Playwright/tests/
-
-- `auth.setup.ts` — Global Authentication Setup (~536 tok)
-- `diagnostic.spec.ts` — Declares currentUrl (~498 tok)
-
-## Tests/AngularNetTutorial-Playwright/tests/accessibility/
-
-- `aria-labels.spec.ts` — ARIA Labels Tests (~2252 tok)
-- `keyboard-navigation.spec.ts` — Keyboard Navigation Tests (~2162 tok)
-
-## Tests/AngularNetTutorial-Playwright/tests/ai/
-
-- `ai-assistant.spec.ts` — AI Assistant Page Tests (~1506 tok)
-- `ai-hr-insight.spec.ts` — AI HR Insight Page Tests (~1200 tok)
-- `ai-navigation.spec.ts` — AI Submenu Navigation Tests (~1174 tok)
-- `ai-nl-search.spec.ts` — AI NL Search Page Tests (~1478 tok)
-- `ai-vector-search.spec.ts` — AI Vector Search Page Tests (~1679 tok)
-
-## Tests/AngularNetTutorial-Playwright/tests/api/
-
-- `auth-api.spec.ts` — Authentication API Tests (~5266 tok)
-- `cache-api.spec.ts` — Cache API Tests (~4486 tok)
-- `departments-api.spec.ts` — Department API Tests (~4574 tok)
-- `employees-api.spec.ts` — Employee API Tests (~5235 tok)
-
-## Tests/AngularNetTutorial-Playwright/tests/auth/
-
-- `login.spec.ts` — Authentication Tests - Login Flow (~2167 tok)
-
-## Tests/AngularNetTutorial-Playwright/tests/dashboard/
-
-- `dashboard-metrics.spec.ts` — Dashboard Metrics Tests (~2546 tok)
-- `dashboard-navigation.spec.ts` — Dashboard Navigation Tests (~3406 tok)
-
-## Tests/AngularNetTutorial-Playwright/tests/department-management/
-
-- `department-crud.spec.ts` — Department CRUD Tests (~2104 tok)
-- `department-validation.spec.ts` — Department Validation Tests (~2776 tok)
-
-## Tests/AngularNetTutorial-Playwright/tests/employee-management/
-
-- `employee-create.spec.ts` — Employee Create Tests (~3904 tok)
-- `employee-delete.spec.ts` — Employee Delete Tests (~2562 tok)
-- `employee-edit.spec.ts` — Employee Edit Tests (~2653 tok)
-- `employee-smoke.spec.ts` — Employee Management Smoke Tests (~1898 tok)
-
-## Tests/AngularNetTutorial-Playwright/tests/error-handling/
-
-- `api-errors.spec.ts` — API Error Handling Tests (~2315 tok)
-- `network-errors.spec.ts` — Network Error Handling Tests (~2617 tok)
-
-## Tests/AngularNetTutorial-Playwright/tests/navigation/
-
-- `routing.spec.ts` — Navigation & Routing Tests (~3421 tok)
-
-## Tests/AngularNetTutorial-Playwright/tests/performance/
-
-- `large-datasets.spec.ts` — Large Datasets Performance Tests (~2161 tok)
-- `load-time.spec.ts` — Load Time Performance Tests (~1689 tok)
-
-## Tests/AngularNetTutorial-Playwright/tests/position-management/
-
-- `position-crud.spec.ts` — Position CRUD Tests (HRAdmin Only) (~2524 tok)
-
-## Tests/AngularNetTutorial-Playwright/tests/salary-ranges/
-
-- `salary-range-crud.spec.ts` — Salary Range CRUD Tests (~2128 tok)
-- `salary-range-validation.spec.ts` — Salary Range Validation Tests (~2615 tok)
-
-## Tests/AngularNetTutorial-Playwright/tests/screenshots/
-
-- `blog-screenshots.spec.ts` — Blog Screenshots (~10494 tok)
-
-## Tests/AngularNetTutorial-Playwright/tests/validation/
-
-- `form-validation.spec.ts` — Form Validation Edge Cases Tests (~4296 tok)
-
-## Tests/AngularNetTutorial-Playwright/tests/visual/
-
-- `dashboard-visual.spec.ts` — Dashboard Visual Regression Tests (~1494 tok)
-- `forms-visual.spec.ts` — Forms Visual Regression Tests (~1318 tok)
-
-## Tests/AngularNetTutorial-Playwright/tests/workflows/
-
-- `complete-employee-workflow.spec.ts` — Complete Employee Workflow Test (~2659 tok)
-- `hradmin-operations.spec.ts` — HRAdmin Operations Workflow Test (~3424 tok)
-- `manager-daily-tasks.spec.ts` — Manager Daily Tasks Workflow Test (~3308 tok)
-
-## Tests/AngularNetTutorial-Playwright/utils/
-
-- `token-manager.ts` — Token Manager Utility (~2218 tok)
-
-## blogs/
-
-- `SERIES-NAVIGATION-TOC.md` — AngularNetTutorial — Series Navigation (~1927 tok)
-
-## blogs/series-6-ai-app-features/
-
-- `6.1-dotnet-ai-foundation.md` — Run a Local LLM in Your .NET 10 API with Ollama (~6391 tok)
-- `6.2-dotnet-ai-hr-assistant.md` — Build an HR AI Assistant That Knows Your Data (~4920 tok)
-- `6.3-angular-ai-chat-widget.md` — Build a Dedicated AI Section in Angular with Submenu Navigation (~4967 tok)
-- `6.4-angular-ai-nl-search.md` — Natural Language Employee Search in Angular Material (~4897 tok)
-- `6.4.1-dotnet-natural-language-search.md` — Natural Language Employee Search with LLM Query Parsing (~7263 tok)
-- `6.5-angular-ai-vector-search.md` — Semantic Position Search with Vector Embeddings in Angular Material (~4180 tok)
-- `6.6-dotnet-ai-response-caching.md` — Cache Your AI Responses: Save Time and API Costs (~4185 tok)
-- `6.7-dotnet-mssql-vector-search.md` — Semantic Position Search with SQL Server Native Vector Search (~6594 tok)
-- `6.8-dotnet-ai-response-caching.md` — Cache Your AI Responses: Save Time and API Costs (~4228 tok)
diff --git a/.wolf/buglog.json b/.wolf/buglog.json
deleted file mode 100644
index 4b2cbee..0000000
--- a/.wolf/buglog.json
+++ /dev/null
@@ -1,885 +0,0 @@
-{
- "version": 1,
- "bugs": [
- {
- "id": "bug-001",
- "timestamp": "2026-04-21T17:17:05.870Z",
- "error_message": "Type error",
- "file": "Tests/AngularNetTutorial-Playwright/tests/screenshots/blog-screenshots.spec.ts",
- "root_cause": "Missing or incorrect type annotation",
- "fix": "Added type assertion/annotation",
- "tags": [
- "auto-detected",
- "type-fix",
- "ts"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-21T17:17:05.870Z"
- },
- {
- "id": "bug-002",
- "timestamp": "2026-04-22T02:40:07.617Z",
- "error_message": "Incorrect value in code",
- "file": "Clients/TalentManagement-Angular-Material/talent-management/public/data/menu.json",
- "root_cause": "Had \"psychology\"",
- "fix": "Changed to \"memory\"",
- "tags": [
- "auto-detected",
- "wrong-value",
- "json"
- ],
- "related_bugs": [],
- "occurrences": 2,
- "last_seen": "2026-04-22T02:41:41.298Z"
- },
- {
- "id": "bug-003",
- "timestamp": "2026-04-22T02:41:36.799Z",
- "error_message": "Incorrect value in code",
- "file": "Clients/TalentManagement-Angular-Material/talent-management/src/index.html",
- "root_cause": "Had \"https://fonts.googleapis.com/icon?family=Material",
- "fix": "Changed to \"preconnect\"",
- "tags": [
- "auto-detected",
- "wrong-value",
- "html"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-22T02:41:36.799Z"
- },
- {
- "id": "bug-004",
- "timestamp": "2026-04-22T02:52:28.052Z",
- "error_message": "Wrong return value",
- "file": "Clients/TalentManagement-Angular-Material/talent-management/src/app/services/api/ai.service.ts",
- "root_cause": "Was returning: this.http.post(`${this.apiUrl}/",
- "fix": "Now returns: this.http.post>(`${th",
- "tags": [
- "auto-detected",
- "return-value",
- "ts"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-22T02:52:28.052Z"
- },
- {
- "id": "bug-005",
- "timestamp": "2026-04-22T02:58:35.635Z",
- "error_message": "Wrong return value",
- "file": "Clients/TalentManagement-Angular-Material/talent-management/src/app/services/api/ai.service.ts",
- "root_cause": "Was returning: this.http.post(`${this.apiUrl}/a",
- "fix": "Now returns: this.http.post>(`${thi",
- "tags": [
- "auto-detected",
- "return-value",
- "ts"
- ],
- "related_bugs": [],
- "occurrences": 2,
- "last_seen": "2026-04-22T03:02:43.517Z"
- },
- {
- "id": "bug-006",
- "timestamp": "2026-04-22T02:58:44.709Z",
- "error_message": "Null/undefined access in ",
- "file": "Clients/TalentManagement-Angular-Material/talent-management/src/app/routes/ai/ai-nl-search/ai-nl-search.component.html",
- "root_cause": "Property access on potentially null/undefined value",
- "fix": "Added null safety (optional chaining or null check)",
- "tags": [
- "auto-detected",
- "null-safety",
- "html"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-22T02:58:44.709Z"
- },
- {
- "id": "bug-007",
- "timestamp": "2026-04-22T03:04:54.340Z",
- "error_message": "Null/undefined access in ",
- "file": "Clients/TalentManagement-Angular-Material/talent-management/src/app/routes/ai/ai-nl-search/ai-nl-search.component.ts",
- "root_cause": "Property access on potentially null/undefined value",
- "fix": "Added null safety (optional chaining or null check)",
- "tags": [
- "auto-detected",
- "null-safety",
- "ts"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-22T03:04:54.340Z"
- },
- {
- "id": "bug-008",
- "timestamp": "2026-04-22T03:21:14.869Z",
- "error_message": "Incorrect value in code",
- "file": "blogs/series-6-ai-app-features/6.3-angular-ai-chat-widget.md",
- "root_cause": "Had 's AI features from a single `",
- "fix": "Changed to `ai`",
- "tags": [
- "auto-detected",
- "wrong-value",
- "md"
- ],
- "related_bugs": [],
- "occurrences": 2,
- "last_seen": "2026-04-22T03:21:36.132Z"
- },
- {
- "id": "bug-009",
- "timestamp": "2026-04-22T11:15:22.923Z",
- "error_message": "Significant refactor of ",
- "file": "../../Users/Fuji Nguyen/.claude/projects/c--apps-AngularNetTutotial/memory/project_blog_image_paths.md",
- "root_cause": "19 lines replaced/restructured",
- "fix": "Rewrote 51→83 lines (19 removed)",
- "tags": [
- "auto-detected",
- "refactor",
- "md"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-22T11:15:22.923Z"
- },
- {
- "id": "bug-010",
- "timestamp": "2026-04-22T11:15:29.442Z",
- "error_message": "Incorrect value in code",
- "file": "../../Users/Fuji Nguyen/.claude/projects/c--apps-AngularNetTutotial/memory/MEMORY.md",
- "root_cause": "Had `../../Tests/AngularNetTutorial-Playwright/screens",
- "fix": "Changed to `../../Tests/AngularNetTutorial-Playwright/screens",
- "tags": [
- "auto-detected",
- "wrong-value",
- "md"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-22T11:15:29.442Z"
- },
- {
- "id": "bug-011",
- "timestamp": "2026-04-22T11:47:54.676Z",
- "error_message": "Significant refactor of ",
- "file": "blogs/series-6-ai-app-features/6.1-dotnet-ai-foundation.md",
- "root_cause": "2 lines replaced/restructured",
- "fix": "Rewrote 14→16 lines (2 removed) | Also: The `IChatClient` abstraction from Microsoft is th; * **Provider-agnostic AI abstractions** — The `ICh",
- "tags": [
- "auto-detected",
- "refactor",
- "md"
- ],
- "related_bugs": [],
- "occurrences": 2,
- "last_seen": "2026-04-22T11:48:18.706Z"
- },
- {
- "id": "bug-012",
- "timestamp": "2026-04-22T14:07:06.877Z",
- "error_message": "Incorrect value in code",
- "file": "ApiResources/TalentManagement-API/TalentManagementAPI.Infrastructure.Shared/TalentManagementAPI.Infrastructure.Shared.csproj",
- "root_cause": "Had \"OllamaSharp\"",
- "fix": "Changed to \"Microsoft.Extensions.AI\"",
- "tags": [
- "auto-detected",
- "wrong-value",
- "csproj"
- ],
- "related_bugs": [],
- "occurrences": 2,
- "last_seen": "2026-04-22T14:07:32.511Z"
- },
- {
- "id": "bug-013",
- "timestamp": "2026-04-22T14:14:23.091Z",
- "error_message": "Incorrect value in code",
- "file": "ApiResources/TalentManagement-API/TalentManagementAPI.Infrastructure.Shared/TalentManagementAPI.Infrastructure.Shared.csproj",
- "root_cause": "Had \"Microsoft.Extensions.AI\"",
- "fix": "Changed to \"Microsoft.Extensions.AI.Abstractions\"",
- "tags": [
- "auto-detected",
- "wrong-value",
- "csproj"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-22T14:14:23.091Z"
- },
- {
- "id": "bug-014",
- "timestamp": "2026-04-22T18:13:22.809Z",
- "error_message": "Significant refactor of ",
- "file": "blogs/series-6-ai-app-features/6.1-dotnet-ai-foundation.md",
- "root_cause": "3 lines replaced/restructured",
- "fix": "Rewrote 7→9 lines (3 removed) | Also: Add OllamaSharp to the Infrastructure.Shared proje; **Why OllamaSharp instead of `Microsoft.Extensions | Also: In `Infrastructure.Shared/ServiceRegistration.cs`,; // Register the Ollama client as a singleton — one | Also: OllamaSharp's native `IAsyncEnumerable<>` streamin; The custom `IAiChatService` interface pattern is t",
- "tags": [
- "auto-detected",
- "refactor",
- "md"
- ],
- "related_bugs": [],
- "occurrences": 4,
- "last_seen": "2026-04-22T18:14:46.417Z"
- },
- {
- "id": "bug-015",
- "timestamp": "2026-04-22T18:15:05.533Z",
- "error_message": "Incorrect value in code",
- "file": "blogs/series-6-ai-app-features/6.1-dotnet-ai-foundation.md",
- "root_cause": "Had `404`",
- "fix": "Changed to `503 Service Unavailable`",
- "tags": [
- "auto-detected",
- "wrong-value",
- "md"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-22T18:15:05.533Z"
- },
- {
- "id": "bug-016",
- "timestamp": "2026-04-22T18:52:28.062Z",
- "error_message": "Incorrect value in code",
- "file": "Tests/AngularNetTutorial-Playwright/tests/screenshots/blog-screenshots.spec.ts",
- "root_cause": "Had 'screenshots-output'",
- "fix": "Changed to '..'",
- "tags": [
- "auto-detected",
- "wrong-value",
- "ts"
- ],
- "related_bugs": [],
- "occurrences": 3,
- "last_seen": "2026-04-22T18:53:34.248Z"
- },
- {
- "id": "bug-017",
- "timestamp": "2026-04-22T18:56:28.261Z",
- "error_message": "Wrong reference: images should be screenshots",
- "file": "Tests/AngularNetTutorial-Playwright/tests/screenshots/blog-screenshots.spec.ts",
- "root_cause": "Used \"images\" instead of \"screenshots\"",
- "fix": "Changed images → screenshots",
- "tags": [
- "auto-detected",
- "wrong-reference",
- "ts"
- ],
- "related_bugs": [],
- "occurrences": 2,
- "last_seen": "2026-04-22T18:56:42.804Z"
- },
- {
- "id": "bug-018",
- "timestamp": "2026-04-22T18:56:38.182Z",
- "error_message": "Wrong reference: images should be screenshots",
- "file": "blogs/series-6-ai-app-features/6.1-dotnet-ai-foundation.md",
- "root_cause": "Used \"images\" instead of \"screenshots\"",
- "fix": "Changed images → screenshots",
- "tags": [
- "auto-detected",
- "wrong-reference",
- "md"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-22T18:56:38.182Z"
- },
- {
- "id": "bug-019",
- "timestamp": "2026-04-22T20:34:48.779Z",
- "error_message": "Significant refactor of ",
- "file": "blogs/SERIES-NAVIGATION-TOC.md",
- "root_cause": "3 lines replaced/restructured",
- "fix": "Rewrote 8→10 lines (3 removed)",
- "tags": [
- "auto-detected",
- "refactor",
- "md"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-22T20:34:48.779Z"
- },
- {
- "id": "bug-020",
- "timestamp": "2026-04-23T02:33:32.404Z",
- "error_message": "Incorrect value in code",
- "file": "Clients/TalentManagement-Angular-Material/talent-management/src/app/app.routes.ts",
- "root_cause": "Had 'ai-chat'",
- "fix": "Changed to '403'",
- "tags": [
- "auto-detected",
- "wrong-value",
- "ts"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-23T02:33:32.404Z"
- },
- {
- "id": "bug-021",
- "timestamp": "2026-04-23T02:34:00.669Z",
- "error_message": "Incorrect value in code",
- "file": "blogs/series-6-ai-app-features/6.3-angular-ai-chat-widget.md",
- "root_cause": "Had `ai-chat`",
- "fix": "Changed to `ai`",
- "tags": [
- "auto-detected",
- "wrong-value",
- "md"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-23T02:34:00.669Z"
- },
- {
- "id": "bug-022",
- "timestamp": "2026-04-28T01:04:57.687Z",
- "error_message": "Significant refactor of ",
- "file": "Tests/AngularNetTutorial-Playwright/package.json",
- "root_cause": "2 lines replaced/restructured",
- "fix": "Rewrote 14→23 lines (2 removed)",
- "tags": [
- "auto-detected",
- "refactor",
- "json"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-28T01:04:57.687Z"
- },
- {
- "id": "bug-023",
- "timestamp": "2026-04-28T01:06:22.653Z",
- "error_message": "Type error",
- "file": "Tests/AngularNetTutorial-Playwright/config/test-config.ts",
- "root_cause": "Missing or incorrect type annotation",
- "fix": "Added type assertion/annotation",
- "tags": [
- "auto-detected",
- "type-fix",
- "ts"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-28T01:06:22.653Z"
- },
- {
- "id": "bug-024",
- "timestamp": "2026-04-28T01:07:15.206Z",
- "error_message": "Incorrect value in code",
- "file": "Tests/AngularNetTutorial-Playwright/fixtures/auth.fixtures.ts",
- "root_cause": "Had '../config/test-users.json'",
- "fix": "Changed to '../config/test-config'",
- "tags": [
- "auto-detected",
- "wrong-value",
- "ts"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-28T01:07:15.206Z"
- },
- {
- "id": "bug-025",
- "timestamp": "2026-04-28T01:07:31.053Z",
- "error_message": "Significant refactor of ",
- "file": "Tests/AngularNetTutorial-Playwright/fixtures/auth.fixtures.ts",
- "root_cause": "51 lines replaced/restructured",
- "fix": "Rewrote 72→10 lines (51 removed)",
- "tags": [
- "auto-detected",
- "refactor",
- "ts"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-28T01:07:31.053Z"
- },
- {
- "id": "bug-026",
- "timestamp": "2026-04-28T01:07:57.167Z",
- "error_message": "Significant refactor of ",
- "file": "Tests/AngularNetTutorial-Playwright/utils/token-manager.ts",
- "root_cause": "4 lines replaced/restructured",
- "fix": "Rewrote 6→6 lines (4 removed)",
- "tags": [
- "auto-detected",
- "refactor",
- "ts"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-28T01:07:57.167Z"
- },
- {
- "id": "bug-027",
- "timestamp": "2026-04-28T01:09:25.096Z",
- "error_message": "Incorrect value in code",
- "file": "Tests/AngularNetTutorial-Playwright/tests/visual/dashboard-visual.spec.ts",
- "root_cause": "Had '../../fixtures/auth.fixtures'",
- "fix": "Changed to '../../config/test-config'",
- "tags": [
- "auto-detected",
- "wrong-value",
- "ts"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-28T01:09:25.096Z"
- },
- {
- "id": "bug-028",
- "timestamp": "2026-04-28T01:09:25.182Z",
- "error_message": "Incorrect value in code",
- "file": "Tests/AngularNetTutorial-Playwright/tests/ai/ai-assistant.spec.ts",
- "root_cause": "Had '../../fixtures/auth.fixtures'",
- "fix": "Changed to '../../page-objects/ai-assistant.page'",
- "tags": [
- "auto-detected",
- "wrong-value",
- "ts"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-28T01:09:25.182Z"
- },
- {
- "id": "bug-029",
- "timestamp": "2026-04-28T01:09:33.507Z",
- "error_message": "Incorrect value in code",
- "file": "Tests/AngularNetTutorial-Playwright/tests/ai/ai-hr-insight.spec.ts",
- "root_cause": "Had '../../fixtures/auth.fixtures'",
- "fix": "Changed to '../../page-objects/ai-hr-insight.page'",
- "tags": [
- "auto-detected",
- "wrong-value",
- "ts"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-28T01:09:33.507Z"
- },
- {
- "id": "bug-030",
- "timestamp": "2026-04-28T01:09:34.340Z",
- "error_message": "Incorrect value in code",
- "file": "Tests/AngularNetTutorial-Playwright/tests/visual/forms-visual.spec.ts",
- "root_cause": "Had '../../fixtures/auth.fixtures'",
- "fix": "Changed to '../../config/test-config'",
- "tags": [
- "auto-detected",
- "wrong-value",
- "ts"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-28T01:09:34.340Z"
- },
- {
- "id": "bug-031",
- "timestamp": "2026-04-28T01:09:50.908Z",
- "error_message": "Significant refactor of ",
- "file": "Tests/AngularNetTutorial-Playwright/tests/accessibility/aria-labels.spec.ts",
- "root_cause": "9 lines replaced/restructured",
- "fix": "Rewrote 12→4 lines (9 removed)",
- "tags": [
- "auto-detected",
- "refactor",
- "ts"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-28T01:09:50.908Z"
- },
- {
- "id": "bug-032",
- "timestamp": "2026-04-28T01:09:52.830Z",
- "error_message": "Incorrect value in code",
- "file": "Tests/AngularNetTutorial-Playwright/tests/ai/ai-nl-search.spec.ts",
- "root_cause": "Had '../../fixtures/auth.fixtures'",
- "fix": "Changed to '../../page-objects/ai-nl-search.page'",
- "tags": [
- "auto-detected",
- "wrong-value",
- "ts"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-28T01:09:52.830Z"
- },
- {
- "id": "bug-033",
- "timestamp": "2026-04-28T01:09:55.269Z",
- "error_message": "Incorrect value in code",
- "file": "Tests/AngularNetTutorial-Playwright/tests/accessibility/keyboard-navigation.spec.ts",
- "root_cause": "Had '../../fixtures/auth.fixtures'",
- "fix": "Changed to '../../page-objects/employee-form.page'",
- "tags": [
- "auto-detected",
- "wrong-value",
- "ts"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-28T01:09:55.269Z"
- },
- {
- "id": "bug-034",
- "timestamp": "2026-04-28T01:09:59.701Z",
- "error_message": "Significant refactor of ",
- "file": "Tests/AngularNetTutorial-Playwright/tests/accessibility/keyboard-navigation.spec.ts",
- "root_cause": "9 lines replaced/restructured",
- "fix": "Rewrote 12→4 lines (9 removed)",
- "tags": [
- "auto-detected",
- "refactor",
- "ts"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-28T01:09:59.702Z"
- },
- {
- "id": "bug-035",
- "timestamp": "2026-04-28T01:10:04.321Z",
- "error_message": "Incorrect value in code",
- "file": "Tests/AngularNetTutorial-Playwright/tests/ai/ai-vector-search.spec.ts",
- "root_cause": "Had '../../fixtures/auth.fixtures'",
- "fix": "Changed to '../../page-objects/ai-vector-search.page'",
- "tags": [
- "auto-detected",
- "wrong-value",
- "ts"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-28T01:10:04.321Z"
- },
- {
- "id": "bug-036",
- "timestamp": "2026-04-28T01:15:41.133Z",
- "error_message": "Significant refactor of ",
- "file": "Tests/AngularNetTutorial-Playwright/playwright.config.ts",
- "root_cause": "2 lines replaced/restructured",
- "fix": "Rewrote 6→14 lines (2 removed)",
- "tags": [
- "auto-detected",
- "refactor",
- "ts"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-28T01:15:41.133Z"
- },
- {
- "id": "bug-037",
- "timestamp": "2026-04-28T01:15:51.145Z",
- "error_message": "Significant refactor of ",
- "file": "Tests/AngularNetTutorial-Playwright/package.json",
- "root_cause": "4 lines replaced/restructured",
- "fix": "Rewrote 8→10 lines (4 removed)",
- "tags": [
- "auto-detected",
- "refactor",
- "json"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-28T01:15:51.145Z"
- },
- {
- "id": "bug-038",
- "timestamp": "2026-04-28T01:18:14.566Z",
- "error_message": "Significant refactor of ",
- "file": "Tests/AngularNetTutorial-Playwright/config/environments.json",
- "root_cause": "5 lines replaced/restructured",
- "fix": "Rewrote 8→9 lines (5 removed)",
- "tags": [
- "auto-detected",
- "refactor",
- "json"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-28T01:18:14.566Z"
- },
- {
- "id": "bug-039",
- "timestamp": "2026-04-28T01:24:04.116Z",
- "error_message": "Incorrect value in code",
- "file": "Tests/AngularNetTutorial-Playwright/fixtures/auth.fixtures.ts",
- "root_cause": "Had 'a:has-text(\"",
- "fix": "Changed to `a:has-text(\"",
- "tags": [
- "auto-detected",
- "wrong-value",
- "ts"
- ],
- "related_bugs": [],
- "occurrences": 2,
- "last_seen": "2026-04-28T01:27:31.169Z"
- },
- {
- "id": "bug-040",
- "timestamp": "2026-04-28T01:25:07.366Z",
- "error_message": "Missing await",
- "file": "Tests/AngularNetTutorial-Playwright/config/test-config.ts",
- "root_cause": "Async call without await — returned Promise instead of value",
- "fix": "Added await to async call",
- "tags": [
- "auto-detected",
- "async-fix",
- "ts"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-28T01:25:07.366Z"
- },
- {
- "id": "bug-041",
- "timestamp": "2026-04-28T01:26:13.761Z",
- "error_message": "Wrong condition in logic",
- "file": "Tests/AngularNetTutorial-Playwright/tests/diagnostic.spec.ts",
- "root_cause": "Condition was: if (currentUrl.includes('localhost:4200')",
- "fix": "Changed to: if (currentUrl.includes(new URL(APP_URLS.angular)",
- "tags": [
- "auto-detected",
- "logic-fix",
- "ts"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-28T01:26:13.761Z"
- },
- {
- "id": "bug-042",
- "timestamp": "2026-04-28T01:26:27.368Z",
- "error_message": "Wrong reference: localhost should be getAngularUrlPattern",
- "file": "Tests/AngularNetTutorial-Playwright/tests/auth/login.spec.ts",
- "root_cause": "Used \"localhost\" instead of \"getAngularUrlPattern\"",
- "fix": "Changed localhost → getAngularUrlPattern",
- "tags": [
- "auto-detected",
- "wrong-reference",
- "ts"
- ],
- "related_bugs": [],
- "occurrences": 2,
- "last_seen": "2026-04-28T01:26:33.257Z"
- },
- {
- "id": "bug-043",
- "timestamp": "2026-04-28T01:26:44.009Z",
- "error_message": "Wrong reference: localhost should be getAngularUrlPattern",
- "file": "Tests/AngularNetTutorial-Playwright/tests/navigation/routing.spec.ts",
- "root_cause": "Used \"localhost\" instead of \"getAngularUrlPattern\"",
- "fix": "Changed localhost → getAngularUrlPattern",
- "tags": [
- "auto-detected",
- "wrong-reference",
- "ts"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-28T01:26:44.009Z"
- },
- {
- "id": "bug-044",
- "timestamp": "2026-04-28T01:26:55.903Z",
- "error_message": "Wrong reference: localhost should be getAngularUrlPattern",
- "file": "Tests/AngularNetTutorial-Playwright/tests/workflows/manager-daily-tasks.spec.ts",
- "root_cause": "Used \"localhost\" instead of \"getAngularUrlPattern\"",
- "fix": "Changed localhost → getAngularUrlPattern",
- "tags": [
- "auto-detected",
- "wrong-reference",
- "ts"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-28T01:26:55.903Z"
- },
- {
- "id": "bug-045",
- "timestamp": "2026-04-28T01:27:06.245Z",
- "error_message": "Wrong reference: localhost should be getAngularUrlPattern",
- "file": "Tests/AngularNetTutorial-Playwright/tests/workflows/complete-employee-workflow.spec.ts",
- "root_cause": "Used \"localhost\" instead of \"getAngularUrlPattern\"",
- "fix": "Changed localhost → getAngularUrlPattern",
- "tags": [
- "auto-detected",
- "wrong-reference",
- "ts"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-28T01:27:06.245Z"
- },
- {
- "id": "bug-046",
- "timestamp": "2026-04-28T01:27:16.938Z",
- "error_message": "Wrong reference: localhost should be getAngularUrlPattern",
- "file": "Tests/AngularNetTutorial-Playwright/tests/workflows/hradmin-operations.spec.ts",
- "root_cause": "Used \"localhost\" instead of \"getAngularUrlPattern\"",
- "fix": "Changed localhost → getAngularUrlPattern",
- "tags": [
- "auto-detected",
- "wrong-reference",
- "ts"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-28T01:27:16.938Z"
- },
- {
- "id": "bug-047",
- "timestamp": "2026-04-28T01:48:42.968Z",
- "error_message": "Incorrect value in code",
- "file": "Tests/AngularNetTutorial-Playwright/fixtures/auth.fixtures.ts",
- "root_cause": "Had 'networkidle'",
- "fix": "Changed to 'domcontentloaded'",
- "tags": [
- "auto-detected",
- "wrong-value",
- "ts"
- ],
- "related_bugs": [],
- "occurrences": 2,
- "last_seen": "2026-04-28T01:48:46.422Z"
- },
- {
- "id": "bug-048",
- "timestamp": "2026-04-28T01:50:16.302Z",
- "error_message": "Significant refactor of ",
- "file": "Tests/AngularNetTutorial-Playwright/fixtures/auth.fixtures.ts",
- "root_cause": "3 lines replaced/restructured",
- "fix": "Rewrote 18→22 lines (3 removed)",
- "tags": [
- "auto-detected",
- "refactor",
- "ts"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-28T01:50:16.302Z"
- },
- {
- "id": "bug-049",
- "timestamp": "2026-04-28T01:52:37.674Z",
- "error_message": "Missing error handling in function",
- "file": "Tests/AngularNetTutorial-Playwright/fixtures/auth.fixtures.ts",
- "root_cause": "Code path had no error handling — exceptions would propagate uncaught",
- "fix": "Added try/catch block",
- "tags": [
- "auto-detected",
- "error-handling",
- "ts"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-28T01:52:37.674Z"
- },
- {
- "id": "bug-050",
- "timestamp": "2026-04-28T01:52:42.769Z",
- "error_message": "Type error",
- "file": "Tests/AngularNetTutorial-Playwright/tests/employee-management/employee-smoke.spec.ts",
- "root_cause": "Missing or incorrect type annotation",
- "fix": "Added type assertion/annotation",
- "tags": [
- "auto-detected",
- "type-fix",
- "ts"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-28T01:52:42.769Z"
- },
- {
- "id": "bug-051",
- "timestamp": "2026-04-28T01:57:12.310Z",
- "error_message": "Type error",
- "file": "Tests/AngularNetTutorial-Playwright/fixtures/auth.fixtures.ts",
- "root_cause": "Missing or incorrect type annotation",
- "fix": "Added type assertion/annotation",
- "tags": [
- "auto-detected",
- "type-fix",
- "ts"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-28T01:57:12.310Z"
- },
- {
- "id": "bug-052",
- "timestamp": "2026-04-28T01:57:33.003Z",
- "error_message": "Significant refactor of ",
- "file": "Tests/AngularNetTutorial-Playwright/tests/employee-management/employee-smoke.spec.ts",
- "root_cause": "2 lines replaced/restructured",
- "fix": "Rewrote 3→5 lines (2 removed)",
- "tags": [
- "auto-detected",
- "refactor",
- "ts"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-28T01:57:33.003Z"
- },
- {
- "id": "bug-053",
- "timestamp": "2026-04-28T01:58:28.569Z",
- "error_message": "Type error",
- "file": "Tests/AngularNetTutorial-Playwright/tests/employee-management/employee-smoke.spec.ts",
- "root_cause": "Missing or incorrect type annotation",
- "fix": "Added type assertion/annotation | Also: // test.fixme: The employee list table is populated from the; // (https://localhost:44378). Currently the API returns ERR_",
- "tags": [
- "auto-detected",
- "type-fix",
- "ts"
- ],
- "related_bugs": [],
- "occurrences": 2,
- "last_seen": "2026-04-28T01:58:32.878Z"
- },
- {
- "id": "bug-054",
- "timestamp": "2026-04-28T02:28:40.719Z",
- "error_message": "Incorrect value in code",
- "file": ".vscode/settings.json",
- "root_cause": "Had \"playwright.configFile\"",
- "fix": "Changed to \"playwright.workingDirectory\"",
- "tags": [
- "auto-detected",
- "wrong-value",
- "json"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-28T02:28:40.719Z"
- },
- {
- "id": "bug-055",
- "timestamp": "2026-04-28T02:35:14.419Z",
- "error_message": "Incorrect value in code",
- "file": ".vscode/settings.json",
- "root_cause": "Had \"playwright.workingDirectory\"",
- "fix": "Changed to \"playwright.configFile\"",
- "tags": [
- "auto-detected",
- "wrong-value",
- "json"
- ],
- "related_bugs": [],
- "occurrences": 1,
- "last_seen": "2026-04-28T02:35:14.419Z"
- }
- ]
-}
\ No newline at end of file
diff --git a/.wolf/cerebrum.md b/.wolf/cerebrum.md
deleted file mode 100644
index 895349b..0000000
--- a/.wolf/cerebrum.md
+++ /dev/null
@@ -1,23 +0,0 @@
-# Cerebrum
-
-> OpenWolf's learning memory. Updated automatically as the AI learns from interactions.
-> Do not edit manually unless correcting an error.
-> Last updated: 2026-04-21
-
-## User Preferences
-
-
-
-## Key Learnings
-
-- **Project:** angularnettutotial
-- **Description:** This repository demonstrates the **Client, API Resource, and Token Service (CAT)** pattern using Angular and .NET technologies.
-
-## Do-Not-Repeat
-
-
-
-
-## Decision Log
-
-
diff --git a/.wolf/config.json b/.wolf/config.json
deleted file mode 100644
index d2c76ae..0000000
--- a/.wolf/config.json
+++ /dev/null
@@ -1,73 +0,0 @@
-{
- "version": 1,
- "openwolf": {
- "enabled": true,
- "anatomy": {
- "auto_scan_on_init": true,
- "rescan_interval_hours": 6,
- "max_description_length": 100,
- "max_files": 500,
- "exclude_patterns": [
- "node_modules",
- ".git",
- "dist",
- "build",
- ".wolf",
- ".next",
- ".nuxt",
- "coverage",
- "__pycache__",
- ".cache",
- "target",
- ".vscode",
- ".idea",
- ".turbo",
- ".vercel",
- ".netlify",
- ".output",
- "*.min.js",
- "*.min.css"
- ]
- },
- "token_audit": {
- "enabled": true,
- "report_frequency": "weekly",
- "waste_threshold_percent": 15,
- "chars_per_token_code": 3.5,
- "chars_per_token_prose": 4.0
- },
- "cron": {
- "enabled": true,
- "max_retry_attempts": 3,
- "dead_letter_enabled": true,
- "heartbeat_interval_minutes": 30,
- "use_claude_p": true,
- "api_key_env": null
- },
- "memory": {
- "consolidation_after_days": 7,
- "max_entries_before_consolidation": 200
- },
- "cerebrum": {
- "max_tokens": 2000,
- "reflection_frequency": "weekly"
- },
- "daemon": {
- "port": 18790,
- "log_level": "info"
- },
- "dashboard": {
- "enabled": true,
- "port": 18791
- },
- "designqc": {
- "enabled": true,
- "viewports": [
- { "name": "desktop", "width": 1440, "height": 900 },
- { "name": "mobile", "width": 375, "height": 812 }
- ],
- "max_screenshots": 6,
- "chrome_path": null
- }
- }
-}
diff --git a/.wolf/cron-manifest.json b/.wolf/cron-manifest.json
deleted file mode 100644
index 7e2d7f3..0000000
--- a/.wolf/cron-manifest.json
+++ /dev/null
@@ -1,97 +0,0 @@
-{
- "version": 1,
- "tasks": [
- {
- "id": "anatomy-rescan",
- "name": "Full anatomy rescan",
- "schedule": "0 */6 * * *",
- "description": "Re-scans project filesystem and reconciles anatomy.md",
- "action": { "type": "scan_project" },
- "retry": {
- "max_attempts": 3,
- "backoff": "exponential",
- "base_delay_seconds": 30
- },
- "failsafe": {
- "on_failure": "log_and_continue",
- "alert_after_consecutive_failures": 2,
- "dead_letter": true
- },
- "enabled": true
- },
- {
- "id": "memory-consolidation",
- "name": "Consolidate old memory",
- "schedule": "0 2 * * *",
- "description": "Compress memory.md entries older than 7 days",
- "action": {
- "type": "consolidate_memory",
- "params": { "older_than_days": 7 }
- },
- "retry": {
- "max_attempts": 2,
- "backoff": "exponential",
- "base_delay_seconds": 60
- },
- "failsafe": {
- "on_failure": "skip_and_retry_next_cycle",
- "dead_letter": false
- },
- "enabled": true
- },
- {
- "id": "token-audit",
- "name": "Token audit report",
- "schedule": "0 0 * * 1",
- "description": "Weekly waste pattern detection",
- "action": { "type": "generate_token_report" },
- "retry": {
- "max_attempts": 2,
- "backoff": "linear",
- "base_delay_seconds": 60
- },
- "failsafe": { "on_failure": "log_and_continue", "dead_letter": true },
- "enabled": true
- },
- {
- "id": "cerebrum-reflection",
- "name": "Cerebrum reflection",
- "schedule": "0 3 * * 0",
- "description": "Weekly AI review of cerebrum.md — prune stale entries, consolidate duplicates",
- "action": {
- "type": "ai_task",
- "params": {
- "prompt": "Review this cerebrum.md. Remove duplicate preferences (keep newer). Remove Do-Not-Repeat entries older than 90 days if no longer relevant. Consolidate Key Learnings that overlap. Keep the file under 2000 tokens. Return the cleaned file content only.",
- "context_files": [".wolf/cerebrum.md"]
- }
- },
- "retry": {
- "max_attempts": 1,
- "backoff": "none",
- "base_delay_seconds": 0
- },
- "failsafe": { "on_failure": "log_and_continue", "dead_letter": false },
- "enabled": true
- },
- {
- "id": "project-suggestions",
- "name": "AI suggestions",
- "schedule": "0 4 * * 1",
- "description": "Weekly AI analysis with project improvement suggestions",
- "action": {
- "type": "ai_task",
- "params": {
- "prompt": "Based on the recent memory entries and current project structure, provide: 1) Key achievements this week, 2) Code improvements to consider, 3) Logical next tasks, 4) Technical debt or risks. Be specific and actionable. Return as JSON: {achievements:[], improvements:[], next_tasks:[], risks:[]}",
- "context_files": [".wolf/memory.md", ".wolf/anatomy.md"]
- }
- },
- "retry": {
- "max_attempts": 1,
- "backoff": "none",
- "base_delay_seconds": 0
- },
- "failsafe": { "on_failure": "log_and_continue", "dead_letter": false },
- "enabled": true
- }
- ]
-}
diff --git a/.wolf/cron-state.json b/.wolf/cron-state.json
deleted file mode 100644
index ce4dcb7..0000000
--- a/.wolf/cron-state.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "last_heartbeat": null,
- "engine_status": "initialized",
- "execution_log": [],
- "dead_letter_queue": [],
- "upcoming": []
-}
diff --git a/.wolf/designqc-report.json b/.wolf/designqc-report.json
deleted file mode 100644
index 8f658ee..0000000
--- a/.wolf/designqc-report.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "captured_at": null,
- "captures": [],
- "total_size_kb": 0,
- "estimated_tokens": 0
-}
\ No newline at end of file
diff --git a/.wolf/hooks/_session.json b/.wolf/hooks/_session.json
deleted file mode 100644
index fdffbbb..0000000
--- a/.wolf/hooks/_session.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "session_id": "session-2026-05-04-1507",
- "started": "2026-05-04T19:07:01.457Z",
- "files_read": {},
- "files_written": [],
- "edit_counts": {},
- "anatomy_hits": 0,
- "anatomy_misses": 0,
- "repeated_reads_warned": 0,
- "cerebrum_warnings": 0,
- "stop_count": 0
-}
\ No newline at end of file
diff --git a/.wolf/hooks/package.json b/.wolf/hooks/package.json
deleted file mode 100644
index 3dbc1ca..0000000
--- a/.wolf/hooks/package.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "type": "module"
-}
diff --git a/.wolf/hooks/post-read.js b/.wolf/hooks/post-read.js
deleted file mode 100644
index 5d22fbb..0000000
--- a/.wolf/hooks/post-read.js
+++ /dev/null
@@ -1,69 +0,0 @@
-import * as path from "node:path";
-import { getWolfDir, ensureWolfDir, readJSON, writeJSON, readMarkdown, parseAnatomy, estimateTokens, readStdin, normalizePath } from "./shared.js";
-async function main() {
- ensureWolfDir();
- const wolfDir = getWolfDir();
- const hooksDir = path.join(wolfDir, "hooks");
- const sessionFile = path.join(hooksDir, "_session.json");
- const raw = await readStdin();
- let input;
- try {
- input = JSON.parse(raw);
- }
- catch {
- process.exit(0);
- return;
- }
- const filePath = input.tool_input?.file_path ?? input.tool_input?.path ?? "";
- const content = input.tool_output?.content ?? "";
- if (!filePath) {
- process.exit(0);
- return;
- }
- const normalizedFile = normalizePath(filePath);
- // Skip tracking for .wolf/ internal files — consistent with pre-read
- const projectDir = normalizePath(process.env.CLAUDE_PROJECT_DIR || process.cwd());
- const relToProject = normalizedFile.startsWith(projectDir)
- ? normalizedFile.slice(projectDir.length).replace(/^\//, "")
- : "";
- if (relToProject.startsWith(".wolf/") || relToProject.startsWith(".wolf\\")) {
- process.exit(0);
- return;
- }
- const ext = path.extname(filePath).toLowerCase();
- const codeExts = new Set([".ts", ".js", ".tsx", ".jsx", ".py", ".rs", ".go", ".java", ".c", ".cpp", ".css", ".json", ".yaml", ".yml"]);
- const proseExts = new Set([".md", ".txt", ".rst"]);
- const type = codeExts.has(ext) ? "code" : proseExts.has(ext) ? "prose" : "mixed";
- let tokens = content ? estimateTokens(content, type) : 0;
- // Fallback: if tool_output had no content, use anatomy token estimate
- if (tokens === 0) {
- const anatomyContent = readMarkdown(path.join(wolfDir, "anatomy.md"));
- const sections = parseAnatomy(anatomyContent);
- for (const [sectionKey, entries] of sections) {
- for (const entry of entries) {
- const entryRelPath = normalizePath(path.join(sectionKey, entry.file));
- if (normalizedFile.endsWith(entryRelPath) || normalizedFile.endsWith("/" + entryRelPath)) {
- tokens = entry.tokens;
- break;
- }
- }
- if (tokens > 0)
- break;
- }
- }
- const session = readJSON(sessionFile, { files_read: {} });
- if (session.files_read[normalizedFile]) {
- session.files_read[normalizedFile].tokens = tokens;
- }
- else {
- session.files_read[normalizedFile] = {
- count: 1,
- tokens,
- first_read: new Date().toISOString(),
- };
- }
- writeJSON(sessionFile, session);
- process.exit(0);
-}
-main().catch(() => process.exit(0));
-//# sourceMappingURL=post-read.js.map
\ No newline at end of file
diff --git a/.wolf/hooks/post-write.js b/.wolf/hooks/post-write.js
deleted file mode 100644
index 9a5ce97..0000000
--- a/.wolf/hooks/post-write.js
+++ /dev/null
@@ -1,503 +0,0 @@
-import * as fs from "node:fs";
-import * as path from "node:path";
-import * as crypto from "node:crypto";
-import { getWolfDir, ensureWolfDir, readJSON, writeJSON, parseAnatomy, serializeAnatomy, extractDescription, estimateTokens, appendMarkdown, timeShort, readStdin, normalizePath } from "./shared.js";
-async function main() {
- ensureWolfDir();
- const wolfDir = getWolfDir();
- const hooksDir = path.join(wolfDir, "hooks");
- const sessionFile = path.join(hooksDir, "_session.json");
- const projectRoot = process.env.CLAUDE_PROJECT_DIR || process.cwd();
- const raw = await readStdin();
- let input;
- try {
- input = JSON.parse(raw);
- }
- catch {
- process.exit(0);
- return;
- }
- const toolName = input.tool_name ?? "Write";
- const filePath = input.tool_input?.file_path ?? input.tool_input?.path ?? "";
- if (!filePath) {
- process.exit(0);
- return;
- }
- const absolutePath = path.isAbsolute(filePath) ? filePath : path.join(projectRoot, filePath);
- // Skip processing for .wolf/ internal files to avoid slow self-referential updates
- const relPath = normalizePath(path.relative(projectRoot, absolutePath));
- if (relPath.startsWith(".wolf/")) {
- process.exit(0);
- return;
- }
- // Never track .env files in anatomy — they contain secrets
- const baseName = path.basename(absolutePath);
- if (baseName === ".env" || baseName.startsWith(".env.")) {
- process.exit(0);
- return;
- }
- const oldStr = input.tool_input?.old_string ?? "";
- const newStr = input.tool_input?.new_string ?? "";
- // 1. Update anatomy.md
- try {
- const anatomyPath = path.join(wolfDir, "anatomy.md");
- let anatomyContent;
- try {
- anatomyContent = fs.readFileSync(anatomyPath, "utf-8");
- }
- catch {
- anatomyContent = "# anatomy.md\n\n> Auto-maintained by OpenWolf.\n";
- }
- const sections = parseAnatomy(anatomyContent);
- const relPathLocal = normalizePath(path.relative(projectRoot, absolutePath));
- const dir = path.dirname(relPathLocal);
- const fileName = path.basename(relPathLocal);
- const sectionKey = dir === "." ? "./" : dir + "/";
- let fileContent = "";
- try {
- fileContent = fs.readFileSync(absolutePath, "utf-8");
- }
- catch {
- fileContent = input.tool_input?.content ?? "";
- }
- const desc = extractDescription(absolutePath).slice(0, 100);
- const ext = path.extname(absolutePath).toLowerCase();
- const codeExts = new Set([".ts", ".js", ".tsx", ".jsx", ".py", ".json", ".yaml", ".yml", ".css"]);
- const proseExts = new Set([".md", ".txt", ".rst"]);
- const type = codeExts.has(ext) ? "code" : proseExts.has(ext) ? "prose" : "mixed";
- const tokens = estimateTokens(fileContent, type);
- if (!sections.has(sectionKey))
- sections.set(sectionKey, []);
- const entries = sections.get(sectionKey);
- const idx = entries.findIndex((e) => e.file === fileName);
- if (idx !== -1) {
- entries[idx] = { file: fileName, description: desc, tokens };
- }
- else {
- entries.push({ file: fileName, description: desc, tokens });
- }
- let fileCount = 0;
- for (const [, list] of sections)
- fileCount += list.length;
- const serialized = serializeAnatomy(sections, {
- lastScanned: new Date().toISOString(),
- fileCount,
- hits: 0,
- misses: 0,
- });
- const tmp = anatomyPath + "." + crypto.randomBytes(4).toString("hex") + ".tmp";
- try {
- fs.writeFileSync(tmp, serialized, "utf-8");
- fs.renameSync(tmp, anatomyPath);
- }
- catch {
- try {
- fs.writeFileSync(anatomyPath, serialized, "utf-8");
- }
- catch { }
- try {
- fs.unlinkSync(tmp);
- }
- catch { }
- }
- }
- catch { }
- // 2. Append richer entry to memory.md
- try {
- const action = toolName === "Write" ? "Created" : toolName === "MultiEdit" ? "Multi-edited" : "Edited";
- const relFile = normalizePath(path.relative(projectRoot, absolutePath));
- const fileContent = input.tool_input?.content ?? "";
- const ext = path.extname(absolutePath).toLowerCase();
- const codeExts = new Set([".ts", ".js", ".tsx", ".jsx", ".py", ".json", ".yaml", ".yml", ".css"]);
- const type = codeExts.has(ext) ? "code" : "mixed";
- const writeTokens = estimateTokens(fileContent || newStr, type);
- let changeDesc = "";
- if (oldStr && newStr) {
- changeDesc = summarizeEdit(oldStr, newStr, baseName);
- }
- const memoryPath = path.join(wolfDir, "memory.md");
- const outcome = changeDesc || "—";
- appendMarkdown(memoryPath, `| ${timeShort()} | ${action} ${relFile} | ${outcome} | ~${writeTokens} |\n`);
- }
- catch { }
- // 3. Record in session tracker + track edit counts
- try {
- const session = readJSON(sessionFile, { files_written: [], edit_counts: {} });
- if (!session.edit_counts)
- session.edit_counts = {};
- const normalizedFile = normalizePath(filePath);
- const action = toolName === "Write" ? "create" : "edit";
- const fileContent = input.tool_input?.content ?? "";
- const tokens = estimateTokens(fileContent || newStr, "code");
- session.files_written.push({
- file: normalizedFile,
- action,
- tokens,
- at: new Date().toISOString(),
- });
- const editKey = normalizePath(path.relative(projectRoot, absolutePath));
- session.edit_counts[editKey] = (session.edit_counts[editKey] || 0) + 1;
- writeJSON(sessionFile, session);
- if (session.edit_counts[editKey] >= 3) {
- process.stderr.write(`⚠️ OpenWolf: ${baseName} has been edited ${session.edit_counts[editKey]} times this session. If you're fixing a bug, remember to log it to .wolf/buglog.json.\n`);
- }
- }
- catch { }
- // 4. Auto-detect bug-fix patterns and log them
- try {
- if (oldStr && newStr) {
- autoDetectBugFix(wolfDir, absolutePath, projectRoot, oldStr, newStr);
- }
- }
- catch { }
- process.exit(0);
-}
-// ─── Edit Summarizer ─────────────────────────────────────────────
-function summarizeEdit(oldStr, newStr, filename) {
- const oldLines = oldStr.split("\n");
- const newLines = newStr.split("\n");
- const oldCount = oldLines.length;
- const newCount = newLines.length;
- const ext = path.extname(filename).toLowerCase();
- // --- Structural fixes ---
- if (newStr.includes("try") && newStr.includes("catch") && !oldStr.includes("catch")) {
- return "added error handling";
- }
- if (newStr.includes("?.") && !oldStr.includes("?."))
- return "added optional chaining";
- if (newStr.includes("?? ") && !oldStr.includes("?? "))
- return "added nullish coalescing";
- // --- Deleted code ---
- if (!newStr.trim() || newStr.trim().length < oldStr.trim().length * 0.2) {
- return `removed ${oldCount} lines`;
- }
- // --- Import changes ---
- const oldImports = oldLines.filter(l => /^\s*(import|require|use |from )/.test(l)).length;
- const newImports = newLines.filter(l => /^\s*(import|require|use |from )/.test(l)).length;
- if (newImports > oldImports && Math.abs(newCount - oldCount) <= newImports - oldImports + 1) {
- return `added ${newImports - oldImports} import(s)`;
- }
- // --- Value/string replacement (common bug fix: wrong value) ---
- if (oldCount === 1 && newCount === 1) {
- const o = oldStr.trim();
- const n = newStr.trim();
- // String literal change
- const oStr = o.match(/['"`]([^'"`]+)['"`]/);
- const nStr = n.match(/['"`]([^'"`]+)['"`]/);
- if (oStr && nStr && oStr[1] !== nStr[1]) {
- return `"${oStr[1].slice(0, 25)}" → "${nStr[1].slice(0, 25)}"`;
- }
- // Number change
- const oNum = o.match(/\b(\d+\.?\d*)\b/);
- const nNum = n.match(/\b(\d+\.?\d*)\b/);
- if (oNum && nNum && oNum[1] !== nNum[1] && o.replace(oNum[1], "") === n.replace(nNum[1], "")) {
- return `${oNum[1]} → ${nNum[1]}`;
- }
- return "inline fix";
- }
- // --- Method/function call changes ---
- const oldCalls = extractCalls(oldStr);
- const newCalls = extractCalls(newStr);
- const addedCalls = newCalls.filter(c => !oldCalls.includes(c));
- const removedCalls = oldCalls.filter(c => !newCalls.includes(c));
- if (removedCalls.length === 1 && addedCalls.length === 1) {
- return `${removedCalls[0]}() → ${addedCalls[0]}()`;
- }
- // --- CSS/style changes ---
- if (ext === ".css" || ext === ".scss" || ext === ".vue" || ext === ".tsx" || ext === ".jsx") {
- const oldProps = (oldStr.match(/[\w-]+\s*:/g) || []).map(p => p.replace(/\s*:/, ""));
- const newProps = (newStr.match(/[\w-]+\s*:/g) || []).map(p => p.replace(/\s*:/, ""));
- const changed = newProps.filter(p => !oldProps.includes(p));
- if (changed.length > 0 && changed.length <= 3) {
- return `CSS: ${changed.join(", ")}`;
- }
- }
- // --- Condition changes ---
- const oldConds = (oldStr.match(/if\s*\(([^)]+)\)/g) || []);
- const newConds = (newStr.match(/if\s*\(([^)]+)\)/g) || []);
- if (newConds.length > oldConds.length) {
- return `added ${newConds.length - oldConds.length} condition(s)`;
- }
- // --- Function modified ---
- const fnMatch = newStr.match(/(?:function|def|fn|func|async\s+function)\s+(\w+)/);
- if (fnMatch) {
- return `modified ${fnMatch[1]}()`;
- }
- // --- Class/method context ---
- const methodMatch = newStr.match(/(?:public|private|protected)?\s*(?:async\s+)?(\w+)\s*\([^)]*\)\s*[:{]/);
- if (methodMatch) {
- return `modified ${methodMatch[1]}()`;
- }
- // --- Size-based fallback ---
- if (newCount > oldCount + 5)
- return `expanded (+${newCount - oldCount} lines)`;
- if (oldCount > newCount + 5)
- return `reduced (-${oldCount - newCount} lines)`;
- return `${oldCount}→${newCount} lines`;
-}
-function extractCalls(code) {
- return [...new Set((code.match(/(\w+)\s*\(/g) || [])
- .map(m => m.match(/(\w+)/)?.[1] || "")
- .filter(n => n.length > 2 && !["if", "for", "while", "switch", "catch", "function", "return", "new", "typeof", "instanceof", "const", "let", "var"].includes(n)))];
-}
-// ─── Auto Bug Detection ──────────────────────────────────────────
-function autoDetectBugFix(wolfDir, absolutePath, projectRoot, oldStr, newStr) {
- const bugLogPath = path.join(wolfDir, "buglog.json");
- const bugLog = readJSON(bugLogPath, { version: 1, bugs: [] });
- const relFile = normalizePath(path.relative(projectRoot, absolutePath));
- const basename = path.basename(absolutePath);
- const ext = path.extname(basename).toLowerCase();
- // Detect what kind of fix this is
- const detection = detectFixPattern(oldStr, newStr, ext);
- if (!detection)
- return;
- // Check for recent duplicate (same file + same category within 5 min)
- const recentDupe = bugLog.bugs.find(b => {
- if (path.basename(b.file) !== basename)
- return false;
- if (!b.tags.includes("auto-detected"))
- return false;
- if (!b.tags.includes(detection.category))
- return false;
- const bugTime = new Date(b.last_seen).getTime();
- return (Date.now() - bugTime) < 5 * 60 * 1000;
- });
- if (recentDupe) {
- recentDupe.occurrences++;
- recentDupe.last_seen = new Date().toISOString();
- // Append additional context
- if (detection.context && !recentDupe.fix.includes(detection.context)) {
- recentDupe.fix += ` | Also: ${detection.context}`;
- }
- writeJSON(bugLogPath, bugLog);
- return;
- }
- const nextId = `bug-${String(bugLog.bugs.length + 1).padStart(3, "0")}`;
- bugLog.bugs.push({
- id: nextId,
- timestamp: new Date().toISOString(),
- error_message: detection.summary,
- file: relFile,
- root_cause: detection.rootCause,
- fix: detection.fix,
- tags: ["auto-detected", detection.category, ext.replace(".", "") || "unknown"],
- related_bugs: [],
- occurrences: 1,
- last_seen: new Date().toISOString(),
- });
- writeJSON(bugLogPath, bugLog);
-}
-function detectFixPattern(oldStr, newStr, ext) {
- const oldLines = oldStr.split("\n");
- const newLines = newStr.split("\n");
- // --- Error handling added ---
- if (newStr.includes("catch") && !oldStr.includes("catch")) {
- const fn = newStr.match(/(?:function|def|async)\s+(\w+)/)?.[1] || "unknown";
- return {
- category: "error-handling",
- summary: `Missing error handling in ${path.basename(fn)}`,
- rootCause: "Code path had no error handling — exceptions would propagate uncaught",
- fix: `Added try/catch block`,
- context: extractChangedLines(oldStr, newStr),
- };
- }
- // --- Null/undefined safety ---
- if ((newStr.includes("?.") && !oldStr.includes("?.")) ||
- (newStr.includes("?? ") && !oldStr.includes("?? ")) ||
- (/!==?\s*(null|undefined)/.test(newStr) && !/!==?\s*(null|undefined)/.test(oldStr))) {
- return {
- category: "null-safety",
- summary: `Null/undefined access in ${path.basename(path.basename(""))}`,
- rootCause: "Property access on potentially null/undefined value",
- fix: `Added null safety (optional chaining or null check)`,
- context: extractChangedLines(oldStr, newStr),
- };
- }
- // --- Guard clause / early return added ---
- if (/if\s*\([^)]*\)\s*(return|throw|continue|break)/.test(newStr) &&
- !/if\s*\([^)]*\)\s*(return|throw|continue|break)/.test(oldStr)) {
- const condition = newStr.match(/if\s*\(([^)]+)\)/)?.[1]?.trim().slice(0, 60) || "condition";
- return {
- category: "guard-clause",
- summary: `Missing guard clause`,
- rootCause: `No early return/throw for edge case: ${condition}`,
- fix: `Added guard clause: if (${condition.slice(0, 40)})`,
- };
- }
- // --- Wrong value / string fix (very common bug) ---
- if (oldLines.length <= 3 && newLines.length <= 3) {
- const oldJoined = oldStr.trim();
- const newJoined = newStr.trim();
- // String literal changed
- const oStrs = oldJoined.match(/['"`]([^'"`]{2,})['"`]/g) || [];
- const nStrs = newJoined.match(/['"`]([^'"`]{2,})['"`]/g) || [];
- if (oStrs.length > 0 && nStrs.length > 0) {
- for (let i = 0; i < Math.min(oStrs.length, nStrs.length); i++) {
- if (oStrs[i] !== nStrs[i]) {
- return {
- category: "wrong-value",
- summary: `Incorrect value in code`,
- rootCause: `Had ${oStrs[i].slice(0, 50)}`,
- fix: `Changed to ${nStrs[i].slice(0, 50)}`,
- };
- }
- }
- }
- // Variable name / method call changed
- const oldTokens = tokenizeCode(oldJoined);
- const newTokens = tokenizeCode(newJoined);
- const changed = [];
- for (let i = 0; i < Math.min(oldTokens.length, newTokens.length); i++) {
- if (oldTokens[i] !== newTokens[i]) {
- changed.push([oldTokens[i], newTokens[i]]);
- }
- }
- if (changed.length === 1 && changed[0][0].length > 2) {
- return {
- category: "wrong-reference",
- summary: `Wrong reference: ${changed[0][0]} should be ${changed[0][1]}`,
- rootCause: `Used "${changed[0][0]}" instead of "${changed[0][1]}"`,
- fix: `Changed ${changed[0][0]} → ${changed[0][1]}`,
- };
- }
- }
- // --- Logic fix (condition changed) ---
- const oldCond = oldStr.match(/if\s*\(([^)]+)\)/)?.[1];
- const newCond = newStr.match(/if\s*\(([^)]+)\)/)?.[1];
- if (oldCond && newCond && oldCond !== newCond && oldLines.length <= 5) {
- return {
- category: "logic-fix",
- summary: `Wrong condition in logic`,
- rootCause: `Condition was: if (${oldCond.slice(0, 50)})`,
- fix: `Changed to: if (${newCond.slice(0, 50)})`,
- };
- }
- // --- Operator fix (=== vs ==, > vs >=, etc.) ---
- const opChange = findOperatorChange(oldStr, newStr);
- if (opChange) {
- return {
- category: "operator-fix",
- summary: `Wrong operator: ${opChange.old} should be ${opChange.new}`,
- rootCause: `Used "${opChange.old}" instead of "${opChange.new}"`,
- fix: `Changed operator ${opChange.old} → ${opChange.new}`,
- };
- }
- // --- Missing import/require ---
- const oldImports = new Set((oldStr.match(/(?:import|require)\s*\(?['"]([^'"]+)['"]\)?/g) || []).map(m => m));
- const newImports = (newStr.match(/(?:import|require)\s*\(?['"]([^'"]+)['"]\)?/g) || []);
- const addedImports = newImports.filter(i => !oldImports.has(i));
- if (addedImports.length > 0 && newLines.length - oldLines.length <= addedImports.length + 2) {
- const modules = addedImports.map(i => i.match(/['"]([^'"]+)['"]/)?.[1] || "").filter(Boolean);
- return {
- category: "missing-import",
- summary: `Missing import: ${modules.join(", ")}`,
- rootCause: `Module(s) not imported: ${modules.join(", ")}`,
- fix: `Added import(s) for ${modules.join(", ")}`,
- };
- }
- // --- Return value fix ---
- const oldReturn = oldStr.match(/return\s+(.+)/)?.[1]?.trim();
- const newReturn = newStr.match(/return\s+(.+)/)?.[1]?.trim();
- if (oldReturn && newReturn && oldReturn !== newReturn && oldLines.length <= 5) {
- return {
- category: "return-value",
- summary: `Wrong return value`,
- rootCause: `Was returning: ${oldReturn.slice(0, 50)}`,
- fix: `Now returns: ${newReturn.slice(0, 50)}`,
- };
- }
- // --- Async/await fix ---
- if (newStr.includes("await ") && !oldStr.includes("await ")) {
- return {
- category: "async-fix",
- summary: `Missing await`,
- rootCause: `Async call without await — returned Promise instead of value`,
- fix: `Added await to async call`,
- context: extractChangedLines(oldStr, newStr),
- };
- }
- if (newStr.includes("async ") && !oldStr.includes("async ")) {
- return {
- category: "async-fix",
- summary: `Function not marked async`,
- rootCause: `Function uses await but wasn't declared async`,
- fix: `Added async modifier`,
- };
- }
- // --- Type annotation/cast fix ---
- if (ext === ".ts" || ext === ".tsx") {
- if ((newStr.includes(" as ") && !oldStr.includes(" as ")) ||
- (newStr.includes(": ") && !oldStr.includes(": ") && oldLines.length <= 3)) {
- return {
- category: "type-fix",
- summary: `Type error`,
- rootCause: `Missing or incorrect type annotation`,
- fix: `Added type assertion/annotation`,
- context: extractChangedLines(oldStr, newStr),
- };
- }
- }
- // --- CSS/style fix ---
- if (ext === ".css" || ext === ".scss" || ext === ".vue" || ext === ".tsx" || ext === ".jsx") {
- const oldProps = extractCSSProps(oldStr);
- const newProps = extractCSSProps(newStr);
- const changedProps = [...newProps.entries()].filter(([k, v]) => oldProps.get(k) !== v && oldProps.has(k));
- if (changedProps.length > 0 && changedProps.length <= 3) {
- const desc = changedProps.map(([k, v]) => `${k}: ${oldProps.get(k)} → ${v}`).join("; ");
- return {
- category: "style-fix",
- summary: `CSS fix: ${changedProps.map(([k]) => k).join(", ")}`,
- rootCause: desc,
- fix: `Changed ${desc}`,
- };
- }
- }
- // --- Significant diff (catch-all for substantial edits) ---
- const diffRatio = Math.abs(newStr.length - oldStr.length) / Math.max(oldStr.length, 1);
- if (diffRatio > 0.3 && oldLines.length >= 3 && newLines.length >= 3) {
- // Only log if there's meaningful structural change, not just additions
- const removedLines = oldLines.filter(l => l.trim() && !newLines.some(nl => nl.trim() === l.trim()));
- if (removedLines.length >= 2) {
- return {
- category: "refactor",
- summary: `Significant refactor of ${path.basename("")}`,
- rootCause: `${removedLines.length} lines replaced/restructured`,
- fix: `Rewrote ${oldLines.length}→${newLines.length} lines (${removedLines.length} removed)`,
- context: removedLines.slice(0, 2).map(l => l.trim().slice(0, 50)).join("; "),
- };
- }
- }
- return null;
-}
-function extractChangedLines(oldStr, newStr) {
- const oldLines = new Set(oldStr.split("\n").map(l => l.trim()).filter(Boolean));
- const newLines = newStr.split("\n").map(l => l.trim()).filter(Boolean);
- const added = newLines.filter(l => !oldLines.has(l));
- return added.slice(0, 2).map(l => l.slice(0, 60)).join("; ");
-}
-function tokenizeCode(code) {
- return code.replace(/[^\w$]/g, " ").split(/\s+/).filter(t => t.length > 0);
-}
-function findOperatorChange(oldStr, newStr) {
- const operators = ["===", "!==", "==", "!=", ">=", "<=", ">>", "<<", "&&", "||", "??"];
- for (const op of operators) {
- if (oldStr.includes(op) && !newStr.includes(op)) {
- for (const op2 of operators) {
- if (op2 !== op && newStr.includes(op2) && !oldStr.includes(op2)) {
- return { old: op, new: op2 };
- }
- }
- }
- }
- return null;
-}
-function extractCSSProps(code) {
- const props = new Map();
- const matches = code.matchAll(/([\w-]+)\s*:\s*([^;}\n]+)/g);
- for (const m of matches) {
- props.set(m[1].trim(), m[2].trim());
- }
- return props;
-}
-main().catch(() => process.exit(0));
-//# sourceMappingURL=post-write.js.map
\ No newline at end of file
diff --git a/.wolf/hooks/pre-read.js b/.wolf/hooks/pre-read.js
deleted file mode 100644
index 6ba67d0..0000000
--- a/.wolf/hooks/pre-read.js
+++ /dev/null
@@ -1,80 +0,0 @@
-import * as path from "node:path";
-import { getWolfDir, ensureWolfDir, readJSON, writeJSON, readMarkdown, parseAnatomy, readStdin, normalizePath } from "./shared.js";
-async function main() {
- ensureWolfDir();
- const wolfDir = getWolfDir();
- const hooksDir = path.join(wolfDir, "hooks");
- const sessionFile = path.join(hooksDir, "_session.json");
- const raw = await readStdin();
- let input;
- try {
- input = JSON.parse(raw);
- }
- catch {
- process.exit(0);
- return;
- }
- const filePath = input.tool_input?.file_path ?? input.tool_input?.path ?? "";
- if (!filePath) {
- process.exit(0);
- return;
- }
- const normalizedFile = normalizePath(filePath);
- // Skip tracking for .wolf/ internal files — they're infrastructure, not project files.
- // Counting them inflates anatomy miss rates since .wolf/ is excluded from anatomy scanning.
- const projectDir = normalizePath(process.env.CLAUDE_PROJECT_DIR || process.cwd());
- const relToProject = normalizedFile.startsWith(projectDir)
- ? normalizedFile.slice(projectDir.length).replace(/^\//, "")
- : "";
- if (relToProject.startsWith(".wolf/") || relToProject.startsWith(".wolf\\")) {
- process.exit(0);
- return;
- }
- const session = readJSON(sessionFile, {
- session_id: "", files_read: {}, anatomy_hits: 0, anatomy_misses: 0,
- repeated_reads_warned: 0,
- });
- // Check if already read this session
- if (session.files_read[normalizedFile]) {
- const prev = session.files_read[normalizedFile];
- process.stderr.write(`⚡ OpenWolf: ${path.basename(normalizedFile)} was already read this session (~${prev.tokens} tokens). Consider using your existing knowledge of this file.\n`);
- session.files_read[normalizedFile].count++;
- session.repeated_reads_warned++;
- writeJSON(sessionFile, session);
- process.exit(0);
- return;
- }
- // Check anatomy.md for this file
- const anatomyContent = readMarkdown(path.join(wolfDir, "anatomy.md"));
- const sections = parseAnatomy(anatomyContent);
- let found = false;
- for (const [sectionKey, entries] of sections) {
- for (const entry of entries) {
- // Build the full relative path from the section key + filename for accurate matching
- const entryRelPath = normalizePath(path.join(sectionKey, entry.file));
- if (normalizedFile.endsWith(entryRelPath) || normalizedFile.endsWith("/" + entryRelPath)) {
- process.stderr.write(`📋 OpenWolf anatomy: ${entry.file} — ${entry.description} (~${entry.tokens} tok)\n`);
- found = true;
- break;
- }
- }
- if (found)
- break;
- }
- if (found) {
- session.anatomy_hits++;
- }
- else {
- session.anatomy_misses++;
- }
- // Record initial read entry (tokens will be updated in post-read)
- session.files_read[normalizedFile] = {
- count: 1,
- tokens: 0,
- first_read: new Date().toISOString(),
- };
- writeJSON(sessionFile, session);
- process.exit(0);
-}
-main().catch(() => process.exit(0));
-//# sourceMappingURL=pre-read.js.map
\ No newline at end of file
diff --git a/.wolf/hooks/pre-write.js b/.wolf/hooks/pre-write.js
deleted file mode 100644
index 2e84303..0000000
--- a/.wolf/hooks/pre-write.js
+++ /dev/null
@@ -1,121 +0,0 @@
-import * as fs from "node:fs";
-import * as path from "node:path";
-import { getWolfDir, ensureWolfDir, readJSON, readMarkdown, readStdin } from "./shared.js";
-async function main() {
- ensureWolfDir();
- const wolfDir = getWolfDir();
- const raw = await readStdin();
- let input;
- try {
- input = JSON.parse(raw);
- }
- catch {
- process.exit(0);
- return;
- }
- // For Edit tool, the meaningful content is old_string + new_string
- const content = input.tool_input?.content ?? "";
- const oldStr = input.tool_input?.old_string ?? "";
- const newStr = input.tool_input?.new_string ?? "";
- const filePath = input.tool_input?.file_path ?? input.tool_input?.path ?? "";
- const allContent = [content, oldStr, newStr].join("\n");
- if (!allContent.trim()) {
- process.exit(0);
- return;
- }
- // 1. Cerebrum Do-Not-Repeat check
- checkCerebrum(wolfDir, allContent);
- // 2. Bug log: search for similar past bugs when editing code
- // This fires when Claude is about to edit a file — if the edit looks like a fix
- // (changing error handling, modifying catch blocks, etc.), check the bug log
- if (filePath && (oldStr || content)) {
- checkBugLog(wolfDir, filePath, oldStr, newStr, content);
- }
- process.exit(0);
-}
-function checkCerebrum(wolfDir, content) {
- const cerebrumContent = readMarkdown(path.join(wolfDir, "cerebrum.md"));
- const doNotRepeatSection = cerebrumContent.split("## Do-Not-Repeat")[1];
- if (!doNotRepeatSection)
- return;
- const entries = doNotRepeatSection.split("## ")[0];
- const lines = entries.split("\n").filter((l) => l.trim().startsWith("[") || l.trim().startsWith("-"));
- for (const line of lines) {
- const trimmed = line.trim().replace(/^[-*]\s*/, "").replace(/^\[[\d-]+\]\s*/, "");
- if (!trimmed)
- continue;
- const patterns = [];
- const quotedMatches = trimmed.match(/"([^"]+)"/g) || trimmed.match(/'([^']+)'/g) || trimmed.match(/`([^`]+)`/g);
- if (quotedMatches) {
- for (const qm of quotedMatches) {
- patterns.push(qm.replace(/["'`]/g, ""));
- }
- }
- const neverMatch = trimmed.match(/(?:never use|avoid|don't use|do not use)\s+(\w+)/i);
- if (neverMatch)
- patterns.push(neverMatch[1]);
- for (const pattern of patterns) {
- try {
- const regex = new RegExp(`\\b${pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "i");
- if (regex.test(content)) {
- process.stderr.write(`⚠️ OpenWolf cerebrum warning: "${trimmed}" — check your code before proceeding.\n`);
- }
- }
- catch { }
- }
- }
-}
-// Common words that appear in most code — must be excluded from similarity matching
-const STOP_WORDS = new Set([
- "error", "function", "return", "const", "this", "that", "with", "from",
- "import", "export", "class", "interface", "type", "undefined", "null",
- "true", "false", "string", "number", "object", "array", "value",
- "file", "path", "name", "data", "response", "request", "result",
- "should", "must", "does", "have", "been", "will", "would", "could",
- "when", "then", "else", "each", "some", "every", "only",
-]);
-function checkBugLog(wolfDir, filePath, oldStr, newStr, content) {
- const bugLogPath = path.join(wolfDir, "buglog.json");
- if (!fs.existsSync(bugLogPath))
- return;
- const bugLog = readJSON(bugLogPath, { version: 1, bugs: [] });
- if (bugLog.bugs.length === 0)
- return;
- const basename = path.basename(filePath);
- // ONLY surface bugs that match the SAME file being edited.
- // Cross-file matching is too noisy and risks misdirecting Claude.
- const fileMatches = bugLog.bugs.filter(b => {
- const bugBasename = path.basename(b.file);
- return bugBasename === basename;
- });
- if (fileMatches.length === 0)
- return;
- // Further filter: require tag or error_message overlap with the edit content
- const editText = (oldStr + " " + newStr + " " + content).toLowerCase();
- const editTokens = tokenize(editText);
- const relevant = fileMatches.filter(bug => {
- // Check if any bug tag appears in the edit content
- const tagHit = bug.tags.some(t => editText.includes(t.toLowerCase()));
- if (tagHit)
- return true;
- // Check meaningful word overlap (excluding stop words)
- const bugTokens = tokenize(bug.error_message + " " + bug.root_cause);
- const overlap = [...editTokens].filter(t => bugTokens.has(t));
- // Require at least 3 meaningful overlapping words
- return overlap.length >= 3;
- });
- if (relevant.length === 0)
- return;
- // Surface as a FYI, not a directive — Claude should evaluate, not blindly apply
- process.stderr.write(`📋 OpenWolf buglog: ${relevant.length} past bug(s) found for ${basename} — review for context, do NOT apply blindly:\n`);
- for (const bug of relevant.slice(0, 2)) {
- process.stderr.write(` [${bug.id}] "${bug.error_message.slice(0, 70)}"\n Cause: ${bug.root_cause.slice(0, 80)}\n Fix: ${bug.fix.slice(0, 80)}\n`);
- }
-}
-function tokenize(text) {
- return new Set(text.replace(/[^\w\s]/g, " ").split(/\s+/)
- .filter(w => w.length > 3 && !STOP_WORDS.has(w.toLowerCase()))
- .map(w => w.toLowerCase()));
-}
-main().catch(() => process.exit(0));
-//# sourceMappingURL=pre-write.js.map
\ No newline at end of file
diff --git a/.wolf/hooks/session-start.js b/.wolf/hooks/session-start.js
deleted file mode 100644
index 7da38f3..0000000
--- a/.wolf/hooks/session-start.js
+++ /dev/null
@@ -1,77 +0,0 @@
-import * as fs from "node:fs";
-import * as path from "node:path";
-import { getWolfDir, ensureWolfDir, writeJSON, appendMarkdown, readJSON, timestamp, timeShort } from "./shared.js";
-async function main() {
- ensureWolfDir();
- const wolfDir = getWolfDir();
- // Clean up stale .tmp files left from failed atomic writes
- try {
- const files = fs.readdirSync(wolfDir);
- for (const f of files) {
- if (f.endsWith(".tmp")) {
- try {
- fs.unlinkSync(path.join(wolfDir, f));
- }
- catch { }
- }
- }
- }
- catch { }
- const hooksDir = path.join(wolfDir, "hooks");
- const sessionFile = path.join(hooksDir, "_session.json");
- const now = new Date();
- const sessionId = `session-${now.toISOString().slice(0, 10)}-${String(now.getHours()).padStart(2, "0")}${String(now.getMinutes()).padStart(2, "0")}`;
- // Create fresh session state
- writeJSON(sessionFile, {
- session_id: sessionId,
- started: timestamp(),
- files_read: {},
- files_written: [],
- edit_counts: {},
- anatomy_hits: 0,
- anatomy_misses: 0,
- repeated_reads_warned: 0,
- cerebrum_warnings: 0,
- stop_count: 0,
- });
- // Append session header to memory.md
- const memoryPath = path.join(wolfDir, "memory.md");
- const header = `\n## Session: ${now.toISOString().slice(0, 10)} ${timeShort()}\n\n| Time | Action | File(s) | Outcome | ~Tokens |\n|------|--------|---------|---------|--------|\n`;
- appendMarkdown(memoryPath, header);
- // Check cerebrum freshness — remind Claude to learn
- try {
- const cerebrumPath = path.join(wolfDir, "cerebrum.md");
- const cerebrumContent = fs.readFileSync(cerebrumPath, "utf-8");
- const stat = fs.statSync(cerebrumPath);
- const daysSinceUpdate = (Date.now() - stat.mtimeMs) / (1000 * 60 * 60 * 24);
- // Count actual entries (non-comment, non-empty lines in content sections)
- const entryLines = cerebrumContent.split("\n").filter(l => {
- const t = l.trim();
- return t.startsWith("- ") || t.startsWith("* ") || (t.startsWith("[") && t.includes("]"));
- });
- if (entryLines.length < 3) {
- process.stderr.write(`💡 OpenWolf: cerebrum.md has only ${entryLines.length} entries. Learn from this session — record user preferences, project conventions, and mistakes to .wolf/cerebrum.md.\n`);
- }
- else if (daysSinceUpdate > 3) {
- process.stderr.write(`💡 OpenWolf: cerebrum.md hasn't been updated in ${Math.floor(daysSinceUpdate)} days. Look for opportunities to add learnings this session.\n`);
- }
- }
- catch { }
- // Check buglog — remind if empty
- try {
- const buglogPath = path.join(wolfDir, "buglog.json");
- const buglog = readJSON(buglogPath, { bugs: [] });
- if (buglog.bugs.length === 0) {
- process.stderr.write(`📋 OpenWolf: buglog.json is empty. If you encounter or fix any bugs, errors, or failed tests this session, log them to .wolf/buglog.json.\n`);
- }
- }
- catch { }
- // Increment total_sessions in token-ledger
- const ledgerPath = path.join(wolfDir, "token-ledger.json");
- const ledger = readJSON(ledgerPath, { version: 1, lifetime: { total_sessions: 0 } });
- ledger.lifetime.total_sessions++;
- writeJSON(ledgerPath, ledger);
- process.exit(0);
-}
-main().catch(() => process.exit(0));
-//# sourceMappingURL=session-start.js.map
\ No newline at end of file
diff --git a/.wolf/hooks/shared.js b/.wolf/hooks/shared.js
deleted file mode 100644
index e402d75..0000000
--- a/.wolf/hooks/shared.js
+++ /dev/null
@@ -1,614 +0,0 @@
-import * as fs from "node:fs";
-import * as path from "node:path";
-import * as crypto from "node:crypto";
-export function getWolfDir() {
- // Prefer CLAUDE_PROJECT_DIR so hooks work even if CWD changes during a session
- const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
- return path.join(projectDir, ".wolf");
-}
-/**
- * Bail out silently if .wolf/ directory doesn't exist in the current project.
- * Call this at the top of every hook to avoid crashes in non-OpenWolf projects.
- */
-export function ensureWolfDir() {
- const wolfDir = getWolfDir();
- if (!fs.existsSync(wolfDir)) {
- process.exit(0);
- }
-}
-export function readJSON(filePath, fallback) {
- try {
- return JSON.parse(fs.readFileSync(filePath, "utf-8"));
- }
- catch {
- return fallback;
- }
-}
-export function writeJSON(filePath, data) {
- const dir = path.dirname(filePath);
- if (!fs.existsSync(dir))
- fs.mkdirSync(dir, { recursive: true });
- const tmp = filePath + "." + crypto.randomBytes(4).toString("hex") + ".tmp";
- try {
- fs.writeFileSync(tmp, JSON.stringify(data, null, 2), "utf-8");
- fs.renameSync(tmp, filePath);
- }
- catch {
- // On Windows, rename can fail if another process holds a handle.
- // Fall back to direct write and clean up the tmp file.
- try {
- fs.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8");
- }
- catch { }
- try {
- fs.unlinkSync(tmp);
- }
- catch { }
- }
-}
-export function readMarkdown(filePath) {
- try {
- return fs.readFileSync(filePath, "utf-8");
- }
- catch {
- return "";
- }
-}
-export function appendMarkdown(filePath, line) {
- const dir = path.dirname(filePath);
- if (!fs.existsSync(dir))
- fs.mkdirSync(dir, { recursive: true });
- fs.appendFileSync(filePath, line, "utf-8");
-}
-export function parseAnatomy(content) {
- const sections = new Map();
- let currentSection = "";
- for (const line of content.split("\n")) {
- const sm = line.match(/^## (.+)/);
- if (sm) {
- currentSection = sm[1].trim();
- if (!sections.has(currentSection))
- sections.set(currentSection, []);
- continue;
- }
- if (!currentSection)
- continue;
- const em = line.match(/^- `([^`]+)`(?:\s+—\s+(.+?))?\s*\(~(\d+)\s+tok\)$/);
- if (em) {
- sections.get(currentSection).push({
- file: em[1],
- description: em[2] || "",
- tokens: parseInt(em[3], 10),
- });
- }
- }
- return sections;
-}
-export function serializeAnatomy(sections, metadata) {
- const lines = [
- "# anatomy.md",
- "",
- `> Auto-maintained by OpenWolf. Last scanned: ${metadata.lastScanned}`,
- `> Files: ${metadata.fileCount} tracked | Anatomy hits: ${metadata.hits} | Misses: ${metadata.misses}`,
- "",
- ];
- const keys = [...sections.keys()].sort();
- for (const key of keys) {
- lines.push(`## ${key}`);
- lines.push("");
- const entries = sections.get(key).sort((a, b) => a.file.localeCompare(b.file));
- for (const e of entries) {
- const desc = e.description ? ` — ${e.description}` : "";
- lines.push(`- \`${e.file}\`${desc} (~${e.tokens} tok)`);
- }
- lines.push("");
- }
- return lines.join("\n");
-}
-export function extractDescription(filePath) {
- const MAX_DESC = 150;
- const basename = path.basename(filePath);
- const ext = path.extname(basename).toLowerCase();
- const known = {
- "package.json": "Node.js package manifest",
- "tsconfig.json": "TypeScript configuration",
- ".gitignore": "Git ignore rules",
- "README.md": "Project documentation",
- "composer.json": "PHP package manifest",
- "requirements.txt": "Python dependencies",
- "schema.sql": "Database schema",
- "Dockerfile": "Docker container definition",
- "docker-compose.yml": "Docker Compose services",
- "Cargo.toml": "Rust package manifest",
- "go.mod": "Go module definition",
- "Gemfile": "Ruby dependencies",
- "pubspec.yaml": "Dart/Flutter package manifest",
- };
- if (known[basename])
- return known[basename];
- let content;
- try {
- const fd = fs.openSync(filePath, "r");
- const buf = Buffer.alloc(12288); // 12KB
- const n = fs.readSync(fd, buf, 0, 12288, 0);
- fs.closeSync(fd);
- content = buf.subarray(0, n).toString("utf-8");
- }
- catch {
- return "";
- }
- if (!content.trim())
- return "";
- const cap = (s) => s.length <= MAX_DESC ? s : s.slice(0, MAX_DESC - 3) + "...";
- // Markdown heading
- if (ext === ".md" || ext === ".mdx") {
- const m = content.match(/^#{1,2}\s+(.+)$/m);
- if (m)
- return cap(m[1].trim());
- }
- // HTML title
- if (ext === ".html" || ext === ".htm") {
- const m = content.match(/]*>([^<]+)<\/title>/i);
- if (m)
- return cap(m[1].trim());
- }
- // JSDoc / PHPDoc / Javadoc — first meaningful line
- const jm = content.match(/\/\*\*\s*\n?\s*\*?\s*(.+)/);
- if (jm) {
- const l = jm[1].replace(/\*\/$/, "").trim();
- if (l && !l.startsWith("@") && l.length > 5)
- return cap(l);
- }
- // Python docstring
- if (ext === ".py") {
- const dm = content.match(/^(?:#[^\n]*\n)*\s*(?:"""(.+?)"""|'''(.+?)''')/s);
- if (dm) {
- const first = (dm[1] || dm[2]).split("\n")[0].trim();
- if (first && first.length > 3)
- return cap(first);
- }
- }
- // Rust doc comments
- if (ext === ".rs") {
- const lines = content.split("\n");
- for (const line of lines.slice(0, 20)) {
- const m = line.match(/^\s*(?:\/\/\/|\/\/!)\s*(.+)/);
- if (m && m[1].length > 5)
- return cap(m[1].trim());
- }
- }
- // Go package comment
- if (ext === ".go") {
- const m = content.match(/\/\/\s*Package\s+\w+\s+(.*)/);
- if (m)
- return cap(m[1].trim());
- }
- // C# XML doc
- if (ext === ".cs") {
- const m = content.match(/\s*([\s\S]*?)\s*<\/summary>/);
- if (m) {
- const text = m[1].replace(/\/\/\/\s*/g, "").replace(/\s+/g, " ").trim();
- if (text.length > 5)
- return cap(text);
- }
- }
- // Elixir @moduledoc
- if (ext === ".ex" || ext === ".exs") {
- const m = content.match(/@moduledoc\s+"""\s*\n\s*(.*)/);
- if (m)
- return cap(m[1].trim());
- }
- // Header comment (skip generic ones)
- const hdrLines = content.split("\n");
- for (const line of hdrLines.slice(0, 15)) {
- const t = line.trim();
- if (!t || t === " 5 && !lower.startsWith("copyright") && !lower.startsWith("license") && !lower.startsWith("@") && !lower.startsWith("strict") && !lower.startsWith("generated") && !lower.startsWith("eslint-") && !lower.startsWith("nolint")) {
- return cap(text);
- }
- }
- if (!t.startsWith("//") && !t.startsWith("#") && !t.startsWith("/*") && !t.startsWith("*") && !t.startsWith("--"))
- break;
- }
- // ─── PHP / Laravel ───────────────────────────────────────
- if (ext === ".php") {
- if (basename.endsWith(".blade.php")) {
- const ext2 = content.match(/@extends\(\s*['"]([^'"]+)['"]\s*\)/);
- const sections = (content.match(/@section\(\s*['"](\w+)['"]/g) || []).map(s => s.match(/['"](\w+)['"]/)?.[1]).filter(Boolean);
- const parts = [];
- if (ext2)
- parts.push(`extends ${ext2[1]}`);
- if (sections.length)
- parts.push(`sections: ${sections.join(", ")}`);
- return cap(parts.length ? `Blade: ${parts.join(", ")}` : "Blade template");
- }
- const classM = content.match(/class\s+(\w+)(?:\s+extends\s+(\w+))?/);
- const className = classM?.[1] || "";
- const parent = classM?.[2] || "";
- const pubMethods = (content.match(/public\s+function\s+(\w+)/g) || [])
- .map(m => m.match(/public\s+function\s+(\w+)/)?.[1])
- .filter(n => n && n !== "__construct" && n !== "middleware");
- if (basename.endsWith("Controller.php") || parent === "Controller") {
- if (pubMethods.length > 0) {
- const display = pubMethods.slice(0, 5).join(", ");
- return cap(pubMethods.length > 5 ? `${display} + ${pubMethods.length - 5} more` : display);
- }
- }
- if (parent === "Model" || parent === "Authenticatable") {
- const parts = [];
- const tbl = content.match(/\$table\s*=\s*['"]([^'"]+)['"]/);
- if (tbl)
- parts.push(`table: ${tbl[1]}`);
- const fill = content.match(/\$fillable\s*=\s*\[([^\]]*)\]/s);
- if (fill) {
- const c = (fill[1].match(/['"]/g) || []).length / 2;
- parts.push(`${Math.floor(c)} fields`);
- }
- const rels = (content.match(/\$this->(hasMany|hasOne|belongsTo|belongsToMany|morphMany|morphTo)\(/g) || []).length;
- if (rels)
- parts.push(`${rels} rels`);
- return cap(parts.length ? `Model — ${parts.join(", ")}` : `Model: ${className}`);
- }
- if (basename.match(/^\d{4}_\d{2}_\d{2}/)) {
- const create = content.match(/Schema::create\(\s*['"]([^'"]+)['"]/);
- if (create)
- return `Migration: create ${create[1]} table`;
- const alter = content.match(/Schema::table\(\s*['"]([^'"]+)['"]/);
- if (alter)
- return `Migration: alter ${alter[1]} table`;
- return "Database migration";
- }
- if (className && pubMethods.length > 0) {
- const display = pubMethods.slice(0, 4).join(", ");
- return cap(pubMethods.length > 4 ? `${className}: ${display} + ${pubMethods.length - 4} more` : `${className}: ${display}`);
- }
- }
- // ─── TS/JS/React/Next.js ─────────────────────────────────
- if (ext === ".ts" || ext === ".tsx" || ext === ".js" || ext === ".jsx" || ext === ".mjs" || ext === ".cjs") {
- // React component
- if (ext === ".tsx" || ext === ".jsx") {
- const comp = content.match(/(?:export\s+(?:default\s+)?)?(?:function|const)\s+(\w+)/);
- const parts = [];
- if (comp)
- parts.push(comp[1]);
- const renders = [];
- if (/<(?:form|Form)/i.test(content))
- renders.push("form");
- if (/<(?:table|Table|DataTable)/i.test(content))
- renders.push("table");
- if (/<(?:dialog|Dialog|Modal|Drawer)/i.test(content))
- renders.push("modal");
- if (renders.length)
- parts.push(`renders ${renders.join(", ")}`);
- if (parts.length)
- return cap(parts.join(" — "));
- }
- // Next.js conventions
- if (basename === "page.tsx" || basename === "page.js")
- return "Next.js page component";
- if (basename === "layout.tsx" || basename === "layout.js")
- return "Next.js layout";
- if (basename === "route.ts" || basename === "route.js") {
- const methods = [...new Set((content.match(/export\s+(?:async\s+)?function\s+(GET|POST|PUT|PATCH|DELETE)/g) || [])
- .map(m => m.match(/(GET|POST|PUT|PATCH|DELETE)/)?.[1]))].filter(Boolean);
- return methods.length ? `Next.js API route: ${methods.join(", ")}` : "Next.js API route";
- }
- // Express/Fastify routes
- const routeHits = content.match(/\.(get|post|put|patch|delete)\s*\(\s*['"`]/g);
- if (routeHits && routeHits.length > 0) {
- const methods = [...new Set(routeHits.map(r => r.match(/\.(get|post|put|patch|delete)/)?.[1]?.toUpperCase()))];
- return cap(`API routes: ${methods.join(", ")} (${routeHits.length} endpoints)`);
- }
- // tRPC router
- if (content.includes("createTRPCRouter") || content.includes("publicProcedure")) {
- const procs = (content.match(/\.(query|mutation|subscription)\s*\(/g) || []).length;
- return procs ? `tRPC router: ${procs} procedures` : "tRPC router";
- }
- // Zod schemas
- if (content.includes("z.object") || content.includes("z.string")) {
- const schemas = (content.match(/(?:export\s+)?(?:const|let)\s+(\w+)\s*=\s*z\./g) || [])
- .map(s => s.match(/(?:const|let)\s+(\w+)/)?.[1]).filter(Boolean);
- if (schemas.length)
- return cap(`Zod schemas: ${schemas.slice(0, 4).join(", ")}${schemas.length > 4 ? ` + ${schemas.length - 4} more` : ""}`);
- }
- // Exports summary
- const exports = (content.match(/export\s+(?:async\s+)?(?:function|class|const|interface|type|enum)\s+(\w+)/g) || [])
- .map(e => e.match(/(\w+)$/)?.[1]).filter(Boolean);
- if (exports.length > 0 && exports.length <= 5)
- return `Exports ${exports.join(", ")}`;
- if (exports.length > 5)
- return cap(`Exports ${exports.slice(0, 4).join(", ")} + ${exports.length - 4} more`);
- }
- // ─── Python / Django / FastAPI / Flask ────────────────────
- if (ext === ".py") {
- // Django model
- if (content.includes("models.Model")) {
- const cls = content.match(/class\s+(\w+)\(.*models\.Model\)/);
- const fields = (content.match(/^\s+\w+\s*=\s*models\.\w+/gm) || []).length;
- return cap(`Model: ${cls?.[1] || "unknown"}, ${fields} fields`);
- }
- // FastAPI/Flask routes
- if (content.includes("@router.") || content.includes("@app.")) {
- const routes = (content.match(/@(?:router|app)\.(get|post|put|patch|delete)\s*\(/g) || []);
- return cap(routes.length ? `API: ${routes.length} endpoints` : "API router");
- }
- // Pydantic
- if (content.includes("BaseModel") && content.includes("Field(")) {
- const cls = content.match(/class\s+(\w+)\(.*BaseModel\)/);
- return cls ? `Pydantic: ${cls[1]}` : "Pydantic model";
- }
- // Celery
- if (content.includes("@shared_task") || content.includes("@app.task")) {
- const tasks = (content.match(/def\s+(\w+)/g) || []).map(m => m.match(/def\s+(\w+)/)?.[1]).filter(n => n && !n.startsWith("_"));
- return cap(tasks.length ? `Celery tasks: ${tasks.join(", ")}` : "Celery task");
- }
- // Generic
- const pyClass = content.match(/class\s+(\w+)/);
- const funcs = (content.match(/def\s+(\w+)/g) || []).map(f => f.match(/def\s+(\w+)/)?.[1]).filter(n => n && !n.startsWith("_"));
- if (pyClass && funcs.length > 0)
- return cap(funcs.length > 4 ? `${pyClass[1]}: ${funcs.slice(0, 4).join(", ")} + ${funcs.length - 4} more` : `${pyClass[1]}: ${funcs.join(", ")}`);
- if (funcs.length > 0)
- return cap(funcs.slice(0, 4).join(", "));
- }
- // ─── Go ──────────────────────────────────────────────────
- if (ext === ".go") {
- const handlers = (content.match(/func\s+(\w+)\s*\(\s*\w+\s+http\.ResponseWriter/g) || [])
- .map(m => m.match(/func\s+(\w+)/)?.[1]).filter(Boolean);
- if (handlers.length)
- return cap(`HTTP handlers: ${handlers.slice(0, 5).join(", ")}`);
- const iface = content.match(/type\s+(\w+)\s+interface\s*\{/);
- if (iface)
- return `Interface: ${iface[1]}`;
- const structM = content.match(/type\s+(\w+)\s+struct\s*\{/);
- if (structM)
- return `Struct: ${structM[1]}`;
- const funcs = (content.match(/^func\s+(\w+)/gm) || []).map(m => m.match(/func\s+(\w+)/)?.[1]).filter(n => n && n[0] === n[0].toUpperCase());
- if (funcs.length)
- return cap(funcs.slice(0, 5).join(", "));
- }
- // ─── Rust ────────────────────────────────────────────────
- if (ext === ".rs") {
- const structM = content.match(/pub\s+struct\s+(\w+)/);
- if (structM) {
- const methods = (content.match(/pub\s+(?:async\s+)?fn\s+(\w+)/g) || []).map(m => m.match(/fn\s+(\w+)/)?.[1]).filter(Boolean);
- return cap(methods.length ? `${structM[1]}: ${methods.slice(0, 4).join(", ")}` : `Struct: ${structM[1]}`);
- }
- const traitM = content.match(/pub\s+trait\s+(\w+)/);
- if (traitM)
- return `Trait: ${traitM[1]}`;
- const enumM = content.match(/pub\s+enum\s+(\w+)/);
- if (enumM)
- return `Enum: ${enumM[1]}`;
- const fns = (content.match(/pub\s+(?:async\s+)?fn\s+(\w+)/g) || []).map(m => m.match(/fn\s+(\w+)/)?.[1]).filter(Boolean);
- if (fns.length)
- return cap(fns.slice(0, 5).join(", "));
- }
- // ─── Java / Spring ───────────────────────────────────────
- if (ext === ".java") {
- const cls = content.match(/(?:public\s+)?class\s+(\w+)/);
- const className = cls?.[1] || basename.replace(".java", "");
- const annotations = (content.match(/@(RestController|Controller|Service|Repository|Component|Entity|Configuration)/g) || []).map(a => a.slice(1));
- const mappings = (content.match(/@(?:Get|Post|Put|Patch|Delete|Request)Mapping/g) || []).length;
- if (mappings)
- return cap(`${annotations[0] || "Spring"}: ${className} (${mappings} endpoints)`);
- if (annotations.length)
- return `${annotations[0]}: ${className}`;
- if (content.includes("@Entity"))
- return `Entity: ${className}`;
- const methods = (content.match(/public\s+(?:static\s+)?(?:\w+(?:<[\w,\s]+>)?)\s+(\w+)\s*\(/g) || [])
- .map(m => m.match(/(\w+)\s*\(/)?.[1]).filter(n => n && n !== className);
- if (methods.length)
- return cap(`${className}: ${methods.slice(0, 4).join(", ")}`);
- return className ? `Class: ${className}` : "";
- }
- // ─── Kotlin ──────────────────────────────────────────────
- if (ext === ".kt" || ext === ".kts") {
- const cls = content.match(/(?:data\s+)?class\s+(\w+)/);
- if (content.match(/data\s+class/))
- return `Data class: ${cls?.[1] || basename.replace(/\.kts?$/, "")}`;
- if (content.includes("routing {"))
- return "Ktor routing";
- const fns = (content.match(/fun\s+(\w+)/g) || []).map(m => m.match(/fun\s+(\w+)/)?.[1]).filter(Boolean);
- if (cls && fns.length)
- return cap(`${cls[1]}: ${fns.slice(0, 4).join(", ")}`);
- if (fns.length)
- return cap(fns.slice(0, 5).join(", "));
- }
- // ─── C# / .NET ───────────────────────────────────────────
- if (ext === ".cs") {
- const cls = content.match(/(?:public\s+)?(?:partial\s+)?class\s+(\w+)(?:\s*:\s*(\w+))?/);
- const className = cls?.[1] || basename.replace(".cs", "");
- const parent = cls?.[2] || "";
- if (parent === "Controller" || parent === "ControllerBase" || content.includes("[ApiController]")) {
- const actions = (content.match(/\[Http(Get|Post|Put|Patch|Delete)\]/g) || []).map(a => a.match(/Http(\w+)/)?.[1]).filter(Boolean);
- return cap(actions.length ? `API Controller: ${className} (${[...new Set(actions)].join(", ")})` : `Controller: ${className}`);
- }
- if (parent === "DbContext" || content.includes("DbSet<")) {
- const sets = (content.match(/DbSet<(\w+)>/g) || []).map(s => s.match(/<(\w+)>/)?.[1]).filter(Boolean);
- return cap(sets.length ? `DbContext: ${sets.join(", ")}` : `DbContext: ${className}`);
- }
- return className ? `Class: ${className}` : "";
- }
- // ─── Ruby / Rails ────────────────────────────────────────
- if (ext === ".rb") {
- const cls = content.match(/class\s+(\w+)(?:\s*<\s*(\w+(?:::\w+)?))?/);
- const className = cls?.[1] || "";
- const parent = cls?.[2] || "";
- if (parent?.includes("Controller")) {
- const actions = (content.match(/def\s+(index|show|new|create|edit|update|destroy|\w+)/g) || [])
- .map(m => m.match(/def\s+(\w+)/)?.[1]).filter(n => n && !n.startsWith("_"));
- return cap(actions.length ? `Controller: ${actions.join(", ")}` : `Controller: ${className}`);
- }
- if (parent === "ApplicationRecord" || parent === "ActiveRecord::Base")
- return `Model: ${className}`;
- if (basename.match(/^\d{14}_/)) {
- const create = content.match(/create_table\s+:(\w+)/);
- return create ? `Migration: create ${create[1]}` : "Database migration";
- }
- const methods = (content.match(/def\s+(\w+)/g) || []).map(m => m.match(/def\s+(\w+)/)?.[1]).filter(n => n && !n.startsWith("_"));
- if (cls && methods.length)
- return cap(`${className}: ${methods.slice(0, 4).join(", ")}`);
- }
- // ─── Swift ───────────────────────────────────────────────
- if (ext === ".swift") {
- if (content.includes(": View") || content.includes("some View")) {
- const name = content.match(/struct\s+(\w+)\s*:\s*View/);
- return name ? `SwiftUI view: ${name[1]}` : "SwiftUI view";
- }
- const proto = content.match(/protocol\s+(\w+)/);
- if (proto)
- return `Protocol: ${proto[1]}`;
- const struct = content.match(/(?:public\s+)?struct\s+(\w+)/);
- const cls = content.match(/(?:public\s+)?class\s+(\w+)/);
- const name = struct?.[1] || cls?.[1] || "";
- if (name)
- return `${struct ? "Struct" : "Class"}: ${name}`;
- }
- // ─── Dart / Flutter ──────────────────────────────────────
- if (ext === ".dart") {
- if (content.includes("StatefulWidget") || content.includes("StatelessWidget")) {
- const name = content.match(/class\s+(\w+)\s+extends\s+(?:Stateful|Stateless)Widget/);
- return name ? `${content.includes("StatefulWidget") ? "Stateful" : "Stateless"} widget: ${name[1]}` : "Flutter widget";
- }
- const cls = content.match(/class\s+(\w+)/);
- if (cls)
- return `Class: ${cls[1]}`;
- }
- // ─── Vue / Svelte / Astro ────────────────────────────────
- if (ext === ".vue") {
- const name = content.match(/name:\s*['"]([^'"]+)['"]/);
- const setup = content.includes("