bmad-module-skill-forge 0.9.0 → 0.10.1-alpha.0
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/.claude-plugin/marketplace.json +39 -0
- package/.gitattributes +16 -0
- package/README.md +105 -38
- package/docs/404.md +13 -10
- package/docs/RELEASING.md +185 -0
- package/docs/STABILITY.md +123 -0
- package/docs/_data/pinned.yaml +98 -0
- package/docs/agents.md +16 -11
- package/docs/architecture.md +221 -0
- package/docs/bmad-synergy.md +11 -13
- package/docs/concepts.md +18 -108
- package/docs/examples.md +121 -80
- package/docs/getting-started.md +85 -147
- package/docs/how-it-works.md +25 -536
- package/docs/index.md +44 -27
- package/docs/skill-model.md +328 -0
- package/docs/troubleshooting.md +39 -0
- package/docs/verifying-a-skill.md +232 -0
- package/docs/why-skf.md +93 -0
- package/docs/workflows.md +119 -13
- package/package.json +25 -4
- package/src/README.md +23 -25
- package/src/forger/forge-tier.yaml +4 -2
- package/src/forger/preferences.yaml +7 -1
- package/src/knowledge/agentskills-spec.md +1 -1
- package/src/knowledge/architecture-verification.md +1 -1
- package/src/knowledge/ccc-bridge.md +16 -15
- package/src/knowledge/overview.md +11 -11
- package/src/knowledge/progressive-capability.md +3 -3
- package/src/knowledge/provenance-tracking.md +9 -3
- package/src/knowledge/qmd-registry.md +8 -8
- package/src/knowledge/skf-knowledge-index.csv +2 -2
- package/src/knowledge/skill-lifecycle.md +16 -1
- package/src/knowledge/split-body-strategy.md +1 -1
- package/src/knowledge/version-paths.md +17 -12
- package/src/module-help.csv +18 -17
- package/src/module.yaml +4 -0
- package/src/shared/health-check.md +372 -0
- package/src/shared/references/feasibility-report-schema.md +86 -0
- package/src/shared/references/headless-gate-convention.md +62 -0
- package/src/shared/references/output-contract-schema.md +35 -0
- package/src/shared/references/pipeline-contracts.md +102 -0
- package/src/shared/scripts/skf-atomic-write.py +370 -0
- package/src/shared/scripts/skf-manifest-ops.py +236 -0
- package/src/shared/scripts/skf-preflight.py +164 -0
- package/src/shared/scripts/skf-rebuild-managed-sections.py +201 -0
- package/src/shared/scripts/skf-severity-classify.py +163 -0
- package/src/shared/scripts/skf-skill-inventory.py +210 -0
- package/src/shared/scripts/skf-structural-diff.py +257 -0
- package/src/shared/scripts/skf-validate-frontmatter.py +318 -0
- package/src/shared/scripts/skf-validate-output.py +247 -0
- package/src/skf-analyze-source/SKILL.md +57 -0
- package/src/skf-analyze-source/assets/skill-brief-schema.md +125 -0
- package/src/skf-analyze-source/references/unit-detection-heuristics.md +124 -0
- package/src/{workflows/analyze-source → skf-analyze-source}/steps-c/step-01-init.md +6 -66
- package/src/{workflows/analyze-source → skf-analyze-source}/steps-c/step-01b-continue.md +4 -59
- package/src/{workflows/analyze-source → skf-analyze-source}/steps-c/step-02-scan-project.md +7 -68
- package/src/{workflows/analyze-source → skf-analyze-source}/steps-c/step-03-identify-units.md +6 -66
- package/src/{workflows/analyze-source → skf-analyze-source}/steps-c/step-04-map-and-detect.md +7 -68
- package/src/{workflows/analyze-source → skf-analyze-source}/steps-c/step-05-recommend.md +6 -66
- package/src/{workflows/analyze-source → skf-analyze-source}/steps-c/step-06-generate-briefs.md +14 -66
- package/src/skf-analyze-source/steps-c/step-07-health-check.md +22 -0
- package/src/skf-audit-skill/SKILL.md +59 -0
- package/src/{workflows/audit-skill/data → skf-audit-skill/assets}/drift-report-template.md +7 -7
- package/src/{workflows/audit-skill/data → skf-audit-skill/references}/severity-rules.md +11 -11
- package/src/{workflows/audit-skill → skf-audit-skill}/steps-c/step-01-init.md +24 -68
- package/src/{workflows/audit-skill → skf-audit-skill}/steps-c/step-02-re-index.md +36 -71
- package/src/{workflows/audit-skill → skf-audit-skill}/steps-c/step-03-structural-diff.md +21 -63
- package/src/{workflows/audit-skill → skf-audit-skill}/steps-c/step-04-semantic-diff.md +6 -65
- package/src/{workflows/audit-skill → skf-audit-skill}/steps-c/step-05-severity-classify.md +5 -63
- package/src/{workflows/audit-skill → skf-audit-skill}/steps-c/step-06-report.md +10 -59
- package/src/skf-audit-skill/steps-c/step-07-health-check.md +22 -0
- package/src/skf-brief-skill/SKILL.md +53 -0
- package/src/{workflows/brief-skill/data → skf-brief-skill/assets}/scope-templates.md +23 -0
- package/src/skf-brief-skill/assets/skill-brief-schema.md +206 -0
- package/src/{workflows/brief-skill → skf-brief-skill}/steps-c/step-01-gather-intent.md +6 -67
- package/src/{workflows/brief-skill → skf-brief-skill}/steps-c/step-02-analyze-target.md +6 -66
- package/src/{workflows/brief-skill → skf-brief-skill}/steps-c/step-03-scope-definition.md +12 -68
- package/src/{workflows/brief-skill → skf-brief-skill}/steps-c/step-04-confirm-brief.md +5 -63
- package/src/{workflows/brief-skill → skf-brief-skill}/steps-c/step-05-write-brief.md +10 -69
- package/src/skf-brief-skill/steps-c/step-06-health-check.md +22 -0
- package/src/skf-create-skill/SKILL.md +63 -0
- package/src/{workflows/create-skill/data → skf-create-skill/assets}/compile-assembly-rules.md +60 -0
- package/src/{workflows/create-skill/data → skf-create-skill/assets}/skill-sections.md +92 -23
- package/src/skf-create-skill/assets/tessl-dismissal-rules.md +102 -0
- package/src/{workflows/create-skill/data → skf-create-skill/references}/extraction-patterns-tracing.md +13 -13
- package/src/{workflows/create-skill/data → skf-create-skill/references}/extraction-patterns.md +8 -3
- package/src/skf-create-skill/references/source-resolution-protocols.md +239 -0
- package/src/{workflows/create-skill/data → skf-create-skill/references}/tier-degradation-rules.md +8 -7
- package/src/{workflows/create-skill → skf-create-skill}/steps-c/step-01-load-brief.md +25 -63
- package/src/{workflows/create-skill → skf-create-skill}/steps-c/step-02-ecosystem-check.md +8 -64
- package/src/skf-create-skill/steps-c/step-03-extract.md +379 -0
- package/src/{workflows/create-skill → skf-create-skill}/steps-c/step-03d-component-extraction.md +18 -35
- package/src/{workflows/create-skill → skf-create-skill}/steps-c/step-04-enrich.md +12 -69
- package/src/{workflows/create-skill → skf-create-skill}/steps-c/step-05-compile.md +71 -75
- package/src/skf-create-skill/steps-c/step-06-validate.md +267 -0
- package/src/{workflows/create-skill → skf-create-skill}/steps-c/step-07-generate-artifacts.md +30 -74
- package/src/skf-create-skill/steps-c/step-08-report.md +145 -0
- package/src/skf-create-skill/steps-c/step-09-health-check.md +23 -0
- package/src/{workflows/create-skill/steps-c → skf-create-skill/steps-c/sub}/step-02b-ccc-discover.md +8 -66
- package/src/skf-create-skill/steps-c/sub/step-03b-fetch-temporal.md +229 -0
- package/src/{workflows/create-skill/steps-c → skf-create-skill/steps-c/sub}/step-03c-fetch-docs.md +18 -42
- package/src/skf-create-stack-skill/SKILL.md +64 -0
- package/src/{workflows/create-stack-skill/data → skf-create-stack-skill/assets}/stack-skill-template.md +2 -1
- package/src/{workflows/create-stack-skill/data → skf-create-stack-skill/references}/compose-mode-rules.md +19 -5
- package/src/{workflows/create-stack-skill/data → skf-create-stack-skill/references}/integration-patterns.md +1 -1
- package/src/{workflows/create-stack-skill/data → skf-create-stack-skill/references}/manifest-patterns.md +10 -10
- package/src/{workflows/create-stack-skill → skf-create-stack-skill}/steps-c/step-01-init.md +14 -55
- package/src/{workflows/create-stack-skill → skf-create-stack-skill}/steps-c/step-02-detect-manifests.md +29 -63
- package/src/{workflows/create-stack-skill → skf-create-stack-skill}/steps-c/step-03-rank-and-confirm.md +9 -59
- package/src/{workflows/create-stack-skill → skf-create-stack-skill}/steps-c/step-04-parallel-extract.md +29 -64
- package/src/skf-create-stack-skill/steps-c/step-05-detect-integrations.md +164 -0
- package/src/{workflows/create-stack-skill → skf-create-stack-skill}/steps-c/step-06-compile-stack.md +7 -60
- package/src/skf-create-stack-skill/steps-c/step-07-generate-output.md +328 -0
- package/src/{workflows/create-stack-skill → skf-create-stack-skill}/steps-c/step-08-validate.md +17 -63
- package/src/skf-create-stack-skill/steps-c/step-09-report.md +128 -0
- package/src/skf-create-stack-skill/steps-c/step-10-health-check.md +22 -0
- package/src/skf-drop-skill/SKILL.md +57 -0
- package/src/{workflows/drop-skill → skf-drop-skill}/steps-c/step-01-select.md +9 -69
- package/src/{workflows/drop-skill → skf-drop-skill}/steps-c/step-02-execute.md +30 -106
- package/src/skf-drop-skill/steps-c/step-03-report.md +83 -0
- package/src/skf-drop-skill/steps-c/step-04-health-check.md +22 -0
- package/src/skf-export-skill/SKILL.md +57 -0
- package/src/skf-export-skill/assets/managed-section-format.md +138 -0
- package/src/{workflows/export-skill/data → skf-export-skill/assets}/snippet-format.md +8 -12
- package/src/skf-export-skill/steps-c/step-01-load-skill.md +247 -0
- package/src/{workflows/export-skill → skf-export-skill}/steps-c/step-02-package.md +5 -58
- package/src/{workflows/export-skill → skf-export-skill}/steps-c/step-03-generate-snippet.md +15 -77
- package/src/{workflows/export-skill → skf-export-skill}/steps-c/step-04-update-context.md +59 -133
- package/src/{workflows/export-skill → skf-export-skill}/steps-c/step-05-token-report.md +4 -58
- package/src/{workflows/export-skill → skf-export-skill}/steps-c/step-06-summary.md +20 -64
- package/src/skf-export-skill/steps-c/step-07-health-check.md +22 -0
- package/src/skf-forger/SKILL.md +110 -0
- package/src/skf-forger/bmad-skill-manifest.yaml +11 -0
- package/src/skf-quick-skill/SKILL.md +56 -0
- package/src/{workflows/quick-skill/data → skf-quick-skill/references}/registry-resolution.md +6 -6
- package/src/{workflows/quick-skill → skf-quick-skill}/steps-c/step-01-resolve-target.md +9 -62
- package/src/{workflows/quick-skill → skf-quick-skill}/steps-c/step-02-ecosystem-check.md +5 -59
- package/src/{workflows/quick-skill → skf-quick-skill}/steps-c/step-03-quick-extract.md +18 -61
- package/src/{workflows/quick-skill → skf-quick-skill}/steps-c/step-04-compile.md +6 -60
- package/src/{workflows/quick-skill → skf-quick-skill}/steps-c/step-05-validate.md +58 -78
- package/src/skf-quick-skill/steps-c/step-06-write.md +73 -0
- package/src/skf-quick-skill/steps-c/step-07-health-check.md +22 -0
- package/src/skf-refine-architecture/SKILL.md +57 -0
- package/src/{workflows/refine-architecture/data → skf-refine-architecture/references}/refinement-rules.md +16 -16
- package/src/{workflows/refine-architecture → skf-refine-architecture}/steps-c/step-01-init.md +6 -63
- package/src/{workflows/refine-architecture → skf-refine-architecture}/steps-c/step-02-gap-analysis.md +29 -71
- package/src/{workflows/refine-architecture → skf-refine-architecture}/steps-c/step-03-issue-detection.md +5 -65
- package/src/{workflows/refine-architecture → skf-refine-architecture}/steps-c/step-04-improvements.md +5 -64
- package/src/{workflows/refine-architecture → skf-refine-architecture}/steps-c/step-05-compile.md +5 -56
- package/src/{workflows/refine-architecture → skf-refine-architecture}/steps-c/step-06-report.md +14 -64
- package/src/skf-refine-architecture/steps-c/step-07-health-check.md +22 -0
- package/src/skf-rename-skill/SKILL.md +58 -0
- package/src/{workflows/rename-skill → skf-rename-skill}/steps-c/step-01-select.md +12 -73
- package/src/{workflows/rename-skill → skf-rename-skill}/steps-c/step-02-execute.md +32 -113
- package/src/skf-rename-skill/steps-c/step-03-report.md +83 -0
- package/src/skf-rename-skill/steps-c/step-04-health-check.md +22 -0
- package/src/skf-setup/SKILL.md +55 -0
- package/src/{workflows/setup-forge → skf-setup}/steps-c/step-01-detect-and-tier.md +6 -62
- package/src/{workflows/setup-forge → skf-setup}/steps-c/step-01b-ccc-index.md +9 -65
- package/src/{workflows/setup-forge → skf-setup}/steps-c/step-02-write-config.md +15 -60
- package/src/{workflows/setup-forge → skf-setup}/steps-c/step-03-auto-index.md +7 -66
- package/src/skf-setup/steps-c/step-04-report.md +92 -0
- package/src/skf-setup/steps-c/step-05-health-check.md +22 -0
- package/src/skf-test-skill/SKILL.md +59 -0
- package/src/skf-test-skill/references/migration-section-rules.md +114 -0
- package/src/skf-test-skill/references/scoring-rules.md +207 -0
- package/src/skf-test-skill/references/source-access-protocol.md +109 -0
- package/src/skf-test-skill/scripts/compute-score.py +310 -0
- package/src/skf-test-skill/steps-c/step-01-init.md +238 -0
- package/src/{workflows/test-skill → skf-test-skill}/steps-c/step-02-detect-mode.md +4 -62
- package/src/skf-test-skill/steps-c/step-03-coverage-check.md +347 -0
- package/src/skf-test-skill/steps-c/step-04-coherence-check.md +239 -0
- package/src/{workflows/test-skill → skf-test-skill}/steps-c/step-04b-external-validators.md +42 -77
- package/src/skf-test-skill/steps-c/step-05-score.md +266 -0
- package/src/skf-test-skill/steps-c/step-06-report.md +290 -0
- package/src/skf-test-skill/steps-c/step-07-health-check.md +25 -0
- package/src/skf-test-skill/templates/test-report-template.md +58 -0
- package/src/skf-update-skill/SKILL.md +58 -0
- package/src/{workflows/update-skill/data → skf-update-skill/references}/manual-section-rules.md +6 -6
- package/src/{workflows/update-skill/data → skf-update-skill/references}/merge-conflict-rules.md +5 -5
- package/src/skf-update-skill/references/remote-source-resolution.md +94 -0
- package/src/{workflows/update-skill → skf-update-skill}/steps-c/step-01-init.md +22 -69
- package/src/skf-update-skill/steps-c/step-02-detect-changes.md +255 -0
- package/src/skf-update-skill/steps-c/step-03-re-extract.md +336 -0
- package/src/{workflows/update-skill → skf-update-skill}/steps-c/step-04-merge.md +60 -72
- package/src/{workflows/update-skill → skf-update-skill}/steps-c/step-05-validate.md +5 -62
- package/src/skf-update-skill/steps-c/step-06-write.md +278 -0
- package/src/{workflows/update-skill → skf-update-skill}/steps-c/step-07-report.md +11 -66
- package/src/skf-update-skill/steps-c/step-08-health-check.md +22 -0
- package/src/skf-verify-stack/SKILL.md +60 -0
- package/src/skf-verify-stack/assets/feasibility-report-template.md +76 -0
- package/src/{workflows/verify-stack/data → skf-verify-stack/references}/coverage-patterns.md +12 -4
- package/src/skf-verify-stack/references/integration-verification-rules.md +78 -0
- package/src/skf-verify-stack/steps-c/step-01-init.md +178 -0
- package/src/{workflows/verify-stack → skf-verify-stack}/steps-c/step-02-coverage.md +27 -72
- package/src/skf-verify-stack/steps-c/step-03-integrations.md +179 -0
- package/src/{workflows/verify-stack → skf-verify-stack}/steps-c/step-04-requirements.md +14 -67
- package/src/skf-verify-stack/steps-c/step-05-synthesize.md +147 -0
- package/src/skf-verify-stack/steps-c/step-06-report.md +156 -0
- package/src/skf-verify-stack/steps-c/step-07-health-check.md +25 -0
- package/tools/cli/commands/install.js +1 -1
- package/tools/cli/commands/status.js +21 -23
- package/tools/cli/commands/uninstall.js +11 -19
- package/tools/cli/commands/update.js +2 -2
- package/tools/cli/lib/ide-skills.js +216 -0
- package/tools/cli/lib/installer.js +29 -52
- package/tools/cli/lib/manifest.js +11 -5
- package/tools/cli/lib/platform-codes.yaml +223 -0
- package/tools/cli/lib/ui.js +76 -47
- package/tools/validate-docs-drift.js +193 -0
- package/src/agents/forger.agent.yaml +0 -114
- package/src/workflows/README.md +0 -174
- package/src/workflows/analyze-source/data/skill-brief-schema.md +0 -125
- package/src/workflows/analyze-source/data/unit-detection-heuristics.md +0 -124
- package/src/workflows/analyze-source/workflow.md +0 -61
- package/src/workflows/audit-skill/workflow.md +0 -64
- package/src/workflows/brief-skill/data/skill-brief-schema.md +0 -159
- package/src/workflows/brief-skill/workflow.md +0 -58
- package/src/workflows/create-skill/data/source-resolution-protocols.md +0 -200
- package/src/workflows/create-skill/steps-c/step-03-extract.md +0 -301
- package/src/workflows/create-skill/steps-c/step-03b-fetch-temporal.md +0 -253
- package/src/workflows/create-skill/steps-c/step-06-validate.md +0 -254
- package/src/workflows/create-skill/steps-c/step-08-report.md +0 -176
- package/src/workflows/create-skill/workflow.md +0 -53
- package/src/workflows/create-stack-skill/steps-c/step-05-detect-integrations.md +0 -196
- package/src/workflows/create-stack-skill/steps-c/step-07-generate-output.md +0 -270
- package/src/workflows/create-stack-skill/steps-c/step-09-report.md +0 -145
- package/src/workflows/create-stack-skill/workflow.md +0 -59
- package/src/workflows/drop-skill/steps-c/step-03-report.md +0 -135
- package/src/workflows/drop-skill/workflow.md +0 -63
- package/src/workflows/export-skill/data/managed-section-format.md +0 -103
- package/src/workflows/export-skill/steps-c/step-01-load-skill.md +0 -237
- package/src/workflows/export-skill/workflow.md +0 -58
- package/src/workflows/quick-skill/steps-c/step-06-write.md +0 -179
- package/src/workflows/quick-skill/workflow.md +0 -58
- package/src/workflows/refine-architecture/workflow.md +0 -61
- package/src/workflows/rename-skill/steps-c/step-03-report.md +0 -132
- package/src/workflows/rename-skill/workflow.md +0 -64
- package/src/workflows/setup-forge/steps-c/step-04-report.md +0 -156
- package/src/workflows/setup-forge/workflow.md +0 -51
- package/src/workflows/test-skill/data/scoring-rules.md +0 -123
- package/src/workflows/test-skill/data/source-access-protocol.md +0 -51
- package/src/workflows/test-skill/steps-c/step-01-init.md +0 -217
- package/src/workflows/test-skill/steps-c/step-03-coverage-check.md +0 -280
- package/src/workflows/test-skill/steps-c/step-04-coherence-check.md +0 -271
- package/src/workflows/test-skill/steps-c/step-05-score.md +0 -222
- package/src/workflows/test-skill/steps-c/step-06-report.md +0 -207
- package/src/workflows/test-skill/templates/test-report-template.md +0 -28
- package/src/workflows/test-skill/workflow.md +0 -57
- package/src/workflows/update-skill/data/remote-source-resolution.md +0 -57
- package/src/workflows/update-skill/steps-c/step-02-detect-changes.md +0 -221
- package/src/workflows/update-skill/steps-c/step-03-re-extract.md +0 -256
- package/src/workflows/update-skill/steps-c/step-06-write.md +0 -265
- package/src/workflows/update-skill/workflow.md +0 -56
- package/src/workflows/verify-stack/data/feasibility-report-template.md +0 -63
- package/src/workflows/verify-stack/data/integration-verification-rules.md +0 -73
- package/src/workflows/verify-stack/steps-c/step-01-init.md +0 -179
- package/src/workflows/verify-stack/steps-c/step-03-integrations.md +0 -195
- package/src/workflows/verify-stack/steps-c/step-05-synthesize.md +0 -191
- package/src/workflows/verify-stack/steps-c/step-06-report.md +0 -197
- package/src/workflows/verify-stack/workflow.md +0 -61
- package/tools/cli/lib/compiler.js +0 -306
- package/tools/cli/lib/ide-commands.js +0 -244
- /package/src/{workflows/analyze-source → skf-analyze-source}/templates/analysis-report-template.md +0 -0
- /package/src/{workflows/quick-skill/data → skf-quick-skill/assets}/skill-template.md +0 -0
- /package/src/{workflows/setup-forge/data → skf-setup/references}/tier-rules.md +0 -0
- /package/src/{workflows/test-skill/data → skf-test-skill/assets}/output-section-formats.md +0 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
# /// script
|
|
2
|
+
# requires-python = ">=3.9"
|
|
3
|
+
# dependencies = ["pyyaml"]
|
|
4
|
+
# ///
|
|
5
|
+
"""SKF Validate Frontmatter — agentskills.io frontmatter compliance checker.
|
|
6
|
+
|
|
7
|
+
Validates SKILL.md frontmatter against the agentskills.io specification.
|
|
8
|
+
Aligned with the canonical reference implementation at
|
|
9
|
+
agentskills/agentskills/skills-ref/src/skills_ref/validator.py.
|
|
10
|
+
|
|
11
|
+
Designed for pre-check use in test-skill, create-skill, update-skill, and
|
|
12
|
+
export-skill workflows. Returns structured JSON for deterministic integration.
|
|
13
|
+
|
|
14
|
+
CLI:
|
|
15
|
+
python3 skf-validate-frontmatter.py <skill-md-path>
|
|
16
|
+
python3 skf-validate-frontmatter.py <skill-md-path> --skill-dir-name <name>
|
|
17
|
+
|
|
18
|
+
Input:
|
|
19
|
+
Path to a SKILL.md file.
|
|
20
|
+
Optional --skill-dir-name: expected directory name for name-match check.
|
|
21
|
+
If omitted, derived from the parent directory of the SKILL.md path.
|
|
22
|
+
|
|
23
|
+
Output:
|
|
24
|
+
JSON object:
|
|
25
|
+
status: "pass" | "fail" | "warn"
|
|
26
|
+
issues: list of { severity, field, message }
|
|
27
|
+
frontmatter: parsed frontmatter dict (if parseable)
|
|
28
|
+
summary: { total, high, medium, low }
|
|
29
|
+
|
|
30
|
+
Exit codes:
|
|
31
|
+
0 — pass (no high-severity issues)
|
|
32
|
+
1 — fail (high-severity issues found) or error
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
import argparse
|
|
38
|
+
import json
|
|
39
|
+
import sys
|
|
40
|
+
import unicodedata
|
|
41
|
+
from pathlib import Path
|
|
42
|
+
|
|
43
|
+
import yaml
|
|
44
|
+
|
|
45
|
+
# agentskills.io permitted frontmatter fields (matches canonical validator.py)
|
|
46
|
+
ALLOWED_FIELDS = frozenset({
|
|
47
|
+
"name", "description", "license", "compatibility", "metadata", "allowed-tools",
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
MAX_SKILL_NAME_LENGTH = 64
|
|
51
|
+
MAX_DESCRIPTION_LENGTH = 1024
|
|
52
|
+
MAX_COMPATIBILITY_LENGTH = 500
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def parse_frontmatter(content: str) -> tuple[dict | None, list[dict]]:
|
|
56
|
+
"""Extract and parse YAML frontmatter from SKILL.md content.
|
|
57
|
+
|
|
58
|
+
Returns (parsed_dict, issues). If frontmatter is structurally broken,
|
|
59
|
+
parsed_dict is None and issues contains the structural error.
|
|
60
|
+
"""
|
|
61
|
+
issues: list[dict] = []
|
|
62
|
+
|
|
63
|
+
if not content.startswith("---\n") and not content.startswith("---\r\n"):
|
|
64
|
+
issues.append({
|
|
65
|
+
"severity": "high",
|
|
66
|
+
"field": "frontmatter",
|
|
67
|
+
"message": "Missing opening --- delimiter",
|
|
68
|
+
})
|
|
69
|
+
return None, issues
|
|
70
|
+
|
|
71
|
+
# Find closing --- on its own line (not a substring inside YAML values)
|
|
72
|
+
closing_idx = -1
|
|
73
|
+
search_start = content.index("\n") + 1 # skip past opening ---\n
|
|
74
|
+
for i, line in enumerate(content[search_start:].split("\n")):
|
|
75
|
+
if line.rstrip("\r") == "---":
|
|
76
|
+
closing_idx = search_start + sum(len(l) + 1 for l in content[search_start:].split("\n")[:i])
|
|
77
|
+
break
|
|
78
|
+
if closing_idx == -1:
|
|
79
|
+
issues.append({
|
|
80
|
+
"severity": "high",
|
|
81
|
+
"field": "frontmatter",
|
|
82
|
+
"message": "Missing closing --- delimiter",
|
|
83
|
+
})
|
|
84
|
+
return None, issues
|
|
85
|
+
|
|
86
|
+
# Extract text between opening and closing delimiters
|
|
87
|
+
fm_text = content[search_start:closing_idx]
|
|
88
|
+
|
|
89
|
+
# Parse with PyYAML for proper YAML handling (nested metadata, multi-line values)
|
|
90
|
+
try:
|
|
91
|
+
fm = yaml.safe_load(fm_text)
|
|
92
|
+
except yaml.YAMLError as exc:
|
|
93
|
+
issues.append({
|
|
94
|
+
"severity": "high",
|
|
95
|
+
"field": "frontmatter",
|
|
96
|
+
"message": f"Invalid YAML in frontmatter: {exc}",
|
|
97
|
+
})
|
|
98
|
+
return None, issues
|
|
99
|
+
|
|
100
|
+
if fm is None:
|
|
101
|
+
# Empty frontmatter block
|
|
102
|
+
fm = {}
|
|
103
|
+
|
|
104
|
+
if not isinstance(fm, dict):
|
|
105
|
+
issues.append({
|
|
106
|
+
"severity": "high",
|
|
107
|
+
"field": "frontmatter",
|
|
108
|
+
"message": "Frontmatter must be a YAML mapping",
|
|
109
|
+
})
|
|
110
|
+
return None, issues
|
|
111
|
+
|
|
112
|
+
# Normalize metadata sub-dict values to strings (matches canonical parser.py)
|
|
113
|
+
if "metadata" in fm and isinstance(fm["metadata"], dict):
|
|
114
|
+
fm["metadata"] = {str(k): str(v) for k, v in fm["metadata"].items()}
|
|
115
|
+
|
|
116
|
+
return fm, issues
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _validate_name(name: str, skill_dir_name: str | None) -> list[dict]:
|
|
120
|
+
"""Validate skill name format. Aligned with canonical validator.py."""
|
|
121
|
+
issues: list[dict] = []
|
|
122
|
+
|
|
123
|
+
if not name or not isinstance(name, str) or not name.strip():
|
|
124
|
+
issues.append({
|
|
125
|
+
"severity": "high",
|
|
126
|
+
"field": "name",
|
|
127
|
+
"message": "name field missing or empty",
|
|
128
|
+
})
|
|
129
|
+
return issues
|
|
130
|
+
|
|
131
|
+
name = unicodedata.normalize("NFKC", name.strip())
|
|
132
|
+
|
|
133
|
+
if len(name) > MAX_SKILL_NAME_LENGTH:
|
|
134
|
+
issues.append({
|
|
135
|
+
"severity": "high",
|
|
136
|
+
"field": "name",
|
|
137
|
+
"message": f"name exceeds {MAX_SKILL_NAME_LENGTH} chars ({len(name)} chars)",
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
if name != name.lower():
|
|
141
|
+
issues.append({
|
|
142
|
+
"severity": "high",
|
|
143
|
+
"field": "name",
|
|
144
|
+
"message": f"name '{name}' must be lowercase",
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
if name.startswith("-") or name.endswith("-"):
|
|
148
|
+
issues.append({
|
|
149
|
+
"severity": "high",
|
|
150
|
+
"field": "name",
|
|
151
|
+
"message": "name cannot start or end with a hyphen",
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
if "--" in name:
|
|
155
|
+
issues.append({
|
|
156
|
+
"severity": "high",
|
|
157
|
+
"field": "name",
|
|
158
|
+
"message": "name cannot contain consecutive hyphens",
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
if not all(c.isalnum() or c == "-" for c in name):
|
|
162
|
+
issues.append({
|
|
163
|
+
"severity": "high",
|
|
164
|
+
"field": "name",
|
|
165
|
+
"message": f"name '{name}' contains invalid characters (only letters, digits, and hyphens allowed)",
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
# Directory name match (with Unicode normalization)
|
|
169
|
+
if skill_dir_name:
|
|
170
|
+
dir_name = unicodedata.normalize("NFKC", skill_dir_name)
|
|
171
|
+
if dir_name != name:
|
|
172
|
+
issues.append({
|
|
173
|
+
"severity": "high",
|
|
174
|
+
"field": "name",
|
|
175
|
+
"message": f"name '{name}' does not match directory name '{skill_dir_name}'",
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
return issues
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def validate_frontmatter(
|
|
182
|
+
content: str,
|
|
183
|
+
skill_dir_name: str | None = None,
|
|
184
|
+
) -> dict:
|
|
185
|
+
"""Validate SKILL.md frontmatter against agentskills.io spec.
|
|
186
|
+
|
|
187
|
+
Returns a result dict with status, issues, frontmatter, and summary.
|
|
188
|
+
"""
|
|
189
|
+
fm, issues = parse_frontmatter(content)
|
|
190
|
+
|
|
191
|
+
if fm is not None:
|
|
192
|
+
# Name validation
|
|
193
|
+
name = fm.get("name", "")
|
|
194
|
+
issues.extend(_validate_name(name, skill_dir_name))
|
|
195
|
+
|
|
196
|
+
# Description: required, non-empty, max 1024 chars
|
|
197
|
+
desc = fm.get("description", "")
|
|
198
|
+
if not desc or not isinstance(desc, str) or not desc.strip():
|
|
199
|
+
issues.append({
|
|
200
|
+
"severity": "high",
|
|
201
|
+
"field": "description",
|
|
202
|
+
"message": "description field missing or empty",
|
|
203
|
+
})
|
|
204
|
+
elif len(desc) > MAX_DESCRIPTION_LENGTH:
|
|
205
|
+
issues.append({
|
|
206
|
+
"severity": "medium",
|
|
207
|
+
"field": "description",
|
|
208
|
+
"message": f"description exceeds {MAX_DESCRIPTION_LENGTH} chars ({len(desc)} chars)",
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
# Compatibility: optional, max 500 chars
|
|
212
|
+
compat = fm.get("compatibility")
|
|
213
|
+
if compat is not None:
|
|
214
|
+
if not isinstance(compat, str):
|
|
215
|
+
issues.append({
|
|
216
|
+
"severity": "medium",
|
|
217
|
+
"field": "compatibility",
|
|
218
|
+
"message": "compatibility must be a string",
|
|
219
|
+
})
|
|
220
|
+
elif len(compat) > MAX_COMPATIBILITY_LENGTH:
|
|
221
|
+
issues.append({
|
|
222
|
+
"severity": "medium",
|
|
223
|
+
"field": "compatibility",
|
|
224
|
+
"message": f"compatibility exceeds {MAX_COMPATIBILITY_LENGTH} chars ({len(compat)} chars)",
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
# Unknown fields
|
|
228
|
+
for key in fm:
|
|
229
|
+
if key not in ALLOWED_FIELDS:
|
|
230
|
+
issues.append({
|
|
231
|
+
"severity": "low",
|
|
232
|
+
"field": key,
|
|
233
|
+
"message": f"Unknown frontmatter field: '{key}'",
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
# Build summary
|
|
237
|
+
severity_counts = {"high": 0, "medium": 0, "low": 0}
|
|
238
|
+
for issue in issues:
|
|
239
|
+
severity_counts[issue["severity"]] += 1
|
|
240
|
+
|
|
241
|
+
if severity_counts["high"] > 0:
|
|
242
|
+
status = "fail"
|
|
243
|
+
elif severity_counts["medium"] > 0 or severity_counts["low"] > 0:
|
|
244
|
+
status = "warn"
|
|
245
|
+
else:
|
|
246
|
+
status = "pass"
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
"status": status,
|
|
250
|
+
"issues": issues,
|
|
251
|
+
"frontmatter": fm,
|
|
252
|
+
"summary": {
|
|
253
|
+
"total": len(issues),
|
|
254
|
+
**severity_counts,
|
|
255
|
+
},
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def main() -> int:
|
|
260
|
+
parser = argparse.ArgumentParser(
|
|
261
|
+
prog="skf-validate-frontmatter.py",
|
|
262
|
+
description=(
|
|
263
|
+
"Validate SKILL.md frontmatter against agentskills.io specification.\n"
|
|
264
|
+
"\n"
|
|
265
|
+
"Checks: frontmatter delimiters, name format (Unicode letters + digits +\n"
|
|
266
|
+
"hyphens, no consecutive/trailing hyphens), name-directory match,\n"
|
|
267
|
+
"description presence and length, compatibility length, and unknown fields.\n"
|
|
268
|
+
"\n"
|
|
269
|
+
"Exit code 0 means pass (no high-severity issues).\n"
|
|
270
|
+
"Exit code 1 means fail (high-severity issues found)."
|
|
271
|
+
),
|
|
272
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
273
|
+
)
|
|
274
|
+
parser.add_argument(
|
|
275
|
+
"skill_md",
|
|
276
|
+
metavar="SKILL.md",
|
|
277
|
+
help="path to the SKILL.md file to validate",
|
|
278
|
+
)
|
|
279
|
+
parser.add_argument(
|
|
280
|
+
"--skill-dir-name",
|
|
281
|
+
metavar="NAME",
|
|
282
|
+
default=None,
|
|
283
|
+
help="expected skill directory name (default: derived from parent directory)",
|
|
284
|
+
)
|
|
285
|
+
parser.add_argument(
|
|
286
|
+
"-o", "--output",
|
|
287
|
+
metavar="FILE",
|
|
288
|
+
help="write JSON output to FILE instead of stdout",
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
args = parser.parse_args()
|
|
292
|
+
skill_md_path = Path(args.skill_md)
|
|
293
|
+
|
|
294
|
+
if not skill_md_path.exists():
|
|
295
|
+
result = {
|
|
296
|
+
"status": "fail",
|
|
297
|
+
"issues": [{"severity": "high", "field": "file", "message": f"File not found: {skill_md_path}"}],
|
|
298
|
+
"frontmatter": None,
|
|
299
|
+
"summary": {"total": 1, "high": 1, "medium": 0, "low": 0},
|
|
300
|
+
}
|
|
301
|
+
else:
|
|
302
|
+
content = skill_md_path.read_text(encoding="utf-8")
|
|
303
|
+
skill_dir_name = args.skill_dir_name or skill_md_path.parent.name
|
|
304
|
+
result = validate_frontmatter(content, skill_dir_name)
|
|
305
|
+
|
|
306
|
+
output_text = json.dumps(result, indent=2)
|
|
307
|
+
|
|
308
|
+
if args.output:
|
|
309
|
+
out_path = Path(args.output)
|
|
310
|
+
out_path.write_text(output_text + "\n", encoding="utf-8")
|
|
311
|
+
else:
|
|
312
|
+
print(output_text)
|
|
313
|
+
|
|
314
|
+
return 0 if result["status"] != "fail" else 1
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
if __name__ == "__main__":
|
|
318
|
+
sys.exit(main())
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# /// script
|
|
2
|
+
# requires-python = ">=3.10"
|
|
3
|
+
# dependencies = []
|
|
4
|
+
# ///
|
|
5
|
+
"""SKF Validate Output — Validate skill package artifacts.
|
|
6
|
+
|
|
7
|
+
Validates SKILL.md frontmatter, context-snippet.md format, and metadata.json
|
|
8
|
+
schema against agentskills.io specification. Outputs JSON validation results.
|
|
9
|
+
|
|
10
|
+
CLI: python3 skf-validate-output.py <skill-package-dir>
|
|
11
|
+
python3 skf-validate-output.py <skill-package-dir> --generated-by quick-skill
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import re
|
|
18
|
+
import sys
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def validate_frontmatter(content, skill_name=None):
|
|
23
|
+
"""Validate SKILL.md frontmatter. Returns list of issues."""
|
|
24
|
+
issues = []
|
|
25
|
+
|
|
26
|
+
# Check frontmatter delimiters
|
|
27
|
+
if not content.startswith("---\n"):
|
|
28
|
+
issues.append({"severity": "high", "field": "frontmatter", "message": "Missing opening --- delimiter"})
|
|
29
|
+
return issues
|
|
30
|
+
|
|
31
|
+
# Find closing --- on its own line (not a substring match inside YAML values)
|
|
32
|
+
end_idx = -1
|
|
33
|
+
for i, line in enumerate(content.split("\n")[1:], start=1):
|
|
34
|
+
if line.rstrip() == "---":
|
|
35
|
+
end_idx = sum(len(l) + 1 for l in content.split("\n")[:i])
|
|
36
|
+
break
|
|
37
|
+
if end_idx == -1:
|
|
38
|
+
issues.append({"severity": "high", "field": "frontmatter", "message": "Missing closing --- delimiter"})
|
|
39
|
+
return issues
|
|
40
|
+
|
|
41
|
+
fm_text = content[4:end_idx].strip()
|
|
42
|
+
fm = {}
|
|
43
|
+
for line in fm_text.split("\n"):
|
|
44
|
+
if ":" in line:
|
|
45
|
+
key, _, val = line.partition(":")
|
|
46
|
+
fm[key.strip()] = val.strip().strip("'\"")
|
|
47
|
+
|
|
48
|
+
# Required fields
|
|
49
|
+
name = fm.get("name", "")
|
|
50
|
+
if not name:
|
|
51
|
+
issues.append({"severity": "high", "field": "name", "message": "name field missing or empty"})
|
|
52
|
+
elif not re.match(r"^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", name) or len(name) > 64:
|
|
53
|
+
issues.append({"severity": "high", "field": "name", "message": f"name must be lowercase alphanumeric + hyphens, 1-64 chars, got: {name}"})
|
|
54
|
+
|
|
55
|
+
if skill_name and name and name != skill_name:
|
|
56
|
+
issues.append({"severity": "high", "field": "name", "message": f"name '{name}' does not match directory name '{skill_name}'"})
|
|
57
|
+
|
|
58
|
+
desc = fm.get("description", "")
|
|
59
|
+
if not desc:
|
|
60
|
+
issues.append({"severity": "high", "field": "description", "message": "description field missing or empty"})
|
|
61
|
+
|
|
62
|
+
# Allowed fields
|
|
63
|
+
allowed = {"name", "description", "license", "compatibility", "metadata", "allowed-tools"}
|
|
64
|
+
for key in fm:
|
|
65
|
+
if key not in allowed:
|
|
66
|
+
issues.append({"severity": "low", "field": key, "message": f"Unknown frontmatter field: {key}"})
|
|
67
|
+
|
|
68
|
+
return issues
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def validate_body_structure(content):
|
|
72
|
+
"""Validate SKILL.md body has required sections. Returns list of issues."""
|
|
73
|
+
issues = []
|
|
74
|
+
body = content.split("---", 2)[-1] if content.startswith("---") else content
|
|
75
|
+
|
|
76
|
+
required_sections = ["Overview", "Key Exports", "Usage"]
|
|
77
|
+
for section in required_sections:
|
|
78
|
+
pattern = rf"^##\s+.*{re.escape(section)}"
|
|
79
|
+
if not re.search(pattern, body, re.MULTILINE | re.IGNORECASE):
|
|
80
|
+
issues.append({"severity": "medium", "field": f"section:{section}", "message": f"Missing ## {section} section"})
|
|
81
|
+
|
|
82
|
+
return issues
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def validate_context_snippet(content):
|
|
86
|
+
"""Validate context-snippet.md format. Returns list of issues."""
|
|
87
|
+
issues = []
|
|
88
|
+
|
|
89
|
+
if not content or not content.strip():
|
|
90
|
+
issues.append({"severity": "high", "field": "content", "message": "Context snippet is empty"})
|
|
91
|
+
return issues
|
|
92
|
+
|
|
93
|
+
lines = content.strip().split("\n")
|
|
94
|
+
|
|
95
|
+
# First line: [name vVersion]|root: prefix
|
|
96
|
+
if lines:
|
|
97
|
+
first = lines[0]
|
|
98
|
+
if not re.match(r"\[.+ v.+\]\|root:", first):
|
|
99
|
+
issues.append({"severity": "medium", "field": "line1", "message": f"First line doesn't match expected pattern: [{first[:50]}...]"})
|
|
100
|
+
|
|
101
|
+
# Second line: |IMPORTANT:
|
|
102
|
+
if len(lines) > 1:
|
|
103
|
+
if not lines[1].startswith("|IMPORTANT:"):
|
|
104
|
+
issues.append({"severity": "medium", "field": "line2", "message": "Second line should start with |IMPORTANT:"})
|
|
105
|
+
|
|
106
|
+
# Approximate token count (rough: ~4 chars per token)
|
|
107
|
+
approx_tokens = len(content) // 4
|
|
108
|
+
if approx_tokens < 40:
|
|
109
|
+
issues.append({"severity": "low", "field": "length", "message": f"Context snippet may be too short (~{approx_tokens} tokens)"})
|
|
110
|
+
elif approx_tokens > 200:
|
|
111
|
+
issues.append({"severity": "low", "field": "length", "message": f"Context snippet may be too long (~{approx_tokens} tokens)"})
|
|
112
|
+
|
|
113
|
+
return issues
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def validate_metadata_json(data, generated_by=None):
|
|
117
|
+
"""Validate metadata.json fields. Returns list of issues."""
|
|
118
|
+
issues = []
|
|
119
|
+
|
|
120
|
+
required_str = ["name", "version", "source_authority", "language", "generation_date"]
|
|
121
|
+
for field in required_str:
|
|
122
|
+
val = data.get(field)
|
|
123
|
+
if not val or not isinstance(val, str):
|
|
124
|
+
issues.append({"severity": "high", "field": field, "message": f"{field} missing or not a string"})
|
|
125
|
+
|
|
126
|
+
# source_repo should be a URL
|
|
127
|
+
repo = data.get("source_repo", "")
|
|
128
|
+
if not repo:
|
|
129
|
+
issues.append({"severity": "medium", "field": "source_repo", "message": "source_repo missing"})
|
|
130
|
+
|
|
131
|
+
# generated_by check
|
|
132
|
+
gb = data.get("generated_by", "")
|
|
133
|
+
if not gb:
|
|
134
|
+
issues.append({"severity": "medium", "field": "generated_by", "message": "generated_by missing"})
|
|
135
|
+
elif generated_by and gb != generated_by:
|
|
136
|
+
issues.append({"severity": "low", "field": "generated_by", "message": f"generated_by is '{gb}', expected '{generated_by}'"})
|
|
137
|
+
|
|
138
|
+
# confidence_tier
|
|
139
|
+
if not data.get("confidence_tier"):
|
|
140
|
+
issues.append({"severity": "medium", "field": "confidence_tier", "message": "confidence_tier missing"})
|
|
141
|
+
|
|
142
|
+
# stats
|
|
143
|
+
stats = data.get("stats", {})
|
|
144
|
+
if not isinstance(stats, dict):
|
|
145
|
+
issues.append({"severity": "high", "field": "stats", "message": "stats must be an object"})
|
|
146
|
+
else:
|
|
147
|
+
required_stats = ["exports_documented", "exports_public_api", "exports_total", "public_api_coverage", "total_coverage"]
|
|
148
|
+
for field in required_stats:
|
|
149
|
+
val = stats.get(field)
|
|
150
|
+
if val is None:
|
|
151
|
+
issues.append({"severity": "medium", "field": f"stats.{field}", "message": f"stats.{field} missing"})
|
|
152
|
+
elif not isinstance(val, (int, float)):
|
|
153
|
+
issues.append({"severity": "medium", "field": f"stats.{field}", "message": f"stats.{field} must be a number"})
|
|
154
|
+
|
|
155
|
+
return issues
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def validate_skill_package(skill_dir, generated_by=None):
|
|
159
|
+
"""Validate a complete skill package directory."""
|
|
160
|
+
skill_dir = Path(skill_dir)
|
|
161
|
+
skill_name = skill_dir.name
|
|
162
|
+
|
|
163
|
+
result = {
|
|
164
|
+
"status": "ok",
|
|
165
|
+
"skill_dir": str(skill_dir),
|
|
166
|
+
"skill_name": skill_name,
|
|
167
|
+
"files_found": {},
|
|
168
|
+
"validation": {},
|
|
169
|
+
"summary": {"total_issues": 0, "by_severity": {"high": 0, "medium": 0, "low": 0}},
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
# Check file existence
|
|
173
|
+
files = {
|
|
174
|
+
"SKILL.md": skill_dir / "SKILL.md",
|
|
175
|
+
"context-snippet.md": skill_dir / "context-snippet.md",
|
|
176
|
+
"metadata.json": skill_dir / "metadata.json",
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
for name, path in files.items():
|
|
180
|
+
result["files_found"][name] = path.exists()
|
|
181
|
+
|
|
182
|
+
# Validate SKILL.md
|
|
183
|
+
skill_md_path = files["SKILL.md"]
|
|
184
|
+
if skill_md_path.exists():
|
|
185
|
+
content = skill_md_path.read_text(encoding="utf-8")
|
|
186
|
+
fm_issues = validate_frontmatter(content, skill_name)
|
|
187
|
+
body_issues = validate_body_structure(content)
|
|
188
|
+
result["validation"]["skill_md"] = {"frontmatter": fm_issues, "body": body_issues}
|
|
189
|
+
for issue in fm_issues + body_issues:
|
|
190
|
+
result["summary"]["total_issues"] += 1
|
|
191
|
+
result["summary"]["by_severity"][issue["severity"]] += 1
|
|
192
|
+
else:
|
|
193
|
+
result["validation"]["skill_md"] = {"error": "SKILL.md not found"}
|
|
194
|
+
result["summary"]["total_issues"] += 1
|
|
195
|
+
result["summary"]["by_severity"]["high"] += 1
|
|
196
|
+
|
|
197
|
+
# Validate context-snippet.md
|
|
198
|
+
snippet_path = files["context-snippet.md"]
|
|
199
|
+
if snippet_path.exists():
|
|
200
|
+
content = snippet_path.read_text(encoding="utf-8")
|
|
201
|
+
snippet_issues = validate_context_snippet(content)
|
|
202
|
+
result["validation"]["context_snippet"] = {"issues": snippet_issues}
|
|
203
|
+
for issue in snippet_issues:
|
|
204
|
+
result["summary"]["total_issues"] += 1
|
|
205
|
+
result["summary"]["by_severity"][issue["severity"]] += 1
|
|
206
|
+
else:
|
|
207
|
+
result["validation"]["context_snippet"] = {"skipped": "context-snippet.md not found"}
|
|
208
|
+
|
|
209
|
+
# Validate metadata.json
|
|
210
|
+
meta_path = files["metadata.json"]
|
|
211
|
+
if meta_path.exists():
|
|
212
|
+
try:
|
|
213
|
+
with open(meta_path, encoding="utf-8") as f:
|
|
214
|
+
meta = json.load(f)
|
|
215
|
+
meta_issues = validate_metadata_json(meta, generated_by)
|
|
216
|
+
result["validation"]["metadata"] = {"issues": meta_issues}
|
|
217
|
+
for issue in meta_issues:
|
|
218
|
+
result["summary"]["total_issues"] += 1
|
|
219
|
+
result["summary"]["by_severity"][issue["severity"]] += 1
|
|
220
|
+
except json.JSONDecodeError as e:
|
|
221
|
+
result["validation"]["metadata"] = {"error": f"JSON parse error: {e}"}
|
|
222
|
+
result["summary"]["total_issues"] += 1
|
|
223
|
+
result["summary"]["by_severity"]["high"] += 1
|
|
224
|
+
else:
|
|
225
|
+
result["validation"]["metadata"] = {"skipped": "metadata.json not found"}
|
|
226
|
+
|
|
227
|
+
# Overall pass/fail
|
|
228
|
+
result["result"] = "PASS" if result["summary"]["by_severity"]["high"] == 0 else "FAIL"
|
|
229
|
+
|
|
230
|
+
return result
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
if __name__ == "__main__":
|
|
234
|
+
if len(sys.argv) < 2:
|
|
235
|
+
print("Usage: python3 skf-validate-output.py <skill-package-dir> [--generated-by <generator>]", file=sys.stderr)
|
|
236
|
+
sys.exit(1)
|
|
237
|
+
|
|
238
|
+
pkg_dir = sys.argv[1]
|
|
239
|
+
gen_by = None
|
|
240
|
+
if "--generated-by" in sys.argv:
|
|
241
|
+
idx = sys.argv.index("--generated-by")
|
|
242
|
+
if idx + 1 < len(sys.argv):
|
|
243
|
+
gen_by = sys.argv[idx + 1]
|
|
244
|
+
|
|
245
|
+
result = validate_skill_package(pkg_dir, generated_by=gen_by)
|
|
246
|
+
print(json.dumps(result, indent=2))
|
|
247
|
+
sys.exit(0 if result["result"] == "PASS" else 1)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: skf-analyze-source
|
|
3
|
+
description: Discover what to skill in a large repo and produce recommended skill briefs. Use when the user requests to "analyze source for skills" or "discover skill opportunities."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Analyze Source
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Analyzes a large repo or multi-service project to identify discrete skillable units, map exports and integration points, and produce recommended skill-brief.yaml files as the primary entry point for brownfield onboarding. The analysis must be thorough enough to produce actionable briefs, but scoped enough to avoid overwhelming the user with false positives. Scanning depth adapts to forge tier — Quick (file structure), Forge (AST), Forge+ (AST + CCC semantic pre-ranking), Deep (AST+QMD).
|
|
11
|
+
|
|
12
|
+
## Role
|
|
13
|
+
|
|
14
|
+
You are a source code analyst and decomposition architect collaborating with a developer onboarding an existing project. You bring expertise in codebase analysis, service boundary detection, and skill scoping, while the user brings their domain knowledge. Work together as equals.
|
|
15
|
+
|
|
16
|
+
## Workflow Rules
|
|
17
|
+
|
|
18
|
+
These rules apply to every step in this workflow:
|
|
19
|
+
|
|
20
|
+
- Read each step file completely before taking any action
|
|
21
|
+
- Follow the mandatory sequence in each step exactly — do not skip, reorder, or optimize
|
|
22
|
+
- Only load one step file at a time — never preload future steps
|
|
23
|
+
- Update `stepsCompleted` in output file frontmatter before loading next step
|
|
24
|
+
- If any instruction references a subprocess or tool you lack, achieve the outcome in your main context thread
|
|
25
|
+
- Always communicate in `{communication_language}`
|
|
26
|
+
- If `{headless_mode}` is true, auto-proceed through confirmation gates with their default action and log each auto-decision
|
|
27
|
+
|
|
28
|
+
## Stages
|
|
29
|
+
|
|
30
|
+
| # | Step | File | Auto-proceed |
|
|
31
|
+
|---|------|------|--------------|
|
|
32
|
+
| 1 | Initialize | steps-c/step-01-init.md | Yes |
|
|
33
|
+
| 1b | Continue (session resume) | steps-c/step-01b-continue.md | Yes |
|
|
34
|
+
| 2 | Scan Project | steps-c/step-02-scan-project.md | No (confirm) |
|
|
35
|
+
| 3 | Identify Units | steps-c/step-03-identify-units.md | No (confirm) |
|
|
36
|
+
| 4 | Map & Detect | steps-c/step-04-map-and-detect.md | Yes |
|
|
37
|
+
| 5 | Recommend | steps-c/step-05-recommend.md | No (confirm) |
|
|
38
|
+
| 6 | Generate Briefs | steps-c/step-06-generate-briefs.md | Yes |
|
|
39
|
+
| 7 | Workflow Health Check | steps-c/step-07-health-check.md | Yes |
|
|
40
|
+
|
|
41
|
+
## Invocation Contract
|
|
42
|
+
|
|
43
|
+
| Aspect | Detail |
|
|
44
|
+
|--------|--------|
|
|
45
|
+
| **Inputs** | project_path [required], scope_hint [optional] |
|
|
46
|
+
| **Gates** | step-02: Confirm Gate [C] | step-03: Confirm Gate [C] | step-05: Confirm Gate [C] |
|
|
47
|
+
| **Outputs** | analysis-report.md, skill-brief.yaml files (one per recommended unit) |
|
|
48
|
+
| **Headless** | All gates auto-resolve with default action when `{headless_mode}` is true |
|
|
49
|
+
|
|
50
|
+
## On Activation
|
|
51
|
+
|
|
52
|
+
1. Load config from `{project-root}/_bmad/skf/config.yaml` and resolve:
|
|
53
|
+
- `project_name`, `output_folder`, `user_name`, `communication_language`, `document_output_language`, `forge_data_folder`, `skills_output_folder`, `sidecar_path`
|
|
54
|
+
|
|
55
|
+
2. **Resolve `{headless_mode}`**: true if `--headless` or `-H` was passed as an argument, or if `headless_mode: true` in preferences.yaml. Default: false.
|
|
56
|
+
|
|
57
|
+
3. Load, read the full file, and then execute `./steps-c/step-01-init.md` to begin the workflow.
|