skilldrop-cli 0.16.31 → 0.16.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/bin/skilldrop.js +133 -18
  2. package/contracts/skill.schema.json +3 -3
  3. package/package.json +1 -1
  4. package/packs/converters/skills/file-to-markdown/SKILL.md +8 -4
  5. package/packs/converters/skills/file-to-markdown/manifest.json +11 -2
  6. package/packs/converters/skills/file-to-markdown/scripts/to_markdown.py +118 -8
  7. package/packs/converters/skills/md-to-docx/examples/q3-support-review.md +7 -0
  8. package/packs/converters/skills/md-to-xlsx/examples/vendor-review.md +6 -1
  9. package/packs/converters/skills/mermaid-render/manifest.json +11 -1
  10. package/packs/dev-team/skills/codebase-tour/evals/files/3/.github/workflows/ci.yml +2 -2
  11. package/packs/dev-team/skills/codebase-tour/evals/files/3/go.mod +2 -2
  12. package/packs/dev-team/skills/dependency-upgrade-review/SKILL.md +52 -34
  13. package/packs/dev-team/skills/dependency-upgrade-review/evals/evals.json +33 -0
  14. package/packs/dev-team/skills/dependency-upgrade-review/evals/files/6/.github/workflows/ci.yml +39 -0
  15. package/packs/dev-team/skills/dependency-upgrade-review/evals/files/6/.github/workflows/integration.yml +20 -0
  16. package/packs/dev-team/skills/dependency-upgrade-review/evals/files/6/PR.md +39 -0
  17. package/packs/dev-team/skills/dependency-upgrade-review/evals/files/6/go.mod +17 -0
  18. package/packs/dev-team/skills/dependency-upgrade-review/evals/files/6/internal/notify/client.go +35 -0
  19. package/packs/dev-team/skills/dependency-upgrade-review/evals/files/6/internal/payments/client.go +66 -0
  20. package/packs/dev-team/skills/dependency-upgrade-review/evals/files/6/internal/payments/client_integration_test.go +38 -0
  21. package/packs/dev-team/skills/dependency-upgrade-review/evals/files/6/internal/payments/client_test.go +53 -0
  22. package/packs/dev-team/skills/dependency-upgrade-review/evals/files/6/internal/payments/settle.go +30 -0
  23. package/packs/dev-team/skills/dependency-upgrade-review/evals/files/6/pr.diff +42 -0
  24. package/packs/dev-team/skills/dependency-upgrade-review/evals/files/6/vendor-notes/httpkit-CHANGELOG.md +43 -0
  25. package/packs/dev-team/skills/dependency-upgrade-review/evals/files/7/.github/workflows/ci.yml +67 -0
  26. package/packs/dev-team/skills/dependency-upgrade-review/evals/files/7/.lintwright.json +7 -0
  27. package/packs/dev-team/skills/dependency-upgrade-review/evals/files/7/.nvmrc +1 -0
  28. package/packs/dev-team/skills/dependency-upgrade-review/evals/files/7/PR.md +110 -0
  29. package/packs/dev-team/skills/dependency-upgrade-review/evals/files/7/package.json +25 -0
  30. package/packs/dev-team/skills/dependency-upgrade-review/evals/files/7/pr.diff +146 -0
  31. package/packs/dev-team/skills/dependency-upgrade-review/evals/files/7/src/due-date.ts +16 -0
  32. package/packs/dev-team/skills/dependency-upgrade-review/evals/files/7/src/totals.ts +11 -0
  33. package/packs/dev-team/skills/dependency-upgrade-review/evals/files/7/tests/due-date.test.ts +17 -0
  34. package/packs/dev-team/skills/dependency-upgrade-review/evals/files/7/tests/totals.test.ts +12 -0
  35. package/packs/dev-team/skills/dependency-upgrade-review/examples/ledgerline-tidyparse.md +23 -14
  36. package/packs/dev-team/skills/dependency-upgrade-review/manifest.json +17 -5
  37. package/packs/dev-team/skills/dependency-upgrade-review/reference.md +36 -1
  38. package/packs/dev-team/skills/dependency-upgrade-review/scripts/upgrade_diff.py +22 -4
  39. package/packs/dev-team/skills/sonar-review/manifest.json +12 -0
  40. package/packs/infra-as-code/skills/terraform-module/manifest.json +11 -0
  41. package/packs/infra-as-code/skills/terraform-plan-review/manifest.json +8 -1
  42. package/packs/solution-architect/skills/figma-diagrams/manifest.json +12 -1
