This commit is contained in:
2026-07-12 21:26:08 +08:00
commit 9dd41afd48
502 changed files with 129901 additions and 0 deletions
+309
View File
@@ -0,0 +1,309 @@
#!/bin/bash
# Regression: build-graph-data.sh should upgrade edge type from default EXTRACTED
# to explicit INFERRED / AMBIGUOUS when the same (from, to) pair is referenced
# multiple times within a single source file.
#
# Motivates fix for:
# scripts/build-graph-data.sh edge-merge awk block —
# previously the condition `saved_conf[key] == ""` was always false once the
# key had been seen (because the ternary in the same branch always filled
# it with "EXTRACTED" or the current conf), so the first occurrence locked
# the edge type forever. Any later `<!-- confidence: INFERRED -->` annotation
# on the same [[link]] in subsequent lines was silently ignored.
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
SCRIPT="$REPO_ROOT/scripts/build-graph-data.sh"
fail() {
echo "FAIL: $1" >&2
exit 1
}
test_explicit_confidence_overrides_default_on_same_page() {
local tmp_dir
tmp_dir="$(mktemp -d)"
trap 'rm -rf "$tmp_dir"' RETURN
mkdir -p "$tmp_dir/wiki/entities"
# A.md: first reference to B has no annotation (default EXTRACTED),
# later reference in the 相关页面 list carries explicit INFERRED.
# Before the fix, first occurrence locks type to EXTRACTED forever.
cat > "$tmp_dir/wiki/entities/A.md" <<'EOF'
---
tags: [test]
---
# A
正文中提到 [[B]] 是一个相关的模块。
## 相关页面
- [[B]] <!-- confidence: INFERRED --> — 推断关联
EOF
cat > "$tmp_dir/wiki/entities/B.md" <<'EOF'
---
tags: [test]
---
# B
## 相关页面
- [[A]] <!-- confidence: INFERRED --> — 推断关联
EOF
# Minimal purpose.md so build-graph-data doesn't bail
cat > "$tmp_dir/purpose.md" <<'EOF'
# 研究目的
test
EOF
LLM_WIKI_TEST_MODE=1 \
bash "$SCRIPT" "$tmp_dir" "$tmp_dir/graph-data.json" > /dev/null 2>&1 \
|| fail "build-graph-data.sh should succeed on fixture"
local a_to_b_type b_to_a_type
a_to_b_type=$(jq -r '.edges[] | select(.from == "A" and .to == "B") | .type' "$tmp_dir/graph-data.json")
b_to_a_type=$(jq -r '.edges[] | select(.from == "B" and .to == "A") | .type' "$tmp_dir/graph-data.json")
[ "$a_to_b_type" = "INFERRED" ] \
|| fail "A→B should be INFERRED after merging (default + explicit INFERRED on same page), got: $a_to_b_type"
[ "$b_to_a_type" = "INFERRED" ] \
|| fail "B→A should be INFERRED (only one reference, explicitly annotated), got: $b_to_a_type"
}
test_ambiguous_overrides_default_when_mixed() {
local tmp_dir
tmp_dir="$(mktemp -d)"
trap 'rm -rf "$tmp_dir"' RETURN
mkdir -p "$tmp_dir/wiki/entities"
# X.md references Y multiple times: plain body + AMBIGUOUS annotation
cat > "$tmp_dir/wiki/entities/X.md" <<'EOF'
---
tags: [test]
---
# X
See [[Y]] in context.
Also mention [[Y]] again.
## 相关页面
- [[Y]] <!-- confidence: AMBIGUOUS --> — 待确认
EOF
cat > "$tmp_dir/wiki/entities/Y.md" <<'EOF'
---
tags: [test]
---
# Y
## 相关页面
- [[X]]
EOF
cat > "$tmp_dir/purpose.md" <<'EOF'
# 研究目的
test
EOF
LLM_WIKI_TEST_MODE=1 \
bash "$SCRIPT" "$tmp_dir" "$tmp_dir/graph-data.json" > /dev/null 2>&1 \
|| fail "build-graph-data.sh should succeed on fixture"
local x_to_y_type
x_to_y_type=$(jq -r '.edges[] | select(.from == "X" and .to == "Y") | .type' "$tmp_dir/graph-data.json")
[ "$x_to_y_type" = "AMBIGUOUS" ] \
|| fail "X→Y should be AMBIGUOUS (default + default + AMBIGUOUS merge), got: $x_to_y_type"
}
test_default_extracted_when_no_annotation() {
local tmp_dir
tmp_dir="$(mktemp -d)"
trap 'rm -rf "$tmp_dir"' RETURN
mkdir -p "$tmp_dir/wiki/entities"
cat > "$tmp_dir/wiki/entities/P.md" <<'EOF'
---
tags: [test]
---
# P
See [[Q]].
## 相关页面
- [[Q]]
EOF
cat > "$tmp_dir/wiki/entities/Q.md" <<'EOF'
---
tags: [test]
---
# Q
## 相关页面
- [[P]]
EOF
cat > "$tmp_dir/purpose.md" <<'EOF'
# 研究目的
test
EOF
LLM_WIKI_TEST_MODE=1 \
bash "$SCRIPT" "$tmp_dir" "$tmp_dir/graph-data.json" > /dev/null 2>&1 \
|| fail "build-graph-data.sh should succeed on fixture"
local p_to_q_type
p_to_q_type=$(jq -r '.edges[] | select(.from == "P" and .to == "Q") | .type' "$tmp_dir/graph-data.json")
[ "$p_to_q_type" = "EXTRACTED" ] \
|| fail "P→Q should default to EXTRACTED when no annotation exists, got: $p_to_q_type"
}
test_first_explicit_wins_among_multiple_explicit() {
# When two different explicit confidence values appear for the same
# (from, to) pair within the same page, the first-seen one should win
# (stable + deterministic). This keeps semantics aligned with the
# existing "first occurrence" comment in the awk block.
local tmp_dir
tmp_dir="$(mktemp -d)"
trap 'rm -rf "$tmp_dir"' RETURN
mkdir -p "$tmp_dir/wiki/entities"
cat > "$tmp_dir/wiki/entities/M.md" <<'EOF'
---
tags: [test]
---
# M
See [[N]] <!-- confidence: INFERRED --> in the body.
## 相关页面
- [[N]] <!-- confidence: AMBIGUOUS --> — should NOT override INFERRED
EOF
cat > "$tmp_dir/wiki/entities/N.md" <<'EOF'
---
tags: [test]
---
# N
## 相关页面
- [[M]]
EOF
cat > "$tmp_dir/purpose.md" <<'EOF'
# 研究目的
test
EOF
LLM_WIKI_TEST_MODE=1 \
bash "$SCRIPT" "$tmp_dir" "$tmp_dir/graph-data.json" > /dev/null 2>&1 \
|| fail "build-graph-data.sh should succeed on fixture"
local m_to_n_type
m_to_n_type=$(jq -r '.edges[] | select(.from == "M" and .to == "N") | .type' "$tmp_dir/graph-data.json")
[ "$m_to_n_type" = "INFERRED" ] \
|| fail "M→N should keep first explicit INFERRED, got: $m_to_n_type"
}
test_edge_contract_separates_relation_type_and_confidence() {
local tmp_dir
tmp_dir="$(mktemp -d)"
trap 'rm -rf "$tmp_dir"' RETURN
mkdir -p "$tmp_dir/wiki/entities"
cat > "$tmp_dir/wiki/entities/A.md" <<'EOF'
---
tags: [test]
---
# A
See [[B]] without labels first.
## 相关页面
- [[B]] <!-- relation: 实现 --> <!-- confidence: INFERRED --> — explicit contract
- [[C]] — default contract
EOF
cat > "$tmp_dir/wiki/entities/B.md" <<'EOF'
---
tags: [test]
---
# B
EOF
cat > "$tmp_dir/wiki/entities/C.md" <<'EOF'
---
tags: [test]
---
# C
EOF
cat > "$tmp_dir/purpose.md" <<'EOF'
# 研究目的
test
EOF
LLM_WIKI_TEST_MODE=1 \
bash "$SCRIPT" "$tmp_dir" "$tmp_dir/graph-data.json" > /dev/null 2>&1 \
|| fail "build-graph-data.sh should succeed on relation contract fixture"
local a_to_b_type a_to_b_confidence a_to_b_relation
a_to_b_type=$(jq -r '.edges[] | select(.from == "A" and .to == "B") | .type' "$tmp_dir/graph-data.json")
a_to_b_confidence=$(jq -r '.edges[] | select(.from == "A" and .to == "B") | .confidence' "$tmp_dir/graph-data.json")
a_to_b_relation=$(jq -r '.edges[] | select(.from == "A" and .to == "B") | .relation_type' "$tmp_dir/graph-data.json")
[ "$a_to_b_type" = "INFERRED" ] \
|| fail "A→B legacy type should remain INFERRED for compatibility, got: $a_to_b_type"
[ "$a_to_b_confidence" = "INFERRED" ] \
|| fail "A→B confidence should be INFERRED, got: $a_to_b_confidence"
[ "$a_to_b_relation" = "实现" ] \
|| fail "A→B relation_type should be 实现, got: $a_to_b_relation"
local a_to_c_confidence a_to_c_relation
a_to_c_confidence=$(jq -r '.edges[] | select(.from == "A" and .to == "C") | .confidence' "$tmp_dir/graph-data.json")
a_to_c_relation=$(jq -r '.edges[] | select(.from == "A" and .to == "C") | .relation_type' "$tmp_dir/graph-data.json")
[ "$a_to_c_confidence" = "EXTRACTED" ] \
|| fail "A→C confidence should default to EXTRACTED, got: $a_to_c_confidence"
[ "$a_to_c_relation" = "依赖" ] \
|| fail "A→C relation_type should default to 依赖, got: $a_to_c_relation"
}
test_explicit_confidence_overrides_default_on_same_page
test_ambiguous_overrides_default_when_mixed
test_default_extracted_when_no_annotation
test_first_explicit_wins_among_multiple_explicit
test_edge_contract_separates_relation_type_and_confidence
echo "PASS: graph-data-confidence-merge.regression-1"