Catch cross-platform package.json script bugs before CI or teammates do.
ScriptSpect is a preflight checker for npm-style package.json scripts. Point
it at a Node.js project or monorepo: without running the scripts, it shows the
command fragments that may break in posix-sh, Windows cmd, or optional
powershell, explains the affected platform, and offers a fix only when its
safety conditions are proved.
Tip
Verified release: scriptspect@0.1.2. The immutable Action
tag is v0.1.2; security-sensitive workflows can pin
the full release commit 6f439bb974b297d5a334cebe989b4b50d7483677.
Run in 30 seconds · See the real demo · GitHub Actions · Rules
Requires Node.js 22 or newer. Run the exact verified npm release without a global install:
npx --yes scriptspect@0.1.2 .With pnpm:
pnpm dlx scriptspect@0.1.2 .Findings exit 1; a clean scan exits 0; invalid input, configuration, or I/O
exits 2. Start with --fix-dry-run before applying any reviewed fix.
A build that works on your Mac can fail on a contributor's Windows laptop.
ScriptSpect catches those shell assumptions in package.json before they reach CI.
| Your workflow | What ScriptSpect gives you |
|---|---|
| Maintain a JS/TS app, library, or CLI | Find the command and platform behind a portability failure. |
| Work in a monorepo | Check the root package and discovered workspaces together. |
| Review human- or agent-written scripts | Get structured JSON or PR annotations, then preview a fix with --fix-dry-run. |
Point it at a repository → inspect the findings → review the patch. Analysis is local and read-only by default. You decide which fixes to apply.
The payoff: catch a shell mismatch while reviewing a change, instead of waiting for a teammate's machine or an OS-specific CI job to fail. This is particularly useful for cross-platform teams, published developer tools, and repositories where coding agents frequently change package scripts.
You maintain a Vite project. A teammate—or a coding agent—adds the two scripts
below. On a Mac they use familiar shell syntax; native Windows npm scripts use
cmd, where inline environment assignments and rm -rf are incompatible.
Try it yourself: save this complete example as package.json in a new,
empty folder. Open a terminal there. For this scan-and-patch demo, you do not
need to install or execute the declared build tools.
Before — two scripts that assume a POSIX shell:
{
"name": "portable-demo",
"private": true,
"scripts": {
"build": "NODE_ENV=production vite build",
"clean": "rm -rf dist"
},
"devDependencies": {
"cross-env": "^7.0.3",
"rimraf": "^6.0.1",
"vite": "^7.0.0"
}
}1. Find the problem before running the build:
npx --yes scriptspect@0.1.2 .| Script | Finding | What it means for you |
|---|---|---|
build |
PS001 · NODE_ENV=production |
Windows cmd does not use this environment-variable assignment syntax. |
clean |
PS010 · rm -rf dist |
Native Windows cmd does not provide this command. |
The actual scan reports 2 errors and 2 advisories, with exit code 1.
The advisories explain how the same build command is parsed differently.
The screenshot and patch below come from the executable
demo fixture.
Selectable terminal text · Full generated patch · Verified after file
2. Preview the proposed changes without modifying the file:
npx --yes scriptspect@0.1.2 . --fix-dry-runThe important changes are shown below. These rewrites are available because
cross-env and rimraf are already declared in this example:
-"build": "NODE_ENV=production vite build"
-"clean": "rm -rf dist"
+"build": "cross-env NODE_ENV=production vite build"
+"clean": "rimraf dist"3. Apply the reviewed changes, then scan again:
npx --yes scriptspect@0.1.2 . --fix
npx --yes scriptspect@0.1.2 .Actual result with the published package:
scriptspect: fixed 2 script(s) in package.json
Scanned 2 scripts across 1 package · 0 errors · 0 warnings
The final scan exits 0, with no findings. The two demonstrated shell
incompatibilities have been removed before anyone runs the build.
Review fixes in your own project; the tool leaves dependency installation to you.
After an agent edits package.json, run:
npx --yes scriptspect@0.1.2 . --format jsonFor the original example, a finding contains these fields (excerpt):
{
"ruleId": "PS001",
"scriptName": "build",
"packagePath": "package.json",
"severity": "error",
"affectedTargets": ["cmd"]
}Paste the JSON output into your coding agent's conversation, or have the agent run the command through its terminal tool. Ask it to propose a minimal patch for the reported file, script and target. Review its patch and rerun the scan. The same command discovers supported workspaces in a monorepo, so each package's script can be identified separately.
Add the GitHub Actions workflow below. If a future PR adds
the incompatible clean script, the Action marks the check as failed and
annotates package.json; a clean fixture passes. See the real hosted result
below the workflow. Reviewers get the problem alongside the code change.
The CLI supports human, JSON, and GitHub-friendly output, focused rule runs, explicit target matrices, and opt-in fixes.
npx --yes scriptspect@0.1.2 .
npx --yes scriptspect@0.1.2 . --format json
npx --yes scriptspect@0.1.2 . --target posix-sh,cmd,powershell
npx --yes scriptspect@0.1.2 . --rule PS001,PS010
npx --yes scriptspect@0.1.2 . --fix-dry-run
npx --yes scriptspect@0.1.2 . --fix
npx --yes scriptspect@0.1.2 explain PS010Presentation filters do not hide failure semantics: any configured error
fails, and the unfiltered warning count is compared with --max-warnings.
Use the verified immutable release tag for readable workflows.
For the strongest supply-chain pin, replace v0.1.2 with the full release
commit 6f439bb974b297d5a334cebe989b4b50d7483677.
name: scriptspect
on: [pull_request]
permissions:
contents: read
jobs:
scripts:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: Tom409114/scriptspect@v0.1.2
with:
path: .The Action writes annotations, a job summary, and numeric outputs named
exit-code, packages, scripts, errors, warnings, and advisories before
marking a finding run as failed. Its default mode is read-only.
Real hosted proof — not a mock screenshot. On main at c9c671c8, public
CI run #33482453059
consumed uses: ./ against both clean and broken fixtures. The clean consumer
reported 1 package · 1 script · 0 errors; the broken fixture emitted 2 check
annotations, including PS010: scripts.clean on package.json.
Selectable Action evidence · Committed source evidence · Open the hosted job
Defaults target posix-sh and cmd. Put the same small contract in the root
package.json under scriptspect, or in scriptspect.config.json:
{
"targets": ["posix-sh", "cmd"],
"severity": { "PS015": "advisory" },
"ignore": [
{ "packages": ["examples/**"], "rules": ["PS030"] },
{ "scripts": ["docs:unix"], "rules": ["PS010", "PS011"] }
]
}Precedence is deterministic and replacement-based:
--config → package.json#scriptspect → scriptspect.config.json → defaults.
--target then replaces only the selected config's target list. Config sources
are never merged. Ignore entries must name rules and should stay narrow enough
to explain an intentional platform-specific script.
Contracts: config JSON Schema · JSON output Schema
| Area | Current behavior |
|---|---|
| Projects | root package.json plus npm/Yarn/Bun workspaces and pnpm-workspace.yaml |
| Targets | posix-sh + cmd by default; opt-in powershell evidence |
| Findings | error, warning, and advisory with high/medium confidence |
| Output | stylish terminal text, versioned JSON, GitHub annotations + summary |
| Fixes | dry-run plus provable safe/conditional rewrites; ambiguous cases stay manual |
| Privacy | offline analysis; scripts are not executed; no telemetry |
Release: npm 0.1.2 · Action v0.1.2 · full SHA 6f439bb974b297d5a334cebe989b4b50d7483677.
Have a real cross-platform failure? Open an issue with the script, target shell, and expected behavior. Your example helps improve the rules.
Does it run my scripts? No. It reads package manifests and performs static structural analysis.
Why did the scan exit 1 when I filtered warnings from the display? Failure
is calculated before presentation filtering: configured errors and the full
warning budget still count. Use --format json to inspect the complete contract.
Why was no automatic fix offered? The parser must agree on the replacement's structural role across active targets, and conditional fixes require the exact dependency to be declared. Otherwise the finding remains explanatory and manual.
Which config won? Explicit --config wins, followed by the package.json
field, the standalone file, then defaults. Non-default sources are reported in
human-readable output.
Can I use it in production CI today? Yes—use the verified scriptspect@0.1.2
package or immutable v0.1.2 Action reference above. Pin 6f439bb974b297d5a334cebe989b4b50d7483677
when your policy requires an exact commit.