Skip to content

Commit a04a0ff

Browse files
kvzclaude
andauthored
feat(verify): Python verification + modular architecture + verified headers (#496)
* feat(verify): add Python verification support (15/17 passing) - Add PYTHON_MODULES mapping for string and math module functions - Add convertJsLineToPython with JS→Python syntax conversions: - true/false/null/undefined → True/False/None - Infinity/NaN → float('inf')/float('nan') - .length → len() - Add jsToPython to generate runnable Python code with imports - Add Python verification logic in verifyFunction - Normalize Python float output (.0 stripping) - Bump CACHE_VERSION to 5 Results: 15/17 Python functions pass verification against Python 3.12 Remaining failures: - capwords: JS preserves double spaces, Python collapses them - printable: .length on function result not converted 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(verify): refactor to modular architecture with verified header support Architecture changes: - Split verify.ts (1000+ lines) into modular structure: - scripts/verify/types.ts - Shared interfaces - scripts/verify/cache.ts - Caching utilities - scripts/verify/docker.ts - Docker utilities - scripts/verify/runner.ts - JS execution - scripts/verify/parser.ts - Function file parsing - scripts/verify/languages/index.ts - Language registry - scripts/verify/languages/php.ts - PHP handler - scripts/verify/languages/python.ts - Python handler - Main verify.ts is now ~250 lines CLI entry point New features: - Add `verified: X.Y` header support in function files - Functions with header are verified in CI mode (default) - Functions without header are skipped unless --all is used - Add --summary flag to show verification status counts - Add --all flag to include unverified functions CI integration: - Default mode only runs functions with verified: header - Exit code 1 only for verified function failures - Unverified functions shown as "skipped" count Demo verified headers added: - src/php/strings/trim.js: verified: 8.3 - src/python/math/factorial.js: verified: 3.12 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 3ff59d0 commit a04a0ff

File tree

11 files changed

+1259
-746
lines changed

11 files changed

+1259
-746
lines changed

scripts/verify.ts

Lines changed: 207 additions & 746 deletions
Large diffs are not rendered by default.

scripts/verify/cache.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/**
2+
* Caching utilities for verification results
3+
*/
4+
5+
import { createHash } from 'node:crypto'
6+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
7+
import { join } from 'node:path'
8+
import type { CacheEntry } from './types.ts'
9+
10+
export const CACHE_VERSION = 6
11+
12+
/**
13+
* Calculate hash of file, its dependencies, and verify script
14+
* Including verify script ensures cache invalidation when translation logic changes
15+
*/
16+
export function calculateHash(filePath: string, deps: string[], srcDir: string, verifyScriptPath: string): string {
17+
const hash = createHash('sha256')
18+
19+
// Include verify script content so translation changes invalidate cache
20+
hash.update(readFileSync(verifyScriptPath))
21+
hash.update(readFileSync(filePath))
22+
23+
for (const dep of deps) {
24+
const depPath = join(srcDir, dep)
25+
if (existsSync(depPath)) {
26+
hash.update(readFileSync(depPath))
27+
}
28+
}
29+
30+
return hash.digest('hex').slice(0, 16)
31+
}
32+
33+
/**
34+
* Load cached verification results for a function
35+
*/
36+
export function loadCache(funcPath: string, cacheDir: string): CacheEntry | null {
37+
const cacheFile = join(cacheDir, funcPath.replace(/\//g, '_') + '.json')
38+
if (!existsSync(cacheFile)) {
39+
return null
40+
}
41+
42+
try {
43+
const entry = JSON.parse(readFileSync(cacheFile, 'utf8')) as CacheEntry
44+
if (entry.version !== CACHE_VERSION) {
45+
return null
46+
}
47+
return entry
48+
} catch {
49+
return null
50+
}
51+
}
52+
53+
/**
54+
* Save verification results to cache
55+
*/
56+
export function saveCache(funcPath: string, entry: CacheEntry, cacheDir: string): void {
57+
mkdirSync(cacheDir, { recursive: true })
58+
const cacheFile = join(cacheDir, funcPath.replace(/\//g, '_') + '.json')
59+
writeFileSync(cacheFile, JSON.stringify(entry, null, 2))
60+
}

scripts/verify/docker.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* Docker utilities for running code in language runtimes
3+
*/
4+
5+
import { execSync, spawnSync } from 'node:child_process'
6+
7+
/**
8+
* Check if Docker image exists, pull if not
9+
*/
10+
export function ensureDockerImage(image: string): boolean {
11+
try {
12+
execSync(`docker image inspect ${image}`, { stdio: 'pipe' })
13+
return true
14+
} catch {
15+
console.log(` Pulling ${image}...`)
16+
try {
17+
execSync(`docker pull ${image}`, { stdio: 'pipe' })
18+
return true
19+
} catch {
20+
return false
21+
}
22+
}
23+
}
24+
25+
/**
26+
* Check if Docker is available
27+
*/
28+
export function checkDockerAvailable(): boolean {
29+
try {
30+
execSync('docker --version', { stdio: 'pipe' })
31+
return true
32+
} catch {
33+
return false
34+
}
35+
}
36+
37+
export interface DockerRunResult {
38+
success: boolean
39+
output: string
40+
error?: string
41+
}
42+
43+
/**
44+
* Run code in a Docker container
45+
*/
46+
export function runInDocker(
47+
image: string,
48+
cmd: string[],
49+
options: { mountRepo?: boolean; repoPath?: string; timeout?: number } = {},
50+
): DockerRunResult {
51+
const { mountRepo = false, repoPath, timeout = 10000 } = options
52+
53+
try {
54+
const dockerArgs = ['run', '--rm', '-i']
55+
if (mountRepo && repoPath) {
56+
dockerArgs.push('-v', `${repoPath}:/work`, '-w', '/work')
57+
}
58+
59+
const result = spawnSync('docker', [...dockerArgs, image, ...cmd], {
60+
encoding: 'utf8',
61+
timeout,
62+
})
63+
64+
if (result.error) {
65+
return { success: false, output: '', error: result.error.message }
66+
}
67+
68+
if (result.status !== 0 || (result.stderr && result.stderr.trim())) {
69+
return {
70+
success: false,
71+
output: result.stdout || '',
72+
error: result.stderr?.trim() || 'Unknown error',
73+
}
74+
}
75+
76+
return { success: true, output: result.stdout }
77+
} catch (e) {
78+
return { success: false, output: '', error: String(e) }
79+
}
80+
}

scripts/verify/languages/index.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* Language handler registry
3+
*/
4+
5+
import type { LanguageHandler } from '../types.ts'
6+
import { phpHandler } from './php.ts'
7+
import { pythonHandler } from './python.ts'
8+
9+
const handlers: Record<string, LanguageHandler> = {
10+
php: phpHandler,
11+
python: pythonHandler,
12+
}
13+
14+
/**
15+
* Get the language handler for a given language
16+
* Returns undefined if the language is not supported yet
17+
*/
18+
export function getLanguageHandler(language: string): LanguageHandler | undefined {
19+
return handlers[language]
20+
}
21+
22+
/**
23+
* Check if a language has verification support
24+
*/
25+
export function isLanguageSupported(language: string): boolean {
26+
return language in handlers
27+
}
28+
29+
/**
30+
* Get all supported languages
31+
*/
32+
export function getSupportedLanguages(): string[] {
33+
return Object.keys(handlers)
34+
}

0 commit comments

Comments
 (0)