build: ask262Debug

This commit is contained in:
2026-04-08 13:55:13 +05:30
parent 0b58f8483a
commit ebe36c3737
6 changed files with 395 additions and 38 deletions
+117
View File
@@ -0,0 +1,117 @@
{
"name": ".opencode",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"@opencode-ai/plugin": "1.3.17"
}
},
"node_modules/@opencode-ai/plugin": {
"version": "1.3.17",
"resolved": "https://registry.npmjs.org/@opencode-ai/plugin/-/plugin-1.3.17.tgz",
"integrity": "sha512-N5lckFtYvEu2R8K1um//MIOTHsJHniF2kHoPIWPCrxKG5Jpismt1ISGzIiU3aKI2ht/9VgcqKPC5oZFLdmpxPw==",
"license": "MIT",
"dependencies": {
"@opencode-ai/sdk": "1.3.17",
"zod": "4.1.8"
},
"peerDependencies": {
"@opentui/core": ">=0.1.96",
"@opentui/solid": ">=0.1.96"
},
"peerDependenciesMeta": {
"@opentui/core": {
"optional": true
},
"@opentui/solid": {
"optional": true
}
}
},
"node_modules/@opencode-ai/sdk": {
"version": "1.3.17",
"resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.3.17.tgz",
"integrity": "sha512-2+MGgu7wynqTBwxezR01VAGhILXlpcHDY/pF7SWB87WOgLt3kD55HjKHNj6PWxyY8n575AZolR95VUC3gtwfmA==",
"license": "MIT",
"dependencies": {
"cross-spawn": "7.0.6"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"license": "ISC"
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/zod": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.1.8.tgz",
"integrity": "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
}
}
}
+1 -1
View File
@@ -3,4 +3,4 @@
"dependencies": { "dependencies": {
"@opencode-ai/plugin": "1.3.17" "@opencode-ai/plugin": "1.3.17"
} }
} }
+132 -37
View File
@@ -26,10 +26,11 @@ function getEnclosingConditionalExpression(path: NodePath) {
return null; return null;
} }
type NeededNames = 'Completion' | 'AbruptCompletion' | 'Assert' | 'Call' | 'IteratorClose' | 'AsyncIteratorClose' | 'Value' | 'skipDebugger'; type NeededNames = 'Completion' | 'AbruptCompletion' | 'Assert' | 'Call' | 'IteratorClose' | 'AsyncIteratorClose' | 'Value' | 'skipDebugger' | 'ask262Debug';
interface State extends PluginPass { interface State extends PluginPass {
needed: Partial<Record<NeededNames, boolean>>; needed: Partial<Record<NeededNames, boolean>>;
fileRelativePath?: string;
} }
interface Macro<R extends PublicReplacements = Record<string, Node | null>> { interface Macro<R extends PublicReplacements = Record<string, Node | null>> {
@@ -98,38 +99,72 @@ export default ({ types: t, template }: typeof import('@babel/core')): PluginObj
`); `);
} }
function addSectionFromComments(path: NodePath<t.FunctionDeclaration> | NodePath<t.VariableDeclaration> | NodePath<t.ExportNamedDeclaration>) { function createImportAsk262Debug() {
if (path.node.leadingComments) { return template.ast(`
for (const c of path.node.leadingComments) { import { ask262Debug } from "#self";
let name: string; `);
switch (path.type) { }
case 'FunctionDeclaration':
name = path.node.id!.name; function addSectionFromComments(
break; path: NodePath<t.FunctionDeclaration> | NodePath<t.VariableDeclaration> | NodePath<t.ExportNamedDeclaration> | NodePath<t.ClassMethod> | NodePath<t.ObjectMethod>,
case 'ExportNamedDeclaration': state: State,
name = (path.node.declaration as t.FunctionDeclaration).id!.name; getName: () => string,
break; getBody: () => t.BlockStatement | null,
case 'VariableDeclaration': insertSection: boolean,
name = (path.node.declarations[0].id as t.Identifier).name; ) {
break; if (!path.node.leadingComments) return;
default:
throw (path as NodePath).buildCodeFrameError('Internal error: Unsupported path to addSectionFromComments'); const sectionIds: string[] = [];
} let url = '';
const lines = c.value.split('\n'); let firstComment: t.Comment | null = null;
for (const line of lines) {
if (/#sec/.test(line)) { for (const c of path.node.leadingComments) {
const section = line.split(' ').find((l) => l.includes('#sec'))!; for (const line of c.value.split('\n')) {
const url = section.includes('https') ? section : `https://tc39.es/ecma262/${section}`; const matches = line.match(/#(sec-[a-zA-Z0-9._%-]+)/g);
const result = path.insertAfter(withSource(c, template.ast(`${name}.section = '${url}';`))); if (matches) {
if (path.node.trailingComments) { sectionIds.push(...matches.map((m) => m.substring(1)));
result[result.length - 1].node.trailingComments = path.node.trailingComments; if (!firstComment) {
path.node.trailingComments = null; firstComment = c;
}
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;
} }
return;
} }
} }
} }
} }
if (sectionIds.length === 0) return;
const name = getName();
// 1. Keep existing .section (backward compat) - uses first URL
// Only applies when caller explicitly allows it
if (name && url && firstComment && insertSection) {
const result = path.insertAfter(withSource(firstComment, template.ast(`${name}.section = '${url}';`)));
if (path.node.trailingComments) {
result[result.length - 1].node.trailingComments = path.node.trailingComments;
path.node.trailingComments = null;
}
}
// 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 sectionIdsStr = JSON.stringify(sectionIds);
const filePathStr = JSON.stringify(state.fileRelativePath);
const markCall = template.statement(
`ask262Debug.mark(${sectionIdsStr}, ${filePathStr}, ${line});`,
)();
body.body.unshift(markCall);
state.needed.ask262Debug = true;
}
} }
@@ -254,8 +289,11 @@ export default ({ types: t, template }: typeof import('@babel/core')): PluginObj
return { return {
visitor: { visitor: {
Program: { Program: {
enter(_path, state) { enter(path, state) {
state.needed = {}; state.needed = {};
// Capture relative file path from state.filename (relative to src/)
const absolutePath = state.filename || '';
state.fileRelativePath = absolutePath.replace(/.*\/src\//, '') || 'unknown.mts';
}, },
exit(path, state) { exit(path, state) {
if (state.needed.skipDebugger) { if (state.needed.skipDebugger) {
@@ -282,6 +320,9 @@ export default ({ types: t, template }: typeof import('@babel/core')): PluginObj
if (state.needed.Value) { if (state.needed.Value) {
path.unshiftContainer('body', createImportValue()); path.unshiftContainer('body', createImportValue());
} }
if (state.needed.ask262Debug) {
path.unshiftContainer('body', createImportAsk262Debug());
}
}, },
}, },
CallExpression(path, state) { CallExpression(path, state) {
@@ -438,17 +479,71 @@ export default ({ types: t, template }: typeof import('@babel/core')): PluginObj
} }
} }
}, },
FunctionDeclaration(path) { FunctionDeclaration(path, state) {
addSectionFromComments(path); addSectionFromComments(
path,
state,
() => path.node.id!.name,
() => path.node.body,
true,
);
}, },
VariableDeclaration(path) { VariableDeclaration(path, state) {
if (path.get('declarations.0.init').isArrowFunctionExpression() || path.get('declarations.0.init').isFunctionExpression()) { const init = path.get('declarations.0.init');
addSectionFromComments(path); if (init.isFunctionExpression()) {
const id = path.node.declarations[0].id as t.Identifier;
addSectionFromComments(
path,
state,
() => id.name,
() => init.node.body,
true,
);
} 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),
true,
);
} }
}, },
ExportNamedDeclaration(path) { ExportNamedDeclaration(path, state) {
if (path.get('declaration').isFunctionDeclaration()) { const declaration = path.node.declaration;
addSectionFromComments(path); if (declaration?.type === 'FunctionDeclaration') {
addSectionFromComments(
path,
state,
() => declaration.id!.name,
() => declaration.body,
true,
);
}
},
ClassMethod(path, state) {
const key = path.node.key;
if (key.type === 'Identifier') {
addSectionFromComments(
path,
state,
() => key.name,
() => path.node.body,
false,
);
}
},
ObjectMethod(path, state) {
const key = path.node.key;
if (key.type === 'Identifier') {
addSectionFromComments(
path,
state,
() => key.name,
() => path.node.body,
false,
);
} }
}, },
}, },
+50
View File
@@ -0,0 +1,50 @@
#!/usr/bin/env node
/**
* Verification script for ask262Debug module.
* Tests that the module is properly exported and captures spec section marks.
*/
import {
ask262Debug, Agent, ManagedRealm, setSurroundingAgent,
} 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();
setSurroundingAgent(agent);
const realm = new ManagedRealm();
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 = ask262Debug.marks;
console.log(` Captured ${marks.length} unique marks\n`);
if (marks.length === 0) {
console.error('FAIL: No marks captured');
process.exit(1);
}
// 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('=== All Checks Passed ===');
+94
View File
@@ -0,0 +1,94 @@
/**
* Runtime execution tracker for ECMAScript spec section analysis.
* Records which spec sections are entered during execution with deduplication.
* Used for AI analysis of execution flow mapped to ECMAScript spec sections.
*/
/**
* Represents a mark entry captured during execution.
*/
export interface MarkData {
/** Array of ECMAScript spec section IDs (e.g., ["sec-array.prototype.every"]) */
readonly sectionIds: string[];
/** Relative file path from engine262/src/ directory */
readonly fileRelativePath: string;
/** Line number in source file (1-indexed) */
readonly lineNumber: number;
/** Whether this mark was captured during an "important" execution phase */
readonly important: boolean;
}
/**
* Runtime execution tracker that deduplicates marks by (sectionIds, file, line).
* Provides chronological trace of spec section entry points during execution.
*/
class Ask262Debug {
/** Collected mark entries with deduplication */
marks: MarkData[] = [];
/** Whether to mark new entries as important */
private _important = false;
/** Map from deduplication key to index in marks array */
private _markIndex = new Map<string, number>();
/**
* Creates a deduplication key from section IDs, file path, and line number.
* @param sectionIds - Array of spec section IDs
* @param file - Relative file path
* @param line - Line number
* @returns String key for deduplication
*/
private _makeKey(sectionIds: string[], file: string, line: number): string {
return `${sectionIds.join(',')}|${file}|${line}`;
}
/**
* Records a mark for spec section entry during execution.
* Deduplicates based on (sectionIds, file, line) combination.
* If duplicate found, merges important flags (OR logic).
* @param sectionIds - Array of ECMAScript spec section IDs
* @param file - Relative file path from engine262/src/
* @param line - Line number in source file (1-indexed)
*/
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 using OR logic
const existing = this.marks[existingIndex];
if (this._important && !existing.important) {
(this.marks[existingIndex] as MarkData & { important: boolean }).important = true;
}
return;
}
const newMark: MarkData = {
sectionIds: [...sectionIds], // defensive copy
fileRelativePath: file,
lineNumber: line,
important: this._important,
};
this._markIndex.set(key, this.marks.length);
this.marks.push(newMark);
}
/**
* Marks subsequent mark() calls as important.
* Used to annotate interesting execution phases.
*/
startImportant() {
this._important = true;
}
/**
* Stops marking subsequent mark() calls as important.
*/
stopImportant() {
this._important = false;
}
}
export const ask262Debug = new Ask262Debug();
+1
View File
@@ -12,6 +12,7 @@ export { type ErrorType, type Formattable, Throw } from './host-defined/error-me
export * from './evaluator.mts'; export * from './evaluator.mts';
export { captureStack } from './helpers.mts'; export { captureStack } from './helpers.mts';
export { ask262Debug, type MarkData } from './ask262-debug.mts';
export { export {
gc, runJobQueue, type ManagedRealmHostDefined, ManagedRealm, gc, runJobQueue, type ManagedRealmHostDefined, ManagedRealm,
} from './api.mts'; } from './api.mts';