xrefkit 0.3.0__tar.gz → 0.4.0__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.
- {xrefkit-0.3.0 → xrefkit-0.4.0}/PKG-INFO +47 -1
- {xrefkit-0.3.0 → xrefkit-0.4.0}/README.md +46 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/pyproject.toml +1 -1
- xrefkit-0.4.0/tests/test_boundary_analysis.py +157 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/tests/test_dashboard.py +69 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/__init__.py +1 -1
- xrefkit-0.4.0/xrefkit/boundary_analysis.py +653 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/cli.py +2 -1
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/dashboard.py +319 -13
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/mcp/catalog.py +4 -3
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/mcp/contracts.py +5 -3
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/mcp/schemas.py +6 -1
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/mcp/server.py +21 -14
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/operations_cli.py +43 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit.egg-info/PKG-INFO +47 -1
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit.egg-info/SOURCES.txt +2 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/LICENSE +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/setup.cfg +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/tests/test_base_sync_ownership.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/tests/test_calibration_lint.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/tests/test_check_skill_knowledge_xids.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/tests/test_cli.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/tests/test_collect_analyzer_sarif.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/tests/test_convert_to_xrefkit_skill.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/tests/test_cs_scope_probe.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/tests/test_csharp_commonality.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/tests/test_csharp_naming_profile.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/tests/test_ctx.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/tests/test_cutover_readiness.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/tests/test_error_policy_audit.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/tests/test_error_policy_locator.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/tests/test_fm_multiroot.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/tests/test_gate.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/tests/test_goal_desired_state.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/tests/test_knowledge_relations_validator.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/tests/test_ownership.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/tests/test_packmeta.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/tests/test_project_quality_baseline.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/tests/test_resource_provider.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/tests/test_runtime_contracts.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/tests/test_sarif_to_locator.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/tests/test_skill_runtime_audit.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/tests/test_skillmeta.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/tests/test_structure_catalog.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/tests/test_xref.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/tests/test_xrefkit_instance.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/tests/test_xrefkit_tools.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/tests/test_xrefkit_v2_discovery.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/tests/test_xrefkit_v2_models.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/tests/test_xrefkit_v2_pipeline.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/__main__.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/catalog_cli.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/contracts.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/ctx.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/discovery.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/gate.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/goalstate.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/hashing.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/import_skill.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/instance.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/loaders.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/mcp/__init__.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/mcp/audit.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/mcp/bootstrap.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/mcp/cli.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/mcp/client_cache.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/mcp/context_registry.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/mcp/dist.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/mcp/ownership.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/mcp/repository.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/mcp/startup_contract_pack.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/mcp_tools.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/models/__init__.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/models/common.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/models/effective_bundle.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/models/local_manifest.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/models/package_manifest.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/models/run_log.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/models/server_config.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/models/skill_definition.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/ownership.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/packmeta.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/registry.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/resolver.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/resource_provider.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/resources/base/contracts.json +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/resources/base/current.json +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/resources/base/generations/7a682a5272907354/contracts.json +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/resources/base/generations/7a682a5272907354/model_body.md +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/resources/base/generations/9929294385ccb7b0/contracts.json +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/resources/base/generations/9929294385ccb7b0/model_body.md +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/resources/base/model_body.md +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/runlog.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/skillmeta.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/skillrun.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/structure_catalog.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/tools/__init__.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/tools/__main__.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/v2_cli.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/workspace.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit/xref.py +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit.egg-info/dependency_links.txt +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit.egg-info/entry_points.txt +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit.egg-info/requires.txt +0 -0
- {xrefkit-0.3.0 → xrefkit-0.4.0}/xrefkit.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xrefkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Portable XID, Skill, Knowledge, workflow, and MCP runtime for XRefKit
|
|
5
5
|
Author: synthaicode
|
|
6
6
|
License: MIT License
|
|
@@ -133,6 +133,52 @@ searches and XID resolutions then share the same `run_id` as the client Skill
|
|
|
133
133
|
Run. The client separately records actual model-context loading and judgment
|
|
134
134
|
application with `xrefkit skill knowledge --action load|apply`.
|
|
135
135
|
|
|
136
|
+
## Skill Run Observation Dashboard
|
|
137
|
+
|
|
138
|
+
The local dashboard lets a human inspect Skill run status, closure and quality
|
|
139
|
+
gates, evidence, handoffs, XID usage, missing information, and proposal-only
|
|
140
|
+
boundary analysis.
|
|
141
|
+
|
|
142
|
+
Start it from the repository root:
|
|
143
|
+
|
|
144
|
+
```powershell
|
|
145
|
+
python -m xrefkit dashboard serve --root .
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Open [http://127.0.0.1:8765/](http://127.0.0.1:8765/). To open the browser
|
|
149
|
+
automatically, add `--open-browser`. Use `--port 8766` when the default port is
|
|
150
|
+
already in use, or `--sessions-dir path\to\sessions` when logs are stored
|
|
151
|
+
elsewhere.
|
|
152
|
+
|
|
153
|
+
The main tabs are:
|
|
154
|
+
|
|
155
|
+
- **Overview / Attention / Closure**: run status, blockers, phases, closure,
|
|
156
|
+
and quality-gate state.
|
|
157
|
+
- **Evidence / Handoff**: outputs, checks, handoffs, unknowns, risks, and
|
|
158
|
+
judgments needed for review and continuity.
|
|
159
|
+
- **XID Usage**: selected, resolved, loaded, used, available, and unused XIDs.
|
|
160
|
+
- **Analysis**: deterministic candidates for Knowledge correction, Skill
|
|
161
|
+
correction, split, merge, or usage-gap investigation. Review the evidence,
|
|
162
|
+
counterevidence, unknowns, and verification plan before changing canonical
|
|
163
|
+
files. The dashboard never applies these proposals automatically.
|
|
164
|
+
- **Missing Information**: absent correlation, MCP, Knowledge, or feedback
|
|
165
|
+
records.
|
|
166
|
+
|
|
167
|
+
Export the dashboard data and create a human-reviewable boundary report:
|
|
168
|
+
|
|
169
|
+
```powershell
|
|
170
|
+
python -m xrefkit dashboard data --root . > work/reports/dashboard-observation.json
|
|
171
|
+
python -m xrefkit analysis boundary report `
|
|
172
|
+
--input work/reports/dashboard-observation.json `
|
|
173
|
+
--out work/reports/boundary-observation.md
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
The running dashboard also exposes JSON at
|
|
177
|
+
[http://127.0.0.1:8765/api/runs](http://127.0.0.1:8765/api/runs) and health at
|
|
178
|
+
[http://127.0.0.1:8765/healthz](http://127.0.0.1:8765/healthz). Stop a foreground
|
|
179
|
+
server with `Ctrl+C`. For the complete review loop and screen guide, see the
|
|
180
|
+
[Skill Run Observation Dashboard Usage guide](docs/guides/086_skill_run_observation_dashboard_usage.md).
|
|
181
|
+
|
|
136
182
|
XRefKit is designed to be driven by an AI agent. The agent first resolves the
|
|
137
183
|
startup contract XID, selects a Skill or source target from a compact catalog,
|
|
138
184
|
and expands only the selected body.
|
|
@@ -94,6 +94,52 @@ searches and XID resolutions then share the same `run_id` as the client Skill
|
|
|
94
94
|
Run. The client separately records actual model-context loading and judgment
|
|
95
95
|
application with `xrefkit skill knowledge --action load|apply`.
|
|
96
96
|
|
|
97
|
+
## Skill Run Observation Dashboard
|
|
98
|
+
|
|
99
|
+
The local dashboard lets a human inspect Skill run status, closure and quality
|
|
100
|
+
gates, evidence, handoffs, XID usage, missing information, and proposal-only
|
|
101
|
+
boundary analysis.
|
|
102
|
+
|
|
103
|
+
Start it from the repository root:
|
|
104
|
+
|
|
105
|
+
```powershell
|
|
106
|
+
python -m xrefkit dashboard serve --root .
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Open [http://127.0.0.1:8765/](http://127.0.0.1:8765/). To open the browser
|
|
110
|
+
automatically, add `--open-browser`. Use `--port 8766` when the default port is
|
|
111
|
+
already in use, or `--sessions-dir path\to\sessions` when logs are stored
|
|
112
|
+
elsewhere.
|
|
113
|
+
|
|
114
|
+
The main tabs are:
|
|
115
|
+
|
|
116
|
+
- **Overview / Attention / Closure**: run status, blockers, phases, closure,
|
|
117
|
+
and quality-gate state.
|
|
118
|
+
- **Evidence / Handoff**: outputs, checks, handoffs, unknowns, risks, and
|
|
119
|
+
judgments needed for review and continuity.
|
|
120
|
+
- **XID Usage**: selected, resolved, loaded, used, available, and unused XIDs.
|
|
121
|
+
- **Analysis**: deterministic candidates for Knowledge correction, Skill
|
|
122
|
+
correction, split, merge, or usage-gap investigation. Review the evidence,
|
|
123
|
+
counterevidence, unknowns, and verification plan before changing canonical
|
|
124
|
+
files. The dashboard never applies these proposals automatically.
|
|
125
|
+
- **Missing Information**: absent correlation, MCP, Knowledge, or feedback
|
|
126
|
+
records.
|
|
127
|
+
|
|
128
|
+
Export the dashboard data and create a human-reviewable boundary report:
|
|
129
|
+
|
|
130
|
+
```powershell
|
|
131
|
+
python -m xrefkit dashboard data --root . > work/reports/dashboard-observation.json
|
|
132
|
+
python -m xrefkit analysis boundary report `
|
|
133
|
+
--input work/reports/dashboard-observation.json `
|
|
134
|
+
--out work/reports/boundary-observation.md
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
The running dashboard also exposes JSON at
|
|
138
|
+
[http://127.0.0.1:8765/api/runs](http://127.0.0.1:8765/api/runs) and health at
|
|
139
|
+
[http://127.0.0.1:8765/healthz](http://127.0.0.1:8765/healthz). Stop a foreground
|
|
140
|
+
server with `Ctrl+C`. For the complete review loop and screen guide, see the
|
|
141
|
+
[Skill Run Observation Dashboard Usage guide](docs/guides/086_skill_run_observation_dashboard_usage.md).
|
|
142
|
+
|
|
97
143
|
XRefKit is designed to be driven by an AI agent. The agent first resolves the
|
|
98
144
|
startup contract XID, selects a Skill or source target from a compact catalog,
|
|
99
145
|
and expands only the selected body.
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
import io
|
|
3
|
+
import json
|
|
4
|
+
import tempfile
|
|
5
|
+
import unittest
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from xrefkit.__main__ import main
|
|
9
|
+
from xrefkit.boundary_analysis import analyze_dashboard_payload, render_markdown
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BoundaryAnalysisTests(unittest.TestCase):
|
|
13
|
+
def _run(
|
|
14
|
+
self,
|
|
15
|
+
skill_id: str,
|
|
16
|
+
path: str,
|
|
17
|
+
xids: list[str],
|
|
18
|
+
*,
|
|
19
|
+
feedback: list[dict[str, str]] | None = None,
|
|
20
|
+
) -> dict[str, object]:
|
|
21
|
+
return {
|
|
22
|
+
"path": path,
|
|
23
|
+
"name": Path(path).name,
|
|
24
|
+
"skill_id": skill_id,
|
|
25
|
+
"run_id": f"run-{path}",
|
|
26
|
+
"mcp_session_id": f"mcp-{path}",
|
|
27
|
+
"repository_fingerprint": "repo-001",
|
|
28
|
+
"status": "closed",
|
|
29
|
+
"closure_status": "done",
|
|
30
|
+
"quality_required": True,
|
|
31
|
+
"quality_status": "done",
|
|
32
|
+
"selected_xids": xids,
|
|
33
|
+
"queried_xids": xids,
|
|
34
|
+
"loaded_xids": xids,
|
|
35
|
+
"used_xids": xids,
|
|
36
|
+
"unused_xids": [],
|
|
37
|
+
"available_xids": xids,
|
|
38
|
+
"queried_not_loaded_xids": [],
|
|
39
|
+
"loaded_not_applied_xids": [],
|
|
40
|
+
"missing_information": [],
|
|
41
|
+
"observation_events": feedback or [],
|
|
42
|
+
"mcp_events": [],
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
def _payload(self) -> dict[str, object]:
|
|
46
|
+
xid_a = "XID-A-001"
|
|
47
|
+
xid_b = "XID-B-001"
|
|
48
|
+
return {
|
|
49
|
+
"schema": "xrefkit.dashboard/v1",
|
|
50
|
+
"audit_errors": [],
|
|
51
|
+
"runs": [
|
|
52
|
+
self._run(
|
|
53
|
+
"alpha",
|
|
54
|
+
"work/sessions/alpha-a-1.md",
|
|
55
|
+
[xid_a],
|
|
56
|
+
feedback=[
|
|
57
|
+
{"event": "human.feedback", "status": "corrected", "target": xid_a, "note": "wrong fact"},
|
|
58
|
+
{"event": "human.feedback", "status": "corrected", "target": "OUT-001", "note": "wrong procedure"},
|
|
59
|
+
],
|
|
60
|
+
),
|
|
61
|
+
self._run(
|
|
62
|
+
"alpha",
|
|
63
|
+
"work/sessions/alpha-a-2.md",
|
|
64
|
+
[xid_a],
|
|
65
|
+
feedback=[
|
|
66
|
+
{"event": "human.feedback", "status": "rejected", "target": xid_a, "note": "stale fact"},
|
|
67
|
+
{"event": "human.feedback", "status": "rejected", "target": "OUT-002", "note": "wrong decision"},
|
|
68
|
+
],
|
|
69
|
+
),
|
|
70
|
+
self._run("alpha", "work/sessions/alpha-b-1.md", [xid_b]),
|
|
71
|
+
self._run("alpha", "work/sessions/alpha-b-2.md", [xid_b]),
|
|
72
|
+
self._run("beta", "work/sessions/beta-1.md", [xid_a, xid_b]),
|
|
73
|
+
self._run("beta", "work/sessions/beta-2.md", [xid_a, xid_b]),
|
|
74
|
+
],
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
def test_analysis_emits_conservative_boundary_candidates(self) -> None:
|
|
78
|
+
report = analyze_dashboard_payload(self._payload(), source_hash="source-001", min_samples=2)
|
|
79
|
+
|
|
80
|
+
self.assertEqual("xrefkit.boundary_observation/v1", report["schema"])
|
|
81
|
+
self.assertEqual("proposal_only", report["status"])
|
|
82
|
+
self.assertEqual(6, report["sample_count"])
|
|
83
|
+
self.assertEqual(6, report["correlation"]["exact"])
|
|
84
|
+
categories = {item["category"] for item in report["proposals"]}
|
|
85
|
+
self.assertIn("split", categories)
|
|
86
|
+
self.assertIn("merge", categories)
|
|
87
|
+
self.assertIn("knowledge_correction", categories)
|
|
88
|
+
self.assertIn("skill_correction", categories)
|
|
89
|
+
self.assertTrue(all(item["decision"]["status"] == "pending" for item in report["proposals"]))
|
|
90
|
+
|
|
91
|
+
xid_row = next(item for item in report["xid_usage"] if item["xid"] == "XID-A-001")
|
|
92
|
+
self.assertEqual(4, xid_row["run_count"])
|
|
93
|
+
self.assertEqual(4, xid_row["used_count"])
|
|
94
|
+
|
|
95
|
+
def test_markdown_explains_proposal_only_boundary(self) -> None:
|
|
96
|
+
markdown = render_markdown(analyze_dashboard_payload(self._payload(), source_hash="source-001"))
|
|
97
|
+
|
|
98
|
+
self.assertIn("Proposal-only output", markdown)
|
|
99
|
+
self.assertIn("## Proposals", markdown)
|
|
100
|
+
self.assertIn("Counterevidence", markdown)
|
|
101
|
+
self.assertIn("## Decision", markdown)
|
|
102
|
+
self.assertIn("XID-A-001", markdown)
|
|
103
|
+
|
|
104
|
+
def test_cli_writes_markdown_and_can_emit_json(self) -> None:
|
|
105
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
106
|
+
root = Path(tmp)
|
|
107
|
+
source = root / "dashboard.json"
|
|
108
|
+
output = root / "reports" / "boundary.md"
|
|
109
|
+
source.write_text(json.dumps(self._payload()), encoding="utf-8")
|
|
110
|
+
stdout = io.StringIO()
|
|
111
|
+
with contextlib.redirect_stdout(stdout):
|
|
112
|
+
result = main(
|
|
113
|
+
[
|
|
114
|
+
"analysis",
|
|
115
|
+
"boundary",
|
|
116
|
+
"report",
|
|
117
|
+
"--input",
|
|
118
|
+
str(source),
|
|
119
|
+
"--out",
|
|
120
|
+
str(output),
|
|
121
|
+
"--json",
|
|
122
|
+
]
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
self.assertEqual(0, result)
|
|
126
|
+
self.assertTrue(output.exists())
|
|
127
|
+
self.assertIn("Proposal-only output", output.read_text(encoding="utf-8"))
|
|
128
|
+
response = json.loads(stdout.getvalue())
|
|
129
|
+
self.assertEqual("proposal_only", response["status"])
|
|
130
|
+
self.assertEqual(4, response["summary"]["proposals"])
|
|
131
|
+
|
|
132
|
+
def test_invalid_dashboard_input_fails_without_writing(self) -> None:
|
|
133
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
134
|
+
source = Path(tmp) / "invalid.json"
|
|
135
|
+
output = Path(tmp) / "boundary.md"
|
|
136
|
+
source.write_text(json.dumps({"runs": "not-a-list"}), encoding="utf-8")
|
|
137
|
+
stdout = io.StringIO()
|
|
138
|
+
with contextlib.redirect_stdout(stdout):
|
|
139
|
+
result = main(
|
|
140
|
+
[
|
|
141
|
+
"analysis",
|
|
142
|
+
"boundary",
|
|
143
|
+
"report",
|
|
144
|
+
"--input",
|
|
145
|
+
str(source),
|
|
146
|
+
"--out",
|
|
147
|
+
str(output),
|
|
148
|
+
]
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
self.assertEqual(1, result)
|
|
152
|
+
self.assertFalse(output.exists())
|
|
153
|
+
self.assertIn("runs array", stdout.getvalue())
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
if __name__ == "__main__":
|
|
157
|
+
unittest.main()
|
|
@@ -379,6 +379,20 @@ class DashboardTests(unittest.TestCase):
|
|
|
379
379
|
self.assertEqual({}, by_run)
|
|
380
380
|
self.assertIn("cannot read audit log", errors[0])
|
|
381
381
|
|
|
382
|
+
def test_dashboard_payload_includes_proposal_only_boundary_analysis(self) -> None:
|
|
383
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
384
|
+
root = Path(tmp)
|
|
385
|
+
self._write_closed_run(root)
|
|
386
|
+
|
|
387
|
+
payload = build_payload(root, root / "work" / "sessions")
|
|
388
|
+
analysis = payload["boundary_analysis"]
|
|
389
|
+
|
|
390
|
+
self.assertIsInstance(analysis, dict)
|
|
391
|
+
self.assertEqual("xrefkit.boundary_observation/v1", analysis["schema"])
|
|
392
|
+
self.assertEqual("proposal_only", analysis["status"])
|
|
393
|
+
self.assertEqual(1, analysis["sample_count"])
|
|
394
|
+
self.assertEqual(0, analysis["summary"]["proposals"])
|
|
395
|
+
|
|
382
396
|
def test_dashboard_html_splits_categories(self) -> None:
|
|
383
397
|
with tempfile.TemporaryDirectory() as tmp:
|
|
384
398
|
root = Path(tmp)
|
|
@@ -392,6 +406,7 @@ class DashboardTests(unittest.TestCase):
|
|
|
392
406
|
self.assertIn('data-panel="evidence"', html)
|
|
393
407
|
self.assertIn('data-panel="handoff"', html)
|
|
394
408
|
self.assertIn('data-panel="xids"', html)
|
|
409
|
+
self.assertIn('data-panel="analysis"', html)
|
|
395
410
|
self.assertIn('data-panel="missing-information"', html)
|
|
396
411
|
self.assertIn('id="overview"', html)
|
|
397
412
|
self.assertIn('id="attention"', html)
|
|
@@ -399,8 +414,62 @@ class DashboardTests(unittest.TestCase):
|
|
|
399
414
|
self.assertIn('id="evidence"', html)
|
|
400
415
|
self.assertIn('id="handoff"', html)
|
|
401
416
|
self.assertIn('id="xids"', html)
|
|
417
|
+
self.assertIn('id="analysis"', html)
|
|
402
418
|
self.assertIn('id="missing-information"', html)
|
|
419
|
+
self.assertIn("Proposal-only analysis", html)
|
|
420
|
+
self.assertIn("No boundary proposals reached", html)
|
|
403
421
|
self.assertIn("Missing Information Ranking", html)
|
|
404
422
|
self.assertIn("Available Knowledge XIDs (base/local)", html)
|
|
405
423
|
self.assertIn("local-service-map-001", html)
|
|
406
424
|
self.assertIn("LOCAL-KNOWLEDGE-UNUSED-001", html)
|
|
425
|
+
self.assertIn('id="run-search"', html)
|
|
426
|
+
self.assertIn('data-status="blocked"', html)
|
|
427
|
+
self.assertIn('id="refresh-runs"', html)
|
|
428
|
+
self.assertIn("async function refreshDashboard()", html)
|
|
429
|
+
self.assertIn("selectRun(run.dataset.runPath)", html)
|
|
430
|
+
self.assertIn("data-run-path=", html)
|
|
431
|
+
|
|
432
|
+
def test_dashboard_html_search_index_contains_run_correlation_fields(self) -> None:
|
|
433
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
434
|
+
root = Path(tmp)
|
|
435
|
+
self._write_closed_run(root)
|
|
436
|
+
|
|
437
|
+
payload = build_payload(root, root / "work" / "sessions")
|
|
438
|
+
run_id = payload["runs"][0]["run_id"]
|
|
439
|
+
html = _html_page(payload)
|
|
440
|
+
|
|
441
|
+
self.assertIsNotNone(run_id)
|
|
442
|
+
self.assertIn(str(run_id).lower(), html)
|
|
443
|
+
self.assertIn("sample_skill", html)
|
|
444
|
+
|
|
445
|
+
def test_dashboard_html_escapes_boundary_proposal_fields(self) -> None:
|
|
446
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
447
|
+
root = Path(tmp)
|
|
448
|
+
self._write_closed_run(root)
|
|
449
|
+
|
|
450
|
+
payload = build_payload(root, root / "work" / "sessions")
|
|
451
|
+
payload["boundary_analysis"]["summary"]["proposals"] = 1
|
|
452
|
+
payload["boundary_analysis"]["proposals"] = [
|
|
453
|
+
{
|
|
454
|
+
"proposal_id": "bo-test",
|
|
455
|
+
"proposal": "investigate",
|
|
456
|
+
"category": "skill_correction",
|
|
457
|
+
"skill_ids": ["sample_skill"],
|
|
458
|
+
"subject_xids": ["xid-test"],
|
|
459
|
+
"support": 2,
|
|
460
|
+
"evidence_refs": ["<script>alert(1)</script>"],
|
|
461
|
+
"rationale": "<script>alert(2)</script>",
|
|
462
|
+
"counterevidence": ["counter"],
|
|
463
|
+
"unknowns": ["unknown"],
|
|
464
|
+
"verification_plan": ["verify"],
|
|
465
|
+
"decision": {"status": "pending", "owner": None},
|
|
466
|
+
}
|
|
467
|
+
]
|
|
468
|
+
|
|
469
|
+
html = _html_page(payload)
|
|
470
|
+
|
|
471
|
+
self.assertIn("bo-test", html)
|
|
472
|
+
self.assertIn("Counterevidence", html)
|
|
473
|
+
self.assertIn("<script>alert(1)</script>", html)
|
|
474
|
+
self.assertIn("<script>alert(2)</script>", html)
|
|
475
|
+
self.assertNotIn("<script>alert(1)", html)
|