Skip to content

Commit c54ab08

Browse files
tyaginidhiclaude
andcommitted
Harden against Windows-breaking symlinks: forbid symlinked manifests + convert CLAUDE.md mirrors
Follow-up to #203. That PR fixed the issue #201 install failure by replacing the legacy .claude-plugin manifest symlinks with real JSON files, but two gaps remained: 1. The validator only compared parsed JSON, so a manifest re-committed as a symlink would still pass on Linux/CI (which resolves symlinks) and let the #201 regression back in. Per the open review suggestion on #203, assert each legacy manifest is a committed regular file (not a symlink) before comparing — this is the assertion that actually guards the cross-platform fix. 2. The per-directory CLAUDE.md files were still symlinks to AGENTS.md, carrying the same Windows hazard (git materializes them as tiny text files on core.symlinks=off clones). Convert the 4 CLAUDE.md symlinks (root + canvas-apps/model-apps/power-pages) to real file copies and extend the validator to guard them (regular file + identical content to the sibling AGENTS.md, only where CLAUDE.md exists). The DRY shared-content symlinks (report-issue/telemetry workflows, telemetry/lib) are intentionally left as-is: they rely on the marketplace installer dereferencing them and converting them would duplicate shared content against the documented architecture. Verified: validator passes clean; fails on a re-introduced manifest symlink, a re-introduced CLAUDE.md symlink, and CLAUDE.md content drift. validate-plugin-names and validate-skill-descriptions still pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 85b7be6 commit c54ab08

5 files changed

Lines changed: 939 additions & 4 deletions

File tree

CLAUDE.md

Lines changed: 0 additions & 1 deletion
This file was deleted.

