@chrono-meta/fh-gate 1.4.83 → 1.4.84
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/rules/fh_4axis_gate.md +45 -1
- package/.claude-plugin/marketplace.json +2 -2
- package/CATALOG.md +23 -1
- package/CHEATSHEET.md +1 -1
- package/CLAUDE.md +9 -3
- package/knowledge/shared/harness-core/field_harness_diagnostic.md +1 -0
- package/knowledge/shared/harness-core/harness_incubator_doctrine.md +10 -0
- package/knowledge/shared/harness-core/harness_verification_core_extended.md +167 -0
- package/knowledge/shared/learnings/subagent_invocations_log.yaml +190 -0
- package/knowledge/shared/rules/operational_adaptation.md +35 -9
- package/package.json +2 -1
- package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-commons/skills/convergence-loop/SKILL.md +25 -10
- package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-meta/agents/challenger.md +1 -1
- package/plugins/fh-meta/skills/phantom-quench/SKILL.md +1 -1
- package/plugins/fh-meta/skills/steel-quench/SKILL.md +78 -10
- package/plugins/fh-meta/skills/steel-quench/SKILL_detail.md +3 -3
- package/scripts/consent_registry_check.sh +187 -15
- package/scripts/destructive_pre_gate.sh +32 -5
- package/scripts/package_coverage_check.sh +10 -0
- package/scripts/pipe_verdict_guard.sh +43 -3
- package/scripts/selfcheck.sh +104 -2
- package/scripts/session_close_check.sh +52 -1
- package/scripts/stale_clone_guard.sh +49 -6
- package/scripts/substrate_jump_detector.sh +21 -0
- package/scripts/test_consent_registry.sh +341 -42
- package/scripts/test_destructive_pre_gate_lanes.sh +42 -0
- package/scripts/test_pipe_verdict_guard_lanes.sh +71 -0
- package/scripts/test_selfcheck_state_lanes.sh +102 -0
- package/scripts/test_session_close_lanes.sh +187 -15
- package/scripts/test_stale_clone_guard_lanes.sh +54 -0
- package/templates/.git-hooks/pre-push +10 -1
- package/templates/consent_classes.yaml.example +18 -0
|
@@ -33,7 +33,13 @@ UNPUSHED=$(git -C "$FH" log --oneline @{u}.. 2>/dev/null | wc -l | tr -d ' ')
|
|
|
33
33
|
|
|
34
34
|
# ①-b open-PR sweep (surface-not-auto — requires gh; skip silently offline)
|
|
35
35
|
if command -v gh >/dev/null 2>&1; then
|
|
36
|
-
|
|
36
|
+
# `gh --json` emits COMPACT single-line JSON when piped, so counting LINES returned 1 for ANY
|
|
37
|
+
# non-zero number of open PRs — the sweep hid every PR after the first, in the exact step CLAUDE.md
|
|
38
|
+
# pairs with count consistency. Count OCCURRENCES instead. Measured 2026-08-02 with a known pair:
|
|
39
|
+
# `[{"number":227},{"number":226},{"number":225}]` → old 1, new 3; `[]` → 0 both ways — which is
|
|
40
|
+
# why an environment with zero open PRs can never surface this, and why the 0-case alone is not a
|
|
41
|
+
# calibration. Observed live the same day: 2 open PRs reported as 1.
|
|
42
|
+
PRS=$(gh pr list --author "@me" --state open --json number 2>/dev/null | grep -o '"number"' | wc -l | tr -d ' ' || true)
|
|
37
43
|
[ "${PRS:-0}" -gt 0 ] && echo "⚠️ ①-b $PRS open PR(s) by you — classify: self-mergeable vs awaiting-external"
|
|
38
44
|
fi
|
|
39
45
|
|
|
@@ -134,6 +140,51 @@ if [ -f "$CARD" ]; then
|
|
|
134
140
|
else
|
|
135
141
|
echo "✅ ⑤ card is the newest close artifact (card-last holds)"
|
|
136
142
|
fi
|
|
143
|
+
|
|
144
|
+
# ⑤ tie probe (ADVISORY — never changes the verdict, never blocks).
|
|
145
|
+
# `-newer` is a STRICT comparison, so an artifact whose mtime exactly equals the card's is invisible
|
|
146
|
+
# to the check above: ⑤ reports card-last holds when the ordering was never actually established.
|
|
147
|
+
# That is the same blind spot that made the ⑤-N *lane* flake in CI on 2026-08-02, one layer down —
|
|
148
|
+
# and the lane repair hardened the fixture, not this production path, which still runs against
|
|
149
|
+
# ordinary session writes with no deterministic separation.
|
|
150
|
+
# Why advisory and not a verdict: tightening ⑤ to treat a tie as a violation would BLOCK a healthy
|
|
151
|
+
# close whose two writes happened to land in one clock tick, and an over-blocking close gate trains
|
|
152
|
+
# the override reflex that disarms it (the same reasoning the ⑤-P lane already encodes). Whether
|
|
153
|
+
# real closes ever tie is UNMEASURED — so this line measures it instead of guessing at a fix.
|
|
154
|
+
# Diagnosis, not prevention; promote it to a verdict only on evidence that ties actually occur.
|
|
155
|
+
#
|
|
156
|
+
# COST, measured before shipping (this runs on every push via the pre-push hook, so an unbounded
|
|
157
|
+
# per-file cost is a live regression, not a design risk). `! -newer "$CARD"` does the only safe
|
|
158
|
+
# narrowing: strictly-newer files are already reported by ⑤ above, so the in-loop "is it newer?"
|
|
159
|
+
# check is redundant and dropped — one fork per candidate, not two.
|
|
160
|
+
# A `-mtime -1` bound was tried and REMOVED: it scopes the window to *now*, but the invariant is
|
|
161
|
+
# anchored to the *card*, and this script runs on every push — routinely against a card written
|
|
162
|
+
# days ago. Two artifacts tied with an old card then fall outside the window and ⑤ reports
|
|
163
|
+
# card-last holds on an ordering never established, with the probe scoped out of seeing it. Speed
|
|
164
|
+
# that hides the thing being measured is not speed. Measured cost of the correct form on the
|
|
165
|
+
# current corpus: 140 candidates, 0.57s. Linear in tracks/_meta; revisit if that reaches thousands.
|
|
166
|
+
#
|
|
167
|
+
# Both emitted lines start with the same literal `⚠️ ⑤ tie` on purpose — the pre-push hook greps
|
|
168
|
+
# for it, and when the two lines carried different prefixes the hook surfaced the warning while
|
|
169
|
+
# dropping the sentence that says what to do about it, leaving a reader with only "⑤'s strict
|
|
170
|
+
# comparison cannot see it" — precisely the misreading the summary line exists to prevent.
|
|
171
|
+
# Keep the shared prefix; test_session_close_lanes.sh ⑤-T asserts the hook's exact pattern.
|
|
172
|
+
TIES=0
|
|
173
|
+
while IFS= read -r f; do
|
|
174
|
+
[ -n "$f" ] || continue
|
|
175
|
+
[ -n "$(find "$CARD" -newer "$f" 2>/dev/null)" ] && continue # strictly older → fine
|
|
176
|
+
TIES=$((TIES + 1))
|
|
177
|
+
echo "⚠️ ⑤ tie: ${f#"$FH"/} shares the card's exact mtime — ⑤'s strict comparison cannot see it"
|
|
178
|
+
done <<EOF
|
|
179
|
+
$(find "$FH/tracks/_meta" -maxdepth 1 -type f \( -name "fh_completed_*.md" -o -name "fh_signal_*.md" \) ! -newer "$CARD" 2>/dev/null)
|
|
180
|
+
EOF
|
|
181
|
+
# What a recurring tie would actually license (cross-family correction, 2026-08-02): NOT making ⑤'s
|
|
182
|
+
# comparison non-strict. Ties do not show the comparison is too strict — they show **mtime is not a
|
|
183
|
+
# reliable witness of close ordering on this machine**. A non-strict ⑤ would convert every same-tick
|
|
184
|
+
# healthy close into a false block, which is the failure mode ⑤-P exists to prevent. The fix that
|
|
185
|
+
# ties would justify is an explicit ordering record (the card writing a marker ①–④ can be compared
|
|
186
|
+
# against) or a deterministic close write sequence — not a looser comparison.
|
|
187
|
+
[ "$TIES" -gt 0 ] && echo "⚠️ ⑤ tie → $TIES artifact(s) ordering-ambiguous. If this recurs, mtime is not a sound ordering witness here; the fix is an explicit ordering record, NOT making ⑤ non-strict (that would false-block every healthy same-tick close)."
|
|
137
188
|
else
|
|
138
189
|
echo "❌ ⑤ session card missing: $CARD"
|
|
139
190
|
FAIL=1
|
|
@@ -41,7 +41,7 @@ import json,sys
|
|
|
41
41
|
try: d = json.load(sys.stdin)
|
|
42
42
|
except Exception: sys.exit(0)
|
|
43
43
|
if d.get("tool_name") != "Write": sys.exit(0)
|
|
44
|
-
sys.stdout.write(d.get("tool_input", {}).get("file_path", "") or "")
|
|
44
|
+
sys.stdout.buffer.write((d.get("tool_input", {}).get("file_path", "") or "").encode("utf-8"))
|
|
45
45
|
' 2>/dev/null) || FILE=""
|
|
46
46
|
[ -n "$FILE" ] || exit 0
|
|
47
47
|
|
|
@@ -68,13 +68,55 @@ MARKER="$MARKER_DIR/$(printf '%s' "$ROOT" | cksum | cut -d' ' -f1)-$(date +%Y%m%
|
|
|
68
68
|
[ -e "$MARKER" ] && exit 0
|
|
69
69
|
|
|
70
70
|
UPSTREAM=$(git -C "$ROOT" rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null) || exit 0
|
|
71
|
-
|
|
71
|
+
# Remote name resolution (leg-C LOW, 2026-08-01): `${UPSTREAM%%/*}` truncates a remote whose NAME
|
|
72
|
+
# carries `/` (git accepts them via config even though `git remote add` rejects them) — `a/b/main`
|
|
73
|
+
# became remote `a`, the fetch failed, and the guard went silently inert on exactly that clone.
|
|
74
|
+
# `%(upstream:remotename)` is git's own parse; the cut stays as fallback for git versions without it.
|
|
75
|
+
_HEAD_REF=$(git -C "$ROOT" symbolic-ref -q HEAD 2>/dev/null) || _HEAD_REF=""
|
|
76
|
+
REMOTE=""
|
|
77
|
+
[ -n "$_HEAD_REF" ] && REMOTE=$(git -C "$ROOT" for-each-ref --format='%(upstream:remotename)' "$_HEAD_REF" 2>/dev/null | head -1)
|
|
78
|
+
[ -n "$REMOTE" ] || REMOTE="${UPSTREAM%%/*}" # fallback; never a hardcoded origin (GPT pass)
|
|
72
79
|
[ -n "$REMOTE" ] || exit 0
|
|
73
80
|
|
|
74
81
|
if [ "${FH_STALE_CLONE_NO_FETCH:-0}" != "1" ]; then
|
|
75
|
-
#
|
|
76
|
-
# timeout
|
|
77
|
-
|
|
82
|
+
# Internally-bounded fetch (leg-C MED, 2026-08-01): the previous hard bound was the RUNNER's
|
|
83
|
+
# hook timeout (20s), which kills the whole guard — preempting the exit-0 contract AND skipping
|
|
84
|
+
# the marker write, so a wedged transport re-stalled EVERY Write for the rest of the day
|
|
85
|
+
# (20s × N calls, a session-killer). The guard now owns its bound: fetch in the background,
|
|
86
|
+
# poll, and on expiry kill the fetch, ARM the day-throttle, exit 0. Arming on timeout is a
|
|
87
|
+
# deliberate trade: one silent day on a broken-network machine (fail-open, the documented
|
|
88
|
+
# degrade direction) beats a stall on every file creation. This is NOT the attempt-marker the
|
|
89
|
+
# GPT pass rejected — that armed on every early exit; this arms only after a full budget spent
|
|
90
|
+
# against a wedged transport. GIT_TERMINAL_PROMPT=0 still kills the credential-prompt class.
|
|
91
|
+
# Budget: tenths of a second; default 150 (15s) stays under the snippet's 20s runner timeout.
|
|
92
|
+
# CAPPED at 150 (terra round, 2026-08-01): an env-supplied budget > the runner timeout would
|
|
93
|
+
# restore the exact runner-preemption this bound exists to remove. (No boundary lane on purpose
|
|
94
|
+
# — it would idle its full 15s by construction; the cap is these three lines.)
|
|
95
|
+
# LENGTH-first, then value (terra round 2, 2026-08-01): an all-digit literal wider than the
|
|
96
|
+
# shell's integer width makes `[ "$x" -gt 150 ]` itself an ERROR ("integer expression expected" /
|
|
97
|
+
# "number truncated"), so the numeric cap never ran and the value stayed unnormalized. Digits are
|
|
98
|
+
# capped at 3 (max 999) before any arithmetic touches the value — safe in bash 3.2 and zsh alike.
|
|
99
|
+
_BUDGET="${FH_STALE_CLONE_FETCH_BUDGET_TENTHS:-150}"
|
|
100
|
+
case "$_BUDGET" in ''|*[!0-9]*) _BUDGET=150 ;; esac
|
|
101
|
+
[ "${#_BUDGET}" -gt 3 ] && _BUDGET=150
|
|
102
|
+
[ "$_BUDGET" -gt 150 ] && _BUDGET=150
|
|
103
|
+
# stdout AND stderr to /dev/null: the child inherits this hook's stdout pipe, and a
|
|
104
|
+
# still-running child holding it open would stall the hook runner past our own exit.
|
|
105
|
+
GIT_TERMINAL_PROMPT=0 git -C "$ROOT" fetch -q "$REMOTE" >/dev/null 2>&1 &
|
|
106
|
+
_FPID=$!
|
|
107
|
+
_t=0
|
|
108
|
+
while kill -0 "$_FPID" 2>/dev/null && [ "$_t" -lt "$_BUDGET" ]; do
|
|
109
|
+
sleep 0.1; _t=$((_t+1))
|
|
110
|
+
done
|
|
111
|
+
if kill -0 "$_FPID" 2>/dev/null; then
|
|
112
|
+
# No wait after the kill: a shell-script transport defers TERM until its foreground child
|
|
113
|
+
# exits (measured: the lane's wedge shim held a wait for the full 30s hang). The child is
|
|
114
|
+
# reparented at our exit; TERM is best-effort cleanup, the BOUND is the contract.
|
|
115
|
+
kill "$_FPID" 2>/dev/null || true
|
|
116
|
+
: > "$MARKER" 2>/dev/null || true
|
|
117
|
+
exit 0
|
|
118
|
+
fi
|
|
119
|
+
wait "$_FPID" 2>/dev/null || exit 0
|
|
78
120
|
fi
|
|
79
121
|
|
|
80
122
|
BEHIND=$(git -C "$ROOT" rev-list --count 'HEAD..@{u}' 2>/dev/null) || exit 0
|
|
@@ -85,7 +127,8 @@ case "$BEHIND" in ''|*[!0-9]*) exit 0 ;; esac
|
|
|
85
127
|
MSG=" ⚠️ STALE-CLONE $ROOT is behind $UPSTREAM by $BEHIND commit(s).
|
|
86
128
|
Building on a stale clone manufactures duplicates of work that already exists upstream
|
|
87
129
|
(measured: a 46-PR-behind clone rebuilt an existing lane, 2026-07-31).
|
|
88
|
-
|
|
130
|
+
Advisory timing: this notice arrives with the Write already evaluated — reconcile NOW,
|
|
131
|
+
before the next file: git -C '$ROOT' pull --ff-only (or rebase your branch).
|
|
89
132
|
found→extend applies at repo level too — check what upstream already has."
|
|
90
133
|
|
|
91
134
|
if json_out=$(printf '%s' "$MSG" | PYTHONIOENCODING=utf-8 python3 -c '
|
|
@@ -55,6 +55,27 @@ EOF
|
|
|
55
55
|
echo " → run the shed/advance pass: re-check capability-compensating scaffolding against the new"
|
|
56
56
|
echo " substrate (sonnet_floor_doctrine.md §durable-mechanization — shed what the model no longer"
|
|
57
57
|
echo " needs, advance what the new substrate enables). Removals go through the 4-axis gate."
|
|
58
|
+
# Until 2026-08-02 the line above WAS the whole pass: an instruction with no instrument behind it, so
|
|
59
|
+
# nothing measured whether a rule still earned its residency. It now hands off to something runnable.
|
|
60
|
+
if [ ! -f "$FH/scripts/probe_scope_check.sh" ]; then
|
|
61
|
+
echo " (probe-scope check NOT run — scripts/probe_scope_check.sh absent. Expected in the npm"
|
|
62
|
+
echo " package, which ships neither the script nor its probe set; in a SOURCE checkout it is an"
|
|
63
|
+
echo " instrument gap, not a skip — selfcheck fails on exactly that state.)"
|
|
64
|
+
else
|
|
65
|
+
echo ""
|
|
66
|
+
echo " ── probe Scope resolution (bash scripts/probe_scope_check.sh) ──"
|
|
67
|
+
# Capture, THEN branch on rc — a pipe here would report the pipe's status, and a `sed` window keyed
|
|
68
|
+
# on a line the tool no longer prints would swallow the whole result silently.
|
|
69
|
+
_ps_out=$(bash "$FH/scripts/probe_scope_check.sh" 2>&1); _ps_rc=$?
|
|
70
|
+
if [ "$_ps_rc" -ne 0 ]; then
|
|
71
|
+
echo " ⚠️ probe-scope check FAILED (exit $_ps_rc) — a probe points at a section that is not there."
|
|
72
|
+
printf '%s\n' "$_ps_out" | sed 's/^/ /'
|
|
73
|
+
else
|
|
74
|
+
printf '%s\n' "$_ps_out" | grep -E 'control B|✅' | sed 's/^/ /'
|
|
75
|
+
fi
|
|
76
|
+
echo " This says the probe set still points at real sections — NOT how much of the asset is"
|
|
77
|
+
echo " defended. Coverage measurement is unsolved; do not read a green check as 'fully probed'."
|
|
78
|
+
fi
|
|
58
79
|
|
|
59
80
|
printf '%s\n' "$CURRENT" > "$STATE"
|
|
60
81
|
exit 0
|