@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,169 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Migrate skill `category` field to the 6-value canonical set.
4
+ *
5
+ * Target enum: foundations | engineering | design | quality | agent | product
6
+ *
7
+ * The current schema (v4) keeps `category` as an open string. This script
8
+ * standardises every skill in skill-graph/skills/ onto the canonical 6.
9
+ * Schema closure (enum constraint, v5 bump) is a follow-up step that is
10
+ * only safe AFTER every skill has been migrated by this script.
11
+ *
12
+ * Per-skill mapping is explicit (no heuristics). Skills already in the
13
+ * canonical set are left untouched. Domain is updated to the target
14
+ * sub-path; existing domain values are preserved when they already match
15
+ * the new category's sub-tree.
16
+ *
17
+ * Usage:
18
+ * node scripts/migrate-category-to-enum.js --dry-run # show diff only
19
+ * node scripts/migrate-category-to-enum.js # apply changes
20
+ */
21
+
22
+ const fs = require('fs');
23
+ const path = require('path');
24
+
25
+ const REPO_ROOT = path.resolve(__dirname, '..');
26
+ const SKILLS_DIR = path.join(REPO_ROOT, 'skills');
27
+ const DRY_RUN = process.argv.includes('--dry-run');
28
+
29
+ // Per-skill mapping. Skills not listed retain their current category.
30
+ // Format: skillName: { category, domain }
31
+ const MAPPING = {
32
+ // === From knowledge (29) ===
33
+ 'ai-native-development': { category: 'agent', domain: 'agent/concepts' },
34
+ 'conceptual-modeling': { category: 'engineering', domain: 'engineering/modeling' },
35
+ 'constraint-awareness': { category: 'foundations', domain: 'foundations/strategy' },
36
+ 'context-engineering': { category: 'agent', domain: 'agent/context' },
37
+ 'context-graph': { category: 'agent', domain: 'agent/context' },
38
+ 'context-management': { category: 'agent', domain: 'agent/context' },
39
+ 'context-window': { category: 'agent', domain: 'agent/context' },
40
+ 'diagnosis': { category: 'engineering', domain: 'engineering/debugging' },
41
+ 'documentation': { category: 'engineering', domain: 'engineering/documentation' },
42
+ 'graph-audit': { category: 'quality', domain: 'quality/audit' },
43
+ 'information-architecture': { category: 'design', domain: 'design/information-architecture' },
44
+ 'knowledge-modeling': { category: 'foundations', domain: 'foundations/knowledge' },
45
+ 'linguistics': { category: 'foundations', domain: 'foundations/language' },
46
+ 'microcopy': { category: 'design', domain: 'design/ux' },
47
+ 'ontology-modeling': { category: 'foundations', domain: 'foundations/ontology' },
48
+ 'pattern-recognition': { category: 'foundations', domain: 'foundations/cognition' },
49
+ 'problem-locating-solving': { category: 'engineering', domain: 'engineering/debugging' },
50
+ 'project-knowledge-extraction': { category: 'agent', domain: 'agent/knowledge' },
51
+ 'prompt-craft': { category: 'agent', domain: 'agent/prompts' },
52
+ 'semantic-center': { category: 'foundations', domain: 'foundations/semantics' },
53
+ 'semantic-relations': { category: 'foundations', domain: 'foundations/semantics' },
54
+ 'semantics': { category: 'foundations', domain: 'foundations/semantics' },
55
+ 'semiotics': { category: 'foundations', domain: 'foundations/semantics' },
56
+ 'skill-infrastructure': { category: 'agent', domain: 'agent/skill-system' },
57
+ 'skill-router': { category: 'agent', domain: 'agent/skill-system' },
58
+ 'skill-scaffold': { category: 'agent', domain: 'agent/skill-system' },
59
+ 'task-analysis': { category: 'foundations', domain: 'foundations/analysis' },
60
+ 'taxonomy-design': { category: 'foundations', domain: 'foundations/classification' },
61
+ 'writing-humanizer': { category: 'design', domain: 'design/content' },
62
+
63
+ // === From frontend (9) — most are design, a11y is quality ===
64
+ 'a11y': { category: 'quality', domain: 'quality/accessibility' },
65
+ 'dark-mode-implementation': { category: 'design', domain: 'design/visual' },
66
+ 'design-system-architecture': { category: 'design', domain: 'design/system' },
67
+ 'form-ux-architecture': { category: 'design', domain: 'design/ux' },
68
+ 'frontend-architecture': { category: 'engineering', domain: 'engineering/frontend' },
69
+ 'interaction-feedback': { category: 'design', domain: 'design/interaction' },
70
+ 'interaction-patterns': { category: 'design', domain: 'design/interaction' },
71
+ 'layout-composition': { category: 'design', domain: 'design/layout' },
72
+ 'visual-design-foundations': { category: 'design', domain: 'design/visual' },
73
+
74
+ // === From ai-engineering (9) ===
75
+ 'command-palette': { category: 'design', domain: 'design/ui' },
76
+ 'content-monitor': { category: 'agent', domain: 'agent/ops' },
77
+ 'governance': { category: 'quality', domain: 'quality/governance' },
78
+ 'guardrails': { category: 'quality', domain: 'quality/safety' },
79
+ 'merge-queue': { category: 'engineering', domain: 'engineering/git' },
80
+ 'methodology': { category: 'quality', domain: 'quality/method' },
81
+ 'reasoning': { category: 'foundations', domain: 'foundations/cognition' },
82
+ 'spec-driven-development': { category: 'engineering', domain: 'engineering/methodology' },
83
+ 'summarization': { category: 'agent', domain: 'agent/cognition' },
84
+
85
+ // === From data (2) ===
86
+ 'compression': { category: 'engineering', domain: 'engineering/data' },
87
+ 'entity-relationship-modeling': { category: 'engineering', domain: 'engineering/modeling' },
88
+
89
+ // === From workflow (1) ===
90
+ 'background-jobs': { category: 'engineering', domain: 'engineering/async' },
91
+
92
+ // === From security (1) ===
93
+ 'owasp-security': { category: 'quality', domain: 'quality/security' },
94
+
95
+ // === From integration (4) — all engineering with sub-domain ===
96
+ 'cron-scheduling': { category: 'engineering', domain: 'engineering/scheduling' },
97
+ 'printify': { category: 'engineering', domain: 'engineering/integrations' },
98
+ 'real-time-updates': { category: 'engineering', domain: 'engineering/realtime' },
99
+ 'shopify': { category: 'engineering', domain: 'engineering/integrations' },
100
+ };
101
+
102
+ function updateFrontmatter(content, skillName) {
103
+ if (!MAPPING[skillName]) return null; // not in mapping, skip
104
+
105
+ const target = MAPPING[skillName];
106
+ let updated = content;
107
+
108
+ // Update category line (works for both YAML scalar forms)
109
+ updated = updated.replace(
110
+ /^category:\s*.+$/m,
111
+ `category: ${target.category}`
112
+ );
113
+
114
+ // Update or insert domain line
115
+ if (/^domain:\s*.*$/m.test(updated)) {
116
+ updated = updated.replace(
117
+ /^domain:\s*.*$/m,
118
+ `domain: ${target.domain}`
119
+ );
120
+ } else {
121
+ // Insert domain after category line
122
+ updated = updated.replace(
123
+ /^(category:\s*.+)$/m,
124
+ `$1\ndomain: ${target.domain}`
125
+ );
126
+ }
127
+
128
+ return updated;
129
+ }
130
+
131
+ function main() {
132
+ const skills = fs.readdirSync(SKILLS_DIR, { withFileTypes: true })
133
+ .filter(d => d.isDirectory())
134
+ .map(d => d.name);
135
+
136
+ let changed = 0;
137
+ let skipped = 0;
138
+
139
+ for (const skill of skills) {
140
+ const skillPath = path.join(SKILLS_DIR, skill, 'SKILL.md');
141
+ if (!fs.existsSync(skillPath)) continue;
142
+
143
+ const content = fs.readFileSync(skillPath, 'utf8');
144
+ const updated = updateFrontmatter(content, skill);
145
+
146
+ if (updated === null) {
147
+ skipped++;
148
+ continue;
149
+ }
150
+
151
+ if (updated === content) {
152
+ // mapping exists but content unchanged (already correct)
153
+ continue;
154
+ }
155
+
156
+ if (DRY_RUN) {
157
+ const oldCat = (content.match(/^category:\s*(.+)$/m) || [])[1];
158
+ const oldDom = (content.match(/^domain:\s*(.+)$/m) || [])[1] || '(none)';
159
+ console.log(`${skill.padEnd(40)} ${oldCat} → ${MAPPING[skill].category} | domain ${oldDom} → ${MAPPING[skill].domain}`);
160
+ } else {
161
+ fs.writeFileSync(skillPath, updated);
162
+ }
163
+ changed++;
164
+ }
165
+
166
+ console.log(`\n${DRY_RUN ? '[DRY-RUN] would change' : 'Changed'}: ${changed}, skipped (no mapping): ${skipped}, total: ${skills.length}`);
167
+ }
168
+
169
+ main();
@@ -0,0 +1,424 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Skill Graph codemod: schema_version 2 → 3.
4
+ *
5
+ * Transforms a SKILL.md (or a directory of skill directories) from the v2
6
+ * frontmatter shape to the v3 protocol shape. v3 introduces a small number of
7
+ * breaking shape changes and one rename; all new v3 fields are optional and
8
+ * are intentionally NOT inserted by this codemod — authors opt into them
9
+ * individually when the added metadata is real.
10
+ *
11
+ * Transformations:
12
+ *
13
+ * 1. schema_version: 2 → schema_version: 3
14
+ * 2. family: <value> → browse_category: <value> (rename)
15
+ * 3. drift_check: "YYYY-..." → drift_check: (date → object)
16
+ * last_verified: "YYYY-..."
17
+ * 4. compatibility: "<text>" → compatibility: (string → object)
18
+ * notes: "<text>"
19
+ *
20
+ * What this codemod does NOT do:
21
+ * - Does not insert `project_tags`, `category`, `runtime_telemetry`,
22
+ * `lifecycle`, or `truth_source_hashes`. These are opt-in in v3; adding
23
+ * stub values would inflate the frontmatter and create fake metadata.
24
+ * - Does not convert `relations.boundary` or `relations.depends_on` items
25
+ * to the `{skill, reason}` / `{skill, min_version}` object form. The
26
+ * bare-string form is still valid in v3; upgrade item-by-item when the
27
+ * reason or min_version is real.
28
+ * - Does not attempt to parse freeform `compatibility` strings into
29
+ * `runtimes` / `node`. The string is moved verbatim into `notes`; the
30
+ * author upgrades to structured fields manually.
31
+ *
32
+ * Line-based transformation: preserves comments, quoting style, and
33
+ * indentation as authored. If a skill uses non-standard YAML (multi-line
34
+ * strings, anchors, flow sequences) and a transformation cannot be applied
35
+ * safely, the codemod reports the file as "needs manual migration" without
36
+ * corrupting it.
37
+ *
38
+ * Usage:
39
+ * node scripts/migrate-skill-v2-to-v3.js <path> # migrate one file or dir
40
+ * node scripts/migrate-skill-v2-to-v3.js skills/ # migrate every skill
41
+ * node scripts/migrate-skill-v2-to-v3.js --dry-run <path> # show diffs, do not write
42
+ * node scripts/migrate-skill-v2-to-v3.js --include-template # also migrate examples/skill-metadata-template.md
43
+ *
44
+ * Self-contained. Only uses Node built-ins — no external dependencies.
45
+ * Exit 0 on success, 1 if any file needed manual migration or failed to parse.
46
+ */
47
+
48
+ 'use strict';
49
+
50
+ const fs = require('fs');
51
+ const path = require('path');
52
+ const { workspaceRoot } = require('./lib/roots');
53
+
54
+ const REPO_ROOT = workspaceRoot();
55
+ const SKILLS_DIR = path.join(REPO_ROOT, 'skills');
56
+ const TEMPLATE_PATH = path.join(REPO_ROOT, 'examples', 'skill-metadata-template.md');
57
+
58
+ // ---------------------------------------------------------------------------
59
+ // Frontmatter extraction — line-level so we can preserve the body verbatim.
60
+ // ---------------------------------------------------------------------------
61
+
62
+ /**
63
+ * Split a Markdown document into (frontmatterLines, bodyText). Returns null
64
+ * if no frontmatter block is found.
65
+ */
66
+ function splitFrontmatter(text) {
67
+ const lines = text.split('\n');
68
+ if (lines[0] !== '---') return null;
69
+ let closeIdx = -1;
70
+ for (let i = 1; i < lines.length; i++) {
71
+ if (lines[i] === '---') { closeIdx = i; break; }
72
+ }
73
+ if (closeIdx === -1) return null;
74
+ const frontmatter = lines.slice(1, closeIdx);
75
+ const body = lines.slice(closeIdx + 1).join('\n');
76
+ return { frontmatter, body };
77
+ }
78
+
79
+ /**
80
+ * Rejoin frontmatter lines + body into a full document.
81
+ */
82
+ function joinDocument(frontmatter, body) {
83
+ return '---\n' + frontmatter.join('\n') + '\n---\n' + body;
84
+ }
85
+
86
+ // ---------------------------------------------------------------------------
87
+ // Transformations
88
+ //
89
+ // Each transformation returns:
90
+ // { changed: boolean, lines: string[], note?: string, error?: string }
91
+ //
92
+ // On error, the caller aborts the migration for that file and reports manually.
93
+ // ---------------------------------------------------------------------------
94
+
95
+ /**
96
+ * T1: schema_version 2 → 3.
97
+ */
98
+ function transformSchemaVersion(lines) {
99
+ const out = [];
100
+ let changed = false;
101
+ for (const line of lines) {
102
+ const m = line.match(/^(\s*)schema_version\s*:\s*("?)(\d+)("?)\s*(#.*)?$/);
103
+ if (m && m[3] === '2') {
104
+ out.push(`${m[1]}schema_version: 3${m[5] ? ' ' + m[5] : ''}`);
105
+ changed = true;
106
+ } else {
107
+ out.push(line);
108
+ }
109
+ }
110
+ return { changed, lines: out };
111
+ }
112
+
113
+ /**
114
+ * T2: family → browse_category (simple rename; value unchanged).
115
+ */
116
+ function transformFamilyRename(lines) {
117
+ const out = [];
118
+ let changed = false;
119
+ for (const line of lines) {
120
+ const m = line.match(/^(\s*)family(\s*:\s*)(.*)$/);
121
+ if (m && !/browse_category/.test(line)) {
122
+ out.push(`${m[1]}browse_category${m[2]}${m[3]}`);
123
+ changed = true;
124
+ } else {
125
+ out.push(line);
126
+ }
127
+ }
128
+ return { changed, lines: out };
129
+ }
130
+
131
+ /**
132
+ * T3: drift_check: "date" → drift_check:\n last_verified: "date".
133
+ *
134
+ * Only transforms when drift_check is a scalar on a single line. If the
135
+ * value is already an object block (no scalar on the key line), no change.
136
+ */
137
+ function transformDriftCheck(lines) {
138
+ const out = [];
139
+ let changed = false;
140
+ let error = null;
141
+
142
+ for (let i = 0; i < lines.length; i++) {
143
+ const line = lines[i];
144
+ const m = line.match(/^(\s*)drift_check\s*:\s*(.*?)(\s*#.*)?$/);
145
+ if (!m) { out.push(line); continue; }
146
+
147
+ const indent = m[1];
148
+ const value = (m[2] || '').trim();
149
+ const trailingComment = m[3] || '';
150
+
151
+ // Already an object (no scalar on the same line) — skip.
152
+ if (value === '') { out.push(line); continue; }
153
+
154
+ // Extract the ISO date regardless of quoting style.
155
+ const dateMatch = value.match(/^"(\d{4}-\d{2}-\d{2})"$|^'(\d{4}-\d{2}-\d{2})'$|^(\d{4}-\d{2}-\d{2})$/);
156
+ if (!dateMatch) {
157
+ error = `drift_check value is not an ISO date on a single line — manual migration required. Line: ${line.trim()}`;
158
+ out.push(line);
159
+ continue;
160
+ }
161
+ const date = dateMatch[1] || dateMatch[2] || dateMatch[3];
162
+
163
+ out.push(`${indent}drift_check:${trailingComment ? ' ' + trailingComment : ''}`);
164
+ out.push(`${indent} last_verified: "${date}"`);
165
+ changed = true;
166
+ }
167
+
168
+ return { changed, lines: out, error };
169
+ }
170
+
171
+ /**
172
+ * T4: compatibility: "string" → compatibility:\n notes: "string".
173
+ *
174
+ * Only transforms when compatibility is a scalar string on a single line.
175
+ */
176
+ function transformCompatibility(lines) {
177
+ const out = [];
178
+ let changed = false;
179
+ let error = null;
180
+
181
+ for (let i = 0; i < lines.length; i++) {
182
+ const line = lines[i];
183
+ const m = line.match(/^(\s*)compatibility\s*:\s*(.*?)(\s*#.*)?$/);
184
+ if (!m) { out.push(line); continue; }
185
+
186
+ const indent = m[1];
187
+ const value = (m[2] || '').trim();
188
+ const trailingComment = m[3] || '';
189
+
190
+ // Already an object (no scalar on the same line) — skip.
191
+ if (value === '') { out.push(line); continue; }
192
+
193
+ // Extract the string value regardless of quoting style.
194
+ let stringValue;
195
+ if (/^".*"$/.test(value)) {
196
+ stringValue = value.slice(1, -1);
197
+ } else if (/^'.*'$/.test(value)) {
198
+ stringValue = value.slice(1, -1);
199
+ } else {
200
+ stringValue = value;
201
+ }
202
+
203
+ // Escape any embedded double quotes for safe YAML double-quoted re-emit.
204
+ const escaped = stringValue.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
205
+
206
+ out.push(`${indent}compatibility:${trailingComment ? ' ' + trailingComment : ''}`);
207
+ out.push(`${indent} notes: "${escaped}"`);
208
+ changed = true;
209
+ }
210
+
211
+ return { changed, lines: out, error };
212
+ }
213
+
214
+ // ---------------------------------------------------------------------------
215
+ // Per-file migration pipeline.
216
+ // ---------------------------------------------------------------------------
217
+
218
+ /**
219
+ * Apply the full v2 → v3 transformation pipeline to a single SKILL.md file.
220
+ *
221
+ * @returns {object} { changed, newText, note, error }
222
+ */
223
+ function migrateFile(text) {
224
+ const split = splitFrontmatter(text);
225
+ if (!split) return { changed: false, newText: text, error: 'no frontmatter block found' };
226
+
227
+ let lines = split.frontmatter;
228
+ let anyChanged = false;
229
+ const errors = [];
230
+
231
+ const pipeline = [
232
+ { name: 'schema_version', fn: transformSchemaVersion },
233
+ { name: 'family', fn: transformFamilyRename },
234
+ { name: 'drift_check', fn: transformDriftCheck },
235
+ { name: 'compatibility', fn: transformCompatibility },
236
+ ];
237
+
238
+ for (const step of pipeline) {
239
+ const result = step.fn(lines);
240
+ if (result.error) errors.push(`${step.name}: ${result.error}`);
241
+ if (result.changed) anyChanged = true;
242
+ lines = result.lines;
243
+ }
244
+
245
+ const newText = joinDocument(lines, split.body);
246
+ return {
247
+ changed: anyChanged,
248
+ newText,
249
+ error: errors.length > 0 ? errors.join('; ') : null,
250
+ };
251
+ }
252
+
253
+ // ---------------------------------------------------------------------------
254
+ // CLI entry.
255
+ // ---------------------------------------------------------------------------
256
+
257
+ function collectTargets(args) {
258
+ const includeTemplate = args.includes('--include-template');
259
+ const explicit = args.filter(a => !a.startsWith('--'));
260
+ const targets = [];
261
+
262
+ if (explicit.length === 0) {
263
+ if (fs.existsSync(SKILLS_DIR)) {
264
+ for (const name of fs.readdirSync(SKILLS_DIR).sort()) {
265
+ const skillMd = path.join(SKILLS_DIR, name, 'SKILL.md');
266
+ if (fs.existsSync(skillMd)) targets.push(skillMd);
267
+ }
268
+ }
269
+ if (includeTemplate && fs.existsSync(TEMPLATE_PATH)) targets.push(TEMPLATE_PATH);
270
+ return targets;
271
+ }
272
+
273
+ for (const arg of explicit) {
274
+ const abs = path.resolve(arg);
275
+ if (!fs.existsSync(abs)) {
276
+ console.error(`ERROR ${arg}: path does not exist`);
277
+ process.exit(1);
278
+ }
279
+ const stat = fs.statSync(abs);
280
+ if (stat.isDirectory()) {
281
+ // Directory: if it contains SKILL.md, migrate that; otherwise walk for skill dirs.
282
+ const skillMd = path.join(abs, 'SKILL.md');
283
+ if (fs.existsSync(skillMd)) {
284
+ targets.push(skillMd);
285
+ } else {
286
+ for (const name of fs.readdirSync(abs).sort()) {
287
+ const nested = path.join(abs, name, 'SKILL.md');
288
+ if (fs.existsSync(nested)) targets.push(nested);
289
+ }
290
+ }
291
+ } else if (abs.endsWith('.md')) {
292
+ targets.push(abs);
293
+ }
294
+ }
295
+
296
+ return targets;
297
+ }
298
+
299
+ /**
300
+ * Produce a simple unified-ish diff summary (line-granular) for --dry-run.
301
+ */
302
+ function diffSummary(oldText, newText) {
303
+ const oldLines = oldText.split('\n');
304
+ const newLines = newText.split('\n');
305
+ const diffs = [];
306
+ // Use a cheap line-by-line comparison; sufficient for frontmatter diffs.
307
+ const maxLen = Math.max(oldLines.length, newLines.length);
308
+ for (let i = 0; i < maxLen; i++) {
309
+ if (oldLines[i] !== newLines[i]) {
310
+ if (oldLines[i] !== undefined) diffs.push(` - ${oldLines[i]}`);
311
+ if (newLines[i] !== undefined) diffs.push(` + ${newLines[i]}`);
312
+ }
313
+ }
314
+ return diffs.join('\n');
315
+ }
316
+
317
+ function main() {
318
+ const args = process.argv.slice(2);
319
+
320
+ if (args.includes('--help') || args.includes('-h')) {
321
+ console.log('Usage: migrate-skill-v2-to-v3.js [--dry-run] [--all] [--skill <name>] [--include-template] [<path>...]');
322
+ console.log('Default mode writes files. Use --dry-run to preview without writing.');
323
+ console.log('Must specify --all, --skill <name>, or a positional <path>.');
324
+ process.exit(0);
325
+ }
326
+
327
+ const dryRun = args.includes('--dry-run');
328
+ const all = args.includes('--all');
329
+ const skillIdx = args.indexOf('--skill');
330
+ const skill = skillIdx !== -1 ? args[skillIdx + 1] : null;
331
+
332
+ if (skillIdx !== -1 && (!skill || skill.startsWith('--'))) {
333
+ console.error('ERROR: --skill requires a skill name argument.');
334
+ process.exit(1);
335
+ }
336
+
337
+ // Exclude the --skill flag and its value from positionals.
338
+ const positionals = args.filter((a, i) => {
339
+ if (a.startsWith('--')) return false;
340
+ if (skillIdx !== -1 && i === skillIdx + 1) return false; // skip --skill's value
341
+ return true;
342
+ });
343
+
344
+ if (!all && !skill && positionals.length === 0) {
345
+ console.error('ERROR: must specify --skill <name>, --all, or a target path.');
346
+ process.exit(1);
347
+ }
348
+
349
+ // Build the effective args list for collectTargets, preserving --include-template.
350
+ // collectTargets accepts a flag-and-path mixed argv and handles both.
351
+ let effectiveArgs;
352
+ if (skill) {
353
+ const includeFlag = args.includes('--include-template') ? ['--include-template'] : [];
354
+ effectiveArgs = [...includeFlag, path.join(SKILLS_DIR, skill)];
355
+ } else if (all) {
356
+ // Pass only flags; collectTargets defaults to SKILLS_DIR when no positionals.
357
+ effectiveArgs = args.filter(a => a.startsWith('--'));
358
+ } else {
359
+ effectiveArgs = args.filter(a => a.startsWith('--')).concat(positionals);
360
+ }
361
+
362
+ const targets = collectTargets(effectiveArgs);
363
+
364
+ if (skill && targets.length === 0) {
365
+ console.error(`ERROR: skill '${skill}' not found. Check the skill name and try again.`);
366
+ process.exit(1);
367
+ }
368
+
369
+ if (targets.length === 0) {
370
+ // When scanning all skills (--all or a directory), 0 results means the
371
+ // target is already fully migrated or the skills/ directory is empty — not
372
+ // an error. Only treat 0 results as an error when an explicit positional
373
+ // path was provided (the user pointed at something that should exist).
374
+ if (all || positionals.length === 0) {
375
+ console.log('0 file(s) processed, 0 would migrate, 0 needing manual attention.');
376
+ process.exit(0);
377
+ }
378
+ console.error('No SKILL.md files found to migrate.');
379
+ process.exit(1);
380
+ }
381
+
382
+ let changedCount = 0;
383
+ let errorCount = 0;
384
+
385
+ for (const filePath of targets) {
386
+ const rel = path.relative(REPO_ROOT, filePath);
387
+ let text;
388
+ try {
389
+ text = fs.readFileSync(filePath, 'utf8');
390
+ } catch (e) {
391
+ console.error(`ERROR ${rel}: cannot read — ${e.message}`);
392
+ errorCount++;
393
+ continue;
394
+ }
395
+
396
+ const result = migrateFile(text);
397
+
398
+ if (result.error) {
399
+ console.error(`MANUAL ${rel}: ${result.error}`);
400
+ errorCount++;
401
+ continue;
402
+ }
403
+
404
+ if (!result.changed) {
405
+ console.log(`SKIP ${rel} (already v3 or nothing to migrate)`);
406
+ continue;
407
+ }
408
+
409
+ if (dryRun) {
410
+ console.log(`DIFF ${rel}`);
411
+ console.log(diffSummary(text, result.newText));
412
+ } else {
413
+ fs.writeFileSync(filePath, result.newText, 'utf8');
414
+ console.log(`OK ${rel}`);
415
+ }
416
+ changedCount++;
417
+ }
418
+
419
+ const verb = dryRun ? 'would migrate' : 'migrated';
420
+ console.log(`\n${targets.length} file(s) processed, ${changedCount} ${verb}, ${errorCount} needing manual attention.`);
421
+ process.exit(errorCount > 0 ? 1 : 0);
422
+ }
423
+
424
+ main();