@@ -1,6 +1,6 @@
1
1
  # dependency-upgrade-review reference
2
2
 
3
- Long-form material for the steps in `SKILL.md`: what each ecosystem's diff shows, where the risky signals live, how to list the releases between two versions, how to verify a pin, and how to roll back.
3
+ Long-form material for the steps in `SKILL.md`: what each ecosystem's diff shows, where the risky signals live, how to list the releases between two versions, how to trace a behaviour change to its callers, where runtime floors bite, how to verify a pin, and how to roll back.
4
4
 
5
5
  ## Reading a lockfile diff, by ecosystem
6
6
 
@@ -44,6 +44,36 @@ What to pull from each release, in this order:
44
44
  3. **Deprecations:** still works, removed later. A deprecated call the repo makes is a 🟧, not a 🟥.
45
45
  4. **Licence:** a changed `license` field or a LICENSE file change. Moves between permissive licences (MIT, BSD, Apache-2.0, ISC) are a note. A move to a copyleft licence (GPL, AGPL, MPL at file level, LGPL) or a source-available licence (BUSL, SSPL, Elastic, "Commons Clause") needs a sign-off from whoever owns licence policy before merge.
46
46
 
47
+ ## Tracing a behaviour change to its callers
48
+
49
+ A search hit is where the review starts. Read the calling function and write what it does under the new version.
50
+
51
+ | Change in the notes | What to look for in the caller | What breaks |
52
+ |---|---|---|
53
+ | Returns `None` / `-1` / `false` → raises | `if result is None`, `== -1`, `if not ok` branches; retry loops and fallbacks built on them | The branch never runs; the first failure escapes as an exception and the retry or fallback is dead code |
54
+ | Raises → returns a sentinel | `try` / `except` around the call | The error is swallowed; the code carries on with the sentinel |
55
+ | Exception class renamed or re-parented | `except OldError`, `catch (e instanceof OldError)` | The handler stops catching it |
56
+ | Tuple or dict → object | `a, b = f(...)`, `result["key"]` | Unpacking or key access fails at runtime |
57
+ | Changed default | Every call that does not pass the option | Each one gets the new behaviour silently |
58
+ | Module or function removed | Imports at the top of a file, and imports inside functions | A top-level import fails at startup and in tests; an import inside a function fails only when that function runs |
59
+
60
+ Name the fix in the repo's code, and any migration aid an intermediate release ships (an opt-in flag that turns on the new behaviour early). Moving to that release first lets the code change land and be tested before the major.
61
+
62
+ ## Runtime floors
63
+
64
+ A release that drops a runtime version breaks wherever the old runtime still runs, and CI usually runs a newer one.
65
+
66
+ | Floor raised in | Compare with |
67
+ |---|---|
68
+ | Release notes ("requires Python 3.10"), `python_requires`, `python-versions` in `poetry.lock`, `requires-python` in `uv.lock` | Dockerfile `FROM`, `setup-python` version and matrix, `.python-version`, the Python the lockfile was compiled on |
69
+ | `engines.node` in `package-lock.json` or `package.json` | `.nvmrc`, `setup-node` / toolchain version, Dockerfile `FROM`, `engine-strict` in `.npmrc` |
70
+ | `go` directive in `go.mod`, `toolchain` line | `setup-go` version, Dockerfile builder image |
71
+ | `rust-version` (MSRV) in a crate | `rust-toolchain.toml`, CI toolchain |
72
+ | Wheels or prebuilt binaries only for some versions or platforms | Every image and runner that installs it; elsewhere it builds from source and needs a compiler or toolchain |
73
+ | An action's runtime (`node20` → `node24`) | Self-hosted runner versions; GitHub-hosted runners update themselves |
74
+
75
+ A lockfile generated on a newer runtime than production can resolve versions production cannot install. `pip install --require-hashes` and `npm ci` fail at build time, not at review time.
76
+
47
77
  ## Verifying a pin
48
78
 
49
79
  ```bash
@@ -57,6 +87,7 @@ git ls-remote https://github.com/<owner>/<repo> refs/tags/<tag> 'refs/tags/<tag>
57
87
  - A SHA with no `# vX.Y.Z` comment: list the tags (`git ls-remote --tags https://github.com/<owner>/<repo>`) and find the SHA. No tag points to it means it is not a release.
58
88
  - Tag-only pin (`uses: owner/repo@v3`): the maintainer, or anyone who takes over the account, can point `v3` at new code after review. Pin to the commit the `^{}` line returns and keep the tag as a comment.
59
89
  - Branch pin (`@main`): every run gets whatever was pushed last. A PR that introduces one is DO NOT MERGE.
90
+ - Two SHAs for one tag: when the same `owner/repo` and `# vX.Y.Z` comment appear with different SHAs across workflow files, at most one is the tag's commit. Run the command once and compare both; until then, the PR is DO NOT MERGE. Read the PR's commit list: a bot writes every pin the same way, so a pin that differs from the bot's usually came from a hand-made commit.
60
91
 
61
92
  Stakes come from the job, not the action: look at `permissions:` (`contents: write`, `id-token: write`, `packages: write`), the `secrets.*` it receives, and the `environment:` it runs in.
62
93
 
@@ -70,6 +101,10 @@ Stakes come from the job, not the action: look at `permissions:` (`contents: wri
70
101
  | A build or publish step (bundler, packager, uploader) not in the PR workflow | No |
71
102
  | A source file no test imports | No |
72
103
  | A test that imports the file but never hits the changed behaviour (a default, an edge case the notes name) | Not the part that changed |
104
+ | A test that mocks the dependency with the old contract (a fake that returns a value where the new version raises) | No; it tests the old behaviour |
105
+ | An import inside a function no test calls | No; it never fails at import or collection |
106
+ | An install script, when the PR job installs with `--ignore-scripts` and the release job does not | No; it first runs in the release job, with that job's secrets and permissions |
107
+ | A runtime older than the one CI uses (production image, self-hosted runner) | No |
73
108
 
74
109
  Ways to exercise an unrun path before merge: a `workflow_dispatch` dry run on the PR branch, a release to a test index (TestPyPI, a scratch npm scope), or a test that covers the changed behaviour, added in the same PR.
75
110
 
@@ -161,6 +161,7 @@ def host_of(url):
161
161
  class Report:
162
162
  def __init__(self):
163
163
  self.changes, self.flags, self.files, self.unattributed = [], [], [], {}
164
+ self.sha_pins = {} # (repo, tag) -> {sha: [files]}, from added `uses:` lines
164
165
 
165
166
  def change(self, path, eco, where, name, old, new, note=None):
166
167
  if old == new:
@@ -256,6 +257,12 @@ def merge_blocks(rep, path, eco, hunks, key_fn, field_res, name_fn, reset_re=Non
256
257
  rep.flag("source", path, f"{label}: {f} moved from {oh} to {nh}")
257
258
  elif nh and not o and f != "index" and nh not in DEFAULT_HOSTS:
258
259
  rep.flag("source", path, f"{label}: new package comes from {nh}, not the default registry")
260
+ for f in ("node", "python-versions"):
261
+ orv, nrv = of.get(f, (None,))[0], nf.get(f, (None,))[0]
262
+ if o and nrv and orv != nrv:
263
+ rt = "Node" if f == "node" else "Python"
264
+ rep.flag("runtime", path, f"{label}: requires {rt} {nrv} (was {orv or 'unstated'}); check every "
265
+ f"place it installs and runs (base images, CI versions, runners)")
259
266
  ol, nl = of.get("license", (None,))[0], nf.get("license", (None,))[0]
260
267
  if ol and nl and ol != nl:
261
268
  rep.flag("licence", path, f"{label}: licence changed from {ol} to {nl}")
@@ -317,7 +324,7 @@ def npm_lock_name(key):
317
324
 
318
325
 
319
326
  NPM_FIELDS = {f: re.compile(rf'^\s*"{f}":\s*"?([^",]+)"?,?\s*$') for f in
320
- ("version", "resolved", "license", "hasInstallScript", "dev", "optional", "gypfile")}
327
+ ("version", "resolved", "license", "hasInstallScript", "dev", "optional", "gypfile", "node")}
321
328
 
322
329
 
323
330
  def h_package_lock(rep, path, hunks):
@@ -360,7 +367,8 @@ def h_pnpm_lock(rep, path, hunks):
360
367
 
361
368
  TOML_PKG_FIELDS = {"version": re.compile(r'^version\s*=\s*"([^"]+)"'),
362
369
  "source": re.compile(r'^source\s*=\s*"([^"]+)"'),
363
- "url": re.compile(r'^(?:url|source\s*=\s*\{\s*(?:registry|git|url))\s*=\s*"([^"]+)"')}
370
+ "url": re.compile(r'^(?:url|source\s*=\s*\{\s*(?:registry|git|url))\s*=\s*"([^"]+)"'),
371
+ "python-versions": re.compile(r'^python-versions\s*=\s*"([^"]+)"')}
364
372
 
365
373
 
366
374
  def h_toml_lock(eco):
@@ -527,6 +535,7 @@ def h_workflow(rep, path, hunks):
527
535
  if n and SHA40.fullmatch(n[0]):
528
536
  tag_ = re.match(r"^(v?[\w.\-]+)", n[1]).group(1) if n[1] else None
529
537
  if tag_:
538
+ rep.sha_pins.setdefault((repo, tag_), {}).setdefault(n[0], []).append(path)
530
539
  rep.flag("pin", path, f"{name}@{n[0]} claims {tag_}. UNVERIFIED until the tag resolves to this commit: "
531
540
  f"`git ls-remote https://github.com/{repo} refs/tags/{tag_} 'refs/tags/{tag_}^{{}}'` "
532
541
  f"(the `^{{}}` line is the commit for an annotated tag; it must equal {n[0]})")
