Gate the concept roll on PRODUCT.md existing

Paul reproduced the Opus smoke failure in a fresh repo: given a
natural-language build intent, the model runs concept-seed directly and
skips the init divert entirely, so PRODUCT.md never exists and nothing
grounds the challenger fusion. Prose already says init-first in both
SKILL.md routing and new-work.md; prose alone does not hold the floor.
The deal path now refuses with a NO_PRODUCT_MD directive routing to
reference/init.md when loadContext finds no PRODUCT.md. The --chosen
telemetry ping stays ungated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Paul Bakaus
2026-07-21 22:08:02 -07:00
co-authored by Claude Fable 5
parent ea68bb4722
commit f6cecf6149
2 changed files with 51 additions and 0 deletions
+13
View File
@@ -511,6 +511,19 @@ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.ur
});
process.stdout.write(sent ? 'choice recorded\n' : 'choice ping skipped\n');
} else {
// Mechanical init gate: prose alone does not keep a model from dealing
// before init, and fresh repos produced exactly that skip (the model
// rolled directions with no PRODUCT.md, so nothing grounded the fusion).
// The --chosen branch above stays ungated; telemetry never blocks.
const { loadContext } = await import('./context.mjs');
if (!loadContext(process.cwd()).hasProduct) {
process.stdout.write([
'NO_PRODUCT_MD: the dice stay in the cup until product truth exists.',
'Complete the init ask round and write PRODUCT.md first (reference/init.md), then re-run this exact command.',
'Challengers fuse their form with facts from PRODUCT.md; without it every direction is ungrounded.',
].join(' ') + '\n');
process.exit(1);
}
process.stdout.write(await renderConceptSeed({
scope: scopeIdx !== -1 ? args[scopeIdx + 1] : 'surface',
key: fromIdx !== -1
+38
View File
@@ -1,6 +1,8 @@
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import { mkdtempSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import {
@@ -348,3 +350,39 @@ describe('concept seed scopes', () => {
assert.equal(output.indexOf('CREATIVE SPARK:') < output.indexOf('SYSTEM GRAMMAR:'), true);
});
});
describe('init gate', () => {
const gateRun = (cwd) => spawnSync(process.execPath, [SCRIPT, '--scope', 'direction', '--from', 'gate-test'], {
cwd,
encoding: 'utf-8',
env: { ...process.env, IMPECCABLE_CATALOG_DIR: FIXTURE_DIR, IMPECCABLE_CONTEXT_DIR: '' },
});
it('refuses to deal when no PRODUCT.md exists and routes to init', () => {
const dir = mkdtempSync(path.join(tmpdir(), 'concept-seed-noproduct-'));
const result = gateRun(dir);
assert.equal(result.status, 1);
assert.match(result.stdout, /NO_PRODUCT_MD/);
assert.match(result.stdout, /init/);
assert.doesNotMatch(result.stdout, /ASSIGNED INDEX/);
});
it('deals normally once PRODUCT.md exists', () => {
const dir = mkdtempSync(path.join(tmpdir(), 'concept-seed-product-'));
writeFileSync(path.join(dir, 'PRODUCT.md'), '# Test Product\n\n## Register\n\nbrand\n');
const result = gateRun(dir);
assert.equal(result.status, 0);
assert.doesNotMatch(result.stdout, /NO_PRODUCT_MD/);
});
it('never gates the choice ping', () => {
const dir = mkdtempSync(path.join(tmpdir(), 'concept-seed-ping-'));
const result = spawnSync(process.execPath, [SCRIPT, '--chosen', 'assigned', '--from', 'gate-test'], {
cwd: dir,
encoding: 'utf-8',
env: { ...process.env, IMPECCABLE_CATALOG_DIR: FIXTURE_DIR, IMPECCABLE_NO_TELEMETRY: '1' },
});
assert.equal(result.status, 0);
assert.doesNotMatch(result.stdout, /NO_PRODUCT_MD/);
});
});