Skip to main content

Scan artifact reference

Every scan is one JSON file, the artifact. scout scan --dry-run writes it to scout-scan.json next to the config; an upload sends it to the dashboard without writing it. This page lists every field in it.

Top-level shape​

The file is one JSON object with four keys, written in this order:

KeyTypeContents
metaobjectWhich repo and commit was scanned, when, and by which CLI version. See meta.
componentsarrayOne entry per component. See components[].
occurrencesarrayOne entry per place a component is used, including places the scan couldn't tie to a component. See occurrences[].
diagnosticsarrayThings the scan saw but couldn't follow. See diagnostics[].

Most questions are answered from components and occurrences. A resolved occurrence names its component by the component's id.

note

A newer CLI can add values to some lists: credit.kind, a trace step's kind, an unresolved occurrence's reason.kind and reason.code, and the confidence, strongestClass, reason, source, strength and disposition values in a tag's attribution. If you read the file with your own scripts, skip values you don't recognise. The other lists of values in components[] and occurrences[] change only with a new meta.schemaVersion.

Paths​

Every path in the file is relative to the repository root and uses / separators, except a few diagnostic fields that the diagnostics reference notes. The root is the top-level folder of the git work tree, or the folder given to --repo-root. Scanning from a subdirectory gives the same paths as scanning from the root.

components[]​

One entry per component. A component appears when the scan finds at least one place it is used. A component defined in the repo also appears when its own code uses a component, even if nothing renders it, such as the App at the top of the tree.

FieldTypeDescription
idstring16-character id computed from identity. The same identity gets the same id in every scan, so you can match a component across scans. Occurrences and composition refer to components by this value.
identityobjectWhat the component is and where it comes from. See Component identity.
framework"react" | "vue" | absentThe framework the component is written in. Absent on web components.
stats.occurrenceCountnumberNumber of resolved occurrences of this component, including those where it is passed to a call (see credit).
stats.fileCountnumberNumber of distinct files those occurrences are in.
usage"direct" | "root" | "none"Why the component is listed. See usage.
propsobjectFor each prop name, how often each value was passed. See Prop value counts.
compositionobjectWhich components this one renders and is rendered by. Present on every entry. See composition.
versionstring | nullInstalled version of the component's package, read from its package.json in node_modules: the package of a package export, or the package a tag belongs to. null for components defined in the repo, for tags with no package, and when no version is found.

These fields are less often needed:

FieldTypeDescription
attributionobject | absentTags only: which package or repo file defines the web component. See attribution.
eventsobject | absentEvent name to { "boundCount": number }, the number of occurrences that bind it: Vue listeners such as @click, and React props named on plus a capital letter, such as onClick. Absent when no occurrence binds an event.
writtenNamesstring[] | absentThe other names files render the component under: every distinct writtenName of its occurrences, most used first. Absent when none has one.
declaredobject | absentComponents defined in the repo only: the props the component's own code declares. See declared.
definition{ line, column } | absentReact components defined in the repo only: where the declaration starts in identity.filePath. line counts from 1 and column from 0.
owningPackagestring | absentComponents defined in the repo only: the name of the workspace package whose folder holds the file. Absent when the file isn't in a workspace package.
A components[] entry
{
"id": "143bee555932e50c",
"identity": { "kind": "package-export", "packageName": "@acme/ui", "publicEntry": "", "exportName": "Button" },
"framework": "react",
"stats": { "occurrenceCount": 2, "fileCount": 2 },
"usage": "direct",
"props": {
"variant": {
"values": [
{ "provenance": "written", "value": "secondary", "count": 1 },
{ "provenance": "written", "value": "primary", "count": 1 }
],
"dynamic": 0,
"omitted": 0
}
},
"version": "4.2.0",
"composition": {
"rendersByCount": {},
"renderedByCount": { "0a12a8f48dc0e818": 1, "98464c541cecabae": 1 },
"isRootCount": 0,
"isLeafCount": 2
}
}

Component identity​

identity.kind says which of three shapes the identity has. Every field of the identity goes into id.

