mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 14:16:28 +03:00
Route the Cursor deny cap through the clamp and match the whole footer
Bugbot follow-up: cursorBlockMessage tail-sliced at 4000 chars after render, which the default 8000-char budget made reachable, and a cut that spared the footer's opening words still committed the session flag. The 4000 cap now feeds through the renderer's footer-preserving clamp, and commitFooterShown matches the complete footer text instead of a sentinel. AI-assisted (Cursor agent), directed and reviewed by @abdulwahabone. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -16,6 +16,7 @@ import path from 'node:path';
|
||||
|
||||
import {
|
||||
ALLOWED_EXTS,
|
||||
DEFAULT_CONFIG,
|
||||
EDIT_COUNT_THRESHOLD,
|
||||
GENERATED_PATH,
|
||||
SENSITIVE_PATH,
|
||||
@@ -348,13 +349,26 @@ async function detectProposedHtml(detector, content, filePath, scanOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
// Cursor caps deny messages around 4000 chars. The cap feeds through the
|
||||
// renderer's clamp, which preserves the policy footer, rather than tail-
|
||||
// slicing the rendered text, which cut the footer off any message the
|
||||
// default 8000-char budget let past 4000.
|
||||
const CURSOR_DENY_LIMIT = 4000;
|
||||
const BLOCK_PREFIX = 'Impeccable design hook blocked this write before it landed. ';
|
||||
|
||||
function cursorBlockMessage(findings, filePath, config, cwd, footerMode, reserveChars) {
|
||||
const rendered = renderTemplate(findings, filePath, config, { cwd, footer: footerMode, reserveChars });
|
||||
const blocked = rendered.replace(
|
||||
const limits = config?.limits || DEFAULT_CONFIG.limits;
|
||||
const budget = Math.min(
|
||||
limits.maxChars || DEFAULT_CONFIG.limits.maxChars,
|
||||
CURSOR_DENY_LIMIT - BLOCK_PREFIX.length,
|
||||
);
|
||||
const rendered = renderTemplate(findings, filePath,
|
||||
{ ...config, limits: { ...limits, maxChars: budget } },
|
||||
{ cwd, footer: footerMode, reserveChars });
|
||||
return rendered.replace(
|
||||
'[impeccable@1] Design hook findings requiring review',
|
||||
'[impeccable@1] Impeccable design hook blocked this write before it landed. Design hook findings requiring review',
|
||||
`[impeccable@1] ${BLOCK_PREFIX}Design hook findings requiring review`,
|
||||
);
|
||||
return blocked.length > 4000 ? `${blocked.slice(0, 3984)}\n...(truncated)` : blocked;
|
||||
}
|
||||
|
||||
function findingSignature(findings) {
|
||||
|
||||
@@ -1702,14 +1702,15 @@ export function designNoteReserve(scanOptions, cache, sessionId) {
|
||||
// emissions and Cursor denials share the session flag (`footerShown`), so a
|
||||
// session pays the full policy exactly once however it first fires. The mode
|
||||
// is a peek: the clamp can downgrade a requested full footer under a tight
|
||||
// budget, so the flag commits only when the full policy actually reached the
|
||||
// output (commitFooterShown, matched on the footer's opening words).
|
||||
// budget, so the flag commits only when the complete full policy actually
|
||||
// reached the output. Matching the whole footer text (not a sentinel) keeps
|
||||
// the flag honest against any truncation that spares the opening words.
|
||||
export function footerModeForSession(cache, sessionId) {
|
||||
return ensureSession(cache, sessionId).footerShown ? 'short' : 'full';
|
||||
}
|
||||
|
||||
export function commitFooterShown(cache, sessionId, text) {
|
||||
if (!text || !text.includes(FULL_FOOTER_SENTINEL)) return;
|
||||
if (!text || !text.includes(directiveFooter())) return;
|
||||
const session = ensureSession(cache, sessionId);
|
||||
if (session.footerShown) return;
|
||||
session.footerShown = true;
|
||||
@@ -1718,10 +1719,6 @@ export function commitFooterShown(cache, sessionId, text) {
|
||||
|
||||
const HOOK_ADMIN_COMMAND = `node ${quoteCommandArg(path.join(__dirname, 'hook-admin.mjs'))}`;
|
||||
|
||||
// Opening words of the full footer; commitFooterShown matches on it to tell
|
||||
// whether the full policy survived the clamp.
|
||||
const FULL_FOOTER_SENTINEL = 'Triage each finding';
|
||||
|
||||
// The directive footer is the part of the hook output that steers model
|
||||
// behavior. Intentional moves, in order:
|
||||
// 1. **Imperative, not advisory.** "Triage each finding..." beats
|
||||
@@ -1749,7 +1746,7 @@ function directiveFooter(opts = {}) {
|
||||
return 'Triage per the session policy: fix real problems; persist confident false-positive or sanctioned-exception ignores via `hook-admin.mjs ignore-value` and disclose them in your reply; unsure, ask in one line.';
|
||||
}
|
||||
return [
|
||||
`${FULL_FOOTER_SENTINEL}, then state in your reply what you fixed, what you suppressed, and what you left standing:`,
|
||||
'Triage each finding, then state in your reply what you fixed, what you suppressed, and what you left standing:',
|
||||
'- Real design problem: fix it. Keep intentional design as designed.',
|
||||
`- Confident false positive or sanctioned exception (an intentional demo or fixture, documentation of bad design, literal or domain-appropriate motion, a choice the user confirmed): persist the narrowest ignore yourself and disclose it. Run \`${HOOK_ADMIN_COMMAND} ignore-value <rule> "<value>" --reason "<who decided: evidence>"\` with the pair shown on the finding line, or value "*" plus \`--file <path>\` when the line shows none. Write "user confirmed" in a reason only when the user did.`,
|
||||
'- Unsure: leave it as is and ask the user in one line.',
|
||||
|
||||
@@ -50,6 +50,7 @@ import {
|
||||
coLocatedStylesheets,
|
||||
runHook,
|
||||
runStopHook,
|
||||
commitFooterShown,
|
||||
IMMEDIATE_TIER_RULES,
|
||||
splitFindingsByTier,
|
||||
perEditTieringActive,
|
||||
@@ -2305,6 +2306,22 @@ describe('runHook() — session-scoped notices', () => {
|
||||
const cache = readCache(cwd);
|
||||
assert.ok(!cache.sessions['sid-1'].footerShown, 'a downgraded footer does not spend the session flag');
|
||||
});
|
||||
|
||||
it('commits the footer flag only for the complete full policy, not its opening words', () => {
|
||||
const cache = { version: 1, sessions: {} };
|
||||
const full = renderTemplate(
|
||||
[finding('tiny-text', 1, { name: 'Tiny text' })],
|
||||
'/x/a.css', DEFAULT_CONFIG, { cwd: '/x' },
|
||||
);
|
||||
|
||||
// A tail truncation can spare "Triage each finding" while cutting the
|
||||
// policy body. That must not count as delivered.
|
||||
commitFooterShown(cache, 'sid-1', full.slice(0, full.length - 40));
|
||||
assert.ok(!cache.sessions['sid-1']?.footerShown, 'a truncated policy must not spend the flag');
|
||||
|
||||
commitFooterShown(cache, 'sid-1', full);
|
||||
assert.ok(cache.sessions['sid-1'].footerShown, 'the intact policy commits the flag');
|
||||
});
|
||||
});
|
||||
|
||||
describe('runHook() — clean-ack noise', () => {
|
||||
@@ -3113,6 +3130,8 @@ describe('Cursor hook scripts', () => {
|
||||
assert.match(payload.user_message, /blocked this write/);
|
||||
assert.match(payload.user_message, /side-tab/);
|
||||
assert.match(payload.agent_message, /Triage each finding/);
|
||||
assert.match(payload.agent_message, /Full suppression ladder/, 'the deny message carries the complete policy, not a truncated head');
|
||||
assert.ok(payload.agent_message.length <= 4000, 'the deny message respects the Cursor cap');
|
||||
|
||||
const entries = fs.readFileSync(logPath, 'utf-8').trim().split('\n').map((line) => JSON.parse(line));
|
||||
assert.equal(entries[0].event, 'preToolUse');
|
||||
|
||||
Reference in New Issue
Block a user