Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 40 additions & 18 deletions DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -459,46 +459,63 @@ Creates:

### vg report

Render a saved scan artifact. There is no HTML report. The formats are `md`,
`text`, and `json`. `text` is the default. `json` prints the artifact unchanged
and is the only stable machine-readable contract. `sarif` is not a `vg report`
format; write that with `vg scan --format sarif`.
Render a saved scan artifact. The formats are `md`, `text`, `json`, and `html`.
`text` is the default. `json` prints the artifact unchanged and is the only
stable machine-readable contract. `sarif` is not a `vg report` format; write
that with `vg scan --format sarif`.

```bash
vg report [--in <file>] [--format md|text|json]
vg report [--in <file>] [--format md|text|json|html]
```

| Flag | Default | Description |
| ---------- | ---------------------------- | --------------------------------------------------------------------------- |
| `--in` | `.vibgrate/scan_result.json` | Input artifact file |
| `--format` | `text` | `md`, `text`, or `json`. Any other value is a usage error (exit `5`) |
| Flag | Default | Description |
| ---------- | ---------------------------- | -------------------------------------------------------------------------------- |
| `--in` | `.vibgrate/scan_result.json` | Input artifact file |
| `--format` | `text` | `md`, `text`, `json`, or `html`. Any other value is a usage error (exit `5`) |

An unknown value is rejected before the file is read. The process exits `5`,
writes nothing to stdout, and prints one stderr line that names the value and
lists the valid ones:

```text
error: unknown --format "html" (expected md, text, json)
error: unknown --format "sarif" (expected md, text, json, html)
```

The comparison is exact. `HTML`, `sarif`, and `md ` are unknown. Omitting
`--format` stays on `text`.

`md`, `text`, and `json` are unchanged. `html` writes one self-contained page
from that same artifact. It does not call the network and does not load
external files. The page opens with a summary:

- DriftScore and the four component scores. A missing score is `n/a`. A measured `0` stays `0`.
- Counts by severity for vulnerability findings and for security findings. A check that did not run stays "Not scanned" rather than a row of zeros. A check that finished with nothing to report shows zeros.
- When a finding carries `details.fixedVersions`, how many of those findings name a fix.
- Links to the full tables under the summary: vulnerabilities, security findings, and the other findings.

The same artifact always produces the same page. Save it with a shell redirect:

```bash
vg report --format html > report.html
```

#### Report format coverage

`--format md` is `formatMarkdown` (`src/reporting/formatters/markdown.ts`).
`--format text` is `formatText` (`src/reporting/formatters/text.ts`). Both read
the same artifact. A section below is omitted when its guard is false. Lists
follow artifact order except where a sort is named. Colours in the text report
are terminal styling; under `NO_COLOR` the same words are plain. A null score
is `n/a` in both formats. A measured `0` stays `0`.
`--format text` is `formatText` (`src/reporting/formatters/text.ts`).
`--format html` is `formatHtmlReport` (`src/reporting/formatters/html.ts`).
All three read the same artifact. A section below is omitted when its guard is
false. Lists follow artifact order except where a sort is named. Colours in the
text report are terminal styling; under `NO_COLOR` the same words are plain. A
null score is `n/a` in the human formats. A measured `0` stays `0`.

The same artifact, three times:
The same artifact, four formats:

```bash
vg report --in .vibgrate/scan_result.json --format md
vg report --in .vibgrate/scan_result.json --format text
vg report --in .vibgrate/scan_result.json --format json
vg report --in .vibgrate/scan_result.json --format html
```

##### Markdown (`formatMarkdown`)
Expand Down Expand Up @@ -566,7 +583,11 @@ When set:
| Architecture Layers | `extended.architecture`. `archetype`, `archetypeConfidence`, `totalClassified`, `unclassified`. **Folders** — the first 12 of `folders`: `path`, `layer`, `confidence`, `fileCount`. **Coverage**, when set — `ratio`, `classified`, `unclassified`, and `bySource` counts sorted by count descending then source name. **Unclassified source** — `unclassifiedFiles.length` when that list is non-empty. **Unclassified by folder** — the first 8 of `unclassifiedFolders`: `count`, `path`. **Layers** with `fileCount` greater than 0: `layer`, `fileCount`, `driftScore`, `riskLevel`. **Boundary violations** — violations whose `rule` does not start with `graph-conflict:`, first 8: `fromFile`, `toFile`, `rule`. **Layer conflicts** — the `graph-conflict:` violations, first 8, same three fields. A capped violation list is marked when `violationsCapped` is set |
| Solution Drift Summary | `solutions` is non-empty. `name`, `projectPaths.length`, and `drift.score` or `n/a` |

