bmad-module-skill-forge 5.0.0 → 6.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.
Files changed (91) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/docs/_data/pinned.yaml +1 -1
  3. package/docs/agents.md +1 -1
  4. package/docs/architecture.md +3 -3
  5. package/docs/campaign.md +1 -1
  6. package/docs/troubleshooting.md +9 -3
  7. package/docs/verifying-a-skill.md +9 -9
  8. package/docs/workflows.md +4 -4
  9. package/package.json +1 -1
  10. package/src/knowledge/tool-resolution.md +6 -6
  11. package/src/shared/references/headless-gate-convention.md +4 -1
  12. package/src/shared/references/output-contract-schema.md +3 -3
  13. package/src/shared/references/pipeline-contracts.md +2 -1
  14. package/src/shared/scripts/schemas/skf-test-result-envelope.v1.json +1 -1
  15. package/src/shared/scripts/skf-build-change-manifest.py +332 -54
  16. package/src/shared/scripts/skf-classify-changed-files.py +15 -10
  17. package/src/shared/scripts/skf-emit-result-envelope.py +58 -15
  18. package/src/shared/scripts/skf-extract-public-api.py +33 -13
  19. package/src/shared/scripts/skf-parse-gaps.py +3 -2
  20. package/src/shared/scripts/skf-verify-provenance-completeness.py +108 -11
  21. package/src/skf-analyze-source/SKILL.md +1 -0
  22. package/src/skf-analyze-source/references/generate-briefs.md +1 -1
  23. package/src/skf-analyze-source/references/headless-contract.md +1 -1
  24. package/src/skf-audit-skill/SKILL.md +2 -1
  25. package/src/skf-audit-skill/references/report.md +1 -1
  26. package/src/skf-brief-skill/SKILL.md +1 -0
  27. package/src/skf-brief-skill/references/invocation-contract.md +2 -2
  28. package/src/skf-campaign/SKILL.md +1 -0
  29. package/src/skf-campaign/references/campaign-contracts.md +2 -0
  30. package/src/skf-campaign/references/step-05-skill-loop.md +2 -2
  31. package/src/skf-campaign/references/step-10-export.md +3 -2
  32. package/src/skf-create-skill/SKILL.md +2 -1
  33. package/src/skf-create-skill/references/ccc-index-check.md +1 -1
  34. package/src/skf-create-skill/references/component-extraction.md +1 -1
  35. package/src/skf-create-skill/references/extract.md +1 -1
  36. package/src/skf-create-skill/references/generate-artifacts.md +1 -1
  37. package/src/skf-create-skill/references/report.md +2 -2
  38. package/src/skf-create-skill/references/sub/ccc-discover.md +1 -1
  39. package/src/skf-create-skill/references/validate.md +1 -1
  40. package/src/skf-create-stack-skill/SKILL.md +1 -0
  41. package/src/skf-create-stack-skill/references/generate-output.md +1 -1
  42. package/src/skf-create-stack-skill/references/invocation-contract.md +1 -1
  43. package/src/skf-create-stack-skill/references/parallel-extract.md +2 -2
  44. package/src/skf-create-stack-skill/references/report.md +1 -1
  45. package/src/skf-drop-skill/SKILL.md +1 -0
  46. package/src/skf-drop-skill/references/invocation-contract.md +1 -1
  47. package/src/skf-drop-skill/references/report.md +1 -1
  48. package/src/skf-export-skill/SKILL.md +1 -0
  49. package/src/skf-export-skill/references/invocation-contract.md +1 -1
  50. package/src/skf-export-skill/references/summary.md +1 -1
  51. package/src/skf-forger/SKILL.md +1 -0
  52. package/src/skf-forger/references/pipeline-mode.md +2 -2
  53. package/src/skf-quick-skill/SKILL.md +2 -1
  54. package/src/skf-quick-skill/references/finalize.md +1 -1
  55. package/src/skf-quick-skill/references/halt-contract.md +2 -2
  56. package/src/skf-quick-skill/references/write-and-validate.md +1 -1
  57. package/src/skf-refine-architecture/SKILL.md +2 -1
  58. package/src/skf-refine-architecture/customize.toml +1 -1
  59. package/src/skf-refine-architecture/references/report.md +1 -1
  60. package/src/skf-rename-skill/SKILL.md +1 -0
  61. package/src/skf-rename-skill/references/invocation-contract.md +2 -2
  62. package/src/skf-setup/SKILL.md +1 -0
  63. package/src/skf-setup/references/ccc-index.md +1 -1
  64. package/src/skf-setup/references/invocation-contract.md +1 -1
  65. package/src/skf-test-skill/SKILL.md +1 -0
  66. package/src/skf-test-skill/assets/output-section-formats.md +4 -4
  67. package/src/skf-test-skill/references/coherence-check.md +8 -6
  68. package/src/skf-test-skill/references/coverage-check-branches.md +23 -3
  69. package/src/skf-test-skill/references/coverage-check-tiers.md +3 -3
  70. package/src/skf-test-skill/references/coverage-check.md +30 -43
  71. package/src/skf-test-skill/references/init.md +1 -1
  72. package/src/skf-test-skill/references/inventory-contract.md +27 -0
  73. package/src/skf-test-skill/references/invocation-contract.md +3 -3
  74. package/src/skf-test-skill/references/report.md +3 -3
  75. package/src/skf-test-skill/references/scoring-rules.md +6 -6
  76. package/src/skf-test-skill/references/signature-comparison-rules.md +72 -0
  77. package/src/skf-test-skill/references/source-access-protocol.md +12 -8
  78. package/src/skf-test-skill/references/step-hard-gate.md +1 -0
  79. package/src/skf-test-skill/scripts/build-result-context.py +20 -3
  80. package/src/skf-test-skill/scripts/gap-ledger.py +78 -13
  81. package/src/skf-test-skill/scripts/load-coverage-inputs.py +345 -9
  82. package/src/skf-test-skill/scripts/locate-export-segments.py +591 -63
  83. package/src/skf-test-skill/scripts/score-signatures.py +939 -74
  84. package/src/skf-test-skill/scripts/validate-inventory.py +245 -29
  85. package/src/skf-update-skill/SKILL.md +1 -0
  86. package/src/skf-update-skill/references/gap-driven.md +28 -22
  87. package/src/skf-update-skill/references/invocation-contract.md +1 -1
  88. package/src/skf-update-skill/references/merge.md +4 -4
  89. package/src/skf-update-skill/references/report.md +1 -1
  90. package/src/skf-update-skill/references/write.md +5 -4
  91. package/src/skf-verify-stack/SKILL.md +2 -1
