@chrono-meta/fh-gate 2.6.0 โ†’ 2.7.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 (44) hide show
  1. package/.claude/rules/fh_4axis_gate.md +26 -3
  2. package/.claude-plugin/marketplace.json +2 -2
  3. package/AGENTS.md +24 -3
  4. package/CLAUDE.md +39 -12
  5. package/README.ja.md +32 -8
  6. package/README.ko.md +32 -8
  7. package/README.md +112 -15
  8. package/README.zh.md +28 -8
  9. package/knowledge/shared/harness-core/field_verdict_crossfamily_gate.md +19 -5
  10. package/knowledge/shared/harness-core/harness_incubator_doctrine.md +12 -2
  11. package/knowledge/shared/harness-core/multi_model_sidecar_strategy.md +32 -0
  12. package/knowledge/shared/harness-core/ship_readiness_gate.md +77 -8
  13. package/knowledge/shared/learnings/subagent_invocations_log.yaml +90 -0
  14. package/knowledge/shared/rules/knowledge_layer_seam.md +1 -1
  15. package/package.json +13 -2
  16. package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
  17. package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
  18. package/plugins/fh-meta/CHANGELOG.md +111 -0
  19. package/plugins/fh-meta/agents/persona-innovator.md +170 -0
  20. package/plugins/fh-meta/skills/public-surface-audit/SKILL_detail.md +16 -1
  21. package/plugins/fh-meta/skills/steel-quench/SKILL.md +97 -0
  22. package/scripts/adapters/peer_resolve.sh +58 -6
  23. package/scripts/cluster_capability_scan.sh +42 -13
  24. package/scripts/digest_landing_check.sh +142 -2
  25. package/scripts/fh_hub_identity.sh +83 -0
  26. package/scripts/fh_session_load.sh +53 -5
  27. package/scripts/fh_track_resolve.sh +114 -0
  28. package/scripts/field_canon_preload.sh +50 -5
  29. package/scripts/package_coverage_check.sh +8 -0
  30. package/scripts/prior_art_prompt.sh +168 -0
  31. package/scripts/psa_scan_lib.sh +201 -15
  32. package/scripts/residency_admission_check.sh +204 -0
  33. package/scripts/selfcheck.sh +88 -0
  34. package/scripts/test_adapter_lanes.sh +67 -2
  35. package/scripts/test_heavy_classifier_lanes.sh +144 -0
  36. package/scripts/test_marker_defense_lanes.sh +152 -0
  37. package/scripts/test_marker_soul_check_lanes.sh +211 -0
  38. package/scripts/test_prior_art_prompt_lanes.sh +128 -0
  39. package/scripts/test_psa_singlefile_lanes.sh +351 -1
  40. package/scripts/test_residency_admission_lanes.sh +60 -0
  41. package/scripts/test_track_resolve_lanes.sh +158 -0
  42. package/templates/.git-hooks/pre-commit +400 -4
  43. package/templates/.git-hooks/pre-push +17 -2
  44. package/templates/settings.PriorArt.snippet.json +15 -0
