@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,483 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* skill-graph drift — the drift sentinel for Skill Metadata Protocol grounding metadata.
|
|
4
|
+
*
|
|
5
|
+
* Walks every skill with a `grounding.truth_sources` list, hashes each source
|
|
6
|
+
* file, and compares against the stored
|
|
7
|
+
* `drift_check.truth_source_hashes` baseline. Reports four states:
|
|
8
|
+
*
|
|
9
|
+
* - DRIFT truth source has changed since last verification
|
|
10
|
+
* - STALE `drift_check.last_verified` is older than lifecycle.stale_after_days
|
|
11
|
+
* - BROKEN a declared local truth source file does not exist
|
|
12
|
+
* - NO_BASELINE truth sources exist but no hashes are recorded yet
|
|
13
|
+
* - EXTERNAL_UNHASHED
|
|
14
|
+
* a URL truth source is valid but not fetched by this tool
|
|
15
|
+
*
|
|
16
|
+
* This tool is the reason `grounding.truth_sources` is in the contract at all.
|
|
17
|
+
* Without an executable drift check, grounding is decorative; with this tool,
|
|
18
|
+
* grounding anchors every skill to evidence and surfaces the moment evidence
|
|
19
|
+
* moves out from under the skill.
|
|
20
|
+
*
|
|
21
|
+
* Usage:
|
|
22
|
+
* node scripts/skill-graph-drift.js # check all skills
|
|
23
|
+
* node scripts/skill-graph-drift.js skills/shopify # check one skill
|
|
24
|
+
* node scripts/skill-graph-drift.js --json # JSON output
|
|
25
|
+
* node scripts/skill-graph-drift.js --record skills/shopify # preview YAML
|
|
26
|
+
* node scripts/skill-graph-drift.js --record --apply skills/shopify # write in place
|
|
27
|
+
*
|
|
28
|
+
* Self-contained. Only uses Node built-ins — no external dependencies.
|
|
29
|
+
* Exit 0 when no DRIFT or BROKEN; 1 otherwise. STALE, NO_BASELINE, and
|
|
30
|
+
* EXTERNAL_UNHASHED are informational and do not fail.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
'use strict';
|
|
34
|
+
|
|
35
|
+
const fs = require('fs');
|
|
36
|
+
const path = require('path');
|
|
37
|
+
const crypto = require('crypto');
|
|
38
|
+
const { parseFrontmatter } = require('./lib/parse-frontmatter');
|
|
39
|
+
const { workspaceRoot, resolveTruthSourcePath } = require('./lib/roots');
|
|
40
|
+
|
|
41
|
+
const REPO_ROOT = workspaceRoot();
|
|
42
|
+
const DEFAULT_SKILLS_DIR = path.join(REPO_ROOT, 'skills');
|
|
43
|
+
const CONFIG_PATH = path.join(REPO_ROOT, '.skill-graph', 'config.json');
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Workspace config (shared with generate-manifest.js)
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
function loadWorkspaceConfig() {
|
|
50
|
+
if (!fs.existsSync(CONFIG_PATH)) return null;
|
|
51
|
+
try {
|
|
52
|
+
const raw = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
|
|
53
|
+
return (raw && raw.workspace) || null;
|
|
54
|
+
} catch (e) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function resolveSkillRoots(workspace) {
|
|
60
|
+
if (!workspace || !Array.isArray(workspace.skill_roots) || workspace.skill_roots.length === 0) {
|
|
61
|
+
return [{ absPath: DEFAULT_SKILLS_DIR, project: null }];
|
|
62
|
+
}
|
|
63
|
+
return workspace.skill_roots
|
|
64
|
+
.map(entry => {
|
|
65
|
+
if (typeof entry === 'string') return { absPath: path.resolve(REPO_ROOT, entry), project: null };
|
|
66
|
+
if (entry && typeof entry === 'object' && typeof entry.path === 'string') {
|
|
67
|
+
return {
|
|
68
|
+
absPath: path.resolve(REPO_ROOT, entry.path),
|
|
69
|
+
project: (typeof entry.project === 'string' && entry.project.length > 0) ? entry.project : null,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
})
|
|
74
|
+
.filter(Boolean);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
// Truth source normalization + hashing
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
function normalizeTruthSource(src) {
|
|
82
|
+
if (typeof src === 'string') {
|
|
83
|
+
return { key: src, path: src, lineRange: null, anchor: null, raw: src };
|
|
84
|
+
}
|
|
85
|
+
if (src && typeof src === 'object' && typeof src.path === 'string') {
|
|
86
|
+
const lineRange = src.line_range && typeof src.line_range === 'object'
|
|
87
|
+
? {
|
|
88
|
+
start: Number.isInteger(src.line_range.start) ? src.line_range.start : null,
|
|
89
|
+
end: Number.isInteger(src.line_range.end) ? src.line_range.end : null,
|
|
90
|
+
}
|
|
91
|
+
: null;
|
|
92
|
+
const anchor = typeof src.anchor === 'string' && src.anchor.length > 0 ? src.anchor : null;
|
|
93
|
+
let key = src.path;
|
|
94
|
+
if (lineRange && lineRange.start) {
|
|
95
|
+
key += `#L${lineRange.start}-L${lineRange.end || lineRange.start}`;
|
|
96
|
+
} else if (anchor) {
|
|
97
|
+
key += `#${anchor}`;
|
|
98
|
+
}
|
|
99
|
+
return { key, path: src.path, lineRange, anchor, raw: src };
|
|
100
|
+
}
|
|
101
|
+
return { key: String(src), path: null, lineRange: null, anchor: null, raw: src, malformed: true };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function hashContent(content) {
|
|
105
|
+
return crypto.createHash('sha256').update(content).digest('hex');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function isRemoteTruthSourcePath(value) {
|
|
109
|
+
return /^https?:\/\//i.test(String(value));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function slugifyHeading(headingText) {
|
|
113
|
+
return headingText
|
|
114
|
+
.replace(/^#+\s*/, '')
|
|
115
|
+
.toLowerCase()
|
|
116
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
117
|
+
.replace(/-+/g, '-')
|
|
118
|
+
.replace(/^-|-$/g, '');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function sectionForHeadingAnchor(text, anchor) {
|
|
122
|
+
const lines = text.split('\n');
|
|
123
|
+
let start = -1;
|
|
124
|
+
let level = null;
|
|
125
|
+
for (let i = 0; i < lines.length; i++) {
|
|
126
|
+
const m = lines[i].match(/^(#{1,6})\s+(.+?)\s*$/);
|
|
127
|
+
if (m && slugifyHeading(m[2]) === anchor) {
|
|
128
|
+
start = i;
|
|
129
|
+
level = m[1].length;
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (start === -1) return null;
|
|
134
|
+
let end = lines.length;
|
|
135
|
+
for (let i = start + 1; i < lines.length; i++) {
|
|
136
|
+
const m = lines[i].match(/^(#{1,6})\s+/);
|
|
137
|
+
if (m && m[1].length <= level) {
|
|
138
|
+
end = i;
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return lines.slice(start, end).join('\n');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function sha256TruthSource(src, skillRoots = []) {
|
|
146
|
+
const normalized = normalizeTruthSource(src);
|
|
147
|
+
if (normalized.malformed || !normalized.path) {
|
|
148
|
+
return { normalized, hash: null, error: 'malformed truth source' };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (isRemoteTruthSourcePath(normalized.path)) {
|
|
152
|
+
return {
|
|
153
|
+
normalized,
|
|
154
|
+
hash: null,
|
|
155
|
+
error: 'external URL not hashed by zero-dependency drift sentinel',
|
|
156
|
+
external: true,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const absPath = resolveTruthSourcePath(normalized.path, REPO_ROOT, skillRoots);
|
|
161
|
+
if (!fs.existsSync(absPath)) {
|
|
162
|
+
return { normalized, hash: null, error: 'file not found' };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const text = fs.readFileSync(absPath, 'utf8').replace(/\r\n?/g, '\n');
|
|
166
|
+
let content = text;
|
|
167
|
+
|
|
168
|
+
if (normalized.lineRange && normalized.lineRange.start) {
|
|
169
|
+
const lines = text.split('\n');
|
|
170
|
+
const start = normalized.lineRange.start;
|
|
171
|
+
const end = normalized.lineRange.end || start;
|
|
172
|
+
if (start < 1 || end < start || end > lines.length) {
|
|
173
|
+
return { normalized, hash: null, error: `line range ${start}-${end} out of bounds` };
|
|
174
|
+
}
|
|
175
|
+
content = lines.slice(start - 1, end).join('\n');
|
|
176
|
+
} else if (normalized.anchor) {
|
|
177
|
+
const section = sectionForHeadingAnchor(text, normalized.anchor);
|
|
178
|
+
if (section !== null) {
|
|
179
|
+
content = section;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (normalized.anchor && !text.includes(normalized.anchor) && sectionForHeadingAnchor(text, normalized.anchor) === null) {
|
|
184
|
+
return { normalized, hash: null, error: `anchor "${normalized.anchor}" not found` };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return { normalized, hash: hashContent(content), error: null };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
// Drift check per skill
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
|
|
194
|
+
function checkSkill(skillMdPath, skillRoots = []) {
|
|
195
|
+
const rel = path.relative(REPO_ROOT, skillMdPath);
|
|
196
|
+
const text = fs.readFileSync(skillMdPath, 'utf8');
|
|
197
|
+
const fm = parseFrontmatter(text);
|
|
198
|
+
|
|
199
|
+
if (!fm) {
|
|
200
|
+
return { skill: rel, status: 'NO_FRONTMATTER', details: 'cannot parse frontmatter', truth_sources: [] };
|
|
201
|
+
}
|
|
202
|
+
const name = fm.name || path.basename(path.dirname(skillMdPath));
|
|
203
|
+
const grounding = fm.grounding;
|
|
204
|
+
if (!grounding || !Array.isArray(grounding.truth_sources) || grounding.truth_sources.length === 0) {
|
|
205
|
+
return { skill: name, path: rel, status: 'UNGROUNDED', details: 'no truth_sources declared', truth_sources: [] };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const driftCheck = fm.drift_check || {};
|
|
209
|
+
const recordedHashes = driftCheck.truth_source_hashes || {};
|
|
210
|
+
const lastVerified = driftCheck.last_verified || null;
|
|
211
|
+
const lifecycle = fm.lifecycle || {};
|
|
212
|
+
|
|
213
|
+
const truthSources = [];
|
|
214
|
+
let anyDrift = false;
|
|
215
|
+
let anyBroken = false;
|
|
216
|
+
let anyMissingHash = false;
|
|
217
|
+
let anyExternal = false;
|
|
218
|
+
|
|
219
|
+
for (const src of grounding.truth_sources) {
|
|
220
|
+
const hashed = sha256TruthSource(src, skillRoots);
|
|
221
|
+
const liveHash = hashed.hash;
|
|
222
|
+
const sourceKey = hashed.normalized.key;
|
|
223
|
+
const recorded = recordedHashes[sourceKey];
|
|
224
|
+
|
|
225
|
+
let entryStatus;
|
|
226
|
+
if (hashed.external) { entryStatus = 'EXTERNAL_UNHASHED'; anyExternal = true; }
|
|
227
|
+
else if (liveHash === null) { entryStatus = 'BROKEN'; anyBroken = true; }
|
|
228
|
+
else if (!recorded) { entryStatus = 'NO_BASELINE'; anyMissingHash = true; }
|
|
229
|
+
else if (liveHash !== recorded) { entryStatus = 'DRIFT'; anyDrift = true; }
|
|
230
|
+
else { entryStatus = 'CLEAN'; }
|
|
231
|
+
|
|
232
|
+
truthSources.push({
|
|
233
|
+
source: sourceKey,
|
|
234
|
+
path: hashed.normalized.path,
|
|
235
|
+
line_range: hashed.normalized.lineRange,
|
|
236
|
+
anchor: hashed.normalized.anchor,
|
|
237
|
+
live_hash: liveHash,
|
|
238
|
+
recorded_hash: recorded || null,
|
|
239
|
+
error: hashed.error,
|
|
240
|
+
status: entryStatus,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Staleness.
|
|
245
|
+
let stale = false;
|
|
246
|
+
let daysSinceVerified = null;
|
|
247
|
+
if (lastVerified && lifecycle.stale_after_days) {
|
|
248
|
+
const a = new Date(lastVerified).getTime();
|
|
249
|
+
const b = Date.now();
|
|
250
|
+
if (!isNaN(a)) {
|
|
251
|
+
daysSinceVerified = Math.floor((b - a) / 86400000);
|
|
252
|
+
stale = daysSinceVerified > lifecycle.stale_after_days;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
let status;
|
|
257
|
+
if (anyDrift) status = 'DRIFT';
|
|
258
|
+
else if (anyBroken) status = 'BROKEN';
|
|
259
|
+
else if (stale) status = 'STALE';
|
|
260
|
+
else if (anyMissingHash) status = 'NO_BASELINE';
|
|
261
|
+
else if (anyExternal) status = 'EXTERNAL_UNHASHED';
|
|
262
|
+
else status = 'CLEAN';
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
skill: name,
|
|
266
|
+
path: rel,
|
|
267
|
+
status,
|
|
268
|
+
stale,
|
|
269
|
+
days_since_verified: daysSinceVerified,
|
|
270
|
+
stale_after_days: lifecycle.stale_after_days || null,
|
|
271
|
+
last_verified: lastVerified,
|
|
272
|
+
truth_sources: truthSources,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ---------------------------------------------------------------------------
|
|
277
|
+
// --record mode: emit a drift_check block with current hashes + today's date.
|
|
278
|
+
//
|
|
279
|
+
// Prints a YAML fragment by default. With --apply, rewrites the SKILL.md file
|
|
280
|
+
// in place using line-based replacement. Line-based so we preserve the
|
|
281
|
+
// author's comments, quoting, and indentation.
|
|
282
|
+
// ---------------------------------------------------------------------------
|
|
283
|
+
|
|
284
|
+
function todayISO() {
|
|
285
|
+
const d = new Date();
|
|
286
|
+
const pad = n => String(n).padStart(2, '0');
|
|
287
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function buildDriftCheckBlock(truthSources, indent) {
|
|
291
|
+
const out = [];
|
|
292
|
+
out.push(`${indent}drift_check:`);
|
|
293
|
+
out.push(`${indent} last_verified: "${todayISO()}"`);
|
|
294
|
+
if (truthSources.length > 0) {
|
|
295
|
+
out.push(`${indent} truth_source_hashes:`);
|
|
296
|
+
for (const ts of truthSources) {
|
|
297
|
+
if (ts.live_hash) {
|
|
298
|
+
out.push(`${indent} "${ts.source}": "${ts.live_hash}"`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return out;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Rewrite the drift_check block of a SKILL.md file in place.
|
|
307
|
+
*
|
|
308
|
+
* Locates the existing drift_check block (single-line scalar OR multi-line
|
|
309
|
+
* object), removes it, and inserts the newly computed block at the same
|
|
310
|
+
* position. All other frontmatter lines are preserved verbatim.
|
|
311
|
+
*/
|
|
312
|
+
function applyRecord(skillMdPath, truthSources) {
|
|
313
|
+
const text = fs.readFileSync(skillMdPath, 'utf8');
|
|
314
|
+
const newline = text.includes('\r\n') ? '\r\n' : '\n';
|
|
315
|
+
const lines = text.split(/\r?\n/);
|
|
316
|
+
if (lines[0].replace(/^\uFEFF/, '') !== '---') throw new Error('no frontmatter delimiter');
|
|
317
|
+
let closeIdx = -1;
|
|
318
|
+
for (let i = 1; i < lines.length; i++) {
|
|
319
|
+
if (lines[i].trim() === '---') { closeIdx = i; break; }
|
|
320
|
+
}
|
|
321
|
+
if (closeIdx === -1) throw new Error('unterminated frontmatter');
|
|
322
|
+
|
|
323
|
+
// Locate drift_check block: starts at a top-level `drift_check:` line,
|
|
324
|
+
// ends just before the next line at the same or shallower indent level.
|
|
325
|
+
let start = -1;
|
|
326
|
+
let end = -1;
|
|
327
|
+
let indent = '';
|
|
328
|
+
for (let i = 1; i < closeIdx; i++) {
|
|
329
|
+
const m = lines[i].match(/^(\s*)drift_check\s*:/);
|
|
330
|
+
if (m) {
|
|
331
|
+
start = i;
|
|
332
|
+
indent = m[1];
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if (start === -1) {
|
|
337
|
+
throw new Error('drift_check block not found — run migrate-skill-v2-to-v3.js first');
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Find the end of the block.
|
|
341
|
+
const blockIndentLen = indent.length;
|
|
342
|
+
end = start;
|
|
343
|
+
for (let i = start + 1; i < closeIdx; i++) {
|
|
344
|
+
const line = lines[i];
|
|
345
|
+
if (line.trim() === '') { end = i; continue; }
|
|
346
|
+
const lineIndent = line.match(/^ */)[0].length;
|
|
347
|
+
if (lineIndent <= blockIndentLen) {
|
|
348
|
+
end = i - 1;
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
end = i;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const newBlock = buildDriftCheckBlock(truthSources, indent);
|
|
355
|
+
const before = lines.slice(0, start);
|
|
356
|
+
const after = lines.slice(end + 1);
|
|
357
|
+
const newLines = before.concat(newBlock, after);
|
|
358
|
+
fs.writeFileSync(skillMdPath, newLines.join(newline), 'utf8');
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// ---------------------------------------------------------------------------
|
|
362
|
+
// CLI
|
|
363
|
+
// ---------------------------------------------------------------------------
|
|
364
|
+
|
|
365
|
+
function collectSkillFiles(args, roots) {
|
|
366
|
+
const explicit = args.filter(a => !a.startsWith('--'));
|
|
367
|
+
|
|
368
|
+
if (explicit.length > 0) {
|
|
369
|
+
const out = [];
|
|
370
|
+
for (const arg of explicit) {
|
|
371
|
+
const abs = path.resolve(arg);
|
|
372
|
+
if (!fs.existsSync(abs)) {
|
|
373
|
+
console.error(`ERROR ${arg}: path does not exist`);
|
|
374
|
+
process.exit(1);
|
|
375
|
+
}
|
|
376
|
+
const stat = fs.statSync(abs);
|
|
377
|
+
if (stat.isDirectory()) {
|
|
378
|
+
const skillMd = path.join(abs, 'SKILL.md');
|
|
379
|
+
if (fs.existsSync(skillMd)) out.push(skillMd);
|
|
380
|
+
} else if (abs.endsWith('SKILL.md')) {
|
|
381
|
+
out.push(abs);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return out;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const out = [];
|
|
388
|
+
for (const { absPath } of roots) {
|
|
389
|
+
if (!fs.existsSync(absPath)) continue;
|
|
390
|
+
for (const name of fs.readdirSync(absPath).sort()) {
|
|
391
|
+
const skillMd = path.join(absPath, name, 'SKILL.md');
|
|
392
|
+
if (fs.existsSync(skillMd)) out.push(skillMd);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return out;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function main() {
|
|
399
|
+
const args = process.argv.slice(2);
|
|
400
|
+
const outputJson = args.includes('--json');
|
|
401
|
+
const record = args.includes('--record');
|
|
402
|
+
const apply = args.includes('--apply');
|
|
403
|
+
|
|
404
|
+
const workspace = loadWorkspaceConfig();
|
|
405
|
+
const roots = resolveSkillRoots(workspace);
|
|
406
|
+
|
|
407
|
+
const files = collectSkillFiles(args, roots);
|
|
408
|
+
if (files.length === 0) {
|
|
409
|
+
console.error('No SKILL.md files found.');
|
|
410
|
+
process.exit(1);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const reports = files.map(f => checkSkill(f, roots));
|
|
414
|
+
|
|
415
|
+
// -------------------------------------------------------------------------
|
|
416
|
+
// Record mode
|
|
417
|
+
// -------------------------------------------------------------------------
|
|
418
|
+
if (record) {
|
|
419
|
+
let exitCode = 0;
|
|
420
|
+
for (let i = 0; i < reports.length; i++) {
|
|
421
|
+
const report = reports[i];
|
|
422
|
+
const skillMdPath = files[i];
|
|
423
|
+
if (report.status === 'UNGROUNDED' || report.status === 'NO_FRONTMATTER') continue;
|
|
424
|
+
const truthSources = report.truth_sources.filter(ts => ts.live_hash);
|
|
425
|
+
if (truthSources.length === 0) continue;
|
|
426
|
+
|
|
427
|
+
if (apply) {
|
|
428
|
+
try {
|
|
429
|
+
applyRecord(skillMdPath, truthSources);
|
|
430
|
+
console.log(`OK ${path.relative(REPO_ROOT, skillMdPath)} — drift_check recorded`);
|
|
431
|
+
} catch (e) {
|
|
432
|
+
console.error(`FAIL ${path.relative(REPO_ROOT, skillMdPath)}: ${e.message}`);
|
|
433
|
+
exitCode = 1;
|
|
434
|
+
}
|
|
435
|
+
} else {
|
|
436
|
+
console.log(`# ${path.relative(REPO_ROOT, skillMdPath)} — preview (re-run with --apply to write)`);
|
|
437
|
+
console.log(buildDriftCheckBlock(truthSources, '').join('\n'));
|
|
438
|
+
console.log('');
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
process.exit(exitCode);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// -------------------------------------------------------------------------
|
|
445
|
+
// Check mode (default)
|
|
446
|
+
// -------------------------------------------------------------------------
|
|
447
|
+
if (outputJson) {
|
|
448
|
+
process.stdout.write(JSON.stringify({ reports }, null, 2) + '\n');
|
|
449
|
+
} else {
|
|
450
|
+
const statusCounts = {};
|
|
451
|
+
for (const r of reports) statusCounts[r.status] = (statusCounts[r.status] || 0) + 1;
|
|
452
|
+
|
|
453
|
+
for (const r of reports) {
|
|
454
|
+
if (r.status === 'UNGROUNDED' || r.status === 'CLEAN') continue;
|
|
455
|
+
const staleInfo = r.stale ? ` (stale ${r.days_since_verified}d / limit ${r.stale_after_days}d)` : '';
|
|
456
|
+
console.log(`${r.status.padEnd(13)} ${r.skill}${staleInfo}`);
|
|
457
|
+
for (const ts of r.truth_sources) {
|
|
458
|
+
if (ts.status !== 'CLEAN') {
|
|
459
|
+
console.log(` ${ts.status.padEnd(13)} ${ts.source}`);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const summary = Object.entries(statusCounts)
|
|
465
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
466
|
+
.map(([s, n]) => `${n} ${s}`)
|
|
467
|
+
.join(', ');
|
|
468
|
+
console.log(`\n${reports.length} skill(s): ${summary || 'all clean'}`);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const hasDriftOrBroken = reports.some(r => r.status === 'DRIFT' || r.status === 'BROKEN');
|
|
472
|
+
process.exit(hasDriftOrBroken ? 1 : 0);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
module.exports = {
|
|
476
|
+
checkSkill,
|
|
477
|
+
hashContent,
|
|
478
|
+
isRemoteTruthSourcePath,
|
|
479
|
+
normalizeTruthSource,
|
|
480
|
+
sha256TruthSource,
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
if (require.main === module) main();
|