@chrono-meta/fh-gate 1.4.73 → 1.4.74

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,91 @@
1
+ #!/usr/bin/env bash
2
+ # below_floor_scan.sh — standing consumer for below-floor adversarial markers
3
+ #
4
+ # The pre-commit hook accepts a below-floor Axis-2 pass when an operator ack is
5
+ # present, on the promise that "the weekly audit re-queues below-floor markers
6
+ # for floor-tier re-run" (§Floor governance, multi_model_sidecar_strategy.md).
7
+ # This script IS that consumer: it enumerates below-floor markers and reports
8
+ # which ones still await floor-tier re-validation. Read-only, zero side effects.
9
+ #
10
+ # Resolution protocol (append to the marker after acting — machine-greppable):
11
+ # floor-rerun: <YYYY-MM-DD> <model> PASS|FAIL ← Axis 2 re-run at >= floor
12
+ # floor-writeoff: <YYYY-MM-DD> <one-line reason> ← operator writes the ack off
13
+ #
14
+ # Usage: bash scripts/below_floor_scan.sh [repo_root]
15
+ # Exit: 0 = no pending below-floor markers; 1 = pending re-runs found
16
+ # (manual weekly-audit step — Phase 1.5; exit 1 = raise the pending
17
+ # items as S-tier. No automated caller is wired yet.)
18
+
19
+ set -uo pipefail
20
+
21
+ REPO_ROOT="${1:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
22
+ MARKER_DIR="$REPO_ROOT/tracks/_meta"
23
+ PENDING=0
24
+ TOTAL=0
25
+
26
+ echo "── below-floor marker scan: $MARKER_DIR ──"
27
+
28
+ if [ ! -d "$MARKER_DIR" ]; then
29
+ echo " (no tracks/_meta directory — nothing to scan)"
30
+ exit 0
31
+ fi
32
+
33
+ SONNET_PENDING=0
34
+ for m in "$MARKER_DIR"/.axes_23_passed_*.marker; do
35
+ [ -e "$m" ] || continue
36
+ # sonnet-floor lane (Sonnet-Floor Doctrine 2026-07-10): anchored Sonnet passes are
37
+ # first-class at commit time but provisional for judged depth — queued here as
38
+ # R-tier (advisory re-validation), distinct from below-floor's S-tier hard queue.
39
+ if grep -qE '^[[:space:]]*floor-status:[[:space:]]*sonnet-floor' "$m"; then
40
+ name=$(basename "$m")
41
+ rerun=$(grep -m1 -E '^[[:space:]]*floor-rerun:' "$m" \
42
+ | sed -E 's/^[[:space:]]*floor-rerun:[[:space:]]*//' || true)
43
+ writeoff=$(grep -m1 -E '^[[:space:]]*floor-writeoff:' "$m" \
44
+ | sed -E 's/^[[:space:]]*floor-writeoff:[[:space:]]*//' || true)
45
+ if [ -n "$rerun" ]; then
46
+ echo "✅ RESOLVED (re-run) $name — floor-rerun: $rerun"
47
+ elif [ -n "$writeoff" ]; then
48
+ echo "✅ RESOLVED (writeoff) $name — floor-writeoff: $writeoff"
49
+ else
50
+ SONNET_PENDING=$((SONNET_PENDING + 1))
51
+ anchor=$(grep -m1 -E '^[[:space:]]*axis2-anchor:' "$m" \
52
+ | sed -E 's/^[[:space:]]*axis2-anchor:[[:space:]]*//' || true)
53
+ echo "🟨 R-tier re-validate $name (sonnet-floor)"
54
+ echo " axis2-anchor: ${anchor:-<missing>}"
55
+ echo " action: re-run judged depth at >= opus or via sidecar dispatch when"
56
+ echo " available, append 'floor-rerun: ...' — or 'floor-writeoff: ...'"
57
+ fi
58
+ continue
59
+ fi
60
+ grep -qE '^[[:space:]]*floor-status:[[:space:]]*below-floor' "$m" || continue
61
+ TOTAL=$((TOTAL + 1))
62
+ name=$(basename "$m")
63
+ model=$(grep -m1 -E '^[[:space:]]*axis2-model:' "$m" \
64
+ | sed -E 's/^[[:space:]]*axis2-model:[[:space:]]*//' || true)
65
+ ack=$(grep -m1 -E '^[[:space:]]*below-floor-ack:' "$m" \
66
+ | sed -E 's/^[[:space:]]*below-floor-ack:[[:space:]]*//' || true)
67
+ rerun=$(grep -m1 -E '^[[:space:]]*floor-rerun:' "$m" \
68
+ | sed -E 's/^[[:space:]]*floor-rerun:[[:space:]]*//' || true)
69
+ writeoff=$(grep -m1 -E '^[[:space:]]*floor-writeoff:' "$m" \
70
+ | sed -E 's/^[[:space:]]*floor-writeoff:[[:space:]]*//' || true)
71
+
72
+ if [ -n "$rerun" ]; then
73
+ echo "✅ RESOLVED (re-run) $name — floor-rerun: $rerun"
74
+ elif [ -n "$writeoff" ]; then
75
+ echo "✅ RESOLVED (writeoff) $name — floor-writeoff: $writeoff"
76
+ else
77
+ PENDING=$((PENDING + 1))
78
+ echo "🟥 PENDING re-run $name"
79
+ echo " axis2-model: ${model:-<missing>} | ack: ${ack:-<missing>}"
80
+ echo " action: re-run Axis 2 at >= floor (opus), append 'floor-rerun: ...'"
81
+ echo " or operator writes off: append 'floor-writeoff: ...'"
82
+ fi
83
+ done
84
+
85
+ echo "── below-floor markers: $TOTAL total, $PENDING pending (S-tier) ──"
86
+ echo "── sonnet-floor markers pending re-validation: $SONNET_PENDING (R-tier, advisory) ──"
87
+ # Exit semantics unchanged: only below-floor (S-tier) hard-fails the scan.
88
+ # sonnet-floor pendings are advisory — reported, never exit-blocking (doctrine:
89
+ # Sonnet base is first-class; the queue exists so residuals terminate, not decorate).
90
+ [ "$PENDING" -gt 0 ] && exit 1
91
+ exit 0
@@ -0,0 +1,184 @@
1
+ #!/usr/bin/env bash
2
+ # chamber_run.sh — chamber run orchestrator (the runner the skeleton's gaps G1-forcing/G2/G4/STATUS all
3
+ # hung on). GLUE ONLY: it wires the pieces that already exist (workspace convention · budget gate notion ·
4
+ # the isolated persona agents · the Emission Gate · the G4 ledger) into one intent-driven, resumable flow
5
+ # so a chamber run can be *completed by intent* rather than hand-followed from the skeleton doc.
6
+ #
7
+ # What it MECHANIZES: workspace + INTENT/BUDGET templates · STATUS stamping (resumable — re-run to advance) ·
8
+ # the budget-entry gate (G2: blocks step 4 until an ESTIMATE is recorded — no uncapped run) · the step-4
9
+ # isolation gate (G1-forcing: blocks step 5 until SIM_NOTES has ≥3 blind persona sections) · the Emission
10
+ # Gate verdict capture · actual-cost record · and the G4 ledger auto-append (idempotent).
11
+ #
12
+ # What it CANNOT mechanize (honest muscle boundary, documented in CHAMBER_RUN_SKELETON.md): bash cannot
13
+ # spawn the isolated Agents itself. Step 4 PRINTS the exact dispatch and GATES on the ≥3 persona artifact —
14
+ # the human/Claude does the actual `fh-meta:{beginner,challenger,main-player}` dispatch. Isolation stays a
15
+ # salience+artifact gate, not a spawn. Budget/cost numbers calibrate only across real runs (muscle, not wiring).
16
+ #
17
+ # Usage: bash scripts/chamber_run.sh <candidate-slug> # create/advance the run (idempotent)
18
+ # bash scripts/chamber_run.sh <candidate-slug> status # show where the run is
19
+ # exit 0 = advanced or already complete · exit 1 = blocked on a missing artifact (message says which)
20
+ # exit 2 = harness error (FH root / bad slug)
21
+
22
+ set -uo pipefail
23
+
24
+ FH="$(cd "$(dirname "$0")/.." && pwd)"
25
+ if [ ! -d "$FH/tracks" ] || [ ! -d "$FH/plugins" ]; then
26
+ echo "❌ FH root not found at '$FH' — run from the FH repo." >&2; exit 2
27
+ fi
28
+
29
+ SLUG="${1:-}"
30
+ [ -z "$SLUG" ] && { echo "usage: chamber_run.sh <candidate-slug> [status]" >&2; exit 2; }
31
+ # slug charset: [A-Za-z0-9-] only, no leading dash. Rejecting regex metachars (`.` `+` `[` `*`) is
32
+ # load-bearing — $SLUG is interpolated raw into an ERE idempotency grep below; `a.b` would let `.` match
33
+ # any char (idempotency mismatch → duplicate ledger row), `a+b` would break the ERE (Axis-2 LOW-5).
34
+ case "$SLUG" in -*) echo "❌ bad slug '$SLUG' (no leading dash)" >&2; exit 2 ;; esac
35
+ case "$SLUG" in *[!A-Za-z0-9-]*) echo "❌ bad slug '$SLUG' (allowed: letters, digits, hyphen)" >&2; exit 2 ;; esac
36
+ CMD="${2:-advance}"
37
+
38
+ WS="$FH/tracks/_chamber/$SLUG"
39
+ LEDGER="$FH/tracks/_chamber/INDEX.md"
40
+ STATUS_F="$WS/STATUS"
41
+ TODAY="$(date +%Y-%m-%d)"
42
+
43
+ _status() { [ -f "$STATUS_F" ] && cat "$STATUS_F" || echo "step-0"; }
44
+ _stamp() { printf '%s\n' "$1" > "$STATUS_F"; }
45
+
46
+ if [ "$CMD" = "status" ]; then
47
+ echo "chamber run '$SLUG' → STATUS: $(_status)"
48
+ [ -d "$WS" ] && ls -1 "$WS" 2>/dev/null | sed 's/^/ /'
49
+ exit 0
50
+ fi
51
+
52
+ echo "── chamber run: $SLUG (STATUS: $(_status)) ──"
53
+
54
+ # STEP 1 — workspace
55
+ if [ ! -d "$WS" ]; then
56
+ mkdir -p "$WS" 2>/dev/null || { echo "❌ cannot create workspace $WS" >&2; exit 2; }
57
+ echo " ✓ step 1: workspace created ($WS)"
58
+ fi
59
+ _stamp "step-1-done"
60
+
61
+ # STEP 2 — INTENT.md (template if absent; block until it has real content)
62
+ if [ ! -f "$WS/INTENT.md" ]; then
63
+ cat > "$WS/INTENT.md" <<EOF
64
+ # INTENT — $SLUG (chamber run)
65
+
66
+ ## Candidate intent
67
+ <one line: the capability/project to incubate>
68
+
69
+ ## Success conditions (each with a check class: mandatory-pass / measured / judged)
70
+ 1.
71
+ 2.
72
+
73
+ ## Failure cost (blast radius AND reinvention risk)
74
+ -
75
+
76
+ ## Chamber metadata
77
+ - entry reason: <uncertain | exploratory | failure-expensive | high-reinvention-risk>
78
+ - date: $TODAY
79
+ EOF
80
+ echo " ⛔ step 2 BLOCKED: fill in $WS/INTENT.md (template written), then re-run."; exit 1
81
+ fi
82
+ if grep -q '<one line: the capability' "$WS/INTENT.md"; then
83
+ echo " ⛔ step 2 BLOCKED: $WS/INTENT.md still has the placeholder — fill it, then re-run."; exit 1
84
+ fi
85
+ # gate (a) is "artifact exists WITH real content", not just "placeholder removed" (Axis-2 LOW-6): require
86
+ # at least one non-empty numbered success condition so a gutted INTENT.md doesn't pass.
87
+ if ! awk '/^## Success conditions/{f=1;next} /^## /{f=0} f && /^[0-9]+\.[[:space:]]*[^[:space:]]/{print; exit}' "$WS/INTENT.md" | grep -q .; then
88
+ echo " ⛔ step 2 BLOCKED: $WS/INTENT.md has no filled success condition (need a numbered line with content), re-run."; exit 1
89
+ fi
90
+ _stamp "step-2-done"; echo " ✓ step 2: INTENT.md present"
91
+
92
+ # STEP 3 — budget-entry gate (G2). Cannot invoke goal-quench from bash; MECHANICALLY require a recorded
93
+ # estimate before any (expensive) simulation runs. No ESTIMATE = no run — that IS the entry cap.
94
+ if [ ! -f "$WS/BUDGET.md" ]; then
95
+ cat > "$WS/BUDGET.md" <<EOF
96
+ # BUDGET — $SLUG (chamber run)
97
+
98
+ # Route through goal-quench's budget gate for an expensive run, then record here.
99
+ # Demo-scale runs may self-cap — but an ESTIMATE line is mandatory (this is the entry cap).
100
+ ESTIMATE: <e.g. ~3 persona dispatches, demo-scale, self-capped | or a token budget>
101
+ ACTUAL: <filled at step 6>
102
+ EOF
103
+ echo " ⛔ step 3 BLOCKED: record an ESTIMATE in $WS/BUDGET.md (budget-entry gate G2), then re-run."; exit 1
104
+ fi
105
+ if grep -qE '^ESTIMATE:[[:space:]]*<' "$WS/BUDGET.md" || ! grep -qE '^ESTIMATE:[[:space:]]*\S' "$WS/BUDGET.md"; then
106
+ echo " ⛔ step 3 BLOCKED: $WS/BUDGET.md ESTIMATE is empty/placeholder (G2 entry cap), then re-run."; exit 1
107
+ fi
108
+ _stamp "step-3-done"; echo " ✓ step 3: budget ESTIMATE recorded (entry cap satisfied)"
109
+
110
+ # STEP 4 — persona simulation (G1-forcing gate). Dispatch is human/Claude-side (bash can't spawn Agents);
111
+ # gate on the ≥3 blind persona artifact. Isolation is the mechanism — the runner enforces the artifact, not the spawn.
112
+ if [ ! -f "$WS/SIM_NOTES.md" ]; then
113
+ echo " ⛔ step 4 BLOCKED: dispatch 3 BLIND ISOLATED Agents and record each in $WS/SIM_NOTES.md:"
114
+ echo " Agent fh-meta:beginner → first-contact friction"
115
+ echo " Agent fh-meta:main-player → daily-use / target-user value"
116
+ echo " Agent fh-meta:challenger → skeptic: emit value? failure cost? what's invisible?"
117
+ echo " Each as '## <persona> ...' section. (sim-conductor fills persona_container_schema's 6 slots.)"
118
+ exit 1
119
+ fi
120
+ # count DISTINCT personas (not raw lines — 3×"## beginner" must NOT satisfy the 3-blind-persona gate).
121
+ NPERS=0
122
+ for _p in beginner main-player challenger; do
123
+ grep -iqE "^##[[:space:]].*$_p" "$WS/SIM_NOTES.md" 2>/dev/null && NPERS=$((NPERS+1))
124
+ done
125
+ if [ "$NPERS" -lt 3 ]; then
126
+ echo " ⛔ step 4 BLOCKED: SIM_NOTES.md has $NPERS/3 DISTINCT blind persona sections (need all of beginner + main-player + challenger)."; exit 1
127
+ fi
128
+ _stamp "step-4-done"; echo " ✓ step 4: $NPERS blind persona sections present (isolation-gate satisfied)"
129
+
130
+ # STEP 5 — Emission Gate. Require a VERDICT: EMIT | PARTIAL-EMIT | KILL.
131
+ if [ ! -f "$WS/EMISSION_VERDICT.md" ]; then
132
+ cat > "$WS/EMISSION_VERDICT.md" <<EOF
133
+ # Emission Gate Verdict — $SLUG (chamber run)
134
+
135
+ VERDICT: <EMIT | PARTIAL-EMIT | KILL>
136
+
137
+ ## Judged: does the simulation hold? (+ mechanical anchor: overlap grep / gate verdicts / reproduced flows)
138
+
139
+ ## Carry-forward (what compounds into the next run)
140
+ -
141
+ EOF
142
+ echo " ⛔ step 5 BLOCKED: decide WITH the operator (HITL), record VERDICT in $WS/EMISSION_VERDICT.md, re-run."; exit 1
143
+ fi
144
+ # PARTIAL-EMIT listed FIRST in every alternation so it is never mis-extracted as its EMIT substring.
145
+ VERDICT=$(grep -ioE '^VERDICT:[[:space:]]*(PARTIAL-EMIT|EMIT|KILL)' "$WS/EMISSION_VERDICT.md" 2>/dev/null | head -1 | grep -ioE 'PARTIAL-EMIT|EMIT|KILL' | head -1 | tr 'a-z' 'A-Z')
146
+ # a bare "## Verdict:" prose line (run #3 style) also counts if it names KILL/EMIT
147
+ [ -z "$VERDICT" ] && VERDICT=$(grep -ioE 'VERDICT[: *]+\**(PARTIAL-EMIT|EMIT|KILL)' "$WS/EMISSION_VERDICT.md" 2>/dev/null | grep -ioE 'PARTIAL-EMIT|EMIT|KILL' | head -1 | tr 'a-z' 'A-Z')
148
+ if [ -z "$VERDICT" ]; then
149
+ echo " ⛔ step 5 BLOCKED: no VERDICT (EMIT|PARTIAL-EMIT|KILL) found in $WS/EMISSION_VERDICT.md, re-run."; exit 1
150
+ fi
151
+ _stamp "step-5-done"; echo " ✓ step 5: Emission Gate verdict = $VERDICT"
152
+
153
+ # STEP 6 — actual cost / carry-forward record.
154
+ if grep -qE '^ACTUAL:[[:space:]]*<' "$WS/BUDGET.md" || ! grep -qE '^ACTUAL:[[:space:]]*\S' "$WS/BUDGET.md"; then
155
+ echo " ⛔ step 6 BLOCKED: record ACTUAL cost in $WS/BUDGET.md (actual-vs-estimate calibration), re-run."; exit 1
156
+ fi
157
+ _stamp "step-6-done"; echo " ✓ step 6: actual cost recorded"
158
+
159
+ # STEP 7 — terminus + G4 ledger auto-append (idempotent).
160
+ if [ ! -f "$LEDGER" ]; then
161
+ echo " ⚠ step 7: no ledger at $LEDGER — skipping auto-append (fail-visible)."
162
+ elif grep -qE "^\|[^|]*\|[^|]*\|[^|]*\`$SLUG\`" "$LEDGER"; then
163
+ echo " ✓ step 7: ledger already has a row for '$SLUG' (idempotent — no duplicate append)."
164
+ else
165
+ NEXT=$(grep -oE '^\|[[:space:]]*#([0-9]+)' "$LEDGER" | grep -oE '[0-9]+' | sort -n | tail -1)
166
+ NEXT=$(( ${NEXT:-0} + 1 ))
167
+ CARRY=$(grep -A2 -iE '^##[[:space:]]*Carry-forward' "$WS/EMISSION_VERDICT.md" 2>/dev/null | grep -E '^-[[:space:]]*\S' | head -1 | sed 's/^-[[:space:]]*//; s/|/·/g')
168
+ [ -z "$CARRY" ] && CARRY="see $SLUG/EMISSION_VERDICT.md"
169
+ printf '| #%s | %s | `%s` | **%s** | %s | `%s/` |\n' "$NEXT" "$TODAY" "$SLUG" "$VERDICT" "$CARRY" "$SLUG" >> "$LEDGER"
170
+ echo " ✓ step 7: appended run #$NEXT ($VERDICT) to the G4 ledger."
171
+ fi
172
+ _stamp "step-7-done"
173
+
174
+ echo ""
175
+ case "$VERDICT" in
176
+ EMIT) echo "TERMINUS (EMIT): route by class — field harness → Full-Harness Mode (auto_project_mapping.md §6);"
177
+ echo " FH-internal utility → New-Skill Pre-Commit gate + asset-placement-gate." ;;
178
+ PARTIAL-EMIT) echo "TERMINUS (PARTIAL-EMIT): the standing candidate is killed; fold the surviving sliver into an"
179
+ echo " existing asset / the skeleton (no new asset). Workspace stays as evidence." ;;
180
+ KILL) echo "TERMINUS (KILL): first-class success — a cheap run prevented a speculative/reinvention build."
181
+ echo " No emit. Workspace stays as the evidence record; seen-filter will skip re-listing it." ;;
182
+ esac
183
+ echo "chamber run '$SLUG' COMPLETE (STATUS: step-7-done, verdict $VERDICT)."
184
+ exit 0
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env bash
2
+ # fh_env_delta_scan.sh — Mode D SessionStart ENVIRONMENT-DELTA detector (mechanical).
3
+ #
4
+ # WHY: FH's "undeployed-asset discovery + auto-mapping" (CLAUDE.md claim ②) works ONLY when a skill
5
+ # is explicitly invoked. The AUTONOMOUS half — "the environment changed (a new sibling repo pulled,
6
+ # a task-first session opened in an unmapped project) → detect it and propose setup" — did NOT exist
7
+ # in code. It was suppressed by the onboarding guards (metadata-is-not-intent, task-first skip), which
8
+ # are correct (they stop FALSE onboarding from branch-name metadata) but also silence GENUINE
9
+ # env-change setup. (Measured miss 2026-07-06: pulling pmh in the company env was not self-detected.
10
+ # Cross-family confirmed 2026-07-06: Claude workflow + codex both rated ② PARTIAL/THEATER, biggest 허풍.)
11
+ #
12
+ # WHAT: this is the SIBLING of scripts/fh_session_load.sh. That hook closes the companion-store
13
+ # FRESHNESS gap ("did the store change?"); THIS hook closes the CAPABILITY-SURFACE gap ("did the
14
+ # environment change?"). It scans the projects root for git repos that are neither mapped
15
+ # (tracks/{name}/ — the is-mapped signal, feedback_tracks_dir_is_mapped_signal) nor wizard-done
16
+ # (~/.cc_sentinels/{name}_wizard_done), and emits a ONE-LINE proposal into turn-0 context. It fires
17
+ # BEFORE the first user turn regardless of what the user types — so it closes the task-first salience
18
+ # gap mechanically, exactly as fh_session_load.sh does for freshness.
19
+ #
20
+ # INVARIANTS:
21
+ # - PROPOSE, never auto-act. Detection is mechanical; mapping/install stays approval-gated (HITL).
22
+ # The hook emits a proposal line; the agent decides whether to surface/act. (metadata-is-not-intent:
23
+ # a mechanical fs delta is a proposal input, not an executed mapping.)
24
+ # - ONE-LINE proposal, NOT the onboarding menu. The guards suppress menus for a reason; this is a
25
+ # targeted delta, not a door skeleton.
26
+ # - Silent no-op when nothing is new (no repos, or all mapped/wizard-done). Majority path = quiet.
27
+ # - Offline-safe / fast: pure local filesystem, no network, maxdepth-1 scan.
28
+ # - Non-Mode-D public users: harmless — it only ever PROPOSES /install-wizard --dry-run, which is
29
+ # itself read-only. Still, gate on the hub being present so a bare plugin install stays silent.
30
+
31
+ set -u
32
+
33
+ # FH = the HUB, derived from THIS script's location ($FH/scripts/fh_env_delta_scan.sh → ../ = hub) —
34
+ # NOT from CLAUDE_PROJECT_DIR. (codex cross-family review 2026-07-06 [HIGH]: defaulting FH to
35
+ # CLAUDE_PROJECT_DIR made a hook run inside a new FIELD repo resolve FH to that field repo → the
36
+ # `tracks` guard failed → the hook silently did NOT fire in exactly the new-project scenario it exists
37
+ # for. Deriving from $0 makes FH correct wherever invoked.) HUB_DIR override still wins for tests.
38
+ FH="${HUB_DIR:-$(CDPATH= cd -- "$(dirname -- "$0")/.." 2>/dev/null && pwd)}"
39
+ FH="${FH:-$HOME/projects/forge-harness}"
40
+ ROOT="${FH_PROJECTS_ROOT:-$(dirname "$FH")}"
41
+ BE="${BE_DIR:-}" # companion store — never a mapping candidate
42
+ SENT="${CC_SENTINEL_DIR:-$HOME/.cc_sentinels}"
43
+
44
+ # Only operate where the hub actually exists (keeps bare-plugin installs silent).
45
+ [ -d "$FH/tracks" ] || exit 0
46
+ [ -d "$ROOT" ] || exit 0
47
+
48
+ _abspath() { CDPATH= cd -- "$1" 2>/dev/null && pwd; }
49
+ FH_ABS="$(_abspath "$FH")"
50
+ BE_ABS=""; [ -n "$BE" ] && BE_ABS="$(_abspath "$BE")"
51
+
52
+ # _candidate DIR → echoes basename if DIR is an unmapped mapping candidate, else nothing + returns 1.
53
+ # ONE predicate for BOTH sibling-scan and current-cwd (codex [MED]×2: cwd previously had weaker
54
+ # exclusions + ignored the skip sentinel → could propose the hub/companion/hidden repo and re-nag a
55
+ # skipped repo). [LOW]: `-e .git` (not `-d`) so git worktrees/submodules (`.git` is a file) count.
56
+ _candidate() {
57
+ local d abs name
58
+ abs="$(_abspath "$1")" || return 1
59
+ [ -n "$abs" ] || return 1
60
+ [ -e "$abs/.git" ] || return 1 # real repo (dir OR file .git = worktree)
61
+ name="$(basename "$abs")"
62
+ [ "$abs" = "$FH_ABS" ] && return 1 # not the hub
63
+ [ -n "$BE_ABS" ] && [ "$abs" = "$BE_ABS" ] && return 1 # not the companion store
64
+ case "$name" in _*|.*) return 1 ;; esac # not underscore/hidden
65
+ [ -d "$FH/tracks/$name" ] && return 1 # mapped (is-mapped signal)
66
+ [ -f "$SENT/${name}_wizard_done" ] && return 1 # wizard already run
67
+ [ -f "$SENT/${name}_mapping_skipped" ] && return 1 # operator skipped → mechanical no-re-nag
68
+ printf '%s' "$name"
69
+ }
70
+
71
+ CANDIDATES=""
72
+ COUNT=0
73
+ for d in "$ROOT"/*/; do
74
+ [ -d "$d" ] || continue
75
+ name="$(_candidate "$d")" || continue
76
+ [ -n "$name" ] || continue
77
+ COUNT=$((COUNT + 1))
78
+ [ "$COUNT" -le 6 ] && CANDIDATES="${CANDIDATES}${name}, "
79
+ done
80
+
81
+ # Current cwd itself an unmapped project? (the pmh-pull / task-first-in-new-project case) — SAME
82
+ # predicate, so identical exclusions + skip-sentinel apply.
83
+ CWD_UNMAPPED="$(_candidate "${CLAUDE_PROJECT_DIR:-$PWD}")" || CWD_UNMAPPED=""
84
+
85
+ # Nothing new → silent no-op (majority path).
86
+ [ "$COUNT" -eq 0 ] && [ -z "$CWD_UNMAPPED" ] && exit 0
87
+
88
+ # Emit the delta proposal (one block, imperative). Trim trailing ", ".
89
+ LIST="${CANDIDATES%, }"
90
+ # Emit: keep it LOW-NOISE (target-tier sim 2026-07-06 [noise finding]: naming irrelevant repos on a
91
+ # task-first entry reads as a nag). The current-cwd case is the high-signal one (the pmh-pull scenario
92
+ # = you are working IN an unmapped project right now); siblings collapse to a count + a single
93
+ # actionable (/install-wizard --dry-run), not a named list.
94
+ {
95
+ echo "⚙️ [FH env-delta] capability-surface change detected (mechanically, even on task-first entry):"
96
+ if [ -n "$CWD_UNMAPPED" ]; then
97
+ echo " • ★ current cwd '$CWD_UNMAPPED' is an UNMAPPED project — you are working in it now; propose mapping it (auto_project_mapping, HITL)."
98
+ fi
99
+ if [ "$COUNT" -gt 0 ]; then
100
+ echo " • $COUNT unmapped sibling repo(s) under $ROOT — run \`/install-wizard --dry-run\` to review + map (HITL); skip via touch \"$SENT/<name>_mapping_skipped\" (mechanical, no re-nag)."
101
+ fi
102
+ # Honest scope (target-tier sim [surfacing residual]): this hook mechanizes the CONTEXT-INJECTION
103
+ # half of claim ② — the env-change now reliably REACHES turn-0 context. It does NOT mechanize the
104
+ # SURFACING half (whether the model mentions it to the operator), which stays salience-dependent —
105
+ # a model cannot be mechanically forced to emit. Surface this in ONE line if the cwd is unmapped;
106
+ # for siblings-only, mention at most once and never derail an explicit task.
107
+ }
108
+ exit 0
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env bash
2
+ # package_coverage_check.sh — a shipped document must not point at a file the package omits.
3
+ #
4
+ # WHY (measured 2026-07-28): the npm tarball shipped CLAUDE.md, README, CATALOG and the knowledge/
5
+ # base while omitting much of what they instruct the reader to open — 35 distinct paths existed in
6
+ # the repo, were named by a shipped document, and were absent from the tarball. `CLAUDE.md` told
7
+ # consumers to run `templates/predelete_check.sh` before a destructive op; that file did not ship.
8
+ # A gate you are told to run and cannot run is worse than one you were never told about.
9
+ #
10
+ # This is the ANTI-REGROWTH instrument for that class. Closing the 35 once is worth little: the set
11
+ # regrows every time a doc gains a reference or files[] gains an entry. So the check is mechanical
12
+ # and the exceptions are ENUMERATED, never implicit.
13
+ #
14
+ # SOURCE-TREE ONLY. Inside an installed package the un-shipped files are legitimately absent and
15
+ # package.json's files[] may not even be present in a comparable form, so the check self-skips.
16
+ #
17
+ # Usage: bash scripts/package_coverage_check.sh
18
+ # Exit: 0 = every referenced path is either shipped or explicitly accepted; 1 = a new phantom.
19
+ set -uo pipefail
20
+
21
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
22
+ cd "$REPO_ROOT" || exit 1
23
+
24
+ if [ ! -d .git ] || [ ! -f package.json ]; then
25
+ echo "SKIP package-coverage (not a source checkout)"
26
+ exit 0
27
+ fi
28
+
29
+ # ── Accepted-absent, with the reason each one is NOT a defect ────────────────────────────────
30
+ # Adding a line here is a decision, not a silencer: each entry states why shipping it would be
31
+ # wrong. If you cannot write that sentence, the file probably belongs in files[].
32
+ #
33
+ # .claude/registry/LOCAL_SKILL_REGISTRY.md — per-environment, generated by the registry scan.
34
+ # Its consumers probe it with `ls … 2>/dev/null` and handle absence; shipping one machine's
35
+ # registry would hand every consumer a false map of skills they do not have.
36
+ # .claude/regression/probes.md — per-environment prompt-regression baselines. The
37
+ # skill explicitly prints NO_CUSTOM_PROBES when absent. Shipping FH's baselines would make
38
+ # a consumer's regression run compare against someone else's harness.
39
+ # scripts/sync-to-be.sh — operator-private companion-store sync. Never ships.
40
+ # (The reference that flags it is a TEST FIXTURE in prepush_guard_check.sh which *creates*
41
+ # a file of that name to exercise the LOW allowlist — not a pointer to this file at all.)
42
+ # scripts/sync_guard_check.sh — anchor for that same operator-private mirror sync;
43
+ # no shipped hook invokes it.
44
+ ACCEPTED_ABSENT=(
45
+ ".claude/registry/LOCAL_SKILL_REGISTRY.md"
46
+ ".claude/regression/probes.md"
47
+ "scripts/sync-to-be.sh"
48
+ "scripts/sync_guard_check.sh"
49
+ )
50
+
51
+ out=$(python3 - "${ACCEPTED_ABSENT[@]}" <<'PY'
52
+ import re, os, json, sys
53
+ accepted = set(sys.argv[1:])
54
+ files = json.load(open('package.json'))['files']
55
+
56
+ def covered(p):
57
+ return any(p == f or p.startswith(f.rstrip('/') + '/') for f in files)
58
+
59
+ shipped = []
60
+ for f in files:
61
+ if os.path.isfile(f):
62
+ shipped.append(f)
63
+ elif os.path.isdir(f):
64
+ for root, _, names in os.walk(f):
65
+ shipped.extend(os.path.join(root, n) for n in names)
66
+
67
+ # Only text surfaces can carry a reference a human or agent would follow.
68
+ shipped = [s for s in shipped if s.endswith(('.md', '.sh', '.js', '.json', '.yaml', '.yml'))]
69
+
70
+ pat = re.compile(
71
+ r'(?<![\w/.-])((?:scripts|templates|bin|docs|knowledge|plugins|\.claude)'
72
+ r'/[A-Za-z0-9_./-]+\.(?:sh|py|js|md|yaml|yml|json|defaults))'
73
+ )
74
+
75
+ phantom = {}
76
+ for s in shipped:
77
+ try:
78
+ text = open(s, encoding='utf-8', errors='ignore').read()
79
+ except OSError:
80
+ continue
81
+ for m in set(pat.findall(text)):
82
+ # Only a path that REALLY EXISTS here but is left out of the tarball is this defect.
83
+ # A path that exists nowhere is the ordinary phantom-reference class the ref-path
84
+ # check above already owns; a path outside files[] that is also absent is nothing.
85
+ if os.path.exists(m) and not covered(m) and m not in accepted:
86
+ phantom.setdefault(m, set()).add(s)
87
+
88
+ # Impossible-zero guard: this repo always has shipped docs. Zero scanned means the extractor
89
+ # broke — report that as a failure rather than letting a dead check print a pass
90
+ # (same rule as count_check.sh and the ref-path extractor).
91
+ if not shipped:
92
+ print("EXTRACTOR_BROKE")
93
+ raise SystemExit(2)
94
+
95
+ for p, srcs in sorted(phantom.items(), key=lambda kv: (-len(kv[1]), kv[0])):
96
+ print(f"{p}\t{len(srcs)}\t{sorted(srcs)[0]}")
97
+ raise SystemExit(1 if phantom else 0)
98
+ PY
99
+ )
100
+ rc=$?
101
+
102
+ if [ "$rc" -eq 2 ] || [ "$out" = "EXTRACTOR_BROKE" ]; then
103
+ echo "FAIL package-coverage: extractor scanned 0 shipped docs — the check broke, it did not pass"
104
+ exit 1
105
+ fi
106
+
107
+ if [ "$rc" -ne 0 ]; then
108
+ echo "FAIL package-coverage: shipped document(s) point at file(s) the package omits:"
109
+ printf '%s\n' "$out" | while IFS=$'\t' read -r path n src; do
110
+ [ -z "$path" ] && continue
111
+ printf ' %s (named by %s shipped doc(s), e.g. %s)\n' "$path" "$n" "$src"
112
+ done
113
+ echo " Fix: add the path to package.json files[], OR list it in ACCEPTED_ABSENT here"
114
+ echo " with a one-sentence reason why shipping it would be wrong."
115
+ exit 1
116
+ fi
117
+
118
+ echo "PASS package-coverage: every referenced path is shipped or explicitly accepted (${#ACCEPTED_ABSENT[@]} accepted)"
119
+ exit 0
@@ -99,6 +99,18 @@ else
99
99
  fail=1
100
100
  fi
101
101
 
102
+ # package-coverage — a shipped doc must not point at a file the tarball omits. Distinct from the
103
+ # ref-path check below: that one asks "does this path exist at all", this one asks "does the
104
+ # CONSUMER get it". Measured 2026-07-28: 35 paths existed, were named by a shipped doc, and were
105
+ # absent from the tarball — including templates/predelete_check.sh, which CLAUDE.md instructs you
106
+ # to run before a destructive op. Wired here in the same commit that created it, because the two
107
+ # previous anchors this session shipped with zero callers.
108
+ if [ -f scripts/package_coverage_check.sh ]; then
109
+ if ! bash scripts/package_coverage_check.sh; then
110
+ fail=1
111
+ fi
112
+ fi
113
+
102
114
  # Referenced-path existence is a source-tree check. The npm package intentionally
103
115
  # ships a narrower runtime surface, so package-mode selfcheck skips this section.
104
116
  if [ -d ".claude/rules" ]; then
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env bash
2
+ # substrate_jump_detector.sh — detect substrate-version jumps (the trigger that was a phantom).
3
+ #
4
+ # WHY: the substrate self-adaptation loop's initiate leg cited a "substrate-version jump trigger"
5
+ # that had NO detector (codex census 2026-07-10 refuted the self-assessment — the trigger existed
6
+ # only as inventory text). This is STRUCTURE-ENFORCING mechanization per the durable-mechanization
7
+ # criterion (sonnet_floor_doctrine.md): version drift lives OUTSIDE the session's context boundary —
8
+ # an infinitely strong model still cannot know what changed on the machine between sessions.
9
+ #
10
+ # WHAT: snapshots substrate versions to a gitignored state file; on the next run, diffs and emits
11
+ # a jump notice naming the doctrine's shed/advance pass. Silent when nothing changed.
12
+ # Wire: one line in the SessionStart hook (fh_session_load.sh) or run standalone.
13
+ #
14
+ # Exit: always 0 (detector, not gate). State: tracks/_meta/.substrate_versions (gitignored).
15
+
16
+ set -uo pipefail
17
+
18
+ FH="${1:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
19
+ STATE="$FH/tracks/_meta/.substrate_versions"
20
+
21
+ snapshot() {
22
+ # one line per component: name=version (unavailable components recorded as absent — an
23
+ # appearing/disappearing component is itself a jump)
24
+ echo "claude=$(claude --version 2>/dev/null | head -1 || echo absent)"
25
+ echo "codex=$(codex --version 2>/dev/null | head -1 || echo absent)"
26
+ echo "agy=$(agy --version 2>/dev/null | head -1 || echo absent)"
27
+ echo "node=$(node --version 2>/dev/null || echo absent)"
28
+ echo "git=$(git --version 2>/dev/null || echo absent)"
29
+ echo "os=$(uname -sr 2>/dev/null || echo absent)"
30
+ }
31
+
32
+ CURRENT="$(snapshot)"
33
+
34
+ if [ ! -f "$STATE" ]; then
35
+ printf '%s\n' "$CURRENT" > "$STATE"
36
+ echo "🧭 [substrate] baseline snapshot recorded ($(echo "$CURRENT" | wc -l | tr -d ' ') components)"
37
+ exit 0
38
+ fi
39
+
40
+ PREV="$(cat "$STATE")"
41
+ if [ "$CURRENT" = "$PREV" ]; then
42
+ # silent no-op — a detector that talks every session trains the reader to skip it
43
+ exit 0
44
+ fi
45
+
46
+ echo "🧭 [substrate] VERSION JUMP detected — substrate loop initiate leg fires:"
47
+ # show only changed lines (name-keyed diff, bash-3.2 safe)
48
+ while IFS= read -r cur; do
49
+ name="${cur%%=*}"
50
+ old=$(printf '%s\n' "$PREV" | grep -m1 "^$name=" || echo "$name=<new>")
51
+ [ "$cur" != "$old" ] && echo " $old → $cur"
52
+ done <<EOF
53
+ $CURRENT
54
+ EOF
55
+ echo " → run the shed/advance pass: re-check capability-compensating scaffolding against the new"
56
+ echo " substrate (sonnet_floor_doctrine.md §durable-mechanization — shed what the model no longer"
57
+ echo " needs, advance what the new substrate enables). Removals go through the 4-axis gate."
58
+
59
+ printf '%s\n' "$CURRENT" > "$STATE"
60
+ exit 0
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env bash
2
+ # test_marker_floor_lanes.sh — regression fixtures for pre-commit validate_marker_floor lanes.
3
+ #
4
+ # Ships with the 2026-07-10 sonnet-floor lane (Sonnet-Floor Doctrine): each closed hole gets a
5
+ # mechanical regression test (Field-Harness Load-Bearing Change Gate convergence condition).
6
+ # Fixtures assert BOTH directions: the new lane admits exactly its intended shape, and every
7
+ # pre-existing guard still blocks (no degrade-toward-permissive regression).
8
+ #
9
+ # Usage: bash scripts/test_marker_floor_lanes.sh Exit: 0 = all fixtures behave; 1 = regression.
10
+
11
+ set -uo pipefail
12
+ REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
13
+ HOOK="$REPO_ROOT/templates/.git-hooks/pre-commit"
14
+ T=$(mktemp -d); trap 'rm -rf "$T"' EXIT
15
+
16
+ sed -n '/^marker_recreate_hint()/,/^}/p;/^validate_marker_floor()/,/^}/p' "$HOOK" > "$T/fn.sh"
17
+
18
+ run() { bash -c "source '$T/fn.sh'; validate_marker_floor '$1'" >/dev/null 2>&1; }
19
+
20
+ FAIL=0
21
+ check() { # $1=fixture $2=expected(PASS|BLOCK) $3=label
22
+ if run "$1"; then got=PASS; else got=BLOCK; fi
23
+ if [ "$got" = "$2" ]; then echo "✅ $3 → $got"; else echo "❌ $3 → $got (expected $2)"; FAIL=1; fi
24
+ }
25
+
26
+ printf 'axis2-engine: inline\naxis2-model: sonnet\nfloor-status: sonnet-floor\naxis2-anchor: regression test 5/5 pass\naxis2-evidence: PASS no-S, 2B applied\n' > "$T/m1"
27
+ printf 'axis2-engine: inline\naxis2-model: sonnet\nfloor-status: sonnet-floor\naxis2-evidence: PASS no-S\n' > "$T/m2"
28
+ printf 'axis2-engine: inline\naxis2-model: haiku\nfloor-status: sonnet-floor\naxis2-anchor: probe 3/3\naxis2-evidence: PASS no-S\n' > "$T/m3"
29
+ printf 'axis2-engine: inline\naxis2-model: sonnet\nfloor-status: at-floor\naxis2-evidence: PASS no-S\n' > "$T/m4"
30
+ printf 'axis2-engine: inline\naxis2-model: haiku\nfloor-status: below-floor\naxis2-evidence: PASS no-S\n' > "$T/m5"
31
+ printf 'axis2-engine: quench-challenger\naxis2-model: opus\nfloor-status: at-floor\naxis2-evidence: 1S/4A fixed\n' > "$T/m6"
32
+ printf 'axis2-engine: inline\naxis2-model: haiku\nfloor-status: below-floor\nbelow-floor-ack: "approved, proceed" — canary-only change\naxis2-evidence: PASS no-S\n' > "$T/m7"
33
+ printf 'axis2-engine: inline\naxis2-model: opus\nfloor-status: bogus-status\naxis2-evidence: PASS\n' > "$T/m8"
34
+
35
+ check "$T/m1" PASS "sonnet-floor + anchor + sonnet model (new lane, intended shape)"
36
+ check "$T/m2" BLOCK "sonnet-floor WITHOUT anchor (anchor is the compensating requirement)"
37
+ check "$T/m3" BLOCK "haiku claiming sonnet-floor (lane mislabel)"
38
+ check "$T/m4" BLOCK "sonnet claiming at-floor (2026-06-10 mislabel class — guard intact)"
39
+ check "$T/m5" BLOCK "below-floor without ack (guard intact)"
40
+ check "$T/m6" PASS "opus at-floor (legacy lane intact)"
41
+ check "$T/m7" PASS "below-floor with quoted ack (legacy lane intact)"
42
+ check "$T/m8" BLOCK "invalid floor-status (fail-closed on unknown value)"
43
+
44
+ [ "$FAIL" -eq 0 ] && echo "── all marker-floor lane fixtures behave ──"
45
+ exit "$FAIL"