diff --git a/.opencode/plans/1775540058831-quick-planet.md b/.opencode/plans/1775540058831-quick-planet.md index a53e127..18b39c3 100644 --- a/.opencode/plans/1775540058831-quick-planet.md +++ b/.opencode/plans/1775540058831-quick-planet.md @@ -16,9 +16,6 @@ Runtime execution tracker that records which ECMAScript spec sections are entere |------|--------| | `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** — 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 | @@ -26,6 +23,8 @@ Runtime execution tracker that records which ECMAScript spec sections are entere ### 1a. Extend State Interface +Add `ask262Debug` to the `NeededNames` type and extend `State` interface: + ```typescript type NeededNames = 'Completion' | 'AbruptCompletion' | 'Assert' | 'Call' | 'IteratorClose' | 'AsyncIteratorClose' | 'Value' | @@ -37,14 +36,29 @@ interface State extends PluginPass { } ``` -### 1b. Refactor `addSectionFromComments` +### 1b. Add Import Creator Function + +Add alongside existing `createImport*` functions: + +```typescript +function createImportAsk262Debug() { + return template.ast(` + import { ask262Debug } from "#self"; + `); +} +``` + +### 1c. Refactor `addSectionFromComments` + +The current function (lines 101-133) handles only single URLs. Update to capture multiple section IDs and inject `mark()`: ```typescript function addSectionFromComments( path: NodePath | NodePath | NodePath | NodePath | NodePath, state: State, - getName: () => string + getName: () => string, + getBody: () => t.BlockStatement | null ) { if (!path.node.leadingComments) return; @@ -58,8 +72,9 @@ function addSectionFromComments( sectionIds.push(...matches.map(m => m.substring(1))); if (!url) { const section = line.split(' ').find(l => l.includes('#sec')); - if (section) { - url = section.includes('https') ? section : `https://tc39.es/ecma262/${section}`; + // Only capture external URLs (skip local/fragment references) + if (section?.startsWith('https://')) { + url = section; } } } @@ -71,30 +86,13 @@ function addSectionFromComments( const name = getName(); // 1. Keep existing .section (backward compat) - uses first URL - if (url) { + // Only applies to named functions + if (name && url) { 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. Get function body via callback (node-type check done by caller) + const body = getBody(); // 3. Inject mark() at start of function body (only for block bodies) if (body?.type === 'BlockStatement') { @@ -108,7 +106,9 @@ function addSectionFromComments( } ``` -### 1c. Add Visitors for All Function-like Nodes +### 1d. Update Visitors + +Update existing visitors to use new `addSectionFromComments` signature, and add new visitors for ClassMethod and ObjectMethod: ```typescript return { @@ -121,38 +121,68 @@ return { state.fileRelativePath = absolutePath.replace(/.*\/(src\/)/, '$1').replace(/\.mts$/, '.mts') || 'unknown.mts'; }, exit(path, state) { - // Existing imports... + // ... existing imports (skipDebugger, Completion, etc.) ... + // NEW: Add ask262Debug import if needed if (state.needed.ask262Debug) { - path.unshiftContainer('body', template.ast(`import ask262Debug from '#self/ask262Debug';`)); + path.unshiftContainer('body', createImportAsk262Debug()); } }, }, FunctionDeclaration(path, state) { - addSectionFromComments(path, state, () => path.node.id!.name); + addSectionFromComments( + path, state, + () => path.node.id!.name, + () => path.node.body + ); }, VariableDeclaration(path, state) { const init = path.get('declarations.0.init'); - if (init.isFunctionExpression() || init.isArrowFunctionExpression()) { + if (init.isFunctionExpression()) { const id = path.node.declarations[0].id as t.Identifier; - addSectionFromComments(path, state, () => id.name); + addSectionFromComments( + path, state, + () => id.name, + () => init.node.body + ); + } else if (init.isArrowFunctionExpression()) { + const id = path.node.declarations[0].id as t.Identifier; + addSectionFromComments( + path, state, + () => id.name, + () => init.node.body.type === 'BlockStatement' ? init.node.body : null + ); } }, ExportNamedDeclaration(path, state) { const declaration = path.node.declaration; if (declaration?.type === 'FunctionDeclaration') { - addSectionFromComments(path, state, () => declaration.id!.name); + addSectionFromComments( + path, state, + () => declaration.id!.name, + () => declaration.body + ); } }, + // NEW: Add ClassMethod visitor ClassMethod(path, state) { const key = path.node.key; if (key.type === 'Identifier') { - addSectionFromComments(path, state, () => key.name); + addSectionFromComments( + path, state, + () => key.name, + () => path.node.body + ); } }, + // NEW: Add ObjectMethod visitor ObjectMethod(path, state) { const key = path.node.key; if (key.type === 'Identifier') { - addSectionFromComments(path, state, () => key.name); + addSectionFromComments( + path, state, + () => key.name, + () => path.node.body + ); } }, // ... rest of existing visitors (CallExpression, ThrowStatement, etc.) unchanged @@ -160,100 +190,22 @@ return { }; ``` -### 1d. Arrow Function Special Handling +### 1e. Arrow Function Special Handling The updated `addSectionFromComments` handles this automatically: - Arrow with block body `() => { ... }`: `.section` + `mark()` injected - Arrow with expression body `() => expr`: `.section` only, no `mark()` -## 2. `engine262/package.json` — Add Subpath Import +## 2. `engine262/src/index.mts` — Add Named Re-export -```json -{ - "imports": { - "#self": "./lib/engine262.mjs", - "#self/ask262Debug": "./src/ask262-debug.mts" - } -} -``` - -## 3. `engine262/tsconfig.base.json` — Add Path Mapping - -```json -{ - "compilerOptions": { - "paths": { - "#self": ["./lib/engine262.mjs"], - "#self/ask262Debug": ["./src/ask262-debug.mts"] - } - } -} -``` - -## 4. `engine262/scripts/rollup.config.mts` — Add resolve-self to Main Build - -### 4a. Extend Existing resolve-self Plugin +Add the re-export alongside existing exports (around line 20): ```typescript -// 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 to existing exports from './helpers.mts' or create new export section +export { ask262Debug, type MarkData } from './ask262-debug.mts'; ``` -### 4b. Add to Main Engine262 Build - -```typescript -{ - 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 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 +## 3. `engine262/src/ask262-debug.mts` — The Module Deduplicated data collection. No stack parsing, no `node:` imports, no decorators. @@ -313,8 +265,7 @@ class Ask262Debug { } } -const ask262Debug = new Ask262Debug(); -export default ask262Debug; +export const ask262Debug = new Ask262Debug(); ``` ### Key Behaviors @@ -325,17 +276,17 @@ export default ask262Debug; | Generator resumption | Mark on every `.next()` call (may create duplicates if same location) | | Nested functions | Each entry adds its mark (chronological trace) | -## 7. `engine262/scripts/verify-ask262-debug.mts` — Verification Script +## 4. `engine262/scripts/verify-ask262-debug.mts` — Verification Script ```typescript #!/usr/bin/env node -import engine262 from '../lib/engine262.mjs'; +import { ask262Debug, Agent, ManagedRealm } from '#self'; console.log('=== ask262Debug Verification ===\n'); // 1. Check export exists console.log('1. Checking export...'); -if (!engine262.ask262Debug) { +if (!ask262Debug) { console.error('FAIL: ask262Debug not exported'); process.exit(1); } @@ -343,8 +294,8 @@ 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 }); +const agent = new Agent(); +const realm = new ManagedRealm({ agent }); realm.evaluateScript(` // Array.prototype.every - should hit sec-array.prototype.every @@ -354,9 +305,9 @@ realm.evaluateScript(` new Proxy({}, {}); `); -// 3. Verify marks were captured +// 4. Verify marks were captured console.log('3. Checking marks...'); -const marks = engine262.ask262Debug.marks; +const marks = ask262Debug.marks; console.log(` Captured ${marks.length} unique marks\n`); if (marks.length === 0) { @@ -364,13 +315,13 @@ if (marks.length === 0) { process.exit(1); } -// 4. Show sample marks +// 5. 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 ==='); +console.log('=== All Checks Passed ==='); ``` ## Key Design Decisions Summary @@ -378,12 +329,11 @@ console.log('\n=== All Checks Passed ==='); | Decision | Choice | Rationale | |----------|--------|-----------| | **Use case** | AI execution flow analysis | Map runtime to spec sections | +| **Import pattern** | `import { ask262Debug } from '#self'` | Uses existing pattern (315+ occurrences) | +| **Bundle strategy** | Included in engine262.mjs | Single artifact, no config changes needed | | **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 | @@ -397,14 +347,26 @@ console.log('\n=== All Checks Passed ==='); | **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 Steps +## Implementation Checklist -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 +1. [ ] **Create** `engine262/src/ask262-debug.mts` with the module code +2. [ ] **Modify** `engine262/src/index.mts` - add re-export +3. [ ] **Modify** `engine262/scripts/transform.mts`: + - [ ] Add `ask262Debug` to `NeededNames` type + - [ ] Extend `State` interface with `fileRelativePath` + - [ ] Add `createImportAsk262Debug()` function + - [ ] Update `addSectionFromComments()` signature and logic + - [ ] Update `Program.exit` to inject ask262Debug import + - [ ] Update `FunctionDeclaration` visitor + - [ ] Update `VariableDeclaration` visitor + - [ ] Update `ExportNamedDeclaration` visitor + - [ ] Add `ClassMethod` visitor + - [ ] Add `ObjectMethod` visitor +4. [ ] **Create** `engine262/scripts/verify-ask262-debug.mts` +5. [ ] **Type checking**: `cd engine262 && npx tsc -b .` +6. [ ] **Linting**: `cd engine262 && npm run lint` +7. [ ] **Build**: `cd engine262 && npm run build:engine` +8. [ ] **Verification**: `cd engine262 && node --enable-source-maps --disable-warning=ExperimentalWarning --experimental-strip-types scripts/verify-ask262-debug.mts` ## Expected Output from Verification @@ -425,3 +387,21 @@ console.log('\n=== All Checks Passed ==='); === All Checks Passed === ``` + +## Troubleshooting + +### Issue: No marks captured after build +**Cause:** Transform not injecting `mark()` calls +**Fix:** Check that functions have URL comments with `#sec-` patterns + +### Issue: Build fails with "Cannot find module '#self'" +**Cause:** Rollup not resolving the import +**Fix:** Ensure babel transform runs before resolution in rollup config + +### Issue: Line numbers are off +**Cause:** Babel source maps not configured +**Fix:** Check that `sourceMap: true` in babelOptions and `loc` is preserved + +### Issue: Duplicate marks for same function +**Cause:** Generator resumption or recursion +**Fix:** This is expected behavior - deduplication happens on `(sectionIds, file, line)`