add-resolver — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited add-resolver (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Guide the agent through adding a format-specific resolver to packages/core end to end — file creation, dispatcher wiring, tests, and docs — without missing any of the four required touch points.
Answer these before writing any code:
.prisma, composer.lock, .tf, .toml)0.95 by convention. Lockfileconflicts are almost always trivially re-generable, so high confidence is appropriate.
packages/core must run inbrowsers — no Node.js imports (fs, path, child_process). If no parser lib is available, write a pure string-manipulation parser.
Confidence score guidelines:
| File type | Score |
|---|---|
| Lockfile | 0.95 |
| Structured config (JSON, YAML, TOML, dotenv) | 0.85–0.90 |
| Source code | 0.70–0.80 |
Never exceed 0.95 unless perfectly deterministic | — |
Create packages/core/src/resolvers/<format>.ts. Follow this exact shape, which mirrors all existing resolvers in the directory:
/**
* GitWand — <FORMAT> resolver
*
* Strategy: [describe the resolution strategy precisely]
*
* Handled cases:
* - [case 1]
* - [case 2]
* - [non-resolvable case → return null]
*/
// ✅ Allowed: pure string manipulation, parsing, algorithms
// ❌ FORBIDDEN: import * as fs from 'fs' / import * as path from 'path'
export interface ResolveResult {
/** Resolved lines, or null if not resolvable */
lines: string[] | null;
/** Human-readable description of what was done (or why it failed) */
reason: string;
}
/**
* Attempts to resolve a <format> conflict.
* Confidence: 0.XX — [justify the score here]
*/
export function tryResolve<Format>Conflict(
baseLines: string[],
oursLines: string[],
theirsLines: string[],
): ResolveResult {
// ... resolution logic ...
return { lines: null, reason: "Non-resolvable: [reason]." };
}Key points:
lines: null rather than throwing — the dispatcherfalls back gracefully when a resolver returns null
Open packages/core/src/resolvers/dispatcher.ts and make four changes:
3a. Add a detection function in the // ─── Type detection ─── section:
/** Checks whether the file is a <format> file */
export function is<Format>File(filePath: string): boolean {
return /\.<ext>$/i.test(filePath);
// For exact filenames: /(?:^|[\\/])<name>$/i.test(filePath)
}3b. Add the import at the top with the other resolver imports:
import { tryResolve<Format>Conflict } from "./<format>.js";3c. Add the route in `tryFormatAwareResolve()`, keeping specificity order — more specific detections first (e.g. composer.lock before *.lock, a named lockfile before a generic JSON):
if (is<Format>File(filePath)) {
const result = tryResolve<Format>Conflict(
hunk.baseLines,
hunk.oursLines,
hunk.theirsLines,
);
return {
lines: result.lines,
reason: `[<format>] ${result.reason}`,
resolverUsed: "<format>",
};
}3d. Extend the `resolverUsed` union in FormatResolveResult:
resolverUsed: "json" | "markdown" | ... | "<format>" | "structural" | "none";Create packages/core/src/__tests__/resolvers/<format>.test.ts with at least 5 cases:
import { describe, it, expect } from "vitest";
import { resolve } from "../../resolver.js";
// Case 1 — Simple resolvable conflict (happy path)
describe("<FORMAT> — non-conflicting addition", () => {
it("auto-resolves and keeps both sides", () => { /* ... */ });
it("reason mentions [<format>]", () => { /* ... */ });
});
// Case 2 — Irresolvable complex conflict
describe("<FORMAT> — irresolvable conflict", () => {
it("returns resolved: false", () => { /* ... */ });
});
// Case 3 — Empty / minimal structure
describe("<FORMAT> — minimal file", () => {
it("does not throw", () => { /* ... */ });
});
// Cases 4 & 5 — Representative real-world conflicts for this format
describe("<FORMAT> — real scenario 1", () => { /* ... */ });
describe("<FORMAT> — real scenario 2", () => { /* ... */ });Build fixtures using real git conflict markers (<<<<<<< ours / ||||||| base / ======= / >>>>>>> theirs). Never mock the diff algorithms.
In packages/core/CLAUDE.md, add the new resolver name to the resolvers list.
cd packages/core && pnpm testIf the resolver touches the main resolution pipeline, also run the parity probe to make sure Rust and TypeScript outputs stay in sync:
cd apps/desktop && pnpm test:parityresolverUsed unioncauses a TypeScript error that only surfaces at build time.
*.lock matcher placed beforepnpm-lock.yaml would silently shadow the existing pnpm resolver.
packages/core runs in browsers and TauriWebViews; any Node.js import will crash at runtime in those environments.
lines: null with a reason;let the dispatcher fall through to the text engine.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.