@@ -591,9 +600,16 @@ def analyse(files, title):
591
600
  for r in rep.changes:
592
601
  short = r["name"].split("/")[-1].lower()
593
602
  r["in_title"] = r["name"].lower() in t or (len(short) > 3 and re.search(rf"\b{re.escape(short)}\b", t) is not None)
603
+ for (repo, tag_), shas in rep.sha_pins.items():
604
+ if len(shas) > 1:
605
+ where = "; ".join(f"{s} in {', '.join(dict.fromkeys(ps))}" for s, ps in shas.items())
606
+ rep.flag("pin-conflict", next(iter(shas.values()))[0],
607
+ f"{repo} {tag_} is pinned to {len(shas)} different commits ({where}). A tag points to one "
608
+ f"commit, so at most one of these is {tag_}; find which with "
609
+ f"`git ls-remote https://github.com/{repo} refs/tags/{tag_} 'refs/tags/{tag_}^{{}}'`")
594
610
  reasons_stop, reasons_check = [], []
595
611
  for fl in rep.flags:
596
- if fl["kind"] in ("install-script", "source"):
612
+ if fl["kind"] in ("install-script", "source", "pin-conflict"):
597
613
  reasons_stop.append(f"{fl['kind']}: {fl['text']}")
598
614
  elif fl["kind"] == "pin":
599
615
  reasons_check.append("pin: " + fl["text"].split(". ")[0])
@@ -662,9 +678,11 @@ def render_md(rep, floor, stop, check, title):
662
678
  out += ["", f"## Beyond the title ({len(beyond)})", ""]
663
679
  out += [f"- `{r['name']}` {r['kind']}: {r['old'] or '—'} → {r['new'] or '—'} ({r['file']})" for r in beyond] or ["None."]
