@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,200 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Skill Graph codemod: schema_version 3 -> 4.
|
|
4
|
+
*
|
|
5
|
+
* Transforms a SKILL.md file, a skill directory, or a directory of skills from
|
|
6
|
+
* the v3 frontmatter shape to the v4 naming contract.
|
|
7
|
+
*
|
|
8
|
+
* Transformations:
|
|
9
|
+
*
|
|
10
|
+
* 1. schema_version: 3 -> schema_version: 4
|
|
11
|
+
* 2. skill.v3.schema.json -> skill.v4.schema.json
|
|
12
|
+
* 3. browse_category: <value> -> category: <value>
|
|
13
|
+
* 4. category: <path> -> domain: <path>
|
|
14
|
+
* 5. category_path: <path> -> domain: <path>
|
|
15
|
+
* 6. project_tags: [...] -> workspace_tags: [...]
|
|
16
|
+
* 7. routing_groups: [...] -> routing_bundles: [...]
|
|
17
|
+
*
|
|
18
|
+
* What this codemod does NOT do:
|
|
19
|
+
* - Does not author a workspace config. Workspace/project ownership is
|
|
20
|
+
* generated from `.skill-graph/config.json`, not written into portable
|
|
21
|
+
* SKILL.md files.
|
|
22
|
+
* - Does not infer missing domain paths. If a v3 skill had only
|
|
23
|
+
* browse_category, v4 keeps only category.
|
|
24
|
+
*
|
|
25
|
+
* Line-based transformation: preserves comments, quoting style, and
|
|
26
|
+
* indentation as authored. It only rewrites top-level frontmatter keys.
|
|
27
|
+
*
|
|
28
|
+
* Usage:
|
|
29
|
+
* node scripts/migrate-skill-v3-to-v4.js <path>
|
|
30
|
+
* node scripts/migrate-skill-v3-to-v4.js skills/
|
|
31
|
+
* node scripts/migrate-skill-v3-to-v4.js --dry-run <path>
|
|
32
|
+
* node scripts/migrate-skill-v3-to-v4.js --include-template <path>
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
'use strict';
|
|
36
|
+
|
|
37
|
+
const fs = require('fs');
|
|
38
|
+
const path = require('path');
|
|
39
|
+
const { workspaceRoot } = require('./lib/roots');
|
|
40
|
+
|
|
41
|
+
const REPO_ROOT = workspaceRoot();
|
|
42
|
+
const TEMPLATE_PATH = path.join(REPO_ROOT, 'examples', 'skill-metadata-template.md');
|
|
43
|
+
|
|
44
|
+
function splitFrontmatter(text) {
|
|
45
|
+
const lines = text.replace(/\r\n/g, '\n').split('\n');
|
|
46
|
+
if (lines[0] !== '---') return null;
|
|
47
|
+
let closeIdx = -1;
|
|
48
|
+
for (let i = 1; i < lines.length; i++) {
|
|
49
|
+
if (lines[i] === '---') { closeIdx = i; break; }
|
|
50
|
+
}
|
|
51
|
+
if (closeIdx === -1) return null;
|
|
52
|
+
return { lines, closeIdx };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function migrateLine(line) {
|
|
56
|
+
if (line.includes('https://skillgraph.dev/schemas/skill.v3.schema.json')) {
|
|
57
|
+
return line.replace('https://skillgraph.dev/schemas/skill.v3.schema.json', 'https://skillgraph.dev/schemas/skill.v4.schema.json');
|
|
58
|
+
}
|
|
59
|
+
let m = line.match(/^(\s*)schema_version(\s*:\s*)(["']?)3\3(\s*(?:#.*)?)$/);
|
|
60
|
+
if (m) return `${m[1]}schema_version${m[2]}${m[3]}4${m[3]}${m[4]}`;
|
|
61
|
+
|
|
62
|
+
m = line.match(/^(\s*)browse_category(\s*:.*)$/);
|
|
63
|
+
if (m) return `${m[1]}category${m[2]}`;
|
|
64
|
+
|
|
65
|
+
m = line.match(/^(\s*)category_path(\s*:.*)$/);
|
|
66
|
+
if (m) return `${m[1]}domain${m[2]}`;
|
|
67
|
+
|
|
68
|
+
m = line.match(/^(\s*)category(\s*:.*)$/);
|
|
69
|
+
if (m) return `${m[1]}domain${m[2]}`;
|
|
70
|
+
|
|
71
|
+
m = line.match(/^(\s*)project_tags(\s*:.*)$/);
|
|
72
|
+
if (m) return `${m[1]}workspace_tags${m[2]}`;
|
|
73
|
+
|
|
74
|
+
m = line.match(/^(\s*)routing_groups(\s*:.*)$/);
|
|
75
|
+
if (m) return `${m[1]}routing_bundles${m[2]}`;
|
|
76
|
+
|
|
77
|
+
return line;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function migrateFile(filePath) {
|
|
81
|
+
const oldText = fs.readFileSync(filePath, 'utf8');
|
|
82
|
+
const split = splitFrontmatter(oldText);
|
|
83
|
+
if (!split) return { changed: false, newText: oldText, error: 'no frontmatter block found' };
|
|
84
|
+
|
|
85
|
+
const lines = split.lines.slice();
|
|
86
|
+
let changed = false;
|
|
87
|
+
for (let i = 1; i < split.closeIdx; i++) {
|
|
88
|
+
const next = migrateLine(lines[i]);
|
|
89
|
+
if (next !== lines[i]) {
|
|
90
|
+
lines[i] = next;
|
|
91
|
+
changed = true;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return { changed, newText: lines.join('\n'), error: null };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function collectSkillFiles(targetPath, out = []) {
|
|
98
|
+
if (!fs.existsSync(targetPath)) return out;
|
|
99
|
+
const stat = fs.statSync(targetPath);
|
|
100
|
+
if (stat.isFile()) {
|
|
101
|
+
if (path.basename(targetPath) === 'SKILL.md' || targetPath.endsWith('.md')) out.push(targetPath);
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
104
|
+
const directSkill = path.join(targetPath, 'SKILL.md');
|
|
105
|
+
if (fs.existsSync(directSkill)) out.push(directSkill);
|
|
106
|
+
for (const entry of fs.readdirSync(targetPath, { withFileTypes: true })) {
|
|
107
|
+
if (entry.isDirectory()) collectSkillFiles(path.join(targetPath, entry.name), out);
|
|
108
|
+
}
|
|
109
|
+
return [...new Set(out)];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function parseArgs(argv) {
|
|
113
|
+
if (argv.includes('--help') || argv.includes('-h')) {
|
|
114
|
+
console.log('Usage: migrate-skill-v3-to-v4.js [--dry-run] [--all] [--skill <name>] [--include-template] [<path>...]');
|
|
115
|
+
console.log('Default mode writes files. Use --dry-run to preview without writing.');
|
|
116
|
+
console.log('Must specify --all, --skill <name>, or a positional <path>.');
|
|
117
|
+
process.exit(0);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const skillIdx = argv.indexOf('--skill');
|
|
121
|
+
const skill = skillIdx !== -1 ? argv[skillIdx + 1] : null;
|
|
122
|
+
// Positional targets are non-flag args, excluding the --skill value if present.
|
|
123
|
+
const targets = argv.filter((a, i) => {
|
|
124
|
+
if (a.startsWith('--')) return false;
|
|
125
|
+
if (skillIdx !== -1 && i === skillIdx + 1) return false;
|
|
126
|
+
return true;
|
|
127
|
+
});
|
|
128
|
+
return {
|
|
129
|
+
dryRun: argv.includes('--dry-run'),
|
|
130
|
+
all: argv.includes('--all'),
|
|
131
|
+
includeTemplate: argv.includes('--include-template'),
|
|
132
|
+
skill,
|
|
133
|
+
targets,
|
|
134
|
+
hasSkillFlag: skillIdx !== -1,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function main() {
|
|
139
|
+
const args = parseArgs(process.argv.slice(2));
|
|
140
|
+
|
|
141
|
+
// Arg validation: require at least one targeting argument.
|
|
142
|
+
if (args.hasSkillFlag && (!args.skill || args.skill.startsWith('--'))) {
|
|
143
|
+
console.error('ERROR: --skill requires a skill name argument.');
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
if (!args.all && !args.skill && args.targets.length === 0) {
|
|
147
|
+
console.error('ERROR: must specify --skill <name>, --all, or a target path.');
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
let targetArgs;
|
|
152
|
+
if (args.skill) {
|
|
153
|
+
targetArgs = [path.join('skills', args.skill)];
|
|
154
|
+
} else if (args.all) {
|
|
155
|
+
targetArgs = ['skills'];
|
|
156
|
+
} else {
|
|
157
|
+
targetArgs = args.targets;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const targets = [];
|
|
161
|
+
for (const target of targetArgs) {
|
|
162
|
+
targets.push(...collectSkillFiles(path.resolve(REPO_ROOT, target)));
|
|
163
|
+
}
|
|
164
|
+
if (args.includeTemplate) targets.push(TEMPLATE_PATH);
|
|
165
|
+
|
|
166
|
+
if (args.skill && targets.length === 0) {
|
|
167
|
+
console.error(`ERROR: skill '${args.skill}' not found. Check the skill name and try again.`);
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
let changed = 0;
|
|
172
|
+
let failed = 0;
|
|
173
|
+
for (const filePath of [...new Set(targets)]) {
|
|
174
|
+
const rel = path.relative(REPO_ROOT, filePath).split(path.sep).join('/');
|
|
175
|
+
const result = migrateFile(filePath);
|
|
176
|
+
if (result.error) {
|
|
177
|
+
console.error(`FAIL ${rel}: ${result.error}`);
|
|
178
|
+
failed++;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (!result.changed) {
|
|
182
|
+
console.log(`SKIP ${rel} (already v4 or nothing to migrate)`);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
changed++;
|
|
186
|
+
if (args.dryRun) {
|
|
187
|
+
console.log(`WOULD ${rel}`);
|
|
188
|
+
} else {
|
|
189
|
+
fs.writeFileSync(filePath, result.newText, 'utf8');
|
|
190
|
+
console.log(`OK ${rel}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
console.log(`${args.dryRun ? 'Would migrate' : 'Migrated'} ${changed} file(s); ${failed} failed.`);
|
|
195
|
+
if (failed > 0) process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (require.main === module) main();
|
|
199
|
+
|
|
200
|
+
module.exports = { migrateFile, migrateLine };
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Skill Graph codemod: schema_version 5 -> 6.
|
|
4
|
+
*
|
|
5
|
+
* Transforms a SKILL.md file, a skill directory, or a directory of skills from
|
|
6
|
+
* the v5 frontmatter shape to v6.
|
|
7
|
+
*
|
|
8
|
+
* Transformations:
|
|
9
|
+
*
|
|
10
|
+
* 1. schema_version: "5" | 5 -> schema_version: 6
|
|
11
|
+
* 2. metadata.concept.{mental_model,
|
|
12
|
+
* purpose, boundary, analogy,
|
|
13
|
+
* misconception} -> flat top-level fields under metadata:
|
|
14
|
+
* (only when nested sub-field is NOT pipe-empty; v6 retires `taxonomy`)
|
|
15
|
+
* 3. concept block retained for v5 back-compat (v6 schema anyOf rule)
|
|
16
|
+
* 4. Body `## Concept Card` section retention: this codemod does NOT touch
|
|
17
|
+
* body content. Body section is retired in v6 but removal is out of
|
|
18
|
+
* scope here — handle separately if needed.
|
|
19
|
+
*
|
|
20
|
+
* What this codemod does NOT do:
|
|
21
|
+
* - Does not author flat fields when nested sub-field is pipe-empty ("|") or
|
|
22
|
+
* missing. The comprehension grader surfaces the gap; hand-authoring
|
|
23
|
+
* produces better content than null-promotion.
|
|
24
|
+
* - Does not lift `definition` (covered by `description`) or `taxonomy`
|
|
25
|
+
* (covered by `category` + `relations.broader`). Both are retired in v6.
|
|
26
|
+
* - Does not delete the nested concept block. The v6 schema retains it via
|
|
27
|
+
* anyOf for v5 back-compat; the migration doc explains the deprecation.
|
|
28
|
+
* - Does not bump body content, headings, or `## Concept Card` sections.
|
|
29
|
+
*
|
|
30
|
+
* Line-based transformation: preserves comments, quoting style, and
|
|
31
|
+
* indentation as authored. Only inserts flat fields and rewrites the
|
|
32
|
+
* schema_version line.
|
|
33
|
+
*
|
|
34
|
+
* Usage:
|
|
35
|
+
* node scripts/migrate-skill-v5-to-v6.js <path>
|
|
36
|
+
* node scripts/migrate-skill-v5-to-v6.js skills/
|
|
37
|
+
* node scripts/migrate-skill-v5-to-v6.js --dry-run <path> # default
|
|
38
|
+
* node scripts/migrate-skill-v5-to-v6.js --apply <path>
|
|
39
|
+
* node scripts/migrate-skill-v5-to-v6.js --skill ref-patterns
|
|
40
|
+
*
|
|
41
|
+
* Default mode is dry-run. Pass --apply to write changes.
|
|
42
|
+
*
|
|
43
|
+
* See: skill-metadata-protocol/docs/migrations/v5-to-v6.md
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
'use strict';
|
|
47
|
+
|
|
48
|
+
const fs = require('fs');
|
|
49
|
+
const path = require('path');
|
|
50
|
+
const { workspaceRoot } = require('./lib/roots');
|
|
51
|
+
|
|
52
|
+
const REPO_ROOT = workspaceRoot();
|
|
53
|
+
const V6_FIELDS = ['mental_model', 'purpose', 'boundary', 'analogy', 'misconception'];
|
|
54
|
+
|
|
55
|
+
function splitFrontmatter(text) {
|
|
56
|
+
const lines = text.replace(/\r\n/g, '\n').split('\n');
|
|
57
|
+
if (lines[0] !== '---') return null;
|
|
58
|
+
let closeIdx = -1;
|
|
59
|
+
for (let i = 1; i < lines.length; i++) {
|
|
60
|
+
if (lines[i] === '---') { closeIdx = i; break; }
|
|
61
|
+
}
|
|
62
|
+
if (closeIdx === -1) return null;
|
|
63
|
+
return { lines, closeIdx };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Find a line `<indent><field>:<rest>` within [start, end). Returns
|
|
67
|
+
// { index, indent, rest } or null. Matches indent ≤ 4 spaces (root or
|
|
68
|
+
// under `metadata:`).
|
|
69
|
+
function findFieldLine(lines, field, start, end) {
|
|
70
|
+
const re = new RegExp(`^([ \\t]{0,4})${field}:[ \\t]*(.*)$`);
|
|
71
|
+
for (let i = start; i < end; i++) {
|
|
72
|
+
const m = lines[i].match(re);
|
|
73
|
+
if (m) return { index: i, indent: m[1], rest: m[2] };
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Unwrap a YAML-quoted JSON-stringified string into a plain JSON string.
|
|
79
|
+
// Examples:
|
|
80
|
+
// '"{\\"definition\\":\\"...\\"}"' -> '{"definition":"..."}'
|
|
81
|
+
// '{"definition":"..."}' -> unchanged
|
|
82
|
+
function unwrapQuotedJson(raw) {
|
|
83
|
+
let s = raw.trim();
|
|
84
|
+
if ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'"))) {
|
|
85
|
+
s = s.slice(1, -1);
|
|
86
|
+
}
|
|
87
|
+
// Unescape YAML-escaped quotes and newlines.
|
|
88
|
+
s = s.replace(/\\"/g, '"').replace(/\\n/g, '\n');
|
|
89
|
+
return s;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function parseConceptValue(raw) {
|
|
93
|
+
const s = unwrapQuotedJson(raw);
|
|
94
|
+
if (!s.startsWith('{')) return null;
|
|
95
|
+
try {
|
|
96
|
+
return JSON.parse(s);
|
|
97
|
+
} catch (e) {
|
|
98
|
+
return { __parseError: e.message };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function isPipeEmpty(v) {
|
|
103
|
+
if (v === undefined || v === null) return true;
|
|
104
|
+
const s = String(v).trim();
|
|
105
|
+
return s === '' || s === '|' || s === '""' || s === "''";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Format a string value as a YAML field. Single-line -> double-quoted with
|
|
109
|
+
// escaping. Multi-line -> block-scalar with `|` and proper indentation.
|
|
110
|
+
function formatFieldValue(indent, field, value) {
|
|
111
|
+
const str = String(value);
|
|
112
|
+
if (!str.includes('\n')) {
|
|
113
|
+
// Single-line: double-quote with embedded-quote and backslash escape.
|
|
114
|
+
const escaped = str.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
115
|
+
return `${indent}${field}: "${escaped}"`;
|
|
116
|
+
}
|
|
117
|
+
// Multi-line: block-scalar (|) at +2 indent
|
|
118
|
+
const childIndent = indent + ' ';
|
|
119
|
+
const body = str.split('\n').map(line => `${childIndent}${line}`).join('\n');
|
|
120
|
+
return `${indent}${field}: |\n${body}`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function migrateFile(filePath) {
|
|
124
|
+
const oldText = fs.readFileSync(filePath, 'utf8');
|
|
125
|
+
const split = splitFrontmatter(oldText);
|
|
126
|
+
if (!split) return { changed: false, newText: oldText, error: 'no frontmatter block found', stats: null };
|
|
127
|
+
|
|
128
|
+
const lines = split.lines.slice();
|
|
129
|
+
const fmStart = 1;
|
|
130
|
+
const fmEnd = split.closeIdx; // exclusive
|
|
131
|
+
|
|
132
|
+
const stats = {
|
|
133
|
+
schemaBumped: false,
|
|
134
|
+
fieldsLifted: [],
|
|
135
|
+
fieldsSkippedPipeEmpty: [],
|
|
136
|
+
fieldsSkippedAlreadyFlat: [],
|
|
137
|
+
conceptFound: false,
|
|
138
|
+
conceptParseError: null,
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// 1. Bump schema_version "5" | 5 -> 6
|
|
142
|
+
const schemaLine = findFieldLine(lines, 'schema_version', fmStart, fmEnd);
|
|
143
|
+
if (schemaLine) {
|
|
144
|
+
const val = schemaLine.rest.trim();
|
|
145
|
+
if (val === '"5"' || val === "'5'" || val === '5') {
|
|
146
|
+
lines[schemaLine.index] = `${schemaLine.indent}schema_version: 6`;
|
|
147
|
+
stats.schemaBumped = true;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// 2. Locate concept block (single-line JSON-stringified value at any indent ≤ 4).
|
|
152
|
+
const conceptLine = findFieldLine(lines, 'concept', fmStart, fmEnd);
|
|
153
|
+
if (!conceptLine) {
|
|
154
|
+
// No concept block to migrate; bump-only file.
|
|
155
|
+
return finalize();
|
|
156
|
+
}
|
|
157
|
+
stats.conceptFound = true;
|
|
158
|
+
|
|
159
|
+
const concept = parseConceptValue(conceptLine.rest);
|
|
160
|
+
if (!concept) {
|
|
161
|
+
// concept: value isn't a JSON object — maybe it's already a YAML-block-scalar
|
|
162
|
+
// shape (rare). Leave it untouched.
|
|
163
|
+
return finalize();
|
|
164
|
+
}
|
|
165
|
+
if (concept.__parseError) {
|
|
166
|
+
stats.conceptParseError = concept.__parseError;
|
|
167
|
+
return finalize();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// 3. For each v6 field, lift to flat top-level (at same indent as concept:)
|
|
171
|
+
// when nested sub-field is non-empty AND no flat field already exists.
|
|
172
|
+
const insertLines = [];
|
|
173
|
+
for (const field of V6_FIELDS) {
|
|
174
|
+
const existingFlat = findFieldLine(lines, field, fmStart, fmEnd);
|
|
175
|
+
if (existingFlat) {
|
|
176
|
+
stats.fieldsSkippedAlreadyFlat.push(field);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const nestedVal = concept[field];
|
|
180
|
+
if (isPipeEmpty(nestedVal)) {
|
|
181
|
+
stats.fieldsSkippedPipeEmpty.push(field);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
insertLines.push(formatFieldValue(conceptLine.indent, field, nestedVal));
|
|
185
|
+
stats.fieldsLifted.push(field);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (insertLines.length > 0) {
|
|
189
|
+
// Insert flat fields BEFORE the concept: line (so they read together,
|
|
190
|
+
// flat first as canonical, nested second as v5 back-compat).
|
|
191
|
+
lines.splice(conceptLine.index, 0, ...insertLines);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return finalize();
|
|
195
|
+
|
|
196
|
+
function finalize() {
|
|
197
|
+
const changed = stats.schemaBumped || stats.fieldsLifted.length > 0;
|
|
198
|
+
return { changed, newText: lines.join('\n'), error: null, stats };
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function collectSkillFiles(targetPath, out = []) {
|
|
203
|
+
if (!fs.existsSync(targetPath)) return out;
|
|
204
|
+
const stat = fs.statSync(targetPath);
|
|
205
|
+
if (stat.isFile()) {
|
|
206
|
+
if (path.basename(targetPath) === 'SKILL.md' || targetPath.endsWith('.md')) out.push(targetPath);
|
|
207
|
+
return out;
|
|
208
|
+
}
|
|
209
|
+
const directSkill = path.join(targetPath, 'SKILL.md');
|
|
210
|
+
if (fs.existsSync(directSkill)) out.push(directSkill);
|
|
211
|
+
for (const entry of fs.readdirSync(targetPath, { withFileTypes: true })) {
|
|
212
|
+
if (entry.isDirectory()) collectSkillFiles(path.join(targetPath, entry.name), out);
|
|
213
|
+
}
|
|
214
|
+
return [...new Set(out)];
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function parseArgs(argv) {
|
|
218
|
+
// Default to dry-run; --apply opts into mutation.
|
|
219
|
+
const args = { apply: false, skill: null, targets: [] };
|
|
220
|
+
for (let i = 0; i < argv.length; i++) {
|
|
221
|
+
const a = argv[i];
|
|
222
|
+
if (a === '--apply') args.apply = true;
|
|
223
|
+
else if (a === '--dry-run') args.apply = false;
|
|
224
|
+
else if (a === '--skill') args.skill = argv[++i];
|
|
225
|
+
else if (a === '--help' || a === '-h') {
|
|
226
|
+
console.log('Usage: migrate-skill-v5-to-v6.js [--dry-run|--apply] [--skill <name>] [<path>...]');
|
|
227
|
+
console.log('Default mode is dry-run. Default target is skills/.');
|
|
228
|
+
process.exit(0);
|
|
229
|
+
} else if (!a.startsWith('--')) args.targets.push(a);
|
|
230
|
+
}
|
|
231
|
+
return args;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function main() {
|
|
235
|
+
const args = parseArgs(process.argv.slice(2));
|
|
236
|
+
|
|
237
|
+
let targets = [];
|
|
238
|
+
if (args.skill) {
|
|
239
|
+
targets.push(path.resolve(REPO_ROOT, 'skills', args.skill, 'SKILL.md'));
|
|
240
|
+
} else {
|
|
241
|
+
const targetArgs = args.targets.length > 0 ? args.targets : ['skills'];
|
|
242
|
+
for (const target of targetArgs) {
|
|
243
|
+
targets.push(...collectSkillFiles(path.resolve(REPO_ROOT, target)));
|
|
244
|
+
}
|
|
245
|
+
targets = [...new Set(targets)];
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
let touched = 0;
|
|
249
|
+
let bumpedOnly = 0;
|
|
250
|
+
let liftedTotal = 0;
|
|
251
|
+
let parseErrors = 0;
|
|
252
|
+
let alreadyV6 = 0;
|
|
253
|
+
let noConcept = 0;
|
|
254
|
+
|
|
255
|
+
for (const filePath of targets) {
|
|
256
|
+
const rel = path.relative(REPO_ROOT, filePath).split(path.sep).join('/');
|
|
257
|
+
const result = migrateFile(filePath);
|
|
258
|
+
if (result.error) {
|
|
259
|
+
console.error(`FAIL ${rel}: ${result.error}`);
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
const s = result.stats || {};
|
|
263
|
+
if (s.conceptParseError) {
|
|
264
|
+
console.error(`PARSE ${rel}: concept JSON unparseable — ${s.conceptParseError}`);
|
|
265
|
+
parseErrors++;
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
if (!result.changed) {
|
|
269
|
+
if (!s.conceptFound) noConcept++;
|
|
270
|
+
else alreadyV6++;
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
touched++;
|
|
274
|
+
if (s.fieldsLifted.length === 0 && s.schemaBumped) bumpedOnly++;
|
|
275
|
+
liftedTotal += s.fieldsLifted.length;
|
|
276
|
+
|
|
277
|
+
const detail = [
|
|
278
|
+
s.schemaBumped ? 'schema:5→6' : null,
|
|
279
|
+
s.fieldsLifted.length > 0 ? `lift:[${s.fieldsLifted.join(',')}]` : null,
|
|
280
|
+
s.fieldsSkippedPipeEmpty.length > 0 ? `skip-empty:[${s.fieldsSkippedPipeEmpty.join(',')}]` : null,
|
|
281
|
+
s.fieldsSkippedAlreadyFlat.length > 0 ? `skip-flat:[${s.fieldsSkippedAlreadyFlat.join(',')}]` : null,
|
|
282
|
+
].filter(Boolean).join(' ');
|
|
283
|
+
|
|
284
|
+
if (args.apply) {
|
|
285
|
+
fs.writeFileSync(filePath, result.newText, 'utf8');
|
|
286
|
+
console.log(`OK ${rel} | ${detail}`);
|
|
287
|
+
} else {
|
|
288
|
+
console.log(`WOULD ${rel} | ${detail}`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
console.log('');
|
|
293
|
+
console.log(`${args.apply ? 'Migrated' : 'Would migrate'} ${touched} file(s).`);
|
|
294
|
+
console.log(` bumped-only (no liftable fields): ${bumpedOnly}`);
|
|
295
|
+
console.log(` total v6 fields lifted: ${liftedTotal}`);
|
|
296
|
+
console.log(` already v6 (no changes needed): ${alreadyV6}`);
|
|
297
|
+
console.log(` no concept block (v6-shaped): ${noConcept}`);
|
|
298
|
+
console.log(` concept JSON parse errors: ${parseErrors}`);
|
|
299
|
+
if (parseErrors > 0) process.exit(1);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (require.main === module) main();
|
|
303
|
+
|
|
304
|
+
module.exports = { migrateFile, parseConceptValue, formatFieldValue };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const fs = require("node:fs");
|
|
3
|
+
const path = require("node:path");
|
|
4
|
+
const { execSync } = require("node:child_process");
|
|
5
|
+
|
|
6
|
+
const ROOT = path.resolve(__dirname, "../../skills/skills");
|
|
7
|
+
const APPLY = process.argv.includes("--apply");
|
|
8
|
+
const QUORUM = 3;
|
|
9
|
+
const VALID = ["foundations", "engineering", "design", "quality", "agent", "product"];
|
|
10
|
+
|
|
11
|
+
function readFrontmatter(filePath) {
|
|
12
|
+
const lines = fs.readFileSync(filePath, "utf8").split("\n");
|
|
13
|
+
if (lines[0] !== "---") return { category: null, domain: null };
|
|
14
|
+
let inMeta = false, category = null, domain = null;
|
|
15
|
+
for (let i = 1; i < lines.length; i++) {
|
|
16
|
+
const line = lines[i];
|
|
17
|
+
if (line === "---") break;
|
|
18
|
+
if (/^metadata:\s*$/.test(line)) { inMeta = true; continue; }
|
|
19
|
+
if (inMeta && /^[a-zA-Z_]/.test(line)) inMeta = false;
|
|
20
|
+
if (inMeta) {
|
|
21
|
+
const c = line.match(/^\s+category:\s*['"]?([a-z]+)['"]?\s*$/);
|
|
22
|
+
if (c) category = c[1];
|
|
23
|
+
const d = line.match(/^\s+domain:\s*['"]?([a-z0-9/-]+)['"]?\s*$/);
|
|
24
|
+
if (d) domain = d[1];
|
|
25
|
+
} else {
|
|
26
|
+
const c = line.match(/^category:\s*['"]?([a-z]+)['"]?\s*$/);
|
|
27
|
+
if (c && !category) category = c[1];
|
|
28
|
+
const d = line.match(/^domain:\s*['"]?([a-z0-9/-]+)['"]?\s*$/);
|
|
29
|
+
if (d && !domain) domain = d[1];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return { category, domain };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const skills = [];
|
|
36
|
+
const errors = [];
|
|
37
|
+
for (const e of fs.readdirSync(ROOT, { withFileTypes: true })) {
|
|
38
|
+
if (!e.isDirectory() || e.name.startsWith("_") || e.name.startsWith(".")) continue;
|
|
39
|
+
const md = path.join(ROOT, e.name, "SKILL.md");
|
|
40
|
+
if (!fs.existsSync(md)) continue;
|
|
41
|
+
const fm = readFrontmatter(md);
|
|
42
|
+
if (!fm.category || !VALID.includes(fm.category)) {
|
|
43
|
+
errors.push({ name: e.name, reason: `category=${fm.category}` });
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
skills.push({ name: e.name, currentPath: path.join(ROOT, e.name), category: fm.category, domain: fm.domain });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const counts = new Map();
|
|
50
|
+
for (const s of skills) {
|
|
51
|
+
if (!s.domain) continue;
|
|
52
|
+
const k = `${s.category}/${s.domain}`;
|
|
53
|
+
counts.set(k, (counts.get(k) || 0) + 1);
|
|
54
|
+
}
|
|
55
|
+
const planned = skills.map((s) => {
|
|
56
|
+
const k = s.domain ? `${s.category}/${s.domain}` : null;
|
|
57
|
+
const useSub = k && counts.get(k) >= QUORUM;
|
|
58
|
+
const rel = useSub
|
|
59
|
+
? path.join(s.category, s.domain.split("/").pop(), s.name)
|
|
60
|
+
: path.join(s.category, s.name);
|
|
61
|
+
return { ...s, targetPath: path.join(ROOT, rel), targetRel: rel };
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
console.log(`${planned.length} skills, ${errors.length} errors`);
|
|
65
|
+
if (errors.length) console.log("Errors:", errors);
|
|
66
|
+
|
|
67
|
+
if (!APPLY) {
|
|
68
|
+
console.log("Dry-run. Pass --apply.");
|
|
69
|
+
process.exit(0);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let moved = 0, failed = 0;
|
|
73
|
+
for (const s of planned) {
|
|
74
|
+
if (s.currentPath === s.targetPath) continue;
|
|
75
|
+
const dir = path.dirname(s.targetPath);
|
|
76
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
77
|
+
try {
|
|
78
|
+
execSync(`git mv "${s.currentPath}" "${s.targetPath}"`, { cwd: ROOT, stdio: "pipe" });
|
|
79
|
+
moved++;
|
|
80
|
+
} catch (err) {
|
|
81
|
+
failed++;
|
|
82
|
+
console.error(`FAIL ${s.name}: ${err.message.split("\n")[0]}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
console.log(`Moved ${moved}, failed ${failed}.`);
|