@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.
Files changed (330) hide show
  1. package/CHANGELOG.md +247 -0
  2. package/LICENSE +200 -0
  3. package/NOTICE +62 -0
  4. package/README.md +398 -0
  5. package/SKILL_GRAPH.md +443 -0
  6. package/bin/skill-graph.js +374 -0
  7. package/docs/ADOPTION.md +117 -0
  8. package/docs/CONFORMANCE.md +66 -0
  9. package/docs/PRIMER.md +384 -0
  10. package/docs/QUICKSTART-30MIN.md +333 -0
  11. package/docs/ROUTING-METRICS.md +120 -0
  12. package/docs/SKILL-MD-FORMAT-COMPATIBILITY.md +127 -0
  13. package/docs/SKILL_AUDIT_CHECKLIST.md +199 -0
  14. package/docs/SKILL_AUDIT_LOOP.md +195 -0
  15. package/docs/SKILL_METADATA_PROTOCOL.md +609 -0
  16. package/docs/_archived/marketplace-publication-priority-2026-05-18.md +239 -0
  17. package/docs/adr/0001-predicate-set.md +69 -0
  18. package/docs/adr/0002-json-ld-context.md +82 -0
  19. package/docs/adr/0003-ontoclean-rigidity-tags.md +65 -0
  20. package/docs/adr/0004-persistent-identifiers.md +74 -0
  21. package/docs/adr/0005-freshness-consolidation.md +70 -0
  22. package/docs/adr/0006-revise-predicate-rename.md +105 -0
  23. package/docs/adr/0007-audit-loop-cadence.md +99 -0
  24. package/docs/adr/0008-skill-surface-split-and-curation-policy.md +93 -0
  25. package/docs/category-consumers.md +168 -0
  26. package/docs/concept-map.md +194 -0
  27. package/docs/diagrams/drift-states.mmd +21 -0
  28. package/docs/diagrams/manifest-pipeline.mmd +25 -0
  29. package/docs/diagrams/routing-harness.mmd +41 -0
  30. package/docs/diagrams/starter-graph.mmd +53 -0
  31. package/docs/field-decision-guide.md +315 -0
  32. package/docs/field-rationale.md +211 -0
  33. package/docs/field-reference.generated.md +624 -0
  34. package/docs/field-reference.md +1426 -0
  35. package/docs/glossary.md +190 -0
  36. package/docs/head-noun-glossary.md +63 -0
  37. package/docs/images/audit-phases.png +0 -0
  38. package/docs/images/drift-states.png +0 -0
  39. package/docs/images/graded-mode.png +0 -0
  40. package/docs/images/manifest-pipeline.png +0 -0
  41. package/docs/images/routing-harness.png +0 -0
  42. package/docs/images/skill-anatomy.png +0 -0
  43. package/docs/images/starter-graph.png +0 -0
  44. package/docs/images/system-model.png +0 -0
  45. package/docs/integrations/github-actions.md +155 -0
  46. package/docs/manifest-field-mapping.md +443 -0
  47. package/docs/marketplace-publication-queue.generated.md +240 -0
  48. package/docs/marketplace-release-agent-prompt.md +82 -0
  49. package/docs/marketplace-skill-candidate-list.md +272 -0
  50. package/docs/marketplace-syndication.md +222 -0
  51. package/docs/migration-sample-review.md +155 -0
  52. package/docs/migrations/v4-to-v5.md +168 -0
  53. package/docs/migrations/v5-to-v6.md +221 -0
  54. package/docs/name-exceptions.yaml +37 -0
  55. package/docs/plans/marketplace-p1-public-migration-plan.md +41 -0
  56. package/docs/plans/multi-root-workspace.md +148 -0
  57. package/docs/plans/scripts-roadmap.md +107 -0
  58. package/docs/plans/v4-schema-bump.md +160 -0
  59. package/docs/plans/wave-2-extraction.md +122 -0
  60. package/docs/positioning-vs-marketplaces.md +175 -0
  61. package/docs/proposals/skill-audit-loop-positioning.md +160 -0
  62. package/docs/quality-doctrine.md +138 -0
  63. package/docs/recommended-skills.md +150 -0
  64. package/docs/research/skill-comprehension-eval-research.md +1830 -0
  65. package/docs/research/skill-retrieval-evidence.md +66 -0
  66. package/docs/skill-metadata-protocol.md +471 -0
  67. package/docs/skills-sh-maintainer-cleanup-request.md +80 -0
  68. package/examples/audits/a11y/findings.md +52 -0
  69. package/examples/audits/a11y/scorecard.md +21 -0
  70. package/examples/audits/a11y/verdict.md +44 -0
  71. package/examples/audits/debugging/findings.md +59 -0
  72. package/examples/audits/debugging/scorecard.md +22 -0
  73. package/examples/audits/debugging/verdict.md +33 -0
  74. package/examples/audits/documentation/findings.md +59 -0
  75. package/examples/audits/documentation/scorecard.md +22 -0
  76. package/examples/audits/documentation/verdict.md +33 -0
  77. package/examples/evals/a11y.json +140 -0
  78. package/examples/evals/api-design.json +52 -0
  79. package/examples/evals/code-review.json +52 -0
  80. package/examples/evals/data-modeling.json +52 -0
  81. package/examples/evals/database-migration.json +52 -0
  82. package/examples/evals/debugging.json +118 -0
  83. package/examples/evals/dependency-architecture.json +52 -0
  84. package/examples/evals/design-system-architecture.json +52 -0
  85. package/examples/evals/error-tracking.json +52 -0
  86. package/examples/evals/event-contract-design.json +52 -0
  87. package/examples/evals/form-ux-architecture.json +52 -0
  88. package/examples/evals/framework-fit-analysis.json +52 -0
  89. package/examples/evals/graph-audit.json +139 -0
  90. package/examples/evals/information-architecture.json +52 -0
  91. package/examples/evals/interaction-feedback.json +52 -0
  92. package/examples/evals/interaction-patterns.json +52 -0
  93. package/examples/evals/layout-composition.json +52 -0
  94. package/examples/evals/lint-overlay.json +117 -0
  95. package/examples/evals/microcopy.json +52 -0
  96. package/examples/evals/observability-modeling.json +52 -0
  97. package/examples/evals/pattern-recognition.json +96 -0
  98. package/examples/evals/performance-engineering.json +52 -0
  99. package/examples/evals/refactor.json +128 -0
  100. package/examples/evals/semiotics.json +52 -0
  101. package/examples/evals/skill-infrastructure.json +96 -0
  102. package/examples/evals/skill-router.json +140 -0
  103. package/examples/evals/skill-router.routing.json +113 -0
  104. package/examples/evals/system-interface-contracts.json +52 -0
  105. package/examples/evals/task-analysis.json +52 -0
  106. package/examples/evals/testing-strategy.json +118 -0
  107. package/examples/evals/type-safety.json +249 -0
  108. package/examples/evals/visual-design-foundations.json +52 -0
  109. package/examples/evals/webhook-integration.json +52 -0
  110. package/examples/exports/a11y.skill-md.md +80 -0
  111. package/examples/exports/debugging.skill-md.md +80 -0
  112. package/examples/exports/refactor.skill-md.md +78 -0
  113. package/examples/exports/testing-strategy.skill-md.md +81 -0
  114. package/examples/projects/markdown-static-site/README.md +115 -0
  115. package/examples/projects/markdown-static-site/skills/content-source-router/SKILL.md +131 -0
  116. package/examples/projects/markdown-static-site/skills/image-optimization-pipeline-config/SKILL.md +132 -0
  117. package/examples/projects/markdown-static-site/skills/link-rot-detection/SKILL.md +103 -0
  118. package/examples/projects/markdown-static-site/skills/markdown-post-frontmatter-validation/SKILL.md +133 -0
  119. package/examples/projects/markdown-static-site/skills/migrate-posts-to-v2-frontmatter/SKILL.md +140 -0
  120. package/examples/projects/saas-stripe-postgres/README.md +208 -0
  121. package/examples/projects/saas-stripe-postgres/db/migrations/0004_canonicalize_orders.sql +37 -0
  122. package/examples/projects/saas-stripe-postgres/db/schema.sql +112 -0
  123. package/examples/projects/saas-stripe-postgres/skills/migrate-orders-to-canonical-schema/SKILL.md +149 -0
  124. package/examples/projects/saas-stripe-postgres/skills/nextjs-server-action-validation/SKILL.md +154 -0
  125. package/examples/projects/saas-stripe-postgres/skills/payment-provider-router/SKILL.md +153 -0
  126. package/examples/projects/saas-stripe-postgres/skills/postgres-rls-pattern/SKILL.md +163 -0
  127. package/examples/projects/saas-stripe-postgres/skills/stripe-webhook-signature-verification/SKILL.md +137 -0
  128. package/examples/protocol/skill-metadata-template.md +301 -0
  129. package/examples/protocol/skills.manifest.sample.json +13245 -0
  130. package/examples/skill-metadata-template.md +317 -0
  131. package/examples/skills.manifest.sample.json +13519 -0
  132. package/examples/tests/v3-1-skos-fixture/SKILL.md +93 -0
  133. package/marketplace/README.md +17 -0
  134. package/marketplace/skills/a11y/SKILL.md +66 -0
  135. package/marketplace/skills/acid-fundamentals/SKILL.md +106 -0
  136. package/marketplace/skills/agent-engineering/SKILL.md +386 -0
  137. package/marketplace/skills/agent-eval-design/SKILL.md +55 -0
  138. package/marketplace/skills/ai-native-development/SKILL.md +294 -0
  139. package/marketplace/skills/api-design/SKILL.md +60 -0
  140. package/marketplace/skills/architecture-decision-records/SKILL.md +55 -0
  141. package/marketplace/skills/background-jobs/SKILL.md +265 -0
  142. package/marketplace/skills/bounded-context-mapping/SKILL.md +55 -0
  143. package/marketplace/skills/cap-theorem-tradeoffs/SKILL.md +127 -0
  144. package/marketplace/skills/client-server-boundary/SKILL.md +187 -0
  145. package/marketplace/skills/code-review/SKILL.md +120 -0
  146. package/marketplace/skills/color-system-design/SKILL.md +43 -0
  147. package/marketplace/skills/component-architecture/SKILL.md +126 -0
  148. package/marketplace/skills/compression/SKILL.md +112 -0
  149. package/marketplace/skills/conceptual-modeling/SKILL.md +181 -0
  150. package/marketplace/skills/connection-pooling/SKILL.md +105 -0
  151. package/marketplace/skills/constraint-awareness/SKILL.md +287 -0
  152. package/marketplace/skills/content-monitor/SKILL.md +209 -0
  153. package/marketplace/skills/context-engineering/SKILL.md +320 -0
  154. package/marketplace/skills/context-graph/SKILL.md +174 -0
  155. package/marketplace/skills/context-management/SKILL.md +174 -0
  156. package/marketplace/skills/context-window/SKILL.md +239 -0
  157. package/marketplace/skills/contract-testing/SKILL.md +120 -0
  158. package/marketplace/skills/cron-scheduling/SKILL.md +223 -0
  159. package/marketplace/skills/dark-mode-implementation/SKILL.md +47 -0
  160. package/marketplace/skills/data-modeling/SKILL.md +59 -0
  161. package/marketplace/skills/data-modeling-fundamentals/SKILL.md +117 -0
  162. package/marketplace/skills/database-migration/SKILL.md +429 -0
  163. package/marketplace/skills/debugging/SKILL.md +67 -0
  164. package/marketplace/skills/dependency-architecture/SKILL.md +58 -0
  165. package/marketplace/skills/design-module-composition/SKILL.md +43 -0
  166. package/marketplace/skills/design-system-architecture/SKILL.md +61 -0
  167. package/marketplace/skills/design-thinking/SKILL.md +44 -0
  168. package/marketplace/skills/diagnosis/SKILL.md +296 -0
  169. package/marketplace/skills/diff-analysis/SKILL.md +188 -0
  170. package/marketplace/skills/e2e-test-design/SKILL.md +113 -0
  171. package/marketplace/skills/entity-relationship-modeling/SKILL.md +218 -0
  172. package/marketplace/skills/epistemic-grounding/SKILL.md +112 -0
  173. package/marketplace/skills/error-boundary/SKILL.md +235 -0
  174. package/marketplace/skills/error-tracking/SKILL.md +261 -0
  175. package/marketplace/skills/eval-driven-development/SKILL.md +147 -0
  176. package/marketplace/skills/evaluation/SKILL.md +113 -0
  177. package/marketplace/skills/event-contract-design/SKILL.md +60 -0
  178. package/marketplace/skills/event-storming/SKILL.md +56 -0
  179. package/marketplace/skills/form-ux-architecture/SKILL.md +60 -0
  180. package/marketplace/skills/framework-fit-analysis/SKILL.md +59 -0
  181. package/marketplace/skills/frontend-architecture/SKILL.md +43 -0
  182. package/marketplace/skills/generative-ui/SKILL.md +118 -0
  183. package/marketplace/skills/graph-audit/SKILL.md +81 -0
  184. package/marketplace/skills/guardrails/SKILL.md +118 -0
  185. package/marketplace/skills/hooks-patterns/SKILL.md +185 -0
  186. package/marketplace/skills/http-semantics/SKILL.md +136 -0
  187. package/marketplace/skills/ideation/SKILL.md +41 -0
  188. package/marketplace/skills/indexing-strategy/SKILL.md +108 -0
  189. package/marketplace/skills/information-architecture/SKILL.md +59 -0
  190. package/marketplace/skills/integration-test-design/SKILL.md +111 -0
  191. package/marketplace/skills/intent-recognition/SKILL.md +136 -0
  192. package/marketplace/skills/interaction-feedback/SKILL.md +59 -0
  193. package/marketplace/skills/interaction-patterns/SKILL.md +59 -0
  194. package/marketplace/skills/journey-mapping/SKILL.md +41 -0
  195. package/marketplace/skills/keywords/SKILL.md +213 -0
  196. package/marketplace/skills/knowledge-modeling/SKILL.md +232 -0
  197. package/marketplace/skills/layout-composition/SKILL.md +59 -0
  198. package/marketplace/skills/linguistics/SKILL.md +429 -0
  199. package/marketplace/skills/lint-overlay/SKILL.md +76 -0
  200. package/marketplace/skills/mental-models/SKILL.md +126 -0
  201. package/marketplace/skills/merge-queue/SKILL.md +94 -0
  202. package/marketplace/skills/methodology/SKILL.md +317 -0
  203. package/marketplace/skills/microcopy/SKILL.md +232 -0
  204. package/marketplace/skills/middleware-patterns/SKILL.md +363 -0
  205. package/marketplace/skills/mobile-responsive-ux/SKILL.md +287 -0
  206. package/marketplace/skills/mutation-testing/SKILL.md +112 -0
  207. package/marketplace/skills/naming-conventions/SKILL.md +112 -0
  208. package/marketplace/skills/observability-modeling/SKILL.md +59 -0
  209. package/marketplace/skills/ontology-modeling/SKILL.md +67 -0
  210. package/marketplace/skills/owasp-security/SKILL.md +153 -0
  211. package/marketplace/skills/pattern-recognition/SKILL.md +472 -0
  212. package/marketplace/skills/performance-budgets/SKILL.md +185 -0
  213. package/marketplace/skills/performance-engineering/SKILL.md +58 -0
  214. package/marketplace/skills/performance-testing/SKILL.md +125 -0
  215. package/marketplace/skills/printify/SKILL.md +42 -0
  216. package/marketplace/skills/prioritization/SKILL.md +118 -0
  217. package/marketplace/skills/problem-framing/SKILL.md +41 -0
  218. package/marketplace/skills/problem-locating-solving/SKILL.md +203 -0
  219. package/marketplace/skills/project-knowledge-extraction/SKILL.md +54 -0
  220. package/marketplace/skills/prompt-craft/SKILL.md +134 -0
  221. package/marketplace/skills/prompt-injection-defense/SKILL.md +132 -0
  222. package/marketplace/skills/property-based-testing/SKILL.md +100 -0
  223. package/marketplace/skills/prototyping/SKILL.md +43 -0
  224. package/marketplace/skills/query-optimization/SKILL.md +144 -0
  225. package/marketplace/skills/real-time-updates/SKILL.md +324 -0
  226. package/marketplace/skills/ref-patterns/SKILL.md +284 -0
  227. package/marketplace/skills/refactor/SKILL.md +65 -0
  228. package/marketplace/skills/rendering-models/SKILL.md +142 -0
  229. package/marketplace/skills/replication-patterns/SKILL.md +110 -0
  230. package/marketplace/skills/research-synthesis/SKILL.md +41 -0
  231. package/marketplace/skills/route-handler-design/SKILL.md +347 -0
  232. package/marketplace/skills/schema-evolution/SKILL.md +140 -0
  233. package/marketplace/skills/security-fundamentals/SKILL.md +139 -0
  234. package/marketplace/skills/semantic-center/SKILL.md +194 -0
  235. package/marketplace/skills/semantic-relations/SKILL.md +250 -0
  236. package/marketplace/skills/semantics/SKILL.md +366 -0
  237. package/marketplace/skills/semiotics/SKILL.md +230 -0
  238. package/marketplace/skills/seo-strategy/SKILL.md +260 -0
  239. package/marketplace/skills/server-actions-design/SKILL.md +243 -0
  240. package/marketplace/skills/server-components-design/SKILL.md +190 -0
  241. package/marketplace/skills/sharding-strategy/SKILL.md +123 -0
  242. package/marketplace/skills/shopify/SKILL.md +42 -0
  243. package/marketplace/skills/skill-infrastructure/SKILL.md +320 -0
  244. package/marketplace/skills/skill-router/SKILL.md +71 -0
  245. package/marketplace/skills/skill-scaffold/SKILL.md +105 -0
  246. package/marketplace/skills/snapshot-testing/SKILL.md +120 -0
  247. package/marketplace/skills/spec-driven-development/SKILL.md +148 -0
  248. package/marketplace/skills/state-machine-modeling/SKILL.md +56 -0
  249. package/marketplace/skills/state-management/SKILL.md +134 -0
  250. package/marketplace/skills/streaming-architecture/SKILL.md +194 -0
  251. package/marketplace/skills/summarization/SKILL.md +156 -0
  252. package/marketplace/skills/suspense-patterns/SKILL.md +265 -0
  253. package/marketplace/skills/system-interface-contracts/SKILL.md +59 -0
  254. package/marketplace/skills/task-analysis/SKILL.md +201 -0
  255. package/marketplace/skills/taxonomy-design/SKILL.md +66 -0
  256. package/marketplace/skills/test-coverage-strategy/SKILL.md +108 -0
  257. package/marketplace/skills/test-doubles-design/SKILL.md +98 -0
  258. package/marketplace/skills/test-driven-development/SKILL.md +96 -0
  259. package/marketplace/skills/testing-strategy/SKILL.md +67 -0
  260. package/marketplace/skills/theme-system-design/SKILL.md +43 -0
  261. package/marketplace/skills/tool-call-flow/SKILL.md +229 -0
  262. package/marketplace/skills/tool-call-strategy/SKILL.md +292 -0
  263. package/marketplace/skills/transaction-isolation/SKILL.md +98 -0
  264. package/marketplace/skills/type-safety/SKILL.md +177 -0
  265. package/marketplace/skills/typography-system/SKILL.md +43 -0
  266. package/marketplace/skills/usability-testing/SKILL.md +43 -0
  267. package/marketplace/skills/user-research/SKILL.md +43 -0
  268. package/marketplace/skills/vercel-composition-patterns/SKILL.md +157 -0
  269. package/marketplace/skills/version-control/SKILL.md +233 -0
  270. package/marketplace/skills/visual-design-foundations/SKILL.md +59 -0
  271. package/marketplace/skills/visual-hierarchy/SKILL.md +43 -0
  272. package/marketplace/skills/webhook-integration/SKILL.md +331 -0
  273. package/marketplace/skills/writing-humanizer/SKILL.md +380 -0
  274. package/package.json +67 -0
  275. package/schemas/manifest.schema.json +811 -0
  276. package/schemas/manifest.v2.schema.json +164 -0
  277. package/schemas/manifest.v3.schema.json +758 -0
  278. package/schemas/manifest.v4.schema.json +755 -0
  279. package/schemas/manifest.v5.schema.json +755 -0
  280. package/schemas/manifest.v6.schema.json +811 -0
  281. package/schemas/skill.context.jsonld +279 -0
  282. package/schemas/skill.schema.json +919 -0
  283. package/schemas/skill.v2.schema.json +201 -0
  284. package/schemas/skill.v3.schema.json +827 -0
  285. package/schemas/skill.v4.schema.json +822 -0
  286. package/schemas/skill.v5.schema.json +830 -0
  287. package/schemas/skill.v6.schema.json +946 -0
  288. package/schemas/vocabulary/keywords.json +180 -0
  289. package/schemas/vocabulary/workspace_tags.json +23 -0
  290. package/scripts/__tests__/migrate-skill-v2-to-v3.test.js +161 -0
  291. package/scripts/__tests__/migrate-skill-v3-to-v4.test.js +158 -0
  292. package/scripts/__tests__/test-export-parser-drift.js +149 -0
  293. package/scripts/__tests__/test-marketplace-export.js +114 -0
  294. package/scripts/__tests__/test-router-paths.js +82 -0
  295. package/scripts/__tests__/test-stability-promotion.js +244 -0
  296. package/scripts/__tests__/test-v3-1-alias-contract.js +109 -0
  297. package/scripts/__tests__/test-v3-1-skos-runtime.js +116 -0
  298. package/scripts/backfill-schema-version.js +198 -0
  299. package/scripts/build-field-reference.js +160 -0
  300. package/scripts/build-retrieval-baseline.js +511 -0
  301. package/scripts/check-markdown-links.js +211 -0
  302. package/scripts/check-protocol-consistency.js +979 -0
  303. package/scripts/export-marketplace-skills.js +610 -0
  304. package/scripts/export-skill.js +374 -0
  305. package/scripts/generate-manifest.js +787 -0
  306. package/scripts/lib/alias-contract.js +83 -0
  307. package/scripts/lib/audit-prompt-builder.js +771 -0
  308. package/scripts/lib/mock-grader.js +134 -0
  309. package/scripts/lib/parse-frontmatter.js +429 -0
  310. package/scripts/lib/roots.js +119 -0
  311. package/scripts/lint/check-archetype-sections.js +185 -0
  312. package/scripts/lint/check-category-enum.js +83 -0
  313. package/scripts/lint/check-routing-eval.js +146 -0
  314. package/scripts/lint/check-routing-quality.js +211 -0
  315. package/scripts/lint/check-stability-promotion.js +220 -0
  316. package/scripts/lint/format-code-frame.js +206 -0
  317. package/scripts/marketplace-install.js +125 -0
  318. package/scripts/migrate-category-to-enum.js +169 -0
  319. package/scripts/migrate-skill-v2-to-v3.js +424 -0
  320. package/scripts/migrate-skill-v3-to-v4.js +200 -0
  321. package/scripts/migrate-skill-v5-to-v6.js +304 -0
  322. package/scripts/restructure-by-category.js +85 -0
  323. package/scripts/seed-publication-classification.js +282 -0
  324. package/scripts/skill-audit.js +893 -0
  325. package/scripts/skill-graph-drift.js +483 -0
  326. package/scripts/skill-graph-route.js +766 -0
  327. package/scripts/skill-graph-routing-eval.js +393 -0
  328. package/scripts/skill-lint.js +1317 -0
  329. package/scripts/skill-overlap.js +213 -0
  330. package/scripts/verify-skill-md-export.js +201 -0
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * mock-grader.js — deterministic stand-in for an external grader CLI.
4
+ *
5
+ * Use this to smoke-test `scripts/skill-audit.js --graded` without an API
6
+ * key. It reads the composed prompt from stdin, pattern-matches the
7
+ * dimension id, and prints a canned <verdict>…</verdict> block on stdout
8
+ * that the audit runner can parse and merge into the artifact files.
9
+ *
10
+ * Usage (from the repo root):
11
+ * node scripts/skill-audit.js documentation \
12
+ * --graded \
13
+ * --grader-cli "node scripts/lib/mock-grader.js" \
14
+ * --force
15
+ *
16
+ * Self-contained — only Node built-ins. Exit 0 on success, 1 on error.
17
+ */
18
+
19
+ 'use strict';
20
+
21
+ function readStdin() {
22
+ return new Promise((resolve, reject) => {
23
+ let data = '';
24
+ process.stdin.setEncoding('utf8');
25
+ process.stdin.on('data', (chunk) => { data += chunk; });
26
+ process.stdin.on('end', () => resolve(data));
27
+ process.stdin.on('error', reject);
28
+ });
29
+ }
30
+
31
+ function extractDimensionId(prompt) {
32
+ const m = prompt.match(/<dimension id="([a-z]+)"/);
33
+ return m ? m[1] : 'unknown';
34
+ }
35
+
36
+ // Canned per-dimension verdicts. Intentionally varied so the merged artifacts
37
+ // show all four verdict states (PASS, PASS WITH FIXES, FAIL, N/A) and all five
38
+ // severity levels. These are NOT real audit judgments — they exist to prove
39
+ // the contract end-to-end.
40
+ const CANNED = {
41
+ metadata: {
42
+ score: 5,
43
+ verdict: 'PASS',
44
+ justification: 'All thirteen required v2 frontmatter fields are present and well-typed; schema_version is 2.',
45
+ findings: [],
46
+ },
47
+ activation: {
48
+ score: 4,
49
+ verdict: 'PASS WITH FIXES',
50
+ justification: 'Description names real trigger scenarios and keywords are specific, but the skill has no explicit `triggers` array for label-based routing.',
51
+ findings: [
52
+ {
53
+ severity: 'P3',
54
+ surface: 'frontmatter: triggers',
55
+ problem: 'No triggers array is declared; the skill is only discoverable via keyword matching.',
56
+ evidence: 'triggers: (absent from frontmatter)',
57
+ required_action: 'Add a `triggers: [documentation-skill]` entry so label-based routers can activate the skill deterministically.',
58
+ },
59
+ ],
60
+ },
61
+ relation: {
62
+ score: 5,
63
+ verdict: 'PASS',
64
+ justification: 'adjacent and boundary relations are concise and point at real sibling skills; no dangling targets.',
65
+ findings: [],
66
+ },
67
+ grounding: {
68
+ score: 'N/A',
69
+ verdict: 'N/A',
70
+ justification: 'scope: portable — grounding dimension does not apply.',
71
+ findings: [],
72
+ },
73
+ content: {
74
+ score: 4,
75
+ verdict: 'PASS WITH FIXES',
76
+ justification: 'Coverage, Philosophy, and Verification sections are present and concrete, but "Do NOT Use When" boundaries are implicit rather than an explicit named section.',
77
+ findings: [
78
+ {
79
+ severity: 'P2',
80
+ surface: 'skill body',
81
+ problem: 'No explicit `## Do NOT Use When` section; negative routing is only implied.',
82
+ evidence: 'Section headings observed: `# Documentation`, `## Coverage`, `## Philosophy`, `## Verification` — no explicit negative-bounds section.',
83
+ required_action: 'Add a `## Do NOT Use When` section listing at least two cases where the skill must not activate (e.g. UI accessibility behavior, runtime debugging).',
84
+ },
85
+ ],
86
+ },
87
+ eval: {
88
+ score: 4,
89
+ verdict: 'PASS WITH FIXES',
90
+ justification: 'Eval artifact ships with seven grounded prompts; boundary coverage is good, but failure-mode prompts are missing.',
91
+ findings: [
92
+ {
93
+ severity: 'P3',
94
+ surface: 'examples/evals/comprehension.json',
95
+ problem: 'Eval covers happy-path and boundary prompts but has no explicit failure-mode eval.',
96
+ evidence: 'Seven prompts, all affirmative; no prompt tests what the skill should refuse.',
97
+ required_action: 'Add one failure-mode prompt per skills/evaluation SKILL.md guidance (≥ 1 negative expectation per skill).',
98
+ },
99
+ ],
100
+ },
101
+ portability: {
102
+ score: 5,
103
+ verdict: 'PASS',
104
+ justification: 'Skill is generic, portable, and the skill-md export via scripts/export-skill.js round-trips cleanly.',
105
+ findings: [],
106
+ },
107
+ };
108
+
109
+ (async function main() {
110
+ try {
111
+ const prompt = await readStdin();
112
+ const dimId = extractDimensionId(prompt);
113
+ const payload = CANNED[dimId] || {
114
+ score: 'N/A',
115
+ verdict: 'N/A',
116
+ justification: `mock-grader has no canned verdict for dimension "${dimId}".`,
117
+ findings: [],
118
+ };
119
+
120
+ const body = {
121
+ dimension: dimId,
122
+ score: payload.score,
123
+ verdict: payload.verdict,
124
+ justification: payload.justification,
125
+ findings: payload.findings,
126
+ };
127
+
128
+ process.stdout.write('<verdict>\n' + JSON.stringify(body, null, 2) + '\n</verdict>\n');
129
+ process.exit(0);
130
+ } catch (err) {
131
+ process.stderr.write(`mock-grader error: ${err.message}\n`);
132
+ process.exit(1);
133
+ }
134
+ })();
@@ -0,0 +1,429 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Minimal YAML frontmatter parser for the subset used in Skill Graph SKILL.md
4
+ * files. Handles: scalar keys, quoted strings, block sequences, nested objects,
5
+ * block sequences of objects (v3 boundary/depends_on `- skill: ... reason: ...`
6
+ * form), inline comments, and inline maps (`key: { a: 1, b: 2 }`) at leaf level.
7
+ *
8
+ * Extracted from scripts/skill-lint.js so skill-lint, generate-manifest,
9
+ * export-skill, skill-graph-route, and skill-graph-drift can share the same
10
+ * parser without duplication.
11
+ *
12
+ * Usage:
13
+ * const { parseFrontmatter } = require('./lib/parse-frontmatter');
14
+ * const fm = parseFrontmatter(markdownText); // returns object or null
15
+ */
16
+
17
+ 'use strict';
18
+
19
+ /**
20
+ * Parse the YAML frontmatter block from a Markdown string.
21
+ *
22
+ * @param {string} text - Full contents of the Markdown file.
23
+ * @returns {object|null} Parsed frontmatter as a plain object, or null if no
24
+ * frontmatter block is found.
25
+ */
26
+ function parseFrontmatter(text) {
27
+ const m = text.match(/^\uFEFF?---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
28
+ if (!m) return null;
29
+ const lines = m[1].split(/\r?\n/);
30
+ let i = 0;
31
+
32
+ /**
33
+ * Strip a trailing YAML inline comment from a scalar value.
34
+ *
35
+ * Rules (YAML 1.2): an inline comment starts at a `#` preceded by
36
+ * whitespace and runs to end-of-line. A `#` inside a quoted string does
37
+ * NOT start a comment. This matters for `examples` / `anti_examples`
38
+ * arrays where authors annotate hard-negative prompts with `# reason`
39
+ * tails — the intent is documentation, not part of the routing signal.
40
+ *
41
+ * Handles three cases:
42
+ * 1. Value starts with a quote — find the matching close quote, then
43
+ * everything after it up to an optional ` #` tail is stripped.
44
+ * 2. Unquoted value — the first ` #` sequence begins the comment.
45
+ * 3. Value contains no `#` — return unchanged.
46
+ */
47
+ function stripInlineComment(raw) {
48
+ if (raw.length === 0) return raw;
49
+ const first = raw[0];
50
+ if (first === '"' || first === "'") {
51
+ // Scan for the matching close quote.
52
+ for (let idx = 1; idx < raw.length; idx++) {
53
+ if (raw[idx] === '\\') { idx++; continue; }
54
+ if (raw[idx] === first) {
55
+ // Found close quote. Trim everything after it if it matches ` #...`.
56
+ const tail = raw.slice(idx + 1);
57
+ const commentMatch = tail.match(/^\s+#/);
58
+ if (commentMatch) return raw.slice(0, idx + 1);
59
+ return raw;
60
+ }
61
+ }
62
+ // Unterminated quote — leave the author's text alone.
63
+ return raw;
64
+ }
65
+ // Unquoted: find ` #` (space-hash).
66
+ const m = raw.match(/\s+#/);
67
+ if (m) return raw.slice(0, m.index);
68
+ return raw;
69
+ }
70
+
71
+ function splitTopLevel(value, separator) {
72
+ const parts = [];
73
+ let start = 0;
74
+ let depth = 0;
75
+ let quote = null;
76
+ for (let idx = 0; idx < value.length; idx++) {
77
+ const ch = value[idx];
78
+ if (quote) {
79
+ if (ch === '\\') { idx++; continue; }
80
+ if (ch === quote) quote = null;
81
+ continue;
82
+ }
83
+ if (ch === '"' || ch === "'") {
84
+ quote = ch;
85
+ continue;
86
+ }
87
+ if (ch === '[' || ch === '{') {
88
+ depth++;
89
+ continue;
90
+ }
91
+ if (ch === ']' || ch === '}') {
92
+ depth--;
93
+ continue;
94
+ }
95
+ if (ch === separator && depth === 0) {
96
+ parts.push(value.slice(start, idx).trim());
97
+ start = idx + 1;
98
+ }
99
+ }
100
+ parts.push(value.slice(start).trim());
101
+ return parts.filter(Boolean);
102
+ }
103
+
104
+ function findTopLevelColon(value) {
105
+ let depth = 0;
106
+ let quote = null;
107
+ for (let idx = 0; idx < value.length; idx++) {
108
+ const ch = value[idx];
109
+ if (quote) {
110
+ if (ch === '\\') { idx++; continue; }
111
+ if (ch === quote) quote = null;
112
+ continue;
113
+ }
114
+ if (ch === '"' || ch === "'") {
115
+ quote = ch;
116
+ continue;
117
+ }
118
+ if (ch === '[' || ch === '{') {
119
+ depth++;
120
+ continue;
121
+ }
122
+ if (ch === ']' || ch === '}') {
123
+ depth--;
124
+ continue;
125
+ }
126
+ if (ch === ':' && depth === 0) return idx;
127
+ }
128
+ return -1;
129
+ }
130
+
131
+ function parseInlineArray(v) {
132
+ const body = v.slice(1, -1).trim();
133
+ if (body === '') return [];
134
+ return splitTopLevel(body, ',').map(parseValue);
135
+ }
136
+
137
+ function parseInlineMap(v) {
138
+ const body = v.slice(1, -1).trim();
139
+ if (body === '') return {};
140
+ const obj = {};
141
+ for (const part of splitTopLevel(body, ',')) {
142
+ const colonIdx = findTopLevelColon(part);
143
+ if (colonIdx <= 0) return v;
144
+ const key = extractKey(part.slice(0, colonIdx));
145
+ const rawValue = part.slice(colonIdx + 1).trim();
146
+ obj[key] = parseValue(rawValue);
147
+ }
148
+ return obj;
149
+ }
150
+
151
+ function parseValue(v) {
152
+ v = stripInlineComment(v).trim();
153
+ if (v === '') return null;
154
+ if (v.startsWith('[') && v.endsWith(']')) return parseInlineArray(v);
155
+ if (v.startsWith('{') && v.endsWith('}')) return parseInlineMap(v);
156
+ if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
157
+ return v.slice(1, -1);
158
+ }
159
+ if (/^-?\d+$/.test(v)) return parseInt(v, 10);
160
+ if (/^-?\d+\.\d+$/.test(v)) return parseFloat(v);
161
+ if (v === 'true') return true;
162
+ if (v === 'false') return false;
163
+ if (v === 'null' || v === '~') return null;
164
+ return v;
165
+ }
166
+
167
+ /**
168
+ * Find the index of the key-separator colon in a YAML line. Handles quoted
169
+ * keys — a key like `"schemas/foo.json":` has a colon inside the quoted
170
+ * string that is NOT the separator. Returns the index of the separating
171
+ * colon or -1 when none is found.
172
+ */
173
+ function findKeyColon(content) {
174
+ if (content.startsWith('"') || content.startsWith("'")) {
175
+ const quote = content[0];
176
+ let i = 1;
177
+ while (i < content.length) {
178
+ if (content[i] === '\\') { i += 2; continue; }
179
+ if (content[i] === quote) break;
180
+ i++;
181
+ }
182
+ // i is at the closing quote; look for `:` after it
183
+ const afterQuote = content.indexOf(':', i + 1);
184
+ return afterQuote;
185
+ }
186
+ return content.indexOf(':');
187
+ }
188
+
189
+ /**
190
+ * Extract the key portion from a "key: value" line, unquoting if the key
191
+ * was wrapped in quotes.
192
+ */
193
+ function extractKey(raw) {
194
+ const trimmed = raw.trim();
195
+ if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
196
+ (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
197
+ return trimmed.slice(1, -1);
198
+ }
199
+ return trimmed;
200
+ }
201
+
202
+ /**
203
+ * Parse a block sequence (array). Supports:
204
+ * - scalar items: "- string"
205
+ * - inline objects: "- { key: value }"
206
+ * - block-mapping items: "- key: value\n nextKey: value"
207
+ *
208
+ * The sequence ends when a line's indent drops to or below `indent`, or
209
+ * when the block is exhausted.
210
+ */
211
+ function parseBlockSequence(indent) {
212
+ const arr = [];
213
+ while (i < lines.length) {
214
+ const l = lines[i];
215
+ if (l.trim() === '' || l.trim().startsWith('#')) { i++; continue; }
216
+ const li = l.match(/^ */)[0].length;
217
+ if (li <= indent) break;
218
+ const lc = l.slice(li);
219
+ if (!lc.startsWith('- ')) break;
220
+
221
+ const inline = lc.slice(2).trim();
222
+
223
+ if ((inline.startsWith('{') && inline.endsWith('}')) ||
224
+ (inline.startsWith('[') && inline.endsWith(']'))) {
225
+ arr.push(parseValue(inline));
226
+ i++;
227
+ continue;
228
+ }
229
+
230
+ // Block mapping as an item: "- key: value" optionally followed by
231
+ // more keys at indent (li + 2). Distinguish from scalar items like
232
+ // "- http://example.com" (no space after colon) or `- "a:b"` (quoted).
233
+ const isQuoted = inline.startsWith('"') || inline.startsWith("'");
234
+ const colonIdx = isQuoted ? -1 : inline.indexOf(': ');
235
+ const endsWithColon = !isQuoted && inline.endsWith(':');
236
+
237
+ if (!isQuoted && (colonIdx > 0 || endsWithColon)) {
238
+ // Parse as object item.
239
+ const obj = {};
240
+ const itemIndent = li + 2;
241
+
242
+ // First key on the dash line.
243
+ if (endsWithColon) {
244
+ const k = inline.slice(0, -1).trim();
245
+ i++;
246
+ // The value of the first key is whatever follows at deeper indent.
247
+ // Leave it for the subsequent-key loop to handle — we just record
248
+ // the key as null so downstream code sees it as present.
249
+ obj[k] = null;
250
+ } else {
251
+ const k = inline.slice(0, colonIdx).trim();
252
+ const v = inline.slice(colonIdx + 1).trim();
253
+ obj[k] = parseValue(v);
254
+ i++;
255
+ }
256
+
257
+ // Subsequent keys at itemIndent belong to this object.
258
+ while (i < lines.length) {
259
+ const nl = lines[i];
260
+ if (nl.trim() === '' || nl.trim().startsWith('#')) { i++; continue; }
261
+ const nli = nl.match(/^ */)[0].length;
262
+ if (nli < itemIndent) break;
263
+ if (nli === itemIndent) {
264
+ const nlc = nl.slice(nli);
265
+ if (nlc.startsWith('- ')) break; // next item in the outer sequence
266
+ const ci = nlc.indexOf(':');
267
+ if (ci <= 0) break;
268
+ const k = nlc.slice(0, ci).trim();
269
+ const v = nlc.slice(ci + 1).trim();
270
+ i++;
271
+ if (v === '') {
272
+ // Nested block — parse recursively at deeper indent.
273
+ let peek = i;
274
+ while (peek < lines.length && (lines[peek].trim() === '' || lines[peek].trim().startsWith('#'))) peek++;
275
+ if (peek < lines.length) {
276
+ const peekIndent = lines[peek].match(/^ */)[0].length;
277
+ const peekContent = lines[peek].slice(peekIndent);
278
+ if (peekIndent > itemIndent && peekContent.startsWith('- ')) {
279
+ obj[k] = parseBlockSequence(itemIndent);
280
+ } else if (peekIndent > itemIndent) {
281
+ obj[k] = parseBlock(peekIndent);
282
+ } else {
283
+ obj[k] = null;
284
+ }
285
+ }
286
+ } else {
287
+ obj[k] = parseValue(v);
288
+ }
289
+ } else {
290
+ // Deeper indent without a matching itemIndent sibling — skip
291
+ // gracefully; authors using exotic indent should fall back to
292
+ // single-line scalars.
293
+ i++;
294
+ }
295
+ }
296
+
297
+ arr.push(obj);
298
+ } else {
299
+ arr.push(parseValue(inline));
300
+ i++;
301
+ }
302
+ }
303
+ return arr;
304
+ }
305
+
306
+ function parseBlock(indent) {
307
+ const result = {};
308
+ while (i < lines.length) {
309
+ const line = lines[i];
310
+ if (line.trim() === '' || line.trim().startsWith('#')) { i++; continue; }
311
+ const currentIndent = line.match(/^ */)[0].length;
312
+ if (currentIndent < indent) return result;
313
+ if (currentIndent > indent) { i++; continue; }
314
+ const content = line.slice(indent);
315
+ const colonIdx = findKeyColon(content);
316
+ if (colonIdx === -1) { i++; continue; }
317
+ const key = extractKey(content.slice(0, colonIdx));
318
+ const rest = content.slice(colonIdx + 1).trim();
319
+ i++;
320
+ if (rest === '') {
321
+ let peek = i;
322
+ while (peek < lines.length && (lines[peek].trim() === '' || lines[peek].trim().startsWith('#'))) peek++;
323
+ if (peek < lines.length) {
324
+ const peekLine = lines[peek];
325
+ const peekIndent = peekLine.match(/^ */)[0].length;
326
+ const peekContent = peekLine.slice(peekIndent);
327
+ if (peekIndent > indent && peekContent.startsWith('- ')) {
328
+ result[key] = parseBlockSequence(indent);
329
+ } else if (peekIndent > indent) {
330
+ result[key] = parseBlock(peekIndent);
331
+ } else {
332
+ result[key] = null;
333
+ }
334
+ }
335
+ } else {
336
+ result[key] = parseValue(rest);
337
+ }
338
+ }
339
+ return result;
340
+ }
341
+
342
+ return parseBlock(0);
343
+ }
344
+
345
+ // Set of base SKILL.md fields that stay at the top level in the marketplace
346
+ // export shape. Anything else found under `metadata:` is a Skill Graph
347
+ // extension field that the export pipeline relocated to satisfy Anthropic
348
+ // Claude Code's frontmatter contract. The normalizer lifts those back to
349
+ // top-level so downstream consumers (lint, manifest, route, drift) can read
350
+ // the canonical shape regardless of which format the file is in.
351
+ const SKILL_MD_BASE_FIELDS = new Set([
352
+ 'name',
353
+ 'description',
354
+ 'license',
355
+ 'compatibility',
356
+ 'allowed-tools',
357
+ ]);
358
+
359
+ // Provenance fields added by the export pipeline (see
360
+ // scripts/export-marketplace-skills.js § PROVENANCE_KEYS and the related
361
+ // description-length book-keeping fields). These are export-only metadata
362
+ // describing where the marketplace file came from; they are not authoring
363
+ // fields and the canonical schema does not declare them. The normalizer
364
+ // strips them so lint and manifest tooling see only the authored shape.
365
+ const EXPORT_PROVENANCE_FIELDS = new Set([
366
+ 'skill_graph_source_repo',
367
+ 'skill_graph_protocol',
368
+ 'skill_graph_project',
369
+ 'skill_graph_canonical_skill',
370
+ 'skill_graph_export_description',
371
+ 'skill_graph_canonical_description_length',
372
+ ]);
373
+
374
+ /**
375
+ * Convert a parsed frontmatter object from the marketplace export shape
376
+ * (Skill Graph extension fields nested under `metadata:`, complex values
377
+ * JSON-stringified) back to the canonical Skill Graph shape (all fields at
378
+ * top-level, complex values as native arrays/objects).
379
+ *
380
+ * Idempotent: a canonical-shape input returns unchanged (modulo a defensive
381
+ * copy). Top-level fields take precedence over `metadata.*` if both are
382
+ * present — the explicit author signal wins.
383
+ *
384
+ * @param {object|null} fm - Parsed frontmatter (output of parseFrontmatter).
385
+ * @returns {object|null} Canonicalized frontmatter, or null/undefined passthrough.
386
+ */
387
+ function normalizeFrontmatter(fm) {
388
+ if (!fm || typeof fm !== 'object') return fm;
389
+ const metadata = fm.metadata;
390
+ if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata)) {
391
+ return fm;
392
+ }
393
+
394
+ const normalized = { ...fm };
395
+ delete normalized.metadata;
396
+
397
+ for (const [key, rawValue] of Object.entries(metadata)) {
398
+ if (EXPORT_PROVENANCE_FIELDS.has(key)) continue;
399
+ // Top-level wins. Authors who explicitly set both forms get the
400
+ // top-level version preserved without surprise.
401
+ if (key in normalized && !SKILL_MD_BASE_FIELDS.has(key)) continue;
402
+
403
+ // Marketplace export JSON-stringifies arrays and objects so the resulting
404
+ // YAML stays a flat string-to-string map. Restore native shape here.
405
+ //
406
+ // Two unescape passes are needed in order: our parseFrontmatter does not
407
+ // currently decode YAML double-quote escape sequences (`\"` → `"`,
408
+ // `\\` → `\`), so the raw value still contains literal backslashes from
409
+ // the on-disk representation. Strip those before JSON.parse.
410
+ if (typeof rawValue === 'string') {
411
+ const trimmed = rawValue.trim();
412
+ if ((trimmed.startsWith('[') && trimmed.endsWith(']')) ||
413
+ (trimmed.startsWith('{') && trimmed.endsWith('}'))) {
414
+ const yamlUnescaped = trimmed.replace(/\\"/g, '"').replace(/\\\\/g, '\\');
415
+ try {
416
+ normalized[key] = JSON.parse(yamlUnescaped);
417
+ continue;
418
+ } catch (_err) {
419
+ // Not valid JSON — fall through and keep the raw string.
420
+ }
421
+ }
422
+ }
423
+ normalized[key] = rawValue;
424
+ }
425
+
426
+ return normalized;
427
+ }
428
+
429
+ module.exports = { parseFrontmatter, normalizeFrontmatter };
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Root resolution for local development and installed CLI usage.
3
+ *
4
+ * The npm package lives in its own package root, but most commands should act
5
+ * on the caller's workspace. `bin/skill-graph.js` sets SKILL_GRAPH_WORKSPACE
6
+ * to the process cwd before spawning a script; direct `node scripts/*.js`
7
+ * calls fall back to the current working directory.
8
+ */
9
+
10
+ 'use strict';
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+
15
+ const PACKAGE_ROOT = process.env.SKILL_GRAPH_PACKAGE_ROOT
16
+ ? path.resolve(process.env.SKILL_GRAPH_PACKAGE_ROOT)
17
+ : path.resolve(__dirname, '..', '..');
18
+
19
+ function workspaceRoot() {
20
+ return path.resolve(process.env.SKILL_GRAPH_WORKSPACE || process.env.SKILL_GRAPH_ROOT || process.cwd());
21
+ }
22
+
23
+ function packageRoot() {
24
+ return PACKAGE_ROOT;
25
+ }
26
+
27
+ function resolvePackagedOrWorkspacePath(root, ...parts) {
28
+ const workspacePath = path.join(root, ...parts);
29
+ if (fs.existsSync(workspacePath)) return workspacePath;
30
+ return path.join(PACKAGE_ROOT, ...parts);
31
+ }
32
+
33
+ function resolveSchemaPath(root, fileName) {
34
+ return resolvePackagedOrWorkspacePath(root, 'schemas', fileName);
35
+ }
36
+
37
+ function loadWorkspaceConfig(root = workspaceRoot(), onWarning = null) {
38
+ const configPath = path.join(root, '.skill-graph', 'config.json');
39
+ if (!fs.existsSync(configPath)) return null;
40
+ try {
41
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
42
+ if (!config || typeof config !== 'object') return null;
43
+ if (!config.workspace || typeof config.workspace !== 'object') return null;
44
+ return config.workspace;
45
+ } catch (e) {
46
+ if (typeof onWarning === 'function') {
47
+ onWarning(`.skill-graph/config.json: cannot parse - ${e.message}`);
48
+ }
49
+ return null;
50
+ }
51
+ }
52
+
53
+ function resolveSkillRoots(root = workspaceRoot(), workspace = loadWorkspaceConfig(root)) {
54
+ const defaultRoot = { absPath: path.join(root, 'skills'), project: null };
55
+ if (!workspace || !Array.isArray(workspace.skill_roots) || workspace.skill_roots.length === 0) {
56
+ return [defaultRoot];
57
+ }
58
+
59
+ return workspace.skill_roots
60
+ .map(entry => {
61
+ if (typeof entry === 'string') {
62
+ return { absPath: path.resolve(root, entry), project: null };
63
+ }
64
+ if (entry && typeof entry === 'object' && typeof entry.path === 'string') {
65
+ return {
66
+ absPath: path.resolve(root, entry.path),
67
+ project: (typeof entry.project === 'string' && entry.project.length > 0) ? entry.project : null,
68
+ };
69
+ }
70
+ return null;
71
+ })
72
+ .filter(Boolean);
73
+ }
74
+
75
+ /**
76
+ * Resolve a truth_source-style workspace-relative path against the configured
77
+ * skill library, falling back to the repo root for back-compat.
78
+ *
79
+ * Truth_source paths in eval files and `grounding.truth_sources` blocks look
80
+ * like `skills/<name>/SKILL.md` — workspace-relative to where the skill
81
+ * library lives. Pre-2026-05-16 monorepo split, that was always
82
+ * `<repo-root>/skills/<name>/SKILL.md`. Post-split, the skill library can
83
+ * live in a sibling repo configured via `.skill-graph/config.json` →
84
+ * `workspace.skill_roots`. This helper tries the skill-library-aware
85
+ * resolution first when the relPath starts with `skills/` and the configured
86
+ * skill_root's basename is `skills`, then falls back to repo-root resolution.
87
+ *
88
+ * Behavior:
89
+ * - Default workspace (no config, skill_roots = ["./skills"]):
90
+ * `skills/a11y/SKILL.md` → `<repo-root>/skills/a11y/SKILL.md` (unchanged)
91
+ * - Configured workspace (skill_roots = ["../skills/skills"]):
92
+ * `skills/a11y/SKILL.md` → `<repo-root>/../skills/skills/a11y/SKILL.md`
93
+ * - Paths that don't start with `skills/` (e.g., `schemas/foo.json`) always
94
+ * resolve against `<repo-root>` — they reference the tooling repo itself.
95
+ *
96
+ * @param {string} relPath — workspace-relative path from a truth_source
97
+ * @param {string} repoRoot — the tooling repo workspace root (= REPO_ROOT)
98
+ * @param {Array<{absPath: string, project?: string|null}>} skillRoots — output of resolveSkillRoots()
99
+ * @returns {string} absolute path (existence not guaranteed)
100
+ */
101
+ function resolveTruthSourcePath(relPath, repoRoot, skillRoots) {
102
+ if (typeof relPath === 'string' && relPath.startsWith('skills/') && Array.isArray(skillRoots) && skillRoots.length > 0) {
103
+ const firstRoot = skillRoots[0] && skillRoots[0].absPath;
104
+ if (firstRoot && path.basename(firstRoot) === 'skills') {
105
+ return path.resolve(path.dirname(firstRoot), relPath);
106
+ }
107
+ }
108
+ return path.resolve(repoRoot, relPath);
109
+ }
110
+
111
+ module.exports = {
112
+ packageRoot,
113
+ workspaceRoot,
114
+ loadWorkspaceConfig,
115
+ resolvePackagedOrWorkspacePath,
116
+ resolveSchemaPath,
117
+ resolveSkillRoots,
118
+ resolveTruthSourcePath,
119
+ };