feat(rul,cml): durability findings — config lost after compaction (v5.5.0 / A)

Per the official "what survives compaction" model (context-window.md):
only the project-root CLAUDE.md (+ unscoped rules) is re-injected after a
context compaction. Two additive, structural findings (low severity):

- RUL: a large (>50-line) PATH-SCOPED rule — reloads only on a matching
  file read, and is not re-injected after compaction, so must-hold rules
  can silently drop mid-session.
- CML: a NESTED (subdirectory) CLAUDE.md — not re-injected after
  compaction (only the project root is).

Additive (no new scanner, count stays 13); one humanizer entry each;
hermetic temp-fixture tests (positive + negative). Suite 957 -> 961,
SC-5 byte-stable, self-audit A/A.

Known limitation (pre-existing, broader than A): the lightweight
frontmatter parser reads inline `paths:` but not YAML block sequences,
so block-sequence-scoped rules are still seen as unscoped. Deferred.

Part of v5.5.0 "steering-model I". Foundation (active-config-reader
enumeration) deferred to v5.6 with B.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ter3E2JSi1Khgmuf2kady8
This commit is contained in:
Kjell Tore Guttormsen 2026-06-20 11:20:04 +02:00
commit f3aadb5183
6 changed files with 149 additions and 5 deletions

View file

@ -10,6 +10,7 @@ import { SEVERITY } from './lib/severity.mjs';
import { parseFrontmatter, extractSections, findImports } from './lib/yaml-parser.mjs';
import { lineCount, truncate } from './lib/string-utils.mjs';
import { LARGE_CONTEXT_WINDOW, LARGE_CONTEXT_SCALE, withCommas } from './lib/context-window.mjs';
import { dirname } from 'node:path';
const SCANNER = 'CML';
const MAX_RECOMMENDED_LINES = 200;
@ -68,6 +69,23 @@ export async function scan(targetPath, discovery) {
const sections = extractSections(body);
const imports = findImports(content);
// A nested (subdirectory) CLAUDE.md is NOT re-injected after a context
// compaction — only the project-root CLAUDE.md is (context-window.md). Its
// instructions silently drop until a file in that directory is read again.
const relDir = dirname(file.relPath);
if (file.scope === 'project' && relDir !== '.' && relDir !== '.claude' && lines > 5) {
findings.push(finding({
scanner: SCANNER,
severity: SEVERITY.low,
title: 'Nested CLAUDE.md is not re-injected after compaction',
description: `${file.relPath} is a nested (subdirectory) CLAUDE.md. It loads when Claude reads a file in that directory, but after a context compaction it is not re-injected (only the project-root CLAUDE.md is) — its instructions silently drop until a file in that directory is read again.`,
file: file.absPath,
evidence: `${lines} lines, nested (scope=project, dir="${relDir}")`,
recommendation: 'If these instructions must always apply, move the must-hold parts to the project-root CLAUDE.md (re-injected after compaction). Keep nested CLAUDE.md for guidance only needed when working in that directory.',
autoFixable: false,
}));
}
// --- Length checks ---
// Raw line count is no longer an absolute adherence threshold: CC 2.1.169
// scales the "too long" warning by context window, and cache-prefix