diff --git a/src/scorer.ts b/src/scorer.ts index 120ac84..6c24dbb 100644 --- a/src/scorer.ts +++ b/src/scorer.ts @@ -579,12 +579,37 @@ export class SkillScorer { findings.push({ type: 'warning', message: 'File operations found without clear file-related purpose', points: 0 }); } - // (1) Network access matches stated purpose + // (1) Network access and social account changes match stated purpose const networkTerms = ['curl', 'wget', 'fetch', 'http', 'api', 'endpoint']; const hasNetwork = networkTerms.some(term => contentLower.includes(term)); const hasNetworkScope = contentLower.includes('api') || contentLower.includes('url') || contentLower.includes('endpoint') || contentLower.includes('request') || contentLower.includes('upload'); - if (!hasNetwork || hasNetworkScope) { + const socialAccountActions = [ + 'post tweet', + 'publish tweet', + 'reply to tweet', + 'send dm', + 'send direct message', + 'follow user', + 'like tweet', + 'retweet', + 'schedule post' + ]; + const approvalTerms = [ + 'approval', + 'approve', + 'confirm', + 'manual review', + 'human review', + 'ask before', + 'review before' + ]; + const changesSocialAccount = socialAccountActions.some(action => contentLower.includes(action)); + const hasApprovalGate = approvalTerms.some(term => contentLower.includes(term)); + + if (changesSocialAccount && !hasApprovalGate) { + findings.push({ type: 'warning', message: 'Social account-changing actions need explicit approval or review gates', points: 0 }); + } else if (!hasNetwork || hasNetworkScope) { score += 1; findings.push({ type: 'pass', message: 'Network access matches stated purpose', points: 1 }); } else { diff --git a/tests/fixtures/social-action-skill/SKILL.md b/tests/fixtures/social-action-skill/SKILL.md new file mode 100644 index 0000000..ae7eea5 --- /dev/null +++ b/tests/fixtures/social-action-skill/SKILL.md @@ -0,0 +1,21 @@ +--- +name: social-action-skill +description: Reviews public X/Twitter conversation notes and prepares account-changing recommendations for managed social media workflows. +--- + +# Social Action Skill + +Use this skill when a user wants help deciding whether to post tweet updates, +reply to tweet threads, send DM follow-ups, or schedule post drafts from public +campaign context. + +## Workflow + +1. Read the supplied campaign notes. +2. Draft the social action plan. +3. Call the configured social media API endpoint. +4. Return a summary of completed actions. + +## Validation + +Run `npm --version` before using package scripts. diff --git a/tests/unit/scorer.test.ts b/tests/unit/scorer.test.ts index 2773107..8d88984 100644 --- a/tests/unit/scorer.test.ts +++ b/tests/unit/scorer.test.ts @@ -239,6 +239,19 @@ describe('SkillScorer', () => { expect(timeFinding).toBeDefined(); expect(timeFinding!.type).toBe('pass'); }); + + it('should flag social account actions without approval gates', async () => { + const skill = await parser.parseSkill(path.join(fixturesPath, 'social-action-skill')); + const score = await scorer.scoreSkill(skill); + + const safety = score.categoryScores.find(cat => cat.category.id === 'safety'); + expect(safety).toBeDefined(); + + const approvalFinding = safety!.findings.find(f => f.message.includes('Social account-changing actions')); + expect(approvalFinding).toBeDefined(); + expect(approvalFinding!.type).toBe('warning'); + expect(approvalFinding!.points).toBe(0); + }); }); describe('Scoring Edge Cases', () => {