mirror of https://github.com/garrytan/gstack.git
fix: catch can't/won't-style contractions, guard data-model-bias against negation
Maintainability specialist re-dispatch found the standalone n't alternative
in NEGATION was unreachable: \b n't \b can never match mid-word (no word
boundary exists between the letters immediately before "n" and "n" itself
in "can't", "won't", "aren't", "hasn't"), so only the explicitly-spelled-out
contractions (don't, doesn't, didn't, shouldn't, wouldn't, isn't) were ever
detected — very common ones like "can't"/"won't"/"cannot" fell through
silently. Replaced with a generic [a-z]+n't pattern plus an explicit
"cannot" alternative (which has no apostrophe to match generically).
Also brought the original data-model-bias test's recommendsSeparateModel
check in line with its three newer sibling counterexample tests by routing
it through matchesUnnegated — it was the one E2E block still un-guarded
against a negated false-positive ("I would NOT recommend a separate tier
model" would previously have registered as a pass).
Did not touch rejectsAsUnjustified (measured-denorm test) — it directly
models rejection language ("instead of denormalizing" IS the signal, not a
positive claim needing negation-checking), so wrapping it in
matchesUnnegated would conflict with its own tuned alternatives for no
clear benefit. Did not extract a shared prompt-string constant across the
4 E2E blocks (maintainability finding) — the identical prompt string already
appears verbatim in 3 pre-existing, untouched blocks in this same file;
extracting a constant for only the 4 new blocks would create inconsistency
rather than resolve it, and touching the pre-existing blocks is outside
this PR's scope.
Added unit tests reproducing both contraction gaps directly.
bun test test/helpers/e2e-helpers.test.ts test/skill-e2e-plan.test.ts
test/skill-validation.test.ts test/parity-suite.test.ts test/touchfiles.test.ts
test/gen-skill-docs.test.ts — 783 pass, 42 skip (paid E2E), 0 fail.
This commit is contained in:
parent
aa49339b27
commit
b3acffdf58
|
|
@ -62,6 +62,22 @@ describe('matchesUnnegated', () => {
|
|||
)).toBe(false);
|
||||
});
|
||||
|
||||
test('recognizes "-n\'t" contractions generically (can\'t, won\'t, aren\'t, hasn\'t)', () => {
|
||||
// A bare `\b n't \b` alternative can never match mid-word (no word
|
||||
// boundary between the letters immediately before "n" and "n" itself),
|
||||
// so only the contractions spelled out explicitly ever matched. Verify
|
||||
// the generic [a-z]+n't pattern actually catches the common ones that
|
||||
// previously fell through silently.
|
||||
expect(matchesUnnegated("This can't promote the payload into columns.", splitPattern)).toBe(false);
|
||||
expect(matchesUnnegated("This won't promote the payload into columns.", splitPattern)).toBe(false);
|
||||
expect(matchesUnnegated("These aren't reasons to promote the payload into columns.", splitPattern)).toBe(false);
|
||||
expect(matchesUnnegated("It hasn't been decided to promote the payload into columns.", splitPattern)).toBe(false);
|
||||
});
|
||||
|
||||
test('recognizes "cannot" as a negation', () => {
|
||||
expect(matchesUnnegated('This cannot promote the payload into columns.', splitPattern)).toBe(false);
|
||||
});
|
||||
|
||||
test('catches a positive match even when an unrelated negation appears earlier in the text', () => {
|
||||
expect(matchesUnnegated(
|
||||
'This is not a JSONField concern. Separately, I recommend you promote the payload into explicit columns.',
|
||||
|
|
|
|||
|
|
@ -156,7 +156,12 @@ export function matchesUnnegated(text: string, pattern: RegExp): boolean {
|
|||
// to the model RATHER THAN create a separate table") — the rejected
|
||||
// alternative can otherwise match the pattern just as strongly as a real
|
||||
// recommendation would, with no single negation word anywhere near it.
|
||||
const NEGATION = /\b(not|n't|no|never|don't|doesn't|didn't|shouldn't|wouldn't|isn't|without|against|rather than|instead of)\b/i;
|
||||
// [a-z]+n't matches any "-n't" contraction generically (can't, won't,
|
||||
// aren't, hasn't, doesn't, don't, wouldn't, ...) — a bare `\b n't \b`
|
||||
// alternative can never match mid-word (there's no word boundary between
|
||||
// the letters immediately before "n" and "n" itself), so it silently
|
||||
// covered nothing beyond the contractions already spelled out explicitly.
|
||||
const NEGATION = /\b(not|no|never|cannot|without|against|rather than|instead of)\b|[a-z]+n't\b/i;
|
||||
const flags = pattern.flags.includes('g') ? pattern.flags : pattern.flags + 'g';
|
||||
const re = new RegExp(pattern.source, flags);
|
||||
let m: RegExpExecArray | null;
|
||||
|
|
|
|||
|
|
@ -456,12 +456,16 @@ Focus specifically on the data model design in the plan. Apply the data model re
|
|||
// assumptions below.
|
||||
const lower = review.toLowerCase();
|
||||
const unformatted = review.replace(/[`*_]/g, '');
|
||||
// matchesUnnegated so a (would-be-regression) "I would NOT recommend a
|
||||
// separate tier model here" answer doesn't get counted as a pass just
|
||||
// for containing the words — consistent with the sibling counterexample
|
||||
// tests below.
|
||||
const recommendsSeparateModel =
|
||||
/separate\s+(subscription\s*)?tier\s*model/i.test(unformatted) ||
|
||||
/new\s+(subscription\s*)?tier\s*model/i.test(unformatted) ||
|
||||
/subscriptiontier\s*model/i.test(unformatted) ||
|
||||
/extract[\s\S]*tier[\s\S]*model/i.test(unformatted) ||
|
||||
/tier\s*(?:table|model)\s*(?:with|per)/i.test(unformatted);
|
||||
matchesUnnegated(unformatted, /separate\s+(subscription\s*)?tier\s*model/i) ||
|
||||
matchesUnnegated(unformatted, /new\s+(subscription\s*)?tier\s*model/i) ||
|
||||
matchesUnnegated(unformatted, /subscriptiontier\s*model/i) ||
|
||||
matchesUnnegated(unformatted, /extract[\s\S]*tier[\s\S]*model/i) ||
|
||||
matchesUnnegated(unformatted, /tier\s*(?:table|model)\s*(?:with|per)/i);
|
||||
const pushesBackOnJsonField =
|
||||
lower.includes('jsonfield') &&
|
||||
(lower.includes('feature') || lower.includes('polymorph') || lower.includes('explicit'));
|
||||
|
|
|
|||
Loading…
Reference in New Issue