@salesforce/afv-skills 1.16.0 → 1.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/activating-datacloud/SKILL.md +2 -2
- package/skills/analyzing-omnistudio-dependencies/SKILL.md +1 -1
- package/skills/building-sf-integrations/SKILL.md +1 -1
- package/skills/building-ui-bundle-app/SKILL.md +1 -1
- package/skills/configuring-code-analyzer/SKILL.md +482 -0
- package/skills/configuring-code-analyzer/examples/apex-project-config.yml +41 -0
- package/skills/configuring-code-analyzer/examples/ci-github-actions.yml +96 -0
- package/skills/configuring-code-analyzer/examples/fullstack-project-config.yml +46 -0
- package/skills/configuring-code-analyzer/examples/lwc-project-config.yml +26 -0
- package/skills/configuring-code-analyzer/references/ci-cd-templates.md +648 -0
- package/skills/configuring-code-analyzer/references/config-schema.md +257 -0
- package/skills/configuring-code-analyzer/references/diagnostic-flow.md +70 -0
- package/skills/configuring-code-analyzer/references/engine-prerequisites.md +276 -0
- package/skills/configuring-code-analyzer/references/rule-name-resolution.md +67 -0
- package/skills/configuring-code-analyzer/references/troubleshooting.md +298 -0
- package/skills/configuring-code-analyzer/scripts/check-prerequisites.sh +189 -0
- package/skills/configuring-code-analyzer/scripts/generate-config.sh +143 -0
- package/skills/configuring-code-analyzer/scripts/validate-config.sh +153 -0
- package/skills/connecting-datacloud/SKILL.md +2 -2
- package/skills/creating-b2b-commerce-store/SKILL.md +0 -1
- package/skills/developing-agentforce/SKILL.md +0 -1
- package/skills/generating-apex/SKILL.md +1 -0
- package/skills/generating-mermaid-diagrams/assets/datamodel/b2b-commerce-erd.md +1 -1
- package/skills/generating-mermaid-diagrams/assets/datamodel/campaigns-erd.md +1 -1
- package/skills/generating-mermaid-diagrams/assets/datamodel/consent-erd.md +1 -1
- package/skills/generating-mermaid-diagrams/assets/datamodel/files-erd.md +1 -1
- package/skills/generating-mermaid-diagrams/assets/datamodel/forecasting-erd.md +1 -1
- package/skills/generating-mermaid-diagrams/assets/datamodel/fsl-erd.md +1 -1
- package/skills/generating-mermaid-diagrams/assets/datamodel/party-model-erd.md +1 -1
- package/skills/generating-mermaid-diagrams/assets/datamodel/quote-order-erd.md +1 -1
- package/skills/generating-mermaid-diagrams/assets/datamodel/revenue-cloud-erd.md +1 -1
- package/skills/generating-mermaid-diagrams/assets/datamodel/sales-cloud-erd.md +1 -1
- package/skills/generating-mermaid-diagrams/assets/datamodel/salesforce-erd.md +1 -1
- package/skills/generating-mermaid-diagrams/assets/datamodel/scheduler-erd.md +1 -1
- package/skills/generating-mermaid-diagrams/assets/datamodel/service-cloud-erd.md +1 -1
- package/skills/generating-mermaid-diagrams/assets/datamodel/territory-management-erd.md +1 -1
- package/skills/generating-mermaid-diagrams/references/erd-conventions.md +1 -1
- package/skills/generating-mermaid-diagrams/references/preview-guide.md +2 -5
- package/skills/harmonizing-datacloud/SKILL.md +2 -2
- package/skills/implementing-ui-bundle-agentforce-conversation-client/SKILL.md +1 -2
- package/skills/investigating-agentforce-d360/SKILL.md +1 -1
- package/skills/managing-cdc-enablement/SKILL.md +164 -0
- package/skills/managing-cdc-enablement/assets/PlatformEventChannel-template.xml +5 -0
- package/skills/managing-cdc-enablement/assets/PlatformEventChannelMember-template.xml +11 -0
- package/skills/managing-cdc-enablement/references/deploy-troubleshooting.md +73 -0
- package/skills/managing-cdc-enablement/references/filter-expressions.md +93 -0
- package/skills/observing-agentforce/SKILL.md +0 -1
- package/skills/observing-agentforce/references/stdm-queries.md +3 -11
- package/skills/orchestrating-datacloud/README.md +5 -7
- package/skills/orchestrating-datacloud/SKILL.md +3 -3
- package/skills/orchestrating-datacloud/references/feature-readiness.md +2 -2
- package/skills/orchestrating-datacloud/references/plugin-setup.md +6 -8
- package/skills/orchestrating-datacloud/scripts/diagnose-org.mjs +1 -1
- package/skills/orchestrating-datacloud/scripts/verify-plugin.sh +2 -2
- package/skills/preparing-datacloud/SKILL.md +2 -2
- package/skills/retrieving-datacloud/SKILL.md +3 -3
- package/skills/reviewing-lwc-mobile-offline/SKILL.md +0 -1
- package/skills/running-code-analyzer/SKILL.md +264 -267
- package/skills/running-code-analyzer/references/post-scan-workflows.md +286 -0
- package/skills/running-code-analyzer/scripts/describe-rule.js +382 -0
- package/skills/running-code-analyzer/scripts/list-rules.js +260 -0
- package/skills/running-code-analyzer/scripts/query-results.js +230 -0
- package/skills/testing-agentforce/SKILL.md +0 -1
- package/skills/using-mobile-native-capabilities/SKILL.md +0 -1
- package/skills/using-salesforce-archive/SKILL.md +121 -0
- package/skills/using-salesforce-archive/examples/monitor-failed-jobs.md +47 -0
- package/skills/using-salesforce-archive/references/archive-activity-entity.md +59 -0
- package/skills/using-salesforce-archive/references/connect-api-operations.md +157 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
# Post-Scan Workflows
|
|
2
|
+
|
|
3
|
+
After presenting initial scan results (Step 5), the user may ask follow-up questions to explore results or understand violations. This reference covers three post-scan workflows:
|
|
4
|
+
|
|
5
|
+
1. **Result Querying** — filter/drill into existing results without re-scanning
|
|
6
|
+
2. **Rule Description** — explain what a rule does and how to fix violations
|
|
7
|
+
3. **Rule Listing** — browse available rules without running a scan
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Result Querying (Step 7)
|
|
12
|
+
|
|
13
|
+
### When to Use
|
|
14
|
+
|
|
15
|
+
Trigger this workflow when the user asks to explore existing results:
|
|
16
|
+
- "Show me just the security violations"
|
|
17
|
+
- "What's in AccountService.cls?"
|
|
18
|
+
- "Show only PMD issues"
|
|
19
|
+
- "Filter to severity 1 and 2"
|
|
20
|
+
- "What ESLint rules fired?"
|
|
21
|
+
- "Show violations in the lwc folder"
|
|
22
|
+
- "Top 20 by file"
|
|
23
|
+
|
|
24
|
+
### How It Works
|
|
25
|
+
|
|
26
|
+
The `query-results.js` script re-filters the SAME results JSON file (from Step 4) with different criteria. No re-scan is needed — it is instant.
|
|
27
|
+
|
|
28
|
+
### Script Reference
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
node "<skill_dir>/scripts/query-results.js" "<results-file.json>" [options]
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Filter options (combine any):**
|
|
35
|
+
|
|
36
|
+
| Option | Description | Example |
|
|
37
|
+
|--------|-------------|---------|
|
|
38
|
+
| `--engine <name>` | Filter by engine | `--engine pmd` |
|
|
39
|
+
| `--severity <n>` | Filter by severity (comma-separated) | `--severity 1,2` |
|
|
40
|
+
| `--category <tag>` | Filter by category/tag | `--category Security` |
|
|
41
|
+
| `--rule <name>` | Filter by exact rule name | `--rule ApexCRUDViolation` |
|
|
42
|
+
| `--file <substring>` | Filter by file path substring | `--file AccountService` |
|
|
43
|
+
| `--top <n>` | Return top N results (default: 10) | `--top 20` |
|
|
44
|
+
| `--sort <field>` | Sort by: severity, rule, engine, file | `--sort file` |
|
|
45
|
+
| `--sort-dir <dir>` | Sort direction: asc, desc | `--sort-dir desc` |
|
|
46
|
+
| `--summary` | Show only counts (no individual violations) | `--summary` |
|
|
47
|
+
|
|
48
|
+
**Options can be combined freely:**
|
|
49
|
+
```bash
|
|
50
|
+
# Security violations in PMD, top 5
|
|
51
|
+
node "<skill_dir>/scripts/query-results.js" "./results.json" --engine pmd --category Security --top 5
|
|
52
|
+
|
|
53
|
+
# All Critical+High in a specific file
|
|
54
|
+
node "<skill_dir>/scripts/query-results.js" "./results.json" --severity 1,2 --file AccountService.cls
|
|
55
|
+
|
|
56
|
+
# Summary of ESLint issues only
|
|
57
|
+
node "<skill_dir>/scripts/query-results.js" "./results.json" --engine eslint --summary
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Output Format
|
|
61
|
+
|
|
62
|
+
The script outputs JSON with this structure:
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"query": { "engine": "pmd", "severity": [1,2], ... },
|
|
67
|
+
"totalViolations": 500,
|
|
68
|
+
"totalMatches": 23,
|
|
69
|
+
"severityCounts": { "1": 5, "2": 18, "3": 0, "4": 0, "5": 0 },
|
|
70
|
+
"topRules": [{ "rule": "ApexCRUDViolation", "engine": "pmd", "count": 12 }, ...],
|
|
71
|
+
"topFiles": [{ "file": "AccountService.cls", "count": 8 }, ...],
|
|
72
|
+
"violations": [
|
|
73
|
+
{ "rule": "...", "engine": "...", "severity": 1, "message": "...", "file": "...", "startLine": 42, "tags": [...] },
|
|
74
|
+
...
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
When `--summary` is used, the `violations` array is omitted.
|
|
80
|
+
|
|
81
|
+
### Presentation Rules
|
|
82
|
+
|
|
83
|
+
Present query results using the same format as Step 5, but with a header indicating the active filter:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
## Filtered Results: [description of filter]
|
|
87
|
+
|
|
88
|
+
**X matches** out of Y total violations.
|
|
89
|
+
|
|
90
|
+
| Severity | Count |
|
|
91
|
+
|----------|-------|
|
|
92
|
+
| Critical (1) | X |
|
|
93
|
+
| High (2) | X |
|
|
94
|
+
| ... |
|
|
95
|
+
|
|
96
|
+
### Matching Violations
|
|
97
|
+
| # | Rule | Engine | Sev | File | Line |
|
|
98
|
+
|---|------|--------|-----|------|------|
|
|
99
|
+
| 1 | ... | ... | ... | ... | ... |
|
|
100
|
+
|
|
101
|
+
### Top Rules (within filter)
|
|
102
|
+
| Rule | Engine | Count |
|
|
103
|
+
|------|--------|-------|
|
|
104
|
+
| ... | ... | ... |
|
|
105
|
+
|
|
106
|
+
Full results: `<original-results-file>`
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Follow-Up Offers
|
|
110
|
+
|
|
111
|
+
After presenting filtered results, offer:
|
|
112
|
+
- "Want me to narrow further?" (add more filters)
|
|
113
|
+
- "Want me to explain any of these rules?" (→ Step 8)
|
|
114
|
+
- "Want me to apply fixes for these?" (→ Step 6, scoped to matched rules)
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Rule Description (Step 8)
|
|
119
|
+
|
|
120
|
+
### When to Use
|
|
121
|
+
|
|
122
|
+
Trigger this workflow when the user asks about a specific rule:
|
|
123
|
+
- "What is ApexCRUDViolation?"
|
|
124
|
+
- "Explain this rule"
|
|
125
|
+
- "What does no-var mean?"
|
|
126
|
+
- "How do I fix OperationWithLimitsInLoop?"
|
|
127
|
+
- "Tell me about the ApexSOQLInjection rule"
|
|
128
|
+
- "Why is this flagged?"
|
|
129
|
+
|
|
130
|
+
### How It Works
|
|
131
|
+
|
|
132
|
+
The `describe-rule.js` script calls `sf code-analyzer rules` with a targeted selector to extract rule metadata including description and documentation links.
|
|
133
|
+
|
|
134
|
+
### Script Reference
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
node "<skill_dir>/scripts/describe-rule.js" "<rule-name>" [--engine <engine>]
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Arguments:**
|
|
141
|
+
|
|
142
|
+
| Argument | Description | Example |
|
|
143
|
+
|----------|-------------|---------|
|
|
144
|
+
| `<rule-name>` | The rule name to look up | `ApexCRUDViolation` |
|
|
145
|
+
| `--engine <engine>` | Narrow to a specific engine (optional) | `--engine pmd` |
|
|
146
|
+
|
|
147
|
+
**Examples:**
|
|
148
|
+
```bash
|
|
149
|
+
node "<skill_dir>/scripts/describe-rule.js" "ApexCRUDViolation" --engine pmd
|
|
150
|
+
node "<skill_dir>/scripts/describe-rule.js" "no-var" --engine eslint
|
|
151
|
+
node "<skill_dir>/scripts/describe-rule.js" "OperationWithLimitsInLoop"
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Output Format
|
|
155
|
+
|
|
156
|
+
**Success — single rule found:**
|
|
157
|
+
```json
|
|
158
|
+
{
|
|
159
|
+
"status": "success",
|
|
160
|
+
"rule": {
|
|
161
|
+
"name": "ApexCRUDViolation",
|
|
162
|
+
"engine": "pmd",
|
|
163
|
+
"severity": "2 (High)",
|
|
164
|
+
"tags": ["Security", "Recommended", "Apex"],
|
|
165
|
+
"description": "Validates that CRUD and FLS checks are performed before DML operations...",
|
|
166
|
+
"resources": ["https://pmd.github.io/latest/pmd_rules_apex_security.html#apexcrudviolation"]
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Multiple matches (partial name):**
|
|
172
|
+
```json
|
|
173
|
+
{
|
|
174
|
+
"status": "multiple_matches",
|
|
175
|
+
"message": "Rule \"CRUD\" not found as exact match. Found 3 potential matches:",
|
|
176
|
+
"candidates": [
|
|
177
|
+
{ "name": "ApexCRUDViolation", "engine": "pmd", "severity": "2", "tags": "Security, Recommended" },
|
|
178
|
+
...
|
|
179
|
+
]
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Not found:**
|
|
184
|
+
```json
|
|
185
|
+
{
|
|
186
|
+
"status": "not_found",
|
|
187
|
+
"message": "Rule \"FakeRule\" not found. Verify the rule name with: sf code-analyzer rules ..."
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Presentation Rules
|
|
192
|
+
|
|
193
|
+
**For a successful lookup**, present:
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
## Rule: ApexCRUDViolation
|
|
197
|
+
|
|
198
|
+
| Property | Value |
|
|
199
|
+
|----------|-------|
|
|
200
|
+
| Engine | pmd |
|
|
201
|
+
| Severity | 2 (High) |
|
|
202
|
+
| Tags | Security, Recommended, Apex |
|
|
203
|
+
|
|
204
|
+
### Description
|
|
205
|
+
Validates that CRUD and FLS checks are performed before DML operations. Without these
|
|
206
|
+
checks, data may be accessed or modified without proper user permissions, violating
|
|
207
|
+
the Salesforce security model.
|
|
208
|
+
|
|
209
|
+
### How to Fix
|
|
210
|
+
[Provide actionable fix guidance based on the description. If the description mentions
|
|
211
|
+
a fix pattern, elaborate. If resources are available, include the link.]
|
|
212
|
+
|
|
213
|
+
### Resources
|
|
214
|
+
- [PMD Documentation](https://pmd.github.io/...)
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
Want me to show all violations of this rule in your scan results?
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
**For multiple matches**, present:
|
|
221
|
+
|
|
222
|
+
```
|
|
223
|
+
I found multiple rules matching "CRUD":
|
|
224
|
+
|
|
225
|
+
| # | Rule | Engine | Severity |
|
|
226
|
+
|---|------|--------|----------|
|
|
227
|
+
| 1 | ApexCRUDViolation | pmd | 2 (High) |
|
|
228
|
+
| 2 | ... | ... | ... |
|
|
229
|
+
|
|
230
|
+
Which rule would you like details on?
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
**For not found**, present:
|
|
234
|
+
|
|
235
|
+
```
|
|
236
|
+
I couldn't find a rule named "FakeRule". Would you like me to:
|
|
237
|
+
- Search for similar rules? (I'll grep the full rule list)
|
|
238
|
+
- List all rules for a specific engine or category?
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### After Describing a Rule
|
|
242
|
+
|
|
243
|
+
Offer next steps:
|
|
244
|
+
- "Want me to show all violations of this rule in your results?" (→ Step 7 with `--rule`)
|
|
245
|
+
- "Want me to apply the engine fix for this rule?" (→ Step 6)
|
|
246
|
+
- "Want me to explain another rule?"
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Rule Listing (Step 9)
|
|
251
|
+
|
|
252
|
+
### Presentation Rules
|
|
253
|
+
|
|
254
|
+
Present available rules in this format:
|
|
255
|
+
|
|
256
|
+
```
|
|
257
|
+
## Available Rules: Security
|
|
258
|
+
|
|
259
|
+
**Found X rules** across Y engines.
|
|
260
|
+
|
|
261
|
+
| Engine | Count |
|
|
262
|
+
|--------|-------|
|
|
263
|
+
| pmd | 12 |
|
|
264
|
+
| eslint | 6 |
|
|
265
|
+
|
|
266
|
+
| Severity | Count |
|
|
267
|
+
|----------|-------|
|
|
268
|
+
| Critical (1) | 3 |
|
|
269
|
+
| High (2) | 15 |
|
|
270
|
+
|
|
271
|
+
### Rules (top 25)
|
|
272
|
+
| # | Rule | Engine | Severity | Tags |
|
|
273
|
+
|---|------|--------|----------|------|
|
|
274
|
+
| 1 | ApexCRUDViolation | pmd | 2 (High) | Security, Recommended |
|
|
275
|
+
| 2 | ApexSOQLInjection | pmd | 1 (Critical) | Security, Recommended |
|
|
276
|
+
| ... |
|
|
277
|
+
|
|
278
|
+
Want me to explain any of these rules? Or run a scan with this selector?
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Follow-Up Offers
|
|
282
|
+
|
|
283
|
+
After listing rules:
|
|
284
|
+
- "Want me to explain any of these?" (→ Step 8)
|
|
285
|
+
- "Want me to scan with this selector?" (→ Steps 1-5 with the same selector)
|
|
286
|
+
- "Narrow to just high severity?" (re-run with `--severity 1,2`)
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Version: v1.1 | SHA256: placeholder
|
|
3
|
+
// Get detailed description and documentation for a Code Analyzer rule
|
|
4
|
+
// Usage: node describe-rule.js <rule-name> [--engine <engine>]
|
|
5
|
+
//
|
|
6
|
+
// This script runs `sf code-analyzer rules --view detail` with a targeted
|
|
7
|
+
// selector and parses the output to extract rule details including description,
|
|
8
|
+
// severity, tags, and documentation resources.
|
|
9
|
+
//
|
|
10
|
+
// The CLI output format is:
|
|
11
|
+
// === 1. RuleName
|
|
12
|
+
// severity: 2 (High)
|
|
13
|
+
// engine: pmd
|
|
14
|
+
// tags: Recommended, Security, Apex
|
|
15
|
+
// resource: https://...
|
|
16
|
+
// description: Some description text
|
|
17
|
+
|
|
18
|
+
const { execSync } = require("child_process");
|
|
19
|
+
|
|
20
|
+
function printUsage() {
|
|
21
|
+
console.error(`Usage: node describe-rule.js <rule-name> [--engine <engine>]
|
|
22
|
+
|
|
23
|
+
Arguments:
|
|
24
|
+
<rule-name> The rule name to look up (case-insensitive partial match)
|
|
25
|
+
|
|
26
|
+
Options:
|
|
27
|
+
--engine <engine> Narrow lookup to a specific engine (pmd, eslint, cpd, etc.)
|
|
28
|
+
|
|
29
|
+
Examples:
|
|
30
|
+
node describe-rule.js ApexCRUDViolation
|
|
31
|
+
node describe-rule.js ApexCRUDViolation --engine pmd
|
|
32
|
+
node describe-rule.js no-var --engine eslint
|
|
33
|
+
node describe-rule.js OperationWithLimitsInLoop`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Parse CLI arguments
|
|
38
|
+
const args = process.argv.slice(2);
|
|
39
|
+
if (args.length < 1 || args[0] === "--help" || args[0] === "-h") {
|
|
40
|
+
printUsage();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const ruleName = args[0];
|
|
44
|
+
let engine = null;
|
|
45
|
+
|
|
46
|
+
for (let i = 1; i < args.length; i++) {
|
|
47
|
+
if (args[i] === "--engine" && args[i + 1]) {
|
|
48
|
+
engine = args[++i].toLowerCase();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Build the rule selector for the lookup
|
|
53
|
+
const selector = engine ? `${engine}:${ruleName}` : ruleName;
|
|
54
|
+
|
|
55
|
+
// Run `sf code-analyzer rules` with --view detail to get full rule info
|
|
56
|
+
let rawOutput;
|
|
57
|
+
try {
|
|
58
|
+
const cmd = `sf code-analyzer rules --rule-selector "${selector}" --view detail 2>&1`;
|
|
59
|
+
rawOutput = execSync(cmd, {
|
|
60
|
+
encoding: "utf8",
|
|
61
|
+
timeout: 60000,
|
|
62
|
+
maxBuffer: 2 * 1024 * 1024,
|
|
63
|
+
});
|
|
64
|
+
} catch (err) {
|
|
65
|
+
// execSync throws on non-zero exit, but we still want the output
|
|
66
|
+
rawOutput = err.stdout || err.stderr || (err.output && err.output.join("")) || "";
|
|
67
|
+
if (!rawOutput) {
|
|
68
|
+
console.log(JSON.stringify({
|
|
69
|
+
status: "error",
|
|
70
|
+
message: `Failed to run sf code-analyzer rules: ${err.message}`,
|
|
71
|
+
}));
|
|
72
|
+
process.exit(0);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Parse the detail view output
|
|
77
|
+
// Format: === N. RuleName\n key: value\n key: value\n
|
|
78
|
+
const rules = parseDetailOutput(rawOutput);
|
|
79
|
+
|
|
80
|
+
if (rules.length === 0) {
|
|
81
|
+
// Try grep fallback for partial/substring match
|
|
82
|
+
const grepResult = tryGrepFallback(ruleName, engine);
|
|
83
|
+
if (grepResult) {
|
|
84
|
+
console.log(JSON.stringify(grepResult));
|
|
85
|
+
process.exit(0);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Try fuzzy match as final fallback (catches typos like "Violtion" → "Violation")
|
|
89
|
+
const fuzzyResult = tryFuzzyFallback(ruleName, engine);
|
|
90
|
+
if (fuzzyResult) {
|
|
91
|
+
console.log(JSON.stringify(fuzzyResult));
|
|
92
|
+
process.exit(0);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
console.log(JSON.stringify({
|
|
96
|
+
status: "not_found",
|
|
97
|
+
message: `Rule "${ruleName}" not found${engine ? ` in engine "${engine}"` : ""}. Verify the rule name with: sf code-analyzer rules --rule-selector ${engine || "all"} 2>&1 | grep -i "${ruleName}"`,
|
|
98
|
+
}));
|
|
99
|
+
process.exit(0);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Find exact match (case-insensitive)
|
|
103
|
+
const exactMatch = rules.find(
|
|
104
|
+
(r) => r.name.toLowerCase() === ruleName.toLowerCase()
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
if (exactMatch) {
|
|
108
|
+
console.log(JSON.stringify({
|
|
109
|
+
status: "success",
|
|
110
|
+
rule: exactMatch,
|
|
111
|
+
}));
|
|
112
|
+
} else if (rules.length === 1) {
|
|
113
|
+
// Single result, use it
|
|
114
|
+
console.log(JSON.stringify({
|
|
115
|
+
status: "success",
|
|
116
|
+
rule: rules[0],
|
|
117
|
+
}));
|
|
118
|
+
} else {
|
|
119
|
+
// Multiple matches
|
|
120
|
+
console.log(JSON.stringify({
|
|
121
|
+
status: "multiple_matches",
|
|
122
|
+
message: `Found ${rules.length} rules matching "${ruleName}":`,
|
|
123
|
+
candidates: rules.map((r) => ({
|
|
124
|
+
name: r.name,
|
|
125
|
+
engine: r.engine,
|
|
126
|
+
severity: r.severity,
|
|
127
|
+
tags: r.tags.join(", "),
|
|
128
|
+
})),
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Parse the `sf code-analyzer rules --view detail` output format.
|
|
134
|
+
*
|
|
135
|
+
* Expected format:
|
|
136
|
+
* === 1. RuleName
|
|
137
|
+
* severity: 2 (High)
|
|
138
|
+
* engine: pmd
|
|
139
|
+
* tags: Recommended, Security, Apex
|
|
140
|
+
* resource: https://pmd.github.io/...
|
|
141
|
+
* description: Validates that CRUD permissions...
|
|
142
|
+
*/
|
|
143
|
+
function parseDetailOutput(output) {
|
|
144
|
+
const rules = [];
|
|
145
|
+
const lines = output.split("\n");
|
|
146
|
+
|
|
147
|
+
let currentRule = null;
|
|
148
|
+
|
|
149
|
+
for (let i = 0; i < lines.length; i++) {
|
|
150
|
+
const line = lines[i];
|
|
151
|
+
|
|
152
|
+
// Match rule header: === N. RuleName (must have a number prefix)
|
|
153
|
+
// Skip "=== Summary" which is the footer section
|
|
154
|
+
const headerMatch = line.match(/^===\s+(\d+)\.\s+(.+)$/);
|
|
155
|
+
if (headerMatch) {
|
|
156
|
+
if (currentRule && currentRule.name) {
|
|
157
|
+
rules.push(currentRule);
|
|
158
|
+
}
|
|
159
|
+
currentRule = {
|
|
160
|
+
name: headerMatch[2].trim(),
|
|
161
|
+
engine: "unknown",
|
|
162
|
+
severity: "unknown",
|
|
163
|
+
tags: [],
|
|
164
|
+
description: "",
|
|
165
|
+
resources: [],
|
|
166
|
+
};
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Skip lines if no current rule context
|
|
171
|
+
if (!currentRule) continue;
|
|
172
|
+
|
|
173
|
+
// Match key-value pairs (indented with spaces):
|
|
174
|
+
// severity: 3 (Moderate)
|
|
175
|
+
// engine: eslint
|
|
176
|
+
// tags: Recommended, BestPractices, JavaScript
|
|
177
|
+
// resource: https://...
|
|
178
|
+
// description: Some text here
|
|
179
|
+
const kvMatch = line.match(/^\s{2,}(\w+):\s+(.+)$/);
|
|
180
|
+
if (kvMatch) {
|
|
181
|
+
const key = kvMatch[1].toLowerCase();
|
|
182
|
+
const value = kvMatch[2].trim();
|
|
183
|
+
|
|
184
|
+
switch (key) {
|
|
185
|
+
case "severity":
|
|
186
|
+
currentRule.severity = value;
|
|
187
|
+
break;
|
|
188
|
+
case "engine":
|
|
189
|
+
currentRule.engine = value;
|
|
190
|
+
break;
|
|
191
|
+
case "tags":
|
|
192
|
+
currentRule.tags = value.split(",").map((t) => t.trim()).filter(Boolean);
|
|
193
|
+
break;
|
|
194
|
+
case "resource":
|
|
195
|
+
currentRule.resources.push(value);
|
|
196
|
+
break;
|
|
197
|
+
case "description":
|
|
198
|
+
currentRule.description = value;
|
|
199
|
+
// Description may continue on next lines (indented further)
|
|
200
|
+
while (
|
|
201
|
+
i + 1 < lines.length &&
|
|
202
|
+
lines[i + 1].match(/^\s{14,}/) &&
|
|
203
|
+
!lines[i + 1].match(/^\s{2,}\w+:/)
|
|
204
|
+
) {
|
|
205
|
+
i++;
|
|
206
|
+
currentRule.description += " " + lines[i].trim();
|
|
207
|
+
}
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Push the last rule
|
|
214
|
+
if (currentRule && currentRule.name) {
|
|
215
|
+
rules.push(currentRule);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return rules;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Fallback: grep the full rule list for partial matches
|
|
223
|
+
*/
|
|
224
|
+
function tryGrepFallback(ruleName, engine) {
|
|
225
|
+
try {
|
|
226
|
+
const sel = engine || "Recommended";
|
|
227
|
+
const cmd = `sf code-analyzer rules --rule-selector "${sel}" 2>&1 | grep -i "${ruleName}"`;
|
|
228
|
+
const grepOutput = execSync(cmd, {
|
|
229
|
+
encoding: "utf8",
|
|
230
|
+
timeout: 60000,
|
|
231
|
+
maxBuffer: 2 * 1024 * 1024,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
if (!grepOutput.trim()) return null;
|
|
235
|
+
|
|
236
|
+
// Parse table output lines
|
|
237
|
+
// Format: index name engine severity tags
|
|
238
|
+
const candidates = grepOutput
|
|
239
|
+
.trim()
|
|
240
|
+
.split("\n")
|
|
241
|
+
.filter((line) => line.trim() && !line.startsWith("─") && !line.startsWith("="))
|
|
242
|
+
.slice(0, 10)
|
|
243
|
+
.map((line) => {
|
|
244
|
+
const parts = line.trim().split(/\s{2,}/);
|
|
245
|
+
// Try to identify which part is the rule name (usually index 1 after the row number)
|
|
246
|
+
if (parts.length >= 4) {
|
|
247
|
+
return {
|
|
248
|
+
name: parts[1] || parts[0],
|
|
249
|
+
engine: parts[2] || "unknown",
|
|
250
|
+
severity: parts[3] || "unknown",
|
|
251
|
+
tags: parts[4] || "",
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
return { name: line.trim(), engine: "unknown", severity: "unknown", tags: "" };
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
if (candidates.length === 0) return null;
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
status: "multiple_matches",
|
|
261
|
+
message: `Rule "${ruleName}" not found as exact match. Found ${candidates.length} potential matches:`,
|
|
262
|
+
candidates,
|
|
263
|
+
};
|
|
264
|
+
} catch (err) {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Fuzzy fallback: get all rule names and find closest matches by edit distance.
|
|
271
|
+
* Catches typos like "ApexCRUDVioltion" → "ApexCRUDViolation"
|
|
272
|
+
*/
|
|
273
|
+
function tryFuzzyFallback(ruleName, engine) {
|
|
274
|
+
try {
|
|
275
|
+
const sel = engine || "Recommended";
|
|
276
|
+
const cmd = `sf code-analyzer rules --rule-selector "${sel}" 2>&1`;
|
|
277
|
+
const output = execSync(cmd, {
|
|
278
|
+
encoding: "utf8",
|
|
279
|
+
timeout: 60000,
|
|
280
|
+
maxBuffer: 2 * 1024 * 1024,
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// Extract rule names from table output
|
|
284
|
+
// Lines with rule data have: index name engine severity tags
|
|
285
|
+
const ruleNames = [];
|
|
286
|
+
const ruleInfo = {};
|
|
287
|
+
output.split("\n").forEach((line) => {
|
|
288
|
+
const parts = line.trim().split(/\s{2,}/);
|
|
289
|
+
if (parts.length >= 4 && /^\d+$/.test(parts[0])) {
|
|
290
|
+
const name = parts[1];
|
|
291
|
+
ruleNames.push(name);
|
|
292
|
+
ruleInfo[name] = {
|
|
293
|
+
name: name,
|
|
294
|
+
engine: parts[2] || "unknown",
|
|
295
|
+
severity: parts[3] || "unknown",
|
|
296
|
+
tags: parts[4] || "",
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
if (ruleNames.length === 0) return null;
|
|
302
|
+
|
|
303
|
+
// Score each rule by edit distance to the query
|
|
304
|
+
const queryLower = ruleName.toLowerCase();
|
|
305
|
+
const scored = ruleNames
|
|
306
|
+
.map((name) => ({
|
|
307
|
+
name,
|
|
308
|
+
distance: levenshtein(queryLower, name.toLowerCase()),
|
|
309
|
+
// Also check if query is a subsequence (handles missing chars)
|
|
310
|
+
containsSubseq: isSubsequence(queryLower, name.toLowerCase()),
|
|
311
|
+
}))
|
|
312
|
+
.filter((r) => {
|
|
313
|
+
// Only include if distance is reasonable (within 30% of query length)
|
|
314
|
+
const maxDistance = Math.max(3, Math.floor(ruleName.length * 0.3));
|
|
315
|
+
return r.distance <= maxDistance || r.containsSubseq;
|
|
316
|
+
})
|
|
317
|
+
.sort((a, b) => a.distance - b.distance)
|
|
318
|
+
.slice(0, 5);
|
|
319
|
+
|
|
320
|
+
if (scored.length === 0) return null;
|
|
321
|
+
|
|
322
|
+
const candidates = scored.map((s) => ({
|
|
323
|
+
...ruleInfo[s.name],
|
|
324
|
+
distance: s.distance,
|
|
325
|
+
}));
|
|
326
|
+
|
|
327
|
+
// If the best match is very close (distance <= 2), mark as likely match
|
|
328
|
+
const best = scored[0];
|
|
329
|
+
if (best.distance <= 2) {
|
|
330
|
+
return {
|
|
331
|
+
status: "multiple_matches",
|
|
332
|
+
message: `Rule "${ruleName}" not found. Did you mean "${best.name}"? (${best.distance} character${best.distance === 1 ? "" : "s"} different)`,
|
|
333
|
+
candidates,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return {
|
|
338
|
+
status: "multiple_matches",
|
|
339
|
+
message: `Rule "${ruleName}" not found. Closest matches by name similarity:`,
|
|
340
|
+
candidates,
|
|
341
|
+
};
|
|
342
|
+
} catch (err) {
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Levenshtein edit distance between two strings
|
|
349
|
+
*/
|
|
350
|
+
function levenshtein(a, b) {
|
|
351
|
+
const m = a.length;
|
|
352
|
+
const n = b.length;
|
|
353
|
+
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
|
|
354
|
+
|
|
355
|
+
for (let i = 0; i <= m; i++) dp[i][0] = i;
|
|
356
|
+
for (let j = 0; j <= n; j++) dp[0][j] = j;
|
|
357
|
+
|
|
358
|
+
for (let i = 1; i <= m; i++) {
|
|
359
|
+
for (let j = 1; j <= n; j++) {
|
|
360
|
+
if (a[i - 1] === b[j - 1]) {
|
|
361
|
+
dp[i][j] = dp[i - 1][j - 1];
|
|
362
|
+
} else {
|
|
363
|
+
dp[i][j] = 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return dp[m][n];
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Check if 'query' is a subsequence of 'target' (handles missing chars)
|
|
373
|
+
* e.g., "CRUDVioltion" is a subsequence of "CRUDViolation"
|
|
374
|
+
*/
|
|
375
|
+
function isSubsequence(query, target) {
|
|
376
|
+
let qi = 0;
|
|
377
|
+
for (let ti = 0; ti < target.length && qi < query.length; ti++) {
|
|
378
|
+
if (query[qi] === target[ti]) qi++;
|
|
379
|
+
}
|
|
380
|
+
// Consider it a match if at least 80% of query chars appear in order
|
|
381
|
+
return qi >= query.length * 0.8;
|
|
382
|
+
}
|