@jevitate/cli 0.12.0 → 0.14.0

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 (271) hide show
  1. package/dist/author-journey-cli.d.ts +9 -0
  2. package/dist/author-journey-cli.js +18 -1
  3. package/dist/author-journey-cli.js.map +1 -1
  4. package/dist/bin.js +25673 -24216
  5. package/dist/build-info.generated.js +2 -2
  6. package/dist/catalog-analysis.js +4 -2
  7. package/dist/catalog-analysis.js.map +1 -1
  8. package/dist/catalog-bundle-api.js +1 -3
  9. package/dist/catalog-bundle-api.js.map +1 -1
  10. package/dist/catalog-bundle-findings.js +1 -3
  11. package/dist/catalog-bundle-findings.js.map +1 -1
  12. package/dist/catalog-bundle.js +1 -4
  13. package/dist/catalog-bundle.js.map +1 -1
  14. package/dist/catalog-cli.js +48 -6
  15. package/dist/catalog-cli.js.map +1 -1
  16. package/dist/catalog-review.d.ts +8 -0
  17. package/dist/catalog-review.js +26 -0
  18. package/dist/catalog-review.js.map +1 -1
  19. package/dist/catalog.d.ts +42 -0
  20. package/dist/catalog.js +67 -4
  21. package/dist/catalog.js.map +1 -1
  22. package/dist/check-execute.js +9 -0
  23. package/dist/check-execute.js.map +1 -1
  24. package/dist/check-plan.d.ts +5 -2
  25. package/dist/check-plan.js +9 -5
  26. package/dist/check-plan.js.map +1 -1
  27. package/dist/check-suite.js +1 -3
  28. package/dist/check-suite.js.map +1 -1
  29. package/dist/cli-output.js +1 -3
  30. package/dist/cli-output.js.map +1 -1
  31. package/dist/cli-shared.d.ts +1 -1
  32. package/dist/cli-shared.js +2 -0
  33. package/dist/cli-shared.js.map +1 -1
  34. package/dist/defect-evidence.js +4 -8
  35. package/dist/defect-evidence.js.map +1 -1
  36. package/dist/environments.d.ts +1 -1
  37. package/dist/environments.js +1 -1
  38. package/dist/environments.js.map +1 -1
  39. package/dist/exit-codes.d.ts +0 -1
  40. package/dist/exit-codes.js +2 -0
  41. package/dist/exit-codes.js.map +1 -1
  42. package/dist/explore-adversarial.d.ts +10 -1
  43. package/dist/explore-adversarial.js +3 -0
  44. package/dist/explore-adversarial.js.map +1 -1
  45. package/dist/explore-cli/adversarial-strategy.d.ts +3 -0
  46. package/dist/explore-cli/adversarial-strategy.js +101 -0
  47. package/dist/explore-cli/adversarial-strategy.js.map +1 -0
  48. package/dist/explore-cli/coverage-strategy.d.ts +3 -0
  49. package/dist/explore-cli/coverage-strategy.js +97 -0
  50. package/dist/explore-cli/coverage-strategy.js.map +1 -0
  51. package/dist/explore-cli/feature-strategy.d.ts +3 -0
  52. package/dist/explore-cli/feature-strategy.js +60 -0
  53. package/dist/explore-cli/feature-strategy.js.map +1 -0
  54. package/dist/explore-cli/goal-strategy.d.ts +3 -0
  55. package/dist/explore-cli/goal-strategy.js +185 -0
  56. package/dist/explore-cli/goal-strategy.js.map +1 -0
  57. package/dist/explore-cli/options.d.ts +3 -0
  58. package/dist/explore-cli/options.js +157 -0
  59. package/dist/explore-cli/options.js.map +1 -0
  60. package/dist/explore-cli/run.d.ts +7 -0
  61. package/dist/explore-cli/run.js +702 -0
  62. package/dist/explore-cli/run.js.map +1 -0
  63. package/dist/explore-cli/scope-region.d.ts +17 -0
  64. package/dist/explore-cli/scope-region.js +24 -0
  65. package/dist/explore-cli/scope-region.js.map +1 -0
  66. package/dist/explore-cli/shared.d.ts +167 -0
  67. package/dist/explore-cli/shared.js +20 -0
  68. package/dist/explore-cli/shared.js.map +1 -0
  69. package/dist/explore-cli/usability-strategy.d.ts +3 -0
  70. package/dist/explore-cli/usability-strategy.js +137 -0
  71. package/dist/explore-cli/usability-strategy.js.map +1 -0
  72. package/dist/explore-cli.d.ts +2 -2
  73. package/dist/explore-cli.js +2 -1347
  74. package/dist/explore-cli.js.map +1 -1
  75. package/dist/explore-coverage.d.ts +10 -1
  76. package/dist/explore-coverage.js +3 -0
  77. package/dist/explore-coverage.js.map +1 -1
  78. package/dist/explore-feature.d.ts +6 -0
  79. package/dist/explore-feature.js +1 -0
  80. package/dist/explore-feature.js.map +1 -1
  81. package/dist/explore-goal.d.ts +6 -0
  82. package/dist/explore-goal.js +1 -0
  83. package/dist/explore-goal.js.map +1 -1
  84. package/dist/explore-specs.d.ts +3 -1
  85. package/dist/explore-specs.js +2 -1
  86. package/dist/explore-specs.js.map +1 -1
  87. package/dist/github-issue-filer.js +1 -4
  88. package/dist/github-issue-filer.js.map +1 -1
  89. package/dist/goal-run-flags.d.ts +0 -1
  90. package/dist/goal-run-flags.js.map +1 -1
  91. package/dist/inbox-cli.js +1 -3
  92. package/dist/inbox-cli.js.map +1 -1
  93. package/dist/init-skills.js +1 -4
  94. package/dist/init-skills.js.map +1 -1
  95. package/dist/journey-anchor-api.d.ts +10 -9
  96. package/dist/journey-anchor-api.js +12 -22
  97. package/dist/journey-anchor-api.js.map +1 -1
  98. package/dist/journey-anchor-cli.d.ts +1 -1
  99. package/dist/journey-anchor-cli.js +22 -8
  100. package/dist/journey-anchor-cli.js.map +1 -1
  101. package/dist/journey-cli/anchors.d.ts +4 -0
  102. package/dist/journey-cli/anchors.js +48 -0
  103. package/dist/journey-cli/anchors.js.map +1 -0
  104. package/dist/journey-cli/find.d.ts +4 -0
  105. package/dist/journey-cli/find.js +44 -0
  106. package/dist/journey-cli/find.js.map +1 -0
  107. package/dist/journey-cli/lint.d.ts +4 -0
  108. package/dist/journey-cli/lint.js +84 -0
  109. package/dist/journey-cli/lint.js.map +1 -0
  110. package/dist/journey-cli/list.d.ts +4 -0
  111. package/dist/journey-cli/list.js +48 -0
  112. package/dist/journey-cli/list.js.map +1 -0
  113. package/dist/journey-cli/migrate.d.ts +4 -0
  114. package/dist/journey-cli/migrate.js +101 -0
  115. package/dist/journey-cli/migrate.js.map +1 -0
  116. package/dist/journey-cli/promote.d.ts +4 -0
  117. package/dist/journey-cli/promote.js +181 -0
  118. package/dist/journey-cli/promote.js.map +1 -0
  119. package/dist/journey-cli/publish.d.ts +4 -0
  120. package/dist/journey-cli/publish.js +70 -0
  121. package/dist/journey-cli/publish.js.map +1 -0
  122. package/dist/journey-cli/review.d.ts +4 -0
  123. package/dist/journey-cli/review.js +108 -0
  124. package/dist/journey-cli/review.js.map +1 -0
  125. package/dist/journey-cli/run.d.ts +4 -0
  126. package/dist/journey-cli/run.js +244 -0
  127. package/dist/journey-cli/run.js.map +1 -0
  128. package/dist/journey-cli/shared.d.ts +9 -0
  129. package/dist/journey-cli/shared.js +12 -0
  130. package/dist/journey-cli/shared.js.map +1 -0
  131. package/dist/journey-cli/verify.d.ts +4 -0
  132. package/dist/journey-cli/verify.js +125 -0
  133. package/dist/journey-cli/verify.js.map +1 -0
  134. package/dist/journey-cli.d.ts +3 -2
  135. package/dist/journey-cli.js +25 -950
  136. package/dist/journey-cli.js.map +1 -1
  137. package/dist/journey-link-api.d.ts +40 -0
  138. package/dist/journey-link-api.js +90 -0
  139. package/dist/journey-link-api.js.map +1 -0
  140. package/dist/journey-link-cli.d.ts +7 -0
  141. package/dist/journey-link-cli.js +58 -0
  142. package/dist/journey-link-cli.js.map +1 -0
  143. package/dist/journey-link-hints.d.ts +8 -0
  144. package/dist/journey-link-hints.js +68 -0
  145. package/dist/journey-link-hints.js.map +1 -0
  146. package/dist/journey-lint-sarif.js +3 -0
  147. package/dist/journey-lint-sarif.js.map +1 -1
  148. package/dist/journey-network-checks.d.ts +9 -1
  149. package/dist/journey-network-checks.js +55 -12
  150. package/dist/journey-network-checks.js.map +1 -1
  151. package/dist/journey-prefix.d.ts +9 -1
  152. package/dist/journey-prefix.js +12 -0
  153. package/dist/journey-prefix.js.map +1 -1
  154. package/dist/journey-review/render.d.ts +43 -0
  155. package/dist/journey-review/render.js +461 -0
  156. package/dist/journey-review/render.js.map +1 -0
  157. package/dist/journey-review-api.d.ts +1 -0
  158. package/dist/journey-review-api.js +7 -0
  159. package/dist/journey-review-api.js.map +1 -1
  160. package/dist/journey-review-store.js +1 -0
  161. package/dist/journey-review-store.js.map +1 -1
  162. package/dist/journey-review.d.ts +6 -4
  163. package/dist/journey-review.js +18 -393
  164. package/dist/journey-review.js.map +1 -1
  165. package/dist/journey-verify.js +2 -1
  166. package/dist/journey-verify.js.map +1 -1
  167. package/dist/ledger-api.js +1 -4
  168. package/dist/ledger-api.js.map +1 -1
  169. package/dist/locator-health-api.js +1 -3
  170. package/dist/locator-health-api.js.map +1 -1
  171. package/dist/locator-health.d.ts +0 -1
  172. package/dist/locator-health.js +0 -3
  173. package/dist/locator-health.js.map +1 -1
  174. package/dist/log-classes.js +1 -3
  175. package/dist/log-classes.js.map +1 -1
  176. package/dist/log-sources.js +17 -1
  177. package/dist/log-sources.js.map +1 -1
  178. package/dist/mcp-api.js +16 -821
  179. package/dist/mcp-api.js.map +1 -1
  180. package/dist/mcp-cli-tools.js +48 -7
  181. package/dist/mcp-cli-tools.js.map +1 -1
  182. package/dist/mcp-tools/cli.d.ts +3 -0
  183. package/dist/mcp-tools/cli.js +20 -0
  184. package/dist/mcp-tools/cli.js.map +1 -0
  185. package/dist/mcp-tools/explore.d.ts +3 -0
  186. package/dist/mcp-tools/explore.js +141 -0
  187. package/dist/mcp-tools/explore.js.map +1 -0
  188. package/dist/mcp-tools/findings.d.ts +3 -0
  189. package/dist/mcp-tools/findings.js +277 -0
  190. package/dist/mcp-tools/findings.js.map +1 -0
  191. package/dist/mcp-tools/inbox.d.ts +3 -0
  192. package/dist/mcp-tools/inbox.js +106 -0
  193. package/dist/mcp-tools/inbox.js.map +1 -0
  194. package/dist/mcp-tools/journey.d.ts +3 -0
  195. package/dist/mcp-tools/journey.js +260 -0
  196. package/dist/mcp-tools/journey.js.map +1 -0
  197. package/dist/mcp-tools/shared.d.ts +39 -0
  198. package/dist/mcp-tools/shared.js +69 -0
  199. package/dist/mcp-tools/shared.js.map +1 -0
  200. package/dist/mission-fixtures.js +1 -8
  201. package/dist/mission-fixtures.js.map +1 -1
  202. package/dist/mission-queue-cli.js +1 -3
  203. package/dist/mission-queue-cli.js.map +1 -1
  204. package/dist/multi-run.js +1 -3
  205. package/dist/multi-run.js.map +1 -1
  206. package/dist/pre-approval.js +4 -4
  207. package/dist/pre-approval.js.map +1 -1
  208. package/dist/readiness.js +2 -2
  209. package/dist/readiness.js.map +1 -1
  210. package/dist/report-api.js +1 -4
  211. package/dist/report-api.js.map +1 -1
  212. package/dist/resource-preflight.js.map +1 -1
  213. package/dist/run-metadata.js +1 -3
  214. package/dist/run-metadata.js.map +1 -1
  215. package/dist/run-tags-cli.d.ts +0 -2
  216. package/dist/run-tags-cli.js +0 -4
  217. package/dist/run-tags-cli.js.map +1 -1
  218. package/dist/session-check.js +1 -3
  219. package/dist/session-check.js.map +1 -1
  220. package/dist/signal-triage.js +6 -2
  221. package/dist/signal-triage.js.map +1 -1
  222. package/dist/suite-explore-options.d.ts +8 -0
  223. package/dist/suite-explore-options.js +4 -0
  224. package/dist/suite-explore-options.js.map +1 -1
  225. package/dist/sweep-api.d.ts +9 -0
  226. package/dist/sweep-api.js +70 -4
  227. package/dist/sweep-api.js.map +1 -1
  228. package/dist/sweep-cli.d.ts +2 -0
  229. package/dist/sweep-cli.js +22 -5
  230. package/dist/sweep-cli.js.map +1 -1
  231. package/dist/transcript-file.d.ts +0 -3
  232. package/dist/transcript-file.js +0 -9
  233. package/dist/transcript-file.js.map +1 -1
  234. package/dist/ui-api.js +1 -4
  235. package/dist/ui-api.js.map +1 -1
  236. package/dist/ux-usability/analysis.d.ts +14 -0
  237. package/dist/ux-usability/analysis.js +106 -0
  238. package/dist/ux-usability/analysis.js.map +1 -0
  239. package/dist/ux-usability/done-adjudication.d.ts +15 -0
  240. package/dist/ux-usability/done-adjudication.js +56 -0
  241. package/dist/ux-usability/done-adjudication.js.map +1 -0
  242. package/dist/ux-usability/hang.d.ts +5 -0
  243. package/dist/ux-usability/hang.js +41 -0
  244. package/dist/ux-usability/hang.js.map +1 -0
  245. package/dist/ux-usability/loop.d.ts +15 -0
  246. package/dist/ux-usability/loop.js +104 -0
  247. package/dist/ux-usability/loop.js.map +1 -0
  248. package/dist/ux-usability/observe.d.ts +7 -0
  249. package/dist/ux-usability/observe.js +89 -0
  250. package/dist/ux-usability/observe.js.map +1 -0
  251. package/dist/ux-usability/result.d.ts +11 -0
  252. package/dist/ux-usability/result.js +113 -0
  253. package/dist/ux-usability/result.js.map +1 -0
  254. package/dist/ux-usability/setup.d.ts +76 -0
  255. package/dist/ux-usability/setup.js +204 -0
  256. package/dist/ux-usability/setup.js.map +1 -0
  257. package/dist/ux-usability/teardown.d.ts +3 -0
  258. package/dist/ux-usability/teardown.js +13 -0
  259. package/dist/ux-usability/teardown.js.map +1 -0
  260. package/dist/ux-usability.d.ts +20 -17
  261. package/dist/ux-usability.js +15 -589
  262. package/dist/ux-usability.js.map +1 -1
  263. package/dist/verify-fix-api.js +1 -4
  264. package/dist/verify-fix-api.js.map +1 -1
  265. package/package.json +5 -6
  266. package/skills/jevitate-explore/SKILL.md +9 -1
  267. package/skills/jevitate-getting-started/SKILL.md +1 -0
  268. package/skills/jevitate-promotion-ready/SKILL.md +76 -0
  269. package/skills/jevitate-record/SKILL.md +3 -0
  270. package/skills/jevitate-run-journey/SKILL.md +3 -0
  271. package/skills/jevitate-test-campaign/SKILL.md +5 -3
