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
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
# /// script
|
|
2
|
+
# requires-python = ">=3.11"
|
|
3
|
+
# dependencies = ["pyyaml"]
|
|
4
|
+
# ///
|
|
5
|
+
"""SKF GitHub Fetch: stage files of a GitHub repository at one ref in a folder.
|
|
6
|
+
|
|
7
|
+
quick-skill reads a repository it never clones: its README, its manifest,
|
|
8
|
+
its entry-point files, and a skills module's SKILL.md files and
|
|
9
|
+
module-help.csv. This helper writes each file byte for byte under --dest,
|
|
10
|
+
laid out as in the repository, so the helper that reads it next
|
|
11
|
+
(skf-extract-public-api.py --manifest-file and --entry-file,
|
|
12
|
+
skf-skills-module.py extract) gets the file itself: no file text passes
|
|
13
|
+
through the model, a shell string or a rendered web page, and a quote or an
|
|
14
|
+
apostrophe in it arrives as written.
|
|
15
|
+
|
|
16
|
+
CLI:
|
|
17
|
+
uv run skf-github-fetch.py --repo <repo> --ref <ref> --tree-file <file> \\
|
|
18
|
+
--dest <folder> [--limit <n>] [--exclude <glob>]... \\
|
|
19
|
+
[--patterns-file <file>] [--timeout <seconds>] [<pattern>...]
|
|
20
|
+
|
|
21
|
+
--repo takes a github.com repository as skf-github-probe.py does
|
|
22
|
+
(`owner/repo` or a URL). --ref takes a branch, tag or commit; an empty,
|
|
23
|
+
`null`, `none` or `head` value (any letter case) is HEAD, the default
|
|
24
|
+
branch.
|
|
25
|
+
|
|
26
|
+
Which files. --tree-file is the repository's file listing at --ref:
|
|
27
|
+
skf-github-probe.py tree output, or any listing skf-detect-language.py's
|
|
28
|
+
--tree-file reads (a listing that reports a failure is refused). Each
|
|
29
|
+
pattern is a repo-relative path or a glob, read by
|
|
30
|
+
skf-resolve-authoritative-files.py's rules: `**` spans any number of path
|
|
31
|
+
segments, none included, and `*` and `?` stay inside one. A pattern takes
|
|
32
|
+
the listed files it matches, in path order, at most --limit of them (0,
|
|
33
|
+
the default, for all), leaving out the files an --exclude glob matches. A
|
|
34
|
+
pattern that matches no listed file is reported in `unmatched` and costs
|
|
35
|
+
no request: an optional file the repository does not have (one more
|
|
36
|
+
entry-point candidate, a module-help.csv) is a normal answer, not an
|
|
37
|
+
error. A listing GitHub cut short (`truncated`) may lack a file the
|
|
38
|
+
repository has, so on one a pattern with no `*` or `?` that matches no
|
|
39
|
+
listed file is read anyway, one request: it is `fetched` when it is read,
|
|
40
|
+
and stays in `unmatched` when it cannot be. --patterns-file adds one
|
|
41
|
+
pattern per line (blank lines skipped), such as the list
|
|
42
|
+
skf-skills-module.py sniff writes with --fetch-list. A file two patterns
|
|
43
|
+
match is fetched once.
|
|
44
|
+
|
|
45
|
+
How. Each file is read from
|
|
46
|
+
https://raw.githubusercontent.com/<owner>/<repo>/<ref>/<path> without
|
|
47
|
+
credentials, which needs neither gh nor the API's rate limit. When that
|
|
48
|
+
fails (a private repository answers 404 there too), `gh api -H "Accept:
|
|
49
|
+
application/vnd.github.raw" repos/<owner>/<repo>/contents/<path>?ref=<ref>`
|
|
50
|
+
reads it with the user's GitHub login; once gh has read a file the raw URL
|
|
51
|
+
could not, the files left go to gh first. gh runs as skf-github-probe.py
|
|
52
|
+
runs it (through skf-source-tree.py's runner, never a gh in the current
|
|
53
|
+
folder). A path the listing holds that leads outside --dest (an absolute
|
|
54
|
+
path, a `..` segment) is never written.
|
|
55
|
+
|
|
56
|
+
The whole call finishes within about --timeout seconds (120 by default);
|
|
57
|
+
one request gets at most 20 of them.
|
|
58
|
+
|
|
59
|
+
Output (one ASCII JSON line on stdout):
|
|
60
|
+
status "ok": every file a pattern matched was written (no pattern
|
|
61
|
+
matching anything is ok too); "partial": some could not be;
|
|
62
|
+
"unavailable": none could be
|
|
63
|
+
message one line naming what failed, else null
|
|
64
|
+
repo "owner/repo"
|
|
65
|
+
ref the ref read ("HEAD" for the default branch)
|
|
66
|
+
dest the folder written to, absolute, with `/`
|
|
67
|
+
via "raw" | "gh" | "raw+gh" | null: what read the files written
|
|
68
|
+
fetched the repo-relative paths written under --dest, in pattern order
|
|
69
|
+
unmatched the patterns that matched no listed file (and, on a
|
|
70
|
+
truncated listing, a path that could not be read either)
|
|
71
|
+
failed [{"path", "detail"}]: files that could not be read or written
|
|
72
|
+
truncated the listing was cut short, so a glob may miss a file
|
|
73
|
+
|
|
74
|
+
Exit codes:
|
|
75
|
+
0 status ok
|
|
76
|
+
3 status partial or unavailable (JSON on stdout; the caller decides)
|
|
77
|
+
1 unexpected error: {"status": "error", "message": ...} on stderr
|
|
78
|
+
2 usage error: argparse, an invalid --repo or --ref, or a --tree-file
|
|
79
|
+
or --patterns-file that cannot be read ({"status": "error", ...} on
|
|
80
|
+
stderr)
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
from __future__ import annotations
|
|
84
|
+
|
|
85
|
+
import argparse
|
|
86
|
+
import importlib.util
|
|
87
|
+
import json
|
|
88
|
+
import socket
|
|
89
|
+
import sys
|
|
90
|
+
import time
|
|
91
|
+
import urllib.error
|
|
92
|
+
import urllib.parse
|
|
93
|
+
import urllib.request
|
|
94
|
+
from pathlib import Path
|
|
95
|
+
|
|
96
|
+
RAW_ROOT = "https://raw.githubusercontent.com"
|
|
97
|
+
DEFAULT_TIMEOUT_SEC = 120.0
|
|
98
|
+
REQUEST_TIMEOUT_SEC = 20.0
|
|
99
|
+
|
|
100
|
+
_SIBLINGS: dict[str, object] = {}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _load(filename: str):
|
|
104
|
+
"""A script of this folder, loaded once."""
|
|
105
|
+
if filename not in _SIBLINGS:
|
|
106
|
+
path = Path(__file__).resolve().parent / filename
|
|
107
|
+
spec = importlib.util.spec_from_file_location(filename[:-3].replace("-", "_"), path)
|
|
108
|
+
if spec is None or spec.loader is None:
|
|
109
|
+
raise ImportError(f"cannot load {path}")
|
|
110
|
+
module = importlib.util.module_from_spec(spec)
|
|
111
|
+
spec.loader.exec_module(module)
|
|
112
|
+
_SIBLINGS[filename] = module
|
|
113
|
+
return _SIBLINGS[filename]
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _probe():
|
|
117
|
+
"""skf-github-probe.py: its repository grammar, ref rule, user agent and gh error reading."""
|
|
118
|
+
return _load("skf-github-probe.py")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class UsageError(ValueError):
|
|
122
|
+
"""An input the call cannot run with: exit 2."""
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# --------------------------------------------------------------------------
|
|
126
|
+
# Which files
|
|
127
|
+
# --------------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def select(listing: list[str], patterns: list[str], limit: int = 0,
|
|
131
|
+
excludes: list[str] | None = None, truncated: bool = False) -> tuple[list[str], list[str]]:
|
|
132
|
+
"""(the listed paths the patterns take, in pattern order and each once;
|
|
133
|
+
the patterns that matched no listed file). On a truncated listing a
|
|
134
|
+
pattern with no `*` or `?` that matched nothing is in both: the
|
|
135
|
+
repository may hold it all the same."""
|
|
136
|
+
glob_match = _load("skf-resolve-authoritative-files.py").glob_match
|
|
137
|
+
excludes = excludes or []
|
|
138
|
+
files = sorted(p for p in dict.fromkeys(listing) if not any(glob_match(p, x) for x in excludes))
|
|
139
|
+
chosen: list[str] = []
|
|
140
|
+
unmatched: list[str] = []
|
|
141
|
+
for pattern in patterns:
|
|
142
|
+
pattern = pattern.strip().removeprefix("./")
|
|
143
|
+
matched = [p for p in files if glob_match(p, pattern)]
|
|
144
|
+
if limit > 0:
|
|
145
|
+
matched = matched[:limit]
|
|
146
|
+
if not matched:
|
|
147
|
+
unmatched.append(pattern)
|
|
148
|
+
if truncated and pattern and not any(c in pattern for c in "*?") \
|
|
149
|
+
and not any(glob_match(pattern, x) for x in excludes):
|
|
150
|
+
matched = [pattern]
|
|
151
|
+
chosen.extend(p for p in matched if p not in chosen)
|
|
152
|
+
return chosen, unmatched
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _target(dest: Path, path: str) -> Path | None:
|
|
156
|
+
"""Where `path` is written under `dest`, or None when it would land outside it."""
|
|
157
|
+
parts = path.split("/")
|
|
158
|
+
if path.startswith("/") or "\\" in path or any(part in ("", ".", "..") for part in parts):
|
|
159
|
+
return None
|
|
160
|
+
target = dest.joinpath(*parts)
|
|
161
|
+
try:
|
|
162
|
+
target.resolve().relative_to(dest.resolve())
|
|
163
|
+
except ValueError:
|
|
164
|
+
return None
|
|
165
|
+
return target
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# --------------------------------------------------------------------------
|
|
169
|
+
# Reading a file
|
|
170
|
+
# --------------------------------------------------------------------------
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
_deadline: float | None = None
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _start_clock(seconds: float | None) -> None:
|
|
177
|
+
global _deadline
|
|
178
|
+
_deadline = None if seconds is None else time.monotonic() + max(0.0, seconds)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _budget() -> float | None:
|
|
182
|
+
"""Seconds the next request may take, or None when no time is left."""
|
|
183
|
+
if _deadline is None:
|
|
184
|
+
return REQUEST_TIMEOUT_SEC
|
|
185
|
+
left = _deadline - time.monotonic()
|
|
186
|
+
return min(REQUEST_TIMEOUT_SEC, left) if left > 0.5 else None
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _raw(owner: str, repo: str, ref: str, path: str) -> tuple[bytes | None, str]:
|
|
190
|
+
"""The file from raw.githubusercontent.com, without credentials: (bytes or None, detail)."""
|
|
191
|
+
timeout = _budget()
|
|
192
|
+
if timeout is None:
|
|
193
|
+
return None, "no time was left to read it"
|
|
194
|
+
url = f"{RAW_ROOT}/{owner}/{repo}/{urllib.parse.quote(ref, safe='/')}/{urllib.parse.quote(path, safe='/')}"
|
|
195
|
+
req = urllib.request.Request(url, headers={"User-Agent": _probe().USER_AGENT})
|
|
196
|
+
try:
|
|
197
|
+
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
|
198
|
+
return resp.read(), ""
|
|
199
|
+
except urllib.error.HTTPError as e:
|
|
200
|
+
return None, f"HTTP {e.code} from {RAW_ROOT}"
|
|
201
|
+
except (TimeoutError, socket.timeout):
|
|
202
|
+
return None, f"{RAW_ROOT} did not answer within {timeout:g} seconds"
|
|
203
|
+
except (urllib.error.URLError, OSError, ValueError) as e:
|
|
204
|
+
return None, f"{RAW_ROOT}: {getattr(e, 'reason', e)}"
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _gh_raw(owner: str, repo: str, ref: str, path: str) -> tuple[bytes | None, str]:
|
|
208
|
+
"""The file through `gh api` with the raw media type: (bytes or None, detail)."""
|
|
209
|
+
tree = _probe()._sibling()
|
|
210
|
+
exe = tree._resolve_outside_cwd("gh")
|
|
211
|
+
if exe is None:
|
|
212
|
+
return None, "gh is not installed"
|
|
213
|
+
timeout = _budget()
|
|
214
|
+
if timeout is None:
|
|
215
|
+
return None, "no time was left to run gh"
|
|
216
|
+
api_path = (f"repos/{owner}/{repo}/contents/{urllib.parse.quote(path, safe='/')}"
|
|
217
|
+
f"?ref={urllib.parse.quote(ref, safe='')}")
|
|
218
|
+
try:
|
|
219
|
+
rc, out, err, _killed = tree._run([exe, "api", "--hostname", "github.com", "-H",
|
|
220
|
+
"Accept: application/vnd.github.raw", api_path], timeout)
|
|
221
|
+
except (OSError, ValueError) as e:
|
|
222
|
+
return None, f"gh could not run: {e}"
|
|
223
|
+
if rc is None:
|
|
224
|
+
return None, f"gh did not answer within {timeout:g} seconds"
|
|
225
|
+
if rc == 0:
|
|
226
|
+
return out, ""
|
|
227
|
+
if rc == 4:
|
|
228
|
+
return None, "gh is not logged in"
|
|
229
|
+
return None, _probe()._last_line(err.decode("utf-8", errors="replace")) or f"gh exited {rc}"
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def fetch(owner: str, repo: str, ref: str, paths: list[str], dest: Path) -> dict:
|
|
233
|
+
"""Write each path under dest; the output's fetched, failed and via keys."""
|
|
234
|
+
fetched: list[str] = []
|
|
235
|
+
failed: list[dict] = []
|
|
236
|
+
used: list[str] = []
|
|
237
|
+
gh_first = False
|
|
238
|
+
for path in paths:
|
|
239
|
+
target = _target(dest, path)
|
|
240
|
+
if target is None:
|
|
241
|
+
failed.append({"path": path, "detail": "the path leads outside the destination folder"})
|
|
242
|
+
continue
|
|
243
|
+
readers = (("gh", _gh_raw), ("raw", _raw)) if gh_first else (("raw", _raw), ("gh", _gh_raw))
|
|
244
|
+
body, details, reader = None, [], None
|
|
245
|
+
for name, read in readers:
|
|
246
|
+
body, detail = read(owner, repo, ref, path)
|
|
247
|
+
if body is not None:
|
|
248
|
+
reader = name
|
|
249
|
+
break
|
|
250
|
+
details.append(f"{name}: {detail}")
|
|
251
|
+
if body is None:
|
|
252
|
+
failed.append({"path": path, "detail": "; ".join(details)})
|
|
253
|
+
continue
|
|
254
|
+
if reader == "gh" and not gh_first:
|
|
255
|
+
gh_first = True
|
|
256
|
+
try:
|
|
257
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
258
|
+
target.write_bytes(body)
|
|
259
|
+
except OSError as e:
|
|
260
|
+
failed.append({"path": path, "detail": f"cannot write {target.as_posix()}: {e.strerror or e}"})
|
|
261
|
+
continue
|
|
262
|
+
fetched.append(path)
|
|
263
|
+
if reader not in used:
|
|
264
|
+
used.append(reader)
|
|
265
|
+
via = "+".join(name for name in ("raw", "gh") if name in used) or None
|
|
266
|
+
return {"fetched": fetched, "failed": failed, "via": via}
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def run(repo_arg: str, ref_arg: str | None, tree_file: str, dest_arg: str, patterns: list[str],
|
|
270
|
+
limit: int = 0, excludes: list[str] | None = None) -> dict:
|
|
271
|
+
probe = _probe()
|
|
272
|
+
parsed = probe.parse_repo(repo_arg)
|
|
273
|
+
if parsed is None:
|
|
274
|
+
raise UsageError(f"{repo_arg} is not a github.com repository (expected owner/repo or a URL).")
|
|
275
|
+
owner, repo = parsed
|
|
276
|
+
ref = probe._tree_ref(ref_arg)
|
|
277
|
+
if ref != "HEAD" and not probe._sibling().REF_RE.fullmatch(ref):
|
|
278
|
+
raise UsageError(f"{ref} is not a valid branch, tag or commit name.")
|
|
279
|
+
try:
|
|
280
|
+
listing, truncated = _load("skf-detect-language.py").read_tree_file(tree_file)
|
|
281
|
+
except ValueError as e:
|
|
282
|
+
raise UsageError(str(e)) from e
|
|
283
|
+
dest = Path(dest_arg).absolute()
|
|
284
|
+
chosen, unmatched = select(listing, patterns, limit, excludes, truncated)
|
|
285
|
+
result = fetch(owner, repo, ref, chosen, dest)
|
|
286
|
+
# A path the truncated listing lacks was a guess: read, it matched; unread, it stays unmatched.
|
|
287
|
+
guessed = {p for p in chosen if p in unmatched}
|
|
288
|
+
unmatched = [p for p in unmatched if p not in result["fetched"]]
|
|
289
|
+
failed = [f for f in result["failed"] if f["path"] not in guessed]
|
|
290
|
+
if not failed:
|
|
291
|
+
status, message = "ok", None
|
|
292
|
+
else:
|
|
293
|
+
status = "partial" if result["fetched"] else "unavailable"
|
|
294
|
+
names = ", ".join(f["path"] for f in failed[:3]) + (f" and {len(failed) - 3} more" if len(failed) > 3 else "")
|
|
295
|
+
message = (f"Could not fetch {names} from {owner}/{repo} at {ref}: {failed[0]['detail']}. "
|
|
296
|
+
"Check the network, or log in with gh auth login for a private repository.")
|
|
297
|
+
return {"status": status, "message": message, "repo": f"{owner}/{repo}", "ref": ref,
|
|
298
|
+
"dest": dest.as_posix(), "via": result["via"], "fetched": result["fetched"],
|
|
299
|
+
"unmatched": unmatched, "failed": failed, "truncated": truncated}
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
# --------------------------------------------------------------------------
|
|
303
|
+
# CLI
|
|
304
|
+
# --------------------------------------------------------------------------
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _patterns_file(value: str) -> list[str]:
|
|
308
|
+
try:
|
|
309
|
+
with open(value, encoding="utf-8-sig") as fh:
|
|
310
|
+
return [line.strip() for line in fh if line.strip()]
|
|
311
|
+
except (OSError, UnicodeDecodeError) as e:
|
|
312
|
+
raise UsageError(f"cannot read --patterns-file {value}: {getattr(e, 'strerror', None) or e}") from e
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
316
|
+
parser = argparse.ArgumentParser(
|
|
317
|
+
prog="skf-github-fetch",
|
|
318
|
+
description="Write files of a GitHub repository at one ref under a folder, picked from its file listing.",
|
|
319
|
+
)
|
|
320
|
+
parser.add_argument("patterns", nargs="*", metavar="pattern", help="a repo-relative path or glob")
|
|
321
|
+
parser.add_argument("--repo", required=True, help="owner/repo or a github.com URL")
|
|
322
|
+
parser.add_argument("--ref", default="HEAD", help="branch, tag or commit (default: the default branch)")
|
|
323
|
+
parser.add_argument("--tree-file", required=True,
|
|
324
|
+
help="the repository's file listing at --ref (skf-github-probe.py tree output)")
|
|
325
|
+
parser.add_argument("--dest", required=True, help="the folder the files are written under")
|
|
326
|
+
parser.add_argument("--limit", type=int, default=0, help="files one pattern takes at most (0: all)")
|
|
327
|
+
parser.add_argument("--exclude", action="append", default=[], help="a glob of listed files to leave out")
|
|
328
|
+
parser.add_argument("--patterns-file", help="more patterns, one per line")
|
|
329
|
+
parser.add_argument("--timeout", type=float, default=DEFAULT_TIMEOUT_SEC,
|
|
330
|
+
help="seconds the whole call may take (default 120)")
|
|
331
|
+
return parser
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def main(argv: list[str] | None = None) -> int:
|
|
335
|
+
args = _build_parser().parse_args(argv)
|
|
336
|
+
try:
|
|
337
|
+
_start_clock(args.timeout)
|
|
338
|
+
patterns = list(args.patterns)
|
|
339
|
+
if args.patterns_file:
|
|
340
|
+
patterns.extend(_patterns_file(args.patterns_file))
|
|
341
|
+
out = run(args.repo, args.ref, args.tree_file, args.dest, patterns, args.limit, args.exclude)
|
|
342
|
+
except UsageError as e:
|
|
343
|
+
print(json.dumps({"status": "error", "message": str(e)}), file=sys.stderr)
|
|
344
|
+
return 2
|
|
345
|
+
except Exception as e: # noqa: BLE001 - one JSON error line, never a traceback
|
|
346
|
+
print(json.dumps({"status": "error", "message": f"{type(e).__name__}: {e}"}), file=sys.stderr)
|
|
347
|
+
return 1
|
|
348
|
+
print(json.dumps(out))
|
|
349
|
+
return 0 if out["status"] == "ok" else 3
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def _force_utf8(*streams) -> None:
|
|
353
|
+
"""Reconfigure the JSON streams to UTF-8 (a Windows console uses cp1252)."""
|
|
354
|
+
for stream in streams:
|
|
355
|
+
if hasattr(stream, "reconfigure"):
|
|
356
|
+
errors = getattr(stream, "errors", None)
|
|
357
|
+
if errors is None:
|
|
358
|
+
stream.reconfigure(encoding="utf-8")
|
|
359
|
+
else:
|
|
360
|
+
stream.reconfigure(encoding="utf-8", errors=errors)
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
if __name__ == "__main__":
|
|
364
|
+
_force_utf8(sys.stdout, sys.stderr)
|
|
365
|
+
sys.exit(main())
|