@chrono-meta/fh-gate 2.0.1 โ†’ 2.2.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 (37) hide show
  1. package/.claude/rules/fh_4axis_gate.md +77 -1
  2. package/.claude-plugin/marketplace.json +3 -3
  3. package/AGENTS.md +26 -3
  4. package/CLAUDE.md +240 -4
  5. package/knowledge/shared/harness-core/capability_composition_contract.md +68 -0
  6. package/knowledge/shared/harness-core/fh_three_layer_canon.md +38 -12
  7. package/knowledge/shared/harness-core/field_verdict_crossfamily_gate.md +115 -3
  8. package/knowledge/shared/harness-core/harness_incubator_doctrine.md +82 -0
  9. package/knowledge/shared/harness-core/harness_terminal_correlation_and_recommendations.md +85 -10
  10. package/knowledge/shared/harness-core/ship_readiness_gate.md +241 -1
  11. package/knowledge/shared/learnings/subagent_invocations_log.yaml +102 -0
  12. package/knowledge/shared/rules/sister_asset_protocol.md +12 -0
  13. package/package.json +7 -2
  14. package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
  15. package/plugins/fh-commons/agents/quench-challenger.md +6 -1
  16. package/plugins/fh-commons/skills/ko-tech-writer/fixtures/known_negative.md +19 -0
  17. package/plugins/fh-commons/skills/ko-tech-writer/fixtures/known_positive.md +20 -0
  18. package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
  19. package/plugins/fh-meta/CHANGELOG.md +88 -0
  20. package/plugins/fh-meta/skills/auto-decorrelation/SKILL.md +14 -2
  21. package/plugins/fh-meta/skills/steel-quench/SKILL_detail.md +13 -0
  22. package/plugins/fh-meta/skills/verify-bidirectional/SKILL.md +34 -1
  23. package/scripts/capability_registry_check.sh +75 -12
  24. package/scripts/chamber_run.sh +39 -5
  25. package/scripts/chamber_witness.sh +25 -0
  26. package/scripts/fh-gate.sh +16 -1
  27. package/scripts/ko_tech_writer_calibrate.py +127 -0
  28. package/scripts/package_coverage_check.sh +10 -0
  29. package/scripts/prepublish_scope_note.sh +139 -0
  30. package/scripts/publish_freshness_check.sh +21 -2
  31. package/scripts/selfcheck.sh +32 -6
  32. package/scripts/test_fh_gate_regressions.sh +21 -9
  33. package/scripts/test_ko_tech_writer_lanes.sh +61 -0
  34. package/scripts/test_marker_axes_run_lanes.sh +87 -3
  35. package/scripts/test_marker_crossfamily_lanes.sh +31 -1
  36. package/templates/.git-hooks/pre-commit +225 -10
  37. package/templates/subagent-tally-hook.json +2 -2
@@ -10,6 +10,94 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
10
10
 
11
11
  ## Plugin Level
12
12
 
