eduevidence 6.2.0 → 6.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +395 -0
- package/README.md +22 -13
- package/README.zh-CN.md +15 -8
- package/SKILL.md +10 -9
- package/benchmarks/evidence-library.json +277 -1
- package/docs/architecture.md +6 -3
- package/docs/j-ev-experimental.md +250 -0
- package/docs/reproducibility.md +138 -0
- package/domains/_neutral/copy/few_shots.json +21 -0
- package/domains/_neutral/copy/framing_lexicon.json +19 -0
- package/domains/_neutral/copy/module_labels.json +5 -0
- package/domains/_neutral/copy/module_labels_footer.json +102 -0
- package/domains/_neutral/copy/module_labels_modules.json +204 -0
- package/domains/_neutral/copy/module_labels_nav.json +126 -0
- package/domains/_neutral/copy/module_labels_summary.json +98 -0
- package/domains/_neutral/copy/module_labels_tables.json +164 -0
- package/domains/_neutral/copy/module_labels_v2.json +90 -0
- package/domains/_neutral/copy/risk_constructs.json +20 -0
- package/domains/_neutral/copy/section_titles.json +66 -0
- package/domains/_neutral/copy/terminology.json +11 -0
- package/domains/check_copy_packs.py +103 -0
- package/domains/education/copy/few_shots.json +22 -0
- package/domains/education/copy/framing_enums.json +167 -0
- package/domains/education/copy/framing_lexicon.json +166 -0
- package/domains/education/copy/module_labels.json +169 -0
- package/domains/education/copy/risk_constructs.json +48 -0
- package/domains/education/copy/section_titles.json +186 -0
- package/domains/education/copy/terminology.json +70 -0
- package/domains/education/manifest.json +1 -1
- package/domains/education/outcome_taxonomy.json +2 -2
- package/domains/manifest.json +1 -1
- package/domains/policy/copy/few_shots.json +22 -0
- package/domains/policy/copy/framing_enums.json +94 -0
- package/domains/policy/copy/framing_lexicon.json +174 -0
- package/domains/policy/copy/module_labels.json +168 -0
- package/domains/policy/copy/risk_constructs.json +33 -0
- package/domains/policy/copy/section_titles.json +186 -0
- package/domains/policy/copy/terminology.json +64 -0
- package/engine/capabilities.py +57 -5
- package/engine/decision_policy.py +88 -17
- package/engine/library_builtin.py +7 -4
- package/engine/tribunal.py +17 -23
- package/engine/versions.py +1 -1
- package/examples/ai-coding-assistant-evidence/EduEvidence_Report.html +4 -4
- package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_academic.html +4 -4
- package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_claude.html +4 -4
- package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_datalab-dark.html +4 -4
- package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_datalab.html +4 -4
- package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_presentation.html +4 -4
- package/examples/spaced-retrieval-practice/EduEvidence_Report.html +2728 -0
- package/examples/spaced-retrieval-practice/report.html +2522 -0
- package/examples/spaced-retrieval-practice/reports-5themes/EduEvidence_Report_academic.html +4 -4
- package/examples/spaced-retrieval-practice/reports-5themes/EduEvidence_Report_claude.html +4 -4
- package/examples/spaced-retrieval-practice/reports-5themes/EduEvidence_Report_datalab-dark.html +4 -4
- package/examples/spaced-retrieval-practice/reports-5themes/EduEvidence_Report_datalab.html +4 -4
- package/examples/spaced-retrieval-practice/reports-5themes/EduEvidence_Report_presentation.html +4 -4
- package/examples/workplace-ai-assistant/EduEvidence_Report.html +2814 -0
- package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_academic.html +36 -36
- package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_claude.html +36 -36
- package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_datalab-dark.html +36 -36
- package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_datalab.html +36 -36
- package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_presentation.html +36 -36
- package/integrations/jev/__init__.py +115 -0
- package/integrations/jev/approval.py +212 -0
- package/integrations/jev/cli.py +84 -0
- package/integrations/jev/config.py +112 -0
- package/integrations/jev/gateway.py +128 -0
- package/integrations/jev/modes.py +38 -0
- package/integrations/jev/tools_classify.py +88 -0
- package/integrations/jev/tools_extract.py +111 -0
- package/integrations/jev/tools_rerank.py +71 -0
- package/integrations/jev/tools_screen.py +87 -0
- package/integrations/jev/tools_verify.py +95 -0
- package/integrations/jev_mcp.py +22 -0
- package/integrations/semantic_decide.py +286 -0
- package/integrations/semdecide_cli.py +55 -0
- package/package.json +9 -1
- package/pyproject.toml +1 -1
- package/references/report-copy-style.md +43 -3
- package/schemas/v2/decision-snapshot.schema.json +20 -9
- package/schemas/v2/intake.schema.json +191 -0
- package/scripts/build_evidence_library.py +15 -5
- package/scripts/dashboard_server.py +13 -2
- package/scripts/intake/__init__.py +31 -0
- package/scripts/intake/__main__.py +18 -0
- package/scripts/intake/background.py +78 -0
- package/scripts/intake/browser.py +79 -0
- package/scripts/intake/cli.py +57 -0
- package/scripts/intake/constants.py +57 -0
- package/scripts/intake/depth.py +53 -0
- package/scripts/intake/enhancements.py +106 -0
- package/scripts/intake/hooks.py +90 -0
- package/scripts/intake/prefs.py +76 -0
- package/scripts/intake/prompts.py +85 -0
- package/scripts/intake/session.py +152 -0
- package/scripts/lint_file_layers.py +126 -0
- package/scripts/orchestrator.py +68 -17
- package/scripts/pre_verdict_gate.py +21 -7
- package/scripts/skill_lint.py +11 -1
- package/scripts/skill_payload.py +3 -3
- package/scripts/test_adversarial_empirical.py +70 -6
- package/skill/agents/evidence-judge.md +49 -7
- package/skill/workflows/experimental-jev.md +170 -0
- package/skill/workflows/intake.md +120 -0
- package/visualization/eduevidence-report/scripts/build_infographics.py +32 -14
- package/visualization/eduevidence-report/scripts/build_report.py +75 -662
- package/visualization/eduevidence-report/scripts/report_copy_pack.py +296 -0
- package/visualization/eduevidence-report/scripts/report_copy_policy_guard.py +47 -0
- package/visualization/eduevidence-report/scripts/zh_labels.py +61 -0
- package/scripts/build_esl_artifacts.py +0 -1921
- package/scripts/build_killer_demo.py +0 -295
- package/scripts/enrich_projects_human_and_lieflat.py +0 -315
- package/scripts/generate_new_projects.py +0 -686
- package/scripts/sync_killer_demo_report.py +0 -270
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"""SemDecide CLI integration (experimental mode 2 / hybrid 3).
|
|
2
|
+
|
|
3
|
+
Exit 2/3/4 never resolve silently — escalate to the host large model.
|
|
4
|
+
Secrets stay in env files; never passed on argv.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
import shutil
|
|
11
|
+
import subprocess
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
SEMDECIDE_ENV_FILE = os.environ.get("JEV_ENV_FILE", "~/.eduevidence/env")
|
|
16
|
+
SEMDECIDE_BIN_ENV = "SEMDECIDE_BIN"
|
|
17
|
+
SEMDECIDE_UNAVAILABLE = "SEMDECIDE_UNAVAILABLE"
|
|
18
|
+
SEMDECIDE_APPROVAL_REQUIRED = "SEMDECIDE_APPROVAL_REQUIRED"
|
|
19
|
+
ESCALATE_TO_LLM = "ESCALATE_TO_LLM"
|
|
20
|
+
DEFAULT_THRESHOLD = 0.7
|
|
21
|
+
DEFAULT_TIMEOUT = 10.0
|
|
22
|
+
DEFAULT_MIN_CONFIDENCE = 0.0
|
|
23
|
+
SEMDECIDE_TIMEOUT = DEFAULT_TIMEOUT
|
|
24
|
+
EXIT_OK = 0
|
|
25
|
+
EXIT_FALSE = 1
|
|
26
|
+
ESCALATE_EXIT_CODES = frozenset({2, 3, 4})
|
|
27
|
+
EXIT_MEANINGS = {
|
|
28
|
+
0: "true/selected/match",
|
|
29
|
+
1: "false/no match",
|
|
30
|
+
2: "invalid input",
|
|
31
|
+
3: "uncertain",
|
|
32
|
+
4: "provider failure",
|
|
33
|
+
}
|
|
34
|
+
WRAPPED_COMMANDS = ("is", "filter", "choose", "score", "guard")
|
|
35
|
+
|
|
36
|
+
def _env_file_values(path: str | Path | None = None) -> dict[str, str]:
|
|
37
|
+
try:
|
|
38
|
+
text = Path(os.path.expanduser(path or SEMDECIDE_ENV_FILE)).read_text(
|
|
39
|
+
encoding="utf-8")
|
|
40
|
+
except OSError:
|
|
41
|
+
return {}
|
|
42
|
+
values: dict[str, str] = {}
|
|
43
|
+
for line in text.splitlines():
|
|
44
|
+
line = line.strip()
|
|
45
|
+
if not line or line.startswith("#") or "=" not in line:
|
|
46
|
+
continue
|
|
47
|
+
if line.startswith("export "):
|
|
48
|
+
line = line[len("export "):]
|
|
49
|
+
key, _, value = line.partition("=")
|
|
50
|
+
values[key.strip()] = value.strip().strip("\"'")
|
|
51
|
+
return values
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def binary_path() -> str | None:
|
|
55
|
+
override = os.environ.get(SEMDECIDE_BIN_ENV)
|
|
56
|
+
if override and os.path.exists(os.path.expanduser(override)):
|
|
57
|
+
return os.path.expanduser(override)
|
|
58
|
+
return shutil.which("semdecide")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def detect_semdecide() -> dict[str, Any]:
|
|
62
|
+
"""Probe the local semdecide CLI (never the network)."""
|
|
63
|
+
path = binary_path()
|
|
64
|
+
env_values = _env_file_values()
|
|
65
|
+
has_key = bool(
|
|
66
|
+
os.environ.get("TYPESAFE_API_KEY")
|
|
67
|
+
or env_values.get("TYPESAFE_API_KEY")
|
|
68
|
+
or os.environ.get("AI_GATEWAY_API_KEY")
|
|
69
|
+
or env_values.get("AI_GATEWAY_API_KEY")
|
|
70
|
+
)
|
|
71
|
+
available = path is not None
|
|
72
|
+
reasons: list[str] = []
|
|
73
|
+
if not available:
|
|
74
|
+
reasons.append("semdecide binary not on PATH (pipx/uv tool install "
|
|
75
|
+
"from sharziki/semdecide release wheel)")
|
|
76
|
+
if not has_key:
|
|
77
|
+
reasons.append("TYPESAFE_API_KEY / AI_GATEWAY_API_KEY not set — "
|
|
78
|
+
"semdecide will exit 4 (provider_failure)")
|
|
79
|
+
return {
|
|
80
|
+
"available": available,
|
|
81
|
+
"state": "available" if available else "unavailable",
|
|
82
|
+
"mode": "semdecide" if available else "none",
|
|
83
|
+
"binary": path,
|
|
84
|
+
"credentials_declared": has_key,
|
|
85
|
+
"reason": "; ".join(reasons) or "semdecide on PATH",
|
|
86
|
+
"reasons": reasons,
|
|
87
|
+
"hint": ("install: pipx install "
|
|
88
|
+
"https://github.com/sharziki/semdecide/releases/download/"
|
|
89
|
+
"v0.2.1/semdecide-0.2.1-py3-none-any.whl"
|
|
90
|
+
if not available else ""),
|
|
91
|
+
"commands": list(WRAPPED_COMMANDS),
|
|
92
|
+
"escalate_exit_codes": sorted(ESCALATE_EXIT_CODES),
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def requires_llm_escalation(result: dict[str, Any]) -> bool:
|
|
98
|
+
"""True for exit 2 / 3 / 4 (and missing binary)."""
|
|
99
|
+
if result.get("status") == SEMDECIDE_UNAVAILABLE:
|
|
100
|
+
return True
|
|
101
|
+
return result.get("exit_code") in ESCALATE_EXIT_CODES
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _run(argv: list[str], *, stdin_text: str | None = None,
|
|
105
|
+
timeout: float = DEFAULT_TIMEOUT) -> dict[str, Any]:
|
|
106
|
+
path = binary_path()
|
|
107
|
+
if not path:
|
|
108
|
+
return {
|
|
109
|
+
"status": SEMDECIDE_UNAVAILABLE,
|
|
110
|
+
"exit_code": None,
|
|
111
|
+
"escalate": ESCALATE_TO_LLM,
|
|
112
|
+
"reason": "semdecide binary not found",
|
|
113
|
+
}
|
|
114
|
+
# Credentials come from the process env / credentials.env — never argv.
|
|
115
|
+
env = os.environ.copy()
|
|
116
|
+
for key, value in _env_file_values().items():
|
|
117
|
+
env.setdefault(key, value)
|
|
118
|
+
try:
|
|
119
|
+
proc = subprocess.run(
|
|
120
|
+
[path, *argv],
|
|
121
|
+
input=stdin_text if stdin_text is not None else "",
|
|
122
|
+
capture_output=True,
|
|
123
|
+
text=True,
|
|
124
|
+
timeout=timeout,
|
|
125
|
+
env=env,
|
|
126
|
+
)
|
|
127
|
+
except subprocess.TimeoutExpired:
|
|
128
|
+
return {
|
|
129
|
+
"status": "SEMDECIDE_TIMEOUT",
|
|
130
|
+
"exit_code": None,
|
|
131
|
+
"escalate": ESCALATE_TO_LLM,
|
|
132
|
+
"reason": f"semdecide timed out after {timeout}s",
|
|
133
|
+
}
|
|
134
|
+
except OSError as exc:
|
|
135
|
+
return {
|
|
136
|
+
"status": SEMDECIDE_UNAVAILABLE,
|
|
137
|
+
"exit_code": None,
|
|
138
|
+
"escalate": ESCALATE_TO_LLM,
|
|
139
|
+
"reason": f"semdecide spawn failed: {type(exc).__name__}",
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
code = proc.returncode
|
|
143
|
+
stdout = proc.stdout or ""
|
|
144
|
+
stderr = proc.stderr or ""
|
|
145
|
+
payload: Any = None
|
|
146
|
+
if stdout.strip().startswith("{") or stdout.strip().startswith("["):
|
|
147
|
+
try:
|
|
148
|
+
payload = json.loads(stdout)
|
|
149
|
+
except json.JSONDecodeError:
|
|
150
|
+
payload = None
|
|
151
|
+
|
|
152
|
+
escalate = code in ESCALATE_EXIT_CODES
|
|
153
|
+
return {
|
|
154
|
+
"status": "ok" if code in (EXIT_OK, EXIT_FALSE) else EXIT_MEANINGS.get(
|
|
155
|
+
code, "unknown_exit"),
|
|
156
|
+
"exit_code": code,
|
|
157
|
+
"exit_meaning": EXIT_MEANINGS.get(code, "unknown_exit"),
|
|
158
|
+
"stdout": stdout,
|
|
159
|
+
"stderr": stderr[-2000:],
|
|
160
|
+
"json": payload,
|
|
161
|
+
"escalate": ESCALATE_TO_LLM if escalate else None,
|
|
162
|
+
"requires_llm": escalate,
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def is_predicate(text: str, predicate: str, *, threshold: float = DEFAULT_THRESHOLD,
|
|
168
|
+
uncertainty_margin: float = 0.0, use_json: bool = True,
|
|
169
|
+
timeout: float = DEFAULT_TIMEOUT) -> dict[str, Any]:
|
|
170
|
+
"""`semdecide is` — semantic predicate. Exit 2/3/4 escalate to the LLM."""
|
|
171
|
+
argv = ["is", predicate, f"--threshold={threshold}",
|
|
172
|
+
f"--uncertainty-margin={uncertainty_margin}"]
|
|
173
|
+
if use_json:
|
|
174
|
+
argv.append("--json")
|
|
175
|
+
result = _run(argv, stdin_text=text, timeout=timeout)
|
|
176
|
+
result["command"] = "is"
|
|
177
|
+
result["predicate"] = predicate
|
|
178
|
+
if isinstance(result.get("json"), dict):
|
|
179
|
+
result["verdict"] = result["json"].get("verdict")
|
|
180
|
+
result["probability"] = result["json"].get("probability")
|
|
181
|
+
result["confidence"] = result["json"].get("confidence")
|
|
182
|
+
return result
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def filter_records(records: list[dict[str, Any]] | str, predicate: str, *,
|
|
186
|
+
field: str | None = "text", raw: bool = False,
|
|
187
|
+
max_records: int | None = None,
|
|
188
|
+
timeout: float = DEFAULT_TIMEOUT) -> dict[str, Any]:
|
|
189
|
+
"""`semdecide filter` — semantic JSONL filtering (order preserved)."""
|
|
190
|
+
if isinstance(records, str):
|
|
191
|
+
jsonl = records
|
|
192
|
+
else:
|
|
193
|
+
jsonl = "\n".join(json.dumps(r, ensure_ascii=False) for r in records)
|
|
194
|
+
argv = ["filter", predicate]
|
|
195
|
+
if field:
|
|
196
|
+
argv.extend(["--field", field])
|
|
197
|
+
if raw:
|
|
198
|
+
argv.append("--raw")
|
|
199
|
+
if max_records is not None:
|
|
200
|
+
argv.extend(["--max-records", str(int(max_records))])
|
|
201
|
+
result = _run(argv, stdin_text=jsonl + ("\n" if jsonl else ""), timeout=timeout)
|
|
202
|
+
result["command"] = "filter"
|
|
203
|
+
result["predicate"] = predicate
|
|
204
|
+
matched: list[Any] = []
|
|
205
|
+
for line in (result.get("stdout") or "").splitlines():
|
|
206
|
+
line = line.strip()
|
|
207
|
+
if not line:
|
|
208
|
+
continue
|
|
209
|
+
try:
|
|
210
|
+
matched.append(json.loads(line))
|
|
211
|
+
except json.JSONDecodeError:
|
|
212
|
+
matched.append(line)
|
|
213
|
+
result["matched"] = matched
|
|
214
|
+
result["match_count"] = len(matched)
|
|
215
|
+
# filter: exit 1 means no definite match — not an escalation by itself.
|
|
216
|
+
if result.get("exit_code") == EXIT_FALSE:
|
|
217
|
+
result["escalate"] = None
|
|
218
|
+
result["requires_llm"] = False
|
|
219
|
+
return result
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def choose_route(text: str, question: str,
|
|
223
|
+
options: dict[str, str], *,
|
|
224
|
+
min_confidence: float = DEFAULT_MIN_CONFIDENCE,
|
|
225
|
+
use_json: bool = True,
|
|
226
|
+
timeout: float = DEFAULT_TIMEOUT) -> dict[str, Any]:
|
|
227
|
+
"""`semdecide choose` — route among named options. Exit 2/3/4 escalate."""
|
|
228
|
+
argv = ["choose", question, f"--min-confidence={min_confidence}"]
|
|
229
|
+
for key, description in options.items():
|
|
230
|
+
argv.append(f"--option={key}={description}")
|
|
231
|
+
if use_json:
|
|
232
|
+
argv.append("--json")
|
|
233
|
+
result = _run(argv, stdin_text=text, timeout=timeout)
|
|
234
|
+
result["command"] = "choose"
|
|
235
|
+
result["question"] = question
|
|
236
|
+
if isinstance(result.get("json"), dict):
|
|
237
|
+
result["selected"] = result["json"].get("selected") or result["json"].get("choice")
|
|
238
|
+
result["probabilities"] = result["json"].get("probabilities")
|
|
239
|
+
result["confidence"] = result["json"].get("confidence")
|
|
240
|
+
return result
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def escalate_plan(result: dict[str, Any], *, stage: str = "") -> dict[str, Any]:
|
|
244
|
+
"""Build the fail-closed hand-off when exit is 2/3/4 (or binary missing)."""
|
|
245
|
+
code = result.get("exit_code")
|
|
246
|
+
meaning = result.get("exit_meaning") or EXIT_MEANINGS.get(code, "unknown")
|
|
247
|
+
return {
|
|
248
|
+
"action": ESCALATE_TO_LLM,
|
|
249
|
+
"reason": meaning,
|
|
250
|
+
"exit_code": code,
|
|
251
|
+
"stage": stage,
|
|
252
|
+
"policy": {
|
|
253
|
+
2: "invalid input — repair inputs and re-run, or hand the whole "
|
|
254
|
+
"decision to the large model (never invent a verdict)",
|
|
255
|
+
3: "uncertain under margin — route to large model / human; do not "
|
|
256
|
+
"force true/false",
|
|
257
|
+
4: "provider failure — degrade to large model for this call only; "
|
|
258
|
+
"record the attempt",
|
|
259
|
+
}.get(code if isinstance(code, int) else -1,
|
|
260
|
+
"unavailable — use Platform Native / large model path"),
|
|
261
|
+
"raw_status": result.get("status"),
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
__all__ = [
|
|
267
|
+
"DEFAULT_THRESHOLD", "DEFAULT_TIMEOUT", "ESCALATE_TO_LLM",
|
|
268
|
+
"SEMDECIDE_APPROVAL_REQUIRED", "SEMDECIDE_BIN_ENV", "SEMDECIDE_ENV_FILE",
|
|
269
|
+
"SEMDECIDE_UNAVAILABLE", "binary_path", "choose_route", "detect_semdecide",
|
|
270
|
+
"escalate_plan", "filter_records", "is_predicate", "requires_llm_escalation",
|
|
271
|
+
]
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
if __name__ == "__main__":
|
|
275
|
+
# `python3 integrations/semantic_decide.py ...` runs this file as a script,
|
|
276
|
+
# so the repository root must be importable before `integrations.*` is used.
|
|
277
|
+
# The CLI itself lives in integrations/semdecide_cli.py (file-size budget).
|
|
278
|
+
import sys as _sys
|
|
279
|
+
|
|
280
|
+
_ROOT = Path(__file__).resolve().parent.parent
|
|
281
|
+
if str(_ROOT) not in _sys.path:
|
|
282
|
+
_sys.path.insert(0, str(_ROOT))
|
|
283
|
+
|
|
284
|
+
from integrations.semdecide_cli import main as _cli_main
|
|
285
|
+
|
|
286
|
+
raise SystemExit(_cli_main())
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""SemDecide detect CLI: the documented `--experimental <mode>` entry point.
|
|
2
|
+
|
|
3
|
+
Kept separate from `integrations/semantic_decide.py` so the wrapper module stays
|
|
4
|
+
inside the repository's 300-line file budget, the same way the Jev layer keeps
|
|
5
|
+
its CLI in `integrations/jev/cli.py`.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import json
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from integrations.semantic_decide import (
|
|
14
|
+
ESCALATE_EXIT_CODES,
|
|
15
|
+
WRAPPED_COMMANDS,
|
|
16
|
+
detect_semdecide,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def main(argv: list[str] | None = None) -> int:
|
|
21
|
+
"""Detect probe: local binary/credential state plus the resolved mode plan."""
|
|
22
|
+
parser = argparse.ArgumentParser(
|
|
23
|
+
description="SemDecide experimental detect probe (never calls the network)")
|
|
24
|
+
parser.add_argument("--experimental", nargs="?", const="1", default=None,
|
|
25
|
+
metavar="MODE",
|
|
26
|
+
help="enable experimental mode 0|1|2|3 (default 1) and "
|
|
27
|
+
"print the resolved plan")
|
|
28
|
+
args = parser.parse_args(argv)
|
|
29
|
+
|
|
30
|
+
out: dict[str, Any] = {"detect": detect_semdecide()}
|
|
31
|
+
if args.experimental is not None:
|
|
32
|
+
from integrations.jev.config import MODE_HYBRID, MODE_SEMDECIDE, MODE_STANDARD
|
|
33
|
+
from integrations.jev.modes import resolve_experimental_mode
|
|
34
|
+
|
|
35
|
+
mode = resolve_experimental_mode(args.experimental)
|
|
36
|
+
out["experimental"] = {
|
|
37
|
+
"mode": mode,
|
|
38
|
+
"mode_name": {0: "standard", 1: "jev-mcp", 2: "semdecide",
|
|
39
|
+
3: "hybrid"}.get(mode, str(mode)),
|
|
40
|
+
"enabled": mode in (MODE_SEMDECIDE, MODE_HYBRID),
|
|
41
|
+
"active": mode != MODE_STANDARD,
|
|
42
|
+
"commands": list(WRAPPED_COMMANDS),
|
|
43
|
+
"escalate_exit_codes": sorted(ESCALATE_EXIT_CODES),
|
|
44
|
+
"fail_closed": ("exit 2/3/4 and a missing binary escalate to the "
|
|
45
|
+
"large model; filter exit 1 alone does not"),
|
|
46
|
+
}
|
|
47
|
+
print(json.dumps(out, ensure_ascii=False, indent=2))
|
|
48
|
+
return 0
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
__all__ = ["main"]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
if __name__ == "__main__":
|
|
55
|
+
raise SystemExit(main())
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eduevidence",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.0",
|
|
4
4
|
"description": "Evidence research and decision skill with education and organizational policy domains.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "EduEvidence Contributors",
|
|
@@ -40,6 +40,9 @@
|
|
|
40
40
|
"references/",
|
|
41
41
|
"schemas/",
|
|
42
42
|
"scripts/",
|
|
43
|
+
"!scripts/build_esl_artifacts.py",
|
|
44
|
+
"!scripts/generate_new_projects.py",
|
|
45
|
+
"!scripts/enrich_projects_human_and_lieflat.py",
|
|
43
46
|
"retrieval/",
|
|
44
47
|
"integrations/",
|
|
45
48
|
"visualization/eduevidence-report/",
|
|
@@ -52,6 +55,9 @@
|
|
|
52
55
|
"setup.py",
|
|
53
56
|
"docs/architecture.md",
|
|
54
57
|
"docs/sciverse-api.md",
|
|
58
|
+
"docs/reproducibility.md",
|
|
59
|
+
"docs/j-ev-experimental.md",
|
|
60
|
+
"CHANGELOG.md",
|
|
55
61
|
"CONTRIBUTING.md",
|
|
56
62
|
"web/architecture.html",
|
|
57
63
|
"docs/install-guide.md",
|
|
@@ -69,9 +75,11 @@
|
|
|
69
75
|
"examples/ai-coding-assistant-evidence/reports-5themes/*.html",
|
|
70
76
|
"examples/workplace-ai-assistant/*.json",
|
|
71
77
|
"examples/workplace-ai-assistant/*.jsonl",
|
|
78
|
+
"examples/workplace-ai-assistant/*.html",
|
|
72
79
|
"examples/workplace-ai-assistant/reports-5themes/*.html",
|
|
73
80
|
"examples/spaced-retrieval-practice/*.json",
|
|
74
81
|
"examples/spaced-retrieval-practice/*.jsonl",
|
|
82
|
+
"examples/spaced-retrieval-practice/*.html",
|
|
75
83
|
"examples/spaced-retrieval-practice/reports-5themes/*.html",
|
|
76
84
|
"docs/demo-workplace-ai.md",
|
|
77
85
|
"docs/demo.md",
|
package/pyproject.toml
CHANGED
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "eduevidence"
|
|
7
|
-
version = "6.
|
|
7
|
+
version = "6.3.0"
|
|
8
8
|
description = "Evidence research and decision skill with education and organizational policy domains."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -32,7 +32,47 @@
|
|
|
32
32
|
|
|
33
33
|
英文字数上限按等义折算(约为中文字数 ÷ 3 个单词)。
|
|
34
34
|
|
|
35
|
-
## 4.
|
|
35
|
+
## 4. 按域选文案(Domain copy packs)
|
|
36
|
+
|
|
37
|
+
报告层**不得**再写死教育域用语。面向读者的研究文案、章节标题、模块标签与框架枚举一律从领域文案包读取:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
domains/<domain_id>/copy/
|
|
41
|
+
framing_lexicon.json # frame 枚举 + 字段/子字段标签
|
|
42
|
+
section_titles.json # 01–12 章节标题与引导语、默认章节大纲、摘要块标题
|
|
43
|
+
module_labels.json # 域敏感 UI 模块标签(干预/评价/护栏/分组)
|
|
44
|
+
terminology.json # 方法学项标签 + 字段展示名
|
|
45
|
+
risk_constructs.json # 结果分组、结果分离标题/注、构念护栏
|
|
46
|
+
few_shots.json # 四态(adopt / pilot / reject / insufficient_evidence)各 1 例文案
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
中性 UI 词典在 `domains/_neutral/copy/`(`module_labels*.json` 按主题拆分;渲染器 chrome,无研究口吻)。大词典允许主题拆伴文件:`framing_enums.json`、`module_labels_*.json`,加载时并入上述六件套视图。加载与合并:`visualization/eduevidence-report/scripts/report_copy_pack.py`。
|
|
50
|
+
|
|
51
|
+
**域名解析**:`result.meta.domain` → `result.research_frame.extensions.domain`。缺省按 `education`(引擎惯例)并告警;未知域中性回退 + 告警(`strict=True` 则 fail-clear)。
|
|
52
|
+
|
|
53
|
+
**域差异(写作时对照)**:
|
|
54
|
+
|
|
55
|
+
| 概念 | education | policy |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| 干预 | AI 干预 / 教学干预 | **干预方案**(禁止「AI 干预」) |
|
|
58
|
+
| target_learners | 目标学习者 | **目标人群** |
|
|
59
|
+
| 结果分离 | 任务表现 ≠ 学习效果 | 过程产出 ≠ 政策效果 |
|
|
60
|
+
| 评价指标 | 过程 / **学习** / 风险 | 过程 / **效果** / 风险 |
|
|
61
|
+
| 构念护栏 | 任务 vs 学习护栏 | 产出 vs 效果护栏 |
|
|
62
|
+
| 章节 09 | 教学干预 | 干预方案 |
|
|
63
|
+
|
|
64
|
+
**policy 词典硬约束**:`python3 domains/check_copy_packs.py` 断言 policy 文案包不含教学词(教学/学习者/学习指标/AI 干预/…);`report_copy_pack.assert_policy_copy_clean` 在加载时同样 fail-clear。`build_infographics` 遗留的教育 SVG 标题由 `retitle_infographics()` 按域改写,不改适配器本体。
|
|
65
|
+
|
|
66
|
+
**新增第三域(例如 workplace / health)**:
|
|
67
|
+
|
|
68
|
+
1. 在 `domains/manifest.json` 注册 `id` / frame_schema / outcome_taxonomy / methodology_checklist(沿用 v4 领域契约)。
|
|
69
|
+
2. 新建 `domains/<id>/copy/`,写入上述 6 个 JSON(`domain` 字段必须等于 `<id>`)。
|
|
70
|
+
3. 口吻:从 policy 包复制结构,换成该域构念词;禁止混入其他域硬词(用 `check_copy_packs.py` 的词表扩展后自检)。
|
|
71
|
+
4. `few_shots.json` 四态各写 1 例该域决策口吻(≤ 规范字数上限)。
|
|
72
|
+
5. 结果分组:在 `risk_constructs.json` 的 `outcome_groups` 把该域 outcome token 映射到 `task` / `learning` / `risk`(内部桶名不变,展示名由 `module_labels` 决定)。
|
|
73
|
+
6. 跑 `python3 domains/check_copy_packs.py` + 用该域 `result.json` 跑一次 `build_report.py`,确认 HTML 无外域硬词。
|
|
74
|
+
|
|
75
|
+
## 5. 术语对照
|
|
36
76
|
|
|
37
77
|
同一概念全文只用一个说法:
|
|
38
78
|
|
|
@@ -49,7 +89,7 @@
|
|
|
49
89
|
| `transfer` | 迁移 | transfer |
|
|
50
90
|
| `applicability` | 适用性 | applicability |
|
|
51
91
|
|
|
52
|
-
##
|
|
92
|
+
## 6. 禁止
|
|
53
93
|
|
|
54
94
|
- 内部字段名与存储标识(`effect_direction`、`first_programming_course_...`)出现在叙述句里;它们只能出现在「原始标识」提示或溯源展开区。
|
|
55
95
|
- 证据 ID 堆砌(`E-001、E-006`)出现在决策叙事里;引用研究用「作者-年份 + 人话描述」。
|
|
@@ -57,7 +97,7 @@
|
|
|
57
97
|
- 半句截断、`null` 残留、中英夹生。
|
|
58
98
|
- 用兜底句掩盖缺失:字段没产出就如实说没产出。
|
|
59
99
|
|
|
60
|
-
##
|
|
100
|
+
## 7. 门禁如何执行
|
|
61
101
|
|
|
62
102
|
- `check_language_parallel()`:叙述字段必须为对应语言、双语不得完全相同、不得含内部键名。
|
|
63
103
|
- 原始标识检查:`research_frame` 与决策字段中的多词串若含未注册的 `snake_case`,记为缺陷。
|
|
@@ -2,14 +2,21 @@
|
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
3
|
"$id": "https://eduevidence.dev/schemas/v2/decision-snapshot.schema.json",
|
|
4
4
|
"title": "DecisionSnapshot",
|
|
5
|
-
"description": "A revision-bound adjudication. Snapshots are immutable: changing the graph later never mutates an old snapshot file.",
|
|
6
5
|
"type": "object",
|
|
7
6
|
"additionalProperties": false,
|
|
8
7
|
"required": [
|
|
9
|
-
"decision_snapshot_id",
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
8
|
+
"decision_snapshot_id",
|
|
9
|
+
"decision",
|
|
10
|
+
"confidence_label",
|
|
11
|
+
"confidence_score_internal",
|
|
12
|
+
"claim_assessments",
|
|
13
|
+
"key_evidence_links",
|
|
14
|
+
"key_risks",
|
|
15
|
+
"applicability_boundary",
|
|
16
|
+
"missing_evidence",
|
|
17
|
+
"graph_revision",
|
|
18
|
+
"policy_versions",
|
|
19
|
+
"created_at"
|
|
13
20
|
],
|
|
14
21
|
"properties": {
|
|
15
22
|
"decision_snapshot_id": { "type": "string", "pattern": "^DEC-" },
|
|
@@ -17,6 +24,7 @@
|
|
|
17
24
|
"type": "string",
|
|
18
25
|
"enum": ["ADOPT", "PILOT", "REJECT", "INSUFFICIENT_EVIDENCE"]
|
|
19
26
|
},
|
|
27
|
+
"downgrade_reason": { "type": ["string", "null"] },
|
|
20
28
|
"confidence_label": {
|
|
21
29
|
"type": "string",
|
|
22
30
|
"enum": ["High", "Moderate", "Low", "Insufficient"]
|
|
@@ -37,17 +45,20 @@
|
|
|
37
45
|
"type": "array",
|
|
38
46
|
"items": { "type": "string" }
|
|
39
47
|
},
|
|
40
|
-
"applicability_boundary": { "type": "string"
|
|
48
|
+
"applicability_boundary": { "type": "string" },
|
|
41
49
|
"missing_evidence": {
|
|
42
50
|
"type": "array",
|
|
43
51
|
"items": { "type": "string" }
|
|
44
52
|
},
|
|
45
|
-
"graph_revision": { "type": "
|
|
53
|
+
"graph_revision": { "type": ["string", "integer"] },
|
|
46
54
|
"policy_versions": {
|
|
47
55
|
"type": "object",
|
|
48
56
|
"additionalProperties": { "type": "string" }
|
|
49
57
|
},
|
|
50
|
-
"created_at": { "type": "string"
|
|
51
|
-
"extensions": {
|
|
58
|
+
"created_at": { "type": "string" },
|
|
59
|
+
"extensions": {
|
|
60
|
+
"type": "object",
|
|
61
|
+
"additionalProperties": true
|
|
62
|
+
}
|
|
52
63
|
}
|
|
53
64
|
}
|