You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A hands-on usability audit of vettd 0.9.3 / skill-scanner 0.1.4 (built from source, Windows 11 x86_64, prod endpoint) surfaced a cluster of behaviors where the CLI reports more certainty than it has: it returns a clean bill of health on paths it never examined, shows 3 of 33 flagged artifacts, and runs for 76 minutes without indicating whether it is working or hung. A second cluster makes the public directory output insufficient to support an install decision.
Findings already covered by open issues are excluded and listed at the bottom.
A. The CLI reports success it hasn't earned
A1 — scan file <directory> and scan folder <file> report "clean" and exit 0 without scanning anything.
$ vettd scan folder <a single file> exit 0 "No AI execution artifacts detected."
vettd scan repo <a non-repo dir> is tolerant and scans correctly, so the inconsistency is between subcommands. Path validation at crates/vettd-cli/src/cli.rs:1135-1150 checks only path.exists(); a directory exists, so File passes validation and the pipeline finds no single file to scan. A file/folder mix-up returns a green light on unexamined content, in the vocabulary of safety. Related to #180 (--out given a directory), but a different surface.
A2 — The overview renders only 3 risk cards regardless of how many artifacts are flagged.
vettd scan full on this host:
RISK 33 flagged · 105 review · 647 clear (785 artifact(s))
✗ CRITICAL skill risk 100 ~\.claude\plugins\...\mcp-integration\SKILL.md
✗ CRITICAL skill risk 91 (...)
✗ CRITICAL skill risk 91 (...)
… and 782 more: (path + artifact type only — no score, no severity, no reason)
30 flagged artifacts are collapsed into the "782 more" list, visually indistinguishable from the 647 clear ones. Confirmed not lite mode: no ~/.vettd/.vettd.toml exists. Same truncation occurs in scan quick ("… and 27 more"). After a 76-minute scan the tool states 33 artifacts need attention and displays three of them.
A3 — Long scans emit nothing, and cache reuse is never disclosed.
$ vettd scan full exit 0 · 4,574,717 ms (76.2 min)
stdout: 0 bytes for the entire run, then 70,590 bytes at completion
stderr: 166 bytes total, one warning emitted at the start
$ vettd scan quick --no-cache 84,579 ms · 30 artifacts
$ vettd scan quick 10,501 ms · 30 artifacts
occurrences of "cache" in either run's stdout/stderr: 0
A progress indicator exists but is TTY-gated (cli.rs:1160-1164), so this applies to non-interactive and agent callers. An 8× timing swing with no explanation also means a user who edits a file and re-scans cannot tell whether the result is fresh or replayed. Distinct from #211, which covers cache internals rather than user-facing disclosure.
A4 — A warning advertises a --deep flag that does not exist.
stderr: warning: scan depth capped at 5; some files may have been skipped (use --deep for a full scan)
$ vettd scan quick --deep
error: unexpected argument '--deep' found
Not listed in vettd scan quick --help. Same class as #201 (post-scan guidance printing invalid commands), on a different code path. The warning is also emitted 5 times byte-identically in a single run.
B. Directory output cannot support an install decision
B1 — --json omits the identity fields the API returns, so duplicate entries are unaddressable.
The API returns two genuinely distinct records under one slug:
API -> {'slug':'us-federal-tax-assistant','id':'cmsi19j0j01bbad07e9sin0gz','publicId':'e3b15630-…','sourceUrl':'https://github.com/calef/…'}
API -> {'slug':'us-federal-tax-assistant','id':'cmsi16jfv019yad07zd98f0gr','publicId':'074c871b-…','sourceUrl':'https://github.com/calef/…'}
CLI --json keys: slug, name, description, version, author, category, badgeStatus,
overallGrade, sourceType, scannerRunCount, freshness
DirectoryCard (crates/vettd-cli/src/directory.rs:54-88) does not declare id, publicId, or sourceUrl, per the documented "limited to what we actually render" policy at directory.rs:40-44. That policy is right for a terminal table and wrong for --json, whose consumers render nothing — one struct is serving as both the human render model and the public JSON schema. Since directory view accepts only a slug, the second record is unreachable from the CLI by any route.
B2 — Duplicate entries are indistinguishable, and compare cannot disambiguate them.
[C] [ok] us-federal-tax-assistant github 2 scanners Use this skill when helping users prepare…
[C] [ok] us-federal-tax-assistant github 2 scanners Use this skill when helping users prepare…
[A] [ok] visa-doc-translate github 2 scanners Translate visa application documents (ima…
[A] [ok] visa-doc-translate github 2 scanners ビザ申請書類(画像)を英語に翻訳し…
directory view <slug> silently resolves to one record with no indication a sibling exists. directory compare <slug> <slug> with the same slug twice exits 0 and renders two identical columns without noticing. The visa-doc-translate pair differs only in description language.
B3 — Grades and freshness states have no legend, and freshness vocabulary differs per command.
Search and compare render [ok] / [offline] / [?]; view renders [current] for the same concept. Grades [A]–[F] appear with no scale, no derivation, and no explanation of what is being graded, in any help text or output. Observed mapping, undocumented: 1 medium + 12 info → B, 4 medium + 14 info → C, 1 critical + 1 medium + 17 info → F.
B4 — MCP capability scope does not affect the risk score.
All three score the mcp_config type base (risk_engine.rs:79) and nothing else. Mount scope and npx -y fetch-and-execute are not represented in any weight table. Note the scanner handles real secrets correctly — an AWS-shaped key in the same file escalates to CRITICAL/45 — so this is specifically a capability-scope gap. Adjacent to #202/#207 but not covered by either.
B5 — Risk rationale is truncated without saying so.
The highest-scoring artifact found shows risk 100 with reasons dangerous_combo:shell+network+fs (+30), keyword:shell (+15) — 45 of 100 points explained. risk_engine.rs:166 truncates the reasons list to the top 2 contributions with no "top 2 of N" label, and never shows the artifact-type base; score.min(100) at :161 clamps the total, so the pre-clamp score is unknowable. The gap scales with severity and is largest where a user most needs to understand the verdict.
Minor
Mixed path separators inside one rendered path: ~\AppData\Roaming\Code/User\globalStorage\...\settings/.
YAML block-scalar markers leak into descriptions: directory list shows descriptions of | and >- for feishu-wiki, bigquery-basics, firebase-basics. Present in the raw API response, so platform-side, but the CLI is where users meet it.
README Quick start line 1 is wrong: documents vettd as an "Interactive wizard"; bare vettd prints help and exits 0 in both TTY and non-TTY modes. No wizard:: call is reachable without a subcommand — the picker is at cli.rs:1122 inside the scan handler. docs/user-flows.md describes this correctly.
scan file given a directory, and scan folder given a file, fail with a message naming the correct subcommand and exit non-zero
A scan reporting N flagged artifacts displays all N; truncation applies only to artifacts below the flagged threshold
Non-interactive scans emit periodic progress to stderr, or the absence of progress in non-interactive mode is a documented decision
Scan output states when results were served from cache and how to force a fresh scan
The depth-cap warning names a flag that exists, and is emitted once per run
directory search --json includes id, publicId, and sourceUrl; view / findings / compare accept a publicId
directory view on an ambiguous slug indicates that more than one record matched; compare warns when given the same identity twice
Grade and freshness values carry a legend reachable from CLI output or help
search, view, and compare use one freshness vocabulary
MCP filesystem mount scope contributes to the risk score, with a regression test asserting root-scope scores above project-scope
Risk rationale shows the artifact-type base and labels any truncation, or shows all contributions
README no longer describes bare vettd as an interactive wizard, and no longer claims the full scan is uncapped
Audit environment: vettd 0.9.3 / skill-scanner 0.1.4, built from source at b0845a0; Windows 11 26200 x86_64; prod endpoint https://vettd.agentichighway.ai; contract v2.4.0. All evidence above is from observed command output. Interactive/TTY paths were not tested — no PTY was available in the audit environment — and authenticated flows (inventory, --submit) were not exercised for lack of a valid API key.
Description
A hands-on usability audit of
vettd 0.9.3/skill-scanner 0.1.4(built from source, Windows 11 x86_64, prod endpoint) surfaced a cluster of behaviors where the CLI reports more certainty than it has: it returns a clean bill of health on paths it never examined, shows 3 of 33 flagged artifacts, and runs for 76 minutes without indicating whether it is working or hung. A second cluster makes the public directory output insufficient to support an install decision.Findings already covered by open issues are excluded and listed at the bottom.
A. The CLI reports success it hasn't earned
A1 —
scan file <directory>andscan folder <file>report "clean" and exit 0 without scanning anything.Same directory, two commands:
The mirror case fails the same way:
vettd scan repo <a non-repo dir>is tolerant and scans correctly, so the inconsistency is between subcommands. Path validation atcrates/vettd-cli/src/cli.rs:1135-1150checks onlypath.exists(); a directory exists, soFilepasses validation and the pipeline finds no single file to scan. Afile/foldermix-up returns a green light on unexamined content, in the vocabulary of safety. Related to #180 (--outgiven a directory), but a different surface.A2 — The overview renders only 3 risk cards regardless of how many artifacts are flagged.
vettd scan fullon this host:30 flagged artifacts are collapsed into the "782 more" list, visually indistinguishable from the 647 clear ones. Confirmed not lite mode: no
~/.vettd/.vettd.tomlexists. Same truncation occurs inscan quick("… and 27 more"). After a 76-minute scan the tool states 33 artifacts need attention and displays three of them.A3 — Long scans emit nothing, and cache reuse is never disclosed.
A progress indicator exists but is TTY-gated (
cli.rs:1160-1164), so this applies to non-interactive and agent callers. An 8× timing swing with no explanation also means a user who edits a file and re-scans cannot tell whether the result is fresh or replayed. Distinct from #211, which covers cache internals rather than user-facing disclosure.A4 — A warning advertises a
--deepflag that does not exist.Not listed in
vettd scan quick --help. Same class as #201 (post-scan guidance printing invalid commands), on a different code path. The warning is also emitted 5 times byte-identically in a single run.B. Directory output cannot support an install decision
B1 —
--jsonomits the identity fields the API returns, so duplicate entries are unaddressable.The API returns two genuinely distinct records under one slug:
DirectoryCard(crates/vettd-cli/src/directory.rs:54-88) does not declareid,publicId, orsourceUrl, per the documented "limited to what we actually render" policy atdirectory.rs:40-44. That policy is right for a terminal table and wrong for--json, whose consumers render nothing — one struct is serving as both the human render model and the public JSON schema. Sincedirectory viewaccepts only a slug, the second record is unreachable from the CLI by any route.B2 — Duplicate entries are indistinguishable, and
comparecannot disambiguate them.directory view <slug>silently resolves to one record with no indication a sibling exists.directory compare <slug> <slug>with the same slug twice exits 0 and renders two identical columns without noticing. Thevisa-doc-translatepair differs only in description language.B3 — Grades and freshness states have no legend, and freshness vocabulary differs per command.
Search and compare render
[ok]/[offline]/[?];viewrenders[current]for the same concept. Grades[A]–[F]appear with no scale, no derivation, and no explanation of what is being graded, in any help text or output. Observed mapping, undocumented:1 medium + 12 info → B,4 medium + 14 info → C,1 critical + 1 medium + 17 info → F.B4 — MCP capability scope does not affect the risk score.
All three score the
mcp_configtype base (risk_engine.rs:79) and nothing else. Mount scope andnpx -yfetch-and-execute are not represented in any weight table. Note the scanner handles real secrets correctly — an AWS-shaped key in the same file escalates to CRITICAL/45 — so this is specifically a capability-scope gap. Adjacent to #202/#207 but not covered by either.B5 — Risk rationale is truncated without saying so.
The highest-scoring artifact found shows
risk 100with reasonsdangerous_combo:shell+network+fs (+30), keyword:shell (+15)— 45 of 100 points explained.risk_engine.rs:166truncates the reasons list to the top 2 contributions with no "top 2 of N" label, and never shows the artifact-type base;score.min(100)at:161clamps the total, so the pre-clamp score is unknowable. The gap scales with severity and is largest where a user most needs to understand the verdict.Minor
~\AppData\Roaming\Code/User\globalStorage\...\settings/.directory listshows descriptions of|and>-forfeishu-wiki,bigquery-basics,firebase-basics. Present in the raw API response, so platform-side, but the CLI is where users meet it.Quick startline 1 is wrong: documentsvettdas an "Interactive wizard"; barevettdprints help and exits 0 in both TTY and non-TTY modes. Nowizard::call is reachable without a subcommand — the picker is atcli.rs:1122inside thescanhandler.docs/user-flows.mddescribes this correctly.discovery.rs:24setsMAX_ROOT_SCAN_FILES = 500_000and the binary warns accordingly at runtime. Runtime behavior is correct; the doc is not. Related to Scanner silently truncates its scan surface and still reports success #204/Docs, dead code, and stale tooling have drifted from the real codebase #210.Scope
In scope:
scan file/scan folder(A1)--deepwarning text and its duplicate emission (A4)DirectoryCardfield coverage for--json, and slug-ambiguity handling inview/compare(B1, B2)search/view/compare(B3)Out of scope:
vettd directory download/ install flows — tracked in Epic:vettd directory downloadcommand #176, Addvettd directory downloadcommand #169, Spike: end-to-end architecture fordirectory download#175, Spike: versioning strategy fordirectory download#170, Spike: scan-at-download strategy fordirectory download#171--json/--stdoutredundancy — tracked in Machine-readable output flags are inconsistent, and post-scan guidance prints invalid commands #201MAX_FILES) — tracked in Scanner silently truncates its scan surface and still reports success #204deep_keyword— tracked in Consolidate the hand-synced scoring/capability tables and fix the dead deep_keyword path #202, Custom detection rules cannot affect risk scores despite what the docs promise #207Acceptance Criteria
scan filegiven a directory, andscan foldergiven a file, fail with a message naming the correct subcommand and exit non-zerodirectory search --jsonincludesid,publicId, andsourceUrl;view/findings/compareaccept apublicIddirectory viewon an ambiguous slug indicates that more than one record matched;comparewarns when given the same identity twicesearch,view, andcompareuse one freshness vocabularyvettdas an interactive wizard, and no longer claims the full scan is uncappedAudit environment:
vettd 0.9.3/skill-scanner 0.1.4, built from source atb0845a0; Windows 11 26200 x86_64; prod endpointhttps://vettd.agentichighway.ai; contract v2.4.0. All evidence above is from observed command output. Interactive/TTY paths were not tested — no PTY was available in the audit environment — and authenticated flows (inventory,--submit) were not exercised for lack of a valid API key.