13
+ ### [2.2.0] โ€” 2026-08-17
14
+
15
+ ๐ŸŸฅ **BREAKING (gate)**: chamber step 6 now reads `ACTUAL.md`, **not `BUDGET.md`**. An in-flight
16
+ chamber run whose actual cost was written into `BUDGET.md` will **block at step 6** until the value
17
+ moves to a new `ACTUAL.md` in the same workspace.
18
+ - **Remedy**: the runner prints the exact path when it blocks โ€” move the `ACTUAL:` line to
19
+ `tracks/_chamber/<slug>/ACTUAL.md`. `BUDGET.md` keeps `ESTIMATE:` only.
20
+ - **Why**: `BUDGET.md`'s pre-verdict hash IS the ordering witness (`ship_readiness_gate ยงโ‘ก P1`), and
21
+ step 6 was hard-blocking until that same file changed. So **every run that reached COMPLETE
22
+ necessarily mutated a witnessed artifact** and `verify` returned `TAMPERED` โ€” identity โ‘ก's only
23
+ promotion condition was unsatisfiable by construction, not by strictness. Measured on chamber run
24
+ #11, the first run ever taken through step 7. Two roles (immutable witness / post-verdict
25
+ calibration sink) had collided in one file; each was correct alone, so neither side's code showed
26
+ the conflict.
27
+
28
+ **Added**
29
+ - `scripts/ko_tech_writer_calibrate.py` + `scripts/test_ko_tech_writer_lanes.sh` + two known-pair
30
+ fixtures โ€” the discrimination of `ko-tech-writer` Step 2 (five translationese classes) and
31
+ Step 4-b (universal-claim candidates) is now **reproducible**: positive โ‰ฅ1 / negative 0 per class,
32
+ plus two META controls. Wired into `selfcheck.sh` and shipped in `files[]`.
33
+ ๐ŸŸฅ **What this does NOT prove**: "zero residue" in any real document. Discrimination and residue
34
+ are different propositions; the suite prints that warning itself.
35
+ - Chamber lane suite **12 โ†’ 33 lanes**, including the **runner ร— witness seam** that no test covered
36
+ (the runner's lanes excluded the witness by design; the witness self-test ran it standalone).
37
+
38
+ **Changed**
39
+ - `chamber_run.sh` now teaches the **two-commit discipline** where the actor reads it: gate hashes
40
+ and the verdict hash must land in **separate commits** (and, for a squash-merge repo, **separate
41
+ PRs**). Committing them together yields `UNORDERED` โ€” the runner previously advised the opposite.
42
+ - `chamber_witness.sh do_record` skips a byte-identical re-record of the same
43
+ `(run, artifact, sha)` triple. A **changed** artifact still appends โ€” that is the tamper evidence.
44
+
45
+ **Fixed**
46
+ - `harness_terminal_correlation_and_recommendations.md` Appendix: Step 2 / Step 4 rows move from
47
+ ๐ŸŸฅ `UNCALIBRATED` to ๐ŸŸก **partially resolved**, with the reproduction command recorded. The
48
+ original claims ("zero residue" in that document; Step 4's numeric extraction half) remain
49
+ **unverified and are labelled as such**.
50
+
51
+ **Known residual (not closed)**
52
+ - Identity โ‘ก stays **RC**. No run has yet been `WITNESSED` on `main`; the two-PR prescription is
53
+ reasoned from the verify logic, and its efficacy is provable only by the next EMIT run.
54
+ - `--delete-branch` still orphans ordering evidence โ€” warned in prose, not blocked.
55
+ - Of Step 2's five "machine-detectable" classes, only two ship an actual grep in `SKILL.md`; the
56
+ calibration surfaced that the doc over-claims. C4's pattern is a closed noun list with **low recall**.
57
+
58
+ ### [2.1.0] โ€” 2026-08-17
59
+
60
+ ๐ŸŸฅ **BREAKING (gate)**: `crossfamily: declined` in an Axes 2-3 marker now requires grounds naming a
61
+ record path that **resolves on disk**. Bare `declined`, and `declined` justified by author judgment,
62
+ are **blocked at commit**.
63
+ - **Remedy**: cite where the operator decision lives โ€” e.g.
64
+ `crossfamily: declined โ€” operator declined sidecars, per knowledge/shared/rules/operational_adaptation.md`
65
+ - **Or use the value that describes what actually happened**: if a panel was reachable and you chose
66
+ not to recruit it, that is `DEGRADED_PANEL_UNUSED`, not `declined`.
67
+ - **Why**: `declined` was the only value in the enum with no grounds requirement, so an author
68
+ judgment flowed into the nearest permissive token and passed clean. A cross-family review then
69
+ broke the first (vocabulary-grep) fix three ways โ€” self-validating on the value's own token,
70
+ vacuous keyword passes, and over-blocking genuine declinations phrased in natural prose โ€” so the
71
+ check asserts a **resolvable record** rather than words. It proves a cited record EXISTS, not that
72
+ it says what is claimed; that residual is the marker's own declared scope, stated in the code.
73
+
74
+ **Added**
75
+ - `standpoint:` gains **`tier1b`** (a STATIC read of a target repo โ€” executed nothing) plus a
76
+ decide-in-order procedure, after blind floor-tier sims graded pure cold-reads as `tier2` for three
77
+ rounds, defeating three separate rewordings via the enum's own internal logic.
78
+ - steel-quench Wave 1's sixth angle (**gate-locality**) gains the output-template row it never had โ€”
79
+ a mandatory angle that was structurally unreportable. Gate-locality failing its own check.
80
+ - `verify-bidirectional` category 5 โ€” **prescriptive doctrine statement**, the operator-correction
81
+ shape that fell between its doubt-shaped triggers and its "simple correction, no review" exception.
82
+ - Sister Asset Protocol gains an **active-adoption** condition โ€” all four prior conditions were
83
+ passive discovery, so installing an external framework and running it against our own asset
84
+ matched none of them.
85
+ - Resident doctrine: **Mechanization Boundary** (machinery at irreversible edges and channels;
86
+ judgment left to evolution) ยท **Local Execution First** (CI is a backstop, never the discovery
87
+ mechanism) ยท **Skeleton-not-Muscle** (a wiring change is done when the floor tier executes it) ยท
88
+ **Expedition** track ยท **this package's versioning policy** (major reserved for built-anew /
89
+ identity-established / capability-class change โ€” never for tightening an existing gate).
90
+
91
+ **Fixed**
92
+ - `fh-gate.sh` survives a missing or unreadable `package.json`; `selfcheck.sh` decides npm-surface
93
+ applicability at the call site.
94
+ - `capability_registry_check.sh` M3 basename-token matching (a path containing "claude" was silently
95
+ rejected) + a stdin channel for calibration arms.
96
+ - `subagent-tally-hook.json` root resolution is 3-tier, matching the convention the sibling
97
+ `SessionStart` hook already used.
98
+
99
+ ---
100
+
13
101
  ### [2.0.1] โ€” 2026-08-16
14
102
 
15
103
  - **feat(cadence)**: `harness-doctor` 30์ผ ์บ๋˜์Šค๊ฐ€ ์‚ฐ๋ฌธ ์ œ์•ˆ์—์„œ SessionStart ํ›…์œผ๋กœ ์Šน๊ฒฉ โ€”
@@ -264,8 +264,20 @@ enforceable layer) from the **declined** case above (chosen floor, first-class,
264
264
  **shared-body / cross-harness-boundary** change โ€” scoped by *effect* (alters another harness's
265
265
  behavior, gate outcome, or interaction contract), not merely by touching a synced file path โ€” also
266
266
  emit `standpoint:` alongside `crossfamily:` in the same marker โ€” recruiting family diversity here
267
- does not substitute for it. Values: `tier1` (content-only, the default) ยท `tier2(<harness>)`
268
- (peer-simulated โ€” ran the target's own repo, content only) ยท `tier2b(<harness>)` (same operator,
267
+ does not substitute for it. Values: `tier1` (content-only, the default) ยท **`tier1b(<harness>)`
268
+ (STATIC read of the target's own files โ€” executed nothing)** ยท `tier2(<harness>)`
269
+ (peer-simulated โ€” **EXECUTED CODE in** the target's own repo and observed the result.
270
+ ๐ŸŸฅ **Reading the target's real files, however cold, is `tier1b`, not this.** The discriminator is
271
+ mechanical: *name the command you ran and the output you saw*; cannot name one โ†’ `tier1b`, always.
272
+ This line said **"ran the target's own repo, content only"** until 2026-08-17 โ€” actively teaching
273
+ the opposite of the canon it summarizes. ๐ŸŸฅ **RETRACTED (2026-08-17)**: this passage used to add
274
+ that blind Sonnet sims *"STILL graded a pure cold-read `tier2`, both quoting this phrasing"* after
275
+ the other two copies were fixed. Those runs had **`tool_uses: 0`** โ€” nothing was read, so nothing
276
+ was measured, and the claim that the sims "reached for this copy" was itself unfounded
277
+ (`tracks/_meta/fh_completed_2026-08-16.md:690`; the live re-run inverted the grade at reps=1, below
278
+ bar). **The reason to fix this copy needs no sim**: a summary that states the opposite of its canon
279
+ teaches the opposite to whoever reads only the summary, and **two of three copies fixed is a fix
280
+ that does not exist** โ€” that is gate-locality, not a measurement. See `ยง7` for the canon) ยท `tier2b(<harness>)` (same operator,
269
281
  target's real runtime โ€” local wiring visible, but not an independent reviewer) ยท `tier3(<harness>)`
270
282
  (a *different* operator of the target harness ran it โ€” the only fully independent + local-wiring
271
283
  rung) ยท `not-applicable` (no target-harness standpoint exists โ€” most same-repo dispatches) ยท
@@ -60,9 +60,22 @@ Added Wave 1 attack angles: N items
60
60
  | Bus factor | S/A/B | [single-person dependency area] | โ—‹/โ–ณ/ร— |
61
61
  | Platform obsolescence | S/A/B | [vulnerability point] | โ—‹/โ–ณ/ร— |
62
62
  | Self-referential structure | S/A/B | [closed circuit detection result] | โ—‹/โ–ณ/ร— |
63
+ | Gate-locality | S/A/B | [any gate defined only where the enforcing actor never reads it] | โ—‹/โ–ณ/ร— |
63
64
 
64
65
  S-grade blockers: N / A-grade: N / B-grade: N
65
66
 
67
+ > ๐ŸŸฅ **The 6th row was missing until 2026-08-16, and its absence was itself a gate-locality defect.**
68
+ > `SKILL.md` Wave 1 has defined **six** angles since Gate-locality was added; this template shipped
69
+ > **five rows**, and `quench-challenger.md` still said "5 angles" and omitted it from the
70
+ > enumeration. So a mandatory angle had **no slot to be reported in** โ€” an agent filling this table
71
+ > correctly would report the other five and structurally never surface the sixth. That is the exact
72
+ > shape Gate-locality itself is the check for: *a requirement placed where the actor who must
73
+ > satisfy it does not read it.* Found by a sister-asset audit (an external adversarial framework's
74
+ > **typed** attack registry vs. this repo's seven overlapping prose lists) โ€” the fragmentation is
75
+ > what let the three copies drift apart silently. If you add a Wave 1 angle, it lands in **three**
76
+ > places or it does not land: `SKILL.md`'s table, this template, and `quench-challenger.md`'s
77
+ > role-separation paragraph.
78
+
66
79
  Optional numeric score (0.0โ€“1.0):
67
80
  overall_score: {score}
68
81
  [0.0โ€“0.3] S-grade present โ†’ immediate blocker, do not proceed
@@ -39,8 +39,41 @@ Also triggered in external user environments by these natural language phrases:
39
39
  | "what's your basis?", "why do you think that?" | Baseline grep trigger |
40
40
  | "check that one more time" | Self-validation request |
41
41
 
42
+ ### ๐ŸŸฅ 5. Prescriptive doctrine statement โ€” the category that fell through (added 2026-08-16)
43
+
44
+ Every trigger above is shaped as **doubt about a claim**. A large class of operator correction is not
45
+ doubt at all โ€” it is a **standing rule being handed over**, stated as fact or instruction:
46
+
47
+ | Shape | Real examples (2026-08-16 session) |
48
+ |---|---|
49
+ | *"X should also include Y"* | *"์ž…์žฅ๋ฆฌ๋ทฐ์—๋Š” ์ •์ ๋ฆฌ๋ทฐ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ **๋™์ ๋ฆฌ๋ทฐ๋„ ํฌํ•จ๋˜์–ด์•ผ ํ•  ๊ฑฐ์•ผ**"* |
50
+ | *"the real point of X is Z"* (redefining, not doubting) | *"๋ถ€์ŠคํŒ…๋ณด๋‹ค ์ธํ๋ฒ ์ดํ„ฐ์˜ ์žฅ์ ์€ ๊ทธ ๋ ˆํฌ ์ „์ฒด๋ฅผ ๊ฐ์‹ธ์„œ โ€ฆ**๋ชจ๋“  ๊ฒƒ์„ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ**์ด ์žˆ๋Š” ๊ฑฐ์•ผ"* |
51
+ | *"isn't this too late / wrong-ordered?"* (rhetorical, expects agreement) | *"์ด ์‹คํŒจ๊ฐ€ CI ํ™•์ธ ๋‹จ๊ณ„์—์„œ์•ผ ๋ฐœ๊ฒฌ๋˜๋Š” ๊ฑด ๋งค์šฐ **๋Šฆ์€ ๊ฒŒ ์•„๋‹๊นŒ**"* |
52
+ | *"from now on, do W at Z"* | *"์•ž์œผ๋กœ๋„ ๋งˆ๊ฐํ•  ๋•Œ ๊ทธ ๊ฐˆ๋ž˜๋กœ ์ด์–ด๊ฐˆ ์ˆ˜ ์žˆ๊ฒŒ **์•Œ์•„์„œ ์ •๋ฆฌํ•ด์ค˜**"* |
53
+
54
+ **Why these were missed 4 times out of 4** โ€” they sit in the blind spot between the triggers (which
55
+ expect a *challenge*) and the Exceptions below (which release a *simple correction* with "no
56
+ review"). A doctrine statement is neither: it does not dispute a claim, and it is not a one-off fix
57
+ to apply and forget. Treated as the exception, it gets a verbal acknowledgment and evaporates at
58
+ session end. **Measured**: four such statements in one session, all acknowledged in conversation,
59
+ **zero landed in any file** until a later review grepped for them and found nothing.
60
+
61
+ **The tell is grammatical, and it is language-independent** โ€” the listed phrases above are all
62
+ English interrogatives, which is why a Korean declarative (`~ํ•ด์•ผ ํ•  ๊ฑฐ์•ผ` ยท `~์ธ ๊ฑฐ์•ผ` ยท `~์•„๋‹๊นŒ`)
63
+ matched none of them. Do not fix this by appending more literals (Grep-Collision Treadmill, P10);
64
+ the discriminator is: **does this utterance describe how I should behave from now on, rather than
65
+ what is wrong with this one output?** If yes, it is this category regardless of language or phrasing.
66
+
67
+ **Required action** โ€” an acknowledgment is not compliance. The statement must land **in a file** in
68
+ the same session: canon (`CLAUDE.md` / `knowledge/`) if it governs future behavior, memory if it is
69
+ about this operator, a signal file if it needs a decision first. Then say WHERE it landed, so the
70
+ operator can see it did.
71
+
42
72
  **Exceptions** (this skill does NOT apply):
43
- - Simple user correction ("this is wrong, redo it") = direct negation โ†’ immediate correction (no review)
73
+ - Simple user correction ("this is wrong, redo it") = direct negation โ†’ immediate correction (no review).
74
+ โš ๏ธ **Not the same as category 5 above** โ€” "redo this" is scoped to one output; "from now on do X"
75
+ is a standing rule. When both readings fit, take it as category 5: over-landing costs a file edit,
76
+ under-landing loses the rule entirely.
44
77
  - This harness AI self-catch (no external counter-argument) = `fact-checker` rule (narrow 1 / broad N+1)
45
78
 
46
79
  ## Execution Steps
@@ -54,7 +54,7 @@ RC_OK=0; RC_REJECT=1; RC_HARNESS=10
54
54
  # `summary`/`tags` ๋Š” **์ถ”์ฒœ ์ „์šฉ**(2026-08-16, cluster-wizard). ํŒ์ •์—๋Š” ์•ˆ ์“ฐ์ด๊ณ 
55
55
  # `cluster_capability_scan.sh recommend` ์˜ ์–ดํœ˜ ๋งค์นญ์—๋งŒ ์“ฐ์ธ๋‹ค. ๋‹ซํžŒ ๋ชฉ๋ก์— ๋„ฃ๋Š” ์ด์œ ๋Š”
56
56
  # ์ด ๋ชฉ๋ก์˜ ๋ชฉ์ ์ด ยซ์˜คํƒ€ ์ถ• ๋ฌด์Œ ๋“œ๋กญ ๋ฐฉ์ง€ยป ์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค โ€” ์•ˆ ๋„ฃ์œผ๋ฉด ์ •๋‹นํ•œ ํ‚ค๊ฐ€ SCHEMA ๋กœ ๋ง‰ํžŒ๋‹ค.
57
- CLOSED_KEYS="id entry requires_cwd summary tags verdict_channel verdict_enum verdict_stdout_key upstream_argv echoes_upstream approval reversibility residency degrade tier_floor writes judge verdict_binding calibration_positive_args calibration_positive_expect calibration_negative_args calibration_negative_expect"
57
+ CLOSED_KEYS="id entry requires_cwd summary tags verdict_channel verdict_enum verdict_stdout_key upstream_argv echoes_upstream approval reversibility residency degrade tier_floor writes judge verdict_binding calibration_positive_args calibration_positive_expect calibration_negative_args calibration_negative_expect calibration_positive_stdin calibration_negative_stdin"
58
58
 
59
59
  # ใ€Œ์•ˆ ๋Œ์•˜๋‹คใ€๋ฅผ ๋œปํ•˜๋Š” ์ด๋ฆ„๋“ค โ€” ์ถ”๊ฐ€์กฐํ•ญ(ยงโ“‘.4 B1)์ด ์š”๊ตฌํ•˜๋Š” ๊ตฌ๋ถ„ํ•ญ
60
60
  DIDNOTRUN_NAMES="DID_NOT_RUN DIDNOTRUN NOT_RUN NO_TARGET SKIPPED UNMEASURED NOT_CONFIGURED HARNESS_ERROR"
@@ -70,6 +70,9 @@ _parse() {
70
70
  CAP_id=""; CAP_entry=""; CAP_requires_cwd=""; CAP_verdict_channel=""
71
71
  CAP_verdict_enum=""; CAP_verdict_stdout_key=""; CAP_judge=""; CAP_writes=""
72
72
  CAP_cal_pos_args=""; CAP_cal_pos_expect=""; CAP_cal_neg_args=""; CAP_cal_neg_expect=""
73
+ # ๋‹ค์ค‘ capfile ์‹คํ–‰์—์„œ ์•ž ํŒŒ์ผ์˜ ์„ ์–ธ์ด ๋’ค ํŒŒ์ผ๋กœ ์ƒˆ์ง€ ์•Š๊ฒŒ ํ•œ๋‹ค โ€” ์ดˆ๊ธฐํ™” ๋ˆ„๋ฝ์€
74
+ # "๋’ค ํŒŒ์ผ์ด ์„ ์–ธํ•˜์ง€ ์•Š์€ stdin ์œผ๋กœ ๋Œ์•˜๋‹ค" ๋ฅผ ๋งŒ๋“ค๊ณ , ๊ทธ๊ฑด ์กฐ์šฉํžˆ ํ†ต๊ณผํ•œ๋‹ค.
75
+ CAP_cal_pos_stdin=""; CAP_cal_neg_stdin=""
73
76
  UNKNOWN_KEYS=""; CAP_requires_cwd_was_self=0
74
77
  # ใ€Œ์„ ์–ธํ–ˆ๋Š”๋ฐ ๊ฐ’์ด ๋น„์—ˆ๋‹คใ€์™€ ใ€Œ์„ ์–ธ ์ž์ฒด๊ฐ€ ์—†๋‹คใ€๋Š” ๋‹ค๋ฅธ ์‚ฌ์‹ค์ด๋‹ค โ€” ๊ด€์ธก ๋ฒ”์œ„ ๋ธ”๋ก์ด
75
78
  # ๋‘˜์„ ๊ตฌ๋ถ„ํ•ด ์ ์œผ๋ ค๋ฉด ํŒŒ์„œ๊ฐ€ ๊ทธ ๊ตฌ๋ถ„์„ ๋ณด์กดํ•ด์•ผ ํ•œ๋‹ค(๋นˆ ๋ฌธ์ž์—ด ํ•˜๋‚˜๋กœ๋Š” ๋ชป ๋‚˜๋ˆˆ๋‹ค).
@@ -93,6 +96,8 @@ _parse() {
93
96
  calibration_positive_args) CAP_cal_pos_args="$val"; CAP_cal_pos_args_seen=1 ;;
94
97
  calibration_positive_expect) CAP_cal_pos_expect="$val" ;;
95
98
  calibration_negative_args) CAP_cal_neg_args="$val"; CAP_cal_neg_args_seen=1 ;;
99
+ calibration_positive_stdin) CAP_cal_pos_stdin="$val" ;;
100
+ calibration_negative_stdin) CAP_cal_neg_stdin="$val" ;;
96
101
  calibration_negative_expect) CAP_cal_neg_expect="$val" ;;
97
102
  esac
98
103
  done < "$1"
@@ -119,10 +124,43 @@ _validate_arm_args() { # $1=args โ†’ ์…ธ ๋ฉ”ํƒ€๋ฌธ์ž/์ƒ์œ„๊ฒฝ๋กœ ํƒˆ์ถœ์„
119
124
  return 0
120
125
  }
121
126
 
122
- _run_arm() { # $1=extra args โ†’ ARM_RC / ARM_NAME. ํŒŒ์ดํ”„๋กœ ์ฝ์ง€ ์•Š๋Š”๋‹ค(PIPE-VERDICT).
127
+ # stdin ํŒŒ์ผ ๊ฒ€์ฆ โ€” args ์™€ ๊ฐ™์€ ๊ทœ์œจ(๋ฉ”ํƒ€๋ฌธ์žยท์ƒ์œ„๊ฒฝ๋กœ ํƒˆ์ถœ ๊ฑฐ๋ถ€) + ์‹ค์žฌ ํ™•์ธ.
128
+ _validate_arm_stdin() { # $1=์„ ์–ธ๋œ ๊ฒฝ๋กœ(๋นˆ ๊ฐ’ ํ—ˆ์šฉ)
129
+ [ -n "$1" ] || return 0
130
+ case "$1" in
131
+ *'|'*|*';'*|*'&'*|*'>'*|*'<'*|*'`'*|*'$('*|*$'\n'*)
132
+ _fail "M4" "์บ˜๋ฆฌ๋ธŒ๋ ˆ์ด์…˜ stdin ๊ฒฝ๋กœ์— ์…ธ ๋ฉ”ํƒ€๋ฌธ์ž๊ฐ€ ์žˆ๋‹ค: $1"; return 1 ;;
133
+ *'../'*|/*)
134
+ _fail "M4" "์บ˜๋ฆฌ๋ธŒ๋ ˆ์ด์…˜ stdin ๊ฒฝ๋กœ๊ฐ€ ๋ ˆํฌ ๋ฐ–์„ ๊ฐ€๋ฆฌํ‚จ๋‹ค(์ƒ๋Œ€๊ฒฝ๋กœ๋งŒ ํ—ˆ์šฉ): $1"; return 1 ;;
135
+ esac
136
+ [ -f "$CAP_requires_cwd/$1" ] || {
137
+ _fail "M4" "์บ˜๋ฆฌ๋ธŒ๋ ˆ์ด์…˜ stdin ํŒŒ์ผ์ด ์—†๋‹ค: $1 (์„ ์–ธ์€ ์‹ค์žฌํ•˜๋Š” ํ”ฝ์Šค์ฒ˜๋ฅผ ๊ฐ€๋ฆฌ์ผœ์•ผ ํ•œ๋‹ค)"; return 1; }
138
+ return 0
139
+ }
140
+
141
+ _run_arm() { # $1=extra args ยท $2=stdin ํŒŒ์ผ(์„ ํƒ) โ†’ ARM_RC / ARM_NAME. ํŒŒ์ดํ”„๋กœ ์ฝ์ง€ ์•Š๋Š”๋‹ค(PIPE-VERDICT).
142
+ # โ”€โ”€ STDIN ์€ ํ•ญ์ƒ ์ •์˜๋œ๋‹ค. ์ƒ์†ํ•˜์ง€ ์•Š๋Š”๋‹ค. โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
143
+ # ์‹ค์ธก 2026-08-16 (์ž๊ธฐ์„ ์–ธ ์บ ํŽ˜์ธ, the-bible arm): ์ด ํ•จ์ˆ˜๋Š” `> /dev/null 2>&1` ๋กœ
144
+ # stdout/stderr ๋งŒ ๋ง‰๊ณ  **stdin ์€ ๊ฒ€์‚ฌ๊ธฐ์˜ ๊ฒƒ์„ ๋ฌผ๋ ค๋ฐ›์•˜๋‹ค.** ๊ฒฐ๊ณผ๊ฐ€ ๋‘ ๊ฐ€์ง€๋กœ ๋‚˜๋นด๋‹ค.
145
+ # โ“ **๋น„๊ฒฐ์ •**: ๊ฐ™์€ ์„ ์–ธ์ด ํ„ฐ๋ฏธ๋„ยทํŒŒ์ดํ”„ยทCI ์–ด๋””์„œ ๋Œ๋ฆฌ๋А๋ƒ์— ๋”ฐ๋ผ arm ์˜ ์ž…๋ ฅ์ด ๋‹ฌ๋ผ์ง„๋‹ค.
146
+ # ํŒ์ • ๊ณ„๊ธฐ ์•ˆ์˜ ๋น„๊ฒฐ์ •์€ ๊ทธ ์ž์ฒด๋กœ ๊ฒฐํ•จ์ด๋‹ค โ€” ์ดˆ๋ก์˜ ์ด์œ ๊ฐ€ ์‹คํ–‰ ํ™˜๊ฒฝ์— ๋‹ฌ๋ฆฌ๊ฒŒ ๋œ๋‹ค.
147
+ # โ“‘ **ํ‘œํ˜„ ๋ถˆ๊ฐ€**: ๋Œ€์ƒ์„ stdin ์œผ๋กœ ๋ฐ›๋Š” ์ง„์ž…์ (JSON ๋ธŒ๋ฆฌ์ง€ ๋“ฑ)์€ ์–‘ยท์Œ arm ์ด **๋‘˜ ๋‹ค ๋นˆ
148
+ # ์ž…๋ ฅ**์œผ๋กœ ๋Œ์•„ ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ๋‚ด๊ณ , ํŒ๋ณ„ ์‹คํŒจ๋กœ REJECT ๋œ๋‹ค. ๋Šฅ๋ ฅ์ด ์—†์–ด์„œ๊ฐ€ ์•„๋‹ˆ๋ผ
149
+ # **์Šคํ‚ค๋งˆ์— ์ฑ„๋„์ด ์—†์–ด์„œ** ๋ง‰ํžŒ ๊ฒƒ์ด๋‹ค. ์‹ค์ œ๋กœ 4๋ฐฉํ–ฅ์œผ๋กœ ๊ฐˆ๋ฆฌ๋Š” ๊ฒŒ์ดํŠธ๊ฐ€ ๊ทธ๋ ‡๊ฒŒ ๋ง‰ํ˜”๋‹ค.
150
+ # โ‡’ ์„ ์–ธ๋œ ํŒŒ์ผ์ด ์žˆ์œผ๋ฉด ๊ทธ๊ฑธ ๋จน์ด๊ณ , ์—†์œผ๋ฉด **๋ช…์‹œ์ ์œผ๋กœ /dev/null** ์„ ๋จน์ธ๋‹ค. ํ›„์ž๊ฐ€
151
+ # ์ค‘์š”ํ•˜๋‹ค โ€” "์„ ์–ธ ์•ˆ ํ–ˆ์œผ๋‹ˆ ์ƒ์†"์ด ์•„๋‹ˆ๋ผ "์„ ์–ธ ์•ˆ ํ–ˆ์œผ๋ฉด ๋นˆ ์ž…๋ ฅ"์ด๋ผ๊ณ  **์ •ํ•ด์•ผ** โ“ ๊ฐ€ ๋‹ซํžŒ๋‹ค.
152
+ # ๐ŸŸฅ `set --` ๋Š” ์œ„์น˜ ์ธ์ž๋ฅผ **๋ฎ์–ด์“ด๋‹ค** โ€” ๊ทธ ๋’ค์˜ `$2` ๋Š” stdin ํŒŒ์ผ์ด ์•„๋‹ˆ๋ผ entry ์˜ ๋‘ ๋ฒˆ์งธ
153
+ # ํ† ํฐ์ด๋‹ค. ์ดˆํŒ์ด ๊ทธ๊ฑธ ๋ฐŸ์•˜๊ณ , ๊ทธ ๊ฒฐ๊ณผ ๋‘ arm ์ด **์ง„์ž…์  ์Šคํฌ๋ฆฝํŠธ ์ž์‹ ์„ stdin ์œผ๋กœ ๋จน์—ˆ๋‹ค.**
154
+ # ๋ฐœ๊ฒฌ ๊ฒฝ์œ„๊ฐ€ ์ด ์ˆ˜์ •์˜ ์š”์ ์ด๋‹ค: ๊ทธ๋•Œ **์Œ์„ฑ arm ๋งŒ ๋นจ๊ฐœ์กŒ๊ณ  ์–‘์„ฑ arm ์€ ํ†ต๊ณผํ–ˆ๋‹ค** โ€” ์Šคํฌ๋ฆฝํŠธ
155
+ # ์†Œ์Šค์— ๋งˆ์นจ ์–‘์„ฑ ํ”ฝ์Šค์ฒ˜์™€ ๊ฐ™์€ ํ† ํฐ์ด ๋“ค์–ด ์žˆ์–ด์„œ๋‹ค. ์ฆ‰ **๋‘˜ ๋‹ค ํ‹€๋ฆฐ ์ž…๋ ฅ์„ ๋จน์—ˆ๋Š”๋ฐ ํ•˜๋‚˜๊ฐ€
156
+ # ์šฐ์—ฐํžˆ ๊ธฐ๋Œ€๊ฐ’๊ณผ ๋งž์•˜๋‹ค.** ํ•œ์ชฝ๋งŒ ๋ณด๊ณ  ์žˆ์—ˆ์œผ๋ฉด ยซ์–‘์„ฑ์€ ๋˜๋Š”๋ฐ ์Œ์„ฑ์ด ์ด์ƒํ•˜๋‹คยป๋กœ ์˜ค์ง„ํ–ˆ๋‹ค.
157
+ # โ‡’ set -- ์•ž์—์„œ ์ด๋ฆ„ ์žˆ๋Š” ๋ณ€์ˆ˜๋กœ ์žก๋Š”๋‹ค.
158
+ local _stdin="${2:-}"
123
159
  ( cd "$CAP_requires_cwd" 2>/dev/null || exit 127
124
160
  # shellcheck disable=SC2086 # argv ํ† ํฐ ๋ถ„๋ฆฌ๋Š” ์˜๋„ (noglob ๋กœ ํ™•์žฅ์€ ๋ง‰ํ˜€ ์žˆ๋‹ค)
125
- set -- $CAP_entry $1; "$@" ) > /dev/null 2>&1
161
+ set -- $CAP_entry $1
162
+ if [ -n "$_stdin" ]; then "$@" < "$_stdin"; else "$@" < /dev/null; fi
163
+ ) > /dev/null 2>&1
126
164
  ARM_RC=$?
127
165
  ARM_NAME="$(_enum_name_of "$ARM_RC" "$CAP_verdict_enum")"
128
166
  }
@@ -256,11 +294,35 @@ _check_one() {
256
294
  # โ”€โ”€ M3 ๋ชจ๋ธ ๋…๋ฆฝ์„ฑ (์„ ์–ธ ๊ฒ€์‚ฌ + ๋ช…๋ฐฑํ•œ ๋ชจ์ˆœ๋งŒ) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
257
295
  case "$CAP_judge" in
258
296
  mechanical)
259
- case "$CAP_entry" in
260
- *claude*|*codex*|*gemini*|*copilot*|*ollama*|*llm*)
261
- _fail "M3" "judge: mechanical ์„ ์–ธ์ธ๋ฐ entry ๊ฐ€ ๋ชจ๋ธ CLI ๋ฅผ ๋ถ€๋ฅธ๋‹ค: $CAP_entry" ;;
262
- *) _ok "M3" "judge: mechanical (๋ชจ๋ธ ๋ฏธ๊ฐœ์ž… ์„ ์–ธ)" ;;
263
- esac ;;
297
+ # ํ† ํฐ์˜ BASENAME ์œผ๋กœ ๋ณธ๋‹ค โ€” ๋ฌธ์ž์—ด ์ „์ฒด ๋ถ€๋ถ„๋งค์นญ์ด ์•„๋‹ˆ๋‹ค.
298
+ #
299
+ # ์‹ค์ธก 2026-08-16 (์ž๊ธฐ์„ ์–ธ ์บ ํŽ˜์ธ, qasp-dev arm ์ด ์ง€๋ชฉ): ์ด ๊ฒ€์‚ฌ๋Š” `*claude*` ์ฒ˜๋Ÿผ
300
+ # entry **๋ฌธ์ž์—ด ์ „์ฒด**์— ๋ถ€๋ถ„๋งค์นญํ–ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์ง„์ž…์ ์ด ๋ชจ๋ธ์„ ์ „ํ˜€ ์•ˆ ๋ถ€๋ฅด๋Š”๋ฐ๋„
301
+ # **๊ฒฝ๋กœ์— ๊ทธ ๋‹จ์–ด๊ฐ€ ์žˆ๋‹ค๋Š” ์ด์œ ๋งŒ์œผ๋กœ** REJECT ๋‚ฌ๋‹ค โ€” ์‹ค์ œ ์‚ฌ๋ก€๋Š” ์Šคํฌ๋ž˜์น˜ํŒจ๋“œ ๊ฒฝ๋กœ
302
+ # `/private/tmp/claude-501/...` ์˜€๊ณ , ๊ฒฝ๋กœ๋งŒ ๋ฐ”๊พธ๋‹ˆ ๊ฐ™์€ ์„ ์–ธ์ด PASS ํ–ˆ๋‹ค.
303
+ # ๊ตฌ์กฐ์ ์œผ๋กœ ๋” ๋‚˜์œ ๊ฒฝ์šฐ๊ฐ€ ๋‘˜ ์žˆ๋‹ค: โ“ ์ง„์ž…์ ์ด `.claude/` ์•„๋ž˜ ์‚ฌ๋Š” ํ•˜๋„ค์Šค๋Š” **์ „๋ถ€**
304
+ # ๋ง‰ํžŒ๋‹ค(FH ์ž๊ธฐ ์„ ์–ธ์ด `.claude/capabilities/` ์— ์‚ฌ๋Š” ๊ฑธ ์ƒ๊ฐํ•˜๋ฉด ๋‚จ ์–˜๊ธฐ๊ฐ€ ์•„๋‹ˆ๋‹ค)
305
+ # โ“‘ `scripts/claude_md_lint.sh` ์ฒ˜๋Ÿผ ์ด๋ฆ„์— ๊ทธ ๋‹จ์–ด๊ฐ€ ๋“  ์ˆœ์ˆ˜ ๊ธฐ๊ณ„ ์Šคํฌ๋ฆฝํŠธ๋„ ๋ง‰ํžŒ๋‹ค.
306
+ #
307
+ # ๊ณผ์ฐจ๋‹จ์€ ยซ์•ˆ์ „ํ•œ ๋ฐฉํ–ฅยป์ด ์•„๋‹ˆ๋‹ค โ€” ์ •๋ณธ์ด ๋ช…์‹œํ•˜๋“ฏ override ๋ฅผ ์Šต๊ด€ํ™”์‹œ์ผœ ๊ฐ™์€ ํ›…์˜
308
+ # ๋‹ค๋ฅธ ๊ฒŒ์ดํŠธ๊นŒ์ง€ ๋ฌด์žฅํ•ด์ œ์‹œํ‚จ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด ์˜คํƒ์€ **๋ฌด์Œ**์ด์—ˆ๋‹ค: ๊ฑฐ๋ถ€ ์‚ฌ์œ ๊ฐ€
309
+ # "๋ชจ๋ธ CLI ๋ฅผ ๋ถ€๋ฅธ๋‹ค" ๋กœ ์ฐํžˆ๋ฏ€๋กœ, ์ฝ๋Š” ์‚ฌ๋žŒ์€ ์ž๊ธฐ ์ง„์ž…์ ์„ ์˜์‹ฌํ•˜์ง€ ๊ฒฝ๋กœ๋ฅผ ์˜์‹ฌํ•˜์ง€ ์•Š๋Š”๋‹ค.
310
+ #
311
+ # ์ด ๊ฒ€์‚ฌ๊ฐ€ ์‹ค์ œ๋กœ ๋‹ตํ•  ์ˆ˜ ์žˆ๋Š” ์งˆ๋ฌธ์€ ยซentry ์ค„์ด ๋ชจ๋ธ CLI ๋ฅผ **์ง์ ‘ ์ด๋ฆ„์œผ๋กœ** ๋ถ€๋ฅด๋Š”๊ฐ€ยป
312
+ # ๋ฟ์ด๋‹ค. ๋ž˜ํผ ์•ˆ์—์„œ ๋ถ€๋ฅด๋Š” ๊ฑด ์„ ์–ธ ์ธต์—์„œ ์›๋ž˜ ๋ชป ๋ณธ๋‹ค(M6 ํšจ๊ณผ ํ”„๋กœ๋ธŒ์˜ ๋ชซ).
313
+ # ๊ทธ๋Ÿฌ๋‹ˆ ๊ฐ ํ† ํฐ์˜ basename ์„ ๋ณด๊ณ , ํ™•์žฅ์ž๋ฅผ ๋–ผ๊ณ , **์ •ํ™•ํžˆ ๊ทธ ์ด๋ฆ„์ผ ๋•Œ๋งŒ** ๋ง‰๋Š”๋‹ค.
314
+ _m3_bad=""
315
+ for _tok in $CAP_entry; do
316
+ _base="${_tok##*/}"; _base="${_base%.sh}"; _base="${_base%.js}"; _base="${_base%.py}"
317
+ case "$_base" in
318
+ claude|codex|gemini|copilot|ollama|llm) _m3_bad="$_base" ;;
319
+ esac
320
+ done
321
+ if [ -n "$_m3_bad" ]; then
322
+ _fail "M3" "judge: mechanical ์„ ์–ธ์ธ๋ฐ entry ๊ฐ€ ๋ชจ๋ธ CLI ๋ฅผ ์ง์ ‘ ๋ถ€๋ฅธ๋‹ค: $_m3_bad ($CAP_entry)"
323
+ else
324
+ _ok "M3" "judge: mechanical (entry ํ† ํฐ์— ๋ชจ๋ธ CLI ์—†์Œ โ€” basename ๊ธฐ์ค€)"
325
+ fi ;;
264
326
  model) _ok "M3" "judge: model โ€” ์„ ์–ธ๋จ(ํ•ฉ๋ฒ•). ์กฐํ•ฉ์—์„œ ์ด PASS ๋Š” NON_CLEARING ์ด๋‹ค" ;;
