Files
ask262/.opencode/plans/archive/1775540058831-quick-planet.md
T

14 KiB

model
model
accounts/fireworks/routers/kimi-k2p5-turbo

Plan: ask262Debug Module for engine262

Overview

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 with deduplication logic
engine262/src/index.mts MODIFY — add named re-export
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

1a. Extend State Interface

Add ask262Debug to the NeededNames type and extend State interface:

type NeededNames = 'Completion' | 'AbruptCompletion' | 'Assert' | 'Call' | 
                   'IteratorClose' | 'AsyncIteratorClose' | 'Value' | 
                   'skipDebugger' | 'ask262Debug';  // NEW

interface State extends PluginPass {
  needed: Partial<Record<NeededNames, boolean>>;
  fileRelativePath?: string;  // NEW: captured at Program enter
}

1b. Add Import Creator Function

Add alongside existing createImport* functions:

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():

function addSectionFromComments(
  path: NodePath<t.FunctionDeclaration> | NodePath<t.VariableDeclaration> | 
        NodePath<t.ExportNamedDeclaration> | NodePath<t.ClassMethod> | NodePath<t.ObjectMethod>,
  state: State,
  getName: () => string,
  getBody: () => t.BlockStatement | null
) {
  if (!path.node.leadingComments) return;

  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);
      if (matches) {
        sectionIds.push(...matches.map(m => m.substring(1)));
        if (!url) {
          const section = line.split(' ').find(l => l.includes('#sec'));
          // Only capture external URLs (skip local/fragment references)
          if (section?.startsWith('https://')) {
            url = section;
          }
        }
      }
    }
  }
  
  if (sectionIds.length === 0) return;

  const name = getName();
  
  // 1. Keep existing .section (backward compat) - uses first URL
  // Only applies to named functions
  if (name && url) {
    path.insertAfter(template.ast(`${name}.section = '${url}';`));
  }

  // 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') {
    const line = path.node.loc?.start.line ?? 0;
    const markCall = template.statement(
      `ask262Debug.mark(${JSON.stringify(sectionIds)}, ${JSON.stringify(state.fileRelativePath)}, ${line});`
    )();
    body.body.unshift(markCall);
    state.needed.ask262Debug = true;
  }
}

1d. Update Visitors

Update existing visitors to use new addSectionFromComments signature, and add new visitors for ClassMethod and ObjectMethod:

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 (skipDebugger, Completion, etc.) ...
        // NEW: Add ask262Debug import if needed
        if (state.needed.ask262Debug) {
          path.unshiftContainer('body', createImportAsk262Debug());
        }
      },
    },
    FunctionDeclaration(path, state) {
      addSectionFromComments(
        path, state,
        () => path.node.id!.name,
        () => path.node.body
      );
    },
    VariableDeclaration(path, state) {
      const init = path.get('declarations.0.init');
      if (init.isFunctionExpression()) {
        const id = path.node.declarations[0].id as t.Identifier;
        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,
          () => declaration.body
        );
      }
    },
    // NEW: Add ClassMethod visitor
    ClassMethod(path, state) {
      const key = path.node.key;
      if (key.type === 'Identifier') {
        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,
          () => path.node.body
        );
      }
    },
    // ... rest of existing visitors (CallExpression, ThrowStatement, etc.) unchanged
  },
};

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/src/index.mts — Add Named Re-export

Add the re-export alongside existing exports (around line 20):

// Add to existing exports from './helpers.mts' or create new export section
export { ask262Debug, type MarkData } from './ask262-debug.mts';

3. engine262/src/ask262-debug.mts — The Module

Deduplicated data collection. No stack parsing, no node: imports, no decorators.

export interface MarkData {
  readonly sectionIds: string[];
  readonly fileRelativePath: string;
  readonly lineNumber: number;
  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<string, number>(); // 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) {
    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() {
    this._importantStack = true;
  }

  stopImportant() {
    this._importantStack = false;
  }
}

export const ask262Debug = new Ask262Debug();

Key Behaviors

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)

4. engine262/scripts/verify-ask262-debug.mts — Verification Script

#!/usr/bin/env node
import { ask262Debug, Agent, ManagedRealm } from '#self';

console.log('=== ask262Debug Verification ===\n');

// 1. Check export exists
console.log('1. Checking export...');
if (!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 Agent();
const realm = new 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({}, {});
`);

// 4. Verify marks were captured
console.log('3. Checking marks...');
const marks = ask262Debug.marks;
console.log(`   Captured ${marks.length} unique marks\n`);

if (marks.length === 0) {
  console.error('FAIL: No marks captured');
  process.exit(1);
}

// 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('=== All Checks Passed ===');

Key Design Decisions Summary

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
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

Implementation Checklist

  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

=== 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 ===

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)