diff --git a/eslint.config.js b/eslint.config.js index 4960087..1c7f2eb 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,12 +1,12 @@ // Flat ESLint config for the Obelisk root (Core + skill runtime + tests). -// Scope: the ESM sources under scripts/ and tests/. The Electron app has its own -// package and toolchain and is intentionally excluded (see docs/adr/0003). -// typescript-eslint is deferred to Phase 4, when the first .ts files land. +// Scope: the ESM/TS sources under scripts/ and tests/. The Electron app has its +// own package and toolchain and is intentionally excluded (see docs/adr/0003). import js from '@eslint/js'; import globals from 'globals'; +import tseslint from 'typescript-eslint'; -export default [ +export default tseslint.config( { ignores: [ 'node_modules/**', @@ -32,4 +32,14 @@ export default [ 'no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }], }, }, -]; + { + files: ['**/*.ts'], + extends: [...tseslint.configs.recommended], + languageOptions: { + globals: { ...globals.node }, + }, + rules: { + '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }], + }, + }, +); diff --git a/package-lock.json b/package-lock.json index 82626ed..d49896c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,8 @@ "@types/node": "^26.1.1", "eslint": "^10.6.0", "globals": "^17.7.0", - "typescript": "^6.0.3" + "typescript": "^6.0.3", + "typescript-eslint": "^8.63.0" } }, "node_modules/@eslint-community/eslint-utils": { @@ -241,6 +242,236 @@ "undici-types": "~8.3.0" } }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.63.0.tgz", + "integrity": "sha512-rvwSgqT+DHpWdzfSzPatRLm02a0GlESt++9iy3hLCDY4BgkaLcl8LBi9Yh7XGFBpwcBE/K3024QuXWTpbz4FfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.63.0", + "@typescript-eslint/type-utils": "8.63.0", + "@typescript-eslint/utils": "8.63.0", + "@typescript-eslint/visitor-keys": "8.63.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.63.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.63.0.tgz", + "integrity": "sha512-gwh4gvvlaVDKKxyfxMG+Gnu1u9X0OQBwyGLkbwB65dIzBKnxeRiJlNFqlI3zwVhNXJIs6qV7mlFCn/BIajlVig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.63.0", + "@typescript-eslint/types": "8.63.0", + "@typescript-eslint/typescript-estree": "8.63.0", + "@typescript-eslint/visitor-keys": "8.63.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.63.0.tgz", + "integrity": "sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.63.0", + "@typescript-eslint/types": "^8.63.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.63.0.tgz", + "integrity": "sha512-uUyfMWCnDSN8bCpcrY8nGP2BLkQ9Xn0GsipcONcpIDWhwhO4ZSyHvyS14U3X75mzxWxL3I2UZIrenTzdzcJO8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.63.0", + "@typescript-eslint/visitor-keys": "8.63.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.63.0.tgz", + "integrity": "sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.63.0.tgz", + "integrity": "sha512-Nzzh/OGxVCOjObjaj1CQF2RUasyYy2Jfuh+zZ3PjLzG2fYRriAiZLib9UKtO+CpQAS3YHiAS+ckZDclwqI1TPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.63.0", + "@typescript-eslint/typescript-estree": "8.63.0", + "@typescript-eslint/utils": "8.63.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.63.0.tgz", + "integrity": "sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.63.0.tgz", + "integrity": "sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.63.0", + "@typescript-eslint/tsconfig-utils": "8.63.0", + "@typescript-eslint/types": "8.63.0", + "@typescript-eslint/visitor-keys": "8.63.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.63.0.tgz", + "integrity": "sha512-fUKaeAvrTuQg/Tgt3nliAUSZHJM6DlCcfyEmxCvlX8kieWSStBX+5O5Fnidtc3i2JrH+9c/GL4RY2iasd/GPTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.63.0", + "@typescript-eslint/types": "8.63.0", + "@typescript-eslint/typescript-estree": "8.63.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.63.0.tgz", + "integrity": "sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.63.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/acorn": { "version": "8.17.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", @@ -533,6 +764,24 @@ "dev": true, "license": "MIT" }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -821,6 +1070,19 @@ "node": ">=8" } }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -841,6 +1103,19 @@ "node": ">=6" } }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -864,6 +1139,36 @@ "node": ">=8" } }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -891,6 +1196,30 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.63.0.tgz", + "integrity": "sha512-xgwXyzG4sK9ALkBxbyGkTMMOS+imnW65iPhxCQMK83KhxyoDNW7l+IDqEf9vMdoUidHpOoS967RCq4eMiTexwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.63.0", + "@typescript-eslint/parser": "8.63.0", + "@typescript-eslint/typescript-estree": "8.63.0", + "@typescript-eslint/utils": "8.63.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, "node_modules/undici-types": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", diff --git a/package.json b/package.json index 95b69bc..fc0e33f 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@types/node": "^26.1.1", "eslint": "^10.6.0", "globals": "^17.7.0", - "typescript": "^6.0.3" + "typescript": "^6.0.3", + "typescript-eslint": "^8.63.0" } } diff --git a/scripts/core.ts b/scripts/core.ts new file mode 100644 index 0000000..841f6de --- /dev/null +++ b/scripts/core.ts @@ -0,0 +1,64 @@ +// Obelisk Core (see docs/adr/0003-core-typescript-esm-precompiled.md). +// +// The single shared implementation behind every transport. runtime.mjs (skill), +// and later the CLI and MCP server, are thin shells over these four functions; +// none of them re-implement retrieval or own the DB lifecycle. +// +// Authored in TypeScript with erasable-only syntax so Node can run it directly +// via type stripping in development, while the skill artifact ships the tsc +// output (Phase 6). The heavy internals (db/indexer/query) remain .mjs for now +// and are migrated in later phases; Core is the typed seam over them. + +import { createContext, runInNewContext } from 'node:vm'; + +import { DB_PATH, openDb } from './db.mjs'; +import { buildIndex } from './indexer.mjs'; +import { createQueryApi, createAttuneApi } from './query.mjs'; + +export { buildIndex, DB_PATH }; + +type SandboxApi = Record; + +// Run a user-supplied CodeAct script inside the query/attune sandbox. The script +// body runs as an async IIFE with a 30s timeout; its `return` value is resolved. +function runInSandbox(api: SandboxApi, scriptContent: string): Promise { + const sandbox = { + ...api, JSON, Math, Array, Object, Set, Map, Date, RegExp, + parseInt, parseFloat, String, Number, Boolean, Error, Promise, console, setTimeout, + }; + const ctx = createContext(sandbox); + return runInNewContext(`(async()=>{${scriptContent}})()`, ctx, { timeout: 30000 }); +} + +// FTS search over indexed message text. Refreshes the index, then queries. +export function searchText(text: string, opts?: Record): unknown { + buildIndex(); + const db = openDb(); + try { + return createQueryApi(db).search(text, opts); + } finally { + db.close(); + } +} + +// Execute a read-only CodeAct query script and resolve its returned value. +export async function executeQuery(scriptContent: string): Promise { + buildIndex(); + const db = openDb(); + try { + return await runInSandbox(createQueryApi(db), scriptContent); + } finally { + db.close(); + } +} + +// Execute a memory-mutation CodeAct script (remember/forget only). +export async function executeAttune(scriptContent: string): Promise { + buildIndex(); + const db = openDb(); + try { + return await runInSandbox(createAttuneApi(db), scriptContent); + } finally { + db.close(); + } +} diff --git a/scripts/runtime.mjs b/scripts/runtime.mjs index e3c1546..5794372 100644 --- a/scripts/runtime.mjs +++ b/scripts/runtime.mjs @@ -1,32 +1,16 @@ #!/usr/bin/env node +// Skill transport: a thin CLI shell over Obelisk Core (scripts/core.ts). +// It only parses args, reads script files, prints JSON, and owns the uniform +// { error, stack } + exit-1 error envelope. All logic lives in Core. + import { createRequire } from 'node:module'; const require = createRequire(import.meta.url); const fs = require('node:fs'); const path = require('node:path'); -const vm = require('node:vm'); -import { DB_PATH, openDb } from './db.mjs'; -import { buildIndex } from './indexer.mjs'; -import { createQueryApi, createAttuneApi } from './query.mjs'; +import { DB_PATH, buildIndex, searchText, executeQuery, executeAttune } from './core.ts'; -function executeScript(api, scriptContent) { - const sandbox = { - ...api, JSON, Math, Array, Object, Set, Map, Date, RegExp, - parseInt, parseFloat, String, Number, Boolean, Error, Promise, console, setTimeout, - }; - const ctx = vm.createContext(sandbox); - return vm.runInNewContext(`(async()=>{${scriptContent}})()`, ctx, { timeout: 30000 }); -} - -function executeQuery(db, scriptContent) { - return executeScript(createQueryApi(db), scriptContent); -} - -function executeAttune(db, scriptContent) { - return executeScript(createAttuneApi(db), scriptContent); -} - -function main() { +async function main() { const args = process.argv.slice(2); // Uniform error envelope across all four verbs: a failure is reported as // { error, stack } on stdout with exit code 1, never a raw crash on stderr. @@ -34,6 +18,8 @@ function main() { process.stdout.write(JSON.stringify({ error: e.message, stack: e.stack }) + '\n'); process.exitCode = 1; }; + const emit = (r) => process.stdout.write(JSON.stringify(r, null, 2) + '\n'); + if (args[0] === '--build') { try { buildIndex({ force: true }); @@ -42,34 +28,15 @@ function main() { return; } if (args[0] === '--search' && args[1]) { - let db; - try { - buildIndex(); - db = openDb(); - process.stdout.write(JSON.stringify(createQueryApi(db).search(args.slice(1).join(' ')), null, 2) + '\n'); - } catch (e) { - fail(e); - } finally { - if (db) db.close(); - } + try { emit(searchText(args.slice(1).join(' '))); } catch (e) { fail(e); } return; } if (args[0] === '--query' && args[1]) { - buildIndex(); - const db = openDb(); - const script = fs.readFileSync(path.resolve(args[1]), 'utf8'); - executeQuery(db, script) - .then(r => { process.stdout.write(JSON.stringify(r, null, 2) + '\n'); db.close(); }) - .catch(e => { process.stdout.write(JSON.stringify({ error: e.message, stack: e.stack }) + '\n'); db.close(); process.exitCode = 1; }); + try { emit(await executeQuery(fs.readFileSync(path.resolve(args[1]), 'utf8'))); } catch (e) { fail(e); } return; } if (args[0] === '--attune' && args[1]) { - buildIndex(); - const db = openDb(); - const script = fs.readFileSync(path.resolve(args[1]), 'utf8'); - executeAttune(db, script) - .then(r => { process.stdout.write(JSON.stringify(r, null, 2) + '\n'); db.close(); }) - .catch(e => { process.stdout.write(JSON.stringify({ error: e.message, stack: e.stack }) + '\n'); db.close(); process.exitCode = 1; }); + try { emit(await executeAttune(fs.readFileSync(path.resolve(args[1]), 'utf8'))); } catch (e) { fail(e); } return; } process.stderr.write('Usage:\n node runtime.mjs --build\n node runtime.mjs --search "text"\n node runtime.mjs --query \n node runtime.mjs --attune \n'); diff --git a/tsconfig.json b/tsconfig.json index 469f325..e28485c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,7 @@ "noEmit": true, "allowJs": true, "checkJs": false, + "erasableSyntaxOnly": true, "skipLibCheck": true, "esModuleInterop": true, "resolveJsonModule": true,