@appliqation/scriptgen 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Appliqation Pty Ltd
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,70 @@
1
+ # Appliqation Scriptgen
2
+
3
+ **Drafts and verifies an enterprise-grade Playwright script for one test case — and never claims it passes without actually running it.**
4
+
5
+ Point it at a test case that has no canonical automation yet, and it investigates the surrounding context (scenario intent, sibling test flakiness, linked defects, prior execution evidence), writes a real Playwright spec into your repo, and iterates against a real `npx playwright test` run until it's genuinely green — not until the model *says* it's green.
6
+
7
+ ## Why this exists
8
+
9
+ Most "AI writes your tests" tools generate a script and hope. This one is judged by a single rule: **`testRun.ok` is only ever `true` if a real, `execFile`-reported Playwright process exited 0, and that run happened *after* the most recent file write.** An earlier passing run that predates the latest edit doesn't count — that's a stale result, not a verification. If the generated script never actually ran, the exit code reflects that honestly, every time.
10
+
11
+ ## How it works
12
+
13
+ ```mermaid
14
+ flowchart TD
15
+ A[test case UUID] --> B[gather context:<br/>scenario, sibling flakiness,<br/>linked defects, evidence]
16
+ B --> C[draft a Playwright spec]
17
+ C --> D[npx playwright test]
18
+ D --> E{passed?}
19
+ E -- no --> F[read the real failure,<br/>revise the spec]
20
+ F --> D
21
+ E -- yes --> G[report: verified: true<br/>+ the written file path]
22
+ ```
23
+
24
+ The context-gathering and drafting *methodology* itself lives in Appliqation's own `appq:automate` MCP prompt — this repo is deliberately thin: it just gives that workflow two tool surfaces (read-only Appliqation context tools, and real filesystem + an allowlisted shell) and lets it do the work.
25
+
26
+ - **No appq write tool, no git operation.** This agent writes local files only — nothing is synced back to Appliqation and nothing is committed. [`appliqation-pr-raise`](https://github.com/appliqation/appliqation-pr-raise) handles turning the result into a real PR.
27
+ - **A hardcoded, non-negotiable shell allowlist.** `npm init/install -D`, `npx playwright install/--version/test`, `node --version`, `git status/diff` — nothing else can run, checked before execution, spawned via `execFile` with an explicit argv array (never a shell string).
28
+
29
+ ## Quick start
30
+
31
+ ```bash
32
+ npm install -g @appliqation/scriptgen
33
+ ```
34
+
35
+ Create a `.env` file (in whatever directory you'll run it from) with:
36
+
37
+ ```
38
+ APPQ_API_KEY=your-appliqation-api-key
39
+ ANTHROPIC_API_KEY=your-anthropic-key # or OPENAI_API_KEY — pick one
40
+ ```
41
+
42
+ ```bash
43
+ appliqation-scriptgen generate \
44
+ --test-case-uuid <uuid> \
45
+ --repo-path /path/to/your/checkout
46
+ ```
47
+
48
+ Add `--environment <name>` if the target repo needs a fresh Playwright config bootstrapped (its `baseURL` context), `--role <name>` to authenticate as a specific role (otherwise inferred per-TC automatically), `--autotest-run-id <id>` to ground the draft in a prior [`appliqation-autotest`](https://github.com/appliqation/appliqation-autotest) run's real execution evidence, and `--json`/`--ci` for a structured summary + CI-friendly exit code.
49
+
50
+ ## Configuration
51
+
52
+ Copy `.env.example` to `.env`. Requires `APPQ_API_KEY` and one of `ANTHROPIC_API_KEY`/`OPENAI_API_KEY`.
53
+
54
+ ## Development
55
+
56
+ ```bash
57
+ git clone https://github.com/appliqation/appliqation-scriptgen.git
58
+ cd appliqation-scriptgen
59
+ npm install
60
+ cp .env.example .env # fill in APPQ_API_KEY and one LLM provider key
61
+ npm run dev -- generate --test-case-uuid <uuid> --repo-path <path>
62
+ npm run typecheck
63
+ npm test
64
+ ```
65
+
66
+ See `CLAUDE.md` for a map of this repo if you're working in it with an AI coding assistant.
67
+
68
+ ## License
69
+
70
+ MIT — see [LICENSE](./LICENSE).
@@ -0,0 +1,25 @@
1
+ // Extracted out of cli/index.ts so this is testable without triggering that
2
+ // file's top-level program.parseAsync(process.argv) side effect — same
3
+ // reasoning as appliqation-autotest's cli/resolvers.ts.
4
+ import { safeRecord } from '@appliqation/agent-core';
5
+ import { exitCodeFor } from './output.js';
6
+ export async function recordGenerateRun(args) {
7
+ const { sink, startedAt, endedAt, model, usage, testCaseUuid, result } = args;
8
+ const summary = result
9
+ ? { testCaseUuid, writtenPaths: result.writtenPaths, testRan: result.testRun.ran, verified: result.testRun.ok, report: result.report }
10
+ : undefined;
11
+ await safeRecord(sink, {
12
+ agent: 'appliqation-scriptgen',
13
+ subcommand: 'generate',
14
+ startedAt,
15
+ endedAt,
16
+ durationMillis: endedAt - startedAt,
17
+ model,
18
+ usage,
19
+ turns: result?.turns,
20
+ budgetExceeded: result?.budgetExceeded,
21
+ exitCode: summary ? exitCodeFor(summary) : 1,
22
+ outcome: summary ? { ...summary } : { testCaseUuid, error: true },
23
+ });
24
+ }
25
+ //# sourceMappingURL=audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/cli/audit.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,uEAAuE;AACvE,wDAAwD;AAExD,OAAO,EAAE,UAAU,EAAoC,MAAM,yBAAyB,CAAC;AAEvF,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAc1C,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,IAA2B;IACjE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAC9E,MAAM,OAAO,GAAgC,MAAM;QACjD,CAAC,CAAC,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,CAAC,YAAY,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,QAAQ,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;QACtI,CAAC,CAAC,SAAS,CAAC;IAEd,MAAM,UAAU,CAAC,IAAI,EAAE;QACrB,KAAK,EAAE,uBAAuB;QAC9B,UAAU,EAAE,UAAU;QACtB,SAAS;QACT,OAAO;QACP,cAAc,EAAE,OAAO,GAAG,SAAS;QACnC,KAAK;QACL,KAAK;QACL,KAAK,EAAE,MAAM,EAAE,KAAK;QACpB,cAAc,EAAE,MAAM,EAAE,cAAc;QACtC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5C,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,KAAK,EAAE,IAAI,EAAE;KAClE,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env node
2
+ // `generate`: draft + verify a Playwright script for one test case, via
3
+ // appq's (enriched) appq:automate workflow given real filesystem + shell
4
+ // tools. See src/orchestrator/generate.ts for the actual mechanism.
5
+ import { Command } from 'commander';
6
+ import { createMcpClient, createAnthropicAdapter, createOpenAiAdapter, createUsageAccumulator, resolveScenarioId, fetchScenarioInfo, resolveUrl, knownRolesForProject, inferRole, } from '@appliqation/agent-core';
7
+ import { config, resolveProvider, resolveModel } from '../config/env.js';
8
+ import { generate } from '../orchestrator/generate.js';
9
+ import { recordGenerateRun } from './audit.js';
10
+ import { printJsonSummary, printHumanSummary, exitCodeFor } from './output.js';
11
+ const client = createMcpClient({ origin: config.appqOrigin, apiKey: config.appqApiKey() });
12
+ function buildAdapter() {
13
+ const provider = resolveProvider();
14
+ const model = resolveModel();
15
+ return provider === 'anthropic'
16
+ ? createAnthropicAdapter(config.anthropicApiKey, model, config.anthropicMaxTokens)
17
+ : createOpenAiAdapter(config.openaiApiKey, model, config.openaiMaxOutputTokens);
18
+ }
19
+ function logEvent(prefix) {
20
+ return (e) => {
21
+ if (e.type === 'assistant') {
22
+ const text = (e.detail ?? '').trim();
23
+ if (text)
24
+ console.error(`${prefix}[thinking] ${text}`);
25
+ }
26
+ else if (e.type === 'tool') {
27
+ const d = e.detail;
28
+ console.error(`${prefix}[tool] ${d.name} -> ${d.result.slice(0, 300)}`);
29
+ }
30
+ else if (e.type === 'log') {
31
+ console.error(`${prefix}[log] ${e.detail}`);
32
+ }
33
+ else if (e.type === 'usage') {
34
+ const u = e.detail;
35
+ const cacheNote = u.cacheReadTokens
36
+ ? ` (${u.cacheReadTokens} from cache)`
37
+ : u.cacheWriteTokens
38
+ ? ` (${u.cacheWriteTokens} written to cache)`
39
+ : '';
40
+ console.error(`${prefix}[usage] in=${u.inputTokens} out=${u.outputTokens}${cacheNote}`);
41
+ }
42
+ };
43
+ }
44
+ const program = new Command();
45
+ program
46
+ .name('appliqation-scriptgen')
47
+ .description('Draft and verify an enterprise-grade Playwright script for one Appliqation test case.');
48
+ program
49
+ .command('generate')
50
+ .description("Compose a Playwright test for one TC via appq's appq:automate workflow (context: scenario, sibling-TC " +
51
+ 'flakiness, linked defects, manual/agentic execution evidence, canonical script if present), given real ' +
52
+ 'filesystem + an allowlisted shell so it can bootstrap the target repo and actually run the result — ' +
53
+ '`npx playwright test` is what decides pass/fail, never the model\'s own claim. Writes local files only: ' +
54
+ 'no appq write tool call, no git operation. scenario_id/project_id are always derived from ' +
55
+ '--test-case-uuid, never accepted as separate inputs.')
56
+ .requiredOption('--test-case-uuid <uuid>', 'test case UUID to generate a script for')
57
+ .option('--environment <name>', 'environment name — its URL (from get_project_settings) is offered as context for the target app\'s ' +
58
+ 'baseURL, only if the target repo needs a Playwright config bootstrapped from scratch')
59
+ .option('--role <name>', 'authenticate as this role in the generated script (setupAuth). Omit for per-TC inference from the TC\'s ' +
60
+ 'own tag/name (same mechanism appliqation-autotest uses), or for ungated projects where neither applies.')
61
+ .option('--autotest-run-id <id>', 'a prior appliqation-autotest run for this TC. Passed straight through as appq:automate\'s autotest_run_id ' +
62
+ "arg — pulls that run's execution evidence and switches to autonomous mode (no confirmation steps, no " +
63
+ 'live-browsing phase).')
64
+ .option('--repo-path <path>', 'target repo root every file/command tool call is scoped to', process.cwd())
65
+ .option('--file-path <path>', 'local spec file to extend or create. Omit to let the AI discover/propose one.')
66
+ .option('--max-turns <n>', 'override BUDGET_MAX_TURNS for this run')
67
+ .option('--json', 'print a single structured JSON summary on stdout instead of a human-readable report')
68
+ .option('--ci', 'shorthand for --json; exit code already reflects the real, execFile-verified outcome either way')
69
+ .action(async (opts) => {
70
+ const json = (opts.json ?? false) || (opts.ci ?? false);
71
+ const adapter = buildAdapter();
72
+ const scenarioId = resolveScenarioId({ testCaseUuid: opts.testCaseUuid });
73
+ const { projectId, tcs } = await fetchScenarioInfo(client, scenarioId);
74
+ const environmentUrl = opts.environment ? await resolveUrl(client, opts.environment, projectId) : undefined;
75
+ let role = opts.role;
76
+ if (!role) {
77
+ const knownRoles = knownRolesForProject(projectId);
78
+ const tcInfo = tcs.find((t) => t.testCaseUuid === opts.testCaseUuid);
79
+ const inferred = tcInfo ? inferRole(tcInfo, knownRoles) : null;
80
+ if (inferred) {
81
+ role = inferred;
82
+ console.error(`[setup] authenticated as role "${inferred}" (inferred)`);
83
+ }
84
+ }
85
+ const budget = { ...config.budget, ...(opts.maxTurns ? { maxTurns: Number(opts.maxTurns) } : {}) };
86
+ const startedAt = Date.now();
87
+ const usage = createUsageAccumulator();
88
+ const baseLog = logEvent('');
89
+ let result;
90
+ try {
91
+ result = await generate({
92
+ client,
93
+ adapter,
94
+ projectId,
95
+ scenarioId,
96
+ testCaseUuid: opts.testCaseUuid,
97
+ repoPath: opts.repoPath,
98
+ budget,
99
+ commandTimeoutMs: config.commandTimeoutMs,
100
+ filePath: opts.filePath,
101
+ autotestRunId: opts.autotestRunId,
102
+ role,
103
+ environmentUrl,
104
+ onEvent: (e) => {
105
+ baseLog(e);
106
+ if (e.type === 'usage')
107
+ usage.onUsage(e.detail);
108
+ },
109
+ });
110
+ }
111
+ finally {
112
+ // Audit write happens whether the run succeeded or threw — see
113
+ // @appliqation/agent-core's audit/sink.ts: safeRecord() (used
114
+ // inside recordGenerateRun) never lets a failed/unreachable audit
115
+ // sink affect this process's real outcome.
116
+ await recordGenerateRun({ sink: config.auditSink, startedAt, endedAt: Date.now(), model: resolveModel(), usage: usage.totals(), testCaseUuid: opts.testCaseUuid, result });
117
+ }
118
+ if (!json) {
119
+ console.log('\n=== Report ===\n');
120
+ console.log(result.report);
121
+ console.error(`\n(${result.turns} turns, budget exceeded: ${result.budgetExceeded})`);
122
+ }
123
+ const summary = {
124
+ testCaseUuid: opts.testCaseUuid,
125
+ writtenPaths: result.writtenPaths,
126
+ testRan: result.testRun.ran,
127
+ verified: result.testRun.ok,
128
+ report: result.report,
129
+ };
130
+ if (json)
131
+ printJsonSummary(summary);
132
+ else
133
+ printHumanSummary(summary);
134
+ process.exitCode = exitCodeFor(summary);
135
+ });
136
+ program.parseAsync(process.argv);
137
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ // --json/--ci's renderer, matching appliqation-autotest's output.ts shape.
2
+ // exitCodeFor() never trusts the model's own report text — only
3
+ // GenerateResult.testRun.ok, which is derived from a real execFile exit
4
+ // code, decides success. A file that was never actually run — or was run
5
+ // before its last edit — is not a pass, no matter what the report claims.
6
+ export function printJsonSummary(summary) {
7
+ console.log(JSON.stringify(summary, null, 2));
8
+ }
9
+ export function printHumanSummary(summary) {
10
+ console.log(`\n=== Test case ${summary.testCaseUuid} ===\n`);
11
+ if (summary.writtenPaths.length === 0) {
12
+ console.log(' No files were written.');
13
+ }
14
+ else {
15
+ for (const p of summary.writtenPaths)
16
+ console.log(` wrote ${p}`);
17
+ }
18
+ if (!summary.testRan) {
19
+ console.log('\n Never actually ran `npx playwright test` — not verified.');
20
+ }
21
+ else {
22
+ console.log(`\n Verification: ${summary.verified ? 'PASSED' : 'FAILED (or stale — run predates the last edit)'}`);
23
+ }
24
+ }
25
+ /** 1 unless the file was actually run — via a real, execFile-reported exit code — after its last edit, and passed. */
26
+ export function exitCodeFor(summary) {
27
+ return summary.testRan && summary.verified ? 0 : 1;
28
+ }
29
+ //# sourceMappingURL=output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.js","sourceRoot":"","sources":["../../src/cli/output.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,gEAAgE;AAChE,wEAAwE;AACxE,yEAAyE;AACzE,0EAA0E;AAU1E,MAAM,UAAU,gBAAgB,CAAC,OAAwB;IACvD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,OAAwB;IACxD,OAAO,CAAC,GAAG,CAAC,mBAAmB,OAAO,CAAC,YAAY,QAAQ,CAAC,CAAC;IAC7D,IAAI,OAAO,CAAC,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtC,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;IAC1C,CAAC;SAAM,CAAC;QACN,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,YAAY;YAAE,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;IACrE,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QACrB,OAAO,CAAC,GAAG,CAAC,8DAA8D,CAAC,CAAC;IAC9E,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,qBAAqB,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,gDAAgD,EAAE,CAAC,CAAC;IACrH,CAAC;AACH,CAAC;AAED,sHAAsH;AACtH,MAAM,UAAU,WAAW,CAAC,OAAwB;IAClD,OAAO,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACrD,CAAC"}
@@ -0,0 +1,49 @@
1
+ import 'dotenv/config';
2
+ import { DEFAULT_ANTHROPIC_MODEL, DEFAULT_OPENAI_MODEL } from '@appliqation/agent-core/providers';
3
+ import { required, optional } from '@appliqation/agent-core/config';
4
+ import { resolveAuditSink } from '@appliqation/agent-core/audit';
5
+ export const config = {
6
+ appqOrigin: optional('APPQ_ORIGIN') ?? 'https://appq.appliqation.io',
7
+ appqApiKey: () => required('APPQ_API_KEY'),
8
+ anthropicApiKey: optional('ANTHROPIC_API_KEY'),
9
+ openaiApiKey: optional('OPENAI_API_KEY'),
10
+ anthropicModel: optional('ANTHROPIC_MODEL'),
11
+ openaiModel: optional('OPENAI_MODEL'),
12
+ anthropicMaxTokens: Number(optional('ANTHROPIC_MAX_TOKENS') ?? 8192),
13
+ openaiMaxOutputTokens: Number(optional('OPENAI_MAX_OUTPUT_TOKENS') ?? 8192),
14
+ // A single, generous budget — unlike appliqation-autotest there is no
15
+ // executor/validator split here, just one drafting-and-verifying pass that
16
+ // may need several rounds of "run the test, read the failure, fix it".
17
+ budget: {
18
+ maxCalls: Number(optional('BUDGET_MAX_CALLS') ?? 60),
19
+ // This agent never calls browser_navigate (no browser tools offered at
20
+ // all), so BudgetTracker's page counter never increments — a large,
21
+ // effectively-unreachable cap rather than 0, since 0 would trip
22
+ // immediately (exceeded() checks pages >= maxPages, and 0 >= 0 is true).
23
+ maxPages: Number(optional('BUDGET_MAX_PAGES') ?? 999_999),
24
+ maxMillis: Number(optional('BUDGET_MAX_MILLIS') ?? 20 * 60 * 1000),
25
+ maxTurns: Number(optional('BUDGET_MAX_TURNS') ?? 60),
26
+ },
27
+ // Wall-clock cap per run_command invocation (npm install / playwright test
28
+ // can each legitimately take a while) — separate from the overall budget.
29
+ commandTimeoutMs: Number(optional('COMMAND_TIMEOUT_MS') ?? 5 * 60 * 1000),
30
+ // Observability, entirely opt-in — see @appliqation/agent-core's audit/sink.ts.
31
+ auditSink: resolveAuditSink({
32
+ auditMongoUri: optional('AUDIT_MONGO_URI'),
33
+ auditMongoDb: optional('AUDIT_MONGO_DB'),
34
+ auditMongoCollection: optional('AUDIT_MONGO_COLLECTION'),
35
+ auditJsonlPath: optional('AUDIT_JSONL_PATH'),
36
+ }),
37
+ };
38
+ export function resolveProvider() {
39
+ if (config.anthropicApiKey)
40
+ return 'anthropic';
41
+ if (config.openaiApiKey)
42
+ return 'openai';
43
+ throw new Error('Set ANTHROPIC_API_KEY or OPENAI_API_KEY');
44
+ }
45
+ export function resolveModel() {
46
+ const provider = resolveProvider();
47
+ return provider === 'anthropic' ? (config.anthropicModel ?? DEFAULT_ANTHROPIC_MODEL) : (config.openaiModel ?? DEFAULT_OPENAI_MODEL);
48
+ }
49
+ //# sourceMappingURL=env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/config/env.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAC;AACvB,OAAO,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAClG,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAEjE,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,UAAU,EAAE,QAAQ,CAAC,aAAa,CAAC,IAAI,6BAA6B;IACpE,UAAU,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC;IAC1C,eAAe,EAAE,QAAQ,CAAC,mBAAmB,CAAC;IAC9C,YAAY,EAAE,QAAQ,CAAC,gBAAgB,CAAC;IACxC,cAAc,EAAE,QAAQ,CAAC,iBAAiB,CAAC;IAC3C,WAAW,EAAE,QAAQ,CAAC,cAAc,CAAC;IACrC,kBAAkB,EAAE,MAAM,CAAC,QAAQ,CAAC,sBAAsB,CAAC,IAAI,IAAI,CAAC;IACpE,qBAAqB,EAAE,MAAM,CAAC,QAAQ,CAAC,0BAA0B,CAAC,IAAI,IAAI,CAAC;IAC3E,sEAAsE;IACtE,2EAA2E;IAC3E,uEAAuE;IACvE,MAAM,EAAE;QACN,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAC,IAAI,EAAE,CAAC;QACpD,uEAAuE;QACvE,oEAAoE;QACpE,gEAAgE;QAChE,yEAAyE;QACzE,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAC,IAAI,OAAO,CAAC;QACzD,SAAS,EAAE,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;QAClE,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAC,IAAI,EAAE,CAAC;KACrD;IACD,2EAA2E;IAC3E,0EAA0E;IAC1E,gBAAgB,EAAE,MAAM,CAAC,QAAQ,CAAC,oBAAoB,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;IAEzE,gFAAgF;IAChF,SAAS,EAAE,gBAAgB,CAAC;QAC1B,aAAa,EAAE,QAAQ,CAAC,iBAAiB,CAAC;QAC1C,YAAY,EAAE,QAAQ,CAAC,gBAAgB,CAAC;QACxC,oBAAoB,EAAE,QAAQ,CAAC,wBAAwB,CAAC;QACxD,cAAc,EAAE,QAAQ,CAAC,kBAAkB,CAAC;KAC7C,CAAC;CACH,CAAC;AAEF,MAAM,UAAU,eAAe;IAC7B,IAAI,MAAM,CAAC,eAAe;QAAE,OAAO,WAAW,CAAC;IAC/C,IAAI,MAAM,CAAC,YAAY;QAAE,OAAO,QAAQ,CAAC;IACzC,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;AAC7D,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,MAAM,QAAQ,GAAG,eAAe,EAAE,CAAC;IACnC,OAAO,QAAQ,KAAK,WAAW,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,cAAc,IAAI,uBAAuB,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,IAAI,oBAAoB,CAAC,CAAC;AACtI,CAAC"}
@@ -0,0 +1,77 @@
1
+ // Calls the (now enriched, see appq's AutomatePrompt.php) appq:automate
2
+ // workflow through the shared engine, offering it exactly two tool
3
+ // surfaces: read-only appq context tools (get_scenario, get_run_evidence,
4
+ // get_defect_context, ...) and real coding tools (read_file/write_file/
5
+ // list_directory/run_command, scoped to one repo). No appq write tool, no
6
+ // git operation, no browser — the prompt's own Phase 0/3/5/6 already
7
+ // describe exactly the bootstrap-draft-verify loop needed, and Phase 4
8
+ // (optional live browsing) is skipped outright once autotest_run_id is
9
+ // passed (see AutomatePrompt.php's autonomous mode).
10
+ import { fetchAppqToolDefs, createGatedAppqDispatcher, runWorkflow, } from '@appliqation/agent-core';
11
+ import { READONLY_CONTEXT_TOOLS } from '../tools/safety.js';
12
+ import { CODING_TOOL_DEFS, CodingTools } from '../tools/codingTools.js';
13
+ function seedMessage(opts) {
14
+ const lines = [
15
+ `Project ID: ${opts.projectId}`,
16
+ `Scenario ID: ${opts.scenarioId}`,
17
+ `Test case UUID: ${opts.testCaseUuid}`,
18
+ `Target repo root (every file/command tool call is scoped here): ${opts.repoPath}`,
19
+ ];
20
+ if (opts.filePath)
21
+ lines.push(`Target spec file: ${opts.filePath}`);
22
+ if (opts.role) {
23
+ lines.push(`This TC authenticates as role "${opts.role}" — if the target app is gated, the generated script ` +
24
+ `should call setupAuth({ project_id: ${opts.projectId}, role: "${opts.role}" }) per the contract rule.`);
25
+ }
26
+ if (opts.environmentUrl) {
27
+ lines.push(`Target app base URL (for playwright.config's baseURL, only if you need to bootstrap one): ${opts.environmentUrl}`);
28
+ }
29
+ lines.push('Begin now — start with get_scenario.');
30
+ return lines.join('\n');
31
+ }
32
+ export async function generate(opts) {
33
+ const coding = new CodingTools(opts.repoPath, opts.commandTimeoutMs);
34
+ const appqToolDefs = await fetchAppqToolDefs(opts.client, READONLY_CONTEXT_TOOLS);
35
+ const gatedAppq = createGatedAppqDispatcher(opts.client, READONLY_CONTEXT_TOOLS);
36
+ const codingToolNames = new Set(CODING_TOOL_DEFS.map((t) => t.name));
37
+ const dispatch = async (name, args) => {
38
+ if (codingToolNames.has(name))
39
+ return coding.dispatch(name, args);
40
+ return gatedAppq(name, args);
41
+ };
42
+ const promptArgs = {
43
+ project_id: opts.projectId,
44
+ scenario_id: opts.scenarioId,
45
+ test_case_uuid: opts.testCaseUuid,
46
+ };
47
+ if (opts.filePath)
48
+ promptArgs.file_path = opts.filePath;
49
+ if (opts.autotestRunId)
50
+ promptArgs.autotest_run_id = opts.autotestRunId;
51
+ const loopResult = await runWorkflow({
52
+ source: { kind: 'appq', name: 'appq:automate', args: promptArgs },
53
+ fetchPrompt: opts.client.fetchPrompt,
54
+ seedMessage: seedMessage(opts),
55
+ tools: [...appqToolDefs, ...CODING_TOOL_DEFS],
56
+ dispatch,
57
+ adapter: opts.adapter,
58
+ budget: opts.budget,
59
+ onEvent: opts.onEvent,
60
+ });
61
+ const writtenPaths = coding.getWrittenPaths();
62
+ const lastTestRun = coding.lastPlaywrightTestRun();
63
+ const lastWriteAt = writtenPaths.size > 0 ? Math.max(...writtenPaths.values()) : 0;
64
+ const verifiedAfterLastWrite = lastTestRun !== null && lastTestRun.ok && lastTestRun.timestamp >= lastWriteAt;
65
+ return {
66
+ report: loopResult.report,
67
+ turns: loopResult.turns,
68
+ budgetExceeded: loopResult.budgetExceeded,
69
+ writtenPaths: [...writtenPaths.keys()],
70
+ testRun: {
71
+ ran: lastTestRun !== null,
72
+ ok: verifiedAfterLastWrite,
73
+ exitCode: lastTestRun?.exitCode ?? null,
74
+ },
75
+ };
76
+ }
77
+ //# sourceMappingURL=generate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.js","sourceRoot":"","sources":["../../src/orchestrator/generate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,173 @@
1
+ // The capability appq:automate's own prompt (Phase 0/3/5/6) assumes a full
2
+ // coding-agent session has: read/write a local repo, and run real shell
3
+ // commands to bootstrap Playwright and actually verify the generated spec.
4
+ // Every path is scoped to repoPath (no traversal out of it); every command
5
+ // goes through commandGate.ts's allowlist AND is spawned via execFile with
6
+ // an explicit argv array — never a shell string — so the OS never parses
7
+ // arguments as shell syntax in the first place.
8
+ import { readFile, writeFile, mkdir, readdir } from 'node:fs/promises';
9
+ import { resolve, dirname, relative, isAbsolute } from 'node:path';
10
+ import { execFile } from 'node:child_process';
11
+ import { assertCommandAllowed } from './commandGate.js';
12
+ // Hand-rolled rather than util.promisify(execFile) — Node's real execFile
13
+ // resolves {stdout, stderr} via an internal util.promisify.custom symbol
14
+ // that a mocked module in tests won't have, so promisify(execFile) silently
15
+ // degrades to generic single-value promisify (stdout only, stderr dropped)
16
+ // under mocking. This wrapper has one obvious behavior either way.
17
+ function execFileAsync(command, args, options) {
18
+ return new Promise((resolvePromise, rejectPromise) => {
19
+ execFile(command, args, options, (error, stdout, stderr) => {
20
+ if (error) {
21
+ const failure = error;
22
+ failure.stdout = String(stdout ?? '');
23
+ failure.stderr = String(stderr ?? '');
24
+ rejectPromise(failure);
25
+ }
26
+ else {
27
+ resolvePromise({ stdout: String(stdout ?? ''), stderr: String(stderr ?? '') });
28
+ }
29
+ });
30
+ });
31
+ }
32
+ export const CODING_TOOL_DEFS = [
33
+ {
34
+ name: 'read_file',
35
+ description: 'Read a text file, relative to the target repo root.',
36
+ inputSchema: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] },
37
+ },
38
+ {
39
+ name: 'write_file',
40
+ description: 'Write (create or overwrite) a text file, relative to the target repo root. Creates parent ' +
41
+ 'directories as needed. This is how you write the generated spec and any config files.',
42
+ inputSchema: {
43
+ type: 'object',
44
+ properties: { path: { type: 'string' }, content: { type: 'string' } },
45
+ required: ['path', 'content'],
46
+ },
47
+ },
48
+ {
49
+ name: 'list_directory',
50
+ description: 'List entries (files and directories) in a directory, relative to the target repo root.',
51
+ inputSchema: { type: 'object', properties: { path: { type: 'string' } }, required: [] },
52
+ },
53
+ {
54
+ name: 'run_command',
55
+ description: 'Run an allowlisted shell command (npm init/install -D, npx playwright install/--version/test, ' +
56
+ 'node --version, git status/diff) in the target repo. Anything outside that allowlist is refused ' +
57
+ 'before it runs. Use this for Phase 0 bootstrap and Phase 6 verification — never claim a test ' +
58
+ 'passed without actually running it here.',
59
+ inputSchema: {
60
+ type: 'object',
61
+ properties: {
62
+ command: { type: 'string', description: 'The binary, e.g. "npm", "npx", "node", "git".' },
63
+ args: { type: 'array', items: { type: 'string' }, description: 'Argv, one element per argument.' },
64
+ },
65
+ required: ['command', 'args'],
66
+ },
67
+ },
68
+ ];
69
+ /** Wraps a real filesystem + shell surface, scoped to one repo root, tracking what actually happened. */
70
+ export class CodingTools {
71
+ repoPath;
72
+ commandTimeoutMs;
73
+ writtenPaths = new Map(); // relative path -> last-written timestamp
74
+ commandHistory = [];
75
+ constructor(repoPath, commandTimeoutMs) {
76
+ this.repoPath = repoPath;
77
+ this.commandTimeoutMs = commandTimeoutMs;
78
+ }
79
+ resolveScoped(relPath) {
80
+ const resolved = resolve(this.repoPath, relPath);
81
+ const rel = relative(this.repoPath, resolved);
82
+ if (rel.startsWith('..') || isAbsolute(rel)) {
83
+ throw new Error(`Path "${relPath}" escapes the target repo root — refusing.`);
84
+ }
85
+ return resolved;
86
+ }
87
+ /** Relative paths written so far, each with the timestamp of its most recent write. */
88
+ getWrittenPaths() {
89
+ return new Map(this.writtenPaths);
90
+ }
91
+ getCommandHistory() {
92
+ return [...this.commandHistory];
93
+ }
94
+ /** The most recent `npx playwright test` invocation's real, execFile-reported outcome — or null if none ran. */
95
+ lastPlaywrightTestRun() {
96
+ for (let i = this.commandHistory.length - 1; i >= 0; i--) {
97
+ const r = this.commandHistory[i];
98
+ if (r.command === 'npx' && r.args[0] === 'playwright' && r.args[1] === 'test')
99
+ return r;
100
+ }
101
+ return null;
102
+ }
103
+ async dispatch(name, args) {
104
+ switch (name) {
105
+ case 'read_file': {
106
+ const rawPath = String(args.path ?? '');
107
+ try {
108
+ const content = await readFile(this.resolveScoped(rawPath), 'utf-8');
109
+ return {
110
+ ok: true,
111
+ text: content.length > 50_000 ? `${content.slice(0, 50_000)}\n... (truncated)` : content,
112
+ };
113
+ }
114
+ catch (err) {
115
+ return { ok: false, text: `Could not read "${rawPath}": ${err.message}` };
116
+ }
117
+ }
118
+ case 'write_file': {
119
+ const rawPath = String(args.path ?? '');
120
+ const content = String(args.content ?? '');
121
+ const resolved = this.resolveScoped(rawPath);
122
+ await mkdir(dirname(resolved), { recursive: true });
123
+ await writeFile(resolved, content, 'utf-8');
124
+ this.writtenPaths.set(rawPath, Date.now());
125
+ return { ok: true, text: `Wrote ${content.length} bytes to ${rawPath}` };
126
+ }
127
+ case 'list_directory': {
128
+ const rawPath = String(args.path ?? '.');
129
+ try {
130
+ const entries = await readdir(this.resolveScoped(rawPath), { withFileTypes: true });
131
+ const lines = entries.map((e) => `${e.isDirectory() ? 'dir ' : 'file'} ${e.name}`).sort();
132
+ return { ok: true, text: lines.join('\n') || '(empty directory)' };
133
+ }
134
+ catch (err) {
135
+ return { ok: false, text: `Could not list "${rawPath}": ${err.message}` };
136
+ }
137
+ }
138
+ case 'run_command': {
139
+ const command = String(args.command ?? '');
140
+ const cmdArgs = Array.isArray(args.args) ? args.args.map(String) : [];
141
+ try {
142
+ assertCommandAllowed(command, cmdArgs);
143
+ }
144
+ catch (err) {
145
+ return { ok: false, text: err.message };
146
+ }
147
+ return this.runAllowed(command, cmdArgs);
148
+ }
149
+ default:
150
+ return { ok: false, text: `Unknown coding tool "${name}"` };
151
+ }
152
+ }
153
+ async runAllowed(command, cmdArgs) {
154
+ try {
155
+ const { stdout, stderr } = await execFileAsync(command, cmdArgs, {
156
+ cwd: this.repoPath,
157
+ timeout: this.commandTimeoutMs,
158
+ maxBuffer: 10 * 1024 * 1024,
159
+ });
160
+ this.commandHistory.push({ command, args: cmdArgs, exitCode: 0, ok: true, timestamp: Date.now() });
161
+ const out = `${stdout}${stderr ? `\n[stderr]\n${stderr}` : ''}`.trim();
162
+ return { ok: true, text: out.length > 20_000 ? out.slice(-20_000) : out || '(no output, exit 0)' };
163
+ }
164
+ catch (err) {
165
+ const e = err;
166
+ const exitCode = typeof e.code === 'number' ? e.code : null;
167
+ this.commandHistory.push({ command, args: cmdArgs, exitCode, ok: false, timestamp: Date.now() });
168
+ const out = `${e.stdout ?? ''}\n[stderr]\n${e.stderr ?? e.message}`.trim();
169
+ return { ok: false, text: out.length > 20_000 ? out.slice(-20_000) : out };
170
+ }
171
+ }
172
+ }
173
+ //# sourceMappingURL=codingTools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codingTools.js","sourceRoot":"","sources":["../../src/tools/codingTools.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ // This agent's ONE genuinely new, more-dangerous capability compared to the
2
+ // rest of the family: it can run real shell commands (npm/npx/git), needed
3
+ // to bootstrap a target repo's Playwright setup and actually execute the
4
+ // generated spec — appq:automate's Phase 0/5/6 assume exactly this.
5
+ //
6
+ // Hardcoded, non-negotiable invariant (same class of thing as
7
+ // destructiveActionGate.ts / the appq tool allowlists elsewhere in this
8
+ // family): an explicit allowlist of (command, argv-shape), checked BEFORE
9
+ // execution, never widened by prompt text. The actual execution path
10
+ // (codingTools.ts) uses child_process.execFile with an argv array — never a
11
+ // shell string — so even an allowed command can't smuggle a second command
12
+ // via `;`/`&&`/backticks; the OS never parses the arguments as shell syntax
13
+ // in the first place. The allowlist is defense in depth on top of that, not
14
+ // the only layer.
15
+ const PACKAGE_SPEC_RE = /^@?[a-z0-9][a-z0-9._/-]*(@[\w.\-^~]+)?$/i;
16
+ function isPlainPackageSpec(spec) {
17
+ return PACKAGE_SPEC_RE.test(spec) && !spec.includes('..') && !spec.startsWith('-');
18
+ }
19
+ /** A path-shaped argument (a test file glob/relative path) — no traversal, no shell metacharacters. */
20
+ function isSafePathArg(arg) {
21
+ return !arg.includes('..') && !/[;&|`$(){}<>]/.test(arg) && !arg.startsWith('-');
22
+ }
23
+ const ALLOWED_COMMANDS = {
24
+ npm: (args) => {
25
+ if (args.length === 2 && args[0] === 'init' && args[1] === '-y')
26
+ return true;
27
+ // npm install -D <package...> — dev deps only, plain package specs only.
28
+ if (args.length >= 3 && args[0] === 'install' && args[1] === '-D') {
29
+ return args.slice(2).every(isPlainPackageSpec);
30
+ }
31
+ return false;
32
+ },
33
+ npx: (args) => {
34
+ if (args[0] !== 'playwright')
35
+ return false;
36
+ if (args[1] === '--version' && args.length === 2)
37
+ return true;
38
+ if (args[1] === 'install') {
39
+ // optional browser names (chromium/firefox/webkit) or --with-deps
40
+ return args.slice(2).every((a) => /^(chromium|firefox|webkit|--with-deps)$/.test(a));
41
+ }
42
+ if (args[1] === 'test') {
43
+ // optional --grep "<title>" and/or a relative spec file path
44
+ return args.slice(2).every((a) => isSafePathArg(a) || a === '--grep');
45
+ }
46
+ return false;
47
+ },
48
+ node: (args) => args.length === 1 && args[0] === '--version',
49
+ git: (args) => {
50
+ if (args.length === 1 && args[0] === 'status')
51
+ return true;
52
+ if (args.length === 1 && args[0] === 'diff')
53
+ return true;
54
+ if (args.length === 2 && args[0] === 'diff' && args[1] === '--stat')
55
+ return true;
56
+ return false;
57
+ },
58
+ };
59
+ export function assertCommandAllowed(command, args) {
60
+ const validator = ALLOWED_COMMANDS[command];
61
+ if (!validator || !validator(args)) {
62
+ throw new Error(`Command "${command} ${args.join(' ')}" is not in the allowlist. This is a hardcoded ` +
63
+ `boundary — no workflow prompt can widen it. Allowed: npm init -y, npm install -D <pkgs>, ` +
64
+ `npx playwright install/--version/test, node --version, git status/diff.`);
65
+ }
66
+ }
67
+ //# sourceMappingURL=commandGate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commandGate.js","sourceRoot":"","sources":["../../src/tools/commandGate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ // This agent's own domain knowledge of which appq tools it may touch — the
2
+ // enforcement mechanism (assertToolAllowed / the gated dispatcher) lives in
3
+ // @appliqation/agent-core, shared with every sibling agent; only the
4
+ // allowlist content is local. Zero write tools — not gated behind a flag,
5
+ // genuinely absent from the set. This agent never calls an appq write tool
6
+ // and never performs a git operation; it writes local files only, and a
7
+ // separate future agent is responsible for reviewing/committing/pushing
8
+ // them.
9
+ export const READONLY_CONTEXT_TOOLS = new Set([
10
+ 'get_scenario',
11
+ 'get_defect_context',
12
+ 'get_analytics',
13
+ 'get_failure_patterns',
14
+ 'get_execution_evidence',
15
+ 'get_run_evidence',
16
+ 'get_automation_readiness',
17
+ 'get_coverage_analysis',
18
+ 'get_evidence_summary',
19
+ 'get_test_results',
20
+ 'get_project_settings',
21
+ 'get_validation_targets',
22
+ 'search_tests',
23
+ 'get_project_test_data',
24
+ ]);
25
+ //# sourceMappingURL=safety.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safety.js","sourceRoot":"","sources":["../../src/tools/safety.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,4EAA4E;AAC5E,qEAAqE;AACrE,0EAA0E;AAC1E,2EAA2E;AAC3E,wEAAwE;AACxE,wEAAwE;AACxE,QAAQ;AAER,MAAM,CAAC,MAAM,sBAAsB,GAAG,IAAI,GAAG,CAAC;IAC5C,cAAc;IACd,oBAAoB;IACpB,eAAe;IACf,sBAAsB;IACtB,wBAAwB;IACxB,kBAAkB;IAClB,0BAA0B;IAC1B,uBAAuB;IACvB,sBAAsB;IACtB,kBAAkB;IAClB,sBAAsB;IACtB,wBAAwB;IACxB,cAAc;IACd,uBAAuB;CACxB,CAAC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@appliqation/scriptgen",
3
+ "version": "0.1.1",
4
+ "description": "Standalone agent that generates enterprise-grade Playwright automation scripts for Appliqation test cases, grounded in every available signal (manual/agentic execution evidence, defects, flakiness, sibling test cases) and verified by actually running the result.",
5
+ "type": "module",
6
+ "bin": {
7
+ "appliqation-scriptgen": "./dist/cli/index.js"
8
+ },
9
+ "engines": {
10
+ "node": ">=20"
11
+ },
12
+ "files": [
13
+ "dist/",
14
+ "README.md",
15
+ "LICENSE"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsc -p tsconfig.build.json",
19
+ "dev": "tsx src/cli/index.ts",
20
+ "typecheck": "tsc -p tsconfig.json --noEmit",
21
+ "lint": "eslint src --ext .ts",
22
+ "test": "vitest run",
23
+ "test:watch": "vitest"
24
+ },
25
+ "dependencies": {
26
+ "@appliqation/agent-core": "^0.1.0",
27
+ "@appliqation/automation-sdk": "^2.7.0",
28
+ "commander": "^13.1.0",
29
+ "dotenv": "^16.4.7"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^22.13.10",
33
+ "tsx": "^4.19.3",
34
+ "typescript": "^5.8.2",
35
+ "vitest": "^3.2.7"
36
+ }
37
+ }