feat(humanizer): forbidden-words lint runner + test wrapper (SC-3) [skip-docs]
Step 8 of v5.1.0 humanizer Wave 4. Adds tests/lint-default-output.mjs
runner and tests/scanners/lint-default-output.test.mjs wrapper that
exercise SC-3 against the 6 prose CLIs (scan-orchestrator, posture,
token-hotspots-cli, plugin-health-scanner, drift-cli, fix-cli) running
in default (humanized) mode against tests/fixtures/marketplace-medium.
Lint scope is stderr only — JSON envelope keys ("scanner", "severity")
are structural, not prose. Humanized prose fields embedded inside JSON
are already covered by tests/lib/humanizer-data.test.mjs tier1/tier3
checks. Code references inside backticks pass the lint
(stripBacktickSpans) so technical identifiers can appear when wrapped.
Default-mode prose fixes to land lint at zero violations:
- scan-orchestrator: top banner switches to "Config-Audit v2.2.0" and
per-scanner progress wraps "[XXX] Label" in backticks. --raw and
--json paths preserve the v5.0.0 verbatim banner via new
opts.humanizedProgress flag on runAllScanners.
- plugin-health-scanner: top banner switches to "Plugin Health v2.1.0"
in default mode; --raw/--json keep "Plugin Health Scanner v2.1.0".
- scoring.mjs generateHealthScorecard humanized branch: area names
(CLAUDE.md, Hooks, MCP, Settings, Rules, Imports, Conflicts, Token
Efficiency, Plugin Hygiene) are wrapped in backticks; dot-padding
compensates so column alignment matches v5.0.0 layout.
- posture / drift-cli / fix-cli: thread humanizedProgress flag through
their runAllScanners calls so default mode emits humanized progress
and --raw/--json preserve the v5.0.0 stderr snapshot.
Test infrastructure only — user-facing docs land in Wave 5/6 once
commands and agents consume the humanized payload.
Tests: 735 to 736 (+1 SC-3 wrapper). Full suite passes.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
ebe1890762
commit
c5c937e94e
8 changed files with 259 additions and 13 deletions
|
|
@ -75,7 +75,7 @@ async function main() {
|
|||
process.stderr.write(`Saving baseline "${baselineName}" for ${resolve(targetPath)}\n\n`);
|
||||
}
|
||||
|
||||
const envelope = await runAllScanners(targetPath, { includeGlobal });
|
||||
const envelope = await runAllScanners(targetPath, { includeGlobal, humanizedProgress: !jsonMode && !rawMode });
|
||||
const result = await saveBaseline(envelope, baselineName);
|
||||
|
||||
if (jsonMode || rawMode) {
|
||||
|
|
@ -107,7 +107,10 @@ async function main() {
|
|||
}
|
||||
|
||||
// Run current scan
|
||||
const current = await runAllScanners(targetPath, { includeGlobal });
|
||||
const current = await runAllScanners(targetPath, {
|
||||
includeGlobal,
|
||||
humanizedProgress: !jsonMode && !rawMode,
|
||||
});
|
||||
|
||||
// Diff
|
||||
const diff = diffEnvelopes(baseline, current);
|
||||
|
|
|
|||
|
|
@ -49,7 +49,10 @@ async function main() {
|
|||
}
|
||||
|
||||
// 1. Run all scanners
|
||||
const envelope = await runAllScanners(targetPath, { includeGlobal });
|
||||
const envelope = await runAllScanners(targetPath, {
|
||||
includeGlobal,
|
||||
humanizedProgress: !machineMode,
|
||||
});
|
||||
|
||||
// 2. Plan fixes
|
||||
const { fixes, skipped, manual } = planFixes(envelope);
|
||||
|
|
|
|||
|
|
@ -362,14 +362,23 @@ export function generateHealthScorecard(areaScores, opportunityCount, options =
|
|||
lines.push(humanized ? ' Area scores' : ' Area Scores');
|
||||
lines.push(' ───────────');
|
||||
|
||||
// Format areas in 2-column layout (quality areas only)
|
||||
// Format areas in 2-column layout (quality areas only).
|
||||
// In humanized mode, area names are wrapped in backticks so SC-3 can treat
|
||||
// them as code references (technical identifiers like CLAUDE.md, MCP, Hooks
|
||||
// are tier3 jargon outside backtick spans). Padding compensates for the
|
||||
// two extra characters so column alignment matches the v5.0.0 layout.
|
||||
const padBase = humanized ? 22 : 20;
|
||||
const padCol = humanized ? 37 : 35;
|
||||
const labelOf = (a) => (humanized ? `\`${a.name}\`` : a.name);
|
||||
for (let i = 0; i < qualityAreas.length; i += 2) {
|
||||
const left = qualityAreas[i];
|
||||
const right = qualityAreas[i + 1];
|
||||
const leftStr = ` ${left.name} ${'.'.repeat(Math.max(1, 20 - left.name.length))} ${left.grade} (${left.score})`;
|
||||
const leftLabel = labelOf(left);
|
||||
const leftStr = ` ${leftLabel} ${'.'.repeat(Math.max(1, padBase - leftLabel.length))} ${left.grade} (${left.score})`;
|
||||
if (right) {
|
||||
const rightStr = `${right.name} ${'.'.repeat(Math.max(1, 20 - right.name.length))} ${right.grade} (${right.score})`;
|
||||
lines.push(`${leftStr.padEnd(35)}${rightStr}`);
|
||||
const rightLabel = labelOf(right);
|
||||
const rightStr = `${rightLabel} ${'.'.repeat(Math.max(1, padBase - rightLabel.length))} ${right.grade} (${right.score})`;
|
||||
lines.push(`${leftStr.padEnd(padCol)}${rightStr}`);
|
||||
} else {
|
||||
lines.push(leftStr);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -433,7 +433,8 @@ async function main() {
|
|||
}
|
||||
}
|
||||
|
||||
process.stderr.write(`Plugin Health Scanner v2.1.0\n`);
|
||||
const humanizedProgress = !jsonMode && !rawMode;
|
||||
process.stderr.write(humanizedProgress ? `Plugin Health v2.1.0\n` : `Plugin Health Scanner v2.1.0\n`);
|
||||
process.stderr.write(`Target: ${resolve(targetPath)}\n\n`);
|
||||
|
||||
const result = await scan(targetPath);
|
||||
|
|
|
|||
|
|
@ -83,7 +83,13 @@ async function main() {
|
|||
}
|
||||
|
||||
const filterFixtures = !args.includes('--include-fixtures');
|
||||
const result = await runPosture(targetPath, { includeGlobal, fullMachine, filterFixtures });
|
||||
const humanizedProgress = !jsonMode && !rawMode;
|
||||
const result = await runPosture(targetPath, {
|
||||
includeGlobal,
|
||||
fullMachine,
|
||||
filterFixtures,
|
||||
humanizedProgress,
|
||||
});
|
||||
|
||||
// stdout JSON path: --json and --raw both write the v5.0.0-shape result
|
||||
// (byte-identical). Default mode writes nothing to stdout.
|
||||
|
|
|
|||
|
|
@ -101,7 +101,10 @@ export async function runAllScanners(targetPath, opts = {}) {
|
|||
const result = await scanner.fn(resolvedPath, discovery);
|
||||
results.push(result);
|
||||
const count = result.findings.length;
|
||||
process.stderr.write(` [${scanner.name}] ${scanner.label}: ${count} finding(s) (${Date.now() - scanStart}ms)\n`);
|
||||
const label = opts.humanizedProgress
|
||||
? `\`[${scanner.name}] ${scanner.label}\``
|
||||
: `[${scanner.name}] ${scanner.label}`;
|
||||
process.stderr.write(` ${label}: ${count} finding(s) (${Date.now() - scanStart}ms)\n`);
|
||||
} catch (err) {
|
||||
results.push({
|
||||
scanner: scanner.name,
|
||||
|
|
@ -112,7 +115,10 @@ export async function runAllScanners(targetPath, opts = {}) {
|
|||
counts: { critical: 0, high: 0, medium: 0, low: 0, info: 0 },
|
||||
error: err.message,
|
||||
});
|
||||
process.stderr.write(` [${scanner.name}] ${scanner.label}: ERROR — ${err.message}\n`);
|
||||
const label = opts.humanizedProgress
|
||||
? `\`[${scanner.name}] ${scanner.label}\``
|
||||
: `[${scanner.name}] ${scanner.label}`;
|
||||
process.stderr.write(` ${label}: ERROR — ${err.message}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -218,12 +224,19 @@ async function main() {
|
|||
const jsonMode = args.includes('--json');
|
||||
const rawMode = args.includes('--raw');
|
||||
|
||||
process.stderr.write(`Config-Audit Scanner v2.2.0\n`);
|
||||
const humanizedProgress = !jsonMode && !rawMode;
|
||||
process.stderr.write(humanizedProgress ? `Config-Audit v2.2.0\n` : `Config-Audit Scanner v2.2.0\n`);
|
||||
process.stderr.write(`Target: ${resolve(targetPath)}\n`);
|
||||
process.stderr.write(`Scope: ${fullMachine ? 'full-machine' : includeGlobal ? 'global' : 'project'}\n`);
|
||||
process.stderr.write(`Fixtures: ${filterFixtures ? 'excluded' : 'included'}\n\n`);
|
||||
|
||||
const result = await runAllScanners(targetPath, { includeGlobal, fullMachine, suppress, filterFixtures });
|
||||
const result = await runAllScanners(targetPath, {
|
||||
includeGlobal,
|
||||
fullMachine,
|
||||
suppress,
|
||||
filterFixtures,
|
||||
humanizedProgress,
|
||||
});
|
||||
|
||||
// Default mode runs the humanizer; --json and --raw bypass for v5.0.0 byte-equal output.
|
||||
const output = (jsonMode || rawMode) ? result : humanizeEnvelope(result);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue