@chrono-meta/fh-gate 2.15.0 → 3.0.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/.claude/rules/fh_4axis_gate.md +38 -0
- package/.claude-plugin/marketplace.json +2 -2
- package/CLAUDE.md +1 -1
- package/README.md +6 -1
- package/knowledge/shared/harness-core/fh_three_layer_canon.md +47 -0
- package/knowledge/shared/harness-core/field_verdict_crossfamily_gate.md +10 -0
- package/knowledge/shared/harness-core/measurement-integrity-checklist.md +15 -1
- package/knowledge/shared/harness-core/ship_readiness_gate.md +20 -3
- package/knowledge/shared/learnings/subagent_invocations_log.yaml +111 -0
- package/package.json +9 -1
- package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-meta/CHANGELOG.md +49 -0
- package/plugins/fh-meta/skills/salience-splitter/SKILL.md +1 -1
- package/scripts/backtick_guard.sh +194 -0
- package/scripts/capability_effect_probe.sh +14 -1
- package/scripts/context_continuity_score.sh +106 -12
- package/scripts/fh-gate.sh +3 -3
- package/scripts/files_manifest_shipping_check.sh +19 -0
- package/scripts/gate_pathspec_check.sh +1 -1
- package/scripts/package_coverage_check.sh +25 -2
- package/scripts/proposal_hook.sh +89 -0
- package/scripts/public_surface_scan_files.sh +11 -2
- package/scripts/revert_probe.sh +250 -0
- package/scripts/selfcheck.sh +67 -3
- package/scripts/sim_isolated_run.sh +97 -7
- package/scripts/test_backtick_guard_lanes.sh +115 -0
- package/scripts/test_degrade_scan_shell_probes.sh +7 -7
- package/scripts/test_files_manifest_shipping_lanes.sh +5 -5
- package/scripts/test_heavy_classifier_lanes.sh +1 -1
- package/scripts/test_lane_runner_lanes.sh +59 -33
- package/scripts/test_mapped_tracks_lanes.sh +1 -1
- package/scripts/test_marker_soul_check_lanes.sh +24 -0
- package/scripts/test_node_check_lanes.sh +34 -34
- package/scripts/test_package_coverage_lanes.sh +53 -27
- package/scripts/test_pipe_verdict_guard_lanes.sh +5 -5
- package/scripts/test_precommit_pointer_index_lanes.sh +33 -0
- package/scripts/test_preprep_drift_anchor.sh +13 -4
- package/scripts/test_preprep_drift_anchor_lanes.sh +23 -0
- package/scripts/test_proposal_hook_lanes.sh +36 -0
- package/scripts/test_revert_probe_lanes.sh +146 -0
- package/scripts/test_session_close_lanes.sh +3 -5
- package/scripts/test_sim_isolated_run_lanes.sh +17 -0
- package/scripts/test_verdict_watermark_lanes.sh +27 -1
- package/scripts/utterance_landing_check.sh +2 -2
- package/templates/.git-hooks/pre-commit +27 -4
- package/templates/settings.PreToolUse.snippet.json +37 -1
- package/plugins/fh-commons/README.md +0 -38
|
@@ -63,6 +63,7 @@ ACCEPTED_ABSENT=(
|
|
|
63
63
|
"scripts/round/target_pin.sh"
|
|
64
64
|
"scripts/round/instrument_manifest.sh"
|
|
65
65
|
"scripts/round/eligcheck_qset.sh"
|
|
66
|
+
"scripts/round/gatecheck_qset.sh" # 같은 이유 — 회차 개시 게이트, 소비자 표면 아님 (2026-09-02 짝표 등재로 참조가 생겼다)
|
|
66
67
|
"scripts/test_round_instruments_lanes.sh"
|
|
67
68
|
"scripts/fixtures/isolation_assembly_BROKEN_2026-08-30_ccrun7.json" # 역사 산출물(등급표가 증거로 인용)
|
|
68
69
|
"scripts/outbound_query_guard.sh"
|
|
@@ -359,12 +360,34 @@ if ORACLE == 'tarball':
|
|
|
359
360
|
if r.returncode != 0:
|
|
360
361
|
print(f"ORACLE_UNAVAILABLE\tnpm pack exited {r.returncode}")
|
|
361
362
|
raise SystemExit(2)
|
|
362
|
-
|
|
363
|
+
# Two shapes have been seen for `npm pack --dry-run --json`: the documented one carries
|
|
364
|
+
# `[0]['files'][*]['path']`; inside `npm publish`'s prepublishOnly on the CI runner
|
|
365
|
+
# (Node 22 / npm 10, 2026-09-04, v3.0.0 first OIDC publish) the same call returned JSON
|
|
366
|
+
# WITHOUT that key and this block died with a bare KeyError — fail-closed (correct) but
|
|
367
|
+
# blind (no diagnosis). Parse defensively, and when the JSON does not carry a file list
|
|
368
|
+
# fall back to the text listing (`npm notice <size> <path>` lines), which is what a human
|
|
369
|
+
# reads. The diagnostic line prints the head of stdout so the NEXT failure names its shape.
|
|
370
|
+
parsed = json.loads(r.stdout)
|
|
371
|
+
entry = parsed[0] if isinstance(parsed, list) and parsed else (parsed if isinstance(parsed, dict) else None)
|
|
372
|
+
flist = (entry or {}).get('files') if isinstance(entry, dict) else None
|
|
373
|
+
if flist and all(isinstance(f, dict) and 'path' in f for f in flist):
|
|
374
|
+
packed = {f['path'] for f in flist}
|
|
375
|
+
else:
|
|
376
|
+
t = subprocess.run(['npm', 'pack', '--dry-run'], capture_output=True, text=True, timeout=180)
|
|
377
|
+
lines = (t.stdout + '\n' + t.stderr).splitlines()
|
|
378
|
+
packed = set()
|
|
379
|
+
for ln in lines:
|
|
380
|
+
m = re.match(r'^npm notice\s+[0-9.]+[kMG]?B\s+(\S+)\s*$', ln)
|
|
381
|
+
if m:
|
|
382
|
+
packed.add(m.group(1))
|
|
383
|
+
if not packed:
|
|
384
|
+
print(f"ORACLE_UNAVAILABLE\tnpm pack --json had no files[].path and the text listing had no file lines; json head: {r.stdout[:200]!r}")
|
|
385
|
+
raise SystemExit(2)
|
|
363
386
|
except FileNotFoundError:
|
|
364
387
|
print("ORACLE_UNAVAILABLE\tnpm is not on PATH — the tarball cannot be read")
|
|
365
388
|
raise SystemExit(2)
|
|
366
389
|
except (json.JSONDecodeError, KeyError, IndexError) as e:
|
|
367
|
-
print(f"ORACLE_UNAVAILABLE\tnpm pack --json did not parse ({type(e).__name__})")
|
|
390
|
+
print(f"ORACLE_UNAVAILABLE\tnpm pack --json did not parse ({type(e).__name__}); stdout head: {r.stdout[:200]!r}")
|
|
368
391
|
raise SystemExit(2)
|
|
369
392
|
except subprocess.TimeoutExpired:
|
|
370
393
|
print("ORACLE_UNAVAILABLE\tnpm pack timed out")
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# proposal_hook.sh — PreToolUse(Edit|Write|Bash) advisory: a verdict/guard line in scripts/**/*.sh or
|
|
3
|
+
# templates/*.sh is about to change → put ONE proposal instruction into the model's context
|
|
4
|
+
# (additionalContext): «offer the user a known-pair control + degrade_direction_scan.sh in one line».
|
|
5
|
+
#
|
|
6
|
+
# WHY A HOOK (identity ⑤, measured 2026-09-03)
|
|
7
|
+
# r3: a CLAUDE.md table row keyed on this exact file class fired 1/15 at floor tier (that 1 a
|
|
8
|
+
# recitation). r4: this hook, installed in a disposable clone, fired on 9/10 editing reps and the
|
|
9
|
+
# floor session relayed the proposal 9/9; the hard negative (a usage-string edit in a .sh) 0/5.
|
|
10
|
+
# Same tasks, same tier, same prose layer — the row 0, the channel 100%. That is the
|
|
11
|
+
# «explicit instruction 3/3 · advisory 0/3 · framing 0/3» result of 2026-08-21 seen a second time
|
|
12
|
+
# (tracks/_meta/RESULT_2026-09-03_identity5-r4.md · prior_art_prompt.sh header). A channel is
|
|
13
|
+
# built at the channel (§Mechanization Boundary); what the proposal SAYS stays the model's.
|
|
14
|
+
#
|
|
15
|
+
# WHAT IT DOES NOT CLAIM
|
|
16
|
+
# Relaying an injected instruction is not initiative. Of r4's 9 hits, 4 carried task-specific
|
|
17
|
+
# substance beyond the hook's own wording (the K1 «judgment residue» — innovator signal
|
|
18
|
+
# fh_signal_2026-09-03_innovator-identity5-r4.md). This hook opens the window; whether ⑤'s bar
|
|
19
|
+
# («proposes unasked») counts a relayed proposal is the operator's call, recorded there, not here.
|
|
20
|
+
#
|
|
21
|
+
# DISCRIMINATOR (mechanical, quote-aware where it can be)
|
|
22
|
+
# file class : scripts/**/*.sh · templates/*.sh (docs, tracks, tests-as-fixtures: no)
|
|
23
|
+
# edit kind : the touched text carries a verdict/guard token — exit N · return N ·
|
|
24
|
+
# `|| continue|exit|true|return` · `&& continue|exit` · -ne/-eq/-gt/-lt · ==/!= ·
|
|
25
|
+
# `[ -e/-f/-s/-n/-z` · comm/diff/cmp · grep -q — AND for Edit the change is not
|
|
26
|
+
# confined to quoted strings (old/new with quotes stripped must differ). A usage
|
|
27
|
+
# string that happens to contain `exit 2` does not fire (r4 HARD 0/5).
|
|
28
|
+
# Bash path : an edit made through the shell (sed -i · > · >> · tee) — the r4 miss (T2 r5 edited
|
|
29
|
+
# via Bash, hook 0). No old/new here, so the rule is weaker: target file class AND the
|
|
30
|
+
# the RAW command text (quotes included) carries a token. Quotes are NOT stripped on
|
|
31
|
+
# this path — in `sed -i 's/exit 1/exit 2/' x.sh` the token is inside the quotes by
|
|
32
|
+
# construction, and stripping made the path silent on exactly the shape it exists for
|
|
33
|
+
# (found by the Air node 2026-09-03; the original lane's known-positive only fired
|
|
34
|
+
# because its token sat outside the quotes). Named residual now: over-fire when a
|
|
35
|
+
# quoted token elsewhere in the command co-occurs with a script-file edit (advisory).
|
|
36
|
+
#
|
|
37
|
+
# DEGRADE DIRECTION: advisory, exit 0 always, no permissionDecision (same contract as pipe_verdict_guard).
|
|
38
|
+
# Unparseable payload → silent. python3 absent → silent (a dead interpreter must not block edits).
|
|
39
|
+
# Evidence line appended to $CLAUDE_PROJECT_DIR/.claude/.proposal_hook_events.tsv (gitignored dir)
|
|
40
|
+
# so a sim arm can prove the hook fired INSIDE its clone (runner header: absence of that file
|
|
41
|
+
# invalidates the arm, never the hypothesis).
|
|
42
|
+
# Opt out on one call with `# noqa: proposal-hook`.
|
|
43
|
+
# test: printf '%s' '<PreToolUse JSON>' | bash scripts/proposal_hook.sh
|
|
44
|
+
set -u
|
|
45
|
+
RAW=$(cat 2>/dev/null || true)
|
|
46
|
+
printf '%s' "$RAW" | grep -qE '#[[:space:]]*noqa:?[[:space:]]*proposal-hook' && exit 0
|
|
47
|
+
read -r FP FLAG < <(printf '%s' "$RAW" | python3 -c '
|
|
48
|
+
import json,sys,re
|
|
49
|
+
try: d=json.load(sys.stdin)
|
|
50
|
+
except Exception: print("",""); sys.exit(0)
|
|
51
|
+
tn=d.get("tool_name",""); ti=d.get("tool_input",{}) or {}
|
|
52
|
+
TOK=r"exit [0-9]|return [0-9]|\|\| *(continue|exit|true|return)|&& *(continue|exit)|-ne |-eq |-gt |-lt | == | != |\[ -[efsnz] |\bcomm |\bdiff |\bcmp |grep -q"
|
|
53
|
+
def strip(x): return re.sub(r"\"[^\"]*\"|\x27[^\x27]*\x27","",x)
|
|
54
|
+
fp=""; flag="0"
|
|
55
|
+
if tn in ("Edit","Write"):
|
|
56
|
+
fp=ti.get("file_path","") or ""
|
|
57
|
+
old=ti.get("old_string","") or ""; new=(ti.get("new_string","") or ti.get("content","") or "")
|
|
58
|
+
touches=bool(re.search(TOK, old+"\n"+new)); real=strip(old).strip()!=strip(new).strip()
|
|
59
|
+
flag="1" if (touches and real) else "0"
|
|
60
|
+
elif tn=="Bash":
|
|
61
|
+
cmd=(ti.get("command","") or "").replace("\n"," ")
|
|
62
|
+
m=re.search(r"(?:sed\s+-i\S*(?:\s+(?:\x27[^\x27]*\x27|\"[^\"]*\"|\S+)){1,2}\s+|>>?\s*|tee\s+(?:-a\s+)?)[\"\x27]?([^\s\"\x27|;&)<>]+\.sh)\b", cmd)
|
|
63
|
+
if m:
|
|
64
|
+
fp=m.group(1); flag="1" if re.search(TOK, cmd) else "0" # raw cmd, NOT strip(): in a sed -i the token lives INSIDE the quoted expression by construction (Air 2026-09-03: a1 silent, known-positive only fired because its token sat outside the quotes)
|
|
65
|
+
print(fp, flag)
|
|
66
|
+
' 2>/dev/null) || exit 0
|
|
67
|
+
[ -n "${FP:-}" ] || exit 0
|
|
68
|
+
case "$FP" in *scripts/*.sh|*templates/*.sh|scripts/*.sh|templates/*.sh|*/.git-hooks/*|.git-hooks/*) ;; *) exit 0 ;; esac # .git-hooks/* has no .sh suffix — the gate files themselves were outside the filter (arm C wt2 2026-09-03: pre-commit edit, no FIRE)
|
|
69
|
+
[ "${FLAG:-0}" = 1 ] || exit 0
|
|
70
|
+
_D="${CLAUDE_PROJECT_DIR:-.}/.claude"; mkdir -p "$_D" 2>/dev/null
|
|
71
|
+
printf '%s\t%s\t%s\n' "$(date -u +%FT%TZ)" "FIRE" "$FP" >> "$_D/.proposal_hook_events.tsv" 2>/dev/null
|
|
72
|
+
# ── Fact lines (r8, 2026-09-03): the two preconditions of the proposal are DETERMINISTIC, so the hook checks
|
|
73
|
+
# them itself and carries the result as a «사실» line — agents propose, solvers verify. Measured r8: on the
|
|
74
|
+
# stimulus whose grounds sit in a neighbouring file, wording-only (r7 B) got 1/5 withdraw/amend, fact lines
|
|
75
|
+
# got 4/5 (hand-judged, n=5). Sonnet used the fact as an INPUT (one rep rejected a stale fact against a
|
|
76
|
+
# reproduced bug; one opened the scan file itself) — it did not recite it.
|
|
77
|
+
# Self-lane case: editing `scripts/test_X_lanes.sh` IS the lane — r8's discriminator missed it and emitted
|
|
78
|
+
# a proposal for a lane that already was the file. Fixed here (F2 lane).
|
|
79
|
+
_ROOT="${CLAUDE_PROJECT_DIR:-.}"; _BN=$(basename "$FP" .sh); _FACT=""; _ITEMS=""
|
|
80
|
+
case "$_BN" in
|
|
81
|
+
test_*_lanes) _FACT="$_FACT · 사실: 이 파일 자체가 레인(known-pair 픽스처)이다 — 새 known-pair 컨트롤은 «이 파일 안에» 추가하거나 생략" ;;
|
|
82
|
+
*) if [ -f "$_ROOT/scripts/test_${_BN}_lanes.sh" ]; then _FACT="$_FACT · 사실: 이 파일의 레인 \`scripts/test_${_BN}_lanes.sh\` 가 이미 있다(known-pair 컨트롤은 거기에 붙이거나 생략)"; else _ITEMS="$_ITEMS known-pair(고친 케이스 + 반대 케이스) 컨트롤"; fi ;;
|
|
83
|
+
esac
|
|
84
|
+
_SCAN=$(ls -t "$_ROOT"/scripts/.degrade_scan_last_*.txt 2>/dev/null | head -1)
|
|
85
|
+
if [ -n "$_SCAN" ] && grep -q -- "$(basename "$FP")" "$_SCAN" 2>/dev/null; then _FACT="$_FACT · 사실: 오늘 degrade_direction_scan 결과 \`$(basename "$_SCAN")\` 가 이 파일을 이미 덮었다($(grep -m1 -oE 'findings: [0-9]+' "$_SCAN" 2>/dev/null || echo 'findings: ?')) — 스캐너 통과이지 손 확인이 아니다"; else _ITEMS="$_ITEMS degrade_direction_scan.sh 로 실패가 «통과»로 접히는 방향 확인"; fi
|
|
86
|
+
if [ -z "$_ITEMS" ]; then MSG="🔎 방금 고치는 줄은 판정·가드·종료코드 줄이다(scripts/*.sh · templates/*.sh)${_FACT}. 둘 다 이미 있으니 새 제안은 내지 말고, 작업을 마친 뒤 그 사실을 한 줄로만 말해라(형식: «확인 | basis: <위 사실>»)."
|
|
87
|
+
else MSG="🔎 방금 고치는 줄은 판정·가드·종료코드 줄이다(scripts/*.sh · templates/*.sh)${_FACT}. 없는 것만 사용자에게 한 줄로 제안해라 —${_ITEMS} — 형식은 «제안: … | basis: <네가 확인한 근거 한 구절>». 가능하면 이 파일·이 케이스의 실제 이름으로. 제안이지 실행이 아니다."; fi
|
|
88
|
+
python3 -c 'import json,sys; m=sys.argv[1]; print(json.dumps({"systemMessage":m,"hookSpecificOutput":{"hookEventName":"PreToolUse","additionalContext":m}}, ensure_ascii=False))' "$MSG" 2>/dev/null || exit 0
|
|
89
|
+
exit 0
|
|
@@ -124,7 +124,16 @@ fi
|
|
|
124
124
|
# content-generating lifecycle is ever added.
|
|
125
125
|
# ── Resolve the exact npm-published file set (fail-closed if unresolved OR partial) ──
|
|
126
126
|
FILES=$(npm pack --dry-run --json 2>/dev/null \
|
|
127
|
-
| node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{JSON.parse(s)[0].files.forEach(f=>console.log(f.path))}catch(e){process.exit(3)}})' 2>/dev/null
|
|
127
|
+
| node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{JSON.parse(s)[0].files.forEach(f=>console.log(f.path))}catch(e){process.exit(3)}})' 2>/dev/null)
|
|
128
|
+
# 2026-09-04 (v3.0.0 first OIDC publish): on the CI runner, inside `npm publish`'s prepublishOnly,
|
|
129
|
+
# `npm pack --dry-run --json` returned JSON WITHOUT files[] and this resolution came back EMPTY —
|
|
130
|
+
# fail-closed (correct) but the publish could not proceed at all. Same fallback as the other two
|
|
131
|
+
# tarball readers (package_coverage_check.sh · files_manifest_shipping_check.sh): rebuild the set
|
|
132
|
+
# from the text listing (`npm notice <size> <path>`). Still fail-closed when THAT is empty too.
|
|
133
|
+
if [ -z "$FILES" ]; then
|
|
134
|
+
FILES=$(npm pack --dry-run 2>&1 | sed -nE 's/^npm notice +[0-9.]+[kMG]?B +([^ ]+) *$/\1/p')
|
|
135
|
+
[ -n "$FILES" ] && echo " ⚠️ npm pack --json carried no files[] — file set rebuilt from the text listing ($(printf '%s\n' "$FILES" | wc -l | tr -d ' ') paths)"
|
|
136
|
+
fi
|
|
128
137
|
if [ -z "$FILES" ]; then
|
|
129
138
|
echo " ❌ could not resolve the npm-published file set (npm pack --dry-run failed)."
|
|
130
139
|
[ "${PUBLIC_SURFACE_OK:-0}" = "1" ] && { echo " ⚠️ proceeding by PUBLIC_SURFACE_OK=1"; exit 0; }
|
|
@@ -135,7 +144,7 @@ fi
|
|
|
135
144
|
# Wrong-set guard (challenger M6): a future npm --json shape change could yield a NON-empty but PARTIAL
|
|
136
145
|
# file list (forEach iterates a renamed/nested structure without throwing) → files silently unscanned.
|
|
137
146
|
# npm always ships package.json in the tarball; its absence means the parse got a wrong set → fail-closed.
|
|
138
|
-
if ! printf '%s\n' "$FILES" | grep -qx "package.json"; then
|
|
147
|
+
if ! printf '%s\n' "$FILES" | grep -qx "package.json"; then # portability-noqa: checks npm's own packaging invariant (every npm tarball ships package.json), not a repo-specific fixture read from disk — true for any ported npm package
|
|
139
148
|
echo " ❌ published file set looks wrong — 'package.json' (always shipped) is absent from the parse."
|
|
140
149
|
[ "${PUBLIC_SURFACE_OK:-0}" = "1" ] && { echo " ⚠️ proceeding by PUBLIC_SURFACE_OK=1"; exit 0; }
|
|
141
150
|
echo " Fail-closed (possible npm --json shape change). Verify npm pack output or PUBLIC_SURFACE_OK=1."
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# revert_probe.sh — general-purpose ⓕ revert-and-observe probe (rung 강화 #2, six_axis_review_2026-09-04).
|
|
3
|
+
#
|
|
4
|
+
# WHY THIS EXISTS. `되돌림`(revert-and-observe) has been done by hand 15+ times in this repo's own
|
|
5
|
+
# history (see 6축 실측: ⓕ none 비율 57.4%, «정확히 4레인 적색» 급 실물은 매번 손으로 짠 1회성
|
|
6
|
+
# 스크립트였다). Hand-rolled revert probes are the exact shape §Mechanize-at-repetition names —
|
|
7
|
+
# N≥3 recurrence on the SAME operation (swap a file to an older version, rerun a suite, read which
|
|
8
|
+
# lines changed color) is a mechanization trigger, not a one-off. This is that mechanization.
|
|
9
|
+
#
|
|
10
|
+
# 🟥 FRONTIER WARNING — read before citing this tool's PASS as "the suite has detection power".
|
|
11
|
+
# A single revert of a single file is ONE mutant. Mutation-testing research (arXiv 2607.22880,
|
|
12
|
+
# and the companion Meta engineering write-up cited alongside it in
|
|
13
|
+
# frontier_verification_map_2026-09-04.md §ⓕ) found that coverage/mutation SCORE loses its
|
|
14
|
+
# correlation with real fault-detection effectiveness once suite size is controlled for — a
|
|
15
|
+
# single kill is evidence the ANCHOR under test is load-bearing for THIS ONE reverted file,
|
|
16
|
+
# never a general claim that the suite "has good mutation coverage" or "catches regressions".
|
|
17
|
+
# Run this against every file you actually care about; do not average or extrapolate from one.
|
|
18
|
+
#
|
|
19
|
+
# WHAT IT DOES
|
|
20
|
+
# 1. Runs the lane suite AS-IS against the file's CURRENT (working-tree) content — the "수리 후"
|
|
21
|
+
# run.
|
|
22
|
+
# 2. Swaps ONLY the target file to its content at --baseline (default HEAD), backing up the
|
|
23
|
+
# current content first.
|
|
24
|
+
# 3. Reruns the same lane suite against that swapped-in baseline content — the "되돌린" run.
|
|
25
|
+
# 4. Restores the target file to its exact pre-probe content — ALWAYS, even if either suite run
|
|
26
|
+
# crashes, hangs past its timeout, or this script itself errors. Restore is attempted from an
|
|
27
|
+
# EXIT trap (safety net) in addition to the normal-path restore, so a `kill`-free abnormal
|
|
28
|
+
# exit still restores. Physical restore, never `git checkout <ref> -- <path>` — that stages
|
|
29
|
+
# the revert into the index ([[feedback_git_checkout_path_stages_the_revert]]); this tool
|
|
30
|
+
# writes bytes to the file only, with `git show <ref>:<path>` (never `git checkout`), and
|
|
31
|
+
# never touches the index.
|
|
32
|
+
# 5. Diffs the two runs' ✅/❌ label lines (this repo's universal `ok()`/`no()` convention —
|
|
33
|
+
# every test_*.sh / *_lanes.sh in scripts/ prints ` ✅ <label>` / ` ❌ <label>`) and reports
|
|
34
|
+
# EXACTLY which labels flipped from ✅ (current) to ❌ (baseline) — i.e. which lane actually
|
|
35
|
+
# went red when the fix was undone.
|
|
36
|
+
#
|
|
37
|
+
# WHAT IT ASSUMES (named, not hidden): the lane suite's ✅/❌ label text is STABLE across the two
|
|
38
|
+
# runs for a given lane (the suite script itself does not change between the two invocations —
|
|
39
|
+
# only the target file's content does). A suite whose pass/fail label text is dynamically built
|
|
40
|
+
# from data that changes with the target file (e.g. embeds a byte count in the label) will show
|
|
41
|
+
# as "label only in one run" rather than a flip — reported honestly as UNMATCHED, not silently
|
|
42
|
+
# dropped, and not counted toward the flip total.
|
|
43
|
+
#
|
|
44
|
+
# USAGE
|
|
45
|
+
# bash scripts/revert_probe.sh <target-file> <lane-suite-script> [--baseline <ref>] [--timeout <sec>]
|
|
46
|
+
# bash scripts/revert_probe.sh scripts/foo.sh scripts/test_foo_lanes.sh
|
|
47
|
+
# bash scripts/revert_probe.sh scripts/foo.sh scripts/test_foo_lanes.sh --baseline HEAD~1
|
|
48
|
+
#
|
|
49
|
+
# EXIT CODES (fail-closed, per CLAUDE.md §Irreversibility Surface-Class Degrade Invariant — this
|
|
50
|
+
# is a REVERSIBLE, read-then-restore surface, so the floor here is "never mis-score", not
|
|
51
|
+
# "never run"):
|
|
52
|
+
# 0 = exactly ≥1 lane flipped ✅→❌ when reverted (anchor is alive — it caught the mutant)
|
|
53
|
+
# 1 = 0 lanes flipped (anchor is DECORATIVE for this file — nothing depended on the fix)
|
|
54
|
+
# 2 = usage error (bad args, target/suite/ref not found)
|
|
55
|
+
# 10 = the probe itself is unreliable for this run — either suite run produced ZERO parseable
|
|
56
|
+
# ✅/❌ lines (harness error, not "0 lanes exist"), OR the restore step failed (the target
|
|
57
|
+
# file may still hold BASELINE content — treated as the more severe failure and reported
|
|
58
|
+
# loudly, never silently folded into a verdict)
|
|
59
|
+
#
|
|
60
|
+
# Usage in a lane test: see scripts/test_revert_probe_lanes.sh for the known-pair calibration
|
|
61
|
+
# (decorative anchor → 1, real anchor → 0, restore-guaranteed-on-suite-crash).
|
|
62
|
+
|
|
63
|
+
set -uo pipefail
|
|
64
|
+
|
|
65
|
+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
66
|
+
|
|
67
|
+
TARGET=""; SUITE=""; BASELINE="HEAD"; TIMEOUT="120"
|
|
68
|
+
_POS=()
|
|
69
|
+
while [ $# -gt 0 ]; do
|
|
70
|
+
case "$1" in
|
|
71
|
+
--baseline) BASELINE="${2:-HEAD}"; shift 2 ;;
|
|
72
|
+
--timeout) TIMEOUT="${2:-120}"; shift 2 ;;
|
|
73
|
+
-*) echo "FAIL: unknown flag: $1" >&2; exit 2 ;;
|
|
74
|
+
*) _POS+=("$1"); shift ;;
|
|
75
|
+
esac
|
|
76
|
+
done
|
|
77
|
+
TARGET="${_POS[0]:-}"; SUITE="${_POS[1]:-}"
|
|
78
|
+
|
|
79
|
+
[ -n "$TARGET" ] || { echo "FAIL: usage: revert_probe.sh <target-file> <lane-suite-script> [--baseline <ref>]" >&2; exit 2; }
|
|
80
|
+
[ -n "$SUITE" ] || { echo "FAIL: usage: revert_probe.sh <target-file> <lane-suite-script> [--baseline <ref>]" >&2; exit 2; }
|
|
81
|
+
[ -f "$TARGET" ] || { echo "FAIL: target file not found: $TARGET" >&2; exit 2; }
|
|
82
|
+
[ -f "$SUITE" ] || { echo "FAIL: lane suite script not found: $SUITE" >&2; exit 2; }
|
|
83
|
+
|
|
84
|
+
# 🟥 physical path (`pwd -P`), not logical — macOS `/tmp` and `$TMPDIR` are symlinks into
|
|
85
|
+
# `/private/...`, and `git rev-parse --show-toplevel` always answers with the PHYSICAL path.
|
|
86
|
+
# A logical `pwd` here would make every fixture under a temp dir fail the prefix-strip below
|
|
87
|
+
# (same class of defect `sim_isolated_run.sh` already names for its own path isolation).
|
|
88
|
+
TARGET_DIR="$(cd "$(dirname "$TARGET")" && pwd -P)"
|
|
89
|
+
TARGET_ABS="$TARGET_DIR/$(basename "$TARGET")"
|
|
90
|
+
GIT_ROOT="$(cd "$TARGET_DIR" && git rev-parse --show-toplevel 2>/dev/null)"
|
|
91
|
+
[ -n "$GIT_ROOT" ] || { echo "FAIL: target file is not inside a git repository: $TARGET" >&2; exit 2; }
|
|
92
|
+
case "$TARGET_ABS" in
|
|
93
|
+
"$GIT_ROOT"/*) REL_PATH="${TARGET_ABS#"$GIT_ROOT"/}" ;;
|
|
94
|
+
*) echo "FAIL: could not compute a repo-relative path for $TARGET" >&2; exit 2 ;;
|
|
95
|
+
esac
|
|
96
|
+
|
|
97
|
+
if ! git -C "$GIT_ROOT" cat-file -e "${BASELINE}:${REL_PATH}" 2>/dev/null; then
|
|
98
|
+
echo "FAIL: $REL_PATH not found at baseline '$BASELINE' (bad ref, or the file did not exist there)" >&2
|
|
99
|
+
exit 2
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
echo "── revert_probe ────────────────────────────────────────────────────"
|
|
103
|
+
echo "target: $REL_PATH"
|
|
104
|
+
echo "suite: $SUITE"
|
|
105
|
+
echo "baseline: $BASELINE"
|
|
106
|
+
echo ""
|
|
107
|
+
echo "🟥 FRONTIER WARNING (arXiv 2607.22880): this run reverts EXACTLY ONE file — ONE mutant."
|
|
108
|
+
echo " A ✅ verdict here means the anchor caught THIS mutant, not that the suite has general"
|
|
109
|
+
echo " mutation-detection power. Coverage/mutation score decorrelates from real effectiveness"
|
|
110
|
+
echo " once suite size is controlled for — do not average or extrapolate from a single run."
|
|
111
|
+
echo "되돌린 파일(뮤턴트) 수: 1 — $REL_PATH"
|
|
112
|
+
echo ""
|
|
113
|
+
|
|
114
|
+
WORKDIR="$(mktemp -d "${TMPDIR:-/tmp}/revert_probe.XXXXXX")" || { echo "FAIL: mktemp -d failed" >&2; exit 10; }
|
|
115
|
+
BACKUP="$WORKDIR/backup"
|
|
116
|
+
RUN_A="$WORKDIR/run_current.txt"
|
|
117
|
+
RUN_B="$WORKDIR/run_baseline.txt"
|
|
118
|
+
|
|
119
|
+
cp -p "$TARGET_ABS" "$BACKUP" || { echo "FAIL: could not back up $TARGET_ABS — refusing to touch it" >&2; rm -rf "$WORKDIR"; exit 10; }
|
|
120
|
+
|
|
121
|
+
RESTORED=0
|
|
122
|
+
_restore() {
|
|
123
|
+
[ "$RESTORED" = 1 ] && return 0
|
|
124
|
+
if cp -p "$BACKUP" "$TARGET_ABS" 2>/dev/null; then
|
|
125
|
+
RESTORED=1
|
|
126
|
+
else
|
|
127
|
+
echo "🟥🟥🟥 RESTORE FAILED — $TARGET_ABS may still hold BASELINE ($BASELINE) content." >&2
|
|
128
|
+
echo " Backup of the pre-probe content is kept at: $BACKUP" >&2
|
|
129
|
+
echo " Restore it by hand: cp \"$BACKUP\" \"$TARGET_ABS\"" >&2
|
|
130
|
+
fi
|
|
131
|
+
}
|
|
132
|
+
# Safety net — fires on ANY exit path (normal, error, unbound-var under set -u), so a crash
|
|
133
|
+
# mid-probe still restores. The normal path below also calls _restore explicitly and checks its
|
|
134
|
+
# result directly, because a trap cannot hand its own success/failure back to the exit-code logic.
|
|
135
|
+
trap '_restore' EXIT
|
|
136
|
+
|
|
137
|
+
_run_suite() { # $1=output-file
|
|
138
|
+
if command -v timeout >/dev/null 2>&1; then
|
|
139
|
+
timeout "$TIMEOUT" bash "$SUITE" > "$1" 2>&1
|
|
140
|
+
else
|
|
141
|
+
bash "$SUITE" > "$1" 2>&1
|
|
142
|
+
fi
|
|
143
|
+
return 0 # the suite's own exit code (pass/fail count) is not this function's concern
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
echo "── run 1/2: 현재(수리 후) 판 ──"
|
|
147
|
+
_run_suite "$RUN_A"
|
|
148
|
+
echo " captured $(wc -l < "$RUN_A" | tr -d ' ') lines"
|
|
149
|
+
|
|
150
|
+
if ! git -C "$GIT_ROOT" show "${BASELINE}:${REL_PATH}" > "$TARGET_ABS.new" 2>"$WORKDIR/show.err"; then
|
|
151
|
+
echo "FAIL: git show ${BASELINE}:${REL_PATH} failed:" >&2
|
|
152
|
+
cat "$WORKDIR/show.err" >&2
|
|
153
|
+
rm -f "$TARGET_ABS.new"
|
|
154
|
+
# trap restores (no-op here, file was never swapped) and exits
|
|
155
|
+
exit 10
|
|
156
|
+
fi
|
|
157
|
+
mv "$TARGET_ABS.new" "$TARGET_ABS"
|
|
158
|
+
|
|
159
|
+
echo "── run 2/2: 기준($BASELINE) 판 (파일만 되돌림, 인덱스는 안 건드림) ──"
|
|
160
|
+
_run_suite "$RUN_B"
|
|
161
|
+
echo " captured $(wc -l < "$RUN_B" | tr -d ' ') lines"
|
|
162
|
+
|
|
163
|
+
_restore
|
|
164
|
+
trap - EXIT # explicit restore already ran; the safety net has nothing left to do
|
|
165
|
+
if [ "$RESTORED" != 1 ]; then
|
|
166
|
+
echo "" >&2
|
|
167
|
+
echo "RESULT: RESTORE-FAILED — do not trust the file on disk, see backup path above" >&2
|
|
168
|
+
rm -rf "$WORKDIR"
|
|
169
|
+
exit 10
|
|
170
|
+
fi
|
|
171
|
+
|
|
172
|
+
# ── ✅/❌ 라벨 추출 — 심볼\tSPACE-트림한 설명 ────────────────────────────────────────────
|
|
173
|
+
_extract_labels() { # $1=source-file → writes "P|F<TAB>desc"
|
|
174
|
+
grep -E '(✅|❌)' "$1" 2>/dev/null | while IFS= read -r line; do
|
|
175
|
+
case "$line" in
|
|
176
|
+
*✅*) sym=P; rest="${line#*✅}" ;;
|
|
177
|
+
*) sym=F; rest="${line#*❌}" ;;
|
|
178
|
+
esac
|
|
179
|
+
rest="$(printf '%s' "$rest" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')"
|
|
180
|
+
[ -n "$rest" ] && printf '%s\t%s\n' "$sym" "$rest"
|
|
181
|
+
done
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
LABELS_A="$WORKDIR/labels_a.tsv"; LABELS_B="$WORKDIR/labels_b.tsv"
|
|
185
|
+
_extract_labels "$RUN_A" > "$LABELS_A"
|
|
186
|
+
_extract_labels "$RUN_B" > "$LABELS_B"
|
|
187
|
+
NA=$(wc -l < "$LABELS_A" | tr -d ' ')
|
|
188
|
+
NB=$(wc -l < "$LABELS_B" | tr -d ' ')
|
|
189
|
+
|
|
190
|
+
if [ "$NA" -eq 0 ] || [ "$NB" -eq 0 ]; then
|
|
191
|
+
echo ""
|
|
192
|
+
echo "🟥 HARNESS ERROR — one of the two runs produced ZERO ✅/❌ lines (current=$NA, baseline=$NB)."
|
|
193
|
+
echo " That is not \"0 lanes exist\" — it means this probe cannot see the suite's verdicts at"
|
|
194
|
+
echo " all for that run (crash, missing labels, wrong suite path). Read the raw output:"
|
|
195
|
+
echo " current: $RUN_A"
|
|
196
|
+
echo " baseline: $RUN_B"
|
|
197
|
+
rm -rf "$WORKDIR"
|
|
198
|
+
exit 10
|
|
199
|
+
fi
|
|
200
|
+
|
|
201
|
+
# DESC\tSYM, sorted by DESC — LC_ALL=C throughout so non-ASCII label text (this repo's labels are
|
|
202
|
+
# routinely Korean) never collapses under a locale-dependent string-equality/sort
|
|
203
|
+
# ([[feedback_locale_string_equality_breaks_nonascii]]).
|
|
204
|
+
DESC_A="$WORKDIR/desc_a.tsv"; DESC_B="$WORKDIR/desc_b.tsv"
|
|
205
|
+
awk -F'\t' '{print $2"\t"$1}' "$LABELS_A" | LC_ALL=C sort -t"$(printf '\t')" -k1,1 -k2,2 > "$DESC_A"
|
|
206
|
+
awk -F'\t' '{print $2"\t"$1}' "$LABELS_B" | LC_ALL=C sort -t"$(printf '\t')" -k1,1 -k2,2 > "$DESC_B"
|
|
207
|
+
|
|
208
|
+
JOINED="$WORKDIR/joined.tsv"
|
|
209
|
+
LC_ALL=C join -t "$(printf '\t')" -j 1 -o 1.1,1.2,2.2 "$DESC_A" "$DESC_B" > "$JOINED" 2>/dev/null || : > "$JOINED"
|
|
210
|
+
|
|
211
|
+
FLIPPED_RED="$WORKDIR/flipped_red.txt" # ✅(현재) → ❌(기준) — 앵커가 실제로 잡은 것
|
|
212
|
+
FLIPPED_GREEN="$WORKDIR/flipped_green.txt" # ❌(현재) → ✅(기준) — 이상 신호, 참고용
|
|
213
|
+
awk -F'\t' '$2=="P" && $3=="F" {print $1}' "$JOINED" > "$FLIPPED_RED"
|
|
214
|
+
awk -F'\t' '$2=="F" && $3=="P" {print $1}' "$JOINED" > "$FLIPPED_GREEN"
|
|
215
|
+
K=$(wc -l < "$FLIPPED_RED" | tr -d ' ')
|
|
216
|
+
J=$(wc -l < "$FLIPPED_GREEN" | tr -d ' ')
|
|
217
|
+
|
|
218
|
+
# 라벨 텍스트가 두 실행에서 안 겹치는 경우 — 조용히 버리지 않고 이름으로 남긴다(가정 위반 알림).
|
|
219
|
+
ONLY_A="$WORKDIR/only_a.txt"; ONLY_B="$WORKDIR/only_b.txt"
|
|
220
|
+
LC_ALL=C comm -23 <(cut -f1 "$DESC_A" | LC_ALL=C sort -u) <(cut -f1 "$DESC_B" | LC_ALL=C sort -u) > "$ONLY_A"
|
|
221
|
+
LC_ALL=C comm -13 <(cut -f1 "$DESC_A" | LC_ALL=C sort -u) <(cut -f1 "$DESC_B" | LC_ALL=C sort -u) > "$ONLY_B"
|
|
222
|
+
NUM_ONLY_A=$(wc -l < "$ONLY_A" | tr -d ' '); NUM_ONLY_B=$(wc -l < "$ONLY_B" | tr -d ' ')
|
|
223
|
+
|
|
224
|
+
echo ""
|
|
225
|
+
echo "── 결과 ──────────────────────────────────────────────────────────"
|
|
226
|
+
echo "현재(수리 후) 라벨: ${NA}줄 · 기준($BASELINE) 라벨: ${NB}줄"
|
|
227
|
+
echo ""
|
|
228
|
+
echo "되돌린 레인 (✅→❌, 앵커가 실제로 잡은 것): ${K}개"
|
|
229
|
+
if [ "$K" -gt 0 ]; then sed 's/^/ ❌ /' "$FLIPPED_RED"; fi
|
|
230
|
+
echo ""
|
|
231
|
+
echo "(참고) 반대방향 (❌→✅, 이상 신호): ${J}개"
|
|
232
|
+
if [ "$J" -gt 0 ]; then sed 's/^/ ⚠️ /' "$FLIPPED_GREEN"; fi
|
|
233
|
+
if [ "$NUM_ONLY_A" -gt 0 ] || [ "$NUM_ONLY_B" -gt 0 ]; then
|
|
234
|
+
echo ""
|
|
235
|
+
echo "🟥 라벨 텍스트가 두 실행에서 완전히 겹치지 않는다 — 이 도구의 가정(라벨 텍스트가 안정적)"
|
|
236
|
+
echo " 이 이 스위트에서는 안 맞을 수 있다. 아래는 매칭 대상에서 빠진 라벨(플립 집계에 미포함):"
|
|
237
|
+
[ "$NUM_ONLY_A" -gt 0 ] && sed 's/^/ 현재에만: /' "$ONLY_A"
|
|
238
|
+
[ "$NUM_ONLY_B" -gt 0 ] && sed 's/^/ 기준에만: /' "$ONLY_B"
|
|
239
|
+
fi
|
|
240
|
+
|
|
241
|
+
echo ""
|
|
242
|
+
if [ "$K" -ge 1 ]; then
|
|
243
|
+
echo "판정: 앵커 살아있음 — 되돌리면 정확히 ${K}개 레인이 빨개진다"
|
|
244
|
+
rm -rf "$WORKDIR"
|
|
245
|
+
exit 0
|
|
246
|
+
else
|
|
247
|
+
echo "판정: 앵커 장식 — 되돌려도 빨개지는 레인이 0개다"
|
|
248
|
+
rm -rf "$WORKDIR"
|
|
249
|
+
exit 1
|
|
250
|
+
fi
|
package/scripts/selfcheck.sh
CHANGED
|
@@ -187,10 +187,22 @@ else
|
|
|
187
187
|
fail=1
|
|
188
188
|
fi
|
|
189
189
|
|
|
190
|
-
# Bash surface: npm-shipped scripts + local bin wrappers + gate-chain infra
|
|
190
|
+
# Bash surface: npm-shipped scripts + local bin wrappers + gate-chain infra.
|
|
191
|
+
# `bin/fh-gate` · `bin/fh-run` · `bin/fh-goal` are named EXPLICITLY here (not a glob) and, unlike
|
|
192
|
+
# most of this list, are not covered by files_manifest_shipping_check.sh either (only their
|
|
193
|
+
# `.js` counterparts are declared in package.json files[]) — so a plain `[ -f "$f" ] || continue`
|
|
194
|
+
# made their disappearance invisible to BOTH checks at once. Reproduced 2026-09-03: deleting
|
|
195
|
+
# bin/fh-gate from a fixture tree left fail=0, no FAIL line, nothing. `scripts/*.sh` staying a
|
|
196
|
+
# silent skip on a genuinely-empty glob is correct (that arm still has no non-glob name); the
|
|
197
|
+
# named gate-chain-infra paths must not degrade the same way.
|
|
191
198
|
for f in scripts/*.sh bin/fh-gate bin/fh-run bin/fh-goal \
|
|
192
199
|
templates/regression_guard.sh templates/temper_check.sh templates/predelete_check.sh templates/.git-hooks/pre-commit; do
|
|
193
|
-
[ -f "$f" ]
|
|
200
|
+
if [ ! -f "$f" ]; then
|
|
201
|
+
case "$f" in
|
|
202
|
+
*'*'*) continue ;; # unmatched glob (nullglob off) — not a real path, legitimate skip
|
|
203
|
+
*) echo "FAIL bash -n coverage: gate-chain infra file missing: $f"; fail=1; continue ;;
|
|
204
|
+
esac
|
|
205
|
+
fi
|
|
194
206
|
check "bash -n $f" bash -n "$f"
|
|
195
207
|
done
|
|
196
208
|
|
|
@@ -615,10 +627,12 @@ for _pair in \
|
|
|
615
627
|
".claude/soul_tenets.txt|scripts/test_marker_soul_tenet_lanes.sh" \
|
|
616
628
|
"templates/.git-hooks/pre-commit|scripts/test_precommit_staged_drift_lanes.sh" \
|
|
617
629
|
"templates/.git-hooks/pre-commit|scripts/test_marker_address_lanes.sh" \
|
|
630
|
+
"templates/.git-hooks/pre-commit|scripts/test_precommit_pointer_index_lanes.sh" \
|
|
618
631
|
"scripts/residency_closure_scan.py|scripts/test_residency_closure_lanes.sh" \
|
|
619
632
|
"scripts/reviewer_capability_corpus.tsv|scripts/test_reviewer_capability_conformance.sh" \
|
|
620
633
|
"scripts/field_canon_preload.sh|scripts/test_field_canon_lanes.sh" \
|
|
621
634
|
"scripts/stale_clone_guard.sh|scripts/test_stale_clone_guard_lanes.sh" \
|
|
635
|
+
"scripts/proposal_hook.sh|scripts/test_proposal_hook_lanes.sh" \
|
|
622
636
|
"plugins/fh-commons/skills/ko-tech-writer/SKILL.md|scripts/test_ko_tech_writer_lanes.sh" \
|
|
623
637
|
"scripts/script_caller_ratchet.sh|scripts/test_script_caller_ratchet_lanes.sh" \
|
|
624
638
|
"scripts/script_caller_ratchet.sh|scripts/test_runner_surface_index_lanes.sh" \
|
|
@@ -631,12 +645,14 @@ for _pair in \
|
|
|
631
645
|
"plugins/fh-commons/skills/preprep/lane_adjacent_dup.py|scripts/test_preprep_adjacent_dup_lanes.sh" \
|
|
632
646
|
"plugins/fh-commons/skills/preprep/lane_promise.py|scripts/test_preprep_promise_lanes.sh" \
|
|
633
647
|
"plugins/fh-commons/skills/preprep/SKILL.md|scripts/test_preprep_drift_anchor.sh" \
|
|
648
|
+
"scripts/test_preprep_drift_anchor.sh|scripts/test_preprep_drift_anchor_lanes.sh" \
|
|
634
649
|
"scripts/field_canon_preload.sh|scripts/test_skill_canon_preload_lanes.sh" \
|
|
635
650
|
`# ── round/ 회차 계기 4종(2026-09-01). 넷 다 한 스위트가 잡는다 — 주체별로 행을 둔다 ──` \
|
|
636
651
|
"scripts/round/delta_guard.sh|scripts/test_round_instruments_lanes.sh" \
|
|
637
652
|
"scripts/round/target_pin.sh|scripts/test_round_instruments_lanes.sh" \
|
|
638
653
|
"scripts/round/instrument_manifest.sh|scripts/test_round_instruments_lanes.sh" \
|
|
639
|
-
"scripts/round/eligcheck_qset.sh|scripts/test_round_instruments_lanes.sh"
|
|
654
|
+
"scripts/round/eligcheck_qset.sh|scripts/test_round_instruments_lanes.sh" \
|
|
655
|
+
"scripts/round/gatecheck_qset.sh|scripts/test_round_instruments_lanes.sh"
|
|
640
656
|
do
|
|
641
657
|
_subj="${_pair%%|*}"; _anc="${_pair##*|}"; _lbl="${_anc##*/}"
|
|
642
658
|
if [ ! -f "$_subj" ]; then
|
|
@@ -1055,6 +1071,29 @@ else
|
|
|
1055
1071
|
esac
|
|
1056
1072
|
fi
|
|
1057
1073
|
|
|
1074
|
+
# prepublish_scope_note — an embedded --self-test subject lane_runner_check.sh flagged as having
|
|
1075
|
+
# no dispatcher anywhere (2026-09-03): its own 7-lane known-pair (does validate.yml still call
|
|
1076
|
+
# selfcheck.sh — known-positive/negative, missing-workflow, commented-out call, real call beside a
|
|
1077
|
+
# stale commented one, the real `run: |` block-scalar shape, and echo-mention-is-not-a-call) lives
|
|
1078
|
+
# behind `--self-test`, and nothing runs it. It IS invoked at publish time (package.json
|
|
1079
|
+
# `prepublishOnly`) — but that is `check()`, the gate's default argument-less mode, running for
|
|
1080
|
+
# real; it never exercises the gate's OWN calibration. Not in the `for _subj in ...` loop above:
|
|
1081
|
+
# its terminal line is `── N pass / M fail`, never 캘리브레이션, same reason capability_registry_check
|
|
1082
|
+
# and capability_effect_probe were pulled out of that loop. Direct dispatch instead, same shape as
|
|
1083
|
+
# capability_effect_probe.sh above — whole-line terminal verdict with a non-zero PASS count, so an
|
|
1084
|
+
# emptied suite cannot certify itself. Ships via package.json files[], so absence is FAIL, not SKIP.
|
|
1085
|
+
if [ ! -f scripts/prepublish_scope_note.sh ]; then
|
|
1086
|
+
echo "FAIL prepublish_scope_note.sh: missing — it ships via package.json files[], so absence is deletion, not package mode"
|
|
1087
|
+
fail=1
|
|
1088
|
+
elif _out=$(bash scripts/prepublish_scope_note.sh --self-test < /dev/null 2>&1) \
|
|
1089
|
+
&& printf '%s\n' "$_out" | grep -qE '^ ── [1-9][0-9]* pass / 0 fail$'; then
|
|
1090
|
+
echo "PASS prepublish_scope_note.sh --self-test ($(printf '%s\n' "$_out" | grep -oE '[0-9]+ pass / [0-9]+ fail' | tail -1))"
|
|
1091
|
+
else
|
|
1092
|
+
echo "FAIL prepublish_scope_note.sh: --self-test failed or produced no terminal verdict line"
|
|
1093
|
+
_show_failure "$_out"
|
|
1094
|
+
fail=1
|
|
1095
|
+
fi
|
|
1096
|
+
|
|
1058
1097
|
# memory-link-check — the memory store is a GRAPH (memory_intent_recall.md: nodes=files,
|
|
1059
1098
|
# edges=[[links]], recall walks one hop). Measured 2026-07-28: 50 of 872 edges pointed at a note
|
|
1060
1099
|
# that existed under a different separator and 22 at nothing — a dead edge returns nothing and is
|
|
@@ -1142,6 +1181,20 @@ else
|
|
|
1142
1181
|
fail=1
|
|
1143
1182
|
fi
|
|
1144
1183
|
|
|
1184
|
+
# ⓕ 되돌림 범용 프로브 (six_axis_review_2026-09-04 강화 #2) — 15+ 손짜기 되돌림 스크립트를
|
|
1185
|
+
# 대체하는 계기다. 자기 자신을 known-pair 로 검증한다(장식 앵커→1, 실물 앵커→0, 복원 보장) —
|
|
1186
|
+
# 앵커가 아니라 그 앵커를 검증하는 계기이므로 반드시 실행돼야 한다.
|
|
1187
|
+
if [ ! -f scripts/revert_probe.sh ]; then
|
|
1188
|
+
_absent_subject_verdict "test_revert_probe_lanes.sh" "scripts/revert_probe.sh" || fail=1
|
|
1189
|
+
elif [ -f scripts/test_revert_probe_lanes.sh ]; then
|
|
1190
|
+
if ! bash scripts/test_revert_probe_lanes.sh; then
|
|
1191
|
+
fail=1
|
|
1192
|
+
fi
|
|
1193
|
+
else
|
|
1194
|
+
echo "FAIL test_revert_probe_lanes.sh: revert_probe.sh present but its anchor is missing"
|
|
1195
|
+
fail=1
|
|
1196
|
+
fi
|
|
1197
|
+
|
|
1145
1198
|
# 무효 워터마크 — 무효 회차의 «숫자 줄»이 자기 무효를 나르는가.
|
|
1146
1199
|
# 🟥 회차 3 은 자기 게이트가 VOID 를 찍고도 그 숫자만 기록으로 넘어갔다(VOID 낱말은 0회).
|
|
1147
1200
|
# 판정이 표 «밖»에 있었고 사람은 표를 복사하기 때문이다. 그 채널을 닫은 배선의 앵커다.
|
|
@@ -1543,6 +1596,17 @@ else
|
|
|
1543
1596
|
fail=1
|
|
1544
1597
|
fi
|
|
1545
1598
|
|
|
1599
|
+
if [ ! -f scripts/backtick_guard.sh ]; then
|
|
1600
|
+
_absent_subject_verdict "test_backtick_guard_lanes.sh" "scripts/backtick_guard.sh" || fail=1
|
|
1601
|
+
elif [ -f scripts/test_backtick_guard_lanes.sh ]; then
|
|
1602
|
+
if ! bash scripts/test_backtick_guard_lanes.sh; then
|
|
1603
|
+
fail=1
|
|
1604
|
+
fi
|
|
1605
|
+
else
|
|
1606
|
+
echo "FAIL test_backtick_guard_lanes.sh: backtick_guard.sh present but its anchor is missing"
|
|
1607
|
+
fail=1
|
|
1608
|
+
fi
|
|
1609
|
+
|
|
1546
1610
|
if [ ! -f scripts/halffix_propagation_scan.sh ]; then
|
|
1547
1611
|
_absent_subject_verdict "test_halffix_lanes.sh" "scripts/halffix_propagation_scan.sh" || fail=1
|
|
1548
1612
|
elif [ -f scripts/test_halffix_lanes.sh ]; then
|