@@ -8,13 +8,29 @@ Structural validator for the SKF test-skill coverage step (coverage-check.md
8
8
  §1a "Schema validation" block). test-skill is a quality gate, so it must not
9
9
  trust subagent output blindly: before any downstream step consumes the
10
10
  documented inventory, the shape is validated. That validation is pure
11
- structural checking — strip wrapping fences, parse JSON, assert required keys
11
+ structural checking (strip wrapping fences, parse JSON, assert required keys
12
12
  and types, assert each export entry is a dict with a non-empty `name` and a
13
- `kind` drawn from a fixed enum (and, when present, a `signature_form` of
14
- `partial` or `full`), and assert each cross-check mismatch carries its
15
- five fields — with exactly one correct pass/fail per input and no
16
- interpretation of meaning. It is therefore script work, not prompt work; the
17
- prompt reads this verdict and owns only the HALT decision.
13
+ `kind` drawn from a fixed enum and, when present, a `signature_form` of
14
+ `partial` or `full`, and assert each cross-check mismatch carries its fields
15
+ and one other side, in order), with exactly one correct pass/fail per input
16
+ and no interpretation of meaning. It is therefore script work, not prompt
17
+ work; the prompt reads this verdict and owns only the HALT decision.
18
+
19
+ A cross-check mismatch (coverage-check.md §1 compares the SKILL.md body
20
+ with each reference file, and two reference files the body does not settle)
21
+ carries `export`, `reference_file`, `reference_line` and `issue`, and the
22
+ other side of its pair: `skill_md_line` for the SKILL.md body, or
23
+ `other_file` and `other_line` for a second reference file, never both and
24
+ never neither (a JSON null counts as absent). Each file is a
25
+ `references/**/*.md` file of the package, normalized to its path relative
26
+ to the package with forward slashes (an absolute path counts only inside
27
+ --skill-package), and the inventory echoed back carries that path; each line
28
+ is a positive integer. A pair of references is written in one order,
29
+ (`other_file`, `other_line`) sorting before (`reference_file`,
30
+ `reference_line`) in codepoint order, so a rerun gives the gap one title and
31
+ the ledger records it once; one export documented twice in one reference is
32
+ a pair of its two lines. A violation of the other side names both shapes,
33
+ and one of the order names both places.
18
34
 
