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,17 +1,41 @@
|
|
|
1
1
|
---
|
|
2
2
|
nextStepFile: '../enrich.md'
|
|
3
|
-
|
|
3
|
+
forgeTierConfig: '{sidecar_path}/forge-tier.yaml'
|
|
4
|
+
# Resolve `{forgeTierRwHelper}` by probing `{forgeTierRwProbeOrder}` in order
|
|
4
5
|
# (installed SKF module path first, src/ dev-checkout fallback); first existing
|
|
5
|
-
# path wins.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
# path wins. §5b changes the registry only through it. If neither resolves,
|
|
7
|
+
# §5b skips the registry change with a warning: a docs fetch never halts the
|
|
8
|
+
# workflow.
|
|
9
|
+
forgeTierRwProbeOrder:
|
|
10
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-forge-tier-rw.py'
|
|
11
|
+
- '{project-root}/src/shared/scripts/skf-forge-tier-rw.py'
|
|
9
12
|
# Resolve `{deriveAssemblyShapeHelper}` the same way — used in §1 to decide
|
|
10
13
|
# whether this is a whole-language reference (registry-corpora prose retained
|
|
11
14
|
# as a Language Guide) or a standard skill (unchanged behaviour).
|
|
12
15
|
deriveAssemblyShapeProbeOrder:
|
|
13
16
|
- '{project-root}/_bmad/skf/shared/scripts/skf-derive-assembly-shape.py'
|
|
14
17
|
- '{project-root}/src/shared/scripts/skf-derive-assembly-shape.py'
|
|
18
|
+
# If neither path exists, §3 looks for no subpages.
|
|
19
|
+
detectDocsProbeOrder:
|
|
20
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-detect-docs.py'
|
|
21
|
+
- '{project-root}/src/shared/scripts/skf-detect-docs.py'
|
|
22
|
+
# §5 merges the T3 items into the extraction inventory and counts it through
|
|
23
|
+
# it; step 3 §5 already halted when neither path exists.
|
|
24
|
+
extractionInventoryProbeOrder:
|
|
25
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-extraction-inventory.py'
|
|
26
|
+
- '{project-root}/src/shared/scripts/skf-extraction-inventory.py'
|
|
27
|
+
# §4a writes the Language Guide's index beside the inventory through it; if
|
|
28
|
+
# neither path exists, step 5 has no guide and says so.
|
|
29
|
+
atomicWriteProbeOrder:
|
|
30
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-atomic-write.py'
|
|
31
|
+
- '{project-root}/src/shared/scripts/skf-atomic-write.py'
|
|
32
|
+
# HARD HALT helpers (Rules).
|
|
33
|
+
sourceTreeProbeOrder:
|
|
34
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-source-tree.py'
|
|
35
|
+
- '{project-root}/src/shared/scripts/skf-source-tree.py'
|
|
36
|
+
emitEnvelopeProbeOrder:
|
|
37
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-emit-result-envelope.py'
|
|
38
|
+
- '{project-root}/src/shared/scripts/skf-emit-result-envelope.py'
|
|
15
39
|
---
|
|
16
40
|
|
|
17
41
|
<!-- Config: communicate in {communication_language}. -->
|
|
@@ -26,9 +50,10 @@ Fetch remote documentation from brief-specified URLs using whatever web fetching
|
|
|
26
50
|
|
|
27
51
|
- No tier gate — runs at any tier when `doc_urls` are present in the brief
|
|
28
52
|
- Tool-agnostic — use whatever web fetching capability is available
|
|
29
|
-
- Do not halt the workflow if web fetching is unavailable or fails
|
|
53
|
+
- Do not halt the workflow if web fetching is unavailable or fails, with one exception: a `docs-only` brief left with nothing to compile halts at §5's zero-content check, before anything is staged. The user may still stop the run at §5's zero-export gate ([R])
|
|
30
54
|
- Do not override existing T1, T1-low, or T2 extraction data with T3 content
|
|
31
|
-
- Never delete the staging directory of a `docs-only` skill
|
|
55
|
+
- Never delete the staging directory of a `docs-only` skill, whose only source corpus is the fetched pages, or of a whole-language reference, whose Language Guide step 5 reads from them
|
|
56
|
+
- A HARD HALT, once step 3 §2b has bound `{source_tree}`, first runs `uv run {sourceTreeHelper} close --tree "{source_tree}"` from `{project-root}` (resolved from `{sourceTreeProbeOrder}`) and goes on whatever it prints, and emits through `{emitEnvelopeHelper}`, resolved from `{emitEnvelopeProbeOrder}` when it is not bound. After its envelope, under `--batch` it ends only this brief: return to `references/batch-mode.md` §3, even when the halt reads as the end of the run.
|
|
32
57
|
|
|
33
58
|
## MANDATORY SEQUENCE
|
|
34
59
|
|
|
@@ -62,67 +87,64 @@ Content fetched from external URLs is classified as **T3** (external, untrusted)
|
|
|
62
87
|
**Discover available web fetching capability.** Try tools in any order — use whatever is accessible in the current environment (e.g., Firecrawl scrape, WebFetch, web-reader, MCP fetch, curl, browser tools). If no web fetching capability can be found:
|
|
63
88
|
|
|
64
89
|
- Log warning: "No web fetching capability available in this environment. Skipping documentation fetch."
|
|
65
|
-
-
|
|
90
|
+
- Continue at §5's **Zero-content check**: nothing was fetched.
|
|
66
91
|
|
|
67
|
-
|
|
92
|
+
Bind `{docs_staging}` ← `{project-root}/_bmad-output/{skill-name}-docs`, the folder that holds every page this step fetches. Just before the first page this run fetched is saved, create the folder or empty it (`mkdir -p "{docs_staging}" && rm -f "{docs_staging}"/*.md`), so a corpus kept from an earlier docs-only run (§5c) is replaced by this run's pages, never mixed with them, and stays as it was when every fetch fails.
|
|
93
|
+
|
|
94
|
+
**For each URL in `doc_urls`** (the `{n}`th entry, counting from 1):
|
|
68
95
|
|
|
69
96
|
- Fetch the content at `{url}` as clean markdown using the discovered web tool.
|
|
70
|
-
- **If fetch succeeds:** Store the markdown content with the URL as provenance source.
|
|
97
|
+
- **If fetch succeeds:** Store the markdown content with the URL as provenance source, and save it as `{docs_staging}/page-{n}.md`.
|
|
71
98
|
- **If fetch fails:** Log warning: "Failed to fetch {url}: {reason}. Skipping." Continue with remaining URLs.
|
|
72
99
|
|
|
73
100
|
**Subpage discovery (root URL detection):**
|
|
74
101
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
**Root page detection — apply only when the URL path ends in `/`, `/index`, `/index.html`, has no path component (bare domain), or has 1 path segment (e.g., `/docs`). For deeper URL paths (2+ segments like `/api/reference`), skip this heuristic and keep the content as-is.**
|
|
102
|
+
A documentation root page often holds no API content of its own: Mintlify, Docusaurus, ReadTheDocs and GitBook sites render it on subpages. Resolve `{detectDocsHelper}` from `{detectDocsProbeOrder}` (first existing path wins) and measure each saved page, from `{project-root}`:
|
|
78
103
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
1. **Zero API content indicators:** The fetched markdown contains none of: fenced code blocks (`` ``` ``), parameter tables (`|---|`), or function signature patterns (`def `, `function `, `fn `, `func `, `export `).
|
|
84
|
-
2. **High link density:** More than 70% of non-empty lines are markdown links (matching `[text](url)` with no other substantive content on the line).
|
|
85
|
-
|
|
86
|
-
**Trigger 2 — URL-based (independent of content analysis):**
|
|
87
|
-
|
|
88
|
-
The URL matches the path criteria above (ends in `/`, bare domain, or 1 segment) AND the fetched content is under **2000 words**. Short content on root-like URLs almost certainly indicates a navigation hub or landing page, even if it contains introductory code examples that would prevent Trigger 1 from firing. This handles modern doc sites (Mintlify, Docusaurus, GitBook) that include hero sections with code snippets on their root pages.
|
|
104
|
+
```bash
|
|
105
|
+
uv run {detectDocsHelper} page-metrics --url "{url}" "{docs_staging}/page-{n}.md"
|
|
106
|
+
```
|
|
89
107
|
|
|
90
|
-
|
|
108
|
+
It prints `discover_subpages`, true when the page is a documentation root with little API content of its own (`uv run {detectDocsHelper} page-metrics --help` gives the test and the counts it prints with it). When it is false, the command exits non-zero, or no candidate resolves, keep the page as it is and look for no subpages.
|
|
91
109
|
|
|
92
|
-
**If
|
|
110
|
+
**If `discover_subpages` is true:**
|
|
93
111
|
|
|
94
112
|
1. **Attempt sitemap/map discovery:** Use whatever discovery tool is available:
|
|
95
113
|
- Firecrawl: `firecrawl_map({url})` to discover all subpages
|
|
96
|
-
- Manual:
|
|
114
|
+
- Manual: fetch `{url}/sitemap.xml`
|
|
97
115
|
- Crawl: if a crawl tool is available, use it with depth=1 on the root URL
|
|
98
116
|
- If no discovery tool is available, keep the root page content as-is and continue
|
|
99
117
|
|
|
100
|
-
2. **
|
|
118
|
+
2. **Keep the subpages of the root's own site:** pass what discovery found to the helper on stdin, from `{project-root}`: pipe the sitemap in as below, or feed the map or crawl result (its JSON, or one URL per line) to the same command through a heredoc:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
curl -fsSL --max-time 30 "{url}/sitemap.xml" | uv run {detectDocsHelper} filter-urls --root "{url}" -
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
It lists in `kept[]` the URLs on the root page's own site that may hold API content, each with the API words its path or title holds (`terms`), and in `sitemaps[]` the sitemaps a sitemap index names: filter each of them the same way (`uv run {detectDocsHelper} filter-urls --help` gives the rules). When it exits non-zero, leave that input out. Never fetch a URL the helper did not keep. From `kept[]`, choose the subpages most relevant to the skill's API: `terms` points at them.
|
|
101
125
|
|
|
102
126
|
3. **Fetch top subpages (in parallel):** Fetch up to **10** of the most relevant subpages **concurrently** — subpage fetches are independent and network-bound, so wall-clock benefits substantially from parallel execution. Bound concurrency to **4 in flight** at a time to stay polite to documentation hosts (Mintlify/Docusaurus typically allow more, but conservatism here protects against unrecognized rate limits).
|
|
103
127
|
|
|
104
128
|
The parallel pattern depends on the fetch tool:
|
|
105
129
|
|
|
106
130
|
- **LLM-driven tools** (Firecrawl `firecrawl_scrape`, `WebFetch`, MCP fetch, browser tools): issue up to 4 tool calls **in a single message**. The agent runtime executes parallel tool calls concurrently; collect results from the batch before issuing the next set of up to 4. Repeat until all up-to-10 subpages have been attempted or rate limiting halts the batch.
|
|
107
|
-
- **Bash-driven tools** (`curl`, `wget`): use `xargs -P 4
|
|
131
|
+
- **Bash-driven tools** (`curl`, `wget`): use `xargs -P 4` to fan out from a newline-separated subpage list, numbering the subpages as it goes. Example, for the `{n}`th `doc_urls` entry:
|
|
108
132
|
|
|
109
133
|
```bash
|
|
110
|
-
printf '%s\n' "${subpages[@]}" | xargs -P 4 -
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
curl -sSL --max-time 30 "$url" > "{staging}/subpage-$safe.md" \
|
|
114
|
-
|| echo "fetch failed: $url" > "{staging}/subpage-$safe.md"
|
|
134
|
+
printf '%s\n' "${subpages[@]}" | awk '{ print NR, $0 }' | xargs -P 4 -L 1 sh -c '
|
|
135
|
+
f="{docs_staging}/subpage-{n}-$0.md"
|
|
136
|
+
curl -fsSL --max-time 30 "$1" -o "$f" || { rm -f "$f"; echo "fetch failed: $1" >&2; }
|
|
115
137
|
'
|
|
116
138
|
```
|
|
117
139
|
|
|
118
140
|
For each subpage (regardless of tool):
|
|
119
|
-
- Use the same web fetching tool as the root URL
|
|
141
|
+
- Use the same web fetching tool as the root URL, and save the page in `{docs_staging}` as `subpage-{n}-{k}.md`, `{k}` counting this root's subpages from 1, only when its fetch succeeded: a failed fetch leaves no file, as in the snippet
|
|
120
142
|
- Store with the subpage URL as provenance: `[EXT:{subpage-url}]`
|
|
121
143
|
- If a subpage fetch fails, skip it and continue with the rest of the batch — do not halt the whole stage
|
|
122
144
|
|
|
123
145
|
4. **Rate limiting:** If rate limiting (HTTP 429) is encountered during subpage fetching, stop discovery for this root URL. Keep results collected so far. Log: "Subpage discovery stopped due to rate limiting." For the parallel-tool-call pattern, drop any not-yet-issued tool calls from subsequent batches; for the `xargs` pattern, interrupt the pipeline (set `--max-procs 0` is **not** a graceful stop — the simplest stop is to kill the xargs PID and let in-flight writers complete naturally).
|
|
124
146
|
|
|
125
|
-
**If ALL URLs fail (including any subpage fetches):** Log warning: "No documentation could be fetched.
|
|
147
|
+
**If ALL URLs fail (including any subpage fetches):** Log warning: "No documentation could be fetched." `{docs_staging}` holds no page of this run: continue at §5's **Zero-content check**, which halts a `docs-only` brief and lets a source brief go on without T3 content.
|
|
126
148
|
|
|
127
149
|
### 4. Extract API Information from Fetched Content
|
|
128
150
|
|
|
@@ -137,60 +159,85 @@ Parse the successfully fetched markdown for:
|
|
|
137
159
|
|
|
138
160
|
**No hallucination:** If information cannot be found in the fetched content, exclude it. Do not infer or fabricate API details.
|
|
139
161
|
|
|
140
|
-
**Whole-language references
|
|
162
|
+
**Whole-language references (`whole_language_reference: true`): retain the prose, do not shred it.** For a whole-language reference the registry-sourced corpora (the guide/Book, the standard/library docs) ARE the product, not the compiler's internal exports. So for these briefs, extract no per-export items from the registry corpora (`source: language-registry`): §4a below keeps their pages as the Language Guide.
|
|
141
163
|
|
|
142
164
|
### 4a. Retain the Language Guide (whole-language references only)
|
|
143
165
|
|
|
144
166
|
**Skip this section entirely unless `whole_language_reference: true`.** When it is true, for each `doc_urls` entry whose `source` is `language-registry`:
|
|
145
167
|
|
|
146
|
-
- Do not reduce its fetched markdown to per-export items.
|
|
147
|
-
-
|
|
168
|
+
- Do not reduce its fetched markdown to per-export items. Its pages stay in `{docs_staging}` as §3 saved them (`page-{n}.md` and its `subpage-{n}-{k}.md` files), and §5c keeps the folder: step 5 reads the prose from them and retains it as the skill's Language Guide, each block cited `[EXT:{url}]`.
|
|
169
|
+
- List the entry in a `language_guide[]` index, in `doc_urls` order, as `{url, label, pages}`: `pages` names the files of `{docs_staging}` that hold its fetched pages, and is empty when the fetch failed (warn: step 5 surfaces the gap rather than emitting a thin guide silently).
|
|
170
|
+
|
|
171
|
+
Write the index beside the extraction inventory, so step 5 finds the pages after a context compaction. Bind `{language_guide_json}` ← `{project-root}/_bmad-output/.skf-stage/{skill-name}.language-guide.json`, resolve `{atomicWriteHelper}` ← first existing path in `{atomicWriteProbeOrder}` and, from `{project-root}`, run:
|
|
148
172
|
|
|
149
|
-
|
|
173
|
+
```bash
|
|
174
|
+
uv run {atomicWriteHelper} write --target "{language_guide_json}" <<'SKF_LANGUAGE_GUIDE'
|
|
175
|
+
{"docs_folder": "{docs_staging}", "language_guide": [{"url": "...", "label": "...", "pages": ["page-1.md"]}]}
|
|
176
|
+
SKF_LANGUAGE_GUIDE
|
|
177
|
+
```
|
|
150
178
|
|
|
151
|
-
|
|
179
|
+
When it fails, or no path resolves, warn "The Language Guide was not saved: {its message}": step 5 then assembles without it. The guide is a **distinct** carrier: it is not merged into the extraction inventory and is not subject to the §5 conflict rule, so the canonical prose survives intact into step 5 (compile), which foregrounds it as the skill's Language Guide. Non-registry docs (README-detected, homepage, Pages, docs-folder) still flow through §4's normal per-export extraction and the §5 merge unchanged.
|
|
152
180
|
|
|
153
181
|
### 5. Build Doc-Fetch Inventory
|
|
154
182
|
|
|
155
183
|
**Mode determines merge behavior:**
|
|
156
184
|
|
|
157
|
-
- **`source_type: "docs-only"
|
|
158
|
-
- **`source_type: "source"` (supplemental mode)
|
|
185
|
+
- **`source_type: "docs-only"`**: the doc-fetch inventory IS the extraction inventory. Its T3 items fill the empty inventory step 3 wrote, since there was no source code to extract from.
|
|
186
|
+
- **`source_type: "source"` (supplemental mode)**: the T3 items join the exports step 3 extracted.
|
|
187
|
+
|
|
188
|
+
Either way, merge them into `{extraction_inventory}`, the file step 3 §5 wrote, one object per item with its `[EXT:{url}]` citation and, when the item documents an export, its `export_name`. Resolve `{extractionInventoryHelper}` ← first existing path in `{extractionInventoryProbeOrder}` and, from `{project-root}`, run:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
uv run {extractionInventoryHelper} add --inventory "{extraction_inventory}" --field t3_items <<'SKF_T3'
|
|
192
|
+
[{"export_name": "...", "kind": "signature|type|config|example", "content": "...", "citation": "[EXT:{url}]"}]
|
|
193
|
+
SKF_T3
|
|
194
|
+
```
|
|
159
195
|
|
|
160
|
-
**Conflict rule:** T3 items never override existing T1, T1-low, or T2 items for the same export.
|
|
196
|
+
**Conflict rule:** T3 items never override existing T1, T1-low, or T2 items for the same export. The helper applies it: an item whose `export_name` names an export the inventory already holds is left out and listed in `dropped`. When it exits non-zero, fix the JSON and run it once more.
|
|
161
197
|
|
|
162
|
-
**Language-Guide carve-out:** the `language_guide[]`
|
|
198
|
+
**Language-Guide carve-out:** the `language_guide[]` from §4a (whole-language references) is not part of the export inventory and is therefore not subject to this conflict rule: it carries no export key, so it cannot collide with a T1 compiler export and can never be pruned. Step 5 reads it through `{language_guide_json}` and renders it as the foregrounded Language Guide section. Only the per-export T3 items participate in the T1/T2/T3 merge.
|
|
163
199
|
|
|
164
|
-
**Edge case
|
|
200
|
+
**Edge case: T1-zero supplemental mode.** If T1 extraction produced zero results and `doc_urls` are present in supplemental mode, compile uses the T3 items as the primary inventory, since no T1 data exists to conflict with.
|
|
165
201
|
|
|
166
202
|
**Aggregate totals for reporting:**
|
|
167
203
|
- URLs fetched successfully vs. total
|
|
168
204
|
- URLs that failed
|
|
169
205
|
- T3 items extracted
|
|
170
206
|
|
|
207
|
+
**Zero-content check.** Run `uv run {extractionInventoryHelper} summary --inventory "{extraction_inventory}"` from `{project-root}` and read its `counts`:
|
|
208
|
+
|
|
209
|
+
- **`source_type: "docs-only"`, no URL or subpage fetched in this run, and `counts.items` is 0:** there is nothing to compile, and a docs-only skill has no other source. Stage and promote nothing: **HARD HALT** (exit code 3, `docs-unreachable`, phase `fetch-docs`; stage `{run_dir}/halt.json` per the Workflow Rules and run `uv run {emitEnvelopeHelper} emit-halt --workflow skf-create-skill --run-dir "{run_dir}" --target stderr < "{run_dir}/halt.json"`): "**No documentation could be fetched for `{skill-name}`, so there is nothing to compile.** Failed: {each `doc_urls` URL with its reason}. Check the network and that this environment has a web fetch tool, or fix `doc_urls` in the brief, then re-run create-skill."
|
|
210
|
+
- **`source_type: "source"`, `counts.exports` is 0 (or equals `counts.not_public`) and every `doc_urls` fetch failed:** step 3 §6's zero-export check let this brief through because it declares `doc_urls`. Apply that check here: show its warning and offer **[C] Continue anyway** or **[R] Refine the brief**: stop here. **GATE [default: C]**: under `{headless_mode}`, log and record its `zero-exports` decision as step 3 §6 says, and continue. On [R], **HARD HALT** (exit code 6, `halted-for-brief-refinement`, phase `fetch-docs`; stage `{run_dir}/halt.json` per the Workflow Rules and run `uv run {emitEnvelopeHelper} emit-halt --workflow skf-create-skill --run-dir "{run_dir}" --target stderr < "{run_dir}/halt.json"`): "**Halting create-skill:** no public export was extracted (or, for a `public-api` brief, every export found is off the public surface: the `Off the public surface` warning names them) and no `doc_urls` page could be fetched. Fix `scope.include`, `target_version` or `doc_urls` in the brief (or re-run `skf-brief-skill`), then re-run `skf-create-skill`."
|
|
211
|
+
- **Otherwise:** continue. When no page of this run was saved (every fetch failed, or no fetch tool), skip §5b and §5c, which have nothing to index or remove, and go on to §6: `{docs_staging}` stays as it was.
|
|
212
|
+
|
|
171
213
|
### 5b. Index into QMD (Deep Tier Only)
|
|
172
214
|
|
|
173
215
|
**If tier is not Deep:** Skip this section silently.
|
|
174
216
|
|
|
175
217
|
**If tier is Deep and at least one URL was fetched successfully:**
|
|
176
218
|
|
|
177
|
-
1.
|
|
178
|
-
2. Index into QMD with atomic replace + rollback: if a `{skill-name}-docs` collection already exists, run `qmd collection remove {skill-name}-docs` first, then `qmd collection add {project-root}/_bmad-output/{skill-name}-docs/ --name {skill-name}-docs --mask "*.md"`.
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
```
|
|
183
|
-
- name: "{skill-name}-docs"
|
|
184
|
-
type: "docs"
|
|
185
|
-
source_workflow: "create-skill"
|
|
186
|
-
skill_name: "{skill-name}"
|
|
187
|
-
created_at: "{current ISO date}"
|
|
188
|
-
```
|
|
219
|
+
1. The fetched pages are already in `{docs_staging}` (section 3), which this section indexes.
|
|
220
|
+
2. Index into QMD with atomic replace + rollback: if a `{skill-name}-docs` collection already exists, run `qmd collection remove {skill-name}-docs` first, then `qmd collection add {project-root}/_bmad-output/{skill-name}-docs/ --name {skill-name}-docs --mask "*.md"`. Resolve `{forgeTierRwHelper}` from `{forgeTierRwProbeOrder}`: every registry change of this section goes through it, and it holds `{sidecar_path}/forge-tier.yaml.lock` for its one read-modify-write, so no step takes a lock of its own. **If `qmd collection add` fails after a successful `remove`:** remove the registry entry too, so the registry matches QMD's actual state, warn in evidence-report, and skip steps 3 and 4, since docs enrichment degrades gracefully:
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
uv run {forgeTierRwHelper} remove-qmd-collection --target "{forgeTierConfig}" --name {skill-name}-docs
|
|
224
|
+
```
|
|
189
225
|
|
|
190
|
-
|
|
226
|
+
3. Generate embeddings scoped to this collection (only if step 2 `add` succeeded): `qmd embed --collection {skill-name}-docs` (required for semantic `type:'vec'` and HyDE `type:'hyde'` sub-queries within the QMD `query` tool). If the installed `qmd` CLI does not accept `--collection`, gate the embed behind a freshness check: read the registry with `uv run {forgeTierRwHelper} read --target "{forgeTierConfig}"`, skip re-embedding when the `{skill-name}-docs` entry in its `data.qmd_collections` was created within the last 24 hours, and log the skip in the evidence report to prevent unbounded batch-mode re-embedding.
|
|
227
|
+
4. Register the collection in the `qmd_collections` array (only if step 2 `add` succeeded). The helper replaces the entry with the same `name` or appends it:
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
uv run {forgeTierRwHelper} register-qmd-collection --target "{forgeTierConfig}" <<'SKF_REGISTRY_ENTRY'
|
|
231
|
+
{"name": "{skill-name}-docs", "type": "docs", "source_workflow": "create-skill", "skill_name": "{skill-name}", "created_at": "{current ISO date}"}
|
|
232
|
+
SKF_REGISTRY_ENTRY
|
|
233
|
+
```
|
|
191
234
|
|
|
192
235
|
**If QMD indexing fails:** Warn: "QMD indexing of fetched docs failed. T3 items are still in the extraction inventory — enrichment will proceed without QMD-indexed docs." Continue.
|
|
193
236
|
|
|
237
|
+
### 5c. Keep or Remove the Fetched Pages
|
|
238
|
+
|
|
239
|
+
Once the pages are extracted, and at Deep tier indexed by §5b, remove `{docs_staging}` **only when `source_type` is `"source"` and the brief is not a whole-language reference** (`whole_language_reference` false): `rm -rf {project-root}/_bmad-output/{skill-name}-docs/`. A whole-language reference keeps the folder, since its Language Guide index (§4a) points step 5 at the pages in it. At Deep tier the folder is the source path of the `{skill-name}-docs` collection §5b registered; removing it is accepted for supplemental docs, whose T3 items already live in the extraction inventory. **When `source_type` is `"docs-only"`, keep the folder at every tier:** the fetched pages are the skill's only source corpus, since there is no code tree, so deleting them would leave nothing to verify citations against and, at Deep tier, the just-registered collection with nothing to refresh from. Record the kept path in the evidence report.
|
|
240
|
+
|
|
194
241
|
### 6. Report
|
|
195
242
|
|
|
196
243
|
Display:
|
|
@@ -200,7 +247,7 @@ Display:
|
|
|
200
247
|
**T3 items extracted:** {count}
|
|
201
248
|
**Confidence:** All doc-fetched items are T3 — `[EXT:{url}]` citations applied.
|
|
202
249
|
{If docs-only mode: '**Mode:** Docs-only — all skill content is T3. source_authority: community'}
|
|
203
|
-
{If docs-only mode
|
|
250
|
+
{If docs-only mode or a whole-language reference: '**Docs corpus kept:** `{docs_staging}`{if the `{skill-name}-docs` collection was registered in §5b: ', the source path of QMD collection `{skill-name}-docs`'}'}
|
|
204
251
|
|
|
205
252
|
Proceeding to enrichment..."
|
|
206
253
|
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
---
|
|
2
2
|
nextStepFile: 'fetch-docs.md'
|
|
3
|
-
|
|
3
|
+
forgeTierConfig: '{sidecar_path}/forge-tier.yaml'
|
|
4
|
+
# Resolve `{forgeTierRwHelper}` by probing `{forgeTierRwProbeOrder}` in order
|
|
4
5
|
# (installed SKF module path first, src/ dev-checkout fallback); first existing
|
|
5
|
-
# path wins.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
# path wins. §2 reads the registry through it and §4 changes the registry only
|
|
7
|
+
# through it. If neither resolves, §2 finds no cached entry and §4 skips the
|
|
8
|
+
# registry change with a warning: temporal enrichment never halts the workflow.
|
|
9
|
+
forgeTierRwProbeOrder:
|
|
10
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-forge-tier-rw.py'
|
|
11
|
+
- '{project-root}/src/shared/scripts/skf-forge-tier-rw.py'
|
|
12
|
+
# If neither path exists, §1 skips the step: temporal enrichment never
|
|
13
|
+
# halts the workflow.
|
|
14
|
+
fetchTemporalProbeOrder:
|
|
15
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-fetch-temporal.py'
|
|
16
|
+
- '{project-root}/src/shared/scripts/skf-fetch-temporal.py'
|
|
9
17
|
---
|
|
10
18
|
|
|
11
19
|
<!-- Config: communicate in {communication_language}. -->
|
|
@@ -14,47 +22,44 @@ atomicWriteProbeOrder:
|
|
|
14
22
|
|
|
15
23
|
## STEP GOAL:
|
|
16
24
|
|
|
17
|
-
To fetch temporal context (issues, PRs, changelogs, release notes) from the source repository and index
|
|
25
|
+
To fetch temporal context (issues, PRs, changelogs, release notes) from the source repository, keep the fetched files as the skill's temporal feeder, and index them into a QMD collection for Deep tier enrichment. This ensures step 4 has historical data to search when annotating extracted functions with T2 provenance, and that step 5c's doc-rot scan can read the same files.
|
|
18
26
|
|
|
19
27
|
## Rules
|
|
20
28
|
|
|
21
29
|
- Deep tier only — Quick, Forge, and Forge+ tiers skip this step entirely and silently
|
|
22
30
|
- GitHub repositories only — other source types degrade gracefully
|
|
23
31
|
- Do not halt the workflow if fetching or indexing fails
|
|
24
|
-
- Do not modify extraction
|
|
32
|
+
- Do not modify the extraction inventory step 3 wrote: this step only reads its `top_exports`, and writes the temporal feeder folder, the fetch folder that replaces it, and the QMD collection indexed from it
|
|
25
33
|
|
|
26
34
|
## MANDATORY SEQUENCE
|
|
27
35
|
|
|
28
36
|
### 1. Check Eligibility
|
|
29
37
|
|
|
30
|
-
|
|
38
|
+
Set `{temporal_feeder}` to null first, so that in a `--batch` run one brief's feeder never carries into the next. Then evaluate the following conditions sequentially. **If ANY condition fails, leave `{temporal_feeder}` null and skip silently to section 5 (auto-proceed) with no output:**
|
|
31
39
|
|
|
32
40
|
1. **Tier is Deep:** If tier is Quick, Forge, or Forge+, skip silently.
|
|
33
|
-
2. **
|
|
34
|
-
3. **`gh` CLI is available:** Run `timeout 10s gh auth status` to verify the CLI is installed and authenticated (the short timeout protects against a misconfigured network or hung auth helper blocking the workflow). If it fails or times out, skip silently.
|
|
41
|
+
2. **The source is a GitHub repository `gh` can read:** resolve `{fetchTemporalHelper}` from `{fetchTemporalProbeOrder}` and, from `{project-root}`, run:
|
|
35
42
|
|
|
36
|
-
|
|
43
|
+
```bash
|
|
44
|
+
uv run {fetchTemporalHelper} repo --source-repo "{source_repo}"
|
|
45
|
+
```
|
|
37
46
|
|
|
38
|
-
|
|
47
|
+
Skip silently when the command exits non-zero, when its `skip_reason` is not null, and when no candidate resolves. Otherwise bind `{temporal_repo}` ← its `repo`, and when its `via` is `origin`, log: "**Local source with GitHub remote detected:** {temporal_repo}. Fetching temporal context."
|
|
39
48
|
|
|
40
|
-
|
|
49
|
+
Both conditions must pass to proceed to section 2. Then bind `{temporal_feeder}` ← `{forge_data_folder}/{skill-name}/.skf-temporal`: the folder where this step keeps what it fetched (sections 3 and 4). Step 5c's doc-rot scan reads the files in it, and a null `{temporal_feeder}` tells step 5c that no temporal feeder was expected.
|
|
41
50
|
|
|
42
|
-
|
|
51
|
+
### 2. Check Cache (Skip If Fresh)
|
|
43
52
|
|
|
44
|
-
|
|
45
|
-
2. If not a git repo: skip silently to section 5 (current behavior).
|
|
46
|
-
3. Extract the origin remote: `git -C "{source_repo}" remote get-url origin`
|
|
47
|
-
4. If the remote URL contains `github.com`:
|
|
48
|
-
- Extract `owner/repo` from the remote URL (strip `.git` suffix, handle both HTTPS and SSH formats)
|
|
49
|
-
- Log: "**Local source with GitHub remote detected:** {owner}/{repo} — fetching temporal context."
|
|
50
|
-
- Use the extracted `owner/repo` for all `gh` API calls in sections 3-4. Continue to condition 3 (gh CLI check).
|
|
51
|
-
5. If no remote, or remote is not GitHub: skip silently to section 5 (current behavior).
|
|
53
|
+
Resolve `{forgeTierRwHelper}` from `{forgeTierRwProbeOrder}` and read the registry as JSON, from `{project-root}`:
|
|
52
54
|
|
|
53
|
-
|
|
55
|
+
```bash
|
|
56
|
+
uv run {forgeTierRwHelper} read --target "{forgeTierConfig}"
|
|
57
|
+
```
|
|
54
58
|
|
|
55
|
-
|
|
59
|
+
Bind `{registry_collections}` ← its `data.qmd_collections`: an empty list when `exists` is false, the list is missing, the command fails or no candidate resolves. Section 4 reads it again for its embed check.
|
|
56
60
|
|
|
57
|
-
- Look for a `
|
|
61
|
+
- Look for a `{registry_collections}` entry where `skill_name` matches the current brief AND `type` is `"temporal"`.
|
|
62
|
+
- If `{temporal_feeder}` holds no `.md` file, continue to section 3 even when that entry is fresh: a cache hit must leave step 5c files to read, and an earlier SKF release deleted the folder once it was indexed.
|
|
58
63
|
- If found AND `created_at` is within the last **7 days** (rationale: temporal context — issues, PRs, changelogs — rarely changes meaningfully on shorter horizons; a 7-day window balances freshness against re-fetch cost and GitHub rate limits): the temporal collection is fresh. Display:
|
|
59
64
|
|
|
60
65
|
"**Temporal context: cached.** Collection `{skill-name}-temporal` is fresh ({days} days old). Skipping re-fetch."
|
|
@@ -65,175 +70,57 @@ Skip to section 5 (auto-proceed).
|
|
|
65
70
|
|
|
66
71
|
### 3. Fetch Temporal Context
|
|
67
72
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
Resolve the `owner` and `repo` from `source_repo` (e.g., `acme/toolkit` from `https://github.com/acme/toolkit`).
|
|
71
|
-
|
|
72
|
-
Execute the four fetches below **in parallel** — they are independent and the network round-trips dominate wall-clock. Background each, then `wait` for the batch. **If any individual fetch fails, log a warning and continue with the others.** The 4-concurrent fan-out is well under GitHub's authenticated REST rate limit (5000/hr); no bounded-concurrency guard is needed for this set.
|
|
73
|
+
Fetch through the helper, from `{project-root}`. `--exports` names the extraction inventory step 3 §5 wrote, `{extraction_inventory}`: the helper reads its `top_exports` for the targeted issue searches, and an empty list (docs-only mode, or a source extraction with no public exports) runs none:
|
|
73
74
|
|
|
74
75
|
```bash
|
|
75
|
-
|
|
76
|
-
# 1. Issues (last 100)
|
|
77
|
-
( gh issue list -R {owner}/{repo} --state all --limit 100 \
|
|
78
|
-
--json number,title,state,labels,createdAt,closedAt,body \
|
|
79
|
-
| jq -r '...' > {staging}/issues.md ) &
|
|
80
|
-
# 2. Merged PRs (last 100)
|
|
81
|
-
( gh pr list -R {owner}/{repo} --state merged --limit 100 \
|
|
82
|
-
--json number,title,mergedAt,labels,body \
|
|
83
|
-
| jq -r '...' > {staging}/prs.md ) &
|
|
84
|
-
# 3. Release tags only (the per-tag fetch loop runs sequentially below to
|
|
85
|
-
# preserve append-immediately crash-resume semantics for releases.md)
|
|
86
|
-
( gh release list -R {owner}/{repo} --limit 10 \
|
|
87
|
-
--json tagName,name,publishedAt > {staging}/.release-tags.json ) &
|
|
88
|
-
# 4. Changelog (404 is silent skip — note `set +e` so the subshell doesn't
|
|
89
|
-
# propagate `gh api`'s non-zero exit on missing file)
|
|
90
|
-
( set +e
|
|
91
|
-
gh api repos/{owner}/{repo}/contents/CHANGELOG.md --jq '.content' \
|
|
92
|
-
| base64 -d > {staging}/changelog.md 2>/dev/null
|
|
93
|
-
[ -s {staging}/changelog.md ] || rm -f {staging}/changelog.md ) &
|
|
94
|
-
wait
|
|
76
|
+
uv run {fetchTemporalHelper} fetch --repo "{temporal_repo}" --feeder "{temporal_feeder}" --exports "{extraction_inventory}"
|
|
95
77
|
```
|
|
96
78
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
1. **Issues (last 100):** 100 is `gh issue list`'s default max-per-page; one paginated call captures recent activity without extra round trips or rate-limit pressure. Output → `{staging}/issues.md` formatted as a markdown document with one section per issue.
|
|
100
|
-
|
|
101
|
-
2. **Merged PRs (last 100):** Same 100-per-page convention as issues — captures the most recent merges in one API call. Output → `{staging}/prs.md`.
|
|
102
|
-
|
|
103
|
-
3. **Release tags + per-release fetches (last 10):** Release notes accumulate slowly relative to issues/PRs; the most recent 10 tags cover roughly the last 6-18 months of changelog-relevant history for typical OSS projects, which is enough context for T2-past annotations without fanning out to dozens of `gh release view` calls.
|
|
104
|
-
|
|
105
|
-
**Note:** `gh release list --json` does **not** support the `body` field. The parallel block above fetches tags only (Step 1). After `wait`, run **Step 2 sequentially** to preserve the append-immediately crash-resume contract:
|
|
106
|
-
|
|
107
|
-
If `{staging}/.release-tags.json` is empty (no releases), skip Step 2 and omit the releases section entirely. Otherwise:
|
|
108
|
-
|
|
109
|
-
```bash
|
|
110
|
-
# Sequential per-tag loop. Iterate the JSON tag array verbatim so a
|
|
111
|
-
# crash mid-loop leaves a partial-but-well-formed releases.md on disk.
|
|
112
|
-
echo "# Releases (partial if interrupted)" > {staging}/releases.md
|
|
113
|
-
ERR_FILE="{staging}/.gh-release-err" # per-run stderr capture inside staging
|
|
114
|
-
jq -r '.[].tagName' {staging}/.release-tags.json | while IFS= read -r tag; do
|
|
115
|
-
if gh release view "$tag" -R {owner}/{repo} \
|
|
116
|
-
--json tagName,name,publishedAt,body 2>"$ERR_FILE"; then
|
|
117
|
-
jq -r '...' >> {staging}/releases.md # one ## {tag} block per release
|
|
118
|
-
else
|
|
119
|
-
echo "## $tag — fetch failed: $(cat "$ERR_FILE")" \
|
|
120
|
-
>> {staging}/releases.md
|
|
121
|
-
fi
|
|
122
|
-
done
|
|
123
|
-
rm -f "$ERR_FILE"
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
Failed individual fetches get a one-line placeholder; the loop continues with remaining tags. If a rate limit (HTTP 429) is hit, stop the release loop, keep the partial `releases.md` file in place (do not delete it), and log: "Release fetch stopped at tag {N}/{total} due to rate limiting — partial releases.md retained."
|
|
127
|
-
|
|
128
|
-
**Why sequential here when the rest is parallel:** the append-per-release pattern guarantees that a mid-loop abort (rate limit, network drop, user interrupt) leaves a partial but well-formed `releases.md` with every release fetched so far. Parallel writers appending to the same file would need file locking and per-writer ordering — the simpler sequential loop is robust for free, and 10 release fetches contribute only ~5-10s of the total wall-clock.
|
|
129
|
-
|
|
130
|
-
4. **Changelog:** `CHANGELOG.md` or `RELEASES.md` at the repository root. The parallel block above writes to `{staging}/changelog.md` only when the file exists; non-existence (404 from `gh api`) leaves no file behind.
|
|
79
|
+
It fetches into a fresh folder beside `{temporal_feeder}` and replaces the feeder with it only when the fetch returned something (`uv run {fetchTemporalHelper} --help` describes the files it writes and the swap).
|
|
131
80
|
|
|
132
|
-
|
|
81
|
+
Show each of its `warnings`. Then:
|
|
133
82
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
**Limit:** Search the top **10 function names** maximum to control API call volume and avoid `gh` rate limiting. (rationale: 10 targeted searches + generic fetches from §3 stays well under GitHub's unauthenticated search rate limit of 10 requests/minute and authenticated 30/minute; matches the `top_exports[]` size emitted by step 3 §5 so every tracked export gets one search.)
|
|
139
|
-
|
|
140
|
-
For each function name in `top_exports[]` (up to 10), **sanitize first**: strip every character that is not in `[A-Za-z0-9_]` from `function_name` to produce `safe_name`. This prevents shell injection and `gh` query parser errors when an export name contains punctuation (e.g., `<T>`, `.method`, `::namespace`, quotes). If `safe_name` is empty after sanitization (the original was entirely punctuation — rare but possible for symbol exports), fall back to piping the original name through stdin via `--query-from-file -`-style indirection if your `gh` version supports it; otherwise skip that one entry with a log line — never substitute the unsanitized name back into the shell command.
|
|
141
|
-
|
|
142
|
-
**Parallel fan-out (bounded concurrency = 5):** sanitized searches are independent and benefit from parallelism. Use `xargs -P 5` so at most 5 `gh search` calls are in flight at once — this stays well under GitHub's authenticated search rate limit (30/min) while saving ~5-8s of wall-clock on a top-10 export list compared to fully sequential.
|
|
143
|
-
|
|
144
|
-
```bash
|
|
145
|
-
# 1. Sanitize and write one safe_name per line; skip empties.
|
|
146
|
-
# Python is the canonical sanitizer because awk/sed regex semantics
|
|
147
|
-
# vary across platforms.
|
|
148
|
-
jq -r '.top_exports[]' {extraction_inventory.json} \
|
|
149
|
-
| python3 -c 'import sys,re
|
|
150
|
-
for line in sys.stdin:
|
|
151
|
-
s = re.sub(r"[^A-Za-z0-9_]", "", line.strip())
|
|
152
|
-
if s: print(s)' > {staging}/.safe-names.txt
|
|
153
|
-
|
|
154
|
-
# 2. Fan out to gh search, 5 in flight. Each writer emits a self-contained
|
|
155
|
-
# section to its own per-name file; we concatenate after the wait.
|
|
156
|
-
# --limit 5: top-5 issues per function keeps signal-to-noise high and caps
|
|
157
|
-
# total response size across 10 fan-outs at 50 issues.
|
|
158
|
-
mkdir -p {staging}/targeted
|
|
159
|
-
cat {staging}/.safe-names.txt | xargs -P 5 -I {} bash -c '
|
|
160
|
-
gh search issues --repo {owner}/{repo} "{}" --limit 5 \
|
|
161
|
-
--json number,title,state,body 2>/dev/null \
|
|
162
|
-
| jq -r ". | \"## {}\\n\" + (...)" > {staging}/targeted/{}.md \
|
|
163
|
-
|| echo "## {} — fetch failed" > {staging}/targeted/{}.md
|
|
164
|
-
'
|
|
165
|
-
|
|
166
|
-
# 3. Concatenate in stable order (alpha by safe_name) into the single
|
|
167
|
-
# aggregated file the rest of the workflow expects.
|
|
168
|
-
sort {staging}/.safe-names.txt | while IFS= read -r name; do
|
|
169
|
-
cat "{staging}/targeted/$name.md"
|
|
170
|
-
done > {staging}/targeted-issues.md
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
Aggregate all targeted search results into a single file: `{staging}/targeted-issues.md`. The per-name temp directory `{staging}/targeted/` can be removed after concat — it exists only to give each parallel writer an isolated output stream.
|
|
174
|
-
|
|
175
|
-
**If `gh search` is unavailable** (older `gh` CLI versions): skip targeted searches silently. The generic fetches from section 3 still provide baseline temporal context.
|
|
176
|
-
|
|
177
|
-
**If rate limiting occurs** (HTTP 429 or similar): stop targeted searches immediately, keep results collected so far. Log: "Targeted search stopped at function {N}/{total} due to rate limiting."
|
|
178
|
-
|
|
179
|
-
**After all fetching,** verify at least one file was written to the staging directory. If the staging directory is empty (all fetches failed), log a warning and skip to section 5.
|
|
83
|
+
- **Exit 0** (`status: "replaced"`): `{temporal_feeder}` now holds this fetch, and `files` lists it. Continue to section 4.
|
|
84
|
+
- **Exit 3** (`status: "kept"`: no fetch returned anything): log a warning and skip to section 5. `{temporal_feeder}` stays bound and keeps what the last good fetch left in it: step 5c scans those files, or reports the missing feeder in the evidence report when there are none.
|
|
85
|
+
- **Exit 1 or 2:** as for exit 3, with the helper's error (its stderr) in the warning.
|
|
180
86
|
|
|
181
87
|
### 4. Index Into QMD & Register
|
|
182
88
|
|
|
183
|
-
**Index
|
|
89
|
+
**Index `{temporal_feeder}`:**
|
|
184
90
|
|
|
185
|
-
If a `{skill-name}-temporal` collection already exists, remove and recreate for atomic replace. **Wrap the remove + add pair with rollback on `add` failure
|
|
91
|
+
If a `{skill-name}-temporal` collection already exists, remove and recreate for atomic replace. **Wrap the remove + add pair with rollback on `add` failure:** a `remove` that succeeds followed by an `add` that fails must not leave the registry claiming a collection that no longer exists in QMD. The registry changes below go through `{forgeTierRwHelper}` (resolved in section 2), which holds `{sidecar_path}/forge-tier.yaml.lock` for its one read-modify-write, so no step takes a lock of its own:
|
|
186
92
|
|
|
187
93
|
```bash
|
|
188
94
|
qmd collection remove {skill-name}-temporal
|
|
189
|
-
if ! qmd collection add {
|
|
190
|
-
# add failed after remove succeeded
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
echo "WARN: qmd add failed after remove — registry entry for {skill-name}-temporal removed to keep forge-tier.yaml consistent with QMD state."
|
|
194
|
-
# [skip the embed step]
|
|
95
|
+
if ! qmd collection add "{temporal_feeder}" --name {skill-name}-temporal --mask "*.md"; then
|
|
96
|
+
# add failed after remove succeeded: the collection is gone from QMD, so its registry entry goes too.
|
|
97
|
+
uv run {forgeTierRwHelper} remove-qmd-collection --target "{forgeTierConfig}" --name {skill-name}-temporal
|
|
98
|
+
echo "WARN: qmd add failed after remove; registry entry for {skill-name}-temporal removed to keep forge-tier.yaml consistent with QMD state."
|
|
195
99
|
else
|
|
196
100
|
qmd embed --collection {skill-name}-temporal
|
|
197
101
|
fi
|
|
198
102
|
```
|
|
199
103
|
|
|
200
|
-
**Rollback rule:** if the `qmd collection add` step fails (non-zero exit, network error, parse error) after the prior `remove` succeeded, remove the
|
|
104
|
+
**Rollback rule:** if the `qmd collection add` step fails (non-zero exit, network error, parse error) after the prior `remove` succeeded, the `remove-qmd-collection` call above removes the registry entry, to match QMD's actual state. A dangling registry entry that points at a non-existent QMD collection poisons subsequent cache-hit checks in §2. Emit a warning in evidence-report and skip the embed and the registration below: enrichment degrades to no-QMD for this run.
|
|
201
105
|
|
|
202
|
-
**Scope the embed:** Always pass `--collection {skill-name}-temporal` to `qmd embed`. An unscoped `qmd embed` re-embeds every collection in the QMD store, which can take minutes per run in batch mode and generates wasteful GPU/API cost. If the installed `qmd` CLI does not accept `--collection` (older upstream versions), gate the embed behind a per-skill check: if
|
|
106
|
+
**Scope the embed:** Always pass `--collection {skill-name}-temporal` to `qmd embed`. An unscoped `qmd embed` re-embeds every collection in the QMD store, which can take minutes per run in batch mode and generates wasteful GPU/API cost. If the installed `qmd` CLI does not accept `--collection` (older upstream versions), gate the embed behind a per-skill check: if the `{skill-name}-temporal` entry section 2 found in `{registry_collections}` has a `created_at` within 24 hours, skip the embed entirely and warn "qmd embed skipped: upstream qmd lacks --collection scope; re-embedding all collections would be wasteful in batch mode". Log the skip in the evidence report.
|
|
203
107
|
|
|
204
108
|
**Note:** `qmd embed` generates vector embeddings required for semantic (`type:'vec'`) and HyDE (`type:'hyde'`) sub-queries inside the QMD `query` tool. Without embeddings, only BM25 (`type:'lex'`) keyword search works. Run `qmd embed` after every `qmd collection add`.
|
|
205
109
|
|
|
206
|
-
**
|
|
207
|
-
|
|
208
|
-
1. Acquire an exclusive `flock` on `{sidecar_path}/forge-tier.yaml.lock` (create the lock file if absent). Use `flock -x {lockfile} -c "..."` or an equivalent `fcntl.flock(LOCK_EX)` guard.
|
|
209
|
-
2. Read the current `forge-tier.yaml`, capturing its `st_mtime` as `mtime_before`.
|
|
210
|
-
3. Perform the read-modify-write below.
|
|
211
|
-
4. Write via `python3 {atomicWriteHelper} write --target {sidecar_path}/forge-tier.yaml`.
|
|
212
|
-
5. Release the flock.
|
|
213
|
-
|
|
214
|
-
**Fallback when `flock` is unavailable:** re-stat the file after the write; if the on-disk `st_mtime` is newer than `mtime_before` by more than this run's own write timestamp, halt with "forge-tier.yaml modified mid-update by another process — refusing to clobber. Re-run after the other run completes." This read-CAS-by-mtime is the belt-and-braces safety net for environments without `flock`.
|
|
215
|
-
|
|
216
|
-
If an entry with `name: "{skill-name}-temporal"` already exists in `qmd_collections`, replace it. Otherwise, append:
|
|
217
|
-
|
|
218
|
-
```yaml
|
|
219
|
-
- name: "{skill-name}-temporal"
|
|
220
|
-
type: "temporal"
|
|
221
|
-
source_workflow: "create-skill"
|
|
222
|
-
skill_name: "{skill-name}"
|
|
223
|
-
created_at: "{current ISO date}"
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
**Clean up** the staging directory after successful indexing:
|
|
110
|
+
**Register the collection** only when `qmd collection add` succeeded. The helper replaces the entry with `name: "{skill-name}-temporal"` or appends it, and its lock keeps concurrent batch runs from losing each other's entries:
|
|
227
111
|
|
|
228
112
|
```bash
|
|
229
|
-
|
|
113
|
+
uv run {forgeTierRwHelper} register-qmd-collection --target "{forgeTierConfig}" <<'SKF_REGISTRY_ENTRY'
|
|
114
|
+
{"name": "{skill-name}-temporal", "type": "temporal", "source_workflow": "create-skill", "skill_name": "{skill-name}", "created_at": "{current ISO date}"}
|
|
115
|
+
SKF_REGISTRY_ENTRY
|
|
230
116
|
```
|
|
231
117
|
|
|
118
|
+
**Keep `{temporal_feeder}`.** It is the source path of the `{skill-name}-temporal` collection and the temporal feeder step 5c's doc-rot scan reads, on this run and on every cache hit until a later fetch replaces it (section 3), so this step never deletes it. Its `.skf-` name, like the fetch folder's, marks it as SKF output to SKF's ownership checks, so it stays with the skill's forge folder: a purge of the whole skill deletes it and a rename moves it.
|
|
119
|
+
|
|
232
120
|
**Error handling:**
|
|
233
121
|
|
|
234
|
-
- If QMD indexing fails: log the error, note that temporal enrichment will be unavailable. Do not fail the workflow.
|
|
235
|
-
- If registry
|
|
236
|
-
- If cleanup fails: log a warning and continue.
|
|
122
|
+
- If QMD indexing fails: log the error, note that temporal enrichment will be unavailable. Do not fail the workflow. The files stay in `{temporal_feeder}` for step 5c.
|
|
123
|
+
- If a registry command fails, or `{forgeTierRwHelper}` does not resolve: log the error, continue. The collection may exist in QMD even if the registry entry failed.
|
|
237
124
|
|
|
238
125
|
Display brief confirmation:
|
|
239
126
|
|