ci-preflight — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited ci-preflight (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.
A green CI is not a reward for guessing correctly. It's the outcome of a local gate that already ran.
This skill exists because these three failures happen repeatedly and all have the same root cause: pushing before checking.
What happens: A new shipped file (hook, agent, command, module) passes unit tests but fails integration tests because it wasn't added to one of several whitelists/registries/inventories the repo maintains.
Why it's hard: These surfaces are spread across multiple files with no obvious connection — you find one and miss two others.
Rule: Before pushing any new file that ships to users, run a completeness search.
# Find every place "existing-hook.js" is referenced in the repo
grep -r "gsd-existing-hook" . --include="*.js" --include="*.cjs" --include="*.md" --include="*.json" -lUse an existing sibling file as the probe: if gsd-existing-hook.js appears in N files, your new hook must appear in the same N files. Missing even one fails CI.
In this repo, adding a new hook requires updating all of:
hooks/managed-hooks-registry.cjs — MANAGED_HOOKS arrayscripts/build-hooks.js — HOOKS_TO_COPY arraybin/install.js — registration block with fs.existsSync guardget-shit-done/bin/lib/installer-migration-report.cjs — BUNDLED_GSD_HOOK_FILES Setdocs/INVENTORY.md — headline count + table rowdocs/INVENTORY-MANIFEST.json — run node scripts/gen-inventory-manifest.cjs --writeThe pattern generalises: every repo with drift-guard tests has its own set of registration surfaces. Find them before you write the first line.
What happens: Tests that would have caught the problem run in CI but not locally. Each failure costs a full CI round-trip (5–15 minutes) instead of a 30-second local run.
Rule: Run the repo's full test command before every push. Not a subset — the full suite.
npm test # or whatever this repo uses
npm run lint # catch lint errors before they hit CIIf the full suite is too slow for every push, run npm test:affected (or the repo's equivalent) — but know what it misses.
⚠️ `npm test` does not always mirror CI's unit scope. In gsd-core specifically, the default npm test (node scripts/run-tests.cjs) selects a different set than CI's unit job — observed in practice reporting fail 0 locally while CI's unit job failed on inventory-counts. Mirror the CI unit job directly before pushing:
npm run test:unit # mirrors CI's "test (ubuntu, NN)" / "full test" unit scope; catches drift-guard tests npm test can skipSpecific CI gates to run locally before pushing:
npm run lint:changeset # catches missing/malformed changeset
npm run lint:docs # catches missing docs update
node scripts/gen-inventory-manifest.cjs # verify manifest is current
node --test tests/inventory-counts.test.cjs tests/inventory-manifest-sync.test.cjs tests/bug-3628*When you add a new shipped surface, run the drift-guard tests explicitly — they're fast and catch exactly the "I forgot to update the registry" error.
What happens: A test fails on a platform you can't run locally (Windows). You form a hypothesis, push a fix, it fails again. Repeat 3 times.
Why it's expensive: Each guess costs a full 10-minute Windows CI run. Three guesses = 30 minutes of CI time and a messy commit history.
Rule: Diagnose before fixing. Understand exactly what the platform does differently before writing a single line of code.
Diagnosis checklist for cross-platform path failures:
path.join, os.tmpdir, fs.realpathSync)git rev-parse --show-toplevel output)path.resolve() outputfs.realpathSync() outputOn Windows these can differ in: separator (/ vs \), case (Runner vs runner), 8.3 short name vs long name.
os.tmpdir() returns C:\Users\RUNNER~1\AppData\Local\Temp (8.3 short name). fs.realpathSync expands to the long name. git --show-toplevel returns whatever form the working tree was registered under. These three can all differ.git --show-toplevel outputs (same binary, same format) worked; comparing path.resolve vs realpathSync didn't.The key insight: Normalisation fights the platform. Sameness of origin avoids the fight entirely.
Run through this before every git push on a branch that changes shipped files:
[ ] npm run test:unit (mirrors CI's unit job — npm test alone can under-cover it; see Anti-pattern 2)
[ ] npm run lint
[ ] If adding a new shipped file:
[ ] Grepped for an existing sibling to find all registration surfaces
[ ] Added to every surface the sibling appears in
[ ] Ran drift-guard tests directly: node --test tests/inventory-*.test.cjs tests/bug-*whitelist*.test.cjs
[ ] If writing cross-platform path code:
[ ] Listed every path variable and its origin (test-constructed / git / path.resolve / realpathSync)
[ ] Identified which sources can diverge on Windows (8.3 names, case, separators)
[ ] Chosen "same source" over "normalise disparate sources"
[ ] No TODOs left in the diff that belong in CI| Test file pattern | What it guards |
|---|---|
inventory-counts | docs/INVENTORY.md headline numbers match filesystem counts |
inventory-manifest-sync | docs/INVENTORY-MANIFEST.json matches filesystem |
bug-3628-bundled-hook-* | BUNDLED_GSD_HOOK_FILES matches hooks/ directory |
bug-1754-js-hook-guard | Every .js hook has fs.existsSync guard in install.js |
managed-hooks | managed-hooks-registry.cjs matches hooks/ directory |
When you add a new hook, run all five before pushing.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.