bmad-module-skill-forge 2.2.0 → 3.0.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 +1 -1
- package/README.md +49 -49
- package/docs/404.md +10 -8
- package/docs/_data/pinned.yaml +22 -13
- package/docs/_internal/STABILITY.md +58 -35
- package/docs/agents.md +39 -53
- package/docs/architecture.md +65 -51
- package/docs/bmad-synergy.md +53 -51
- package/docs/campaign.md +127 -75
- package/docs/concepts.md +34 -28
- package/docs/examples.md +70 -68
- package/docs/forge-auto.md +34 -31
- package/docs/getting-started.md +118 -57
- package/docs/how-it-works.md +25 -21
- package/docs/index.md +15 -15
- package/docs/skill-model.md +70 -59
- package/docs/troubleshooting.md +135 -18
- package/docs/verifying-a-skill.md +83 -57
- package/docs/why-skf.md +24 -22
- package/docs/workflows.md +274 -110
- package/package.json +18 -10
- package/src/forger/preferences.yaml +8 -1
- package/src/knowledge/agentskills-spec.md +2 -0
- package/src/knowledge/ccc-bridge.md +40 -24
- package/src/knowledge/confidence-tiers.md +9 -5
- package/src/knowledge/overview.md +3 -3
- package/src/knowledge/progressive-capability.md +8 -8
- package/src/knowledge/provenance-tracking.md +9 -5
- package/src/knowledge/qmd-registry.md +2 -2
- package/src/knowledge/skf-knowledge-index.csv +2 -2
- package/src/knowledge/skill-lifecycle.md +18 -11
- package/src/knowledge/split-body-strategy.md +0 -3
- package/src/knowledge/tool-resolution.md +4 -2
- package/src/knowledge/version-paths.md +101 -15
- package/src/module-help.csv +3 -3
- package/src/module.yaml +10 -0
- package/src/shared/data/ast-grep-recipes.yaml +608 -0
- package/src/shared/data/ide-context-files.json +30 -0
- package/src/shared/health-check.md +32 -12
- package/src/shared/references/description-guard-protocol.md +12 -9
- package/src/shared/references/feasibility-report-schema.md +19 -13
- package/src/shared/references/headless-gate-convention.md +55 -3
- package/src/shared/references/output-contract-schema.md +19 -8
- package/src/shared/references/pipeline-contracts.md +94 -29
- package/src/shared/references/tessl-review.md +77 -0
- package/src/shared/scripts/schemas/skf-analyze-result-envelope.v1.json +194 -0
- package/src/shared/scripts/schemas/skf-audit-result-envelope.v1.json +192 -0
- package/src/shared/scripts/schemas/skf-brief-result-envelope.v1.json +32 -4
- package/src/shared/scripts/schemas/skf-campaign-result-envelope.v1.json +174 -0
- package/src/shared/scripts/schemas/skf-create-skill-result-envelope.v1.json +284 -0
- package/src/shared/scripts/schemas/skf-drop-skill-result-envelope.v1.json +164 -0
- package/src/shared/scripts/schemas/skf-export-result-envelope.v1.json +160 -0
- package/src/shared/scripts/schemas/skf-quick-skill-result-envelope.v1.json +261 -0
- package/src/shared/scripts/schemas/skf-refine-architecture-result-envelope.v1.json +192 -0
- package/src/shared/scripts/schemas/skf-rename-skill-result-envelope.v1.json +179 -0
- package/src/shared/scripts/schemas/skf-setup-result-envelope.v1.json +27 -11
- package/src/shared/scripts/schemas/skf-stack-result-envelope.v1.json +176 -0
- package/src/shared/scripts/schemas/skf-test-result-envelope.v1.json +173 -0
- package/src/shared/scripts/schemas/skf-update-result-envelope.v1.json +19 -5
- package/src/shared/scripts/schemas/skf-verify-stack-result-envelope.v1.json +180 -0
- package/src/shared/scripts/schemas/skill-brief.v1.json +34 -0
- package/src/shared/scripts/schemas/workspace-detection.v1.json +1 -1
- package/src/shared/scripts/skf-atomic-write.py +30 -14
- package/src/shared/scripts/skf-build-change-manifest.py +2203 -24
- package/src/shared/scripts/skf-ccc-git-hygiene.py +532 -0
- package/src/shared/scripts/skf-check-unit-records.py +184 -0
- package/src/shared/scripts/skf-check-workspace-drift.py +538 -9
- package/src/shared/scripts/skf-classify-changed-files.py +694 -0
- package/src/shared/scripts/skf-comention-pairs.py +1333 -123
- package/src/shared/scripts/skf-compare-file-hashes.py +1 -1
- package/src/shared/scripts/skf-count-imports.py +1867 -0
- package/src/shared/scripts/skf-count-tokens.py +1 -1
- package/src/shared/scripts/skf-derive-assembly-shape.py +3 -2
- package/src/shared/scripts/skf-description-guard.py +99 -11
- package/src/shared/scripts/skf-detect-docs.py +694 -2
- package/src/shared/scripts/skf-detect-language.py +486 -107
- package/src/shared/scripts/skf-detect-registry.py +887 -0
- package/src/shared/scripts/skf-detect-scripts-assets.py +1 -1
- package/src/shared/scripts/skf-detect-tools.py +209 -27
- package/src/shared/scripts/skf-detect-workspaces.py +506 -30
- package/src/shared/scripts/skf-disqualify-candidates.py +821 -182
- package/src/shared/scripts/skf-emit-brief-result-envelope.py +69 -191
- package/src/shared/scripts/skf-emit-result-envelope.py +1604 -106
- package/src/shared/scripts/skf-enumerate-stack-skills.py +986 -117
- package/src/shared/scripts/skf-extract-public-api.py +4084 -85
- package/src/shared/scripts/skf-extraction-inventory.py +746 -0
- package/src/shared/scripts/skf-extraction-snapshot.py +539 -0
- package/src/shared/scripts/skf-fetch-temporal.py +674 -0
- package/src/shared/scripts/skf-find-cycles.py +97 -29
- package/src/shared/scripts/skf-find-test-report.py +476 -0
- package/src/shared/scripts/skf-forge-tier-rw.py +444 -203
- package/src/shared/scripts/skf-github-fetch.py +365 -0
- package/src/shared/scripts/skf-github-probe.py +661 -0
- package/src/shared/scripts/skf-hash-content.py +166 -12
- package/src/shared/scripts/skf-language-corpora.py +1 -1
- package/src/shared/scripts/skf-load-provenance.py +159 -27
- package/src/shared/scripts/skf-manifest-ops.py +1 -1
- package/src/shared/scripts/skf-merge-ccc-exclusions.py +2307 -223
- package/src/shared/scripts/skf-merge-doc-urls.py +1 -1
- package/src/shared/scripts/skf-names-present.py +221 -0
- package/src/shared/scripts/skf-pair-intersect.py +119 -28
- package/src/shared/scripts/skf-parse-gaps.py +1384 -0
- package/src/shared/scripts/skf-preapply.py +1 -1
- package/src/shared/scripts/skf-preflight.py +161 -27
- package/src/shared/scripts/skf-promote-staged.py +430 -0
- package/src/shared/scripts/skf-provenance-gap-dispatch.py +39 -7
- package/src/shared/scripts/skf-qmd-classify-collections.py +229 -45
- package/src/shared/scripts/skf-quick-batch.py +774 -0
- package/src/shared/scripts/skf-rebuild-managed-sections.py +972 -88
- package/src/shared/scripts/skf-recommend-scope-type.py +323 -156
- package/src/shared/scripts/skf-render-metadata-stats.py +156 -12
- package/src/shared/scripts/skf-render-quick-metadata.py +132 -28
- package/src/shared/scripts/skf-render-stack-metadata.py +976 -0
- package/src/shared/scripts/skf-resolve-authoritative-files.py +128 -13
- package/src/shared/scripts/skf-resolve-package.py +523 -106
- package/src/shared/scripts/skf-rewrite-skill-name.py +433 -47
- package/src/shared/scripts/skf-run-lock.py +577 -0
- package/src/shared/scripts/skf-scan-manifests.py +998 -57
- package/src/shared/scripts/skf-scan-skill-md-structure.py +1341 -37
- package/src/shared/scripts/skf-severity-classify.py +712 -108
- package/src/shared/scripts/skf-shape-detect.py +298 -204
- package/src/shared/scripts/skf-shard-body.py +9 -8
- package/src/shared/scripts/skf-skill-inventory.py +2412 -89
- package/src/shared/scripts/skf-skills-module.py +472 -0
- package/src/shared/scripts/skf-source-tree.py +1796 -0
- package/src/shared/scripts/skf-structural-diff.py +670 -119
- package/src/shared/scripts/skf-tessl-review.py +706 -0
- package/src/shared/scripts/skf-update-active-symlink.py +5 -5
- package/src/shared/scripts/skf-update-run-state.py +624 -0
- package/src/shared/scripts/skf-validate-brief-inputs.py +92 -29
- package/src/shared/scripts/skf-validate-brief-schema.py +4 -1
- package/src/shared/scripts/skf-validate-feasibility-report.py +577 -66
- package/src/shared/scripts/skf-validate-frontmatter.py +103 -26
- package/src/shared/scripts/skf-validate-output.py +712 -64
- package/src/shared/scripts/skf-validate-pins.py +1 -1
- package/src/shared/scripts/skf-verify-no-trace.py +116 -10
- package/src/shared/scripts/skf-verify-provenance-completeness.py +2919 -54
- package/src/shared/scripts/skf-write-skill-brief.py +367 -19
- package/src/shared/tool-requirements.yaml +201 -0
- package/src/skf-analyze-source/SKILL.md +41 -36
- package/src/skf-analyze-source/assets/skill-brief-schema.md +7 -8
- package/src/skf-analyze-source/customize.toml +36 -24
- package/src/skf-analyze-source/references/auto-docs-only.md +58 -20
- package/src/skf-analyze-source/references/continue.md +2 -13
- package/src/skf-analyze-source/references/discover-additional-source.md +147 -0
- package/src/skf-analyze-source/references/generate-briefs.md +121 -55
- package/src/skf-analyze-source/references/headless-contract.md +42 -15
- package/src/skf-analyze-source/references/identify-units.md +96 -110
- package/src/skf-analyze-source/references/init.md +73 -71
- package/src/skf-analyze-source/references/map-and-detect.md +123 -105
- package/src/skf-analyze-source/references/map-unit-exports.md +68 -0
- package/src/skf-analyze-source/references/recommend.md +51 -70
- package/src/skf-analyze-source/references/scan-project.md +60 -84
- package/src/skf-analyze-source/references/scan-root.md +39 -0
- package/src/skf-analyze-source/references/step-auto-scope-coexistence.md +47 -0
- package/src/skf-analyze-source/references/step-auto-scope-corpora.md +53 -0
- package/src/skf-analyze-source/references/step-auto-scope-split.md +91 -0
- package/src/skf-analyze-source/references/step-auto-scope.md +208 -293
- package/src/skf-analyze-source/references/step-shape-detect.md +14 -12
- package/src/skf-analyze-source/references/unit-detection-heuristics.md +37 -42
- package/src/skf-audit-skill/SKILL.md +43 -51
- package/src/skf-audit-skill/assets/drift-report-template.md +27 -3
- package/src/skf-audit-skill/customize.toml +41 -20
- package/src/skf-audit-skill/references/constituent-freshness.md +91 -0
- package/src/skf-audit-skill/references/doc-drift.md +141 -0
- package/src/skf-audit-skill/references/headless-contract.md +29 -0
- package/src/skf-audit-skill/references/init.md +148 -210
- package/src/skf-audit-skill/references/re-index.md +96 -108
- package/src/skf-audit-skill/references/report.md +96 -33
- package/src/skf-audit-skill/references/semantic-diff.md +27 -18
- package/src/skf-audit-skill/references/severity-classify.md +79 -65
- package/src/skf-audit-skill/references/severity-rules.md +15 -6
- package/src/skf-audit-skill/references/structural-diff.md +75 -90
- package/src/skf-audit-skill/references/upstream-checkout.md +77 -0
- package/src/skf-audit-skill/scripts/render-drift-tables.py +685 -0
- package/src/skf-brief-skill/SKILL.md +36 -25
- package/src/skf-brief-skill/assets/description-voice-examples.md +1 -1
- package/src/skf-brief-skill/assets/scope-templates.md +17 -24
- package/src/skf-brief-skill/assets/skill-brief-schema.md +29 -15
- package/src/skf-brief-skill/customize.toml +48 -20
- package/src/skf-brief-skill/references/analyze-target.md +142 -125
- package/src/skf-brief-skill/references/confirm-brief.md +10 -8
- package/src/skf-brief-skill/references/draft-checkpoint.md +36 -17
- package/src/skf-brief-skill/references/gather-intent-ratify.md +68 -0
- package/src/skf-brief-skill/references/gather-intent.md +87 -182
- package/src/skf-brief-skill/references/headless-args.md +84 -16
- package/src/skf-brief-skill/references/health-check.md +1 -1
- package/src/skf-brief-skill/references/invocation-contract.md +18 -13
- package/src/skf-brief-skill/references/portfolio-similarity-check.md +1 -1
- package/src/skf-brief-skill/references/qmd-collection-registration.md +7 -10
- package/src/skf-brief-skill/references/scope-definition.md +123 -47
- package/src/skf-brief-skill/references/step-auto-brief.md +24 -80
- package/src/skf-brief-skill/references/step-auto-validate.md +20 -121
- package/src/skf-brief-skill/references/version-resolution.md +3 -2
- package/src/skf-brief-skill/references/write-brief.md +66 -57
- package/src/skf-campaign/SKILL.md +31 -34
- package/src/skf-campaign/assets/campaign-state-schema.json +25 -4
- package/src/skf-campaign/customize.toml +47 -16
- package/src/skf-campaign/references/campaign-contracts.md +39 -29
- package/src/skf-campaign/references/campaign-directive-spec.md +4 -2
- package/src/skf-campaign/references/health-check.md +7 -16
- package/src/skf-campaign/references/step-01-setup.md +46 -84
- package/src/skf-campaign/references/step-02-strategy.md +31 -30
- package/src/skf-campaign/references/step-03-pins.md +26 -18
- package/src/skf-campaign/references/step-04-provenance.md +24 -23
- package/src/skf-campaign/references/step-05-skill-loop.md +67 -48
- package/src/skf-campaign/references/step-06-batch.md +41 -34
- package/src/skf-campaign/references/step-07-capstone.md +40 -25
- package/src/skf-campaign/references/step-08-verify.md +28 -29
- package/src/skf-campaign/references/step-09-refine.md +29 -30
- package/src/skf-campaign/references/step-10-export.md +48 -36
- package/src/skf-campaign/references/step-11-maintenance.md +31 -34
- package/src/skf-campaign/references/step-resume.md +37 -68
- package/src/skf-campaign/scripts/campaign-deps.py +57 -18
- package/src/skf-campaign/scripts/campaign-parse-manifest.py +361 -54
- package/src/skf-campaign/scripts/campaign-provenance.py +96 -53
- package/src/skf-campaign/scripts/campaign-quality-gate.py +592 -0
- package/src/skf-campaign/scripts/campaign-render-batch.py +257 -49
- package/src/skf-campaign/scripts/campaign-render-kickoff.py +343 -50
- package/src/skf-campaign/scripts/campaign-report.py +204 -37
- package/src/skf-campaign/scripts/campaign-state.py +1128 -0
- package/src/skf-campaign/scripts/campaign-status.py +10 -74
- package/src/skf-campaign/scripts/campaign-validate-pins.py +30 -3
- package/src/skf-campaign/scripts/campaign-validate-state.py +34 -4
- package/src/skf-campaign/templates/campaign-brief-template.yaml +4 -5
- package/src/skf-campaign/templates/campaign-report-template.md +5 -1
- package/src/skf-campaign/templates/kickoff-template.md +0 -14
- package/src/skf-create-skill/SKILL.md +27 -20
- package/src/skf-create-skill/assets/compile-assembly-rules.md +6 -5
- package/src/skf-create-skill/assets/skill-sections.md +15 -5
- package/src/skf-create-skill/customize.toml +20 -8
- package/src/skf-create-skill/references/authoritative-files-protocol.md +41 -42
- package/src/skf-create-skill/references/batch-mode.md +81 -0
- package/src/skf-create-skill/references/ccc-index-check.md +27 -0
- package/src/skf-create-skill/references/compile.md +107 -29
- package/src/skf-create-skill/references/component-extraction.md +113 -91
- package/src/skf-create-skill/references/enrich.md +41 -23
- package/src/skf-create-skill/references/entry-points-by-hand.md +20 -0
- package/src/skf-create-skill/references/extract.md +200 -161
- package/src/skf-create-skill/references/extraction-patterns-by-hand.md +141 -0
- package/src/skf-create-skill/references/extraction-patterns-tracing.md +2 -2
- package/src/skf-create-skill/references/extraction-patterns.md +584 -196
- package/src/skf-create-skill/references/generate-artifacts.md +145 -93
- package/src/skf-create-skill/references/health-check.md +1 -1
- package/src/skf-create-skill/references/load-brief.md +53 -36
- package/src/skf-create-skill/references/relabel-rule.md +12 -0
- package/src/skf-create-skill/references/report.md +28 -59
- package/src/skf-create-skill/references/source-resolution-protocols.md +49 -155
- package/src/skf-create-skill/references/step-auto-shard.md +20 -93
- package/src/skf-create-skill/references/step-doc-rot.md +44 -60
- package/src/skf-create-skill/references/step-doc-sources.md +28 -19
- package/src/skf-create-skill/references/sub/ccc-discover.md +18 -7
- package/src/skf-create-skill/references/sub/fetch-docs.md +106 -59
- package/src/skf-create-skill/references/sub/fetch-temporal.md +53 -166
- package/src/skf-create-skill/references/tier-degradation-rules.md +18 -9
- package/src/skf-create-skill/references/validate.md +173 -99
- package/src/skf-create-skill/scripts/scan-doc-rot.py +232 -99
- package/src/skf-create-stack-skill/SKILL.md +29 -56
- package/src/skf-create-stack-skill/assets/metadata-contract.md +51 -0
- package/src/skf-create-stack-skill/assets/provenance-map-schema.md +15 -15
- package/src/skf-create-stack-skill/assets/stack-skill-template.md +23 -57
- package/src/skf-create-stack-skill/customize.toml +23 -12
- package/src/skf-create-stack-skill/references/compile-stack.md +57 -66
- package/src/skf-create-stack-skill/references/compose-mode-rules.md +24 -44
- package/src/skf-create-stack-skill/references/detect-integrations.md +92 -95
- package/src/skf-create-stack-skill/references/detect-manifests.md +46 -71
- package/src/skf-create-stack-skill/references/generate-output.md +138 -128
- package/src/skf-create-stack-skill/references/init.md +90 -32
- package/src/skf-create-stack-skill/references/integration-patterns.md +1 -17
- package/src/skf-create-stack-skill/references/invocation-contract.md +34 -0
- package/src/skf-create-stack-skill/references/manifest-patterns.md +32 -20
- package/src/skf-create-stack-skill/references/parallel-extract.md +112 -38
- package/src/skf-create-stack-skill/references/rank-and-confirm.md +57 -75
- package/src/skf-create-stack-skill/references/report.md +39 -33
- package/src/skf-create-stack-skill/references/validate.md +20 -39
- package/src/skf-drop-skill/SKILL.md +28 -51
- package/src/skf-drop-skill/customize.toml +44 -30
- package/src/skf-drop-skill/references/execute.md +79 -146
- package/src/skf-drop-skill/references/invocation-contract.md +64 -0
- package/src/skf-drop-skill/references/report.md +46 -30
- package/src/skf-drop-skill/references/select.md +118 -124
- package/src/skf-drop-skill/scripts/dir-sizes.py +54 -26
- package/src/skf-drop-skill/scripts/drop-roster.py +322 -0
- package/src/skf-export-skill/SKILL.md +40 -62
- package/src/skf-export-skill/assets/managed-section-format.md +14 -50
- package/src/skf-export-skill/assets/snippet-format.md +3 -1
- package/src/skf-export-skill/customize.toml +42 -13
- package/src/skf-export-skill/references/generate-snippet.md +54 -46
- package/src/skf-export-skill/references/health-check.md +2 -0
- package/src/skf-export-skill/references/invocation-contract.md +15 -0
- package/src/skf-export-skill/references/load-skill.md +88 -62
- package/src/skf-export-skill/references/manifest-rebuild.md +9 -10
- package/src/skf-export-skill/references/multi-skill-mode.md +4 -4
- package/src/skf-export-skill/references/orphan-context-detection.md +13 -5
- package/src/skf-export-skill/references/orphan-row-detection.md +31 -22
- package/src/skf-export-skill/references/package.md +15 -73
- package/src/skf-export-skill/references/preflight-snippet-root-probe.md +51 -70
- package/src/skf-export-skill/references/result-envelope.md +31 -16
- package/src/skf-export-skill/references/summary.md +41 -17
- package/src/skf-export-skill/references/token-report.md +4 -12
- package/src/skf-export-skill/references/update-context.md +118 -237
- package/src/skf-forger/SKILL.md +61 -37
- package/src/skf-forger/bmad-skill-manifest.yaml +4 -1
- package/src/skf-forger/customize.toml +43 -0
- package/src/skf-forger/references/pipeline-mode.md +73 -18
- package/src/skf-forger/scripts/forge-status.py +299 -0
- package/src/skf-forger/scripts/parse-pipeline.py +322 -54
- package/src/skf-forger/scripts/pipeline-gate.py +428 -0
- package/src/skf-forger/scripts/pipeline-journal.py +1052 -0
- package/src/skf-quick-skill/SKILL.md +54 -53
- package/src/skf-quick-skill/assets/skill-template.md +4 -2
- package/src/skf-quick-skill/customize.toml +51 -20
- package/src/skf-quick-skill/references/batch-mode.md +74 -21
- package/src/skf-quick-skill/references/compile.md +30 -31
- package/src/skf-quick-skill/references/ecosystem-check.md +7 -57
- package/src/skf-quick-skill/references/finalize.md +69 -18
- package/src/skf-quick-skill/references/halt-contract.md +41 -29
- package/src/skf-quick-skill/references/health-check.md +5 -3
- package/src/skf-quick-skill/references/quick-extract.md +114 -45
- package/src/skf-quick-skill/references/registry-resolution.md +9 -64
- package/src/skf-quick-skill/references/resolve-target.md +116 -66
- package/src/skf-quick-skill/references/write-and-validate.md +79 -40
- package/src/skf-refine-architecture/SKILL.md +43 -53
- package/src/skf-refine-architecture/customize.toml +49 -20
- package/src/skf-refine-architecture/references/compile.md +97 -54
- package/src/skf-refine-architecture/references/exit-codes.md +30 -0
- package/src/skf-refine-architecture/references/finding-storage.md +21 -0
- package/src/skf-refine-architecture/references/gap-analysis.md +101 -38
- package/src/skf-refine-architecture/references/improvements.md +14 -20
- package/src/skf-refine-architecture/references/init.md +98 -57
- package/src/skf-refine-architecture/references/issue-detection.md +32 -39
- package/src/skf-refine-architecture/references/refinement-rules.md +37 -48
- package/src/skf-refine-architecture/references/report.md +65 -54
- package/src/skf-refine-architecture/scripts/skf-check-preservation.py +1619 -0
- package/src/skf-rename-skill/SKILL.md +19 -54
- package/src/skf-rename-skill/customize.toml +32 -26
- package/src/skf-rename-skill/references/execute.md +230 -184
- package/src/skf-rename-skill/references/exit-codes.md +7 -7
- package/src/skf-rename-skill/references/health-check.md +1 -7
- package/src/skf-rename-skill/references/invocation-contract.md +25 -0
- package/src/skf-rename-skill/references/report.md +63 -52
- package/src/skf-rename-skill/references/select.md +197 -115
- package/src/skf-rename-skill/scripts/skf-validate-rename-name.py +161 -21
- package/src/skf-setup/SKILL.md +26 -26
- package/src/skf-setup/customize.toml +14 -10
- package/src/skf-setup/references/auto-index.md +50 -63
- package/src/skf-setup/references/ccc-index.md +29 -90
- package/src/skf-setup/references/detect-and-tier.md +58 -67
- package/src/skf-setup/references/health-check.md +2 -0
- package/src/skf-setup/references/invocation-contract.md +14 -0
- package/src/skf-setup/references/report.md +43 -166
- package/src/skf-setup/references/tier-rules.md +5 -5
- package/src/skf-setup/references/write-config.md +34 -57
- package/src/skf-test-skill/SKILL.md +17 -57
- package/src/skf-test-skill/assets/output-section-formats.md +33 -34
- package/src/skf-test-skill/customize.toml +27 -13
- package/src/skf-test-skill/references/coherence-check.md +134 -62
- package/src/skf-test-skill/references/coverage-check-tiers.md +35 -0
- package/src/skf-test-skill/references/coverage-check.md +217 -254
- package/src/skf-test-skill/references/detect-mode.md +5 -5
- package/src/skf-test-skill/references/external-validators.md +94 -82
- package/src/skf-test-skill/references/health-check.md +6 -7
- package/src/skf-test-skill/references/init.md +163 -88
- package/src/skf-test-skill/references/invocation-contract.md +66 -0
- package/src/skf-test-skill/references/migration-section-rules.md +5 -2
- package/src/skf-test-skill/references/report.md +162 -142
- package/src/skf-test-skill/references/score.md +80 -107
- package/src/skf-test-skill/references/scoring-rules.md +41 -155
- package/src/skf-test-skill/references/source-access-protocol.md +19 -92
- package/src/skf-test-skill/references/step-hard-gate.md +51 -33
- package/src/skf-test-skill/scripts/aggregate-coherence.py +218 -30
- package/src/skf-test-skill/scripts/build-result-context.py +287 -0
- package/src/skf-test-skill/scripts/check-metadata-coherence.py +37 -4
- package/src/skf-test-skill/scripts/combine-external-scores.py +23 -6
- package/src/skf-test-skill/scripts/compute-score.py +223 -45
- package/src/skf-test-skill/scripts/gap-ledger.py +1381 -0
- package/src/skf-test-skill/scripts/hard-gate.py +203 -0
- package/src/skf-test-skill/scripts/load-coverage-inputs.py +760 -0
- package/src/skf-test-skill/scripts/locate-export-segments.py +835 -0
- package/src/skf-test-skill/scripts/reconcile-coverage.py +273 -79
- package/src/skf-test-skill/scripts/score-signatures.py +459 -0
- package/src/skf-test-skill/scripts/stage-helper-payload.py +148 -0
- package/src/skf-test-skill/scripts/validate-inventory.py +311 -16
- package/src/skf-test-skill/scripts/verify-declared-numerator.py +127 -52
- package/src/skf-test-skill/templates/test-report-template.md +21 -15
- package/src/skf-update-skill/SKILL.md +38 -25
- package/src/skf-update-skill/customize.toml +27 -12
- package/src/skf-update-skill/references/detect-changes.md +233 -222
- package/src/skf-update-skill/references/gap-driven.md +336 -0
- package/src/skf-update-skill/references/health-check.md +31 -5
- package/src/skf-update-skill/references/init.md +250 -84
- package/src/skf-update-skill/references/invocation-contract.md +12 -0
- package/src/skf-update-skill/references/manual-section-rules.md +6 -23
- package/src/skf-update-skill/references/merge.md +180 -47
- package/src/skf-update-skill/references/re-extract.md +53 -262
- package/src/skf-update-skill/references/report.md +100 -35
- package/src/skf-update-skill/references/write.md +331 -149
- package/src/skf-update-skill/scripts/skf-new-file-diff.py +29 -7
- package/src/skf-verify-stack/SKILL.md +23 -42
- package/src/skf-verify-stack/assets/feasibility-report-template.md +11 -5
- package/src/skf-verify-stack/customize.toml +55 -22
- package/src/skf-verify-stack/references/coverage-patterns.md +17 -8
- package/src/skf-verify-stack/references/coverage.md +67 -64
- package/src/skf-verify-stack/references/exit-codes.md +21 -8
- package/src/skf-verify-stack/references/init.md +107 -62
- package/src/skf-verify-stack/references/integration-verification-rules.md +12 -13
- package/src/skf-verify-stack/references/integrations.md +106 -65
- package/src/skf-verify-stack/references/report.md +59 -42
- package/src/skf-verify-stack/references/requirements.md +32 -9
- package/src/skf-verify-stack/references/synthesize.md +53 -39
- package/src/skf-verify-stack/scripts/skf-coverage-tally.py +398 -52
- package/src/skf-verify-stack/scripts/skf-previous-report.py +330 -0
- package/src/skf-verify-stack/scripts/skf-report-delta.py +453 -49
- package/src/skf-verify-stack/scripts/skf-verdict-rollup.py +240 -27
- package/tools/cli/commands/install.js +11 -4
- package/tools/cli/commands/status.js +15 -3
- package/tools/cli/commands/update.js +11 -4
- package/tools/cli/lib/tool-check.js +320 -0
- package/tools/cli/lib/version-check.js +22 -9
- package/tools/cli/skf-cli.js +4 -1
- package/.gitattributes +0 -16
- package/.nvmrc +0 -1
- package/docs/_internal/RELEASING.md +0 -679
- package/release-audits/v1.0.0-launch-audit.md +0 -728
- package/src/skf-audit-skill/references/step-doc-drift.md +0 -177
- package/src/skf-brief-skill/references/headless-source-authority-detection.md +0 -26
- package/src/skf-campaign/manifest.yaml +0 -15
- package/src/skf-create-skill/assets/tessl-dismissal-rules.md +0 -106
- package/src/skf-create-skill/references/ecosystem-check.md +0 -74
- package/src/skf-drop-skill/references/headless-contract.md +0 -18
- package/src/skf-update-skill/references/merge-conflict-rules.md +0 -15
- package/src/skf-update-skill/references/remote-source-resolution.md +0 -98
- package/src/skf-update-skill/references/validate.md +0 -124
- package/tools/validate-docs-drift.js +0 -233
- package/tools/validate-docs-links.js +0 -665
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# /// script
|
|
3
|
-
# requires-python = ">=3.
|
|
3
|
+
# requires-python = ">=3.11"
|
|
4
4
|
# dependencies = []
|
|
5
5
|
# ///
|
|
6
6
|
"""SKF Render Metadata Stats — derive metadata.json's computed stats block and
|
|
@@ -40,9 +40,16 @@ distribution ALWAYS bins `entries[]`, so it always sums to `len(entries)`. What
|
|
|
40
40
|
to the *citation count* (= `len(entries)`), NOT to `exports_documented`
|
|
41
41
|
(which is the pattern-surface proxy `pattern_surfaces_documented`, a
|
|
42
42
|
judgment value). `effective_denominator` is never emitted.
|
|
43
|
-
- **stack
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
- **stack**: `exports_documented` comes from the payload (a stack's own
|
|
44
|
+
barrel is empty by design), and the distribution still sums to the
|
|
45
|
+
*entry count* (= `len(entries)`), one per provenance entry. A stack's
|
|
46
|
+
metadata.json counts libraries instead: its `confidence_distribution`
|
|
47
|
+
bins each library once, by the library's tier, and sums to
|
|
48
|
+
`library_count`. So create-stack-skill runs this shape in compute mode
|
|
49
|
+
for the label check (`coherence`) and for its evidence report's
|
|
50
|
+
per-entry counts, and writes neither `stats` nor
|
|
51
|
+
`confidence_distribution` into metadata.json; `--check` does not fit a
|
|
52
|
+
stack.
|
|
46
53
|
|
|
47
54
|
Modes
|
|
48
55
|
-----
|
|
@@ -57,7 +64,10 @@ metadata.json; re-derive the computed values (taking the judgment values —
|
|
|
57
64
|
on-disk metadata itself) and compare against what is on disk. `coherence.ok`
|
|
58
65
|
is false with a `violations[]` list when the on-disk file drifted; the emitted
|
|
59
66
|
`stats`/`confidence_distribution` are the corrected values `validate.md §7`
|
|
60
|
-
writes back.
|
|
67
|
+
writes back. While label violations are present (see Label agreement below)
|
|
68
|
+
they are not: the distribution is binned from the mislabeled
|
|
69
|
+
`signature_source`, so the caller relabels the provenance-map, re-runs the
|
|
70
|
+
helper, and writes the re-run's values.
|
|
61
71
|
|
|
62
72
|
Judgment payload (stdin JSON, compute mode)::
|
|
63
73
|
|
|
@@ -88,11 +98,45 @@ Output JSON (both modes)::
|
|
|
88
98
|
"coherence": {"ok": true, "violations": []}
|
|
89
99
|
}
|
|
90
100
|
|
|
101
|
+
Label agreement (both modes)
|
|
102
|
+
---------------------------
|
|
103
|
+
|
|
104
|
+
Every `entries[]` row whose `confidence` is T1 or T1-low (case-insensitive)
|
|
105
|
+
must carry the labels its `extraction_method` implies, so a label always names
|
|
106
|
+
the tool that produced the entry, never the forge tier:
|
|
107
|
+
|
|
108
|
+
- `extraction_method` is one of ast-grep, source-read, ast_bridge,
|
|
109
|
+
source_reading, qmd_bridge, compose-from-skill.
|
|
110
|
+
- ast-grep and ast_bridge need confidence T1; ast-grep also needs a
|
|
111
|
+
non-null `ast_node_type` (the `kind` the matching ast-grep recipe declares
|
|
112
|
+
in create-skill's extraction-patterns.md).
|
|
113
|
+
- source-read and source_reading need confidence T1-low and a
|
|
114
|
+
`signature_source` other than T1; source-read also needs `ast_node_type`
|
|
115
|
+
null (an absent key counts as null).
|
|
116
|
+
- qmd_bridge and compose-from-skill carry no pairing.
|
|
117
|
+
|
|
118
|
+
A row read by eye (source-read, source_reading) is checked at any other
|
|
119
|
+
`confidence` (T2, T3, none) too, for its `signature_source` alone: the
|
|
120
|
+
distribution bins every row by `signature_source`, so a T1 there would count
|
|
121
|
+
a signature read by eye as T1. The other rows with another confidence
|
|
122
|
+
(ast-grep, ast_bridge, qmd_bridge, compose-from-skill, an unknown method)
|
|
123
|
+
and `file_entries[]` are not checked. Each disagreement is a violation keyed
|
|
124
|
+
by the row's index, because reference-app and stack maps repeat export
|
|
125
|
+
names::
|
|
126
|
+
|
|
127
|
+
{"field": "provenance.entries[12].confidence", "export_name": "search",
|
|
128
|
+
"expected": "T1-low", "actual": "T1",
|
|
129
|
+
"note": "extraction_method source-read pairs with confidence T1-low"}
|
|
130
|
+
|
|
131
|
+
The step that runs the helper relabels the entry to match its method (never
|
|
132
|
+
the reverse) and runs the helper again.
|
|
133
|
+
|
|
91
134
|
Exit codes:
|
|
92
|
-
0
|
|
93
|
-
1
|
|
94
|
-
|
|
95
|
-
|
|
135
|
+
0: coherence ok (no violations)
|
|
136
|
+
1: coherence violations present (drift to auto-fix, a bad signature_source,
|
|
137
|
+
or a label that disagrees with extraction_method); the JSON on stdout is
|
|
138
|
+
still authoritative, so rely on it, not the code
|
|
139
|
+
2: usage, IO or JSON-decode error
|
|
96
140
|
"""
|
|
97
141
|
|
|
98
142
|
from __future__ import annotations
|
|
@@ -109,6 +153,16 @@ _SIG_MAP = {"T1": "t1", "T1-LOW": "t1_low", "T2": "t2", "T3": "t3"}
|
|
|
109
153
|
_VALID_SHAPES = ("library", "stack", "reference-app")
|
|
110
154
|
_DIST_KEYS = ("t1", "t1_low", "t2", "t3")
|
|
111
155
|
|
|
156
|
+
# extraction_method values a T1 or T1-low entry may carry, and the labels
|
|
157
|
+
# each one implies. ast-grep / source-read are what create-skill and
|
|
158
|
+
# update-skill write; the other four come from stack maps.
|
|
159
|
+
_KNOWN_METHODS = (
|
|
160
|
+
"ast-grep", "source-read", "ast_bridge", "source_reading", "qmd_bridge", "compose-from-skill",
|
|
161
|
+
)
|
|
162
|
+
_T1_METHODS = ("ast-grep", "ast_bridge") # confidence T1
|
|
163
|
+
_T1_LOW_METHODS = ("source-read", "source_reading") # confidence T1-low, signature_source never T1
|
|
164
|
+
_LABELED_CONFIDENCE = ("T1", "T1-LOW")
|
|
165
|
+
|
|
112
166
|
|
|
113
167
|
# --------------------------------------------------------------------------
|
|
114
168
|
# IO
|
|
@@ -275,13 +329,95 @@ def _cmp_ratio(violations: list, field: str, expected, actual) -> None:
|
|
|
275
329
|
violations.append({"field": field, "expected": expected, "actual": actual})
|
|
276
330
|
|
|
277
331
|
|
|
332
|
+
def _upper(value: object) -> str | None:
|
|
333
|
+
return value.strip().upper() if isinstance(value, str) else None
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def check_label_agreement(prov: dict) -> list[dict]:
|
|
337
|
+
"""Check that each T1 / T1-low entry's labels match its extraction_method,
|
|
338
|
+
and that no entry read by eye claims a T1 signature_source.
|
|
339
|
+
|
|
340
|
+
Returns one violation per disagreeing field, keyed by the entry's index
|
|
341
|
+
(`provenance.entries[<i>].<field>`) since export names repeat in
|
|
342
|
+
reference-app and stack maps. `expected` is the value the method implies;
|
|
343
|
+
the caller relabels the entry to it and never changes a known method.
|
|
344
|
+
An entry read by eye (source-read, source_reading) with any other
|
|
345
|
+
confidence is checked for its signature_source alone; the other entries
|
|
346
|
+
with another confidence, and `file_entries[]`, are not checked.
|
|
347
|
+
"""
|
|
348
|
+
entries = prov.get("entries")
|
|
349
|
+
if not isinstance(entries, list):
|
|
350
|
+
return []
|
|
351
|
+
violations: list[dict] = []
|
|
352
|
+
|
|
353
|
+
for i, entry in enumerate(entries):
|
|
354
|
+
if not isinstance(entry, dict):
|
|
355
|
+
continue
|
|
356
|
+
confidence = entry.get("confidence")
|
|
357
|
+
method = entry.get("extraction_method")
|
|
358
|
+
labeled = _upper(confidence) in _LABELED_CONFIDENCE
|
|
359
|
+
# Off T1 / T1-low only an entry read by eye is checked, for its
|
|
360
|
+
# signature_source: binning reads that label whatever the confidence,
|
|
361
|
+
# so a T1 there would count a signature read by eye as T1.
|
|
362
|
+
if not labeled and method not in _T1_LOW_METHODS:
|
|
363
|
+
continue
|
|
364
|
+
name = entry.get("export_name")
|
|
365
|
+
|
|
366
|
+
def flag(field: str, expected: object, actual: object, note: str) -> None:
|
|
367
|
+
violations.append({
|
|
368
|
+
"field": f"provenance.entries[{i}].{field}",
|
|
369
|
+
"export_name": name,
|
|
370
|
+
"expected": expected,
|
|
371
|
+
"actual": actual,
|
|
372
|
+
"note": note,
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
if method not in _KNOWN_METHODS:
|
|
376
|
+
flag("extraction_method", sorted(_KNOWN_METHODS), method,
|
|
377
|
+
"a T1 or T1-low entry names the tool that produced it: set ast-grep for an "
|
|
378
|
+
"ast-grep match or source-read for an entry read by eye")
|
|
379
|
+
continue
|
|
380
|
+
|
|
381
|
+
if method in _T1_METHODS:
|
|
382
|
+
if _upper(confidence) != "T1":
|
|
383
|
+
flag("confidence", "T1", confidence,
|
|
384
|
+
f"extraction_method {method} pairs with confidence T1")
|
|
385
|
+
node = entry.get("ast_node_type")
|
|
386
|
+
# "non-null" is the violation's own expected value, not a node kind:
|
|
387
|
+
# a fixer that copied it has still not recorded the kind.
|
|
388
|
+
if method == "ast-grep" and (not (isinstance(node, str) and node.strip())
|
|
389
|
+
or _upper(node) == "NON-NULL"):
|
|
390
|
+
flag("ast_node_type", "non-null", node,
|
|
391
|
+
"extraction_method ast-grep pairs with a non-null ast_node_type: copy "
|
|
392
|
+
"the kind the matching recipe declares")
|
|
393
|
+
elif method in _T1_LOW_METHODS:
|
|
394
|
+
if labeled and _upper(confidence) != "T1-LOW":
|
|
395
|
+
flag("confidence", "T1-low", confidence,
|
|
396
|
+
f"extraction_method {method} pairs with confidence T1-low")
|
|
397
|
+
sig = entry.get("signature_source")
|
|
398
|
+
if _upper(sig) == "T1":
|
|
399
|
+
flag("signature_source", "T1-low", sig,
|
|
400
|
+
f"extraction_method {method} pairs with a signature_source other than T1 "
|
|
401
|
+
"(any tier but T1 passes)")
|
|
402
|
+
node = entry.get("ast_node_type")
|
|
403
|
+
if labeled and method == "source-read" and node is not None:
|
|
404
|
+
flag("ast_node_type", None, node,
|
|
405
|
+
"extraction_method source-read pairs with ast_node_type null: no ast-grep "
|
|
406
|
+
"rule matched this entry")
|
|
407
|
+
# qmd_bridge and compose-from-skill carry no pairing.
|
|
408
|
+
|
|
409
|
+
return violations
|
|
410
|
+
|
|
411
|
+
|
|
278
412
|
def coherence_compute(derived: dict, prov: dict) -> dict:
|
|
279
413
|
"""Internal-consistency checks for compute mode.
|
|
280
414
|
|
|
281
415
|
The distribution must account for every provenance entry (a bin-sum below
|
|
282
416
|
the entry count means some entries carry a missing/unrecognized
|
|
283
417
|
signature_source). When the provenance-map already tracks scripts/assets in
|
|
284
|
-
file_entries[], those counts must agree with the stats counts.
|
|
418
|
+
file_entries[], those counts must agree with the stats counts. Every T1 or
|
|
419
|
+
T1-low entry's labels must match its extraction_method, and no entry read
|
|
420
|
+
by eye may claim a T1 signature_source (check_label_agreement).
|
|
285
421
|
"""
|
|
286
422
|
violations: list[dict] = []
|
|
287
423
|
entry_count = derived["_derivation"]["entry_count"]
|
|
@@ -311,6 +447,7 @@ def coherence_compute(derived: dict, prov: dict) -> dict:
|
|
|
311
447
|
_cmp_int(violations, "stats.assets_count", fe["asset"], ac,
|
|
312
448
|
"stats.assets_count disagrees with provenance-map file_entries[file_type=asset]")
|
|
313
449
|
|
|
450
|
+
violations.extend(check_label_agreement(prov))
|
|
314
451
|
return {"ok": not violations, "violations": violations}
|
|
315
452
|
|
|
316
453
|
|
|
@@ -320,6 +457,8 @@ def check_stats(derived: dict, metadata: dict, prov: dict) -> dict:
|
|
|
320
457
|
Every mismatch is a `{field, expected, actual}` violation, where `expected`
|
|
321
458
|
is the re-derived (correct) value and `actual` is what is on disk.
|
|
322
459
|
validate.md §7 either writes `derived` verbatim or fixes each violation.
|
|
460
|
+
Label violations from check_label_agreement are appended too; they concern
|
|
461
|
+
the provenance-map, which is relabeled before the stats are fixed.
|
|
323
462
|
"""
|
|
324
463
|
violations: list[dict] = []
|
|
325
464
|
m_stats = metadata.get("stats") if isinstance(metadata.get("stats"), dict) else {}
|
|
@@ -364,6 +503,8 @@ def check_stats(derived: dict, metadata: dict, prov: dict) -> dict:
|
|
|
364
503
|
_cmp_int(violations, "provenance.file_entries.asset", claimed_ac, fe["asset"],
|
|
365
504
|
"provenance-map file_entries[file_type=asset] count disagrees with metadata assets_count")
|
|
366
505
|
|
|
506
|
+
# provenance entry labels vs extraction_method (the map, not metadata.json)
|
|
507
|
+
violations.extend(check_label_agreement(prov))
|
|
367
508
|
return {"ok": not violations, "violations": violations}
|
|
368
509
|
|
|
369
510
|
|
|
@@ -406,8 +547,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
406
547
|
description=(
|
|
407
548
|
"Derive metadata.json stats + confidence_distribution from the staged "
|
|
408
549
|
"provenance-map (compute mode), or re-verify an on-disk metadata.json "
|
|
409
|
-
"against the map (--check).
|
|
410
|
-
"
|
|
550
|
+
"against the map (--check). Both modes also check that each T1 or "
|
|
551
|
+
"T1-low entry's confidence, signature_source and ast_node_type match "
|
|
552
|
+
"its extraction_method, and that no entry read by eye claims a T1 "
|
|
553
|
+
"signature_source at any confidence. Replaces the hand-binning/summing "
|
|
554
|
+
"in skf-create-skill compile.md §4 and validate.md §7."
|
|
411
555
|
),
|
|
412
556
|
)
|
|
413
557
|
parser.add_argument("provenance_map", help="path to the staged provenance-map.json")
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
# /// script
|
|
2
|
-
# requires-python = ">=3.
|
|
2
|
+
# requires-python = ">=3.11"
|
|
3
3
|
# dependencies = []
|
|
4
4
|
# ///
|
|
5
|
-
"""SKF Render Quick Metadata
|
|
5
|
+
"""SKF Render Quick Metadata: render metadata.json per the canonical
|
|
6
6
|
skill-template.md schema with quick-skill-specific population rules.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
skf-
|
|
8
|
+
Replaces the hand-assembly the LLM previously did in skf-quick-skill step 4
|
|
9
|
+
section 4. The export list, the dependencies and the package's version come
|
|
10
|
+
from the files step 3 wrote (skf-extract-public-api.py's output, or
|
|
11
|
+
skf-skills-module.py extract's), and the SKF version from the installed
|
|
12
|
+
module, so the model stages only the fields it decides: no export list and
|
|
13
|
+
no file probe passes through a shell string.
|
|
12
14
|
|
|
13
15
|
Constants vs. input-derived split mirrors step 4's documentation:
|
|
14
16
|
|
|
@@ -23,7 +25,7 @@ Constants vs. input-derived split mirrors step 4's documentation:
|
|
|
23
25
|
stats.exports_internal, stats.scripts_count, stats.assets_count 0
|
|
24
26
|
stats.public_api_coverage, stats.total_coverage 1.0
|
|
25
27
|
|
|
26
|
-
Input-derived
|
|
28
|
+
Input-derived:
|
|
27
29
|
name, version, description, language, source_repo,
|
|
28
30
|
source_root, source_commit, source_package,
|
|
29
31
|
exports[], dependencies[], compatibility,
|
|
@@ -35,7 +37,33 @@ Constants vs. input-derived split mirrors step 4's documentation:
|
|
|
35
37
|
confidence_distribution.t1_low count(exports)
|
|
36
38
|
stats.exports_documented / public_api / total count(exports)
|
|
37
39
|
|
|
38
|
-
|
|
40
|
+
CLI:
|
|
41
|
+
|
|
42
|
+
uv run skf-render-quick-metadata.py --input <metadata-input.json> \\
|
|
43
|
+
[--extraction <file>]... [--skf-root <dir>] [--output <metadata.json>]
|
|
44
|
+
python3 skf-render-quick-metadata.py < payload.json
|
|
45
|
+
|
|
46
|
+
With --input, the payload is that file (a quoted heredoc stages it, so a
|
|
47
|
+
description holding an apostrophe, a quote or a `$` arrives as written),
|
|
48
|
+
and each --extraction file is the JSON step 3 printed for one module, in
|
|
49
|
+
module order (the parent first). A payload field that is absent or null
|
|
50
|
+
takes its value from them:
|
|
51
|
+
|
|
52
|
+
exports every extraction's exports[] names, in order, each once
|
|
53
|
+
(a payload `exports` list is the --exports override and
|
|
54
|
+
wins)
|
|
55
|
+
dependencies every extraction's dependencies[], each once
|
|
56
|
+
version the first extraction's version that is set, else "1.0.0"
|
|
57
|
+
(a payload version, the target's pinned version, wins)
|
|
58
|
+
source_package the first extraction's package_name, else name
|
|
59
|
+
description the first extraction's description
|
|
60
|
+
|
|
61
|
+
and tool_versions.skf, unless the payload names skf_version, is probed
|
|
62
|
+
under --skf-root (the installed module, {project-root}/_bmad/skf): the
|
|
63
|
+
`version` of its package.json, else the first line of its VERSION file,
|
|
64
|
+
else "unknown". --output also writes the rendered metadata.json there.
|
|
65
|
+
|
|
66
|
+
Without --input, the whole payload comes on stdin, every field typed:
|
|
39
67
|
|
|
40
68
|
{
|
|
41
69
|
"name": "foo",
|
|
@@ -54,15 +82,15 @@ Input JSON shape (stdin):
|
|
|
54
82
|
"skf_version": "1.2.0" (default "unknown")
|
|
55
83
|
}
|
|
56
84
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
python3 skf-render-quick-metadata.py < input.json
|
|
85
|
+
The rendered metadata.json is printed on stdout either way.
|
|
60
86
|
|
|
61
87
|
Exit codes:
|
|
62
88
|
|
|
63
89
|
0 success
|
|
64
|
-
1 payload-level error (missing required field
|
|
65
|
-
|
|
90
|
+
1 payload-level error (missing required field: name, language or
|
|
91
|
+
source_repo)
|
|
92
|
+
2 stdin / argparse / JSON-decode error, or an --input, --extraction or
|
|
93
|
+
--output file that cannot be read or written
|
|
66
94
|
"""
|
|
67
95
|
|
|
68
96
|
from __future__ import annotations
|
|
@@ -71,6 +99,7 @@ import argparse
|
|
|
71
99
|
import datetime as dt
|
|
72
100
|
import json
|
|
73
101
|
import sys
|
|
102
|
+
from pathlib import Path
|
|
74
103
|
|
|
75
104
|
|
|
76
105
|
REQUIRED_FIELDS = ("name", "language", "source_repo")
|
|
@@ -103,6 +132,46 @@ def _iso_utc_now() -> str:
|
|
|
103
132
|
return dt.datetime.now(dt.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
104
133
|
|
|
105
134
|
|
|
135
|
+
def probe_skf_version(skf_root: str | Path | None) -> str:
|
|
136
|
+
"""The installed SKF's version: package.json `version`, else the VERSION file's first line, else "unknown"."""
|
|
137
|
+
if not skf_root:
|
|
138
|
+
return "unknown"
|
|
139
|
+
root = Path(skf_root)
|
|
140
|
+
try:
|
|
141
|
+
version = json.loads((root / "package.json").read_text(encoding="utf-8-sig")).get("version")
|
|
142
|
+
if isinstance(version, str) and version.strip():
|
|
143
|
+
return version.strip()
|
|
144
|
+
except (OSError, ValueError, AttributeError):
|
|
145
|
+
pass
|
|
146
|
+
try:
|
|
147
|
+
lines = (root / "VERSION").read_text(encoding="utf-8-sig").strip().splitlines()
|
|
148
|
+
if lines and lines[0].strip():
|
|
149
|
+
return lines[0].strip()
|
|
150
|
+
except (OSError, UnicodeDecodeError):
|
|
151
|
+
pass
|
|
152
|
+
return "unknown"
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def merge_extractions(payload: dict, extractions: list[dict], skf_version: str | None = None) -> dict:
|
|
156
|
+
"""The full renderer payload: the staged fields, then what the extraction files give."""
|
|
157
|
+
merged = dict(payload)
|
|
158
|
+
|
|
159
|
+
def first(key: str):
|
|
160
|
+
return next((e[key] for e in extractions if isinstance(e.get(key), str) and e[key].strip()), None)
|
|
161
|
+
|
|
162
|
+
if not isinstance(merged.get("exports"), list):
|
|
163
|
+
merged["exports"] = [x for e in extractions for x in (e.get("exports") or []) if isinstance(x, (dict, str))]
|
|
164
|
+
if not isinstance(merged.get("dependencies"), list):
|
|
165
|
+
deps = [d for e in extractions for d in (e.get("dependencies") or []) if isinstance(d, str)]
|
|
166
|
+
merged["dependencies"] = list(dict.fromkeys(deps))
|
|
167
|
+
for key, source in (("version", "version"), ("source_package", "package_name"), ("description", "description")):
|
|
168
|
+
if not merged.get(key):
|
|
169
|
+
merged[key] = first(source)
|
|
170
|
+
if not merged.get("skf_version") and skf_version is not None:
|
|
171
|
+
merged["skf_version"] = skf_version
|
|
172
|
+
return merged
|
|
173
|
+
|
|
174
|
+
|
|
106
175
|
def render_metadata(payload: dict, *, now_fn=_iso_utc_now) -> dict:
|
|
107
176
|
"""Render the metadata.json envelope. Pass `now_fn` to inject a
|
|
108
177
|
deterministic timestamp in tests.
|
|
@@ -161,30 +230,65 @@ def render_metadata(payload: dict, *, now_fn=_iso_utc_now) -> dict:
|
|
|
161
230
|
return metadata
|
|
162
231
|
|
|
163
232
|
|
|
233
|
+
def _read_object(path: str, flag: str) -> dict:
|
|
234
|
+
"""A JSON object from a file; raises ValueError with the message to print."""
|
|
235
|
+
try:
|
|
236
|
+
data = json.loads(Path(path).read_text(encoding="utf-8-sig"))
|
|
237
|
+
except (OSError, UnicodeDecodeError) as e:
|
|
238
|
+
raise ValueError(f"cannot read {flag} {path}: {getattr(e, 'strerror', None) or e}") from e
|
|
239
|
+
except json.JSONDecodeError as e:
|
|
240
|
+
raise ValueError(f"{flag} {path} is not valid JSON: {e}") from e
|
|
241
|
+
if not isinstance(data, dict):
|
|
242
|
+
raise ValueError(f"{flag} {path} must hold a JSON object")
|
|
243
|
+
return data
|
|
244
|
+
|
|
245
|
+
|
|
164
246
|
def main(argv: list[str]) -> int:
|
|
165
247
|
parser = argparse.ArgumentParser(
|
|
166
|
-
description="Render skf-quick-skill metadata.json from
|
|
248
|
+
description="Render skf-quick-skill metadata.json from staged fields and the extraction files "
|
|
249
|
+
"(or from a whole payload on stdin).",
|
|
167
250
|
)
|
|
168
|
-
parser.
|
|
251
|
+
parser.add_argument("--input", help="the staged fields (metadata-input.json); without it, stdin")
|
|
252
|
+
parser.add_argument("--extraction", action="append", default=[],
|
|
253
|
+
help="step 3's extraction output for one module (repeatable, parent first)")
|
|
254
|
+
parser.add_argument("--skf-root", help="the installed SKF module folder to read its version from")
|
|
255
|
+
parser.add_argument("--output", help="also write the rendered metadata.json here")
|
|
256
|
+
args = parser.parse_args(argv)
|
|
169
257
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
258
|
+
if args.input is None:
|
|
259
|
+
raw = sys.stdin.read()
|
|
260
|
+
if not raw.strip():
|
|
261
|
+
sys.stderr.write("error: no input on stdin (expected JSON payload)\n")
|
|
262
|
+
return 2
|
|
263
|
+
try:
|
|
264
|
+
payload = json.loads(raw)
|
|
265
|
+
except json.JSONDecodeError as e:
|
|
266
|
+
sys.stderr.write(f"error: stdin JSON parse error: {e}\n")
|
|
267
|
+
return 2
|
|
268
|
+
if not isinstance(payload, dict):
|
|
269
|
+
sys.stderr.write("error: stdin payload must be a JSON object\n")
|
|
270
|
+
return 2
|
|
271
|
+
else:
|
|
272
|
+
try:
|
|
273
|
+
payload = _read_object(args.input, "--input")
|
|
274
|
+
extractions = [_read_object(path, "--extraction") for path in args.extraction]
|
|
275
|
+
except ValueError as e:
|
|
276
|
+
sys.stderr.write(f"error: {e}\n")
|
|
277
|
+
return 2
|
|
278
|
+
payload = merge_extractions(payload, extractions, probe_skf_version(args.skf_root))
|
|
182
279
|
|
|
183
280
|
result = render_metadata(payload)
|
|
184
281
|
if "_error" in result:
|
|
185
282
|
sys.stderr.write(f"error: {result['_error']}\n")
|
|
186
283
|
return 1
|
|
187
|
-
|
|
284
|
+
text = json.dumps(result, indent=2)
|
|
285
|
+
if args.output:
|
|
286
|
+
try:
|
|
287
|
+
Path(args.output).write_text(text + "\n", encoding="utf-8")
|
|
288
|
+
except OSError as e:
|
|
289
|
+
sys.stderr.write(f"error: cannot write --output {args.output}: {e.strerror or e}\n")
|
|
290
|
+
return 2
|
|
291
|
+
print(text)
|
|
188
292
|
return 0
|
|
189
293
|
|
|
190
294
|
|