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,6 +1,14 @@
---
bc-version: [all]
domain: mcp
keywords: [mcp, agent, business-process, status, write-scope]
technologies: [al]
countries: [w1]
application-area: [all]
---
# CURABIS MCP: Agents Must Not Write Business Process Status Fields
## Core Principle
## Description
MCP agents must only write developer-managed tracking fields — never fields that drive business process workflows such as invoicing, approval, or time registration. Writing a business status field from an agent can block downstream operations for users working in Business Central.
@ -22,10 +30,8 @@ Developer tracking fields are independent of BC workflow. Business process statu
## Example Agent Instruction
```
Write only gitHubDevStatus and gitHubBranch on tasks.
Never write Status — it controls the invoicing workflow.
```
Write only gitHubDevStatus and gitHubBranch on tasks.
Never write Status — it controls the invoicing workflow.
## Verification

View file

@ -1,15 +1,15 @@
---
rule-id: CURABIS-MCP-007
title: Agent must resolve developer identity from BC
category: mcp
severity: warning
applies-to: [agent-files, bc-mcp]
bc-version: [all]
domain: mcp
keywords: [mcp, s2s, developer-identity, users-api, bc]
technologies: [al]
countries: [w1]
application-area: [all]
---
# Agent must resolve developer identity from BC
## Rule
## Description
Agent files must not contain static employee-to-code mappings.
Developer identity must always be resolved at runtime from the BC users tool (PAG6102903).
@ -45,4 +45,4 @@ Static employee tables in agent files are forbidden:
## Exceptions
None. If the users tool is temporarily unavailable, say so and stop -- do not fall back
to a hardcoded table.
to a hardcoded table.

View file

