diff --git a/CHANGELOG.md b/CHANGELOG.md index c1a7a86..4b2ba8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +# [1.29.0](https://github.com/forcedotcom/sf-skills/compare/1.28.0...1.29.0) (2026-07-03) + + +### Features + +* Release 2 new skills: platform-value-set-generate, dx-code-analyzer-custom-rule-create @W-23286064@ ([e07b6b3](https://github.com/forcedotcom/sf-skills/commit/e07b6b37b399107249f056213cf73f7e6d469653)) + + + +# [1.28.0](https://github.com/forcedotcom/sf-skills/compare/1.27.0...1.28.0) (2026-07-03) + + +### Features + +* Release: Standardize skill folder structure scripts/, references/, assets/ @W-23262449@ ([3a358c3](https://github.com/forcedotcom/sf-skills/commit/3a358c3b1cce0e0df8c6ba09cc7cd48bb0295341)) + + + # [1.27.0](https://github.com/forcedotcom/sf-skills/compare/1.26.0...1.27.0) (2026-06-29) diff --git a/package.json b/package.json index ccce48d..fb06b9c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@salesforce/afv-skills", - "version": "1.27.0", + "version": "1.29.0", "description": "Salesforce skills for Agentforce Vibes", "license": "CC-BY-NC-4.0", "files": [ diff --git a/skills/agentforce-architecture-analyze/README.md b/skills/agentforce-architecture-analyze/README.md index 72a2a45..f0e44c7 100644 --- a/skills/agentforce-architecture-analyze/README.md +++ b/skills/agentforce-architecture-analyze/README.md @@ -73,12 +73,11 @@ agentforce-architecture-analyze/ │ ├── finalize.py Merge waves → metadata_tree.json │ ├── render_architecture.py architecture.md + Mermaid graph │ ├── resolve_invocation_target.py ID-prefix router for NGA InvocationTargets +│ ├── emit_env.py Env-var emit helper (Phase 0.5) +│ ├── emit_result.py Final RESULT block renderer +│ ├── sanitize.py Stdin → safe-string filter +│ ├── write_emit_ctx.py Per-phase ctx writer │ └── tests/ Unit + integration tests (unittest) -└── tools/ - ├── emit_env.py Env-var emit helper (Phase 0.5) - ├── emit_result.py Final RESULT block renderer - ├── sanitize.py Stdin → safe-string filter - └── write_emit_ctx.py Per-phase ctx writer ``` --- diff --git a/skills/agentforce-architecture-analyze/SKILL.md b/skills/agentforce-architecture-analyze/SKILL.md index 1f52efe..4b36bba 100644 --- a/skills/agentforce-architecture-analyze/SKILL.md +++ b/skills/agentforce-architecture-analyze/SKILL.md @@ -135,7 +135,7 @@ set -e # Final RESULT block is emit_result.py's stdout — MUST be the last thing # stdout sees. emit_result exits 0 on render success; the bash harness # propagates main.py's rc for the agent's exit status. -WORK_DIR="$WORK_DIR" python3 "$SKILL_ROOT/tools/emit_result.py" +WORK_DIR="$WORK_DIR" python3 "$SKILL_ROOT/scripts/emit_result.py" exit "$_rc" ``` diff --git a/skills/agentforce-architecture-analyze/tools/emit_env.py b/skills/agentforce-architecture-analyze/scripts/emit_env.py similarity index 100% rename from skills/agentforce-architecture-analyze/tools/emit_env.py rename to skills/agentforce-architecture-analyze/scripts/emit_env.py diff --git a/skills/agentforce-architecture-analyze/tools/emit_result.py b/skills/agentforce-architecture-analyze/scripts/emit_result.py similarity index 100% rename from skills/agentforce-architecture-analyze/tools/emit_result.py rename to skills/agentforce-architecture-analyze/scripts/emit_result.py diff --git a/skills/agentforce-architecture-analyze/tools/sanitize.py b/skills/agentforce-architecture-analyze/scripts/sanitize.py similarity index 93% rename from skills/agentforce-architecture-analyze/tools/sanitize.py rename to skills/agentforce-architecture-analyze/scripts/sanitize.py index 6c3864f..a0c0f5c 100755 --- a/skills/agentforce-architecture-analyze/tools/sanitize.py +++ b/skills/agentforce-architecture-analyze/scripts/sanitize.py @@ -10,7 +10,7 @@ or malformed input could inject fake KEY=VALUE pairs that downstream consumers would treat as authoritative. Usage: - _safe=$(python3 "$SKILL/tools/sanitize.py" "$raw_value") + _safe=$(python3 "$SKILL/scripts/sanitize.py" "$raw_value") Arguments: argv[1] raw string (may be empty) diff --git a/skills/agentforce-architecture-analyze/scripts/tests/_bootstrap.py b/skills/agentforce-architecture-analyze/scripts/tests/_bootstrap.py index 8486742..88e1e79 100644 --- a/skills/agentforce-architecture-analyze/scripts/tests/_bootstrap.py +++ b/skills/agentforce-architecture-analyze/scripts/tests/_bootstrap.py @@ -6,7 +6,7 @@ does `from config import ...`. Tests mirror that by inserting scripts/ onto sys.path. fs_guard is now sourced via ``from config import fs_guard`` (config.py -re-exports it from the plugin _shared/ package). No tools/ entry on +re-exports it from the plugin _shared/ package). No separate entry on sys.path is required — config.py's dev-fallback walks up to the repo's ``plugins/investigating-agentforce-architecture/shared/`` when ``scripts/_shared/`` hasn't yet been mirrored by install.sh / the build script. diff --git a/skills/agentforce-architecture-analyze/scripts/tests/test_end_to_end_fixture.py b/skills/agentforce-architecture-analyze/scripts/tests/test_end_to_end_fixture.py index a03dcb4..beb6b74 100644 --- a/skills/agentforce-architecture-analyze/scripts/tests/test_end_to_end_fixture.py +++ b/skills/agentforce-architecture-analyze/scripts/tests/test_end_to_end_fixture.py @@ -44,10 +44,10 @@ from tests.fixtures import genai_payloads as fx # type: ignore _REPO_SOQL_DIR = Path(__file__).resolve().parent.parent.parent / "assets" / "soql" soql_loader.SOQL_DIR = _REPO_SOQL_DIR -# emit_result.py lives under tools/; tests invoke it via subprocess so the +# emit_result.py lives under scripts/; tests invoke it via subprocess so the # tool's RESULT-formatting logic is exercised under the same contract the # SKILL.md Bash wrapper uses. -_TOOLS_DIR = Path(__file__).resolve().parent.parent.parent / "tools" +_TOOLS_DIR = Path(__file__).resolve().parent.parent # --------------------------------------------------------------------------- diff --git a/skills/agentforce-architecture-analyze/scripts/tests/test_write_emit_ctx.py b/skills/agentforce-architecture-analyze/scripts/tests/test_write_emit_ctx.py index 4db3d24..c15f078 100644 --- a/skills/agentforce-architecture-analyze/scripts/tests/test_write_emit_ctx.py +++ b/skills/agentforce-architecture-analyze/scripts/tests/test_write_emit_ctx.py @@ -32,9 +32,9 @@ from . import _bootstrap # noqa: F401 def _tools_dir() -> pathlib.Path: - """Absolute path to the skill's tools/ dir — where the scripts live.""" + """Absolute path to the skill's scripts/ dir — where the emit helpers live.""" here = pathlib.Path(__file__).resolve() - return here.parent.parent.parent / "tools" + return here.parent.parent def _run_script(script_name: str, env: dict) -> subprocess.CompletedProcess: @@ -439,7 +439,7 @@ class EmitResultPositionalSafetyTests(unittest.TestCase): def test_build_block_happy_path_auto_promotes_without_asserting(self): """Sanity: the assertion does NOT fire under normal operation.""" - # Force import of emit_result from the tools/ dir. + # Force import of emit_result from the scripts/ dir. import importlib.util spec = importlib.util.spec_from_file_location( "emit_result_mod", _tools_dir() / "emit_result.py", diff --git a/skills/agentforce-architecture-analyze/tools/write_emit_ctx.py b/skills/agentforce-architecture-analyze/scripts/write_emit_ctx.py similarity index 99% rename from skills/agentforce-architecture-analyze/tools/write_emit_ctx.py rename to skills/agentforce-architecture-analyze/scripts/write_emit_ctx.py index 756d90a..55d7a26 100755 --- a/skills/agentforce-architecture-analyze/tools/write_emit_ctx.py +++ b/skills/agentforce-architecture-analyze/scripts/write_emit_ctx.py @@ -83,7 +83,7 @@ import sys # local, dependency-free token scrub mirroring the patterns in # `scripts/rest_client.redact_text`. Keeping the redactor inline preserves -# the tools/ <-> scripts/ decoupling (tools/ is stdlib-only by policy) and +# the scripts/ emit helpers (stdlib-only by policy) and # avoids adding a sys.path hop on every write_emit_ctx invocation. The two # implementations must stay in sync; the shared regex shapes are: # * Authorization: Bearer diff --git a/skills/agentforce-observe/apex/AgentforceOptimizeService.cls b/skills/agentforce-observe/assets/apex/AgentforceOptimizeService.cls similarity index 100% rename from skills/agentforce-observe/apex/AgentforceOptimizeService.cls rename to skills/agentforce-observe/assets/apex/AgentforceOptimizeService.cls diff --git a/skills/agentforce-observe/apex/AgentforceOptimizeService.cls-meta.xml b/skills/agentforce-observe/assets/apex/AgentforceOptimizeService.cls-meta.xml similarity index 100% rename from skills/agentforce-observe/apex/AgentforceOptimizeService.cls-meta.xml rename to skills/agentforce-observe/assets/apex/AgentforceOptimizeService.cls-meta.xml diff --git a/skills/agentforce-observe/references/stdm-queries.md b/skills/agentforce-observe/references/stdm-queries.md index 1e0851d..b214031 100644 --- a/skills/agentforce-observe/references/stdm-queries.md +++ b/skills/agentforce-observe/references/stdm-queries.md @@ -24,9 +24,9 @@ Methods: mkdir -p /force-app/main/default/classes # Copy from the skill's apex directory -cp ../apex/AgentforceOptimizeService.cls \ +cp ../assets/apex/AgentforceOptimizeService.cls \ /force-app/main/default/classes/ -cp ../apex/AgentforceOptimizeService.cls-meta.xml \ +cp ../assets/apex/AgentforceOptimizeService.cls-meta.xml \ /force-app/main/default/classes/ ``` diff --git a/skills/automation-flow-generate/SKILL.md b/skills/automation-flow-generate/SKILL.md index c26a161..4fe780e 100644 --- a/skills/automation-flow-generate/SKILL.md +++ b/skills/automation-flow-generate/SKILL.md @@ -189,7 +189,7 @@ When no custom objects needed: - `referenceTo`: (Lookup only) The target object API name - Include only objects and fields that are relevant to the flow being generated -## 🎯 Mandatory Enhancement Rules +## Mandatory Enhancement Rules - **userPrompt**: REQUIRED. - If the user requests a **single flow**: use the user's prompt as-is. - If the user requests **multiple flows**: you MUST **split** the request and write a **separate, focused `userPrompt` for each individual flow**. Each `userPrompt` must describe only ONE flow. Do NOT pass the entire multi-flow request as a single `userPrompt`. See the multiple flows section below for examples. diff --git a/skills/design-systems-slds-apply/SKILL.md b/skills/design-systems-slds-apply/SKILL.md index f1df972..8a979b3 100644 --- a/skills/design-systems-slds-apply/SKILL.md +++ b/skills/design-systems-slds-apply/SKILL.md @@ -85,7 +85,7 @@ If no LBC exists (or not using LWC), select an SLDS Blueprint. See [references/c ## Hook Naming Traps -SLDS hook families do NOT all follow the same naming pattern. Agents frequently invent hooks that don't exist by assuming `{prefix}-{number}` works universally. **Always verify a hook exists** via the bundled `search-hooks.cjs` script or `metadata/hooks-index.json` before using it. +SLDS hook families do NOT all follow the same naming pattern. Agents frequently invent hooks that don't exist by assuming `{prefix}-{number}` works universally. **Always verify a hook exists** via the bundled `search-hooks.cjs` script or `assets/hooks-index.json` before using it. ### Trap 1: Font size hooks are NOT numbered @@ -130,10 +130,10 @@ Run the appropriate search command **before** emitting any SLDS artifact: | Artifact | Verification command | Source of truth | |----------|---------------------|-----------------| -| Styling hook (`--slds-g-*`) | `node scripts/search-hooks.cjs --prefix ""` | `metadata/hooks-index.json` | -| Utility class (`slds-*`) | `node scripts/search-utilities.cjs --search ""` | `metadata/utilities-index.json` | -| Blueprint / CSS class | `node scripts/search-blueprints.cjs --search ""` then read the YAML | `metadata/blueprints/components/*.yaml` | -| Icon | `node scripts/search-icons.cjs --query ""` | `metadata/icon-metadata.json` | +| Styling hook (`--slds-g-*`) | `node scripts/search-hooks.cjs --prefix ""` | `assets/hooks-index.json` | +| Utility class (`slds-*`) | `node scripts/search-utilities.cjs --search ""` | `assets/utilities-index.json` | +| Blueprint / CSS class | `node scripts/search-blueprints.cjs --search ""` then read the YAML | `assets/blueprints/components/*.yaml` | +| Icon | `node scripts/search-icons.cjs --query ""` | `assets/icon-metadata.json` | If the search returns no match: **do not use the artifact.** Find an alternative from the search results or build custom with verified hooks. @@ -187,10 +187,10 @@ This skill bundles comprehensive SLDS knowledge. Read files as needed -- don't r | Folder | Content | Index | |--------|---------|-------| -| `guidance/overviews/` | Foundational concepts (color, spacing, typography, etc.) | [guidance/README.md](guidance/README.md) | -| `guidance/styling-hooks/` | Hook categories with detailed usage | [guidance/README.md](guidance/README.md) | -| `guidance/utilities/` | 27 utility class categories | [guidance/README.md](guidance/README.md) | -| `guidance/slds-development-guide.md` | Full SLDS development guide | -- | +| `references/overviews/` | Foundational concepts (color, spacing, typography, etc.) | [references/README.md](references/README.md) | +| `references/styling-hooks/` | Hook categories with detailed usage | [references/README.md](references/README.md) | +| `references/utilities/` | 27 utility class categories | [references/README.md](references/README.md) | +| `references/slds-development-guide.md` | Full SLDS development guide | -- | ### Raw Metadata (structured data for lookup) @@ -198,10 +198,10 @@ This skill bundles comprehensive SLDS knowledge. Read files as needed -- don't r | File | Content | Lines | |------|---------|-------| -| `metadata/blueprints/components/*.yaml` | 85 blueprint specs (classes, variants, a11y, HTML) | ~50-200 each | -| `metadata/hooks-index.json` | 523 hooks with values and CSS properties | ~6,300 | -| `metadata/icon-metadata.json` | 1,732 icons with synonyms for search | ~38,500 | -| `metadata/utilities-index.json` | 1,147 utility classes with CSS rules | ~6,900 | +| `assets/blueprints/components/*.yaml` | 85 blueprint specs (classes, variants, a11y, HTML) | ~50-200 each | +| `assets/hooks-index.json` | 523 hooks with values and CSS properties | ~6,300 | +| `assets/icon-metadata.json` | 1,732 icons with synonyms for search | ~38,500 | +| `assets/utilities-index.json` | 1,147 utility classes with CSS rules | ~6,900 | --- @@ -219,7 +219,7 @@ Identify: 1. **If LWC**: Check the [Lightning Component Library](https://developer.salesforce.com/docs/component-library/overview/components) for an LBC 2. **Search blueprints**: `node scripts/search-blueprints.cjs --search ""` -3. **Read the blueprint YAML**: `metadata/blueprints/components/.yaml` for exact classes, modifiers, states, and accessibility requirements +3. **Read the blueprint YAML**: `assets/blueprints/components/.yaml` for exact classes, modifiers, states, and accessibility requirements 4. **No match?** Build custom with hooks (see Phase 3) Details: [references/component-selection.md](references/component-selection.md) @@ -249,7 +249,7 @@ npx @salesforce-ux/slds-linter@latest lint The linter catches hardcoded values, class overrides, and deprecated tokens. **Fix all violations before proceeding.** Do not rationalize violations as acceptable. -**Step 2: Verify no invented hooks.** Confirm every `--slds-g-*` hook in the output exists in `metadata/hooks-index.json`. Cross-reference against the T051 check in [checklists.md](checklists.md). +**Step 2: Verify no invented hooks.** Confirm every `--slds-g-*` hook in the output exists in `assets/hooks-index.json`. Cross-reference against the T051 check in [checklists.md](checklists.md). **Step 3: Run through [checklists.md](checklists.md)** for the checks the linter cannot automate: - All `var(--slds-g-*)` have fallback values (T002) diff --git a/skills/design-systems-slds-apply/metadata/README.md b/skills/design-systems-slds-apply/assets/README.md similarity index 100% rename from skills/design-systems-slds-apply/metadata/README.md rename to skills/design-systems-slds-apply/assets/README.md diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/accordion.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/accordion.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/accordion.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/accordion.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/activity-timeline.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/activity-timeline.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/activity-timeline.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/activity-timeline.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/alert.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/alert.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/alert.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/alert.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/app-launcher.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/app-launcher.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/app-launcher.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/app-launcher.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/avatar-group.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/avatar-group.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/avatar-group.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/avatar-group.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/avatar.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/avatar.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/avatar.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/avatar.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/badges.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/badges.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/badges.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/badges.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/brand-band.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/brand-band.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/brand-band.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/brand-band.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/breadcrumbs.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/breadcrumbs.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/breadcrumbs.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/breadcrumbs.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/builder-header.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/builder-header.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/builder-header.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/builder-header.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/button-groups.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/button-groups.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/button-groups.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/button-groups.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/button-icons.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/button-icons.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/button-icons.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/button-icons.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/buttons.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/buttons.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/buttons.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/buttons.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/cards.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/cards.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/cards.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/cards.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/carousel.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/carousel.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/carousel.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/carousel.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/chat.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/chat.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/chat.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/chat.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/checkbox-button-group.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/checkbox-button-group.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/checkbox-button-group.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/checkbox-button-group.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/checkbox-button.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/checkbox-button.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/checkbox-button.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/checkbox-button.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/checkbox-toggle.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/checkbox-toggle.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/checkbox-toggle.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/checkbox-toggle.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/checkbox.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/checkbox.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/checkbox.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/checkbox.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/color-picker.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/color-picker.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/color-picker.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/color-picker.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/combobox.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/combobox.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/combobox.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/combobox.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/counter.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/counter.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/counter.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/counter.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/data-tables.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/data-tables.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/data-tables.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/data-tables.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/datepickers.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/datepickers.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/datepickers.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/datepickers.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/datetime-picker.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/datetime-picker.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/datetime-picker.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/datetime-picker.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/docked-composer.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/docked-composer.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/docked-composer.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/docked-composer.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/docked-form-footer.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/docked-form-footer.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/docked-form-footer.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/docked-form-footer.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/docked-utility-bar.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/docked-utility-bar.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/docked-utility-bar.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/docked-utility-bar.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/drop-zone.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/drop-zone.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/drop-zone.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/drop-zone.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/dueling-picklist.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/dueling-picklist.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/dueling-picklist.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/dueling-picklist.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/dynamic-icons.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/dynamic-icons.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/dynamic-icons.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/dynamic-icons.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/dynamic-menu.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/dynamic-menu.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/dynamic-menu.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/dynamic-menu.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/expandable-section.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/expandable-section.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/expandable-section.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/expandable-section.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/expression.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/expression.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/expression.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/expression.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/feeds.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/feeds.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/feeds.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/feeds.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/file-selector.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/file-selector.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/file-selector.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/file-selector.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/files.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/files.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/files.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/files.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/form-element.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/form-element.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/form-element.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/form-element.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/global-header.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/global-header.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/global-header.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/global-header.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/global-navigation.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/global-navigation.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/global-navigation.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/global-navigation.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/icons.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/icons.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/icons.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/icons.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/illustration.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/illustration.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/illustration.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/illustration.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/input.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/input.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/input.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/input.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/list-builder.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/list-builder.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/list-builder.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/list-builder.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/lookups.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/lookups.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/lookups.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/lookups.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/map.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/map.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/map.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/map.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/menus.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/menus.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/menus.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/menus.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/modals.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/modals.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/modals.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/modals.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/notifications.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/notifications.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/notifications.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/notifications.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/page-headers.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/page-headers.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/page-headers.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/page-headers.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/panels.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/panels.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/panels.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/panels.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/path.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/path.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/path.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/path.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/picklist.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/picklist.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/picklist.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/picklist.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/pills.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/pills.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/pills.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/pills.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/popovers.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/popovers.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/popovers.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/popovers.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/progress-bar.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/progress-bar.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/progress-bar.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/progress-bar.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/progress-indicator.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/progress-indicator.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/progress-indicator.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/progress-indicator.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/progress-ring.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/progress-ring.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/progress-ring.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/progress-ring.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/prompt.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/prompt.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/prompt.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/prompt.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/publishers.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/publishers.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/publishers.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/publishers.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/radio-button-group.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/radio-button-group.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/radio-button-group.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/radio-button-group.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/radio-group.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/radio-group.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/radio-group.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/radio-group.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/rich-text-editor.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/rich-text-editor.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/rich-text-editor.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/rich-text-editor.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/scoped-notifications.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/scoped-notifications.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/scoped-notifications.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/scoped-notifications.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/scoped-tabs.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/scoped-tabs.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/scoped-tabs.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/scoped-tabs.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/select.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/select.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/select.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/select.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/setup-assistant.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/setup-assistant.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/setup-assistant.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/setup-assistant.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/slider.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/slider.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/slider.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/slider.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/spinners.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/spinners.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/spinners.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/spinners.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/split-view.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/split-view.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/split-view.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/split-view.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/summary-detail.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/summary-detail.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/summary-detail.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/summary-detail.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/tabs.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/tabs.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/tabs.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/tabs.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/textarea.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/textarea.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/textarea.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/textarea.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/tiles.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/tiles.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/tiles.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/tiles.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/timepicker.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/timepicker.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/timepicker.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/timepicker.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/toast.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/toast.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/toast.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/toast.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/tooltips.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/tooltips.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/tooltips.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/tooltips.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/tree-grid.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/tree-grid.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/tree-grid.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/tree-grid.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/trees.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/trees.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/trees.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/trees.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/trial-bar.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/trial-bar.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/trial-bar.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/trial-bar.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/vertical-navigation.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/vertical-navigation.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/vertical-navigation.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/vertical-navigation.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/vertical-tabs.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/vertical-tabs.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/vertical-tabs.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/vertical-tabs.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/visual-picker.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/visual-picker.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/visual-picker.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/visual-picker.yaml diff --git a/skills/design-systems-slds-apply/metadata/blueprints/components/welcome-mat.yaml b/skills/design-systems-slds-apply/assets/blueprints/components/welcome-mat.yaml similarity index 100% rename from skills/design-systems-slds-apply/metadata/blueprints/components/welcome-mat.yaml rename to skills/design-systems-slds-apply/assets/blueprints/components/welcome-mat.yaml diff --git a/skills/design-systems-slds-apply/metadata/hooks-index.json b/skills/design-systems-slds-apply/assets/hooks-index.json similarity index 100% rename from skills/design-systems-slds-apply/metadata/hooks-index.json rename to skills/design-systems-slds-apply/assets/hooks-index.json diff --git a/skills/design-systems-slds-apply/metadata/icon-metadata.json b/skills/design-systems-slds-apply/assets/icon-metadata.json similarity index 100% rename from skills/design-systems-slds-apply/metadata/icon-metadata.json rename to skills/design-systems-slds-apply/assets/icon-metadata.json diff --git a/skills/design-systems-slds-apply/metadata/utilities-index.json b/skills/design-systems-slds-apply/assets/utilities-index.json similarity index 100% rename from skills/design-systems-slds-apply/metadata/utilities-index.json rename to skills/design-systems-slds-apply/assets/utilities-index.json diff --git a/skills/design-systems-slds-apply/checklists.md b/skills/design-systems-slds-apply/checklists.md index 7a43725..b0d2bbb 100644 --- a/skills/design-systems-slds-apply/checklists.md +++ b/skills/design-systems-slds-apply/checklists.md @@ -22,7 +22,7 @@ Code produced by this skill should score high on T-series checks. | **Shadow hooks** | Shadows use `var(--slds-g-shadow-*)` | T040 | | **Border radius hooks** | Border radius uses `var(--slds-g-radius-*)` | T041 | | **Color hooks numbered** | Every `--slds-g-color-*` hook ends in a number (no bare `on-surface`, `on-accent`, etc.) | T050 | -| **No invented hooks** | Every hook referenced actually exists in `metadata/hooks-index.json` | T051 | +| **No invented hooks** | Every hook referenced actually exists in `assets/hooks-index.json` | T051 | | **No hardcoded colors** | No hex, rgb, or named colors (linter also catches this) | linter | | **No class overrides** | No `.slds-*` class overrides (linter also catches this) | linter | | **No deprecated tokens** | No `--lwc-*` tokens (linter also catches this) | linter | diff --git a/skills/design-systems-slds-apply/examples.md b/skills/design-systems-slds-apply/examples.md index b2180e8..205d130 100644 --- a/skills/design-systems-slds-apply/examples.md +++ b/skills/design-systems-slds-apply/examples.md @@ -24,7 +24,7 @@ node scripts/search-blueprints.cjs --search "modal" # Found: Modals (category: Overlay, root: slds-modal) ``` -**Read blueprint YAML** for class details: `metadata/blueprints/components/modals.yaml` +**Read blueprint YAML** for class details: `assets/blueprints/components/modals.yaml` Key takeaway: `LightningModal` handles the `slds-modal`, `slds-backdrop`, and ARIA attributes automatically. No need to apply blueprint classes manually in LWC. @@ -99,8 +99,8 @@ node scripts/search-blueprints.cjs --search "badge" ``` **Read YAMLs:** -- `metadata/blueprints/components/cards.yaml` -- classes: `slds-card`, `slds-card__header`, `slds-card__body`, `slds-card__footer` -- `metadata/blueprints/components/badges.yaml` -- classes: `slds-badge`, modifiers: `slds-badge_lightest`, `slds-badge_inverse` +- `assets/blueprints/components/cards.yaml` -- classes: `slds-card`, `slds-card__header`, `slds-card__body`, `slds-card__footer` +- `assets/blueprints/components/badges.yaml` -- classes: `slds-badge`, modifiers: `slds-badge_lightest`, `slds-badge_inverse` ### Phase 3: Apply Styling diff --git a/skills/design-systems-slds-apply/guidance/README.md b/skills/design-systems-slds-apply/references/README.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/README.md rename to skills/design-systems-slds-apply/references/README.md diff --git a/skills/design-systems-slds-apply/guidance/blueprints-index.md b/skills/design-systems-slds-apply/references/blueprints-index.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/blueprints-index.md rename to skills/design-systems-slds-apply/references/blueprints-index.md diff --git a/skills/design-systems-slds-apply/references/component-selection.md b/skills/design-systems-slds-apply/references/component-selection.md index de0dac5..8806d6f 100644 --- a/skills/design-systems-slds-apply/references/component-selection.md +++ b/skills/design-systems-slds-apply/references/component-selection.md @@ -68,7 +68,7 @@ node scripts/search-blueprints.cjs --name "modals" ### How to read a blueprint YAML -Each file in `metadata/blueprints/components/{name}.yaml` contains: +Each file in `assets/blueprints/components/{name}.yaml` contains: ```yaml name: "Modals" @@ -107,6 +107,6 @@ Rules for custom components: ## Deep Reference -- Full blueprint details: `guidance/blueprints-index.md` -- All blueprint YAMLs: `metadata/blueprints/components/` +- Full blueprint details: `references/blueprints-index.md` +- All blueprint YAMLs: `assets/blueprints/components/` - LBC documentation: [Lightning Component Library](https://developer.salesforce.com/docs/component-library/overview/components) diff --git a/skills/design-systems-slds-apply/references/icons-decision-guide.md b/skills/design-systems-slds-apply/references/icons-decision-guide.md index 4803299..2ebf1a4 100644 --- a/skills/design-systems-slds-apply/references/icons-decision-guide.md +++ b/skills/design-systems-slds-apply/references/icons-decision-guide.md @@ -119,6 +119,6 @@ Key classes: ## Deep Reference -- Icon implementation guidance: `guidance/icons-guidance.md` -- Icons overview: `guidance/overviews/icons.md` -- Full icon metadata (1,732 icons with synonyms): `metadata/icon-metadata.json` +- Icon implementation guidance: `references/icons-guidance.md` +- Icons overview: `references/overviews/icons.md` +- Full icon metadata (1,732 icons with synonyms): `assets/icon-metadata.json` diff --git a/skills/design-systems-slds-apply/guidance/icons-guidance.md b/skills/design-systems-slds-apply/references/icons-guidance.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/icons-guidance.md rename to skills/design-systems-slds-apply/references/icons-guidance.md diff --git a/skills/design-systems-slds-apply/guidance/overviews/borders.md b/skills/design-systems-slds-apply/references/overviews/borders.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/overviews/borders.md rename to skills/design-systems-slds-apply/references/overviews/borders.md diff --git a/skills/design-systems-slds-apply/guidance/overviews/color.md b/skills/design-systems-slds-apply/references/overviews/color.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/overviews/color.md rename to skills/design-systems-slds-apply/references/overviews/color.md diff --git a/skills/design-systems-slds-apply/guidance/overviews/display-density.md b/skills/design-systems-slds-apply/references/overviews/display-density.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/overviews/display-density.md rename to skills/design-systems-slds-apply/references/overviews/display-density.md diff --git a/skills/design-systems-slds-apply/guidance/overviews/icons.md b/skills/design-systems-slds-apply/references/overviews/icons.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/overviews/icons.md rename to skills/design-systems-slds-apply/references/overviews/icons.md diff --git a/skills/design-systems-slds-apply/guidance/overviews/illustrations.md b/skills/design-systems-slds-apply/references/overviews/illustrations.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/overviews/illustrations.md rename to skills/design-systems-slds-apply/references/overviews/illustrations.md diff --git a/skills/design-systems-slds-apply/guidance/overviews/shadows.md b/skills/design-systems-slds-apply/references/overviews/shadows.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/overviews/shadows.md rename to skills/design-systems-slds-apply/references/overviews/shadows.md diff --git a/skills/design-systems-slds-apply/guidance/overviews/spacing.md b/skills/design-systems-slds-apply/references/overviews/spacing.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/overviews/spacing.md rename to skills/design-systems-slds-apply/references/overviews/spacing.md diff --git a/skills/design-systems-slds-apply/guidance/overviews/typography.md b/skills/design-systems-slds-apply/references/overviews/typography.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/overviews/typography.md rename to skills/design-systems-slds-apply/references/overviews/typography.md diff --git a/skills/design-systems-slds-apply/guidance/overviews/utilities.md b/skills/design-systems-slds-apply/references/overviews/utilities.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/overviews/utilities.md rename to skills/design-systems-slds-apply/references/overviews/utilities.md diff --git a/skills/design-systems-slds-apply/guidance/slds-development-guide.md b/skills/design-systems-slds-apply/references/slds-development-guide.md similarity index 99% rename from skills/design-systems-slds-apply/guidance/slds-development-guide.md rename to skills/design-systems-slds-apply/references/slds-development-guide.md index 04ccb6b..c2912e4 100644 --- a/skills/design-systems-slds-apply/guidance/slds-development-guide.md +++ b/skills/design-systems-slds-apply/references/slds-development-guide.md @@ -158,13 +158,13 @@ export default class MyModal extends LightningModal { ## Core Rules -### Do ✅ +### Do - Follow hierarchy: LBC → Blueprints → Styling Hooks → Custom CSS - Use `var(--slds-g-*)` with fallbacks for all themeable values - Create custom classes (e.g., `my-*`) instead of overriding `.slds-*` - Verify components/hooks exist before implementing -### Don't ❌ +### Don't - Hard-code colors, spacing, or typography - Override `.slds-*` classes directly - Use deprecated `--lwc-*` tokens as primary values diff --git a/skills/design-systems-slds-apply/references/styling-decision-guide.md b/skills/design-systems-slds-apply/references/styling-decision-guide.md index 2b8336f..efaaf4e 100644 --- a/skills/design-systems-slds-apply/references/styling-decision-guide.md +++ b/skills/design-systems-slds-apply/references/styling-decision-guide.md @@ -24,7 +24,7 @@ Component hooks are scoped to specific Lightning Base Components. To find availa 2. **Browser DevTools**: Render the LBC, inspect the element, and look for `--slds-c-*` properties in the computed styles 3. **Known patterns**: Component hooks follow `--slds-c-{component}-{property}-{state}` naming, e.g., `--slds-c-button-success-shadow-hover` -There is no centralized metadata file for `--slds-c-*` hooks — they are documented per-component. Some examples exist in `guidance/overviews/shadows.md` and `guidance/styling-hooks/index.md`. +There is no centralized metadata file for `--slds-c-*` hooks — they are documented per-component. Some examples exist in `references/overviews/shadows.md` and `references/styling-hooks/index.md`. --- @@ -46,7 +46,7 @@ Always use `var()` with a fallback value: The fallback in `var(--slds-g-*, fallback)` is used when the hook isn't loaded (e.g., outside Lightning Experience, in static HTML previews, or during SSR). Use the **light-mode default** value: -1. **Look up the value in `metadata/hooks-index.json`** — each hook has a `value` field showing its resolved default +1. **Look up the value in `assets/hooks-index.json`** — each hook has a `value` field showing its resolved default 2. **Use the search script**: `node scripts/search-hooks.cjs --prefix "--slds-g-color-surface-"` shows values 3. **Common defaults**: `#ffffff` for surfaces, `#181818` for text, `1rem` for spacing-4, `0.25rem` for radius-border-2 @@ -218,11 +218,11 @@ Some dimension values have no SLDS hook (e.g., `min-width: 7rem` for label align ## Deep Reference -- Styling hooks index: `guidance/styling-hooks/index.md` -- Color hooks deep dive: `guidance/styling-hooks/color/` -- Color overview (85-5-10 rule): `guidance/overviews/color.md` -- Spacing overview: `guidance/overviews/spacing.md` -- Typography hooks: `guidance/styling-hooks/typography.md` -- Borders overview: `guidance/overviews/borders.md` -- Shadows overview: `guidance/overviews/shadows.md` -- All 523 hooks searchable: `metadata/hooks-index.json` +- Styling hooks index: `references/styling-hooks/index.md` +- Color hooks deep dive: `references/styling-hooks/color/` +- Color overview (85-5-10 rule): `references/overviews/color.md` +- Spacing overview: `references/overviews/spacing.md` +- Typography hooks: `references/styling-hooks/typography.md` +- Borders overview: `references/overviews/borders.md` +- Shadows overview: `references/overviews/shadows.md` +- All 523 hooks searchable: `assets/hooks-index.json` diff --git a/skills/design-systems-slds-apply/guidance/styling-hooks/borders.md b/skills/design-systems-slds-apply/references/styling-hooks/borders.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/styling-hooks/borders.md rename to skills/design-systems-slds-apply/references/styling-hooks/borders.md diff --git a/skills/design-systems-slds-apply/guidance/styling-hooks/color/expressive-palette-hooks.md b/skills/design-systems-slds-apply/references/styling-hooks/color/expressive-palette-hooks.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/styling-hooks/color/expressive-palette-hooks.md rename to skills/design-systems-slds-apply/references/styling-hooks/color/expressive-palette-hooks.md diff --git a/skills/design-systems-slds-apply/guidance/styling-hooks/color/index.md b/skills/design-systems-slds-apply/references/styling-hooks/color/index.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/styling-hooks/color/index.md rename to skills/design-systems-slds-apply/references/styling-hooks/color/index.md diff --git a/skills/design-systems-slds-apply/guidance/styling-hooks/color/semantic/accent-hooks.md b/skills/design-systems-slds-apply/references/styling-hooks/color/semantic/accent-hooks.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/styling-hooks/color/semantic/accent-hooks.md rename to skills/design-systems-slds-apply/references/styling-hooks/color/semantic/accent-hooks.md diff --git a/skills/design-systems-slds-apply/guidance/styling-hooks/color/semantic/feedback-hooks.md b/skills/design-systems-slds-apply/references/styling-hooks/color/semantic/feedback-hooks.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/styling-hooks/color/semantic/feedback-hooks.md rename to skills/design-systems-slds-apply/references/styling-hooks/color/semantic/feedback-hooks.md diff --git a/skills/design-systems-slds-apply/guidance/styling-hooks/color/semantic/surface-hooks.md b/skills/design-systems-slds-apply/references/styling-hooks/color/semantic/surface-hooks.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/styling-hooks/color/semantic/surface-hooks.md rename to skills/design-systems-slds-apply/references/styling-hooks/color/semantic/surface-hooks.md diff --git a/skills/design-systems-slds-apply/guidance/styling-hooks/color/system-hooks.md b/skills/design-systems-slds-apply/references/styling-hooks/color/system-hooks.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/styling-hooks/color/system-hooks.md rename to skills/design-systems-slds-apply/references/styling-hooks/color/system-hooks.md diff --git a/skills/design-systems-slds-apply/guidance/styling-hooks/index.md b/skills/design-systems-slds-apply/references/styling-hooks/index.md similarity index 99% rename from skills/design-systems-slds-apply/guidance/styling-hooks/index.md rename to skills/design-systems-slds-apply/references/styling-hooks/index.md index cc967c5..4795e34 100644 --- a/skills/design-systems-slds-apply/guidance/styling-hooks/index.md +++ b/skills/design-systems-slds-apply/references/styling-hooks/index.md @@ -245,7 +245,7 @@ font-size: var(--slds-g-font-size-base); /* Or use rem values */ ## Critical Rules -### ✅ DO: +### DO: - Reference hooks with `var()`: `color: var(--slds-g-color-accent-1);` - Use numbered spacing: `spacing-4` not `spacing-medium` @@ -254,7 +254,7 @@ font-size: var(--slds-g-font-size-base); /* Or use rem values */ - Pair container colors with on-colors for text/icons - Follow the 50-point rule for text contrast, 40-point rule for UI elements -### ❌ DON'T: +### DON'T: - Reassign hook values: `--slds-g-color-accent-1: #ff0000;` ❌ - Use private hooks (`--_slds-*` or `--slds-s-*`) diff --git a/skills/design-systems-slds-apply/guidance/styling-hooks/shadows.md b/skills/design-systems-slds-apply/references/styling-hooks/shadows.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/styling-hooks/shadows.md rename to skills/design-systems-slds-apply/references/styling-hooks/shadows.md diff --git a/skills/design-systems-slds-apply/guidance/styling-hooks/spacing.md b/skills/design-systems-slds-apply/references/styling-hooks/spacing.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/styling-hooks/spacing.md rename to skills/design-systems-slds-apply/references/styling-hooks/spacing.md diff --git a/skills/design-systems-slds-apply/guidance/styling-hooks/typography.md b/skills/design-systems-slds-apply/references/styling-hooks/typography.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/styling-hooks/typography.md rename to skills/design-systems-slds-apply/references/styling-hooks/typography.md diff --git a/skills/design-systems-slds-apply/references/utilities-quick-ref.md b/skills/design-systems-slds-apply/references/utilities-quick-ref.md index dfb38e5..3ffd29d 100644 --- a/skills/design-systems-slds-apply/references/utilities-quick-ref.md +++ b/skills/design-systems-slds-apply/references/utilities-quick-ref.md @@ -120,6 +120,6 @@ node scripts/search-utilities.cjs --pattern "slds-p-around_*" ## Deep Reference -- Utility index: `guidance/utilities/index.md` -- Individual category guides: `guidance/utilities/{category}.md` -- Full metadata (1,147 classes): `metadata/utilities-index.json` +- Utility index: `references/utilities/index.md` +- Individual category guides: `references/utilities/{category}.md` +- Full metadata (1,147 classes): `assets/utilities-index.json` diff --git a/skills/design-systems-slds-apply/guidance/utilities/alignment.md b/skills/design-systems-slds-apply/references/utilities/alignment.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/utilities/alignment.md rename to skills/design-systems-slds-apply/references/utilities/alignment.md diff --git a/skills/design-systems-slds-apply/guidance/utilities/borders.md b/skills/design-systems-slds-apply/references/utilities/borders.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/utilities/borders.md rename to skills/design-systems-slds-apply/references/utilities/borders.md diff --git a/skills/design-systems-slds-apply/guidance/utilities/box.md b/skills/design-systems-slds-apply/references/utilities/box.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/utilities/box.md rename to skills/design-systems-slds-apply/references/utilities/box.md diff --git a/skills/design-systems-slds-apply/guidance/utilities/color.md b/skills/design-systems-slds-apply/references/utilities/color.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/utilities/color.md rename to skills/design-systems-slds-apply/references/utilities/color.md diff --git a/skills/design-systems-slds-apply/guidance/utilities/dark-mode.md b/skills/design-systems-slds-apply/references/utilities/dark-mode.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/utilities/dark-mode.md rename to skills/design-systems-slds-apply/references/utilities/dark-mode.md diff --git a/skills/design-systems-slds-apply/guidance/utilities/description-list.md b/skills/design-systems-slds-apply/references/utilities/description-list.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/utilities/description-list.md rename to skills/design-systems-slds-apply/references/utilities/description-list.md diff --git a/skills/design-systems-slds-apply/guidance/utilities/floats.md b/skills/design-systems-slds-apply/references/utilities/floats.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/utilities/floats.md rename to skills/design-systems-slds-apply/references/utilities/floats.md diff --git a/skills/design-systems-slds-apply/guidance/utilities/grid.md b/skills/design-systems-slds-apply/references/utilities/grid.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/utilities/grid.md rename to skills/design-systems-slds-apply/references/utilities/grid.md diff --git a/skills/design-systems-slds-apply/guidance/utilities/horizontal-list.md b/skills/design-systems-slds-apply/references/utilities/horizontal-list.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/utilities/horizontal-list.md rename to skills/design-systems-slds-apply/references/utilities/horizontal-list.md diff --git a/skills/design-systems-slds-apply/guidance/utilities/hyphenation.md b/skills/design-systems-slds-apply/references/utilities/hyphenation.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/utilities/hyphenation.md rename to skills/design-systems-slds-apply/references/utilities/hyphenation.md diff --git a/skills/design-systems-slds-apply/guidance/utilities/index.md b/skills/design-systems-slds-apply/references/utilities/index.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/utilities/index.md rename to skills/design-systems-slds-apply/references/utilities/index.md diff --git a/skills/design-systems-slds-apply/guidance/utilities/interactions.md b/skills/design-systems-slds-apply/references/utilities/interactions.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/utilities/interactions.md rename to skills/design-systems-slds-apply/references/utilities/interactions.md diff --git a/skills/design-systems-slds-apply/guidance/utilities/layout.md b/skills/design-systems-slds-apply/references/utilities/layout.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/utilities/layout.md rename to skills/design-systems-slds-apply/references/utilities/layout.md diff --git a/skills/design-systems-slds-apply/guidance/utilities/line-clamp.md b/skills/design-systems-slds-apply/references/utilities/line-clamp.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/utilities/line-clamp.md rename to skills/design-systems-slds-apply/references/utilities/line-clamp.md diff --git a/skills/design-systems-slds-apply/guidance/utilities/margin.md b/skills/design-systems-slds-apply/references/utilities/margin.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/utilities/margin.md rename to skills/design-systems-slds-apply/references/utilities/margin.md diff --git a/skills/design-systems-slds-apply/guidance/utilities/media-object.md b/skills/design-systems-slds-apply/references/utilities/media-object.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/utilities/media-object.md rename to skills/design-systems-slds-apply/references/utilities/media-object.md diff --git a/skills/design-systems-slds-apply/guidance/utilities/name-value-list.md b/skills/design-systems-slds-apply/references/utilities/name-value-list.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/utilities/name-value-list.md rename to skills/design-systems-slds-apply/references/utilities/name-value-list.md diff --git a/skills/design-systems-slds-apply/guidance/utilities/padding.md b/skills/design-systems-slds-apply/references/utilities/padding.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/utilities/padding.md rename to skills/design-systems-slds-apply/references/utilities/padding.md diff --git a/skills/design-systems-slds-apply/guidance/utilities/position.md b/skills/design-systems-slds-apply/references/utilities/position.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/utilities/position.md rename to skills/design-systems-slds-apply/references/utilities/position.md diff --git a/skills/design-systems-slds-apply/guidance/utilities/print.md b/skills/design-systems-slds-apply/references/utilities/print.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/utilities/print.md rename to skills/design-systems-slds-apply/references/utilities/print.md diff --git a/skills/design-systems-slds-apply/guidance/utilities/scrollable.md b/skills/design-systems-slds-apply/references/utilities/scrollable.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/utilities/scrollable.md rename to skills/design-systems-slds-apply/references/utilities/scrollable.md diff --git a/skills/design-systems-slds-apply/guidance/utilities/sizing.md b/skills/design-systems-slds-apply/references/utilities/sizing.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/utilities/sizing.md rename to skills/design-systems-slds-apply/references/utilities/sizing.md diff --git a/skills/design-systems-slds-apply/guidance/utilities/themes.md b/skills/design-systems-slds-apply/references/utilities/themes.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/utilities/themes.md rename to skills/design-systems-slds-apply/references/utilities/themes.md diff --git a/skills/design-systems-slds-apply/guidance/utilities/truncate.md b/skills/design-systems-slds-apply/references/utilities/truncate.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/utilities/truncate.md rename to skills/design-systems-slds-apply/references/utilities/truncate.md diff --git a/skills/design-systems-slds-apply/guidance/utilities/typography.md b/skills/design-systems-slds-apply/references/utilities/typography.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/utilities/typography.md rename to skills/design-systems-slds-apply/references/utilities/typography.md diff --git a/skills/design-systems-slds-apply/guidance/utilities/vertical-list.md b/skills/design-systems-slds-apply/references/utilities/vertical-list.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/utilities/vertical-list.md rename to skills/design-systems-slds-apply/references/utilities/vertical-list.md diff --git a/skills/design-systems-slds-apply/guidance/utilities/visibility.md b/skills/design-systems-slds-apply/references/utilities/visibility.md similarity index 100% rename from skills/design-systems-slds-apply/guidance/utilities/visibility.md rename to skills/design-systems-slds-apply/references/utilities/visibility.md diff --git a/skills/design-systems-slds-apply/scripts/search-blueprints.cjs b/skills/design-systems-slds-apply/scripts/search-blueprints.cjs index 8fb335e..c6eb7ae 100644 --- a/skills/design-systems-slds-apply/scripts/search-blueprints.cjs +++ b/skills/design-systems-slds-apply/scripts/search-blueprints.cjs @@ -13,7 +13,7 @@ const fs = require('fs'); const path = require('path'); -const BLUEPRINTS_DIR = path.join(__dirname, '..', 'metadata', 'blueprints', 'components'); +const BLUEPRINTS_DIR = path.join(__dirname, '..', 'assets', 'blueprints', 'components'); function loadBlueprints() { const blueprints = []; @@ -67,7 +67,7 @@ function run() { console.log(`Category: ${bp.category}`); console.log(`Root class: ${bp.rootClass}`); console.log(`Description: ${bp.description}`); - console.log(`\nFull YAML: metadata/blueprints/components/${bp.file}.yaml`); + console.log(`\nFull YAML: assets/blueprints/components/${bp.file}.yaml`); console.log('\nRead the full YAML file for classes, modifiers, states, accessibility, and example HTML.'); return; } diff --git a/skills/design-systems-slds-apply/scripts/search-hooks.cjs b/skills/design-systems-slds-apply/scripts/search-hooks.cjs index 3172245..0c42857 100644 --- a/skills/design-systems-slds-apply/scripts/search-hooks.cjs +++ b/skills/design-systems-slds-apply/scripts/search-hooks.cjs @@ -15,7 +15,7 @@ const fs = require('fs'); const path = require('path'); -const INDEX_PATH = path.join(__dirname, '..', 'metadata', 'hooks-index.json'); +const INDEX_PATH = path.join(__dirname, '..', 'assets', 'hooks-index.json'); function loadHooks() { if (!fs.existsSync(INDEX_PATH)) { diff --git a/skills/design-systems-slds-apply/scripts/search-icons.cjs b/skills/design-systems-slds-apply/scripts/search-icons.cjs index f1596c6..18d8c2e 100644 --- a/skills/design-systems-slds-apply/scripts/search-icons.cjs +++ b/skills/design-systems-slds-apply/scripts/search-icons.cjs @@ -13,7 +13,7 @@ const fs = require('fs'); const path = require('path'); -const ICONS_PATH = path.join(__dirname, '..', 'metadata', 'icon-metadata.json'); +const ICONS_PATH = path.join(__dirname, '..', 'assets', 'icon-metadata.json'); function loadIcons() { if (!fs.existsSync(ICONS_PATH)) { diff --git a/skills/design-systems-slds-apply/scripts/search-utilities.cjs b/skills/design-systems-slds-apply/scripts/search-utilities.cjs index d6845e0..8779084 100644 --- a/skills/design-systems-slds-apply/scripts/search-utilities.cjs +++ b/skills/design-systems-slds-apply/scripts/search-utilities.cjs @@ -13,8 +13,8 @@ const fs = require('fs'); const path = require('path'); -const INDEX_PATH = path.join(__dirname, '..', 'metadata', 'utilities-index.json'); -const GUIDANCE_DIR = path.join(__dirname, '..', 'guidance', 'utilities'); +const INDEX_PATH = path.join(__dirname, '..', 'assets', 'utilities-index.json'); +const GUIDANCE_DIR = path.join(__dirname, '..', 'references', 'utilities'); function loadUtilities() { if (!fs.existsSync(INDEX_PATH)) { @@ -100,7 +100,7 @@ function run() { const guidePath = path.join(GUIDANCE_DIR, `${catKey}.md`); if (fs.existsSync(guidePath)) { - console.log(`\nDetailed guidance: guidance/utilities/${catKey}.md`); + console.log(`\nDetailed guidance: references/utilities/${catKey}.md`); } return; } diff --git a/skills/dx-code-analyzer-configure/SKILL.md b/skills/dx-code-analyzer-configure/SKILL.md index 3340222..737e5d7 100644 --- a/skills/dx-code-analyzer-configure/SKILL.md +++ b/skills/dx-code-analyzer-configure/SKILL.md @@ -1,15 +1,32 @@ --- name: dx-code-analyzer-configure -description: "Set up, configure, and troubleshoot Salesforce Code Analyzer for any project. Handles installation, prerequisite checks, diagnosing broken setups, creating and editing code-analyzer.yml overrides, engine-specific settings, ignore patterns, severity overrides, and CI/CD pipeline setup. TRIGGER when: user says 'set up code analyzer', 'configure code analyzer', 'install code analyzer', 'code analyzer not working', 'fix my setup', 'scan is failing', 'check my setup', 'is code analyzer installed', 'enable/disable engine', 'exclude files', 'change severity', 'set up GitHub Actions', 'set up CI/CD', 'add code analyzer to pipeline', 'make pipeline fail', 'update my workflow', 'quality gate', 'fail on violations', 'scan changed files only', 'add SARIF', 'code-analyzer.yml', 'ESLint config', 'increase SFGE memory', or reports errors running Code Analyzer. DO NOT TRIGGER when: user wants to run a scan (use dx-code-analyzer-run), fix violations, explain rules, create custom rules, or suppress violations." +description: "Set up, configure, and troubleshoot Salesforce Code Analyzer for any project. Handles installation, prerequisite checks, diagnosing broken setups, creating and editing code-analyzer.yml overrides, engine-specific settings, ignore patterns, severity overrides, and CI/CD pipeline setup. TRIGGER when: user says 'set up code analyzer', 'configure code analyzer', 'install code analyzer', 'code analyzer not working', 'fix my setup', 'scan failing', 'check my setup', 'enable/disable engine', 'exclude files', 'change severity', 'set up GitHub Actions', 'set up CI/CD', 'add to pipeline', 'pipeline fail', 'update my workflow', 'quality gate', 'fail on violations', 'scan changed files only', 'add SARIF', 'code-analyzer.yml', 'ESLint config', 'increase SFGE memory', or reports errors running Code Analyzer. DO NOT TRIGGER when: user wants to run a scan (use dx-code-analyzer-run), fix violations, explain rules, create custom rules (use dx-code-analyzer-custom-rule-create), or suppress violations." metadata: version: "1.0" - argument-hint: "[--check-prerequisites] [--generate-config] [--engine pmd|eslint|cpd|retire-js|regex|flow|sfge|apexguru] [--ci github-actions|jenkins]" + relatedSkills: + - "dx-code-analyzer-run" + - "dx-code-analyzer-custom-rule-create" + cliTools: + - tool: ["sf"] + semver: ">=2.0.0" + - tool: ["java"] + semver: ">=11.0.0" + - tool: ["node"] + semver: ">=18.0.0" + - tool: ["python3"] + semver: ">=3.10.0" + - tool: ["git"] + semver: ">=2.0.0" + - tool: ["npm"] + semver: ">=9.0.0" --- # Configuring Code Analyzer Skill ## Overview +> **Ecosystem:** This skill is part of a 3-skill Code Analyzer suite — `dx-code-analyzer-run` (scans & results) · `dx-code-analyzer-configure` (setup, config, CI/CD) · `dx-code-analyzer-custom-rule-create` (custom rule authoring). + This skill manages the `code-analyzer.yml` configuration file — the single source of truth for how Code Analyzer behaves in a project. All customization (engines, rules, ignores, suppressions) is done by creating or editing this file. If the file doesn't exist, this skill creates it in the current working directory. --- @@ -26,8 +43,9 @@ This skill manages the `code-analyzer.yml` configuration file — the single sou - Environment validation and troubleshooting **Out of scope:** -- Running scans (use `dx-code-analyzer-run` skill) -- Fixing violations, explaining rules, creating custom rules, suppression management +- Running scans → use `dx-code-analyzer-run` skill +- Fixing violations, explaining rules, suppression management → use `dx-code-analyzer-run` skill +- Creating custom rules → use `dx-code-analyzer-custom-rule-create` skill --- @@ -127,7 +145,7 @@ ls code-analyzer.yml code-analyzer.yaml 2>/dev/null The CLI auto-discovers `code-analyzer.yml` in the current directory. Since scans run from project root, the file must live there. -### ⚠️ Rule Name Resolution — ALWAYS Before Writing YAML +### Rule Name Resolution — ALWAYS Before Writing YAML When a user references rules by partial, descriptive, or approximate names (e.g., "the doc rule", "CRUD violation", "console rule", "hardcoded values"), you MUST resolve to exact rule names using the lookup in **Step 6.1** BEFORE writing any YAML. The `code-analyzer.yml` file silently ignores rule names that don't exactly match — there is no error, the override just won't apply. @@ -381,14 +399,10 @@ engines: target_org: "my-org-alias" flow: python_command: "python3" - regex: - custom_rules: - NoHardcodedIds: - regex: "/[a-zA-Z0-9]{15,18}/" - file_extensions: [".cls", ".trigger"] - description: "Detects hardcoded Salesforce record IDs" - severity: 2 - tags: ["Security"] + # regex.custom_rules — use the dx-code-analyzer-custom-rule-create skill to create these. + # Never hand-write regex patterns into code-analyzer.yml: quotes/backslashes + # inside YAML cause parsing failures. The create-regex-rule.js script handles + # serialization correctly and must always be used for regex rule creation. ``` For full property list per engine, read `/references/config-schema.md`. @@ -427,9 +441,13 @@ If a user says "scan my code" / "run code analyzer" but it fails (CLI missing, p After any successful configuration action, offer to run a scan (e.g., "Setup complete! Want me to run a scan?", "Config updated — want to scan and verify?"). If user says yes, proceed with `dx-code-analyzer-run` behavior. +### When THIS skill hands off to `dx-code-analyzer-custom-rule-create`: + +If the user asks to create a custom rule while you are working on configuration (e.g., "also add a rule that bans System.debug", "set up a PMD XPath rule"), delegate to `dx-code-analyzer-custom-rule-create`. When that skill finishes, the `custom_rulesets` or `eslint_config_file` pointer in `code-analyzer.yml` may need to be set — that edit belongs here, in this skill. + ### When user intent spans BOTH skills: -Handle end-to-end: "not working" → Diagnose → Fix → Scan. "Set up and scan" → Install → Scan. "Disable ESLint and scan Apex" → Edit config → Run with `--rule-selector pmd`. Always follow through to the user's final intent. +Handle end-to-end: "not working" → Diagnose → Fix → Scan. "Set up and scan" → Install → Scan. "Disable ESLint and scan Apex" → Edit config → Run with `--rule-selector pmd`. "Configure custom rules and scan" → dx-code-analyzer-custom-rule-create → wire config → dx-code-analyzer-run. Always follow through to the user's final intent. --- diff --git a/skills/dx-code-analyzer-custom-rule-create/SKILL.md b/skills/dx-code-analyzer-custom-rule-create/SKILL.md new file mode 100644 index 0000000..76dfb9b --- /dev/null +++ b/skills/dx-code-analyzer-custom-rule-create/SKILL.md @@ -0,0 +1,484 @@ +--- +name: dx-code-analyzer-custom-rule-create +description: "Create custom Code Analyzer rules for Regex (pattern matching), PMD (XPath/AST for Apex and metadata XML), and ESLint (LWC/JavaScript/TypeScript). Use when users want to enforce coding standards, ban patterns, detect hardcoded values, govern metadata, or add rules not in the built-in set. TRIGGER when: user says 'create a rule', 'ban System.debug', 'enforce naming convention', 'detect hardcoded IDs', 'custom rule', 'xpath rule', 'regex rule', 'add a PMD rule', 'enforce a policy', 'create a check for', 'flag this pattern', 'make a rule that catches', 'metadata rule', 'check permissions', 'enforce API version', 'eslint rule', 'lwc rule', 'override rule threshold', 'customize complexity', or describes a pattern to enforce. DO NOT TRIGGER when: user wants to run a scan (use dx-code-analyzer-run), configure engines (use dx-code-analyzer-configure), or explain existing rules (use dx-code-analyzer-run)." +metadata: + version: "1.0" + relatedSkills: + - "dx-code-analyzer-run" + - "dx-code-analyzer-configure" + cliTools: + - tool: ["sf"] + semver: ">=2.0.0" + - tool: ["node"] + semver: ">=18.0.0" + - tool: ["npm"] + semver: ">=9.0.0" +--- + +# dx-code-analyzer-custom-rule-create: Custom Code Analyzer Rule Authoring + +> **Ecosystem:** This skill is part of a 3-skill Code Analyzer suite — `dx-code-analyzer-run` (scans & results) · `dx-code-analyzer-configure` (setup, config, CI/CD) · `dx-code-analyzer-custom-rule-create` (custom rule authoring). + +Use this skill when the user needs to **create a custom rule** that enforces a pattern not covered by Code Analyzer's built-in rules. Supports Regex engine (text pattern matching) and PMD engine (structural XPath queries against the AST). + +## When This Skill Owns the Task + +Use `dx-code-analyzer-custom-rule-create` when the work involves: +- Creating a new custom rule for Code Analyzer (any engine) +- Enforcing team-specific coding standards via static analysis +- Banning specific patterns (System.debug, hardcoded IDs, TODOs) +- Writing XPath expressions for PMD rules (Apex or metadata XML) +- Writing regex patterns for the Regex engine +- Setting up custom ESLint rules/plugins for LWC/JavaScript +- Enforcing metadata governance (API versions, field descriptions, dangerous permissions) +- Overriding built-in rule thresholds (CyclomaticComplexity, ExcessiveParameterList, etc.) +- Organizing multiple rules into shared rulesets +- Iterating on a custom rule that isn't matching correctly + +Delegate elsewhere when the user is: +- Running a scan against existing rules → `dx-code-analyzer-run` skill +- Configuring engines, prerequisites, CI/CD → `dx-code-analyzer-configure` skill +- Explaining what an existing built-in rule means → `dx-code-analyzer-run` skill +- Writing Apex code or tests → `generating-apex` / `running-apex-tests` skills + +--- + +## Required Context to Gather First + +Ask for or infer: +- **What pattern to catch** — what code should be flagged? (If user selected code in their IDE, the selection IS the answer — do not re-ask.) +- **What to allow** — any exceptions? (test classes, specific contexts) +- **File scope** — which file types? (.cls, .trigger, .js, all?) +- **Severity** — how critical? (default: 3/Moderate) + +If the user **selected code** (IDE selection context present), treat it as the pattern definition. Skip clarification unless genuinely ambiguous about what aspect of the selection to target. + +If the request is vague with NO selection ("add a rule for best practices"), ask ONE clarifying question: +> "What specific pattern should this rule flag?" + +--- + +## Hard Constraints + +These are non-negotiable rules. Violating any of them is a skill failure regardless of whether the output happens to work. + +1. **ALWAYS run `ast-dump` before writing XPath.** No exceptions. Do not use node names from memory, references, or prior conversations. The AST is the source of truth — run `sf code-analyzer ast-dump`, read the output, then write XPath that matches what you see. Even for "well-known" patterns like SOQL-in-loop, run ast-dump first. If you skip this step and the rule works, it is still a process failure. + +2. **ALWAYS use the scripts to create rules.** For regex rules, ALWAYS use `create-regex-rule.js`. For PMD rules, ALWAYS use `create-pmd-rule.js`. Do NOT manually edit `code-analyzer.yml` to add rule definitions — regex patterns in YAML cause escaping failures (quotes inside quotes, backslashes getting eaten). The scripts handle YAML serialization correctly every time. + +3. **NEVER manually edit `code-analyzer.yml` after a script writes to it — even to fix a bad value.** The scripts produce correctly-escaped YAML. If you then rewrite or restructure the file, you WILL break the escaping. If the user added top-level config (like `ignores.files`), leave it alone too — only touch what you wrote. + + **If a script's output looks wrong (rule fails to validate, YAML parse error, stray characters in the regex):** + - DO NOT patch the YAML by hand. That is exactly the failure mode this constraint exists to prevent. + - **Always** delete the broken rule's entire YAML block, then re-invoke the script with corrected arguments. Removing a block you just wrote does not violate this rule; rewriting fields inside it does. + - If the script accepted bad input and produced bad output, the **input** was wrong (e.g., `--regex "/.../ g"` with a stray space — the flags must be `/g` exactly, no whitespace). Re-invoke with the corrected argument. + - If you genuinely believe the script has a bug, STOP and surface it to the user. Do not hand-edit as a workaround. + +4. **`--regex` must be `/pattern/flags` with NO whitespace.** The script trims and validates flags strictly — only `g`, `i`, `m`, `s`, `u`, `y`. `/pat/ g` (with a space) is rejected; so is `/pat/x` (invalid flag) and `/pat/` (no flags). The global flag `g` is mandatory. If validation fails, fix the argument — do NOT bypass by writing YAML directly. + +5. **ALWAYS validate after creation.** Run `sf code-analyzer rules --rule-selector :` before testing. If `Found 0 rules`, the YAML didn't parse — delete the block, fix the argument, re-invoke the script. + +6. **ALWAYS test against a sample file.** Confirm at least one true positive and one true negative. + + For regex rules, the negative sample MUST NOT contain the pattern text anywhere — including inside comments and string literals. Regex engines scan raw text; `// no System.debug here` IS a match for `/System\.debug/g`. Trace your pattern against the negative file mentally before running it. + +7. **Create rules ONE AT A TIME, sequentially.** When the user requests multiple rules, create each rule individually through the full workflow (create → validate → test positive → test negative) before starting the next one. Do NOT batch-create rules — if one fails, it corrupts the config for all subsequent rules. Complete each rule end-to-end, confirm it works, then move to the next. + +8. **For regex rules, exclude test classes via `ignores.files` — `regex_ignore` does NOT do this.** `regex_ignore` is a per-LINE filter (the line must match BOTH the rule and the ignore pattern); it cannot exclude an entire test class. If the user's intent is "skip test classes," add a top-level `ignores.files` block with globs like `"**/*Test.cls"` AFTER all rules are created — do not interleave config edits with script invocations. + +--- + +## Engine Selection + +| Pattern Type | Engine | Why | +|---|---|---| +| Text/string pattern (TODO, hardcoded ID, keyword) | **Regex** | Simple, fast, no Java needed | +| Apex code structure (method calls, nesting, SOQL in loops) | **PMD/XPath** (language=apex) | Understands AST, not fooled by comments/strings | +| Metadata XML governance (API version, permissions, descriptions) | **PMD/XPath** (language=xml) | Structural XML matching with namespace handling | +| LWC/JavaScript/TypeScript patterns | **ESLint*** | Standard JS tooling, plugin ecosystem | +| Both could work (Apex/metadata only) | **Regex first** | Simpler to create and maintain | + +\* **For ESLint:** ALWAYS check Tier 1 (built-in rules) and Tier 2 (configurable rules) BEFORE creating a custom plugin. See `references/eslint-rules-discovery.md`. + +⚠️ **"Both could work → Regex first" NEVER applies to JavaScript/LWC/TypeScript files.** JS/LWC/TS patterns MUST use ESLint — Regex cannot distinguish code from comments/strings in JS and produces false positives. Do NOT rationalize Regex for JS files based on "simplicity" or "no npm dependencies." + +Tell the user which engine you chose and why. Respect their preference if they disagree. + +### Excluding Test Classes — Strategy by Engine + +When a rule should NOT apply to test classes, the approach differs by engine: + +| Engine | How to exclude test classes | Notes | +|--------|---------------------------|-------| +| **PMD (Apex)** | Add `[not(ancestor::UserClass[ModifierNode[@Test = true()]])]` to the XPath | Structural exclusion — works perfectly, no config changes needed | +| **Regex** | Use `ignores.files` in `code-analyzer.yml` with globs like `**/*Test.cls` | `regex_ignore` is per-LINE, not per-FILE — it CANNOT exclude entire test classes. Only use `regex_ignore` for per-line patterns like `// NOPMD` | +| **ESLint** | Use `ignores` array in `eslint.config.js` | Standard ESLint file-level ignores | + +⚠️ **`regex_ignore` is NOT file-level exclusion.** It only skips matches on lines that ALSO match the ignore pattern. Example: `regex_ignore: "/@isTest/i"` only suppresses violations on lines containing `@isTest` — a SOQL query on line 50 of a test class still flags because line 50 doesn't contain `@isTest`. To exclude test files from regex rules entirely, use: +```yaml +ignores: + files: + - "**/*Test.cls" + - "**/*_Test.cls" +``` + +⚠️ **`ignores.files` is GLOBAL** — it affects ALL engines and ALL rules. If you need test-class exclusion for some rules but not others (e.g., exclude tests from SOQL rules but still scan tests for @AuraEnabled), use **PMD with XPath** for the rules that need selective exclusion. PMD's XPath can structurally check `@Test = true()` per-method or per-class — Regex cannot. + +**Decision guide for Apex rules that should skip test classes:** +- If the pattern is structural (method calls, annotations, nesting) → use PMD. XPath handles test-class exclusion natively. +- If the pattern is purely textual AND all regex rules should skip tests → use Regex + `ignores.files`. +- If you have a mix (some rules skip tests, others don't) → use PMD for the test-sensitive rules, Regex for the others. + +--- + +## Workflow + +### When User Selects Code (IDE Selection) + +When the user highlights a code block in their editor and asks to "catch this", "flag this pattern", "create a rule for this", or similar: + +1. **The selection IS your positive sample.** Do NOT ask "what pattern should this rule flag?" — the user already showed you. Do NOT write a new sample from scratch. +2. **Identify what's structural vs. incidental** in the selection: + - Structural (rule-worthy): the method call, the loop pattern, the missing keyword, the nesting + - Incidental (ignore): specific variable names, string values, parameter counts + - Ask ONE question if ambiguous: "Should the rule catch all `System.debug` calls, or only those without a LoggingLevel parameter?" +3. **Ast-dump the ACTUAL file** the user has open (not a new sample file): + ```bash + sf code-analyzer ast-dump --file --output-file + ``` +4. **Find the selection in the AST output** — locate the nodes corresponding to the highlighted lines. These are your target nodes. +5. **Generalize the XPath** — write XPath that matches the structural pattern, NOT the specific instance. Replace specific variable names with wildcards, keep structural nodes and discriminating attributes. +6. **Continue with standard workflow** (negative sample, create rule, validate, test positive + negative). + +**Example flow:** +- User selects: `Database.query('SELECT Id FROM ' + objectName)` +- Structural pattern: `Database.query` call (dynamic SOQL) +- Incidental: the specific string concatenation inside +- Engine: PMD (structural call detection) +- XPath: `//MethodCallExpression[@FullMethodName='Database.query']` +- NOT: regex matching `Database.query` (would miss multiline, match comments) + +**Example flow (block selection):** +- User selects a 5-line block with SOQL inside a for-each loop +- Structural: SOQL query as descendant of loop body +- Incidental: specific query fields, variable names +- Engine: PMD +- Ast-dump the open file → find ForEachStatement + SoqlExpression in body +- XPath: `//ForEachStatement/BlockStatement//SoqlExpression` + +--- + +### For Regex Rules + +1. **Write a positive sample** (5-10 lines) demonstrating the violation. Write sample files inside the project workspace (e.g., a temporary `samples/` directory at the project root) so Code Analyzer can target them. +2. **Write a SEPARATE negative sample file** — code that looks similar but must NOT be flagged. Test your regex mentally against this file BEFORE creating the rule. +3. **Build and create the rule** — read `references/regex-rule-schema.md` for the complete schema, then run the script: + ```bash + node "/scripts/create-regex-rule.js" \ + --name "" --regex "" --description "" \ + --severity <1-5> --file-extensions ".cls,.trigger" + ``` + ⚠️ **ALWAYS use the script.** Do NOT manually write regex patterns into `code-analyzer.yml` — regex characters (quotes, backslashes, braces) inside YAML cause parsing failures. The script handles serialization correctly. +4. **Validate** — `sf code-analyzer rules --rule-selector regex:` +5. **Test positive** — `sf code-analyzer run --rule-selector regex: --target ` — must find violations +6. **Test negative** — `sf code-analyzer run --rule-selector regex: --target ` — must find 0 violations. If it flags clean code, your regex is too broad — go back and tighten the pattern. +7. **Iterate** if test fails (adjust regex, add `regex_ignore`, narrow extensions) +8. **Cleanup** — delete ALL sample files you created. Do not leave temporary test fixtures in the user's project. + +### For PMD/XPath Rules (Apex) + +1. **Write a minimal sample** (5-10 lines) demonstrating the violation. Write sample files inside the project workspace (e.g., a temporary `samples/` directory at the project root) so Code Analyzer can target them. **After both positive and negative tests pass, delete ALL sample files you created** — both the original samples and any copies made during testing. Do not leave temporary test fixtures in the user's project. For loop-based rules, the positive sample MUST include all 3 Apex loop types (`for-each`, traditional `for`, and `while`) — omitting any loop type means the XPath won't be validated against it and may silently miss violations. +2. **⚠️ MANDATORY: Dump the AST** — `sf code-analyzer ast-dump --file --output-file ` — this step is NOT optional. Do NOT skip it even if you "already know" the node names. Run it, read the output, confirm the exact node names and attributes. +3. **Read the AST output** — identify the target node and its attributes from the ACTUAL ast-dump output (not from memory). Use `references/apex-ast-reference.md` and `references/xpath-patterns.md` as supplementary context only. +4. **Write XPath** — target the smallest stable node with discriminating attributes. Every node name in your XPath MUST appear verbatim in the ast-dump output you just read. Use `/Child` (direct child) vs `//Descendant` deliberately — check the ast-dump to understand which nodes are siblings vs nested. +5. **⚠️ BEFORE creating the rule: Write a SEPARATE negative sample file** (5-10 lines) showing code that is CORRECT and must NOT be flagged. This MUST be a distinct file from the positive sample — do NOT combine positive and negative cases into one file. For loop-based rules, include `for (x : [SELECT...])` idiom. Run `sf code-analyzer ast-dump` on this negative sample file too. Read the output and trace your XPath against it — confirm it does NOT match any node in the negative AST. If it would match, go back to step 4 and tighten the XPath BEFORE proceeding. Do NOT skip this step or defer it until after rule creation. The negative file will be used again in step 9 for an explicit zero-violation confirmation. +6. **Create the rule** — run the script: + ```bash + node "/scripts/create-pmd-rule.js" \ + --name "" --xpath "" --message "" \ + --language apex --priority <1-5> + ``` +7. **Validate** — `sf code-analyzer rules --rule-selector pmd:` +8. **Test positive** — `sf code-analyzer run --rule-selector pmd: --target ` — must find violations +9. **Test negative** — `sf code-analyzer run --rule-selector pmd: --target ` — must find 0 violations. If it flags clean code, your XPath is too broad — go back to step 4. +10. **Iterate** if test fails (re-examine AST, adjust XPath, check node names) +11. **Cleanup** — delete ALL sample files you created (both positive and negative samples, and any ast-dump output files). Do not leave temporary test fixtures in the user's project. + +### For PMD/XPath Rules (Metadata XML) + +1. **Identify the metadata file type** (field, permissionset, profile, flow, etc.) +2. **Dump the XML AST** — `sf code-analyzer ast-dump --file -meta.xml --language xml --output-file `. If ast-dump fails with an error (e.g., `"XmlEncoding is not a valid XML name"`), fall back to reading the raw XML file directly — the XML DOM structure IS the AST for metadata files (what you see in the file is what PMD sees). Read the file, note element names, nesting, and text content. +3. **Read the DOM structure** — confirm element names, nesting, and text content from the ast-dump output OR the raw file. Use `references/metadata-xml-rules.md` as supplementary context only. +4. **Write XPath for PMD 7** — CRITICAL rules for metadata XML XPath: + - All element matching MUST use `local-name()='ElementName'` (namespace blocks bare names) + - **Text content matching MUST use `@Text` attribute** (NOT `text()` — the `text()` function does not work in PMD 7's XML language). Example: `//*[@Text='ModifyAllData']` + - Navigate from text nodes UP to parent elements using `../..` (text node → element → parent element) + - Check sibling conditions via `.//*[@Text='value']` on the parent + - See `references/metadata-xml-rules.md` for the complete PMD 7 XPath pattern +5. **Configure file extensions** — PMD's XML language only processes `.xml` by default. Salesforce metadata files use compound extensions (`.permissionset-meta.xml`, `.field-meta.xml`, etc.) but `path.extname()` returns `.xml` from these, so `.xml` is sufficient: + ```yaml + engines: + pmd: + file_extensions: + xml: [".xml"] + ``` + ⚠️ Do NOT add compound extensions like `.permissionset-meta.xml` — Code Analyzer's validator rejects them (`/^[.][a-zA-Z0-9]+$/` pattern). Just `.xml` covers all Salesforce metadata files automatically. +6. **Write a SEPARATE negative sample file** — same as Apex rules: create a metadata file that is correct and must NOT be flagged. Verify the XPath does not match it BEFORE creating the rule. Both positive and negative samples should be `.xml` extension files in the workspace so PMD can scan them directly. +7. **Create the rule** — run the script: + ```bash + node "/scripts/create-pmd-rule.js" \ + --name "" --xpath "" --message "" \ + --language xml --priority <1-5> + ``` +8. **Validate** — `sf code-analyzer rules --rule-selector pmd:` +9. **Test positive** — `sf code-analyzer run --rule-selector pmd: --target .xml` — must find violations +10. **Test negative** — `sf code-analyzer run --rule-selector pmd: --target .xml` — must find 0 violations +11. **Iterate** if test fails (check `@Text` vs `text()`, verify `local-name()` usage, check file extensions config) +12. **Cleanup** — delete ALL sample files you created (both positive and negative samples, and any `.xml` copies made for testing). Do not leave temporary test fixtures in the user's project. + +### For ESLint Rules (LWC/JavaScript/TypeScript) + +#### ESLint Discovery Workflow (Read First) + +ESLint has 200+ built-in rules plus thousands more from plugins. DO NOT attempt to create a custom plugin before checking if a built-in rule exists. The discovery workflow is: + +1. **Tier 1 (Built-In Rules)** — 70% of requests + - Run: `sf code-analyzer rules --rule-selector eslint` + - Search for keywords (e.g., "console", "unused", "equal") + - Check LWC plugin rules (`@lwc/lwc/*`) + - If found: Configure and STOP + +2. **Tier 2 (Configurable Rules)** — 20% of requests + - Pattern is "ban function X"? → `no-restricted-globals` + - Pattern is "ban syntax Y"? → `no-restricted-syntax` + - Pattern is "ban property Z"? → `no-restricted-properties` + - If applicable: Configure and STOP + +3. **Tier 3 (Custom Plugins)** — 10% of requests + - Only for domain-specific multi-node patterns + - Requires Node.js code, testing, meta.docs metadata + - See: `references/eslint-custom-plugins.md` + +**See:** `references/eslint-rules-discovery.md` for complete discovery guide with examples. + +--- + +0. **⚠️ MANDATORY: Run the ESLint discovery workflow FIRST.** 90% of ESLint requests are solved by built-in rules (Tier 1) or configurable rules like `no-restricted-syntax` (Tier 2). Creating a custom plugin (Tier 3) is a LAST RESORT. Before proceeding: + + a) **Run:** `sf code-analyzer rules --rule-selector eslint` + b) **Search** the output for keywords from the user's request (e.g., "console", "equal", "unused") + c) **Check LWC plugin rules:** grep for `@lwc/lwc/` in the output + d) **If found:** Configure it (see `references/eslint-rules-discovery.md`) and STOP. Do NOT create a custom plugin. + e) **If not found:** Check if `no-restricted-globals`, `no-restricted-syntax`, or `no-restricted-properties` can express the pattern (Tier 2). + f) **Only if no Tier 1 or Tier 2 solution exists:** Proceed to custom plugin creation. + + **Validation:** After configuration, run `sf code-analyzer rules --rule-selector eslint:` to confirm it appears. If it doesn't, the config is wrong. + + ⚠️ **Skipping this discovery workflow and creating a custom plugin when a built-in rule exists is a skill failure**, regardless of whether the custom plugin works. + +⚠️ **Built-in ESLint rules vs. configurable ESLint rules:** Code Analyzer bundles a SUBSET of ESLint rules in its base config. Rules like `no-restricted-globals`, `no-restricted-syntax`, and `no-restricted-properties` are core ESLint rules but are NOT active until you enable them in an `eslint.config.js`. They WILL appear in `sf code-analyzer rules` output ONLY after you configure them. **Always validate** (step 4) to confirm the rule actually loaded — do NOT assume a rule exists just because it's a core ESLint rule. + +1. **Install the ESLint plugin** (skip if using a built-in/core ESLint rule) — `npm install --save-dev eslint-plugin-` +2. **Create/update `eslint.config.js`** — add plugin and rule configuration (or just enable the built-in rule with `"error"` severity). **The file MUST exist** before configuring `engines.eslint.eslint_config_file` in `code-analyzer.yml` — Code Analyzer validates the path and fails if the file is missing. +3. **Configure Code Analyzer** — set `engines.eslint.eslint_config_file` in `code-analyzer.yml`. Do this AFTER the file exists. +4. **Validate** — `sf code-analyzer rules --rule-selector eslint:`. If the rule does NOT appear, the config is wrong — do NOT proceed to testing. Check: (a) Is the `eslint.config.js` file in the correct location? (b) Does the plugin have `meta.docs.description` and `meta.docs.url`? (c) Is the rule deprecated? Code Analyzer silently excludes deprecated rules. +5. **Write a test sample file** in the workspace (e.g., `lwc/testSample/testSample.js`) demonstrating the violation +6. **Test positive** — `sf code-analyzer run --rule-selector eslint: --target ` — must find violations +7. **Test negative** — run against existing clean LWC files — must find 0 violations +8. **Cleanup** — delete ALL sample files AND their parent directories (LWC requires a folder per component). Use `rm -rf `, not just `rm `. Do not leave empty directories or temporary test fixtures in the user's project. +9. See `references/eslint-custom-plugins.md` for complete guide + +### Common ESLint patterns without a custom plugin + +For `no-restricted-globals`, `no-restricted-syntax`, and `no-restricted-properties` config examples, see `/references/eslint-custom-plugins.md` — "Banning APIs Without a Custom Plugin" section. + +### ESLint Discovery Examples + +**Example 1: Built-in rule (Tier 1)** +- User: "Ban console.log in LWC" +- Discovery: `sf code-analyzer rules --rule-selector eslint | grep console` → finds `no-console` +- Action: Enable `no-console` in eslint.config.js +- Result: ✅ Done in 2 minutes (no custom plugin needed) + +**Example 2: Configurable rule (Tier 2)** +- User: "Ban setTimeout in LWC" +- Discovery: No built-in `no-setTimeout` rule +- Action: Use `no-restricted-globals` with custom message +- Result: ✅ Done in 5 minutes (no custom plugin needed) + +**Example 3: LWC plugin rule (Tier 1)** +- User: "Ban innerHTML for XSS prevention" +- Discovery: `@lwc/lwc/no-inner-html` already exists +- Action: Enable `@lwc/lwc/no-inner-html` in config +- Result: ✅ Done in 2 minutes (no custom plugin needed) + +**Example 4: Custom plugin justified (Tier 3)** +- User: "Flag imperative Apex calls without error handling" +- Discovery: No built-in rule for this pattern +- Analysis: Pattern requires checking `import` + `.then()` without `.catch()` — multi-node traversal +- Action: Create custom plugin with visitor pattern +- Result: ✅ Custom plugin is justified (20+ minutes effort) + +See `references/eslint-rules-discovery.md` for complete discovery workflow. + +--- + +### Overriding Built-in Rule Thresholds + +To customize severity or properties on existing rules without writing new ones: + +1. **Create a custom ruleset** referencing the built-in rule with overrides +2. **Add to config** — `engines.pmd.custom_rulesets` in `code-analyzer.yml` +3. See `references/advanced-pmd-patterns.md` for override syntax and common examples + +--- + +## Multi-Rule Requests + +When the user asks for multiple rules at once (e.g., "create 5 rules for AppExchange review"), follow this protocol: + +### Step 1: Plan the engine assignments FIRST + +Before creating any rules, list ALL requested rules with their engine assignments. Present this plan to the user: + +```text +Rule plan: +1. RequireUserMode → PMD/XPath (needs test-class exclusion via AST) +2. NoHardcodedIds → Regex (text pattern, no structural context needed) +3. AuraEnabledCacheable → PMD/XPath (needs annotation + DML structural check) +4. CustomFieldDescription → PMD/XML (metadata governance) +5. NoSetTimeout → ESLint (JavaScript pattern) +``` + +Key decision factors for engine assignment: +- Does the rule need to **exclude test classes selectively**? → PMD (XPath can check `@Test = true()`) +- Is it a **pure text pattern** with no structural context? → Regex +- Does it need to **understand code structure** (annotations, nesting, DML)? → PMD +- Is it **JavaScript/LWC**? → ESLint (never Regex) +- Is it **metadata XML**? → PMD with `language="xml"` + +### Step 2: Create rules ONE AT A TIME, sequentially + +Create each rule through its full workflow (create → validate → test positive → test negative → confirm working) before starting the next one. Do NOT batch-create rules — if one fails, it corrupts the config for all subsequent rules. + +**Order of creation:** +1. Regex rules first (fastest, fewest dependencies) +2. PMD Apex rules (require ast-dump per rule) +3. PMD XML rules (may share a single ruleset file) +4. ESLint rules last (require npm/config setup) + +### Step 3: Multiple PMD rules can share ONE ruleset file + +When creating multiple PMD rules, use the `create-pmd-rule.js` script for the first rule (it creates the ruleset XML file). For subsequent PMD rules going into the SAME ruleset file, you may add them manually to the existing XML file — this is the ONE exception to the "never edit manually" rule. The script always creates a new file; adding rules to an existing file requires editing the XML directly. + +### What NOT to do + +- ❌ Do NOT rewrite the entire `code-analyzer.yml` to reorganize it +- ❌ Do NOT create all rules in parallel then validate all at once +- ❌ Do NOT create a PMD rule with an unverified XPath from the reference docs — ast-dump each pattern +- ❌ Do NOT mix engine types in one creation step (e.g., creating regex + PMD rules simultaneously) +- ❌ Do NOT add `engines.eslint.eslint_config_file` to `code-analyzer.yml` before the file exists + +--- + +## High-Signal Rules + +| Rule | Rationale | +|------|-----------| +| Rule names must match `/^[A-Za-z@][A-Za-z_0-9@\-/]*$/` | Code Analyzer validation rejects others | +| Regex must be `/pattern/flags` format | JavaScript regex literal notation required | +| File extensions must start with `.` | Validation enforces `/^([.][a-zA-Z0-9-_]+)+$/` | +| Always validate after creation | `sf code-analyzer rules --rule-selector :` catches config errors | +| Always test against sample code | Catches XPath/regex mismatches before full scan | +| Use `@FullMethodName` for method calls in XPath | More reliable than `@Image` or `@MethodName` alone | +| **⚠️ NEVER skip `ast-dump`** | Run `ast-dump`, read output, THEN write XPath. No exceptions — even for "obvious" patterns. Using node names from memory without ast-dump verification is a skill failure. | +| **PMD 7 boolean attributes: use `= false()` XPath function** | In PMD 7, boolean attributes like `@WithSharing`, `@Abstract`, `@Final` are ALWAYS present on the node (never absent). Compare with `= false()` (XPath boolean function), NOT string `'false'`. `@WithSharing = false()` works. `@WithSharing='false'` (string) does NOT. `not(@WithSharing)` does NOT (attribute is always present). | +| `code-analyzer.yml` at project root | Auto-discovered by CLI — placing it elsewhere causes silent failures for rule authors | +| **XML rules MUST use `local-name()`** | Salesforce metadata namespace breaks bare element names | +| **XML text matching MUST use `@Text` attribute** | `text()` does NOT work in PMD 7's XML language. Use `//*[@Text='value']` to match text content, then navigate up with `../..` to reach parent elements. | +| **`@Text` lives on CHILD text nodes, NOT on elements** | `//*[local-name()='apiVersion'][@Text < 60]` → WRONG (0 matches). `//*[local-name()='apiVersion']/*[number(@Text) < 60]` → CORRECT. The `/*` navigates to the child text node. For exact-match patterns, `//*[@Text='value']` works because it searches ALL nodes including text nodes. But when you target a specific element by `local-name()` first, you must use `/*[@Text...]` to reach its text child. | +| **XML rules need `file_extensions` config** | PMD only scans `.xml` by default — add `file_extensions: { xml: [".xml"] }` under `engines.pmd`. Do NOT add compound extensions (`.permissionset-meta.xml`) — the validator rejects them and `.xml` alone already covers all Salesforce metadata files. | +| Named capture groups `(?...)` in regex | Narrows the violation highlight to just the captured portion | +| **Salesforce IDs start with `0`, are exactly 15 or 18 chars** | Use `/['"](?0[a-zA-Z0-9]{14}(?:[a-zA-Z0-9]{3})?)['\"]/g` — NOT `{15,18}` which also matches 16/17 char strings and produces false positives on normal words like `'BusinessAccount'` | +| **`regex_ignore` is per-LINE, not per-FILE** | It only skips lines where the ignore pattern matches. It does NOT exclude entire files or classes. A hardcoded ID on line 10 of a test class still flags unless that specific line contains the ignore pattern (e.g., `@isTest`). | +| **ALWAYS use scripts to create rules** | Do NOT manually edit `code-analyzer.yml` for regex rules — quotes/backslashes in regex inside YAML cause parsing failures. The `create-regex-rule.js` script handles serialization correctly. | +| Override built-in rules via `` | Change thresholds without writing new rules | +| **NEVER use Regex for JS/LWC/TS files** | Regex cannot distinguish code from comments/strings in JS — always use ESLint for JavaScript patterns | +| **Check built-in ESLint rules before writing custom ones** | `no-console`, `no-debugger`, `no-alert`, `eqeqeq`, `no-eval` etc. already exist — just enable them in config | + +--- + +## Gotchas + +| Issue | Resolution | +|-------|------------| +| XPath written without running ast-dump | **ALWAYS run ast-dump first.** Even if the rule works, skipping ast-dump is a process failure. Node names change between PMD versions and are not guessable. | +| SOQL-in-loop rule flags `for (x : [SELECT...])` | Use `//ForEachStatement/BlockStatement//SoqlExpression` (scope to body). The iterable SOQL is a direct child of ForEachStatement alongside BlockStatement — `//ForEachStatement//SoqlExpression` matches it as a false positive. | +| Used Regex for a JS/LWC/TS pattern | **NEVER use Regex for JavaScript files.** Regex cannot distinguish code from comments/strings in JS. Always use ESLint — check if a built-in rule (e.g., `no-console`) already exists first. | +| XPath returns 0 matches (XML metadata) | Three common causes: (1) Forgot `local-name()` — namespace blocks bare element names. (2) Used `text()='value'` — does NOT work in PMD 7. Use `@Text='value'` instead. (3) Put `[@Text]` predicate on the element — `@Text` lives on CHILD text nodes. Use `/*[@Text...]` to reach them. | +| Regex rule written inline into `code-analyzer.yml` — YAML parse error | **ALWAYS use `create-regex-rule.js`** — quotes and backslashes inside YAML cause escaping failures. Never hand-write regex into the config file. | +| `regex_ignore` doesn't exclude test classes | `regex_ignore` is **per-LINE only**. A SOQL query on line 50 of a test class flags because line 50 doesn't contain `@isTest`. For file-level exclusion: use `ignores.files` (global) or PMD XPath `[not(ancestor::UserClass[ModifierNode[@Test = true()]])]` (per-rule). | +| XPath `@WithSharing='false'` or `not(@WithSharing)` doesn't work | PMD 7 boolean attributes (`@WithSharing`, `@Abstract`, `@Final`) are ALWAYS present. String `='false'` doesn't match (it's a boolean). `not(@attr)` doesn't work (attribute is always present). Use the XPath boolean function: `@WithSharing = false()`. | +| Loop-based rule only covers `ForEachStatement` | Apex has 3 loop types: `ForEachStatement`, `ForLoopStatement`, `WhileLoopStatement`. All 3 must be in the XPath — omitting any one creates a silent coverage gap. | +| Rewrote `code-analyzer.yml` after script wrote it — YAML parse error | **NEVER manually rewrite the file after a script runs.** Add top-level config blocks (like `ignores.files`) as new entries only — do NOT touch the `engines.regex.custom_rules` section the script generated. | +| Multiple rules created at once — one failure breaks all subsequent rules | **Create rules one at a time, sequentially.** Complete the full workflow (create → validate → test) for each rule before starting the next. | + +For additional diagnostics (wrong severity, Java not found, ESLint config path, metadata file type scoping, etc.) see `/references/troubleshooting.md`. + +--- + +## Cross-Skill Integration + +| Need | Delegate to | Reason | +|------|-------------|--------| +| Run a full scan after creating rule | `dx-code-analyzer-run` | Scan execution and result presentation | +| Install Code Analyzer / fix prerequisites | `dx-code-analyzer-configure` | Setup and troubleshooting | +| Explain an existing built-in rule | `dx-code-analyzer-run` | Rule description and docs lookup | +| Edit `code-analyzer.yml` for engine settings | `dx-code-analyzer-configure` | Configuration management | + +--- + +## Script Execution + +`` is the absolute path to the directory containing this SKILL.md file. + +All scripts are bundled in the `scripts/` subdirectory of the same directory that contains this SKILL.md file. Use the absolute path to that directory — do NOT use `./scripts/` as that resolves relative to the current working directory, not the skill directory. + +```bash +node "/scripts/create-regex-rule.js" \ + --name "RuleName" --regex "/pattern/flags" ... +``` + +⚠️ **DO NOT:** +- ❌ Invent or generate script code yourself +- ❌ Use bare relative paths like `node scripts/create-regex-rule.js` (won't resolve from user's CWD) +- ❌ Use heredocs or inline script content +- ❌ Skip resolving `` — find the absolute path first + +--- + +## Reference File Index + +| File | When to read | +|------|-------------| +| `references/regex-rule-schema.md` | Building a regex rule — complete field reference, validation rules, multi-rule example | +| `references/xpath-patterns.md` | Writing XPath for Apex — index of pattern categories with syntax reference and AST node vocabulary | +| `references/xpath-patterns-governor-limits.md` | XPath patterns for SOQL/DML in loops, Database methods in loops | +| `references/xpath-patterns-method-calls.md` | XPath patterns for banning methods and annotation patterns (@AuraEnabled, @future, @IsTest) | +| `references/xpath-patterns-security.md` | XPath patterns for sharing declarations, SOQL security, hardcoded IDs | +| `references/xpath-patterns-structure.md` | XPath patterns for code structure, test quality, naming conventions | +| `references/apex-ast-reference.md` | Reading Apex AST dumps — node hierarchy, modifier attributes, key node types | +| `references/metadata-xml-rules.md` | Writing rules for metadata XML — namespace workaround, common structures, XPath patterns | +| `references/advanced-pmd-patterns.md` | Multi-rule rulesets, overriding built-in rules, exclusion patterns, Java rules, sharing across projects | +| `references/eslint-rules-discovery.md` | **READ FIRST for ANY ESLint request** — discovery workflow, built-in rules, Tier 1-3 index | +| `references/eslint-tier2-configurable.md` | ESLint Tier 2: no-restricted-globals, no-restricted-syntax, no-restricted-properties patterns | +| `references/eslint-tier3-custom-plugins.md` | ESLint Tier 3: when to create custom plugins + complete examples for all tiers | +| `references/eslint-custom-plugins.md` | Creating custom ESLint plugins — ONLY after discovery workflow confirms no built-in or configurable rule exists (Tier 3) | +| `references/troubleshooting.md` | When validation or testing fails — error diagnosis by engine type | +| `assets/pmd-ruleset-template.xml` | PMD XML skeleton with placeholders for the create-pmd-rule script | +| `examples/regex-examples.md` | 6 real-world regex rules solving community-reported problems | +| `examples/xpath-examples.md` | 6 real-world XPath rules for Apex with AST context and step-by-step creation | +| `examples/metadata-xml-examples.md` | Index of 6 real-world metadata XML rules (permissions, descriptions, API versions, flows) | +| `examples/metadata-xml-example-permissions.md` | Metadata examples: ModifyAllData/ViewAllData, field permissions in profiles | +| `examples/metadata-xml-example-fields-api.md` | Metadata examples: custom field descriptions, minimum API version | +| `examples/metadata-xml-example-flows.md` | Metadata examples: flow auto-layout, flow fault handlers | diff --git a/skills/dx-code-analyzer-custom-rule-create/assets/pmd-ruleset-template.xml b/skills/dx-code-analyzer-custom-rule-create/assets/pmd-ruleset-template.xml new file mode 100644 index 0000000..07f743f --- /dev/null +++ b/skills/dx-code-analyzer-custom-rule-create/assets/pmd-ruleset-template.xml @@ -0,0 +1,31 @@ + + + + {RULESET_DESCRIPTION} + + + + {RULE_DESCRIPTION} + {PRIORITY} + + + + + + + + + + + + diff --git a/skills/dx-code-analyzer-custom-rule-create/examples/metadata-xml-example-fields-api.md b/skills/dx-code-analyzer-custom-rule-create/examples/metadata-xml-example-fields-api.md new file mode 100644 index 0000000..6fab100 --- /dev/null +++ b/skills/dx-code-analyzer-custom-rule-create/examples/metadata-xml-example-fields-api.md @@ -0,0 +1,87 @@ +# Metadata XML Examples: Fields and API Version + +[← Back to Metadata XML Examples Index](metadata-xml-examples.md) + +## Example 2: Require Description on Custom Fields + +**Problem:** Custom fields without descriptions make orgs hard to maintain. + +**Target file:** `*.field-meta.xml` + +**Sample violating metadata:** +```xml + + Revenue__c + + Currency + + +``` + +**XPath (PMD 7):** +```xpath +//*[local-name()='CustomField'][not(*[local-name()='description']) or *[local-name()='description'][not(@Text)]] +``` + +**How it works:** Matches `CustomField` elements that either lack a `` child entirely, or have one with no text content (`@Text` absent means empty). + +**PMD ruleset:** +```xml + + Enforces that all custom fields have a description for documentation + 3 + + + + + + +``` + +--- + +## Example 3: Enforce Minimum API Version + +**Problem:** Metadata using API versions older than 60.0 should be updated. + +**Target file:** Any `*-meta.xml` with `` + +**Sample violating metadata:** +```xml + + 52.0 + Active + +``` + +**XPath (PMD 7):** +```xpath +//*[local-name()='apiVersion']/*[number(@Text) < 60] +``` + +**How it works:** Finds `` elements, then navigates to their child text node with `/*`. The `@Text` attribute on the text node holds the value (e.g., "52.0"). `number(@Text)` converts it to a float, and flags if < 60. + +⚠️ **Key insight:** `@Text` lives on CHILD text nodes, not on elements. You must use `/*` to reach the text node — putting `[@Text]` directly on the element (after `local-name()='apiVersion'`) returns 0 matches. + +**PMD ruleset:** +```xml + + Enforces minimum API version of 60.0 across all metadata + 3 + + + + + + +``` diff --git a/skills/dx-code-analyzer-custom-rule-create/examples/metadata-xml-example-flows.md b/skills/dx-code-analyzer-custom-rule-create/examples/metadata-xml-example-flows.md new file mode 100644 index 0000000..0314662 --- /dev/null +++ b/skills/dx-code-analyzer-custom-rule-create/examples/metadata-xml-example-flows.md @@ -0,0 +1,105 @@ +# Metadata XML Examples: Flows + +[← Back to Metadata XML Examples Index](metadata-xml-examples.md) + +## Example 5: Require Flow Auto-Layout + +**Problem:** Flows should use Auto-Layout canvas for consistency and maintainability. + +**Target file:** `*.flow-meta.xml` + +**Sample violating metadata:** +```xml + + 58.0 + + + CanvasMode + FREE_FORM_CANVAS + + +``` + +**XPath (PMD 7 — flags FREE_FORM_CANVAS):** +```xpath +//*[@Text='FREE_FORM_CANVAS']/../.. + [local-name()='value'] + [ancestor::*[local-name()='processMetadataValues'][.//*[@Text='CanvasMode']]] +``` + +**How it works:** Finds text nodes with "FREE_FORM_CANVAS", navigates up to the `` element, then confirms it's inside a `processMetadataValues` block that also contains "CanvasMode". + +**PMD ruleset:** +```xml + + Enforces auto-layout canvas mode on all flows for consistency + 3 + + + + + + +``` + +--- + +## Example 6: Flow Missing Fault Handler + +**Problem:** Flow actions without fault connectors can fail silently. + +**Target file:** `*.flow-meta.xml` + +**Sample violating metadata:** +```xml + + + Create_Account + Account + + Next_Step + + + + +``` + +**XPath:** +```xpath +//*[local-name()='Flow']/*[ + local-name()='recordCreates' or local-name()='recordUpdates' + or local-name()='recordDeletes' or local-name()='recordLookups' +][ + not(*[local-name()='faultConnector']) +] +``` + +**PMD ruleset:** +```xml + + Ensures all data operations in flows have fault paths + 2 + + + + + + +``` diff --git a/skills/dx-code-analyzer-custom-rule-create/examples/metadata-xml-example-permissions.md b/skills/dx-code-analyzer-custom-rule-create/examples/metadata-xml-example-permissions.md new file mode 100644 index 0000000..86a29da --- /dev/null +++ b/skills/dx-code-analyzer-custom-rule-create/examples/metadata-xml-example-permissions.md @@ -0,0 +1,95 @@ +# Metadata XML Examples: Permissions + +[← Back to Metadata XML Examples Index](metadata-xml-examples.md) + +## Example 1: Flag ModifyAllData / ViewAllData Permissions + +**Problem:** Permission sets granting ModifyAllData or ViewAllData are a security risk. + +**Target file:** `*.permissionset-meta.xml` + +**Sample violating metadata:** +```xml + + + + true + ModifyAllData + + +``` + +**XPath (PMD 7):** +```xpath +//*[@Text='ModifyAllData' or @Text='ViewAllData']/../.. + [local-name()='userPermissions'] + [.//*[@Text='true']] +``` + +**How it works:** +1. `//*[@Text='ModifyAllData' or @Text='ViewAllData']` — find text node with dangerous permission name +2. `/../..` — navigate up: text node → `` element → `` parent +3. `[local-name()='userPermissions']` — confirm we're at the right element +4. `[.//*[@Text='true']]` — check that a descendant text node contains "true" (the `` value) + +**PMD ruleset:** +```xml + + Flags permission sets that grant ModifyAllData or ViewAllData + 1 + + + + + + +``` + +--- + +## Example 4: No Field Permissions in Profiles + +**Problem:** Field permissions should be managed via Permission Sets, not Profiles. + +**Target file:** `*.profile-meta.xml` + +**Sample violating metadata:** +```xml + + + true + Account.Revenue__c + true + + +``` + +**XPath:** +```xpath +//*[local-name()='Profile']/*[local-name()='fieldPermissions'] +``` + +**PMD ruleset:** +```xml + + Profiles should not contain field-level security — use Permission Sets + 2 + + + + + + +``` diff --git a/skills/dx-code-analyzer-custom-rule-create/examples/metadata-xml-examples.md b/skills/dx-code-analyzer-custom-rule-create/examples/metadata-xml-examples.md new file mode 100644 index 0000000..b7d6c1c --- /dev/null +++ b/skills/dx-code-analyzer-custom-rule-create/examples/metadata-xml-examples.md @@ -0,0 +1,84 @@ +# Metadata XML Rule Examples + +Real-world custom PMD rules targeting Salesforce metadata XML files for org governance. + +⚠️ **PMD 7 Note:** All examples use `@Text` for text content matching. The `text()` function does NOT work in PMD 7's XML language. See `references/metadata-xml-rules.md` for full details. + +## Example Index + +| # | Example | File | +|---|---------|------| +| 1 | Flag ModifyAllData / ViewAllData Permissions | [metadata-xml-example-permissions.md](metadata-xml-example-permissions.md) | +| 2 | Require Description on Custom Fields | [metadata-xml-example-fields-api.md](metadata-xml-example-fields-api.md) | +| 3 | Enforce Minimum API Version | [metadata-xml-example-fields-api.md](metadata-xml-example-fields-api.md) | +| 4 | No Field Permissions in Profiles | [metadata-xml-example-permissions.md](metadata-xml-example-permissions.md) | +| 5 | Require Flow Auto-Layout | [metadata-xml-example-flows.md](metadata-xml-example-flows.md) | +| 6 | Flow Missing Fault Handler | [metadata-xml-example-flows.md](metadata-xml-example-flows.md) | + +--- + +## How to Create These + +1. Run `sf code-analyzer ast-dump --file your-file.xml --language xml` to see the DOM structure. If ast-dump fails, read the raw XML file directly — the file content IS the AST. +2. Write XPath using `local-name()` for element matching and `@Text` for text content (NOT `text()`) +3. Navigate from text nodes to parent elements using `../..` +4. Scope to the correct metadata type by checking the root element or using `ancestor::` +5. Configure `file_extensions: { xml: [".xml"] }` in `code-analyzer.yml` (just `.xml` covers all compound metadata extensions) +6. Add the rule to a ruleset XML file with `language="xml"` +7. Reference the ruleset in `code-analyzer.yml` under `engines.pmd.custom_rulesets` +8. Validate: `sf code-analyzer rules --rule-selector pmd:RuleName` +9. Test positive + negative: run against both a violating and clean metadata file + +## Complete Multi-Rule Ruleset Example + +```xml + + + + Org governance rules for Salesforce metadata + + + 1 + + + + + + + + + 3 + + + + + + + + + 3 + + + + + + + +``` diff --git a/skills/dx-code-analyzer-custom-rule-create/examples/regex-examples.md b/skills/dx-code-analyzer-custom-rule-create/examples/regex-examples.md new file mode 100644 index 0000000..778351f --- /dev/null +++ b/skills/dx-code-analyzer-custom-rule-create/examples/regex-examples.md @@ -0,0 +1,127 @@ +# Regex Rule Examples + +Real-world custom regex rules solving problems from the Code Analyzer community. + +## Example 1: Ban Hardcoded Salesforce IDs + +**Problem:** Developers hardcode record IDs that differ between orgs. +(GitHub issue #738 — community request for detecting bad patterns) + +```yaml +engines: + regex: + custom_rules: + NoHardcodedSalesforceIds: + regex: "/['\"](?0[a-zA-Z0-9]{14}(?:[a-zA-Z0-9]{3})?)['\"]/g" + description: "Detects hardcoded 15 or 18 character Salesforce record IDs" + violation_message: "Replace hardcoded ID with Custom Label, Custom Metadata, or Custom Setting" + severity: 2 + tags: ["Custom", "Security"] + file_extensions: [".cls", ".trigger"] +``` + +**Why this pattern:** +- Salesforce IDs are exactly **15 or 18 characters** (never 16 or 17) +- They always **start with `0`** (the key prefix) +- `0[a-zA-Z0-9]{14}` matches 15-char IDs (0 + 14 more) +- `(?:[a-zA-Z0-9]{3})?` optionally matches the 3-char case-safe suffix (making 18-char) +- `(?...)` narrows the violation highlight to just the ID, not the surrounding quotes +- ⚠️ Do NOT use `{15,18}` — this also matches 16/17 char strings and false-positives on normal words like `'BusinessAccount'` or `'RecordTypeInfos'` + +**Note on `regex_ignore`:** This field works **per-line**, not per-file. Adding `/@isTest/` only skips lines that literally contain `@isTest` — it does NOT exclude entire test classes. To exclude test files entirely, use `ignores.files` in `code-analyzer.yml` with a glob like `**/*Test.cls`. + +--- + +## Example 2: Flag TODO/FIXME Before Merge + +**Problem:** Developers leave TODOs that never get resolved. + +```yaml +engines: + regex: + custom_rules: + NoUnresolvedTodos: + regex: "/(?:TODO|FIXME|HACK|XXX)\\b/gi" + description: "Flags unresolved TODO/FIXME comments" + violation_message: "Resolve this TODO/FIXME before merging to main" + severity: 4 + tags: ["Custom", "BestPractices"] +``` + +--- + +## Example 3: Enforce WITH USER_MODE on SOQL + +**Problem:** SOQL queries without FLS enforcement are a security risk. + +```yaml +engines: + regex: + custom_rules: + RequireUserModeOnSoql: + regex: "/\\[\\s*SELECT(?![^\\]]*WITH\\s+(?:USER_MODE|SECURITY_ENFORCED))[^\\]]*\\]/gi" + description: "SOQL queries must use WITH USER_MODE or WITH SECURITY_ENFORCED" + violation_message: "Add WITH USER_MODE to this SOQL query for FLS enforcement" + severity: 2 + tags: ["Custom", "Security"] + file_extensions: [".cls", ".trigger"] + regex_ignore: "/@isTest|@IsTest|@testSetup|@TestSetup/" +``` + +--- + +## Example 4: Ban @SuppressWarnings and NOPMD + +**Problem:** Teams want to prevent suppression of violations. +(GitHub issue #1972 — real user request) + +```yaml +engines: + regex: + custom_rules: + ProhibitSuppressWarnings: + regex: "/@SuppressWarnings\\([^)]*\\)|\\/\\/\\s*NOPMD/gi" + description: "Prohibits suppression of code analysis warnings" + violation_message: "Fix the underlying issue instead of suppressing warnings" + severity: 2 + tags: ["Custom", "BestPractices"] + file_extensions: [".cls", ".trigger"] +``` + +--- + +## Example 5: Detect Old API Versions in Metadata + +**Problem:** Metadata files using API versions more than 2 years old. + +```yaml +engines: + regex: + custom_rules: + NoOldApiVersions: + regex: "/(4[0-9]|5[0-5])\\.0<\\/apiVersion>/g" + description: "Detects metadata files using API version 55.0 or below" + violation_message: "Update API version to 60.0 or higher" + severity: 3 + tags: ["Custom", "BestPractices"] + file_extensions: [".xml"] +``` + +--- + +## Example 6: Enforce Test Class Naming Convention + +**Problem:** Test classes should end with `_TEST` or `Test`. + +```yaml +engines: + regex: + custom_rules: + TestClassNaming: + regex: "/@(?:isTest|IsTest)(?:\\([^)]*\\))?\\s*(?:public|private)\\s+class\\s+(?!.*(?:_TEST|Test)\\b)\\w+/g" + description: "Test classes must end with _TEST or Test suffix" + violation_message: "Rename this test class to end with _TEST or Test (e.g., MyServiceTest)" + severity: 3 + tags: ["Custom", "CodeStyle"] + file_extensions: [".cls"] +``` diff --git a/skills/dx-code-analyzer-custom-rule-create/examples/xpath-examples.md b/skills/dx-code-analyzer-custom-rule-create/examples/xpath-examples.md new file mode 100644 index 0000000..c022b49 --- /dev/null +++ b/skills/dx-code-analyzer-custom-rule-create/examples/xpath-examples.md @@ -0,0 +1,227 @@ +# XPath Rule Examples + +Real-world custom PMD/XPath rules for Apex, derived from community requests and common enforcement needs. + +## Example 1: Ban System.debug in Production Code + +**Problem:** Debug statements clutter logs and expose sensitive data. + +**Sample violating code:** +```apex +public class MyService { + public void doWork() { + System.debug('Sensitive: ' + record.SSN__c); + } +} +``` + +**AST nodes (from ast-dump):** +```xml + +``` + +**XPath:** +```xpath +//MethodCallExpression[@FullMethodName='System.debug'] + [not(ancestor::UserClass[ModifierNode[@Test = true()]])] +``` + +**PMD ruleset:** +```xml + + 3 + + + //MethodCallExpression[@FullMethodName='System.debug'][not(ancestor::UserClass[ModifierNode[@Test = true()]])] + + + +``` + +--- + +## Example 2: SOQL Query Inside a Loop + +**Problem:** Governor limit risk — N+1 queries. + +**Sample violating code:** +```apex +public class AccountProcessor { + public void process(List accounts) { + for (Account acc : accounts) { + List contacts = [SELECT Id FROM Contact WHERE AccountId = :acc.Id]; + } + } +} +``` + +**AST nodes:** +```xml + + ... + + + + +``` + +**XPath:** +```xpath +//ForEachStatement/BlockStatement//SoqlExpression +| +//ForLoopStatement/BlockStatement//SoqlExpression +| +//WhileLoopStatement/BlockStatement//SoqlExpression +``` + +> ⚠️ **Why `/BlockStatement//`?** ForEachStatement has the iterable SOQL as a direct child alongside BlockStatement. Without scoping to `/BlockStatement//`, the XPath also matches `for (Contact c : [SELECT...])` — a valid Apex idiom that is NOT a governor limit risk. + +--- + +## Example 3: Enforce @IsTest(testFor) Annotation + +**Problem:** Test classes should specify which class they test for traceability. +(GitHub issue #2008 — real community request) + +**Sample violating code:** +```apex +@IsTest +public class MyServiceTest { + // Missing testFor parameter +} +``` + +**AST nodes:** +```xml + + + + + + +``` + +**XPath:** +```xpath +/ApexFile/UserClass/ModifierNode/Annotation[ + @Image='IsTest' + and count(AnnotationParameter[@Name='testFor']) = 0 +] +``` + +--- + +## Example 4: DML Operations Without Try-Catch + +**Problem:** Unhandled DML exceptions cause runtime failures. +(GitHub issue #738 — user requested "detect missing try-catch") + +**Sample violating code:** +```apex +public class AccountService { + public void createAccount(String name) { + Account acc = new Account(Name = name); + insert acc; // No try-catch! + } +} +``` + +**AST nodes:** +```xml + + + + +``` + +**XPath:** +```xpath +//DmlInsertStatement[not(ancestor::TryCatchFinallyBlockStatement)] +| +//DmlUpdateStatement[not(ancestor::TryCatchFinallyBlockStatement)] +| +//DmlDeleteStatement[not(ancestor::TryCatchFinallyBlockStatement)] +``` + +--- + +## Example 5: Nested If Statements (Max Depth 3) + +**Problem:** Deep nesting reduces readability and maintainability. + +**Sample violating code:** +```apex +public class ComplexLogic { + public void process(Integer a, Integer b, Integer c, Integer d) { + if (a > 0) { + if (b > 0) { + if (c > 0) { + if (d > 0) { // 4th level — violation! + doWork(); + } + } + } + } + } +} +``` + +**XPath (flags depth 4+):** +```xpath +//IfBlockStatement[ + ancestor::IfBlockStatement[ + ancestor::IfBlockStatement[ + ancestor::IfBlockStatement + ] + ] +] +``` + +--- + +## Example 6: Class Without Explicit Sharing Declaration + +**Problem:** Classes without sharing default to "without sharing" — security risk. + +**Sample violating code:** +```apex +public class UnsafeService { // No "with sharing" or "without sharing" + public List getAccounts() { + return [SELECT Id FROM Account]; + } +} +``` + +**AST nodes:** +```xml + + +``` + +**XPath:** +```xpath +//UserClass[ + ModifierNode[ + @WithSharing = false() + and @WithoutSharing = false() + and @InheritedSharing = false() + ] + and not(ModifierNode[@Test = true()]) +] +``` + +--- + +## How to Create These + +For each example above, the creation process was: + +1. Write the sample violating code (5-10 lines) +2. Run: `sf code-analyzer ast-dump --file sample.cls` +3. Find the relevant node in the XML output +4. Write XPath using the exact node names and attributes +5. Create the ruleset XML using the template +6. Validate: `sf code-analyzer rules --rule-selector pmd:RuleName` +7. Test: `sf code-analyzer run --rule-selector pmd:RuleName --target sample.cls` diff --git a/skills/dx-code-analyzer-custom-rule-create/references/advanced-pmd-patterns.md b/skills/dx-code-analyzer-custom-rule-create/references/advanced-pmd-patterns.md new file mode 100644 index 0000000..2d49586 --- /dev/null +++ b/skills/dx-code-analyzer-custom-rule-create/references/advanced-pmd-patterns.md @@ -0,0 +1,288 @@ +# Advanced PMD Patterns + +Multi-rule rulesets, overriding built-in rules, exclusion patterns, Java-based rules, and sharing rules across projects. + +## Multiple Rules in One Ruleset File + +A single ruleset XML can contain any number of rules. This is the recommended approach for teams maintaining a shared rule set: + +```xml + + + + Team coding standards + + + + 3 + + + + + + + + + + 2 + + + + + + + + + + 3 + + + + + + + +``` + +Rules with different `language` values (apex, xml, visualforce, etc.) can coexist in one ruleset. PMD applies each rule only to files matching its language. + +## Overriding Built-in PMD Rules + +Override severity, priority, or configurable properties on PMD's built-in rules without writing new ones: + +```xml + + + + Customized built-in rule thresholds + + + + 2 + + + + + + + + + + + + + + + + 2 + + + + + + + + + + +``` + +**Configuration:** +```yaml +# code-analyzer.yml +engines: + pmd: + custom_rulesets: + - "custom-rules/customized-builtins.xml" +``` + +### How rule ref works + +| Pattern | Effect | +|---------|--------| +| `` | Override one specific rule | +| `` | Include entire category | +| `` | Include category minus specific rules | +| Nested `` | Override severity (1=Critical, 2=High, 3=Moderate, 4=Low, 5=Info) | +| Nested `` | Override configurable thresholds/options | + +### Common built-in rules to customize + +| Rule | Useful Properties | +|------|-------------------| +| `CyclomaticComplexity` | `classReportLevel` (default: 40), `methodReportLevel` (default: 25) | +| `ExcessiveParameterList` | `minimum` (default: 4) | +| `ExcessiveClassLength` | `minimum` (default: 1000) | +| `TooManyFields` | `maxfields` (default: 15) | +| `NcssMethodCount` | `minimum` (default: 60) | +| `DebugsShouldUseLoggingLevel` | `strictMode` (default: false) | +| `ApexUnitTestClassShouldHaveAsserts` | (no properties — exclude if unwanted) | + +## Exclusion Patterns + +Three mechanisms for excluding files and rules: + +### 1. File exclusions in `code-analyzer.yml` + +Exclude entire directories or file patterns from all engines: + +```yaml +# code-analyzer.yml +ignores: + files: + - "**/node_modules/**" + - "**/fflib_*" + - "**/*Test*.cls" + - "**/generated/**" +``` + +Supports glob patterns: `*`, `**`, `?`, `[...]`, `{...}` + +### 2. Rule exclusions in PMD rulesets + +Exclude specific rules when including an entire category: + +```xml + + + + +``` + +### 3. Exclude patterns in PMD rulesets + +Exclude files at the PMD level (applies only to rules in that ruleset): + +```xml + + .*/fflib_.* + .*/test/.* + + + ... + + +``` + +### 4. Disabling individual rules via config + +Disable any rule without editing rulesets: + +```yaml +# code-analyzer.yml +rules: + pmd: + ApexUnitTestClassShouldHaveAsserts: + disabled: true + CyclomaticComplexity: + severity: "Low" # Downgrade instead of disable +``` + +## Java-Based Custom Rules (Advanced) + +For rules that need logic beyond XPath (complex data flow, cross-file analysis), write Java PMD rules: + +1. **Create a Java class** extending `net.sourceforge.pmd.lang.apex.rule.AbstractApexRule` +2. **Compile into a JAR** file +3. **Reference in config:** + +```yaml +# code-analyzer.yml +engines: + pmd: + java_classpath_entries: + - "custom-rules/my-rules.jar" # JAR with compiled rule classes + - "custom-rules/lib/" # Folder of JARs (all JARs inside loaded) + custom_rulesets: + - "category/myteam/rules.xml" # Ruleset inside the JAR (classpath resource) +``` + +The JAR's ruleset XML references the Java class: +```xml + + 3 + +``` + +**Requirements:** +- Java 11+ for compilation +- PMD 7.x API (use `net.sourceforge.pmd.lang.rule.xpath.XPathRule` for XPath, or extend `AbstractApexRule` for visitor-pattern Java rules) + +## Sharing Rules Across Projects + +### Approach 1: Relative paths with shared config + +If projects share a parent directory or monorepo: +```yaml +# code-analyzer.yml +engines: + pmd: + custom_rulesets: + - "../shared-rules/team-standards.xml" # Relative to config_root +``` + +`config_root` is the directory containing `code-analyzer.yml`. All relative paths resolve from there. + +### Approach 2: JAR-based distribution + +Package rules into a JAR and distribute via artifact repository: + +1. Build a JAR containing your ruleset XML at a classpath resource path (e.g., `category/myteam/standards.xml`) +2. Place the JAR in each project's `custom-rules/` directory (or download in CI) +3. Reference in config: + +```yaml +engines: + pmd: + java_classpath_entries: + - "custom-rules/myteam-rules-1.0.jar" + custom_rulesets: + - "category/myteam/standards.xml" # Resource inside the JAR +``` + +### Approach 3: Git submodule or symlink + +For simpler setups, share the ruleset XML via git submodule: +```bash +git submodule add https://github.com/myteam/pmd-rules.git custom-rules/shared +``` + +```yaml +engines: + pmd: + custom_rulesets: + - "custom-rules/shared/team-standards.xml" +``` + +### CI/CD Integration + +All approaches work in CI because paths are relative to `config_root`. Example GitHub Actions step: + +```yaml +- name: Run Code Analyzer + run: | + sf code-analyzer run --rule-selector "pmd:2,regex:2" --target force-app/ +``` + +No absolute paths needed — the config file handles resolution. diff --git a/skills/dx-code-analyzer-custom-rule-create/references/apex-ast-reference.md b/skills/dx-code-analyzer-custom-rule-create/references/apex-ast-reference.md new file mode 100644 index 0000000..a46a3c5 --- /dev/null +++ b/skills/dx-code-analyzer-custom-rule-create/references/apex-ast-reference.md @@ -0,0 +1,127 @@ +# Apex AST Node Reference + +PMD 7.x Apex AST node types and their attributes. Use `sf code-analyzer ast-dump --file ` to see the actual tree for your code. + +## Node Hierarchy (Common Structure) + +```text +ApexFile +├── UserClass / UserInterface / UserTrigger / UserEnum +│ ├── FormalComment (Javadoc) +│ ├── ModifierNode (public, private, static, etc.) +│ ├── Field (class-level variables) +│ │ └── VariableDeclaration +│ └── Method +│ ├── ModifierNode +│ ├── Parameter +│ └── BlockStatement +│ ├── VariableDeclarationStatements +│ ├── ExpressionStatement +│ │ └── MethodCallExpression +│ ├── IfElseBlockStatement +│ │ └── IfBlockStatement +│ ├── ForEachStatement / ForLoopStatement / WhileLoopStatement +│ ├── TryCatchFinallyBlockStatement +│ │ ├── BlockStatement (try body) +│ │ ├── CatchBlockStatement +│ │ └── BlockStatement (finally body) +│ ├── DmlInsertStatement / DmlUpdateStatement / DmlDeleteStatement +│ ├── ReturnStatement +│ └── ThrowStatement +``` + +## ModifierNode Attributes + +Every class, method, field, and parameter has a `ModifierNode` with these boolean attributes: + +| Attribute | Meaning | +|-----------|---------| +| `Public` | public access | +| `Private` | private access | +| `Protected` | protected access | +| `Global` | global access | +| `Static` | static member | +| `Final` | final/const | +| `Abstract` | abstract method/class | +| `Virtual` | virtual method/class | +| `Override` | overrides parent method | +| `Test` | @IsTest annotated | +| `TestOrTestSetup` | @IsTest or @TestSetup | +| `WebService` | webservice method | +| `WithSharing` | with sharing class | +| `WithoutSharing` | without sharing class | +| `InheritedSharing` | inherited sharing class | +| `Transient` | transient field | +| `Modifiers` | Bitmask integer of all modifiers | + +⚠️ **PMD 7 Boolean Attribute Comparison:** + +In PMD 7, these boolean attributes are **always present** on the node — they are never absent. This means: +- ✅ `@WithSharing = false()` — correct (XPath boolean function) +- ✅ `@WithSharing = true()` — correct +- ❌ `@WithSharing = 'false'` — WRONG (string comparison, won't match) +- ❌ `not(@WithSharing)` — WRONG (attribute always exists, so this is always false) +- ❌ `@WithSharing` (as existence check) — WRONG (always true, attribute always present) + +Use the XPath `true()`/`false()` functions for boolean comparisons: +```xpath + +//UserClass[ModifierNode[@WithSharing = false() and @WithoutSharing = false() and @InheritedSharing = false()]] + + +//UserClass[ModifierNode[@Abstract = true()]] +``` + +## MethodCallExpression Attributes + +| Attribute | Example | Notes | +|-----------|---------|-------| +| `FullMethodName` | `'System.debug'`, `'Database.query'` | Most reliable for matching | +| `MethodName` | `'debug'`, `'query'` | Just the method part | +| `InputParametersSize` | `'1'`, `'0'` | Number of arguments | + +## LiteralExpression Attributes + +| Attribute | Values | Notes | +|-----------|--------|-------| +| `LiteralType` | `STRING`, `INTEGER`, `DECIMAL`, `DOUBLE`, `LONG`, `BOOLEAN` | Type of literal | +| `Image` | The literal value | For strings, excludes quotes | +| `String` | `'true'` / `'false'` | Boolean shorthand | +| `Null` | `'true'` / `'false'` | Is it a null literal | + +## SoqlExpression Attributes + +| Attribute | Example | Notes | +|-----------|---------|-------| +| `Query` | `'SELECT Id FROM Account WHERE Name = :name'` | Original SOQL as written | +| `CanonicalQuery` | `'SELECT Id FROM Account WHERE Name = :tmpVar1'` | Normalized (bind vars replaced) | + +## Annotation Node + +```xml + + + +``` + +Use: `//Annotation[@Image='IsTest']` to find test annotations. + +## DML Nodes + +| Statement | Node | Notes | +|-----------|------|-------| +| `insert x;` | `DmlInsertStatement` | | +| `update x;` | `DmlUpdateStatement` | | +| `delete x;` | `DmlDeleteStatement` | | +| `upsert x;` | `DmlUpsertStatement` | | +| `undelete x;` | `DmlUndeleteStatement` | | +| `Database.insert(x)` | `MethodCallExpression[@FullMethodName='Database.insert']` | Method-form DML | + +## Tips for Reading AST Dumps + +1. **`Image` attribute** = the actual source name (variable name, class name, method name) +2. **`DefiningType` attribute** = which class this node belongs to (present on every node) +3. **`RealLoc='true'`** = this node has a real source position (not compiler-generated) +4. **`RealLoc='false'`** = compiler-generated node (e.g., implicit modifiers, empty references) +5. **`EmptyReferenceExpression`** = placeholder for unqualified variable access (ignore these) +6. **`Type` attribute on VariableDeclaration** = the declared type (e.g., `'Account'`, `'List'`) diff --git a/skills/dx-code-analyzer-custom-rule-create/references/eslint-custom-plugins.md b/skills/dx-code-analyzer-custom-rule-create/references/eslint-custom-plugins.md new file mode 100644 index 0000000..322b604 --- /dev/null +++ b/skills/dx-code-analyzer-custom-rule-create/references/eslint-custom-plugins.md @@ -0,0 +1,247 @@ +# ESLint Custom Rules for LWC/JavaScript + +Create and integrate custom ESLint rules for Lightning Web Components, JavaScript, and TypeScript through Code Analyzer. + +## How It Works + +Code Analyzer's ESLint engine loads your project's ESLint configuration alongside its built-in rules. You can: +- Use your own `eslint.config.js` with custom plugins +- Extend or override built-in base configs (LWC, TypeScript, SLDS, React) +- Install any npm ESLint plugin and have Code Analyzer pick it up + +## Configuration + +```yaml +# code-analyzer.yml +engines: + eslint: + # Point to your project's ESLint config + eslint_config_file: "eslint.config.js" + + # Or auto-discover from workspace (searches for eslint.config.js/mjs/cjs) + auto_discover_eslint_config: true + + # Disable base configs you don't need + disable_javascript_base_config: false + disable_lwc_base_config: false + disable_typescript_base_config: false + disable_slds_base_config: false + disable_react_base_config: false + + # Custom file extension mapping + file_extensions: + javascript: [".js", ".cjs", ".mjs", ".jsx"] + typescript: [".ts", ".tsx"] + html: [".html", ".htm", ".cmp"] + css: [".css", ".scss"] +``` + +## Adding Custom ESLint Plugins + +### Step 1: Install the plugin + +```bash +npm install --save-dev eslint-plugin-my-custom +``` + +### Step 2: Create or update `eslint.config.js` (flat config) + +```javascript +const myPlugin = require('eslint-plugin-my-custom'); + +module.exports = [ + { + plugins: { + 'my-custom': myPlugin + }, + rules: { + 'my-custom/no-dangerous-pattern': 'error', + 'my-custom/enforce-naming': ['warn', { pattern: '^[a-z]' }] + } + } +]; +``` + +### Step 3: Reference in `code-analyzer.yml` + +```yaml +engines: + eslint: + eslint_config_file: "eslint.config.js" +``` + +### Step 4: Validate and run + +```bash +# Check that custom rules appear +sf code-analyzer rules --rule-selector eslint + +# Run against targets +sf code-analyzer run --rule-selector eslint --target force-app/main/default/lwc/ +``` + +## Common LWC Custom Rule Patterns + +### Enforce component naming convention + +```javascript +// eslint.config.js +module.exports = [ + { + rules: { + '@lwc/lwc/no-unknown-wire-adapters': 'error', + '@lwc/lwc/no-api-reassignments': 'error', + '@lwc/lwc/no-leaky-event-listeners': 'error' + } + } +]; +``` + +### Add SSR compatibility checks + +```javascript +module.exports = [ + { + rules: { + '@lwc/lwc/no-restricted-browser-globals-during-ssr': 'error', + '@lwc/lwc/no-unsupported-ssr-properties': 'error' + } + } +]; +``` + +### TypeScript strict rules + +```javascript +module.exports = [ + { + rules: { + '@typescript-eslint/no-explicit-any': 'error', + '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], + '@typescript-eslint/strict-boolean-expressions': 'warn' + } + } +]; +``` + +## Disabling Base Configs + +When you want full control over which rules run, disable the built-in base configs: + +```yaml +engines: + eslint: + eslint_config_file: "eslint.config.js" + disable_javascript_base_config: true + disable_lwc_base_config: true + disable_typescript_base_config: true +``` + +This prevents Code Analyzer's bundled rules from running — only your custom config's rules apply. Parsers are still configured (so files parse correctly), but no built-in rules fire. + +## Overriding Rule Severity via Config + +After ESLint rules are loaded, override severity in `code-analyzer.yml`: + +```yaml +rules: + eslint: + no-unused-vars: + severity: "Low" + my-custom/dangerous-pattern: + severity: "Critical" + no-console: + disabled: true +``` + +## Legacy Config Support + +If your project uses `.eslintrc.js` (ESLint v8 format), it still works: + +```yaml +engines: + eslint: + eslint_config_file: ".eslintrc.js" + eslint_ignore_file: ".eslintignore" # Only needed with legacy config +``` + +Flat config (`eslint.config.js`) is recommended for new projects. + +## Plugin Requirements + +For Code Analyzer to pick up custom plugin rules, each rule must have: +- `meta.docs.description` — rule description +- `meta.docs.url` — documentation URL + +Rules without these metadata fields are silently excluded. Deprecated rules are also excluded. + +## Banning APIs Without a Custom Plugin + +Core ESLint includes powerful "restrictor" rules that can ban specific globals, syntax patterns, or properties — no plugin installation needed. These rules are NOT active by default — you must enable them in `eslint.config.js`: + +### `no-restricted-globals` — ban global functions/variables + +```javascript +module.exports = [ + { + files: ["**/lwc/**/*.js"], + rules: { + "no-restricted-globals": ["error", + { "name": "setTimeout", "message": "Use lifecycle hooks instead of setTimeout." }, + { "name": "setInterval", "message": "Use lifecycle hooks instead of setInterval." }, + { "name": "eval", "message": "eval is forbidden for security." } + ] + } + } +]; +``` + +### `no-restricted-syntax` — ban arbitrary AST patterns + +```javascript +module.exports = [ + { + files: ["**/lwc/**/*.js"], + rules: { + "no-restricted-syntax": ["error", + { "selector": "CallExpression[callee.name='fetch']", "message": "Use Lightning Data Service instead of fetch." }, + { "selector": "NewExpression[callee.name='XMLHttpRequest']", "message": "Use fetch or LDS." } + ] + } + } +]; +``` + +### `no-restricted-properties` — ban specific object methods + +```javascript +module.exports = [ + { + files: ["**/lwc/**/*.js"], + rules: { + "no-restricted-properties": ["error", + { "object": "window", "property": "location", "message": "Use NavigationMixin." }, + { "object": "document", "property": "cookie", "message": "Cookies are not available in LWC." } + ] + } + } +]; +``` + +⚠️ **These rules will NOT appear in `sf code-analyzer rules` output until you:** +1. Create the `eslint.config.js` file with the rule enabled +2. Set `engines.eslint.eslint_config_file: "eslint.config.js"` in `code-analyzer.yml` +3. Run `sf code-analyzer rules --rule-selector eslint:no-restricted-globals` to verify + +They are core ESLint rules, not Code Analyzer built-ins — they require your config to activate. + +## When to Use ESLint vs Regex vs PMD + +| Need | Engine | +|------|--------| +| JavaScript/TypeScript code patterns | **ESLint** | +| LWC component best practices | **ESLint** (with @lwc plugin) | +| HTML template issues | **ESLint** (with SLDS or custom HTML plugin) | +| Simple string patterns in JS/TS | **ESLint** (Regex cannot distinguish code from comments/strings in JS) | +| Apex code structure | **PMD** (ESLint doesn't parse Apex) | +| Metadata XML governance | **PMD** with `language="xml"` | diff --git a/skills/dx-code-analyzer-custom-rule-create/references/eslint-rules-discovery.md b/skills/dx-code-analyzer-custom-rule-create/references/eslint-rules-discovery.md new file mode 100644 index 0000000..b9d596b --- /dev/null +++ b/skills/dx-code-analyzer-custom-rule-create/references/eslint-rules-discovery.md @@ -0,0 +1,188 @@ +# ESLint Rules Discovery & Configuration + +**READ THIS FIRST** for ANY ESLint request. 90% of ESLint rules already exist — this guide shows you how to find and configure them instead of creating custom plugins. + +--- + +## Three Tiers of ESLint Rules + +| Tier | What It Is | When to Use | Effort | Coverage | +|---|---|---|---|---| +| **Tier 1: Built-In Rules** | 200+ core ESLint rules | First choice — check here FIRST | LOW (enable in config) | 70% of requests | +| **Tier 2: Configurable Rules** | no-restricted-syntax, no-restricted-globals, no-restricted-properties | When no built-in rule exists but pattern is generic | MEDIUM (AST selector) | 20% of requests | +| **Tier 3: Custom Plugins** | Write your own ESLint plugin | LAST RESORT — domain-specific multi-node patterns only | HIGH (Node code, testing) | 10% of requests | + +**Always start at Tier 1 and work down.** + +--- + +## Tier 1: Discover Built-In Rules + +### Discovery Workflow (MANDATORY) + +Before creating ANY custom ESLint rule: + +1. **Run:** `sf code-analyzer rules --rule-selector eslint` +2. **Search output** for keywords from the user's request: + - User says "ban console.log" → search for "console" + - User says "enforce ===" → search for "equal" or "strict" + - User says "no unused variables" → search for "unused" +3. **Check naming patterns:** + - `no-*` — Disallow something (no-console, no-debugger, no-eval) + - `prefer-*` — Prefer one style (prefer-const, prefer-arrow-callback) + - `require-*` — Require something (require-await, require-yield) + - `@lwc/lwc/*` — LWC-specific rules (no-inner-html, no-document-query) +4. **If found:** Configure it (see "Configuration Workflow" below). **STOP — do NOT create a custom plugin.** +5. **If NOT found:** Proceed to Tier 2. + +### Common Built-In Rules by Category + +#### Code Quality +| User Request | Rule Name | Config Example | +|---|---|---| +| "No unused variables" | `no-unused-vars` | `"no-unused-vars": "error"` | +| "No unused imports" | `no-unused-vars` | Same rule covers imports | +| "Require await in async functions" | `require-await` | `"require-await": "error"` | +| "No empty blocks" | `no-empty` | `"no-empty": "error"` | +| "No unreachable code" | `no-unreachable` | `"no-unreachable": "error"` | + +#### Security +| User Request | Rule Name | Config Example | +|---|---|---| +| "Ban eval()" | `no-eval` | `"no-eval": "error"` | +| "Ban debugger" | `no-debugger` | `"no-debugger": "error"` | +| "No implied eval" | `no-implied-eval` | `"no-implied-eval": "error"` | +| "Ban alert()" | `no-alert` | `"no-alert": "error"` | + +#### Best Practices +| User Request | Rule Name | Config Example | +|---|---|---| +| "Enforce ===" | `eqeqeq` | `"eqeqeq": ["error", "always"]` | +| "Prefer const" | `prefer-const` | `"prefer-const": "error"` | +| "No var" | `no-var` | `"no-var": "error"` | +| "Prefer arrow functions" | `prefer-arrow-callback` | `"prefer-arrow-callback": "error"` | +| "Require default in switch" | `default-case` | `"default-case": "error"` | + +#### Logging/Debugging +| User Request | Rule Name | Config Example | +|---|---|---| +| "Ban console.log" | `no-console` | `"no-console": "error"` | +| "Ban console.* except error" | `no-console` | `"no-console": ["error", { "allow": ["error", "warn"] }]` | + +### LWC Plugin Rules (Check if Available) + +If Code Analyzer has `@lwc/eslint-plugin-lwc` enabled: + +| User Request | Rule Name | Notes | +|---|---|---| +| "Ban innerHTML" | `@lwc/lwc/no-inner-html` | XSS prevention | +| "No document.querySelector" | `@lwc/lwc/no-document-query` | Use template queries | +| "Validate @api usage" | `@lwc/lwc/no-api-reassignments` | Prevents reassigning @api properties | +| "Validate @wire syntax" | `@lwc/lwc/valid-wire` | Built-in | +| "No async in getters" | `@lwc/lwc/no-async-operation` | Lifecycle hook validation | + +**Check availability:** +```bash +sf code-analyzer rules --rule-selector eslint | grep -i "@lwc" +``` + +### External Documentation + +- **All ESLint built-in rules:** https://eslint.org/docs/latest/rules/ +- **LWC ESLint plugin:** https://github.com/salesforce/eslint-plugin-lwc +- **Salesforce Lightning plugin:** https://github.com/forcedotcom/eslint-plugin-lightning + +--- + +## Configuration Workflow (Tier 1) + +When a built-in rule exists: + +1. **Create `eslint.config.js`** if it doesn't exist: + ```javascript + module.exports = [ + { + files: ["**/lwc/**/*.js"], // or ["**/*.js"] for all JS files + rules: { + // Rules go here + } + } + ]; + ``` + +2. **Add the rule** with desired severity: + ```javascript + rules: { + "no-console": "error", // Ban completely + "eqeqeq": ["error", "always"], // With options + "no-unused-vars": ["warn"] // Warning instead of error + } + ``` + +3. **Update `code-analyzer.yml`** (AFTER file exists): + ```yaml + engines: + eslint: + eslint_config_file: "eslint.config.js" + ``` + +4. **Validate:** + ```bash + sf code-analyzer rules --rule-selector eslint:no-console + ``` + If the rule does NOT appear in the output, the config is wrong. Do NOT proceed to testing. + +5. **Test positive:** + ```bash + sf code-analyzer run --rule-selector eslint:no-console --target lwc/ + ``` + +6. **Test negative:** Run against clean code, confirm 0 violations. + +--- + +## Tier 2 and Tier 3: Configurable Rules and Custom Plugins + +For detailed information on these tiers: + +| Tier | File | When to Use | +|------|------|-------------| +| **Tier 2: Configurable Rules** | [eslint-tier2-configurable.md](eslint-tier2-configurable.md) | no-restricted-globals, no-restricted-syntax, no-restricted-properties — ban specific patterns without writing plugins | +| **Tier 3: Custom Plugins** | [eslint-tier3-custom-plugins.md](eslint-tier3-custom-plugins.md) | Complete examples for all tiers + when to create custom plugins (LAST RESORT) | + +--- + +## Decision Tree + +```text +User asks for ESLint rule + ↓ +Run: sf code-analyzer rules --rule-selector eslint + ↓ +Search output for keywords + ↓ + ├─ Built-in rule found? → Configure it (Tier 1) → DONE ✅ + │ + ├─ Pattern is "ban function X"? → Use no-restricted-globals (Tier 2) → DONE ✅ + │ + ├─ Pattern is "ban syntax Y"? → Use no-restricted-syntax (Tier 2) → DONE ✅ + │ + └─ Complex multi-node pattern? → Create custom plugin (Tier 3) → See eslint-custom-plugins.md +``` + +--- + +## Key Rules + +1. **ALWAYS run discovery FIRST** — 90% of requests are Tier 1 or Tier 2 +2. **NEVER create a custom plugin without checking built-in rules** — this is a skill failure +3. **VALIDATE after configuration** — `sf code-analyzer rules --rule-selector eslint:` must show the rule +4. **TEST both positive and negative samples** — confirm violations are caught AND clean code passes + +--- + +## When to Read Other References + +- **All ESLint requests start here** (discovery) +- **If Tier 3 custom plugin needed:** Read `references/eslint-custom-plugins.md` +- **If troubleshooting config issues:** Read `references/troubleshooting.md` (ESLint section) diff --git a/skills/dx-code-analyzer-custom-rule-create/references/eslint-tier2-configurable.md b/skills/dx-code-analyzer-custom-rule-create/references/eslint-tier2-configurable.md new file mode 100644 index 0000000..20256ec --- /dev/null +++ b/skills/dx-code-analyzer-custom-rule-create/references/eslint-tier2-configurable.md @@ -0,0 +1,114 @@ +# ESLint Tier 2: Configurable Rules + +[← Back to ESLint Rules Discovery](eslint-rules-discovery.md) + +## Tier 2: Configurable Rules + +When no built-in rule exists but the pattern is generic (ban a function, ban a property, ban a syntax construct): + +### Option A: `no-restricted-globals` + +Ban specific global functions or variables. + +**Example: Ban setTimeout/setInterval** +```javascript +// eslint.config.js +module.exports = [ + { + files: ["**/lwc/**/*.js"], + rules: { + "no-restricted-globals": ["error", + { + name: "setTimeout", + message: "Use LWC lifecycle hooks instead of setTimeout" + }, + { + name: "setInterval", + message: "Use LWC lifecycle hooks instead of setInterval" + } + ] + } + } +]; +``` + +**Example: Ban window.localStorage** +```javascript +rules: { + "no-restricted-globals": ["error", + { + name: "localStorage", + message: "Use Salesforce state management instead of localStorage" + } + ] +} +``` + +### Option B: `no-restricted-syntax` + +Ban specific AST patterns using ESLint selectors. + +**Example: Ban eval() calls** +```javascript +rules: { + "no-restricted-syntax": ["error", + { + selector: "CallExpression[callee.name='eval']", + message: "eval() is forbidden for security reasons" + } + ] +} +``` + +**Example: Ban innerHTML property access** +```javascript +rules: { + "no-restricted-syntax": ["error", + { + selector: "MemberExpression[property.name='innerHTML']", + message: "innerHTML is forbidden - use textContent or render() for XSS prevention" + } + ] +} +``` + +**Example: Ban for-in loops** +```javascript +rules: { + "no-restricted-syntax": ["error", + { + selector: "ForInStatement", + message: "for-in loops are forbidden - use for-of or Object.keys()" + } + ] +} +``` + +#### ESLint Selector Syntax Cheat Sheet + +| Pattern | Selector | Example | +|---|---|---| +| Function call | `CallExpression[callee.name='functionName']` | `eval()`, `alert()` | +| Property access | `MemberExpression[property.name='propName']` | `obj.innerHTML` | +| Statement type | `ForInStatement`, `WithStatement` | for-in, with statements | +| Operator | `BinaryExpression[operator='==']` | `==` instead of `===` | +| Literal value | `Literal[value='string']` | Specific string literals | + +**Full selector docs:** https://eslint.org/docs/latest/extend/selectors + +### Option C: `no-restricted-properties` + +Ban specific object properties. + +**Example: Ban Object.prototype methods** +```javascript +rules: { + "no-restricted-properties": ["error", + { + object: "Object", + property: "setPrototypeOf", + message: "setPrototypeOf is forbidden for performance reasons" + } + ] +} +``` diff --git a/skills/dx-code-analyzer-custom-rule-create/references/eslint-tier3-custom-plugins.md b/skills/dx-code-analyzer-custom-rule-create/references/eslint-tier3-custom-plugins.md new file mode 100644 index 0000000..12ded1b --- /dev/null +++ b/skills/dx-code-analyzer-custom-rule-create/references/eslint-tier3-custom-plugins.md @@ -0,0 +1,113 @@ +# ESLint Tier 3: Custom Plugins + +[← Back to ESLint Rules Discovery](eslint-rules-discovery.md) + +## Tier 3: Custom Plugins (LAST RESORT) + +Only create a custom ESLint plugin when: +- ❌ No built-in rule exists (checked Tier 1) +- ❌ No configurable rule can express the pattern (checked Tier 2 — see [eslint-tier2-configurable.md](eslint-tier2-configurable.md)) +- ✅ The pattern requires checking multiple AST nodes with complex logic +- ✅ The pattern is domain-specific to your codebase + +**Examples that justify custom plugins:** +- "Flag LWC components with @api properties but no input validation in connectedCallback" +- "Detect imperative Apex calls without error handling (import + .then() without .catch())" +- "Enforce naming conventions on specific decorator patterns" + +**See:** [eslint-custom-plugins.md](eslint-custom-plugins.md) for the plugin creation guide. + +--- + +## Tier-by-Tier Worked Examples + +Tier 2 worked examples (banning globals, restricting syntax, restricting properties) live in [eslint-tier2-configurable.md](eslint-tier2-configurable.md). The examples below cover Tier 1 (built-in rules and the LWC plugin) so the discovery → configure → validate flow is documented end-to-end. + +### Example 1: "Ban console.log" (Tier 1 — built-in) + +**User Request:** "Create a rule to ban console.log in LWC" + +**Discovery:** +```bash +$ sf code-analyzer rules --rule-selector eslint | grep -i console +eslint:no-console disallow the use of console +``` + +**Result:** Built-in rule exists. ✅ + +**Configuration:** +```javascript +// eslint.config.js +module.exports = [ + { + files: ["**/lwc/**/*.js"], + rules: { + "no-console": "error" + } + } +]; +``` + +**Validation:** +```bash +sf code-analyzer rules --rule-selector eslint:no-console +# Should show: eslint:no-console | error | ... +``` + +**Test:** +```bash +sf code-analyzer run --rule-selector eslint:no-console --target lwc/ +``` + +--- + +### Example 2: "Ban innerHTML" (Tier 1 — LWC plugin) + +**User Request:** "Ban innerHTML in LWC for XSS prevention" + +**Discovery:** +```bash +$ sf code-analyzer rules --rule-selector eslint | grep -i inner +@lwc/lwc/no-inner-html disallow use of innerHTML +``` + +**Result:** LWC plugin rule exists. ✅ + +**Configuration:** +```javascript +// eslint.config.js +module.exports = [ + { + files: ["**/lwc/**/*.js"], + rules: { + "@lwc/lwc/no-inner-html": "error" + } + } +]; +``` + +**Validation:** +```bash +sf code-analyzer rules --rule-selector eslint:@lwc/lwc/no-inner-html +``` + +--- + +### Example 3: "Enforce === over ==" (Tier 1 — built-in) + +**User Request:** "Enforce strict equality checks" + +**Discovery:** +```bash +$ sf code-analyzer rules --rule-selector eslint | grep -i equal +eslint:eqeqeq require the use of === and !== +``` + +**Result:** Built-in rule `eqeqeq` exists. ✅ + +**Configuration:** +```javascript +rules: { + "eqeqeq": ["error", "always"] +} +``` diff --git a/skills/dx-code-analyzer-custom-rule-create/references/metadata-xml-rules.md b/skills/dx-code-analyzer-custom-rule-create/references/metadata-xml-rules.md new file mode 100644 index 0000000..eccf40c --- /dev/null +++ b/skills/dx-code-analyzer-custom-rule-create/references/metadata-xml-rules.md @@ -0,0 +1,285 @@ +# Metadata XML Rules (PMD with language="xml") + +Write PMD XPath rules that target Salesforce metadata XML files (`.field-meta.xml`, `.permissionset-meta.xml`, `.profile-meta.xml`, `.flow-meta.xml`, etc.) to enforce org governance. + +## How It Works + +PMD's `language="xml"` mode parses any XML file into a DOM-like AST. You write XPath expressions against element/attribute names in the XML structure — same mechanism as Apex rules, but the AST is the XML DOM. + +## Critical: PMD 7 XML XPath Rules + +### Rule 1: Namespace — use `local-name()` + +Salesforce metadata files declare a namespace: +```xml + + + My_Field__c + ... + +``` + +This namespace **breaks standard XPath** because `//fullName` won't match — PMD sees it as `//metadata:fullName` internally. + +**Solution:** Use `local-name()` to match element names regardless of namespace: + +```xpath +//*[local-name()='CustomField']/*[local-name()='fullName'] +``` + +**This applies to ALL Salesforce metadata XPath rules.** Never use bare element names without `local-name()`. + +### Rule 2: Text content — use `@Text` (NOT `text()`) + +⚠️ **PMD 7's XML language does NOT support `text()` for value comparison.** The `text()` function returns 0 matches regardless of content. This is a PMD 7 change from PMD 6. + +**Use the `@Text` attribute instead:** + +```xpath +// ❌ WRONG — does not work in PMD 7: +//*[local-name()='name'][text()='ModifyAllData'] + +// ✅ CORRECT — use @Text attribute: +//*[@Text='ModifyAllData'] +``` + +### Rule 3: Navigation — text nodes require `../..` + +In PMD 7 XML, `@Text` matches TEXT NODES (child nodes of elements), not the elements themselves. To navigate from a matched text node to its parent element hierarchy: + +```xpath +// @Text='ModifyAllData' matches the TEXT NODE inside ModifyAllData +// To get the element: go up one level +//*[@Text='ModifyAllData']/.. + +// To get the parent: go up two levels +//*[@Text='ModifyAllData']/../.. + +// To check a sibling's text value from the parent: +//*[@Text='ModifyAllData']/../..[.//*[@Text='true']] +``` + +### Complete Pattern for Checking Parent + Sibling Text Values + +The canonical PMD 7 pattern for "find element X that contains child with text A AND child with text B": + +```xpath +//*[@Text='TargetValue']/../.. + [local-name()='ParentElement'] + [.//*[@Text='ConditionValue']] +``` + +Example — find `userPermissions` where name=ModifyAllData AND enabled=true: +```xpath +//*[@Text='ModifyAllData']/../.. + [local-name()='userPermissions'] + [.//*[@Text='true']] +``` + +## AST Dump for Metadata Files + +Dump the AST to see the exact XML structure: +```bash +sf code-analyzer ast-dump --file force-app/main/default/objects/Account/fields/MyField__c.field-meta.xml --language xml +``` + +The output shows the DOM tree — element nodes, text nodes, and attributes. Use this to confirm element names before writing XPath. + +## Ruleset XML Template for Metadata Rules + +```xml + + + + Custom rules for Salesforce metadata governance + + + + What this rule enforces + 3 + + + + + + + + +``` + +## Configuration + +```yaml +# code-analyzer.yml +engines: + pmd: + custom_rulesets: + - "custom-rules/metadata-governance.xml" + file_extensions: + xml: [".xml"] +``` + +⚠️ **IMPORTANT:** PMD's XML language only scans `.xml` files by default. You MUST add `file_extensions: { xml: [".xml"] }` under `engines.pmd` in your config. This single entry covers ALL Salesforce metadata compound extensions (`.permissionset-meta.xml`, `.field-meta.xml`, `.flow-meta.xml`, etc.) because the system reads the final `.xml` portion of the filename. + +⚠️ **Do NOT add compound extensions** like `.permissionset-meta.xml` to the list — Code Analyzer's validator only accepts single-segment extensions matching `/^[.][a-zA-Z0-9]+$/` and will reject them with a config error. + +## Common Metadata File Structures + +### Custom Field (`.field-meta.xml`) +```xml + + Revenue__c + + Currency + false + Annual revenue + +``` + +### Permission Set (`.permissionset-meta.xml`) +```xml + + + + true + ModifyAllData + + + true + Account.Revenue__c + true + + +``` + +### Profile (`.profile-meta.xml`) +```xml + + + true + ViewSetup + + + true + Account.SSN__c + + +``` + +### Flow (`.flow-meta.xml`) +```xml + + 58.0 + + AutoLaunchedFlow + Active + + CanvasMode + AUTO_LAYOUT_CANVAS + + + Create_Account + Account + + + Loop_Records + Get_Records + + +``` + +## XPath Patterns for Metadata (PMD 7) + +All patterns below use `@Text` for text content matching (NOT `text()` which is broken in PMD 7). + +### Require description on Custom Fields + +**Missing description entirely:** +```xpath +//*[local-name()='CustomField'][not(*[local-name()='description'])] +``` + +**Missing OR empty description (catches `` too):** +```xpath +//*[local-name()='CustomField'][not(*[local-name()='description']) or *[local-name()='description'][not(.//*[@Text])]] +``` +Note: `.//*[@Text]` checks for any descendant text node with content. Do NOT put `[not(@Text)]` directly on the `` element — `@Text` only exists on child text nodes, not on elements themselves. Putting it on the element always evaluates to true (false positive). + +### Flag ModifyAllData / ViewAllData in Permission Sets +```xpath +//*[@Text='ModifyAllData' or @Text='ViewAllData']/../.. + [local-name()='userPermissions'] + [.//*[@Text='true']] +``` + +### Enforce minimum API version (60.0+) +```xpath +//*[local-name()='apiVersion']/*[number(@Text) < 60] +``` +Note: `/*` navigates to the **child text node** where `@Text` lives. Do NOT put `@Text` directly on the element — it won't match because `@Text` only exists on text nodes (children), not on elements themselves. + +### Flag field permissions in Profiles (should be in Perm Sets) +```xpath +//*[local-name()='Profile']/*[local-name()='fieldPermissions'] +``` +(No text matching needed — structural only.) + +### Flag dangerous permissions (ModifyAllData only, enabled) +```xpath +//*[@Text='ModifyAllData']/../.. + [local-name()='userPermissions'] + [.//*[@Text='true']] +``` + +### Flag DML (recordCreates/recordUpdates/recordDeletes) inside Flow loops +```xpath +//*[local-name()='loops'][ + following-sibling::*[local-name()='recordCreates' or local-name()='recordUpdates' or local-name()='recordDeletes'] +] +``` +(No text matching needed — structural only.) + +## Targeting Specific File Types + +PMD only scans `.xml` files by default. You must add `.xml` to the config. This single extension covers ALL Salesforce compound metadata extensions (`.permissionset-meta.xml`, `.field-meta.xml`, etc.) because `path.extname()` returns `.xml` from these filenames. + +```yaml +# code-analyzer.yml +engines: + pmd: + file_extensions: + xml: [".xml"] +``` + +⚠️ Do NOT add compound extensions — the validator rejects anything that doesn't match `/^[.][a-zA-Z0-9]+$/`. + +Additionally, scope your XPath to the correct root element to avoid false positives across file types: + +```xpath +//*[@Text='ModifyAllData']/../..[local-name()='userPermissions'] + [ancestor::*[local-name()='PermissionSet']] +``` + +This ensures the rule only fires on permission set files, not on profile or other metadata files. + +## Gotchas + +| Issue | Resolution | +|-------|------------| +| XPath matches nothing | Three causes: (1) Forgot `local-name()`. (2) Used `text()` — broken in PMD 7, use `@Text`. (3) File extension not in `file_extensions.xml` config. | +| Rule fires on wrong metadata type | Add root element scope: `[ancestor::*[local-name()='PermissionSet']]` | +| Can't match text content | **Use `@Text` attribute** (NOT `text()`). Example: `//*[@Text='ModifyAllData']` | +| `@Text` matches but parent navigation fails | `@Text` matches TEXT NODES. Go up with `../..`: text→element→parent. | +| Number comparison fails | `@Text` lives on the **child text node**, NOT on the element. Use: `//*[local-name()='element']/*[number(@Text) < N]` — the `/*` navigates to the text node. Do NOT put `[@Text]` predicate directly on the element (returns 0 matches). | +| Too many violations on large orgs | Scope with `file_extensions` in PMD config or add `ignores.files` patterns | +| Rule doesn't fire on metadata files | Add `file_extensions: { xml: [".xml"] }` under `engines.pmd` — this covers all compound extensions. Do NOT add `.permissionset-meta.xml` etc. (validator rejects them). | +| ast-dump fails with "XmlEncoding" error | Known issue. Read the raw XML file — the file content IS the DOM structure | +| ast-dump shows different structure | XML AST is the literal DOM — what you see in the file is what you get | diff --git a/skills/dx-code-analyzer-custom-rule-create/references/regex-rule-schema.md b/skills/dx-code-analyzer-custom-rule-create/references/regex-rule-schema.md new file mode 100644 index 0000000..15cf5b5 --- /dev/null +++ b/skills/dx-code-analyzer-custom-rule-create/references/regex-rule-schema.md @@ -0,0 +1,174 @@ +# Regex Rule Schema Reference + +Custom regex rules are defined inline in `code-analyzer.yml` under `engines.regex.custom_rules`. + +## Complete Schema + +```yaml +engines: + regex: + custom_rules: + RuleName: # Must match: /^[A-Za-z@][A-Za-z_0-9@\-/]*$/ + regex: "/pattern/flags" # REQUIRED — JavaScript regex literal format + description: "What this checks" # REQUIRED — Purpose of the rule + violation_message: "Fix this" # Optional — Message shown on violation + severity: 3 # Optional — 1=Critical, 2=High, 3=Moderate, 4=Low, 5=Info + tags: # Optional — Default: ['Recommended', 'Custom'] + - "Custom" + - "Security" + file_extensions: # Optional — Default: all text files + - ".cls" + - ".trigger" + regex_ignore: "/exception/i" # Optional — Matches excluded from violations +``` + +## Field Details + +### `regex` (Required) + +JavaScript regex literal format: `/pattern/flags` + +| Format | Example | Meaning | +|--------|---------|---------| +| `/pattern/g` | `/TODO/g` | Case-sensitive, global (find all matches) — **minimum required** | +| `/pattern/gi` | `/todo/gi` | Global + case-insensitive | +| `/pattern/gm` | `/^import/gm` | Global + multiline (^ matches each line start) | + +**Common pitfalls:** +- **The `/g` (global) flag is REQUIRED.** Code Analyzer rejects regex patterns without at least one flag after the closing `/`. Always include `/g` (or `/gi`, `/gm`, etc.). +- Backslashes must be escaped: `\d` → write as `/\\d/` +- Dots match any character: use `/\\.cls/` not `/.cls/` +- Regex is tested against file content line by line + +**Named capture groups** narrow the violation highlight to a specific portion of the match: +```yaml +# Without named group — entire match is highlighted: +regex: "/System\\.debug\\([^)]*\\)/g" +# Highlights: "System.debug('hello')" (entire expression) + +# With named group — only captured portion highlighted: +regex: "/(?System\\.debug)\\([^)]*\\)/g" +# Highlights: "System.debug" (just the method name) +``` + +Use `(?...)` when the regex needs surrounding context to match correctly, but the violation should point to a narrower location. Common patterns: + +```yaml +# Highlight just the hardcoded ID, not the surrounding quotes +regex: "/['\"](?[0-9a-zA-Z]{15,18})['\"]/g" + +# Highlight the annotation, not the whole line +regex: "/(?@SuppressWarnings\\([^)]*\\))/g" + +# Highlight the method call within a larger expression +regex: "/(?Database\\.query)\\(/g" +``` + +### `description` (Required) + +Brief explanation of what the rule enforces. Used in rule listings and generated messages. + +### `violation_message` (Optional) + +Message shown when a violation is found. If omitted, auto-generated as: +> "A match of the regular expression `` was found for rule '': " + +Good violation messages tell the user HOW to fix, not just WHAT's wrong: +- ❌ "Hardcoded ID found" +- ✅ "Replace hardcoded Salesforce ID with a Custom Label or Custom Metadata reference" + +### `severity` (Optional, default: 3) + +| Value | Name | Meaning | +|-------|------|---------| +| 1 | Critical | Security vulnerability, must fix before deploy | +| 2 | High | Significant quality issue, should fix | +| 3 | Moderate | Recommended improvement | +| 4 | Low | Minor style issue | +| 5 | Info | Informational, no action required | + +Also accepts string names: `"Critical"`, `"High"`, `"Moderate"`, `"Low"`, `"Info"` + +### `tags` (Optional, default: ['Recommended', 'Custom']) + +Array of tag strings. Used for `--rule-selector` filtering. Common tags: +- `Custom` — auto-applied to user-created rules +- `Security` — security-related rules +- `BestPractices` — coding standards +- `Performance` — performance anti-patterns +- `CodeStyle` — formatting/naming rules + +### `file_extensions` (Optional, default: all text files) + +Array of file extensions to scan. Each must start with `.` and match: `/^([.][a-zA-Z0-9-_]+)+$/` + +Common values for Salesforce: +- `.cls` — Apex classes +- `.trigger` — Apex triggers +- `.js` — JavaScript/LWC +- `.html` — LWC HTML templates +- `.xml` — Metadata files +- `.page` — Visualforce pages +- `.component` — Visualforce components + +### `regex_ignore` (Optional) + +A second regex pattern — matches that ALSO match this pattern are excluded. Useful for reducing false positives. + +⚠️ **`regex_ignore` operates per-LINE, not per-FILE.** It only skips a match if the same line also matches the ignore pattern. It does NOT exclude entire files or classes. For example, adding `/@isTest/` only suppresses violations on lines that literally contain `@isTest` — a hardcoded ID on line 50 of a test class still flags because line 50 doesn't contain `@isTest`. + +To exclude entire files, use `ignores.files` in `code-analyzer.yml`: +```yaml +ignores: + files: + - "**/*Test.cls" + - "**/test/**" +``` + +```yaml +# Flag System.debug everywhere EXCEPT in lines with "// OK" comment +regex: "/System\\.debug/g" +regex_ignore: "/\\/\\/ OK/" +``` + +## Validation Rules + +| Rule | Error if violated | +|------|-------------------| +| Name matches `/^[A-Za-z@][A-Za-z_0-9@\-/]*$/` | "Invalid rule name" | +| Regex starts and ends with `/` | "Invalid regex format" | +| File extensions start with `.` | "Invalid file extension" | +| Severity is 1-5 or valid name | "Invalid severity" | +| No duplicate rule names | "Rule already exists" | + +## Multiple Rules Example + +```yaml +engines: + regex: + custom_rules: + NoHardcodedIds: + regex: "/['\"][0-9a-zA-Z]{15,18}['\"]/g" + description: "Detects hardcoded Salesforce record IDs" + violation_message: "Use Custom Labels or Custom Metadata instead of hardcoded IDs" + severity: 2 + tags: ["Custom", "Security"] + file_extensions: [".cls", ".trigger"] + + NoTodos: + regex: "/TODO|FIXME|HACK/gi" + description: "Flags TODO/FIXME/HACK comments that should be resolved" + violation_message: "Resolve or remove this TODO/FIXME/HACK before merging" + severity: 4 + tags: ["Custom", "BestPractices"] + + NoSuppressWarnings: + regex: "/(?@SuppressWarnings\\([^)]*\\))/g" + description: "Bans @SuppressWarnings annotations — violations must be fixed, not suppressed" + violation_message: "Remove @SuppressWarnings and fix the underlying violation instead" + severity: 2 + tags: ["Recommended", "Custom", "BestPractices"] + file_extensions: [".cls", ".trigger"] +``` + +⚠️ **Note on test-class exclusion:** The `regex_ignore` field is per-LINE only. Adding `regex_ignore: "/@isTest/i"` does NOT exclude entire test classes — it only skips lines that literally contain `@isTest`. For SOQL rules that should skip test classes, use PMD/XPath instead (which can structurally check `[not(ancestor::UserClass[ModifierNode[@Test = true()]])]`). See the "Excluding Test Classes" section in SKILL.md. diff --git a/skills/dx-code-analyzer-custom-rule-create/references/troubleshooting.md b/skills/dx-code-analyzer-custom-rule-create/references/troubleshooting.md new file mode 100644 index 0000000..40d99e0 --- /dev/null +++ b/skills/dx-code-analyzer-custom-rule-create/references/troubleshooting.md @@ -0,0 +1,141 @@ +# Troubleshooting Custom Rules + +## Regex Rule Issues + +| Problem | Cause | Fix | +|---------|-------|-----| +| "Invalid rule name" | Name starts with number or contains spaces | Use PascalCase: `NoHardcodedIds`, `BanTodoComments` | +| "Invalid regex" | Missing `/` delimiters | Must be `/pattern/flags` format | +| YAML parse error when adding regex | Quotes/backslashes in regex conflict with YAML syntax | **Use `create-regex-rule.js` script** — do not manually write regex into YAML. The script handles serialization correctly. | +| Rule not listed | YAML indentation wrong | Must be under `engines.regex.custom_rules` with correct nesting | +| No violations found | Regex doesn't match content | Test regex at regex101.com with your file content | +| Matches in wrong files | `file_extensions` not set | Add `file_extensions: [".cls"]` to limit scope | +| Matches in comments | Regex is text-based | Consider using PMD/XPath instead for code-aware matching | +| Too many matches | Regex too broad | Tighten pattern first (e.g., require leading `0` for IDs), then add `regex_ignore` for remaining edge cases | +| `regex_ignore` doesn't exclude test classes | `regex_ignore` is per-LINE, not per-FILE | It only skips lines where the ignore pattern matches. To exclude entire test files, use `ignores.files: ["**/*Test.cls"]` in `code-analyzer.yml` | +| Hardcoded ID regex flags normal words | Pattern `{15,18}` matches 16/17 char words | Salesforce IDs are exactly 15 OR 18 chars, start with `0`. Use: `0[a-zA-Z0-9]{14}(?:[a-zA-Z0-9]{3})?` | +| Special chars not working | YAML escaping issue | Use `create-regex-rule.js` script to avoid escaping issues. If manual: double-escape (`\\d` → `\d`) | + +### Common Regex Escaping in YAML + +| Character | In YAML | In regex | +|-----------|---------|----------| +| Backslash | `\\\\` | `\\` | +| Dot | `\\.` | `.` as literal | +| Quote | `\\"` or use single quotes | `"` | +| Newline | `\\n` | Line break | + +## PMD/XPath Rule Issues + +| Problem | Cause | Fix | +|---------|-------|-----| +| "Could not locate Java" | Java not installed or not on PATH | Install Java 11+ or set `engines.pmd.java_command` in config | +| "Language not supported" | Invalid language identifier | Use: apex, visualforce, html, xml, javascript | +| "File not found" for ruleset | Wrong path in `custom_rulesets` | Path is relative to `code-analyzer.yml` location | +| Rule not listed after creation | XML format wrong | Verify against template in `assets/pmd-ruleset-template.xml` | +| XPath returns 0 matches | Node name or attribute wrong | Re-run `sf code-analyzer ast-dump` and compare | +| XPath too broad | Missing attribute filter | Add `[@attribute='value']` conditions | +| XPath `@WithSharing='false'` returns 0 matches | PMD 7 boolean attributes are always present, not string-typed | Use XPath boolean function: `@WithSharing = false()`. NOT string `'false'`, NOT `not(@WithSharing)`. Same for `@Abstract`, `@Final`, etc. | +| Loop rule flags `for (x : [SELECT...])` | Used `//ForEachStatement//SoqlExpression` (matches iterable) | Scope to body: `//ForEachStatement/BlockStatement//SoqlExpression` | +| PMD parse error on sample code | Invalid Apex syntax in sample | Ensure sample compiles (doesn't need to deploy) | + +### Debugging XPath Step by Step + +1. **Dump the AST:** `sf code-analyzer ast-dump --file sample.cls` +2. **Find the target node:** Search the XML for the pattern (e.g., grep for "System.debug") +3. **Note the exact node name:** e.g., `MethodCallExpression` (not `MethodCall`) +4. **Note the exact attribute:** e.g., `FullMethodName='System.debug'` (not `Name` or `Image`) +5. **Check ancestry:** What's the parent chain? Does your XPath's `ancestor::` match? +6. **Simplify first:** Start with `//MethodCallExpression` → verify it matches → then add filters + +### Verifying Rule Registration + +```bash +# Check if rule appears in the rule list +sf code-analyzer rules --rule-selector regex:MyRuleName +sf code-analyzer rules --rule-selector pmd:MyRuleName + +# Check all custom rules +sf code-analyzer rules --rule-selector Custom +``` + +### Verifying Rule Execution + +```bash +# Test against a file that SHOULD violate +sf code-analyzer run --rule-selector regex:MyRuleName --target ./sample-violation.cls + +# Test against a file that should NOT violate (expect 0 results) +sf code-analyzer run --rule-selector regex:MyRuleName --target ./clean-file.cls +``` + +## Metadata XML Rule Issues + +| Problem | Cause | Fix | +|---------|-------|-----| +| XPath matches nothing on metadata | Missing `local-name()` | MUST use `*[local-name()='element']` — namespace blocks bare names | +| XPath matches nothing (text values) | Used `text()` — broken in PMD 7 | Use `@Text` attribute: `//*[@Text='value']` | +| Rule fires on wrong file type | XPath not scoped to root element | Add root check or `ancestor::*[local-name()='PermissionSet']` | +| `text()` comparison always returns 0 | PMD 7 XML language change | Replace ALL `text()='value'` with `@Text='value'`. This is a PMD 7 breaking change. | +| `@Text` matches but can't navigate up | `@Text` matches TEXT NODES, not elements | Navigate up: `//*[@Text='value']/..` (element), `/../..` (grandparent) | +| `[@Text]` predicate on element returns 0 matches | `@Text` lives on CHILD text nodes, not on elements | Use `/*[@Text...]` to navigate to child text node. E.g., `//*[local-name()='apiVersion']/*[number(@Text) < 60]` — NOT `//*[local-name()='apiVersion'][number(@Text) < 60]` | +| Child predicate `[*[local-name()='x'][@Text='y']]` returns 0 | PMD 7 doesn't support `@Text` in child predicates | Use bottom-up navigation: start from `@Text` match, go up with `../..` | +| Number comparison doesn't work | Need to use `@Text` not `text()` | `number(substring-before(@Text, '.'))` | +| Rule doesn't fire on metadata files | PMD only scans `.xml` extension by default | Add `file_extensions: { xml: [".xml"] }` under `engines.pmd`. This covers all compound extensions (`.permissionset-meta.xml`, etc.). Do NOT add compound extensions — the validator rejects them. | +| "Too many violations" on metadata | Rule too broad, scanning all XML | Scope to specific file types via root element check | +| ast-dump for XML fails with "XmlEncoding" error | Known bug in some versions | Read the raw XML file — the file content IS the DOM structure | +| ast-dump for XML shows flat DOM | Expected — XML AST is the literal DOM tree | Elements, text nodes, and attributes — not like Apex AST | + +### Debugging Metadata XML XPath (PMD 7) + +1. **Dump the AST** (or read raw file if ast-dump fails): `sf code-analyzer ast-dump --file MyField.field-meta.xml --language xml` +2. **Check namespace:** If file has `xmlns="..."`, ALL XPath must use `local-name()` +3. **Use `@Text` for text values:** Never use `text()` — it does not work in PMD 7 XML +4. **Start from the text node, navigate up:** `//*[@Text='TargetValue']/../..` to reach parent elements +5. **Test with `//*` first:** Confirm PMD is scanning the file at all (should return many violations) +6. **Test `@Text` matching:** `//*[@Text='YourValue']` — confirm the text node is found +7. **Add navigation one step at a time:** `../..`, then `[local-name()='parent']`, then sibling checks +8. **Check file extensions:** Ensure `file_extensions: { xml: [".xml"] }` is in your `engines.pmd` config. Just `.xml` is enough — it covers all Salesforce compound extensions. + +## ESLint Rule Issues + +| Problem | Cause | Fix | +|---------|-------|-----| +| Custom rules not appearing | Plugin missing `meta.docs.description` + `meta.docs.url` | Ensure plugin exports rule metadata | +| "Cannot find module" for plugin | Plugin not installed | Run `npm install --save-dev eslint-plugin-` | +| Base rules conflict with custom | Both Code Analyzer base + custom config active | Set `disable__base_config: true` to suppress built-in rules | +| ESLint config not found | Wrong path or auto-discover off | Set `eslint_config_file` explicitly or enable `auto_discover_eslint_config` | +| Deprecated rules excluded | ESLint engine filters deprecated rules | Use replacement rule name (check ESLint docs) | +| `eslint_config_file` path in `code-analyzer.yml` fails at startup | Code Analyzer validates the path at load time | **Create `eslint.config.js` BEFORE adding it to `code-analyzer.yml`.** File must exist first. | +| Core rule (e.g., `no-restricted-globals`) not appearing in `sf code-analyzer rules` | Rule not enabled in `eslint.config.js` | Core ESLint rules only appear after you enable them in config AND Code Analyzer loads it. Always validate after configuration. | +| Created same rule in both Regex AND PMD — double-flagging | Rule defined in two engines | A rule should live in ONE engine only. Use PMD when structural exclusions (e.g., test classes) are needed; Regex for simple text patterns with no exclusions. | + +## PMD Apex Rule Issues + +> XPath authoring gotchas (loop-type coverage, PMD 7 boolean attributes like `@WithSharing`, etc.) live in the SKILL.md **Gotchas** table — that is the single source. This section covers PMD issues outside XPath authoring. + +| Problem | Cause | Fix | +|---------|-------|-----| +| Rule works but wrong severity shows | Severity set in wrong place | Regex: edit `severity` under `engines.regex.custom_rules.` in `code-analyzer.yml`. PMD: set `` in the ruleset XML. | +| Too many false positives | Pattern too broad | Regex: tighten pattern, then add `regex_ignore` for edge cases. PMD: tighten XPath with `[not(...)]` predicates. | +| Want to customize threshold on a built-in rule | Can't modify built-in ruleset files | Use `` with nested `` override — see `references/advanced-pmd-patterns.md`. | + +## Config File Issues + +| Problem | Cause | Fix | +|---------|-------|-----| +| Config not picked up | File named wrong or in wrong directory | Must be `code-analyzer.yml` or `code-analyzer.yaml` at project root | +| YAML parse error | Invalid YAML syntax | Check indentation (2 spaces), colon spacing, quote balance | +| "engines.regex.custom_rules" not recognized | Wrong nesting level | Ensure correct hierarchy: `engines` → `regex` → `custom_rules` → `RuleName` | +| Multiple config files conflict | Both `.yml` and `.yaml` exist | Delete one — `.yaml` takes precedence over `.yml` | +| Custom ruleset path not found | Path not relative to config_root | `custom_rulesets` paths resolve relative to `code-analyzer.yml` directory | +| Rule severity override not working | Wrong config location | Use `rules.pmd.RuleName.severity` in config, not inside ruleset XML | +| Can't disable a specific rule | Wrong field name | Use `rules.pmd.RuleName.disabled: true` in `code-analyzer.yml` | + +## Exclusion Issues + +| Problem | Cause | Fix | +|---------|-------|-----| +| Rule still fires on excluded files | Glob pattern wrong | Use `**` for recursive: `"**/fflib_*"` not `"fflib_*"` | +| PMD exclude-pattern ignored | Pattern syntax differs from glob | PMD uses Java regex in ``, not glob | +| Want to exclude one rule from a category | Missing `` in ref | Use `` | diff --git a/skills/dx-code-analyzer-custom-rule-create/references/xpath-patterns-governor-limits.md b/skills/dx-code-analyzer-custom-rule-create/references/xpath-patterns-governor-limits.md new file mode 100644 index 0000000..57250df --- /dev/null +++ b/skills/dx-code-analyzer-custom-rule-create/references/xpath-patterns-governor-limits.md @@ -0,0 +1,83 @@ +# XPath Patterns for Governor Limits + +[← Back to XPath Patterns Index](xpath-patterns.md) + +## Governor Limit Patterns + +### SOQL inside loops + +⚠️ **Must scope to `/BlockStatement//`** — ForEachStatement has the iterable SOQL as a direct child alongside BlockStatement. Without this, `for (Contact c : [SELECT...])` is a false positive. + +**ForEachStatement child structure (from ast-dump):** +```text +ForEachStatement +├── VariableDeclarationStatements ← loop variable declaration +├── VariableExpression ← loop variable reference +├── BlockStatement ← LOOP BODY — only match here +└── SoqlExpression (or VariableExpression) ← ITERABLE — do NOT flag +``` + +```xpath +//ForEachStatement/BlockStatement//SoqlExpression +| +//ForLoopStatement/BlockStatement//SoqlExpression +| +//WhileLoopStatement/BlockStatement//SoqlExpression +``` + +**Catches:** `for (Id accId : ids) { Account acc = [SELECT ...]; }` (SOQL in body) +**Does NOT catch (correct):** `for (Contact c : [SELECT Id FROM Contact]) { ... }` (SOQL as iterable) + +### DML inside loops + +Same principle — scope to BlockStatement: + +```xpath +//ForEachStatement/BlockStatement//DmlInsertStatement +| +//ForEachStatement/BlockStatement//DmlUpdateStatement +| +//ForEachStatement/BlockStatement//DmlDeleteStatement +| +//ForEachStatement/BlockStatement//DmlUpsertStatement +| +//ForLoopStatement/BlockStatement//DmlInsertStatement +| +//ForLoopStatement/BlockStatement//DmlUpdateStatement +| +//ForLoopStatement/BlockStatement//DmlDeleteStatement +| +//ForLoopStatement/BlockStatement//DmlUpsertStatement +| +//WhileLoopStatement/BlockStatement//DmlInsertStatement +| +//WhileLoopStatement/BlockStatement//DmlUpdateStatement +| +//WhileLoopStatement/BlockStatement//DmlDeleteStatement +| +//WhileLoopStatement/BlockStatement//DmlUpsertStatement +``` + +**Shorter variant** (ForEach only, most common): +```xpath +//ForEachStatement/BlockStatement//DmlInsertStatement +| //ForEachStatement/BlockStatement//DmlUpdateStatement +| //ForEachStatement/BlockStatement//DmlDeleteStatement +| //ForEachStatement/BlockStatement//DmlUpsertStatement +``` + +### Database methods inside loops + +```xpath +//ForEachStatement/BlockStatement//MethodCallExpression[@FullMethodName='Database.query'] +| +//ForEachStatement/BlockStatement//MethodCallExpression[@FullMethodName='Database.insert'] +| +//ForEachStatement/BlockStatement//MethodCallExpression[@FullMethodName='Database.update'] +| +//ForEachStatement/BlockStatement//MethodCallExpression[@FullMethodName='Database.delete'] +| +//ForLoopStatement/BlockStatement//MethodCallExpression[@FullMethodName='Database.query'] +| +//WhileLoopStatement/BlockStatement//MethodCallExpression[@FullMethodName='Database.query'] +``` diff --git a/skills/dx-code-analyzer-custom-rule-create/references/xpath-patterns-method-calls.md b/skills/dx-code-analyzer-custom-rule-create/references/xpath-patterns-method-calls.md new file mode 100644 index 0000000..bb984ff --- /dev/null +++ b/skills/dx-code-analyzer-custom-rule-create/references/xpath-patterns-method-calls.md @@ -0,0 +1,108 @@ +# XPath Patterns for Method Calls and Annotations + +[← Back to XPath Patterns Index](xpath-patterns.md) + +## Method Call Patterns + +### Ban System.debug (non-test classes) + +```xpath +//MethodCallExpression[@FullMethodName='System.debug'] + [not(ancestor::UserClass[ModifierNode[@Test = true()]])] +``` + +**AST evidence:** `MethodCallExpression` has `@FullMethodName='System.debug'`. Test classes have `ModifierNode[@Test = true()]`. + +**Catches:** `System.debug('anything');` in production classes +**Does NOT catch (correct):** Same call inside `@IsTest` classes + +### Ban Database.query (dynamic SOQL — SOQL injection risk) + +```xpath +//MethodCallExpression[@FullMethodName='Database.query'] +``` + +**AST evidence:** `MethodCallExpression[@FullMethodName='Database.query']` (line 118 of verified AST) + +To exclude test classes: +```xpath +//MethodCallExpression[@FullMethodName='Database.query'] + [not(ancestor::UserClass[ModifierNode[@Test = true()]])] +``` + +### Ban Test.isRunningTest() in production code + +```xpath +//MethodCallExpression[@FullMethodName='Test.isRunningTest'] + [not(ancestor::UserClass[ModifierNode[@Test = true()]])] +``` + +### Ban specific method calls (generic pattern) + +```xpath +//MethodCallExpression[@FullMethodName='ClassName.methodName'] +``` + +Common examples: +- `@FullMethodName='System.debug'` +- `@FullMethodName='Database.query'` +- `@FullMethodName='Test.isRunningTest'` +- `@FullMethodName='UserInfo.getUserId'` +- `@FullMethodName='Limits.getQueries'` + +--- + +## Annotation Patterns + +### @AuraEnabled without cacheable=true + +```xpath +//Method/ModifierNode/Annotation[@Name='AuraEnabled' + and not(AnnotationParameter[@Name='cacheable' and @Value='true'])] +``` + +**AST evidence:** `Annotation[@Name='AuraEnabled']` with child `AnnotationParameter[@Name='cacheable'][@Value='true']` + +**Catches:** `@AuraEnabled public static ...` (no cacheable) +**Does NOT catch (correct):** `@AuraEnabled(cacheable=true) public static ...` + +### @future methods (recommend Queueable instead) + +```xpath +//Method/ModifierNode/Annotation[@Name='Future'] +``` + +**AST evidence:** Annotation `@Name='Future'` (note: PMD normalizes `@future` → `Future` in the Name attribute, but `@RawName='future'`) + +### @SuppressWarnings usage (audit/ban) + +```xpath +//Annotation[@Name='SuppressWarnings'] +``` + +**AST evidence:** `Annotation[@Name='SuppressWarnings']` with `AnnotationParameter[@Name='value'][@Value='PMD.RuleName']` + +To flag specific suppressions: +```xpath +//Annotation[@Name='SuppressWarnings']/AnnotationParameter[contains(@Value, 'ApexCRUDViolation')] +``` + +### @IsTest without testFor parameter + +From GitHub issue #2008: +```xpath +//UserClass/ModifierNode/Annotation[ + @Name='IsTest' + and not(AnnotationParameter[@Name='testFor']) +] +``` + +### @IsTest class without System.runAs + +```xpath +//UserClass[ModifierNode[@Test = true()]] + /Method[ModifierNode[@Test = true()]] + /BlockStatement[not(.//RunAsBlockStatement)] +``` + +**AST evidence:** `RunAsBlockStatement` is the node for `System.runAs(...) { }` blocks. diff --git a/skills/dx-code-analyzer-custom-rule-create/references/xpath-patterns-security.md b/skills/dx-code-analyzer-custom-rule-create/references/xpath-patterns-security.md new file mode 100644 index 0000000..efffa3a --- /dev/null +++ b/skills/dx-code-analyzer-custom-rule-create/references/xpath-patterns-security.md @@ -0,0 +1,45 @@ +# XPath Patterns for Security + +[← Back to XPath Patterns Index](xpath-patterns.md) + +## Security Patterns + +### Class without sharing declaration + +```xpath +//UserClass[ + @Nested = false() + and ModifierNode[@WithSharing = false() and @WithoutSharing = false() and @InheritedSharing = false()] +] +``` + +**AST evidence:** `ModifierNode` has `@WithSharing`, `@WithoutSharing`, `@InheritedSharing` — all `false()` means no sharing keyword declared (implicit without sharing). + +Added `@Nested = false()` to exclude inner classes (which inherit from parent). + +> ⚠️ **PMD 7 boolean attributes:** `@WithSharing`, `@WithoutSharing`, `@InheritedSharing`, and `@Nested` are boolean-typed in PMD 7 — string comparison (`='false'`) errors with "Cannot compare xs:boolean to xs:string". Always use `= false()` / `= true()`. See [xpath-patterns.md](xpath-patterns.md) for the full list. + +### SOQL without WITH USER_MODE or SECURITY_ENFORCED + +```xpath +//SoqlExpression[ + not(contains(@CanonicalQuery, 'WITH USER_MODE')) + and not(contains(@CanonicalQuery, 'WITH SECURITY_ENFORCED')) +] +[not(ancestor::UserClass[ModifierNode[@Test = true()]])] +``` + +**AST evidence:** `SoqlExpression[@CanonicalQuery]` contains the full normalized query text. + +### Hardcoded Salesforce IDs (structural — string literals 15-18 chars) + +```xpath +//LiteralExpression[ + @LiteralType='STRING' + and string-length(@Image) >= 15 + and string-length(@Image) <= 18 +] +[not(ancestor::UserClass[ModifierNode[@Test = true()]])] +``` + +**Note:** `matches()` function may not be available in all PMD XPath versions. The string-length check catches most cases. For precision, use Regex engine instead. diff --git a/skills/dx-code-analyzer-custom-rule-create/references/xpath-patterns-structure.md b/skills/dx-code-analyzer-custom-rule-create/references/xpath-patterns-structure.md new file mode 100644 index 0000000..f6b07b1 --- /dev/null +++ b/skills/dx-code-analyzer-custom-rule-create/references/xpath-patterns-structure.md @@ -0,0 +1,127 @@ +# XPath Patterns for Code Structure, Tests, and Naming + +[← Back to XPath Patterns Index](xpath-patterns.md) + +## Code Structure Patterns + +### DML without try-catch + +```xpath +//DmlInsertStatement[not(ancestor::TryCatchFinallyBlockStatement)] +| +//DmlUpdateStatement[not(ancestor::TryCatchFinallyBlockStatement)] +| +//DmlDeleteStatement[not(ancestor::TryCatchFinallyBlockStatement)] +| +//DmlUpsertStatement[not(ancestor::TryCatchFinallyBlockStatement)] +``` + +**AST evidence:** DML nodes sit inside `TryCatchFinallyBlockStatement` when wrapped in try-catch. + +To exclude test classes: +```xpath +//DmlInsertStatement[ + not(ancestor::TryCatchFinallyBlockStatement) + and not(ancestor::UserClass[ModifierNode[@Test = true()]]) +] +``` + +### Empty catch blocks (swallowed exceptions) + +```xpath +//CatchBlockStatement[BlockStatement[not(*)]] +``` + +**AST evidence:** `CatchBlockStatement` contains a `BlockStatement`. Empty body = `BlockStatement` with no children. + +### Nested if statements (max depth 3) + +```xpath +//IfBlockStatement[ + ancestor::IfBlockStatement[ + ancestor::IfBlockStatement[ + ancestor::IfBlockStatement + ] + ] +] +``` + +### Methods with too many parameters + +```xpath +//Method[@Arity >= 5 and @Constructor = false()] +``` + +**AST evidence:** `Method[@Arity]` gives parameter count directly. + +### Logic in trigger (should delegate to handler) + +For trigger files (UserTrigger node): +```xpath +//UserTrigger/BlockStatement//DmlInsertStatement +| +//UserTrigger/BlockStatement//DmlUpdateStatement +| +//UserTrigger/BlockStatement//SoqlExpression +| +//UserTrigger/BlockStatement//MethodCallExpression[@FullMethodName='Database.query'] +``` + +--- + +## Test Quality Patterns + +### Test method without assertions + +```xpath +//Method[ModifierNode[@Test = true()]] + /BlockStatement[ + not(.//MethodCallExpression[ + contains(@FullMethodName, 'System.assert') + or contains(@FullMethodName, 'Assert.') + ]) + ] +``` + +**Catches:** Test methods with no `System.assert*` or `Assert.*` calls anywhere in their body. + +### Test method without System.runAs + +```xpath +//Method[ModifierNode[@Test = true()]] + /BlockStatement[not(.//RunAsBlockStatement)] +``` + +**AST evidence:** `System.runAs(user) { ... }` becomes `RunAsBlockStatement` in the AST. + +### Test using seeAllData=true + +```xpath +//Annotation[@Name='IsTest']/AnnotationParameter[@Name='seeAllData' and @Value='true'] +``` + +--- + +## Naming Convention Patterns + +### Class name doesn't match file (non-test) + +This is better enforced by the built-in rule, but the XPath pattern for a specific convention: +```xpath +//UserClass[@Nested = false() and not(starts-with(@Image, 'Test')) and not(ends-with(@Image, 'Test'))] +``` + +### Method naming (non-standard) + +```xpath +//Method[ + @Constructor = false() + and not(ModifierNode[@Test = true()]) + and not(starts-with(@Image, 'get')) + and not(starts-with(@Image, 'set')) + and not(starts-with(@Image, 'is')) + and matches(@Image, '^[A-Z]') +] +``` + +**Catches:** Methods starting with uppercase in non-test code (Apex convention is camelCase). diff --git a/skills/dx-code-analyzer-custom-rule-create/references/xpath-patterns.md b/skills/dx-code-analyzer-custom-rule-create/references/xpath-patterns.md new file mode 100644 index 0000000..53bf3cf --- /dev/null +++ b/skills/dx-code-analyzer-custom-rule-create/references/xpath-patterns.md @@ -0,0 +1,131 @@ +# XPath Patterns for Apex Custom Rules + +Pre-validated PMD XPath patterns for Salesforce Apex code. Every pattern below has been verified against actual `sf code-analyzer ast-dump` output. Use these directly — but still run `ast-dump` on YOUR code to confirm node names haven't changed in newer PMD versions. + +## XPath Syntax Quick Reference + +| Syntax | Meaning | +|--------|---------| +| `//Node` | Find Node anywhere in tree | +| `//Node[@attr='value']` | Node with specific string attribute value | +| `//Node[@attr = true()]` | Node with boolean attribute = true (**PMD 7 requirement**) | +| `//Parent//Child` | Child anywhere inside Parent (any descendant) | +| `//Parent/Child` | **Direct child only** — use this to avoid false positives | +| `//Node[not(...)]` | Node where condition is NOT true | +| `//Node[ancestor::Other]` | Node that has Other as an ancestor | +| `//Node[.//Other]` | Node that contains Other somewhere inside | +| `@Image` | The name/text of the node | +| `@FullMethodName` | Full qualified method name (e.g., 'System.debug') | +| `@LiteralType` | Type of literal: STRING, INTEGER, BOOLEAN, TRUE, FALSE, NULL | + +### PMD 7 Boolean Attributes — MUST use `true()` / `false()` + +In PMD 7, many node attributes are **boolean typed**, not strings — even though `ast-dump` renders them as `Nested='false'` or `Test='true'` in the XML output. String attributes (`@FullMethodName`, `@Name`, `@Image`, `@LiteralType`, etc.) are safe to copy from the AST dump as-is. But **attributes whose values are `true` or `false` in the dump are boolean-typed** — comparing them with string literals (`@Nested='false'`) causes the error: "Cannot compare xs:boolean to xs:string". + +Known boolean attributes by node: + +| Node | Boolean Attributes | +|------|--------------------| +| `ModifierNode` | `@Test`, `@Public`, `@Private`, `@Protected`, `@Static`, `@Abstract`, `@Final`, `@Global`, `@WithSharing`, `@WithoutSharing`, `@InheritedSharing` | +| `UserClass` | `@Nested` | +| `Method` | `@Constructor` | + +| WRONG | CORRECT | +|-------|---------| +| `UserClass[@Nested='false']` | `UserClass[@Nested = false()]` | +| `UserClass[@Nested='true']` | `UserClass[@Nested = true()]` | +| `ModifierNode[@Test='true']` | `ModifierNode[@Test = true()]` | +| `ModifierNode[@Static='false']` | `ModifierNode[@Static = false()]` | +| `Method[@Constructor = false()]` | `Method[@Constructor = false()]` | + +`true()` and `false()` are XPath boolean functions. If you see an attribute that looks boolean in the AST dump, assume it is typed as boolean and use `true()`/`false()`. + +--- + +### `/` vs `//` — The Most Common Source of False Positives + +| XPath | Meaning | Risk | +|-------|---------|------| +| `//ForEachStatement//SoqlExpression` | SOQL anywhere inside ForEachStatement | ❌ Matches iterable position too | +| `//ForEachStatement/BlockStatement//SoqlExpression` | SOQL inside loop **body** only | ✅ Correct | + +**Rule of thumb:** When matching inside a structural node (loop, if, try), always scope to `/BlockStatement//` to target the body, not sibling children like iterables or conditions. + +--- + +## Pattern Categories (By Topic) + +For detailed patterns in each category, see the dedicated files below: + +| Category | File | Contents | +|----------|------|----------| +| Governor Limits | [xpath-patterns-governor-limits.md](xpath-patterns-governor-limits.md) | SOQL/DML in loops, Database methods in loops | +| Method Calls & Annotations | [xpath-patterns-method-calls.md](xpath-patterns-method-calls.md) | Ban specific methods, @AuraEnabled, @future, @IsTest, @SuppressWarnings patterns | +| Security | [xpath-patterns-security.md](xpath-patterns-security.md) | Sharing declarations, SOQL security modes, hardcoded IDs | +| Code Structure, Tests & Naming | [xpath-patterns-structure.md](xpath-patterns-structure.md) | DML error handling, empty catches, test assertions, naming conventions | + +--- + +## Key Apex AST Node Names + +| Apex Construct | AST Node | Key Attributes | +|---|---|---| +| Class | `UserClass` | `Image`, `SuperClassName`, `InterfaceNames`, `Nested` | +| Interface | `UserInterface` | `Image`, `SuperInterfaceName` | +| Method | `Method` | `Image`, `Arity`, `ReturnType`, `Constructor` | +| Trigger | `UserTrigger` | `Image`, `TargetName` | +| SOQL query | `SoqlExpression` | `Query`, `CanonicalQuery` | +| DML insert | `DmlInsertStatement` | | +| DML update | `DmlUpdateStatement` | | +| DML delete | `DmlDeleteStatement` | | +| DML upsert | `DmlUpsertStatement` | | +| Method call | `MethodCallExpression` | `FullMethodName`, `MethodName`, `InputParametersSize` | +| For-each loop | `ForEachStatement` | children: VariableDeclarationStatements, VariableExpression, **BlockStatement** (body), iterable | +| For loop | `ForLoopStatement` | children: init, condition, update, **BlockStatement** (body) | +| While loop | `WhileLoopStatement` | children: condition, **BlockStatement** (body) | +| If/else | `IfElseBlockStatement` > `IfBlockStatement` | `ElseStatement` | +| Try-catch | `TryCatchFinallyBlockStatement` | | +| Catch block | `CatchBlockStatement` | `ExceptionType`, `VariableName` | +| RunAs | `RunAsBlockStatement` | | +| String literal | `LiteralExpression` | `@LiteralType='STRING'`, `@Image` (value without quotes) | +| Integer literal | `LiteralExpression` | `@LiteralType='INTEGER'`, `@Image` | +| Boolean true | `LiteralExpression` | `@LiteralType='TRUE'` | +| Boolean false | `LiteralExpression` | `@LiteralType='FALSE'` | +| Null | `LiteralExpression` | `@LiteralType='NULL'` | +| Variable | `VariableExpression` | `@Image` (name) | +| Assignment | `AssignmentExpression` | `@Op` (=, +=, etc.) | +| Binary expression | `BinaryExpression` | `@Op` (+, -, *, /) | +| Boolean expression | `BooleanExpression` | `@Op` (>, <, ==, !=, >=, <=) | +| New object | `NewKeyValueObjectExpression` | `@Type` | +| New object (no-arg) | `NewObjectExpression` | `@Type` | +| Return | `ReturnStatement` | | +| Annotation | `Annotation` | `@Name` (IsTest, AuraEnabled, Future, SuppressWarnings) | +| Annotation param | `AnnotationParameter` | `@Name`, `@Value` | +| Modifier | `ModifierNode` | `Public`, `Private`, `Static`, `Test`, `WithSharing`, `Global`, etc. | +| Parameter | `Parameter` | `@Image` (name), `@Type` | +| New list | `NewListInitExpression` | | +| New map | `NewMapInitExpression` | | + +--- + +## XPath Best Practices + +1. **ALWAYS run `ast-dump` first** — never guess node names, even for patterns listed here +2. **Use `/BlockStatement//` for loop/if body** — avoids matching iterables, conditions, etc. +3. **Use `@FullMethodName`** for method calls — not `@Image` or `@MethodName` alone +4. **Exclude test classes** with `[not(ancestor::UserClass[ModifierNode[@Test = true()]])]` +5. **Test with BOTH positive AND negative cases** — ensure no false positives +6. **Prefer `//Node` over absolute paths** — code structure varies +7. **Use `ancestor::` / `not(ancestor::)`** for structural exclusions (try-catch, test class) +8. **Keep XPath simple** — complex expressions are fragile and hard to maintain + +## Common False Positive Traps + +| Pattern | Trap | Fix | +|---------|------|-----| +| SOQL in loop | `//ForEachStatement//SoqlExpression` matches iterable | Use `/BlockStatement//` | +| DML in loop | Same as above | Use `/BlockStatement//` | +| Ban method in all code | Flags test code too | Add `[not(ancestor::UserClass[ModifierNode[@Test = true()]])]` | +| Empty block detection | Matches intentional empty constructors | Add `[@Constructor = false()]` or exclude specific patterns | +| No sharing declaration | Flags inner classes (which inherit) | Add `[@Nested = false()]` | +| String literal length check | Matches test data strings | Exclude test classes | diff --git a/skills/dx-code-analyzer-custom-rule-create/scripts/create-pmd-rule.js b/skills/dx-code-analyzer-custom-rule-create/scripts/create-pmd-rule.js new file mode 100644 index 0000000..8194878 --- /dev/null +++ b/skills/dx-code-analyzer-custom-rule-create/scripts/create-pmd-rule.js @@ -0,0 +1,209 @@ +#!/usr/bin/env node +// Creates a PMD XPath custom rule (XML ruleset file + config reference) +// Usage: node create-pmd-rule.js --name --xpath --message [options] + +const fs = require("fs"); +const path = require("path"); + +function printUsage() { + console.error(`Usage: node create-pmd-rule.js --name --xpath --message [options] + +Required: + --name Rule name (PascalCase, no spaces) + --xpath XPath expression to match violations + --message Violation message shown to users + +Optional: + --description Detailed rule description (default: same as message) + --language PMD language (default: apex) + --priority <1-5> PMD priority (default: 3) + --example Example violating code snippet + --config-file Path to code-analyzer.yml (default: ./code-analyzer.yml) + --ruleset-dir Directory for ruleset XML (default: ./custom-rules) + +Examples: + node create-pmd-rule.js --name NoSystemDebug --xpath "//MethodCallExpression[@FullMethodName='System.debug']" --message "System.debug not allowed" --priority 3 + node create-pmd-rule.js --name SoqlInLoop --xpath "//ForEachStatement//SoqlExpression" --message "SOQL inside loop" --priority 2`); + process.exit(1); +} + +// Parse arguments +const args = process.argv.slice(2); +if (args.length < 1 || args[0] === "--help" || args[0] === "-h") { + printUsage(); +} + +const options = { + name: null, + xpath: null, + message: null, + description: null, + language: "apex", + priority: 3, + example: null, + configFile: "./code-analyzer.yml", + rulesetDir: "./custom-rules", +}; + +for (let i = 0; i < args.length; i++) { + switch (args[i]) { + case "--name": options.name = args[++i]; break; + case "--xpath": options.xpath = args[++i]; break; + case "--message": options.message = args[++i]; break; + case "--description": options.description = args[++i]; break; + case "--language": options.language = args[++i]; break; + case "--priority": options.priority = parseInt(args[++i], 10); break; + case "--example": options.example = args[++i]; break; + case "--config-file": options.configFile = args[++i]; break; + case "--ruleset-dir": options.rulesetDir = args[++i]; break; + default: + console.error(`Unknown option: ${args[i]}`); + printUsage(); + } +} + +// Validate required fields +if (!options.name) { console.error("Error: --name is required"); process.exit(1); } +if (!options.xpath) { console.error("Error: --xpath is required"); process.exit(1); } +if (!options.message) { console.error("Error: --message is required"); process.exit(1); } + +// Validate rule name +const RULE_NAME_PATTERN = /^[A-Za-z@][A-Za-z_0-9@\-/]*$/; +if (!RULE_NAME_PATTERN.test(options.name)) { + console.error(`Error: Invalid rule name "${options.name}". Must match: ${RULE_NAME_PATTERN}`); + process.exit(1); +} + +// Validate priority +if (options.priority < 1 || options.priority > 5) { + console.error("Error: Priority must be 1-5"); + process.exit(1); +} + +// Validate language +const VALID_LANGUAGES = ["apex", "visualforce", "html", "xml", "javascript"]; +if (!VALID_LANGUAGES.includes(options.language.toLowerCase())) { + console.error(`Error: Invalid language "${options.language}". Supported: ${VALID_LANGUAGES.join(", ")}`); + process.exit(1); +} + +// Set defaults +if (!options.description) { + options.description = options.message; +} + +// Generate the PMD ruleset XML +function buildRulesetXml() { + const escXpath = options.xpath.replace(/&/g, "&").replace(//g, ">"); + const escMessage = options.message.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); + const escDescription = options.description.replace(/&/g, "&").replace(//g, ">"); + + let xml = ` + + + Custom rules for ${options.name} + + + + ${escDescription} + ${options.priority} + + + + + + `; + + if (options.example) { + xml += ` + + + + `; + } + + xml += ` + + +`; + + return xml; +} + +// Create ruleset directory if needed +const rulesetDir = path.resolve(options.rulesetDir); +if (!fs.existsSync(rulesetDir)) { + fs.mkdirSync(rulesetDir, { recursive: true }); +} + +// Write ruleset XML +const rulesetFileName = `${options.name}-pmd-ruleset.xml`; +const rulesetPath = path.join(rulesetDir, rulesetFileName); + +if (fs.existsSync(rulesetPath)) { + console.error(`Error: Ruleset file already exists: ${rulesetPath}`); + process.exit(1); +} + +const rulesetXml = buildRulesetXml(); +fs.writeFileSync(rulesetPath, rulesetXml, "utf8"); + +// Update code-analyzer.yml to reference the ruleset +const configPath = path.resolve(options.configFile); +const rulesetRelativePath = path.relative(path.dirname(configPath), rulesetPath); +let configContent = ""; + +if (fs.existsSync(configPath)) { + configContent = fs.readFileSync(configPath, "utf8"); +} + +// Check if this ruleset path is already referenced in config (deduplication) +const alreadyReferenced = configContent.includes(rulesetRelativePath); + +if (!configContent) { + // Create new config + configContent = `engines:\n pmd:\n custom_rulesets:\n - "${rulesetRelativePath}"\n`; +} else if (alreadyReferenced) { + // Ruleset path already in config — skip adding duplicate entry + // (This happens when the XML was deleted and recreated during iteration) +} else if (configContent.includes("custom_rulesets:") && configContent.includes("pmd:")) { + // Add to existing custom_rulesets + const insertPoint = configContent.indexOf("custom_rulesets:"); + const afterLine = configContent.indexOf("\n", insertPoint) + 1; + configContent = configContent.slice(0, afterLine) + ` - "${rulesetRelativePath}"\n` + configContent.slice(afterLine); +} else if (configContent.includes("pmd:")) { + // Add custom_rulesets under pmd + const insertPoint = configContent.indexOf("pmd:"); + const afterLine = configContent.indexOf("\n", insertPoint) + 1; + configContent = configContent.slice(0, afterLine) + ` custom_rulesets:\n - "${rulesetRelativePath}"\n` + configContent.slice(afterLine); +} else if (configContent.includes("engines:")) { + // Add pmd section under engines + const insertPoint = configContent.indexOf("engines:"); + const afterLine = configContent.indexOf("\n", insertPoint) + 1; + configContent = configContent.slice(0, afterLine) + ` pmd:\n custom_rulesets:\n - "${rulesetRelativePath}"\n` + configContent.slice(afterLine); +} else { + // Append engines section + configContent += `\nengines:\n pmd:\n custom_rulesets:\n - "${rulesetRelativePath}"\n`; +} + +fs.writeFileSync(configPath, configContent, "utf8"); + +console.log(JSON.stringify({ + status: "success", + ruleName: options.name, + engine: "pmd", + language: options.language, + rulesetFile: rulesetPath, + configFile: configPath, + message: `Rule "${options.name}" created. Validate with: sf code-analyzer rules --rule-selector pmd:${options.name}` +})); diff --git a/skills/dx-code-analyzer-custom-rule-create/scripts/create-regex-rule.js b/skills/dx-code-analyzer-custom-rule-create/scripts/create-regex-rule.js new file mode 100644 index 0000000..1c4804e --- /dev/null +++ b/skills/dx-code-analyzer-custom-rule-create/scripts/create-regex-rule.js @@ -0,0 +1,220 @@ +#!/usr/bin/env node +// Creates a regex custom rule in code-analyzer.yml +// Usage: node create-regex-rule.js --name --regex --description [options] + +const fs = require("fs"); +const path = require("path"); + +function printUsage() { + console.error(`Usage: node create-regex-rule.js --name --regex --description [options] + +Required: + --name Rule name (PascalCase, no spaces) + --regex Regex in /pattern/flags format + --description What the rule checks + +Optional: + --violation-message Message shown on violation + --severity <1-5> Severity level (default: 3) + --tags Comma-separated tags (default: Recommended,Custom) + --file-extensions Comma-separated extensions (e.g., .cls,.trigger) + --regex-ignore Negative pattern to exclude matches + --config-file Path to code-analyzer.yml (default: ./code-analyzer.yml) + +Examples: + node create-regex-rule.js --name NoHardcodedIds --regex "/[0-9a-zA-Z]{18}/g" --description "Detects hardcoded IDs" --severity 2 --file-extensions ".cls,.trigger" + node create-regex-rule.js --name NoTodos --regex "/TODO|FIXME/gi" --description "Flags TODO comments" --severity 4`); + process.exit(1); +} + +// Parse arguments +const args = process.argv.slice(2); +if (args.length < 1 || args[0] === "--help" || args[0] === "-h") { + printUsage(); +} + +const options = { + name: null, + regex: null, + description: null, + violationMessage: null, + severity: 3, + tags: ["Recommended", "Custom"], + fileExtensions: null, + regexIgnore: null, + configFile: "./code-analyzer.yml", +}; + +for (let i = 0; i < args.length; i++) { + switch (args[i]) { + case "--name": options.name = args[++i]; break; + case "--regex": options.regex = args[++i]; break; + case "--description": options.description = args[++i]; break; + case "--violation-message": options.violationMessage = args[++i]; break; + case "--severity": options.severity = parseInt(args[++i], 10); break; + case "--tags": options.tags = args[++i].split(",").map(t => t.trim()); break; + case "--file-extensions": options.fileExtensions = args[++i].split(",").map(e => e.trim()); break; + case "--regex-ignore": options.regexIgnore = args[++i]; break; + case "--config-file": options.configFile = args[++i]; break; + default: + console.error(`Unknown option: ${args[i]}`); + printUsage(); + } +} + +// Validate required fields +if (!options.name) { console.error("Error: --name is required"); process.exit(1); } +if (!options.regex) { console.error("Error: --regex is required"); process.exit(1); } +if (!options.description) { console.error("Error: --description is required"); process.exit(1); } + +// Validate rule name +const RULE_NAME_PATTERN = /^[A-Za-z@][A-Za-z_0-9@\-/]*$/; +if (!RULE_NAME_PATTERN.test(options.name)) { + console.error(`Error: Invalid rule name "${options.name}". Must match: ${RULE_NAME_PATTERN}`); + process.exit(1); +} + +// Validate regex format — must be /pattern/flags with no surrounding whitespace +// and the flags portion must contain only valid JavaScript regex flag characters. +options.regex = options.regex.trim(); +if (!options.regex.startsWith("/") || options.regex.lastIndexOf("/") <= 0) { + console.error(`Error: Regex must be in /pattern/flags format. Got: "${options.regex}"`); + process.exit(1); +} +const lastSlash = options.regex.lastIndexOf("/"); +const flags = options.regex.slice(lastSlash + 1); +if (!flags) { + console.error(`Error: Regex must include flags after the closing /. Use /pattern/g at minimum. Got: "${options.regex}"`); + process.exit(1); +} +// Strict flags validation — only valid JS regex flag chars allowed, no spaces, no junk. +if (!/^[gimsuy]+$/.test(flags)) { + console.error(`Error: Invalid regex flags "${flags}". Allowed: g, i, m, s, u, y (no spaces, no other characters). Got: "${options.regex}"`); + process.exit(1); +} +// Code Analyzer regex rules require the global flag. +if (!flags.includes("g")) { + console.error(`Error: Regex must include the global flag 'g'. Got flags: "${flags}"`); + process.exit(1); +} + +// Validate severity +if (options.severity < 1 || options.severity > 5) { + console.error("Error: Severity must be 1-5"); + process.exit(1); +} + +// Validate file extensions +if (options.fileExtensions) { + for (const ext of options.fileExtensions) { + if (!ext.startsWith(".")) { + console.error(`Error: File extension must start with dot: "${ext}"`); + process.exit(1); + } + } +} + +// Safely quote a string for YAML output. +// CRITICAL: regex patterns contain backslashes (`\.`, `\d`, `\\`, etc.). +// Double-quoted YAML treats `\` as an escape introducer — `\.` is an unknown +// escape and YAML rejects the file with "unknown escape sequence". Single-quoted +// YAML treats backslash as literal, which is exactly what regex needs. +// Strategy: +// - If value contains a backslash → ALWAYS use single quotes (escape ' as '') +// - Else if value has no quotes → double quotes (simplest, most readable) +// - Else → single quotes (escape ' as '') +function yamlQuote(value) { + const hasBackslash = value.includes("\\"); + const hasSingle = value.includes("'"); + const hasDouble = value.includes('"'); + + if (!hasBackslash && !hasSingle && !hasDouble) { + return `"${value}"`; + } + + // Single-quoted YAML: only ' needs escaping (as ''). Backslashes pass through. + const escaped = value.replace(/'/g, "''"); + return `'${escaped}'`; +} + +// Build the rule YAML block +function buildRuleYaml() { + const indent = " "; + const lines = []; + lines.push(` ${options.name}:`); + lines.push(`${indent} regex: ${yamlQuote(options.regex)}`); + lines.push(`${indent} description: ${yamlQuote(options.description)}`); + + if (options.violationMessage) { + lines.push(`${indent} violation_message: ${yamlQuote(options.violationMessage)}`); + } + + lines.push(`${indent} severity: ${options.severity}`); + + if (options.tags && options.tags.length > 0) { + lines.push(`${indent} tags:`); + options.tags.forEach(tag => lines.push(`${indent} - "${tag}"`)); + } + + if (options.fileExtensions && options.fileExtensions.length > 0) { + lines.push(`${indent} file_extensions:`); + options.fileExtensions.forEach(ext => lines.push(`${indent} - "${ext}"`)); + } + + if (options.regexIgnore) { + lines.push(`${indent} regex_ignore: ${yamlQuote(options.regexIgnore)}`); + } + + return lines.join("\n"); +} + +// Read or create config file +const configPath = path.resolve(options.configFile); +let configContent = ""; + +if (fs.existsSync(configPath)) { + configContent = fs.readFileSync(configPath, "utf8"); + + // Check if rule already exists + if (configContent.includes(`${options.name}:`)) { + console.error(`Error: Rule "${options.name}" already exists in ${configPath}`); + process.exit(1); + } +} + +const ruleYaml = buildRuleYaml(); + +// Upsert into config +if (!configContent) { + // Create new file + configContent = `engines:\n regex:\n custom_rules:\n${ruleYaml}\n`; +} else if (configContent.includes("custom_rules:") && configContent.includes("regex:")) { + // Add to existing custom_rules section + const insertPoint = configContent.indexOf("custom_rules:"); + const afterCustomRules = configContent.indexOf("\n", insertPoint) + 1; + configContent = configContent.slice(0, afterCustomRules) + ruleYaml + "\n" + configContent.slice(afterCustomRules); +} else if (configContent.includes("regex:")) { + // Add custom_rules section under regex + const insertPoint = configContent.indexOf("regex:"); + const afterRegex = configContent.indexOf("\n", insertPoint) + 1; + configContent = configContent.slice(0, afterRegex) + " custom_rules:\n" + ruleYaml + "\n" + configContent.slice(afterRegex); +} else if (configContent.includes("engines:")) { + // Add regex section under engines + const insertPoint = configContent.indexOf("engines:"); + const afterEngines = configContent.indexOf("\n", insertPoint) + 1; + configContent = configContent.slice(0, afterEngines) + " regex:\n custom_rules:\n" + ruleYaml + "\n" + configContent.slice(afterEngines); +} else { + // Append engines section + configContent += "\nengines:\n regex:\n custom_rules:\n" + ruleYaml + "\n"; +} + +// Write config +fs.writeFileSync(configPath, configContent, "utf8"); + +console.log(JSON.stringify({ + status: "success", + ruleName: options.name, + engine: "regex", + configFile: configPath, + message: `Rule "${options.name}" created. Validate with: sf code-analyzer rules --rule-selector regex:${options.name}` +})); diff --git a/skills/dx-code-analyzer-run/SKILL.md b/skills/dx-code-analyzer-run/SKILL.md index 40e6dc7..99664ce 100644 --- a/skills/dx-code-analyzer-run/SKILL.md +++ b/skills/dx-code-analyzer-run/SKILL.md @@ -1,19 +1,27 @@ --- name: dx-code-analyzer-run -description: "Run Salesforce Code Analyzer to scan code for security, performance, best practice, and code style violations. Supports all engines (PMD, ESLint, CPD, RetireJS, Flow, SFGE, ApexGuru), targets (files, folders, git diff), categories, and severities. Also handles post-scan exploration: filtering results by engine/severity/category/file, and explaining what specific rules mean. TRIGGER when: user says 'scan my code', 'check for security issues', 'run PMD/ESLint', 'find duplicates', 'analyze Flows', 'check vulnerable libraries', 'AppExchange review', 'lint my LWC', 'static analysis', 'code quality', 'show only security violations', 'what is this rule', 'explain ApexCRUDViolation', 'filter results', or mentions engines/file types (.cls, .trigger, .js, .flow-meta.xml). Use this skill for scanning, exploring results, understanding rules, and listing available rules. DO NOT TRIGGER when: user wants to fix code without scanning, or asks ONLY about installation/configuration." -allowed-tools: Read, Bash(sf code-analyzer), Bash(node), Bash(git diff), Bash(date), Write, Edit +description: "Run Salesforce Code Analyzer to scan code for security, performance, best practice, and code style violations. Supports all engines (PMD, ESLint, CPD, RetireJS, Flow, SFGE, ApexGuru), targets (files, folders, git diff), categories, and severities. Also handles post-scan exploration: filtering results by engine/severity/category/file, and explaining what rules mean. TRIGGER when: user says 'scan my code', 'check security issues', 'run PMD/ESLint', 'find duplicates', 'analyze Flows', 'check vulnerable libraries', 'AppExchange review', 'lint my LWC', 'static analysis', 'code quality', 'show security violations', 'what is this rule', 'explain ApexCRUDViolation', 'filter results', or mentions engines/file types (.cls, .trigger, .js, .flow-meta.xml). Use this skill for scanning, exploring results, and listing rules. DO NOT TRIGGER when: user asks only about installation/configuration (use dx-code-analyzer-configure), or wants to create a custom rule (use dx-code-analyzer-custom-rule-create)." metadata: version: "1.0" - argument-hint: "[target-path] [--engine pmd|eslint|cpd|retire-js|regex|flow|sfge|apexguru] [--category Security|Performance|BestPractices|...] [--severity 1-5] [--diff]" + relatedSkills: + - "dx-code-analyzer-configure" + - "dx-code-analyzer-custom-rule-create" + cliTools: + - tool: ["sf"] + semver: ">=2.0.0" + - tool: ["node"] + semver: ">=18.0.0" + - tool: ["git"] + semver: ">=2.0.0" --- # Running Code Analyzer Skill -## ⚠️ CRITICAL: Mandatory Script Usage +## CRITICAL: Mandatory Script Usage Every interaction with Code Analyzer results MUST go through the bundled scripts in `/scripts/`. No exceptions. -### ❌ WRONG — never do this: +### WRONG — never do this: ```bash # WRONG: inline Python to parse results @@ -31,7 +39,7 @@ Read tool → code-analyzer-results-*.json Also forbidden: `run_code_analyzer` and any `mcp__*` tool — Bash only. -### ✅ RIGHT — always do this: +### RIGHT — always do this: ```bash # Summarize scan results @@ -67,11 +75,13 @@ Any aggregation, filter, or rank question ("which file has the most violations?" ## Overview +> **Ecosystem:** This skill is part of a 3-skill Code Analyzer suite — `dx-code-analyzer-run` (scans & results) · `dx-code-analyzer-configure` (setup, config, CI/CD) · `dx-code-analyzer-custom-rule-create` (custom rule authoring). + This skill translates natural-language requests ("scan for security issues", "check my changes") into the correct `sf code-analyzer run` command, executes scans across any combination of engines/targets/severities, and presents actionable results. When engine-provided fixes are available, it discovers them, asks for user confirmation, applies them safely, and offers verification. Use it for static analysis, security reviews, AppExchange certification, code-quality checks, and finding duplicates/vulnerabilities in Salesforce projects. **In scope:** running scans, parsing/filtering/ranking results, applying engine auto-fixes, diff-based scans, all output formats (JSON/HTML/SARIF/CSV/XML), describing/listing rules, scan-failure troubleshooting. -**Out of scope:** installing/configuring `sf` or the plugin (→ `dx-code-analyzer-configure`), writing custom rules/engines, AI-generated fixes beyond engine-provided ones, deep refactoring, CI/CD setup (→ `dx-code-analyzer-configure`). +**Out of scope:** installing/configuring `sf` or the plugin (→ `dx-code-analyzer-configure`), writing custom rules/engines (→ `dx-code-analyzer-custom-rule-create`), AI-generated fixes beyond engine-provided ones, deep refactoring, CI/CD setup (→ `dx-code-analyzer-configure`). **Allowed tools:** Bash (`sf code-analyzer`, `node`, `git diff`, `date`), Read, Write, Edit. **Forbidden:** any MCP tool, Agent tool, web tools, other skills, Python, `jq`, inline scripts/heredocs. This skill owns the complete scan-fix-verify-query-explain workflow end-to-end. @@ -203,7 +213,7 @@ Use the **Bash tool only** — never the `run_code_analyzer` MCP tool. 1. Generate the timestamp via Bash: `date +%Y%m%d-%H%M%S` → e.g. `20260512-143022`. 2. Tell the user: - ``` + ```text Starting scan... Results: ./code-analyzer-results-20260512-143022.json Log: ./code-analyzer-results-20260512-143022.log @@ -237,7 +247,7 @@ node "/scripts/parse-results.js" "./code-analyzer-results-TIMESTAMP.j ### Presentation template -``` +```text ## Scan Complete **Found X violations** across Y files. @@ -296,7 +306,7 @@ node "/scripts/discover-fixes.js" "./code-analyzer-results-TIMESTAMP. ### 6.3 Present + ASK (then STOP) -``` +```text ### Engine-Provided Fixes Available **X of Y violations** have auto-fixes provided by the analysis engine: @@ -327,7 +337,7 @@ node "/scripts/summarize-fixes.js" "./code-analyzer-results-TIMESTAMP Then present: -``` +```text ### Engine-Provided Fixes Applied Successfully ✓ **Applied X auto-fixes across Y files.** @@ -439,6 +449,29 @@ node "/scripts/list-rules.js" "" [options] Filters: `--engine`, `--severity`, `--top` (default 100), `--count-only`. The script pre-validates selector tokens (catches typos like `secruity`) before calling the CLI. Presentation: `/references/post-scan-workflows.md`. +--- +## Cross-Skill Integration + +This skill is part of a 3-skill Code Analyzer ecosystem. Hand off cleanly rather than attempting work that belongs to another skill. + +### When THIS skill delegates to `dx-code-analyzer-configure`: + +- Pre-flight check fails (CLI missing, plugin not installed, engine prereqs broken) → stop, delegate, return here after fix +- User asks to set up CI/CD, edit `code-analyzer.yml`, change severities, or disable engines → delegate entirely + +### When THIS skill delegates to `dx-code-analyzer-custom-rule-create`: + +- User asks to create a new rule, write XPath, write a regex rule, or enforce a pattern not covered by built-in rules → delegate entirely. Do NOT attempt to create rules here. + +### When other skills hand off HERE: + +- `dx-code-analyzer-configure` completes setup → proceed with scan (Step 1–5) +- `dx-code-analyzer-custom-rule-create` finishes creating a rule → proceed with scan targeting the new rule (e.g., `--rule-selector pmd:`) to verify it works + +### Ownership boundary + +This skill owns the complete **scan → explore → fix** workflow end-to-end. It does NOT own installation, config file management, or rule authoring. + --- ## Constraints & Gotchas diff --git a/skills/dx-code-analyzer-run/examples/command-variations.md b/skills/dx-code-analyzer-run/examples/command-variations.md index 846fcac..348f3af 100644 --- a/skills/dx-code-analyzer-run/examples/command-variations.md +++ b/skills/dx-code-analyzer-run/examples/command-variations.md @@ -297,7 +297,7 @@ sf code-analyzer run \ ## Anti-Patterns (DO NOT USE) -### ❌ Using `--format` flag +### Using `--format` flag ```bash # WRONG - v3 syntax, does not exist in v4+ sf code-analyzer run --format json @@ -306,7 +306,7 @@ sf code-analyzer run --format json --- -### ❌ Using `$TIMESTAMP` variable in command +### Using `$TIMESTAMP` variable in command ```bash # WRONG - variable substitution fails in permission prompts sf code-analyzer run --output-file "./results-${TIMESTAMP}.json" @@ -315,7 +315,7 @@ sf code-analyzer run --output-file "./results-${TIMESTAMP}.json" --- -### ❌ Running in background for long scans +### Running in background for long scans ```bash # WRONG - loses output stream sf code-analyzer run --rule-selector sfge & @@ -324,7 +324,7 @@ sf code-analyzer run --rule-selector sfge & --- -### ❌ Partial rule names +### Partial rule names ```bash # WRONG - returns 0 results sf code-analyzer run --rule-selector "no-hardcoded-values" diff --git a/skills/dx-org-permission-set-assign/SKILL.md b/skills/dx-org-permission-set-assign/SKILL.md index ca80cdd..1648039 100644 --- a/skills/dx-org-permission-set-assign/SKILL.md +++ b/skills/dx-org-permission-set-assign/SKILL.md @@ -12,7 +12,7 @@ Assigns one or more permission sets to org users using `sf org assign permset`. --- -## ⚠️ Tool Restrictions +## Tool Restrictions **Use ONLY the Bash tool** to execute `sf org assign permset`. Do NOT use MCP tools like `assign_permission_set` — ignore them completely. diff --git a/skills/experience-lwc-generate/references/async-notification-patterns.md b/skills/experience-lwc-generate/references/async-notification-patterns.md index 5109a01..f9ef312 100644 --- a/skills/experience-lwc-generate/references/async-notification-patterns.md +++ b/skills/experience-lwc-generate/references/async-notification-patterns.md @@ -617,7 +617,7 @@ connectedCallback() { ## Best Practices -### DO ✅ +### DO | Practice | Reason | |----------|--------| @@ -627,7 +627,7 @@ connectedCallback() { | Use `refreshApex` after events | Keeps wire data in sync | | Set reasonable replay ID | `-1` for real-time, `-2` for recovery | -### DON'T ❌ +### DON'T | Anti-Pattern | Problem | |--------------|---------| diff --git a/skills/experience-lwc-generate/references/state-management.md b/skills/experience-lwc-generate/references/state-management.md index 86a8c6e..8437f79 100644 --- a/skills/experience-lwc-generate/references/state-management.md +++ b/skills/experience-lwc-generate/references/state-management.md @@ -511,7 +511,7 @@ export default class ComposedStateComponent extends LightningElement { ## Anti-Patterns to Avoid -### ❌ BAD: Mutating Objects In Place +### BAD: Mutating Objects In Place ```javascript // DON'T - won't trigger reactivity @@ -519,7 +519,7 @@ this.user.name = 'New Name'; this.items.push(newItem); ``` -### ✅ GOOD: Create New References +### GOOD: Create New References ```javascript // DO - triggers reactivity @@ -527,7 +527,7 @@ this.user = { ...this.user, name: 'New Name' }; this.items = [...this.items, newItem]; ``` -### ❌ BAD: Heavy Computation in Getters +### BAD: Heavy Computation in Getters ```javascript // DON'T - runs every render cycle @@ -539,7 +539,7 @@ get expensiveComputation() { } ``` -### ✅ GOOD: Cache Computed Values +### GOOD: Cache Computed Values ```javascript _cachedResult; @@ -558,7 +558,7 @@ get optimizedComputation() { } ``` -### ❌ BAD: Forgetting to Unsubscribe +### BAD: Forgetting to Unsubscribe ```javascript // DON'T - memory leak @@ -567,7 +567,7 @@ connectedCallback() { } ``` -### ✅ GOOD: Clean Up Subscriptions +### GOOD: Clean Up Subscriptions ```javascript // DO - proper cleanup diff --git a/skills/experience-lwc-generate/references/template-anti-patterns.md b/skills/experience-lwc-generate/references/template-anti-patterns.md index 6b25d57..9904478 100644 --- a/skills/experience-lwc-generate/references/template-anti-patterns.md +++ b/skills/experience-lwc-generate/references/template-anti-patterns.md @@ -26,7 +26,7 @@ This guide documents systematic errors in Lightning Web Component templates, wit **Critical Rule**: LWC templates do NOT support JavaScript expressions. Only property references are allowed. -### ❌ BAD: Arithmetic in Template +### BAD: Arithmetic in Template ```html @@ -37,7 +37,7 @@ This guide documents systematic errors in Lightning Web Component templates, wit ``` -### ✅ GOOD: Use Getters +### GOOD: Use Getters ```javascript // component.js @@ -69,7 +69,7 @@ export default class PriceCalculator extends LightningElement { ``` -### ❌ BAD: String Concatenation in Template +### BAD: String Concatenation in Template ```html @@ -79,7 +79,7 @@ export default class PriceCalculator extends LightningElement { ``` -### ✅ GOOD: Computed Properties +### GOOD: Computed Properties ```javascript // component.js @@ -112,7 +112,7 @@ export default class Greeting extends LightningElement { **Critical Rule**: Ternary operators (`condition ? a : b`) are NOT allowed in LWC templates. -### ❌ BAD: Ternary in Template +### BAD: Ternary in Template ```html @@ -123,7 +123,7 @@ export default class Greeting extends LightningElement { ``` -### ✅ GOOD: Use Getters for Conditional Values +### GOOD: Use Getters for Conditional Values ```javascript // component.js @@ -155,7 +155,7 @@ export default class StatusDisplay extends LightningElement { ``` -### ✅ GOOD: Use if:true/if:false for Conditional Rendering +### GOOD: Use if:true/if:false for Conditional Rendering ```html @@ -189,7 +189,7 @@ get hasCount() { **Critical Rule**: Object literals (`{}`) cannot be passed directly as attribute values. -### ❌ BAD: Inline Object Literals +### BAD: Inline Object Literals ```html @@ -206,7 +206,7 @@ get hasCount() { ``` -### ✅ GOOD: Define Objects in JavaScript +### GOOD: Define Objects in JavaScript ```javascript // component.js @@ -237,7 +237,7 @@ export default class ParentComponent extends LightningElement { ``` -### ❌ BAD: Inline Array Literals +### BAD: Inline Array Literals ```html @@ -246,7 +246,7 @@ export default class ParentComponent extends LightningElement { ``` -### ✅ GOOD: Define Arrays in JavaScript +### GOOD: Define Arrays in JavaScript ```javascript // component.js @@ -272,7 +272,7 @@ export default class ColorPicker extends LightningElement { **Critical Rule**: No method calls, comparisons, or logical operators in templates. -### ❌ BAD: Method Calls in Template +### BAD: Method Calls in Template ```html @@ -284,7 +284,7 @@ export default class ColorPicker extends LightningElement { ``` -### ✅ GOOD: Use Getters for Transformations +### GOOD: Use Getters for Transformations ```javascript // component.js @@ -322,7 +322,7 @@ export default class DataDisplay extends LightningElement { ``` -### ❌ BAD: Comparisons in Template +### BAD: Comparisons in Template ```html @@ -337,7 +337,7 @@ export default class DataDisplay extends LightningElement { ``` -### ✅ GOOD: Getter-Based Comparisons +### GOOD: Getter-Based Comparisons ```javascript // component.js @@ -363,7 +363,7 @@ get isActive() { ``` -### ❌ BAD: Logical Operators in Template +### BAD: Logical Operators in Template ```html @@ -378,7 +378,7 @@ get isActive() { ``` -### ✅ GOOD: Computed Boolean Properties +### GOOD: Computed Boolean Properties ```javascript // component.js @@ -408,7 +408,7 @@ get isNotLoading() { ## 5. Event Handler Mistakes -### ❌ BAD: Inline Event Handlers with Arguments +### BAD: Inline Event Handlers with Arguments ```html @@ -419,7 +419,7 @@ get isNotLoading() { ``` -### ✅ GOOD: Handler Functions with Data Attributes +### GOOD: Handler Functions with Data Attributes ```html @@ -449,7 +449,7 @@ handleChange(event) { } ``` -### ❌ BAD: Event Binding with bind() +### BAD: Event Binding with bind() ```html @@ -458,7 +458,7 @@ handleChange(event) { ``` -### ✅ GOOD: Use Data Attributes for Context +### GOOD: Use Data Attributes for Context ```html @@ -491,7 +491,7 @@ handleItemClick(event) { ## 6. Iteration Anti-Patterns -### ❌ BAD: Missing Key in Iteration +### BAD: Missing Key in Iteration ```html @@ -502,7 +502,7 @@ handleItemClick(event) { ``` -### ✅ GOOD: Always Include Key +### GOOD: Always Include Key ```html @@ -513,7 +513,7 @@ handleItemClick(event) { ``` -### ❌ BAD: Using Index as Key +### BAD: Using Index as Key ```html @@ -524,7 +524,7 @@ handleItemClick(event) { ``` -### ✅ GOOD: Use Unique Identifier as Key +### GOOD: Use Unique Identifier as Key ```javascript // If items don't have unique IDs, generate them @@ -545,7 +545,7 @@ connectedCallback() { ``` -### ❌ BAD: Nested Iteration Without Proper Keys +### BAD: Nested Iteration Without Proper Keys ```html @@ -562,7 +562,7 @@ connectedCallback() { ``` -### ✅ GOOD: Compound Keys for Nested Iteration +### GOOD: Compound Keys for Nested Iteration ```javascript // component.js @@ -595,7 +595,7 @@ get processedCategories() { ## 7. Conditional Rendering Issues -### ❌ BAD: if:true on Non-Boolean Values +### BAD: if:true on Non-Boolean Values ```html @@ -612,7 +612,7 @@ get processedCategories() { ``` -### ✅ GOOD: Explicit Boolean Conversion +### GOOD: Explicit Boolean Conversion ```javascript // component.js @@ -638,7 +638,7 @@ get hasCount() { ``` -### ❌ BAD: Multiple Conditions Without Else +### BAD: Multiple Conditions Without Else ```html @@ -655,7 +655,7 @@ get hasCount() { ``` -### ✅ GOOD: Use a State Getter +### GOOD: Use a State Getter ```javascript // component.js @@ -694,7 +694,7 @@ get isEmptyState() { return this.viewState === 'empty'; } ## 8. Slot and Composition Errors -### ❌ BAD: Named Slot with Wrong Syntax +### BAD: Named Slot with Wrong Syntax ```html @@ -711,7 +711,7 @@ get isEmptyState() { return this.viewState === 'empty'; } ``` -### ✅ GOOD: LWC Slot Syntax +### GOOD: LWC Slot Syntax ```html @@ -746,7 +746,7 @@ get isEmptyState() { return this.viewState === 'empty'; } ## 9. Data Binding Mistakes -### ❌ BAD: Two-Way Binding Syntax +### BAD: Two-Way Binding Syntax ```html @@ -757,7 +757,7 @@ get isEmptyState() { return this.viewState === 'empty'; } ``` -### ✅ GOOD: One-Way Binding with Event Handler +### GOOD: One-Way Binding with Event Handler ```html @@ -790,7 +790,7 @@ handleInputChange(event) { } ``` -### ❌ BAD: Direct Property Mutation in Template +### BAD: Direct Property Mutation in Template ```html @@ -799,7 +799,7 @@ handleInputChange(event) { ``` -### ✅ GOOD: Mutate in Handler +### GOOD: Mutate in Handler ```javascript // component.js @@ -821,7 +821,7 @@ handleIncrement() { ## 10. Style and Class Binding -### ❌ BAD: Dynamic Styles in Template +### BAD: Dynamic Styles in Template ```html @@ -836,7 +836,7 @@ handleIncrement() { ``` -### ✅ GOOD: CSS Custom Properties (Recommended) +### GOOD: CSS Custom Properties (Recommended) ```javascript // component.js @@ -864,7 +864,7 @@ renderedCallback() { ``` -### ✅ GOOD: Computed Style String (When Necessary) +### GOOD: Computed Style String (When Necessary) ```javascript // component.js @@ -880,7 +880,7 @@ get dynamicStyle() { ``` -### ❌ BAD: Dynamic Class with Expression +### BAD: Dynamic Class with Expression ```html @@ -890,7 +890,7 @@ get dynamicStyle() { ``` -### ✅ GOOD: Computed Class String +### GOOD: Computed Class String ```javascript // component.js diff --git a/skills/experience-lwc-generate/hooks/scripts/lwc-lsp-validate.py b/skills/experience-lwc-generate/scripts/lwc-lsp-validate.py similarity index 100% rename from skills/experience-lwc-generate/hooks/scripts/lwc-lsp-validate.py rename to skills/experience-lwc-generate/scripts/lwc-lsp-validate.py diff --git a/skills/experience-lwc-generate/hooks/scripts/post-tool-validate.py b/skills/experience-lwc-generate/scripts/post-tool-validate.py similarity index 100% rename from skills/experience-lwc-generate/hooks/scripts/post-tool-validate.py rename to skills/experience-lwc-generate/scripts/post-tool-validate.py diff --git a/skills/experience-lwc-generate/hooks/scripts/slds_data/deprecated_patterns.json b/skills/experience-lwc-generate/scripts/slds_data/deprecated_patterns.json similarity index 100% rename from skills/experience-lwc-generate/hooks/scripts/slds_data/deprecated_patterns.json rename to skills/experience-lwc-generate/scripts/slds_data/deprecated_patterns.json diff --git a/skills/experience-lwc-generate/hooks/scripts/slds_data/styling_hooks.json b/skills/experience-lwc-generate/scripts/slds_data/styling_hooks.json similarity index 100% rename from skills/experience-lwc-generate/hooks/scripts/slds_data/styling_hooks.json rename to skills/experience-lwc-generate/scripts/slds_data/styling_hooks.json diff --git a/skills/experience-lwc-generate/hooks/scripts/slds_data/valid_slds_classes.json b/skills/experience-lwc-generate/scripts/slds_data/valid_slds_classes.json similarity index 100% rename from skills/experience-lwc-generate/hooks/scripts/slds_data/valid_slds_classes.json rename to skills/experience-lwc-generate/scripts/slds_data/valid_slds_classes.json diff --git a/skills/experience-lwc-generate/hooks/scripts/slds_linter_wrapper.py b/skills/experience-lwc-generate/scripts/slds_linter_wrapper.py similarity index 100% rename from skills/experience-lwc-generate/hooks/scripts/slds_linter_wrapper.py rename to skills/experience-lwc-generate/scripts/slds_linter_wrapper.py diff --git a/skills/experience-lwc-generate/hooks/scripts/slds_rules/__init__.py b/skills/experience-lwc-generate/scripts/slds_rules/__init__.py similarity index 100% rename from skills/experience-lwc-generate/hooks/scripts/slds_rules/__init__.py rename to skills/experience-lwc-generate/scripts/slds_rules/__init__.py diff --git a/skills/experience-lwc-generate/hooks/scripts/template_validator.py b/skills/experience-lwc-generate/scripts/template_validator.py similarity index 100% rename from skills/experience-lwc-generate/hooks/scripts/template_validator.py rename to skills/experience-lwc-generate/scripts/template_validator.py diff --git a/skills/experience-lwc-generate/hooks/scripts/validate_slds.py b/skills/experience-lwc-generate/scripts/validate_slds.py similarity index 100% rename from skills/experience-lwc-generate/hooks/scripts/validate_slds.py rename to skills/experience-lwc-generate/scripts/validate_slds.py diff --git a/skills/experience-ui-bundle-agentforce-client-generate/references/constraints.md b/skills/experience-ui-bundle-agentforce-client-generate/references/constraints.md index ad443c6..e8d101e 100644 --- a/skills/experience-ui-bundle-agentforce-client-generate/references/constraints.md +++ b/skills/experience-ui-bundle-agentforce-client-generate/references/constraints.md @@ -65,19 +65,19 @@ When updating an existing component: ## Examples -### ❌ Wrong - Using containerStyle +### Wrong - Using containerStyle ```tsx ``` -### ✅ Correct - Using width/height directly +### Correct - Using width/height directly ```tsx ``` -### ❌ Wrong - Creating CSS file +### Wrong - Creating CSS file ```css /* agent-styles.css */ @@ -93,7 +93,7 @@ import "./agent-styles.css"; ; ``` -### ✅ Correct - Using styleTokens +### Correct - Using styleTokens ```tsx ``` -### ❌ Wrong - Creating style tag +### Wrong - Creating style tag ```tsx <> @@ -114,7 +114,7 @@ import "./agent-styles.css"; ``` -### ✅ Correct - Using styleTokens +### Correct - Using styleTokens ```tsx ``` -### ❌ Wrong - Editing implementation file +### Wrong - Editing implementation file Reading or editing: `node_modules/@salesforce/ui-bundle-template-feature-react-agentforce-conversation-client/src/AgentforceConversationClient.tsx` -### ✅ Correct - Editing usage file +### Correct - Editing usage file Reading and editing: usage files where the component is imported and used (for example, `src/app.tsx`, a route component, or a feature page) diff --git a/skills/experience-ui-bundle-custom-app-generate/SKILL.md b/skills/experience-ui-bundle-custom-app-generate/SKILL.md index 7f71d83..699a51f 100644 --- a/skills/experience-ui-bundle-custom-app-generate/SKILL.md +++ b/skills/experience-ui-bundle-custom-app-generate/SKILL.md @@ -55,10 +55,10 @@ Use the default template in the doc below. Values in `{braces}` are resolved pro | Metadata Type | Template Reference | |--------------|-------------------| -| CustomApplication | [configure-metadata-custom-application.md](docs/configure-metadata-custom-application.md) | +| CustomApplication | [configure-metadata-custom-application.md](references/configure-metadata-custom-application.md) | ### Execution Note for Step 4: Load and use the doc -- Agents MUST read the full contents of the docs/*.md file referenced in Step 4 before attempting to populate metadata fields. +- Agents MUST read the full contents of the references/*.md file referenced in Step 4 before attempting to populate metadata fields. - Read the file in full, replace placeholders (e.g. `{appName}`) with the resolved values, then use the expanded template to populate the metadata XML content. - If Step 2 determined the older field names apply, substitute `` with `` in the generated output. diff --git a/skills/experience-ui-bundle-custom-app-generate/docs/configure-metadata-custom-application.md b/skills/experience-ui-bundle-custom-app-generate/references/configure-metadata-custom-application.md similarity index 100% rename from skills/experience-ui-bundle-custom-app-generate/docs/configure-metadata-custom-application.md rename to skills/experience-ui-bundle-custom-app-generate/references/configure-metadata-custom-application.md diff --git a/skills/experience-ui-bundle-frontend-generate/SKILL.md b/skills/experience-ui-bundle-frontend-generate/SKILL.md index 16243d7..e355205 100644 --- a/skills/experience-ui-bundle-frontend-generate/SKILL.md +++ b/skills/experience-ui-bundle-frontend-generate/SKILL.md @@ -13,9 +13,9 @@ Determine which category the request falls into: | Category | Examples | Implementation Guide | |----------|----------|---------------------| -| **Page** | New routed page (contacts, dashboard, settings) | `implementation/page.md` | -| **Header / Footer** | Site-wide nav bar, footer, branding | `implementation/header-footer.md` | -| **Component** | Widget, card, table, form, dialog | `implementation/component.md` | +| **Page** | New routed page (contacts, dashboard, settings) | `references/page.md` | +| **Header / Footer** | Site-wide nav bar, footer, branding | `references/header-footer.md` | +| **Component** | Widget, card, table, form, dialog | `references/component.md` | --- diff --git a/skills/experience-ui-bundle-frontend-generate/implementation/component.md b/skills/experience-ui-bundle-frontend-generate/references/component.md similarity index 100% rename from skills/experience-ui-bundle-frontend-generate/implementation/component.md rename to skills/experience-ui-bundle-frontend-generate/references/component.md diff --git a/skills/experience-ui-bundle-frontend-generate/implementation/header-footer.md b/skills/experience-ui-bundle-frontend-generate/references/header-footer.md similarity index 100% rename from skills/experience-ui-bundle-frontend-generate/implementation/header-footer.md rename to skills/experience-ui-bundle-frontend-generate/references/header-footer.md diff --git a/skills/experience-ui-bundle-frontend-generate/implementation/page.md b/skills/experience-ui-bundle-frontend-generate/references/page.md similarity index 100% rename from skills/experience-ui-bundle-frontend-generate/implementation/page.md rename to skills/experience-ui-bundle-frontend-generate/references/page.md diff --git a/skills/experience-ui-bundle-metadata-generate/SKILL.md b/skills/experience-ui-bundle-metadata-generate/SKILL.md index 8e3970b..e040ebb 100644 --- a/skills/experience-ui-bundle-metadata-generate/SKILL.md +++ b/skills/experience-ui-bundle-metadata-generate/SKILL.md @@ -149,5 +149,5 @@ Whenever the app references a new external domain: CDN images, external fonts, t Always also set `isApplicableToConnectSrc` to `true` for preflight/redirect handling. -4. **Create the metadata file** — follow `implementation/csp-metadata-format.md` for the `.cspTrustedSite-meta.xml` format. Place in `force-app/main/default/cspTrustedSites/`. +4. **Create the metadata file** — follow `references/csp-metadata-format.md` for the `.cspTrustedSite-meta.xml` format. Place in `force-app/main/default/cspTrustedSites/`. diff --git a/skills/experience-ui-bundle-metadata-generate/implementation/csp-metadata-format.md b/skills/experience-ui-bundle-metadata-generate/references/csp-metadata-format.md similarity index 100% rename from skills/experience-ui-bundle-metadata-generate/implementation/csp-metadata-format.md rename to skills/experience-ui-bundle-metadata-generate/references/csp-metadata-format.md diff --git a/skills/experience-ui-bundle-site-generate/SKILL.md b/skills/experience-ui-bundle-site-generate/SKILL.md index e401588..ed96eed 100644 --- a/skills/experience-ui-bundle-site-generate/SKILL.md +++ b/skills/experience-ui-bundle-site-generate/SKILL.md @@ -47,23 +47,23 @@ Use the default templates in the docs below. Values in `{braces}` are resolved p | Metadata Type | Template Reference | |--------------|-------------------| -| Network | [configure-metadata-network.md](docs/configure-metadata-network.md) | -| CustomSite | [configure-metadata-custom-site.md](docs/configure-metadata-custom-site.md) | -| DigitalExperienceConfig | [configure-metadata-digital-experience-config.md](docs/configure-metadata-digital-experience-config.md) | -| DigitalExperienceBundle | [configure-metadata-digital-experience-bundle.md](docs/configure-metadata-digital-experience-bundle.md) | -| DigitalExperience (sfdc_cms__site) | [configure-metadata-digital-experience.md](docs/configure-metadata-digital-experience.md) | +| Network | [configure-metadata-network.md](references/configure-metadata-network.md) | +| CustomSite | [configure-metadata-custom-site.md](references/configure-metadata-custom-site.md) | +| DigitalExperienceConfig | [configure-metadata-digital-experience-config.md](references/configure-metadata-digital-experience-config.md) | +| DigitalExperienceBundle | [configure-metadata-digital-experience-bundle.md](references/configure-metadata-digital-experience-bundle.md) | +| DigitalExperience (sfdc_cms__site) | [configure-metadata-digital-experience.md](references/configure-metadata-digital-experience.md) | -For URL updates, see [update-site-urls.md](docs/update-site-urls.md). +For URL updates, see [update-site-urls.md](references/update-site-urls.md). ### Execution Note for Step 3: Load and use the docs -- Agents MUST read the full contents of each docs/*.md file referenced in Step 3 before attempting to populate metadata fields. +- Agents MUST read the full contents of each references/*.md file referenced in Step 3 before attempting to populate metadata fields. - Use your platform's file-read tool (for example, `read_file`) to load these files in full, then perform placeholder substitution for values in `{braces}` using the resolved properties from Step 1. - Files to load: - - `docs/configure-metadata-network.md` - - `docs/configure-metadata-custom-site.md` - - `docs/configure-metadata-digital-experience-config.md` - - `docs/configure-metadata-digital-experience-bundle.md` - - `docs/configure-metadata-digital-experience.md` + - `references/configure-metadata-network.md` + - `references/configure-metadata-custom-site.md` + - `references/configure-metadata-digital-experience-config.md` + - `references/configure-metadata-digital-experience-bundle.md` + - `references/configure-metadata-digital-experience.md` - Read entire file contents, replace placeholders (e.g. `{siteName}`) with the resolved values, then use the expanded templates to populate the metadata XML/JSON content. ### Step 4: Do Not Modify Non-Templated Properties @@ -88,5 +88,5 @@ sf project deploy validate --metadata Network CustomSite DigitalExperienceConfig **Use when** user wants to update or change site URLs (urlPathPrefix). **Steps**: -- [ ] Read [update-site-urls.md](docs/update-site-urls.md) to understand the three-component architecture and URL update workflow +- [ ] Read [update-site-urls.md](references/update-site-urls.md) to understand the three-component architecture and URL update workflow - [ ] Follow the step-by-step workflow in the doc to update URLs consistently across all three components (DigitalExperienceConfig, Network, CustomSite) diff --git a/skills/experience-ui-bundle-site-generate/docs/configure-metadata-custom-site.md b/skills/experience-ui-bundle-site-generate/references/configure-metadata-custom-site.md similarity index 100% rename from skills/experience-ui-bundle-site-generate/docs/configure-metadata-custom-site.md rename to skills/experience-ui-bundle-site-generate/references/configure-metadata-custom-site.md diff --git a/skills/experience-ui-bundle-site-generate/docs/configure-metadata-digital-experience-bundle.md b/skills/experience-ui-bundle-site-generate/references/configure-metadata-digital-experience-bundle.md similarity index 100% rename from skills/experience-ui-bundle-site-generate/docs/configure-metadata-digital-experience-bundle.md rename to skills/experience-ui-bundle-site-generate/references/configure-metadata-digital-experience-bundle.md diff --git a/skills/experience-ui-bundle-site-generate/docs/configure-metadata-digital-experience-config.md b/skills/experience-ui-bundle-site-generate/references/configure-metadata-digital-experience-config.md similarity index 100% rename from skills/experience-ui-bundle-site-generate/docs/configure-metadata-digital-experience-config.md rename to skills/experience-ui-bundle-site-generate/references/configure-metadata-digital-experience-config.md diff --git a/skills/experience-ui-bundle-site-generate/docs/configure-metadata-digital-experience.md b/skills/experience-ui-bundle-site-generate/references/configure-metadata-digital-experience.md similarity index 100% rename from skills/experience-ui-bundle-site-generate/docs/configure-metadata-digital-experience.md rename to skills/experience-ui-bundle-site-generate/references/configure-metadata-digital-experience.md diff --git a/skills/experience-ui-bundle-site-generate/docs/configure-metadata-network.md b/skills/experience-ui-bundle-site-generate/references/configure-metadata-network.md similarity index 100% rename from skills/experience-ui-bundle-site-generate/docs/configure-metadata-network.md rename to skills/experience-ui-bundle-site-generate/references/configure-metadata-network.md diff --git a/skills/experience-ui-bundle-site-generate/docs/update-site-urls.md b/skills/experience-ui-bundle-site-generate/references/update-site-urls.md similarity index 100% rename from skills/experience-ui-bundle-site-generate/docs/update-site-urls.md rename to skills/experience-ui-bundle-site-generate/references/update-site-urls.md diff --git a/skills/integration-connectivity-generate/SKILL.md b/skills/integration-connectivity-generate/SKILL.md index dfcc6ec..7dc9018 100644 --- a/skills/integration-connectivity-generate/SKILL.md +++ b/skills/integration-connectivity-generate/SKILL.md @@ -163,8 +163,8 @@ Next step: - `assets/endpoint-security/` — Remote Site Setting and CSP Trusted Site XML templates ### Automation hooks -- `hooks/scripts/suggest_credential_setup.py` — auto-suggests credential configuration steps when integration files are detected -- `hooks/scripts/validate_integration.py` — validates integration patterns before agent responses +- `scripts/suggest_credential_setup.py` — auto-suggests credential configuration steps when integration files are detected +- `scripts/validate_integration.py` — validates integration patterns before agent responses --- diff --git a/skills/integration-connectivity-generate/hooks/scripts/suggest_credential_setup.py b/skills/integration-connectivity-generate/scripts/suggest_credential_setup.py similarity index 100% rename from skills/integration-connectivity-generate/hooks/scripts/suggest_credential_setup.py rename to skills/integration-connectivity-generate/scripts/suggest_credential_setup.py diff --git a/skills/integration-connectivity-generate/hooks/scripts/validate_integration.py b/skills/integration-connectivity-generate/scripts/validate_integration.py similarity index 100% rename from skills/integration-connectivity-generate/hooks/scripts/validate_integration.py rename to skills/integration-connectivity-generate/scripts/validate_integration.py diff --git a/skills/mobile-platform-native-capabilities-integrate/SKILL.md b/skills/mobile-platform-native-capabilities-integrate/SKILL.md index 0194246..74c4812 100644 --- a/skills/mobile-platform-native-capabilities-integrate/SKILL.md +++ b/skills/mobile-platform-native-capabilities-integrate/SKILL.md @@ -1,6 +1,6 @@ --- name: mobile-platform-native-capabilities-integrate -description: "Build a Salesforce LWC that uses native mobile device capabilities — barcode scanner, biometrics, location, NFC, calendar, contacts, document scanner, geofencing, AR space capture, app review, and payments. Use this skill when the user asks for an LWC that scans a barcode, captures a photo of a document, reads location or geofences, prompts for biometrics, reads/writes the device calendar or contacts, taps NFC, takes a payment, prompts for an app review, or scans an AR space. Also triggers on \"lightning/mobileCapabilities\", \"mobile capability\", \"Nimbus\", \"device capability\". Do not use for mobile offline / Komaci priming reviews (use `mobile-platform-offline-validate`) or for picking generic Lightning base components (use a generic Lightning base components skill)." +description: "Build a Salesforce LWC that uses native mobile device capabilities — barcode scanner, biometrics, location, NFC, calendar, contacts, document scanner, geofencing, AR space capture, app review, and payments. Use this skill when the user asks for an LWC that scans a barcode, captures a photo of a document, reads location or geofences, prompts for biometrics, reads/writes the device calendar or contacts, taps NFC, takes a payment, prompts for an app review, or scans an AR space. Also triggers on \"lightning/mobileCapabilities\", \"mobile capability\", \"Nimbus\", \"device capability\". Do not use for mobile offline / Komaci priming reviews (use `mobile-platform-offline-validate`) or for picking generic Lightning base components (use `design-systems-slds-apply`)." metadata: version: "1.0" --- @@ -32,8 +32,8 @@ Do NOT use this skill for: - Mobile-offline review of an LWC (lwc:if, inline GraphQL, Komaci-priming violations) — use `mobile-platform-offline-validate`. -- Picking generic Lightning Base Components — use - `using-lightning-base-components`. +- Choosing or styling generic Lightning Base Components / SLDS blueprints — + use `design-systems-slds-apply`. ## Prerequisites diff --git a/skills/omnistudio-callable-apex-generate/SKILL.md b/skills/omnistudio-callable-apex-generate/SKILL.md index 8f33577..9af9f0e 100644 --- a/skills/omnistudio-callable-apex-generate/SKILL.md +++ b/skills/omnistudio-callable-apex-generate/SKILL.md @@ -175,7 +175,7 @@ action contract stable. --- -## ⛔ Guardrails (Mandatory) +## Guardrails (Mandatory) Stop and ask the user if any of these would be introduced: - Dynamic method execution based on user input (no reflection) diff --git a/skills/omnistudio-epc-catalog-generate/examples/.gitkeep b/skills/omnistudio-epc-catalog-generate/examples/.gitkeep deleted file mode 100644 index 8b13789..0000000 --- a/skills/omnistudio-epc-catalog-generate/examples/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - diff --git a/skills/platform-apex-logs-debug/references/benchmarking-guide.md b/skills/platform-apex-logs-debug/references/benchmarking-guide.md index bbc380a..6bc0934 100644 --- a/skills/platform-apex-logs-debug/references/benchmarking-guide.md +++ b/skills/platform-apex-logs-debug/references/benchmarking-guide.md @@ -228,7 +228,7 @@ public void processWithSafety(List accounts) { ## Benchmarking Anti-Patterns -### ❌ Don't Do These +### Don't Do These | Anti-Pattern | Problem | Solution | |--------------|---------|----------| diff --git a/skills/platform-apex-test-run/SKILL.md b/skills/platform-apex-test-run/SKILL.md index 2e99bd5..c17c1d7 100644 --- a/skills/platform-apex-test-run/SKILL.md +++ b/skills/platform-apex-test-run/SKILL.md @@ -143,7 +143,7 @@ Next step: | `assets/test-data-factory.cls` | Template: reusable `TestDataFactory` with create and insert helpers | | `assets/dml-mock.cls` | Template: `IDML` interface + `DMLMock` implementation for database-free unit tests | | `assets/stub-provider-example.cls` | Template: `StubProvider`-based dependency injection stub | -| `hooks/scripts/parse-test-results.py` | Post-tool hook — parses `sf apex run test` JSON output and formats failures for the auto-fix loop | +| `scripts/parse-test-results.py` | Post-tool hook — parses `sf apex run test` JSON output and formats failures for the auto-fix loop | --- diff --git a/skills/platform-apex-test-run/references/testing-best-practices.md b/skills/platform-apex-test-run/references/testing-best-practices.md index b72c520..51cc508 100644 --- a/skills/platform-apex-test-run/references/testing-best-practices.md +++ b/skills/platform-apex-test-run/references/testing-best-practices.md @@ -324,7 +324,7 @@ static void testBulkOperation_StaysWithinLimits() { ## Common Anti-Patterns to Avoid -### ❌ SeeAllData=true +### SeeAllData=true ```apex // ❌ BAD: Depends on org data @@ -342,7 +342,7 @@ static void testGoodPattern() { } ``` -### ❌ No Assertions +### No Assertions ```apex // ❌ BAD: No assertions - test passes even if code is broken @@ -364,7 +364,7 @@ static void testWithAssertions() { } ``` -### ❌ Hardcoded IDs +### Hardcoded IDs ```apex // ❌ BAD: Hardcoded IDs fail across orgs diff --git a/skills/platform-apex-test-run/hooks/scripts/parse-test-results.py b/skills/platform-apex-test-run/scripts/parse-test-results.py similarity index 100% rename from skills/platform-apex-test-run/hooks/scripts/parse-test-results.py rename to skills/platform-apex-test-run/scripts/parse-test-results.py diff --git a/skills/platform-custom-application-generate/SKILL.md b/skills/platform-custom-application-generate/SKILL.md index d373267..aab844b 100644 --- a/skills/platform-custom-application-generate/SKILL.md +++ b/skills/platform-custom-application-generate/SKILL.md @@ -19,7 +19,7 @@ Use this skill when you need to: Custom applications (Lightning Apps) that group tabs and functionality to provide a focused user experience for specific business processes. Always configured for Lightning Experience. -## 🎯 Purpose +## Purpose - Organize related functionality into focused applications - Group tabs and components for specific user roles - Provide tailored user experiences @@ -29,7 +29,7 @@ Custom applications (Lightning Apps) that group tabs and functionality to provid - Override standard actions with custom Lightning pages for enhanced user experience - Enable profile-specific experiences through profile action overrides -## ⚙️ Required Properties +## Required Properties ### Core Application Properties - **fullName**: API name of the application @@ -51,7 +51,7 @@ Custom applications (Lightning Apps) that group tabs and functionality to provid - **isNavPersonalizationDisabled**: Personalization setting (default: false) - **isNavTabPersistenceDisabled**: Tab persistence setting (default: false) -## 🔧 Application Configuration +## Application Configuration ### Navigation Type Selection (CRITICAL) **Decision Criteria for navType:** @@ -116,7 +116,7 @@ Custom applications (Lightning Apps) that group tabs and functionality to provid - **profileActionOverrides.profile**: Profile API name (e.g., "Admin", "Standard User") - Enables different page layouts for different user profiles -## 📱 Device Support +## Device Support ### Desktop Configuration - **formFactor**: "Large" @@ -130,7 +130,7 @@ Custom applications (Lightning Apps) that group tabs and functionality to provid - **formFactor**: "Medium" - **tabs**: Tablet-appropriate tab selection -## 🎨 User Experience Features +## User Experience Features ### Navigation Behavior - **Auto Temporary Tabs**: Can be enabled/disabled @@ -142,12 +142,12 @@ Custom applications (Lightning Apps) that group tabs and functionality to provid - **Screen Reader**: Compatible with assistive technologies - **High Contrast**: Support for high contrast modes -## 🔗 Integration Points +## Integration Points - **Custom Tabs**: Include custom object and web tabs - **Standard Tabs**: Include standard Salesforce tabs - **Lightning Pages**: Integrate with Lightning page layouts - **Components**: Include custom Lightning components -## ✅ Best Practices +## Best Practices - **Always use Lightning UI**: Set `uiType` to "Lightning" for modern apps - **Choose appropriate navigation**: CRITICAL - Analyze requirements carefully for `navType` selection - Use "Standard" (DEFAULT) for general business applications @@ -168,7 +168,7 @@ Custom applications (Lightning Apps) that group tabs and functionality to provid - **Leverage action overrides**: Customize page layouts for specific objects using FlexiPages from flexipage expert - **Use profile overrides**: Provide role-specific experiences by referencing different flexipage expert generated pages per profile -## 🎯 Enhancement Rules +## Enhancement Rules - **uiType**: Always set to "Lightning" for modern app experience - **navType**: CRITICAL DECISION - Analyze user requirements carefully - Set to "Standard" (DEFAULT) for general business applications @@ -192,7 +192,7 @@ Custom applications (Lightning Apps) that group tabs and functionality to provid - **Profile Action Overrides**: Reference flexipage expert generated pages for role-based customization - **Form Factors**: Use "Large" for desktop, "Small" for mobile in overrides -## ⚠️ CRITICAL Verification Checklist (MUST VERIFY) +## CRITICAL Verification Checklist (MUST VERIFY) - [ ] All tabs are included in the application - [ ] **navType IS CORRECTLY SET** - Verify Console vs Standard selection - [ ] Default to "Standard" for most general business applications diff --git a/skills/platform-custom-field-generate/SKILL.md b/skills/platform-custom-field-generate/SKILL.md index ac62d6b..8dc5844 100644 --- a/skills/platform-custom-field-generate/SKILL.md +++ b/skills/platform-custom-field-generate/SKILL.md @@ -1,48 +1,33 @@ --- name: platform-custom-field-generate -description: "Use this skill when users need to create, generate, or validate Salesforce Custom Field metadata. Trigger when users mention custom fields, field types, Roll-up Summary fields, Master-Detail relationships, Lookup relationships, formula fields, picklists, or field metadata. Also use when users encounter field deployment errors, especially around Roll-up Summary format, Master-Detail constraints, or formula issues. Always use this skill for any custom field metadata work, field generation, or field troubleshooting." +description: "Use this skill when users need to create, generate, or validate Salesforce Custom Field metadata. Trigger when users mention custom fields, field types, Roll-up Summary fields, Master-Detail relationships, Lookup relationships, formula fields, picklists, dependent (controlling) picklists, referencing a value set from a field, or scoping/limiting picklist values for a specific record type. Also use when users encounter field deployment errors, especially around Roll-up Summary format, Master-Detail constraints, formula issues, or a record type that won't deploy without a business process. Use this skill for custom field metadata work, field generation, and field troubleshooting. DO NOT TRIGGER for creating or customizing the value set itself — defining a new GlobalValueSet, or modifying a StandardValueSet catalog like Industry or Lead Source — use platform-value-set-generate instead; this skill covers the field that references a value set, not the value set definition." metadata: version: "1.0" + minApiVersion: "51.0" --- -## When to Use This Skill - -Use this skill when you need to: -- Create custom fields on any object -- Generate field metadata for any field type -- Set up relationship fields (Lookup or Master-Detail) -- Create formula or roll-up summary fields -- Troubleshoot deployment errors related to custom fields - # Salesforce Custom Field Generator and Validator ## Overview -Generate and validate Salesforce Custom Field metadata with mandatory constraints to prevent deployment errors. This skill has special focus on the **highest-failure-rate field types**: Roll-up Summary and Master-Detail relationships. - -## Specification - -## 1. Purpose - -This document defines the mandatory constraints for generating CustomField metadata XML. The agent must verify these constraints before outputting XML to prevent Metadata API deployment errors. - -**Critical Focus Areas:** -- Roll-up Summary field format errors -- Master-Detail field attribute restrictions -- Lookup Filter restrictions +Generates and validates Salesforce CustomField metadata XML, with special handling for the **highest-failure-rate types** — Roll-Up Summary and Master-Detail. The agent must verify the constraints below before outputting XML to prevent Metadata API deployment errors. --- -## 2. Universal Mandatory Attributes +## 1. Universal Mandatory Attributes Every generated field must include these tags: | Attribute | Requirement | Notes | |-----------|-------------|-------| -| `` | Required | Derive from `