@chrono-meta/fh-gate 1.4.58 → 1.4.60
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-plugin/marketplace.json +2 -2
- package/CATALOG.md +27 -0
- package/CLAUDE.md +84 -88
- package/README.ja.md +8 -7
- package/README.ko.md +7 -6
- package/README.md +7 -6
- package/README.zh.md +5 -5
- package/bin/fh-codex-doctor.js +34 -3
- package/bin/fh-gate.js +17 -5
- package/bin/fh-goal.js +13 -5
- package/bin/fh-run.js +13 -5
- package/knowledge/shared/harness-core/claude_md_gate_details.md +88 -1
- package/knowledge/shared/harness-core/fh_detail_protocols.md +20 -4
- package/knowledge/shared/harness-core/harness_incubator_doctrine.md +30 -11
- package/knowledge/shared/harness-core/ship_readiness_gate.md +15 -9
- package/package.json +2 -1
- package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-meta/skills/harness-doctor/SKILL.md +109 -10
- package/scripts/count_check.sh +8 -1
- package/scripts/fh-gate.sh +150 -13
- package/scripts/fh-goal.sh +46 -5
- package/scripts/fh-run.sh +11 -0
- package/scripts/selfcheck.sh +40 -10
- package/scripts/test_fh_gate_regressions.sh +208 -0
|
@@ -103,9 +103,79 @@ pass/block verdict* — e.g. `agent-composer`, `goal-quench`, `asset-placement-g
|
|
|
103
103
|
|
|
104
104
|
---
|
|
105
105
|
|
|
106
|
+
## §Cross-Family-Complement
|
|
107
|
+
|
|
108
|
+
Execution detail for CLAUDE.md §FH Improvement 4-Axis Auto-Gate → *Cross-family complement*. The rule
|
|
109
|
+
that a load-bearing change recruits ≥1 different-family auditor, that it is autonomous once consented,
|
|
110
|
+
and that the governor keeps the terminal verdict + source-grounds every finding — all stay in CLAUDE.md.
|
|
111
|
+
|
|
112
|
+
**Sidecar mapping (per the UAP)** — pick by task class, not by preference:
|
|
113
|
+
|
|
114
|
+
| Task class | Recruit | Why |
|
|
115
|
+
|---|---|---|
|
|
116
|
+
| Repo-grounded code / security audit | `codex` (`gpt-5.5`, xhigh) | reads the actual tree; strongest on verdict-code logic |
|
|
117
|
+
| Breadth / multimodal / frontier scan | `agy` (Gemini) | wide recall, video + image ingest |
|
|
118
|
+
| Batch / free-tier arm | local 4090 over Tailscale | no token cost; weaker judge — anchor it |
|
|
119
|
+
|
|
120
|
+
**Degrade**: when no different-family auditor is reachable, say so and fall back to single-session — but
|
|
121
|
+
note the exception in §Field-Harness Load-Bearing Change Gate, where an unreachable cross-family panel is
|
|
122
|
+
**NOT-CONVERGED** rather than a silent same-family pass (that surface is pre-merge and irreversible-adjacent).
|
|
123
|
+
|
|
124
|
+
**Dogfood evidence (2026-06-27)**: a cross-family pass caught a HIGH execution-side-effect blind spot that
|
|
125
|
+
the same-family reviewers **and** the target-tier sim all shared. That is the decorrelation value made
|
|
126
|
+
concrete: the miss was not a depth failure (the sim ran at the right tier) but a *correlation* failure —
|
|
127
|
+
every reviewer in the Claude family read the change the same optimistic way. Decorrelation is the only
|
|
128
|
+
lever that moves a correlated blind spot; more same-family review does not.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## §Destructive-Op-Hook-Coverage
|
|
133
|
+
|
|
134
|
+
Execution detail for CLAUDE.md §Destructive-Op Gate. The **order invariant** (enumerate → recover →
|
|
135
|
+
destroy), the **3 steps**, the `DESTRUCTIVE_OP_OK=1` override, and the **fail-closed degrade direction**
|
|
136
|
+
stay in CLAUDE.md — they are load-bearing every session. What follows is the mechanics + honest scope.
|
|
137
|
+
|
|
138
|
+
**Per-ref verdict (pre-push hook)**: the hook detects the destructive refspec on stdin — *delete* = local
|
|
139
|
+
SHA all-zeros; *force* = remote SHA not an ancestor of local — then judges each ref:
|
|
140
|
+
|
|
141
|
+
| Ref state | Verdict | Hook action |
|
|
142
|
+
|---|---|---|
|
|
143
|
+
| Branch delete, fully merged | SAFE | allowed |
|
|
144
|
+
| Branch delete, commits off base + 0 unique paths | CHECK | **blocked** — needs a judged content look |
|
|
145
|
+
| Branch delete, unique paths present | REVIEW | **blocked** — recovery mandatory |
|
|
146
|
+
| Force / non-ff push | — | **always blocked** |
|
|
147
|
+
| Tag / notes delete | — | **always blocked** |
|
|
148
|
+
|
|
149
|
+
The verdict is load-bearing, not decorative: a merged-branch cleanup passes, a silent-loss CHECK does not.
|
|
150
|
+
This is the enumerate step as a mechanical floor rather than prose.
|
|
151
|
+
|
|
152
|
+
**What it does and does NOT close (honest)**: it closes the **honest-weak-model** gap — an agent that
|
|
153
|
+
simply *forgot* the prose gate is now mechanically stopped. It does **not** close the
|
|
154
|
+
**injected/adversarial** gap: an agent under instruction can set the override or pass `--no-verify`, and a
|
|
155
|
+
client-side hook is readable and bypassable by design. The hard floor for the adversarial case is
|
|
156
|
+
**server-side branch protection** (GitHub *Restrict deletions* / *Restrict force pushes*) — this hook is
|
|
157
|
+
the honest-model floor, branch protection is the hard floor.
|
|
158
|
+
|
|
159
|
+
**Scope**: covers only git pushes *from a hook-installed repo*. `npm publish` is mechanized separately via
|
|
160
|
+
`prepublishOnly` (see §Pre-Publish-Hook-Coverage (c)); the remaining non-git surface — a separate-repo
|
|
161
|
+
`gh repo create --public` / visibility flip — is genuinely un-hookable and stays prose +
|
|
162
|
+
`PRE-PUBLISH-CHECKLIST.md`.
|
|
163
|
+
|
|
164
|
+
**Portability defect class**: the hook is bash-3.2 safe (macOS default `/bin/bash`). The original draft
|
|
165
|
+
used a bash-4 associative array that crashed **fail-OPEN** on 3.2 — caught in test. Worth naming: a
|
|
166
|
+
portability break in a gate degrades toward permissive unless the gate is written to fail closed on its
|
|
167
|
+
own errors.
|
|
168
|
+
|
|
169
|
+
**Origin (2026-06-10 branch cleanup)**: pre-deletion enumeration recovered a parallel session's card
|
|
170
|
+
(weekly-audit completion + #88 merge state) that existed **only on an unmerged branch** with zero unique
|
|
171
|
+
paths — exactly the CHECK class, and invisible to "is it merged?" intuition. Deletion without the gate
|
|
172
|
+
destroys live state without anyone noticing. This is why the loss class is called *silent*.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
106
176
|
## §Pre-Publish-Hook-Coverage
|
|
107
177
|
|
|
108
|
-
**Hook coverage —
|
|
178
|
+
**Hook coverage — three distinct actions** (refined 2026-06-17 for (a)/(b); (c) added 2026-06-27):
|
|
109
179
|
- **(a) repo-go-public** (`gh repo create --public` / a visibility flip) is irreversible and usually in a
|
|
110
180
|
**separate repo** — the FH pre-commit hook **cannot** catch it. That stays **AI-behavioral** (proactive
|
|
111
181
|
trigger) **+ a portable checklist** (`templates/PRE-PUBLISH-CHECKLIST.md`), run on any repo/machine.
|
|
@@ -126,6 +196,23 @@ pass/block verdict* — e.g. `agent-composer`, `goal-quench`, `asset-placement-g
|
|
|
126
196
|
plaintext only (encoded tokens out of scope); a line-split backstop catches a token wrapped across
|
|
127
197
|
lines; `PUBLIC_SURFACE_OK=1` overrides and is logged to a gitignored audit trail for the weekly audit.
|
|
128
198
|
Residuals (split-encoding, override-not-populated, override abuse) are documented, not silent.
|
|
199
|
+
- **(c) `npm publish`** — mechanically gated by `scripts/public_surface_scan_files.sh`, wired into
|
|
200
|
+
`prepublishOnly` (`npm run release` also runs it *outside* the lifecycle). Unlike (b) it scans the
|
|
201
|
+
**full content of the exact npm-published file set** (`npm pack --dry-run`), *not* a commit diff — so a
|
|
202
|
+
token committed **before the scan existed**, or carried in a `files[]` entry, is still caught at the
|
|
203
|
+
registry boundary. HIGH/MED block; `PUBLIC_SURFACE_OK=1` overrides + logs. **Fail-closed** when patterns
|
|
204
|
+
or the file set are unresolved, when the parse looks partial, **or when the gitignored operator override
|
|
205
|
+
is absent** — defaults-only would otherwise green-PASS a HIGH company literal on a fresh clone or CI runner.
|
|
206
|
+
|
|
207
|
+
**Named residuals for (c)** — it is a denylist **on the npm CLI path with scripts enabled**, not a
|
|
208
|
+
universal secret-scanner:
|
|
209
|
+
|
|
210
|
+
| # | Residual | Mitigation |
|
|
211
|
+
|---|---|---|
|
|
212
|
+
| i | `npm publish --ignore-scripts`, a CI `.npmrc` with `ignore-scripts=true`, or `pnpm`/`yarn publish` **skip the lifecycle hook entirely** | route publishes through `npm run release`, or add an explicit CI scan step |
|
|
213
|
+
| ii | scans only the **loaded patterns** — an **un-patterned secret shape** (an API key the patterns don't describe) still ships | pattern coverage is the limit; pair with a real secret-scanner if that shape matters |
|
|
214
|
+
| iii | on a runner without the gitignored override it is **defaults-only** unless populated | populate the override in each authoring env (esp. the company env) |
|
|
215
|
+
| iv | scans **working-tree content, not the final tarball bytes** | benign today (content-neutral lifecycle: prepare=chmod, no prepack) — **re-open if a content-generating publish lifecycle is added** (cross-family audit 2026-06-27) |
|
|
129
216
|
|
|
130
217
|
> Origin: 2026-06-05 `phantom-gate` shipped public, then needed a private→de-company-scrub→re-public
|
|
131
218
|
> round-trip (`fh_signal_2026-06-05_fh-direct`). PSA existed but nothing forced it pre-publish. 2026-06-17
|
|
@@ -58,10 +58,26 @@ Then **fail-closed** (irreversible-ish: a silent empty overwrite blinds the bus)
|
|
|
58
58
|
**and** the existing registry has >0 entries, do **not** overwrite — flag `⚠️ scan returned 0 (root=$ROOT);
|
|
59
59
|
kept existing registry` and skip the rewrite. Only rewrite when the scan is non-empty (or the registry
|
|
60
60
|
was absent). Group by project (parent dir name). Record per skill: name · path · description · trigger
|
|
61
|
-
phrases · `requires_cwd` · `direct-executable` · `origin(FH|project|external)`+trust
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
phrases · `requires_cwd` · `direct-executable` · `origin(FH|project|external)`+trust ·
|
|
62
|
+
**`residency(public|company|operator-private)`** · **`generality(general-purpose|project-specific)`**.
|
|
63
|
+
**Non-FH skills are propose-only (ask-tier), never auto-run** — a cross-project skill body is an
|
|
64
|
+
injection surface. Propose cross-project skills when a request maps to the registry. Scan once per
|
|
65
|
+
session. (Detection belongs at install too — `/install-wizard` records HUB/ROOT so the runtime never
|
|
66
|
+
guesses; see install-wizard.)
|
|
67
|
+
|
|
68
|
+
**`residency` derivation (mechanical, not asserted)** — from the project's git remote at scan time:
|
|
69
|
+
company org/account (e.g. a known company-dev namespace) → `company`; the operator's own account, repo
|
|
70
|
+
not `public` on the host → `operator-private`; else → `public`. **`generality` derivation (judged, not
|
|
71
|
+
mechanical — the scan flags a candidate, a session confirms)**: a skill whose description names no
|
|
72
|
+
project/company-specific noun and needs no project-local context to run elsewhere → `general-purpose`
|
|
73
|
+
candidate; confirmed only when a session actually reads the skill body and judges it works outside its
|
|
74
|
+
origin project (never auto-confirmed from the tag alone — chamber run #7, 2026-07-14, found the
|
|
75
|
+
generality field itself absent and the confirmed-general-purpose seed count effectively 0, which is
|
|
76
|
+
exactly the gap these two fields close). **Output landing-surface rule** (residency-restricted
|
|
77
|
+
combinations must never reach a public surface): any derived recommendation, "better-together" list, or
|
|
78
|
+
synergy output that names a `company`/`operator-private` residency entry lands **only** in gitignored
|
|
79
|
+
`tracks/_meta/` (or the private companion store) — **never** in tracked `tracks/{project}/` or any other
|
|
80
|
+
public-tracked file. A `public`-only combination may land in tracked docs.
|
|
65
81
|
|
|
66
82
|
### Step 2 — Active Proposal
|
|
67
83
|
|
|
@@ -99,19 +99,38 @@ this chamber's field emit terminus); an **FH-internal utility** (a skill/script/
|
|
|
99
99
|
field harness) instead routes through the **New-Skill Pre-Commit gate + `asset-placement-gate`** (the
|
|
100
100
|
same gate every FH asset passes). KILL emits nothing — the workspace stays as the evidence record.
|
|
101
101
|
|
|
102
|
-
**EMIT-worthiness — the measured screening criterion (
|
|
103
|
-
all KILL.
|
|
104
|
-
|
|
105
|
-
|
|
102
|
+
**EMIT-worthiness — the measured screening criterion (runs #5–#6, 2026-07-14)**: six chamber runs, EMIT
|
|
103
|
+
0/6, all KILL. A candidate is emit-worthy only if it clears **all four** of — (1) **net-new** (not a
|
|
104
|
+
reinvention of an existing FH/official asset, nor a cosmetic re-wrap of code that already ships — runs
|
|
105
|
+
#2–#4 died here, and run #6 partially here too — its core was already conceived in a parked FH signal);
|
|
106
106
|
(2) **artifact-shaped** (a tool/script/rule that stands alone, *not* a judgment-method — run #5's genuine
|
|
107
107
|
niche was real, but its value lived in a scan∪cross-family *lens*, i.e. an LLM judgment, which cannot be
|
|
108
|
-
`npm publish`ed); (3) **real-code-precision-adequate** (its mechanical form, measured on real
|
|
109
|
-
does not cry-wolf — run #5's rule scored 5/5 false-positive on 111 real files
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
108
|
+
`npm publish`ed); (3) **real-code/real-data-precision-adequate** (its mechanical form, measured on real
|
|
109
|
+
external inputs, does not cry-wolf — run #5's rule scored 5/5 false-positive on 111 real files; run #6's
|
|
110
|
+
heuristic scored 14/22 false-fire on a real sibling-folder scan); (4) **hub-state-independent** (run #6,
|
|
111
|
+
new axis — a capability whose value structurally depends on hub-held state, e.g. the curated registry +
|
|
112
|
+
company-residency knowledge, is not a standalone-first candidate: run #6's `harness-orchestrator` hit
|
|
113
|
+
private/company repos it structurally could not know to suppress, because residency knowledge lives only
|
|
114
|
+
in the hub. Contrast with fh-commons's 4 skills, which graduated cleanly to portable precisely because they
|
|
115
|
+
never depended on hub state). 0/6 candidates cleared all four. This is not "keep trying" — it is a
|
|
116
|
+
**pre-screen for future candidates**, cheapest-to-costliest: (1)/(2)/(4) are cheap to predict from the
|
|
117
|
+
candidate's own design (does it need hub-only knowledge to work correctly?); only (3) needs a measurement
|
|
118
|
+
leg (a real-input precision run), which runs #5–#6 established as the decisive test. The chamber's honest
|
|
119
|
+
value to date remains *screening* — preventing reinventions, low-precision births, and premature
|
|
120
|
+
standalone graduations — not yet *birthing*. **Graduation order** (run #6's positive finding): a
|
|
121
|
+
hub-state-dependent capability graduates hub-internal → proven in use → THEN extracted portable, never
|
|
122
|
+
speculated standalone-first — the only path every successfully-portable FH asset actually took.
|
|
123
|
+
|
|
124
|
+
**Chamber scope — what belongs in the chamber at all (run #7, 2026-07-14)**: run #7 tested a hub-internal
|
|
125
|
+
reactivation of the cluster-wizard signal and KILLed it — decisively on its own merits (its "narrow
|
|
126
|
+
net-new" claim collapsed against the real shipped registry and an already-existing synergy skill), but
|
|
127
|
+
it also surfaced a scope question worth keeping regardless: **a small feature graft onto an
|
|
128
|
+
already-shipped hub-internal mechanism is ordinary Mode D self-development under the 4-axis gate, not
|
|
129
|
+
automatically a chamber-EMIT question.** The chamber screens candidates that would become a **new
|
|
130
|
+
independent artifact** (a skill, a plugin, a standalone tool) — not every internal feature extension.
|
|
131
|
+
Route by this test: *would this, if built, be net-new as a standalone thing someone installs/adopts, or
|
|
132
|
+
is it two lines added to something already shipped?* The former is chamber-scope; the latter is ordinary
|
|
133
|
+
self-dev review.
|
|
115
134
|
|
|
116
135
|
*Vocabulary reservation (term hygiene, not standardization)*: a run of this skeleton is a **chamber
|
|
117
136
|
run** — going forward, run/workspace/log labels use "chamber" for incubation and keep "sim/simulation"
|
|
@@ -109,20 +109,26 @@ decision is logged here and the tag's notes state the real status.
|
|
|
109
109
|
| ③ | 거버넌스 게이트 (governance) | 🟢 GREEN | pre-commit/pre-push physically block; moat measured 3–4 family blind (HITL 8/8 ABSENT); cross-family caught a real companion-store-name leak 2026-07-14 (fail-closed) |
|
|
110
110
|
| ⑤ | 증폭자 (amplifier) | 🟢 GREEN | short-intent→literature-grounding→ultimate-doc real instances; rules-diet −18.2k measured; intent-routing probe 94% (below) |
|
|
111
111
|
| ④ | 프런티어→조직 전파 | 🟡 YELLOW | frontier-digest launchd auto + AX submission docs both real, but digest→org never closed as ONE pipeline |
|
|
112
|
-
| ① | 멀티하네스 클러스터
|
|
113
|
-
| ② | 프로젝트 인큐베이터
|
|
112
|
+
| ① | 멀티하네스 클러스터 | 🟡 PARTIAL | routing runs for real — 17 nodes mapped, sidecar-orchestrator, Skill Bus routing qasp/dashboard/stockbattle (so NOT 🔴 ideal-only). Missing: continuous 2-node relay channel + external-harness recommend (cluster-wizard parked) → 🟡 not 🟢 |
|
|
113
|
+
| ② | 프로젝트 인큐베이터 | 🟡 PARTIAL | incubation is running — **stockbattle is being incubated now** (S1 built, mid-flight) + qasp/pmh spin-out precedent + scaffold-emit shipped (doctrine: "emit shipped today as scaffold+approval; the chamber flow is the named target"). What's still 0 is the **formal chamber simulate-then-emit** mechanism (6 runs, 6 KILL — runs #5–#6 *measured* the emit-worthiness criterion: net-new ∧ artifact-shaped ∧ real-data-precision-adequate ∧ hub-state-independent, 0/6 cleared all four; run #6 also confirmed the graduation-order principle — hub-internal proof before standalone extraction, never the reverse). That mechanism is ONE path of ②, not the whole identity → 🔴 was too narrow; incubation runs but no closed emit-via-incubation yet → 🟡 |
|
|
114
114
|
|
|
115
115
|
**Cross-cutting measured (intent-based autonomous completion)**: blind floor-tier Sonnet trigger-accuracy
|
|
116
116
|
probe (n=10, 2026-07-14): **should-fire 7.5/8 (94%), false-fire 0/2**. One weak trigger (simulate-first /
|
|
117
117
|
incubator entry absorbed into deep-clarify) — the identity-② weakness surfaces in routing too.
|
|
118
118
|
|
|
119
|
-
**Verdict (2026-07-14)**: FH is tagged **`v0.1.0` = honest baseline**, not all-green. ③⑤ are
|
|
120
|
-
|
|
121
|
-
above). **`v1.0.0` remains the all-green target.** What blocks v1.0 is **
|
|
122
|
-
2-node
|
|
123
|
-
measured
|
|
124
|
-
artifact, tracked in `tracks/_meta/identity_audit_*.md`.
|
|
125
|
-
|
|
119
|
+
**Verdict (2026-07-14, ①② corrected)**: FH is tagged **`v0.1.0` = honest baseline**, not all-green. ③⑤ are
|
|
120
|
+
🟢, ①②④ 🟡, **none 🔴** — the `v0.1.0` notes state this and make no all-green claim (per the refined 0.x↔1.0
|
|
121
|
+
mapping above). **`v1.0.0` remains the all-green target.** What blocks v1.0 is **closing the 🟡s**: ①'s
|
|
122
|
+
continuous 2-node relay channel, ②'s first closed emit-via-incubation (formal chamber first EMIT — criterion
|
|
123
|
+
measured in run #5 — or a chamber-incubated spin-out closing), ④'s closed digest→org pipeline. Each remedy
|
|
124
|
+
is a run that leaves an artifact, tracked in `tracks/_meta/identity_audit_*.md`.
|
|
125
|
+
|
|
126
|
+
> **①② correction (2026-07-14)**: an earlier pass marked ①② 🔴 by collapsing each identity onto its most
|
|
127
|
+
> advanced *single mechanism* — ② onto the formal chamber EMIT (0/5), ① onto the continuous-relay channel.
|
|
128
|
+
> That contradicts the doctrine (emit is "shipped today as scaffold+approval; the chamber is the named
|
|
129
|
+
> target") and the live reality (routing runs; **stockbattle is being incubated now**; qasp/pmh spun out).
|
|
130
|
+
> An identity whose broad path *runs* is not 🔴 ideal-only. Both are 🟡: running, not yet closed. Lesson:
|
|
131
|
+
> do not score an identity by its hardest sub-mechanism — that reads a live-but-incomplete path as zero.
|
|
126
132
|
|
|
127
133
|
## For a field harness (e.g. pmh, qasp)
|
|
128
134
|
Same gate, its own identities. A field harness ships to its team when its identity checklist is all-green,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chrono-meta/fh-gate",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.60",
|
|
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": [
|
|
@@ -63,6 +63,7 @@
|
|
|
63
63
|
"scripts/fh-goal.sh",
|
|
64
64
|
"scripts/count_check.sh",
|
|
65
65
|
"scripts/selfcheck.sh",
|
|
66
|
+
"scripts/test_fh_gate_regressions.sh",
|
|
66
67
|
"templates/local_fh_context.md",
|
|
67
68
|
"plugins/fh-meta/.claude-plugin/plugin.json",
|
|
68
69
|
"plugins/fh-meta/skills",
|
|
@@ -70,29 +70,128 @@ confirms the gap*, never a verdict the report emits on its own; surface each gap
|
|
|
70
70
|
|
|
71
71
|
### Step 3. L2 — Complexity Diagnosis
|
|
72
72
|
|
|
73
|
+
**Scope the instrument first — field vs meta** (CLAUDE.md §Identity Core Axis): a **field harness** must get
|
|
74
|
+
"simpler over time" (complexity = warning signal), so raw size is a real signal there. A **meta-harness**
|
|
75
|
+
(the FH hub itself) *optimizes* rather than simplifies — **complexity earns its scope**, and the doctrine's
|
|
76
|
+
red flags are **orphaned, redundant, and decorative units, not size**. Applying the field rule to a
|
|
77
|
+
meta-harness produces a false M-tier on healthy growth.
|
|
78
|
+
|
|
79
|
+
**Scope is mechanical, never self-declared** — else any repo dodges the line rows by calling itself meta
|
|
80
|
+
(the self-label loophole CLAUDE.md already names for "docs-only" at the Irreversibility gate). A target is
|
|
81
|
+
**meta** iff its root holds **all three**: `tracks/` **and** `knowledge/` **and** `plugins/` (Step 1's FH-environment
|
|
82
|
+
test). Anything else is **field** — including a repo that merely *contains* skills or a `.claude/` dir:
|
|
83
|
+
|
|
84
|
+
**The test is rooted at the TARGET, never at cwd.** FH's own default mode diagnoses a field project
|
|
85
|
+
*without* switching cwd (CLAUDE.md §Agent Dispatch — "Direct edit … no cwd switch needed"), so a bare
|
|
86
|
+
`[ -d tracks ]` run from the hub misclassifies **every** field target as meta and silently deletes the
|
|
87
|
+
field rows — a permissive misread reachable through the harness's own recommended workflow. Always pass
|
|
88
|
+
the target path explicitly:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
# meta iff all three exist AT THE TARGET ROOT — otherwise field. TARGET is required, never implied by cwd.
|
|
92
|
+
TARGET="${1:?pass the target root explicitly — cwd is not the target}"
|
|
93
|
+
[ -d "$TARGET/tracks" ] && [ -d "$TARGET/knowledge" ] && [ -d "$TARGET/plugins" ] \
|
|
94
|
+
&& echo "scope: meta ($TARGET)" || echo "scope: field ($TARGET)"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Scope is a **coarse instrument-selector, not a security boundary**: `[ -d ]` tests existence, not contents,
|
|
98
|
+
so three empty dirs would flip field→meta. That is acceptable here (the operator names the target; there is
|
|
99
|
+
no adversary picking it) — but it means scope must never gate anything that matters on its own, only *which
|
|
100
|
+
size instrument* is read. The footprint rows below apply to **both** scopes and are the actual verdict.
|
|
101
|
+
|
|
73
102
|
| Check | Verdict |
|
|
74
103
|
|---|---|
|
|
75
|
-
| CLAUDE.md ~
|
|
76
|
-
| CLAUDE.md
|
|
77
|
-
| CLAUDE.md
|
|
78
|
-
|
|
|
104
|
+
| **Field/project** CLAUDE.md 100~200 lines | S-tier warning |
|
|
105
|
+
| **Field/project** CLAUDE.md 200+ lines | M-tier — separation or reduction needed |
|
|
106
|
+
| **Field/project** 15+ `##` sections in CLAUDE.md | S-tier warning |
|
|
107
|
+
| **Meta-harness (FH hub)** CLAUDE.md — raw line / section count | **Not a verdict.** Judge by the always-loaded footprint rows below (char-based = actual token cost) + the doctrine's red flags (orphaned · redundant · decorative). Report the count as context only |
|
|
108
|
+
| **Meta-harness** growth since last run: decompose into *new sections* vs *existing-section growth* (mechanical — diff `##` section names + line counts vs the prior run's commit; **first run / no prior commit → report both as n/a, no tier**) | **Tier is decided by the two counts alone**: **S-tier iff existing-section growth > new-section growth** (the file is thickening faster than it is gaining capability); otherwise advisory, no tier. Report both numbers **and** the new sections' names — the names are *reporting output for the human*, never an input to the tier. Do not judge "was this growth capability-bearing?" per line |
|
|
79
109
|
| SKILL.md > 300 lines AND no `SKILL_detail.md` | S-tier — propose `/salience-splitter` (governance-semantic split, not compression) |
|
|
80
110
|
| Rules files unreferenced in CLAUDE.md | R-tier |
|
|
81
|
-
| Always-loaded footprint
|
|
82
|
-
| Always-loaded footprint > 80k chars | M-tier — same
|
|
111
|
+
| Always-loaded footprint > 40k chars (see scan below for what counts) | S-tier — **lever depends on where the chars live**: rules/detail still auto-loading → relocate to a non-loaded dir (e.g. `knowledge/shared/rules/`), pointers stay in CLAUDE.md · narrative inside CLAUDE.md → `/salience-splitter` · **behavioral content only, nothing left to relocate** → capability-level (merge/retire a governance unit) |
|
|
112
|
+
| Always-loaded footprint > 80k chars | M-tier — same lever selection, mandatory, **and never self-discharged** (see below) |
|
|
83
113
|
| **Pointer-illusion**: a CLAUDE.md "detail/detailed procedure" pointer whose target is itself an always-loaded `.claude/rules/*.md` | S-tier — the split saves zero context (rules/ auto-loads regardless); move the target out of auto-load, keep the pointer |
|
|
84
114
|
| weekly_audit 14~30 days elapsed | S-tier |
|
|
85
115
|
| weekly_audit 30+ days elapsed | M-tier |
|
|
86
116
|
|
|
117
|
+
**Per-unit ≠ aggregate — do not slide between them.** "Every section earns its scope" (the per-unit
|
|
118
|
+
doctrine test) and "the always-loaded total is affordable" (the budget test) are **different questions, and
|
|
119
|
+
both can be true at once**. A meta-harness can pass the red-flag test on every single section and still be
|
|
120
|
+
over its footprint budget. So a per-unit PASS never discharges the footprint rows — and conversely, a
|
|
121
|
+
footprint M-tier is *not* evidence that some section failed to earn its scope. When footprint is over
|
|
122
|
+
budget but every unit earns its scope, the remaining lever is **capability-level** — merge or retire a
|
|
123
|
+
governance unit — **not** a salience split, which by construction only moves narrative and returns ~nothing
|
|
124
|
+
when the content is behavioral.
|
|
125
|
+
|
|
126
|
+
**No M-tier in this skill is ever self-discharged — not just the footprint one.** "The cost is priced /
|
|
127
|
+
accepted", "it's all necessary", "over budget but fine" are **not** verdicts this skill may reach on its
|
|
128
|
+
own: an M-tier stands in the report and is closed only by an explicit operator acknowledgment logged to
|
|
129
|
+
`tracks/_meta/` (same shape as any other logged override). This is **row-agnostic on purpose** — the
|
|
130
|
+
rationale (a run under ship pressure prices away the one row that fired) is not specific to footprint, so
|
|
131
|
+
scoping the prohibition to a single row would leave every other M-tier open to the same silent PASS. That
|
|
132
|
+
is the default-toward-PASS class `field_verdict_crossfamily_gate.md` exists to catch, committed inside the
|
|
133
|
+
diagnostic that names it. **Report it; do not price it.**
|
|
134
|
+
|
|
135
|
+
> Origin (2026-07-15, dogfood): a run read FH's CLAUDE.md at 891 lines, fired the raw-count M-tier, and
|
|
136
|
+
> prescribed `/salience-splitter`. Measurement inverted both halves: **+244 of the +381 30-day growth (64%)
|
|
137
|
+
> was 6 new governance sections**, each behavioral and salience-passing (two — Voice/Tone, Envelope-Boundary —
|
|
138
|
+
> had been *promoted* to always-loaded precisely because memory-only placement made them miss), and actually
|
|
139
|
+
> running the splitter on 4 sections yielded **−27 lines / −3.2k chars (−3.7%)** — confirming salience-splitter's
|
|
140
|
+
> own Target Selection rule ("splitting a file with only behavioral content adds structure without governance
|
|
141
|
+
> value"). The **footprint** row meanwhile fired M-tier correctly and had been M since 06-15. Two rows measured
|
|
142
|
+
> the same property; the worse instrument drove the verdict. The line-count rows are now field-scoped.
|
|
143
|
+
|
|
87
144
|
Always-loaded + pointer-illusion checks are mechanical (found 2026-07-12 — FH itself shipped ~50k chars of rules/ behind "detail pointers" that saved nothing; the meta-harness blind spot this row closes):
|
|
88
145
|
|
|
89
146
|
```bash
|
|
90
|
-
# always-loaded footprint (chars)
|
|
91
|
-
|
|
92
|
-
#
|
|
93
|
-
|
|
147
|
+
# always-loaded footprint (chars). Counts every file the session loads before turn 1:
|
|
148
|
+
# CLAUDE.md + CLAUDE.local.md + rules files lacking paths: frontmatter + their DIRECT @-imports.
|
|
149
|
+
# EVERY path is rooted at $TARGET — the same root the scope test used. Reading from cwd instead
|
|
150
|
+
# measures whichever harness you happen to be standing in (usually the hub, while diagnosing a
|
|
151
|
+
# field target) — a wrong-target measurement that reports the hub's number as the target's.
|
|
152
|
+
TARGET="${1:?pass the target root explicitly — cwd is not the target}"
|
|
153
|
+
T=0
|
|
154
|
+
for f in "$TARGET/CLAUDE.md" "$TARGET/CLAUDE.local.md"; do
|
|
155
|
+
[ -f "$f" ] && T=$((T + $(wc -c < "$f")))
|
|
156
|
+
done
|
|
157
|
+
# find | while, not a glob: an unmatched glob aborts under zsh, and a here-string of "" still yields
|
|
158
|
+
# one empty line -> head -5 "" errors and the sum breaks. A scan that dies mid-run reports a LOW
|
|
159
|
+
# number (= toward PASS), so guard the empty case explicitly.
|
|
160
|
+
while IFS= read -r f; do
|
|
161
|
+
[ -n "$f" ] || continue
|
|
162
|
+
head -5 "$f" | grep -q '^paths:' || T=$((T + $(wc -c < "$f")))
|
|
163
|
+
done < <(find "$TARGET/.claude/rules" -name '*.md' 2>/dev/null)
|
|
164
|
+
# DIRECT @-imports only (not transitive — a nested import chain is a named residual below).
|
|
165
|
+
# Resolve against the IMPORTING FILE's own dir first, then ~/.claude/. SUM into T — never
|
|
166
|
+
# print-and-ask-the-reader-to-add: a scan that delegates arithmetic re-introduces the judgment
|
|
167
|
+
# it exists to remove, and the omission points toward PASS.
|
|
168
|
+
for f in "$TARGET/CLAUDE.md" "$TARGET/CLAUDE.local.md"; do
|
|
169
|
+
[ -f "$f" ] || continue
|
|
170
|
+
d=$(dirname "$f")
|
|
171
|
+
for i in $(grep -oE '^@[A-Za-z0-9_./-]+' "$f" 2>/dev/null | sed 's/^@//'); do
|
|
172
|
+
for c in "$d/$i" "$HOME/.claude/$i"; do
|
|
173
|
+
[ -f "$c" ] && { T=$((T + $(wc -c < "$c"))); echo " +import $c: $(wc -c < "$c") chars"; break; }
|
|
174
|
+
done
|
|
175
|
+
done
|
|
176
|
+
done
|
|
177
|
+
echo "always-loaded TOTAL ($TARGET): $T chars"
|
|
178
|
+
# Verdict: the HIGHEST tripped threshold wins — 80k supersedes 40k. Reporting only the
|
|
179
|
+
# S-tier row while >80k is a silent FAIL->CONDITIONAL_PASS downgrade.
|
|
180
|
+
[ "$T" -gt 80000 ] && echo " => M-tier (>80k)" || { [ "$T" -gt 40000 ] && echo " => S-tier (>40k)" || echo " => footprint ok"; }
|
|
181
|
+
# pointer-illusion: CLAUDE.md pointers targeting still-auto-loaded rules files (any filename shape)
|
|
182
|
+
grep -oE '\.claude/rules/[A-Za-z0-9_./-]+\.md' "$TARGET/CLAUDE.md" 2>/dev/null | sort -u | while read -r p; do
|
|
183
|
+
[ -f "$TARGET/$p" ] && echo "ILLUSION: $p (pointed-to AND always-loaded)"
|
|
184
|
+
done
|
|
94
185
|
```
|
|
95
186
|
|
|
187
|
+
**Named residuals of this scan** (all documented, none silent): **transitive imports are not followed** —
|
|
188
|
+
an import chain `CLAUDE.md → A.md → B.md` counts A but not B, so a deep chain under-counts *toward PASS*;
|
|
189
|
+
**`head -5 … grep '^paths:'`** is a proxy, not a frontmatter parser — an incidental early `paths:` line
|
|
190
|
+
falsely excludes an always-loaded rule (toward PASS), while a `paths:` below line 5 over-counts (toward
|
|
191
|
+
FAIL, the safe direction); and the scope test reads directory *existence*, so three empty dirs flip
|
|
192
|
+
field→meta (acceptable: the operator names the target, and the footprint rows apply to **both** scopes
|
|
193
|
+
regardless — but it does skip the field-only line rows).
|
|
194
|
+
|
|
96
195
|
### Step 3-L. Language Lint (`--lint` mode only)
|
|
97
196
|
|
|
98
197
|
> Activate with `/harness-doctor --lint` or when harvest-loop surfaces a P10-series signal. Skipped in standard runs.
|
package/scripts/count_check.sh
CHANGED
|
@@ -74,7 +74,14 @@ if [ "$meta_sk" -eq 0 ]; then
|
|
|
74
74
|
fi
|
|
75
75
|
|
|
76
76
|
count_check() { # count_check <label> <file> <expected-string>
|
|
77
|
-
|
|
77
|
+
# Containment, not equality, was the bug: every expected string starts with a digit, so a
|
|
78
|
+
# stale "16 skills + 2 agents" CONTAINS "6 skills + 2 agents" and a plain `grep -q` reported
|
|
79
|
+
# PASS while the count had actually drifted. Guard the boundaries so a longer number cannot
|
|
80
|
+
# satisfy a shorter one, and escape the expected text (it carries `+` and `(` `)`, which are
|
|
81
|
+
# ERE metacharacters) so it is matched as the literal it is meant to be.
|
|
82
|
+
local esc
|
|
83
|
+
esc=$(printf '%s' "$3" | sed 's/[][\.*^$+?(){}|\\/]/\\&/g')
|
|
84
|
+
if read_tree "$2" | grep -qE "(^|[^0-9])${esc}([^0-9]|\$)"; then
|
|
78
85
|
echo "PASS count: $1"
|
|
79
86
|
else
|
|
80
87
|
echo "FAIL count: $1 — expected \"$3\" in $2 (actual: fh-meta ${meta_sk}sk/${meta_ag}ag, fh-commons ${com_sk}sk/${com_ag}ag)"
|