@skill-graph/cli 0.5.6
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/CHANGELOG.md +247 -0
- package/LICENSE +200 -0
- package/NOTICE +62 -0
- package/README.md +398 -0
- package/SKILL_GRAPH.md +443 -0
- package/bin/skill-graph.js +374 -0
- package/docs/ADOPTION.md +117 -0
- package/docs/CONFORMANCE.md +66 -0
- package/docs/PRIMER.md +384 -0
- package/docs/QUICKSTART-30MIN.md +333 -0
- package/docs/ROUTING-METRICS.md +120 -0
- package/docs/SKILL-MD-FORMAT-COMPATIBILITY.md +127 -0
- package/docs/SKILL_AUDIT_CHECKLIST.md +199 -0
- package/docs/SKILL_AUDIT_LOOP.md +195 -0
- package/docs/SKILL_METADATA_PROTOCOL.md +609 -0
- package/docs/_archived/marketplace-publication-priority-2026-05-18.md +239 -0
- package/docs/adr/0001-predicate-set.md +69 -0
- package/docs/adr/0002-json-ld-context.md +82 -0
- package/docs/adr/0003-ontoclean-rigidity-tags.md +65 -0
- package/docs/adr/0004-persistent-identifiers.md +74 -0
- package/docs/adr/0005-freshness-consolidation.md +70 -0
- package/docs/adr/0006-revise-predicate-rename.md +105 -0
- package/docs/adr/0007-audit-loop-cadence.md +99 -0
- package/docs/adr/0008-skill-surface-split-and-curation-policy.md +93 -0
- package/docs/category-consumers.md +168 -0
- package/docs/concept-map.md +194 -0
- package/docs/diagrams/drift-states.mmd +21 -0
- package/docs/diagrams/manifest-pipeline.mmd +25 -0
- package/docs/diagrams/routing-harness.mmd +41 -0
- package/docs/diagrams/starter-graph.mmd +53 -0
- package/docs/field-decision-guide.md +315 -0
- package/docs/field-rationale.md +211 -0
- package/docs/field-reference.generated.md +624 -0
- package/docs/field-reference.md +1426 -0
- package/docs/glossary.md +190 -0
- package/docs/head-noun-glossary.md +63 -0
- package/docs/images/audit-phases.png +0 -0
- package/docs/images/drift-states.png +0 -0
- package/docs/images/graded-mode.png +0 -0
- package/docs/images/manifest-pipeline.png +0 -0
- package/docs/images/routing-harness.png +0 -0
- package/docs/images/skill-anatomy.png +0 -0
- package/docs/images/starter-graph.png +0 -0
- package/docs/images/system-model.png +0 -0
- package/docs/integrations/github-actions.md +155 -0
- package/docs/manifest-field-mapping.md +443 -0
- package/docs/marketplace-publication-queue.generated.md +240 -0
- package/docs/marketplace-release-agent-prompt.md +82 -0
- package/docs/marketplace-skill-candidate-list.md +272 -0
- package/docs/marketplace-syndication.md +222 -0
- package/docs/migration-sample-review.md +155 -0
- package/docs/migrations/v4-to-v5.md +168 -0
- package/docs/migrations/v5-to-v6.md +221 -0
- package/docs/name-exceptions.yaml +37 -0
- package/docs/plans/marketplace-p1-public-migration-plan.md +41 -0
- package/docs/plans/multi-root-workspace.md +148 -0
- package/docs/plans/scripts-roadmap.md +107 -0
- package/docs/plans/v4-schema-bump.md +160 -0
- package/docs/plans/wave-2-extraction.md +122 -0
- package/docs/positioning-vs-marketplaces.md +175 -0
- package/docs/proposals/skill-audit-loop-positioning.md +160 -0
- package/docs/quality-doctrine.md +138 -0
- package/docs/recommended-skills.md +150 -0
- package/docs/research/skill-comprehension-eval-research.md +1830 -0
- package/docs/research/skill-retrieval-evidence.md +66 -0
- package/docs/skill-metadata-protocol.md +471 -0
- package/docs/skills-sh-maintainer-cleanup-request.md +80 -0
- package/examples/audits/a11y/findings.md +52 -0
- package/examples/audits/a11y/scorecard.md +21 -0
- package/examples/audits/a11y/verdict.md +44 -0
- package/examples/audits/debugging/findings.md +59 -0
- package/examples/audits/debugging/scorecard.md +22 -0
- package/examples/audits/debugging/verdict.md +33 -0
- package/examples/audits/documentation/findings.md +59 -0
- package/examples/audits/documentation/scorecard.md +22 -0
- package/examples/audits/documentation/verdict.md +33 -0
- package/examples/evals/a11y.json +140 -0
- package/examples/evals/api-design.json +52 -0
- package/examples/evals/code-review.json +52 -0
- package/examples/evals/data-modeling.json +52 -0
- package/examples/evals/database-migration.json +52 -0
- package/examples/evals/debugging.json +118 -0
- package/examples/evals/dependency-architecture.json +52 -0
- package/examples/evals/design-system-architecture.json +52 -0
- package/examples/evals/error-tracking.json +52 -0
- package/examples/evals/event-contract-design.json +52 -0
- package/examples/evals/form-ux-architecture.json +52 -0
- package/examples/evals/framework-fit-analysis.json +52 -0
- package/examples/evals/graph-audit.json +139 -0
- package/examples/evals/information-architecture.json +52 -0
- package/examples/evals/interaction-feedback.json +52 -0
- package/examples/evals/interaction-patterns.json +52 -0
- package/examples/evals/layout-composition.json +52 -0
- package/examples/evals/lint-overlay.json +117 -0
- package/examples/evals/microcopy.json +52 -0
- package/examples/evals/observability-modeling.json +52 -0
- package/examples/evals/pattern-recognition.json +96 -0
- package/examples/evals/performance-engineering.json +52 -0
- package/examples/evals/refactor.json +128 -0
- package/examples/evals/semiotics.json +52 -0
- package/examples/evals/skill-infrastructure.json +96 -0
- package/examples/evals/skill-router.json +140 -0
- package/examples/evals/skill-router.routing.json +113 -0
- package/examples/evals/system-interface-contracts.json +52 -0
- package/examples/evals/task-analysis.json +52 -0
- package/examples/evals/testing-strategy.json +118 -0
- package/examples/evals/type-safety.json +249 -0
- package/examples/evals/visual-design-foundations.json +52 -0
- package/examples/evals/webhook-integration.json +52 -0
- package/examples/exports/a11y.skill-md.md +80 -0
- package/examples/exports/debugging.skill-md.md +80 -0
- package/examples/exports/refactor.skill-md.md +78 -0
- package/examples/exports/testing-strategy.skill-md.md +81 -0
- package/examples/projects/markdown-static-site/README.md +115 -0
- package/examples/projects/markdown-static-site/skills/content-source-router/SKILL.md +131 -0
- package/examples/projects/markdown-static-site/skills/image-optimization-pipeline-config/SKILL.md +132 -0
- package/examples/projects/markdown-static-site/skills/link-rot-detection/SKILL.md +103 -0
- package/examples/projects/markdown-static-site/skills/markdown-post-frontmatter-validation/SKILL.md +133 -0
- package/examples/projects/markdown-static-site/skills/migrate-posts-to-v2-frontmatter/SKILL.md +140 -0
- package/examples/projects/saas-stripe-postgres/README.md +208 -0
- package/examples/projects/saas-stripe-postgres/db/migrations/0004_canonicalize_orders.sql +37 -0
- package/examples/projects/saas-stripe-postgres/db/schema.sql +112 -0
- package/examples/projects/saas-stripe-postgres/skills/migrate-orders-to-canonical-schema/SKILL.md +149 -0
- package/examples/projects/saas-stripe-postgres/skills/nextjs-server-action-validation/SKILL.md +154 -0
- package/examples/projects/saas-stripe-postgres/skills/payment-provider-router/SKILL.md +153 -0
- package/examples/projects/saas-stripe-postgres/skills/postgres-rls-pattern/SKILL.md +163 -0
- package/examples/projects/saas-stripe-postgres/skills/stripe-webhook-signature-verification/SKILL.md +137 -0
- package/examples/protocol/skill-metadata-template.md +301 -0
- package/examples/protocol/skills.manifest.sample.json +13245 -0
- package/examples/skill-metadata-template.md +317 -0
- package/examples/skills.manifest.sample.json +13519 -0
- package/examples/tests/v3-1-skos-fixture/SKILL.md +93 -0
- package/marketplace/README.md +17 -0
- package/marketplace/skills/a11y/SKILL.md +66 -0
- package/marketplace/skills/acid-fundamentals/SKILL.md +106 -0
- package/marketplace/skills/agent-engineering/SKILL.md +386 -0
- package/marketplace/skills/agent-eval-design/SKILL.md +55 -0
- package/marketplace/skills/ai-native-development/SKILL.md +294 -0
- package/marketplace/skills/api-design/SKILL.md +60 -0
- package/marketplace/skills/architecture-decision-records/SKILL.md +55 -0
- package/marketplace/skills/background-jobs/SKILL.md +265 -0
- package/marketplace/skills/bounded-context-mapping/SKILL.md +55 -0
- package/marketplace/skills/cap-theorem-tradeoffs/SKILL.md +127 -0
- package/marketplace/skills/client-server-boundary/SKILL.md +187 -0
- package/marketplace/skills/code-review/SKILL.md +120 -0
- package/marketplace/skills/color-system-design/SKILL.md +43 -0
- package/marketplace/skills/component-architecture/SKILL.md +126 -0
- package/marketplace/skills/compression/SKILL.md +112 -0
- package/marketplace/skills/conceptual-modeling/SKILL.md +181 -0
- package/marketplace/skills/connection-pooling/SKILL.md +105 -0
- package/marketplace/skills/constraint-awareness/SKILL.md +287 -0
- package/marketplace/skills/content-monitor/SKILL.md +209 -0
- package/marketplace/skills/context-engineering/SKILL.md +320 -0
- package/marketplace/skills/context-graph/SKILL.md +174 -0
- package/marketplace/skills/context-management/SKILL.md +174 -0
- package/marketplace/skills/context-window/SKILL.md +239 -0
- package/marketplace/skills/contract-testing/SKILL.md +120 -0
- package/marketplace/skills/cron-scheduling/SKILL.md +223 -0
- package/marketplace/skills/dark-mode-implementation/SKILL.md +47 -0
- package/marketplace/skills/data-modeling/SKILL.md +59 -0
- package/marketplace/skills/data-modeling-fundamentals/SKILL.md +117 -0
- package/marketplace/skills/database-migration/SKILL.md +429 -0
- package/marketplace/skills/debugging/SKILL.md +67 -0
- package/marketplace/skills/dependency-architecture/SKILL.md +58 -0
- package/marketplace/skills/design-module-composition/SKILL.md +43 -0
- package/marketplace/skills/design-system-architecture/SKILL.md +61 -0
- package/marketplace/skills/design-thinking/SKILL.md +44 -0
- package/marketplace/skills/diagnosis/SKILL.md +296 -0
- package/marketplace/skills/diff-analysis/SKILL.md +188 -0
- package/marketplace/skills/e2e-test-design/SKILL.md +113 -0
- package/marketplace/skills/entity-relationship-modeling/SKILL.md +218 -0
- package/marketplace/skills/epistemic-grounding/SKILL.md +112 -0
- package/marketplace/skills/error-boundary/SKILL.md +235 -0
- package/marketplace/skills/error-tracking/SKILL.md +261 -0
- package/marketplace/skills/eval-driven-development/SKILL.md +147 -0
- package/marketplace/skills/evaluation/SKILL.md +113 -0
- package/marketplace/skills/event-contract-design/SKILL.md +60 -0
- package/marketplace/skills/event-storming/SKILL.md +56 -0
- package/marketplace/skills/form-ux-architecture/SKILL.md +60 -0
- package/marketplace/skills/framework-fit-analysis/SKILL.md +59 -0
- package/marketplace/skills/frontend-architecture/SKILL.md +43 -0
- package/marketplace/skills/generative-ui/SKILL.md +118 -0
- package/marketplace/skills/graph-audit/SKILL.md +81 -0
- package/marketplace/skills/guardrails/SKILL.md +118 -0
- package/marketplace/skills/hooks-patterns/SKILL.md +185 -0
- package/marketplace/skills/http-semantics/SKILL.md +136 -0
- package/marketplace/skills/ideation/SKILL.md +41 -0
- package/marketplace/skills/indexing-strategy/SKILL.md +108 -0
- package/marketplace/skills/information-architecture/SKILL.md +59 -0
- package/marketplace/skills/integration-test-design/SKILL.md +111 -0
- package/marketplace/skills/intent-recognition/SKILL.md +136 -0
- package/marketplace/skills/interaction-feedback/SKILL.md +59 -0
- package/marketplace/skills/interaction-patterns/SKILL.md +59 -0
- package/marketplace/skills/journey-mapping/SKILL.md +41 -0
- package/marketplace/skills/keywords/SKILL.md +213 -0
- package/marketplace/skills/knowledge-modeling/SKILL.md +232 -0
- package/marketplace/skills/layout-composition/SKILL.md +59 -0
- package/marketplace/skills/linguistics/SKILL.md +429 -0
- package/marketplace/skills/lint-overlay/SKILL.md +76 -0
- package/marketplace/skills/mental-models/SKILL.md +126 -0
- package/marketplace/skills/merge-queue/SKILL.md +94 -0
- package/marketplace/skills/methodology/SKILL.md +317 -0
- package/marketplace/skills/microcopy/SKILL.md +232 -0
- package/marketplace/skills/middleware-patterns/SKILL.md +363 -0
- package/marketplace/skills/mobile-responsive-ux/SKILL.md +287 -0
- package/marketplace/skills/mutation-testing/SKILL.md +112 -0
- package/marketplace/skills/naming-conventions/SKILL.md +112 -0
- package/marketplace/skills/observability-modeling/SKILL.md +59 -0
- package/marketplace/skills/ontology-modeling/SKILL.md +67 -0
- package/marketplace/skills/owasp-security/SKILL.md +153 -0
- package/marketplace/skills/pattern-recognition/SKILL.md +472 -0
- package/marketplace/skills/performance-budgets/SKILL.md +185 -0
- package/marketplace/skills/performance-engineering/SKILL.md +58 -0
- package/marketplace/skills/performance-testing/SKILL.md +125 -0
- package/marketplace/skills/printify/SKILL.md +42 -0
- package/marketplace/skills/prioritization/SKILL.md +118 -0
- package/marketplace/skills/problem-framing/SKILL.md +41 -0
- package/marketplace/skills/problem-locating-solving/SKILL.md +203 -0
- package/marketplace/skills/project-knowledge-extraction/SKILL.md +54 -0
- package/marketplace/skills/prompt-craft/SKILL.md +134 -0
- package/marketplace/skills/prompt-injection-defense/SKILL.md +132 -0
- package/marketplace/skills/property-based-testing/SKILL.md +100 -0
- package/marketplace/skills/prototyping/SKILL.md +43 -0
- package/marketplace/skills/query-optimization/SKILL.md +144 -0
- package/marketplace/skills/real-time-updates/SKILL.md +324 -0
- package/marketplace/skills/ref-patterns/SKILL.md +284 -0
- package/marketplace/skills/refactor/SKILL.md +65 -0
- package/marketplace/skills/rendering-models/SKILL.md +142 -0
- package/marketplace/skills/replication-patterns/SKILL.md +110 -0
- package/marketplace/skills/research-synthesis/SKILL.md +41 -0
- package/marketplace/skills/route-handler-design/SKILL.md +347 -0
- package/marketplace/skills/schema-evolution/SKILL.md +140 -0
- package/marketplace/skills/security-fundamentals/SKILL.md +139 -0
- package/marketplace/skills/semantic-center/SKILL.md +194 -0
- package/marketplace/skills/semantic-relations/SKILL.md +250 -0
- package/marketplace/skills/semantics/SKILL.md +366 -0
- package/marketplace/skills/semiotics/SKILL.md +230 -0
- package/marketplace/skills/seo-strategy/SKILL.md +260 -0
- package/marketplace/skills/server-actions-design/SKILL.md +243 -0
- package/marketplace/skills/server-components-design/SKILL.md +190 -0
- package/marketplace/skills/sharding-strategy/SKILL.md +123 -0
- package/marketplace/skills/shopify/SKILL.md +42 -0
- package/marketplace/skills/skill-infrastructure/SKILL.md +320 -0
- package/marketplace/skills/skill-router/SKILL.md +71 -0
- package/marketplace/skills/skill-scaffold/SKILL.md +105 -0
- package/marketplace/skills/snapshot-testing/SKILL.md +120 -0
- package/marketplace/skills/spec-driven-development/SKILL.md +148 -0
- package/marketplace/skills/state-machine-modeling/SKILL.md +56 -0
- package/marketplace/skills/state-management/SKILL.md +134 -0
- package/marketplace/skills/streaming-architecture/SKILL.md +194 -0
- package/marketplace/skills/summarization/SKILL.md +156 -0
- package/marketplace/skills/suspense-patterns/SKILL.md +265 -0
- package/marketplace/skills/system-interface-contracts/SKILL.md +59 -0
- package/marketplace/skills/task-analysis/SKILL.md +201 -0
- package/marketplace/skills/taxonomy-design/SKILL.md +66 -0
- package/marketplace/skills/test-coverage-strategy/SKILL.md +108 -0
- package/marketplace/skills/test-doubles-design/SKILL.md +98 -0
- package/marketplace/skills/test-driven-development/SKILL.md +96 -0
- package/marketplace/skills/testing-strategy/SKILL.md +67 -0
- package/marketplace/skills/theme-system-design/SKILL.md +43 -0
- package/marketplace/skills/tool-call-flow/SKILL.md +229 -0
- package/marketplace/skills/tool-call-strategy/SKILL.md +292 -0
- package/marketplace/skills/transaction-isolation/SKILL.md +98 -0
- package/marketplace/skills/type-safety/SKILL.md +177 -0
- package/marketplace/skills/typography-system/SKILL.md +43 -0
- package/marketplace/skills/usability-testing/SKILL.md +43 -0
- package/marketplace/skills/user-research/SKILL.md +43 -0
- package/marketplace/skills/vercel-composition-patterns/SKILL.md +157 -0
- package/marketplace/skills/version-control/SKILL.md +233 -0
- package/marketplace/skills/visual-design-foundations/SKILL.md +59 -0
- package/marketplace/skills/visual-hierarchy/SKILL.md +43 -0
- package/marketplace/skills/webhook-integration/SKILL.md +331 -0
- package/marketplace/skills/writing-humanizer/SKILL.md +380 -0
- package/package.json +67 -0
- package/schemas/manifest.schema.json +811 -0
- package/schemas/manifest.v2.schema.json +164 -0
- package/schemas/manifest.v3.schema.json +758 -0
- package/schemas/manifest.v4.schema.json +755 -0
- package/schemas/manifest.v5.schema.json +755 -0
- package/schemas/manifest.v6.schema.json +811 -0
- package/schemas/skill.context.jsonld +279 -0
- package/schemas/skill.schema.json +919 -0
- package/schemas/skill.v2.schema.json +201 -0
- package/schemas/skill.v3.schema.json +827 -0
- package/schemas/skill.v4.schema.json +822 -0
- package/schemas/skill.v5.schema.json +830 -0
- package/schemas/skill.v6.schema.json +946 -0
- package/schemas/vocabulary/keywords.json +180 -0
- package/schemas/vocabulary/workspace_tags.json +23 -0
- package/scripts/__tests__/migrate-skill-v2-to-v3.test.js +161 -0
- package/scripts/__tests__/migrate-skill-v3-to-v4.test.js +158 -0
- package/scripts/__tests__/test-export-parser-drift.js +149 -0
- package/scripts/__tests__/test-marketplace-export.js +114 -0
- package/scripts/__tests__/test-router-paths.js +82 -0
- package/scripts/__tests__/test-stability-promotion.js +244 -0
- package/scripts/__tests__/test-v3-1-alias-contract.js +109 -0
- package/scripts/__tests__/test-v3-1-skos-runtime.js +116 -0
- package/scripts/backfill-schema-version.js +198 -0
- package/scripts/build-field-reference.js +160 -0
- package/scripts/build-retrieval-baseline.js +511 -0
- package/scripts/check-markdown-links.js +211 -0
- package/scripts/check-protocol-consistency.js +979 -0
- package/scripts/export-marketplace-skills.js +610 -0
- package/scripts/export-skill.js +374 -0
- package/scripts/generate-manifest.js +787 -0
- package/scripts/lib/alias-contract.js +83 -0
- package/scripts/lib/audit-prompt-builder.js +771 -0
- package/scripts/lib/mock-grader.js +134 -0
- package/scripts/lib/parse-frontmatter.js +429 -0
- package/scripts/lib/roots.js +119 -0
- package/scripts/lint/check-archetype-sections.js +185 -0
- package/scripts/lint/check-category-enum.js +83 -0
- package/scripts/lint/check-routing-eval.js +146 -0
- package/scripts/lint/check-routing-quality.js +211 -0
- package/scripts/lint/check-stability-promotion.js +220 -0
- package/scripts/lint/format-code-frame.js +206 -0
- package/scripts/marketplace-install.js +125 -0
- package/scripts/migrate-category-to-enum.js +169 -0
- package/scripts/migrate-skill-v2-to-v3.js +424 -0
- package/scripts/migrate-skill-v3-to-v4.js +200 -0
- package/scripts/migrate-skill-v5-to-v6.js +304 -0
- package/scripts/restructure-by-category.js +85 -0
- package/scripts/seed-publication-classification.js +282 -0
- package/scripts/skill-audit.js +893 -0
- package/scripts/skill-graph-drift.js +483 -0
- package/scripts/skill-graph-route.js +766 -0
- package/scripts/skill-graph-routing-eval.js +393 -0
- package/scripts/skill-lint.js +1317 -0
- package/scripts/skill-overlap.js +213 -0
- package/scripts/verify-skill-md-export.js +201 -0
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Generate the public marketplace SKILL.md export surface.
|
|
4
|
+
*
|
|
5
|
+
* This script keeps Skill Metadata Protocol files authoritative under
|
|
6
|
+
* skills/<name>/SKILL.md and writes plain SKILL.md exports under
|
|
7
|
+
* marketplace/skills/<name>/SKILL.md for release to SKILL.md marketplaces.
|
|
8
|
+
*
|
|
9
|
+
* The generated surface is intentionally checked here, not by convention:
|
|
10
|
+
* - every exported skill is plain Agent Skills shape
|
|
11
|
+
* - every description fits the 1024-character marketplace limit
|
|
12
|
+
* - every exported skill carries Skill Graph provenance metadata
|
|
13
|
+
* - generated markdown links resolve or point back to the canonical repo
|
|
14
|
+
* - generated text is scanned for private/local/personal/token-like signals
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* node scripts/export-marketplace-skills.js
|
|
18
|
+
* node scripts/export-marketplace-skills.js --check
|
|
19
|
+
* node scripts/export-marketplace-skills.js --validate-only
|
|
20
|
+
* node scripts/export-marketplace-skills.js --output marketplace
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
'use strict';
|
|
24
|
+
|
|
25
|
+
const fs = require('fs');
|
|
26
|
+
const path = require('path');
|
|
27
|
+
const { parseFrontmatter } = require('./lib/parse-frontmatter');
|
|
28
|
+
const { workspaceRoot, loadWorkspaceConfig, resolveSkillRoots } = require('./lib/roots');
|
|
29
|
+
const { buildExportedSkill, normalizeExportName } = require('./export-skill');
|
|
30
|
+
const { validateExportedFrontmatter } = require('./verify-skill-md-export');
|
|
31
|
+
const { checkFile } = require('./check-markdown-links');
|
|
32
|
+
|
|
33
|
+
const REPO_ROOT = workspaceRoot();
|
|
34
|
+
const WORKSPACE_CONFIG = loadWorkspaceConfig(REPO_ROOT, msg => process.stderr.write(`WARN ${msg}\n`));
|
|
35
|
+
const SKILL_ROOTS = resolveSkillRoots(REPO_ROOT, WORKSPACE_CONFIG);
|
|
36
|
+
// Primary skill root — first configured root, or local skills/ as fallback.
|
|
37
|
+
const DEFAULT_SOURCE_DIR = SKILL_ROOTS[0].absPath;
|
|
38
|
+
const DEFAULT_OUTPUT_ROOT = path.join(REPO_ROOT, 'marketplace');
|
|
39
|
+
const MARKETPLACE_DESCRIPTION_LIMIT = 1024;
|
|
40
|
+
const SKILL_GRAPH_SOURCE_REPO = 'https://github.com/jacob-balslev/skill-graph';
|
|
41
|
+
const SKILL_GRAPH_PROTOCOL = 'Skill Metadata Protocol v4';
|
|
42
|
+
const SKILL_GRAPH_PROJECT = 'Skill Graph';
|
|
43
|
+
const RELEASE_TARGET_REPO = 'jacob-balslev/skills';
|
|
44
|
+
// Public GitHub URL for the release target repo (skills library). Used to rewrite
|
|
45
|
+
// relative cross-repo links that resolve into the sibling skills library.
|
|
46
|
+
const SKILLS_LIBRARY_REPO = `https://github.com/${RELEASE_TARGET_REPO}`;
|
|
47
|
+
|
|
48
|
+
const PROVENANCE_KEYS = [
|
|
49
|
+
'skill_graph_source_repo',
|
|
50
|
+
'skill_graph_protocol',
|
|
51
|
+
'skill_graph_project',
|
|
52
|
+
'skill_graph_canonical_skill',
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
// Export description overrides for skills whose canonical description exceeds the
|
|
56
|
+
// 1024-character Agent Skills marketplace limit. Only add an entry when the canonical
|
|
57
|
+
// description (in the skills/ library) is actually over the limit — the export gate
|
|
58
|
+
// enforces this and will throw if an override exists for an under-limit description.
|
|
59
|
+
const EXPORT_DESCRIPTION_OVERRIDES = {
|
|
60
|
+
'middleware-patterns': 'Use when designing or reviewing Next.js middleware (`middleware.ts`): cross-cutting request/response transforms before route resolution, Edge Runtime constraints, `matcher` config, `NextRequest`/`NextResponse` API, the four response shapes (next/rewrite/redirect/direct), canonical patterns (auth gate, locale routing, A/B testing, header injection, geo-routing, bot blocking), and the design rule that middleware is for cross-cutting concerns across many routes — never per-route business logic. Do NOT use for per-route HTTP endpoint logic (use route-handler-design), Server Action mutations (use server-actions-design), abstract HTTP semantics (use http-semantics), CSP and hardening (use security-fundamentals), or the cross-cutting streaming model (use streaming-architecture).',
|
|
61
|
+
'ref-patterns': 'Use when designing or reviewing React ref usage: refs vs state (mutable handle vs reactive value), `useRef` for DOM access and mutable instance values, ref callbacks, `forwardRef` for passing refs through boundaries (and the React 19 ref-as-prop change), `useImperativeHandle` for controlled imperative surfaces, ref forwarding through compound-component primitives (Radix Slot / Headless UI), and the rule that refs are an escape hatch for DOM access, non-React library integration, focus management, animation, and imperative APIs — never a substitute for state. Do NOT use for hook discipline (use hooks-patterns), component layering (use component-architecture), state ownership (use state-management), client/server boundary (use client-server-boundary), or form-state patterns (use form-ux-architecture).',
|
|
62
|
+
'route-handler-design': 'Use when designing or reviewing Next.js Route Handlers (`route.ts`): when a Route Handler is right vs Server Actions or Server Components, the HTTP-method-as-export contract (GET/POST/PUT/PATCH/DELETE/HEAD/OPTIONS), Web-standard Request/Response interface, body-parsing primitives, GET caching and opt-out, dynamic segments, manual CORS, runtime selection (Edge vs Node), streaming via ReadableStream, status/header discipline, error responses, webhook-style handlers, and the rule that Route Handlers are the right surface when the caller is not your own Next.js UI. Do NOT use for internal UI mutations (use server-actions-design), REST contract design (use api-design), abstract HTTP semantics (use http-semantics), cross-route preprocessing (use middleware-patterns), or webhook reliability (use webhook-integration).',
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const PRIVACY_PATTERNS = [
|
|
66
|
+
{
|
|
67
|
+
id: 'windows_user_path',
|
|
68
|
+
message: 'local Windows user path',
|
|
69
|
+
regex: /\b[A-Za-z]:[\\/]+Users[\\/]+[^ \t\r\n"')]+/g,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: 'posix_user_path',
|
|
73
|
+
message: 'local macOS user path',
|
|
74
|
+
regex: /(^|[\s"'(])\/Users\/[^ \t\r\n"')]+/g,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: 'linux_home_path',
|
|
78
|
+
message: 'local Linux home path',
|
|
79
|
+
regex: /(^|[\s"'(])\/home\/[^\/\s"')]+\/[^ \t\r\n"')]+/g,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: 'email_address',
|
|
83
|
+
message: 'email address',
|
|
84
|
+
regex: /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi,
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: 'private_key',
|
|
88
|
+
message: 'private key block',
|
|
89
|
+
regex: /-----BEGIN (?:RSA |DSA |EC |OPENSSH |PGP )?PRIVATE KEY-----/g,
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: 'known_secret_prefix',
|
|
93
|
+
message: 'token-like secret prefix',
|
|
94
|
+
regex: /\b(?:AIza[0-9A-Za-z_-]{20,}|AKIA[0-9A-Z]{16}|sk-[A-Za-z0-9]{20,}|gh[pousr]_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,}|shpat_[A-Za-z0-9]{20,}|shpss_[A-Za-z0-9]{20,}|napi_[A-Za-z0-9]{20,}|xox[baprs]-[A-Za-z0-9-]{20,})\b/g,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: 'local_artifact_path',
|
|
98
|
+
message: 'local-only artifact path',
|
|
99
|
+
regex: /(^|[\s"'(])(?:\.artifacts|\.research|\.roundtable|audits\/_state|audits\\_state)(?:[\/\\]|$)/gi,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: 'private_project_name',
|
|
103
|
+
message: 'known private project name',
|
|
104
|
+
regex: /\b(?:placeholder-project-name|boardmeeting|free-oppression-data)\b/gi,
|
|
105
|
+
},
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
function repoRelative(filePath) {
|
|
109
|
+
return path.relative(REPO_ROOT, filePath).split(path.sep).join('/');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function lineForIndex(text, index) {
|
|
113
|
+
return text.slice(0, index).split(/\r?\n/).length;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function isExternalTarget(target) {
|
|
117
|
+
return /^(?:https?:|mailto:|tel:|ftp:|data:|javascript:)/i.test(target);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function splitMarkdownTarget(rawTarget) {
|
|
121
|
+
let target = rawTarget.trim();
|
|
122
|
+
if (target.startsWith('<') && target.endsWith('>')) target = target.slice(1, -1);
|
|
123
|
+
const hashIdx = target.indexOf('#');
|
|
124
|
+
if (hashIdx === -1) return { pathPart: target, anchor: '' };
|
|
125
|
+
return {
|
|
126
|
+
pathPart: target.slice(0, hashIdx),
|
|
127
|
+
anchor: target.slice(hashIdx),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Relative posix prefix (from REPO_ROOT) that identifies the skills library repo root.
|
|
132
|
+
// When the source dir is inside the sibling skills repo, links that resolve to within
|
|
133
|
+
// that repo must be rewritten to SKILLS_LIBRARY_REPO rather than SKILL_GRAPH_SOURCE_REPO.
|
|
134
|
+
// Example: source at "../skills/skills/skill-scaffold/SKILL.md" → prefix "../skills/"
|
|
135
|
+
const SKILLS_LIBRARY_ROOT_PREFIX = (() => {
|
|
136
|
+
const skillsDir = DEFAULT_SOURCE_DIR;
|
|
137
|
+
// Compute the skills library *repo* root (parent of DEFAULT_SOURCE_DIR, e.g. "../skills")
|
|
138
|
+
const skillsLibRoot = path.dirname(skillsDir);
|
|
139
|
+
const rel = path.relative(REPO_ROOT, skillsLibRoot);
|
|
140
|
+
// Convert to posix and ensure trailing slash for prefix matching
|
|
141
|
+
const posix = rel.split(path.sep).join('/');
|
|
142
|
+
return posix ? posix + '/' : '';
|
|
143
|
+
})();
|
|
144
|
+
|
|
145
|
+
function canonicalRepoUrlForLink(sourceRelPath, pathPart, anchor) {
|
|
146
|
+
const sourceDir = path.posix.dirname(sourceRelPath);
|
|
147
|
+
const normalized = path.posix.normalize(path.posix.join(sourceDir, pathPart));
|
|
148
|
+
if (!normalized || normalized === '..') return null;
|
|
149
|
+
|
|
150
|
+
// Link resolves within the skill-graph repo itself
|
|
151
|
+
if (!normalized.startsWith('../')) {
|
|
152
|
+
return `${SKILL_GRAPH_SOURCE_REPO}/blob/main/${normalized}${anchor || ''}`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Link resolves into the sibling skills library repo — rewrite to that repo's GitHub URL
|
|
156
|
+
if (SKILLS_LIBRARY_ROOT_PREFIX && normalized.startsWith(SKILLS_LIBRARY_ROOT_PREFIX)) {
|
|
157
|
+
const skillsRepoRelPath = normalized.slice(SKILLS_LIBRARY_ROOT_PREFIX.length);
|
|
158
|
+
if (skillsRepoRelPath && !skillsRepoRelPath.startsWith('../')) {
|
|
159
|
+
return `${SKILLS_LIBRARY_REPO}/blob/main/${skillsRepoRelPath}${anchor || ''}`;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function rewriteLocalMarkdownLinksToCanonicalRepo(text, sourceRelPath) {
|
|
167
|
+
return text.replace(/(!?\[[^\]\n]*\]\()([^) \n]+)(\))/g, (match, prefix, rawTarget, suffix) => {
|
|
168
|
+
const { pathPart, anchor } = splitMarkdownTarget(rawTarget);
|
|
169
|
+
if (!pathPart || isExternalTarget(pathPart) || pathPart.startsWith('#')) return match;
|
|
170
|
+
const url = canonicalRepoUrlForLink(sourceRelPath, pathPart, anchor);
|
|
171
|
+
return url ? `${prefix}${url}${suffix}` : match;
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Root of the skills library repo (e.g. ~/Development/skills when using the sibling config).
|
|
176
|
+
// Used to compute canonical skill source paths relative to the skills library, not skill-graph.
|
|
177
|
+
const SKILLS_LIBRARY_REPO_ROOT = SKILLS_LIBRARY_ROOT_PREFIX
|
|
178
|
+
? path.resolve(REPO_ROOT, SKILLS_LIBRARY_ROOT_PREFIX.slice(0, -1))
|
|
179
|
+
: REPO_ROOT;
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Returns the canonical source path for a skill file — relative to the skills library
|
|
183
|
+
* repo root (e.g. "skills/a11y/SKILL.md"). When skills live in a sibling repo, this
|
|
184
|
+
* strips the sibling prefix so the provenance value is stable and skills-library-relative
|
|
185
|
+
* rather than skill-graph-relative ("../skills/skills/a11y/SKILL.md").
|
|
186
|
+
*/
|
|
187
|
+
function canonicalSourcePath(skillMd) {
|
|
188
|
+
return path.relative(SKILLS_LIBRARY_REPO_ROOT, skillMd).split(path.sep).join('/');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function collectCanonicalSkills(sourceDir = DEFAULT_SOURCE_DIR) {
|
|
192
|
+
const skills = [];
|
|
193
|
+
for (const entry of fs.readdirSync(sourceDir, { withFileTypes: true })) {
|
|
194
|
+
if (!entry.isDirectory()) continue;
|
|
195
|
+
const skillDir = path.join(sourceDir, entry.name);
|
|
196
|
+
const skillMd = path.join(skillDir, 'SKILL.md');
|
|
197
|
+
if (!fs.existsSync(skillMd)) continue;
|
|
198
|
+
const text = fs.readFileSync(skillMd, 'utf8');
|
|
199
|
+
const fm = parseFrontmatter(text);
|
|
200
|
+
if (!fm) {
|
|
201
|
+
throw new Error(`Source skill has no parseable frontmatter: ${repoRelative(skillMd)}`);
|
|
202
|
+
}
|
|
203
|
+
skills.push({
|
|
204
|
+
dirName: entry.name,
|
|
205
|
+
sourcePath: skillMd,
|
|
206
|
+
// sourceRelPath: skill-graph-repo-relative path — used for link rewriting (filesystem context).
|
|
207
|
+
sourceRelPath: repoRelative(skillMd),
|
|
208
|
+
// canonicalSkillPath: skills-library-relative path — used for provenance metadata.
|
|
209
|
+
// When skills live in the sibling skills repo, this is "skills/a11y/SKILL.md" rather
|
|
210
|
+
// than the skill-graph-relative "../skills/skills/a11y/SKILL.md".
|
|
211
|
+
canonicalSkillPath: canonicalSourcePath(skillMd),
|
|
212
|
+
text,
|
|
213
|
+
fm,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
return skills.sort((a, b) => String(a.fm.name).localeCompare(String(b.fm.name)));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function provenanceForSkill(sourceRelPath) {
|
|
220
|
+
return {
|
|
221
|
+
skill_graph_source_repo: SKILL_GRAPH_SOURCE_REPO,
|
|
222
|
+
skill_graph_protocol: SKILL_GRAPH_PROTOCOL,
|
|
223
|
+
skill_graph_project: SKILL_GRAPH_PROJECT,
|
|
224
|
+
skill_graph_canonical_skill: sourceRelPath,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function exportDescriptionForSkill(skill) {
|
|
229
|
+
const sourceDescription = skill.fm.description || '';
|
|
230
|
+
const override = EXPORT_DESCRIPTION_OVERRIDES[skill.fm.name];
|
|
231
|
+
|
|
232
|
+
if (sourceDescription.length > MARKETPLACE_DESCRIPTION_LIMIT) {
|
|
233
|
+
if (!override) {
|
|
234
|
+
throw new Error(
|
|
235
|
+
`${skill.sourceRelPath} description is ${sourceDescription.length} characters; add an export-specific override`
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
if (override.length > MARKETPLACE_DESCRIPTION_LIMIT) {
|
|
239
|
+
throw new Error(
|
|
240
|
+
`${skill.fm.name} export description is ${override.length} characters; limit is ${MARKETPLACE_DESCRIPTION_LIMIT}`
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
description: override,
|
|
245
|
+
shortened: true,
|
|
246
|
+
sourceLength: sourceDescription.length,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (override) {
|
|
251
|
+
throw new Error(
|
|
252
|
+
`${skill.fm.name} has an export description override but the canonical description is within the limit`
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
description: sourceDescription,
|
|
258
|
+
shortened: false,
|
|
259
|
+
sourceLength: sourceDescription.length,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function buildMarketplaceSkillText(skill) {
|
|
264
|
+
const description = exportDescriptionForSkill(skill);
|
|
265
|
+
// Use canonicalSkillPath (skills-library-relative) for provenance, so the value is
|
|
266
|
+
// stable across layouts — "skills/a11y/SKILL.md" regardless of where skill-graph lives.
|
|
267
|
+
const metadata = provenanceForSkill(skill.canonicalSkillPath);
|
|
268
|
+
if (description.shortened) {
|
|
269
|
+
metadata.skill_graph_export_description = 'shortened for Agent Skills 1024-character description limit; canonical source keeps the full routing contract';
|
|
270
|
+
metadata.skill_graph_canonical_description_length = String(description.sourceLength);
|
|
271
|
+
}
|
|
272
|
+
const exported = buildExportedSkill(skill.text, {
|
|
273
|
+
description: description.description,
|
|
274
|
+
metadata,
|
|
275
|
+
});
|
|
276
|
+
if (!exported) throw new Error(`Unable to export ${skill.sourceRelPath}`);
|
|
277
|
+
return rewriteLocalMarkdownLinksToCanonicalRepo(exported, skill.sourceRelPath);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function generatedReadme(skillCount) {
|
|
281
|
+
return [
|
|
282
|
+
'# Skill Graph Marketplace Export',
|
|
283
|
+
'',
|
|
284
|
+
'This directory is generated from the canonical Skill Metadata Protocol source in `skills/`.',
|
|
285
|
+
'Do not edit generated files here by hand; run `node scripts/export-marketplace-skills.js` from the canonical repo.',
|
|
286
|
+
'',
|
|
287
|
+
`Canonical source repo: ${SKILL_GRAPH_SOURCE_REPO}`,
|
|
288
|
+
`Release target repo: ${RELEASE_TARGET_REPO}`,
|
|
289
|
+
`Generated public skills: ${skillCount}`,
|
|
290
|
+
'',
|
|
291
|
+
'Each skill under `skills/<name>/SKILL.md` is a plain Agent Skills-compatible export.',
|
|
292
|
+
'Protocol fields are preserved as string values under `metadata`, with factual Skill Graph provenance.',
|
|
293
|
+
'',
|
|
294
|
+
'After the release target is published, install with:',
|
|
295
|
+
'',
|
|
296
|
+
'```bash',
|
|
297
|
+
`npx skills add ${RELEASE_TARGET_REPO}`,
|
|
298
|
+
'```',
|
|
299
|
+
'',
|
|
300
|
+
].join('\n');
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function expectedSurfaceFiles(outputRoot) {
|
|
304
|
+
const skills = collectCanonicalSkills();
|
|
305
|
+
const files = new Map();
|
|
306
|
+
files.set(path.join(outputRoot, 'README.md'), generatedReadme(skills.length));
|
|
307
|
+
|
|
308
|
+
for (const skill of skills) {
|
|
309
|
+
const exportName = normalizeExportName(skill.fm.name);
|
|
310
|
+
const dest = path.join(outputRoot, 'skills', exportName, 'SKILL.md');
|
|
311
|
+
files.set(dest, buildMarketplaceSkillText(skill));
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return { skills, files };
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function assertSafeOutputRoot(outputRoot) {
|
|
318
|
+
const resolved = path.resolve(outputRoot);
|
|
319
|
+
const rel = path.relative(REPO_ROOT, resolved);
|
|
320
|
+
if (!rel || rel.startsWith('..') || path.isAbsolute(rel)) {
|
|
321
|
+
throw new Error(`Refusing to write marketplace export outside a repo subdirectory: ${outputRoot}`);
|
|
322
|
+
}
|
|
323
|
+
const first = rel.split(path.sep)[0];
|
|
324
|
+
const blocked = new Set(['.git', 'bin', 'docs', 'examples', 'schemas', 'scripts', 'skills']);
|
|
325
|
+
if (blocked.has(first)) {
|
|
326
|
+
throw new Error(`Refusing to use protected repo directory as marketplace output: ${rel}`);
|
|
327
|
+
}
|
|
328
|
+
return resolved;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function writeSurface(outputRoot, expectedFiles) {
|
|
332
|
+
assertSafeOutputRoot(outputRoot);
|
|
333
|
+
fs.rmSync(outputRoot, { recursive: true, force: true });
|
|
334
|
+
for (const [filePath, text] of expectedFiles) {
|
|
335
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
336
|
+
fs.writeFileSync(filePath, text, 'utf8');
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function collectGeneratedSkillFiles(outputRoot) {
|
|
341
|
+
const skillsDir = path.join(outputRoot, 'skills');
|
|
342
|
+
if (!fs.existsSync(skillsDir)) return [];
|
|
343
|
+
const files = [];
|
|
344
|
+
for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) {
|
|
345
|
+
if (!entry.isDirectory()) continue;
|
|
346
|
+
const skillMd = path.join(skillsDir, entry.name, 'SKILL.md');
|
|
347
|
+
if (fs.existsSync(skillMd)) files.push(skillMd);
|
|
348
|
+
}
|
|
349
|
+
return files.sort((a, b) => repoRelative(a).localeCompare(repoRelative(b)));
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function scanPrivacyText(text, filePath) {
|
|
353
|
+
const findings = [];
|
|
354
|
+
for (const pattern of PRIVACY_PATTERNS) {
|
|
355
|
+
pattern.regex.lastIndex = 0;
|
|
356
|
+
let match;
|
|
357
|
+
while ((match = pattern.regex.exec(text)) !== null) {
|
|
358
|
+
findings.push({
|
|
359
|
+
file: repoRelative(filePath),
|
|
360
|
+
line: lineForIndex(text, match.index),
|
|
361
|
+
id: pattern.id,
|
|
362
|
+
message: pattern.message,
|
|
363
|
+
match: String(match[0]).trim().slice(0, 120),
|
|
364
|
+
});
|
|
365
|
+
if (match.index === pattern.regex.lastIndex) pattern.regex.lastIndex++;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return findings;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function validateGeneratedSurface(outputRoot, expectedSkills = null) {
|
|
372
|
+
const errors = [];
|
|
373
|
+
const privacyFindings = [];
|
|
374
|
+
const markdownFailures = [];
|
|
375
|
+
const skillFiles = collectGeneratedSkillFiles(outputRoot);
|
|
376
|
+
const expectedByName = new Map(
|
|
377
|
+
(expectedSkills || collectCanonicalSkills()).map(skill => [normalizeExportName(skill.fm.name), skill])
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
if (skillFiles.length !== expectedByName.size) {
|
|
381
|
+
errors.push(`expected ${expectedByName.size} exported skills, found ${skillFiles.length}`);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const seen = new Set();
|
|
385
|
+
const readme = path.join(outputRoot, 'README.md');
|
|
386
|
+
const markdownFiles = fs.existsSync(readme) ? [readme, ...skillFiles] : skillFiles;
|
|
387
|
+
|
|
388
|
+
for (const filePath of markdownFiles) {
|
|
389
|
+
const text = fs.readFileSync(filePath, 'utf8');
|
|
390
|
+
privacyFindings.push(...scanPrivacyText(text, filePath));
|
|
391
|
+
for (const linkError of checkFile(filePath)) {
|
|
392
|
+
markdownFailures.push({
|
|
393
|
+
file: repoRelative(filePath),
|
|
394
|
+
line: linkError.line,
|
|
395
|
+
message: linkError.message,
|
|
396
|
+
target: linkError.target,
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
for (const skillMd of skillFiles) {
|
|
402
|
+
const parentName = path.basename(path.dirname(skillMd));
|
|
403
|
+
seen.add(parentName);
|
|
404
|
+
const text = fs.readFileSync(skillMd, 'utf8');
|
|
405
|
+
const fm = parseFrontmatter(text);
|
|
406
|
+
if (!fm) {
|
|
407
|
+
errors.push(`${repoRelative(skillMd)} has no parseable frontmatter`);
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const shape = validateExportedFrontmatter(fm);
|
|
412
|
+
for (const error of shape.errors) {
|
|
413
|
+
errors.push(`${repoRelative(skillMd)}: ${error}`);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (fm.name !== parentName) {
|
|
417
|
+
errors.push(`${repoRelative(skillMd)}: exported name "${fm.name}" does not match parent directory "${parentName}"`);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (typeof fm.description !== 'string' || fm.description.length > MARKETPLACE_DESCRIPTION_LIMIT) {
|
|
421
|
+
errors.push(
|
|
422
|
+
`${repoRelative(skillMd)}: description length ${(fm.description || '').length} exceeds ${MARKETPLACE_DESCRIPTION_LIMIT}`
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (!fm.metadata || typeof fm.metadata !== 'object') {
|
|
427
|
+
errors.push(`${repoRelative(skillMd)}: missing metadata provenance`);
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
for (const key of PROVENANCE_KEYS) {
|
|
432
|
+
if (typeof fm.metadata[key] !== 'string' || fm.metadata[key].length === 0) {
|
|
433
|
+
errors.push(`${repoRelative(skillMd)}: missing metadata.${key}`);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const expectedSkill = expectedByName.get(parentName);
|
|
438
|
+
if (!expectedSkill) {
|
|
439
|
+
errors.push(`${repoRelative(skillMd)}: no matching canonical skill`);
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
if (fm.metadata.skill_graph_canonical_skill !== expectedSkill.canonicalSkillPath) {
|
|
443
|
+
errors.push(
|
|
444
|
+
`${repoRelative(skillMd)}: metadata.skill_graph_canonical_skill must be ${expectedSkill.canonicalSkillPath}`
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
for (const expectedName of expectedByName.keys()) {
|
|
450
|
+
if (!seen.has(expectedName)) errors.push(`missing exported skill ${expectedName}`);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
for (const finding of privacyFindings) {
|
|
454
|
+
errors.push(`${finding.file}:${finding.line}: privacy ${finding.id}: ${finding.message} (${finding.match})`);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
for (const failure of markdownFailures) {
|
|
458
|
+
errors.push(`${failure.file}:${failure.line}: markdown link ${failure.message} (${failure.target})`);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return {
|
|
462
|
+
ok: errors.length === 0,
|
|
463
|
+
errors,
|
|
464
|
+
skillCount: skillFiles.length,
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function checkSurface(outputRoot, expectedFiles) {
|
|
469
|
+
const errors = [];
|
|
470
|
+
const expectedPaths = new Set([...expectedFiles.keys()].map(filePath => path.resolve(filePath)));
|
|
471
|
+
|
|
472
|
+
for (const [filePath, expectedText] of expectedFiles) {
|
|
473
|
+
if (!fs.existsSync(filePath)) {
|
|
474
|
+
errors.push(`missing generated file ${repoRelative(filePath)}`);
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
const actual = fs.readFileSync(filePath, 'utf8');
|
|
478
|
+
if (actual !== expectedText) {
|
|
479
|
+
errors.push(`stale generated file ${repoRelative(filePath)}`);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
if (fs.existsSync(outputRoot)) {
|
|
484
|
+
const stack = [outputRoot];
|
|
485
|
+
while (stack.length > 0) {
|
|
486
|
+
const dir = stack.pop();
|
|
487
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
488
|
+
const abs = path.join(dir, entry.name);
|
|
489
|
+
if (entry.isDirectory()) {
|
|
490
|
+
stack.push(abs);
|
|
491
|
+
} else if (!expectedPaths.has(path.resolve(abs))) {
|
|
492
|
+
errors.push(`unexpected generated file ${repoRelative(abs)}`);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
return errors;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function parseArgs(argv) {
|
|
502
|
+
const options = {
|
|
503
|
+
outputRoot: DEFAULT_OUTPUT_ROOT,
|
|
504
|
+
check: false,
|
|
505
|
+
validateOnly: false,
|
|
506
|
+
json: false,
|
|
507
|
+
quiet: false,
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
for (let i = 0; i < argv.length; i++) {
|
|
511
|
+
const arg = argv[i];
|
|
512
|
+
if (arg === '--help' || arg === '-h') {
|
|
513
|
+
options.help = true;
|
|
514
|
+
} else if (arg === '--check') {
|
|
515
|
+
options.check = true;
|
|
516
|
+
} else if (arg === '--validate-only') {
|
|
517
|
+
options.validateOnly = true;
|
|
518
|
+
} else if (arg === '--json') {
|
|
519
|
+
options.json = true;
|
|
520
|
+
} else if (arg === '--quiet') {
|
|
521
|
+
options.quiet = true;
|
|
522
|
+
} else if (arg === '--output') {
|
|
523
|
+
if (!argv[i + 1]) throw new Error('--output requires a path');
|
|
524
|
+
options.outputRoot = path.resolve(argv[++i]);
|
|
525
|
+
} else {
|
|
526
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
return options;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function printHelp() {
|
|
534
|
+
process.stdout.write(`Usage: node scripts/export-marketplace-skills.js [options]
|
|
535
|
+
|
|
536
|
+
Options:
|
|
537
|
+
--output <dir> Marketplace output root. Default: marketplace
|
|
538
|
+
--check Do not write; fail if generated files are missing or stale
|
|
539
|
+
--validate-only Validate an existing generated surface only
|
|
540
|
+
--json Print JSON summary
|
|
541
|
+
--quiet Suppress success text
|
|
542
|
+
--help Show this help
|
|
543
|
+
`);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function main() {
|
|
547
|
+
try {
|
|
548
|
+
const options = parseArgs(process.argv.slice(2));
|
|
549
|
+
if (options.help) {
|
|
550
|
+
printHelp();
|
|
551
|
+
process.exit(0);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const outputRoot = assertSafeOutputRoot(options.outputRoot);
|
|
555
|
+
const expected = options.validateOnly ? null : expectedSurfaceFiles(outputRoot);
|
|
556
|
+
const errors = [];
|
|
557
|
+
|
|
558
|
+
if (options.check) {
|
|
559
|
+
errors.push(...checkSurface(outputRoot, expected.files));
|
|
560
|
+
} else if (!options.validateOnly) {
|
|
561
|
+
writeSurface(outputRoot, expected.files);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const validation = validateGeneratedSurface(outputRoot, expected ? expected.skills : null);
|
|
565
|
+
errors.push(...validation.errors);
|
|
566
|
+
|
|
567
|
+
const result = {
|
|
568
|
+
output: repoRelative(outputRoot),
|
|
569
|
+
canonical_skills: expected ? expected.skills.length : collectCanonicalSkills().length,
|
|
570
|
+
exported_skills: validation.skillCount,
|
|
571
|
+
ok: errors.length === 0,
|
|
572
|
+
errors,
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
if (options.json) {
|
|
576
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
577
|
+
} else if (!options.quiet) {
|
|
578
|
+
for (const error of errors) process.stderr.write(`FAIL ${error}\n`);
|
|
579
|
+
if (errors.length === 0) {
|
|
580
|
+
const mode = options.check ? 'checked' : options.validateOnly ? 'validated' : 'generated';
|
|
581
|
+
process.stdout.write(`OK marketplace export ${mode}: ${validation.skillCount} skill(s) in ${repoRelative(outputRoot)}\n`);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
process.exit(errors.length > 0 ? 1 : 0);
|
|
586
|
+
} catch (error) {
|
|
587
|
+
process.stderr.write(`ERROR ${error.message}\n`);
|
|
588
|
+
process.exit(1);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
module.exports = {
|
|
593
|
+
EXPORT_DESCRIPTION_OVERRIDES,
|
|
594
|
+
MARKETPLACE_DESCRIPTION_LIMIT,
|
|
595
|
+
PRIVACY_PATTERNS,
|
|
596
|
+
PROVENANCE_KEYS,
|
|
597
|
+
RELEASE_TARGET_REPO,
|
|
598
|
+
SKILL_GRAPH_PROTOCOL,
|
|
599
|
+
SKILL_GRAPH_PROJECT,
|
|
600
|
+
SKILL_GRAPH_SOURCE_REPO,
|
|
601
|
+
buildMarketplaceSkillText,
|
|
602
|
+
collectCanonicalSkills,
|
|
603
|
+
exportDescriptionForSkill,
|
|
604
|
+
provenanceForSkill,
|
|
605
|
+
rewriteLocalMarkdownLinksToCanonicalRepo,
|
|
606
|
+
scanPrivacyText,
|
|
607
|
+
validateGeneratedSurface,
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
if (require.main === module) main();
|