@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,979 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Protocol consistency checker for Skill Graph.
|
|
4
|
+
*
|
|
5
|
+
* Runs 8 machine-detectable checks across the repo's protocol artifacts.
|
|
6
|
+
* These checks are complementary to skill-lint.js, which validates per-skill
|
|
7
|
+
* schema correctness. This script validates cross-artifact consistency:
|
|
8
|
+
* does the field reference doc match the schema? Does the manifest field mapping
|
|
9
|
+
* accurately describe what the generator does? Is the sample manifest correct?
|
|
10
|
+
*
|
|
11
|
+
* The 8 checks:
|
|
12
|
+
* C1 -- Field-set parity: docs/field-reference.md section headers vs
|
|
13
|
+
* schemas/skill.schema.json top-level properties.
|
|
14
|
+
* C2 -- Authored-to-generated parity: every skill.schema.json property either
|
|
15
|
+
* appears in manifest.schema.json (possibly grouped) or is listed as
|
|
16
|
+
* intentional loss in docs/manifest-field-mapping.md.
|
|
17
|
+
* C3 -- Artifact-root convention: detect when a shipped audit example (one that
|
|
18
|
+
* exists under examples/audits/) is referenced by a bare "audits/<skill>/"
|
|
19
|
+
* path in docs, conflicting with the canonical "examples/audits/<skill>/"
|
|
20
|
+
* root. Warns on conflicts; the two-tier consumer convention itself is
|
|
21
|
+
* intentional and is not flagged.
|
|
22
|
+
* C4 -- Sample manifest correctness: examples/skills.manifest.sample.json
|
|
23
|
+
* validates against schemas/manifest.schema.json AND summary.total_skills
|
|
24
|
+
* equals skills.length.
|
|
25
|
+
* C5 -- Example truth invariants:
|
|
26
|
+
* C5a: No worked scorecard claims 'exports cleanly to all' when portability
|
|
27
|
+
* targets are still aspirational (without a qualifying phrase).
|
|
28
|
+
* C5b: No eval artifact uses "eval_status" as a JSON key (deprecated v1
|
|
29
|
+
* field; post-SH-5784 use eval_artifacts, eval_state, routing_eval).
|
|
30
|
+
* C5c: Scorecard portability rows must not use v1 sub-field names
|
|
31
|
+
* ("level" or "exports") -- use v2 names "readiness" and "targets".
|
|
32
|
+
* C6 -- Versioned schema parity: schemas/skill.v2.schema.json and
|
|
33
|
+
* schemas/manifest.v2.schema.json must be content-identical to the
|
|
34
|
+
* unversioned schemas/skill.schema.json and schemas/manifest.schema.json,
|
|
35
|
+
* modulo $id and title. Drift between them breaks the pinning promise
|
|
36
|
+
* documented in docs/skill-metadata-protocol.md § Schema Versioning Policy.
|
|
37
|
+
* C7 -- Generated field-reference parity: docs/field-reference.generated.md
|
|
38
|
+
* must match live regeneration from the current pinned skill schema.
|
|
39
|
+
* C8 -- JSON-LD context coverage: every top-level authored schema field must
|
|
40
|
+
* appear in schemas/skill.context.jsonld and every compact IRI prefix
|
|
41
|
+
* used there must be declared.
|
|
42
|
+
*
|
|
43
|
+
* Usage:
|
|
44
|
+
* node scripts/check-protocol-consistency.js
|
|
45
|
+
* node scripts/check-protocol-consistency.js --verbose
|
|
46
|
+
*
|
|
47
|
+
* Self-contained. Only uses Node built-ins -- no external dependencies.
|
|
48
|
+
* Exit 0 on success, 1 on any check failure.
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
'use strict';
|
|
52
|
+
|
|
53
|
+
const fs = require('fs');
|
|
54
|
+
const path = require('path');
|
|
55
|
+
const { parseFrontmatter } = require('./lib/parse-frontmatter');
|
|
56
|
+
|
|
57
|
+
const REPO_ROOT = path.resolve(__dirname, '..');
|
|
58
|
+
const VERBOSE = process.argv.includes('--verbose');
|
|
59
|
+
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Helpers
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
function readJson(filePath) {
|
|
65
|
+
try {
|
|
66
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
67
|
+
} catch (e) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function readText(filePath) {
|
|
73
|
+
try {
|
|
74
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
75
|
+
} catch (e) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Extract all ## heading texts from a Markdown document. */
|
|
81
|
+
function extractH2Headings(text) {
|
|
82
|
+
const headings = [];
|
|
83
|
+
for (const line of text.split(/\r?\n/)) {
|
|
84
|
+
const m = line.match(/^##\s+(.+)$/);
|
|
85
|
+
if (m) headings.push(m[1].trim());
|
|
86
|
+
}
|
|
87
|
+
return headings;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Collect all .md files recursively under a directory. */
|
|
91
|
+
function collectMdFiles(dir) {
|
|
92
|
+
const results = [];
|
|
93
|
+
if (!fs.existsSync(dir)) return results;
|
|
94
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
95
|
+
const full = path.join(dir, entry);
|
|
96
|
+
const stat = fs.statSync(full);
|
|
97
|
+
if (stat.isDirectory()) {
|
|
98
|
+
results.push(...collectMdFiles(full));
|
|
99
|
+
} else if (entry.endsWith('.md')) {
|
|
100
|
+
results.push(full);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return results;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Minimal JSON Schema validator -- reused from generate-manifest.js. */
|
|
107
|
+
function validate(value, schema, pointer) {
|
|
108
|
+
if (pointer === undefined) pointer = '#';
|
|
109
|
+
const errors = [];
|
|
110
|
+
if (!schema || typeof schema !== 'object') return errors;
|
|
111
|
+
|
|
112
|
+
if (schema.type) {
|
|
113
|
+
const types = Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
114
|
+
const matchesType = (t) => {
|
|
115
|
+
if (t === 'null') return value === null;
|
|
116
|
+
if (t === 'array') return Array.isArray(value);
|
|
117
|
+
if (t === 'integer') return typeof value === 'number' && Number.isInteger(value);
|
|
118
|
+
if (t === 'number') return typeof value === 'number';
|
|
119
|
+
if (t === 'object') return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
120
|
+
return typeof value === t;
|
|
121
|
+
};
|
|
122
|
+
if (!types.some(matchesType)) {
|
|
123
|
+
const actualType = value === null ? 'null' : Array.isArray(value) ? 'array' : typeof value;
|
|
124
|
+
errors.push(`${pointer}: expected type ${schema.type}, got ${actualType}`);
|
|
125
|
+
return errors;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (schema.const !== undefined && value !== schema.const) {
|
|
130
|
+
errors.push(`${pointer}: expected const ${JSON.stringify(schema.const)}, got ${JSON.stringify(value)}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (schema.enum && !schema.enum.includes(value)) {
|
|
134
|
+
errors.push(`${pointer}: value ${JSON.stringify(value)} not in enum [${schema.enum.map(e => JSON.stringify(e)).join(', ')}]`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (schema.pattern && typeof value === 'string') {
|
|
138
|
+
if (!new RegExp(schema.pattern).test(value)) {
|
|
139
|
+
errors.push(`${pointer}: "${value}" does not match pattern ${schema.pattern}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (schema.format === 'date' && typeof value === 'string') {
|
|
144
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
|
145
|
+
errors.push(`${pointer}: "${value}" is not a valid date (expected YYYY-MM-DD)`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (schema.format === 'date-time' && typeof value === 'string') {
|
|
149
|
+
if (isNaN(Date.parse(value))) {
|
|
150
|
+
errors.push(`${pointer}: "${value}" is not a valid date-time`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (schema.minimum !== undefined && typeof value === 'number' && value < schema.minimum) {
|
|
155
|
+
errors.push(`${pointer}: ${value} < minimum ${schema.minimum}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (schema.oneOf) {
|
|
159
|
+
const matchCount = schema.oneOf.filter(sub => validate(value, sub, pointer + '/oneOf').length === 0).length;
|
|
160
|
+
if (matchCount !== 1) {
|
|
161
|
+
errors.push(`${pointer}: does not match exactly one of the oneOf variants (matched ${matchCount})`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
166
|
+
const props = schema.properties || {};
|
|
167
|
+
const required = schema.required || [];
|
|
168
|
+
for (const req of required) {
|
|
169
|
+
if (!(req in value)) {
|
|
170
|
+
errors.push(`${pointer}/${req}: missing required field`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (schema.additionalProperties === false) {
|
|
174
|
+
for (const key of Object.keys(value)) {
|
|
175
|
+
if (!(key in props)) {
|
|
176
|
+
errors.push(`${pointer}/${key}: additional property not allowed`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
for (const [key, subValue] of Object.entries(value)) {
|
|
181
|
+
if (props[key]) {
|
|
182
|
+
errors.push(...validate(subValue, props[key], `${pointer}/${key}`));
|
|
183
|
+
} else if (schema.additionalProperties && typeof schema.additionalProperties === 'object') {
|
|
184
|
+
errors.push(...validate(subValue, schema.additionalProperties, `${pointer}/${key}`));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (Array.isArray(value)) {
|
|
190
|
+
if (schema.items) {
|
|
191
|
+
for (let i = 0; i < value.length; i++) {
|
|
192
|
+
errors.push(...validate(value[i], schema.items, `${pointer}/${i}`));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return errors;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
// Check C1 -- Field-set parity
|
|
202
|
+
//
|
|
203
|
+
// The ## headings in docs/field-reference.md are the authoritative list of
|
|
204
|
+
// authored fields. The top-level properties of schemas/skill.schema.json are
|
|
205
|
+
// the schema's field list. Both sets must be identical.
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
|
|
208
|
+
function checkC1FieldSetParity() {
|
|
209
|
+
const errors = [];
|
|
210
|
+
const refPath = path.join(REPO_ROOT, 'docs', 'field-reference.md');
|
|
211
|
+
const schemaPath = path.join(REPO_ROOT, 'schemas', 'skill.schema.json');
|
|
212
|
+
|
|
213
|
+
const refText = readText(refPath);
|
|
214
|
+
if (!refText) {
|
|
215
|
+
errors.push('C1 [docs/field-reference.md]: cannot read file -- field-set parity check skipped');
|
|
216
|
+
return errors;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const schema = readJson(schemaPath);
|
|
220
|
+
if (!schema || !schema.properties) {
|
|
221
|
+
errors.push('C1 [schemas/skill.schema.json]: cannot read schema or no "properties" key -- field-set parity check skipped');
|
|
222
|
+
return errors;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Extract ## heading backtick-wrapped field names like ## `schema_version`
|
|
226
|
+
const headings = extractH2Headings(refText);
|
|
227
|
+
const docFields = new Set();
|
|
228
|
+
for (const h of headings) {
|
|
229
|
+
const m = h.match(/^`([^`]+)`$/);
|
|
230
|
+
if (m) docFields.add(m[1]);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const schemaFields = new Set(Object.keys(schema.properties));
|
|
234
|
+
|
|
235
|
+
const inSchemaNotDoc = [...schemaFields].filter(f => !docFields.has(f));
|
|
236
|
+
const inDocNotSchema = [...docFields].filter(f => !schemaFields.has(f));
|
|
237
|
+
|
|
238
|
+
if (inSchemaNotDoc.length > 0) {
|
|
239
|
+
errors.push(
|
|
240
|
+
`C1 [docs/field-reference.md vs schemas/skill.schema.json]: ` +
|
|
241
|
+
`${inSchemaNotDoc.length} schema field(s) not documented in field-reference.md: ` +
|
|
242
|
+
inSchemaNotDoc.map(f => `"${f}"`).join(', ')
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
if (inDocNotSchema.length > 0) {
|
|
246
|
+
errors.push(
|
|
247
|
+
`C1 [docs/field-reference.md vs schemas/skill.schema.json]: ` +
|
|
248
|
+
`${inDocNotSchema.length} field(s) documented in field-reference.md but absent from schema: ` +
|
|
249
|
+
inDocNotSchema.map(f => `"${f}"`).join(', ')
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (VERBOSE && errors.length === 0) {
|
|
254
|
+
console.log(` C1: OK -- ${docFields.size} fields documented, ${schemaFields.size} in schema, sets match`);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return errors;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ---------------------------------------------------------------------------
|
|
261
|
+
// Check C2 -- Authored-to-generated parity
|
|
262
|
+
//
|
|
263
|
+
// Every property in schemas/skill.schema.json must either:
|
|
264
|
+
// (a) appear in schemas/manifest.schema.json (at top level or grouped under
|
|
265
|
+
// a parent like `activation`, `health`), OR
|
|
266
|
+
// (b) appear in the rename map table in docs/manifest-field-mapping.md, OR
|
|
267
|
+
// (c) be listed as intentional loss in the dropped-field list of
|
|
268
|
+
// docs/manifest-field-mapping.md.
|
|
269
|
+
//
|
|
270
|
+
// The rename map is parsed from the "Top-level authored fields" table.
|
|
271
|
+
// ---------------------------------------------------------------------------
|
|
272
|
+
|
|
273
|
+
function checkC2AuthoredToGeneratedParity() {
|
|
274
|
+
const errors = [];
|
|
275
|
+
const skillSchemaPath = path.join(REPO_ROOT, 'schemas', 'skill.schema.json');
|
|
276
|
+
const manifestSchemaPath = path.join(REPO_ROOT, 'schemas', 'manifest.schema.json');
|
|
277
|
+
const mappingPath = path.join(REPO_ROOT, 'docs', 'manifest-field-mapping.md');
|
|
278
|
+
|
|
279
|
+
const skillSchema = readJson(skillSchemaPath);
|
|
280
|
+
if (!skillSchema || !skillSchema.properties) {
|
|
281
|
+
errors.push('C2 [schemas/skill.schema.json]: cannot read schema -- authored-to-generated parity check skipped');
|
|
282
|
+
return errors;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const manifestSchema = readJson(manifestSchemaPath);
|
|
286
|
+
if (!manifestSchema) {
|
|
287
|
+
errors.push('C2 [schemas/manifest.schema.json]: cannot read schema -- authored-to-generated parity check skipped');
|
|
288
|
+
return errors;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const mappingText = readText(mappingPath);
|
|
292
|
+
if (!mappingText) {
|
|
293
|
+
errors.push('C2 [docs/manifest-field-mapping.md]: cannot read file -- authored-to-generated parity check skipped');
|
|
294
|
+
return errors;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Build the set of manifest field names -- top-level and grouped under skill items
|
|
298
|
+
const manifestFieldNames = new Set();
|
|
299
|
+
|
|
300
|
+
// From manifest schema skill item properties
|
|
301
|
+
const skillItemSchema =
|
|
302
|
+
manifestSchema.properties &&
|
|
303
|
+
manifestSchema.properties.skills &&
|
|
304
|
+
manifestSchema.properties.skills.items;
|
|
305
|
+
if (skillItemSchema && skillItemSchema.properties) {
|
|
306
|
+
for (const k of Object.keys(skillItemSchema.properties)) {
|
|
307
|
+
manifestFieldNames.add(k);
|
|
308
|
+
// Also collect sub-properties of grouped objects (activation, health, etc.)
|
|
309
|
+
const sub = skillItemSchema.properties[k];
|
|
310
|
+
if (sub && sub.properties) {
|
|
311
|
+
for (const sk of Object.keys(sub.properties)) {
|
|
312
|
+
manifestFieldNames.add(sk);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Parse the rename map table from manifest-field-mapping.md.
|
|
319
|
+
// Table rows look like: | N | `authored_field` | fate | manifest projection |
|
|
320
|
+
const renameMapFields = new Set();
|
|
321
|
+
const droppedFields = new Set();
|
|
322
|
+
|
|
323
|
+
let inRenameMap = false;
|
|
324
|
+
let inDroppedList = false;
|
|
325
|
+
for (const line of mappingText.split('\n')) {
|
|
326
|
+
if (/^##+ Top-level authored fields/.test(line)) {
|
|
327
|
+
inRenameMap = true;
|
|
328
|
+
inDroppedList = false;
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
if (/^##+ (Generated-only|Loss Policy|Migration|Worked Example|Verification)/.test(line)) {
|
|
332
|
+
inRenameMap = false;
|
|
333
|
+
}
|
|
334
|
+
if (/Current dropped-field list/.test(line)) {
|
|
335
|
+
inDroppedList = true;
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
if (inDroppedList && /^###/.test(line)) {
|
|
339
|
+
inDroppedList = false;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (inRenameMap && line.startsWith('|')) {
|
|
343
|
+
// Column 2 (index 2 after splitting on '|') holds the authored field name
|
|
344
|
+
const cols = line.split('|').map(c => c.trim());
|
|
345
|
+
if (cols.length >= 3) {
|
|
346
|
+
const cell = cols[2];
|
|
347
|
+
const m = cell.match(/^`([^`]+)`/);
|
|
348
|
+
if (m) renameMapFields.add(m[1]);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
if (inDroppedList && line.startsWith('|')) {
|
|
352
|
+
const cols = line.split('|').map(c => c.trim());
|
|
353
|
+
if (cols.length >= 2) {
|
|
354
|
+
const cell = cols[1];
|
|
355
|
+
const m = cell.match(/^`([^`]+)`/);
|
|
356
|
+
if (m && m[1] !== 'Field') droppedFields.add(m[1]);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// For each authored field, verify coverage
|
|
362
|
+
const authored = Object.keys(skillSchema.properties);
|
|
363
|
+
const uncovered = [];
|
|
364
|
+
for (const field of authored) {
|
|
365
|
+
const inManifest = manifestFieldNames.has(field);
|
|
366
|
+
const inRenameMapTable = renameMapFields.has(field);
|
|
367
|
+
const inDropped = droppedFields.has(field);
|
|
368
|
+
if (!inManifest && !inRenameMapTable && !inDropped) {
|
|
369
|
+
uncovered.push(field);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (uncovered.length > 0) {
|
|
374
|
+
errors.push(
|
|
375
|
+
`C2 [schemas/skill.schema.json -> schemas/manifest.schema.json]: ` +
|
|
376
|
+
`${uncovered.length} authored field(s) not covered by manifest schema, rename map, or loss policy: ` +
|
|
377
|
+
uncovered.map(f => `"${f}"`).join(', ')
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (VERBOSE && errors.length === 0) {
|
|
382
|
+
console.log(
|
|
383
|
+
` C2: OK -- ${authored.length} authored fields, all covered ` +
|
|
384
|
+
`(${manifestFieldNames.size} in manifest, ${renameMapFields.size} in rename map, ${droppedFields.size} dropped)`
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return errors;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// ---------------------------------------------------------------------------
|
|
392
|
+
// Check C3 -- Artifact-root convention
|
|
393
|
+
//
|
|
394
|
+
// Skill Graph uses a two-tier artifact root convention (documented in
|
|
395
|
+
// SKILL_AUDIT_LOOP.md after commit 873c463):
|
|
396
|
+
// - examples/audits/<skill>/ -- shipped, curated worked examples in this repo
|
|
397
|
+
// - audits/<skill>/ -- downstream consumer output (adopters' own repos)
|
|
398
|
+
//
|
|
399
|
+
// A conflict occurs when a doc describes a SHIPPED EXAMPLE living at the bare
|
|
400
|
+
// "audits/<skill>/" root instead of "examples/audits/<skill>/".
|
|
401
|
+
//
|
|
402
|
+
// We detect this by finding docs that reference a bare "audits/<skill>/" path
|
|
403
|
+
// for a skill that is known to have a shipped example under examples/audits/.
|
|
404
|
+
// The bare-path convention for consumer output is intentional and is NOT flagged.
|
|
405
|
+
//
|
|
406
|
+
// Excluded: docs/plans/ (describes script behavior, uses consumer paths by design)
|
|
407
|
+
// Lines that explicitly document the two-tier convention
|
|
408
|
+
// Fenced code blocks
|
|
409
|
+
//
|
|
410
|
+
// This is a WARN-level check -- it does not cause exit(1) by itself.
|
|
411
|
+
// ---------------------------------------------------------------------------
|
|
412
|
+
|
|
413
|
+
function checkC3ArtifactRootConvention() {
|
|
414
|
+
const warnings = [];
|
|
415
|
+
|
|
416
|
+
// Discover which skill names have shipped audit examples
|
|
417
|
+
const examplesAuditsDir = path.join(REPO_ROOT, 'examples', 'audits');
|
|
418
|
+
const shippedAuditSkills = new Set();
|
|
419
|
+
if (fs.existsSync(examplesAuditsDir)) {
|
|
420
|
+
for (const entry of fs.readdirSync(examplesAuditsDir)) {
|
|
421
|
+
const stat = fs.statSync(path.join(examplesAuditsDir, entry));
|
|
422
|
+
if (stat.isDirectory()) shippedAuditSkills.add(entry);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (shippedAuditSkills.size === 0) {
|
|
427
|
+
if (VERBOSE) console.log(' C3: OK -- no shipped audit examples found in examples/audits/');
|
|
428
|
+
return warnings;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Scan docs/ but exclude plans/ (those describe consumer script output paths)
|
|
432
|
+
const docsDir = path.join(REPO_ROOT, 'docs');
|
|
433
|
+
const mdFiles = collectMdFiles(docsDir).filter(f => {
|
|
434
|
+
const rel = path.relative(docsDir, f);
|
|
435
|
+
return !rel.startsWith('plans' + path.sep);
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
for (const filePath of mdFiles) {
|
|
439
|
+
const text = readText(filePath);
|
|
440
|
+
if (!text) continue;
|
|
441
|
+
const rel = path.relative(REPO_ROOT, filePath);
|
|
442
|
+
|
|
443
|
+
const lines = text.split('\n');
|
|
444
|
+
let inFencedBlock = false;
|
|
445
|
+
for (let i = 0; i < lines.length; i++) {
|
|
446
|
+
const line = lines[i];
|
|
447
|
+
|
|
448
|
+
if (line.trim().startsWith('```')) {
|
|
449
|
+
inFencedBlock = !inFencedBlock;
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
if (inFencedBlock) continue;
|
|
453
|
+
|
|
454
|
+
// Skip lines that explicitly document the two-tier convention
|
|
455
|
+
if (/two-tier|downstream consumer|adopter/i.test(line)) continue;
|
|
456
|
+
|
|
457
|
+
// Flag: bare "audits/<shipped-skill>/" not preceded by "examples/"
|
|
458
|
+
for (const skill of shippedAuditSkills) {
|
|
459
|
+
// Negative lookbehind: not preceded by "examples/"
|
|
460
|
+
const barePattern = new RegExp(`(?<!examples/)audits/${skill}/`);
|
|
461
|
+
if (barePattern.test(line)) {
|
|
462
|
+
warnings.push(
|
|
463
|
+
`C3 [${rel}:${i + 1}]: shipped audit example for skill "${skill}" referenced ` +
|
|
464
|
+
`as bare "audits/${skill}/" -- canonical shipped location is ` +
|
|
465
|
+
`"examples/audits/${skill}/". Line: ${line.trim().slice(0, 100)}`
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (VERBOSE && warnings.length === 0) {
|
|
473
|
+
console.log(' C3: OK -- no artifact-root conflicts found for shipped examples in docs/');
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return warnings;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// ---------------------------------------------------------------------------
|
|
480
|
+
// Check C4 -- Sample manifest correctness
|
|
481
|
+
//
|
|
482
|
+
// examples/skills.manifest.sample.json must:
|
|
483
|
+
// (a) validate against schemas/manifest.schema.json
|
|
484
|
+
// (b) have summary.total_skills === skills.length
|
|
485
|
+
// ---------------------------------------------------------------------------
|
|
486
|
+
|
|
487
|
+
function checkC4SampleManifestCorrectness() {
|
|
488
|
+
const errors = [];
|
|
489
|
+
const samplePath = path.join(REPO_ROOT, 'examples', 'skills.manifest.sample.json');
|
|
490
|
+
const schemaPath = path.join(REPO_ROOT, 'schemas', 'manifest.schema.json');
|
|
491
|
+
|
|
492
|
+
const sample = readJson(samplePath);
|
|
493
|
+
if (!sample) {
|
|
494
|
+
errors.push('C4 [examples/skills.manifest.sample.json]: cannot read file -- sample manifest correctness check skipped');
|
|
495
|
+
return errors;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const schema = readJson(schemaPath);
|
|
499
|
+
if (!schema) {
|
|
500
|
+
errors.push('C4 [schemas/manifest.schema.json]: cannot read schema -- sample manifest correctness check skipped');
|
|
501
|
+
return errors;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// (a) Validate against schema
|
|
505
|
+
const validationErrors = validate(sample, schema);
|
|
506
|
+
for (const e of validationErrors) {
|
|
507
|
+
errors.push(`C4 [examples/skills.manifest.sample.json]: schema validation failed -- ${e}`);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// (b) summary.total_skills === skills.length
|
|
511
|
+
if (typeof sample.summary === 'object' && sample.summary !== null && Array.isArray(sample.skills)) {
|
|
512
|
+
const declared = sample.summary.total_skills;
|
|
513
|
+
const actual = sample.skills.length;
|
|
514
|
+
if (declared !== actual) {
|
|
515
|
+
errors.push(
|
|
516
|
+
`C4 [examples/skills.manifest.sample.json]: ` +
|
|
517
|
+
`summary.total_skills (${declared}) does not equal skills.length (${actual})`
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
} else {
|
|
521
|
+
if (!Array.isArray(sample.skills)) {
|
|
522
|
+
errors.push('C4 [examples/skills.manifest.sample.json]: missing or non-array "skills" field');
|
|
523
|
+
}
|
|
524
|
+
if (typeof sample.summary !== 'object' || sample.summary === null) {
|
|
525
|
+
errors.push('C4 [examples/skills.manifest.sample.json]: missing or non-object "summary" field');
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (VERBOSE && errors.length === 0) {
|
|
530
|
+
console.log(
|
|
531
|
+
` C4: OK -- sample manifest validates against schema; ` +
|
|
532
|
+
`total_skills=${sample.summary.total_skills} matches skills.length=${sample.skills.length}`
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return errors;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// ---------------------------------------------------------------------------
|
|
540
|
+
// Check C5 -- Example truth invariants
|
|
541
|
+
//
|
|
542
|
+
// C5a: No worked scorecard claims "exports cleanly to all" without qualifying
|
|
543
|
+
// that some portability targets are still aspirational. The pre-873c463
|
|
544
|
+
// scorecard said "exports cleanly to all four" (unqualified). The repair
|
|
545
|
+
// added a qualifier. We flag any scorecard that restores the bare form.
|
|
546
|
+
//
|
|
547
|
+
// C5b: No eval artifact uses "eval_status" as a JSON metadata key. This is the
|
|
548
|
+
// deprecated v1 field; post-SH-5784 the correct keys are eval_artifacts,
|
|
549
|
+
// eval_state, and routing_eval. We check JSON object keys -- not prompt text
|
|
550
|
+
// (prompt text may discuss the old field name for educational purposes).
|
|
551
|
+
//
|
|
552
|
+
// C5c: Scorecard portability rows must not use v1 sub-field names ("level" or
|
|
553
|
+
// "exports") -- use v2 names "readiness" and "targets" instead.
|
|
554
|
+
// ---------------------------------------------------------------------------
|
|
555
|
+
|
|
556
|
+
function checkC5ExampleTruthInvariants() {
|
|
557
|
+
const errors = [];
|
|
558
|
+
const auditsDir = path.join(REPO_ROOT, 'examples', 'audits');
|
|
559
|
+
const evalsDir = path.join(REPO_ROOT, 'examples', 'evals');
|
|
560
|
+
|
|
561
|
+
// C5a + C5c -- Scorecard checks
|
|
562
|
+
const scorecardFiles = collectMdFiles(auditsDir).filter(f => f.endsWith('scorecard.md'));
|
|
563
|
+
for (const filePath of scorecardFiles) {
|
|
564
|
+
const text = readText(filePath);
|
|
565
|
+
if (!text) continue;
|
|
566
|
+
const rel = path.relative(REPO_ROOT, filePath);
|
|
567
|
+
|
|
568
|
+
const lines = text.split('\n');
|
|
569
|
+
for (let i = 0; i < lines.length; i++) {
|
|
570
|
+
const line = lines[i];
|
|
571
|
+
|
|
572
|
+
// C5a: "exports cleanly to all" without aspirational qualifier nearby
|
|
573
|
+
if (/exports cleanly to all/i.test(line)) {
|
|
574
|
+
const window = lines.slice(Math.max(0, i - 1), i + 3).join(' ');
|
|
575
|
+
if (!/aspirational|still planned|not yet|pending/i.test(window)) {
|
|
576
|
+
errors.push(
|
|
577
|
+
`C5a [${rel}:${i + 1}]: scorecard claims "exports cleanly to all" without qualifying ` +
|
|
578
|
+
`that some portability targets are still aspirational. ` +
|
|
579
|
+
`Add a qualifier (e.g. "cursor, windsurf, and copilot are still aspirational"). ` +
|
|
580
|
+
`Line: ${line.trim().slice(0, 100)}`
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// C5c: v1 portability sub-field names
|
|
586
|
+
if (/portability\.level|portability\.exports|\blevel:\s*(high|medium|low)\b/i.test(line)) {
|
|
587
|
+
errors.push(
|
|
588
|
+
`C5c [${rel}:${i + 1}]: scorecard uses v1 portability field names ` +
|
|
589
|
+
`("level" or "exports") -- use schema_version 2 names "readiness" and "targets". ` +
|
|
590
|
+
`Line: ${line.trim().slice(0, 100)}`
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// C5b -- Eval artifact JSON key checks
|
|
597
|
+
const evalFiles = [];
|
|
598
|
+
if (fs.existsSync(evalsDir)) {
|
|
599
|
+
for (const entry of fs.readdirSync(evalsDir)) {
|
|
600
|
+
if (entry.endsWith('.json')) {
|
|
601
|
+
evalFiles.push(path.join(evalsDir, entry));
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
for (const filePath of evalFiles) {
|
|
607
|
+
const text = readText(filePath);
|
|
608
|
+
if (!text) continue;
|
|
609
|
+
const rel = path.relative(REPO_ROOT, filePath);
|
|
610
|
+
|
|
611
|
+
// Parse JSON and inspect object keys recursively.
|
|
612
|
+
// We check keys, not string values -- prompt text may reference "eval_status"
|
|
613
|
+
// as an educational or historical reference, which is intentional.
|
|
614
|
+
let evalData;
|
|
615
|
+
try {
|
|
616
|
+
evalData = JSON.parse(text);
|
|
617
|
+
} catch (e) {
|
|
618
|
+
errors.push(`C5b [${rel}]: cannot parse JSON -- ${e.message}`);
|
|
619
|
+
continue;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function findDeprecatedKey(obj, jsonPath) {
|
|
623
|
+
if (Array.isArray(obj)) {
|
|
624
|
+
for (let idx = 0; idx < obj.length; idx++) {
|
|
625
|
+
findDeprecatedKey(obj[idx], `${jsonPath}[${idx}]`);
|
|
626
|
+
}
|
|
627
|
+
} else if (obj !== null && typeof obj === 'object') {
|
|
628
|
+
for (const key of Object.keys(obj)) {
|
|
629
|
+
if (key === 'eval_status') {
|
|
630
|
+
errors.push(
|
|
631
|
+
`C5b [${rel}]: eval artifact metadata uses deprecated v1 JSON key "eval_status" ` +
|
|
632
|
+
`at ${jsonPath}.${key}. ` +
|
|
633
|
+
`Post-SH-5784, use "eval_artifacts", "eval_state", and "routing_eval" instead.`
|
|
634
|
+
);
|
|
635
|
+
} else {
|
|
636
|
+
findDeprecatedKey(obj[key], `${jsonPath}.${key}`);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
findDeprecatedKey(evalData, '$');
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
if (VERBOSE && errors.length === 0) {
|
|
645
|
+
console.log(
|
|
646
|
+
` C5: OK -- ${scorecardFiles.length} scorecard(s), ${evalFiles.length} eval file(s) ` +
|
|
647
|
+
`pass all example truth invariants`
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
return errors;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// ---------------------------------------------------------------------------
|
|
655
|
+
// C6 -- Versioned schema parity (version-aware)
|
|
656
|
+
// ---------------------------------------------------------------------------
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Resolve the current schema version from the unversioned skill schema.
|
|
660
|
+
* Looks at the `schema_version.const` variant in the oneOf or at the top-level
|
|
661
|
+
* const. Returns a number (e.g. 2, 3) or null if it cannot be determined.
|
|
662
|
+
*/
|
|
663
|
+
function resolveCurrentSchemaVersion(skillSchema) {
|
|
664
|
+
const sv = skillSchema && skillSchema.properties && skillSchema.properties.schema_version;
|
|
665
|
+
if (!sv) return null;
|
|
666
|
+
if (Number.isInteger(sv.const)) return sv.const;
|
|
667
|
+
if (Array.isArray(sv.oneOf)) {
|
|
668
|
+
for (const variant of sv.oneOf) {
|
|
669
|
+
if (Number.isInteger(variant.const)) return variant.const;
|
|
670
|
+
if (typeof variant.const === 'string' && /^\d+$/.test(variant.const)) return parseInt(variant.const, 10);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
return null;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* Check C6 — schema parity across the authored / generated / pinned files.
|
|
678
|
+
*
|
|
679
|
+
* The invariants:
|
|
680
|
+
* (a) The unversioned schemas (`skill.schema.json`, `manifest.schema.json`)
|
|
681
|
+
* must be content-identical to the pinned copy of the current version
|
|
682
|
+
* (`skill.v{N}.schema.json`, `manifest.v{N}.schema.json`), modulo
|
|
683
|
+
* `$id` and `title`.
|
|
684
|
+
* (b) All prior pinned versions (v2 when v3 is current, etc.) are treated
|
|
685
|
+
* as FROZEN: they must still exist and be readable, but they are NOT
|
|
686
|
+
* checked against the unversioned files. Freezing them is the whole
|
|
687
|
+
* point of pinning.
|
|
688
|
+
*
|
|
689
|
+
* Returns a list of error strings. Empty array means pass.
|
|
690
|
+
*/
|
|
691
|
+
function checkC6VersionedSchemaParity() {
|
|
692
|
+
const errors = [];
|
|
693
|
+
|
|
694
|
+
const skillUnversioned = readJson(path.join(REPO_ROOT, 'schemas/skill.schema.json'));
|
|
695
|
+
const manifestUnversioned = readJson(path.join(REPO_ROOT, 'schemas/manifest.schema.json'));
|
|
696
|
+
if (!skillUnversioned) { errors.push('schemas/skill.schema.json: missing or unreadable'); return errors; }
|
|
697
|
+
if (!manifestUnversioned) { errors.push('schemas/manifest.schema.json: missing or unreadable'); return errors; }
|
|
698
|
+
|
|
699
|
+
const current = resolveCurrentSchemaVersion(skillUnversioned);
|
|
700
|
+
if (!current) {
|
|
701
|
+
errors.push('schemas/skill.schema.json: cannot determine current schema_version for parity check');
|
|
702
|
+
return errors;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// (a) Current-version pinned copies must match the unversioned files.
|
|
706
|
+
const currentPairs = [
|
|
707
|
+
{ unversioned: 'schemas/skill.schema.json', versioned: `schemas/skill.v${current}.schema.json`, data: skillUnversioned },
|
|
708
|
+
{ unversioned: 'schemas/manifest.schema.json', versioned: `schemas/manifest.v${current}.schema.json`, data: manifestUnversioned },
|
|
709
|
+
];
|
|
710
|
+
|
|
711
|
+
const stripped = (obj) => {
|
|
712
|
+
const copy = JSON.parse(JSON.stringify(obj));
|
|
713
|
+
delete copy.$id;
|
|
714
|
+
delete copy.title;
|
|
715
|
+
return copy;
|
|
716
|
+
};
|
|
717
|
+
|
|
718
|
+
for (const { unversioned, versioned, data } of currentPairs) {
|
|
719
|
+
const v = readJson(path.join(REPO_ROOT, versioned));
|
|
720
|
+
if (!v) { errors.push(`${versioned}: missing — pinned copy of current schema version ${current} must exist`); continue; }
|
|
721
|
+
const uStr = JSON.stringify(stripped(data));
|
|
722
|
+
const vStr = JSON.stringify(stripped(v));
|
|
723
|
+
if (uStr !== vStr) {
|
|
724
|
+
errors.push(`${versioned} is out of sync with ${unversioned} (content differs modulo $id/title). After editing the unversioned schema, copy it to the versioned file and keep the v${current} $id/title.`);
|
|
725
|
+
}
|
|
726
|
+
if (VERBOSE && uStr === vStr) console.log(` ${versioned}: tracks ${unversioned}`);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// (b) Prior-version pinned copies must exist but are not parity-checked.
|
|
730
|
+
for (let v = 2; v < current; v++) {
|
|
731
|
+
for (const kind of ['skill', 'manifest']) {
|
|
732
|
+
const frozenPath = `schemas/${kind}.v${v}.schema.json`;
|
|
733
|
+
if (!fs.existsSync(path.join(REPO_ROOT, frozenPath))) {
|
|
734
|
+
errors.push(`${frozenPath}: missing — frozen prior-version schema must remain in the repo for consumers pinned to v${v}`);
|
|
735
|
+
} else if (VERBOSE) {
|
|
736
|
+
console.log(` ${frozenPath}: frozen (parity not checked)`);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
return errors;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// ---------------------------------------------------------------------------
|
|
745
|
+
// C7 -- Generated field-reference parity
|
|
746
|
+
// ---------------------------------------------------------------------------
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* Check C7 — `docs/field-reference.generated.md` must match live regeneration
|
|
750
|
+
* from the current pinned schema description strings via
|
|
751
|
+
* `scripts/build-field-reference.js`.
|
|
752
|
+
*
|
|
753
|
+
* Invariant: the generated index is a deterministic projection of the schema's
|
|
754
|
+
* description fields. If the schema changes (a description is added, edited, or
|
|
755
|
+
* a new field is introduced) without regenerating the index, the docs drift
|
|
756
|
+
* silently. C7 closes this loop.
|
|
757
|
+
*
|
|
758
|
+
* Implementation strategy: spawn `node scripts/build-field-reference.js --check`
|
|
759
|
+
* which performs the regeneration in-memory and exits non-zero when the live
|
|
760
|
+
* file differs from regenerated output. C7 surfaces that exit signal as a
|
|
761
|
+
* structured protocol-consistency error.
|
|
762
|
+
*
|
|
763
|
+
* Returns a list of error strings. Empty array means pass.
|
|
764
|
+
*/
|
|
765
|
+
function checkC7GeneratedFieldReferenceParity() {
|
|
766
|
+
const errors = [];
|
|
767
|
+
const builderPath = path.join(REPO_ROOT, 'scripts', 'build-field-reference.js');
|
|
768
|
+
const generatedPath = path.join(REPO_ROOT, 'docs', 'field-reference.generated.md');
|
|
769
|
+
|
|
770
|
+
if (!fs.existsSync(builderPath)) {
|
|
771
|
+
errors.push('scripts/build-field-reference.js: missing — required to regenerate docs/field-reference.generated.md');
|
|
772
|
+
return errors;
|
|
773
|
+
}
|
|
774
|
+
if (!fs.existsSync(generatedPath)) {
|
|
775
|
+
errors.push('docs/field-reference.generated.md: missing — run `node scripts/build-field-reference.js` to generate');
|
|
776
|
+
return errors;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
const result = require('child_process').spawnSync(
|
|
780
|
+
process.execPath,
|
|
781
|
+
[builderPath, '--check'],
|
|
782
|
+
{ cwd: REPO_ROOT, encoding: 'utf8' }
|
|
783
|
+
);
|
|
784
|
+
|
|
785
|
+
if (result.error) {
|
|
786
|
+
errors.push(`C7 [docs/field-reference.generated.md]: cannot invoke build-field-reference.js — ${result.error.message}`);
|
|
787
|
+
return errors;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
if (result.status !== 0) {
|
|
791
|
+
errors.push(
|
|
792
|
+
`docs/field-reference.generated.md is out of step with the current skill schema description strings. ` +
|
|
793
|
+
`Run \`node scripts/build-field-reference.js\` to regenerate, then commit the result alongside any schema description edits.`
|
|
794
|
+
);
|
|
795
|
+
if (VERBOSE && result.stderr) {
|
|
796
|
+
console.error(result.stderr.trim());
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
return errors;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// ---------------------------------------------------------------------------
|
|
804
|
+
// C8 -- JSON-LD context coverage
|
|
805
|
+
// ---------------------------------------------------------------------------
|
|
806
|
+
|
|
807
|
+
/**
|
|
808
|
+
* Check C8 - `schemas/skill.context.jsonld` must cover every top-level
|
|
809
|
+
* authored field in `schemas/skill.schema.json`.
|
|
810
|
+
*
|
|
811
|
+
* ADR 0002 makes the JSON-LD context the FAIR interoperability bridge. If a
|
|
812
|
+
* new top-level schema field is added without a context term, RDF consumers
|
|
813
|
+
* silently lose that field. This check turns the prior hand-review rule into a
|
|
814
|
+
* deterministic cross-artifact gate.
|
|
815
|
+
*
|
|
816
|
+
* It also validates compact IRI prefixes used by context mappings. For example,
|
|
817
|
+
* `"keywords": "dcat:keyword"` requires a top-level `"dcat": "..."` namespace
|
|
818
|
+
* declaration in the same `@context` object.
|
|
819
|
+
*/
|
|
820
|
+
function checkC8JsonLdContextCoverage() {
|
|
821
|
+
const errors = [];
|
|
822
|
+
const schemaPath = path.join(REPO_ROOT, 'schemas', 'skill.schema.json');
|
|
823
|
+
const contextPath = path.join(REPO_ROOT, 'schemas', 'skill.context.jsonld');
|
|
824
|
+
|
|
825
|
+
const schema = readJson(schemaPath);
|
|
826
|
+
if (!schema || !schema.properties) {
|
|
827
|
+
errors.push('C8 [schemas/skill.schema.json]: cannot read schema -- JSON-LD context coverage check skipped');
|
|
828
|
+
return errors;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
const contextDoc = readJson(contextPath);
|
|
832
|
+
const context = contextDoc && contextDoc['@context'];
|
|
833
|
+
if (!context || typeof context !== 'object' || Array.isArray(context)) {
|
|
834
|
+
errors.push('C8 [schemas/skill.context.jsonld]: missing or invalid @context object');
|
|
835
|
+
return errors;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
const schemaFields = Object.keys(schema.properties);
|
|
839
|
+
const contextKeys = new Set(Object.keys(context));
|
|
840
|
+
const missing = schemaFields.filter(field => !contextKeys.has(field));
|
|
841
|
+
|
|
842
|
+
if (missing.length > 0) {
|
|
843
|
+
errors.push(
|
|
844
|
+
`C8 [schemas/skill.context.jsonld]: ${missing.length} top-level schema field(s) missing from @context: ` +
|
|
845
|
+
missing.map(f => `"${f}"`).join(', ')
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
const declaredPrefixes = new Set();
|
|
850
|
+
for (const [key, value] of Object.entries(context)) {
|
|
851
|
+
if (typeof value === 'string' && /^https?:\/\//.test(value)) {
|
|
852
|
+
declaredPrefixes.add(key);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
const prefixUses = [];
|
|
857
|
+
function collectCompactIris(value, jsonPath) {
|
|
858
|
+
if (typeof value === 'string') {
|
|
859
|
+
const m = value.match(/^([A-Za-z][A-Za-z0-9_-]*):[^/]/);
|
|
860
|
+
if (m) prefixUses.push({ prefix: m[1], value, jsonPath });
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
if (Array.isArray(value)) {
|
|
864
|
+
value.forEach((item, idx) => collectCompactIris(item, `${jsonPath}[${idx}]`));
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
if (value && typeof value === 'object') {
|
|
868
|
+
for (const [key, subValue] of Object.entries(value)) {
|
|
869
|
+
if (key.startsWith('_')) continue;
|
|
870
|
+
collectCompactIris(subValue, `${jsonPath}.${key}`);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
for (const [key, value] of Object.entries(context)) {
|
|
876
|
+
if (declaredPrefixes.has(key)) continue;
|
|
877
|
+
collectCompactIris(value, `@context.${key}`);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
for (const use of prefixUses) {
|
|
881
|
+
if (!declaredPrefixes.has(use.prefix)) {
|
|
882
|
+
errors.push(
|
|
883
|
+
`C8 [schemas/skill.context.jsonld]: ${use.jsonPath} uses compact IRI ` +
|
|
884
|
+
`"${use.value}" but prefix "${use.prefix}" is not declared in @context`
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
if (VERBOSE && errors.length === 0) {
|
|
890
|
+
console.log(
|
|
891
|
+
` C8: OK -- ${schemaFields.length} top-level schema fields covered; ` +
|
|
892
|
+
`${declaredPrefixes.size} namespace prefix(es) declared`
|
|
893
|
+
);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
return errors;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// ---------------------------------------------------------------------------
|
|
900
|
+
// Main
|
|
901
|
+
// ---------------------------------------------------------------------------
|
|
902
|
+
|
|
903
|
+
function main() {
|
|
904
|
+
console.log('Running protocol consistency checks...\n');
|
|
905
|
+
|
|
906
|
+
const allErrors = [];
|
|
907
|
+
const allWarnings = [];
|
|
908
|
+
|
|
909
|
+
// C1
|
|
910
|
+
process.stdout.write('C1 Field-set parity... ');
|
|
911
|
+
const c1 = checkC1FieldSetParity();
|
|
912
|
+
if (c1.length === 0) console.log('OK');
|
|
913
|
+
else { console.log('FAIL'); for (const e of c1) { console.error(` ERROR ${e}`); } }
|
|
914
|
+
allErrors.push(...c1);
|
|
915
|
+
|
|
916
|
+
// C2
|
|
917
|
+
process.stdout.write('C2 Authored-to-generated parity... ');
|
|
918
|
+
const c2 = checkC2AuthoredToGeneratedParity();
|
|
919
|
+
if (c2.length === 0) console.log('OK');
|
|
920
|
+
else { console.log('FAIL'); for (const e of c2) { console.error(` ERROR ${e}`); } }
|
|
921
|
+
allErrors.push(...c2);
|
|
922
|
+
|
|
923
|
+
// C3 (warnings -- does not affect exit code)
|
|
924
|
+
process.stdout.write('C3 Artifact-root convention... ');
|
|
925
|
+
const c3 = checkC3ArtifactRootConvention();
|
|
926
|
+
if (c3.length === 0) console.log('OK');
|
|
927
|
+
else {
|
|
928
|
+
console.log(`WARN (${c3.length})`);
|
|
929
|
+
for (const w of c3) { console.warn(` WARN ${w}`); }
|
|
930
|
+
}
|
|
931
|
+
allWarnings.push(...c3);
|
|
932
|
+
|
|
933
|
+
// C4
|
|
934
|
+
process.stdout.write('C4 Sample manifest correctness... ');
|
|
935
|
+
const c4 = checkC4SampleManifestCorrectness();
|
|
936
|
+
if (c4.length === 0) console.log('OK');
|
|
937
|
+
else { console.log('FAIL'); for (const e of c4) { console.error(` ERROR ${e}`); } }
|
|
938
|
+
allErrors.push(...c4);
|
|
939
|
+
|
|
940
|
+
// C5
|
|
941
|
+
process.stdout.write('C5 Example truth invariants... ');
|
|
942
|
+
const c5 = checkC5ExampleTruthInvariants();
|
|
943
|
+
if (c5.length === 0) console.log('OK');
|
|
944
|
+
else { console.log('FAIL'); for (const e of c5) { console.error(` ERROR ${e}`); } }
|
|
945
|
+
allErrors.push(...c5);
|
|
946
|
+
|
|
947
|
+
// C6
|
|
948
|
+
process.stdout.write('C6 Versioned schema parity... ');
|
|
949
|
+
const c6 = checkC6VersionedSchemaParity();
|
|
950
|
+
if (c6.length === 0) console.log('OK');
|
|
951
|
+
else { console.log('FAIL'); for (const e of c6) { console.error(` ERROR ${e}`); } }
|
|
952
|
+
allErrors.push(...c6);
|
|
953
|
+
|
|
954
|
+
// C7
|
|
955
|
+
process.stdout.write('C7 Generated field-reference parity... ');
|
|
956
|
+
const c7 = checkC7GeneratedFieldReferenceParity();
|
|
957
|
+
if (c7.length === 0) console.log('OK');
|
|
958
|
+
else { console.log('FAIL'); for (const e of c7) { console.error(` ERROR ${e}`); } }
|
|
959
|
+
allErrors.push(...c7);
|
|
960
|
+
|
|
961
|
+
// C8
|
|
962
|
+
process.stdout.write('C8 JSON-LD context coverage... ');
|
|
963
|
+
const c8 = checkC8JsonLdContextCoverage();
|
|
964
|
+
if (c8.length === 0) console.log('OK');
|
|
965
|
+
else { console.log('FAIL'); for (const e of c8) { console.error(` ERROR ${e}`); } }
|
|
966
|
+
allErrors.push(...c8);
|
|
967
|
+
|
|
968
|
+
console.log('');
|
|
969
|
+
|
|
970
|
+
if (allErrors.length > 0) {
|
|
971
|
+
console.error(`FAIL: ${allErrors.length} error(s) found. ${allWarnings.length} warning(s).`);
|
|
972
|
+
process.exit(1);
|
|
973
|
+
} else {
|
|
974
|
+
console.log(`PASS: all protocol consistency checks passed. ${allWarnings.length} warning(s).`);
|
|
975
|
+
process.exit(0);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
main();
|