`relationshipDiagram` is not rendered. The text formatter leaves that diagram in the JSON artifact.
`relationshipDiagram` is not rendered. Markdown, text, and HTML leave that diagram in the JSON artifact.

##### HTML (`formatHtmlReport`)

`vg report --format html` writes one self-contained page from the same artifact. It does not call the network and does not load external files. The page opens with the summary listed above (DriftScore, severity counts, a fix-available count when a finding carries `details.fixedVersions`, and links to the tables), then the full vulnerability, security, and other-finding tables. A check that did not run stays "Not scanned". A missing score is `n/a`. The same artifact always produces the same page.

##### Differences

Expand All @@ -579,10 +600,11 @@ When set:
- DriftScore: Markdown puts the lower-is-better gloss in the opening cell. Text puts the score in **DriftScore Summary** without that gloss.
- VCS: Markdown uses separate VCS, Branch, and Commit rows, and Commit requires `vcs.sha`. Text uses one line and can show `shortSha` without requiring `sha`.
- Runtime wording: Markdown always says `major(s)`. Text says `major` or `majors`.
- HTML is the browser page: the summary above, then the vulnerability, security, and other-finding tables. It does not print Markdown's recommended-standards section or the text inventory.

##### JSON

`--format json` prints `JSON.stringify` of the loaded artifact with a two-space indent. Keys, values, and omissions are unchanged. Parse this when a script needs a field. Markdown and text drop fields, cap lists, and add derived lines; they are not a stable contract.
`--format json` prints `JSON.stringify` of the loaded artifact with a two-space indent. Keys, values, and omissions are unchanged. Parse this when a script needs a field. Markdown, text, and HTML drop fields, cap lists, and add derived lines; they are not a stable contract.

---

Expand Down
4 changes: 2 additions & 2 deletions plugins/claude/vg/skills/vg/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: vg
description: Query the local code graph (vg) for structure, impact, and navigation instead of grepping/reading many files.
---

<!-- vg:v4 · managed by `vg install` — auto-refreshed when these instructions evolve; remove this line to opt out -->
<!-- vg:v5 · managed by `vg install` — auto-refreshed when these instructions evolve; remove this line to opt out -->

# vg — the code map

Expand Down Expand Up @@ -39,7 +39,7 @@ measured and the tools improved):
- **Upgrade drift:** `vg drift` lists what is outdated across dependencies
(offline; `--online` for currency). `vg scan` scores upgrade drift
(`text`, `json`, `sarif`, or `md`). `vg report` renders a saved scan
artifact as `text` (the default), `md`, or `json`. There is no HTML report.
artifact as `text` (the default), `md`, `json`, or `html` (a self-contained page).
- **Version-correct docs:** `vg lib <name>` returns drift-annotated, version-
specific usage docs for a library — inject these instead of guessing an API.

Expand Down
4 changes: 2 additions & 2 deletions skills/vg/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: vg
description: Query the local code graph (vg) for structure, impact, and navigation instead of grepping/reading many files.
---

<!-- vg:v4 · managed by `vg install` — auto-refreshed when these instructions evolve; remove this line to opt out -->
<!-- vg:v5 · managed by `vg install` — auto-refreshed when these instructions evolve; remove this line to opt out -->

# vg — the code map

Expand Down Expand Up @@ -39,7 +39,7 @@ measured and the tools improved):
- **Upgrade drift:** `vg drift` lists what is outdated across dependencies
(offline; `--online` for currency). `vg scan` scores upgrade drift
(`text`, `json`, `sarif`, or `md`). `vg report` renders a saved scan
artifact as `text` (the default), `md`, or `json`. There is no HTML report.
artifact as `text` (the default), `md`, `json`, or `html` (a self-contained page).
- **Version-correct docs:** `vg lib <name>` returns drift-annotated, version-
specific usage docs for a library — inject these instead of guessing an API.

Expand Down
7 changes: 4 additions & 3 deletions src/install/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ export const NUDGE_END = '<!-- vg:end -->';
* v3 = library-docs workflow resolves by package name only, and an
* `entityCount` of 0 means read `node_modules` rather than retry;
* v4 = `vg report` formats are text (default), md, and json. There is no
* HTML report, and sarif belongs to `vg scan`.
* HTML report, and sarif belongs to `vg scan`;
* v5 = `vg report` also accepts `html` (a self-contained page).
*/
export const INSTALL_CONTENT_VERSION = 4;
export const INSTALL_CONTENT_VERSION = 5;

