Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,6 @@
"!android/ime-helper/dist/*.idsig",
"!android/ime-helper/README.md",
"linux/atspi-dump.py",
"skills/agent-device",
"skills/dogfood",
"server.json",
"smithery.yaml",
"README.md",
Expand Down
48 changes: 48 additions & 0 deletions scripts/__tests__/agent-setup-startup-contract.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { test } from 'vitest';

const ROOT = join(import.meta.dirname, '..', '..');
const AGENT_SETUP = join(ROOT, 'website', 'docs', 'docs', 'agent-setup.md');
const OPEN_FIRST = 'For a normal app-driving task, start immediately.';
const MANDATORY_STARTUP_PROBES = [
{
pattern: /Before planning commands, run `agent-device --version`/,
example: 'Before planning commands, run `agent-device --version`',
},
{
pattern: /Before planning device work, run `agent-device --version`/,
example: 'Before planning device work, run `agent-device --version`',
},
{
pattern: /run `agent-device help workflow` before planning/,
example: 'run `agent-device help workflow` before planning',
},
] as const;

function assertOpenFirstSetup(content: string): void {
const openFirstRules = content.split(OPEN_FIRST).length - 1;
assert.equal(openFirstRules, 3, 'recommended, Cursor, and Claude rules must start with open');
for (const probe of MANDATORY_STARTUP_PROBES) {
assert.doesNotMatch(
content,
probe.pattern,
`agent setup contains mandatory startup probe: ${probe.pattern}`,
);
}
}

test('agent setup rules start normal work with open and avoid mandatory probes', async () => {
assertOpenFirstSetup(await readFile(AGENT_SETUP, 'utf8'));
});

for (const probe of MANDATORY_STARTUP_PROBES) {
test(`agent setup contract rejects ${probe.pattern}`, async () => {
const content = await readFile(AGENT_SETUP, 'utf8');
assert.throws(
() => assertOpenFirstSetup(`${content}\n${probe.example}\n`),
/agent setup contains mandatory startup probe/,
);
});
}
21 changes: 21 additions & 0 deletions scripts/__tests__/npm-skills-exclusion.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { test } from 'vitest';

const ROOT = join(import.meta.dirname, '..', '..');

test('the npm package excludes repository skills', async () => {
const manifest = JSON.parse(await readFile(join(ROOT, 'package.json'), 'utf8')) as {
files?: string[];
};
const publishedSkills = (manifest.files ?? []).filter(
(entry) => entry === 'skills' || entry.startsWith('skills/'),
);

assert.deepEqual(
publishedSkills,
[],
'skills are installed from the repository and must not ship in the npm CLI package',
);
});
73 changes: 53 additions & 20 deletions scripts/__tests__/simulator-skills-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,62 +10,84 @@ type Contract = {
requiredText: string;
};

type Prohibition = {
id: string;
forbiddenText: string;
};

