bmad-module-skill-forge 2.1.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 +51 -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 +14 -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 +117 -9
- package/src/shared/scripts/skf-detect-docs.py +892 -4
- 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 +215 -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 +17 -6
- 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 +55 -18
- package/src/skf-create-skill/references/sub/ccc-discover.md +18 -7
- package/src/skf-create-skill/references/sub/fetch-docs.md +106 -57
- 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 -89
- package/src/skf-setup/references/detect-and-tier.md +58 -66
- 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 -645
- 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,15 +1,39 @@
|
|
|
1
1
|
---
|
|
2
2
|
nextStepFile: 'detect-changes.md'
|
|
3
|
-
|
|
3
|
+
# `{gapDrivenStepFile}`: §8 loads it in place of `{nextStepFile}` when
|
|
4
|
+
# `update_mode` is `gap-driven`: it translates and verifies the test
|
|
5
|
+
# report's gaps in place of steps 2 and 3, and goes on to step 4.
|
|
6
|
+
gapDrivenStepFile: 'gap-driven.md'
|
|
4
7
|
# Resolve `{hashContentHelper}` to the first existing path; HALT if neither
|
|
5
|
-
# candidate exists
|
|
6
|
-
# exact pre-write [MANUAL] inventory (per-block byte-exact interior hashes),
|
|
7
|
-
# which write.md §1 (HALT gate) and validate.md Check B later verify against.
|
|
8
|
-
# An LLM marker-count would miss an interior truncation that leaves the marker
|
|
9
|
-
# count unchanged.
|
|
8
|
+
# candidate exists: a [MANUAL] marker count by eye misses a truncated block.
|
|
10
9
|
hashContentProbeOrder:
|
|
11
10
|
- '{project-root}/_bmad/skf/shared/scripts/skf-hash-content.py'
|
|
12
11
|
- '{project-root}/src/shared/scripts/skf-hash-content.py'
|
|
12
|
+
# Resolve `{skillInventoryHelper}` to the first existing path. §1 lists the
|
|
13
|
+
# skills SKF generated when the invocation names none (interactive only), and
|
|
14
|
+
# step 4 runs it with `--skill` before a flat skill moves: only a flat skill
|
|
15
|
+
# whose metadata.json carries an SKF marker (`flat_skf`) is migrated. If
|
|
16
|
+
# neither exists, §1 shows its prompt without the list and §6c records no
|
|
17
|
+
# source version.
|
|
18
|
+
skillInventoryProbeOrder:
|
|
19
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-skill-inventory.py'
|
|
20
|
+
- '{project-root}/src/shared/scripts/skf-skill-inventory.py'
|
|
21
|
+
# Resolve `{runLockHelper}` to the first existing path; HALT if neither
|
|
22
|
+
# candidate exists: without the run lock two updates could write one skill.
|
|
23
|
+
runLockProbeOrder:
|
|
24
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-run-lock.py'
|
|
25
|
+
- '{project-root}/src/shared/scripts/skf-run-lock.py'
|
|
26
|
+
# Resolve `{sourceTreeHelper}` to the first existing path.
|
|
27
|
+
# HALT if neither resolves: without it a remote skill is read at whatever
|
|
28
|
+
# commit SKF's shared clone of its repository is on.
|
|
29
|
+
sourceTreeProbeOrder:
|
|
30
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-source-tree.py'
|
|
31
|
+
- '{project-root}/src/shared/scripts/skf-source-tree.py'
|
|
32
|
+
# `{findTestReportHelper}`: §1 and §4b find the test report. If neither
|
|
33
|
+
# exists, §1 warns and keeps normal mode and §4b offers nothing.
|
|
34
|
+
findTestReportProbeOrder:
|
|
35
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-find-test-report.py'
|
|
36
|
+
- '{project-root}/src/shared/scripts/skf-find-test-report.py'
|
|
13
37
|
---
|
|
14
38
|
|
|
15
39
|
<!-- Config: communicate in {communication_language}. -->
|
|
@@ -22,106 +46,138 @@ Load the existing skill and all its provenance data, detect whether this is an i
|
|
|
22
46
|
|
|
23
47
|
## Rules
|
|
24
48
|
|
|
25
|
-
- Focus only on loading existing artifacts and establishing the baseline
|
|
26
|
-
- Do not begin change detection (
|
|
49
|
+
- Focus only on loading existing artifacts and establishing the baseline: read-only operations, except the flat-to-versioned migration in §1, the cleanup of an interrupted update §1b finds, and the private source tree §6b prepares (a folder of this run's own, never the shared workspace clone)
|
|
50
|
+
- Do not begin change detection (step 2)
|
|
27
51
|
|
|
28
52
|
## Steps
|
|
29
53
|
|
|
54
|
+
**Halt procedure.** Every HALT in this step names its payload (`status`, `phase`, `path` when it has one, and `reason`), displays its message, and then runs, from `{project-root}`, the halt helper SKILL.md On Activation resolved.
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
uv run {runStateHelper} halt --run-dir "{run_dir}" \
|
|
58
|
+
[--tree "{source_tree}"] \
|
|
59
|
+
[--lock "{forge_data_folder}/{skill_name}/.skf-update.lock" --owner "{lock_owner}"] \
|
|
60
|
+
[--emit] <<'SKF_JSON'
|
|
61
|
+
{"status": "<status>", "phase": "<phase>", "path": "<path; leave the key out when the halt names none>", "reason": "<reason>", "skill_name": "<{skill_name}, or unknown before §1 resolves it>", "version": "<the metadata.json version, or unknown before §2 reads it>", "previous_version": "<the same>", "update_mode": "<normal, gap-driven or degraded; normal before §1 decides>"}
|
|
62
|
+
SKF_JSON
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Pass `--tree` once §6b has bound `{source_tree}`, `--lock` and `--owner` once §1b has bound `{lock_owner}`, and `--emit` in `{headless_mode}`. It removes the private source tree, releases the run lock (never one another run holds) and, with `--emit`, prints the halt's `SKF_UPDATE_RESULT_JSON:` line through the shared emitter, which adds the decisions recorded so far, the `error` object and a warning for each step the helper could not finish (`source-tree-not-removed`, `run-lock-not-released`); it never stops on a result. The emitter adds `files_written: []`. Write each payload value as a JSON string: escape `"` and `\`, and write every path with `/`. Display the line it prints verbatim. When it exits 1 and its message names the payload, fix the payload once and run it again, which redoes nothing already done; when it still fails or prints no line, display the halt message alone. An interactive HALT displays its message and emits nothing. A HALT that names no payload (a helper the frontmatter says to HALT without, resolving to no path) takes `status: "blocked"`, `phase: "init:<the helper's file name>"` and `reason: "<the helper's file name> is missing; re-install SKF"`.
|
|
66
|
+
|
|
67
|
+
The halt leaves `{run_dir}` in place. No HALT in this step adds a `headless_decisions[]` entry: a halt is not an auto-resolved gate.
|
|
68
|
+
|
|
69
|
+
**Warnings go to the run log.** Record each warning this step adds to `warnings[]` the moment it is raised: write its text to `{run_dir}/warning.txt` with a file write (a warning can hold quotes, `$` or backticks), then, from `{project-root}`, run `uv run {emitEnvelopeHelper} record --run-dir "{run_dir}" --warning "$(cat "{run_dir}/warning.txt")"`. The halt line and the result line read warnings only from `{run_dir}/warnings.jsonl`.
|
|
70
|
+
|
|
30
71
|
### 1. Request Skill Path
|
|
31
72
|
|
|
73
|
+
**The invocation's skill.** When `{requested_skill}` is set (SKILL.md On Activation), it answers the question below in either mode, and the prompt is not shown.
|
|
74
|
+
|
|
75
|
+
**No skill passed, interactive:** offer the skills SKF generated. Resolve `{skillInventoryHelper}` ← first existing path in `{skillInventoryProbeOrder}` and, from `{project-root}`, run `uv run {skillInventoryHelper} "{skills_output_folder}"`. Above the prompt, list, numbered, the `name` and `active_version` of each `skills[]` entry whose `skf_skill` is true; a number answers with that skill's name. When no candidate resolves, the command fails or prints no JSON, or no entry qualifies, show the prompt alone.
|
|
76
|
+
|
|
77
|
+
**No skill passed, headless:** there is no one to answer, and update-skill never guesses a skill. HALT (halt procedure: `status: "blocked"`, `phase: "init:skill-name"`, `path: "{skills_output_folder}"`, `reason: "input-missing: a headless run needs the skill's name or folder path as its argument"`) and display "**No skill to update.** A headless run takes the skill's name or folder path as its argument, for example `@Ferris US <skill> --headless`."
|
|
78
|
+
|
|
32
79
|
"**Which skill would you like to update?**
|
|
33
80
|
|
|
34
81
|
Provide either:
|
|
35
82
|
- A skill name (resolves via version-aware path resolution — see `knowledge/version-paths.md`)
|
|
36
83
|
- A full path to the skill folder
|
|
37
84
|
- A skill name with `--from-test-report` to use the test report's gap findings instead of source drift detection
|
|
38
|
-
- `--allow-workspace-drift` (gap-driven mode only) to intentionally bypass the
|
|
39
|
-
- `--allow-degraded` (headless mode only) to pre-authorize the lossy degraded full re-extraction if §4 finds no provenance map
|
|
85
|
+
- `--allow-workspace-drift` (gap-driven mode only) to intentionally bypass the gap-driven.md §3 guard that halts when the local workspace HEAD does not match `metadata.source_commit`. Under it update-skill takes nothing from HEAD: it moves or pins no provenance line read there, reads no signature, parameter list, return type or node kind there and counts no public API there, and gap-driven.md §3 halts `halted-for-workspace-drift` before merge on any gap that needs one, which every new or modified export does, and on every rescope; step 5 will NOT automatically re-pin
|
|
86
|
+
- `--allow-degraded` (headless mode only) to pre-authorize the lossy degraded full re-extraction if §4 finds no provenance map (not with `--from-test-report`, which needs the map): without it, a headless run halts `blocked` there rather than silently rebuilding
|
|
87
|
+
- `--target-ref <tag|branch|HEAD|commit>` (normal mode, a skill forged from a remote repository) to read that ref's current commit instead of the skill's recorded `source_ref` (for example the `upstream_ref` an audit reports). When the update writes, step 5 records the ref as the new `source_ref` together with the commit it read. `HEAD` follows the remote's default branch; a full 40-character commit pins that commit
|
|
40
88
|
- `--detect-only` to run detect-changes only and exit; emits the change manifest with no further work and no writes
|
|
41
|
-
- `--dry-run` to run detect-changes + re-extract and exit before merge/write; emits what WOULD change without modifying any artifact
|
|
89
|
+
- `--dry-run` to run detect-changes + re-extract and exit before merge/write; emits what WOULD change without modifying any artifact, and a gap-driven.md halt such as `halted-for-remediation-path` or `halted-for-workspace-drift` still stops it
|
|
42
90
|
|
|
43
|
-
**Skill:** {user provides
|
|
91
|
+
**Skill:** {user provides a number from the list, a name or a path}"
|
|
44
92
|
|
|
45
93
|
**Version-Aware Path Resolution:**
|
|
46
94
|
1. Read `{skills_output_folder}/.export-manifest.json` and look up the skill name in `exports` to get `active_version`
|
|
47
|
-
2. If found:
|
|
48
|
-
3. If not in manifest: check for `active` symlink at `{skills_output_folder}/{skill-name}/active` — resolve to `{skill_group}/active/{skill-name}/`
|
|
49
|
-
4. If neither: fall back to flat path `{skills_output_folder}/{skill-name}/`. If SKILL.md exists
|
|
95
|
+
2. If found: **Manifest-lag guard.** Also read the `active` link at `{skills_output_folder}/{skill-name}/active`. When it resolves to a different version than `active_version`, bind `{active_version}` to the link's target version instead and show an Info note: "manifest active_version {M} lags the active link {N}: updating {N}, the version the last update or forge wrote; run export-skill to publish it." The manifest advances only when export-skill runs, while every update that writes makes a new version and points `active` at it (step 5 §8): a manifest-first read would update the previous version again, and step 4 §6b would then stop at the version folder the last update created. Resolve to `{skill_package}` = `{skills_output_folder}/{skill-name}/{active_version}/{skill-name}/` (see `knowledge/version-paths.md` "Reading Workflows")
|
|
96
|
+
3. If not in manifest: check for `active` symlink at `{skills_output_folder}/{skill-name}/active` — bind `{active_version}` to the version it names and resolve to `{skill_group}/active/{skill-name}/`
|
|
97
|
+
4. If neither: fall back to the flat path `{skills_output_folder}/{skill-name}/`. If `SKILL.md` exists there, check that SKF generated it before anything moves:
|
|
98
|
+
- Resolve `{skillInventoryHelper}` ← first existing path in `{skillInventoryProbeOrder}`, run `uv run {skillInventoryHelper} {skills_output_folder} --skill {skill-name}`, and bind `{group_flat_skf}` ← `skills[0].flat_skf` and `{group_errors}` ← `skills[0].errors`.
|
|
99
|
+
- **`{group_flat_skf}` is true:** if the invocation carries `--detect-only` or `--dry-run` (read them from the invocation here; the flag handling below comes later), do not migrate and HALT: those modes never move a skill, and every later step reads the versioned forge workspace, which a flat skill does not have yet. Display "**`{skill-name}` still uses the flat layout: nothing was moved.** `--detect-only` and `--dry-run` never migrate a skill, and they need the versioned layout. Run `@Ferris US {skill-name}` once without them, or AS, TS or EX, to move it into that layout, then re-run with the flag." This runs before the §1b lock, so there is no lock to release. The halt procedure takes `status: "blocked"`, `phase: "init:read-only-flat-layout"`, `path: "{skills_output_folder}/{skill-name}/"`, `reason: "flat-layout: read-only modes never migrate a skill; run once without --detect-only or --dry-run"`, with `version` and `previous_version` `"unknown"`. Otherwise auto-migrate per `knowledge/version-paths.md` migration rules.
|
|
100
|
+
- **Otherwise** (`{group_flat_skf}` is false, the status is not `ok`, `skills[]` has no entry, or no helper candidate resolves): do not migrate. HALT before anything moves, with `halt_reason: "not-skf-output"` and this message: "**`{skill-name}` is not SKF output: nothing was moved.** `{skills_output_folder}/{skill-name}/SKILL.md` has no SKF marker in the `metadata.json` beside it, so SKF will not move or update it. A shared `{skills_output_folder}` is supported: SKF leaves the skills it did not generate alone, so manage `{skill-name}` yourself. Only if `{skills_output_folder}` holds a module's own source rather than skills, set `skills_output_folder` in `{project-root}/_bmad/skf/config.yaml` to a folder of its own, move your SKF skills there and re-run `/skf-setup`." When there is another reason, show it in place of the marker sentence: `{group_errors}` when it is non-empty (for example, the folder is a link, which SKF never moves), the helper's `error` when the status is not `ok`, and, when no helper candidate resolved, that SKF could not check the marker because `skf-skill-inventory.py` is missing, so re-install SKF. This runs before the §1b lock, so there is no lock to release. The halt procedure takes `status: "blocked"`, `phase: "init:ownership-gate"`, `path: "{skills_output_folder}/{skill-name}/"`, `reason: "not-skf-output: {the reason the message shows}"`, with `version` and `previous_version` `"unknown"`.
|
|
50
101
|
5. Store the resolved path as `{resolved_skill_package}` for all subsequent artifact loading
|
|
51
|
-
6. Bind `{baseline_version}` to the pre-update version — for an update this is the version being updated, i.e. the `{active_version}`
|
|
102
|
+
6. Bind `{baseline_version}` to the pre-update version — for an update this is the version being updated, i.e. the `{active_version}` steps 1–3 resolved (the flat-path fallback in step 4 has no version, so use the package version read from metadata.json in §2). Step 2 §1c passes `{baseline_version}` to `skf-provenance-gap-dispatch.py` as a required argument; leaving it unbound makes the helper search a wrong/empty directory and silently return `no-report`, dropping the major-version off-ramp.
|
|
52
103
|
|
|
53
104
|
Resolve the path to an absolute skill folder location.
|
|
54
105
|
|
|
55
|
-
**If `--from-test-report` was provided (or user references a test report):**
|
|
106
|
+
**If `--from-test-report` was provided (or user references a test report):** find the newest finished test report of the resolved skill with `{findTestReportHelper}` (resolve it ← first existing path in `{findTestReportProbeOrder}`). From `{project-root}`, run:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
uv run {findTestReportHelper} find \
|
|
110
|
+
--forge-data-folder "{forge_data_folder}" \
|
|
111
|
+
--skill-name "{skill_name}" \
|
|
112
|
+
[--version "{active_version}"]
|
|
113
|
+
```
|
|
56
114
|
|
|
57
|
-
`
|
|
115
|
+
Pass `--version` only when steps 1-3 above bound `{active_version}` (the flat fallback in step 4 has none). Never glob, sort or read a report's frontmatter by hand.
|
|
58
116
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
3. If neither glob returns anything, look for the stable companion `skf-test-skill-result-latest.json` in the same two directories (versioned first, then flat). Read the report path from `outputs[]` per the canonical contract documented at `shared/references/output-contract-schema.md` (resolved by skf-test-skill step 6 §4c) and load that file.
|
|
117
|
+
- **`status` is `found` and `report_exists` is true:** set `test_report_path` ← `path`, `{test_report_run_id}` ← `run_id` and `update_mode: gap-driven`. Name the report the helper picked (its file name, such as `test-report-{skill_name}-20260507T050917Z-487606-9b2f.md`, with its `testResult`, `score` and `source`) and each newer one it passed over as unfinished (`skipped[]`), so an operator can find them from the log, and add each of its `warnings[]` to `warnings[]` as `test-report: <entry>`.
|
|
118
|
+
- **Otherwise** (`not-found`; `found` with `report_exists` false, a result file naming a report that is gone; no candidate resolves; or the command fails or prints no JSON): warn that no test report was found, with the cause, and continue in normal source drift mode.
|
|
62
119
|
|
|
63
|
-
If
|
|
120
|
+
**If `--allow-workspace-drift` was provided:** set `allow_workspace_drift: true` in workflow context. This flag is consumed by gap-driven.md §3's pre-flight drift guard (gap-driven mode only) and has no effect in normal source-drift mode.
|
|
64
121
|
|
|
65
|
-
**If `--allow-
|
|
122
|
+
**If `--allow-degraded` was provided:** set `allow_degraded: true` in workflow context. This flag is consumed by §4 below when no provenance map is found under `{headless_mode}`; it has no effect interactively (the [D]/[X] prompt is shown), when a provenance map is present, or in gap-driven mode, where §4 halts `blocked` instead.
|
|
66
123
|
|
|
67
|
-
**If `--
|
|
124
|
+
**If `--target-ref` was provided:** set `{target_ref_override}` to its value in workflow context; §6b passes it to `{sourceTreeHelper}`. Decide this after the test-report lookup above: when `update_mode` is `gap-driven` it has no effect, since gap-driven mode repairs the skill at its pinned commit, so warn the user once at flag-parse time ("`--target-ref` has no effect with `--from-test-report`: gap-driven mode repairs the skill at its pinned commit") and leave `{target_ref_override}` unset. When `--from-test-report` found no report, the run continues in normal mode and keeps `{target_ref_override}`; when §4b switches a run to gap-driven mode, it unsets it with the same warning.
|
|
68
125
|
|
|
69
|
-
**If `--detect-only` was provided:** set `detect_only_mode: true` in workflow context. After step 2 (detect-changes) completes, jump directly to step
|
|
126
|
+
**If `--detect-only` was provided:** set `detect_only_mode: true` in workflow context. After step 2 (detect-changes) completes (gap-driven.md §2 in gap-driven mode), jump directly to step 6 (report): skip re-extract, merge and write. The report emits the change manifest and a `SKF_UPDATE_RESULT_JSON` envelope with `status: "detect-only"`. **Compatibility:** `--detect-only` short-circuits before gap-driven.md §3 runs, so `--allow-workspace-drift` is silently ignored in detect-only mode (warn the user once at flag-parse time: "`--allow-workspace-drift` has no effect with `--detect-only`: the workspace drift guard runs after the gaps are translated, which `--detect-only` skips").
|
|
70
127
|
|
|
71
|
-
**If `--dry-run` was provided:** set `dry_run_mode: true` in workflow context. After step 3 (re-extract) completes, jump directly to step
|
|
128
|
+
**If `--dry-run` was provided:** set `dry_run_mode: true` in workflow context. After step 3 (re-extract) completes (gap-driven.md §5 in gap-driven mode), jump directly to step 6 (report): skip merge and write. The report emits what would change with `status: "dry-run"` in the envelope. No artifact on disk is modified, in any mode (a repair from a test report and a docs-only skill included), and step 2 reports each skill brief amendment it would make as proposed instead of writing it: `--dry-run` is the "show me what an update would do without committing" mode.
|
|
72
129
|
|
|
73
130
|
**If BOTH `--detect-only` AND `--dry-run` were provided:** `--detect-only` wins (it's the more restrictive). Warn the user once: "`--detect-only` supersedes `--dry-run`; re-extract is skipped." Set `detect_only_mode: true`, ignore `dry_run_mode`.
|
|
74
131
|
|
|
75
132
|
### 1b. Concurrency Guard
|
|
76
133
|
|
|
77
|
-
**Skip this section entirely if `detect_only_mode` OR `dry_run_mode` is true.** Both inspection modes are read-only
|
|
134
|
+
**Skip this section entirely if `detect_only_mode` OR `dry_run_mode` is true.** Both inspection modes are read-only, and the private source tree §6b prepares for them never writes to the shared workspace clone.
|
|
78
135
|
|
|
79
|
-
Two concurrent `skf-update-skill` runs against the same `{forge_data_folder}/{skill_name}/` can corrupt provenance: one would write metadata.json mid-way through the other's extraction. The lock
|
|
136
|
+
Two concurrent `skf-update-skill` runs against the same `{forge_data_folder}/{skill_name}/` can corrupt provenance: one would write metadata.json mid-way through the other's extraction. The run lock is separate from the `.skf-workspace.lock` on the source clone, which only write.md §9 takes.
|
|
80
137
|
|
|
81
|
-
|
|
138
|
+
Resolve `{runLockHelper}` ← first existing path in `{runLockProbeOrder}`; it stays bound for the rest of the run. The lock's owner carries this run's `{run_id}`, the name of its run folder, so a later update that finds the lock stale knows which run folder holds what this run wrote. From `{project-root}`, run:
|
|
82
139
|
|
|
83
140
|
```bash
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if [ -f "$LOCK" ]; then
|
|
89
|
-
HELD_PID=$(head -n1 "$LOCK" 2>/dev/null | awk '{print $1}')
|
|
90
|
-
if [ -n "$HELD_PID" ] && kill -0 "$HELD_PID" 2>/dev/null; then
|
|
91
|
-
# Live PID — another update is running. HALT.
|
|
92
|
-
echo "skf-update-skill: another update is in progress (pid=$HELD_PID, started $(awk 'NR==2' "$LOCK" 2>/dev/null))"
|
|
93
|
-
# (LLM emits SKF_UPDATE_RESULT_JSON status=halted-for-concurrent-run, see below)
|
|
94
|
-
exit 1
|
|
95
|
-
fi
|
|
96
|
-
# Dead PID — lock left by a prior halted or crashed run; clear + overwrite
|
|
97
|
-
echo "skf-update-skill: clearing lock from a prior halted/crashed run (pid=$HELD_PID)"
|
|
98
|
-
fi
|
|
99
|
-
|
|
100
|
-
# Acquire: write our PID + start timestamp (one per line)
|
|
101
|
-
printf '%s\n%s\n' "$$" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$LOCK"
|
|
141
|
+
uv run {runLockHelper} acquire \
|
|
142
|
+
--lock "{forge_data_folder}/{skill_name}/.skf-update.lock" \
|
|
143
|
+
--owner "update-skill:{skill_name}:{run_id}" \
|
|
144
|
+
--stale-after 60
|
|
102
145
|
```
|
|
103
146
|
|
|
104
|
-
|
|
147
|
+
It prints one JSON line. Dispatch on its exit code:
|
|
105
148
|
|
|
106
|
-
-
|
|
107
|
-
-
|
|
149
|
+
- **0** (`acquired` true): bind `{lock_owner}` ← `owner`; every renewal and release passes that value. When `stale_replaced` is not null, this run replaced a stale lock, left by a run that stopped without releasing it or held by one still waiting at a gate, whose renewal will then halt. Display the helper's `message` and add `run-lock-replaced: {stale_replaced.held_by} since {stale_replaced.held_since}` to `warnings[]` (`an unnamed owner` in place of a null `held_by`, as in a lock file an older SKF version wrote), then clean up after it as below.
|
|
150
|
+
- **3** (`acquired` false): another run holds a fresh lock. HALT (halt procedure: `status: "halted-for-concurrent-run"`, `phase: "init:concurrency-guard"`, `path: "{forge_data_folder}/{skill_name}/.skf-update.lock"`, `reason: "another update in progress: {message}"`, with `version` and `previous_version` `"unknown"`, since §2 reads metadata.json later) and display "**Another update of {skill_name} is in progress.** {message}", with the helper's `message`, which names that run, the lock file to delete when no update of the skill is running and when the lock goes stale. This run took no lock, so it releases none.
|
|
151
|
+
- **No candidate resolves, any other exit, or no JSON:** HALT (halt procedure: `status: "blocked"`, `phase: "init:concurrency-guard"`, `path: "{forge_data_folder}/{skill_name}/.skf-update.lock"`, `reason: "run-lock-failed: {that message}"`) and display "**The run lock could not be taken:** {the `message` the helper printed on stderr, or 'skf-run-lock.py is missing; re-install SKF'}". No lock was taken, so none is released.
|
|
152
|
+
|
|
153
|
+
**Clean up an interrupted update.** When `stale_replaced.held_by` is not null, the update that held the lock may have stopped without finishing (a crash, a killed session, or a wait at a gate past the stale time) and left what it was writing half done: a gap-driven repair's package edited in place, or a new version folder the `active` link never reached. Undo it now, rather than stopping later at "version already exists". From `{project-root}`, run the helper SKILL.md On Activation resolved, with that owner exactly as the acquire printed it:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
uv run {runStateHelper} rollback \
|
|
157
|
+
--run-root "{project-root}/_bmad-output/.skf-run" \
|
|
158
|
+
--owner "{stale_replaced.held_by}" \
|
|
159
|
+
--skill "{skill_name}" \
|
|
160
|
+
--remove-run-dir
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
It finds that run's folder by the run id in the owner (`update-skill:{skill_name}:<run id>`), reads what the run recorded before its first write, restores a repair's package (with the version's provenance map, evidence report and skill brief) from the snapshot it took, removes the version folders it created (never one the `active` link names), and deletes its run folder (only a `.skf-run/skf-update-skill-<run id>` folder: it refuses a run id that would lead anywhere else); a run that finished its writes, or never began them, needs nothing undone. An owner of another skill or workflow, or one with no run id (an older SKF's lock), has no run folder: `status` `no-run-folder`, and nothing to report. On exit 0 with any other `status`, add `interrupted-run-cleaned: {stale_replaced.held_by}: {status}; restored {restored}; removed {removed}` to `warnings[]` and tell the user in one line what was put back. On any other exit, or no JSON, add `interrupted-run-not-cleaned: {stale_replaced.held_by}: {its failed[] or message}` and go on: a version folder it left then stops step 4 §6b, whose message says which folders to delete.
|
|
108
164
|
|
|
109
165
|
**Release contract:**
|
|
110
166
|
|
|
111
|
-
-
|
|
112
|
-
-
|
|
113
|
-
- The
|
|
167
|
+
- Every exit after this section releases the lock, so a run that halts does not block the next one: step 7 on a run that reaches it, and every other exit through the halt procedure of the step file it fires in: in this step §2's ABORTs, the Stack Skill Guard, §3, the §4 and §6 halts and the §6b source tree halts, and every later step's halts through their own. The release never stops on the result: when it fails or prints no JSON, the halt helper adds `run-lock-not-released: {forge_data_folder}/{skill_name}/.skf-update.lock` to its envelope's `warnings[]`. A release deletes the lock only while `{lock_owner}` holds it, so it never removes a lock another run took; the read-only modes take no lock and release none.
|
|
168
|
+
- A crashed or killed run leaves its lock until it goes stale. A run that waits at a gate past that time can lose its lock to the next update, so merge.md §6b and write.md §2 renew the lock before the run writes the skill, and halt `halted-for-concurrent-run` when this run no longer holds it.
|
|
169
|
+
- The private source tree §6b prepares has its own contract: step 7 removes it, every HALT or ABORT after §6b removes it first (the halt procedure of its step file), and a later run's §6b removes a tree a crashed or abandoned run left behind once it is seven days old.
|
|
114
170
|
|
|
115
171
|
### 2. Validate Required Artifacts
|
|
116
172
|
|
|
117
173
|
**Check SKILL.md exists:**
|
|
118
174
|
- Load `{resolved_skill_package}/SKILL.md`
|
|
119
|
-
- If missing:
|
|
175
|
+
- If missing: HALT (halt procedure: `status: "blocked"`, `phase: "init:validate-artifacts"`, `path: "{resolved_skill_package}/SKILL.md"`, `reason: "no SKILL.md in the skill package"`): "No SKILL.md found at `{resolved_skill_package}`. Run create-skill first."
|
|
120
176
|
|
|
121
177
|
**Check metadata.json exists:**
|
|
122
178
|
- Load `{resolved_skill_package}/metadata.json`
|
|
123
|
-
- Extract: `name`, `skill_type` (single or stack), `version`, `generation_date`, `confidence_tier`, `source_root`
|
|
124
|
-
- If missing:
|
|
179
|
+
- Extract: `name`, `skill_type` (single or stack), `version`, `generation_date`, `confidence_tier`, `source_type`, `scope_type` and `language` (when present), `source_repo`, `source_root`, `source_ref`, `source_commit`
|
|
180
|
+
- If missing: HALT (halt procedure: `status: "blocked"`, `phase: "init:validate-artifacts"`, `path: "{resolved_skill_package}/metadata.json"`, `reason: "no metadata.json in the skill package"`): "No metadata.json found. This skill may have been created manually. Run create-skill to generate provenance data."
|
|
125
181
|
|
|
126
182
|
**Detect skill type from metadata:**
|
|
127
183
|
- If `skill_type == "single"` or absent: flag as single skill
|
|
@@ -136,7 +192,7 @@ After loading metadata.json, check `skill_type`:
|
|
|
136
192
|
**To update this stack skill**, run `skf-create-stack-skill` with the same project path. It will re-analyze manifests (code-mode) or re-read constituent skills (compose-mode) and produce an updated stack.
|
|
137
193
|
|
|
138
194
|
If you came here from an audit report, the drift report identifies which constituent libraries changed — use that to decide whether re-composition is needed."
|
|
139
|
-
-
|
|
195
|
+
- Then HALT (halt procedure: `status: "blocked"`, `phase: "init:stack-skill-guard"`, `path: "{resolved_skill_package}"`, `reason: "stack-skill: a stack skill is re-composed with skf-create-stack-skill, never updated"`): the redirect ends the run, and step 2 never runs
|
|
140
196
|
|
|
141
197
|
**This guard is the single gate for stack skills** — every stack is redirected to `skf-create-stack-skill` here, before step 2, and no flag (`--detect-only`, `--dry-run`, `--from-test-report`, `--allow-workspace-drift`) bypasses it. Every later stage therefore runs against a single skill only and carries no stack-merge branch.
|
|
142
198
|
|
|
@@ -144,24 +200,26 @@ After loading metadata.json, check `skill_type`:
|
|
|
144
200
|
|
|
145
201
|
**Load `{sidecar_path}/forge-tier.yaml`:**
|
|
146
202
|
- Extract: `tier` (Quick, Forge, Forge+, or Deep), available tools
|
|
147
|
-
- If missing:
|
|
203
|
+
- If missing: HALT (halt procedure: `status: "blocked"`, `phase: "init:forge-tier"`, `path: "{sidecar_path}/forge-tier.yaml"`, `reason: "no forge-tier.yaml; run setup first"`): "No forge-tier.yaml found. Run setup first to detect available tools."
|
|
148
204
|
|
|
149
205
|
**Apply tier override:** Read `{sidecar_path}/preferences.yaml`. If `tier_override` is set and is a valid tier value (Quick, Forge, Forge+, or Deep), use it instead of the detected tier.
|
|
150
206
|
|
|
151
207
|
**Determine analysis capabilities:**
|
|
152
208
|
- **Quick:** text pattern matching only → T1-low confidence
|
|
153
|
-
- **Forge:** AST structural extraction → T1
|
|
154
|
-
- **Forge+:** AST structural extraction
|
|
155
|
-
- **Deep:** AST + QMD semantic enrichment →
|
|
209
|
+
- **Forge:** AST structural extraction → T1 for each export an ast-grep rule matches, T1-low for each export read by eye
|
|
210
|
+
- **Forge+:** AST structural extraction, plus a CCC check of rename candidates in a local source (step 2 Category C) → the same labels as Forge
|
|
211
|
+
- **Deep:** AST + QMD semantic enrichment → the same labels as Forge, plus T2
|
|
156
212
|
|
|
157
213
|
### 4. Load Provenance Map
|
|
158
214
|
|
|
159
|
-
**
|
|
160
|
-
- Extract: export list, file mappings, extraction timestamps, confidence tiers
|
|
161
|
-
- Calculate provenance age (days since last extraction)
|
|
215
|
+
**Find `{forge_data_folder}/{skill_name}/{active_version}/provenance-map.json`** (i.e., `{forge_version}/provenance-map.json`). If not found at the versioned path, fall back to `{forge_data_folder}/{skill_name}/provenance-map.json`. Bind `{provenance_map_path}` to the path found. Do not load the map into context: the steps that need it read the file through their helpers (step 2's, gap-driven.md §1's in a repair, and step 5's `apply`). Read from it only what §7 shows: the number of `entries[]` (`{export_count}`) and the provenance age, the days since its `last_update`, else its `generated_at`.
|
|
162
216
|
|
|
163
217
|
**If provenance map missing at both paths:**
|
|
164
218
|
|
|
219
|
+
**When `update_mode` is `gap-driven`, offer no [D]**, interactive or headless, `--allow-degraded` included: a repair spot-checks the report's gaps against this map. HALT (halt procedure: `status: "blocked"`, `phase: "init:load-provenance-map"`, `path: "{forge_version}/provenance-map.json"`, `reason: "gap-driven-needs-provenance-map: run a normal update first, which offers the degraded rebuild and writes a map, then re-run test-skill"`) and display "**A repair from a test report needs a provenance map, and `{skill_name}` has none.** Run a normal update first, which offers the degraded rebuild and writes a map, then re-run test-skill."
|
|
220
|
+
|
|
221
|
+
Otherwise:
|
|
222
|
+
|
|
165
223
|
"**WARNING:** No provenance map found at `{forge_version}/provenance-map.json` or flat fallback.
|
|
166
224
|
|
|
167
225
|
Without a provenance map, update-skill cannot perform targeted change detection. Options:
|
|
@@ -172,39 +230,130 @@ Without a provenance map, update-skill cannot perform targeted change detection.
|
|
|
172
230
|
Select: [D] Degraded / [X] Abort"
|
|
173
231
|
|
|
174
232
|
- If D: set `degraded_mode = true`, proceed with full extraction scope
|
|
175
|
-
- If X:
|
|
233
|
+
- If X: HALT (halt procedure: `status: "blocked"`, `phase: "init:load-provenance-map"`, `path: "{forge_version}/provenance-map.json"`, `reason: "no provenance map; the user chose to run create-skill first"`)
|
|
176
234
|
|
|
177
|
-
**
|
|
235
|
+
**GATE [default: HALT unless `--allow-degraded`]**: in `{headless_mode}` without `--allow-degraded` (default), do not auto-select [D]. Degraded mode is a full, lossy T1-low re-extraction: choosing it unattended would silently swap surgical update for a create-skill-equivalent rebuild, a policy call that belongs to an operator. HALT instead (halt procedure: `status: "blocked"`, `phase: "init:load-provenance-map"`, `path: "{forge_version}/provenance-map.json"`, `reason: "no provenance map at versioned or flat path; degraded full re-extraction needs a human decision"`).
|
|
178
236
|
|
|
179
|
-
**In `{headless_mode}` with `--allow-degraded` (`allow_degraded: true`):** the operator pre-authorized the lossy rebuild for this run, so treat it as an auto-resolved [D] rather than a halt. Set `degraded_mode = true`, proceed with full extraction scope, and
|
|
237
|
+
**In `{headless_mode}` with `--allow-degraded` (`allow_degraded: true`):** the operator pre-authorized the lossy rebuild for this run, so treat it as an auto-resolved [D] rather than a halt. Set `degraded_mode = true`, proceed with full extraction scope, and record the decision in the run's decision log, from `{project-root}`:
|
|
180
238
|
|
|
181
|
-
|
|
239
|
+
```bash
|
|
240
|
+
uv run {emitEnvelopeHelper} record --workflow skf-update-skill --run-dir "{run_dir}" --decision <<'SKF_JSON'
|
|
241
|
+
{"gate": "init.degraded-rebuild", "default_action": "X", "taken_action": "D", "reason": "headless: --allow-degraded pre-authorized degraded full re-extraction", "evidence": {"provenance_map": "<{forge_version}/provenance-map.json, with / separators>", "flat_fallback": "missing"}}
|
|
242
|
+
SKF_JSON
|
|
243
|
+
```
|
|
182
244
|
|
|
183
|
-
|
|
245
|
+
Continue to §4b, which skips itself in degraded mode.
|
|
184
246
|
|
|
185
|
-
|
|
247
|
+
### 4b. Offer an Unconsumed Test Report
|
|
248
|
+
|
|
249
|
+
**Run this section only when `--from-test-report` was not given and `degraded_mode` is false** (a repair reads the provenance map): a user who follows a failing test often runs a plain update. Resolve `{findTestReportHelper}` as §1 does and, from `{project-root}`, run:
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
uv run {findTestReportHelper} find \
|
|
253
|
+
--forge-data-folder "{forge_data_folder}" \
|
|
254
|
+
--skill-name "{skill_name}" \
|
|
255
|
+
[--version "{active_version}"] \
|
|
256
|
+
--newer-than "{generation_date}" \
|
|
257
|
+
--provenance-map "{provenance_map_path}"
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
The report is **unconsumed** when `status` is `found`, `report_exists` is true, `testResult` is `fail` or `pass-with-drift` (the two verdicts test-skill sends to update-skill), `newer` is not `false` (`null`, a time that cannot be read, offers the report rather than hiding it) and `applied` is not `true` (write.md §3 records the report a gap-driven repair applied). Otherwise, or when no candidate resolves or the command fails or prints no JSON, continue to §5. For an unconsumed report, bind `{unconsumed_test_report}` ← `path` and `{unconsumed_test_result}` ← `testResult`, then:
|
|
261
|
+
|
|
262
|
+
- **Interactive:** present the choice below. Its default follows the verdict: [G] for `fail`; [S] for `pass-with-drift`, which test-skill reached against a workspace HEAD other than the pinned commit, so a repair would read that same tree and halt `halted-for-workspace-drift`, while a normal update reads a fresh tree and records its commit.
|
|
263
|
+
|
|
264
|
+
"**Test report `{its file name}` ({testResult}, score {score}) is newer than this skill and has not been applied.**
|
|
265
|
+
|
|
266
|
+
[G] Repair the gaps it lists (gap-driven mode, as `--from-test-report` runs it)
|
|
267
|
+
[S] Check the source for changes (normal mode)"
|
|
268
|
+
|
|
269
|
+
- **[G]:** set `test_report_path` ← `{unconsumed_test_report}`, `{test_report_run_id}` ← `run_id` and `update_mode: gap-driven`, then unbind `{unconsumed_test_report}`. When `{target_ref_override}` is set, give §1's `--target-ref` warning and unset it.
|
|
270
|
+
- **[S]:** keep normal mode and add `unconsumed-test-report: {unconsumed_test_report}` to `warnings[]`.
|
|
271
|
+
- **GATE [default: S]**: headless (`{headless_mode}` true), keep normal mode, the mode the caller asked for, and add `unconsumed-test-report: {unconsumed_test_report}` to `warnings[]`. The warning is the notice, so the gate adds no `headless_decisions[]` entry.
|
|
272
|
+
|
|
273
|
+
While `{unconsumed_test_report}` stays bound, step 6's no-change report points to it instead of saying no action is required.
|
|
274
|
+
|
|
275
|
+
### 5. Load [MANUAL] Section Inventory
|
|
276
|
+
|
|
277
|
+
**Capture the [MANUAL] inventory deterministically.** The workflow's headline rule is "[MANUAL] sections survive regeneration with zero content loss", and the pre-write inventory captured here is the exact baseline step 4 §4 amends with the user's decisions and write.md §1 verifies against, so it must be a per-block byte-exact hash, not an eyeballed marker count. Run the `manual-inventory` subcommand of `{hashContentHelper}` and persist its JSON beside the §1b lock, never in a version folder: an update that writes leaves the previous version's folders unchanged (step 4 §6b).
|
|
186
278
|
|
|
187
279
|
```bash
|
|
188
280
|
uv run {hashContentHelper} manual-inventory {resolved_skill_package}/SKILL.md \
|
|
189
|
-
> {
|
|
281
|
+
> {forge_data_folder}/{skill_name}/.skf-update-manual-inventory.json
|
|
190
282
|
```
|
|
191
283
|
|
|
192
|
-
The emitted JSON is `{"blocks":[{name, content_hash, byte_offset, parent_heading}...], "count":N}
|
|
284
|
+
When `detect_only_mode` or `dry_run_mode` is true, run the same command without the redirect and read its output instead: those modes write nothing and never verify the inventory. The emitted JSON is `{"blocks":[{name, content_hash, byte_offset, parent_heading}...], "count":N}`: each `content_hash` covers the block's byte-exact interior, so a later interior truncation that leaves the marker count unchanged is still caught. Bind the persisted path as `{manual_inventory}` in context (the read-only modes bind none); step 4 §4 amends it and rebinds `{manual_inventory}` to the amended copy that later `manual-verify` calls read. Surface the block `count` in the baseline summary (§7 `{manual_count}`).
|
|
285
|
+
|
|
286
|
+
### 6. Resolve the Source
|
|
193
287
|
|
|
194
|
-
|
|
288
|
+
Bind the source fields from the `metadata.json` loaded in §2 (the provenance map does not carry `source_root`): `{source_root}` ← `source_root`, `{source_repo}` ← `source_repo`, `{source_ref}` ← `source_ref` and `{source_commit}` ← `source_commit`, each an empty string when the field is null or missing. `{source_ref}` and `{source_commit}` keep these values for the whole run: step 5 records any new commit in the artifacts only.
|
|
195
289
|
|
|
196
|
-
**
|
|
197
|
-
-
|
|
198
|
-
-
|
|
290
|
+
- **Docs-only skill** (`source_type: "docs-only"` in the brief or metadata.json, as step 3 §1 checks): there is no source tree. Skip §6b, §6c and the path check below. If `{target_ref_override}` is set, HALT as §6b's **Every §6b HALT** describes, with `{source_tree_reason}` = `target-ref-needs-remote-source` and `{source_tree_message}` = "`--target-ref` applies only to a skill forged from a remote repository".
|
|
291
|
+
- **Gap-driven mode** (`update_mode` is `gap-driven`): a repair reads the commit the skill is pinned to, and gap-driven.md §3 checks that `{source_root}` holds it before reading anything. Skip §6b and §6c and run the path check below.
|
|
292
|
+
- **Every other mode**, `--detect-only` and `--dry-run` included: run §6b. §6b runs the path check below only when the helper reports `skipped`.
|
|
199
293
|
|
|
200
|
-
**
|
|
294
|
+
**Path check:** validate that `{source_root}` exists and is accessible.
|
|
201
295
|
|
|
202
|
-
|
|
296
|
+
**If the source path is invalid or missing:**
|
|
297
|
+
|
|
298
|
+
"**Source path from metadata.json is invalid:** `{source_root}`
|
|
203
299
|
|
|
204
300
|
Provide the current source code path:
|
|
205
301
|
**Path:** {user provides path}"
|
|
206
302
|
|
|
207
|
-
|
|
303
|
+
Bind `{source_root}` to the path the user gives.
|
|
304
|
+
|
|
305
|
+
**In `{headless_mode}`:** there is no operator to supply a path. HALT (halt procedure: `status: "blocked"`, `phase: "init:resolve-source-path"`, `path: "{source_root}"`, `reason: "source_root from metadata.json is invalid or inaccessible and no interactive path can be supplied"`).
|
|
306
|
+
|
|
307
|
+
### 6b. Prepare the Source Tree
|
|
308
|
+
|
|
309
|
+
A skill forged from a remote repository at Forge tier or above records, as its `source_root`, the clone SKF keeps for that repository. Every SKF run on the repository shares that clone and leaves it at the commit it needed, so this run never reads it as it stands. `{sourceTreeHelper}` gives the run a private tree at one commit (the commit `{source_ref}` points to upstream now, or the tag, branch, `HEAD` or commit `--target-ref` names) and lists the files git changed between `{source_commit}` and that commit. Change detection, re-extraction, merge's file copies and write's citation check all read that tree. The helper never writes to the shared clone (step 5 moves the clone at the end of a real update), so this section runs in every mode.
|
|
310
|
+
|
|
311
|
+
Resolve `{sourceTreeHelper}` ← first existing path in `{sourceTreeProbeOrder}`. Bind `{tree_timeout}` to the seconds the helper may take: `100` when your shell tool stops a command after two minutes or you do not know its limit, otherwise a little under that limit, such as `540` under a 10-minute limit: a first fetch of a large repository is slow. The helper stops itself within `--timeout` seconds and still prints its result, so give the command a shell timeout longer than `{tree_timeout}`. From `{project-root}`, run:
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
uv run {sourceTreeHelper} open \
|
|
315
|
+
--source-repo "{source_repo}" \
|
|
316
|
+
--source-root "{source_root}" \
|
|
317
|
+
--source-ref "{source_ref}" \
|
|
318
|
+
--pinned-commit "{source_commit}" \
|
|
319
|
+
--timeout "{tree_timeout}" \
|
|
320
|
+
[--target-ref "{target_ref_override}"]
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Pass `--target-ref` only when `{target_ref_override}` is set.
|
|
324
|
+
|
|
325
|
+
Bind `{source_tree_status}` ← `status`, `{source_tree_reason}` ← `reason`, `{source_tree_message}` ← `message`, `{source_tree}` ← `tree`, `{workspace_clone}` ← `clone`, `{target_ref}` ← `target_ref`, `{target_commit}` ← `target_commit`, `{source_moved}` ← `moved`, `{source_diff_status}` ← `diff_status`, `{source_changed_files}` ← `changed_files`, `{source_changed_counts}` ← `changed_counts` and `{source_tree_warnings}` ← `warnings`. Add each `{source_tree_warnings}` entry to `warnings[]` as `source-tree: <entry>`. `{source_tree}` is the tree itself; `{source_changed_files}` and `run.json` sit in its run folder, one level up.
|
|
326
|
+
|
|
327
|
+
What the helper does:
|
|
328
|
+
|
|
329
|
+
- **Which sources it takes.** Only the clone SKF keeps for a remote repository: `{source_repo}` names that repository and `{source_root}` is the clone path SKF computes for it (compared as resolved paths, so a trailing slash or `~` does not matter), or a clone of the same repository at `repos/<host>/<owner>/<repo>` in any SKF workspace folder, whatever the letter case of those folder names. Any other source — a local folder, or the remote URL a Quick-tier skill records — is read as it stands (`skipped`).
|
|
330
|
+
- **The commit.** It asks the remote which commit the ref points to (a tag before a branch of the same name; an annotated tag resolves to its commit), fetches that one commit into a folder of this run's own and checks it out there. It takes the objects from the shared clone when the clone already has them, and never writes to the clone. When the remote cannot be reached or the commit cannot be fetched, and `--target-ref` was not given, it checks out `{source_commit}` from the shared clone instead (`offline`).
|
|
331
|
+
- **The file list.** It writes git's list of added, modified and deleted files between `{source_commit}` and the tree's commit to `{source_changed_files}`; `{source_diff_status}` is `unavailable` when `{source_commit}` cannot be read.
|
|
332
|
+
- **Housekeeping.** It first removes trees earlier runs left behind for seven days or more.
|
|
333
|
+
|
|
334
|
+
Dispatch on `{source_tree_status}`:
|
|
335
|
+
|
|
336
|
+
- **`ready`** (exit 0): bind `{source_root}` ← `{source_tree}`; every later step reads source there. No step writes `{source_tree}` into an artifact.
|
|
337
|
+
- **`offline`** (exit 0): bind `{source_root}` ← `{source_tree}` — a tree at the skill's own `{source_commit}` — display `{source_tree_message}` as a warning, and add `source-not-fetched: {source_tree_message}` to `warnings[]`. This run compares the skill with the commit it was built from and cannot see upstream changes.
|
|
338
|
+
- **`skipped`** (exit 0): keep `{source_root}` from metadata.json and run §6's path check. If `{target_ref_override}` is set, HALT as below with `{source_tree_reason}` = `target-ref-needs-remote-source` and `{source_tree_message}` = "`--target-ref` applies only to a skill forged from a remote repository".
|
|
339
|
+
- **`unavailable`** (exit 3): no commit to read could be obtained (`{source_tree_reason}` is `invalid-ref`, `git-unavailable`, `upstream-unreachable`, `ref-not-found`, `fetch-failed`, `checkout-failed`, `tree-folder-failed` or `timed-out`). When it is `timed-out` and your shell tool allows a command longer than `{tree_timeout}` seconds, raise `{tree_timeout}` toward that limit, run the command once more and dispatch on that result. Otherwise HALT before any step reads source; display `{source_tree_message}`. The helper leaves no tree behind.
|
|
340
|
+
- **No candidate resolves, or the command exits 1 or 2, or prints no JSON:** HALT with `{source_tree_reason}` = `helper-failed` and `{source_tree_message}` = "skf-source-tree.py did not finish: {the first stderr line; 'the shell stopped it before it printed a result' when your shell tool's timeout ended it; or 'it is missing; re-install SKF'}". A tree a stopped run left behind is removed by a later run once it is seven days old. Reading the shared clone as it stands is what this section exists to prevent.
|
|
341
|
+
|
|
342
|
+
**Every §6b HALT** runs the halt procedure with `status: "blocked"`, `phase: "init:source-tree"`, `path: "{source_repo}"`, `reason: "{source_tree_reason}: {source_tree_message}"`, and `version` and `previous_version` the metadata.json `version`. No `headless_decisions[]` entry: this is a hard halt, not an auto-resolved gate.
|
|
343
|
+
|
|
344
|
+
### 6c. Detect the Source Version
|
|
345
|
+
|
|
346
|
+
Run only when `{source_tree_status}` is `ready` or `offline`. Read the source's version from `{source_root}` with the Version Reconciliation rules of `skf-create-skill/references/source-resolution-protocols.md`: the version file for the detected language (`pyproject.toml`, `setup.py`, `__version__`, `package.json`, `Cargo.toml`, `go.mod`) and its monorepo `package.json` priority, matching package names against this skill's `name`. When no version file gives one, leave `{source_version_detected}` unset and skip the rest of this section. Otherwise compare it with the metadata.json `version` through `{skillInventoryHelper}` (resolve it ← first existing path in `{skillInventoryProbeOrder}`), from `{project-root}`:
|
|
347
|
+
|
|
348
|
+
```bash
|
|
349
|
+
uv run {skillInventoryHelper} version order "{the source's version}" "{version}"
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
It prints `order` and `major_minor` (the source's version against `{version}`) and `a.normalized` (the source's version as a version folder name). Never compare the two by hand.
|
|
353
|
+
|
|
354
|
+
- **`order` is `higher`** (a higher semantic version than the metadata.json `version`): bind `{source_version_detected}` ← `a.normalized`. Step 4 §6b and step 5 §2 name the version this update writes after it.
|
|
355
|
+
- **Otherwise** leave `{source_version_detected}` unset. When `major_minor` is `lower`, add `source-version-lower: {target_ref} reads {a.normalized}, older than {version}; this update keeps the patch-version rule` to `warnings[]`. A source version lower only in its patch number is expected, not a regression: every update that finds no higher version increments the skill's patch number (step 4 §6b), so a skill forged at the source's 1.2.0 is 1.2.1 after one update while the source still reads 1.2.0.
|
|
356
|
+
- **Exit 1** (`code` `NOT_A_VERSION`: a value that names no version, such as a dynamic version in `pyproject.toml`), **no candidate, or no JSON:** leave `{source_version_detected}` unset: step 4 §6b takes the next patch version.
|
|
208
357
|
|
|
209
358
|
### 7. Present Baseline Summary
|
|
210
359
|
|
|
@@ -216,7 +365,8 @@ Provide the current source code path:
|
|
|
216
365
|
| **Type** | single |
|
|
217
366
|
| **Version** | {version} |
|
|
218
367
|
| **Created** | {created date} |
|
|
219
|
-
| **Source** | {
|
|
368
|
+
| **Source** | {source_display} |
|
|
369
|
+
| **Source commit** | {source_commit_line} |
|
|
220
370
|
| **Forge Tier** | {forge_tier} (current) vs {original_tier} (at creation) |
|
|
221
371
|
| **Provenance Age** | {days} days since last extraction |
|
|
222
372
|
| **Exports** | {export_count} tracked exports |
|
|
@@ -225,14 +375,30 @@ Provide the current source code path:
|
|
|
225
375
|
|
|
226
376
|
**Analysis plan:** {tier_description}
|
|
227
377
|
- {Quick: text pattern diff → T1-low findings}
|
|
228
|
-
- {Forge: AST structural diff → T1 findings}
|
|
229
|
-
- {Deep: AST structural + QMD semantic diff →
|
|
378
|
+
- {Forge: AST structural diff → T1 findings where an ast-grep rule matches, T1-low where an export is read by eye}
|
|
379
|
+
- {Deep: AST structural + QMD semantic diff → the same labels as Forge, plus T2 findings}
|
|
230
380
|
|
|
231
381
|
**Ready to detect changes and update this skill?**"
|
|
232
382
|
|
|
383
|
+
`{source_display}` is `{source_repo}` at `{target_ref}` when `{source_tree_status}` is `ready` or `offline` — plus ` (re-pinned from {source_ref})` when `{target_ref_override}` is set — and `{source_root}` otherwise. `{source_commit_line}` shows commits as their first 8 characters:
|
|
384
|
+
|
|
385
|
+
- `ready` and `{source_moved}` true: `{source_commit} → {target_commit}` plus ` ({added} added, {modified} modified, {deleted} deleted files)` from `{source_changed_counts}`, or ` (file list unavailable)` when `{source_diff_status}` is `unavailable`;
|
|
386
|
+
- `ready` and `{source_moved}` false: `{target_commit} (no newer commit)`;
|
|
387
|
+
- `ready` and `{source_moved}` null: `{target_commit} (no pinned commit before this run)`;
|
|
388
|
+
- `offline`: `{source_commit} (upstream not reached — comparing the pinned commit only)`;
|
|
389
|
+
- any other source: `{source_commit} (read as it stands)`.
|
|
390
|
+
|
|
391
|
+
Steps 5 and 6 reuse `{source_display}` and `{source_commit_line}`.
|
|
392
|
+
|
|
233
393
|
### 8. Confirmation Gate
|
|
234
394
|
|
|
235
|
-
Present "**Select:** [C] Continue to Change Detection" and wait for the user to confirm; on [C], load, read the full file, then execute {nextStepFile}
|
|
395
|
+
Present "**Select:** [C] Continue to Change Detection" ("**Select:** [C] Continue to Gap-Driven Repair" when `update_mode` is `gap-driven`) and wait for the user to confirm; on [C], load, read the full file, then execute `{gapDrivenStepFile}` when `update_mode` is `gap-driven`, else `{nextStepFile}`.
|
|
396
|
+
|
|
397
|
+
**GATE [default: C]**: headless (`{headless_mode}` true), auto-continue and record the decision in the run's decision log, from `{project-root}` (the emitter checks it against `shared/scripts/schemas/skf-update-result-envelope.v1.json`, and step 6's line carries it):
|
|
236
398
|
|
|
237
|
-
|
|
399
|
+
```bash
|
|
400
|
+
uv run {emitEnvelopeHelper} record --workflow skf-update-skill --run-dir "{run_dir}" --decision <<'SKF_JSON'
|
|
401
|
+
{"gate": "init.update-confirmation", "default_action": "C", "taken_action": "C", "reason": "headless: no user to prompt"}
|
|
402
|
+
SKF_JSON
|
|
403
|
+
```
|
|
238
404
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Invocation Contract: skf-update-skill
|
|
2
|
+
|
|
3
|
+
The argument set, gate map, outputs and headless result line of `skf-update-skill`. Interactive runs do not need this file; headless callers and pipeline integrators read it.
|
|
4
|
+
|
|
5
|
+
| Aspect | Detail |
|
|
6
|
+
|--------|--------|
|
|
7
|
+
| **Inputs** | skill_name [required]: the skill's name or folder path, as the argument. With none, an interactive run offers the skills SKF generated, and a headless run halts `blocked` (`error.phase` `init:skill-name`, `error.reason` starting `input-missing`) |
|
|
8
|
+
| **Flags** | `--headless` / `-H`; `--from-test-report` (gap-driven mode); `--target-ref <ref>`; `--allow-workspace-drift` (gap-driven only: update-skill takes nothing from HEAD, and a gap that needs the pinned tree, a rescope included, halts `halted-for-workspace-drift` before merge); `--allow-degraded`; `--detect-only` (status `detect-only`); `--dry-run` (status `dry-run`, writes nothing in any mode; a gap-driven.md halt such as `halted-for-remediation-path` or `halted-for-workspace-drift` still stops it). init.md §1 describes each; `--detect-only` wins over `--dry-run`. |
|
|
9
|
+
| **Gates** | step 1: init.md §4's degraded-rebuild gate [D]/[X] when no provenance map is found (headless: halts, or takes [D] under `--allow-degraded`; a gap-driven run halts `blocked` there instead); init.md §4b's [G]/[S] test-report offer, interactive only (headless warns `unconsumed-test-report`); Confirm Gate [C] (init.md §8) | step 2, each only when triggered: detect-changes.md §1b's promote prompt and §1c's scope-expansion prompt [P]/[S]/[U] (headless: defers each candidate to a person), §2.2's deletion-ratio gate [C]/[B]/[A] (headless: [C]) | step 2g (gap-driven): gap-driven.md §1 rule R1's [D]/[R] for each gap whose remediation names removal (headless: [D], or [R] only when the export is internal or `#[doc(hidden)]`) | step 4: Confirm Gate [C] only after the user resolved [MANUAL] conflicts (merge.md §4); a clean merge auto-proceeds, and a headless run with conflicts halts `halted-for-manual-mismatch` before anything is written |
|
|
10
|
+
| **Outputs** | A new version folder `{skill_group}/{new_version}/` and its forge folder, beside the unchanged previous version (a gap-driven repair writes the current version in place), and a finished run's `update-skill-result-{YYYYMMDD-HHmmss}.json` and `update-skill-result-latest.json` in that version's forge folder (a run that found no change: the current one's). `--detect-only` and `--dry-run` write none of these, nor the skill brief. Run state goes to a run folder under `{project-root}/_bmad-output/.skf-run/` that step 7 removes; a halt keeps it |
|
|
11
|
+
| **Concurrency** | A second real update of the same skill halts `halted-for-concurrent-run` while the first holds its run lock (init.md §1b); `--detect-only` and `--dry-run` take no lock. A remote skill is read from a private tree, and no mode writes to the shared workspace clone before write.md §9. |
|
|
12
|
+
| **Headless** | No gate prompts: each resolves as the Gates row says, and the run records the decision. One `SKF_UPDATE_RESULT_JSON` line (report.md §5b), at step 6 or at a HALT, printed by the shared emitter and checked against `shared/scripts/schemas/skf-update-result-envelope.v1.json`. It is the run's last line: the shared health check displays it after everything else; a HALT displays its line and stops. Pipelines branch on `skf_update.status`: `success`, `no-changes`, `detect-only` and `dry-run` are successful exits. |
|
|
@@ -22,26 +22,9 @@ It must be preserved during any update operation.
|
|
|
22
22
|
|
|
23
23
|
### Rules
|
|
24
24
|
|
|
25
|
-
1. **Never modify content between [MANUAL] markers
|
|
26
|
-
2. **Preserve marker positions
|
|
27
|
-
3. **
|
|
28
|
-
4. **
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
### Conflict Types
|
|
32
|
-
|
|
33
|
-
| Conflict | Severity | Resolution |
|
|
34
|
-
|------------------------------------------------|----------|-------------------------------------------------------|
|
|
35
|
-
| Regenerated content overlaps [MANUAL] position | HIGH | Present both versions, user chooses |
|
|
36
|
-
| Parent section deleted | WARNING | Flag orphaned [MANUAL], user decides keep/remove |
|
|
37
|
-
| [MANUAL] references deleted export | MEDIUM | Flag stale reference, suggest update |
|
|
38
|
-
| New export inserted adjacent to [MANUAL] | LOW | Auto-resolve: place new content before [MANUAL] block |
|
|
39
|
-
|
|
40
|
-
### Preservation Algorithm
|
|
41
|
-
|
|
42
|
-
1. Extract all [MANUAL] blocks with their section-name identifiers
|
|
43
|
-
2. Map each block to its parent section (by heading hierarchy)
|
|
44
|
-
3. Perform merge on generated content only
|
|
45
|
-
4. Re-insert [MANUAL] blocks at their mapped positions
|
|
46
|
-
5. If position conflict: halt and present to user
|
|
47
|
-
6. If clean insert: auto-place and continue
|
|
25
|
+
1. **Never delete or modify content between [MANUAL] markers without the user's merge §4 [R]emove or [E]dit decision**: treat it as immutable otherwise. The decision is recorded in the run's manual plan, and the post-merge check verifies the blocks against the inventory that plan amends
|
|
26
|
+
2. **Preserve marker positions**: if the surrounding generated content moves, the [MANUAL] block moves with its logical parent section
|
|
27
|
+
3. **Multiple [MANUAL] blocks**: a single SKILL.md may have multiple [MANUAL] sections; preserve all
|
|
28
|
+
4. **Nested [MANUAL] forbidden**: [MANUAL] blocks cannot be nested; if detected, flag as ERROR
|
|
29
|
+
|
|
30
|
+
merge.md §3 and §4 name the conflicts (ORPHAN, STALE_REFERENCE, POSITION) and how each is resolved.
|