ClaudeMods
☰
JA
● 0 人がオンライン ・閲覧 0 回
スポンサー作品を投稿
GitHub リポジトリ · 投稿者 KilimcininKorOglu

lockfile-sync

git commit 後に manifest の依存関係変更で対応する lockfile が残されていないか確認します。npm、Composer、Cargo、Go、Python、Bundler、Dart、Mix に対応し、lockfile が古い間は commit、push、merge を任意でブロックできます。

KilimcininKorOglu@KilimcininKorOglu

KilimcininKorOglu/claude-code-mods/tree/main/plugins/lockfile-sync

翻訳済み

この mod について

lockfile-sync は Bash 経由で行われたコミットを調べます。git commit で HEAD が移動した後、変更された manifest と対応する lockfile を組み合わせ、抜けている lockfile を報告します。パッケージマネージャーのチェックと manifest diff の比較で、本当に遅れているかを判断できます。/lockfile-sync ではモードと未処理の lockfile を表示します。デフォルトは note で、deny にすると git commit、git push、git merge も停止します。インストールは claude plugin marketplace add KilimcininKorOglu/claude-code-mods と claude plugin install lockfile-sync@kilimcininkoroglu-mods です。

インストール

まず作者の README で marketplace とプラグイン名を確認してください。コマンドはリポジトリの構成によって変わる場合があります。

claude plugin marketplace add KilimcininKorOglu/claude-code-mods
claude plugin install lockfile-sync
原文 / README

lockfile-sync

The model adds a dependency to package.json, commits that file alone, and the lockfile stays behind. The next npm ci fails in CI, or a teammate installs a different version than the one you tested. This mod tells the model when a commit changes the dependencies of a manifest but not its lockfile. After each git commit the model runs, the mod adds the manifests whose lockfile the commit left out to the commit's result. The commit itself is never stopped.

