@dailephd/my-dev-kit-lab 0.2.2 → 0.3.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/README.md +3 -2
- package/dist/scripts/audits/runAudit.js +101 -0
- package/dist/src/audits/codeRot/codeRotDetectors.js +17 -0
- package/dist/src/audits/codeRot/detectors/architectureDriftDetector.js +271 -0
- package/dist/src/audits/codeRot/detectors/crossPlatformRotDetector.js +214 -0
- package/dist/src/audits/codeRot/detectors/deadCodeCandidateDetector.js +344 -0
- package/dist/src/audits/codeRot/detectors/dependencyEnvironmentRotDetector.js +244 -0
- package/dist/src/audits/codeRot/detectors/docsCodeMismatchDetector.js +219 -0
- package/dist/src/audits/codeRot/detectors/duplicateImplementationDetector.js +273 -0
- package/dist/src/audits/codeRot/detectors/packageReleaseRotDetector.js +325 -0
- package/dist/src/audits/codeRot/detectors/securityValidationAssumptionRotDetector.js +385 -0
- package/dist/src/audits/codeRot/detectors/staleCommandReferenceDetector.js +135 -0
- package/dist/src/audits/codeRot/detectors/testRotDetector.js +348 -0
- package/dist/src/audits/codeRot/utils/boundedRead.js +23 -0
- package/dist/src/audits/codeRot/utils/docClaimPatterns.js +62 -0
- package/dist/src/audits/codeRot/utils/docCommandReferences.js +64 -0
- package/dist/src/audits/codeRot/utils/filePatternUtils.js +47 -0
- package/dist/src/audits/codeRot/utils/issueFactories.js +38 -0
- package/dist/src/audits/codeRot/utils/textLines.js +12 -0
- package/dist/src/audits/core/auditConfig.js +125 -0
- package/dist/src/audits/core/auditExitCode.js +75 -0
- package/dist/src/audits/core/auditIssue.js +20 -0
- package/dist/src/audits/core/auditRegistry.js +40 -0
- package/dist/src/audits/core/auditRunner.js +71 -0
- package/dist/src/audits/core/auditTarget.js +19 -0
- package/dist/src/audits/core/auditTypes.js +30 -0
- package/dist/src/audits/core/projectInventory.js +308 -0
- package/dist/src/audits/core/sourceOfTruth.js +339 -0
- package/dist/src/audits/core/textLines.js +24 -0
- package/dist/src/audits/report/auditReportModel.js +241 -0
- package/dist/src/audits/report/auditReportPaths.js +13 -0
- package/dist/src/audits/report/renderAuditJsonReport.js +29 -0
- package/dist/src/audits/report/renderAuditTextReport.js +165 -0
- package/dist/src/audits/report/sanitizeAuditText.js +39 -0
- package/dist/src/audits/report/writeAuditReports.js +27 -0
- package/dist/src/core/resolveCommand.js +53 -2
- package/package.json +3 -2
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { AUDIT_FAIL_ON_THRESHOLDS, AUDIT_INCLUDE_AREAS, AUDIT_OUTPUT_FORMATS, AUDIT_TYPES, DEFAULT_AUDIT_FAIL_ON_THRESHOLD, DEFAULT_AUDIT_INCLUDE_AREAS, DEFAULT_AUDIT_OUTPUT_FORMATS, DEFAULT_AUDIT_TYPES, PLANNED_AUDIT_TYPES, } from "./auditTypes.js";
|
|
3
|
+
const FLAGS_WITH_VALUE = {
|
|
4
|
+
"--target": "target",
|
|
5
|
+
"--types": "types",
|
|
6
|
+
"--include": "include",
|
|
7
|
+
"--format": "format",
|
|
8
|
+
"--fail-on": "failOn",
|
|
9
|
+
"--out": "out",
|
|
10
|
+
};
|
|
11
|
+
export function parseAuditArgs(argv) {
|
|
12
|
+
const result = {};
|
|
13
|
+
for (let i = 0; i < argv.length; i++) {
|
|
14
|
+
const arg = argv[i];
|
|
15
|
+
const key = FLAGS_WITH_VALUE[arg];
|
|
16
|
+
if (!key)
|
|
17
|
+
continue;
|
|
18
|
+
if (i + 1 >= argv.length) {
|
|
19
|
+
throw new Error(`Missing value for ${arg}. Expected a value after ${arg}.`);
|
|
20
|
+
}
|
|
21
|
+
result[key] = argv[++i];
|
|
22
|
+
}
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
export function normalizeAuditConfig(raw, toolRoot) {
|
|
26
|
+
const types = parseTypesOption(raw.types);
|
|
27
|
+
const include = parseIncludeOption(raw.include);
|
|
28
|
+
const formats = parseFormatOption(raw.format);
|
|
29
|
+
const failOn = parseFailOnOption(raw.failOn);
|
|
30
|
+
const out = raw.out !== undefined ? resolveOutDir(raw.out) : defaultOutDir(toolRoot, types.value);
|
|
31
|
+
return {
|
|
32
|
+
targetPathArg: raw.target,
|
|
33
|
+
targetMode: raw.target === undefined ? "self" : "external",
|
|
34
|
+
types: types.value,
|
|
35
|
+
typesWereDefault: types.wasDefault,
|
|
36
|
+
include: include.value,
|
|
37
|
+
includeWereDefault: include.wasDefault,
|
|
38
|
+
formats: formats.value,
|
|
39
|
+
formatsWereDefault: formats.wasDefault,
|
|
40
|
+
failOn: failOn.value,
|
|
41
|
+
failOnWasDefault: failOn.wasDefault,
|
|
42
|
+
out,
|
|
43
|
+
outWasDefault: raw.out === undefined,
|
|
44
|
+
isDefaultRun: raw.target === undefined &&
|
|
45
|
+
raw.types === undefined &&
|
|
46
|
+
raw.include === undefined &&
|
|
47
|
+
raw.format === undefined &&
|
|
48
|
+
raw.failOn === undefined &&
|
|
49
|
+
raw.out === undefined,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function splitAndNormalizeList(raw, flagName) {
|
|
53
|
+
const parts = raw.split(",").map((p) => p.trim());
|
|
54
|
+
if (parts.some((p) => p.length === 0)) {
|
|
55
|
+
throw new Error(`Invalid ${flagName} value: empty entries are not allowed. Got "${raw}".`);
|
|
56
|
+
}
|
|
57
|
+
return parts;
|
|
58
|
+
}
|
|
59
|
+
function parseTypesOption(raw) {
|
|
60
|
+
if (raw === undefined) {
|
|
61
|
+
return { value: [...DEFAULT_AUDIT_TYPES], wasDefault: true };
|
|
62
|
+
}
|
|
63
|
+
const parts = splitAndNormalizeList(raw, "--types");
|
|
64
|
+
const unknown = parts.filter((p) => !AUDIT_TYPES.includes(p));
|
|
65
|
+
if (unknown.length > 0) {
|
|
66
|
+
throw new Error(`Invalid --types value(s): ${unknown.join(", ")}. Valid values are: ${AUDIT_TYPES.join(", ")}.`);
|
|
67
|
+
}
|
|
68
|
+
const planned = parts.filter((p) => PLANNED_AUDIT_TYPES.includes(p));
|
|
69
|
+
if (planned.length > 0) {
|
|
70
|
+
throw new Error(`--types value(s) planned but not implemented: ${planned.join(", ")}. Only "code-rot" is implemented in this version.`);
|
|
71
|
+
}
|
|
72
|
+
const unique = new Set(parts);
|
|
73
|
+
const ordered = AUDIT_TYPES.filter((id) => unique.has(id));
|
|
74
|
+
return { value: ordered, wasDefault: false };
|
|
75
|
+
}
|
|
76
|
+
function parseIncludeOption(raw) {
|
|
77
|
+
if (raw === undefined) {
|
|
78
|
+
return { value: [...DEFAULT_AUDIT_INCLUDE_AREAS], wasDefault: true };
|
|
79
|
+
}
|
|
80
|
+
const parts = splitAndNormalizeList(raw, "--include");
|
|
81
|
+
const invalid = parts.filter((p) => !AUDIT_INCLUDE_AREAS.includes(p));
|
|
82
|
+
if (invalid.length > 0) {
|
|
83
|
+
throw new Error(`Invalid --include value(s): ${invalid.join(", ")}. Valid values are: ${AUDIT_INCLUDE_AREAS.join(", ")}.`);
|
|
84
|
+
}
|
|
85
|
+
const unique = new Set(parts);
|
|
86
|
+
const ordered = AUDIT_INCLUDE_AREAS.filter((id) => unique.has(id));
|
|
87
|
+
return { value: ordered, wasDefault: false };
|
|
88
|
+
}
|
|
89
|
+
function parseFormatOption(raw) {
|
|
90
|
+
if (raw === undefined) {
|
|
91
|
+
return { value: [...DEFAULT_AUDIT_OUTPUT_FORMATS], wasDefault: true };
|
|
92
|
+
}
|
|
93
|
+
const parts = splitAndNormalizeList(raw, "--format");
|
|
94
|
+
const invalid = parts.filter((p) => !AUDIT_OUTPUT_FORMATS.includes(p));
|
|
95
|
+
if (invalid.length > 0) {
|
|
96
|
+
throw new Error(`Invalid --format value(s): ${invalid.join(", ")}. Valid values are: ${AUDIT_OUTPUT_FORMATS.join(", ")}.`);
|
|
97
|
+
}
|
|
98
|
+
const unique = new Set(parts);
|
|
99
|
+
const ordered = AUDIT_OUTPUT_FORMATS.filter((id) => unique.has(id));
|
|
100
|
+
return { value: ordered, wasDefault: false };
|
|
101
|
+
}
|
|
102
|
+
function parseFailOnOption(raw) {
|
|
103
|
+
if (raw === undefined) {
|
|
104
|
+
return { value: DEFAULT_AUDIT_FAIL_ON_THRESHOLD, wasDefault: true };
|
|
105
|
+
}
|
|
106
|
+
const value = raw.trim();
|
|
107
|
+
if (!AUDIT_FAIL_ON_THRESHOLDS.includes(value)) {
|
|
108
|
+
throw new Error(`Invalid --fail-on value: "${raw}". Valid values are: ${AUDIT_FAIL_ON_THRESHOLDS.join(", ")}.`);
|
|
109
|
+
}
|
|
110
|
+
return { value: value, wasDefault: false };
|
|
111
|
+
}
|
|
112
|
+
function resolveOutDir(raw) {
|
|
113
|
+
const trimmed = raw.trim();
|
|
114
|
+
if (trimmed.length === 0) {
|
|
115
|
+
throw new Error("Invalid --out value: path must not be empty.");
|
|
116
|
+
}
|
|
117
|
+
return path.resolve(trimmed);
|
|
118
|
+
}
|
|
119
|
+
function defaultOutDir(toolRoot, types) {
|
|
120
|
+
// Batch 1 only ever has "code-rot" as a valid resolved type, but this stays
|
|
121
|
+
// written generically so later batches (quality/security/project) don't
|
|
122
|
+
// need to touch this function, only extend the audit-type vocabulary.
|
|
123
|
+
const primaryType = types[0] ?? "code-rot";
|
|
124
|
+
return path.join(path.resolve(toolRoot), "reports", "audits", primaryType);
|
|
125
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// v0.3.0 Batch 1 — audit exit code policy.
|
|
3
|
+
//
|
|
4
|
+
// 0 = no issue met the --fail-on threshold (and no config/target/runtime
|
|
5
|
+
// error occurred).
|
|
6
|
+
// 1 = at least one issue met or exceeded the --fail-on threshold.
|
|
7
|
+
// 2 = invalid config, invalid target, or an audit runtime failure. This
|
|
8
|
+
// code is never returned by calculateAuditExitCode() below (a pure,
|
|
9
|
+
// issue-driven function) -- it is the caller's (scripts/audits/
|
|
10
|
+
// runAudit.ts) responsibility to catch parse/config/target errors and
|
|
11
|
+
// exit with AUDIT_EXIT_CODES.FATAL_ERROR directly, mirroring how
|
|
12
|
+
// scripts/security/validate.ts handles its own parse/target try/catch.
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
export const AUDIT_EXIT_CODES = {
|
|
15
|
+
SUCCESS: 0,
|
|
16
|
+
THRESHOLD_BREACHED: 1,
|
|
17
|
+
FATAL_ERROR: 2,
|
|
18
|
+
};
|
|
19
|
+
// "info" is a valid issue severity but is intentionally below every
|
|
20
|
+
// selectable --fail-on threshold (including "low") -- it can never breach.
|
|
21
|
+
const SEVERITY_RANK = {
|
|
22
|
+
info: 0,
|
|
23
|
+
low: 1,
|
|
24
|
+
medium: 2,
|
|
25
|
+
high: 3,
|
|
26
|
+
blocker: 4,
|
|
27
|
+
};
|
|
28
|
+
// "none" ranks above every severity so it can never be breached.
|
|
29
|
+
const THRESHOLD_RANK = {
|
|
30
|
+
low: 1,
|
|
31
|
+
medium: 2,
|
|
32
|
+
high: 3,
|
|
33
|
+
blocker: 4,
|
|
34
|
+
none: Number.POSITIVE_INFINITY,
|
|
35
|
+
};
|
|
36
|
+
export function issueBreachesFailOnThreshold(severity, failOn) {
|
|
37
|
+
if (failOn === "none")
|
|
38
|
+
return false;
|
|
39
|
+
return SEVERITY_RANK[severity] >= THRESHOLD_RANK[failOn];
|
|
40
|
+
}
|
|
41
|
+
// v0.3.0 Batch 5 — additive export. Report-building code (src/audits/report/
|
|
42
|
+
// auditReportModel.ts) needs the same severity ranking to compute a
|
|
43
|
+
// "highest severity present" summary field, and must not hardcode a second
|
|
44
|
+
// copy of this table (see Batch 5 spec 3.2). Signature/behavior of the two
|
|
45
|
+
// functions above is unchanged.
|
|
46
|
+
export function getHighestSeverity(issues) {
|
|
47
|
+
if (issues.length === 0)
|
|
48
|
+
return null;
|
|
49
|
+
let highest = issues[0].severity;
|
|
50
|
+
for (const issue of issues) {
|
|
51
|
+
if (SEVERITY_RANK[issue.severity] > SEVERITY_RANK[highest]) {
|
|
52
|
+
highest = issue.severity;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return highest;
|
|
56
|
+
}
|
|
57
|
+
// Pure function: given a final issue list and the configured --fail-on
|
|
58
|
+
// threshold, decides between SUCCESS and THRESHOLD_BREACHED. Never returns
|
|
59
|
+
// FATAL_ERROR -- see file header.
|
|
60
|
+
export function calculateAuditExitCode(issues, failOn) {
|
|
61
|
+
if (failOn === "none") {
|
|
62
|
+
return { exitCode: AUDIT_EXIT_CODES.SUCCESS, reason: "--fail-on none: findings never block the command." };
|
|
63
|
+
}
|
|
64
|
+
const breaching = issues.filter((issue) => issueBreachesFailOnThreshold(issue.severity, failOn));
|
|
65
|
+
if (breaching.length > 0) {
|
|
66
|
+
return {
|
|
67
|
+
exitCode: AUDIT_EXIT_CODES.THRESHOLD_BREACHED,
|
|
68
|
+
reason: `${breaching.length} issue(s) met or exceeded the --fail-on ${failOn} threshold.`,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
exitCode: AUDIT_EXIT_CODES.SUCCESS,
|
|
73
|
+
reason: `No issues met the --fail-on ${failOn} threshold.`,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const AUDIT_EVIDENCE_KINDS = [
|
|
2
|
+
"file",
|
|
3
|
+
"command",
|
|
4
|
+
"diff",
|
|
5
|
+
"reference",
|
|
6
|
+
"observation",
|
|
7
|
+
];
|
|
8
|
+
const MAX_EXCERPT_LENGTH = 400;
|
|
9
|
+
// Bounds an excerpt to a safe preview length so evidence never embeds huge
|
|
10
|
+
// file contents. Does not attempt secret redaction here — detectors that
|
|
11
|
+
// scan for secret-shaped content are responsible for redacting before this
|
|
12
|
+
// point, same division of responsibility documented in exploitEvidence.ts.
|
|
13
|
+
export function boundExcerpt(raw, maxLength = MAX_EXCERPT_LENGTH) {
|
|
14
|
+
if (raw.length <= maxLength)
|
|
15
|
+
return raw;
|
|
16
|
+
return `${raw.slice(0, maxLength)}... [truncated, ${raw.length} chars total]`;
|
|
17
|
+
}
|
|
18
|
+
export function makeAuditEvidence(evidence) {
|
|
19
|
+
return evidence.excerpt !== undefined ? { ...evidence, excerpt: boundExcerpt(evidence.excerpt) } : evidence;
|
|
20
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { STALE_COMMAND_REFERENCE_DETECTOR, DOCS_CODE_MISMATCH_DETECTOR, PACKAGE_RELEASE_ROT_DETECTOR, DUPLICATE_IMPLEMENTATION_DETECTOR, DEAD_CODE_CANDIDATE_DETECTOR, TEST_ROT_DETECTOR, ARCHITECTURE_DRIFT_DETECTOR, DEPENDENCY_ENVIRONMENT_ROT_DETECTOR, CROSS_PLATFORM_ROT_DETECTOR, SECURITY_VALIDATION_ASSUMPTION_ROT_DETECTOR, } from "../codeRot/codeRotDetectors.js";
|
|
2
|
+
// v0.3.0 Batch 3/4 — code-rot detectors. Batch 4 completes the full
|
|
3
|
+
// code-rot detector family planned in docs/ROADMAP.md's v0.3.0 section:
|
|
4
|
+
// stale command/workflow reference, docs/code mismatch, package/release
|
|
5
|
+
// rot (Batch 3), plus duplicate-implementation, dead-code, test-rot,
|
|
6
|
+
// architecture-drift, dependency/environment-rot, cross-platform-rot, and
|
|
7
|
+
// security/validation-assumption-rot (Batch 4).
|
|
8
|
+
export const DEFAULT_AUDIT_REGISTRY = createAuditRegistry([
|
|
9
|
+
STALE_COMMAND_REFERENCE_DETECTOR,
|
|
10
|
+
DOCS_CODE_MISMATCH_DETECTOR,
|
|
11
|
+
PACKAGE_RELEASE_ROT_DETECTOR,
|
|
12
|
+
DUPLICATE_IMPLEMENTATION_DETECTOR,
|
|
13
|
+
DEAD_CODE_CANDIDATE_DETECTOR,
|
|
14
|
+
TEST_ROT_DETECTOR,
|
|
15
|
+
ARCHITECTURE_DRIFT_DETECTOR,
|
|
16
|
+
DEPENDENCY_ENVIRONMENT_ROT_DETECTOR,
|
|
17
|
+
CROSS_PLATFORM_ROT_DETECTOR,
|
|
18
|
+
SECURITY_VALIDATION_ASSUMPTION_ROT_DETECTOR,
|
|
19
|
+
]);
|
|
20
|
+
// Validates a detector list has no duplicate ids. Later batches call this
|
|
21
|
+
// when assembling their own registries; Batch 1's empty default trivially
|
|
22
|
+
// passes.
|
|
23
|
+
export function createAuditRegistry(detectors) {
|
|
24
|
+
const seen = new Set();
|
|
25
|
+
for (const detector of detectors) {
|
|
26
|
+
if (seen.has(detector.id)) {
|
|
27
|
+
throw new Error(`Duplicate audit detector id: "${detector.id}".`);
|
|
28
|
+
}
|
|
29
|
+
seen.add(detector.id);
|
|
30
|
+
}
|
|
31
|
+
return detectors;
|
|
32
|
+
}
|
|
33
|
+
// Filters a registry down to detectors matching the selected audit types and
|
|
34
|
+
// at least one selected include area. Order is preserved from the input
|
|
35
|
+
// registry (deterministic, not re-sorted).
|
|
36
|
+
export function selectDetectors(registry, types, include) {
|
|
37
|
+
const typeSet = new Set(types);
|
|
38
|
+
const includeSet = new Set(include);
|
|
39
|
+
return registry.filter((detector) => typeSet.has(detector.auditType) && detector.supportedIncludeAreas.some((area) => includeSet.has(area)));
|
|
40
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { calculateAuditExitCode } from "./auditExitCode.js";
|
|
2
|
+
import { DEFAULT_AUDIT_REGISTRY, selectDetectors } from "./auditRegistry.js";
|
|
3
|
+
import { resolveAuditTarget } from "./auditTarget.js";
|
|
4
|
+
import { scanProjectInventory } from "./projectInventory.js";
|
|
5
|
+
import { collectSourceOfTruth } from "./sourceOfTruth.js";
|
|
6
|
+
export async function runAudit(options) {
|
|
7
|
+
const { config, toolRoot } = options;
|
|
8
|
+
const registry = options.registry ?? DEFAULT_AUDIT_REGISTRY;
|
|
9
|
+
const target = options.target ?? resolveAuditTarget(config.targetPathArg, toolRoot);
|
|
10
|
+
const inventory = scanProjectInventory(target.rootPath);
|
|
11
|
+
const sourceOfTruth = collectSourceOfTruth(target.rootPath, inventory);
|
|
12
|
+
const selected = selectDetectors(registry, config.types, config.include);
|
|
13
|
+
const ctx = { target, config, inventory, sourceOfTruth };
|
|
14
|
+
const issues = [];
|
|
15
|
+
const skippedDetectors = [];
|
|
16
|
+
const detectorErrors = [];
|
|
17
|
+
// Sequential, in registry order -- keeps issue ordering deterministic
|
|
18
|
+
// without needing a secondary sort step.
|
|
19
|
+
for (const detector of selected) {
|
|
20
|
+
try {
|
|
21
|
+
if (detector.shouldSkip) {
|
|
22
|
+
const decision = detector.shouldSkip(ctx);
|
|
23
|
+
if (decision.skip) {
|
|
24
|
+
skippedDetectors.push({ id: detector.id, reason: decision.reason });
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const detectorIssues = await detector.run(ctx);
|
|
29
|
+
issues.push(...detectorIssues);
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
detectorErrors.push({
|
|
33
|
+
id: detector.id,
|
|
34
|
+
message: err instanceof Error ? err.message : String(err),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const issueCounts = countIssuesBySeverity(issues);
|
|
39
|
+
const { exitCode, reason } = calculateAuditExitCode(issues, config.failOn);
|
|
40
|
+
return {
|
|
41
|
+
configSummary: {
|
|
42
|
+
types: config.types,
|
|
43
|
+
include: config.include,
|
|
44
|
+
formats: config.formats,
|
|
45
|
+
failOn: config.failOn,
|
|
46
|
+
out: config.out,
|
|
47
|
+
isDefaultRun: config.isDefaultRun,
|
|
48
|
+
},
|
|
49
|
+
targetSummary: {
|
|
50
|
+
rootPath: target.rootPath,
|
|
51
|
+
displayName: target.displayName,
|
|
52
|
+
isSelf: target.isSelf,
|
|
53
|
+
},
|
|
54
|
+
issues,
|
|
55
|
+
skippedDetectors,
|
|
56
|
+
detectorErrors,
|
|
57
|
+
issueCounts,
|
|
58
|
+
exitCode,
|
|
59
|
+
exitReason: reason,
|
|
60
|
+
inventory,
|
|
61
|
+
sourceOfTruth,
|
|
62
|
+
noDetectorsRegistered: selected.length === 0,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function countIssuesBySeverity(issues) {
|
|
66
|
+
const counts = { blocker: 0, high: 0, medium: 0, low: 0, info: 0 };
|
|
67
|
+
for (const issue of issues) {
|
|
68
|
+
counts[issue.severity] += 1;
|
|
69
|
+
}
|
|
70
|
+
return counts;
|
|
71
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { resolveLocalProjectTarget } from "../../core/localProjectTarget.js";
|
|
3
|
+
// Resolves and validates an audit target. Throws a descriptive Error (no
|
|
4
|
+
// stack trace wrapper) for a missing path or a non-directory path. Does not
|
|
5
|
+
// modify the target in any way.
|
|
6
|
+
export function resolveAuditTarget(targetPathArg, toolRoot) {
|
|
7
|
+
const metadata = resolveLocalProjectTarget(targetPathArg, toolRoot);
|
|
8
|
+
const displayName = metadata.packageName ?? path.basename(metadata.targetRoot);
|
|
9
|
+
return {
|
|
10
|
+
rootPath: metadata.targetRoot,
|
|
11
|
+
displayName,
|
|
12
|
+
exists: true,
|
|
13
|
+
isDirectory: true,
|
|
14
|
+
packageJsonPath: metadata.hasPackageJson ? path.join(metadata.targetRoot, "package.json") : null,
|
|
15
|
+
gitRoot: metadata.hasGit ? metadata.targetRoot : null,
|
|
16
|
+
isSelf: metadata.isSelf,
|
|
17
|
+
safeReportOutputRoot: path.join(path.resolve(toolRoot), "reports", "audits"),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// v0.3.0 Batch 1 — generic audit framework core vocabulary.
|
|
3
|
+
//
|
|
4
|
+
// Deliberately kept separate from src/securityValidation/types.ts. Audit and
|
|
5
|
+
// security-validation are two distinct frameworks (see docs/ROADMAP.md); a
|
|
6
|
+
// later v0.3.2 batch may integrate their results into a unified report, but
|
|
7
|
+
// this batch keeps the audit issue schema independent even where a
|
|
8
|
+
// vocabulary word (e.g. "blocker") happens to be shared.
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Recognized --types values. Only "code-rot" has real detector coverage in
|
|
11
|
+
// Batch 1 — the rest are known, named future audit types that must fail
|
|
12
|
+
// cleanly with a "planned but not implemented" message rather than being
|
|
13
|
+
// silently accepted or treated as an unknown/garbage value.
|
|
14
|
+
export const AUDIT_TYPES = ["code-rot", "quality", "security", "project", "all"];
|
|
15
|
+
export const IMPLEMENTED_AUDIT_TYPES = ["code-rot"];
|
|
16
|
+
export const PLANNED_AUDIT_TYPES = ["quality", "security", "project", "all"];
|
|
17
|
+
export const DEFAULT_AUDIT_TYPES = IMPLEMENTED_AUDIT_TYPES;
|
|
18
|
+
export const AUDIT_INCLUDE_AREAS = ["docs", "tests", "package", "architecture", "cli"];
|
|
19
|
+
export const DEFAULT_AUDIT_INCLUDE_AREAS = AUDIT_INCLUDE_AREAS;
|
|
20
|
+
export const AUDIT_OUTPUT_FORMATS = ["text", "json"];
|
|
21
|
+
export const DEFAULT_AUDIT_OUTPUT_FORMATS = ["text", "json"];
|
|
22
|
+
// "none" is a valid, explicit opt-out — findings never block the command.
|
|
23
|
+
export const AUDIT_FAIL_ON_THRESHOLDS = ["blocker", "high", "medium", "low", "none"];
|
|
24
|
+
export const DEFAULT_AUDIT_FAIL_ON_THRESHOLD = "blocker";
|
|
25
|
+
// Issue severity. Distinct from AuditFailOnThreshold: "info" is a valid
|
|
26
|
+
// issue severity but not a selectable --fail-on threshold (an info-severity
|
|
27
|
+
// issue can never breach any threshold, including "low").
|
|
28
|
+
export const AUDIT_SEVERITIES = ["blocker", "high", "medium", "low", "info"];
|
|
29
|
+
export const AUDIT_CONFIDENCE_LEVELS = ["high", "medium", "low"];
|
|
30
|
+
export const AUDIT_FALSE_POSITIVE_RISK_LEVELS = ["high", "medium", "low"];
|