skilldrop-cli 0.16.35 → 0.16.37
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.
- package/README.md +1 -0
- package/agents/README.md +4 -1
- package/agents/docs-reviewer.md +47 -0
- package/agents/finding-adjudicator.md +49 -0
- package/agents/test-reviewer.md +52 -0
- package/catalogue.json +19 -7
- package/model-routing.json +48 -0
- package/package.json +1 -1
- package/packs/ai-engineering/skills/agent-config-review/SKILL.md +116 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/eval_queries.json +12 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/evals.json +75 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/1/.claude/agents/db-migrator.md +8 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/1/.claude/agents/reviewer.md +9 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/1/.claude/settings.json +48 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/1/.claude/settings.local.json +5 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/1/.mcp.json +22 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/1/package.json +12 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/2/.cursor/mcp.json +17 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/2/config.toml +21 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/4/my-user-settings.json +11 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/4/quillstack/.claude/agents/release-notes.md +13 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/4/quillstack/.claude/settings.json +39 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/4/quillstack/.mcp.json +18 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/4/quillstack/package.json +14 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/5/.github/workflows/codex-triage.yml +29 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/5/AGENTS.md +16 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/5/codex/config.toml +34 -0
- package/packs/ai-engineering/skills/agent-config-review/examples/inputs/tidewater-ingest/.claude/settings.json +22 -0
- package/packs/ai-engineering/skills/agent-config-review/examples/inputs/tidewater-ingest/.mcp.json +9 -0
- package/packs/ai-engineering/skills/agent-config-review/examples/python-repo-tighten.md +81 -0
- package/packs/ai-engineering/skills/agent-config-review/manifest.json +60 -0
- package/packs/ai-engineering/skills/agent-config-review/reference.md +205 -0
- package/packs/ai-engineering/skills/agent-config-review/scripts/agent_config_scan.py +989 -0
- package/packs/ai-engineering/skills/agent-incident-response/SKILL.md +175 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/eval_queries.json +14 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/evals.json +107 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/1/agent-session-excerpt.txt +11 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/1/ci-build-log.txt +24 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/1/incident-notes.md +19 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/2/mcp-config.json +20 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/2/session-transcript.txt +8 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/4/agent-session.log +70 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/4/infra/staging-db.tf +25 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/5/jobs/nightly-triage/job.yaml +19 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/5/jobs/nightly-triage/run.log +13 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/5/usage-export-2026-10-20.csv +13 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/6/.mcp.json +20 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/6/git-log.txt +12 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/6/notesync-excerpt.txt +30 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/6/notesync.log +15 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/6/ssh-config.txt +16 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/.github/workflows/ci.yml +28 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/SECURITY.md +8 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/issue-479.md +35 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/package.json +15 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/pr-482.diff +20 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/run-log.txt +30 -0
- package/packs/ai-engineering/skills/agent-incident-response/examples/injected-issue-comment.md +57 -0
- package/packs/ai-engineering/skills/agent-incident-response/manifest.json +55 -0
- package/packs/ai-engineering/skills/agent-incident-response/reference.md +152 -0
- package/packs/ai-engineering/skills/agent-incident-response/scripts/find_secret_exposure.py +239 -0
- package/packs/ai-engineering/skills/agent-incident-response/scripts/usage_split.py +194 -0
- package/packs/ai-engineering/skills/judge-audit/SKILL.md +157 -0
- package/packs/ai-engineering/skills/judge-audit/evals/eval_queries.json +11 -0
- package/packs/ai-engineering/skills/judge-audit/evals/evals.json +136 -0
- package/packs/ai-engineering/skills/judge-audit/evals/files/1/judgements.csv +321 -0
- package/packs/ai-engineering/skills/judge-audit/evals/files/1/notes.md +28 -0
- package/packs/ai-engineering/skills/judge-audit/evals/files/2/pairwise.csv +101 -0
- package/packs/ai-engineering/skills/judge-audit/evals/files/2/run-notes.md +16 -0
- package/packs/ai-engineering/skills/judge-audit/evals/files/4/grades.csv +97 -0
- package/packs/ai-engineering/skills/judge-audit/evals/files/5/pairwise_judgements.jsonl +120 -0
- package/packs/ai-engineering/skills/judge-audit/evals/files/6/rag-judge/README.md +36 -0
- package/packs/ai-engineering/skills/judge-audit/evals/files/6/rag-judge/judgments.csv +301 -0
- package/packs/ai-engineering/skills/judge-audit/evals/files/7/moderation/gold_labels.csv +101 -0
- package/packs/ai-engineering/skills/judge-audit/evals/files/7/moderation/run_1.jsonl +100 -0
- package/packs/ai-engineering/skills/judge-audit/evals/files/7/moderation/run_2.jsonl +100 -0
- package/packs/ai-engineering/skills/judge-audit/evals/files/8/patches.csv +241 -0
- package/packs/ai-engineering/skills/judge-audit/evals/files/9/adequacy_ratings.csv +151 -0
- package/packs/ai-engineering/skills/judge-audit/examples/summary-panel.md +70 -0
- package/packs/ai-engineering/skills/judge-audit/manifest.json +44 -0
- package/packs/ai-engineering/skills/judge-audit/reference.md +176 -0
- package/packs/ai-engineering/skills/judge-audit/scripts/judge_stats.py +1735 -0
- package/packs/converters/skills/file-to-markdown/SKILL.md +2 -1
- package/packs/converters/skills/file-to-markdown/evals/eval_queries.json +4 -0
- package/packs/converters/skills/file-to-markdown/manifest.json +2 -2
- package/packs/converters/skills/file-to-markdown/scripts/to_markdown.py +253 -3
- package/packs/converters/skills/md-to-confluence/SKILL.md +139 -0
- package/packs/converters/skills/md-to-confluence/evals/eval_queries.json +42 -0
- package/packs/converters/skills/md-to-confluence/evals/evals.json +89 -0
- package/packs/converters/skills/md-to-confluence/evals/files/1/img/latency-dashboard.png +0 -0
- package/packs/converters/skills/md-to-confluence/evals/files/1/payments-runbook.md +60 -0
- package/packs/converters/skills/md-to-confluence/evals/files/2/deploy-checklist.md +33 -0
- package/packs/converters/skills/md-to-confluence/evals/files/3/img/after/screen.png +0 -0
- package/packs/converters/skills/md-to-confluence/evals/files/3/img/before/screen.png +0 -0
- package/packs/converters/skills/md-to-confluence/evals/files/3/release-notes-4.2.md +49 -0
- package/packs/converters/skills/md-to-confluence/evals/files/4/docs/img/topology.png +0 -0
- package/packs/converters/skills/md-to-confluence/evals/files/4/docs/ingest-pipeline-design.md +65 -0
- package/packs/converters/skills/md-to-confluence/evals/files/5/support/troubleshooting-sso.md +57 -0
- package/packs/converters/skills/md-to-confluence/examples/inputs/img/queue-depth.png +0 -0
- package/packs/converters/skills/md-to-confluence/examples/inputs/queue-runbook.fixed.md +21 -0
- package/packs/converters/skills/md-to-confluence/examples/inputs/queue-runbook.md +21 -0
- package/packs/converters/skills/md-to-confluence/examples/queue-runbook.md +57 -0
- package/packs/converters/skills/md-to-confluence/manifest.json +52 -0
- package/packs/converters/skills/md-to-confluence/reference.md +92 -0
- package/packs/converters/skills/md-to-confluence/scripts/md_to_confluence.py +821 -0
- package/packs/converters/skills/md-to-pptx/SKILL.md +131 -0
- package/packs/converters/skills/md-to-pptx/evals/eval_queries.json +12 -0
- package/packs/converters/skills/md-to-pptx/evals/evals.json +84 -0
- package/packs/converters/skills/md-to-pptx/evals/files/1/img/defect-burndown.png +0 -0
- package/packs/converters/skills/md-to-pptx/evals/files/1/status-report.md +75 -0
- package/packs/converters/skills/md-to-pptx/evals/files/3/brand.json +12 -0
- package/packs/converters/skills/md-to-pptx/evals/files/3/night-shift-induction.md +53 -0
- package/packs/converters/skills/md-to-pptx/evals/files/4/charts/ontime.png +0 -0
- package/packs/converters/skills/md-to-pptx/evals/files/4/qbr-q3.md +85 -0
- package/packs/converters/skills/md-to-pptx/evals/files/5/vendor-sync-notes.md +28 -0
- package/packs/converters/skills/md-to-pptx/examples/support-weekly-input.md +48 -0
- package/packs/converters/skills/md-to-pptx/examples/support-weekly.md +75 -0
- package/packs/converters/skills/md-to-pptx/examples/tickets-by-week.png +0 -0
- package/packs/converters/skills/md-to-pptx/manifest.json +49 -0
- package/packs/converters/skills/md-to-pptx/reference.md +139 -0
- package/packs/converters/skills/md-to-pptx/scripts/md_to_pptx.py +1841 -0
- package/packs/dev-team/loops/modernize/LOOP.md +124 -0
- package/packs/dev-team/loops/modernize/evals/eval_queries.json +9 -0
- package/packs/dev-team/loops/modernize/evals/evals.json +27 -0
- package/packs/dev-team/loops/modernize/loop.json +60 -0
- package/packs/dev-team/skills/behaviour-equivalence/SKILL.md +155 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/eval_queries.json +11 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/evals.json +76 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/cases.jsonl +12 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/new.jsonl +12 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/notes.md +15 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/old.jsonl +12 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/old_rerun.jsonl +12 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/pricing_v1.py +45 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/pricing_v2.py +65 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/rules.json +10 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/3/diff-summary.txt +16 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/4/diff.txt +13 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/4/fee_calc_py2.py +16 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/4/fee_calc_py3.py +24 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/4/input.csv +13 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/4/new_output.csv +13 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/4/old_output.csv +13 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/5/TEAM_NOTES.md +19 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/5/normalisation.yaml +12 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/5/recordings/new_run.jsonl +8 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/5/recordings/old_run1.jsonl +8 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/5/recordings/old_run2.jsonl +8 -0
- package/packs/dev-team/skills/behaviour-equivalence/examples/delivery-date.md +78 -0
- package/packs/dev-team/skills/behaviour-equivalence/manifest.json +33 -0
- package/packs/dev-team/skills/behaviour-equivalence/reference.md +97 -0
- package/packs/dev-team/skills/behaviour-equivalence/scripts/golden_diff.py +524 -0
- package/packs/dev-team/skills/behaviour-equivalence/templates/accepted.json +10 -0
- package/packs/dev-team/skills/behaviour-equivalence/templates/rules.json +7 -0
- package/packs/frontend/pack.json +61 -0
- package/packs/frontend/skills/bundle-size-review/SKILL.md +114 -0
- package/packs/frontend/skills/bundle-size-review/evals/eval_queries.json +12 -0
- package/packs/frontend/skills/bundle-size-review/evals/evals.json +108 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/1/package.json +28 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/1/src/utils/format.js +26 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/1/stats.json +1334 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/2/PR.md +21 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/2/meta-after.json +440 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/2/meta-before.json +360 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/3/package.json +29 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/3/stats.json +1583 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/4/src/components/CommentBox.jsx +24 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/4/stats/after.json +299 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/4/stats/before.json +275 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/5/.browserslistrc +3 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/5/build/meta.json +371 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/5/build.mjs +18 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/5/package-lock.json +39 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/5/package.json +22 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/5/src/components/Icon.jsx +8 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/5/src/polyfills.js +7 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/5/src/utils/groupRows.js +4 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/6/.browserslistrc +3 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/6/package.json +25 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/6/src/App.tsx +8 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/6/src/i18n/chartLocales.ts +16 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/6/src/i18n/index.ts +8 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/6/src/i18n/messages/de.ts +5 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/6/src/i18n/messages/en.ts +5 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/6/src/i18n/useLocale.ts +14 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/6/src/main.tsx +10 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/6/src/pages/Home.tsx +14 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/6/src/pages/Reports.tsx +31 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/6/src/routes.tsx +17 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/6/stats.json +1869 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/6/vite.config.ts +13 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/backoffice/package.json +26 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/backoffice/src/App.tsx +27 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/backoffice/src/pages/Analytics.tsx +10 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/backoffice/src/pages/Users.tsx +13 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/backoffice/stats.json +1421 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/backoffice/webpack.config.js +25 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/storefront/package.json +27 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/storefront/src/App.tsx +18 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/storefront/src/index.tsx +4 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/storefront/src/pages/Home.tsx +10 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/storefront/src/pages/Product.tsx +13 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/storefront/stats.json +1496 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/storefront/webpack.config.js +26 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/7/package.json +9 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/7/packages/ui/dist/Button.css +3 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/7/packages/ui/dist/Button.js +14 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/7/packages/ui/dist/Chart.js +21 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/7/packages/ui/dist/index.js +22 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/7/packages/ui/package.json +20 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/7/packages/ui/src/Button.css +3 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/7/packages/ui/src/Button.tsx +9 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/7/packages/ui/src/Chart.tsx +14 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/7/packages/ui/src/index.ts +6 -0
- package/packs/frontend/skills/bundle-size-review/evals/files/7/packages/ui/tsconfig.json +14 -0
- package/packs/frontend/skills/bundle-size-review/examples/inputs/stats-after.json +1358 -0
- package/packs/frontend/skills/bundle-size-review/examples/inputs/stats-before.json +1334 -0
- package/packs/frontend/skills/bundle-size-review/examples/larkspur-sparkline.md +52 -0
- package/packs/frontend/skills/bundle-size-review/manifest.json +52 -0
- package/packs/frontend/skills/bundle-size-review/reference.md +147 -0
- package/packs/frontend/skills/bundle-size-review/scripts/bundle_stats.py +1633 -0
- package/packs/frontend/skills/rendering-strategy/SKILL.md +149 -0
- package/packs/frontend/skills/rendering-strategy/evals/eval_queries.json +38 -0
- package/packs/frontend/skills/rendering-strategy/evals/evals.json +77 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/1/fernhill-app.md +40 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/3/routes.md +33 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/4/build-notes.txt +5 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/4/nuxt.config.ts +19 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/4/pages/recipes/[slug].vue +37 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/4/routes.md +15 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/5/routes.txt +18 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/5/src/hooks.server.ts +14 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/5/src/routes/(marketing)/pricing/+page.server.ts +10 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/5/src/routes/+layout.server.ts +10 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/5/svelte.config.js +10 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/5/traffic.md +18 -0
- package/packs/frontend/skills/rendering-strategy/examples/saas-site.md +42 -0
- package/packs/frontend/skills/rendering-strategy/manifest.json +50 -0
- package/packs/frontend/skills/rendering-strategy/reference.md +116 -0
- package/packs/frontend/skills/rendering-strategy/scripts/render_load.py +181 -0
- package/packs/frontend/skills/web-performance-review/SKILL.md +106 -0
- package/packs/frontend/skills/web-performance-review/evals/eval_queries.json +38 -0
- package/packs/frontend/skills/web-performance-review/evals/evals.json +76 -0
- package/packs/frontend/skills/web-performance-review/evals/files/1/psi.json +1065 -0
- package/packs/frontend/skills/web-performance-review/evals/files/2/after.json +359 -0
- package/packs/frontend/skills/web-performance-review/evals/files/2/before.json +375 -0
- package/packs/frontend/skills/web-performance-review/evals/files/4/psi-pdp-mobile-2026-10-19.json +171 -0
- package/packs/frontend/skills/web-performance-review/evals/files/4/rum-inp-attribution-oct14-19.csv +6 -0
- package/packs/frontend/skills/web-performance-review/evals/files/4/rum-inp-daily.csv +15 -0
- package/packs/frontend/skills/web-performance-review/evals/files/5/crux-home-phone.json +35 -0
- package/packs/frontend/skills/web-performance-review/evals/files/5/lighthouse-home-mobile.json +173 -0
- package/packs/frontend/skills/web-performance-review/examples/quayside-article-lhr.json +480 -0
- package/packs/frontend/skills/web-performance-review/examples/quayside-article.md +103 -0
- package/packs/frontend/skills/web-performance-review/manifest.json +49 -0
- package/packs/frontend/skills/web-performance-review/reference.md +105 -0
- package/packs/frontend/skills/web-performance-review/scripts/lh_summary.py +743 -0
- package/packs/product-manager/skills/lean-canvas/SKILL.md +162 -0
- package/packs/product-manager/skills/lean-canvas/evals/eval_queries.json +42 -0
- package/packs/product-manager/skills/lean-canvas/evals/evals.json +82 -0
- package/packs/product-manager/skills/lean-canvas/evals/files/1/pawpass-notes.md +54 -0
- package/packs/product-manager/skills/lean-canvas/evals/files/3/lunchloop-pitch.md +30 -0
- package/packs/product-manager/skills/lean-canvas/evals/files/4/clubkit-notes.md +44 -0
- package/packs/product-manager/skills/lean-canvas/evals/files/5/cost-model.csv +5 -0
- package/packs/product-manager/skills/lean-canvas/evals/files/5/proposal.md +40 -0
- package/packs/product-manager/skills/lean-canvas/evals/files/5/stakeholder-emails.txt +18 -0
- package/packs/product-manager/skills/lean-canvas/examples/bike-repair.md +72 -0
- package/packs/product-manager/skills/lean-canvas/manifest.json +34 -0
- package/packs/product-manager/skills/lean-canvas/reference.md +102 -0
- package/packs/product-manager/skills/lean-canvas/scripts/unit_economics.py +211 -0
- package/packs/solution-architect/skills/api-contract-draft/SKILL.md +2 -2
- package/packs/solution-architect/skills/api-contract-draft/manifest.json +28 -6
- package/packs/solution-architect/skills/event-contract-draft/SKILL.md +167 -0
- package/packs/solution-architect/skills/event-contract-draft/evals/eval_queries.json +38 -0
- package/packs/solution-architect/skills/event-contract-draft/evals/evals.json +76 -0
- package/packs/solution-architect/skills/event-contract-draft/evals/files/1/order-events-brief.md +45 -0
- package/packs/solution-architect/skills/event-contract-draft/evals/files/4/NOTES.md +23 -0
- package/packs/solution-architect/skills/event-contract-draft/evals/files/4/schemas/shipment-update.json +40 -0
- package/packs/solution-architect/skills/event-contract-draft/evals/files/5/invoice.example.json +25 -0
- package/packs/solution-architect/skills/event-contract-draft/examples/order-service.asyncapi.json +246 -0
- package/packs/solution-architect/skills/event-contract-draft/examples/order-service.md +92 -0
- package/packs/solution-architect/skills/event-contract-draft/manifest.json +25 -0
- package/packs/solution-architect/skills/event-contract-draft/reference.md +113 -0
- package/packs/solution-architect/skills/event-contract-draft/scripts/check_asyncapi.py +502 -0
- package/packs/solution-architect/skills/event-contract-draft/templates/asyncapi-skeleton.json +144 -0
- package/packs/sre-oncall/skills/slo-spec/SKILL.md +161 -0
- package/packs/sre-oncall/skills/slo-spec/evals/eval_queries.json +38 -0
- package/packs/sre-oncall/skills/slo-spec/evals/evals.json +75 -0
- package/packs/sre-oncall/skills/slo-spec/evals/files/1/order-service.md +51 -0
- package/packs/sre-oncall/skills/slo-spec/evals/files/3/search-slo-status.md +30 -0
- package/packs/sre-oncall/skills/slo-spec/evals/files/4/alerts/payments-api.rules.yaml +16 -0
- package/packs/sre-oncall/skills/slo-spec/evals/files/4/docs/dependencies.md +8 -0
- package/packs/sre-oncall/skills/slo-spec/evals/files/4/metrics/summary-90d.md +51 -0
- package/packs/sre-oncall/skills/slo-spec/evals/files/5/ops-notes.md +34 -0
- package/packs/sre-oncall/skills/slo-spec/examples/ticket-booking.md +88 -0
- package/packs/sre-oncall/skills/slo-spec/manifest.json +34 -0
- package/packs/sre-oncall/skills/slo-spec/reference.md +113 -0
- package/packs/sre-oncall/skills/slo-spec/scripts/slo_math.py +449 -0
- package/packs/sre-oncall/skills/slo-spec/templates/slo-spec.md +35 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Find where a leaked key appears in a repository's git history and in log files.
|
|
3
|
+
|
|
4
|
+
Read-only. Never prints the key: every match is shown masked (first 8 characters,
|
|
5
|
+
an ellipsis, the last 4, and the length). Identify the key by a prefix you already
|
|
6
|
+
know (--prefix, optionally with --suffix for the last characters) or by piping the
|
|
7
|
+
full key on stdin (--key-stdin) so it never lands in shell history.
|
|
8
|
+
|
|
9
|
+
python3 find_secret_exposure.py --prefix sk-live-AbCdEf12 --repo . --log ci.txt
|
|
10
|
+
printf '%s' "$LEAKED" | python3 find_secret_exposure.py --key-stdin --repo .
|
|
11
|
+
|
|
12
|
+
Reports: commits that added or removed it (author and commit time, UTC), whether the
|
|
13
|
+
current tree and working copy still hold it, which branches and tags still reach the
|
|
14
|
+
adding commits, commits reachable only from the local reflog, and every log line that
|
|
15
|
+
holds it with the line's own timestamp. Writes only the -o path when given.
|
|
16
|
+
"""
|
|
17
|
+
import argparse
|
|
18
|
+
import datetime as dt
|
|
19
|
+
import os
|
|
20
|
+
import re
|
|
21
|
+
import subprocess
|
|
22
|
+
import sys
|
|
23
|
+
|
|
24
|
+
TOKEN_CHARS = r"[A-Za-z0-9_\-+/=.]"
|
|
25
|
+
STAMP = re.compile(
|
|
26
|
+
r"(\d{4}-\d{2}-\d{2})[T ](\d{2}:\d{2}:\d{2})(?:\.\d+)?(Z|[+-]\d{2}:?\d{2})?"
|
|
27
|
+
)
|
|
28
|
+
MIN_PREFIX = 10
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def mask(token):
|
|
32
|
+
if len(token) <= 12:
|
|
33
|
+
return token[:4] + "…" + " (len %d)" % len(token)
|
|
34
|
+
return "%s…%s (len %d)" % (token[:8], token[-4:], len(token))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Needle:
|
|
38
|
+
def __init__(self, prefix=None, suffix=None, full=None):
|
|
39
|
+
self.full = full
|
|
40
|
+
self.prefix = full[:12] if full else prefix
|
|
41
|
+
self.suffix = suffix
|
|
42
|
+
self.rx = re.compile(re.escape(self.prefix) + TOKEN_CHARS + "*")
|
|
43
|
+
self.seen = set()
|
|
44
|
+
|
|
45
|
+
def tokens(self, text):
|
|
46
|
+
"""Masked tokens in text that match the key."""
|
|
47
|
+
out = []
|
|
48
|
+
for m in self.rx.finditer(text):
|
|
49
|
+
tok = m.group(0).rstrip(".=") # trailing punctuation from prose or logs
|
|
50
|
+
if self.full and self.full not in m.group(0):
|
|
51
|
+
continue
|
|
52
|
+
if self.suffix and not tok.endswith(self.suffix):
|
|
53
|
+
continue
|
|
54
|
+
out.append(mask(self.full if self.full else tok))
|
|
55
|
+
self.seen.update(out)
|
|
56
|
+
return out
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def utc(epoch):
|
|
60
|
+
return dt.datetime.fromtimestamp(int(epoch), dt.timezone.utc).strftime("%Y-%m-%d %H:%M:%SZ")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def line_stamp(line):
|
|
64
|
+
m = STAMP.search(line)
|
|
65
|
+
if not m:
|
|
66
|
+
return "no timestamp on the line"
|
|
67
|
+
day, clock, tz = m.groups()
|
|
68
|
+
if not tz:
|
|
69
|
+
return "%s %s (no timezone on the line)" % (day, clock)
|
|
70
|
+
tz = "+00:00" if tz == "Z" else (tz if ":" in tz else tz[:3] + ":" + tz[3:])
|
|
71
|
+
try:
|
|
72
|
+
when = dt.datetime.fromisoformat("%sT%s%s" % (day, clock, tz))
|
|
73
|
+
except ValueError:
|
|
74
|
+
return "%s %s%s (unparsed)" % (day, clock, tz)
|
|
75
|
+
return when.astimezone(dt.timezone.utc).strftime("%Y-%m-%d %H:%M:%SZ")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def git(repo, *args):
|
|
79
|
+
r = subprocess.run(["git", "-C", repo] + list(args), capture_output=True)
|
|
80
|
+
return r.returncode, r.stdout.decode("utf-8", "replace"), r.stderr.decode("utf-8", "replace")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def scan_repo(repo, needle):
|
|
84
|
+
rc, _, err = git(repo, "rev-parse", "--git-dir")
|
|
85
|
+
if rc != 0:
|
|
86
|
+
return None, "not a git repository: %s (%s)" % (repo, err.strip())
|
|
87
|
+
rc, out, err = git(
|
|
88
|
+
repo, "log", "--all", "--reflog", "--no-color", "--no-ext-diff", "-p",
|
|
89
|
+
"-S" + needle.prefix, "--format=%x00COMMIT %H %at %ct",
|
|
90
|
+
)
|
|
91
|
+
if rc != 0:
|
|
92
|
+
return None, "git log failed: %s" % err.strip()
|
|
93
|
+
commits, cur, path = [], None, None
|
|
94
|
+
for line in out.splitlines():
|
|
95
|
+
if line.startswith("\x00COMMIT "):
|
|
96
|
+
sha, at, ct = line.split()[1:4]
|
|
97
|
+
cur = {"sha": sha, "authored": utc(at), "committed": utc(ct), "added": [], "removed": []}
|
|
98
|
+
commits.append(cur)
|
|
99
|
+
elif cur is None:
|
|
100
|
+
continue
|
|
101
|
+
elif line.startswith("diff --git "):
|
|
102
|
+
path = line.split(" b/", 1)[-1]
|
|
103
|
+
elif line.startswith("+") and not line.startswith("+++"):
|
|
104
|
+
if needle.tokens(line):
|
|
105
|
+
cur["added"].append(path)
|
|
106
|
+
elif line.startswith("-") and not line.startswith("---"):
|
|
107
|
+
if needle.tokens(line):
|
|
108
|
+
cur["removed"].append(path)
|
|
109
|
+
commits = [c for c in commits if c["added"] or c["removed"]]
|
|
110
|
+
for c in commits:
|
|
111
|
+
c["added"], c["removed"] = sorted(set(c["added"])), sorted(set(c["removed"]))
|
|
112
|
+
if c["added"]:
|
|
113
|
+
_, b, _ = git(repo, "branch", "-a", "--contains", c["sha"])
|
|
114
|
+
_, t, _ = git(repo, "tag", "--contains", c["sha"])
|
|
115
|
+
c["refs"] = [x.strip("* ").strip() for x in b.splitlines() if x.strip()]
|
|
116
|
+
c["refs"] += ["tag " + x.strip() for x in t.splitlines() if x.strip()]
|
|
117
|
+
present = {}
|
|
118
|
+
for label, args in (("HEAD", ["grep", "-n", "-I", "-F", "-e", needle.prefix, "HEAD", "--"]),
|
|
119
|
+
("working copy", ["grep", "-n", "-I", "-F", "--untracked", "-e", needle.prefix])):
|
|
120
|
+
_, g, _ = git(repo, *args)
|
|
121
|
+
hits = []
|
|
122
|
+
for gl in g.splitlines():
|
|
123
|
+
parts = gl.split(":", 3 if label == "HEAD" else 2)
|
|
124
|
+
loc = ":".join(parts[1:3]) if label == "HEAD" else ":".join(parts[0:2])
|
|
125
|
+
if needle.tokens(gl):
|
|
126
|
+
hits.append(loc)
|
|
127
|
+
present[label] = hits
|
|
128
|
+
return {"commits": commits, "present": present}, None
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def scan_log(path, needle):
|
|
132
|
+
hits = []
|
|
133
|
+
with open(path, "r", encoding="utf-8", errors="replace") as fh:
|
|
134
|
+
for n, line in enumerate(fh, 1):
|
|
135
|
+
toks = needle.tokens(line)
|
|
136
|
+
if toks:
|
|
137
|
+
hits.append((n, line_stamp(line), toks[0]))
|
|
138
|
+
return hits
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def report(args, needle, repo_res, repo_err, logs):
|
|
142
|
+
o = ["# Where the key appears", ""]
|
|
143
|
+
if len(needle.seen) > 1:
|
|
144
|
+
o += ["%d different keys match this prefix: %s. Pass --suffix with the leaked key's last "
|
|
145
|
+
"characters to keep only that one." % (len(needle.seen), ", ".join(sorted(needle.seen))), ""]
|
|
146
|
+
if args.repo:
|
|
147
|
+
o.append("## Git history: %s" % args.repo)
|
|
148
|
+
if repo_err:
|
|
149
|
+
o += ["", "Not scanned: " + repo_err]
|
|
150
|
+
else:
|
|
151
|
+
cs = repo_res["commits"]
|
|
152
|
+
adds = [c for c in cs if c["added"]]
|
|
153
|
+
if not cs:
|
|
154
|
+
o += ["", "No commit adds or removes it (searched every branch, tag, stash and the local reflog)."]
|
|
155
|
+
if adds:
|
|
156
|
+
first = min(adds, key=lambda c: c["authored"])
|
|
157
|
+
o += ["", "First commit that adds it: `%s`, authored %s, committed %s. Commit time is "
|
|
158
|
+
"when it was committed locally; when it reached a remote is in the host's audit or push log."
|
|
159
|
+
% (first["sha"][:12], first["authored"], first["committed"])]
|
|
160
|
+
for c in cs:
|
|
161
|
+
o.append("")
|
|
162
|
+
o.append("- `%s` authored %s, committed %s" % (c["sha"][:12], c["authored"], c["committed"]))
|
|
163
|
+
if c["added"]:
|
|
164
|
+
o.append(" - adds it in: %s" % ", ".join(c["added"]))
|
|
165
|
+
o.append(" - still reachable from: %s" % (", ".join(c["refs"]) if c["refs"]
|
|
166
|
+
else "no branch or tag; only the local reflog keeps it"))
|
|
167
|
+
if c["removed"]:
|
|
168
|
+
o.append(" - removes it from: %s (the earlier commits still hold it)" % ", ".join(c["removed"]))
|
|
169
|
+
for label, hits in repo_res["present"].items():
|
|
170
|
+
o.append("")
|
|
171
|
+
o.append("In %s now: %s" % (label, ", ".join(hits) if hits else "not present"))
|
|
172
|
+
o.append("")
|
|
173
|
+
for path, hits, err in logs:
|
|
174
|
+
o.append("## Log: %s" % path)
|
|
175
|
+
o.append("")
|
|
176
|
+
if err:
|
|
177
|
+
o.append("Not scanned: " + err)
|
|
178
|
+
elif not hits:
|
|
179
|
+
o.append("Not found.")
|
|
180
|
+
else:
|
|
181
|
+
for n, stamp, tok in hits:
|
|
182
|
+
o.append("- line %d, %s, %s" % (n, stamp, tok))
|
|
183
|
+
o.append("")
|
|
184
|
+
o += ["## Not searched", "",
|
|
185
|
+
"Forks, other clones, pull-request refs that were never fetched, CI caches and artifacts, "
|
|
186
|
+
"the hosting provider's own copies, chat and ticket systems. A clean result here does not "
|
|
187
|
+
"mean the key is safe: revoke it regardless.", ""]
|
|
188
|
+
return "\n".join(o)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def main(argv=None):
|
|
192
|
+
p = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
|
|
193
|
+
who = p.add_mutually_exclusive_group(required=True)
|
|
194
|
+
who.add_argument("--prefix", help="the first characters of the key (at least %d)" % MIN_PREFIX)
|
|
195
|
+
who.add_argument("--key-stdin", action="store_true", help="read the full key from stdin")
|
|
196
|
+
p.add_argument("--suffix", help="the last characters of the key, to tell it apart from keys with the same prefix")
|
|
197
|
+
p.add_argument("--repo", help="path to a git repository to search")
|
|
198
|
+
p.add_argument("--log", action="append", default=[], help="a log file to search (repeatable)")
|
|
199
|
+
p.add_argument("-o", "--out", help="write the report here instead of stdout")
|
|
200
|
+
a = p.parse_args(argv)
|
|
201
|
+
if a.key_stdin:
|
|
202
|
+
full = sys.stdin.read().strip()
|
|
203
|
+
if len(full) < MIN_PREFIX:
|
|
204
|
+
p.error("the key on stdin is shorter than %d characters" % MIN_PREFIX)
|
|
205
|
+
needle = Needle(full=full)
|
|
206
|
+
else:
|
|
207
|
+
if len(a.prefix) < MIN_PREFIX:
|
|
208
|
+
p.error("--prefix needs at least %d characters: a short prefix such as a provider's "
|
|
209
|
+
"key-type marker matches every key of that type" % MIN_PREFIX)
|
|
210
|
+
needle = Needle(prefix=a.prefix, suffix=a.suffix)
|
|
211
|
+
if not a.repo and not a.log:
|
|
212
|
+
p.error("give --repo, --log, or both")
|
|
213
|
+
repo_res, repo_err = (None, None)
|
|
214
|
+
if a.repo:
|
|
215
|
+
if not os.path.isdir(a.repo):
|
|
216
|
+
repo_err = "no such directory: %s" % a.repo
|
|
217
|
+
else:
|
|
218
|
+
repo_res, repo_err = scan_repo(a.repo, needle)
|
|
219
|
+
logs = []
|
|
220
|
+
for path in a.log:
|
|
221
|
+
if not os.path.isfile(path):
|
|
222
|
+
logs.append((path, [], "no such file"))
|
|
223
|
+
continue
|
|
224
|
+
try:
|
|
225
|
+
logs.append((path, scan_log(path, needle), None))
|
|
226
|
+
except OSError as e:
|
|
227
|
+
logs.append((path, [], str(e)))
|
|
228
|
+
text = report(a, needle, repo_res, repo_err, logs)
|
|
229
|
+
if a.out:
|
|
230
|
+
with open(a.out, "w", encoding="utf-8") as fh:
|
|
231
|
+
fh.write(text)
|
|
232
|
+
print("wrote %s" % a.out)
|
|
233
|
+
else:
|
|
234
|
+
print(text)
|
|
235
|
+
return 0
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
if __name__ == "__main__":
|
|
239
|
+
sys.exit(main())
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Split a provider usage or billing export (CSV) by who spent it.
|
|
3
|
+
|
|
4
|
+
Read-only. Sums cost exactly (decimal, not float) per group of columns, so a bill
|
|
5
|
+
is split from the export rather than estimated. Groups by the key, model and
|
|
6
|
+
user-agent columns it finds (override with --by). Each group shows its total,
|
|
7
|
+
its share of the whole, its row and request counts, and the time of its first and
|
|
8
|
+
last row as written in the export.
|
|
9
|
+
|
|
10
|
+
python3 usage_split.py usage.csv
|
|
11
|
+
python3 usage_split.py usage.csv --since 2026-10-19T20:00 --expect model=model-medium
|
|
12
|
+
python3 usage_split.py usage.csv --by api_key_name,user_agent -o split.txt
|
|
13
|
+
cat usage.csv | python3 usage_split.py -
|
|
14
|
+
|
|
15
|
+
--expect col=value (repeatable) marks every group whose value in that column
|
|
16
|
+
differs as UNEXPECTED: traffic the known job does not explain. --since/--until
|
|
17
|
+
compare the time column as text, so give them in the export's own format.
|
|
18
|
+
Writes only the -o path when given.
|
|
19
|
+
"""
|
|
20
|
+
import argparse
|
|
21
|
+
import csv
|
|
22
|
+
import io
|
|
23
|
+
import sys
|
|
24
|
+
from decimal import Decimal, InvalidOperation
|
|
25
|
+
|
|
26
|
+
COST_HINTS = ("cost_usd", "cost", "amount_usd", "amount", "spend", "usd", "price")
|
|
27
|
+
TIME_HINTS = ("date_hour_utc", "timestamp", "time", "hour", "date", "start_time", "period")
|
|
28
|
+
REQ_HINTS = ("requests", "request_count", "num_requests", "calls")
|
|
29
|
+
GROUP_HINTS = (
|
|
30
|
+
("api_key_name", "api_key", "key_name", "key_id", "key"),
|
|
31
|
+
("model", "model_name", "model_id"),
|
|
32
|
+
("user_agent", "useragent", "ua", "client"),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def find_col(headers, hints, contains=False):
|
|
37
|
+
low = {h.lower().strip(): h for h in headers}
|
|
38
|
+
for hint in hints:
|
|
39
|
+
if hint in low:
|
|
40
|
+
return low[hint]
|
|
41
|
+
if contains:
|
|
42
|
+
for hint in hints:
|
|
43
|
+
for key, orig in low.items():
|
|
44
|
+
if hint in key:
|
|
45
|
+
return orig
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def to_decimal(text):
|
|
50
|
+
cleaned = (text or "").strip().replace("$", "").replace(",", "")
|
|
51
|
+
if cleaned == "":
|
|
52
|
+
return Decimal("0")
|
|
53
|
+
return Decimal(cleaned)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def money(value):
|
|
57
|
+
return "${:,.2f}".format(value)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def main(argv=None):
|
|
61
|
+
ap = argparse.ArgumentParser(description=__doc__.split("\n")[0])
|
|
62
|
+
ap.add_argument("export", help="CSV usage export path, or - for stdin")
|
|
63
|
+
ap.add_argument("--by", help="comma-separated columns to group by (default: key, model, user agent)")
|
|
64
|
+
ap.add_argument("--cost", help="cost column name (default: auto-detect)")
|
|
65
|
+
ap.add_argument("--time", help="time column name (default: auto-detect)")
|
|
66
|
+
ap.add_argument("--since", help="keep rows whose time is >= this text")
|
|
67
|
+
ap.add_argument("--until", help="keep rows whose time is < this text")
|
|
68
|
+
ap.add_argument("--expect", action="append", default=[], metavar="COL=VALUE",
|
|
69
|
+
help="mark groups whose COL differs from VALUE as UNEXPECTED")
|
|
70
|
+
ap.add_argument("-o", "--output", help="write the report to this path instead of stdout")
|
|
71
|
+
args = ap.parse_args(argv)
|
|
72
|
+
|
|
73
|
+
if args.export == "-":
|
|
74
|
+
text = sys.stdin.read()
|
|
75
|
+
else:
|
|
76
|
+
try:
|
|
77
|
+
with open(args.export, encoding="utf-8", newline="") as fh:
|
|
78
|
+
text = fh.read()
|
|
79
|
+
except OSError as exc:
|
|
80
|
+
print("error: cannot read {}: {}".format(args.export, exc), file=sys.stderr)
|
|
81
|
+
return 2
|
|
82
|
+
text = text.lstrip("")
|
|
83
|
+
reader = csv.DictReader(io.StringIO(text))
|
|
84
|
+
headers = reader.fieldnames or []
|
|
85
|
+
if not headers:
|
|
86
|
+
print("error: the export has no header row", file=sys.stderr)
|
|
87
|
+
return 2
|
|
88
|
+
|
|
89
|
+
cost_col = args.cost or find_col(headers, COST_HINTS, contains=True)
|
|
90
|
+
time_col = args.time or find_col(headers, TIME_HINTS, contains=True)
|
|
91
|
+
req_col = find_col(headers, REQ_HINTS)
|
|
92
|
+
if not cost_col or cost_col not in headers:
|
|
93
|
+
print("error: no cost column found; columns are: {}; pass --cost".format(", ".join(headers)),
|
|
94
|
+
file=sys.stderr)
|
|
95
|
+
return 2
|
|
96
|
+
if args.by:
|
|
97
|
+
group_cols = [c.strip() for c in args.by.split(",") if c.strip()]
|
|
98
|
+
else:
|
|
99
|
+
group_cols = [c for c in (find_col(headers, h) for h in GROUP_HINTS) if c]
|
|
100
|
+
missing = [c for c in group_cols if c not in headers]
|
|
101
|
+
if missing or not group_cols:
|
|
102
|
+
print("error: group columns not in the export: {}; columns are: {}".format(
|
|
103
|
+
", ".join(missing) or "(none found)", ", ".join(headers)), file=sys.stderr)
|
|
104
|
+
return 2
|
|
105
|
+
if (args.since or args.until) and not time_col:
|
|
106
|
+
print("error: --since/--until need a time column; pass --time", file=sys.stderr)
|
|
107
|
+
return 2
|
|
108
|
+
|
|
109
|
+
expects = []
|
|
110
|
+
for item in args.expect:
|
|
111
|
+
if "=" not in item:
|
|
112
|
+
print("error: --expect takes COL=VALUE, got {!r}".format(item), file=sys.stderr)
|
|
113
|
+
return 2
|
|
114
|
+
col, val = item.split("=", 1)
|
|
115
|
+
if col not in group_cols:
|
|
116
|
+
print("error: --expect column {!r} must be one of the group columns: {}".format(
|
|
117
|
+
col, ", ".join(group_cols)), file=sys.stderr)
|
|
118
|
+
return 2
|
|
119
|
+
expects.append((col, val))
|
|
120
|
+
|
|
121
|
+
groups = {}
|
|
122
|
+
total = Decimal("0")
|
|
123
|
+
kept = 0
|
|
124
|
+
bad = []
|
|
125
|
+
for line_no, row in enumerate(reader, start=2):
|
|
126
|
+
when = (row.get(time_col) or "").strip() if time_col else ""
|
|
127
|
+
if args.since and when < args.since:
|
|
128
|
+
continue
|
|
129
|
+
if args.until and when >= args.until:
|
|
130
|
+
continue
|
|
131
|
+
try:
|
|
132
|
+
cost = to_decimal(row.get(cost_col))
|
|
133
|
+
except InvalidOperation:
|
|
134
|
+
bad.append(line_no)
|
|
135
|
+
continue
|
|
136
|
+
key = tuple((row.get(c) or "").strip() for c in group_cols)
|
|
137
|
+
g = groups.setdefault(key, {"cost": Decimal("0"), "rows": 0, "requests": 0,
|
|
138
|
+
"first": None, "last": None, "times": []})
|
|
139
|
+
g["cost"] += cost
|
|
140
|
+
g["rows"] += 1
|
|
141
|
+
if req_col:
|
|
142
|
+
try:
|
|
143
|
+
g["requests"] += int(float((row.get(req_col) or "0").replace(",", "")))
|
|
144
|
+
except ValueError:
|
|
145
|
+
pass
|
|
146
|
+
if when:
|
|
147
|
+
g["times"].append(when)
|
|
148
|
+
g["first"] = when if g["first"] is None or when < g["first"] else g["first"]
|
|
149
|
+
g["last"] = when if g["last"] is None or when > g["last"] else g["last"]
|
|
150
|
+
total += cost
|
|
151
|
+
kept += 1
|
|
152
|
+
|
|
153
|
+
out = []
|
|
154
|
+
window = ""
|
|
155
|
+
if args.since or args.until:
|
|
156
|
+
window = " ({} to {})".format(args.since or "start", args.until or "end")
|
|
157
|
+
out.append("Rows: {}{} Total: {}".format(kept, window, money(total)))
|
|
158
|
+
out.append("Grouped by: {} Cost column: {}{}".format(
|
|
159
|
+
", ".join(group_cols), cost_col, " Time column: " + time_col if time_col else ""))
|
|
160
|
+
out.append("")
|
|
161
|
+
for key, g in sorted(groups.items(), key=lambda kv: kv[1]["cost"], reverse=True):
|
|
162
|
+
share = (g["cost"] / total * 100) if total else Decimal("0")
|
|
163
|
+
label = " | ".join("{}={}".format(c, v) for c, v in zip(group_cols, key))
|
|
164
|
+
flag = ""
|
|
165
|
+
if expects:
|
|
166
|
+
off = [c for c, v in expects if key[group_cols.index(c)] != v]
|
|
167
|
+
if off:
|
|
168
|
+
flag = " UNEXPECTED ({} differs)".format(", ".join(off))
|
|
169
|
+
out.append("{} {} ({:.1f}%){}".format(money(g["cost"]), label, share, flag))
|
|
170
|
+
detail = " rows={}".format(g["rows"])
|
|
171
|
+
if req_col:
|
|
172
|
+
detail += " requests={:,}".format(g["requests"])
|
|
173
|
+
if g["first"]:
|
|
174
|
+
detail += " first={} last={}".format(g["first"], g["last"])
|
|
175
|
+
out.append(detail)
|
|
176
|
+
if g["times"] and len(g["times"]) <= 12:
|
|
177
|
+
out.append(" times: " + ", ".join(sorted(g["times"])))
|
|
178
|
+
if bad:
|
|
179
|
+
out.append("")
|
|
180
|
+
out.append("Skipped rows with an unreadable cost (CSV line numbers): " +
|
|
181
|
+
", ".join(str(n) for n in bad))
|
|
182
|
+
report = "\n".join(out) + "\n"
|
|
183
|
+
|
|
184
|
+
if args.output:
|
|
185
|
+
with open(args.output, "w", encoding="utf-8") as fh:
|
|
186
|
+
fh.write(report)
|
|
187
|
+
print("wrote {}".format(args.output))
|
|
188
|
+
else:
|
|
189
|
+
sys.stdout.write(report)
|
|
190
|
+
return 0
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
if __name__ == "__main__":
|
|
194
|
+
sys.exit(main())
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: judge-audit
|
|
3
|
+
description: Audit whether an LLM-as-judge, or a panel of judges, can be trusted for the decision its scores drive, before anyone acts on them — above all whether the judge errs differently on the variants being compared (prompt v1 vs v2, model A vs B, short vs long inputs), plus chance-corrected and weighted agreement with expert labels, precision and recall on a rare class, each run reported separately with the items that flipped, position and length bias, and confidence intervals, checked against the user's own gate or criterion and ending in a direct answer (ship or not, which judge to use). Use when the user asks "can we trust our LLM judge", "is our grader reliable enough to gate releases", "the grader says v2 is better, can we ship it", "which is the cheapest judge we can switch to", "our two judges agree 90%, is that good", "can we turn on auto-remove", "check our judge for position bias", or has a file of judge scores and wants to know whether to act on them.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# judge-audit
|
|
7
|
+
|
|
8
|
+
Decides whether a judge's scores are good enough for one named decision, from the judgements
|
|
9
|
+
themselves, and answers the question the user asked. A grader can match an expert well overall
|
|
10
|
+
and still over-score the very variant it is being used to pick. This skill audits judgements that
|
|
11
|
+
already exist; designing the eval set and grading plan is `llm-eval-harness`, generating eval
|
|
12
|
+
cases for a skill is `eval-harness-generator`, and measuring AI tool usage is `ai-usage-report`.
|
|
13
|
+
|
|
14
|
+
## How to respond
|
|
15
|
+
|
|
16
|
+
**Four rules come before the steps and outrank them:**
|
|
17
|
+
|
|
18
|
+
- **Answer what was asked, first.** Open with the answer, the decision or the artifact, in plain words. Scores, matrices, frameworks and tags come after it, and anything that doesn't change the answer is cut.
|
|
19
|
+
- **Use only what you were given.** Don't add facts, names, numbers, incidents, history, steps or sections the input doesn't contain. What you need and don't have is left out of the artifact and listed once at the end under "To confirm".
|
|
20
|
+
- **Deliver from what you have.** When the request gives you something to work on, state your assumptions in a line and produce the result. When it gives you nothing to work on, ask for it in one or two plain sentences and say what you will do once you have it.
|
|
21
|
+
- **Write for someone who has never heard of this skill.** No skill names, no paths or scripts from this folder, no internal terms, and nothing about how the run was set up. A next step is one plain sentence at the end that describes the work.
|
|
22
|
+
- **For this skill:** The pass mark is the user's, never yours. Judge the numbers against the gate or criterion the user or their files state; when there is none, say what the numbers do to their decision. A threshold the input doesn't contain ("kappa ≥ 0.6", "n ≥ 100") never decides the answer.
|
|
23
|
+
|
|
24
|
+
1. **Pin the decision, the user's criterion, and the comparison.** Find the decision (rank
|
|
25
|
+
variants, publish a number, gate a release, act on single items) and the criterion the user
|
|
26
|
+
or their files give: ✅ *"Gate: a build ships if its precision@5 is within 2.0 points of
|
|
27
|
+
production; the change compares short against long documents."* Read every file for it
|
|
28
|
+
(READMEs, run notes) before asking. No criterion stated: no pass mark, and the answer says
|
|
29
|
+
what the errors would do to the decision. Name the variable the decision compares: version,
|
|
30
|
+
model, arm, or a property the planned change shifts (length, language).
|
|
31
|
+
|
|
32
|
+
2. **Compute, don't estimate.** Run the script, grouped by the compared variable, with the
|
|
33
|
+
user's criterion as `--gate`:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Claude Code
|
|
37
|
+
python3 "${CLAUDE_SKILL_DIR}/scripts/judge_stats.py" grades.csv --group-by prompt_version
|
|
38
|
+
python3 "${CLAUDE_SKILL_DIR}/scripts/judge_stats.py" run_1.jsonl run_2.jsonl --gold gold.csv --map true=violation,false=ok --positive violation
|
|
39
|
+
# Other IDEs (from the skill folder)
|
|
40
|
+
python3 scripts/judge_stats.py wide.csv --judges big,small --positive relevant --group-by "tokens>=400" --gate "bias_diff<=2.0"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Several files are one run each, joined on the item id with columns resolved per file;
|
|
44
|
+
`--gold` reads expert labels from their own file; `--judges` reads one column per judge;
|
|
45
|
+
`--map` renames labels that mean the same; `--col` maps any other column. Given only counts,
|
|
46
|
+
rebuild one row per item and run it anyway; without a shell, use the formulas in
|
|
47
|
+
[`reference.md`](reference.md) and show the arithmetic.
|
|
48
|
+
|
|
49
|
+
3. **Quote every number from the output, with its subset, run and n.** All-rows and
|
|
50
|
+
labelled-rows figures are different numbers; so are run 1 and run 2. ✅ *"On the 20 items the
|
|
51
|
+
reviewer scored, the grader averages 3.7 against her 3.1."* — ❌ a run-1 false-positive count
|
|
52
|
+
presented as the result when run 2 has more. No figure appears that the output doesn't print.
|
|
53
|
+
|
|
54
|
+
4. **Use the scale and the metric the user uses.** For 1–5 scores report quadratic-weighted
|
|
55
|
+
kappa (say "quadratic"), exact agreement and mean bias; unweighted kappa treats 4-vs-5 like
|
|
56
|
+
1-vs-5. Pass `--threshold` only when the user stated a pass score. When the user quotes a
|
|
57
|
+
figure ("kappa 0.8", "they agree 80%"), reproduce it first, say which statistic it is on which
|
|
58
|
+
rows, then show what it hides.
|
|
59
|
+
|
|
60
|
+
5. **Lead with the differential.** Per variant compare exact agreement, bias judge − expert
|
|
61
|
+
with its interval, how often the judge scores higher and lower, and the bias-vs-length
|
|
62
|
+
correlation; then the judge's v2 − v1 gain against the expert's own on the labelled rows. ✅
|
|
63
|
+
*"It matches the reviewer on 18 of 20 A answers but 9 of 20 B answers and over-scores B by
|
|
64
|
+
+0.55 [+0.30, +0.80]; her own scores show B ahead by +0.10 (interval spans zero)."* When the
|
|
65
|
+
bias tracks a property the planned change shifts, the shift in bias is what lands on the gate.
|
|
66
|
+
|
|
67
|
+
6. **Chance-correct agreement, and on a rare class count the rare class.** Two raters who each
|
|
68
|
+
say "pass" 90% of the time agree 82% by chance: ✅ *"Raw 88%, chance 80%, kappa 0.40."* With
|
|
69
|
+
one class under about 15%, lead with how many expert positives exist, the confusion counts,
|
|
70
|
+
recall and precision with intervals; accuracy only beside the always-majority baseline. ✅
|
|
71
|
+
*"6 of 120 posts are violations, so always saying 'fine' scores 95%. It caught 4 of 6 (30%–90%)
|
|
72
|
+
and flagged 2 fine posts."* — ❌ *"96% accurate, so it's reliable."*
|
|
73
|
+
|
|
74
|
+
7. **With repeated runs, report each run, then the flips.** One row per run: precision, recall,
|
|
75
|
+
false positives. Then run-against-run agreement joined on the item id, the items that
|
|
76
|
+
flipped and which of them the expert labels positive, and what was caught in both runs. ✅
|
|
77
|
+
*"Run 1 wrongly flagged 1 post, run 2 flagged 2; 5 posts changed verdict, 2 of them real
|
|
78
|
+
violations."* Files in different row orders or formats are joined on id, never by line.
|
|
79
|
+
|
|
80
|
+
8. **For pairwise judges, read the order column before the win rate.** State the direction from
|
|
81
|
+
the counts: ✅ *"The first-shown answer won 70 of 100; the new variant went first on 15 of 25
|
|
82
|
+
questions."* Report the order-balanced win rate per judge; the unit is the question, not the
|
|
83
|
+
judgement. An interval excluding 50% does not fix an order confound; judging both orders does.
|
|
84
|
+
|
|
85
|
+
9. **Measure stability and judge-to-judge agreement across every trial.** Flip rate per judge,
|
|
86
|
+
with the flipped items named; trial-matched kappa between judges, and Fleiss' across every
|
|
87
|
+
judge × trial rating. Judges sharing a blind spot agree on it: expert agreement ranks first.
|
|
88
|
+
|
|
89
|
+
10. **Say what the data cannot measure, in one line.** One run: no flip rate. One expert: no
|
|
90
|
+
expert-expert baseline. No expert labels: nothing shows the judge is right. Never fill a gap
|
|
91
|
+
with a guess.
|
|
92
|
+
|
|
93
|
+
11. **Answer the question asked, against the user's criterion.** Each gate check reads
|
|
94
|
+
*clears* (whole interval meets it), *fails* (none of it does) or *can't tell* (the interval
|
|
95
|
+
spans it). "Which judge can we use" names one: ✅ *"Switch to the mid tier: it is the cheapest
|
|
96
|
+
judge whose error doesn't move with length (bias shift −2 points against a 2.0-point gate);
|
|
97
|
+
the small tier says relevant to 9 of 10 long passages the reviewer rejected."* — ❌ *"None
|
|
98
|
+
can be recommended until 100 more labels exist."* Conditions are actions with an n (label 30
|
|
99
|
+
more long passages); a small sample widens the interval, it doesn't withhold the answer.
|
|
100
|
+
When the judge in use today also reads *can't tell*, the sample can't certify anyone: compare
|
|
101
|
+
the candidate with the incumbent on the same items (the accuracy difference and its interval).
|
|
102
|
+
Kappa bands (Landis–Koch "moderate") appear only as labelled context, never as the reason.
|
|
103
|
+
Verdict words: **TRUSTWORTHY FOR THIS DECISION**, **USE WITH CONDITIONS**, **DO NOT RELY ON IT**.
|
|
104
|
+
|
|
105
|
+
12. **Write the reply short.** First line: the answer (yes/no, which judge, which arm it
|
|
106
|
+
favours and by how much). Then the two to four numbers behind it, each with its subset and
|
|
107
|
+
run; what can't be measured; one next step that fits the user's deadline. No kappa tutorial,
|
|
108
|
+
no tables unless asked. A chat question gets a chat answer.
|
|
109
|
+
|
|
110
|
+
**Non-interactive runs** (subagent, CI, headless): no criterion in the input becomes
|
|
111
|
+
`[assumption] no stated gate; the reply says what the errors do to the decision`; a compared
|
|
112
|
+
variable the input does not name becomes `[assumption] no grouping`. No judgements and no counts
|
|
113
|
+
emit `BLOCKED: need the per-item judgements (item, judge, label or score) or the agreement
|
|
114
|
+
counts`, because a verdict without them is invented.
|
|
115
|
+
|
|
116
|
+
## Useful references in this skill
|
|
117
|
+
|
|
118
|
+
- [`reference.md`](reference.md) — turning the user's gate into the number that answers it, kappa (unweighted, weighted, Fleiss') and Wilson formulas with worked tables, the both-orders protocol, and every input shape the script reads
|
|
119
|
+
- [`scripts/judge_stats.py`](scripts/judge_stats.py) — per-group, per-run and pooled agreement, weighted kappa, rare-class precision and recall, flips joined on item id, position and length bias, `--gate` (stdlib only; `--self-test` checks the maths)
|
|
120
|
+
- [`examples/summary-panel.md`](examples/summary-panel.md) — worked example: a three-judge panel on summary faithfulness, a USE WITH CONDITIONS verdict
|
|
121
|
+
|
|
122
|
+
## Quality bar
|
|
123
|
+
|
|
124
|
+
- **The first line answers the question asked:** ship or not, which judge, which arm it favours.
|
|
125
|
+
- **The criterion is the user's;** with none, no invented pass mark decides anything.
|
|
126
|
+
- **Statistics are stratified by the compared variable** before any pooled figure is quoted.
|
|
127
|
+
- **Ordinal scores get weighted kappa, named as quadratic or linear.**
|
|
128
|
+
- **A rare class gets its count, confusion, recall and precision with intervals,** not accuracy.
|
|
129
|
+
- **Each run is reported on its own, with the flips,** joined on item id.
|
|
130
|
+
- **Every number matches the output for the subset and run it is labelled with,** n included.
|
|
131
|
+
- **What the data cannot measure is named,** never estimated.
|
|
132
|
+
|
|
133
|
+
## When to use this skill
|
|
134
|
+
|
|
135
|
+
- ✅ "The grader says v2 beats v1 — can we ship on it?" with a file of scores
|
|
136
|
+
- ✅ "Which is the cheapest judge we can switch to?" with several judges on the same items
|
|
137
|
+
- ✅ "It's 96% accurate on two runs — can we turn on auto-remove?"
|
|
138
|
+
- ✅ A pairwise A/B judged by a model, before shipping the winner
|
|
139
|
+
- ✅ "Our two graders agree 90%, good enough?"
|
|
140
|
+
|
|
141
|
+
## When NOT to use this skill
|
|
142
|
+
|
|
143
|
+
- ❌ Designing the eval set, grading methods and pass bar for an LLM feature — `llm-eval-harness`
|
|
144
|
+
- ❌ Generating eval cases for a skill's `evals/` folder — `eval-harness-generator`
|
|
145
|
+
- ❌ Measuring how much teams use AI tools — `ai-usage-report`
|
|
146
|
+
- ❌ Inter-rater reliability for humans only, with no model in the loop — the statistics transfer, but the bias checks here assume a model
|
|
147
|
+
|
|
148
|
+
## Anti-patterns to avoid
|
|
149
|
+
|
|
150
|
+
- ❌ **An invented gate.** "Kappa must be ≥ 0.6 with n ≥ 100" when the user's gate is a 1.0-point tolerance.
|
|
151
|
+
- ❌ **Refusing to pick.** "Which is cheapest?" answered with "none yet" instead of a name and a condition.
|
|
152
|
+
- ❌ **Run 1 as the result.** Two runs are two rows, then the flips.
|
|
153
|
+
- ❌ **Joining runs by line number.** Rows in a different order make a line-by-line kappa meaningless.
|
|
154
|
+
- ❌ **Accuracy on a rare class.** 96% when 95% is the always-"fine" score.
|
|
155
|
+
- ❌ **A pooled kappa for a comparison,** or unweighted kappa on a 1–5 scale.
|
|
156
|
+
- ❌ **Position bias stated backwards,** or judgements counted as the unit instead of questions.
|
|
157
|
+
- ❌ **Leading with the stats dump.** The reader asked whether to act; tell them, then show why.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
[
|
|
2
|
+
{ "query": "Our LLM grader and a second model agree 88% on pass/fail. Is that enough to use it as a release gate?", "should_trigger": true },
|
|
3
|
+
{ "query": "Here's a CSV of judge scores with three trials per item and some human labels. Can we trust the judge?", "should_trigger": true },
|
|
4
|
+
{ "query": "Check our pairwise LLM judge for position bias before we pick the winning prompt", "should_trigger": true },
|
|
5
|
+
{ "query": "Compute Cohen's kappa between our model judge and the annotators and tell me whether the judge is usable", "should_trigger": true },
|
|
6
|
+
{ "query": "We want to swap our expensive judge model for a cheaper one. Here are both models' grades on the same 100 answers plus expert labels.", "should_trigger": true },
|
|
7
|
+
{ "query": "We want an LLM judge to grade helpfulness on our chatbot. How should we set that up?", "should_trigger": false },
|
|
8
|
+
{ "query": "Design an eval set and pass bar for our RAG assistant before we change the prompt", "should_trigger": false },
|
|
9
|
+
{ "query": "Generate eval cases for this skilldrop SKILL.md so I can drop them in its evals folder", "should_trigger": false },
|
|
10
|
+
{ "query": "Summarise how much each team used AI coding assistants last quarter from this telemetry export", "should_trigger": false }
|
|
11
|
+
]
|