@@ -0,0 +1,152 @@
1
+ #!/usr/bin/env bash
2
+ # test_marker_defense_lanes.sh โ€” regression fixtures for pre-commit's validate_defense_leg
3
+ # (Wave 1-D defense line, 2026-08-20).
4
+ #
5
+ # WHY THIS FILE EXISTS AT ALL: the procedure was absorbed from a sibling harness whose own document
6
+ # asserted "the commit hook reads that line" and "pinned by scripts/test_marker_floor_lanes.sh".
7
+ # ๐ŸŸฅ Measured twice, with a control (`crossfamily` โ†’ 21 hits in the same file): BOTH claims were
8
+ # **0 hits** there. The prose was portable; the machine was not. This lane is the machine, built
9
+ # here, so that FH's own claim is true when FH makes it.
10
+ #
11
+ # Fixtures assert BOTH directions (known-pair): every intended shape is admitted, and every hole the
12
+ # leg closes still blocks. A suite that only asserts BLOCK cannot tell "blocked" from "blocked for
13
+ # the wrong reason"; one that only asserts PASS cannot see a leg that admits everything.
14
+ #
15
+ # Usage: bash scripts/test_marker_defense_lanes.sh Exit: 0 = all behave; 1 = regression.
16
+
17
+ set -uo pipefail
18
+ # Script-relative, NOT `git rev-parse --show-toplevel` โ€” same reason as the sibling lanes: in a
19
+ # vendored tree rev-parse answers with the OUTER repo and the suite measures somebody else's files.
20
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
21
+ HOOK="$REPO_ROOT/templates/.git-hooks/pre-commit"
22
+ T=$(mktemp -d); trap 'rm -rf "$T"' EXIT
23
+
24
+ sed -n '/^validate_defense_leg()/,/^}/p' "$HOOK" > "$T/fn.sh"
25
+
26
+ # Instrument calibration BEFORE any fixture runs. An empty extraction would let every BLOCK
27
+ # fixture "pass" against nothing and every PASS fixture fail โ€” and the suite would still print a
28
+ # verdict. Assert the body actually arrived.
29
+ if ! grep -q 'estimation-layer' "$T/fn.sh"; then
30
+ echo "โŒ HARNESS-ERROR โ€” validate_defense_leg did not extract from $HOOK."
31
+ echo " Fixtures below would measure an empty function. Aborting rather than reporting green."
32
+ exit 1
33
+ fi
34
+
35
+ pass=0; fail=0
36
+ mk() { printf '%s\n' "$2" > "$T/$1"; }
37
+ run() { bash -c "source '$T/fn.sh'; validate_defense_leg '$T/$1'" >/dev/null 2>&1; }
38
+ want() { # $1=fixture $2=expect(pass|block) $3=label
39
+ local rc; run "$1"; rc=$?
40
+ local got; [ "$rc" -eq 0 ] && got=pass || got=block
41
+ if [ "$got" = "$2" ]; then printf ' โœ… %-6s %s\n' "$2" "$3"; pass=$((pass+1))
42
+ else printf ' โŒ %-6s %s โ€” got %s\n' "$2" "$3" "$got"; fail=$((fail+1)); fi
43
+ }
44
+
45
+ GOOD='axis2-defense: reproducibility=bash scripts/test_marker_defense_lanes.sh at 4c1f2ab fairness=both arms reps=3, same fixture dir, same shell estimation-layer=all measured; known-pair separated arm from control'
46
+
47
+ echo "[marker-defense] known-pair fixtures"
48
+ echo " โ”€โ”€ admitted (must not over-block; over-blocking teaches --no-verify) โ”€โ”€"
49
+ mk ok.md "floor-status: sonnet-floor
50
+ $GOOD"
51
+ want ok.md pass "all three substantive"
52
+ mk multiline.md "floor-status: sonnet-floor
53
+ axis2-defense: reproducibility=git show 9cd7350 -- scripts/selfcheck.sh
54
+ fairness=arm and control both on 2.6.0 tarball, reps=1 each, named
55
+ estimation-layer=counts measured; the 2,367 b/day figure is a quotation"
56
+ want multiline.md pass "continuation lines are one value"
57
+ mk order.md "floor-status: below-floor
58
+ axis2-defense: estimation-layer=measured via npm pack --dry-run --json fairness=same tarball both arms reproducibility=npm pack @chrono-meta/fh-gate@2.6.0"
59
+ want order.md pass "sub-answers in any order"
60
+
61
+ echo " โ”€โ”€ blocked โ”€โ”€"
62
+ mk none.md "floor-status: sonnet-floor"
63
+ want none.md block "no axis2-defense line at all"
64
+ mk empty.md "floor-status: sonnet-floor
65
+ axis2-defense:"
66
+ want empty.md block "key present, value empty (silence is not an answer)"
67
+ mk partial.md "floor-status: sonnet-floor
68
+ axis2-defense: reproducibility=bash scripts/selfcheck.sh completes rc=0 fairness=both arms on same tree"
69
+ want partial.md block "estimation-layer missing โ€” a partial form is not a defense"
70
+ # ๐ŸŸฅ rc IS NOT ENOUGH HERE โ€” measured by a revert probe on 2026-08-20, the day this was written.
71
+ # Deleting the completeness guard leaves `partial.md` STILL blocked, because an absent sub-answer
72
+ # also reads as an empty (vacuous) one and the next guard catches it. Gating on the exit code alone
73
+ # therefore certifies a guard that is doing nothing: the classic decorative anchor. What the
74
+ # completeness guard actually buys is the PRESCRIPTION โ€” "missing sub-answer(s): estimation-layer"
75
+ # tells the author which question they skipped, where "too short" sends them to rewrite the wrong
76
+ # field. A gate's output is half its product, so the lane measures the output.
77
+ want_msg() { # $1=fixture $2=substring $3=label
78
+ local out; out=$(bash -c "source '$T/fn.sh'; validate_defense_leg '$T/$1'" 2>&1)
79
+ if printf '%s' "$out" | grep -qF "$2"; then printf ' โœ… %-6s %s\n' "msg" "$3"; pass=$((pass+1))
80
+ else printf ' โŒ %-6s %s โ€” output did not contain %s\n' "msg" "$3" "$2"; fail=$((fail+1)); fi
81
+ }
82
+ want_msg partial.md "missing sub-answer(s): estimation-layer" \
83
+ "prescription NAMES the skipped question (guard is load-bearing for the message)"
84
+ # (the short.md prescription control lives below, AFTER that fixture is created โ€”
85
+ # ๐ŸŸฅ it was first written here, before `mk short.md`, so it measured a NON-EXISTENT file and
86
+ # read the "no line at all" message. The lane was wrong, not the subject. Fixture order is
87
+ # part of the instrument.)
88
+ mk vacuous.md "floor-status: sonnet-floor
89
+ axis2-defense: reproducibility=ok fairness=yes estimation-layer=n/a"
90
+ want vacuous.md block "filled form, not an answer"
91
+ mk short.md "floor-status: sonnet-floor
92
+ axis2-defense: reproducibility=ran it fairness=same estimation-layer=nums"
93
+ want short.md block "too short to name a command, reps or layer"
94
+ want_msg short.md "vacuous answer(s)" \
95
+ "control โ€” the short case takes the OTHER prescription (the two are distinguishable)"
96
+ mk dup.md "floor-status: sonnet-floor
97
+ $GOOD
98
+ $GOOD"
99
+ want dup.md block "two lines โ€” the reader takes the first, so a correction is shadowed"
100
+
101
+ echo " โ”€โ”€ call-site pin (a function nobody calls is prose) โ”€โ”€"
102
+ # ๐ŸŸฅ Found by cross-family review 2026-08-20 (codex/gpt-5.5), self-detection 0: this suite extracts
103
+ # the FUNCTION and calls it directly, so deleting the hook's call site left every lane green while
104
+ # the leg stopped running โ€” and the skill doc claimed "pre-commit runs this". That is the exact
105
+ # defect class this whole file was built to stop importing, reproduced one layer up.
106
+ # The pin is a grep, and its limit is stated rather than hidden: it proves the call EXISTS and sits
107
+ # in the full-gate marker block, not that the block is reached. Reachability is pinned by the hook's
108
+ # own end-to-end lanes, not here.
109
+ cs=$(grep -c 'validate_defense_leg "\$MARKER"' "$HOOK")
110
+ if [ "$cs" -eq 1 ]; then
111
+ printf ' โœ… %-6s %s\n' "call" "hook calls validate_defense_leg exactly once"; pass=$((pass+1))
112
+ else
113
+ printf ' โŒ %-6s %s โ€” found %s call site(s)\n' "call" "hook calls validate_defense_leg exactly once" "$cs"; fail=$((fail+1))
114
+ fi
115
+ # and that the call is gated on the floor tiers the doc claims โ€” not on load-bearing, which is
116
+ # where it was first (wrongly) wired.
117
+ if grep -B4 'validate_defense_leg "\$MARKER"' "$HOOK" | grep -qE 'floor-status:.*sonnet-floor\|below-floor'; then
118
+ printf ' โœ… %-6s %s\n' "call" "call is gated on floor-status (sonnet-floor|below-floor)"; pass=$((pass+1))
119
+ else
120
+ printf ' โŒ %-6s %s\n' "call" "call is NOT gated on floor-status โ€” scope drifted from the doc"; fail=$((fail+1))
121
+ fi
122
+
123
+ echo " โ”€โ”€ prescriptions (rc alone certifies guards that do nothing) โ”€โ”€"
124
+ # ๐ŸŸฅ Same shadowing as the completeness guard: removing the presence guard leaves none.md/empty.md
125
+ # still blocked by the missing-subanswer guard, so rc cannot see it. What presence buys is the
126
+ # prescription that shows the author the whole field shape.
127
+ want_msg none.md "no 'axis2-defense:' line" \
128
+ "presence guard is load-bearing for its own message"
129
+ want_msg empty.md "no 'axis2-defense:' line" \
130
+ "empty value takes the same prescription as absent (silence is silence)"
131
+
132
+ echo " โ”€โ”€ controls (the leg discriminates; it does not just fire) โ”€โ”€"
133
+ # ๐ŸŸฅ known-NEGATIVE for over-blocking: a legitimate answer that merely LOOKS terse per-field but
134
+ # clears the bar. Without this, a leg that blocked everything would pass every BLOCK row above.
135
+ mk borderline.md "floor-status: sonnet-floor
136
+ axis2-defense: reproducibility=see PR #471 CI log job 96296828886 fairness=arms differ: control is v2.5.1 tarball โ€” stated estimation-layer=one measured, one quoted from CHANGELOG"
137
+ want borderline.md pass "control โ€” admits an honest answer that NAMES an asymmetry"
138
+ # ๐ŸŸฅ known-negative on the em-dash: `fairness` here contains a dash, which must NOT truncate parsing
139
+ mk dashes.md "floor-status: sonnet-floor
140
+ axis2-defense: reproducibility=git log main..HEAD --oneline fairness=both arms โ€” same reps, same env estimation-layer=measured; instrument calibrated on a known pair"
141
+ want dashes.md pass "control โ€” dashes inside a value do not truncate it"
142
+
143
+ echo
144
+ if [ "$fail" -eq 0 ]; then
145
+ echo "โœ… marker-defense: $pass lanes hold (0 failed)"
146
+ echo " ๐ŸŸฅ SCOPE: this suite pins PRESENCE, COMPLETENESS and NON-VACUITY. It cannot pin whether"
147
+ echo " the answers are TRUE โ€” that is the operator's and the weekly audit's, like every other"
148
+ echo " marker field. A green line here is not a claim that the defense was actually mounted."
149
+ exit 0
150
+ fi
151
+ echo "โŒ marker-defense: $fail failed / $pass passed"
152
+ exit 1
@@ -0,0 +1,211 @@
1
+ #!/usr/bin/env bash
2
+ # test_marker_soul_check_lanes.sh โ€” regression fixtures for pre-commit's
3
+ # validate_soul_check_leg
4
+ # (โ“’ ๊ฒฉ๋ฆฌ ๊ทธ๋ผ์šด๋”ฉ โ€” ์‚ฌ์ „ ์„ ์–ธ์œผ๋กœ ํ™•์žฅ, 2026-08-21).
5
+ #
6
+ # ๐ŸŸฅ NOT a seventh axis. `fh_three_layer_canon.md ยง1-a-2` splits axes by ยซ๋ฌด์—‡์„ ๋ฐ›์•˜๋Š”๊ฐ€ยป, and
7
+ # โ“’ already receives ยซ์ €์ž๊ฐ€ ์“ด ๋ฌธ์žฅ + ์ง€๊ธˆ์˜ ํŠธ๋ฆฌยป โ€” identical to this leg's input. Tense
8
+ # (์‚ฌ์ „ ์„ ์–ธ vs ์‚ฌํ›„ ์ฃผ์žฅ) is a posture, not an axis.
9
+ #
10
+ # WHY: `โ‘ ์˜ํ˜ผ` has been required on every marker since 2026-08-09 and is written 4/4 on the real
11
+ # corpus, with ZERO consuming code (measured with a control: `crossfamily` = 21 hits in the hook,
12
+ # `โ‘ ์˜ํ˜ผ`/`soul` = 0 lines that read it; `fh_4axis_gate.md` does not name the field). Slot with no
13
+ # consumer โ†’ presence does the judging โ†’ always "done".
14
+ #
15
+ # SCOPE โ€” channel, not judgment. These lanes assert the RECORD's properties: single ยท closed enum ยท
16
+ # non-vacuous ยท not wearing another axis's tokens ยท consistent with the โ‘ ์˜ํ˜ผ line in the SAME file.
17
+ # They never assert the read-back reached the right conclusion.
18
+ #
19
+ # ๐ŸŸฅ NAMED RESIDUAL โ€” a fabricated `reflected(<fluent reason>)` on a marker that DOES carry an
20
+ # โ‘ ์˜ํ˜ผ line PASSES. Provenance is not checkable from a file (the hook says so at the โ‘ ์˜ํ˜ผ
21
+ # comment, and that part is correct). The hole is ยง4-b's โ€” cross-family reads the marker.
22
+ #
23
+ # Fixtures assert BOTH directions (known-pair): every intended shape is admitted, and every hole
24
+ # the lane closes still blocks.
25
+ #
26
+ # Usage: bash scripts/test_marker_soul_check_lanes.sh Exit: 0 = all behave; 1 = regression.
27
+
28
+ set -uo pipefail
29
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
30
+ HOOK="$REPO_ROOT/templates/.git-hooks/pre-commit"
31
+ T=$(mktemp -d); trap 'rm -rf "$T"' EXIT
32
+
33
+ sed -n '/^validate_soul_check_leg()/,/^}/p' "$HOOK" > "$T/fn.sh"
34
+
35
+ # Instrument calibration โ€” an empty extraction would let every fixture "pass" against nothing.
36
+ if ! grep -q 'DEGRADED_NO_SOUL' "$T/fn.sh"; then
37
+ echo "โŒ HARNESS-ERROR โ€” validate_soul_check_leg did not extract from $HOOK."
38
+ echo " Fixtures below would measure an empty function. Aborting rather than reporting green."
39
+ exit 1
40
+ fi
41
+
42
+ SOUL='โ‘ ์˜ํ˜ผ: ์„ฑ๊ณต ์ •์˜ = ยซ์†Œ๋น„์ž ๊ฒฝ๋กœ์—์„œ ์™„์ฃผํ•ด rc=0 ์„ ๋‚ด๊ณ  ์ปจํŠธ๋กค๋กœ ๊ฐ€๋ฅธ๋‹คยป. ์ ˆ๋Œ€ ์•ˆ ํ•จ = ยซFAIL ์„ SKIP ์œผ๋กœ ๋ฐ”๊พธ๋Š” ๊ฒƒยป'
43
+
44
+ FAIL=0
45
+ lane() { # $1 = id $2 = expect(BLOCK|PASS) $3 = marker body
46
+ local id="$1" expect="$2" body="$3" rc out
47
+ printf '%s\n' "$body" > "$T/m.marker"
48
+ out=$( bash -c 'set -uo pipefail; . "$1"; validate_soul_check_leg "$2"' _ "$T/fn.sh" "$T/m.marker" 2>&1 ); rc=$?
49
+ local got=PASS; [ $rc -ne 0 ] && got=BLOCK
50
+ if [ "$got" = "$expect" ]; then
51
+ printf ' โœ… %-40s %s\n' "$id" "$got"
52
+ else
53
+ printf ' โŒ %-40s expected %s, got %s\n %s\n' "$id" "$expect" "$got" "$(printf '%s' "$out" | head -3)"
54
+ FAIL=1
55
+ fi
56
+ }
57
+
58
+ echo "== soul-check lanes โ€” BLOCK side (holes that must stay closed) =="
59
+ lane G1-not-in-enum BLOCK "$SOUL
60
+ soul-check: done"
61
+ lane G2-bare-reflected BLOCK "$SOUL
62
+ soul-check: reflected"
63
+ lane G3-vacuous-grounds BLOCK "$SOUL
64
+ soul-check: reflected(ok)"
65
+ lane G4-duplicate-line BLOCK "$SOUL
66
+ soul-check: UNKNOWN
67
+ soul-check: reflected(cross-family agy ๊ฐ€ ๋ธํƒ€๋ฅผ ์„ฑ๊ณต์ •์˜์— ๋น„์ถฐ 2๊ฑด ์ง€์ )"
68
+ lane G5-crossaxis-tier2 BLOCK "$SOUL
69
+ soul-check: tier2(qasp)"
70
+ lane G6-crossaxis-checked BLOCK "$SOUL
71
+ soul-check: checked(prior art ์—†์Œ)"
72
+ lane G7-crossaxis-panel BLOCK "$SOUL
73
+ soul-check: panel(codex,agy)"
74
+ echo " -- the consistency check (two fields, one record) --"
75
+ lane G8-reflected-no-soul BLOCK "axis2-model: opus
76
+ soul-check: reflected(๋ธํƒ€๋ฅผ ์„ฑ๊ณต์ •์˜์— ๋น„์ถฐ ๋ดค๊ณ  ์–ด๊ธ‹๋‚จ ์—†์Œ)"
77
+ lane G9-reflected-empty-soul BLOCK "โ‘ ์˜ํ˜ผ: ์—†์Œ
78
+ soul-check: reflected(๋ธํƒ€๋ฅผ ์„ฑ๊ณต์ •์˜์— ๋น„์ถฐ ๋ดค๊ณ  ์–ด๊ธ‹๋‚จ ์—†์Œ)"
79
+ lane G10-violated-no-soul BLOCK "axis2-model: opus
80
+ soul-check: violated(์ ˆ๋Œ€์•ˆํ•จ ์„ ์–ด๊ฒผ๋‹ค โ€” SKIP ์œผ๋กœ ๋ฐ”๊ฟจ๋‹ค)"
81
+ lane G11-nosoul-but-soul-is BLOCK "$SOUL
82
+ soul-check: DEGRADED_NO_SOUL(์˜ํ˜ผ ์ค„์ด ์—†์–ด์„œ ๋˜๋น„์ถœ ๋Œ€์ƒ ์ž์ฒด๊ฐ€ ์—†๋‹ค)"
83
+ lane G11b-nosoul-but-soul-key BLOCK "soul: ์„ฑ๊ณต ์ •์˜ = ์†Œ๋น„์ž ๊ฒฝ๋กœ์—์„œ ์™„์ฃผํ•œ๋‹ค. ์ ˆ๋Œ€ ์•ˆ ํ•จ = FAIL ์„ SKIP ์œผ๋กœ ๋ฐ”๊พธ์ง€ ์•Š๋Š”๋‹ค
84
+ soul-check: DEGRADED_NO_SOUL(์˜ํ˜ผ ์ค„์ด ์—†์–ด์„œ ๋˜๋น„์ถœ ๋Œ€์ƒ ์ž์ฒด๊ฐ€ ์—†๋‹ค)"
85
+
86
+ echo "== soul-check lanes โ€” PASS side (intended shapes must be admitted) =="
87
+ lane G12-absent-is-grace PASS "$SOUL
88
+ axis2-model: opus"
89
+ lane G13-reflected-ok PASS "$SOUL
90
+ soul-check: reflected(agy/gemini ๊ฐ€ ๋ธํƒ€๋ฅผ ์„ฑ๊ณต์ •์˜์— ๋น„์ถฐ ์žฌ๊ฒ€ โ€” ์ ˆ๋Œ€์•ˆํ•จ ์œ„๋ฐ˜ 0, ์„ฑ๊ณต์ •์˜ ๋ฏธ๋‹ฌ 0)"
91
+ lane G13b-reflected-soul-key PASS "soul: ์„ฑ๊ณต ์ •์˜ = ์†Œ๋น„์ž ๊ฒฝ๋กœ์—์„œ ์™„์ฃผํ•œ๋‹ค. ์ ˆ๋Œ€ ์•ˆ ํ•จ = FAIL ์„ SKIP ์œผ๋กœ ๋ฐ”๊พธ์ง€ ์•Š๋Š”๋‹ค
92
+ soul-check: reflected(codex ๊ฐ€ soul ํ•„๋“œ๋ฅผ ์„ฑ๊ณต์ •์˜์— ๋น„์ถฐ ์žฌ๊ฒ€ โ€” ์œ„๋ฐ˜ 0)"
93
+ lane G14-violated-is-legal PASS "$SOUL
94
+ soul-check: violated(์ ˆ๋Œ€์•ˆํ•จ ยซFAIL ์„ SKIP ์œผ๋กœยป ๋ฅผ ์–ด๊ฒผ๋‹ค โ€” ๋˜๋Œ๋ฆฌ๊ณ  ์ฃผ์ฒด๋ฅผ ์ด๋ฆ„์œผ๋กœ ์ฐ๊ฒŒ ๊ณ ์ณค๋‹ค)"
95
+ lane G15-unknown-bare-ok PASS "$SOUL
96
+ soul-check: UNKNOWN"
97
+ lane G16-degraded-not-run PASS "$SOUL
98
+ soul-check: DEGRADED_NOT_RUN(๋˜๋น„์ถœ ์‚ฌ์ด๋“œ์นด๊ฐ€ ์—†์–ด ์ด๋ฒˆ ์ปค๋ฐ‹์—์„  ์•ˆ ๋Œ๋ ธ๋‹ค)"
99
+ lane G17-nosoul-when-truly PASS "axis2-model: opus
100
+ soul-check: DEGRADED_NO_SOUL(๊ฒฝ๋Ÿ‰ ์˜ˆ์™ธ ์ปค๋ฐ‹์ด๋ผ ์˜ํ˜ผ ์ค„์„ ์•ˆ ์ผ๋‹ค)"
101
+ lane G18-not-applicable PASS "$SOUL
102
+ soul-check: not-applicable(๋ฌธ์„œ ์˜คํƒ€ ์ˆ˜์ •์ด๋ผ ์„ฑ๊ณต์ •์˜์— ๊ฑธ๋ฆฌ๋Š” ๋ธํƒ€๊ฐ€ ์—†๋‹ค)"
103
+ lane G19-em-dash-reason PASS "$SOUL
104
+ soul-check: reflected(codex ๊ฐ€ ์„ฑ๊ณต์ •์˜ ๋Œ€์กฐ โ€” ์–ด๊ธ‹๋‚จ ์—†์Œ) โ€” 2026-08-21"
105
+
106
+
107
+ # โ”€โ”€ โ‘ ์˜ํ˜ผ presence lane (validate_soul_present_leg) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
108
+ sed -n '/^validate_soul_present_leg()/,/^}/p' "$HOOK" > "$T/fnp.sh"
109
+ if ! grep -q 'SOUL_PRESENT_GRACE' "$T/fnp.sh" && ! grep -q 'โ‘ ์˜ํ˜ผ line' "$T/fnp.sh"; then
110
+ echo "โŒ HARNESS-ERROR โ€” validate_soul_present_leg did not extract. Aborting."
111
+ exit 1
112
+ fi
113
+ GRACE=$(grep -m1 '^SOUL_PRESENT_GRACE_DATE=' "$HOOK" | sed -E 's/.*"(.*)"/\1/')
114
+
115
+ planep() { # $1=id $2=expect $3=filename-date $4=body
116
+ local id="$1" expect="$2" d="$3" body="$4" rc out f
117
+ f="$T/.axes_23_passed_fix_x_${d}.marker"
118
+ printf '%s\n' "$body" > "$f"
119
+ out=$( bash -c 'set -uo pipefail; SOUL_PRESENT_GRACE_DATE="$3"; . "$1"; validate_soul_present_leg "$2"' \
120
+ _ "$T/fnp.sh" "$f" "$GRACE" 2>&1 ); rc=$?
121
+ local got=PASS; [ $rc -ne 0 ] && got=BLOCK
122
+ if [ "$got" = "$expect" ]; then printf ' โœ… %-40s %s\n' "$id" "$got"
123
+ else printf ' โŒ %-40s expected %s, got %s\n %s\n' "$id" "$expect" "$got" "$(printf '%s' "$out"|head -2)"; FAIL=1; fi
124
+ }
125
+
126
+ echo
127
+ echo "== โ‘ ์˜ํ˜ผ presence lanes (grace = $GRACE) =="
128
+ planep P1-missing-post-grace BLOCK "$GRACE" 'axes-run: โ“=codex โ“‘=โ†’standpoint
129
+ crossfamily: panel(codex,agy) โ€” ๊ฒน์นœ ์ง€์  0'
130
+ # ๐ŸŸฅ P2 flipped BLOCKโ†’PASS on 2026-08-21: a bare `์—†์Œ` is the value CLAUDE.md ยง์ž๊ธฐ ๋Œ€์กฐ
131
+ # explicitly permits, and blocking it punished the honest form while a padded ยซI didn't write
132
+ # oneยป paragraph passed. Found by a peer session's first real use, not by this suite.
133
+ planep P2-declared-absent-ok PASS "$GRACE" 'โ‘ ์˜ํ˜ผ: ์—†์Œ
134
+ axes-run: โ“=codex'
135
+ planep P2b-empty-field-blocks BLOCK "$GRACE" 'soul:
136
+ axes-run: โ“=codex'
137
+ planep P2c-absent-heading-form PASS "$GRACE" '## โ‘ ์˜ํ˜ผ โ€” ์—†์Œ
138
+ axes-run: โ“=codex'
139
+ # fixture updated 2026-08-21: `์—†์Œ` became a legal declared-absence value, so a truly vacuous
140
+ # field is now the discriminating input (the point of codex's lane was masking-by-next-field,
141
+ # not the token `์—†์Œ`).
142
+ planep P2b-vacuous-before-fields BLOCK "$GRACE" 'โ‘ ์˜ํ˜ผ: ok
143
+ axis2-engine: steel-quench
144
+ axis2-model: opus
145
+ floor-status: ok
146
+ axes-run: โ“=codex โ“‘=โ†’standpoint โ“’=x โ““=x โ“”=x โ“•=x
147
+ controls: alive โ€” known-positive x 1 hit ยท known-negative 0'
148
+ planep P3-present-korean PASS "$GRACE" "$SOUL
149
+ axes-run: โ“=codex"
150
+ planep P4-present-soul-key PASS "$GRACE" 'soul: ์„ฑ๊ณต ์ •์˜ = ์†Œ๋น„์ž ๊ฒฝ๋กœ์—์„œ ์™„์ฃผํ•œ๋‹ค. ์ ˆ๋Œ€ ์•ˆ ํ•จ = FAIL ์„ SKIP ์œผ๋กœ ๋ฐ”๊พธ์ง€ ์•Š๋Š”๋‹ค
151
+ axes-run: โ“=codex'
152
+ planep P5-grace-old-exempt PASS "2026-08-01" 'axes-run: โ“=codex
153
+ crossfamily: panel(codex,agy) โ€” ๊ฒน์นœ ์ง€์  0'
154
+ planep P6-real-corpus-negative BLOCK "$GRACE" "$(cat "$REPO_ROOT/tracks/_meta/.axes_23_passed_feat_target-freeze-gate_2026-08-18.marker" 2>/dev/null || echo 'axes-run: x')"
155
+ planep P7-real-corpus-positive PASS "$GRACE" "$(cat "$REPO_ROOT/tracks/_meta/.axes_23_passed_fix_witness-two-commit-discipline_2026-08-17.marker" 2>/dev/null || echo 'โ‘ ์˜ํ˜ผ: ์„ฑ๊ณต ์ •์˜ = ํ•˜๋‚˜ ๋‘˜ ์…‹ ๋„ท ๋‹ค์„ฏ ์—ฌ์„ฏ')"
156
+
157
+ echo
158
+ echo "== cross-family findings โ€” regression lanes (codex/gpt-5.5, diff ์ถ•, 2026-08-21) =="
159
+ # F1 self-referential FP: codex's own alignment fix flipped G8 to PASS because the
160
+ # `soul-check:` line contains ใ€Œ์„ฑ๊ณต์ •์˜ใ€ and matched as its own evidence.
161
+ lane F1-selfref-soulcheck-only BLOCK 'axis2-model: opus
162
+ soul-check: reflected(๋ธํƒ€๋ฅผ ์„ฑ๊ณต์ •์˜์— ๋น„์ถฐ ๋ดค๊ณ  ์ ˆ๋Œ€ ์•ˆ ํ•จ ๋„ ์•ˆ ์–ด๊ฒผ๋‹ค)'
163
+ # F4 grounds outside the parens
164
+ lane F4-grounds-outside-parens BLOCK "$SOUL
165
+ soul-check: reflected() โ€” ์•„์ฃผ ๊ธธ๊ณ  ๊ทธ๋Ÿด๋“ฏํ•œ ๊ผฌ๋ฆฌ๋ง์ด ์—ฌ๊ธฐ ๋ถ™์–ด ์žˆ์–ด์„œ ๊ธธ์ด๋ฅผ ์ฑ„์šด๋‹ค"
166
+ # F3 near-miss key reads as absent
167
+ lane F3-nearmiss-key BLOCK "$SOUL
168
+ soul_check: reflected(๋ธํƒ€๋ฅผ ์„ฑ๊ณต์ •์˜์— ๋น„์ถฐ ์žฌ๊ฒ€ํ–ˆ๋‹ค)"
169
+ lane F3b-nearmiss-space BLOCK "$SOUL
170
+ soul-check : reflected(๋ธํƒ€๋ฅผ ์„ฑ๊ณต์ •์˜์— ๋น„์ถฐ ์žฌ๊ฒ€ํ–ˆ๋‹ค)"
171
+ planep F1b-presence-selfref BLOCK "$GRACE" 'axes-run: โ“=codex
172
+ soul-check: reflected(์„ฑ๊ณต ์ •์˜ ๋Œ€์กฐ ยท ์ ˆ๋Œ€ ์•ˆ ํ•จ ํ™•์ธ)'
173
+ planep F1c-comment-not-soul BLOCK "$GRACE" '# ์„ฑ๊ณต ์ •์˜ ๋ฅผ ๋‚˜์ค‘์— ์“ธ ์˜ˆ์ •์ด๋‹ค ์ ˆ๋Œ€ ์•ˆ ํ•จ ๋„
174
+ axes-run: โ“=codex'
175
+ # F2: unparseable date must NOT take the grace exit (was fail-open)
176
+ printf 'axes-run: a=codex\n' > "$T/.axes_23_passed_nodate.marker"
177
+ _o=$( bash -c 'set -uo pipefail; SOUL_PRESENT_GRACE_DATE="$3"; . "$1"; validate_soul_present_leg "$2"' \
178
+ _ "$T/fnp.sh" "$T/.axes_23_passed_nodate.marker" "$GRACE" 2>&1 ); _r=$?
179
+ if [ $_r -ne 0 ]; then printf ' โœ… %-40s %s\n' "F2-unparseable-date-not-exempt" "BLOCK"
180
+ else printf ' โŒ %-40s expected BLOCK, got PASS\n' "F2-unparseable-date-not-exempt"; FAIL=1; fi
181
+
182
+ echo
183
+ echo "== cross-family findings โ€” regression lanes (agy/gemini-3.1-pro, ์ฃผ์žฅยท๊ธฐ๋ก ์ถ•) =="
184
+ # A6 [S]: grounds counted with ${#} passed on a single long Korean token. Word count now.
185
+ lane A6-korean-onetoken-grounds BLOCK "$SOUL
186
+ soul-check: reflected(์–ด๊ธ‹๋‚จ์—†์Œํ™•์ธ์™„๋ฃŒํ–ˆ์Œ)"
187
+ # A6b [S]: the two legs disagreed on the SAME soul block (6 vs 4 words). Aligned to 6.
188
+ # 5 words under `wc -w` โ€” passed the old 4-word floor in the check leg while the present leg
189
+ # demanded 6 on the SAME block. Empirically calibrated, not guessed: `wc -w` does not equal a
190
+ # human word count on Korean (ยซโ‘ ์˜ํ˜ผ: ์™„์ฃผยป reports 3), so the fixture was measured, not reasoned.
191
+ lane A6b-threshold-aligned BLOCK 'โ‘ ์˜ํ˜ผ: ์„ฑ๊ณต ์ •์˜
192
+ soul-check: reflected(๋ธํƒ€๋ฅผ ์„ฑ๊ณต์ •์˜์— ๋น„์ถฐ ์žฌ๊ฒ€ํ–ˆ๊ณ  ์–ด๊ธ‹๋‚จ ์—†์Œ)'
193
+ # A6-control: a genuinely grounded, multi-word reason still passes (this lane must NOT block).
194
+ lane A6c-control-passes PASS "$SOUL
195
+ soul-check: reflected(codex ๊ฐ€ ์„ฑ๊ณต์ •์˜ ๋Œ€์กฐ โ€” ์–ด๊ธ‹๋‚จ ์—†์Œ)"
196
+
197
+ echo
198
+ echo "== ์ฒซ ์‹ค์‚ฌ์šฉ findings โ€” peer session (forge-harness-59, 2026-08-21) =="
199
+ # A marker that declares NO success definition cannot claim to have reflected against one.
200
+ lane N1-absent-soul-vs-reflected BLOCK 'soul: ์—†์Œ
201
+ soul-check: reflected(๋ธํƒ€๋ฅผ ์„ฑ๊ณต์ •์˜์— ๋น„์ถฐ ์žฌ๊ฒ€ํ–ˆ๊ณ  ์–ด๊ธ‹๋‚จ ์—†์Œ)'
202
+ lane N2-absent-soul-degraded-ok PASS 'soul: ์—†์Œ
203
+ soul-check: DEGRADED_NO_SOUL(์„ค๊ณ„ ์ „์— ์„ฑ๊ณต ์ •์˜๋ฅผ ์“ฐ์ง€ ์•Š์•˜๋‹ค โ€” ๋˜๋น„์ถœ ๋Œ€์ƒ์ด ์—†๋‹ค)'
204
+ # The padded confession that passed in the field: it IS a real declaration of absence, and once
205
+ # `์—†์Œ` is a first-class value the padded form is no longer the only way through.
206
+ lane N3-padded-confession PASS 'soul: ์—†์Œ โ€” ์„ค๊ณ„ ์ „์— ์•ˆ ์ผ๋‹ค. ์‚ฌํ›„ ์žฌ๊ตฌ์„ฑ์ด ์•„๋‹ˆ๋ผ ๊ทธ ์‚ฌ์‹ค์„ ์ ๋Š”๋‹ค. ๋ถ™์žก๊ณ  ์žˆ๋˜ ๊ฒƒ์€ ๋ฒ”์œ„ ๊ฒฐ์ • ํ•˜๋‚˜์˜€๋‹ค
207
+ axes-run: โ“=codex'
208
+
209
+ echo
210
+ if [ $FAIL -eq 0 ]; then echo "SOUL LANES: PASS"; else echo "SOUL LANES: FAIL"; fi
211
+ exit $FAIL
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env bash
2
+ # test_prior_art_prompt_lanes.sh โ€” T2 ํ›…์˜ known-pair ์•ต์ปค.
3
+ # ๐ŸŸฅ ์ด ํ›…์˜ ์‚ฌํ™œ์€ ยซ๋ง‰๋‚˜ยป ๊ฐ€ ์•„๋‹ˆ๋ผ **ยซ์†Œ์Œ์ด ์•„๋‹Œ๊ฐ€ยป** ๋‹ค. ๋ƒ‰๋…์ž๊ฐ€ ์ง€๋ชฉํ•œ ์ž๊ธฐ๋ฌด๋ ฅํ™” ๊ฒฝ๋กœ:
4
+ # ํŠธ๋ฆฌ๊ฑฐ๊ฐ€ ๋ถ€์ •ํ™•ํ•˜๋ฉด ํ•˜๋ฃจ ์ˆ˜์‹ญ ๋ฒˆ ๋œจ๊ณ , 3ํšŒ ๊ฑฐ์ ˆ ์–ต์ œ๊ฐ€ ์˜ค์ „ ์ค‘์— ์ผœ์ ธ์„œ ๊ธฐ๋Šฅ์ด ์กฐ์šฉํžˆ ์ฃฝ๋Š”๋‹ค.
5
+ # ๊ทธ๋ž˜์„œ ์Œ์„ฑ ๋ ˆ์ธ์ด ์–‘์„ฑ๋ณด๋‹ค ๋งŽ๋‹ค.
6
+ set -uo pipefail
7
+ FH="$(cd "$(dirname "$0")/.." && pwd)"
8
+ H="$FH/scripts/prior_art_prompt.sh"
9
+ PASS=0; FAIL=0
10
+ T="$(mktemp -d -t pap.XXXXXX)" || exit 3
11
+ trap 'rm -rf "$T"' EXIT INT TERM
12
+ chk(){ if [ "$2" = "$3" ]; then echo " โœ… $1"; PASS=$((PASS+1)); else echo " โŒ $1 (got=$2 want=$3)"; FAIL=$((FAIL+1)); fi; }
13
+ # ๊ฐ ํ˜ธ์ถœ์€ ์„ธ์…˜-์Šคํƒฌํ”„๋ฅผ ๊ฒฉ๋ฆฌํ•ด์•ผ ยซํ•œ ๋ฒˆ๋งŒยป ๊ทœ์น™์— ์„œ๋กœ ์•ˆ ๊ฑธ๋ฆฐ๋‹ค
14
+ fires(){ local d; d="$(mktemp -d -t papd.XXXXXX)"; mkdir -p "$d/.claude"
15
+ local o; o=$(printf '{"tool_input":{"file_path":"%s"}}' "$1" | CLAUDE_PROJECT_DIR="$d" bash "$H" 2>/dev/null)
16
+ rm -rf "$d"; [ -n "$o" ] && echo yes || echo no; }
17
+
18
+ echo "โ”€โ”€ ์–‘์„ฑ: ์ƒˆ ยซ๋ฉ”์ปค๋‹ˆ์ฆ˜ยป ์„ ์“ฐ๊ธฐ ์ง์ „ โ”€โ”€"
19
+ chk "P1 ์ƒˆ .sh" "$(fires "$T/new_gate.sh")" yes
20
+ chk "P2 ์ƒˆ SKILL.md" "$(fires "$T/plugins/x/skills/y/SKILL.md")" yes
21
+ chk "P3 ์ƒˆ agents/*.md" "$(fires "$T/plugins/x/agents/z.md")" yes
22
+ chk "P4 ์ƒˆ .py" "$(fires "$T/tool.py")" yes
23
+
24
+ echo "โ”€โ”€ ์Œ์„ฑ: ์†Œ์Œ์ด ๋˜๋ฉด ์ด ๋Šฅ๋ ฅ์ด ์ฃฝ๋Š”๋‹ค โ”€โ”€"
25
+ printf 'x\n' > "$T/exists.sh"
26
+ chk "N1 ๐ŸŸฅ ยซ์ด๋ฏธ ์žˆ๋Š” ํŒŒ์ผ ํŽธ์ง‘ยป ์€ ์ƒˆ๋กœ ์ง“๊ธฐ๊ฐ€ ์•„๋‹ˆ๋‹ค" "$(fires "$T/exists.sh")" no
27
+ chk "N2 ๊ธฐ๋ก/์‚ฐ์ถœ๋ฌผ(.md ์ผ๋ฐ˜)" "$(fires "$T/notes.md")" no
28
+ chk "N3 tracks/ ์•„๋ž˜ ๊ธฐ๋ก" "$(fires "$T/tracks/_meta/x.sh")" no
29
+ chk "N4 ๋กœ๊ทธ" "$(fires "$T/logs/a.sh")" no
30
+ chk "N5 json/lock" "$(fires "$T/a.json")" no
31
+
32
+ echo "โ”€โ”€ ๐ŸŸฅ ์Šค์ฝ”ํ”„๊ฐ€ ยซ์„ธ์…˜ยป ์ด์ง€ ยซ๋‚ ์งœยป ๊ฐ€ ์•„๋‹ˆ๋‹ค (์ดˆํŒ ๊ฒฐํ•จ์˜ ์•ต์ปค) โ”€โ”€"
33
+ # ์ดˆํŒ์€ ์Šคํƒฌํ”„๋ฅผ ๋‚ ์งœ๋กœ ์žก์•„, ํ•˜๋ฃจ์— ์„ธ์…˜์„ ์—ฌ๋Ÿฟ ๋„๋Š” ํ™˜๊ฒฝ์—์„œ **์ฒซ ์„ธ์…˜๋งŒ ๋œจ๊ณ  ๋‚˜๋จธ์ง€๋Š” ์ „๋ถ€
34
+ # ์นจ๋ฌต**ํ–ˆ๋‹ค. ์ฃผ์„์€ ยซ์„ธ์…˜๋‹นยป ์ด์—ˆ๊ณ  ๊ตฌํ˜„์€ ยซํ•˜๋ฃจ๋‹นยป ์ด์—ˆ๋‹ค โ€” ๊ทธ ๋‘˜์ด ๊ฐˆ๋ฆฌ๋Š” ๊ฒƒ์ด ์ด ๋ ˆ์ธ์ด๋‹ค.
35
+ S="$(mktemp -d -t papsid.XXXXXX)"; mkdir -p "$S/.claude"
36
+ sid_fire(){ local o; o=$(printf '{"session_id":"%s","tool_input":{"file_path":"%s/%s.sh"}}' "$1" "$T" "$2" \
37
+ | CLAUDE_PROJECT_DIR="$S" bash "$H" 2>/dev/null); [ -n "$o" ] && echo yes || echo no; }
38
+ chk "S1 ์„ธ์…˜ A 1ํšŒ์ฐจ โ†’ ๋œฌ๋‹ค" "$(sid_fire sess-A f1)" yes
39
+ chk "S2 ์„ธ์…˜ A 2ํšŒ์ฐจ โ†’ ๋ฌด์Œ" "$(sid_fire sess-A f2)" no
40
+ chk "S3 ๐ŸŸฅ ์„ธ์…˜ B 1ํšŒ์ฐจ โ†’ ๋œฌ๋‹ค (๊ฐ™์€ ๋‚  ๋‹ค๋ฅธ ์„ธ์…˜)" "$(sid_fire sess-B f3)" yes
41
+ chk "S4 CONTROL โ€” session_id ์—†์œผ๋ฉด ๋‚ ์งœ๋กœ degrade(๋ฌด์Œ ์•„๋‹˜)" \
42
+ "$(printf '{"tool_input":{"file_path":"%s/f4.sh"}}' "$T" | CLAUDE_PROJECT_DIR="$(mktemp -d -t papnos.XXXXXX)" bash "$H" 2>/dev/null | grep -c additionalContext)" 1
43
+ rm -rf "$S"
44
+
45
+ echo "โ”€โ”€ ์„ธ์…˜๋‹น ํ•œ ๋ฒˆ (3ํšŒ ๊ฑฐ์ ˆ ์–ต์ œ๋กœ ์กฐ์šฉํžˆ ์ฃฝ๋Š” ๊ฒฝ๋กœ ์ฐจ๋‹จ) โ”€โ”€"
46
+ D="$(mktemp -d -t paponce.XXXXXX)"; mkdir -p "$D/.claude"
47
+ o1=$(printf '{"tool_input":{"file_path":"%s/a.sh"}}' "$T" | CLAUDE_PROJECT_DIR="$D" bash "$H" 2>/dev/null)
48
+ o2=$(printf '{"tool_input":{"file_path":"%s/b.sh"}}' "$T" | CLAUDE_PROJECT_DIR="$D" bash "$H" 2>/dev/null)
49
+ chk "O1 ์ฒซ ๋ฒˆ์งธ๋Š” ๋œฌ๋‹ค" "$([ -n "$o1" ] && echo yes || echo no)" yes
50
+ chk "O2 ๋‘ ๋ฒˆ์งธ๋Š” ์•ˆ ๋œฌ๋‹ค" "$([ -n "$o2" ] && echo yes || echo no)" no
51
+ rm -rf "$D"
52
+
53
+ echo "โ”€โ”€ ๐ŸŸฅ ์ ˆ๋Œ€ ์•ˆ ๋ง‰๋Š”๋‹ค (๊ฐ€์—ญ ํ‘œ๋ฉด) โ”€โ”€"
54
+ D2="$(mktemp -d -t papnb.XXXXXX)"; mkdir -p "$D2/.claude"
55
+ out=$(printf '{"tool_input":{"file_path":"%s/c.sh"}}' "$T" | CLAUDE_PROJECT_DIR="$D2" bash "$H" 2>/dev/null); rc=$?
56
+ chk "B1 exit 0" "$rc" 0
57
+ chk "B2 permissionDecision ์„ ์•ˆ ๋‚ธ๋‹ค (์ž๋™์Šน์ธ ์—ญ์ „ ๊ธˆ์ง€)" \
58
+ "$(printf '%s' "$out" | grep -c permissionDecision)" 0
59
+ chk "B3 ๋ชจ๋ธ ์ฑ„๋„(additionalContext)๋กœ ๊ฐ„๋‹ค" \
60
+ "$(printf '%s' "$out" | grep -c additionalContext)" 1
61
+ rm -rf "$D2"
62
+
63
+ echo "โ”€โ”€ D: Bash ๊ฒฝ๋กœ โ€” ๐ŸŸฅ ์ฒซ ์‹ค์‚ฌ์šฉ์ด ยซ๋น„๋ฐœํ™”ยป ๋กœ ๋‚ฌ๋˜ ์ž๋ฆฌ (2026-08-21) โ”€โ”€"
64
+ # matcher ๊ฐ€ `Write` ๋ฟ์ด์—ˆ๋Š”๋ฐ auto-mode ๋Š” ํŒŒ์ผ์„ **Bash ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ/heredoc** ์œผ๋กœ ๋งŒ๋“ ๋‹ค.
65
+ # ์ฆ‰ ์ด ํ›…์ด ๊ฒจ๋ƒฅํ•œ ์ˆœ๊ฐ„์ด ๊ตฌ์กฐ์ ์œผ๋กœ Write ๋ฅผ ์•ˆ ํƒ”๋‹ค. ์•„๋ž˜๊ฐ€ ๊ทธ ์žฌํ˜„์ด๋‹ค.
66
+ D3=$(mktemp -d); export CLAUDE_PROJECT_DIR="$D3"; mkdir -p "$D3/.claude"
67
+ chk "D1 Bash heredoc ๋กœ ์ƒˆ .sh โ†’ ๋ฐœํ™”" \
68
+ "$(printf '%s' '{"session_id":"d1","tool_input":{"command":"cat > scripts/new_thing.sh <<EOF\necho hi\nEOF"}}' | bash "$H" 2>/dev/null | grep -c additionalContext)" 1
69
+ rm -f "$D3/.claude/.prior_art_prompted_"*
70
+ chk "D2 printf ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ๋กœ ์ƒˆ SKILL.md โ†’ ๋ฐœํ™”" \
71
+ "$(printf '%s' '{"session_id":"d2","tool_input":{"command":"printf x > plugins/z/skills/q/SKILL.md"}}' | bash "$H" 2>/dev/null | grep -c additionalContext)" 1
72
+ rm -f "$D3/.claude/.prior_art_prompted_"*
73
+ # โ˜…์ปจํŠธ๋กค โ€” D1/D2 ๊ฐ€ ยซBash ๋ฉด ๋ฌด์กฐ๊ฑด ๋ฐœํ™”ยป ๊ฐ€ ์•„๋‹˜์„ ๋ณด์ธ๋‹ค. ์†Œ์Œ์ด ์ด ํ›…์„ ์ฃฝ์ธ๋‹ค.
74
+ chk "D3 โ˜…์ปจํŠธ๋กค ๋กœ๊ทธ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ + 2>/dev/null โ†’ ์นจ๋ฌต" \
75
+ "$(printf '%s' '{"session_id":"d3","tool_input":{"command":"echo x > /tmp/a.log 2>/dev/null"}}' | bash "$H" 2>/dev/null | wc -c | tr -d ' ')" 0
76
+ chk "D4 โ˜…์ปจํŠธ๋กค tracks/ ์‚ฐ์ถœ๋ฌผ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ โ†’ ์นจ๋ฌต" \
77
+ "$(printf '%s' '{"session_id":"d4","tool_input":{"command":"cat > tracks/_meta/note.sh <<EOF\nx\nEOF"}}' | bash "$H" 2>/dev/null | wc -c | tr -d ' ')" 0
78
+ chk "D5 โ˜…์ปจํŠธ๋กค ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ์—†๋Š” ๋ช…๋ น โ†’ ์นจ๋ฌต" \
79
+ "$(printf '%s' '{"session_id":"d5","tool_input":{"command":"git status --short"}}' | bash "$H" 2>/dev/null | wc -c | tr -d ' ')" 0
80
+ chk "D6 โ˜…์ปจํŠธ๋กค ๊ฐ™์€ ์„ธ์…˜ ๋‘ ๋ฒˆ์งธ Bash โ†’ ์นจ๋ฌต (์„ธ์…˜๋‹น 1ํšŒ๊ฐ€ Bash ์—์„œ๋„ ์‚ฐ๋‹ค)" \
81
+ "$( { printf '%s' '{"session_id":"d6","tool_input":{"command":"cat > scripts/a.sh <<EOF\nx\nEOF"}}' | bash "$H" >/dev/null 2>&1
82
+ printf '%s' '{"session_id":"d6","tool_input":{"command":"cat > scripts/b.sh <<EOF\nx\nEOF"}}' | bash "$H" 2>/dev/null | wc -c | tr -d ' '; } )" 0
83
+ # ๐ŸŸฅ E: cross-family 4 HIGH โ€” **์ „๋ถ€ ์˜ค๋ฐœํ™”**๋‹ค (2026-08-21). ์ด ํ›…์—์„œ ์˜ค๋ฐœํ™”๋Š” ๋ฏธ๋ฐœํ™”๋ณด๋‹ค
84
+ # ํ›จ์”ฌ ๋น„์‹ธ๋‹ค: ์†Œ์Œ์ด ๋˜๋ฉด ์šด์˜์ž๊ฐ€ ๋ฌด์‹œํ•˜๊ณ  ๋Šฅ๋ ฅ์ด ์ฃฝ๋Š”๋‹ค(์› ์ €์ž๊ฐ€ ยซ์ž๊ธฐ๋ฌด๋ ฅํ™” ๊ฒฝ๋กœยป ๋ผ ๋ช…๋ช…).
85
+ rm -f "$D3/.claude/.prior_art_prompted_"*
86
+ chk "E1 โ˜…์ปจํŠธ๋กค tee_probe.py ์–ธ๊ธ‰ (tee ์— ๋’ค ๊ฒฝ๊ณ„ ์—†๋˜ ์ž๋ฆฌ) โ†’ ์นจ๋ฌต" \
87
+ "$(printf '%s' '{"session_id":"e1","tool_input":{"command":"rg tee_probe.py scripts"}}' | bash "$H" 2>/dev/null | wc -c | tr -d ' ')" 0
88
+ chk "E2 โ˜…์ปจํŠธ๋กค stderr ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ 2> โ€ฆ/errors.sh โ†’ ์นจ๋ฌต (>>? ๊ฐ€ 2> ์•ˆ์˜ > ๋ฅผ ์žก๋˜ ์ž๋ฆฌ)" \
89
+ "$(printf '%s' '{"session_id":"e2","tool_input":{"command":"pytest 2> /tmp/pytest_errors.sh"}}' | bash "$H" 2>/dev/null | wc -c | tr -d ' ')" 0
90
+ chk "E2b โ˜…์ปจํŠธ๋กค stdout ๋ฒ„๋ฆฌ๊ณ  stderr ๋งŒ .sh ๋กœ โ†’ ์นจ๋ฌต" \
91
+ "$(printf '%s' '{"session_id":"e2b","tool_input":{"command":"make test > /dev/null 2> /tmp/failures.sh"}}' | bash "$H" 2>/dev/null | wc -c | tr -d ' ')" 0
92
+ chk "E3 โ˜…์ปจํŠธ๋กค ํ”„๋กœ์ ํŠธ ๋ฐ– ๋ฆฌํฌํŠธ git diff > /tmp/review_delta.sh โ†’ ์นจ๋ฌต" \
93
+ "$(printf '%s' '{"session_id":"e3","tool_input":{"command":"git diff > /tmp/review_delta.sh"}}' | bash "$H" 2>/dev/null | wc -c | tr -d ' ')" 0
94
+ chk "E4 โ˜…์ปจํŠธ๋กค ๋ฏธํ™•์žฅ ๋ณ€์ˆ˜ \$LOG_DIR/x.sh โ†’ ์นจ๋ฌต (๋ฆฌํ„ฐ๋Ÿด๋กœ ์กด์žฌ๊ฒ€์‚ฌํ•˜๋ฉด ๊ฑฐ์ง“์ด๋‹ค)" \
95
+ "$(printf '%s' '{"session_id":"e4","tool_input":{"command":"echo more >> \"$LOG_DIR/x.sh\""}}' | bash "$H" 2>/dev/null | wc -c | tr -d ' ')" 0
96
+ # ๐ŸŸฅ E1b/E2c โ€” **ํ”„๋กœ์ ํŠธ ยซ์•ˆยป** ๊ฒฝ๋กœ๋กœ ๊ฐ™์€ ๋‘ ๊ฒฐํ•จ์„ ์นœ๋‹ค. E1/E2 ๋Š” /tmp ๋ผ์„œ
97
+ # ํ”„๋กœ์ ํŠธ-๋ฒ”์œ„ ๊ทœ์น™(โ“’)๋งŒ์œผ๋กœ๋„ ์นจ๋ฌตํ–ˆ๋‹ค โ€” ๋˜๋Œ๋ฆผ ์‹ค์ธก์—์„œ ์ •๊ทœ์‹ ์ˆ˜๋ฆฌ๋ฅผ ๋˜๋Œ๋ ค๋„
98
+ # ์ ์ƒ‰์ด 0 ์ด์—ˆ๊ณ (=์žฅ์‹), ๊ทธ๊ฒŒ ์ด ๋‘ ๋ ˆ์ธ์„ ๋งŒ๋“  ์ด์œ ๋‹ค. ์•„๋ž˜๋Š” โ“’ ๊ฐ€ ๋ชป ์žก๋Š” ์ž๋ฆฌ๋‹ค.
99
+ rm -f "$D3/.claude/.prior_art_prompted_"*
100
+ chk "E1b โ˜…์ปจํŠธ๋กค ํ”„๋กœ์ ํŠธ ์•ˆ tee_probe.sh ์–ธ๊ธ‰ โ†’ ์นจ๋ฌต (tee ๋’ค ๊ฒฝ๊ณ„๋ฅผ ๊ฒฉ๋ฆฌํ•ด์„œ ์นœ๋‹ค)" \
101
+ "$(printf '%s' '{"session_id":"e1b","tool_input":{"command":"rg tee_probe.sh scripts"}}' | bash "$H" 2>/dev/null | wc -c | tr -d ' ')" 0
102
+ chk "E2c โ˜…์ปจํŠธ๋กค ํ”„๋กœ์ ํŠธ ์•ˆ stderr 2> scripts/errors.sh โ†’ ์นจ๋ฌต (๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ์•ž ๊ฒฝ๊ณ„๋ฅผ ๊ฒฉ๋ฆฌํ•ด์„œ ์นœ๋‹ค)" \
103
+ "$(printf '%s' '{"session_id":"e2c","tool_input":{"command":"pytest 2> scripts/errors.sh"}}' | bash "$H" 2>/dev/null | wc -c | tr -d ' ')" 0
104
+ # E5/E6 โ€” session_id ๋Š” ์‹ ๋ขฐํ•  ์ˆ˜ ์—†๋Š” ์ž…๋ ฅ์ด๋‹ค
105
+ _ESC=$(dirname "$D3")/pap_lane_escape_$$
106
+ rm -f "$_ESC"
107
+ printf '%s' "{\"session_id\":\"x/../../../$(basename "$_ESC")\",\"tool_input\":{\"file_path\":\"scripts/zz_new.sh\"}}" | bash "$H" >/dev/null 2>&1
108
+ chk "E5 session_id ๊ฒฝ๋กœ ํƒˆ์ถœ โ†’ ํ”„๋กœ์ ํŠธ ๋ฐ–์— ํŒŒ์ผ์ด ์•ˆ ์ƒ๊ธด๋‹ค" \
109
+ "$([ -e "$_ESC" ] && echo escaped || echo none)" none
110
+ rm -f "$_ESC"
111
+ rm -f "$D3/.claude/.prior_art_prompted_"*
112
+ chk "E6 session_id ๋์— ยซ/ยป โ†’ ๋‘ ๋ฒˆ์งธ ํ˜ธ์ถœ์€ ์นจ๋ฌต (์Šคํƒฌํ”„๊ฐ€ ์‹ค์ œ๋กœ ์จ์ง„๋‹ค)" \
113
+ "$( { printf '%s' '{"session_id":"x/","tool_input":{"file_path":"scripts/zz_a.sh"}}' | bash "$H" >/dev/null 2>&1
114
+ printf '%s' '{"session_id":"x/","tool_input":{"file_path":"scripts/zz_b.sh"}}' | bash "$H" 2>/dev/null | wc -c | tr -d ' '; } )" 0
115
+ # E7 โ€” ๐ŸŸฅ ยซadditionalContext ๊ฐ€ ๋–ด๋‹คยป ๋Š” ํŒŒ์„œ๊ฐ€ **์˜ณ์€ ํ† ํฐ**์„ ๋ฝ‘์•˜๋‹ค๋Š” ์ฆ๊ฑฐ๊ฐ€ ์•„๋‹ˆ๋‹ค(cross-family ๋ ˆ์ธ ์ง€์ ).
116
+ rm -f "$D3/.claude/.prior_art_prompted_"*
117
+ chk "E7 ์ถ”์ถœ๋œ ๋Œ€์ƒ์ด ์‹ค์ œ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๋Œ€์ƒ์ด๋‹ค (ํŒŒ์„œ๊ฐ€ ์—‰๋šฑํ•œ ํ† ํฐ์„ ๋ฝ‘์•„๋„ ํ†ต๊ณผํ•˜๋˜ ์ž๋ฆฌ)" \
118
+ "$(printf '%s' '{"session_id":"e7","tool_input":{"command":"cat > scripts/uniq_target_name.sh <<EOF\nx\nEOF"}}' | bash "$H" 2>/dev/null | grep -c 'uniq_target_name.sh')" 1
119
+ unset CLAUDE_PROJECT_DIR; rm -rf "$D3"
120
+
121
+ echo "โ”€โ”€ CONTROL: ๊ณ„๊ธฐ๊ฐ€ ์ฃฝ์œผ๋ฉด ์กฐ์šฉํžˆ ํ†ต๊ณผ (advisory ๋Š” ์„ธ์…˜์„ ๋ชป ์ฃฝ์ธ๋‹ค) โ”€โ”€"
122
+ chk "C1 ์ž…๋ ฅ์ด JSON ์ด ์•„๋‹ˆ๋ฉด ๋ฌด์Œ exit 0" \
123
+ "$(printf 'not json' | bash "$H" >/dev/null 2>&1; echo $?)" 0
124
+ chk "C2 file_path ์—†์œผ๋ฉด ๋ฌด์Œ" "$(printf '{"tool_input":{}}' | bash "$H" 2>/dev/null | wc -c | tr -d ' ')" 0
125
+
126
+ echo
127
+ if [ "$FAIL" -eq 0 ]; then echo "โ•โ•โ•โ• prior-art-prompt lanes: $PASS passed ยท 0 failed โ•โ•โ•โ•"; exit 0
128
+ else echo "๐ŸŸฅ prior-art-prompt lanes: $FAIL ์‹คํŒจ / $((PASS+FAIL))"; exit 1; fi