Merge pull request #43 from Curabis/setup/bcquality-knowledge-sync

[Setup] Deploy BCQuality knowledge sync mechanism to every CURABIS project
This commit is contained in:
Michael Dieringer 2026-07-01 14:40:34 +02:00 • committed by GitHub
commit 4ee1af5d12
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 183 additions and 12 deletions

View file

@ -1,15 +1,17 @@
--- ---
kind: action-skill kind: action-skill
id: curabis-standard-setup id: curabis-standard-setup
version: 5 version: 6
title: CURABIS Standard — Project Setup title: CURABIS Standard — Project Setup
description: > description: >
Configures a new or existing repository to the CURABIS Standard development Configures a new or existing repository to the CURABIS Standard development
environment. Writes CLAUDE.md, BCQuality agents, .mcp.json and cspell.json environment. Writes CLAUDE.md, BCQuality agents, .mcp.json and cspell.json
from authoritative templates in BCQuality. Deploys bc-mcp-bridge.js to the from authoritative templates in BCQuality. Deploys bc-mcp-bridge.js to the
developer's machine. Also handles updates to an already-configured project. developer's machine. Syncs a local three-layer BCQuality knowledge mirror
(custom/community/microsoft + INDEX.md) into the repo. Also handles updates
to an already-configured project.
inputs: [repo-root] inputs: [repo-root]
outputs: [CLAUDE.md, .mcp.json, .github/.agents/*, cspell.json, projectmemory/, docs/] outputs: [CLAUDE.md, .mcp.json, .github/.agents/*, .github/.agents/bcquality-knowledge/, cspell.json, projectmemory/, docs/]
domain: setup domain: setup
keywords: [setup, bootstrap, update, mcp, bcquality, standard, new-project] keywords: [setup, bootstrap, update, mcp, bcquality, standard, new-project]
--- ---
@ -60,6 +62,7 @@ AGENTS_BASE = https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/ag
| aurelius.agent.md | `{AGENTS_BASE}/aurelius.agent.md` | | aurelius.agent.md | `{AGENTS_BASE}/aurelius.agent.md` |
| munger.agent.md | `{AGENTS_BASE}/munger.agent.md` | | munger.agent.md | `{AGENTS_BASE}/munger.agent.md` |
| cspell.json | `{BASE}/templates/cspell.json` | | cspell.json | `{BASE}/templates/cspell.json` |
| sync-bcquality-knowledge.ps1 | `{BASE}/sync-bcquality-knowledge.ps1` |
CLAUDE.md and .mcp.json are generated dynamically — not fetched as static templates CLAUDE.md and .mcp.json are generated dynamically — not fetched as static templates
because they contain project-specific paths. because they contain project-specific paths.
@ -144,15 +147,20 @@ This file is read automatically by Claude Code at the start of every session.
At the start of every session, before doing anything else: At the start of every session, before doing anything else:
1. Read `.github/.agents/bcquality.agent.md` 1. Read `.github/.agents/bcquality.agent.md`
2. Read BCQuality knowledge files from local cache (no network — fast): 2. Read knowledge files vendored locally under `.github/.agents/bcquality-knowledge/`:
``` - **`custom/`** — always read in full (CURABIS-specific rules, always active)
C:\Users\mid\.claude\bcquality-knowledge\architecture\*.md - **`community/`** and **`microsoft/`** — do NOT read in full (~200 files is too
C:\Users\mid\.claude\bcquality-knowledge\testing\*.md much to preload every session). Instead read `INDEX.md` first and open only
C:\Users\mid\.claude\bcquality-knowledge\mcp\*.md the files whose domain/keywords match the task at hand.
```
The cache is populated automatically when BCQuality updates (via global CLAUDE.md These files are a local copy of the Curabis BCQuality knowledge base, spanning
auto-update). If the cache is missing or empty on first run, the auto-update will all three layers (custom/community/microsoft). They are read from disk each
populate it. Do not fetch URLs manually unless explicitly asked. session — no network fetch required. Refresh them from upstream periodically
(e.g. monthly, or whenever a session flags the mirror as stale) by running:
powershell -File .github/.agents/sync-bcquality-knowledge.ps1
Then review the diff and commit any changes.
These rules are always active. These rules are always active.
@ -352,6 +360,21 @@ Fetch and write verbatim:
Create `.github/.agents/` if it does not exist. Create `.github/.agents/` if it does not exist.
#### 4c-2. bcquality-knowledge/ (local mirror of all three BCQuality layers)
1. Fetch `{BASE}/sync-bcquality-knowledge.ps1` → write to
`.github/.agents/sync-bcquality-knowledge.ps1`
2. Run it once: `powershell -ExecutionPolicy Bypass -File .github/.agents/sync-bcquality-knowledge.ps1`
This populates `.github/.agents/bcquality-knowledge/{custom,community,microsoft}/`
plus an `INDEX.md` (domain + keywords per file, for relevance-based lookup —
`custom/` is always read in full, `community/` and `microsoft/` are scanned via
the index rather than preloaded, since together they run into the hundreds of files).
3. Confirm: "bcquality-knowledge/ synkroniseret — [antal] filer på tværs af 3 lag."
This mirror is what Step 4a's generated CLAUDE.md instructs Claude to read at
session start. Without this step, the CLAUDE.md reference in 4a points at a
folder that doesn't exist yet.
#### 4d. cspell.json #### 4d. cspell.json
Fetch `{BASE}/templates/cspell.json` and write to repo root. Fetch `{BASE}/templates/cspell.json` and write to repo root.
@ -447,6 +470,8 @@ Never touches `CLAUDE.md`, `projectmemory/`, `docs/`, or `~/.bc-mcp.config.json`
| `.github/.agents/weber.agent.md` | Fetch fresh from BCQuality, overwrite (add if missing) | | `.github/.agents/weber.agent.md` | Fetch fresh from BCQuality, overwrite (add if missing) |
| `.github/.agents/algo-settings.agent.md` | Fetch fresh from BCQuality, overwrite (add if missing) | | `.github/.agents/algo-settings.agent.md` | Fetch fresh from BCQuality, overwrite (add if missing) |
| `.github/.agents/smiley.agent.md` | Fetch fresh from BCQuality, overwrite (add if missing) | | `.github/.agents/smiley.agent.md` | Fetch fresh from BCQuality, overwrite (add if missing) |
| `.github/.agents/sync-bcquality-knowledge.ps1` | Fetch fresh from BCQuality, overwrite (add if missing) |
| `.github/.agents/bcquality-knowledge/` | Re-run the sync script (see below), commit the diff |
| `cspell.json` — words from template | Merge new words, keep project words | | `cspell.json` — words from template | Merge new words, keep project words |
| `.mcp.json` — `al` entry | Add if `find-altool.ps1` now exists and entry is missing | | `.mcp.json` — `al` entry | Add if `find-altool.ps1` now exists and entry is missing |
| `.mcp.json` — `businesscentral` path | Validate and correct if wrong (see below) | | `.mcp.json` — `businesscentral` path | Validate and correct if wrong (see below) |
@ -454,6 +479,27 @@ Never touches `CLAUDE.md`, `projectmemory/`, `docs/`, or `~/.bc-mcp.config.json`
| `HEARTBEAT.md` | Create from template if missing (substitute tokens), never overwrite | | `HEARTBEAT.md` | Create from template if missing (substitute tokens), never overwrite |
| `docs/specs/`, `docs/decisions/`, `docs/cleanup/` | Create if missing, never overwrite content | | `docs/specs/`, `docs/decisions/`, `docs/cleanup/` | Create if missing, never overwrite content |
### bcquality-knowledge/ re-sync (Mode B)
After overwriting `.github/.agents/sync-bcquality-knowledge.ps1`, always re-run it:
```
powershell -ExecutionPolicy Bypass -File .github/.agents/sync-bcquality-knowledge.ps1
```
This refreshes `.github/.agents/bcquality-knowledge/{custom,community,microsoft}/`
and regenerates `INDEX.md` from the live BCQuality tree. Run this every time Mode B
runs, not just when the script itself changed — the mirror goes stale independently
of the script (new upstream knowledge files land on their own schedule). Stage the
resulting diff for the update commit in "After update — report and commit" below.
If `.github/.agents/sync-bcquality-knowledge.ps1` does not exist yet (project set up
before this mechanism existed): fetch it, run it, and also check whether the
project's CLAUDE.md still has the old flat `architecture/*.md, testing/*.md, mcp/*.md`
BCQuality-reading instruction from before the three-layer mirror existed. If so, propose
replacing it with the current template from Step 4a and ask for confirmation before
editing CLAUDE.md (same confirmation gate as the agent-synligheds-check below).
### .mcp.json — hardcoded developer-path validation (Mode B) ### .mcp.json — hardcoded developer-path validation (Mode B)
`.mcp.json` is git-committed and shared — it must not contain a path baked in for `.mcp.json` is git-committed and shared — it must not contain a path baked in for

View file

@ -0,0 +1,125 @@
# Refresh the local mirror of the Curabis BCQuality knowledge base.
#
# Mirrors three layers from https://github.com/Curabis/BCQuality:
# custom/ - Curabis org-specific rules (ALWAYS read in full each session)
# community/ - BC community patterns (loaded on relevance via INDEX.md)
# microsoft/ - platform guardrails (loaded on relevance via INDEX.md)
#
# The upstream file list is discovered dynamically from the GitHub tree API, so
# new/removed upstream files propagate automatically - nothing is hardcoded.
# After downloading, an INDEX.md is generated (one line per file, with domain +
# keywords from each file's frontmatter) so an agent can scan and pull only the
# files relevant to a task instead of loading all ~100 every session.
#
# Run periodically, then review the diff and commit:
# pwsh .github/.agents/sync-bcquality-knowledge.ps1
$ErrorActionPreference = 'Stop'
$repo = 'Curabis/BCQuality'
$branch = 'main'
$dest = Join-Path $PSScriptRoot 'bcquality-knowledge'
$staging = "$dest.tmp"
$rawBase = "https://raw.githubusercontent.com/$repo/$branch"
$treeUrl = "https://api.github.com/repos/$repo/git/trees/$branch" + '?recursive=1'
# Upstream path prefix -> local layer folder
$layerMap = [ordered]@{
'custom/knowledge/' = 'custom'
'community/knowledge/' = 'community'
'microsoft/knowledge/' = 'microsoft'
}
function Get-Frontmatter {
param([string]$Path)
$lines = Get-Content -Path $Path
$fm = @{}
if ($lines.Count -eq 0 -or $lines[0].Trim() -ne '---') { return $fm }
for ($i = 1; $i -lt $lines.Count; $i++) {
if ($lines[$i].Trim() -eq '---') { break }
if ($lines[$i] -match '^\s*([\w-]+):\s*(.*)$') {
$fm[$matches[1]] = $matches[2].Trim()
}
}
return $fm
}
Write-Host "Fetching file tree from $repo@$branch ..."
$headers = @{ 'User-Agent' = 'wareco-bcquality-sync'; 'Accept' = 'application/vnd.github+json' }
$tree = (Invoke-RestMethod -Uri $treeUrl -Headers $headers).tree
# Build the download worklist from the tree
$files = @()
foreach ($node in $tree) {
if ($node.type -ne 'blob') { continue }
if ($node.path -notlike '*.md') { continue }
foreach ($prefix in $layerMap.Keys) {
if ($node.path.StartsWith($prefix)) {
$relative = $node.path.Substring($prefix.Length) # e.g. performance/avoid-commit-inside-loops.md
$files += [pscustomobject]@{
Url = "$rawBase/$($node.path)"
Layer = $layerMap[$prefix]
Relative = $relative
LocalPath = Join-Path $staging (Join-Path $layerMap[$prefix] $relative)
}
break
}
}
}
if ($files.Count -eq 0) { throw 'No knowledge files found in upstream tree - aborting.' }
# Download into a staging folder so a mid-run failure never wipes the live copy
if (Test-Path $staging) { Remove-Item -Recurse -Force $staging }
New-Item -ItemType Directory -Force $staging | Out-Null
Write-Host "Downloading $($files.Count) knowledge files ..."
$rc = 0
foreach ($f in $files) {
New-Item -ItemType Directory -Force (Split-Path $f.LocalPath) | Out-Null
try {
Invoke-WebRequest -Uri $f.Url -OutFile $f.LocalPath -UseBasicParsing -ErrorAction Stop
Write-Host "OK $($f.Layer)/$($f.Relative)"
} catch {
Write-Error "FAIL $($f.Layer)/$($f.Relative)"
$rc = 1
}
}
# Generate INDEX.md (relevance index for all layers)
Write-Host "Generating INDEX.md ..."
$idx = [System.Collections.Generic.List[string]]::new()
$idx.Add('# BCQuality Knowledge Index')
$idx.Add('')
$idx.Add('<!-- Generated by sync-bcquality-knowledge.ps1 - do not edit by hand. -->')
$idx.Add('')
$idx.Add('Layers: `custom` is ALWAYS read in full each session. For `community` and')
$idx.Add('`microsoft`, scan this index and read only the files whose domain/keywords')
$idx.Add('match the task at hand.')
$idx.Add('')
foreach ($layer in @('custom', 'community', 'microsoft')) {
$layerFiles = $files | Where-Object { $_.Layer -eq $layer } | Sort-Object Relative
if (-not $layerFiles) { continue }
$note = if ($layer -eq 'custom') { ' (always-on)' } else { ' (load on relevance)' }
$idx.Add("## $layer$note")
$idx.Add('')
foreach ($f in $layerFiles) {
$fm = Get-Frontmatter $f.LocalPath
$domain = if ($fm.ContainsKey('domain')) { $fm['domain'] } else { '' }
$keywords = if ($fm.ContainsKey('keywords')) { $fm['keywords'].Trim('[', ']') } else { '' }
$rel = $f.Relative -replace '\.md$', ''
$idx.Add("- ``$layer/$rel`` - domain: $domain; keywords: $keywords")
}
$idx.Add('')
}
Set-Content -Path (Join-Path $staging 'INDEX.md') -Value $idx -Encoding utf8
# Swap staging into place
if (Test-Path $dest) { Remove-Item -Recurse -Force $dest }
Rename-Item -Path $staging -NewName (Split-Path $dest -Leaf)
Write-Host ''
Write-Host "Done. $($files.Count) files across $($layerMap.Count) layers."
Write-Host "Review changes with: git diff $dest"
exit $rc