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,55 +1,74 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# /// script
|
|
3
|
-
# requires-python = ">=3.
|
|
3
|
+
# requires-python = ">=3.11"
|
|
4
4
|
# ///
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
resolved feeder artifacts, matches every line against the fixed
|
|
10
|
-
|
|
11
|
-
no semantics), and emits
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
5
|
+
"""Doc-rot correction-candidate scan (step-doc-rot.md §2).
|
|
6
|
+
|
|
7
|
+
Step 5c writes a `## CORRECTION` block for each live upstream correction to an
|
|
8
|
+
export the compiled skill documents. This helper finds the candidates: it
|
|
9
|
+
walks the resolved feeder artifacts, matches every line against the fixed
|
|
10
|
+
13-row keyword table with case-insensitive substring containment (no regex,
|
|
11
|
+
no semantics), and emits each hit with the kind of change its keyword
|
|
12
|
+
suggests (`candidate_category`), a ranking hint. A keyword is not a
|
|
13
|
+
correction: "No breaking changes in this release" holds one, and so do a pull
|
|
14
|
+
request template checkbox and a bug fix that mentions a deprecation warning.
|
|
15
|
+
Nor is every correction worded with one: a Keep a Changelog "### Removed"
|
|
16
|
+
bullet that names `Client.close_all`, or "Renamed `load` to `read`", holds
|
|
17
|
+
none. So with --provenance (the staged provenance map), a line of the
|
|
18
|
+
project's own announcements (the temporal feeder's changelog.md,
|
|
19
|
+
releases.md and prs.md) that names an export the map lists is a candidate
|
|
20
|
+
too. Every candidate carries the nearest headings above it: `release`, the
|
|
21
|
+
nearest one naming a version (`## [2.3.0] - 2024-05-01`, `## v2.3.0`), and
|
|
22
|
+
`section`, the nearest one under that (`### Removed`). The step's judgment
|
|
23
|
+
pass decides which candidates are live corrections, then caps the blocks at
|
|
24
|
+
10. Running the scan here (instead of in-prompt) keeps the candidate list
|
|
25
|
+
identical for identical feeders: the model never hand-greps multi-KB
|
|
26
|
+
artifacts.
|
|
27
|
+
|
|
28
|
+
Before it returns, the script drops matches that land inside the compiled
|
|
29
|
+
SKILL.md's own YAML frontmatter or its own `## Migration & Deprecation
|
|
30
|
+
Warnings` section (both are self-authored: compile §2 wrote the frontmatter
|
|
31
|
+
`description` and compile §4b wrote the migration bullets, so re-emitting
|
|
32
|
+
either would be circular), and then bounds what survives.
|
|
33
|
+
|
|
34
|
+
Bounding matters because the temporal feeder is a verbatim upstream dump
|
|
35
|
+
(changelog, release notes, issue and pull request bodies), so an unbounded
|
|
36
|
+
run can hand the judgment pass hundreds of "breaking" and "deprecated" lines.
|
|
37
|
+
Two deterministic bounds run after the exclusions: duplicate collapse (same
|
|
38
|
+
candidate category + same normalized line text) and a cap on the candidate
|
|
39
|
+
pool. Keyword hits, and export mentions under a change section, come first;
|
|
40
|
+
a plain export mention (an "### Added" bullet) gets only the places left. A
|
|
41
|
+
change section is a heading whose whole text is a change name (Keep a
|
|
42
|
+
Changelog's `### Deprecated`, `### Removed` and `### Changed`, `## Breaking
|
|
43
|
+
Changes`, `## Migration Guide`), never one with such a word inside it:
|
|
44
|
+
GitHub's generated `## What's Changed` and a pull request template's
|
|
45
|
+
`## Type of change` head every line of their feeder. Within each of those two
|
|
46
|
+
ranks the feeders share the cap in turns, so a years-deep changelog cannot
|
|
47
|
+
crowd out the release notes, and the compiled SKILL.md's own lines of a rank
|
|
48
|
+
come after every other feeder's lines of that rank, so the skill's own text
|
|
49
|
+
never outranks an upstream line of the same rank.
|
|
50
|
+
Nothing is silently destroyed: a collapsed record carries `occurrences` and
|
|
51
|
+
`duplicate_of`, and the counts of what was collapsed and capped are reported
|
|
52
|
+
so the step can log them.
|
|
39
53
|
|
|
40
54
|
CLI usage:
|
|
41
|
-
uv run scan-doc-rot.py --skill-md <staged SKILL.md> [FEEDER ...]
|
|
55
|
+
uv run scan-doc-rot.py --skill-md <staged SKILL.md> [--provenance <map>] [FEEDER ...]
|
|
42
56
|
uv run scan-doc-rot.py --feeder evidence-report.md --feeder provenance-map.json
|
|
43
57
|
|
|
44
58
|
--skill-md the compiled/staged SKILL.md feeder (feeder #4); matches
|
|
45
59
|
inside its YAML frontmatter or its `## Migration &
|
|
46
|
-
Deprecation Warnings` section are excluded
|
|
60
|
+
Deprecation Warnings` section are excluded, and its other
|
|
61
|
+
candidates come last when the pool is capped. It is also
|
|
47
62
|
scanned like any other feeder.
|
|
48
63
|
FEEDER any other feeder artifact (evidence-report.md,
|
|
49
64
|
provenance-map.json, temporal-context files). Repeatable
|
|
50
65
|
positionally or via --feeder.
|
|
51
|
-
--
|
|
52
|
-
|
|
66
|
+
--provenance the staged provenance-map.json: its entries[].export_name
|
|
67
|
+
values are the exports a changelog.md, releases.md or
|
|
68
|
+
prs.md line may name (`default` aside). Missing, empty or
|
|
69
|
+
unreadable: no export is known, as without the flag.
|
|
70
|
+
--max-candidates cap on emitted candidates (default 50). 0 or negative
|
|
71
|
+
means unlimited.
|
|
53
72
|
|
|
54
73
|
Missing or empty files are skipped silently (not an error), matching §1's
|
|
55
74
|
"attempt to load; if it does not exist or is empty, skip it."
|
|
@@ -57,9 +76,13 @@ CLI usage:
|
|
|
57
76
|
Output (stdout, one object):
|
|
58
77
|
{
|
|
59
78
|
"scanned": ["<path>", ...], # feeders that existed and were non-empty
|
|
60
|
-
"matches": [ #
|
|
61
|
-
{"source": "<path>", "pattern": "deprecated",
|
|
79
|
+
"matches": [ # the candidates step 5c's judgment pass reviews
|
|
80
|
+
{"source": "<path>", "pattern": "deprecated" | null, # null: an export mention
|
|
81
|
+
"candidate_category": "Deprecation" | null,
|
|
62
82
|
"context_line": "<line text>", "line_number": <1-indexed int>,
|
|
83
|
+
"exports": ["<export_name>", ...], # the known exports the line names
|
|
84
|
+
"release": "<heading>" | null, # nearest heading above naming a version
|
|
85
|
+
"section": "<heading>" | null, # nearest heading below that one
|
|
63
86
|
"occurrences": <int>, # 1 unless duplicates collapsed into this record
|
|
64
87
|
"duplicate_of": [{"source": "<path>", "line_number": <int>}, ...]},
|
|
65
88
|
...
|
|
@@ -67,8 +90,9 @@ Output (stdout, one object):
|
|
|
67
90
|
"match_count": <int>, # len(matches), after exclusions/collapse/cap
|
|
68
91
|
"excluded_count": <int>, # SKILL.md matches dropped (frontmatter + §4b)
|
|
69
92
|
"deduped_count": <int>, # matches collapsed into a surviving record
|
|
70
|
-
"capped_count": <int>, #
|
|
71
|
-
"cap": <int
|
|
93
|
+
"capped_count": <int>, # candidates dropped because the cap was reached
|
|
94
|
+
"cap": <int>, # effective cap (0 = unlimited)
|
|
95
|
+
"exports_known": <int> # export names --provenance gave
|
|
72
96
|
}
|
|
73
97
|
|
|
74
98
|
Exit codes:
|
|
@@ -79,12 +103,14 @@ Exit codes:
|
|
|
79
103
|
from __future__ import annotations
|
|
80
104
|
|
|
81
105
|
import argparse
|
|
106
|
+
import itertools
|
|
82
107
|
import json
|
|
83
108
|
import re
|
|
84
109
|
import sys
|
|
85
110
|
from pathlib import Path
|
|
86
111
|
|
|
87
|
-
# Fixed
|
|
112
|
+
# Fixed keyword table: (pattern, candidate_category). A hit makes the line a
|
|
113
|
+
# candidate for step-doc-rot.md §2's judgment pass, never a correction by itself.
|
|
88
114
|
# Order is the table order; scanning is per-pattern so overlapping patterns
|
|
89
115
|
# (e.g. "deprecated" ⊂ "@deprecated") each record their own hit, exactly as the
|
|
90
116
|
# 13-row table enumerates them.
|
|
@@ -106,32 +132,83 @@ PATTERN_TABLE: list[tuple[str, str]] = [
|
|
|
106
132
|
|
|
107
133
|
_MIGRATION_HEADING = re.compile(r"^\s*##\s+Migration\s*&\s*Deprecation Warnings", re.IGNORECASE)
|
|
108
134
|
|
|
109
|
-
#
|
|
110
|
-
#
|
|
111
|
-
#
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
135
|
+
# The temporal feeder files that carry the project's own announcements: a line
|
|
136
|
+
# of one that names a known export is a candidate (issues.md and
|
|
137
|
+
# targeted-issues.md hold reports anyone can open, which §2 drops anyway).
|
|
138
|
+
EXPORT_FEEDERS = frozenset({"changelog.md", "releases.md", "prs.md"})
|
|
139
|
+
# An export name too common to mean the export when a line says it.
|
|
140
|
+
IGNORED_EXPORT_NAMES = frozenset({"default"})
|
|
141
|
+
_HEADING = re.compile(r"^(#{1,6})\s+(.*?)\s*#*\s*$")
|
|
142
|
+
_FENCE = re.compile(r"^\s*(```|~~~)")
|
|
143
|
+
# A heading that names a release: a version (`[2.3.0]`, `v2.3.0`, `2.3`) or Unreleased.
|
|
144
|
+
_RELEASE_HEADING = re.compile(r"\bv?\d+\.\d+|\bunreleased\b", re.IGNORECASE)
|
|
145
|
+
# A change section: a heading whose whole text, its marks stripped, is one of
|
|
146
|
+
# these names. Its export mentions rank with the keyword hits.
|
|
147
|
+
_CHANGE_SECTION = re.compile(
|
|
148
|
+
r"deprecated|deprecations?|removed|removals?|changed|breaking(?: changes?)?|migrations?(?: guide)?",
|
|
149
|
+
re.IGNORECASE,
|
|
150
|
+
)
|
|
151
|
+
# The marks around a heading's words: `[Removed]`, `Removed:`, `⚠ BREAKING CHANGES`.
|
|
152
|
+
_HEADING_MARKS = re.compile(r"^[\W_]+|[\W_]+$")
|
|
153
|
+
_IDENTIFIER = re.compile(r"[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*")
|
|
154
|
+
|
|
155
|
+
# Cap on the candidate pool one run hands the judgment pass (step-doc-rot.md §2).
|
|
156
|
+
# The pass keeps at most 10 corrections (step 5b budgets the compiled body at 400
|
|
157
|
+
# lines and each block is ~7 lines), so 50 candidates leaves room for the many
|
|
158
|
+
# keyword lines that are no correction at all.
|
|
159
|
+
DEFAULT_MAX_CANDIDATES = 50
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def named_exports(line: str, exports: frozenset[str]) -> list[str]:
|
|
163
|
+
"""The known export names `line` names: each dotted identifier on it
|
|
164
|
+
(`Client.close_all`) and each of its parts (`close_all`), checked
|
|
165
|
+
against the set. Sorted, each once."""
|
|
166
|
+
found: set[str] = set()
|
|
167
|
+
for token in _IDENTIFIER.findall(line):
|
|
168
|
+
for name in (token, *token.split(".")):
|
|
169
|
+
if name in exports:
|
|
170
|
+
found.add(name)
|
|
171
|
+
return sorted(found)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def scan_text(
|
|
175
|
+
text: str, source: str, exports: frozenset[str] = frozenset(), export_feeder: bool = False
|
|
176
|
+
) -> list[dict]:
|
|
116
177
|
"""Case-insensitive substring scan of `text` against PATTERN_TABLE.
|
|
117
178
|
|
|
118
|
-
Returns one record per (line, pattern) hit, in file order then table
|
|
119
|
-
|
|
179
|
+
Returns one record per (line, pattern) hit, in file order then table
|
|
180
|
+
order, and, when `export_feeder`, one record for a line with no hit that
|
|
181
|
+
names an export of `exports` (pattern and candidate_category null).
|
|
182
|
+
Every record carries the line's `exports` and the nearest `release` and
|
|
183
|
+
`section` headings above it (headings inside a code fence do not count).
|
|
184
|
+
line_number is 1-indexed. Pure: no I/O.
|
|
120
185
|
"""
|
|
121
186
|
matches: list[dict] = []
|
|
187
|
+
release: str | None = None
|
|
188
|
+
section: str | None = None
|
|
189
|
+
fenced = False
|
|
122
190
|
for idx, line in enumerate(text.splitlines(), start=1):
|
|
191
|
+
if _FENCE.match(line):
|
|
192
|
+
fenced = not fenced
|
|
193
|
+
heading = None if fenced else _HEADING.match(line)
|
|
194
|
+
if heading:
|
|
195
|
+
title = line.strip()
|
|
196
|
+
if _RELEASE_HEADING.search(heading.group(2)):
|
|
197
|
+
release, section = title, None
|
|
198
|
+
else:
|
|
199
|
+
section = title
|
|
200
|
+
named = named_exports(line, exports) if exports else []
|
|
123
201
|
lowered = line.lower()
|
|
124
|
-
|
|
202
|
+
context = {"context_line": line.strip(), "line_number": idx, "exports": named,
|
|
203
|
+
"release": release, "section": None if heading else section}
|
|
204
|
+
hit = False
|
|
205
|
+
for pattern, candidate_category in PATTERN_TABLE:
|
|
125
206
|
if pattern.lower() in lowered:
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
"context_line": line.strip(),
|
|
132
|
-
"line_number": idx,
|
|
133
|
-
}
|
|
134
|
-
)
|
|
207
|
+
hit = True
|
|
208
|
+
matches.append({"source": source, "pattern": pattern,
|
|
209
|
+
"candidate_category": candidate_category, **context})
|
|
210
|
+
if not hit and export_feeder and named:
|
|
211
|
+
matches.append({"source": source, "pattern": None, "candidate_category": None, **context})
|
|
135
212
|
return matches
|
|
136
213
|
|
|
137
214
|
|
|
@@ -240,23 +317,25 @@ def apply_migration_exclusion(
|
|
|
240
317
|
|
|
241
318
|
|
|
242
319
|
def collapse_duplicates(matches: list[dict]) -> tuple[list[dict], int]:
|
|
243
|
-
"""Collapse matches that repeat the same text under the same category.
|
|
320
|
+
"""Collapse matches that repeat the same text under the same candidate category.
|
|
244
321
|
|
|
245
322
|
Returns (kept, collapsed). The dedup key is
|
|
246
|
-
`(
|
|
247
|
-
changelog restates the same deprecation across many releases, and
|
|
248
|
-
|
|
249
|
-
same thing.
|
|
323
|
+
`(candidate_category, whitespace-normalized lowercased context_line)`: a
|
|
324
|
+
temporal changelog restates the same deprecation across many releases, and
|
|
325
|
+
a pull request template repeats the same checkbox in every pull request, so
|
|
326
|
+
each restatement would otherwise be its own candidate saying the same thing.
|
|
250
327
|
|
|
251
328
|
The first occurrence in scan order survives and gains two fields:
|
|
252
|
-
`occurrences` (how many
|
|
253
|
-
`duplicate_of` (the `{source, line_number}` of every later
|
|
254
|
-
nothing is silently destroyed.
|
|
329
|
+
`occurrences` (how many keyword hits collapsed into it, itself included)
|
|
330
|
+
and `duplicate_of` (the `{source, line_number}` of every later hit), so
|
|
331
|
+
nothing is silently destroyed. Two keywords of one candidate category on
|
|
332
|
+
one line count twice, and the second hit points back at the same line.
|
|
333
|
+
Records are copies: the inputs are untouched.
|
|
255
334
|
"""
|
|
256
335
|
seen: dict[tuple[str, str], dict] = {}
|
|
257
336
|
kept: list[dict] = []
|
|
258
337
|
for m in matches:
|
|
259
|
-
key = (m["
|
|
338
|
+
key = (m["candidate_category"], " ".join(m["context_line"].split()).lower())
|
|
260
339
|
survivor = seen.get(key)
|
|
261
340
|
if survivor is not None:
|
|
262
341
|
survivor["occurrences"] += 1
|
|
@@ -270,24 +349,50 @@ def collapse_duplicates(matches: list[dict]) -> tuple[list[dict], int]:
|
|
|
270
349
|
return kept, len(matches) - len(kept)
|
|
271
350
|
|
|
272
351
|
|
|
352
|
+
def change_section(section: str | None) -> bool:
|
|
353
|
+
"""True when `section` (a heading line) is a change section: its text,
|
|
354
|
+
without its `#`s and the marks around its words, is a change name as a
|
|
355
|
+
whole. `### Removed` and `## Breaking Changes` are; `## What's Changed`
|
|
356
|
+
and `## Type of change` are not."""
|
|
357
|
+
title = _HEADING_MARKS.sub("", (section or "").strip().lstrip("#"))
|
|
358
|
+
return bool(_CHANGE_SECTION.fullmatch(title))
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def rank(match: dict) -> int:
|
|
362
|
+
"""0 for a keyword hit or a line under a change section, 1 for a plain
|
|
363
|
+
export mention (an "### Added" bullet)."""
|
|
364
|
+
if match.get("pattern") is not None:
|
|
365
|
+
return 0
|
|
366
|
+
return 0 if change_section(match.get("section")) else 1
|
|
367
|
+
|
|
368
|
+
|
|
273
369
|
def apply_cap(
|
|
274
370
|
matches: list[dict], cap: int, skill_md_source: str | None
|
|
275
371
|
) -> tuple[list[dict], int]:
|
|
276
|
-
"""Keep at most `cap`
|
|
277
|
-
|
|
278
|
-
A cap of 0 or less means unlimited.
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
372
|
+
"""Keep at most `cap` candidates. Returns (kept, dropped).
|
|
373
|
+
|
|
374
|
+
A cap of 0 or less means unlimited. Rank 0 (keyword hits, and export
|
|
375
|
+
mentions under a change section) fills the cap before rank 1 (plain
|
|
376
|
+
export mentions). Within a rank the feeders share the cap in turns: each
|
|
377
|
+
turn takes the next candidate of every feeder, in scan order, so one long
|
|
378
|
+
feeder (a years-deep changelog) cannot crowd out the rest. The compiled
|
|
379
|
+
SKILL.md's own candidates take only the places of their rank left after
|
|
380
|
+
every other feeder's, because its body restates what compile wrote: the
|
|
381
|
+
skill's own text never outranks an upstream line of its rank. The kept records are
|
|
382
|
+
returned in scan order, so a run that does not hit the cap is ordered
|
|
383
|
+
exactly as the scan.
|
|
283
384
|
"""
|
|
284
385
|
if cap <= 0 or len(matches) <= cap:
|
|
285
386
|
return matches, 0
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
387
|
+
order: list[int] = []
|
|
388
|
+
for level in (0, 1):
|
|
389
|
+
by_feeder: dict[str, list[int]] = {}
|
|
390
|
+
for i, m in enumerate(matches):
|
|
391
|
+
if m["source"] != skill_md_source and rank(m) == level:
|
|
392
|
+
by_feeder.setdefault(m["source"], []).append(i)
|
|
393
|
+
order += [i for turn in itertools.zip_longest(*by_feeder.values()) for i in turn if i is not None]
|
|
394
|
+
order += [i for i, m in enumerate(matches) if m["source"] == skill_md_source and rank(m) == level]
|
|
395
|
+
keep = sorted(order[:cap])
|
|
291
396
|
return [matches[i] for i in keep], len(matches) - cap
|
|
292
397
|
|
|
293
398
|
|
|
@@ -303,18 +408,35 @@ def _load(path: str) -> str | None:
|
|
|
303
408
|
return text
|
|
304
409
|
|
|
305
410
|
|
|
411
|
+
def load_exports(path: str | None) -> frozenset[str]:
|
|
412
|
+
"""The entries[].export_name values of a provenance map; empty when the
|
|
413
|
+
path is None, missing, empty or not a provenance map."""
|
|
414
|
+
text = _load(path) if path else None
|
|
415
|
+
if text is None:
|
|
416
|
+
return frozenset()
|
|
417
|
+
try:
|
|
418
|
+
data = json.loads(text)
|
|
419
|
+
except ValueError:
|
|
420
|
+
return frozenset()
|
|
421
|
+
entries = data.get("entries") if isinstance(data, dict) else None
|
|
422
|
+
names = {e.get("export_name") for e in entries or [] if isinstance(e, dict)}
|
|
423
|
+
return frozenset(n for n in names if isinstance(n, str) and n and n not in IGNORED_EXPORT_NAMES)
|
|
424
|
+
|
|
425
|
+
|
|
306
426
|
def scan_files(
|
|
307
427
|
feeders: list[str],
|
|
308
428
|
skill_md: str | None,
|
|
309
429
|
*,
|
|
310
|
-
|
|
430
|
+
max_candidates: int = DEFAULT_MAX_CANDIDATES,
|
|
431
|
+
provenance: str | None = None,
|
|
311
432
|
) -> dict:
|
|
312
433
|
"""Scan every feeder + the skill-md feeder, apply the self-authorship
|
|
313
|
-
exclusions, collapse duplicates, cap the
|
|
314
|
-
object. Deterministic: feeders scanned in the given order."""
|
|
434
|
+
exclusions, collapse duplicates, cap the candidate pool, and return the
|
|
435
|
+
result object. Deterministic: feeders scanned in the given order."""
|
|
315
436
|
scanned: list[str] = []
|
|
316
437
|
matches: list[dict] = []
|
|
317
438
|
skill_md_text = None
|
|
439
|
+
exports = load_exports(provenance)
|
|
318
440
|
|
|
319
441
|
ordered = list(feeders)
|
|
320
442
|
if skill_md is not None:
|
|
@@ -327,7 +449,8 @@ def scan_files(
|
|
|
327
449
|
scanned.append(path)
|
|
328
450
|
if path == skill_md:
|
|
329
451
|
skill_md_text = text
|
|
330
|
-
|
|
452
|
+
export_feeder = path != skill_md and Path(path).name in EXPORT_FEEDERS
|
|
453
|
+
matches.extend(scan_text(text, path, exports, export_feeder))
|
|
331
454
|
|
|
332
455
|
# Frontmatter first, then the §4b section — the two windows are disjoint, so
|
|
333
456
|
# the total is order-independent, but running them in file order keeps the
|
|
@@ -335,7 +458,7 @@ def scan_files(
|
|
|
335
458
|
matches, front_excluded = apply_frontmatter_exclusion(matches, skill_md, skill_md_text)
|
|
336
459
|
matches, section_excluded = apply_migration_exclusion(matches, skill_md, skill_md_text)
|
|
337
460
|
matches, deduped = collapse_duplicates(matches)
|
|
338
|
-
matches, capped = apply_cap(matches,
|
|
461
|
+
matches, capped = apply_cap(matches, max_candidates, skill_md)
|
|
339
462
|
return {
|
|
340
463
|
"scanned": scanned,
|
|
341
464
|
"matches": matches,
|
|
@@ -343,7 +466,8 @@ def scan_files(
|
|
|
343
466
|
"excluded_count": front_excluded + section_excluded,
|
|
344
467
|
"deduped_count": deduped,
|
|
345
468
|
"capped_count": capped,
|
|
346
|
-
"cap": max(
|
|
469
|
+
"cap": max(max_candidates, 0),
|
|
470
|
+
"exports_known": len(exports),
|
|
347
471
|
}
|
|
348
472
|
|
|
349
473
|
|
|
@@ -351,11 +475,12 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
351
475
|
parser = argparse.ArgumentParser(
|
|
352
476
|
prog="scan-doc-rot",
|
|
353
477
|
description=(
|
|
354
|
-
"
|
|
478
|
+
"Doc-rot correction-candidate scan (step-doc-rot.md §2): "
|
|
355
479
|
"case-insensitive substring match of feeder artifacts against the fixed "
|
|
356
|
-
"
|
|
480
|
+
"keyword table, with the compiled SKILL.md's frontmatter and "
|
|
357
481
|
"Migration & Deprecation Warnings section excluded, duplicates collapsed, "
|
|
358
|
-
"and the
|
|
482
|
+
"and the candidate pool capped. The step's judgment pass decides which "
|
|
483
|
+
"candidates are corrections."
|
|
359
484
|
),
|
|
360
485
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
361
486
|
)
|
|
@@ -377,15 +502,23 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
377
502
|
dest="skill_md",
|
|
378
503
|
default=None,
|
|
379
504
|
help="Compiled/staged SKILL.md feeder; its frontmatter and its Migration & "
|
|
380
|
-
"Deprecation Warnings section are excluded from matches
|
|
505
|
+
"Deprecation Warnings section are excluded from matches, and its other "
|
|
506
|
+
"candidates come last when the pool is capped.",
|
|
507
|
+
)
|
|
508
|
+
parser.add_argument(
|
|
509
|
+
"--provenance",
|
|
510
|
+
dest="provenance",
|
|
511
|
+
default=None,
|
|
512
|
+
help="The staged provenance-map.json: a changelog.md, releases.md or prs.md line "
|
|
513
|
+
"that names one of its entries[].export_name values is a candidate too.",
|
|
381
514
|
)
|
|
382
515
|
parser.add_argument(
|
|
383
|
-
"--max-
|
|
384
|
-
dest="
|
|
516
|
+
"--max-candidates",
|
|
517
|
+
dest="max_candidates",
|
|
385
518
|
type=int,
|
|
386
|
-
default=
|
|
519
|
+
default=DEFAULT_MAX_CANDIDATES,
|
|
387
520
|
help=(
|
|
388
|
-
f"Maximum
|
|
521
|
+
f"Maximum candidates to emit (default {DEFAULT_MAX_CANDIDATES}). "
|
|
389
522
|
"0 or negative means unlimited."
|
|
390
523
|
),
|
|
391
524
|
)
|
|
@@ -400,7 +533,7 @@ def main(argv=None) -> int:
|
|
|
400
533
|
parser.print_usage(file=sys.stderr)
|
|
401
534
|
print("error: supply at least one feeder path or --skill-md", file=sys.stderr)
|
|
402
535
|
return 1
|
|
403
|
-
result = scan_files(feeders, args.skill_md,
|
|
536
|
+
result = scan_files(feeders, args.skill_md, max_candidates=args.max_candidates, provenance=args.provenance)
|
|
404
537
|
print(json.dumps(result, indent=2))
|
|
405
538
|
return 0
|
|
406
539
|
|
|
@@ -11,9 +11,10 @@ Produces a consolidated stack skill documenting how libraries connect. **Code-mo
|
|
|
11
11
|
|
|
12
12
|
## Conventions
|
|
13
13
|
|
|
14
|
-
- Bare paths (e.g. `references/<name>.md`) resolve from the skill root.
|
|
15
|
-
-
|
|
16
|
-
-
|
|
14
|
+
- Bare paths (e.g. `references/<name>.md`) resolve from the skill root, inside a `references/` file too: only a stage's frontmatter `nextStepFile` names a file beside it.
|
|
15
|
+
- **Module-level path exception:** bare paths beginning with `knowledge/` or `shared/` resolve from the SKF module root (`{project-root}/_bmad/skf/` installed, `src/` in dev), not the skill root; e.g. `knowledge/tool-resolution.md`, `knowledge/version-paths.md` and `shared/references/feasibility-report-schema.md`.
|
|
16
|
+
- **Sibling skills:** a path that names another SKF skill's folder (`skf-<name>/...`) resolves from the SKF module root, and that skill must be installed with this one.
|
|
17
|
+
- `references/` holds prompt content carved out of SKILL.md (workflow stages chained via frontmatter `nextStepFile`, plus static reference docs); `assets/` holds the stack skill template, the metadata contract and the provenance map schema.
|
|
17
18
|
- `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives, if present).
|
|
18
19
|
- `{project-root}`-prefixed paths resolve from the project working directory.
|
|
19
20
|
- `{skill-name}` resolves to the skill directory's basename.
|
|
@@ -28,23 +29,33 @@ These rules apply to every step in this workflow:
|
|
|
28
29
|
|
|
29
30
|
- Zero hallucination — all extracted content must trace to actual source code (compose-mode inferences must be labeled)
|
|
30
31
|
- Only load one step file at a time — never preload future steps
|
|
31
|
-
- If any instruction references a subprocess or tool you lack, achieve the outcome in your main context thread
|
|
32
|
+
- If any instruction references a subprocess or tool you lack, achieve the outcome in your main context thread, with two exceptions: never decide by hand whether SKF generated a folder (the ownership check), and never work out by hand what a shared helper computes where its step names a `helper-missing` HALT: that helper resolving to no path stops the run with exit 3 `helper-missing` (`references/invocation-contract.md` lists every such halt). Where a step says what to do without its helper (an advisory check, a default value), do as it says
|
|
33
|
+
- Never write into a skill folder SKF did not generate — generate-output §1 runs the inventory's write check before any prior metadata is read and again before staging
|
|
32
34
|
- Always communicate in `{communication_language}`
|
|
33
|
-
- If `{headless_mode}` is true, auto-proceed through confirmation gates with their default action and
|
|
34
|
-
-
|
|
35
|
+
- If `{headless_mode}` is true, auto-proceed through confirmation gates with their default action, and record each auto-decision in the run sink the moment the gate takes it, with the `record --decision` command the gate shows
|
|
36
|
+
- Every HARD HALT names its exit code, `halt_reason` and phase, and prints its envelope through the shared emitter with the command its step file shows (the `init:emitter` halt, with no emitter, only displays its message); never type an `SKF_STACK_RESULT_JSON` line
|
|
37
|
+
- Warnings use a single accumulator: see `## Workflow state contract` below for shape and surfacing.
|
|
35
38
|
|
|
36
39
|
## Workflow state contract
|
|
37
40
|
|
|
38
|
-
Every step that emits a warning appends a structured entry to a single
|
|
41
|
+
Every step that emits a warning appends a structured entry to a single list named `workflow_warnings[]` (the one accumulator for the whole workflow). Each entry has the shape `{step: "step-NN", severity: "info|warn|error", code: "<short-slug>", message: "<human text>", context: {<optional fields>}}`. Appending one means recording it in the run sink at once, the `context` fields folded into the message. Write its `[{step}/{severity}] {code}: {message}` line to `{run_dir}/warning.txt` with a file write, never an `echo`: a message can quote backticks or `$( )`, which a shell runs. Then run:
|
|
39
42
|
|
|
40
|
-
|
|
43
|
+
```bash
|
|
44
|
+
uv run {emitEnvelopeHelper} record --run-dir "{run_dir}" --warning "$(cat "{run_dir}/warning.txt")"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The shell does not expand what the substitution prints, so the message reaches the sink as text.
|
|
48
|
+
|
|
49
|
+
The sink, `{run_dir}/warnings.jsonl`, is the list: step 7 §8b lists it in `evidence-report.md`, step 9 §1 renders it, and the result envelope carries it.
|
|
50
|
+
|
|
51
|
+
**Single-pass, no resume:** the run's state lives in `{run_dir}`, the run folder step 1 creates under `{project-root}/_bmad-output/.skf-run/` (the warnings and auto-decisions, step 3's import counts, step 4's extraction bundle and step 6's draft), and an interrupted run starts again from step 1.
|
|
41
52
|
|
|
42
53
|
## Stages
|
|
43
54
|
|
|
44
55
|
| # | Step | File | Auto-proceed |
|
|
45
56
|
|---|------|------|--------------|
|
|
46
|
-
| 1 | Initialize & Mode Detection | references/init.md |
|
|
47
|
-
| 2 | Detect Manifests | references/detect-manifests.md |
|
|
57
|
+
| 1 | Initialize & Mode Detection | references/init.md | Conditional |
|
|
58
|
+
| 2 | Detect Manifests | references/detect-manifests.md | Conditional (no manifest found) |
|
|
48
59
|
| 3 | Rank & Confirm Libraries | references/rank-and-confirm.md | No (confirm) |
|
|
49
60
|
| 4 | Parallel Extract | references/parallel-extract.md | Yes |
|
|
50
61
|
| 5 | Detect Integrations | references/detect-integrations.md | Yes |
|
|
@@ -56,35 +67,7 @@ Every step that emits a warning appends a structured entry to a single in-memory
|
|
|
56
67
|
|
|
57
68
|
## Invocation Contract
|
|
58
69
|
|
|
59
|
-
|
|
60
|
-
|--------|--------|
|
|
61
|
-
| **Inputs** | project_path [required], mode (code/compose) [auto-detected] |
|
|
62
|
-
| **Gates** | step 3: Confirm Gate [C] | step 6: Review Gate [C] |
|
|
63
|
-
| **Outputs** | SKILL.md (stack), context-snippet.md, metadata.json |
|
|
64
|
-
| **Headless** | All gates auto-resolve with default action when `{headless_mode}` is true |
|
|
65
|
-
| **Exit codes** | See "Exit Codes" below |
|
|
66
|
-
|
|
67
|
-
## Exit Codes
|
|
68
|
-
|
|
69
|
-
Every HARD HALT in this workflow exits with a stable code so headless automators can branch on the failure class without grepping message text:
|
|
70
|
-
|
|
71
|
-
| Code | Meaning | Raised by (halt_reason) |
|
|
72
|
-
| ---- | -------------------- | ------------------------------------------------------------------------------------------ |
|
|
73
|
-
| 0 | success | step 10 (terminal handoff to shared health-check) |
|
|
74
|
-
| 2 | input / precondition invalid | step 1 §0 `config.yaml` missing/malformed (`config-missing`); step 2 §2 headless with no manifests (`no-manifests`, S2); step 4 §3 all extractions failed (`all-extractions-failed`, B7); step 5 §2 feasibility-report `schemaVersion` mismatch (`schema-version-mismatch`) |
|
|
75
|
-
| 3 | resolution-failure | step 1 §1 `forge-tier.yaml` missing (`forge-tier-missing`); step 2 §0 compose-mode skill-resolution corruption (manifest + symlink both fail); step 2 §0 compose-mode zero qualifying skills (S1/B4); step 4 §0 compose-cycle — all `resolution-failure` |
|
|
76
|
-
| 4 | write-failure | step 7 §1 stage-dir / commit-dir failure; step 7 §1 group-dir collision when an existing non-stack skill occupies the target path — both `write-failure` |
|
|
77
|
-
| 6 | user-cancelled | any interactive menu in step 3 / step 6 when the user selects `[X]` Cancel and exit (`user-cancelled`) |
|
|
78
|
-
|
|
79
|
-
## Result Contract (Headless)
|
|
80
|
-
|
|
81
|
-
When `{headless_mode}` is true, step 9 emits a single-line JSON envelope on **stdout** before chaining to step 10, and every HARD HALT emits the same envelope shape on **stderr** with `status: "error"`:
|
|
82
|
-
|
|
83
|
-
```
|
|
84
|
-
SKF_STACK_RESULT_JSON: {"status":"success|error","skill_package":"…|null","skill_name":"…","stack_libraries":["…"],"mode":"code|compose","exit_code":0,"halt_reason":null}
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
`status` is `"success"` on the terminal happy path, `"error"` on any HALT. `skill_package` is the absolute path to the committed stack-skill directory (or `null` on error before commit). `skill_name` is the stack skill's published name (e.g. `{project_name}-stack`). `stack_libraries` is the array of library names included in the stack (constituent skill names in compose-mode, dependency names in code-mode). `mode` is `"code"` or `"compose"` per the run's resolved mode (`null` if the run halts before mode resolution). `halt_reason` is one of: `null` (success), `"config-missing"`, `"forge-tier-missing"`, `"no-manifests"`, `"all-extractions-failed"`, `"schema-version-mismatch"`, `"resolution-failure"`, `"write-failure"`, `"user-cancelled"`. `exit_code` matches the table above. Fields unknown at the halt point are `null` (`skill_name`) or `[]` (`stack_libraries`) — e.g. a `config-missing` halt precedes `project_name` resolution.
|
|
70
|
+
`references/invocation-contract.md` holds the inputs, gates and outputs, the exit code and `halt_reason` of every HARD HALT, and the `SKF_STACK_RESULT_JSON` result envelope.
|
|
88
71
|
|
|
89
72
|
## On Activation
|
|
90
73
|
|
|
@@ -100,30 +83,20 @@ SKF_STACK_RESULT_JSON: {"status":"success|error","skill_package":"…|null","ski
|
|
|
100
83
|
3. **Resolve workflow customization.** Run:
|
|
101
84
|
|
|
102
85
|
```bash
|
|
103
|
-
|
|
104
|
-
--skill {skill-root} --key workflow
|
|
86
|
+
uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow
|
|
105
87
|
```
|
|
106
88
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
- `{skill-root}/customize.toml` — bundled defaults
|
|
110
|
-
- `_bmad/custom/<skill-name>.toml` under `{project-root}` — team overrides (committed)
|
|
111
|
-
- `_bmad/custom/<skill-name>.user.toml` under `{project-root}` — personal overrides (gitignored)
|
|
112
|
-
|
|
113
|
-
If the script fails or is missing, fall back to reading `{skill-root}/customize.toml` directly — the bundled defaults are an empty string for each path scalar.
|
|
89
|
+
It merges the bundled `{skill-root}/customize.toml` with `{project-root}/_bmad/custom/skf-create-stack-skill.toml` (team overrides, committed) and `.user.toml` (personal overrides, gitignored). When it exits non-zero, prints no JSON or is missing, print one line, `[activation/warn] customization_resolver_unavailable: <reason>` (`<reason>`: its first stderr line, `not found` when the script is missing, `no JSON` when it printed none). If the resolver cannot run, read `{skill-root}/customize.toml` alone and use its bundled defaults: the `{project-root}/_bmad/custom/` overrides do not apply to this run. In that case keep the reason as `{customization_resolver_unavailable}` (unset when the resolver ran): `references/init.md` records it as the run's first warning once its pre-flight has created `{run_dir}`.
|
|
114
90
|
|
|
115
|
-
Apply the path-scalar fallback now so stage files don't have to repeat the conditional logic. For each of the
|
|
91
|
+
Apply the path-scalar fallback now so stage files don't have to repeat the conditional logic. For each of the two scalars, if the merged value is empty or absent, use the bundled default:
|
|
116
92
|
|
|
117
|
-
- `{stackSkillTemplatePath}` ← `workflow.stack_skill_template_path` if non-empty, else `assets/stack-skill-template.md`
|
|
93
|
+
- `{stackSkillTemplatePath}` ← `workflow.stack_skill_template_path` if non-empty, else `assets/stack-skill-template.md` (the SKILL.md, snippet and reference-file structures; `assets/metadata-contract.md` holds the `metadata.json` contract, which no override replaces)
|
|
118
94
|
- `{integrationPatternsPath}` ← `workflow.integration_patterns_path` if non-empty, else `references/integration-patterns.md`
|
|
119
|
-
- `{manifestPatternsPath}` ← `workflow.manifest_patterns_path` if non-empty, else `references/manifest-patterns.md`
|
|
120
|
-
- `{composeModeRulesPath}` ← `workflow.compose_mode_rules_path` if non-empty, else `references/compose-mode-rules.md`
|
|
121
|
-
- `{provenanceMapSchemaPath}` ← `workflow.provenance_map_schema_path` if non-empty, else `assets/provenance-map-schema.md`
|
|
122
95
|
|
|
123
|
-
Also resolve `{onCompleteCommand}` ← `workflow.on_complete` if non-empty, else empty string (no-op
|
|
96
|
+
Also resolve `{onCompleteCommand}` ← `workflow.on_complete` if non-empty, else empty string (no-op: `references/report.md` §2c then skips the hook).
|
|
124
97
|
|
|
125
|
-
Stash
|
|
98
|
+
Stash both paths plus `{onCompleteCommand}` as workflow-context variables. Stage files reference `{stackSkillTemplatePath}` and `{integrationPatternsPath}` directly; empty-string overrides fall through to the bundled default.
|
|
126
99
|
|
|
127
|
-
Also apply the array surfaces: run `workflow.activation_steps_prepend` now, keep `workflow.persistent_facts` as standing context (`file:` entries load their contents), then run `workflow.activation_steps_append` after.
|
|
100
|
+
Also apply the array surfaces: run `workflow.activation_steps_prepend` now, keep `workflow.persistent_facts` as standing context (`file:` entries load their contents: the bundled default loads every `project-context.md` under `{project-root}`; an entry prefixed `!` drops each earlier entry it names and loads nothing itself, so an override's `"!file:{project-root}/**/project-context.md"` turns that default off), then run `workflow.activation_steps_append` after.
|
|
128
101
|
|
|
129
102
|
4. Load, read the full file, and then execute `references/init.md` to begin the workflow.
|