@ -1,144 +1,95 @@
---
bc-version: [all]
domain: mcp
keywords: [ai-eval, scores, bc-table, hill-climbing, telemetry]
technologies: [al]
countries: [w1]
application-area: [all]
---
# CURABIS-MCP-008 — AI eval scores must be posted to the BC posting table
## Rule
## Description
When an AI agent completes a hill climbing eval iteration on a BC sub-task, all
resulting scores — compile result, test score, BCQuality score, F1 score, verdict,
and model identity — must be posted to the `CUR Project AI Score` table in Business
Central via the designated MCP tool (`bc_post_ai_score`).
Scores must **not** be stored as:
- task comments
- local files or agent memory
- inline in agent files or knowledge files
- any other location outside the BC posting table
Central via the designated MCP tool (`bc_post_ai_score`). Scores must **not** be
stored as task comments, local files, agent memory, inline in agent or knowledge
files, or any other location outside the BC posting table.
## Why
The `CUR Project AI Score` table is a **posting table**: one immutable entry per
iteration, with a clustered key on `Entry No.`. It is the single source of truth for
hill climbing history on a sub-task.
Storing scores elsewhere breaks this guarantee:
| Alternate location | Problem |
|---|---|
| Task comment | 250-char limit, unstructured, not queryable, mixed with human notes |
| Local file | Session-scoped, repo-specific, invisible to other agents and BC reporting |
| Agent memory | Volatile, not persisted between sessions |
| Hard-coded in agent file | Frozen at time of writing, violates CURABIS-MCP-007 pattern |
The BC posting table enables:
1. Reporting across tasks and projects (MatchRate over time)
2. The Court reviewing objective score data from Edison
3. The orchestrator reading prior iterations via `bc_get_ai_scores` to decide verdict
4. BC users seeing hill climbing progress directly on the sub-task
iteration, clustered on `Entry No.` — the single source of truth for hill climbing
history on a sub-task. Alternate locations all break that guarantee: task comments
are 250-char, unstructured and unqueryable; local files are session- and
repo-scoped; agent memory is volatile; scores hard-coded in agent files are frozen
at time of writing. The BC table is what enables cross-project reporting, the
Court reviewing Edison's score data, the orchestrator reading prior iterations via
`bc_get_ai_scores`, and BC users seeing progress directly on the sub-task.
## Compliant
After each eval iteration, the orchestrator calls:
```
bc_post_ai_score(
projectNo = "DEV2026-00010",
subTaskNo = "0014",
iterationNo = 3,
compile = true,
testScore = 0.80,
bcquality = 0.86,
f1Score = 0.83,
verdict = "Keep",
model = "claude-sonnet-4-6"
)
```
bc_post_ai_score(
projectNo = "DEV2026-00010",
subTaskNo = "0014",
iterationNo = 3,
compile = true,
testScore = 0.80,
bcquality = 0.86,
f1Score = 0.83,
verdict = "Keep",
model = "claude-sonnet-4-6"
)
BC sets `Eval DateTime` automatically. The orchestrator may additionally post a
brief human-readable comment ("Iteration 3: F1=0.83 → Keep") — this is allowed,
as it communicates progress; the score itself is in BC.
BC sets `Eval DateTime` automatically. A brief human-readable comment in addition
("Iteration 3: F1=0.83 → Keep") is allowed — the score itself is in BC.
## Non-compliant
```
# Storing score as task comment only
bc_add_comment(
projectNo = "DEV2026-00010",
subTaskNo = "0014",
comment = "Iter 3: compile ✅ tests 4/5 BCQ 6/7 F1=0.83 Keep"
)
# → Score is unstructured text. Not queryable. Lost to reporting.
```
# Storing score as task comment only
bc_add_comment(projectNo = "DEV2026-00010", subTaskNo = "0014",
comment = "Iter 3: compile OK tests 4/5 BCQ 6/7 F1=0.83 Keep")
# -> unstructured text; not queryable; lost to reporting
```
# Storing score in agent file
## Hill climbing log
- Iteration 1: F1=0.43 Revert
- Iteration 2: F1=0.71 Keep
- Iteration 3: F1=0.83 Keep ← frozen, session-specific, wrong location
```
# Storing score in an agent file's "Hill climbing log" section
# -> frozen, session-specific, wrong location
## False positive
An agent that posts a human-readable summary comment **in addition to** calling
`bc_post_ai_score` is **not** violating this rule. The comment is human
communication; the score is in BC. Both are permitted.
The violation is using the comment or any other location **instead of** posting to
the BC table.
Posting a human-readable summary comment **in addition to** calling
`bc_post_ai_score` is not a violation. The violation is using the comment or any
other location **instead of** the BC table.
## API reference
- Page: `CUR MCP Project AI Scores` (PAG6102906)
- Entity: `projectAIScores`
- Page: `CUR MCP Project AI Scores` (PAG6102906), entity `projectAIScores`
- Publisher: `curabis`, Group: `projectMgmt`, Version: `v2.0`
- Insert: allowed. Modify: never. Delete: never.
- `Eval DateTime` is set by BC `OnInsertRecord` — do not pass it.
## Applies to
Agent files that implement hill climbing eval loops on BC sub-tasks.
## Eval at task boundaries (hill-climbing baseline and final)
To generate meaningful hill-climbing data, the project's eval script MUST be
run at two specific moments per task:
To generate meaningful hill-climbing data, the project's eval script MUST run at
two moments per task:
| Moment | When | Verdict to post |
|---|---|---|
| **Baseline** | Before the first code change for a task | `"Baseline"` |
| **Final** | After all changes are complete, before merging to track branch | `"Final"` |
| **Final** | After all changes, before merging to track branch | `"Final"` |
The delta `Final.score - Baseline.score` is the quality impact of the task:
The delta `Final.score - Baseline.score` is the task's quality impact: positive
means improved quality; negative means technical debt was introduced (note it in
the BC task comment); zero is neutral. Never skip the baseline "because the task
is small" — without it the delta cannot be computed and history is incomplete.
- **Positive delta** -- the task improved code quality.
- **Negative delta** -- technical debt was introduced; note it in the BC task comment.
- **Zero or negligible delta** -- neutral; no action required.
Each project declares its eval script in `CLAUDE.md`; that script emits the score
posted via `bc_post_ai_score` and appends to the project's eval history.
### Project eval script
## Applies to
Each project declares its eval script in `CLAUDE.md`. That script emits a score
and appends to the project's eval history. The score posted to `bc_post_ai_score`
is the score emitted by that project-specific script.
### Non-compliant
```
# Skipping the baseline "because the task is small"
# delta cannot be computed; hill-climbing history is incomplete
```
### Compliant
```
# Task start: run eval -> post baseline
bc_post_ai_score(projectNo, subTaskNo, iterationNo, ..., verdict="Baseline")
# ... implement the task ...
# Task end (before merge): run eval -> post final
bc_post_ai_score(projectNo, subTaskNo, iterationNo, ..., verdict="Final")
```
### Scope
Applies to all tasks where the project has an eval script declared in `CLAUDE.md`.
Documentation-only tasks (no code change) are exempt.
Agent files implementing hill climbing eval loops on BC sub-tasks, and all tasks
where the project declares an eval script in `CLAUDE.md`. Documentation-only
tasks (no code change) are exempt.

View file

