agent-scenario-loop 0.1.2 → 0.1.4

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.
Files changed (87) hide show
  1. package/README.md +9 -9
  2. package/app/profile-session.ts +352 -12
  3. package/dist/core/agent-summary.d.ts +3 -2
  4. package/dist/core/agent-summary.js +44 -2
  5. package/dist/core/artifact-contract.d.ts +28 -8
  6. package/dist/core/artifact-contract.js +676 -26
  7. package/dist/core/comparison.d.ts +57 -3
  8. package/dist/core/comparison.js +113 -1
  9. package/dist/core/planner.d.ts +32 -1
  10. package/dist/core/planner.js +144 -0
  11. package/dist/core/run-index.d.ts +4 -0
  12. package/dist/core/run-index.js +55 -1
  13. package/dist/core/schema-validator.d.ts +2 -0
  14. package/dist/core/schema-validator.js +2 -0
  15. package/dist/runner/android-adb-driver.d.ts +7 -2
  16. package/dist/runner/android-adb-driver.js +7 -1
  17. package/dist/runner/android-adb.d.ts +40 -5
  18. package/dist/runner/android-adb.js +1046 -664
  19. package/dist/runner/compare-latest.d.ts +8 -4
  20. package/dist/runner/compare-latest.js +24 -5
  21. package/dist/runner/example-android-live.d.ts +10 -1
  22. package/dist/runner/example-android-live.js +55 -0
  23. package/dist/runner/example-ios-live.d.ts +10 -1
  24. package/dist/runner/example-ios-live.js +55 -0
  25. package/dist/runner/ios-simctl.d.ts +6 -0
  26. package/dist/runner/ios-simctl.js +7 -0
  27. package/dist/runner/live-comparison.d.ts +2 -2
  28. package/dist/runner/live-comparison.js +2 -1
  29. package/dist/runner/live-proof-summary.d.ts +5 -4
  30. package/dist/runner/live-proof-summary.js +12 -2
  31. package/dist/runner/live-proof.d.ts +3 -2
  32. package/dist/runner/live-proof.js +9 -2
  33. package/dist/runner/profile-android.d.ts +16 -1
  34. package/dist/runner/profile-android.js +364 -26
  35. package/dist/runner/profile-ios.d.ts +13 -2
  36. package/dist/runner/profile-ios.js +341 -19
  37. package/dist/runner/profile-mobile.d.ts +39 -3
  38. package/dist/runner/profile-mobile.js +1054 -42
  39. package/dist/runner/validate-project.js +3 -0
  40. package/dist/scripts/consumer-rehearsal.d.ts +119 -0
  41. package/dist/scripts/consumer-rehearsal.js +757 -0
  42. package/dist/scripts/downstream-local-package-gate.d.ts +2 -0
  43. package/dist/scripts/downstream-local-package-gate.js +264 -0
  44. package/dist/scripts/package-smoke.d.ts +96 -0
  45. package/dist/scripts/package-smoke.js +2282 -0
  46. package/dist/scripts/release-readiness.d.ts +2 -0
  47. package/dist/scripts/release-readiness.js +520 -0
  48. package/docs/adapters.md +7 -1
  49. package/docs/api.md +2 -2
  50. package/docs/architecture.md +90 -0
  51. package/docs/authoring.md +39 -3
  52. package/docs/concepts.md +3 -24
  53. package/docs/consumer-rehearsal.md +31 -1
  54. package/docs/contracts.md +45 -101
  55. package/docs/external-adapter-protocol.md +219 -0
  56. package/docs/live-proofs.md +86 -3
  57. package/docs/principles.md +9 -15
  58. package/examples/mobile-app/README.md +12 -0
  59. package/examples/mobile-app/runner-manifests/evidence-provider.json +3 -3
  60. package/examples/mobile-app/runner-manifests/primary-runner.json +1 -0
  61. package/examples/mobile-app/scripts/asl-capture-profiler-provider.mjs +25 -0
  62. package/examples/runners/README.md +4 -3
  63. package/examples/runners/adb-android.json +1 -0
  64. package/examples/runners/agent-device-android.json +1 -0
  65. package/examples/runners/agent-device-ios.json +1 -0
  66. package/examples/runners/argent-android.json +1 -0
  67. package/examples/runners/argent-ios.json +1 -0
  68. package/examples/runners/axe-accessibility-provider.json +2 -2
  69. package/examples/runners/script-accessibility-provider.json +2 -2
  70. package/examples/runners/script-memory-provider.json +2 -2
  71. package/examples/runners/script-network-provider.json +2 -2
  72. package/examples/runners/script-profiler-provider.json +2 -2
  73. package/examples/runners/xcodebuildmcp-ios.json +1 -0
  74. package/package.json +12 -3
  75. package/schemas/causal-run.schema.json +85 -2
  76. package/schemas/comparison.schema.json +130 -2
  77. package/schemas/external-adapter-message.schema.json +693 -0
  78. package/schemas/health.schema.json +72 -0
  79. package/schemas/live-proof-set.schema.json +1 -1
  80. package/schemas/live-proof.schema.json +14 -6
  81. package/schemas/manifest.schema.json +515 -4
  82. package/schemas/profiler.schema.json +243 -0
  83. package/schemas/runner-capabilities.schema.json +28 -2
  84. package/schemas/scenario.schema.json +34 -2
  85. package/templates/evidence-provider.json +3 -3
  86. package/templates/primary-runner.json +1 -0
  87. package/templates/scripts/asl-capture-profiler-provider.mjs +20 -0
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,520 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const assert = require('node:assert/strict');
5
+ const fs = require('node:fs');
6
+ const path = require('node:path');
7
+ const { execFileSync } = require('node:child_process');
8
+ const REQUIRED_BIN_TARGETS = {
9
+ 'agent-scenario-loop': 'dist/runner/check-plan.js',
10
+ 'asl-agent-device': 'dist/runner/agent-device.js',
11
+ 'asl-android-adb': 'dist/runner/android-adb.js',
12
+ 'asl-argent': 'dist/runner/argent.js',
13
+ 'asl-check-plan': 'dist/runner/check-plan.js',
14
+ 'asl-compare': 'dist/runner/compare.js',
15
+ 'asl-compare-latest': 'dist/runner/compare-latest.js',
16
+ 'asl-demo-loop': 'dist/runner/demo-loop.js',
17
+ 'asl-example-android-live': 'dist/runner/example-android-live.js',
18
+ 'asl-example-ios-live': 'dist/runner/example-ios-live.js',
19
+ 'asl-host-doctor': 'dist/runner/host-doctor.js',
20
+ 'asl-init': 'dist/runner/init-project.js',
21
+ 'asl-ios-simctl': 'dist/runner/ios-simctl.js',
22
+ 'asl-live-android': 'dist/runner/live-android.js',
23
+ 'asl-live-ios': 'dist/runner/live-ios.js',
24
+ 'asl-live-proof': 'dist/runner/live-proof.js',
25
+ 'asl-profile-android': 'dist/runner/profile-android.js',
26
+ 'asl-profile-ios': 'dist/runner/profile-ios.js',
27
+ 'asl-validate-project': 'dist/runner/validate-project.js',
28
+ };
29
+ const REQUIRED_EXPORTS = {
30
+ '.': {
31
+ require: './dist/index.js',
32
+ types: './dist/index.d.ts',
33
+ import: './dist/index.js',
34
+ default: './dist/index.js',
35
+ },
36
+ './app/profile-session': {
37
+ require: './app/profile-session.ts',
38
+ types: './app/profile-session.ts',
39
+ import: './app/profile-session.ts',
40
+ default: './app/profile-session.ts',
41
+ },
42
+ './examples/*': './examples/*',
43
+ './package.json': './package.json',
44
+ './runner/agent-device': {
45
+ require: './dist/runner/agent-device.js',
46
+ types: './dist/runner/agent-device.d.ts',
47
+ import: './dist/runner/agent-device.js',
48
+ default: './dist/runner/agent-device.js',
49
+ },
50
+ './runner/agent-device-driver': {
51
+ require: './dist/runner/agent-device-driver.js',
52
+ types: './dist/runner/agent-device-driver.d.ts',
53
+ import: './dist/runner/agent-device-driver.js',
54
+ default: './dist/runner/agent-device-driver.js',
55
+ },
56
+ './runner/argent-driver': {
57
+ require: './dist/runner/argent-driver.js',
58
+ types: './dist/runner/argent-driver.d.ts',
59
+ import: './dist/runner/argent-driver.js',
60
+ default: './dist/runner/argent-driver.js',
61
+ },
62
+ './runner/argent': {
63
+ require: './dist/runner/argent.js',
64
+ types: './dist/runner/argent.d.ts',
65
+ import: './dist/runner/argent.js',
66
+ default: './dist/runner/argent.js',
67
+ },
68
+ './runner/android-adb': {
69
+ require: './dist/runner/android-adb.js',
70
+ types: './dist/runner/android-adb.d.ts',
71
+ import: './dist/runner/android-adb.js',
72
+ default: './dist/runner/android-adb.js',
73
+ },
74
+ './runner/android-adb-driver': {
75
+ require: './dist/runner/android-adb-driver.js',
76
+ types: './dist/runner/android-adb-driver.d.ts',
77
+ import: './dist/runner/android-adb-driver.js',
78
+ default: './dist/runner/android-adb-driver.js',
79
+ },
80
+ './runner/check-plan': {
81
+ require: './dist/runner/check-plan.js',
82
+ types: './dist/runner/check-plan.d.ts',
83
+ import: './dist/runner/check-plan.js',
84
+ default: './dist/runner/check-plan.js',
85
+ },
86
+ './runner/compare': {
87
+ require: './dist/runner/compare.js',
88
+ types: './dist/runner/compare.d.ts',
89
+ import: './dist/runner/compare.js',
90
+ default: './dist/runner/compare.js',
91
+ },
92
+ './runner/compare-latest': {
93
+ require: './dist/runner/compare-latest.js',
94
+ types: './dist/runner/compare-latest.d.ts',
95
+ import: './dist/runner/compare-latest.js',
96
+ default: './dist/runner/compare-latest.js',
97
+ },
98
+ './runner/demo-loop': {
99
+ require: './dist/runner/demo-loop.js',
100
+ types: './dist/runner/demo-loop.d.ts',
101
+ import: './dist/runner/demo-loop.js',
102
+ default: './dist/runner/demo-loop.js',
103
+ },
104
+ './runner/example-android-live': {
105
+ require: './dist/runner/example-android-live.js',
106
+ types: './dist/runner/example-android-live.d.ts',
107
+ import: './dist/runner/example-android-live.js',
108
+ default: './dist/runner/example-android-live.js',
109
+ },
110
+ './runner/example-ios-live': {
111
+ require: './dist/runner/example-ios-live.js',
112
+ types: './dist/runner/example-ios-live.d.ts',
113
+ import: './dist/runner/example-ios-live.js',
114
+ default: './dist/runner/example-ios-live.js',
115
+ },
116
+ './runner/host-doctor': {
117
+ require: './dist/runner/host-doctor.js',
118
+ types: './dist/runner/host-doctor.d.ts',
119
+ import: './dist/runner/host-doctor.js',
120
+ default: './dist/runner/host-doctor.js',
121
+ },
122
+ './runner/init-project': {
123
+ require: './dist/runner/init-project.js',
124
+ types: './dist/runner/init-project.d.ts',
125
+ import: './dist/runner/init-project.js',
126
+ default: './dist/runner/init-project.js',
127
+ },
128
+ './runner/ios-simctl': {
129
+ require: './dist/runner/ios-simctl.js',
130
+ types: './dist/runner/ios-simctl.d.ts',
131
+ import: './dist/runner/ios-simctl.js',
132
+ default: './dist/runner/ios-simctl.js',
133
+ },
134
+ './runner/ios-simctl-driver': {
135
+ require: './dist/runner/ios-simctl-driver.js',
136
+ types: './dist/runner/ios-simctl-driver.d.ts',
137
+ import: './dist/runner/ios-simctl-driver.js',
138
+ default: './dist/runner/ios-simctl-driver.js',
139
+ },
140
+ './runner/live-android': {
141
+ require: './dist/runner/live-android.js',
142
+ types: './dist/runner/live-android.d.ts',
143
+ import: './dist/runner/live-android.js',
144
+ default: './dist/runner/live-android.js',
145
+ },
146
+ './runner/live-ios': {
147
+ require: './dist/runner/live-ios.js',
148
+ types: './dist/runner/live-ios.d.ts',
149
+ import: './dist/runner/live-ios.js',
150
+ default: './dist/runner/live-ios.js',
151
+ },
152
+ './runner/live-proof': {
153
+ require: './dist/runner/live-proof.js',
154
+ types: './dist/runner/live-proof.d.ts',
155
+ import: './dist/runner/live-proof.js',
156
+ default: './dist/runner/live-proof.js',
157
+ },
158
+ './runner/profile-android': {
159
+ require: './dist/runner/profile-android.js',
160
+ types: './dist/runner/profile-android.d.ts',
161
+ import: './dist/runner/profile-android.js',
162
+ default: './dist/runner/profile-android.js',
163
+ },
164
+ './runner/profile-ios': {
165
+ require: './dist/runner/profile-ios.js',
166
+ types: './dist/runner/profile-ios.d.ts',
167
+ import: './dist/runner/profile-ios.js',
168
+ default: './dist/runner/profile-ios.js',
169
+ },
170
+ './runner/validate-project': {
171
+ require: './dist/runner/validate-project.js',
172
+ types: './dist/runner/validate-project.d.ts',
173
+ import: './dist/runner/validate-project.js',
174
+ default: './dist/runner/validate-project.js',
175
+ },
176
+ './schemas/*': './schemas/*',
177
+ './templates/*': './templates/*',
178
+ };
179
+ const REQUIRED_PACKAGE_FILES = [
180
+ 'LICENSE',
181
+ 'README.md',
182
+ 'app/profile-session.ts',
183
+ 'core/config-template.json',
184
+ 'dist',
185
+ 'docs',
186
+ 'examples',
187
+ 'schemas',
188
+ 'templates',
189
+ ];
190
+ const REQUIRED_PACKAGE_EXCLUSIONS = [
191
+ '!dist/**/__tests__',
192
+ '!examples/mobile-app/.expo',
193
+ '!examples/mobile-app/.expo/**',
194
+ '!examples/mobile-app/android',
195
+ '!examples/mobile-app/android/**',
196
+ '!examples/mobile-app/artifacts',
197
+ '!examples/mobile-app/artifacts/**',
198
+ '!examples/mobile-app/ios',
199
+ '!examples/mobile-app/ios/**',
200
+ '!examples/mobile-app/node_modules',
201
+ '!examples/mobile-app/node_modules/**',
202
+ ];
203
+ const REQUIRED_GITIGNORE_ENTRIES = [
204
+ 'node_modules/',
205
+ '.agents/',
206
+ 'dist/',
207
+ 'core/artifacts/',
208
+ 'artifacts/',
209
+ 'examples/mobile-app/.expo/',
210
+ 'examples/mobile-app/android/',
211
+ 'examples/mobile-app/artifacts/',
212
+ 'examples/mobile-app/ios/',
213
+ 'examples/mobile-app/node_modules/',
214
+ ];
215
+ const FORBIDDEN_TRACKED_PATH_PATTERNS = [
216
+ /^artifacts\//u,
217
+ /^core\/artifacts\//u,
218
+ /^dist\//u,
219
+ /^examples\/mobile-app\/(?:\.expo|android|artifacts|ios|node_modules)(?:\/|$)/u,
220
+ ];
221
+ const REQUIRED_CONFIG_STRING_FIELDS = [
222
+ ['app', 'profileSessionScheme'],
223
+ ['app', 'iosBundleId'],
224
+ ['app', 'androidPackage'],
225
+ ['paths', 'artifactRoot'],
226
+ ['paths', 'iosArtifactsRoot'],
227
+ ['paths', 'androidArtifactsRoot'],
228
+ ['paths', 'scenarioRoot'],
229
+ ];
230
+ const REQUIRED_PLATFORM_SCRIPT_PAIRS = [
231
+ ['asl:check:ios', 'asl:check:android'],
232
+ ['asl:profile:ios', 'asl:profile:android'],
233
+ ['asl:profile:ios:provider', 'asl:profile:android:provider'],
234
+ ['asl:profile:ios:live', 'asl:profile:android:live'],
235
+ ['asl:ios:live', 'asl:android:live'],
236
+ ['asl:ios:live:agent-device', 'asl:android:live:agent-device'],
237
+ ['asl:ios:live:argent', 'asl:android:live:argent'],
238
+ ['asl:ios:live:runners', 'asl:android:live:runners'],
239
+ ['asl:agent-device:ios', 'asl:agent-device:android'],
240
+ ['asl:argent:ios', 'asl:argent:android'],
241
+ ['asl:compare:ios', 'asl:compare:android'],
242
+ ['asl:live-proof:ios', 'asl:live-proof:android'],
243
+ ];
244
+ const REQUIRED_STRICT_EXAMPLE_LIVE_SCRIPTS = [
245
+ 'example:android:live',
246
+ 'example:android:live:agent-device',
247
+ 'example:android:live:argent',
248
+ 'example:android:live:runners',
249
+ 'example:ios:live',
250
+ 'example:ios:live:agent-device',
251
+ 'example:ios:live:argent',
252
+ 'example:ios:live:runners',
253
+ ];
254
+ /**
255
+ * Reads and parses a JSON object from disk.
256
+ *
257
+ * @param {string} filePath
258
+ * @returns {Record<string, unknown>}
259
+ */
260
+ function readJsonObject(filePath) {
261
+ const value = JSON.parse(fs.readFileSync(filePath, 'utf8'));
262
+ assert.equal(typeof value, 'object', `${filePath} must contain a JSON object`);
263
+ assert.notEqual(value, null, `${filePath} must contain a JSON object`);
264
+ assert.equal(Array.isArray(value), false, `${filePath} must contain a JSON object`);
265
+ return value;
266
+ }
267
+ /**
268
+ * Reads a nested value from a JSON object.
269
+ *
270
+ * @param {Record<string, unknown>} object
271
+ * @param {string[]} pathSegments
272
+ * @returns {unknown}
273
+ */
274
+ function readNestedValue(object, pathSegments) {
275
+ let value = object;
276
+ for (const segment of pathSegments) {
277
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
278
+ return undefined;
279
+ }
280
+ value = value[segment];
281
+ }
282
+ return value;
283
+ }
284
+ /**
285
+ * Returns a string map property from a JSON object.
286
+ *
287
+ * @param {Record<string, unknown>} object
288
+ * @param {string} key
289
+ * @returns {Record<string, string>}
290
+ */
291
+ function getStringMap(object, key) {
292
+ const value = object[key];
293
+ assert.equal(typeof value, 'object', `${key} must be an object`);
294
+ assert.notEqual(value, null, `${key} must be an object`);
295
+ assert.equal(Array.isArray(value), false, `${key} must be an object`);
296
+ for (const [entryKey, entryValue] of Object.entries(value)) {
297
+ assert.equal(typeof entryValue, 'string', `${key}.${entryKey} must be a string`);
298
+ }
299
+ return value;
300
+ }
301
+ /**
302
+ * Returns an object map property from a JSON object.
303
+ *
304
+ * @param {Record<string, unknown>} object
305
+ * @param {string} key
306
+ * @returns {Record<string, unknown>}
307
+ */
308
+ function getObjectMap(object, key) {
309
+ const value = object[key];
310
+ assert.equal(typeof value, 'object', `${key} must be an object`);
311
+ assert.notEqual(value, null, `${key} must be an object`);
312
+ assert.equal(Array.isArray(value), false, `${key} must be an object`);
313
+ return value;
314
+ }
315
+ /**
316
+ * Returns a string array property from a JSON object.
317
+ *
318
+ * @param {Record<string, unknown>} object
319
+ * @param {string} key
320
+ * @returns {string[]}
321
+ */
322
+ function getStringArray(object, key) {
323
+ const value = object[key];
324
+ assert.equal(Array.isArray(value), true, `${key} must be an array`);
325
+ for (const entry of value) {
326
+ assert.equal(typeof entry, 'string', `${key} entries must be strings`);
327
+ }
328
+ return value;
329
+ }
330
+ /**
331
+ * Asserts that every expected value appears in an actual string list.
332
+ *
333
+ * @param {string[]} actual
334
+ * @param {string[]} expected
335
+ * @param {string} label
336
+ * @returns {void}
337
+ */
338
+ function assertIncludesAll(actual, expected, label) {
339
+ for (const entry of expected) {
340
+ assert.equal(actual.includes(entry), true, `${label} is missing ${entry}`);
341
+ }
342
+ }
343
+ /**
344
+ * Asserts that package metadata still describes the intended public package.
345
+ *
346
+ * @param {Record<string, unknown>} packageJson
347
+ * @returns {void}
348
+ */
349
+ function assertPublicPackageMetadata(packageJson) {
350
+ assert.equal(packageJson.name, 'agent-scenario-loop');
351
+ assert.match(String(packageJson.version), /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/u);
352
+ assert.equal(packageJson.private, false);
353
+ assert.equal(packageJson.license, 'MIT');
354
+ assert.equal(packageJson.type, 'commonjs');
355
+ assert.equal(packageJson.main, 'dist/index.js');
356
+ assert.equal(packageJson.types, 'dist/index.d.ts');
357
+ assert.equal(getObjectMap(packageJson, 'publishConfig').access, 'public');
358
+ assert.equal(getObjectMap(packageJson, 'engines').node, '>=20');
359
+ }
360
+ /**
361
+ * Asserts that release scripts keep npm publishing behind the full validation gate.
362
+ *
363
+ * @param {Record<string, unknown>} packageJson
364
+ * @returns {void}
365
+ */
366
+ function assertReleaseScripts(packageJson) {
367
+ const scripts = getStringMap(packageJson, 'scripts');
368
+ assert.equal(scripts.prepublishOnly, 'pnpm release:check');
369
+ assert.equal(scripts['release:readiness'], 'pnpm build && node dist/scripts/release-readiness.js');
370
+ assert.equal(scripts['release:check'], 'pnpm test && pnpm release:readiness && pnpm package:smoke && pnpm consumer:rehearse');
371
+ assert.equal(scripts['package:smoke'], 'pnpm build && node dist/scripts/package-smoke.js');
372
+ assert.equal(scripts['consumer:rehearse'], 'pnpm build && node dist/scripts/consumer-rehearsal.js');
373
+ assert.equal(scripts['downstream:local-package'], 'pnpm build && node dist/scripts/downstream-local-package-gate.js');
374
+ for (const scriptName of REQUIRED_STRICT_EXAMPLE_LIVE_SCRIPTS) {
375
+ assert.match(scripts[scriptName], /--compare-latest/u, `${scriptName} must write comparison evidence by default`);
376
+ assert.match(scripts[scriptName], /--fail-on-regression/u, `${scriptName} must fail on regressions by default`);
377
+ }
378
+ assert.match(scripts['example:mobile:live-proof'], /--require-platforms android,ios/u);
379
+ assert.match(scripts['example:mobile:live-proof'], /--out artifacts\/example-mobile-app\/live-proof-set/u);
380
+ assert.match(scripts['example:mobile:live-proof'], /--fail-on-regression/u);
381
+ assert.match(scripts['example:app:start:isolated'], /--port 8097 --host localhost --clear/u);
382
+ }
383
+ /**
384
+ * Asserts that public binaries and package subpath exports map to built files.
385
+ *
386
+ * @param {Record<string, unknown>} packageJson
387
+ * @returns {void}
388
+ */
389
+ function assertPublicEntrypoints(packageJson) {
390
+ assert.deepEqual(getStringMap(packageJson, 'bin'), REQUIRED_BIN_TARGETS);
391
+ assert.deepEqual(getObjectMap(packageJson, 'exports'), REQUIRED_EXPORTS);
392
+ }
393
+ /**
394
+ * Extracts documented runner subpath specifiers from the public API guide.
395
+ *
396
+ * @param {string} repoRoot
397
+ * @returns {string[]}
398
+ */
399
+ function readDocumentedRunnerSubpaths(repoRoot) {
400
+ const apiDocs = fs.readFileSync(path.join(repoRoot, 'docs', 'api.md'), 'utf8');
401
+ const subpaths = new Set();
402
+ for (const match of apiDocs.matchAll(/`(agent-scenario-loop\/runner\/[^`]+)`/gu)) {
403
+ subpaths.add(match[1]);
404
+ }
405
+ return Array.from(subpaths).sort();
406
+ }
407
+ /**
408
+ * Extracts concrete runner subpath specifiers from package exports.
409
+ *
410
+ * @param {Record<string, unknown>} packageJson
411
+ * @returns {string[]}
412
+ */
413
+ function readExportedRunnerSubpaths(packageJson) {
414
+ return Object.keys(getObjectMap(packageJson, 'exports'))
415
+ .filter((subpath) => subpath.startsWith('./runner/'))
416
+ .map((subpath) => `agent-scenario-loop/${subpath.slice(2)}`)
417
+ .sort();
418
+ }
419
+ /**
420
+ * Asserts that the public API guide and package exports describe the same runner surface.
421
+ *
422
+ * @param {Record<string, unknown>} packageJson
423
+ * @param {string} repoRoot
424
+ * @returns {void}
425
+ */
426
+ function assertPublicApiDocs(packageJson, repoRoot) {
427
+ assert.deepEqual(readDocumentedRunnerSubpaths(repoRoot), readExportedRunnerSubpaths(packageJson), 'docs/api.md runner subpaths must match package.json exports');
428
+ }
429
+ /**
430
+ * Asserts that package include/exclude metadata protects generated local state.
431
+ *
432
+ * @param {Record<string, unknown>} packageJson
433
+ * @returns {void}
434
+ */
435
+ function assertPackageFileList(packageJson) {
436
+ const files = getStringArray(packageJson, 'files');
437
+ assertIncludesAll(files, REQUIRED_PACKAGE_FILES, 'package files');
438
+ assertIncludesAll(files, REQUIRED_PACKAGE_EXCLUSIONS, 'package files');
439
+ for (const forbidden of ['.github', 'scripts', 'runner', 'node_modules', 'artifacts']) {
440
+ assert.equal(files.includes(forbidden), false, `package files must not include ${forbidden}`);
441
+ }
442
+ }
443
+ /**
444
+ * Asserts that ignored local state paths remain explicit in the repository.
445
+ *
446
+ * @param {string} repoRoot
447
+ * @returns {void}
448
+ */
449
+ function assertGitignoreState(repoRoot) {
450
+ const gitignore = fs.readFileSync(path.join(repoRoot, '.gitignore'), 'utf8').split(/\r?\n/u);
451
+ assertIncludesAll(gitignore, REQUIRED_GITIGNORE_ENTRIES, '.gitignore');
452
+ }
453
+ /**
454
+ * Asserts that generated runtime/native state is not tracked by git.
455
+ *
456
+ * @param {string} repoRoot
457
+ * @returns {void}
458
+ */
459
+ function assertNoTrackedGeneratedState(repoRoot) {
460
+ const output = execFileSync('git', ['ls-files', '-z'], {
461
+ cwd: repoRoot,
462
+ encoding: 'utf8',
463
+ stdio: ['ignore', 'pipe', 'pipe'],
464
+ });
465
+ const trackedPaths = output.split('\0').filter(Boolean);
466
+ for (const trackedPath of trackedPaths) {
467
+ assert.equal(FORBIDDEN_TRACKED_PATH_PATTERNS.some((pattern) => pattern.test(trackedPath)), false, `generated state must not be tracked: ${trackedPath}`);
468
+ }
469
+ }
470
+ /**
471
+ * Asserts that shipped config templates contain identifiers needed by both mobile platforms.
472
+ *
473
+ * @param {string} repoRoot
474
+ * @returns {void}
475
+ */
476
+ function assertConfigTemplates(repoRoot) {
477
+ for (const relativePath of ['core/config-template.json', 'templates/project.config.json']) {
478
+ const config = readJsonObject(path.join(repoRoot, relativePath));
479
+ for (const fieldPath of REQUIRED_CONFIG_STRING_FIELDS) {
480
+ const value = readNestedValue(config, fieldPath);
481
+ assert.equal(typeof value, 'string', `${relativePath} must include ${fieldPath.join('.')}`);
482
+ assert.notEqual(String(value).trim(), '', `${relativePath} must include non-empty ${fieldPath.join('.')}`);
483
+ }
484
+ }
485
+ }
486
+ /**
487
+ * Asserts that shipped package-script snippets expose paired iOS and Android lanes.
488
+ *
489
+ * @param {string} repoRoot
490
+ * @returns {void}
491
+ */
492
+ function assertPlatformPackageScripts(repoRoot) {
493
+ for (const relativePath of ['templates/package-scripts.json', 'examples/mobile-app/asl/package-scripts.json']) {
494
+ const scripts = readJsonObject(path.join(repoRoot, relativePath));
495
+ for (const [iosScriptName, androidScriptName] of REQUIRED_PLATFORM_SCRIPT_PAIRS) {
496
+ assert.equal(typeof scripts[iosScriptName], 'string', `${relativePath} must include ${iosScriptName}`);
497
+ assert.equal(typeof scripts[androidScriptName], 'string', `${relativePath} must include ${androidScriptName}`);
498
+ }
499
+ }
500
+ }
501
+ /**
502
+ * Runs the release-readiness assertions for the current repository checkout.
503
+ *
504
+ * @returns {void}
505
+ */
506
+ function main() {
507
+ const repoRoot = path.resolve(__dirname, '..', '..');
508
+ const packageJson = readJsonObject(path.join(repoRoot, 'package.json'));
509
+ assertPublicPackageMetadata(packageJson);
510
+ assertReleaseScripts(packageJson);
511
+ assertPublicEntrypoints(packageJson);
512
+ assertPublicApiDocs(packageJson, repoRoot);
513
+ assertPackageFileList(packageJson);
514
+ assertGitignoreState(repoRoot);
515
+ assertNoTrackedGeneratedState(repoRoot);
516
+ assertConfigTemplates(repoRoot);
517
+ assertPlatformPackageScripts(repoRoot);
518
+ process.stdout.write('release readiness passed\n');
519
+ }
520
+ main();
package/docs/adapters.md CHANGED
@@ -102,6 +102,8 @@ The built-in adb and simctl adapters show the expected boundary:
102
102
 
103
103
  External tools such as agent-device, Argent, XcodeBuildMCP, axe, profilers, and custom scripts should plug in behind the same shape. The tactical tool can change; the scenario and artifact contract should not.
104
104
 
105
+ Prefer capability-based orchestration over forcing one tool to own every surface. Use adb and simctl as the primary live profile capture lanes for app launch, logs, screenshots, profile-session truth, and causal timelines. Attach heavier or tool-specific diagnostics after the active profile window through provider commands or rehydration. Agent Device is a good fit for Android snapshots and cross-platform network/performance evidence when its session is bound to the target. Argent is a good fit for iOS accessibility descriptions when `describe` can return AXRuntime evidence; native hierarchy, video, trace, React DevTools, and long profiler captures should be explicit heavy lanes until a runner/provider maps them into stable ASL artifacts.
106
+
105
107
  ## Preserve Evidence
106
108
 
107
109
  Every run should leave agent-readable proof:
@@ -133,7 +135,7 @@ asl-profile-android \
133
135
  --capture screenshot:artifacts/provider/final-screen.png
134
136
  ```
135
137
 
136
- If the provider should run during profiling, declare `providerCommands` in its manifest. Commands run without a shell, preserve stdout/stderr/exit code, and inventory outputs in `manifest.artifacts.evidenceAttachments`. Runtime profiles reject a provider whose `platforms` do not include the selected platform before command execution, preserving the same active-provider semantics used by planner compatibility.
138
+ If the provider should run during profile artifact assembly, declare `providerCommands` in its manifest. Profile runners execute provider commands after the selected platform evidence source is available, including live `--adb-capture` / `--simctl-capture` runs and later `--adb-artifacts` / `--simctl-artifacts` rehydration runs. Use `phase: "afterCapture"` for diagnostics collected from an existing capture sidecar; use `phase: "postRun"` for evidence that should be understood as post-profile enrichment. Commands run without a shell, preserve stdout/stderr/exit code, and inventory outputs in `manifest.artifacts.evidenceAttachments`. Provider command outputs may set `required: true` so the matching diagnostic inventory entry is required when the provider successfully captures that output; scenario-authored required artifacts and capabilities remain canonical too. Runtime profiles reject a provider whose `platforms` do not include the selected platform before command execution, preserving the same active-provider semantics used by planner compatibility.
137
139
 
138
140
  ## Acceptance Checklist
139
141
 
@@ -143,3 +145,7 @@ If the provider should run during profiling, declare `providerCommands` in its m
143
145
  - Passed runs write the standard artifact set.
144
146
  - Attached evidence is inventoried with stable run-relative paths.
145
147
  - Package docs describe whether the adapter is bundled, a fixture target, or a project-local integration.
148
+
149
+ ## Read next
150
+
151
+ - [Consumer App Rehearsal](consumer-rehearsal.md) for adopting the package inside an existing app
package/docs/api.md CHANGED
@@ -80,11 +80,11 @@ For concrete runner and evidence-provider integration steps, see [Adapter Onboar
80
80
 
81
81
  ## App Helper
82
82
 
83
- `app/profile-session.ts` is shipped as source for React Native apps to copy into their own codebase. It is not a compiled CommonJS runtime export because it depends on app-side React Native modules, app bundling, and platform storage behavior.
83
+ `agent-scenario-loop/app/profile-session` is shipped as React Native source. Apps can copy `app/profile-session.ts` into their own codebase or re-export the package subpath from an app-local helper module. It is not a compiled CommonJS runtime export because it depends on app-side React Native modules, app bundling, and platform storage behavior.
84
84
 
85
85
  The intended integration is:
86
86
 
87
- 1. Copy `app/profile-session.ts` into the app.
87
+ 1. Copy `app/profile-session.ts` into the app, or re-export `agent-scenario-loop/app/profile-session` from an app-local helper.
88
88
  2. Wire `useProfileSessionBootstrap()` once near the app root.
89
89
  3. Emit app-owned truth events with `emitProfileEvent()`.
90
90
  4. Register optional command targets with `registerProfileCommandTargetHandler()`.
@@ -0,0 +1,90 @@
1
+ # Architecture
2
+
3
+ ASL is implemented in TypeScript, but its contracts are language-neutral.
4
+
5
+ The TypeScript package is the reference implementation for planning, execution,
6
+ schema validation, artifact writing, health/verdict/comparison interpretation,
7
+ run indexing, CLIs, the TypeScript runner SDK, and the React Native/Expo helper.
8
+ Those implementation modules are not the interoperability contract.
9
+
10
+ ## Contract Boundary
11
+
12
+ JSON Schema and normative documentation are the source of truth for external
13
+ participants. TypeScript interfaces should reflect those contracts, not replace
14
+ them.
15
+
16
+ Language-neutral contracts include:
17
+
18
+ - scenario schemas;
19
+ - runner and evidence-provider manifests;
20
+ - capability definitions;
21
+ - truth-event envelopes;
22
+ - command and result envelopes;
23
+ - lifecycle states;
24
+ - error taxonomy;
25
+ - artifact schemas;
26
+ - cancellation and timeout semantics;
27
+ - protocol-version negotiation.
28
+
29
+ External adapters must be able to participate out of process. A valid adapter
30
+ may be an executable written in Swift, Kotlin, Python, Rust, shell, TypeScript,
31
+ or another language, provided it satisfies the documented schemas and protocol.
32
+ The minimal executable protocol is described in
33
+ [External Adapter Protocol](external-adapter-protocol.md).
34
+
35
+ ## Reference Environments
36
+
37
+ React Native and Expo remain the primary active battle-testing environment.
38
+ They provide real Android and iOS pressure across lifecycle behavior, command
39
+ transport, native boundaries, instrumentation, packaging, evidence provenance,
40
+ and agent-facing summaries.
41
+
42
+ The React Native helper is a reference transport. It does not define the truth
43
+ event contract by itself. Native apps must be able to emit ASL truth events
44
+ without embedding a JavaScript runtime.
45
+
46
+ ## Public Interoperability Rules
47
+
48
+ - Scenario files must remain structured data, not arbitrary JavaScript.
49
+ - Runners and providers must not be required to subclass TypeScript classes.
50
+ - Large evidence should be passed by file reference, not embedded as base64.
51
+ - External adapters should use structured protocol messages over stdio.
52
+ - Messages should carry run ids, attempt ids, sequence numbers, operation ids,
53
+ deadlines, platform, clock-domain metadata, adapter identity, and artifact
54
+ references where applicable.
55
+ - Failed operations should return structured failure data with stable codes,
56
+ classes, retryability, and next-action hints.
57
+
58
+ ## Audit Snapshot
59
+
60
+ Current public contracts are JSON schemas and JSON manifests. Scenario fixtures
61
+ are structured JSON and do not require callbacks or closures. Runner/provider
62
+ manifests describe capabilities and commands as data.
63
+
64
+ Known implementation-specific surfaces are intentionally reference paths:
65
+
66
+ - npm package scripts and Node CLIs are the TypeScript distribution channel.
67
+ - built-in adb, simctl, Argent, and agent-device runners are TypeScript
68
+ adapters.
69
+ - React Native profile-session logging and AsyncStorage are reference truth
70
+ event transports.
71
+ - provider command examples use Node scripts, but provider manifests can point
72
+ at any executable.
73
+
74
+ These are acceptable as reference implementation details. They should not become
75
+ requirements in scenario schemas, artifact schemas, or external-adapter protocol
76
+ messages.
77
+
78
+ ## Future Design Test
79
+
80
+ Could a Swift, Kotlin, Python, or Rust implementation satisfy this contract
81
+ using only the schemas, protocol documentation, executable interface, and
82
+ conformance fixtures?
83
+
84
+ If yes, TypeScript remains a productive reference implementation. If no, the
85
+ implementation-specific assumption should be identified and removed from the
86
+ contract surface.
87
+
88
+ ## Read next
89
+
90
+ - [External Adapter Protocol](external-adapter-protocol.md) for the out-of-process adapter envelope, operations, failures, and conformance fixture