const SKILLS = [
{
name: 'iOS Simulator',
path: join(ROOT, 'skills', 'ios-simulator', 'SKILL.md'),
contracts: [
{
id: 'foreground platform open',
requiredText: 'agent-device open <app-or-bundle-id> --platform ios --foreground',
id: 'human-owned CLI installation',
requiredText: 'npm install -g agent-device@latest',
},
{
id: 'immediate app-driving start',
requiredText: 'For a normal app-driving task, start immediately.',
},
{ id: 'validation help routing', requiredText: 'agent-device help validate' },
{
id: 'initial interactive snapshot',
requiredText: '`open` returns the initial interactive snapshot.',
id: 'autonomous mutable install refusal',
requiredText: 'Do not run that command autonomously',
},
{ id: 'current ref or selector', requiredText: 'Use its current refs or a selector.' },
{ id: 'settled planned actions', requiredText: 'agent-device press @eN --settle' },
{ id: 'type settle exception', requiredText: '`type` never takes `--settle`;' },
{
id: 'end-state verification',
requiredText: 'Verify the end state with a selector or exact text, then close:',
id: 'foreground platform open',
requiredText: 'agent-device open <app-or-bundle-id> --platform ios --foreground',
},
{ id: 'session close', requiredText: 'agent-device close' },
] satisfies Contract[],
prohibitions: [
{ id: 'version startup probe', forbiddenText: 'agent-device --version' },
{ id: 'routine help startup probe', forbiddenText: 'agent-device help manual-qa' },
] satisfies Prohibition[],
},
{
name: 'Android Emulator',
path: join(ROOT, 'skills', 'android-emulator', 'SKILL.md'),
contracts: [
{
id: 'foreground platform open',
requiredText: 'agent-device open <app-or-package-id> --platform android --foreground',
id: 'human-owned CLI installation',
requiredText: 'npm install -g agent-device@latest',
},
{
id: 'initial interactive snapshot',
requiredText: '`open` returns the initial interactive snapshot.',
id: 'immediate app-driving start',
requiredText: 'For a normal app-driving task, start immediately.',
},
{ id: 'current ref or selector', requiredText: 'Use its current refs or a selector.' },
{ id: 'settled planned actions', requiredText: 'agent-device press @eN --settle' },
{ id: 'type settle exception', requiredText: '`type` never takes `--settle`;' },
{ id: 'validation help routing', requiredText: 'agent-device help validate' },
{
id: 'end-state verification',
requiredText: 'Verify the end state with a selector or exact text, then close:',
id: 'autonomous mutable install refusal',
requiredText: 'Do not run that command autonomously',
},
{
id: 'foreground platform open',
requiredText: 'agent-device open <app-or-package-id> --platform android --foreground',
},
{ id: 'session close', requiredText: 'agent-device close' },
] satisfies Contract[],
prohibitions: [
{ id: 'version startup probe', forbiddenText: 'agent-device --version' },
{ id: 'routine help startup probe', forbiddenText: 'agent-device help manual-qa' },
] satisfies Prohibition[],
},
] as const;

function assertSkillContract(content: string, contract: Contract): void {
assert.ok(content.includes(contract.requiredText), `missing ${contract.id} guidance`);
}

function assertSkillProhibition(content: string, prohibition: Prohibition): void {
assert.ok(!content.includes(prohibition.forbiddenText), `contains ${prohibition.id} guidance`);
}

describe('simulator skill contracts', () => {
for (const skill of SKILLS) {
test(`${skill.name} keeps its required workflow guidance`, async () => {
const content = await readFile(skill.path, 'utf8');
for (const contract of skill.contracts) assertSkillContract(content, contract);
for (const prohibition of skill.prohibitions) {
assertSkillProhibition(content, prohibition);
}
});

for (const contract of skill.contracts) {
Expand All @@ -79,5 +101,16 @@ describe('simulator skill contracts', () => {
);
});
}

for (const prohibition of skill.prohibitions) {
test(`${skill.name} rejects ${prohibition.id} guidance`, async () => {
const content = await readFile(skill.path, 'utf8');
const broken = `${content}\n${prohibition.forbiddenText}\n`;
assert.throws(
() => assertSkillProhibition(broken, prohibition),
new RegExp(`contains ${prohibition.id} guidance`),
);
});
}
}
});
28 changes: 8 additions & 20 deletions skills/android-emulator/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,28 @@ description: Verify and debug native, React Native, Expo, or Flutter apps on an

# Android Emulator

Use `agent-device` on an Android Emulator to verify a running app. Work from the live UI, act on current refs or selectors, and verify the result before closing the session.

For an app or package id, open it in the foreground:
Require the `agent-device` CLI to be installed separately before driving an emulator:

```bash
agent-device open <app-or-package-id> --platform android --foreground
npm install -g agent-device@latest
```

`open` returns the initial interactive snapshot. Use its current refs or a selector. For a planned action, use `--settle`. If the settled diff shows the next target, continue from it:

```bash
agent-device press @eN --settle
agent-device fill @eN "text" --settle
```
Treat installation and upgrades as user-owned setup steps. Do not run that command autonomously or substitute a mutable `npx -y agent-device@latest` invocation.

Run `agent-device snapshot -i` only when the settled diff does not show the next target. `type` never takes `--settle`; verify it with a snapshot or named `wait`. Keep state-changing commands serial. Verify the end state with a selector or exact text, then close:
For a normal app-driving task, start immediately. Do not probe first with `--help`, `--version`, `devices`, `appstate`, `snapshot`, or `screenshot`. Target Android explicitly when opening an app or package id:

```bash
agent-device close
agent-device open <app-or-package-id> --platform android --foreground
```