19
35
  With --skill-package it also runs the ground-truth spot-check (coverage-check.md
20
36
  §1a): it sorts the exports by name (a stable sort), samples the first, the
@@ -27,6 +43,18 @@ violation, and the prompt re-dispatches the subagent with them, as it does
27
43
  for a schema violation. No grep runs in the prompt, so an export named
28
44
  `$state` or `a.b` is never a shell variable or a regex.
29
45
 
46
+ With --floor it also checks the inventory against the floor
47
+ load-coverage-inputs.py floor wrote (#790): the names the skill documents in
48
+ a fixed form. A floor name is checked when one of its origins is SKILL.md
49
+ or a file the inventory's `references` lists (the spot-check's files; a
50
+ listed absolute path inside the --skill-package folder counts as its path
51
+ relative to it); a name only an unlisted reference writes is not. It is
52
+ kept when any inventory entry has that name, whatever its kind: kinds stay
53
+ the reader's. Each checked name not
54
+ kept is a violation naming its first checked origin, and the inventory is
55
+ invalid, as for a fabricated name, so the prompt re-dispatches the subagent
56
+ with the names it left out.
57
+
30
58
  An entry's optional `signature_form` says how the skill presents its
31
59
  signature: `partial` only when the skill labels the form partial (a
32
60
  simplified form, a key-parameter summary, a `...` that stands in for
@@ -70,7 +98,7 @@ declaration the skill cites.
70
98
 
71
99
  CLI usage (mirrors reconcile-coverage.py):
72
100
  uv run validate-inventory.py --input <response file> [--output <inventory file>]
73
- [--skill-package <package folder>]
101
+ [--skill-package <package folder>] [--floor <floor file>]
74
102
  uv run validate-inventory.py '<raw response>' # positional
75
103
  uv run validate-inventory.py --json-input '<raw response>' # explicit flag
76
104
  echo '<raw response>' | uv run validate-inventory.py --stdin # piped input
@@ -84,7 +112,8 @@ path, so a quote or an apostrophe in a description never meets a shell.
84
112
  JSON) when it is valid, for the steps that read it by path; an invalid
85
113
  inventory removes a file left there by an earlier attempt. --skill-package
86
114
  names the skill package under test (the folder that holds SKILL.md) and
87
- adds the spot-check.
115
+ adds the spot-check. --floor names the floor file (UTF-8 JSON) and adds the
116
+ floor check.
88
117
 
89
118
  Input: the subagent's RAW response text (JSON, optionally wrapped in a markdown
90
119
  code fence — a leading line of three backticks with an optional language tag and
@@ -107,15 +136,21 @@ Output (stdout, one object):
107
136
  "absent": [<name>, ...], # the sampled names no file writes
108
137
  "files": [<path>, ...], # the files read, relative to the package
109
138
  "unread": [<path>, ...] # listed references that are missing,
110
- } # unreadable or outside the package
139
+ }, # unreadable or outside the package
140
+ "floor": { # with --floor, once the schema passed
141
+ "names": <int>, # the floor's names
142
+ "checked": <int>, # those with an origin in SKILL.md or a listed reference
143
+ "absent": [<name>, ...] # the checked names no inventory entry lists
144
+ }
111
145
  }
