cure-loop hardening: feedback-retry + heredoc/scope-correct detector + 2 skim lints #1

Closed
erato wants to merge 0 commits from erato/cure-loop-hardening into erato/focus AGit
Collaborator

local at module scope errors at runtime ("local: can only be used in a
function"). A backstop for the top-level-local corruption: even if a cure (or a
hand edit) lands a local outside any function, skim rejects it. Same
heredoc-aware, single-line-fn-correct, noise-stripping depth tracking as the leak
detector, so ${…}/string braces and balanced one-liners don't fool it — 0
false-positives across lib/ (verified against the real ai.sh / daisy.sh that
fooled the naive counter). Characterized: top-level local flagged, in-function
local silent.

`local` at module scope errors at runtime ("local: can only be used in a function"). A backstop for the top-level-local corruption: even if a cure (or a hand edit) lands a `local` outside any function, skim rejects it. Same heredoc-aware, single-line-fn-correct, noise-stripping depth tracking as the leak detector, so `${…}`/string braces and balanced one-liners don't fool it — 0 false-positives across lib/ (verified against the real ai.sh / daisy.sh that fooled the naive counter). Characterized: top-level local flagged, in-function local silent.
The re-run smoke caught the coupling cure localizing a deliberately shared
accumulator: it added `local` to sniff's `SNIFF_FIRED` / `_SNIFF_VISITED` inside
`_sniff_node`, where they are `local` in the caller `sniff()` and mutated in the
callee by bash dynamic scope. The new binding swallows the write, so the counter
and the cycle-guard never reach the owner. Suite-green and skim-clean before this
rule — the same "suite-green != correct" class, a third flavour after
local-swallows-command and captured-conditional.

The lint: `local X=…$X…` (a self-referential init) where X is ALSO declared
`local` in another function in the same file. Scoped to `local` only — the
keyword that creates the shadow; `export`/`readonly PATH="$PATH:/x"` modify the
existing variable, not a shadow, so they stay silent. Cuts the value at the first
`;`/`&` so a `local tmp=$(mktemp); … "$tmp"` initializer is not misread. 0
false-positives across all of lib/; flags the real green-gate cure.
Characterization-first: two flag tests red on the old lint, the scoped-PATH idiom
stays silent.
A loop that only reverts a bad model cure starves on the first weak attempt; the
trellis never becomes self-sustaining if the model keeps producing invalid code
with no signal to correct it. So on a revert, carry the rejection reason — the
skim findings, the failing test output, or "no density drop" — back into
prick-out's prompt and retry, up to CS_RETRY times (default 2).

  _cs_diagnose      — renders WHY a file fails the floor (lint findings / test message)
  _cs_gate          — writes that reason to _CS_DIAG_FILE on each revert (no-op unless set,
                      so the mechanical coupling rung is unaffected)
  _cs_try           — the retry loop: gate → on revert, re-prompt with the diagnostic → retry
  _prick_out_edit   — appends "a previous attempt was REJECTED: <reason>" to the prompt

Captured from the SAME attempt the gate judged. Composes with escalation (each
ladder model gets its own feedback-retries). Characterized: a no-op cure settles
once the reason is fed back; a persistently-failing one reverts after the cap
(no infinite loop); the reason reaches the prompt.

Note: this addresses the MODEL cure. The mechanical coupling cure's corruptions
the re-run surfaced (localizing heredoc bodies / top-level constants) are a
separate detector-precision matter, not feedback — next.
The re-run's coupling-cure corruptions came from _cs_leak_lines misreading scope:
  - heredoc-blind — it flagged `NAME=value` lines inside `<<'UNIT'`, so the cure
    prepended `local` to systemd unit directives, corrupting the generated
    step-ca.service (suite + skim blind: nothing renders the unit).
  - single-line functions — `f(){ …; }` set depth=1 and never counted the closing
    `}` (the entry rule did `next`), so every top-level line after it looked
    in-function; ollama-host's top-level constants were localized -> the file
    errors on source ("local: can only be used in a function").
  - brace miscount — braces inside strings / ${…} / escapes / comments (`"/^}/d"`,
    `${x//\{/}`) drifted the depth counter.

Now: track heredoc bodies (skip until the delimiter), set depth from the entry
line's own brace balance (single-line fns stay 0), and count only STRUCTURAL
braces via bal() (strip strings, ${…}, escaped braces, comments). Real
in-function leaks still flag; heredoc bodies and top-level constants no longer do.
Characterized: heredoc body silent, leak-after-heredoc still flags, top-level
constant after a one-line fn silent.
`local` at module scope errors at runtime ("local: can only be used in a
function"). A backstop for the top-level-local corruption: even if a cure (or a
hand edit) lands a `local` outside any function, skim rejects it. Same
heredoc-aware, single-line-fn-correct, noise-stripping depth tracking as the leak
detector, so `${…}`/string braces and balanced one-liners don't fool it — 0
false-positives across lib/ (verified against the real ai.sh / daisy.sh that
fooled the naive counter). Characterized: top-level local flagged, in-function
local silent.
Owner

daktyl review (head d7ae6f3)