What it does

  1. The mod hooks the Bash tool. A command that runs git commit (also git -C <dir> commit and the forms with git's global flags in front, not --dry-run, --help or -h) is checked.

  2. Before the command runs, it finds the repository root from the session's directory, the last cd before the commit and the commit's git -C, and records HEAD.

  3. After a successful command that moved HEAD, it lists the commit's added and modified files with git show --name-status HEAD, and pairs each manifest with its lockfile:

    | Manifest | Lockfile | |---|---| | package.json | package-lock.json, yarn.lock, pnpm-lock.yaml, bun.lock, bun.lockb | | composer.json | composer.lock | | Cargo.toml | Cargo.lock | | go.mod | go.sum | | pyproject.toml | poetry.lock, uv.lock, pdm.lock | | Pipfile | Pipfile.lock | | Gemfile | Gemfile.lock | | pubspec.yaml | pubspec.lock | | mix.exs | mix.lock |

    The lockfile is the first one on disk from the manifest's directory up to the repository root, so a workspace package pairs with the root lockfile. A manifest without a lockfile on disk is left alone: the project does not keep one.

  4. When the commit leaves that lockfile out, the mod first asks the lockfile's own package manager whether the lockfile still fits the manifest. It runs the check by argv in the lockfile's directory, with a 60 s limit:

    | Lockfile | Check | Behind when | |---|---|---| | Cargo.lock | cargo metadata --locked --format-version 1 --manifest-path <manifest> | cannot update the lock file | | package-lock.json | npm ci --dry-run --ignore-scripts | are in sync in the failure | | pnpm-lock.yaml | pnpm install --frozen-lockfile --lockfile-only --ignore-pnpmfile --ignore-scripts | don't match specifiers | | bun.lock, bun.lockb | bun install --frozen-lockfile --dry-run --ignore-scripts | lockfile had changes | | yarn.lock (v1) | yarn check | Lockfile does not contain pattern | | composer.lock | composer validate --no-check-all --no-check-publish --check-lock --no-plugins | lock file is not up to date | | go.sum | go mod tidy -diff | the diff holds a go.sum hunk | | uv.lock | uv lock --check | needs to be updated | | poetry.lock | poetry check --lock | changed significantly | | pdm.lock | pdm lock --check | satisfy the project requirements | | Pipfile.lock | pipenv verify | out-of-date | | Gemfile.lock | bundle lock --print | the printed lockfile differs, platforms and the Bundler version aside | | pubspec.lock | dart pub get --enforce-lockfile --dry-run | Unable to satisfy | | mix.lock | mix deps.get --check-locked, with MIX_DEPS_PATH in $TMPDIR/lockfile-sync | mix.lock is out of date |

    Each check was measured to write nothing into the repository. A pass means the lockfile fits and no finding opens: a features change in Cargo.toml that pulls in no new crate opens nothing, and a features = ["derive"] that pulls in serde_derive does. A failure that says the lockfile is behind opens the finding. Any other answer proves nothing: the tool is not installed, it ran past the limit, it failed for another reason, the lockfile is a Yarn 2+ yarn.lock, or the manifest or the lockfile differs from HEAD in the working tree. The check reads the working tree and the finding speaks of the commit, so a lockfile written but left out of the commit would read as in step. A tool that did not start is logged once:

    lockfile-sync: cargo did not run: <reason>; the manifest's diff decides
    

    Then the mod reads the manifest's diff (git show --unified=20 HEAD -- <manifest>) and checks where the changed lines sit. Only a change that can change the lockfile counts:

    | Manifest | Counts | Does not count | |---|---|---| | package.json, composer.json | dependencies, devDependencies, peerDependencies, optionalDependencies, overrides, resolutions, require, require-dev and the like | scripts, version, other keys | | Cargo.toml, pyproject.toml, Pipfile | [dependencies], [dev-dependencies], [target.*.dependencies], [project], [tool.poetry.dependencies], [packages] and the like | [package], [tool.ruff], other tables | | go.mod | require, replace, exclude lines and blocks | go 1.22, module | | Gemfile | gem, source, gemspec, group lines | comments | | pubspec.yaml | dependencies, dev_dependencies, dependency_overrides | other keys | | mix.exs | every change | |

    The section of a changed line is read from the whole manifest (git show HEAD:<manifest>), not from the diff's own 20 lines of context: a change 40 lines into a package.json never reaches the root { inside the hunk, and every root-level key would read as a dependency. A key or table the manifest itself does not place counts, so a file that cannot be read still gets the note.

  5. The model reads this note after the commit's result:

    lockfile-sync: this commit changes package.json but not package-lock.json · go.mod but not go.sum. Run the package manager's install so the lockfile matches, and commit it.
    
  6. At the same moment one line reaches the transcript, so you see what the model was told. The line holds the pairs alone, without the instruction:

    lockfile-sync: this commit changes package.json but not package-lock.json · go.mod but not go.sum
    

    The note and the line are separate channels: the model never reads the line, and you never read the note.

  7. While the sidebar is open, those pairs go there instead, one line per pair (the lockfile red, but not faint), as an entry in its stream, and the transcript stays clean. The entry stays until newer ones push it off the pane. With the sidebar closed, or without that mod installed, the transcript line is written as above.

  8. Each commit that leaves a lockfile out opens its own finding, with its own sidebar entry keyed by its manifests. A later commit adds its finding beside the open ones and never writes over one; a pair an open finding already names is not opened twice. Every finding closes on its own measure.

    A finding is never a remembered answer. Each measure, after every later commit, at the end of each main-loop turn and before a guarded git command in deny mode, asks git and the package manager again, so a finding closes three ways:

    • the lockfile was written: a later commit changed it, or git status --porcelain shows it changed in the working tree;
    • the package manager reads the lockfile as in step with the manifest (the check of step 4);
    • the check proves nothing and the manifest asks for no lockfile change any more: git log -1 -- <lockfile> names the commit that last wrote the lockfile, and the manifest's diff against that commit touches no dependency. A change that was reverted reads this way. A lockfile the package manager reads as behind stays open whatever the diff says.

    The entry is cleared and a green one says which of the three it was:

    lockfile-sync: a later change brought the lockfiles along: package-lock.json
    lockfile-sync: cargo reads Cargo.lock as in step with Cargo.toml
    lockfile-sync: the dependencies match the lockfile again: package.json
    

    With the sidebar closed the same text is one transcript line. The model reads nothing of this: the finding closed by its own work, so a note would only repeat what it just did.

  9. A finding the model did not close is measured again at the end of each main-loop turn, and what is left reaches the model as one note with its next prompt:

    lockfile-sync: 1 lockfile(s) are still behind their manifest: package-lock.json behind package.json. Run the package manager's install so the lockfile is written, or take the dependency change back.
    

    One note per turn, not one per prompt. Without this the finding would be said once, at the commit, and then stand in the pane while the model forgot it. You read nothing new: the pane already carries the same finding.

  10. In deny mode the mod also stops git commit, git push and git merge while a lockfile is behind. Before it stops one it runs both measures, so a lockfile the package manager just wrote, and a dependency change that was taken back, each open the gate themselves. A git commit answers for its own files alone: the mod reads the index (git diff --cached --name-only -z) and lets the commit run when it holds none of the open manifests, with one line to you naming how many still stand. A push and a merge hold no index to read, so every pair stands there. There is no bypass; only you turn the gate off, with /lockfile-sync mode note. note mode is the default and stops nothing.

A git error is written as a yellow entry (the transcript line with the sidebar closed), once until a different one comes, and the commit's result stays as it was.

In the live check the model raised a package.json dependency, committed only that file, and quoted the note word for word.

Command

/lockfile-sync                 on or off, the mode, and the lockfiles still behind
/lockfile-sync on | off        on by default
/lockfile-sync mode note       note only; the default
/lockfile-sync mode deny       a commit, a push and a merge also stop while a lockfile is behind

Install

claude plugin marketplace add KilimcininKorOglu/claude-code-mods
claude plugin install lockfile-sync@kilimcininkoroglu-mods

Function hooks are early access. Claude Code 2.1.288 and later load them by default, so there is nothing to switch on.

After installing

  1. Restart Claude Code.

What it can reach

Validated with claude plugin validate on Claude Code 2.1.283:

❯ ./register.ts hooks: session.start, command.run{command=lockfile-sync}, turn.complete, prompt.submit, tool.call{tool=Bash}
❯ ./register.ts calls: $.command.register, $.env.get (via tmpDir), $.fs.exists (via lockOnDisk), $.fs.read (via treeText), $.process.run (via git, lockVerdict), $.session.cwd (via beforeCommit), $.sidebar.clear (via dropEntry), $.sidebar.set (via toPerson), $.store.get (via readSettings), $.store.set (via runCommand, setMode), $.ui.log (via denyFor, toPerson, toolFailed)

Reach L3, runs processes that reach the network.

1. Reads:    the Bash command text; whether lockfiles exist in the repository; each open finding's manifest and lockfile in the working tree; through git, the commit's file list, manifest diffs and each manifest at HEAD; TMPDIR
2. Runs:     git rev-parse, git show, git status, git log and git diff, read-only, by argv; and the lockfile's package manager check of step 4, once per manifest without its lockfile at a commit and once per open pair at each measure, also at the turn's end
3. Sends:    a note to the model after the commit's result, one more with the next prompt while a finding stands, and one line to the transcript; the package manager may ask its registry for the package metadata it resolves against
4. Persists: in $.store, the on/off setting and the mode; the package managers keep their own caches, and mix fetches into $TMPDIR/lockfile-sync/mix-deps
5. Hostile input: the directory comes from the command text and reaches git and the package manager only as the working directory, never through a shell; manifest paths reach them as one argv entry. The check runs code the project holds: a Gemfile is Ruby and a mix.exs is Elixir, and both are evaluated. npm, pnpm and bun run with --ignore-scripts, pnpm with --ignore-pnpmfile, and composer with --no-plugins, so their project scripts and plugins do not run

Limits

  • Where the package manager check proves nothing, the mod compares file names and diff sections alone, and does not check that the lockfile's content matches the manifest.
  • The verdict is the package manager's own: npm ci does not compare the root package's version, and yarn check reads a lockfile that still lists a removed dependency as in step.
  • A Yarn 2+ yarn.lock has no check here: yarn install --immutable links node_modules into the project, and --mode=update-lockfile does not combine with --immutable.
  • While a finding stands, its check runs again at every main-loop turn's end, up to 60 s per pair.
  • A lockfile no commit ever wrote has nothing to compare the manifest against, so only the first measure can close its finding.
  • Two lockfiles of one manager in one directory (a yarn.lock beside a package-lock.json) pair with the first in the table.
  • A commit through a script or an alias that hides git commit is not seen.
  • A cd or git -C whose directory the shell expands first (cd $D, cd ~/x, a backquote) names no directory the mod can tell. That commit is not checked, and the yellow line names the word, for example the commit's directory is not known: cd $D. A word in single quotes stays as written.
  • A merge commit's combined diff is not read.
  • The deny mode has no bypass. When a finding cannot be fixed, you turn the gate off with /lockfile-sync mode note.
  • The gate reads any change to the lockfile in the working tree as the fix; it does not check what that change holds.
  • A git commit -a, a -am and a commit with a pathspec after -- are not narrowed to the index, because they commit files the index does not hold yet. Every open pair stands for those.

Development

make install     # eslint, typescript-eslint, typescript
make lint        # complexity limit 10, the build fails above it
make typecheck   # needs .claude/types/ from /plugin-types
make validate
make test        # claude plugin test

関連作品