All AEM as a Cloud Service skills: component development, Workflow model design, development, triggering, launchers, debugging, and triaging, Dispatcher config authoring, advisory, incident response, performance tuning, and security hardening, Rapid Development Environment (RDE) deployment, log inspection, snapshots, and troubleshooting via `aio aem rde`, Jackrabbit Oak query/index tuning (property, Lucene index definitions), and extracting/inventorying JCR queries (XPath, SQL2, QueryBuilder) across a codebase.
85%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Low
Low-risk findings worth noting
/**
* Analyzer Runner
*
* Thin wrapper around the code-assessment deterministic analyzer
* (`code-assessment/scripts/analyze.sh`). Shells out once over a workspace,
* parses the `{ findings, warnings }` JSON, and normalizes the analyzer's
* pattern slugs into the migration skill's canonical pattern taxonomy.
*
* This is the "analyzer (local fallback)" tier of the runbook cascade — it can
* find every BPA-addressed pattern (including `replication`, which the BPA CSV
* parser cannot map) directly from source, with no BPA report or MCP needed.
*/
'use strict';
const path = require('path');
const fs = require('fs');
const { execFileSync } = require('child_process');
// Default location of the code-assessment analyzer relative to this file:
// .../skills/migration/scripts/analyzer-runner.js
// .../skills/code-assessment/scripts/analyze.sh
const DEFAULT_ANALYZE_SCRIPT = path.resolve(__dirname, '../../code-assessment/scripts/analyze.sh');
// analyzer pattern slug → migration canonical pattern id.
// event-migration covers both JCR EventListener and OSGi EventHandler, which the
// migration apply flow routes to the same guide — so we keep them merged.
const ANALYZER_TO_CANONICAL = {
'scheduler': 'scheduler',
'resource-change-listener': 'resourceChangeListener',
'event-migration': 'event-migration',
'replication': 'replication',
'asset-manager': 'assetApi',
};
/** Collapse a (possibly multi-line) code snippet into a single, table-safe cell. */
function sanitizeSnippet(snippet, maxLen = 120) {
const oneLine = (snippet || '').replace(/\s+/g, ' ').trim();
return oneLine.length > maxLen ? `${oneLine.slice(0, maxLen - 1)}…` : oneLine;
}
/**
* Is the analyzer usable? Requires the analyze.sh script to exist on disk.
* (A missing JDK surfaces only at run time as a non-zero exit — handled in run().)
*/
function isAnalyzerAvailable(analyzeScript = DEFAULT_ANALYZE_SCRIPT) {
return fs.existsSync(analyzeScript);
}
/**
* Run the analyzer once over `workspaceRoot` and return normalized findings.
*
* @returns {{
* ok: boolean,
* findingsByPattern: Record<string, Array<{location: string, detail: string, severity: string}>>,
* rawFindingsByPattern: Record<string, Array<{pattern: string, file: string, line: number, snippet: string}>>,
* warnings: string[],
* error?: string,
* }}
* `findingsByPattern` is the display shape, keyed by canonical migration
* pattern id, for rendering the runbook markdown. `rawFindingsByPattern`
* preserves the analyzer's own `{pattern, file, line, snippet}` shape
* verbatim (same keys) so the apply handoff can hand exact, re-locatable
* findings to code-assessment without re-running the analyzer. `ok:false`
* means the analyzer could not run (missing script, no JDK, compile/parse
* failure) — the caller should treat the analyzer tier as unavailable.
*/
function runAnalyzer(workspaceRoot, options = {}) {
const { analyzeScript = DEFAULT_ANALYZE_SCRIPT } = options;
if (!isAnalyzerAvailable(analyzeScript)) {
return { ok: false, findingsByPattern: {}, rawFindingsByPattern: {}, warnings: [], error: `analyzer not found at ${analyzeScript}` };
}
let raw;
try {
raw = execFileSync('bash', [analyzeScript, workspaceRoot], {
encoding: 'utf8',
maxBuffer: 64 * 1024 * 1024,
// Cap runtime so a hung analyze.sh (e.g. a wedged JVM) can't block runbook
// generation forever — a timeout throws, and the catch below returns
// ok:false so the caller falls through to the LLM-scan tier.
timeout: 5 * 60 * 1000,
stdio: ['ignore', 'pipe', 'pipe'],
});
} catch (err) {
// exit 3 (no JDK), 5 (compile error), etc. — analyzer tier unavailable.
const stderr = (err.stderr || '').toString().trim();
return {
ok: false,
findingsByPattern: {},
rawFindingsByPattern: {},
warnings: [],
error: stderr || `analyzer exited with code ${err.status}`,
};
}
let parsed;
try {
parsed = JSON.parse(raw);
} catch (e) {
return { ok: false, findingsByPattern: {}, rawFindingsByPattern: {}, warnings: [], error: `could not parse analyzer output: ${e.message}` };
}
const findingsByPattern = {};
const rawFindingsByPattern = {};
for (const f of parsed.findings || []) {
const canonical = ANALYZER_TO_CANONICAL[f.pattern];
if (!canonical) continue; // skip non-BPA patterns (inject-in-sling-model, outdated-dependencies, …)
(findingsByPattern[canonical] = findingsByPattern[canonical] || []).push({
location: f.line ? `${f.file}:${f.line}` : f.file,
detail: sanitizeSnippet(f.snippet),
severity: 'high',
});
(rawFindingsByPattern[canonical] = rawFindingsByPattern[canonical] || []).push({
pattern: f.pattern,
file: f.file,
line: f.line,
snippet: f.snippet,
});
}
return { ok: true, findingsByPattern, rawFindingsByPattern, warnings: parsed.warnings || [] };
}
module.exports = {
runAnalyzer,
isAnalyzerAvailable,
ANALYZER_TO_CANONICAL,
DEFAULT_ANALYZE_SCRIPT,
};.tessl-plugin
skills
aem-rde
aem-workflow
code-assessment
content-distribution
create-component
assets
references
dispatcher
incident-response
references
dispatcher-foundation
performance-tuning
references
dispatcher-foundation
security-hardening
references
dispatcher-foundation
technical-advisory
references
dispatcher-foundation
workflow-orchestrator
references
dispatcher-foundation
ensure-agents-md
references
migration
references
dispatcher
legacy-ui
template-modernization
validation
scripts
dispatcher-tool
validate-migration