@appliqation/explorer 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,60 @@
1
+ # Appliqation Explorer
2
+
3
+ **Runs Appliqation's exploratory-QA workflow (`appq:runman`) headlessly against a live app under test — the same senior-QA heuristics pass a human runs interactively in Claude Code, without a human at the confirmation gate.**
4
+
5
+ Point it at a URL (or let it resolve one from a project) and a plain-English intent, and it drives a real Playwright browser through surface mapping, a 13-category senior-QA heuristics pass, and mandatory security/network/caching/mobile probes — the exact same methodology Appliqation already serves interactively, just run unattended.
6
+
7
+ ## Why this exists
8
+
9
+ Every other agent in this family answers a narrow, structured question: does this test case pass (autotest), is there a script for it (scriptgen), can this defect be fixed (defect-fix). None of them go looking for the bugs a scripted test never checks for — accessibility gaps, race conditions, caching bugs, things that only show up when someone actually explores. Appliqation already has this covered as `appq:runman`, but until now the only way to run it was interactively. This is the standalone, unattended client for it.
10
+
11
+ ## The one thing this agent deliberately can't do
12
+
13
+ `appq:runman`'s own prompt ends a pass by calling `enrich_project_context` with `action=write`, to persist findings (`known_issues`, `high_risk_areas`, `regression_watchlist`) as memory for future passes. **This agent refuses that call.**
14
+
15
+ When `appq:runman` runs interactively inside Claude Code, a human is present at its Phase 2 confirmation gate and reads the findings report before anything happens next — informal but real supervision over what gets written back as fact for future agents to trust. A standalone, headless invocation has no equivalent: nothing reviews it turn to turn. So this agent holds the conservative default instead: it can read project context (`enrich_project_context` with `action=read` — informs what's worth exploring), but a write attempt is refused at the dispatch layer, in code, before it ever reaches appq — not honored just because the served prompt asks for it. Genuinely read-only end to end; see [`@appliqation/agent-core`](https://github.com/appliqation/appliqation-agent-core)'s `createReadOnlyProjectContextDispatcher`.
16
+
17
+ ## Quick start
18
+
19
+ ```bash
20
+ npm install -g @appliqation/explorer
21
+ npx playwright install chromium
22
+ ```
23
+
24
+ Create a `.env` file (in whatever directory you'll run it from) with:
25
+
26
+ ```
27
+ APPQ_API_KEY=your-appliqation-api-key # read-only is enough
28
+ ANTHROPIC_API_KEY=your-anthropic-key # or OPENAI_API_KEY — pick one
29
+ ```
30
+
31
+ ```bash
32
+ appliqation-explorer explore \
33
+ --prompt "Explore the signup flow like a senior QA lead, focus on the phone number field" \
34
+ --project-id 1349 \
35
+ --site-url https://stage.example.com
36
+ ```
37
+
38
+ Add `--json`/`--ci` for a structured summary. There's no `--dry-run` — this agent never has real write access to anything, so there's nothing to suppress. The exit code is 0 unless the pass hit its own budget cap and ended early; "found bugs" is never a failure — that's the entire point of running it.
39
+
40
+ ## Configuration
41
+
42
+ Copy `.env.example` to `.env`. Requires `APPQ_API_KEY` (read-only access is sufficient) and one of `ANTHROPIC_API_KEY`/`OPENAI_API_KEY`. `EXPLORE_MAX_STEPS`/`EXPLORE_MAX_PAGES`/`EXPLORE_MAX_MINUTES` are the workflow's own self-regulated budget (what the model is told to respect); `BUDGET_MAX_*` is a separate, code-enforced backstop set generously above them in case the model doesn't honor its own limits.
43
+
44
+ ## Development
45
+
46
+ ```bash
47
+ git clone https://github.com/appliqation/appliqation-explorer.git
48
+ cd appliqation-explorer
49
+ npm install
50
+ cp .env.example .env # fill in APPQ_API_KEY (read-only is enough) and one LLM provider key
51
+ npm run dev -- explore --prompt "<text>" [--project-id <id>] [--site-url <url>]
52
+ npm run typecheck
53
+ npm test
54
+ ```
55
+
56
+ See `CLAUDE.md` for a map of this repo if you're working in it with an AI coding assistant.
57
+
58
+ ## License
59
+
60
+ 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
+ export async function recordExploreRun(args) {
6
+ const { sink, startedAt, endedAt, model, usage, prompt, projectId, siteUrl, result } = args;
7
+ await safeRecord(sink, {
8
+ agent: 'appliqation-explorer',
9
+ subcommand: 'explore',
10
+ startedAt,
11
+ endedAt,
12
+ durationMillis: endedAt - startedAt,
13
+ model,
14
+ usage,
15
+ turns: result?.turns,
16
+ budgetExceeded: result?.budgetExceeded,
17
+ // Never based on what the report found — budgetExceeded means the pass
18
+ // ended early, an unrelated failure to explore() throwing outright.
19
+ exitCode: result ? (result.budgetExceeded ? 1 : 0) : 1,
20
+ outcome: result
21
+ ? { prompt, projectId, siteUrl, turns: result.turns, budgetExceeded: result.budgetExceeded, report: result.report }
22
+ : { prompt, projectId, siteUrl, 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;AAgBvF,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,IAA0B;IAC/D,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAC5F,MAAM,UAAU,CAAC,IAAI,EAAE;QACrB,KAAK,EAAE,sBAAsB;QAC7B,UAAU,EAAE,SAAS;QACrB,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,uEAAuE;QACvE,oEAAoE;QACpE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACtD,OAAO,EAAE,MAAM;YACb,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,cAAc,EAAE,MAAM,CAAC,cAAc,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;YACnH,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE;KAChD,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env node
2
+ // `explore`: run appq's real appq:runman exploratory-QA workflow headlessly
3
+ // against a live target. See src/orchestrator/explore.ts for the actual
4
+ // mechanism; this file is just Commander wiring + result rendering.
5
+ import { Command } from 'commander';
6
+ import { createMcpClient, createAnthropicAdapter, createOpenAiAdapter, createUsageAccumulator } from '@appliqation/agent-core';
7
+ import { config, resolveProvider, resolveModel } from '../config/env.js';
8
+ import { explore } from '../orchestrator/explore.js';
9
+ import { recordExploreRun } 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-explorer')
47
+ .description("Run Appliqation's exploratory-QA workflow (appq:runman) headlessly against a live app under test.");
48
+ program
49
+ .command('explore')
50
+ .description("Run appq:runman — surface mapping, a senior-QA heuristics pass, and mandatory security/network/caching/" +
51
+ 'mobile probes — against a real target, always headlessly (interactive: "false", no confirmation gate). ' +
52
+ 'Read-only end to end: offered appq context tools plus enrich_project_context, but its own attempted ' +
53
+ 'action=write persistence at the end of the workflow is refused, not honored — see README.md.')
54
+ .requiredOption('--prompt <text>', 'plain-English exploration intent — can embed a URL')
55
+ .option('--project-id <id>', 'appq project id — enables reading persistent project-context memory')
56
+ .option('--site-url <url>', "override the URL appq:runman would otherwise resolve on its own")
57
+ .option('--max-steps <n>', 'override EXPLORE_MAX_STEPS for this run')
58
+ .option('--max-pages <n>', 'override EXPLORE_MAX_PAGES for this run')
59
+ .option('--max-minutes <n>', 'override EXPLORE_MAX_MINUTES for this run')
60
+ .option('--json', 'print a single structured JSON summary on stdout instead of a human-readable report')
61
+ .option('--ci', 'shorthand for --json')
62
+ .action(async (opts) => {
63
+ const json = (opts.json ?? false) || (opts.ci ?? false);
64
+ const adapter = buildAdapter();
65
+ const projectId = opts.projectId !== undefined ? Number(opts.projectId) : undefined;
66
+ const startedAt = Date.now();
67
+ const usage = createUsageAccumulator();
68
+ const baseLog = logEvent('');
69
+ let result;
70
+ try {
71
+ result = await explore({
72
+ client,
73
+ adapter,
74
+ prompt: opts.prompt,
75
+ projectId,
76
+ siteUrl: opts.siteUrl,
77
+ maxSteps: opts.maxSteps ? Number(opts.maxSteps) : config.exploreMaxSteps,
78
+ maxPages: opts.maxPages ? Number(opts.maxPages) : config.exploreMaxPages,
79
+ maxMinutes: opts.maxMinutes ? Number(opts.maxMinutes) : config.exploreMaxMinutes,
80
+ budget: config.budget,
81
+ ringBufferCap: config.evidenceRingBufferCap,
82
+ onEvent: (e) => {
83
+ baseLog(e);
84
+ if (e.type === 'usage')
85
+ usage.onUsage(e.detail);
86
+ },
87
+ });
88
+ }
89
+ finally {
90
+ // Audit write happens whether the run succeeded or threw — see
91
+ // @appliqation/agent-core's audit/sink.ts: safeRecord() (used
92
+ // inside recordExploreRun) never lets a failed/unreachable audit
93
+ // sink affect this process's real outcome, and this finally never
94
+ // blocks the throw below from propagating unchanged.
95
+ await recordExploreRun({
96
+ sink: config.auditSink,
97
+ startedAt,
98
+ endedAt: Date.now(),
99
+ model: resolveModel(),
100
+ usage: usage.totals(),
101
+ prompt: opts.prompt,
102
+ projectId,
103
+ siteUrl: opts.siteUrl,
104
+ result,
105
+ });
106
+ }
107
+ const summary = {
108
+ prompt: opts.prompt,
109
+ projectId,
110
+ siteUrl: opts.siteUrl,
111
+ turns: result.turns,
112
+ budgetExceeded: result.budgetExceeded,
113
+ report: result.report,
114
+ };
115
+ if (json)
116
+ printJsonSummary(summary);
117
+ else
118
+ printHumanSummary(summary);
119
+ process.exitCode = exitCodeFor(summary);
120
+ });
121
+ program.parseAsync(process.argv);
122
+ //# 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,20 @@
1
+ // --json/--ci's renderer. Unlike appliqation-scriptgen/-defect-fix,
2
+ // "found bugs" is not a failure condition here — that's this agent's whole
3
+ // job. exitCodeFor() is non-zero only when the pass itself didn't
4
+ // complete (budgetExceeded), never based on what the report says it found.
5
+ export function printJsonSummary(summary) {
6
+ console.log(JSON.stringify(summary, null, 2));
7
+ }
8
+ export function printHumanSummary(summary) {
9
+ console.log(`\n=== Exploration: "${summary.prompt}" ===\n`);
10
+ console.log(summary.report);
11
+ console.log(`\n(${summary.turns} turns, budget exceeded: ${summary.budgetExceeded})`);
12
+ if (summary.budgetExceeded) {
13
+ console.log(' Pass ended early on its own budget cap — the report above may be incomplete.');
14
+ }
15
+ }
16
+ /** 1 only when the pass itself didn't complete — never based on what the report found. */
17
+ export function exitCodeFor(summary) {
18
+ return summary.budgetExceeded ? 1 : 0;
19
+ }
20
+ //# sourceMappingURL=output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.js","sourceRoot":"","sources":["../../src/cli/output.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,2EAA2E;AAC3E,kEAAkE;AAClE,2EAA2E;AAW3E,MAAM,UAAU,gBAAgB,CAAC,OAAuB;IACtD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,OAAuB;IACvD,OAAO,CAAC,GAAG,CAAC,uBAAuB,OAAO,CAAC,MAAM,SAAS,CAAC,CAAC;IAC5D,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5B,OAAO,CAAC,GAAG,CAAC,MAAM,OAAO,CAAC,KAAK,4BAA4B,OAAO,CAAC,cAAc,GAAG,CAAC,CAAC;IACtF,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;QAC3B,OAAO,CAAC,GAAG,CAAC,gFAAgF,CAAC,CAAC;IAChG,CAAC;AACH,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,WAAW,CAAC,OAAuB;IACjD,OAAO,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACxC,CAAC"}
@@ -0,0 +1,52 @@
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
+ // appq:runman's own self-regulated budget (max_steps/max_pages/max_minutes
15
+ // args) — what the model is actually told to respect. Defaults match the
16
+ // workflow's own (RunmanPrompt.php).
17
+ exploreMaxSteps: Number(optional('EXPLORE_MAX_STEPS') ?? 50),
18
+ exploreMaxPages: Number(optional('EXPLORE_MAX_PAGES') ?? 12),
19
+ exploreMaxMinutes: Number(optional('EXPLORE_MAX_MINUTES') ?? 15),
20
+ // A code-enforced backstop in case the model doesn't honor the
21
+ // prompt-level limits above — set generously above them, not equal to
22
+ // them (unlike appliqation-scriptgen's ~infinite maxPages, this agent
23
+ // really does drive browser_navigate, so the cap has to be real).
24
+ budget: {
25
+ maxCalls: Number(optional('BUDGET_MAX_CALLS') ?? 150),
26
+ maxPages: Number(optional('BUDGET_MAX_PAGES') ?? 40),
27
+ maxMillis: Number(optional('BUDGET_MAX_MILLIS') ?? 25 * 60 * 1000),
28
+ maxTurns: Number(optional('BUDGET_MAX_TURNS') ?? 80),
29
+ },
30
+ evidenceRingBufferCap: Number(optional('EVIDENCE_RING_BUFFER_CAP') ?? 500),
31
+ // Observability, entirely opt-in — see @appliqation/agent-core's audit/sink.ts.
32
+ // Nothing is recorded anywhere unless AUDIT_MONGO_URI or AUDIT_JSONL_PATH is
33
+ // actually set; resolveAuditSink() falls back to a no-op sink otherwise.
34
+ auditSink: resolveAuditSink({
35
+ auditMongoUri: optional('AUDIT_MONGO_URI'),
36
+ auditMongoDb: optional('AUDIT_MONGO_DB'),
37
+ auditMongoCollection: optional('AUDIT_MONGO_COLLECTION'),
38
+ auditJsonlPath: optional('AUDIT_JSONL_PATH'),
39
+ }),
40
+ };
41
+ export function resolveProvider() {
42
+ if (config.anthropicApiKey)
43
+ return 'anthropic';
44
+ if (config.openaiApiKey)
45
+ return 'openai';
46
+ throw new Error('Set ANTHROPIC_API_KEY or OPENAI_API_KEY');
47
+ }
48
+ export function resolveModel() {
49
+ const provider = resolveProvider();
50
+ return provider === 'anthropic' ? (config.anthropicModel ?? DEFAULT_ANTHROPIC_MODEL) : (config.openaiModel ?? DEFAULT_OPENAI_MODEL);
51
+ }
52
+ //# 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;IAE3E,2EAA2E;IAC3E,yEAAyE;IACzE,qCAAqC;IACrC,eAAe,EAAE,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAC,IAAI,EAAE,CAAC;IAC5D,eAAe,EAAE,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAC,IAAI,EAAE,CAAC;IAC5D,iBAAiB,EAAE,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAAC,IAAI,EAAE,CAAC;IAEhE,+DAA+D;IAC/D,sEAAsE;IACtE,sEAAsE;IACtE,kEAAkE;IAClE,MAAM,EAAE;QACN,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAC,IAAI,GAAG,CAAC;QACrD,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAC,IAAI,EAAE,CAAC;QACpD,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;IAED,qBAAqB,EAAE,MAAM,CAAC,QAAQ,CAAC,0BAA0B,CAAC,IAAI,GAAG,CAAC;IAE1E,gFAAgF;IAChF,6EAA6E;IAC7E,yEAAyE;IACzE,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,67 @@
1
+ // Calls appq's real appq:runman workflow through the shared engine, headlessly
2
+ // (interactive: "false" — no human to answer a mid-run confirmation), offering
3
+ // it real browser_* tools plus a read-only appq context allowlist. The
4
+ // workflow's own methodology (7 phases: prerequisites/URL resolution, context
5
+ // gathering, surface mapping, a senior-QA heuristics pass plus mandatory
6
+ // security/network/caching/mobile probes, an evidence-discipline gate, and a
7
+ // findings report) lives entirely server-side — nothing here duplicates it.
8
+ //
9
+ // Deliberately doesn't structurally parse a bug count or severity out of the
10
+ // returned Markdown report — this agent's job is producing a report for a
11
+ // human or appliqation-autopilot to read and reason about, not a pass/fail
12
+ // verdict a pipeline gates on. budgetExceeded (from LoopResult, unchanged) is
13
+ // the only thing that decides the CLI's exit code — see cli/index.ts.
14
+ import { chromium } from 'playwright';
15
+ import { PlaywrightBrowserTools, BROWSER_TOOL_DEFS, fetchAppqToolDefs, createGatedAppqDispatcher, createReadOnlyProjectContextDispatcher, runWorkflow, } from '@appliqation/agent-core';
16
+ import { READONLY_APPQ_TOOLS } from '../tools/safety.js';
17
+ export async function explore(opts) {
18
+ const appqToolDefs = await fetchAppqToolDefs(opts.client, READONLY_APPQ_TOOLS);
19
+ // Argument-level gate applied outermost — see @appliqation/agent-core's
20
+ // tools/projectContext.ts — so this agent's own attempted
21
+ // enrich_project_context write (appq:runman's own Phase 6 instruction,
22
+ // meant for an interactive session with a human at the confirmation gate)
23
+ // is refused before it ever reaches appq, not silently honored just
24
+ // because nothing is watching this particular invocation.
25
+ const gatedAppq = createReadOnlyProjectContextDispatcher(createGatedAppqDispatcher(opts.client, READONLY_APPQ_TOOLS));
26
+ const browser = await chromium.launch();
27
+ const page = await browser.newPage();
28
+ try {
29
+ const browserTools = new PlaywrightBrowserTools(page, opts.ringBufferCap);
30
+ const dispatch = async (name, args) => {
31
+ if (name.startsWith('browser_'))
32
+ return browserTools.dispatch(name, args);
33
+ return gatedAppq(name, args);
34
+ };
35
+ const promptArgs = {
36
+ prompt: opts.prompt,
37
+ interactive: 'false',
38
+ max_steps: opts.maxSteps,
39
+ max_pages: opts.maxPages,
40
+ max_minutes: opts.maxMinutes,
41
+ };
42
+ if (opts.projectId !== undefined)
43
+ promptArgs.project_id = opts.projectId;
44
+ if (opts.siteUrl)
45
+ promptArgs.site_url = opts.siteUrl;
46
+ const seedLines = [`Exploration intent: ${opts.prompt}`];
47
+ if (opts.siteUrl)
48
+ seedLines.push(`URL under test: ${opts.siteUrl}`);
49
+ if (opts.projectId !== undefined)
50
+ seedLines.push(`Project ID: ${opts.projectId}`);
51
+ seedLines.push('Begin now — start with browser_snapshot.');
52
+ return await runWorkflow({
53
+ source: { kind: 'appq', name: 'appq:runman', args: promptArgs },
54
+ fetchPrompt: opts.client.fetchPrompt,
55
+ seedMessage: seedLines.join('\n'),
56
+ tools: [...BROWSER_TOOL_DEFS, ...appqToolDefs],
57
+ dispatch,
58
+ adapter: opts.adapter,
59
+ budget: opts.budget,
60
+ onEvent: opts.onEvent,
61
+ });
62
+ }
63
+ finally {
64
+ await browser.close();
65
+ }
66
+ }
67
+ //# sourceMappingURL=explore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"explore.js","sourceRoot":"","sources":["../../src/orchestrator/explore.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
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. Genuinely read-only end to end, including
5
+ // enrich_project_context: it's offered here because it has a real read
6
+ // mode this agent needs (known_issues/high_risk_areas inform what's worth
7
+ // exploring), but @appliqation/agent-core's createReadOnlyProjectContextDispatcher
8
+ // refuses its action=write mode unconditionally — see cli/index.ts and
9
+ // README.md's Safety section for why this agent, unlike an interactive
10
+ // appq:runman session, never gets to use it.
11
+ export const READONLY_APPQ_TOOLS = new Set([
12
+ 'get_scenario',
13
+ 'get_coverage_analysis',
14
+ 'get_defects',
15
+ 'get_failure_patterns',
16
+ 'get_quality_context',
17
+ 'get_evidence_summary',
18
+ 'get_run_evidence',
19
+ 'search_tests',
20
+ 'get_project_settings',
21
+ 'enrich_project_context',
22
+ 'get_validation_targets',
23
+ ]);
24
+ //# 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,wEAAwE;AACxE,uEAAuE;AACvE,0EAA0E;AAC1E,mFAAmF;AACnF,uEAAuE;AACvE,uEAAuE;AACvE,6CAA6C;AAE7C,MAAM,CAAC,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC;IACzC,cAAc;IACd,uBAAuB;IACvB,aAAa;IACb,sBAAsB;IACtB,qBAAqB;IACrB,sBAAsB;IACtB,kBAAkB;IAClB,cAAc;IACd,sBAAsB;IACtB,wBAAwB;IACxB,wBAAwB;CACzB,CAAC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@appliqation/explorer",
3
+ "version": "0.1.1",
4
+ "description": "Standalone agent that runs Appliqation's exploratory-QA workflow (appq:runman) headlessly against a live app under test \u2014 the same senior-QA heuristics pass, without a human at the confirmation gate.",
5
+ "type": "module",
6
+ "bin": {
7
+ "appliqation-explorer": "./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
+ "playwright": "^1.51.0"
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
+ }