bmad-module-skill-forge 1.9.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/README.md +10 -5
- package/docs/_data/pinned.yaml +2 -2
- package/docs/agents.md +11 -2
- package/docs/architecture.md +5 -4
- package/docs/bmad-synergy.md +30 -7
- package/docs/campaign.md +172 -0
- package/docs/examples.md +7 -3
- package/docs/forge-auto.md +90 -0
- package/docs/getting-started.md +9 -0
- package/docs/how-it-works.md +6 -4
- package/docs/index.md +4 -3
- package/docs/skill-model.md +1 -1
- package/docs/troubleshooting.md +17 -1
- package/docs/verifying-a-skill.md +2 -2
- package/docs/why-skf.md +1 -1
- package/docs/workflows.md +64 -24
- package/package.json +2 -2
- package/src/knowledge/skill-lifecycle.md +1 -1
- package/src/module-help.csv +4 -3
- package/src/shared/data/language-corpora.json +32 -0
- package/src/shared/references/pipeline-contracts.md +6 -3
- package/src/shared/scripts/schemas/skill-brief.v1.json +6 -1
- 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-derive-assembly-shape.py +107 -0
- package/src/shared/scripts/skf-detect-docs.py +240 -4
- package/src/shared/scripts/skf-detect-language.py +82 -3
- package/src/shared/scripts/skf-detect-tools.py +105 -2
- package/src/shared/scripts/skf-emit-result-envelope.py +17 -0
- package/src/shared/scripts/skf-enumerate-stack-skills.py +111 -2
- package/src/shared/scripts/skf-extract-public-api.py +53 -0
- 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-language-corpora.py +100 -0
- package/src/shared/scripts/skf-manifest-ops.py +67 -1
- package/src/shared/scripts/skf-merge-doc-urls.py +244 -0
- package/src/shared/scripts/skf-preapply.py +1 -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-shape-detect.py +576 -25
- 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 +41 -6
- 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 +3 -3
- package/src/shared/scripts/skf-verify-no-trace.py +214 -0
- package/src/shared/scripts/skf-verify-provenance-completeness.py +439 -0
- package/src/shared/scripts/skf-write-skill-brief.py +21 -5
- package/src/skf-analyze-source/SKILL.md +17 -32
- package/src/skf-analyze-source/assets/skill-brief-schema.md +1 -1
- 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 +211 -197
- package/src/skf-analyze-source/references/step-shape-detect.md +20 -13
- 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/assets/skill-brief-schema.md +1 -1
- package/src/skf-brief-skill/customize.toml +14 -0
- package/src/skf-brief-skill/references/analyze-target.md +13 -18
- package/src/skf-brief-skill/references/confirm-brief.md +2 -15
- package/src/skf-brief-skill/references/draft-checkpoint.md +8 -8
- package/src/skf-brief-skill/references/gather-intent.md +27 -24
- 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 +6 -15
- package/src/skf-brief-skill/references/step-auto-brief.md +22 -9
- package/src/skf-brief-skill/references/step-auto-validate.md +3 -7
- package/src/skf-brief-skill/references/version-resolution.md +1 -1
- package/src/skf-brief-skill/references/write-brief.md +19 -13
- package/src/skf-campaign/SKILL.md +121 -0
- package/src/skf-campaign/assets/campaign-state-schema.json +191 -0
- package/src/skf-campaign/customize.toml +75 -0
- package/src/skf-campaign/manifest.yaml +15 -0
- package/src/skf-campaign/references/campaign-contracts.md +67 -0
- package/src/skf-campaign/references/campaign-directive-spec.md +32 -0
- package/src/skf-campaign/references/health-check.md +35 -0
- package/src/skf-campaign/references/step-01-setup.md +121 -0
- package/src/skf-campaign/references/step-02-strategy.md +102 -0
- package/src/skf-campaign/references/step-03-pins.md +56 -0
- package/src/skf-campaign/references/step-04-provenance.md +63 -0
- package/src/skf-campaign/references/step-05-skill-loop.md +103 -0
- package/src/skf-campaign/references/step-06-batch.md +88 -0
- package/src/skf-campaign/references/step-07-capstone.md +63 -0
- package/src/skf-campaign/references/step-08-verify.md +75 -0
- package/src/skf-campaign/references/step-09-refine.md +83 -0
- package/src/skf-campaign/references/step-10-export.md +106 -0
- package/src/skf-campaign/references/step-11-maintenance.md +84 -0
- package/src/skf-campaign/references/step-resume.md +115 -0
- package/src/skf-campaign/scripts/.gitkeep +0 -0
- package/src/skf-campaign/scripts/campaign-deps.py +244 -0
- package/src/skf-campaign/scripts/campaign-parse-manifest.py +119 -0
- package/src/skf-campaign/scripts/campaign-provenance.py +247 -0
- package/src/skf-campaign/scripts/campaign-render-batch.py +199 -0
- package/src/skf-campaign/scripts/campaign-render-kickoff.py +158 -0
- package/src/skf-campaign/scripts/campaign-report.py +249 -0
- package/src/skf-campaign/scripts/campaign-status.py +162 -0
- package/src/skf-campaign/scripts/campaign-validate-pins.py +174 -0
- package/src/skf-campaign/scripts/campaign-validate-state.py +207 -0
- package/src/skf-campaign/templates/campaign-brief-template.yaml +34 -0
- package/src/skf-campaign/templates/campaign-report-template.md +54 -0
- package/src/skf-campaign/templates/kickoff-template.md +48 -0
- package/src/skf-create-skill/SKILL.md +15 -4
- package/src/skf-create-skill/assets/compile-assembly-rules.md +35 -34
- 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 +37 -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 +20 -27
- package/src/skf-create-skill/references/extraction-patterns-tracing.md +1 -1
- package/src/skf-create-skill/references/extraction-patterns.md +10 -8
- package/src/skf-create-skill/references/generate-artifacts.md +20 -25
- package/src/skf-create-skill/references/health-check.md +2 -2
- package/src/skf-create-skill/references/load-brief.md +16 -18
- package/src/skf-create-skill/references/report.md +27 -14
- package/src/skf-create-skill/references/source-resolution-protocols.md +4 -4
- package/src/skf-create-skill/references/step-auto-shard.md +52 -25
- package/src/skf-create-skill/references/step-doc-rot.md +33 -15
- package/src/skf-create-skill/references/step-doc-sources.md +13 -4
- package/src/skf-create-skill/references/sub/ccc-discover.md +2 -13
- package/src/skf-create-skill/references/sub/fetch-docs.md +33 -46
- 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 +69 -29
- 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 +67 -36
- 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 +44 -10
- package/src/skf-drop-skill/references/select.md +93 -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 +32 -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 +16 -8
- package/src/skf-refine-architecture/customize.toml +9 -0
- package/src/skf-refine-architecture/references/compile.md +10 -8
- 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 +14 -11
- 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 +112 -82
- 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 +21 -13
- package/src/skf-setup/customize.toml +10 -0
- package/src/skf-setup/references/auto-index.md +11 -10
- package/src/skf-setup/references/ccc-index.md +14 -15
- package/src/skf-setup/references/detect-and-tier.md +10 -8
- package/src/skf-setup/references/report.md +26 -32
- 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 +111 -67
- 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 +39 -21
- 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 +15 -5
- 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 +135 -4
- 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 +11 -6
- package/src/skf-update-skill/references/detect-changes.md +48 -36
- package/src/skf-update-skill/references/health-check.md +5 -11
- package/src/skf-update-skill/references/init.md +40 -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 +27 -25
- 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/tools/cli/lib/ui.js +3 -2
- package/docs/deepwiki.md +0 -89
- package/src/skf-rename-skill/references/rebuild-context.md +0 -110
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# /// script
|
|
3
|
+
# requires-python = ">=3.10"
|
|
4
|
+
# ///
|
|
5
|
+
"""SKF Validate Feasibility Report — deterministic structure/schema gate.
|
|
6
|
+
|
|
7
|
+
Validates a skf-verify-stack feasibility report (.md) against the two
|
|
8
|
+
producer↔consumer contract invariants the report.md §1 prompt previously
|
|
9
|
+
eyeballed by re-reading the file each run:
|
|
10
|
+
|
|
11
|
+
1. The five required body sections are all present and appear in the
|
|
12
|
+
canonical order defined by the shared feasibility-report schema:
|
|
13
|
+
## Executive Summary
|
|
14
|
+
## Coverage Analysis
|
|
15
|
+
## Integration Verdicts
|
|
16
|
+
## Recommendations
|
|
17
|
+
## Evidence Sources
|
|
18
|
+
2. Frontmatter `schemaVersion` equals the literal producer version "1.0".
|
|
19
|
+
|
|
20
|
+
Both are pure structure/schema checks — identical input always yields
|
|
21
|
+
identical pass/fail — so this belongs in a deterministic script, not an LLM
|
|
22
|
+
re-read. The verdict is emitted as JSON on stdout:
|
|
23
|
+
|
|
24
|
+
{
|
|
25
|
+
"status": "ok" | "error",
|
|
26
|
+
"path": "<report path>",
|
|
27
|
+
"schemaVersionOk": bool,
|
|
28
|
+
"schemaVersionFound": "<value>" | null,
|
|
29
|
+
"headingsOk": bool,
|
|
30
|
+
"missingHeadings": [...],
|
|
31
|
+
"orderViolations": [...],
|
|
32
|
+
"violation": "schema-violation" | null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
Frontmatter is parsed with the standard library only (no pyyaml dep) using the
|
|
36
|
+
same delimiter handling as skf-validate-output.py: a `---`-delimited block at
|
|
37
|
+
the top of the file, parsed as simple `key: value` scalars with surrounding
|
|
38
|
+
quotes stripped. This is sufficient for the single `schemaVersion` scalar and
|
|
39
|
+
keeps the script stdlib-only.
|
|
40
|
+
|
|
41
|
+
Exit codes (per script-standards):
|
|
42
|
+
0 valid — all sections present + in order AND schemaVersion == "1.0"
|
|
43
|
+
1 schema-violation — missing/mis-ordered section, or schemaVersion mismatch
|
|
44
|
+
(the JSON `violation` field + detail lists carry why)
|
|
45
|
+
2 IO/parse error — report file could not be read
|
|
46
|
+
|
|
47
|
+
CLI:
|
|
48
|
+
python3 skf-validate-feasibility-report.py <report.md>
|
|
49
|
+
python3 skf-validate-feasibility-report.py <report.md> -o result.json
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
from __future__ import annotations
|
|
53
|
+
|
|
54
|
+
import argparse
|
|
55
|
+
import json
|
|
56
|
+
import re
|
|
57
|
+
import sys
|
|
58
|
+
from pathlib import Path
|
|
59
|
+
|
|
60
|
+
# Canonical body-section order per the shared feasibility-report schema
|
|
61
|
+
# (src/shared/references/feasibility-report-schema.md → "Body section headings").
|
|
62
|
+
# Case-sensitive, exact heading text.
|
|
63
|
+
CANONICAL_HEADINGS = [
|
|
64
|
+
"Executive Summary",
|
|
65
|
+
"Coverage Analysis",
|
|
66
|
+
"Integration Verdicts",
|
|
67
|
+
"Recommendations",
|
|
68
|
+
"Evidence Sources",
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
# Producer schema version the consumer/producer both pin to.
|
|
72
|
+
EXPECTED_SCHEMA_VERSION = "1.0"
|
|
73
|
+
|
|
74
|
+
# A level-2 markdown heading: exactly two '#', then whitespace, then the text.
|
|
75
|
+
# `### Foo` does not match (the 3rd char is '#', not whitespace).
|
|
76
|
+
_H2_RE = re.compile(r"^##\s+(.+?)\s*$")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def split_frontmatter(content):
|
|
80
|
+
"""Return (frontmatter_dict, body_text).
|
|
81
|
+
|
|
82
|
+
Stdlib-only, no YAML dependency — mirrors skf-validate-output.py's delimiter
|
|
83
|
+
handling. The opening delimiter must be a line that is exactly `---`; the
|
|
84
|
+
closing delimiter is the next line that is exactly `---`. The block between
|
|
85
|
+
them is parsed as simple `key: value` scalars with surrounding quotes
|
|
86
|
+
stripped. If no valid frontmatter block is present, returns ({}, content).
|
|
87
|
+
"""
|
|
88
|
+
lines = content.split("\n")
|
|
89
|
+
if not lines or lines[0].rstrip() != "---":
|
|
90
|
+
return {}, content
|
|
91
|
+
|
|
92
|
+
end_idx = None
|
|
93
|
+
for i in range(1, len(lines)):
|
|
94
|
+
if lines[i].rstrip() == "---":
|
|
95
|
+
end_idx = i
|
|
96
|
+
break
|
|
97
|
+
if end_idx is None:
|
|
98
|
+
return {}, content
|
|
99
|
+
|
|
100
|
+
fm = {}
|
|
101
|
+
for line in lines[1:end_idx]:
|
|
102
|
+
if ":" in line:
|
|
103
|
+
key, _, val = line.partition(":")
|
|
104
|
+
fm[key.strip()] = val.strip().strip("'\"")
|
|
105
|
+
|
|
106
|
+
body = "\n".join(lines[end_idx + 1:])
|
|
107
|
+
return fm, body
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def scan_headings(body):
|
|
111
|
+
"""Scan the body for the canonical level-2 sections.
|
|
112
|
+
|
|
113
|
+
Returns (missing, order_violations):
|
|
114
|
+
- missing: canonical headings not found at all (in canonical order).
|
|
115
|
+
- order_violations: human-readable strings describing each inversion,
|
|
116
|
+
detected by walking the canonical order and flagging any present
|
|
117
|
+
heading whose first occurrence is earlier than the previous present
|
|
118
|
+
canonical heading's first occurrence.
|
|
119
|
+
|
|
120
|
+
Deterministic: same body → same lists.
|
|
121
|
+
"""
|
|
122
|
+
lines = body.split("\n")
|
|
123
|
+
first_line = {} # heading text -> 0-based body line index of first occurrence
|
|
124
|
+
for idx, line in enumerate(lines):
|
|
125
|
+
m = _H2_RE.match(line)
|
|
126
|
+
if not m:
|
|
127
|
+
continue
|
|
128
|
+
text = m.group(1).strip()
|
|
129
|
+
if text in CANONICAL_HEADINGS and text not in first_line:
|
|
130
|
+
first_line[text] = idx
|
|
131
|
+
|
|
132
|
+
missing = [h for h in CANONICAL_HEADINGS if h not in first_line]
|
|
133
|
+
|
|
134
|
+
order_violations = []
|
|
135
|
+
prev_name = None
|
|
136
|
+
prev_idx = -1
|
|
137
|
+
for h in CANONICAL_HEADINGS:
|
|
138
|
+
if h not in first_line:
|
|
139
|
+
continue
|
|
140
|
+
cur_idx = first_line[h]
|
|
141
|
+
if prev_name is not None and cur_idx < prev_idx:
|
|
142
|
+
order_violations.append(
|
|
143
|
+
f"'## {h}' appears at body line {cur_idx + 1}, before "
|
|
144
|
+
f"'## {prev_name}' at body line {prev_idx + 1} "
|
|
145
|
+
f"(expected '{prev_name}' to precede '{h}')"
|
|
146
|
+
)
|
|
147
|
+
prev_name = h
|
|
148
|
+
prev_idx = cur_idx
|
|
149
|
+
|
|
150
|
+
return missing, order_violations
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def validate_report(path):
|
|
154
|
+
"""Validate one feasibility report. Returns (result_dict, exit_code)."""
|
|
155
|
+
p = Path(path)
|
|
156
|
+
try:
|
|
157
|
+
content = p.read_text(encoding="utf-8")
|
|
158
|
+
except (OSError, UnicodeDecodeError) as e:
|
|
159
|
+
return (
|
|
160
|
+
{
|
|
161
|
+
"status": "error",
|
|
162
|
+
"path": str(p),
|
|
163
|
+
"error": f"could not read report file: {e}",
|
|
164
|
+
"schemaVersionOk": False,
|
|
165
|
+
"schemaVersionFound": None,
|
|
166
|
+
"headingsOk": False,
|
|
167
|
+
"missingHeadings": [],
|
|
168
|
+
"orderViolations": [],
|
|
169
|
+
"violation": "io-error",
|
|
170
|
+
},
|
|
171
|
+
2,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
fm, body = split_frontmatter(content)
|
|
175
|
+
schema_version = fm.get("schemaVersion") # None if the key is absent
|
|
176
|
+
schema_version_ok = schema_version == EXPECTED_SCHEMA_VERSION
|
|
177
|
+
|
|
178
|
+
missing, order_violations = scan_headings(body)
|
|
179
|
+
headings_ok = not missing and not order_violations
|
|
180
|
+
|
|
181
|
+
ok = schema_version_ok and headings_ok
|
|
182
|
+
result = {
|
|
183
|
+
"status": "ok" if ok else "error",
|
|
184
|
+
"path": str(p),
|
|
185
|
+
"schemaVersionOk": schema_version_ok,
|
|
186
|
+
"schemaVersionFound": schema_version,
|
|
187
|
+
"headingsOk": headings_ok,
|
|
188
|
+
"missingHeadings": missing,
|
|
189
|
+
"orderViolations": order_violations,
|
|
190
|
+
"violation": None if ok else "schema-violation",
|
|
191
|
+
}
|
|
192
|
+
return result, (0 if ok else 1)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def main(argv=None):
|
|
196
|
+
parser = argparse.ArgumentParser(
|
|
197
|
+
description=(
|
|
198
|
+
"Validate a feasibility report's section order and schemaVersion. "
|
|
199
|
+
"Confirms the five required body sections (Executive Summary, "
|
|
200
|
+
"Coverage Analysis, Integration Verdicts, Recommendations, "
|
|
201
|
+
"Evidence Sources) are present and in canonical order, and that "
|
|
202
|
+
"frontmatter schemaVersion == \"1.0\". Exit 0 valid, 1 "
|
|
203
|
+
"schema-violation, 2 IO/parse error."
|
|
204
|
+
)
|
|
205
|
+
)
|
|
206
|
+
parser.add_argument("report", help="path to the feasibility report .md file")
|
|
207
|
+
parser.add_argument(
|
|
208
|
+
"-o",
|
|
209
|
+
"--output",
|
|
210
|
+
help="write the JSON verdict to this file instead of stdout",
|
|
211
|
+
)
|
|
212
|
+
parser.add_argument(
|
|
213
|
+
"--verbose",
|
|
214
|
+
action="store_true",
|
|
215
|
+
help="print the resolved exit code to stderr",
|
|
216
|
+
)
|
|
217
|
+
args = parser.parse_args(argv)
|
|
218
|
+
|
|
219
|
+
result, exit_code = validate_report(args.report)
|
|
220
|
+
text = json.dumps(result, indent=2)
|
|
221
|
+
|
|
222
|
+
if args.output:
|
|
223
|
+
Path(args.output).write_text(text + "\n", encoding="utf-8")
|
|
224
|
+
else:
|
|
225
|
+
print(text)
|
|
226
|
+
|
|
227
|
+
if args.verbose:
|
|
228
|
+
print(f"exit_code={exit_code} violation={result.get('violation')}", file=sys.stderr)
|
|
229
|
+
|
|
230
|
+
return exit_code
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
if __name__ == "__main__":
|
|
234
|
+
sys.exit(main())
|
|
@@ -32,11 +32,11 @@ Input:
|
|
|
32
32
|
exceeds N lines. Opt-in — when omitted, body size is never checked and
|
|
33
33
|
the verdict is unchanged. Callers pass the skill-check `body.max_lines`
|
|
34
34
|
default (500) to pre-catch that hard reject before commit.
|
|
35
|
-
Optional --max-body-tokens N: when set, emit a
|
|
36
|
-
issue if the estimated body token count exceeds N. Token count is
|
|
37
|
-
estimated as ceil(character_count / 4).
|
|
38
|
-
`body.max_tokens`
|
|
39
|
-
|
|
35
|
+
Optional --max-body-tokens N: when set, emit a low-severity (advisory)
|
|
36
|
+
`body` issue if the estimated body token count exceeds N. Token count is
|
|
37
|
+
estimated as ceil(character_count / 4). skill-check treats
|
|
38
|
+
`body.max_tokens` as a non-blocking warning, so this is advisory — the
|
|
39
|
+
hard body pre-check is --max-body-lines.
|
|
40
40
|
|
|
41
41
|
Output:
|
|
42
42
|
JSON object:
|
|
@@ -254,8 +254,9 @@ def validate_frontmatter(
|
|
|
254
254
|
|
|
255
255
|
When `max_body_lines` is set, additionally emit a high-severity `body`
|
|
256
256
|
issue if the body exceeds that many lines. When `max_body_tokens` is
|
|
257
|
-
set, emit a
|
|
258
|
-
exceeds that limit
|
|
257
|
+
set, emit a low-severity (advisory) `body` issue if the estimated token
|
|
258
|
+
count exceeds that limit — skill-check treats body.max_tokens as a
|
|
259
|
+
non-blocking warning. Both are opt-in — the verdict is unchanged when
|
|
259
260
|
they are None. Returns a result dict with status, issues, frontmatter,
|
|
260
261
|
body_lines, body_tokens, and summary.
|
|
261
262
|
"""
|
|
@@ -272,7 +273,11 @@ def validate_frontmatter(
|
|
|
272
273
|
body_tokens = body_token_estimate(content)
|
|
273
274
|
if max_body_tokens is not None and body_tokens is not None and body_tokens > max_body_tokens:
|
|
274
275
|
issues.append({
|
|
275
|
-
|
|
276
|
+
# skill-check treats body.max_tokens as a non-blocking warning, so
|
|
277
|
+
# this is advisory (low), not a hard reject; --max-body-lines is the
|
|
278
|
+
# hard body pre-check. The char/4 estimate also runs high versus
|
|
279
|
+
# skill-check's own whitespace-split count, so keep it soft.
|
|
280
|
+
"severity": "low",
|
|
276
281
|
"field": "body",
|
|
277
282
|
"message": f"body token estimate {body_tokens} exceeds max {max_body_tokens}",
|
|
278
283
|
})
|
|
@@ -390,10 +395,10 @@ def main() -> int:
|
|
|
390
395
|
type=int,
|
|
391
396
|
default=None,
|
|
392
397
|
help=(
|
|
393
|
-
"when set, emit a
|
|
394
|
-
"token count exceeds N (opt-in; omitted = body
|
|
395
|
-
"Token estimate uses ceil(char_count / 4).
|
|
396
|
-
"body.max_tokens
|
|
398
|
+
"when set, emit a low-severity (advisory) 'body' issue if the "
|
|
399
|
+
"estimated body token count exceeds N (opt-in; omitted = body "
|
|
400
|
+
"tokens not checked). Token estimate uses ceil(char_count / 4). "
|
|
401
|
+
"skill-check treats body.max_tokens as a non-blocking warning."
|
|
397
402
|
),
|
|
398
403
|
)
|
|
399
404
|
parser.add_argument(
|