vmware-debug 1.11.2__tar.gz → 1.11.3__tar.gz
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.
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/.claude-plugin/plugin.json +1 -1
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/.mcp.json +1 -1
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/PKG-INFO +2 -2
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/RELEASE_NOTES.md +36 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/pyproject.toml +2 -2
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/server.json +2 -2
- vmware_debug-1.11.3/tests/__init__.py +9 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/eval/capability/_scoring.py +2 -2
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/eval/regression/test_capability_grader.py +3 -3
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/eval/regression/test_environment_resolver_isolation.py +1 -1
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/eval/regression/test_investigation_metrics.py +1 -1
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/test_case_conclusion.py +5 -5
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/test_case_evidence.py +2 -2
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/test_case_grading.py +5 -5
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/test_case_hypotheses.py +1 -1
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/test_case_readiness.py +1 -1
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/test_case_store.py +5 -5
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/test_case_timeline_close.py +2 -2
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/test_knowledge.py +1 -1
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/uv.lock +5 -5
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/vmware_debug/__init__.py +1 -1
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/vmware_debug/cli.py +25 -1
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/vmware_debug/ops/cases/conclusion.py +5 -5
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/vmware_debug/ops/cases/evidence.py +4 -4
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/vmware_debug/ops/cases/hypotheses.py +3 -3
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/vmware_debug/ops/cases/knowledge.py +1 -1
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/vmware_debug/ops/cases/store.py +4 -4
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/vmware_debug/ops/cases/timeline.py +4 -4
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/.gitignore +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/README-CN.md +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/README.md +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/SECURITY.md +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/docs/plans/2026-08-28-investigation-layer-design.md +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/skills/vmware-debug/SKILL.md +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/skills/vmware-debug/references/agent-guardrails.md +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/skills/vmware-debug/references/capabilities.md +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/skills/vmware-debug/references/cli-reference.md +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/skills/vmware-debug/references/event-envelope.md +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/skills/vmware-debug/references/routing.md +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/skills/vmware-debug/references/setup-guide.md +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/eval/__init__.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/eval/capability/__init__.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/eval/capability/_family.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/eval/capability/_scores.json +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/eval/capability/_skill.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/eval/capability/conftest.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/eval/capability/test_entity_reachability.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/eval/capability/test_error_actionability.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/eval/capability/test_tool_description_quality.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/eval/capability/test_tool_manifest_budget.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/eval/regression/__init__.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/eval/regression/_vsphere_event_vocabulary.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/eval/regression/test_case_id_non_ascii.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/eval/regression/test_case_plan_max_steps.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/eval/regression/test_classification_against_vsphere_vocabulary.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/eval/regression/test_debug_regressions.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/eval/regression/test_every_tool_parameter_is_documented.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/eval/regression/test_evidence_payload_contract.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/eval/regression/test_host_lifecycle_classification.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/eval/regression/test_result_envelope.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/eval/regression/test_skill_name_aliases.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/eval/regression/test_spike_resolution.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/eval/regression/test_tool_annotations.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/test_case_ids.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/test_case_plan.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/test_case_tools.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/test_safe_error_passthrough.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/test_timeline.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/vmware_debug/envelope.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/vmware_debug/mcp/__init__.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/vmware_debug/mcp/tools.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/vmware_debug/mcp_server/__init__.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/vmware_debug/mcp_server/server.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/vmware_debug/ops/__init__.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/vmware_debug/ops/cases/__init__.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/vmware_debug/ops/cases/api.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/vmware_debug/ops/cases/grading.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/vmware_debug/ops/cases/ids.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/vmware_debug/ops/cases/model.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/vmware_debug/ops/cases/payloads.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/vmware_debug/ops/cases/plan.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/vmware_debug/ops/cases/readiness.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/vmware_debug/ops/cases/sources.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/vmware_debug/ops/skill_names.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/vmware_debug/ops/timeline.py +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/vmware_debug/rules/evidence_sources.yaml +0 -0
- {vmware_debug-1.11.2 → vmware_debug-1.11.3}/vmware_debug/rules/grading_rules.yaml +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://anthropic.com/claude-code/plugin.schema.json",
|
|
3
3
|
"name": "vmware-debug",
|
|
4
4
|
"displayName": "VMware Debug",
|
|
5
|
-
"version": "1.11.
|
|
5
|
+
"version": "1.11.3",
|
|
6
6
|
"description": "Read-only VMware incident correlation: timeline, spikes, root-cause routing. 2 MCP tools.",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Wei Zhou",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: vmware-debug
|
|
3
|
-
Version: 1.11.
|
|
3
|
+
Version: 1.11.3
|
|
4
4
|
Summary: VMware diagnostic brain — read-only incident triage, log/event correlation, and root-cause routing across the VMware skill family
|
|
5
5
|
Project-URL: Homepage, https://github.com/vmware-skills/VMware-Debug
|
|
6
6
|
Project-URL: Repository, https://github.com/vmware-skills/VMware-Debug
|
|
@@ -18,7 +18,7 @@ Requires-Dist: mcp[cli]<2.0,>=1.10
|
|
|
18
18
|
Requires-Dist: pyyaml<7.0,>=6.0
|
|
19
19
|
Requires-Dist: rich<15.0,>=13.0
|
|
20
20
|
Requires-Dist: typer<1.0,>=0.12
|
|
21
|
-
Requires-Dist: vmware-policy<2.0,>=1.
|
|
21
|
+
Requires-Dist: vmware-policy<2.0,>=1.12.0
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
23
|
|
|
24
24
|
<!-- mcp-name: io.github.vmware-skills/vmware-debug -->
|
|
@@ -1,3 +1,39 @@
|
|
|
1
|
+
## v1.11.3 — 398 tests never ran
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
**`tests/__init__.py` was missing, and the whole suite -- 398 tests -- failed at
|
|
5
|
+
collection.** `tests/eval/` and `tests/eval/regression/` were already packages,
|
|
6
|
+
so `from tests.eval.regression._vsphere_event_vocabulary import ...` resolved
|
|
7
|
+
only where the namespace-package fallback happened to work. It did not on
|
|
8
|
+
Windows. One file.
|
|
9
|
+
|
|
10
|
+
**The suite now runs on a cp936 machine.** Round 3 of the VCF 9 field testing ran
|
|
11
|
+
on Windows Server 2025 with locale cp936. Across the family four repos' suites --
|
|
12
|
+
1687 tests -- never executed at all, dying at collection reading our own UTF-8
|
|
13
|
+
sources, and 101 more failed the same way. Most of those were the tests that
|
|
14
|
+
verify the destructive-operation guardrails: the guardrails were fine, the tests
|
|
15
|
+
that check them could not open a file. On the UTF-8 CI every one of them was
|
|
16
|
+
green. A security test that cannot run is not a security test.
|
|
17
|
+
|
|
18
|
+
Every text read and write here names its encoding now, `tests/` included -- the
|
|
19
|
+
previous round fixed only the package, which is why this came back. A gate in
|
|
20
|
+
`family_smoke` scans both trees by AST, and the whole family's suites were re-run
|
|
21
|
+
under an ASCII locale to confirm: 15 of 15 green, from 1 of 15.
|
|
22
|
+
|
|
23
|
+
**`--help` no longer dies on a console that cannot encode it.** On any console
|
|
24
|
+
whose encoding cannot carry the characters in our own help text, `--help` exited
|
|
25
|
+
with a `UnicodeEncodeError` traceback -- unavailable exactly on the machines
|
|
26
|
+
where it is most needed. Four repos were affected; the handler is now relaxed in
|
|
27
|
+
all fifteen so a glyph degrades instead of killing the command.
|
|
28
|
+
|
|
29
|
+
**Unknown tool arguments are refused instead of dropped.** The schema declared
|
|
30
|
+
`additionalProperties: false` and the runtime accepted them anyway, so a filter
|
|
31
|
+
argument whose name a model guessed wrong returned the *unfiltered* result with
|
|
32
|
+
nothing to indicate anything had been discarded. Fixed in vmware-policy 1.12.0
|
|
33
|
+
and in force here.
|
|
34
|
+
|
|
35
|
+
Requires vmware-policy 1.12.0.
|
|
36
|
+
|
|
1
37
|
## v1.11.2 — the resolver hijack, the discarded event identifier, and a parameter that did nothing
|
|
2
38
|
|
|
3
39
|
Three findings from the 2026-08-30 real-hardware re-test.
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "vmware-debug"
|
|
7
|
-
version = "1.11.
|
|
7
|
+
version = "1.11.3"
|
|
8
8
|
description = "VMware diagnostic brain — read-only incident triage, log/event correlation, and root-cause routing across the VMware skill family"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -21,7 +21,7 @@ dependencies = [
|
|
|
21
21
|
"typer>=0.12,<1.0",
|
|
22
22
|
"rich>=13.0,<15.0",
|
|
23
23
|
"mcp[cli]>=1.10,<2.0",
|
|
24
|
-
"vmware-policy>=1.
|
|
24
|
+
"vmware-policy>=1.12.0,<2.0",
|
|
25
25
|
# Imported directly by ops/cases/grading.py for the rules file. It has
|
|
26
26
|
# always arrived transitively via vmware-policy; a package should
|
|
27
27
|
# declare what it imports rather than rely on a dependency's dependency.
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
"url": "https://github.com/vmware-skills/VMware-Debug",
|
|
8
8
|
"source": "github"
|
|
9
9
|
},
|
|
10
|
-
"version": "1.11.
|
|
10
|
+
"version": "1.11.3",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "pypi",
|
|
14
14
|
"identifier": "vmware-debug",
|
|
15
|
-
"version": "1.11.
|
|
15
|
+
"version": "1.11.3",
|
|
16
16
|
"runtimeHint": "uvx",
|
|
17
17
|
"packageArguments": [
|
|
18
18
|
{
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Test package.
|
|
2
|
+
|
|
3
|
+
``tests/eval/`` and ``tests/eval/regression/`` were already packages while this
|
|
4
|
+
file was missing, so ``from tests.eval.regression._vsphere_event_vocabulary
|
|
5
|
+
import ...`` resolved only where the namespace-package fallback happened to
|
|
6
|
+
work. It did not on Windows, and the whole suite -- 398 tests -- failed at
|
|
7
|
+
collection. The other repos that use cross-module test imports all carry this
|
|
8
|
+
file; this one is the half-built package.
|
|
9
|
+
"""
|
|
@@ -132,13 +132,13 @@ class ScoreBoard:
|
|
|
132
132
|
),
|
|
133
133
|
"scores": merged,
|
|
134
134
|
}
|
|
135
|
-
path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n")
|
|
135
|
+
path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
136
136
|
|
|
137
137
|
|
|
138
138
|
def previous_scores(path: Path = SCORES_PATH) -> dict[str, Any]:
|
|
139
139
|
"""Load the last recorded run, or ``{}`` on a first run / unreadable file."""
|
|
140
140
|
try:
|
|
141
|
-
return json.loads(path.read_text()).get("scores", {})
|
|
141
|
+
return json.loads(path.read_text(encoding="utf-8")).get("scores", {})
|
|
142
142
|
except (OSError, ValueError):
|
|
143
143
|
return {}
|
|
144
144
|
|
|
@@ -184,7 +184,7 @@ def test_an_error_returned_via_a_local_is_still_seen(tmp_path):
|
|
|
184
184
|
" except Exception as exc:\n"
|
|
185
185
|
" payload = {'error': str(exc), 'hint': _HINT}\n"
|
|
186
186
|
" return payload\n"
|
|
187
|
-
)
|
|
187
|
+
, encoding="utf-8")
|
|
188
188
|
sites = list(_error_returns_in_server(server_dir=tmp_path, module=None))
|
|
189
189
|
|
|
190
190
|
assert len(sites) == 2, f"a returned local was not seen: {sites}"
|
|
@@ -219,7 +219,7 @@ def test_a_partial_run_does_not_erase_metrics_it_did_not_measure(tmp_path):
|
|
|
219
219
|
partial.add(Score(name="alpha", value=2, maximum=2))
|
|
220
220
|
partial.write(path)
|
|
221
221
|
|
|
222
|
-
scores = json.loads(path.read_text())["scores"]
|
|
222
|
+
scores = json.loads(path.read_text(encoding="utf-8"))["scores"]
|
|
223
223
|
assert set(scores) == {"alpha", "beta"}, "a partial run deleted a metric"
|
|
224
224
|
assert scores["alpha"]["value"] == 2, "the fresh measurement must win"
|
|
225
225
|
assert not scores["alpha"].get("stale"), "a freshly measured metric is not stale"
|
|
@@ -238,7 +238,7 @@ def test_an_empty_run_leaves_the_baseline_alone(tmp_path):
|
|
|
238
238
|
seeded.write(path)
|
|
239
239
|
|
|
240
240
|
ScoreBoard().write(path)
|
|
241
|
-
assert set(json.loads(path.read_text())["scores"]) == {"alpha"}
|
|
241
|
+
assert set(json.loads(path.read_text(encoding="utf-8"))["scores"]) == {"alpha"}
|
|
242
242
|
|
|
243
243
|
|
|
244
244
|
def test_credits_a_real_subcommand_under_a_group():
|
{vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/eval/regression/test_investigation_metrics.py
RENAMED
|
@@ -233,7 +233,7 @@ def test_every_unreachable_source_carries_a_remedy():
|
|
|
233
233
|
def _kb(tmp_path, name, applies_block):
|
|
234
234
|
p = tmp_path / "vmware" / "knowledge" / "kb" / name
|
|
235
235
|
p.parent.mkdir(parents=True, exist_ok=True)
|
|
236
|
-
p.write_text(f"---\nid: {name}\n{applies_block}---\nbody\n")
|
|
236
|
+
p.write_text(f"---\nid: {name}\n{applies_block}---\nbody\n", encoding="utf-8")
|
|
237
237
|
|
|
238
238
|
|
|
239
239
|
def _scoped_case(versions):
|
|
@@ -54,18 +54,18 @@ class TestRecording:
|
|
|
54
54
|
|
|
55
55
|
def test_conclusion_md_gains_an_entry(self, case):
|
|
56
56
|
record_grade(case, grade_case(case), at="2026-08-28T09:30:00Z")
|
|
57
|
-
body = (case_dir(case) / "conclusion.md").read_text()
|
|
57
|
+
body = (case_dir(case) / "conclusion.md").read_text(encoding="utf-8")
|
|
58
58
|
assert "candidate" in body.lower()
|
|
59
59
|
assert "2026-08-28T09:30:00Z" in body
|
|
60
60
|
|
|
61
61
|
def test_the_entry_names_the_rules_that_produced_it(self, case):
|
|
62
62
|
record_grade(case, grade_case(case), at="2026-08-28T09:30:00Z")
|
|
63
|
-
assert "grading_rules.yaml" in (case_dir(case) / "conclusion.md").read_text()
|
|
63
|
+
assert "grading_rules.yaml" in (case_dir(case) / "conclusion.md").read_text(encoding="utf-8")
|
|
64
64
|
|
|
65
65
|
def test_the_placeholder_is_gone_once_a_grade_exists(self, case):
|
|
66
|
-
assert "Not graded yet" in (case_dir(case) / "conclusion.md").read_text()
|
|
66
|
+
assert "Not graded yet" in (case_dir(case) / "conclusion.md").read_text(encoding="utf-8")
|
|
67
67
|
record_grade(case, grade_case(case), at="2026-08-28T09:30:00Z")
|
|
68
|
-
assert "Not graded yet" not in (case_dir(case) / "conclusion.md").read_text()
|
|
68
|
+
assert "Not graded yet" not in (case_dir(case) / "conclusion.md").read_text(encoding="utf-8")
|
|
69
69
|
|
|
70
70
|
|
|
71
71
|
class TestHistoryIsAppendOnly:
|
|
@@ -108,7 +108,7 @@ class TestHistoryIsAppendOnly:
|
|
|
108
108
|
def test_every_entry_survives_in_the_file(self, case):
|
|
109
109
|
for i, at in enumerate(["09:30", "09:40", "09:50"]):
|
|
110
110
|
record_grade(case, grade_case(case), at=f"2026-08-28T{at}:00Z")
|
|
111
|
-
body = (case_dir(case) / "conclusion.md").read_text()
|
|
111
|
+
body = (case_dir(case) / "conclusion.md").read_text(encoding="utf-8")
|
|
112
112
|
for at in ["09:30", "09:40", "09:50"]:
|
|
113
113
|
assert f"2026-08-28T{at}:00Z" in body
|
|
114
114
|
|
|
@@ -73,7 +73,7 @@ class TestRecording:
|
|
|
73
73
|
|
|
74
74
|
def test_the_payload_is_stored_beside_the_record(self, case):
|
|
75
75
|
e = record_evidence(case, an_evidence(), payload={"rows": [1, 2, 3]})
|
|
76
|
-
body = json.loads((case_dir(case) / "evidence" / f"{e.evidence_id}.json").read_text())
|
|
76
|
+
body = json.loads((case_dir(case) / "evidence" / f"{e.evidence_id}.json").read_text(encoding="utf-8"))
|
|
77
77
|
assert body["payload"] == {"rows": [1, 2, 3]}
|
|
78
78
|
|
|
79
79
|
def test_recording_against_a_case_that_does_not_exist_is_an_error(self):
|
|
@@ -95,7 +95,7 @@ class TestTheTimeBasisContract:
|
|
|
95
95
|
|
|
96
96
|
def test_an_unknown_time_source_is_explicit_not_missing(self, case):
|
|
97
97
|
e = record_evidence(case, an_evidence(time_source=None, clock_skew_s=None))
|
|
98
|
-
body = json.loads((case_dir(case) / "evidence" / f"{e.evidence_id}.json").read_text())
|
|
98
|
+
body = json.loads((case_dir(case) / "evidence" / f"{e.evidence_id}.json").read_text(encoding="utf-8"))
|
|
99
99
|
assert "time_source" in body and body["time_source"] is None
|
|
100
100
|
assert "clock_skew_s" in body and body["clock_skew_s"] is None
|
|
101
101
|
|
|
@@ -42,7 +42,7 @@ def mounted_kb(tmp_path):
|
|
|
42
42
|
"""
|
|
43
43
|
p = tmp_path / "vmware" / "knowledge" / "kb" / "KB-fixture.md"
|
|
44
44
|
p.parent.mkdir(parents=True, exist_ok=True)
|
|
45
|
-
p.write_text("---\nid: KB-fixture\napplies_to:\n product: vsphere\n---\nbody\n")
|
|
45
|
+
p.write_text("---\nid: KB-fixture\napplies_to:\n product: vsphere\n---\nbody\n", encoding="utf-8")
|
|
46
46
|
return p
|
|
47
47
|
|
|
48
48
|
|
|
@@ -229,7 +229,7 @@ class TestRules:
|
|
|
229
229
|
site.parent.mkdir(parents=True)
|
|
230
230
|
site.write_text(
|
|
231
231
|
"grades:\n probable:\n min_independent_sources: 1\n blocked_by_gaps: false\n"
|
|
232
|
-
)
|
|
232
|
+
, encoding="utf-8")
|
|
233
233
|
record_evidence(case, evidence(skill="vmware-monitor"))
|
|
234
234
|
r = grade_case(case)
|
|
235
235
|
assert r.grade == "probable"
|
|
@@ -242,7 +242,7 @@ class TestRules:
|
|
|
242
242
|
def test_a_broken_site_file_is_an_error_not_a_silent_fallback(self, tmp_path):
|
|
243
243
|
site = tmp_path / "vmware" / "investigation" / "grading_rules.yaml"
|
|
244
244
|
site.parent.mkdir(parents=True)
|
|
245
|
-
site.write_text("grades: [this is a list not a mapping")
|
|
245
|
+
site.write_text("grades: [this is a list not a mapping", encoding="utf-8")
|
|
246
246
|
with pytest.raises(ValueError, match="grading_rules.yaml"):
|
|
247
247
|
load_rules()
|
|
248
248
|
|
|
@@ -263,7 +263,7 @@ class TestTheCeilingIsMeasuredNotAsserted:
|
|
|
263
263
|
def test_a_populated_knowledge_library_raises_the_ceiling(self, case, tmp_path):
|
|
264
264
|
kb = tmp_path / "vmware" / "knowledge" / "kb"
|
|
265
265
|
kb.mkdir(parents=True)
|
|
266
|
-
(kb / "KB-2026-0417.md").write_text("---\nid: KB-2026-0417\n---\nbody\n")
|
|
266
|
+
(kb / "KB-2026-0417.md").write_text("---\nid: KB-2026-0417\n---\nbody\n", encoding="utf-8")
|
|
267
267
|
r = grade_case(case)
|
|
268
268
|
assert r.ceiling == "confirmed"
|
|
269
269
|
|
|
@@ -288,7 +288,7 @@ class TestTheRulesFileHasNoDecorativeKnobs:
|
|
|
288
288
|
def _site(self, tmp_path, body):
|
|
289
289
|
p = tmp_path / "vmware" / "investigation" / "grading_rules.yaml"
|
|
290
290
|
p.parent.mkdir(parents=True, exist_ok=True)
|
|
291
|
-
p.write_text(body)
|
|
291
|
+
p.write_text(body, encoding="utf-8")
|
|
292
292
|
|
|
293
293
|
def test_relaxing_the_prerequisite_actually_relaxes_it(self, case, tmp_path, mounted_kb):
|
|
294
294
|
"""One decisive source, no corroboration: Confirmed under the relaxed
|
|
@@ -67,7 +67,7 @@ class TestRegistering:
|
|
|
67
67
|
|
|
68
68
|
def test_it_lands_in_hypotheses_md_as_readable_text(self, case):
|
|
69
69
|
add_hypothesis(case, "failing NVMe device")
|
|
70
|
-
body = (case_dir(case) / "hypotheses.md").read_text()
|
|
70
|
+
body = (case_dir(case) / "hypotheses.md").read_text(encoding="utf-8")
|
|
71
71
|
assert "H1" in body and "failing NVMe device" in body
|
|
72
72
|
assert "_Empty." not in body
|
|
73
73
|
|
|
@@ -122,7 +122,7 @@ class TestReadiness:
|
|
|
122
122
|
assert readiness()["classes"]["knowledge"]["available"] is False
|
|
123
123
|
kb = tmp_path / "vmware" / "knowledge" / "kb"
|
|
124
124
|
kb.mkdir(parents=True)
|
|
125
|
-
(kb / "KB-1.md").write_text("---\napplies_to: {}\n---\nbody\n")
|
|
125
|
+
(kb / "KB-1.md").write_text("---\napplies_to: {}\n---\nbody\n", encoding="utf-8")
|
|
126
126
|
assert readiness()["classes"]["knowledge"]["available"] is True
|
|
127
127
|
|
|
128
128
|
def test_hardware_stays_unavailable_no_matter_what_is_installed(self):
|
|
@@ -82,18 +82,18 @@ class TestCreate:
|
|
|
82
82
|
differently to whoever opens the folder."""
|
|
83
83
|
d = case_dir(create_case(a_scope(), at=AT).case_id)
|
|
84
84
|
for name in ("timeline.md", "hypotheses.md", "conclusion.md"):
|
|
85
|
-
body = (d / name).read_text()
|
|
85
|
+
body = (d / name).read_text(encoding="utf-8")
|
|
86
86
|
assert body.strip(), f"{name} is blank"
|
|
87
87
|
assert body.lstrip().startswith("#"), f"{name} has no heading"
|
|
88
88
|
|
|
89
89
|
def test_jsonl_and_json_start_valid_not_blank(self):
|
|
90
90
|
d = case_dir(create_case(a_scope(), at=AT).case_id)
|
|
91
|
-
assert (d / "plan.jsonl").read_text() == ""
|
|
92
|
-
assert json.loads((d / "gaps.json").read_text()) == {"gaps": []}
|
|
91
|
+
assert (d / "plan.jsonl").read_text(encoding="utf-8") == ""
|
|
92
|
+
assert json.loads((d / "gaps.json").read_text(encoding="utf-8")) == {"gaps": []}
|
|
93
93
|
|
|
94
94
|
def test_refuses_to_overwrite_an_existing_case(self):
|
|
95
95
|
case = create_case(a_scope(), at=AT)
|
|
96
|
-
(case_dir(case.case_id) / "evidence" / "E001.json").write_text("{}")
|
|
96
|
+
(case_dir(case.case_id) / "evidence" / "E001.json").write_text("{}", encoding="utf-8")
|
|
97
97
|
with pytest.raises(CaseExists) as e:
|
|
98
98
|
create_case(a_scope(), at=AT)
|
|
99
99
|
assert case.case_id in str(e.value)
|
|
@@ -133,7 +133,7 @@ class TestLoad:
|
|
|
133
133
|
|
|
134
134
|
def test_a_case_whose_scope_is_unreadable_is_an_error_not_an_empty_scope(self):
|
|
135
135
|
case = create_case(a_scope(), at=AT)
|
|
136
|
-
(case_dir(case.case_id) / "scope.json").write_text("{ this is not json")
|
|
136
|
+
(case_dir(case.case_id) / "scope.json").write_text("{ this is not json", encoding="utf-8")
|
|
137
137
|
with pytest.raises(ValueError, match="scope.json"):
|
|
138
138
|
load_case(case.case_id)
|
|
139
139
|
|
|
@@ -97,7 +97,7 @@ class TestTimeline:
|
|
|
97
97
|
def test_it_writes_timeline_md(self, case):
|
|
98
98
|
submit(case, payload=EVENTS)
|
|
99
99
|
build_case_timeline(case)
|
|
100
|
-
body = (case_dir(case) / "timeline.md").read_text()
|
|
100
|
+
body = (case_dir(case) / "timeline.md").read_text(encoding="utf-8")
|
|
101
101
|
assert "esxi-03" in body
|
|
102
102
|
assert "_Empty." not in body
|
|
103
103
|
|
|
@@ -132,7 +132,7 @@ class TestClose:
|
|
|
132
132
|
def test_the_grade_is_recorded_at_close_not_assumed(self, case):
|
|
133
133
|
out = close_case(case, at=AT)
|
|
134
134
|
assert out["grade"] == "candidate"
|
|
135
|
-
assert "candidate" in (case_dir(case) / "conclusion.md").read_text().lower()
|
|
135
|
+
assert "candidate" in (case_dir(case) / "conclusion.md").read_text(encoding="utf-8").lower()
|
|
136
136
|
|
|
137
137
|
def test_closing_twice_is_refused_rather_than_rewriting_the_record(self, case):
|
|
138
138
|
close_case(case, at=AT)
|
|
@@ -1293,7 +1293,7 @@ wheels = [
|
|
|
1293
1293
|
|
|
1294
1294
|
[[package]]
|
|
1295
1295
|
name = "vmware-debug"
|
|
1296
|
-
version = "1.11.
|
|
1296
|
+
version = "1.11.3"
|
|
1297
1297
|
source = { editable = "." }
|
|
1298
1298
|
dependencies = [
|
|
1299
1299
|
{ name = "mcp", extra = ["cli"] },
|
|
@@ -1316,7 +1316,7 @@ requires-dist = [
|
|
|
1316
1316
|
{ name = "pyyaml", specifier = ">=6.0,<7.0" },
|
|
1317
1317
|
{ name = "rich", specifier = ">=13.0,<15.0" },
|
|
1318
1318
|
{ name = "typer", specifier = ">=0.12,<1.0" },
|
|
1319
|
-
{ name = "vmware-policy", specifier = ">=1.
|
|
1319
|
+
{ name = "vmware-policy", specifier = ">=1.12.0,<2.0" },
|
|
1320
1320
|
]
|
|
1321
1321
|
|
|
1322
1322
|
[package.metadata.requires-dev]
|
|
@@ -1328,14 +1328,14 @@ dev = [
|
|
|
1328
1328
|
|
|
1329
1329
|
[[package]]
|
|
1330
1330
|
name = "vmware-policy"
|
|
1331
|
-
version = "1.
|
|
1331
|
+
version = "1.12.0"
|
|
1332
1332
|
source = { registry = "https://pypi.org/simple" }
|
|
1333
1333
|
dependencies = [
|
|
1334
1334
|
{ name = "pyyaml" },
|
|
1335
1335
|
{ name = "rich" },
|
|
1336
1336
|
{ name = "typer" },
|
|
1337
1337
|
]
|
|
1338
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
1338
|
+
sdist = { url = "https://files.pythonhosted.org/packages/24/ce/6d3eda9c10648e34facba0eaeff5a58991246a54e6b0f203a499440e3939/vmware_policy-1.12.0.tar.gz", hash = "sha256:4f9e7b80020eec628d65955ba0d61d8080bde730a257e940912917fd3d5ba36d", size = 156796, upload-time = "2026-08-31T00:20:49.206Z" }
|
|
1339
1339
|
wheels = [
|
|
1340
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1340
|
+
{ url = "https://files.pythonhosted.org/packages/f7/58/c13af514586e3d6ba18144305b421e0e3830cc33e58e87b447c4821509e7/vmware_policy-1.12.0-py3-none-any.whl", hash = "sha256:cbd4dbc454678604fedf4c82e3e6bcecb696e3a1ce6c0ba795bb4dac134c1d17", size = 60569, upload-time = "2026-08-31T00:20:47.595Z" },
|
|
1341
1341
|
]
|
|
@@ -20,6 +20,30 @@ from rich.table import Table
|
|
|
20
20
|
from vmware_debug import __version__
|
|
21
21
|
from vmware_debug.mcp.tools import incident_timeline, list_symptom_categories
|
|
22
22
|
|
|
23
|
+
|
|
24
|
+
def _harden_console_encoding() -> None:
|
|
25
|
+
"""Never let one unrepresentable glyph kill a command.
|
|
26
|
+
|
|
27
|
+
On a console whose encoding cannot carry the characters we print -- cp936 on
|
|
28
|
+
the Chinese Windows boxes this family is tested on, or any ASCII locale --
|
|
29
|
+
``print`` raises ``UnicodeEncodeError`` and the whole command dies with a
|
|
30
|
+
traceback. ``--help`` died that way in four repos. A mangled dash is a
|
|
31
|
+
cosmetic loss; a dead ``--help`` is an outage, so the error handler is
|
|
32
|
+
relaxed rather than the vocabulary narrowed.
|
|
33
|
+
|
|
34
|
+
Best effort: ``reconfigure`` is absent when stdout has been replaced by a
|
|
35
|
+
plain object (pytest capture, some MCP hosts), and losing the hardening
|
|
36
|
+
there is not worth an exception at import.
|
|
37
|
+
"""
|
|
38
|
+
for stream in (sys.stdout, sys.stderr):
|
|
39
|
+
try:
|
|
40
|
+
stream.reconfigure(errors="backslashreplace")
|
|
41
|
+
except (AttributeError, ValueError, OSError):
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
_harden_console_encoding()
|
|
46
|
+
|
|
23
47
|
app = typer.Typer(
|
|
24
48
|
add_completion=False,
|
|
25
49
|
help="VMware diagnostic brain — read-only incident triage and root-cause routing.",
|
|
@@ -54,7 +78,7 @@ def triage(
|
|
|
54
78
|
top_n: int = typer.Option(5, help="Max hypotheses to return."),
|
|
55
79
|
) -> None:
|
|
56
80
|
"""Correlate a set of pre-collected events into a ranked incident timeline."""
|
|
57
|
-
raw = events_file.read_text() if events_file else sys.stdin.read()
|
|
81
|
+
raw = events_file.read_text(encoding="utf-8") if events_file else sys.stdin.read()
|
|
58
82
|
try:
|
|
59
83
|
events = json.loads(raw)
|
|
60
84
|
except json.JSONDecodeError as exc:
|
|
@@ -76,7 +76,7 @@ def record_grade(case_id: str, result: GradeResult, at: str) -> GradeEntry:
|
|
|
76
76
|
)
|
|
77
77
|
|
|
78
78
|
try:
|
|
79
|
-
body = path.read_text()
|
|
79
|
+
body = path.read_text(encoding="utf-8")
|
|
80
80
|
except OSError as exc:
|
|
81
81
|
raise CaseError(
|
|
82
82
|
f"Cannot read conclusion.md for case {case_id}: {exc}. The case "
|
|
@@ -84,13 +84,13 @@ def record_grade(case_id: str, result: GradeResult, at: str) -> GradeEntry:
|
|
|
84
84
|
) from exc
|
|
85
85
|
|
|
86
86
|
body = body.replace(_PLACEHOLDER + "\n\n", "").replace(_PLACEHOLDER + "\n", "")
|
|
87
|
-
path.write_text(body.rstrip("\n") + "\n\n" + _render(entry))
|
|
87
|
+
path.write_text(body.rstrip("\n") + "\n\n" + _render(entry), encoding="utf-8")
|
|
88
88
|
|
|
89
89
|
index_path = d / "case.json"
|
|
90
|
-
index = json.loads(index_path.read_text())
|
|
90
|
+
index = json.loads(index_path.read_text(encoding="utf-8"))
|
|
91
91
|
index["grade"] = result.grade
|
|
92
92
|
index["graded_at"] = at
|
|
93
|
-
index_path.write_text(json.dumps(index, indent=2, ensure_ascii=False) + "\n")
|
|
93
|
+
index_path.write_text(json.dumps(index, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
94
94
|
|
|
95
95
|
return entry
|
|
96
96
|
|
|
@@ -137,7 +137,7 @@ def grade_history(case_id: str) -> tuple[GradeEntry, ...]:
|
|
|
137
137
|
f"incomplete; restore it from your copy rather than recreating it."
|
|
138
138
|
)
|
|
139
139
|
out = []
|
|
140
|
-
for m in _ENTRY_RE.finditer(path.read_text()):
|
|
140
|
+
for m in _ENTRY_RE.finditer(path.read_text(encoding="utf-8")):
|
|
141
141
|
try:
|
|
142
142
|
d = json.loads(m.group("json"))
|
|
143
143
|
except json.JSONDecodeError as exc:
|
|
@@ -215,7 +215,7 @@ def record_evidence(case_id: str, evidence: Evidence, payload: Any = None) -> Ev
|
|
|
215
215
|
body = stamped.to_json()
|
|
216
216
|
if payload is not None:
|
|
217
217
|
body["payload"] = payload
|
|
218
|
-
path.write_text(json.dumps(body, indent=2, ensure_ascii=False) + "\n")
|
|
218
|
+
path.write_text(json.dumps(body, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
219
219
|
return stamped
|
|
220
220
|
|
|
221
221
|
|
|
@@ -243,7 +243,7 @@ def load_evidence(case_id: str) -> tuple[Evidence, ...]:
|
|
|
243
243
|
out = []
|
|
244
244
|
for path in sorted(d.glob("E*.json")):
|
|
245
245
|
try:
|
|
246
|
-
out.append(Evidence.from_json(json.loads(path.read_text())))
|
|
246
|
+
out.append(Evidence.from_json(json.loads(path.read_text(encoding="utf-8"))))
|
|
247
247
|
except (OSError, json.JSONDecodeError) as exc:
|
|
248
248
|
raise ValueError(
|
|
249
249
|
f"Evidence file {path.name} in case {case_id} is unreadable: "
|
|
@@ -271,7 +271,7 @@ def record_gap(case_id: str, gap: Gap) -> Gap:
|
|
|
271
271
|
gap_id=_next_id([g.gap_id for g in existing], "G"),
|
|
272
272
|
)
|
|
273
273
|
payload = {"gaps": [g.to_json() for g in existing] + [stamped.to_json()]}
|
|
274
|
-
(d / _GAPS).write_text(json.dumps(payload, indent=2, ensure_ascii=False) + "\n")
|
|
274
|
+
(d / _GAPS).write_text(json.dumps(payload, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
275
275
|
return stamped
|
|
276
276
|
|
|
277
277
|
|
|
@@ -284,7 +284,7 @@ def load_gaps(case_id: str) -> tuple[Gap, ...]:
|
|
|
284
284
|
"""
|
|
285
285
|
path = _case_dir_or_raise(case_id) / _GAPS
|
|
286
286
|
try:
|
|
287
|
-
body = json.loads(path.read_text())
|
|
287
|
+
body = json.loads(path.read_text(encoding="utf-8"))
|
|
288
288
|
except OSError as exc:
|
|
289
289
|
raise ValueError(f"Cannot read {_GAPS} for case {case_id}: {exc}.") from exc
|
|
290
290
|
except json.JSONDecodeError as exc:
|
|
@@ -55,7 +55,7 @@ def load_hypotheses(case_id: str) -> tuple[Hypothesis, ...]:
|
|
|
55
55
|
"""Every registered hypothesis, in registration order."""
|
|
56
56
|
path = _path(case_id)
|
|
57
57
|
try:
|
|
58
|
-
body = path.read_text()
|
|
58
|
+
body = path.read_text(encoding="utf-8")
|
|
59
59
|
except OSError as exc:
|
|
60
60
|
raise CaseError(f"Cannot read {_FILE} for case {case_id}: {exc}") from exc
|
|
61
61
|
out = []
|
|
@@ -97,12 +97,12 @@ def add_hypothesis(case_id: str, statement: str, at: str = "") -> Hypothesis:
|
|
|
97
97
|
statement=statement.strip(),
|
|
98
98
|
at=at,
|
|
99
99
|
)
|
|
100
|
-
body = path.read_text().replace(_PLACEHOLDER_MARK, "").rstrip("\n")
|
|
100
|
+
body = path.read_text(encoding="utf-8").replace(_PLACEHOLDER_MARK, "").rstrip("\n")
|
|
101
101
|
machine = json.dumps(
|
|
102
102
|
{"id": h.hypothesis_id, "statement": h.statement, "at": h.at},
|
|
103
103
|
ensure_ascii=False,
|
|
104
104
|
)
|
|
105
|
-
path.write_text(f"{body}\n\n## {h.hypothesis_id} — {h.statement}\n\n{_ENTRY.format(machine)}\n")
|
|
105
|
+
path.write_text(f"{body}\n\n## {h.hypothesis_id} — {h.statement}\n\n{_ENTRY.format(machine)}\n", encoding="utf-8")
|
|
106
106
|
return h
|
|
107
107
|
|
|
108
108
|
|
|
@@ -298,7 +298,7 @@ def _parse(path: Path, section: str) -> tuple[list[KnowledgeEntry], str | None]:
|
|
|
298
298
|
return [one(row) for row in csv.DictReader(raw.splitlines(), delimiter=delim)], None
|
|
299
299
|
if ext in (".txt", ".log"):
|
|
300
300
|
sidecar = path.with_suffix(".yaml")
|
|
301
|
-
d = yaml.safe_load(sidecar.read_text()) if sidecar.is_file() else {}
|
|
301
|
+
d = yaml.safe_load(sidecar.read_text(encoding="utf-8")) if sidecar.is_file() else {}
|
|
302
302
|
return [one(d or {}, raw.strip())], None
|
|
303
303
|
except Exception as exc:
|
|
304
304
|
return [], f"{path.name}: {type(exc).__name__}: {exc}"
|
|
@@ -65,7 +65,7 @@ def case_dir(case_id: str) -> Path:
|
|
|
65
65
|
|
|
66
66
|
|
|
67
67
|
def _write_json(path: Path, payload: object) -> None:
|
|
68
|
-
path.write_text(json.dumps(payload, indent=2, ensure_ascii=False) + "\n")
|
|
68
|
+
path.write_text(json.dumps(payload, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
69
69
|
|
|
70
70
|
|
|
71
71
|
def _read_json(path: Path, what: str) -> dict:
|
|
@@ -76,7 +76,7 @@ def _read_json(path: Path, what: str) -> dict:
|
|
|
76
76
|
it would be at its most costly here.
|
|
77
77
|
"""
|
|
78
78
|
try:
|
|
79
|
-
raw = path.read_text()
|
|
79
|
+
raw = path.read_text(encoding="utf-8")
|
|
80
80
|
except OSError as exc:
|
|
81
81
|
raise CaseError(
|
|
82
82
|
f"Cannot read {what} at {path}: {exc}. The case directory may have "
|
|
@@ -125,10 +125,10 @@ def create_case(scope: Scope, at: str) -> Case:
|
|
|
125
125
|
(d / "evidence").mkdir()
|
|
126
126
|
|
|
127
127
|
_write_json(d / "scope.json", scope.to_json())
|
|
128
|
-
(d / "plan.jsonl").write_text("")
|
|
128
|
+
(d / "plan.jsonl").write_text("", encoding="utf-8")
|
|
129
129
|
_write_json(d / "gaps.json", {"gaps": []})
|
|
130
130
|
for name, body in _SKELETON_MD.items():
|
|
131
|
-
(d / name).write_text(body)
|
|
131
|
+
(d / name).write_text(body, encoding="utf-8")
|
|
132
132
|
_write_json(
|
|
133
133
|
d / "case.json",
|
|
134
134
|
{"case_id": case_id, "state": "open", "opened_at": at, "grade": None},
|
|
@@ -52,7 +52,7 @@ def build_case_timeline(
|
|
|
52
52
|
for item in evidence:
|
|
53
53
|
path = d / f"{item.evidence_id}.json"
|
|
54
54
|
try:
|
|
55
|
-
body = json.loads(path.read_text())
|
|
55
|
+
body = json.loads(path.read_text(encoding="utf-8"))
|
|
56
56
|
except (OSError, json.JSONDecodeError):
|
|
57
57
|
rejected.append(f"{item.evidence_id}: payload unreadable")
|
|
58
58
|
continue
|
|
@@ -125,7 +125,7 @@ def _write_timeline_md(case_id: str, result: dict, rows: list) -> None:
|
|
|
125
125
|
if result.get("rejected"):
|
|
126
126
|
lines += ["", "## Could not be read", ""]
|
|
127
127
|
lines += [f"- {r}" for r in result["rejected"]]
|
|
128
|
-
(case_dir(case_id) / "timeline.md").write_text("\n".join(lines) + "\n")
|
|
128
|
+
(case_dir(case_id) / "timeline.md").write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
129
129
|
|
|
130
130
|
|
|
131
131
|
def close_case(case_id: str, at: str) -> dict[str, Any]:
|
|
@@ -146,11 +146,11 @@ def close_case(case_id: str, at: str) -> dict[str, Any]:
|
|
|
146
146
|
open_gaps = [g.gap_id for g in load_gaps(case_id) if g.blocks]
|
|
147
147
|
index_path = case_dir(case_id) / "case.json"
|
|
148
148
|
try:
|
|
149
|
-
index = json.loads(index_path.read_text())
|
|
149
|
+
index = json.loads(index_path.read_text(encoding="utf-8"))
|
|
150
150
|
except (OSError, json.JSONDecodeError) as exc:
|
|
151
151
|
raise CaseError(f"Cannot read case.json for {case_id}: {exc}") from exc
|
|
152
152
|
index.update({"state": "closed", "closed_at": at, "grade": result.grade})
|
|
153
|
-
index_path.write_text(json.dumps(index, indent=2, ensure_ascii=False) + "\n")
|
|
153
|
+
index_path.write_text(json.dumps(index, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
154
154
|
|
|
155
155
|
note = f"Closed at {result.grade}."
|
|
156
156
|
if open_gaps:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vmware_debug-1.11.2 → vmware_debug-1.11.3}/docs/plans/2026-08-28-investigation-layer-design.md
RENAMED
|
File without changes
|
|
File without changes
|
{vmware_debug-1.11.2 → vmware_debug-1.11.3}/skills/vmware-debug/references/agent-guardrails.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vmware_debug-1.11.2 → vmware_debug-1.11.3}/skills/vmware-debug/references/event-envelope.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/eval/capability/test_entity_reachability.py
RENAMED
|
File without changes
|
{vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/eval/capability/test_error_actionability.py
RENAMED
|
File without changes
|
{vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/eval/capability/test_tool_description_quality.py
RENAMED
|
File without changes
|
{vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/eval/capability/test_tool_manifest_budget.py
RENAMED
|
File without changes
|
|
File without changes
|
{vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/eval/regression/_vsphere_event_vocabulary.py
RENAMED
|
File without changes
|
|
File without changes
|
{vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/eval/regression/test_case_plan_max_steps.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/eval/regression/test_evidence_payload_contract.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vmware_debug-1.11.2 → vmware_debug-1.11.3}/tests/eval/regression/test_skill_name_aliases.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|