opencode-skills-collection 3.0.51 → 3.1.1
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 +44 -12
- package/bundled-skills/.antigravity-install-manifest.json +84 -1
- package/bundled-skills/android-ui-journey-testing/SKILL.md +191 -0
- package/bundled-skills/ask-matt/SKILL.md +92 -0
- package/bundled-skills/bugs-are-annoying/SKILL.md +137 -0
- package/bundled-skills/codebase-design/DEEPENING.md +37 -0
- package/bundled-skills/codebase-design/DESIGN-IT-TWICE.md +44 -0
- package/bundled-skills/codebase-design/SKILL.md +145 -0
- package/bundled-skills/competitor-analysis/LICENSE.txt +21 -0
- package/bundled-skills/competitor-analysis/SKILL.md +434 -0
- package/bundled-skills/competitor-analysis/references/battle-card-subagent.md +127 -0
- package/bundled-skills/competitor-analysis/references/battle-card.md +91 -0
- package/bundled-skills/competitor-analysis/references/example-research.md +130 -0
- package/bundled-skills/competitor-analysis/references/report-template.html +127 -0
- package/bundled-skills/competitor-analysis/references/research-patterns.md +217 -0
- package/bundled-skills/competitor-analysis/references/workflow.md +434 -0
- package/bundled-skills/competitor-analysis/scripts/capture_screenshots.mjs +142 -0
- package/bundled-skills/competitor-analysis/scripts/compile_report.mjs +929 -0
- package/bundled-skills/competitor-analysis/scripts/extract_vs_names.mjs +140 -0
- package/bundled-skills/competitor-analysis/scripts/gate_candidates.mjs +194 -0
- package/bundled-skills/competitor-analysis/scripts/list_urls.mjs +90 -0
- package/bundled-skills/competitor-analysis/scripts/md_utils.mjs +50 -0
- package/bundled-skills/competitor-analysis/scripts/merge_partials.mjs +291 -0
- package/bundled-skills/competitor-analysis/scripts/package.json +6 -0
- package/bundled-skills/design-it/3d-ui/SKILL.md +259 -0
- package/bundled-skills/design-it/SKILL.md +170 -0
- package/bundled-skills/design-it/ai-native-ui/SKILL.md +295 -0
- package/bundled-skills/design-it/aurora-ui/SKILL.md +307 -0
- package/bundled-skills/design-it/bento-ui/SKILL.md +314 -0
- package/bundled-skills/design-it/brutalism/SKILL.md +270 -0
- package/bundled-skills/design-it/brutalist-typography/SKILL.md +287 -0
- package/bundled-skills/design-it/card-based-design/SKILL.md +262 -0
- package/bundled-skills/design-it/claymorphism/SKILL.md +287 -0
- package/bundled-skills/design-it/color-blocking/SKILL.md +278 -0
- package/bundled-skills/design-it/command-center-ui/SKILL.md +345 -0
- package/bundled-skills/design-it/cyber-y2k/SKILL.md +312 -0
- package/bundled-skills/design-it/cyberpunk-ui/SKILL.md +262 -0
- package/bundled-skills/design-it/dark-mode/SKILL.md +289 -0
- package/bundled-skills/design-it/dashboard-design/SKILL.md +331 -0
- package/bundled-skills/design-it/data-dense-design/SKILL.md +322 -0
- package/bundled-skills/design-it/duotone-design/SKILL.md +248 -0
- package/bundled-skills/design-it/editorial-design/SKILL.md +328 -0
- package/bundled-skills/design-it/flat-design/SKILL.md +221 -0
- package/bundled-skills/design-it/flat-design-2/SKILL.md +240 -0
- package/bundled-skills/design-it/floating-ui/SKILL.md +299 -0
- package/bundled-skills/design-it/frutiger-aero/SKILL.md +274 -0
- package/bundled-skills/design-it/glassmorphism/SKILL.md +272 -0
- package/bundled-skills/design-it/gradient-design/SKILL.md +309 -0
- package/bundled-skills/design-it/high-contrast/SKILL.md +288 -0
- package/bundled-skills/design-it/holographic-ui/SKILL.md +310 -0
- package/bundled-skills/design-it/isometric-design/SKILL.md +228 -0
- package/bundled-skills/design-it/layered-design/SKILL.md +247 -0
- package/bundled-skills/design-it/material-design/SKILL.md +275 -0
- package/bundled-skills/design-it/maximalism/SKILL.md +297 -0
- package/bundled-skills/design-it/minimalism/SKILL.md +267 -0
- package/bundled-skills/design-it/monochromatic-ui/SKILL.md +296 -0
- package/bundled-skills/design-it/neo-brutalism/SKILL.md +270 -0
- package/bundled-skills/design-it/neumorphism/SKILL.md +248 -0
- package/bundled-skills/design-it/retro-design/SKILL.md +283 -0
- package/bundled-skills/design-it/retro-futurism/SKILL.md +259 -0
- package/bundled-skills/design-it/sci-fi-interface/SKILL.md +309 -0
- package/bundled-skills/design-it/skeuomorphism/SKILL.md +280 -0
- package/bundled-skills/design-it/soft-pastel/SKILL.md +307 -0
- package/bundled-skills/design-it/spatial-computing-ui/SKILL.md +300 -0
- package/bundled-skills/design-it/spatial-design/SKILL.md +268 -0
- package/bundled-skills/design-it/swiss-design/SKILL.md +293 -0
- package/bundled-skills/design-it/synthwave/SKILL.md +257 -0
- package/bundled-skills/design-it/tile-design/SKILL.md +297 -0
- package/bundled-skills/design-it/typography-first/SKILL.md +247 -0
- package/bundled-skills/design-it/vaporwave/SKILL.md +331 -0
- package/bundled-skills/design-it/vibrant-maximalism/SKILL.md +291 -0
- package/bundled-skills/design-it/widget-based-design/SKILL.md +274 -0
- package/bundled-skills/design-it/y2k-design/SKILL.md +268 -0
- package/bundled-skills/diagnosing-bugs/SKILL.md +165 -0
- package/bundled-skills/diagnosing-bugs/scripts/hitl-loop.template.sh +41 -0
- package/bundled-skills/docs/contributors/skill-scoring.md +235 -0
- package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
- package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
- package/bundled-skills/docs/maintainers/repo-growth-seo.md +3 -3
- package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
- package/bundled-skills/docs/users/bundles.md +1 -1
- package/bundled-skills/docs/users/claude-code-skills.md +1 -1
- package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
- package/bundled-skills/docs/users/getting-started.md +1 -1
- package/bundled-skills/docs/users/kiro-integration.md +1 -1
- package/bundled-skills/docs/users/usage.md +4 -4
- package/bundled-skills/docs/users/visual-guide.md +4 -4
- package/bundled-skills/domain-modeling/ADR-FORMAT.md +47 -0
- package/bundled-skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
- package/bundled-skills/domain-modeling/SKILL.md +105 -0
- package/bundled-skills/grill-me/SKILL.md +36 -0
- package/bundled-skills/grill-with-docs/SKILL.md +36 -0
- package/bundled-skills/grilling/SKILL.md +39 -0
- package/bundled-skills/handoff/SKILL.md +45 -0
- package/bundled-skills/image-generator/.env.example +7 -0
- package/bundled-skills/image-generator/SKILL.md +509 -0
- package/bundled-skills/improve-codebase-architecture/HTML-REPORT.md +123 -0
- package/bundled-skills/improve-codebase-architecture/SKILL.md +97 -0
- package/bundled-skills/learn/SKILL.md +156 -0
- package/bundled-skills/lesson-generator/SKILL.md +90 -0
- package/bundled-skills/llm-council/.env.example +7 -0
- package/bundled-skills/llm-council/SKILL.md +602 -0
- package/bundled-skills/loop-library/SKILL.md +205 -0
- package/bundled-skills/loop-library/agents/openai.yaml +4 -0
- package/bundled-skills/loop-library/references/catalog.md +270 -0
- package/bundled-skills/mailtrap-managing-contacts/SKILL.md +112 -0
- package/bundled-skills/mailtrap-sending-emails/SKILL.md +167 -0
- package/bundled-skills/mailtrap-setting-up-sending-domain/SKILL.md +77 -0
- package/bundled-skills/mailtrap-testing-with-sandbox/SKILL.md +110 -0
- package/bundled-skills/prototype/LOGIC.md +79 -0
- package/bundled-skills/prototype/SKILL.md +62 -0
- package/bundled-skills/prototype/UI.md +112 -0
- package/bundled-skills/setup-matt-pocock-skills/SKILL.md +158 -0
- package/bundled-skills/setup-matt-pocock-skills/domain.md +51 -0
- package/bundled-skills/setup-matt-pocock-skills/issue-tracker-github.md +34 -0
- package/bundled-skills/setup-matt-pocock-skills/issue-tracker-gitlab.md +35 -0
- package/bundled-skills/setup-matt-pocock-skills/issue-tracker-local.md +19 -0
- package/bundled-skills/setup-matt-pocock-skills/triage-labels.md +15 -0
- package/bundled-skills/survey-generator/LICENSE +21 -0
- package/bundled-skills/survey-generator/SKILL.md +143 -0
- package/bundled-skills/survey-generator/build_artifact.py +208 -0
- package/bundled-skills/survey-generator/examples/agentic-engineering/research_bundle.json +1196 -0
- package/bundled-skills/survey-generator/examples/agentic-engineering/survey.html +706 -0
- package/bundled-skills/survey-generator/style_spec.json +85 -0
- package/bundled-skills/survey-generator/templates/research_bundle_template.json +69 -0
- package/bundled-skills/tdd/SKILL.md +139 -0
- package/bundled-skills/tdd/mocking.md +59 -0
- package/bundled-skills/tdd/refactoring.md +10 -0
- package/bundled-skills/tdd/tests.md +61 -0
- package/bundled-skills/teach/GLOSSARY-FORMAT.md +35 -0
- package/bundled-skills/teach/LEARNING-RECORD-FORMAT.md +46 -0
- package/bundled-skills/teach/MISSION-FORMAT.md +31 -0
- package/bundled-skills/teach/RESOURCES-FORMAT.md +32 -0
- package/bundled-skills/teach/SKILL.md +169 -0
- package/bundled-skills/to-issues/SKILL.md +115 -0
- package/bundled-skills/to-prd/SKILL.md +104 -0
- package/bundled-skills/tools-page-seo-optimizer/SKILL.md +616 -0
- package/bundled-skills/triage/AGENT-BRIEF.md +207 -0
- package/bundled-skills/triage/OUT-OF-SCOPE.md +105 -0
- package/bundled-skills/triage/SKILL.md +143 -0
- package/bundled-skills/vibecode-production-qa-validator/SKILL.md +371 -141
- package/bundled-skills/wiki-builder/SKILL.md +157 -0
- package/bundled-skills/wiki-builder/agents/openai.yaml +5 -0
- package/bundled-skills/wiki-builder/references/wiki-flavors.md +98 -0
- package/bundled-skills/wiki-builder/scripts/init_wiki.sh +105 -0
- package/bundled-skills/wiki-builder/templates/index.md +20 -0
- package/bundled-skills/wiki-builder/templates/maintenance-log.md +7 -0
- package/bundled-skills/wiki-builder/templates/prompts/compile-concept-page.md +12 -0
- package/bundled-skills/wiki-builder/templates/prompts/compile-index.md +11 -0
- package/bundled-skills/wiki-builder/templates/prompts/compile-source-page.md +12 -0
- package/bundled-skills/wiki-builder/templates/prompts/lint-wiki.md +10 -0
- package/bundled-skills/wiki-builder/templates/prompts/query-and-file.md +11 -0
- package/bundled-skills/wiki-builder/templates/sources.md +9 -0
- package/bundled-skills/wiki-builder/templates/wiki.config.md +53 -0
- package/bundled-skills/writing-great-skills/GLOSSARY.md +181 -0
- package/bundled-skills/writing-great-skills/SKILL.md +111 -0
- package/bundled-skills/yao-meta-skill/SKILL.md +86 -0
- package/bundled-skills/yao-meta-skill/agents/interface.yaml +26 -0
- package/bundled-skills/yao-meta-skill/manifest.json +24 -0
- package/bundled-skills/yao-meta-skill/references/artifact-design-doctrine.md +49 -0
- package/bundled-skills/yao-meta-skill/references/authoring-discipline.md +78 -0
- package/bundled-skills/yao-meta-skill/references/autonomous-adaptation.md +65 -0
- package/bundled-skills/yao-meta-skill/references/distribution-registry-method.md +60 -0
- package/bundled-skills/yao-meta-skill/references/eval-playbook.md +69 -0
- package/bundled-skills/yao-meta-skill/references/gate-selection.md +68 -0
- package/bundled-skills/yao-meta-skill/references/governance.md +134 -0
- package/bundled-skills/yao-meta-skill/references/human-review-template.md +54 -0
- package/bundled-skills/yao-meta-skill/references/intent-dialogue.md +138 -0
- package/bundled-skills/yao-meta-skill/references/iteration-philosophy.md +30 -0
- package/bundled-skills/yao-meta-skill/references/non-skill-decision-tree.md +39 -0
- package/bundled-skills/yao-meta-skill/references/operating-modes.md +107 -0
- package/bundled-skills/yao-meta-skill/references/output-eval-method.md +113 -0
- package/bundled-skills/yao-meta-skill/references/output-quality-risk.md +41 -0
- package/bundled-skills/yao-meta-skill/references/output-visual-quality.md +53 -0
- package/bundled-skills/yao-meta-skill/references/packaging-contracts.md +70 -0
- package/bundled-skills/yao-meta-skill/references/pattern-extraction-doctrine.md +76 -0
- package/bundled-skills/yao-meta-skill/references/platform-capability-matrix.md +49 -0
- package/bundled-skills/yao-meta-skill/references/prompt-engineering-doctrine.md +76 -0
- package/bundled-skills/yao-meta-skill/references/qa-ladder.md +57 -0
- package/bundled-skills/yao-meta-skill/references/reference-scan.md +126 -0
- package/bundled-skills/yao-meta-skill/references/regression-cause-taxonomy.md +80 -0
- package/bundled-skills/yao-meta-skill/references/resource-boundaries.md +120 -0
- package/bundled-skills/yao-meta-skill/references/review-studio-method.md +87 -0
- package/bundled-skills/yao-meta-skill/references/review-waiver-method.md +76 -0
- package/bundled-skills/yao-meta-skill/references/runtime-conformance-method.md +21 -0
- package/bundled-skills/yao-meta-skill/references/skill-archetypes.md +86 -0
- package/bundled-skills/yao-meta-skill/references/skill-atlas-method.md +35 -0
- package/bundled-skills/yao-meta-skill/references/skill-engineering-method.md +210 -0
- package/bundled-skills/yao-meta-skill/references/skill-ir-method.md +41 -0
- package/bundled-skills/yao-meta-skill/references/skillops-decision-policy.md +53 -0
- package/bundled-skills/yao-meta-skill/references/systems-thinking-doctrine.md +75 -0
- package/bundled-skills/yao-meta-skill/references/telemetry-drift-method.md +182 -0
- package/bundled-skills/yao-meta-skill/references/trust-security-method.md +79 -0
- package/bundled-skills/yao-meta-skill/references/user-memory-policy.md +35 -0
- package/bundled-skills/youtube-notetaker/SKILL.md +209 -0
- package/bundled-skills/youtube-notetaker/reference/artifact.html +269 -0
- package/bundled-skills/youtube-notetaker/scripts/contact_sheet.py +53 -0
- package/bundled-skills/youtube-notetaker/scripts/detect_slides.sh +19 -0
- package/bundled-skills/youtube-notetaker/scripts/download.sh +24 -0
- package/bundled-skills/youtube-notetaker/scripts/extract_slides.py +43 -0
- package/bundled-skills/youtube-notetaker/scripts/serve.py +173 -0
- package/bundled-skills/youtube-notetaker/scripts/setup.sh +27 -0
- package/bundled-skills/youtube-notetaker/scripts/verify.sh +31 -0
- package/bundled-skills/youtube-notetaker/scripts/vtt_to_transcript.py +59 -0
- package/bundled-skills/youtube-notetaker/scripts/write_library_item.py +69 -0
- package/dist/skill-pointer/config-loader.d.ts +14 -0
- package/dist/skill-pointer/config-loader.js +30 -3
- package/dist/skill-pointer/content-scanner.d.ts +38 -0
- package/dist/skill-pointer/content-scanner.js +118 -0
- package/dist/skill-pointer/index.d.ts +7 -2
- package/dist/skill-pointer/index.js +14 -4
- package/dist/skill-pointer/pointer-generator.js +2 -0
- package/dist/skill-pointer/skill-patcher.d.ts +13 -0
- package/dist/skill-pointer/skill-patcher.js +99 -0
- package/package.json +1 -1
- package/skills_index.json +1956 -286
- package/bundled-skills/ai-md/SKILL.md +0 -523
- package/bundled-skills/atlas-contract/SKILL.md +0 -650
- package/bundled-skills/busybox-on-windows/SKILL.md +0 -40
- package/bundled-skills/monte-carlo-prevent/SKILL.md +0 -257
- package/bundled-skills/monte-carlo-prevent/references/TROUBLESHOOTING.md +0 -23
- package/bundled-skills/monte-carlo-prevent/references/parameters.md +0 -32
- package/bundled-skills/monte-carlo-prevent/references/workflows.md +0 -478
- package/bundled-skills/monte-carlo-push-ingestion/SKILL.md +0 -372
- package/bundled-skills/monte-carlo-push-ingestion/references/anomaly-detection.md +0 -87
- package/bundled-skills/monte-carlo-push-ingestion/references/custom-lineage.md +0 -203
- package/bundled-skills/monte-carlo-push-ingestion/references/direct-http-api.md +0 -207
- package/bundled-skills/monte-carlo-push-ingestion/references/prerequisites.md +0 -150
- package/bundled-skills/monte-carlo-push-ingestion/references/push-lineage.md +0 -160
- package/bundled-skills/monte-carlo-push-ingestion/references/push-metadata.md +0 -158
- package/bundled-skills/monte-carlo-push-ingestion/references/push-query-logs.md +0 -219
- package/bundled-skills/monte-carlo-push-ingestion/references/validation.md +0 -257
- package/bundled-skills/monte-carlo-push-ingestion/scripts/sample_verify.py +0 -357
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery/collect_and_push_lineage.py +0 -70
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery/collect_and_push_metadata.py +0 -65
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery/collect_and_push_query_logs.py +0 -70
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery/collect_lineage.py +0 -214
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery/collect_metadata.py +0 -160
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery/collect_query_logs.py +0 -164
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery/push_lineage.py +0 -198
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery/push_metadata.py +0 -193
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery/push_query_logs.py +0 -207
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery-iceberg/collect_and_push_metadata.py +0 -71
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery-iceberg/collect_and_push_query_logs.py +0 -64
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery-iceberg/collect_metadata.py +0 -253
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery-iceberg/collect_query_logs.py +0 -149
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery-iceberg/push_metadata.py +0 -190
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery-iceberg/push_query_logs.py +0 -208
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/databricks/collect_and_push_lineage.py +0 -83
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/databricks/collect_and_push_metadata.py +0 -77
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/databricks/collect_and_push_query_logs.py +0 -83
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/databricks/collect_lineage.py +0 -240
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/databricks/collect_metadata.py +0 -212
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/databricks/collect_query_logs.py +0 -204
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/databricks/push_lineage.py +0 -192
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/databricks/push_metadata.py +0 -178
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/databricks/push_query_logs.py +0 -200
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/hive/collect_and_push_lineage.py +0 -119
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/hive/collect_and_push_metadata.py +0 -119
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/hive/collect_and_push_query_logs.py +0 -117
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/hive/collect_lineage.py +0 -265
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/hive/collect_metadata.py +0 -313
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/hive/collect_query_logs.py +0 -284
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/hive/push_lineage.py +0 -309
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/hive/push_metadata.py +0 -245
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/hive/push_query_logs.py +0 -255
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/redshift/collect_and_push_lineage.py +0 -78
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/redshift/collect_and_push_metadata.py +0 -80
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/redshift/collect_and_push_query_logs.py +0 -88
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/redshift/collect_lineage.py +0 -235
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/redshift/collect_metadata.py +0 -219
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/redshift/collect_query_logs.py +0 -239
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/redshift/push_lineage.py +0 -178
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/redshift/push_metadata.py +0 -178
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/redshift/push_query_logs.py +0 -196
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/snowflake/collect_and_push_lineage.py +0 -154
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/snowflake/collect_and_push_metadata.py +0 -137
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/snowflake/collect_and_push_query_logs.py +0 -137
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/snowflake/collect_lineage.py +0 -349
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/snowflake/collect_metadata.py +0 -329
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/snowflake/collect_query_logs.py +0 -254
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/snowflake/push_lineage.py +0 -307
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/snowflake/push_metadata.py +0 -228
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/snowflake/push_query_logs.py +0 -248
- package/bundled-skills/monte-carlo-push-ingestion/scripts/test_template_sdk_usage.py +0 -340
- package/bundled-skills/skill-optimizer/SKILL.md +0 -271
- package/bundled-skills/using-superpowers/SKILL.md +0 -98
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Standalone viewer + API server for a YouTube deep-dive library.
|
|
3
|
+
|
|
4
|
+
Zero framework dependencies (Python stdlib + PyYAML). It serves the interactive
|
|
5
|
+
artifact and a small read/write API over a plain folder of markdown files, so the
|
|
6
|
+
whole thing runs anywhere with no custom backend.
|
|
7
|
+
|
|
8
|
+
python3 serve.py [--dir LIBRARY] [--port 8000] [--artifact path/to/artifact.html]
|
|
9
|
+
|
|
10
|
+
LIBRARY defaults to $VIDEO_LIBRARY_DIR or ~/video-deepdives. Layout:
|
|
11
|
+
LIBRARY/<YTID>.md one markdown file per video (frontmatter + transcript)
|
|
12
|
+
LIBRARY/_media/<YTID>-slide-NN.jpg slide images
|
|
13
|
+
|
|
14
|
+
Routes (the artifact talks to these; the /api/video-deepdives namespace is
|
|
15
|
+
arbitrary and kept only so the same artifact HTML works unmodified):
|
|
16
|
+
GET / the artifact (single-page app)
|
|
17
|
+
GET /api/video-deepdives list every video (flattened frontmatter)
|
|
18
|
+
GET /api/video-deepdives/<id> one video: {meta, body}
|
|
19
|
+
GET /api/video-deepdives/_media/<f> a slide image
|
|
20
|
+
PATCH /api/video-deepdives/<id> merge {fields:{...}} into frontmatter, rewrite
|
|
21
|
+
"""
|
|
22
|
+
import argparse, json, os, sys, re, mimetypes, posixpath
|
|
23
|
+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
import yaml
|
|
27
|
+
except ImportError:
|
|
28
|
+
sys.exit("pip install pyyaml")
|
|
29
|
+
|
|
30
|
+
API = "/api/video-deepdives"
|
|
31
|
+
FM_RE = re.compile(r"^---\n(.*?)\n---\n?(.*)$", re.DOTALL)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def split_frontmatter(text):
|
|
35
|
+
"""Return (meta_dict, body_str) from a markdown file with YAML frontmatter."""
|
|
36
|
+
m = FM_RE.match(text)
|
|
37
|
+
if not m:
|
|
38
|
+
return {}, text
|
|
39
|
+
meta = yaml.safe_load(m.group(1)) or {}
|
|
40
|
+
return meta, m.group(2)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def dump_file(meta, body):
|
|
44
|
+
out = "---\n" + yaml.safe_dump(meta, sort_keys=False, allow_unicode=True, width=100) + "---\n"
|
|
45
|
+
return out + body
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def load_item(lib, slug):
|
|
49
|
+
path = os.path.join(lib, slug + ".md")
|
|
50
|
+
if not os.path.isfile(path):
|
|
51
|
+
return None
|
|
52
|
+
meta, body = split_frontmatter(open(path, encoding="utf-8").read())
|
|
53
|
+
return path, meta, body
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def list_items(lib):
|
|
57
|
+
items = []
|
|
58
|
+
for fn in sorted(os.listdir(lib)):
|
|
59
|
+
if not fn.endswith(".md") or fn.startswith("_"):
|
|
60
|
+
continue
|
|
61
|
+
slug = fn[:-3]
|
|
62
|
+
loaded = load_item(lib, slug)
|
|
63
|
+
if not loaded:
|
|
64
|
+
continue
|
|
65
|
+
_, meta, body = loaded
|
|
66
|
+
it = dict(meta)
|
|
67
|
+
it["slug"] = slug
|
|
68
|
+
it["file"] = fn
|
|
69
|
+
it["preview"] = body.strip()[:160]
|
|
70
|
+
items.append(it)
|
|
71
|
+
return items
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class Handler(BaseHTTPRequestHandler):
|
|
75
|
+
lib = None
|
|
76
|
+
artifact = None
|
|
77
|
+
|
|
78
|
+
def log_message(self, *a):
|
|
79
|
+
pass # quiet
|
|
80
|
+
|
|
81
|
+
def _send(self, code, body, ctype="application/json"):
|
|
82
|
+
if isinstance(body, (dict, list)):
|
|
83
|
+
body = json.dumps(body).encode()
|
|
84
|
+
elif isinstance(body, str):
|
|
85
|
+
body = body.encode()
|
|
86
|
+
self.send_response(code)
|
|
87
|
+
self.send_header("Content-Type", ctype)
|
|
88
|
+
self.send_header("Content-Length", str(len(body)))
|
|
89
|
+
self.send_header("Access-Control-Allow-Origin", "*")
|
|
90
|
+
self.send_header("Access-Control-Allow-Methods", "GET, PATCH, OPTIONS")
|
|
91
|
+
self.send_header("Access-Control-Allow-Headers", "Content-Type")
|
|
92
|
+
self.end_headers()
|
|
93
|
+
if self.command != "HEAD":
|
|
94
|
+
self.wfile.write(body)
|
|
95
|
+
|
|
96
|
+
def do_OPTIONS(self):
|
|
97
|
+
self._send(204, b"")
|
|
98
|
+
|
|
99
|
+
def do_GET(self):
|
|
100
|
+
path = self.path.split("?", 1)[0].rstrip("/") or "/"
|
|
101
|
+
if path in ("/", "/index.html"):
|
|
102
|
+
try:
|
|
103
|
+
return self._send(200, open(self.artifact, encoding="utf-8").read(), "text/html; charset=utf-8")
|
|
104
|
+
except OSError:
|
|
105
|
+
return self._send(500, {"error": "artifact not found: " + self.artifact})
|
|
106
|
+
|
|
107
|
+
if path == API:
|
|
108
|
+
items = list_items(self.lib)
|
|
109
|
+
return self._send(200, {"collection": "video-deepdives", "total": len(items), "items": items})
|
|
110
|
+
|
|
111
|
+
if path.startswith(API + "/_media/"):
|
|
112
|
+
fn = posixpath.basename(path) # strip any traversal
|
|
113
|
+
fp = os.path.join(self.lib, "_media", fn)
|
|
114
|
+
if not os.path.isfile(fp):
|
|
115
|
+
return self._send(404, {"error": "no such media"})
|
|
116
|
+
ctype = mimetypes.guess_type(fp)[0] or "application/octet-stream"
|
|
117
|
+
with open(fp, "rb") as f:
|
|
118
|
+
return self._send(200, f.read(), ctype)
|
|
119
|
+
|
|
120
|
+
if path.startswith(API + "/"):
|
|
121
|
+
slug = posixpath.basename(path)
|
|
122
|
+
loaded = load_item(self.lib, slug)
|
|
123
|
+
if not loaded:
|
|
124
|
+
return self._send(404, {"error": "no such item"})
|
|
125
|
+
_, meta, body = loaded
|
|
126
|
+
return self._send(200, {"slug": slug, "type": "video-deepdive", "meta": meta, "body": body.rstrip("\n")})
|
|
127
|
+
|
|
128
|
+
return self._send(404, {"error": "not found"})
|
|
129
|
+
|
|
130
|
+
def do_PATCH(self):
|
|
131
|
+
path = self.path.split("?", 1)[0].rstrip("/")
|
|
132
|
+
if not path.startswith(API + "/"):
|
|
133
|
+
return self._send(404, {"error": "not found"})
|
|
134
|
+
slug = posixpath.basename(path)
|
|
135
|
+
loaded = load_item(self.lib, slug)
|
|
136
|
+
if not loaded:
|
|
137
|
+
return self._send(404, {"error": "no such item"})
|
|
138
|
+
fp, meta, body = loaded
|
|
139
|
+
try:
|
|
140
|
+
n = int(self.headers.get("Content-Length", 0))
|
|
141
|
+
payload = json.loads(self.rfile.read(n) or b"{}")
|
|
142
|
+
except (ValueError, json.JSONDecodeError):
|
|
143
|
+
return self._send(400, {"error": "bad json"})
|
|
144
|
+
fields = payload.get("fields", payload) # accept {fields:{...}} or a bare dict
|
|
145
|
+
if not isinstance(fields, dict):
|
|
146
|
+
return self._send(400, {"error": "fields must be an object"})
|
|
147
|
+
meta.update(fields)
|
|
148
|
+
open(fp, "w", encoding="utf-8").write(dump_file(meta, body))
|
|
149
|
+
return self._send(200, {"ok": True, "slug": slug, "updated": list(fields.keys())})
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def main():
|
|
153
|
+
ap = argparse.ArgumentParser()
|
|
154
|
+
ap.add_argument("--dir", default=os.path.expanduser(os.environ.get("VIDEO_LIBRARY_DIR", "~/video-deepdives")))
|
|
155
|
+
ap.add_argument("--port", type=int, default=int(os.environ.get("VIDEO_LIBRARY_PORT", "8000")))
|
|
156
|
+
ap.add_argument("--host", default="127.0.0.1")
|
|
157
|
+
here = os.path.dirname(os.path.abspath(__file__))
|
|
158
|
+
ap.add_argument("--artifact", default=os.path.join(here, "..", "reference", "artifact.html"))
|
|
159
|
+
a = ap.parse_args()
|
|
160
|
+
|
|
161
|
+
lib = os.path.abspath(os.path.expanduser(a.dir))
|
|
162
|
+
os.makedirs(lib, exist_ok=True)
|
|
163
|
+
Handler.lib = lib
|
|
164
|
+
Handler.artifact = os.path.abspath(a.artifact)
|
|
165
|
+
n = len([f for f in os.listdir(lib) if f.endswith(".md") and not f.startswith("_")])
|
|
166
|
+
print(f"Library: {lib} ({n} videos)")
|
|
167
|
+
print(f"Artifact: {Handler.artifact}")
|
|
168
|
+
print(f"Serving on http://{a.host}:{a.port}/ (Ctrl-C to stop)")
|
|
169
|
+
ThreadingHTTPServer((a.host, a.port), Handler).serve_forever()
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
if __name__ == "__main__":
|
|
173
|
+
main()
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Resolve a YouTube id from a URL/id, print the scratch dir, and report embeddability.
|
|
3
|
+
# Usage: setup.sh "<youtube_url_or_id>"
|
|
4
|
+
#
|
|
5
|
+
# Library location is configurable via the VIDEO_LIBRARY_DIR env var
|
|
6
|
+
# (default: ~/video-deepdives). One markdown file per video lives there.
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
IN="${1:?usage: setup.sh <youtube_url_or_id>}"
|
|
9
|
+
LIB="${VIDEO_LIBRARY_DIR:-$HOME/video-deepdives}"
|
|
10
|
+
|
|
11
|
+
# Extract 11-char id from common URL shapes, or accept a bare id.
|
|
12
|
+
YTID="$(printf '%s' "$IN" | sed -nE 's#.*(youtu\.be/|v=|/embed/|/shorts/)([A-Za-z0-9_-]{11}).*#\2#p')"
|
|
13
|
+
[ -z "$YTID" ] && [ "${#IN}" -eq 11 ] && YTID="$IN"
|
|
14
|
+
[ -z "$YTID" ] && { echo "Could not parse a YouTube id from: $IN" >&2; exit 1; }
|
|
15
|
+
|
|
16
|
+
SCRATCH="/tmp/ytnote-$YTID"
|
|
17
|
+
mkdir -p "$SCRATCH"
|
|
18
|
+
|
|
19
|
+
# Embeddability: oembed returns 200 if embedding allowed, 401 if the owner disabled it.
|
|
20
|
+
CODE="$(curl -s -o /dev/null -w '%{http_code}' \
|
|
21
|
+
"https://www.youtube.com/oembed?url=https://www.youtube.com/watch?v=$YTID&format=json" || echo "000")"
|
|
22
|
+
if [ "$CODE" = "200" ]; then EMBED="allowed"; else EMBED="BLOCKED (oembed $CODE) — inline player disabled, artifact falls back to YouTube link"; fi
|
|
23
|
+
|
|
24
|
+
echo "YTID: $YTID"
|
|
25
|
+
echo "SCRATCH: $SCRATCH"
|
|
26
|
+
echo "EMBED: $EMBED"
|
|
27
|
+
echo "LIBRARY: $LIB/$YTID.md"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Verify a video is correctly served by the standalone server + appears in the index.
|
|
3
|
+
# Usage: verify.sh <YTID> [base_url]
|
|
4
|
+
# Start the server first: python3 scripts/serve.py --dir <LIBRARY> --port 8000
|
|
5
|
+
set -uo pipefail
|
|
6
|
+
YTID="${1:?usage: verify.sh <YTID> [base_url]}"
|
|
7
|
+
BASE="${2:-http://127.0.0.1:8000}" # standalone serve.py default port
|
|
8
|
+
COLL="$BASE/api/video-deepdives"
|
|
9
|
+
fail=0
|
|
10
|
+
|
|
11
|
+
code(){ curl -s -o /dev/null -w '%{http_code}' "$1"; }
|
|
12
|
+
|
|
13
|
+
echo "1) collection list:"
|
|
14
|
+
C=$(code "$COLL"); echo " GET $COLL -> $C"; [ "$C" = 200 ] || fail=1
|
|
15
|
+
if curl -s "$COLL" | grep -q "\"$YTID\""; then echo " ✓ $YTID present in index"; else echo " ✗ $YTID NOT in index"; fail=1; fi
|
|
16
|
+
|
|
17
|
+
echo "2) item:"
|
|
18
|
+
C=$(code "$COLL/$YTID"); echo " GET $COLL/$YTID -> $C"; [ "$C" = 200 ] || fail=1
|
|
19
|
+
|
|
20
|
+
echo "3) first slide image:"
|
|
21
|
+
C=$(code "$COLL/_media/$YTID-slide-01.jpg"); echo " GET .../_media/$YTID-slide-01.jpg -> $C"; [ "$C" = 200 ] || fail=1
|
|
22
|
+
|
|
23
|
+
echo "4) artifact shell:"
|
|
24
|
+
C=$(code "$BASE/"); echo " GET / -> $C"; [ "$C" = 200 ] || fail=1
|
|
25
|
+
|
|
26
|
+
if [ "$fail" = 0 ]; then
|
|
27
|
+
echo "ALL GOOD. Open: $BASE/#/$YTID"
|
|
28
|
+
else
|
|
29
|
+
echo "SOME CHECKS FAILED — is serve.py running and pointed at the library that contains $YTID?"
|
|
30
|
+
fi
|
|
31
|
+
exit $fail
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Convert a YouTube .vtt (manual or auto-captions) into clean [HH:MM:SS] transcript lines.
|
|
3
|
+
|
|
4
|
+
Usage: vtt_to_transcript.py <input.vtt> <output.txt>
|
|
5
|
+
|
|
6
|
+
Handles the rolling-duplicate problem in auto-captions: each cue repeats the tail of the
|
|
7
|
+
previous cue, so we keep only newly-added words per cue and emit one line per cue start
|
|
8
|
+
time. Strips inline <00:00:00.000> word-timing tags and HTML tags.
|
|
9
|
+
"""
|
|
10
|
+
import sys, re, html
|
|
11
|
+
|
|
12
|
+
TS=re.compile(r'(\d{2}):(\d{2}):(\d{2})\.\d{3}\s*-->\s*(\d{2}):(\d{2}):(\d{2})')
|
|
13
|
+
INLINE=re.compile(r'<[^>]+>')
|
|
14
|
+
|
|
15
|
+
def hhmmss(h,m,s): return f"[{int(h):02d}:{int(m):02d}:{int(s):02d}]"
|
|
16
|
+
|
|
17
|
+
def clean(text):
|
|
18
|
+
text=INLINE.sub('',text)
|
|
19
|
+
text=html.unescape(text)
|
|
20
|
+
return re.sub(r'\s+',' ',text).strip()
|
|
21
|
+
|
|
22
|
+
def main():
|
|
23
|
+
if len(sys.argv)!=3: sys.exit("usage: vtt_to_transcript.py <in.vtt> <out.txt>")
|
|
24
|
+
raw=open(sys.argv[1],encoding='utf-8',errors='replace').read().splitlines()
|
|
25
|
+
cues=[] # (start_label, text)
|
|
26
|
+
i=0; cur=None
|
|
27
|
+
while i<len(raw):
|
|
28
|
+
m=TS.search(raw[i])
|
|
29
|
+
if m:
|
|
30
|
+
if cur: cues.append(cur)
|
|
31
|
+
cur=[hhmmss(*m.groups()[:3]),[]]
|
|
32
|
+
i+=1
|
|
33
|
+
while i<len(raw) and not TS.search(raw[i]) and raw[i].strip()!='':
|
|
34
|
+
if raw[i].strip() and not raw[i].strip().isdigit():
|
|
35
|
+
cur[1].append(clean(raw[i]))
|
|
36
|
+
i+=1
|
|
37
|
+
else:
|
|
38
|
+
i+=1
|
|
39
|
+
if cur: cues.append(cur)
|
|
40
|
+
|
|
41
|
+
# De-duplicate rolling captions: keep only the suffix not already seen.
|
|
42
|
+
out=[]; seen_words=[]
|
|
43
|
+
for label,parts in cues:
|
|
44
|
+
text=clean(' '.join(parts))
|
|
45
|
+
if not text: continue
|
|
46
|
+
words=text.split()
|
|
47
|
+
# find longest overlap of seen tail with this cue's head
|
|
48
|
+
overlap=0; maxk=min(len(words),len(seen_words))
|
|
49
|
+
for k in range(maxk,0,-1):
|
|
50
|
+
if seen_words[-k:]==words[:k]: overlap=k; break
|
|
51
|
+
new=words[overlap:]
|
|
52
|
+
if new:
|
|
53
|
+
out.append(f"{label} {' '.join(new)}")
|
|
54
|
+
seen_words=(seen_words+new)[-40:] # bounded window
|
|
55
|
+
with open(sys.argv[2],'w',encoding='utf-8') as f:
|
|
56
|
+
f.write('\n'.join(out)+'\n')
|
|
57
|
+
print(f"wrote {len(out)} transcript lines -> {sys.argv[2]}")
|
|
58
|
+
|
|
59
|
+
if __name__=="__main__": main()
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Assemble the library markdown file for a video deep-dive.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
write_library_item.py --id <YTID> --title "..." --speaker "..." \
|
|
6
|
+
--tags a,b,c --slides slides.json --transcript transcript.txt [--created YYYY-MM-DD]
|
|
7
|
+
|
|
8
|
+
slides.json: a JSON array of slide objects. Each:
|
|
9
|
+
{
|
|
10
|
+
"idx": 1, # sequence/original frame number (display only; sorted by t)
|
|
11
|
+
"t": 55.7, # seconds (float ok) — used for video seeking
|
|
12
|
+
"mmss": "00:55", # display label
|
|
13
|
+
"title": "Slide title", # short headline
|
|
14
|
+
"note": "1-3 sentences grounded in the transcript at this timestamp.",
|
|
15
|
+
"img": "/api/video-deepdives/_media/<YTID>-slide-01.jpg"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
Writes $VIDEO_LIBRARY_DIR/<YTID>.md (default ~/video-deepdives/<YTID>.md)
|
|
19
|
+
with YAML frontmatter + transcript body. No em dashes or arrows in titles/notes.
|
|
20
|
+
"""
|
|
21
|
+
import argparse, json, os, sys, datetime
|
|
22
|
+
try:
|
|
23
|
+
import yaml
|
|
24
|
+
except ImportError:
|
|
25
|
+
sys.exit("pip install pyyaml")
|
|
26
|
+
|
|
27
|
+
LIB = os.path.expanduser(os.environ.get("VIDEO_LIBRARY_DIR", "~/video-deepdives"))
|
|
28
|
+
|
|
29
|
+
def main():
|
|
30
|
+
ap=argparse.ArgumentParser()
|
|
31
|
+
ap.add_argument("--id",required=True)
|
|
32
|
+
ap.add_argument("--title",required=True)
|
|
33
|
+
ap.add_argument("--speaker",default="")
|
|
34
|
+
ap.add_argument("--tags",default="")
|
|
35
|
+
ap.add_argument("--slides",required=True)
|
|
36
|
+
ap.add_argument("--transcript",required=True)
|
|
37
|
+
ap.add_argument("--created",default=datetime.date.today().isoformat())
|
|
38
|
+
a=ap.parse_args()
|
|
39
|
+
|
|
40
|
+
slides=json.load(open(a.slides))
|
|
41
|
+
slides=sorted(slides,key=lambda s:s["t"])
|
|
42
|
+
for bad in ("—","→"):
|
|
43
|
+
for s in slides:
|
|
44
|
+
if bad in (s.get("title") or "")+(s.get("note") or ""):
|
|
45
|
+
sys.exit(f"Found forbidden char {bad!r} in slide notes/titles; remove it.")
|
|
46
|
+
|
|
47
|
+
fm={
|
|
48
|
+
"id":a.id,
|
|
49
|
+
"title":a.title,
|
|
50
|
+
"youtube_id":a.id,
|
|
51
|
+
"speaker":a.speaker,
|
|
52
|
+
"source_url":f"https://www.youtube.com/watch?v={a.id}",
|
|
53
|
+
"slide_count":len(slides),
|
|
54
|
+
"created":a.created,
|
|
55
|
+
"tags":[t.strip() for t in a.tags.split(",") if t.strip()],
|
|
56
|
+
"slides":slides,
|
|
57
|
+
}
|
|
58
|
+
body=open(a.transcript,encoding="utf-8").read().strip()
|
|
59
|
+
os.makedirs(LIB,exist_ok=True)
|
|
60
|
+
path=os.path.join(LIB,f"{a.id}.md")
|
|
61
|
+
with open(path,"w",encoding="utf-8") as f:
|
|
62
|
+
f.write("---\n")
|
|
63
|
+
yaml.safe_dump(fm,f,sort_keys=False,allow_unicode=True,width=100)
|
|
64
|
+
f.write("---\n## Transcript\n")
|
|
65
|
+
f.write(body+"\n")
|
|
66
|
+
print(f"wrote {path} ({len(slides)} slides, {len(body.splitlines())} transcript lines)")
|
|
67
|
+
print("Verify with: scripts/verify.sh "+a.id)
|
|
68
|
+
|
|
69
|
+
if __name__=="__main__": main()
|
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
import type { RiskLevel } from "./risk-level.js";
|
|
2
|
+
export interface ScanPattern {
|
|
3
|
+
id: string;
|
|
4
|
+
pattern: string;
|
|
5
|
+
description: string;
|
|
6
|
+
severity: "block" | "warn";
|
|
7
|
+
}
|
|
8
|
+
export interface SkillPatch {
|
|
9
|
+
skillId: string;
|
|
10
|
+
find: string;
|
|
11
|
+
replace: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
}
|
|
2
14
|
export interface SkillRiskFilterConfig {
|
|
3
15
|
excludedRiskLevels?: RiskLevel[];
|
|
4
16
|
excludedSkills?: string[];
|
|
17
|
+
scanPatterns?: ScanPattern[];
|
|
18
|
+
skillPatches?: SkillPatch[];
|
|
5
19
|
}
|
|
6
20
|
export declare const DEFAULT_FILTER_CONFIG_PATH: string;
|
|
7
21
|
/**
|
|
@@ -1,13 +1,34 @@
|
|
|
1
|
-
import os from "os";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import fs from "fs";
|
|
1
|
+
import * as os from "node:os";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import * as fs from "node:fs";
|
|
4
4
|
import stripJsonComments from "strip-json-comments";
|
|
5
5
|
const DEFAULT_CONFIG = {
|
|
6
6
|
excludedRiskLevels: [],
|
|
7
7
|
excludedSkills: [],
|
|
8
|
+
scanPatterns: [],
|
|
9
|
+
skillPatches: [],
|
|
8
10
|
};
|
|
9
11
|
export const DEFAULT_FILTER_CONFIG_PATH = path.join(os.homedir(), ".config", "opencode", "skill-filter.jsonc");
|
|
10
12
|
const VALID_RISK_LEVELS = ["none", "safe", "critical", "offensive", "unknown"];
|
|
13
|
+
const VALID_SEVERITIES = ["block", "warn"];
|
|
14
|
+
function isValidScanPattern(entry) {
|
|
15
|
+
if (typeof entry !== "object" || entry === null)
|
|
16
|
+
return false;
|
|
17
|
+
const obj = entry;
|
|
18
|
+
return (typeof obj.id === "string" &&
|
|
19
|
+
typeof obj.pattern === "string" &&
|
|
20
|
+
typeof obj.description === "string" &&
|
|
21
|
+
typeof obj.severity === "string" &&
|
|
22
|
+
VALID_SEVERITIES.includes(obj.severity));
|
|
23
|
+
}
|
|
24
|
+
function isValidSkillPatch(entry) {
|
|
25
|
+
if (typeof entry !== "object" || entry === null)
|
|
26
|
+
return false;
|
|
27
|
+
const obj = entry;
|
|
28
|
+
return (typeof obj.skillId === "string" &&
|
|
29
|
+
typeof obj.find === "string" &&
|
|
30
|
+
typeof obj.replace === "string");
|
|
31
|
+
}
|
|
11
32
|
/**
|
|
12
33
|
* Loads filter config from skill-filter.jsonc. Missing file or section returns defaults.
|
|
13
34
|
* @param configPath Optional override (for testing).
|
|
@@ -28,6 +49,12 @@ export function loadFilterConfig(configPath) {
|
|
|
28
49
|
excludedSkills: Array.isArray(parsed.excludedSkills)
|
|
29
50
|
? parsed.excludedSkills
|
|
30
51
|
: DEFAULT_CONFIG.excludedSkills,
|
|
52
|
+
scanPatterns: Array.isArray(parsed.scanPatterns)
|
|
53
|
+
? parsed.scanPatterns.filter(isValidScanPattern)
|
|
54
|
+
: DEFAULT_CONFIG.scanPatterns,
|
|
55
|
+
skillPatches: Array.isArray(parsed.skillPatches)
|
|
56
|
+
? parsed.skillPatches.filter(isValidSkillPatch)
|
|
57
|
+
: DEFAULT_CONFIG.skillPatches,
|
|
31
58
|
};
|
|
32
59
|
}
|
|
33
60
|
catch {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ScanPattern } from "./config-loader.js";
|
|
2
|
+
import type { SkillIndexEntry } from "./vault-installer.js";
|
|
3
|
+
export interface ScanResult {
|
|
4
|
+
skillId: string;
|
|
5
|
+
matchedPatterns: {
|
|
6
|
+
id: string;
|
|
7
|
+
severity: "block" | "warn";
|
|
8
|
+
}[];
|
|
9
|
+
blocked: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface ScanOutput {
|
|
12
|
+
passed: SkillIndexEntry[];
|
|
13
|
+
quarantined: ScanResult[];
|
|
14
|
+
warned: ScanResult[];
|
|
15
|
+
}
|
|
16
|
+
/** Default patterns that detect recursive loop triggers in skill content. */
|
|
17
|
+
export declare const DEFAULT_SCAN_PATTERNS: ScanPattern[];
|
|
18
|
+
/**
|
|
19
|
+
* Merges user-supplied scan patterns with built-in defaults.
|
|
20
|
+
* Config patterns with the same `id` override the default entry,
|
|
21
|
+
* but only if the config pattern has a valid regex. Invalid overrides
|
|
22
|
+
* are rejected and the default is preserved to prevent silently
|
|
23
|
+
* disabling built-in protection.
|
|
24
|
+
*/
|
|
25
|
+
export declare function mergePatterns(configPatterns: ScanPattern[]): ScanPattern[];
|
|
26
|
+
/**
|
|
27
|
+
* Tests skill content against a list of scan patterns.
|
|
28
|
+
* Returns the list of matched pattern ids and severities.
|
|
29
|
+
*/
|
|
30
|
+
export declare function scanSkillContent(content: string, patterns: ScanPattern[]): {
|
|
31
|
+
id: string;
|
|
32
|
+
severity: "block" | "warn";
|
|
33
|
+
}[];
|
|
34
|
+
/**
|
|
35
|
+
* Scans every skill's SKILL.md for dangerous content patterns.
|
|
36
|
+
* Returns lists of passed, quarantined (blocked), and warned entries.
|
|
37
|
+
*/
|
|
38
|
+
export declare function scanSkills(bundledSkillsPath: string, index: SkillIndexEntry[], configPatterns?: ScanPattern[]): ScanOutput;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { SKILL_FILENAME } from "../constants/constants.js";
|
|
4
|
+
/** Default patterns that detect recursive loop triggers in skill content. */
|
|
5
|
+
export const DEFAULT_SCAN_PATTERNS = [
|
|
6
|
+
{
|
|
7
|
+
id: "recursive-skill-invocation",
|
|
8
|
+
pattern: String.raw `(invoke|use|check|load)\b.*?\bskills?\b.*?\bbefore\s+(any|every|all)\s+(response|action|message)`,
|
|
9
|
+
description: "Detects instructions mandating skill invocation before every response",
|
|
10
|
+
severity: "block",
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
id: "aggressive-match-threshold",
|
|
14
|
+
pattern: String.raw `(even\s+)?(a\s+)?1%\s+chance.*skill`,
|
|
15
|
+
description: "Detects extremely low probability thresholds causing over-triggering",
|
|
16
|
+
severity: "block",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: "mandatory-pre-response-skill-check",
|
|
20
|
+
pattern: String.raw `you\s+(absolutely\s+)?must.*invoke.*skill`,
|
|
21
|
+
description: "Detects mandatory skill invocation directives",
|
|
22
|
+
severity: "block",
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
/**
|
|
26
|
+
* Merges user-supplied scan patterns with built-in defaults.
|
|
27
|
+
* Config patterns with the same `id` override the default entry,
|
|
28
|
+
* but only if the config pattern has a valid regex. Invalid overrides
|
|
29
|
+
* are rejected and the default is preserved to prevent silently
|
|
30
|
+
* disabling built-in protection.
|
|
31
|
+
*/
|
|
32
|
+
export function mergePatterns(configPatterns) {
|
|
33
|
+
const merged = new Map();
|
|
34
|
+
for (const p of DEFAULT_SCAN_PATTERNS) {
|
|
35
|
+
merged.set(p.id, p);
|
|
36
|
+
}
|
|
37
|
+
for (const p of configPatterns) {
|
|
38
|
+
// Validate regex before allowing override
|
|
39
|
+
try {
|
|
40
|
+
new RegExp(p.pattern, "is");
|
|
41
|
+
merged.set(p.id, p);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// If this would override a default, keep the default — don't silently disable protection
|
|
45
|
+
if (!merged.has(p.id)) {
|
|
46
|
+
// New pattern with invalid regex — just skip it
|
|
47
|
+
console.warn(`Invalid regex for pattern '${p.id}': ${p.pattern}`);
|
|
48
|
+
}
|
|
49
|
+
// Existing default stays in place
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return [...merged.values()];
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Tests skill content against a list of scan patterns.
|
|
56
|
+
* Returns the list of matched pattern ids and severities.
|
|
57
|
+
*/
|
|
58
|
+
export function scanSkillContent(content, patterns) {
|
|
59
|
+
const matches = [];
|
|
60
|
+
for (const p of patterns) {
|
|
61
|
+
try {
|
|
62
|
+
const re = new RegExp(p.pattern, "is");
|
|
63
|
+
if (re.test(content)) {
|
|
64
|
+
matches.push({ id: p.id, severity: p.severity });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// Invalid regex — skip silently
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return matches;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Scans every skill's SKILL.md for dangerous content patterns.
|
|
75
|
+
* Returns lists of passed, quarantined (blocked), and warned entries.
|
|
76
|
+
*/
|
|
77
|
+
export function scanSkills(bundledSkillsPath, index, configPatterns) {
|
|
78
|
+
const patterns = mergePatterns(configPatterns ?? []);
|
|
79
|
+
const passed = [];
|
|
80
|
+
const quarantined = [];
|
|
81
|
+
const warned = [];
|
|
82
|
+
for (const entry of index) {
|
|
83
|
+
// Path traversal guard: quarantine entries whose id escapes the bundled-skills directory
|
|
84
|
+
if (entry.id.includes("..") || entry.id.includes(path.sep) || entry.id.includes("/")) {
|
|
85
|
+
quarantined.push({
|
|
86
|
+
skillId: entry.id,
|
|
87
|
+
matchedPatterns: [{ id: "path-traversal", severity: "block" }],
|
|
88
|
+
blocked: true,
|
|
89
|
+
});
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const skillFile = path.join(bundledSkillsPath, entry.id, SKILL_FILENAME);
|
|
93
|
+
if (!fs.existsSync(skillFile)) {
|
|
94
|
+
passed.push(entry);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const content = fs.readFileSync(skillFile, "utf-8");
|
|
98
|
+
const matches = scanSkillContent(content, patterns);
|
|
99
|
+
if (matches.length === 0) {
|
|
100
|
+
passed.push(entry);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const hasBlock = matches.some((m) => m.severity === "block");
|
|
104
|
+
const result = {
|
|
105
|
+
skillId: entry.id,
|
|
106
|
+
matchedPatterns: matches,
|
|
107
|
+
blocked: hasBlock,
|
|
108
|
+
};
|
|
109
|
+
if (hasBlock) {
|
|
110
|
+
quarantined.push(result);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
warned.push(result);
|
|
114
|
+
passed.push(entry);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return { passed, quarantined, warned };
|
|
118
|
+
}
|
|
@@ -17,10 +17,15 @@ export interface SkillPointerOptions {
|
|
|
17
17
|
* Orchestrates the full SkillPointer pipeline:
|
|
18
18
|
*
|
|
19
19
|
* 1. Reads skills_index.json bundled alongside the skills snapshot.
|
|
20
|
-
* 2.
|
|
21
|
-
* 3.
|
|
20
|
+
* 2. Filters by risk level / excluded skills.
|
|
21
|
+
* 3. Copies skills into the vault, categorised by the index.
|
|
22
|
+
* 4. Applies config-driven content patches to installed skills.
|
|
23
|
+
* 5. Generates pointer SKILL.md files in activeSkillsDir with full skill
|
|
22
24
|
* listings so keyword searches (e.g. "laravel") resolve out of the box.
|
|
23
25
|
*
|
|
26
|
+
* Content safety scanning is performed at CI time (sync-skills.yml),
|
|
27
|
+
* not at runtime — dangerous skills are removed before npm publish.
|
|
28
|
+
*
|
|
24
29
|
* The activeSkillsDir is never used as a staging area — user custom
|
|
25
30
|
* skills already present there are never moved or overwritten.
|
|
26
31
|
*/
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import os from "os";
|
|
2
|
-
import path from "path";
|
|
1
|
+
import * as os from "node:os";
|
|
2
|
+
import * as path from "node:path";
|
|
3
3
|
import { VAULT_DIR_NAME } from "../constants/constants.js";
|
|
4
4
|
import { ensureDir } from "../utils/fs.utils.js";
|
|
5
5
|
import { generatePointers } from "./pointer-generator.js";
|
|
6
6
|
import { installSkillsToVault, loadSkillsIndex } from "./vault-installer.js";
|
|
7
7
|
import { filterIndex } from "./skill-risk-filter.js";
|
|
8
8
|
import { loadFilterConfig, DEFAULT_FILTER_CONFIG_PATH } from "./config-loader.js";
|
|
9
|
+
import { applySkillPatches } from "./skill-patcher.js";
|
|
9
10
|
function resolveDefaultVaultDir() {
|
|
10
11
|
return path.join(os.homedir(), ".config", "opencode", VAULT_DIR_NAME);
|
|
11
12
|
}
|
|
@@ -13,10 +14,15 @@ function resolveDefaultVaultDir() {
|
|
|
13
14
|
* Orchestrates the full SkillPointer pipeline:
|
|
14
15
|
*
|
|
15
16
|
* 1. Reads skills_index.json bundled alongside the skills snapshot.
|
|
16
|
-
* 2.
|
|
17
|
-
* 3.
|
|
17
|
+
* 2. Filters by risk level / excluded skills.
|
|
18
|
+
* 3. Copies skills into the vault, categorised by the index.
|
|
19
|
+
* 4. Applies config-driven content patches to installed skills.
|
|
20
|
+
* 5. Generates pointer SKILL.md files in activeSkillsDir with full skill
|
|
18
21
|
* listings so keyword searches (e.g. "laravel") resolve out of the box.
|
|
19
22
|
*
|
|
23
|
+
* Content safety scanning is performed at CI time (sync-skills.yml),
|
|
24
|
+
* not at runtime — dangerous skills are removed before npm publish.
|
|
25
|
+
*
|
|
20
26
|
* The activeSkillsDir is never used as a staging area — user custom
|
|
21
27
|
* skills already present there are never moved or overwritten.
|
|
22
28
|
*/
|
|
@@ -29,5 +35,9 @@ export function runSkillPointer(options) {
|
|
|
29
35
|
const config = loadFilterConfig(configPath);
|
|
30
36
|
const filteredIndex = filterIndex(index, config);
|
|
31
37
|
installSkillsToVault(options.bundledSkillsPath, vaultDir, filteredIndex);
|
|
38
|
+
// Apply content patches
|
|
39
|
+
if (config.skillPatches && config.skillPatches.length > 0) {
|
|
40
|
+
applySkillPatches(vaultDir, filteredIndex, config.skillPatches);
|
|
41
|
+
}
|
|
32
42
|
generatePointers(options.activeSkillsDir, vaultDir, filteredIndex);
|
|
33
43
|
}
|
|
@@ -35,6 +35,8 @@ ${skillList}
|
|
|
35
35
|
**Vault path:** \`${normalizedPath}\`
|
|
36
36
|
|
|
37
37
|
> Do not guess best practices — always read from the vault first.
|
|
38
|
+
|
|
39
|
+
> ⚠️ **Anti-loop guard**: Do NOT invoke skills recursively or check for applicable skills before every response. Each skill should be loaded at most once per user request. If you have already identified and loaded the relevant skill for this task, proceed with execution — do not re-scan for skills.
|
|
38
40
|
`;
|
|
39
41
|
}
|
|
40
42
|
/**
|