@chrono-meta/fh-gate 1.4.76 → 1.4.78

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.
@@ -0,0 +1,233 @@
1
+ # AGENTS.md Runtime Details
2
+
3
+ > **Load: on demand.** `AGENTS.md` is the always-loaded runtime entry point. Read only the section
4
+ > named by its imperative pointer.
5
+
6
+ ## §Architecture-and-output-routing
7
+
8
+ forge-harness has two distinct layers:
9
+
10
+ | Layer | Contents | Compatibility |
11
+ |---|---|---|
12
+ | **Methodology** | `tracks/`, `knowledge/`, `SKILL.md` documents, session protocols | Model-agnostic |
13
+ | **Automation** | Plugin agents, hooks, slash commands, `CLAUDE.md` rules | Claude Code-native |
14
+
15
+ FH agents ship under `plugins/*/agents/` through the plugin channel. `.claude/agents/` is the
16
+ field-project local/override slot, not FH's shipping location. Skills straddle both layers: their
17
+ methodology is portable, while their automatic invocation is Claude-native.
18
+
19
+ The methodology layer is Codex-compatible beta. Gemini, Codex, and other runtimes can apply it by
20
+ replacing hooks and native dispatch with manual invocation.
21
+
22
+ Directory names do not determine publication residency:
23
+
24
+ | Content | Default destination |
25
+ |---|---|
26
+ | Reusable methodology, docs, skills, public guidance, polished external conclusions | Public mirror: `knowledge/`, `plugins/`, `docs/` |
27
+ | Raw signals, operator observations, private validation, handoffs, paper drafts, PR-background reasoning | Private companion store or local-only |
28
+
29
+ In a workspace pairing a public mirror with a private companion store, preserve repository ownership
30
+ even when both are locally available. Treat observational or operator-specific material as
31
+ private-first and promote only the polished result.
32
+
33
+ ## §Sidecar-routing-and-waiting
34
+
35
+ A sidecar is a capability-routing layer, not a second harness or co-governor. Gemini/Antigravity is
36
+ suited to breadth and multimodal work. Codex's primary FH role is repo-grounded audit: file reads,
37
+ source-close grep, diff/patch review, gate execution, and phantom/backtrace. A Codex session with
38
+ Browser or Chrome connectors may also take live web-flow automation.
39
+
40
+ Sidecars are Bash/adapter invocations coordinated inline; they bypass plugin agent dispatch and this
41
+ registry. Route Codex to repo-grounded audit by default, not breadth, discovery, or design-depth work.
42
+
43
+ After dispatch, wait mechanically:
44
+
45
+ ```bash
46
+ printf '%s' "$prompt" | bash scripts/sidecar_wait.sh out.txt 900 -- codex exec -m gpt-5.5 -
47
+ ```
48
+
49
+ Interpret only the typed verdict:
50
+
51
+ | Verdict | Meaning |
52
+ |---|---|
53
+ | `SIDECAR_VERDICT=COMPLETE exit=0 bytes=N` | The process completed; read the output file |
54
+ | `SIDECAR_VERDICT=TIMEOUT waited=Ns bytes=N` | The process is still alive; this is not a result |
55
+ | `SIDECAR_VERDICT=EMPTY exit=0` | The completed process returned no content |
56
+
57
+ A live process and a completed empty process can both show a zero-byte file. Never judge state by
58
+ file inspection. Canonical authority and waiting doctrine:
59
+ `knowledge/shared/harness-core/multi_model_sidecar_strategy.md §Runtime Authority` and
60
+ `plugins/fh-meta/skills/auto-decorrelation/SKILL.md §S-1b`.
61
+
62
+ ## §Mandatory-checklist-procedures
63
+
64
+ ### FH asset changes
65
+
66
+ Read `.claude/rules/fh_4axis_gate.md` before changing an FH asset. It defines the mandatory
67
+ Backward, Adversarial, Forward, and Record axes, marker fields, lightweight exception, and
68
+ substantive carve-out. The pre-commit hook blocks commits that lack required evidence.
69
+
70
+ When running `templates/regression_guard.sh`, prefer `REGRESSION_GUARD_RESULT_FILE=<path>` and read
71
+ `result=pass|review|block|skip|error`. Without that environment variable, read the typed
72
+ `REGRESSION_GUARD_RESULT=` stdout line. Exit 0 alone cannot distinguish PASS from SKIP.
73
+
74
+ ### Company residency
75
+
76
+ Keep raw company source, secrets, hostnames, internal names, stack traces, and unredacted findings
77
+ local. Outbound requests may contain only a sanitized summary. An exception requires explicit
78
+ operator approval plus a gitignored audit note. Canonical procedure:
79
+ `CLAUDE.md §Field-Harness Diagnostic`.
80
+
81
+ ### Author exposure
82
+
83
+ Before completing a material deliverable, use `agent-composer §Author-Exposure Table`:
84
+
85
+ | Blind spot | Lens |
86
+ |---|---|
87
+ | Cold entry | `beginner` |
88
+ | Everyday friction | `main-player` |
89
+ | Outside currency | `expert` |
90
+ | Optimistic self-verification | `challenger` plus cross-family evidence |
91
+ | Rebuilding an existing asset | `fact-checker` |
92
+ | Ungrounded numbers or references | `phantom-quench` |
93
+ | Unclear | `challenger` |
94
+
95
+ Run an agent lens through `fh-run` or a direct `codex exec` reading the agent spec. The result remains
96
+ evidence for the governor to source-close.
97
+
98
+ ### Intent marshaling
99
+
100
+ For ordinary work requests, read
101
+ `knowledge/shared/harness-core/intent_marshaling_general_work.md` before applying its ladder or when a
102
+ capability gap appears. The required loop is: restate deliverable and doneness; enumerate installed
103
+ and mapped capability with trust tiers; compose and run reversible FH-native work; cite the scan
104
+ before declaring a gap; search the internal registry, then external capability, then synthesize
105
+ in-session; apply the Author-Exposure check to material output.
106
+
107
+ Marshaling never upgrades trust. Non-FH sibling `ask-tier` capability remains propose-only.
108
+ Send, post, deploy, delete, and payment retain their own gates. Installing external capability routes
109
+ to `plugin-recommender` HITL; persisting a synthesized skill routes to the New-Skill gate.
110
+
111
+ ### Measurement integrity
112
+
113
+ Read `knowledge/shared/harness-core/measurement-integrity-checklist.md` before relying on a scan,
114
+ checker, or metric. Demonstrate that the instrument separates one known-positive target from one
115
+ known-clean target, and inspect at least one hit before stating a count. Report no-target and
116
+ mid-run failure as `UNMEASURED`. Treat all-pass or all-fail output as an instrument warning.
117
+
118
+ ### Irreversible surfaces
119
+
120
+ The Pre-Publish and Destructive-Op gates in `CLAUDE.md` fire on intent rather than file paths.
121
+ Read the relevant gate before any publish, delete, or history rewrite. The pre-push hook provides
122
+ only the git-side mechanical backstop.
123
+
124
+ ## §Invocation-patterns
125
+
126
+ ### Single agent
127
+
128
+ Ask for the required lens directly, for example: "Analyze this SKILL.md for structural flaws before
129
+ I commit it." Claude may description-dispatch `quench-challenger`; a non-Claude runtime invokes the
130
+ agent through the adapter.
131
+
132
+ ### Parallel independent work
133
+
134
+ Dispatch two or more agents concurrently only when their tasks are independent, such as a
135
+ `fact-checker` duplicate scan and a `persona-innovator` naming-gap scan. The orchestrator integrates
136
+ their evidence after both finish.
137
+
138
+ ### Wave composition
139
+
140
+ For complex dependent work, use `agent-composer`: Wave 0 reconnaissance, Wave 1 execution, then
141
+ Wave 2 synthesis.
142
+
143
+ ## §Codex-entry-points
144
+
145
+ Read a skill workflow directly:
146
+
147
+ ```bash
148
+ cat plugins/fh-meta/skills/steel-quench/SKILL.md
149
+ ```
150
+
151
+ Prefer the runtime adapter:
152
+
153
+ ```bash
154
+ FH_BACKEND=codex npx --package @chrono-meta/fh-gate fh-run \
155
+ --skill steel-quench \
156
+ --file path/to/artifact.md
157
+
158
+ FH_BACKEND=codex npx --package @chrono-meta/fh-gate fh-run \
159
+ --agent fh-commons:quench-challenger \
160
+ --file path/to/artifact.md
161
+ ```
162
+
163
+ Direct headless fallback:
164
+
165
+ ```bash
166
+ cat plugins/fh-meta/skills/steel-quench/SKILL.md path/to/artifact.md \
167
+ | codex exec -m gpt-5.5 -
168
+ ```
169
+
170
+ `codex exec -m gpt-5.5 -` reads stdin headlessly. Interactive `npx @openai/codex` requires a TTY and
171
+ is not the headless substitute.
172
+
173
+ ## §Compatibility-tiers
174
+
175
+ | Tier | Definition | Examples |
176
+ |---|---|---|
177
+ | **M1 — Full** | No Claude-native dependency | `token-budget-gate`, `asset-placement-gate`, `phantom-quench`, `deep-clarify`, `convergence-loop` |
178
+ | **M2 — Partial** | Core works; native agent/slash-command steps need adaptation | `deliberation`, `steel-quench`, `harness-doctor`, `context-doctor`, `sim-conductor`, `harvest-loop` |
179
+ | **M3 — Claude-only** | Requires a Claude hook or session-scoped dispatch | `goal-quench`, `hub-cc-pr-reviewer`, `install-wizard` |
180
+
181
+ **Which phase needs adapting** — the operative half of the M2/M3 rows. Without this a tier label
182
+ tells a non-Claude runtime that a skill is "partial" but not *where* to intervene, which is the only
183
+ thing it can act on. (Restored 2026-07-30 during review of the salience split: the rows survived the
184
+ move, these per-skill cues did not, and they existed in no other file.)
185
+
186
+ | Skill | Runs unchanged | Needs substitution |
187
+ |---|---|---|
188
+ | `steel-quench` | Waves 1–3 | the `quench-challenger` agent step |
189
+ | `harvest-loop` | the git-scan phase | PR auto-proposal |
190
+ | `deliberation` | proposal/synthesis structure | Mediator and Jury agent steps |
191
+ | `goal-quench` (M3) | — | Phase 3 depends on a Claude Stop hook |
192
+ | `hub-cc-pr-reviewer` (M3) | — | needs Claude session context |
193
+ | `install-wizard` (M3) | — | writes `settings.json` |
194
+
195
+ For M2, replace `Agent(subagent_type=...)` and slash-command steps with `fh-run` or direct
196
+ `codex exec` reading the relevant spec.
197
+
198
+ Use Codex native goal/session control when available. FH's portable role is the post-goal quality
199
+ gate (`fh-gate`). `fh-goal` is for non-interactive one-shot runs followed automatically by
200
+ `fh-gate`; it does not replace native goal control.
201
+
202
+ ## §Beta-removal
203
+
204
+ | Condition | Status |
205
+ |---|---|
206
+ | Known-limitations document published at `docs/codex-compat.md` | Done |
207
+ | At least 5 externally validated M1 skill runs from non-authors | Pending |
208
+ | At least 1 external Codex user confirms methodology reproduction | Pending |
209
+ | README badge removes `beta` | Blocked on the external conditions |
210
+
211
+ Internal author validation does not satisfy the external conditions. The author validated
212
+ `phantom-quench` against a phantom-seeded fixture and `asset-placement-gate` against a duplicate-skill
213
+ proposal on 2026-06-04. See `docs/codex-compat.md` for limitations and validation details.
214
+
215
+ Report external validation through an issue on `chrono-meta/forge-harness` with the
216
+ `codex-validation` label.
217
+
218
+ ## §Adding-agents
219
+
220
+ Before adding an agent:
221
+
222
+ 1. Run `asset-placement-gate` and confirm the role does not duplicate an existing asset.
223
+ 2. Use a plain description with no self-marketing language.
224
+ 3. Define at least one explicit `Done When` condition.
225
+ 4. Provide at least three natural-language trigger examples.
226
+ 5. Make the agent independently executable or document its dependencies.
227
+ 6. Add the canonical spec under the correct `plugins/*/agents/` directory.
228
+ 7. Synchronize `AGENTS.md` and `.claude/registry/agent_cards.json`.
229
+
230
+ Before commit, apply `.claude/rules/fh_4axis_gate.md §FH Improvement 4-Axis Auto-Gate` and read
231
+ `knowledge/shared/rules/operations.md §Sub-agent Operations`. After at least two weeks of use,
232
+ strengthen an agent when accepted invocations are at least 60%; redefine or deprecate it when
233
+ rejected invocations are at least 40%.
@@ -716,7 +716,7 @@ Missing any layer = compression risk. (Path conventions adapt per project — se
716
716
  ## References
717
717
 
718
718
  - `README.md §Architecture — 2-layer design` — sidecar note in Automation layer section
719
- - `AGENTS.md §2-Layer Architecture Context` — sidecar note distinguishing Bash invocation from agent dispatch
719
+ - `knowledge/shared/harness-core/agents_md_runtime_details.md §Sidecar-routing-and-waiting` — sidecar note distinguishing adapter invocation from agent dispatch
720
720
  - FH paper (Zenodo DOI: 10.5281/zenodo.20397566, arXiv: submit/7657304) — harness-as-durable-layer thesis
721
721
  - A sister-harness `sidecar-orchestrator` SKILL.md (2026-06-01) — gh copilot + corporate endpoint + 3-tier fallback + 3-layer persistence
722
722
  - arXiv:2605.26302 AgingBench — compression aging defense rationale
@@ -580,3 +580,17 @@
580
580
  withheld by a "standing session instruction"; the operator pointed out they never said that — it is
581
581
  session-level SYSTEM configuration, and the wording blurred the source. Corrected in the markers and
582
582
  here. The operator then explicitly requested the agent, which satisfies the configured exception.
583
+ - date: 2026-07-30
584
+ agent: fh-meta:challenger
585
+ task: "Axis-2 adversarial review of the per-node floor check + install-wizard SessionStart gap (5 rounds + 1 confirmation)"
586
+ mode: isolated Agent, opus, same-family (no cross-family sidecar reachable — disclosed in the marker, not silently passed)
587
+ outcome: accepted
588
+ evidence: "S-grade 6 → 5 → 4 → 2 → 0, CONVERGED. Caught, among others: a false 'tracked settings.json survives a clone' claim; two of this repo's own gate scripts already red; a group-level hook merge that deleted a user's own hook; a broken machine going permanently silent after one report; foreign (husky) hooks counted as FH floors; and a Mode D applicability gate that silenced the exact incident that started the work."
589
+ note: "Its standing prescription — write the known-pair lanes BEFORE the fix — was adopted at round 4 and ended three consecutive rounds of fix-induced regressions (0 in rounds 4, 5, confirmation). One prescription was REJECTED with measured grounds (widening the package-coverage predicate: 25 new hits, 24 artifacts), and the challenger then correctly showed my rejection rationale was itself circular; the file now labels that check UNQUANTIFIED."
590
+ - date: 2026-07-30
591
+ agent: general-purpose (Sonnet-pinned target-tier sim)
592
+ task: "Blind fresh-session simulation: would a new machine end up correctly wired if the user opens with a task, not a greeting?"
593
+ mode: isolated Agent, sonnet, run BEFORE the fixes
594
+ outcome: accepted
595
+ evidence: "Two findings, both closed. (1) The node check was unreachable on the fresh-machine case because it lived in a gitignored registration — drove the split into scripts/fh_node_check.sh + a tracked snippet. (2) The wizard wrote a literal '<your-store>' placeholder and reported success, leaving a dead hook — the real BE_DIR is now baked in at write time."
596
+ note: "Sonnet tier was the right instrument here: both findings are about instructions being followed literally, which is exactly where a weaker tier diverges from an author's intent."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrono-meta/fh-gate",
3
- "version": "1.4.76",
3
+ "version": "1.4.78",
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": [
@@ -106,6 +106,7 @@
106
106
  "scripts/test_memory_link_check.sh",
107
107
  "scripts/memory_nearcheck.py",
108
108
  "scripts/sidecar_wait.sh",
109
+ "scripts/test_sidecar_wait_stdin.sh",
109
110
  "scripts/test_session_close_lanes.sh",
110
111
  "scripts/test_card_drift_probe.sh",
111
112
  "scripts/universal_guard_check.sh",
@@ -124,6 +125,11 @@
124
125
  "README.md",
125
126
  "README.ko.md",
126
127
  "README.zh.md",
127
- "README.ja.md"
128
+ "README.ja.md",
129
+ "scripts/fh_node_check.sh",
130
+ "templates/settings.SessionStart.snippet.json",
131
+ "scripts/test_node_check_lanes.sh",
132
+ "scripts/sidecar_calibrate.sh",
133
+ "scripts/test_sidecar_calibrate_lanes.sh"
128
134
  ]
