Custom-laget bestaar nu begge CI-checks: 72 validator-fejl -> 0

Normalisering af alle 39 custom knowledge-filer til READ-kontraktens
skema (validate_frontmatter.py + Test-KnowledgeIndex.ps1 begge groenne):

- R01/R02: 28 filer manglede frontmatter eller brugte aeldre skemaer
  (title/category/severity/rule-id m.fl.) - alle har nu praecis de 6
  kraevede noegler; keywords haandskrevet pr. fil da de driver
  worklist-selektionen i INDEX/knowledge-index
- R09: manglende Description-sektion - regel-agtige foersteoverskrifter
  (Core Rule/Rule/Regel/Core Principle) omdoebt, eller sektion indsat
  efter titlen hvor intro-tekst fandtes
- R10: fenced code blocks konverteret til 4-space indrykkede blokke
  i alle filer (indhold uaendret)
- R11: 4 filer over 100 linjer fortaettet redaktionelt uden semantisk
  tab (ai-eval-scores 143->100, git-lifecycle 121->97,
  permission-sets 113->99, test-feature-scenario-tags 105->91)
- R05: AL0197->al0197, add_repo->add-repo; keyword-lister trimmet
  til maks 10

Ingen regler er fjernet eller aendret i betydning - kun form.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Michael Dieringer 2026-07-01 23:47:31 +02:00
parent ec2892f0ab
commit dd5637b1db
39 changed files with 729 additions and 814 deletions

View file

@ -1,7 +1,7 @@
---
bc-version: [all]
domain: architecture
keywords: [build, output, alpackages, duplicate, language-server, app-package, project-root, AL0197]
keywords: [build, output, alpackages, duplicate, language-server, app-package, project-root, al0197]
technologies: [al]
countries: [w1]
application-area: [all]
@ -20,16 +20,12 @@ AL build output (`.app` files) **must not** accumulate in the project root folde
Configure the build output path to a dedicated subfolder that is excluded from language server scanning.
In `.vscode/settings.json`:
```json
{
"al.outputPath": ".output"
}
```
{
"al.outputPath": ".output"
}
When using the MCP `al_build` tool, pass `outputPath` explicitly:
```
al_build projectPath="..." outputPath=".output/AppName.app"
```
al_build projectPath="..." outputPath=".output/AppName.app"
Add `.output/` to `.gitignore` if not already excluded.

View file

@ -1,6 +1,14 @@
---
bc-version: [all]
domain: architecture
keywords: [identifiers, naming, english, captions, translation]
technologies: [al]
countries: [w1]
application-area: [all]
---
# AL Naming Convention: English Identifiers Only
## Core Rule
## Description
All AL identifiers must be written in English, regardless of the developer's native language. "Translations are handled separately via XLIFF files — never by writing Danish, German or other language identifiers in AL source code."

View file

@ -39,15 +39,13 @@ Ask before coding if any of the following is true:
State what you understand the task to be, then list the specific questions:
```
I understand the task as: [one sentence summary]
I understand the task as: [one sentence summary]
Before I proceed, I need clarification on:
1. [specific question]
2. [specific question]
Before I proceed, I need clarification on:
1. [specific question]
2. [specific question]
I will not write any code until these are answered.
```
I will not write any code until these are answered.
Do not write partial code while waiting. Do not write "placeholder" code.
Do not write code with TODO comments where a business decision is needed.
@ -66,29 +64,25 @@ files first and resolve the ambiguity yourself before asking the developer.
## Anti Pattern
```
// WRONG: Assuming and building
// Task: "write a test for the vendor flow"
// Agent assumes: Vendor, Incoming, Handle+Invoice, no contact
// Writes 80 lines of code
// Developer says: "I meant outgoing flow with a customer"
// Result: wasted work, possible production code changes to revert
```
// WRONG: Assuming and building
// Task: "write a test for the vendor flow"
// Agent assumes: Vendor, Incoming, Handle+Invoice, no contact
// Writes 80 lines of code
// Developer says: "I meant outgoing flow with a customer"
// Result: wasted work, possible production code changes to revert
## Best Practice
```
// CORRECT: Clarify first
// Task: "write a test for the vendor flow"
// CORRECT: Clarify first
// Task: "write a test for the vendor flow"
I understand the task as: writing a test codeunit for a Settlement Voucher
flow where the counterparty is a Vendor.
I understand the task as: writing a test codeunit for a Settlement Voucher
flow where the counterparty is a Vendor.
Before I proceed, I need clarification on:
1. Incoming (vendor delivers goods) or Outgoing (vendor picks up goods)?
2. Handle-only, Invoice-only, or combined Handle+Invoice in one run?
3. Should the test use an existing vendor from the database or create one
via LibraryPurchase.CreateVendor?
Before I proceed, I need clarification on:
1. Incoming (vendor delivers goods) or Outgoing (vendor picks up goods)?
2. Handle-only, Invoice-only, or combined Handle+Invoice in one run?
3. Should the test use an existing vendor from the database or create one
via LibraryPurchase.CreateVendor?
I will not write any code until these are answered.
```
I will not write any code until these are answered.

