From abf9892cecfbdc0217bb285d9ddb3744499c3fc9 Mon Sep 17 00:00:00 2001 From: JustineDevs Date: Sun, 23 Aug 2026 04:25:02 +0800 Subject: [PATCH 1/2] Make the executable source surface TypeScript Keep authored SDK, tests, operational scripts, and Next configuration in TypeScript while compiling Node-compatible JavaScript artifacts for execution. Constraint: Node 18 and framework loaders require compiled JavaScript for runtime entrypoints Rejected: TypeScript runtime loader dependency | adds runtime coupling and weakens the published package contract Confidence: high Scope-risk: moderate Directive: Keep authored application code in TypeScript; retain JavaScript only where a tool loader requires it or as compiler output Tested: pnpm check; pnpm --filter agent-compat-docs build; 53 tests and 41 adapter conformance cases passed Not-tested: Remote CI after this commit --- .gitignore | 2 + .knip.json | 1 + apps/docs/{next.config.mjs => next.config.ts} | 0 biome.json | 2 + package.json | 8 ++-- packages/agents/package.json | 4 +- ...onformance.test.js => conformance.test.ts} | 4 +- .../test/{smoke.test.js => smoke.test.ts} | 42 +++++++++++-------- packages/agents/tsconfig.test.json | 11 +++++ ...ort-matrix.mjs => check-support-matrix.ts} | 7 +++- scripts/{live-smoke.mjs => live-smoke.ts} | 25 ++++++++--- scripts/package.json | 4 ++ ...-package.mjs => publish-github-package.ts} | 10 ++--- scripts/tsconfig.json | 17 ++++++++ 14 files changed, 98 insertions(+), 39 deletions(-) rename apps/docs/{next.config.mjs => next.config.ts} (100%) rename packages/agents/test/{conformance.test.js => conformance.test.ts} (91%) rename packages/agents/test/{smoke.test.js => smoke.test.ts} (93%) create mode 100644 packages/agents/tsconfig.test.json rename scripts/{check-support-matrix.mjs => check-support-matrix.ts} (76%) rename scripts/{live-smoke.mjs => live-smoke.ts} (66%) create mode 100644 scripts/package.json rename scripts/{publish-github-package.mjs => publish-github-package.ts} (87%) create mode 100644 scripts/tsconfig.json diff --git a/.gitignore b/.gitignore index 3fa88ff..5ab099e 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ apps/docs/tsconfig.tsbuildinfo apps/docs/AGENTS.md apps/docs/CLAUDE.md dist/ +.dist/ +packages/agents/dist-test/ coverage/ .pnpm-store/ .turbo/ diff --git a/.knip.json b/.knip.json index e8ba768..13f78f7 100644 --- a/.knip.json +++ b/.knip.json @@ -1,4 +1,5 @@ { + "entry": ["scripts/*.ts"], "ignoreDependencies": [ "@semantic-release/commit-analyzer", "@semantic-release/github", diff --git a/apps/docs/next.config.mjs b/apps/docs/next.config.ts similarity index 100% rename from apps/docs/next.config.mjs rename to apps/docs/next.config.ts diff --git a/biome.json b/biome.json index 475e4f4..7342c35 100644 --- a/biome.json +++ b/biome.json @@ -6,6 +6,8 @@ "!**/.omx", "!**/node_modules", "!**/dist", + "!**/dist-test", + "!**/.dist", "!**/coverage", "!**/.next", "!**/.source", diff --git a/package.json b/package.json index 079f7c3..d9c45b5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "agent-compat", "private": true, + "type": "module", "description": "Workspace for the @jstn-sdk/agents compatibility SDK", "packageManager": "pnpm@9.15.4", "engines": { @@ -12,9 +13,10 @@ "test": "pnpm --filter @jstn-sdk/agents test", "verify": "pnpm --filter @jstn-sdk/agents verify", "publish:agents": "pnpm --filter @jstn-sdk/agents publish --access public", - "publish:github": "node scripts/publish-github-package.mjs", - "live-smoke": "node scripts/live-smoke.mjs", - "matrix:check": "node scripts/check-support-matrix.mjs", + "scripts:build": "tsc -p scripts/tsconfig.json", + "publish:github": "pnpm scripts:build && node .dist/scripts/publish-github-package.js", + "live-smoke": "pnpm scripts:build && node .dist/scripts/live-smoke.js", + "matrix:check": "pnpm scripts:build && node .dist/scripts/check-support-matrix.js", "check": "pnpm format:check && pnpm build && pnpm typecheck && pnpm test && pnpm matrix:check && pnpm docs:content && pnpm knip", "format": "biome format --write .", "format:check": "biome format .", diff --git a/packages/agents/package.json b/packages/agents/package.json index eec6d24..c478231 100644 --- a/packages/agents/package.json +++ b/packages/agents/package.json @@ -38,8 +38,8 @@ "scripts": { "build": "tsc -p tsconfig.json", "typecheck": "tsc --noEmit -p tsconfig.json", - "test": "pnpm build && node --test", - "verify": "pnpm build && node --test test/conformance.test.js" + "test": "pnpm build && tsc -p tsconfig.test.json && node --test dist-test/test/*.test.js", + "verify": "pnpm build && tsc -p tsconfig.test.json && node --test dist-test/test/conformance.test.js" }, "engines": { "node": ">=18" diff --git a/packages/agents/test/conformance.test.js b/packages/agents/test/conformance.test.ts similarity index 91% rename from packages/agents/test/conformance.test.js rename to packages/agents/test/conformance.test.ts index 9ce1475..7189b28 100644 --- a/packages/agents/test/conformance.test.js +++ b/packages/agents/test/conformance.test.ts @@ -3,8 +3,8 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { test } from "node:test"; -import { Agents } from "../dist/index.js"; -import { verifyAdapter } from "../dist/testing/index.js"; +import { Agents } from "../src/index.js"; +import { verifyAdapter } from "../src/testing/index.js"; test("every registered adapter passes compile/write/read/validate conformance", async (t) => { const manifest = { diff --git a/packages/agents/test/smoke.test.js b/packages/agents/test/smoke.test.ts similarity index 93% rename from packages/agents/test/smoke.test.js rename to packages/agents/test/smoke.test.ts index 095431d..64c0b74 100644 --- a/packages/agents/test/smoke.test.js +++ b/packages/agents/test/smoke.test.ts @@ -3,7 +3,9 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import test from "node:test"; -import { Agents } from "../dist/index.js"; +import { Agents } from "../src/index.js"; + +const invalid = (value: unknown) => value as never; test("detect, compile, validate", async () => { const root = await fs.mkdtemp(path.join(os.tmpdir(), "agents-")); @@ -63,7 +65,7 @@ test("detect, compile, validate", async () => { assert.match(rendered, /## Roles/); const report = await Agents.validate(root); - assert.equal(report.cursor, "✓"); + assert.equal((report as Record).cursor, "✓"); assert.equal(report.valid, true); }); @@ -182,7 +184,6 @@ test("compiles native artifacts for all targets and preserves unmanaged files", }, }, { - root, output: root, targets: [ "cursor", @@ -293,40 +294,40 @@ test("dry-run and validation expose real artifact state", async () => { "\n---\ndescription: broken\n---\n\n", ); const report = await Agents.validate(root); - assert.equal(report.cursor, "×"); - assert.equal(report.cursorValidation.status, "invalid"); - assert.equal(report.results.cursor.status, "invalid"); + const legacyReport = report as Record; + assert.equal(legacyReport.cursor, "×"); + assert.equal(legacyReport.cursorValidation.status, "invalid"); + assert.equal(legacyReport.results.cursor.status, "invalid"); await fs.rm(root, { recursive: true, force: true }); }); test("rejects malformed manifests and unknown targets", async () => { await assert.rejects( - () => Agents.compile({ version: 2 }, { targets: ["cursor"] }), + () => Agents.compile(invalid({ version: 2 }), { targets: ["cursor"] }), /manifest.version/, ); await assert.rejects( () => Agents.compile( - { + invalid({ version: 1, workflows: { implementation: { steps: "not-an-array" } }, - }, + }), { targets: ["cursor"] }, ), /workflow implementation\.steps/, ); await assert.rejects( () => - Agents.compile( - { version: 1, targets: { auto: "yes" } }, - { targets: ["cursor"] }, - ), + Agents.compile(invalid({ version: 1, targets: { auto: "yes" } }), { + targets: ["cursor"], + }), /manifest\.targets\.auto/, ); await assert.rejects( () => Agents.compile( - { version: 1, roles: { reviewer: { permissions: "read" } } }, + invalid({ version: 1, roles: { reviewer: { permissions: "read" } } }), { targets: ["cursor"] }, ), /role reviewer\.permissions/, @@ -336,17 +337,22 @@ test("rejects malformed manifests and unknown targets", async () => { ["workflows", []], ["roles", []], ["targets", []], - ]) { + ] as const) { await assert.rejects( () => - Agents.compile({ version: 1, [field]: value }, { targets: ["cursor"] }), + Agents.compile(invalid({ version: 1, [field]: value }), { + targets: ["cursor"], + }), new RegExp(`manifest\\.${field} must be an object`), ); } await assert.rejects( () => Agents.compile( - { version: 1, skills: { review: { instructions: ["ok", 1] } } }, + invalid({ + version: 1, + skills: { review: { instructions: ["ok", 1] } }, + }), { targets: ["cursor"] }, ), /skill review\.instructions must be an array of strings/, @@ -410,7 +416,7 @@ test("scopes validation and reports shared output collisions", async () => { const report = await Agents.validate(root, { targets: ["cursor"] }); assert.deepEqual(report.targets, ["cursor"]); assert.equal(report.valid, true); - assert.equal(report.results.codex, undefined); + assert.equal((report as Record).results.codex, undefined); const collision = await Agents.compile( { version: 1, project: { name: "collision" }, instructions: [] }, diff --git a/packages/agents/tsconfig.test.json b/packages/agents/tsconfig.test.json new file mode 100644 index 0000000..072a51c --- /dev/null +++ b/packages/agents/tsconfig.test.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDir": ".", + "outDir": "./dist-test", + "declaration": false, + "declarationMap": false, + "sourceMap": false + }, + "include": ["src/**/*.ts", "test/**/*.ts"] +} diff --git a/scripts/check-support-matrix.mjs b/scripts/check-support-matrix.ts similarity index 76% rename from scripts/check-support-matrix.mjs rename to scripts/check-support-matrix.ts index d4b3444..a2cdc8b 100644 --- a/scripts/check-support-matrix.mjs +++ b/scripts/check-support-matrix.ts @@ -1,5 +1,10 @@ import { readFile } from "node:fs/promises"; -import { Agents } from "../packages/agents/dist/index.js"; +import { join } from "node:path"; +import { pathToFileURL } from "node:url"; + +const { Agents } = (await import( + pathToFileURL(join(process.cwd(), "packages/agents/dist/index.js")).href +)) as typeof import("../packages/agents/dist/index.js"); const matrix = await readFile("docs/support-matrix.md", "utf8"); const adapters = Agents.list(); diff --git a/scripts/live-smoke.mjs b/scripts/live-smoke.ts similarity index 66% rename from scripts/live-smoke.mjs rename to scripts/live-smoke.ts index f904148..ce4f7f9 100644 --- a/scripts/live-smoke.mjs +++ b/scripts/live-smoke.ts @@ -3,11 +3,15 @@ import { tmpdir } from "node:os"; import path from "node:path"; import { execFile } from "node:child_process"; import { promisify } from "node:util"; -import { Agents } from "../packages/agents/dist/index.js"; +import { pathToFileURL } from "node:url"; + +const { Agents } = (await import( + pathToFileURL(path.join(process.cwd(), "packages/agents/dist/index.js")).href +)) as typeof import("../packages/agents/dist/index.js"); const run = promisify(execFile); const manifest = { - version: 1, + version: 1 as const, project: { name: "live-runtime-smoke" }, instructions: ["Use the generated project instructions."], }; @@ -16,7 +20,7 @@ const runtimes = [ ["claude-code", "claude"], ["cursor", "cursor"], ["hermes", "hermes"], -]; +] as const; const root = await mkdtemp(path.join(tmpdir(), "agent-compat-live-")); let failures = 0; @@ -27,7 +31,10 @@ try { targets: [id], }); const validation = await Agents.validate(root, { targets: [id] }); - if (!compiled.success || validation.results[id]?.status !== "valid") { + const result = (validation.results as Record)[ + id + ]; + if (!compiled.success || result?.status !== "valid") { console.error(`${id}: FAIL generated artifact validation`); failures += 1; continue; @@ -35,11 +42,17 @@ try { try { await run(binary, ["--help"], { timeout: 15_000, maxBuffer: 20_000 }); } catch (error) { - if (error.code === "ENOENT") { + if ( + error && + typeof error === "object" && + "code" in error && + error.code === "ENOENT" + ) { console.log(`${id}: SKIP (${binary} is not installed)`); continue; } - console.error(`${id}: FAIL runtime startup (${error.message})`); + const message = error instanceof Error ? error.message : String(error); + console.error(`${id}: FAIL runtime startup (${message})`); failures += 1; continue; } diff --git a/scripts/package.json b/scripts/package.json new file mode 100644 index 0000000..e986b24 --- /dev/null +++ b/scripts/package.json @@ -0,0 +1,4 @@ +{ + "private": true, + "type": "module" +} diff --git a/scripts/publish-github-package.mjs b/scripts/publish-github-package.ts similarity index 87% rename from scripts/publish-github-package.mjs rename to scripts/publish-github-package.ts index 92d5d82..46670c5 100644 --- a/scripts/publish-github-package.mjs +++ b/scripts/publish-github-package.ts @@ -3,7 +3,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { spawn } from "node:child_process"; -const packageRoot = new URL("../packages/agents/", import.meta.url); +const packageRoot = new URL("../../packages/agents/", import.meta.url); const packageJson = JSON.parse( await readFile(new URL("package.json", packageRoot), "utf8"), ); @@ -33,7 +33,7 @@ try { )}\n`, ); - await new Promise((resolve, reject) => { + await new Promise((resolve, reject) => { const child = spawn( "npm", [ @@ -43,11 +43,7 @@ try { "--registry", "https://npm.pkg.github.com", ], - { - cwd: stage, - stdio: "inherit", - env: process.env, - }, + { cwd: stage, stdio: "inherit", env: process.env }, ); child.on("error", reject); child.on("exit", (code) => diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json new file mode 100644 index 0000000..a544fde --- /dev/null +++ b/scripts/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "rootDir": ".", + "outDir": "../.dist/scripts", + "strict": true, + "skipLibCheck": true, + "types": ["node"], + "typeRoots": [ + "../node_modules/@types", + "../packages/agents/node_modules/@types" + ] + }, + "include": ["./*.ts"] +} From e99bf50d4b9dcdf829b32aecdee2e1bd090ab089 Mon Sep 17 00:00:00 2001 From: JustineDevs Date: Sun, 23 Aug 2026 04:28:33 +0800 Subject: [PATCH 2/2] Run semantic release on its supported Node version Semantic-release refuses Node 20, so the release workflow must use Node 22 while the package runtime contract remains Node 18. Constraint: semantic-release requires Node 22.14 or newer Rejected: Downgrade semantic-release | would discard the current release toolchain Confidence: high Scope-risk: narrow Directive: Keep CI release Node aligned with semantic-release support requirements Tested: git diff --check; workflow change is configuration-only Not-tested: semantic-release execution locally because local Node is 20 --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cf45f48..d1a878f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,7 +30,7 @@ jobs: version: 9.15.4 - uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 cache: pnpm registry-url: https://registry.npmjs.org - run: pnpm install --frozen-lockfile