fix/skill-check-compliance-and-flow #13

Merged
admin merged 4 commits from fix/skill-check-compliance-and-flow into develop 2026-08-31 03:16:18 +00:00
Showing only changes of commit 1b223bd350 - Show all commits
+43 -31
View File
@@ -1,6 +1,6 @@
---
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.
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. Hold suspects at their old version and retry once, and revert only on a real install, build or test failure. Supports nodejs, python, and dotnet projects. Use for dependency refresh; not for adding or removing packages.
---
# pkg-update — batch-update packages
@@ -10,45 +10,57 @@ description: Update every external package of a project to its latest stable ver
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.
- **stop** — report and end the run. Never enter step 4, so nothing is reverted and the working tree is left exactly as it stands.
- **revert** — go to step 4, which reverts the working tree.
**What a stop leaves behind.** Step 1 writes nothing, so a stop there leaves every file untouched. From step 2 on, a stop can land after `apply-version.sh` has already rewritten one or more version source files, and the stop route still reverts nothing: a broken toolchain is not a broken package, and step 1 already proved the tree was clean, so those rewrites are recoverable by hand. Every stop from step 2 on therefore reports three things — the failing tool with its exit code, every package already applied, and every version source file already rewritten. Report "no file was changed" only for a stop that happened before step 2 started.
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".
One number carries one category across all six tools, and the table below names the object that category points at for each tool. 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 |
| Tool | 0 | 1 (bad argument count) | 2 (unknown input) | 3 (no source file) | 4 | 5 | 6 (project dir not found) | other |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| `git-guard.sh` | continue | stop (also `revert` without `--confirm-destructive`) | stop (unknown subcommand) | — | stop (git missing) | stop (not a git work tree, tree not clean, or still dirty after a revert) | stop | stop |
| `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** |
| `latest-version.sh` | continue | stop | stop (unknown ecosystem) | — | skip (not in registry) | stop (curl or python3 missing) | — (takes no project dir) | stop |
| `apply-version.sh` | continue | stop | stop (unknown ecosystem) | skip | skip (package absent from the file) | stop (python3 missing) | stop | stop |
| `install-deps.sh` | continue | stop | stop (unknown ecosystem) | skip | stop (npm, pip or dotnet missing) | — | stop | **revert** |
| `build-test.sh` | continue | stop | stop (unknown ecosystem) | — (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.
`build-test.sh --detect` returns the same codes with the same routes. It writes nothing, so every stop route it takes leaves the working tree untouched.
## 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.
5. **Comments this step writes.** Holding a package at an older version, or coding around an incompatibility, sometimes needs a comment in the version source file or in the code. Write **why** the pin or the workaround exists, never a tracking number. The one this skill trips over most: **a third-party package's issue link is on the allow list** — it is what states the cause of the workaround and the condition for removing it, as in `// works around github.com/foo/bar/issues/88; drop this pin once that ships`. Out of a comment: internal issue ids, work package ids, personal names, `@` mentions and generated-by credits. Full list and allow list: `jsc-review/references/comment-scope.md`. `jsc-hooks/hooks/comment-scope.sh` compares every write against that list right after the file is written and prints a warning on a hit; fix the comment on the spot, then continue this step. Completion condition: every comment line this step added names a reason, carries no internal issue id, work package id, personal name, `@` mention or generated-by credit, and every `comment-scope.sh` warning this step received has been fixed and re-checked with no warning left.
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.
1. **Preflight: gate on a clean tree, then take inventory and prove a build and test command exists.** Nothing in this step writes a file. Run the three substeps in this order — each one only earns its cost once the one before it passed:
1. **The gate.** Run `tools/git-guard.sh check {project dir}` first, before any other tool. It proves the directory is a git work tree and that `git status --porcelain` prints zero bytes, untracked `??` lines included. Step 4 runs `git clean -fd` through the same script, which deletes untracked files with no reflog and no way back, so a clean tree is the precondition for the whole run. A non-zero exit ends the run here: report the guard's stderr verbatim, including every `??` line, and run no further tool. Order the gate first on purpose — a dirty tree then costs one guard call instead of a full package scan that gets thrown away.
2. **Inventory.** The gate exited 0 → run `tools/list-packages.sh {project dir}` for every external package (ecosystem, name, current version). Route the exit code per the table above. Zero rows → stop and report the project directory you scanned plus the supported ecosystems (nodejs / python / dotnet). Keep every row's current version: step 3 pins packages back to it.
3. **Build and test commands.** For every distinct ecosystem the inventory listed, run `tools/build-test.sh --detect {ecosystem} {project dir}`. This mode only detects, so the working tree stays untouched here too.
- 0 → the commands are inferable. Record them against this ecosystem.
- 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, and record both against this ecosystem. A user who cannot name them takes the stop route right here, where no file has been touched and there is nothing to revert.
- 1, 2, 4 or 6 → stop route. Report the code together with the bad argument, unknown ecosystem, missing command or missing directory.
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.
Asking this question here is the whole point: found out in step 3 instead, it wastes every rewrite and forces a revert.
4. Step 1 is done only when all three hold: `git-guard.sh check` exited 0, the inventory holds at least one row with an ecosystem, a name and a current version, and every ecosystem in that inventory carries either a detected command set or a user-supplied build command plus test command, written down. Report all three results, then enter step 2.
2. Update ecosystem by ecosystem. This step **MUST run as a sub agent — one sub agent per ecosystem, and the ecosystems run in parallel**, because no ecosystem touches another's version source files:
1. Query first, write second. Run `tools/latest-version.sh {ecosystem} {name}` for **every package of the ecosystem as one concurrent batch**; the queries are independent registry reads. Then run `tools/apply-version.sh {ecosystem} {project dir} {name} {version}` **one package at a time, sequentially**: several packages of one ecosystem share one version source file (package.json / requirements.txt / pyproject.toml / *.csproj), and concurrent rewrites of one file lose edits. 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, list every package already applied and every file already rewritten, and end the run without entering step 4. 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: hand that ecosystem to step 3's hold-and-retry route at substep 3.2. 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.
5. **Comments this step writes.** Coding around an incompatibility needs a comment in the version source file or in the code, and so does every hold written in step 3. Write **why** the workaround or the hold exists. The one this skill trips over most sits on the allow list, not the ban list: **a third-party package's issue link belongs in the comment** — it is what states the cause and the condition for removing the workaround, as in `// works around github.com/foo/bar/issues/88; drop this hold once that ships`. The ban list itself has one home, `jsc-review/references/comment-scope.md`, and `jsc-hooks/hooks/comment-scope.sh` enforces it in code, so do not re-audit it line by line here. Know when that enforcement actually fires: **only claude scans per file at write time** (PostToolUse). codex scans when a turn ends, kiro only when the next prompt is submitted, copilot and antigravity only through a session-level wrapper — so on those four the `comment-scope.sh sweep` that `jsc-git:commit` runs in step 5 is the only pass that lands before the comment reaches a commit. Fix any warning that does arrive on the spot. This substep is done when every comment this step wrote names the reason for its workaround or hold.
3. **Build and test, and on a real failure hold the culprits and retry once.** Holding is the only route that leaves a package on an older version, and it runs at most one extra build and test round per failing ecosystem:
1. Run the build and test for every ecosystem, using what step 1 recorded: `tools/build-test.sh {ecosystem} {project dir}` where substep 1.3 detected the commands, or the user's build command followed by the user's test command where substep 1.3 collected them. 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 → substep 1.3 already proved a command existed, so this code means the project changed underneath the run. Stop route: report the contradiction against what substep 1.3 recorded, and do not ask the same question twice.
- any other code → a real build or test failure. Go to substep 2. An ecosystem handed over by substep 2.3 for a real `install-deps.sh` failure enters at substep 2 as well.
2. Name the suspects: every package this run updated whose name appears in the install, build or test failure output. No named suspect → go straight to step 4.
3. Run `tools/apply-version.sh {ecosystem} {project dir} {name} {old version}` for each suspect, sequentially, with the old version substep 1.2 recorded. Route per the table, with one change: `apply-version.sh` exit 3 and exit 4 mean the hold cannot be written at all, so that ecosystem goes to step 4 instead of skipping the package.
4. Write the comment for every hold per substep 2.5 — the reason for the hold and, where a third-party issue drives it, that issue's link.
5. Run `tools/install-deps.sh {ecosystem} {project dir}` once, then this ecosystem's build and test command once, routing both per substep 2.3 and substep 3.1. Retry exactly once: exit 0 → this ecosystem passed with those packages held, and step 5 reports them as held rather than updated. Any non-zero on the retry → step 4.
Step 3 is done only when every ecosystem landed in exactly one of three end states: exit 0 on the first run; exit 0 on the single retry, with every suspect of that ecosystem carrying a written hold plus a comment naming its reason; or entered step 4.
4. A real install, build or test failure reached this step → revert. Run `tools/git-guard.sh revert {project dir} --confirm-destructive`. The script re-proves the target is a git work tree before it runs anything destructive, restores every tracked file with `git checkout -- .`, removes this run's untracked files with `git clean -fd`, and verifies `git status --porcelain` prints nothing. 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. Route the exit code per the table above. Step 4 is done only when the script exited 0; report the failing packages with an error summary after that.
5. Success → report the update list (package, old version, new version), every package held at its old version by step 3 with the reason, and every skip with its reason, then hand off to `jsc-git:commit`. Step 5 is done when the hand-off is made and every package from step 1 appears exactly once, as an update, a hold or a skip.