From eefac961a90f412941d15e495c671888b514dc7a Mon Sep 17 00:00:00 2001 From: Ricky Schema Cascade Date: Mon, 24 Aug 2026 14:42:25 +0200 Subject: [PATCH 1/2] fix(cli): ship every persona-authoring kit in the CLI install tree MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A persona is authored as persona.ts and compiled by the CLI, which resolves the file's imports out of the CLI's own install tree — a globally installed CLI plus a repo with no node_modules is the normal case. #325 fixed the resolution mechanism, but only persona-kit was actually in that tree. turn-kit and review-kit both export define*Persona entry points a persona.ts imports, and neither was a CLI dependency, so `defineTurnPersona` and `defineReviewPersona` personas still failed to compile on a fresh install — the same bug #325 fixed, one layer over. Added to @agentworkforce/cli rather than to deploy (which does the resolving): review-kit's tests import @agentworkforce/deploy, so depending on it from deploy makes a cyclic workspace dependency that pnpm warns about and that leaves `pnpm -r build` order ambiguous. The CLI is the installed surface and nothing depends on it, so the graph stays acyclic. Verified against the published 4.1.49 tree with turn-kit added: a turn persona compiles from a repo with no node_modules, and turn-kit's optional @agent-assistant/turn-context peer is not dragged into the bundle when it is absent. The new test derives the kit list from the source rather than hardcoding it, so a future kit that exports a define*Persona is caught the day it lands. Root `test` now globs scripts/*.test.mjs so new script tests run in CI. Co-Authored-By: Claude Opus 5 --- package.json | 2 +- packages/cli/package.json | 2 + pnpm-lock.yaml | 6 +++ scripts/authoring-kits.test.mjs | 65 +++++++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 scripts/authoring-kits.test.mjs diff --git a/package.json b/package.json index d2eef977..c7d29891 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "dev:cli": "node packages/cli/dist/cli.js", "typecheck": "pnpm -r typecheck && pnpm run typecheck:examples", "typecheck:examples": "tsc -p examples/tsconfig.json --noEmit", - "test": "node --test scripts/release-workflows.test.mjs && pnpm -r test && pnpm run test:e2e:agent-card", + "test": "node --test 'scripts/*.test.mjs' && pnpm -r test && pnpm run test:e2e:agent-card", "test:e2e:agent-card": "pnpm -r build && tsx scripts/e2e-agent-card.ts", "lint": "pnpm -r lint", "check": "pnpm run lint && pnpm run typecheck && pnpm run test" diff --git a/packages/cli/package.json b/packages/cli/package.json index f1dcd0bb..fe11ac3d 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -16,7 +16,9 @@ "@agentworkforce/local-surface": "workspace:*", "@agentworkforce/persona-kit": "workspace:*", "@agentworkforce/persona-registry": "workspace:*", + "@agentworkforce/review-kit": "workspace:*", "@agentworkforce/runtime": "workspace:*", + "@agentworkforce/turn-kit": "workspace:*", "@agentworkforce/workload-router": "workspace:*", "@relayburn/sdk": "^2.5.2", "@relayfile/local-mount": "^0.10.23", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d3707932..35a56657 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -61,9 +61,15 @@ importers: '@agentworkforce/persona-registry': specifier: workspace:* version: link:../persona-registry + '@agentworkforce/review-kit': + specifier: workspace:* + version: link:../review-kit '@agentworkforce/runtime': specifier: workspace:* version: link:../runtime + '@agentworkforce/turn-kit': + specifier: workspace:* + version: link:../turn-kit '@agentworkforce/workload-router': specifier: workspace:* version: link:../workload-router diff --git a/scripts/authoring-kits.test.mjs b/scripts/authoring-kits.test.mjs new file mode 100644 index 00000000..be48dd9e --- /dev/null +++ b/scripts/authoring-kits.test.mjs @@ -0,0 +1,65 @@ +import assert from 'node:assert/strict'; +import { readFileSync, readdirSync } from 'node:fs'; +import test from 'node:test'; + +/** + * A persona is authored as `persona.ts` and compiled by the CLI, which resolves + * the file's imports out of the CLI's own install tree — the user's repo + * usually has no `node_modules` at all when the CLI is installed globally. + * + * So every kit a persona.ts can import has to ship inside that tree, or + * `agentworkforce deploy ./persona.ts` dies with "Could not resolve" on a fresh + * install. persona-kit was already there; turn-kit and review-kit were not, + * which is the same failure workforce#325 fixed for persona-kit. + */ +function packageJson(dir) { + return JSON.parse(readFileSync(`packages/${dir}/package.json`, 'utf8')); +} + +function personaAuthoringKits() { + const kits = []; + for (const dir of readdirSync('packages')) { + let sources; + try { + sources = readdirSync(`packages/${dir}/src`); + } catch { + continue; // not a source package + } + const authorsPersonas = sources.some((file) => { + if (!file.endsWith('.ts') || file.endsWith('.test.ts')) return false; + const source = readFileSync(`packages/${dir}/src/${file}`, 'utf8'); + return /export function define\w*Persona\b/.test(source); + }); + if (authorsPersonas) kits.push(packageJson(dir).name); + } + return kits.sort(); +} + +test('every persona-authoring kit ships in the CLI install tree', () => { + const kits = personaAuthoringKits(); + // Guard against the discovery silently finding nothing and passing vacuously. + assert.ok( + kits.includes('@agentworkforce/persona-kit'), + `discovery failed to find the authoring kits (found: ${kits.join(', ') || 'none'})` + ); + + const cli = packageJson('cli'); + for (const kit of kits) { + assert.ok( + cli.dependencies?.[kit], + `${kit} exports a define*Persona entry point, so a persona.ts can import it, ` + + 'but @agentworkforce/cli does not depend on it — it will not be installed ' + + 'alongside the CLI and the persona will fail to compile on a fresh install' + ); + } +}); + +test('authoring kits are published in lockstep with the CLI', () => { + const publishWorkflow = readFileSync('.github/workflows/publish.yml', 'utf8'); + const targets = publishWorkflow.match(/echo "packages=([^"]+)"/)[1].trim().split(/\s+/); + const published = new Set(targets.map((dir) => packageJson(dir).name)); + + for (const kit of personaAuthoringKits()) { + assert.ok(published.has(kit), `${kit} must publish with the CLI to stay version-matched`); + } +}); From 25fd42b0d86ab42d5c3a39c71b1351262751ebad Mon Sep 17 00:00:00 2001 From: Ricky Schema Cascade Date: Mon, 24 Aug 2026 14:58:07 +0200 Subject: [PATCH 2/2] test: harden kit discovery and the workspace test glob (PR feedback) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cubic findings, all valid: - Discovery scanned only the top level of packages/*/src and only matched `export function`, so a kit exporting `export const define*Persona` or placing its entry in a subdirectory would be silently skipped — the guard would pass while the fresh-install break it exists to catch went live. Walks src recursively and matches const / async function too. - The lockstep check indexed a regex match directly, so a reformatted publish.yml line threw an unrelated TypeError instead of naming the cause. - The root test glob was single-quoted; cmd.exe does not strip those, so npm on Windows would hand node the pattern verbatim and match no files. Verified discovery still finds persona-kit, review-kit and turn-kit. Co-Authored-By: Claude Opus 5 --- package.json | 2 +- scripts/authoring-kits.test.mjs | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index c7d29891..7ec27984 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "dev:cli": "node packages/cli/dist/cli.js", "typecheck": "pnpm -r typecheck && pnpm run typecheck:examples", "typecheck:examples": "tsc -p examples/tsconfig.json --noEmit", - "test": "node --test 'scripts/*.test.mjs' && pnpm -r test && pnpm run test:e2e:agent-card", + "test": "node --test \"scripts/*.test.mjs\" && pnpm -r test && pnpm run test:e2e:agent-card", "test:e2e:agent-card": "pnpm -r build && tsx scripts/e2e-agent-card.ts", "lint": "pnpm -r lint", "check": "pnpm run lint && pnpm run typecheck && pnpm run test" diff --git a/scripts/authoring-kits.test.mjs b/scripts/authoring-kits.test.mjs index be48dd9e..9f9402b7 100644 --- a/scripts/authoring-kits.test.mjs +++ b/scripts/authoring-kits.test.mjs @@ -21,14 +21,16 @@ function personaAuthoringKits() { for (const dir of readdirSync('packages')) { let sources; try { - sources = readdirSync(`packages/${dir}/src`); + sources = readdirSync(`packages/${dir}/src`, { recursive: true }); } catch { continue; // not a source package } const authorsPersonas = sources.some((file) => { if (!file.endsWith('.ts') || file.endsWith('.test.ts')) return false; const source = readFileSync(`packages/${dir}/src/${file}`, 'utf8'); - return /export function define\w*Persona\b/.test(source); + // `function`, `const`, `async function`, and nested files all count — a + // kit that hides from this check is a kit that breaks on a fresh install. + return /export (?:async )?(?:function|const) define\w*Persona\b/.test(source); }); if (authorsPersonas) kits.push(packageJson(dir).name); } @@ -56,8 +58,13 @@ test('every persona-authoring kit ships in the CLI install tree', () => { test('authoring kits are published in lockstep with the CLI', () => { const publishWorkflow = readFileSync('.github/workflows/publish.yml', 'utf8'); - const targets = publishWorkflow.match(/echo "packages=([^"]+)"/)[1].trim().split(/\s+/); - const published = new Set(targets.map((dir) => packageJson(dir).name)); + const targets = publishWorkflow.match(/echo "packages=([^"]+)"/); + // Without this the reformatted-workflow case throws an unrelated TypeError + // and reads as a broken test rather than a broken workflow. + assert.ok(targets, 'publish workflow must declare its package targets'); + const published = new Set( + targets[1].trim().split(/\s+/).map((dir) => packageJson(dir).name) + ); for (const kit of personaAuthoringKits()) { assert.ok(published.has(kit), `${kit} must publish with the CLI to stay version-matched`);