# Research Study Deps

> Use when pulling upstream source or cloning a dependency at its installed version to study internals, read code, or create pinned worktrees.

- Skill: `tony/research-study-deps` (Agent Skill)
- Install (CLI): `npx skillmds@latest add tony/research-study-deps`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tony/research-study-deps/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Research & Search
- Author: tony (https://skillmd.com/u/tony)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tony/research-study-deps

---


# Study Dependencies

Clone and study your project's dependencies at their exact pinned versions using git worktrees under `~/study/`.

Use `$ARGUMENTS` as the user's filter. If `$ARGUMENTS` is empty, ask the user which dependency to study.

Parse `$ARGUMENTS` for flags and strip them from the filter text:

| Flag | Effect |
|------|--------|
| `--lang <language>` | Override auto-detected language directory |
| `--no-worktree` | Clone only, skip worktree creation |

The remaining text after stripping flags is the **filter** — a package name, `"all"`, or a category like `"dev"` or `"build"`.

## Step 1: Detect Preferred Tools

```bash
for tool in rg ag fd jq; do
  command -v "$tool" >/dev/null 2>&1 && echo "$tool:available" || echo "$tool:missing"
done
```

For content search, prefer `rg` over `ag` over `grep`. For file finding, prefer `fd` over `find`. For JSON parsing, use `jq` when available, otherwise parse manually.

## Step 2: Scan Manifest Files

Search the current project root for manifest files and extract dependencies with their version constraints.

| Manifest | Language dir | Lockfiles | Extraction method |
|----------|-------------|-----------|-------------------|
| `package.json` | `typescript` | `package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`, `bun.lock` | `dependencies`, `devDependencies`, `peerDependencies` fields |
| `pyproject.toml` | `python` | `uv.lock`, `poetry.lock`, `requirements.txt` | `[project]` `dependencies`, `[tool.poetry.dependencies]` |
| `Cargo.toml` | `rust` | `Cargo.lock` | `[dependencies]`, `[dev-dependencies]`, `[build-dependencies]` |
| `go.mod` | `golang` | `go.sum` | `require` entries (block and single-line) |
| `Gemfile` | `ruby` | `Gemfile.lock` | `gem` declarations |
| `mix.exs` | `elixir` | `mix.lock` | `deps` function return |
| `build.gradle` / `build.gradle.kts` | `java` | `gradle.lockfile` | `implementation`, `api`, `testImplementation` |
| `pom.xml` | `java` | — | `<dependency>` elements |

For each manifest found, extract the dependency name, version constraint, and category (runtime, dev, build, peer).

When a lockfile exists alongside the manifest, also extract the **resolved version** (exact pinned version) for each dependency. Prefer the lockfile resolved version over the manifest constraint for tag resolution in Step 7.

## Step 3: Filter Dependencies

Apply the filter from `$ARGUMENTS`:

- **Specific package name** — match exactly (case-insensitive)
- **`"all"`** — include every dependency
- **Category** (`"dev"`, `"build"`, `"peer"`, `"runtime"`) — filter by dependency category

Check `~/study/` for existing clones and worktrees:

```bash
ls -d ~/study/*/*/ 2>/dev/null
```

Mark each dependency as: **new** (needs clone + worktree), **update** (clone exists, needs worktree), or **exists** (worktree at correct version already present). Plain directories (e.g., `vite/`) are main clones; version-suffixed directories (e.g., `vite-6.2.0/`) are worktrees.

If `--lang` was provided, use that as the language directory instead of auto-detecting from the manifest.

## Step 4: Resolve Source Repositories

For each filtered dependency, find the official source repository URL.

**Resolution order** (stop at first success):

1. **Manifest metadata** — `repository` field in `package.json`, `[package]` `repository` in `Cargo.toml`, `project.urls` in `pyproject.toml`
2. **Package registry metadata** — `npm view <pkg> repository.url`, `cargo metadata`, `pip show <pkg>`, `go list -m -json <module>`
3. **WebSearch** — search for `"<package-name>" official source repository` and verify the result

Normalize all URLs to `https://<host>/<owner>/<repo>.git` format where possible. Strip `.git` suffix for display, keep it for clone commands.

## Step 5: Present Plan and Confirm

Show a summary table of planned actions:

| Package | Version | Repository | Action | Target Path |
|---------|---------|------------|--------|-------------|
| vite | 6.2.0 | vitejs/vite | clone + worktree | `~/study/typescript/vite-6.2.0/` |
| react | 19.1.0 | facebook/react | worktree only | `~/study/typescript/react-19.1.0/` |
| zod | 3.25.0 | colinhacks/zod | exists | `~/study/typescript/zod-3.25.0/` |

**Confirmation gate** — ask the user to approve before proceeding. Use `ask-user-choice` with options:

- **Proceed** — clone and create worktrees as shown
- **Select specific** — let the user pick individual packages from the list
- **Cancel** — abort

Do not proceed past this step without user approval.

## Step 6: Clone Repositories

Always quote variables and use `--` to separate options from arguments to prevent shell injection from manifest-derived values.

For each approved dependency that needs cloning:

```bash
git clone -- "$repo_url" "$HOME/study/<language>/<repo-name>/"
```

If the clone directory already exists, fetch latest tags instead:

```bash
git -C "$HOME/study/<language>/<repo-name>/" fetch --tags --force
```

Clone one repository at a time. Report progress after each clone.

## Step 7: Resolve Version Tag or Branch

For each dependency, find the matching git ref. Try these patterns in order (stop at first match):

| Priority | Pattern | Example |
|----------|---------|---------|
| 1 | Exact tag | `5.2.0` |
| 2 | `v`-prefixed tag | `v5.2.0` |
| 3 | Scoped package tag | `@scope/pkg@5.2.0`, `pkg@5.2.0` |
| 4 | Crate-style tag | `pkg-v5.2.0`, `pkg-5.2.0` |
| 5 | Minor branch | `release/5.2`, `stable/5.2.x`, `5.2.x` |
| 6 | Major branch | `release/5.x`, `v5` |

Use the lockfile resolved version (from Step 2) when available, otherwise use the manifest version constraint stripped of range operators (`^`, `~`, `>=`, etc.).

```bash
git -C "$HOME/study/<language>/<repo-name>/" tag -l
```

```bash
git -C "$HOME/study/<language>/<repo-name>/" branch -r -l
```

If no matching ref is found, warn the user and offer to use the default branch instead.

## Step 8: Create Version-Pinned Worktree

Skip this step if `--no-worktree` was passed.

For tag refs (detached HEAD):

```bash
git -C "$HOME/study/<language>/<repo-name>/" worktree add --detach "$HOME/study/<language>/<repo-name>-<version>/" <tag>
```

For branch refs:

```bash
git -C "$HOME/study/<language>/<repo-name>/" worktree add "$HOME/study/<language>/<repo-name>-<version>/" <branch>
```

The worktree path follows the convention: `~/study/<language>/<repo-name>-<version>/`.

If the worktree path already exists, skip creation and report it as already present.

For monorepo-hosted packages (e.g., `@tanstack/react-query` and `@tanstack/react-table` both in `tanstack/query`), the entire repo is cloned once. The worktree contains all packages — the user can navigate to the specific package subdirectory.

## Step 9: Report Results

Present a summary of what was done:

| Status | Package | Path |
|--------|---------|------|
| created | vite@6.2.0 | `~/study/typescript/vite-6.2.0/` |
| skipped | zod@3.25.0 | `~/study/typescript/zod-3.25.0/` (already exists) |
| failed | some-pkg@1.0.0 | no matching tag found |

Include the full path for each created worktree so the user can navigate directly.

If any dependencies failed, suggest manual steps to resolve (e.g., checking available tags, using a different version).


## Portability notes

- `ask-user-choice` — present the listed options and wait for the user to pick one. Hosts with a structured multiple-choice tool (Claude Code's `AskUserQuestion`) should use it; otherwise print a numbered list and wait for a numbered reply. Never proceed on an assumed answer.
- `$ARGUMENTS` — the text the user passed when invoking this skill. If your host does not substitute it, read it as the user's request in the current turn, and ask when there is none.