const VERSION_MARKER_RE = /<!--\s*vg:v(\d+)\b[^>]*-->/;

Expand Down Expand Up @@ -94,7 +95,7 @@ measured and the tools improved):
- **Upgrade drift:** \`vg drift\` lists what is outdated across dependencies
(offline; \`--online\` for currency). \`vg scan\` scores upgrade drift
(\`text\`, \`json\`, \`sarif\`, or \`md\`). \`vg report\` renders a saved scan
artifact as \`text\` (the default), \`md\`, or \`json\`. There is no HTML report.
artifact as \`text\` (the default), \`md\`, \`json\`, or \`html\` (a self-contained page).
- **Version-correct docs:** \`vg lib <name>\` returns drift-annotated, version-
specific usage docs for a library — inject these instead of guessing an API.

Expand Down
36 changes: 34 additions & 2 deletions src/reporting/commands/report.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as os from 'node:os';
import * as path from 'node:path';
import { main } from '../../cli.js';
import { ExitCode } from '../../util/exit.js';
import { REPORT_FORMATS, reportCommand } from './report.js';

const SENTINEL = 'report-format-sentinel';

Expand Down Expand Up @@ -91,14 +92,45 @@ async function runCli(args: string[]): Promise<{ code: number; stderr: string; s
}

describe('vg report --format', () => {
it('names html in help and in the accepted list', () => {
const option = reportCommand.options.find((item) => item.long === '--format');
expect(option?.description).toBe('Output format (md|text|json|html)');
expect(REPORT_FORMATS).toEqual(['md', 'text', 'json', 'html']);
});

it('renders html from the local artifact and leaves text, json, and md alone', async () => {
const file = writeArtifact();
const html = await runCli(['report', '--in', file, '--format', 'html']);
expect(html.code).toBe(0);
expect(html.stdout).toContain('<!doctype html>');
expect(html.stdout).toContain('id="summary"');
expect(html.stdout).toContain('id="drift-score">1');
expect(html.stderr).toBe('');

const text = await runCli(['report', '--in', file]);
expect(text.code).toBe(0);
expect(text.stdout).toContain('Vibgrate Drift Report');
expect(text.stdout).not.toContain('<!doctype');

const md = await runCli(['report', '--in', file, '--format', 'md']);
expect(md.code).toBe(0);
expect(md.stdout).toContain('# Vibgrate Drift Report');
expect(md.stdout).not.toContain('<!doctype');

const json = await runCli(['report', '--in', file, '--format', 'json']);
expect(json.code).toBe(0);
expect(JSON.parse(json.stdout)).toEqual(artifact);
expect(json.stdout).not.toContain('<!doctype');
});

it('rejects an unknown format with exit 5, the value, the valid list, and empty stdout', async () => {
const file = writeArtifact();
for (const format of ['html', 'HTML', 'sarif']) {
for (const format of ['HTML', 'sarif', 'html ']) {
const result = await runCli(['report', '--in', file, '--format', format]);
expect(result.code).toBe(ExitCode.USAGE_ERROR);
expect(result.stdout).toBe('');
expect(result.stderr).toBe(
`error: unknown --format ${JSON.stringify(format)} (expected md, text, json)\n`,
`error: unknown --format ${JSON.stringify(format)} (expected md, text, json, html)\n`,
);
expect(result.stderr).not.toContain(SENTINEL);
}
Expand Down
8 changes: 6 additions & 2 deletions src/reporting/commands/report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import { usageError } from '../../util/exit.js';
import { pathExists, readJsonFile } from '../utils/fs.js';
import { formatText } from '../formatters/text.js';
import { formatMarkdown } from '../formatters/markdown.js';
import { formatHtmlReport } from '../formatters/html.js';
import type { ReportFormat, ScanArtifact } from '../types.js';

/** Accepted by `vg report --format`. `text` is the commander default. */
const REPORT_FORMATS = ['md', 'text', 'json'] as const satisfies readonly ReportFormat[];
/** Formats `vg report` accepts. `text` is the default. */
export const REPORT_FORMATS = ['md', 'text', 'json', 'html'] as const satisfies readonly ReportFormat[];

function assertReportFormat(format: string): asserts format is ReportFormat {
if ((REPORT_FORMATS as readonly string[]).includes(format)) return;
Expand Down Expand Up @@ -43,5 +44,8 @@ export const reportCommand = new Command('report')
case 'text':
console.log(formatText(artifact));
break;
case 'html':
console.log(formatHtmlReport(artifact));
break;
}
});
Loading
Loading