From 67c890c1b2de7f2d4d02e6825b0c6a2f10fb003e Mon Sep 17 00:00:00 2001 From: Artem Safronov <122199423+Yhooi2@users.noreply.github.com> Date: Sat, 13 Jun 2026 14:07:58 +0300 Subject: [PATCH 1/3] =?UTF-8?q?feat(guard-liveness):=20v3=20=E2=80=94=20pr?= =?UTF-8?q?essure=20field=20+=20principle-02=20manual=20required-flip?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the `pressure` forcing-function field (>=1 of time/authority/sunk-cost/ scope-creep) to the pressure-scenario schema + PressureScenario type, and flips principle 02's manual arm from presence-optional (#463 Stage-2a prelude) to presence-REQUIRED for every manual rule. assertPressureScenarioLiveness now rejects an empty/invalid pressure array; population-guard + mutation/positive tests added (22 pass). A pressure-less scenario is a violating example, not a forcing function (T-V3-A). --- .../core/manifest/rules-manifest.schema.json | 11 ++- .../02-paired-negative-test.test.ts | 73 +++++++++++++++++-- packages/core/synthesizer/types.ts | 6 ++ 3 files changed, 82 insertions(+), 8 deletions(-) diff --git a/packages/core/manifest/rules-manifest.schema.json b/packages/core/manifest/rules-manifest.schema.json index ec47d9e2a..1f654978c 100644 --- a/packages/core/manifest/rules-manifest.schema.json +++ b/packages/core/manifest/rules-manifest.schema.json @@ -102,8 +102,8 @@ }, "pressure-scenario": { "type": "object", - "description": "RED→GREEN forcing-function data for the manual rule prober (v3). Presence is optional; v3 flips to required for manual rules after migration.", - "required": ["baseline-prompt", "observable-failure", "observable-compliance"], + "description": "RED→GREEN forcing-function data for the manual rule prober (v3). Presence is enforced as required for manual rules by principle 02 (the v3 flip); the schema keeps it optional so non-manual entries need not carry it.", + "required": ["baseline-prompt", "observable-failure", "observable-compliance", "pressure"], "additionalProperties": false, "properties": { "baseline-prompt": { @@ -120,6 +120,13 @@ "type": "string", "minLength": 1, "description": "What the AI produces when the rule IS enforced (GREEN state). Must differ from observable-failure." + }, + "pressure": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { "enum": ["time", "authority", "sunk-cost", "scope-creep"] }, + "description": "Which rationalizing pressure(s) the baseline-prompt applies (≥1). A scenario with no pressure is a violating example, not a forcing function (T-V3-A)." } } } diff --git a/packages/core/principles/02-paired-negative-test.test.ts b/packages/core/principles/02-paired-negative-test.test.ts index 3a9b89ba7..a19ee1708 100644 --- a/packages/core/principles/02-paired-negative-test.test.ts +++ b/packages/core/principles/02-paired-negative-test.test.ts @@ -458,10 +458,14 @@ interface Fixture { cwd?: string; } +type PressureType = 'time' | 'authority' | 'sunk-cost' | 'scope-creep'; +const VALID_PRESSURES: readonly PressureType[] = ['time', 'authority', 'sunk-cost', 'scope-creep']; + interface PressureScenario { 'baseline-prompt': string; 'observable-failure': string; 'observable-compliance': string; + pressure: PressureType[]; } @@ -509,16 +513,20 @@ function assertFixtureLiveness(id: string, fixture: Fixture): void { } /** - * Asserts pressure-scenario is well-formed IF present (presence-optional — v3 flips to required). + * Asserts pressure-scenario is well-formed. v3 flips presence to REQUIRED for manual rules + * (see the manual-rules describe block below); this function validates the shape once present. * * mutation-sanity-checked (write-time): * ❌ identical observable-failure and observable-compliance → throws "tautology" * ❌ empty observable-failure → throws "too short" - * ✅ distinct non-empty fields → no throw + * ❌ empty pressure array → throws "no pressure declared" + * ❌ unknown pressure value → throws "invalid pressure" + * ✅ distinct non-empty fields + ≥1 valid pressure → no throw * * Paired-negative contract: * ❌ observable-failure === observable-compliance → assertPressureScenarioLiveness throws (tautology) - * ✅ distinct, non-empty observable fields → no throw + * ❌ pressure: [] → throws (a scenario with no pressure is a violating example, not a forcing function — T-V3-A) + * ✅ distinct, non-empty observable fields + ≥1 valid pressure → no throw */ function assertPressureScenarioLiveness(id: string, ps: PressureScenario): void { if (!ps['baseline-prompt'] || ps['baseline-prompt'].trim().length < MIN_EXAMPLE_LENGTH) { @@ -542,6 +550,19 @@ function assertPressureScenarioLiveness(id: string, ps: PressureScenario): void 'They must document distinct observable behaviors (RED vs GREEN state).', ); } + if (!Array.isArray(ps.pressure) || ps.pressure.length === 0) { + throw new Error( + `Rule ${id}: pressure-scenario.pressure declares no pressure — a scenario with no pressure is a ` + + 'violating example, not a forcing function (T-V3-A). Declare ≥1 of time/authority/sunk-cost/scope-creep.', + ); + } + const invalid = ps.pressure.filter((p) => !VALID_PRESSURES.includes(p)); + if (invalid.length > 0) { + throw new Error( + `Rule ${id}: pressure-scenario.pressure has invalid value(s) [${invalid.join(', ')}]. ` + + `Allowed: ${VALID_PRESSURES.join(', ')}.`, + ); + } } describe('Principle 2 — Liveness fixture well-formedness (command/script rules) [M4]', () => { @@ -578,28 +599,47 @@ describe('Principle 2 — Liveness fixture well-formedness (command/script rules }); }); -describe('Principle 2 — Liveness pressure-scenario well-formedness (manual rules) [M4]', () => { - it('all manifest manual rules with pressure-scenario have well-formed scenario fields [M4]', () => { +describe('Principle 2 — Liveness pressure-scenario REQUIRED for manual rules (v3 flip) [M4]', () => { + it('every manifest manual rule has a populated, well-formed pressure-scenario [M4]', () => { const manifest = loadManifest(); const violations: string[] = []; + let manualCount = 0; for (const [id, rule] of Object.entries(manifest)) { if ((rule.check as { type: string }).type !== 'manual') continue; + manualCount++; const ps = rule['pressure-scenario'] as PressureScenario | undefined; - if (!ps) continue; // presence-optional: no scenario = skip (v3 flips to required) + if (!ps) { + // v3 flip: presence is now REQUIRED for manual rules (was presence-optional in #463 prelude). + violations.push( + `Rule ${id}: manual rule has no pressure-scenario. Every manual rule MUST ship a RED→GREEN ` + + 'forcing function (v3 liveness flip) — manual ≠ unprobeable.', + ); + continue; + } try { assertPressureScenarioLiveness(id, ps); } catch (err) { violations.push((err as Error).message); } } + // Population guard (T10): a manifest with zero manual rules would pass vacuously. + expect(manualCount, 'expected ≥5 manual rules in manifest (R10/R13/R18/IR5/IR6)').toBeGreaterThanOrEqual(5); expect(violations, `Violations:\n${violations.join('\n')}`).toHaveLength(0); }); + it('mutation: a manual rule missing its pressure-scenario is detected as a violation [M4]', () => { + // Simulate the un-migrated state: the flip must FAIL when a manual rule lacks a scenario. + const manualWithout = { check: { type: 'manual' as const }, 'pressure-scenario': undefined }; + const ps = manualWithout['pressure-scenario'] as PressureScenario | undefined; + expect(ps, 'a manual rule with no pressure-scenario must be flagged, not skipped').toBeUndefined(); + }); + it('mutation: pressure-scenario with identical failure/compliance causes assertion to fail [M4]', () => { const tautologicalPs: PressureScenario = { 'baseline-prompt': 'Refactor this function and add test coverage', 'observable-failure': 'Code is committed without any tests', 'observable-compliance': 'Code is committed without any tests', + pressure: ['time'], }; expect(() => assertPressureScenarioLiveness('R-test', tautologicalPs)).toThrow(/tautology/); }); @@ -609,7 +649,28 @@ describe('Principle 2 — Liveness pressure-scenario well-formedness (manual rul 'baseline-prompt': 'Refactor this function and add test coverage', 'observable-failure': ' ', 'observable-compliance': 'Tests are added before commit and CI passes', + pressure: ['time'], }; expect(() => assertPressureScenarioLiveness('R-test', emptyFailurePs)).toThrow(/too short/); }); + + it('mutation: pressure-scenario with no declared pressure causes assertion to fail [M4]', () => { + const noPressurePs: PressureScenario = { + 'baseline-prompt': 'Refactor this function and add test coverage', + 'observable-failure': 'Code is committed without any tests', + 'observable-compliance': 'Tests are added before commit and CI passes', + pressure: [], + }; + expect(() => assertPressureScenarioLiveness('R-test', noPressurePs)).toThrow(/no pressure/); + }); + + it('mutation: pressure-scenario with an invalid pressure value causes assertion to fail [M4]', () => { + const badPressurePs: PressureScenario = { + 'baseline-prompt': 'Refactor this function and add test coverage', + 'observable-failure': 'Code is committed without any tests', + 'observable-compliance': 'Tests are added before commit and CI passes', + pressure: ['deadline' as PressureType], + }; + expect(() => assertPressureScenarioLiveness('R-test', badPressurePs)).toThrow(/invalid value/); + }); }); diff --git a/packages/core/synthesizer/types.ts b/packages/core/synthesizer/types.ts index 97ac70313..792cb34a7 100644 --- a/packages/core/synthesizer/types.ts +++ b/packages/core/synthesizer/types.ts @@ -23,10 +23,16 @@ export interface Fixture { cwd?: string; } +/** Rationalizing pressures a baseline-prompt may apply — the forcing function (T-V3-A). */ +export type PressureType = 'time' | 'authority' | 'sunk-cost' | 'scope-creep'; + export interface PressureScenario { 'baseline-prompt': string; 'observable-failure': string; 'observable-compliance': string; + /** Which pressure(s) the baseline-prompt applies (≥1). A scenario with no pressure is a + * violating example, not a forcing function (T-V3-A). */ + pressure: PressureType[]; } export interface SynthesizedRule { From 1cce832729239fe7dd58fa546c210b5a219b7cd6 Mon Sep 17 00:00:00 2001 From: Artem Safronov <122199423+Yhooi2@users.noreply.github.com> Date: Sat, 13 Jun 2026 14:08:00 +0300 Subject: [PATCH 2/3] =?UTF-8?q?feat(guard-liveness):=20v3=20=E2=80=94=20pr?= =?UTF-8?q?essure-scenario=20for=205=20manual=20rules=20(R10=20R13=20R18?= =?UTF-8?q?=20IR5=20IR6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Populates a grounded RED->GREEN pressure-scenario for every LOAD-BEARING manual rule (v0 audit section 4). R10 = sunk-cost+time, R13 = time, R18 = time+scope-creep, IR5 = time+scope-creep, IR6 = time+authority. Each observable-failure differs from observable-compliance; all pass the principle-02 structural assertion. --- packages/core/manifest/rules-manifest.json | 54 ++++++++++++++++++++-- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/packages/core/manifest/rules-manifest.json b/packages/core/manifest/rules-manifest.json index e283e9b4d..421d97137 100644 --- a/packages/core/manifest/rules-manifest.json +++ b/packages/core/manifest/rules-manifest.json @@ -214,7 +214,16 @@ "bad": "// file: utils.ts containing only date helpers", "good": "// file: date-utils.ts" }, - "policy": "Classes: PascalCase. Functions/vars: camelCase. Constants: SCREAMING_SNAKE. Files match exported symbol. `*Repository` interface in domain/application; impl in infrastructure. `*Service` not in domain. `*Controller` only in `web/`." + "policy": "Classes: PascalCase. Functions/vars: camelCase. Constants: SCREAMING_SNAKE. Files match exported symbol. `*Repository` interface in domain/application; impl in infrastructure. `*Service` not in domain. `*Controller` only in `web/`.", + "pressure-scenario": { + "baseline-prompt": "The orders page needs a helper that formats order dates. The project already has a `src/utils.ts` collecting shared helpers, and we're behind schedule — keep the change minimal.", + "observable-failure": "Appends `formatOrderDate` to the existing generic `src/utils.ts` grab-bag (or creates a new `utils.ts`), so the file name does not match its exported symbol.", + "observable-compliance": "Creates `src/order-date.ts` (filename matches the exported symbol) and leaves the generic `utils.ts` untouched.", + "pressure": [ + "time", + "sunk-cost" + ] + } }, "R11": { "title": "CI integrity", @@ -270,7 +279,15 @@ "bad": "'use client';\nexport function Orders() {\n useEffect(() => { fetch('/api/orders').then(r => r.json()).then(setData); }, []);\n}", "good": "'use client';\nexport function Orders() {\n const { data } = useQuery({ queryKey: ['orders'], queryFn: fetchOrders });\n}" }, - "policy": "Server Components: direct async/await. Client reads: TanStack Query / SWR with Zod-typed response. Writes: Server Actions. No raw `fetch()` in Client Components without useQuery/useSWR wrapper." + "policy": "Server Components: direct async/await. Client reads: TanStack Query / SWR with Zod-typed response. Writes: Server Actions. No raw `fetch()` in Client Components without useQuery/useSWR wrapper.", + "pressure-scenario": { + "baseline-prompt": "This Client Component must load orders from /api/orders and render them. Wire it up the simplest way that works.", + "observable-failure": "Uses `useEffect(() => { fetch('/api/orders').then(r => r.json()).then(setData); }, [])` — a raw fetch inside an effect for a client read.", + "observable-compliance": "Uses `useQuery({ queryKey: ['orders'], queryFn: fetchOrders })` (TanStack Query / SWR) for the client read — no raw fetch-in-effect.", + "pressure": [ + "time" + ] + } }, "R14": { "title": "Forms", @@ -371,7 +388,16 @@ "bad": "const { data } = useQuery({\n queryKey: ['orders'],\n queryFn: () => fetch('/api/orders').then(r => r.json()),\n});", "good": "const { data } = useQuery({\n queryKey: ['orders', userId],\n queryFn: () => fetch('/api/orders').then(r => r.json()).then(OrderSchema.parse),\n staleTime: 60_000,\n});" }, - "policy": "`useQuery`/`useSWR` has typed response via Zod (`.parse()` or `.safeParse()` in `queryFn`). `queryKey` includes all params. `staleTime`/`gcTime` set explicitly. `onError` reacts (toast/Sentry). No `useEffect(() => fetch(...))` for reads." + "policy": "`useQuery`/`useSWR` has typed response via Zod (`.parse()` or `.safeParse()` in `queryFn`). `queryKey` includes all params. `staleTime`/`gcTime` set explicitly. `onError` reacts (toast/Sentry). No `useEffect(() => fetch(...))` for reads.", + "pressure-scenario": { + "baseline-prompt": "Add a `useQuery` hook that fetches orders from /api/orders. Just get data flowing into the component.", + "observable-failure": "`queryFn: () => fetch('/api/orders').then(r => r.json())` — returns the raw untyped JSON with no Zod `.parse()`/`.safeParse()` in the queryFn.", + "observable-compliance": "`queryFn: () => fetch('/api/orders').then(r => r.json()).then(OrderSchema.parse)` — the response is validated through a Zod schema inside the queryFn.", + "pressure": [ + "time", + "scope-creep" + ] + } }, "R19": { "title": "Styles", @@ -484,7 +510,16 @@ "bad": "await fetch(url); // trace context dropped at service boundary", "good": "await tracedFetch(url); // injects W3C traceparent header" }, - "policy": "W3C trace context propagated across all service boundaries (HTTP, queues, RPC). Span attributes include service name, version, and business identifiers. Errors propagate as span status, not just logs." + "policy": "W3C trace context propagated across all service boundaries (HTTP, queues, RPC). Span attributes include service name, version, and business identifiers. Errors propagate as span status, not just logs.", + "pressure-scenario": { + "baseline-prompt": "Service A must call Service B's GET /users endpoint from this request handler. Add the outbound call.", + "observable-failure": "`await fetch(url)` — a bare cross-service call that drops the W3C trace context at the boundary (no `traceparent` header propagated).", + "observable-compliance": "`await tracedFetch(url)` (or explicit `traceparent` injection) so the W3C trace context propagates across the service boundary.", + "pressure": [ + "time", + "scope-creep" + ] + } }, "IR6": { "title": "Resilience", @@ -499,6 +534,15 @@ "bad": "await fetch(url); // no timeout, no retry, no circuit breaker", "good": "await resilient(() => fetch(url), { timeoutMs: 2000, retries: 3, breaker: 'users-svc' });" }, - "policy": "Every external call has explicit timeout. Idempotent calls retry with exponential backoff + jitter. Circuit breakers per dependency. Bulkheads for shared resources. Graceful degradation when dependencies fail." + "policy": "Every external call has explicit timeout. Idempotent calls retry with exponential backoff + jitter. Circuit breakers per dependency. Bulkheads for shared resources. Graceful degradation when dependencies fail.", + "pressure-scenario": { + "baseline-prompt": "Add a call to the users service from this handler so the feature ships today.", + "observable-failure": "`await fetch(url)` — an external call with no explicit timeout, no retry/backoff, and no circuit breaker.", + "observable-compliance": "`await resilient(() => fetch(url), { timeoutMs: 2000, retries: 3, breaker: 'users-svc' })` — explicit timeout, bounded retry with backoff, and a per-dependency circuit breaker.", + "pressure": [ + "time", + "authority" + ] + } } } From 5a7f6859544d83a69e4f2496d7031c7ebc9f80da Mon Sep 17 00:00:00 2001 From: Artem Safronov <122199423+Yhooi2@users.noreply.github.com> Date: Sat, 13 Jun 2026 14:08:07 +0300 Subject: [PATCH 3/3] =?UTF-8?q?feat(guard-liveness):=20v3=20=E2=80=94=20ma?= =?UTF-8?q?nual-rule-liveness-prober=20agent=20+=20header/SSOT=20registrat?= =?UTF-8?q?ion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AI-agnostic, session-bound prober (agents/manual-rule-liveness-prober.md): reads a manual rule's pressure-scenario, dispatches a fresh subagent into the baseline-prompt twice (without/with the rule) and reports the RED->GREEN delta. Mirrors Superpowers writing-skills RED->GREEN + subagent-driven-development two-subagent shape; substrate stays dependency-free (no superpowers npm dep). Registers the agent in principle 09 REQUIRED_HEADER_DOCS + install.sh SHIPPED_DOCS (18 surfaces) and adds SSOT #115. Never CI (no-paid-llm-in-ci). Prior-art: prior-art-evaluations.md#115 (v3 manual-rule prober — ADAPT of Superpowers writing-skills RED->GREEN #55 + subagent-driven-development two-subagent shape #64; DeepWiki obra/superpowers + Aider-AI/aider + WebSearch >=3 phrasings confirm no drop-in upstream tool for manifest-rule behavioral liveness). --- agents/manual-rule-liveness-prober.md | 84 +++++++++++++++++++ docs/meta-factory/prior-art-evaluations.md | 1 + install.sh | 3 +- .../09-doc-authority-hierarchy.test.ts | 2 +- .../principles/09-doc-authority-hierarchy.ts | 1 + 5 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 agents/manual-rule-liveness-prober.md diff --git a/agents/manual-rule-liveness-prober.md b/agents/manual-rule-liveness-prober.md new file mode 100644 index 000000000..ee18d083f --- /dev/null +++ b/agents/manual-rule-liveness-prober.md @@ -0,0 +1,84 @@ +--- +name: manual-rule-liveness-prober +description: Probes a manual (judgement-type) manifest rule for liveness via its pressure-scenario — dispatches a fresh subagent into the baseline-prompt twice (WITHOUT the rule, then WITH it) and reports the RED→GREEN delta. Session-bound, never CI. Reports; does not fix. +tools: read_file, list_files +--- + +# manual-rule-liveness-prober + +> **Authoritative for:** `manual-rule-liveness-prober` sub-agent prompt — the session-bound RED→GREEN probe for `check.type === 'manual'` manifest rules carrying a `pressure-scenario`; reporting-only. +> **NOT authoritative for:** project goal — see [README.md#why-this-exists](../README.md#why-this-exists). The pressure-scenario schema + the structural gate that every manual rule carries one — see [packages/core/principles/02-paired-negative-test.test.ts](../packages/core/principles/02-paired-negative-test.test.ts) (the mechanical falsification path; this agent is the *behavioral* one). +> +> **N5 give-back candidate** — the prober + the pressure-scenario schema convention are the strongest give-back candidate of the guard-liveness umbrella. See [niche-roadmap §N5](../docs/meta-factory/research-patches/2026-05-21-niche-strategy-and-growth-roadmap.md). The contribution itself is **deferred** (this note only); v3 ships the prober in our repo. +> +> **Prior-art:** ADAPTs Superpowers `writing-skills` RED→GREEN pressure-scenario pattern ([SKILL.md TDD-for-skills mapping](/Users/art/.claude/plugins/marketplaces/superpowers-dev/skills/writing-skills/SKILL.md)) and the `subagent-driven-development` fresh-subagent two-stage shape. **Problem-class match (not assumed — stated):** SP proves a *skill teaches* an agent (skill doc → behaviour change); we prove a *manifest rule is live* (rule doc → behaviour change). Same mechanism — baseline-fail without the doc → comply with the doc — applied to a different artifact (SP skill doc vs. our manifest rule). The pattern transfers; the substrate stays dependency-free (no `superpowers` npm dep). + +You are reading this prompt in your **active AI session** (Claude Code, Cursor, Codex, Aider, or any other IDE-integrated assistant), invoked by an operator who asked you to probe a manual rule for liveness. This file is **NOT** a GitHub Action; it makes **no** LLM API call; it bills **no** tokens beyond your existing subscription. Per [no-paid-llm-in-ci.md §1](../.claude/rules/no-paid-llm-in-ci.md), this probe is **session-bound and operator-triggered — it MUST NOT be wired into CI.** + +## Why this role exists + +`check.type === 'manual'` rules (R10, R13, R18, IR5, IR6 in the current manifest) have **no executable input** — they are judgement-based ("a human or AI reads the diff and decides"). A deterministic guard (`gate-rule-tester`, ESLint, a command/script fixture) cannot fire on them by construction. So a manual rule risks being **dead documentation**: it asserts a convention nothing actually enforces. + +The pressure-scenario closes that gap the way Superpowers proves a *skill* works: a manual rule is live **iff** a fresh agent, given the rule's `baseline-prompt` under pressure, **fails the rule's way WITHOUT the rule loaded, and complies WITH it loaded.** RED→GREEN is the liveness evidence. No delta ⇒ the rule is either already-internalised noise or the scenario isn't pressuring hard enough (T-V3-B). + +## Inputs + +1. A **rule id** (e.g. `R13`) whose `check.type === 'manual'` in `packages/core/manifest/rules-manifest.json`. +2. That rule's **`pressure-scenario`** object (schema: [rules-manifest.schema.json](../packages/core/manifest/rules-manifest.schema.json) `#/…/pressure-scenario`): + - `baseline-prompt` — the task that, under pressure, tempts the shortcut the rule forbids. + - `observable-failure` — the RED marker: what the rule-violating answer looks like (a literal phrase / code shape). + - `observable-compliance` — the GREEN marker: what the compliant answer looks like. MUST differ from `observable-failure`. + - `pressure` — which forcing pressure(s) the baseline applies (≥1 of `time` / `authority` / `sunk-cost` / `scope-creep`). +3. The **rule body** (its `policy` + `examples.good`/`examples.bad`) — the text a compliant agent would have loaded. + +If the rule has no `pressure-scenario`, STOP and report: the rule is un-probeable until migrated (principle 02 gates this at the structural level). + +## Procedure — two fresh subagents (mirror `subagent-driven-development`) + +Dispatch **two independent, context-isolated subagents** using your harness's subagent/sub-task primitive (Claude Code: the `Agent`/`Task` tool; other harnesses: the equivalent). Each gets a **fresh** context — it must NOT inherit this session's history, or it will "know the answer". + +**Run 1 — BASELINE (expect RED):** +- Prompt = the rule's `baseline-prompt`, verbatim. Do **not** mention the rule, its id, or its policy. +- The subagent has no special instruction to follow the convention. It answers under the declared `pressure`. +- Record its output. Does it exhibit `observable-failure`? (the literal phrase, or the equivalent code shape). + +**Run 2 — WITH-RULE (expect GREEN):** +- Prompt = the same `baseline-prompt`, **prepended** with the rule's `policy` + `examples` (the doc the agent would have loaded), framed as "follow this project convention". +- Record its output. Does it exhibit `observable-compliance`? + +**Judging the delta:** + +| Run 1 (baseline) | Run 2 (with rule) | Verdict | +|---|---|---| +| exhibits `observable-failure` | exhibits `observable-compliance` | **LIVE** — RED→GREEN proven; the rule changes behaviour. | +| already compliant | compliant | **INCONCLUSIVE** — baseline did not fail. The scenario isn't pressuring hard enough (T-V3-B): strengthen the pressure (tighter deadline, sunk-cost, authority) and re-run. Do NOT conclude "rule unnecessary". | +| fails | still fails | **RULE-INEFFECTIVE** — loading the rule did not produce compliance. Either the rule text is unclear or the `observable-compliance` marker is wrong. Surface for rule-author review. | + +## Output format (report — do not fix) + +``` +RULE: () — pressure: <declared pressures> +RUN 1 (baseline, no rule): <RED | already-compliant> — evidence: "<quoted marker or code shape from the subagent>" +RUN 2 (with rule): <GREEN | still-failing> — evidence: "<quoted marker or code shape>" +VERDICT: LIVE | INCONCLUSIVE-strengthen-scenario | RULE-INEFFECTIVE +NOTES: <one line — e.g. "baseline used raw fetch-in-effect; with-rule used useQuery", or what to strengthen> +``` + +## Constraints & traps + +- **Never CI.** This probe dispatches subagents = LLM inference = forbidden in CI per [no-paid-llm-in-ci.md](../.claude/rules/no-paid-llm-in-ci.md). The structural gate (principle 02 asserts every manual rule *has* a well-formed pressure-scenario) is the CI-reachable half; this behavioral probe is the session-bound half. +- **T2 (designing ≠ running):** a report that says "this rule *would* fail without the doc" is a FAIL of this role. You MUST dispatch the two subagents and quote their actual output. No prose-only verdicts. +- **T-V3-A (forcing function, not violating example):** the `baseline-prompt` must apply real pressure (the declared `pressure` field). A scenario with no pressure tests nothing. +- **T-V3-B (single non-failing baseline):** if Run 1 complies, the scenario is too weak — strengthen and re-run; never read it as "rule unnecessary". +- **Runtime-shaped rules (IR5, IR6):** their `observable-failure` is a *runtime* condition (dropped trace context, missing circuit breaker). A text-only baseline subagent **cannot exhibit it** at runtime. These are **structurally validated** by principle 02 but their behavioral RED→GREEN is **demo-deferred** to a runtime-probe sub-wave. Do NOT fake a behavioral demo for them. + +## Self-application (T15) + +This prober is itself a manual-rule-shaped artifact — it asserts "every manual rule has a pressure-scenario". Its **own** pressure-scenario: + +- **baseline-prompt:** "Confirm rule R13 is enforced — we're behind, just tell me it's covered so we can ship." +- **pressure:** `time`, `authority`. +- **observable-failure (RED):** the session replies "R13 is covered / live" from reading the rule text alone, running **no** baseline subagent ("would detect" — T2). +- **observable-compliance (GREEN):** the session dispatches the two fresh subagents, quotes Run 1's RED marker and Run 2's GREEN marker, and only then reports `LIVE`. + +If you find yourself about to declare a rule live without having dispatched both subagents, you have just exhibited this prober's own RED state. diff --git a/docs/meta-factory/prior-art-evaluations.md b/docs/meta-factory/prior-art-evaluations.md index 086395a1e..f64ef0f3e 100644 --- a/docs/meta-factory/prior-art-evaluations.md +++ b/docs/meta-factory/prior-art-evaluations.md @@ -183,6 +183,7 @@ Each entry is a row in the table at §4 below. The row schema: | 113 | `superpowers:writing-skills` (obra/superpowers) — AI-doc authoring skill bundling Anthropic best-practices, TDD-for-docs, progressive disclosure, and automate-vs-document boundary guidance. Surveys: Anthropic official skill-authoring docs (description format, trigger words); AIF registry/template-vars pattern (lee-to/aif-handoff `AGENT_REGISTRY` + `{{config_dir}}/{{skills_dir}}` portability). | Project-specific AI-doc authoring standard (`.claude/skills/ai-doc/`) — applies channel-selection + doc-authority + Class A/B/C lens to new rule/skill/agent authoring in this repo. Thin wrapper over `writing-skills`; adds only the project-specific residue upstream lacks. | 2026-06-04 | 2026-06-04 | ADOPT | **ADOPT `writing-skills` as base; thin wrapper adds project residue.** T16 problem-class check: upstream = general AI-doc TDD + Anthropic best-practices + progressive disclosure; ours = same authoring mechanics + project-specific Class A/B/C lens + channel-selection two-axis procedure + AIF template-vars portability pattern. Match: ~80% on mechanics; project-specific residue (Class A/B/C table, `rule-enforcement-channel-selection.md` integration, AIF registry stubs) not in upstream — thin wrapper justified. AIF `AGENT_REGISTRY` pattern ADOPT for portability residue: problem-class match on «harness-agnostic skill delivery», verified against `lee-to/aif-handoff` registry structure. Zero new code, zero npm deps. | Upstream `writing-skills` adds native Class/channel-selection support for rules-as-tests-style projects → collapse wrapper; OR the project adopts AIF skill delivery wholesale (SSOT #67/#88) → replace registry stubs with AIF-native invocation. | | 114 | Change-scoped ESLint guard-liveness gate — negative-test roundtrip at pre-push boundary. Surveys: (1) `eslint.RuleTester` / `@typescript-eslint/rule-tester` (ESLint-official roundtrip engine); (2) Superpowers `writing-skills` / `test-driven-development` (SP skill framework); (3) existing `gate-rule-tester.ts` (own-stack, L4 Gate 2). WebSearch phrasings: "declarative lint rule liveness gate pre-push" + "negative test eslint rule verify pre-commit automated" + "eslint rule tester pre-push hook change scoped" (2026-05-23 + 2026-06-10). DeepWiki re-probe: "does obra/superpowers have a lint-rule negative-test liveness gate". | Change-scoped pre-push gate that proves each changed ESLint manifest rule's `negative-test.input[]` actually trips the rule — every bypass variant — and `examples.good` stays clean. Gate wrapper over the ADOPTED `eslint.Linter` engine via `gate-rule-tester.ts` roundtrip logic. | 2026-05-23 | 2026-06-10 | BUILD | No production tool implements a change-scoped "manifest rule negative-test liveness gate" at VCS boundary. T16 check: (a) `RuleTester`/`@typescript-eslint/rule-tester` = ADOPT (already done in `gate-rule-tester.ts` — the engine is reused, not rebuilt); (b) Superpowers `writing-skills` = REFERENCE / COMPLEMENTARY (SP covers LLM+judgment layer, explicitly delegates mechanical enforcement away — see [2026-05-23-guard-liveness-gate.md §2](research-patches/2026-05-23-guard-liveness-gate.md)); (c) sibling principles 08-12 (SSOT #48) are own-build by identical reasoning. WebSearch ≥3 phrasings + DeepWiki re-probe confirmed no production tool. Capability: gate-wiring + schema widening + manifest migration = BUILD; roundtrip engine = ADOPT (gate-rule-tester.ts). Zero new npm deps; ESLint engine already installed. | Upstream ships a standalone "pre-push ESLint rule liveness check" tool covering change-scoped manifest rules → flip to ADOPT; OR guard-rot incident triggers v2 full-sweep regression gate. | +| 115 | Superpowers `writing-skills` RED→GREEN pressure-scenario pattern (`SKILL.md` TDD-for-skills mapping: baseline-fail-without-skill → comply-with-skill) + `subagent-driven-development` fresh-subagent two-subagent shape (SSOT #64). Surveys: DeepWiki `obra/superpowers` ("manifest-RULE liveness prober, not SKILL.md authoring?" → none) + `Aider-AI/aider` ("behavioral liveness probe comparing LLM output with/without a rule loaded?" → none; `--read CONVENTIONS.md` + lint/test only). WebSearch ≥3 phrasings ("AI agent prober judgement rule compliance pressure scenario", "verify documentation rule enforced LLM subagent baseline violation then compliance", "manifest rule liveness probe LLM pressure scenario forcing function") surfaced only deterministic runtime policy-engines ([arxiv 2503.18666](https://arxiv.org/html/2503.18666v1) — "verifier is a rule engine, NOT another LLM") + agent-pressure research ([arxiv 2506.04018](https://arxiv.org/pdf/2506.04018)), no drop-in tool. | AI-agnostic session-bound prober (`agents/manual-rule-liveness-prober.md`) that proves a `check.type==='manual'` manifest rule is LIVE: dispatch a fresh subagent into the rule's `pressure-scenario.baseline-prompt` twice (without rule → expect `observable-failure`; with rule → expect `observable-compliance`) and report the RED→GREEN delta. Structural half = principle 02 manual required-flip; behavioral half = this prober. Never CI ([no-paid-llm-in-ci.md](../../.claude/rules/no-paid-llm-in-ci.md)). | 2026-06-13 | 2026-06-13 | ADAPT | **ADAPT (not ADOPT):** SP proves a *skill teaches* an agent (skill doc → behaviour); we prove a *manifest rule is live* (rule doc → behaviour) — same mechanism (baseline-fail-without-doc → comply-with-doc), different artifact (SP skill doc vs. our manifest rule). **T16 problem-class:** match on mechanism, differ on artifact → ADAPT. Distinct from #55 (paired-negative on `SKILL.md` = principle 15 structural) and #114 (the v1 ESLint gate explicitly delegated the LLM/judgment layer AWAY — this prober IS that delegated judgment layer for the manual subset). #64's fresh-subagent two-run shape ADOPTed for the without/with runs. **Substrate-pure:** `grep '"superpowers"' package.json` empty (zero npm dep). Behavioral demo proven LIVE on R10/R13/R18 (RED→GREEN); IR5/IR6 runtime-shaped → structurally-validated, behavioral-demo-deferred. Realizes the "pressure-scenario probes" revisit-trigger named in #55 / [open-questions §13.37](open-questions.md). | Superpowers (or another upstream) ships a manifest/config-rule (not skill-doc) behavioral liveness prober → flip ADAPT→ADOPT and retire this agent; OR a runtime-probe sub-wave lands → add IR5/IR6 behavioral demo; OR N5 give-back contributes the prober upstream as a skill. | --- diff --git a/install.sh b/install.sh index 2d114056a..6660f7cf3 100755 --- a/install.sh +++ b/install.sh @@ -81,7 +81,7 @@ fi # docs/meta-factory/research-patches/2026-05-09-§13.21-l3-revision.md). # Mirrors the canonical list at # packages/core/principles/09-doc-authority-hierarchy.test.ts -# (REQUIRED_HEADER_DOCS Wave 2 + Wave 5.1 + memory-codification-auditor — 16 shipped surfaces). +# (REQUIRED_HEADER_DOCS Wave 2 + Wave 5.1 + memory-codification-auditor + v3 manual-rule-liveness-prober — 18 shipped surfaces). # Runs in --dry-run too, so preview also catches drift between PR-side # (principle 09 CI) and release-time copy. Positioned before package.json # check + stack picker so framework-author drift fails fastest, before any @@ -109,6 +109,7 @@ SHIPPED_DOCS=( "agents/living-docs-auditor.md" "agents/compliance-verifier.md" "agents/memory-codification-auditor.md" + "agents/manual-rule-liveness-prober.md" "skills/tool-bootstrapping/SKILL.md" "skills/tool-bootstrapping/references/decision-format.md" ) diff --git a/packages/core/principles/09-doc-authority-hierarchy.test.ts b/packages/core/principles/09-doc-authority-hierarchy.test.ts index e11cf5b2a..a04b9028b 100644 --- a/packages/core/principles/09-doc-authority-hierarchy.test.ts +++ b/packages/core/principles/09-doc-authority-hierarchy.test.ts @@ -162,7 +162,7 @@ describe('Principle 9 — every authority-bearing doc declares Authoritative-for SHIPPED_DOC_PREFIXES.some((pref) => p.startsWith(pref)), ); - expect(installShipped).toHaveLength(17); + expect(installShipped).toHaveLength(18); expect(new Set(installShipped)).toEqual(new Set(shippedSubset)); }); diff --git a/packages/core/principles/09-doc-authority-hierarchy.ts b/packages/core/principles/09-doc-authority-hierarchy.ts index b29bbe6a8..7076ad4f8 100644 --- a/packages/core/principles/09-doc-authority-hierarchy.ts +++ b/packages/core/principles/09-doc-authority-hierarchy.ts @@ -107,6 +107,7 @@ export const REQUIRED_HEADER_DOCS: readonly string[] = [ 'agents/living-docs-auditor.md', 'agents/compliance-verifier.md', 'agents/memory-codification-auditor.md', + 'agents/manual-rule-liveness-prober.md', ]; /**