--- name: pkg-update description: Update every external package of a project to its latest stable version using list-packages.sh and latest-version.sh, then run build and tests. Revert all changes on failure. Supports nodejs, python, and dotnet projects. Use for dependency refresh; not for adding or removing packages. --- # pkg-update — batch-update packages ## Exit-code routes Every tool exit code below has exactly one route. Three routes exist: - **skip** — record the reason, keep going. - **stop** — report and end the run. Change no file, and never enter step 5. - **revert** — go to step 5, which reverts the working tree. Only a real install, build or test failure takes the revert route. A missing input, a bad argument, an unknown ecosystem or a missing command is a broken call or a broken toolchain, so it takes the stop route. One number carries one meaning across all five tools. Exit 6 is always a missing project directory and always stops the run — never fold it into the exit 3 skip, or a bad path hides as "nothing to do". | Tool | 0 | 1 (bad argument count) | 2 (unknown ecosystem) | 3 (no source file) | 4 | 5 | 6 (project dir not found) | other | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | `list-packages.sh` | continue | stop | — | — | stop (python3 missing) | — | stop | stop | | `latest-version.sh` | continue | stop | stop | — | skip (not in registry) | stop (curl or python3 missing) | — (takes no project dir) | stop | | `apply-version.sh` | continue | stop | stop | skip | skip (package absent from the file) | stop (python3 missing) | stop | stop | | `install-deps.sh` | continue | stop | stop | skip | stop (npm, pip or dotnet missing) | — | stop | **revert** | | `build-test.sh` | continue | stop | stop | — (uses 5 instead) | stop (npm, python3, pytest or dotnet missing) | ask the user | stop | **revert** | Exit 4 and exit 5 are the two codes whose meaning depends on the tool, so read them off this table rather than from memory. ## Steps 1. Run `tools/list-packages.sh {project dir}` to get every external package (ecosystem, name, current version). Route the exit code per the table above. Step 1 is done only when the tool exited 0 and at least one row came back, with an ecosystem, a name and a current version in every row. Zero rows → stop and report the project directory you scanned plus the supported ecosystems (nodejs / python / dotnet), and run no further step. 2. Prove the project directory is a git repository with a clean tree. Run this before any step changes a file: 1. Run `git -C {project dir} rev-parse --git-dir`. Exit 0 is required to continue, because step 5's revert is impossible outside a git repository. Any other exit code → stop and report the directory, and run no further step. 2. Run `git -C {project dir} status --porcelain`. Continuing requires that it prints **nothing at all**. Any output stops the run and reports that output, **including `??` untracked lines**: step 5 runs `git clean -fd`, which deletes untracked files with no reflog and no way back. 3. Step 2 is done only when `rev-parse --git-dir` exited 0 and `status --porcelain` printed zero bytes. Report both results, then enter step 3. 3. Update ecosystem by ecosystem. This step **MUST run as a sub agent** (one sub agent per ecosystem): 1. For every package, run `tools/latest-version.sh {ecosystem} {name}` for the latest stable version, then `tools/apply-version.sh {ecosystem} {project dir} {name} {version}` to rewrite the version source file (package.json / requirements.txt / pyproject.toml / *.csproj). This substep is done when every package of the ecosystem carries either an applied version or a skip reason from substep 2. 2. Route every exit code per the table above. The skip route covers `latest-version.sh` exit 4 (package not found in the registry), `apply-version.sh` exit 3 (no version source file) and `apply-version.sh` exit 4 (package absent from the file): record the package and the reason, move on to the next package, and never abort the whole ecosystem. The stop route covers exit 1, exit 2, `latest-version.sh` or `apply-version.sh` exit 5 (a required command is missing) and `apply-version.sh` exit 6 (project directory not found): report the code and end the run without entering step 5. This substep is done when every exit code seen has taken exactly one route. 3. Run `tools/install-deps.sh {ecosystem} {project dir}` once, after every package of that ecosystem is applied. Exit 0 means the install finished. Exit 3 means this ecosystem has no dependency source file, which is the same documented skip as substep 2: record the ecosystem and the reason, and install nothing. Exit 1, exit 2, exit 4 and exit 6 take the stop route — exit 6 is a missing project directory, so report it instead of skipping it. Only another non-zero code is a real install failure and goes to step 5. This substep is done when the exit code is recorded together with the route it took. 4. The sub agent returns one row per package from substep 1: name, old version, new version — or `skipped` plus the reason. It also returns the `install-deps.sh` exit code and its route. The ecosystem is done only when every package appears in exactly one row. 4. Run `tools/build-test.sh {ecosystem} {project dir}` for every ecosystem and route on the exit code: - 0 → this ecosystem passed. - 1, 2, 4 or 6 → stop route. Report the code and the missing argument, ecosystem, command or directory, and enter no further step. Exit 4 means the toolchain is broken, not that the packages are broken, so reverting would destroy files for nothing. - 5 → no build or test command could be inferred. Ask the user for the build command and the test command per the `jsc-ask:ask` rules, run the answers in that order, and take their exit codes as this ecosystem's result: all 0 → passed; any non-zero → step 5. - any other code → a real build or test failure. Go to step 5. Step 4 is done only when every ecosystem reached exit 0, from the tool or from the user's own commands. 5. A real install, build or test failure reached this step → revert: 1. Run `git -C {project dir} checkout -- .` to restore every tracked file to HEAD. 2. Run `git -C {project dir} clean -fd` to remove the untracked files and directories this run created, such as a new lock file. Gitignored paths survive `-fd` on purpose: `node_modules`, `__pycache__`, `bin/` and `obj/` stay behind, so restoring install output is out of scope for this skill. Do not reach for `-fdx` — it deletes far more than this run created. 3. The revert is done only when `git -C {project dir} status --porcelain` prints nothing. Report the failing packages with an error summary after that check passes. 6. Success → report the update list (package, old version, new version), plus every skip and its reason, and hand off to `jsc-git:commit`. Step 6 is done when the hand-off is made and every package from step 1 appears either as an update or as a skip.