@@ -0,0 +1,702 @@
1
+ import { taggedAction } from "../run-tags-cli.js";
2
+ import { existsSync } from "node:fs";
3
+ import { envCredentialStore } from "@jevitate/ai-core";
4
+ import { loadLocalCredentials } from "../credentials-file.js";
5
+ import { UnauthorizedExploreTargetError, TypeFixtureSpecError, validateAllowControlPatterns, validateDenyPatterns } from "@jevitate/explore";
6
+ import { loadTypeFixtures } from "../type-fixture-file.js";
7
+ import { secretCommandRunner } from "../secret-command.js";
8
+ import { ok, fail } from "../envelope.js";
9
+ import { SessionFileInProjectError, assertSessionFileOutsideProject } from "../project-dir.js";
10
+ import { InvariantsFileError, loadInvariantFiles, resolveInvariantAuthTokens } from "../invariants-file.js";
11
+ import { FilingConfigError, loadFilingFileConfig, resolveFilingConfig } from "../findings-filing.js";
12
+ import { fileDraftsWithEvidence } from "../defect-evidence.js";
13
+ import { parseScreenshotsArg } from "../run-screenshots.js";
14
+ import { GitHubIssueFiler } from "../github-issue-filer.js";
15
+ import { TargetConfigError, loadTargetsFile, resolveTargetConfig } from "../target-config.js";
16
+ import { setupRefFreeUrl } from "../fixture-cli.js";
17
+ import { LITERAL_SECRET_WARNING, SecretArgError, resolveSecretArgs } from "../secret-args.js";
18
+ import { withEngine } from "../engine.js";
19
+ import { setKillSwitchOutput } from "../kill-signal.js";
20
+ import { EXIT_CODES } from "../exit-codes.js";
21
+ import { formatMissionHuman, formatMultiRunHuman } from "../cli-output.js";
22
+ import { resolveExploreAllowlist } from "../explore-api.js";
23
+ import { parseLogSourceSpecs, LogSourceSpecError } from "../log-sources.js";
24
+ import { parseLogDefectSpecs, parseLogIgnoreSpecs, parseLogScopeSpecs } from "../log-correlation.js";
25
+ import { LogClassesError, loadLogClassRules } from "../log-classes.js";
26
+ import { parseCorrelationHeaders, parseLogIdPatterns } from "../log-trace.js";
27
+ import { LogSpecError } from "../log-lines.js";
28
+ import { MultiRunArgsError, projectPersonaFor, resolveMultiRunPlan, wantsMultiRun } from "../multi-run.js";
29
+ import { authExpiredResult, ensurePersonaSession, LoginArgsError, parseAuthCheck } from "../persona-login.js";
30
+ import { MultiRunAbortedError, runExploreMultiRun } from "../multi-run-cli.js";
31
+ import { checkActorsAgainstSpec, resolveMissionActors } from "../mission-actors.js";
32
+ import { PlaywrightBrowserPort } from "@jevitate/playwright";
33
+ import { browserRunFromFlags, emulationFromFlags, emitCommandResult, environmentSeams, resolveDbPath, resolveJourneysDir, resolveMissionTargetsDir, EXPLORE_STRATEGIES } from "../cli-shared.js";
34
+ import { allowWithExtensions, assertExtensionTargetLoaded, multiWindowWarning } from "../browser-run-options.js";
35
+ import { ParamValidationError } from "@jevitate/journey";
36
+ import { SiteGateRefusedError } from "@jevitate/runtime";
37
+ import { JourneyRequiresAuthError, UnknownJourneyError } from "../journey-api.js";
38
+ import { environmentFromFlags, isEnvironmentError } from "../environments.js";
39
+ import { resolve as resolvePath } from "node:path";
40
+ import { CAMPAIGN_LIMITS, isSweepMode } from "@jevitate/journey";
41
+ import { CampaignSpecError, runCampaign, validateCampaignSpec } from "../campaign-api.js";
42
+ import { formatCampaignHuman } from "../campaign-cli.js";
43
+ import { forwardedArgv } from "../multi-run-cli.js";
44
+ import { resolveScopeRegions } from "./scope-region.js";
45
+ import { ANCHORED_STRATEGIES, JourneyPrefixArgsError, JourneyPrefixStaleError, journeyStaleResult, resolveJourneyPrefix } from "../journey-prefix.js";
46
+ import { clock } from "@jevitate/domain";
47
+ import { GoalRunFlagError, conversationFromFlags, dialogsFromFlags, secretFieldsFromFlags, targetFlagsFromFlags } from "../goal-run-flags.js";
48
+ import { declareExploreCommand } from "./options.js";
49
+ import { runCoverageStrategy } from "./coverage-strategy.js";
50
+ import { runAdversarialStrategy } from "./adversarial-strategy.js";
51
+ import { runUsabilityStrategy } from "./usability-strategy.js";
52
+ import { runFeatureStrategy } from "./feature-strategy.js";
53
+ import { runGoalStrategy } from "./goal-strategy.js";
54
+ import { AUTH_PREFLIGHT_TIMEOUT_MS, SWEEP_OWNED, fixtureBoundUrlRoot } from "./shared.js";
55
+ /**
56
+ * Registers `jevitate explore` (every strategy: goal, coverage, exploratory, adversarial, usability, feature, multi-run).
57
+ * `buildProgram` builds a fresh program for each run of a multi-run (#141/#143).
58
+ */
59
+ export function registerExploreRun(program, deps, buildProgram) {
60
+ declareExploreCommand(program)
61
+ .action(taggedAction(program, "explore", async function () {
62
+ const o = this.opts();
63
+ // #210: one output rule for every strategy — the envelope with --json, a human summary without.
64
+ const emitExplore = (envelope, exitCode, human = formatMissionHuman) => emitCommandResult(program, envelope, { json: o.json === true, command: "explore", human, ...(exitCode === undefined ? {} : { exitCode }) });
65
+ // #230: an unknown --strategy must be refused before any other required-option message — it
66
+ // would otherwise fall through to the default goal-strategy path and silently run a goal
67
+ // mission. Checked first, ahead of every other validation below.
68
+ if (o.strategy !== undefined && !EXPLORE_STRATEGIES.includes(o.strategy)) {
69
+ emitExplore(fail("E_EXPLORE_ARGS", `unknown strategy ${JSON.stringify(o.strategy)} (one of ${EXPLORE_STRATEGIES.join(", ")})`));
70
+ return;
71
+ }
72
+ // #334: a mistyped --dialogs never silently means "dismiss".
73
+ let dialogs;
74
+ try {
75
+ dialogs = dialogsFromFlags(o);
76
+ }
77
+ catch (err) {
78
+ if (!(err instanceof GoalRunFlagError))
79
+ throw err;
80
+ emitExplore(fail(err.code, err.message));
81
+ return;
82
+ }
83
+ // #427: an unusable --auth-check is a usage error, refused before anything runs.
84
+ let authCheck;
85
+ try {
86
+ authCheck = parseAuthCheck(o.authCheck);
87
+ }
88
+ catch (err) {
89
+ if (!(err instanceof LoginArgsError))
90
+ throw err;
91
+ emitExplore(fail("E_EXPLORE_ARGS", err.message));
92
+ return;
93
+ }
94
+ // #195: a session file never lands in the repo's .jevitate/ (refused before any run, multi-runs included).
95
+ if (o.saveStorageState !== undefined) {
96
+ try {
97
+ assertSessionFileOutsideProject(o.saveStorageState, "--save-storage-state");
98
+ }
99
+ catch (err) {
100
+ if (!(err instanceof SessionFileInProjectError))
101
+ throw err;
102
+ emitExplore(fail(err.code, err.message));
103
+ return;
104
+ }
105
+ }
106
+ const strategy = o.strategy ?? "goal";
107
+ // #519: --scope-region on a strategy without a coverage gate, or one that does not parse, is refused up front
108
+ // (also ahead of a --at-step sweep, whose per-stop runs would each refuse it).
109
+ try {
110
+ resolveScopeRegions({ scopeRegion: o.scopeRegion, strategy, ...(o.feature === undefined ? {} : { feature: o.feature }), ...(o.goal === undefined ? {} : { goal: o.goal }) });
111
+ }
112
+ catch (err) {
113
+ emitExplore(fail("E_EXPLORE_ARGS", err instanceof Error ? err.message : String(err)));
114
+ return;
115
+ }
116
+ // #293 journey-anchored exploration: the Journey, step, params and environment are resolved (and
117
+ // refused, exit 64) before anything else — the start URL every later check uses is where the
118
+ // Journey's prefix lands.
119
+ let journeyPrefix;
120
+ /** #399: how the prefix was resolved — re-resolved once `${setup.x}` in a --param is bound (goal + --fixtures). */
121
+ let prefixRequest;
122
+ const anchoredFlags = [o.fromJourney, o.atStep, o.journeysDir, o.env, o.baseUrl].some((v) => v !== undefined);
123
+ if (anchoredFlags || Object.keys(o.param).length > 0) {
124
+ const refuse = (message) => emitExplore(fail("E_EXPLORE_ARGS", message));
125
+ if (o.fromJourney === undefined || o.atStep === undefined) {
126
+ refuse("--from-journey and --at-step go together (and --param, --env, --base-url and --journeys-dir need them)");
127
+ return;
128
+ }
129
+ if (o.url !== undefined) {
130
+ refuse("--url cannot be combined with --from-journey: the mission starts where the Journey's prefix leaves the page");
131
+ return;
132
+ }
133
+ if (o.feature !== undefined || !ANCHORED_STRATEGIES.includes(strategy)) {
134
+ refuse(`--from-journey supports --strategy ${ANCHORED_STRATEGIES.join(", ")} (not --feature)`);
135
+ return;
136
+ }
137
+ if (wantsMultiRun(o) || o.actor.length > 0) {
138
+ refuse("--from-journey runs one anchored mission: --repeat, --persona, --personas and --actor are not supported with it (a campaign runs several)");
139
+ return;
140
+ }
141
+ // #293 sweep: `--at-step all|anchors` runs the strategy from EVERY step (or anchor), each in a
142
+ // fresh session with --fixtures restored around it, --max-actions/--max-decisions split evenly
143
+ // over the stop points, and reads them as one deduped report (a one-job campaign).
144
+ // #312: one anchored step with a state restore (--fixtures/--before/--after) on a non-goal
145
+ // strategy runs the same way — as a one-stop campaign, whose runner restores around the run.
146
+ const restoredSingle = !isSweepMode(o.atStep) && strategy !== "goal" && (o.fixtures !== undefined || o.before !== undefined || o.after !== undefined);
147
+ if ((isSweepMode(o.atStep) || restoredSingle) && Object.values(o.param).some((v) => v.includes("${setup."))) {
148
+ refuse("a --param holding ${setup.x} is bound from --fixtures only by a single --strategy goal run; pass the value itself here");
149
+ return;
150
+ }
151
+ if (isSweepMode(o.atStep) || restoredSingle) {
152
+ if (o.real !== true && o.fakeAi !== true) {
153
+ emitExplore(fail("E_AI_SETUP_REQUIRED", "a sweep's missions are model-driven: pass --real or --fake-ai"));
154
+ return;
155
+ }
156
+ const journeysDir = resolveJourneysDir(deps, o.journeysDir);
157
+ const abs = (p) => resolvePath(p);
158
+ const spec = {
159
+ version: 1,
160
+ name: restoredSingle ? `${o.fromJourney} at step ${o.atStep.trim()} (${strategy}, restored)` : `sweep of ${o.fromJourney} (${o.atStep}, ${strategy})`,
161
+ ...(o.env === undefined ? {} : { env: o.env }),
162
+ ...(o.baseUrl === undefined ? {} : { baseUrl: o.baseUrl }),
163
+ ...(o.storageState === undefined ? {} : { storageState: abs(o.storageState) }),
164
+ ...(o.fixtures === undefined ? {} : { fixtures: abs(o.fixtures) }),
165
+ ...(o.before === undefined ? {} : { before: o.before }),
166
+ ...(o.after === undefined ? {} : { after: o.after }),
167
+ discovery: false,
168
+ maxRuns: CAMPAIGN_LIMITS.maxRuns,
169
+ jobs: [
170
+ {
171
+ id: "sweep",
172
+ journey: o.fromJourney,
173
+ params: o.param,
174
+ anchors: restoredSingle ? [o.atStep.trim()] : o.atStep.trim(),
175
+ strategies: [strategy],
176
+ ...(o.goal === undefined ? {} : { goal: o.goal }),
177
+ ...(o.appClass === undefined ? {} : { appClass: o.appClass }),
178
+ ...(o.success.length === 0 ? {} : { success: o.success }),
179
+ ...(o.maxActions === undefined ? {} : { maxActions: Number(o.maxActions) }),
180
+ ...(o.maxDecisions === undefined ? {} : { maxDecisions: Number(o.maxDecisions) }),
181
+ },
182
+ ],
183
+ };
184
+ try {
185
+ const plan = await validateCampaignSpec(spec, resolvePath("explore-sweep.json"), {
186
+ journeysDir,
187
+ allowShellHooks: o.allowShellHooks === true,
188
+ ...(o.hookTimeoutMs === undefined ? {} : { hookTimeoutMs: Number(o.hookTimeoutMs) }),
189
+ environmentSeams: environmentSeams(deps),
190
+ });
191
+ const result = await runCampaign(plan, {
192
+ newProgram: () => buildProgram(deps),
193
+ journeysDir,
194
+ missionTargetsDir: resolveMissionTargetsDir(deps),
195
+ ...(o.out === undefined ? {} : { outDir: o.out }),
196
+ ...(o.real === true ? { real: true } : {}),
197
+ ...(o.fakeAi === true ? { fakeAi: true } : {}),
198
+ missionArgs: forwardedArgv(this, SWEEP_OWNED),
199
+ });
200
+ emitExplore(ok(withEngine({ sweep: { journeyId: o.fromJourney, mode: restoredSingle ? "step" : o.atStep.trim(), ...(restoredSingle ? { atStep: o.atStep.trim() } : {}), strategy, stops: plan.totalRuns, budgetPerStop: { maxActions: plan.jobs[0]?.maxActions, ...(plan.jobs[0]?.maxDecisions === undefined ? {} : { maxDecisions: plan.jobs[0].maxDecisions }) } }, ...result })), result.exitCode, formatCampaignHuman);
201
+ }
202
+ catch (err) {
203
+ if (err instanceof CampaignSpecError)
204
+ emitExplore(fail("E_EXPLORE_ARGS", err.message));
205
+ else
206
+ emitExplore(fail("E_EXPLORE_RUN", String(err instanceof Error ? err.message : err)));
207
+ }
208
+ return;
209
+ }
210
+ try {
211
+ const environment = environmentFromFlags({ ...(o.env === undefined ? {} : { env: o.env }), ...(o.baseUrl === undefined ? {} : { baseUrl: o.baseUrl }) }, environmentSeams(deps));
212
+ // The environment's own session applies when --storage-state names none (as `journey run`).
213
+ if (o.storageState === undefined && environment?.storageState !== undefined)
214
+ o.storageState = environment.storageState;
215
+ prefixRequest = {
216
+ dir: resolveJourneysDir(deps, o.journeysDir),
217
+ id: o.fromJourney,
218
+ atStep: o.atStep,
219
+ params: o.param,
220
+ ...(environment === undefined ? {} : { environment }),
221
+ ...(o.storageState === undefined ? {} : { storageState: o.storageState }),
222
+ dbPath: resolveDbPath(deps),
223
+ environmentFlags: { ...(o.env === undefined ? {} : { env: o.env }), ...(o.baseUrl === undefined ? {} : { baseUrl: o.baseUrl }) },
224
+ };
225
+ journeyPrefix = await resolveJourneyPrefix(prefixRequest);
226
+ }
227
+ catch (err) {
228
+ if (isEnvironmentError(err) || err instanceof JourneyPrefixArgsError)
229
+ emitExplore(fail(err.code, err.message));
230
+ else if (err instanceof UnknownJourneyError)
231
+ emitExplore(fail("E_UNKNOWN_JOURNEY", err.message));
232
+ else if (err instanceof ParamValidationError)
233
+ emitExplore(fail("E_INVALID_PARAMS", err.message));
234
+ else if (err instanceof JourneyRequiresAuthError)
235
+ emitExplore(fail("E_JOURNEY_REQUIRES_AUTH", err.message));
236
+ else
237
+ emitExplore(fail("E_EXPLORE_ARGS", err instanceof Error ? err.message : String(err)));
238
+ return;
239
+ }
240
+ // The prefix types its secret params into the page the mission perceives: they are redacted like
241
+ // --secret. Coverage/exploratory carry no redaction set, so a prefix with secrets refuses them.
242
+ if (journeyPrefix.secrets.length > 0 && (strategy === "coverage" || strategy === "exploratory")) {
243
+ refuse(`journey '${o.fromJourney}' types a secret param before step ${journeyPrefix.branch.step}: --strategy ${strategy} cannot redact it — use goal, adversarial or usability`);
244
+ return;
245
+ }
246
+ o.url = journeyPrefix.startUrl;
247
+ // Default allowlist: the origins the Journey's steps may be on (the environment's), not just the landing's.
248
+ if (o.allow.length === 0)
249
+ o.allow = [...journeyPrefix.allowedOrigins];
250
+ }
251
+ const withPrefix = journeyPrefix === undefined ? {} : { journeyPrefix };
252
+ // #519: the scope region — the anchor's (authoritative) or --scope-region's — resolved and refused before any browser opens.
253
+ let withScopeRegion;
254
+ try {
255
+ const region = resolveScopeRegions({ scopeRegion: o.scopeRegion, strategy, ...(o.feature === undefined ? {} : { feature: o.feature }), ...(o.goal === undefined ? {} : { goal: o.goal }) }, journeyPrefix);
256
+ withScopeRegion = region === undefined ? {} : { scopeRegions: region.descriptors, scopeRegionSpec: region.spec };
257
+ }
258
+ catch (err) {
259
+ emitExplore(fail("E_EXPLORE_ARGS", err instanceof Error ? err.message : String(err)));
260
+ return;
261
+ }
262
+ /** #293: a prefix that no longer replays, or a site policy that refused it — typed, handled once for every strategy. */
263
+ const emitJourneyFailure = (err) => {
264
+ if (err instanceof JourneyPrefixStaleError) {
265
+ emitExplore(ok(withEngine(journeyStaleResult(err, strategy))), EXIT_CODES.inconclusive);
266
+ return true;
267
+ }
268
+ if (err instanceof SiteGateRefusedError) {
269
+ emitExplore(fail(err.code, err.message));
270
+ return true;
271
+ }
272
+ return false;
273
+ };
274
+ // #195: `--secret env:VAR` is resolved from the environment before anything runs (fail closed).
275
+ try {
276
+ const resolved = resolveSecretArgs(o.secret, process.env, "--secret");
277
+ if (resolved.literals > 0)
278
+ program.configureOutput().writeErr?.(LITERAL_SECRET_WARNING);
279
+ o.secret = [...resolved.secrets, ...(journeyPrefix?.secrets ?? [])];
280
+ }
281
+ catch (err) {
282
+ if (!(err instanceof SecretArgError))
283
+ throw err;
284
+ emitExplore(fail("E_EXPLORE_ARGS", err.message));
285
+ return;
286
+ }
287
+ // #245 demo mode: resolved (and a headed run without a display refused, exit 64) before any
288
+ // browser opens — a multi-run's every run re-resolves the same flags.
289
+ let browser;
290
+ try {
291
+ browser = browserRunFromFlags(o, deps.explore?.env ?? process.env);
292
+ // #256: a chrome-extension:// --url must be a loaded extension's; loaded extensions' origins are allowed.
293
+ assertExtensionTargetLoaded(o.url, browser);
294
+ o.allow = allowWithExtensions(o.url, o.allow, browser);
295
+ }
296
+ catch (err) {
297
+ emitExplore(fail("E_EXPLORE_ARGS", err instanceof Error ? err.message : String(err)));
298
+ return;
299
+ }
300
+ // #251: an unusable --screenshots value is a usage error (64), refused before any browser opens.
301
+ let screenshots;
302
+ try {
303
+ screenshots = parseScreenshotsArg(o.screenshots);
304
+ }
305
+ catch (err) {
306
+ emitExplore(fail("E_EXPLORE_ARGS", err instanceof Error ? err.message : String(err)));
307
+ return;
308
+ }
309
+ // #251/#250: the capture options every strategy's runner takes.
310
+ const screenshotsOpt = { ...(screenshots === undefined ? {} : { screenshots }), ...(o.evidenceVideo === true ? { evidenceVideo: true } : {}) };
311
+ if (browser?.headed === true && (wantsMultiRun(o) || o.actor.length > 1)) {
312
+ program.configureOutput().writeErr?.(multiWindowWarning(o.actor.length > 1 ? "several --actor sessions" : "--repeat/--persona"));
313
+ }
314
+ /**
315
+ * #427: the pre-flight auth check of one session — undefined when it is alive (or refreshed, or
316
+ * not checked: --auth-check off, no --url, a --from-journey run whose prefix replay is its own
317
+ * check), or the `auth-expired` result when it is not. Throws `UnauthorizedExploreTargetError`.
318
+ */
319
+ const sessionPreflight = async (storageState, persona) => {
320
+ if (authCheck.mode === "off" || o.url === undefined || journeyPrefix !== undefined)
321
+ return undefined;
322
+ const allowlist = resolveExploreAllowlist(o.url, o.allow);
323
+ // A start URL bound from --fixtures (`${setup.x}`) does not exist before the setup runs: the
324
+ // check opens the app's root instead (the mission itself starts after the setup, as before).
325
+ const checkUrl = o.url.includes("${") ? fixtureBoundUrlRoot(o.url) : o.url;
326
+ if (checkUrl === undefined)
327
+ return undefined; // no origin before the placeholder: the mission's own URL check refuses it
328
+ const r = await ensurePersonaSession({
329
+ ...(persona === undefined ? {} : { persona: persona.name }),
330
+ ...(persona?.login === undefined ? {} : { login: persona.login }),
331
+ storageState,
332
+ url: checkUrl,
333
+ allowlist,
334
+ check: authCheck,
335
+ port: (deps.explore?.browserPortFactory ?? (() => new PlaywrightBrowserPort()))(),
336
+ ...(browser === undefined ? {} : { browser }),
337
+ env: deps.explore?.env ?? process.env,
338
+ timeoutMs: AUTH_PREFLIGHT_TIMEOUT_MS,
339
+ note: (line) => program.configureOutput().writeErr?.(line),
340
+ });
341
+ return r.ok ? undefined : authExpiredResult(strategy, o.url, r, persona?.name);
342
+ };
343
+ // Repeat-and-vote (#141) / persona matrix (#143): the same command, run sequentially and aggregated.
344
+ if (wantsMultiRun(o)) {
345
+ try {
346
+ const plan = resolveMultiRunPlan(o);
347
+ const result = await runExploreMultiRun({
348
+ cmd: this,
349
+ newProgram: () => buildProgram(deps),
350
+ plan,
351
+ strategy,
352
+ ...(o.out === undefined ? {} : { out: o.out }),
353
+ // #220: a killed multi-run prints ITS partial summary, by this command's own output rule.
354
+ killOutput: (partial) => (o.json === true ? `${JSON.stringify(ok(withEngine(partial)))}\n` : formatMultiRunHuman(partial)),
355
+ // #427: each run's session (its persona's, or the mission's own) is checked before it starts.
356
+ ...(authCheck.mode === "off"
357
+ ? {}
358
+ : {
359
+ authPreflight: async (p) => {
360
+ const state = p?.storageState ?? o.storageState;
361
+ if (state === undefined)
362
+ return undefined;
363
+ const r = await sessionPreflight(state, p ?? projectPersonaFor(state));
364
+ return r === undefined ? undefined : withEngine(r);
365
+ },
366
+ }),
367
+ });
368
+ emitExplore(ok(withEngine(result)), result.exitCode, formatMultiRunHuman);
369
+ }
370
+ catch (err) {
371
+ if (err instanceof MultiRunArgsError)
372
+ emitExplore(fail(err.code, err.message));
373
+ else if (err instanceof UnauthorizedExploreTargetError)
374
+ emitExplore(fail("E_UNAUTHORIZED_EXPLORE_TARGET", err.message));
375
+ else if (err instanceof MultiRunAbortedError)
376
+ emitExplore(fail(err.envelope.error.code, err.envelope.error.message));
377
+ else
378
+ emitExplore(fail("E_EXPLORE_RUN", String(err instanceof Error ? err.message : err)));
379
+ }
380
+ return;
381
+ }
382
+ // #120: a killed run prints what this command would have printed — the envelope with --json,
383
+ // else the human summary (#210) — before it exits.
384
+ setKillSwitchOutput(o.json === true ? "envelope" : "human");
385
+ let conversation;
386
+ try {
387
+ conversation = conversationFromFlags(o);
388
+ }
389
+ catch (err) {
390
+ if (!(err instanceof GoalRunFlagError))
391
+ throw err;
392
+ emitExplore(fail(err.code, err.message));
393
+ return;
394
+ }
395
+ // #154: refused BEFORE any browser opens — `nonNegativeIntArg` already rejects a bad value at
396
+ // parse time (#275: the value is a number here). 0 is valid: "don't replay" — a hang is then
397
+ // reported unconfirmed (inconclusive), never replayed and never a crash.
398
+ try {
399
+ validateDenyPatterns(o.deny);
400
+ validateDenyPatterns(o.paid, "--paid");
401
+ validateAllowControlPatterns(o.allowControl ?? []);
402
+ }
403
+ catch (err) {
404
+ emitExplore(fail("E_EXPLORE_ARGS", err instanceof Error ? err.message : String(err)));
405
+ return;
406
+ }
407
+ // #149: refused BEFORE any browser opens (an unknown --device, or --viewport + --device together).
408
+ let emulation;
409
+ try {
410
+ emulation = emulationFromFlags(o);
411
+ }
412
+ catch (err) {
413
+ emitExplore(fail("E_EXPLORE_ARGS", err instanceof Error ? err.message : String(err)));
414
+ return;
415
+ }
416
+ const overflow = { checkOverflow: o.checkOverflow ?? false, ignoreSelectors: o.ignoreOverflow };
417
+ // Issue filing: drafts are always written; filing needs --file-issues (or config) AND a repo.
418
+ let filing;
419
+ if (o.url !== undefined) {
420
+ try {
421
+ filing = resolveFilingConfig(loadFilingFileConfig(deps.explore?.filingConfigPath), {
422
+ ...(o.fileIssues === undefined ? {} : { fileIssues: o.fileIssues }),
423
+ ...(o.issueRepo === undefined ? {} : { issueRepo: o.issueRepo }),
424
+ ...(o.jevitateRepo === undefined ? {} : { jevitateRepo: o.jevitateRepo }),
425
+ }, new URL(setupRefFreeUrl(o.url)).origin);
426
+ }
427
+ catch (err) {
428
+ if (err instanceof FilingConfigError) {
429
+ emitExplore(fail(err.code, err.message));
430
+ return;
431
+ }
432
+ if (!(err instanceof TypeError))
433
+ throw err;
434
+ // An unparseable --url is refused by the authorized-target guard below.
435
+ }
436
+ }
437
+ // Per-target settle/hang configuration: ~/.jevitate/targets.json by origin, plus flags.
438
+ let target;
439
+ if (o.url !== undefined) {
440
+ try {
441
+ target = resolveTargetConfig(loadTargetsFile(deps.explore?.targetsConfigPath), new URL(setupRefFreeUrl(o.url)).origin, targetFlagsFromFlags(o, dialogs));
442
+ }
443
+ catch (err) {
444
+ if (err instanceof TargetConfigError) {
445
+ emitExplore(fail(err.code, err.message));
446
+ return;
447
+ }
448
+ if (!(err instanceof TypeError))
449
+ throw err;
450
+ }
451
+ }
452
+ const issueFiler = deps.explore?.issueFiler ??
453
+ (() => new GitHubIssueFiler({ store: envCredentialStore(process.env, loadLocalCredentials()) }));
454
+ // #250 --evidence-video: the runner attaches each defect's captioned repro clip + key
455
+ // screenshots; drafts are filed only AFTER their media is linked (the run writes drafts only).
456
+ const evidenceOn = o.evidenceVideo === true;
457
+ const runFiling = filing === undefined ? undefined : evidenceOn ? { ...filing, enabled: false } : filing;
458
+ const withEvidence = async (result) => {
459
+ if (!evidenceOn)
460
+ return result;
461
+ let out = result;
462
+ if (filing?.enabled === true)
463
+ out = await fileDraftsWithEvidence(out, filing, issueFiler, clock.nowIso());
464
+ return out;
465
+ };
466
+ // `--fixture` feeds the upload op, which only the explore loop (goal and
467
+ // usability strategies) can issue. Refuse it elsewhere rather than
468
+ // silently ignoring a file the user expected to be uploaded.
469
+ // #424: the minimum effort gates the goal loop's own endings (report / blocked / done) — a goal run's.
470
+ if ((o.minActions !== undefined || o.minDistinctStates !== undefined) && (o.feature !== undefined || strategy !== "goal")) {
471
+ emitExplore(fail("E_EXPLORE_ARGS", "--min-actions and --min-distinct-states are supported only with --strategy goal (a goal or find-out run)"));
472
+ return;
473
+ }
474
+ if (o.fixture !== undefined && (o.feature !== undefined || (strategy !== "goal" && strategy !== "usability"))) {
475
+ emitExplore(fail("E_EXPLORE_ARGS", "--fixture is supported only with --strategy goal or usability"));
476
+ return;
477
+ }
478
+ // #198: product facts and polish shape the UX review's findings only.
479
+ if ((o.product !== undefined || o.polish === true || o.probeGuards === true) && (o.feature !== undefined || strategy !== "usability")) {
480
+ emitExplore(fail("E_EXPLORE_ARGS", "--product, --polish and --probe-guards are supported only with --strategy usability"));
481
+ return;
482
+ }
483
+ // #225: success checks judge a goal / a usability job — every other strategy (and --feature) would
484
+ // silently ignore them, so they are refused up front, never dropped.
485
+ if ((o.success.length > 0 || o.successWhen !== undefined || o.allowVacuousChecks === true) &&
486
+ (o.feature !== undefined || (strategy !== "goal" && strategy !== "usability"))) {
487
+ emitExplore(fail("E_EXPLORE_ARGS", `--success, --success-when and --allow-vacuous-checks are supported only with --strategy goal or usability (not ${o.feature !== undefined ? "--feature" : `--strategy ${strategy}`})`));
488
+ return;
489
+ }
490
+ // #303: action deltas are recorded by the goal loop (goal and usability runs) only — refused
491
+ // elsewhere, never silently ignored.
492
+ if (o.actionDeltas === true && o.feature !== undefined) {
493
+ emitExplore(fail("E_EXPLORE_ARGS", "--action-deltas is not supported with --feature (goal, usability, coverage, exploratory and adversarial runs record deltas)"));
494
+ return;
495
+ }
496
+ // #427: the project persona (.jevitate/personas.json) this --storage-state is, when it is one — its
497
+ // session may not exist yet: the pre-flight mints it from its login parameters.
498
+ let projectPersona;
499
+ try {
500
+ projectPersona = o.storageState === undefined ? undefined : projectPersonaFor(o.storageState);
501
+ }
502
+ catch (err) {
503
+ if (!(err instanceof MultiRunArgsError))
504
+ throw err;
505
+ emitExplore(fail(err.code, err.message));
506
+ return;
507
+ }
508
+ if (o.storageState !== undefined && !existsSync(o.storageState) && (authCheck.mode === "off" || projectPersona?.login === undefined)) {
509
+ emitExplore(fail("E_EXPLORE_ARGS", `storage state not found: ${o.storageState}`));
510
+ return;
511
+ }
512
+ // Multi-actor missions (#147): the first --actor is the primary, the rest are observers.
513
+ let actors;
514
+ try {
515
+ actors = resolveMissionActors(o.actor);
516
+ }
517
+ catch (err) {
518
+ if (!(err instanceof MultiRunArgsError))
519
+ throw err;
520
+ emitExplore(fail(err.code, err.message));
521
+ return;
522
+ }
523
+ if (actors !== null) {
524
+ if (o.feature !== undefined || strategy !== "goal") {
525
+ emitExplore(fail("E_EXPLORE_ARGS", "--actor is supported only with --strategy goal"));
526
+ return;
527
+ }
528
+ if (o.storageState !== undefined) {
529
+ emitExplore(fail("E_EXPLORE_ARGS", "--storage-state cannot be combined with --actor (the first --actor is the primary's session)"));
530
+ return;
531
+ }
532
+ }
533
+ // The primary's session: its --actor state, else --storage-state.
534
+ const primaryStorageState = actors?.primary.storageState ?? o.storageState;
535
+ // App-declared invariants (#86): validated (schema, observables, probe origins) BEFORE any browser.
536
+ let invariants;
537
+ // #135: authFrom.secret refs (env:VAR), resolved from the environment HERE — the one place this
538
+ // package reads process.env for invariants — never inside @jevitate/explore or @jevitate/recording.
539
+ let invariantAuthTokens;
540
+ if (o.invariants.length > 0) {
541
+ if (o.url !== undefined) {
542
+ try {
543
+ const loaded = loadInvariantFiles(o.invariants, {
544
+ allowlist: resolveExploreAllowlist(o.url, o.allow),
545
+ baseUrl: o.url,
546
+ observers: actors?.observers.map((a) => a.name) ?? [],
547
+ });
548
+ invariants = loaded;
549
+ invariantAuthTokens = loaded === undefined ? undefined : resolveInvariantAuthTokens(loaded, process.env);
550
+ checkActorsAgainstSpec(actors, loaded);
551
+ }
552
+ catch (err) {
553
+ if (err instanceof MultiRunArgsError) {
554
+ emitExplore(fail(err.code, err.message));
555
+ return;
556
+ }
557
+ if (!(err instanceof InvariantsFileError))
558
+ throw err;
559
+ emitExplore(fail(err.code, err.message));
560
+ return;
561
+ }
562
+ }
563
+ // #147: captures and cross-actor checks run in the goal loop only — never silently skipped elsewhere.
564
+ if (invariants?.capture !== undefined && (o.feature !== undefined || strategy !== "goal")) {
565
+ emitExplore(fail("E_EXPLORE_ARGS", "invariants with capture (cross-actor checks) are supported only with --strategy goal"));
566
+ return;
567
+ }
568
+ }
569
+ const withInvariants = {
570
+ ...(invariants === undefined ? {} : { invariants }),
571
+ ...(invariantAuthTokens === undefined || invariantAuthTokens.size === 0 ? {} : { invariantAuthTokens }),
572
+ };
573
+ // Backend log sources (#142): validated (spec shape, --allow-log-cmd gate, matcher regexes)
574
+ // BEFORE any browser opens — the same fail-closed discipline as --invariants above. Supported
575
+ // on every strategy, INCLUDING usability (#142 follow-up): lines attach to usability steps the
576
+ // same way, though a UX run's own outcome stays advisory (a server-log defect is still reported,
577
+ // never gates the exit code — the same rule as every other UX finding).
578
+ let serverLog;
579
+ if (o.logSource.length > 0 || o.logDefect.length > 0) {
580
+ try {
581
+ const sources = parseLogSourceSpecs(o.logSource, o.allowLogCmd ?? false);
582
+ const logDefect = parseLogDefectSpecs(o.logDefect);
583
+ const logIgnore = parseLogIgnoreSpecs(o.logIgnore);
584
+ const logScope = parseLogScopeSpecs(o.logScope);
585
+ const correlationHeaders = parseCorrelationHeaders(o.logCorrelationHeader);
586
+ const idPatterns = parseLogIdPatterns(o.logIdPattern);
587
+ // #422: the project's .jevitate/log-classes.json, validated before any browser opens.
588
+ const logClasses = loadLogClassRules();
589
+ serverLog = {
590
+ sources,
591
+ logDefect,
592
+ allowLogCmd: o.allowLogCmd ?? false,
593
+ quietOk: o.logQuietOk,
594
+ logIgnore,
595
+ logScope,
596
+ correlationHeaders,
597
+ idPatterns,
598
+ logClasses,
599
+ ...(o.serverLogDrainMs === undefined ? {} : { drainMs: Number(o.serverLogDrainMs) }),
600
+ };
601
+ }
602
+ catch (err) {
603
+ if (err instanceof LogSourceSpecError || err instanceof LogSpecError || err instanceof LogClassesError) {
604
+ emitExplore(fail(err.code, err.message));
605
+ return;
606
+ }
607
+ throw err;
608
+ }
609
+ }
610
+ if (o.logTriage === true) {
611
+ if (serverLog === undefined) {
612
+ emitExplore(fail("E_EXPLORE_ARGS", "--log-triage triages the run's backend and browser signals: it needs at least one --log-source"));
613
+ return;
614
+ }
615
+ serverLog = { ...serverLog, triage: {} };
616
+ }
617
+ const withServerLog = serverLog === undefined ? {} : { serverLog };
618
+ // Secret field bindings (#72): resolved from the environment here, typed by code in the goal loop.
619
+ let secretFields = [];
620
+ if (o.secretField.length > 0 || o.totp.length > 0) {
621
+ if (o.feature !== undefined || (strategy !== "goal" && strategy !== "usability")) {
622
+ emitExplore(fail("E_EXPLORE_ARGS", "--secret-field and --totp are supported only with --strategy goal or usability"));
623
+ return;
624
+ }
625
+ try {
626
+ secretFields = secretFieldsFromFlags(o);
627
+ }
628
+ catch (err) {
629
+ if (!(err instanceof GoalRunFlagError))
630
+ throw err;
631
+ emitExplore(fail(err.code, err.message));
632
+ return;
633
+ }
634
+ }
635
+ // #324: a cmd: binding's command runs at type time; its output becomes a run secret at once.
636
+ const secretCommand = secretFields.some((f) => f.kind === "cmd") ? secretCommandRunner() : undefined;
637
+ // #281: fields typed with a file's exact text — read (and checked) before any browser opens.
638
+ let typeFixtures = [];
639
+ if (o.typeFixture.length > 0) {
640
+ if (o.feature !== undefined || strategy !== "goal") {
641
+ emitExplore(fail("E_EXPLORE_ARGS", "--type-fixture is supported only with --strategy goal"));
642
+ return;
643
+ }
644
+ try {
645
+ typeFixtures = loadTypeFixtures(o.typeFixture);
646
+ }
647
+ catch (err) {
648
+ if (!(err instanceof TypeFixtureSpecError))
649
+ throw err;
650
+ emitExplore(fail(err.code, err.message));
651
+ return;
652
+ }
653
+ }
654
+ // Mission fixtures (#140/#144) run around the goal loop and its replays only.
655
+ const fixtureFlagsGiven = o.fixtures !== undefined || o.before !== undefined || o.after !== undefined;
656
+ if (fixtureFlagsGiven && (o.feature !== undefined || strategy !== "goal")) {
657
+ emitExplore(fail("E_EXPLORE_ARGS", "--fixtures, --before and --after are supported only with --strategy goal, or with --from-journey (any anchored strategy)"));
658
+ return;
659
+ }
660
+ // #427 pre-flight auth check: a run that starts from a session first proves the session is alive
661
+ // (re-minting it once from a persona's login parameters) — else it ends now, `auth-expired`.
662
+ if (primaryStorageState !== undefined) {
663
+ let expired;
664
+ try {
665
+ expired = await sessionPreflight(primaryStorageState, actors?.primary ?? projectPersona);
666
+ }
667
+ catch (err) {
668
+ if (err instanceof UnauthorizedExploreTargetError)
669
+ emitExplore(fail("E_UNAUTHORIZED_EXPLORE_TARGET", err.message));
670
+ // The check could not open its browser (the mission could not have either): the run did not start.
671
+ else
672
+ emitExplore(fail("E_EXPLORE_RUN", `the pre-flight auth check failed: ${err instanceof Error ? err.message : String(err)}`));
673
+ return;
674
+ }
675
+ if (expired !== undefined) {
676
+ emitExplore(ok(withEngine(expired)), EXIT_CODES.inconclusive);
677
+ return;
678
+ }
679
+ }
680
+ const ctx = {
681
+ deps, o, emitExplore, strategy, target, browser, screenshotsOpt, emulation, overflow, withInvariants, serverLog, withServerLog, withPrefix, withScopeRegion, emitJourneyFailure, withEvidence, runFiling, issueFiler, conversation, secretFields, secretCommand, actors, primaryStorageState, invariants, journeyPrefix, prefixRequest, typeFixtures,
682
+ };
683
+ if (strategy === "coverage" || strategy === "exploratory") {
684
+ await runCoverageStrategy({ ...ctx, strategy });
685
+ return;
686
+ }
687
+ if (strategy === "adversarial") {
688
+ await runAdversarialStrategy(ctx);
689
+ return;
690
+ }
691
+ if (strategy === "usability") {
692
+ await runUsabilityStrategy(ctx);
693
+ return;
694
+ }
695
+ if (o.feature) {
696
+ await runFeatureStrategy(ctx);
697
+ return;
698
+ }
699
+ await runGoalStrategy(ctx);
700
+ }));
701
+ }
702
+ //# sourceMappingURL=run.js.map