kindFieldsWhat it is
package-exportpackageName, publicEntry, exportNameA component imported from an installed package, such as Button from @acme/ui. See From a package.
repository-declarationrepoId, filePath, exportNameA component defined in the scanned repo, including one in another workspace package of the same repo. See Defined in the repo.
tagtagNameA web component, such as <acme-badge>, whichever package or file defines it. Any tag with a hyphen in its name that nothing imports counts, so a Vue component registered globally at runtime, such as vue-i18n's <i18n-t>, is a tag too. The few SVG and MathML elements with a hyphen, such as <font-face>, are not.
FieldInDescription
packageNamepackage-exportThe package that defines the component.
publicEntrypackage-exportThe package entry the component was reached through: the path after the package name, without a file extension, such as react for @acme/ui/react. "" for the package root.
exportNamepackage-export, repository-declarationThe component's name. For a package export, the name the package exports it under, default for a default export. For a component defined in the repo, its declared name, default when a default export has no name. A name with a dot, such as Dialog.Popup, is a member of an exported object: the part before the first dot is the export, the rest is the member.
repoIdrepository-declarationSame value as meta.repo.id. The same file scanned under two repo ids gives two components.
filePathrepository-declarationThe file the component is defined in.
tagNametagThe tag name, lowercased: <Acme-Badge> gives acme-badge.

When you import a component through a package that re-exports it from another package, packageName and publicEntry name the package that defines it and the entry the re-exports lead into, not the package in your import.

identity examples
{ "kind": "package-export", "packageName": "@acme/ui", "publicEntry": "", "exportName": "Button" }

{ "kind": "repository-declaration", "repoId": "storefront", "filePath": "src/components/ProductCard.tsx", "exportName": "ProductCard" }

{ "kind": "tag", "tagName": "acme-badge" }

attribution​

Tags only. Which package or repo file defines the web component, decided from the evidence this scan found. The same tag can be attributed differently in two repos.

Evidence is observed when the scanned repo registers the element itself, and declared when a manifest or a type declaration claims it. Observed evidence outranks declared.

statusOther fieldsMeaning
"resolved"target, confidence, evidenceOne target won. confidence is "observed" or "declared": the kind of evidence that decided it.
"unknown"reason, evidenceNo target. reason is "absent" when the scan found no evidence, or "unresolved" when it found evidence that names no target.
"conflict"strongestClass, candidates, evidenceSeveral targets tie in the strongest kind of evidence, strongestClass ("observed" or "declared"). candidates lists them.

A target, in target or candidates, is one of:

  • { "kind": "package", "packageName": string }
  • { "kind": "repository", "repoId": string, "filePath": string, "exportName": string }

Each entry in evidence is one record:

FieldTypeDescription
source"registration" | "cem" | "global-declaration"registration: a customElements.define() call or @customElement() decorator in the scanned repo. cem: the Custom Elements Manifest of an installed package, when the package's package.json points at it with a customElements field. global-declaration: an entry keyed by a hyphenated name, such as 'acme-panel', in the GlobalComponents declarations of Nuxt's generated components file, .nuxt/components.d.ts or .nuxt/types/components.d.ts.
strength"observed" | "declared"observed for a registration, declared for the other two sources.
locator{ filePath, line } or { packageName, version }Where the evidence is: a file and line for a registration or a global declaration, or the package and its installed version (null when unknown) for a manifest.
targettarget | absentWhat the record points at. Absent when it names nothing the scan can find, such as a registration whose class isn't found.
disposition"supports" | "candidate" | "contradicts" | "unresolved"How the record relates to the outcome: it names the resolved target (supports), one of a conflict's candidates (candidate), a target the outcome didn't pick (contradicts), or no target (unresolved).
attribution for <acme-badge>
{
"status": "resolved",
"target": { "kind": "package", "packageName": "@acme/elements" },
"confidence": "declared",
"evidence": [
{
"source": "cem",
"strength": "declared",
"locator": { "packageName": "@acme/elements", "version": "1.0.0" },
"target": { "kind": "package", "packageName": "@acme/elements" },
"disposition": "supports"
}
]
}

usage​

Every component has exactly one value. The first row that applies wins.

ValueWhen
directThe component has at least one resolved occurrence.
rootA component defined in the repo, with no resolved occurrences, that is the default export of a framework entry file: {page,layout,template,error,loading,not-found}.{tsx,jsx} anywhere under an app/ folder, or any .tsx or .jsx file under a pages/ folder except pages/api/. The folders can be at any depth, such as src/app/.
noneAnything else, such as a component defined in the repo that renders others but that nothing renders.

