From 2d2f3fc5912b7eea710e567f043cfa50f63d3285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Mon, 8 Jun 2026 12:46:46 +0300 Subject: [PATCH] =?UTF-8?q?feat(m7-phase5):=20classifyNormative=20?= =?UTF-8?q?=E2=80=94=20=D0=B4=D0=B5=D1=82=D0=B5=D1=80=D0=BC=D0=B8=D0=BD?= =?UTF-8?q?=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=BD=D1=8B=D0=B9=20=D0=9A?= =?UTF-8?q?=D0=90=D0=A0=D0=A2=D0=90/=D0=97=D0=90=D0=9A=D0=9E=D0=9D=20+=20b?= =?UTF-8?q?uild-loop=20SE-D=20(=C2=A76)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Новые чистые экспорты в enforce-normative-content-rules: isDisciplineSourcePath (исходники машин М1–М6 по префиксам enforce-/judge-/floor-/escape-grant/action-journal/receipt-/ shell-content-rules/plan-lock/classify-destructive/path-normalization), contentTouchesLaw (контент правит правила/дисциплину), classifyNormative → {kind:'CARD'|'LAW'}. КАРТА = CLAUDE.md/MEMORY.md/memory; ЗАКОН = Pravila/PSR/Tooling + дисциплинарный исходник ВНЕ плана + контент-правка правил; build-loop: дисциплинарный исходник ПОД sealed-планом → КАРТА; сомнение → ЗАКОН. Тотален (null guard). Интеграция в decide — Task 2. 44/44 GREEN. --- tools/enforce-normative-content-rules.mjs | 43 +++++++++++++++++++ .../enforce-normative-content-rules.test.mjs | 37 ++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/tools/enforce-normative-content-rules.mjs b/tools/enforce-normative-content-rules.mjs index 6316c9bd..4065982a 100644 --- a/tools/enforce-normative-content-rules.mjs +++ b/tools/enforce-normative-content-rules.mjs @@ -40,6 +40,49 @@ export function isProtectiveNormativePath(filePath) { return PROTECTIVE_NORMATIVE_PATTERNS.some((re) => re.test(n)); } +// §6 build-loop (SE-D): исходники машин М1–М6 — правка этих файлов меняет САМИ правила/дисциплину. +// Вне запечатанного плана → ЗАКОН (ad-hoc самомодификация требует escape); под планом → КАРТА +// (управляется стеной М2 + content-floor + TDD — цикл сборки не клинит). +const DISCIPLINE_SOURCE_RE = /(^|\/)tools\/(enforce-|judge-|floor-|escape-grant|action-journal|receipt-|shell-content-rules|plan-lock|classify-destructive|path-normalization)[^/]*\.mjs$/; +export function isDisciplineSourcePath(filePath) { + if (typeof filePath !== 'string') return false; + return DISCIPLINE_SOURCE_RE.test(filePath.replace(/\\/g, '/')); +} + +// §6 контент-детектор «правит секцию правил/дисциплины» (сомнение → ЗАКОН): маркеры правки +// самих машин/правил в теле (имя enforce-/judge-/floor- + правило/блок/снять/disable/gate). +const LAW_CONTENT_RE = /(enforce-|judge-|floor-)[a-z-]+[^.\n]{0,60}(?:правил|правит|блок|снять|disable|переопредел|gate)/iu; +export function contentTouchesLaw(content) { + return typeof content === 'string' && LAW_CONTENT_RE.test(content); +} + +/** + * §6 классификатор КАРТА/ЗАКОН. КАРТА (operational) — CLAUDE.md/MEMORY.md/memory: контроллер + * ведёт свободно (claude-md-management). ЗАКОН (normative) — Pravila/PSR/Tooling + дисциплинарный + * исходник ВНЕ плана + контент, правящий правила: требует escape владельца (М6). build-loop: + * дисциплинарный исходник ПОД запечатанным планом (sealedPlanCoversEdit) → КАРТА. Сомнение → ЗАКОН. + * @returns {{kind:'CARD'|'LAW', reason:string}} + */ +export function classifyNormative(filePath, { content = '', sealedPlanCoversEdit = false } = {}) { + const n = String(filePath || '').replace(/\\/g, '/'); + // ЗАКОН-пути: Pravila/PSR/Tooling (нормативные документы-правила). + if (/(^|\/)docs\/(Pravila_|Plugin_stack_rules_|Tooling_)[^/]*\.md$/.test(n)) { + return { kind: 'LAW', reason: 'normative rules document (Pravila/PSR/Tooling)' }; + } + // Дисциплинарный исходник: build-loop различитель (SE-D). + if (isDisciplineSourcePath(n)) { + return sealedPlanCoversEdit + ? { kind: 'CARD', reason: 'discipline source under sealed plan (build-loop)' } + : { kind: 'LAW', reason: 'discipline source edited ad-hoc (outside sealed plan)' }; + } + // Контент правит правила/дисциплину машин → ЗАКОН (сомнение → ЗАКОН). + if (contentTouchesLaw(content)) { + return { kind: 'LAW', reason: 'content edits a rules/discipline section' }; + } + // Иначе — операционная КАРТА (CLAUDE.md/MEMORY.md/memory/заметки). + return { kind: 'CARD', reason: 'operational map (CLAUDE.md/MEMORY.md/memory)' }; +} + /** Extract the new content a mutating tool would write. */ export function extractWrittenContent(toolName, toolInput) { const i = toolInput || {}; diff --git a/tools/enforce-normative-content-rules.test.mjs b/tools/enforce-normative-content-rules.test.mjs index c568160f..9f3dffa7 100644 --- a/tools/enforce-normative-content-rules.test.mjs +++ b/tools/enforce-normative-content-rules.test.mjs @@ -209,3 +209,40 @@ describe('decide 7.2 (H3): деградация судьи fail-CLOSE тольк expect(r.reason).toMatch(/recovery/i); }); }); + +import { classifyNormative, isDisciplineSourcePath } from './enforce-normative-content-rules.mjs'; + +describe('isDisciplineSourcePath — исходники машин М1–М6', () => { + for (const p of ['tools/enforce-floor.mjs', 'tools/judge-orchestrator.mjs', 'tools/floor-signer.mjs', 'tools/escape-grant.mjs', 'tools/action-journal.mjs', 'tools/shell-content-rules.mjs', 'tools/plan-lock.mjs']) { + it(`${p} → true`, () => expect(isDisciplineSourcePath(p)).toBe(true)); + } + for (const p of ['tools/cost-aggregator.mjs', 'tools/observer-routing-detector.mjs', 'app/Models/User.php', 'CLAUDE.md']) { + it(`${p} → false`, () => expect(isDisciplineSourcePath(p)).toBe(false)); + } +}); + +describe('classifyNormative (§6 КАРТА/ЗАКОН + build-loop SE-D)', () => { + it('CLAUDE.md / MEMORY.md / memory → CARD (operational, низкий порог)', () => { + expect(classifyNormative('CLAUDE.md').kind).toBe('CARD'); + expect(classifyNormative('MEMORY.md').kind).toBe('CARD'); + expect(classifyNormative('memory/feedback_x.md').kind).toBe('CARD'); + }); + it('Pravila / PSR / Tooling → LAW (правила, что связывают контроллера)', () => { + expect(classifyNormative('docs/Pravila_raboty_Claude_v1_1.md').kind).toBe('LAW'); + expect(classifyNormative('docs/Plugin_stack_rules_v1.md').kind).toBe('LAW'); + expect(classifyNormative('docs/Tooling_v8_3.md').kind).toBe('LAW'); + }); + it('дисциплинарный исходник ВНЕ плана → LAW (ad-hoc самомодификация)', () => { + expect(classifyNormative('tools/enforce-floor.mjs', { sealedPlanCoversEdit: false }).kind).toBe('LAW'); + }); + it('дисциплинарный исходник ПОД запечатанным планом → CARD (build-loop, SE-D)', () => { + expect(classifyNormative('tools/enforce-floor.mjs', { sealedPlanCoversEdit: true }).kind).toBe('CARD'); + }); + it('контент правит секцию правил/дисциплины в любом файле → LAW (сомнение → ЗАКОН)', () => { + const content = 'обнови правило enforce-floor: блок снять при флаге'; + expect(classifyNormative('memory/x.md', { content }).kind).toBe('LAW'); + }); + it('обычный КАРТА-контент без правило-маркеров → CARD', () => { + expect(classifyNormative('memory/x.md', { content: 'урок: rebase ломается на observer-файлах' }).kind).toBe('CARD'); + }); +});