Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/scaffold-core/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@stackbilt/scaffold-core",
"sideEffects": false,
"version": "1.7.0",
"version": "1.7.1",
"description": "Zero-dependency scaffold engine core — pattern classification, knowledge, governance, codegen, and materializer",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
Expand Down
34 changes: 34 additions & 0 deletions packages/scaffold-core/src/__tests__/classify.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,40 @@ describe('classification quality and confidence', () => {
});
});

// ─── Stripe keyword dominance regression (#424) ─────────────────────────────

describe('scaffold_classify — Stripe keyword does not dominate SaaS intent (#424)', () => {
it('SaaS billing dashboard with Stripe integration classifies as workers-saas', () => {
const result = classify(
'Build me a SaaS billing dashboard with Stripe integration, user management, and usage analytics',
);
expect(result.pattern).toBe('workers-saas');
expect(result.traits).toContain('jwt-auth');
expect(result.traits).not.toContain('hmac-stripe');
});

it('explicit Stripe webhook still classifies as stripe-webhook pattern', () => {
const result = classify('Build a Stripe webhook handler with HMAC verification and event routing');
expect(result.traits).toContain('hmac-stripe');
expect(result.confidence).toBeGreaterThan(0.5);
});

it('billing alone without payment keywords does not fire Payment Signal', () => {
const result = classify('Build a billing management dashboard with invoices and PDF export');
expect(result.traits).not.toContain('hmac-stripe');
});

it('stripe + billing together still classifies as stripe-webhook', () => {
const result = classify('Stripe billing webhook for subscription lifecycle events');
expect(result.traits).toContain('hmac-stripe');
});

it('dashboard keyword contributes to SaaS Signal score', () => {
const result = classify('Admin dashboard with user management, roles, and audit logs');
expect(result.pattern).toBe('workers-saas');
});
});

// ─── Rust/WASM classification tests (charter#230) ────────────────────────────

describe('Rust/WASM pattern classification', () => {
Expand Down
4 changes: 2 additions & 2 deletions packages/scaffold-core/src/__tests__/package.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ describe('@stackbilt/scaffold-core package metadata', () => {
expect(pkg.name).toBe('@stackbilt/scaffold-core');
});

it('version is 1.7.0', () => {
expect(pkg.version).toBe('1.7.0');
it('version is 1.7.1', () => {
expect(pkg.version).toBe('1.7.1');
});

it('license is Apache-2.0', () => {
Expand Down
20 changes: 14 additions & 6 deletions packages/scaffold-core/src/classify/patterns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,12 @@ export const SCORED_PATTERNS: ScoredPatternDef[] = [
signal: 'Payment Signal',
priority: 100,
score: (text: string) => {
const stripeHits = keywordScore(text, ['stripe', 'billing', 'subscription', 'checkout', 'payment', 'payments']);
if (stripeHits === 0 && text.includes('webhook')) return 0;
return stripeHits + (text.includes('webhook') ? 1 : 0);
// "billing" is intentionally excluded here — it appears in architectural contexts
// ("billing dashboard", "billing management") that are not Stripe webhook handlers.
// Require at least one explicit payment-action keyword before scoring billing terms.
const stripeHits = keywordScore(text, ['stripe', 'subscription', 'checkout', 'payment', 'payments']);
if (stripeHits === 0) return 0;
return stripeHits + (text.includes('billing') ? 1 : 0) + (text.includes('webhook') ? 1 : 0);
},
traitMap: {
route_shape: 'post-handler',
Expand All @@ -104,7 +107,9 @@ export const SCORED_PATTERNS: ScoredPatternDef[] = [
signal: 'Webhook Signal',
priority: 95,
score: (text: string) => {
if (keywordScore(text, ['stripe', 'billing', 'subscription', 'checkout', 'payment', 'payments']) >= 1) return 0;
// Exclude only if explicit payment-action keywords are present (not "billing" alone —
// see Payment Signal comment; "billing webhook" without Stripe is generic).
if (keywordScore(text, ['stripe', 'subscription', 'checkout', 'payment', 'payments']) >= 1) return 0;
return keywordScore(text, ['webhook', 'signature verification', 'x-hub-signature', 'github webhook', 'slack webhook', 'twilio webhook']);
},
traitMap: {
Expand All @@ -124,11 +129,14 @@ export const SCORED_PATTERNS: ScoredPatternDef[] = [
name: 'workers-saas' as PatternName,
status: 'ACTIVE',
category: 'COMPUTE',
keywords: ['saas', 'tenant', 'multi-tenant', 'org', 'workspace'],
keywords: ['saas', 'tenant', 'multi-tenant', 'org', 'workspace', 'dashboard', 'analytics', 'user management'],
traits: ['rest', 'jwt-auth', 'resource-router', 'fetch-trigger'],
signal: 'SaaS Signal',
priority: 90,
score: (text: string) => keywordScore(text, ['saas', 'tenant', 'multi-tenant', 'org', 'workspace']),
score: (text: string) => keywordScore(
text,
['saas', 'tenant', 'multi-tenant', 'org', 'workspace', 'dashboard', 'analytics', 'user management'],
),
traitMap: {
route_shape: 'rest',
verification: 'jwt-auth',
Expand Down