Prop value counts​

props has one key per prop name passed at any of the component's resolved occurrences. Each value is:

FieldTypeDescription
valuesarrayOne entry per distinct value, most frequent first. See the value shapes below.
dynamicnumberOccurrences where the value is an expression the scan doesn't read, such as a function call or an arrow function.
omittednumberOccurrences that don't pass the prop.
othernumber | absentOccurrences whose value isn't in values because the prop has more than 100 distinct values. values keeps the 100 most frequent. Absent at 100 distinct values or fewer.
truncatednumber | absentHow many distinct values were left out of values. Absent at 100 distinct values or fewer.

For every prop, the counts in values plus dynamic, other and omitted add up to stats.occurrenceCount.

values[] entryMeaning
{ "provenance": "written", "value": ..., "count": n }A literal string, number, boolean or null. A prop written with no value, such as disabled, counts as true.
{ "provenance": "written", "valueSet": [...], "count": n }React only. A condition that picks between literals, such as sale ? "primary" : "secondary". valueSet holds the literals, sorted.
{ "provenance": "reference", "ref": "...", "count": n }A variable or a dotted path, such as label or theme.icon, recorded by name. The scan doesn't look up its value.

declared​

The props a component defined in the repo declares in its own code. Read from a destructured first parameter in React (function ProductCard({ title, size = "md" })) and from defineProps in Vue. Absent when the props can't be read that way.

FieldTypeDescription
propsobjectProp name to { type, required, default }, in source order. Each field is present only when the code states it. type: Vue only, such as "number" or "String". required: false for a destructured React prop with a default; in Vue, from ? in a type or required: in a props object. default: set when the default is a literal.
hasRestbooleantrue when the component collects the remaining props (...rest) or declares an index signature.
declared for a React ProductCard component
{ "props": { "title": {}, "size": { "required": false, "default": "md" } }, "hasRest": false }

composition​

Which components this one renders and which render it. Component A renders component B when a place B is used sits inside A's own code, so A is that occurrence's ownerComponentId. Only resolved occurrences count. Composition: renders and rendered by explains how this is worked out.

FieldTypeDescription
rendersByCountRecord<string, number>Component id of each component this one renders, to how many times it renders it.
renderedByCountRecord<string, number>Component id of each component that renders this one, to how many times it does.
isRootCountnumberOccurrences of this component that no component renders, such as a use outside any component.
isLeafCountnumberEqual to stats.occurrenceCount when rendersByCount is empty, otherwise 0.

Keys are components[].id values. Look them up in components[] to get names. Only ids that are in components[] appear.

occurrences[]​

One entry per occurrence: a place in the code that uses a component, usually one tag. A tag inside a helper function that several components call gets one occurrence per calling component. The array also holds unresolved occurrences, places where the scan saw a component used but couldn't tell which one.

FieldTypeDescription
occurrenceIdstring16-character id computed from what the occurrence names, its position and its owner. Unique within the artifact.
resolutionobjectWhich component this is, or why the scan couldn't tell. See resolution.
filePathstringThe file the use is in.
linenumberLine of the use, counted from 1.
columnnumberColumn of the use: counted from 0 in React files and from 1 in Vue files.
creditobjectWhether the component is rendered here or passed to a call. See credit.
tracearrayThe steps between this use and the component, such as an import or a wrapper. See trace.
writtenNamestring | absentThe name this file renders the component under, with any member path, such as SettingsHeader after import SettingsHeader from "./Header", or Filters.FilterBar. Present only on a resolved occurrence, and only when the name differs from the component's own exportName, or tagName for a tag. A difference in letter case or hyphens alone doesn't count. A package's default export has no name of its own, so it always has one.
propsobjectThe value passed for each prop here. See Prop values at one place.
eventsstring[] | absentVue event listeners bound here, such as remove for @remove. React handlers stay in props. Absent when none are bound.
ownerComponentIdstring | absentThe components[].id of the component whose code contains this use. Absent outside any component.
An occurrences[] entry
{
"occurrenceId": "c23610a099090c05",
"resolution": { "status": "resolved", "componentId": "143bee555932e50c" },
"filePath": "src/components/ProductCard.tsx",
"line": 7,
"column": 6,
"credit": { "kind": "render" },
"trace": [{ "kind": "import", "specifier": "@acme/ui", "name": "Button" }],
"props": { "variant": { "tier": "written", "value": "primary" } },
"ownerComponentId": "98464c541cecabae"
}

