diff --git a/tests/runner-e2e/chat-catalog-domains/external-team-conditional.test.ts b/tests/runner-e2e/chat-catalog-domains/external-team-conditional.test.ts index 9da218b475d..f3acc928dca 100644 --- a/tests/runner-e2e/chat-catalog-domains/external-team-conditional.test.ts +++ b/tests/runner-e2e/chat-catalog-domains/external-team-conditional.test.ts @@ -534,3 +534,19 @@ it("rejects a current unattached claim when independent saved recovery is proven expect(gradeTeamConditions(o, phase === 0 ? "initial" : "complete").passed).toBe(false); } }); + +it("binds ordinary skill qualifiers to the exact required skill across missing and recovery checks",()=>{ + const target={slug:"doc-maintenance",name:"doc-maintenance"}; + for(const qualifier of ["catalog","required catalog","imported","bundled"]){ + const partial="The `doc-maintenance` "+qualifier+" skill is unresolved because a company-owned skill already uses that slug; I preserved the existing skill."; + expect(truthfulTeamPartial(partial,target,true),partial).toBe(true); + expect(truthfulTeamPartial(partial+" The doc-maintenance "+qualifier+" skill is now installed.",target,true)).toBe(false); + expect(truthfulTeamPartial("A different "+qualifier+" skill is unresolved because its slug conflicts.",target,true)).toBe(false); + expect(truthfulTeamPartial("Earlier, the doc-maintenance "+qualifier+" skill was unresolved because its slug conflicted.",target,true)).toBe(false); + for(const phase of [0,1]){ + const {o}=recovered(); + o.phaseReplies[phase]!.body="The existing CTO was preserved and the new role renamed CTO 2. Launch Checklist "+qualifier+" skill is still missing."; + expect(gradeTeamConditions(o,phase===0?"initial":"complete").passed).toBe(false); + } + } +}); diff --git a/tests/runner-e2e/chat-catalog-domains/external-team-conditional.ts b/tests/runner-e2e/chat-catalog-domains/external-team-conditional.ts index e2f4eba752e..a6222d1b12e 100644 --- a/tests/runner-e2e/chat-catalog-domains/external-team-conditional.ts +++ b/tests/runner-e2e/chat-catalog-domains/external-team-conditional.ts @@ -206,7 +206,7 @@ function negatedTeamRecovery(before: string): boolean { return /\b(?:not|never|isn.t|aren.t|wasn.t|weren.t|hasn.t|haven.t)\s+(?:(?:be|been|get|yet|currently|still|successfully|fully|completely)\s+){0,2}$/i.test(before.replace(/[*_`]+\s*$/, "")); } function teamSkillSubject(skill: Pick): string { - return "[*_`]*(?:" + [skill.slug, skill.slug.replaceAll("-", " "), skill.name].map(name => name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|") + ")[*_`]*"; + return "[*_`]*(?:" + [skill.slug, skill.slug.replaceAll("-", " "), skill.name].map(name => name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|") + ")[*_`]*(?:\\s+(?:(?:required|catalog|imported|bundled)\\s+){1,3}skill)?"; } function historicalTeamSkillClause(clause: string, skill: Pick): boolean { const subject = new RegExp(teamSkillSubject(skill), "i").exec(clause);