Merge pull request #5 from Curabis:startup-&-agents

Startup & agents
This commit is contained in:
Michael Dieringer 2026-06-21 12:34:01 +02:00 committed by GitHub
commit e4038667f6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 818 additions and 0 deletions

View file

@ -15,3 +15,68 @@ custom/
Fork or clone BCQuality into your own repository and add your content here. Knowledge files in `/custom/knowledge/` follow the same frontmatter schema and section requirements as every other layer. Action skills in `/custom/skills/` follow the Action Skill template defined in `/skills/`.
When agents consume BCQuality, the custom layer is loaded alongside Microsoft and Community — your overrides apply automatically.
---
# CURABIS — BCQuality customizations
## Developer onboarding (new machine)
Two files must be placed on the developer's machine. Everything else is automatic.
### 1. Global Claude Code instructions
Copy [`setup/machine/CLAUDE.md`](setup/machine/CLAUDE.md) to `~/.claude/CLAUDE.md`
and fill in your name and username.
This file tells Claude Code about CURABIS Standard in every session —
including brand-new, unconfigured repositories.
### 2. BC MCP credentials
Create `~/.bc-mcp.config.json` with your BC service-to-service credentials:
```json
{
"tenantId": "<your-tenant-id>",
"clientId": "<your-client-id>",
"clientSecret": "<your-client-secret>",
"baseUrl": "https://api.businesscentral.dynamics.com"
}
```
**Never commit this file.** It contains secrets.
## Configuring a new project
Once the two machine files are in place, open any AL-Go repository in VS Code
and tell Claude Code:
> "Konfigurer dette projekt til CURABIS Standard"
Claude fetches [`setup/curabis-standard.agent.md`](setup/curabis-standard.agent.md)
and writes all project files automatically:
`CLAUDE.md`, `.mcp.json`, `.github/.agents/`, `cspell.json`, `projectmemory/`.
The BC MCP bridge (`bc-mcp-bridge.js`) is also installed to `~/.claude/`
from this repo — so it stays up to date every time setup is re-run.
## Folder structure
```
custom/
README.md ← this file
knowledge/
architecture/ ← AL architecture rules
testing/ ← test quality rules
mcp/ ← BC MCP / API page rules
setup/
curabis-standard.agent.md ← project setup agent
bc-mcp-bridge.js ← BC MCP bridge (authoritative copy)
machine/
CLAUDE.md ← global Claude Code instructions template
templates/
bcquality.agent.md ← BCQuality review agent (per project)
immanuel.agent.md ← Rule guardian agent (per project)
cspell.json ← Standard spell-check config
```

View file

