bmad-module-skill-forge 2.1.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/README.md +51 -49
- package/docs/404.md +10 -8
- package/docs/_data/pinned.yaml +22 -13
- package/docs/_internal/STABILITY.md +58 -35
- package/docs/agents.md +39 -53
- package/docs/architecture.md +65 -51
- package/docs/bmad-synergy.md +53 -51
- package/docs/campaign.md +127 -75
- package/docs/concepts.md +34 -28
- package/docs/examples.md +70 -68
- package/docs/forge-auto.md +34 -31
- package/docs/getting-started.md +118 -57
- package/docs/how-it-works.md +25 -21
- package/docs/index.md +15 -15
- package/docs/skill-model.md +70 -59
- package/docs/troubleshooting.md +135 -18
- package/docs/verifying-a-skill.md +83 -57
- package/docs/why-skf.md +24 -22
- package/docs/workflows.md +274 -110
- package/package.json +18 -10
- package/src/forger/preferences.yaml +8 -1
- package/src/knowledge/agentskills-spec.md +2 -0
- package/src/knowledge/ccc-bridge.md +40 -24
- package/src/knowledge/confidence-tiers.md +9 -5
- package/src/knowledge/overview.md +3 -3
- package/src/knowledge/progressive-capability.md +8 -8
- package/src/knowledge/provenance-tracking.md +9 -5
- package/src/knowledge/qmd-registry.md +2 -2
- package/src/knowledge/skf-knowledge-index.csv +2 -2
- package/src/knowledge/skill-lifecycle.md +18 -11
- package/src/knowledge/split-body-strategy.md +0 -3
- package/src/knowledge/tool-resolution.md +4 -2
- package/src/knowledge/version-paths.md +101 -15
- package/src/module-help.csv +3 -3
- package/src/module.yaml +10 -0
- package/src/shared/data/ast-grep-recipes.yaml +608 -0
- package/src/shared/data/ide-context-files.json +30 -0
- package/src/shared/health-check.md +32 -12
- package/src/shared/references/description-guard-protocol.md +14 -9
- package/src/shared/references/feasibility-report-schema.md +19 -13
- package/src/shared/references/headless-gate-convention.md +55 -3
- package/src/shared/references/output-contract-schema.md +19 -8
- package/src/shared/references/pipeline-contracts.md +94 -29
- package/src/shared/references/tessl-review.md +77 -0
- package/src/shared/scripts/schemas/skf-analyze-result-envelope.v1.json +194 -0
- package/src/shared/scripts/schemas/skf-audit-result-envelope.v1.json +192 -0
- package/src/shared/scripts/schemas/skf-brief-result-envelope.v1.json +32 -4
- package/src/shared/scripts/schemas/skf-campaign-result-envelope.v1.json +174 -0
- package/src/shared/scripts/schemas/skf-create-skill-result-envelope.v1.json +284 -0
- package/src/shared/scripts/schemas/skf-drop-skill-result-envelope.v1.json +164 -0
- package/src/shared/scripts/schemas/skf-export-result-envelope.v1.json +160 -0
- package/src/shared/scripts/schemas/skf-quick-skill-result-envelope.v1.json +261 -0
- package/src/shared/scripts/schemas/skf-refine-architecture-result-envelope.v1.json +192 -0
- package/src/shared/scripts/schemas/skf-rename-skill-result-envelope.v1.json +179 -0
- package/src/shared/scripts/schemas/skf-setup-result-envelope.v1.json +27 -11
- package/src/shared/scripts/schemas/skf-stack-result-envelope.v1.json +176 -0
- package/src/shared/scripts/schemas/skf-test-result-envelope.v1.json +173 -0
- package/src/shared/scripts/schemas/skf-update-result-envelope.v1.json +19 -5
- package/src/shared/scripts/schemas/skf-verify-stack-result-envelope.v1.json +180 -0
- package/src/shared/scripts/schemas/skill-brief.v1.json +34 -0
- package/src/shared/scripts/schemas/workspace-detection.v1.json +1 -1
- package/src/shared/scripts/skf-atomic-write.py +30 -14
- package/src/shared/scripts/skf-build-change-manifest.py +2203 -24
- package/src/shared/scripts/skf-ccc-git-hygiene.py +532 -0
- package/src/shared/scripts/skf-check-unit-records.py +184 -0
- package/src/shared/scripts/skf-check-workspace-drift.py +538 -9
- package/src/shared/scripts/skf-classify-changed-files.py +694 -0
- package/src/shared/scripts/skf-comention-pairs.py +1333 -123
- package/src/shared/scripts/skf-compare-file-hashes.py +1 -1
- package/src/shared/scripts/skf-count-imports.py +1867 -0
- package/src/shared/scripts/skf-count-tokens.py +1 -1
- package/src/shared/scripts/skf-derive-assembly-shape.py +3 -2
- package/src/shared/scripts/skf-description-guard.py +117 -9
- package/src/shared/scripts/skf-detect-docs.py +892 -4
- package/src/shared/scripts/skf-detect-language.py +486 -107
- package/src/shared/scripts/skf-detect-registry.py +887 -0
- package/src/shared/scripts/skf-detect-scripts-assets.py +1 -1
- package/src/shared/scripts/skf-detect-tools.py +215 -27
- package/src/shared/scripts/skf-detect-workspaces.py +506 -30
- package/src/shared/scripts/skf-disqualify-candidates.py +821 -182
- package/src/shared/scripts/skf-emit-brief-result-envelope.py +69 -191
- package/src/shared/scripts/skf-emit-result-envelope.py +1604 -106
- package/src/shared/scripts/skf-enumerate-stack-skills.py +986 -117
- package/src/shared/scripts/skf-extract-public-api.py +4084 -85
- package/src/shared/scripts/skf-extraction-inventory.py +746 -0
- package/src/shared/scripts/skf-extraction-snapshot.py +539 -0
- package/src/shared/scripts/skf-fetch-temporal.py +674 -0
- package/src/shared/scripts/skf-find-cycles.py +97 -29
- package/src/shared/scripts/skf-find-test-report.py +476 -0
- package/src/shared/scripts/skf-forge-tier-rw.py +444 -203
- package/src/shared/scripts/skf-github-fetch.py +365 -0
- package/src/shared/scripts/skf-github-probe.py +661 -0
- package/src/shared/scripts/skf-hash-content.py +166 -12
- package/src/shared/scripts/skf-language-corpora.py +1 -1
- package/src/shared/scripts/skf-load-provenance.py +159 -27
- package/src/shared/scripts/skf-manifest-ops.py +1 -1
- package/src/shared/scripts/skf-merge-ccc-exclusions.py +2307 -223
- package/src/shared/scripts/skf-merge-doc-urls.py +1 -1
- package/src/shared/scripts/skf-names-present.py +221 -0
- package/src/shared/scripts/skf-pair-intersect.py +119 -28
- package/src/shared/scripts/skf-parse-gaps.py +1384 -0
- package/src/shared/scripts/skf-preapply.py +1 -1
- package/src/shared/scripts/skf-preflight.py +161 -27
- package/src/shared/scripts/skf-promote-staged.py +430 -0
- package/src/shared/scripts/skf-provenance-gap-dispatch.py +39 -7
- package/src/shared/scripts/skf-qmd-classify-collections.py +229 -45
- package/src/shared/scripts/skf-quick-batch.py +774 -0
- package/src/shared/scripts/skf-rebuild-managed-sections.py +972 -88
- package/src/shared/scripts/skf-recommend-scope-type.py +323 -156
- package/src/shared/scripts/skf-render-metadata-stats.py +156 -12
- package/src/shared/scripts/skf-render-quick-metadata.py +132 -28
- package/src/shared/scripts/skf-render-stack-metadata.py +976 -0
- package/src/shared/scripts/skf-resolve-authoritative-files.py +128 -13
- package/src/shared/scripts/skf-resolve-package.py +523 -106
- package/src/shared/scripts/skf-rewrite-skill-name.py +433 -47
- package/src/shared/scripts/skf-run-lock.py +577 -0
- package/src/shared/scripts/skf-scan-manifests.py +998 -57
- package/src/shared/scripts/skf-scan-skill-md-structure.py +1341 -37
- package/src/shared/scripts/skf-severity-classify.py +712 -108
- package/src/shared/scripts/skf-shape-detect.py +298 -204
- package/src/shared/scripts/skf-shard-body.py +9 -8
- package/src/shared/scripts/skf-skill-inventory.py +2412 -89
- package/src/shared/scripts/skf-skills-module.py +472 -0
- package/src/shared/scripts/skf-source-tree.py +1796 -0
- package/src/shared/scripts/skf-structural-diff.py +670 -119
- package/src/shared/scripts/skf-tessl-review.py +706 -0
- package/src/shared/scripts/skf-update-active-symlink.py +5 -5
- package/src/shared/scripts/skf-update-run-state.py +624 -0
- package/src/shared/scripts/skf-validate-brief-inputs.py +92 -29
- package/src/shared/scripts/skf-validate-brief-schema.py +4 -1
- package/src/shared/scripts/skf-validate-feasibility-report.py +577 -66
- package/src/shared/scripts/skf-validate-frontmatter.py +103 -26
- package/src/shared/scripts/skf-validate-output.py +712 -64
- package/src/shared/scripts/skf-validate-pins.py +1 -1
- package/src/shared/scripts/skf-verify-no-trace.py +116 -10
- package/src/shared/scripts/skf-verify-provenance-completeness.py +2919 -54
- package/src/shared/scripts/skf-write-skill-brief.py +367 -19
- package/src/shared/tool-requirements.yaml +201 -0
- package/src/skf-analyze-source/SKILL.md +41 -36
- package/src/skf-analyze-source/assets/skill-brief-schema.md +7 -8
- package/src/skf-analyze-source/customize.toml +36 -24
- package/src/skf-analyze-source/references/auto-docs-only.md +58 -20
- package/src/skf-analyze-source/references/continue.md +2 -13
- package/src/skf-analyze-source/references/discover-additional-source.md +147 -0
- package/src/skf-analyze-source/references/generate-briefs.md +121 -55
- package/src/skf-analyze-source/references/headless-contract.md +42 -15
- package/src/skf-analyze-source/references/identify-units.md +96 -110
- package/src/skf-analyze-source/references/init.md +73 -71
- package/src/skf-analyze-source/references/map-and-detect.md +123 -105
- package/src/skf-analyze-source/references/map-unit-exports.md +68 -0
- package/src/skf-analyze-source/references/recommend.md +51 -70
- package/src/skf-analyze-source/references/scan-project.md +60 -84
- package/src/skf-analyze-source/references/scan-root.md +39 -0
- package/src/skf-analyze-source/references/step-auto-scope-coexistence.md +47 -0
- package/src/skf-analyze-source/references/step-auto-scope-corpora.md +53 -0
- package/src/skf-analyze-source/references/step-auto-scope-split.md +91 -0
- package/src/skf-analyze-source/references/step-auto-scope.md +208 -293
- package/src/skf-analyze-source/references/step-shape-detect.md +14 -12
- package/src/skf-analyze-source/references/unit-detection-heuristics.md +37 -42
- package/src/skf-audit-skill/SKILL.md +43 -51
- package/src/skf-audit-skill/assets/drift-report-template.md +27 -3
- package/src/skf-audit-skill/customize.toml +41 -20
- package/src/skf-audit-skill/references/constituent-freshness.md +91 -0
- package/src/skf-audit-skill/references/doc-drift.md +141 -0
- package/src/skf-audit-skill/references/headless-contract.md +29 -0
- package/src/skf-audit-skill/references/init.md +148 -210
- package/src/skf-audit-skill/references/re-index.md +96 -108
- package/src/skf-audit-skill/references/report.md +96 -33
- package/src/skf-audit-skill/references/semantic-diff.md +27 -18
- package/src/skf-audit-skill/references/severity-classify.md +79 -65
- package/src/skf-audit-skill/references/severity-rules.md +15 -6
- package/src/skf-audit-skill/references/structural-diff.md +75 -90
- package/src/skf-audit-skill/references/upstream-checkout.md +77 -0
- package/src/skf-audit-skill/scripts/render-drift-tables.py +685 -0
- package/src/skf-brief-skill/SKILL.md +36 -25
- package/src/skf-brief-skill/assets/description-voice-examples.md +1 -1
- package/src/skf-brief-skill/assets/scope-templates.md +17 -24
- package/src/skf-brief-skill/assets/skill-brief-schema.md +29 -15
- package/src/skf-brief-skill/customize.toml +48 -20
- package/src/skf-brief-skill/references/analyze-target.md +142 -125
- package/src/skf-brief-skill/references/confirm-brief.md +10 -8
- package/src/skf-brief-skill/references/draft-checkpoint.md +36 -17
- package/src/skf-brief-skill/references/gather-intent-ratify.md +68 -0
- package/src/skf-brief-skill/references/gather-intent.md +87 -182
- package/src/skf-brief-skill/references/headless-args.md +84 -16
- package/src/skf-brief-skill/references/health-check.md +1 -1
- package/src/skf-brief-skill/references/invocation-contract.md +18 -13
- package/src/skf-brief-skill/references/portfolio-similarity-check.md +1 -1
- package/src/skf-brief-skill/references/qmd-collection-registration.md +7 -10
- package/src/skf-brief-skill/references/scope-definition.md +123 -47
- package/src/skf-brief-skill/references/step-auto-brief.md +24 -80
- package/src/skf-brief-skill/references/step-auto-validate.md +20 -121
- package/src/skf-brief-skill/references/version-resolution.md +3 -2
- package/src/skf-brief-skill/references/write-brief.md +66 -57
- package/src/skf-campaign/SKILL.md +31 -34
- package/src/skf-campaign/assets/campaign-state-schema.json +25 -4
- package/src/skf-campaign/customize.toml +47 -16
- package/src/skf-campaign/references/campaign-contracts.md +39 -29
- package/src/skf-campaign/references/campaign-directive-spec.md +4 -2
- package/src/skf-campaign/references/health-check.md +7 -16
- package/src/skf-campaign/references/step-01-setup.md +46 -84
- package/src/skf-campaign/references/step-02-strategy.md +31 -30
- package/src/skf-campaign/references/step-03-pins.md +26 -18
- package/src/skf-campaign/references/step-04-provenance.md +24 -23
- package/src/skf-campaign/references/step-05-skill-loop.md +67 -48
- package/src/skf-campaign/references/step-06-batch.md +41 -34
- package/src/skf-campaign/references/step-07-capstone.md +40 -25
- package/src/skf-campaign/references/step-08-verify.md +28 -29
- package/src/skf-campaign/references/step-09-refine.md +29 -30
- package/src/skf-campaign/references/step-10-export.md +48 -36
- package/src/skf-campaign/references/step-11-maintenance.md +31 -34
- package/src/skf-campaign/references/step-resume.md +37 -68
- package/src/skf-campaign/scripts/campaign-deps.py +57 -18
- package/src/skf-campaign/scripts/campaign-parse-manifest.py +361 -54
- package/src/skf-campaign/scripts/campaign-provenance.py +96 -53
- package/src/skf-campaign/scripts/campaign-quality-gate.py +592 -0
- package/src/skf-campaign/scripts/campaign-render-batch.py +257 -49
- package/src/skf-campaign/scripts/campaign-render-kickoff.py +343 -50
- package/src/skf-campaign/scripts/campaign-report.py +204 -37
- package/src/skf-campaign/scripts/campaign-state.py +1128 -0
- package/src/skf-campaign/scripts/campaign-status.py +10 -74
- package/src/skf-campaign/scripts/campaign-validate-pins.py +30 -3
- package/src/skf-campaign/scripts/campaign-validate-state.py +34 -4
- package/src/skf-campaign/templates/campaign-brief-template.yaml +4 -5
- package/src/skf-campaign/templates/campaign-report-template.md +5 -1
- package/src/skf-campaign/templates/kickoff-template.md +0 -14
- package/src/skf-create-skill/SKILL.md +27 -20
- package/src/skf-create-skill/assets/compile-assembly-rules.md +6 -5
- package/src/skf-create-skill/assets/skill-sections.md +17 -6
- package/src/skf-create-skill/customize.toml +20 -8
- package/src/skf-create-skill/references/authoritative-files-protocol.md +41 -42
- package/src/skf-create-skill/references/batch-mode.md +81 -0
- package/src/skf-create-skill/references/ccc-index-check.md +27 -0
- package/src/skf-create-skill/references/compile.md +107 -29
- package/src/skf-create-skill/references/component-extraction.md +113 -91
- package/src/skf-create-skill/references/enrich.md +41 -23
- package/src/skf-create-skill/references/entry-points-by-hand.md +20 -0
- package/src/skf-create-skill/references/extract.md +200 -161
- package/src/skf-create-skill/references/extraction-patterns-by-hand.md +141 -0
- package/src/skf-create-skill/references/extraction-patterns-tracing.md +2 -2
- package/src/skf-create-skill/references/extraction-patterns.md +584 -196
- package/src/skf-create-skill/references/generate-artifacts.md +145 -93
- package/src/skf-create-skill/references/health-check.md +1 -1
- package/src/skf-create-skill/references/load-brief.md +53 -36
- package/src/skf-create-skill/references/relabel-rule.md +12 -0
- package/src/skf-create-skill/references/report.md +28 -59
- package/src/skf-create-skill/references/source-resolution-protocols.md +49 -155
- package/src/skf-create-skill/references/step-auto-shard.md +20 -93
- package/src/skf-create-skill/references/step-doc-rot.md +44 -60
- package/src/skf-create-skill/references/step-doc-sources.md +55 -18
- package/src/skf-create-skill/references/sub/ccc-discover.md +18 -7
- package/src/skf-create-skill/references/sub/fetch-docs.md +106 -57
- package/src/skf-create-skill/references/sub/fetch-temporal.md +53 -166
- package/src/skf-create-skill/references/tier-degradation-rules.md +18 -9
- package/src/skf-create-skill/references/validate.md +173 -99
- package/src/skf-create-skill/scripts/scan-doc-rot.py +232 -99
- package/src/skf-create-stack-skill/SKILL.md +29 -56
- package/src/skf-create-stack-skill/assets/metadata-contract.md +51 -0
- package/src/skf-create-stack-skill/assets/provenance-map-schema.md +15 -15
- package/src/skf-create-stack-skill/assets/stack-skill-template.md +23 -57
- package/src/skf-create-stack-skill/customize.toml +23 -12
- package/src/skf-create-stack-skill/references/compile-stack.md +57 -66
- package/src/skf-create-stack-skill/references/compose-mode-rules.md +24 -44
- package/src/skf-create-stack-skill/references/detect-integrations.md +92 -95
- package/src/skf-create-stack-skill/references/detect-manifests.md +46 -71
- package/src/skf-create-stack-skill/references/generate-output.md +138 -128
- package/src/skf-create-stack-skill/references/init.md +90 -32
- package/src/skf-create-stack-skill/references/integration-patterns.md +1 -17
- package/src/skf-create-stack-skill/references/invocation-contract.md +34 -0
- package/src/skf-create-stack-skill/references/manifest-patterns.md +32 -20
- package/src/skf-create-stack-skill/references/parallel-extract.md +112 -38
- package/src/skf-create-stack-skill/references/rank-and-confirm.md +57 -75
- package/src/skf-create-stack-skill/references/report.md +39 -33
- package/src/skf-create-stack-skill/references/validate.md +20 -39
- package/src/skf-drop-skill/SKILL.md +28 -51
- package/src/skf-drop-skill/customize.toml +44 -30
- package/src/skf-drop-skill/references/execute.md +79 -146
- package/src/skf-drop-skill/references/invocation-contract.md +64 -0
- package/src/skf-drop-skill/references/report.md +46 -30
- package/src/skf-drop-skill/references/select.md +118 -124
- package/src/skf-drop-skill/scripts/dir-sizes.py +54 -26
- package/src/skf-drop-skill/scripts/drop-roster.py +322 -0
- package/src/skf-export-skill/SKILL.md +40 -62
- package/src/skf-export-skill/assets/managed-section-format.md +14 -50
- package/src/skf-export-skill/assets/snippet-format.md +3 -1
- package/src/skf-export-skill/customize.toml +42 -13
- package/src/skf-export-skill/references/generate-snippet.md +54 -46
- package/src/skf-export-skill/references/health-check.md +2 -0
- package/src/skf-export-skill/references/invocation-contract.md +15 -0
- package/src/skf-export-skill/references/load-skill.md +88 -62
- package/src/skf-export-skill/references/manifest-rebuild.md +9 -10
- package/src/skf-export-skill/references/multi-skill-mode.md +4 -4
- package/src/skf-export-skill/references/orphan-context-detection.md +13 -5
- package/src/skf-export-skill/references/orphan-row-detection.md +31 -22
- package/src/skf-export-skill/references/package.md +15 -73
- package/src/skf-export-skill/references/preflight-snippet-root-probe.md +51 -70
- package/src/skf-export-skill/references/result-envelope.md +31 -16
- package/src/skf-export-skill/references/summary.md +41 -17
- package/src/skf-export-skill/references/token-report.md +4 -12
- package/src/skf-export-skill/references/update-context.md +118 -237
- package/src/skf-forger/SKILL.md +61 -37
- package/src/skf-forger/bmad-skill-manifest.yaml +4 -1
- package/src/skf-forger/customize.toml +43 -0
- package/src/skf-forger/references/pipeline-mode.md +73 -18
- package/src/skf-forger/scripts/forge-status.py +299 -0
- package/src/skf-forger/scripts/parse-pipeline.py +322 -54
- package/src/skf-forger/scripts/pipeline-gate.py +428 -0
- package/src/skf-forger/scripts/pipeline-journal.py +1052 -0
- package/src/skf-quick-skill/SKILL.md +54 -53
- package/src/skf-quick-skill/assets/skill-template.md +4 -2
- package/src/skf-quick-skill/customize.toml +51 -20
- package/src/skf-quick-skill/references/batch-mode.md +74 -21
- package/src/skf-quick-skill/references/compile.md +30 -31
- package/src/skf-quick-skill/references/ecosystem-check.md +7 -57
- package/src/skf-quick-skill/references/finalize.md +69 -18
- package/src/skf-quick-skill/references/halt-contract.md +41 -29
- package/src/skf-quick-skill/references/health-check.md +5 -3
- package/src/skf-quick-skill/references/quick-extract.md +114 -45
- package/src/skf-quick-skill/references/registry-resolution.md +9 -64
- package/src/skf-quick-skill/references/resolve-target.md +116 -66
- package/src/skf-quick-skill/references/write-and-validate.md +79 -40
- package/src/skf-refine-architecture/SKILL.md +43 -53
- package/src/skf-refine-architecture/customize.toml +49 -20
- package/src/skf-refine-architecture/references/compile.md +97 -54
- package/src/skf-refine-architecture/references/exit-codes.md +30 -0
- package/src/skf-refine-architecture/references/finding-storage.md +21 -0
- package/src/skf-refine-architecture/references/gap-analysis.md +101 -38
- package/src/skf-refine-architecture/references/improvements.md +14 -20
- package/src/skf-refine-architecture/references/init.md +98 -57
- package/src/skf-refine-architecture/references/issue-detection.md +32 -39
- package/src/skf-refine-architecture/references/refinement-rules.md +37 -48
- package/src/skf-refine-architecture/references/report.md +65 -54
- package/src/skf-refine-architecture/scripts/skf-check-preservation.py +1619 -0
- package/src/skf-rename-skill/SKILL.md +19 -54
- package/src/skf-rename-skill/customize.toml +32 -26
- package/src/skf-rename-skill/references/execute.md +230 -184
- package/src/skf-rename-skill/references/exit-codes.md +7 -7
- package/src/skf-rename-skill/references/health-check.md +1 -7
- package/src/skf-rename-skill/references/invocation-contract.md +25 -0
- package/src/skf-rename-skill/references/report.md +63 -52
- package/src/skf-rename-skill/references/select.md +197 -115
- package/src/skf-rename-skill/scripts/skf-validate-rename-name.py +161 -21
- package/src/skf-setup/SKILL.md +26 -26
- package/src/skf-setup/customize.toml +14 -10
- package/src/skf-setup/references/auto-index.md +50 -63
- package/src/skf-setup/references/ccc-index.md +29 -89
- package/src/skf-setup/references/detect-and-tier.md +58 -66
- package/src/skf-setup/references/health-check.md +2 -0
- package/src/skf-setup/references/invocation-contract.md +14 -0
- package/src/skf-setup/references/report.md +43 -166
- package/src/skf-setup/references/tier-rules.md +5 -5
- package/src/skf-setup/references/write-config.md +34 -57
- package/src/skf-test-skill/SKILL.md +17 -57
- package/src/skf-test-skill/assets/output-section-formats.md +33 -34
- package/src/skf-test-skill/customize.toml +27 -13
- package/src/skf-test-skill/references/coherence-check.md +134 -62
- package/src/skf-test-skill/references/coverage-check-tiers.md +35 -0
- package/src/skf-test-skill/references/coverage-check.md +217 -254
- package/src/skf-test-skill/references/detect-mode.md +5 -5
- package/src/skf-test-skill/references/external-validators.md +94 -82
- package/src/skf-test-skill/references/health-check.md +6 -7
- package/src/skf-test-skill/references/init.md +163 -88
- package/src/skf-test-skill/references/invocation-contract.md +66 -0
- package/src/skf-test-skill/references/migration-section-rules.md +5 -2
- package/src/skf-test-skill/references/report.md +162 -142
- package/src/skf-test-skill/references/score.md +80 -107
- package/src/skf-test-skill/references/scoring-rules.md +41 -155
- package/src/skf-test-skill/references/source-access-protocol.md +19 -92
- package/src/skf-test-skill/references/step-hard-gate.md +51 -33
- package/src/skf-test-skill/scripts/aggregate-coherence.py +218 -30
- package/src/skf-test-skill/scripts/build-result-context.py +287 -0
- package/src/skf-test-skill/scripts/check-metadata-coherence.py +37 -4
- package/src/skf-test-skill/scripts/combine-external-scores.py +23 -6
- package/src/skf-test-skill/scripts/compute-score.py +223 -45
- package/src/skf-test-skill/scripts/gap-ledger.py +1381 -0
- package/src/skf-test-skill/scripts/hard-gate.py +203 -0
- package/src/skf-test-skill/scripts/load-coverage-inputs.py +760 -0
- package/src/skf-test-skill/scripts/locate-export-segments.py +835 -0
- package/src/skf-test-skill/scripts/reconcile-coverage.py +273 -79
- package/src/skf-test-skill/scripts/score-signatures.py +459 -0
- package/src/skf-test-skill/scripts/stage-helper-payload.py +148 -0
- package/src/skf-test-skill/scripts/validate-inventory.py +311 -16
- package/src/skf-test-skill/scripts/verify-declared-numerator.py +127 -52
- package/src/skf-test-skill/templates/test-report-template.md +21 -15
- package/src/skf-update-skill/SKILL.md +38 -25
- package/src/skf-update-skill/customize.toml +27 -12
- package/src/skf-update-skill/references/detect-changes.md +233 -222
- package/src/skf-update-skill/references/gap-driven.md +336 -0
- package/src/skf-update-skill/references/health-check.md +31 -5
- package/src/skf-update-skill/references/init.md +250 -84
- package/src/skf-update-skill/references/invocation-contract.md +12 -0
- package/src/skf-update-skill/references/manual-section-rules.md +6 -23
- package/src/skf-update-skill/references/merge.md +180 -47
- package/src/skf-update-skill/references/re-extract.md +53 -262
- package/src/skf-update-skill/references/report.md +100 -35
- package/src/skf-update-skill/references/write.md +331 -149
- package/src/skf-update-skill/scripts/skf-new-file-diff.py +29 -7
- package/src/skf-verify-stack/SKILL.md +23 -42
- package/src/skf-verify-stack/assets/feasibility-report-template.md +11 -5
- package/src/skf-verify-stack/customize.toml +55 -22
- package/src/skf-verify-stack/references/coverage-patterns.md +17 -8
- package/src/skf-verify-stack/references/coverage.md +67 -64
- package/src/skf-verify-stack/references/exit-codes.md +21 -8
- package/src/skf-verify-stack/references/init.md +107 -62
- package/src/skf-verify-stack/references/integration-verification-rules.md +12 -13
- package/src/skf-verify-stack/references/integrations.md +106 -65
- package/src/skf-verify-stack/references/report.md +59 -42
- package/src/skf-verify-stack/references/requirements.md +32 -9
- package/src/skf-verify-stack/references/synthesize.md +53 -39
- package/src/skf-verify-stack/scripts/skf-coverage-tally.py +398 -52
- package/src/skf-verify-stack/scripts/skf-previous-report.py +330 -0
- package/src/skf-verify-stack/scripts/skf-report-delta.py +453 -49
- package/src/skf-verify-stack/scripts/skf-verdict-rollup.py +240 -27
- package/tools/cli/commands/install.js +11 -4
- package/tools/cli/commands/status.js +15 -3
- package/tools/cli/commands/update.js +11 -4
- package/tools/cli/lib/tool-check.js +320 -0
- package/tools/cli/lib/version-check.js +22 -9
- package/tools/cli/skf-cli.js +4 -1
- package/.gitattributes +0 -16
- package/.nvmrc +0 -1
- package/docs/_internal/RELEASING.md +0 -645
- package/release-audits/v1.0.0-launch-audit.md +0 -728
- package/src/skf-audit-skill/references/step-doc-drift.md +0 -177
- package/src/skf-brief-skill/references/headless-source-authority-detection.md +0 -26
- package/src/skf-campaign/manifest.yaml +0 -15
- package/src/skf-create-skill/assets/tessl-dismissal-rules.md +0 -106
- package/src/skf-create-skill/references/ecosystem-check.md +0 -74
- package/src/skf-drop-skill/references/headless-contract.md +0 -18
- package/src/skf-update-skill/references/merge-conflict-rules.md +0 -15
- package/src/skf-update-skill/references/remote-source-resolution.md +0 -98
- package/src/skf-update-skill/references/validate.md +0 -124
- package/tools/validate-docs-drift.js +0 -233
- package/tools/validate-docs-links.js +0 -665
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# /// script
|
|
3
|
-
# requires-python = ">=3.
|
|
3
|
+
# requires-python = ">=3.11"
|
|
4
4
|
# ///
|
|
5
5
|
"""Deterministic parse + validation of a forger pipeline invocation.
|
|
6
6
|
|
|
7
7
|
Pipeline Mode (references/pipeline-mode.md §1-2) turns a raw code sequence
|
|
8
|
-
(`BS CS[cocoindex] TS[min:80] EX`, `AN -> CS -> TS -> EX`, or
|
|
9
|
-
`forge-
|
|
8
|
+
(`BS CS[cocoindex] TS[min:80] EX`, `AN -> CS -> TS -> EX`, or an alias such as
|
|
9
|
+
`forge-quick cognee`) into a normalized run plan and a list of sequence
|
|
10
|
+
anti-patterns.
|
|
10
11
|
Splitting the tokens, expanding aliases against a fixed table, classifying each
|
|
11
12
|
bracket argument, and detecting anti-patterns (duplicates, ordering, missing
|
|
12
13
|
prerequisites) is set/order plumbing with exactly one correct answer per input —
|
|
@@ -14,34 +15,75 @@ so it runs here, not in the prompt. The prompt keeps only the judgment the
|
|
|
14
15
|
script cannot make: interpreting the user's freeform request into a code
|
|
15
16
|
sequence, and deciding whether to proceed past a warned anti-pattern.
|
|
16
17
|
|
|
18
|
+
The forger passes the user's whole invocation, so an alias may carry its
|
|
19
|
+
arguments after it (`forge-auto <repo-or-doc-url>`, `forge <repo-url-or-path>
|
|
20
|
+
<skill-name>`, `forge-quick <package-or-url>`, `maintain <skill>`) and
|
|
21
|
+
`--pin <version>` may appear anywhere. The alias expands, and its arguments
|
|
22
|
+
bind, in order, to the inputs of the alias's first workflow (ALIAS_INPUTS);
|
|
23
|
+
`--pin` joins them, as the Pipeline Arguments rule says. An input left
|
|
24
|
+
unbound is reported, because that workflow requires it and a pipeline runs
|
|
25
|
+
headless. A chain of codes binds no input, so a runnable plan whose first
|
|
26
|
+
workflow (past a leading SF) starts from an input that no bracket gives
|
|
27
|
+
reports it as `first_input` (FIRST_INPUTS) and stays valid: the forger asks
|
|
28
|
+
for it, or halts headless, before any workflow runs. A quoted argument (a
|
|
29
|
+
path with a space) stays one token. Codes and the bracket keywords `min` and
|
|
30
|
+
`auto` match in any case; a bracket that starts like `min` but is not
|
|
31
|
+
`min:<number>` is reported as malformed instead of passing on as a target.
|
|
32
|
+
|
|
17
33
|
The alias-expansion and anti-pattern tables mirror
|
|
18
34
|
`src/shared/references/pipeline-contracts.md` (the human-readable contract);
|
|
19
35
|
this script is the executable form the forger consumes.
|
|
20
36
|
|
|
21
37
|
CLI usage:
|
|
22
38
|
uv run scripts/parse-pipeline.py 'BS CS TS EX' # positional
|
|
23
|
-
uv run scripts/parse-pipeline.py 'forge-
|
|
39
|
+
uv run scripts/parse-pipeline.py 'forge-quick cognee'
|
|
40
|
+
uv run scripts/parse-pipeline.py 'forge-quick' # exit 3: missing_args [target]
|
|
41
|
+
uv run scripts/parse-pipeline.py 'QS TS EX' # exit 0: first_input, QS's target
|
|
42
|
+
uv run scripts/parse-pipeline.py 'forge-auto https://github.com/o/r --pin 0.2.1'
|
|
24
43
|
echo 'AN -> CS -> TS -> EX' | uv run scripts/parse-pipeline.py --stdin
|
|
25
44
|
|
|
26
45
|
Output (stdout, one object):
|
|
27
46
|
{
|
|
28
47
|
"raw": "<input>",
|
|
29
|
-
"alias": "forge" | null, # set when the input
|
|
48
|
+
"alias": "forge" | null, # set when the input starts with an alias
|
|
30
49
|
"deprecated_alias": "deepwiki" | null,
|
|
31
50
|
"removed_alias": "onboard" | null,
|
|
32
51
|
"expanded": ["BS", "CS", "TS", "EX"], # normalized tokens (post-expansion)
|
|
33
52
|
"plan": [{"code","min","mode","target"}, ...],
|
|
34
53
|
"codes": ["BS", "CS", "TS", "EX"],
|
|
54
|
+
"args": {"<input>": "<value>", "pin": "<version>"}, # for the first workflow
|
|
35
55
|
"unknown_codes": [],
|
|
56
|
+
"unexpected_args": [], # extra alias arguments, unknown flags
|
|
57
|
+
"missing_args": [], # alias inputs left unbound, e.g. "target"
|
|
58
|
+
"malformed_brackets": [], # e.g. "TS[min:80%]"
|
|
36
59
|
"anti_patterns": [{"pattern","message","suggestion"}, ...],
|
|
37
|
-
"
|
|
38
|
-
|
|
60
|
+
"first_input": null | {"code","input","form","halt_reason"},
|
|
61
|
+
"valid": <bool> # runnable: real codes, nothing unknown,
|
|
62
|
+
} # unexpected, missing or malformed, not
|
|
63
|
+
# removed
|
|
64
|
+
|
|
65
|
+
`args` holds only what the invocation gave: `project_path` (forge-auto),
|
|
66
|
+
`target_repo` and `skill_name` (forge), `target` (forge-quick), `skill_name`
|
|
67
|
+
(maintain), and `pin`. `--headless`/`-H` is Ferris's own flag, read at
|
|
68
|
+
activation, so it is accepted and not returned.
|
|
69
|
+
|
|
70
|
+
`first_input` names the input the chain's first workflow lacks (`input`, as
|
|
71
|
+
"a target"). `form` is the invocation to parse again with the answer in
|
|
72
|
+
place of each `<...>` placeholder (`QS[<target>] TS EX`), or null when no
|
|
73
|
+
chain can carry that input: RS and DS need more than one bracket, a BS
|
|
74
|
+
outside `forge <repo-url-or-path> <skill-name>` has no bracket for its skill
|
|
75
|
+
name, and a bracket that already holds `min:N` or `auto` holds one value.
|
|
76
|
+
`halt_reason` is what a headless run halts with (`QS needs a target before
|
|
77
|
+
the pipeline can start: give it as QS[<target>]`), or null for a leading CS:
|
|
78
|
+
create-skill compiles the only brief, or halts naming the briefs.
|
|
39
79
|
|
|
40
80
|
Exit codes:
|
|
41
|
-
0
|
|
42
|
-
1
|
|
43
|
-
2
|
|
44
|
-
3
|
|
81
|
+
0 parsed, runnable (anti-patterns and a deprecated alias are warnings)
|
|
82
|
+
1 no input provided (usage error)
|
|
83
|
+
2 removed alias (e.g. `onboard`): the forger HALTs
|
|
84
|
+
3 nothing runnable (empty, or an unknown code, an unexpected or missing
|
|
85
|
+
argument or a malformed bracket): the forger names the problem and asks,
|
|
86
|
+
or halts headless, before any workflow runs
|
|
45
87
|
"""
|
|
46
88
|
|
|
47
89
|
from __future__ import annotations
|
|
@@ -49,6 +91,7 @@ from __future__ import annotations
|
|
|
49
91
|
import argparse
|
|
50
92
|
import json
|
|
51
93
|
import re
|
|
94
|
+
import shlex
|
|
52
95
|
import sys
|
|
53
96
|
|
|
54
97
|
# Workflow codes that can appear in a pipeline. KI/WS are inline actions, not
|
|
@@ -57,9 +100,11 @@ KNOWN_CODES = frozenset(
|
|
|
57
100
|
{"SF", "AN", "BS", "CS", "QS", "SS", "US", "AS", "VS", "RA", "TS", "EX", "RS", "DS"}
|
|
58
101
|
)
|
|
59
102
|
|
|
60
|
-
# Only these
|
|
61
|
-
#
|
|
62
|
-
|
|
103
|
+
# Only these take a `min:N`: AN as the fewest units it must confirm (the
|
|
104
|
+
# pipeline gate's --min) and TS as its test threshold (--threshold). CS, AS and
|
|
105
|
+
# VS have circuit breakers with no number to set, so on them, as on any other
|
|
106
|
+
# code, a `min:N` bracket is ignored (recorded as `min: null`, with a warning).
|
|
107
|
+
THRESHOLD_CODES = frozenset({"AN", "TS"})
|
|
63
108
|
|
|
64
109
|
# Alias table — mirrors pipeline-contracts.md. `forge-auto`'s TS[min:90] gate is
|
|
65
110
|
# non-default and is kept in lockstep with init.md §1b's forge-auto → 90 lookup.
|
|
@@ -72,27 +117,146 @@ ALIASES = {
|
|
|
72
117
|
DEPRECATED_ALIASES = {"deepwiki": "forge-auto"} # renamed; still expands
|
|
73
118
|
REMOVED_ALIASES = {"onboard"} # no expansion — the forger HALTs
|
|
74
119
|
|
|
120
|
+
# The arguments each alias takes after it, in order. Each names the input of
|
|
121
|
+
# the alias's first workflow that receives it: analyze-source's project_path,
|
|
122
|
+
# brief-skill's target_repo and skill_name, quick-skill's target and
|
|
123
|
+
# audit-skill's skill_name. Mirrors the alias forms in SKILL.md's Pipelines
|
|
124
|
+
# paragraph.
|
|
125
|
+
ALIAS_INPUTS = {
|
|
126
|
+
"forge-auto": ("project_path",),
|
|
127
|
+
"forge": ("target_repo", "skill_name"),
|
|
128
|
+
"forge-quick": ("target",),
|
|
129
|
+
"maintain": ("skill_name",),
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
# What a code's workflow starts from at the head of a chain of codes, where no
|
|
133
|
+
# alias argument binds it: the `args` keys that give it, the input as the ask
|
|
134
|
+
# names it, and the placeholder its bracket takes. A pipeline runs every
|
|
135
|
+
# workflow headless, so one left without its input halts at once. SF and SS
|
|
136
|
+
# start from none, and the check looks past a leading SF. CS's input is its
|
|
137
|
+
# brief: headless, create-skill compiles the only brief or halts naming them,
|
|
138
|
+
# so a leading CS is asked for its input but never halted for it.
|
|
139
|
+
FIRST_INPUTS = {
|
|
140
|
+
"AN": (("project_path",), "a project path", "<project-path>"),
|
|
141
|
+
"QS": (("target",), "a target", "<target>"),
|
|
142
|
+
"CS": (("skill_name", "brief_path"), "a skill name", "<skill-name>"),
|
|
143
|
+
"TS": (("skill_name",), "a skill name", "<skill-name>"),
|
|
144
|
+
"EX": (("skill_name",), "a skill name", "<skill-name>"),
|
|
145
|
+
"US": (("skill_name",), "a skill name", "<skill-name>"),
|
|
146
|
+
"AS": (("skill_name",), "a skill name", "<skill-name>"),
|
|
147
|
+
"VS": (("architecture_doc_path",), "an architecture document", "<architecture-doc>"),
|
|
148
|
+
"RA": (("architecture_doc_path",), "an architecture document", "<architecture-doc>"),
|
|
149
|
+
}
|
|
150
|
+
# BS needs a target and a skill name, and a bracket holds one value: only the
|
|
151
|
+
# `forge` alias gives it both.
|
|
152
|
+
BS_INPUTS = ("target_repo", "skill_name")
|
|
153
|
+
FORGE_FORM = "forge <repo-url-or-path> <skill-name>"
|
|
154
|
+
# These need more than a bracket can hold (rename-skill a new name, drop-skill
|
|
155
|
+
# a version and a drop mode), so they run on their own, outside a chain.
|
|
156
|
+
ON_THEIR_OWN = {
|
|
157
|
+
"RS": "a skill name and its new name",
|
|
158
|
+
"DS": "a skill name, a version and a drop mode",
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
# Pipeline-level flags (pipeline-contracts.md Pipeline Arguments): each takes a
|
|
162
|
+
# value and reaches the first workflow as the named input.
|
|
163
|
+
VALUE_FLAGS = {"--pin": "pin"}
|
|
164
|
+
# Ferris's own flag: On Activation already read it from the invocation.
|
|
165
|
+
IGNORED_FLAGS = frozenset({"--headless", "-H"})
|
|
166
|
+
|
|
75
167
|
_TOKEN_RE = re.compile(r"^([A-Za-z]+)(?:\[([^\]]*)\])?$")
|
|
76
168
|
_ARROWS = re.compile(r"->|→|—>|»")
|
|
169
|
+
_MIN_RE = re.compile(r"min:(\d+)", re.IGNORECASE)
|
|
170
|
+
# `min`, `min:`, `min=80`, `MIN:80%`: a threshold written wrong. A target such
|
|
171
|
+
# as `minimatch` does not match, because no `:` or `=` follows `min`.
|
|
172
|
+
_MIN_LIKE_RE = re.compile(r"min\s*(?:[:=]|$)", re.IGNORECASE)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _unquote(token):
|
|
176
|
+
"""Drop one pair of matching quotes around a token."""
|
|
177
|
+
if len(token) >= 2 and token[0] == token[-1] and token[0] in "'\"":
|
|
178
|
+
return token[1:-1]
|
|
179
|
+
return token
|
|
77
180
|
|
|
78
181
|
|
|
79
182
|
def _tokenize(raw):
|
|
80
|
-
"""Split a raw
|
|
183
|
+
"""Split a raw invocation on whitespace and arrow separators.
|
|
184
|
+
|
|
185
|
+
A quoted argument stays one token, without its quotes. Backslashes are
|
|
186
|
+
kept as they are, so a Windows path survives; an unbalanced quote falls
|
|
187
|
+
back to a plain whitespace split.
|
|
188
|
+
"""
|
|
81
189
|
normalized = _ARROWS.sub(" ", raw)
|
|
82
|
-
|
|
190
|
+
lexer = shlex.shlex(normalized, posix=False)
|
|
191
|
+
lexer.whitespace_split = True
|
|
192
|
+
lexer.commenters = "" # a URL fragment (`#readme`) is not a comment
|
|
193
|
+
try:
|
|
194
|
+
tokens = list(lexer)
|
|
195
|
+
except ValueError:
|
|
196
|
+
return normalized.split()
|
|
197
|
+
return [t for t in map(_unquote, tokens) if t]
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _split_flags(tokens):
|
|
201
|
+
"""Return (other tokens, args from flags, unexpected flag tokens).
|
|
202
|
+
|
|
203
|
+
`--pin <version>` and `--pin=<version>` give `pin`. A flag with no value,
|
|
204
|
+
one given twice or one the pipeline does not take is unexpected: which
|
|
205
|
+
value was meant has no single answer, so the parse reports it.
|
|
206
|
+
"""
|
|
207
|
+
rest, args, unexpected = [], {}, []
|
|
208
|
+
tokens = list(tokens)
|
|
209
|
+
while tokens:
|
|
210
|
+
tok = tokens.pop(0)
|
|
211
|
+
if not tok.startswith("-") or tok == "-":
|
|
212
|
+
rest.append(tok)
|
|
213
|
+
continue
|
|
214
|
+
if tok in IGNORED_FLAGS:
|
|
215
|
+
continue
|
|
216
|
+
name, eq, value = tok.partition("=")
|
|
217
|
+
if name not in VALUE_FLAGS:
|
|
218
|
+
unexpected.append(tok)
|
|
219
|
+
continue
|
|
220
|
+
if not eq and tokens and not tokens[0].startswith("-"):
|
|
221
|
+
value = tokens.pop(0)
|
|
222
|
+
tok = f"{name} {value}"
|
|
223
|
+
key = VALUE_FLAGS[name]
|
|
224
|
+
if not value or key in args:
|
|
225
|
+
unexpected.append(tok)
|
|
226
|
+
else:
|
|
227
|
+
args[key] = value
|
|
228
|
+
return rest, args, unexpected
|
|
83
229
|
|
|
84
230
|
|
|
85
231
|
def _classify_bracket(code, value):
|
|
86
|
-
"""Return (min, mode, target) for a bracket value on a
|
|
232
|
+
"""Return (min, mode, target, malformed) for a bracket value on a code.
|
|
233
|
+
|
|
234
|
+
Keywords match in any case (`TS[MIN:80]`, `AN[Auto]`). `min:N` counts only
|
|
235
|
+
on AN and TS; elsewhere it is recorded as `min: null`.
|
|
236
|
+
"""
|
|
87
237
|
if value is None or value == "":
|
|
88
|
-
return None, None, None
|
|
89
|
-
m =
|
|
238
|
+
return None, None, None, False
|
|
239
|
+
m = _MIN_RE.fullmatch(value)
|
|
240
|
+
if m:
|
|
241
|
+
# min:N only applies to AN and TS; ignored elsewhere.
|
|
242
|
+
return (int(m.group(1)) if code in THRESHOLD_CODES else None), None, None, False
|
|
243
|
+
if _MIN_LIKE_RE.match(value):
|
|
244
|
+
return None, None, None, True
|
|
245
|
+
if value.lower() == "auto":
|
|
246
|
+
return None, "auto", None, False
|
|
247
|
+
return None, None, value, False
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _normalized_token(code, value):
|
|
251
|
+
"""The token as `expanded` lists it: keywords lower case, `min:N` as a number."""
|
|
252
|
+
if value in (None, ""):
|
|
253
|
+
return code
|
|
254
|
+
m = _MIN_RE.fullmatch(value)
|
|
90
255
|
if m:
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
return None, None, value
|
|
256
|
+
return f"{code}[min:{int(m.group(1))}]"
|
|
257
|
+
if value.lower() == "auto":
|
|
258
|
+
return f"{code}[auto]"
|
|
259
|
+
return f"{code}[{value}]"
|
|
96
260
|
|
|
97
261
|
|
|
98
262
|
def _detect_anti_patterns(codes):
|
|
@@ -154,9 +318,86 @@ def _detect_anti_patterns(codes):
|
|
|
154
318
|
return found
|
|
155
319
|
|
|
156
320
|
|
|
321
|
+
def _min_ignored(tokens):
|
|
322
|
+
"""The warning for `min:N` brackets on codes other than AN and TS."""
|
|
323
|
+
return {
|
|
324
|
+
"pattern": "min-ignored",
|
|
325
|
+
"codes": [t.split("[", 1)[0] for t in tokens],
|
|
326
|
+
"message": f"{', '.join(tokens)} is ignored: only AN (a unit count) and TS (a test threshold) take min:N",
|
|
327
|
+
"suggestion": "remove it, or put the threshold on TS (TS[min:N])",
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def _form(result, index, token):
|
|
332
|
+
"""The invocation again, with `token` in place of the plan entry at `index`."""
|
|
333
|
+
tokens = list(result["expanded"])
|
|
334
|
+
tokens[index] = token
|
|
335
|
+
return _with_pin(" ".join(tokens), result["args"])
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _with_pin(text, args):
|
|
339
|
+
return f"{text} --pin {shlex.quote(args['pin'])}" if args.get("pin") else text
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def _needs(code, needs, form, rest):
|
|
343
|
+
"""A `first_input` entry; `rest` follows the reason's fixed opening."""
|
|
344
|
+
reason = f"{code} needs {needs} before the pipeline can start{rest}" if rest is not None else None
|
|
345
|
+
return {"code": code, "input": needs, "form": form, "halt_reason": reason}
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def _bs_input(result, index, entry):
|
|
349
|
+
"""BS's two inputs: the forge alias gives both, a chain of codes no skill name."""
|
|
350
|
+
if all(key in result["args"] for key in BS_INPUTS):
|
|
351
|
+
return None
|
|
352
|
+
target = entry["target"]
|
|
353
|
+
after = result["plan"][index + 1:]
|
|
354
|
+
if (index == 0 and result["codes"] == ALIASES["forge"] and entry["mode"] is None
|
|
355
|
+
and all(e["min"] is None and e["mode"] is None and e["target"] is None for e in after)):
|
|
356
|
+
needs = "a skill name" if target else "a target and a skill name"
|
|
357
|
+
form = FORGE_FORM.replace("<repo-url-or-path>", target) if target else FORGE_FORM
|
|
358
|
+
return _needs("BS", needs, _with_pin(form, result["args"]),
|
|
359
|
+
f": give {'it' if target else 'them'} as {form}")
|
|
360
|
+
return _needs("BS", "a target and a skill name", None,
|
|
361
|
+
f", and a chain gives it one bracket: run BS on its own first, or use {FORGE_FORM}")
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def _first_input(result):
|
|
365
|
+
"""The input the chain's first workflow lacks, or None (pipeline-mode.md step 1).
|
|
366
|
+
|
|
367
|
+
Only for a runnable plan. The first workflow is the first plan entry
|
|
368
|
+
other than SF, and it lacks its input when no bracket target and no
|
|
369
|
+
alias argument gives it.
|
|
370
|
+
"""
|
|
371
|
+
if not result["valid"]:
|
|
372
|
+
return None
|
|
373
|
+
index = next((i for i, e in enumerate(result["plan"]) if e["code"] != "SF"), None)
|
|
374
|
+
if index is None:
|
|
375
|
+
return None
|
|
376
|
+
entry = result["plan"][index]
|
|
377
|
+
code = entry["code"]
|
|
378
|
+
if code in ON_THEIR_OWN:
|
|
379
|
+
return _needs(code, ON_THEIR_OWN[code], None,
|
|
380
|
+
f", and a chain gives it one bracket: run {code} on its own, outside a chain")
|
|
381
|
+
if code == "BS":
|
|
382
|
+
return _bs_input(result, index, entry)
|
|
383
|
+
if code not in FIRST_INPUTS:
|
|
384
|
+
return None
|
|
385
|
+
keys, needs, placeholder = FIRST_INPUTS[code]
|
|
386
|
+
if entry["target"] is not None or any(key in result["args"] for key in keys):
|
|
387
|
+
return None
|
|
388
|
+
taken = "auto" if entry["mode"] else (f"min:{entry['min']}" if entry["min"] is not None else None)
|
|
389
|
+
token = f"{code}[{placeholder}]"
|
|
390
|
+
if code == "CS":
|
|
391
|
+
return None if taken else _needs(code, needs, _form(result, index, token), None)
|
|
392
|
+
if taken:
|
|
393
|
+
hint = ", so start the chain as forge-auto <repo-or-doc-url>" if code == "AN" and taken == "auto" else ""
|
|
394
|
+
return _needs(code, needs, None,
|
|
395
|
+
f", and its bracket already holds {taken}: a bracket holds one value{hint}")
|
|
396
|
+
return _needs(code, needs, _form(result, index, token), f": give it as {token}")
|
|
397
|
+
|
|
398
|
+
|
|
157
399
|
def parse_pipeline(raw):
|
|
158
|
-
"""Parse and validate a raw pipeline
|
|
159
|
-
tokens = _tokenize(raw)
|
|
400
|
+
"""Parse and validate a raw pipeline invocation. Deterministic."""
|
|
160
401
|
result = {
|
|
161
402
|
"raw": raw,
|
|
162
403
|
"alias": None,
|
|
@@ -165,29 +406,42 @@ def parse_pipeline(raw):
|
|
|
165
406
|
"expanded": [],
|
|
166
407
|
"plan": [],
|
|
167
408
|
"codes": [],
|
|
409
|
+
"args": {},
|
|
168
410
|
"unknown_codes": [],
|
|
411
|
+
"unexpected_args": [],
|
|
412
|
+
"missing_args": [],
|
|
413
|
+
"malformed_brackets": [],
|
|
169
414
|
"anti_patterns": [],
|
|
415
|
+
"first_input": None,
|
|
170
416
|
"valid": False,
|
|
171
417
|
}
|
|
418
|
+
tokens, args, unexpected = _split_flags(_tokenize(raw))
|
|
419
|
+
result["args"] = args
|
|
420
|
+
result["unexpected_args"] = unexpected
|
|
172
421
|
if not tokens:
|
|
173
422
|
return result
|
|
174
423
|
|
|
175
|
-
#
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
424
|
+
# A leading alias expands; the tokens after it are its arguments.
|
|
425
|
+
lead = tokens[0].lower()
|
|
426
|
+
if lead in REMOVED_ALIASES:
|
|
427
|
+
result["removed_alias"] = lead
|
|
428
|
+
return result
|
|
429
|
+
alias = None
|
|
430
|
+
if lead in DEPRECATED_ALIASES:
|
|
431
|
+
result["deprecated_alias"] = lead
|
|
432
|
+
alias = DEPRECATED_ALIASES[lead]
|
|
433
|
+
elif lead in ALIASES:
|
|
434
|
+
alias = lead
|
|
435
|
+
if alias:
|
|
436
|
+
result["alias"] = alias
|
|
437
|
+
positionals = tokens[1:]
|
|
438
|
+
names = ALIAS_INPUTS[alias]
|
|
439
|
+
result["args"] = {**dict(zip(names, positionals)), **args}
|
|
440
|
+
result["unexpected_args"] = positionals[len(names):] + unexpected
|
|
441
|
+
result["missing_args"] = list(names[len(positionals):])
|
|
442
|
+
tokens = list(ALIASES[alias])
|
|
443
|
+
|
|
444
|
+
ignored_min = []
|
|
191
445
|
for tok in tokens:
|
|
192
446
|
m = _TOKEN_RE.match(tok)
|
|
193
447
|
if not m:
|
|
@@ -195,20 +449,31 @@ def parse_pipeline(raw):
|
|
|
195
449
|
continue
|
|
196
450
|
code = m.group(1).upper()
|
|
197
451
|
value = m.group(2)
|
|
198
|
-
result["expanded"].append(
|
|
199
|
-
f"{code}[{value}]" if value not in (None, "") else code
|
|
200
|
-
)
|
|
452
|
+
result["expanded"].append(_normalized_token(code, value))
|
|
201
453
|
if code not in KNOWN_CODES:
|
|
202
454
|
result["unknown_codes"].append(tok)
|
|
203
455
|
continue
|
|
204
|
-
min_n, mode, target = _classify_bracket(code, value)
|
|
456
|
+
min_n, mode, target, malformed = _classify_bracket(code, value)
|
|
457
|
+
if malformed:
|
|
458
|
+
result["malformed_brackets"].append(tok)
|
|
459
|
+
elif value and _MIN_RE.fullmatch(value) and code not in THRESHOLD_CODES:
|
|
460
|
+
ignored_min.append(_normalized_token(code, value))
|
|
205
461
|
result["codes"].append(code)
|
|
206
462
|
result["plan"].append(
|
|
207
463
|
{"code": code, "min": min_n, "mode": mode, "target": target}
|
|
208
464
|
)
|
|
209
465
|
|
|
210
466
|
result["anti_patterns"] = _detect_anti_patterns(result["codes"])
|
|
211
|
-
|
|
467
|
+
if ignored_min:
|
|
468
|
+
result["anti_patterns"].append(_min_ignored(ignored_min))
|
|
469
|
+
result["valid"] = (
|
|
470
|
+
bool(result["codes"])
|
|
471
|
+
and not result["unknown_codes"]
|
|
472
|
+
and not result["unexpected_args"]
|
|
473
|
+
and not result["missing_args"]
|
|
474
|
+
and not result["malformed_brackets"]
|
|
475
|
+
)
|
|
476
|
+
result["first_input"] = _first_input(result)
|
|
212
477
|
return result
|
|
213
478
|
|
|
214
479
|
|
|
@@ -220,9 +485,9 @@ def _build_parser():
|
|
|
220
485
|
prog="parse-pipeline",
|
|
221
486
|
description=(
|
|
222
487
|
"Deterministic parse + validation of a forger pipeline invocation "
|
|
223
|
-
"(pipeline-mode.md §1-2): tokenize, expand aliases,
|
|
224
|
-
"arguments
|
|
225
|
-
"run plan as JSON."
|
|
488
|
+
"(pipeline-mode.md §1-2): tokenize, expand aliases, bind an alias's "
|
|
489
|
+
"arguments to its first workflow, classify bracket arguments, and "
|
|
490
|
+
"detect sequence anti-patterns, returning a normalized run plan as JSON."
|
|
226
491
|
),
|
|
227
492
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
228
493
|
)
|
|
@@ -230,19 +495,22 @@ def _build_parser():
|
|
|
230
495
|
src.add_argument(
|
|
231
496
|
"sequence",
|
|
232
497
|
nargs="?",
|
|
233
|
-
help="
|
|
498
|
+
help="The whole pipeline invocation as a positional argument.",
|
|
234
499
|
)
|
|
235
500
|
src.add_argument(
|
|
236
501
|
"--stdin",
|
|
237
502
|
action="store_true",
|
|
238
|
-
help="Read the
|
|
503
|
+
help="Read the whole pipeline invocation from stdin.",
|
|
239
504
|
)
|
|
240
505
|
return parser
|
|
241
506
|
|
|
242
507
|
|
|
243
508
|
def _resolve_input(args):
|
|
244
509
|
if args.stdin:
|
|
245
|
-
|
|
510
|
+
# A Windows console reads cp1252: the arrows and paths need UTF-8.
|
|
511
|
+
if hasattr(sys.stdin, "reconfigure"):
|
|
512
|
+
sys.stdin.reconfigure(encoding="utf-8")
|
|
513
|
+
return sys.stdin.read().strip()
|
|
246
514
|
if args.sequence is not None:
|
|
247
515
|
return args.sequence
|
|
248
516
|
return ""
|
|
@@ -267,4 +535,4 @@ def main(argv=None):
|
|
|
267
535
|
|
|
268
536
|
|
|
269
537
|
if __name__ == "__main__":
|
|
270
|
-
|
|
538
|
+
sys.exit(main())
|