diff --git a/.opencode/plans/1775540058831-quick-planet.md b/.opencode/plans/1775540058831-quick-planet.md index af2ab29..a53e127 100644 --- a/.opencode/plans/1775540058831-quick-planet.md +++ b/.opencode/plans/1775540058831-quick-planet.md @@ -1,5 +1,5 @@ --- -model: mimo-v2-pro +model: accounts/fireworks/routers/kimi-k2p5-turbo --- # Plan: `ask262Debug` Module for engine262 @@ -8,30 +8,49 @@ model: mimo-v2-pro Runtime execution tracker that records which ECMAScript spec sections are entered during execution. The Babel transform auto-injects `ask262Debug.mark()` at the start of every function body with URL comments — developer writes nothing. All metadata (sectionId, file, line) resolved at build time. +**Primary Use Case:** AI analysis of execution flow mapped to ECMAScript spec sections for understanding JavaScript engine behavior. + ## Files to Change | File | Action | |------|--------| -| `engine262/src/ask262-debug.mts` | **CREATE** — the module | -| `engine262/src/index.mts` | **MODIFY** — add re-export | -| `engine262/tsconfig.base.json` | **MODIFY** — add `#self/ask262Debug` path | +| `engine262/src/ask262-debug.mts` | **CREATE** — the module with deduplication logic | +| `engine262/src/index.mts` | **MODIFY** — add named re-export | +| `engine262/tsconfig.base.json` | **MODIFY** — add `#self/ask262Debug` path mapping | | `engine262/package.json` | **MODIFY** — add `#self/ask262Debug` import | -| `engine262/scripts/rollup.config.mts` | **MODIFY** — extend `resolve-self` plugin | -| `engine262/scripts/transform.mts` | **MODIFY** — auto-inject `mark()` into function bodies | +| `engine262/scripts/rollup.config.mts` | **MODIFY** — add resolve-self plugin to main build, extend for subpaths | +| `engine262/scripts/transform.mts` | **MODIFY** — auto-inject `mark()` and import | +| `engine262/scripts/verify-ask262-debug.mts` | **CREATE** — post-build verification script | -## 1. `scripts/transform.mts` — Enhance the Babel plugin +## 1. `scripts/transform.mts` — Enhance the Babel Plugin -### 1a. Refactor `addSectionFromComments` to also inject `mark()` into body - -Current behavior: sets `FuncName.section = 'url'` after the declaration. -New behavior: also injects `ask262Debug.mark(["sec-..."], "file.mts", lineNumber)` as the first statement inside the function body. +### 1a. Extend State Interface ```typescript -function addSectionFromComments(path, getName) { +type NeededNames = 'Completion' | 'AbruptCompletion' | 'Assert' | 'Call' | + 'IteratorClose' | 'AsyncIteratorClose' | 'Value' | + 'skipDebugger' | 'ask262Debug'; // NEW + +interface State extends PluginPass { + needed: Partial>; + fileRelativePath?: string; // NEW: captured at Program enter +} +``` + +### 1b. Refactor `addSectionFromComments` + +```typescript +function addSectionFromComments( + path: NodePath | NodePath | + NodePath | NodePath | NodePath, + state: State, + getName: () => string +) { if (!path.node.leadingComments) return; - const sectionIds = []; + const sectionIds: string[] = []; let url = ''; + for (const c of path.node.leadingComments) { for (const line of c.value.split('\n')) { const matches = line.match(/#(sec-[a-zA-Z0-9._%-]+)/g); @@ -39,110 +58,204 @@ function addSectionFromComments(path, getName) { sectionIds.push(...matches.map(m => m.substring(1))); if (!url) { const section = line.split(' ').find(l => l.includes('#sec')); - url = section.includes('https') ? section : `https://tc39.es/ecma262/${section}`; + if (section) { + url = section.includes('https') ? section : `https://tc39.es/ecma262/${section}`; + } } } } } + if (sectionIds.length === 0) return; const name = getName(); + + // 1. Keep existing .section (backward compat) - uses first URL + if (url) { + path.insertAfter(template.ast(`${name}.section = '${url}';`)); + } - // 1. Keep existing .section (backward compat) - path.insertAfter(template.ast(`${name}.section = '${url}';`)); + // 2. Determine if we can inject mark() + let body: t.BlockStatement | null = null; + + if (path.isFunctionDeclaration()) { + body = path.node.body; + } else if (path.isClassMethod() || path.isObjectMethod()) { + body = path.node.body; + } else if (path.isVariableDeclaration()) { + const init = (path.get('declarations.0') as NodePath).get('init'); + if (init.isFunctionExpression()) { + body = init.node.body; + } else if (init.isArrowFunctionExpression()) { + body = init.node.body.type === 'BlockStatement' ? init.node.body : null; + } + } else if (path.isExportNamedDeclaration()) { + const declaration = path.node.declaration; + if (declaration?.type === 'FunctionDeclaration') { + body = declaration.body; + } + } - // 2. Inject mark() at start of function body - const body = path.node.body; + // 3. Inject mark() at start of function body (only for block bodies) if (body?.type === 'BlockStatement') { - const relativeFile = getRelativeFilePath(state); const line = path.node.loc?.start.line ?? 0; - const markCall = template.ast( - 'ask262Debug.mark(SECTION_IDS, FILE, LINE);' - .replace('SECTION_IDS', JSON.stringify(sectionIds)) - .replace('FILE', JSON.stringify(relativeFile)) - .replace('LINE', String(line)) - ); + const markCall = template.statement( + `ask262Debug.mark(${JSON.stringify(sectionIds)}, ${JSON.stringify(state.fileRelativePath)}, ${line});` + )(); body.body.unshift(markCall); + state.needed.ask262Debug = true; } } ``` -### 1b. Add visitors for all function-like nodes +### 1c. Add Visitors for All Function-like Nodes ```typescript -FunctionDeclaration(path) { - addSectionFromComments(path, () => path.node.id!.name); -}, -ClassMethod(path) { - addSectionFromComments(path, () => (path.node.key as t.Identifier).name); -}, -ObjectMethod(path) { - addSectionFromComments(path, () => (path.node.key as t.Identifier).name); -}, -// Existing visitors (VariableDeclaration, ExportNamedDeclaration) unchanged +return { + visitor: { + Program: { + enter(path, state) { + state.needed = {}; + // Capture relative file path from state.filename + const absolutePath = state.filename || ''; + state.fileRelativePath = absolutePath.replace(/.*\/(src\/)/, '$1').replace(/\.mts$/, '.mts') || 'unknown.mts'; + }, + exit(path, state) { + // Existing imports... + if (state.needed.ask262Debug) { + path.unshiftContainer('body', template.ast(`import ask262Debug from '#self/ask262Debug';`)); + } + }, + }, + FunctionDeclaration(path, state) { + addSectionFromComments(path, state, () => path.node.id!.name); + }, + VariableDeclaration(path, state) { + const init = path.get('declarations.0.init'); + if (init.isFunctionExpression() || init.isArrowFunctionExpression()) { + const id = path.node.declarations[0].id as t.Identifier; + addSectionFromComments(path, state, () => id.name); + } + }, + ExportNamedDeclaration(path, state) { + const declaration = path.node.declaration; + if (declaration?.type === 'FunctionDeclaration') { + addSectionFromComments(path, state, () => declaration.id!.name); + } + }, + ClassMethod(path, state) { + const key = path.node.key; + if (key.type === 'Identifier') { + addSectionFromComments(path, state, () => key.name); + } + }, + ObjectMethod(path, state) { + const key = path.node.key; + if (key.type === 'Identifier') { + addSectionFromComments(path, state, () => key.name); + } + }, + // ... rest of existing visitors (CallExpression, ThrowStatement, etc.) unchanged + }, +}; ``` -### 1c. Multi-section support +### 1d. Arrow Function Special Handling -Functions with multiple URL comments get all section IDs as an array: +The updated `addSectionFromComments` handles this automatically: +- Arrow with block body `() => { ... }`: `.section` + `mark()` injected +- Arrow with expression body `() => expr`: `.section` only, no `mark()` -```typescript -/** https://tc39.es/ecma262/#sec-array.prototype.every */ -/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.every */ -function* ArrayProto_every(args) { ... } +## 2. `engine262/package.json` — Add Subpath Import -// Transform injects: -function* ArrayProto_every(args) { - ask262Debug.mark(["sec-array.prototype.every", "sec-%typedarray%.prototype.every"], "intrinsics/ArrayPrototypeShared.mts", 173); - ... +```json +{ + "imports": { + "#self": "./lib/engine262.mjs", + "#self/ask262Debug": "./src/ask262-debug.mts" + } } ``` -## 2. `engine262/package.json` — Add subpath import +## 3. `engine262/tsconfig.base.json` — Add Path Mapping ```json -"#self/ask262Debug": "./src/ask262-debug.mts" +{ + "compilerOptions": { + "paths": { + "#self": ["./lib/engine262.mjs"], + "#self/ask262Debug": ["./src/ask262-debug.mts"] + } + } +} ``` -## 3. `engine262/tsconfig.base.json` — Add path mapping +## 4. `engine262/scripts/rollup.config.mts` — Add resolve-self to Main Build -```json -"#self/ask262Debug": ["./src/ask262-debug.mts"] -``` - -## 4. `scripts/rollup.config.mts` — Extend resolve-self plugin - -Change from exact `#self` match to prefix `#self/` match in both builds: +### 4a. Extend Existing resolve-self Plugin ```typescript -resolveId(source, _importer, _options) { - if (source === '#self' || source.startsWith('#self/')) { - return { id: './engine262.mjs' } as any; // inspector build - } - return undefined; -}, +// Inspector build (existing plugin, modified) +{ + name: 'resolve-self', + resolveId(source, _importer, _options) { + if (source === '#self') { + return { id: './engine262.mjs' }; + } + if (source.startsWith('#self/')) { + // For inspector build, resolve subpaths to bundled engine262 + return { id: './engine262.mjs' }; // All #self/* resolve to main bundle + } + return undefined; + }, +} ``` -Add similar plugin to main engine262 build (which currently lacks one): +### 4b. Add to Main Engine262 Build ```typescript { - name: 'resolve-self', - resolveId(source) { - if (source === '#self' || source.startsWith('#self/')) return { id: '#self' }; - }, -}, + input: './src/index.mts', + plugins: [ + importUnicodeLib(), + (json.default || json)({ compact: true }), + (commonjs.default || commonjs)(), + nodeResolve({ exportConditions: ['rollup'], extensions: ['.mts'] }), + { + name: 'resolve-self', + resolveId(source) { + if (source === '#self') { + return { id: '#self' }; // Mark as internal, bundled + } + if (source === '#self/ask262Debug') { + return { id: './src/ask262-debug.mts' }; // Will be bundled + } + return undefined; + }, + }, + babel({ + ...babelOptions, + plugins: [ + './scripts/transform.mts', + ['@babel/plugin-proposal-decorators', { 'version': '2023-11' }], + ], + }), + // ... dts plugin + ], + // ... output config +} ``` -## 5. `engine262/src/index.mts` — Add re-export +## 5. `engine262/src/index.mts` — Add Named Re-export ```typescript +// Add to existing exports export { default as ask262Debug, type MarkData } from './ask262-debug.mts'; ``` -## 6. `engine262/src/ask262-debug.mts` — The module +## 6. `engine262/src/ask262-debug.mts` — The Module -Pure data collection. No stack parsing, no `node:` imports, no decorators. +Deduplicated data collection. No stack parsing, no `node:` imports, no decorators. ```typescript export interface MarkData { @@ -152,17 +265,43 @@ export interface MarkData { readonly important: boolean; } +interface MarkKey { + readonly sectionIds: string; + readonly file: string; + readonly line: number; +} + class Ask262Debug { marks: MarkData[] = []; private _importantStack = false; + private _markIndex = new Map(); // key -> index in marks + + private _makeKey(sectionIds: string[], file: string, line: number): string { + return `${sectionIds.join(',')}|${file}|${line}`; + } mark(sectionIds: string[], file: string, line: number) { - this.marks.push({ - sectionIds, + const key = this._makeKey(sectionIds, file, line); + const existingIndex = this._markIndex.get(key); + + if (existingIndex !== undefined) { + // Merge important flag + const existing = this.marks[existingIndex]; + if (this._importantStack && !existing.important) { + (this.marks[existingIndex] as any).important = true; + } + return; + } + + const newMark: MarkData = { + sectionIds: [...sectionIds], // defensive copy fileRelativePath: file, lineNumber: line, important: this._importantStack, - }); + }; + + this._markIndex.set(key, this.marks.length); + this.marks.push(newMark); } startImportant() { @@ -178,47 +317,111 @@ const ask262Debug = new Ask262Debug(); export default ask262Debug; ``` -### Developer writes +### Key Behaviors -Nothing. The transform auto-injects `mark()` into every function body with URL comments. +| Scenario | Behavior | +|----------|----------| +| Duplicate `(sectionIds, file, line)` | Keep one entry, merge `important` flags (OR logic) | +| Generator resumption | Mark on every `.next()` call (may create duplicates if same location) | +| Nested functions | Each entry adds its mark (chronological trace) | -### What the transform produces +## 7. `engine262/scripts/verify-ask262-debug.mts` — Verification Script ```typescript -// Original: -/** https://tc39.es/ecma262/#sec-proxycreate */ -export function ProxyCreate(target, handler) { - // ... +#!/usr/bin/env node +import engine262 from '../lib/engine262.mjs'; + +console.log('=== ask262Debug Verification ===\n'); + +// 1. Check export exists +console.log('1. Checking export...'); +if (!engine262.ask262Debug) { + console.error('FAIL: ask262Debug not exported'); + process.exit(1); +} +console.log(' ✓ ask262Debug exported\n'); + +// 2. Run code that hits known spec sections +console.log('2. Executing test code...'); +const agent = new engine262.Agent(); +const realm = new engine262.ManagedRealm({ agent }); + +realm.evaluateScript(` + // Array.prototype.every - should hit sec-array.prototype.every + [1, 2, 3].every(x => x > 0); + + // Proxy creation - should hit sec-proxycreate or similar + new Proxy({}, {}); +`); + +// 3. Verify marks were captured +console.log('3. Checking marks...'); +const marks = engine262.ask262Debug.marks; +console.log(` Captured ${marks.length} unique marks\n`); + +if (marks.length === 0) { + console.error('FAIL: No marks captured'); + process.exit(1); } -// Transformed: -export function ProxyCreate(target, handler) { - ask262Debug.mark(["sec-proxycreate"], "abstract-ops/proxy-objects.mts", 540); - // ... -} -ProxyCreate.section = 'https://tc39.es/ecma262/#sec-proxycreate'; +// 4. Show sample marks +console.log('4. Sample marks:'); +marks.slice(0, 5).forEach((m, i) => { + console.log(` [${i}] ${m.sectionIds.join(', ')} @ ${m.fileRelativePath}:${m.lineNumber}${m.important ? ' [important]' : ''}`); +}); + +console.log('\n=== All Checks Passed ==='); ``` -## Key Design Decisions +## Key Design Decisions Summary | Decision | Choice | Rationale | |----------|--------|-----------| -| Module name | `#self/ask262Debug` | Follows existing `#self` pattern | -| Export style | Singleton class, default export | `ask262Debug.marks` access | -| `MarkData` type | Named export | Users can type-annotate | -| All metadata | From transform (build time) | No runtime stack parsing, no `node:` imports | -| `mark()` injection | Auto-injected at start of function body | Developer writes nothing | -| Multi-section | Array of section IDs per function | Functions can implement multiple spec sections | -| `markLine()` | Removed | `mark()` covers all cases | -| `.section` | Kept (backward compat) | Used by test262-intrinsics | -| `important` | Stubbed with boolean flag | `startImportant()`/`stopImportant()` | -| Function types | FunctionDeclaration + ClassMethod + ObjectMethod | Covers all URL comment patterns | +| **Use case** | AI execution flow analysis | Map runtime to spec sections | +| **Data structure** | Deduplicated array | Chronological trace with unique entries only | +| **Deduplication key** | `(sectionIds, file, line)` | Same location = same semantic entry | +| **Duplicate handling** | Merge `important` flags (OR) | If any call was important, mark it important | +| **Module name** | `#self/ask262Debug` | Follows existing `#self` pattern | +| **Export style** | Named export from engine262 | `import { ask262Debug } from '#self'` | +| **Bundle strategy** | Included in engine262.mjs | Single artifact, no external dependency | +| **All metadata** | From transform (build time) | No runtime stack parsing, no `node:` imports | +| **`mark()` injection** | Auto-injected at start of block body | Developer writes nothing | +| **Arrow functions** | Block body: `.section` + `mark()`, Expression: `.section` only | Can't inject statement in expression | +| **Multi-section** | Array of section IDs per function | Functions can implement multiple spec sections | +| **`.section`** | Kept (backward compat) | Used by test262-intrinsics | +| **Generators** | Mark on every `.next()` | Simpler, deduplication handles repeats | +| **Nested functions** | Mark all | Chronological trace for AI analysis | +| **`important`** | User-driven `startImportant()`/`stopImportant()` | Manual annotation of interesting sections | +| **Function types** | FunctionDeclaration + ClassMethod + ObjectMethod + VariableDeclaration | Covers all URL comment patterns | +| **Class/Object method keys** | Identifier names only | Simple, covers 99% of cases | +| **File paths** | Relative to `engine262/src/`, `.mts` extension | Maps to source, not compiled output | +| **Line numbers** | 1-indexed from Babel AST | Matches source file line numbers | -## Verification +## Verification Steps -1. `cd engine262 && npx tsc -b .` — types compile -2. `cd engine262 && npm run lint` — no ESLint violations -3. Verify transform output: check that functions with URL comments have `ask262Debug.mark(...)` injected at start of body -4. Verify multi-section: check `ArrayProto_every` gets both section IDs -5. Run `npm run build:engine` — rollup resolves `#self/ask262Debug` correctly -6. Inspect `ask262Debug.marks` after running a test to verify marks are populated with correct data +1. **Type checking**: `cd engine262 && npx tsc -b .` +2. **Linting**: `cd engine262 && npm run lint` +3. **Build**: `cd engine262 && npm run build:engine` +4. **Verification**: `cd engine262 && node scripts/verify-ask262-debug.mts` +5. **Manual inspection**: Check that transformed files have `ask262Debug.mark()` at start of functions with URL comments +6. **Multi-section test**: Find a function with multiple URL comments (e.g., `ArrayProto_every`) and verify all section IDs are in the array + +## Expected Output from Verification + +``` +=== ask262Debug Verification === + +1. Checking export... + ✓ ask262Debug exported + +2. Executing test code... +3. Checking marks... + Captured 47 unique marks + +4. Sample marks: + [0] sec-array.prototype.every @ intrinsics/ArrayPrototypeShared.mts:173 + [1] sec-proxycreate @ abstract-ops/proxy-objects.mts:540 + ... + +=== All Checks Passed === +```