bmad-module-skill-forge 0.9.0 → 0.10.1-alpha.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 +39 -0
- package/.gitattributes +16 -0
- package/README.md +105 -38
- package/docs/404.md +13 -10
- package/docs/RELEASING.md +185 -0
- package/docs/STABILITY.md +123 -0
- package/docs/_data/pinned.yaml +98 -0
- package/docs/agents.md +16 -11
- package/docs/architecture.md +221 -0
- package/docs/bmad-synergy.md +11 -13
- package/docs/concepts.md +18 -108
- package/docs/examples.md +121 -80
- package/docs/getting-started.md +85 -147
- package/docs/how-it-works.md +25 -536
- package/docs/index.md +44 -27
- package/docs/skill-model.md +328 -0
- package/docs/troubleshooting.md +39 -0
- package/docs/verifying-a-skill.md +232 -0
- package/docs/why-skf.md +93 -0
- package/docs/workflows.md +119 -13
- package/package.json +25 -4
- package/src/README.md +23 -25
- package/src/forger/forge-tier.yaml +4 -2
- package/src/forger/preferences.yaml +7 -1
- package/src/knowledge/agentskills-spec.md +1 -1
- package/src/knowledge/architecture-verification.md +1 -1
- package/src/knowledge/ccc-bridge.md +16 -15
- package/src/knowledge/overview.md +11 -11
- package/src/knowledge/progressive-capability.md +3 -3
- package/src/knowledge/provenance-tracking.md +9 -3
- package/src/knowledge/qmd-registry.md +8 -8
- package/src/knowledge/skf-knowledge-index.csv +2 -2
- package/src/knowledge/skill-lifecycle.md +16 -1
- package/src/knowledge/split-body-strategy.md +1 -1
- package/src/knowledge/version-paths.md +17 -12
- package/src/module-help.csv +18 -17
- package/src/module.yaml +4 -0
- package/src/shared/health-check.md +372 -0
- package/src/shared/references/feasibility-report-schema.md +86 -0
- package/src/shared/references/headless-gate-convention.md +62 -0
- package/src/shared/references/output-contract-schema.md +35 -0
- package/src/shared/references/pipeline-contracts.md +102 -0
- package/src/shared/scripts/skf-atomic-write.py +370 -0
- package/src/shared/scripts/skf-manifest-ops.py +236 -0
- package/src/shared/scripts/skf-preflight.py +164 -0
- package/src/shared/scripts/skf-rebuild-managed-sections.py +201 -0
- package/src/shared/scripts/skf-severity-classify.py +163 -0
- package/src/shared/scripts/skf-skill-inventory.py +210 -0
- package/src/shared/scripts/skf-structural-diff.py +257 -0
- package/src/shared/scripts/skf-validate-frontmatter.py +318 -0
- package/src/shared/scripts/skf-validate-output.py +247 -0
- package/src/skf-analyze-source/SKILL.md +57 -0
- package/src/skf-analyze-source/assets/skill-brief-schema.md +125 -0
- package/src/skf-analyze-source/references/unit-detection-heuristics.md +124 -0
- package/src/{workflows/analyze-source → skf-analyze-source}/steps-c/step-01-init.md +6 -66
- package/src/{workflows/analyze-source → skf-analyze-source}/steps-c/step-01b-continue.md +4 -59
- package/src/{workflows/analyze-source → skf-analyze-source}/steps-c/step-02-scan-project.md +7 -68
- package/src/{workflows/analyze-source → skf-analyze-source}/steps-c/step-03-identify-units.md +6 -66
- package/src/{workflows/analyze-source → skf-analyze-source}/steps-c/step-04-map-and-detect.md +7 -68
- package/src/{workflows/analyze-source → skf-analyze-source}/steps-c/step-05-recommend.md +6 -66
- package/src/{workflows/analyze-source → skf-analyze-source}/steps-c/step-06-generate-briefs.md +14 -66
- package/src/skf-analyze-source/steps-c/step-07-health-check.md +22 -0
- package/src/skf-audit-skill/SKILL.md +59 -0
- package/src/{workflows/audit-skill/data → skf-audit-skill/assets}/drift-report-template.md +7 -7
- package/src/{workflows/audit-skill/data → skf-audit-skill/references}/severity-rules.md +11 -11
- package/src/{workflows/audit-skill → skf-audit-skill}/steps-c/step-01-init.md +24 -68
- package/src/{workflows/audit-skill → skf-audit-skill}/steps-c/step-02-re-index.md +36 -71
- package/src/{workflows/audit-skill → skf-audit-skill}/steps-c/step-03-structural-diff.md +21 -63
- package/src/{workflows/audit-skill → skf-audit-skill}/steps-c/step-04-semantic-diff.md +6 -65
- package/src/{workflows/audit-skill → skf-audit-skill}/steps-c/step-05-severity-classify.md +5 -63
- package/src/{workflows/audit-skill → skf-audit-skill}/steps-c/step-06-report.md +10 -59
- package/src/skf-audit-skill/steps-c/step-07-health-check.md +22 -0
- package/src/skf-brief-skill/SKILL.md +53 -0
- package/src/{workflows/brief-skill/data → skf-brief-skill/assets}/scope-templates.md +23 -0
- package/src/skf-brief-skill/assets/skill-brief-schema.md +206 -0
- package/src/{workflows/brief-skill → skf-brief-skill}/steps-c/step-01-gather-intent.md +6 -67
- package/src/{workflows/brief-skill → skf-brief-skill}/steps-c/step-02-analyze-target.md +6 -66
- package/src/{workflows/brief-skill → skf-brief-skill}/steps-c/step-03-scope-definition.md +12 -68
- package/src/{workflows/brief-skill → skf-brief-skill}/steps-c/step-04-confirm-brief.md +5 -63
- package/src/{workflows/brief-skill → skf-brief-skill}/steps-c/step-05-write-brief.md +10 -69
- package/src/skf-brief-skill/steps-c/step-06-health-check.md +22 -0
- package/src/skf-create-skill/SKILL.md +63 -0
- package/src/{workflows/create-skill/data → skf-create-skill/assets}/compile-assembly-rules.md +60 -0
- package/src/{workflows/create-skill/data → skf-create-skill/assets}/skill-sections.md +92 -23
- package/src/skf-create-skill/assets/tessl-dismissal-rules.md +102 -0
- package/src/{workflows/create-skill/data → skf-create-skill/references}/extraction-patterns-tracing.md +13 -13
- package/src/{workflows/create-skill/data → skf-create-skill/references}/extraction-patterns.md +8 -3
- package/src/skf-create-skill/references/source-resolution-protocols.md +239 -0
- package/src/{workflows/create-skill/data → skf-create-skill/references}/tier-degradation-rules.md +8 -7
- package/src/{workflows/create-skill → skf-create-skill}/steps-c/step-01-load-brief.md +25 -63
- package/src/{workflows/create-skill → skf-create-skill}/steps-c/step-02-ecosystem-check.md +8 -64
- package/src/skf-create-skill/steps-c/step-03-extract.md +379 -0
- package/src/{workflows/create-skill → skf-create-skill}/steps-c/step-03d-component-extraction.md +18 -35
- package/src/{workflows/create-skill → skf-create-skill}/steps-c/step-04-enrich.md +12 -69
- package/src/{workflows/create-skill → skf-create-skill}/steps-c/step-05-compile.md +71 -75
- package/src/skf-create-skill/steps-c/step-06-validate.md +267 -0
- package/src/{workflows/create-skill → skf-create-skill}/steps-c/step-07-generate-artifacts.md +30 -74
- package/src/skf-create-skill/steps-c/step-08-report.md +145 -0
- package/src/skf-create-skill/steps-c/step-09-health-check.md +23 -0
- package/src/{workflows/create-skill/steps-c → skf-create-skill/steps-c/sub}/step-02b-ccc-discover.md +8 -66
- package/src/skf-create-skill/steps-c/sub/step-03b-fetch-temporal.md +229 -0
- package/src/{workflows/create-skill/steps-c → skf-create-skill/steps-c/sub}/step-03c-fetch-docs.md +18 -42
- package/src/skf-create-stack-skill/SKILL.md +64 -0
- package/src/{workflows/create-stack-skill/data → skf-create-stack-skill/assets}/stack-skill-template.md +2 -1
- package/src/{workflows/create-stack-skill/data → skf-create-stack-skill/references}/compose-mode-rules.md +19 -5
- package/src/{workflows/create-stack-skill/data → skf-create-stack-skill/references}/integration-patterns.md +1 -1
- package/src/{workflows/create-stack-skill/data → skf-create-stack-skill/references}/manifest-patterns.md +10 -10
- package/src/{workflows/create-stack-skill → skf-create-stack-skill}/steps-c/step-01-init.md +14 -55
- package/src/{workflows/create-stack-skill → skf-create-stack-skill}/steps-c/step-02-detect-manifests.md +29 -63
- package/src/{workflows/create-stack-skill → skf-create-stack-skill}/steps-c/step-03-rank-and-confirm.md +9 -59
- package/src/{workflows/create-stack-skill → skf-create-stack-skill}/steps-c/step-04-parallel-extract.md +29 -64
- package/src/skf-create-stack-skill/steps-c/step-05-detect-integrations.md +164 -0
- package/src/{workflows/create-stack-skill → skf-create-stack-skill}/steps-c/step-06-compile-stack.md +7 -60
- package/src/skf-create-stack-skill/steps-c/step-07-generate-output.md +328 -0
- package/src/{workflows/create-stack-skill → skf-create-stack-skill}/steps-c/step-08-validate.md +17 -63
- package/src/skf-create-stack-skill/steps-c/step-09-report.md +128 -0
- package/src/skf-create-stack-skill/steps-c/step-10-health-check.md +22 -0
- package/src/skf-drop-skill/SKILL.md +57 -0
- package/src/{workflows/drop-skill → skf-drop-skill}/steps-c/step-01-select.md +9 -69
- package/src/{workflows/drop-skill → skf-drop-skill}/steps-c/step-02-execute.md +30 -106
- package/src/skf-drop-skill/steps-c/step-03-report.md +83 -0
- package/src/skf-drop-skill/steps-c/step-04-health-check.md +22 -0
- package/src/skf-export-skill/SKILL.md +57 -0
- package/src/skf-export-skill/assets/managed-section-format.md +138 -0
- package/src/{workflows/export-skill/data → skf-export-skill/assets}/snippet-format.md +8 -12
- package/src/skf-export-skill/steps-c/step-01-load-skill.md +247 -0
- package/src/{workflows/export-skill → skf-export-skill}/steps-c/step-02-package.md +5 -58
- package/src/{workflows/export-skill → skf-export-skill}/steps-c/step-03-generate-snippet.md +15 -77
- package/src/{workflows/export-skill → skf-export-skill}/steps-c/step-04-update-context.md +59 -133
- package/src/{workflows/export-skill → skf-export-skill}/steps-c/step-05-token-report.md +4 -58
- package/src/{workflows/export-skill → skf-export-skill}/steps-c/step-06-summary.md +20 -64
- package/src/skf-export-skill/steps-c/step-07-health-check.md +22 -0
- package/src/skf-forger/SKILL.md +110 -0
- package/src/skf-forger/bmad-skill-manifest.yaml +11 -0
- package/src/skf-quick-skill/SKILL.md +56 -0
- package/src/{workflows/quick-skill/data → skf-quick-skill/references}/registry-resolution.md +6 -6
- package/src/{workflows/quick-skill → skf-quick-skill}/steps-c/step-01-resolve-target.md +9 -62
- package/src/{workflows/quick-skill → skf-quick-skill}/steps-c/step-02-ecosystem-check.md +5 -59
- package/src/{workflows/quick-skill → skf-quick-skill}/steps-c/step-03-quick-extract.md +18 -61
- package/src/{workflows/quick-skill → skf-quick-skill}/steps-c/step-04-compile.md +6 -60
- package/src/{workflows/quick-skill → skf-quick-skill}/steps-c/step-05-validate.md +58 -78
- package/src/skf-quick-skill/steps-c/step-06-write.md +73 -0
- package/src/skf-quick-skill/steps-c/step-07-health-check.md +22 -0
- package/src/skf-refine-architecture/SKILL.md +57 -0
- package/src/{workflows/refine-architecture/data → skf-refine-architecture/references}/refinement-rules.md +16 -16
- package/src/{workflows/refine-architecture → skf-refine-architecture}/steps-c/step-01-init.md +6 -63
- package/src/{workflows/refine-architecture → skf-refine-architecture}/steps-c/step-02-gap-analysis.md +29 -71
- package/src/{workflows/refine-architecture → skf-refine-architecture}/steps-c/step-03-issue-detection.md +5 -65
- package/src/{workflows/refine-architecture → skf-refine-architecture}/steps-c/step-04-improvements.md +5 -64
- package/src/{workflows/refine-architecture → skf-refine-architecture}/steps-c/step-05-compile.md +5 -56
- package/src/{workflows/refine-architecture → skf-refine-architecture}/steps-c/step-06-report.md +14 -64
- package/src/skf-refine-architecture/steps-c/step-07-health-check.md +22 -0
- package/src/skf-rename-skill/SKILL.md +58 -0
- package/src/{workflows/rename-skill → skf-rename-skill}/steps-c/step-01-select.md +12 -73
- package/src/{workflows/rename-skill → skf-rename-skill}/steps-c/step-02-execute.md +32 -113
- package/src/skf-rename-skill/steps-c/step-03-report.md +83 -0
- package/src/skf-rename-skill/steps-c/step-04-health-check.md +22 -0
- package/src/skf-setup/SKILL.md +55 -0
- package/src/{workflows/setup-forge → skf-setup}/steps-c/step-01-detect-and-tier.md +6 -62
- package/src/{workflows/setup-forge → skf-setup}/steps-c/step-01b-ccc-index.md +9 -65
- package/src/{workflows/setup-forge → skf-setup}/steps-c/step-02-write-config.md +15 -60
- package/src/{workflows/setup-forge → skf-setup}/steps-c/step-03-auto-index.md +7 -66
- package/src/skf-setup/steps-c/step-04-report.md +92 -0
- package/src/skf-setup/steps-c/step-05-health-check.md +22 -0
- package/src/skf-test-skill/SKILL.md +59 -0
- package/src/skf-test-skill/references/migration-section-rules.md +114 -0
- package/src/skf-test-skill/references/scoring-rules.md +207 -0
- package/src/skf-test-skill/references/source-access-protocol.md +109 -0
- package/src/skf-test-skill/scripts/compute-score.py +310 -0
- package/src/skf-test-skill/steps-c/step-01-init.md +238 -0
- package/src/{workflows/test-skill → skf-test-skill}/steps-c/step-02-detect-mode.md +4 -62
- package/src/skf-test-skill/steps-c/step-03-coverage-check.md +347 -0
- package/src/skf-test-skill/steps-c/step-04-coherence-check.md +239 -0
- package/src/{workflows/test-skill → skf-test-skill}/steps-c/step-04b-external-validators.md +42 -77
- package/src/skf-test-skill/steps-c/step-05-score.md +266 -0
- package/src/skf-test-skill/steps-c/step-06-report.md +290 -0
- package/src/skf-test-skill/steps-c/step-07-health-check.md +25 -0
- package/src/skf-test-skill/templates/test-report-template.md +58 -0
- package/src/skf-update-skill/SKILL.md +58 -0
- package/src/{workflows/update-skill/data → skf-update-skill/references}/manual-section-rules.md +6 -6
- package/src/{workflows/update-skill/data → skf-update-skill/references}/merge-conflict-rules.md +5 -5
- package/src/skf-update-skill/references/remote-source-resolution.md +94 -0
- package/src/{workflows/update-skill → skf-update-skill}/steps-c/step-01-init.md +22 -69
- package/src/skf-update-skill/steps-c/step-02-detect-changes.md +255 -0
- package/src/skf-update-skill/steps-c/step-03-re-extract.md +336 -0
- package/src/{workflows/update-skill → skf-update-skill}/steps-c/step-04-merge.md +60 -72
- package/src/{workflows/update-skill → skf-update-skill}/steps-c/step-05-validate.md +5 -62
- package/src/skf-update-skill/steps-c/step-06-write.md +278 -0
- package/src/{workflows/update-skill → skf-update-skill}/steps-c/step-07-report.md +11 -66
- package/src/skf-update-skill/steps-c/step-08-health-check.md +22 -0
- package/src/skf-verify-stack/SKILL.md +60 -0
- package/src/skf-verify-stack/assets/feasibility-report-template.md +76 -0
- package/src/{workflows/verify-stack/data → skf-verify-stack/references}/coverage-patterns.md +12 -4
- package/src/skf-verify-stack/references/integration-verification-rules.md +78 -0
- package/src/skf-verify-stack/steps-c/step-01-init.md +178 -0
- package/src/{workflows/verify-stack → skf-verify-stack}/steps-c/step-02-coverage.md +27 -72
- package/src/skf-verify-stack/steps-c/step-03-integrations.md +179 -0
- package/src/{workflows/verify-stack → skf-verify-stack}/steps-c/step-04-requirements.md +14 -67
- package/src/skf-verify-stack/steps-c/step-05-synthesize.md +147 -0
- package/src/skf-verify-stack/steps-c/step-06-report.md +156 -0
- package/src/skf-verify-stack/steps-c/step-07-health-check.md +25 -0
- package/tools/cli/commands/install.js +1 -1
- package/tools/cli/commands/status.js +21 -23
- package/tools/cli/commands/uninstall.js +11 -19
- package/tools/cli/commands/update.js +2 -2
- package/tools/cli/lib/ide-skills.js +216 -0
- package/tools/cli/lib/installer.js +29 -52
- package/tools/cli/lib/manifest.js +11 -5
- package/tools/cli/lib/platform-codes.yaml +223 -0
- package/tools/cli/lib/ui.js +76 -47
- package/tools/validate-docs-drift.js +193 -0
- package/src/agents/forger.agent.yaml +0 -114
- package/src/workflows/README.md +0 -174
- package/src/workflows/analyze-source/data/skill-brief-schema.md +0 -125
- package/src/workflows/analyze-source/data/unit-detection-heuristics.md +0 -124
- package/src/workflows/analyze-source/workflow.md +0 -61
- package/src/workflows/audit-skill/workflow.md +0 -64
- package/src/workflows/brief-skill/data/skill-brief-schema.md +0 -159
- package/src/workflows/brief-skill/workflow.md +0 -58
- package/src/workflows/create-skill/data/source-resolution-protocols.md +0 -200
- package/src/workflows/create-skill/steps-c/step-03-extract.md +0 -301
- package/src/workflows/create-skill/steps-c/step-03b-fetch-temporal.md +0 -253
- package/src/workflows/create-skill/steps-c/step-06-validate.md +0 -254
- package/src/workflows/create-skill/steps-c/step-08-report.md +0 -176
- package/src/workflows/create-skill/workflow.md +0 -53
- package/src/workflows/create-stack-skill/steps-c/step-05-detect-integrations.md +0 -196
- package/src/workflows/create-stack-skill/steps-c/step-07-generate-output.md +0 -270
- package/src/workflows/create-stack-skill/steps-c/step-09-report.md +0 -145
- package/src/workflows/create-stack-skill/workflow.md +0 -59
- package/src/workflows/drop-skill/steps-c/step-03-report.md +0 -135
- package/src/workflows/drop-skill/workflow.md +0 -63
- package/src/workflows/export-skill/data/managed-section-format.md +0 -103
- package/src/workflows/export-skill/steps-c/step-01-load-skill.md +0 -237
- package/src/workflows/export-skill/workflow.md +0 -58
- package/src/workflows/quick-skill/steps-c/step-06-write.md +0 -179
- package/src/workflows/quick-skill/workflow.md +0 -58
- package/src/workflows/refine-architecture/workflow.md +0 -61
- package/src/workflows/rename-skill/steps-c/step-03-report.md +0 -132
- package/src/workflows/rename-skill/workflow.md +0 -64
- package/src/workflows/setup-forge/steps-c/step-04-report.md +0 -156
- package/src/workflows/setup-forge/workflow.md +0 -51
- package/src/workflows/test-skill/data/scoring-rules.md +0 -123
- package/src/workflows/test-skill/data/source-access-protocol.md +0 -51
- package/src/workflows/test-skill/steps-c/step-01-init.md +0 -217
- package/src/workflows/test-skill/steps-c/step-03-coverage-check.md +0 -280
- package/src/workflows/test-skill/steps-c/step-04-coherence-check.md +0 -271
- package/src/workflows/test-skill/steps-c/step-05-score.md +0 -222
- package/src/workflows/test-skill/steps-c/step-06-report.md +0 -207
- package/src/workflows/test-skill/templates/test-report-template.md +0 -28
- package/src/workflows/test-skill/workflow.md +0 -57
- package/src/workflows/update-skill/data/remote-source-resolution.md +0 -57
- package/src/workflows/update-skill/steps-c/step-02-detect-changes.md +0 -221
- package/src/workflows/update-skill/steps-c/step-03-re-extract.md +0 -256
- package/src/workflows/update-skill/steps-c/step-06-write.md +0 -265
- package/src/workflows/update-skill/workflow.md +0 -56
- package/src/workflows/verify-stack/data/feasibility-report-template.md +0 -63
- package/src/workflows/verify-stack/data/integration-verification-rules.md +0 -73
- package/src/workflows/verify-stack/steps-c/step-01-init.md +0 -179
- package/src/workflows/verify-stack/steps-c/step-03-integrations.md +0 -195
- package/src/workflows/verify-stack/steps-c/step-05-synthesize.md +0 -191
- package/src/workflows/verify-stack/steps-c/step-06-report.md +0 -197
- package/src/workflows/verify-stack/workflow.md +0 -61
- package/tools/cli/lib/compiler.js +0 -306
- package/tools/cli/lib/ide-commands.js +0 -244
- /package/src/{workflows/analyze-source → skf-analyze-source}/templates/analysis-report-template.md +0 -0
- /package/src/{workflows/quick-skill/data → skf-quick-skill/assets}/skill-template.md +0 -0
- /package/src/{workflows/setup-forge/data → skf-setup/references}/tier-rules.md +0 -0
- /package/src/{workflows/test-skill/data → skf-test-skill/assets}/output-section-formats.md +0 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# /// script
|
|
2
|
+
# requires-python = ">=3.10"
|
|
3
|
+
# dependencies = ["pyyaml"]
|
|
4
|
+
# ///
|
|
5
|
+
"""SKF Preflight — Config and sidecar loader for all SKF skills.
|
|
6
|
+
|
|
7
|
+
Loads config.yaml, validates sidecar_path, loads preferences.yaml and
|
|
8
|
+
forge-tier.yaml, and outputs a unified JSON blob for step consumption.
|
|
9
|
+
|
|
10
|
+
CLI: python3 skf-preflight.py <project-root>
|
|
11
|
+
python3 skf-preflight.py <project-root> --config-path <alt-config>
|
|
12
|
+
|
|
13
|
+
Output: JSON to stdout with all resolved config variables and sidecar state.
|
|
14
|
+
Exit 0 on success, exit 1 on hard-halt conditions (missing config, bad sidecar).
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import sys
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
import yaml
|
|
25
|
+
except ImportError:
|
|
26
|
+
print(
|
|
27
|
+
json.dumps({"error": "PyYAML not installed. Run: pip install pyyaml", "code": "MISSING_DEPENDENCY"}),
|
|
28
|
+
)
|
|
29
|
+
sys.exit(1)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def load_yaml_file(path):
|
|
33
|
+
"""Load a YAML file, returning (data, None) or (None, error_string)."""
|
|
34
|
+
try:
|
|
35
|
+
with open(path, encoding="utf-8") as f:
|
|
36
|
+
data = yaml.safe_load(f)
|
|
37
|
+
return data or {}, None
|
|
38
|
+
except FileNotFoundError:
|
|
39
|
+
return None, f"File not found: {path}"
|
|
40
|
+
except yaml.YAMLError as e:
|
|
41
|
+
return None, f"YAML parse error in {path}: {e}"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def run_preflight(project_root, config_path=None):
|
|
45
|
+
"""Run preflight checks and return a result dict."""
|
|
46
|
+
project_root = Path(project_root).resolve()
|
|
47
|
+
|
|
48
|
+
# 1. Load config.yaml
|
|
49
|
+
if config_path:
|
|
50
|
+
cfg_path = Path(config_path)
|
|
51
|
+
else:
|
|
52
|
+
cfg_path = project_root / "_bmad" / "skf" / "config.yaml"
|
|
53
|
+
|
|
54
|
+
config, err = load_yaml_file(cfg_path)
|
|
55
|
+
if err:
|
|
56
|
+
return {
|
|
57
|
+
"status": "hard-halt",
|
|
58
|
+
"error": f"Cannot initialize. SKF config not found at {cfg_path}. Run the skf-setup skill to initialize your forge environment.",
|
|
59
|
+
"code": "CONFIG_MISSING",
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# 2. Resolve config variables
|
|
63
|
+
resolved = {
|
|
64
|
+
"project_root": str(project_root),
|
|
65
|
+
"config_path": str(cfg_path),
|
|
66
|
+
"project_name": config.get("project_name", ""),
|
|
67
|
+
"output_folder": config.get("output_folder", ""),
|
|
68
|
+
"user_name": config.get("user_name", ""),
|
|
69
|
+
"communication_language": config.get("communication_language", "English"),
|
|
70
|
+
"document_output_language": config.get("document_output_language", "English"),
|
|
71
|
+
"sidecar_path": config.get("sidecar_path", ""),
|
|
72
|
+
"skills_output_folder": config.get("skills_output_folder", ""),
|
|
73
|
+
"forge_data_folder": config.get("forge_data_folder", ""),
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
# 3. Validate sidecar_path
|
|
77
|
+
sidecar_path = resolved["sidecar_path"]
|
|
78
|
+
if not sidecar_path or sidecar_path == "{sidecar_path}":
|
|
79
|
+
return {
|
|
80
|
+
"status": "hard-halt",
|
|
81
|
+
"error": (
|
|
82
|
+
"Cannot initialize. sidecar_path is not defined in your installed config.yaml. "
|
|
83
|
+
f"Add sidecar_path: {project_root}/_bmad/_memory/forger-sidecar to your project "
|
|
84
|
+
"config.yaml and retry. This is a known installer issue with prompt: false config variables."
|
|
85
|
+
),
|
|
86
|
+
"code": "SIDECAR_UNDEFINED",
|
|
87
|
+
"config": resolved,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
sidecar_dir = Path(sidecar_path)
|
|
91
|
+
if not sidecar_dir.is_absolute():
|
|
92
|
+
sidecar_dir = project_root / sidecar_path
|
|
93
|
+
|
|
94
|
+
if not sidecar_dir.is_dir():
|
|
95
|
+
return {
|
|
96
|
+
"status": "hard-halt",
|
|
97
|
+
"error": f"Sidecar directory not found: {sidecar_dir}. Run skf-setup to initialize.",
|
|
98
|
+
"code": "SIDECAR_MISSING",
|
|
99
|
+
"config": resolved,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
resolved["sidecar_path_resolved"] = str(sidecar_dir)
|
|
103
|
+
|
|
104
|
+
# 4. Load sidecar files
|
|
105
|
+
sidecar = {}
|
|
106
|
+
|
|
107
|
+
prefs_path = sidecar_dir / "preferences.yaml"
|
|
108
|
+
prefs, prefs_err = load_yaml_file(prefs_path)
|
|
109
|
+
if prefs_err:
|
|
110
|
+
sidecar["preferences"] = None
|
|
111
|
+
sidecar["preferences_error"] = prefs_err
|
|
112
|
+
else:
|
|
113
|
+
sidecar["preferences"] = prefs
|
|
114
|
+
|
|
115
|
+
tier_path = sidecar_dir / "forge-tier.yaml"
|
|
116
|
+
tier, tier_err = load_yaml_file(tier_path)
|
|
117
|
+
if tier_err:
|
|
118
|
+
sidecar["forge_tier"] = None
|
|
119
|
+
sidecar["forge_tier_error"] = tier_err
|
|
120
|
+
else:
|
|
121
|
+
sidecar["forge_tier"] = tier
|
|
122
|
+
|
|
123
|
+
# 5. Derive convenience fields
|
|
124
|
+
tier_value = None
|
|
125
|
+
if tier:
|
|
126
|
+
tier_value = tier.get("tier")
|
|
127
|
+
|
|
128
|
+
compact_greeting = False
|
|
129
|
+
if prefs:
|
|
130
|
+
compact_greeting = prefs.get("compact_greeting", False) is True
|
|
131
|
+
|
|
132
|
+
tier_override = None
|
|
133
|
+
if prefs:
|
|
134
|
+
tier_override = prefs.get("tier_override")
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
"status": "ok",
|
|
138
|
+
"config": resolved,
|
|
139
|
+
"sidecar": sidecar,
|
|
140
|
+
"derived": {
|
|
141
|
+
"tier": tier_override or tier_value,
|
|
142
|
+
"tier_source": "override" if tier_override else ("detected" if tier_value else None),
|
|
143
|
+
"compact_greeting": compact_greeting,
|
|
144
|
+
"is_first_run": tier_value is None,
|
|
145
|
+
},
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
if __name__ == "__main__":
|
|
150
|
+
if len(sys.argv) < 2:
|
|
151
|
+
print("Usage: python3 skf-preflight.py <project-root> [--config-path <path>]", file=sys.stderr)
|
|
152
|
+
sys.exit(1)
|
|
153
|
+
|
|
154
|
+
proj_root = sys.argv[1]
|
|
155
|
+
cfg_path = None
|
|
156
|
+
|
|
157
|
+
if "--config-path" in sys.argv:
|
|
158
|
+
idx = sys.argv.index("--config-path")
|
|
159
|
+
if idx + 1 < len(sys.argv):
|
|
160
|
+
cfg_path = sys.argv[idx + 1]
|
|
161
|
+
|
|
162
|
+
result = run_preflight(proj_root, cfg_path)
|
|
163
|
+
print(json.dumps(result, indent=2))
|
|
164
|
+
sys.exit(1 if result["status"] == "hard-halt" else 0)
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# /// script
|
|
2
|
+
# requires-python = ">=3.10"
|
|
3
|
+
# dependencies = []
|
|
4
|
+
# ///
|
|
5
|
+
"""SKF Rebuild Managed Sections — Context file marker surgery.
|
|
6
|
+
|
|
7
|
+
Reads a context file (CLAUDE.md/AGENTS.md/.cursorrules), finds the
|
|
8
|
+
<!-- SKF:BEGIN --> / <!-- SKF:END --> markers, and replaces the managed
|
|
9
|
+
section with new content. Preserves all content outside the markers.
|
|
10
|
+
|
|
11
|
+
CLI: python3 skf-rebuild-managed-sections.py <context-file> <action> [args]
|
|
12
|
+
|
|
13
|
+
Actions:
|
|
14
|
+
read — Extract current managed section content
|
|
15
|
+
replace — Replace managed section with content from stdin or --content
|
|
16
|
+
clear — Remove managed section entirely (markers + content)
|
|
17
|
+
insert — Insert managed section if not present (at end of file)
|
|
18
|
+
check — Check if managed section exists, report status
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
import re
|
|
25
|
+
import sys
|
|
26
|
+
from datetime import datetime, timezone
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
|
|
29
|
+
BEGIN_MARKER_PREFIX = "<!-- SKF:BEGIN"
|
|
30
|
+
END_MARKER = "<!-- SKF:END -->"
|
|
31
|
+
MARKER_PATTERN = re.compile(
|
|
32
|
+
r"(<!-- SKF:BEGIN[^>]*-->)(.*?)(<!-- SKF:END -->)",
|
|
33
|
+
re.DOTALL,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def read_context_file(file_path):
|
|
38
|
+
"""Read a context file. Returns (content, None) or (None, error)."""
|
|
39
|
+
try:
|
|
40
|
+
return Path(file_path).read_text(encoding="utf-8"), None
|
|
41
|
+
except FileNotFoundError:
|
|
42
|
+
return None, f"File not found: {file_path}"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def find_managed_section(content):
|
|
46
|
+
"""Find the managed section in content. Returns match or None."""
|
|
47
|
+
return MARKER_PATTERN.search(content)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def cmd_check(file_path):
|
|
51
|
+
"""Check if managed section exists."""
|
|
52
|
+
content, err = read_context_file(file_path)
|
|
53
|
+
if err:
|
|
54
|
+
return {"status": "error", "error": err}
|
|
55
|
+
|
|
56
|
+
has_begin = BEGIN_MARKER_PREFIX in content
|
|
57
|
+
has_end = END_MARKER in content
|
|
58
|
+
match = find_managed_section(content)
|
|
59
|
+
|
|
60
|
+
if match:
|
|
61
|
+
section_content = match.group(2)
|
|
62
|
+
return {
|
|
63
|
+
"status": "ok",
|
|
64
|
+
"has_managed_section": True,
|
|
65
|
+
"markers_valid": True,
|
|
66
|
+
"section_length": len(section_content.strip()),
|
|
67
|
+
"section_line_count": len(section_content.strip().split("\n")) if section_content.strip() else 0,
|
|
68
|
+
}
|
|
69
|
+
elif has_begin and not has_end:
|
|
70
|
+
return {
|
|
71
|
+
"status": "ok",
|
|
72
|
+
"has_managed_section": False,
|
|
73
|
+
"markers_valid": False,
|
|
74
|
+
"error_detail": "Found <!-- SKF:BEGIN but no matching <!-- SKF:END -->",
|
|
75
|
+
}
|
|
76
|
+
elif not has_begin and has_end:
|
|
77
|
+
return {
|
|
78
|
+
"status": "ok",
|
|
79
|
+
"has_managed_section": False,
|
|
80
|
+
"markers_valid": False,
|
|
81
|
+
"error_detail": "Found <!-- SKF:END --> but no matching <!-- SKF:BEGIN",
|
|
82
|
+
}
|
|
83
|
+
else:
|
|
84
|
+
return {"status": "ok", "has_managed_section": False, "markers_valid": True}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def cmd_read(file_path):
|
|
88
|
+
"""Extract the managed section content."""
|
|
89
|
+
content, err = read_context_file(file_path)
|
|
90
|
+
if err:
|
|
91
|
+
return {"status": "error", "error": err}
|
|
92
|
+
|
|
93
|
+
match = find_managed_section(content)
|
|
94
|
+
if not match:
|
|
95
|
+
return {"status": "ok", "has_managed_section": False, "content": None}
|
|
96
|
+
|
|
97
|
+
return {"status": "ok", "has_managed_section": True, "content": match.group(2)}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def cmd_replace(file_path, new_content):
|
|
101
|
+
"""Replace managed section content between markers."""
|
|
102
|
+
content, err = read_context_file(file_path)
|
|
103
|
+
if err:
|
|
104
|
+
return {"status": "error", "error": err}
|
|
105
|
+
|
|
106
|
+
match = find_managed_section(content)
|
|
107
|
+
if not match:
|
|
108
|
+
return {"status": "error", "error": "No managed section found. Use 'insert' to create one."}
|
|
109
|
+
|
|
110
|
+
# Replace content between markers with fresh timestamp
|
|
111
|
+
today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
|
112
|
+
new_section = f"<!-- SKF:BEGIN updated:{today} -->\n{new_content}\n{END_MARKER}"
|
|
113
|
+
updated = content[: match.start()] + new_section + content[match.end() :]
|
|
114
|
+
|
|
115
|
+
Path(file_path).write_text(updated, encoding="utf-8")
|
|
116
|
+
return {"status": "ok", "action": "replaced", "bytes_written": len(updated)}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def cmd_clear(file_path):
|
|
120
|
+
"""Remove managed section entirely (markers + content)."""
|
|
121
|
+
content, err = read_context_file(file_path)
|
|
122
|
+
if err:
|
|
123
|
+
return {"status": "error", "error": err}
|
|
124
|
+
|
|
125
|
+
match = find_managed_section(content)
|
|
126
|
+
if not match:
|
|
127
|
+
return {"status": "ok", "action": "no_change", "reason": "No managed section found"}
|
|
128
|
+
|
|
129
|
+
# Remove the entire marker block, plus any surrounding blank lines
|
|
130
|
+
before = content[: match.start()].rstrip("\n")
|
|
131
|
+
after = content[match.end() :].lstrip("\n")
|
|
132
|
+
updated = before + ("\n\n" if before and after else "") + after
|
|
133
|
+
|
|
134
|
+
Path(file_path).write_text(updated, encoding="utf-8")
|
|
135
|
+
return {"status": "ok", "action": "cleared", "bytes_written": len(updated)}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def cmd_insert(file_path, new_content):
|
|
139
|
+
"""Insert managed section at end of file if not present."""
|
|
140
|
+
content, err = read_context_file(file_path)
|
|
141
|
+
if err:
|
|
142
|
+
# File doesn't exist — create it
|
|
143
|
+
if "not found" in err.lower():
|
|
144
|
+
content = ""
|
|
145
|
+
else:
|
|
146
|
+
return {"status": "error", "error": err}
|
|
147
|
+
|
|
148
|
+
match = find_managed_section(content)
|
|
149
|
+
if match:
|
|
150
|
+
return {"status": "error", "error": "Managed section already exists. Use 'replace' instead."}
|
|
151
|
+
|
|
152
|
+
today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
|
153
|
+
section = f"\n<!-- SKF:BEGIN updated:{today} -->\n{new_content}\n{END_MARKER}\n"
|
|
154
|
+
updated = content.rstrip("\n") + "\n" + section if content.strip() else section.lstrip("\n")
|
|
155
|
+
|
|
156
|
+
Path(file_path).write_text(updated, encoding="utf-8")
|
|
157
|
+
return {"status": "ok", "action": "inserted", "bytes_written": len(updated)}
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def main():
|
|
161
|
+
if len(sys.argv) < 3:
|
|
162
|
+
print("Usage: python3 skf-rebuild-managed-sections.py <context-file> <action> [--content <text>]", file=sys.stderr)
|
|
163
|
+
print("Actions: read, replace, clear, insert, check", file=sys.stderr)
|
|
164
|
+
sys.exit(1)
|
|
165
|
+
|
|
166
|
+
file_path = sys.argv[1]
|
|
167
|
+
action = sys.argv[2]
|
|
168
|
+
|
|
169
|
+
content_arg = None
|
|
170
|
+
if "--content" in sys.argv:
|
|
171
|
+
idx = sys.argv.index("--content")
|
|
172
|
+
if idx + 1 < len(sys.argv):
|
|
173
|
+
content_arg = sys.argv[idx + 1]
|
|
174
|
+
elif action in ("replace", "insert") and not sys.stdin.isatty():
|
|
175
|
+
content_arg = sys.stdin.read()
|
|
176
|
+
|
|
177
|
+
if action == "check":
|
|
178
|
+
result = cmd_check(file_path)
|
|
179
|
+
elif action == "read":
|
|
180
|
+
result = cmd_read(file_path)
|
|
181
|
+
elif action == "replace":
|
|
182
|
+
if content_arg is None:
|
|
183
|
+
result = {"status": "error", "error": "replace requires --content or stdin"}
|
|
184
|
+
else:
|
|
185
|
+
result = cmd_replace(file_path, content_arg)
|
|
186
|
+
elif action == "clear":
|
|
187
|
+
result = cmd_clear(file_path)
|
|
188
|
+
elif action == "insert":
|
|
189
|
+
if content_arg is None:
|
|
190
|
+
result = {"status": "error", "error": "insert requires --content or stdin"}
|
|
191
|
+
else:
|
|
192
|
+
result = cmd_insert(file_path, content_arg)
|
|
193
|
+
else:
|
|
194
|
+
result = {"status": "error", "error": f"Unknown action: {action}"}
|
|
195
|
+
|
|
196
|
+
print(json.dumps(result, indent=2))
|
|
197
|
+
sys.exit(0 if result["status"] == "ok" else 1)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
if __name__ == "__main__":
|
|
201
|
+
main()
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# /// script
|
|
2
|
+
# requires-python = ">=3.10"
|
|
3
|
+
# dependencies = []
|
|
4
|
+
# ///
|
|
5
|
+
"""SKF Severity Classify — Rule-based severity classification for drift findings.
|
|
6
|
+
|
|
7
|
+
Applies severity rules to a list of drift findings and computes an overall
|
|
8
|
+
drift score. Used by audit-skill and test-skill.
|
|
9
|
+
|
|
10
|
+
CLI: python3 skf-severity-classify.py <findings-json>
|
|
11
|
+
echo '<JSON>' | python3 skf-severity-classify.py -
|
|
12
|
+
|
|
13
|
+
Input: JSON array of findings, each with:
|
|
14
|
+
- type: "removed"|"added"|"changed"|"moved"|"semantic"
|
|
15
|
+
- category: "export"|"signature"|"parameter"|"module"|"pattern"|"convention"|...
|
|
16
|
+
- detail: description string
|
|
17
|
+
|
|
18
|
+
Output: JSON with classified findings and overall drift score.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
import sys
|
|
25
|
+
|
|
26
|
+
# --- Severity Rules (from severity-rules.md) ---
|
|
27
|
+
|
|
28
|
+
CRITICAL_RULES = [
|
|
29
|
+
{"type": "removed", "categories": {"export", "module", "class", "interface"}},
|
|
30
|
+
{"type": "changed", "categories": {"signature", "parameter_count", "return_type", "inheritance", "interface_contract"}},
|
|
31
|
+
{"type": "renamed", "categories": {"export", "module"}},
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
HIGH_RULES = [
|
|
35
|
+
{"type": "added", "categories": {"export"}, "threshold": 3}, # >3 new exports
|
|
36
|
+
{"type": "removed", "categories": {"internal_helper"}},
|
|
37
|
+
{"type": "changed", "categories": {"default_value", "required_parameter"}},
|
|
38
|
+
{"type": "deprecated", "categories": {"export"}},
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
MEDIUM_RULES = [
|
|
42
|
+
{"type": "changed", "categories": {"implementation", "optional_parameter", "internal_pattern"}},
|
|
43
|
+
{"type": "added", "categories": {"export"}, "threshold_max": 3}, # 1-3 new exports
|
|
44
|
+
{"type": "moved", "categories": {"export", "function"}},
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
LOW_CATEGORIES = {"style", "convention", "comment", "documentation", "whitespace", "test", "internal", "private"}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def classify_finding(finding, added_export_count=0):
|
|
51
|
+
"""Classify a single finding's severity. Returns severity string."""
|
|
52
|
+
f_type = finding.get("type", "").lower()
|
|
53
|
+
f_category = finding.get("category", "").lower()
|
|
54
|
+
|
|
55
|
+
# Check CRITICAL
|
|
56
|
+
for rule in CRITICAL_RULES:
|
|
57
|
+
if f_type == rule["type"] and f_category in rule["categories"]:
|
|
58
|
+
return "CRITICAL"
|
|
59
|
+
|
|
60
|
+
# Check HIGH
|
|
61
|
+
for rule in HIGH_RULES:
|
|
62
|
+
if f_type == rule["type"]:
|
|
63
|
+
if f_category in rule["categories"]:
|
|
64
|
+
if "threshold" in rule:
|
|
65
|
+
if f_type == "added" and f_category == "export" and added_export_count > rule["threshold"]:
|
|
66
|
+
return "HIGH"
|
|
67
|
+
else:
|
|
68
|
+
return "HIGH"
|
|
69
|
+
|
|
70
|
+
# Check MEDIUM
|
|
71
|
+
for rule in MEDIUM_RULES:
|
|
72
|
+
if f_type == rule["type"]:
|
|
73
|
+
if f_category in rule["categories"]:
|
|
74
|
+
if "threshold_max" in rule:
|
|
75
|
+
if f_type == "added" and f_category == "export" and added_export_count <= rule["threshold_max"]:
|
|
76
|
+
return "MEDIUM"
|
|
77
|
+
else:
|
|
78
|
+
return "MEDIUM"
|
|
79
|
+
|
|
80
|
+
# Check LOW
|
|
81
|
+
if f_category in LOW_CATEGORIES:
|
|
82
|
+
return "LOW"
|
|
83
|
+
|
|
84
|
+
# Semantic findings default to MEDIUM
|
|
85
|
+
if f_type == "semantic":
|
|
86
|
+
return "MEDIUM"
|
|
87
|
+
|
|
88
|
+
# Default: MEDIUM for unrecognized patterns
|
|
89
|
+
return "MEDIUM"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def compute_drift_score(classified):
|
|
93
|
+
"""Compute overall drift score from classified findings."""
|
|
94
|
+
severities = {f["severity"] for f in classified}
|
|
95
|
+
|
|
96
|
+
if not classified:
|
|
97
|
+
return "CLEAN"
|
|
98
|
+
if "CRITICAL" in severities:
|
|
99
|
+
return "CRITICAL"
|
|
100
|
+
if "HIGH" in severities or "MEDIUM" in severities:
|
|
101
|
+
return "SIGNIFICANT"
|
|
102
|
+
return "MINOR"
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def classify_all(findings):
|
|
106
|
+
"""Classify all findings and compute drift score."""
|
|
107
|
+
if not isinstance(findings, list):
|
|
108
|
+
return {"status": "error", "error": "Input must be a JSON array of findings"}
|
|
109
|
+
|
|
110
|
+
# Count added exports for threshold rules
|
|
111
|
+
added_export_count = sum(
|
|
112
|
+
1 for f in findings
|
|
113
|
+
if f.get("type", "").lower() == "added" and f.get("category", "").lower() == "export"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
classified = []
|
|
117
|
+
for finding in findings:
|
|
118
|
+
severity = classify_finding(finding, added_export_count)
|
|
119
|
+
classified.append({**finding, "severity": severity})
|
|
120
|
+
|
|
121
|
+
# Sort by severity: CRITICAL > HIGH > MEDIUM > LOW
|
|
122
|
+
severity_order = {"CRITICAL": 0, "HIGH": 1, "MEDIUM": 2, "LOW": 3}
|
|
123
|
+
classified.sort(key=lambda f: severity_order.get(f["severity"], 4))
|
|
124
|
+
|
|
125
|
+
drift_score = compute_drift_score(classified)
|
|
126
|
+
|
|
127
|
+
# Summary
|
|
128
|
+
by_severity = {"CRITICAL": 0, "HIGH": 0, "MEDIUM": 0, "LOW": 0}
|
|
129
|
+
for f in classified:
|
|
130
|
+
by_severity[f["severity"]] = by_severity.get(f["severity"], 0) + 1
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
"status": "ok",
|
|
134
|
+
"drift_score": drift_score,
|
|
135
|
+
"total_findings": len(classified),
|
|
136
|
+
"by_severity": by_severity,
|
|
137
|
+
"findings": classified,
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
if __name__ == "__main__":
|
|
142
|
+
if len(sys.argv) < 2:
|
|
143
|
+
print("Usage: python3 skf-severity-classify.py <findings-json-or-file>", file=sys.stderr)
|
|
144
|
+
print(" echo '<JSON>' | python3 skf-severity-classify.py -", file=sys.stderr)
|
|
145
|
+
sys.exit(1)
|
|
146
|
+
|
|
147
|
+
arg = sys.argv[1]
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
if arg == "-":
|
|
151
|
+
data = json.load(sys.stdin)
|
|
152
|
+
elif arg.startswith("["):
|
|
153
|
+
data = json.loads(arg)
|
|
154
|
+
else:
|
|
155
|
+
with open(arg, encoding="utf-8") as f:
|
|
156
|
+
data = json.load(f)
|
|
157
|
+
except (json.JSONDecodeError, FileNotFoundError) as e:
|
|
158
|
+
print(json.dumps({"status": "error", "error": str(e)}))
|
|
159
|
+
sys.exit(1)
|
|
160
|
+
|
|
161
|
+
result = classify_all(data)
|
|
162
|
+
print(json.dumps(result, indent=2))
|
|
163
|
+
sys.exit(0 if result["status"] == "ok" else 1)
|