diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 55daeae3..b3e7cf1f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -443,6 +443,9 @@ jobs: - name: Lint run: npm --prefix apps/marketing run lint + - name: Capability parity + run: npm --prefix apps/marketing test + - name: Typecheck run: npm --prefix apps/marketing run typecheck diff --git a/apps/marketing/package.json b/apps/marketing/package.json index 8e86fdc5..15043cfd 100644 --- a/apps/marketing/package.json +++ b/apps/marketing/package.json @@ -9,7 +9,8 @@ "build": "tsc -b && vite build", "preview": "vite preview", "lint": "eslint .", - "typecheck": "tsc -b --noEmit" + "typecheck": "tsc -b --noEmit", + "test": "node --test tests/capability-parity.test.mjs" }, "dependencies": { "clsx": "^2.1.1", diff --git a/apps/marketing/src/App.tsx b/apps/marketing/src/App.tsx index ac2cb3f4..16d7fb40 100644 --- a/apps/marketing/src/App.tsx +++ b/apps/marketing/src/App.tsx @@ -21,7 +21,7 @@ import { useLandingTheme } from '@/hooks/useLandingTheme'; * * The two hero calls to action open full-screen panels over the page rather * than navigating away: "See how it works" runs a scripted walkthrough of a - * real analysis, and "Analyze a Repository" shows how to run PARTHA against + * fictional interface and proposed checks, and "Analyze a Repository" shows how to run PARTHA against * your own code. Closing either returns the reader exactly where they were. */ export function App() { diff --git a/apps/marketing/src/components/DemoModal.tsx b/apps/marketing/src/components/DemoModal.tsx index 98308b3f..9c5fc950 100644 --- a/apps/marketing/src/components/DemoModal.tsx +++ b/apps/marketing/src/components/DemoModal.tsx @@ -70,11 +70,11 @@ export function DemoModal({ onClose }: { onClose: () => void }) { Scripted simulation · sample repository

- See what a PARTHA analysis produces + Explore the review interface and proposed checks

A scripted walkthrough of a made-up sample repository ({SAMPLE_REPO.name}) -- not a live analysis of any - real code. It uses PARTHA's actual finding categories and output shape. + real code. The four checks below are proposed and unimplemented. Categories describe current coverage; all metrics and source spans are fictional.

@@ -102,8 +102,7 @@ export function DemoModal({ onClose }: { onClose: () => void }) { {phase === 'idle' && (

- Run the simulation to watch PARTHA walk through a sample repository and produce Engineering Review - and Repository Insights output. + Run the interface simulation to explore fictional metrics and proposed review checks.

- {CATEGORY_LABELS[finding.category]} + {CATEGORY_LABELS[finding.category]} · Proposed check (not implemented)

{finding.explanation}

diff --git a/apps/marketing/src/data/sampleAnalysis.ts b/apps/marketing/src/data/sampleAnalysis.ts index 7d1c8882..0077face 100644 --- a/apps/marketing/src/data/sampleAnalysis.ts +++ b/apps/marketing/src/data/sampleAnalysis.ts @@ -1,14 +1,6 @@ -/** - * Canned data for the scripted product simulation (#382). - * - * Every category id, severity level, and field name here matches the real - * product's actual Engineering Review / Repository Insights response shape - * (apps/backend/app/schemas -- cross-checked against - * apps/frontend/src/shared/services/api/generated.ts, the generated OpenAPI - * types) and the real category labels - * (apps/backend/app/review/review_service.py's `_CATEGORY_LABELS`). The - * repository, findings, and numbers themselves are entirely made up for a - * fictional sample repo -- nothing here is captured from a real analysis. +/** Fictional interface simulation. Category labels and coverage states mirror + * the current review contract; the explicitly proposed checks are unimplemented. + * Metrics, source spans and repository details are made up, not analyzer output. */ export const SAMPLE_REPO = { @@ -22,7 +14,7 @@ export const SIMULATION_STEPS = [ 'Extracting structural facts (Python, TypeScript)', 'Resolving module and dependency relationships', 'Sealing the ri.v1 snapshot', - 'Running Engineering Review and Repository Insights', + 'Showing proposed review checks and fictional Repository Insights', ] as const; export type ReviewSeverity = 'info' | 'low' | 'medium' | 'high' | 'critical'; @@ -53,6 +45,7 @@ export const CATEGORY_LABELS: Record = { export interface SampleFinding { id: string; + implementation: 'proposed'; title: string; category: ReviewCategoryId; severity: ReviewSeverity; @@ -63,9 +56,11 @@ export interface SampleFinding { endLine: number; } +// These proposed checks are not emitted by the current Engineering Review rules. export const SAMPLE_FINDINGS: SampleFinding[] = [ { id: 'finding-1', + implementation: 'proposed', title: 'Payment adapter imports directly from the checkout domain layer', category: 'architecture_boundaries', severity: 'medium', @@ -79,6 +74,7 @@ export const SAMPLE_FINDINGS: SampleFinding[] = [ }, { id: 'finding-2', + implementation: 'proposed', title: 'Declared dependency pin resolves to a version not requested anywhere', category: 'dependency_declarations', severity: 'low', @@ -91,11 +87,12 @@ export const SAMPLE_FINDINGS: SampleFinding[] = [ }, { id: 'finding-3', + implementation: 'proposed', title: 'Session cookie is issued without an explicit SameSite attribute', category: 'authentication_evidence', severity: 'high', explanation: - 'src/auth/session.py sets the session cookie without a SameSite attribute, so browsers fall back to a permissive default that varies by browser rather than an explicit, reviewable policy.', + 'src/auth/session.py sets the session cookie without a SameSite attribute, so browser defaults apply rather than an explicit, reviewable policy.', remediationGuidance: 'Set SameSite explicitly (Lax or Strict) when issuing the session cookie.', path: 'src/auth/session.py', startLine: 47, @@ -103,6 +100,7 @@ export const SAMPLE_FINDINGS: SampleFinding[] = [ }, { id: 'finding-4', + implementation: 'proposed', title: 'Two modules independently resolve the same order-total calculation', category: 'relationship_resolution', severity: 'info', @@ -124,13 +122,13 @@ export interface SampleCategoryAssessment { } export const SAMPLE_CATEGORIES: SampleCategoryAssessment[] = [ - { id: 'architecture_boundaries', label: CATEGORY_LABELS.architecture_boundaries, state: 'assessed', findingCount: 1, explanation: 'Module boundaries were assessed from resolved import relationships.' }, - { id: 'relationship_resolution', label: CATEGORY_LABELS.relationship_resolution, state: 'assessed', findingCount: 1, explanation: 'Cross-module relationships were assessed from resolved facts.' }, + { id: 'architecture_boundaries', label: CATEGORY_LABELS.architecture_boundaries, state: 'partially_assessed', findingCount: 0, explanation: 'Structural relationships are supported; domain boundary checks are proposed.' }, + { id: 'relationship_resolution', label: CATEGORY_LABELS.relationship_resolution, state: 'assessed', findingCount: 0, explanation: 'Cross-module relationships were assessed from resolved facts.' }, { id: 'source_extraction', label: CATEGORY_LABELS.source_extraction, state: 'assessed', findingCount: 0, explanation: 'No source-extraction diagnostics were raised for this snapshot.' }, - { id: 'dependency_declarations', label: CATEGORY_LABELS.dependency_declarations, state: 'assessed', findingCount: 1, explanation: 'Direct declarations and lockfile pins were assessed for this snapshot.' }, + { id: 'dependency_declarations', label: CATEGORY_LABELS.dependency_declarations, state: 'partially_assessed', findingCount: 0, explanation: 'Dependency inventory is supported; lockfile reconciliation is a proposed check.' }, { id: 'security_vulnerability_scanning', label: CATEGORY_LABELS.security_vulnerability_scanning, state: 'not_assessed', findingCount: 0, explanation: 'Vulnerability scanning is not implemented; this category is not assessed.' }, - { id: 'authentication_evidence', label: CATEGORY_LABELS.authentication_evidence, state: 'assessed', findingCount: 1, explanation: 'The supported Python/FastAPI authentication subgraph was assessed.' }, - { id: 'repository_structure', label: CATEGORY_LABELS.repository_structure, state: 'assessed', findingCount: 0, explanation: 'No repository-structure diagnostics were raised for this snapshot.' }, + { id: 'authentication_evidence', label: CATEGORY_LABELS.authentication_evidence, state: 'partially_assessed', findingCount: 0, explanation: 'Authentication inventory is partial; cookie attribute checks are proposed.' }, + { id: 'repository_structure', label: CATEGORY_LABELS.repository_structure, state: 'partially_assessed', findingCount: 0, explanation: 'No repository-structure diagnostics were raised for this snapshot.' }, { id: 'analysis_integrity', label: CATEGORY_LABELS.analysis_integrity, state: 'assessed', findingCount: 0, explanation: 'The analysis completed with no integrity diagnostics.' }, ]; diff --git a/apps/marketing/tests/capability-parity.test.mjs b/apps/marketing/tests/capability-parity.test.mjs new file mode 100644 index 00000000..4b750519 --- /dev/null +++ b/apps/marketing/tests/capability-parity.test.mjs @@ -0,0 +1,25 @@ +import { URL } from 'node:url'; +import { Buffer } from 'node:buffer'; +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import test from 'node:test'; +import ts from 'typescript'; +const source = readFileSync(new URL('../src/data/sampleAnalysis.ts', import.meta.url), 'utf8'); +const compiled = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.ESNext } }).outputText; +const { SAMPLE_FINDINGS, SAMPLE_CATEGORIES } = await import(`data:text/javascript;base64,${Buffer.from(compiled).toString('base64')}`); +test('unsupported demo checks are explicitly proposed and never counted as analyzer findings', () => { + assert.equal(SAMPLE_FINDINGS.length, 4); + for (const finding of SAMPLE_FINDINGS) assert.equal(finding.implementation, 'proposed'); + for (const category of SAMPLE_CATEGORIES) assert.equal(category.findingCount, 0); + const component = readFileSync(new URL('../src/components/DemoModal.tsx', import.meta.url), 'utf8'); + assert.match(component, /Proposed check \(not implemented\)/); + assert.match(component, /four checks below are proposed and unimplemented/); +}); +test('limited categories retain current backend assessment states', () => { + const backend = readFileSync(new URL('../../backend/app/review/review_service.py', import.meta.url), 'utf8'); + for (const id of ['architecture_boundaries', 'dependency_declarations', 'authentication_evidence', 'repository_structure']) { + assert.equal(SAMPLE_CATEGORIES.find(category => category.id === id).state, 'partially_assessed'); + assert.match(backend, new RegExp(String.raw`"${id}": \([\s\S]{0,100}"partially_assessed"`)); + } + assert.equal(SAMPLE_CATEGORIES.find(category => category.id === 'security_vulnerability_scanning').state, 'not_assessed'); +});