For non-routine work, use the version-matched CLI help:
Follow the initial interactive snapshot and corrective error hints. If the shell reports that `agent-device` is unavailable, stop and ask the user to install it or expose their existing installation on `PATH`.

```bash
agent-device help validate
```

Read only the relevant follow-up topic for specialized work:
Only when the task is specialized or a command shape is unclear, read the relevant version-matched help topic:

```bash
agent-device help validate # engineering validation and build freshness
agent-device help debugging # screenshots, logs, traces, video, and failures
agent-device help react-native # React Native and Expo runtime guidance
agent-device help react-devtools # component tree, props/state/hooks, and renders
agent-device help scripting # durable replay and CI workflows
```

Use `adb shell` only for platform operations. Use this workflow to verify the app and keep diagnostic output when it fails.
28 changes: 8 additions & 20 deletions skills/ios-simulator/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,28 @@ description: Verify and debug native, React Native, Expo, or Flutter apps on an

# iOS Simulator

Use `agent-device` on an iOS Simulator to verify a running app. Work from the live UI, act on current refs or selectors, and verify the result before closing the session.

For an app or bundle id, open it in the foreground:
Require the `agent-device` CLI to be installed separately before driving a simulator:

```bash
agent-device open <app-or-bundle-id> --platform ios --foreground
npm install -g agent-device@latest
```

`open` returns the initial interactive snapshot. Use its current refs or a selector. For a planned action, use `--settle`. If the settled diff shows the next target, continue from it:

```bash
agent-device press @eN --settle
agent-device fill @eN "text" --settle
```
Treat installation and upgrades as user-owned setup steps. Do not run that command autonomously or substitute a mutable `npx -y agent-device@latest` invocation.

Run `agent-device snapshot -i` only when the settled diff does not show the next target. `type` never takes `--settle`; verify it with a snapshot or named `wait`. Keep state-changing commands serial. Verify the end state with a selector or exact text, then close:
For a normal app-driving task, start immediately. Do not probe first with `--help`, `--version`, `devices`, `appstate`, `snapshot`, or `screenshot`. Target iOS explicitly when opening an app or bundle id:

```bash
agent-device close
agent-device open <app-or-bundle-id> --platform ios --foreground
```

For non-routine work, use the version-matched CLI help:
Follow the initial interactive snapshot and corrective error hints. If the shell reports that `agent-device` is unavailable, stop and ask the user to install it or expose their existing installation on `PATH`.

```bash
agent-device help validate
```

Read only the relevant follow-up topic for specialized work:
Only when the task is specialized or a command shape is unclear, read the relevant version-matched help topic:

```bash
agent-device help validate # engineering validation and build freshness
agent-device help debugging # screenshots, logs, traces, video, and failures
agent-device help react-native # React Native and Expo runtime guidance
agent-device help react-devtools # component tree, props/state/hooks, and renders
agent-device help scripting # durable replay and CI workflows
```

If platform help says a capability is unavailable, follow it. Keep diagnostic output when verification fails.
2 changes: 1 addition & 1 deletion src/utils/update-check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export function maybeRunUpgradeNotifier(options: UpgradeNotifierOptions): void {
if (shouldShowUpgradeNotice(cache, options.currentVersion)) {
process.stderr.write(
`Update available: ${PACKAGE_NAME} ${options.currentVersion} -> ${cache.latestVersion}. ` +
`Run \`npm install -g ${PACKAGE_NAME}@latest\` to upgrade the CLI and bundled skills.\n`,
`Run \`npm install -g ${PACKAGE_NAME}@latest\` to upgrade the CLI.\n`,
);
writeUpdateCheckCache(cachePath, {
...cache,
Expand Down
2 changes: 2 additions & 0 deletions vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ export default defineConfig({
'scripts/__tests__/help-conformance-error-recovery-coverage.test.ts',
'scripts/__tests__/help-conformance-sample-outputs.test.ts',
'scripts/__tests__/help-conformance-topic-coverage.test.ts',
'scripts/__tests__/agent-setup-startup-contract.test.ts',
'scripts/__tests__/npm-skills-exclusion.test.ts',
'scripts/__tests__/simulator-skills-contract.test.ts',
// The publishing gate's closure audit against fixture packages: parse-only, and the
// only place the gate's failure direction is exercised at all (the gate itself needs a
Expand Down
Loading
Loading