| name | dxkit-action |
| description | Read a dxkit report and execute fixes — prioritize findings by severity, plan the fix sequence, run the fix, verify the score moved, re-baseline if appropriate. Supports a SCOPED pass to burn down one category at a time (dependency/BOM vulnerabilities, security, code quality, tests, docs), and a BASELINE-CLEANUP pass driven by `vyuh-dxkit debt` (the prioritized inventory of grandfathered debt — the broken build / failing tests recorded in the baseline's floor-debt envelope, then findings by severity; fix the build first). Use when the user says "fix these findings", "act on the health report", "close out these vulnerabilities", "just fix the dependency vulns", "only the security findings", "clean up the baseline", "burn down the debt", "fix the build the baseline grandfathered", "pay down the grandfathered findings", or after dxkit-reports / doctor's debt recommendation has surfaced something concrete. |
dxkit-action
This skill takes a dxkit report and drives the fix loop with the user. Reach for it after dxkit-reports has surfaced concrete findings.
Cleaning the BASELINE (grandfathered debt): start from debt
For a whole-baseline cleanup pass — the deployment where the gates are armed
and your job is to burn down what they grandfathered — start from the composed
inventory instead of a single report:
npx vyuh-dxkit debt --json
It returns one priority-ordered plan across BOTH debt classes:
- Correctness-floor debt (broken build, failing tests) — live-run, with
the reproduction command and captured error output per failing check, and
provenance vs the baseline's
floorDebt envelope (failing since baseline
= the debt you're here to fix; new since baseline = the gate's business,
it already blocks). Fix the build first — until it compiles, nothing
else is reliably measurable.
- Finding debt (secrets, CVEs, SAST, lint backlog) — fingerprinted
baseline entries grouped by severity; drive each group with the scoped
action loop below.
The command is informational and always exits 0 — the gates do the blocking,
and they also hold the ratchet: once you fix a floor check, any regression is
net-new and blocks automatically. No re-baselining is needed for floor fixes;
finding fixes surface as removed on the next check and leave the baseline
on the next re-capture.
The action loop
[1] Read the report → understand what's flagged
[2] Prioritize → severity + reachability + blast radius + cost
[3] Plan → ordered list of edits
[4] Execute → fix one finding at a time
[5] Verify → re-run the analyzer, confirm score moved
[6] Decide on baseline → commit fix or accept-as-baseline
Don't skip [5]. Re-running the analyzer is the only way to confirm the fix landed correctly.
For the richest input, read the detailed report with graph context attached:
npx vyuh-dxkit vulnerabilities --detailed --graph-context
--graph-context adds a "Graph context" column (the module a finding lives in + its blast radius — how many files call into it) so you can plan the fix without separately discovering structure. It's a structural HINT, not ground truth — read "Graph context" below for how to use it safely.
Add --attribute when you need to know who to ask about a pre-existing finding — it adds a "Who to ask" column from git blame routed through the active-owner model, so an inactive author is forwarded to the file's current owner (names + @handles, never emails). It's opt-in and historical only: a net-new finding the guardrail just blocked was introduced by your own change, so "who to ask" there is simply the PR author — no blame needed. Honor the honesty caveat (blame is last-touch, not necessarily who introduced it).
Scoped fix — fix one category at a time
Often the user doesn't want "fix everything" — they want to burn down one
category: "just the dependency vulns", "only the security findings", "the
code-quality stuff." Honor that. A scoped pass keeps the change reviewable and
lets the team sequence the work.
When the user names a scope (or you propose one), run the report that already
partitions that dimension and work only that worklist — prioritization
(severity → reachability → blast radius) still applies, but within the scope:
| Scope the user asks for | Run | Findings in scope | Notes / hand-off |
|---|
| "dependency / BOM vulnerabilities" | npx vyuh-dxkit bom (or vulnerabilities --detailed) | dep-vuln | upgrade-first; see "Dependency vulnerability" below |
| "security" | npx vyuh-dxkit vulnerabilities --detailed --graph-context | code-SAST, secrets, dep-vuln | reachable findings first (the report orders them); secrets always top |
| "code quality" | npx vyuh-dxkit quality --detailed --graph-context | duplication, code-reimplementation, slop, lint, complexity | see "Slop / code-pattern finding" + "Structural duplicate" below |
| "tests" / "coverage" | npx vyuh-dxkit test-gaps --detailed --graph-context | test-gap | hand off to dxkit-test for a real test-writing push |
| "documentation" | npx vyuh-dxkit health --detailed (Documentation) | doc-gap | hand off to dxkit-docs |
Rules for a scoped pass:
- Stay in the lane. If the user said "just deps," don't also start fixing
SAST findings you notice — surface them ("I also see 3 HIGH SAST findings;
want a separate pass?") but don't expand scope unasked.
- Tests / docs scopes are hand-offs, not inline work. Closing test gaps or
writing docs is a dedicated loop — route to
dxkit-test / dxkit-docs
rather than half-doing it here.
- Verify within the scope. Step [5] re-runs the scope's own report; the
scoped dimension should move and nothing else should regress (guardrail). For a
SUPPRESSION (you allowlisted a finding — inline
dxkit-allow: or file-level —
rather than fixing it), the raw count is unchanged BY DESIGN (raw-truth model):
verify instead that the finding now prints (allowlisted: <category>) in the
report and that the score moved, or confirm the gate impact with
vyuh-dxkit guardrail check (a suppressed finding drops out of the verdict and
is counted under "suppressed"). Do not expect the raw total to drop.
Without a named scope, work the full Priority order below.
Priority order
Walk findings in this order (highest to lowest):
- CRITICAL secrets (leaked credentials) — these are public-internet-facing. Stop everything and rotate.
- CRITICAL / HIGH SAST findings in primary-architecture paths (controllers/handlers/services for backend; components/pages for frontend).
- CRITICAL / HIGH dep-vulns with known exploits + a patched version available.
- HIGH test-gap findings on primary-architecture files.
- MEDIUM SAST / dep-vuln.
- LOW anything (often defer to backlog).
Skip items where reachability is "no" (graphify can't find a call path) UNLESS the finding is a secret leak (those don't depend on reachability).
Graph context (structural blast radius)
When the report was generated with --graph-context, each finding carries a "Graph context" cell: the module/role it belongs to and its blast radius (role · N caller files) — how many other files call into the finding's file. Use it to sharpen prioritization and planning, under three hard rules.
1. Additive only — it never overrides severity or reachability. Blast radius is a tie-breaker between findings of similar severity, not a re-ranking of the priority list above. Among two HIGH findings, fix the one with the larger blast radius first (more depends on it). A LOW finding never jumps a HIGH one because its blast radius is bigger.
2. A blank or zero blast radius is NOT "safe to change". The cell reads blast radius n/a (call graph) for languages whose call graph the analyzer can't resolve (C# is the known case — cross-assembly references aren't followed, so heavily-used files look like they have zero callers). Treat n/a — and even a literal 0 caller files — as unknown, never as evidence the file is safe to edit freely. When blast radius is n/a, fall back to the module/role label (that part is reliable) and verify callers the normal way (grep / read) before a risky edit. Do not deprioritize a real finding just because its blast radius is empty.
3. Confirm the symbol before you act on it. The context may name an enclosing symbol (the function the finding sits in). It's a best-effort guess (the graph stores declaration lines, not end lines), so open the file and confirm the finding is actually inside that symbol before editing or writing a test against it.
Used within those rules, the win is concrete: a high blast radius tells you which caller files to re-check and re-test in step [5] after the fix, and the module label orients you fast. Same-name symbols can inflate the count — a suspiciously huge number is usually conflation, not reality.
Common fix recipes
Secret in code
npx vyuh-dxkit vulnerabilities --json | jq '.summary.findings'
Don't try to redact the secret in place — the git history still has it. Rotation is the only true fix.
Secrets in test files — TRIAGE, don't assume
dxkit deliberately does not lower a secret's severity just because it sits in a test file: the scanner can't tell a throwaway fixture from a real credential someone pasted into an integration test, and a real leak in a test is still a leak. So test-file secrets reach you at full severity, and the report flags how many are test-located — that's your cue to triage each one, not to dismiss them:
- Open the line and judge the value. Obvious throwaway fixture (
'password1', 'changeme', 'sk_test_xxxx', low-entropy / dictionary)? Or does it look like a real credential (high-entropy token, a real-looking host/user/password, something that would actually authenticate)?
- Fixture → allowlist it.
vyuh-dxkit allowlist add <file>:<line> --category=test-fixture --reason="...". This is the right tool: it records why, and test-fixture (like false-positive) lifts the finding from the Security score, not just the guardrail — so a properly-triaged test file stops dragging the score down. Don't waste effort "rotating" a fake value.
- Real credential → treat it as a live leak. Rotate in the provider, remove from the file, scrub git history (
git filter-repo / BFG). Being in a test does not make it safe.
- Unsure? Ask the developer before allowlisting — never allowlist a value you haven't confirmed is fake.
Allowlisting under test-fixture is a deliberate per-finding judgment, not a blanket "ignore tests" — don't add a .dxkit-ignore for the test directory to make the noise go away (that also drops the files from coverage + test-gap analysis, and would hide a real leak the same way). See "Allowlisting (when fix is not viable)" below.
SAST finding (semgrep)
If the finding is a true false positive or intentional pattern (test fixture, mitigated externally), suppress via dxkit's allowlist — NOT via semgrep's // nosemgrep:. The dxkit allowlist is the canonical surface (single source of truth across every scanner), carries a typed category + reason, and is audit-trackable through vyuh-dxkit allowlist audit. See "Allowlisting (when fix is not viable)" below.
Ingested SAST finding (Snyk Code / CodeQL)
Interprocedural findings ingested from an external engine (tool: snyk-code / codeql, brought in via the dxkit-ingest skill) appear in the report exactly like native code findings — same fingerprint, same allowlist, same baseline. Fix them the same way, with two differences that make them easier, not harder:
- They're taint findings, so trace the flow, don't just patch the sink. The title names a source→sink path (e.g. "file data flows to an HTTP response"). Fixing only the flagged line often misses the real fix — sanitize/validate at the boundary the taint enters, then confirm the path is broken.
- Lean on
--graph-context. Run npx vyuh-dxkit vulnerabilities --detailed --graph-context so each ingested finding carries its enclosing symbol + blast radius (caller files). This is context the source engine's own autofix doesn't have: it tells you which callers to re-test after the fix and whether the change is high-blast-radius. Use it to plan the fix and to scope step [5] verification.
Verify by re-running the engine (re-ingest) — a graph-only or semgrep re-run won't confirm an interprocedural fix landed.
Dependency vulnerability
npm install <pkg>@<patched-version>
npx vyuh-dxkit vulnerabilities
For peer-dep conflicts: npm install <pkg>@<patched-version> --legacy-peer-deps (matches the post-create.sh fallback chain).
For Python: pip install --upgrade <pkg>=<patched> then re-pip-freeze. For Go: go get <pkg>@<patched> then go mod tidy. For Ruby: edit Gemfile, bundle update <pkg>. For Rust: cargo update -p <pkg> --precise <patched>.
If no patched version exists OR the upgrade breaks other constraints AND the risk is mitigated externally (network policy, WAF, runtime guard), allowlist with category=mitigated-externally and a reason describing the mitigation. If the team is accepting the risk while waiting on a fix, category=accepted-risk + an expiry tied to the fix deadline.
Test gap
For one test gap inside a broader fix pass, close it inline:
npm test
npx vyuh-dxkit test-gaps
Don't write tests that just import the module — write tests that exercise behavior. Useless tests inflate the count but don't move the dimension.
For a dedicated push to close many gaps / raise the Tests score — reading the blast-radius-weighted worklist, orienting via the graph, writing meaningful tests in the repo's framework — hand off to the dxkit-test skill (the testing mirror of dxkit-docs).
Slop / code-pattern finding
If the finding is a false positive, add // slop-ok: <reason> on the offending line (or # slop-ok for non-JS).
Structural duplicate (code-reimplementation)
A code-reimplementation finding means your change added a function that
structurally re-implements another one — same helper set, same name shape, read
from the call graph. It's the copy-paste-instead-of-parameterize pattern (a CLI
handler pasted from the HTTP one, a findByX cloned into findByIdX). It is
distinct from the token-level duplication (jscpd) finding — this one survives
rename/reformat because it reads structure, not text.
The finding is a PAIR of anchors, and the guardrail tells you which side your
change introduced — the console reads added: <new> ≈ existing: <twin>, and
the JSON marks the new anchor with "changed": true. That's your direction:
the new side is what you fix; the existing side is what you consolidate
toward.
npx vyuh-dxkit guardrail check
Prefer parameterizing the existing function over keeping two near-copies — that's
the fix the finding is asking for. If the parallel is deliberate (a
sanctioned by-design twin — two adapters that must stay structurally identical, a
generated-then-specialized pair), allowlist it instead:
npx vyuh-dxkit allowlist add --fingerprint=<id> \
--kind=code-reimplementation --category=false-positive \
--reason="<why the parallel is intentional>"
The guardrail's block/warn message prints this exact command with the fingerprint
filled in. Since a duplicate is warn-tier on its own, it won't fail the build —
but leaving it un-consolidated is exactly the maintainability debt the gate
exists to surface, so fix it unless the parallel is genuinely intended.
Allowlisting (when fix is not viable)
Fix first. The allowlist is the SECOND option, not the first. When you reach for it, choose deliberately — every allowlist entry is a future maintenance burden the team will revisit.
Five typed categories signal WHY the suppression is in place:
| Category | Meaning | Where it lives |
|---|
false-positive | Scanner is wrong about this code | Inline annotation OR file-level |
test-fixture | Intentional pattern in a fixture / test file | Inline annotation OR file-level |
mitigated-externally | Real risk but neutralized at runtime (WAF, env, etc.) | Inline annotation OR file-level |
accepted-risk | Real risk, accepted by the team, signed off | File-level only (needs expiry + acknowledged severity) |
deferred | Real, will fix later, tracked work | File-level only (needs expiry) |
accepted-risk and deferred require an expiresAt date because they describe assertions that should age out — by default the CLI sets 90 days. false-positive, test-fixture, and mitigated-externally describe assertions that don't naturally stale; they may omit expiry.
The two surfaces
Inline annotation is the natural fit for source-anchored findings (secrets, code, config, dep-vuln, hygiene) with an inline-compatible category. The annotation lives next to the line it suppresses:
api_key = "sk_test_xxxx"
Or, for long source lines, above:
const apiKey = "sk_test_xxxx";
The grammar is uniform across every language; only the comment marker varies (# for python/ruby, // for typescript/go/rust/csharp/kotlin/java). Don't type it by hand — let dxkit insert it for you (see CLI below).
File-level allowlist lives at .dxkit/allowlist.json and is the surface for:
- Cross-file or whole-file findings (duplication, coverage-gap, test-gap, god-file, large-file, stale-file)
- Findings with no stable single-line attachment (dep-vuln, secret-hmac)
- Any
accepted-risk or deferred suppression regardless of kind
Add an allowlist entry (canonical path)
Don't hand-edit the annotation comment or the JSON file — let the CLI insert it correctly:
npx vyuh-dxkit allowlist add src/auth/oauth.ts:42 \
--category=test-fixture --reason="placeholder in unit test"
npx vyuh-dxkit allowlist add --fingerprint=a3f9c0e8b7d2e1f4 \
--kind=dep-vuln --category=accepted-risk \
--reason="WAF rule X mitigates this CVE" --expires=2026-08-22
The guardrail's block message gives you the exact command to paste for every blocked finding — file path + line for inline-compatible kinds, fingerprint + kind for everything else. Just copy-paste.
When the finding only carries an id (sanitized / ref-based baseline)
If you're seeing a blocked finding labelled <sanitized> (in baseline show output) OR with no file/line columns in the PR-comment table, the repo's baseline mode is committed-sanitized or ref-based and the human-readable locator was stripped at write time. Two options:
- Inspect the finding in the current scan. The fingerprint pairs against the live scan's output, which is rich (always). Re-run the matching analyzer (
vyuh-dxkit vulnerabilities, vyuh-dxkit health, etc.) and grep the JSON output for the fingerprint — that finding has full file:line context.
- Allowlist by fingerprint anyway. File-level allowlist entries only need fingerprint + kind, both of which the guardrail message still provides. The category + reason apply regardless of whether the locator is visible at baseline time.
The fingerprint contract is preserved across all three modes — committed-full, committed-sanitized, ref-based all produce the same identity bytes for the same finding. Sanitization only strips the human-readable rendering; it doesn't change which findings pair across runs.
Review what's allowlisted
npx vyuh-dxkit allowlist list
npx vyuh-dxkit allowlist show <fingerprint>
npx vyuh-dxkit allowlist audit
npx vyuh-dxkit allowlist prune
Run audit periodically — accepted-risk and deferred entries that pass their expiry should either be re-justified (renew expiry) or pruned (remove the entry; the underlying finding will re-flag on the next scan). For the full lifecycle — auditing orphaned entries after a re-baseline (audit --against-baseline), removing a single stale fingerprint (allowlist remove), and exporting Snyk-originated suppressions back to a .snyk (allowlist export --snyk) — hand off to the dxkit-allowlist skill.
Stale annotations
If the underlying finding is fixed but the inline annotation lingers, the next scan emits a stale-allow finding pointing at the orphaned comment. The remediation is always to remove the annotation — dxkit refuses to allowlist a stale-allow finding (allowlisting the warning that an annotation is stale would defeat the entire model).
Verification — never skip
After each fix:
npx vyuh-dxkit vulnerabilities
The fix is verified when:
- The specific finding fingerprint disappears from the report.
- The dimension score moves in the right direction.
- (Optional) The dashboard's diff view confirms the count dropped.
If the finding's still there: the fix didn't work, try again.
Baseline decisions
Once a finding is processed (fixed, allowlisted, or accepted), the workflow depends on which path you took:
| Scenario | Action |
|---|
| Fix landed via a code change | Commit the code. Baseline + allowlist are unchanged. Future scans confirm the fix held. |
| Genuine false positive OR intentional pattern | vyuh-dxkit allowlist add with category=false-positive or test-fixture. Commit the annotation / allowlist file. Baseline is unchanged. |
| Real risk neutralized externally (WAF, runtime guard) | vyuh-dxkit allowlist add with category=mitigated-externally + a reason describing the mitigation. Baseline unchanged. |
| Real risk, accepted by team, won't fix | vyuh-dxkit allowlist add with category=accepted-risk + --expires=YYYY-MM-DD (defaults 90 days). Acknowledged-severity required for high/critical. |
| Real risk, will fix later (tracked work) | vyuh-dxkit allowlist add with category=deferred + --expires=YYYY-MM-DD. The expiry forces re-review when the deadline passes. |
Fix landed via a config change (e.g., new entry in .dxkit-ignore) | Re-baseline through the dxkit-baseline-refresh CI workflow (NOT a local baseline create --force — see the note below). Commit the .dxkit-ignore change; let CI refresh + commit the baseline. |
| Brownfield acceptance (the whole CURRENT state is known mess; future regressions must be net-new) | Re-baseline with an explicit reason in the commit message. Reserve this for the deliberate "draw a line here" moment, not per-finding suppression. |
Prefer the allowlist over re-baselining for per-finding decisions. The allowlist carries a typed category + reason + (when relevant) expiry; the baseline carries only "this finding was here." Future maintainers reading vyuh-dxkit allowlist show <fingerprint> see WHY the suppression is in place; reading the baseline file shows only that the finding existed at capture time. Per-finding decisions belong in the allowlist; codebase-wide brownfield acceptance belongs in the baseline.
Never re-baseline a finding silently — the commit message should explain why the regression is accepted. Future maintainers reading git log .dxkit/baselines/ should see the rationale.
Refresh the baseline in CI, not locally. When a re-baseline is the right call, run it through the bundled dxkit-baseline-refresh workflow (or a runner pinned to CI's scanner versions) — not a local npx vyuh-dxkit baseline create --force. A local refresh records your machine's semgrep / npm-audit / jscpd versions in the committed baseline; when they differ from CI's, the next PR's guardrail surfaces spurious TOOLING-DRIFT warnings and phantom "resolved" findings. A local --force is fine only for the very first capture or a throwaway experiment.
Workflow guardrail
After fixing N findings, run the guardrail check before pushing:
npx vyuh-dxkit guardrail check
Exit 0 = your fixes didn't introduce any net-new regressions (you only removed/fixed things). Exit 1 = something new appeared; address that before pushing.
When fixes get expensive
Sometimes the right call is: don't fix, allowlist (or re-baseline if it's brownfield-wide).
Examples:
- Legacy code on a deprecation path (sunset > fix) →
accepted-risk with expiry matching the sunset date
- A SAST finding in vendored code you don't maintain →
mitigated-externally if the vendor patches separately, else accepted-risk
- A test gap on a one-off script that doesn't merit tests →
accepted-risk
- An import line flagged by a scanner that you've reviewed and confirmed safe →
false-positive inline annotation at the import
In those cases: vyuh-dxkit allowlist add is the right tool for per-finding decisions (typed reason + expiry where relevant). Reserve "accept as baseline" for the deliberate one-shot brownfield moment ("this entire current state is known mess; today's findings are the new baseline"). The two surfaces complement each other — allowlist for individual judgment calls, baseline for the codebase-wide line in the sand.
Hand-offs
- For ignore-file edits as part of a fix →
dxkit-config skill
- For hook-related issues during a fix push →
dxkit-hooks skill
- For re-running reports between fixes →
dxkit-reports skill
- For broken dxkit install (hooks not firing, vyuh-dxkit not on PATH) →
dxkit-fix skill
- For a dedicated test-writing push (close many gaps / raise the Tests score) →
dxkit-test skill
- For generating missing documentation →
dxkit-docs skill
- For raising the PR once fixes are done + guardrail-green →
dxkit-pr skill (PR body from the diff + the guardrail/allowlist signals + a reviewer checklist)
- For allowlist management beyond the per-finding
add path — auditing existing entries (including orphans after a re-baseline), removing stale fingerprints, pruning expired ones, exporting to a .snyk, or reviewing the team's overall suppression posture → dxkit-allowlist skill