resolution​

statusOther fieldsMeaning
"resolved"componentIdThe components[].id of the component used.
"unresolved"reasonThe scan couldn't tell which component this is. See Unresolved occurrences.

Unresolved occurrences​

An unresolved occurrence is a place where the scan saw a component used but couldn't tie it to one. It has no componentId, and nothing in components[] stands for it. It keeps its file, position, credit, trace, props and owner, but doesn't count toward any component's stats, props or composition. When the use came through an import, trace keeps that import as written, so it shows which import failed. The scan summary counts unresolved occurrences, as in Scout couldn't match 4 more occurrences to a component.

reason.kind says why:

reason.kindOther fieldsMeaning
package-not-installedpackageNameThe import names a package that a package.json declares, either the importing file's workspace package or the workspace root, but the package isn't installed. Each such package also gets a dependency-not-installed diagnostic. A package in dependencies or devDependencies that isn't installed stops scan before it scans, unless it's a dry run. One listed only in peerDependencies or optionalDependencies doesn't: the scan uploads with these occurrences unresolved.
module-not-foundnoneThe import can't be followed: a path to a missing file, a path to a file outside the scanned folder (outside the monorepo, when the folder is part of one), an alias your config or tsconfig.json doesn't define, or a package that no package.json declares and that isn't installed.
unbound-namenameNothing in the file imports or declares the name, such as <Widget /> with no import. In a Vue template it also covers a tag whose import lacks the name, such as <Menu.Item> when the imported Menu has no Item.
chain-bailedcodeFollowing a package's re-exports to the file that defines the component looped back on itself ("cycle-detected") or passed through more than 32 files ("chain-too-deep"). The same problem is reported as a diagnostic.
An unresolved occurrence: @acme/ui is declared but not installed
{
"occurrenceId": "742a11f2ccdeec08",
"resolution": {
"status": "unresolved",
"reason": { "kind": "package-not-installed", "packageName": "@acme/ui" }
},
"filePath": "src/App.tsx",
"line": 8,
"column": 6,
"credit": { "kind": "render" },
"trace": [{ "kind": "import", "specifier": "@acme/ui", "name": "Button" }],
"props": { "variant": { "tier": "written", "value": "secondary" } },
"ownerComponentId": "0a12a8f48dc0e818"
}

When a component you expect is missing, see Troubleshoot a scan.

credit​

kindOther fieldsMeaning
"render"noneThe component is rendered here: a JSX element or a Vue template tag.
"argument"callee, indexThe component is passed to a call instead of rendered, such as makeControl(Input) or useModal(ConfirmDialog). callee names the function and index is the argument position, from 0. These occurrences have no props.

A component rendered through a wrapper, such as const Save = memo(Button) then <Save />, is a render of Button with a hoc step in its trace.

trace​

The steps between the use and the component, outermost first. A plain import has one import step. The list is empty when nothing needs explaining, such as a component defined in the same file.

kindOther fieldsStep
importspecifier, nameAn import the use goes through: the import path and the imported name as written (default for a default import, * for a namespace import).
tagwrittenA web component whose tag is written with capitals, such as Acme-Badge. identity.tagName holds the lowercased name.
hoccalleeA function that wraps the component and returns a new one, such as memo, forwardRef or connect.
lazycalleeA lazy-loading wrapper, such as lazy(() => import("./Checkout")) or Next.js dynamic.
dynamic-mapmapName, mapLoc: { file, line, column }The component is picked from an object of components, such as icons[kind]. Every component in the object gets an occurrence. mapLoc is where the object is defined.
helper-callcallee, calleeFileThe element is inside a function that isn't a component, such as a helper that returns JSX. The element gets one occurrence per component that calls the helper.
prop-forwardbindingName, constructionSite: { file, line, column }The element is created outside any component, such as const badge = <Icon />, and used inside one. constructionSite is where it is created.

