Skip to content

Commit cd571b7

Browse files
xuefei-wangclaude
andcommitted
fix(dashboard): clean mid-word truncated Evolving-knowledge insights
Insight `text` in knowledge.json/arc1_knowledge.json was hard-clamped to 240 chars (raw insights 1200) mid-word by a pre-#690 distillation pass. The full wording was never persisted, so back each cut off to the last word boundary and append an ellipsis so it reads as intentional. Add declamp_insights.py to reapply after any rebuild from the legacy knowledge DBs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent f6f41da commit cd571b7

3 files changed

Lines changed: 5858 additions & 5777 deletions

File tree

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#!/usr/bin/env python3
2+
"""Cosmetic cleanup for hard-clamped insight text in the dashboard data files.
3+
4+
Upstream (pre-#690) KCSI distillation wrote insight `text` with a hard
5+
``text[:cap]`` slice, cutting operative clauses mid-word (e.g. "...not
6+
agent-logic fa"). The full text was never persisted, so it cannot be restored
7+
— this only makes the surviving truncation *read* as intentional: back the cut
8+
off to the last word boundary and append an ellipsis ("...not agent-logic…").
9+
10+
A field is treated as clamped when its length is exactly one of the known cap
11+
values AND it does not already end in terminal punctuation (so naturally short
12+
or already-complete insights are left untouched). Idempotent and safe to re-run
13+
after any dashboard rebuild from the legacy knowledge DBs.
14+
"""
15+
from __future__ import annotations
16+
17+
import json
18+
import sys
19+
from pathlib import Path
20+
21+
# Hard-slice cap values used by the legacy distillation / builder paths.
22+
CAPS = {240, 1200}
23+
# Keys whose string values are insight bodies (the "curated voice" surface).
24+
TEXT_KEYS = {"text", "insight"}
25+
TERMINAL = ".!?…"
26+
27+
28+
def declamp(text: str) -> str | None:
29+
"""Return cleaned text, or None if the value was not a mid-word clamp."""
30+
if len(text) not in CAPS or not text:
31+
return None
32+
if text[-1] in TERMINAL:
33+
return None # ends cleanly — almost certainly not a mid-word cut
34+
space = text.rfind(" ")
35+
# Only back off to a boundary in the latter half; otherwise keep the words.
36+
base = text[:space] if space > len(text) // 2 else text
37+
base = base.rstrip(" ,;:-—")
38+
return base + "…"
39+
40+
41+
def walk(node, stats: dict[str, int]) -> None:
42+
if isinstance(node, dict):
43+
for key, val in node.items():
44+
if key in TEXT_KEYS and isinstance(val, str):
45+
fixed = declamp(val)
46+
if fixed is not None:
47+
node[key] = fixed
48+
stats["fixed"] += 1
49+
else:
50+
walk(val, stats)
51+
elif isinstance(node, list):
52+
for val in node:
53+
walk(val, stats)
54+
55+
56+
def process(path: Path) -> None:
57+
data = json.loads(path.read_text(encoding="utf-8"))
58+
stats = {"fixed": 0}
59+
walk(data, stats)
60+
# Match the builder's writer exactly (indent=2, ensure_ascii=True) so the
61+
# diff touches only the changed text lines.
62+
path.write_text(json.dumps(data, indent=2), encoding="utf-8")
63+
print(f"{path.name}: declamped {stats['fixed']} insight fields")
64+
65+
66+
def main(argv: list[str]) -> int:
67+
here = Path(__file__).resolve().parents[1] / "static" / "data"
68+
targets = [Path(a) for a in argv[1:]] or [
69+
here / "knowledge.json",
70+
here / "arc1_knowledge.json",
71+
]
72+
for p in targets:
73+
if not p.exists():
74+
print(f"skip (missing): {p}", file=sys.stderr)
75+
continue
76+
process(p)
77+
return 0
78+
79+
80+
if __name__ == "__main__":
81+
raise SystemExit(main(sys.argv))

0 commit comments

Comments
 (0)