import test from 'node:test' import assert from 'node:assert/strict' import fs from 'node:fs' import os from 'node:os' import path from 'node:path' import * as plugin from '../index.js' import { findAstGrep } from '../lib/sg.js' const DSH = '/usr/lib/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai' async function harness() { const { default: LocalFileSystem } = await import(`${DSH}/dsh-fs-local/lib/index.js`) const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'omp-ast-')) const fsImpl = Object.create(LocalFileSystem.prototype) Object.assign(fsImpl, { config: { cwd: dir, diffBasisMaxBytes: 1 << 24 }, internals: {}, locks: new Map() }) const tools = new Map() const ctx = { fs: fsImpl, tools: { register(d) { tools.set(d.name, d); return () => {} } }, emit() {}, effect() {}, get() {} } plugin.apply(ctx, {}) const exec = { agent: { session: { header: { cwd: dir } } }, signal: new AbortController().signal } const call = async (name, args) => { const t = tools.get(name); const v = await t.execute(args, exec); return v } return { dir, call, w: (f, s) => fs.writeFileSync(path.join(dir, f), s), r: (f) => fs.readFileSync(path.join(dir, f), 'utf8') } } const skip = !findAstGrep() test('ast_grep finds calls with captures', { skip }, async () => { const h = await harness() h.w('a.js', 'function f(x) {\n console.log(x)\n console.log(x, 2)\n log(x)\n}\n') const v = await h.call('ast_grep', { pat: 'console.log($$$ARGS)' }) assert.match(v.text, /^2 matches in 1 file/) assert.match(v.text, /^2: console\.log\(x\)$/m) assert.match(v.text, /\$\$\$ARGS=\[x, 2\]/) }) test('ast_edit previews, then applies by id; multiple ops chain', { skip }, async () => { const h = await harness() h.w('a.py', 'def f(a):\n print(a)\n print(a, 1)\n') const p = await h.call('ast_edit', { ops: [{ pat: 'print($$$A)', out: 'log($$$A)' }, { pat: 'log($X, 1)', out: 'log($X)' }], paths: ['a.py'] }) assert.match(p.text, /Staged as proposal (\w+)/) assert.equal(h.r('a.py'), 'def f(a):\n print(a)\n print(a, 1)\n') // untouched const id = p.proposal const a = await h.call('ast_edit', { apply: id }) assert.match(a.text, /Applied proposal/) assert.equal(h.r('a.py'), 'def f(a):\n log(a)\n log(a)\n') await assert.rejects(h.call('ast_edit', { apply: id }), /No pending proposal/) }) test('ast_edit refuses stale proposal', { skip }, async () => { const h = await harness() h.w('b.ts', 'const a = foo(1)\n') const p = await h.call('ast_edit', { ops: [{ pat: 'foo($A)', out: 'bar($A)' }], paths: ['b.ts'] }) h.w('b.ts', 'const a = foo(2)\n') await assert.rejects(h.call('ast_edit', { apply: p.proposal }), /changed since the preview/) }) test('ast_edit reject and no-match', { skip }, async () => { const h = await harness() h.w('c.go', 'package main\nfunc main() { println("x") }\n') const n = await h.call('ast_edit', { ops: [{ pat: 'nothing($A)', out: 'x' }], paths: ['.'] }) assert.match(n.text, /No changes/) const p = await h.call('ast_edit', { ops: [{ pat: 'println($A)', out: 'fmt.Println($A)' }], paths: ['c.go'] }) const r = await h.call('ast_edit', { reject: p.proposal }) assert.match(r.text, /discarded/) })