CLAUDE.md

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
# Power Platform Skills - Development Guidelines
2+
3+
This file provides guidance to AI Agents when working with code in this repository.
4+
5+
## What This Repo Is
6+
7+
A **plugin marketplace** for Power Platform development by Microsoft. The Open Plugins marketplace manifest (`marketplace.json`) references individual plugins in `plugins/`. Each plugin has its own `AGENTS.md` with plugin-specific guidance.
8+
9+
## Repository Structure
10+
11+
```
12+
power-platform-skills/
13+
├── marketplace.json # Open Plugins marketplace manifest (lists all available plugins)
14+
├── .claude-plugin/ # Legacy manifest mirrors for existing subscriptions
15+
│ └── marketplace.json
16+
├── plugins/ # Directory containing individual plugins
17+
│ └── <plugin-name>/ # Individual plugin (e.g., power-pages)
18+
│ ├── .plugin/
19+
│ │ └── plugin.json # Plugin manifest
20+
│ ├── .claude-plugin/
21+
│ │ └── plugin.json # Legacy manifest mirror
22+
│ ├── AGENTS.md # Plugin-specific development guidelines
23+
│ ├── agents/ # Agent persona files
24+
│ ├── commands/ # Command entry points
25+
│ ├── shared/ # Shared resources and documentation
26+
│ └── skills/ # Skill workflows (SKILL.md in subdirectories)
27+
├── shared/ # Cross-plugin shared resources
28+
│ └── skills/ # Shared skill definitions
29+
│ └── <skill-name>/ # SKILL.template.md + workflow .md files
30+
├── AGENTS.md # Generic development guidelines (this file)
31+
└── README.md # Repository overview
32+
```
33+
34+
## Local Development
35+
36+
Test a plugin locally by launching your AI agent with the plugin path:
37+
38+
```bash
39+
claude --plugin-dir /path/to/plugins/<plugin-name>
40+
```
41+
42+
No root-level build, lint, or test commands exist. Build/test tooling lives inside each plugin.
43+
44+
## Plugin Conventions
45+
46+
Each plugin follows this structure:
47+
48+
- `.plugin/plugin.json` — Open Plugins metadata (name, version, keywords)
49+
- `.claude-plugin/plugin.json` — legacy mirror of `.plugin/plugin.json` kept for existing subscriptions
50+
- `.mcp.json` — MCP server configuration (optional)
51+
- `agents/` — Agent definitions (`.md` files with YAML frontmatter)
52+
- `skills/` — Skill definitions, each in its own subdirectory with a `SKILL.md`
53+
- `scripts/` — Shared utility scripts referenced by skills and agents
54+
- `references/` — Shared reference documents used by multiple skills
55+
56+
Skills are defined in `SKILL.md` files with YAML frontmatter (name, description, allowed-tools, model, hooks). The `allowed-tools` field must use a **comma-separated list** (e.g., `allowed-tools: Read, Write, Edit, Bash, Glob, Grep`) — not JSON array syntax (`["Read", "Write"]`) or YAML list syntax. Each skill may include validation scripts in a `scripts/` subdirectory, run as Stop hooks when the skill session ends.
57+
58+
## Cross-Plugin Shared Skills
59+
60+
Skills that apply to all plugins live in `shared/skills/<skill-name>/`. The workflow logic is written once in a shared `.md` file, and each plugin has a thin `skills/<skill-name>/SKILL.md` that contains only the YAML frontmatter and a reference to the workflow path bundled inside that plugin at install time.
61+
62+
**Pattern:**
63+
- `shared/skills/<skill-name>/<workflow>.md` — Full workflow (phases, instructions, field definitions)
64+
- `shared/skills/<skill-name>/SKILL.template.md` — Template SKILL.md (frontmatter + reference to workflow); supports `{{PLUGIN_NAME}}` placeholder
65+
- `plugins/<plugin>/skills/<skill-name>/SKILL.md` — Per-plugin wrapper generated from the template above
66+
- `plugins/<plugin>/skills/<skill-name>/<workflow>.md` — Symlink to the shared workflow when the plugin must work after installing only its own plugin directory
67+
68+
This keeps the skill discoverable in each plugin while preserving install-time portability. Marketplace installs copy only the plugin directory, so per-plugin wrappers must not reference repo-root `shared/` paths at runtime. Instead, point the wrapper at `${PLUGIN_ROOT}/skills/<skill-name>/<workflow>.md` and keep a symlink from that per-plugin path to the repo-root shared workflow; marketplace installers dereference same-marketplace symlinks into the installed plugin cache. When updating a shared skill, edit the workflow file and/or `SKILL.template.md` in `shared/`, then update the per-plugin wrappers (frontmatter + bundled workflow reference, with `{{PLUGIN_NAME}}` substituted) and ensure any per-plugin symlinks still resolve under `plugins/<plugin>/skills/<skill-name>/`. Commit the shared source and per-plugin symlinks together.
69+
70+
## Shared Telemetry
71+
72+
1DS telemetry code for all plugins lives at `shared/telemetry/`. Each adopting plugin **symlinks** the library into its own tree — `plugins/<plugin>/scripts/lib/telemetry/lib` is a symlink to `shared/telemetry/lib`. The marketplace installer dereferences that symlink into the installed plugin at install time, so the shared code ships without copying it into each plugin. Each plugin keeps its own real `ikey.json` next to the symlink.
73+
74+
Edit `shared/telemetry/` directly — the symlink makes changes live for every adopting plugin immediately; there is nothing to re-sync.
75+
76+
Per-plugin iKey/collector routing is pluggable via a `resolver.js` placed next to the plugin's `ikey.json` (implementing the `resolve`/`isProvisioned` contract); the shared library ships only that contract plus a static-key fallback, not any routing logic. A per-plugin opt-out env var `POWER_PLATFORM_SKILLS_TELEMETRY_<PLUGIN>_OPTOUT` (derived as the uppercased plugin name with non-alphanumerics collapsed to `_`, suffixed `_OPTOUT`) disables transmission for automation when set to `1`/`true` (dotnet `*_TELEMETRY_OPTOUT` convention); it has the **highest precedence**, overriding both the persisted `config.json` choice and `/<plugin>:telemetry on`.
77+
78+
### CI must opt out of telemetry transmission
79+
80+
An adopting plugin's committed `ikey.json` ships **enabled** (`disabled: false`) with a real production instrumentation key, so any process that runs a telemetry-emitting hook or script **without isolating emission** will POST a real (but fake-in-content) event to the production collector. CI runs are not real usage, and such events pollute the production telemetry stream.
81+
82+
**Therefore: every GitHub Actions job that runs the test suite — or any step that could execute a telemetry-emitting hook/script for an adopting plugin — MUST set the plugin's opt-out env var at the job (or workflow) level.** For `power-pages`:
83+
84+
```yaml
85+
jobs:
86+
<job-name>:
87+
runs-on: <runner>
88+
env:
89+
POWER_PLATFORM_SKILLS_TELEMETRY_POWER_PAGES_OPTOUT: "1"
90+
steps: ...
91+
```
92+
93+
This opt-out suppresses **transmission only** (the local diagnostic mirror is still written), so it is safe and has no effect on what the job actually tests. Tests that need to assert that emission *happens* clear the var in their own spawned-process env and route the event to a local `POWER_PLATFORM_SKILLS_FAKE_HTTPS` probe instead of the real collector — so the job-level opt-out never breaks them. Existing reference: `.github/workflows/power-pages-script-tests.yml`. When you add a new such workflow (or a new emitting step to an existing one), add this env var in the same change; treat a CI job that runs the tests without it as a production-telemetry leak.
94+
95+
Current adopters: `power-pages`. Others adopt on demand.
96+
97+
## Legacy Marketplace Compatibility
98+
99+
Keep the root `.claude-plugin/marketplace.json` and each plugin's
100+
`.claude-plugin/plugin.json` as JSON mirrors of their Open Plugins counterparts.
101+
The shared root marketplace must stay dual-compatible: use repository-root-relative
102+
plugin `source` paths and preserve legacy `category`/`tags` fields alongside Open
103+
Plugins metadata. Existing marketplace subscriptions may still resolve the legacy
104+
paths during auto-update, so removing or drifting these files can force users to
105+
reinstall. Because mirrors are committed files (not symlinks), update both source
106+
and legacy copies together, then run
107+
`node scripts/validate-legacy-compatibility.js` after metadata changes.
108+
109+
## Code Conventions
110+
111+
**DRY (Don't Repeat Yourself):** Never duplicate logic across files. Each plugin has shared utilities (e.g., `scripts/lib/`) and shared reference docs (e.g., `references/`). Always check for and reuse existing helpers before writing new code. When adding shared logic, put it in the plugin's shared modules — not in individual skill directories.
112+
113+
### Code comments
114+
115+
Most code in this repo is Node.js scripts and hooks that shell out to `pac`/`az`, call the Dataverse and Power Platform APIs, and parse loosely structured CLI output. The reasoning behind a line is rarely obvious from the line alone, so comments matter.
116+
117+
* Err on the side of over-commenting code when the reasoning is not obvious. Comments should explain **WHY** code is written a particular way; the **WHY** is the most important part.
118+
* Do comment non-obvious implementation details: concurrency hazards, lifecycle constraints, compatibility requirements, platform quirks, upstream PAC CLI / Dataverse workarounds, and intentional deviations from the obvious helper or API.
119+
* When parsing strings, logs, CLI output, OData payloads, or other loosely structured data, include a comment with an example of the raw format being parsed. Show edge cases, escaping rules, delimiters, optional fields, or malformed-but-observed inputs when they affect the parser.
120+
* When code follows an external standard, protocol, or Power Platform convention (Dataverse status codes, OData error shapes, telemetry field contracts), include valid links to the relevant Microsoft Learn or specification source so future readers can verify the rule and understand why the code follows it.
121+
* When code touches telemetry, auth tokens, or anything privacy/security-sensitive, explain the scope, the opt-in/fail-closed behavior, and **why** — not just what it does.
122+
* Do not add comments that simply narrate clear code, such as "set the interval" immediately before assigning an interval.
123+
* Keep workaround comments close to the workaround. Include an issue link when the workaround is tied to an upstream bug, and describe the condition for removing it when that is known.
124+
125+
Good comments explain the constraint or tradeoff:
126+
127+
```javascript
128+
// `pac auth who` cold-starts the .NET runtime (~4s on Windows), so cache the parsed
129+
// result per process — repeated hook invocations must only fork the CLI once.
130+
let cachedAuth;
131+
```
132+
133+
```javascript
134+
// Refresh the bearer token roughly every 60s instead of on every poll. A long solution
135+
// export outlives the token's lifetime, but refreshing each 5s cycle would hammer the
136+
// az CLI for no benefit.
137+
const tokenRefreshEvery = Math.max(1, Math.floor(60000 / intervalMs));
138+
```
139+
140+
```javascript
141+
// Telemetry must never break the hook it runs inside, so this is fail-closed: a missing
142+
// executable, a timeout, or an unparseable banner all resolve to null rather than throw.
143+
return null;
144+
```
145+
146+
```javascript
147+
// Allowlist-only scrubbing: the event spec already restricts payload fields to values
148+
// that cannot carry PII, so this is a documented seam for a future regex pass — not a
149+
// no-op left unfinished by mistake.
150+
function scrub(value) {
151+
return value;
152+
}
153+
```
154+
155+
Code that follows an external standard or convention should link the source:
156+
157+
```javascript
158+
// Dataverse asyncoperations terminal states: statecode 3 (Completed) with statuscode 30
159+
// (Succeeded) means done; 31 (Failed) and 32 (Canceled) are the failure terminals.
160+
// See: https://learn.microsoft.com/en-us/power-apps/developer/data-platform/reference/entities/asyncoperation
161+
if (statecode === 3 && statuscode === 30) {
162+
return { status: 'Succeeded' };
163+
}
164+
```
165+
166+
Keep workaround comments next to the workaround and link the tracking issue:
167+
168+
```javascript
169+
// Workaround: `pac solution export` can exit 0 while the Dataverse async job is still
170+
// running, so ignore the exit code and poll asyncoperations to a terminal state instead.
171+
// Remove once the CLI blocks on the job result.
172+
// Tracking: https://github.com/microsoft/power-platform-skills/issues/1234 (use the real issue)
173+
const status = await pollAsyncOperation(asyncJobId, envUrl, token);
174+
```
175+
176+
Parsing comments should show the raw shape and important edge cases:
177+
178+
```javascript
179+
// Parse the `pac auth who` banner, a label/value block, e.g.:
180+
// Authority: https://login.microsoftonline.com/<tenant>
181+
// Tenant ID: 00000000-0000-0000-0000-000000000000
182+
// User: user@contoso.com
183+
// Values can themselves contain ':' (URLs), so match only up to the first colon after
184+
// the label, then trim. The JSON profile files are intentionally NOT parsed — that
185+
// format is internal and varies across PAC CLI versions.
186+
// `label` is a fixed, code-controlled string (e.g. 'Tenant ID'), so it is safe to
187+
// interpolate into the pattern. If a label ever comes from untrusted input, escape it
188+
// first to avoid regex injection.
189+
const re = new RegExp('^\\s*' + label + '\\s*:\\s*(\\S.*?)\\s*$', 'im');
190+
```
191+
192+
```javascript
193+
// Dataverse OData errors arrive as:
194+
// { "error": { "code": "0x80040217", "message": "..." } }
195+
// but some PAC surfaces capitalize the envelope as "Error", so check both before
196+
// falling back to plain-text pattern matching.
197+
const odataError = parsed.error || parsed.Error;
198+
```
199+
200+
Avoid comments that restate the code:
201+
202+
```javascript
203+
// Set the interval to five seconds.
204+
const intervalMs = 5000;
205+
206+
// Loop over the findings.
207+
for (const finding of findings) {
208+
report(finding);
209+
}
210+
```
211+
212+
## Maintaining This File
213+
214+
When you add new plugins or change the repository-level structure, update this file. For plugin-specific changes, update the plugin's own `AGENTS.md` (e.g., `plugins/power-pages/AGENTS.md`).
215+
216+
## External Documentation
217+
218+
- <a href="https://learn.microsoft.com/en-us/power-pages/configure/create-code-sites">Power Pages Code Sites</a>
219+
- <a href="https://learn.microsoft.com/en-us/power-platform/developer/cli/reference/pages">PAC CLI Reference</a>
220+
- <a href="https://learn.microsoft.com/en-us/rest/api/power-platform/powerpages/websites/create-website">Create Website API</a>

plugins/canvas-apps/CLAUDE.md

Lines changed: 0 additions & 1 deletion
This file was deleted.

plugins/canvas-apps/CLAUDE.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# AGENTS.md — Canvas Apps Plugin
2+
3+
This file provides guidance to AI Agents when working with the **canvas-apps** plugin.
4+
5+
## What This Plugin Is
6+
7+
A plugin for authoring Power Apps Canvas Apps. The Canvas Authoring MCP server (`CanvasAuthoringMcpServer`) exposes tools that agents use to generate, validate, and compile Canvas App YAML files (`.pa.yaml`) in conjunction with a running coauthoring studio session. The Power Apps Studio browser tab must remain open for the duration of the session — closing it ends the coauthoring session, which breaks `compile_canvas` and `sync_canvas` operations.
8+
9+
Skills orchestrate specialist agents via the `Task` tool. Agents are not invoked directly by users.
10+
11+
## Local Development
12+
13+
Test this plugin locally:
14+
15+
```bash
16+
claude --plugin-dir /path/to/plugins/canvas-apps
17+
```
18+
19+
## Architecture
20+
21+
```
22+
.plugin/plugin.json ← Open Plugins metadata (name, version, keywords)
23+
.mcp.json ← MCP server config (canvas-authoring, auto-registered)
24+
AGENTS.md ← Plugin guidance for AI agents (this file)
25+
CLAUDE.md ← Symlink → AGENTS.md
26+
references/
27+
TechnicalGuide.md ← YAML syntax, control selection, layout strategies, Power Fx patterns
28+
DesignGuide.md ← Aesthetic guidelines, anti-patterns, design process
29+
QAChecks.md ← Runtime anti-pattern checks for self-QA
30+
PlanTemplates.md ← CREATE and EDIT plan document structures for canvas-app-planner
31+
agents/
32+
canvas-app-planner.md ← Discovers resources and writes plan document; invoked by canvas-app
33+
canvas-screen-builder.md ← Builds or modifies one screen; invoked by canvas-app (parallel)
34+
skills/
35+
canvas-app/
36+
SKILL.md ← Unified skill: create or edit a Canvas App (auto-detects mode)
37+
configure-canvas-mcp/
38+
SKILL.md ← Registers the Canvas Authoring MCP server with Claude Code
39+
add-data-source/
40+
SKILL.md ← Guides user to add a data source or connector in Studio, then verifies
41+
generate-canvas-app/
42+
SKILL.md ← [DEPRECATED] Redirects to canvas-app
43+
```
44+
45+
## Skills
46+
47+
| Skill | Description |
48+
|-------|-------------|
49+
| `/canvas-app` | Create or edit a Canvas App — auto-detects whether to generate from scratch or edit existing |
50+
| `/configure-canvas-mcp` | Configure the Canvas Authoring MCP server for the current coauthoring session |
51+
| `/add-data-source` | Guide the user to add a data source, connection, or API connector in Studio, then verify it is available |
52+
53+
## Agents
54+
55+
Agents are invoked by skills via the `Task` tool — they are not user-invocable.
56+
57+
| Agent | Invoked By | Description |
58+
|-------|-----------|-------------|
59+
| `canvas-app-planner` | `canvas-app` | Receives the approved plan from the skill. Discovers available controls, APIs, and data sources; gathers control property definitions (`describe_control`); writes `App.pa.yaml` (CREATE mode) and `canvas-app-plan.md` for downstream screen builders. |
60+
| `canvas-screen-builder` | `canvas-app` | For Create actions: writes YAML for one new screen based on the plan. For Modify actions: applies targeted edits to one existing screen. Runs in parallel with other builders; validation is performed later by `canvas-app` using `compile_canvas`. |
61+
62+
## MCP Tools
63+
64+
The `canvas-authoring` MCP server exposes the following tools:
65+
66+
| Tool | Description |
67+
|------|-------------|
68+
| `configure` | Configures the MCP server for a specific coauthoring session (environment ID, app ID, cluster category) |
69+
| `compile_canvas` | Validates canvas app YAML files in a directory using the Power Apps authoring service |
70+
| `describe_api` | Gets detailed information about a specific API (connector) including its operations and parameters |
71+
| `describe_control` | Gets detailed information about a specific Power Apps control including properties, variants, and metadata |
72+
| `get_data_source_schema` | Gets the schema (columns and their Power Fx types) for a specific data source in the current authoring session |
73+
| `list_apis` | Lists all available APIs (connectors) in the current authoring session |
74+
| `list_controls` | Lists all available Power Apps controls in the current authoring session |
75+
| `list_data_sources` | Lists all available data sources in the current authoring session |
76+
| `sync_canvas` | Syncs the current coauthoring session state from the server to a local directory, writing all YAML files |
77+
78+
## Prerequisites
79+
80+
Before the MCP server will start, you need:
81+
82+
**.NET 10 SDK**[Download from Microsoft](https://dotnet.microsoft.com/download/dotnet/10.0)

plugins/model-apps/CLAUDE.md

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)