@ -1,6 +1,14 @@
---
bc-version: [all]
domain: mcp
keywords: [api-page, flowfield, calcfields, odata]
technologies: [al]
countries: [w1]
application-area: [all]
---
# CURABIS MCP: FlowFields on API Pages Rule Summary
## The Rule
## Description
**FlowFields on API pages must be explicitly calculated** via `CalcFields()` in the `OnAfterGetRecord` trigger, or they return empty values in OData responses.
## Key Points
@ -15,12 +23,10 @@
The provided example demonstrates proper implementation:
```al
trigger OnAfterGetRecord()
begin
Rec.CalcFields("Elapsed time (Chargeable)", "Customer Name");
end;
```
trigger OnAfterGetRecord()
begin
Rec.CalcFields("Elapsed time (Chargeable)", "Customer Name");
end;
## Verification Approach

View file

@ -1,6 +1,14 @@
---
bc-version: [all]
domain: mcp
keywords: [api-page, key-fields, editable, insert, odata]
technologies: [al]
countries: [w1]
application-area: [all]
---
# CURABIS MCP: ODataKeyFields Editability Rule
## The Rule
## Description
Key fields declared in `ODataKeyFields` cannot have `Editable = false` when the API page permits inserts and **the field is consumer-provided**. This restriction causes the OData layer to reject the field as an unknown property during POST operations.
@ -11,20 +19,16 @@ When a field is marked read-only, Business Central removes it from the OData wri
## Problematic vs. Correct Approach
**Incorrect:**
```al
field(projectNo; Rec."Project No.")
{
Editable = false; // prevents API inserts when consumer must supply the value
}
```
field(projectNo; Rec."Project No.")
{
Editable = false; // prevents API inserts when consumer must supply the value
}
**Correct:**
```al
field(projectNo; Rec."Project No.")
{
// No Editable = false — consumer supplies this on POST
}
```
field(projectNo; Rec."Project No.")
{
// No Editable = false — consumer supplies this on POST
}
## Key Takeaways

View file

@ -1,6 +1,14 @@
---
bc-version: [all]
domain: mcp
keywords: [api-page, least-privilege, write-access, odata, security]
technologies: [al]
countries: [w1]
application-area: [all]
---
# CURABIS MCP: API Pages Must Use Least-Privilege Write Access
## Core Principle
## Description
A general-purpose API page that exposes many fields should not be widened to allow writes on a single additional field. Instead, create a dedicated minimal API page that exposes only the fields the consumer needs to read and write. This limits the blast radius of any agent or integration mistake.
@ -10,26 +18,22 @@ An MCP agent operates with the permissions of its service identity, not an indiv
## Pattern to Avoid
```al
// WRONG: General page widened with write access to one field
// Now the agent can accidentally (or intentionally) write to all other fields too
field(status; Rec.Status) { } // should be read-only
field(gitHubRepository; Rec."GitHub Repository") { } // the one field we want writable
field(estimatedHours; Rec."Estimated Hours") { } // should be read-only
```
// WRONG: General page widened with write access to one field
// Now the agent can accidentally (or intentionally) write to all other fields too
field(status; Rec.Status) { } // should be read-only
field(gitHubRepository; Rec."GitHub Repository") { } // the one field we want writable
field(estimatedHours; Rec."Estimated Hours") { } // should be read-only
## Correct Pattern
Create a separate, minimal API page:
```al
page 6102904 "CUR MCP Project Repository"
{
// Only two fields: the key and the one writable field
field(no; Rec."No.") { Editable = false; }
field(gitHubRepository; Rec."GitHub Repository") { }
}
```
page 6102904 "CUR MCP Project Repository"
{
// Only two fields: the key and the one writable field
field(no; Rec."No.") { Editable = false; }
field(gitHubRepository; Rec."GitHub Repository") { }
}
## Requirements

View file

@ -1,10 +1,10 @@
---
name: bc-mcp-find-active-task-for-branch
description: >
Standard recipe for finding the BC sub-task linked to the current git branch,
including exact action names and field names for each BC MCP endpoint.
layer: 2
category: mcp
bc-version: [all]
domain: mcp
keywords: [mcp, bc-task, branch, active-task, recipe]
technologies: [al]
countries: [w1]
application-area: [all]
---
# BC MCP: Find Active Task for Branch
@ -38,16 +38,14 @@ Derived from the AL page source (EntityName property + PAG + page ID):
## Standard recipe: find task for current branch
```
1. git branch --show-current → e.g. "PriceLookup"
2. git remote get-url origin → e.g. "https://github.com/Curabis/Wareco.git"
3. bc_actions_invoke List_project_PAG6102901
filter: "gitHubRepository eq 'https://github.com/Curabis/Wareco.git'"
→ get projectNo (e.g. "W-2024-001")
4. bc_actions_invoke List_activeTask_PAG6102900
filter: "projectNo eq 'W-2024-001' and gitHubBranch eq 'PriceLookup'"
→ get taskId (global commit-message ID), taskNo, description, status
```
1. git branch --show-current → e.g. "PriceLookup"
2. git remote get-url origin → e.g. "https://github.com/Curabis/Wareco.git"
3. bc_actions_invoke List_project_PAG6102901
filter: "gitHubRepository eq 'https://github.com/Curabis/Wareco.git'"
→ get projectNo (e.g. "W-2024-001")
4. bc_actions_invoke List_activeTask_PAG6102900
filter: "projectNo eq 'W-2024-001' and gitHubBranch eq 'PriceLookup'"
→ get taskId (global commit-message ID), taskNo, description, status
If step 3 returns no project, the repo is not linked — see `[[bc-mcp-link-repo-to-project]]`.
If step 4 returns no task, the branch has no registered task — create one or ask the PM.
@ -75,15 +73,13 @@ Never write `gitHubRepository` on the task (obsolete, will be removed in v29).
The bridge runs as app identity `BC_DevelopmentMCP`. To attribute work:
```
1. git config user.email → developer's git email
2. bc_actions_invoke List_consultant_PAG50009
filter: "email eq 'mic.dieringer@gmail.com'"
→ get employeeCode (e.g. "MID")
3. Use employeeCode to filter "my tasks":
List_activeTask_PAG6102900 filter: "taskResponsible eq 'MID'"
4. Sign status comments: end with "— Michael" so attribution survives S2S
```
1. git config user.email → developer's git email
2. bc_actions_invoke List_consultant_PAG50009
filter: "email eq 'mic.dieringer@gmail.com'"
→ get employeeCode (e.g. "MID")
3. Use employeeCode to filter "my tasks":
List_activeTask_PAG6102900 filter: "taskResponsible eq 'MID'"
4. Sign status comments: end with "— Michael" so attribution survives S2S
Some developers use personal email for git but have a Curabis email as secondary
on GitHub. If the git email doesn't match, try the `@curabis.dk` variant.

View file

@ -1,16 +1,15 @@
---
name: bc-mcp-scope-tasks-to-repository
description: >
When a developer asks for their open tasks, scope the result to the current
git repository only. If no BC project is linked to the repo, raise it as a
flag instead of returning all tasks.
layer: 2
category: mcp
bc-version: [all]
domain: mcp
keywords: [mcp, bc-task, repository-scope, project]
technologies: [al]
countries: [w1]
application-area: [all]
---
# BC MCP: Scope Task Lists to the Current Repository
## Rule
## Description
When a developer asks "what tasks do I have", "what are my open tasks", or any
equivalent question about their work queue, **only return tasks that belong to
@ -29,20 +28,18 @@ wrong project.
## Standard recipe
```
1. git remote get-url origin
→ e.g. "https://github.com/Curabis/Wareco.git"
1. git remote get-url origin
→ e.g. "https://github.com/Curabis/Wareco.git"
2. List_ProjectRepositories_PAG6102904
filter: "gitHubRepository eq '<url>'"
→ get projectNo(s) for this repo
2. List_ProjectRepositories_PAG6102904
filter: "gitHubRepository eq '<url>'"
→ get projectNo(s) for this repo
3. IF no projects found → STOP and flag (see "No linked project" below)
3. IF no projects found → STOP and flag (see "No linked project" below)
4. List_ActiveTasks_PAG6102900
filter: "projectNo eq '<projectNo>' and taskResponsible eq '<employeeCode>'"
→ return only tasks in this repo's project(s)
```
4. List_ActiveTasks_PAG6102900
filter: "projectNo eq '<projectNo>' and taskResponsible eq '<employeeCode>'"
→ return only tasks in this repo's project(s)
For developer identity (resolving `employeeCode` from git email), see
`[[bc-mcp-find-active-task-for-branch]]`.

View file

@ -1,3 +1,11 @@
---
bc-version: [all]
domain: mcp
keywords: [mcp, tools, preload, session-start, bc]
technologies: [al]
countries: [w1]
application-area: [all]
---
---
rule: bc-mcp-tools-must-be-preloaded
title: BC MCP tool schemas must be pre-loaded at session start
@ -7,14 +15,12 @@ severity: required
# BC MCP tool schemas must be pre-loaded at session start
## Rule
## Description
When the `bc-mcp.agent.md` agent is invoked, the very first action must be to load
the BC MCP tool schemas via `ToolSearch` — before producing any user-visible output.
```
ToolSearch query: select:mcp__businesscentral__bc_actions_search,mcp__businesscentral__bc_actions_invoke,mcp__businesscentral__bc_actions_describe
```
ToolSearch query: select:mcp__businesscentral__bc_actions_search,mcp__businesscentral__bc_actions_invoke,mcp__businesscentral__bc_actions_describe
This call must complete before the agent responds to the user.
@ -35,16 +41,14 @@ and eliminates mid-task delays entirely.
## Correct pattern
```
# bc-mcp.agent.md session start
# bc-mcp.agent.md session start
1. ToolSearch: select:mcp__businesscentral__bc_actions_search,
mcp__businesscentral__bc_actions_invoke,
mcp__businesscentral__bc_actions_describe
2. [proceed with user request]
```
1. ToolSearch: select:mcp__businesscentral__bc_actions_search,
mcp__businesscentral__bc_actions_invoke,
mcp__businesscentral__bc_actions_describe
2. [proceed with user request]
## Scope
Applies to every invocation of `bc-mcp.agent.md` in every CURABIS project that uses
the Business Central MCP bridge (`bc-mcp-bridge.js`).
the Business Central MCP bridge (`bc-mcp-bridge.js`).

View file

