mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-11 21:57:14 +03:00
Enforce the ask_instruction sentence-initial contract
Review on #576 caught document.md:71 splicing {{ask_instruction}} after "then", which is the same defect this branch set out to fix. Rendered for Codex it produced "Show the user the existing file, then STOP and use Codex's structured user-input/question tool...". The line now starts a new sentence. The comment added to PROVIDER_PLACEHOLDERS asserted the contract without enforcing it, which is exactly how four reference files shipped the splice in the first place. validateAskInstructionSites() in build.js now checks every call site and fails the build on a mid-sentence interpolation, and the comment points at the gate instead of asking authors to remember. Prepared with AI assistance (Claude Code). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
0e5c6cbe17
commit
d4e1b0902f
+56
-1
@@ -371,6 +371,57 @@ function validateSkillProse(rootDir) {
|
||||
return errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that every `{{ask_instruction}}` interpolation starts a sentence.
|
||||
*
|
||||
* The placeholder's per-provider values are complete capitalized sentences
|
||||
* ("STOP and call the AskUserQuestion tool to clarify."), so a call site that
|
||||
* splices it mid-sentence ships malformed guidance to every provider at once:
|
||||
* `stop and STOP and call the AskUserQuestion tool to clarify. before expanding
|
||||
* it`. Four reference files shipped exactly that before this gate existed, and
|
||||
* a comment in PROVIDER_PLACEHOLDERS asking authors to keep the contract is
|
||||
* what failed to prevent it.
|
||||
*
|
||||
* Returns the number of validation errors. Build fails if > 0.
|
||||
*/
|
||||
function validateAskInstructionSites(rootDir) {
|
||||
const dir = path.join(rootDir, 'skill', 'reference');
|
||||
const token = '{{ask_instruction}}';
|
||||
let errors = 0;
|
||||
let sites = 0;
|
||||
|
||||
if (!fs.existsSync(dir)) return 0;
|
||||
|
||||
for (const file of fs.readdirSync(dir)) {
|
||||
if (path.extname(file) !== '.md') continue;
|
||||
const rel = path.join('skill/reference', file);
|
||||
fs.readFileSync(path.join(dir, file), 'utf-8')
|
||||
.split('\n')
|
||||
.forEach((line, i) => {
|
||||
let idx = line.indexOf(token);
|
||||
while (idx !== -1) {
|
||||
sites++;
|
||||
// Bold/italic markers may sit between the punctuation and the token.
|
||||
const before = line.slice(0, idx).replace(/[*_`]+\s*$/, '').trimEnd();
|
||||
if (before !== '' && !/[.!?:]$/.test(before)) {
|
||||
console.error(` ❌ ${rel}:${i + 1}: ${token} is spliced mid-sentence`);
|
||||
console.error(` ...${before.slice(-60)} ${token}`);
|
||||
console.error(` Provider values are full sentences. Start a new one.`);
|
||||
errors++;
|
||||
}
|
||||
idx = line.indexOf(token, idx + 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (errors === 0) {
|
||||
console.log(`✓ ask_instruction call sites: ${sites} sentence-initial`);
|
||||
} else {
|
||||
console.error(`\n❌ ${errors} of ${sites} {{ask_instruction}} site(s) spliced mid-sentence.`);
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that every hand-authored HTML page carries the shared site header.
|
||||
* The partial is stamped with `<!-- site-header v1 -->` so drift is loud.
|
||||
@@ -738,7 +789,11 @@ async function build() {
|
||||
// that has no technical reading. Hardening repetition is intentionally allowed.
|
||||
const skillProseErrors = validateSkillProse(ROOT_DIR);
|
||||
|
||||
if (countErrors > 0 || versionErrors > 0 || manifestShapeErrors > 0 || proseErrors > 0 || skillProseErrors > 0) {
|
||||
// Placeholder values are full sentences; a mid-sentence splice ships broken
|
||||
// guidance to every provider at once.
|
||||
const askSiteErrors = validateAskInstructionSites(ROOT_DIR);
|
||||
|
||||
if (countErrors > 0 || versionErrors > 0 || manifestShapeErrors > 0 || proseErrors > 0 || skillProseErrors > 0 || askSiteErrors > 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
||||
@@ -477,8 +477,10 @@ export const PROVIDER_PLACEHOLDERS = {
|
||||
'codex': {
|
||||
model: 'GPT',
|
||||
config_file: 'AGENTS.md',
|
||||
// Every {{ask_instruction}} call site is sentence-initial, so each value is a
|
||||
// complete capitalized sentence. Keep it that way when adding a provider.
|
||||
// Each value is a complete capitalized sentence, because every
|
||||
// {{ask_instruction}} call site is sentence-initial. That is enforced by
|
||||
// validateAskInstructionSites() in scripts/build.js, not left to authors:
|
||||
// four reference files had already spliced the placeholder mid-sentence.
|
||||
ask_instruction: "STOP and use Codex's structured user-input/question tool when available; if unavailable, ask directly in chat to clarify what you cannot infer.",
|
||||
command_prefix: '$'
|
||||
},
|
||||
|
||||
@@ -68,7 +68,7 @@ Omit irrelevant sections rather than filling them with invented rules. Put respo
|
||||
- An existing `DESIGN.md` is stale (the design has drifted).
|
||||
- Before a large redesign, to capture the current state as a reference.
|
||||
|
||||
If a `DESIGN.md` already exists, **do not silently overwrite it**. Show the user the existing file, then {{ask_instruction}} The choice is refresh, overwrite, or merge.
|
||||
If a `DESIGN.md` already exists, **do not silently overwrite it**. Show the user the existing file first. {{ask_instruction}} The choice is refresh, overwrite, or merge.
|
||||
|
||||
## Two paths
|
||||
|
||||
|
||||
Reference in New Issue
Block a user