diff --git a/.github/scripts/validate_frontmatter.py b/.github/scripts/validate_frontmatter.py index 682a801..a92bc4b 100644 --- a/.github/scripts/validate_frontmatter.py +++ b/.github/scripts/validate_frontmatter.py @@ -45,8 +45,9 @@ STANDARD_INPUTS = { "pr-diff", "object-list", "file-path", "repository", "telemetry-query", + "object-spec", } -ALLOWED_OUTPUTS = {"findings-report"} +ALLOWED_OUTPUTS = {"findings-report", "code-artifact"} VALID_SAMPLE_KINDS = {"good", "bad"} ACTION_SKILL_SECTIONS = ["Source", "Relevance", "Worklist", "Action", "Output"] diff --git a/README.md b/README.md index ddab523..ced5db5 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 the AL review leaf skills under [`microsoft/skills/review/`](microsoft/skills/review/) — one per knowledge domain. +- **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 the AL review leaf skills under [`microsoft/skills/review/`](microsoft/skills/review/) — one per knowledge domain. Most action skills today *review* code and emit a findings-report, but the family also includes an `author` group that *generates* code — applying the same knowledge to scaffold new objects and emitting a `code-artifact` instead of findings. The first example is [`microsoft/skills/author/al-api-page-author.md`](microsoft/skills/author/al-api-page-author.md), which generates a Business Central API page from an object spec. ### Agent bootstrapping diff --git a/agent-consumption.md b/agent-consumption.md index 0d2e5b4..c325fa9 100644 --- a/agent-consumption.md +++ b/agent-consumption.md @@ -71,6 +71,8 @@ The output contract is defined in the DO meta-skill so that every action skill The orchestrator parses this **without skill-specific logic**. This is the point of the contract: orchestrators and action skills evolve independently. +> **Authoring skills.** The same four-step pattern also drives *authoring* skills, which generate code rather than review it. They consume an `object-spec` and emit a `code-artifact` (the full generated AL as an escaped string, with per-artifact `references`, `notes`, and task-level `open-questions`) instead of a findings-report. The orchestrator maps each artifact to **file creation or scaffolding** in the target repo rather than to PR comments or build gates. The first example is `microsoft/skills/author/al-api-page-author.md`; the `code-artifact` contract is defined in [`skills/do.md`](skills/do.md). + ### 7. Orchestrator integrates The orchestrator turns findings into PR comments, build gates, or IDE diagnostics, and links the references back to the knowledge files so the PR author — human or agent — can read the guidance. diff --git a/microsoft/skills/author/al-api-page-author.md b/microsoft/skills/author/al-api-page-author.md new file mode 100644 index 0000000..e4cd4d5 --- /dev/null +++ b/microsoft/skills/author/al-api-page-author.md @@ -0,0 +1,131 @@ +--- +kind: action-skill +id: al-api-page-author +version: 1 +title: AL API page author +description: Generates a correct Business Central API page (PageType = API) from an object spec, applying BCQuality web-services API guidance. +inputs: [object-spec] +outputs: [code-artifact] +bc-version: [all] +technologies: [al] +countries: [w1] +application-area: [all] +--- + +# AL API page author + +Generates a Business Central API page (`PageType = API`) from an `object-spec`, applying the `web-services` knowledge domain in BCQuality, and emits a `code-artifact`. This is a leaf action skill: it invokes no sub-skills. It is the authoring counterpart to `al-web-services-review` — where the review skill consumes a diff and flags API-page defects, this skill consumes a spec and generates a page that does not have them. + +An orchestrator invokes this skill with an `object-spec` — an abstract description of the object to generate (target table, desired entity naming, the fields to expose, the read-only flag, and any other generation parameters). The skill produces a single JSON document conforming to the DO `code-artifact` 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 `web-services` 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 authoring reads the index plus the handful of worklisted articles instead of every file under `*/knowledge/web-services/**`. + +## Relevance + +Apply the frontmatter matching rules defined in READ (*Frontmatter matching semantics*) against the task context: + +- `bc-version` — the target BC version from the `object-spec` or the orchestrator-supplied version. 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 application area declared by the target table or the `object-spec`. Pass the actual set; do not substitute `[all]`. If the area cannot be determined, 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; an artifact generated under any `unknown` dimension MUST have `confidence` no higher than `medium`, AND the artifact's `notes` MUST name the dimension or dimensions that were unknown. + +## Worklist + +Narrow the relevant files to the subset that applies to **authoring an API page** for this spec. A web-services file enters the worklist when its `keywords` or topic (derived from the index entry's `path`, `title`, and `description`) concern `PageType = API` page authoring. Match against API-page authoring vocabulary: + +- `api-page`, `pagetype-api`, `odatakeyfields`, `systemid`, `apiversion`, `apipublisher`, `apigroup`, `entityname`, `entitysetname`, `sourcetable` — the required identifying metadata and the stable-key rule. +- `bound-actions`, `serviceenabled`, `webserviceactioncontext` — when the spec asks for operations rather than plain field writes. +- `read-only`, `insertallowed`, `modifyallowed`, `deleteallowed`, `editable` — when the spec marks the page read-only. +- `committed-data`, `readisolation`, `readcommitted` — when the spec requires committed-only reads. +- `apiversion`, `versioning` — when the spec evolves an already-published API. + +Read an article's full file — its `## Best Practice` / `## Anti Pattern` bodies, plus any `.good.al` / `.bad.al` companions — only after it makes the worklist; candidate selection uses the index alone. The `.good.al` companion of a worklisted rule is the authoring template to follow. + +Once the candidate worklist is known, resolve layer-precedence conflicts per READ. Drop lower-precedence files whose normative guidance 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 web-services API knowledge survives — and the skill therefore emits no artifact — emit `outcome: "no-knowledge"`. + +## Action + +From the `object-spec` (target table, entity naming, fields to expose, read-only flag), generate **one** API page object that satisfies every worklisted rule: + +- `PageType = API` with `APIPublisher`, `APIGroup`, and `APIVersion` set, plus `EntityName` (singular) and `EntitySetName` (plural), and a `SourceTable` bound to the spec's target table — the full identifying-property checklist. +- `ODataKeyFields = SystemId`, exposed as a non-editable `field(id; Rec.SystemId)` (`Editable = false`), so the endpoint is addressed by the stable GUID rather than a renamable business key. +- `DelayedInsert = true`. +- A single `repeater` under `area(content)` projecting the spec's fields with camelCase API field names mapped from the underlying table fields; keep the business key (for example `No.`) as an ordinary exposed field, not as the OData key. +- When the spec marks the page read-only: set `InsertAllowed = false`, `ModifyAllowed = false`, `DeleteAllowed = false`, and `Editable = false`. When the spec asks for a record operation (post, ship, release), expose it as a `[ServiceEnabled]` bound action that reports its result through `WebServiceActionContext`, rather than a writable status flag whose `OnValidate` performs the operation. +- When the spec requires committed-only reads, set `Rec.ReadIsolation := IsolationLevel::ReadCommitted;` in `OnOpenPage`. When the spec evolves an already-published API, add the new version to the `APIVersion` list instead of mutating the published one. Expose only committed data; do not surface in-flight writes. + +Cite each applied knowledge file in the artifact's `references` (same shape as a finding's `references`). The artifact's `references` SHOULD be non-empty: an authored page is expected to cite the rules it satisfies. Only when the agent generates purely from its own competence — no curated web-services knowledge backing the page — is `references` empty, in which case `confidence` is capped at `medium`, mirroring DO's additive agent-findings principle. + +Never silently invent values the spec does not provide. Put unresolved ambiguities — a missing object ID, an unspecified field set, an unknown publisher or group — in `open-questions` (task level) and in the artifact's `notes` (per artifact), and emit them as clearly-labeled placeholders in the generated source rather than guessed values. Emit the generated AL as a single escaped JSON string in `artifacts[].content`: every embedded double quote (for example `Rec."No."`) escaped as `\"`, every newline as `\n`. + +Set `confidence` to `high` only when the spec is complete and every worklisted rule was applied; cap at `medium` when any frontmatter dimension was `unknown`, the spec was ambiguous, or the page carries placeholders. + +Outcome selection: + +- `completed` — the skill generated and emitted at least one artifact. +- `no-knowledge` — no applicable web-services API knowledge survived Source, Relevance, configuration filtering, and conflict resolution, and no artifact was emitted. `artifacts` is empty. +- `not-applicable` — the `object-spec` is not an API-page request, or it carries no AL dimension (the `technologies` filter rejected the task). +- `partial` — a time or token budget was hit before every requested artifact was generated. `summary.coverage` reflects the produced subset; `outcome-reason` explains the cause. +- `failed` — an unrecoverable error occurred. `outcome-reason` is required. + +## Output + +Output conforms to the DO `code-artifact` output contract. A populated example — a generated `customer` API page whose spec left the object ID, publisher, and group unspecified: + +```json +{ + "skill": { "id": "al-api-page-author", "version": 1 }, + "outcome": "completed", + "summary": { + "counts": { "artifacts": 1, "objects": 1 }, + "coverage": { "knowledge-applied": 2 } + }, + "artifacts": [ + { + "id": "customer-api-page", + "object-type": "page", + "object-name": "Customer Entity", + "path": "src/Api/CustomerEntity.Page.al", + "content": "page 50100 \"Customer Entity\"\n{\n // TODO: object ID 50100 is a placeholder; replace with an ID from your assigned range.\n PageType = API;\n Caption = 'customer';\n APIPublisher = 'PLACEHOLDER-publisher';\n APIGroup = 'PLACEHOLDER-group';\n APIVersion = 'v1.0';\n EntityName = 'customer';\n EntitySetName = 'customers';\n ODataKeyFields = SystemId;\n SourceTable = Customer;\n DelayedInsert = true;\n\n layout\n {\n area(content)\n {\n repeater(records)\n {\n field(id; Rec.SystemId)\n {\n Caption = 'id';\n Editable = false;\n }\n field(number; Rec.\"No.\")\n {\n Caption = 'number';\n }\n field(displayName; Rec.Name)\n {\n Caption = 'displayName';\n }\n }\n }\n }\n}", + "references": [ + { "path": "microsoft/knowledge/web-services/set-required-api-page-properties.md" }, + { "path": "microsoft/knowledge/web-services/expose-systemid-as-the-api-key.md" } + ], + "confidence": "medium", + "notes": "Object ID, APIPublisher, and APIGroup are placeholders the spec did not supply; replace them before deploying. Field set inferred as No. and Name from the customer spec." + } + ], + "open-questions": [ + "Which object ID (from the consuming extension's assigned range) should this page use?", + "What APIPublisher and APIGroup identify this endpoint?", + "Is the No. + Name field set complete, or should more Customer fields be exposed?" + ], + "suppressed": [], + "sub-results": [], + "skipped-sub-skills": [] +} +``` + +The no-knowledge case — when no web-services API knowledge survives filtering, so no page can be authored against curated guidance — produces: + +```json +{ + "skill": { "id": "al-api-page-author", "version": 1 }, + "outcome": "no-knowledge", + "summary": { + "counts": { "artifacts": 0, "objects": 0 }, + "coverage": { "knowledge-applied": 0 } + }, + "artifacts": [], + "open-questions": [], + "suppressed": [], + "sub-results": [], + "skipped-sub-skills": [] +} +``` diff --git a/skills/do.md b/skills/do.md index 777f89f..c364633 100644 --- a/skills/do.md +++ b/skills/do.md @@ -43,7 +43,7 @@ application-area: [all] `bc-version`, `technologies`, `countries`, `application-area` are optional filters that let an orchestrator pre-select applicable skills for a task. They follow the same semantics as in READ. -`inputs` is a list of abstract input types the skill **accepts**. Standard values: `pr-diff`, `object-list`, `file-path`, `repository`, `telemetry-query`. Semantics are any-of: the orchestrator supplies whichever listed input types it has, and the skill is invoked with a non-empty subset of its declared `inputs`. A skill that cannot proceed with the supplied subset MUST return `outcome: "not-applicable"`. `outputs` is always a single-element list naming the output kind; today only `findings-report` is defined. +`inputs` is a list of abstract input types the skill **accepts**. Standard values: `pr-diff`, `object-list`, `file-path`, `repository`, `telemetry-query`, `object-spec`. `object-spec` is an abstract description of the BC object(s) to generate — the target table, the desired entity naming, the fields to expose, the read-only flag, and any other generation parameters — supplied by the orchestrator for authoring tasks (the authoring counterpart to a review skill's `pr-diff`). Semantics are any-of: the orchestrator supplies whichever listed input types it has, and the skill is invoked with a non-empty subset of its declared `inputs`. A skill that cannot proceed with the supplied subset MUST return `outcome: "not-applicable"`. `outputs` is always a single-element list naming the output kind; two output kinds are defined: `findings-report` (review skills) and `code-artifact` (authoring skills). `sub-skills` is an optional field. When present and non-empty, the skill is a **super-skill** that composes other action skills; see *Composition* below. Values are repo-relative paths to action-skill files. @@ -213,6 +213,81 @@ Severity taxonomy: - `minor` — quality concern; worth flagging but not a gate. - `info` — observation or context; not actionable on its own. +### Code-artifact output (authoring skills) + +Review skills consume a diff and emit a `findings-report`. **Authoring skills** do the inverse: they consume an `object-spec` and emit generated code. Their output kind is `code-artifact` — the same single-element `outputs` declaration, naming `code-artifact` instead of `findings-report`. A `code-artifact` document reuses the findings-report envelope (`skill`, `outcome`, `outcome-reason`, `suppressed`, `sub-results`, `skipped-sub-skills`) and replaces the `findings`/`summary.counts` pair with `artifacts`/`open-questions`: + +```json +{ + "skill": { "id": "string", "version": 1 }, + "outcome": "completed | not-applicable | no-knowledge | partial | failed", + "outcome-reason": "string", + "summary": { + "counts": { "artifacts": 0, "objects": 0 }, + "coverage": { "knowledge-applied": 0 } + }, + "artifacts": [ + { + "id": "string", + "object-type": "string", + "object-name": "string", + "path": "string", + "content": "string", + "references": [ + { "path": "string", "sha": "string" } + ], + "confidence": "high | medium | low", + "notes": "string" + } + ], + "open-questions": [ "string" ], + "suppressed": [ + { + "reference": { "path": "string", "sha": "string" }, + "reason": "layer-precedence | configuration" + } + ], + "sub-results": [ ], + "skipped-sub-skills": [ ] +} +``` + +The same JSON-validity rules apply unchanged: the document MUST be strict, valid JSON, and every string value MUST escape embedded double quotes as `\"`, newlines as `\n`, and other control characters with their JSON escapes (see *JSON validity* above). This matters most for `artifacts[].content`, which carries generated AL source — see its semantics below. + +Field semantics specific to `code-artifact`: + +**`outcome`** — reuses the findings-report enum with authoring-specific meaning: + +- `completed` — the skill ran end-to-end and emitted at least one artifact. +- `no-knowledge` — the skill ran but no applicable authoring knowledge survived Source, Relevance, configuration filtering, and conflict resolution, AND no artifact was emitted. `artifacts` MUST be empty. +- `not-applicable` — the skill's frontmatter filters or the supplied `object-spec` rejected the task (for example, the spec is not a request this skill authors); the skill declined to run. +- `partial` — the skill emitted some but not all of the requested artifacts before a time or token budget was hit. `summary.coverage` reflects the produced subset. Set `outcome-reason`. +- `failed` — an unrecoverable error occurred and the emitted artifacts are unreliable. Set `outcome-reason`. Consumers SHOULD ignore `artifacts` on a failed outcome. + +`outcome-reason` follows the findings-report rule: optional for `completed`, `not-applicable`, and `no-knowledge`; required for `partial` and `failed`. + +**`summary.counts`** — `artifacts` is the number of emitted `artifacts[]` entries; `objects` is the number of distinct BC objects across them (an artifact MAY contain more than one object). `summary.coverage.knowledge-applied` is the count of distinct knowledge files cited across all artifacts. + +**`artifacts[].id`** — a stable, skill-defined slug (kebab-case) identifying the generated artifact within the run; consumers MAY deduplicate by `id`. + +**`artifacts[].object-type` / `artifacts[].object-name`** — the AL object kind (for example `page`, `table`, `codeunit`) and the object name being generated. + +**`artifacts[].path`** — the suggested repo-relative path for the generated file, using forward slashes. It is a suggestion the consumer MAY relocate to match the target project's layout. + +**`artifacts[].content`** — the **full** generated AL source for the artifact, as a single JSON string. Every embedded double quote (for example a quoted identifier such as `Rec."No."`) MUST be escaped as `\"` and every newline as `\n`; the multi-line source is one JSON string with `\n` separators, never a literal multi-line block. The consumer writes this verbatim to `path`. + +**`artifacts[].references`** — array of knowledge-file references that informed the generated source, same shape as `findings[].references` (`path` required, `sha` optional). It SHOULD be non-empty whenever curated BCQuality knowledge was applied — an authored artifact is expected to cite the rules it satisfies. When the agent generates purely from its own competence with no curated knowledge backing the artifact, `references` is `[]` and the artifact's `confidence` is capped at `medium` — the authoring mirror of DO's additive agent-findings principle: without a curated rule behind it there is no authoritative basis for `high` confidence. + +**`artifacts[].confidence`** — the skill's confidence that the generated artifact is correct and complete: `high`, `medium`, or `low`. Cap at `medium` when any frontmatter dimension was `unknown`, when the spec was ambiguous, or when the artifact carries no `references`. + +**`artifacts[].notes`** — per-artifact caveats: placeholders the consumer must fill in, values the skill could not resolve, or assumptions it baked in. The consumer MUST read `notes` before treating the artifact as final. + +**`open-questions`** — task-level spec ambiguities the skill could not resolve (for example a missing object ID, an unspecified field set, or an unknown publisher/group). Unlike `notes`, which is per-artifact, `open-questions` is the run-level list of decisions the orchestrator or author still owes the skill. + +`suppressed`, `sub-results`, and `skipped-sub-skills` carry the same meaning as in the findings-report contract above (knowledge-file suppression; nested sub-skill reports for an authoring super-skill; declared-but-not-invoked sub-skills). A leaf authoring skill leaves `sub-results` and `skipped-sub-skills` empty. + +When an orchestrator consumes a `code-artifact`, it maps each artifact to **file creation or scaffolding** in the target repository — writing `content` to `path` and surfacing `notes`/`open-questions` to the author — rather than to PR comments and build gates the way it consumes a findings-report. + ## Composition (super-skills) A **super-skill** is an action skill whose frontmatter declares a non-empty `sub-skills: [...]`. A super-skill does not evaluate knowledge files directly; it invokes other action skills and composes their output.