mirror of
https://github.com/microsoft/BCQuality.git
synced 2026-10-06 07:06:54 +01:00
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:
parent
ec2892f0ab
commit
dd5637b1db
39 changed files with 729 additions and 814 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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]]`.
|
||||
|
|
|
|||
|
|
@ -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`).
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue