@vibgrate/cli 1.0.50 → 1.0.52
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/DOCS.md +8 -2
- package/README.md +7 -1
- package/dist/{baseline-UOF24CBK.js → baseline-FRBISJ66.js} +2 -2
- package/dist/{chunk-PL6UM3Z3.js → chunk-TCYJSLL2.js} +1 -1
- package/dist/{chunk-6RMNQ6MQ.js → chunk-TYAGUEXG.js} +100 -1
- package/dist/cli.js +32 -20
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -4
- package/package.json +1 -1
- package/dist/chunk-PTMLMDZU.js +0 -95
package/DOCS.md
CHANGED
|
@@ -162,12 +162,12 @@ Creates:
|
|
|
162
162
|
The primary command. Scans your project for upgrade drift.
|
|
163
163
|
|
|
164
164
|
```bash
|
|
165
|
-
vibgrate scan [path] [--format text|json|sarif] [--out <file>] [--fail-on warn|error] [--offline] [--package-manifest <file>] [--no-local-artifacts] [--max-privacy] [--baseline <file>] [--drift-budget <score>] [--drift-worsening <percent>] [--changed-only] [--concurrency <n>]
|
|
165
|
+
vibgrate scan [path] [--format text|json|sarif|md] [--out <file>] [--fail-on warn|error] [--offline] [--package-manifest <file>] [--no-local-artifacts] [--max-privacy] [--baseline <file>] [--drift-budget <score>] [--drift-worsening <percent>] [--changed-only] [--concurrency <n>]
|
|
166
166
|
```
|
|
167
167
|
|
|
168
168
|
| Flag | Default | Description |
|
|
169
169
|
|------|---------|-------------|
|
|
170
|
-
| `--format` | `text` | Output format: `text`, `json`, or `
|
|
170
|
+
| `--format` | `text` | Output format: `text`, `json`, `sarif`, or `md` |
|
|
171
171
|
| `--out <file>` | — | Write output to a file |
|
|
172
172
|
| `--fail-on <level>` | — | Exit with code 2 if findings at this level exist |
|
|
173
173
|
| `--baseline <file>` | — | Compare against a previous baseline |
|
|
@@ -625,6 +625,12 @@ Maps security findings into OWASP Top 10 categories for security triage inside e
|
|
|
625
625
|
|
|
626
626
|
### GitHub Actions
|
|
627
627
|
|
|
628
|
+
Use the maintained templates in this package for copy-paste setup:
|
|
629
|
+
|
|
630
|
+
- `examples/github-actions/driftscore-ci.yml` (JSON artifact + drift gate)
|
|
631
|
+
- `examples/github-actions/driftscore-sarif.yml` (SARIF upload to code scanning)
|
|
632
|
+
- `docs/ci/github-actions.md` (integration notes)
|
|
633
|
+
|
|
628
634
|
```yaml
|
|
629
635
|
steps:
|
|
630
636
|
- name: Vibgrate Scan
|
package/README.md
CHANGED
|
@@ -176,7 +176,7 @@ When offline mode runs without a package manifest, package freshness is marked a
|
|
|
176
176
|
## Core commands
|
|
177
177
|
|
|
178
178
|
```bash
|
|
179
|
-
vibgrate scan [path] [--format text|json|sarif] [--out <file>] [--fail-on warn|error] [--offline] [--package-manifest <file>] [--no-local-artifacts] [--max-privacy]
|
|
179
|
+
vibgrate scan [path] [--format text|json|sarif|md] [--out <file>] [--fail-on warn|error] [--offline] [--package-manifest <file>] [--no-local-artifacts] [--max-privacy]
|
|
180
180
|
vibgrate baseline [path]
|
|
181
181
|
vibgrate report [--in <artifact.json>] [--format md|text|json]
|
|
182
182
|
vibgrate push [--dsn <dsn>] [--file <artifact.json>] [--strict]
|
|
@@ -249,6 +249,12 @@ npx @vibgrate/cli scan . --baseline .vibgrate/baseline.json
|
|
|
249
249
|
|
|
250
250
|
### GitHub Actions
|
|
251
251
|
|
|
252
|
+
Use the maintained templates in this package for copy-paste setup:
|
|
253
|
+
|
|
254
|
+
- `examples/github-actions/driftscore-ci.yml` (JSON artifact + drift gate)
|
|
255
|
+
- `examples/github-actions/driftscore-sarif.yml` (SARIF upload to code scanning)
|
|
256
|
+
- `docs/ci/github-actions.md` (integration notes)
|
|
257
|
+
|
|
252
258
|
```yaml
|
|
253
259
|
- name: Vibgrate scan
|
|
254
260
|
env:
|
|
@@ -965,6 +965,98 @@ function toSarifResult(finding) {
|
|
|
965
965
|
};
|
|
966
966
|
}
|
|
967
967
|
|
|
968
|
+
// src/formatters/markdown.ts
|
|
969
|
+
function formatMarkdown(artifact) {
|
|
970
|
+
const lines = [];
|
|
971
|
+
lines.push("# Vibgrate Drift Report");
|
|
972
|
+
lines.push("");
|
|
973
|
+
lines.push(`| Metric | Value |`);
|
|
974
|
+
lines.push(`|--------|-------|`);
|
|
975
|
+
lines.push(`| **Drift Score** | ${artifact.drift.score}/100 |`);
|
|
976
|
+
lines.push(`| **Risk Level** | ${artifact.drift.riskLevel.toUpperCase()} |`);
|
|
977
|
+
lines.push(`| **Projects** | ${artifact.projects.length} |`);
|
|
978
|
+
const scannedMeta = [artifact.timestamp];
|
|
979
|
+
if (artifact.durationMs !== void 0) scannedMeta.push(`${(artifact.durationMs / 1e3).toFixed(1)}s`);
|
|
980
|
+
if (artifact.filesScanned !== void 0) scannedMeta.push(`${artifact.filesScanned} files`);
|
|
981
|
+
if (artifact.treeSummary) scannedMeta.push(`${artifact.treeSummary.totalFiles.toLocaleString()} workspace files \xB7 ${artifact.treeSummary.totalDirs.toLocaleString()} dirs`);
|
|
982
|
+
lines.push(`| **Scanned** | ${scannedMeta.join(" \xB7 ")} |`);
|
|
983
|
+
if (artifact.vcs) {
|
|
984
|
+
lines.push(`| **VCS** | ${artifact.vcs.type} |`);
|
|
985
|
+
if (artifact.vcs.branch) lines.push(`| **Branch** | ${artifact.vcs.branch} |`);
|
|
986
|
+
if (artifact.vcs.sha) lines.push(`| **Commit** | \`${artifact.vcs.shortSha}\` |`);
|
|
987
|
+
}
|
|
988
|
+
lines.push("");
|
|
989
|
+
lines.push("## Score Breakdown");
|
|
990
|
+
lines.push("");
|
|
991
|
+
lines.push(`| Component | Score |`);
|
|
992
|
+
lines.push(`|-----------|-------|`);
|
|
993
|
+
lines.push(`| Runtime | ${artifact.drift.components.runtimeScore} |`);
|
|
994
|
+
lines.push(`| Frameworks | ${artifact.drift.components.frameworkScore} |`);
|
|
995
|
+
lines.push(`| Dependencies | ${artifact.drift.components.dependencyScore} |`);
|
|
996
|
+
lines.push(`| EOL Risk | ${artifact.drift.components.eolScore} |`);
|
|
997
|
+
lines.push("");
|
|
998
|
+
lines.push("## Projects");
|
|
999
|
+
lines.push("");
|
|
1000
|
+
for (const project of artifact.projects) {
|
|
1001
|
+
lines.push(`### ${project.name} (${project.type})`);
|
|
1002
|
+
lines.push("");
|
|
1003
|
+
if (project.runtime) {
|
|
1004
|
+
const lag = project.runtimeMajorsBehind !== void 0 && project.runtimeMajorsBehind > 0 ? ` \u2014 ${project.runtimeMajorsBehind} major(s) behind` : " \u2014 current";
|
|
1005
|
+
lines.push(`- **Runtime:** ${project.runtime}${lag}`);
|
|
1006
|
+
}
|
|
1007
|
+
if (project.frameworks.length > 0) {
|
|
1008
|
+
lines.push("- **Frameworks:**");
|
|
1009
|
+
for (const fw of project.frameworks) {
|
|
1010
|
+
const lag = fw.majorsBehind !== null ? fw.majorsBehind === 0 ? "current" : `${fw.majorsBehind} behind` : "unknown";
|
|
1011
|
+
lines.push(` - ${fw.name}: ${fw.currentVersion ?? "?"} \u2192 ${fw.latestVersion ?? "?"} (${lag})`);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
const b = project.dependencyAgeBuckets;
|
|
1015
|
+
const total = b.current + b.oneBehind + b.twoPlusBehind + b.unknown;
|
|
1016
|
+
if (total > 0) {
|
|
1017
|
+
lines.push(`- **Dependencies:** ${b.current} current, ${b.oneBehind} 1-behind, ${b.twoPlusBehind} 2+ behind, ${b.unknown} unknown`);
|
|
1018
|
+
}
|
|
1019
|
+
lines.push("");
|
|
1020
|
+
}
|
|
1021
|
+
if (artifact.extended?.uiPurpose) {
|
|
1022
|
+
const up = artifact.extended.uiPurpose;
|
|
1023
|
+
lines.push("## Product Purpose Signals");
|
|
1024
|
+
lines.push("");
|
|
1025
|
+
lines.push(`- **Frameworks:** ${up.detectedFrameworks.length > 0 ? up.detectedFrameworks.join(", ") : "unknown"}`);
|
|
1026
|
+
lines.push(`- **Evidence Items:** ${up.topEvidence.length}${up.capped ? ` (capped from ${up.evidenceCount})` : ""}`);
|
|
1027
|
+
if (up.topEvidence.length > 0) {
|
|
1028
|
+
lines.push("- **Top Evidence:**");
|
|
1029
|
+
for (const item of up.topEvidence.slice(0, 10)) {
|
|
1030
|
+
lines.push(` - [${item.kind}] ${item.value} (${item.file})`);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
if (up.unknownSignals.length > 0) {
|
|
1034
|
+
lines.push("- **Unknowns:**");
|
|
1035
|
+
for (const u of up.unknownSignals.slice(0, 5)) {
|
|
1036
|
+
lines.push(` - ${u}`);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
lines.push("");
|
|
1040
|
+
}
|
|
1041
|
+
if (artifact.findings.length > 0) {
|
|
1042
|
+
lines.push("## Findings");
|
|
1043
|
+
lines.push("");
|
|
1044
|
+
lines.push(`| Level | Rule | Message | Location |`);
|
|
1045
|
+
lines.push(`|-------|------|---------|----------|`);
|
|
1046
|
+
for (const f of artifact.findings) {
|
|
1047
|
+
const emoji = f.level === "error" ? "\u{1F534}" : f.level === "warning" ? "\u{1F7E1}" : "\u{1F535}";
|
|
1048
|
+
lines.push(`| ${emoji} ${f.level} | ${f.ruleId} | ${f.message} | ${f.location} |`);
|
|
1049
|
+
}
|
|
1050
|
+
lines.push("");
|
|
1051
|
+
}
|
|
1052
|
+
if (artifact.delta !== void 0) {
|
|
1053
|
+
const dir = artifact.delta > 0 ? "\u{1F4C8}" : artifact.delta < 0 ? "\u{1F4C9}" : "\u27A1\uFE0F";
|
|
1054
|
+
lines.push(`## Drift Delta: ${dir} ${artifact.delta > 0 ? "+" : ""}${artifact.delta} vs baseline`);
|
|
1055
|
+
lines.push("");
|
|
1056
|
+
}
|
|
1057
|
+
return lines.join("\n");
|
|
1058
|
+
}
|
|
1059
|
+
|
|
968
1060
|
// src/commands/dsn.ts
|
|
969
1061
|
import * as crypto2 from "crypto";
|
|
970
1062
|
import * as path from "path";
|
|
@@ -7880,6 +7972,12 @@ async function runScan(rootDir, opts) {
|
|
|
7880
7972
|
} else {
|
|
7881
7973
|
console.log(sarifStr);
|
|
7882
7974
|
}
|
|
7975
|
+
} else if (opts.format === "md") {
|
|
7976
|
+
const markdown = formatMarkdown(artifact);
|
|
7977
|
+
console.log(markdown);
|
|
7978
|
+
if (opts.out) {
|
|
7979
|
+
await writeTextFile(path22.resolve(opts.out), markdown);
|
|
7980
|
+
}
|
|
7883
7981
|
} else {
|
|
7884
7982
|
const text = formatText(artifact);
|
|
7885
7983
|
console.log(text);
|
|
@@ -7982,7 +8080,7 @@ function parseNonNegativeNumber(value, label) {
|
|
|
7982
8080
|
}
|
|
7983
8081
|
return parsed;
|
|
7984
8082
|
}
|
|
7985
|
-
var scanCommand = new Command3("scan").description("Scan a project for upgrade drift").argument("[path]", "Path to scan", ".").option("--out <file>", "Output file path").option("--format <format>", "Output format (text|json|sarif)", "text").option("--fail-on <level>", "Fail on warn or error").option("--baseline <file>", "Compare against baseline").option("--changed-only", "Only scan changed files").option("--concurrency <n>", "Max concurrent npm calls", "8").option("--push", "Auto-push results to Vibgrate API after scan").option("--dsn <dsn>", "DSN token for push (or use VIBGRATE_DSN env)").option("--region <region>", "Override data residency region for push (us, eu)").option("--strict", "Fail on push errors").option("--install-tools", "Auto-install missing security scanners via Homebrew").option("--ui-purpose", "Enable optional UI purpose evidence extraction (slower)").option("--no-local-artifacts", "Do not write .vibgrate JSON artifacts to disk").option("--max-privacy", "Enable strongest privacy mode (minimal scanners, no local artifacts)").option("--offline", "Run without network calls; do not upload results").option("--package-manifest <file>", "Use local package-version manifest JSON/ZIP (for offline mode)").option("--drift-budget <score>", "Fail if drift score is above budget (0-100)").option("--drift-worsening <percent>", "Fail if drift worsens by more than % since baseline").action(async (targetPath, opts) => {
|
|
8083
|
+
var scanCommand = new Command3("scan").description("Scan a project for upgrade drift").argument("[path]", "Path to scan", ".").option("--out <file>", "Output file path").option("--format <format>", "Output format (text|json|sarif|md)", "text").option("--fail-on <level>", "Fail on warn or error").option("--baseline <file>", "Compare against baseline").option("--changed-only", "Only scan changed files").option("--concurrency <n>", "Max concurrent npm calls", "8").option("--push", "Auto-push results to Vibgrate API after scan").option("--dsn <dsn>", "DSN token for push (or use VIBGRATE_DSN env)").option("--region <region>", "Override data residency region for push (us, eu)").option("--strict", "Fail on push errors").option("--install-tools", "Auto-install missing security scanners via Homebrew").option("--ui-purpose", "Enable optional UI purpose evidence extraction (slower)").option("--no-local-artifacts", "Do not write .vibgrate JSON artifacts to disk").option("--max-privacy", "Enable strongest privacy mode (minimal scanners, no local artifacts)").option("--offline", "Run without network calls; do not upload results").option("--package-manifest <file>", "Use local package-version manifest JSON/ZIP (for offline mode)").option("--drift-budget <score>", "Fail if drift score is above budget (0-100)").option("--drift-worsening <percent>", "Fail if drift worsens by more than % since baseline").action(async (targetPath, opts) => {
|
|
7986
8084
|
const rootDir = path22.resolve(targetPath);
|
|
7987
8085
|
if (!await pathExists(rootDir)) {
|
|
7988
8086
|
console.error(chalk6.red(`Path does not exist: ${rootDir}`));
|
|
@@ -8057,6 +8155,7 @@ export {
|
|
|
8057
8155
|
VERSION,
|
|
8058
8156
|
formatText,
|
|
8059
8157
|
formatSarif,
|
|
8158
|
+
formatMarkdown,
|
|
8060
8159
|
dsnCommand,
|
|
8061
8160
|
pushCommand,
|
|
8062
8161
|
runScan,
|
package/dist/cli.js
CHANGED
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
formatMarkdown
|
|
4
|
-
} from "./chunk-PTMLMDZU.js";
|
|
5
2
|
import {
|
|
6
3
|
baselineCommand
|
|
7
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-TCYJSLL2.js";
|
|
8
5
|
import {
|
|
9
6
|
VERSION,
|
|
10
7
|
dsnCommand,
|
|
8
|
+
formatMarkdown,
|
|
11
9
|
formatText,
|
|
12
10
|
pushCommand,
|
|
13
11
|
scanCommand,
|
|
14
12
|
writeDefaultConfig
|
|
15
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-TYAGUEXG.js";
|
|
16
14
|
import {
|
|
17
15
|
ensureDir,
|
|
18
16
|
pathExists,
|
|
@@ -41,7 +39,7 @@ var initCommand = new Command("init").description("Initialize vibgrate in a proj
|
|
|
41
39
|
console.log(chalk.green("\u2714") + ` Created ${chalk.bold("vibgrate.config.ts")}`);
|
|
42
40
|
}
|
|
43
41
|
if (opts.baseline) {
|
|
44
|
-
const { runBaseline } = await import("./baseline-
|
|
42
|
+
const { runBaseline } = await import("./baseline-FRBISJ66.js");
|
|
45
43
|
await runBaseline(rootDir);
|
|
46
44
|
}
|
|
47
45
|
console.log("");
|
|
@@ -104,11 +102,16 @@ async function checkForUpdate() {
|
|
|
104
102
|
}
|
|
105
103
|
const controller = new AbortController();
|
|
106
104
|
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
105
|
+
timeout.unref?.();
|
|
106
|
+
let response;
|
|
107
|
+
try {
|
|
108
|
+
response = await fetch(REGISTRY_URL, {
|
|
109
|
+
headers: { Accept: "application/json" },
|
|
110
|
+
signal: controller.signal
|
|
111
|
+
});
|
|
112
|
+
} finally {
|
|
113
|
+
clearTimeout(timeout);
|
|
114
|
+
}
|
|
112
115
|
if (!response.ok) return null;
|
|
113
116
|
const data = await response.json();
|
|
114
117
|
const latest = data.version;
|
|
@@ -127,11 +130,16 @@ async function fetchLatestVersion() {
|
|
|
127
130
|
try {
|
|
128
131
|
const controller = new AbortController();
|
|
129
132
|
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
133
|
+
timeout.unref?.();
|
|
134
|
+
let response;
|
|
135
|
+
try {
|
|
136
|
+
response = await fetch(REGISTRY_URL, {
|
|
137
|
+
headers: { Accept: "application/json" },
|
|
138
|
+
signal: controller.signal
|
|
139
|
+
});
|
|
140
|
+
} finally {
|
|
141
|
+
clearTimeout(timeout);
|
|
142
|
+
}
|
|
135
143
|
if (!response.ok) return null;
|
|
136
144
|
const data = await response.json();
|
|
137
145
|
const latest = data.version;
|
|
@@ -412,14 +420,18 @@ program.addCommand(dsnCommand);
|
|
|
412
420
|
program.addCommand(pushCommand);
|
|
413
421
|
program.addCommand(updateCommand);
|
|
414
422
|
program.addCommand(sbomCommand);
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
423
|
+
function notifyIfUpdateAvailable() {
|
|
424
|
+
void checkForUpdate().then((update) => {
|
|
425
|
+
if (!update?.updateAvailable) return;
|
|
418
426
|
console.error("");
|
|
419
427
|
console.error(chalk5.yellow(` Update available: ${update.current} \u2192 ${update.latest}`));
|
|
420
428
|
console.error(chalk5.dim(' Run "vibgrate update" to install the latest version.'));
|
|
421
429
|
console.error("");
|
|
422
|
-
}
|
|
430
|
+
}).catch(() => {
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
program.parseAsync().then(() => {
|
|
434
|
+
notifyIfUpdateAvailable();
|
|
423
435
|
}).catch((err) => {
|
|
424
436
|
console.error(err);
|
|
425
437
|
process.exit(1);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
type DepSection = 'dependencies' | 'devDependencies' | 'peerDependencies' | 'optionalDependencies';
|
|
2
2
|
type RiskLevel = 'low' | 'moderate' | 'high';
|
|
3
3
|
type ProjectType = 'node' | 'dotnet' | 'python' | 'java' | 'go' | 'rust' | 'php' | 'typescript' | 'ruby' | 'swift' | 'kotlin' | 'dart' | 'scala' | 'r' | 'objective-c' | 'elixir' | 'haskell' | 'lua' | 'perl' | 'julia' | 'shell' | 'clojure' | 'groovy' | 'c' | 'cpp' | 'cobol' | 'fortran' | 'visual-basic' | 'pascal' | 'ada' | 'assembly' | 'rpg';
|
|
4
|
-
type OutputFormat = 'text' | 'json' | 'sarif';
|
|
4
|
+
type OutputFormat = 'text' | 'json' | 'sarif' | 'md';
|
|
5
5
|
interface DependencyRow {
|
|
6
6
|
package: string;
|
|
7
7
|
section: DepSection;
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
formatMarkdown
|
|
3
|
-
} from "./chunk-PTMLMDZU.js";
|
|
4
1
|
import {
|
|
5
2
|
computeDriftScore,
|
|
3
|
+
formatMarkdown,
|
|
6
4
|
formatSarif,
|
|
7
5
|
formatText,
|
|
8
6
|
generateFindings,
|
|
9
7
|
runScan
|
|
10
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-TYAGUEXG.js";
|
|
11
9
|
import "./chunk-RNVZIZNL.js";
|
|
12
10
|
export {
|
|
13
11
|
computeDriftScore,
|
package/package.json
CHANGED
package/dist/chunk-PTMLMDZU.js
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
// src/formatters/markdown.ts
|
|
2
|
-
function formatMarkdown(artifact) {
|
|
3
|
-
const lines = [];
|
|
4
|
-
lines.push("# Vibgrate Drift Report");
|
|
5
|
-
lines.push("");
|
|
6
|
-
lines.push(`| Metric | Value |`);
|
|
7
|
-
lines.push(`|--------|-------|`);
|
|
8
|
-
lines.push(`| **Drift Score** | ${artifact.drift.score}/100 |`);
|
|
9
|
-
lines.push(`| **Risk Level** | ${artifact.drift.riskLevel.toUpperCase()} |`);
|
|
10
|
-
lines.push(`| **Projects** | ${artifact.projects.length} |`);
|
|
11
|
-
const scannedMeta = [artifact.timestamp];
|
|
12
|
-
if (artifact.durationMs !== void 0) scannedMeta.push(`${(artifact.durationMs / 1e3).toFixed(1)}s`);
|
|
13
|
-
if (artifact.filesScanned !== void 0) scannedMeta.push(`${artifact.filesScanned} files`);
|
|
14
|
-
if (artifact.treeSummary) scannedMeta.push(`${artifact.treeSummary.totalFiles.toLocaleString()} workspace files \xB7 ${artifact.treeSummary.totalDirs.toLocaleString()} dirs`);
|
|
15
|
-
lines.push(`| **Scanned** | ${scannedMeta.join(" \xB7 ")} |`);
|
|
16
|
-
if (artifact.vcs) {
|
|
17
|
-
lines.push(`| **VCS** | ${artifact.vcs.type} |`);
|
|
18
|
-
if (artifact.vcs.branch) lines.push(`| **Branch** | ${artifact.vcs.branch} |`);
|
|
19
|
-
if (artifact.vcs.sha) lines.push(`| **Commit** | \`${artifact.vcs.shortSha}\` |`);
|
|
20
|
-
}
|
|
21
|
-
lines.push("");
|
|
22
|
-
lines.push("## Score Breakdown");
|
|
23
|
-
lines.push("");
|
|
24
|
-
lines.push(`| Component | Score |`);
|
|
25
|
-
lines.push(`|-----------|-------|`);
|
|
26
|
-
lines.push(`| Runtime | ${artifact.drift.components.runtimeScore} |`);
|
|
27
|
-
lines.push(`| Frameworks | ${artifact.drift.components.frameworkScore} |`);
|
|
28
|
-
lines.push(`| Dependencies | ${artifact.drift.components.dependencyScore} |`);
|
|
29
|
-
lines.push(`| EOL Risk | ${artifact.drift.components.eolScore} |`);
|
|
30
|
-
lines.push("");
|
|
31
|
-
lines.push("## Projects");
|
|
32
|
-
lines.push("");
|
|
33
|
-
for (const project of artifact.projects) {
|
|
34
|
-
lines.push(`### ${project.name} (${project.type})`);
|
|
35
|
-
lines.push("");
|
|
36
|
-
if (project.runtime) {
|
|
37
|
-
const lag = project.runtimeMajorsBehind !== void 0 && project.runtimeMajorsBehind > 0 ? ` \u2014 ${project.runtimeMajorsBehind} major(s) behind` : " \u2014 current";
|
|
38
|
-
lines.push(`- **Runtime:** ${project.runtime}${lag}`);
|
|
39
|
-
}
|
|
40
|
-
if (project.frameworks.length > 0) {
|
|
41
|
-
lines.push("- **Frameworks:**");
|
|
42
|
-
for (const fw of project.frameworks) {
|
|
43
|
-
const lag = fw.majorsBehind !== null ? fw.majorsBehind === 0 ? "current" : `${fw.majorsBehind} behind` : "unknown";
|
|
44
|
-
lines.push(` - ${fw.name}: ${fw.currentVersion ?? "?"} \u2192 ${fw.latestVersion ?? "?"} (${lag})`);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
const b = project.dependencyAgeBuckets;
|
|
48
|
-
const total = b.current + b.oneBehind + b.twoPlusBehind + b.unknown;
|
|
49
|
-
if (total > 0) {
|
|
50
|
-
lines.push(`- **Dependencies:** ${b.current} current, ${b.oneBehind} 1-behind, ${b.twoPlusBehind} 2+ behind, ${b.unknown} unknown`);
|
|
51
|
-
}
|
|
52
|
-
lines.push("");
|
|
53
|
-
}
|
|
54
|
-
if (artifact.extended?.uiPurpose) {
|
|
55
|
-
const up = artifact.extended.uiPurpose;
|
|
56
|
-
lines.push("## Product Purpose Signals");
|
|
57
|
-
lines.push("");
|
|
58
|
-
lines.push(`- **Frameworks:** ${up.detectedFrameworks.length > 0 ? up.detectedFrameworks.join(", ") : "unknown"}`);
|
|
59
|
-
lines.push(`- **Evidence Items:** ${up.topEvidence.length}${up.capped ? ` (capped from ${up.evidenceCount})` : ""}`);
|
|
60
|
-
if (up.topEvidence.length > 0) {
|
|
61
|
-
lines.push("- **Top Evidence:**");
|
|
62
|
-
for (const item of up.topEvidence.slice(0, 10)) {
|
|
63
|
-
lines.push(` - [${item.kind}] ${item.value} (${item.file})`);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
if (up.unknownSignals.length > 0) {
|
|
67
|
-
lines.push("- **Unknowns:**");
|
|
68
|
-
for (const u of up.unknownSignals.slice(0, 5)) {
|
|
69
|
-
lines.push(` - ${u}`);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
lines.push("");
|
|
73
|
-
}
|
|
74
|
-
if (artifact.findings.length > 0) {
|
|
75
|
-
lines.push("## Findings");
|
|
76
|
-
lines.push("");
|
|
77
|
-
lines.push(`| Level | Rule | Message | Location |`);
|
|
78
|
-
lines.push(`|-------|------|---------|----------|`);
|
|
79
|
-
for (const f of artifact.findings) {
|
|
80
|
-
const emoji = f.level === "error" ? "\u{1F534}" : f.level === "warning" ? "\u{1F7E1}" : "\u{1F535}";
|
|
81
|
-
lines.push(`| ${emoji} ${f.level} | ${f.ruleId} | ${f.message} | ${f.location} |`);
|
|
82
|
-
}
|
|
83
|
-
lines.push("");
|
|
84
|
-
}
|
|
85
|
-
if (artifact.delta !== void 0) {
|
|
86
|
-
const dir = artifact.delta > 0 ? "\u{1F4C8}" : artifact.delta < 0 ? "\u{1F4C9}" : "\u27A1\uFE0F";
|
|
87
|
-
lines.push(`## Drift Delta: ${dir} ${artifact.delta > 0 ? "+" : ""}${artifact.delta} vs baseline`);
|
|
88
|
-
lines.push("");
|
|
89
|
-
}
|
|
90
|
-
return lines.join("\n");
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export {
|
|
94
|
-
formatMarkdown
|
|
95
|
-
};
|