@chrono-meta/fh-gate 1.4.84 → 1.4.85

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.
@@ -11,13 +11,13 @@
11
11
  "plugins": [
12
12
  {
13
13
  "name": "fh-meta",
14
- "version": "1.4.84",
14
+ "version": "1.4.85",
15
15
  "description": "Hub meta-operations toolkit — 35 skills + 7 agents. New in 1.4.53: `fh-codex-doctor` (npm bin) — Codex adapter drift scanner; reads the documented M1/M2/M3 skill tier map + skill/agent source and reports codex-native/adapter-required/claude-native/unclassified per unit, wired into `npm test`/`prepublishOnly` (fail-closed on unclassified Claude-native primitives). New in 1.4.49: steel-quench gains Step 0.6 Verdict-Invariance Probe (groundedness axis — a load-bearing judged gate's verdict must track behavior, not rubric phrasing; measured flip-count over cross-family paraphrases; arXiv:2605.06161 Policy Invariance anchor); multi_model_sidecar_strategy §Vendor-native harness (a model is strongest in its own vendor CLI — Claude/CC, GPT/codex, Gemini/Antigravity; a universal router degrades all of them, so it stays an autocomplete/QA sidecar, never orchestration); predelete_check.sh fail-closed rewrite; memory-hygiene A-TMA anchor. New in 1.4.48: phantom-quench + steel-quench gain external frontier anchors (arXiv:2607.02052 package-hallucination; arXiv:2607.02057 prompt-coverage-adequacy); README model-flat claim reframed from a per-release point-curve to structural invariants (operation flattens across tiers; depth tier-order fixed within a generation). New in 1.4.47: onboarding step ① surfaces the Mode D companion-store session-start load in the auto-read salience anchor (previously only in the local binding + rules, so a greeting could skip the load). New in 1.4.46: context-doctor command-output axis (route to rtk/proxy for verbose CLI stdout, complementing .claudeignore; risk-gated to token-scarce envs). New in 1.4.41: context-doctor 2026 trigger vocab (context engineering/rot/collapse) + phantom-citation hardening; hub measurement-integrity-checklist (cross-model measurement pre-flight: display-name pin/reps≥3/discriminating probe). New in 1.4.40: install-wizard queryable-wiki scaffold (INDEX + session-start read + R/W/C ingest). New in 1.4.39: auto-decorrelation (cross-family verifier sidecar recruitment) + video-ingest (capability-routed video ingestion). New in 1.4.x: verify-axis check-class taxonomy (mandatory-pass/measured/judged), no-reinvention Tier-0 inventory, 7-class failure taxonomy, Destructive-Op Gate, Wave-T (Temper), tier-floor governance, Mode D Model Notice, FC consent lane, default-Sonnet guidance. New in 1.3.0: public-surface-audit, field-harvest Mode B auto-trigger, 4-axis gate scope ext. Validated cross-CLI: Claude Code, Codex, Gemini.",
16
16
  "source": "./plugins/fh-meta"
17
17
  },
18
18
  {
19
19
  "name": "fh-commons",
20
- "version": "1.4.84",
20
+ "version": "1.4.85",
21
21
  "description": "Project-agnostic utility skills — 4 skills (convergence-loop · deliberation · mcp-circuit-breaker · token-budget-gate) + 1 agent (quench-challenger). Domain-independent utilities transplantable into any project.",
22
22
  "source": "./plugins/fh-commons"
23
23
  }
package/CLAUDE.md CHANGED
@@ -595,10 +595,18 @@ Default operation is a **standard interactive session**. Agent dispatch (single
595
595
 
596
596
  Mapped paths: check `auto_project_mapping.md` or `find ~/projects -maxdepth 1 -type d` for actuals.
597
597
 
598
- **Invocation log obligation (always-loaded do not rely on recall)**: immediately after any custom
599
- sub-agent invocation, append to `knowledge/shared/learnings/subagent_invocations_log.yaml` (8 fields ·
600
- outcome: `accepted`/`partial`/`rejected`/`sustained` — `sustained` = decided NOT to invoke, also recorded).
601
- This feeds the 60/40 promotion gate + UAP loop; detail: `knowledge/shared/rules/operations.md`.
598
+ **Invocation log obligation — now mechanically reconciled, not recall-dependent**: immediately after
599
+ any custom sub-agent invocation, append to `knowledge/shared/learnings/subagent_invocations_log.yaml`
600
+ (8 fields · outcome: `accepted`/`partial`/`rejected`/`sustained` — `sustained` = decided NOT to invoke,
601
+ also recorded). This feeds the 60/40 promotion gate + UAP loop; detail:
602
+ `knowledge/shared/rules/operations.md`.
603
+ **Floor (2026-08-02)**: a `SubagentStop` hook tallies every dispatch and `session_close_check.sh` ④-e
604
+ blocks a close push when dispatches happened and the day's entries are ZERO. Consolidating a class of
605
+ dispatches into one entry with measured counts is fine and is not penalised — the check catches the
606
+ total miss, not imperfect bookkeeping. **Why it needed a floor**: this line was prose-only and a single
607
+ session dispatched 20+ subagents and logged none of them, in the same session that recovered this very
608
+ log file from a branch queued for deletion. The hook only tallies; it never writes an entry, because a
609
+ fabricated `outcome`/`evidence` would poison the promotion gate worse than a missing one.
602
610
 
603
611
  ### Context Card — Required Format for Dispatch
604
612
  ```
@@ -812,3 +812,19 @@
812
812
  whether an agent can locate it. Both arms independently cited the same residual (the paths: trigger is a READ,
813
813
  so a from-scratch Write never loads the detail rule), which is exactly the path the probe encodes. Method costs
814
814
  ~2 min/section and replaces the coverage number that was cut for being wrong four times.'
815
+
816
+ - date: '2026-08-02'
817
+ agent: general-purpose x6 pinned to sonnet (ablation known pairs — 3 sections x 2 arms)
818
+ task: Ablation sweep — 3 sections x 2 arms (6 dispatches; the day's SubagentStop tally read 7, the extra being one earlier probe). Two largest MEASURABLE sections of CLAUDE.md — §Autonomous Initiative (12,224 chars,
819
+ 8 probes) and §Session Wrap-up (8,398 chars, 2 probes) — plus the earlier §New Skill Creation Pre-Commit Gate
820
+ mode: paired arms per section (asset WITH vs WITHOUT the section), floor tier, all file access denied except the
821
+ one arm file
822
+ outcome: accepted
823
+ evidence: '3 sections ablated, 3 KEEP. §Autonomous Initiative: 5 routing behaviours GROUNDED in arm A and NOT
824
+ IN MY CONTEXT in arm B (context-doctor, harvest-loop, goal-quench, deep-clarify, the already-running Guard).
825
+ §Session Wrap-up: 4 of 6 close-chain answers fully absent without it (the ordered sequence, the absolute-last
826
+ rule and its reason, the late-finding re-run rule, the open-PR sweep). §New Skill gate: 3 of 3 absent.'
827
+ note: 'By-product worth more than the verdicts: the Pre-Publish row IN the initiative table is redundant — arm
828
+ B answered the full chain and the order invariant correctly from the gate''s own section (719 chars, ablatable).
829
+ And the headline: three measurements, three KEEPs. The ''CLAUDE.md is bloated'' premise is not surviving contact
830
+ — the earlier 46%-unmeasured framing measured probe coverage, which is not necessity.'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrono-meta/fh-gate",
3
- "version": "1.4.84",
3
+ "version": "1.4.85",
4
4
  "description": "FH runtime adapters — run FH governance, skills, and agents via Claude or Codex with machine-parseable gates.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -104,6 +104,7 @@
104
104
  "scripts/prepush_guard_check.sh",
105
105
  "scripts/psa_scan_lib.sh",
106
106
  "scripts/session_close_check.sh",
107
+ "scripts/test_dispatch_log_lanes.sh",
107
108
  "scripts/memory_link_check.py",
108
109
  "scripts/test_memory_link_check.sh",
109
110
  "scripts/memory_nearcheck.py",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fh-commons",
3
- "version": "1.4.84",
3
+ "version": "1.4.85",
4
4
  "engines": {
5
5
  "claudeCode": ">=1.0.0"
6
6
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fh-meta",
3
- "version": "1.4.84",
3
+ "version": "1.4.85",
4
4
  "engines": {
5
5
  "claudeCode": ">=1.0.0"
6
6
  },
@@ -330,6 +330,19 @@ else
330
330
  fail=1
331
331
  fi
332
332
 
333
+ # ④-e dispatch-log reconciliation + its tally hook. Wired in the same commit that ships them: the
334
+ # obligation they mechanize lost 20/20 in a single session, so leaving the checker itself unrun
335
+ # would be the same defect one layer up.
336
+ if [ -f scripts/test_dispatch_log_lanes.sh ]; then
337
+ if ! bash scripts/test_dispatch_log_lanes.sh >/dev/null 2>&1; then
338
+ echo "FAIL test_dispatch_log_lanes.sh: the dispatch-log reconciliation would mis-report"
339
+ bash scripts/test_dispatch_log_lanes.sh 2>&1 | tail -14
340
+ fail=1
341
+ else
342
+ echo "PASS test_dispatch_log_lanes.sh (date-spelling + verdict + tally-hook lanes)"
343
+ fi
344
+ fi
345
+
333
346
  # selfcheck's own subject-presence discriminators. Every other guard under scripts/ has a lane suite;
334
347
  # this decision had none, and it shipped two mis-routings in one session — a two-arm form that fell
335
348
  # through in silence, then a package discriminator keyed on a file that actually ships. Both are
@@ -117,6 +117,57 @@ if [ -n "$LAST_TAG" ] && [ -f "$FH/package.json" ]; then
117
117
  fi
118
118
  fi
119
119
 
120
+ # ④-e DISPATCH-LOG RECONCILIATION — mechanical, because prose failed completely.
121
+ # CLAUDE.md makes an invocation-log entry MANDATORY immediately after any custom sub-agent
122
+ # invocation (it feeds the 60/40 promotion gate and the UAP loop). Measured 2026-08-02: a single
123
+ # session dispatched 20+ subagents and logged ZERO — not a marginal lapse, a total one, in the same
124
+ # session that RECOVERED that very log file from a branch about to be deleted. An obligation that
125
+ # loses 20 times out of 20 is not under-emphasised; it is unmechanized. Per this repo's own rule
126
+ # (1-2 occurrences -> prose; N>=3 or a repeat on another surface -> mechanize), this is well past it.
127
+ #
128
+ # The check is a RECONCILIATION, not an auto-writer. A hook cannot fill `outcome` or `evidence`
129
+ # without fabricating judgment, and a fabricated log entry is worse than a missing one — it would
130
+ # poison the promotion gate with invented outcomes. So the hook only TALLIES (SubagentStop appends a
131
+ # date line) and this step compares the tally against today's entries.
132
+ #
133
+ # It deliberately does NOT demand 1:1. Consolidating twenty challenger rounds into one entry with
134
+ # measured counts is better bookkeeping than twenty stubs, and punishing it would train stub-spam.
135
+ # What it catches is the failure that actually happened: dispatches occurred and NOTHING was written.
136
+ # ABSENCE IS NOT ZERO. The tally comes from a SubagentStop hook configured in `.claude/settings.json`,
137
+ # which is GITIGNORED by design (it also carries local permissions). So a fresh clone, another
138
+ # machine, or a wiped settings file has no hook — and without this branch the check would read an
139
+ # empty tally as "no dispatches today" and pass in silence. That is the exact fail-open this whole
140
+ # step exists to close, re-created inside it; caught before commit by asking where the tally comes
141
+ # from. Installable snippet: templates/subagent-tally-hook.json.
142
+ TALLY="$FH/tracks/_meta/.subagent_dispatch_tally"
143
+ LOG="$FH/knowledge/shared/learnings/subagent_invocations_log.yaml"
144
+ HOOK_OK=0
145
+ if [ -f "$FH/.claude/settings.json" ]; then
146
+ grep -q '"SubagentStop"' "$FH/.claude/settings.json" 2>/dev/null && HOOK_OK=1
147
+ fi
148
+ if [ "$HOOK_OK" -eq 0 ]; then
149
+ echo "⚠️ ④-e dispatch log NOT MEASURED — no SubagentStop tally hook in .claude/settings.json"
150
+ echo " (that file is gitignored, so a fresh clone has none). An unmeasured dispatch count is"
151
+ echo " NOT a count of zero. Install: templates/subagent-tally-hook.json → .claude/settings.json"
152
+ fi
153
+ DISPATCHED=$(grep -c "^$TODAY$" "$TALLY" 2>/dev/null | tr -d ' ' || echo 0)
154
+ # Both quotings, because the file carries both: hand-written entries use `- date: 2026-08-02`
155
+ # while anything appended via yaml.dump renders `- date: '"'"'2026-08-02'"'"'`. Matching one form counted
156
+ # half the entries as absent — a divergent-normalizer miss inside the check that exists to catch
157
+ # missing records. Known-pair calibrated below in test_dispatch_log_lanes.sh.
158
+ LOGGED=$(grep -cE "^- date: *'?$TODAY'?" "$LOG" 2>/dev/null | tr -d ' ' || echo 0)
159
+ if [ "${DISPATCHED:-0}" -gt 0 ] && [ "${LOGGED:-0}" -eq 0 ]; then
160
+ echo "❌ ④-e $DISPATCHED sub-agent dispatch(es) today and ZERO invocation-log entries — the 60/40"
161
+ echo " promotion gate and the UAP loop both read that file; an unlogged session is invisible to"
162
+ echo " them. Append to knowledge/shared/learnings/subagent_invocations_log.yaml (consolidated"
163
+ echo " per class is fine — record counts and outcomes, not one stub per dispatch)."
164
+ FAIL=1
165
+ elif [ "${DISPATCHED:-0}" -gt 0 ]; then
166
+ echo "✅ ④-e dispatch log: $DISPATCHED dispatch(es) today, $LOGGED log entr(ies) recorded"
167
+ elif [ "$HOOK_OK" -eq 1 ]; then
168
+ echo "✅ ④-e dispatch log: no sub-agent dispatches tallied today"
169
+ fi
170
+
120
171
  # ⑤ CARD-LAST invariant — the card must be the NEWEST close artifact. A card older than
121
172
  # fh_completed / signal files written this session = ⑤ ran before ①–④ finished (the bug class).
122
173
  if [ -f "$CARD" ]; then
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env bash
2
+ # test_dispatch_log_lanes.sh — known-pair anchor for session_close_check.sh ④-e (dispatch-log
3
+ # reconciliation) and for the SubagentStop tally hook that feeds it.
4
+ #
5
+ # WHY (2026-08-02): CLAUDE.md makes an invocation-log entry mandatory immediately after any custom
6
+ # sub-agent invocation — it feeds the 60/40 promotion gate and the UAP loop. Measured: one session
7
+ # dispatched 20+ subagents and logged ZERO, in the same session that recovered that very log file
8
+ # from a branch queued for deletion. An obligation that loses 20/20 is not under-emphasised, it is
9
+ # unmechanized, so ④-e now reconciles a hook-written tally against the day's entries.
10
+ #
11
+ # The reconciliation itself then shipped a defect this suite exists to prevent from returning: the
12
+ # log carries TWO date spellings — `- date: 2026-08-02` for hand-written entries and
13
+ # `- date: '2026-08-02'` for anything appended through yaml.dump — and the first matcher saw only
14
+ # one, counting half the entries as absent. A missing-records check that itself miscounts records is
15
+ # the worst possible shape, so both spellings are lanes below.
16
+ #
17
+ # Exit 0 = the reconciliation discriminates · 1 = it would mis-report.
18
+
19
+ set -uo pipefail
20
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
21
+ CHECK="$SCRIPT_DIR/session_close_check.sh"
22
+ SETTINGS="$SCRIPT_DIR/../.claude/settings.json"
23
+ FAILED=0; PASS=0
24
+ chk() { if [ "$1" -eq 0 ]; then PASS=$((PASS+1)); echo " ✅ $2"; else FAILED=1; echo " ❌ $2"; fi; }
25
+
26
+ [ -f "$CHECK" ] || { echo "FAIL dispatch-log lanes: subject $CHECK missing"; exit 1; }
27
+
28
+ # The matcher under test, lifted from the subject rather than re-spelled (a hand-copied predicate is
29
+ # a divergent normalizer — which is exactly the bug being anchored). If it is gone, fail loudly.
30
+ MATCHER=$(grep -oE "grep -cE \"\^- date: \*'\?\\\$TODAY'\?\"" "$CHECK" | head -1)
31
+ if [ -z "$MATCHER" ]; then
32
+ echo "FAIL the ④-e date matcher is no longer in session_close_check.sh in the expected form —"
33
+ echo " this lane cannot verify what it claims to. Update the lane WITH the subject."
34
+ exit 1
35
+ fi
36
+ echo "── matcher located in the subject"
37
+
38
+ T=$(mktemp -d); trap 'rm -rf "$T"' EXIT
39
+ D=2026-08-02
40
+
41
+ echo "── date-spelling lanes (the shipped miscount) ──"
42
+ printf -- "- date: %s\n" "$D" > "$T/plain.yaml"
43
+ printf -- "- date: '%s'\n" "$D" > "$T/quoted.yaml"
44
+ printf -- "- date: %s\n- date: '%s'\n- date: 2020-01-01\n" "$D" "$D" > "$T/both.yaml"
45
+ count() { grep -cE "^- date: *'?$D'?" "$1" 2>/dev/null | tr -d ' '; }
46
+ [ "$(count "$T/plain.yaml")" = 1 ] ; chk $? "unquoted date counted"
47
+ [ "$(count "$T/quoted.yaml")" = 1 ] ; chk $? "yaml.dump-quoted date counted (the half the first matcher missed)"
48
+ [ "$(count "$T/both.yaml")" = 2 ] ; chk $? "mixed file counts both, and ignores another date"
49
+ # known-POSITIVE for the bug itself: the reverted single-form matcher DOES undercount
50
+ [ "$(grep -c "^- date: *$D" "$T/both.yaml")" = 1 ] ; chk $? "known-POSITIVE: the reverted matcher undercounts (1 of 2) — the defect is real"
51
+
52
+ echo "── reconciliation verdict lanes ──"
53
+ verdict() { # $1=dispatched $2=logged -> BLOCK | OK | NONE
54
+ if [ "$1" -gt 0 ] && [ "$2" -eq 0 ]; then echo BLOCK
55
+ elif [ "$1" -gt 0 ]; then echo OK
56
+ else echo NONE; fi
57
+ }
58
+ [ "$(verdict 20 0)" = BLOCK ] ; chk $? "dispatches with ZERO entries → BLOCK (this is the 2026-08-02 failure)"
59
+ [ "$(verdict 20 4)" = OK ] ; chk $? "consolidated logging (20 dispatches, 4 entries) → OK, not punished"
60
+ [ "$(verdict 1 1)" = OK ] ; chk $? "1:1 logging → OK"
61
+ [ "$(verdict 0 0)" = NONE ] ; chk $? "no dispatches → silent, not a failure"
62
+
63
+ echo "── the tally hook that feeds it ──"
64
+ if [ -f "$SETTINGS" ]; then
65
+ python3 - "$SETTINGS" <<'PY' >"$T/hookcmd" 2>/dev/null || true
66
+ import json,sys
67
+ d=json.load(open(sys.argv[1]))
68
+ g=d.get("hooks",{}).get("SubagentStop",[])
69
+ print(g[0]["hooks"][0]["command"] if g and g[0].get("hooks") else "")
70
+ PY
71
+ cmd=$(cat "$T/hookcmd")
72
+ [ -n "$cmd" ] ; chk $? "SubagentStop hook is configured (an untallied dispatch is invisible to ④-e)"
73
+ if [ -n "$cmd" ]; then
74
+ case "$cmd" in *"exit 0"*) ok=0 ;; *) ok=1 ;; esac
75
+ [ "$ok" -eq 0 ] ; chk $? "hook ends in exit 0 — a non-zero hook exit discards its stdout SILENTLY"
76
+ # run it against a scratch HUB and confirm it appends today's date
77
+ CLAUDE_PROJECT_DIR="$T/hub" bash -c "$cmd" >/dev/null 2>&1
78
+ [ "$(grep -c "$(date +%Y-%m-%d)" "$T/hub/tracks/_meta/.subagent_dispatch_tally" 2>/dev/null || echo 0)" -ge 1 ]
79
+ chk $? "hook actually appends a dated line when run (not merely present)"
80
+ fi
81
+ else
82
+ echo " ⏭️ .claude/settings.json absent — hook lanes unchecked (not a pass)"
83
+ fi
84
+
85
+ echo "── absence-is-not-zero (the fail-open this fix nearly shipped) ──"
86
+ # .claude/settings.json is GITIGNORED, so a fresh clone has no hook and an empty tally. Without an
87
+ # explicit branch the reconciliation would read that as "no dispatches" and pass in silence.
88
+ hookless_verdict() { # $1=hook_configured -> MEASURED | NOT-MEASURED
89
+ [ "$1" -eq 1 ] && echo MEASURED || echo NOT-MEASURED
90
+ }
91
+ [ "$(hookless_verdict 0)" = NOT-MEASURED ] ; chk $? "no hook → NOT MEASURED (an unmeasured count is not zero)"
92
+ [ "$(hookless_verdict 1)" = MEASURED ] ; chk $? "hook present → measured"
93
+ grep -q "NOT MEASURED" "$CHECK" ; chk $? "the subject actually carries that branch (not just this lane)"
94
+ [ -f "$SCRIPT_DIR/../templates/subagent-tally-hook.json" ] ; chk $? "installable snippet exists for a clone that has no settings.json"
95
+
96
+ echo ""
97
+ if [ "$FAILED" -ne 0 ]; then echo "DISPATCH-LOG LANES: FAIL"; exit 1; fi
98
+ echo "DISPATCH-LOG LANES: PASS ($PASS/$PASS)"
99
+ exit 0