Dependencies and team facts

Two jobs answer questions about the whole project, not about a single translation unit. One job reports what the project depends on. The other reports what the repository attaches people’s names to. Both jobs produce results, and not console decoration. They travel to the terminal, over the server protocol and into a saved result. They take the same road as the symbol table and the findings.

Neither produces a score. The SBOM says what the manifests say; the authorship job says what git says. Every consumer prints the caveats next to the numbers, and this page states them too.

SBOM

Running it

visao --sbom --sbom-root ~/proj -- ~/proj/main.c
visao --sbom-output sbom.json --sbom-root ~/proj -- ~/proj/main.c
--sbom

Collect the dependencies and carry them in the result. The console prints a Packages section.

--sbom-output <file>

Also write the document. This flag implies --sbom. The file and the result section hold the same facts in two shapes.

--sbom-root <dir>

Where to look. The default is the directory of the first analysed input. That default fits a run over a project tree. Name the directory yourself when you analyse one file outside the tree.

--sbom-format <cyclonedx|spdx>

Document format. Default cyclonedx (CycloneDX 1.5).

The walk skips .git, target, node_modules, vendor, dist, build, out, .cache, __pycache__, .venv and venv, and every dot-directory. A vendored copy of another tree belongs to that tree, so the walk leaves it out.

What is discovered

Ecosystem

Files read

Cargo (Rust)

Cargo.toml, Cargo.lock

npm (Node.js)

package.json, package-lock.json

PyPI (Python)

requirements.txt, pyproject.toml, poetry.lock

Go modules

go.mod

vcpkg (C/C++)

vcpkg.json

Conan (C/C++)

conanfile.txt, conanfile.py, conan.lock, graph_info.json

Maven (Java)

pom.xml

Boss (Delphi)

boss.json, boss-lock.json

Versions: resolved or merely asked for

A manifest states a constraint. A lock file states a version. The analyser reads both, collapses entries on (ecosystem, name), and keeps the two facts apart:

version

The resolved version when a lock file exists. Without a lock file it is the text the manifest spelled, which can be a range.

declared

The manifest’s constraint, when it differs.

resolved

True when version came from a lock file.

• serde@1.0.219 (cargo)
  ↳ manifest asks for: 1.0.100 (version above is what the lock file resolves to)
• express@^4.18.0 (npm)
  ↳ a constraint, not a resolved version — no lock file was found

go.mod pins by construction, so its versions are exact without a lock file. requirements.txt and pyproject.toml fill the version only for == / ===; a >= leaves it empty.

Nothing contacts a registry. The analyser reads only the files in the tree. It never asks the ecosystem to resolve a range.

Licenses

Each package carries the license its manifest declared. An empty field means the manifest declared no license.

A policy file turns that into findings:

visao --sbom --sbom-license-policy policy.txt --sbom-root ~/proj -- ~/proj/main.c
# policy.txt — one verb per line, '#' comments
allow MIT
allow Apache-2.0
deny  AGPL-3.0

With any allow line the policy is a whitelist: a package passes only if one of its SPDX atoms is allowed. With deny lines alone it is a blacklist. OR expressions pass on their most permissive atom. Violations surface as sbom/license/violation findings.

Vulnerabilities

visao --sbom --sbom-vuln-db ~/osv -- ~/proj/main.c

The argument is an OSV record file or a directory of them (one JSON object per file). Matches surface as sbom/vulnerability findings and attach to their package in the result section.

The match is exact when the version is resolved. For an unresolved version the analyser uses the floor of the constraint, which is the lowest version the build can install. The flag resolved = false travels with the package, so a consumer reports that answer as an approximation. A wildcard takes the advisory’s own “no version information” branch, which stays on the safe side.

Comparing two snapshots

visao --sbom-output current.json --sbom-diff previous.json \
      --sbom-diff-output delta.json --sbom-root ~/proj -- ~/proj/main.c

The diff reports added, removed and version-bumped packages as sbom/diff/added, sbom/diff/removed and sbom/diff/bumped.

Gating a pipeline

SBOM findings are ordinary findings, so everything in CI/CD integration applies. This includes --report-format sarif and --baseline:

visao --sbom --sbom-vuln-db ~/osv --sbom-license-policy policy.txt \
      --fail-on high --report-format sarif --report-output visao.sarif \
      -- build/compile_commands.json

