@hunter-harness/workflow-harness 0.2.23 → 0.2.25
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/harness/bundles/general/claude-code/.harness-build.json +1 -1
- package/harness/bundles/general/claude-code/README.md +2 -2
- package/harness/bundles/general/claude-code/agents/harness-evaluator.md +4 -4
- package/harness/bundles/general/claude-code/agents/harness-explorer.md +2 -2
- package/harness/bundles/general/claude-code/agents/harness-reviewer.md +2 -2
- package/harness/bundles/general/claude-code/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-archive/templates/render-summary.mjs +6 -4
- package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-plan/SKILL.md +7 -4
- package/harness/bundles/general/claude-code/harness-plan/checklist.md +12 -17
- package/harness/bundles/general/claude-code/harness-plan/reference.md +7 -19
- package/harness/bundles/general/claude-code/harness-review/SKILL.md +3 -3
- package/harness/bundles/general/claude-code/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-run/protocols.md +3 -3
- package/harness/bundles/general/claude-code/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-submit/checklist.md +8 -2
- package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/scripts/harness_archive.py +124 -17
- package/harness/bundles/general/claude-code/scripts/harness_deploy.py +22 -3
- package/harness/bundles/general/claude-code/scripts/harness_integration.py +77 -2
- package/harness/bundles/general/claude-code/scripts/harness_preflight.py +50 -29
- package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/general/codebuddy/README.md +2 -2
- package/harness/bundles/general/codebuddy/agents/harness-evaluator.md +5 -6
- package/harness/bundles/general/codebuddy/agents/harness-explorer.md +3 -3
- package/harness/bundles/general/codebuddy/agents/harness-reviewer.md +3 -5
- package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-archive/templates/render-summary.mjs +6 -4
- package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +7 -4
- package/harness/bundles/general/codebuddy/harness-plan/checklist.md +12 -17
- package/harness/bundles/general/codebuddy/harness-plan/reference.md +7 -19
- package/harness/bundles/general/codebuddy/harness-review/SKILL.md +3 -3
- package/harness/bundles/general/codebuddy/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-run/protocols.md +3 -3
- package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-submit/checklist.md +8 -2
- package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/scripts/harness_archive.py +124 -17
- package/harness/bundles/general/codebuddy/scripts/harness_deploy.py +22 -3
- package/harness/bundles/general/codebuddy/scripts/harness_integration.py +77 -2
- package/harness/bundles/general/codebuddy/scripts/harness_preflight.py +50 -29
- package/harness/bundles/general/codex/.harness-build.json +1 -1
- package/harness/bundles/general/codex/README.md +2 -2
- package/harness/bundles/general/codex/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-archive/templates/render-summary.mjs +6 -4
- package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-plan/SKILL.md +7 -5
- package/harness/bundles/general/codex/harness-plan/checklist.md +12 -17
- package/harness/bundles/general/codex/harness-plan/reference.md +7 -19
- package/harness/bundles/general/codex/harness-review/SKILL.md +2 -2
- package/harness/bundles/general/codex/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-run/protocols.md +3 -3
- package/harness/bundles/general/codex/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-submit/checklist.md +8 -2
- package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/codex/scripts/harness_archive.py +124 -17
- package/harness/bundles/general/codex/scripts/harness_deploy.py +22 -3
- package/harness/bundles/general/codex/scripts/harness_integration.py +77 -2
- package/harness/bundles/general/codex/scripts/harness_preflight.py +50 -29
- package/harness/bundles/general/cursor/.harness-build.json +1 -1
- package/harness/bundles/general/cursor/README.md +2 -2
- package/harness/bundles/general/cursor/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-archive/templates/render-summary.mjs +6 -4
- package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-plan/SKILL.md +7 -5
- package/harness/bundles/general/cursor/harness-plan/checklist.md +12 -17
- package/harness/bundles/general/cursor/harness-plan/reference.md +7 -19
- package/harness/bundles/general/cursor/harness-review/SKILL.md +2 -2
- package/harness/bundles/general/cursor/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-run/protocols.md +3 -3
- package/harness/bundles/general/cursor/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-submit/checklist.md +8 -2
- package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/cursor/scripts/harness_archive.py +124 -17
- package/harness/bundles/general/cursor/scripts/harness_deploy.py +22 -3
- package/harness/bundles/general/cursor/scripts/harness_integration.py +77 -2
- package/harness/bundles/general/cursor/scripts/harness_preflight.py +50 -29
- package/harness/bundles/java/claude-code/.harness-build.json +1 -1
- package/harness/bundles/java/claude-code/README.md +2 -2
- package/harness/bundles/java/claude-code/agents/harness-evaluator.md +4 -4
- package/harness/bundles/java/claude-code/agents/harness-explorer.md +2 -2
- package/harness/bundles/java/claude-code/agents/harness-reviewer.md +2 -2
- package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-archive/templates/render-summary.mjs +6 -4
- package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-plan/SKILL.md +7 -4
- package/harness/bundles/java/claude-code/harness-plan/checklist.md +12 -17
- package/harness/bundles/java/claude-code/harness-plan/reference.md +7 -19
- package/harness/bundles/java/claude-code/harness-review/SKILL.md +3 -3
- package/harness/bundles/java/claude-code/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-run/protocols.md +3 -3
- package/harness/bundles/java/claude-code/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-submit/checklist.md +8 -2
- package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/scripts/harness_archive.py +124 -17
- package/harness/bundles/java/claude-code/scripts/harness_deploy.py +22 -3
- package/harness/bundles/java/claude-code/scripts/harness_integration.py +77 -2
- package/harness/bundles/java/claude-code/scripts/harness_preflight.py +50 -29
- package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/java/codebuddy/README.md +2 -2
- package/harness/bundles/java/codebuddy/agents/harness-evaluator.md +5 -6
- package/harness/bundles/java/codebuddy/agents/harness-explorer.md +3 -3
- package/harness/bundles/java/codebuddy/agents/harness-reviewer.md +3 -5
- package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-archive/templates/render-summary.mjs +6 -4
- package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +7 -4
- package/harness/bundles/java/codebuddy/harness-plan/checklist.md +12 -17
- package/harness/bundles/java/codebuddy/harness-plan/reference.md +7 -19
- package/harness/bundles/java/codebuddy/harness-review/SKILL.md +3 -3
- package/harness/bundles/java/codebuddy/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-run/protocols.md +3 -3
- package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-submit/checklist.md +8 -2
- package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/scripts/harness_archive.py +124 -17
- package/harness/bundles/java/codebuddy/scripts/harness_deploy.py +22 -3
- package/harness/bundles/java/codebuddy/scripts/harness_integration.py +77 -2
- package/harness/bundles/java/codebuddy/scripts/harness_preflight.py +50 -29
- package/harness/bundles/java/codex/.harness-build.json +1 -1
- package/harness/bundles/java/codex/README.md +2 -2
- package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-archive/templates/render-summary.mjs +6 -4
- package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-plan/SKILL.md +7 -5
- package/harness/bundles/java/codex/harness-plan/checklist.md +12 -17
- package/harness/bundles/java/codex/harness-plan/reference.md +7 -19
- package/harness/bundles/java/codex/harness-review/SKILL.md +2 -2
- package/harness/bundles/java/codex/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-run/protocols.md +3 -3
- package/harness/bundles/java/codex/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-submit/checklist.md +8 -2
- package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/codex/scripts/harness_archive.py +124 -17
- package/harness/bundles/java/codex/scripts/harness_deploy.py +22 -3
- package/harness/bundles/java/codex/scripts/harness_integration.py +77 -2
- package/harness/bundles/java/codex/scripts/harness_preflight.py +50 -29
- package/harness/bundles/java/cursor/.harness-build.json +1 -1
- package/harness/bundles/java/cursor/README.md +2 -2
- package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-archive/templates/render-summary.mjs +6 -4
- package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-plan/SKILL.md +7 -5
- package/harness/bundles/java/cursor/harness-plan/checklist.md +12 -17
- package/harness/bundles/java/cursor/harness-plan/reference.md +7 -19
- package/harness/bundles/java/cursor/harness-review/SKILL.md +2 -2
- package/harness/bundles/java/cursor/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-run/protocols.md +3 -3
- package/harness/bundles/java/cursor/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-submit/checklist.md +8 -2
- package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/cursor/scripts/harness_archive.py +124 -17
- package/harness/bundles/java/cursor/scripts/harness_deploy.py +22 -3
- package/harness/bundles/java/cursor/scripts/harness_integration.py +77 -2
- package/harness/bundles/java/cursor/scripts/harness_preflight.py +50 -29
- package/harness/manifests/general/claude-code.json +25 -25
- package/harness/manifests/general/codebuddy.json +25 -25
- package/harness/manifests/general/codex.json +22 -22
- package/harness/manifests/general/cursor.json +22 -22
- package/harness/manifests/java/claude-code.json +27 -27
- package/harness/manifests/java/codebuddy.json +27 -27
- package/harness/manifests/java/codex.json +24 -24
- package/harness/manifests/java/cursor.json +24 -24
- package/hunter-workflow-family.json +2 -2
- package/package.json +1 -1
|
@@ -900,10 +900,22 @@ def resolve_product_archive_identity(
|
|
|
900
900
|
or ledger.get("finalCommit")
|
|
901
901
|
or ""
|
|
902
902
|
)
|
|
903
|
+
feature_merge = str(
|
|
904
|
+
ledger.get("featureMergeHash")
|
|
905
|
+
or ledger.get("mergeFinalHash")
|
|
906
|
+
or product
|
|
907
|
+
).strip()
|
|
908
|
+
code, current_head, _ = git_run(project_root, "rev-parse", "HEAD")
|
|
909
|
+
release_tip = str(
|
|
910
|
+
ledger.get("releaseTipHash")
|
|
911
|
+
or (current_head if code == 0 else "")
|
|
912
|
+
or ledger.get("archiveCommit")
|
|
913
|
+
or product
|
|
914
|
+
).strip()
|
|
903
915
|
archive = (
|
|
904
916
|
archive_commit
|
|
905
917
|
or ledger.get("archiveCommit")
|
|
906
|
-
or
|
|
918
|
+
or release_tip
|
|
907
919
|
or product
|
|
908
920
|
)
|
|
909
921
|
tree = str(ledger.get("productTreeHash") or "")
|
|
@@ -930,6 +942,8 @@ def resolve_product_archive_identity(
|
|
|
930
942
|
tree = hashlib.sha256(b"fixture-no-product-tree").hexdigest()
|
|
931
943
|
identity = {
|
|
932
944
|
"productCommit": str(product),
|
|
945
|
+
"featureMergeHash": feature_merge,
|
|
946
|
+
"releaseTipHash": release_tip,
|
|
933
947
|
"productTreeHash": str(tree),
|
|
934
948
|
"archiveCommit": str(archive),
|
|
935
949
|
"productTreeHashTruncated": bool(tree_meta.get("truncated")),
|
|
@@ -1659,6 +1673,7 @@ def _ledger_unit_tests(ledger: dict[str, Any] | None) -> dict[str, Any]:
|
|
|
1659
1673
|
|
|
1660
1674
|
run = failures = errors = skipped = 0
|
|
1661
1675
|
passed_count: int | None = None
|
|
1676
|
+
deselected = 0
|
|
1662
1677
|
pass_rate: Any = None
|
|
1663
1678
|
source = "committed"
|
|
1664
1679
|
counted = False
|
|
@@ -1675,6 +1690,7 @@ def _ledger_unit_tests(ledger: dict[str, Any] | None) -> dict[str, Any]:
|
|
|
1675
1690
|
"failures",
|
|
1676
1691
|
"errors",
|
|
1677
1692
|
"skipped",
|
|
1693
|
+
"deselected",
|
|
1678
1694
|
)
|
|
1679
1695
|
):
|
|
1680
1696
|
if "total" in metrics:
|
|
@@ -1690,6 +1706,7 @@ def _ledger_unit_tests(ledger: dict[str, Any] | None) -> dict[str, Any]:
|
|
|
1690
1706
|
failures = int(metrics.get("failed", metrics.get("failures", 0)) or 0)
|
|
1691
1707
|
errors = int(metrics.get("errors", 0) or 0)
|
|
1692
1708
|
skipped = int(metrics.get("skipped", 0) or 0)
|
|
1709
|
+
deselected = int(metrics.get("deselected", 0) or 0)
|
|
1693
1710
|
if "passed" in metrics:
|
|
1694
1711
|
passed_count = int(metrics.get("passed", 0) or 0)
|
|
1695
1712
|
pass_rate = metrics.get("passRate")
|
|
@@ -1708,6 +1725,7 @@ def _ledger_unit_tests(ledger: dict[str, Any] | None) -> dict[str, Any]:
|
|
|
1708
1725
|
failures = int(evidence.get("failures", unit.get("failures", 0)) or 0)
|
|
1709
1726
|
errors = int(evidence.get("errors", unit.get("errors", 0)) or 0)
|
|
1710
1727
|
skipped = int(evidence.get("skipped", unit.get("skipped", 0)) or 0)
|
|
1728
|
+
deselected = int(evidence.get("deselected", unit.get("deselected", 0)) or 0)
|
|
1711
1729
|
pass_rate = evidence.get("passRate") or unit.get("passRate")
|
|
1712
1730
|
source = "committed"
|
|
1713
1731
|
counted = run > 0 or failures > 0 or errors > 0 or skipped > 0
|
|
@@ -1729,6 +1747,7 @@ def _ledger_unit_tests(ledger: dict[str, Any] | None) -> dict[str, Any]:
|
|
|
1729
1747
|
failures = int(unit.get("failures", 0) or 0)
|
|
1730
1748
|
errors = int(unit.get("errors", 0) or 0)
|
|
1731
1749
|
skipped = int(unit.get("skipped", 0) or 0)
|
|
1750
|
+
deselected = int(unit.get("deselected", 0) or 0)
|
|
1732
1751
|
pass_rate = unit.get("passRate")
|
|
1733
1752
|
source = "committed"
|
|
1734
1753
|
|
|
@@ -1755,6 +1774,7 @@ def _ledger_unit_tests(ledger: dict[str, Any] | None) -> dict[str, Any]:
|
|
|
1755
1774
|
"failures": int(failures or 0),
|
|
1756
1775
|
"errors": int(errors or 0),
|
|
1757
1776
|
"skipped": int(skipped or 0),
|
|
1777
|
+
"deselected": int(deselected or 0),
|
|
1758
1778
|
"passRate": pass_rate if pass_rate is not None else NOT_AVAILABLE,
|
|
1759
1779
|
"source": source,
|
|
1760
1780
|
}
|
|
@@ -1771,10 +1791,12 @@ def _ledger_api_tests(
|
|
|
1771
1791
|
empty = {
|
|
1772
1792
|
"status": "NOT_RUN",
|
|
1773
1793
|
"total": 0,
|
|
1794
|
+
"executed": 0,
|
|
1774
1795
|
"passed": 0,
|
|
1775
1796
|
"failed": 0,
|
|
1776
1797
|
"blocked": 0,
|
|
1777
1798
|
"passRate": NOT_AVAILABLE,
|
|
1799
|
+
"executionRate": NOT_AVAILABLE,
|
|
1778
1800
|
"source": "not-run",
|
|
1779
1801
|
}
|
|
1780
1802
|
if not ledger:
|
|
@@ -1853,18 +1875,19 @@ def _ledger_api_tests(
|
|
|
1853
1875
|
pass_rate = api.get("passRate")
|
|
1854
1876
|
source = "committed"
|
|
1855
1877
|
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
pass_rate = NOT_AVAILABLE
|
|
1878
|
+
executed = passed + failed
|
|
1879
|
+
pass_rate = f"{passed / executed:.0%}" if executed > 0 else NOT_AVAILABLE
|
|
1880
|
+
execution_rate = f"{executed / total:.0%}" if total > 0 else NOT_AVAILABLE
|
|
1860
1881
|
|
|
1861
1882
|
return {
|
|
1862
1883
|
"status": status,
|
|
1863
1884
|
"total": int(total or 0),
|
|
1885
|
+
"executed": int(executed or 0),
|
|
1864
1886
|
"passed": int(passed or 0),
|
|
1865
1887
|
"failed": int(failed or 0),
|
|
1866
1888
|
"blocked": int(blocked or 0),
|
|
1867
|
-
"passRate": pass_rate
|
|
1889
|
+
"passRate": pass_rate,
|
|
1890
|
+
"executionRate": execution_rate,
|
|
1868
1891
|
"source": source,
|
|
1869
1892
|
}
|
|
1870
1893
|
|
|
@@ -1937,13 +1960,16 @@ def _typed_test_metrics(entry: dict[str, Any], *, total_key: str) -> dict[str, A
|
|
|
1937
1960
|
total = int(metrics.get(total_key, 0) or 0)
|
|
1938
1961
|
passed = int(metrics.get("passed", 0) or 0)
|
|
1939
1962
|
failed = int(metrics.get("failed", 0) or 0)
|
|
1963
|
+
executed = passed + failed
|
|
1940
1964
|
status = str(entry.get("status") or "").upper() or "NOT_RUN"
|
|
1941
1965
|
out: dict[str, Any] = {
|
|
1942
1966
|
"status": status,
|
|
1943
1967
|
"total": total,
|
|
1968
|
+
"executed": executed,
|
|
1944
1969
|
"passed": passed,
|
|
1945
1970
|
"failed": failed,
|
|
1946
|
-
"passRate": f"{passed /
|
|
1971
|
+
"passRate": f"{passed / executed:.0%}" if executed > 0 else NOT_AVAILABLE,
|
|
1972
|
+
"executionRate": f"{executed / total:.0%}" if total > 0 else NOT_AVAILABLE,
|
|
1947
1973
|
"source": "committed" if total > 0 else "not-run",
|
|
1948
1974
|
}
|
|
1949
1975
|
if "blocked" in metrics:
|
|
@@ -1980,6 +2006,17 @@ def build_verification_projection(
|
|
|
1980
2006
|
browser_e2e = validations.get("browserE2E")
|
|
1981
2007
|
if isinstance(browser_e2e, dict):
|
|
1982
2008
|
projection["browserE2E"] = _typed_test_metrics(browser_e2e, total_key="total")
|
|
2009
|
+
performance = validations.get("performance")
|
|
2010
|
+
if isinstance(performance, dict):
|
|
2011
|
+
projection["performance"] = {
|
|
2012
|
+
"status": str(performance.get("status") or "NOT_RUN").upper(),
|
|
2013
|
+
"metrics": _deepcopy_json(
|
|
2014
|
+
performance.get("metrics")
|
|
2015
|
+
if isinstance(performance.get("metrics"), dict)
|
|
2016
|
+
else {}
|
|
2017
|
+
),
|
|
2018
|
+
"source": "committed",
|
|
2019
|
+
}
|
|
1983
2020
|
return projection
|
|
1984
2021
|
|
|
1985
2022
|
|
|
@@ -2142,14 +2179,25 @@ def _verification_checks_from_events(
|
|
|
2142
2179
|
return checks
|
|
2143
2180
|
|
|
2144
2181
|
|
|
2145
|
-
def _artifacts_from_events(
|
|
2182
|
+
def _artifacts_from_events(
|
|
2183
|
+
events: list[dict[str, Any]],
|
|
2184
|
+
*,
|
|
2185
|
+
change_dir: Path | None = None,
|
|
2186
|
+
) -> list[dict[str, Any]]:
|
|
2146
2187
|
out: list[dict[str, Any]] = []
|
|
2147
2188
|
for e in events:
|
|
2148
2189
|
if e.get("type") != "artifact":
|
|
2149
2190
|
continue
|
|
2191
|
+
raw_path = str(e.get("path") or "")
|
|
2192
|
+
archived_path = raw_path
|
|
2193
|
+
if change_dir is not None and raw_path:
|
|
2194
|
+
product_copy = change_dir / "artifacts" / "product" / raw_path
|
|
2195
|
+
if not (change_dir / raw_path).is_file() and product_copy.is_file():
|
|
2196
|
+
archived_path = product_copy.relative_to(change_dir).as_posix()
|
|
2150
2197
|
out.append(
|
|
2151
2198
|
{
|
|
2152
|
-
"path":
|
|
2199
|
+
"path": archived_path,
|
|
2200
|
+
**({"sourcePath": raw_path} if archived_path != raw_path else {}),
|
|
2153
2201
|
"kind": e.get("kind") or "",
|
|
2154
2202
|
"phase": e.get("phase") or "",
|
|
2155
2203
|
}
|
|
@@ -2882,7 +2930,7 @@ def collect_summary_data(
|
|
|
2882
2930
|
"dbCompatibility": db,
|
|
2883
2931
|
"coverageDisplay": coverage,
|
|
2884
2932
|
}
|
|
2885
|
-
for typed_key in ("apiContract", "browserE2E"):
|
|
2933
|
+
for typed_key in ("apiContract", "browserE2E", "performance"):
|
|
2886
2934
|
if typed_key in projection:
|
|
2887
2935
|
data["verification"][typed_key] = projection[typed_key]
|
|
2888
2936
|
if ci_metrics is not None:
|
|
@@ -2982,6 +3030,8 @@ def collect_summary_data(
|
|
|
2982
3030
|
if not for_replay:
|
|
2983
3031
|
identity = resolve_product_archive_identity(change_dir, project=project)
|
|
2984
3032
|
data["productCommit"] = identity.get("productCommit") or data.get("finalCommit")
|
|
3033
|
+
data["featureMergeHash"] = identity.get("featureMergeHash") or data.get("finalCommit")
|
|
3034
|
+
data["releaseTipHash"] = identity.get("releaseTipHash") or data.get("archiveCommit")
|
|
2985
3035
|
data["productTreeHash"] = identity.get("productTreeHash")
|
|
2986
3036
|
data["archiveCommit"] = identity.get("archiveCommit") or data.get("finalCommit")
|
|
2987
3037
|
data["changeIdentity"] = identity
|
|
@@ -3049,6 +3099,8 @@ def collect_summary_data(
|
|
|
3049
3099
|
data["gitFacts"] = {
|
|
3050
3100
|
"baseCommit": data.get("baseCommit") or "",
|
|
3051
3101
|
"finalCommit": data.get("finalCommit") or "",
|
|
3102
|
+
"featureMergeHash": data.get("featureMergeHash") or "",
|
|
3103
|
+
"releaseTipHash": data.get("releaseTipHash") or "",
|
|
3052
3104
|
"filesChanged": int(diff_for_facts.get("filesChanged") or 0),
|
|
3053
3105
|
"insertions": int(diff_for_facts.get("insertions") or 0),
|
|
3054
3106
|
"deletions": int(diff_for_facts.get("deletions") or 0),
|
|
@@ -3069,7 +3121,9 @@ def collect_summary_data(
|
|
|
3069
3121
|
if not isinstance(data.get("artifacts"), list):
|
|
3070
3122
|
data["artifacts"] = []
|
|
3071
3123
|
if not for_replay:
|
|
3072
|
-
data["artifacts"] = _artifacts_from_events(
|
|
3124
|
+
data["artifacts"] = _artifacts_from_events(
|
|
3125
|
+
projected_events, change_dir=change_dir
|
|
3126
|
+
)
|
|
3073
3127
|
|
|
3074
3128
|
data["reviewSummary"] = _review_summary(
|
|
3075
3129
|
change_dir,
|
|
@@ -3151,7 +3205,9 @@ def collect_summary_data(
|
|
|
3151
3205
|
# Cannot invent commands
|
|
3152
3206
|
pass
|
|
3153
3207
|
verification_checks = _verification_checks_from_events(projected_events, ledger)
|
|
3154
|
-
pipeline_artifacts = _artifacts_from_events(
|
|
3208
|
+
pipeline_artifacts = _artifacts_from_events(
|
|
3209
|
+
projected_events, change_dir=change_dir
|
|
3210
|
+
)
|
|
3155
3211
|
if not sources:
|
|
3156
3212
|
sources = [NOT_AVAILABLE]
|
|
3157
3213
|
|
|
@@ -4012,6 +4068,7 @@ def artifact_preflight(change_dir: Path) -> dict[str, Any]:
|
|
|
4012
4068
|
if not events_p.is_file():
|
|
4013
4069
|
return {"ok": True, "items": [], "blocking": []}
|
|
4014
4070
|
change_id = change_dir.name
|
|
4071
|
+
project_root = find_project_root(change_dir)
|
|
4015
4072
|
for line in events_p.read_text(encoding="utf-8").splitlines():
|
|
4016
4073
|
try:
|
|
4017
4074
|
event = json.loads(line)
|
|
@@ -4058,16 +4115,56 @@ def artifact_preflight(change_dir: Path) -> dict[str, Any]:
|
|
|
4058
4115
|
"correction": f"append correction --target-event-id {event_id} --target-field path --new-value-json \"{canonical}\"",
|
|
4059
4116
|
})
|
|
4060
4117
|
continue
|
|
4061
|
-
|
|
4062
|
-
|
|
4118
|
+
change_file = change_dir / path
|
|
4119
|
+
if change_file.is_file():
|
|
4120
|
+
items.append({
|
|
4121
|
+
"eventId": event_id,
|
|
4122
|
+
"category": "file-backed",
|
|
4123
|
+
"path": path,
|
|
4124
|
+
"exists": True,
|
|
4125
|
+
})
|
|
4126
|
+
continue
|
|
4127
|
+
repository_file = project_root / path
|
|
4128
|
+
if repository_file.is_file():
|
|
4129
|
+
items.append({
|
|
4130
|
+
"eventId": event_id,
|
|
4131
|
+
"category": "repository-file",
|
|
4132
|
+
"path": path,
|
|
4133
|
+
"archivePath": f"artifacts/product/{path}",
|
|
4134
|
+
"exists": True,
|
|
4135
|
+
})
|
|
4136
|
+
continue
|
|
4137
|
+
item = {
|
|
4063
4138
|
"eventId": event_id,
|
|
4064
|
-
"category": "
|
|
4139
|
+
"category": "blocking",
|
|
4065
4140
|
"path": path,
|
|
4066
|
-
"
|
|
4067
|
-
|
|
4141
|
+
"kind": kind,
|
|
4142
|
+
"reason": "artifact file not found in change or project root",
|
|
4143
|
+
}
|
|
4144
|
+
items.append(item)
|
|
4145
|
+
blocking.append(item)
|
|
4068
4146
|
return {"ok": not blocking, "items": items, "blocking": blocking}
|
|
4069
4147
|
|
|
4070
4148
|
|
|
4149
|
+
def materialize_repository_artifacts(change_dir: Path) -> dict[str, Any]:
|
|
4150
|
+
"""Copy project-root business artifacts into the immutable archive namespace."""
|
|
4151
|
+
change_dir = change_dir.resolve()
|
|
4152
|
+
project_root = find_project_root(change_dir)
|
|
4153
|
+
preflight = artifact_preflight(change_dir)
|
|
4154
|
+
copied: list[str] = []
|
|
4155
|
+
for item in preflight.get("items") or []:
|
|
4156
|
+
if item.get("category") != "repository-file":
|
|
4157
|
+
continue
|
|
4158
|
+
source = (project_root / str(item["path"])).resolve()
|
|
4159
|
+
target = (change_dir / str(item["archivePath"])).resolve()
|
|
4160
|
+
if not source.is_relative_to(project_root) or not target.is_relative_to(change_dir):
|
|
4161
|
+
raise ValueError(f"artifact materialization escaped boundary: {item['path']}")
|
|
4162
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
4163
|
+
shutil.copy2(source, target)
|
|
4164
|
+
copied.append(target.relative_to(change_dir).as_posix())
|
|
4165
|
+
return {"ok": True, "copied": sorted(copied)}
|
|
4166
|
+
|
|
4167
|
+
|
|
4071
4168
|
def run_knowledge_poststeps(project_root: Path) -> dict[str, Any]:
|
|
4072
4169
|
results: dict[str, Any] = {"ran": False, "steps": [], "warnings": []}
|
|
4073
4170
|
if not KNOWLEDGE_SCRIPT.is_file():
|
|
@@ -4343,6 +4440,16 @@ def cmd_finalize(
|
|
|
4343
4440
|
warnings.append(f"cleanup failed: {exc}")
|
|
4344
4441
|
payload["steps"]["cleanup"] = {"ok": False, "error": str(exc)}
|
|
4345
4442
|
|
|
4443
|
+
# Product/business artifacts may live at repository-relative paths. Freeze
|
|
4444
|
+
# byte-for-byte copies inside the staged archive before either manifest.
|
|
4445
|
+
try:
|
|
4446
|
+
materialized = materialize_repository_artifacts(work_dir)
|
|
4447
|
+
payload["steps"]["artifact_materialization"] = materialized
|
|
4448
|
+
except (OSError, ValueError) as exc:
|
|
4449
|
+
payload["error"] = f"artifact materialization failed: {exc}"
|
|
4450
|
+
_restore_finalize_failure()
|
|
4451
|
+
return 1, payload
|
|
4452
|
+
|
|
4346
4453
|
# --- 1. before-manifest ---
|
|
4347
4454
|
before_path = work_dir / "evidence" / "archive-manifest-before.json"
|
|
4348
4455
|
try:
|
|
@@ -16,6 +16,7 @@ import re
|
|
|
16
16
|
import shutil
|
|
17
17
|
import subprocess
|
|
18
18
|
import sys
|
|
19
|
+
import time
|
|
19
20
|
import uuid
|
|
20
21
|
from pathlib import Path
|
|
21
22
|
from typing import Any, Iterable
|
|
@@ -38,6 +39,7 @@ HEADING_RE = re.compile(r"^(#{2,6})\s+(.+?)\s*$")
|
|
|
38
39
|
FRAGMENT_HINT_RE = re.compile(r"^>\s*片段:\[\[shared/[^\]]+\]\]\s*.*$")
|
|
39
40
|
|
|
40
41
|
BUILD_MARKER = ".harness-build.json"
|
|
42
|
+
_REPLACE_RETRY_DELAYS_SECONDS = (0.05, 0.1, 0.2, 0.4, 0.8)
|
|
41
43
|
# Install-side managed manifests record which files a prior install owned, so a
|
|
42
44
|
# later conservative install can delete removed managed files while preserving
|
|
43
45
|
# user-owned files (design §3.8 要点4). Skills manifest lives in the skills
|
|
@@ -76,6 +78,23 @@ def now_iso() -> str:
|
|
|
76
78
|
return dt.datetime.now().astimezone().isoformat(timespec="seconds")
|
|
77
79
|
|
|
78
80
|
|
|
81
|
+
def replace_with_retry(source: Path, target: Path) -> None:
|
|
82
|
+
"""Bounded retry for transient directory locks during atomic swaps.
|
|
83
|
+
|
|
84
|
+
Windows indexers and antivirus scanners may briefly hold a newly generated
|
|
85
|
+
bundle directory. Retrying only ``PermissionError`` keeps real path and
|
|
86
|
+
contract failures fail-fast while avoiding an expensive full bundle rerun.
|
|
87
|
+
"""
|
|
88
|
+
for attempt in range(len(_REPLACE_RETRY_DELAYS_SECONDS) + 1):
|
|
89
|
+
try:
|
|
90
|
+
os.replace(source, target)
|
|
91
|
+
return
|
|
92
|
+
except PermissionError:
|
|
93
|
+
if attempt >= len(_REPLACE_RETRY_DELAYS_SECONDS):
|
|
94
|
+
raise
|
|
95
|
+
time.sleep(_REPLACE_RETRY_DELAYS_SECONDS[attempt])
|
|
96
|
+
|
|
97
|
+
|
|
79
98
|
def emit_json(payload: dict[str, Any], *, ok: bool = True) -> int:
|
|
80
99
|
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
|
81
100
|
return 0 if ok else 1
|
|
@@ -596,15 +615,15 @@ def cmd_build(
|
|
|
596
615
|
if out_dir.exists():
|
|
597
616
|
if (out_dir / BUILD_MARKER).is_file():
|
|
598
617
|
old_backup = out_dir.parent / f".{out_dir.name}.old-{uuid.uuid4().hex[:8]}"
|
|
599
|
-
|
|
618
|
+
replace_with_retry(out_dir, old_backup)
|
|
600
619
|
else:
|
|
601
620
|
shutil.rmtree(staging, ignore_errors=True)
|
|
602
621
|
raise ValueError(f"refusing to overwrite unmarked out_dir: {out_dir}")
|
|
603
622
|
try:
|
|
604
|
-
|
|
623
|
+
replace_with_retry(staging, out_dir)
|
|
605
624
|
except OSError:
|
|
606
625
|
if old_backup is not None and old_backup.exists():
|
|
607
|
-
|
|
626
|
+
replace_with_retry(old_backup, out_dir)
|
|
608
627
|
raise
|
|
609
628
|
if old_backup is not None:
|
|
610
629
|
shutil.rmtree(old_backup, ignore_errors=True)
|
|
@@ -257,6 +257,42 @@ def journal_path(project_root: Path, transaction_id: str) -> Path:
|
|
|
257
257
|
return journal_dir(project_root, transaction_id) / "journal.json"
|
|
258
258
|
|
|
259
259
|
|
|
260
|
+
def _write_verify_log(
|
|
261
|
+
project_root: Path,
|
|
262
|
+
transaction_id: str,
|
|
263
|
+
index: int,
|
|
264
|
+
*,
|
|
265
|
+
command: Sequence[str],
|
|
266
|
+
resolved_command: Sequence[str],
|
|
267
|
+
started: dt.datetime,
|
|
268
|
+
finished: dt.datetime,
|
|
269
|
+
exit_code: int | None,
|
|
270
|
+
stdout: str | bytes | None = None,
|
|
271
|
+
stderr: str | bytes | None = None,
|
|
272
|
+
error: str | None = None,
|
|
273
|
+
) -> str:
|
|
274
|
+
def as_text(value: str | bytes | None) -> str:
|
|
275
|
+
if isinstance(value, bytes):
|
|
276
|
+
return value.decode("utf-8", errors="replace")
|
|
277
|
+
return value or ""
|
|
278
|
+
|
|
279
|
+
path = journal_dir(project_root, transaction_id) / "logs" / f"verify-{index + 1}.log"
|
|
280
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
281
|
+
lines = [
|
|
282
|
+
f"command: {json.dumps(list(command), ensure_ascii=False)}",
|
|
283
|
+
f"resolvedCommand: {json.dumps(list(resolved_command), ensure_ascii=False)}",
|
|
284
|
+
f"startedAt: {started.isoformat(timespec='milliseconds')}",
|
|
285
|
+
f"finishedAt: {finished.isoformat(timespec='milliseconds')}",
|
|
286
|
+
f"durationMs: {int((finished - started).total_seconds() * 1000)}",
|
|
287
|
+
f"exitCode: {exit_code if exit_code is not None else 'null'}",
|
|
288
|
+
]
|
|
289
|
+
if error:
|
|
290
|
+
lines.append(f"error: {error}")
|
|
291
|
+
lines.extend(["--- stdout ---", as_text(stdout), "--- stderr ---", as_text(stderr)])
|
|
292
|
+
path.write_text("\n".join(lines) + "\n", encoding="utf-8", newline="\n")
|
|
293
|
+
return path.relative_to(project_root).as_posix()
|
|
294
|
+
|
|
295
|
+
|
|
260
296
|
def load_journal(project_root: Path, transaction_id: str) -> dict[str, Any]:
|
|
261
297
|
path = journal_path(project_root, transaction_id)
|
|
262
298
|
if not path.is_file():
|
|
@@ -965,7 +1001,7 @@ class IntegrationTransaction:
|
|
|
965
1001
|
)
|
|
966
1002
|
intg_root = Path(journal["integrationRoot"]).resolve()
|
|
967
1003
|
results: list[dict[str, Any]] = []
|
|
968
|
-
for command in commands:
|
|
1004
|
+
for index, command in enumerate(commands):
|
|
969
1005
|
executable_command = _normalize_verify_command(command)
|
|
970
1006
|
started = dt.datetime.now().astimezone()
|
|
971
1007
|
try:
|
|
@@ -979,8 +1015,21 @@ class IntegrationTransaction:
|
|
|
979
1015
|
check=False,
|
|
980
1016
|
timeout=timeout_seconds,
|
|
981
1017
|
)
|
|
982
|
-
except subprocess.TimeoutExpired:
|
|
1018
|
+
except subprocess.TimeoutExpired as exc:
|
|
983
1019
|
finished = dt.datetime.now().astimezone()
|
|
1020
|
+
log_path = _write_verify_log(
|
|
1021
|
+
self.project_root,
|
|
1022
|
+
self.transaction_id,
|
|
1023
|
+
index,
|
|
1024
|
+
command=command,
|
|
1025
|
+
resolved_command=executable_command,
|
|
1026
|
+
started=started,
|
|
1027
|
+
finished=finished,
|
|
1028
|
+
exit_code=None,
|
|
1029
|
+
stdout=exc.stdout,
|
|
1030
|
+
stderr=exc.stderr,
|
|
1031
|
+
error=f"timed out after {timeout_seconds}s",
|
|
1032
|
+
)
|
|
984
1033
|
results.append(
|
|
985
1034
|
{
|
|
986
1035
|
"command": list(command),
|
|
@@ -993,6 +1042,7 @@ class IntegrationTransaction:
|
|
|
993
1042
|
"durationMs": int(
|
|
994
1043
|
(finished - started).total_seconds() * 1000
|
|
995
1044
|
),
|
|
1045
|
+
"logPath": log_path,
|
|
996
1046
|
}
|
|
997
1047
|
)
|
|
998
1048
|
journal["verifyResults"] = results
|
|
@@ -1002,6 +1052,17 @@ class IntegrationTransaction:
|
|
|
1002
1052
|
)
|
|
1003
1053
|
except FileNotFoundError as exc:
|
|
1004
1054
|
finished = dt.datetime.now().astimezone()
|
|
1055
|
+
log_path = _write_verify_log(
|
|
1056
|
+
self.project_root,
|
|
1057
|
+
self.transaction_id,
|
|
1058
|
+
index,
|
|
1059
|
+
command=command,
|
|
1060
|
+
resolved_command=executable_command,
|
|
1061
|
+
started=started,
|
|
1062
|
+
finished=finished,
|
|
1063
|
+
exit_code=None,
|
|
1064
|
+
error=str(exc),
|
|
1065
|
+
)
|
|
1005
1066
|
results.append(
|
|
1006
1067
|
{
|
|
1007
1068
|
"command": list(command),
|
|
@@ -1013,6 +1074,7 @@ class IntegrationTransaction:
|
|
|
1013
1074
|
"durationMs": int(
|
|
1014
1075
|
(finished - started).total_seconds() * 1000
|
|
1015
1076
|
),
|
|
1077
|
+
"logPath": log_path,
|
|
1016
1078
|
}
|
|
1017
1079
|
)
|
|
1018
1080
|
journal["verifyResults"] = results
|
|
@@ -1021,6 +1083,18 @@ class IntegrationTransaction:
|
|
|
1021
1083
|
f"verification executable not found: {executable_command[0]}"
|
|
1022
1084
|
) from exc
|
|
1023
1085
|
finished = dt.datetime.now().astimezone()
|
|
1086
|
+
log_path = _write_verify_log(
|
|
1087
|
+
self.project_root,
|
|
1088
|
+
self.transaction_id,
|
|
1089
|
+
index,
|
|
1090
|
+
command=command,
|
|
1091
|
+
resolved_command=executable_command,
|
|
1092
|
+
started=started,
|
|
1093
|
+
finished=finished,
|
|
1094
|
+
exit_code=proc.returncode,
|
|
1095
|
+
stdout=proc.stdout,
|
|
1096
|
+
stderr=proc.stderr,
|
|
1097
|
+
)
|
|
1024
1098
|
results.append(
|
|
1025
1099
|
{
|
|
1026
1100
|
"command": list(command),
|
|
@@ -1032,6 +1106,7 @@ class IntegrationTransaction:
|
|
|
1032
1106
|
"durationMs": int(
|
|
1033
1107
|
(finished - started).total_seconds() * 1000
|
|
1034
1108
|
),
|
|
1109
|
+
"logPath": log_path,
|
|
1035
1110
|
}
|
|
1036
1111
|
)
|
|
1037
1112
|
if proc.returncode != 0:
|
|
@@ -374,12 +374,14 @@ def _resolve_agents_root(skills_root: Path, agents_root: Path | None) -> tuple[P
|
|
|
374
374
|
return (skills_root / "agents").resolve(), None
|
|
375
375
|
|
|
376
376
|
|
|
377
|
-
def _read_host_capabilities(skills_root: Path) -> set[str]:
|
|
377
|
+
def _read_host_capabilities(skills_root: Path) -> tuple[set[str], bool]:
|
|
378
378
|
"""Read agent capabilities declared by the host adapter (retro §5.3).
|
|
379
379
|
|
|
380
380
|
Adapter installs may declare available agent roles in
|
|
381
381
|
`meta/runtime.json` under `agentCapabilities` (list of role names).
|
|
382
|
-
Returns
|
|
382
|
+
Returns ``(capabilities, manifest_present)``. ``manifest_present`` is true
|
|
383
|
+
only when a readable capability list exists; an unrelated runtime.json
|
|
384
|
+
must not turn an unknown host into a hard "agent missing" failure.
|
|
383
385
|
"""
|
|
384
386
|
# runtime.json lives in the change's meta/ dir, but agent capabilities are
|
|
385
387
|
# project-level. Check the adapter's runtime.json at the project root.
|
|
@@ -398,8 +400,8 @@ def _read_host_capabilities(skills_root: Path) -> set[str]:
|
|
|
398
400
|
continue
|
|
399
401
|
caps = data.get("agentCapabilities")
|
|
400
402
|
if isinstance(caps, list):
|
|
401
|
-
return {str(c) for c in caps if isinstance(c, str)}
|
|
402
|
-
return set()
|
|
403
|
+
return {str(c) for c in caps if isinstance(c, str)}, True
|
|
404
|
+
return set(), False
|
|
403
405
|
|
|
404
406
|
|
|
405
407
|
def cmd_check_agents(
|
|
@@ -420,7 +422,7 @@ def cmd_check_agents(
|
|
|
420
422
|
}
|
|
421
423
|
|
|
422
424
|
resolved_agents_root, _ = _resolve_agents_root(skills_root, agents_root)
|
|
423
|
-
host_capabilities = _read_host_capabilities(skills_root)
|
|
425
|
+
host_capabilities, capability_manifest_present = _read_host_capabilities(skills_root)
|
|
424
426
|
host_callable = agent_name in host_capabilities
|
|
425
427
|
|
|
426
428
|
# Three independent fields (retro §5.3):
|
|
@@ -437,32 +439,37 @@ def cmd_check_agents(
|
|
|
437
439
|
agent_path = resolved_agents_root / filename
|
|
438
440
|
definition_present = agent_path.is_file()
|
|
439
441
|
|
|
440
|
-
#
|
|
441
|
-
#
|
|
442
|
+
# Codex/Cursor bundles intentionally do not install fixed harness-* agent
|
|
443
|
+
# definitions. Missing host capabilities therefore select the normal
|
|
444
|
+
# inline path instead of reporting UNKNOWN/"subagent unavailable".
|
|
442
445
|
if not definition_present and not host_callable:
|
|
443
|
-
# Check if this adapter is known to not support custom agents
|
|
444
446
|
adapter_name = skills_root.parent.name
|
|
445
447
|
if adapter_name in (".agents", ".cursor"):
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
448
|
+
reason_code = (
|
|
449
|
+
"HOST_CAPABILITY_NOT_DECLARED"
|
|
450
|
+
if capability_manifest_present
|
|
451
|
+
else "INLINE_BY_ADAPTER"
|
|
452
|
+
)
|
|
453
|
+
return {
|
|
454
|
+
"ok": True,
|
|
455
|
+
"action": "check-agents",
|
|
456
|
+
"agent": agent_name,
|
|
457
|
+
"usable": False,
|
|
458
|
+
"executionMode": "inline",
|
|
459
|
+
"fallbackPolicy": "inline-no-retry",
|
|
460
|
+
"definitionPresent": False,
|
|
461
|
+
"hostCallable": False,
|
|
462
|
+
"toolContractValid": False,
|
|
463
|
+
"capabilityManifestPresent": capability_manifest_present,
|
|
464
|
+
"reasonCode": reason_code,
|
|
465
|
+
"reason": (
|
|
466
|
+
"host capability manifest does not declare this fixed agent; "
|
|
467
|
+
"use the normal inline path"
|
|
468
|
+
if capability_manifest_present
|
|
469
|
+
else "adapter uses inline execution unless the host explicitly "
|
|
470
|
+
"declares a callable agent capability"
|
|
471
|
+
),
|
|
472
|
+
}
|
|
466
473
|
return {
|
|
467
474
|
"ok": False,
|
|
468
475
|
"action": "check-agents",
|
|
@@ -470,9 +477,12 @@ def cmd_check_agents(
|
|
|
470
477
|
"path": str(agent_path) if agent_path else None,
|
|
471
478
|
"agentsRoot": str(resolved_agents_root) if resolved_agents_root else None,
|
|
472
479
|
"usable": False,
|
|
480
|
+
"executionMode": "unavailable",
|
|
481
|
+
"fallbackPolicy": "inline-no-retry",
|
|
473
482
|
"definitionPresent": False,
|
|
474
483
|
"hostCallable": False,
|
|
475
484
|
"toolContractValid": False,
|
|
485
|
+
"capabilityManifestPresent": capability_manifest_present,
|
|
476
486
|
"reasonCode": "AGENT_DEFINITION_NOT_FOUND",
|
|
477
487
|
"reason": f"agent file not found and host does not declare role: {agent_name}",
|
|
478
488
|
}
|
|
@@ -488,9 +498,13 @@ def cmd_check_agents(
|
|
|
488
498
|
"agent": agent_name,
|
|
489
499
|
"path": str(agent_path),
|
|
490
500
|
"usable": False,
|
|
501
|
+
"executionMode": "unavailable",
|
|
502
|
+
"fallbackPolicy": "inline-no-retry",
|
|
491
503
|
"definitionPresent": True,
|
|
492
504
|
"hostCallable": host_callable,
|
|
493
505
|
"toolContractValid": False,
|
|
506
|
+
"capabilityManifestPresent": capability_manifest_present,
|
|
507
|
+
"reasonCode": "AGENT_DEFINITION_UNREADABLE",
|
|
494
508
|
"reason": f"cannot read agent file: {exc}",
|
|
495
509
|
}
|
|
496
510
|
|
|
@@ -502,9 +516,13 @@ def cmd_check_agents(
|
|
|
502
516
|
"agent": agent_name,
|
|
503
517
|
"path": str(agent_path),
|
|
504
518
|
"usable": False,
|
|
519
|
+
"executionMode": "unavailable",
|
|
520
|
+
"fallbackPolicy": "inline-no-retry",
|
|
505
521
|
"definitionPresent": True,
|
|
506
522
|
"hostCallable": host_callable,
|
|
507
523
|
"toolContractValid": False,
|
|
524
|
+
"capabilityManifestPresent": capability_manifest_present,
|
|
525
|
+
"reasonCode": "AGENT_DEFINITION_INVALID",
|
|
508
526
|
"reason": f"frontmatter parse failed: {err or 'unknown'}",
|
|
509
527
|
}
|
|
510
528
|
|
|
@@ -525,7 +543,7 @@ def cmd_check_agents(
|
|
|
525
543
|
name_field = meta.get("name") if meta else None
|
|
526
544
|
tools_field = meta.get("tools") if meta else None
|
|
527
545
|
|
|
528
|
-
reason_code = "READY" if usable else "
|
|
546
|
+
reason_code = "READY" if usable else "TOOL_CONTRACT_INVALID"
|
|
529
547
|
if not definition_present and host_callable:
|
|
530
548
|
reason_code = "DEFINITION_NOT_FOUND_HOST_CAPABLE"
|
|
531
549
|
|
|
@@ -536,9 +554,12 @@ def cmd_check_agents(
|
|
|
536
554
|
"path": str(agent_path) if agent_path else None,
|
|
537
555
|
"agentsRoot": str(resolved_agents_root) if resolved_agents_root else None,
|
|
538
556
|
"usable": usable,
|
|
557
|
+
"executionMode": "delegated" if usable else "unavailable",
|
|
558
|
+
"fallbackPolicy": "inline-no-retry",
|
|
539
559
|
"definitionPresent": definition_present,
|
|
540
560
|
"hostCallable": host_callable,
|
|
541
561
|
"toolContractValid": tool_contract_valid,
|
|
562
|
+
"capabilityManifestPresent": capability_manifest_present,
|
|
542
563
|
"reasonCode": reason_code,
|
|
543
564
|
"reason": (
|
|
544
565
|
"agent available; "
|