@jevitate/cli 0.5.0 → 0.7.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.
- package/dist/author-journey-cli.d.ts +2 -0
- package/dist/author-journey-cli.js +93 -15
- package/dist/author-journey-cli.js.map +1 -1
- package/dist/bin.js +4249 -1396
- package/dist/build-info.generated.js +2 -2
- package/dist/cli-output.js +4 -0
- package/dist/cli-output.js.map +1 -1
- package/dist/demo-aspect-api.d.ts +6 -5
- package/dist/demo-aspect-api.js +7 -1
- package/dist/demo-aspect-api.js.map +1 -1
- package/dist/environments.js +6 -1
- package/dist/environments.js.map +1 -1
- package/dist/explore-adversarial.js +1 -1
- package/dist/explore-adversarial.js.map +1 -1
- package/dist/explore-author.d.ts +21 -3
- package/dist/explore-author.js +64 -32
- package/dist/explore-author.js.map +1 -1
- package/dist/explore-cli.js +73 -92
- package/dist/explore-cli.js.map +1 -1
- package/dist/explore-coverage.js +1 -1
- package/dist/explore-coverage.js.map +1 -1
- package/dist/explore-goal.js +9 -4
- package/dist/explore-goal.js.map +1 -1
- package/dist/goal-run-flags.d.ts +120 -0
- package/dist/goal-run-flags.js +220 -0
- package/dist/goal-run-flags.js.map +1 -0
- package/dist/journey-api.d.ts +72 -5
- package/dist/journey-api.js +125 -14
- package/dist/journey-api.js.map +1 -1
- package/dist/journey-cli.d.ts +1 -1
- package/dist/journey-cli.js +168 -5
- package/dist/journey-cli.js.map +1 -1
- package/dist/journey-demo-api.d.ts +7 -1
- package/dist/journey-demo-api.js +8 -3
- package/dist/journey-demo-api.js.map +1 -1
- package/dist/journey-demo-cli.js +3 -0
- package/dist/journey-demo-cli.js.map +1 -1
- package/dist/journey-lint-sarif.d.ts +19 -0
- package/dist/journey-lint-sarif.js +48 -0
- package/dist/journey-lint-sarif.js.map +1 -0
- package/dist/journey-network-checks.d.ts +45 -9
- package/dist/journey-network-checks.js +117 -30
- package/dist/journey-network-checks.js.map +1 -1
- package/dist/journey-prefix.d.ts +24 -15
- package/dist/journey-prefix.js +53 -7
- package/dist/journey-prefix.js.map +1 -1
- package/dist/journey-verify.d.ts +61 -0
- package/dist/journey-verify.js +131 -0
- package/dist/journey-verify.js.map +1 -0
- package/dist/load-api.js +9 -3
- package/dist/load-api.js.map +1 -1
- package/dist/mcp-cli-tools.d.ts +1 -0
- package/dist/mcp-cli-tools.js +61 -3
- package/dist/mcp-cli-tools.js.map +1 -1
- package/dist/promotions.d.ts +15 -0
- package/dist/promotions.js +52 -0
- package/dist/promotions.js.map +1 -0
- package/dist/self-heal-adapter.js +3 -1
- package/dist/self-heal-adapter.js.map +1 -1
- package/dist/signal-triage.js +5 -1
- package/dist/signal-triage.js.map +1 -1
- package/dist/source-run-api.js +17 -7
- package/dist/source-run-api.js.map +1 -1
- package/dist/usability-capture.js +11 -1
- package/dist/usability-capture.js.map +1 -1
- package/dist/ux-usability.js +1 -1
- package/dist/ux-usability.js.map +1 -1
- package/package.json +10 -10
- package/skills/jevitate-explore/SKILL.md +11 -0
package/dist/explore-author.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { PlaywrightBrowserPort } from "@jevitate/playwright";
|
|
2
|
-
import { sessionLaunchOptions } from "./browser-run-options.js";
|
|
3
|
-
import { CastActor, BrowseTheWeb } from "@jevitate/screenplay";
|
|
4
1
|
import { authorJourney, assertAuthorizedExploreTarget, } from "@jevitate/explore";
|
|
5
2
|
import { FsJourneyStore } from "@jevitate/journey";
|
|
3
|
+
import { runExploration } from "./explore-goal.js";
|
|
4
|
+
import { screenshotsDirFor } from "./run-screenshots.js";
|
|
6
5
|
/**
|
|
7
6
|
* The programmatic surface behind `jevitate explore author-journey` — drives
|
|
8
7
|
* the goal-based exploration mission and feeds its take(s) through RxD's
|
|
@@ -36,6 +35,9 @@ export async function runAuthorJourney(opts) {
|
|
|
36
35
|
browser: opts.browser,
|
|
37
36
|
...(opts.storageState !== undefined ? { storageState: opts.storageState } : {}),
|
|
38
37
|
...(opts.safety === undefined ? {} : { safety: opts.safety }),
|
|
38
|
+
...(opts.goalRun === undefined ? {} : { goalRun: opts.goalRun }),
|
|
39
|
+
...(opts.outDir === undefined ? {} : { outDir: opts.outDir }),
|
|
40
|
+
...(opts.usage === undefined ? {} : { usage: opts.usage }),
|
|
39
41
|
});
|
|
40
42
|
if (result.outcome === "authored") {
|
|
41
43
|
// #170: a Journey authored behind a login (--storage-state) declares it, so a run without a
|
|
@@ -48,41 +50,71 @@ export async function runAuthorJourney(opts) {
|
|
|
48
50
|
}
|
|
49
51
|
return result;
|
|
50
52
|
}
|
|
51
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* Default authoring step (#369): every take — the discovery and each corroborating one — is a full
|
|
55
|
+
* goal run (`runExploration`, exactly what `jevitate explore --strategy goal` runs, with the same
|
|
56
|
+
* run-shaping options), in its own browser, writing its own result, transcript and Recording; its
|
|
57
|
+
* verdict (`goalOutcome`) decides whether the take reached the goal.
|
|
58
|
+
*/
|
|
52
59
|
async function authorViaBrowser(args) {
|
|
53
60
|
if (!args.judge || !args.gen) {
|
|
54
61
|
throw new Error("runAuthorJourney: judge and gen gateways are required to drive the authoring mission");
|
|
55
62
|
}
|
|
56
63
|
const judge = args.judge;
|
|
57
64
|
const gen = args.gen;
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
...(args.
|
|
65
|
+
const g = args.goalRun ?? {};
|
|
66
|
+
// targets.json's safety (`goalRun.target`) wins; a caller's `safety` (#249) applies without one.
|
|
67
|
+
const target = g.target ?? (args.safety === undefined ? undefined : { safety: args.safety });
|
|
68
|
+
return authorJourney({
|
|
69
|
+
goal: args.goal,
|
|
70
|
+
...(args.successAssertion === undefined ? {} : { successAssertion: args.successAssertion }),
|
|
71
|
+
...(args.successChecks === undefined ? {} : { successChecks: args.successChecks }),
|
|
72
|
+
allowlist: args.allowlist,
|
|
73
|
+
startUrl: args.url,
|
|
74
|
+
bounds: args.bounds,
|
|
75
|
+
takes: args.takes,
|
|
76
|
+
journeyId: args.journeyId,
|
|
77
|
+
journeyName: args.journeyName,
|
|
78
|
+
runTake: async ({ goal, successChecks }) => {
|
|
79
|
+
const r = await runExploration({
|
|
80
|
+
...g,
|
|
81
|
+
...(target === undefined ? {} : { target }),
|
|
82
|
+
url: args.url,
|
|
83
|
+
goal,
|
|
84
|
+
successChecks,
|
|
85
|
+
allowlist: args.allowlist,
|
|
86
|
+
judge,
|
|
87
|
+
gen,
|
|
88
|
+
...(args.usage === undefined ? {} : { usage: args.usage }),
|
|
89
|
+
bounds: args.bounds,
|
|
90
|
+
...(args.outDir === undefined ? {} : { outDir: args.outDir }),
|
|
91
|
+
...(args.browserPortFactory === undefined ? {} : { browserPortFactory: args.browserPortFactory }),
|
|
92
|
+
...(args.browser === undefined ? {} : { browser: args.browser }),
|
|
93
|
+
...(args.storageState === undefined ? {} : { storageState: args.storageState }),
|
|
94
|
+
});
|
|
95
|
+
return goalRunTake(r, g);
|
|
96
|
+
},
|
|
65
97
|
});
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
98
|
+
}
|
|
99
|
+
/** #369: one goal run as an authoring take — its Recording and where everything it wrote is. */
|
|
100
|
+
export function goalRunTake(r, g = {}) {
|
|
101
|
+
const reason = r.reason ?? r.failure?.message;
|
|
102
|
+
const recordingPath = r.recordingPaths[0];
|
|
103
|
+
const diagnostics = {
|
|
104
|
+
outcome: r.goalOutcome,
|
|
105
|
+
...(reason === undefined ? {} : { reason }),
|
|
106
|
+
stop: r.stop,
|
|
107
|
+
runOutcome: r.runOutcome,
|
|
108
|
+
...(r.failure === undefined ? {} : { failure: { kind: r.failure.kind, message: r.failure.message } }),
|
|
109
|
+
checks: r.checks,
|
|
110
|
+
decisions: r.decisions,
|
|
111
|
+
actions: r.actions,
|
|
112
|
+
finalUrl: r.finalUrl,
|
|
113
|
+
resultPath: r.resultPath,
|
|
114
|
+
transcriptPath: r.transcriptPath,
|
|
115
|
+
recordingPaths: r.recordingPaths,
|
|
116
|
+
...(g.screenshots === undefined || recordingPath === undefined ? {} : { screenshotsDir: screenshotsDirFor(g.screenshots, recordingPath) }),
|
|
117
|
+
};
|
|
118
|
+
return { outcome: r.goalOutcome, recording: r.recording, diagnostics };
|
|
87
119
|
}
|
|
88
120
|
//# sourceMappingURL=explore-author.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"explore-author.js","sourceRoot":"","sources":["../src/explore-author.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"explore-author.js","sourceRoot":"","sources":["../src/explore-author.ts"],"names":[],"mappings":"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"}
|
package/dist/explore-cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { envCredentialStore, MissingCredentialError } from "@jevitate/ai-core";
|
|
3
3
|
import { loadLocalCredentials } from "./credentials-file.js";
|
|
4
|
-
import { FixtureNotFoundError, ScopeUnderivableError, UnauthorizedExploreTargetError, resolveCoverageThresholds,
|
|
4
|
+
import { FixtureNotFoundError, ScopeUnderivableError, UnauthorizedExploreTargetError, resolveCoverageThresholds, TypeFixtureSpecError, validateDenyPatterns, } from "@jevitate/explore";
|
|
5
5
|
import { loadTypeFixtures } from "./type-fixture-file.js";
|
|
6
6
|
import { secretCommandRunner } from "./secret-command.js";
|
|
7
7
|
import { ok, fail } from "./envelope.js";
|
|
@@ -18,7 +18,7 @@ import { LITERAL_SECRET_WARNING, SecretArgError, resolveSecretArgs } from "./sec
|
|
|
18
18
|
import { withEngine } from "./engine.js";
|
|
19
19
|
import { setKillSwitchOutput } from "./kill-signal.js";
|
|
20
20
|
import { EXIT_CODES } from "./exit-codes.js";
|
|
21
|
-
import {
|
|
21
|
+
import { positiveNumberArg, nonNegativeIntArg, positiveIntArg, ratioArg } from "./cli-args.js";
|
|
22
22
|
import { formatMissionHuman, formatMultiRunHuman } from "./cli-output.js";
|
|
23
23
|
import { runExploration, runCoverageMission, runAdversarialCliMission, CLI_ADVERSARIAL_STRATEGIES, runFeatureCliMission, parseSuccessSpec, resolveExploreAllowlist, } from "./explore-api.js";
|
|
24
24
|
import { parseLogSourceSpecs, LogSourceSpecError } from "./log-sources.js";
|
|
@@ -45,6 +45,7 @@ import { formatCampaignHuman } from "./campaign-cli.js";
|
|
|
45
45
|
import { forwardedArgv } from "./multi-run-cli.js";
|
|
46
46
|
import { ANCHORED_STRATEGIES, JourneyPrefixArgsError, JourneyPrefixStaleError, journeyStaleResult, resolveJourneyPrefix, } from "./journey-prefix.js";
|
|
47
47
|
import { clock } from "@jevitate/domain";
|
|
48
|
+
import { GOAL_RUN_OPTIONS, GoalRunFlagError, conversationFromFlags, dialogsFromFlags, secretFieldsFromFlags, successWhenFromFlags, targetFlagsFromFlags, } from "./goal-run-flags.js";
|
|
48
49
|
/**
|
|
49
50
|
* #293: the flags a sweep sets on each of its missions itself (the rest of the command line is
|
|
50
51
|
* forwarded to every mission as given).
|
|
@@ -95,79 +96,59 @@ export function registerExploreCommands(program, deps, buildProgram) {
|
|
|
95
96
|
"Omit it for a find-out goal (e.g. \"find out how many contacts... report the answer\"): the run must then end",
|
|
96
97
|
"with the model's own `report` op, and the grounded answer (#101) is the verdict — no page/network check needed.",
|
|
97
98
|
].join(" "), (v, prev) => [...prev, v], [])
|
|
98
|
-
.
|
|
99
|
-
|
|
100
|
-
.
|
|
101
|
-
"(an empty result container), a requestMade/responseStatus matched only by a page-load or polling request — FAILS: it cannot verify the goal")
|
|
102
|
-
.option("--action-deltas", "opt-in (#303; every --strategy, not --feature): record what each action changed on the page — an accessibility snapshot before and after, announcements, " +
|
|
103
|
-
"the action's requests — redacted, with a code verdict per step (no-change | relevant-change | inconclusive) used by the goal loop's no-progress check and a persistence re-check after writes (goal), and as defect evidence (adversarial, coverage); " +
|
|
104
|
-
"adds `delta` to every transcript step (and Recording step, goal) and `actionDeltas` to the result. Costs about 50-100 ms per action on a small page, 0.3-0.5 s on a large one")
|
|
99
|
+
.addOption(GOAL_RUN_OPTIONS.successWhen())
|
|
100
|
+
.addOption(GOAL_RUN_OPTIONS.allowVacuousChecks())
|
|
101
|
+
.addOption(GOAL_RUN_OPTIONS.actionDeltas())
|
|
105
102
|
.option("--feature <name>", "run the capability-scoped feature-testing mission (instead of --goal/--success)")
|
|
106
103
|
.option("--route <glob>", "in-scope route glob (repeatable), e.g. /thread/** — for --feature it replaces the default scope (the start URL's route and everything under it); it widens --strategy adversarial/coverage/exploratory beyond the start URL's route", (v, prev) => [...prev, v], [])
|
|
107
104
|
.option("--scope <mode>", "--strategy coverage/exploratory: 'app' widens containment to the whole app (same as --route '/**'); default: the start URL's route plus --route globs")
|
|
108
105
|
.option("--allow <origin>", "authorized origin (repeatable); REPLACES the default allowlist when given (the URL's own origin is used only when --allow is omitted entirely) -- include the URL's own origin explicitly if you still need it", (v, prev) => [...prev, v], [])
|
|
109
|
-
.
|
|
110
|
-
.
|
|
111
|
-
|
|
112
|
-
.
|
|
113
|
-
.
|
|
114
|
-
.option("--fixture <path>", "local file the upload op attaches to a file input (goal and usability strategies); must exist")
|
|
106
|
+
.addOption(GOAL_RUN_OPTIONS.secret())
|
|
107
|
+
.addOption(GOAL_RUN_OPTIONS.secretField())
|
|
108
|
+
.addOption(GOAL_RUN_OPTIONS.totp())
|
|
109
|
+
.addOption(GOAL_RUN_OPTIONS.typeFixture())
|
|
110
|
+
.addOption(GOAL_RUN_OPTIONS.fixture())
|
|
115
111
|
.option("--storage-state <file>", "Playwright storageState JSON to start the session authenticated (deterministic login pre-step); must exist")
|
|
116
112
|
.option("--actor <name=storageState>", "multi-actor mission (#147, goal only; repeatable): the FIRST actor is the primary (the only one the model drives, " +
|
|
117
113
|
"from its own storageState); every other actor is an observer in its OWN fresh context that only runs the " +
|
|
118
114
|
"--invariants' cross-actor checks (capture + probe as:/deniedAs) — never clicks or types. Replaces --storage-state", (v, prev) => [...prev, v], [])
|
|
119
|
-
.
|
|
120
|
-
"Useful with a rotating refresh token: --storage-state's file goes stale after one authenticated run refreshes it, " +
|
|
121
|
-
"so point --save-storage-state at the SAME file (or a new one) to keep it usable for the next run. " +
|
|
122
|
-
"Written on every exit path -- a crash or a SIGTERM/SIGINT kill included (#159), not only a clean end -- but " +
|
|
123
|
-
"never over a good file with a session that already looks lost/logged-out; the last known-good state is used " +
|
|
124
|
-
"instead, or nothing is written if none was ever captured.")
|
|
115
|
+
.addOption(GOAL_RUN_OPTIONS.saveStorageState())
|
|
125
116
|
.option("--max-actions <n>", "hard cap on executed actions", positiveIntArg)
|
|
126
117
|
.option("--max-decisions <n>", "hard cap on model decisions", positiveIntArg)
|
|
127
118
|
.option("--stall-timeout <seconds>", "--strategy coverage/exploratory and --feature: end the run inconclusive (stalled) when no step completes within this many seconds (default 120)", positiveNumberArg)
|
|
128
|
-
.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
.
|
|
132
|
-
|
|
133
|
-
.
|
|
134
|
-
.
|
|
135
|
-
.
|
|
136
|
-
|
|
137
|
-
"it also bounds a busy indicator the app visibly keeps working behind (live progress, a job poll) before it is a hang, " +
|
|
138
|
-
"and a wait the page documents (\"usually takes a minute\") can raise it", positiveIntArg)
|
|
139
|
-
.option("--deny <pattern>", "a control no mission may click (repeatable): an accessible-name regex (/Archive/i or Archive) or a descriptor role=button;name=Archive. " +
|
|
140
|
-
"Session-ending (Sign out), destructive (Delete, Revoke, Rotate) and paid (Buy, Run simulation, Generate, Send invite) controls are refused by default", (v, prev) => [...prev, v], [])
|
|
141
|
-
.option("--paid <pattern>", "an app control that costs money or credits (repeatable; same syntax as --deny), e.g. /^(Analyze|Draft|Improve)\\b/i: treated like the built-in paid " +
|
|
142
|
-
"vocabulary — the budget guard sees it, hang replays never repeat it, and a goal that asks for it may still click it", (v, prev) => [...prev, v], [])
|
|
143
|
-
.option("--allow-destructive", "let missions click session-ending, destructive and paid controls (a --deny pattern still holds). A goal run already may click one its goal asks for")
|
|
144
|
-
.option("--dialogs <policy>", "native window.confirm/prompt dialogs: dismiss (default) or accept. accept still dismisses one whose message names a session-ending, " +
|
|
145
|
-
"destructive or paid action the run may not take (without --allow-destructive or a goal asking for it); every dialog is logged")
|
|
119
|
+
.addOption(GOAL_RUN_OPTIONS.replyWaitMs())
|
|
120
|
+
.addOption(GOAL_RUN_OPTIONS.replyQuietMs())
|
|
121
|
+
.addOption(GOAL_RUN_OPTIONS.replyCeilingMs())
|
|
122
|
+
.addOption(GOAL_RUN_OPTIONS.replyMaxChars())
|
|
123
|
+
.addOption(GOAL_RUN_OPTIONS.jobWaitMs())
|
|
124
|
+
.addOption(GOAL_RUN_OPTIONS.deny())
|
|
125
|
+
.addOption(GOAL_RUN_OPTIONS.paid())
|
|
126
|
+
.addOption(GOAL_RUN_OPTIONS.allowDestructive())
|
|
127
|
+
.addOption(GOAL_RUN_OPTIONS.dialogs())
|
|
146
128
|
.option("--allow-writes", "let a find-out goal (no --success check, ended by report) change the app. By default it is read-only: controls that start a write flow " +
|
|
147
129
|
"(checkout, upgrade, create, save, submit…) are refused and the write requests an action fires are blocked, unless the goal itself asks for a change")
|
|
148
130
|
.option("--allow-write <glob>", "a write-request path a read-only find-out goal never blocks (repeatable; ** spans segments; a glob starting with https:// matches " +
|
|
149
131
|
"origin + path, e.g. https://abc.supabase.co/rest/v1/**), beyond the built-in auth-refresh ones " +
|
|
150
132
|
"(**/refresh*, **/token*, **/oauth/**, **/auth/**/refresh*). The app's background writes outside an action always pass", (v, prev) => [...prev, v], [])
|
|
151
|
-
.
|
|
152
|
-
"gRPC-web/Connect Get*/List*/Search*/Find*/Watch*/Stream*/Count*/Describe*/Read* methods are reads already. Reads are never guarded or reported as duplicate writes", (v, prev) => [...prev, v], [])
|
|
133
|
+
.addOption(GOAL_RUN_OPTIONS.readRpc())
|
|
153
134
|
.option("--real", "use live Jev + OpenRouter gateways (requires keys)", false)
|
|
154
135
|
.option("--fake-ai", "use deterministic fake gateways (pipeline smoke only)", false)
|
|
155
136
|
.option("--out <dir>", "directory to write the emitted Recording")
|
|
156
137
|
.option("--file-issues", "file findings as issues (needs a repo: --issue-repo or ~/.jevitate/filing.json); default: drafts only")
|
|
157
138
|
.option("--issue-repo <owner/name>", "the system-under-test repo findings for THIS target are filed to")
|
|
158
|
-
.
|
|
139
|
+
.addOption(GOAL_RUN_OPTIONS.hangReplays())
|
|
159
140
|
.option("--hang-replay-writes", "let hang replays re-send a paid/destructive write the run sent (default: such a hang is reported inconclusive, never replayed)")
|
|
160
|
-
.
|
|
161
|
-
.
|
|
162
|
-
.
|
|
163
|
-
.
|
|
141
|
+
.addOption(GOAL_RUN_OPTIONS.settleIgnore())
|
|
142
|
+
.addOption(GOAL_RUN_OPTIONS.longPollMs())
|
|
143
|
+
.addOption(GOAL_RUN_OPTIONS.apiPrefix())
|
|
144
|
+
.addOption(GOAL_RUN_OPTIONS.ignoreNoProgress())
|
|
164
145
|
.option("--jevitate-repo <owner/name>", "where jevitate engine findings are filed (default matt-cochran/jevitate)")
|
|
165
146
|
.option("--min-control-coverage <ratio>", "adversarial: share of the target's controls (0..1) a run must exercise before 'found nothing' is clean (default 0.25); below it the run is inconclusive", ratioArg)
|
|
166
147
|
.option("--no-require-form-submit", "adversarial: do not require a submitted form for a clean result (default: required when the target has a form)")
|
|
167
148
|
.option("--invariants <file>", "app-declared invariants JSON (repeatable; goal, coverage, exploratory, adversarial, --feature): checked around every action, a violation is a defect (exit 1). Validated before any browser opens; probes are GET/HEAD on an --allow origin only", (v, prev) => [...prev, v], [])
|
|
168
149
|
.option("--log-source <spec>", "backend log source (repeatable; every strategy, incl. usability): file:<path> (tailed from its current end) | docker:<container> (docker logs -f --since 0s) | cmd:<command> (needs --allow-log-cmd). Read-only, operator-declared, never the model's choice. Error/warning lines are correlated to the step they landed during and attached to its transcript evidence, redacted", (v, prev) => [...prev, v], [])
|
|
169
|
-
.
|
|
170
|
-
.
|
|
150
|
+
.addOption(GOAL_RUN_OPTIONS.allowSecretCmd())
|
|
151
|
+
.addOption(GOAL_RUN_OPTIONS.secretCmdAttempts())
|
|
171
152
|
.option("--allow-log-cmd", "opt-in: a --log-source cmd:<command> may run as a subprocess (operator-declared only; refused otherwise)", false)
|
|
172
153
|
.option("--log-defect <level|/regex/>", "backend log lines matching this (repeatable) become a server-log defect: a level (error|warn|info|debug, matched as level>=this) or a /regex/flags/ over the raw line. Its fingerprint is the normalized message (ids/numbers/uuids/timestamps stripped) plus the correlated route; verify-fix re-checks it by re-tailing the same --log-source(s)", (v, prev) => [...prev, v], [])
|
|
173
154
|
.option("--log-quiet-ok <spec>", "declares a --log-source spec (exact match, repeatable) as legitimately quiet: zero lines from it does not make the --log-defect oracle unhealthy (#169). Without it, a declared source that opened but delivered not one line makes an otherwise-clean run inconclusive, same as one that failed to open", (v, prev) => [...prev, v], [])
|
|
@@ -220,8 +201,14 @@ export function registerExploreCommands(program, deps, buildProgram) {
|
|
|
220
201
|
return;
|
|
221
202
|
}
|
|
222
203
|
// #334: a mistyped --dialogs never silently means "dismiss".
|
|
223
|
-
|
|
224
|
-
|
|
204
|
+
let dialogs;
|
|
205
|
+
try {
|
|
206
|
+
dialogs = dialogsFromFlags(o);
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
209
|
+
if (!(err instanceof GoalRunFlagError))
|
|
210
|
+
throw err;
|
|
211
|
+
emitExplore(fail(err.code, err.message));
|
|
225
212
|
return;
|
|
226
213
|
}
|
|
227
214
|
// #195: a session file never lands in the repo's .jevitate/ (refused before any run, multi-runs included).
|
|
@@ -241,6 +228,8 @@ export function registerExploreCommands(program, deps, buildProgram) {
|
|
|
241
228
|
// refused, exit 64) before anything else — the start URL every later check uses is where the
|
|
242
229
|
// Journey's prefix lands.
|
|
243
230
|
let journeyPrefix;
|
|
231
|
+
/** #399: how the prefix was resolved — re-resolved once `${setup.x}` in a --param is bound (goal + --fixtures). */
|
|
232
|
+
let prefixRequest;
|
|
244
233
|
const anchoredFlags = [o.fromJourney, o.atStep, o.journeysDir, o.env, o.baseUrl].some((v) => v !== undefined);
|
|
245
234
|
if (anchoredFlags || Object.keys(o.param).length > 0) {
|
|
246
235
|
const refuse = (message) => emitExplore(fail("E_EXPLORE_ARGS", message));
|
|
@@ -266,6 +255,10 @@ export function registerExploreCommands(program, deps, buildProgram) {
|
|
|
266
255
|
// #312: one anchored step with a state restore (--fixtures/--before/--after) on a non-goal
|
|
267
256
|
// strategy runs the same way — as a one-stop campaign, whose runner restores around the run.
|
|
268
257
|
const restoredSingle = !isSweepMode(o.atStep) && strategy !== "goal" && (o.fixtures !== undefined || o.before !== undefined || o.after !== undefined);
|
|
258
|
+
if ((isSweepMode(o.atStep) || restoredSingle) && Object.values(o.param).some((v) => v.includes("${setup."))) {
|
|
259
|
+
refuse("a --param holding ${setup.x} is bound from --fixtures only by a single --strategy goal run; pass the value itself here");
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
269
262
|
if (isSweepMode(o.atStep) || restoredSingle) {
|
|
270
263
|
if (o.real !== true && o.fakeAi !== true) {
|
|
271
264
|
emitExplore(fail("E_AI_SETUP_REQUIRED", "a sweep's missions are model-driven: pass --real or --fake-ai"));
|
|
@@ -330,7 +323,7 @@ export function registerExploreCommands(program, deps, buildProgram) {
|
|
|
330
323
|
// The environment's own session applies when --storage-state names none (as `journey run`).
|
|
331
324
|
if (o.storageState === undefined && environment?.storageState !== undefined)
|
|
332
325
|
o.storageState = environment.storageState;
|
|
333
|
-
|
|
326
|
+
prefixRequest = {
|
|
334
327
|
dir: resolveJourneysDir(deps, o.journeysDir),
|
|
335
328
|
id: o.fromJourney,
|
|
336
329
|
atStep: o.atStep,
|
|
@@ -339,7 +332,8 @@ export function registerExploreCommands(program, deps, buildProgram) {
|
|
|
339
332
|
...(o.storageState === undefined ? {} : { storageState: o.storageState }),
|
|
340
333
|
dbPath: resolveDbPath(deps),
|
|
341
334
|
environmentFlags: { ...(o.env === undefined ? {} : { env: o.env }), ...(o.baseUrl === undefined ? {} : { baseUrl: o.baseUrl }) },
|
|
342
|
-
}
|
|
335
|
+
};
|
|
336
|
+
journeyPrefix = await resolveJourneyPrefix(prefixRequest);
|
|
343
337
|
}
|
|
344
338
|
catch (err) {
|
|
345
339
|
if (isEnvironmentError(err) || err instanceof JourneyPrefixArgsError)
|
|
@@ -446,23 +440,14 @@ export function registerExploreCommands(program, deps, buildProgram) {
|
|
|
446
440
|
// #120: a killed run prints what this command would have printed — the envelope with --json,
|
|
447
441
|
// else the human summary (#210) — before it exits.
|
|
448
442
|
setKillSwitchOutput(o.json === true ? "envelope" : "human");
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
...(o.replyCeilingMs === undefined ? {} : { replyCeilingMs: Number(o.replyCeilingMs) }),
|
|
453
|
-
...(o.replyMaxChars === undefined ? {} : { replyMaxChars: Number(o.replyMaxChars) }),
|
|
454
|
-
...(o.jobWaitMs === undefined ? {} : { jobWaitMs: Number(o.jobWaitMs) }),
|
|
455
|
-
};
|
|
456
|
-
if ((conversation.replyWaitMs !== undefined && !(Number.isInteger(conversation.replyWaitMs) && conversation.replyWaitMs > 0)) ||
|
|
457
|
-
(conversation.replyCeilingMs !== undefined &&
|
|
458
|
-
!(Number.isInteger(conversation.replyCeilingMs) && conversation.replyCeilingMs > 0)) ||
|
|
459
|
-
(conversation.replyMaxChars !== undefined &&
|
|
460
|
-
!(Number.isInteger(conversation.replyMaxChars) && conversation.replyMaxChars >= 20 && conversation.replyMaxChars <= 2000))) {
|
|
461
|
-
emitExplore(fail("E_EXPLORE_ARGS", "--reply-wait-ms and --reply-ceiling-ms must be positive integers; --reply-max-chars an integer in 20..2000"));
|
|
462
|
-
return;
|
|
443
|
+
let conversation;
|
|
444
|
+
try {
|
|
445
|
+
conversation = conversationFromFlags(o);
|
|
463
446
|
}
|
|
464
|
-
|
|
465
|
-
|
|
447
|
+
catch (err) {
|
|
448
|
+
if (!(err instanceof GoalRunFlagError))
|
|
449
|
+
throw err;
|
|
450
|
+
emitExplore(fail(err.code, err.message));
|
|
466
451
|
return;
|
|
467
452
|
}
|
|
468
453
|
// #154: refused BEFORE any browser opens — `nonNegativeIntArg` already rejects a bad value at
|
|
@@ -510,20 +495,7 @@ export function registerExploreCommands(program, deps, buildProgram) {
|
|
|
510
495
|
let target;
|
|
511
496
|
if (o.url !== undefined) {
|
|
512
497
|
try {
|
|
513
|
-
target = resolveTargetConfig(loadTargetsFile(deps.explore?.targetsConfigPath), new URL(setupRefFreeUrl(o.url)).origin,
|
|
514
|
-
settleIgnore: o.settleIgnore,
|
|
515
|
-
ignoreNoProgress: o.ignoreNoProgress,
|
|
516
|
-
apiPrefixes: o.apiPrefix,
|
|
517
|
-
deny: o.deny,
|
|
518
|
-
paid: o.paid,
|
|
519
|
-
readRpc: o.readRpc,
|
|
520
|
-
...(o.allowDestructive === true ? { allowDestructive: true } : {}),
|
|
521
|
-
...(o.dialogs === undefined ? {} : { dialogs: o.dialogs }),
|
|
522
|
-
...(o.allowWrites === true ? { allowWrites: true } : {}),
|
|
523
|
-
allowWrite: o.allowWrite,
|
|
524
|
-
...(o.hangReplayWrites === true ? { hangReplayWrites: true } : {}),
|
|
525
|
-
...(o.longPollMs === undefined ? {} : { longPollMs: Number(o.longPollMs) }),
|
|
526
|
-
});
|
|
498
|
+
target = resolveTargetConfig(loadTargetsFile(deps.explore?.targetsConfigPath), new URL(setupRefFreeUrl(o.url)).origin, targetFlagsFromFlags(o, dialogs));
|
|
527
499
|
}
|
|
528
500
|
catch (err) {
|
|
529
501
|
if (err instanceof TargetConfigError) {
|
|
@@ -688,13 +660,10 @@ export function registerExploreCommands(program, deps, buildProgram) {
|
|
|
688
660
|
return;
|
|
689
661
|
}
|
|
690
662
|
try {
|
|
691
|
-
secretFields =
|
|
692
|
-
...o.secretField.map((s) => parseSecretField(s, "value", process.env, { allowCmd: o.allowSecretCmd === true })),
|
|
693
|
-
...o.totp.map((s) => parseSecretField(s, "totp", process.env)),
|
|
694
|
-
];
|
|
663
|
+
secretFields = secretFieldsFromFlags(o);
|
|
695
664
|
}
|
|
696
665
|
catch (err) {
|
|
697
|
-
if (!(err instanceof
|
|
666
|
+
if (!(err instanceof GoalRunFlagError))
|
|
698
667
|
throw err;
|
|
699
668
|
emitExplore(fail(err.code, err.message));
|
|
700
669
|
return;
|
|
@@ -1100,11 +1069,16 @@ export function registerExploreCommands(program, deps, buildProgram) {
|
|
|
1100
1069
|
emitExplore(fail("E_EXPLORE_ASSERTION", String(err instanceof Error ? err.message : err)));
|
|
1101
1070
|
return;
|
|
1102
1071
|
}
|
|
1103
|
-
|
|
1104
|
-
|
|
1072
|
+
let successWhen;
|
|
1073
|
+
try {
|
|
1074
|
+
successWhen = successWhenFromFlags(o);
|
|
1075
|
+
}
|
|
1076
|
+
catch (err) {
|
|
1077
|
+
if (!(err instanceof GoalRunFlagError))
|
|
1078
|
+
throw err;
|
|
1079
|
+
emitExplore(fail(err.code, err.message));
|
|
1105
1080
|
return;
|
|
1106
1081
|
}
|
|
1107
|
-
const successWhen = o.successWhen === "held" || o.successWhen === "final" || o.successWhen === "each" ? o.successWhen : undefined;
|
|
1108
1082
|
const allowlist = resolveExploreAllowlist(setupRefFreeUrl(o.url), o.allow);
|
|
1109
1083
|
// Fixtures (#140/#144): the spec and every ${setup.x} reference are validated here, before any
|
|
1110
1084
|
// browser or request; the setup itself runs just before the mission (below).
|
|
@@ -1120,7 +1094,7 @@ export function registerExploreCommands(program, deps, buildProgram) {
|
|
|
1120
1094
|
...(target?.fixtures === undefined ? {} : { targetFixtures: target.fixtures }),
|
|
1121
1095
|
...(target?.personas === undefined ? {} : { personas: target.personas }),
|
|
1122
1096
|
});
|
|
1123
|
-
checkSetupRefs({ "--url": o.url, "--goal": o.goal, "--success": o.success, ...invariantSetupTexts(invariants) }, fx);
|
|
1097
|
+
checkSetupRefs({ "--url": o.url, "--goal": o.goal, "--success": o.success, ...invariantSetupTexts(invariants), ...(journeyPrefix === undefined ? {} : { "--param": Object.values(o.param) }) }, fx);
|
|
1124
1098
|
}
|
|
1125
1099
|
catch (err) {
|
|
1126
1100
|
if (!(err instanceof FixtureSpecError || err instanceof UnboundSetupRefError))
|
|
@@ -1163,6 +1137,13 @@ export function registerExploreCommands(program, deps, buildProgram) {
|
|
|
1163
1137
|
// #187: ${setup.x} in the invariants (probe paths, deniedAs.open, capture routes), origin-fixed.
|
|
1164
1138
|
if (invariants !== undefined)
|
|
1165
1139
|
runInvariants = { ...withInvariants, invariants: substituteSpecSetupRefs(invariants, b, url) };
|
|
1140
|
+
// #399: `--param name=${setup.x}` for the Journey prefix is bound to the fixture's output, and the
|
|
1141
|
+
// prefix re-resolved so its secret params (and so every redaction) hold the bound value.
|
|
1142
|
+
if (journeyPrefix !== undefined && prefixRequest !== undefined && Object.values(o.param).some((v) => v.includes("${setup."))) {
|
|
1143
|
+
const params = Object.fromEntries(Object.entries(o.param).map(([k, v]) => [k, substituteSetupRefs(v, b, { where: `--param ${k}` })]));
|
|
1144
|
+
journeyPrefix = await resolveJourneyPrefix({ ...prefixRequest, params });
|
|
1145
|
+
o.secret = [...new Set([...o.secret, ...journeyPrefix.secrets])];
|
|
1146
|
+
}
|
|
1166
1147
|
}
|
|
1167
1148
|
catch (err) {
|
|
1168
1149
|
if (!(err instanceof FixtureSetupError || err instanceof UnboundSetupRefError)) {
|
|
@@ -1209,7 +1190,7 @@ export function registerExploreCommands(program, deps, buildProgram) {
|
|
|
1209
1190
|
...runInvariants,
|
|
1210
1191
|
...triagedServerLog(serverLog, judge, o.real === true),
|
|
1211
1192
|
...(fx === undefined ? {} : { fixtures: fx }),
|
|
1212
|
-
...
|
|
1193
|
+
...(journeyPrefix === undefined ? {} : { journeyPrefix }), // #399: re-resolved when a --param was bound
|
|
1213
1194
|
});
|
|
1214
1195
|
// 0 succeeded · 1 assertion not met · 2 the run broke (inconclusive/crashed).
|
|
1215
1196
|
emitExplore(ok(await withEvidence(result)), result.exitCode);
|