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,679 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Releasing SKF
|
|
3
|
-
description: Maintainer reference for the release pipeline — branch protection, release workflow, and rollback procedures.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
This document records the configuration that gates releases of `bmad-module-skill-forge`. It exists so a future maintainer (including future-you) can audit, restore, or extend the pipeline without reverse-engineering GitHub settings.
|
|
7
|
-
|
|
8
|
-
For background on GitHub rulesets vs legacy branch protection, see the [GitHub ruleset docs](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/about-rulesets).
|
|
9
|
-
|
|
10
|
-
## Branch Protection on `main`
|
|
11
|
-
|
|
12
|
-
`main` is gated by a **GitHub repository ruleset** (not legacy branch protection). The legacy "Settings → Branches → Branch protection rules" surface returns 404 for this repo.
|
|
13
|
-
|
|
14
|
-
**Ruleset:** `Default` — id `13855503` — applies to `~DEFAULT_BRANCH` (currently `main`) — enforcement `active`.
|
|
15
|
-
|
|
16
|
-
**Active rules (5):**
|
|
17
|
-
|
|
18
|
-
| Rule | Effect |
|
|
19
|
-
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
|
|
20
|
-
| `deletion` | Branch cannot be deleted. |
|
|
21
|
-
| `non_fast_forward` | Force-push blocked. |
|
|
22
|
-
| `pull_request` | Requires ≥ 1 approving review; `require_code_owner_review: true` (see CODEOWNERS note below); merge/squash/rebase allowed. |
|
|
23
|
-
| `code_quality` | Blocks merge on `severity: errors` from GitHub code-quality checks. |
|
|
24
|
-
| `required_status_checks` | Merge blocked until all seven `quality.yaml` checks pass (names below). |
|
|
25
|
-
|
|
26
|
-
**Required status checks (8):** sourced from `.github/workflows/quality.yaml` job keys. Matrix jobs expand to `jobname (matrix-value)`:
|
|
27
|
-
|
|
28
|
-
- `prettier`
|
|
29
|
-
- `eslint`
|
|
30
|
-
- `markdownlint`
|
|
31
|
-
- `validate (ubuntu-latest)`
|
|
32
|
-
- `validate (windows-latest)`
|
|
33
|
-
- `python (ubuntu-latest)`
|
|
34
|
-
- `python (windows-latest)`
|
|
35
|
-
- `docs-links`
|
|
36
|
-
|
|
37
|
-
**Coupling with `quality.yaml`:** if that workflow renames a job or changes the `strategy.matrix.os` for `validate` or `python`, the ruleset's `required_status_checks` list must be updated in lock-step — otherwise merges to `main` will either block on a check name that no longer reports, or silently pass without the renamed check. Update both in the same PR.
|
|
38
|
-
|
|
39
|
-
**`strict_required_status_checks_policy: false`** — PR branches are not forced to be up-to-date with `main` before merging. This avoids constant rebases on a low-traffic repo. Flip to `true` if concurrent merges start producing logical conflicts the checks can't catch.
|
|
40
|
-
|
|
41
|
-
**CODEOWNERS note:** the `pull_request` rule has `require_code_owner_review: true`, but no `.github/CODEOWNERS` file exists in the repo today. GitHub treats the code-owner requirement as vacuously satisfied when the file is absent, so this setting is currently a no-op — the only active review gate is `required_approving_review_count: 1`. If a CODEOWNERS file is added later, make sure the listed owners can actually approve PRs from other authors. GitHub's universal rule is that a PR author cannot approve their own PR — so a CODEOWNERS file that lists only a solo maintainer would deadlock every PR that maintainer opens (they'd be the sole eligible code-owner reviewer but also the author).
|
|
42
|
-
|
|
43
|
-
**Bypass actors:** `RepositoryRole` actor_id=5 (Admin), `bypass_mode: pull_request`. Admins can bypass the ruleset **only via a pull request**, never via direct push. This preserves `non_fast_forward` and the required-checks gate for the `github-actions[bot]` account that the future `release.yaml` workflow (Story 3.1) will use to push tags and commits to `main`. **Do not add a bot-specific bypass**; it would defeat the whole purpose of this ruleset.
|
|
44
|
-
|
|
45
|
-
### Inspect current state
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
gh api repos/armelhbobdad/bmad-module-skill-forge/rulesets/13855503
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
### Restore from a saved baseline
|
|
52
|
-
|
|
53
|
-
A JSON baseline captured before any change can be replayed via:
|
|
54
|
-
|
|
55
|
-
```bash
|
|
56
|
-
# Extract the full set of fields required by a ruleset PUT from a saved baseline.json.
|
|
57
|
-
# PUT replaces the resource wholesale — omitting any of these fields either 422s or
|
|
58
|
-
# silently resets them server-side. `bypass_actors` is optional (empty array is the
|
|
59
|
-
# default) but is included here to preserve the admin-via-PR bypass.
|
|
60
|
-
jq '{name, target, enforcement, conditions, rules, bypass_actors}' baseline.json > /tmp/restore.json
|
|
61
|
-
|
|
62
|
-
gh api --method PUT \
|
|
63
|
-
repos/armelhbobdad/bmad-module-skill-forge/rulesets/13855503 \
|
|
64
|
-
--input /tmp/restore.json
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
The GitHub ruleset API uses `PUT` (not `PATCH`) for updates, and the `rules` array is replaced wholesale — it is not merged server-side. Always fetch current state, modify the in-memory copy, and `PUT` the complete list.
|
|
68
|
-
|
|
69
|
-
## Release Environment
|
|
70
|
-
|
|
71
|
-
The publish job is gated by a **GitHub [deployment environment](https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment) with a required-reviewer rule** (not by workflow logic). A job declaring `environment: release` pauses until a listed reviewer clicks "Approve and deploy" in the Actions UI.
|
|
72
|
-
|
|
73
|
-
**Environment:** `release` — id `14347249917` — created 2026-04-20.
|
|
74
|
-
|
|
75
|
-
| Setting | Value |
|
|
76
|
-
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
77
|
-
| `wait_timer` | `0` (no artificial delay; approval is the only gate) |
|
|
78
|
-
| `prevent_self_review` | `false` — see rationale below |
|
|
79
|
-
| `reviewers` | `armelhbobdad` (user id `132626034`), 1 approver |
|
|
80
|
-
| `deployment_branch_policy` | `custom_branch_policies: true`, list: `main` only |
|
|
81
|
-
| Environment-scoped secrets | `0` (invariant — see "No secret is scoped to this environment" note below) |
|
|
82
|
-
| Cost | `$0` on public-repo tier (environments, required reviewers, and branch policies are [free for public repositories](https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#about-environments)) |
|
|
83
|
-
|
|
84
|
-
**`prevent_self_review: false` — correctness constraint, not a loosened control.** Solo-maintainer setups cannot self-approve when this is `true`, so the gate would deadlock on any maintainer-triggered publish. The value flips to `true` the moment a second reviewer joins — do not leave it loose by inertia.
|
|
85
|
-
|
|
86
|
-
**No secret is scoped to this environment.** The repo no longer carries an `NPM_TOKEN` secret at any scope (removed in Story 6.3 post-v1.0.0 once OIDC trusted publishing was operationally proven by the v1.0.0 launch). The invariant: the `release` env must have zero environment-scoped secrets, and the repo must have zero `NPM_TOKEN`-shaped secrets at any scope. If a future change scopes any secret to this environment, or if an `NPM_TOKEN` secret is ever re-added at repo scope, re-audit whether the OIDC trusted-publisher path is still in force — the OIDC path SHOULD be self-sufficient and a re-added token is a signal that something has regressed off the canonical path.
|
|
87
|
-
|
|
88
|
-
**Audit command.** Both halves of the invariant are machine-checkable:
|
|
89
|
-
|
|
90
|
-
```bash
|
|
91
|
-
# Repo-scope: expect 0
|
|
92
|
-
gh secret list --repo armelhbobdad/bmad-module-skill-forge | grep -ci npm_token
|
|
93
|
-
|
|
94
|
-
# Env-scope: expect {"total_count":0,"secrets":[]}
|
|
95
|
-
gh api repos/armelhbobdad/bmad-module-skill-forge/environments/release/secrets
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
**Coupling with npm trusted publishing (Story 1.3).** The npm trusted publisher binds on four fields — `organization=armelhbobdad`, `repository=bmad-module-skill-forge`, `workflow filename=release.yaml`, `environment=release`. The environment name above is load-bearing: any rename here must be accompanied by a matching npm-side update in the same change, or the next publish returns 404.
|
|
99
|
-
|
|
100
|
-
### Inspect current state
|
|
101
|
-
|
|
102
|
-
```bash
|
|
103
|
-
# Environment itself (reviewers, branch-policy shape, prevent_self_review)
|
|
104
|
-
gh api repos/armelhbobdad/bmad-module-skill-forge/environments/release
|
|
105
|
-
|
|
106
|
-
# Allowed deployment branches (expect: one entry, name "main")
|
|
107
|
-
gh api repos/armelhbobdad/bmad-module-skill-forge/environments/release/deployment-branch-policies
|
|
108
|
-
|
|
109
|
-
# Environment-scoped secrets (expect: zero)
|
|
110
|
-
gh api repos/armelhbobdad/bmad-module-skill-forge/environments/release/secrets
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
### Restore / re-apply
|
|
114
|
-
|
|
115
|
-
Two-call pattern — the environment and its branch-policy list are separate resources. Creating the environment alone with `custom_branch_policies: true` leaves the allow-list empty, which rejects every dispatch; the second call is mandatory.
|
|
116
|
-
|
|
117
|
-
```bash
|
|
118
|
-
# (1) Create or update the environment with the required-reviewer gate
|
|
119
|
-
gh api --method PUT repos/armelhbobdad/bmad-module-skill-forge/environments/release --input - <<'JSON'
|
|
120
|
-
{
|
|
121
|
-
"wait_timer": 0,
|
|
122
|
-
"prevent_self_review": false,
|
|
123
|
-
"reviewers": [{ "type": "User", "id": 132626034 }],
|
|
124
|
-
"deployment_branch_policy": {
|
|
125
|
-
"protected_branches": false,
|
|
126
|
-
"custom_branch_policies": true
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
JSON
|
|
130
|
-
|
|
131
|
-
# (2) Add main to the allowed deployment branches.
|
|
132
|
-
# Returns HTTP 422 ("already_exists") if main is already on the list —
|
|
133
|
-
# safe to ignore on re-apply. To pre-check:
|
|
134
|
-
# gh api repos/armelhbobdad/bmad-module-skill-forge/environments/release/deployment-branch-policies
|
|
135
|
-
gh api --method POST repos/armelhbobdad/bmad-module-skill-forge/environments/release/deployment-branch-policies --input - <<'JSON'
|
|
136
|
-
{ "name": "main" }
|
|
137
|
-
JSON
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
### Temporarily allowing a feature branch
|
|
141
|
-
|
|
142
|
-
A release workflow that declares `environment: release` will be rejected from any branch not on the allow-list. For legitimate validation cuts from a feature branch (Story 3.2's alpha cut is the canonical case), widen the allow-list for the duration of the test and tighten it back immediately:
|
|
143
|
-
|
|
144
|
-
```bash
|
|
145
|
-
# Allow the feature branch and capture the returned policy id into a shell var.
|
|
146
|
-
# Using command substitution + --jq .id avoids the "eyeball the JSON and paste
|
|
147
|
-
# the id later" footgun — if the POST succeeds, $POLICY_ID is ready for the revoke.
|
|
148
|
-
POLICY_ID=$(gh api --method POST \
|
|
149
|
-
repos/armelhbobdad/bmad-module-skill-forge/environments/release/deployment-branch-policies \
|
|
150
|
-
--input - --jq .id <<'JSON'
|
|
151
|
-
{ "name": "feat/my-validation-branch" }
|
|
152
|
-
JSON
|
|
153
|
-
)
|
|
154
|
-
echo "POLICY_ID=$POLICY_ID" # confirm a numeric id was captured before proceeding
|
|
155
|
-
|
|
156
|
-
# ... run the validation cut, approve via the reviewer UI, confirm publish ...
|
|
157
|
-
|
|
158
|
-
# Revoke immediately — the allow-list must return to { main } before leaving the session.
|
|
159
|
-
# Quote the expansion so the shell never interprets the id as a redirection token.
|
|
160
|
-
gh api --method DELETE \
|
|
161
|
-
"repos/armelhbobdad/bmad-module-skill-forge/environments/release/deployment-branch-policies/$POLICY_ID"
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
Leaving a feature branch on the allow-list is an unguarded hole — any later `workflow_dispatch` from that branch would be able to reach the publish path. Treat the revoke as the last step of the validation, not a follow-up.
|
|
165
|
-
|
|
166
|
-
## npm Trusted Publisher
|
|
167
|
-
|
|
168
|
-
The future `release.yaml` workflow (Story 3.1) publishes to npm via **OIDC trusted publishing** — no `NPM_TOKEN` is consulted during the publish step, and every published version carries an auto-attached SLSA Build Level 2 provenance attestation. For this to work, the npm package `bmad-module-skill-forge` has a trusted-publisher entry on npmjs.com that binds on four fields exactly matching what the workflow asserts at run time. A mismatch on any field causes an opaque `404` at publish time — the error ("npm could not match your workflow run") surfaces the failure class but does not name which of the four fields is wrong.
|
|
169
|
-
|
|
170
|
-
**Registered:** 2026-04-20 by `armelhbobdad`.
|
|
171
|
-
|
|
172
|
-
| Field | Value |
|
|
173
|
-
| ----------------- | ------------------------- |
|
|
174
|
-
| Publisher type | `GitHub Actions` |
|
|
175
|
-
| Organization/user | `armelhbobdad` |
|
|
176
|
-
| Repository | `bmad-module-skill-forge` |
|
|
177
|
-
| Workflow filename | `release.yaml` |
|
|
178
|
-
| Environment | `release` |
|
|
179
|
-
|
|
180
|
-
**Inspect current state:** visit the [npm package settings for `bmad-module-skill-forge`](https://www.npmjs.com/package/bmad-module-skill-forge) → **Settings** tab → **Trusted Publisher** section (npm UI as of 2026-04-20; if the tab is reorganised later, the section still lives on the package Settings page). Modification requires 2FA re-entry on the maintainer account. No CLI or public API for programmatic inspection of Trusted Publisher state exists as of 2026-04-20 — drift detection is UI-only until npm exposes one.
|
|
181
|
-
|
|
182
|
-
**Case-sensitive.** All four fields above use the exact lowercase forms shown; npm's matcher is an exact-string comparison. Do not capitalize on re-registration even if GitHub's UI surfaces a display-form with capitals.
|
|
183
|
-
|
|
184
|
-
**Rename coupling — the four fields are load-bearing.** Renaming OR deleting the `release` GitHub environment (see § Release Environment), renaming or moving `release.yaml` within `.github/workflows/`, or flipping the extension between `.yaml` and `.yml` each require matching updates in the same PR to: (a) the npm-side Trusted Publisher, (b) the Registered table above, and (c) the `## Release Process` enumeration in `README.md` (which names both `release` and the Trusted Publisher). Skipping any of these produces an opaque `404` on the next publish — the error names the failure class, not the specific field.
|
|
185
|
-
|
|
186
|
-
**Pre-registration inversion.** This entry was registered **before** `release.yaml` was authored (Story 3.1). The first live validator of the full OIDC chain is Story 3.2's alpha cut. If that cut's publish step 404s, open a **three-way comparison**: (1) the npm Settings tab, (2) the workflow YAML's `name` / `on` / `jobs.<id>.environment` lines, and (3) the Registered table above. The table is the ground truth because it captured the values at npm-save time — compare both the npm record and the workflow header against the table, never the workflow against itself (verifying the workflow against its own header will silently confirm a typo).
|
|
187
|
-
|
|
188
|
-
**`NPM_TOKEN` is gone — OIDC is the only publish credential surface.** The token was removed from repo-level secrets in Story 6.3 (post-v1.0.0, once OIDC trusted publishing was operationally proven by the v1.0.0 launch). SKF's npm publish credential surface is now OIDC-only. Future incidents requiring credential revocation involve npmjs.com's trusted-publisher config, not a repo secret. `release.yaml` authenticates via Trusted Publisher OIDC and continues to set `NPM_TOKEN: ""` explicitly in both the pre-publish dry-run step and the final publish step as defense-in-depth against a stale token being auto-picked-up by npm from the runner env — those literal empty-string assignments are not `secrets.*` reads and stay load-bearing regardless of the secret's presence or absence. Both sites carry a `# DO NOT REMOVE — FR4 defense-in-depth` inline comment so a future cleanup pass does not silently delete them; audit with `grep -c 'NPM_TOKEN: ""' .github/workflows/release.yaml` (expect `2`). If a future OIDC incident forces a last-resort token-based re-publish path, re-adding `NPM_TOKEN` at repo scope is the exception-path, not the default — document the flip in the commit body, file an issue for the OIDC incident class that required it, and remove the token again the moment OIDC is restored.
|
|
189
|
-
|
|
190
|
-
**Fixing a bad registration.** The npm UI exposes both **Edit** and **Delete** on an existing Trusted Publisher entry (observed 2026-04-20). Prefer edit for a single-field typo; prefer delete-and-re-add if multiple fields are wrong or the edit form ever feels ambiguous. **Pre-Story 3.2**: there is no destructive side effect because no publish is attempted yet, and delete-and-re-add keeps the audit trail cleaner. **Post-Story 3.2**: a publish that fires during the delete-and-re-add window will 404 — gate any delete-and-re-add behind a manual publish freeze (pause any active `release.yaml` runs, confirm no tags are in-flight) before touching the entry.
|
|
191
|
-
|
|
192
|
-
## Cutting a Release
|
|
193
|
-
|
|
194
|
-
The dispatch mechanics are worked end-to-end in [§ Cutting v1.0.0 under --tag latest](#cutting-v100-under---tag-latest). That section is a launch-specific record, but its dispatch command, two-gate sequence, and verification block are the same for every cut. The two steps below are general, apply to every cut, and have no other home — both are invisible until they bite, because the default state of each is the harmless one.
|
|
195
|
-
|
|
196
|
-
### Pre-dispatch — reconcile a non-empty `## [Unreleased]`
|
|
197
|
-
|
|
198
|
-
The `Restore CHANGELOG preamble` step splits `CHANGELOG.md` at the **first** `## [X.Y.Z]` header and treats everything above it as preamble. The generated block for the new version is therefore inserted _below_ any prose sitting under `## [Unreleased]`, and the workflow neither empties that section nor warns about it. Check before dispatching:
|
|
199
|
-
|
|
200
|
-
```bash
|
|
201
|
-
sed -n '/^## \[Unreleased\]/,/^## \[[0-9]/p' CHANGELOG.md
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
If the `## [Unreleased]` header is immediately followed by the first released header, there is nothing to do — that was the state at every cut through v2.0.2, which is why the interaction went unexercised for so long. If it carries prose, those entries describe work that is about to ship, and after the cut they read as unreleased directly above the release containing them.
|
|
205
|
-
|
|
206
|
-
**Do not hand-edit it before dispatch.** The `## [X.Y.Z]` header the prose belongs under does not exist until the workflow generates it. Reconcile after publish in a normal PR: move the prose under the generated version header and leave the `## [Unreleased]` header in place with an empty body — deleting the header itself breaks the section split the preamble-restore step depends on.
|
|
207
|
-
|
|
208
|
-
Prefer moving the prose over dropping it in favour of the generated list. Under `conventionalcommits`, `test:` and `chore:` subjects produce no generated entry at all, and one `fix:` subject often stands in for several distinct fixes, so the hand-written text is frequently the only record of both. v2.1.0 is the worked example: five bullets stranded, moved under `## [2.1.0]` in a follow-up PR.
|
|
209
|
-
|
|
210
|
-
### Post-publish — delete the bot temp branch after an admin-bypass merge
|
|
211
|
-
|
|
212
|
-
`Auto-merge bot PR` runs `gh pr merge --auto --merge --delete-branch`, so on the auto-merge path the temp branch `release/bot/vX.Y.Z-<run_id>` is removed when the queued merge fires. Clearing gate 2 with the PR merge button instead — admin bypass, the observed pattern for most cuts — merges the PR directly, and the queued `--delete-branch` never runs. The branch stays on origin.
|
|
213
|
-
|
|
214
|
-
```bash
|
|
215
|
-
# Expect zero once a cut is fully closed out.
|
|
216
|
-
git ls-remote --heads origin 'release/bot/*'
|
|
217
|
-
|
|
218
|
-
# Delete a leftover only after confirming its commit is reachable from main.
|
|
219
|
-
git fetch origin --quiet
|
|
220
|
-
git merge-base --is-ancestor <temp-branch-sha> origin/main \
|
|
221
|
-
&& git push origin --delete release/bot/vX.Y.Z-<run_id>
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
The tag anchors on the bot PR's merge commit on `main`, not on the temp branch (see the `Create and push tag` step's main-dispatch path), so deleting the branch after the merge orphans nothing. The reachability check is what distinguishes this from the tag-orphan case in [§ Scenario D](#scenario-d--tag-exists-but-npm-publish-failed); if the commit is _not_ reachable, the merge did not land and the branch is still load-bearing.
|
|
225
|
-
|
|
226
|
-
<!-- Rollback Playbook — added in Story 4.1 -->
|
|
227
|
-
|
|
228
|
-
## Rollback Playbook
|
|
229
|
-
|
|
230
|
-
> **NEVER `npm unpublish` v1.0.0.** Once `bmad-module-skill-forge` is published under `--tag latest` at v1.0.0, the version is immutable by policy (NFR6). The default rollback is `npm deprecate` + ship forward. See Scenario C for the narrow 72h / zero-dependents exception — which applies to **pre-v1.0.0 versions only**.
|
|
231
|
-
|
|
232
|
-
Scenarios A–F are recovery paths for a bad publish; Scenario G is a meta-recovery path for the release workflow itself. Each scenario follows a five-element shape: **Trigger** → **CLI** → **Expected outcome** → **Constraints** → **Verification**. A compact [cross-reference matrix](#cross-reference-matrix) sits at the end of this section for under-pressure triage. All `gh api` examples in this section use name-based lookups (ruleset by `name=="Default"`, environment by literal `release`) so they survive ruleset or environment re-creation.
|
|
233
|
-
|
|
234
|
-
Placeholder substitutions used throughout:
|
|
235
|
-
|
|
236
|
-
- `<bad>` — the broken version just published (e.g. `0.10.1`).
|
|
237
|
-
- `<previous_good>` — the last known-good version immediately before `<bad>` (e.g. `0.10.0`).
|
|
238
|
-
- `<next_version>` — the fix version produced by the next `release.yaml` dispatch (e.g. `0.10.2`).
|
|
239
|
-
- `<run-id>` — a GitHub Actions run id visible in the Actions UI and via `gh run list --workflow=release.yaml`.
|
|
240
|
-
|
|
241
|
-
### Scenario A — "Bad version published to `latest`, no users yet (within ~minutes)"
|
|
242
|
-
|
|
243
|
-
- **Trigger.** Maintainer notices a defect within minutes of publish; `npm view bmad-module-skill-forge dist-tags` still shows `<bad>` on `latest`; download count is negligible; no dependents have pinned `<bad>` yet.
|
|
244
|
-
|
|
245
|
-
> **Note on cache lag.** `npm view … dist-tags` reads through a CDN with multi-second to multi-minute eventual consistency. "Still shows `<bad>` on `latest`" is not an authoritative "no users yet" signal. For freshness, cross-check with `npm view bmad-module-skill-forge@<bad> time --json | jq '. | to_entries | last'`.
|
|
246
|
-
|
|
247
|
-
- **CLI.**
|
|
248
|
-
|
|
249
|
-
```bash
|
|
250
|
-
# Flip latest back to the last known-good version.
|
|
251
|
-
npm dist-tag add bmad-module-skill-forge@<previous_good> latest
|
|
252
|
-
|
|
253
|
-
# Verify the flip landed.
|
|
254
|
-
npm dist-tag ls bmad-module-skill-forge
|
|
255
|
-
|
|
256
|
-
# Warn anyone who did install <bad> in the meantime.
|
|
257
|
-
npm deprecate bmad-module-skill-forge@<bad> "Pulled - use <previous_good> instead"
|
|
258
|
-
|
|
259
|
-
# Cut the fix forward via the canonical workflow (OIDC publish, SLSA-L2 provenance).
|
|
260
|
-
gh workflow run release.yaml -f version_bump=patch
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
- **Expected outcome.** `latest` now resolves to `<previous_good>`; `npm install bmad-module-skill-forge@<bad>` emits a deprecation warning; the subsequent patch cut advances `latest` to `<next_version>` cleanly.
|
|
264
|
-
- **Constraints.** Valid only while the blast radius is small. If any downstream has already pinned to `<bad>`, Scenario B applies instead — reverting `latest` won't reach those installs.
|
|
265
|
-
- **Verification.**
|
|
266
|
-
|
|
267
|
-
```bash
|
|
268
|
-
npm view bmad-module-skill-forge dist-tags --json
|
|
269
|
-
# expected: {"latest":"<previous_good>", ...}
|
|
270
|
-
# Other dist-tags (e.g. "alpha") may also appear depending on prior releases.
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
### Scenario B — "Bad version live for hours/days, users may be installing it"
|
|
274
|
-
|
|
275
|
-
- **Trigger.** Defect discovered after `<bad>` has held `latest` long enough that download count is non-zero, or dependents may have already pinned.
|
|
276
|
-
- **CLI.**
|
|
277
|
-
|
|
278
|
-
```bash
|
|
279
|
-
# Warn immediately — every install of <bad> now prints this string.
|
|
280
|
-
npm deprecate bmad-module-skill-forge@<bad> "Critical bug - use <next_version> instead"
|
|
281
|
-
|
|
282
|
-
# Cut the fix forward. release.yaml's dist-tag case-chain auto-updates `latest`
|
|
283
|
-
# for non-prerelease versions, so no manual `dist-tag add` is needed here.
|
|
284
|
-
gh workflow run release.yaml -f version_bump=patch
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
- **Expected outcome.** `npm install bmad-module-skill-forge` starts emitting the deprecation warning; the new patch publishes cleanly; `latest` advances to `<next_version>`.
|
|
288
|
-
- **Constraints.** **Do NOT** `npm dist-tag add @<previous_good> latest` in this scenario. For post-v1.0.0 releases that is an NFR6 violation (it re-exposes a deprecated version as `latest`); for pre-v1.0.0 it fragments user expectation and hides the fix. Ship forward via patch bump.
|
|
289
|
-
- **Verification.**
|
|
290
|
-
|
|
291
|
-
```bash
|
|
292
|
-
# Confirm the deprecation message landed on the registry (no install required —
|
|
293
|
-
# `npm install` would mutate the cwd's package.json/package-lock.json).
|
|
294
|
-
npm view bmad-module-skill-forge@<bad> deprecated
|
|
295
|
-
# expected: the exact deprecation string set above ("Critical bug - use <next_version> instead")
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
NFR3 (rollback frequency <1/quarter post-v1.0.0) is evaluated against the frequency of Scenario B invocations — each trip through Scenario B is an NFR3 data point.
|
|
299
|
-
|
|
300
|
-
### Scenario C — "Bad version within last 72h, zero downloads, zero dependents"
|
|
301
|
-
|
|
302
|
-
- **Trigger.** A narrow eligibility window per the [npm unpublish policy](https://docs.npmjs.com/policies/unpublish/): less than 72 hours since publish **AND** zero downloads **AND** zero dependent packages registered on npmjs.com. All three conditions must be true. Compute the 72-hour boundary from the `time.modified` field of `npm view bmad-module-skill-forge@<bad> --json`, NOT from the responder's local clock — npm's policy engine uses its server timestamp and a drift of even a few minutes at the boundary will 422 the unpublish.
|
|
303
|
-
|
|
304
|
-
> **Caveat on the "zero downloads" criterion.** npm's downloads API is documented as eventually consistent with up to 24–48h lag post-publish. A version can show "0 downloads" while having been installed by hundreds of CI runs in that window. When in doubt in the first 24–48 hours, treat C as Scenario B.
|
|
305
|
-
|
|
306
|
-
- **CLI.**
|
|
307
|
-
|
|
308
|
-
```bash
|
|
309
|
-
npm unpublish bmad-module-skill-forge@<bad>
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
- **Expected outcome.** The version disappears from `npm view`. **But** the version _number_ is permanently burned — npm rejects any future publish of that same version string forever.
|
|
313
|
-
|
|
314
|
-
> **Permanent-burn warning.** The version number is burned. After `npm unpublish bmad-module-skill-forge@0.10.1`, the next `release.yaml` dispatch with `version_bump: patch` on `0.10.0` will produce `0.10.2`, **not** `0.10.1` again. Attempting `npm version 0.10.1 --no-git-tag-version` would drive the workflow's `npm publish` toward an npm-burned version and return `403`. Treat the unpublish as a one-way jump in the version number line, not a true undo.
|
|
315
|
-
|
|
316
|
-
- **Constraints.** **This scenario does NOT apply to v1.0.0.** Per NFR6, v1.0.0 is never unpublished regardless of eligibility window — use Scenario B instead. Scenario C is a pre-v1.0.0-only path.
|
|
317
|
-
- **Verification.**
|
|
318
|
-
|
|
319
|
-
```bash
|
|
320
|
-
npm view bmad-module-skill-forge@<bad> 2>&1
|
|
321
|
-
# expected: npm error 404 No match found for version <bad>
|
|
322
|
-
```
|
|
323
|
-
|
|
324
|
-
### Scenario D — "Tag exists but npm publish failed"
|
|
325
|
-
|
|
326
|
-
- **Trigger.** `release.yaml` pushed the `v*` tag (tag-creation step runs before the npm publish step) and then the publish step failed — OIDC 404, tarball validation error, network drop. `npm view bmad-module-skill-forge@<version>` returns 404; `git tag -l v<version>` shows the tag locally and on origin.
|
|
327
|
-
- **CLI.**
|
|
328
|
-
|
|
329
|
-
```bash
|
|
330
|
-
# VERIFY FIRST that npm did NOT publish — a tag-delete after a successful
|
|
331
|
-
# publish would orphan the npm artifact. If this returns a manifest, STOP
|
|
332
|
-
# and go to Scenario E.
|
|
333
|
-
npm view bmad-module-skill-forge@<version> 2>&1
|
|
334
|
-
|
|
335
|
-
# Tag-only recovery: clear the tag locally and on origin, then re-dispatch.
|
|
336
|
-
git tag -d v<version>
|
|
337
|
-
git push --delete origin v<version>
|
|
338
|
-
|
|
339
|
-
# Re-dispatch with the same bump input — release.yaml will produce a fresh
|
|
340
|
-
# clean tag and successful publish.
|
|
341
|
-
gh workflow run release.yaml -f version_bump=<same-input-as-before>
|
|
342
|
-
```
|
|
343
|
-
|
|
344
|
-
- **Expected outcome.** Tag cleared from origin; re-run produces a fresh clean tag plus a successful publish under the next version number.
|
|
345
|
-
- **Constraints.** Only safe if publish failed. If npm _did_ publish, use Scenario E — tag-deletion after a successful publish leaves the npm artifact without a matching git ref.
|
|
346
|
-
- **Do-NOT clause.** Never re-use the burned `<version>` number. `release.yaml` will produce the next version on redispatch (for example, re-running an `alpha` bump over `0.10.1-alpha.0` produces `0.10.1-alpha.1`, not `0.10.1-alpha.0` again). Forcing the original version back via `npm version <exact> --no-git-tag-version` is out of scope for rollback.
|
|
347
|
-
- **Story 3.2 load-bearing context.** The current `release.yaml` pushes the git tag **before** the npm publish step (see the `Create and push tag` job step vs. the `Publish to npm via OIDC trusted publishing` step). This ordering is pre-existing from Story 3.1 and tracked in `_bmad-output/implementation-artifacts/deferred-work.md` under `§ 3-1/3-2 code review "Create and push tag pushes the git tag BEFORE Publish to npm"` as a post-v1.0.0 hardening candidate. Until that lands, Scenario D's tag-delete path **is** the recovery.
|
|
348
|
-
- **Verification.**
|
|
349
|
-
|
|
350
|
-
```bash
|
|
351
|
-
# Tag gone on origin.
|
|
352
|
-
gh api repos/armelhbobdad/bmad-module-skill-forge/git/refs/tags/v<version> 2>&1
|
|
353
|
-
# expected: 404 Not Found
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
### Scenario E — "npm publish succeeded but GitHub Release / tag push failed"
|
|
357
|
-
|
|
358
|
-
- **Trigger.** Publish succeeded (`npm view bmad-module-skill-forge@<version>` returns a manifest) but **one of**: tag-push to origin failed (network drop, branch-protection edge case); the `Create GitHub Release` softprops step failed (API rate limit, auth expiry); the `Push commit to main` step was (correctly) skipped but the release artifact diverged from `main`.
|
|
359
|
-
- **Premise inversion note.** In the research doc's scenario ordering, publish was assumed to happen before tag push — so publish failure was the likely branch. In the live `release.yaml` the ordering is reversed: the tag push runs **before** the npm publish. That means a publish-succeeded-but-tag-missing window is small, but real — the `Create GitHub Release` and `Push commit to main` steps still run after publish and either can fail independently. This scenario covers the post-publish-tag-or-release-missing recovery path.
|
|
360
|
-
- **CLI — tag recovery.**
|
|
361
|
-
|
|
362
|
-
```bash
|
|
363
|
-
# Obtain the commit-sha the workflow used as its HEAD during the publish run
|
|
364
|
-
# (returns the SHA directly; do not grep the run log — the bump-commit echo
|
|
365
|
-
# line does not contain the SHA, only the post-commit confirmation does).
|
|
366
|
-
COMMIT_SHA=$(gh api \
|
|
367
|
-
repos/armelhbobdad/bmad-module-skill-forge/actions/runs/<run-id> \
|
|
368
|
-
--jq .head_sha)
|
|
369
|
-
|
|
370
|
-
# Recreate the annotated tag and push.
|
|
371
|
-
git tag -a v<version> "$COMMIT_SHA" -m "Release v<version>"
|
|
372
|
-
git push origin v<version>
|
|
373
|
-
```
|
|
374
|
-
|
|
375
|
-
- **CLI — GitHub Release recovery.** The workflow's `Generate release notes`
|
|
376
|
-
step writes to `release_notes.md` inside the runner; that file is **not**
|
|
377
|
-
uploaded as an artifact, so it does not exist outside the run. Either scrape
|
|
378
|
-
the body from the run log first, or use `--generate-notes` to let GitHub
|
|
379
|
-
regenerate from commit subjects.
|
|
380
|
-
|
|
381
|
-
```bash
|
|
382
|
-
# Option A: scrape the notes body from the run log into a local file.
|
|
383
|
-
gh run view <run-id> --log \
|
|
384
|
-
| sed -n '/^## Generate release notes/,/^##[^#]/p' \
|
|
385
|
-
> release_notes.md
|
|
386
|
-
gh release create v<version> \
|
|
387
|
-
--notes-file release_notes.md \
|
|
388
|
-
--title "Skill Forge (SKF) v<version>"
|
|
389
|
-
|
|
390
|
-
# Option B: let GitHub regenerate from commit subjects (loses the curated
|
|
391
|
-
# changelog body but always works).
|
|
392
|
-
gh release create v<version> \
|
|
393
|
-
--generate-notes \
|
|
394
|
-
--title "Skill Forge (SKF) v<version>"
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
- **Expected outcome.** Tag landed on origin pointing at the correct commit; GitHub Release page reflects the published npm artifact; npm + GitHub + git state are now consistent.
|
|
398
|
-
- **Constraints.** **npm state is immutable.** Do NOT try to "clean up" the npm artifact so the release can be re-run from scratch. The npm artifact plus the orphaned post-recovery git state **is** the canonical record — NFR5 (audit-trail completeness) is satisfied by the successful npm publish plus the recovered tag and GitHub Release, not by a clean rerun.
|
|
399
|
-
- **Orphaned-commit caveat (Story 3.2 context).** If the run was dispatched from a feature branch and `Push commit to main` was correctly skipped, the `release: bump to v<version>` commit lives only in the workflow run log until the recovered tag above anchors it. This is NFR12-compliant and expected pre-v1.0.0 behavior; the tag is the authoritative pointer. This is exactly the pattern observed in the Story 3.2 alpha cut (`bmad-module-skill-forge@0.10.1-alpha.0`, run `24714953668`, tag `v0.10.1-alpha.0`, orphaned commit `2a57dcbd`).
|
|
400
|
-
- **Verification.**
|
|
401
|
-
|
|
402
|
-
```bash
|
|
403
|
-
# Tag resolves.
|
|
404
|
-
gh api repos/armelhbobdad/bmad-module-skill-forge/git/refs/tags/v<version>
|
|
405
|
-
|
|
406
|
-
# GitHub Release exists.
|
|
407
|
-
gh release view v<version>
|
|
408
|
-
|
|
409
|
-
# npm + GitHub provenance still match.
|
|
410
|
-
npm view bmad-module-skill-forge@<version> --json | jq '.dist.attestations'
|
|
411
|
-
```
|
|
412
|
-
|
|
413
|
-
### Scenario F — "Suspected OIDC compromise / unauthorized publish"
|
|
414
|
-
|
|
415
|
-
- **Trigger (any of).**
|
|
416
|
-
- `npm view bmad-module-skill-forge time` shows a publish with a timestamp no maintainer triggered.
|
|
417
|
-
- `npm audit signatures` on a published tarball reports attestation verification failure.
|
|
418
|
-
- A GitHub Actions run shows reviewer approval that was not given.
|
|
419
|
-
- npm support notifies of suspected compromise.
|
|
420
|
-
- **Immediate CLI (within minutes of detection).** Revoke the OIDC trust surface before doing anything else:
|
|
421
|
-
- Visit `https://www.npmjs.com/package/bmad-module-skill-forge` → **Settings** tab → **Trusted Publisher** section → **Delete** the registration.
|
|
422
|
-
- This blocks _all_ future OIDC publishes until re-registered, stopping any further unauthorized use of the OIDC path mid-incident.
|
|
423
|
-
- **Audit CLI.**
|
|
424
|
-
|
|
425
|
-
```bash
|
|
426
|
-
# Full publish timeline.
|
|
427
|
-
npm view bmad-module-skill-forge time
|
|
428
|
-
|
|
429
|
-
# Per-version provenance shape (script-friendly).
|
|
430
|
-
npm view bmad-module-skill-forge@<suspect> --json \
|
|
431
|
-
| jq '.dist.attestations'
|
|
432
|
-
# expected shape:
|
|
433
|
-
# {
|
|
434
|
-
# "url": "https://registry.npmjs.org/-/npm/v1/attestations/bmad-module-skill-forge@<suspect>",
|
|
435
|
-
# "provenance": { "predicateType": "https://slsa.dev/provenance/v1" }
|
|
436
|
-
# }
|
|
437
|
-
|
|
438
|
-
# Human-friendly signature-chain check. `npm audit signatures` operates on
|
|
439
|
-
# the cwd's installed dependency tree — running it in an empty dir returns
|
|
440
|
-
# "0 packages have verified attestations", which is a false-clean signal
|
|
441
|
-
# mid-incident. Wrap the install in a throwaway dir, ignore lifecycle
|
|
442
|
-
# scripts (the suspect tarball may be hostile), then audit.
|
|
443
|
-
mkdir -p /tmp/skf-incident-verify && cd /tmp/skf-incident-verify
|
|
444
|
-
npm init -y >/dev/null
|
|
445
|
-
npm install --ignore-scripts bmad-module-skill-forge@<suspect>
|
|
446
|
-
npm audit signatures
|
|
447
|
-
# expected: "X packages have verified attestations" with attestation count >= 1
|
|
448
|
-
```
|
|
449
|
-
|
|
450
|
-
If `dist.attestations` is missing or the `url` field points somewhere unexpected, the publish bypassed the OIDC path.
|
|
451
|
-
|
|
452
|
-
- **Coordination CLI.** If a malicious version was actually published, contact `support@npmjs.com` with the version string, the attestation URL, and the Trusted Publisher registration timestamp; request a manual takedown. npm support can unpublish outside the 72-hour window for security-compromise cases.
|
|
453
|
-
- **Lockdown CLI.**
|
|
454
|
-
|
|
455
|
-
```bash
|
|
456
|
-
# Confirm no unexpected secrets were added to the repo.
|
|
457
|
-
gh api repos/armelhbobdad/bmad-module-skill-forge/actions/secrets
|
|
458
|
-
|
|
459
|
-
# Confirm the write-access list is unchanged.
|
|
460
|
-
gh api repos/armelhbobdad/bmad-module-skill-forge/collaborators
|
|
461
|
-
|
|
462
|
-
# Review all workflows for any pull_request trigger type that could run
|
|
463
|
-
# attacker-controlled code with repo secrets (e.g. `types: [opened]`
|
|
464
|
-
# without a trusted-author gate).
|
|
465
|
-
grep -rn "pull_request:" .github/workflows/
|
|
466
|
-
```
|
|
467
|
-
|
|
468
|
-
- **Post-incident reactivation.** After audit completes and root cause is identified and patched, re-register the Trusted Publisher via the npm UI with the four fields matching the table in `## npm Trusted Publisher` above (`organization=armelhbobdad`, `repository=bmad-module-skill-forge`, `workflow filename=release.yaml`, `environment=release`). Re-verify via an alpha cut (dispatch `release.yaml` from a temporarily-allowed feature branch per the `## Release Environment § Temporarily allowing a feature branch` procedure) before any stable release.
|
|
469
|
-
- **Pre-v1.0.0 context.** Trusted Publisher was pre-registered on 2026-04-20 (Story 1.3). A compromise discovered pre-v1.0.0 is recoverable via delete-and-re-register with no downstream-consumer blast radius (no stable release users yet). Post-v1.0.0, the incident has downstream blast radius and the `support@npmjs.com` coordination path is load-bearing.
|
|
470
|
-
- **Do-NOT clause — `NPM_TOKEN` rotation is not OIDC incident response.** Do NOT rotate `NPM_TOKEN` as a first response. The token is not on the OIDC path; rotating it does nothing to stop an OIDC compromise.
|
|
471
|
-
- If an `NPM_TOKEN` exists at repo scope at the time of an incident (exception-path per § npm Trusted Publisher), revoke it at `https://www.npmjs.com` → **Access Tokens** AND remove it from the repo: `gh secret delete NPM_TOKEN --repo armelhbobdad/bmad-module-skill-forge`. A compromised `NPM_TOKEN` would be a **separate incident class** from OIDC compromise. **Expected outcomes of the delete command under incident pressure** — read the response before escalating: (a) `Secret deleted` = success, token revoked at repo-scope; (b) `could not find secret NPM_TOKEN` (exit 1, 404) = no-op SAFE, the token is already absent per the post-Story-6.3 default and this is the expected normal-operation state, not a broken scope or auth issue; (c) any `401` / `403` / scope-permission error = investigate before retrying, likely a `gh auth` or org-permissions issue unrelated to the token's presence. Story 6.3 removed the token in 2026-04 post-v1.0.0 so outcome (b) is the default; outcome (a) only applies during the exception-path window where a temporary token re-add has already happened. During a window where OIDC is revoked AND an exception-path token is also compromised, there is no valid publish path — the repo enters lockdown until Trusted Publisher is re-registered. Document the flip in the incident post-mortem; do not publish via any stale path.
|
|
472
|
-
|
|
473
|
-
### Scenario G — "release.yaml disabled, reverted, or missing from main"
|
|
474
|
-
|
|
475
|
-
- **Trigger.** `release.yaml` is the single-rooted release workflow (Story 3.3 Patch A neutralized the legacy `publish.yaml` `v*` tag trigger). If `release.yaml` is reverted, renamed, moved, or disabled at the repo-settings level (`gh workflow disable release.yaml`) and someone tries to cut a release, **no workflow fires**. Silent no-op until a maintainer checks `npm view` or the Actions UI.
|
|
476
|
-
- **Detection.** After any PR that touches `.github/workflows/*` — and before any release attempt — run:
|
|
477
|
-
|
|
478
|
-
```bash
|
|
479
|
-
gh api repos/armelhbobdad/bmad-module-skill-forge/actions/workflows \
|
|
480
|
-
--jq '.workflows[] | select(.name=="Release") | {name, state, path}'
|
|
481
|
-
# expected: {"name":"Release","state":"active","path":".github/workflows/release.yaml"}
|
|
482
|
-
```
|
|
483
|
-
|
|
484
|
-
If `state` is not `active` (the GitHub Actions API reports `disabled_manually` when disabled via the UI or `gh workflow disable`), or no matching workflow is returned at all (file renamed, moved, or deleted), Scenario G applies.
|
|
485
|
-
|
|
486
|
-
- **Recovery CLI.**
|
|
487
|
-
|
|
488
|
-
```bash
|
|
489
|
-
# Case 1: workflow is disabled but the file is present.
|
|
490
|
-
gh workflow enable release.yaml
|
|
491
|
-
|
|
492
|
-
# Case 2: the file is missing or reverted.
|
|
493
|
-
# Revert the offending PR via the normal branch-protection review path.
|
|
494
|
-
# Do NOT hand-edit main — branch protection blocks direct push anyway.
|
|
495
|
-
gh pr view <offending-pr-number>
|
|
496
|
-
# `gh pr revert` opens a NEW revert PR; it does not merge anything itself.
|
|
497
|
-
# Capture the URL it prints, then approve + merge that revert PR through
|
|
498
|
-
# the standard review path before retrying the release.
|
|
499
|
-
gh pr revert <offending-pr-number>
|
|
500
|
-
# After approval (manual review step required by branch protection):
|
|
501
|
-
gh pr merge <revert-pr-number> --squash
|
|
502
|
-
```
|
|
503
|
-
|
|
504
|
-
- **Expected outcome.** `release.yaml` is back on `main` with `state: "active"`; next dispatch fires normally.
|
|
505
|
-
- **Prevention.** Single-root invariants to spot-check after any workflow-dir PR. The globs below cover both `.yaml` and `.yml` extensions (the repo currently has at least one `.yml` workflow):
|
|
506
|
-
|
|
507
|
-
```bash
|
|
508
|
-
# Any workflow with id-token: write should be a known release/provenance workflow.
|
|
509
|
-
grep -l 'id-token: write' .github/workflows/*.{yaml,yml} 2>/dev/null
|
|
510
|
-
# expected set:
|
|
511
|
-
# - docs.yaml (GitHub Pages — orthogonal to release)
|
|
512
|
-
# - release.yaml (canonical)
|
|
513
|
-
|
|
514
|
-
# No `v*` push trigger should exist in any workflow — release.yaml is workflow_dispatch-only.
|
|
515
|
-
# Scan the 3 lines following each `push:` block for a v* tag pattern.
|
|
516
|
-
grep -A3 -E '^\s*push:' .github/workflows/*.{yaml,yml} 2>/dev/null \
|
|
517
|
-
| grep -E "['\"]v\*['\"]"
|
|
518
|
-
# expected: zero matches.
|
|
519
|
-
```
|
|
520
|
-
|
|
521
|
-
- **Escalation path — PR-revert only.** There is no out-of-band emergency hatch. Recovery flows exclusively through the Case 2 PR-revert path above: revert the offending workflow change via `gh pr revert`, merge through branch protection, then retry the release via `release.yaml`. Every release threads through branch protection + code review + OIDC Trusted Publisher, matching NFR2 and NFR3.
|
|
522
|
-
|
|
523
|
-
- **Constraints.** Branch protection on `main` blocks direct pushes — recovery goes through a PR in every case. Do not attempt to sidestep branch protection to "fix" `release.yaml` faster; the cost of a bad release (NFR5 audit-trail breakage, NFR10 commit-trail breakage) far exceeds the cost of a normal-review PR.
|
|
524
|
-
|
|
525
|
-
### Cross-reference matrix
|
|
526
|
-
|
|
527
|
-
| Scenario | Trigger class | Primary CLI verb | NFR linkage |
|
|
528
|
-
| -------- | ------------------------------- | -------------------------------- | ----------------- |
|
|
529
|
-
| A | Fresh bad latest | `dist-tag` + `deprecate` | NFR3 |
|
|
530
|
-
| B | Stale bad latest | `deprecate` + ship forward | NFR3, NFR6 |
|
|
531
|
-
| C | Eligible unpublish | `unpublish` | (pre-v1.0.0 only) |
|
|
532
|
-
| D | Tag orphan (publish failed) | `tag -d` + `push --delete` | NFR12 |
|
|
533
|
-
| E | Post-publish state drift | `tag -a` + `gh release create` | NFR5 |
|
|
534
|
-
| F | OIDC compromise | revoke Trusted Publisher + audit | NFR7 |
|
|
535
|
-
| G | `release.yaml` disabled/missing | `workflow enable` / `pr revert` | NFR5, NFR10 |
|
|
536
|
-
|
|
537
|
-
### Baseline snapshots
|
|
538
|
-
|
|
539
|
-
The ruleset-restore snippet at the top of this document (`## Branch Protection on main § Restore from a saved baseline`) uses `gh api --method PUT .../rulesets/<id>`. That call returns `404` if the `Default` ruleset has been deleted, not merely edited. Keeping a recent `baseline-ruleset-Default-YYYYMMDD.json` on disk lets the PUT-then-POST fallback below recover from either case.
|
|
540
|
-
|
|
541
|
-
**One-time capture pattern** — run after any maintainer-initiated change to the ruleset or to the `release` environment, **not on a schedule**:
|
|
542
|
-
|
|
543
|
-
```bash
|
|
544
|
-
# Capture the current Default ruleset as a disaster-recovery baseline.
|
|
545
|
-
# Uses name-based lookup so the snippet survives ruleset re-creation (IDs change, names don't).
|
|
546
|
-
RULESET_ID=$(gh api repos/armelhbobdad/bmad-module-skill-forge/rulesets \
|
|
547
|
-
--jq '.[] | select(.name=="Default") | .id')
|
|
548
|
-
gh api "repos/armelhbobdad/bmad-module-skill-forge/rulesets/$RULESET_ID" \
|
|
549
|
-
> "_bmad-output/planning-artifacts/baseline-ruleset-Default-$(date +%Y%m%d).json"
|
|
550
|
-
|
|
551
|
-
# Capture the release environment + its branch policy list (two separate resources).
|
|
552
|
-
gh api repos/armelhbobdad/bmad-module-skill-forge/environments/release \
|
|
553
|
-
> "_bmad-output/planning-artifacts/baseline-env-release-$(date +%Y%m%d).json"
|
|
554
|
-
gh api repos/armelhbobdad/bmad-module-skill-forge/environments/release/deployment-branch-policies \
|
|
555
|
-
> "_bmad-output/planning-artifacts/baseline-env-release-branch-policies-$(date +%Y%m%d).json"
|
|
556
|
-
```
|
|
557
|
-
|
|
558
|
-
Baselines live in `_bmad-output/planning-artifacts/` (already git-tracked, not shipped with the npm package). The filename date stamp makes the freshness of the baseline obvious at a glance.
|
|
559
|
-
|
|
560
|
-
**Ruleset-deletion recovery path — PUT-then-POST.** The canonical restore flow is:
|
|
561
|
-
|
|
562
|
-
1. Try `PUT` first. If the ruleset exists but drifted, this reconciles it in place.
|
|
563
|
-
|
|
564
|
-
```bash
|
|
565
|
-
RULESET_ID=$(gh api repos/armelhbobdad/bmad-module-skill-forge/rulesets \
|
|
566
|
-
--jq '.[] | select(.name=="Default") | .id')
|
|
567
|
-
# Pick the most recent baseline file (the YYYYMMDD suffix is a real date
|
|
568
|
-
# written by the capture command above; the glob avoids hard-coding it).
|
|
569
|
-
BASELINE=$(ls -t _bmad-output/planning-artifacts/baseline-ruleset-Default-*.json \
|
|
570
|
-
| head -1)
|
|
571
|
-
jq '{name, target, enforcement, conditions, rules, bypass_actors}' "$BASELINE" \
|
|
572
|
-
> /tmp/restore.json
|
|
573
|
-
gh api --method PUT \
|
|
574
|
-
"repos/armelhbobdad/bmad-module-skill-forge/rulesets/$RULESET_ID" \
|
|
575
|
-
--input /tmp/restore.json
|
|
576
|
-
```
|
|
577
|
-
|
|
578
|
-
2. If the `PUT` returns `404` (ruleset was deleted, not edited), recreate via `POST`:
|
|
579
|
-
|
|
580
|
-
```bash
|
|
581
|
-
gh api --method POST \
|
|
582
|
-
repos/armelhbobdad/bmad-module-skill-forge/rulesets \
|
|
583
|
-
--input /tmp/restore.json
|
|
584
|
-
```
|
|
585
|
-
|
|
586
|
-
The `POST` body needs the same `name`, `target`, `enforcement`, `conditions`, `rules`, and `bypass_actors` fields as the PUT — all captured by the baseline snapshot above. Try `PUT` first; on `404`, `POST` recreates with identical semantics. The ruleset gets a new id on re-creation (IDs are not stable across delete+create cycles); update any hard-coded id references in this document on the next planned edit pass.
|
|
587
|
-
|
|
588
|
-
For the `release` environment, a deletion+restore similarly uses the two-call pattern already documented at `## Release Environment § Restore / re-apply` — feed the environment-level baseline JSON to the `PUT .../environments/release` call, then re-POST each entry from the branch-policies baseline to `.../environments/release/deployment-branch-policies`.
|
|
589
|
-
|
|
590
|
-
### Cutting v1.0.0 under --tag latest
|
|
591
|
-
|
|
592
|
-
- **Trigger.** The passing RC has survived a clean-environment smoke test (`release-audits/v1.0.0-launch-audit.md § Story 5.2 RC Cut + Smoke Test § Decision: PASS — cleared for Story 5.3 promotion to v1.0.0 under --tag latest with manual approval`); `npm view bmad-module-skill-forge@rc version` returns the passing RC (at Story 5.3 dispatch time: `1.0.0-rc.3`); Story 5.3 is ready to promote to `latest` under manual approval.
|
|
593
|
-
- **CLI.**
|
|
594
|
-
|
|
595
|
-
```bash
|
|
596
|
-
git checkout main && git pull
|
|
597
|
-
|
|
598
|
-
# Pre-dispatch sanity check (read version from remote, not local clone).
|
|
599
|
-
gh api repos/armelhbobdad/bmad-module-skill-forge/contents/package.json \
|
|
600
|
-
--jq '.content' | base64 -d | jq -r .version
|
|
601
|
-
# expected: the passing RC (e.g., 1.0.0-rc.3)
|
|
602
|
-
|
|
603
|
-
# Dispatch the canonical release workflow. `version_bump: major` on
|
|
604
|
-
# 1.0.0-rc.N strips the prerelease suffix and produces 1.0.0 (node-semver
|
|
605
|
-
# inc(ver, 'major') behavior on a prerelease). `--ref main` is explicit
|
|
606
|
-
# (gh defaults to the repo's default branch, but any non-main ref takes
|
|
607
|
-
# the legacy tag-only path inside release.yaml, so pass it defensively).
|
|
608
|
-
gh workflow run release.yaml -f version_bump=major --ref main
|
|
609
|
-
|
|
610
|
-
# Capture the run id for monitoring + audit transcript.
|
|
611
|
-
RUN_ID=$(gh run list --workflow=release.yaml --limit 1 \
|
|
612
|
-
--json databaseId --jq '.[0].databaseId')
|
|
613
|
-
gh run watch $RUN_ID
|
|
614
|
-
```
|
|
615
|
-
|
|
616
|
-
The workflow pauses at **two** gates that the maintainer must clear in the browser. Gate 1 requires explicit approval; gate 2 accepts either approval or admin-bypass-merge:
|
|
617
|
-
|
|
618
|
-
1. The `release` environment deployment gate (at job start). Approve via "Review deployments" → "Approve and deploy" on the run page.
|
|
619
|
-
2. The bot PR review-decision gate (after the 8 required status checks pass). EITHER approve the bot PR via the review UI, OR admin-bypass-merge via the PR merge button — both paths are accepted by `release.yaml`'s `Wait for PR approval or admin-bypass merge` step. Admin-bypass-merge is the observed pattern for prior cuts (PRs #209 and #213).
|
|
620
|
-
|
|
621
|
-
Expected wall-clock: ~5–8 minutes end-to-end when both gates are approved promptly.
|
|
622
|
-
|
|
623
|
-
- **Expected outcome.** `main` tip advances by 2 commits (the `release: bump to v1.0.0` commit on the bot temp branch `release/bot/v1.0.0-<run_id>`, plus the merge commit from the auto-merged or admin-bypass-merged bot PR); `jq -r .version package.json` → `1.0.0`; `jq -r '.plugins[0].version' .claude-plugin/marketplace.json` → `1.0.0`; `npm view bmad-module-skill-forge dist-tags.latest` → `1.0.0` (flipped from the prior stable, e.g. `0.10.0`); `rc` dist-tag UNCHANGED at the prior RC; SLSA L2 provenance attached (NFR4); GitHub Release `v1.0.0` with `prerelease: false` (NFR6 threshold — `1.0.0` contains no `alpha|beta|rc` substring); tag `v1.0.0` anchors on the bot PR's merge commit per Story 3.4 P9 (the tag is annotated, so `git rev-parse 'v1.0.0^{}'` gives the commit SHA to compare against `main` tip).
|
|
624
|
-
|
|
625
|
-
- **Constraints.**
|
|
626
|
-
- Execute ONLY after the RC audit `§ Sign-off` is populated AND the smoke-test `Decision` is `PASS`.
|
|
627
|
-
- The cut is IRREVERSIBLE — once `npm publish --tag latest` succeeds for `1.0.0`, NFR6 forbids unpublish regardless of eligibility window. Rollback is `npm deprecate` + ship-forward (Scenarios A / B).
|
|
628
|
-
- The CHANGELOG reconciliation is a POST-workflow step (authored as a separate sign-off commit on `feat/v1-final-signoff`). Do NOT hand-edit the `## [1.0.0] - TBD` placeholder BEFORE dispatch — let the workflow auto-generate its `## [1.0.0]` block (which may be EMPTY when there are zero conventional-commit `feat:` / `fix:` entries between the RC and the final cut, which is the normal case), then merge the hand-curated prose into the auto-gen header's position and delete the stale placeholder in one commit.
|
|
629
|
-
- Do NOT pre-bump `package.json` or `.claude-plugin/marketplace.json` — the workflow's `Bump version` and `Update marketplace.json version` steps handle both atomically and own those files.
|
|
630
|
-
- If `npm version major` unexpectedly emits `2.0.0` instead of `1.0.0` on `1.0.0-rc.N`, the node-semver engine behavior has regressed. Abort the dispatch and investigate before retry — a workflow override (`npm version 1.0.0 --no-git-tag-version`) would be required in `release.yaml`.
|
|
631
|
-
|
|
632
|
-
- **Verification.**
|
|
633
|
-
|
|
634
|
-
```bash
|
|
635
|
-
# Confirm the latest dist-tag flipped to 1.0.0.
|
|
636
|
-
npm view bmad-module-skill-forge dist-tags --json
|
|
637
|
-
# expected: {"latest":"1.0.0","alpha":"...","rc":"1.0.0-rc.N"}
|
|
638
|
-
|
|
639
|
-
npm view bmad-module-skill-forge@latest version
|
|
640
|
-
# expected: 1.0.0
|
|
641
|
-
|
|
642
|
-
# Confirm SLSA L2 provenance (NFR4 CRITICAL). A null attestation is a
|
|
643
|
-
# blocker — emergency-deprecate + cut 1.0.1 with provenance per Scenario B.
|
|
644
|
-
npm view bmad-module-skill-forge@1.0.0 --json | jq '.dist.attestations'
|
|
645
|
-
# expected: non-null object; .provenance.predicateType starts with
|
|
646
|
-
# "https://slsa.dev/provenance/"
|
|
647
|
-
|
|
648
|
-
# Confirm the GitHub Release exists and is NOT a prerelease (NFR6 threshold).
|
|
649
|
-
gh release view v1.0.0 --json tagName,isPrerelease
|
|
650
|
-
# expected: {"tagName":"v1.0.0","isPrerelease":false}
|
|
651
|
-
|
|
652
|
-
# Confirm the v1.0.0 tag is reachable from main (annotated tag — dereference with ^{}).
|
|
653
|
-
# Uses --is-ancestor so the check stays valid after subsequent commits land on main;
|
|
654
|
-
# pin to the recorded merge SHA if you need strict tag-anchor equivalence.
|
|
655
|
-
git fetch origin --tags
|
|
656
|
-
git merge-base --is-ancestor "$(git rev-parse 'v1.0.0^{}')" origin/main && echo OK
|
|
657
|
-
# expected: OK
|
|
658
|
-
|
|
659
|
-
# Confirm CHANGELOG reconciliation after the sign-off commit lands on main.
|
|
660
|
-
grep -c '^## \[1\.0\.0\] - TBD' CHANGELOG.md # expected: 0 (stale placeholder gone)
|
|
661
|
-
grep -cE '^## \[1\.0\.0\]' CHANGELOG.md # expected: 1 (one reconciled block)
|
|
662
|
-
grep -cE '^## \[1\.0\.0\]\(https' CHANGELOG.md # expected: 1 (compare-URL header survived)
|
|
663
|
-
```
|
|
664
|
-
|
|
665
|
-
**NFR6 immutability activation** is the `npm publish --tag latest` success timestamp for `1.0.0`, recorded verbatim in `release-audits/v1.0.0-launch-audit.md § Story 5.3 v1.0.0 Final Cut § NFR6 v1.0.0 immutability activation`. For Story 5.3's dispatch, that instant was **2026-04-23T18:56:39Z**. From that moment, `v1.0.0` is forever-burned — `npm deprecate` + ship-forward is the only rollback path.
|
|
666
|
-
|
|
667
|
-
The `release` environment + bot PR approval-or-admin-bypass-merge pattern is the canonical flow for all main-dispatched cuts since Story 3.4 ([GitHub issue #198](https://github.com/armelhbobdad/bmad-module-skill-forge/issues/198), [PR #199](https://github.com/armelhbobdad/bmad-module-skill-forge/pull/199)): the release commit is pushed to a temp branch `release/bot/vX.Y.Z-<run_id>`, a bot PR is opened against `main`, the 7 required status checks are force-triggered against the temp branch via `workflow_dispatch`, and the merge is gated behind maintainer approval at both the `release` environment gate and the PR review-decision gate. Non-main dispatches (feature-branch alpha cuts) skip the PR dance entirely and keep the legacy tag-only behavior.
|
|
668
|
-
|
|
669
|
-
#### Post-publish verification (NFR9)
|
|
670
|
-
|
|
671
|
-
Cross-platform install verification for any cut is performed by the [`install-smoke.yaml`](/.github/workflows/install-smoke.yaml) workflow, not by `release.yaml` itself. Dispatch it within 1 hour of publish per NFR9:
|
|
672
|
-
|
|
673
|
-
```bash
|
|
674
|
-
gh workflow run install-smoke.yaml -f version=latest --ref main
|
|
675
|
-
```
|
|
676
|
-
|
|
677
|
-
The workflow fans a `workflow_dispatch` input over `ubuntu-latest`, `windows-latest`, and `macos-latest`, running `npx --yes bmad-module-skill-forge@<version> --version` on each runner. A clean three-leg run is the canonical post-publish evidence. Any failing leg routes through the `Rollback Playbook § Scenario B` (deprecate + ship `vX.Y.Z+1`).
|
|
678
|
-
|
|
679
|
-
**Where the evidence lives.** For a routine release the workflow run **is** the record — the dispatch satisfies NFR9 on its own and no audit artifact is written. Launch cuts additionally transcribe the run URL and matrix table into a per-launch audit artifact under `release-audits/`; `v1.0.0-launch-audit.md § Story 5.4 Post-Publish Verification` is the worked example and remains the only such artifact. Do not append routine releases to it — each audit file is a forensic record scoped to the launch that produced it.
|