265
327
  '') _fail "M3" "judge ์ถ• ๋ฏธ์„ ์–ธ โ€” ๋ชจ๋ธ ๊ฐœ์ž… ์—ฌ๋ถ€๊ฐ€ ๋ถˆ๋ช…์ด๋ฉด ์กฐํ•ฉ์ด ๊ณ„์‚ฐ๋  ์ˆ˜ ์—†๋‹ค" ;;
266
328
  *) _fail "M3" "judge ๊ฐ’์ด {mechanical|model} ๋ฐ–: '$CAP_judge'" ;;
@@ -314,15 +376,16 @@ _check_one() {
314
376
  elif [ "$FILE_FAILED" -eq 1 ] && [ -z "${CRC_FORCE_M4:-}" ]; then
315
377
  printf ' โญ M4 โ€” ์•ž์„  ์ถ•์ด ์‹คํŒจํ•ด ์‹คํ–‰ ์ƒ๋žต(SKIPPED, PASS ์•„๋‹˜)\n'
316
378
  OBS_WHY="์•ž์„  ์ถ• ์‹คํŒจ๋กœ M4 ์ƒ๋žต"
317
- elif ! _validate_arm_args "$CAP_cal_pos_args" || ! _validate_arm_args "$CAP_cal_neg_args"; then
379
+ elif ! _validate_arm_args "$CAP_cal_pos_args" || ! _validate_arm_args "$CAP_cal_neg_args" \
380
+ || ! _validate_arm_stdin "${CAP_cal_pos_stdin:-}" || ! _validate_arm_stdin "${CAP_cal_neg_stdin:-}"; then
318
381
  printf ' โญ M4 โ€” args ๊ฒ€๋ฌธ ์‹คํŒจ๋กœ arm ์„ ์‹คํ–‰ํ•˜์ง€ ์•Š์•˜๋‹ค(SKIPPED, PASS ์•„๋‹˜)\n'
319
382
  OBS_WHY="args ๊ฒ€๋ฌธ ์‹คํŒจ"
320
383
  else
321
384
  local pos_name neg_name pos_rc neg_rc pos_name2 pos_rc2
322
- _run_arm "$CAP_cal_pos_args"; pos_rc="$ARM_RC"; pos_name="$ARM_NAME"
385
+ _run_arm "$CAP_cal_pos_args" "${CAP_cal_pos_stdin:-}"; pos_rc="$ARM_RC"; pos_name="$ARM_NAME"
323
386
  [ "$pos_rc" = "127" ] && _fail "M4" "requires_cwd ๋กœ ์ง„์ž… ์‹คํŒจ โ€” arm ์„ ๋Œ๋ฆด ์ˆ˜ ์—†๋‹ค"
324
- _run_arm "$CAP_cal_pos_args"; pos_name2="$ARM_NAME"; pos_rc2="$ARM_RC" # reps=2 (M3 ๋ถ€๋ถ„ ๋ฐฉ์–ด)
325
- _run_arm "$CAP_cal_neg_args"; neg_rc="$ARM_RC"; neg_name="$ARM_NAME"
387
+ _run_arm "$CAP_cal_pos_args" "${CAP_cal_pos_stdin:-}"; pos_name2="$ARM_NAME"; pos_rc2="$ARM_RC" # reps=2 (M3 ๋ถ€๋ถ„ ๋ฐฉ์–ด)
388
+ _run_arm "$CAP_cal_neg_args" "${CAP_cal_neg_stdin:-}"; neg_rc="$ARM_RC"; neg_name="$ARM_NAME"
326
389
  # ๊ด€์ธก ์›์žฅ์— ๋‚จ๊ธด๋‹ค โ€” ํŒ์ •๊ณผ ๋ฌด๊ด€ํ•˜๊ฒŒ, ์ด ์‹คํ–‰์ด **์‹ค์ œ๋กœ ๋ฐŸ์€ exit** ๋งŒ ์ ๋Š”๋‹ค.
327
390
  OBS_STATE=run; OBS_POS_RC="$pos_rc"; OBS_POS_RC2="$pos_rc2"; OBS_NEG_RC="$neg_rc"
328
391
  OBS_POS_NAME="$pos_name"; OBS_NEG_NAME="$neg_name"
@@ -56,7 +56,15 @@ _witness_record() { # $1 = artifact path
56
56
  return 0
57
57
  fi
58
58
  if bash "$_WITNESS" record "$SLUG" "$1" >/dev/null 2>&1; then
59
- echo " โ†ณ ์ˆœ์„œ ์ฆ์ธ: $(basename "$1") ํ•ด์‹œ ๊ธฐ๋ก๋จ โ€” **์ปค๋ฐ‹ํ•ด์•ผ** ์ฆ์ธ์ด ๋œ๋‹ค"
59
+ # ๐ŸŸฅ ยซ์ปค๋ฐ‹ํ•ด์•ผ ๋œ๋‹คยป ๋กœ๋งŒ ์ ์œผ๋ฉด **ํ‹€๋ฆฐ ์ฒ˜๋ฐฉ์„ ๊ฐ€๋ฅด์นœ๋‹ค**(2026-08-17 ๋Ÿฐ #12 ์‹ค์ธก:
60
+ # ๋„ค ํ•ด์‹œ๋ฅผ ํ•œ ์ปค๋ฐ‹์— ๋ฐฐ์น˜ํ–ˆ๋”๋‹ˆ ๋ธŒ๋žœ์น˜์—์„œ๋ถ€ํ„ฐ UNORDERED). verify ๋Š”
61
+ # `[ verdict_ts -le pre_max_ts ]` ๋กœ ํŒ์ •ํ•˜๋ฏ€๋กœ **๊ฐ™์€ ์ปค๋ฐ‹ = ๊ฐ™์€ ์ดˆ = ์‹คํŒจ**๋‹ค.
62
+ if [ "$(basename "$1")" = "EMISSION_VERDICT.md" ]; then
63
+ echo " โ†ณ ์ˆœ์„œ ์ฆ์ธ: EMISSION_VERDICT.md ํ•ด์‹œ ๊ธฐ๋ก๋จ"
64
+ echo " ๐ŸŸฅ ์ด ํ•ด์‹œ๋Š” **๊ฒŒ์ดํŠธ ํ•ด์‹œ๋“ค๊ณผ ๋‹ค๋ฅธ ์ปค๋ฐ‹**์œผ๋กœ ์˜ฌ๋ ค์•ผ ํ•œ๋‹ค. ๊ฐ™์ด ์ปค๋ฐ‹ํ•˜๋ฉด UNORDERED ๋‹ค."
65
+ else
66
+ echo " โ†ณ ์ˆœ์„œ ์ฆ์ธ: $(basename "$1") ํ•ด์‹œ ๊ธฐ๋ก๋จ โ€” **verdict ๋ณด๋‹ค ๋จผ์ €, ๋ณ„๋„ ์ปค๋ฐ‹์œผ๋กœ** ์˜ฌ๋ ค๋ผ"
67
+ fi
60
68
  else
61
69
  echo " โš ๏ธ ์ˆœ์„œ ์ฆ์ธ ๊ธฐ๋ก ์‹คํŒจ: $(basename "$1") (์ฆ์ธ ์—†์Œ โ€” ๋ฏธ์ธก์ •์ด์ง€ 0์ด ์•„๋‹ˆ๋‹ค)"
62
70
  fi
@@ -125,8 +133,11 @@ if [ ! -f "$WS/BUDGET.md" ]; then
125
133
 
126
134
  # Route through goal-quench's budget gate for an expensive run, then record here.
127
135
  # Demo-scale runs may self-cap โ€” but an ESTIMATE line is mandatory (this is the entry cap).
136
+ #
137
+ # ๐ŸŸฅ ACTUAL ์€ ์ด ํŒŒ์ผ์— ์ ์ง€ ๋งˆ๋ผ โ€” step 6 ์ด ๋ณ„๋„ ACTUAL.md ๋ฅผ ์“ด๋‹ค.
138
+ # ์ด์œ : ์ด ํŒŒ์ผ์˜ ํŒ์ •-์ „ ํ•ด์‹œ๊ฐ€ ์ˆœ์„œ ์ฆ์ธ(ship_readiness_gate ยงโ‘ก P1)์ด๋‹ค. ์‚ฌํ›„์—
139
+ # ์ด ํŒŒ์ผ์„ ๊ณ ์น˜๋ฉด ์ฆ์ธ์ด TAMPERED ๋กœ ์ฃฝ๋Š”๋‹ค (2026-08-17 ๋Ÿฐ #11 ์‹ค์ธก).
128
140
  ESTIMATE: <e.g. ~3 persona dispatches, demo-scale, self-capped | or a token budget>
129
- ACTUAL: <filled at step 6>
130
141
  EOF
131
142
  echo " โ›” step 3 BLOCKED: record an ESTIMATE in $WS/BUDGET.md (budget-entry gate G2), then re-run."; exit 1
132
143
  fi
@@ -194,7 +205,12 @@ if [ -f "$_WITNESS" ]; then
194
205
  echo " โœ… ์ด EMIT ์€ ์ˆœ์„œ ์ฆ์ธ์„ ๊ฐ€์ง„๋‹ค โ€” identity โ‘ก ์Šน๊ฒฉ ๊ทผ๊ฑฐ๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ"
195
206
  else
196
207
  echo " ๐ŸŸฅ ์ด EMIT ์—๋Š” ์ˆœ์„œ ์ฆ์ธ์ด ์—†๋‹ค(rc=${_wrc:-?}) โ€” identity โ‘ก ์Šน๊ฒฉ ๊ทผ๊ฑฐ๋กœ ์“ฐ์ง€ ๋งˆ๋ผ."
197
- echo " (์›์žฅ ํ•ด์‹œ๋ฅผ ์ปค๋ฐ‹ํ•œ ๋’ค ์žฌ์‹คํ–‰ํ•˜๋ฉด ์ฆ์ธ์ด ์„ฑ๋ฆฝํ•œ๋‹ค)"
208
+ echo " ์ฒ˜๋ฐฉ โ€” ์ปค๋ฐ‹์„ **ํ•ฉ์น˜์ง€ ๋งˆ๋ผ**. ์„ธ ๊ฒฝ๋กœ๊ฐ€ ๊ฐ๊ฐ ์ฆ์ธ์„ ์ฃฝ์ธ๋‹ค(2026-08-17 ์‹ค์ธก):"
209
+ echo " โ‘  ํ•œ ์ปค๋ฐ‹์— ๋ฐฐ์น˜ โ†’ ๊ฐ™์€ ์ดˆ๋ผ UNORDERED (๋Ÿฐ #12 ์—์„œ ์žฌํ˜„)"
210
+ echo " โ‘ก --squash ๋จธ์ง€ โ†’ ๊ฒŒ์ดํŠธ+verdict ๊ฐ€ main ์—์„œ ํ•œ ์ปค๋ฐ‹์œผ๋กœ ์ ‘ํžŒ๋‹ค (๋Ÿฐ #11)"
211
+ echo " โ‘ข --delete-branch โ†’ ์ˆœ์„œ๋ฅผ ๋‹ด์€ ์ปค๋ฐ‹์ด ๋„๋‹ฌ ๋ถˆ๊ฐ€๊ฐ€ ๋œ๋‹ค"
212
+ echo " ์˜ฌ๋ฐ”๋ฅธ ํ˜•ํƒœ: ๊ฒŒ์ดํŠธ ํ•ด์‹œ ์ปค๋ฐ‹ โ†’ (๋ณ„๋„) verdict ํ•ด์‹œ ์ปค๋ฐ‹, **๋‘ PR ๋กœ ๋ถ„๋ฆฌํ•ด ๊ฐ๊ฐ ๋จธ์ง€**."
213
+ echo " ๊ทธ๋Ÿฌ๋ฉด squash ํ•ด๋„ main ์— 2์ปค๋ฐ‹์ด ๋‚จ์•„ ์ˆœ์„œ๊ฐ€ ๋ณด์กด๋œ๋‹ค."
198
214
  # โ˜… ์ถœ๋ ฅ๋งŒ ํ•˜๊ณ  exit 0 ์œผ๋กœ ๋๋‚ด๋ฉด, stdout ์„ ์•ˆ ์ฝ๋Š” ํ˜ธ์ถœ์ž์—๊ฒŒ๋Š” ์ฆ์ธ ์—†๋Š” EMIT ๋„
199
215
  # **์„ฑ๊ณต**์ด๋‹ค(cross-family F8). EMIT ์€ ์Šน๊ฒฉ ์ฃผ์žฅ์ด๋ฏ€๋กœ ๊ธฐ๊ณ„์ ์œผ๋กœ ๊ตฌ๋ถ„๋˜์–ด์•ผ ํ•œ๋‹ค.
200
216
  WITNESS_EMIT_FAIL=1
@@ -203,8 +219,26 @@ if [ -f "$_WITNESS" ]; then
203
219
  fi
204
220
 
205
221
  # STEP 6 โ€” actual cost / carry-forward record.
206
- if grep -qE '^ACTUAL:[[:space:]]*<' "$WS/BUDGET.md" || ! grep -qE '^ACTUAL:[[:space:]]*\S' "$WS/BUDGET.md"; then
207
- echo " โ›” step 6 BLOCKED: record ACTUAL cost in $WS/BUDGET.md (actual-vs-estimate calibration)."
222
+ # ๐ŸŸฅ ๋ณ„๋„ ํŒŒ์ผ์ด๋‹ค. BUDGET.md ๊ฐ€ ์•„๋‹ˆ๋‹ค โ€” ๊ทธ๋ฆฌ๊ณ  ๊ทธ ๋ถ„๋ฆฌ๊ฐ€ ์ด ๊ฒŒ์ดํŠธ์˜ ํ•ต์‹ฌ์ด๋‹ค.
223
+ # 2026-08-17 ๋Ÿฐ #11(์ฒซ ํ˜•์‹ ์™„์ฃผ)์ด ์‹ค์ธกํ•œ ๊ฒฐํ•จ: step 3 ์ด BUDGET.md ์˜ ํ•ด์‹œ๋ฅผ ์ˆœ์„œ ์ฆ์ธ์œผ๋กœ
224
+ # ๊ธฐ๋กํ•˜๋Š”๋ฐ step 6 ์ด ๊ฐ™์€ ํŒŒ์ผ์— ACTUAL ์„ ์š”๊ตฌํ•˜๋ฉฐ ํ•˜๋“œ ์ฐจ๋‹จํ–ˆ๋‹ค โ†’ ์™„์ฃผํ•˜๋ฉด ๋ฐ˜๋“œ์‹œ
225
+ # ์ฆ์ธ ์•„ํ‹ฐํŒฉํŠธ๊ฐ€ ์‚ฌํ›„ ๋ณ€๊ฒฝ๋˜๊ณ  verify ๊ฐ€ TAMPERED ๋ฅผ ๋‚ธ๋‹ค โ†’ **์™„์ฃผํ•œ ๋Ÿฐ์€ ๊ตฌ์กฐ์ ์œผ๋กœ
226
+ # โ‘ก ์Šน๊ธ‰ ๊ทผ๊ฑฐ๊ฐ€ ๋  ์ˆ˜ ์—†์—ˆ๋‹ค.** ๋Œ€์ƒ ์„ ์ • ์‹ค์ˆ˜๊ฐ€ ์•„๋‹ˆ๋ผ ํ•œ ํŒŒ์ผ์— ๋‘ ์—ญํ• (โ‘ ๋ถˆ๋ณ€ ์š”๊ตฌ =
227
+ # ์ฆ์ธ ยท โ‘ก๋ณ€๊ฒฝ ์š”๊ตฌ = ์‚ฌํ›„ ์บ˜๋ฆฌ๋ธŒ๋ ˆ์ด์…˜)์ด ๊ฒน์นœ ๊ฒƒ์ด๊ณ , ๊ฐœ๋ณ„๋กœ๋Š” ๋‘˜ ๋‹ค ์˜ณ์•„์„œ ์–ด๋А ์ชฝ
228
+ # ์ฝ”๋“œ๋ฅผ ์ฝ์–ด๋„ ์•ˆ ๋ณด์˜€๋‹ค. ๊ทธ๋ž˜์„œ ์ŠคํŽ™(P1 ์ด BUDGET ์„ ์ฆ์ธ์œผ๋กœ ์ง€๋ชฉ)์€ ์•ˆ ๊ฑด๋“œ๋ฆฌ๊ณ 
229
+ # **๋ณ€๊ฒฝ ์š”๊ตฌ๋งŒ** ๋นผ๋‚ธ๋‹ค. ACTUAL.md ๋Š” ์ •์˜์ƒ ํŒ์ • ํ›„ ์‚ฐ๋ฌผ์ด๋ฏ€๋กœ **์ฆ์ธ์— ๊ธฐ๋กํ•˜์ง€ ์•Š๋Š”๋‹ค.**
230
+ if [ ! -f "$WS/ACTUAL.md" ]; then
231
+ cat > "$WS/ACTUAL.md" <<EOF
232
+ # ACTUAL โ€” $SLUG (chamber run, ํŒ์ • ํ›„ ๊ธฐ๋ก)
233
+ #
234
+ # ์ด ํŒŒ์ผ์€ **์ˆœ์„œ ์ฆ์ธ ๋Œ€์ƒ์ด ์•„๋‹ˆ๋‹ค** โ€” ํŒ์ • ํ›„์— ์“ฐ์ด๋Š” ๊ฒƒ์ด ์ •์ƒ์ด๋ฏ€๋กœ ํ•ด์‹œ๋ฅผ ๊ฑธ์ง€ ์•Š๋Š”๋‹ค.
235
+ # ESTIMATE ๋Š” BUDGET.md ์— ์žˆ๊ณ  ๊ทธ ํŒŒ์ผ์€ ํŒ์ • ์ „์— ์–ผ์–ด ์žˆ๋‹ค. ๋Œ€์กฐ๋Š” ์‚ฌ๋žŒ์ด ํ•œ๋‹ค.
236
+ ACTUAL: <number/summary โ€” dispatch ์ˆ˜ ยท ํ† ํฐ ยท ์˜ˆ์‚ฐ ๋Œ€๋น„ ํŽธ์ฐจ>
237
+ EOF
238
+ fi
239
+ if grep -qE '^ACTUAL:[[:space:]]*<' "$WS/ACTUAL.md" || ! grep -qE '^ACTUAL:[[:space:]]*\S' "$WS/ACTUAL.md"; then
240
+ echo " โ›” step 6 BLOCKED: record ACTUAL cost in $WS/ACTUAL.md (actual-vs-estimate calibration)."
241
+ echo " ๐ŸŸฅ BUDGET.md ๊ฐ€ ์•„๋‹ˆ๋‹ค โ€” ๊ทธ ํŒŒ์ผ์€ ํŒ์ • ์ „ ์ฆ์ธ์ด๋ผ ์‚ฌํ›„ ๋ณ€๊ฒฝํ•˜๋ฉด ์ฆ์ธ์ด ์ฃฝ๋Š”๋‹ค."
208
242
  echo " Expected โ€” a LINE PREFIX, not a heading:"
209
243
  echo " ACTUAL: <number/summary> โ† this form is checked"
210
244
  echo " ## ACTUAL โ† a heading does NOT satisfy it"
@@ -140,6 +140,31 @@ HDR
140
140
  echo "โŒ ์›์žฅ ํ—ค๋” ์“ฐ๊ธฐ ์‹คํŒจ: $LEDGER" >&2; return 10
141
141
  fi
142
142
  fi
143
+ # โ”€โ”€ ๋ฉฑ๋“ฑ: ๊ฐ™์€ (run, artifact, sha) ์‚ผ์ค‘ํ•ญ์ด ์ด๋ฏธ ์žˆ์œผ๋ฉด ๋‹ค์‹œ ์•ˆ ์ ๋Š”๋‹ค โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
144
+ # ๊ธฐ์› 2026-08-17 ๋Ÿฐ #11(์ฒซ ํ˜•์‹ ์™„์ฃผ): ์ด ํ•จ์ˆ˜๊ฐ€ **๋ฌด์กฐ๊ฑด append** ๋ผ์„œ ๋Ÿฌ๋„ˆ๋ฅผ advance
145
+ # ํ•  ๋•Œ๋งˆ๋‹ค step 2~5 ๊ฐ€ ์žฌ์‹คํ–‰๋˜๋ฉฐ ๊ฐ™์€ ํ•ด์‹œ๊ฐ€ ๋‹ค์‹œ ์Œ“์˜€๋‹ค โ€” 4์Šคํ… ๋Ÿฐ์— **์—”ํŠธ๋ฆฌ 11๊ฐœ**.
146
+ # ์ฆ์ธ ํŒ์ • ์ž์ฒด๋Š” ์•ˆ ํ‹€๋ฆฌ์ง€๋งŒ(๊ฐ™์€ ํ•ด์‹œ๋Š” ๊ฐ™์€ ๊ฒฐ๋ก ) ๊ณต๊ฐœ tracked ํŒŒ์ผ์ด ๋ถ€ํ’€๊ณ ,
147
+ # verify ์ถœ๋ ฅ์ด ๊ฐ™์€ ์ค„์„ ์—ฌ๋Ÿฌ ๋ฒˆ ๋ฑ‰์–ด **์ฝ๋Š” ์‚ฌ๋žŒ์ด ใ€Œ๋ช‡ ๊ฑด์ด ๋ฌธ์ œ์ธ๊ฐ€ใ€๋ฅผ ์˜ค๋…**ํ•œ๋‹ค.
148
+ # ๐ŸŸฅ ๋‚ด์šฉ์ด ๋ฐ”๋€ ๊ฒฝ์šฐ๋Š” **์—ฌ์ „ํžˆ ์ƒˆ ์—”ํŠธ๋ฆฌ๋กœ ๋‚จ๋Š”๋‹ค** โ€” ๊ทธ๊ฒŒ TAMPERED ๋ฅผ ์„ฑ๋ฆฝ์‹œํ‚ค๋Š”
149
+ # ์ฆ๊ฑฐ์ด๋ฏ€๋กœ ์—ฌ๊ธฐ์„œ ์ ‘์œผ๋ฉด ์•ˆ ๋œ๋‹ค. ์ ‘๋Š” ๊ฒƒ์€ ยซ์™„์ „ํžˆ ๋™์ผํ•œ ์žฌ๊ธฐ๋กยป๋ฟ์ด๋‹ค.
150
+ # ๐ŸŸฅ ์˜คํ”„์…‹ ์ฃผ์˜ โ€” `- run: ` ๋Š” **7์ž**๋ผ ๊ฐ’์€ 8๋ถ€ํ„ฐ๋‹ค. ์ดˆํŒ์ด 9๋กœ ์ผ๊ณ (cross-family
151
+ # gpt-5.5 ์ ๋ฐœ, A๊ธ‰) ๊ทธ๋Ÿฌ๋ฉด ์Šฌ๋Ÿฌ๊ทธ ์ฒซ ๊ธ€์ž๊ฐ€ ์ž˜๋ ค **์–ด๋–ค ์ •์ƒ ์—”ํŠธ๋ฆฌ์™€๋„ ๋งค์นญ๋˜์ง€ ์•Š๋Š”๋‹ค**
152
+ # โ†’ ๊ฐ€๋“œ๊ฐ€ ์กฐ์šฉํžˆ ๋ฌด๋ ฅํ™”๋˜๊ณ  append ๊ฐ€ ๊ณ„์†๋œ๋‹ค. ์žฌํ˜„: `printf -- '- run: g2\n' |
153
+ # awk '{print substr($0,9)}'` โ†’ `2`. **๋ ˆ์ธ์ด ์ด๊ฑธ ๋ชป ์žก์•˜๋‹ค**(์•„๋ž˜ L14 ์‹ ์„ค๋กœ ๋‹ซ์Œ).
154
+ # ์ธ์ ‘์„ฑ๋„ ๊ฐ•์ œํ•œ๋‹ค: run โ†’ artifact โ†’ sha256 ์ด **์—ฐ์†**์ผ ๋•Œ๋งŒ ์ธ์ •. ์•ˆ ๊ทธ๋Ÿฌ๋ฉด ๊นจ์ง„
155
+ # YAML ์—์„œ stale run/art ๊ฐ€ ๋ฌด๊ด€ํ•œ sha ์™€ ์ง์ง€์–ด **ํ•„์š”ํ•œ append ๋ฅผ ๋ˆŒ๋Ÿฌ๋ฒ„๋ฆฐ๋‹ค**(๊ฐ™์€ ๋ฆฌ๋ทฐ B๊ธ‰).
156
+ if awk -v r="$slug" -v a="$base" -v s="$h" '
157
+ /^- run: / { run=substr($0,8); art=""; prev="run"; next }
158
+ /^ artifact: / { if (prev=="run") { art=substr($0,13); prev="art" }
159
+ else { art=""; prev="" } ; next }
160
+ /^ sha256: / { if (prev=="art" && run==r && art==a && substr($0,11)==s) { found=1; exit }
161
+ prev=""; next }
162
+ { prev="" }
163
+ END { exit(found?0:1) }
164
+ ' "$LEDGER" 2>/dev/null; then
165
+ echo "witnessed(already): $slug/$base โ€” ๋™์ผ ํ•ด์‹œ๊ฐ€ ์ด๋ฏธ ์›์žฅ์— ์žˆ๋‹ค(์žฌ๊ธฐ๋ก ์•ˆ ํ•จ)"
166
+ return 0
167
+ fi
143
168
  if ! printf -- '- run: %s\n artifact: %s\n sha256: %s\n recorded: %s\n' \
144
169
  "$slug" "$base" "$h" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$LEDGER" 2>/dev/null; then
145
170
  echo "โŒ ์›์žฅ append ์‹คํŒจ: $LEDGER" >&2; return 10
@@ -34,7 +34,22 @@ set -euo pipefail
34
34
  FH_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
35
35
  # Single source of truth: read version from the package.json shipped alongside this script.
36
36
  # No jq dependency (users may not have it); fall back to "unknown" if unreadable.
37
- VERSION="$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$FH_ROOT/package.json" 2>/dev/null | head -1)"
37
+ # VERSION โ€” the `|| true` is load-bearing under `set -euo pipefail`, and it is the ONLY part that
38
+ # is. Without it, a package.json that is missing OR unreadable makes this pipeline non-zero, and
39
+ # because the whole thing is an ASSIGNMENT, `set -e` aborts the script right here โ€” so the
40
+ # `:-unknown` fallback on the next line never runs and the gate exits 1 before doing any work.
41
+ # MEASURED 2026-08-16: a sibling harness carries this file verbatim but ships no package.json (it
42
+ # is not an npm package), so its gate was dead on every invocation and its 30-lane regression
43
+ # suite reported 30 failures that were all this one line. One lane appeared to PASS only because
44
+ # its expected exit code happened to be 1 โ€” the textbook signature of a dead control. Nobody had
45
+ # looked, because that repo had no CI. Latent HERE too, not merely a sibling's problem: a copy of
46
+ # this script run from a directory with no package.json died the same way.
47
+ # A `[ -f "$FH_ROOT/package.json" ]` guard was written first and then REMOVED, because a 4-way
48
+ # mutant matrix refuted it: with the file merely absent either mechanism alone suffices, but with
49
+ # the file present-and-unreadable the guard passes and only `|| true` survives. A guard that
50
+ # covers nothing `|| true` does not already cover is decoration, and the first version of this
51
+ # comment claimed both were load-bearing โ€” that claim was false.
52
+ VERSION="$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$FH_ROOT/package.json" 2>/dev/null | head -1 || true)"
38
53
  VERSION="${VERSION:-unknown}"
