diff --git a/packages/scaffold-core/package.json b/packages/scaffold-core/package.json index fda103e..2baec0e 100644 --- a/packages/scaffold-core/package.json +++ b/packages/scaffold-core/package.json @@ -1,7 +1,7 @@ { "name": "@stackbilt/scaffold-core", "sideEffects": false, - "version": "1.1.0", + "version": "1.2.0", "description": "Zero-dependency scaffold engine core — pattern classification, knowledge, governance, codegen, and materializer", "main": "./dist/index.js", "types": "./dist/index.d.ts", diff --git a/packages/scaffold-core/src/__tests__/oracle-context.test.ts b/packages/scaffold-core/src/__tests__/oracle-context.test.ts new file mode 100644 index 0000000..8fad38c --- /dev/null +++ b/packages/scaffold-core/src/__tests__/oracle-context.test.ts @@ -0,0 +1,45 @@ +import { describe, expect, it } from 'vitest'; +import { buildScaffold, buildOracleContext } from '../index'; + +describe('buildOracleContext', () => { + it('derives all required fields from a LocalScaffoldResult', () => { + const result = buildScaffold('Multi-tenant SaaS API with Stripe billing and JWT auth'); + const ctx = buildOracleContext(result); + + expect(ctx.intention).toBe(result.facts.intention); + expect(ctx.pattern).toBe(result.classification.pattern); + expect(ctx.meta.confidence).toBe(result.classification.confidence); + expect(ctx.meta.tier2Recommended).toBe(result.tier2Recommended); + expect(ctx.traits).toEqual(result.classification.traits); + expect(ctx.governance.threatModel).toBe(result.governance.threatModel); + expect(ctx.governance.adr001).toBe(result.governance.adr001); + expect(ctx.governance.testPlan).toBe(result.governance.testPlan); + expect(ctx.files.length).toBeGreaterThan(0); + expect(ctx.files.every(f => 'path' in f && 'content' in f && 'role' in f)).toBe(true); + }); + + it('maps runtime bindings correctly', () => { + const result = buildScaffold('Durable Object-based real-time collaboration service'); + const ctx = buildOracleContext(result); + + expect(Array.isArray(ctx.runtime.bindings)).toBe(true); + ctx.runtime.bindings.forEach(b => { + expect(b).toHaveProperty('type'); + expect(b).toHaveProperty('name'); + expect(b).toHaveProperty('binding'); + }); + }); + + it('caps topThreats at 5', () => { + const result = buildScaffold('Secure payment processing API with PCI compliance'); + const ctx = buildOracleContext(result); + expect(ctx.knowledge.topThreats.length).toBeLessThanOrEqual(5); + }); + + it('sets adr002 to null when no compliance domains detected', () => { + const result = buildScaffold('Simple scheduled cron worker to clean up old records'); + const ctx = buildOracleContext(result); + // adr002 only present for compliance-domain patterns; null otherwise + expect(ctx.governance.adr002 === null || typeof ctx.governance.adr002 === 'string').toBe(true); + }); +}); diff --git a/packages/scaffold-core/src/index.ts b/packages/scaffold-core/src/index.ts index 602385e..abed285 100644 --- a/packages/scaffold-core/src/index.ts +++ b/packages/scaffold-core/src/index.ts @@ -35,6 +35,7 @@ export type { // Top-level types LocalScaffoldResult, ScaffoldOptions, + OracleContext, } from './types'; // ============================================================================ @@ -51,7 +52,7 @@ export { materializeScaffold } from './materializer/index'; // Orchestrator // ============================================================================ -import type { LocalScaffoldResult, ScaffoldOptions } from './types'; +import type { LocalScaffoldResult, OracleContext, ScaffoldOptions } from './types'; import { classify } from './classify/index'; import { getKnowledge } from './knowledge/index'; import { buildGovernance } from './governance/index'; @@ -127,3 +128,52 @@ export function buildScaffold( tier2Recommended: classification.confidence < 0.6, }; } + +/** + * Derive oracle context from a LocalScaffoldResult for use by an LLM polish pass. + * + * All fields are derived from the existing result — no additional inference or + * network calls required. Consumers pass this to the oracle instead of the + * old promptContext field that was stripped when migrating from the local shim. + * + * @see stackbilt-web oracle.ts + * @see charter#224 + */ +export function buildOracleContext(result: LocalScaffoldResult): OracleContext { + const { classification, knowledge, governance, facts, files, tier2Recommended } = result; + return { + intention: facts.intention, + pattern: classification.pattern, + meta: { + confidence: classification.confidence, + tier2Recommended, + testingLevel: classification.qualityProfile.testingLevel, + complianceDomains: classification.qualityProfile.complianceDomains, + observability: classification.qualityProfile.observability, + authentication: classification.qualityProfile.authentication, + rateLimiting: classification.qualityProfile.rateLimiting, + }, + traits: classification.traits, + runtime: { + bindings: facts.bindings.map(b => ({ type: b.type, name: b.name, binding: b.binding })), + piiHandling: classification.qualityProfile.piiHandling, + }, + governance: { + threatModel: governance.threatModel, + adr001: governance.adr001, + adr002: governance.adr002 ?? null, + testPlan: governance.testPlan, + }, + knowledge: { + adrContext: knowledge.adrContext, + adrDecision: knowledge.adrDecision, + topThreats: knowledge.threats.slice(0, 5).map(t => ({ + id: t.id, + description: t.description, + mitigation: t.mitigation, + severity: t.severity, + })), + }, + files: files.map(f => ({ path: f.path, content: f.content, role: f.role })), + }; +} diff --git a/packages/scaffold-core/src/types.ts b/packages/scaffold-core/src/types.ts index 467e8de..bab493a 100644 --- a/packages/scaffold-core/src/types.ts +++ b/packages/scaffold-core/src/types.ts @@ -143,3 +143,44 @@ export interface ScaffoldOptions { projectName?: string; oracle?: boolean; } + +// ============================================================================ +// Oracle context — structured input for LLM polish pass +// ============================================================================ + +/** + * Structured context derived from LocalScaffoldResult for use by an LLM + * oracle pass. All fields are derivable without a TarotScript round-trip. + * + * Build via: buildOracleContext(result: LocalScaffoldResult) + */ +export interface OracleContext { + intention: string; + pattern: string; + meta: { + confidence: number; + tier2Recommended: boolean; + testingLevel: string; + complianceDomains: string[]; + observability: boolean; + authentication: boolean; + rateLimiting: boolean; + }; + traits: string[]; + runtime: { + bindings: Array<{ type: string; name: string; binding: string }>; + piiHandling: boolean; + }; + governance: { + threatModel: string; + adr001: string; + adr002: string | null; + testPlan: string; + }; + knowledge: { + adrContext: string; + adrDecision: string; + topThreats: Array<{ id: string; description: string; mitigation: string; severity: string }>; + }; + files: Array<{ path: string; content: string; role: string }>; +}