View file

@ -1,3 +1,11 @@
---
bc-version: [all]
domain: architecture
keywords: [claude-md, agents, routing, visibility, setup]
technologies: [al]
countries: [w1]
application-area: [all]
---
bc-version: [all]
domain: architecture
keywords: [claude-md, agents, visibility, setup, mode-b, curabis-standard]
@ -35,9 +43,7 @@ with a proposed addition before the session continues.
After running Mode B (or any agent install), compare:
```
Get-ChildItem .github/.agents/*.agent.md | Select-Object -ExpandProperty BaseName
```
Get-ChildItem .github/.agents/*.agent.md | Select-Object -ExpandProperty BaseName
against the agent references in CLAUDE.md. Any filename present in the directory
but absent from CLAUDE.md is a gap that must be surfaced.
@ -46,13 +52,11 @@ but absent from CLAUDE.md is a gap that must be surfaced.
When a gap is found, output exactly this before continuing:
```
⚠️ Ny agent installeret men ikke refereret i CLAUDE.md:
⚠️ Ny agent installeret men ikke refereret i CLAUDE.md:
- <agent-navn>.agent.md
- <agent-navn>.agent.md
Claude kan ikke kalde denne agent medmindre den tilføjes til CLAUDE.md.
Vil du have mig til at tilføje den nu?
```
Claude kan ikke kalde denne agent medmindre den tilføjes til CLAUDE.md.
Vil du have mig til at tilføje den nu?
Do not continue with other activity until the developer has responded.

View file

@ -1,3 +1,11 @@
---
bc-version: [all]
domain: architecture
keywords: [commit-message, bc-task, task-id, traceability, git]
technologies: [al]
countries: [w1]
application-area: [all]
---
---
name: commit-message-must-include-bc-task-id
description: >
@ -21,19 +29,15 @@ used across Curabis teams.
## Anti Pattern
```
Add Price Lookup feature — FindPrice page, tier prices, currency conversion
```
Add Price Lookup feature — FindPrice page, tier prices, currency conversion
No traceability. Impossible to find the BC task from git history.
## Best Practice
```
[#8738] Add Price Lookup feature — FindPrice page, tier prices, currency conversion
[#8738] Add 22 UI tests for PRICING LOOKUP feature
[#8738] Add translations, shared project memory and cspell config
```
[#8738] Add Price Lookup feature — FindPrice page, tier prices, currency conversion
[#8738] Add 22 UI tests for PRICING LOOKUP feature
[#8738] Add translations, shared project memory and cspell config
## The two task numbers — use taskId, not taskNo
@ -65,4 +69,4 @@ create-task workflow) or ask the project manager to register the work.
## Scope
All commits that reach the main branch — feature, fix, test, chore, docs.
Merge commits and auto-generated commits (renovate, al-go) are exempt.
Merge commits and auto-generated commits (renovate, al-go) are exempt.

View file

@ -1,7 +1,7 @@
---
bc-version: [all]
domain: architecture
keywords: [dependency, source, add_repo, github, curabis, closed-source, test, symbol, black-box]
keywords: [dependency, source, add-repo, github, curabis, closed-source, test, symbol, black-box]
technologies: [al]
countries: [w1]
application-area: [all]
@ -49,26 +49,22 @@ the current project.
## Anti Pattern
```
// WRONG: reverse-engineering the compiled symbol package instead of reading source
// Agent parses SymbolReference.json from .alpackages/*.app to learn
// Contract Management table fields and public procedure signatures.
// Result: incomplete picture, missed validation logic, excluded feature from tests.
```
// WRONG: reverse-engineering the compiled symbol package instead of reading source
// Agent parses SymbolReference.json from .alpackages/*.app to learn
// Contract Management table fields and public procedure signatures.
// Result: incomplete picture, missed validation logic, excluded feature from tests.
## Best Practice
```
// CORRECT: add the source repo and read it directly
add_repo Curabis/ContractMgmt365app
// CORRECT: add the source repo and read it directly
add_repo Curabis/ContractMgmt365app
// Then read the actual table definitions, codeunits, and any Test Library
// codeunits that may already exist in the repo's own test app.
// Then read the actual table definitions, codeunits, and any Test Library
// codeunits that may already exist in the repo's own test app.
// If no Test Library exists in the dependency's test app:
// build GIVEN helpers in the consuming project's own Test Library codeunit
// based on the REAL table field definitions and trigger logic you can now read.
```
// If no Test Library exists in the dependency's test app:
// build GIVEN helpers in the consuming project's own Test Library codeunit
// based on the REAL table field definitions and trigger logic you can now read.
## When to apply this rule

View file

@ -1,5 +1,15 @@
---
bc-version: [all]
domain: architecture
keywords: [permission-set, api-page, web-service, exposure, security]
technologies: [al]
countries: [w1]
application-area: [all]
---
# Exposed objects must be in at least one permission set
## Description
**Rule (CURABIS-ARCH-011):** Every *exposed* object in a CURABIS app must be a member of
at least one permission set shipped by that app. "Exposed" means any object reachable from
outside the app's own UI:

View file

@ -1,13 +1,15 @@
---
name: feature-branch-must-merge-to-track-branch
title: Feature branches must merge into the project's declared track branch
category: architecture
severity: required
bc-version: [all]
domain: architecture
keywords: [git, feature-branch, track-branch, merge, workflow]
technologies: [al]
countries: [w1]
application-area: [all]
---
# Feature branches must merge into the project's declared track branch
## Rule
## Description
When a project declares a track branch in `CLAUDE.md`, all feature branches
MUST merge into that track branch — not into `main` directly. `main` is
@ -17,10 +19,8 @@ reserved for releases and hotfixes.
The track branch is declared once in `CLAUDE.md`:
```yaml
# Declares the integration target for this development sprint/module
trackBranch: purchase
```
# Declares the integration target for this development sprint/module
trackBranch: purchase
If no `trackBranch` is declared, `main` is the default and feature branches
merge there directly.
@ -46,33 +46,27 @@ The rule protects the invariant: **`main` is deployable at any moment.**
## The branching model
```
main
└── <track-branch> (e.g. "purchase" — lives for one sprint/module)
└── feature/<name> ← development happens here
└── feature/<name>
└── bugfix/<name>
└── hotfix/<name> ← branches from main, merges back to main
```
main
└── <track-branch> (e.g. "purchase" — lives for one sprint/module)
└── feature/<name> ← development happens here
└── feature/<name>
└── bugfix/<name>
└── hotfix/<name> ← branches from main, merges back to main
At release: track-branch → main (via PR, after full QA).
## Non-compliant
```bash
# Merging a feature directly to main when a track branch is declared in CLAUDE.md
git checkout main
git merge feature/my-feature # violates rule
```
# Merging a feature directly to main when a track branch is declared in CLAUDE.md
git checkout main
git merge feature/my-feature # violates rule
## Compliant
```bash
# Read track branch from CLAUDE.md → merge there
git checkout purchase
git merge --no-ff feature/my-feature
# Then sync BC: gitHubDevStatus = "Done"
```
# Read track branch from CLAUDE.md → merge there
git checkout purchase
git merge --no-ff feature/my-feature
# Then sync BC: gitHubDevStatus = "Done"
## Scope

View file

@ -48,9 +48,7 @@ whether Mode B executes for a given repository.
At session start, compare:
```
Local per-repo marker: .github/.agents/.bcquality-version (if present)
```
Local per-repo marker: .github/.agents/.bcquality-version (if present)
against the current BCQuality main SHA. If they differ (or the local marker is
missing), run Mode B reconciliation for this repository regardless of what the
@ -60,10 +58,8 @@ global `~/.claude/.bcquality-version` file says.
When a per-repo reconciliation gap is found, output exactly this before continuing:
```
⚠️ Dette repository er ikke reconciled mod seneste BCQuality-SHA, selvom den
globale versions-fil allerede er opdateret (formentlig af et andet projekt).
Kører Mode B-reconciliation for dette repo nu.
```
⚠️ Dette repository er ikke reconciled mod seneste BCQuality-SHA, selvom den
globale versions-fil allerede er opdateret (formentlig af et andet projekt).
Kører Mode B-reconciliation for dette repo nu.
Do not silently skip Mode B just because the global marker looks current.

View file

@ -42,9 +42,7 @@ before the update is considered complete.
After any Mode B run, compare the list of files in `curabis-standard.agent.md`'s
Mode B template table against:
```
Get-ChildItem .github/.agents/*.agent.md | Select-Object -ExpandProperty BaseName
```
Get-ChildItem .github/.agents/*.agent.md | Select-Object -ExpandProperty BaseName
Any template file present in the table but absent from the directory is a gap —
install it, then surface it per `claude-md-must-reference-all-agents.md` if it
@ -54,12 +52,10 @@ also needs a CLAUDE.md reference.
When a reconciliation gap is found, output exactly this before continuing:
```
⚠️ Mode B kørte, men følgende template-fil(er) blev ikke installeret:
⚠️ Mode B kørte, men følgende template-fil(er) blev ikke installeret:
- <agent-navn>.agent.md
- <agent-navn>.agent.md
Vil du have mig til at installere den/dem nu?
```
Vil du have mig til at installere den/dem nu?
Do not continue with other activity until the developer has responded.

View file

@ -1,6 +1,14 @@
---
bc-version: [all]
domain: architecture
keywords: [namespace, verification, bcapps, source-of-truth]
technologies: [al]
countries: [w1]
application-area: [all]
---
# AL Language Namespace Verification Rule
## Core Requirement
## Description
When adding variables or references to Business Central objects, agents must **verify namespaces by reading the actual source file**—not by inference or training data assumptions.

View file

@ -70,12 +70,10 @@ stale symbol cache issue — not a missing implementation.
When this situation occurs, output exactly this message before stopping:
```
WARNING: VS Code needs a refresh before I can check for real compilation errors.
WARNING: VS Code needs a refresh before I can check for real compilation errors.
Please run: Ctrl+Shift+P -> AL: Reload Extension
Please run: Ctrl+Shift+P -> AL: Reload Extension
Let me know when the refresh is done and I will re-check diagnostics.
```
Let me know when the refresh is done and I will re-check diagnostics.
Do not continue with any other activity until the developer confirms the refresh.

View file

@ -1,6 +1,14 @@
---
bc-version: [all]
domain: architecture
keywords: [pages, business-logic, codeunit, separation-of-concerns]
technologies: [al]
countries: [w1]
application-area: [all]
---
# CURABIS Architecture: Page Presentation vs. Business Logic
## Core Rule
## Description
In CURABIS codebases, pages serve exclusively as presentation layers. All business logic—including calculations, validations, and record modifications—must reside in codeunits, not in page triggers or actions. This standard is more rigorous than general Business Central guidance and applies uniformly across all CURABIS PTE applications.

View file

@ -1,6 +1,14 @@
---
bc-version: [all]
domain: architecture
keywords: [permission-set, least-privilege, tiers, security]
technologies: [al]
countries: [w1]
application-area: [all]
---
# CURABIS Architecture: Permission Sets Must Follow Least-Privilege Hierarchy
## Core Rule
## Description
Permission sets in CURABIS apps must be structured in access tiers following the least-privilege principle. Tiers must be **additive** — each tier includes the one below it via `IncludedPermissionSets`. No single permission set should bundle user-level and administrative access in a flat structure.
@ -19,87 +27,69 @@ Permission sets in CURABIS apps must be structured in access tiers following the
## Implementation Pattern
```al
permissionset 50100 "PM365 - View"
{
Access = Public;
Assignable = true;
Caption = 'Project Mgmt 365 - View';
Permissions =
tabledata "PM Project" = R,
tabledata "PM Project Task" = R,
page "PM Project List" = X,
page "PM Project Card" = X;
}
permissionset 50100 "PM365 - View"
{
Access = Public;
Assignable = true;
Caption = 'Project Mgmt 365 - View';
Permissions =
tabledata "PM Project" = R,
page "PM Project List" = X;
}
permissionset 50101 "PM365 - Edit"
{
Access = Public;
Assignable = true;
Caption = 'Project Mgmt 365 - Edit';
IncludedPermissionSets = "PM365 - View";
Permissions =
tabledata "PM Project" = RIMD,
tabledata "PM Project Task" = RIMD,
codeunit "PM Project Management" = X;
}
permissionset 50101 "PM365 - Edit"
{
Access = Public;
Assignable = true;
Caption = 'Project Mgmt 365 - Edit';
IncludedPermissionSets = "PM365 - View";
Permissions =
tabledata "PM Project" = RIMD,
tabledata "PM Project Task" = RIMD,
codeunit "PM Project Management" = X;
}
permissionset 50102 "PM365 - Admin"
{
Access = Public;
Assignable = false;
Caption = 'Project Mgmt 365 - Admin';
IncludedPermissionSets = "PM365 - Edit";
Permissions =
tabledata "PM Setup" = RIMD,
page "PM Setup" = X;
}
```
permissionset 50102 "PM365 - Admin"
{
Access = Public;
Assignable = false;
Caption = 'Project Mgmt 365 - Admin';
IncludedPermissionSets = "PM365 - Edit";
Permissions =
tabledata "PM Setup" = RIMD,
page "PM Setup" = X;
}
## Relationship to CURABIS-ARCH-011
This rule is a **companion to CURABIS-ARCH-011** (`exposed-objects-must-be-in-a-permission-set`):
- **CURABIS-ARCH-011**: Every exposed object *must exist* in at least one permission set
- **This rule**: Permission sets *themselves* must follow the hierarchical least-privilege structure
Both must be satisfied simultaneously: it is not enough that objects appear in a permission set if that set grants excessive access.
Companion to **CURABIS-ARCH-011** (`exposed-objects-must-be-in-a-permission-set`):
ARCH-011 requires every exposed object to *exist* in a permission set; this rule
requires the sets *themselves* to follow the tiered least-privilege structure.
Both must hold — objects in a set that grants excessive access is not enough.
## Anti-Pattern
```al
// Violation: flat "full access" set bundles user and admin access
permissionset 50100 "PM365 - Full Access"
{
Assignable = true;
Permissions =
tabledata "PM Project" = RIMD,
tabledata "PM Setup" = RIMD, // admin data mixed with user data
tabledata "PM Project Task" = RIMD,
codeunit "PM Post Codeunit" = X;
}
```
// Violation: flat "full access" set bundles user and admin access
permissionset 50100 "PM365 - Full Access"
{
Assignable = true;
Permissions =
tabledata "PM Project" = RIMD,
tabledata "PM Setup" = RIMD, // admin data mixed with user data
tabledata "PM Project Task" = RIMD,
codeunit "PM Post Codeunit" = X;
}
## BCApps Reference
BCApps Business Foundation defines exactly this tiered pattern:
```al
// BusFoundEdit.PermissionSet.al
permissionset 4 "Bus. Found. - Edit"
{
Access = Public;
Assignable = true;
Caption = 'Business Foundation - Edit';
IncludedPermissionSets = "Bus. Found. - View";
}
```
Microsoft uses Admin, Edit, View, Obj, and Read tiers with `IncludedPermissionSets` throughout BCApps — never a single flat "full access" set.
BCApps Business Foundation defines exactly this tiered pattern: Microsoft uses
Admin, Edit, View, Obj, and Read tiers with `IncludedPermissionSets` throughout —
never a single flat "full access" set. Each tier inherits from the tier below;
Admin sets use `Assignable = false` to prevent accidental assignment to regular
users.
- **Source:** https://github.com/microsoft/BCApps/tree/main/src/Business%20Foundation/App/Permissions
- **Files:** `BusFoundAdmin`, `BusFoundEdit`, `BusFoundView`, `BusFoundObj`, `BusFoundRead`
- **Pattern:** Each tier inherits from the tier below via `IncludedPermissionSets`. Admin sets use `Assignable = false` to prevent accidental assignment to regular users.
## Verification

View file

@ -1,11 +1,10 @@
---
name: shared-project-memory-must-be-in-repo
description: >
Project-level memory (business rules, architectural decisions, scope boundaries)
must be stored in a version-controlled projectmemory/ folder, not in a user's
local Claude memory store, so all team members benefit from shared knowledge.
layer: 2
category: architecture
bc-version: [all]
domain: architecture
keywords: [projectmemory, shared-memory, repo, team-knowledge]
technologies: [al]
countries: [w1]
application-area: [all]
---
# Shared Project Memory Must Be in the Repository
@ -22,39 +21,33 @@ a different machine. Version-controlled memory is shared, attributed, and persis
## Anti Pattern
```
# Stored only on Michael's laptop — Tod and SJG never see this
~/.claude/projects/d--MyProject/memory/project-pricing-vat-scope.md
```
# Stored only on Michael's laptop — Tod and SJG never see this
~/.claude/projects/d--MyProject/memory/project-pricing-vat-scope.md
A rule observed by one developer stays siloed. The next session on another machine —
or by another team member — starts from zero.
## Best Practice
```
# In the git repository — committed, shared, visible to all
projectmemory/
memoryupdates_mid.md ← Michael's observations
memoryupdates_tod.md ← Tod's observations
memoryupdates_sjg.md ← SJG's observations
```
# In the git repository — committed, shared, visible to all
projectmemory/
memoryupdates_mid.md ← Michael's observations
memoryupdates_tod.md ← Tod's observations
memoryupdates_sjg.md ← SJG's observations
Each file is named after the user who triggered the observation. All files are read
by every team member's Claude session at start, via an instruction in `CLAUDE.md`:
```markdown
## Shared project memory
## 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.
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, write it to
`projectmemory/memoryupdates_<username>.md` for the active user.
When you learn something project-relevant, 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.
```
User-specific preferences (tone, workflow habits) stay in the local
`~/.claude/projects/.../memory/` folder as before.
## What belongs in projectmemory vs local memory

View file

@ -1,7 +1,7 @@
---
bc-version: [all]
domain: architecture
keywords: [xliff, translation, xlf, caption, tooltip, enu, da-dk, de-de, no-nb, sv-se, de-at]
keywords: [xliff, translation, xlf, caption, tooltip, enu, da-dk, de-de, no-nb, sv-se]
technologies: [al]
countries: [w1]
application-area: [all]
@ -51,13 +51,11 @@ The following must remain in English in all locales:
## Trans-unit structure
```xml
<trans-unit id="..." size-unit="char" translate="yes" xml:space="preserve">
<source>Post</source>
<target state="translated">Bogfør</target> ← da-DK example
<note from="Developer" annotates="source" priority="2">Button caption</note>
</trans-unit>
```
<trans-unit id="..." size-unit="char" translate="yes" xml:space="preserve">
<source>Post</source>
<target state="translated">Bogfør</target> ← da-DK example
<note from="Developer" annotates="source" priority="2">Button caption</note>
</trans-unit>
State must always be `translated` — never `needs-translation` or `new`.