664
680
  labels = (("install-script", "Install scripts"), ("native", "Native code"), ("source", "Sources and registries"),
665
- ("licence", "Licences"), ("pin", "Pins (verify every SHA; the script cannot)"))
681
+ ("licence", "Licences"), ("runtime", "Runtime requirements"), ("pin", "Pins (verify every SHA; the script cannot)"))
666
682
  for kind, heading in labels:
667
683
  rows = [f for f in rep.flags if f["kind"] == kind]
684
+ if kind == "pin":
685
+ rows = [f for f in rep.flags if f["kind"] == "pin-conflict"] + rows
668
686
  out += ["", f"## {heading} ({len(rows)})", ""]
669
687
  out += [f"- `{f['file']}`: {f['text']}" for f in rows] or ["None seen in the diff."]
670
688
  if rep.unattributed:
@@ -33,5 +33,17 @@
33
33
  "model": {
34
34
  "tier": "standard",
35
35
  "rationale": "Formats Sonar API findings into a report. The hard analysis is Sonar's; this is synthesis. Escalates on very large diffs."
36
+ },
37
+ "permissions": {
38
+ "network": [
39
+ "sonarcloud.io"
40
+ ],
41
+ "commands": [
42
+ "sonar-scanner",
43
+ "git",
44
+ "curl"
45
+ ],
46
+ "files": "none",
47
+ "notes": "No scripts. Its instructions run sonar-scanner and query the Sonar API with curl, using SONAR_TOKEN from the environment, against sonarcloud.io or the self-hosted SonarQube URL the user configures."
36
48
  }
37
49
  }
@@ -37,5 +37,16 @@
37
37
  "model": {
38
38
  "tier": "standard",
39
39
  "rationale": "Structured generation against fixed module conventions and a secure-defaults checklist; the judgment is bounded to choosing defaults and validation rules for a described need."
40
+ },
41
+ "permissions": {
42
+ "network": [
43
+ "registry.terraform.io",
44
+ "releases.hashicorp.com"
45
+ ],
46
+ "commands": [
47
+ "terraform"
48
+ ],
49
+ "files": "none",
50
+ "notes": "No scripts. Its instructions run `terraform fmt`, `terraform init -backend=false` and `terraform validate` on the module it writes. `init` downloads the providers from the Terraform registry; none of the commands contacts a cloud account."
40
51
  }
41
52
  }
@@ -40,7 +40,14 @@
40
40
  "iam",
41
41
  "drift"
42
42
  ],
43
- "permissions": {"network": [], "commands": [], "files": "named-paths", "notes": "Reads a plan JSON, which can hold secrets in clear text; the script never prints attribute values."},
43
+ "permissions": {
44
+ "network": [],
45
+ "commands": [
46
+ "terraform"
47
+ ],
48
+ "files": "named-paths",
49
+ "notes": "Reads a plan JSON, which can hold secrets in clear text; the script never prints attribute values. Its instructions run `terraform show -json` to produce that plan JSON."
50
+ },
44
51
  "model": {
45
52
  "tier": "heavy",
46
53
  "rationale": "Adversarial review of a destructive change: judging which replacements lose data, which access changes widen exposure, and what is out of scope needs careful reasoning; a missed destroy costs far more than the model."
@@ -28,7 +28,18 @@
28
28
  "design",
29
29
  "api"
30
30
  ],
31
- "permissions": {"network": ["api.figma.com"], "commands": [], "files": "named-paths", "notes": "Reads FIGMA_TOKEN from the environment to call the Figma REST API."},
31
+ "permissions": {
32
+ "network": [
33
+ "api.figma.com",
34
+ "pypi.org",
35
+ "files.pythonhosted.org"
36
+ ],
37
+ "commands": [
38
+ "pip"
39
+ ],
40
+ "files": "named-paths",
41
+ "notes": "Reads FIGMA_TOKEN from the environment to call the Figma REST API. Its instructions install the pinned requirements once with `python3 -m pip install -r requirements.txt`, which reaches PyPI."
42
+ },
32
43
  "model": {
33
44
  "tier": "standard",
34
45
  "rationale": "API calls + spec generation. Bounded transformation."