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,207 @@
|
|
|
1
|
+
# Scoring Rules
|
|
2
|
+
|
|
3
|
+
## Default Threshold
|
|
4
|
+
|
|
5
|
+
**Pass threshold:** 80%
|
|
6
|
+
|
|
7
|
+
## Category Weights
|
|
8
|
+
|
|
9
|
+
| Category | Weight | Description |
|
|
10
|
+
|------------------------|--------|-------------------------------------------------------------------------------------------|
|
|
11
|
+
| Export Coverage | 36% | Percentage of source exports documented in SKILL.md |
|
|
12
|
+
| Signature Accuracy | 22% | Documented signatures match actual source signatures |
|
|
13
|
+
| Type Coverage | 14% | Types and interfaces referenced are complete |
|
|
14
|
+
| Coherence (contextual) | 18% | Cross-references valid, integration patterns complete |
|
|
15
|
+
| Coherence (naive) | 0% | Not applicable — weight redistributed to other categories |
|
|
16
|
+
| External Validation | 10% | Average of skill-check quality score + tessl average score (redistributed if unavailable) |
|
|
17
|
+
|
|
18
|
+
## Naive Mode Weight Redistribution
|
|
19
|
+
|
|
20
|
+
The following weights replace the default table for naive mode. The 18% coherence weight from the default table has been proportionally redistributed into these values. Do not re-redistribute for coherence (already handled in this table). Quick-tier redistribution (zeroing Signature Accuracy and Type Coverage) still applies as an additional step.
|
|
21
|
+
|
|
22
|
+
When running in naive mode (no coherence category):
|
|
23
|
+
- Export Coverage: 45%
|
|
24
|
+
- Signature Accuracy: 25%
|
|
25
|
+
- Type Coverage: 20%
|
|
26
|
+
- External Validation: 10%
|
|
27
|
+
|
|
28
|
+
## External Validation Unavailable
|
|
29
|
+
|
|
30
|
+
When neither skill-check nor tessl is available, redistribute the 10% external validation weight proportionally to the other active categories. When only one tool is available, use that tool's score as the external validation score.
|
|
31
|
+
|
|
32
|
+
## tessl and Split-Body Interaction
|
|
33
|
+
|
|
34
|
+
tessl evaluates SKILL.md body content only — it does not read `references/*.md` files. After split-body extraction, the tessl content score will drop significantly (e.g., 65% to 38%) because Tier 2 content is no longer inline. This is expected behavior and does not reflect actual content quality. When reporting scores for a split-body skill, note: "tessl content score reflects post-split inline content only. Use the pre-split tessl score as the content quality baseline."
|
|
35
|
+
|
|
36
|
+
## Tier-Dependent Scoring
|
|
37
|
+
|
|
38
|
+
### Quick Tier (no tools)
|
|
39
|
+
- Export Coverage: file/structure existence check only
|
|
40
|
+
- Signature Accuracy: skipped (no AST)
|
|
41
|
+
- Type Coverage: skipped (no AST)
|
|
42
|
+
- Score based on: structural completeness only
|
|
43
|
+
- Weight redistribution: skipped categories' weights (Signature Accuracy 22% + Type Coverage 14%) redistributed proportionally to remaining active categories
|
|
44
|
+
|
|
45
|
+
### Docs-Only Mode (all [EXT:...] citations, any tier)
|
|
46
|
+
|
|
47
|
+
When `docs_only_mode: true` is set by step-03 (indicating a skill where all SKILL.md citations are `[EXT:...]` format with no local source code):
|
|
48
|
+
|
|
49
|
+
- **Signature Accuracy:** Not scored (no source to compare against)
|
|
50
|
+
- **Type Coverage:** Not scored (no source to compare against)
|
|
51
|
+
- **Weight redistribution:** Same as Quick tier — Signature Accuracy (22%) and Type Coverage (14%) weights redistributed proportionally to remaining active categories
|
|
52
|
+
- **Export Coverage basis:** Documentation completeness rather than source coverage. Score = (documented_items_with_complete_descriptions / total_documented_items) * 100. A "complete" item has: description, parameters (if function/method), and return type (if function/method).
|
|
53
|
+
- **Coherence:** Standard rules for the detected mode (naive or contextual) apply unchanged
|
|
54
|
+
|
|
55
|
+
This is functionally identical to Quick tier weight redistribution but with a different coverage denominator (self-consistency instead of source comparison).
|
|
56
|
+
|
|
57
|
+
**S4 external-validator requirement for docs-only:** docs-only mode removes two categories (Signature Accuracy, Type Coverage) from scoring. If External Validation is ALSO unavailable, the evidence base collapses to Coverage alone (naive) or Coverage + Coherence (contextual) — which in the naive/Quick case trips the minimum-evidence floor (INCONCLUSIVE). To keep docs-only skills gradable when external validators are present but still deterministic when they are missing: **when `docsOnly: true` AND `externalValidation is null`, step-05 MUST cap `totalScore` at `threshold - 1` (forcing FAIL) before the INCONCLUSIVE floor is evaluated.** This prevents a docs-only skill from PASSing with only one or two redistributed categories carrying all the weight. Implement in step-05 §4 as a pre-compare cap, recorded in the report as `scoring_notes: docs-only without external validators — capped below threshold per S4`.
|
|
58
|
+
|
|
59
|
+
### Stack Skills (Any Tier)
|
|
60
|
+
|
|
61
|
+
When `metadata.json.skill_type == "stack"` (set `stackSkill: true` in the scoring input):
|
|
62
|
+
|
|
63
|
+
- **Signature Accuracy:** N/A — a stack skill's "signature surface" is the external library API it composes (pydantic, SQLAlchemy, FastAPI, etc.), not a proprietary surface the skill authors. Grading signatures against a surface the skill does not own produces meaningless numbers.
|
|
64
|
+
- **Type Coverage:** N/A — same rationale; the type surface belongs to the external libraries.
|
|
65
|
+
- **Weight redistribution:** Same as Quick tier / docs-only / State 2 — Signature Accuracy (22%) and Type Coverage (14%) weights redistributed proportionally to remaining active categories (Export Coverage, Coherence, External Validation).
|
|
66
|
+
- **Applies regardless of detected tier** (Quick, Forge, Forge+, Deep) and is independent of `docsOnly` and `state2`. A stack skill can also be docs-only or State 2; the skip reasons combine additively (e.g. `"stack skill (external type surface) + State 2 (provenance-map)"`).
|
|
67
|
+
- **Detection:** step-05 reads `metadata.json.skill_type` from the skill package. If the value is `"stack"`, set `stackSkill: true` in the scoring input JSON.
|
|
68
|
+
|
|
69
|
+
Equivalence-class note: stack skills with `docsOnly:false` / `state2:false` map to the same equivalence class as State 2 contextual rows (class B) or State 2 naive rows (class D) — the redistribution math is identical; only the `skipReasons` string changes.
|
|
70
|
+
|
|
71
|
+
### State 2 Source Access (Any Tier, Provenance-Map Only)
|
|
72
|
+
|
|
73
|
+
When source is not locally available and analysis resolves to State 2 (provenance-map baseline per source-access-protocol.md):
|
|
74
|
+
|
|
75
|
+
- **Signature Accuracy:** N/A — provenance-map stores parameters as flat string arrays; verification is string comparison only, not semantic AST verification. Type aliases (`str` vs `String`, `list` vs `List[Any]`) cannot be resolved without live source.
|
|
76
|
+
- **Type Coverage:** N/A — cannot verify type completeness without local source access for AST re-parsing.
|
|
77
|
+
- **Weight redistribution:** Same as Quick tier — Signature Accuracy (22%) and Type Coverage (14%) weights redistributed proportionally to remaining active categories (Export Coverage, Coherence, External Validation).
|
|
78
|
+
- **Applies regardless of detected tier** (including Forge, Forge+, Deep) whenever `analysis_confidence` is `provenance-map` and local source is unavailable.
|
|
79
|
+
- **Export Coverage denominator:** Uses the union of provenance-map entry names and metadata.json `exports[]` names (per source-access-protocol.md State 2 rules).
|
|
80
|
+
|
|
81
|
+
Note: When provenance-map entries are predominantly T1 (AST-verified at compilation time), the coverage and name-matching data is already at highest confidence. The N/A categories reflect the inability to re-verify at test time, not low-quality extraction data.
|
|
82
|
+
|
|
83
|
+
**S9 State 2 undercount risk acknowledgement:** provenance-map is a cached extraction snapshot — if the source has evolved since extraction, public API adds/removes will NOT surface in Export Coverage (denominator is frozen to the provenance-map union). When `state2: true` AND step-03 records any provenance vs metadata divergence (e.g. union > either source by >5%), apply a flat **10% deduction** to `exportCoverage` before calling the scoring script, AND set `analysis_confidence: provenance-map` (already set) with a report note: `scoring_notes: State 2 undercount risk acknowledged — 10% deduction applied to Export Coverage`. Rationale: the skill cannot be reliably scored on a frozen denominator when the cache is known to disagree with its own metadata; prefer understating over overstating.
|
|
84
|
+
|
|
85
|
+
### Forge Tier (ast-grep)
|
|
86
|
+
- Export Coverage: AST-backed export comparison
|
|
87
|
+
- Signature Accuracy: AST-verified signature matching
|
|
88
|
+
- Type Coverage: AST-verified type completeness
|
|
89
|
+
- Full scoring formula applied
|
|
90
|
+
|
|
91
|
+
### Forge+ Tier (ast-grep + ccc)
|
|
92
|
+
- Same scoring as Forge tier — ccc provides pre-ranking but does not change scoring weights
|
|
93
|
+
- Improved extraction coverage (from ccc pre-discovery) may increase T1 count, but scoring formula is identical to Forge
|
|
94
|
+
- Full scoring formula applied
|
|
95
|
+
|
|
96
|
+
### Deep Tier (ast-grep + gh + QMD)
|
|
97
|
+
- All Forge tier checks plus:
|
|
98
|
+
- Cross-repository reference verification
|
|
99
|
+
- QMD knowledge enrichment for coherence
|
|
100
|
+
- Full scoring formula with maximum depth
|
|
101
|
+
- **Migration & Deprecation Warnings section:** If T2-future annotations exist in the enrichment data, verify that Section 4b is present in SKILL.md Tier 1 and that each warning traces to a T2 provenance citation. If no T2-future annotations exist, Section 4b should normally be absent (not empty). Presence/absence mismatch is a Medium severity gap — with one Info-severity exception for historical-migration content (completed package renames, consolidated import paths, shipped API cutovers that remain load-bearing for training-data drift remediation). See `steps-c/step-04-coherence-check.md` §2b/§5b for the three-case rule.
|
|
102
|
+
|
|
103
|
+
## Redistribution Combinations Matrix (M3 — terminology + determinism)
|
|
104
|
+
|
|
105
|
+
The table below enumerates every `(mode × tier × docsOnly × state2)` cell and
|
|
106
|
+
the redistribution outcome. Two cells map to the same **equivalence class**
|
|
107
|
+
whenever their skip set and base-weight table are identical — the scoring
|
|
108
|
+
script then emits the same `weights` / `activeCategories` / `skippedCategories`
|
|
109
|
+
output regardless of which specific cell produced it. The rightmost column
|
|
110
|
+
cites the representative fixture in
|
|
111
|
+
`test/fixtures/compute-score-contract.json` used to pin the class's math.
|
|
112
|
+
|
|
113
|
+
| # | mode | tier | docsOnly | state2 | Base table | Skipped (sig/type) | Skipped (ext if null) | Equiv. class | Representative fixture |
|
|
114
|
+
|---|------------|----------|----------|--------|----------------|-----------------------|-----------------------|--------------|-------------------------------|
|
|
115
|
+
| 1 | contextual | Deep | F | F | contextual | — | if null | **A** | `suite_a_all_active` |
|
|
116
|
+
| 2 | contextual | Forge+ | F | F | contextual | — | if null | A | `suite_k_forge_plus` |
|
|
117
|
+
| 3 | contextual | Forge | F | F | contextual | — | if null | A | `suite_p_contextual_forge` |
|
|
118
|
+
| 4 | contextual | Quick | F | F | contextual | Quick tier | if null | **B** | `suite_c_quick_tier` |
|
|
119
|
+
| 5 | contextual | Deep | T | F | contextual | docs-only | if null | B | `suite_r_contextual_deep_docsonly` |
|
|
120
|
+
| 6 | contextual | Forge+ | T | F | contextual | docs-only | if null | B | (equiv. — see `suite_r_*`) |
|
|
121
|
+
| 7 | contextual | Forge | T | F | contextual | docs-only | if null | B | (equiv.) |
|
|
122
|
+
| 8 | contextual | Quick | T | F | contextual | Quick tier + docs-only| if null | B | `suite_f_docs_only` |
|
|
123
|
+
| 9 | contextual | Deep | F | T | contextual | State 2 | if null | B | `suite_g_state2` |
|
|
124
|
+
| 10| contextual | Forge+ | F | T | contextual | State 2 | if null | B | (equiv.) |
|
|
125
|
+
| 11| contextual | Forge | F | T | contextual | State 2 | if null | B | (equiv.) |
|
|
126
|
+
| 12| contextual | Quick | F | T | contextual | Quick + State 2 | if null | B | (equiv. — Quick+state2) |
|
|
127
|
+
| 13| contextual | * | T | T | contextual | docs-only + State 2 | if null | B | (equiv.) |
|
|
128
|
+
| 14| naive | Deep | F | F | naive | — | if null | **C** | `suite_q_naive_deep` |
|
|
129
|
+
| 15| naive | Forge+ | F | F | naive | — | if null | C | (equiv.) |
|
|
130
|
+
| 16| naive | Forge | F | F | naive | — | if null | C | `suite_b_naive` |
|
|
131
|
+
| 17| naive | Quick | F | F | naive | Quick tier | if null | **D** | `suite_e_triple_skip` |
|
|
132
|
+
| 18| naive | Deep | T | F | naive | docs-only | if null | D | `suite_s_naive_deep_docsonly` |
|
|
133
|
+
| 19| naive | Forge+ | T | F | naive | docs-only | if null | D | (equiv.) |
|
|
134
|
+
| 20| naive | Forge | T | F | naive | docs-only | if null | D | (equiv.) |
|
|
135
|
+
| 21| naive | Quick | T | F | naive | Quick + docs-only | if null | D | `suite_o_input_echo` |
|
|
136
|
+
| 22| naive | Deep | F | T | naive | State 2 | if null | D | `suite_t_naive_state2` |
|
|
137
|
+
| 23| naive | Forge+ | F | T | naive | State 2 | if null | D | (equiv.) |
|
|
138
|
+
| 24| naive | Forge | F | T | naive | State 2 | if null | D | (equiv.) |
|
|
139
|
+
| 25| naive | Quick | F | T | naive | Quick + State 2 | if null | D | (equiv.) |
|
|
140
|
+
| 26| naive | * | T | T | naive | docs-only + State 2 | if null | D | (equiv.) |
|
|
141
|
+
|
|
142
|
+
**How to read the matrix.** The four equivalence classes A/B/C/D are
|
|
143
|
+
exhaustive: the scoring script's weight-redistribution output depends ONLY on
|
|
144
|
+
(base table, sig/type skip, ext skip), so every row mapping to class X emits
|
|
145
|
+
the same final weights for identical input scores. "(equiv.)" rows are *not*
|
|
146
|
+
missing coverage — they reduce algebraically to a listed representative. The
|
|
147
|
+
M3 fixture set ships at least one representative per equivalence class and one
|
|
148
|
+
representative per unique skip-reason combination so the `skipReasons` string
|
|
149
|
+
is also pinned (e.g. "Quick tier", "docs-only mode", "State 2
|
|
150
|
+
(provenance-map)", "Quick tier + docs-only mode").
|
|
151
|
+
|
|
152
|
+
**Quick-tier INCONCLUSIVE interaction.** Rows 4, 8, 12, 17, 21, 25 all force
|
|
153
|
+
the minimum-evidence floor to evaluate "Quick tier + Export Coverage alone"
|
|
154
|
+
cases separately — see `Result Determination` below. The matrix above is the
|
|
155
|
+
pre-floor redistribution only; INCONCLUSIVE verdict determination runs after.
|
|
156
|
+
|
|
157
|
+
## Score Calculation
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
score = sum(category_weight * category_score) for each category
|
|
161
|
+
category_score = (items_passing / items_total) * 100
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Coherence Score Aggregation (Contextual Mode)
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
reference_validity = (valid_references / total_references) * 100
|
|
168
|
+
integration_completeness = (complete_patterns / total_patterns) * 100
|
|
169
|
+
combined_coherence = (reference_validity * 0.6) + (integration_completeness * 0.4)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
If no integration patterns exist, combined coherence equals reference validity.
|
|
173
|
+
|
|
174
|
+
## Result Determination
|
|
175
|
+
|
|
176
|
+
Three-state gate — **PASS / FAIL / INCONCLUSIVE**. `INCONCLUSIVE` is not PASS and not FAIL; it signals insufficient evidence to grade the skill. Downstream workflows MUST treat `INCONCLUSIVE` as a hard gate — do not export, do not auto-retry, surface to the human.
|
|
177
|
+
|
|
178
|
+
- **Minimum-Evidence Floor (MANDATORY — applies before PASS/FAIL comparison):**
|
|
179
|
+
- `active_categories` = count of categories with a non-zero final weight *after* all redistribution (Quick tier, docs-only, State 2, external-validator-unavailable). Categories with a redistributed weight of 0 do not count as active, even if they received a score.
|
|
180
|
+
- **If `active_categories < 2`** → force `result: INCONCLUSIVE` with rationale `"insufficient evidence: only {N} active category"`. A single active category cannot cross-validate itself and a PASS would be a false signal.
|
|
181
|
+
- **If `tier == "Quick"` AND the sole active contributor is Export Coverage** → force `result: INCONCLUSIVE` with rationale `"Quick tier: Export Coverage alone is insufficient evidence — add a second active category by upgrading tier or enabling external validators"`. This catches the degenerate case where every signature/type/coherence/external category gets redistributed to 0 and Export Coverage is doing all the work.
|
|
182
|
+
- The floor is enforced by `scripts/compute-score.py`. The step-05 scoring step reads `result` from the script output and writes it into the test report frontmatter unchanged.
|
|
183
|
+
|
|
184
|
+
- Otherwise:
|
|
185
|
+
- score >= threshold → PASS
|
|
186
|
+
- score < threshold → FAIL
|
|
187
|
+
|
|
188
|
+
The floor is intentionally conservative: skf-test-skill grades other skills, so a false PASS has catastrophic downstream effects (polluted exports, misleading feasibility data). Falling back to INCONCLUSIVE is always preferred over a low-evidence PASS.
|
|
189
|
+
|
|
190
|
+
## Gap Severity
|
|
191
|
+
|
|
192
|
+
| Severity | Criteria |
|
|
193
|
+
|----------|----------------------------------------------------------------------------------------------------------------|
|
|
194
|
+
| Critical | Missing exported function/class documentation |
|
|
195
|
+
| High | Signature mismatch between source and SKILL.md |
|
|
196
|
+
| Medium | Missing type or interface documentation |
|
|
197
|
+
| Medium | Migration section present/absent mismatch with T2-future annotation data (Deep tier) |
|
|
198
|
+
| Medium | Metadata drift — intra-cluster export counts diverge (barrel: `stats.exports_public_api` vs `exports[].length`; or documented-surface: `stats.exports_documented` vs provenance-map entry count; >10% divergence) |
|
|
199
|
+
| Medium | Denominator inflation — stratified-scope `scope.include` union exceeds provenance-map entry count by >25% (brief missing `scope.tier_a_include`) |
|
|
200
|
+
| Medium | Script/asset directory exists but no Scripts & Assets section in SKILL.md |
|
|
201
|
+
| Medium | Scripts & Assets section references file not found in scripts/ or assets/ directory |
|
|
202
|
+
| Low | Script/asset file present without provenance entry in provenance-map.json file_entries |
|
|
203
|
+
| Low | Missing optional metadata or examples |
|
|
204
|
+
| Low | Description trigger optimization recommended (third-person voice, negative triggers, or keyword coverage gaps) |
|
|
205
|
+
| Info | Style suggestions, non-blocking observations |
|
|
206
|
+
| Info | Discovery testing not performed — realistic prompt testing recommended before export |
|
|
207
|
+
| Info | Multi-denominator reporting — barrel vs documented-surface clusters diverge by design (>10% cross-cluster) |
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Source Access Protocol
|
|
2
|
+
|
|
3
|
+
## Source API Surface Definition
|
|
4
|
+
|
|
5
|
+
**Source API surface** = the package's top-level public exports. These are the symbols reachable from the primary entry point without importing internal modules:
|
|
6
|
+
|
|
7
|
+
- **Python:** symbols exported in `__init__.py` (including re-exports) — exclude private (`_prefixed`) names
|
|
8
|
+
- **TypeScript/JavaScript:** named exports from `index.ts` / `index.js` — exclude unexported locals
|
|
9
|
+
- **Go:** exported identifiers (capitalized) from the package's public-facing files
|
|
10
|
+
- **Rust:** items in `pub use` from `lib.rs` or `mod.rs`
|
|
11
|
+
- **Empty-barrel packages (copy-paste / subpath-only distribution):** If the primary entry point is empty or re-exports nothing (e.g., `export {};` in `index.ts`, an empty `__init__.py`, `lib.rs` with no `pub use`), the package does not expose a barrel API. Do **not** compute coverage against the empty barrel — the denominator would be zero and the score meaningless. Instead, consult the skill brief's `scope.include` globs (`forge-data/{skill_name}/skill-brief.yaml`) to identify the authorized entry points, and build the public API surface from the **union of named exports across those files**. The skill brief's `scope.notes` field should document this distribution model explicitly; if present, treat it as confirmation that the empty barrel is by design rather than a bug. If no skill brief is available and the barrel is empty, set `analysis_confidence: docs-only` and report that the source API surface could not be determined.
|
|
12
|
+
|
|
13
|
+
- **Stratified-scope monorepo packages (curated subsets of multi-package repos):** If the source is a monorepo (detect via `packages/` layout, `workspaces` field in root `package.json`, `lerna.json`, `rush.json`, `nx.json`, or Cargo `[workspace]`) AND the skill brief's `scope.include` lists a curated file/directory subset rather than the full workspace, the coverage denominator must reflect only the authored surface, not the monorepo's global export count. This is distinct from the empty-barrel case: each workspace package may have a non-empty barrel, but the skill intentionally documents only a tiered subset.
|
|
14
|
+
|
|
15
|
+
**Resolution order:**
|
|
16
|
+
|
|
17
|
+
1. **Prefer `metadata.json.stats.effective_denominator`** when present. `skf-create-skill` step-05 §4 writes this field for stratified-scope skills. When set, use it directly as the `exports_public_api` count for coverage scoring.
|
|
18
|
+
2. **Fall back to live re-derivation** when `effective_denominator` is absent (older skills, quick-tier output, or skills compiled before this rule existed). Read the brief's scope globs from `forge-data/{skill_name}/skill-brief.yaml`, resolve them against `source_path`, filter out files matching `scope.exclude`, and compute the source API surface as the **union of named exports across the matched files only**. The skill brief's `scope.notes` field should document the stratification strategy (e.g., "Tier A: fully documented; Tier B: deferred to references; Tier C: excluded") — when present, treat it as confirmation that the curated subset is by design, not a scope gap.
|
|
19
|
+
|
|
20
|
+
**Honor `scope.tier_a_include` when present.** When re-deriving, prefer the brief-level `scope.tier_a_include` narrow include list over the coarse `scope.include`. `tier_a_include` is an optional brief field that lists only the authoring surface the brief actually intends to document (tier A), letting the denominator match the brief's authoring-vs-installing intent even when `scope.include` uses coarse globs that also match internal infrastructure. When `tier_a_include` is present, resolve its globs (still filtered by `scope.exclude`), compute the union across those files, and use that count as the denominator. When absent, fall back to resolving `scope.include`.
|
|
21
|
+
|
|
22
|
+
3. **Denominator inflation check (absent `tier_a_include`).** If re-derivation used `scope.include` because no `tier_a_include` was provided, compare the resulting union count against the provenance-map entry count (when provenance-map exists). If the `scope.include` union is more than 25% larger than the provenance-map entry count, the coarse globs are almost certainly sweeping in internal infrastructure that the brief did not intend to document. Emit a **Medium**-severity gap titled `denominator inflation — coarse scope.include union exceeds authored surface` that points the user at the brief for rescoping via `scope.tier_a_include`. Report both counts (`scope.include union: {N}`, `provenance-map entries: {M}`, `{percent}% inflation`) and state that the coverage score it produced is driven by denominator inflation rather than documentation gaps. The check is skipped when provenance-map is unavailable (there is no baseline to compare against).
|
|
23
|
+
|
|
24
|
+
Leave `analysis_confidence` unchanged (still `full` or `provenance-map` per the waterfall) — stratified scope does not degrade confidence, only the denominator. Annotate the coverage report with: `Stratified scope — denominator: {effective_denominator | tier_a_include union | scope.include union} ({N} files matched, {M} exports union)`.
|
|
25
|
+
|
|
26
|
+
**When this clause does NOT apply:** `scope.type: "full-library"` skills, single-package repositories, or stratified briefs where the full monorepo is intentionally in scope. For those, use the standard barrel-based denominator — **unless** the single-package repo is a pattern-reference app (see next bullet).
|
|
27
|
+
|
|
28
|
+
- **Pattern-reference apps (non-library source):** If the source is a single-package repo whose purpose is demonstrating an integration pattern rather than distributing a library API — typical markers are `scope.type: "full-library"` **without** a barrel file at any recognized entry-point path (`__init__.py`, `index.ts`/`index.js`, `lib.rs`, `mod.rs`) AND without a monorepo layout — the skill's value lives in wiring patterns, not exports. None of the preceding three clauses fits: there is no barrel to count from, no empty-barrel `scope.include` to consult, and no monorepo stratification to re-derive.
|
|
29
|
+
|
|
30
|
+
**Trigger (either fires):**
|
|
31
|
+
|
|
32
|
+
1. `scope.notes` in `forge-data/{skill_name}/skill-brief.yaml` flags pattern-reference intent (phrases such as "Reference app, not a library", "pattern-reference", "embedded-pattern skill", or "skill value is the … pattern"). The `scope.notes` field is authoritative when the author wrote it.
|
|
33
|
+
2. Source tree lacks a barrel file at every recognized entry-point path AND the repo is not a monorepo (no `packages/`, `workspaces`, `lerna.json`, `rush.json`, `nx.json`, or Cargo `[workspace]`). Detected at test time by filesystem inspection of `{source_path}`.
|
|
34
|
+
|
|
35
|
+
**Denominator:** canonicalized provenance-map entry count (same canonicalization as the "Provenance-map canonicalization" section below). `skf-create-skill`'s extraction pass has already curated the provenance-map to the authored pattern surface; treat it as the authoritative enumeration of the skill's documented reach.
|
|
36
|
+
|
|
37
|
+
**Recommendation — prefer `tier_a_include`:** authors should add `scope.tier_a_include` to the brief listing the files that constitute the authored pattern surface, the same way stratified-scope briefs do. When `tier_a_include` is present, use its re-derived union (filtered by `scope.exclude`) as the denominator exactly as in the stratified-scope clause. When absent, fall back to the canonicalized provenance-map count — do not fabricate a denominator from arbitrary source-tree sweeps.
|
|
38
|
+
|
|
39
|
+
**Confidence:** leave `analysis_confidence` unchanged (still `full` or `provenance-map` per the waterfall). Pattern-reference does not degrade confidence — the surface is smaller than a library barrel, not lower quality. Annotate the coverage report with: `Pattern-reference — denominator: {tier_a_include union | canonicalized provenance-map count} ({N} pattern surfaces)`.
|
|
40
|
+
|
|
41
|
+
**When this clause does NOT apply:** any repo with a non-empty barrel file, any monorepo (use the stratified-scope clause), or any single-package repo whose `scope.type` is explicitly `public-api` / `specific-modules` / `component-library` / `docs-only` (those scope types have their own denominator semantics). Also does NOT apply if `scope.type: "reference-app"` exists in the enum (pending upgrade in `skf-create-skill/steps-c/step-03-extract.md`) — in that case the brief speaks for itself and this clause's filesystem trigger is moot.
|
|
42
|
+
|
|
43
|
+
Internal module symbols are **excluded** from the coverage denominator unless they are explicitly documented in SKILL.md (in which case they count as documented extras, not missing coverage).
|
|
44
|
+
|
|
45
|
+
This matches the extraction-patterns.md convention used during skill creation: coverage measures how well SKILL.md documents what users actually import, not the entire internal codebase.
|
|
46
|
+
|
|
47
|
+
### Provenance-map canonicalization
|
|
48
|
+
|
|
49
|
+
When the test-side intersects documented SKILL.md exports against a stratified-scope provenance-map, raw provenance-map entry names may include **bookkeeping variants** of the same underlying export. These variants are artifacts of how the source library structures its registry (e.g., Storybook's component-plus-story decomposition, accessibility renderer shadowing, exact-match versus fuzzy-match renderer disambiguation). Counting them as separate exports inflates the denominator and produces false "missing documentation" findings for names that are structurally duplicates of an already-documented base export.
|
|
50
|
+
|
|
51
|
+
Before intersecting documented names against the provenance-map entry list, **fold bookkeeping variants back to their base name** using the rules below. This matches the convention `skf-create-skill` records in `metadata.json.stats.effective_denominator_source` (e.g., `"provenance-map canonicalized count (ThemesGlobals_def folds with ThemesGlobals under _def convention)"`) — the base form is authoritative; the variant form is a sibling record, not an independent export.
|
|
52
|
+
|
|
53
|
+
**Folding rules (apply in order, case-sensitive):**
|
|
54
|
+
|
|
55
|
+
1. **Suffix `_def`** — registry definition twin. `ThemesGlobals_def` folds to `ThemesGlobals`. Common in Storybook-style component registries where the definition object and the rendered component share the same public name.
|
|
56
|
+
2. **Suffix `_exact`** — exact-match variant. `ButtonSpec_exact` folds to `ButtonSpec`. Common in matcher/renderer registries where an `_exact` sibling signals a stricter resolution path.
|
|
57
|
+
3. **Prefix `a11y_`** — accessibility renderer shadow. `a11y_Checkbox` folds to `Checkbox`. Common in accessibility-wrapper layers that re-export every component under a parallel prefixed namespace.
|
|
58
|
+
4. **Other renderer-prefix disambiguation** — when the library uses a prefix-namespace convention (e.g., `mobile_`, `web_`, `ssr_`) to shadow the base export, fold the prefix form back to the base. **Only apply when the base form is also present in the provenance-map** — otherwise the prefix form is the real export and should be kept. Document the specific prefix used in the test report so the rule is auditable.
|
|
59
|
+
|
|
60
|
+
**How to apply:**
|
|
61
|
+
|
|
62
|
+
1. Read all entry names from the provenance-map.
|
|
63
|
+
2. Build a canonical-name set by applying the folding rules above — each variant maps to its base. Retain the original variant → base mapping for reporting.
|
|
64
|
+
3. Intersect the documented SKILL.md export names against the **canonical** set, not the raw entry list.
|
|
65
|
+
4. When computing `Export Coverage`, use the **canonical count** as the denominator — not the raw provenance-map entry count. This aligns the denominator with `metadata.json.stats.effective_denominator` (when present), which `skf-create-skill` already writes as the canonicalized count.
|
|
66
|
+
5. In the test report, note the fold summary: `Provenance-map canonicalization: {N} raw entries → {M} canonical bases ({N−M} bookkeeping variants folded: _def×{a}, _exact×{b}, a11y_×{c}, other×{d})`. This makes the reduction auditable by future testers and update runs.
|
|
67
|
+
|
|
68
|
+
**When to skip canonicalization:**
|
|
69
|
+
|
|
70
|
+
- If the library's public surface genuinely distinguishes the variants (e.g., `a11y_Checkbox` is a separately-documented, separately-installed component and not a shadow), do not fold — the variant is a real export. Check SKILL.md for explicit documentation of the variant before folding. When in doubt, err on the side of not folding and report both forms.
|
|
71
|
+
- If `metadata.json.stats.effective_denominator` is present and the provenance-map raw count matches it (no drift), canonicalization is not needed — the denominator is already canonical. Fold only when raw count > `effective_denominator` and the drift corresponds to recognizable bookkeeping suffixes/prefixes.
|
|
72
|
+
- If drift remains after folding (e.g., raw 222 → canonical 215 but `effective_denominator` says 216), record the residual 1-count drift as an unexplained-reconciliation note in the test report. Do not fabricate additional fold rules to close the gap.
|
|
73
|
+
|
|
74
|
+
## Source Access Resolution
|
|
75
|
+
|
|
76
|
+
Before analysis, determine source access level. Walk through these states in order — use the first that succeeds:
|
|
77
|
+
|
|
78
|
+
**State 1 — Local source available:**
|
|
79
|
+
Check if `{source_path}` (from metadata.json `source_root`) exists on disk. If yes → full analysis at detected tier (AST + signatures). Set `analysis_confidence: full`.
|
|
80
|
+
|
|
81
|
+
**State 2 — Local absent, provenance-map exists:**
|
|
82
|
+
Check `{forge_data_folder}/{skill_name}/provenance-map.json`. If present AND contains at least 1 entry, use it as the baseline export inventory — each entry contains structured fields: `export_name`, `export_type`, `params[]`, `return_type`, `source_file`, `source_line`, `confidence`, and `ast_node_type`. Cross-reference against SKILL.md documented exports for name-matching and param-by-param coverage. Signature verification compares SKILL.md's documented params/return types against provenance-map entries directly.
|
|
83
|
+
|
|
84
|
+
**Cross-reference with metadata.json:** After loading provenance-map entries, compare the entry count against `metadata.json`'s `exports[]` array length and `stats.exports_public_api` count. If metadata reports more exports than provenance-map entries:
|
|
85
|
+
- Compute `gap = metadata.exports.length - provenance_map.entries.length`
|
|
86
|
+
- Report: "Provenance-map contains {pmap_count} entries but metadata.json lists {meta_count} exports ({gap} gap). Coverage denominator uses the union."
|
|
87
|
+
- Build the coverage denominator from the **union** of provenance-map entry names and metadata.json `exports[]` names. Exports present in metadata but absent from provenance-map are counted as "missing documentation" in the coverage calculation.
|
|
88
|
+
- If metadata.json is unavailable or has no `exports[]` array, use provenance-map count alone with a note: "Coverage denominator is provenance-map only — may undercount if extraction was incomplete." If remote reading tools are available (zread, deepwiki, gh API, or similar), supplement by reading the entry point file for live signature verification. Set `analysis_confidence: provenance-map`.
|
|
89
|
+
|
|
90
|
+
**State 2 limitations:** Signature verification at State 2 is **string comparison only**, not semantic. Provenance-map stores parameters as flat string arrays (e.g., `["data: Union[BinaryIO, list, str]"]`), so `str` vs `String` or `list` vs `List[Any]` would be treated as mismatches even when semantically equivalent. For full type-aware verification (handling type aliases, generic equivalence), State 1 (local source) with AST re-parsing is required. When the SKILL.md was compiled from the same provenance-map (typical for create-then-test flows), most strings will match. However, enrichment (step-04) and doc-fetching (step-03c) during compilation may alter parameter descriptions, add type annotations, or normalize signatures, causing mismatches even in create-then-test flows. Expect some string-level mismatches and treat them as compilation artifacts, not source drift signals, until signature fidelity is enforced by step-05's Signature Fidelity Rule (see `signature_source` field in provenance-map entries).
|
|
91
|
+
|
|
92
|
+
**State 3 — No provenance-map, metadata exports exist (quick-skill path):**
|
|
93
|
+
If no provenance-map.json exists (typical for quick-skill output), fall back to `metadata.json`'s `exports[]` array for the export name list. Coverage check becomes a self-consistency comparison: are all names in `exports[]` documented in SKILL.md with description, parameters, and return type? Signatures cannot be verified. If remote reading tools are available, supplement by reading the entry point for live export comparison. Set `analysis_confidence: metadata-only`.
|
|
94
|
+
|
|
95
|
+
**State 4 — No local source, no forge-data, remote tools available:**
|
|
96
|
+
If neither provenance-map nor metadata exports provide a usable baseline, but remote reading tools (zread, deepwiki, gh API, or similar) are available and `source_repo` is set in metadata.json, read the entry point remotely to build the export inventory from scratch. Name-matching only — no AST. Set `analysis_confidence: remote-only`.
|
|
97
|
+
|
|
98
|
+
**State 5 — No source access at all:**
|
|
99
|
+
If none of the above succeed, fall through to docs-only mode (as defined in step-03-coverage-check.md Section 0: pre-analysis source type detection). Set `analysis_confidence: docs-only`. Warn: "**No source access available.** Coverage check evaluates documentation self-consistency only. Re-run with local clone or remote access for source-backed verification."
|
|
100
|
+
|
|
101
|
+
Set `analysis_confidence` in context for use in Section 2 analysis depth, step-05 output, and step-05 scoring.
|
|
102
|
+
|
|
103
|
+
**Confidence tier mapping:** `full` = T1, `provenance-map` = T1, `metadata-only` = T1-low, `remote-only` = T1-low, `docs-only` = T3. This aligns with the T1/T1-low/T2/T3 scale used across all SKF workflows.
|
|
104
|
+
|
|
105
|
+
**Degradation notice rules:** When `analysis_confidence` is `provenance-map`, check the `confidence` field of provenance-map entries before emitting a degradation recommendation:
|
|
106
|
+
|
|
107
|
+
- **All/most entries T1 (AST-verified):** The provenance-map data is already at highest confidence. Do NOT recommend re-running with a local clone — it would produce identical results. Use: "Resolved via: provenance-map (T1 AST-verified at compilation time). Local clone not required — provenance data is already at highest confidence."
|
|
108
|
+
- **Mixed T1/T1-low entries:** Report the breakdown. Recommend local clone only for the T1-low entries: "Resolved via: provenance-map ({n} T1, {m} T1-low). Re-run with local clone to upgrade T1-low entries to full AST verification."
|
|
109
|
+
- **All/most entries T1-low or lower:** Keep the standard recommendation: "Re-run with local clone for full AST-backed verification."
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
# /// script
|
|
2
|
+
# requires-python = ">=3.10"
|
|
3
|
+
# dependencies = []
|
|
4
|
+
# ///
|
|
5
|
+
"""Deterministic Completeness Score Calculator.
|
|
6
|
+
|
|
7
|
+
Pure-function scoring script for the SKF test-skill workflow (step-05).
|
|
8
|
+
Implements the weight tables, skip conditions, and proportional redistribution
|
|
9
|
+
defined in scoring-rules.md.
|
|
10
|
+
|
|
11
|
+
CLI: python3 compute-score.py '<JSON>'
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import math
|
|
18
|
+
import sys
|
|
19
|
+
|
|
20
|
+
# --- Weight Tables (from scoring-rules.md) ---
|
|
21
|
+
|
|
22
|
+
CONTEXTUAL_WEIGHTS = {
|
|
23
|
+
"exportCoverage": 36,
|
|
24
|
+
"signatureAccuracy": 22,
|
|
25
|
+
"typeCoverage": 14,
|
|
26
|
+
"coherence": 18,
|
|
27
|
+
"externalValidation": 10,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
NAIVE_WEIGHTS = {
|
|
31
|
+
"exportCoverage": 45,
|
|
32
|
+
"signatureAccuracy": 25,
|
|
33
|
+
"typeCoverage": 20,
|
|
34
|
+
"coherence": 0,
|
|
35
|
+
"externalValidation": 10,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
CATEGORIES = [
|
|
39
|
+
"exportCoverage",
|
|
40
|
+
"signatureAccuracy",
|
|
41
|
+
"typeCoverage",
|
|
42
|
+
"coherence",
|
|
43
|
+
"externalValidation",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
DEFAULT_THRESHOLD = 80
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# --- Helpers ---
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def round2(value):
|
|
53
|
+
"""Round to 2 decimal places using JavaScript-compatible rounding.
|
|
54
|
+
|
|
55
|
+
JavaScript Math.round rounds .5 up (away from zero for positives).
|
|
56
|
+
Python's built-in round uses banker's rounding (.5 to even).
|
|
57
|
+
We replicate JS behavior: floor(value * 100 + 0.5) / 100.
|
|
58
|
+
"""
|
|
59
|
+
return math.floor(value * 100 + 0.5) / 100
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def make_error(message):
|
|
63
|
+
return {"error": message, "code": "INVALID_INPUT"}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# --- Validation ---
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
BOOL_FIELDS = ("docsOnly", "state2", "stackSkill")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def validate_input(inp):
|
|
73
|
+
if inp is None or not isinstance(inp, dict):
|
|
74
|
+
return "Input must be a JSON object"
|
|
75
|
+
|
|
76
|
+
if not inp.get("mode") or inp["mode"] not in ("contextual", "naive"):
|
|
77
|
+
return 'Missing or invalid required field: mode (must be "contextual" or "naive")'
|
|
78
|
+
|
|
79
|
+
valid_tiers = ["Quick", "Forge", "Forge+", "Deep"]
|
|
80
|
+
if not inp.get("tier") or inp["tier"] not in valid_tiers:
|
|
81
|
+
return f"Missing or invalid required field: tier (must be one of: {', '.join(valid_tiers)})"
|
|
82
|
+
|
|
83
|
+
# H3: reject string booleans — bare true/false only.
|
|
84
|
+
# `bool` is a subclass of `int` in Python; accept only actual bools or the
|
|
85
|
+
# absence of the field. Strings like "true"/"false" and ints 0/1 are rejected
|
|
86
|
+
# to catch the common YAML/JSON hand-editing mistake of quoting the value.
|
|
87
|
+
for field in BOOL_FIELDS:
|
|
88
|
+
if field in inp and inp[field] is not None:
|
|
89
|
+
if not isinstance(inp[field], bool):
|
|
90
|
+
return (
|
|
91
|
+
f"Field `{field}` must be a bare boolean (true/false). "
|
|
92
|
+
f"Got {type(inp[field]).__name__}: {inp[field]!r}. "
|
|
93
|
+
"String 'true'/'false' or 0/1 is not accepted — "
|
|
94
|
+
"this typically indicates a YAML/JSON quoting mistake."
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
if "scores" not in inp or not isinstance(inp.get("scores"), dict):
|
|
98
|
+
return "Missing required field: scores"
|
|
99
|
+
|
|
100
|
+
if inp["scores"].get("exportCoverage") is None:
|
|
101
|
+
return "scores.exportCoverage is required and cannot be null"
|
|
102
|
+
|
|
103
|
+
threshold = inp.get("threshold")
|
|
104
|
+
if threshold is not None:
|
|
105
|
+
if not isinstance(threshold, (int, float)) or threshold < 0 or threshold > 100:
|
|
106
|
+
return "threshold must be a number between 0 and 100"
|
|
107
|
+
|
|
108
|
+
for cat in CATEGORIES:
|
|
109
|
+
score = inp["scores"].get(cat)
|
|
110
|
+
if score is not None:
|
|
111
|
+
if isinstance(score, bool) or not isinstance(score, (int, float)):
|
|
112
|
+
return f"scores.{cat} must be a number or null, got: {type(score).__name__}"
|
|
113
|
+
if score < 0 or score > 100:
|
|
114
|
+
return f"scores.{cat} must be between 0 and 100, got: {score}"
|
|
115
|
+
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# --- Core Scoring Function ---
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def compute_score(inp):
|
|
123
|
+
# 1. Validate
|
|
124
|
+
validation_error = validate_input(inp)
|
|
125
|
+
if validation_error:
|
|
126
|
+
return make_error(validation_error)
|
|
127
|
+
|
|
128
|
+
mode = inp["mode"]
|
|
129
|
+
tier = inp["tier"]
|
|
130
|
+
docs_only = inp.get("docsOnly") is True
|
|
131
|
+
state2 = inp.get("state2") is True
|
|
132
|
+
stack_skill = inp.get("stackSkill") is True
|
|
133
|
+
threshold = inp.get("threshold") if inp.get("threshold") is not None else DEFAULT_THRESHOLD
|
|
134
|
+
scores = inp["scores"]
|
|
135
|
+
|
|
136
|
+
# 2. Select base weight table
|
|
137
|
+
base_weights = dict(NAIVE_WEIGHTS if mode == "naive" else CONTEXTUAL_WEIGHTS)
|
|
138
|
+
|
|
139
|
+
# 3. Determine skip set
|
|
140
|
+
skip_reasons = {}
|
|
141
|
+
skip_sig_type = tier == "Quick" or docs_only or state2 or stack_skill
|
|
142
|
+
|
|
143
|
+
if skip_sig_type:
|
|
144
|
+
reasons = []
|
|
145
|
+
if tier == "Quick":
|
|
146
|
+
reasons.append("Quick tier")
|
|
147
|
+
if docs_only:
|
|
148
|
+
reasons.append("docs-only mode")
|
|
149
|
+
if state2:
|
|
150
|
+
reasons.append("State 2 (provenance-map)")
|
|
151
|
+
if stack_skill:
|
|
152
|
+
reasons.append("stack skill (external type surface)")
|
|
153
|
+
reason = " + ".join(reasons)
|
|
154
|
+
skip_reasons["signatureAccuracy"] = reason
|
|
155
|
+
skip_reasons["typeCoverage"] = reason
|
|
156
|
+
|
|
157
|
+
if scores.get("externalValidation") is None:
|
|
158
|
+
skip_reasons["externalValidation"] = "No external validators available"
|
|
159
|
+
|
|
160
|
+
# Collect warnings
|
|
161
|
+
warnings = []
|
|
162
|
+
for cat in skip_reasons:
|
|
163
|
+
if scores.get(cat) is not None:
|
|
164
|
+
warnings.append(
|
|
165
|
+
f"{cat} score provided ({scores[cat]}) but category is skipped — score ignored"
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# Validate active categories have scores
|
|
169
|
+
skipped_set = set(skip_reasons.keys())
|
|
170
|
+
for cat in CATEGORIES:
|
|
171
|
+
is_active = cat not in skipped_set and base_weights[cat] > 0
|
|
172
|
+
score_missing = scores.get(cat) is None
|
|
173
|
+
if is_active and score_missing:
|
|
174
|
+
return make_error(
|
|
175
|
+
f"Category {cat} is active but score is null. "
|
|
176
|
+
"Provide a numeric score or set the appropriate skip condition."
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# 4. Redistribute weights
|
|
180
|
+
adjusted_weights = dict(base_weights)
|
|
181
|
+
for cat in skip_reasons:
|
|
182
|
+
adjusted_weights[cat] = 0
|
|
183
|
+
|
|
184
|
+
sum_active_weights = sum(adjusted_weights[cat] for cat in CATEGORIES)
|
|
185
|
+
|
|
186
|
+
final_weights = {}
|
|
187
|
+
for cat in CATEGORIES:
|
|
188
|
+
if adjusted_weights[cat] == 0:
|
|
189
|
+
final_weights[cat] = 0
|
|
190
|
+
else:
|
|
191
|
+
final_weights[cat] = round2((adjusted_weights[cat] / sum_active_weights) * 100)
|
|
192
|
+
|
|
193
|
+
# 5. Compute weighted scores
|
|
194
|
+
weighted_scores = {}
|
|
195
|
+
for cat in CATEGORIES:
|
|
196
|
+
if final_weights[cat] == 0:
|
|
197
|
+
weighted_scores[cat] = 0
|
|
198
|
+
else:
|
|
199
|
+
weighted_scores[cat] = round2((final_weights[cat] / 100) * scores[cat])
|
|
200
|
+
|
|
201
|
+
# 6. Compute total
|
|
202
|
+
total_score = round2(sum(weighted_scores[cat] for cat in CATEGORIES))
|
|
203
|
+
|
|
204
|
+
# Weight sum for verification
|
|
205
|
+
weight_sum = round2(sum(final_weights[cat] for cat in CATEGORIES))
|
|
206
|
+
|
|
207
|
+
# 7. Determine result — MINIMUM-EVIDENCE FLOOR first, then PASS/FAIL.
|
|
208
|
+
# skf-test-skill grades other skills; a false PASS is catastrophic.
|
|
209
|
+
# If the evidence base is too thin to cross-validate itself, force
|
|
210
|
+
# INCONCLUSIVE (a gate, not a pass/fail). See scoring-rules.md.
|
|
211
|
+
active_categories = [cat for cat in CATEGORIES if final_weights[cat] > 0]
|
|
212
|
+
skipped_categories = [
|
|
213
|
+
cat for cat in CATEGORIES if cat in skipped_set or base_weights[cat] == 0
|
|
214
|
+
]
|
|
215
|
+
|
|
216
|
+
floor_reasons = []
|
|
217
|
+
if len(active_categories) < 2:
|
|
218
|
+
floor_reasons.append(
|
|
219
|
+
f"insufficient evidence: only {len(active_categories)} active category"
|
|
220
|
+
)
|
|
221
|
+
elif tier == "Quick" and active_categories == ["exportCoverage"]:
|
|
222
|
+
# Defensive second check: if somehow there are >= 2 active categories
|
|
223
|
+
# but they collapse to just exportCoverage (shouldn't happen given the
|
|
224
|
+
# first clause, kept for robustness), still force INCONCLUSIVE.
|
|
225
|
+
floor_reasons.append(
|
|
226
|
+
"Quick tier: Export Coverage alone is insufficient evidence "
|
|
227
|
+
"— add a second active category by upgrading tier or enabling "
|
|
228
|
+
"external validators"
|
|
229
|
+
)
|
|
230
|
+
elif tier == "Quick":
|
|
231
|
+
# Cover the case where Export Coverage is the only active category
|
|
232
|
+
# carrying signal in Quick tier even when technically another
|
|
233
|
+
# non-contributing category survived redistribution.
|
|
234
|
+
non_export_active_scores = [
|
|
235
|
+
scores.get(cat) for cat in active_categories if cat != "exportCoverage"
|
|
236
|
+
]
|
|
237
|
+
# All other active categories have a zero score => Export Coverage is
|
|
238
|
+
# the sole real contributor.
|
|
239
|
+
if active_categories and "exportCoverage" in active_categories and all(
|
|
240
|
+
(s == 0) for s in non_export_active_scores
|
|
241
|
+
) and non_export_active_scores:
|
|
242
|
+
floor_reasons.append(
|
|
243
|
+
"Quick tier: Export Coverage is the sole scoring contributor "
|
|
244
|
+
"(other active categories scored 0) — insufficient evidence"
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
if floor_reasons:
|
|
248
|
+
result = "INCONCLUSIVE"
|
|
249
|
+
else:
|
|
250
|
+
result = "PASS" if total_score >= threshold else "FAIL"
|
|
251
|
+
|
|
252
|
+
# Build scores echo with null preservation
|
|
253
|
+
scores_echo = {}
|
|
254
|
+
for cat in CATEGORIES:
|
|
255
|
+
scores_echo[cat] = scores.get(cat)
|
|
256
|
+
|
|
257
|
+
output = {
|
|
258
|
+
"input": {
|
|
259
|
+
"mode": mode,
|
|
260
|
+
"tier": tier,
|
|
261
|
+
"docsOnly": docs_only,
|
|
262
|
+
"state2": state2,
|
|
263
|
+
"stackSkill": stack_skill,
|
|
264
|
+
"threshold": threshold,
|
|
265
|
+
"scores": scores_echo,
|
|
266
|
+
},
|
|
267
|
+
"activeCategories": active_categories,
|
|
268
|
+
"skippedCategories": skipped_categories,
|
|
269
|
+
"skipReasons": skip_reasons,
|
|
270
|
+
"weights": final_weights,
|
|
271
|
+
"weightedScores": weighted_scores,
|
|
272
|
+
"totalScore": total_score,
|
|
273
|
+
"threshold": threshold,
|
|
274
|
+
"result": result,
|
|
275
|
+
"weightSum": weight_sum,
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if warnings:
|
|
279
|
+
output["warnings"] = warnings
|
|
280
|
+
|
|
281
|
+
if floor_reasons:
|
|
282
|
+
output["inconclusiveReasons"] = floor_reasons
|
|
283
|
+
|
|
284
|
+
return output
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
# --- CLI Entry Point ---
|
|
288
|
+
|
|
289
|
+
if __name__ == "__main__":
|
|
290
|
+
if len(sys.argv) < 2:
|
|
291
|
+
print("Usage: python3 compute-score.py '<JSON>'", file=sys.stderr)
|
|
292
|
+
print(
|
|
293
|
+
'Example: python3 compute-score.py \'{"mode":"contextual","tier":"Deep",'
|
|
294
|
+
'"scores":{"exportCoverage":92,"signatureAccuracy":85,"typeCoverage":100,'
|
|
295
|
+
'"coherence":80,"externalValidation":78}}\'',
|
|
296
|
+
file=sys.stderr,
|
|
297
|
+
)
|
|
298
|
+
sys.exit(1)
|
|
299
|
+
|
|
300
|
+
try:
|
|
301
|
+
data = json.loads(sys.argv[1])
|
|
302
|
+
except json.JSONDecodeError:
|
|
303
|
+
print(
|
|
304
|
+
json.dumps(make_error(f"Invalid JSON: {sys.argv[1][:100]}"), indent=2)
|
|
305
|
+
)
|
|
306
|
+
sys.exit(1)
|
|
307
|
+
|
|
308
|
+
result = compute_score(data)
|
|
309
|
+
print(json.dumps(result, indent=2))
|
|
310
|
+
sys.exit(1 if "error" in result else 0)
|