@chrono-meta/fh-gate 3.0.0 โ†’ 3.1.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.
Files changed (111) hide show
  1. package/.claude/regression/probes_live.yaml +137 -0
  2. package/.claude/rules/.residency-patterns.defaults +7 -0
  3. package/.claude/rules/fh_4axis_gate.md +50 -1
  4. package/.claude-plugin/marketplace.json +8 -2
  5. package/AGENTS.md +27 -0
  6. package/CATALOG.md +17 -0
  7. package/CLAUDE.md +12 -2
  8. package/README.ja.md +51 -7
  9. package/README.ko.md +48 -7
  10. package/README.md +37 -5
  11. package/README.zh.md +45 -8
  12. package/docs/STANDARDS_ALIGNMENT.md +61 -0
  13. package/docs/USER_GUIDE.md +3 -0
  14. package/docs/USE_CASES.md +50 -0
  15. package/docs/model_tier_expectations.md +60 -0
  16. package/knowledge/shared/harness-core/fh_three_layer_canon.md +30 -10
  17. package/knowledge/shared/harness-core/field_verdict_crossfamily_gate.md +8 -0
  18. package/knowledge/shared/harness-core/harness_incubator_doctrine.md +9 -0
  19. package/knowledge/shared/harness-core/iso_ai_standards_crosswalk.md +139 -0
  20. package/knowledge/shared/harness-core/measurement-integrity-checklist.md +20 -0
  21. package/knowledge/shared/learnings/subagent_invocations_log.yaml +441 -4
  22. package/package.json +40 -2
  23. package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
  24. package/plugins/fh-commons/skills/preprep/README.md +4 -1
  25. package/plugins/fh-commons/skills/preprep/SKILL.md +99 -3
  26. package/plugins/fh-commons/skills/preprep/diagram_from_json.py +154 -0
  27. package/plugins/fh-commons/skills/preprep/fixtures/fixture_R3_negative.pptx +0 -0
  28. package/plugins/fh-commons/skills/preprep/fixtures/fixture_R3_positive.pptx +0 -0
  29. package/plugins/fh-commons/skills/preprep/fixtures/mk_slide_fixtures.py +179 -0
  30. package/plugins/fh-commons/skills/preprep/interslide_deps.py +98 -9
  31. package/plugins/fh-commons/skills/preprep/lane_adjacent_dup.py +4 -1
  32. package/plugins/fh-commons/skills/preprep/lane_diagram.py +95 -0
  33. package/plugins/fh-commons/skills/preprep/lane_geometry.py +181 -0
  34. package/plugins/fh-commons/skills/preprep/lane_promise.py +4 -1
  35. package/plugins/fh-commons/skills/preprep/lane_slide_refs.py +134 -0
  36. package/plugins/fh-commons/skills/preprep/lane_slide_relations.py +300 -0
  37. package/plugins/fh-commons/skills/preprep/preprep.py +129 -8
  38. package/plugins/fh-commons/skills/preprep/preprep_wire.py +210 -0
  39. package/plugins/fh-commons/skills/preprep/presentation_checklist.md +4 -0
  40. package/plugins/fh-commons/skills/preprep/surfaces.example.yaml +18 -0
  41. package/plugins/fh-commons/skills/preprep/test_preprep_lanes_rp.py +184 -0
  42. package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
  43. package/plugins/fh-meta/CHANGELOG.md +43 -1
  44. package/plugins/fh-meta/skills/auto-decorrelation/SKILL.md +87 -2
  45. package/plugins/fh-meta/skills/frontier-digest/SKILL.md +2 -2
  46. package/plugins/fh-meta/skills/frontier-digest/SKILL_detail.md +25 -5
  47. package/plugins/fh-meta/skills/hub-cc-pr-reviewer/SKILL.md +11 -0
  48. package/plugins/fh-meta/skills/hub-cc-pr-reviewer/SKILL_detail.md +3 -0
  49. package/plugins/fh-qp/.claude-plugin/plugin.json +22 -0
  50. package/plugins/fh-qp/README.md +71 -0
  51. package/plugins/fh-qp/fixtures/evidence_known_clean.txt +3 -0
  52. package/plugins/fh-qp/fixtures/evidence_known_dirty.txt +6 -0
  53. package/plugins/fh-qp/fixtures/reach_known_partial.tsv +4 -0
  54. package/plugins/fh-qp/fixtures/reach_known_reached.tsv +4 -0
  55. package/plugins/fh-qp/fixtures/reach_known_wall.tsv +3 -0
  56. package/plugins/fh-qp/fixtures/verdicts_known_bad_branch.tsv +2 -0
  57. package/plugins/fh-qp/fixtures/verdicts_known_bad_vacuous_machine.tsv +2 -0
  58. package/plugins/fh-qp/fixtures/verdicts_known_good.tsv +4 -0
  59. package/plugins/fh-qp/fixtures/verdicts_verify_only.tsv +3 -0
  60. package/plugins/fh-qp/qp_profile.example.yaml +29 -0
  61. package/plugins/fh-qp/scripts/qp_tools.sh +216 -0
  62. package/plugins/fh-qp/skills/qp/SKILL.md +65 -0
  63. package/plugins/fh-qp/skills/qp-plan/SKILL.md +42 -0
  64. package/plugins/fh-qp/skills/qp-regress/SKILL.md +45 -0
  65. package/plugins/fh-qp/skills/qp-run/SKILL.md +49 -0
  66. package/scripts/chamber_run.sh +14 -5
  67. package/scripts/com.forge-harness.live-eval.plist +84 -0
  68. package/scripts/compaction_probe.sh +9 -35
  69. package/scripts/directional_diff_gate.sh +14 -2
  70. package/scripts/frontier_digest_autopilot.sh +4 -1
  71. package/scripts/map_postprocess.py +90 -0
  72. package/scripts/outbound_query_guard.sh +131 -0
  73. package/scripts/outbound_query_hook.sh +373 -0
  74. package/scripts/package_coverage_check.sh +55 -16
  75. package/scripts/pipe_verdict_guard.sh +41 -1
  76. package/scripts/probe_live_eval.sh +240 -0
  77. package/scripts/probe_live_eval_lib.py +579 -0
  78. package/scripts/proposal_hook.sh +120 -17
  79. package/scripts/push_zone_check.sh +78 -0
  80. package/scripts/residency_closure_scan.py +252 -0
  81. package/scripts/selfcheck.sh +41 -1
  82. package/scripts/session_close_check.sh +100 -0
  83. package/scripts/sim_isolated_run.sh +98 -2
  84. package/scripts/test_action_yml_lanes.sh +97 -0
  85. package/scripts/test_fh_qp_lanes.sh +105 -0
  86. package/scripts/test_gate_two_verdicts_lanes.sh +139 -0
  87. package/scripts/test_map_postprocess_lanes.sh +143 -0
  88. package/scripts/test_marker_affected_lanes.sh +93 -0
  89. package/scripts/test_marker_crossfamily_lanes.sh +90 -6
  90. package/scripts/test_marker_oracle_lanes.sh +136 -0
  91. package/scripts/test_outbound_query_hook_lanes.sh +433 -0
  92. package/scripts/test_outbound_query_lanes.sh +87 -0
  93. package/scripts/test_pipe_verdict_guard_lanes.sh +26 -0
  94. package/scripts/test_preprep_diagram_lanes.sh +87 -0
  95. package/scripts/test_preprep_drift_anchor.sh +3 -3
  96. package/scripts/test_preprep_slide_refs_lanes.sh +169 -0
  97. package/scripts/test_probe_live_eval_lanes.sh +437 -0
  98. package/scripts/test_proposal_hook_lanes.sh +22 -1
  99. package/scripts/test_push_zone_lanes.sh +304 -0
  100. package/scripts/test_residency_closure_lanes.sh +70 -0
  101. package/scripts/test_sim_isolated_run_lanes.sh +119 -0
  102. package/scripts/test_utterance_intake_lanes.sh +414 -0
  103. package/scripts/test_worktree_reclaim_lanes.sh +70 -0
  104. package/scripts/transcript_utterances.py +222 -0
  105. package/scripts/utterance_intake.sh +424 -0
  106. package/scripts/validate_yaml.sh +27 -0
  107. package/scripts/worktree_reclaim.sh +95 -0
  108. package/templates/.git-hooks/pre-commit +353 -1
  109. package/templates/.git-hooks/pre-push +91 -0
  110. package/templates/RED_TEAM_REPORT.md +49 -0
  111. package/templates/settings.PreToolUse.snippet.json +65 -1
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env bash
2
+ # test_preprep_diagram_lanes.sh โ€” L12 diagram ๋ ˆ์ธ + diagram_from_json.py ๊ฑฐ๋ถ€ ๊ฒฝ๋กœ์˜ known-pair.
3
+ #
4
+ # ์žฌ๋Š” ๊ฒƒ:
5
+ # K1 known-negative ์˜์ˆ˜์ฆยท์ง€๋ฌธยทvalidateยทํญยทviewBoxยทpad ์ „๋ถ€ ๋งž์œผ๋ฉด findings 0
6
+ # K2 stale JSON ์„ ํ•œ ๊ธ€์ž ๊ณ ์น˜๋ฉด ยซ์ง€๋ฌธ ๋‹ค๋ฆ„ยป 1๊ฑด์„ ์ด๋ฆ„์œผ๋กœ ์งš๋‚˜
7
+ # K3 resolution PNG ์‹ค์ œ ํญ(IHDR)์ด ํ•˜ํ•œ ๋ฏธ๋‹ฌ์ด๋ฉด 1๊ฑด โ€” ์˜์ˆ˜์ฆ ์ž๊ธฐ์‹ ๊ณ (png_size)๊ฐ€ ์•„๋‹ˆ๋ผ ํ—ค๋”๋ฅผ ์ฝ๋‚˜
8
+ # K4 viewbox viewBox ํญ > ์ƒํ•œ์ด๋ฉด 1๊ฑด(๊ธ€์ž ๋ฐฐ์œจ)
9
+ # K5 unmeasured ์˜์ˆ˜์ฆ ์—†๋Š” PNG ๋Š” findings 0 + UNMEASURED ๋…ธํŠธ(0 ์œผ๋กœ ์•ˆ ์ ‘๋‚˜)
10
+ # K6 not-configured diagram_source ํ‘œ๋ฉด์ด ์—†์œผ๋ฉด NOT_CONFIGURED(ํ†ต๊ณผ ์•„๋‹˜)
11
+ # K7 refuse diagram_from_json.py ๊ฐ€ validate ์‹คํŒจ JSON ์„ **๊ตฝ์ง€ ์•Š๋‚˜** โ€” archify ๋Š” ์Šคํ…์œผ๋กœ ๋Œ€์ฒด
12
+ # (์‹ค๋ฌผ archifyยทChrome ์—†์ด ๋„๋Š” ๋ ˆ์ธ์ด์–ด์•ผ CI ์—์„œ ์•ต์ปค๋‹ค)
13
+ # ๐ŸŸฅ archifyยทChrome ์ด ์—†์–ด๋„ ๋Œ์•„์•ผ ํ•œ๋‹ค โ€” ๊ทธ๋ž˜์„œ K7 ์€ ์Šคํ… archify(ok:false)๋กœ ยซ๊ฑฐ๋ถ€ยป๋งŒ ์žฐ๋‹ค.
14
+ # ยซ๊ตฝํž˜ยป ์ชฝ ์‹ค์‚ฌ์šฉ์€ tracks-meta/dispatch/2026-09-05_archify-preprep/REPORT.md ๊ฐ€ ๊ธฐ๋กํ•œ๋‹ค.
15
+ set -uo pipefail
16
+ HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
17
+ SKILL="$HERE/plugins/fh-commons/skills/preprep"
18
+ PASS=0; FAIL=0
19
+ ok(){ echo " โœ… $1"; PASS=$((PASS+1)); }
20
+ ng(){ echo " โŒ $1"; FAIL=$((FAIL+1)); }
21
+ command -v python3 >/dev/null 2>&1 || { echo " โš ๏ธ UNMEASURED โ€” python3 unavailable (not a pass)"; echo "diagram lanes: SKIPPED (not passed)"; exit 0; }
22
+
23
+ out=$(cd "$SKILL" && python3 - <<'PY' 2>&1
24
+ import sys, os, json, hashlib, struct, zlib, tempfile, subprocess
25
+ sys.path.insert(0, '.')
26
+ import lane_diagram as LD
27
+ def sha(p): return hashlib.sha256(open(p,'rb').read()).hexdigest()
28
+ def png(path, w, h=2):
29
+ # ์ˆœ์ˆ˜ ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ PNG(ํšŒ์ƒ‰ 1๋ฐ”์ดํŠธ/ํ”ฝ์…€) โ€” ํญ๋งŒ ์˜๋ฏธ ์žˆ๋‹ค
30
+ raw = b''.join(b'\x00' + b'\x80'*w for _ in range(h))
31
+ def chunk(t, d): return struct.pack('>I', len(d)) + t + d + struct.pack('>I', zlib.crc32(t+d) & 0xffffffff)
32
+ open(path,'wb').write(b'\x89PNG\r\n\x1a\n' + chunk(b'IHDR', struct.pack('>IIBBBBB', w, h, 8, 0, 0, 0, 0)) + chunk(b'IDAT', zlib.compress(raw)) + chunk(b'IEND', b''))
33
+ d = tempfile.mkdtemp(prefix='l12_')
34
+ spec = os.path.join(d, 'fig.workflow.json'); json.dump({'diagram_type':'workflow','nodes':[{'id':'a','label':'๊ฐœ๋ฐœ'}]}, open(spec,'w'))
35
+ def receipt(pngp, **kw):
36
+ r = {'spec_sha256': sha(spec), 'validate': {'ok': True, 'profile': 'showcase'}, 'viewbox_w': 720, 'pad': 0.03, 'png_size': [3840, 2]}
37
+ r.update(kw); json.dump(r, open(pngp[:-4] + '.receipt.json', 'w'))
38
+ def run(surfs, extra=None):
39
+ cfg = {'surfaces': surfs, 'diagram': {'min_px': 3840, 'max_viewbox_w': 720, 'min_pad': 0.02}}
40
+ return LD.scan(cfg, d)
41
+ R = {}
42
+ # K1 negative
43
+ p1 = os.path.join(d, 'ok.png'); png(p1, 3840); receipt(p1)
44
+ f, n = run([{'id':'fig','path':'fig.workflow.json','kind':'diagram_source','render':'ok.png'}]); R['K1'] = len(f)
45
+ # K2 stale: JSON ๊ณ ์นจ
46
+ open(spec,'a').write('\n'); f, n = run([{'id':'fig','path':'fig.workflow.json','kind':'diagram_source','render':'ok.png'}])
47
+ R['K2'] = [x[2] for x in f]
48
+ receipt(p1) # ๋‹ค์‹œ ๋งž์ถค
49
+ # K3 resolution: ์˜์ˆ˜์ฆ์€ 3840 ์ด๋ผ ยซ๋งํ•˜์ง€๋งŒยป ์‹ค์ œ ํ—ค๋”๋Š” 1000
50
+ p3 = os.path.join(d, 'small.png'); png(p3, 1000); receipt(p3, png_size=[3840, 2])
51
+ f, n = run([{'id':'fig','path':'fig.workflow.json','kind':'diagram_source','render':'small.png'}]); R['K3'] = [x[2] for x in f]
52
+ # K4 viewbox
53
+ p4 = os.path.join(d, 'wide.png'); png(p4, 3840); receipt(p4, viewbox_w=1137)
54
+ f, n = run([{'id':'fig','path':'fig.workflow.json','kind':'diagram_source','render':'wide.png'}]); R['K4'] = [x[2] for x in f]
55
+ # K5 unmeasured (์˜์ˆ˜์ฆ ์—†์Œ)
56
+ p5 = os.path.join(d, 'hand.png'); png(p5, 3840)
57
+ f, n = run([{'id':'fig','path':'fig.workflow.json','kind':'diagram_source','render':'hand.png'}]); R['K5'] = {'n': len(f), 'unm': any('UNMEASURED' in s for s in n)}
58
+ # K6 not configured
59
+ f, n = run([]); R['K6'] = {'n': len(f), 'nc': any('NOT_CONFIGURED' in s for s in n)}
60
+ # K7 refuse โ€” ์Šคํ… archify(ํ•ญ์ƒ ok:false) + ๊ฐ€์งœ chrome. PNG ๊ฐ€ ยซ์•ˆ ์ƒ๊ฒจ์•ผยป ํ•œ๋‹ค
61
+ stub = os.path.join(d, 'archify_stub.mjs'); open(stub,'w').write('console.log(JSON.stringify({ok:false,diagnostics:[{code:"stub/fail",message:"stub"}]}))')
62
+ fake_chrome = os.path.join(d, 'chrome'); open(fake_chrome,'w').write('#!/bin/sh\nexit 0\n'); os.chmod(fake_chrome, 0o755)
63
+ outp = os.path.join(d, 'refused.png')
64
+ import shutil
65
+ if shutil.which('node'):
66
+ r = subprocess.run([sys.executable, 'diagram_from_json.py', spec, '--out', outp, '--archify', stub, '--chrome', fake_chrome], capture_output=True, text=True)
67
+ R['K7'] = {'rc': r.returncode, 'png': os.path.exists(outp), 'msg': 'REFUSE' in r.stdout}
68
+ else:
69
+ R['K7'] = {'rc': 'NO_NODE'}
70
+ print(json.dumps(R, ensure_ascii=False))
71
+ PY
72
+ ); rc=$?
73
+ [ $rc -eq 0 ] || { echo " โŒ INSTRUMENT ERROR โ€” lane_diagram ์‹คํ–‰ ์‹คํŒจ (rc=$rc): $out"; exit 1; }
74
+ g(){ python3 -c "import json,sys;v=json.loads(sys.argv[1]);exec('r=v'+sys.argv[2]);print(r)" "$out" "$1"; }
75
+ [ "$(g "['K1']")" = "0" ] && ok "K1 known-negative: ๋‹ค ๋งž์œผ๋ฉด 0๊ฑด" || ng "K1 ์˜คํƒ $(g "['K1']")๊ฑด"
76
+ [ "$(g "['K2']")" = "['stale']" ] && ok "K2 stale: JSON ๊ณ ์น˜๋ฉด ยซstaleยป 1๊ฑด์„ ์ด๋ฆ„์œผ๋กœ" || ng "K2 ์‹คํŒจ: $(g "['K2']")"
77
+ [ "$(g "['K3']")" = "['resolution']" ] && ok "K3 resolution: ์˜์ˆ˜์ฆ์ด 3840 ์ด๋ผ ํ•ด๋„ IHDR 1000 ์„ ์žก๋Š”๋‹ค(์ž๊ธฐ์‹ ๊ณ  ์•„๋‹˜)" || ng "K3 ์‹คํŒจ: $(g "['K3']")"
78
+ [ "$(g "['K4']")" = "['viewbox']" ] && ok "K4 viewbox: 1137 > 720 ์„ ์žก๋Š”๋‹ค(๊ธ€์ž ๋ฐฐ์œจ)" || ng "K4 ์‹คํŒจ: $(g "['K4']")"
79
+ [ "$(g "['K5']['n']")" = "0" ] && [ "$(g "['K5']['unm']")" = "True" ] && ok "K5 ์˜์ˆ˜์ฆ ์—†๋Š” ์†๊ทธ๋ฆผ = UNMEASURED(0 ์•„๋‹˜ยทํ†ต๊ณผ ์•„๋‹˜)" || ng "K5 ์‹คํŒจ: $(g "['K5']")"
80
+ [ "$(g "['K6']['n']")" = "0" ] && [ "$(g "['K6']['nc']")" = "True" ] && ok "K6 ํ‘œ๋ฉด ์—†์Œ = NOT_CONFIGURED" || ng "K6 ์‹คํŒจ: $(g "['K6']")"
81
+ k7=$(g "['K7']")
82
+ case "$k7" in
83
+ *NO_NODE*) echo " โญ K7 ๊ฑฐ๋ถ€ ๊ฒฝ๋กœ โ€” node ๋ถ€์žฌ๋ผ UNMEASURED (ํ†ต๊ณผ ์•„๋‹˜)";;
84
+ *) [ "$(g "['K7']['rc']")" = "1" ] && [ "$(g "['K7']['png']")" = "False" ] && [ "$(g "['K7']['msg']")" = "True" ] && ok "K7 refuse: validate ์‹คํŒจ๋ฉด exit 1 + PNG ๋ฏธ์ƒ์„ฑ + REFUSE ํ‘œ๊ธฐ" || ng "K7 ์‹คํŒจ: $k7";;
85
+ esac
86
+ echo "diagram lanes: $PASS passed, $FAIL failed"
87
+ [ "$FAIL" -eq 0 ] || exit 1
@@ -33,7 +33,7 @@ sk(){ echo " โญ $1 โ€” SKIPPED (**ํ†ต๊ณผ ์•„๋‹˜**)"; SKIP=$((SKIP+1)); }
33
33
 
34
34
  # D1 โ€” ๋‹จ์ผ ์†Œ์Šค
35
35
  missing=""
36
- for f in preprep.py interslide_deps.py lane_progression.py lane_adjacent_dup.py lane_promise.py SKILL.md README.md surfaces.example.yaml; do
36
+ for f in preprep.py interslide_deps.py lane_progression.py lane_adjacent_dup.py lane_promise.py lane_diagram.py diagram_from_json.py SKILL.md README.md surfaces.example.yaml; do
37
37
  [ -f "$SRC/$f" ] || missing="$missing $f"
38
38
  done
39
39
  if [ -n "$missing" ]; then ng "D1 ๋‹จ์ผ ์†Œ์Šค ๊ฒฐ์†:$missing"
@@ -41,10 +41,10 @@ elif ! command -v python3 >/dev/null 2>&1; then
41
41
  sk "D1 ๊ตฌ๋ฌธ ๊ฒ€์‚ฌ โ€” python3 ๋ถ€์žฌ๋ผ ยซ๋Œ ์ˆ˜ ์žˆ๋‚˜ยป๋ฅผ ๋ชป ์Ÿ€๋‹ค(UNMEASURED)"
42
42
  else
43
43
  synerr=""
44
- for f in preprep.py interslide_deps.py lane_progression.py lane_adjacent_dup.py lane_promise.py; do
44
+ for f in preprep.py interslide_deps.py lane_progression.py lane_adjacent_dup.py lane_promise.py lane_diagram.py diagram_from_json.py; do
45
45
  python3 -c "import ast,sys;ast.parse(open(sys.argv[1],encoding='utf-8').read())" "$SRC/$f" 2>/dev/null || synerr="$synerr $f"
