Bundle d365fo-cli guidance as a first-class Copilot skill - #127
Conversation
- Add skills/d365fo-cli/SKILL.md — core rule canon (renamed from .github/copilot-instructions.md) with SKILL frontmatter and updated resource table pointing to resources/ - Add skills/d365fo-cli/resources/*.md — 19 lazily-loaded X++ topic files emitted from skills/_source (frontmatter stripped) - Update scripts/emit-skills.ps1 — add Emit-CopilotSkill function that writes body-only files to skills/d365fo-cli/resources/ as a third emit target alongside copilot and anthropic - Update scripts/emit-skills.py — matching emit_copilot_skill() function for parity in Python environments - Rewrite scripts/Install-D365FoCopilotSkills.ps1 — deploy skill folder (.github/skills/d365fo-cli/) instead of flat copilot-instructions.md + instructions/; adds migration notice for legacy files - Update README.md, docs/SETUP.md, docs/EXAMPLES.md, docs/MIGRATION_FROM_MCP.md, docs/TROUBLESHOOTING.md — replace old copy-paste integration instructions with one-command skill installer and new skill layout description The legacy skills/copilot/ and skills/anthropic/ outputs are still emitted for non-skill consumers (documented as legacy paths). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…kill Dalton stonessi create d365fo skill
Added strict "NEVER write X++ AOT XML files directly" warning block to all X++ authoring guidance files, instructing use of `d365fo generate ...` CLI commands only. Renamed all "resources/" references and folder structure to "references/" across scripts, SKILL.md, and documentation. Updated all related documentation, comments, and logic to reflect the new folder name and reinforce the supported authoring workflow. No other functional changes.
Audit — thanks, right direction, but a few blockersThe intent is good and the skill format is fundamentally valid (frontmatter checks out against the spec — see "What's good" at the bottom). But there are 4 blocking issues, one of which ships factually outdated X++ guidance. Everything below was verified by checking out Blocking1. Committed Checked out the PR head, ran This isn't whitespace, it's stale guidance. Example from -- `--lang` is required when the file does not embed a single language stem
+- `--lang` only affects path resolution when using `--install-to <MODEL>`; it
+ has no effect when `--file` is given directlyRoot cause: references were generated in Fix: re-run the emitter and commit the result. 2. The CI drift gate doesn't cover the new target — if [ -n "$(git status --porcelain skills/copilot skills/anthropic)" ]; then
3. $copilotSkillOut = Join-Path $OutRoot 'd365fo-cli' 'references'The three-argument Developer PowerShell for VS 2022 and a typical D365FO dev VM are both 5.1. Fix: 4. The installer now hard-depends on & pwsh -NoProfile -File $emitScriptThis replaces the previous Fix: probe Note —
|
Follow-up to the PR dynamics365ninja#127 audit. Addresses the four blocking findings. 1. Regenerate skills/d365fo-cli/references/ from skills/_source/ 14 of 19 files were generated before the upstream main merge (418c5b2) and carried guidance the KB audit had already corrected - e.g. the --lang semantics of `labels create` and the EDT label-inheritance rule. 2. Extend the CI drift gate to the new emit target .github/workflows/ci.yml only diffed skills/copilot and skills/anthropic, which is why (1) went unnoticed. Adds skills/d365fo-cli/references. 3. Make emit-skills.ps1 run on Windows PowerShell 5.1 - Join-Path $OutRoot 'd365fo-cli' 'references' used the 3-argument form (-AdditionalChildPath), which is PowerShell 6+ only. - $PSScriptRoot is empty when param() defaults are evaluated under 5.1, so Source/OutRoot are now resolved in the script body instead. - Emit-CopilotSkill used Set-Content -Encoding utf8, which writes a BOM under 5.1 and would diverge from emit-skills.py; it now uses the same $Utf8NoBom WriteAllText path as the other two emitters. - Progress output is ASCII: these files have no BOM (a BOM would break the #!/usr/bin/env pwsh shebang), so 5.1 reads them as ANSI. 4. Stop the installer from hard-requiring pwsh Install-D365FoCopilotSkills.ps1 called `& pwsh` unconditionally. A stock Windows / D365FO dev VM has no pwsh, so with $ErrorActionPreference = 'Stop' the installer aborted with CommandNotFoundException instead of degrading. It now probes pwsh -> powershell -> python/python3 and warns only when none is available. Also wraps Get-ChildItem in @() so .Count stays valid under Set-StrictMode when references/ is absent. Verified on Windows PowerShell 5.1: both emitters now produce byte-identical output, the installer deploys SKILL.md + 19 references, and the clean-clone regeneration path works. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FfAAnxQDnPHRB1ne5H724n
Follow-up: blocking items 1–4 fixed in
|
| # | Item | What changed |
|---|---|---|
| 1 | Stale references/ |
Regenerated from skills/_source/ — 14 files. Restores the KB-audit corrections the old snapshot was overwriting (--lang semantics on labels create, EDT label inheritance). |
| 2 | CI drift gate | .github/workflows/ci.yml now diffs skills/d365fo-cli/references too, and the step name matches. |
| 3 | emit-skills.ps1 on PS 5.1 |
Nested Join-Path; plus two extra defects found while testing (below). |
| 4 | Installer's pwsh dependency |
Probes pwsh → powershell → python/python3, warns only when none is found. |
Two extra things surfaced while verifying #3
Worth flagging since they weren't in the original audit:
$PSScriptRootis empty whenparam()defaults are evaluated under Windows PowerShell 5.1 (they're evaluated in the caller's scope). Once theJoin-Pathbug was fixed the script still died on its own default parameters — meaningemit-skills.ps1with no arguments had never worked on 5.1. Defaults are now resolved in the script body. Without this the installer fix in feat: Implement D365FO.Bridge for JSON-RPC 2.0 communication #4 would have been useless, since it invokes exactly that path.Emit-CopilotSkillusedSet-Content -Encoding utf8, which writes a BOM under 5.1 — that was item Refactor documentation structure: #6, but it had to come along: with the drift gate from feat: implement custom model matching with glob patterns and update extraction logic #2 now active, BOM'd output would fail CI depending on which emitter the contributor ran. It now uses the same$Utf8NoBomWriteAllTextpath as the other two emitters.
Also swapped the » and ⚠ in console output for ASCII. These files carry no BOM, so 5.1 reads them as ANSI and mangles non-ASCII — and adding a BOM isn't an option, it would break the #!/usr/bin/env pwsh shebang. (Item #10, partially; the box-drawing characters in comments are untouched since they're never printed.)
Item #9 (@(Get-ChildItem ...) for .Count under Set-StrictMode) came along with the installer rewrite.
Verified on Windows PowerShell 5.1
- Both emitters produce output
git diffsees as identical — the drift gate from feat: implement custom model matching with glob patterns and update extraction logic #2 passes either way. Install-D365FoCopilotSkills.ps1deploysSKILL.md+ 19 references.- The clean-clone path works: with
references/absent the installer regenerates and then deploys, andSKILL.mdsurvives regeneration.
Still open, for you
#7 dangling references to the deleted .github/copilot-instructions.md — AgentPromptCommand.cs:37 is the one that matters, since it's the CLI's own output. Then docs/CAPABILITIES.md:378/:452, docs/TOKEN_ECONOMICS.md:25, docs/img/solution-architecture-diagram.svg:137.
#8 the README's "always loaded" note on SKILL.md, #11 the installer never pruning stale references, #12 the applyTo trade-off in the PR description.
And #5 is retracted — see the edit on my earlier comment. VS 2022 does load .github/skills/, it's just undocumented.
…ynamics365ninja#8, dynamics365ninja#11 CI drift The `skills` job failed on the merge result, not on the branch: this PR last merged main at 418c5b2, and 012e018 ("correct remaining 5 KB files against live D365FO VM") has since changed three _source files. Merged main and regenerated, which updates exactly those three references: sysoperation-batch-patterns, table-scaffolding, x++-class-authoring. Worth noting for future rebases: the drift gate runs against refs/pull/N/merge, so a branch that is clean on its own can still fail once main moves the source files underneath it. dynamics365ninja#7 Dangling references to the deleted .github/copilot-instructions.md - AgentPromptCommand.cs now points at skills/d365fo-cli/SKILL.md and names the install target. This one shipped in `d365fo agent-prompt` output, so it was sending users to a file that no longer exists. - docs/CAPABILITIES.md: the "Copilot Skills" section now describes the skill layout, and the source-file table lists all three emit targets. - docs/TOKEN_ECONOMICS.md: describes what the skill model actually costs (name + description until activation) instead of .instructions.md frontmatter, with the legacy figure kept as a parenthetical. - docs/img/solution-architecture-diagram.svg: .github/instructions/ -> .github/skills/d365fo-cli/. dynamics365ninja#8 README "always loaded" Only name + description stay in context; the body is paged in on activation. Relabelled so the diagram doesn't contradict the PR's own token-economics rationale. dynamics365ninja#11 Installer never pruned Install-D365FoCopilotSkills.ps1 now removes reference files in the target that no longer exist upstream, so renamed or retired topics stop feeding Copilot guidance the current skill has dropped. Reported in the summary line and documented in .DESCRIPTION and the README step list. Verified: emitter output is drift-free against the merged _source, and the installer prunes a planted stale reference while deploying SKILL.md + 19. The C# change is text inside a raw string literal; it is not built here (this machine has only .NET SDK 9, the solution targets 10) - CI covers it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FfAAnxQDnPHRB1ne5H724n
dynamics365ninja#12) Follow-up to PR dynamics365ninja#127. Both items were left open at merge time. dynamics365ninja#10 Non-ASCII in Install-D365FoCopilotSkills.ps1 The file has no BOM, so Windows PowerShell 5.1 reads it as ANSI. This is not just a comment-cosmetics issue: the em dashes sit in .PARAMETER and .EXAMPLE blocks, so `Get-Help` rendered them as mojibake - contains — or will contain — the .github/ directory All em dashes and box-drawing characters are now ASCII. Adding a BOM instead is not an option for these scripts: emit-skills.ps1 carries a #!/usr/bin/env pwsh shebang that a BOM would break, and keeping the two scripts encoded the same way is worth more than the typography. Also fixed the deletion hints in the migration notice: they suggested `Remove-Item -Recurse` for a file, and offered commands for paths that don't exist. Each hint is now printed only when its target is present, with -Recurse only on the directory. dynamics365ninja#12 Skill activation is a judgement call, not a glob The skill replaces 19 .instructions.md files that applied deterministically via applyTo. A skill exposes only name + description and the agent decides relevance itself - that is what makes it cheap, but it is a real behaviour change and it was undocumented. docs/SETUP.md now explains this where users hit it, including how to force activation ("use the d365fo-cli skill", or naming a references/ file), where each IDE shows which skills were applied, and that skills/copilot/*.instructions.md is still emitted for anyone who wants the deterministic scoping back. The installer's legacy notice no longer flatly calls those files safe to delete, since that now contradicts documented advice - it says the skill supersedes and does not conflict with them, and points at SETUP.md. Verified on Windows PowerShell 5.1: Get-Help renders clean ASCII, the installer deploys SKILL.md + 19 references and prunes stale ones, and the emitters remain drift-free. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FfAAnxQDnPHRB1ne5H724n
True lazy loaded skills to save on token usage and not be interjected into every prompt.