112
146
 
113
147
  Exit codes:
114
- 0 inventory valid (and every sampled name found)
115
- 1 no input provided, the --input file cannot be read, or the
116
- --skill-package folder holds no readable SKILL.md (usage error)
117
- 2 inventory invalid (parse failure, schema violation or a sampled name
118
- absent; result JSON emitted)
148
+ 0 inventory valid (every sampled name found, every checked floor name kept)
149
+ 1 no input provided, the --input file cannot be read, the
150
+ --skill-package folder holds no readable SKILL.md, or the --floor file
151
+ cannot be read or is not a floor (usage error)
152
+ 2 inventory invalid (parse failure, schema violation, a sampled name
153
+ absent or a floor name left out; result JSON emitted)
119
154
  """
120
155
 
121
156
  from __future__ import annotations
@@ -154,12 +189,20 @@ SIGNATURE_FORMS = ("partial", "full")
154
189
 
155
190
  REQUIRED_MISMATCH_FIELDS = (
156
191
  "export",
157
- "skill_md_line",
158
192
  "reference_file",
159
193
  "reference_line",
160
194
  "issue",
161
195
  )
162
196
 
197
+ # The other side of a cross-check pair, exactly one of these shapes: the
198
+ # SKILL.md body's line, or a second reference file and its line.
199
+ BODY_SIDE = ("skill_md_line",)
200
+ REFERENCE_SIDE = ("other_file", "other_line")
201
+ OTHER_SIDE_SHAPES = (
202
+ "`skill_md_line` (the SKILL.md body) or `other_file` and `other_line` "
203
+ "(a second reference file)"
204
+ )
205
+
163
206
  # Kinds whose documented item also needs its parameters and return type to
164
207
  # be complete (the docs-only Export Coverage rule).
165
208
  SIGNATURE_KINDS = ("function", "method")
@@ -366,6 +409,77 @@ def spot_check(inventory, skill_package_path):
366
409
  }
367
410
 
368
411
 
412
+ # --- Floor ------------------------------------------------------------------
413
+
414
+
415
+ class FloorError(Exception):
416
+ """The --floor file cannot be read or is not a floor: exit 1."""
417
+
418
+
419
+ def _is_origin(origin):
420
+ return (isinstance(origin, dict) and isinstance(origin.get("file"), str) and bool(origin["file"])
421
+ and isinstance(origin.get("line"), int) and not isinstance(origin["line"], bool)
422
+ and isinstance(origin.get("form"), str))
423
+
424
+
425
+ def read_floor(path):
426
+ """The floor load-coverage-inputs.py floor wrote to `path`, its shape checked."""
427
+ try:
428
+ data = json.loads(Path(path).read_bytes().decode("utf-8-sig"))
429
+ except (OSError, UnicodeDecodeError) as exc:
430
+ raise FloorError(f"cannot read {path}: {exc}") from exc
431
+ except json.JSONDecodeError as exc:
432
+ raise FloorError(f"{path} is not JSON: {exc.msg}") from exc
433
+ names = data.get("names") if isinstance(data, dict) else None
434
+ if not isinstance(names, list) or not all(
435
+ isinstance(item, dict) and isinstance(item.get("name"), str) and item["name"]
436
+ and isinstance(item.get("origins"), list) and all(_is_origin(o) for o in item["origins"])
437
+ for item in names):
438
+ raise FloorError(f"{path} is not a floor: `names` must give each name's `name` and `origins` "
439
+ "(each a file, a line and a form)")
440
+ return data
441
+
442
+
443
+ def _listed_path(ref, skill_package_path):
444
+ """A listed reference as a floor origin names it: relative to the package, forward slashes. An absolute
445
+ path inside the package is made relative to it (it needs the package to be)."""
446
+ if skill_package_path is not None and os.path.isabs(ref):
447
+ target, root = os.path.realpath(ref), os.path.realpath(skill_package_path)
448
+ if _inside(target, root):
449
+ return Path(os.path.relpath(target, root)).as_posix()
450
+ return _cited_path(ref)
451
+
452
+
453
+ def floor_check(inventory, floor, skill_package_path=None):
454
+ """The floor names the inventory leaves out: (the `floor` result, its violations).
455
+
456
+ A floor name is checked when one of its origins is SKILL.md or a file
457
+ the inventory's `references` lists (the spot-check's files, an absolute
458
+ path inside `skill_package_path` matched by its path relative to it),
459
+ and kept when any inventory entry has that name, whatever its kind. A
460
+ name left out is a violation naming its first checked origin.
461
+ """
462
+ listed = inventory.get("references")
463
+ files = {"SKILL.md"} | {_listed_path(ref, skill_package_path)
464
+ for ref in (listed if isinstance(listed, list) else [])
465
+ if isinstance(ref, str) and ref.strip()}
466
+ kept = {e.get("name") for e in inventory.get("exports") or [] if isinstance(e, dict)}
467
+ names = floor.get("names") or []
468
+ checked, absent, violations = 0, [], []
469
+ for item in names:
470
+ origins = [o for o in item["origins"] if _cited_path(o["file"]) in files]
471
+ if not origins:
472
+ continue
473
+ checked += 1
474
+ name = item["name"]
475
+ if name in kept:
476
+ continue
477
+ absent.append(name)
478
+ where = f"{origins[0]['file']}:{origins[0]['line']} ({origins[0]['form']})"
479
+ violations.append(f"floor: `{name}` is documented at {where}, but no inventory entry lists it")
480
+ return {"names": len(names), "checked": checked, "absent": absent}, violations
481
+
482
+
369
483
  # --- Completeness -------------------------------------------------------------
370
484
 
371
485
 
@@ -415,13 +529,100 @@ def completeness(exports):
415
529
  # --- Validation ---------------------------------------------------------------
416
530
 
417
531
 
418
- def validate_inventory(raw, skill_package_path=None):
419
- """Structural validation of the subagent inventory response, then the spot-check.
532
+ def _reference_path(value, skill_package_path=None):
533
+ """A pair's file as the package names it (`_listed_path`: relative, forward slashes, no `./`), or None when
534
+ it is no `references/**/*.md` file of the package: empty, not a string, `SKILL.md`, another file, or an
535
+ absolute path outside the package (or with no package to make it relative to)."""
536
+ if not isinstance(value, str) or not value.strip():
537
+ return None
538
+ path = _listed_path(value.strip(), skill_package_path)
539
+ parts = path.split("/")
540
+ if len(parts) < 2 or parts[0] != "references" or ".." in parts or not parts[-1].endswith(".md") \
541
+ or parts[-1] == ".md":
542
+ return None
543
+ return path
544
+
545
+
546
+ def _is_line(value):
547
+ """A line number: a positive integer, never a bool or a string."""
548
+ return isinstance(value, int) and not isinstance(value, bool) and value > 0
549
+
550
+
551
+ def mismatch_violations(idx, entry, skill_package_path=None):
552
+ """The violations of one cross_check_mismatches entry (an object).
553
+
554
+ A required field left out; a `reference_file` or `other_file` that is no
555
+ `references/**/*.md` path of the package; a line that is no positive
556
+ integer; an other side that is not exactly one shape, `skill_md_line` or
557
+ both of `other_file` and `other_line` (a JSON null counts as absent); or a
558
+ pair of references out of order: (`other_file`, `other_line`) must sort
559
+ before (`reference_file`, `reference_line`) in codepoint order, so the pair
560
+ has one title. Each path is checked, and left in the entry, as the package
561
+ names it, relative and with forward slashes.
562
+ """
563
+ where = f"cross_check_mismatches[{idx}]"
564
+ violations = []
565
+ missing = [f for f in REQUIRED_MISMATCH_FIELDS if f not in entry]
566
+ if missing:
567
+ violations.append(f"{where} missing field(s): {', '.join(missing)}")
568
+ ref = None
569
+ if "reference_file" in entry:
570
+ ref = _reference_path(entry["reference_file"], skill_package_path)
571
+ if ref is None:
572
+ violations.append(f"{where}: `reference_file` must be a `references/*.md` file of the skill package, "
573
+ f"not {json.dumps(entry['reference_file'])}")
574
+ else:
575
+ entry["reference_file"] = ref
576
+ lines_ok = True
577
+ for field in ("reference_line",) + BODY_SIDE + REFERENCE_SIDE[1:]:
578
+ given = entry.get(field) is not None or (field == "reference_line" and field in entry)
579
+ if given and not _is_line(entry[field]):
580
+ lines_ok = False
581
+ violations.append(f"{where}: `{field}` must be a line number (a positive integer), "
582
+ f"not {json.dumps(entry[field])}")
583
+ body = [f for f in BODY_SIDE if entry.get(f) is not None]
584
+ reference = [f for f in REFERENCE_SIDE if entry.get(f) is not None]
585
+ if body and reference:
586
+ violations.append(f"{where} gives two other sides: give one, {OTHER_SIDE_SHAPES}")
587
+ return violations
588
+ if not body and not reference:
589
+ violations.append(f"{where} missing its other side: give {OTHER_SIDE_SHAPES}")
590
+ return violations
591
+ if body:
592
+ return violations
593
+ absent = [f for f in REFERENCE_SIDE if f not in reference]
594
+ if absent:
595
+ violations.append(f"{where} missing field(s): {', '.join(absent)}; its other side is "
596
+ f"{OTHER_SIDE_SHAPES}")
597
+ return violations
598
+ other = _reference_path(entry["other_file"], skill_package_path)
599
+ if other is None:
600
+ violations.append(f"{where}: `other_file` must be a `references/*.md` file of the skill package, "
601
+ f"not {json.dumps(entry['other_file'])}")
602
+ return violations
603
+ entry["other_file"] = other
604
+ if ref is None or not lines_ok or "reference_line" not in entry:
605
+ return violations
606
+ first, last = (other, entry["other_line"]), (ref, entry["reference_line"])
607
+ if not first < last:
608
+ relation = "names the same line as" if first == last else "sorts after"
609
+ violations.append(
610
+ f"{where}: `other_file` {json.dumps(other)} at `other_line` {first[1]} {relation} "
611
+ f"`reference_file` {json.dumps(ref)} at `reference_line` {last[1]}: a pair of references gives the "
612
+ "one that sorts first in codepoint order, by path and then line, as `other_file` and `other_line`, "
613
+ "and the one that sorts last as `reference_file` and `reference_line`"
614
+ )
615
+ return violations
616
+
617
+
618
+ def validate_inventory(raw, skill_package_path=None, floor=None):
619
+ """Structural validation of the subagent inventory response, then the spot-check and the floor check.
420
620
 
421
621
  `raw` is the raw response text. Returns the result dict documented in the
422
622
  module docstring. With `skill_package_path`, an inventory that passed the
423
- schema is spot-checked against the package (spot_check). Deterministic:
424
- identical input yields an identical verdict.
623
+ schema is spot-checked against the package (spot_check); with `floor`
624
+ (read_floor's object), it is checked against the floor (floor_check).
625
+ Deterministic: identical input yields an identical verdict.
425
626
  """
