bmad-module-skill-forge 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.nvmrc +1 -1
- package/docs/_data/pinned.yaml +1 -1
- package/package.json +2 -2
- package/src/knowledge/skill-lifecycle.md +1 -1
- package/src/module-help.csv +2 -2
- package/src/shared/scripts/skf-comention-pairs.py +350 -0
- package/src/shared/scripts/skf-count-tokens.py +222 -0
- package/src/shared/scripts/skf-detect-docs.py +232 -1
- package/src/shared/scripts/skf-detect-language.py +77 -0
- package/src/shared/scripts/skf-detect-tools.py +105 -2
- package/src/shared/scripts/skf-enumerate-stack-skills.py +111 -2
- package/src/shared/scripts/skf-find-cycles.py +246 -0
- package/src/shared/scripts/skf-hash-content.py +487 -2
- package/src/shared/scripts/skf-manifest-ops.py +67 -1
- package/src/shared/scripts/skf-rebuild-managed-sections.py +180 -0
- package/src/shared/scripts/skf-recommend-scope-type.py +1 -2
- package/src/shared/scripts/skf-render-metadata-stats.py +499 -0
- package/src/shared/scripts/skf-rewrite-skill-name.py +308 -0
- package/src/shared/scripts/skf-shard-body.py +461 -0
- package/src/shared/scripts/skf-skill-inventory.py +139 -3
- package/src/shared/scripts/skf-structural-diff.py +307 -67
- package/src/shared/scripts/skf-validate-brief-schema.py +39 -1
- package/src/shared/scripts/skf-validate-feasibility-report.py +234 -0
- package/src/shared/scripts/skf-validate-frontmatter.py +17 -12
- package/src/shared/scripts/skf-validate-output.py +443 -10
- package/src/shared/scripts/skf-validate-pins.py +1 -1
- package/src/shared/scripts/skf-verify-no-trace.py +214 -0
- package/src/shared/scripts/skf-verify-provenance-completeness.py +439 -0
- package/src/skf-analyze-source/SKILL.md +17 -32
- package/src/skf-analyze-source/customize.toml +2 -0
- package/src/skf-analyze-source/references/auto-docs-only.md +116 -0
- package/src/skf-analyze-source/references/continue.md +8 -13
- package/src/skf-analyze-source/references/generate-briefs.md +28 -12
- package/src/skf-analyze-source/references/headless-contract.md +33 -0
- package/src/skf-analyze-source/references/health-check.md +1 -2
- package/src/skf-analyze-source/references/identify-units.md +25 -36
- package/src/skf-analyze-source/references/init.md +12 -25
- package/src/skf-analyze-source/references/map-and-detect.md +8 -20
- package/src/skf-analyze-source/references/recommend.md +4 -14
- package/src/skf-analyze-source/references/scan-project.md +3 -11
- package/src/skf-analyze-source/references/step-auto-scope.md +42 -180
- package/src/skf-analyze-source/references/unit-detection-heuristics.md +2 -2
- package/src/skf-audit-skill/SKILL.md +9 -6
- package/src/skf-audit-skill/customize.toml +2 -0
- package/src/skf-audit-skill/references/init.md +58 -32
- package/src/skf-audit-skill/references/re-index.md +15 -34
- package/src/skf-audit-skill/references/report.md +11 -58
- package/src/skf-audit-skill/references/semantic-diff.md +4 -22
- package/src/skf-audit-skill/references/severity-classify.md +53 -87
- package/src/skf-audit-skill/references/step-doc-drift.md +49 -19
- package/src/skf-audit-skill/references/structural-diff.md +43 -68
- package/src/skf-brief-skill/SKILL.md +7 -36
- package/src/skf-brief-skill/assets/scope-templates.md +1 -1
- package/src/skf-brief-skill/customize.toml +14 -0
- package/src/skf-brief-skill/references/analyze-target.md +6 -14
- package/src/skf-brief-skill/references/confirm-brief.md +2 -14
- package/src/skf-brief-skill/references/draft-checkpoint.md +8 -8
- package/src/skf-brief-skill/references/gather-intent.md +19 -22
- package/src/skf-brief-skill/references/headless-args.md +1 -1
- package/src/skf-brief-skill/references/health-check.md +1 -22
- package/src/skf-brief-skill/references/invocation-contract.md +37 -0
- package/src/skf-brief-skill/references/scope-definition.md +4 -14
- package/src/skf-brief-skill/references/step-auto-brief.md +1 -5
- package/src/skf-brief-skill/references/step-auto-validate.md +1 -5
- package/src/skf-brief-skill/references/version-resolution.md +1 -1
- package/src/skf-brief-skill/references/write-brief.md +17 -10
- package/src/skf-campaign/SKILL.md +15 -84
- package/src/skf-campaign/customize.toml +2 -0
- package/src/skf-campaign/manifest.yaml +4 -0
- package/src/skf-campaign/references/campaign-contracts.md +67 -0
- package/src/skf-campaign/references/campaign-directive-spec.md +14 -103
- package/src/skf-campaign/references/step-01-setup.md +4 -5
- package/src/skf-campaign/references/step-02-strategy.md +8 -3
- package/src/skf-campaign/references/step-03-pins.md +1 -1
- package/src/skf-campaign/references/step-04-provenance.md +1 -1
- package/src/skf-campaign/references/step-05-skill-loop.md +1 -1
- package/src/skf-campaign/references/step-06-batch.md +7 -6
- package/src/skf-campaign/references/step-11-maintenance.md +2 -2
- package/src/skf-campaign/references/step-resume.md +9 -8
- package/src/skf-campaign/scripts/campaign-deps.py +10 -1
- package/src/skf-campaign/scripts/campaign-render-batch.py +199 -0
- package/src/skf-campaign/scripts/campaign-status.py +162 -0
- package/src/skf-campaign/templates/campaign-brief-template.yaml +1 -1
- package/src/skf-create-skill/SKILL.md +14 -3
- package/src/skf-create-skill/assets/compile-assembly-rules.md +17 -38
- package/src/skf-create-skill/assets/skill-sections.md +9 -100
- package/src/skf-create-skill/assets/tessl-dismissal-rules.md +4 -13
- package/src/skf-create-skill/customize.toml +13 -0
- package/src/skf-create-skill/references/authoritative-files-protocol.md +7 -7
- package/src/skf-create-skill/references/compile.md +34 -47
- package/src/skf-create-skill/references/component-extraction.md +8 -35
- package/src/skf-create-skill/references/ecosystem-check.md +9 -25
- package/src/skf-create-skill/references/enrich.md +4 -17
- package/src/skf-create-skill/references/extract.md +14 -29
- package/src/skf-create-skill/references/extraction-patterns-tracing.md +1 -1
- package/src/skf-create-skill/references/extraction-patterns.md +7 -7
- package/src/skf-create-skill/references/generate-artifacts.md +9 -23
- package/src/skf-create-skill/references/health-check.md +1 -1
- package/src/skf-create-skill/references/load-brief.md +16 -18
- package/src/skf-create-skill/references/report.md +12 -15
- package/src/skf-create-skill/references/source-resolution-protocols.md +3 -3
- package/src/skf-create-skill/references/step-auto-shard.md +52 -25
- package/src/skf-create-skill/references/step-doc-rot.md +30 -12
- package/src/skf-create-skill/references/step-doc-sources.md +2 -2
- package/src/skf-create-skill/references/sub/ccc-discover.md +2 -13
- package/src/skf-create-skill/references/sub/fetch-docs.md +8 -50
- package/src/skf-create-skill/references/sub/fetch-temporal.md +5 -20
- package/src/skf-create-skill/references/tier-degradation-rules.md +6 -8
- package/src/skf-create-skill/references/validate.md +59 -28
- package/src/skf-create-skill/scripts/scan-doc-rot.py +245 -0
- package/src/skf-create-stack-skill/SKILL.md +15 -9
- package/src/skf-create-stack-skill/assets/provenance-map-schema.md +1 -1
- package/src/skf-create-stack-skill/customize.toml +10 -0
- package/src/skf-create-stack-skill/references/compile-stack.md +7 -8
- package/src/skf-create-stack-skill/references/compose-mode-rules.md +2 -13
- package/src/skf-create-stack-skill/references/detect-integrations.md +49 -54
- package/src/skf-create-stack-skill/references/detect-manifests.md +37 -30
- package/src/skf-create-stack-skill/references/generate-output.md +24 -75
- package/src/skf-create-stack-skill/references/init.md +9 -81
- package/src/skf-create-stack-skill/references/manifest-patterns.md +5 -35
- package/src/skf-create-stack-skill/references/parallel-extract.md +12 -27
- package/src/skf-create-stack-skill/references/rank-and-confirm.md +6 -6
- package/src/skf-create-stack-skill/references/report.md +27 -62
- package/src/skf-create-stack-skill/references/validate.md +35 -31
- package/src/skf-drop-skill/SKILL.md +20 -23
- package/src/skf-drop-skill/customize.toml +10 -0
- package/src/skf-drop-skill/references/execute.md +51 -31
- package/src/skf-drop-skill/references/headless-contract.md +18 -0
- package/src/skf-drop-skill/references/health-check.md +1 -13
- package/src/skf-drop-skill/references/report.md +42 -10
- package/src/skf-drop-skill/references/select.md +88 -48
- package/src/skf-drop-skill/scripts/dir-sizes.py +116 -0
- package/src/skf-export-skill/SKILL.md +17 -14
- package/src/skf-export-skill/assets/managed-section-format.md +2 -5
- package/src/skf-export-skill/customize.toml +10 -6
- package/src/skf-export-skill/references/generate-snippet.md +21 -35
- package/src/skf-export-skill/references/load-skill.md +44 -42
- package/src/skf-export-skill/references/multi-skill-mode.md +1 -1
- package/src/skf-export-skill/references/orphan-context-detection.md +11 -3
- package/src/skf-export-skill/references/package.md +27 -33
- package/src/skf-export-skill/references/preflight-snippet-root-probe.md +24 -5
- package/src/skf-export-skill/references/result-envelope.md +28 -0
- package/src/skf-export-skill/references/summary.md +11 -5
- package/src/skf-export-skill/references/token-report.md +42 -24
- package/src/skf-export-skill/references/update-context.md +32 -33
- package/src/skf-forger/SKILL.md +27 -39
- package/src/skf-forger/references/pipeline-mode.md +34 -0
- package/src/skf-forger/scripts/parse-pipeline.py +270 -0
- package/src/skf-quick-skill/SKILL.md +10 -60
- package/src/skf-quick-skill/customize.toml +9 -0
- package/src/skf-quick-skill/references/batch-mode.md +6 -4
- package/src/skf-quick-skill/references/compile.md +15 -52
- package/src/skf-quick-skill/references/ecosystem-check.md +5 -20
- package/src/skf-quick-skill/references/finalize.md +12 -6
- package/src/skf-quick-skill/references/halt-contract.md +54 -0
- package/src/skf-quick-skill/references/health-check.md +3 -4
- package/src/skf-quick-skill/references/quick-extract.md +7 -18
- package/src/skf-quick-skill/references/registry-resolution.md +1 -23
- package/src/skf-quick-skill/references/resolve-target.md +27 -34
- package/src/skf-quick-skill/references/write-and-validate.md +10 -25
- package/src/skf-refine-architecture/SKILL.md +14 -7
- package/src/skf-refine-architecture/customize.toml +9 -0
- package/src/skf-refine-architecture/references/compile.md +9 -7
- package/src/skf-refine-architecture/references/gap-analysis.md +21 -50
- package/src/skf-refine-architecture/references/health-check.md +1 -1
- package/src/skf-refine-architecture/references/improvements.md +9 -25
- package/src/skf-refine-architecture/references/init.md +9 -16
- package/src/skf-refine-architecture/references/issue-detection.md +10 -25
- package/src/skf-refine-architecture/references/refinement-rules.md +4 -16
- package/src/skf-refine-architecture/references/report.md +13 -10
- package/src/skf-rename-skill/SKILL.md +11 -21
- package/src/skf-rename-skill/customize.toml +10 -0
- package/src/skf-rename-skill/references/execute.md +107 -78
- package/src/skf-rename-skill/references/exit-codes.md +12 -0
- package/src/skf-rename-skill/references/report.md +16 -8
- package/src/skf-rename-skill/references/select.md +69 -42
- package/src/skf-rename-skill/scripts/skf-validate-rename-name.py +161 -0
- package/src/skf-setup/SKILL.md +20 -12
- package/src/skf-setup/customize.toml +10 -0
- package/src/skf-setup/references/auto-index.md +9 -10
- package/src/skf-setup/references/ccc-index.md +14 -15
- package/src/skf-setup/references/detect-and-tier.md +6 -8
- package/src/skf-setup/references/report.md +22 -31
- package/src/skf-setup/references/tier-rules.md +2 -32
- package/src/skf-setup/references/write-config.md +9 -10
- package/src/skf-test-skill/SKILL.md +10 -8
- package/src/skf-test-skill/customize.toml +2 -0
- package/src/skf-test-skill/references/coherence-check.md +19 -31
- package/src/skf-test-skill/references/coverage-check.md +109 -75
- package/src/skf-test-skill/references/detect-mode.md +10 -41
- package/src/skf-test-skill/references/external-validators.md +11 -19
- package/src/skf-test-skill/references/init.md +38 -20
- package/src/skf-test-skill/references/migration-section-rules.md +4 -4
- package/src/skf-test-skill/references/report.md +60 -40
- package/src/skf-test-skill/references/score.md +32 -64
- package/src/skf-test-skill/references/scoring-rules.md +6 -62
- package/src/skf-test-skill/references/source-access-protocol.md +6 -6
- package/src/skf-test-skill/references/step-hard-gate.md +1 -3
- package/src/skf-test-skill/scripts/aggregate-coherence.py +276 -0
- package/src/skf-test-skill/scripts/check-metadata-coherence.py +359 -0
- package/src/skf-test-skill/scripts/combine-external-scores.py +190 -0
- package/src/skf-test-skill/scripts/compute-score.py +131 -1
- package/src/skf-test-skill/scripts/reconcile-coverage.py +327 -0
- package/src/skf-test-skill/scripts/validate-inventory.py +251 -0
- package/src/skf-test-skill/scripts/verify-declared-numerator.py +192 -0
- package/src/skf-update-skill/SKILL.md +17 -5
- package/src/skf-update-skill/customize.toml +14 -0
- package/src/skf-update-skill/references/detect-changes.md +19 -37
- package/src/skf-update-skill/references/health-check.md +3 -9
- package/src/skf-update-skill/references/init.md +39 -32
- package/src/skf-update-skill/references/manual-section-rules.md +1 -2
- package/src/skf-update-skill/references/merge-conflict-rules.md +1 -52
- package/src/skf-update-skill/references/merge.md +13 -57
- package/src/skf-update-skill/references/re-extract.md +12 -24
- package/src/skf-update-skill/references/report.md +14 -7
- package/src/skf-update-skill/references/validate.md +32 -30
- package/src/skf-update-skill/references/write.md +86 -51
- package/src/skf-update-skill/scripts/skf-new-file-diff.py +167 -0
- package/src/skf-verify-stack/SKILL.md +11 -21
- package/src/skf-verify-stack/customize.toml +12 -3
- package/src/skf-verify-stack/references/coverage-patterns.md +1 -29
- package/src/skf-verify-stack/references/coverage.md +21 -10
- package/src/skf-verify-stack/references/exit-codes.md +14 -0
- package/src/skf-verify-stack/references/health-check.md +1 -1
- package/src/skf-verify-stack/references/init.md +20 -25
- package/src/skf-verify-stack/references/integration-verification-rules.md +10 -19
- package/src/skf-verify-stack/references/integrations.md +44 -49
- package/src/skf-verify-stack/references/report.md +28 -34
- package/src/skf-verify-stack/references/requirements.md +3 -3
- package/src/skf-verify-stack/references/synthesize.md +36 -39
- package/src/skf-verify-stack/scripts/skf-coverage-tally.py +196 -0
- package/src/skf-verify-stack/scripts/skf-report-delta.py +256 -0
- package/src/skf-verify-stack/scripts/skf-verdict-rollup.py +258 -0
- package/src/skf-rename-skill/references/rebuild-context.md +0 -110
|
@@ -11,6 +11,9 @@ feasibilitySchemaProbeOrder:
|
|
|
11
11
|
atomicWriteProbeOrder:
|
|
12
12
|
- '{project-root}/_bmad/skf/shared/scripts/skf-atomic-write.py'
|
|
13
13
|
- '{project-root}/src/shared/scripts/skf-atomic-write.py'
|
|
14
|
+
validateFeasibilityReportProbeOrder:
|
|
15
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-validate-feasibility-report.py'
|
|
16
|
+
- '{project-root}/src/shared/scripts/skf-validate-feasibility-report.py'
|
|
14
17
|
nextStepFile: 'health-check.md'
|
|
15
18
|
---
|
|
16
19
|
|
|
@@ -25,7 +28,7 @@ Present the complete feasibility report to the user. Display the overall verdict
|
|
|
25
28
|
## Rules
|
|
26
29
|
|
|
27
30
|
- Focus only on presenting the completed report — no new analysis or changes to verdicts
|
|
28
|
-
- Chains to the local health-check step via `{nextStepFile}` after completion — the user-facing report is
|
|
31
|
+
- Chains to the local health-check step via `{nextStepFile}` after completion — the user-facing report is not the terminal step
|
|
29
32
|
|
|
30
33
|
## MANDATORY SEQUENCE
|
|
31
34
|
|
|
@@ -35,11 +38,24 @@ Read the entire `{outputFile}` to have all data available for presentation.
|
|
|
35
38
|
|
|
36
39
|
**Resolve `{feasibilitySchemaRef}`** from `{feasibilitySchemaProbeOrder}`; first existing path wins (installed SKF module path first, dev-checkout `src/` fallback).
|
|
37
40
|
|
|
38
|
-
|
|
41
|
+
**Validate report structure and schema version (deterministic gate).** Resolve `{validateFeasibilityReportHelper}` from `{validateFeasibilityReportProbeOrder}`; first existing path wins (installed SKF module path first, dev-checkout `src/` fallback). Then run:
|
|
39
42
|
|
|
40
|
-
|
|
43
|
+
```bash
|
|
44
|
+
python3 {validateFeasibilityReportHelper} {outputFile}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The script (see `--help`) deterministically confirms the five required body sections — `## Executive Summary`, `## Coverage Analysis`, `## Integration Verdicts`, `## Recommendations`, `## Evidence Sources` — are all present and in canonical order per `{feasibilitySchemaRef}`, **and** that frontmatter `schemaVersion == "1.0"`. It emits a JSON verdict on stdout (`headingsOk`, `missingHeadings`, `orderViolations`, `schemaVersionOk`, `schemaVersionFound`, `violation`) and exits `0` when valid, `1` on a schema violation, `2` on an IO/parse error.
|
|
48
|
+
|
|
49
|
+
**Graceful degradation:** if no `{validateFeasibilityReportProbeOrder}` candidate exists (e.g. partial installation, or `python3` unavailable), perform the equivalent structural check inline — confirm the five headings above are all present and in that exact order, and that frontmatter `schemaVersion` is `"1.0"` — and apply the same halt semantics below. The report is already on disk from steps 1-5, so a missing validator degrades to the inline check rather than blocking presentation.
|
|
50
|
+
|
|
51
|
+
On any non-zero exit (or an inline check that fails), HALT (exit code 5, `halt_reason: "schema-violation"`) — do not display partial results. Report the specific violation from the JSON:
|
|
41
52
|
|
|
42
|
-
|
|
53
|
+
- a missing or out-of-order section (`missingHeadings` / `orderViolations`); or
|
|
54
|
+
- a schemaVersion mismatch — "Report frontmatter schemaVersion `{schemaVersionFound}` does not match producer schema `1.0` — report was corrupted between steps. Re-run [VS]." (Producer never proceeds past a schema mismatch.)
|
|
55
|
+
|
|
56
|
+
In headless, emit the error envelope per SKILL.md "Result Contract (Headless)" with `report_path: "{outputFile}"`, `overall_verdict: null`.
|
|
57
|
+
|
|
58
|
+
With the deterministic gate passed (sections present + in order, `schemaVersion == "1.0"`), **extract metrics from `{outputFile}` frontmatter** (per shared schema in `{feasibilitySchemaRef}`): `skillsAnalyzed`, `coveragePercentage`, `pairsVerified` (as `verified_count`), `pairsPlausible` (as `plausible_count`), `pairsRisky` (as `risky_count`), `pairsBlocked` (as `blocked_count`), `requirementsFulfilled` (as `fulfilled_count`), `requirementsPartial` (as `partial_count`), `requirementsNotAddressed` (as `not_addressed_count`), `requirementsPass`, `overallVerdict`, and `recommendationCount`. Use these mapped display names in the summary table and next steps below.
|
|
43
59
|
|
|
44
60
|
### 2. Present Summary
|
|
45
61
|
|
|
@@ -76,37 +92,17 @@ Walk through the highlights — coverage gaps, risky/blocked integrations, and p
|
|
|
76
92
|
|
|
77
93
|
### 4. Present Next Steps
|
|
78
94
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
**IF `overallVerdict == "FEASIBLE"`:**
|
|
82
|
-
"**Your stack is verified.** All technologies are covered, integrations are compatible, and requirements are all fulfilled (or requirements pass was skipped).
|
|
83
|
-
|
|
84
|
-
**Recommended next steps:**
|
|
85
|
-
1. **[RA] Refine Architecture** — Produce an implementation-ready architecture document enriched with skill-backed API details
|
|
86
|
-
2. **[SS] Create Stack Skill** — compose your individual skills into a unified stack skill, providing the refined architecture doc when prompted
|
|
87
|
-
3. **[TS] Test Skill** → **[EX] Export Skill** — Verify completeness and package for distribution"
|
|
88
|
-
|
|
89
|
-
**IF `overallVerdict == "CONDITIONALLY_FEASIBLE"`:**
|
|
90
|
-
"**Your stack is conditionally feasible.** There are {recommendationCount} items to address before proceeding.
|
|
91
|
-
|
|
92
|
-
**Required actions:**
|
|
93
|
-
{List the specific recommendations from Step 05 synthesis}
|
|
94
|
-
|
|
95
|
-
**After addressing these items:** Re-run **[VS] Verify Stack** to confirm resolution, then proceed to **[RA]**."
|
|
96
|
-
|
|
97
|
-
**IF `overallVerdict == "NOT_FEASIBLE"`:**
|
|
98
|
-
"**Critical blockers must be resolved.** The stack cannot support the architecture as described.
|
|
99
|
-
|
|
100
|
-
**Critical actions:**
|
|
101
|
-
{List the blocked integration recommendations and missing skill actions from Step 05}
|
|
95
|
+
Step 05 already wrote a **Suggested next workflow** block (keyed on the case-sensitive `overallVerdict` token) at the end of `## Recommendations`. Surface that block from the §1 load rather than re-deriving it, prefixed with the one-line verdict-specific framing:
|
|
102
96
|
|
|
103
|
-
**
|
|
97
|
+
- **`FEASIBLE`:** "**Your stack is verified.** All technologies are covered, integrations are compatible, and requirements are all fulfilled (or requirements pass was skipped)."
|
|
98
|
+
- **`CONDITIONALLY_FEASIBLE`:** "**Your stack is conditionally feasible.** There are {recommendationCount} items to address before proceeding." — then list the specific recommendations from the report's `## Recommendations` section.
|
|
99
|
+
- **`NOT_FEASIBLE`:** "**Critical blockers must be resolved.** The stack cannot support the architecture as described." — then list the blocked-integration and missing-skill recommendations from the report's `## Recommendations` section.
|
|
104
100
|
|
|
105
101
|
### 4b. Result Contract
|
|
106
102
|
|
|
107
|
-
**Resolve `{atomicWriteHelper}`** from `{atomicWriteProbeOrder}`; first existing path wins.
|
|
103
|
+
**Resolve `{atomicWriteHelper}`** from `{atomicWriteProbeOrder}`; first existing path wins. If no candidate exists: HALT (exit code 3, `halt_reason: "resolution-failure"`); in headless, emit the error envelope.
|
|
108
104
|
|
|
109
|
-
Write the result contract per `shared/references/output-contract-schema.md` (this path resolves relative to the SKF module root — `{project-root}/_bmad/skf/` when installed, `{project-root}/src/` during development —
|
|
105
|
+
Write the result contract per `shared/references/output-contract-schema.md` (this path resolves relative to the SKF module root — `{project-root}/_bmad/skf/` when installed, `{project-root}/src/` during development — not relative to this step file): the per-run record at `{forge_data_folder}/verify-stack-result-{YYYYMMDD-HHmmss}.json` (UTC timestamp, resolution to seconds) and a copy at `{forge_data_folder}/verify-stack-result-latest.json` (stable path for pipeline consumers — copy, not symlink). Include the feasibility report path (both `{outputFile}` and `{outputFileLatest}`) in `outputs`; include `overallVerdict` (`FEASIBLE` / `CONDITIONALLY_FEASIBLE` / `NOT_FEASIBLE`), `coveragePercentage`, and `recommendationCount` in `summary` — use the case-sensitive schema tokens.
|
|
110
106
|
|
|
111
107
|
Write both JSON files through `python3 {atomicWriteHelper} write --target ...` to avoid partial-write corruption. On any non-zero exit: HALT (exit code 4, `halt_reason: "write-failed"`) and emit the error envelope.
|
|
112
108
|
|
|
@@ -118,7 +114,7 @@ SKF_VERIFY_STACK_RESULT_JSON: {"status":"success","report_path":"{outputFile}","
|
|
|
118
114
|
|
|
119
115
|
`{overallVerdict}` uses the schema tokens (`FEASIBLE` / `CONDITIONALLY_FEASIBLE` / `NOT_FEASIBLE`).
|
|
120
116
|
|
|
121
|
-
**Result-contract ordering:** The result contract is written exactly once on the first entry to step 6 (the `[X] Exit verification` path). Re-walks of the report via the `[R] Review full report` menu option do
|
|
117
|
+
**Result-contract ordering:** The result contract is written exactly once on the first entry to step 6 (the `[X] Exit verification` path). Re-walks of the report via the `[R] Review full report` menu option do not regenerate it — the contract captures the run, not the presentation loop. If the user selects `[R]` repeatedly before exiting, the single on-disk contract written on first entry remains authoritative.
|
|
122
118
|
|
|
123
119
|
### 5. Present Menu
|
|
124
120
|
|
|
@@ -133,13 +129,11 @@ Re-run **[VS] Verify Stack** anytime after making changes to your skills or arch
|
|
|
133
129
|
|
|
134
130
|
**Verification workflow complete.**"
|
|
135
131
|
|
|
136
|
-
Then load, read the full file, and execute `{nextStepFile}` — the health-check step is the true terminal step of this workflow.
|
|
132
|
+
If `{workflow.on_complete}` is non-empty, execute it now (e.g. route the verdict onward or trigger a downstream step); in headless, log the action. Then load, read the full file, and execute `{nextStepFile}` — the health-check step is the true terminal step of this workflow.
|
|
137
133
|
|
|
138
134
|
#### EXECUTION RULES:
|
|
139
135
|
|
|
140
|
-
- ALWAYS halt and wait for user input after presenting the menu
|
|
141
136
|
- **GATE [default: X]** — If `{headless_mode}`: auto-proceed with [X] Exit verification, log: "headless: auto-exit past report menu"
|
|
142
137
|
- R may be selected multiple times — always walk through the full report
|
|
143
|
-
- X triggers the health check, which is the true workflow exit
|
|
144
138
|
|
|
145
139
|
|
|
@@ -28,7 +28,7 @@ If a PRD or vision document was provided in Step 01, verify that the combined ca
|
|
|
28
28
|
|
|
29
29
|
### 1. Check PRD Availability
|
|
30
30
|
|
|
31
|
-
**Resolve `{atomicWriteHelper}`** from `{atomicWriteProbeOrder}`; first existing path wins.
|
|
31
|
+
**Resolve `{atomicWriteHelper}`** from `{atomicWriteProbeOrder}`; first existing path wins. If no candidate exists: HALT (exit code 3, `halt_reason: "resolution-failure"`); in headless, emit the error envelope.
|
|
32
32
|
|
|
33
33
|
**Read `prdAvailable` from `{outputFile}` frontmatter (set in Step 01). If `prdAvailable` is false (no PRD/vision document was provided):**
|
|
34
34
|
|
|
@@ -42,7 +42,7 @@ To include this pass, re-run **[VS]** with a PRD or vision document path.
|
|
|
42
42
|
|
|
43
43
|
Update `{outputFile}` frontmatter: append `'requirements'` to `stepsCompleted`; set `requirementsPass: "skipped"`. Pipe the updated content through `python3 {atomicWriteHelper} write --target {outputFile}` and again with `--target {outputFileLatest}`.
|
|
44
44
|
|
|
45
|
-
Load, read the full file and then execute `{nextStepFile}`.
|
|
45
|
+
Load, read the full file and then execute `{nextStepFile}`. The no-PRD path ends here — sections 2-6 are the PRD-present branch and do not run.
|
|
46
46
|
|
|
47
47
|
**If PRD/vision document was provided:** Continue to section 2.
|
|
48
48
|
|
|
@@ -78,7 +78,7 @@ For each requirement, evaluate whether the combined capabilities of the generate
|
|
|
78
78
|
- **Partially Fulfilled** — skills provide related capability but gaps remain (specify what is covered and what is not)
|
|
79
79
|
- **Not Addressed** — no skill in the stack provides capability relevant to this requirement
|
|
80
80
|
|
|
81
|
-
**Each verdict
|
|
81
|
+
**Each verdict includes:**
|
|
82
82
|
- Which skills contribute (if any)
|
|
83
83
|
- Specific exports or capabilities from those skills that are relevant
|
|
84
84
|
- For Partially Fulfilled: what gap remains
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
nextStepFile: 'report.md'
|
|
3
|
+
verdictRollupScript: 'scripts/skf-verdict-rollup.py'
|
|
4
|
+
reportDeltaScript: 'scripts/skf-report-delta.py'
|
|
3
5
|
feasibilitySchemaProbeOrder:
|
|
4
6
|
- '{project-root}/_bmad/skf/shared/references/feasibility-report-schema.md'
|
|
5
7
|
- '{project-root}/src/shared/references/feasibility-report-schema.md'
|
|
@@ -27,33 +29,29 @@ Calculate the overall feasibility verdict based on all three analysis passes, ge
|
|
|
27
29
|
|
|
28
30
|
### 1. Calculate Overall Verdict
|
|
29
31
|
|
|
30
|
-
**
|
|
32
|
+
**The verdict token is deterministic — do not walk the ladder in prose.** All three passes have already persisted their counts (`coveragePercentage`, `pairsBlocked`/`pairsRisky`/`pairsPlausible`/`pairsVerified`, `requirementsPass` + `requirementsNotAddressed`/`requirementsPartial` in `{outputFile}` frontmatter; the Missing technology count in the Coverage Analysis table). Rolling those already-decided counts up into one token has a single correct answer per input, so delegate it. Count Missing rows directly from the Coverage table — half-up rounding can leave `coveragePercentage == 100` with one technology still Missing — assemble the counts, and run:
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
```bash
|
|
35
|
+
echo '<counts JSON>' | uv run {verdictRollupScript} --stdin
|
|
36
|
+
```
|
|
33
37
|
|
|
34
|
-
|
|
38
|
+
Input keys: `coveragePercentage`, `missingCount`, `pairsBlocked`, `pairsRisky`, `pairsPlausible`, `pairsVerified`; plus, only when the requirements pass ran (`requirementsPass == "completed"`), `requirementsEvaluated: true` with `requirementsNotAddressed`/`requirementsPartial`; plus `continuedPastZeroState: true` if the user pressed `[C] Continue anyway` past a step-2 zero-state gate (all-Replaced or 0%-coverage). The script (run `uv run {verdictRollupScript} --help` for the contract) returns `overallVerdict` (one of `FEASIBLE`/`CONDITIONALLY_FEASIBLE`/`NOT_FEASIBLE`), `matchedConditions` (the condition codes that fired), and `zeroPairsGuardFired`. If `uv` is unavailable (e.g. claude.ai web), apply the ladder below inline.
|
|
35
39
|
|
|
36
|
-
**
|
|
37
|
-
-
|
|
38
|
-
-
|
|
40
|
+
**The ladder it applies (documented so the rationale can cite it — the script is the executor; evaluate top-to-bottom, first match wins):**
|
|
41
|
+
- `coveragePercentage == 0` → `NOT_FEASIBLE` (short-circuit: no live coverage, analysis vacuous).
|
|
42
|
+
- Any Blocked integration → `NOT_FEASIBLE` (fundamental architectural incompatibility).
|
|
43
|
+
- Any Missing technology, any Risky integration, or — when requirements ran — any Not Addressed / Partially Fulfilled requirement → `CONDITIONALLY_FEASIBLE`.
|
|
44
|
+
- Otherwise → `FEASIBLE`, but any pair capped at `Plausible` (including Check-4-missing caps) downgrades to `CONDITIONALLY_FEASIBLE`.
|
|
45
|
+
- Post-verdict guard: when all four integration counts are 0 and the user continued past a step-2 zero-state gate, `zeroPairsGuardFired` is true — a `FEASIBLE` verdict is overridden to `CONDITIONALLY_FEASIBLE`.
|
|
39
46
|
|
|
40
|
-
**CONDITIONALLY_FEASIBLE
|
|
41
|
-
If ANY of the following apply, the verdict is `CONDITIONALLY_FEASIBLE`. Include ALL matching conditions in the rationale:
|
|
42
|
-
- Any technology is **Missing** from coverage (no skill exists). Technologies marked **Replaced** in Step 02 are intentionally being removed and do NOT count as Missing — they never trigger CONDITIONALLY_FEASIBLE or a [CS]/[QS] recommendation.
|
|
43
|
-
- Any integration is **Risky** (but none Blocked)
|
|
44
|
-
- Requirements have any **Not Addressed** items
|
|
45
|
-
- Requirements have any **Partially Fulfilled** items
|
|
46
|
-
- Rationale: the stack can work but has gaps, risks, or unverified assumptions that must be addressed
|
|
47
|
+
Technologies marked **Replaced** in Step 02 are intentionally being removed and are already excluded from `missingCount` and the coverage denominator — they never trigger `CONDITIONALLY_FEASIBLE` or a [CS]/[QS] recommendation.
|
|
47
48
|
|
|
48
|
-
**
|
|
49
|
-
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
If zero integration pairs were extracted (all four integration counts are 0), fire the guard whenever the user continued past the step 2 `[C] Continue anyway` prompt (i.e., any `[C]` continuation from 0% coverage or any other zero-state) — regardless of how many technologies the architecture document references:
|
|
55
|
-
- If the verdict was `FEASIBLE`, override to `CONDITIONALLY_FEASIBLE`
|
|
56
|
-
- Regardless of verdict, append this note to the rationale: "No integration claims were found in the architecture document prose. Manual review recommended to confirm that technology relationships are not documented exclusively in diagrams or implied without explicit co-mention."
|
|
49
|
+
**Write the rationale** from `matchedConditions`, naming the specific findings behind each code:
|
|
50
|
+
- `zero-coverage` → "no coverage — analysis vacuous: zero generated skills match the architecture's referenced technologies, so integration and requirements verdicts cannot produce meaningful evidence." Then proceed directly to section 2 to generate recommendations for the Missing and/or Replaced technologies surfaced by Step 02.
|
|
51
|
+
- `blocked-integration` → a blocked integration is a fundamental architectural incompatibility; name each Blocked pair and note any co-occurring `missing-coverage`/`risky-integration` codes so the user sees the full set of problems.
|
|
52
|
+
- `missing-coverage` / `risky-integration` / `requirements-not-addressed` / `requirements-partial` / `plausible-cap` → the stack can work but has gaps, risks, or unverified assumptions that must be addressed; name the specific items behind each code.
|
|
53
|
+
- No codes (`FEASIBLE`) → {IF requirements pass completed:} the stack can support the architecture as described — all requirements fully fulfilled, every integration pair has a literal cross-reference. {IF requirements pass was skipped:} the stack can support the architecture as described — requirements were not evaluated (no PRD provided).
|
|
54
|
+
- `zero-integration-pairs` (present whenever the guard fired, regardless of verdict) → append: "No integration claims were found in the architecture document prose. Manual review recommended to confirm that technology relationships are not documented exclusively in diagrams or implied without explicit co-mention."
|
|
57
55
|
|
|
58
56
|
Store the verdict for use in the report.
|
|
59
57
|
|
|
@@ -66,7 +64,7 @@ For each non-verified finding across all passes, generate an actionable next ste
|
|
|
66
64
|
|
|
67
65
|
**Replaced / being-removed technology (from Step 02):**
|
|
68
66
|
- "`{library_name}` is marked for removal/replacement in the architecture document — no skill is needed. Remove it from the architecture document (or, if it is in fact staying, correct the document to drop the removal marker), then re-run **[VS]**."
|
|
69
|
-
- Do
|
|
67
|
+
- Do not emit a [CS]/[QS] recommendation for a Replaced technology — forging a skill for a technology that is being deleted is exactly the misfire this category prevents.
|
|
70
68
|
|
|
71
69
|
**Risky integration (from Step 03):**
|
|
72
70
|
- If protocol mismatch → "Consider adding a bridge layer between `{lib_a}` and `{lib_b}` (e.g., HTTP adapter, message queue). Document the bridge in the architecture."
|
|
@@ -89,18 +87,20 @@ For each non-verified finding across all passes, generate an actionable next ste
|
|
|
89
87
|
|
|
90
88
|
### 3. Check for Previous Report
|
|
91
89
|
|
|
92
|
-
Read `previousReport` from `{outputFile}` frontmatter (set in Step 01).
|
|
90
|
+
Read `previousReport` from `{outputFile}` frontmatter (set in Step 01). Each run writes a new timestamped `feasibility-report-{projectSlug}-{timestamp}.md`, so prior reports persist on disk automatically — Step 01 auto-discovers the most recent one for delta comparison when no path is supplied. `previousReport` holds the resolved path, or is empty when no prior report exists or the user skipped the comparison.
|
|
93
91
|
|
|
94
|
-
**Note:**
|
|
92
|
+
**Note:** A manual backup is only needed to compare against a *specific older* snapshot rather than the most recent prior run; provide that backup path when prompted in Step 01.
|
|
95
93
|
|
|
96
94
|
**If a previous report is found:**
|
|
97
|
-
-
|
|
98
|
-
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
95
|
+
- Extract from both reports (current run + the previous report's tables/inventory block): the coverage findings (`{technology, verdict}`), the integration findings (`{libA, libB, verdict}`), and each skill's `confidence_tier`. Reading the tables is judgment; classifying the difference is not — so hand the two extracted finding sets to the delta helper rather than diffing in prose (matching pair keys and applying the verdict ranking by hand drifts between runs).
|
|
96
|
+
- Serialize as `{"previous": {"coverage": […], "integration": […]}, "current": {…}, "previousTiers": {skill: tier}, "currentTiers": {…}}` and run:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
echo '<delta JSON>' | uv run {reportDeltaScript} --stdin
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The script (run `uv run {reportDeltaScript} --help` for the contract and rankings) returns `improved`/`regressed`/`unchanged`/`new`/`dropped`/`replaced` label lists with counts, plus `tierDowngrades` (each `{skill, from, to}`) — a tier drop (Tier 1 → Tier 2, or T1 → T1-low) counts as a regression. If `uv` is unavailable, apply the ranking from `--help` inline (coverage Missing<Covered; integration Blocked<Risky<Plausible<Verified; tier T2<T1-low<T1; Replaced findings bucketed, not scored).
|
|
103
|
+
- Render the delta section from those results. For each tier downgrade, flag: "skill `{skill}` regressed from `{from}` to `{to}` — re-extract with [CS] at the prior tier level".
|
|
104
104
|
|
|
105
105
|
**If no previous report found:**
|
|
106
106
|
- Note: "First verification run — no delta available."
|
|
@@ -129,7 +129,7 @@ Assemble the following for the report:
|
|
|
129
129
|
|
|
130
130
|
### 5. Append to Report
|
|
131
131
|
|
|
132
|
-
**Resolve `{atomicWriteHelper}`** from `{atomicWriteProbeOrder}`; first existing path wins.
|
|
132
|
+
**Resolve `{atomicWriteHelper}`** from `{atomicWriteProbeOrder}`; first existing path wins. If no candidate exists: HALT (exit code 3, `halt_reason: "resolution-failure"`); in headless, emit the error envelope.
|
|
133
133
|
|
|
134
134
|
**Resolve `{feasibilitySchemaRef}`** from `{feasibilitySchemaProbeOrder}`; first existing path wins (installed SKF module path first, dev-checkout `src/` fallback).
|
|
135
135
|
|
|
@@ -141,14 +141,11 @@ Write the **Recommendations** and **Evidence Sources** sections to `{outputFile}
|
|
|
141
141
|
- Populate `## Evidence Sources` with per-skill citations (SKILL.md path, `metadata_schema_version`, `confidence_tier`, stack manifest if any) and architecture/PRD doc paths
|
|
142
142
|
- Update frontmatter (shared-schema keys):
|
|
143
143
|
- Append `'synthesize'` to `stepsCompleted`
|
|
144
|
-
- Set `overallVerdict` to one of `FEASIBLE`, `CONDITIONALLY_FEASIBLE`, `NOT_FEASIBLE` (case-sensitive, underscores
|
|
144
|
+
- Set `overallVerdict` to one of `FEASIBLE`, `CONDITIONALLY_FEASIBLE`, `NOT_FEASIBLE` (case-sensitive, underscores not spaces)
|
|
145
145
|
- Set `recommendationCount` to the total number of recommendations
|
|
146
|
-
- If delta was computed (section 3), set `deltaImproved`, `deltaRegressed`, `deltaNew`, `deltaUnchanged`
|
|
146
|
+
- If delta was computed (section 3), set `deltaImproved`, `deltaRegressed`, `deltaNew`, `deltaUnchanged` from the delta helper's `improvedCount` / `regressedCount` / `newCount` / `unchangedCount`
|
|
147
147
|
- Verify that `pairsVerified`, `pairsPlausible`, `pairsRisky`, `pairsBlocked` match the counts from Step 03 (these were set in Step 03). If a discrepancy is found, overwrite the frontmatter counts with the values from Step 03 — the report file is the system of record
|
|
148
|
-
- **Overall verdict enforcement (schema producer obligation):**
|
|
149
|
-
- If any pair has Check 4 missing/weak AND was capped at `Plausible`, that alone does NOT force `NOT_FEASIBLE`, but `FEASIBLE` requires zero such pairs
|
|
150
|
-
- `FEASIBLE` requires 100% coverage AND zero Blocked pairs AND zero Check-4-missing pairs — otherwise downgrade to `CONDITIONALLY_FEASIBLE`
|
|
151
|
-
- `coveragePercentage == 0` forces `NOT_FEASIBLE` (per section 1 short-circuit)
|
|
148
|
+
- **Overall verdict enforcement (schema producer obligation):** write the `overallVerdict` the §1 rollup script returned verbatim — do not re-derive the ladder here. §1 (via `{verdictRollupScript}`) is its single source of truth (the 100%-coverage + zero-Blocked + zero-Check-4-missing bar for `FEASIBLE`, and the `coveragePercentage == 0` → `NOT_FEASIBLE` short-circuit included).
|
|
152
149
|
- Pipe the updated full content through `python3 {atomicWriteHelper} write --target {outputFile}` and again with `--target {outputFileLatest}`
|
|
153
150
|
|
|
154
151
|
### 6. Auto-Proceed to Next Step
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# /// script
|
|
3
|
+
# requires-python = ">=3.10"
|
|
4
|
+
# dependencies = []
|
|
5
|
+
# ///
|
|
6
|
+
"""Deterministic technology-coverage tally for skf-verify-stack (coverage.md §6).
|
|
7
|
+
|
|
8
|
+
The coverage verdicts themselves (Covered / Missing / Replaced) are LLM judgment
|
|
9
|
+
— matching an architecture technology to a generated skill involves aliases and
|
|
10
|
+
prose. This script takes that already-decided coverage matrix and does only the
|
|
11
|
+
part with one correct answer: counting each verdict class and turning the counts
|
|
12
|
+
into `coveragePercentage`.
|
|
13
|
+
|
|
14
|
+
Two gotchas make the in-prose version drift between runs, so they live here
|
|
15
|
+
instead:
|
|
16
|
+
|
|
17
|
+
* The denominator excludes Replaced. `live_count = Covered + Missing`;
|
|
18
|
+
technologies flagged Replaced (intentionally being removed) are not a gap and
|
|
19
|
+
must not dilute the percentage.
|
|
20
|
+
* Rounding is pinned to half-up to the nearest integer, so the same matrix
|
|
21
|
+
always yields the same `coveragePercentage` (the shared schema declares
|
|
22
|
+
`coveragePercentage: <0..100 integer>` but not the rounding rule).
|
|
23
|
+
|
|
24
|
+
CLI usage:
|
|
25
|
+
uv run skf-coverage-tally.py '<JSON>' # JSON literal positional
|
|
26
|
+
uv run skf-coverage-tally.py --json-input '<JSON>' # explicit flag form
|
|
27
|
+
cat input.json | uv run skf-coverage-tally.py --stdin # piped input
|
|
28
|
+
|
|
29
|
+
Input schema (one object):
|
|
30
|
+
{
|
|
31
|
+
"rows": [
|
|
32
|
+
{"technology": "react", "verdict": "Covered"},
|
|
33
|
+
{"technology": "postgres","verdict": "Missing"},
|
|
34
|
+
{"technology": "old-orm", "verdict": "Replaced"}
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
Output (stdout, one object):
|
|
39
|
+
{
|
|
40
|
+
"covered_count": <int>,
|
|
41
|
+
"missing_count": <int>,
|
|
42
|
+
"replaced_count": <int>,
|
|
43
|
+
"live_count": <int>, # Covered + Missing (denominator)
|
|
44
|
+
"total_referenced": <int>, # all rows after dedup
|
|
45
|
+
"coverage_percentage": <int> # round-half-up(covered/live*100); 0 when live==0
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
Exit codes:
|
|
49
|
+
0 — tally emitted successfully
|
|
50
|
+
1 — no input / input could not be parsed as JSON
|
|
51
|
+
2 — input parsed but schema/semantics invalid (error object emitted as JSON)
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
from __future__ import annotations
|
|
55
|
+
|
|
56
|
+
import argparse
|
|
57
|
+
import json
|
|
58
|
+
import math
|
|
59
|
+
import sys
|
|
60
|
+
|
|
61
|
+
VALID_VERDICTS = ("Covered", "Missing", "Replaced")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def make_error(message):
|
|
65
|
+
return {"error": message, "code": "INVALID_INPUT"}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _validate(inp):
|
|
69
|
+
if inp is None or not isinstance(inp, dict):
|
|
70
|
+
return "Input must be a JSON object"
|
|
71
|
+
rows = inp.get("rows")
|
|
72
|
+
if not isinstance(rows, list):
|
|
73
|
+
return "Missing or invalid required field: rows (must be a list)"
|
|
74
|
+
seen = set()
|
|
75
|
+
for i, row in enumerate(rows):
|
|
76
|
+
if not isinstance(row, dict):
|
|
77
|
+
return f"rows[{i}] must be an object"
|
|
78
|
+
tech = row.get("technology")
|
|
79
|
+
if not isinstance(tech, str) or not tech.strip():
|
|
80
|
+
return f"rows[{i}] requires a non-empty string `technology`"
|
|
81
|
+
verdict = row.get("verdict")
|
|
82
|
+
if verdict not in VALID_VERDICTS:
|
|
83
|
+
return (
|
|
84
|
+
f"rows[{i}] verdict {verdict!r} is not one of: "
|
|
85
|
+
f"{', '.join(VALID_VERDICTS)}"
|
|
86
|
+
)
|
|
87
|
+
norm = tech.strip().lower()
|
|
88
|
+
if norm in seen:
|
|
89
|
+
return f"duplicate technology {tech!r} — the coverage list must be deduplicated first"
|
|
90
|
+
seen.add(norm)
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def tally(inp):
|
|
95
|
+
"""Pure tally. Counts each verdict class and computes coverage_percentage."""
|
|
96
|
+
err = _validate(inp)
|
|
97
|
+
if err:
|
|
98
|
+
return make_error(err)
|
|
99
|
+
|
|
100
|
+
covered = missing = replaced = 0
|
|
101
|
+
for row in inp["rows"]:
|
|
102
|
+
verdict = row["verdict"]
|
|
103
|
+
if verdict == "Covered":
|
|
104
|
+
covered += 1
|
|
105
|
+
elif verdict == "Missing":
|
|
106
|
+
missing += 1
|
|
107
|
+
else: # Replaced
|
|
108
|
+
replaced += 1
|
|
109
|
+
|
|
110
|
+
live = covered + missing
|
|
111
|
+
percentage = math.floor(covered / live * 100 + 0.5) if live > 0 else 0
|
|
112
|
+
return {
|
|
113
|
+
"covered_count": covered,
|
|
114
|
+
"missing_count": missing,
|
|
115
|
+
"replaced_count": replaced,
|
|
116
|
+
"live_count": live,
|
|
117
|
+
"total_referenced": covered + missing + replaced,
|
|
118
|
+
"coverage_percentage": percentage,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# --- CLI --------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _build_parser():
|
|
126
|
+
parser = argparse.ArgumentParser(
|
|
127
|
+
prog="skf-coverage-tally",
|
|
128
|
+
description=(
|
|
129
|
+
"Deterministic technology-coverage tally (coverage.md §6). Counts "
|
|
130
|
+
"Covered/Missing/Replaced verdicts and computes coveragePercentage "
|
|
131
|
+
"with Replaced excluded from the denominator and half-up rounding."
|
|
132
|
+
),
|
|
133
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
134
|
+
epilog=(
|
|
135
|
+
"Example:\n"
|
|
136
|
+
" uv run skf-coverage-tally.py "
|
|
137
|
+
"'{\"rows\":[{\"technology\":\"react\",\"verdict\":\"Covered\"},"
|
|
138
|
+
"{\"technology\":\"postgres\",\"verdict\":\"Missing\"}]}'"
|
|
139
|
+
),
|
|
140
|
+
)
|
|
141
|
+
src = parser.add_mutually_exclusive_group()
|
|
142
|
+
src.add_argument(
|
|
143
|
+
"json_input",
|
|
144
|
+
nargs="?",
|
|
145
|
+
help="JSON object as a positional argument (single-quote it on the shell).",
|
|
146
|
+
)
|
|
147
|
+
src.add_argument(
|
|
148
|
+
"--json-input",
|
|
149
|
+
dest="json_input_flag",
|
|
150
|
+
help="JSON object passed via flag (overrides positional).",
|
|
151
|
+
)
|
|
152
|
+
src.add_argument(
|
|
153
|
+
"--stdin",
|
|
154
|
+
action="store_true",
|
|
155
|
+
help="Read the JSON object from stdin.",
|
|
156
|
+
)
|
|
157
|
+
return parser
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _resolve_input(args):
|
|
161
|
+
if args.stdin:
|
|
162
|
+
return sys.stdin.read()
|
|
163
|
+
if args.json_input_flag is not None:
|
|
164
|
+
return args.json_input_flag
|
|
165
|
+
if args.json_input is not None:
|
|
166
|
+
return args.json_input
|
|
167
|
+
return ""
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def main(argv=None):
|
|
171
|
+
parser = _build_parser()
|
|
172
|
+
args = parser.parse_args(argv)
|
|
173
|
+
raw = _resolve_input(args)
|
|
174
|
+
if not raw.strip():
|
|
175
|
+
parser.print_usage(file=sys.stderr)
|
|
176
|
+
print(
|
|
177
|
+
"error: no input provided (positional arg, --json-input, or --stdin)",
|
|
178
|
+
file=sys.stderr,
|
|
179
|
+
)
|
|
180
|
+
return 1
|
|
181
|
+
|
|
182
|
+
try:
|
|
183
|
+
data = json.loads(raw)
|
|
184
|
+
except json.JSONDecodeError as exc:
|
|
185
|
+
print(json.dumps(make_error(f"Invalid JSON: {exc.msg}"), indent=2))
|
|
186
|
+
return 1
|
|
187
|
+
|
|
188
|
+
result = tally(data)
|
|
189
|
+
print(json.dumps(result, indent=2))
|
|
190
|
+
if isinstance(result, dict) and result.get("code") == "INVALID_INPUT":
|
|
191
|
+
return 2
|
|
192
|
+
return 0
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
if __name__ == "__main__":
|
|
196
|
+
raise SystemExit(main())
|