@chrono-meta/fh-gate 1.4.72 → 1.4.73
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/rules/.public-surface-patterns.defaults +44 -0
- package/.claude/rules/fh_4axis_gate.md +207 -0
- package/.claude-plugin/marketplace.json +2 -2
- package/AGENTS.md +26 -2
- package/CATALOG.md +31 -0
- package/knowledge/shared/harness-core/measurement-integrity-checklist.md +10 -0
- package/knowledge/shared/learnings/subagent_invocations_log.yaml +554 -0
- package/package.json +21 -1
- package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-meta/.claude-plugin/plugin.json +2 -2
- package/plugins/fh-meta/skills/context-doctor/SKILL.md +42 -4
- package/plugins/fh-meta/skills/context-doctor/SKILL_detail.md +38 -0
- package/scripts/chamber_candidate_collect.sh +223 -0
- package/scripts/degrade_direction_scan.sh +222 -0
- package/scripts/fh_session_load.sh +202 -0
- package/scripts/gate_pathspec_check.sh +166 -0
- package/scripts/prepush_guard_check.sh +374 -0
- package/scripts/psa_scan_lib.sh +153 -0
- package/scripts/public_surface_scan_files.sh +157 -0
- package/scripts/selfcheck.sh +16 -0
- package/scripts/session_close_check.sh +171 -0
- package/scripts/test_degrade_scan_shell_probes.sh +185 -0
- package/scripts/test_prepush_stdin_integrity.sh +119 -0
- package/scripts/universal_guard_check.sh +280 -0
- package/templates/.claude/rules/mcp_tool_gating.md +157 -0
- package/templates/.git-hooks/pre-commit +848 -0
- package/templates/.git-hooks/pre-push +585 -0
- package/templates/PRE-PUBLISH-CHECKLIST.md +85 -0
- package/templates/degrade_direction_scan.sh +222 -0
- package/templates/predelete_check.sh +72 -0
- package/templates/regression_guard.sh +563 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fh-meta",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.73",
|
|
4
4
|
"engines": {
|
|
5
5
|
"claudeCode": ">=1.0.0"
|
|
6
6
|
},
|
|
7
|
-
"description": "Hub meta-engineering toolkit — 35 skills + 7 agents. New in 1.4.71: SKILL_detail.md brought inside the 4-axis gate — the gate matched the literal `SKILL.md`, which `SKILL_detail.md` does not contain, leaving 27.7% of the skill-spec surface ungated (measured; it leaked twice for real). Fix escalated from a name list to a directory scope after an enumeration sweep found a real uncovered file on its first run; anchored by scripts/gate_pathspec_check.sh (known-pair, wired into pre-commit). One-way mirror sync gains a destination-newer abort + mechanically injected mirror banner after two session cards were silently overwritten, anchored by scripts/sync_guard_check.sh. dialogue-harvest gains Step 4-b cross-corpus provenance (absorbed / held-unused / declined) for single-author corpora, where the original provenance labels were free and measured nothing. frontier-digest arxiv leg category-scoped after a relevance drift the staleness rule structurally could not catch (known-pair calibration recorded). templates/CLAUDE.md hub paths switched to {FH_ROOT} so a copied harness stops carrying one operator's absolute paths into org-visible checkouts. New in 1.4.70: dialogue-harvest (mines AI-dialogue logs — sycophancy strip first, induced-vs-independent provenance labeling, EN+KO known-pair calibration shipped); asset-placement-gate Step 0.6 official-corpora check (Claude Cookbook as Tier-0 consult corpus, judged-flag semantics); context-doctor built-in-/doctor-first sister anchor. New in 1.4.48: phantom-quench + steel-quench gain external frontier anchors (arXiv:2607.02052 package-hallucination; arXiv:2607.02057 prompt-coverage-adequacy); README model-flat claim reframed from a per-release point-curve to structural invariants (operation flattens across tiers; depth tier-order fixed within a generation). New in 1.4.47: onboarding step ① surfaces the Mode D companion-store session-start load in the auto-read salience anchor (previously only in the local binding + rules, so a greeting could skip the load). New in 1.4.46: context-doctor gains a command-output axis — routes to a command-output proxy/hook (rtk) to trim verbose CLI stdout, complementing .claudeignore; risk-gated to token-scarce environments (lossy filtering, off gate-input paths). New in 1.4.41: context-doctor 2026 trigger vocab (context engineering/rot/collapse) + phantom-citation hardening; hub measurement-integrity-checklist (cross-model measurement pre-flight: display-name pin/reps≥3/discriminating probe). New in 1.4.40: install-wizard scaffolds the companion store as a queryable wiki (INDEX + session-start read + Raw/Wiki/Conversation ingest axis). New in 1.4.39: auto-decorrelation (cross-family verifier sidecar recruitment, calibration-gated) + video-ingest (capability-routed video ingestion). New in 1.4.37: corpus-grounding-expander + persona-roster-expander (field-harvested verbatim-relay capability skills). New in 1.3.0: public-surface-audit (git-tracked private-token leak scan), field-harvest Mode B session-end auto-trigger, 4-axis gate scope extension (docs/ + AGENTS.md). New in 1.2.0: pipeline-conductor (4-pipeline gated sweep), return-path-gate (chain closure audit), goal-quench (Stop hook + quality gate), steel-quench Wave 5 (multi-model sidecar challenger), 2-layer architecture docs, YAML validation script. Validated cross-CLI: Claude Code, Codex, Gemini.",
|
|
7
|
+
"description": "New in 1.4.73: the mechanical floor now ships — templates/.git-hooks (pre-commit/pre-push) plus every script they actually invoke, their regression anchors, and the .claude/rules data they read. Until now the package shipped the prose that tells you to run the gates while omitting the gates, and 35 shipped-doc references pointed at files the tarball did not contain. Verified by unpacking the tarball, installing the hooks in a scratch repo and committing through them, not by inspecting files[]. degrade_direction_scan gains shell probes: it collected .sh files that no probe could match and reported them clean (known-positive scored 0/4); git hooks were invisible entirely because the extension test ran against the path, not the basename. The field-propagated templates/ copy had drifted from scripts/ and is now byte-pinned by an anchor. Hub meta-engineering toolkit — 35 skills + 7 agents. New in 1.4.71: SKILL_detail.md brought inside the 4-axis gate — the gate matched the literal `SKILL.md`, which `SKILL_detail.md` does not contain, leaving 27.7% of the skill-spec surface ungated (measured; it leaked twice for real). Fix escalated from a name list to a directory scope after an enumeration sweep found a real uncovered file on its first run; anchored by scripts/gate_pathspec_check.sh (known-pair, wired into pre-commit). One-way mirror sync gains a destination-newer abort + mechanically injected mirror banner after two session cards were silently overwritten, anchored by scripts/sync_guard_check.sh. dialogue-harvest gains Step 4-b cross-corpus provenance (absorbed / held-unused / declined) for single-author corpora, where the original provenance labels were free and measured nothing. frontier-digest arxiv leg category-scoped after a relevance drift the staleness rule structurally could not catch (known-pair calibration recorded). templates/CLAUDE.md hub paths switched to {FH_ROOT} so a copied harness stops carrying one operator's absolute paths into org-visible checkouts. New in 1.4.70: dialogue-harvest (mines AI-dialogue logs — sycophancy strip first, induced-vs-independent provenance labeling, EN+KO known-pair calibration shipped); asset-placement-gate Step 0.6 official-corpora check (Claude Cookbook as Tier-0 consult corpus, judged-flag semantics); context-doctor built-in-/doctor-first sister anchor. New in 1.4.48: phantom-quench + steel-quench gain external frontier anchors (arXiv:2607.02052 package-hallucination; arXiv:2607.02057 prompt-coverage-adequacy); README model-flat claim reframed from a per-release point-curve to structural invariants (operation flattens across tiers; depth tier-order fixed within a generation). New in 1.4.47: onboarding step ① surfaces the Mode D companion-store session-start load in the auto-read salience anchor (previously only in the local binding + rules, so a greeting could skip the load). New in 1.4.46: context-doctor gains a command-output axis — routes to a command-output proxy/hook (rtk) to trim verbose CLI stdout, complementing .claudeignore; risk-gated to token-scarce environments (lossy filtering, off gate-input paths). New in 1.4.41: context-doctor 2026 trigger vocab (context engineering/rot/collapse) + phantom-citation hardening; hub measurement-integrity-checklist (cross-model measurement pre-flight: display-name pin/reps≥3/discriminating probe). New in 1.4.40: install-wizard scaffolds the companion store as a queryable wiki (INDEX + session-start read + Raw/Wiki/Conversation ingest axis). New in 1.4.39: auto-decorrelation (cross-family verifier sidecar recruitment, calibration-gated) + video-ingest (capability-routed video ingestion). New in 1.4.37: corpus-grounding-expander + persona-roster-expander (field-harvested verbatim-relay capability skills). New in 1.3.0: public-surface-audit (git-tracked private-token leak scan), field-harvest Mode B session-end auto-trigger, 4-axis gate scope extension (docs/ + AGENTS.md). New in 1.2.0: pipeline-conductor (4-pipeline gated sweep), return-path-gate (chain closure audit), goal-quench (Stop hook + quality gate), steel-quench Wave 5 (multi-model sidecar challenger), 2-layer architecture docs, YAML validation script. Validated cross-CLI: Claude Code, Codex, Gemini.",
|
|
8
8
|
"author": {
|
|
9
9
|
"name": "chrono-meta",
|
|
10
10
|
"email": "chrono-meta@users.noreply.github.com"
|
|
@@ -20,7 +20,7 @@ Diagnoses the main causes of session token waste and prescribes immediate remedi
|
|
|
20
20
|
3. Not using `/clear` after direction changes → continuing work with accumulated noise
|
|
21
21
|
4. Verbose CLI output → every `git`/`ls`/build/test call floods context with stdout (a *different* layer from 1–3 — see §Command-Output Reduction)
|
|
22
22
|
|
|
23
|
-
**Two reduction axes — keep them distinct.** Causes 1–3 are the **context-footprint** axis (what gets read *into* context: files, history). Cause 4 is the **command-output** axis (tokens produced *by* the tools you run). `.claudeignore` cannot touch command output, and a command-output proxy cannot touch file reads — they are complementary, not substitutes.
|
|
23
|
+
**Two reduction axes — keep them distinct.** Causes 1–3 are the **context-footprint** axis (what gets read *into* context: files, history). Cause 4 is the **command-output** axis (tokens produced *by* the tools you run). `.claudeignore` cannot touch command output, and a command-output proxy cannot touch file reads — they are complementary, not substitutes. (Step 3.5 below adds an optional **third**, orthogonal lever — provider-side cache billing — distinct from both.)
|
|
24
24
|
|
|
25
25
|
**Standalone install** — this skill works normally with plugin install only, without cloning the full meta-harness.
|
|
26
26
|
|
|
@@ -111,6 +111,39 @@ When context is near the limit and you want to *preserve state* rather than rese
|
|
|
111
111
|
|
|
112
112
|
> Models are tools for allocating the right expertise to complexity. Opus for simple tasks = wasted expertise; Haiku for design decisions = insufficient expertise. Switch models when task nature changes.
|
|
113
113
|
|
|
114
|
+
### Step 3.5. Cache-Boundary Audit (prompt caching, optional third lever)
|
|
115
|
+
|
|
116
|
+
Distinct from both axes above: Steps 1–3 reduce what occupies the context *window*; this
|
|
117
|
+
reduces what the provider re-bills as **fresh input tokens** on each turn by preserving
|
|
118
|
+
prompt-cache hits. **Optional** — run it when a metered/quota-limited backend makes cache
|
|
119
|
+
cost the binding constraint (same gating logic as §Command-Output Reduction: diagnose and
|
|
120
|
+
recommend, do not chase this when tokens are merely plentiful).
|
|
121
|
+
|
|
122
|
+
**Audit checklist**:
|
|
123
|
+
- Is the system prompt / CLAUDE.md content **stable byte-for-byte** across turns in a
|
|
124
|
+
session? Any turn-to-turn diff in the fixed prefix invalidates the cache for that prefix —
|
|
125
|
+
which is in tension with Step 5's CLAUDE.md-compression prescription: compress at session
|
|
126
|
+
boundaries (before a fresh cache is built), never mid-session (which would invalidate one
|
|
127
|
+
already warm).
|
|
128
|
+
- Is session-specific / dynamic content (task state, recent results) placed at the
|
|
129
|
+
**end of the user message**, not interleaved into the system prompt or early context?
|
|
130
|
+
|
|
131
|
+
**Reported, UNCALIBRATED by FH** (2026-07-27 frontier digest, issue #102): input:output token
|
|
132
|
+
ratio **> 10:1** favors cache/context engineering over model-level optimization; **> 50:1**,
|
|
133
|
+
prefix caching dominates; cache-boundary control reportedly raised hit rate ~7%→84% in one
|
|
134
|
+
production case (arXiv:2603.09619 *Context Engineering: From Prompts to Corporate Multi-Agent
|
|
135
|
+
Architecture* · appscale.blog).
|
|
136
|
+
|
|
137
|
+
**What is and is not verified** — the two are separate checks and only one has been run:
|
|
138
|
+
the cited paper's **existence** was confirmed 2026-07-28 (arxiv.org/abs/2603.09619 → HTTP 200,
|
|
139
|
+
title matches; measured alongside a known-real control ID, so the check itself is calibrated).
|
|
140
|
+
The **figures** above were not read out of the paper — they are still traced only to the digest
|
|
141
|
+
comment. Treat the ratio thresholds as illustrative, not a calibrated gate, until the source
|
|
142
|
+
text is read directly.
|
|
143
|
+
|
|
144
|
+
> **Detail**: See `SKILL_detail.md §CacheBoundary` — full citation text and the 403 grounding
|
|
145
|
+
> note — read before citing these figures elsewhere.
|
|
146
|
+
|
|
114
147
|
### Step 4. harvest-loop Integration (burst pattern recording)
|
|
115
148
|
|
|
116
149
|
When burst pattern is detected and `tracks/_audit/` exists: locate the latest weekly_audit file and suggest adding the Token Efficiency Check items to it (bash + checklist block in §Step-Bash).
|
|
@@ -132,6 +165,8 @@ Run the audit bash (§Step-Bash) and apply thresholds:
|
|
|
132
165
|
|
|
133
166
|
**Frequency**: When explicitly called with `/context-doctor` or auto-invoked at session start when MEMORY.md is detected at 180+ lines.
|
|
134
167
|
|
|
168
|
+
**Cache-boundary note**: apply CLAUDE.md compression (above) at a session boundary, not mid-session — see Step 3.5's cache-invalidation tension if that step is also in scope.
|
|
169
|
+
|
|
135
170
|
## Context Hierarchy (L1/L2/L3)
|
|
136
171
|
|
|
137
172
|
Information buried in the middle of a long context window suffers measurable accuracy loss — the "lost in the middle" effect, ~10–30% degradation for mid-context information (see `../../../../knowledge/shared/harness-core/harness_frontier_diagnosis_2026-06-02.md`). The remedy is two-fold: tier the context, and place the most important instructions at the **start and end**, never buried in the middle.
|
|
@@ -187,8 +222,8 @@ The reductions above all act on the **context-footprint** axis (files, history r
|
|
|
187
222
|
|
|
188
223
|
| Environment | Behavior |
|
|
189
224
|
|---|---|
|
|
190
|
-
| Meta-harness cloned (Mode A) | Perform full Steps 1–5 / integrate with harvest-loop files |
|
|
191
|
-
| Plugin only (Mode C) | Perform Steps 1–3 / Step 4 output only (no file writes) |
|
|
225
|
+
| Meta-harness cloned (Mode A) | Perform full Steps 1–5 (+ optional Step 3.5) / integrate with harvest-loop files |
|
|
226
|
+
| Plugin only (Mode C) | Perform Steps 1–3 (+ optional Step 3.5, no file writes needed) / Step 4 output only (no file writes) |
|
|
192
227
|
| External general environment | Focus on `.claudeignore` generation + large file guidance |
|
|
193
228
|
|
|
194
229
|
## Invocation Triggers
|
|
@@ -231,6 +266,7 @@ Explicit invocation (`/context-doctor`) always runs regardless of suppress state
|
|
|
231
266
|
- "context diet", "memory audit", "CLAUDE.md is heavy", "MEMORY.md size"
|
|
232
267
|
- "context engineering", "context rot", "context collapse"
|
|
233
268
|
- "command output is huge", "verbose output", "rtk", "token killer", "trim command output"
|
|
269
|
+
- "prompt caching", "cache hit rate", "cache boundary", "why are my API costs so high"
|
|
234
270
|
|
|
235
271
|
### Natural Language Triggers (activates without internal vocabulary)
|
|
236
272
|
|
|
@@ -247,6 +283,7 @@ Also activates when an external user expresses without token/context terminology
|
|
|
247
283
|
| "Context is getting full", "context meter is high" | Approaching context limit | Step 3 — propose Wrap-then-Compact pattern |
|
|
248
284
|
| "context engineering", "doing context engineering", "context rot setting in" | 2026 industry term for context discipline (Chroma 2025 / Anthropic) | Step 2 + Step 3 |
|
|
249
285
|
| "every git command dumps a wall of text", "the build output eats my context" | Verbose command output flooding context | §Command-Output Reduction (route to proxy/hook) |
|
|
286
|
+
| "our token bill is high but context looks fine", "cache keeps missing" | Suspected cache-boundary invalidation, not context bloat | Step 3.5 (optional) |
|
|
250
287
|
|
|
251
288
|
## Three-Doctor Loop Integration
|
|
252
289
|
|
|
@@ -277,9 +314,10 @@ context-doctor (token/context) · harness-doctor (structure) · sim-conductor (s
|
|
|
277
314
|
|
|
278
315
|
| Condition | Completion verdict |
|
|
279
316
|
|---|---|
|
|
280
|
-
| Diagnosis results output to conversation (relevant stages among Steps 1~5) | ✅ Diagnosis complete |
|
|
317
|
+
| Diagnosis results output to conversation (relevant stages among Steps 1~5, incl. optional 3.5) | ✅ Diagnosis complete |
|
|
281
318
|
| `.claudeignore` created or modified + path output | ✅ Prescription complete |
|
|
282
319
|
| Large file detected with split strategy guidance output | ✅ Step 2 complete |
|
|
320
|
+
| Step 3.5 run: checklist findings output, thresholds labeled UNCALIBRATED per source above | ✅ Cache-boundary audit complete (only when Step 3.5 was triggered) |
|
|
283
321
|
| "No context structure issues" judgment output | ✅ Health check complete |
|
|
284
322
|
|
|
285
323
|
**This skill's Done When = "diagnosis report output complete"**. Actual resolution of prescription items is in the user's or follow-up work domain and is not included in this skill's completion criteria.
|
|
@@ -93,6 +93,44 @@ CC Context Audit Results
|
|
|
93
93
|
|
|
94
94
|
---
|
|
95
95
|
|
|
96
|
+
## §CacheBoundary — prompt-caching evidence (Step 3.5, provisional)
|
|
97
|
+
|
|
98
|
+
Full citation for the Step 3.5 evidence line, kept out of the always-read SKILL.md body
|
|
99
|
+
per this file's own role (detail loads on-demand, SKILL.md stays lean).
|
|
100
|
+
|
|
101
|
+
**Source**: forge-harness GitHub issue #102 ("Frontier Digest Log"), 2026-07-27 daily comment,
|
|
102
|
+
itself citing:
|
|
103
|
+
- arXiv:2603.09619 — reported input:output token-ratio thresholds (>10:1 favors context/cache
|
|
104
|
+
engineering over model-level optimization; >50:1, prefix caching dominates) and a
|
|
105
|
+
41–80% cost reduction / 13–31% TTFT improvement range attributed to prompt caching.
|
|
106
|
+
- appscale.blog/en/blog/context-engineering-production-llm-agents-token-budget-compaction-2026
|
|
107
|
+
— the ~7%→84% cache-hit-rate figure from cache-boundary control (fixed system prompt,
|
|
108
|
+
dynamic content at the user-message end).
|
|
109
|
+
|
|
110
|
+
**Grounding status — split verdict (2026-07-28 re-check).** Two separate questions; only one
|
|
111
|
+
is closed. Do not collapse them:
|
|
112
|
+
|
|
113
|
+
| Question | Verdict | Evidence |
|
|
114
|
+
|---|---|---|
|
|
115
|
+
| Does the cited paper exist? | ✅ **VERIFIED** | `arxiv.org/abs/2603.09619` → HTTP 200, title *"Context Engineering: From Prompts to Corporate Multi-Agent Architecture"*. Measured alongside a known-real control ID (1706.03762 → 200), so the instrument is calibrated for this check. |
|
|
116
|
+
| Do the paper/blog actually state these figures? | ❌ **UNVERIFIED** | The source text was never read. The numbers are traced only to the digest comment. |
|
|
117
|
+
|
|
118
|
+
⚠️ **Instrument note (kept because it nearly produced a wrong verdict).** The first re-check
|
|
119
|
+
used the arXiv **API** (`export.arxiv.org/api/query`) and returned empty for the target — but
|
|
120
|
+
also empty for the known-real control, i.e. the instrument was dead, not the paper missing.
|
|
121
|
+
An absence measured without a live control is not evidence. Switching to the `abs` page gave a
|
|
122
|
+
working control and flipped the existence verdict to VERIFIED. Original 403 note below for
|
|
123
|
+
provenance.
|
|
124
|
+
|
|
125
|
+
**Original (2026-07-27) note**: both URLs returned HTTP 403 when that session attempted
|
|
126
|
+
to fetch and verify the claimed spans directly (`WebFetch`), matching the same sandbox
|
|
127
|
+
network-policy limitation the daily frontier-digest routine logged on every run this week
|
|
128
|
+
(WebSearch fallback, no direct API access). The figures above are therefore traced only to
|
|
129
|
+
the digest comment's own text — a real, read GitHub source — and are **not** independently
|
|
130
|
+
re-verified against the original paper/post. Per the Instrument-Calibration doctrine
|
|
131
|
+
(`CLAUDE.md`), do not treat the 10:1 / 50:1 thresholds as a calibrated gate and do not cite
|
|
132
|
+
the ~7%→84% figure elsewhere without re-fetching from an environment with access first.
|
|
133
|
+
|
|
96
134
|
## §Headroom — Tooling (external option)
|
|
97
135
|
|
|
98
136
|
The compression pass in SKILL.md is tool-agnostic, but a concrete, reversible, local-first implementation exists: **Headroom** (`github.com/chopratejas/headroom`, open source, v0.22). It compresses tool outputs, logs, files, and RAG chunks before they reach the LLM — **vendor/coverage-reported** at 60–95% fewer tokens with the same answers ([The Register, 2026-05-31](https://www.theregister.com/ai-ml/2026/05/31/netflix-wiz-creates-app-to-slash-ai-bills-then-open-sources-it/5248702); figures unverified by FH). General token-efficiency basis: `../../../../knowledge/shared/harness-core/harness_frontier_diagnosis_2026-06-02.md`.
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# chamber_candidate_collect.sh — chamber candidate discovery: 6-source converge → dedup → rank → screen.
|
|
3
|
+
#
|
|
4
|
+
# The second half of the discovery entrance (the first half = chamber_candidate_screen.sh, the reinvention
|
|
5
|
+
# first-pass). Design home: tracks/_meta/fh_signal_2026-07-14_self-dev.md (design v2, 2-family verified).
|
|
6
|
+
#
|
|
7
|
+
# WHAT IT DOES: pulls candidate signals from the 6 sources, deduplicates the same gap arriving multiple
|
|
8
|
+
# ways (2-family finding: harvest-loop/fh_signal/UAP observe the SAME session → triple-listing), ranks by
|
|
9
|
+
# source-diversity + frequency, and runs each survivor through the reinvention screener. Output = a ranked
|
|
10
|
+
# candidate queue for the operator's HITL "run the chamber?" decision (judged uncertainty/failure-cost
|
|
11
|
+
# filters stay with the human — this tool does the mechanizable converge/dedup/rank/screen).
|
|
12
|
+
#
|
|
13
|
+
# CANDIDATE-EMISSION CONVENTION (why a convention, not source-specific parsing): the 2-family review
|
|
14
|
+
# flagged per-source parsing as fragile/hand-wave. So a source declares a candidate with ONE robust line:
|
|
15
|
+
# CHAMBER-CANDIDATE: <one-line description of the capability/project to incubate>
|
|
16
|
+
# Any of the 6 sources adds this line when it surfaces something chamber-worthy. Sources adopt it
|
|
17
|
+
# incrementally — until they do, the queue is honestly SPARSE (the design's "6-source volume = n=0,
|
|
18
|
+
# measured only after wiring"; this tool IS that wiring, and its first run measures the volume).
|
|
19
|
+
#
|
|
20
|
+
# NOT MECHANIZED HERE (stays judged/HITL, by design): uncertainty + failure-cost filters (operator judges
|
|
21
|
+
# the ranked survivors); chamber injection (goal-quench budget gate + HITL). This tool ranks; it does not
|
|
22
|
+
# admit. A high rank is a suggestion, never an auto-run.
|
|
23
|
+
#
|
|
24
|
+
# Usage: bash scripts/chamber_candidate_collect.sh [--min-jaccard N] (default 50)
|
|
25
|
+
# exit 0 = ran (ranked queue on stdout; empty queue is a valid honest result)
|
|
26
|
+
# exit 2 = harness error (FH root not found)
|
|
27
|
+
|
|
28
|
+
set -uo pipefail
|
|
29
|
+
|
|
30
|
+
FH="$(cd "$(dirname "$0")/.." && pwd)"
|
|
31
|
+
if [ ! -d "$FH/tracks/_meta" ] || [ ! -d "$FH/plugins" ]; then
|
|
32
|
+
echo "❌ FH root not found at '$FH' — run from the FH repo." >&2
|
|
33
|
+
exit 2
|
|
34
|
+
fi
|
|
35
|
+
SCREEN="$FH/scripts/chamber_candidate_screen.sh"
|
|
36
|
+
MINJ=50
|
|
37
|
+
[ "${1:-}" = "--min-jaccard" ] && MINJ="${2:-50}"
|
|
38
|
+
case "$MINJ" in ''|*[!0-9]*) echo "❌ --min-jaccard must be an integer 0-100 (got '$MINJ')" >&2; exit 2 ;; esac
|
|
39
|
+
|
|
40
|
+
WORK="${TMPDIR:-/tmp}/cand_collect.$$"
|
|
41
|
+
# fail-CLOSED on workdir failure (Axis-2 challenger MED): an unwritable TMPDIR must NOT masquerade as the
|
|
42
|
+
# design's valid "honest-empty queue" — a genuine infra failure and "no markers surfaced" are otherwise
|
|
43
|
+
# indistinguishable. Route infra failure to exit 2 (harness error), never to green-empty.
|
|
44
|
+
mkdir -p "$WORK" 2>/dev/null || { echo "❌ cannot create workdir under '${TMPDIR:-/tmp}' — cannot collect (NOT an empty queue)" >&2; exit 2; }
|
|
45
|
+
trap 'rm -rf "$WORK"' EXIT
|
|
46
|
+
RAW="$WORK/raw"; : > "$RAW" 2>/dev/null || { echo "❌ cannot write workfile (NOT an empty queue)" >&2; exit 2; }
|
|
47
|
+
|
|
48
|
+
# --- LAYER 1: 6-source converge. Each source = a label + a glob; grep the convention line. ---
|
|
49
|
+
# (source label · file glob) — sources that don't exist yet simply contribute nothing (fail-visible below).
|
|
50
|
+
_pull() { # $1=source-label $2..=files
|
|
51
|
+
local label="$1"; shift
|
|
52
|
+
local f
|
|
53
|
+
for f in "$@"; do
|
|
54
|
+
[ -f "$f" ] || continue
|
|
55
|
+
# convention line: "CHAMBER-CANDIDATE: <desc>" (case-insensitive marker, desc after the colon)
|
|
56
|
+
grep -inE '^[[:space:]]*CHAMBER-CANDIDATE:' "$f" 2>/dev/null \
|
|
57
|
+
| sed -E 's/^[0-9]+:[[:space:]]*CHAMBER-CANDIDATE:[[:space:]]*//I' \
|
|
58
|
+
| while IFS= read -r desc; do
|
|
59
|
+
[ -n "$desc" ] && printf '%s\t%s\n' "$label" "$desc" >> "$RAW"
|
|
60
|
+
done
|
|
61
|
+
done
|
|
62
|
+
}
|
|
63
|
+
SRC_SEEN=0
|
|
64
|
+
for spec in \
|
|
65
|
+
"harness-doctor:$FH/tracks/_meta/*harness_doctor*.md" \
|
|
66
|
+
"harvest-loop:$FH/tracks/_audit/*.md" \
|
|
67
|
+
"fh-signal:$FH/tracks/_meta/fh_signal_*.md" \
|
|
68
|
+
"field-harvest:$FH/tracks/_contrib/*.md" \
|
|
69
|
+
"frontier-digest:$FH/tracks/_meta/frontier_digest_*.md" \
|
|
70
|
+
"uap:$FH/tracks/_meta/user_adaptation_profile.md" ; do
|
|
71
|
+
label="${spec%%:*}"; glob="${spec#*:}"
|
|
72
|
+
# word-split the glob deliberately (globbing); nullglob-safe via the -f check in _pull
|
|
73
|
+
files=$(ls $glob 2>/dev/null || true)
|
|
74
|
+
[ -n "$files" ] && SRC_SEEN=$((SRC_SEEN+1))
|
|
75
|
+
# shellcheck disable=SC2086
|
|
76
|
+
_pull "$label" $files
|
|
77
|
+
done
|
|
78
|
+
|
|
79
|
+
NRAW=$(grep -c . "$RAW" 2>/dev/null); NRAW=${NRAW:-0}
|
|
80
|
+
echo "── chamber candidate collect ──"
|
|
81
|
+
echo "sources present: $SRC_SEEN/6 · raw candidate markers found: $NRAW"
|
|
82
|
+
if [ "$NRAW" -eq 0 ]; then
|
|
83
|
+
echo "queue EMPTY — no 'CHAMBER-CANDIDATE:' markers in any source yet (convention not adopted / nothing surfaced)."
|
|
84
|
+
echo " This is an honest result, not an error: the discovery volume is n=0 until sources emit the marker."
|
|
85
|
+
echo " (Add 'CHAMBER-CANDIDATE: <desc>' to a harness-doctor / fh_signal / frontier-digest / harvest entry.)"
|
|
86
|
+
exit 0
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
# --- keyword signature per candidate (for dedup) ---
|
|
90
|
+
STOP='^(that|this|from|into|over|when|what|will|your|there|their|then|than|with|chamber|candidate|skill|agent|tool|does|done|after|before|which|about|through)$'
|
|
91
|
+
_sig() { printf '%s' "$1" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9가-힣' ' ' | tr -s ' ' '\n' \
|
|
92
|
+
| awk 'length($0)>=4' | grep -vE "$STOP" | sort -u; }
|
|
93
|
+
# kill-side signature for the seen-filter: keeps 2+ char tokens (a slug's meaningful short tokens like
|
|
94
|
+
# "qa"/"ui" are dropped by _sig's ≥4 filter → an all-short slug yields an EMPTY sig → kkn=0 → the old
|
|
95
|
+
# match never fired → a KILLed candidate re-entered the queue SILENTLY, Axis-2 MED-3b). Drops stopwords only.
|
|
96
|
+
_ksig() { printf '%s' "$1" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9가-힣' ' ' | tr -s ' ' '\n' \
|
|
97
|
+
| awk 'length($0)>=2' | grep -vE "$STOP" | sort -u; }
|
|
98
|
+
|
|
99
|
+
# --- LAYER 2: dedup by greedy jaccard clustering ---
|
|
100
|
+
# cluster files: $WORK/cluster.N holds "source<TAB>desc" lines; $WORK/sig.N holds the union keyword sig.
|
|
101
|
+
NC=0
|
|
102
|
+
idx=0
|
|
103
|
+
while IFS=$'\t' read -r label desc; do
|
|
104
|
+
idx=$((idx+1))
|
|
105
|
+
_sig "$desc" > "$WORK/candsig.$idx"
|
|
106
|
+
merged=0
|
|
107
|
+
c=1
|
|
108
|
+
while [ "$c" -le "$NC" ]; do
|
|
109
|
+
inter=$(comm -12 "$WORK/sig.$c" "$WORK/candsig.$idx" 2>/dev/null | grep -c . || true)
|
|
110
|
+
union=$(sort -u "$WORK/sig.$c" "$WORK/candsig.$idx" 2>/dev/null | grep -c . || true)
|
|
111
|
+
jac=0; [ "${union:-0}" -gt 0 ] && jac=$(( inter * 100 / union ))
|
|
112
|
+
if [ "$jac" -ge "$MINJ" ]; then
|
|
113
|
+
printf '%s\t%s\n' "$label" "$desc" >> "$WORK/cluster.$c"
|
|
114
|
+
sort -u "$WORK/sig.$c" "$WORK/candsig.$idx" > "$WORK/sig.$c.tmp" && mv "$WORK/sig.$c.tmp" "$WORK/sig.$c"
|
|
115
|
+
merged=1; break
|
|
116
|
+
fi
|
|
117
|
+
c=$((c+1))
|
|
118
|
+
done
|
|
119
|
+
if [ "$merged" -eq 0 ]; then
|
|
120
|
+
NC=$((NC+1))
|
|
121
|
+
printf '%s\t%s\n' "$label" "$desc" > "$WORK/cluster.$NC"
|
|
122
|
+
cp "$WORK/candsig.$idx" "$WORK/sig.$NC"
|
|
123
|
+
fi
|
|
124
|
+
done < "$RAW"
|
|
125
|
+
|
|
126
|
+
# --- seen-filter: pull already-KILLed candidates from the G4 run ledger so a re-listed marker for a
|
|
127
|
+
# candidate the chamber already killed does NOT re-enter the main queue (the run-#3 real-use gap). It is
|
|
128
|
+
# EXCLUDED from the ranked queue but SURFACED in a trailing section (re-emit trigger visibility — a KILL
|
|
129
|
+
# is revisitable once its measured observation lands, so we don't silently erase it). Degrade: no ledger
|
|
130
|
+
# → no seen-filter (fail-visible; equals the pre-seen behavior, safe). ---
|
|
131
|
+
LEDGER="$FH/tracks/_chamber/INDEX.md"
|
|
132
|
+
KILLED="$WORK/killed"; : > "$KILLED"
|
|
133
|
+
if [ -f "$LEDGER" ]; then
|
|
134
|
+
# KILL rows: a run-log table data row (`| #N | date | candidate | VERDICT | ... |`) whose VERDICT
|
|
135
|
+
# field ($5) says KILL. Scope the KILL test to the verdict field — NOT a whole-line grep, which would
|
|
136
|
+
# match "kill" inside "skill" in any row's carry-text (Axis-2 HIGH-1: an EMIT row folding a sliver
|
|
137
|
+
# "into goal-quench skill" would be misread as KILL). Candidate = $4; strip backticks/asterisks/space.
|
|
138
|
+
awk -F'|' '$0 ~ /^\| *#/ && NF>=5 && toupper($5) ~ /KILL/ {print $4}' "$LEDGER" 2>/dev/null \
|
|
139
|
+
| sed 's/[`*]//g; s/^ *//; s/ *$//' | grep -v '^$' >> "$KILLED"
|
|
140
|
+
fi
|
|
141
|
+
NKILL=$(grep -c . "$KILLED" 2>/dev/null); NKILL=${NKILL:-0}
|
|
142
|
+
SEENOUT="$WORK/seen_out"; : > "$SEENOUT"
|
|
143
|
+
|
|
144
|
+
# --- LAYER 2: rank each cluster = source-diversity*2 + frequency; then screen for reinvention ---
|
|
145
|
+
RANKED="$WORK/ranked"; : > "$RANKED"
|
|
146
|
+
c=1
|
|
147
|
+
while [ "$c" -le "$NC" ]; do
|
|
148
|
+
freq=$(grep -c . "$WORK/cluster.$c" 2>/dev/null); freq=${freq:-0}
|
|
149
|
+
ndiv=$(cut -f1 "$WORK/cluster.$c" | sort -u | grep -c . || true)
|
|
150
|
+
srcs=$(cut -f1 "$WORK/cluster.$c" | sort -u | paste -sd, -)
|
|
151
|
+
rep=$(head -1 "$WORK/cluster.$c" | cut -f2-)
|
|
152
|
+
score=$(( ndiv * 2 + freq ))
|
|
153
|
+
# seen-filter: does this candidate match an already-KILLed ledger entry? Two decorrelated tests, either
|
|
154
|
+
# sufficient: (1) kill-name keyword recall ≥60% (the common multi-token case); (2) normalized-substring
|
|
155
|
+
# fallback — the flattened slug (alnum-only, ≥5 chars) appearing in the flattened candidate — which
|
|
156
|
+
# closes the all-short-token silent-re-entry hole (MED-3b) that recall alone leaves open. Residual
|
|
157
|
+
# (documented, accepted): a paraphrased re-listing (router→routing) can still under-recall (MED-4), and
|
|
158
|
+
# a generic slug can over-exclude — but over-exclusion is VISIBLE in the SEEN-KILLED section (operator
|
|
159
|
+
# catches a wrong match), whereas silent re-entry was invisible. Visible-imprecise ≻ silent-miss.
|
|
160
|
+
if [ "$NKILL" -gt 0 ]; then
|
|
161
|
+
_ksig "$rep" > "$WORK/repsig"
|
|
162
|
+
repflat=$(printf '%s' "$rep" | tr '[:upper:]' '[:lower:]' | tr -cd 'a-z0-9')
|
|
163
|
+
seen_match=""
|
|
164
|
+
while IFS= read -r kname; do
|
|
165
|
+
[ -z "$kname" ] && continue
|
|
166
|
+
_ksig "$kname" > "$WORK/ksig"
|
|
167
|
+
kkn=$(grep -c . "$WORK/ksig" 2>/dev/null || true); kkn=${kkn:-0}
|
|
168
|
+
matched=0; [ "$kkn" -gt 0 ] && matched=$(comm -12 "$WORK/ksig" "$WORK/repsig" 2>/dev/null | grep -c . || true)
|
|
169
|
+
kpct=0; [ "$kkn" -gt 0 ] && kpct=$(( matched * 100 / kkn ))
|
|
170
|
+
kflat=$(printf '%s' "$kname" | tr '[:upper:]' '[:lower:]' | tr -cd 'a-z0-9')
|
|
171
|
+
if [ "$kkn" -gt 0 ] && [ "$kpct" -ge 60 ]; then seen_match="$kname"; break; fi
|
|
172
|
+
if [ "${#kflat}" -ge 5 ] && printf '%s' "$repflat" | grep -qF "$kflat"; then seen_match="$kname"; break; fi
|
|
173
|
+
done < "$KILLED"
|
|
174
|
+
if [ -n "$seen_match" ]; then
|
|
175
|
+
printf '%s\t%s\n' "$seen_match" "$rep" >> "$SEENOUT"
|
|
176
|
+
c=$((c+1)); continue
|
|
177
|
+
fi
|
|
178
|
+
fi
|
|
179
|
+
# reinvention first-pass on the representative description. Capture BOTH the verdict AND the matched
|
|
180
|
+
# anchor the screener already emits ("top asset overlap: X") so a DUPLICATE-CANDIDATE is ACTIONABLE
|
|
181
|
+
# ("DUP:skill:goal-quench") instead of a uniform non-discriminating flag (Axis-2 challenger Axis-5).
|
|
182
|
+
verdict="?"
|
|
183
|
+
if [ -x "$SCREEN" ] || [ -f "$SCREEN" ]; then
|
|
184
|
+
sout=$(bash "$SCREEN" "$rep" 2>/dev/null)
|
|
185
|
+
verdict=$(printf '%s\n' "$sout" | grep -oE 'VERDICT: [A-Z-]+' | head -1 | sed 's/VERDICT: //')
|
|
186
|
+
[ -z "$verdict" ] && verdict="?"
|
|
187
|
+
if [ "$verdict" = "DUPLICATE-CANDIDATE" ]; then
|
|
188
|
+
anchor=$(printf '%s\n' "$sout" | grep -oE 'top asset overlap: [^ ]+' | head -1 | sed 's/top asset overlap: //')
|
|
189
|
+
[ -n "$anchor" ] && verdict="DUP:$anchor"
|
|
190
|
+
fi
|
|
191
|
+
fi
|
|
192
|
+
printf '%03d\t%s\t%s\t%s\t%s\n' "$score" "$ndiv" "$srcs" "$verdict" "$rep" >> "$RANKED"
|
|
193
|
+
c=$((c+1))
|
|
194
|
+
done
|
|
195
|
+
|
|
196
|
+
echo "deduped candidates: $NC (from $NRAW raw markers)"
|
|
197
|
+
echo ""
|
|
198
|
+
printf '%-5s %-6s %-24s %-18s %s\n' "SCORE" "SRCS" "SOURCES" "REINVENTION" "CANDIDATE"
|
|
199
|
+
printf '%-5s %-6s %-24s %-18s %s\n' "-----" "------" "------------------------" "------------------" "---------"
|
|
200
|
+
sort -rn "$RANKED" | while IFS=$'\t' read -r score ndiv srcs verdict rep; do
|
|
201
|
+
# drop leading zeros for display
|
|
202
|
+
printf '%-5d %-6d %-24s %-18s %s\n' "$((10#$score))" "$ndiv" "$srcs" "$verdict" "$rep"
|
|
203
|
+
done
|
|
204
|
+
echo ""
|
|
205
|
+
echo "NOTE: SCORE = source-diversity×2 + frequency. REINVENTION is a first-pass flag (DUPLICATE-CANDIDATE"
|
|
206
|
+
echo "= HITL KILL review, not auto-drop). Uncertainty/failure-cost stay JUDGED — operator picks which"
|
|
207
|
+
echo "survivors actually enter the chamber (goal-quench budget gate + HITL). This tool ranks; it never admits."
|
|
208
|
+
|
|
209
|
+
# seen-filter trailing section: candidates the G4 ledger already KILLed are shown here (NOT in the ranked
|
|
210
|
+
# queue) so a re-listed marker does not silently re-enter the chamber, yet the KILL stays visible (a KILL
|
|
211
|
+
# is revisitable once its re-emit trigger lands — see the run's EMISSION_VERDICT for that condition).
|
|
212
|
+
if [ -s "$SEENOUT" ]; then
|
|
213
|
+
echo ""
|
|
214
|
+
echo "SEEN-KILLED — excluded from the queue (G4 ledger $LEDGER already KILLed these):"
|
|
215
|
+
while IFS=$'\t' read -r kn rep; do
|
|
216
|
+
[ -z "$rep" ] && continue
|
|
217
|
+
echo " · \"$rep\" ↔ killed as \"$kn\" (재등재 스킵; re-emit only when that run's trigger condition is met)"
|
|
218
|
+
done < "$SEENOUT"
|
|
219
|
+
elif [ ! -f "$LEDGER" ]; then
|
|
220
|
+
echo ""
|
|
221
|
+
echo "SEEN-FILTER: skipped — no G4 ledger at $LEDGER (fail-visible; no seen-exclusion applied this run)."
|
|
222
|
+
fi
|
|
223
|
+
exit 0
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# degrade_direction_scan.sh — mechanical pre-screen for the "default-toward-PASS" smell
|
|
3
|
+
#
|
|
4
|
+
# The correlated blind spot measured 2026-07-03 across 3 harnesses (qasp/the-bible/pmh):
|
|
5
|
+
# "When a verdict surface cannot mechanically ground its judgment, it defaults toward
|
|
6
|
+
# PASS instead of safe-fail." Same-family review (even frontier + target-tier sim)
|
|
7
|
+
# shares the author's optimistic reading of that discretion and misses it; a
|
|
8
|
+
# different-family auditor catches it. This script is the cheap MECHANICAL pre-screen
|
|
9
|
+
# that runs BEFORE the cross-family pass — it flags the code shapes where a permissive
|
|
10
|
+
# value lands on an unconstrained branch, so the reviewer's attention goes there first.
|
|
11
|
+
#
|
|
12
|
+
# IT IS A REVIEW SURFACE, NOT A HARD GATE. Grep-heuristic → false positives are expected.
|
|
13
|
+
# A hit means "prove this is not default-toward-PASS", not "this is a bug". It never
|
|
14
|
+
# blocks a commit on its own (advisory exit code). The terminal verdict is the
|
|
15
|
+
# cross-family adversarial review + governor source-grounding, never this scan alone.
|
|
16
|
+
# (Irreversibility-gate note: because it is advisory, a degraded/empty run is a no-op,
|
|
17
|
+
# not a free pass — the cross-family review is the load-bearing check it feeds.)
|
|
18
|
+
#
|
|
19
|
+
# NAMED RECALL RESIDUALS (cross-family audit, gpt-5.5, 2026-07-28 — accepted, not closed):
|
|
20
|
+
# * Indirection defeats every probe. `allow() { exit 0; }` … `check || allow` is the same
|
|
21
|
+
# fail-open shape one function call away, and a line-oriented grep cannot follow it. This is
|
|
22
|
+
# inherent to the heuristic, not a bug to patch — it is why the terminal verdict is the
|
|
23
|
+
# cross-family review, and why a clean run is never evidence of safety.
|
|
24
|
+
# * The regression anchor proves the probes on the fixture GRAMMAR it ships, not on every
|
|
25
|
+
# spelling of each class (e.g. `if ! cmd; then :; fi`, arithmetic-context defaults).
|
|
26
|
+
#
|
|
27
|
+
# Usage:
|
|
28
|
+
# bash scripts/degrade_direction_scan.sh [path ...] # scan dirs/files (default: .)
|
|
29
|
+
# git diff --name-only main..HEAD -- '*.py' | xargs bash scripts/degrade_direction_scan.sh
|
|
30
|
+
# Exit: 0 = no smells found; 2 = smells found (ADVISORY signal — do not hard-block on it)
|
|
31
|
+
set -uo pipefail
|
|
32
|
+
|
|
33
|
+
TARGETS=("$@")
|
|
34
|
+
[ ${#TARGETS[@]} -eq 0 ] && TARGETS=(".")
|
|
35
|
+
|
|
36
|
+
# Permissive values a verdict/gate surface must never land on by *default* / fall-through.
|
|
37
|
+
PASS='(True|"PASS"|'"'"'PASS'"'"'|"ALLOW"|'"'"'ALLOW'"'"'|"OK"|'"'"'OK'"'"'|"VALID"|'"'"'VALID'"'"'|"GRANTED"|'"'"'GRANTED'"'"'|"PASSED"|'"'"'PASSED'"'"'|allow|ALLOW)'
|
|
38
|
+
|
|
39
|
+
# Collect target files. Scannable = py + sh (the smell probes are Python-shaped but bash surfaces —
|
|
40
|
+
# incl. this gate's own pre-push/pre-commit-hook trigger category — must not be invisibly dropped).
|
|
41
|
+
# Anything else is tracked as UNSCANNABLE so a load-bearing surface in another language is reported
|
|
42
|
+
# as "not covered", never silently folded into an "advisory clean" (M#2, steel-quench 2026-07-03).
|
|
43
|
+
FILES=(); UNSCANNABLE=()
|
|
44
|
+
for t in "${TARGETS[@]}"; do
|
|
45
|
+
if [ -d "$t" ]; then
|
|
46
|
+
while IFS= read -r f; do FILES+=("$f"); done < <(find "$t" -type f \( -name '*.py' -o -name '*.sh' \) 2>/dev/null)
|
|
47
|
+
# Shebang pass — this is what makes git hooks visible at all. Measured 2026-07-28:
|
|
48
|
+
# `templates/.git-hooks` (files named `pre-push`, no extension, under a dotted directory) —
|
|
49
|
+
# FH's own mechanical floor — reported "no scannable (py/sh) target files", exit 0.
|
|
50
|
+
# Shebang pass. Deliberately NOT restricted to extensionless names: a cross-family audit
|
|
51
|
+
# (2026-07-28, gpt-5.5) found that an earlier draft skipped any dotted basename, so a shell
|
|
52
|
+
# file named `helper.bash` carrying an identical known-positive was dropped from a DIRECTORY
|
|
53
|
+
# target in silence — while the explicit-file branch reported the same file as UNSCANNABLE.
|
|
54
|
+
# Silent-drop on one path and honest-report on the other is the fail-open half. Confirmed by
|
|
55
|
+
# running both paths on the same fixture before accepting the finding.
|
|
56
|
+
while IFS= read -r f; do
|
|
57
|
+
b="${f##*/}" # basename — a dotted DIRECTORY (.git-hooks) is not an extension
|
|
58
|
+
case "$b" in
|
|
59
|
+
*.py|*.sh) continue ;; # already collected above
|
|
60
|
+
*.md|*.json|*.yaml|*.yml|*.txt|*.lock|*.png|*.jpg|*.svg|*.pdf|*.zip) continue ;;
|
|
61
|
+
esac
|
|
62
|
+
head -n1 "$f" 2>/dev/null | grep -qE '^#!.*\b(ba|z|k)?sh\b' && FILES+=("$f")
|
|
63
|
+
done < <(find "$t" -type f 2>/dev/null)
|
|
64
|
+
elif [ -f "$t" ]; then
|
|
65
|
+
tb="${t##*/}"
|
|
66
|
+
case "$tb" in
|
|
67
|
+
*.py|*.sh) FILES+=("$t") ;;
|
|
68
|
+
*.*) UNSCANNABLE+=("$t") ;;
|
|
69
|
+
*) if head -n1 "$t" 2>/dev/null | grep -qE '^#!.*\b(ba|z|k)?sh\b'; then FILES+=("$t"); else UNSCANNABLE+=("$t"); fi ;;
|
|
70
|
+
esac
|
|
71
|
+
fi
|
|
72
|
+
done
|
|
73
|
+
if [ ${#FILES[@]} -eq 0 ]; then
|
|
74
|
+
if [ ${#UNSCANNABLE[@]} -gt 0 ]; then
|
|
75
|
+
echo "degrade-scan: ${#UNSCANNABLE[@]} changed file(s) are OUTSIDE the scannable set (py/sh) — NOT scanned, NOT 'clean':"
|
|
76
|
+
printf ' (unscannable) %s\n' "${UNSCANNABLE[@]}"
|
|
77
|
+
echo "A load-bearing surface in another language must go straight to cross-family review."
|
|
78
|
+
exit 2 # advisory non-clean — an orchestrator keying on exit code must not read this as clean
|
|
79
|
+
fi
|
|
80
|
+
echo "degrade-scan: no scannable (py/sh) target files"; exit 0
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
hits=0
|
|
84
|
+
emit() { printf ' %s:%s\n [%s] %s\n' "$1" "$2" "$3" "$4"; hits=$((hits+1)); }
|
|
85
|
+
|
|
86
|
+
for f in "${FILES[@]}"; do
|
|
87
|
+
# ---- Shell-shaped probes (S*) -------------------------------------------------------------
|
|
88
|
+
# Calibration finding (2026-07-28, known-pair): every probe below the S-block is PYTHON-shaped
|
|
89
|
+
# (`except:` / `.get(k, True)` / `if not x:` / `.split()`), none of which exist in bash. A .sh file
|
|
90
|
+
# was still COLLECTED and counted, so a fail-open shell gate printed "no smells in 1 scanned py/sh
|
|
91
|
+
# file" — a FALSE CLEAN, which is worse than honest non-coverage. A known-positive .sh carrying four
|
|
92
|
+
# distinct default-toward-PASS shapes scored 0/4. These probes close that; they run on any file
|
|
93
|
+
# whose basename ends in .sh OR that carries a shell shebang (see the is_sh test below).
|
|
94
|
+
is_sh=""; fb="${f##*/}"
|
|
95
|
+
case "$fb" in
|
|
96
|
+
*.sh) is_sh=1 ;;
|
|
97
|
+
*.py) ;;
|
|
98
|
+
# Any other collected file reached FILES only via the shebang pass, or is a dotted shell name
|
|
99
|
+
# like `helper.bash`. Re-check the shebang rather than keying on the extension — keying on the
|
|
100
|
+
# extension is what produced the collect-but-never-probe false clean this whole block exists to
|
|
101
|
+
# close (n+10). Collected-but-unprobed must not be reachable again.
|
|
102
|
+
*) head -n1 "$f" 2>/dev/null | grep -qE '^#!.*\b(ba|z|k)?sh\b' && is_sh=1 ;;
|
|
103
|
+
esac
|
|
104
|
+
if [ -n "$is_sh" ]; then
|
|
105
|
+
# S1 — permissive short-circuit on a FAILING CHECK: `scan=$(...) || return 0`, `verify … || exit 0`.
|
|
106
|
+
# The check errored and the surface reports success. Safe-fail is `|| return 1` / `|| exit 1`.
|
|
107
|
+
# SCOPED to check-shaped left-hand sides (command substitution, or a verb like
|
|
108
|
+
# scan/check/verify/grep/audit/validate/gate). A PRECONDITION guard — `[ -d x ] || exit 0`,
|
|
109
|
+
# `[[ $d =~ … ]] || return 0` — is deliberately excluded: "this run does not apply here" is not
|
|
110
|
+
# the same claim as "this check passed". Hand-measured 2026-07-28: unscoped, 6/6 sampled hits
|
|
111
|
+
# were false positives, 4 of them precondition guards.
|
|
112
|
+
# EXCEPTION, re-added after an adversarial pass on this very scoping: a `-f`/`-x` test is a
|
|
113
|
+
# DEPENDENCY check, not a scope check. `[ -f "$GUARD_LIB" ] || exit 0` means "my guard library
|
|
114
|
+
# is missing, therefore allow" — the fail-open shape that bit qasp on 2026-07-28. Excluding it
|
|
115
|
+
# with the scope guards would have hidden exactly the class this scan exists to find.
|
|
116
|
+
while IFS= read -r m; do
|
|
117
|
+
emit "$f" "${m%%:*}" "S1:||→PASS(sh)" "failing check short-circuits to a permissive result (\`|| return 0\` / \`|| exit 0\` / \`|| true\`) — an errored check must fail closed, not report success"
|
|
118
|
+
done < <(grep -nE '\|\|[[:space:]]*(return[[:space:]]+0|exit[[:space:]]+0|true)([[:space:]]*(#|;|$))' "$f" 2>/dev/null \
|
|
119
|
+
| grep -vE '#[[:space:]]*noqa[:[:space:]]*degrade' \
|
|
120
|
+
| grep -vE '^[0-9]+:[[:space:]]*(if[[:space:]]+)?\[\[?[[:space:]]*(-[dznN][[:space:]]|[^]]*=~)' \
|
|
121
|
+
| grep -E '(\$\(|`|\[[[:space:]]*-[fx][[:space:]]|\b(scan|check|verify|validate|audit|grep|gate|assert|lint|test_)[A-Za-z_]*[[:space:](])')
|
|
122
|
+
|
|
123
|
+
# S2 — `else` fall-through to a permissive exit/return within 2 lines (unenumerated case → allow).
|
|
124
|
+
while IFS= read -r ln; do
|
|
125
|
+
emit "$f" "$ln" "S2:else→PASS(sh)" "else/fall-through branch exits permissively — the unenumerated case should fail closed"
|
|
126
|
+
done < <(grep -nE -A2 '^[[:space:]]*else[[:space:]]*$' "$f" 2>/dev/null \
|
|
127
|
+
| grep -E '^[0-9]+[-:][[:space:]]*(exit[[:space:]]+0|return[[:space:]]+0)[[:space:]]*(#.*)?$' \
|
|
128
|
+
| grep -oE '^[0-9]+' | sort -u)
|
|
129
|
+
|
|
130
|
+
# S3 — empty/unset defaulted to a permissive VERDICT: `${V:-PASS}` / `V="PASS"` after a failed read.
|
|
131
|
+
# "the value never arrived" must not be spelled the same way as "the value said PASS".
|
|
132
|
+
# `${V:-0}` and `${V:-true}` are NOT flagged: numeric defaulting is the prescribed integer
|
|
133
|
+
# sanitization against the pipefail-fallback class (see S5), and flagging it would push an
|
|
134
|
+
# author to delete the remedy. Measured 2026-07-28 — `${PRS:-0}` in session_close_check.sh is
|
|
135
|
+
# the fix, not the defect. Only explicit verdict words count.
|
|
136
|
+
while IFS= read -r m; do
|
|
137
|
+
emit "$f" "${m%%:*}" "S3:default→PASS(sh)" "unset/empty defaults to a permissive verdict — absent is not clean (\`not found\` ≠ \`0\`); default to the blocking value"
|
|
138
|
+
done < <(grep -nE "(\\$\{[A-Za-z_][A-Za-z0-9_]*:?-[[:space:]]*(PASS|OK|ALLOW|GRANTED|VALID|PASSED)\}|\|\|[[:space:]]*[A-Za-z_][A-Za-z0-9_]*=[\"']?(PASS|OK|ALLOW|GRANTED|VALID))" "$f" 2>/dev/null \
|
|
139
|
+
| grep -vE '#[[:space:]]*noqa[:[:space:]]*degrade')
|
|
140
|
+
|
|
141
|
+
# S4 — empty-output guard treated as clean: `if [ -z "$out" ]; then return 0/exit 0`.
|
|
142
|
+
# Identical to Probe E's falsy-sentinel class, in shell spelling: an errored scan produces empty
|
|
143
|
+
# output, so "found nothing" and "never ran" become indistinguishable.
|
|
144
|
+
while IFS= read -r ln; do
|
|
145
|
+
emit "$f" "$ln" "S4:empty→PASS(sh)" "empty output treated as clean — a scan that errored also produces empty output; distinguish 'errored/absent' from 'verified clean'"
|
|
146
|
+
done < <(grep -nE -A2 '^[[:space:]]*(if|elif)[[:space:]]+\[+[[:space:]]*-z[[:space:]]' "$f" 2>/dev/null \
|
|
147
|
+
| grep -E '^[0-9]+[-:][[:space:]]*(exit[[:space:]]+0|return[[:space:]]+0)[[:space:]]*(#.*)?$' \
|
|
148
|
+
| grep -oE '^[0-9]+' | sort -u)
|
|
149
|
+
|
|
150
|
+
# S5 — the pipefail-fallback disarm: `... | grep -c ... || echo 0` appends a SECOND line under
|
|
151
|
+
# `set -o pipefail`, so the later `-gt` integer test becomes a bash error (= false) and the guard
|
|
152
|
+
# passes silently, with the error going only to stderr. Measured class, 2026-07-26.
|
|
153
|
+
while IFS= read -r m; do
|
|
154
|
+
emit "$f" "${m%%:*}" "S5:pipefail-fallback(sh)" "\`|| echo 0\` fallback on a pipeline — under \`set -o pipefail\` this yields a multi-line value whose integer comparison errors out and silently passes the guard; split the pipeline and sanitize to an integer"
|
|
155
|
+
done < <(grep -nE '\|[^|]+\|\|[[:space:]]*echo[[:space:]]+[\"'"'"']?0' "$f" 2>/dev/null \
|
|
156
|
+
| grep -vE '#[[:space:]]*noqa[:[:space:]]*degrade')
|
|
157
|
+
fi
|
|
158
|
+
|
|
159
|
+
# Probe A — except/else/finally block returning a permissive value within 2 lines.
|
|
160
|
+
# The classic "swallow the error → report success". A safe-fail returns BLOCK/None/raise.
|
|
161
|
+
while IFS= read -r line; do
|
|
162
|
+
ln="${line%%:*}"
|
|
163
|
+
emit "$f" "$ln" "A:except/else→PASS" "permissive return on an error/fall-through branch — safe-fail must return BLOCK/None or re-raise"
|
|
164
|
+
done < <(grep -nE -A2 '^[[:space:]]*(except([[:space:]][^:]*)?|else|finally)[[:space:]]*:' "$f" 2>/dev/null \
|
|
165
|
+
| grep -E "return[[:space:]]+$PASS([[:space:],)]|$)" | grep -oE '^[0-9]+' | sort -u | sed 's/$/:/')
|
|
166
|
+
|
|
167
|
+
# Probe B — dict default / setdefault to a permissive value (unknown key → PASS).
|
|
168
|
+
while IFS= read -r m; do
|
|
169
|
+
emit "$f" "${m%%:*}" "B:default→PASS" "unknown-key default is permissive — unenumerated case should default to safe-fail"
|
|
170
|
+
done < <(grep -nE "(\.get\([^,]+,[[:space:]]*$PASS[[:space:])]|setdefault\([^,]+,[[:space:]]*$PASS[[:space:])])" "$f" 2>/dev/null)
|
|
171
|
+
|
|
172
|
+
# Probe C — substring membership on a grounding/verdict/state line (loose match, not exact).
|
|
173
|
+
# `if tok in text` masks paid⊂prepaid / 완료⊂미완료. Exact/word-boundary is the safe form.
|
|
174
|
+
while IFS= read -r m; do
|
|
175
|
+
emit "$f" "${m%%:*}" "C:substring-grounding" "substring 'in' on a verdict/state/present line — use exact or word-boundary match, not containment"
|
|
176
|
+
done < <(grep -nE '\b(verdict|present|ground|state|match|expected|assert)\w*\b' "$f" 2>/dev/null \
|
|
177
|
+
| grep -vE ':[[:space:]]*(#|//|from |import )' | grep -vE '#[[:space:]]*noqa[:[:space:]]*degrade' \
|
|
178
|
+
| grep -E '[^._a-zA-Z]in[[:space:]]' | grep -vE '\bfor\b|__contains__|not in|in \(|in \[|in \{|in range|in enumerate|in [A-Z_]+\b' \
|
|
179
|
+
| grep -oE '^[0-9]+' | sed 's/$/:/')
|
|
180
|
+
|
|
181
|
+
# Probe C2 — bare `VAR in VAR` in an if/return/assert/while context, WITHOUT a grounding keyword.
|
|
182
|
+
# Probe C is keyword-gated (low-noise) and therefore misses the doc's own headline example
|
|
183
|
+
# `tok in text` (paid⊂prepaid) when the variables aren't named verdict/state (M#4, steel-quench).
|
|
184
|
+
# C2 closes that: simple var-in-var (not a collection literal / range / for) = a likely
|
|
185
|
+
# containment check that should be exact/word-boundary if it grounds a verdict. Higher noise; advisory.
|
|
186
|
+
while IFS= read -r m; do
|
|
187
|
+
emit "$f" "${m%%:*}" "C2:substring-boolean" "bare 'X in Y' in if/return/assert — if this grounds a presence/verdict check, use exact/word-boundary match, not containment"
|
|
188
|
+
done < <(grep -nE '^[[:space:]]*(if|elif|return|assert|while)[[:space:]]+[A-Za-z_][A-Za-z0-9_]*[[:space:]]+in[[:space:]]+[A-Za-z_][A-Za-z0-9_.]*[[:space:]]*[:)]?[[:space:]]*$' "$f" 2>/dev/null \
|
|
189
|
+
| grep -vE '\bfor\b|in range|in enumerate|not in' \
|
|
190
|
+
| grep -vE '\b(verdict|present|ground|state|match|expected)\w*\b')
|
|
191
|
+
|
|
192
|
+
# Probe E — negated-falsy guard returning permissive (dominance-benchmark round-2 f2 class): an error
|
|
193
|
+
# SENTINEL (None / {} / "" / []) is falsy, so `if not X: return <PASS>` treats "the check errored / never
|
|
194
|
+
# ran" identically to "the check ran and found nothing clean". Distinguish errored from clean before allowing.
|
|
195
|
+
while IFS= read -r ln; do
|
|
196
|
+
emit "$f" "$ln" "E:falsy-sentinel→PASS" "negated-falsy guard returns permissive — a falsy error sentinel (None/{}/'') masquerades as 'clean'; a gate must distinguish 'errored/absent' from 'verified clean'"
|
|
197
|
+
done < <(grep -nE -A2 '^[[:space:]]*if[[:space:]]+not[[:space:]]+[A-Za-z_][A-Za-z0-9_.]*[[:space:]]*:' "$f" 2>/dev/null \
|
|
198
|
+
| grep -E "return[[:space:]]+$PASS([[:space:],)]|$)" | grep -oE '^[0-9]+' | sort -u | sed 's/$/:/')
|
|
199
|
+
|
|
200
|
+
# Probe F — positional field-select from a split result feeding a decision (round-2 c3 class): taking the
|
|
201
|
+
# decision from `parts[-1]`/`parts[0]` of an attacker-influenceable split lets a crafted field (e.g. a
|
|
202
|
+
# signed DENY whose free-form comment ends "::ALLOW") negate the verdict. Validate structure, don't select by position.
|
|
203
|
+
if grep -qE '\.r?split\(' "$f" 2>/dev/null; then
|
|
204
|
+
while IFS= read -r m; do
|
|
205
|
+
emit "$f" "${m%%:*}" "F:split-positional-verdict" "decision taken by position ([-1]/[0]) from a split result — an attacker-controlled trailing/leading field can negate the verdict; validate structure, don't select by position"
|
|
206
|
+
done < <(grep -nE '\[[[:space:]]*-?[01][[:space:]]*\]' "$f" 2>/dev/null \
|
|
207
|
+
| grep -iE 'decision|verdict|allow|deny|approv|grant|status|result|policy')
|
|
208
|
+
fi
|
|
209
|
+
done
|
|
210
|
+
|
|
211
|
+
echo "----"
|
|
212
|
+
[ ${#UNSCANNABLE[@]} -gt 0 ] && printf 'note: %s changed file(s) outside py/sh — NOT covered by this scan (send to cross-family directly).\n' "${#UNSCANNABLE[@]}"
|
|
213
|
+
if [ "$hits" -gt 0 ]; then
|
|
214
|
+
echo "degrade-scan: $hits smell(s) — ADVISORY. Each = 'prove this is not default-toward-PASS'."
|
|
215
|
+
echo "Terminal verdict = cross-family adversarial review (auto-decorrelation), not this scan."
|
|
216
|
+
exit 2
|
|
217
|
+
fi
|
|
218
|
+
# Scope-honest clean message (M#2): "clean" means only "no py/sh-pattern smells in the SCANNED set" —
|
|
219
|
+
# it does NOT assert the changed load-bearing surface is safe (other languages, non-code surfaces,
|
|
220
|
+
# and the lint's own recall gaps are out of scope). The load-bearing check is the cross-family review.
|
|
221
|
+
echo "degrade-scan: no default-toward-PASS smells in ${#FILES[@]} scanned py/sh file(s) — does NOT cover other languages / non-code surfaces / the cross-family check (advisory)."
|
|
222
|
+
exit 0
|