426
627
  violations = []
427
628
  inner = strip_fences(raw)
@@ -491,25 +692,27 @@ def validate_inventory(raw, skill_package_path=None):
491
692
  f"not {json.dumps(entry['signature_form'])}"
492
693
  )
493
694
 
494
- # Non-empty cross-check mismatch entries must carry all five fields.
695
+ # Each cross-check mismatch carries its fields and one other side of its
696
+ # pair, a pair of references in order.
495
697
  for idx, entry in enumerate(cross):
496
698
  if not isinstance(entry, dict):
497
699
  violations.append(f"cross_check_mismatches[{idx}] is not an object")
498
700
  continue
499
- missing = [f for f in REQUIRED_MISMATCH_FIELDS if f not in entry]
500
- if missing:
501
- violations.append(
502
- f"cross_check_mismatches[{idx}] missing field(s): {', '.join(missing)}"
503
- )
701
+ violations.extend(mismatch_violations(idx, entry, skill_package_path))
504
702
 
703
+ schema_valid = not violations
505
704
  checked = None
506
- if not violations and skill_package_path is not None:
705
+ if schema_valid and skill_package_path is not None:
507
706
  checked = spot_check(data, skill_package_path)
508
707
  for name in checked["absent"]:
509
708
  violations.append(
510
709
  f"spot-check: `{name}` is listed as an export, but SKILL.md and the "
511
710
  f"listed reference files never write it"
512
711
  )