39
54
  CALLER_CWD="$(pwd -P)"
40
55
  _TMPDIR="${TMPDIR:-/tmp}"
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """ko_tech_writer_calibrate.py โ€” `ko-tech-writer` Step 2 / Step 4-b ์Šค์บ”์˜ **ํŒ๋ณ„๋ ฅ** known-pair.
4
+
5
+ ์™œ ํŒŒ์ด์ฌ์ธ๊ฐ€ (์—”์ง„ ์„ ํƒ์€ ์ทจํ–ฅ์ด ์•„๋‹ˆ๋ผ ์‹ค์ธก ๊ฒฐ๊ณผ๋‹ค)
6
+ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
7
+ `SKILL.md` Step 4 ๊ฐ€ ํ—ˆ์šฉ ์—”์ง„์„ ๋ช…์‹œํ•œ๋‹ค: *"์œ ๋‹ˆ์ฝ”๋“œ ์ธ์‹ ์—”์ง„(ripgrep ยท GNU grep UTF-8
8
+ ๋กœ์ผ€์ผ ยท Python `re` โ€” **`grep -P` ๋Š” ์˜ˆ์™ธ**)"*. ์ดˆํŒ์€ `rg` ๋กœ ๊ณ ์ •ํ–ˆ๊ณ  **CI ์—์„œ ์ฃฝ์—ˆ๋‹ค** โ€”
9
+ ubuntu-latest ์— ripgrep ์ด ์—†๋‹ค(2026-08-17 ์‹ค์ธก, `HARNESS ERROR ... exited 10`).
10
+ ๊ฐ™์€ ์‹คํ–‰์—์„œ ์ด์‹์„ฑ ๋ฆฐํŠธ๋„ ๊ฑธ๋ ธ๋‹ค: ์…ธ ํŒŒ์ผ ์•ˆ์˜ `[๊ฐ€-ํžฃ]` ๋ฌธ์ž ๋ฒ”์œ„๋Š”
11
+ **GNU grep C ๋กœ์ผ€์ผ์—์„œ exit 2 ๋กœ ์ฃฝ๊ณ  ๊ทธ ๊ฒฐ๊ณผ๊ฐ€ ยซ๋ฌด๋งค์น˜ยป ๋กœ ์ฝํžŒ๋‹ค** โ€” ์ฆ‰ fail-open.
12
+
13
+ Python `re` ๋Š” โ“ ์„ธ ํ—ˆ์šฉ ์—”์ง„ ์ค‘ **์–ด๋””์—๋‚˜ ์žˆ๋Š” ์œ ์ผํ•œ ๊ฒƒ**์ด๊ณ  โ“‘ ์œ ๋‹ˆ์ฝ”๋“œ๋ฅผ ๊ธฐ๋ณธ์œผ๋กœ
14
+ ์ธ์‹ํ•˜๋ฉฐ โ“’ ํŒจํ„ด์ด ์…ธ ๋ฐ–์œผ๋กœ ๋‚˜๊ฐ€ ๋กœ์ผ€์ผ ๋ฌธ์ œ๋ฅผ ๊ตฌ์กฐ์ ์œผ๋กœ ์—†์•ค๋‹ค. **๊ฒฉํ•˜๊ฐ€ ์•„๋‹ˆ๋ผ
15
+ ์ •๋ณธ์ด ์ด๋ฏธ ํ—ˆ์šฉํ•œ ๋“ฑ๊ฐ€ ์—”์ง„์œผ๋กœ์˜ ์ด๋™**์ด๋‹ค.
16
+
17
+ ๋ฌด์—‡์„ ์ฃผ๊ณ  ๋ฌด์—‡์„ ์•ˆ ์ฃผ๋Š”๊ฐ€ โ€” ์„ž์ง€ ๋งˆ๋ผ
18
+ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
19
+ ์ค€๋‹ค ๊ฐ ์Šค์บ”์ด **์–‘์„ฑ์„ ์žก๊ณ  ์Œ์„ฑ์„ ์•ˆ ์žก๋Š”๊ฐ€**(=ํŒ๋ณ„๋ ฅ). ์žฌํ˜„ ์ปค๋งจ๋“œ์™€ ์‹คํ–‰ ์ถœ๋ ฅ.
20
+ ์•ˆ ์ค€๋‹ค ๊ทธ ์Šค์บ”์ด **์ž„์˜ ๋ฌธ์„œ์—์„œ ์ž”์—ฌ 0๊ฑด์ธ๊ฐ€**. ๊ทธ๊ฑด ๋ฌธ์„œ๋งˆ๋‹ค ๋”ฐ๋กœ ์žฌ๋Š” ๊ฒƒ์ด๋‹ค.
21
+ โ‡’ ์ด๊ฑธ ํ†ต๊ณผํ–ˆ๋‹ค๊ณ  ยซStep 2 ์ž”์—ฌ 0ยป ์„ ์ฃผ์žฅํ•˜๋ฉด
22
+ `harness_terminal_correlation_and_recommendations.md` ์˜ ๊ฐ•๋“ฑ ์‚ฌ์œ ๊ฐ€ ๊ทธ๋Œ€๋กœ ์žฌ๋ฐœํ•œ๋‹ค.
23
+
24
+ exit: 0 ์ „ ๋ ˆ์ธ ํŒ๋ณ„ ยท 1 ํŒ๋ณ„ ์‹คํŒจ ยท 10 harness error(ํ”ฝ์Šค์ฒ˜ ๋ถ€์žฌ)
25
+ """
26
+ import os
27
+ import re
28
+ import sys
29
+
30
+ HERE = os.path.dirname(os.path.abspath(__file__))
31
+ FIX = os.path.join(HERE, "..", "plugins", "fh-commons", "skills", "ko-tech-writer", "fixtures")
32
+ POS = os.path.join(FIX, "known_positive.md")
33
+ NEG = os.path.join(FIX, "known_negative.md")
34
+
35
+ # (๋ผ๋ฒจ, ํŒจํ„ด) โ€” Step 2 ์˜ ยซ๊ธฐ๊ณ„ ๊ฒ€์ถœ ๊ฐ€๋Šฅยป 5ํด๋ž˜์Šค + Step 4-b ์˜ 2ํŒจํ„ด.
36
+ #
37
+ # ๐ŸŸฅ ์ •๋ณธ์˜ ๋ถ„๋ฅ˜๊ฐ€ ๊ณผ์žฅ์ด๋ผ๋Š” ๊ฒƒ์ด ์ฒซ ์บ˜๋ฆฌ๋ธŒ๋ ˆ์ด์…˜์—์„œ ๋“œ๋Ÿฌ๋‚ฌ๋‹ค. SKILL.md ๋Š”
38
+ # "์•ž ๋‹ค์„ฏ ์ค„์€ ๊ธฐ๊ณ„ ๊ฒ€์ถœ ๊ฐ€๋Šฅ" ์ด๋ผ ์ ์—ˆ๋Š”๋ฐ, **์‹ค์ œ๋กœ grep ์„ ์‹ฃ๊ณ  ์žˆ๋Š” ๊ฒƒ์€
39
+ # C1(์ค„ํ‘œ)ยทC5(์†Œ์œ  ์ง์—ญ) ๋‘˜๋ฟ**์ด๊ณ  C2ยทC3ยทC4 ์˜ ยซ๊ฒ€์ถœ ํžŒํŠธยป ์นธ์€ ์‚ฐ๋ฌธ ์„œ์ˆ ์ด๋‹ค.
40
+ # ํŠนํžˆ C4 ๋Š” ยซ์„œ์ˆ ์–ด ์—†๋Š” ๋งˆ์นจยป ์ด๋ผ๋Š” ์„œ์ˆ ๋ฟ ํŒจํ„ด์ด ์—†๋‹ค.
41
+ LANES = [
42
+ ("C1 ์ค„ํ‘œ ์ด์–ด๋ถ™์ž„", r"(๋‹ค|๊ฒƒ|์Œ) โ€” "),
43
+ ("C2 ์šฉ์–ด-๋จธ๋ฆฌ", r"^\s*[-*]\s+\*\*[^*]+\*\* โ€” "),
44
+ ("C3 ์ฝœ๋ก  ๋‚˜์—ดํˆฌ", r"[๊ฐ€-ํžฃ]์€: "),
45
+ # C4: SKILL.md ์— ํŒจํ„ด์ด ์—†๋‹ค. ์•„๋ž˜๋Š” **ํ›„๋ณด ๊ฒ€์ถœ**์ด์ง€ ํŒ์ •์ด ์•„๋‹ˆ๋‹ค
46
+ # (SKILL.md ์ž์‹ ์˜ ๊ทœ์œจ: "grep์€ ํ›„๋ณด๋ฅผ ํ‘œ์‹œํ•  ๋ฟ, ๋‚จ๊ธธ์ง€ ํŒ์ •์€ ๋ฌธ๋งฅ์œผ๋กœ").
47
+ # ๋‹ซํžŒ ๋ช…์‚ฌ ์–ดํœ˜๋ผ **recall ์ด ๋‚ฎ๋‹ค** โ€” ๋‚ฎ์€ ์ชฝ์ด ๊ณผ์ฐจ๋‹จ๋ณด๋‹ค ์•ˆ์ „ํ•œ ๋ฐฉํ–ฅ์ด๊ณ ,
48
+ # ๋„“ํžˆ๋ ค๋ฉด ์–ดํœ˜๋ฅผ ๋Š˜๋ฆฌ๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ ํ˜•ํƒœ์†Œ ๋ถ„์„์ด ํ•„์š”ํ•˜๋‹ค.
49
+ ("C4 ์กฐ๊ฐ๋ฌธ(ํ›„๋ณดยท๋‹ซํžŒ ์–ดํœ˜)", r"(์ธต|๊ฒƒ|์ |์ถ•|๊ฑด|๋ฟ|๋ฐ”|์…ˆ)\.\s*(<!--|$)"),
50
+ ("C5 ์†Œ์œ  ์ง์—ญ", r"(์„|๋ฅผ) (๊ฐ–|๊ฐ€์ง€)"),
51
+ ("C6 ์ „์นญ ์–ดํœ˜", r"์ „๋ถ€|๋ชจ๋‘|ํ•˜๋‚˜๋„|์ „ํ˜€|์ผ์ ˆ|์˜ˆ์™ธ ์—†์ด"),
52
+ ("C6b ๋ถ€์ •ํ˜• ์ „์นญ", r"(์•ˆ|๋ชป) ?(ํ–ˆ|๋งŒ๋“ค|๋‚˜์˜ค|์žกํžˆ)|์ง€ ์•Š์•˜(๋‹ค|์Šต๋‹ˆ๋‹ค)"),
53
+ ]
54
+
55
+ # ์ ˆ๋Œ€ ์žกํžˆ๋ฉด ์•ˆ ๋˜๋Š” ํ† ํฐ โ€” ใ€Œ๋Š˜ ์žก๋Š”๋‹คใ€๋ฅผ ๋‚ด๋Š” ์ฃฝ์€ ๊ณ„๊ธฐ ๋ฐฐ์ œ์šฉ
56
+ NEVER = r"ZZZ_absent_token_ZZZ"
57
+
58
+
59
+ def count(path, pattern):
60
+ """ํŒจํ„ด์— ๋งค์นญ๋˜๋Š” **์ค„ ์ˆ˜**. ํŒŒ์ผ ๋ถ€์žฌ๋Š” ์˜ˆ์™ธ๋กœ ์˜ฌ๋ฆฐ๋‹ค(๋ฌด์Œ 0 ๊ธˆ์ง€)."""
61
+ rx = re.compile(pattern, re.MULTILINE)
62
+ n = 0
63
+ with open(path, encoding="utf-8") as fh:
64
+ for line in fh:
65
+ if rx.search(line.rstrip("\n")):
66
+ n += 1
67
+ return n
68
+
69
+
70
+ def main():
71
+ for p in (POS, NEG):
72
+ if not os.path.isfile(p):
73
+ sys.stderr.write("harness error: fixture missing %s\n" % p)
74
+ return 10
75
+
76
+ print("engine: python%d.%d re (unicode-aware)"
77
+ % (sys.version_info[0], sys.version_info[1]))
78
+ print("known-positive: %s" % os.path.relpath(POS, os.path.join(HERE, "..")))
79
+ print("known-negative: %s" % os.path.relpath(NEG, os.path.join(HERE, "..")))
80
+ print("")
81
+
82
+ npass = nfail = 0
83
+ for label, pat in LANES:
84
+ p = count(POS, pat)
85
+ n = count(NEG, pat)
86
+ if p >= 1 and n == 0:
87
+ print("OK %s -- positive %d / negative 0 (discriminates)" % (label, p))
88
+ npass += 1
89
+ elif p < 1:
90
+ print("FAIL %s -- MISSES the positive (positive %d). A blind instrument." % (label, p))
91
+ nfail += 1
92
+ else:
93
+ print("FAIL %s -- HITS the negative (negative %d). Over-blocks; no discrimination."
94
+ % (label, n))
95
+ nfail += 1
96
+
97
+ # META ์ปจํŠธ๋กค 1 โ€” ์—†๋Š” ํ† ํฐ์ด ์–‘์ชฝ ๋‹ค 0์ธ๊ฐ€ (๊ณ„๊ธฐ๊ฐ€ ยซ๋Š˜ ์žก๋Š”๋‹คยป ๋ฅผ ๋‚ด์ง€ ์•Š๋Š”๋‹ค)
98
+ if count(POS, NEVER) == 0 and count(NEG, NEVER) == 0:
99
+ print("OK META control -- an absent token matches nothing in either fixture")
100
+ npass += 1
101
+ else:
102
+ print("FAIL META control -- an absent token matched. Do not trust this suite.")
103
+ nfail += 1
104
+
105
+ # META ์ปจํŠธ๋กค 2 โ€” ๋‘ ํ”ฝ์Šค์ฒ˜๊ฐ€ ์‹ค์ œ๋กœ ๋‹ค๋ฅธ ํŒŒ์ผ์ธ๊ฐ€
106
+ with open(POS, encoding="utf-8") as a, open(NEG, encoding="utf-8") as b:
107
+ if a.read() != b.read():
108
+ print("OK META control -- positive/negative fixtures differ")
109
+ npass += 1
110
+ else:
111
+ print("FAIL META control -- the two fixtures are identical; results are meaningless")
112
+ nfail += 1
113
+
114
+ print("")
115
+ if nfail == 0:
116
+ print("CALIBRATED (%d lanes) -- Step 2 five classes + Step 4-b two patterns discriminate."
117
+ % npass)
118
+ print(" NOT proven: 'zero residue' in any real document. That is measured per document.")
119
+ print(" Citing this suite as evidence of 'zero residue' reproduces the very downgrade")
120
+ print(" recorded in harness_terminal_correlation_and_recommendations.md.")
121
+ return 0
122
+ print("NOT CALIBRATED (%d/%d failed)" % (nfail, npass + nfail))
123
+ return 1
124
+
125
+
126
+ if __name__ == "__main__":
127
+ sys.exit(main())
@@ -51,6 +51,16 @@ ACCEPTED_ABSENT=(
51
51
  # read as claims about THEIR file. The shipped docs name it as the place verdicts live in the
52
52
  # harness repo, which is a pointer for contributors, not a promise of a shipped artifact.
53
53
  ".claude/regression/ablation_verdicts.md"
54
+ # ์ฑ”๋ฒ„ **์ˆœ์„œ ์ฆ์ธ ์›์žฅ**(ship_readiness_gate ยงโ‘ก P1). ๋ฐ”๋กœ ์œ„ ablation_verdicts ์™€ **๊ฐ™์€
55
+ # ๋…ผ๊ฑฐ**๋‹ค: ์ด๊ฑด THIS repo ์˜ ์ฑ”๋ฒ„ ๋Ÿฐ์ด ์–ธ์ œ ๋ฌด์—‡์„ ๊ณ ์ •ํ–ˆ๋Š”์ง€์— ๋Œ€ํ•œ ๊ธฐ๋ก์ด๊ณ , ์†Œ๋น„์ž์˜
56
+ # ์ฑ”๋ฒ„ ๋Ÿฐ์€ ๊ทธ๋“ค ๊ฒƒ์ด๋‹ค โ€” ์ถœํ•˜ ๋ฌธ์„œ์—์„œ ์ธ์šฉ๋œ ์ฑ„๋กœ ๋”ธ๋ ค๊ฐ€๋ฉด ์†Œ๋น„์ž๊ฐ€ **์ž๊ธฐ ๋Ÿฐ์— ๋Œ€ํ•œ
57
+ # ์ฃผ์žฅ**์œผ๋กœ ์ฝ๋Š”๋‹ค. ๊ฒŒ๋‹ค๊ฐ€ ์ด ์›์žฅ์€ ์šฐ๋ฆฌ ๋Ÿฐ slugยท์‹œ๊ฐ์„ ๋‹ด์€ ๊ณต๊ฐœ ํ‘œ๋ฉด์ด๋ผ ์†Œ๋น„์ž์—๊ฒŒ
58
+ # ๋ณด๋‚ด๋Š” ๊ฒƒ์€ ์ •๋ณด ์œ ์ถœ ๋ฐฉํ–ฅ์œผ๋กœ๋„ ํ‹€๋ ธ๋‹ค.
59
+ # ๐ŸŸฅ **๋ถ€์žฌ๊ฐ€ ์†Œ๋น„์ž ์ชฝ ๊ธฐ๋Šฅ์„ ๊นจ์ง€ ์•Š๋Š”๋‹ค** โ€” `chamber_witness.sh do_record` ๋Š” ์›์žฅ์ด
60
+ # ์—†์œผ๋ฉด ํ—ค๋”๋ฅผ ๋งŒ๋“ค์–ด ์ƒ์„ฑํ•œ๋‹ค(๊ฐ™์€ ํŒŒ์ผ์˜ `[ ! -f "$LEDGER" ]` ๋ถ„๊ธฐ). ์ฆ‰ ์Šคํฌ๋ฆฝํŠธ๋Š”
61
+ # ์ถœํ•˜๋˜๊ณ  ์›์žฅ์€ ์†Œ๋น„์ž ๋จธ์‹ ์—์„œ ์ฒ˜์Œ ์“ธ ๋•Œ ์ƒ๊ธด๋‹ค. ใ€Œ์—†์œผ๋ฉด ์ฃฝ๋Š”๋‹คใ€๊ฐ€ ์•„๋‹ˆ๋ผ
62
+ # ใ€Œ์—†๋Š” ๊ฒŒ ์ •์ƒ ์ดˆ๊ธฐ ์ƒํƒœใ€๋‹ค.
63
+ "knowledge/shared/learnings/chamber_ordering_witness.yaml"
54
64
  "scripts/sync-to-be.sh"
55
65
  "scripts/sync_guard_check.sh"
56
66
  # Return path (companion store โ†’ hub) and its anchor. Same reason as the forward path above: the