mirror of
https://github.com/affaan-m/ECC.git
synced 2026-10-14 20:25:26 +02:00
feat(gateguard): ask what outside the repository fixes a format or semantics
Across 240 evaluation trials every question the gate raised was about code structure - importers, callers, public API, data schemas - while every deciding fact in the hidden-intent corpus was an external contract: a consumer's CSV dialect, a reporting timezone, retry safety against a non-idempotent endpoint, collation rules, a quota policy. Obtaining the deciding fact predicted the outcome almost perfectly in both arms, and the gate did not reliably cause it. Add an external-contract question for code targets whose change profile touches data, beside data-schema rather than replacing it: data-schema asks what the change touches, external-contract asks who decides what it has to be. It reuses the existing touchesData signal, so it stays as selective as the question it sits with and adds no new detection, and it therefore does not reach a change with no data involvement - pure retry semantics, for instance - which is a known limit to be measured rather than guessed at. Code edits that touch data now ask four questions instead of three, so the condensed hint for an unknown profile changes too. That string was pinned by a test as legacy-stable; the pin is updated deliberately, because a hint that names fewer questions than the gate asks is worse than a hint that changed. Suites: target-class 31, fact-force 1019, scenario-gate 25, eval 15, and the two effectiveness suites 29 and 26, all passing. The 23 relevantQuestions annotations that listed data-schema now list external-contract as well.
This commit is contained in:
@@ -24,6 +24,7 @@ const QUESTION_IDS = Object.freeze([
|
||||
'callers',
|
||||
'no-duplicate',
|
||||
'data-schema',
|
||||
'external-contract',
|
||||
'loader',
|
||||
'behaviour-change',
|
||||
'no-duplicate-instruction',
|
||||
|
||||
@@ -156,6 +156,8 @@ const QUESTION_TEXT = Object.freeze({
|
||||
'no-duplicate': `Confirm no existing file serves the same purpose ${SEARCH_THE_TREE}`,
|
||||
'data-schema':
|
||||
'If this file reads/writes data files, show field names, structure, and date format (use redacted or synthetic values, not raw production data)',
|
||||
'external-contract':
|
||||
'Name what outside this repository decides the format, units, timezone or protocol semantics here (the consumer, the producer, or a stated convention), or state that the choice is unconstrained',
|
||||
loader: 'Name the harness/loader that reads this file (Claude Code, Codex, Cursor, OpenCode, …) and when it loads it',
|
||||
'behaviour-change': 'Describe what agent behaviour changes as a result',
|
||||
'no-duplicate-instruction': `Confirm no existing instruction, skill, or agent file already covers this ${SEARCH_THE_TREE}`,
|
||||
@@ -189,7 +191,11 @@ function codeQuestionIds(isWrite, profile) {
|
||||
const surface = !known || profile.touchesPublicSurface !== false;
|
||||
const data = !known || profile.touchesData !== false;
|
||||
const opening = isWrite ? ['callers', 'no-duplicate'] : surface ? ['importers', 'public-api'] : ['local-callers'];
|
||||
return data ? [...opening, 'data-schema'] : opening;
|
||||
// A change that handles data is usually constrained by something outside the
|
||||
// tree - a consumer's dialect, a reporting timezone, a protocol's semantics -
|
||||
// and the repository cannot settle it. data-schema asks what the change
|
||||
// touches; external-contract asks who decides what it has to be.
|
||||
return data ? [...opening, 'data-schema', 'external-contract'] : opening;
|
||||
}
|
||||
|
||||
/** True when a class's questions depend on the change profile. */
|
||||
@@ -209,7 +215,7 @@ function questionText(id) {
|
||||
}
|
||||
|
||||
const CODE_CONDENSED_HINT =
|
||||
"briefly state importers/callers, affected API, data schemas if any, and the user's verbatim instruction, then retry.";
|
||||
"briefly state importers/callers, affected API, data schemas if any, what outside the repository fixes the format or semantics, and the user's verbatim instruction, then retry.";
|
||||
|
||||
const CONDENSED_PHRASES = Object.freeze({
|
||||
importers: 'the files that import this file',
|
||||
@@ -218,6 +224,7 @@ const CONDENSED_PHRASES = Object.freeze({
|
||||
callers: 'the file(s) and line(s) that will call it',
|
||||
'no-duplicate': 'that no existing file serves the same purpose',
|
||||
'data-schema': 'the data schemas it reads or writes',
|
||||
'external-contract': 'what outside the repository fixes the format or semantics',
|
||||
'quote-instruction': "the user's verbatim instruction"
|
||||
});
|
||||
|
||||
|
||||
@@ -951,6 +951,7 @@
|
||||
"callers",
|
||||
"no-duplicate",
|
||||
"data-schema",
|
||||
"external-contract",
|
||||
"quote-instruction"
|
||||
]
|
||||
},
|
||||
@@ -1019,6 +1020,7 @@
|
||||
"callers",
|
||||
"no-duplicate",
|
||||
"data-schema",
|
||||
"external-contract",
|
||||
"quote-instruction"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
"callers",
|
||||
"no-duplicate",
|
||||
"data-schema",
|
||||
"external-contract",
|
||||
"quote-instruction"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
"relevantQuestions": [
|
||||
"local-callers",
|
||||
"data-schema",
|
||||
"external-contract",
|
||||
"quote-instruction"
|
||||
]
|
||||
},
|
||||
@@ -135,6 +136,7 @@
|
||||
"relevantQuestions": [
|
||||
"local-callers",
|
||||
"data-schema",
|
||||
"external-contract",
|
||||
"quote-instruction"
|
||||
]
|
||||
},
|
||||
@@ -204,6 +206,7 @@
|
||||
"importers",
|
||||
"public-api",
|
||||
"data-schema",
|
||||
"external-contract",
|
||||
"quote-instruction"
|
||||
]
|
||||
},
|
||||
@@ -271,6 +274,7 @@
|
||||
"callers",
|
||||
"no-duplicate",
|
||||
"data-schema",
|
||||
"external-contract",
|
||||
"quote-instruction"
|
||||
]
|
||||
},
|
||||
@@ -339,6 +343,7 @@
|
||||
"relevantQuestions": [
|
||||
"local-callers",
|
||||
"data-schema",
|
||||
"external-contract",
|
||||
"quote-instruction"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -679,6 +679,7 @@
|
||||
"importers",
|
||||
"public-api",
|
||||
"data-schema",
|
||||
"external-contract",
|
||||
"quote-instruction"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -152,6 +152,7 @@
|
||||
"importers",
|
||||
"public-api",
|
||||
"data-schema",
|
||||
"external-contract",
|
||||
"quote-instruction"
|
||||
]
|
||||
},
|
||||
@@ -385,6 +386,7 @@
|
||||
"importers",
|
||||
"public-api",
|
||||
"data-schema",
|
||||
"external-contract",
|
||||
"quote-instruction"
|
||||
]
|
||||
},
|
||||
@@ -641,6 +643,7 @@
|
||||
"importers",
|
||||
"public-api",
|
||||
"data-schema",
|
||||
"external-contract",
|
||||
"quote-instruction"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -135,6 +135,7 @@
|
||||
"importers",
|
||||
"public-api",
|
||||
"data-schema",
|
||||
"external-contract",
|
||||
"quote-instruction"
|
||||
]
|
||||
},
|
||||
@@ -271,6 +272,7 @@
|
||||
"importers",
|
||||
"public-api",
|
||||
"data-schema",
|
||||
"external-contract",
|
||||
"quote-instruction"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -368,6 +368,7 @@
|
||||
"importers",
|
||||
"public-api",
|
||||
"data-schema",
|
||||
"external-contract",
|
||||
"quote-instruction"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -274,6 +274,7 @@
|
||||
"importers",
|
||||
"public-api",
|
||||
"data-schema",
|
||||
"external-contract",
|
||||
"quote-instruction"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -71,6 +71,7 @@
|
||||
"importers",
|
||||
"public-api",
|
||||
"data-schema",
|
||||
"external-contract",
|
||||
"quote-instruction"
|
||||
]
|
||||
},
|
||||
@@ -321,6 +322,7 @@
|
||||
"importers",
|
||||
"public-api",
|
||||
"data-schema",
|
||||
"external-contract",
|
||||
"quote-instruction"
|
||||
]
|
||||
},
|
||||
@@ -442,6 +444,7 @@
|
||||
"importers",
|
||||
"public-api",
|
||||
"data-schema",
|
||||
"external-contract",
|
||||
"quote-instruction"
|
||||
]
|
||||
},
|
||||
@@ -490,6 +493,7 @@
|
||||
"importers",
|
||||
"public-api",
|
||||
"data-schema",
|
||||
"external-contract",
|
||||
"quote-instruction"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -112,6 +112,7 @@
|
||||
"importers",
|
||||
"public-api",
|
||||
"data-schema",
|
||||
"external-contract",
|
||||
"quote-instruction"
|
||||
]
|
||||
},
|
||||
@@ -246,6 +247,7 @@
|
||||
"importers",
|
||||
"public-api",
|
||||
"data-schema",
|
||||
"external-contract",
|
||||
"quote-instruction"
|
||||
]
|
||||
},
|
||||
@@ -294,6 +296,7 @@
|
||||
"importers",
|
||||
"public-api",
|
||||
"data-schema",
|
||||
"external-contract",
|
||||
"quote-instruction"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -4345,7 +4345,8 @@ function runTests() {
|
||||
'1. Name the file(s) and line(s) that will call this new file',
|
||||
'2. Confirm no existing file serves the same purpose (search the tree — Glob/Grep, or find/grep via Bash)',
|
||||
'3. If this file reads/writes data files, show field names, structure, and date format (use redacted or synthetic values, not raw production data)',
|
||||
"4. Quote the user's current instruction verbatim",
|
||||
'4. Name what outside this repository decides the format, units, timezone or protocol semantics here (the consumer, the producer, or a stated convention), or state that the choice is unconstrained',
|
||||
"5. Quote the user's current instruction verbatim",
|
||||
'',
|
||||
'If this call was sent in a parallel batch, other edits to src/a.py from that batch may already have been applied. Re-read the file before building on them.',
|
||||
'',
|
||||
@@ -4362,7 +4363,8 @@ function runTests() {
|
||||
'1. List ALL files that import/require this file (search the tree — Glob/Grep, or find/grep via Bash)',
|
||||
'2. List the public functions/classes affected by this change',
|
||||
'3. If this file reads/writes data files, show field names, structure, and date format (use redacted or synthetic values, not raw production data)',
|
||||
"4. Quote the user's current instruction verbatim",
|
||||
'4. Name what outside this repository decides the format, units, timezone or protocol semantics here (the consumer, the producer, or a stated convention), or state that the choice is unconstrained',
|
||||
"5. Quote the user's current instruction verbatim",
|
||||
'',
|
||||
'If this call was sent in a parallel batch, other edits to src/b.py from that batch may already have been applied. Re-read the file before building on them.',
|
||||
'',
|
||||
@@ -4424,10 +4426,12 @@ function runTests() {
|
||||
callers: 'Name the file(s) and line(s) that will call this new file',
|
||||
noDuplicate: 'Confirm no existing file serves the same purpose (search the tree — Glob/Grep, or find/grep via Bash)',
|
||||
dataSchema: 'If this file reads/writes data files, show field names, structure, and date format (use redacted or synthetic values, not raw production data)',
|
||||
externalContract:
|
||||
'Name what outside this repository decides the format, units, timezone or protocol semantics here (the consumer, the producer, or a stated convention), or state that the choice is unconstrained',
|
||||
quote: QUOTE_LINE
|
||||
};
|
||||
const B2_FULL_EDIT = [QUESTION_LINES.importers, QUESTION_LINES.publicApi, QUESTION_LINES.dataSchema, QUESTION_LINES.quote];
|
||||
const B2_FULL_WRITE = [QUESTION_LINES.callers, QUESTION_LINES.noDuplicate, QUESTION_LINES.dataSchema, QUESTION_LINES.quote];
|
||||
const B2_FULL_EDIT = [QUESTION_LINES.importers, QUESTION_LINES.publicApi, QUESTION_LINES.dataSchema, QUESTION_LINES.externalContract, QUESTION_LINES.quote];
|
||||
const B2_FULL_WRITE = [QUESTION_LINES.callers, QUESTION_LINES.noDuplicate, QUESTION_LINES.dataSchema, QUESTION_LINES.externalContract, QUESTION_LINES.quote];
|
||||
|
||||
clearState();
|
||||
if (
|
||||
@@ -4455,7 +4459,7 @@ function runTests() {
|
||||
if (
|
||||
test('an Edit that handles data keeps the data-schema question', () => {
|
||||
const reason = profileDenialReason('Edit', { file_path: 'src/calc.py', old_string: ' x = 1', new_string: ' rows = json.load(fh)' });
|
||||
assert.deepStrictEqual(listedQuestions(reason), [QUESTION_LINES.localCallers, QUESTION_LINES.dataSchema, QUESTION_LINES.quote]);
|
||||
assert.deepStrictEqual(listedQuestions(reason), [QUESTION_LINES.localCallers, QUESTION_LINES.dataSchema, QUESTION_LINES.externalContract, QUESTION_LINES.quote]);
|
||||
})
|
||||
)
|
||||
passed++;
|
||||
@@ -4562,7 +4566,7 @@ function runTests() {
|
||||
assert.deepStrictEqual(listedQuestions(exported), [QUESTION_LINES.importers, QUESTION_LINES.publicApi, QUESTION_LINES.quote]);
|
||||
clearState();
|
||||
const fetch = profileDenialReason('Edit', { file_path: 'scripts/q4.sh', old_string: ' run', new_string: ' curl -s "$URL" > out.json' });
|
||||
assert.deepStrictEqual(listedQuestions(fetch), [QUESTION_LINES.localCallers, QUESTION_LINES.dataSchema, QUESTION_LINES.quote]);
|
||||
assert.deepStrictEqual(listedQuestions(fetch), [QUESTION_LINES.localCallers, QUESTION_LINES.dataSchema, QUESTION_LINES.externalContract, QUESTION_LINES.quote]);
|
||||
clearState();
|
||||
const ps = profileDenialReason('Edit', { file_path: 'scripts/q5.ps1', old_string: 'function Get-A {', new_string: 'function Get-B {' });
|
||||
assert.deepStrictEqual(listedQuestions(ps), [QUESTION_LINES.importers, QUESTION_LINES.publicApi, QUESTION_LINES.quote]);
|
||||
|
||||
@@ -324,21 +324,21 @@ test('every non-code class has questions and a condensed hint; code has neither'
|
||||
test('questionIdsFor gives stable ids per class, action and change profile', () => {
|
||||
const known = (touchesPublicSurface, touchesData) => ({ known: true, language: 'js', touchesPublicSurface, touchesData, trivial: false });
|
||||
const table = [
|
||||
['code', false, null, ['importers', 'public-api', 'data-schema', 'quote-instruction']],
|
||||
['code', true, null, ['callers', 'no-duplicate', 'data-schema', 'quote-instruction']],
|
||||
['code', false, { known: false, touchesPublicSurface: false, touchesData: false }, ['importers', 'public-api', 'data-schema', 'quote-instruction']],
|
||||
['code', false, known(true, true), ['importers', 'public-api', 'data-schema', 'quote-instruction']],
|
||||
['code', false, null, ['importers', 'public-api', 'data-schema', 'external-contract', 'quote-instruction']],
|
||||
['code', true, null, ['callers', 'no-duplicate', 'data-schema', 'external-contract', 'quote-instruction']],
|
||||
['code', false, { known: false, touchesPublicSurface: false, touchesData: false }, ['importers', 'public-api', 'data-schema', 'external-contract', 'quote-instruction']],
|
||||
['code', false, known(true, true), ['importers', 'public-api', 'data-schema', 'external-contract', 'quote-instruction']],
|
||||
['code', false, known(true, false), ['importers', 'public-api', 'quote-instruction']],
|
||||
['code', false, known(false, true), ['local-callers', 'data-schema', 'quote-instruction']],
|
||||
['code', false, known(false, true), ['local-callers', 'data-schema', 'external-contract', 'quote-instruction']],
|
||||
['code', false, known(false, false), ['local-callers', 'quote-instruction']],
|
||||
['code', true, known(false, false), ['callers', 'no-duplicate', 'quote-instruction']],
|
||||
['code', true, known(true, true), ['callers', 'no-duplicate', 'data-schema', 'quote-instruction']],
|
||||
['code', true, known(true, true), ['callers', 'no-duplicate', 'data-schema', 'external-contract', 'quote-instruction']],
|
||||
['instruction', false, known(false, false), ['loader', 'behaviour-change', 'no-duplicate-instruction', 'quote-instruction']],
|
||||
['test', true, known(false, false), ['under-test', 'existing-tests', 'quote-instruction']],
|
||||
['prose', true, null, ['supersedes', 'linked-from', 'why-new-file', 'quote-instruction']],
|
||||
['prose', false, null, ['references', 'corrects-or-adds', 'quote-instruction']],
|
||||
['config', false, known(false, false), ['config-reader', 'config-effect', 'no-plaintext-secrets', 'quote-instruction']],
|
||||
['__proto__', false, null, ['importers', 'public-api', 'data-schema', 'quote-instruction']]
|
||||
['__proto__', false, null, ['importers', 'public-api', 'data-schema', 'external-contract', 'quote-instruction']]
|
||||
];
|
||||
for (const [cls, isWrite, profile, expected] of table) {
|
||||
const ids = questionIdsFor(cls, isWrite, profile);
|
||||
@@ -363,7 +363,7 @@ test('condensedHintFor matches the question set', () => {
|
||||
|
||||
test('code condensed hints name exactly the questions of the change profile', () => {
|
||||
const known = (touchesPublicSurface, touchesData) => ({ known: true, language: 'js', touchesPublicSurface, touchesData, trivial: false });
|
||||
const ids = ['importers', 'public-api', 'local-callers', 'callers', 'no-duplicate', 'data-schema', 'quote-instruction'];
|
||||
const ids = ['importers', 'public-api', 'local-callers', 'callers', 'no-duplicate', 'data-schema', 'external-contract', 'quote-instruction'];
|
||||
for (const id of ids) assert.ok(condensedQuestionPhrase(id), `${id} has a phrase`);
|
||||
for (const isWrite of [false, true]) {
|
||||
for (const [s, d] of [[true, true], [true, false], [false, true], [false, false]]) {
|
||||
@@ -404,7 +404,7 @@ test('code condensed hints read as one sentence', () => {
|
||||
);
|
||||
assert.strictEqual(
|
||||
condensedHintFor('code', false, known(true, true)),
|
||||
"briefly state the files that import this file, the public functions/classes affected, the data schemas it reads or writes, and the user's verbatim instruction, then retry."
|
||||
"briefly state the files that import this file, the public functions/classes affected, the data schemas it reads or writes, what outside the repository fixes the format or semantics, and the user's verbatim instruction, then retry."
|
||||
);
|
||||
assert.strictEqual(
|
||||
condensedHintFor('code', true, known(true, false)),
|
||||
@@ -422,11 +422,11 @@ test('code condensed hints keep the data and duplicate checks when the profile a
|
||||
}
|
||||
});
|
||||
|
||||
test('code condensed Edit hint for an unknown profile is unchanged', () => {
|
||||
const legacy = "briefly state importers/callers, affected API, data schemas if any, and the user's verbatim instruction, then retry.";
|
||||
test('code condensed Edit hint for an unknown profile names every question asked', () => {
|
||||
const expected = "briefly state importers/callers, affected API, data schemas if any, what outside the repository fixes the format or semantics, and the user's verbatim instruction, then retry.";
|
||||
for (const profile of [null, undefined, { known: false, touchesPublicSurface: false, touchesData: false }, 'x']) {
|
||||
assert.strictEqual(condensedHintFor('code', false, profile), legacy);
|
||||
assert.strictEqual(condensedHintFor('__proto__', false, profile), legacy);
|
||||
assert.strictEqual(condensedHintFor('code', false, profile), expected);
|
||||
assert.strictEqual(condensedHintFor('__proto__', false, profile), expected);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -434,7 +434,7 @@ test('code condensed Write hint for an unknown profile asks every creation quest
|
||||
for (const profile of [null, undefined, { known: false, touchesPublicSurface: false, touchesData: false }, 'x']) {
|
||||
assert.strictEqual(
|
||||
condensedHintFor('code', true, profile),
|
||||
"briefly state the file(s) and line(s) that will call it, that no existing file serves the same purpose, the data schemas it reads or writes, and the user's verbatim instruction, then retry."
|
||||
"briefly state the file(s) and line(s) that will call it, that no existing file serves the same purpose, the data schemas it reads or writes, what outside the repository fixes the format or semantics, and the user's verbatim instruction, then retry."
|
||||
);
|
||||
}
|
||||
assert.strictEqual(condensedQuestionPhrase('constructor'), '');
|
||||
|
||||
Reference in New Issue
Block a user