712
+ floor_result = None
713
+ if schema_valid and floor is not None:
714
+ floor_result, floor_violations = floor_check(data, floor, skill_package_path)
715
+ violations.extend(floor_violations)
513
716
 
514
717
  valid = not violations
515
718
  result = {
@@ -523,6 +726,8 @@ def validate_inventory(raw, skill_package_path=None):
523
726
  result["completeness"] = completeness(exports)
524
727
  if checked is not None:
525
728
  result["spotCheck"] = checked
729
+ if floor_result is not None:
730
+ result["floor"] = floor_result
526
731
  return result
527
732
 
528
733
 
@@ -536,8 +741,9 @@ def _build_parser():
536
741
  "Deterministic schema validation of the §1 subagent inventory JSON "
537
742
  "(coverage-check.md §1a). Strips wrapping fences, parses, and asserts "
538
743
  "the required-keys/types/enum/mismatch-field contract, then with "
539
- "--skill-package spot-checks sampled names against the package, returning "
540
- "{valid, violations, rejectedCount, exportsCount, inventory, completeness, spotCheck}."
744
+ "--skill-package spot-checks sampled names against the package and --floor checks it "
745
+ "keeps the floor's names, returning "
746
+ "{valid, violations, rejectedCount, exportsCount, inventory, completeness, spotCheck, floor}."
541
747
  ),
542
748
  formatter_class=argparse.RawDescriptionHelpFormatter,
543
749
  )
@@ -573,6 +779,11 @@ def _build_parser():
573
779
  metavar="DIR",
574
780
  help="Spot-check sampled names against this package's SKILL.md and listed references.",
575
781
  )
782
+ parser.add_argument(
783
+ "--floor",
784
+ metavar="PATH",
785
+ help="Check the inventory keeps the names of this load-coverage-inputs.py floor file.",
786
+ )
576
787
  return parser
577
788
 
578
789
 
@@ -612,7 +823,12 @@ def main(argv=None):
612
823
  return 1
613
824
 
614
825
  try:
615
- result = validate_inventory(raw, args.skill_package)
826
+ floor = read_floor(args.floor) if args.floor is not None else None
827
+ except FloorError as exc:
828
+ print(f"error: --floor: {exc}", file=sys.stderr)
829
+ return 1
830
+ try:
831
+ result = validate_inventory(raw, args.skill_package, floor)
616
832
  except PackageError as exc:
617
833
  print(f"error: --skill-package: {exc}", file=sys.stderr)
618
834
  return 1
@@ -31,6 +31,7 @@ These rules apply to every step in this workflow:
31
31
  - Never hallucinate — every statement must have AST provenance
32
32
  - [MANUAL] sections survive regeneration with zero content loss
33
33
  - Only load one step file at a time — never preload future steps
34
+ - Run every subagent in the foreground (parallel ones as several calls in one message, never with `run_in_background`) and wait for every long-running command to exit before the next instruction; in a headless run, never end a turn while either still runs, since under `claude -p` a run stops when its turn ends.
34
35
  - Always communicate in `{communication_language}`
35
36
  - If `{headless_mode}` is true, auto-proceed through confirmation gates with their default action and log each auto-decision
36
37
  - Every HALT, ABORT or other exit before step 7 runs the **Halt procedure** of the step file it fires in: one `{runStateHelper}` `halt` call that undoes this run's writes, removes the private source tree, releases the run lock and, headless, prints the halt's line. A halt never falls through to step 6.