Coding standards

visao --standard misra-c-2012 -- src/*.c

A standard does two things. First, the rules that exist only because the standard requires them start firing. These rules cover banned library functions, braces around every control-statement body, and a default on every switch. Second, each finding that answers to one of its rules carries the rule identifier, in the console, in SARIF and over the protocol.

The run ends with a compliance summary. It states how many rules the standard has, how many this build checks, and how many stay unchecked. It names the unchecked rules.

▎ Compliance (174)
  • MISRA C:2012
    ↳ rules in the standard: 174
    ↳ checked by this build: 67
    ↳ checked rules with findings: 19
    ↳ Rule 21.3 (Required) — 2 finding(s): Use of the memory-allocation functions
    ↳ not checked by this build: 107
    ↳ Dir 1.1 (Required), Dir 3.1 (Required), …

The summary speaks for the rules this build checks. Compliance rests on the standard’s document and the project’s deviation records. This summary is evidence for that argument.

--standard-rules <file> takes a different rule set: an amended edition, or a project’s own selection. Write one rule per line as <rule>[,<category>]. The summary counts the rules from that file that this build knows, and it counts the rest apart.

MISRA findings are ordinary findings, so --report-format sarif, --baseline and --fail-on work on them as on everything else.

Team facts

Findings by author

visao --blame -- src/*.c

Visao attributes every finding to the person who last touched its line. It also prints a per-author breakdown of the rules their code trips.

``git blame`` names who last touched a line, not who introduced the defect. A rename, a reformat or a move rewrites authorship wholesale.

The authorship job

visao --authors -- src/*.c
visao --authors --history HEAD~30..HEAD -- src/*.c

--authors implies --blame and adds commit facts per author. The facts are commits, days, lines added and removed, largest and median commit, and busiest day. They also count the commits that declare an assistant co-author, the commits whose region a later commit worked again, and the commits that share a file with a later fix-shaped commit.

Without --history the job reads the last 200 commits. This bound is deliberate, because the gathering reads the whole diff of the range.

Commit facts alone

visao --history main..HEAD

Prints the same facts and exits. It needs no analysis and no files.

How to read the numbers

  • Commit and line counts measure volume, not value, and reward the wrong things when used as a target.

  • The assistant co-author line is a declaration by the author. Its absence is not evidence of anything.

  • “Region worked again later” overlaps line ranges as each commit recorded them. Lines drift, so read it as a region, not as a proof.

  • “A later fix-shaped commit” is a word match on the subject. Iteration and repair look the same from here.

Visao derives no score from these numbers.

Reviewing a change

visao --since main -- src/*.c        # only lines the change touched
visao --since main --since-whole-file -- src/*.c

--since compares the revision against the working tree, so it includes uncommitted work. A changed file usually carries older findings from outside this change. Use --since-whole-file to see them too, because a change can break a line it never touched.

Over the server

Both are job kinds, requested at start and answered in their own section of the result:

asp-cli connect tcp://127.0.0.1:5555 \
    ws:add file /proj/main.c \
    an:options sbom_root=/proj sbom_vuln_db=/proj/osv \
    start defects sbom authorship wait result

sbom also runs on its own (start sbom). Its work happens when the job instance is built, and not in an analysis pass. authorship attributes findings to an analysis, and it reports commit facts without one.

Options are set on the workspace before start, as key=value pairs:

Key

Meaning

sbom_root

directory to scan

sbom_vuln_db

OSV file or directory

sbom_license_policy

policy file

sbom_format

cyclonedx / spdx

sbom_output

write the document (server-side path)

sbom_diff_prev

previous SBOM to compare against

sbom_diff_output

where to write the diff

history_range

git range for the authorship job

blame

attribute findings without authorship

since

analyse a change against a revision

The paths are on the machine of the analyser. The result sections carry the answer for that reason: sbom_data and authorship_data arrive in the response. A client that reads no file on the server still gets the whole answer.

Facts about the data

  • A range stays a range, because nothing contacts a registry. Without a lock file the version stays a constraint, and the output says so.

  • Transitive dependencies appear only insofar as a lock file lists them; a manifest alone gives direct dependencies.

  • Vulnerability matching is only as current as the OSV data you point it at; nothing is downloaded.

  • git blame attribution is a starting point for a conversation, not an assignment of responsibility.