@chrono-meta/fh-gate 2.15.1 β 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.
- package/.claude/regression/probes_live.yaml +137 -0
- package/.claude/rules/.residency-patterns.defaults +7 -0
- package/.claude/rules/fh_4axis_gate.md +88 -1
- package/.claude-plugin/marketplace.json +8 -2
- package/AGENTS.md +27 -0
- package/CATALOG.md +17 -0
- package/CLAUDE.md +13 -3
- package/README.ja.md +51 -7
- package/README.ko.md +48 -7
- package/README.md +43 -6
- package/README.zh.md +45 -8
- package/docs/STANDARDS_ALIGNMENT.md +61 -0
- package/docs/USER_GUIDE.md +3 -0
- package/docs/USE_CASES.md +50 -0
- package/docs/model_tier_expectations.md +60 -0
- package/knowledge/shared/harness-core/fh_three_layer_canon.md +77 -10
- package/knowledge/shared/harness-core/field_verdict_crossfamily_gate.md +18 -0
- package/knowledge/shared/harness-core/harness_incubator_doctrine.md +9 -0
- package/knowledge/shared/harness-core/iso_ai_standards_crosswalk.md +139 -0
- package/knowledge/shared/harness-core/measurement-integrity-checklist.md +35 -1
- package/knowledge/shared/harness-core/ship_readiness_gate.md +19 -2
- package/knowledge/shared/learnings/subagent_invocations_log.yaml +489 -3
- package/package.json +48 -2
- package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-commons/skills/preprep/README.md +4 -1
- package/plugins/fh-commons/skills/preprep/SKILL.md +99 -3
- package/plugins/fh-commons/skills/preprep/diagram_from_json.py +154 -0
- package/plugins/fh-commons/skills/preprep/fixtures/fixture_R3_negative.pptx +0 -0
- package/plugins/fh-commons/skills/preprep/fixtures/fixture_R3_positive.pptx +0 -0
- package/plugins/fh-commons/skills/preprep/fixtures/mk_slide_fixtures.py +179 -0
- package/plugins/fh-commons/skills/preprep/interslide_deps.py +98 -9
- package/plugins/fh-commons/skills/preprep/lane_adjacent_dup.py +4 -1
- package/plugins/fh-commons/skills/preprep/lane_diagram.py +95 -0
- package/plugins/fh-commons/skills/preprep/lane_geometry.py +181 -0
- package/plugins/fh-commons/skills/preprep/lane_promise.py +4 -1
- package/plugins/fh-commons/skills/preprep/lane_slide_refs.py +134 -0
- package/plugins/fh-commons/skills/preprep/lane_slide_relations.py +300 -0
- package/plugins/fh-commons/skills/preprep/preprep.py +129 -8
- package/plugins/fh-commons/skills/preprep/preprep_wire.py +210 -0
- package/plugins/fh-commons/skills/preprep/presentation_checklist.md +4 -0
- package/plugins/fh-commons/skills/preprep/surfaces.example.yaml +18 -0
- package/plugins/fh-commons/skills/preprep/test_preprep_lanes_rp.py +184 -0
- package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-meta/CHANGELOG.md +67 -0
- package/plugins/fh-meta/skills/auto-decorrelation/SKILL.md +87 -2
- package/plugins/fh-meta/skills/frontier-digest/SKILL.md +2 -2
- package/plugins/fh-meta/skills/frontier-digest/SKILL_detail.md +25 -5
- package/plugins/fh-meta/skills/hub-cc-pr-reviewer/SKILL.md +11 -0
- package/plugins/fh-meta/skills/hub-cc-pr-reviewer/SKILL_detail.md +3 -0
- package/plugins/fh-qp/.claude-plugin/plugin.json +22 -0
- package/plugins/fh-qp/README.md +71 -0
- package/plugins/fh-qp/fixtures/evidence_known_clean.txt +3 -0
- package/plugins/fh-qp/fixtures/evidence_known_dirty.txt +6 -0
- package/plugins/fh-qp/fixtures/reach_known_partial.tsv +4 -0
- package/plugins/fh-qp/fixtures/reach_known_reached.tsv +4 -0
- package/plugins/fh-qp/fixtures/reach_known_wall.tsv +3 -0
- package/plugins/fh-qp/fixtures/verdicts_known_bad_branch.tsv +2 -0
- package/plugins/fh-qp/fixtures/verdicts_known_bad_vacuous_machine.tsv +2 -0
- package/plugins/fh-qp/fixtures/verdicts_known_good.tsv +4 -0
- package/plugins/fh-qp/fixtures/verdicts_verify_only.tsv +3 -0
- package/plugins/fh-qp/qp_profile.example.yaml +29 -0
- package/plugins/fh-qp/scripts/qp_tools.sh +216 -0
- package/plugins/fh-qp/skills/qp/SKILL.md +65 -0
- package/plugins/fh-qp/skills/qp-plan/SKILL.md +42 -0
- package/plugins/fh-qp/skills/qp-regress/SKILL.md +45 -0
- package/plugins/fh-qp/skills/qp-run/SKILL.md +49 -0
- package/scripts/backtick_guard.sh +194 -0
- package/scripts/chamber_run.sh +14 -5
- package/scripts/com.forge-harness.live-eval.plist +84 -0
- package/scripts/compaction_probe.sh +9 -35
- package/scripts/context_continuity_score.sh +49 -7
- package/scripts/directional_diff_gate.sh +14 -2
- package/scripts/fh-gate.sh +3 -3
- package/scripts/files_manifest_shipping_check.sh +19 -0
- package/scripts/frontier_digest_autopilot.sh +4 -1
- package/scripts/gate_pathspec_check.sh +1 -1
- package/scripts/map_postprocess.py +90 -0
- package/scripts/outbound_query_guard.sh +131 -0
- package/scripts/outbound_query_hook.sh +373 -0
- package/scripts/package_coverage_check.sh +79 -18
- package/scripts/pipe_verdict_guard.sh +41 -1
- package/scripts/probe_live_eval.sh +240 -0
- package/scripts/probe_live_eval_lib.py +579 -0
- package/scripts/proposal_hook.sh +192 -0
- package/scripts/public_surface_scan_files.sh +11 -2
- package/scripts/push_zone_check.sh +78 -0
- package/scripts/residency_closure_scan.py +252 -0
- package/scripts/revert_probe.sh +250 -0
- package/scripts/selfcheck.sh +106 -3
- package/scripts/session_close_check.sh +100 -0
- package/scripts/sim_isolated_run.sh +195 -9
- package/scripts/test_action_yml_lanes.sh +97 -0
- package/scripts/test_backtick_guard_lanes.sh +115 -0
- package/scripts/test_degrade_scan_shell_probes.sh +7 -7
- package/scripts/test_fh_qp_lanes.sh +105 -0
- package/scripts/test_files_manifest_shipping_lanes.sh +5 -5
- package/scripts/test_gate_two_verdicts_lanes.sh +139 -0
- package/scripts/test_heavy_classifier_lanes.sh +1 -1
- package/scripts/test_lane_runner_lanes.sh +59 -33
- package/scripts/test_map_postprocess_lanes.sh +143 -0
- package/scripts/test_mapped_tracks_lanes.sh +1 -1
- package/scripts/test_marker_affected_lanes.sh +93 -0
- package/scripts/test_marker_crossfamily_lanes.sh +90 -6
- package/scripts/test_marker_oracle_lanes.sh +136 -0
- package/scripts/test_marker_soul_check_lanes.sh +24 -0
- package/scripts/test_node_check_lanes.sh +34 -34
- package/scripts/test_outbound_query_hook_lanes.sh +433 -0
- package/scripts/test_outbound_query_lanes.sh +87 -0
- package/scripts/test_package_coverage_lanes.sh +53 -27
- package/scripts/test_pipe_verdict_guard_lanes.sh +31 -5
- package/scripts/test_precommit_pointer_index_lanes.sh +33 -0
- package/scripts/test_preprep_diagram_lanes.sh +87 -0
- package/scripts/test_preprep_drift_anchor.sh +16 -7
- package/scripts/test_preprep_drift_anchor_lanes.sh +23 -0
- package/scripts/test_preprep_slide_refs_lanes.sh +169 -0
- package/scripts/test_probe_live_eval_lanes.sh +437 -0
- package/scripts/test_proposal_hook_lanes.sh +57 -0
- package/scripts/test_push_zone_lanes.sh +304 -0
- package/scripts/test_residency_closure_lanes.sh +70 -0
- package/scripts/test_revert_probe_lanes.sh +146 -0
- package/scripts/test_session_close_lanes.sh +3 -5
- package/scripts/test_sim_isolated_run_lanes.sh +136 -0
- package/scripts/test_utterance_intake_lanes.sh +414 -0
- package/scripts/test_worktree_reclaim_lanes.sh +70 -0
- package/scripts/transcript_utterances.py +222 -0
- package/scripts/utterance_intake.sh +424 -0
- package/scripts/utterance_landing_check.sh +2 -2
- package/scripts/validate_yaml.sh +27 -0
- package/scripts/worktree_reclaim.sh +95 -0
- package/templates/.git-hooks/pre-commit +380 -5
- package/templates/.git-hooks/pre-push +91 -0
- package/templates/RED_TEAM_REPORT.md +49 -0
- package/templates/settings.PreToolUse.snippet.json +101 -1
- package/plugins/fh-commons/README.md +0 -38
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# utterance_intake.sh β μ μ¬λ³Έμ μ΄μμ λ°νλ₯Ό **μλμΌλ‘** νλ‘λΈλ‘ λ§λ€μ΄ κΈ°λ‘ μ°©μ§λ₯Ό κ²μ¦νλ€.
|
|
3
|
+
#
|
|
4
|
+
# ββ μ (μ§λ¨, 2026-09-05) ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
5
|
+
# FH μ Β«μ§λ¬ΈνκΈ°Β» μμ§μ external-grounding μμ§μΈλ° **μμ© μ λ°μ΄ μ°λ¬Έμ΄λ€**
|
|
6
|
+
# (`fh_three_layer_canon.md Β§2`: *"묻λ κ²½λ‘λ μκ³ λ€μ΄μ¨ ν΅μ°°μ μμ°ννλ κ²½λ‘κ° μ½νλ€"*).
|
|
7
|
+
# κ³κΈ°λ μ΄λ―Έ λ λ€ μμλ€:
|
|
8
|
+
# compaction_probe.sh seal μ μ¬λ³Έ β μ΄μμ λ°ν μΆμ μμ₯ (λλ€)
|
|
9
|
+
# utterance_landing_check.sh probes.tsv β κΈ°λ‘ νμΌ grep (λλ€, νΈμΆλΆ 0)
|
|
10
|
+
# κ·Έλ°λ° **probes.tsv λ₯Ό λ§λλ κΈ°κ³κ° μμλ€.** μμΌλ‘ μ§μΌ νλ μ무λ μ μ§°κ³ ,
|
|
11
|
+
# `session_close_check.sh` μ΄λμμλ λ λ²μ§Έκ° νΈμΆλμ§ μμλ€(grep 0). λ κ³κΈ° μ¬μ΄μ
|
|
12
|
+
# ν μΉΈμ΄ λΉμ΄ μμ΄μ μ±λμ΄ μλλΌ μ°λ¬ΈμΌλ‘ λ¨μ μλ κ² β μ΄ μ€ν¬λ¦½νΈκ° κ·Έ μΉΈμ΄λ€.
|
|
13
|
+
#
|
|
14
|
+
# μ€μΈ‘ κ·Όκ±°(κ°μ λ ): μ΄μμκ° μΈμ
μ€ λμ§ κ΅λ¦¬Β·μ€κ³κΈ λ°ν 6 κ±΄μ΄ μ°©μ§ν κ²½λ‘λ μ λΆ
|
|
15
|
+
# **κ±°λ²λμ μ**μ΄μλ€(signal νμΌ 2 Β· UAP λΈλ‘ 2 Β· fh_completed). κΈ°κ³λ ν 건λ μ μ‘μλ€.
|
|
16
|
+
#
|
|
17
|
+
# ββ μ΄ μ€ν¬λ¦½νΈκ° νλ κ² / μ νλ κ² ββββββββββββββββββββββββββββββββββββββ
|
|
18
|
+
# νλ€ : λ°ν μΆμΆ β ν€ λ±λ§ νλ‘λΈ μμ± β 컨νΈλ‘€ 2 μ’
μ½μ
β μ°©μ§ κ²μ¦ β λ―Έμ°©μ§ λͺ©λ‘
|
|
19
|
+
# μ νλ€: κΈ°λ‘μ **μ°μ§ μλλ€.** 무μμ μ΄λμ μ μμ§λ νλ¨μ΄κ³ , νλ¨μ μ½λλ‘ κ΅³νλ©΄
|
|
20
|
+
# κ·Έκ² μ²μ₯μ΄ λλ€(`CLAUDE.md Β§Mechanization Boundary`). μ΄κ±΄ Β«μ±λμ΄ κ°μ
|
|
21
|
+
# λλ₯΄λκ°Β» λ§ λ³Έλ€ β Β«κ·Έ κ°μ΄ μ³μκ°Β» λ μ¬λ λͺ«μ΄λ€.
|
|
22
|
+
#
|
|
23
|
+
# ββ π₯ λͺ
λͺ
λ μμ¬ μ
. μΈμ©νκΈ° μ μ μ½μ΄λΌ βββββββββββββββββββββββββββββββββ
|
|
24
|
+
# β **μ΄λ¦νλ μΈ‘μ μ΄ μλλ€** β νλ‘λΈλ Β«ν€ λ±λ§Β» μ΄λΌμ, κΈ°λ‘μ΄ κ°μ λ»μ **λ€λ₯Έ λ±λ§λ‘**
|
|
25
|
+
# μ μμΌλ©΄ μ°©μ§νλλ° λ―Έμ°©μ§λ‘ λ¬λ€. μμ: `utterance_landing_check.sh` ν€λμ
|
|
26
|
+
# *"rc=1 μ Β«κΈ°λ‘νλΌΒ»κ° μλλΌ Β«νμΈνλΌΒ»λ‘ μ½μ΄μΌ νλ€"*. μ΄ λ°©ν₯μ **κ³Όμ°¨λ¨μ΄λΌ μμ **νλ€.
|
|
27
|
+
# β **ν ν°λ§λ€ ν ν, λ°νλ κ³Όλ°μΌλ‘ νμ νλ€.** μ΄νμ ν ν `(λ±λ§1|λ±λ§2|λ±λ§3)` μ΄λΌ
|
|
28
|
+
# **νλλ§ κ²Ήμ³λ** μ°©μ§μλ€ β κ°μ μ£Όμ μ΄ νλ(Β«μΏ λ²λ€ν°μ€Β»)κ° λ κΈ°λ‘μ΄ μ μ±
λ°νλ₯Ό Β«μ°©μ§Β»
|
|
29
|
+
# λ‘ λ λνλ€(cross-family codex, 2026-09-05). μ΄μ λ°νμ μ΅μ₯ ν ν°(β€3)μ **κ°κ° ν ν**μΌλ‘
|
|
30
|
+
# λ΄κ³ , λ°νλ μ°©μ§ νμ΄ βn/2β μ΄μμΌ λλ§ μ°©μ§λ€(3 ν€ β 2 Β· 2 ν€ β 1 Β· 1 ν€ β 1).
|
|
31
|
+
# π₯ λ¨μ fail-open: ν€κ° 2 κ°λΏμΈ λ°νλ μ¬μ ν νλλ§ κ²Ήμ³λ μ°©μ§λ€(OR). rc=0 μ μ¬μ ν
|
|
32
|
+
# Β«μ λλ‘ μ νλ€Β» μ μ¦λͺ
μ΄ μλλ€. λ°ν λ¨μ κ³μμ map μ n_keys λ‘ μ΄ μ€ν¬λ¦½νΈκ° μ§κ³νλ€.
|
|
33
|
+
# β **advisory λ€** β `session_close_check.sh β -f` λ μ΄κ±Έ μ°¨λ¨μΌλ‘ μ°μ§ μλλ€. λ
ΈμΆ μ¬λ€λ¦¬
|
|
34
|
+
# (`fh_three_layer_canon Β§1-a-2 β` shadow) λ‘ λͺ μΈμ
κ΄μ°°ν λ€ μΉκ²© μ¬λΆλ μ΄μμ κ²°μ .
|
|
35
|
+
#
|
|
36
|
+
# ββ 컨νΈλ‘€ λ μ’
(ν μ’
μ΄ μλλ€) βββββββββββββββββββββββββββββββββββββββββββ
|
|
37
|
+
# `utterance_landing_check.sh` μ CONTROL μ **μμ±λ§** μλ€ β γκ³κΈ°κ° μ£½μλγλ μ¬κ³
|
|
38
|
+
# γκ³κΈ°κ° μ무거λ λ€ μ‘λγλ λͺ» μ°λ€(`[[feedback_control_presence_is_not_discrimination]]`).
|
|
39
|
+
# POSITIVE **κΈ°λ‘ λ΄μ©μμ λ½μ μ΅μ₯ ν ν°.** κ±°κΈ° μ€μ λ‘ μμΌλ μ μμ μ‘νμΌ νκ³ ,
|
|
40
|
+
# μ μ‘νλ©΄ νμ΄νλΌμΈμ΄ μ£½μ κ²μ΄λ€(word-split Β· κ²½λ‘ Β· μΈμ½λ©) β rc=10.
|
|
41
|
+
# π₯ μ΄νμ Β«μ€λ λ μ§Β» μλλ° **λ μ§λ νμΌλͺ
μ μμ§ λ΄μ©μ μμ μλ¬΄κ° μλ€** β
|
|
42
|
+
# κ·Έλ¬λ©΄ μ μ λ§κ°λ§λ€ κ±°μ§ κ²½λ³΄κ° λ¬λ€. μ€μΈ‘ κ·Όκ±°λ μλ `_control_rows` μ£Όμ.
|
|
43
|
+
# λ μ§λ Β«μμ λλ§Β» 보쑰 컨νΈλ‘€λ‘ λνλ€.
|
|
44
|
+
# NEGATIVE 무μμ ν ν° β **μμ΄μΌ λ§λ
νλ€.** μ‘νλ©΄ κ³κΈ°κ° μ무거λ μ‘λ κ²μ΄λ―λ‘ μμ rc=10
|
|
45
|
+
# NEGATIVE λ μ΄ λνΌκ° μ§μ κ²μ¬νλ€(νμ λκ΅¬κ° λͺ¨λ₯΄λ μ’
λ₯λΌ κ·Έ νμ 무μνκ³ μ§λκ°λ€).
|
|
46
|
+
#
|
|
47
|
+
# ββ μ¬μ© ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
48
|
+
# bash scripts/utterance_intake.sh <transcript.jsonl> <record-file> [<record-file>...]
|
|
49
|
+
# env:
|
|
50
|
+
# FH_INTAKE_MIN_CHARS κΈ°λ³Έ 20 β μλ Β§λ¬Έν± μ°Έμ‘°
|
|
51
|
+
# FH_INTAKE_NEG_TOKEN μμ± μ»¨νΈλ‘€ ν ν° κ³ μ (λ μΈμ©). λ―Έμ€μ μ΄λ©΄ λ§€ μ€ν 무μμ
|
|
52
|
+
# FH_INTAKE_DUMP_PROBES μμ±λ probes.tsv λ₯Ό μ΄ κ²½λ‘λ‘λ λ¨κΈ΄λ€(λ μΈ/λλ²κ·Έμ©)
|
|
53
|
+
#
|
|
54
|
+
# ββ Β§λ¬Έν±: μ 40 μ΄ μλλΌ 20 μΈκ° (μ€μΈ‘ 2026-09-05) ββββββββββββββββββββββββ
|
|
55
|
+
# μ΄ λ ν¬ μ μ¬λ³Έ μ μ 3191 λ°ν(λ΄ν¬Β·ν΄κ²°κ³Ό μ μΈ)μ κΈΈμ΄ λΆν¬:
|
|
56
|
+
# 00-09 524 Β· 10-19 329 Β· 20-29 284 Β· 30-39 187 Β· 40+ 1867
|
|
57
|
+
# 30β39 κ΅¬κ° νλ³Έμ νμ€ μ§λ λ°νκ° μ°λ€ β Β«κ·Έλ¦¬κ³ μ
μ₯리뷰λ μ μ λμ λͺ¨λ μνν΄μΌν΄Β» Β·
|
|
58
|
+
# Β«4λ μ΄λ‘μΌλ‘ μ¬λ¦¬λλ‘ νμΒ». νκΈμ μλΉ μ 보λμ΄ μ»€μ **40 μ λ°λ₯μ κ΅λ¦¬ λ°νλ₯Ό μ‘°μ©ν
|
|
59
|
+
# λ²λ¦°λ€**(μνν λ°©ν₯). 20 = 853 μ μΈ Β· 2338 보쑴. κ³Όν¬ν¨μ μμ(μμ ), κ³Όμν¬ν¨μ 무μ μμ€.
|
|
60
|
+
#
|
|
61
|
+
# ββ μ’
λ£μ½λ ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
62
|
+
# 0 μ 건 μ°©μ§ 1 λ―Έμ°©μ§ μμ (λͺ©λ‘ μΈμ)
|
|
63
|
+
# 10 HARNESS-ERROR / UNMEASURED β **PASS λ FAIL λ μλλ€**
|
|
64
|
+
# (μ μ¬λ³Έ λΆμ¬ Β· μΆμΆ λ°ν 0 Β· κΈ°λ‘ νμΌ 0 Β· 컨νΈλ‘€ μ¬λ§ Β· νλ‘λΈ μ€λ₯ Β· rcβλͺ©λ‘ λΆμΌμΉ)
|
|
65
|
+
#
|
|
66
|
+
# μ΄μμ±: bash 3.2 Β· λ°±ν± μμ Β· heredoc μ ν¨μ μ + μΈμ© ꡬλΆμ. python3 νμ€ λΌμ΄λΈλ¬λ¦¬λ§.
|
|
67
|
+
|
|
68
|
+
set -uo pipefail
|
|
69
|
+
|
|
70
|
+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
71
|
+
EXTRACTOR="$REPO_ROOT/scripts/transcript_utterances.py"
|
|
72
|
+
LANDING="$REPO_ROOT/scripts/utterance_landing_check.sh"
|
|
73
|
+
MIN_CHARS="${FH_INTAKE_MIN_CHARS:-20}"
|
|
74
|
+
|
|
75
|
+
_die10() { echo "$1"; exit 10; }
|
|
76
|
+
|
|
77
|
+
# π₯ `grep -c x f || echo 0` μ 무맀μΉμΌ λ Β«0Β» μ **λ λ²** λΈλ€ β grep μ΄ 0 μ μ°κ³ rc=1 μ
|
|
78
|
+
# λ΄λ―λ‘ ν΄λ°±κΉμ§ λΆλλ€. `[ "0\n0" -eq 0 ]` μ Β«integer expression expectedΒ» λ‘ μ£½κ³ ,
|
|
79
|
+
# κ·Έ μ£½μμ΄ κ³κΈ° μ¬λ§μΌλ‘ λ λλλ€(λ μΈμ΄ L1/L4b/L8b μμ μ‘μλ€).
|
|
80
|
+
# `[[feedback_pipefail_fallback_disarms_guard]]` μ μ¬μ΄ β ν΄λ°±μ΄ κ°μ μ€μΌμν€λ νν.
|
|
81
|
+
_num() { case "${1:-}" in ''|*[!0-9]*) echo 0 ;; *) printf '%s' "$1" ;; esac; }
|
|
82
|
+
|
|
83
|
+
# ββ μμ± μ»¨νΈλ‘€ βββββββββββββββββββββββββββββββββββββββββββ
|
|
84
|
+
# π₯ μ΄νμ Β«μ€λ λ μ§Β» λ₯Ό μμ± μ»¨νΈλ‘€λ‘ μΌλ€ β Β«μ€λμ κΈ°λ‘ νμΌμλ μ€λ λ μ§κ°
|
|
85
|
+
# λ°λμ μλ€Β» λ κ°μ μ΄μκ³ , **κ±°μ§μ΄λ€.** λ μ§λ νμΌλͺ
μ μμ§ λ΄μ©μ μμ μλ¬΄κ° μλ€.
|
|
86
|
+
# μ€μΈ‘(2026-09-05 sim): νλ‘μ΄ ν°μ΄ μΈμ
μ΄ λ§λ `fh_completed_2026-09-05.md` λ΄μ©μ
|
|
87
|
+
# Β«- β
κ³κΈ° νμΈ β ν΄λ‘ μ μ°κΈ° κ°λ₯Β» ν μ€λΏμ΄λ€. κ·Έλ¬λ©΄ 컨νΈλ‘€μ΄ μ£½μ΄ rc=10 μ΄ λκ³ ,
|
|
88
|
+
# μ μ λ§κ°μ κ±°μ§ κ²½λ³΄κ° λ¬λ€ β κ·Έκ²μ΄ λ°λ‘ μ€ν€λ°μ νλ ¨μν€λ μμμ΄λ€.
|
|
89
|
+
# β 컨νΈλ‘€μ **κΈ°λ‘ λ΄μ©μμ λ½λλ€**: κ±°κΈ° μ€μ λ‘ μλ κΈ΄ ν ν°μ μ μμ μ‘νμΌ νλ―λ‘,
|
|
90
|
+
# μ μ‘νλ©΄ **νμ΄νλΌμΈμ΄ μ£½μ κ²**(word-split Β· κ²½λ‘ Β· μΈμ½λ©)μ΄λ€ β μλ μ¬λ €λ λ°λ‘ κ·Έ μ€ν¨λ€.
|
|
91
|
+
# λ μ§λ Β«μμ λλ§Β» λ λ²μ§Έ 컨νΈλ‘€λ‘ λνλ€(μμΌλ©΄ λ κ°νκ³ , μμ΄λ μΉλͺ
μ μ΄μ§ μλ€).
|
|
92
|
+
_control_rows() { # $1=μ€λλ μ§ $2=μ€ν¬λμΉ $3...=κΈ°λ‘ νμΌλ€
|
|
93
|
+
local _today="$1" _scratch="$2"; shift 2
|
|
94
|
+
local _py="$_scratch/mkcontrol.py"
|
|
95
|
+
cat > "$_py" <<'CTLPY'
|
|
96
|
+
import re, sys
|
|
97
|
+
|
|
98
|
+
today = sys.argv[1]
|
|
99
|
+
best = ''
|
|
100
|
+
seen_today = False
|
|
101
|
+
for path in sys.argv[2:]:
|
|
102
|
+
try:
|
|
103
|
+
txt = open(path, errors='replace').read()
|
|
104
|
+
except OSError:
|
|
105
|
+
continue
|
|
106
|
+
# degrade-scan C2(substring-boolean) λμ: μ΄ ν¬ν¨κ²μ¬λ **κ°μ°μ **μ΄λ€ β λ§μΌλ©΄ 보쑰
|
|
107
|
+
# 컨νΈλ‘€μ΄ νλ λκ³ , ν리면 μ λ λΏμ΄λ€. νμ€μ μλ `best`(νμ 컨νΈλ‘€)κ° μ§λ€.
|
|
108
|
+
# μ΄λ λ°©ν₯μΌλ‘ νλ €λ PASS λ‘ κΈ°μΈμ§ μμΌλ―λ‘ μ ν μΌμΉλ‘ μ’ν μ΄μ κ° μλ€.
|
|
109
|
+
if today in txt: # noqa: degrade β additive only: ADDS a secondary control row, never grounds a verdict
|
|
110
|
+
seen_today = True
|
|
111
|
+
for tok in re.findall(r'[\uac00-\ud7a3]{4,}|[A-Za-z0-9_]{5,}', txt):
|
|
112
|
+
if len(tok) > len(best):
|
|
113
|
+
best = tok
|
|
114
|
+
if best:
|
|
115
|
+
print("CONTROL\t%s\tκΈ°λ‘νμ(μμ±μ»¨νΈλ‘€)" % best)
|
|
116
|
+
if seen_today:
|
|
117
|
+
print("CONTROL\t%s\tμ€λλ μ§(보쑰컨νΈλ‘€)" % today)
|
|
118
|
+
CTLPY
|
|
119
|
+
python3 "$_py" "$_today" "$@"
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
# ββ ν€ λ±λ§ νλ‘λΈ μμ± ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
123
|
+
# νκΈ/μμ«μ ν ν° μ€ **κ°μ₯ κΈ΄** κ² 3 κ°. μ‘°μ¬λ₯Ό λ²κΈ°κ³ , λΆμ©μ΄μ μ§§μ ν ν°μ λΊλ€.
|
|
124
|
+
# μ κ·μ λ©νλ¬Έμκ° μμ΄μ§ μλλ‘ ν ν° λ¬Έμκ΅°μ [\uac00-\ud7a3]Β·[A-Za-z0-9_] λ‘ **λ«μ λλ€** β
|
|
125
|
+
# `.` `-` λ₯Ό νμ©νλ©΄ ERE μμ κ°κ° μμΌλμΉ΄λ/λ²μλ‘ μκ³ , μ΄μ€μΌμ΄νλ₯Ό μμΌλ‘ νλ μκ°
|
|
126
|
+
# γκ΄λν¨ κ°λ¦° μ€λ³΅ μ κ·νγκ° μμλλ€.
|
|
127
|
+
# π₯ `python3 - <<'PY'` λ₯Ό μ°λ©΄ **heredoc μ΄ python μ stdin μ΄ λμ΄** λ°ν tsv κ° ν΅μ§Έλ‘
|
|
128
|
+
# μ μ½νλ€(첫 νλ³Έμ΄ κ·Έλ¬κ³ , λ μΈ L4b κ° Β« TARGET 0 건 Β» μΌλ‘ μ‘μλ€). νλ‘κ·Έλ¨μ νμΌλ‘
|
|
129
|
+
# λ¨μ΄λ¨λ¦¬κ³ stdin μ νΈμΆλΆμ 리λ€μ΄λ νΈκ° κ·Έλλ‘ λλ₯΄κ² λλ€.
|
|
130
|
+
_build_probes() { # stdin: n<TAB>ts<TAB>text stdout: probes rows $1=map κ²½λ‘ $2=μ€ν¬λμΉ λλ ν°λ¦¬
|
|
131
|
+
local _py="$2/mkprobes.py"
|
|
132
|
+
cat > "$_py" <<'PY'
|
|
133
|
+
import os, re, sys
|
|
134
|
+
|
|
135
|
+
MAP = open(sys.argv[1], 'w')
|
|
136
|
+
|
|
137
|
+
# π₯ μ¬μ€ ν ν° λ§μ€νΉ β **ν ν°ν μ μ** νλ€. μ΄ μΆλ ₯μ λ§κ° νλ©΄μ μ°νκ³ μ¬λμ΄ μΉ΄λμ
|
|
138
|
+
# λΆμ¬λ£λλ€. 첫 μ€μ¬μ©(2026-09-05)μμ μ€μ λ‘ `[Image: source: /var/folders/3h/β¦]` κ°
|
|
139
|
+
# λΌλ²¨λ‘ λμλ€ β λ¨Έμ κ³ μ μμκ²½λ‘λ€. λ§μ€νΉμ λΌλ²¨μλ§ κ±Έλ©΄ ν€ λ±λ§ μͺ½μΌλ‘ μλ―λ‘
|
|
140
|
+
# (μ¬μ©μλͺ
κ°μ κ²μ΄ κ·Έλλ‘ ν ν°μ΄ λλ€) μλ¨μμ μ§μ΄λ€.
|
|
141
|
+
# π₯ νκΈ λ²μλ₯Ό `[\uac00-\ud7a3]` λ‘ μ λλ€ β `[κ°-ν£]` κ³Ό **μλ―Έλ κ°μ§λ§**
|
|
142
|
+
# (νμ΄μ¬ `re` μμ λμΉ, known-pair λ‘ νμΈ), μ΄ νμΌμ `.sh` λΌ
|
|
143
|
+
# `test_card_drift_probe.sh` μ locale-range λ¦°νΈκ° μ€μΊνλ€. κ·Έ λ¦°νΈλ μ
Έμ `grep` μ
|
|
144
|
+
# 겨λ₯ν κ²μ΄κ³ (C λ‘μΌμΌμμ λΉASCII λ²μλ exit 2 β «무맀μΉΒ» λ‘ μ½νλ€), heredoc μμ
|
|
145
|
+
# python μΈμ§ ꡬλΆνμ§ λͺ»νλ€. λ¦°νΈλ₯Ό λλ λμ **νκΈ°λ₯Ό λ°κΎΌλ€** β λ‘μΌμΌ ν¨μ μ λν
|
|
146
|
+
# κ·Έ κ²½κ³λ μ΄μ μμ΄μΌ νλ€.
|
|
147
|
+
# (`transcript_utterances.py` λ `.sh` κ° μλλΌ μ€μΊ λμμ΄ μλλ―λ‘ κ°λ
μ± νκΈ°λ₯Ό μ΄λ€.)
|
|
148
|
+
_HOME = os.environ.get('HOME', '')
|
|
149
|
+
_TMP_RE = re.compile(r'/(?:var/folders|private/var/folders)/[^\s\]\)]*')
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
_PATH_RE = re.compile(r'(?<![\w])/(?:[\w.@+-]+/)+[\w.@+-]*') # any absolute path, 2+ segments
|
|
153
|
+
_KV_RE = re.compile(r'\b([A-Z][A-Z0-9_]{2,})=[^\s]+') # KEY=value β KEY=<v>
|
|
154
|
+
_HOST_RE = re.compile(r'\b[a-z0-9-]+(?:\.[a-z0-9-]+)*\.(?:internal|local|corp|lan|intra)\b')
|
|
155
|
+
def mask(t):
|
|
156
|
+
# Labels and probe tokens are an OUTPUT surface (close-check stdout, pasted into cards) β so
|
|
157
|
+
# the masking is not "the operator's own paths" but every absolute path, every KEY=value and
|
|
158
|
+
# every host-like name (codex finding 5, 2026-09-05). The suffix list is a small frozen
|
|
159
|
+
# judgment; it fails loud (an unmasked host shows up in a label), not silent.
|
|
160
|
+
t = _TMP_RE.sub('<tmp>', t)
|
|
161
|
+
if _HOME and len(_HOME) > 4:
|
|
162
|
+
t = t.replace(_HOME, '$HOME')
|
|
163
|
+
t = _PATH_RE.sub('<path>', t)
|
|
164
|
+
t = _KV_RE.sub(r'\1=<v>', t)
|
|
165
|
+
t = _HOST_RE.sub('<host>', t)
|
|
166
|
+
return t
|
|
167
|
+
|
|
168
|
+
# μ‘°μ¬/μ΄λ―Έ β ν ν° λμμ λ²κΈ΄λ€. κΈ΄ κ²λΆν° μλνλ€(μ§§μ κ² λ¨Όμ λ§μΌλ©΄ λ λ²κ²¨μ§λ€).
|
|
169
|
+
PARTICLES = ['μΌλ‘λ', 'μμλ', 'μκ²λ', 'μ΄λΌκ³ ', 'μΌλ‘λ', 'κΉμ§λ', 'λΆν°λ',
|
|
170
|
+
'μΌλ‘', 'μμ', 'μκ²', 'νν
', 'μ²λΌ', '보λ€', 'λΌκ³ ', 'νκ³ ', 'κΉμ§',
|
|
171
|
+
'λΆν°', 'μ΄λ', 'λ μ§', 'λ§λ€', 'μ‘°μ°¨', 'λ°μ', 'μ΄λ', 'μ΄λ ',
|
|
172
|
+
'μ', 'λ', 'μ΄', 'κ°', 'μ', 'λ₯Ό', 'μ', 'μ', 'λ‘', 'λ', 'λ§', 'κ³Ό', 'μ']
|
|
173
|
+
|
|
174
|
+
# λΆμ©μ΄ β μ΄ μ½νΌμ€μμ νν΄μ Β«μ°μ° μΌμΉΒ» λ₯Ό λ§λλ κ²λ€. OR κ²°ν©μ΄λΌ μ¬κΈ°κ° fail-open μ
|
|
175
|
+
# μ£Όλ μ
ꡬλ€(Β§λͺ
λͺ
λ μμ¬ β). κΈΈμ΄ κ·μΉλ§μΌλ‘λ μ κ±Έλ¬μ§λ€.
|
|
176
|
+
STOP = set('''κ·Έλ¦¬κ³ κ·Έλ¬λκΉ κ·Έλμ κ·Έλ¬λ©΄ κ·Έλ°λ° μ΄λ κ² κ·Έλ κ² μ΄λ»κ² νμ§λ§ κ·Έλ¬λ
|
|
177
|
+
λ§μ°¬κ°μ§ μ΄μΌκΈ° μκ° κ²½μ° λΆλΆ λ΄μ© νμΈ μ§ν μμ
νμΌ μΈμ
μ§κΈ λ€μ μ΄λ² μ λ
|
|
178
|
+
μ¬λ λ°©λ² μ΄μ μν κ²°κ³Ό λ¬Έμ λν΄ μν΄ ν΅ν΄ μ°λ¦¬ λλ λλ κ²μ κ²μ΄ κ±°κΈ° μ¬κΈ° μ κΈ°
|
|
179
|
+
ν΄μ€ νμ νλ λλ μλ μλ κ°μ κ°μ΄ μ’μ λ€μ λ¨Όμ μμ§ μ΄μ μ‘°κΈ κ·Έλ₯ λ°λ‘
|
|
180
|
+
this that with from have been will your about would should could there which their
|
|
181
|
+
path host tmp home HOME
|
|
182
|
+
then than when what where because those these into over under more most only just'''.split())
|
|
183
|
+
|
|
184
|
+
def strip_particle(tok):
|
|
185
|
+
if not re.match(r'^[\uac00-\ud7a3]+$', tok):
|
|
186
|
+
return tok
|
|
187
|
+
for p in PARTICLES:
|
|
188
|
+
if tok.endswith(p) and len(tok) - len(p) >= 2:
|
|
189
|
+
return tok[:-len(p)]
|
|
190
|
+
return tok
|
|
191
|
+
|
|
192
|
+
n_rows = 0
|
|
193
|
+
n_unprobeable = 0
|
|
194
|
+
for line in sys.stdin:
|
|
195
|
+
line = line.rstrip('\n')
|
|
196
|
+
if not line:
|
|
197
|
+
continue
|
|
198
|
+
parts = line.split('\t', 2)
|
|
199
|
+
if len(parts) < 3:
|
|
200
|
+
continue
|
|
201
|
+
idx, _ts, text = parts
|
|
202
|
+
text = mask(text)
|
|
203
|
+
toks = re.findall(r'[\uac00-\ud7a3]+|[A-Za-z0-9_]+', text)
|
|
204
|
+
cand = []
|
|
205
|
+
seen = set()
|
|
206
|
+
for t in toks:
|
|
207
|
+
s = strip_particle(t)
|
|
208
|
+
if s in seen or s in STOP or s.lower() in STOP:
|
|
209
|
+
continue
|
|
210
|
+
if re.match(r'^[\uac00-\ud7a3]+$', s):
|
|
211
|
+
if len(s) < 3:
|
|
212
|
+
continue
|
|
213
|
+
else:
|
|
214
|
+
if len(s) < 4 or s.isdigit():
|
|
215
|
+
continue
|
|
216
|
+
seen.add(s)
|
|
217
|
+
cand.append(s)
|
|
218
|
+
# κ°μ₯ κΈ΄ κ²λΆν° 3 κ°. λλ₯ μ λ¨Όμ λμ¨ κ²(λ°ν μμͺ½μ΄ λκ° μ£Όμ μ΄).
|
|
219
|
+
cand.sort(key=lambda s: -len(s))
|
|
220
|
+
keys = cand[:3]
|
|
221
|
+
if not keys:
|
|
222
|
+
# νλ‘λΈλ₯Ό λ§λ€ μ μλ λ°ν β **λ²λ¦¬μ§ μκ³ μΌλ€**(not found != 0).
|
|
223
|
+
n_unprobeable += 1
|
|
224
|
+
MAP.write("%s\tUNPROBEABLE\t%s\t0\n" % (idx, text[:60]))
|
|
225
|
+
continue
|
|
226
|
+
label = "#%s %s" % (idx, text[:40])
|
|
227
|
+
# One TARGET row per key: `#<idx>.<k>/<n> <text>` β the wrapper re-aggregates per utterance
|
|
228
|
+
# (codex finding 1). Keys are bare tokens ([κ°-ν£]+ / [A-Za-z0-9_]+): no ERE metacharacters,
|
|
229
|
+
# never dash-first, so the landing checker reads them as literals.
|
|
230
|
+
for k_i, key in enumerate(keys, 1):
|
|
231
|
+
print("TARGET\t%s\t#%s.%d/%d %s" % (key, idx, k_i, len(keys), text[:36]))
|
|
232
|
+
MAP.write("%s\t%s\t%s\t%d\n" % (idx, label, text[:60], len(keys)))
|
|
233
|
+
n_rows += 1
|
|
234
|
+
MAP.close()
|
|
235
|
+
sys.stderr.write("probe utterances=%d Β· νλ‘λΈλΆκ°=%d\n" % (n_rows, n_unprobeable))
|
|
236
|
+
PY
|
|
237
|
+
python3 "$_py" "$1"
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
# ββ μΈμ ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
241
|
+
if [ "$#" -lt 2 ]; then
|
|
242
|
+
echo "usage: $0 <transcript.jsonl> <record-file> [<record-file>...]" >&2
|
|
243
|
+
exit 10
|
|
244
|
+
fi
|
|
245
|
+
TRANSCRIPT="$1"; shift
|
|
246
|
+
RECORDS=() # λ°°μ΄ β zsh word-split μ μμ‘΄νμ§ μλλ€
|
|
247
|
+
MISSING_RECORDS=0
|
|
248
|
+
for f in "$@"; do
|
|
249
|
+
if [ -f "$f" ]; then RECORDS+=("$f"); else MISSING_RECORDS=$((MISSING_RECORDS+1)); fi
|
|
250
|
+
done
|
|
251
|
+
|
|
252
|
+
[ -f "$EXTRACTOR" ] || _die10 "π₯ HARNESS-ERROR: μΆμΆκΈ° μμ: $EXTRACTOR"
|
|
253
|
+
[ -f "$LANDING" ] || _die10 "π₯ HARNESS-ERROR: μ°©μ§ κ²μ¬κΈ° μμ: $LANDING"
|
|
254
|
+
|
|
255
|
+
if [ ! -f "$TRANSCRIPT" ]; then
|
|
256
|
+
echo "βΉοΈ UNMEASURED β μ μ¬λ³Έ λΆμ¬: $TRANSCRIPT"
|
|
257
|
+
# π₯ μ±κ³΅ νμ 리ν°λ΄(Β«μ λΆ μ°©μ§Β»)μ **λΆμ λ¬Έ μμλ** μ°μ§ λ§λΌ β grep νλ μͺ½μ΄
|
|
258
|
+
# μ±κ³΅μΌλ‘ μ½λλ€(Grep-Collision Treadmill). λ μΈ L7c κ° μ€μ λ‘ κ·Έλ κ² μ€λ
νλ€.
|
|
259
|
+
echo " λΆμ¬λ₯Ό 0 μΌλ‘ μ μ§ λ§λΌ β λ°νκ° μμλ€λ λ»λ, λ€ μ νλ€λ λ»λ μλλ€."
|
|
260
|
+
exit 10
|
|
261
|
+
fi
|
|
262
|
+
if [ "${#RECORDS[@]}" -eq 0 ]; then
|
|
263
|
+
echo "βΉοΈ UNMEASURED β κ²μ¬ν κΈ°λ‘ νμΌμ΄ νλλ μλ€ (μΈμ $# 건 μ€ $MISSING_RECORDS 건 λΆμ¬)"
|
|
264
|
+
echo " κΈ°λ‘μ΄ μλ κ²κ³Ό λ°νκ° μ μ ν κ²μ λ€λ₯Έ λͺ
μ λ€."
|
|
265
|
+
exit 10
|
|
266
|
+
fi
|
|
267
|
+
|
|
268
|
+
WORK="$(mktemp -d 2>/dev/null)" || _die10 "π₯ HARNESS-ERROR: mktemp -d μ€ν¨ (νκ²½ λΆμ¬μ΄μ§ κ³κΈ° κ²°ν¨μ΄ μλλ€)"
|
|
269
|
+
trap 'rm -rf "$WORK"' EXIT INT TERM
|
|
270
|
+
|
|
271
|
+
# ββ 1. λ°ν μΆμΆ ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
272
|
+
# `--strip-system-markers`: peer μλ¦Ό Β· λ¬΄μΈ μ€ν Β· μ΄λ―Έμ§ λ§μ»€λ **μ΄μμ λ°νκ° μλλ€**.
|
|
273
|
+
# seal μ μ΄ νλκ·Έλ₯Ό μ μ£Όλ―λ‘ κΈ°μ‘΄ μμ₯ μΆλ ₯μ κ·Έλλ‘λ€(λ μΈ L10 골λ μ΄ κ·Έκ±Έ κ³ μ νλ€).
|
|
274
|
+
python3 "$EXTRACTOR" "$TRANSCRIPT" --min-chars "$MIN_CHARS" --strip-system-markers \
|
|
275
|
+
> "$WORK/utts.tsv" 2> "$WORK/extract.err"
|
|
276
|
+
_erc=$?
|
|
277
|
+
if [ "$_erc" -ne 0 ]; then
|
|
278
|
+
echo "π₯ HARNESS-ERROR: λ°ν μΆμΆ μ€ν¨ (rc=$_erc)"; sed 's/^/ /' "$WORK/extract.err"; exit 10
|
|
279
|
+
fi
|
|
280
|
+
N_UTT=$(_num "$(grep -c . "$WORK/utts.tsv" 2>/dev/null)")
|
|
281
|
+
if [ "${N_UTT:-0}" -eq 0 ]; then
|
|
282
|
+
echo "βΉοΈ UNMEASURED β μΆμΆλ λ°ν 0 건 (min-chars=$MIN_CHARS)"
|
|
283
|
+
sed 's/^/ /' "$WORK/extract.err"
|
|
284
|
+
echo " λΉ νλ‘λΈ μΈνΈλ ν©κ²©μ΄ μλλ€ β Β«μ무κ²λ μ μλ€Β» λ€."
|
|
285
|
+
exit 10
|
|
286
|
+
fi
|
|
287
|
+
|
|
288
|
+
# ββ 2. νλ‘λΈ μμ± (컨νΈλ‘€ 2 μ’
λ¨Όμ ) βββββββββββββββββββββββββββββββββββββββ
|
|
289
|
+
TODAY="$(date +%Y-%m-%d)"
|
|
290
|
+
NEG_TOKEN="${FH_INTAKE_NEG_TOKEN:-}"
|
|
291
|
+
if [ -z "$NEG_TOKEN" ]; then
|
|
292
|
+
# 무μμ. `date +%N` μ BSD μ μμΌλ―λ‘ μ°μ§ μλλ€.
|
|
293
|
+
NEG_TOKEN="FHNEG$(od -An -N6 -tx1 /dev/urandom 2>/dev/null | tr -d ' \n' | tr 'a-f' 'A-F')"
|
|
294
|
+
case "$NEG_TOKEN" in FHNEG) NEG_TOKEN="FHNEG$$X$(date +%s)" ;; esac
|
|
295
|
+
fi
|
|
296
|
+
{
|
|
297
|
+
_control_rows "$TODAY" "$WORK" "${RECORDS[@]}"
|
|
298
|
+
printf 'NEGATIVE\t%s\t무μμν ν°(μμ±μ»¨νΈλ‘€)\n' "$NEG_TOKEN"
|
|
299
|
+
} > "$WORK/probes.tsv"
|
|
300
|
+
_build_probes "$WORK/probes.map" "$WORK" < "$WORK/utts.tsv" >> "$WORK/probes.tsv" 2> "$WORK/build.err"
|
|
301
|
+
[ -n "${FH_INTAKE_DUMP_PROBES:-}" ] && cp "$WORK/probes.tsv" "$FH_INTAKE_DUMP_PROBES" 2>/dev/null
|
|
302
|
+
N_TARGET=$(_num "$(grep -c "^TARGET$(printf '\t')" "$WORK/probes.tsv" 2>/dev/null)")
|
|
303
|
+
N_PROBED=$(_num "$(LC_ALL=C awk -F'\t' '$2!="UNPROBEABLE"' "$WORK/probes.map" 2>/dev/null | grep -c .)")
|
|
304
|
+
UNPROBE=$(_num "$(LC_ALL=C awk -F'\t' '$2=="UNPROBEABLE"' "$WORK/probes.map" 2>/dev/null | grep -c .)")
|
|
305
|
+
if [ "${N_TARGET:-0}" -eq 0 ]; then
|
|
306
|
+
echo "βΉοΈ UNMEASURED β λ°ν $N_UTT 건μμ νλ‘λΈλ₯Ό νλλ λͺ» λ§λ€μλ€"
|
|
307
|
+
sed 's/^/ /' "$WORK/build.err"
|
|
308
|
+
exit 10
|
|
309
|
+
fi
|
|
310
|
+
# μμ± μ»¨νΈλ‘€μ΄ νλλ μ λμμΌλ©΄(κΈ°λ‘μ΄ μ§§μ 4μ+ νκΈ / 5μ+ μμ«μ ν ν°μ΄ μλ€) κ³κΈ°λ₯Ό λ리μ§
|
|
311
|
+
# μλλ€ β μ°©μ§ κ²μ¬κΈ°λ CONTROL λΆμ¬μ rc=10 μ λ΄μ§λ§, κ·Έ λ©μμ§λ Β«κ³κΈ° μ¬λ§Β» μ΄κ³ μ¬κΈ° μμΈμ
|
|
312
|
+
# «컨νΈλ‘€μ λ§λ€ μ¬λ£κ° μλ€Β» λΌ λ€λ₯΄λ€(codex finding 7). λ λ€ UNMEASURED λ€.
|
|
313
|
+
N_CTL=$(_num "$(grep -c "^CONTROL$(printf '\t')" "$WORK/probes.tsv" 2>/dev/null)")
|
|
314
|
+
if [ "${N_CTL:-0}" -eq 0 ]; then
|
|
315
|
+
echo "βΉοΈ UNMEASURED β κΈ°λ‘μμ μμ± μ»¨νΈλ‘€ ν ν°μ λͺ» λ½μλ€(4μ+ νκΈ Β· 5μ+ μμ«μ ν ν° λΆμ¬, μ€λ λ μ§λ μμ)"
|
|
316
|
+
echo " 컨νΈλ‘€ μλ μ°©μ§ νμ μ κ·Όκ±°κ° μλλ€ β κΈ°λ‘μ΄ λ무 μ§§λ€."
|
|
317
|
+
exit 10
|
|
318
|
+
fi
|
|
319
|
+
|
|
320
|
+
# ββ 3. μμ± μ»¨νΈλ‘€ β νμ λκ΅¬κ° λͺ¨λ₯΄λ μ’
λ₯λΌ μ¬κΈ°μ κ²μ¬νλ€ βββββββββββββ
|
|
321
|
+
# `grep -F` : ν ν°μ 리ν°λ΄λ‘ λ³Έλ€. μ κ·μμΌλ‘ ν΄μλλ©΄ Β«μμ΄μΌ ν κ²Β» μ μλ―Έκ° λ°λλ€.
|
|
322
|
+
if grep -qF -- "$NEG_TOKEN" "${RECORDS[@]}" 2>/dev/null; then
|
|
323
|
+
echo "π₯ HARNESS-ERROR: μμ± μ»¨νΈλ‘€μ΄ κΈ°λ‘μμ μ‘νλ€ β [$NEG_TOKEN]"
|
|
324
|
+
echo " μμ΄μΌ λ§λ
ν ν ν°μ΄ μ‘νλ€ = κ³κΈ°κ° μ무거λ μ‘κ³ μλ€. νκΉ κ²°κ³Όλ μΈμνμ§ μλλ€."
|
|
325
|
+
exit 10
|
|
326
|
+
fi
|
|
327
|
+
|
|
328
|
+
# ββ 4. μ°©μ§ κ²μ¦ (μμ± μ»¨νΈλ‘€μ νμ λκ΅¬κ° λ³Έλ€) βββββββββββββββββββββββββββ
|
|
329
|
+
LAND_OUT="$WORK/land.txt"
|
|
330
|
+
bash "$LANDING" "$WORK/probes.tsv" "${RECORDS[@]}" > "$LAND_OUT" 2>&1
|
|
331
|
+
LRC=$?
|
|
332
|
+
|
|
333
|
+
# π₯ νμ μ **μ’
λ£μ½λ**λ‘ λΈλ€. μλ νμ±μ Β«μ¬λμ΄ μ½μ λͺ©λ‘Β» μ λ§λ€ λΏμ΄κ³ , λμ΄ μ΄κΈλλ©΄
|
|
334
|
+
# κ·Έ μμ²΄κ° μ νΈλ€(`[[feedback_summary_parsing_hides_failure]]` Β· λ μ νΈμ μ΄κΈλ¨).
|
|
335
|
+
ROW_MISS=$(_num "$(grep -c 'λ―Έμ°©μ§$' "$LAND_OUT" 2>/dev/null)")
|
|
336
|
+
# λ°ν λ¨μ μ§κ³ β ν(ν ν°)μ μ°©μ§/λ―Έμ°©μ§λ₯Ό `#idx.k/n` λΌλ²¨λ‘ λ°νμ λλλ¦¬κ³ , βn/2β μ΄μ μ°©μ§λ©΄
|
|
337
|
+
# κ·Έ λ°νλ μ°©μ§λ€. μΆλ ₯: LANDED / UNLANDED κ°μ + λ―Έμ°©μ§ λ°ν idx λͺ©λ‘.
|
|
338
|
+
_AGG="$WORK/agg.py"
|
|
339
|
+
cat > "$_AGG" <<'AGGPY'
|
|
340
|
+
import re, sys
|
|
341
|
+
land, mapf = sys.argv[1], sys.argv[2]
|
|
342
|
+
n_keys = {}
|
|
343
|
+
for line in open(mapf, errors='replace'):
|
|
344
|
+
parts = line.rstrip('\n').split('\t')
|
|
345
|
+
if len(parts) >= 4 and parts[1] != 'UNPROBEABLE':
|
|
346
|
+
n_keys[parts[0]] = int(parts[3])
|
|
347
|
+
hits = {}
|
|
348
|
+
row_re = re.compile(r'^\s*(β
|π₯)\s+#(\d+)\.(\d+)/(\d+)\s')
|
|
349
|
+
for line in open(land, errors='replace'):
|
|
350
|
+
m = row_re.match(line)
|
|
351
|
+
if not m:
|
|
352
|
+
continue
|
|
353
|
+
idx = m.group(2)
|
|
354
|
+
n_keys.setdefault(idx, int(m.group(4)))
|
|
355
|
+
if m.group(1) == 'β
':
|
|
356
|
+
hits[idx] = hits.get(idx, 0) + 1
|
|
357
|
+
landed, unlanded = [], []
|
|
358
|
+
for idx in sorted(n_keys, key=int):
|
|
359
|
+
n = n_keys[idx]
|
|
360
|
+
need = (n + 1) // 2
|
|
361
|
+
(landed if hits.get(idx, 0) >= need else unlanded).append(idx)
|
|
362
|
+
print("LANDED=%d" % len(landed))
|
|
363
|
+
print("UNLANDED=%d" % len(unlanded))
|
|
364
|
+
print("UNLANDED_IDX=%s" % " ".join(unlanded))
|
|
365
|
+
AGGPY
|
|
366
|
+
_AGG_OUT="$(python3 "$_AGG" "$LAND_OUT" "$WORK/probes.map" 2>/dev/null)"
|
|
367
|
+
HIT_N=$(_num "$(printf '%s\n' "$_AGG_OUT" | sed -n 's/^LANDED=//p')")
|
|
368
|
+
MISS_N=$(_num "$(printf '%s\n' "$_AGG_OUT" | sed -n 's/^UNLANDED=//p')")
|
|
369
|
+
MISS_IDX="$(printf '%s\n' "$_AGG_OUT" | sed -n 's/^UNLANDED_IDX=//p')"
|
|
370
|
+
|
|
371
|
+
echo "ββ λ°ν μ°©μ§ κ²μ¬ ββ"
|
|
372
|
+
echo " μ μ¬λ³Έ : $TRANSCRIPT"
|
|
373
|
+
echo " κΈ°λ‘ λμ: ${#RECORDS[@]} νμΌ$([ "$MISSING_RECORDS" -gt 0 ] && printf ' (λΆμ¬ %s 건 μ μΈ)' "$MISSING_RECORDS")"
|
|
374
|
+
echo " μΆμΆ : λ°ν $N_UTT 건 (min-chars=$MIN_CHARS) β νλ‘λΈ λ°ν $N_PROBED 건 (ν ν° ν $N_TARGET) Β· νλ‘λΈλΆκ° $UNPROBE 건"
|
|
375
|
+
sed 's/^/ /' "$WORK/build.err" 2>/dev/null
|
|
376
|
+
sed 's/^/ /' "$WORK/extract.err" 2>/dev/null
|
|
377
|
+
|
|
378
|
+
case "$LRC" in
|
|
379
|
+
10)
|
|
380
|
+
echo
|
|
381
|
+
sed 's/^/ /' "$LAND_OUT"
|
|
382
|
+
echo " π₯ κ³κΈ° μ¬λ§ β μ°©μ§ νμ μ λ΄μ§ μλλ€(μ£½μ κ³κΈ°μ μΆλ ₯μ λ°μ΄ν°κ° μλλ€)."
|
|
383
|
+
exit 10 ;;
|
|
384
|
+
esac
|
|
385
|
+
|
|
386
|
+
if [ "$LRC" -eq 0 ] && [ "$ROW_MISS" -ne 0 ]; then
|
|
387
|
+
echo "π₯ HARNESS-ERROR: rc=0 μΈλ° λ―Έμ°©μ§ νμ΄ $ROW_MISS κ° β λ μ νΈκ° μ΄κΈλλ€. μλ¬Έ:"
|
|
388
|
+
sed 's/^/ /' "$LAND_OUT"; exit 10
|
|
389
|
+
fi
|
|
390
|
+
if [ "$LRC" -eq 1 ] && [ "$ROW_MISS" -eq 0 ]; then
|
|
391
|
+
echo "π₯ HARNESS-ERROR: rc=1 μΈλ° λ―Έμ°©μ§ νμ΄ 0 κ° β λ μ νΈκ° μ΄κΈλλ€. μλ¬Έ:"
|
|
392
|
+
sed 's/^/ /' "$LAND_OUT"; exit 10
|
|
393
|
+
fi
|
|
394
|
+
if [ $((HIT_N + MISS_N)) -ne "$N_PROBED" ]; then
|
|
395
|
+
echo "π₯ HARNESS-ERROR: λ°ν μ§κ³ $HIT_N+$MISS_N β νλ‘λΈ λ°ν $N_PROBED β μ§κ³κΈ°κ° νμ μμλ€. μλ¬Έ:"
|
|
396
|
+
sed 's/^/ /' "$LAND_OUT"; exit 10
|
|
397
|
+
fi
|
|
398
|
+
|
|
399
|
+
echo " μ°©μ§ $HIT_N Β· λ―Έμ°©μ§ $MISS_N Β· νλ‘λΈλΆκ° $UNPROBE (ν: μ°©μ§ $((N_TARGET - ROW_MISS)) Β· λ―Έμ°©μ§ $ROW_MISS Β· νμ = λ°ν ν ν° βn/2β μ΄μ)"
|
|
400
|
+
if [ "$MISS_N" -gt 0 ]; then
|
|
401
|
+
echo
|
|
402
|
+
echo " λ―Έμ°©μ§ λͺ©λ‘ (λ°ν μ 60μ):"
|
|
403
|
+
# λΌλ²¨μ `#<n>` μΌλ‘ map μ λμ§λλ€. μ«μ λΉκ΅λΌ λΉASCII λλ±μ± ν¨μ μ μ κ±Έλ¦°λ€
|
|
404
|
+
# (`[[feedback_locale_string_equality_breaks_nonascii]]`).
|
|
405
|
+
for _k in $MISS_IDX; do
|
|
406
|
+
LC_ALL=C awk -F'\t' -v k="$_k" '$1==k{printf " Β· %s\n", $3}' "$WORK/probes.map"
|
|
407
|
+
done
|
|
408
|
+
fi
|
|
409
|
+
# νλ‘λΈλΆκ° λ°νλ Β«λͺ» μλ€Β» μ§ Β«μ°©μ§Β» κ° μλλ€ β λ―Έμ°©μ§μ λ³κ°λ‘ μΈλ rc μλ κ°μ΄ λ λ€
|
|
410
|
+
# (codex finding 2: MISS_N=0 μ΄λ©΄ μ‘°μ©ν rc=0 μ΄λ κ²μ λ«μ). μ¬λμ΄ νμΈν λͺ©λ‘μΌλ‘ λΈλ€.
|
|
411
|
+
if [ "${UNPROBE:-0}" -gt 0 ]; then
|
|
412
|
+
echo
|
|
413
|
+
echo " νλ‘λΈλΆκ° $UNPROBE 건 (ν€ λ±λ§μ λͺ» λ½μ λ°ν β λ―ΈμΈ‘μ , μ°©μ§ μλ. λμΌλ‘ νμΈ):"
|
|
414
|
+
LC_ALL=C awk -F'\t' '$2=="UNPROBEABLE"{printf " Β· %s\n", $3}' "$WORK/probes.map"
|
|
415
|
+
fi
|
|
416
|
+
if [ "$MISS_N" -gt 0 ] || [ "${UNPROBE:-0}" -gt 0 ]; then
|
|
417
|
+
echo
|
|
418
|
+
echo " β οΈ rc=1 μ Β«κΈ°λ‘νλΌΒ» κ° μλλΌ Β«νμΈνλΌΒ» λ€ β νλ‘λΈ λ
Ένκ° μ²« λ²μ§Έ μμ¬ λμμ΄λ€."
|
|
419
|
+
echo " κ°μ₯ μ λΉ μ§λ κ²μ μμ λ°νκ° μλλΌ **νλμΌλ‘ λμν λ°ν**λ€."
|
|
420
|
+
exit 1
|
|
421
|
+
fi
|
|
422
|
+
echo " β
νλ‘λΈ λ°ν $N_PROBED 건 μ λΆ μ°©μ§ (λ°νλ³ μ΅μ₯ ν ν° β€3 μ€ βn/2β μ΄μ μΌμΉ)"
|
|
423
|
+
echo " β οΈ ν€ 2 κ° μ΄ν λ°νλ νλλ§ κ²Ήμ³λ μ°©μ§λ€ β Β«μ λλ‘ μ νλ€Β» μ μ¦λͺ
μ΄ μλλ€."
|
|
424
|
+
exit 0
|
|
@@ -132,7 +132,7 @@ done
|
|
|
132
132
|
_probe_hits() {
|
|
133
133
|
local pat="$1"; shift
|
|
134
134
|
grep -lE -- "$pat" "$@" 2>/dev/null
|
|
135
|
-
return "${PIPESTATUS[0]:-$?}"
|
|
135
|
+
return "${PIPESTATUS[0]:-$?}" # portability-noqa: shebang (line 1) pins bash β PIPESTATUS is a real array there, unlike zsh
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
# ββ 1λ¨κ³: 컨νΈλ‘€. μ΄μμμμ μ¦λͺ
νκΈ° μ μλ νκΉμ μΈμνμ§ μλλ€ ββ
|
|
@@ -179,7 +179,7 @@ MISS=0; N=0
|
|
|
179
179
|
while IFS=$'\t' read -r kind pat label; do
|
|
180
180
|
[ "${kind:-}" = "TARGET" ] || continue
|
|
181
181
|
N=$((N+1))
|
|
182
|
-
hits=$(_probe_hits "$pat" "${FILES[@]}" | wc -l | tr -d ' '); _rc=${PIPESTATUS[0]}
|
|
182
|
+
hits=$(_probe_hits "$pat" "${FILES[@]}" | wc -l | tr -d ' '); _rc=${PIPESTATUS[0]} # portability-noqa: shebang (line 1) pins bash β PIPESTATUS is a real array there, unlike zsh
|
|
183
183
|
if [ "$_rc" -ge 2 ]; then
|
|
184
184
|
printf " π₯ %-46s νλ‘λΈ μ€λ₯\n" "${label:-$pat}"
|
|
185
185
|
BAD_PAT="${BAD_PAT:+$BAD_PAT, }TARGET/${label:-$pat}"
|
package/scripts/validate_yaml.sh
CHANGED
|
@@ -133,6 +133,33 @@ if [ "$_skills" -eq 0 ] || [ "$_agents" -eq 0 ]; then
|
|
|
133
133
|
fi
|
|
134
134
|
echo " (scanned: $_skills skill(s), $_agents agent(s))"
|
|
135
135
|
|
|
136
|
+
# ββ Whole-file YAML documents that machinery READS (not frontmatter) βββββββββββββββββββββββββ
|
|
137
|
+
# The dispatch ledger is parsed by session_close_check β£-e / activity_log / round/gatecheck_qset and
|
|
138
|
+
# by the 60/40 promotion gate. Measured 2026-09-04: PR #611 merged an entry whose unquoted scalar
|
|
139
|
+
# carried `: ` (`β¦ API error: 529Β»`) and the file had been UNPARSEABLE at HEAD since β while its own
|
|
140
|
+
# marker cited "validate_yaml λ μΈ" as the live control. That lane never read this file: it checks
|
|
141
|
+
# SKILL/agent FRONTMATTER only. This block closes that gap for the files listed here. Known pair at
|
|
142
|
+
# wiring time: the #611 HEAD ledger β β (line 3090), the quoted fix β β
.
|
|
143
|
+
_LEDGERS="knowledge/shared/learnings/subagent_invocations_log.yaml"
|
|
144
|
+
for f in $_LEDGERS; do
|
|
145
|
+
[ -f "$f" ] || { echo " β ledger missing: $f (not found β empty)"; ERRORS=$((ERRORS + 1)); continue; }
|
|
146
|
+
if [ -z "$PARSER" ]; then
|
|
147
|
+
echo " β οΈ $f: UNCALIBRATED β no YAML parser, NOT verified"; UNCALIBRATED=$((UNCALIBRATED + 1)); continue
|
|
148
|
+
fi
|
|
149
|
+
out=$(python3 - "$f" <<'PY' 2>&1
|
|
150
|
+
import sys, yaml
|
|
151
|
+
try:
|
|
152
|
+
d = yaml.safe_load(open(sys.argv[1], encoding='utf-8'))
|
|
153
|
+
except yaml.YAMLError as e:
|
|
154
|
+
print('PARSE ' + str(e).replace('\n', ' ')[:160]); sys.exit(1)
|
|
155
|
+
if not isinstance(d, list) or not d:
|
|
156
|
+
print('NOTLIST-OR-EMPTY'); sys.exit(2)
|
|
157
|
+
print('OK %d entries' % len(d))
|
|
158
|
+
PY
|
|
159
|
+
); rc=$?
|
|
160
|
+
if [ "$rc" -eq 0 ]; then echo " β
$f: $out"; else echo " β $f: $out"; ERRORS=$((ERRORS + 1)); fi
|
|
161
|
+
done
|
|
162
|
+
|
|
136
163
|
echo ""
|
|
137
164
|
if [ "$UNCALIBRATED" -gt 0 ]; then
|
|
138
165
|
echo " β οΈ UNCALIBRATED β $UNCALIBRATED skill(s) unverified (no parser). Not a pass."
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# worktree_reclaim.sh β reclaim gitignored `tracks/**` artifacts from a git worktree BEFORE it is removed.
|
|
3
|
+
#
|
|
4
|
+
# WHY (N=2, so a script β 2026-09-02 signal + 2026-09-05 push-zone dispatch): the Destructive-Op gate
|
|
5
|
+
# enumerates COMMITS, and `tracks/**` is gitignored, so a worktree removal can delete a session's only
|
|
6
|
+
# copy of a signal file / governance log / dispatch report without any gate ever seeing it. Both
|
|
7
|
+
# incidents lost real files (51-line signal Β· a day's governance log). The manual recipe that replaced
|
|
8
|
+
# them β `diff -rq <wt>/tracks tracks | grep "^Only in <wt>"` β list to a FILE β copy β only then
|
|
9
|
+
# remove β is exactly what this script does, in that order, because after the copy Β«never existedΒ»
|
|
10
|
+
# and Β«already movedΒ» are indistinguishable (the list file is the evidence).
|
|
11
|
+
#
|
|
12
|
+
# USAGE
|
|
13
|
+
# bash scripts/worktree_reclaim.sh <worktree-path> # enumerate only (writes the list file)
|
|
14
|
+
# bash scripts/worktree_reclaim.sh <worktree-path> --apply # enumerate + copy + verify
|
|
15
|
+
#
|
|
16
|
+
# EXIT 0 = nothing to reclaim, or everything reclaimed and byte-verified
|
|
17
|
+
# 1 = files exist on BOTH sides with different content β a human must merge (listed, not copied)
|
|
18
|
+
# 2 = usage / not a worktree of a main checkout
|
|
19
|
+
# 10 = harness error (list file unwritable, copy failed verification)
|
|
20
|
+
#
|
|
21
|
+
# NEVER removes the worktree. Removal stays an explicit `git worktree remove` after this exits 0.
|
|
22
|
+
set -uo pipefail
|
|
23
|
+
# bash 3.2 + `set -u`: expanding an EMPTY array with "${a[@]}" is a fatal unbound-variable error, so every
|
|
24
|
+
# array loop below uses the ${a[@]+"${a[@]}"} idiom (measured on the first fixture run, 2026-09-05).
|
|
25
|
+
|
|
26
|
+
WT="${1:-}"; MODE="${2:-}"
|
|
27
|
+
[ -n "$WT" ] || { echo "usage: $0 <worktree-path> [--apply]" >&2; exit 2; }
|
|
28
|
+
[ -d "$WT" ] || { echo "π₯ not a directory: $WT" >&2; exit 2; }
|
|
29
|
+
case "$MODE" in ''|--apply) ;; *) echo "usage: $0 <worktree-path> [--apply]" >&2; exit 2 ;; esac
|
|
30
|
+
|
|
31
|
+
COMMON="$(git -C "$WT" rev-parse --git-common-dir 2>/dev/null)" || { echo "π₯ not a git worktree: $WT" >&2; exit 2; }
|
|
32
|
+
GITDIR="$(git -C "$WT" rev-parse --git-dir 2>/dev/null)"
|
|
33
|
+
case "$COMMON" in /*) ;; *) COMMON="$WT/$COMMON" ;; esac
|
|
34
|
+
case "$GITDIR" in /*) ;; *) GITDIR="$WT/$GITDIR" ;; esac
|
|
35
|
+
COMMON="$(cd "$COMMON" && pwd -P)"; GITDIR="$(cd "$GITDIR" && pwd -P)"
|
|
36
|
+
if [ "$COMMON" = "$GITDIR" ]; then
|
|
37
|
+
echo "π₯ $WT is the MAIN checkout (git-dir == git-common-dir), not a worktree β nothing to reclaim from itself" >&2; exit 2
|
|
38
|
+
fi
|
|
39
|
+
ROOT="$(dirname "$COMMON")" # main checkout root = parent of the shared .git
|
|
40
|
+
WT="$(cd "$WT" && pwd -P)"
|
|
41
|
+
SRC="$WT/tracks"; DST="$ROOT/tracks"
|
|
42
|
+
[ -d "$SRC" ] || { echo "βΉοΈ no tracks/ in worktree β nothing to reclaim (rc=0)"; exit 0; }
|
|
43
|
+
mkdir -p "$DST/_meta/dispatch" 2>/dev/null || { echo "π₯ cannot create $DST/_meta/dispatch" >&2; exit 10; }
|
|
44
|
+
|
|
45
|
+
NAME="$(basename "$WT")"; TS="$(date +%Y%m%d-%H%M%S)"
|
|
46
|
+
LIST="$DST/_meta/dispatch/reclaim_${NAME}_${TS}.txt"
|
|
47
|
+
|
|
48
|
+
# 1. ENUMERATE β the list lands in a file FIRST (the evidence that survives the copy)
|
|
49
|
+
ONLY=(); DIFF=()
|
|
50
|
+
while IFS= read -r line; do
|
|
51
|
+
case "$line" in
|
|
52
|
+
"Only in $SRC"*)
|
|
53
|
+
d="${line#Only in }"; dir="${d%%: *}"; f="${d#*: }"
|
|
54
|
+
rel="${dir#$SRC}"; rel="${rel#/}"
|
|
55
|
+
p="${rel:+$rel/}$f"
|
|
56
|
+
if [ -d "$SRC/$p" ]; then
|
|
57
|
+
while IFS= read -r ff; do ONLY+=("${ff#$SRC/}"); done < <(find "$SRC/$p" -type f)
|
|
58
|
+
else
|
|
59
|
+
ONLY+=("$p")
|
|
60
|
+
fi ;;
|
|
61
|
+
"Files $SRC"*" differ") DIFF+=("$(printf '%s' "${line#Files }" | sed "s# and .*##; s#^$SRC/##")") ;;
|
|
62
|
+
esac
|
|
63
|
+
done < <(diff -rq "$SRC" "$DST" 2>/dev/null)
|
|
64
|
+
|
|
65
|
+
{
|
|
66
|
+
echo "# worktree_reclaim β $WT β $ROOT ($TS)"
|
|
67
|
+
echo "# only-in-worktree: ${#ONLY[@]} differ-both-sides: ${#DIFF[@]} mode: ${MODE:-enumerate}"
|
|
68
|
+
for p in ${ONLY[@]+"${ONLY[@]}"}; do echo "ONLY $p"; done
|
|
69
|
+
for p in ${DIFF[@]+"${DIFF[@]}"}; do echo "DIFF $p"; done
|
|
70
|
+
} > "$LIST" || { echo "π₯ cannot write list file $LIST" >&2; exit 10; }
|
|
71
|
+
echo "ββ worktree_reclaim: $NAME ββ"
|
|
72
|
+
echo " only-in-worktree: ${#ONLY[@]} Β· differ-both-sides: ${#DIFF[@]} Β· list: ${LIST#$ROOT/}"
|
|
73
|
+
for p in ${ONLY[@]+"${ONLY[@]}"}; do echo " ONLY $p"; done
|
|
74
|
+
for p in ${DIFF[@]+"${DIFF[@]}"}; do echo " DIFF $p (both sides exist and differ β NOT copied, merge by hand)"; done
|
|
75
|
+
|
|
76
|
+
# 2. RECLAIM (--apply) β copy, then verify byte-identical; a copy that cannot be verified is a harness error
|
|
77
|
+
if [ "$MODE" = "--apply" ] && [ "${#ONLY[@]}" -gt 0 ]; then
|
|
78
|
+
bad=0
|
|
79
|
+
for p in ${ONLY[@]+"${ONLY[@]}"}; do
|
|
80
|
+
mkdir -p "$(dirname "$DST/$p")" && cp -p "$SRC/$p" "$DST/$p" && cmp -s "$SRC/$p" "$DST/$p" \
|
|
81
|
+
&& echo " β
reclaimed $p" || { echo " π₯ copy/verify FAILED $p"; bad=$((bad+1)); }
|
|
82
|
+
done
|
|
83
|
+
[ "$bad" -eq 0 ] || { echo "π₯ $bad file(s) not reclaimed β do NOT remove the worktree" >&2; exit 10; }
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
if [ "${#DIFF[@]}" -gt 0 ]; then
|
|
87
|
+
echo "β οΈ ${#DIFF[@]} file(s) differ on both sides β reconcile by hand before removing the worktree (rc=1)"
|
|
88
|
+
exit 1
|
|
89
|
+
fi
|
|
90
|
+
if [ "${#ONLY[@]}" -gt 0 ] && [ "$MODE" != "--apply" ]; then
|
|
91
|
+
echo "βΉοΈ ${#ONLY[@]} file(s) exist only in the worktree β re-run with --apply to copy them (rc=1 until reclaimed)"
|
|
92
|
+
exit 1
|
|
93
|
+
fi
|
|
94
|
+
echo "β
nothing left only in the worktree β safe to: git worktree remove $WT"
|
|
95
|
+
exit 0
|