@appliqation/defect-fix 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,71 @@
1
+ # Appliqation Defect-Fix
2
+
3
+ **Loads full defect context, locates and applies a real code fix, syncs the Appliqation scenario, and verifies it by actually running Playwright — never by asking the model if it thinks the fix works.**
4
+
5
+ Point it at a defect ID and it investigates like an engineer would: reads the tester's report, the failing test steps, related defects on the same component, console/network errors, and the routes involved — then reads and edits your actual source, and only calls it done once a real Playwright run against the fix passes.
6
+
7
+ ## Why this exists
8
+
9
+ Autotest tells you a test case is failing. Scriptgen locks in coverage once behaviour is known-good. Neither one touches the underlying bug. This is the agent that closes that gap: given a defect, it does the investigation, applies the fix, and proves it — the same three-phase discipline (investigate → fix → verify with a real tool call) every other agent in this family already applies to its own narrower job.
10
+
11
+ ## How it works
12
+
13
+ ```mermaid
14
+ flowchart TD
15
+ A[defect ID] --> B[load context: tester report,<br/>failing steps, defect history,<br/>console/network errors, routes]
16
+ B --> C[locate the code:<br/>routes_visited -> source files]
17
+ C --> D[apply a fix]
18
+ D --> E[sync the Appliqation scenario<br/>if new/changed test coverage is needed]
19
+ E --> F[npx playwright test]
20
+ F --> G{passed?}
21
+ G -- no --> D
22
+ G -- yes --> H[report: verified: true<br/>+ files changed]
23
+ ```
24
+
25
+ - **The one agent besides autotest's validator with real Appliqation write access** — it can sync test cases (`update_test_cases`/`add_test_cases`) and create a verification run, gated behind `--dry-run` so you can watch it work before trusting it with real writes.
26
+ - **`--test-instruction`** lets a caller (typically [`appliqation-autopilot`](https://github.com/appliqation/appliqation-autopilot), which has the broader context to judge this) specify testing scope beyond the default single-test-case re-run — e.g. "this touches shared validation code, re-verify the whole scenario."
27
+ - **No git operation.** Like scriptgen, this agent writes local files only; [`appliqation-pr-raise`](https://github.com/appliqation/appliqation-pr-raise) handles committing and opening the PR.
28
+
29
+ ## Quick start
30
+
31
+ ```bash
32
+ npm install -g @appliqation/defect-fix
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 # needs write access
39
+ ANTHROPIC_API_KEY=your-anthropic-key # or OPENAI_API_KEY — pick one
40
+ ```
41
+
42
+ ```bash
43
+ appliqation-defect-fix fix \
44
+ --defect-id <id> \
45
+ --repo-path /path/to/your/checkout \
46
+ --dry-run
47
+ ```
48
+
49
+ `--dry-run` is the recommended default for your first run against a real project — the code investigation, fix, and Playwright verification all happen for real, but the Appliqation scenario/run writeback is suppressed and logged instead of sent. Add `--test-instruction "<text>"` to specify verification scope, and `--json`/`--ci` for a structured summary + CI-friendly exit code.
50
+
51
+ ## Configuration
52
+
53
+ Copy `.env.example` to `.env`. Requires `APPQ_API_KEY` (with write access, unless every run uses `--dry-run`) and one of `ANTHROPIC_API_KEY`/`OPENAI_API_KEY`.
54
+
55
+ ## Development
56
+
57
+ ```bash
58
+ git clone https://github.com/appliqation/appliqation-defect-fix.git
59
+ cd appliqation-defect-fix
60
+ npm install
61
+ cp .env.example .env # fill in APPQ_API_KEY (needs write access) and one LLM provider key
62
+ npm run dev -- fix --defect-id <id> --repo-path <path>
63
+ npm run typecheck
64
+ npm test
65
+ ```
66
+
67
+ See `CLAUDE.md` for a map of this repo if you're working in it with an AI coding assistant.
68
+
69
+ ## License
70
+
71
+ 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 recordFixRun(args) {
7
+ const { sink, startedAt, endedAt, model, usage, defectId, dryRun, result } = args;
8
+ const summary = result
9
+ ? { defectId, writtenPaths: result.writtenPaths, testRan: result.testRun.ran, verified: result.testRun.ok, dryRun, report: result.report }
10
+ : undefined;
11
+ await safeRecord(sink, {
12
+ agent: 'appliqation-defect-fix',
13
+ subcommand: 'fix',
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 } : { defectId, dryRun, 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;AAe1C,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAsB;IACvD,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAClF,MAAM,OAAO,GAA2B,MAAM;QAC5C,CAAC,CAAC,EAAE,QAAQ,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,EAAE,MAAM,CAAC,MAAM,EAAE;QAC1I,CAAC,CAAC,SAAS,CAAC;IAEd,MAAM,UAAU,CAAC,IAAI,EAAE;QACrB,KAAK,EAAE,wBAAwB;QAC/B,UAAU,EAAE,KAAK;QACjB,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,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE;KACtE,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env node
2
+ // `fix`: load full defect context, locate and apply a code fix, sync the
3
+ // Appliqation scenario, and verify by actually running Playwright — via
4
+ // appq's own appq:fix workflow given real filesystem + shell tools plus
5
+ // (dry-run-gated) appq write access. See src/orchestrator/fix.ts for the
6
+ // actual mechanism.
7
+ import { Command } from 'commander';
8
+ import { createMcpClient, createAnthropicAdapter, createOpenAiAdapter, createUsageAccumulator } from '@appliqation/agent-core';
9
+ import { config, resolveProvider, resolveModel } from '../config/env.js';
10
+ import { fix } from '../orchestrator/fix.js';
11
+ import { recordFixRun } from './audit.js';
12
+ import { printJsonSummary, printHumanSummary, exitCodeFor } from './output.js';
13
+ const client = createMcpClient({ origin: config.appqOrigin, apiKey: config.appqApiKey() });
14
+ function buildAdapter() {
15
+ const provider = resolveProvider();
16
+ const model = resolveModel();
17
+ return provider === 'anthropic'
18
+ ? createAnthropicAdapter(config.anthropicApiKey, model, config.anthropicMaxTokens)
19
+ : createOpenAiAdapter(config.openaiApiKey, model, config.openaiMaxOutputTokens);
20
+ }
21
+ function logEvent(prefix) {
22
+ return (e) => {
23
+ if (e.type === 'assistant') {
24
+ const text = (e.detail ?? '').trim();
25
+ if (text)
26
+ console.error(`${prefix}[thinking] ${text}`);
27
+ }
28
+ else if (e.type === 'tool') {
29
+ const d = e.detail;
30
+ console.error(`${prefix}[tool] ${d.name} -> ${d.result.slice(0, 300)}`);
31
+ }
32
+ else if (e.type === 'log') {
33
+ console.error(`${prefix}[log] ${e.detail}`);
34
+ }
35
+ else if (e.type === 'usage') {
36
+ const u = e.detail;
37
+ const cacheNote = u.cacheReadTokens
38
+ ? ` (${u.cacheReadTokens} from cache)`
39
+ : u.cacheWriteTokens
40
+ ? ` (${u.cacheWriteTokens} written to cache)`
41
+ : '';
42
+ console.error(`${prefix}[usage] in=${u.inputTokens} out=${u.outputTokens}${cacheNote}`);
43
+ }
44
+ };
45
+ }
46
+ const program = new Command();
47
+ program
48
+ .name('appliqation-defect-fix')
49
+ .description('Fix an Appliqation defect: load full context, locate and apply a code fix, sync the scenario, and verify it by actually running Playwright.');
50
+ program
51
+ .command('fix')
52
+ .description("Fix one defect via appq's appq:fix workflow (context: defect text, test steps, run context, defect " +
53
+ 'history, console/network errors, recording URL), given real filesystem + an allowlisted shell so it ' +
54
+ 'can locate and edit source, plus dry-run-gated appq write access so it can sync the Appliqation ' +
55
+ 'scenario and create a real run to verify against — `npx playwright test` is what decides pass/fail, ' +
56
+ "never the model's own claim. No git operation — a separate agent (appliqation-pr-raise) commits and " +
57
+ 'pushes whatever this one writes locally. project_id/scenario_id/test_case_uuid are never CLI options ' +
58
+ "— appq:fix's own Phase 1 derives all of that from get_defect_context.")
59
+ .requiredOption('--defect-id <id>', 'defect ID to fix')
60
+ .option('--repo-path <path>', 'target repo root every file/command tool call is scoped to', process.cwd())
61
+ .option('--test-instruction <text>', 'testing scope required beyond appq:fix\'s own Phase 5 default (re-running just the reproducing test ' +
62
+ 'case) — e.g. "also re-run the whole scenario, this component has a history of regressions." Typically ' +
63
+ 'supplied by a caller (like appliqation-autopilot) that has already gathered evidence about how much ' +
64
+ 'verification this fix actually warrants.')
65
+ .option('--dry-run', 'apply and verify the fix normally, but suppress update_test_cases/add_test_cases/update_run_results — logs what would have been sent instead')
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 dryRun = opts.dryRun ?? false;
73
+ if (dryRun)
74
+ console.error('[setup] dry-run: appq test-case/run writes will be suppressed.');
75
+ const budget = { ...config.budget, ...(opts.maxTurns ? { maxTurns: Number(opts.maxTurns) } : {}) };
76
+ const startedAt = Date.now();
77
+ const usage = createUsageAccumulator();
78
+ const baseLog = logEvent('');
79
+ let result;
80
+ try {
81
+ result = await fix({
82
+ client,
83
+ adapter,
84
+ defectId: opts.defectId,
85
+ repoPath: opts.repoPath,
86
+ budget,
87
+ commandTimeoutMs: config.commandTimeoutMs,
88
+ dryRun,
89
+ testInstruction: opts.testInstruction,
90
+ onEvent: (e) => {
91
+ baseLog(e);
92
+ if (e.type === 'usage')
93
+ usage.onUsage(e.detail);
94
+ },
95
+ });
96
+ }
97
+ finally {
98
+ // Audit write happens whether the run succeeded or threw — see
99
+ // @appliqation/agent-core's audit/sink.ts: safeRecord() (used
100
+ // inside recordFixRun) never lets a failed/unreachable audit sink
101
+ // affect this process's real outcome.
102
+ await recordFixRun({ sink: config.auditSink, startedAt, endedAt: Date.now(), model: resolveModel(), usage: usage.totals(), defectId: opts.defectId, dryRun, result });
103
+ }
104
+ if (!json) {
105
+ console.log('\n=== Report ===\n');
106
+ console.log(result.report);
107
+ console.error(`\n(${result.turns} turns, budget exceeded: ${result.budgetExceeded})`);
108
+ }
109
+ const summary = {
110
+ defectId: opts.defectId,
111
+ writtenPaths: result.writtenPaths,
112
+ testRan: result.testRun.ran,
113
+ verified: result.testRun.ok,
114
+ dryRun,
115
+ report: result.report,
116
+ };
117
+ if (json)
118
+ printJsonSummary(summary);
119
+ else
120
+ printHumanSummary(summary);
121
+ process.exitCode = exitCodeFor(summary);
122
+ });
123
+ program.parseAsync(process.argv);
124
+ //# 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,32 @@
1
+ // --json/--ci's renderer, matching appliqation-scriptgen's output.ts shape.
2
+ // exitCodeFor() never trusts the model's own report text — only
3
+ // FixResult.testRun.ok, which is derived from a real execFile exit code,
4
+ // decides success. A file that was never actually run — or was run before
5
+ // its last edit — is not a verified fix, 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=== Defect ${summary.defectId} ===\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
+ if (summary.dryRun) {
25
+ console.log('\n Dry run: any Appliqation test-case/run writes were suppressed, not actually sent.');
26
+ }
27
+ }
28
+ /** 1 unless the file was actually run — via a real, execFile-reported exit code — after its last edit, and passed. */
29
+ export function exitCodeFor(summary) {
30
+ return summary.testRan && summary.verified ? 0 : 1;
31
+ }
32
+ //# sourceMappingURL=output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.js","sourceRoot":"","sources":["../../src/cli/output.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,gEAAgE;AAChE,yEAAyE;AACzE,0EAA0E;AAC1E,2EAA2E;AAW3E,MAAM,UAAU,gBAAgB,CAAC,OAAmB;IAClD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,OAAmB;IACnD,OAAO,CAAC,GAAG,CAAC,gBAAgB,OAAO,CAAC,QAAQ,QAAQ,CAAC,CAAC;IACtD,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;IACD,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,OAAO,CAAC,GAAG,CAAC,uFAAuF,CAAC,CAAC;IACvG,CAAC;AACH,CAAC;AAED,sHAAsH;AACtH,MAAM,UAAU,WAAW,CAAC,OAAmB;IAC7C,OAAO,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACrD,CAAC"}
@@ -0,0 +1,48 @@
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 — no executor/validator split here, just one
15
+ // investigate-fix-sync-verify pass that may need several rounds of "run
16
+ // the test, read the failure, adjust the fix".
17
+ budget: {
18
+ maxCalls: Number(optional('BUDGET_MAX_CALLS') ?? 60),
19
+ // No browser tools offered at all, so BudgetTracker's page counter never
20
+ // increments — a large, effectively-unreachable cap rather than 0 (0
21
+ // would trip immediately, since exceeded() checks pages >= maxPages).
22
+ maxPages: Number(optional('BUDGET_MAX_PAGES') ?? 999_999),
23
+ maxMillis: Number(optional('BUDGET_MAX_MILLIS') ?? 20 * 60 * 1000),
24
+ maxTurns: Number(optional('BUDGET_MAX_TURNS') ?? 60),
25
+ },
26
+ // Wall-clock cap per run_command invocation (npm install / playwright
27
+ // test can each legitimately take a while) — separate from the overall budget.
28
+ commandTimeoutMs: Number(optional('COMMAND_TIMEOUT_MS') ?? 5 * 60 * 1000),
29
+ // Observability, entirely opt-in — see @appliqation/agent-core's audit/sink.ts.
30
+ auditSink: resolveAuditSink({
31
+ auditMongoUri: optional('AUDIT_MONGO_URI'),
32
+ auditMongoDb: optional('AUDIT_MONGO_DB'),
33
+ auditMongoCollection: optional('AUDIT_MONGO_COLLECTION'),
34
+ auditJsonlPath: optional('AUDIT_JSONL_PATH'),
35
+ }),
36
+ };
37
+ export function resolveProvider() {
38
+ if (config.anthropicApiKey)
39
+ return 'anthropic';
40
+ if (config.openaiApiKey)
41
+ return 'openai';
42
+ throw new Error('Set ANTHROPIC_API_KEY or OPENAI_API_KEY');
43
+ }
44
+ export function resolveModel() {
45
+ const provider = resolveProvider();
46
+ return provider === 'anthropic' ? (config.anthropicModel ?? DEFAULT_ANTHROPIC_MODEL) : (config.openaiModel ?? DEFAULT_OPENAI_MODEL);
47
+ }
48
+ //# 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,yEAAyE;IACzE,wEAAwE;IACxE,+CAA+C;IAC/C,MAAM,EAAE;QACN,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAC,IAAI,EAAE,CAAC;QACpD,yEAAyE;QACzE,qEAAqE;QACrE,sEAAsE;QACtE,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,sEAAsE;IACtE,+EAA+E;IAC/E,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,65 @@
1
+ // Calls appq's own appq:fix workflow through the shared engine, offering it
2
+ // three tool surfaces: read-only appq context tools (get_defect_context,
3
+ // get_scenario, ...), writable appq tools (update_test_cases/add_test_cases/
4
+ // update_run_results — gated behind --dry-run, see tools/dryRun.ts), and
5
+ // real coding tools (read_file/write_file/list_directory/run_command,
6
+ // scoped to one repo). No git operation — a separate agent
7
+ // (appliqation-pr-raise) is responsible for committing/pushing what this
8
+ // one writes locally.
9
+ import { fetchAppqToolDefs, createGatedAppqDispatcher, runWorkflow, } from '@appliqation/agent-core';
10
+ import { READONLY_CONTEXT_TOOLS, WRITABLE_APPQ_TOOLS } from '../tools/safety.js';
11
+ import { CODING_TOOL_DEFS, CodingTools } from '../tools/codingTools.js';
12
+ import { createDryRunDispatcher } from '../tools/dryRun.js';
13
+ function seedMessage(opts) {
14
+ const lines = [
15
+ `Defect ID: ${opts.defectId}`,
16
+ `Target repo root (every file/command tool call is scoped here): ${opts.repoPath}`,
17
+ ];
18
+ if (opts.dryRun) {
19
+ lines.push('Dry run: update_test_cases/add_test_cases/update_run_results calls will be logged, not actually sent ' +
20
+ 'to Appliqation — proceed exactly as you normally would, the suppression happens below you.');
21
+ }
22
+ if (opts.testInstruction) {
23
+ lines.push(`Required testing scope for Phase 5, beyond just re-running the reproducing test case: ${opts.testInstruction}`);
24
+ }
25
+ lines.push('Begin now — start with get_defect_context.');
26
+ return lines.join('\n');
27
+ }
28
+ export async function fix(opts) {
29
+ const coding = new CodingTools(opts.repoPath, opts.commandTimeoutMs);
30
+ const appqAllowlist = new Set([...READONLY_CONTEXT_TOOLS, ...WRITABLE_APPQ_TOOLS]);
31
+ const appqToolDefs = await fetchAppqToolDefs(opts.client, appqAllowlist);
32
+ const gatedAppq = createDryRunDispatcher(createGatedAppqDispatcher(opts.client, appqAllowlist), opts.dryRun);
33
+ const codingToolNames = new Set(CODING_TOOL_DEFS.map((t) => t.name));
34
+ const dispatch = async (name, args) => {
35
+ if (codingToolNames.has(name))
36
+ return coding.dispatch(name, args);
37
+ return gatedAppq(name, args);
38
+ };
39
+ const loopResult = await runWorkflow({
40
+ source: { kind: 'appq', name: 'appq:fix', args: { defect_id: opts.defectId } },
41
+ fetchPrompt: opts.client.fetchPrompt,
42
+ seedMessage: seedMessage(opts),
43
+ tools: [...appqToolDefs, ...CODING_TOOL_DEFS],
44
+ dispatch,
45
+ adapter: opts.adapter,
46
+ budget: opts.budget,
47
+ onEvent: opts.onEvent,
48
+ });
49
+ const writtenPaths = coding.getWrittenPaths();
50
+ const lastTestRun = coding.lastPlaywrightTestRun();
51
+ const lastWriteAt = writtenPaths.size > 0 ? Math.max(...writtenPaths.values()) : 0;
52
+ const verifiedAfterLastWrite = lastTestRun !== null && lastTestRun.ok && lastTestRun.timestamp >= lastWriteAt;
53
+ return {
54
+ report: loopResult.report,
55
+ turns: loopResult.turns,
56
+ budgetExceeded: loopResult.budgetExceeded,
57
+ writtenPaths: [...writtenPaths.keys()],
58
+ testRun: {
59
+ ran: lastTestRun !== null,
60
+ ok: verifiedAfterLastWrite,
61
+ exitCode: lastTestRun?.exitCode ?? null,
62
+ },
63
+ };
64
+ }
65
+ //# sourceMappingURL=fix.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fix.js","sourceRoot":"","sources":["../../src/orchestrator/fix.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,173 @@
1
+ // The capability appq:fix's own prompt (Phase 2/4/5) assumes a full coding-
2
+ // agent session has: read local code to locate the defect, write a fix, and
3
+ // run real shell commands to actually verify it. Every path is scoped to
4
+ // repoPath (no traversal out of it); every command goes through
5
+ // commandGate.ts's allowlist AND is spawned via execFile with an explicit
6
+ // argv array — never a shell string — so the OS never parses arguments as
7
+ // 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 apply the code fix.',
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 5 verification — never claim a fix works without actually ' +
58
+ 'running the test 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,77 @@
1
+ // This agent's own real shell capability, needed to actually run the
2
+ // Playwright test appq:fix's Phase 5 requires for verification — same
3
+ // mechanism appliqation-scriptgen already established for the same reason.
4
+ //
5
+ // Hardcoded, non-negotiable invariant (same class of thing as
6
+ // destructiveActionGate.ts / the appq tool allowlists elsewhere in this
7
+ // family): an explicit allowlist of (command, argv-shape), checked BEFORE
8
+ // execution, never widened by prompt text. The actual execution path
9
+ // (codingTools.ts) uses child_process.execFile with an argv array — never a
10
+ // shell string — so even an allowed command can't smuggle a second command
11
+ // via `;`/`&&`/backticks; the OS never parses the arguments as shell syntax
12
+ // in the first place. The allowlist is defense in depth on top of that, not
13
+ // the only layer.
14
+ //
15
+ // Extends scriptgen's own allowlist in one place: appq:fix's Phase 5
16
+ // verification invocation is `npx playwright test -- --appq-run-id={run_id}
17
+ // --grep "{name}"` verbatim — the literal `--` separator and an
18
+ // `--appq-run-id=<id>` flag need to be recognized, and --grep must be
19
+ // optional so a broader (whole-file/whole-suite) run is possible when a
20
+ // caller's --test-instruction calls for one.
21
+ const PACKAGE_SPEC_RE = /^@?[a-z0-9][a-z0-9._/-]*(@[\w.\-^~]+)?$/i;
22
+ const APPQ_RUN_ID_FLAG_RE = /^--appq-run-id=[\w-]+$/;
23
+ function isPlainPackageSpec(spec) {
24
+ return PACKAGE_SPEC_RE.test(spec) && !spec.includes('..') && !spec.startsWith('-');
25
+ }
26
+ /** A path-shaped argument (a test file glob/relative path) — no traversal, no shell metacharacters. */
27
+ function isSafePathArg(arg) {
28
+ return !arg.includes('..') && !/[;&|`$(){}<>]/.test(arg) && !arg.startsWith('-');
29
+ }
30
+ const ALLOWED_COMMANDS = {
31
+ npm: (args) => {
32
+ if (args.length === 2 && args[0] === 'init' && args[1] === '-y')
33
+ return true;
34
+ // npm install -D <package...> — dev deps only, plain package specs only.
35
+ if (args.length >= 3 && args[0] === 'install' && args[1] === '-D') {
36
+ return args.slice(2).every(isPlainPackageSpec);
37
+ }
38
+ return false;
39
+ },
40
+ npx: (args) => {
41
+ if (args[0] !== 'playwright')
42
+ return false;
43
+ if (args[1] === '--version' && args.length === 2)
44
+ return true;
45
+ if (args[1] === 'install') {
46
+ // optional browser names (chromium/firefox/webkit) or --with-deps
47
+ return args.slice(2).every((a) => /^(chromium|firefox|webkit|--with-deps)$/.test(a));
48
+ }
49
+ if (args[1] === 'test') {
50
+ // optional `--` separator, an --appq-run-id=<id> flag, a --grep
51
+ // "<title>" pair, and/or a relative spec file path — any combination,
52
+ // any order (mirrors appq:fix's exact Phase 5 invocation shape).
53
+ return args.slice(2).every((a) => a === '--' || a === '--grep' || APPQ_RUN_ID_FLAG_RE.test(a) || isSafePathArg(a));
54
+ }
55
+ return false;
56
+ },
57
+ node: (args) => args.length === 1 && args[0] === '--version',
58
+ git: (args) => {
59
+ if (args.length === 1 && args[0] === 'status')
60
+ return true;
61
+ if (args.length === 1 && args[0] === 'diff')
62
+ return true;
63
+ if (args.length === 2 && args[0] === 'diff' && args[1] === '--stat')
64
+ return true;
65
+ return false;
66
+ },
67
+ };
68
+ export function assertCommandAllowed(command, args) {
69
+ const validator = ALLOWED_COMMANDS[command];
70
+ if (!validator || !validator(args)) {
71
+ throw new Error(`Command "${command} ${args.join(' ')}" is not in the allowlist. This is a hardcoded ` +
72
+ `boundary — no workflow prompt can widen it. Allowed: npm init -y, npm install -D <pkgs>, ` +
73
+ `npx playwright install/--version/test (with an optional -- separator, --appq-run-id=<id>, ` +
74
+ `--grep, and/or a spec path), node --version, git status/diff.`);
75
+ }
76
+ }
77
+ //# 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,23 @@
1
+ // --dry-run support: computes/applies the fix normally but suppresses the
2
+ // actual appq writeback (update_test_cases/add_test_cases/update_run_results
3
+ // — see tools/safety.ts's WRITABLE_APPQ_TOOLS). Implemented as a dispatch-
4
+ // level intercept, not a prompt instruction — appq:fix's own workflow prose
5
+ // is what decides to call these, so "don't write" has to be enforced below
6
+ // that, not asked of it. Same pattern as appliqation-autotest's
7
+ // tools/dryRun.ts, adapted to this agent's own write-tool set (test-case
8
+ // sync + run creation, not a verdict/defect write).
9
+ import { WRITABLE_APPQ_TOOLS } from './safety.js';
10
+ export function createDryRunDispatcher(inner, dryRun) {
11
+ if (!dryRun)
12
+ return inner;
13
+ return async (name, args) => {
14
+ if (!WRITABLE_APPQ_TOOLS.has(name))
15
+ return inner(name, args);
16
+ console.error(`[dry-run] would call ${name} with: ${JSON.stringify(args, null, 2)}`);
17
+ return {
18
+ ok: true,
19
+ text: `[dry-run] ${name} suppressed — no write happened. Args were logged for review, not sent to appq.`,
20
+ };
21
+ };
22
+ }
23
+ //# sourceMappingURL=dryRun.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dryRun.js","sourceRoot":"","sources":["../../src/tools/dryRun.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,6EAA6E;AAC7E,2EAA2E;AAC3E,4EAA4E;AAC5E,2EAA2E;AAC3E,gEAAgE;AAChE,yEAAyE;AACzE,oDAAoD;AAGpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAElD,MAAM,UAAU,sBAAsB,CACpC,KAA2E,EAC3E,MAAe;IAEf,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAE1B,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE;QAC1B,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAE7D,OAAO,CAAC,KAAK,CAAC,wBAAwB,IAAI,UAAU,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;QACrF,OAAO;YACL,EAAE,EAAE,IAAI;YACR,IAAI,EAAE,aAAa,IAAI,iFAAiF;SACzG,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC"}
@@ -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.
5
+ //
6
+ // Unlike appliqation-scriptgen (read-only), this agent genuinely needs write
7
+ // access: appq:fix's Phase 4 requires syncing the Appliqation scenario
8
+ // (update_test_cases/add_test_cases) after a code fix, and Phase 5 requires
9
+ // creating a real run (update_run_results) to verify it. That write access
10
+ // is real appq state, not local files — see tools/dryRun.ts for how it's
11
+ // gated behind --dry-run, the same discipline appliqation-autotest's
12
+ // validator already applies to its own writes.
13
+ export const READONLY_CONTEXT_TOOLS = new Set([
14
+ 'get_defect_context',
15
+ 'get_defects',
16
+ 'get_scenario',
17
+ 'get_failure_patterns',
18
+ 'get_run_evidence',
19
+ 'get_execution_evidence',
20
+ 'get_automation_readiness',
21
+ 'get_coverage_analysis',
22
+ 'get_test_results',
23
+ ]);
24
+ export const WRITABLE_APPQ_TOOLS = new Set(['update_test_cases', 'add_test_cases', 'update_run_results']);
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,8BAA8B;AAC9B,EAAE;AACF,6EAA6E;AAC7E,uEAAuE;AACvE,4EAA4E;AAC5E,2EAA2E;AAC3E,yEAAyE;AACzE,qEAAqE;AACrE,+CAA+C;AAE/C,MAAM,CAAC,MAAM,sBAAsB,GAAG,IAAI,GAAG,CAAC;IAC5C,oBAAoB;IACpB,aAAa;IACb,cAAc;IACd,sBAAsB;IACtB,kBAAkB;IAClB,wBAAwB;IACxB,0BAA0B;IAC1B,uBAAuB;IACvB,kBAAkB;CACnB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC,CAAC,mBAAmB,EAAE,gBAAgB,EAAE,oBAAoB,CAAC,CAAC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@appliqation/defect-fix",
3
+ "version": "0.1.1",
4
+ "description": "Standalone agent that fixes an Appliqation defect: loads full defect context, locates and applies a code fix, syncs the Appliqation scenario, and verifies the fix by actually running Playwright.",
5
+ "type": "module",
6
+ "bin": {
7
+ "appliqation-defect-fix": "./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
+ "commander": "^13.1.0",
28
+ "dotenv": "^16.4.7"
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "^22.13.10",
32
+ "tsx": "^4.19.3",
33
+ "typescript": "^5.8.2",
34
+ "vitest": "^3.2.7"
35
+ }
36
+ }