bmad-module-skill-forge 5.0.0 → 7.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/agents.md +1 -1
- package/docs/architecture.md +3 -3
- package/docs/campaign.md +1 -1
- package/docs/troubleshooting.md +9 -3
- package/docs/verifying-a-skill.md +9 -9
- package/docs/workflows.md +7 -7
- package/package.json +1 -1
- package/src/knowledge/tool-resolution.md +6 -6
- package/src/shared/references/headless-gate-convention.md +4 -1
- package/src/shared/references/output-contract-schema.md +3 -3
- package/src/shared/references/pipeline-contracts.md +2 -1
- package/src/shared/scripts/schemas/skf-test-result-envelope.v1.json +1 -1
- package/src/shared/scripts/skf-build-change-manifest.py +332 -54
- package/src/shared/scripts/skf-classify-changed-files.py +15 -10
- package/src/shared/scripts/skf-emit-result-envelope.py +58 -15
- package/src/shared/scripts/skf-extract-public-api.py +33 -13
- package/src/shared/scripts/skf-parse-gaps.py +3 -2
- package/src/shared/scripts/skf-quick-batch.py +16 -3
- package/src/shared/scripts/skf-verify-provenance-completeness.py +317 -44
- package/src/skf-analyze-source/SKILL.md +1 -0
- package/src/skf-analyze-source/customize.toml +3 -2
- package/src/skf-analyze-source/references/auto-docs-only.md +2 -2
- package/src/skf-analyze-source/references/generate-briefs.md +5 -3
- package/src/skf-analyze-source/references/headless-contract.md +3 -3
- package/src/skf-analyze-source/references/step-auto-scope.md +2 -2
- package/src/skf-audit-skill/SKILL.md +2 -1
- package/src/skf-audit-skill/references/report.md +1 -1
- package/src/skf-brief-skill/SKILL.md +1 -0
- package/src/skf-brief-skill/references/invocation-contract.md +2 -2
- package/src/skf-campaign/SKILL.md +1 -0
- package/src/skf-campaign/references/campaign-contracts.md +3 -1
- package/src/skf-campaign/references/step-05-skill-loop.md +2 -2
- package/src/skf-campaign/references/step-07-capstone.md +3 -3
- package/src/skf-campaign/references/step-08-verify.md +3 -3
- package/src/skf-campaign/references/step-09-refine.md +3 -3
- package/src/skf-campaign/references/step-10-export.md +3 -2
- package/src/skf-campaign/scripts/campaign-state.py +22 -5
- package/src/skf-create-skill/SKILL.md +3 -2
- package/src/skf-create-skill/customize.toml +4 -4
- package/src/skf-create-skill/references/batch-mode.md +3 -1
- package/src/skf-create-skill/references/ccc-index-check.md +1 -1
- package/src/skf-create-skill/references/component-extraction.md +1 -1
- package/src/skf-create-skill/references/extract.md +1 -1
- package/src/skf-create-skill/references/generate-artifacts.md +1 -1
- package/src/skf-create-skill/references/report.md +9 -4
- package/src/skf-create-skill/references/sub/ccc-discover.md +1 -1
- package/src/skf-create-skill/references/validate.md +2 -2
- package/src/skf-create-stack-skill/SKILL.md +1 -0
- package/src/skf-create-stack-skill/references/generate-output.md +1 -1
- package/src/skf-create-stack-skill/references/invocation-contract.md +1 -1
- package/src/skf-create-stack-skill/references/parallel-extract.md +2 -2
- package/src/skf-create-stack-skill/references/report.md +1 -1
- package/src/skf-create-stack-skill/references/validate.md +12 -4
- package/src/skf-drop-skill/SKILL.md +1 -0
- package/src/skf-drop-skill/references/invocation-contract.md +1 -1
- package/src/skf-drop-skill/references/report.md +1 -1
- package/src/skf-export-skill/SKILL.md +1 -0
- package/src/skf-export-skill/references/invocation-contract.md +1 -1
- package/src/skf-export-skill/references/summary.md +1 -1
- package/src/skf-forger/SKILL.md +1 -0
- package/src/skf-forger/references/pipeline-mode.md +2 -2
- package/src/skf-quick-skill/SKILL.md +2 -1
- package/src/skf-quick-skill/references/batch-mode.md +2 -0
- package/src/skf-quick-skill/references/finalize.md +1 -1
- package/src/skf-quick-skill/references/halt-contract.md +2 -2
- package/src/skf-quick-skill/references/write-and-validate.md +20 -10
- package/src/skf-refine-architecture/SKILL.md +2 -1
- package/src/skf-refine-architecture/customize.toml +1 -1
- package/src/skf-refine-architecture/references/report.md +1 -1
- package/src/skf-rename-skill/SKILL.md +1 -0
- package/src/skf-rename-skill/references/invocation-contract.md +2 -2
- package/src/skf-setup/SKILL.md +1 -0
- package/src/skf-setup/references/ccc-index.md +1 -1
- package/src/skf-setup/references/invocation-contract.md +1 -1
- package/src/skf-test-skill/SKILL.md +1 -0
- package/src/skf-test-skill/assets/output-section-formats.md +4 -4
- package/src/skf-test-skill/references/coherence-check.md +8 -6
- package/src/skf-test-skill/references/coverage-check-branches.md +23 -3
- package/src/skf-test-skill/references/coverage-check-tiers.md +3 -3
- package/src/skf-test-skill/references/coverage-check.md +30 -43
- package/src/skf-test-skill/references/init.md +1 -1
- package/src/skf-test-skill/references/inventory-contract.md +27 -0
- package/src/skf-test-skill/references/invocation-contract.md +3 -3
- package/src/skf-test-skill/references/report.md +3 -3
- package/src/skf-test-skill/references/scoring-rules.md +7 -7
- package/src/skf-test-skill/references/signature-comparison-rules.md +74 -0
- package/src/skf-test-skill/references/source-access-protocol.md +12 -8
- package/src/skf-test-skill/references/step-hard-gate.md +1 -0
- package/src/skf-test-skill/scripts/build-result-context.py +20 -3
- package/src/skf-test-skill/scripts/gap-ledger.py +78 -13
- package/src/skf-test-skill/scripts/load-coverage-inputs.py +575 -9
- package/src/skf-test-skill/scripts/locate-export-segments.py +591 -63
- package/src/skf-test-skill/scripts/score-signatures.py +937 -76
- package/src/skf-test-skill/scripts/validate-inventory.py +325 -30
- package/src/skf-update-skill/SKILL.md +1 -0
- package/src/skf-update-skill/customize.toml +1 -1
- package/src/skf-update-skill/references/gap-driven.md +28 -22
- package/src/skf-update-skill/references/invocation-contract.md +1 -1
- package/src/skf-update-skill/references/merge.md +4 -4
- package/src/skf-update-skill/references/report.md +11 -4
- package/src/skf-update-skill/references/write.md +6 -5
- package/src/skf-verify-stack/SKILL.md +2 -1
|
@@ -103,16 +103,21 @@ Modes, from the statuses:
|
|
|
103
103
|
|
|
104
104
|
An unknown --language value adds an `unknown-language` warning.
|
|
105
105
|
|
|
106
|
-
Library use: the one brief-scope test for script and asset paths
|
|
107
|
-
skf-new-file-diff.py (update-skill
|
|
108
|
-
skf-detect-scripts-assets.py detect --brief
|
|
109
|
-
skf-compare-file-hashes.py compare --brief (audit-skill)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
`
|
|
106
|
+
Library use: the one brief-scope test for script and asset paths, and for
|
|
107
|
+
the files a gap-driven repair reads. skf-new-file-diff.py (update-skill
|
|
108
|
+
Category D, --brief), skf-detect-scripts-assets.py detect --brief
|
|
109
|
+
(create-skill), skf-compare-file-hashes.py compare --brief (audit-skill)
|
|
110
|
+
and skf-build-change-manifest.py gap-records --brief (update-skill
|
|
111
|
+
gap-driven, which passes as `cited` each file its gaps cite or their
|
|
112
|
+
Remediation names and each `entries[].source_file` of the provenance
|
|
113
|
+
map) load this file by its path and call load_scope(brief_path, cited).
|
|
114
|
+
It returns a Scope: takes(path) is the in-scope test above
|
|
115
|
+
(`_in_scope`), with the brief's `scope.include` and `scope.exclude`
|
|
116
|
+
normalized as classify normalizes them and, for an empty
|
|
117
|
+
`scope.include`, the extensions of the `cited` paths (the provenance
|
|
118
|
+
map's `entries[].source_file` for the first three callers) and of the
|
|
119
|
+
brief's `language`, so a path with no extension is not taken; wants(kind)
|
|
120
|
+
is False for a `script` or `asset` whose
|
|
116
121
|
`scripts_intent` or `assets_intent` is `none` (an absent or free-text
|
|
117
122
|
intent detects). load_scope raises ValueError, `brief not found: <path>`
|
|
118
123
|
for a path that names no file and load_brief's message for a brief it
|
|
@@ -44,10 +44,17 @@ Subcommands:
|
|
|
44
44
|
that name: the envelope warns that the `_bmad/custom/`
|
|
45
45
|
overrides were not applied.
|
|
46
46
|
|
|
47
|
-
record Append
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
that
|
|
47
|
+
record Append to the run's sink: --decision reads one auto-decision
|
|
48
|
+
(a JSON object) on stdin; --warning takes one warning's text,
|
|
49
|
+
and a repeated --warning records each; --warnings-file reads a
|
|
50
|
+
JSON file that holds the warnings, an array of strings or an
|
|
51
|
+
object whose `warnings` is one (a helper's saved stdout, such
|
|
52
|
+
as skf-parse-gaps.py translate's), and records each, in order.
|
|
53
|
+
A blank or non-string warning, an unreadable file or an object
|
|
54
|
+
with no `warnings` list exits 1 and writes nothing; an empty
|
|
55
|
+
list writes nothing and exits 0. With --workflow a decision is
|
|
56
|
+
checked against that workflow's headless_decisions item schema
|
|
57
|
+
first.
|
|
51
58
|
|
|
52
59
|
validate Read an envelope (without the prefix) as JSON on stdin and
|
|
53
60
|
verify it against the workflow's schema (skf-setup's without
|
|
@@ -131,7 +138,8 @@ envelope field of the same name, except `result_contract` and
|
|
|
131
138
|
|
|
132
139
|
Run sink. Every run owns a folder, `_bmad-output/.skf-run/<workflow>-<run_id>/`,
|
|
133
140
|
passed as --run-dir. Each gate records its auto-decision there the moment
|
|
134
|
-
it decides, and each warning as it is raised, with `record
|
|
141
|
+
it decides, and each warning as it is raised, with `record` (one call can
|
|
142
|
+
record several warnings: each line is appended in that one write):
|
|
135
143
|
|
|
136
144
|
{run_dir}/headless-decisions.jsonl one decision object per line
|
|
137
145
|
{run_dir}/warnings.jsonl one warning string per line
|
|
@@ -299,6 +307,7 @@ the first of `uv run`, `python3`, `python` and `py -3` that works.
|
|
|
299
307
|
uv run skf-emit-result-envelope.py emit-blocked --phase "step 2:write-tools" --path "<path>" --reason "<message>" --stderr-from "{run_dir}/write-tools.err"
|
|
300
308
|
echo '{"skf_setup":{...}}' | uv run skf-emit-result-envelope.py validate
|
|
301
309
|
uv run skf-emit-result-envelope.py record --run-dir "{run_dir}" --warning "customization_resolver_unavailable: <reason>"
|
|
310
|
+
uv run skf-emit-result-envelope.py record --run-dir "{run_dir}" --warnings-file "{run_dir}/translate.json"
|
|
302
311
|
|
|
303
312
|
Exit codes:
|
|
304
313
|
0 success
|
|
@@ -1788,13 +1797,42 @@ def run_emit(workflow: str | None, *, halt: bool, label: str, run_dir: str | Non
|
|
|
1788
1797
|
print(envelope_line(schema, envelope), file=sys.stderr if target == "stderr" else sys.stdout)
|
|
1789
1798
|
|
|
1790
1799
|
|
|
1800
|
+
def _warnings_to_record(args) -> list[str]:
|
|
1801
|
+
"""The warnings one `record` call appends: each --warning, or the --warnings-file's list (a JSON array of
|
|
1802
|
+
strings, or an object whose `warnings` is one). A blank or non-string entry, an unreadable file or an
|
|
1803
|
+
object with no `warnings` list exits 1 before anything is written."""
|
|
1804
|
+
if args.warnings_file is None:
|
|
1805
|
+
entries, label = list(args.warning), "--warning"
|
|
1806
|
+
else:
|
|
1807
|
+
label = f"--warnings-file {args.warnings_file}"
|
|
1808
|
+
try:
|
|
1809
|
+
data = json.loads(Path(args.warnings_file).read_bytes().decode("utf-8-sig"))
|
|
1810
|
+
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as e:
|
|
1811
|
+
_die(1, f"record: cannot read {label}: {getattr(e, 'strerror', None) or e}")
|
|
1812
|
+
if isinstance(data, dict):
|
|
1813
|
+
if not isinstance(data.get("warnings"), list):
|
|
1814
|
+
_die(1, f"record: {label} is an object with no `warnings` list")
|
|
1815
|
+
entries = data["warnings"]
|
|
1816
|
+
elif isinstance(data, list):
|
|
1817
|
+
entries = data
|
|
1818
|
+
else:
|
|
1819
|
+
_die(1, f"record: {label} holds neither a JSON array nor an object with `warnings`")
|
|
1820
|
+
out = []
|
|
1821
|
+
for i, entry in enumerate(entries):
|
|
1822
|
+
if not isinstance(entry, str) or not entry.strip():
|
|
1823
|
+
where = "" if args.warnings_file is None and len(entries) == 1 else f" (entry {i})"
|
|
1824
|
+
_die(1, f"record: {label} must be a non-empty string{where}")
|
|
1825
|
+
out.append(entry.strip())
|
|
1826
|
+
return out
|
|
1827
|
+
|
|
1828
|
+
|
|
1791
1829
|
def cmd_record(args) -> None:
|
|
1792
|
-
"""Append one decision or
|
|
1830
|
+
"""Append one decision, or each warning given, to the run's sink, one JSON value per line."""
|
|
1793
1831
|
run_dir = Path(args.run_dir)
|
|
1794
|
-
if args.warning is not None:
|
|
1795
|
-
|
|
1796
|
-
if not
|
|
1797
|
-
|
|
1832
|
+
if args.warning is not None or args.warnings_file is not None:
|
|
1833
|
+
values = _warnings_to_record(args)
|
|
1834
|
+
if not values:
|
|
1835
|
+
return
|
|
1798
1836
|
name = SINK_WARNINGS
|
|
1799
1837
|
else:
|
|
1800
1838
|
value = _read_stdin_json("record")
|
|
@@ -1808,14 +1846,15 @@ def cmd_record(args) -> None:
|
|
|
1808
1846
|
errors = _validate_against_schema(value, item_schema)
|
|
1809
1847
|
if errors:
|
|
1810
1848
|
_die(1, f"record: the decision fails {schema_path.name}: {'; '.join(errors)}")
|
|
1849
|
+
values = [value]
|
|
1811
1850
|
name = SINK_DECISIONS
|
|
1812
1851
|
# ASCII only: a raw RAW_LINE_BREAKS character would split the line for a
|
|
1813
|
-
# reader that breaks lines there.
|
|
1814
|
-
|
|
1852
|
+
# reader that breaks lines there. Every line goes out in one write.
|
|
1853
|
+
lines = "".join(json.dumps(v, separators=(",", ":"), ensure_ascii=True) + "\n" for v in values)
|
|
1815
1854
|
try:
|
|
1816
1855
|
run_dir.mkdir(parents=True, exist_ok=True)
|
|
1817
1856
|
with open(run_dir / name, "a", encoding="utf-8", newline="\n") as fh:
|
|
1818
|
-
fh.write(
|
|
1857
|
+
fh.write(lines)
|
|
1819
1858
|
except OSError as e:
|
|
1820
1859
|
_die(1, f"record: cannot append to {(run_dir / name).as_posix()}: {e.strerror or e}")
|
|
1821
1860
|
|
|
@@ -1975,7 +2014,7 @@ def main() -> None:
|
|
|
1975
2014
|
p_emit = sub.add_parser("emit", help="Build a run's envelope from its context payload (default).")
|
|
1976
2015
|
p_halt = sub.add_parser("emit-halt", help="Build a HARD HALT's envelope from its halt payload.")
|
|
1977
2016
|
p_blocked = sub.add_parser("emit-blocked", help="Emit skf-setup's status='blocked' envelope for early-halt paths.")
|
|
1978
|
-
p_record = sub.add_parser("record", help="Append an auto-decision or
|
|
2017
|
+
p_record = sub.add_parser("record", help="Append an auto-decision or warnings to the run's sink.")
|
|
1979
2018
|
p_validate = sub.add_parser("validate", help="Validate an envelope payload against the schema.")
|
|
1980
2019
|
p_render = sub.add_parser("render-report", help="Print skf-setup's FORGE STATUS banner for its payload.")
|
|
1981
2020
|
for p in (p_emit, p_halt):
|
|
@@ -1992,7 +2031,11 @@ def main() -> None:
|
|
|
1992
2031
|
help="Check a decision against this workflow's headless_decisions schema.")
|
|
1993
2032
|
kind = p_record.add_mutually_exclusive_group(required=True)
|
|
1994
2033
|
kind.add_argument("--decision", action="store_true", help="Read one decision object on stdin.")
|
|
1995
|
-
kind.add_argument("--warning", default=None,
|
|
2034
|
+
kind.add_argument("--warning", action="append", default=None,
|
|
2035
|
+
help="A warning's text (repeatable: each one is recorded).")
|
|
2036
|
+
kind.add_argument("--warnings-file", default=None, metavar="FILE",
|
|
2037
|
+
help="A JSON file of warnings: an array of strings, or an object whose `warnings` is one "
|
|
2038
|
+
"(a helper's saved stdout).")
|
|
1996
2039
|
p_validate.add_argument("--workflow", default=None,
|
|
1997
2040
|
help="Workflow folder name or schema stem (skf-setup when omitted).")
|
|
1998
2041
|
p_render.add_argument("--run-dir", default=None, help="The setup run folder that holds the staged helper outputs.")
|
|
@@ -385,7 +385,11 @@ the line cap, say), which the caller then reads by eye. An item of a local `expo
|
|
|
385
385
|
list takes instead the type of the top-level binding it names in its file:
|
|
386
386
|
what the recipes record for that declaration exported in place (a const
|
|
387
387
|
bound to an arrow function or a function expression is a function), or
|
|
388
|
-
`re-export` for an import.
|
|
388
|
+
`re-export` for an import. When that binding is declared in the file (not
|
|
389
|
+
an import or a destructured name), the item also gets `declaration_line`
|
|
390
|
+
and `declaration_signature_line`, its `$NAME` line and the source line
|
|
391
|
+
holding it (an overload set's first signature); its `source_line`,
|
|
392
|
+
`signature_line` and `citation` stay at the list.
|
|
389
393
|
|
|
390
394
|
Entry points. The export surface each package declares: every subpath of a
|
|
391
395
|
package.json `exports` map (committed .d.ts/.d.mts targets outside src/
|
|
@@ -551,7 +555,10 @@ Output JSON (stdout, or -o):
|
|
|
551
555
|
"citation": "[AST:<file>:L<line>]", "ast_recipe",
|
|
552
556
|
"ast_node_type", "export_type", "language",
|
|
553
557
|
"from": "<module>" | null, "from_file": "<file>" | null,
|
|
554
|
-
"confidence": "T1", "extraction_method": "ast-grep"
|
|
558
|
+
"confidence": "T1", "extraction_method": "ast-grep",
|
|
559
|
+
# only on a local export list item whose binding is
|
|
560
|
+
# declared in the file:
|
|
561
|
+
"declaration_line": N, "declaration_signature_line": "<line>"}, ...],
|
|
555
562
|
"aggregates": {"exports": N, "by_type": {"function": N, ...},
|
|
556
563
|
"t1": N, "t1_low": 0},
|
|
557
564
|
"entry_points": {"status": "barrel" | "empty-barrel" | "no-entry-point"
|
|
@@ -2009,18 +2016,22 @@ def _binding_rules(language: str) -> list[dict]:
|
|
|
2009
2016
|
return rules + [r for r in _trace_rules(language) if r["id"] in IMPORT_RULES]
|
|
2010
2017
|
|
|
2011
2018
|
|
|
2012
|
-
def
|
|
2013
|
-
"""(file, name) -> the
|
|
2014
|
-
|
|
2019
|
+
def _bindings(matches: list[dict]) -> dict[tuple[str, str], dict]:
|
|
2020
|
+
"""(file, name) -> the top-level binding `name` has in that file, from
|
|
2021
|
+
the matches of _binding_rules: its form, the export type BINDING_TYPES
|
|
2022
|
+
gives it (`type`), and the `line` and `signature_line` of its match. Of
|
|
2023
|
+
several matches of the winning form (an overload set's signatures), the
|
|
2024
|
+
lowest line wins, the line merge_exports keeps for a name."""
|
|
2015
2025
|
rank = list(BINDING_TYPES)
|
|
2016
|
-
best: dict[tuple[str, str],
|
|
2026
|
+
best: dict[tuple[str, str], tuple[tuple[int, int], dict]] = {}
|
|
2017
2027
|
for m in matches:
|
|
2018
2028
|
form = "import" if m["rule"] in IMPORT_RULES else m["rule"].removeprefix(f"{BINDING_RULE}-")
|
|
2019
2029
|
if form in BINDING_TYPES and m["name"]:
|
|
2020
|
-
key = (m["file"], m["name"])
|
|
2021
|
-
if key not in best or
|
|
2022
|
-
best[key] = form
|
|
2023
|
-
|
|
2030
|
+
key, order = (m["file"], m["name"]), (rank.index(form), m["line"])
|
|
2031
|
+
if key not in best or order < best[key][0]:
|
|
2032
|
+
best[key] = (order, {"form": form, "type": BINDING_TYPES[form], "line": m["line"],
|
|
2033
|
+
"signature_line": m["signature_line"]})
|
|
2034
|
+
return {key: binding for key, (_, binding) in best.items()}
|
|
2024
2035
|
|
|
2025
2036
|
|
|
2026
2037
|
# --------------------------------------------------------------------------
|
|
@@ -2743,7 +2754,10 @@ def _type_list_items(exe: str, root: Path, folder: Path, matches: list[dict], ex
|
|
|
2743
2754
|
export_specifier with no `from`) the type of the top-level binding it
|
|
2744
2755
|
names in its file, as BINDING_TYPES has it; the recipe's own
|
|
2745
2756
|
export_type stays when no such binding has the name (a destructured
|
|
2746
|
-
one).
|
|
2757
|
+
one). A binding declared in the file (not an import) also gives the
|
|
2758
|
+
item `declaration_line` and `declaration_signature_line`, its line and
|
|
2759
|
+
source line; the item's own `source_line`, `signature_line` and
|
|
2760
|
+
citation stay at the list."""
|
|
2747
2761
|
items = [e for e in exports if e["ast_node_type"] == "export_specifier" and e["from"] is None]
|
|
2748
2762
|
if not items:
|
|
2749
2763
|
return
|
|
@@ -2755,10 +2769,16 @@ def _type_list_items(exe: str, root: Path, folder: Path, matches: list[dict], ex
|
|
|
2755
2769
|
languages = sorted({lang for f in files for lang in _rule_languages(_file_language(f) or "")}
|
|
2756
2770
|
& set(RECIPE_LANGUAGES))
|
|
2757
2771
|
rules = [rule for language in languages for rule in _binding_rules(language)]
|
|
2758
|
-
|
|
2772
|
+
bindings = _bindings(_scan(exe, root, folder, rules, files, errors, clock))
|
|
2759
2773
|
for e in items:
|
|
2760
2774
|
name = local.get((e["source_file"], e["ast_recipe"], e["export_name"]), e["export_name"])
|
|
2761
|
-
|
|
2775
|
+
binding = bindings.get((e["source_file"], name))
|
|
2776
|
+
if binding is None:
|
|
2777
|
+
continue
|
|
2778
|
+
e["export_type"] = binding["type"]
|
|
2779
|
+
if binding["form"] != "import":
|
|
2780
|
+
e["declaration_line"] = binding["line"]
|
|
2781
|
+
e["declaration_signature_line"] = binding["signature_line"]
|
|
2762
2782
|
|
|
2763
2783
|
|
|
2764
2784
|
# --------------------------------------------------------------------------
|
|
@@ -1021,8 +1021,9 @@ NOT_ROUTED_REASONS = {
|
|
|
1021
1021
|
"`scope.tier_a_include` to it, and documented-surface counts that differ by the `Type::method` entries the "
|
|
1022
1022
|
"named-export count leaves out need no fix"),
|
|
1023
1023
|
"provenance-unverified": (
|
|
1024
|
-
"the line-check rules could not verify the line,
|
|
1025
|
-
"
|
|
1024
|
+
"the line-check rules could not verify the line, or the recorded line is not a number, so a person checks "
|
|
1025
|
+
"by hand which line defines the export and, when it is not the recorded one, sets its provenance "
|
|
1026
|
+
"`source_line` to that line"),
|
|
1026
1027
|
"denominator-inflation": (
|
|
1027
1028
|
"it concerns the brief's scope and the count design, which a person decides in the skill brief, as the "
|
|
1028
1029
|
"gap's remediation says"),
|
|
@@ -124,6 +124,15 @@ summarize
|
|
|
124
124
|
"succeeded", "failed", "status", "fail_fast_triggered", "exit_code",
|
|
125
125
|
"summary_path"}, with "warnings" when a summary file could not be
|
|
126
126
|
written. A batch already summarized prints its event again.
|
|
127
|
+
The batch's result is that event and the file its summary_path names
|
|
128
|
+
(with summary_path null, when the per-batch file could not be written,
|
|
129
|
+
the event and batch.jsonl; a summary_write_failed warning beside a set
|
|
130
|
+
summary_path means only the copy failed): <stem>-latest.json holds the
|
|
131
|
+
last batch summary written, which may be another batch's, so it never
|
|
132
|
+
stands for this batch, and a batch cut off before summarize leaves it in
|
|
133
|
+
place. A summarize on a run folder that is gone (a batch whose targets
|
|
134
|
+
all succeeded, summarized and removed) exits 1, naming the per-batch
|
|
135
|
+
file whose input_file (or input_briefs) is this batch's.
|
|
127
136
|
|
|
128
137
|
The batch file is one JSON object per line, so a write cut short spoils at
|
|
129
138
|
most its own line, which is skipped when read: the next write ends that
|
|
@@ -628,10 +637,14 @@ def summary_event(summary: dict) -> dict:
|
|
|
628
637
|
|
|
629
638
|
def cmd_summarize(run_dir: Path, output_dir: Path) -> dict:
|
|
630
639
|
if not (run_dir / BATCH_FILE).exists() and not run_dir.exists():
|
|
631
|
-
|
|
640
|
+
create = run_dir.name.startswith(f"{CREATE_WORKFLOW}-")
|
|
641
|
+
stem = SUMMARY_STEMS[CREATE_WORKFLOW if create else WORKFLOW]
|
|
642
|
+
names = "input_briefs name its briefs" if create else "input_file names its batch file"
|
|
632
643
|
raise BatchError(f"no batch in {run_dir.as_posix()}: a batch whose targets all succeeded removes its "
|
|
633
|
-
f"run folder once summarized; its summary is "
|
|
634
|
-
f"{
|
|
644
|
+
f"run folder once summarized; its summary is the {stem}-<YYYYMMDD-HHmmss>.json in "
|
|
645
|
+
f"{output_dir.as_posix()} whose {names}, and "
|
|
646
|
+
f"{(output_dir / f'{stem}-latest.json').as_posix()} holds the last batch summary "
|
|
647
|
+
f"written, which may be another batch's")
|
|
635
648
|
state = load(run_dir)
|
|
636
649
|
name = workflow(state)
|
|
637
650
|
if state["summary"] is not None:
|