bmad-module-skill-forge 2.2.0 → 4.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 +143 -18
- package/docs/verifying-a-skill.md +87 -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 +3 -3
- 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 +31 -59
- 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 +233 -64
- package/src/shared/scripts/skf-build-change-manifest.py +2753 -28
- 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 +772 -0
- package/src/shared/scripts/skf-comention-pairs.py +1333 -123
- package/src/shared/scripts/skf-compare-file-hashes.py +267 -105
- 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 +21 -3
- 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 +197 -55
- 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 +5247 -96
- package/src/shared/scripts/skf-extraction-inventory.py +1280 -0
- package/src/shared/scripts/skf-extraction-snapshot.py +664 -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 +469 -205
- package/src/shared/scripts/skf-github-fetch.py +365 -0
- package/src/shared/scripts/skf-github-probe.py +700 -0
- package/src/shared/scripts/skf-hash-content.py +180 -20
- package/src/shared/scripts/skf-language-corpora.py +1 -1
- package/src/shared/scripts/skf-load-provenance.py +161 -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 +143 -20
- 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 +176 -28
- package/src/shared/scripts/skf-promote-staged.py +430 -0
- package/src/shared/scripts/skf-provenance-gap-dispatch.py +64 -8
- 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 +155 -14
- 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 +787 -121
- package/src/shared/scripts/skf-tessl-review.py +706 -0
- package/src/shared/scripts/skf-update-active-symlink.py +120 -67
- 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 +23 -2
- 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 +3945 -152
- package/src/shared/scripts/skf-write-skill-brief.py +406 -25
- 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 +28 -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/health-check.md +2 -0
- package/src/skf-audit-skill/references/init.md +149 -210
- package/src/skf-audit-skill/references/re-index.md +103 -110
- package/src/skf-audit-skill/references/report.md +96 -33
- package/src/skf-audit-skill/references/semantic-diff.md +34 -25
- 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 +80 -90
- package/src/skf-audit-skill/references/upstream-checkout.md +77 -0
- package/src/skf-audit-skill/scripts/render-drift-tables.py +712 -0
- package/src/skf-brief-skill/SKILL.md +37 -26
- package/src/skf-brief-skill/assets/description-voice-examples.md +1 -1
- package/src/skf-brief-skill/assets/scope-templates.md +18 -25
- package/src/skf-brief-skill/assets/skill-brief-schema.md +30 -23
- package/src/skf-brief-skill/customize.toml +48 -20
- package/src/skf-brief-skill/references/analyze-target.md +144 -127
- package/src/skf-brief-skill/references/confirm-brief.md +11 -9
- 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 +26 -100
- package/src/skf-brief-skill/references/step-auto-validate.md +20 -121
- package/src/skf-brief-skill/references/version-resolution.md +7 -10
- 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 +11 -8
- package/src/skf-create-skill/assets/skill-sections.md +21 -11
- package/src/skf-create-skill/customize.toml +20 -8
- package/src/skf-create-skill/references/authoritative-files-protocol.md +36 -58
- 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 +119 -70
- 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 +37 -0
- package/src/skf-create-skill/references/extract.md +204 -162
- package/src/skf-create-skill/references/extraction-patterns-by-hand.md +141 -0
- package/src/skf-create-skill/references/extraction-patterns-tracing.md +11 -11
- package/src/skf-create-skill/references/extraction-patterns.md +588 -201
- 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 +37 -57
- package/src/skf-create-skill/references/source-resolution-protocols.md +58 -169
- 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 +83 -146
- package/src/skf-drop-skill/references/health-check.md +2 -0
- 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 +122 -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 +209 -264
- 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 +164 -91
- 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 +164 -142
- package/src/skf-test-skill/references/score.md +80 -107
- package/src/skf-test-skill/references/scoring-rules.md +42 -155
- package/src/skf-test-skill/references/source-access-protocol.md +18 -94
- 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 +380 -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 +1666 -0
- package/src/skf-test-skill/scripts/hard-gate.py +203 -0
- package/src/skf-test-skill/scripts/load-coverage-inputs.py +1198 -0
- package/src/skf-test-skill/scripts/locate-export-segments.py +835 -0
- package/src/skf-test-skill/scripts/reconcile-coverage.py +547 -79
- package/src/skf-test-skill/scripts/score-signatures.py +479 -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 +133 -56
- 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 +253 -223
- 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 +254 -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 +182 -49
- package/src/skf-update-skill/references/re-extract.md +53 -262
- package/src/skf-update-skill/references/report.md +102 -35
- package/src/skf-update-skill/references/write.md +357 -155
- package/src/skf-update-skill/scripts/skf-new-file-diff.py +183 -46
- 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,45 +1,52 @@
|
|
|
1
1
|
---
|
|
2
2
|
nextStepFile: 're-extract.md'
|
|
3
3
|
noChangeReportFile: 'report.md'
|
|
4
|
-
|
|
5
|
-
#
|
|
6
|
-
# SHA-256 hashing (file-read + size + line-count) and provenance comparison
|
|
7
|
-
# (UNCHANGED / MODIFIED_FILE / DELETED_FILE classification). Falling back to
|
|
8
|
-
# prose-driven hashing would lose hash stability across runs.
|
|
4
|
+
extractionPatternsData: 'skf-create-skill/references/extraction-patterns.md'
|
|
5
|
+
# `{hashContentHelper}`: Category D's hash comparison. HALT if neither exists.
|
|
9
6
|
hashContentProbeOrder:
|
|
10
7
|
- '{project-root}/_bmad/skf/shared/scripts/skf-hash-content.py'
|
|
11
8
|
- '{project-root}/src/shared/scripts/skf-hash-content.py'
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
# the major-version trigger. Falling back to prose-driven count rollups
|
|
16
|
-
# would let the LLM drift on manifest shape across runs.
|
|
9
|
+
# `{buildChangeManifestHelper}`: §3's `build` (and §1's for a docs-only
|
|
10
|
+
# skill), §2.2's `deletion-ratio`, Category B's `baseline-gaps` and Category
|
|
11
|
+
# C's `rename-candidates`. HALT if neither exists.
|
|
17
12
|
buildChangeManifestProbeOrder:
|
|
18
13
|
- '{project-root}/_bmad/skf/shared/scripts/skf-build-change-manifest.py'
|
|
19
14
|
- '{project-root}/src/shared/scripts/skf-build-change-manifest.py'
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
# Out-of-Scope candidates, and classify them against brief.scope.amendments[]
|
|
23
|
-
# in one call. Falling back to prose-driven markdown parsing would let
|
|
24
|
-
# report-format drift produce silent skips of out-of-scope candidates.
|
|
15
|
+
# `{provenanceGapDispatchHelper}`: §1c's drift-report candidates. HALT if
|
|
16
|
+
# neither exists.
|
|
25
17
|
provenanceGapDispatchProbeOrder:
|
|
26
18
|
- '{project-root}/_bmad/skf/shared/scripts/skf-provenance-gap-dispatch.py'
|
|
27
19
|
- '{project-root}/src/shared/scripts/skf-provenance-gap-dispatch.py'
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
# source tree for scripts/assets (directory conventions, shebang signals,
|
|
31
|
-
# `package.json` `bin` entry-points, asset filename patterns) so NEW_FILE
|
|
32
|
-
# detection mirrors create-skill §4c exactly. Falling back to prose-driven
|
|
33
|
-
# file walking would let the LLM drift on the heuristic list and miss new
|
|
34
|
-
# scripts/assets at deeper directory depths — and on installed modules
|
|
35
|
-
# (no `src/` tree) the LLM would otherwise guess a non-existent path.
|
|
20
|
+
# `{detectScriptsAssetsHelper}`: Category D's script and asset walk, the
|
|
21
|
+
# detector create-skill runs, over the whole source. HALT if neither exists.
|
|
36
22
|
detectScriptsAssetsProbeOrder:
|
|
37
23
|
- '{project-root}/_bmad/skf/shared/scripts/skf-detect-scripts-assets.py'
|
|
38
24
|
- '{project-root}/src/shared/scripts/skf-detect-scripts-assets.py'
|
|
39
|
-
# `{newFileDiffHelper}
|
|
40
|
-
#
|
|
41
|
-
# otherwise ask the model to compute by hand. Per-skill helper, always bundled.
|
|
25
|
+
# `{newFileDiffHelper}`: Category D's NEW_FILE set difference, kept to the
|
|
26
|
+
# skill brief's scope, bundled with this skill.
|
|
42
27
|
newFileDiffHelper: 'scripts/skf-new-file-diff.py'
|
|
28
|
+
# `{resolveAuthoritativeFilesHelper}`: §1b's mirror. HALT if neither exists.
|
|
29
|
+
resolveAuthoritativeFilesProbeOrder:
|
|
30
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-resolve-authoritative-files.py'
|
|
31
|
+
- '{project-root}/src/shared/scripts/skf-resolve-authoritative-files.py'
|
|
32
|
+
# `{classifyChangedFilesHelper}`: §2.1 Category A. HALT if neither exists.
|
|
33
|
+
classifyChangedFilesProbeOrder:
|
|
34
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-classify-changed-files.py'
|
|
35
|
+
- '{project-root}/src/shared/scripts/skf-classify-changed-files.py'
|
|
36
|
+
# `{extractPublicApiHelper}`: §2.1 Category B's recipe runner. If neither
|
|
37
|
+
# exists, read the files by eye.
|
|
38
|
+
extractPublicApiProbeOrder:
|
|
39
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-extract-public-api.py'
|
|
40
|
+
- '{project-root}/src/shared/scripts/skf-extract-public-api.py'
|
|
41
|
+
# `{structuralDiffHelper}`: §2.1 Category B's diff. HALT if neither exists.
|
|
42
|
+
structuralDiffProbeOrder:
|
|
43
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-structural-diff.py'
|
|
44
|
+
- '{project-root}/src/shared/scripts/skf-structural-diff.py'
|
|
45
|
+
# `{compareDocHashesHelper}`: §1's docs-only branch, the doc-hash comparison
|
|
46
|
+
# audit-skill's doc-drift step runs. HALT if neither exists.
|
|
47
|
+
compareDocHashesProbeOrder:
|
|
48
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-detect-docs.py'
|
|
49
|
+
- '{project-root}/src/shared/scripts/skf-detect-docs.py'
|
|
43
50
|
---
|
|
44
51
|
|
|
45
52
|
<!-- Config: communicate in {communication_language}. -->
|
|
@@ -52,60 +59,51 @@ Compare current source code state against the provenance map to produce a comple
|
|
|
52
59
|
|
|
53
60
|
## Rules
|
|
54
61
|
|
|
55
|
-
- Focus only on detecting and classifying changes
|
|
56
|
-
-
|
|
62
|
+
- Focus only on detecting and classifying changes: extract only what Category B's diff reads (step 3 reuses it), and merge nothing
|
|
63
|
+
- Run §2.1's categories in order, A, then B, then C: each reads the one before. Fan Category B's reading out in parallel batches (Pattern 4) only when its file list is long
|
|
57
64
|
|
|
58
65
|
## Steps
|
|
59
66
|
|
|
60
|
-
|
|
67
|
+
The helpers below and step 3 pass their JSON to each other through `{run_dir}`, the run folder SKILL.md On Activation created.
|
|
61
68
|
|
|
62
|
-
**
|
|
69
|
+
**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. Nothing this step wrote is undone: it writes the skill brief only with a person's decision or a headless deferral (§1b, §1c), both of which stand, and the read-only modes write a copy in `{run_dir}` instead.
|
|
63
70
|
|
|
64
|
-
|
|
71
|
+
```bash
|
|
72
|
+
uv run {runStateHelper} halt --run-dir "{run_dir}" \
|
|
73
|
+
[--tree "{source_tree}"] \
|
|
74
|
+
[--lock "{forge_data_folder}/{skill_name}/.skf-update.lock" --owner "{lock_owner}"] \
|
|
75
|
+
[--emit] <<'SKF_JSON'
|
|
76
|
+
{"status": "<status>", "phase": "<phase>", "path": "<path; leave the key out when the halt names none>", "reason": "<reason>", "skill_name": "{skill_name}", "version": "<the metadata.json version>", "previous_version": "<the same>", "update_mode": "<normal or degraded>"}
|
|
77
|
+
SKF_JSON
|
|
78
|
+
```
|
|
65
79
|
|
|
66
|
-
|
|
67
|
-
2. Read the **Coverage Analysis** section — each per-export row has documented/missing/mismatch status
|
|
68
|
-
3. Translate findings into change manifest format:
|
|
80
|
+
Pass `--tree` when init.md §6b bound `{source_tree}`, `--lock` and `--owner` when init.md §1b bound `{lock_owner}` (the read-only modes take no lock), 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. 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: "detect-changes:<the helper's file name>"` and `reason: "<the helper's file name> is missing; re-install SKF"`.
|
|
69
81
|
|
|
70
|
-
|
|
71
|
-
|-------------|----------|-----------------|
|
|
72
|
-
| Critical | Missing export documentation | NEW_EXPORT (undocumented public API) — unless the remediation says the export is internal / out of scope; then DELETED_EXPORT (rescope), see rule R1 |
|
|
73
|
-
| High | Signature mismatch | MODIFIED_EXPORT (signature needs update) |
|
|
74
|
-
| Medium | Missing type/interface docs | NEW_EXPORT (undocumented type) |
|
|
75
|
-
| Medium | Stale documentation | MODIFIED_EXPORT (docs reference removed export) |
|
|
76
|
-
| Critical/Medium | Coverage gap whose remediation is **removal** (export is internal, `#[doc(hidden)]`, or explicitly out of scope) | DELETED_EXPORT (rescope) — see rule R1 |
|
|
77
|
-
| Medium/High | Structural / coherence drift (output file only, no source change) | STRUCTURAL_FIX — see rule R2 |
|
|
78
|
-
| Medium | Export documented in SKILL.md/references but **missing from the provenance-map** | NEW_EXPORT (provenance-completeness) — see rule R3 |
|
|
79
|
-
| Low | Missing metadata/examples | metadata update — see rule R4 |
|
|
82
|
+
The halt leaves `{run_dir}` in place, with the decisions the gates below recorded before it.
|
|
80
83
|
|
|
81
|
-
**
|
|
84
|
+
**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`.
|
|
85
|
+
|
|
86
|
+
### 1. Scan Current Source State
|
|
82
87
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
88
|
+
**A docs-only skill** (`source_type: "docs-only"` in the brief or metadata.json) has no source tree for §2's categories to read: its drift is in the documents `doc_sources` records with their hashes. Compare them with the helper audit-skill's doc-drift step runs: resolve `{compareDocHashesHelper}` ← first existing path in `{compareDocHashesProbeOrder}` and, from `{project-root}`, run:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
uv run {compareDocHashesHelper} compare-hashes "{skill_package}/metadata.json" > "{run_dir}/doc-hashes.json"
|
|
92
|
+
```
|
|
87
93
|
|
|
88
|
-
|
|
94
|
+
It fetches each URL with a recorded hash and buckets it `changed`, `unchanged`, `fetch_failed` or `skipped_null_hash`, with `stats`: never fetch or hash a URL by hand. On exit 2, no JSON, or no candidate resolves, HALT with status `blocked` (halt procedure: `phase: "detect-changes:doc-hashes"`, `path: "{skill_package}/metadata.json"`, `reason: "<its error, or skf-detect-docs.py is missing; re-install SKF>"`). Add `doc-fetch-failed: {url} ({reason})` to `warnings[]` for each `fetch_failed` entry (a document that could not be read is not drift), `doc-not-hashed: {url}` for each `skipped_null_hash` entry, and `doc-drift-not-checked: metadata.json records no doc_sources` when `stats.total_tracked` is 0. Then skip §1b to §2.2 and §3's build, and have the helper write the change manifest from that output, never by hand:
|
|
89
95
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
- **`change_category`** — the Change Category resolved from the table above (`NEW_EXPORT`, `MODIFIED_EXPORT`, `DELETED_EXPORT`, `STRUCTURAL_FIX`, or `metadata update`). Step 3 §1a partitions on this field; merge.md §3 dispatches on it.
|
|
94
|
-
- **`remediation`** — the finding's full `Remediation:` text, verbatim. Required for `STRUCTURAL_FIX` (the surgical markdown edit), `metadata update` (the patch description), and `DELETED_EXPORT` rescope (the removal rationale recorded in the `scope.amendments[]` entry). For `NEW_EXPORT` / `MODIFIED_EXPORT` it is informational.
|
|
95
|
-
- **`provenance_completeness: true`** — set on a `NEW_EXPORT` entry whose gap is rule R3 (documented in SKILL.md/`references/` but absent from the provenance-map). Step 3 §0 routes these to §0a regardless of severity.
|
|
96
|
-
5. Set `gap_count` from the total number of translated entries
|
|
97
|
-
6. **Skip to section 5** (Display Change Summary) with the gap-derived manifest
|
|
96
|
+
```bash
|
|
97
|
+
uv run {buildChangeManifestHelper} build --doc-hashes "{run_dir}/doc-hashes.json" > "{run_dir}/change-manifest.json"
|
|
98
|
+
```
|
|
98
99
|
|
|
99
|
-
"
|
|
100
|
+
It writes `{"mode": "docs-only", "no_changes", "changed_urls", "fetch_failed", "counts": {"docs_changed", "docs_fetch_failed"}}`. On exit 1 or no JSON: HALT with status `blocked` (halt procedure: `phase: "detect-changes:change-manifest"`, its stderr as `reason`). §4 then reports no change only when every hashed document matches (`changed_urls` is empty); otherwise §5 routes to step 3, whose §1 re-fetches the changed URLs, and step 5 records their new hashes from `{run_dir}/doc-hashes.json`.
|
|
100
101
|
|
|
101
|
-
**If
|
|
102
|
+
`{source_root}` is the tree init.md §6b prepared at `{target_commit}` when `{source_tree_status}` is `ready` or `offline`, and otherwise the skill's local source. **If `{source_tree_status}` is `ready` or `offline` and `{source_root}` no longer exists**, HALT with status `blocked` per SKILL.md's source-tree rule (halt procedure: `phase: "detect-changes:source-tree-missing"`, `path: "{source_root}"`, `reason: "source tree {source_root} disappeared mid-run"`): reading a missing tree would report every file deleted. §2.1's helpers walk `{source_root}` themselves: build no file inventory here.
|
|
102
103
|
|
|
103
|
-
|
|
104
|
+
**The brief this run reads.** Bind `{brief_path}` ← `{forge_data_folder}/{skill_name}/skill-brief.yaml`. When `detect_only_mode` or `dry_run_mode` is true and that file exists, copy it to `{run_dir}/skill-brief.yaml` and bind `{brief_path}` to the copy: §1b and §1c write each decision there, Category A reads the scope from there, and the brief itself stays as it was. Each amendment a read-only run writes to the copy is a proposed one: add `proposed-amendment: {action} {path} ({category}); not written: {--dry-run or --detect-only}` to `warnings[]` as you write it, and step 6 lists them with the change manifest.
|
|
104
105
|
|
|
105
|
-
|
|
106
|
-
- For each source file: record path, file size, last modified timestamp
|
|
107
|
-
- Focus on file types relevant to the skill (from provenance map file patterns)
|
|
108
|
-
- Exclude non-source files (node_modules, build artifacts, etc.)
|
|
106
|
+
**The scope type this run reads.** Bind `{scope_type}` ← the `scope_type` metadata.json records (init.md §2), else the `scope.type` of `{brief_path}` when that file exists and names one: a skill whose metadata.json records no scope type updates under its brief's. Category B's runner takes it as `--scope-type` and writes it to `{run_dir}/extraction.json` as `scope.type`, where step 3's `records` and step 5's `apply` read it: under `public-api`, a normal-mode update neither documents nor adds to the provenance map a new export off the runner's public surface (re-extract.md §4, write.md §3).
|
|
109
107
|
|
|
110
108
|
### 1b. Discovered Authoritative Files Protocol (Mirror)
|
|
111
109
|
|
|
@@ -113,26 +111,30 @@ Read the source directory at `{source_root}` and build a current file inventory:
|
|
|
113
111
|
|
|
114
112
|
**Skip this section entirely if:**
|
|
115
113
|
|
|
116
|
-
- `
|
|
117
|
-
- `
|
|
114
|
+
- `metadata.json.source_type == "docs-only"` (no source tree to scan), OR
|
|
115
|
+
- `{brief_path}` does not exist (a skill built without a brief, such as a quick skill): there is no scope or amendment to match a document against, and no brief to amend. Display `"Authoritative files mirror: skipped (no skill brief)."`
|
|
118
116
|
|
|
119
|
-
**Procedure (
|
|
117
|
+
**Procedure (the helper and buckets of create-skill §2a):**
|
|
120
118
|
|
|
121
|
-
1. **
|
|
122
|
-
- `llms.txt`, `llms-full.txt`
|
|
123
|
-
- `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, `COPILOT.md`
|
|
124
|
-
- `.cursorrules`, `.windsurfrules`, `.clinerules`
|
|
119
|
+
1. **Scan, classify and hash in one call.** Resolve `{resolveAuthoritativeFilesHelper}` ← first existing path in `{resolveAuthoritativeFilesProbeOrder}` and, from `{project-root}`, run:
|
|
125
120
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
121
|
+
```bash
|
|
122
|
+
uv run {resolveAuthoritativeFilesHelper} resolve \
|
|
123
|
+
--source-root "{source_root}" \
|
|
124
|
+
--brief "{brief_path}" \
|
|
125
|
+
[--provenance-map "{provenance_map_path}"]
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Pass `--provenance-map` whenever init.md §4 found one. It finds the files create-skill §2a looks for (`llms.txt`, `AGENTS.md`, `.cursorrules` and the like), sorts each into a bucket below, and hashes and previews each one a decision needs. Never walk, match or hash these files by hand. On exit 1 (a source root, brief or provenance map it cannot read), no JSON, or no candidate resolves: HALT with status `blocked` (halt procedure: `phase: "detect-changes:authoritative-files"`, `reason: "<its stderr>"`), showing its stderr.
|
|
129
129
|
|
|
130
|
-
|
|
131
|
-
- **`
|
|
132
|
-
- **`
|
|
133
|
-
-
|
|
130
|
+
2. **Apply each bucket:**
|
|
131
|
+
- **`already_tracked[]`:** the provenance map names the file (`entries[]` or `file_entries[]`), so §2 detects its drift. No action here.
|
|
132
|
+
- **`already_in_scope[]`:** the brief's scope already takes the file (a `scope.include` glob matches it, or its latest amendment promoted it), but the map has no row for it. Add the helper's `{path, heuristic, size_bytes, line_count, content_hash}` to `promoted_docs_new[]` (the list [P] Promote below fills), with no prompt, as create-skill §2a does. Display: `"In scope: {path} scheduled for file_entries write."`
|
|
133
|
+
- **`pre_decided[]` with `prior_action: "promoted"`:** a prior run promoted it, but its `file_entries[]` row is missing (e.g. provenance-map was regenerated from source without re-reading amendments). Add it to `promoted_docs_new[]` with the helper's hash, size and line count so §4 merge writes a new `file_entries[]` row. No user prompt: the decision was already made. Display: `"Honoring prior amendment: promoted {path} scheduled for file_entries write."`
|
|
134
|
+
- **`pre_decided[]` with `prior_action: "skipped"`:** a person declined promotion before. Honor the skip silently. No prompt, no action.
|
|
135
|
+
- **`unresolved[]`:** no one has decided the file yet (a `prior_action` of `deferred-headless` marks one a headless run met and left for a person, the legacy `skipped` amendment with reason `headless: no user to prompt` included): continue to the prompt.
|
|
134
136
|
|
|
135
|
-
|
|
137
|
+
3. **Prompt.** For each `unresolved[]` candidate, present the same prompt as create-skill §2a, with the helper's `path`, `line_count`, `size_bytes`, `heuristic` and `preview` as it reported them (never recomputed):
|
|
136
138
|
|
|
137
139
|
```
|
|
138
140
|
**New authoritative file discovered since skill creation**
|
|
@@ -152,36 +154,28 @@ Read the source directory at `{source_root}` and build a current file inventory:
|
|
|
152
154
|
[U] Update — halt this run and return to skf-brief-skill to refine scope
|
|
153
155
|
```
|
|
154
156
|
|
|
155
|
-
|
|
157
|
+
Steps 4 and 5 are the decision protocol for a scope candidate, which §1c follows too, with the differences it names. Every amendment they append goes to `brief.scope.amendments[]` with `path: candidate.path`, `heuristic: {basename}`, `date: {today ISO}` and `workflow: "skf-update-skill"`, and every brief write goes to `{brief_path}` (the run folder's copy in a read-only mode), preserving all other fields.
|
|
156
158
|
|
|
157
|
-
|
|
159
|
+
4. **GATE [default: defer]**: in headless mode (`{headless_mode}` is true), promote nothing and decide nothing: whether a path belongs in scope needs a person. Leave every candidate out of this run and record it as deferred, so the next interactive run asks: append an amendment `action: "deferred-headless"`, `reason: "headless: no user to prompt; left for the next interactive run"`, except for a candidate whose `prior_action` is `deferred-headless`, which a headless run already recorded: write no second amendment for it. A non-interactive update run must never silently add files to scope, and never records a skip no person chose. **Also record one decision per candidate path**, from `{project-root}`:
|
|
158
160
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
5. Display: `"Promoted {path} — brief amended, scheduled as new file_entries row for file_type doc."`
|
|
161
|
+
```bash
|
|
162
|
+
uv run {emitEnvelopeHelper} record --workflow skf-update-skill --run-dir "{run_dir}" --decision <<'SKF_JSON'
|
|
163
|
+
{"gate": "detect-changes.promoted-doc-prompt", "default_action": "S", "taken_action": "deferred-headless", "reason": "headless: no user to prompt; left for the next interactive run", "evidence": {"path": "<candidate.path>"}}
|
|
164
|
+
SKF_JSON
|
|
165
|
+
```
|
|
165
166
|
|
|
166
|
-
|
|
167
|
-
1. Do NOT modify `scope.include`.
|
|
168
|
-
2. Append a `brief.scope.amendments[]` entry: `action: "skipped"`, `path: candidate.path`, `reason: {user-provided or auto: "user declined promotion at update-skill §1b"}`, `heuristic: {basename}`, `date: {today ISO}`, `workflow: "skf-update-skill"`.
|
|
169
|
-
3. **Write the amended brief back to disk** so neither update-skill nor create-skill will re-prompt in future runs.
|
|
170
|
-
4. Display: `"Skipped {path} — decision recorded in amendments."`
|
|
167
|
+
5. **Apply decision:**
|
|
171
168
|
|
|
172
|
-
- **[
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
3. Exit with status `halted-for-brief-refinement`. Change manifest is discarded — no partial writes.
|
|
169
|
+
- **[P] Promote:** append `candidate.path` to `brief.scope.include` as a literal glob, append an amendment `action: "promoted"`, `reason: {user-provided or auto: "discovered post-creation: matched heuristic {basename}"}`, and **write the amended brief back to disk immediately**. Then append the candidate's `{path, heuristic, size_bytes, line_count, content_hash}`, as the helper reported them, to `promoted_docs_new[]`, which this section keeps as the JSON array `{run_dir}/promoted-docs.json` (each record as the helper printed it; `[]` when none): step 5's `apply` writes a new `file_entries[]` row for each (merge Priority 7). Promoted docs do NOT go through step 3's code re-extraction, which would produce ghost entries on non-code files. Display: `"Promoted {path}: brief amended, scheduled as new file_entries row for file_type doc."`
|
|
170
|
+
- **[S] Skip:** leave `scope.include` as it is, append an amendment `action: "skipped"`, `reason: {user-provided or auto: "user declined promotion at update-skill §1b"}`, and write the amended brief back to disk, so neither update-skill nor create-skill re-prompts in a later run. Display: `"Skipped {path}: decision recorded in amendments."`
|
|
171
|
+
- **[U] Update:** HALT with status `halted-for-brief-refinement` (halt procedure: `phase: "detect-changes:authoritative-files"`, `path: "{brief_path}"`, `reason: "the user chose to refine the brief's scope for {path}"`) and display `"Halting update-skill. Re-run skf-brief-skill to refine scope for {skill_name}, then re-run skf-update-skill."`: no partial writes.
|
|
176
172
|
|
|
177
|
-
|
|
173
|
+
6. **Summary.** After all candidates are resolved (or none were found):
|
|
178
174
|
|
|
179
|
-
- `"Authoritative files mirror: {N} candidates, {P} promoted, {S} skipped, {A}
|
|
175
|
+
- `"Authoritative files mirror: {N} candidates, {P} promoted, {S} skipped, {D} deferred to an interactive run, {A} decided by the brief (in scope or amended), {T} already tracked in provenance."`, with N the helper's `summary.candidates_total`, A its `already_in_scope_count` plus `pre_decided_count` and T its `already_tracked_count`.
|
|
180
176
|
- If N = 0: `"Authoritative files mirror: no candidates."`
|
|
181
177
|
|
|
182
|
-
**Record for evidence report:** the
|
|
183
|
-
|
|
184
|
-
**Interaction with §2 change detection:** promoted docs live in `promoted_docs_new[]`, not in the change manifest. §2.0's `change_detection_excludes` set (built below) is what stops §2 Category A from re-classifying them as ADDED — it is the only coordination that survives across the parallel Category A subprocesses.
|
|
178
|
+
**Record for evidence report:** append the record `authoritative_files_mirror: {candidates: N, promoted: P, skipped: S, deferred: D, pre_decided: A, already_tracked: T, decisions: [{path, action, heuristic, reason}]}` to `{run_dir}/evidence-records.jsonl` as one JSON line keyed `authoritative_files_mirror`: step 5 §4 reads it there, never from context.
|
|
185
179
|
|
|
186
180
|
### 1c. Major-Version Scope Reconciliation (Pre-Detection)
|
|
187
181
|
|
|
@@ -189,8 +183,8 @@ Read the source directory at `{source_root}` and build a current file inventory:
|
|
|
189
183
|
|
|
190
184
|
**Skip this section entirely if:**
|
|
191
185
|
|
|
192
|
-
- `update_mode == "gap-driven"` (test-report mode — source hasn't drifted), OR
|
|
193
186
|
- `metadata.json.source_type == "docs-only"` (no source tree to scope), OR
|
|
187
|
+
- `{brief_path}` does not exist (a skill built without a brief, such as a quick skill): there is no scope to expand and no brief to amend. Display `"Scope reconciliation: skipped (no skill brief)."`, OR
|
|
194
188
|
- No audit drift report is available at the path computed in step 1 below.
|
|
195
189
|
|
|
196
190
|
**Procedure:**
|
|
@@ -199,12 +193,14 @@ Read the source directory at `{source_root}` and build a current file inventory:
|
|
|
199
193
|
|
|
200
194
|
```bash
|
|
201
195
|
uv run {provenanceGapDispatchHelper} dispatch \
|
|
202
|
-
--skill-name {skill_name} \
|
|
203
|
-
--baseline-version {baseline_version} \
|
|
204
|
-
--forge-data-folder {forge_data_folder} \
|
|
205
|
-
--brief {
|
|
196
|
+
--skill-name "{skill_name}" \
|
|
197
|
+
--baseline-version "{baseline_version}" \
|
|
198
|
+
--forge-data-folder "{forge_data_folder}" \
|
|
199
|
+
--brief "{brief_path}"
|
|
206
200
|
```
|
|
207
201
|
|
|
202
|
+
On exit 1 (a forge folder it cannot read, or a brief that exists but cannot be read), no JSON, or no candidate resolves: HALT with status `blocked` (halt procedure: `phase: "detect-changes:scope-reconciliation"`, `path: "{brief_path}"`, `reason: "<its stderr>"`).
|
|
203
|
+
|
|
208
204
|
Output envelope:
|
|
209
205
|
|
|
210
206
|
```json
|
|
@@ -220,7 +216,7 @@ Read the source directory at `{source_root}` and build a current file inventory:
|
|
|
220
216
|
| "pre-decided-demoted" | "unresolved",
|
|
221
217
|
"prior_action": "promoted" | "skipped"
|
|
222
218
|
| "demoted-include" | "demoted-exclude"
|
|
223
|
-
| null
|
|
219
|
+
| "deferred-headless" | null
|
|
224
220
|
}, ...
|
|
225
221
|
],
|
|
226
222
|
"summary": {"pre_decided_count": N, "unresolved_count": N}
|
|
@@ -235,7 +231,7 @@ Read the source directory at `{source_root}` and build a current file inventory:
|
|
|
235
231
|
- `status: "already-in-scope"` → skip silently (`prior_action: "promoted"`).
|
|
236
232
|
- `status: "pre-decided-skipped"` → honor silently (`prior_action: "skipped"`).
|
|
237
233
|
- `status: "pre-decided-demoted"` → record as `pre_decided`; do not re-prompt (`prior_action` ∈ `demoted-include`, `demoted-exclude`).
|
|
238
|
-
- `status: "unresolved"` → continue to step
|
|
234
|
+
- `status: "unresolved"` → continue to step 3 (user prompt). A `prior_action` of `deferred-headless` marks a path a headless run deferred (the legacy `skipped` amendment with reason `headless: no user to prompt` reads the same): a person decides it now.
|
|
239
235
|
|
|
240
236
|
**Note:** the Out-of-Scope section is an optional audit-skill output and is often absent or empty — new-file detection is update-skill's job (§1b/§2.2), not audit-skill's — so the `no-report` / `no-candidates` paths above are the common case.
|
|
241
237
|
|
|
@@ -255,95 +251,125 @@ Read the source directory at `{source_root}` and build a current file inventory:
|
|
|
255
251
|
[U] Update — halt this run and return to skf-brief-skill to refine scope
|
|
256
252
|
```
|
|
257
253
|
|
|
258
|
-
4. **
|
|
259
|
-
|
|
260
|
-
5. **Apply decision:**
|
|
261
|
-
|
|
262
|
-
- **[P] Promote:**
|
|
263
|
-
1. Append `candidate.path` to `brief.scope.include` as a literal glob (preserve any wildcards from the drift report).
|
|
264
|
-
2. Append a `brief.scope.amendments[]` entry: `action: "promoted"`, `category: "scope-expansion"`, `path: candidate.path`, `reason: {user-provided or auto: "out-of-scope new public API — drift report {report basename}"}`, `evidence: {evidence string}`, `date: {today ISO}`, `workflow: "skf-update-skill"`.
|
|
265
|
-
3. **Write the amended brief back to disk immediately** at `{forge_data_folder}/{skill_name}/skill-brief.yaml`. Preserve all other fields.
|
|
266
|
-
4. Display: `"Promoted {path} — brief amended; §2 Category A will pick up matching files as ADDED."`
|
|
267
|
-
5. **No `promoted_docs_new[]` entry and no `change_detection_excludes` write** — promoted code globs flow through the standard §2 Category A → §3 extraction path, unlike §1b's promoted docs which bypass extraction.
|
|
254
|
+
4. **GATE [default: defer]**: in headless mode (`{headless_mode}` is true), defer each candidate as §1b step 4 does, with this section's amendment fields (step 5) and the gate `detect-changes.scope-expansion` in its decision record: a non-interactive update run must never silently expand scope.
|
|
268
255
|
|
|
269
|
-
|
|
270
|
-
1. Do NOT modify `scope.include` or `scope.exclude`.
|
|
271
|
-
2. Append a `brief.scope.amendments[]` entry: `action: "skipped"`, `category: "scope-expansion"`, `path: candidate.path`, `reason: {user-provided or auto: "user declined promotion at update-skill §1c"}`, `evidence: {evidence string}`, `date: {today ISO}`, `workflow: "skf-update-skill"`.
|
|
272
|
-
3. **Write the amended brief back to disk** so neither §1c nor a future run will re-prompt.
|
|
273
|
-
4. Display: `"Skipped {path} — decision recorded in amendments."`
|
|
256
|
+
5. **Apply decision** as §1b step 5 does, with these differences:
|
|
274
257
|
|
|
275
|
-
- **[
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
3. Exit with status `halted-for-brief-refinement`. Change manifest is not yet built — no partial writes to provenance.
|
|
258
|
+
- **Amendment fields:** every amendment carries `category: "scope-expansion"` and the candidate's `evidence: {evidence string}` in place of `heuristic`. [P]'s auto reason is "out-of-scope new public API: drift report {report basename}", and [S]'s "user declined promotion at update-skill §1c".
|
|
259
|
+
- **Promotion target:** [P] appends the path to `scope.include` as the drift report gives it, wildcards kept, and adds no `promoted_docs_new[]` entry and no `--exclude`: §2 Category A lists the files a promoted code glob matches as ADDED, and step 3 extracts them, unlike §1b's documents. Display: `"Promoted {path}: brief amended; §2 Category A will pick up matching files as ADDED."`
|
|
260
|
+
- **[S]** leaves `scope.exclude` as it is too, and **[U]** halts with `phase: "detect-changes:scope-reconciliation"`.
|
|
279
261
|
|
|
280
262
|
6. **Summary:** After all candidates are resolved (or none were found):
|
|
281
263
|
|
|
282
|
-
- `"Scope reconciliation: {N} candidates, {P} promoted, {S} skipped, {A} pre-decided from amendments."`
|
|
264
|
+
- `"Scope reconciliation: {N} candidates, {P} promoted, {S} skipped, {D} deferred to an interactive run, {A} pre-decided from amendments."`
|
|
283
265
|
- If N = 0 (section absent or empty): `"Scope reconciliation: no out-of-scope observations in drift report."`
|
|
284
|
-
- If §1c was skipped entirely (no drift report): omit this line; §2.2 will still run.
|
|
266
|
+
- If §1c was skipped entirely (a docs-only skill, no skill brief or no drift report): omit this line; §2.2 will still run.
|
|
285
267
|
|
|
286
|
-
**Record for evidence report:** the
|
|
268
|
+
**Record for evidence report:** append the record `scope_reconciliation_pre: {drift_report: path, candidates: N, promoted: P, skipped: S, deferred: D, pre_decided: A, decisions: [{path, action, evidence}]}` to `{run_dir}/evidence-records.jsonl` as one JSON line keyed `scope_reconciliation_pre` (none when §1c was skipped).
|
|
287
269
|
|
|
288
270
|
### 2. Compare Against Provenance Map
|
|
289
271
|
|
|
290
|
-
**
|
|
272
|
+
**In degraded mode (no provenance map),** run Category A without `--provenance-map`: its `full` mode lists every in-scope file as MODIFIED (with no brief, `--language` scopes the walk), so step 3 re-extracts every export. Run Category B's steps 1 and 2 only (re-extract.md §4's records come from them), and skip its step 3, Category C, Category D and §2.2: there is no map to compare against.
|
|
291
273
|
|
|
292
|
-
#### 2.0
|
|
274
|
+
#### 2.0 Change-Detection Excludes
|
|
293
275
|
|
|
294
|
-
|
|
276
|
+
`promoted_docs_new[]` (§1b) holds documents that reach the provenance map as `file_entries[]` rows through step 4 Priority 7, never through code extraction. Category A leaves each of them out with `--exclude`: each is in scope and the map does not name it yet, so it would read as ADDED, and step 3 would send it to AST extraction, producing ghost entries. A path a `file_entries[]` row already names (a tracked document, script or asset) needs no exclude: Category A never lists one, and Category D compares its hash.
|
|
295
277
|
|
|
296
|
-
|
|
278
|
+
#### 2.1 Categories A, B and C, in Order
|
|
297
279
|
|
|
298
|
-
|
|
299
|
-
- Every source path in `file_entries[].source_file` where `file_type == "doc"` in the existing provenance map. These are already-tracked authoritative docs; any drift in them is handled by Category D (script/asset file changes), not Category A.
|
|
280
|
+
Category B reads the files Category A lists, and Category C pairs what A and B leave deleted and added. Category D (below) needs neither.
|
|
300
281
|
|
|
301
|
-
|
|
282
|
+
**Category A: file-level changes.** Resolve `{classifyChangedFilesHelper}` ← first existing path in `{classifyChangedFilesProbeOrder}` and, from `{project-root}`, run:
|
|
302
283
|
|
|
303
|
-
|
|
284
|
+
```bash
|
|
285
|
+
uv run {classifyChangedFilesHelper} classify \
|
|
286
|
+
--source-root "{source_root}" \
|
|
287
|
+
[--provenance-map "{provenance_map_path}"] \
|
|
288
|
+
[--brief "{brief_path}"] \
|
|
289
|
+
[--language "{language}"] \
|
|
290
|
+
--tree-status "{source_tree_status}" \
|
|
291
|
+
--diff-status "{source_diff_status}" \
|
|
292
|
+
--changed-files "{source_changed_files}" \
|
|
293
|
+
[--exclude "<promoted document path>"] \
|
|
294
|
+
--lists-dir "{run_dir}" \
|
|
295
|
+
> "{run_dir}/category-a.json"
|
|
296
|
+
```
|
|
304
297
|
|
|
305
|
-
|
|
298
|
+
Pass `--provenance-map` whenever init.md §4 found one; `--brief` (as §1c left it) when that file exists (a skill built without a brief, such as a quick skill after its degraded update, has none); `--language` when metadata.json records one; one `--exclude` for each `promoted_docs_new[]` path; and the three init.md §6b values as bound (an empty or `null` value counts as not given). In a tree it takes the changed files from git's list at `{source_changed_files}`, never from file times or sizes, which a checkout rewrites. Add each of its `warnings[]` to `warnings[]`. It writes `{run_dir}/modified-files.json` (the files Category B diffs) and `{run_dir}/extract-files.json` (the modified and added files and each moved file's new path, which Category B and step 3 read): never retype or re-derive a list. On exit 1 (an input it cannot read, named on stderr), no JSON, or no candidate resolves: HALT with status `blocked` (halt procedure: `phase: "detect-changes:category-a"`, the helper's message as `reason`).
|
|
306
299
|
|
|
307
|
-
**Category
|
|
308
|
-
- Files in provenance map but missing from source → DELETED
|
|
309
|
-
- Files in source but not in provenance map AND not in `change_detection_excludes` → ADDED
|
|
310
|
-
- Files in `change_detection_excludes`: skip entirely (routed to file_entries via §1b → step 4 Priority 7, never through Category A)
|
|
311
|
-
- Files in both but with different timestamps/sizes → MODIFIED
|
|
312
|
-
- Files with same content at different paths → MOVED
|
|
300
|
+
**Category B: export-level changes.** Skip it when `{run_dir}/extract-files.json` lists no file.
|
|
313
301
|
|
|
314
|
-
**
|
|
315
|
-
- For each modified file, compare export list against provenance map exports
|
|
316
|
-
- Exports in provenance but not in source → DELETED_EXPORT
|
|
317
|
-
- Exports in source but not in provenance → NEW_EXPORT
|
|
318
|
-
- Exports with changed signatures/types → MODIFIED_EXPORT
|
|
319
|
-
- Exports at different line numbers but same content → MOVED_EXPORT
|
|
302
|
+
1. **The recipe runner.** At Forge tier and above, resolve `{extractPublicApiHelper}` ← first existing path in `{extractPublicApiProbeOrder}` and, from `{project-root}`, run:
|
|
320
303
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
-
|
|
304
|
+
```bash
|
|
305
|
+
uv run {extractPublicApiHelper} --mode full \
|
|
306
|
+
--source-root "{source_root}" \
|
|
307
|
+
--files-from "{run_dir}/extract-files.json" \
|
|
308
|
+
[--language "{language}"] \
|
|
309
|
+
[--scope-type "{scope_type}"] \
|
|
310
|
+
--head-cap 0 \
|
|
311
|
+
-o "{run_dir}/extraction.json"
|
|
312
|
+
```
|
|
324
313
|
|
|
325
|
-
|
|
314
|
+
Pass `--language` when metadata.json records it, and `--scope-type` when §1 bound `{scope_type}`. It takes no `--brief`: Category A applied the scope, and a tracked file the brief's scope leaves out (gap-driven.md §1's rule R1 excludes a rescoped export's file) keeps its exports. `--head-cap 0` keeps every match, since a dropped one would read as a deleted export. Step 3 reads this file and never runs the runner again. **Exit 1** (`incomplete`, its JSON written): run it once more; when still incomplete, step 2 below reads by eye each listed file with no export in `exports[]` or that an `errors[]` item lists in `unread`, and tells the user which files were read by eye. **Exit 2** (an input error, such as a file list it cannot read, or a failure it did not foresee, named in one stderr line with no JSON; `uv` failing to start it exits 2 too): HALT with status `blocked` (halt procedure: `phase: "detect-changes:category-b"`, the stderr line that holds `error:` as `reason`). No by-eye read stands in for it: the skill is not written yet, and reading every file by eye would hide the error. **Exit 3 (`no-ast-grep`), no JSON on another exit, no candidate resolves, or Quick tier:** write `{"exports": []}` to `{run_dir}/extraction.json`, with `"scope": {"type": "{scope_type}"}` when §1 bound `{scope_type}`, so step 5's `apply` can warn that a `public-api` surface was not applied; step 2 reads every listed file by eye (at Quick tier, by text pattern). Tell the user which files were read by eye.
|
|
326
315
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
316
|
+
Then, at every tier (in degraded mode there is no map: skip it), list the map's entries the runner left out that their file still declares: the runner never reports a module, an alias that renames, a dunder such as `__version__` or another underscore name, and step 3's diff would read each as a deleted export. From `{project-root}`, run:
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
uv run {buildChangeManifestHelper} baseline-gaps \
|
|
320
|
+
--provenance-map "{provenance_map_path}" \
|
|
321
|
+
--extraction "{run_dir}/extraction.json" \
|
|
322
|
+
--files "{run_dir}/modified-files.json" \
|
|
323
|
+
--source-root "{source_root}" \
|
|
324
|
+
-o "{run_dir}/baseline-gaps.json"
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
It writes `{"gaps": [{name, file, line, entry, export_type}], "unchecked": [{name, file, export_type}]}`: in `gaps[]` each entry of a modified file that no export of `{run_dir}/extraction.json` holds and whose file still declares it, at the line that declares it, by the verifier's rule (audit-skill's extraction snapshot applies the same one), and in `unchecked[]` each such entry no rule can check (its file neither Python nor TS/JS, a file the rule will not read, such as a test file or one with a symlink on its path, or a dotted module name), which step 2 reads by eye. Never add a gap or drop one by eye. On exit 1 (an input it cannot read, or a `skf-verify-provenance-completeness.py` beside it that cannot be loaded), no JSON, or no candidate resolves: HALT with status `blocked` (halt procedure: `phase: "detect-changes:category-b"`, its stderr as `reason`): without it, every name the runner leaves out would read as deleted.
|
|
328
|
+
|
|
329
|
+
2. **What the recipes do not record.** Workers, in parallel batches (Pattern 4) when the list is long, read the modified and added files and each return ONLY `{"exports": [...]}`, with no prose and no fences. For each function export the runner found whose `params` it left null (it could not read the signature): `export_name` and `source_file` as it wrote them, with `params` (each parameter as the source writes it, `name: type`, the form the provenance map's `params` hold) and `return_type` (null when there is none), read at its `source_line`. For each export it could not find (a name `entry_point_diff.extraction_gaps[]` lists, a name `{run_dir}/baseline-gaps.json` lists in `gaps[]`, a form Known Limitation #11 in `{extractionPatternsData}` lists, an export of a file `file_issues[]` names or that was read by eye): the same, plus `export_type`, `source_line`, `confidence: T1-low` and `extraction_method: source-read`. Record a baseline gap at its `file` and `line` (`source_line`), with, as `export_type`, its `export_type` while the declaration there still has that kind, else the kind it has now (a `variable` now declared by a `def` is a `function`, which step 3 reports as a changed kind), and the declaration's kind when the gap gives none; `params` and `return_type` as the declaration writes them, null when it has none (a module, an alias or a variable). For each `unchecked[]` entry, read its file by eye and record it the same way, at the line that declares it, only when the file still declares it. Record each name and file once: when a file read by eye whole (at Quick tier, or when the runner could not run or left the file unread) holds a name a gap or an `unchecked[]` entry lists, that read's record stands and the gap adds none. Write the union of their `exports` arrays to `{run_dir}/export-details.json`.
|
|
330
|
+
|
|
331
|
+
3. **The diff.** Resolve `{structuralDiffHelper}` ← first existing path in `{structuralDiffProbeOrder}` and, from `{project-root}`, run:
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
uv run {structuralDiffHelper} "{provenance_map_path}" "{run_dir}/extraction.json" \
|
|
335
|
+
--current-extra "{run_dir}/export-details.json" \
|
|
336
|
+
--files "{run_dir}/modified-files.json" \
|
|
337
|
+
-o "{run_dir}/category-b-diff.json"
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Exit 0 (no difference) and exit 1 (differences) both wrote the diff, which §3's helper maps onto Category B: never re-diff by eye. On exit 2, no JSON, or no candidate resolves: HALT with status `blocked` (halt procedure: `phase: "detect-changes:category-b"`, the helper's `error` as `reason`).
|
|
341
|
+
|
|
342
|
+
**Category C: rename detection.** The fixed rules pair Category A's `deleted` files with its `added` ones, and the diff's `removed[]` exports with its `added[]` ones and with the added files' exports, by script, never by eye. From `{project-root}`, run:
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
uv run {buildChangeManifestHelper} rename-candidates \
|
|
346
|
+
--category-a "{run_dir}/category-a.json" \
|
|
347
|
+
[--category-b-diff "{run_dir}/category-b-diff.json"] \
|
|
348
|
+
--provenance-map "{provenance_map_path}" \
|
|
349
|
+
[--extraction "{run_dir}/extraction.json"] \
|
|
350
|
+
[--export-details "{run_dir}/export-details.json"] \
|
|
351
|
+
--tier "{forge_tier}" \
|
|
352
|
+
--source-root "{source_root}" \
|
|
353
|
+
[--sizes-commit "{source_commit}"] \
|
|
354
|
+
-o "{run_dir}/category-c.json"
|
|
334
355
|
```
|
|
335
356
|
|
|
336
|
-
|
|
357
|
+
Pass `--category-b-diff`, `--extraction` and `--export-details` when Category B wrote them, and `--sizes-commit` only when `{source_tree_status}` is `ready` or `offline`: in that tree the helper reads a deleted file's size at the pinned commit (`git cat-file -s`), while in a local source the file is gone and the size test is skipped. Skip Category C in degraded mode (there is no map). Content similarity above 80% (fixed, not configurable) is a rename: at Quick tier a file size within 20% and export names that overlap above 70% (export names above 80% alike), at Forge and above equal export signatures. The helper keeps a pair only when it is each side's best match, so a tie pairs nothing, and writes `category_c` (`renamed_files: [{old_path, new_path}]`, `renamed_exports: [{old_name, new_name, file, old_file?}]`, `old_file` only when it differs from `file`), the `evidence` of each pair and what it left `unpaired`. On exit 1, no JSON, or no candidate resolves: HALT with status `blocked` (halt procedure: `phase: "detect-changes:category-c"`, its stderr as `reason`). Category A's `moved_files[]` (same-content moves) are not Category C's to pair, and §3's helper takes each pair out of the lists it was found in.
|
|
337
358
|
|
|
338
|
-
**Category
|
|
359
|
+
**CCC check (Forge+ and Deep, a local source only).** When `tools.ccc` is true and `{source_tree_status}` is neither `ready` nor `offline` (the tree init.md §6b prepared has no ccc index, and a search there could start indexing a folder step 7 deletes), you may pair what the rules left in `unpaired`: a deleted file with an added one that CCC ranks as the same code (`ccc_bridge.search` over the deleted file's export names; **Tool resolution:** `/ccc` skill search, ccc MCP or `ccc search`). This is the one judgment in Category C: add a pair only on CCC's evidence, name it in the report as a CCC pairing, and never undo a pair the rules made. Write only these pairs, with each path as `unpaired` gives it, to `{run_dir}/ccc-pairs.json` as `{"renamed_files": [{"old_path": "<deleted>", "new_path": "<added>"}]}`; the helpers below read `{run_dir}/category-c.json` themselves, so never copy its pairs.
|
|
339
360
|
|
|
340
|
-
|
|
361
|
+
**Category D: script, asset and tracked-document file changes.**
|
|
362
|
+
|
|
363
|
+
Run the bulk comparison once, keeping its output in the run folder (step 5's `apply` reads it):
|
|
341
364
|
|
|
342
365
|
```bash
|
|
343
|
-
uv run {hashContentHelper} compare
|
|
344
|
-
--provenance-map
|
|
366
|
+
uv run {hashContentHelper} compare "{source_root}" \
|
|
367
|
+
--provenance-map "{provenance_map_path}" \
|
|
368
|
+
> "{run_dir}/category-d-compare.json"
|
|
345
369
|
```
|
|
346
370
|
|
|
371
|
+
On exit 1, no JSON, or no candidate resolves: HALT with status `blocked` (halt procedure: `phase: "detect-changes:category-d"`, its stderr as `reason`).
|
|
372
|
+
|
|
347
373
|
The helper emits:
|
|
348
374
|
|
|
349
375
|
```json
|
|
@@ -357,43 +383,51 @@ The helper emits:
|
|
|
357
383
|
}
|
|
358
384
|
```
|
|
359
385
|
|
|
360
|
-
|
|
361
|
-
- `MODIFIED_FILE` rows → add to manifest as MODIFIED_FILE
|
|
362
|
-
- `DELETED_FILE` rows → add to manifest as DELETED_FILE
|
|
363
|
-
- `UNCHANGED` rows → omit from the manifest (no action needed)
|
|
386
|
+
A map with no `file_entries` field, or a null one (a skill with no script, asset or promoted document, or a map written before the field existed), tracks no file: the comparison lists no row and the set difference below subtracts nothing.
|
|
364
387
|
|
|
365
|
-
The compare helper reports only tracked files; NEW_FILE detection (a file present in source but absent from the provenance map) is a set-difference, so it runs through a script rather than the prompt.
|
|
388
|
+
The compare helper reports only tracked files; NEW_FILE detection (a file present in source but absent from the provenance map) is a set-difference, so it runs through a script rather than the prompt. **When `{brief_path}` does not exist** (a skill built without a brief, such as a quick skill), skip it: with no scope to keep to, the walk would read every script and asset of the repository as new. Add `new-files-not-checked: no skill brief, so no new script or asset was looked for` to `warnings[]`, write no `{run_dir}/new-files.json`, and leave `--new-files` out of §3's `build` and step 5's `apply`. Otherwise pipe the detector create-skill step 3 §4c runs (resolved via `detectScriptsAssetsProbeOrder`), which walks the whole source, into `{newFileDiffHelper}`, which keeps what the brief takes and subtracts what the map tracks:
|
|
366
389
|
|
|
367
390
|
```bash
|
|
368
|
-
uv run {detectScriptsAssetsHelper} detect
|
|
369
|
-
| uv run {newFileDiffHelper} {
|
|
391
|
+
uv run {detectScriptsAssetsHelper} detect "{source_root}" \
|
|
392
|
+
| uv run {newFileDiffHelper} "{provenance_map_path}" [--brief "{brief_path}"] \
|
|
393
|
+
> "{run_dir}/new-files.json"
|
|
370
394
|
```
|
|
371
395
|
|
|
372
|
-
|
|
396
|
+
Pass `--brief` as Category A does, `{brief_path}` as §1c left it (in a read-only run, its copy in `{run_dir}`). On exit 2 (the detector's JSON, the map or the brief it cannot read, named in its JSON `error` on stderr, which also catches a detector that printed nothing), no JSON, or no candidate resolves: HALT with status `blocked` (halt procedure: `phase: "detect-changes:category-d"`, its `error` as `reason`).
|
|
373
397
|
|
|
374
|
-
|
|
398
|
+
It writes `{"new_files":[{source_file, kind}], "skipped_manual":[...], "already_tracked":[...], "tracked_code":[...], "out_of_scope":[...], "intent_none":[...], "stats":{...}}`. §3's `build` reads both files, typing each compare row by its `file_entries[]` row's `file_type` and each new file by its `kind`: sort no row by hand. `skipped_manual[]` are user-authored files under `scripts/[MANUAL]/` or `assets/[MANUAL]/`, preserved and not touched; `already_tracked[]` were handled by the compare above; `tracked_code[]` are code files the map's `entries[]` cite, which Categories A and B compare; `out_of_scope[]` are paths the brief's scope leaves out, by Category A's in-scope test; `intent_none[]` are paths of a kind whose `scripts_intent` or `assets_intent` is `none` (an absent or free-text intent detects). A path in these lists is no new file and no `stats` count, so step 6's report counts each of the three lists from this file, where it shows Category D: a brief scoped wrongly shows as many paths out of scope. A new file Category A lists too stays a new file: Category A lists every changed in-scope file, a genuine script included.
|
|
375
399
|
|
|
376
|
-
**
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
400
|
+
**Write the category JSON** to `{run_dir}/categories.json`, where §2.2 and §3 read it beside the helper files: the two flags, `degraded_mode` and `update_mode: "normal"`. Category C stays in the files its steps wrote, which §2.2, §3 and step 5's `apply` take as `--category-c` and `--ccc-pairs`:
|
|
401
|
+
|
|
402
|
+
```bash
|
|
403
|
+
cat > "{run_dir}/categories.json" <<'SKF_JSON'
|
|
404
|
+
{"degraded_mode": <bool>, "update_mode": "normal"}
|
|
405
|
+
SKF_JSON
|
|
406
|
+
```
|
|
380
407
|
|
|
381
408
|
#### 2.2 — Major-Version Scope Reconciliation (Post-Detection)
|
|
382
409
|
|
|
383
410
|
**Purpose:** §1c catches the major-version case when an audit drift report supplies explicit candidates. §2.2 is the safety net that fires when no audit was run (or audit emitted no out-of-scope section): it inspects the just-built Category A/B results for the deletion-ratio signature of a major-version restructure and gives the user an off-ramp before §3 commits the change manifest.
|
|
384
411
|
|
|
385
|
-
**Trigger computation:** invoke the helper with the
|
|
412
|
+
**Trigger computation:** skip §2.2 in degraded mode (there is no map). Otherwise invoke the helper with the files §3 reads, plus the provenance map:
|
|
386
413
|
|
|
387
414
|
```bash
|
|
388
|
-
|
|
389
|
-
--provenance-map {
|
|
415
|
+
uv run {buildChangeManifestHelper} deletion-ratio \
|
|
416
|
+
--provenance-map "{provenance_map_path}" \
|
|
417
|
+
--category-a "{run_dir}/category-a.json" \
|
|
418
|
+
[--category-b-diff "{run_dir}/category-b-diff.json"] \
|
|
419
|
+
[--category-c "{run_dir}/category-c.json"] \
|
|
420
|
+
[--ccc-pairs "{run_dir}/ccc-pairs.json"] \
|
|
421
|
+
--input "{run_dir}/categories.json"
|
|
390
422
|
```
|
|
391
423
|
|
|
392
|
-
|
|
424
|
+
Pass `--category-b-diff`, `--category-c` and `--ccc-pairs` when Category B, Category C and the CCC check wrote them.
|
|
425
|
+
|
|
426
|
+
The helper handles the two skip conditions internally: when the input has `degraded_mode: true`, or the provenance has zero entries, it returns `skip_reason` set and `should_trigger: false`. `should_trigger` fires when the deletion ratio reaches the fixed bundled threshold of 50% (`ratio >= 0.50`, baked into the helper, not configurable). The output envelope:
|
|
393
427
|
|
|
394
428
|
```json
|
|
395
429
|
{
|
|
396
|
-
"skip_reason": "
|
|
430
|
+
"skip_reason": "degraded-mode" | "zero-provenance-exports" | null,
|
|
397
431
|
"deleted_export_count": N,
|
|
398
432
|
"total_provenance_exports": N,
|
|
399
433
|
"deletion_ratio": 0.X,
|
|
@@ -424,37 +458,37 @@ The upstream surface appears to have been substantially replaced. The brief's
|
|
|
424
458
|
[A] Audit — halt and run skf-audit-skill to map the new surface, then re-run update-skill
|
|
425
459
|
```
|
|
426
460
|
|
|
427
|
-
**
|
|
461
|
+
**GATE [default: C]**: in headless mode (`{headless_mode}` is true), auto-select `[C] Continue`, append the record `scope_reconciliation_post: {trigger: "deletion-ratio", ratio: X, decision: "headless-continue"}` to `{run_dir}/evidence-records.jsonl` as one JSON line keyed `scope_reconciliation_post` (step 5 §4 lists it in the evidence report), and surface the warning in step 6's report. A non-interactive run must not silently halt, but the user must be able to see the signal post-hoc. **Also record the decision**, from `{project-root}`:
|
|
462
|
+
|
|
463
|
+
```bash
|
|
464
|
+
uv run {emitEnvelopeHelper} record --workflow skf-update-skill --run-dir "{run_dir}" --decision <<'SKF_JSON'
|
|
465
|
+
{"gate": "detect-changes.deletion-ratio", "default_action": "C", "taken_action": "C", "reason": "headless: deletion-ratio threshold exceeded but no user to halt", "evidence": {"deletion_ratio": <ratio>, "deleted_export_count": <N>, "total_provenance_exports": <T>}}
|
|
466
|
+
SKF_JSON
|
|
467
|
+
```
|
|
428
468
|
|
|
429
469
|
**Apply decision:**
|
|
430
470
|
|
|
431
|
-
- **[C] Continue:** record `scope_reconciliation_post: {trigger: "deletion-ratio", ratio: X, decision: "continue"}` and proceed to §3.
|
|
432
|
-
- **[B] Brief:**
|
|
433
|
-
- **[A] Audit:**
|
|
471
|
+
- **[C] Continue:** unless the headless branch above appended its record, append `scope_reconciliation_post: {trigger: "deletion-ratio", ratio: X, decision: "continue"}` to `{run_dir}/evidence-records.jsonl` the same way, and proceed to §3.
|
|
472
|
+
- **[B] Brief:** HALT with status `halted-for-brief-refinement` (halt procedure: `phase: "detect-changes:deletion-ratio"`, `reason: "the user chose to refine the brief's scope: {deleted_export_count} of {total_provenance_exports} exports deleted"`). Display: `"Halting update-skill. Re-run skf-brief-skill to refine scope for {skill_name}, then re-run skf-update-skill."` The change manifest is discarded: no partial writes.
|
|
473
|
+
- **[A] Audit:** HALT with status `halted-for-audit` (halt procedure: `phase: "detect-changes:deletion-ratio"`, `reason: "the user chose to audit the new surface first"`). Display: `"Halting update-skill. Run skf-audit-skill against {skill_name} to map the new surface: its drift report will feed §1c on the next update-skill run."` The change manifest is discarded.
|
|
434
474
|
|
|
435
475
|
### 3. Build Change Manifest
|
|
436
476
|
|
|
437
|
-
Hand the
|
|
477
|
+
Hand the helper files and the category JSON to the helper, which takes Category A from the classify output, maps the diff onto Category B, and takes each Category C pair out of the lists it was found in:
|
|
438
478
|
|
|
439
479
|
```bash
|
|
440
|
-
|
|
480
|
+
uv run {buildChangeManifestHelper} build \
|
|
481
|
+
[--category-a "{run_dir}/category-a.json"] \
|
|
482
|
+
[--category-b-diff "{run_dir}/category-b-diff.json"] \
|
|
483
|
+
[--category-c "{run_dir}/category-c.json"] \
|
|
484
|
+
[--ccc-pairs "{run_dir}/ccc-pairs.json"] \
|
|
485
|
+
[--file-compare "{run_dir}/category-d-compare.json" --provenance-map "{provenance_map_path}"] \
|
|
486
|
+
[--new-files "{run_dir}/new-files.json"] \
|
|
487
|
+
--input "{run_dir}/categories.json" \
|
|
488
|
+
> "{run_dir}/change-manifest.json"
|
|
441
489
|
```
|
|
442
490
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
```json
|
|
446
|
-
{
|
|
447
|
-
"category_a": {"modified": [...], "added": [...], "deleted": [...]},
|
|
448
|
-
"category_b": {"modified_exports": [...], "new_exports": [...],
|
|
449
|
-
"deleted_exports": [...], "moved_exports": [...]},
|
|
450
|
-
"category_c": {"renamed_files": [...], "renamed_exports": [...]},
|
|
451
|
-
"category_d": {"scripts_modified": [...], "scripts_added": [...],
|
|
452
|
-
"scripts_deleted": [...], "assets_modified": [...],
|
|
453
|
-
"assets_added": [...], "assets_deleted": [...]},
|
|
454
|
-
"degraded_mode": <bool>,
|
|
455
|
-
"update_mode": "normal" | "gap-driven"
|
|
456
|
-
}
|
|
457
|
-
```
|
|
491
|
+
Pass each helper file its category wrote. On exit 1 or no JSON: HALT with status `blocked` (halt procedure: `phase: "detect-changes:change-manifest"`, its stderr as `reason`).
|
|
458
492
|
|
|
459
493
|
The helper emits the unified manifest envelope:
|
|
460
494
|
|
|
@@ -467,21 +501,23 @@ The helper emits the unified manifest envelope:
|
|
|
467
501
|
"exports_modified": N, "exports_new": N, "exports_deleted": N,
|
|
468
502
|
"exports_renamed": N, "exports_moved": N,
|
|
469
503
|
"scripts_modified": N, "scripts_added": N, "scripts_deleted": N,
|
|
470
|
-
"assets_modified": N, "assets_added": N, "assets_deleted": N
|
|
504
|
+
"assets_modified": N, "assets_added": N, "assets_deleted": N,
|
|
505
|
+
"docs_modified": N, "docs_deleted": N
|
|
471
506
|
},
|
|
472
507
|
"total_export_changes": N,
|
|
473
508
|
"per_file": [
|
|
474
509
|
{"file_path": "...", "status": "MODIFIED|ADDED|DELETED|MOVED",
|
|
475
|
-
"exports_affected": [{name, change_type, old_line, new_line}, ...]}
|
|
476
|
-
]
|
|
510
|
+
"exports_affected": [{name, change_type, old_line, new_line, old_file?}, ...]}
|
|
511
|
+
],
|
|
512
|
+
"category_d": {"scripts_modified": [...], ..., "docs_deleted": [...]}
|
|
477
513
|
}
|
|
478
514
|
```
|
|
479
515
|
|
|
480
|
-
`per_file` entries are sorted MODIFIED → ADDED → DELETED → MOVED, then alphabetically within each status group, so downstream stages can rely on stable ordering. MOVED entries include an extra `old_path` field.
|
|
516
|
+
`per_file` entries are sorted MODIFIED → ADDED → DELETED → MOVED, then alphabetically within each status group, so downstream stages can rely on stable ordering. MOVED entries include an extra `old_path` field, and a `MOVED_EXPORT` across files an `old_file`, the file it left, where `apply` finds its entry. `category_d` lists the paths behind the `scripts_*`, `assets_*` and `docs_*` counts: a `docs_*` path is a tracked document, which only Category D detects, so a change to one alone is a change. `{run_dir}/change-manifest.json` is the change manifest: step 3, step 6 and step 5's `apply` read it there, so it survives a compacted context.
|
|
481
517
|
|
|
482
518
|
### 4. Check for No-Change Shortcut
|
|
483
519
|
|
|
484
|
-
**If zero changes detected across all categories
|
|
520
|
+
**If zero changes detected across all categories** (`no_changes` is true in `{run_dir}/change-manifest.json`; for a docs-only skill, when every hashed document matched):
|
|
485
521
|
|
|
486
522
|
"**No changes detected.** Source code matches provenance map exactly.
|
|
487
523
|
|
|
@@ -489,23 +525,17 @@ The skill `{skill_name}` is current — no update needed.
|
|
|
489
525
|
|
|
490
526
|
**Skipping to report step...**"
|
|
491
527
|
|
|
492
|
-
→ Skip steps
|
|
528
|
+
→ Skip steps 3-5, immediately load {noChangeReportFile} with "no changes" status. A `--detect-only` or `--dry-run` run takes this route too and stays read-only there: report.md §1 prints its line with no result file and never runs `{onCompleteCommand}`.
|
|
493
529
|
|
|
494
530
|
### 5. Display Change Summary and Route
|
|
495
531
|
|
|
496
|
-
"**
|
|
532
|
+
Display one line from the change manifest: "**Detected:** {modified} modified, {added} added, {deleted} deleted and {moved} moved or renamed files; {total_export_changes} exports affected." The report (step 6) shows the full counts.
|
|
497
533
|
|
|
498
|
-
|
|
499
|
-
|----------|-------|
|
|
500
|
-
| Files modified | {count} |
|
|
501
|
-
| Files added | {count} |
|
|
502
|
-
| Files deleted | {count} |
|
|
503
|
-
| Files moved/renamed | {count} |
|
|
504
|
-
| Exports affected | {total_export_changes} |"
|
|
534
|
+
For a docs-only skill, show instead the documents that changed (`changed_urls`) and the ones that could not be fetched.
|
|
505
535
|
|
|
506
536
|
This step auto-proceeds — no user choices. Once the change manifest is fully built, load and fully read the next file, then execute it, per the branch that applies:
|
|
507
537
|
|
|
508
|
-
- **`detect_only_mode == true`** → display "**Detect-only mode
|
|
538
|
+
- **`detect_only_mode == true`** → display "**Detect-only mode: skipping re-extract, merge and write.** Loading report..." and load `{noChangeReportFile}` (report.md), which emits status `detect-only`. Do not load `{nextStepFile}`.
|
|
509
539
|
- **No changes detected** (section 4) → load `{noChangeReportFile}` (report.md), which emits status `no-changes`.
|
|
510
|
-
- **Otherwise** → display "**Proceeding to re-extraction of {affected_file_count
|
|
540
|
+
- **Otherwise** → display "**Proceeding to re-extraction of {affected_file_count} changes...**" and load `{nextStepFile}` (re-extract.md) to begin re-extraction.
|
|
511
541
|
|