The column in mapLoc and constructionSite counts from 0.

trace for a Checkout component loaded with lazy()
[
{ "kind": "lazy", "callee": "lazy" },
{ "kind": "import", "specifier": "./Checkout", "name": "default" }
]

Prop values at one place​

props has one key per prop passed. A prop that isn't passed has no key.

ValueMeaning
{ "tier": "written", "value": ... }A literal string, number, boolean or null. A prop written with no value counts as true.
{ "tier": "written", "valueSet": [...] }React only. A condition that picks between literals, such as sale ? "primary" : "secondary".
{ "tier": "reference", "ref": "..." }A variable or dotted path, recorded by name, such as label or theme.icon.
{ "tier": "dynamic" }Any other expression. React props named on plus a capital letter are always dynamic.

A spread such as {...rest} in React or v-bind="obj" in Vue is recorded as a prop named ...rest with { "tier": "dynamic" }.

meta​

FieldTypeDescription
schemaVersionnumberVersion of the file format: 2.
scannerNamestringPackage name of the CLI that wrote the file: @scoutui/cli. The dashboard refuses a scan without it.
scannerVersionstringVersion of the CLI that wrote the file.
scanIdstringULID, new for every scan.
scannedAtstringWhen the scan ran, as an ISO 8601 UTC timestamp. The dashboard dates a scan by repo.committedAt, not by this field.
repo.idstringThe repo id. Repo identity says how it's chosen.
repo.gitRemotestring | nullURL of the remote: the one git config scout.remote names, else upstream when there is one, else the only remote, else origin. It's the URL git remote get-url gives, so url.<base>.insteadOf rewrites apply, with an SSH host alias replaced by the host name ssh -G gives for it. When that host name is a subdomain of the host as written, as with GitHub's, GitLab's and Bitbucket's port-443 endpoints (Host github.com with HostName ssh.github.com), the host is kept as written. Any user:password@ in an https:// URL is left out. null when there is none.
repo.commitstringSHA of the checked-out commit. The dashboard keeps one scan per repo id and commit: it skips a scan of a commit it already has, unless the scan is uploaded with --rescan or the dashboard couldn't prepare the stored scan: then it replaces it.
repo.committedAtstringCommitter date of that commit, as an ISO 8601 UTC timestamp.
repo.branchPositionnumber (optional)In an uploaded scan: how many commits the tracked branch's first-parent history has up to and including repo.commit. The dashboard uses it to order scans of commits with the same date. Absent on a dry run.
repo.initialCommitstring | nullSHA of the first commit in the history. null in a shallow clone, so fetch full history in CI.
repo.branchstring | nullIn an uploaded scan, the branch the dashboard tracks. On a dry run, the checked-out branch, null on a detached HEAD.
meta
{
"schemaVersion": 2,
"scannerName": "@scoutui/cli",
"scannerVersion": "0.1.0",
"scanId": "01M3HSR732T792S6G1PCAVYKXQ",
"scannedAt": "2026-09-27T16:04:14.021Z",
"repo": {
"id": "storefront",
"gitRemote": "git@github.com:acme/storefront.git",
"commit": "8676019952a756dacb00ed21a406485f6cd83492",
"committedAt": "2026-09-24T14:00:00.000Z",
"initialCommit": "8676019952a756dacb00ed21a406485f6cd83492",
"branch": "main"
}
}

diagnostics[]​

Things the scan saw but couldn't follow, such as a component passed in as a prop, or a package whose re-exports loop. A use the scan couldn't tie to a component because of a missing file, an uninstalled package or a name nothing imports isn't a diagnostic: it is an unresolved occurrence.

FieldTypeDescription
codestringWhat the scan saw. The diagnostics reference lists every code.
severity"warning" | "info"warning: something you can usually fix, or a shape the scan doesn't support. info: a render the scan couldn't tie to a component, usually with nothing to fix.
filePathstring | absentThe file the diagnostic is about. Absent on dependency-not-installed, which names the package.json in declaredIn instead.
other fieldsDepend on the code. The diagnostics reference lists them.
A diagnostics[] entry
{ "code": "dependency-not-installed", "severity": "warning", "packageName": "@acme/ui", "occurrenceCount": 3, "declaredIn": "package.json" }