@ -0,0 +1,168 @@
#!/usr/bin/env node
// bc-mcp-bridge.js
// Lokal stdio <-> streamable-HTTP bro mellem Claude Code og BC MCP-serveren.
// S2S-auth (client credentials) - ingen bruger-login. Broen henter + fornyer token selv,
// og injicerer routing-headers. Claude Code taler stdio til broen (intet DCR/OAuth-problem).
//
// Config: Scripts/bc-mcp.config.json (GITIGNORED) eller env-vars:
// BC_MCP_TENANT, BC_MCP_CLIENT_ID, BC_MCP_CLIENT_SECRET,
// BC_MCP_ENVIRONMENT (default Production), BC_MCP_COMPANY, BC_MCP_CONFIG (default CURABIS_DEV)
//
// .mcp.json:
// "businesscentral": { "command": "node", "args": ["Scripts/bc-mcp-bridge.js"] }
const fs = require("fs");
const path = require("path");
const ENDPOINT = "https://mcp.businesscentral.dynamics.com";
function die(m) { process.stderr.write(`[bc-mcp-bridge] ${m}\n`); process.exit(1); }
function loadConfig() {
let c = {};
// Soeger: 1) repo-lokal Scripts/bc-mcp.config.json 2) pr-maskine ~/.bc-mcp.config.json
const home = process.env.USERPROFILE || process.env.HOME || "";
for (const f of [path.join(__dirname, "bc-mcp.config.json"), path.join(home, ".bc-mcp.config.json")]) {
if (fs.existsSync(f)) {
try { c = JSON.parse(fs.readFileSync(f, "utf8")); break; } catch (e) { die(`Kan ikke laese ${f}: ${e}`); }
}
}
const cfg = {
tenant: process.env.BC_MCP_TENANT || c.tenant,
clientId: process.env.BC_MCP_CLIENT_ID || c.clientId,
clientSecret: process.env.BC_MCP_CLIENT_SECRET || c.clientSecret,
environment: process.env.BC_MCP_ENVIRONMENT || c.environment || "Production",
company: process.env.BC_MCP_COMPANY || c.company,
config: process.env.BC_MCP_CONFIG || c.configurationName || "CURABIS_DEV",
};
for (const k of ["tenant", "clientId", "clientSecret", "company"]) {
if (!cfg[k]) die(`Mangler config '${k}' - saet i Scripts/bc-mcp.config.json eller env-var.`);
}
return cfg;
}
const cfg = loadConfig();
let token = null, tokenExp = 0, sessionId = null;
async function getToken() {
if (token && Date.now() < tokenExp - 60000) return token; // forny 1 min foer udloeb
const body = new URLSearchParams({
client_id: cfg.clientId,
client_secret: cfg.clientSecret,
scope: `${ENDPOINT}/.default`,
grant_type: "client_credentials",
});
const r = await fetch(`https://login.microsoftonline.com/${cfg.tenant}/oauth2/v2.0/token`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body,
});
if (!r.ok) throw new Error(`token ${r.status}: ${await r.text()}`);
const j = await r.json();
token = j.access_token;
tokenExp = Date.now() + (j.expires_in || 3600) * 1000;
return token;
}
// BC kraever Base64 hvis header-vaerdien har ikke-ASCII (ae/oe/aa).
const enc = v => /[^\x00-\x7F]/.test(v) ? `=?base64?${Buffer.from(v, "utf8").toString("base64")}?=` : v;
function parseSSE(text) {
const msgs = [];
for (const block of text.split(/\r?\n\r?\n/)) {
const data = block.split(/\r?\n/).filter(l => l.startsWith("data:")).map(l => l.slice(5).replace(/^ /, ""));
if (data.length) { const p = data.join("\n").trim(); if (p && p !== "[DONE]") msgs.push(p); }
}
return msgs;
}
async function forward(msg) {
const tok = await getToken();
const headers = {
"Authorization": `Bearer ${tok}`,
"Content-Type": "application/json",
"Accept": "application/json, text/event-stream",
"TenantId": cfg.tenant,
"EnvironmentName": cfg.environment,
"Company": enc(cfg.company),
"ConfigurationName": enc(cfg.config),
};
if (sessionId) headers["Mcp-Session-Id"] = sessionId;
const r = await fetch(ENDPOINT, { method: "POST", headers, body: JSON.stringify(msg) });
const sid = r.headers.get("mcp-session-id"); if (sid) sessionId = sid;
const ct = r.headers.get("content-type") || "";
const text = await r.text();
if (!r.ok && !text) throw new Error(`HTTP ${r.status}`);
return ct.includes("text/event-stream") ? parseSSE(text) : (text.trim() ? [text.trim()] : []);
}
// Splits text into chunks of max maxLen chars, breaking at word boundaries.
function splitTextToChunks(text, maxLen = 250) {
const chunks = [];
while (text.length > maxLen) {
let cut = text.lastIndexOf(" ", maxLen);
if (cut <= 0) cut = maxLen; // no space found — hard cut
chunks.push(text.slice(0, cut).trimEnd());
text = text.slice(cut).trimStart();
}
if (text) chunks.push(text);
return chunks;
}
// Intercepts Create_TaskComment calls with comment > 250 chars and splits into multiple lines.
// Each chunk is sent in its own fresh BC session so GetNextLineNo sees previously committed records.
async function dispatchCreateComment(msg) {
const args = (msg.params && msg.params.arguments) || {};
const comment = args.comment || "";
const chunks = splitTextToChunks(comment);
let tempId = Date.now();
const savedSessionId = sessionId; // preserve the main conversation session
for (const chunk of chunks) {
sessionId = null; // fresh session per chunk → independent BC transaction
const chunkMsg = {
...msg,
id: tempId++,
params: { ...msg.params, arguments: { ...args, comment: chunk } },
};
await forward(chunkMsg);
sessionId = null; // discard the chunk session — never bleed into next chunk
}
sessionId = savedSessionId; // restore main session for subsequent calls
return [JSON.stringify({
jsonrpc: "2.0", id: msg.id,
result: { content: [{ type: "text", text: `Kommentar gemt i ${chunks.length} linje(r).` }] },
})];
}
// stdio-loop: newline-delimited JSON-RPC (MCP stdio-transport).
let buf = "";
process.stdin.setEncoding("utf8");
process.stdin.on("data", async (chunk) => {
buf += chunk;
let i;
while ((i = buf.indexOf("\n")) >= 0) {
const line = buf.slice(0, i).trim();
buf = buf.slice(i + 1);
if (!line) continue;
let msg;
try { msg = JSON.parse(line); } catch { continue; }
try {
const isCreateComment =
msg.method === "tools/call" &&
msg.params?.name === "Create_TaskComment_PAG6102902" &&
(msg.params?.arguments?.comment || "").length > 250;
const responses = isCreateComment
? await dispatchCreateComment(msg)
: await forward(msg);
for (const out of responses) process.stdout.write(out + "\n");
} catch (e) {
process.stderr.write(`[bc-mcp-bridge] ${e.message || e}\n`);
if (msg.id !== undefined && msg.id !== null) {
process.stdout.write(JSON.stringify({
jsonrpc: "2.0", id: msg.id, error: { code: -32000, message: String(e.message || e) },
}) + "\n");
}
}
}
});
process.stdin.on("end", () => process.exit(0));