@ -1,21 +1,24 @@
---
rule: CURABIS-BCMCP-008
title: Git lifecycle must sync BC subtask dev status
severity: warning
domain: git, mcp, bc-integration
applies-to: [feature branches, bugfix branches, hotfix branches]
bc-version: [all]
domain: mcp
keywords: [git, lifecycle, bc-status, dev-status, sync]
technologies: [al]
countries: [w1]
application-area: [all]
---
# Git lifecycle must sync BC subtask dev status
## Description
Every AL feature branch is linked to a BC subtask. The `gitHubDevStatus` and
`gitHubBranch` fields on the subtask must reflect the real state of the branch
at all times — automatically, without manual steps.
## Track branch
Each project declares its **track branch** in `CLAUDE.md` — the branch that is
the merge target for all feature branches in the current development track:
Each project declares its **track branch** in `CLAUDE.md` — the merge target for
all feature branches in the current development track:
| Declaration in CLAUDE.md | Meaning |
|---|---|
@ -27,33 +30,16 @@ feature branch is merged into the track branch — not necessarily `main`.
## Branch naming convention
Branches must follow this pattern so automation can parse the BC task reference:
Branches must follow this pattern so automation can parse the BC task reference —
type is `feature`/`bugfix`/`hotfix`, projectNo matches `[A-Z]{2,4}\d{4}-\d{5}`,
taskNo is a plain or zero-padded integer, description is optional:
```
<type>/<projectNo>-<taskNo>[-optional-description]
```
<type>/<projectNo>-<taskNo>[-optional-description]
| Segment | Format | Example |
| --- | --- | --- |
| type | `feature`, `bugfix`, `hotfix` | `feature` |
| projectNo | `[A-Z]{2,4}\d{4}-\d{5}` | `DEV2023-00027` |
| taskNo | zero-padded or plain integer | `004` or `4` |
| description | optional, hyphen-separated | `bc-agent-semantic-tools` |
**Valid examples:**
```
feature/DEV2023-00027-004-bc-agent-semantic-tools
bugfix/DEV2023-00027-003-odata-string-key
hotfix/DEV2023-00012-001-invoicing-crash
feature/DEV2023-00027-4
```
**Invalid (no automation):**
```
my-feature
fix-thing
DEV2023-00027
```
Valid: feature/DEV2023-00027-004-bc-agent-semantic-tools
bugfix/DEV2023-00027-003-odata-string-key
feature/DEV2023-00027-4
Invalid: my-feature / fix-thing / DEV2023-00027 (no automation)
## Status mapping
@ -66,47 +52,34 @@ DEV2023-00027
## Automated implementation (git hooks)
Automation is provided by two git hooks in `.githooks/` (activated via
`git config core.hooksPath .githooks`) that call
`Scripts/Invoke-BCGitSync.ps1`:
- `post-checkout` — detects branch creation and branch abandonment
- `post-commit` — detects commits/merges on the track branch
`Invoke-BCGitSync.ps1` calls the BC OData API directly (same credentials as
`bc-agent.js`) and never blocks the git operation — all errors are swallowed
with a warning.
Git hooks require that branch names follow the `<type>/<projectNo>-<taskNo>`
naming convention. Branches that do not follow this format are ignored by hooks.
Two git hooks in `.githooks/` (activated via `git config core.hooksPath .githooks`)
call `Scripts/Invoke-BCGitSync.ps1`: `post-checkout` detects branch creation and
abandonment; `post-commit` detects commits/merges on the track branch. The script
calls the BC OData API directly (same credentials as `bc-agent.js`), never blocks
the git operation, and ignores branches that do not follow the naming convention.
## Claude-driven synchronization
When Claude executes git operations, the git hooks may not fire — either because
hooks are not configured, or because the branch name does not follow the
`<type>/<projectNo>-<taskNo>` convention.
**Claude MUST call BC MCP explicitly at two points:**
When Claude executes git operations, the hooks may not fire — hooks unconfigured,
or branch name outside the convention. **Claude MUST call BC MCP explicitly at
two points:**
| Moment | BC MCP action |
|---|---|
| Feature branch created | `gitHubDevStatus = "In Progress"`, `gitHubBranch = <branch>` |
| Feature branch merged to track branch | `gitHubDevStatus = "Done"`, `gitHubBranch = <track-branch>` |
Steps:
1. Find the active task using the recipe in `[[bc-mcp-find-active-task-for-branch]]`
2. Call `Modify_activeTask_PAG6102900` with the two writable fields
This requirement applies regardless of branch naming format and regardless of
whether git hooks are also active. If both run, there is no conflict — they write
identical values.
Steps: find the active task using the recipe in
`[[bc-mcp-find-active-task-for-branch]]`, then call
`Modify_activeTask_PAG6102900` with the two writable fields. This applies
regardless of branch naming and regardless of whether hooks are also active —
if both run they write identical values.
## Safety rules
CURABIS-BCMCP-008 The sync script NEVER writes BC subtask `status`
(Created/Accepted/In progress/Finished/Invoiced). It only writes
`gitHubDevStatus` and `gitHubBranch`. These are the only two fields
the agent is allowed to modify (see CURABIS-BCMCP-001).
`gitHubDevStatus` and `gitHubBranch` (see CURABIS-BCMCP-001).
CURABIS-BCMCP-009 The sync script exits 0 on all errors. It must never
block a git commit, checkout, or merge. BC sync is best-effort.
@ -117,7 +90,6 @@ CURABIS-BCMCP-010 Only tasks in `activeTasks` (status = Accepted or In progress)
## BCApps reference
Branch naming conventions and git workflow integration follow the patterns used
in [microsoft/BCApps](https://github.com/microsoft/BCApps) — see
`.github/CONTRIBUTING.md` for Microsoft's own conventions on feature branches
and PR titles that reference work items.
Branch naming and git workflow integration follow
[microsoft/BCApps](https://github.com/microsoft/BCApps) conventions — see its
`.github/CONTRIBUTING.md` for feature-branch and work-item-referencing patterns.

View file

@ -1,14 +1,15 @@
---
rule: CURABIS-MCP-003
title: MCP bridge JavaScript-filer skal gemmes uden UTF-8 BOM
category: mcp
severity: high
tags: [mcp, encoding, node, bridge, windows]
bc-version: [all]
domain: mcp
keywords: [mcp, bridge, encoding, utf-8, stdio]
technologies: [al]
countries: [w1]
application-area: [all]
---
# CURABIS-MCP-003 — MCP bridge JavaScript-filer skal gemmes uden UTF-8 BOM
## Regel
## Description
JavaScript-filer der fungerer som MCP bridge-scripts (fx `bc-mcp-bridge.js`) skal gemmes med UTF-8-enkodning **uden** BOM (Byte Order Mark). En UTF-8 BOM (0xEF 0xBB 0xBF) placeret foran shebang-linjen får Node.js til at crashe med `SyntaxError: Invalid or unexpected token`, og MCP-serveren starter aldrig — uden at producere en brugbar fejlbesked til udvikleren.
@ -25,28 +26,22 @@ BOM introduceres typisk på Windows via:
**Download og gem korrekt (uden BOM):**
```powershell
$content = (Invoke-WebRequest -Uri $url -UseBasicParsing).Content
[System.IO.File]::WriteAllText($destPath, $content, [System.Text.UTF8Encoding]::new($false))
```
$content = (Invoke-WebRequest -Uri $url -UseBasicParsing).Content
[System.IO.File]::WriteAllText($destPath, $content, [System.Text.UTF8Encoding]::new($false))
**Verifikation efter gem:**
```powershell
$bytes = [System.IO.File]::ReadAllBytes($filePath)
if ($bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) {
throw "BOM detected in $filePath — file cannot be used as Node.js entry point"
}
```
$bytes = [System.IO.File]::ReadAllBytes($filePath)
if ($bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) {
throw "BOM detected in $filePath — file cannot be used as Node.js entry point"
}
**Strip af eksisterende BOM (remediation):**
```powershell
$bytes = [System.IO.File]::ReadAllBytes($path)
if ($bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) {
[System.IO.File]::WriteAllBytes($path, $bytes[3..($bytes.Length - 1)])
}
```
$bytes = [System.IO.File]::ReadAllBytes($path)
if ($bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) {
[System.IO.File]::WriteAllBytes($path, $bytes[3..($bytes.Length - 1)])
}
## Hvad der IKKE må ske
@ -63,18 +58,14 @@ Setup scripts der installerer MCP bridge-filer (fx curabis-standard.agent.md) sk
Symptom: MCP-server er konfigureret i `.mcp.json`, men eksponerer ingen tools i sessionen.
Diagnose:
```powershell
# Tjek første bytes
$b = [System.IO.File]::ReadAllBytes("path\to\bridge.js")
"0x{0:X2} 0x{1:X2} 0x{2:X2}" -f $b[0], $b[1], $b[2]
# Hvis output er "0xEF 0xBB 0xBF" er BOM årsagen
```
# Tjek første bytes
$b = [System.IO.File]::ReadAllBytes("path\to\bridge.js")
"0x{0:X2} 0x{1:X2} 0x{2:X2}" -f $b[0], $b[1], $b[2]
# Hvis output er "0xEF 0xBB 0xBF" er BOM årsagen
```bash
# Kør bridge direkte og se om Node.js fejler
node path/to/bridge.js 2>&1 | head -5
```
# Kør bridge direkte og se om Node.js fejler
node path/to/bridge.js 2>&1 | head -5
## Evidens
Observeret i to separate projekter inden for én uge (2026-06-28). I begge tilfælde var BC MCP-tools utilgængelige i alle sessioner. Fejlen kræver manuel byte-inspektion at diagnosticere.
Observeret i to separate projekter inden for én uge (2026-06-28). I begge tilfælde var BC MCP-tools utilgængelige i alle sessioner. Fejlen kræver manuel byte-inspektion at diagnosticere.

View file

@ -1,14 +1,15 @@
---
rule: mcp-config-must-not-hardcode-developer-paths
title: Shared MCP configuration must not hardcode developer-specific paths
category: mcp
severity: error
version: 1
bc-version: [all]
domain: mcp
keywords: [mcp-config, hardcoded-paths, userprofile, portability]
technologies: [al]
countries: [w1]
application-area: [all]
---
# Shared MCP configuration must not hardcode developer-specific paths
## Rule
## Description
A git-committed `.mcp.json` must not hardcode an absolute path that is specific to
one developer's machine — a repository clone location on a particular drive or
@ -39,20 +40,18 @@ Claude Code expands two forms of variable inside `.mcp.json`'s `command`, `args`
Example:
```json
{
"mcpServers": {
"example": {
"command": "powershell",
"args": ["-File", "${CLAUDE_PROJECT_DIR:-.}\\.vscode\\launch-server.ps1"]
},
"bridge": {
"command": "node",
"args": ["${USERPROFILE}\\.claude\\bridge.js"]
{
"mcpServers": {
"example": {
"command": "powershell",
"args": ["-File", "${CLAUDE_PROJECT_DIR:-.}\\.vscode\\launch-server.ps1"]
},
"bridge": {
"command": "node",
"args": ["${USERPROFILE}\\.claude\\bridge.js"]
}
}
}
}
}
```
## What NOT to do

View file

@ -1,14 +1,15 @@
---
rule: mcp-server-must-be-verified-at-session-start
title: MCP server availability must be verified at session start
category: mcp
severity: error
version: 1
bc-version: [all]
domain: mcp
keywords: [mcp-server, verification, session-start]
technologies: [al]
countries: [w1]
application-area: [all]
---
# MCP server availability must be verified at session start
## Rule
## Description
When an MCP server is configured in `.mcp.json`, the agent must at session start verify
that the server's tools appear in the active deferred-tools list. If they are missing,
@ -52,4 +53,4 @@ At session start, before using any MCP-dependent tool:
## Applies to
All CURABIS projects that configure MCP servers in `.mcp.json`.
All CURABIS projects that configure MCP servers in `.mcp.json`.

View file

@ -1,14 +1,15 @@
---
rule: mcp-tool-invocation-must-be-documented
title: MCP tool documentation must include the invocation model
category: mcp
severity: warning
version: 1
bc-version: [all]
domain: mcp
keywords: [mcp, tool-invocation, documentation, transparency]
technologies: [al]
countries: [w1]
application-area: [all]
---
# MCP tool documentation must include the invocation model
## Rule
## Description
An MCP agent's documentation must describe the actual invocation model — including
whether a tool call is direct or wrapped via a generic action tool with a parameter value.
@ -47,4 +48,4 @@ value passed to `bc_actions_invoke`, this documentation will cause agents to fai
## Applies to
Any CURABIS agent documentation that describes how to use an MCP tool.
Any CURABIS agent documentation that describes how to use an MCP tool.

View file

@ -1,15 +1,21 @@
---
bc-version: [all]
domain: mcp
keywords: [api-page, derived-fields, exposure, odata]
technologies: [al]
countries: [w1]
application-area: [all]
---
# CURABIS MCP: Stored Derived Fields Must Be Recalculated in OnAfterGetRecord
## Core Principle
## Description
A stored field whose value is derived from other fields via `OnValidate` triggers can be stale. When the source data changes (e.g., new time entries posted), the stored derived field is not updated automatically — it only recalculates when a specific trigger fires. Exposing such a field directly via an API page returns a value that may be hours, days, or weeks out of date.
## Pattern to Avoid
```al
// WRONG: Exposes the stored snapshot — may be stale
field(timeLeft; Rec."Time left") { }
```
// WRONG: Exposes the stored snapshot — may be stale
field(timeLeft; Rec."Time left") { }
`"Time left"` is recalculated only when `"Estimated time"` is validated. If new time entries are posted, the stored value does not update.
@ -17,20 +23,18 @@ field(timeLeft; Rec."Time left") { }
Recalculate in `OnAfterGetRecord` using a page variable:
```al
trigger OnAfterGetRecord()
begin
Rec.CalcFields("Elapsed time (Chargeable)");
TimeLeftCalc := Rec."Estimated time" - Rec."Elapsed time (Chargeable)";
end;
trigger OnAfterGetRecord()
begin
Rec.CalcFields("Elapsed time (Chargeable)");
TimeLeftCalc := Rec."Estimated time" - Rec."Elapsed time (Chargeable)";
end;
var
TimeLeftCalc: Decimal;
var
TimeLeftCalc: Decimal;
// In layout:
field(timeLeft; TimeLeftCalc) { } // live value
field(elapsedTime; Rec."Elapsed time (Chargeable)") { } // source FlowField
```
// In layout:
field(timeLeft; TimeLeftCalc) { } // live value
field(elapsedTime; Rec."Elapsed time (Chargeable)") { } // source FlowField
## Requirements

View file

@ -1,14 +1,15 @@
---
id: CURABIS-MCP-SHEBANG-001
title: Shebang-integritet ved deploy af script-filer
category: mcp
severity: error
applies-to: [claude-code, windows, mcp-setup]
bc-version: [all]
domain: mcp
keywords: [write, shebang, script-integrity, encoding]
technologies: [al]
countries: [w1]
application-area: [all]
---
# Shebang-integritet ved deploy af script-filer
## Regel
## Description
Når en script-fil med shebang-linje (`.js`, `.sh`, `.ps1`) skrives via Claude Codes
`Write`-værktøj på Windows, skal linje 1 i den deployede fil verificeres umiddelbart
@ -16,9 +17,7 @@ efter skrivning.
Den verificerede linje skal matche den forventede shebang præcist, f.eks.:
```
#!/usr/bin/env node
```
#!/usr/bin/env node
## Baggrund
@ -39,11 +38,9 @@ Brugeren ser ingen fejlbesked i Claude Code — kaldet afvises blot.
## Verifikation (påkrævet efter enhver write af script-fil)
```python
with open(deployed_path, "r", encoding="utf-8") as f:
line1 = f.readline().rstrip()
assert line1 == expected_shebang, f"Shebang fejl: forventet {expected_shebang!r}, fik {line1!r}"
```
with open(deployed_path, "r", encoding="utf-8") as f:
line1 = f.readline().rstrip()
assert line1 == expected_shebang, f"Shebang fejl: forventet {expected_shebang!r}, fik {line1!r}"
Alternativt med Read-værktøjet: læs linje 1 og sammenlign med forventet shebang.
Stop setup-processen og genskriv filen hvis de ikke matcher.