46
46
  done
47
- [ -z "$synerr" ] && ok "D1 ๋‹จ์ผ ์†Œ์Šค 8ํŒŒ์ผ ์‹ค์žฌ + python 5ํŒŒ์ผ ๊ตฌ๋ฌธ ํ†ต๊ณผ" \
47
+ [ -z "$synerr" ] && ok "D1 ๋‹จ์ผ ์†Œ์Šค 10ํŒŒ์ผ ์‹ค์žฌ + python 7ํŒŒ์ผ ๊ตฌ๋ฌธ ํ†ต๊ณผ" \
48
48
  || ng "D1 ๊ตฌ๋ฌธ ์‹คํŒจ:$synerr"
49
49
  fi
50
50
 
@@ -0,0 +1,169 @@
1
+ #!/usr/bin/env bash
2
+ # test_preprep_slide_refs_lanes.sh โ€” preprep L13(๋ฆฌํ„ฐ๋Ÿด ์žฅ ๋ฒˆํ˜ธ ์ฐธ์กฐ)์˜ ๊ณ„์•ฝ์„ ๊ณ ์ •ํ•œ๋‹ค.
3
+ #
4
+ # WHY: ์žฅ์„ ๋นผ๋ฉด ๋’ค ๋ฒˆํ˜ธ๊ฐ€ ํ†ต์งธ๋กœ ๋‹น๊ฒจ์ง„๋‹ค. ์ด์›ƒ ๋ ˆ์ธ๋“ค์€ ยซ๋ง๋กœ ๊ฐ€๋ฆฌํ‚ค๋Š” ๊ฒƒยป๋งŒ ๋ณธ๋‹ค
5
+ # (L5/L8 ํ™”๋ฉด ์žฌ์‚ฌ์šฉยท์‹œ๊ฐ ์ง€์‹œ ยท R1~R5 ์ ‘์†์–ดยท์†Œ์œ ๊ถŒ) โ€” ์ˆซ์ž ์ฐธ์กฐ๋Š” ์•„๋ฌด๋„ ์•ˆ ๋ดค๋‹ค.
6
+ #
7
+ # ๐ŸŸฅ ์ด ๋ ˆ์ธ์˜ ๋‚œ์ ์€ ๊ฒ€์ถœ์ด ์•„๋‹ˆ๋ผ **๋ถ„๋ฅ˜**๋‹ค. ๊ฐ™์€ ยซ์ˆซ์ž+๋‹จ์œ„ยป๊ฐ€ ์…‹์„ ๊ฐ€๋ฆฌํ‚จ๋‹ค:
8
+ # SLIDE(์ง„์งœ ์žฅ) ยท SECTION(์„น์…˜ ๋ฒˆํ˜ธ) ยท CITATION(๋…ผ๋ฌธ ์ชฝ์ˆ˜)
9
+ # ์‹ค์ธก ์ฝ”ํผ์Šค(if(kakao) 121์žฅ)์— ์…‹์ด **์ „๋ถ€** ์žˆ์—ˆ๊ณ , SLIDE ๋Š” **0๊ฑด**์ด์—ˆ๋‹ค.
10
+ # โ‡’ ์‹ค๋ฌผ์€ known-**negative** ๋งŒ ๋Œ€์ค€๋‹ค. ์–‘์„ฑ์€ ์•„๋ž˜ ํ”ฝ์Šค์ฒ˜๊ฐ€ ๋Œ„๋‹ค โ€” ๊ทธ๋ž˜์„œ ๋‘˜ ๋‹ค ์žˆ๋‹ค.
11
+ #
12
+ # ์ข…๋ฃŒ์ฝ”๋“œ: 0 pass ยท 1 ๋ ˆ์ธ ์‹คํŒจ ยท 2 ๋Œ€์ƒ ๋ถ€์žฌ ยท 10 setup ์‹คํŒจ
13
+ set -uo pipefail
14
+ cd "$(dirname "$0")/.." || exit 10
15
+ LANE=plugins/fh-commons/skills/preprep/lane_slide_refs.py
16
+ [ -f "$LANE" ] || { echo "โ“˜ $LANE absent โ€” subject missing (NOT a pass)"; exit 2; }
17
+ command -v python3 >/dev/null 2>&1 || { echo "โ“˜ python3 absent โ€” setup broke"; exit 10; }
18
+ T=$(mktemp -d) || exit 10; trap 'rm -rf "$T"' EXIT
19
+ # ๐ŸŸฅ ํŒŒ์ด์ฌ ๋ฐ”์ดํŠธ์ฝ”๋“œ ์บ์‹œ๋ฅผ ์ง€์šฐ๊ณ  ์‹œ์ž‘ํ•œ๋‹ค. ๋˜๋Œ๋ฆผ ํ”„๋กœ๋ธŒ์—์„œ ์‹ค์ธกํ–ˆ๋‹ค(2026-09-06):
20
+ # ํŒ๋ณ„์ž๋ฅผ ๋ฎคํ„ดํŠธ๋กœ ๋ฐ”๊ฟจ๋‹ค๊ฐ€ ์›๋ณธ์„ ๋ณต์›ํ–ˆ๋Š”๋ฐ **์Šคํ…Œ์ผ __pycache__ ๊ฐ€ ๋ฎคํ„ดํŠธ๋ฅผ ๊ณ„์†
21
+ # ๋จน์—ฌ** ใ€Œ๋ณต์›ํ–ˆ๋Š”๋ฐ๋„ ์ ์ƒ‰ใ€์ด ๋‚˜์™”๋‹ค. ๋ฐ˜๋Œ€ ๋ฐฉํ–ฅ์ด ๋” ์œ„ํ—˜ํ•˜๋‹ค โ€” ๋ฎคํ„ดํŠธ๋ฅผ ๋„ฃ์—ˆ๋Š”๋ฐ
22
+ # ์บ์‹œ๊ฐ€ ์›๋ณธ์„ ๋จน์ด๋ฉด ยซ๋˜๋Œ๋ ค๋„ ์ดˆ๋กยป ์ด ๋˜์–ด **์•ต์ปค๊ฐ€ ์ฃฝ์€ ๊ฒƒ์„ ๋ชป ๋ณธ๋‹ค**.
23
+ rm -rf plugins/fh-commons/skills/preprep/__pycache__
24
+ PASS=0; FAIL=0
25
+ ok(){ echo " โœ… $1"; PASS=$((PASS+1)); }
26
+ ng(){ echo " โŒ $1"; FAIL=$((FAIL+1)); }
27
+
28
+ RUN=$T/run.py
29
+ cat > "$RUN" <<'PY'
30
+ import sys, os, types
31
+ sys.path.insert(0, 'plugins/fh-commons/skills/preprep')
32
+
33
+ # ๐ŸŸฅ python-pptx ๋ฅผ **์˜์กดํ•˜์ง€ ์•Š๋Š”๋‹ค**. ์ด ์Šค์œ„ํŠธ๋Š” ยซ๋ถ„๋ฅ˜ยป ๋ฅผ ์žฌ๋Š” ๊ฒƒ์ด๊ณ , ๊ทธ ์ž…๋ ฅ์€
34
+ # ใ€Œ์žฅ โ†’ ํ™”๋ฉด ํ…์ŠคํŠธ ยท ๋…ธํŠธใ€๋ผ๋Š” **์ž‘์€ ์˜ค๋ฆฌ ํƒ€์ž…**์ด ์ „๋ถ€๋‹ค(lane ์˜ _texts/
35
+ # section_numbers ๊ฐ€ ์ฝ๋Š” ํ‘œ๋ฉด). ์‹ค๋ฌผ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์š”๊ตฌํ•˜๋ฉด ๋Ÿฌ๋„ˆ์— ์—†์„ ๋•Œ
36
+ # HARNESS ERROR(exit 10)๋กœ ์ฃฝ๊ณ  โ€” ๊ทธ๊ฑด ยซ์ธก์ • ๋ชป ํ–ˆ๋‹คยป๋ผ ์˜ณ์€ ๋ฐฉํ–ฅ์ด์ง€๋งŒ, ๊ทธ ๋Œ€๊ฐ€๋กœ
37
+ # **CI ์—์„œ ์ด ๋ ˆ์ธ์ด ์˜์˜ ์•ˆ ๋Œ๊ฒŒ** ๋œ๋‹ค. ์•ˆ ๋„๋Š” ์•ต์ปค๋Š” ์•ต์ปค๊ฐ€ ์•„๋‹ˆ๋‹ค.
38
+ # โ‡’ `sys.modules['pptx']` ์— ์Šคํ…์„ ์‹ฌ์–ด lane ์˜ `from pptx import Presentation`
39
+ # ๊ฒฝ๋กœ๊นŒ์ง€ **๊ทธ๋Œ€๋กœ ํ†ต๊ณผ์‹œํ‚จ๋‹ค** โ€” scan() ์˜ ๊ฒฝ๋กœ ํ•ด์†Œยท๋…ธํŠธ ์ฝ๊ธฐ๊ฐ€ ๋‹ค ๋ฎ์ธ๋‹ค.
40
+ class _Run:
41
+ def __init__(self, t): self.text = t
42
+ class _Para:
43
+ def __init__(self, t): self.runs = [_Run(t)]
44
+ class _TF:
45
+ def __init__(self, t): self.paragraphs = [_Para(t)]; self.text = t
46
+ class _Shape:
47
+ def __init__(self, t): self.has_text_frame = True; self.text_frame = _TF(t)
48
+ class _Notes:
49
+ def __init__(self, t): self.notes_text_frame = _TF(t)
50
+ class _Slide:
51
+ def __init__(self, scr, note):
52
+ self.shapes = [_Shape(x) for x in scr]
53
+ self.has_notes_slide = True
54
+ self.notes_slide = _Notes(note)
55
+ class _Prs:
56
+ def __init__(self, spec): self.slides = [_Slide(a, b) for a, b in spec]
57
+
58
+ _SPEC = {}
59
+ def _Presentation(path):
60
+ key = os.path.normpath(path)
61
+ if key not in _SPEC:
62
+ raise FileNotFoundError(key) # ์‹ค๋ฌผ ์—†์Œ ๊ฒฝ๋กœ๋„ lane ์ด UNMEASURED ๋กœ ๋‚ธ๋‹ค
63
+ return _Prs(_SPEC[key])
64
+ _fake = types.ModuleType('pptx'); _fake.Presentation = _Presentation
65
+ sys.modules['pptx'] = _fake
66
+
67
+ def build(path, slides):
68
+ """slides = [(ํ™”๋ฉด ์ค„๋“ค, ๋Œ€๋ณธ)] โ€” ํ™”๋ฉด ์ค„์€ ๊ฐ๊ฐ ๋ณ„๋„ ํ…์ŠคํŠธ ์ƒ์ž."""
69
+ _SPEC[os.path.normpath(path)] = slides
70
+ open(path, 'w').close() # os.path.exists ๊ฒŒ์ดํŠธ๋ฅผ ์‹ค์ œ๋กœ ์ง€๋‚˜๊ฐ€๊ฒŒ
71
+
72
+ def scan(path, mutate=None):
73
+ import importlib, lane_slide_refs as L
74
+ importlib.reload(L)
75
+ if mutate == 'kill-cite':
76
+ L.CITE = __import__('re').compile(r'(?!x)x') # ์ ˆ๋Œ€ ์•ˆ ๋งž๋Š”๋‹ค
77
+ if mutate == 'kill-section':
78
+ L.section_numbers = lambda prs: set()
79
+ cfg = {'surfaces_by_id': {'built_deck': {'path': path}}}
80
+ return L.scan(cfg, '.')
81
+
82
+ if __name__ == '__main__':
83
+ build(sys.argv[1], eval(sys.argv[2]))
84
+ f, n = scan(sys.argv[1], sys.argv[3] if len(sys.argv) > 3 else None)
85
+ print('FINDINGS', len(f))
86
+ for x in f: print('F|', x[2])
87
+ for x in n: print('N|', x)
88
+ PY
89
+
90
+ SEC=$'3. ์–ด๋–ป๊ฒŒ ๋งŒ๋“ค์—ˆ๋‚˜'
91
+ # ์„น์…˜ ๋ผ๋ฒจ์€ ยซ์—ฌ๋Ÿฌ ์žฅ์— ๋ฐ˜๋ณตยป ๋˜์–ด์•ผ ์„น์…˜์œผ๋กœ ์ธ์ •๋œ๋‹ค โ€” ํ”ฝ์Šค์ฒ˜๋„ ๊ทธ ์กฐ๊ฑด์„ ๋งŒ์กฑ์‹œํ‚จ๋‹ค
92
+ DECK_SEC="[(['$SEC','3์žฅ์—์„œ ๊ฒŒ์ดํŠธ๋ถ€ํ„ฐ ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค'],''),(['$SEC'],''),(['$SEC'],'')]"
93
+ DECK_CITE="[(['1) Ye, ใ€ŽCoding with Enemyใ€, arXiv:2606.05647, 2026 (4p)'],''),([''],''),([''],'')]"
94
+ DECK_DANGLING="[(['๋ณธ๋ฌธ'],'130p ๋ฅผ ๋ณด์„ธ์š”'),(['๋ณธ๋ฌธ'],''),(['๋ณธ๋ฌธ'],'')]"
95
+ DECK_OK="[(['๋ณธ๋ฌธ'],'2p ์—์„œ ๋ณด์…จ๋“ฏ์ด'),(['๋ณธ๋ฌธ'],''),(['๋ณธ๋ฌธ'],'')]"
96
+ DECK_SELF="[(['๋ณธ๋ฌธ'],''),(['๋ณธ๋ฌธ'],'2p ๋ฅผ ๋ณด์„ธ์š”'),(['๋ณธ๋ฌธ'],'')]"
97
+ DECK_DECIMAL="[(['62.5% ๊ฐœ์„ '],'62์žฅ์„ ๋ณด์„ธ์š”'),(['62.5% ๊ฐœ์„ '],''),(['๋ณธ๋ฌธ'],'')]"
98
+ DECK_EMPTY="[(['์•„๋ฌด ์ฐธ์กฐ๋„ ์—†๋‹ค'],''),(['๋ณธ๋ฌธ'],''),(['๋ณธ๋ฌธ'],'')]"
99
+
100
+ run(){ python3 "$RUN" "$T/$1.pptx" "$2" ${3:-} 2>&1; }
101
+
102
+ echo "== L13 โ€” ๋ถ„๋ฅ˜๊ฐ€ ํ•˜์ค‘์„ ์ง„๋‹ค =="
103
+
104
+ OUT=$(run pos "$DECK_DANGLING")
105
+ echo "$OUT" | grep -q '^FINDINGS 1' && echo "$OUT" | grep -q '๋ฒ”์œ„ ๋ฐ–' \
106
+ && ok "L1 known-positive: 3์žฅ ๋ฑ์˜ ยซ130pยป โ†’ ๋ฒ”์œ„ ๋ฐ– finding" \
107
+ || ng "L1 known-positive ์‹คํŒจ: $(echo "$OUT" | head -2 | tr '\n' ' ')"
108
+
109
+ OUT=$(run sec "$DECK_SEC")
110
+ echo "$OUT" | grep -q '^FINDINGS 0' && echo "$OUT" | grep -q 'SECTION 1' \
111
+ && ok "L2 known-negative: ใ€Œ3์žฅ์—์„œใ€ = SECTION, finding 0" \
112
+ || ng "L2 ์„น์…˜์ด ์•ˆ ๊ฐˆ๋ ธ๋‹ค: $(echo "$OUT" | grep '^N|' | head -1)"
113
+
114
+ OUT=$(run cite "$DECK_CITE")
115
+ echo "$OUT" | grep -q '^FINDINGS 0' && echo "$OUT" | grep -q 'CITATION 1' \
116
+ && ok "L3 known-negative: ์ธ์šฉ ใ€Œ(4p)ใ€ = CITATION, finding 0" \
117
+ || ng "L3 ์ธ์šฉ์ด ์•ˆ ๊ฐˆ๋ ธ๋‹ค: $(echo "$OUT" | grep '^N|' | head -1)"
118
+
119
+ OUT=$(run okref "$DECK_OK")
120
+ echo "$OUT" | grep -q '^FINDINGS 0' && echo "$OUT" | grep -q 'SLIDE 1' \
121
+ && ok "L4 ์ •์ƒ SLIDE ์ฐธ์กฐ๋Š” ๋…ธํŠธ๋กœ๋งŒ(๋ฒ”์œ„ ์•ˆ์ด๋ผ finding ์•„๋‹˜)" \
122
+ || ng "L4 ์ •์ƒ ์ฐธ์กฐ ์ฒ˜๋ฆฌ ์ด์ƒ: $(echo "$OUT" | grep '^N|' | head -1)"
123
+
124
+ OUT=$(run self "$DECK_SELF")
125
+ echo "$OUT" | grep -q '์ž๊ธฐ ์ž์‹  ์ฐธ์กฐ' \
126
+ && ok "L5 ์ž๊ธฐ ์ž์‹  ์ฐธ์กฐ๋Š” โš ๏ธ ๋กœ ํ‘œ๋ฉดํ™”(์ฐจ๋‹จ์€ ์•„๋‹˜)" \
127
+ || ng "L5 ์ž๊ธฐ ์ฐธ์กฐ๊ฐ€ ์•ˆ ๋œฌ๋‹ค"
128
+
129
+ OUT=$(run dec "$DECK_DECIMAL")
130
+ if echo "$OUT" | grep -q '^FINDINGS 1' && echo "$OUT" | grep -q '๋ฒ”์œ„ ๋ฐ–'; then
131
+ ok "L6 ยซ62.5%ยป ๋ฅผ ์„น์…˜์œผ๋กœ ์•ˆ ๋จน๋Š”๋‹ค โ†’ ใ€Œ62์žฅใ€์ด SLIDE ๋กœ ๋‚จ์•„ ๋ฒ”์œ„ ๋ฐ–์œผ๋กœ ์žกํžŒ๋‹ค"
132
+ else
133
+ ng "L6 ์†Œ์ˆ˜์ ์ด ์„น์…˜์œผ๋กœ ๋จนํ˜”๋‹ค(์‹ค์ธก ์‚ฌ๊ณ  ์žฌ๋ฐœ): $(echo "$OUT" | grep '^N|' | head -1)"
134
+ fi
135
+
136
+ OUT=$(run empty "$DECK_EMPTY")
137
+ echo "$OUT" | grep -q 'UNMEASURED' \
138
+ && ok "L7 ์ฐธ์กฐ 0๊ฑด โ†’ ยซ๊นจ๋—ํ•˜๋‹คยป๊ฐ€ ์•„๋‹ˆ๋ผ UNMEASURED ๋กœ ์ ๋Š”๋‹ค" \
139
+ || ng "L7 0๊ฑด์„ ํ†ต๊ณผ๋กœ ์ฝ๋Š”๋‹ค(๋ฏธ์ธก์ •โ‰ 0 ์œ„๋ฐ˜)"
140
+
141
+ echo "== ์ปจํŠธ๋กค โ€” ํŒ๋ณ„์ž๋ฅผ ์ฃฝ์ด๋ฉด ๋นจ๊ฐœ์ง€๋Š”๊ฐ€(๋˜๋Œ๋ฆผ) =="
142
+ OUT=$(run cite2 "$DECK_CITE" kill-cite)
143
+ echo "$OUT" | grep -q '^FINDINGS 1' \
144
+ && ok "CTRL-A ์ธ์šฉ ํŒ๋ณ„์ž๋ฅผ ์ฃฝ์ด๋ฉด L3 ๊ฐ€ ์˜คํƒ์œผ๋กœ ๋’ค์ง‘ํžŒ๋‹ค(ํŒ๋ณ„์ž๊ฐ€ ํ•˜์ค‘์„ ์ง„๋‹ค)" \
145
+ || ng "CTRL-A ๋ฎคํ„ดํŠธ์ธ๋ฐ ์•„๋ฌด ์ผ๋„ ์—†๋‹ค โ€” ์ธ์šฉ ๋ถ„๊ธฐ๊ฐ€ ์žฅ์‹์ด๋‹ค"
146
+
147
+ OUT=$(run sec2 "$DECK_SEC" kill-section)
148
+ echo "$OUT" | grep -q 'SLIDE 1' \
149
+ && ok "CTRL-B ์„น์…˜ ํŒ๋ณ„์ž๋ฅผ ์ฃฝ์ด๋ฉด ใ€Œ3์žฅ์—์„œใ€๊ฐ€ SLIDE ๋กœ ๋„˜์–ด๊ฐ„๋‹ค" \
150
+ || ng "CTRL-B ๋ฎคํ„ดํŠธ์ธ๋ฐ ๋ถ„๋ฅ˜๊ฐ€ ๊ทธ๋Œ€๋กœ โ€” ์„น์…˜ ๋ถ„๊ธฐ๊ฐ€ ์žฅ์‹์ด๋‹ค"
151
+
152
+ echo "== ๋ฐฐ์„ ยทํ‡ดํ™” =="
153
+ OUT=$(python3 -c "
154
+ import sys; sys.path.insert(0,'plugins/fh-commons/skills/preprep')
155
+ import lane_slide_refs as L
156
+ print(L.scan({}, '.')[1][0])
157
+ print(L.scan({'surfaces_by_id':{'built_deck':{'path':'/nope/none.pptx'}}}, '.')[1][0])
158
+ " 2>&1)
159
+ echo "$OUT" | grep -q 'NOT_CONFIGURED' && echo "$OUT" | grep -q 'UNMEASURED' \
160
+ && ok "L8 ๋ฏธ์„ ์–ธ โ†’ NOT_CONFIGURED ยท ์‹ค๋ฌผ ์—†์Œ โ†’ UNMEASURED (๋‘˜ ๋‹ค 0 ์•„๋‹˜)" \
161
+ || ng "L8 ํ‡ดํ™” ํ‘œ๊ธฐ๊ฐ€ 0 ์œผ๋กœ ์ ‘ํžŒ๋‹ค: $OUT"
162
+
163
+ grep -q "import lane_slide_refs" plugins/fh-commons/skills/preprep/preprep.py \
164
+ && ok "L9 ๋ฐฐ์„ : preprep.py ๊ฐ€ ์ด ๋ ˆ์ธ์„ ๋ถ€๋ฅธ๋‹ค" \
165
+ || ng "L9 ๋ฐฐ์„  ์—†์Œ โ€” ์ •์˜๋งŒ ์žˆ๊ณ  ์•„๋ฌด๋„ ์•ˆ ๋ถ€๋ฅธ๋‹ค(built-but-not-wired)"
166
+
167
+ echo "โ”€โ”€ preprep slide-refs lanes: PASS=$PASS FAIL=$FAIL"
168
+ [ "$FAIL" -eq 0 ] || exit 1
169
+ exit 0