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:
Kjell Tore Guttormsen 2026-05-01 18:11:15 +02:00
commit c5c937e94e
8 changed files with 259 additions and 13 deletions

View file

@ -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);