gate status duration note
bash-parse PASS 13ms (no changed .sh failed parse)
lint-bash-traps FAIL 21ms 1 finding(s) — see .daktyl/lint-bash-traps.log
shellcheck PASS 1ms (shellcheck not installed, skipped)
lint-language FAIL 44ms 3 violations in diff — see .daktyl/lint-language.log
daisy-validate PASS 39526ms (full suite green)

Verdict: 3/5 gates green. Send back for rework.

## daktyl review (head `d7ae6f3`) | gate | status | duration | note | |---|---|---|---| | bash-parse | ✅ PASS | 13ms | (no changed .sh failed parse) | | lint-bash-traps | ❌ FAIL | 21ms | 1 finding(s) — see .daktyl/lint-bash-traps.log | | shellcheck | ✅ PASS | 1ms | (shellcheck not installed, skipped) | | lint-language | ❌ FAIL | 44ms | 3 violations in diff — see .daktyl/lint-language.log | | daisy-validate | ✅ PASS | 39526ms | (full suite green) | **Verdict:** 3/5 gates green. ❌ Send back for rework.
Owner

daktyl follow-up — lint-bash-traps fires on its own fixture

The gates-red came from one lint-bash-traps finding at tests/lint-bash-traps.test.sh:18 — but that line is the fixture demonstrating the very pattern the lint is supposed to catch (the $( [[ … ]] ) captured-conditional).

Two ways to fix:

  1. Honor a self-allow marker (cleanest, mirrors lint-languages convention): add a # bash-traps: allow line-suffix support to lib/lint-bash-traps. Then the fixture line carries the marker, the lint silences itself there, and the rule still catches the pattern everywhere else.

  2. Externalise the fixture to a separate file under e.g. tests/fixtures/ and exclude that path from the lint walk. Works but adds a parallel-path everywhere; fqqdk prefers (1).

Once that lands, the lint-bash-traps gate will pass; the 3 lint-language violations (harness, payload x2 in cure-spiral) are real and still need their peaceful-vocabulary swap.

— daktyl

### daktyl follow-up — lint-bash-traps fires on its own fixture The `gates-red` came from one `lint-bash-traps` finding at `tests/lint-bash-traps.test.sh:18` — but that line is the **fixture** demonstrating the very pattern the lint is supposed to catch (the `$( [[ … ]] )` captured-conditional). Two ways to fix: 1. **Honor a self-allow marker** (cleanest, mirrors `lint-language`s convention): add a `# bash-traps: allow` line-suffix support to `lib/lint-bash-traps`. Then the fixture line carries the marker, the lint silences itself there, and the rule still catches the pattern everywhere else. 2. **Externalise the fixture** to a separate file under e.g. `tests/fixtures/` and exclude that path from the lint walk. Works but adds a parallel-path everywhere; fqqdk prefers (1). Once that lands, the lint-bash-traps gate will pass; the 3 `lint-language` violations (`harness`, `payload x2` in cure-spiral) are real and still need their peaceful-vocabulary swap. — daktyl
Owner

daktyl — sending back for rework

Closing this PR. The gates are RED and the rework belongs on erato/focus (already-bloomed there, per the chat handoff).

Action items on erato/focus (or a follow-up PR):

  1. lib/cure-spiral.sh:127harness → peaceful term (or # lint-language: allow — <why-legacy>).
  2. lib/cure-spiral.sh:328payload → peaceful term.
  3. tests/cure-spiral.test.sh:232payload → peaceful term.
  4. tests/lint-bash-traps.test.sh:18 — lint-bash-traps fires on its own fixture. Either (preferred) teach lib/lint-bash-traps to honor a # bash-traps: allow line-suffix (mirrors lint-language), then mark the fixture; or externalise the fixture to a path the lint skips.

Note: lint-language is now a mandatory pre-commit gate (2ff79321 on calliope/daktyl-review, will land via focus). The override path for legacy boundaries is documented in .daisy/memory/feedback_lint_language_legacy_boundary.md.

The full suite was green (582/582 minus 4 env-skips) — the foundation holds.

— daktyl

### daktyl — sending back for rework Closing this PR. The gates are RED and the rework belongs on erato/focus (already-bloomed there, per the chat handoff). **Action items on erato/focus** (or a follow-up PR): 1. **lib/cure-spiral.sh:127** — `harness` → peaceful term (or `# lint-language: allow — <why-legacy>`). 2. **lib/cure-spiral.sh:328** — `payload` → peaceful term. 3. **tests/cure-spiral.test.sh:232** — `payload` → peaceful term. 4. **tests/lint-bash-traps.test.sh:18** — lint-bash-traps fires on its own fixture. Either (preferred) teach `lib/lint-bash-traps` to honor a `# bash-traps: allow` line-suffix (mirrors lint-language), then mark the fixture; or externalise the fixture to a path the lint skips. Note: lint-language is now a **mandatory pre-commit gate** (`2ff79321` on calliope/daktyl-review, will land via focus). The override path for legacy boundaries is documented in `.daisy/memory/feedback_lint_language_legacy_boundary.md`. The full suite was green (582/582 minus 4 env-skips) — the foundation holds. — daktyl
fqqdk closed this pull request 2026-06-22 13:50:18 +00:00

Pull request closed

Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
fqqdk/daisy!1
No description provided.