diff --git a/custom/README.md b/custom/README.md index 28d7aa9..0b6c988 100644 --- a/custom/README.md +++ b/custom/README.md @@ -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": "", + "clientId": "", + "clientSecret": "", + "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 +``` diff --git a/custom/setup/bc-mcp-bridge.js b/custom/setup/bc-mcp-bridge.js new file mode 100644 index 0000000..d91a277 --- /dev/null +++ b/custom/setup/bc-mcp-bridge.js @@ -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)); diff --git a/custom/setup/curabis-standard.agent.md b/custom/setup/curabis-standard.agent.md new file mode 100644 index 0000000..1a2c2fa --- /dev/null +++ b/custom/setup/curabis-standard.agent.md @@ -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/ (main app) + c) .apps/ + .apps/.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 `` 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_.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/\ only: + ``` + The app is loaded via MCP hooks: + - .apps\ — main app + ``` +- .apps/\ + .apps/\.Test: + ``` + Both apps are always loaded via MCP hooks: + - .apps\ — main app + - .apps\.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", "/find-altool.ps1", + "launchmcpserver", "--transport", "stdio" + ] + }, + "businesscentral": { + "command": "node", + "args": ["C:\\Users\\\\.claude\\bc-mcp-bridge.js"] + } + } +} +``` + +If `.vscode/find-altool.ps1` does NOT exist: +```json +{ + "mcpServers": { + "businesscentral": { + "command": "node", + "args": ["C:\\Users\\\\.claude\\bc-mcp-bridge.js"] + } + } +} +``` + +Substitute `` and `` 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_.md` if it does not exist: + +```markdown +# Project Memory — () + +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 +``` + +--- + +## 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 +``` + +--- + +## 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). diff --git a/custom/setup/machine/CLAUDE.md b/custom/setup/machine/CLAUDE.md new file mode 100644 index 0000000..0eb5d45 --- /dev/null +++ b/custom/setup/machine/CLAUDE.md @@ -0,0 +1,25 @@ + +# 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) + + +- User: [Your Name] (username: [your-username]) diff --git a/custom/setup/machine/bc-mcp.config.template.json b/custom/setup/machine/bc-mcp.config.template.json new file mode 100644 index 0000000..2bfd9f9 --- /dev/null +++ b/custom/setup/machine/bc-mcp.config.template.json @@ -0,0 +1,6 @@ +{ + "tenantId": "CURABIS-TENANT-ID", + "clientId": "CURABIS-CLIENT-ID", + "clientSecret": "", + "baseUrl": "https://api.businesscentral.dynamics.com" +} diff --git a/custom/setup/templates/bcquality.agent.md b/custom/setup/templates/bcquality.agent.md new file mode 100644 index 0000000..b9291da --- /dev/null +++ b/custom/setup/templates/bcquality.agent.md @@ -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. diff --git a/custom/setup/templates/cspell.json b/custom/setup/templates/cspell.json new file mode 100644 index 0000000..1661e53 --- /dev/null +++ b/custom/setup/templates/cspell.json @@ -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" + ] +} diff --git a/custom/setup/templates/immanuel.agent.md b/custom/setup/templates/immanuel.agent.md new file mode 100644 index 0000000..73fe2a7 --- /dev/null +++ b/custom/setup/templates/immanuel.agent.md @@ -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:** + +| 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//.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."