diff --git a/README.md b/README.md
index 89c8a91..d3f660d 100644
--- a/README.md
+++ b/README.md
@@ -4,14 +4,41 @@
version-sentinel
+
+
+
+
+
+
+
Claude Code plugin that hard-blocks dependency additions, bumps, and downgrades until a fresh, source-cited version check is recorded.
> If Claude tries to add `"lodash": "^4.17.21"` without looking up the latest version first, the tool call is rejected with exit 2. Claude must run `WebSearch`, then `/vs-record`, then retry. Five ecosystems supported in v0.1.
+**Keywords:** Claude Code, Anthropic, AI coding guardrails, LLM supply-chain security, dependency management, hallucinated package versions, npm, PyPI, Cargo, NuGet, PreToolUse hook.
+
## Why
LLM-assisted coding silently ships whatever version the model remembers from its training data. For packages with frequent releases or known compromised versions, that's unacceptable. `version-sentinel` inserts a mandatory "check the registry" step — without stopping you from pinning an old version on purpose.
+## What it prevents
+
+- **Hallucinated versions** — LLM picks a version that never existed or never shipped.
+- **Stale defaults** — model reaches for a 2-year-old pin because training data froze there.
+- **Compromised-release installs** — no guard against yanked / malicious versions without a fresh registry lookup.
+- **Silent downgrades** — Claude "fixes" a CI error by reverting a package to an older vulnerable build.
+- **Supply-chain drift** — no audit trail of *why* a specific version was chosen.
+
+## How it compares
+
+| Tool | Scope | Enforcement |
+|------|-------|-------------|
+| `version-sentinel` | Claude Code **PreToolUse hook** — blocks the tool call before the edit lands | Hard-fail exit 2 |
+| Generic dependency-audit skills | Post-hoc scan of `package.json` / `requirements.txt` | Advisory |
+| Dependabot / Renovate | Scheduled PR bot against remote registries | Async PR |
+
+Unlike post-hoc auditors, `version-sentinel` runs **inside the agent loop** — the agent cannot merge a bad version by accident because the write itself is refused until the check is cited.
+
## Supported ecosystems (v0.1)
| File | Ecosystem | Registry |
@@ -76,6 +103,28 @@ State: `/.version-sentinel/checks.json`. Auto-gitignored on first
/plugin marketplace remove version-sentinel-marketplace
```
+## FAQ
+
+**Does this work with Claude Desktop or just Claude Code?**
+Claude Code only — relies on the PreToolUse hook API exposed by the CLI.
+
+**Does it slow Claude down?**
+First touch of a package: adds one `WebSearch` + one `/vs-record` call (~5–10s). Subsequent edits to the same pin hit the cached sidecar — zero overhead.
+
+**Can I use this for private / internal registries?**
+Yes — add the `ecosystem:pkg` entry to `.version-sentinel/ignore`, or record with a justification string.
+
+**Why not just run `npm audit` / `pip-audit`?**
+Those are post-hoc. `version-sentinel` refuses the write in the first place, so the vulnerable version never enters the repo.
+
+**Does it support Go modules, Gradle, Maven, composer, gems?**
+Not in v0.1. See `docs/roadmap.md`.
+
+## Related
+
+- [Anthropic Claude Code](https://claude.com/claude-code)
+- [Claude Code plugin docs](https://code.claude.com/docs/en/plugin-dependencies)
+
## License
-MIT — see [LICENSE](./LICENSE).
+MIT — see [LICENSE](./LICENSE).
\ No newline at end of file
diff --git a/scripts/detect-manifest-edit.sh b/scripts/detect-manifest-edit.sh
index b235275..f1439b7 100644
--- a/scripts/detect-manifest-edit.sh
+++ b/scripts/detect-manifest-edit.sh
@@ -54,12 +54,13 @@ case "$tool_name" in
;;
MultiEdit)
post_content="$pre_content"
+ edits_tsv=$(echo "$input" | jq -r '.tool_input.edits[]? | [.old_string, .new_string] | @tsv')
while IFS=$'\t' read -r o n; do
[[ -z "$o" ]] && continue
o=$(printf '%s' "$o" | tr -d '\r')
n=$(printf '%s' "$n" | tr -d '\r')
post_content=$(printf '%s' "$post_content" | py_replace_once "$o" "$n")
- done < <(echo "$input" | jq -r '.tool_input.edits[]? | [.old_string, .new_string] | @tsv')
+ done <<< "$edits_tsv"
;;
*) exit 0 ;;
esac
@@ -96,4 +97,4 @@ if [[ "$block" -eq 1 ]]; then
echo "$block_msgs" >&2
exit 2
fi
-exit 0
+exit 0
\ No newline at end of file
diff --git a/scripts/lib/parse-install-cmd.sh b/scripts/lib/parse-install-cmd.sh
index b7d06a9..1e842c1 100644
--- a/scripts/lib/parse-install-cmd.sh
+++ b/scripts/lib/parse-install-cmd.sh
@@ -5,11 +5,15 @@
parse_install_cmd() {
local cmd="$1"
local segment
- while read -r segment; do
+ local segments
+ local old_ifs="$IFS"
+ IFS=$'\n' read -r -d '' -a segments < <(printf '%s\n' "$cmd" | tr ';&|' '\n'; printf '\0') || true
+ IFS="$old_ifs"
+ for segment in "${segments[@]}"; do
segment="${segment#"${segment%%[![:space:]]*}"}"
[[ -z "$segment" ]] && continue
_parse_install_segment "$segment"
- done < <(printf '%s\n' "$cmd" | tr ';&|' '\n')
+ done
}
_parse_install_segment() {
@@ -101,4 +105,4 @@ _emit_dotnet_add() {
esac
done
[[ -n "$name" ]] && printf 'csproj\t%s\t%s\n' "$name" "$ver"
-}
+}
\ No newline at end of file