bmad-module-skill-forge 8.1.0 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/docs/_data/pinned.yaml +1 -1
- package/docs/verifying-a-skill.md +2 -2
- package/package.json +1 -1
- package/src/shared/scripts/skf-extract-public-api.py +15 -8
- package/src/skf-test-skill/assets/output-section-formats.md +2 -2
- package/src/skf-test-skill/references/coverage-check-branches.md +2 -2
- package/src/skf-test-skill/references/coverage-check.md +8 -4
- package/src/skf-test-skill/references/inventory-contract.md +11 -7
- package/src/skf-test-skill/references/signature-comparison-rules.md +7 -5
- package/src/skf-test-skill/references/source-access-protocol.md +1 -1
- package/src/skf-test-skill/scripts/gap-ledger.py +44 -14
- package/src/skf-test-skill/scripts/load-coverage-inputs.py +336 -59
- package/src/skf-test-skill/scripts/reconcile-coverage.py +46 -18
- package/src/skf-test-skill/scripts/score-signatures.py +527 -130
- package/src/skf-test-skill/scripts/validate-inventory.py +528 -68
|
@@ -16,8 +16,13 @@ It covers the four branches §2c enumerates:
|
|
|
16
16
|
|
|
17
17
|
* "barrel" (enumerated path) — intersect documented_set against the source
|
|
18
18
|
barrel_set: Documented = |documented_set ∩ barrel_set|,
|
|
19
|
-
Missing = barrel_set − documented_set,
|
|
20
|
-
|
|
19
|
+
Missing = barrel_set − documented_set, Export Coverage = |Documented| /
|
|
20
|
+
|barrel_set| * 100, Stale = documented_set − (barrel_set ∪ `allSet`).
|
|
21
|
+
`allSet` is the whole public surface: the `all` set and every name on
|
|
22
|
+
the surface's export records, since a nested Python entry's names leave
|
|
23
|
+
`all` but keep their records. So a documented name the counted set
|
|
24
|
+
leaves out but the source exports (a Tier B name) is neither counted nor
|
|
25
|
+
stale. Without `allSet`, Stale = documented_set − barrel_set.
|
|
21
26
|
|
|
22
27
|
* "scalar" (§2b's scalar branch, stats.effective_denominator): the
|
|
23
28
|
denominator is effective_denominator, Missing = max(0, denominator −
|
|
@@ -34,10 +39,9 @@ It covers the four branches §2c enumerates:
|
|
|
34
39
|
present, or `verifiedNumerator`; `missing` lists no name.
|
|
35
40
|
Stale: on a surface read from the source (`sourceRead`, its
|
|
36
41
|
`guards.deflation.applicable`, or a counted set), documented_set − (S ∪
|
|
37
|
-
the whole public surface,
|
|
38
|
-
name
|
|
39
|
-
|
|
40
|
-
(empty).
|
|
42
|
+
`allSet`, the whole public surface, as on the barrel branch), so a
|
|
43
|
+
documented name outside S that the source exports is neither counted
|
|
44
|
+
nor stale; else not enumerable (empty).
|
|
41
45
|
|
|
42
46
|
* "stack" (skill_type == "stack", empty source barrel): look each
|
|
43
47
|
composition-surface name (provenance-map cited contracts, ::-excluded, or
|
|
@@ -83,9 +87,12 @@ each sets the input field it names and wins over the same field in the JSON:
|
|
|
83
87
|
--inventory PATH exports: the validated inventory validate-inventory.py
|
|
84
88
|
wrote with --output
|
|
85
89
|
--surface PATH barrelSet: one name set of load-coverage-inputs.py
|
|
86
|
-
surface, chosen by --surface-set (default "all")
|
|
87
|
-
|
|
88
|
-
name
|
|
90
|
+
surface, chosen by --surface-set (default "all");
|
|
91
|
+
allSet, the whole surface both branches find a
|
|
92
|
+
stale name against (its `all` set and every name on
|
|
93
|
+
its `exports` records, a nested entry's included);
|
|
94
|
+
and what the scalar branch reads: surfaceSet (that
|
|
95
|
+
name), sourceRead (its
|
|
89
96
|
`guards.deflation.applicable`) and setCounted (its
|
|
90
97
|
`denominator.setCounted`, when --surface-set names
|
|
91
98
|
its `denominator.surfaceSet`)
|
|
@@ -107,7 +114,7 @@ Input schema (one object):
|
|
|
107
114
|
"barrelSet": ["a", "b", ...], # barrel, scalar: resolved name set
|
|
108
115
|
"perFileResults": [ {"exports_found": ["a", ...]} ], # barrel: union'd if no barrelSet
|
|
109
116
|
"surfaceSet": "tier_a_include", # scalar: barrelSet's name (null or "" is "all")
|
|
110
|
-
"allSet": ["a", "b", "c", ...], # scalar: the whole public surface
|
|
117
|
+
"allSet": ["a", "b", "c", ...], # barrel, scalar: the whole public surface (stale)
|
|
111
118
|
"sourceRead": <bool>, # scalar: the surface was read from the source
|
|
112
119
|
"setCounted": <bool>, # scalar: count barrelSet (a trusted scope set)
|
|
113
120
|
"denominatorValue": <int>, # scalar/stack: resolved denominator
|
|
@@ -297,6 +304,9 @@ def _validate(inp):
|
|
|
297
304
|
if source == "barrel":
|
|
298
305
|
if inp.get("barrelSet") is None and inp.get("perFileResults") is None:
|
|
299
306
|
return "barrel branch requires either `barrelSet` or `perFileResults`"
|
|
307
|
+
all_set = inp.get("allSet")
|
|
308
|
+
if all_set is not None and not isinstance(all_set, list):
|
|
309
|
+
return "barrel branch: `allSet` must be a list of names"
|
|
300
310
|
elif source == "docsOnly":
|
|
301
311
|
if not isinstance(inp.get("exports"), list):
|
|
302
312
|
return "docsOnly branch requires the §1 inventory `exports` (or --inventory)"
|
|
@@ -343,15 +353,28 @@ def _reads_surface(inp):
|
|
|
343
353
|
return inp.get("barrelSet") is not None and (inp.get("sourceRead") is True or inp.get("setCounted") is True)
|
|
344
354
|
|
|
345
355
|
|
|
356
|
+
def _whole(inp, named):
|
|
357
|
+
"""The whole source surface both branches find a stale name against: the counted set `named` and `allSet`
|
|
358
|
+
(from --surface, the `all` set and every name on the surface's export records, so a nested Python entry's
|
|
359
|
+
names count though they leave `all`). The counted set is always part of it.
|
|
360
|
+
|
|
361
|
+
>>> sorted(_whole({"allSet": ["alpha", "tierB", "nested", "", 3]}, {"alpha", "scoped"}))
|
|
362
|
+
['alpha', 'nested', 'scoped', 'tierB']
|
|
363
|
+
>>> sorted(_whole({}, {"alpha"}))
|
|
364
|
+
['alpha']
|
|
365
|
+
"""
|
|
366
|
+
return named | {n for n in inp.get("allSet") or [] if isinstance(n, str) and n}
|
|
367
|
+
|
|
368
|
+
|
|
346
369
|
def _scalar_surface(inp, denominator):
|
|
347
370
|
"""What the scalar branch reads from a surface read from the source: the stale names, the documented names
|
|
348
|
-
(S
|
|
349
|
-
|
|
350
|
-
|
|
371
|
+
the whole surface (`_whole`: S and `allSet`) lacks, and, when it counts its named set S (`setCounted`, a
|
|
372
|
+
non-empty scope set), the documented names S holds, the names of S the skill does not document and the
|
|
373
|
+
set's size, with the warnings. The denominator stays the stored count."""
|
|
351
374
|
documented_set = build_documented_set(inp.get("exports"))
|
|
352
375
|
named = {n for n in inp["barrelSet"] if isinstance(n, str) and n}
|
|
353
376
|
set_name = _surface_set_name(inp)
|
|
354
|
-
whole =
|
|
377
|
+
whole = _whole(inp, named)
|
|
355
378
|
warnings = []
|
|
356
379
|
counted = inp.get("setCounted") is True and set_name != "all"
|
|
357
380
|
if counted and not named:
|
|
@@ -414,7 +437,8 @@ def reconcile(inp, doc_text=None):
|
|
|
414
437
|
)
|
|
415
438
|
documented_names = documented_set & barrel_set
|
|
416
439
|
missing = sorted(barrel_set - documented_set)
|
|
417
|
-
|
|
440
|
+
# A documented name the counted set leaves out but the whole surface holds is neither counted nor stale.
|
|
441
|
+
stale = sorted(documented_set - _whole(inp, barrel_set))
|
|
418
442
|
denominator = len(barrel_set)
|
|
419
443
|
documented = len(documented_names)
|
|
420
444
|
return {
|
|
@@ -799,11 +823,15 @@ def apply_file_inputs(inp, args):
|
|
|
799
823
|
raise InputFileError(f"--surface {args.surface} has no name set `{args.surface_set}`")
|
|
800
824
|
inp["barrelSet"] = names
|
|
801
825
|
# The scalar branch counts the set only when the surface trusts it (`denominator.setCounted`, for the set
|
|
802
|
-
# it picked)
|
|
803
|
-
#
|
|
826
|
+
# it picked). Both branches find a stale name against the whole public surface (`allSet`), not the set
|
|
827
|
+
# they count: the `all` set and every name on an `exports` record, since a nested Python entry's names
|
|
828
|
+
# leave `all` but keep their records (score-signatures.py's `signature_universe` rule). A scoped-out
|
|
829
|
+
# name has no record, so it stays stale.
|
|
804
830
|
inp["surfaceSet"] = args.surface_set
|
|
805
831
|
if isinstance(sets.get("all"), list):
|
|
806
|
-
|
|
832
|
+
records = data.get("exports") if isinstance(data.get("exports"), list) else []
|
|
833
|
+
on_records = [r.get("name") for r in records if isinstance(r, dict)]
|
|
834
|
+
inp["allSet"] = sorted({n for n in sets["all"] + on_records if isinstance(n, str) and n})
|
|
807
835
|
guards = data.get("guards")
|
|
808
836
|
deflation = guards.get("deflation") if isinstance(guards, dict) else None
|
|
809
837
|
inp["sourceRead"] = isinstance(deflation, dict) and deflation.get("applicable") is True
|