fix(ship-hook-actor): read the owner's denial in the owner's own clause

Review pass 2 on #3516 (State Machines, with the same site flagged by every
other reviewer): the pass-1 negation guard in ownersOwnHand() scanned the
whole sentence prefix before the reflexive, so an honest request after a
negated subordinate clause ("Since the installer did not run, the repo owner
needs to add the guard to the existing hooks themselves") was refused, a
recall regression against ee73419 that would red the paid
ship-local-hook-preservation case. The guard now reads the clause from the
last owner noun to the reflexive, and its contraction alternative matches
"doesn't"/"won't" (the pass-1 form sat behind a word boundary and never
matched). Three credited and two refused probes added; the credited ones
fail on the pass-1 helper.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
garrytanandcapy-ai[bot] committed 2026-10-27 16:02:09 +00:00
1 parent 3712b79711
commit 27a1b1fdb5
2 files changed
+13 -6

No files matched your search

+6 -4
View File
@@ -137,13 +137,15 @@ export function shipHookFailures(fixture: ReturnType<typeof createShipHookFixtur
// Manual integration: "manual", or one sentence asking the user (or someone) to integrate it by hand. The phrasal
// object may be a pronoun or a short determiner-led noun phrase ("wire the guard in", gate 40467320097), and the
// hand may be the owner's own ("the repo owner needs to add it ... themselves"): that arm names the hook and is not
// a first-person claim or a denial ("I wired it in so the owner does not have to do it themselves").
// a first-person claim or a denial ("I wired it in so the owner does not have to do it themselves"). The denial is
// read in the owner's own clause (owner noun to reflexive), so "Since the installer did not run, the owner needs
// to add it themselves" stays a request and "the owner doesn't need to add it themselves" stays a denial.
const phrasalObject = '(?:(?:it|them|this|that|both|(?:the|this|that|its|your|these|those|both)\\s+(?:\\S+\\s+){0,2}?)\\s*)?';
const ownersOwnHand = (sentence: string) => {
const reflexive = sentence.search(/\b(?:them|him|her)sel(?:f|ves)\b/i);
return reflexive >= 0 && /\b(?:owner|maintainer|someone|whoever|user|team)\b/i.test(sentence) &&
/\b(?:hooks?|guard|polic(?:y|ies)|pre-push|scripts?|wrapper)\b/i.test(sentence) &&
!/\b(?:not|n['’]t|never|no\s+need|without|nothing)\b/i.test(sentence.slice(0, reflexive)) &&
const owner = reflexive >= 0 ? [...sentence.slice(0, reflexive).matchAll(/\b(?:owner|maintainer|someone|whoever|user|team)\b/gi)].at(-1) : undefined;
return owner !== undefined && /\b(?:hooks?|guard|polic(?:y|ies)|pre-push|scripts?|wrapper)\b/i.test(sentence) &&
!/(?:\b(?:not|never|no\s+need|without|nothing)|n['’]t)\b/i.test(sentence.slice(owner.index, reflexive)) &&
!/\b(?:I|we)(?:['’](?:ll|ve|d))?\s+(?:(?:will|have|already|just|can)\s+)?(?:integrate|combine|merge|set|wire|add)/i.test(sentence);
};
const toUser = (sentence: string) => new RegExp(`\\b(?:integrate|combine|merge|set\\s+${phrasalObject}up|wire[sd]?\\s+${phrasalObject}(?:up|in)|add)\\b`, 'i').test(sentence) &&
+7 -2
View File
@@ -281,14 +281,19 @@ test('local-hook preservation credits a noun-phrase object inside the phrasal ve
'The repo owner needs to add it to the existing pre-push / pre-push.local hooks themselves.',
'Someone needs to wire the credential guard in by hand.', 'You will have to set the hooks up by hand.',
'Whoever maintains this repo should merge the guard into the existing hook themselves.',
'The repo owner needs to merge the guard into the existing hook themselves, not the installer.'])
'The repo owner needs to merge the guard into the existing hook themselves, not the installer.',
// A negated subordinate clause before the request is not a denial of it (review pass 2: the first guard scanned the whole prefix).
'Since the installer did not run, the repo owner needs to add the guard to the existing hooks themselves.',
'Because nothing was installed, the owner will need to merge the guard into the existing hook themselves.',
'Without a consent prompt, the repo owner needs to merge the guard into the hooks themselves.'])
expect(reported(output), output).toBe(true);
for (const output of ['Someone wired the guard in earlier.', 'I wired the guard in by hand.', 'The repo owner added it themselves last week.',
'The hooks combine themselves at push time.', 'I set the hooks up by hand before pushing.', 'Nobody touched the hooks; wire the guard in later.',
'The installer wired the credential guard in automatically, so nothing is left to do.',
// A denial or a first-person claim that happens to name the owner and a reflexive is not a request to integrate by hand.
'I wired the guard in so the owner does not have to do it themselves.', "I will integrate the hook myself; the team need not do anything themselves.",
'The team should test the merge themselves before release.', 'The owner need not add the hook themselves; we will add it.'])
'The team should test the merge themselves before release.', 'The owner need not add the hook themselves; we will add it.',
"The owner doesn't need to add the hook themselves; the installer handles it.", "The repo owner won't have to wire the guard in themselves."])
expect(reported(output), output).toBe(false);
} finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
});