diff --git a/README.md b/README.md index 9b71c43..a5ebc63 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Skills define how agents consume knowledge. They come in three flavors: READ and DO are read on demand — typically when the first dispatched action skill runs. They are not prerequisites for invoking Entry. WRITE is only used when scaffolding new content. -- **Action skills** — concrete skills that follow the Action Skill template to do real work (review code, audit telemetry, etc.). Action skills live inside the layers that own them (`/microsoft/skills/`, `/community/skills/`, `/custom/skills/`). An action skill is either a **leaf** that evaluates knowledge files directly, or a **super-skill** that composes other action skills (declared via `sub-skills` in frontmatter). The canonical reference is [`microsoft/skills/review/al-code-review.md`](microsoft/skills/review/al-code-review.md) (super-skill), which composes seven leaf skills under [`microsoft/skills/review/`](microsoft/skills/review/) — one per knowledge domain (performance, security, privacy, upgrade, style, UI, error handling). +- **Action skills** — concrete skills that follow the Action Skill template to do real work (review code, audit telemetry, etc.). Action skills live inside the layers that own them (`/microsoft/skills/`, `/community/skills/`, `/custom/skills/`). An action skill is either a **leaf** that evaluates knowledge files directly, or a **super-skill** that composes other action skills (declared via `sub-skills` in frontmatter). The canonical reference is [`microsoft/skills/review/al-code-review.md`](microsoft/skills/review/al-code-review.md) (super-skill), which composes eight leaf skills under [`microsoft/skills/review/`](microsoft/skills/review/) — one per knowledge domain (performance, security, privacy, upgrade, style, UI, error handling, interfaces). ### Agent bootstrapping diff --git a/microsoft/knowledge/interfaces/assign-codeunit-to-interface-for-testability.bad.al b/microsoft/knowledge/interfaces/assign-codeunit-to-interface-for-testability.bad.al new file mode 100644 index 0000000..6c3a332 --- /dev/null +++ b/microsoft/knowledge/interfaces/assign-codeunit-to-interface-for-testability.bad.al @@ -0,0 +1,22 @@ +codeunit 50217 "Standard Discount Calc Bad" +{ + procedure CalculateDiscount(Amount: Decimal): Decimal + begin + if Amount > 1000 then + exit(Amount * 0.1); + exit(0); + end; +} + +codeunit 50216 "Order Total Bad" +{ + // Anti-pattern: the dependency is a concrete codeunit type, so a test + // cannot substitute a double - it always runs the production rule. + var + DiscountCalc: Codeunit "Standard Discount Calc Bad"; + + procedure NetAmount(Amount: Decimal): Decimal + begin + exit(Amount - DiscountCalc.CalculateDiscount(Amount)); + end; +} diff --git a/microsoft/knowledge/interfaces/assign-codeunit-to-interface-for-testability.good.al b/microsoft/knowledge/interfaces/assign-codeunit-to-interface-for-testability.good.al new file mode 100644 index 0000000..e709d22 --- /dev/null +++ b/microsoft/knowledge/interfaces/assign-codeunit-to-interface-for-testability.good.al @@ -0,0 +1,51 @@ +interface IDiscountCalculation +{ + procedure CalculateDiscount(Amount: Decimal): Decimal; +} + +codeunit 50213 "Standard Discount Calc" implements IDiscountCalculation +{ + procedure CalculateDiscount(Amount: Decimal): Decimal + begin + // Production rule: 10% off amounts over 1000. + if Amount > 1000 then + exit(Amount * 0.1); + exit(0); + end; +} + +codeunit 50214 "Test Discount Calc" implements IDiscountCalculation +{ + // Lightweight test double: a fixed, predictable value so a test can assert + // order totals without depending on the production discount rule. + procedure CalculateDiscount(Amount: Decimal): Decimal + begin + exit(100); + end; +} + +codeunit 50215 "Order Total" +{ + var + DiscountCalc: Interface IDiscountCalculation; + + // Production wiring: a codeunit assigns directly to the interface variable. + procedure UseProductionCalculation() + var + StdCalc: Codeunit "Standard Discount Calc"; + begin + DiscountCalc := StdCalc; + end; + + // Setter injection: a test passes "Test Discount Calc" instead, with no + // enum and no change to the consumer. The dependency is an interface. + procedure SetDiscountCalculation(NewDiscountCalc: Interface IDiscountCalculation) + begin + DiscountCalc := NewDiscountCalc; + end; + + procedure NetAmount(Amount: Decimal): Decimal + begin + exit(Amount - DiscountCalc.CalculateDiscount(Amount)); + end; +} diff --git a/microsoft/knowledge/interfaces/assign-codeunit-to-interface-for-testability.md b/microsoft/knowledge/interfaces/assign-codeunit-to-interface-for-testability.md new file mode 100644 index 0000000..e0d50d7 --- /dev/null +++ b/microsoft/knowledge/interfaces/assign-codeunit-to-interface-for-testability.md @@ -0,0 +1,26 @@ +--- +bc-version: [16..] +domain: interfaces +keywords: [interface, dependency-injection, testability, test-double, codeunit, polymorphism, mocking] +technologies: [al] +countries: [w1] +application-area: [all] +--- + +# Assign a codeunit to an interface variable for injectable, testable dependencies + +## Description + +An interface variable can hold any codeunit that `implements` the interface, assigned directly — no enum is required. That is the lever for dependency injection in AL: a consumer depends on the interface, production code injects the real codeunit, and a test injects a lightweight double that returns predictable values. A consumer that instead `var`-declares a concrete `Codeunit` type hardwires the dependency, so a test is forced to exercise the real logic — external calls, posting, and all. Interfaces arrived in Business Central 2020 release wave 1; LLMs still default to concrete codeunit variables and miss the seam that makes code testable. + +## Best Practice + +Declare the dependency as an `Interface` variable on the consumer and supply the implementation from outside — typically setter injection through a procedure that takes an `Interface` parameter, or a parameter on the entry method. Production passes the real implementation codeunit; a test passes a test-double codeunit that implements the same interface with deterministic behaviour. Because a codeunit assigns to an interface variable directly, no enum or factory is needed for the injectable case. The consumer's logic is then verifiable in isolation. + +See sample: `assign-codeunit-to-interface-for-testability.good.al`. + +## Anti Pattern + +A consumer that declares its dependency as a concrete `Codeunit "..."` variable and calls it directly. The collaborator cannot be substituted, so a unit test either runs the production side effects or cannot cover the consumer at all. Detection signal: a `var` of type `Codeunit ""` used for a collaborator that has — or could have — an interface, especially one that performs I/O, posting, or external calls. Extract an interface, depend on the interface variable, and inject the implementation. + +See sample: `assign-codeunit-to-interface-for-testability.bad.al`. diff --git a/microsoft/knowledge/interfaces/prefer-interface-over-case-branching.bad.al b/microsoft/knowledge/interfaces/prefer-interface-over-case-branching.bad.al new file mode 100644 index 0000000..81d7727 --- /dev/null +++ b/microsoft/knowledge/interfaces/prefer-interface-over-case-branching.bad.al @@ -0,0 +1,32 @@ +enum 50204 "Shipping Method Bad" +{ + Extensible = true; + + value(0; Standard) { } + value(1; Express) { } +} + +codeunit 50205 "Shipping Charge Bad" +{ + // Anti-pattern: every call site must 'case' over the enum, and every new + // shipping method forces a synchronized edit to each of these blocks. + procedure GetRate(Method: Enum "Shipping Method Bad"; Weight: Decimal): Decimal + begin + case Method of + Method::Standard: + exit(Weight * 1.5); + Method::Express: + exit((Weight * 1.5) + 25); + end; + end; + + procedure GetDeliveryDays(Method: Enum "Shipping Method Bad"): Integer + begin + case Method of + Method::Standard: + exit(5); + Method::Express: + exit(1); + end; + end; +} diff --git a/microsoft/knowledge/interfaces/prefer-interface-over-case-branching.good.al b/microsoft/knowledge/interfaces/prefer-interface-over-case-branching.good.al new file mode 100644 index 0000000..48d2993 --- /dev/null +++ b/microsoft/knowledge/interfaces/prefer-interface-over-case-branching.good.al @@ -0,0 +1,47 @@ +interface IShippingRate +{ + procedure CalculateRate(Weight: Decimal): Decimal; +} + +codeunit 50200 "Standard Shipping Rate" implements IShippingRate +{ + procedure CalculateRate(Weight: Decimal): Decimal + begin + exit(Weight * 1.5); + end; +} + +codeunit 50201 "Express Shipping Rate" implements IShippingRate +{ + procedure CalculateRate(Weight: Decimal): Decimal + begin + exit((Weight * 1.5) + 25); + end; +} + +enum 50202 "Shipping Method" implements IShippingRate +{ + Extensible = true; + + value(0; Standard) + { + Implementation = IShippingRate = "Standard Shipping Rate"; + } + value(1; Express) + { + Implementation = IShippingRate = "Express Shipping Rate"; + } +} + +codeunit 50203 "Shipping Charge" +{ + // Dispatch is automatic: assign the enum to the interface variable and call. + // A new method = one new enum value + one impl codeunit, with no edit here. + procedure GetRate(Method: Enum "Shipping Method"; Weight: Decimal): Decimal + var + RateProvider: Interface IShippingRate; + begin + RateProvider := Method; + exit(RateProvider.CalculateRate(Weight)); + end; +} diff --git a/microsoft/knowledge/interfaces/prefer-interface-over-case-branching.md b/microsoft/knowledge/interfaces/prefer-interface-over-case-branching.md new file mode 100644 index 0000000..337e0dc --- /dev/null +++ b/microsoft/knowledge/interfaces/prefer-interface-over-case-branching.md @@ -0,0 +1,26 @@ +--- +bc-version: [16..] +domain: interfaces +keywords: [interface, enum-implements-interface, polymorphism, implementation-property, case-statement, variant-behavior, dispatch] +technologies: [al] +countries: [w1] +application-area: [all] +--- + +# Prefer an interface with enum-backed implementation over a case statement for variant behaviour + +## Description + +When behaviour varies by a discrete "type" — a shipping method, a posting strategy, a payment provider — the obvious first draft is a `case` over an enum with one branch per variant. That branch logic gets copied to every call site, and every new variant means editing all of them. AL interfaces (Business Central 2020 release wave 1) combined with enum-with-implementation replace that with automatic dispatch: an `interface` declares the contract, an `enum` that `implements` it maps each value to a codeunit, and the consumer assigns the enum value to an interface variable and calls the method. Adding a variant becomes a new enum value plus a new implementation codeunit — zero consumer edits. LLMs trained on older AL reach for the `case` block by default and rarely model a variant set as an interface. + +## Best Practice + +Declare an `interface` with the method signatures only (no bodies). Define an `enum` that `implements` the interface and set `Implementation = = ;` on each value, pointing at a codeunit that `implements` the same interface. In the consumer, declare a variable of the interface type, assign the enum value to it, and call the method — the platform dispatches to the codeunit mapped to that value. New variants plug in by adding an enum value and its implementation; existing call sites are untouched. The open/closed boundary lives at the enum, not scattered across `case` blocks. + +See sample: `prefer-interface-over-case-branching.good.al`. + +## Anti Pattern + +A `case "Shipping Method" of` block that selects behaviour inline, duplicated across the call sites that need it. Each new method forces a synchronized edit to every block, and a missed branch is a silent gap. Detection signal: a `case` statement over an enum value whose branches choose between variant computations or strategies, especially when the same shape appears in more than one procedure. Replace the enum with one that `implements` an interface, move each branch body into an implementation codeunit, and let dispatch happen through an interface variable. + +See sample: `prefer-interface-over-case-branching.bad.al`. diff --git a/microsoft/knowledge/interfaces/set-defaultimplementation-on-enum.bad.al b/microsoft/knowledge/interfaces/set-defaultimplementation-on-enum.bad.al new file mode 100644 index 0000000..1372235 --- /dev/null +++ b/microsoft/knowledge/interfaces/set-defaultimplementation-on-enum.bad.al @@ -0,0 +1,39 @@ +interface INotifier +{ + procedure Send(Recipient: Text; Body: Text): Boolean; +} + +codeunit 50210 "Email Notifier Bad" implements INotifier +{ + procedure Send(Recipient: Text; Body: Text): Boolean + begin + exit(Recipient <> ''); + end; +} + +enum 50211 "Notification Channel Bad" implements INotifier +{ + Extensible = true; + // No DefaultImplementation declared. + + value(0; Email) + { + Implementation = INotifier = "Email Notifier Bad"; + } + value(1; None) + { + // No Implementation here and no enum-level DefaultImplementation: + // resolving this value to INotifier and calling Send fails at runtime. + } +} + +codeunit 50212 "Notification Dispatch Bad" +{ + procedure Notify(Channel: Enum "Notification Channel Bad"; Recipient: Text; Body: Text): Boolean + var + Notifier: Interface INotifier; + begin + Notifier := Channel; // Channel::None has no implementation + exit(Notifier.Send(Recipient, Body)); // runtime failure for the None value + end; +} diff --git a/microsoft/knowledge/interfaces/set-defaultimplementation-on-enum.good.al b/microsoft/knowledge/interfaces/set-defaultimplementation-on-enum.good.al new file mode 100644 index 0000000..a3217d5 --- /dev/null +++ b/microsoft/knowledge/interfaces/set-defaultimplementation-on-enum.good.al @@ -0,0 +1,49 @@ +interface INotifier +{ + procedure Send(Recipient: Text; Body: Text): Boolean; +} + +codeunit 50206 "Email Notifier" implements INotifier +{ + procedure Send(Recipient: Text; Body: Text): Boolean + begin + // A real implementation would hand the message to an email service. + exit(Recipient <> ''); + end; +} + +codeunit 50207 "Default Notifier" implements INotifier +{ + procedure Send(Recipient: Text; Body: Text): Boolean + begin + // Safe fallback so an unmapped or future channel still resolves to a + // usable object instead of failing where the interface is called. + exit(false); + end; +} + +enum 50208 "Notification Channel" implements INotifier +{ + Extensible = true; + DefaultImplementation = INotifier = "Default Notifier"; + + value(0; Email) + { + Implementation = INotifier = "Email Notifier"; + } + value(1; None) + { + // No explicit Implementation: resolves to DefaultImplementation above. + } +} + +codeunit 50209 "Notification Dispatch" +{ + procedure Notify(Channel: Enum "Notification Channel"; Recipient: Text; Body: Text): Boolean + var + Notifier: Interface INotifier; + begin + Notifier := Channel; + exit(Notifier.Send(Recipient, Body)); + end; +} diff --git a/microsoft/knowledge/interfaces/set-defaultimplementation-on-enum.md b/microsoft/knowledge/interfaces/set-defaultimplementation-on-enum.md new file mode 100644 index 0000000..92ef3cf --- /dev/null +++ b/microsoft/knowledge/interfaces/set-defaultimplementation-on-enum.md @@ -0,0 +1,26 @@ +--- +bc-version: [16..] +domain: interfaces +keywords: [interface, defaultimplementation, enum-implements-interface, fallback, extensible-enum, implementation-property] +technologies: [al] +countries: [w1] +application-area: [all] +--- + +# Set DefaultImplementation on an enum so an unmapped value still resolves to an interface + +## Description + +An `enum` that `implements` an interface maps each value to a codeunit through the `Implementation` property. But an extensible enum can carry values that set no `Implementation` — values added later by an extension, or a value left intentionally blank. Assigning such a value to an interface variable and calling a method on it fails at runtime unless the enum provides a fallback. The enum-level `DefaultImplementation` property names the codeunit used whenever a value has no explicit `Implementation`, so resolution always yields a usable object. LLMs are generally unaware this property exists and leave the gap open. + +## Best Practice + +On any extensible enum that implements an interface, set `DefaultImplementation = = ;` at the enum level, pointing at a safe implementation that does nothing harmful. Values with their own `Implementation` keep using it; every other value — including ones added later by extensions — resolves to the default instead of failing. For the distinct case of an out-of-range integer that matches no declared value, pair it with `UnknownValueImplementation`. The result is that a consumer can assign any enum value to the interface variable and call through it without a runtime guard. + +See sample: `set-defaultimplementation-on-enum.good.al`. + +## Anti Pattern + +An extensible `enum ... implements ` where at least one value sets no `Implementation` and the enum declares no `DefaultImplementation`. Code that assigns that value to an interface variable and invokes a method throws at the call site, and because the enum is extensible the failing value can be introduced by a third party long after the consumer ships. Detection signal: an enum that implements an interface, has a `value(...)` with no `Implementation`, and no enum-level `DefaultImplementation`. Add a `DefaultImplementation` mapping to close the gap. + +See sample: `set-defaultimplementation-on-enum.bad.al`. diff --git a/microsoft/skills/review/al-code-review.md b/microsoft/skills/review/al-code-review.md index 80bd19c..0388b79 100644 --- a/microsoft/skills/review/al-code-review.md +++ b/microsoft/skills/review/al-code-review.md @@ -3,7 +3,7 @@ kind: action-skill id: al-code-review version: 1 title: AL code review -description: Reviews AL source changes by composing the AL review leaf skills (performance, security, privacy, upgrade, style, UI, error handling). +description: Reviews AL source changes by composing the AL review leaf skills (performance, security, privacy, upgrade, style, UI, error handling, interfaces). inputs: [pr-diff, file-path] outputs: [findings-report] bc-version: [all] @@ -18,6 +18,7 @@ sub-skills: - microsoft/skills/review/al-style-review.md - microsoft/skills/review/al-ui-review.md - microsoft/skills/review/al-error-handling-review.md + - microsoft/skills/review/al-interfaces-review.md --- # AL code review @@ -39,6 +40,7 @@ The sub-skills invoked by this skill are those listed in frontmatter `sub-skills - `microsoft/skills/review/al-style-review.md` - `microsoft/skills/review/al-ui-review.md` - `microsoft/skills/review/al-error-handling-review.md` +- `microsoft/skills/review/al-interfaces-review.md` Additional leaf skills (for example, telemetry, testing) are added by updating the `sub-skills` list. The skill does not discover sub-skills implicitly. diff --git a/microsoft/skills/review/al-interfaces-review.md b/microsoft/skills/review/al-interfaces-review.md new file mode 100644 index 0000000..c859b75 --- /dev/null +++ b/microsoft/skills/review/al-interfaces-review.md @@ -0,0 +1,136 @@ +--- +kind: action-skill +id: al-interfaces-review +version: 1 +title: AL interfaces review +description: Reviews AL source changes against interface and enum-with-implementation guidance from BCQuality. +inputs: [pr-diff, file-path] +outputs: [findings-report] +bc-version: [all] +technologies: [al] +countries: [w1] +application-area: [all] +--- + +# AL interfaces review + +Reviews AL source changes against the `interfaces` knowledge domain in BCQuality and emits a findings report. This is a leaf action skill: it invokes no sub-skills. It is one of the skills composed by `al-code-review`. + +An orchestrator invokes this skill with either a `pr-diff` (the standard PR-review entry point) or a `file-path` (single-file review). The skill produces a single JSON document conforming to the DO output contract. + +## Source + +Read the BCQuality knowledge index once — the `knowledge-index.json` BCQuality builds at the root of the knowledge checkout (Entry's preparation step regenerates it over the live, already-filtered clone — see `skills/entry.md`). It lists every article that survived layer and allow/deny filtering and carries, per article, its `path`, `layer`, `domain`, frontmatter dimensions, `keywords`, `title`, and a one-line `description` hint — exactly the fields Relevance and Worklist consume. Take the index entries whose `domain` is `interfaces` as this skill's candidate set across every enabled layer; do not open the individual article files at this step. Open an article's full body only once it enters the Worklist below, so a review reads the index plus the handful of worklisted articles instead of every file under `*/knowledge/interfaces/**`. + +## Relevance + +Apply the frontmatter matching rules defined in READ (*Frontmatter matching semantics*) against the task context: + +- `bc-version` — the target BC version from the PR branch's `app.json` or the orchestrator-supplied version. Interface guidance is gated at Business Central 2020 release wave 1 (BC16), so a target below 16 discards it. If unavailable, the dimension is `unknown`. +- `technologies` — `[al]`. +- `countries` — the countries declared in the consuming app's `app.json`. Default to the orchestrator's configured context; if absent, `unknown`. +- `application-area` — the union of application areas declared by the changed objects. Pass the actual set; do not substitute `[all]`. If the area cannot be determined from the changes, the dimension is `unknown`. + +Discard files that are not applicable. Retain conditionally applicable files (any dimension `unknown`) only when the orchestrator's configuration permits them; findings derived from those files MUST have `confidence` no higher than `medium`, AND the finding's `message` MUST name the dimension or dimensions that were unknown. + +## Worklist + +Narrow the relevant files to the subset that applies to the changes under review. For each relevant file, compute overlap against: + +- The changed AL object names and types — especially `interface` objects, codeunits and enums declared with the `implements` keyword, and consumers that declare or assign an `Interface` variable. +- The changed procedures and triggers, weighted toward factory or dispatch routines that resolve a variant to behaviour, setter-injection procedures that take an `Interface` parameter, and `case`-over-enum blocks that select between strategies. +- Tokens extracted from the diff that relate to interfaces and enum-backed implementation (`interface`, `implements`, `Implementation`, `DefaultImplementation`, `UnknownValueImplementation`, `enum`, `Extensible`, `Interface`, `case`, and the `case of` anti-pattern signal — a `case` over an enum value whose branches choose between variant computations). + +A file enters the candidate worklist when its `keywords` intersect the extracted tokens or its topic (derived from the index entry's `path`, `title`, and `description`) matches a changed object type. Read an article's full file — its `## Best Practice` / `## Anti Pattern` bodies — only after it makes the worklist; candidate selection uses the index alone. + +Once the candidate worklist is known, resolve layer-precedence conflicts per READ. Drop lower-precedence files whose normative guidance (`## Best Practice` or `## Anti Pattern`) directly contradicts a higher-precedence candidate, and record each dropped file in `suppressed` with `reason: "layer-precedence"`. Files that would have been candidates but are hidden because their layer is disabled in consumer configuration are recorded with `reason: "configuration"`. Files that never became candidates are NOT recorded in `suppressed`. + +When the post-conflict worklist is empty because no applicable interfaces knowledge exists, or because configuration suppressed every candidate, emit `outcome: "no-knowledge"`. When the worklist is empty because no applicable interfaces knowledge matched the changes, emit `outcome: "completed"` with an empty `findings` array. + +## Action + +For each worklist entry, evaluate the diff against the file's `## Best Practice` and `## Anti Pattern` sections. Emit findings as follows: + +- When the diff contains a clear match for an Anti Pattern, emit a finding with severity `major` or `blocker`, a message summarizing the anti-pattern, `location` pointing to the offending line or range, and a `references` entry pointing to the knowledge file. Use `blocker` only when the knowledge file states the anti-pattern violates a platform-level guarantee. When the file does not make such a claim, the ceiling is `major`. +- When the diff contains code that contradicts a Best Practice without being a full anti-pattern, emit `minor` with the same reference shape. +- When the skill cannot detect a violation but the file is clearly applicable to the change, emit `info` citing the file. Repository-wide observations MAY omit `location`. + +Set `confidence` to: + +- `high` when the detection is based on an unambiguous pattern match (identifier, syntax, object type). +- `medium` when detection relies on heuristics or when any frontmatter dimension was `unknown`. +- `low` when the finding is an advisory derived only from applicability. + +After evaluating each worklist entry, also consider whether the diff exhibits an interfaces defect the agent recognises from its general AL knowledge that no knowledge file in the worklist covers. Such candidates are agent findings within this skill's domain — emit them with `references: []`, an `id` slug prefixed with `agent:`, `confidence` capped at `medium`, `severity` capped at `minor` (agent findings are advisory and non-gating), and a `message` that is self-contained (describing both the issue and a concrete recommendation, since there is no knowledge-file footer for the consumer to fall back on). Hold every candidate to the precision bar in `skills/do.md` (*Agent findings*): emit only a concrete, material interfaces defect a knowledgeable BC reviewer would agree is wrong — steelman it first and drop anything stylistic, speculative, dependent on code outside the diff, or merely a valid alternative; when in doubt, omit. The scope is strictly interfaces and enum-with-implementation; defects outside this domain belong to other leaves and MUST NOT be emitted here. Before emitting, check the worklist for a knowledge file that matches the candidate — if one exists, upgrade the candidate to a knowledge-backed finding instead. See `skills/do.md` for the full contract. + +For every emitted finding, decide whether the fix is mechanical. A fix is mechanical when it is small, local, and unambiguous from the diff context (for example: add a `DefaultImplementation` mapping to an extensible enum that implements an interface; add the `Implementation` property to a new enum value; change a concrete `Codeunit` collaborator variable to its `Interface` type). For mechanical findings, emit `findings[].suggested-code` with the literal replacement for the source lines indicated by `location`. The payload must be a verbatim replacement — no diff markers, no fences, no commentary — that the consumer can render as a one-click suggestion. When a `.good.al` companion exists and the diff context matches the `.bad.al` shape, adapt the `.good.al` replacement into `suggested-code`. + +Omit `suggested-code` only when the appropriate fix depends on context the skill cannot determine, when multiple defensible replacements exist, or when the fix spans non-contiguous code. If a finding is mechanical-looking but you omit `suggested-code`, set `findings[].suggested-code-omission-reason` to a short explanation. See `skills/do.md` for the full contract. + +Outcome selection: + +- `completed` — the skill evaluated every worklist item; default when the skill finishes normally, including when the resulting `findings` array is empty. +- `no-knowledge` — no applicable interfaces knowledge survived Source, Relevance, configuration filtering, and conflict resolution. `findings` is empty. +- `not-applicable` — the task context lacks an AL dimension (no AL changes in the diff, or `technologies` filter rejected the task). +- `partial` — a time or token budget was hit before the worklist was exhausted. `summary.coverage` reflects the evaluated subset; `outcome-reason` explains the cause. +- `failed` — an unrecoverable error occurred. `outcome-reason` is required. + +## Output + +Output conforms to the DO output contract. A populated example: + +```json +{ + "skill": { "id": "al-interfaces-review", "version": 1 }, + "outcome": "completed", + "summary": { + "counts": { "blocker": 0, "major": 1, "minor": 1, "info": 0 }, + "coverage": { "worklist-size": 2, "items-evaluated": 2 } + }, + "findings": [ + { + "id": "microsoft/knowledge/interfaces/prefer-interface-over-case-branching.md", + "severity": "major", + "message": "Behaviour is selected with a 'case' over the Shipping Method enum, and the same shape is duplicated in a second procedure. Model the enum as one that implements an interface and dispatch through an interface variable so new methods do not edit every call site.", + "location": { + "file": "src/Shipping/ShippingCharge.Codeunit.al", + "line": 22, + "range": { "start-line": 22, "end-line": 31 } + }, + "references": [ + { "path": "microsoft/knowledge/interfaces/prefer-interface-over-case-branching.md" } + ], + "confidence": "high" + }, + { + "id": "microsoft/knowledge/interfaces/set-defaultimplementation-on-enum.md", + "severity": "minor", + "message": "This extensible enum implements an interface but the 'None' value sets no Implementation and the enum declares no DefaultImplementation. Resolving 'None' to the interface and calling a method will fail at runtime. Add a DefaultImplementation mapping.", + "location": { + "file": "src/Notifications/NotificationChannel.Enum.al", + "line": 9 + }, + "references": [ + { "path": "microsoft/knowledge/interfaces/set-defaultimplementation-on-enum.md" } + ], + "confidence": "high" + } + ], + "suppressed": [] +} +``` + +The empty-corpus case — BCQuality's state before interfaces knowledge files land — produces: + +```json +{ + "skill": { "id": "al-interfaces-review", "version": 1 }, + "outcome": "no-knowledge", + "summary": { + "counts": { "blocker": 0, "major": 0, "minor": 0, "info": 0 }, + "coverage": { "worklist-size": 0, "items-evaluated": 0 } + }, + "findings": [], + "suppressed": [] +} +```