View file

@ -0,0 +1,355 @@
---
kind: action-skill
id: curabis-standard-setup
version: 1
title: CURABIS Standard — Project Setup
description: >
Configures a new or existing repository to the CURABIS Standard development
environment. Writes CLAUDE.md, BCQuality agents, .mcp.json and cspell.json
from authoritative templates in BCQuality. Deploys bc-mcp-bridge.js to the
developer's machine. Also handles updates to an already-configured project.
inputs: [repo-root]
outputs: [CLAUDE.md, .mcp.json, .github/.agents/*, cspell.json, projectmemory/]
domain: setup
keywords: [setup, bootstrap, update, mcp, bcquality, standard, new-project]
---
# CURABIS Standard — Project Setup
## Purpose
One command turns an empty or existing AL-Go repository into a fully configured
CURABIS development environment: BCQuality rules loaded, BC MCP wired, Immanuel
on guard, and project memory ready.
## Triggers
This agent runs when the developer says any of:
- **"Konfigurer dette projekt til CURABIS Standard"** → full setup (new project)
- **"Opdater CURABIS Standard fra BCQuality"** → update mode (existing project)
Detect which mode based on the trigger phrase and proceed accordingly.
## Source URLs (BCQuality — always fetch fresh)
```
BASE = https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/setup
```
| Artefakt | URL |
|---|---|
| bc-mcp-bridge.js | `{BASE}/bc-mcp-bridge.js` |
| bc-mcp.config.template.json | `{BASE}/machine/bc-mcp.config.template.json` |
| bcquality.agent.md | `{BASE}/templates/bcquality.agent.md` |
| immanuel.agent.md | `{BASE}/templates/immanuel.agent.md` |
| cspell.json | `{BASE}/templates/cspell.json` |
CLAUDE.md and .mcp.json are generated dynamically — not fetched as static templates
because they contain project-specific paths.
---
## MODE A — Full setup (new project)
Triggered by: "Konfigurer dette projekt til CURABIS Standard"
### Step 1 — Gather context (auto-detect before asking)
Run these checks silently:
```bash
git remote get-url origin # → repo name / URL
git config user.email # → developer identity
git config user.name
```
Check whether these paths exist:
- `.vscode/find-altool.ps1` → AL MCP available
- `CLAUDE.md` → already configured?
- `~/.claude/bc-mcp-bridge.js` → bridge already installed?
- `~/.bc-mcp.config.json` → BC credentials present?
If `CLAUDE.md` already exists, ask: "CLAUDE.md eksisterer allerede. Overskrive? (ja/nej)"
Stop if the developer answers no.
### Step 2 — Ask exactly three questions
Do not proceed until all three are answered.
```
1. Hvad er projektets navn?
(bruges som overskrift i CLAUDE.md og i projectmemory)
2. Hvilke AL-app mapper er i repoen?
Eksempler:
a) Flad struktur — kildefiler direkte i roden (AppSource/)
b) .apps/<AppName> (main app)
c) .apps/<AppName> + .apps/<AppName>.Test (main + test)
Angiv de faktiske mapper.
3. Hvad er dit brugernavn til projectmemory-filen?
(f.eks. "mid" → memoryupdates_mid.md)
```
### Step 3 — Deploy machine files
#### 3a. bc-mcp-bridge.js
1. Fetch `{BASE}/bc-mcp-bridge.js`
2. Write to `~/.claude/bc-mcp-bridge.js` (overwrite silently — BCQuality is authoritative)
3. Confirm: "bc-mcp-bridge.js er opdateret på din maskine."
#### 3b. bc-mcp.config.json
If `~/.bc-mcp.config.json` already exists: skip silently.
If it does NOT exist:
1. Fetch `{BASE}/machine/bc-mcp.config.template.json`
2. Write it to `~/.bc-mcp.config.json` as-is
3. Tell the developer:
> "⚠️ `~/.bc-mcp.config.json` er oprettet fra CURABIS-template.
> Åbn filen og erstat `<indsæt din personlige client secret her>` med din egen secret.
> Gem filen — BC MCP er klar når du genstarter Claude Code."
### Step 4 — Write project files
#### 4a. CLAUDE.md
Generate from this template, substituting answers from Step 2:
```markdown
# {PROJECT_NAME} — Claude Code Instructions
This file is read automatically by Claude Code at the start of every session.
## BCQuality
At the start of every session, before doing anything else:
1. Read `.github/.agents/bcquality.agent.md`
2. Fetch and read ALL knowledge files listed under Source - Layer 2:
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/architecture/pages-must-not-contain-business-logic.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/architecture/namespace-must-be-verified-from-source.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/architecture/al-identifiers-must-be-english.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/architecture/clarify-before-building.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/architecture/xliff-translation-workflow.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/architecture/new-file-requires-vscode-refresh.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/architecture/exposed-objects-must-be-in-a-permission-set.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/architecture/shared-project-memory-must-be-in-repo.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/architecture/commit-message-must-include-bc-task-id.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/architecture/branch-merge-to-main-workflow.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/testing/test-setup-must-use-library-codeunit.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/testing/test-data-must-be-random-and-complete.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/testing/tests-must-adapt-to-existing-code.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/testing/test-one-when-per-test.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/testing/ui-test-codeunit-naming.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/testing/test-feature-scenario-tags.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/mcp/api-page-flowfields-must-be-calcfields.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/mcp/stored-derived-fields-must-not-be-exposed-directly.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/mcp/api-page-key-fields-must-be-editable-on-insert.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/mcp/api-page-least-privilege-write-access.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/mcp/agent-must-not-write-business-process-status.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/mcp/bc-mcp-find-active-task-for-branch.md
These rules are always active.
## On-demand agents
These are invoked only when needed - not at session start:
- `.github/.agents/immanuel.agent.md` - BCQuality rule guardian. Invoke when the user
proposes adding a new rule to BCQuality. Runs the Categorical Imperative test and drafts
the knowledge file. Only Michael (mid) may approve and push rules to BCQuality.
## AL projects
{AL_PROJECTS_SECTION}
## Shared project memory
At session start, read **all files** in `projectmemory/` — they contain shared
project observations from all team members and are version-controlled in git.
When you learn something project-relevant (business rules, architectural decisions,
scope boundaries, known technical debt), write it to
`projectmemory/memoryupdates_<username>.md` for the active user.
User-specific preferences (tone, workflow habits) stay in the local
`~/.claude/projects/.../memory/` folder as before.
## About this project
{PROJECT_NAME} Business Central extension
```
**AL_PROJECTS_SECTION substitution rules:**
- Flat (AppSource/):
```
Main app is in `AppSource/` at repo root.
```
- .apps/\<Name\> only:
```
The app is loaded via MCP hooks:
- .apps\<Name> — main app
```
- .apps/\<Name\> + .apps/\<Name\>.Test:
```
Both apps are always loaded via MCP hooks:
- .apps\<Name> — main app
- .apps\<Name>.Test — test app
```
Add running-tests section only when both main + test app exist:
```markdown
## Running tests
The `al` MCP server is wired into Claude Code via the repo-root `.mcp.json`.
To run the test suite end to end:
1. `al_auth_login` - authenticate to the BC sandbox (once per session).
2. `al_downloadsymbols` - fetch dependency symbols.
3. `al_compile` (or `al_build`) - confirm both apps build clean.
4. `al_publish` - publish main + test app to the sandbox.
5. `al_run_tests` - execute the tests; optionally filter to one codeunit.
After creating any new `.al` file, reload the AL extension in VS Code
(`Ctrl+Shift+P -> AL: Reload Extension`) before trusting diagnostics.
```
#### 4b. .mcp.json
If `.vscode/find-altool.ps1` exists:
```json
{
"mcpServers": {
"al": {
"type": "stdio",
"command": "powershell",
"args": [
"-ExecutionPolicy", "Bypass",
"-File", "<ABS_PATH_TO_VSCODE>/find-altool.ps1",
"launchmcpserver", "--transport", "stdio"
]
},
"businesscentral": {
"command": "node",
"args": ["C:\\Users\\<USERNAME>\\.claude\\bc-mcp-bridge.js"]
}
}
}
```
If `.vscode/find-altool.ps1` does NOT exist:
```json
{
"mcpServers": {
"businesscentral": {
"command": "node",
"args": ["C:\\Users\\<USERNAME>\\.claude\\bc-mcp-bridge.js"]
}
}
}
```
Substitute `<ABS_PATH_TO_VSCODE>` and `<USERNAME>` from detected values.
If `find-altool.ps1` is missing, note after writing .mcp.json:
> " AL MCP er ikke konfigureret endnu. Kør `Ctrl+Shift+P → AL: Configure MCP Server`
> i VS Code for at generere find-altool.ps1, og kør derefter
> 'Opdater CURABIS Standard fra BCQuality' — AL MCP tilføjes automatisk."
#### 4c. .github/.agents/ (fetch from BCQuality)
Fetch and write verbatim:
- `{BASE}/templates/bcquality.agent.md``.github/.agents/bcquality.agent.md`
- `{BASE}/templates/immanuel.agent.md``.github/.agents/immanuel.agent.md`
Create `.github/.agents/` if it does not exist.
#### 4d. cspell.json
Fetch `{BASE}/templates/cspell.json` and write to repo root.
If a `cspell.json` already exists, merge the `words` array — do not overwrite
custom project words.
#### 4e. projectmemory/
Create `projectmemory/` if it does not exist.
Create `projectmemory/memoryupdates_<username>.md` if it does not exist:
```markdown
# Project Memory — <username> (<full name>)
Observations og beslutninger der er relevante for alle på projektet.
Læses automatisk af Claude Code ved session-start (via CLAUDE.md).
---
(Tilføj observationer her)
```
### Step 5 — Confirm and offer initial commit
List all files written, then ask:
> "Setup er færdigt. Vil du have mig til at lave det første commit? (ja/nej)"
If yes, stage and commit:
```
[SETUP] Konfigurer til CURABIS Standard
- CLAUDE.md med BCQuality knowledge-liste
- .github/.agents/bcquality.agent.md + immanuel.agent.md
- .mcp.json med BC MMP bridge
- cspell.json
- projectmemory/ mappe
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
```
---
## MODE B — Update (existing project)
Triggered by: "Opdater CURABIS Standard fra BCQuality"
Updates only the files that come directly from BCQuality.
Never touches `CLAUDE.md`, `projectmemory/`, or `~/.bc-mcp.config.json`.
### What gets updated
| Fil | Handling |
|---|---|
| `~/.claude/bc-mcp-bridge.js` | Fetch fresh from BCQuality, overwrite |
| `.github/.agents/bcquality.agent.md` | Fetch fresh from BCQuality, overwrite |
| `.github/.agents/immanuel.agent.md` | Fetch fresh from BCQuality, overwrite |
| `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 |
### What does NOT get updated
- `CLAUDE.md` — project-specific, managed per project
- `projectmemory/` — team knowledge, never overwritten by tooling
- `~/.bc-mcp.config.json` — contains developer secrets
### After update
Report what changed, then ask:
> "Opdatering færdig. Vil du have mig til at committe ændringerne? (ja/nej)"
If yes, commit:
```
[SETUP] Opdater CURABIS Standard fra BCQuality
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
```
---
## Invocation note
This agent is fetched on demand from BCQuality. Both commands work in any
project — including one not yet configured — because Claude reads the URL
from `~/.claude/CLAUDE.md` (global instructions, present on all CURABIS machines).

View file

@ -0,0 +1,25 @@
<!-- cspell:disable -->
# CURABIS — Global Claude Code Instructions
These instructions apply to every Claude Code session, regardless of project.
## CURABIS Standard project setup
When working in a repository that has no `CLAUDE.md`, or when the user says
**"Konfigurer dette projekt til CURABIS Standard"**, fetch and follow the setup agent:
```
https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/setup/curabis-standard.agent.md
```
This agent configures the repository with BCQuality rules, BC MCP bridge,
Immanuel (rule guardian), and project memory — from a single command.
## Identity
- Organization: CURABIS ApS
- BC MCP bridge is installed at `~/.claude/bc-mcp-bridge.js`
- BC MCP credentials are at `~/.bc-mcp.config.json` (never commit this file)
<!-- Replace the two lines below with your own details -->
- User: [Your Name] (username: [your-username])

View file

@ -0,0 +1,6 @@
{
"tenantId": "CURABIS-TENANT-ID",
"clientId": "CURABIS-CLIENT-ID",
"clientSecret": "<indsæt din personlige client secret her>",
"baseUrl": "https://api.businesscentral.dynamics.com"
}

View file

@ -0,0 +1,61 @@
---
kind: action-skill
id: curabis-al-code-review
version: 1
title: CURABIS AL code review
description: Reviews AL source changes against BCQuality knowledge and CURABIS-specific architecture rules.
inputs: [pr-diff, file-path]
outputs: [findings-report]
bc-version: [all]
technologies: [al]
countries: [w1]
application-area: [all]
domain: architecture
keywords: [page-logic, codeunit, posting, test-library, suppresscommit, asserterror, findset, namespace, english, random-data]
sub-skills:
- microsoft/skills/review/al-code-review.md
---
# CURABIS AL code review
## Source
Layer 1 - Microsoft BCQuality: https://github.com/microsoft/BCQuality
Layer 2 - CURABIS custom knowledge (fetch before applying rules):
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/architecture/pages-must-not-contain-business-logic.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/architecture/namespace-must-be-verified-from-source.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/architecture/al-identifiers-must-be-english.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/architecture/clarify-before-building.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/architecture/xliff-translation-workflow.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/architecture/new-file-requires-vscode-refresh.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/architecture/exposed-objects-must-be-in-a-permission-set.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/architecture/shared-project-memory-must-be-in-repo.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/architecture/commit-message-must-include-bc-task-id.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/architecture/branch-merge-to-main-workflow.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/testing/test-setup-must-use-library-codeunit.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/testing/test-data-must-be-random-and-complete.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/testing/tests-must-adapt-to-existing-code.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/testing/test-one-when-per-test.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/testing/ui-test-codeunit-naming.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/testing/test-feature-scenario-tags.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/mcp/api-page-flowfields-must-be-calcfields.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/mcp/stored-derived-fields-must-not-be-exposed-directly.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/mcp/api-page-key-fields-must-be-editable-on-insert.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/mcp/api-page-least-privilege-write-access.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/mcp/agent-must-not-write-business-process-status.md
- https://raw.githubusercontent.com/Curabis/BCQuality/main/custom/knowledge/mcp/bc-mcp-find-active-task-for-branch.md
## Action
CURABIS-ARCH-001: Logic belongs in codeunits, not pages.
CURABIS-ARCH-002: Pages must not call Modify/Insert/Delete directly.
CURABIS-ARCH-003: Test setup must use the project Test Library.
CURABIS-ARCH-004: SetSuppressCommit(true) before posting codeunit Run() in tests.
CURABIS-ARCH-005: asserterror must be followed by an assertion.
CURABIS-ARCH-006: FindSet(true) only before Modify() inside a loop.
CURABIS-ARCH-007: Test data must be random - never hardcode codes or names.
CURABIS-ARCH-008: Namespaces must be verified from source files or al_symbolsearch.
CURABIS-ARCH-009: All AL identifiers must be English (ENU).
CURABIS-ARCH-010: Clarify before building if task is ambiguous.
CURABIS-ARCH-011: Every exposed object (API page, web-service page/query) must be in at least one permission set.

View file

@ -0,0 +1,34 @@
{
"version": "0.2",
"language": "en,da",
"ignorePaths": [
"**/*.xlf",
"**/*.xml",
"**/node_modules/**",
"projectmemory/**",
"CLAUDE.md"
],
"words": [
"Curabis",
"CURABIS",
"codeunit",
"Codeunits",
"xliff",
"subpage",
"FactBox",
"TestPage",
"pageextension",
"tableextension",
"permissionset",
"RunModal",
"SetValue",
"OpenEdit",
"OpenNew",
"FindFirst",
"FindSet",
"FindLast",
"WorkDate",
"CurrExchRate",
"NoImplicitWith"
]
}

View file

@ -0,0 +1,104 @@
---
kind: action-skill
id: curabis-bcquality-guardian
version: 1
title: Immanuel — BCQuality Rule Guardian
description: >
Validates proposed BCQuality rules against Kant's Categorical Imperative before
they are submitted to Michael Dieringer (mid) for approval. Guards the BCQuality
knowledge base against project-specific, contradictory, or poorly scoped rules.
inputs: [proposed-rule-text]
outputs: [validation-report, draft-knowledge-file]
domain: governance
keywords: [bcquality, rule, categorical-imperative, governance, universal-law]
---
# Immanuel — BCQuality Rule Guardian
## Purpose
BCQuality rules are **universal laws** for all CURABIS developers on all projects.
Before a rule enters the knowledge base, it must pass the Categorical Imperative test:
> "Act only according to that maxim whereby you can at the same time will
> that it should become a universal law."
>
> — Immanuel Kant, *Groundwork of the Metaphysics of Morals* (1785)
Applied to BCQuality: **"What would happen to CURABIS if every developer followed
this rule on every project, every day, without exception?"**
## Authorization
**Only Michael Dieringer (mid) may add rules to BCQuality.**
Immanuel is an advisor, not an executor. He validates, drafts, and recommends.
He never pushes to BCQuality directly. Every rule ends with an explicit
hand-off to Michael for review and approval.
## Validation Protocol
Run all four tests before recommending a rule. If any test fails, the rule
must be revised or redirected to `projectmemory/` instead.
### Test 1 — Universalizability
Ask: *"What if every CURABIS developer followed this rule on every project?"*
- Does the rule still make sense? → **Pass**
- Does it create contradiction, chaos, or absurdity? → **Fail** — rule has a hidden
assumption that limits its applicability
### Test 2 — Project-specificity check
A rule fails this test if it references:
- Specific company names (Wareco, Jernpladsen, Summatim, KLB…)
- Project-specific tables, codeunits, or flows
- Tech choices that are not universal across CURABIS (specific IC patterns, etc.)
- A BC version feature not yet available in all active projects
If it fails: redirect to `projectmemory/` in the relevant repo, not BCQuality.
### Test 3 — Clarity and enforceability
Ask: *"Can a developer know, in the moment of coding, whether they are following
this rule or violating it?"*
- Clear decision point → **Pass**
- Vague or subjective → **Fail** — sharpen the rule before proceeding
### Test 4 — Additive value
Ask: *"Does this rule prevent a real problem that developers would otherwise
not catch?"*
- Fills a genuine gap → **Pass**
- Already covered by an existing BCQuality rule → **Fail** — point to the
existing rule instead; don't duplicate
## Output Format
After running all four tests, produce:
```
## Categorical Imperative Assessment
**Proposed rule:** <one-line summary>
| Test | Result | Notes |
|---|---|---|
| 1. Universalizability | ✅ Pass / ❌ Fail | ... |
| 2. Project-specificity | ✅ Pass / ❌ Fail | ... |
| 3. Clarity | ✅ Pass / ❌ Fail | ... |
| 4. Additive value | ✅ Pass / ❌ Fail | ... |
**Verdict:** APPROVED FOR BCQUALITY / REVISE / REDIRECT TO projectmemory
**Recommended path:** custom/knowledge/<category>/<filename>.md
```
If verdict is APPROVED, also produce the complete draft knowledge file
in BCQuality markdown format, ready for Michael to review and push.
## Hand-off
End every assessment with:
> "Denne regel kræver Michaels godkendelse (mid) inden den tilføjes til BCQuality.
> Ingen andre må tilføje regler til BCQuality-repoen."