129
135
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fh-commons",
3
- "version": "1.4.76",
3
+ "version": "1.4.78",
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.76",
3
+ "version": "1.4.78",
4
4
  "engines": {
5
5
  "claudeCode": ">=1.0.0"
6
6
  },
@@ -48,6 +48,94 @@ claude --version 2>/dev/null || echo "Claude Code not installed"
48
48
 
49
49
  **⚠️ When Bedrock routing confirmed**: Request AWS quota increase (TPM/RPM increase) or raise LiteLLM `max_input_tokens`, then retry. Fundamental solution is switching to direct Anthropic API access.
50
50
 
51
+ ### Node floor check — run on EVERY machine, not once per user
52
+
53
+ A user's context (companion store, memory, session card) travels between machines; **the machine's
54
+ own setup does not**. A rich context makes a fresh laptop read as "already configured", so the two
55
+ mechanical floors below must be checked per node — this is the check `scripts/fh_node_check.sh`
56
+ points at when it reports a missing floor at turn 0.
57
+
58
+ ```bash
59
+ # HUB_DIR — the same variable the hook scripts use. Do NOT invent a second name (an earlier draft
60
+ # read FH_DIR, which nothing sets, so an operator with HUB_DIR set would have been silently told
61
+ # about a different repo).
62
+ FH="${HUB_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
63
+
64
+ # ① git-side floor. Probe the EXECUTABLE HOOK, not the config key — `core.hooksPath` unset is a
65
+ # normal, working install when hooks sit in .git/hooks, and a set-but-empty path is a broken
66
+ # install that the key alone reports as fine. Both directions are wrong; the file is the truth.
67
+ # Resolve the directory with `git rev-parse --git-path` rather than assembling "$FH/.git/hooks":
68
+ # in a LINKED WORKTREE `.git` is a file, so the assembled path does not exist and every hook
69
+ # reads as missing (verified: a worktree with working hooks reported ❌❌ under the old form).
70
+ # Same resolution the script uses — one predicate, not two.
71
+ # --path-format needs git >= 2.31; on older git fall back to the RELATIVE --git-path form resolved
72
+ # against the toplevel, never to a hand-built "$FH/.git/hooks" (which is the worktree bug above).
73
+ HD="$(git -C "$FH" rev-parse --path-format=absolute --git-path hooks 2>/dev/null)"
74
+ if [ -z "$HD" ]; then
75
+ _rel="$(git -C "$FH" rev-parse --git-path hooks 2>/dev/null || echo .git/hooks)"
76
+ case "$_rel" in /*) HD="$_rel" ;; *) HD="$(git -C "$FH" rev-parse --show-toplevel 2>/dev/null || echo "$FH")/$_rel" ;; esac
77
+ fi
78
+ for h in pre-commit pre-push; do
79
+ if [ -x "$HD/$h" ]; then echo "✅ $h executable ($HD)"
80
+ else echo "❌ $h MISSING or not executable at $HD — that gate is not running on this node"; fi
81
+ done
82
+
83
+ # ② turn-0 load floor (Mode D only): the companion-load hook. Its registration lives in the
84
+ # GITIGNORED settings.local.json, so it does NOT survive a re-clone — hence a per-node check.
85
+ # Parse the JSON and match the SCRIPT NAME inside hooks.SessionStart — not a bare grep for the
86
+ # key. settings.json also carries a SessionStart entry (fh_node_check), so keying on the key
87
+ # alone passes on a machine where the companion load is absent; and a plain substring grep also
88
+ # hits commented-out lines, other hook events, and permission strings. Same predicate the script
89
+ # uses — two predicates reading one state differently is how a result leaks silently.
90
+ # Applicability gate FIRST, and keyed the same way the script keys it: a Mode D user is one with
91
+ # an exported BE_DIR, or a CLAUDE.local.md that MENTIONS a companion binding — NOT one who merely
92
+ # HAS a CLAUDE.local.md (that is Claude Code's standard local-override file; anyone may keep one),
93
+ # and NOT one who has a settings.local.json (gitignored, so a fresh clone lacks it — and a fresh
94
+ # clone with a full companion store is the exact case this must not silence). The vocabulary
95
+ # covers every backend the wizard documents (vault · gbrain · *-be repo), because an FH-flavoured
96
+ # regex would silence two first-class backends. It is a MENTION test, not semantic: "I do not use
97
+ # a companion store" also matches, and that over-match costs one informational line — the cheap
98
+ # direction, since the expensive direction is silence.
99
+ if { [ -n "${BE_DIR:-}" ] && [ -d "$BE_DIR" ]; } \
100
+ || { [ -f "$FH/CLAUDE.local.md" ] && grep -qiE 'BE_DIR|companion[ -]store|컴패니언|vault|gbrain|obsidian' "$FH/CLAUDE.local.md"; }; then
101
+ python3 - "$FH" <<'PY'
102
+ import json, os, sys
103
+ hub = sys.argv[1]
104
+ for p in (os.path.join(hub, ".claude", "settings.local.json"),
105
+ os.path.expanduser("~/.claude/settings.json")):
106
+ try: groups = json.load(open(p)).get("hooks", {}).get("SessionStart", [])
107
+ except Exception: continue
108
+ if any("fh_session_load" in h.get("command", "")
109
+ for g in groups for h in g.get("hooks", [])):
110
+ print("✅ companion-load SessionStart registered"); sys.exit(0)
111
+ print("❌ companion-load SessionStart MISSING — freshness + env-delta do not fire at turn 0")
112
+ PY
113
+ else
114
+ echo "N/A companion-load SessionStart (not a Mode D setup — no companion store configured)"
115
+ fi
116
+ ```
117
+
118
+ **Bootstrap note — honest scope**: `scripts/fh_node_check.sh` (the check that *reports* a missing
119
+ floor at turn 0) ships with the clone, but its **registration does not**: every
120
+ `.claude/settings*.json` path in this repo is gitignored (confirm with `git check-ignore -v
121
+ .claude/settings.json` — don't trust a line number, they move), so no SessionStart entry can be
122
+ tracked. The tracked artifact is `templates/settings.SessionStart.snippet.json`; `/install-wizard`
123
+ merges it. A user who never runs the wizard still gets no turn-0 signal — that residual is reduced,
124
+ not closed, and this check item is the backstop for it.
125
+
126
+ **Emission model** (so the output is read correctly): a **missing floor is reported every session**
127
+ until it is fixed — it is a persistent condition, not an event. A healthy machine is silent. So
128
+ seeing this banner twice is not a bug, and seeing it once then never again means it was an *event*
129
+ line (identity change / infra delta), not a floor complaint.
130
+
131
+ **Fix for either ❌**: re-run `/install-wizard` (it registers both — `install-wizard/SKILL_detail.md`
132
+ §Mode-D-Companion-Setup step 3 and the 4-axis gate block), or apply the two commands it uses directly.
133
+
134
+ **Why this is a check item and not a note** (measured 2026-07-30): a second machine held the full
135
+ companion store and memory, yet ran with **both** SessionStart hooks absent. Nothing surfaced it —
136
+ the miss was found by accident. A weak tier is where this bites hardest: a mechanized floor is
137
+ tier-independent, while the prose it replaces is exactly what a weaker model drops first.
138
+
51
139
  ---
52
140
 
53
141
  ## Step 1. Existing Asset Inventory
@@ -88,7 +88,7 @@ All 3 → Proceed in order: Step 0-B (token injection) → Step 0 (environment c
88
88
 
89
89
  **Mode D detected (FH developer/researcher)**: Guide companion-store setup before Step 1. **Ask the backend first** — the store is a role (durable private home for artifacts), not a fixed `*-be` git repo: Obsidian vault / gbrain-ingest / `*-be` git repo (default) all qualify. Do not assume the repo path.
90
90
 
91
- **Set the store up as a queryable WIKI, not an empty folder** — the store earns its value when the agent can *read it well*, so the scaffold includes: an `INDEX.md` wiki-home (**the `CATALOG.md` read-first pattern applied to the store** — named INDEX to avoid two CATALOG.md across the public + private repos; section map + **read-time-derived** pointers, never hand-maintained placeholders that rot) · session-start read wiring the AI **idempotently adds** to `CLAUDE.local.md` (grep-guarded against duplication: read INDEX → open result/signal files newer than the session card, not only handoffs) · the Raw/Wiki/Conversation ingest axis (`sync_push_protocols.md`). The git `*-be` form is the **default within the no-existing-store branch** (it stays an ask-first choice — see the backend question; SKILL.md does not override the detail file's neutral ordering), recommended *there* because for a non-visual user *observability is the agent querying the wiki* (INDEX + session-start read) — git-versioned, agent-native (grep/Read), no cloud egress; FH's answer to its weak Observability layer. Obsidian (graph-view = the visual observability surface) and cloud are equal options when the user already runs one. **Salience caveat**: the session-start read is prose in `CLAUDE.local.md` (no SessionStart hook) on a weak tier it may silently not fire; accepted, revisit if a target-tier sim measures a miss.
91
+ **Set the store up as a queryable WIKI, not an empty folder** — the store earns its value when the agent can *read it well*, so the scaffold includes: an `INDEX.md` wiki-home (**the `CATALOG.md` read-first pattern applied to the store** — named INDEX to avoid two CATALOG.md across the public + private repos; section map + **read-time-derived** pointers, never hand-maintained placeholders that rot) · session-start read wiring the AI **idempotently adds** to `CLAUDE.local.md` (grep-guarded against duplication: read INDEX → open result/signal files newer than the session card, not only handoffs) · the Raw/Wiki/Conversation ingest axis (`sync_push_protocols.md`). The git `*-be` form is the **default within the no-existing-store branch** (it stays an ask-first choice — see the backend question; SKILL.md does not override the detail file's neutral ordering), recommended *there* because for a non-visual user *observability is the agent querying the wiki* (INDEX + session-start read) — git-versioned, agent-native (grep/Read), no cloud egress; FH's answer to its weak Observability layer. Obsidian (graph-view = the visual observability surface) and cloud are equal options when the user already runs one. **Mechanical floor (corrected 2026-07-30)**: the `CLAUDE.local.md` read is prose, so the wizard **also registers the `SessionStart` hooks** (`fh_session_load.sh` companion freshness + `fh_env_delta_scan.sh` env-delta) into the gitignored `settings.local.json` — see `SKILL_detail.md §Mode-D-Companion-Setup` step 3. The earlier text here claimed no such hook existed and marked the gap "accepted"; both were wrong, and a clean second-machine install measured the miss (turn-0 load skipped on a task-first entry, on Opus). Prose is the layer **over** the floor, never the floor.
92
92
 
93
93
  **Beyond Mode D (not forced)**: a non-developer user who accumulates their own context into FH and keeps no separate local store benefits too — but since accumulation is unobservable at *first* install, surface this offer only on a **re-run where FH-written artifacts already exist** (e.g. non-empty `tracks/*/session_*.md`), not preemptively at first setup. A user with a local store, or with no FH artifacts, is not prompted.
94
94
 
@@ -93,8 +93,71 @@ IDX
93
93
  WIRING="At session start: read \$BE_DIR/INDEX.md first; then ls -t paper-signals/ handoff/ digests/ and open anything newer than the session card (card=pointer, store commit=truth) — not only handoffs."
94
94
  grep -qF "read \$BE_DIR/INDEX.md first" "$HUB_DIR/CLAUDE.local.md" 2>/dev/null \
95
95
  || printf '\n## Companion-store session-start read\n%s\n' "$WIRING" >> "$HUB_DIR/CLAUDE.local.md"
96
+
97
+ # 3. MECHANICAL FLOOR over that prose — register the SessionStart hooks (REQUIRED, not optional).
98
+ # WHY: step 2 writes an INSTRUCTION into CLAUDE.local.md. An instruction is salience: on task-first
99
+ # entry (the user's first message is a task, so the onboarding menu is correctly suppressed) or on a
100
+ # weaker tier, it silently does not fire and the session runs on stale local state. The two hooks
101
+ # below fire BEFORE turn 0 regardless of what the user types.
102
+ # - fh_session_load.sh → companion freshness (this section's own load)
103
+ # - fh_env_delta_scan.sh → undeployed-sibling-repo discovery (CLAUDE.md claim ②; rated
104
+ # PARTIAL/THEATER by the 2026-07-06 three-family audit while prose-only)
105
+ # Registration lives in the GITIGNORED .claude/settings.local.json — BE_DIR is an operator-private
106
+ # path and must never land in the project-shared settings.json. Idempotent: re-running replaces, never dups.
107
+ # Measured miss 2026-07-30 (n=2, a freshly installed second machine): neither hook was registered,
108
+ # so the Mode D load ran only because the operator happened to open with a greeting. A task-first
109
+ # first message would have skipped it entirely — on Opus, not merely on a weak tier.
110
+ if [ -n "${BE_DIR:-}" ] && [ -d "$HUB_DIR/scripts" ]; then
111
+ # The real $BE_DIR is passed IN and baked into the command — never a "<your-store>" placeholder
112
+ # for the user to swap later. A placeholder written into a config file is a hook that reports
113
+ # "registered" and then silently resolves to a nonexistent path; the Sonnet target-tier sim
114
+ # (2026-07-30) named exactly this failure — the script prints success, the operator stops, the
115
+ # hook is dead. If a value must be substituted, substitute it at write time or do not write.
116
+ python3 - "$HUB_DIR" "$BE_DIR" <<'PY'
117
+ import json, os, sys, collections
118
+ hub, be = sys.argv[1], sys.argv[2]
119
+ p = os.path.join(hub, ".claude", "settings.local.json")
120
+ d = collections.OrderedDict()
121
+ if os.path.exists(p):
122
+ with open(p) as fh:
123
+ d = json.load(fh, object_pairs_hook=collections.OrderedDict)
124
+ cmd = lambda s: {"type": "command",
125
+ "command": f'BE_DIR="{be}" bash "$CLAUDE_PROJECT_DIR/scripts/{s}"',
126
+ "timeout": 20}
127
+ if os.path.exists(p): # back up before rewriting someone's config; a traceback mid-write truncates
128
+ import shutil; shutil.copy2(p, p + ".prewizard")
129
+ hooks = d.setdefault("hooks", collections.OrderedDict())
130
+ # Filter at HOOK level, not GROUP level: a user hook sharing a group with an FH hook would otherwise
131
+ # be deleted with the group (cross-family review 2026-07-30 reproduced that loss).
132
+ FH_HOOKS = ("fh_session_load.sh", "fh_env_delta_scan.sh")
133
+ existing = []
134
+ for g in hooks.get("SessionStart", []):
135
+ survivors = [h for h in g.get("hooks", [])
136
+ if not any(n in h.get("command", "") for n in FH_HOOKS)]
137
+ if survivors:
138
+ g = dict(g); g["hooks"] = survivors; existing.append(g)
139
+ hooks["SessionStart"] = existing + [
140
+ {"matcher": "", "hooks": [cmd("fh_session_load.sh"), cmd("fh_env_delta_scan.sh")]}]
141
+ with open(p, "w") as fh:
142
+ json.dump(d, fh, indent=2, ensure_ascii=False); fh.write("\n")
143
+ print("SessionStart hooks registered ->", p, "(backup: .prewizard)")
144
+ PY
145
+ # VERIFY by running it once — a registration that was never executed is not a floor.
146
+ # Expected: a "companion-store freshness" line on stdout. If it prints nothing, BE_DIR is wrong.
147
+ BE_DIR="$BE_DIR" bash "$HUB_DIR/scripts/fh_session_load.sh" | head -3
148
+ fi
96
149
  ```
97
150
 
151
+ > **Known-pair check before calling this done** (per `measurement-integrity-checklist.md
152
+ > §Instrument-Calibration`) — **run both legs, do not copy this verdict**:
153
+ > - **known-positive** — `BE_DIR` set → a `companion-store freshness` line prints.
154
+ > - **known-negative** — `BE_DIR` unset → **no companion block prints** and the script exits 0.
155
+ > It is *not* fully silent: the frontier-digest section runs above the Mode-D guard and speaks to
156
+ > every user about their own local digest. That is intended. An earlier draft of this note claimed
157
+ > the negative leg was silent and made that a ship-blocker; running it showed 171 bytes of correct
158
+ > frontier output — the note was written without executing the check it prescribed.
159
+ > If the negative leg prints anything referencing the **companion store**, do not ship.
160
+
98
161
  - **Raw / Wiki / Conversation ingest axis** (`sync_push_protocols.md`): classify each artifact by
99
162
  processing stage — Raw (unprocessed capture) → stays raw; Wiki (distilled + `[[linked]]`) → the
100
163
  compounding layer; Conversation (dialogue/decision log). The Raw→Wiki distill is where linking earns
@@ -102,9 +165,16 @@ grep -qF "read \$BE_DIR/INDEX.md first" "$HUB_DIR/CLAUDE.local.md" 2>/dev/null \
102
165
  - **Backend note**: for an **Obsidian** backend the graph view is the *visual* observability surface
103
166
  (free for that backend); for the recommended **git `*-be`** form, observability is the agent querying
104
167
  INDEX + sections (no visualization needed). gbrain ingests the same markdown.
105
- - **Salience caveat**: the CLAUDE.local.md session-start read is prose (no SessionStart hook) — on a weak
106
- tier it may silently not fire. Accepted limitation (mirrors `operational_adaptation.md §Guards`);
107
- revisit if a target-tier sim measures a miss.
168
+ - **Salience → mechanical (corrected 2026-07-30)**: this used to read *"the session-start read is prose
169
+ (no SessionStart hook) accepted limitation; revisit if a target-tier sim measures a miss."* **Both
170
+ halves were wrong by then.** The hook exists (`scripts/fh_session_load.sh`, shipped 2026-07-05), and
171
+ the revisit-trigger has fired: a freshly installed second machine ran with **neither** SessionStart
172
+ hook registered, on Opus — the load fired only because that session happened to open with a greeting
173
+ instead of a task. So step 3 above **registers the hooks**; the CLAUDE.local.md prose stays as the
174
+ human-readable layer *over* that floor, never as the floor itself.
175
+ **Residual (named, not closed)**: registration lives in the gitignored `settings.local.json`, so a
176
+ user who re-clones the hub without re-running the wizard silently loses it again — the honest
177
+ backstop is `install-doctor`'s check item, not the wizard alone.
108
178
 
109
179
 
110
180
  ---
@@ -446,6 +516,50 @@ source "$FH_DIR/templates/fh_audit_check.zsh"
446
516
  EOF
447
517
  fi
448
518
 
519
+ # Node floor check hook — ALL users, not Mode D only. Source of truth = the tracked snippet
520
+ # templates/settings.SessionStart.snippet.json (`project_settings_json` key). Registration itself
521
+ # cannot be tracked (every .claude/settings*.json path is gitignored), so the wizard is what wires it
522
+ # — which is exactly why this must not be skipped: without it, a user on a fresh machine gets no
523
+ # turn-0 signal that their floors are missing.
524
+ # NPM-INSTALL PRECONDITION: this block reads the snippet from disk, so both it and
525
+ # scripts/fh_node_check.sh must be in package.json `files[]`. They are (added 2026-07-30 after
526
+ # scripts/package_coverage_check.sh caught the omission — without it an npm-installed wizard hit
527
+ # FileNotFoundError here and registered nothing while reporting success upstream).
528
+ python3 - "$FH_DIR" <<'PY'
529
+ import json, os, sys, collections
530
+ hub = sys.argv[1]
531
+ snippet = os.path.join(hub, "templates", "settings.SessionStart.snippet.json")
532
+ target = os.path.join(hub, ".claude", "settings.json")
533
+ entry = json.load(open(snippet))["project_settings_json"]["hooks"]["SessionStart"]
534
+ d = collections.OrderedDict()
535
+ if os.path.exists(target):
536
+ d = json.load(open(target), object_pairs_hook=collections.OrderedDict)
537
+ import shutil; shutil.copy2(target, target + ".prewizard") # back up before rewriting
538
+ hooks = d.setdefault("hooks", collections.OrderedDict())
539
+ # Merge at HOOK level, not group level. A group-level filter drops the whole group when a user's own
540
+ # hook shares a group with the FH one — the common shape when someone hand-edits or appends to an
541
+ # older wizard's output. (Cross-family review 2026-07-30 reproduced the loss: a group holding
542
+ # [my_telemetry.sh, fh_session_load.sh] lost my_telemetry.sh entirely.)
543
+ kept = []
544
+ for g in hooks.get("SessionStart", []):
545
+ survivors = [h for h in g.get("hooks", []) if "fh_node_check" not in h.get("command", "")]
546
+ if survivors:
547
+ g = dict(g); g["hooks"] = survivors; kept.append(g)
548
+ hooks["SessionStart"] = kept + entry
549
+ os.makedirs(os.path.dirname(target), exist_ok=True)
550
+ with open(target, "w") as fh:
551
+ json.dump(d, fh, indent=2, ensure_ascii=False); fh.write("\n")
552
+ print("node-check SessionStart hook registered ->", target)
553
+ PY
554
+ chmod +x "$FH_DIR/scripts/fh_node_check.sh" 2>/dev/null
555
+ # VERIFY against a THROWAWAY state file (FH_NODE_STATE). Verifying against the real state would
556
+ # consume the user's one-shot event report, so their actual first session goes quiet and the notice
557
+ # is buried in install output instead (cross-family review 2026-07-30).
558
+ # known-pair: healthy machine → run 1 prints an event line, run 2 is SILENT.
559
+ # missing floor → prints EVERY run (a floor gap is a condition, not an event).
560
+ _T="$(mktemp)"; FH_NODE_STATE="$_T" bash "$FH_DIR/scripts/fh_node_check.sh"
561
+ FH_NODE_STATE="$_T" bash "$FH_DIR/scripts/fh_node_check.sh"; rm -f "$_T"
562
+
449
563
  # 4-axis verification gate (Mode D / FH-self-development only — OPT-IN, double-confirm required)
450
564
  # SCOPE (state this before asking): this gates commits IN YOUR FH CLONE ($FH_DIR) — git commit there is
451
565
  # blocked until the 4-axis markers pass. It is FH-internal infra (hardcodes hub paths/markers) and is