@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.
- package/dist/author-journey-cli.d.ts +9 -0
- package/dist/author-journey-cli.js +18 -1
- package/dist/author-journey-cli.js.map +1 -1
- package/dist/bin.js +25673 -24216
- package/dist/build-info.generated.js +2 -2
- package/dist/catalog-analysis.js +4 -2
- package/dist/catalog-analysis.js.map +1 -1
- package/dist/catalog-bundle-api.js +1 -3
- package/dist/catalog-bundle-api.js.map +1 -1
- package/dist/catalog-bundle-findings.js +1 -3
- package/dist/catalog-bundle-findings.js.map +1 -1
- package/dist/catalog-bundle.js +1 -4
- package/dist/catalog-bundle.js.map +1 -1
- package/dist/catalog-cli.js +48 -6
- package/dist/catalog-cli.js.map +1 -1
- package/dist/catalog-review.d.ts +8 -0
- package/dist/catalog-review.js +26 -0
- package/dist/catalog-review.js.map +1 -1
- package/dist/catalog.d.ts +42 -0
- package/dist/catalog.js +67 -4
- package/dist/catalog.js.map +1 -1
- package/dist/check-execute.js +9 -0
- package/dist/check-execute.js.map +1 -1
- package/dist/check-plan.d.ts +5 -2
- package/dist/check-plan.js +9 -5
- package/dist/check-plan.js.map +1 -1
- package/dist/check-suite.js +1 -3
- package/dist/check-suite.js.map +1 -1
- package/dist/cli-output.js +1 -3
- package/dist/cli-output.js.map +1 -1
- package/dist/cli-shared.d.ts +1 -1
- package/dist/cli-shared.js +2 -0
- package/dist/cli-shared.js.map +1 -1
- package/dist/defect-evidence.js +4 -8
- package/dist/defect-evidence.js.map +1 -1
- package/dist/environments.d.ts +1 -1
- package/dist/environments.js +1 -1
- package/dist/environments.js.map +1 -1
- package/dist/exit-codes.d.ts +0 -1
- package/dist/exit-codes.js +2 -0
- package/dist/exit-codes.js.map +1 -1
- package/dist/explore-adversarial.d.ts +10 -1
- package/dist/explore-adversarial.js +3 -0
- package/dist/explore-adversarial.js.map +1 -1
- package/dist/explore-cli/adversarial-strategy.d.ts +3 -0
- package/dist/explore-cli/adversarial-strategy.js +101 -0
- package/dist/explore-cli/adversarial-strategy.js.map +1 -0
- package/dist/explore-cli/coverage-strategy.d.ts +3 -0
- package/dist/explore-cli/coverage-strategy.js +97 -0
- package/dist/explore-cli/coverage-strategy.js.map +1 -0
- package/dist/explore-cli/feature-strategy.d.ts +3 -0
- package/dist/explore-cli/feature-strategy.js +60 -0
- package/dist/explore-cli/feature-strategy.js.map +1 -0
- package/dist/explore-cli/goal-strategy.d.ts +3 -0
- package/dist/explore-cli/goal-strategy.js +185 -0
- package/dist/explore-cli/goal-strategy.js.map +1 -0
- package/dist/explore-cli/options.d.ts +3 -0
- package/dist/explore-cli/options.js +157 -0
- package/dist/explore-cli/options.js.map +1 -0
- package/dist/explore-cli/run.d.ts +7 -0
- package/dist/explore-cli/run.js +702 -0
- package/dist/explore-cli/run.js.map +1 -0
- package/dist/explore-cli/scope-region.d.ts +17 -0
- package/dist/explore-cli/scope-region.js +24 -0
- package/dist/explore-cli/scope-region.js.map +1 -0
- package/dist/explore-cli/shared.d.ts +167 -0
- package/dist/explore-cli/shared.js +20 -0
- package/dist/explore-cli/shared.js.map +1 -0
- package/dist/explore-cli/usability-strategy.d.ts +3 -0
- package/dist/explore-cli/usability-strategy.js +137 -0
- package/dist/explore-cli/usability-strategy.js.map +1 -0
- package/dist/explore-cli.d.ts +2 -2
- package/dist/explore-cli.js +2 -1347
- package/dist/explore-cli.js.map +1 -1
- package/dist/explore-coverage.d.ts +10 -1
- package/dist/explore-coverage.js +3 -0
- package/dist/explore-coverage.js.map +1 -1
- package/dist/explore-feature.d.ts +6 -0
- package/dist/explore-feature.js +1 -0
- package/dist/explore-feature.js.map +1 -1
- package/dist/explore-goal.d.ts +6 -0
- package/dist/explore-goal.js +1 -0
- package/dist/explore-goal.js.map +1 -1
- package/dist/explore-specs.d.ts +3 -1
- package/dist/explore-specs.js +2 -1
- package/dist/explore-specs.js.map +1 -1
- package/dist/github-issue-filer.js +1 -4
- package/dist/github-issue-filer.js.map +1 -1
- package/dist/goal-run-flags.d.ts +0 -1
- package/dist/goal-run-flags.js.map +1 -1
- package/dist/inbox-cli.js +1 -3
- package/dist/inbox-cli.js.map +1 -1
- package/dist/init-skills.js +1 -4
- package/dist/init-skills.js.map +1 -1
- package/dist/journey-anchor-api.d.ts +10 -9
- package/dist/journey-anchor-api.js +12 -22
- package/dist/journey-anchor-api.js.map +1 -1
- package/dist/journey-anchor-cli.d.ts +1 -1
- package/dist/journey-anchor-cli.js +22 -8
- package/dist/journey-anchor-cli.js.map +1 -1
- package/dist/journey-cli/anchors.d.ts +4 -0
- package/dist/journey-cli/anchors.js +48 -0
- package/dist/journey-cli/anchors.js.map +1 -0
- package/dist/journey-cli/find.d.ts +4 -0
- package/dist/journey-cli/find.js +44 -0
- package/dist/journey-cli/find.js.map +1 -0
- package/dist/journey-cli/lint.d.ts +4 -0
- package/dist/journey-cli/lint.js +84 -0
- package/dist/journey-cli/lint.js.map +1 -0
- package/dist/journey-cli/list.d.ts +4 -0
- package/dist/journey-cli/list.js +48 -0
- package/dist/journey-cli/list.js.map +1 -0
- package/dist/journey-cli/migrate.d.ts +4 -0
- package/dist/journey-cli/migrate.js +101 -0
- package/dist/journey-cli/migrate.js.map +1 -0
- package/dist/journey-cli/promote.d.ts +4 -0
- package/dist/journey-cli/promote.js +181 -0
- package/dist/journey-cli/promote.js.map +1 -0
- package/dist/journey-cli/publish.d.ts +4 -0
- package/dist/journey-cli/publish.js +70 -0
- package/dist/journey-cli/publish.js.map +1 -0
- package/dist/journey-cli/review.d.ts +4 -0
- package/dist/journey-cli/review.js +108 -0
- package/dist/journey-cli/review.js.map +1 -0
- package/dist/journey-cli/run.d.ts +4 -0
- package/dist/journey-cli/run.js +244 -0
- package/dist/journey-cli/run.js.map +1 -0
- package/dist/journey-cli/shared.d.ts +9 -0
- package/dist/journey-cli/shared.js +12 -0
- package/dist/journey-cli/shared.js.map +1 -0
- package/dist/journey-cli/verify.d.ts +4 -0
- package/dist/journey-cli/verify.js +125 -0
- package/dist/journey-cli/verify.js.map +1 -0
- package/dist/journey-cli.d.ts +3 -2
- package/dist/journey-cli.js +25 -950
- package/dist/journey-cli.js.map +1 -1
- package/dist/journey-link-api.d.ts +40 -0
- package/dist/journey-link-api.js +90 -0
- package/dist/journey-link-api.js.map +1 -0
- package/dist/journey-link-cli.d.ts +7 -0
- package/dist/journey-link-cli.js +58 -0
- package/dist/journey-link-cli.js.map +1 -0
- package/dist/journey-link-hints.d.ts +8 -0
- package/dist/journey-link-hints.js +68 -0
- package/dist/journey-link-hints.js.map +1 -0
- package/dist/journey-lint-sarif.js +3 -0
- package/dist/journey-lint-sarif.js.map +1 -1
- package/dist/journey-network-checks.d.ts +9 -1
- package/dist/journey-network-checks.js +55 -12
- package/dist/journey-network-checks.js.map +1 -1
- package/dist/journey-prefix.d.ts +9 -1
- package/dist/journey-prefix.js +12 -0
- package/dist/journey-prefix.js.map +1 -1
- package/dist/journey-review/render.d.ts +43 -0
- package/dist/journey-review/render.js +461 -0
- package/dist/journey-review/render.js.map +1 -0
- package/dist/journey-review-api.d.ts +1 -0
- package/dist/journey-review-api.js +7 -0
- package/dist/journey-review-api.js.map +1 -1
- package/dist/journey-review-store.js +1 -0
- package/dist/journey-review-store.js.map +1 -1
- package/dist/journey-review.d.ts +6 -4
- package/dist/journey-review.js +18 -393
- package/dist/journey-review.js.map +1 -1
- package/dist/journey-verify.js +2 -1
- package/dist/journey-verify.js.map +1 -1
- package/dist/ledger-api.js +1 -4
- package/dist/ledger-api.js.map +1 -1
- package/dist/locator-health-api.js +1 -3
- package/dist/locator-health-api.js.map +1 -1
- package/dist/locator-health.d.ts +0 -1
- package/dist/locator-health.js +0 -3
- package/dist/locator-health.js.map +1 -1
- package/dist/log-classes.js +1 -3
- package/dist/log-classes.js.map +1 -1
- package/dist/log-sources.js +17 -1
- package/dist/log-sources.js.map +1 -1
- package/dist/mcp-api.js +16 -821
- package/dist/mcp-api.js.map +1 -1
- package/dist/mcp-cli-tools.js +48 -7
- package/dist/mcp-cli-tools.js.map +1 -1
- package/dist/mcp-tools/cli.d.ts +3 -0
- package/dist/mcp-tools/cli.js +20 -0
- package/dist/mcp-tools/cli.js.map +1 -0
- package/dist/mcp-tools/explore.d.ts +3 -0
- package/dist/mcp-tools/explore.js +141 -0
- package/dist/mcp-tools/explore.js.map +1 -0
- package/dist/mcp-tools/findings.d.ts +3 -0
- package/dist/mcp-tools/findings.js +277 -0
- package/dist/mcp-tools/findings.js.map +1 -0
- package/dist/mcp-tools/inbox.d.ts +3 -0
- package/dist/mcp-tools/inbox.js +106 -0
- package/dist/mcp-tools/inbox.js.map +1 -0
- package/dist/mcp-tools/journey.d.ts +3 -0
- package/dist/mcp-tools/journey.js +260 -0
- package/dist/mcp-tools/journey.js.map +1 -0
- package/dist/mcp-tools/shared.d.ts +39 -0
- package/dist/mcp-tools/shared.js +69 -0
- package/dist/mcp-tools/shared.js.map +1 -0
- package/dist/mission-fixtures.js +1 -8
- package/dist/mission-fixtures.js.map +1 -1
- package/dist/mission-queue-cli.js +1 -3
- package/dist/mission-queue-cli.js.map +1 -1
- package/dist/multi-run.js +1 -3
- package/dist/multi-run.js.map +1 -1
- package/dist/pre-approval.js +4 -4
- package/dist/pre-approval.js.map +1 -1
- package/dist/readiness.js +2 -2
- package/dist/readiness.js.map +1 -1
- package/dist/report-api.js +1 -4
- package/dist/report-api.js.map +1 -1
- package/dist/resource-preflight.js.map +1 -1
- package/dist/run-metadata.js +1 -3
- package/dist/run-metadata.js.map +1 -1
- package/dist/run-tags-cli.d.ts +0 -2
- package/dist/run-tags-cli.js +0 -4
- package/dist/run-tags-cli.js.map +1 -1
- package/dist/session-check.js +1 -3
- package/dist/session-check.js.map +1 -1
- package/dist/signal-triage.js +6 -2
- package/dist/signal-triage.js.map +1 -1
- package/dist/suite-explore-options.d.ts +8 -0
- package/dist/suite-explore-options.js +4 -0
- package/dist/suite-explore-options.js.map +1 -1
- package/dist/sweep-api.d.ts +9 -0
- package/dist/sweep-api.js +70 -4
- package/dist/sweep-api.js.map +1 -1
- package/dist/sweep-cli.d.ts +2 -0
- package/dist/sweep-cli.js +22 -5
- package/dist/sweep-cli.js.map +1 -1
- package/dist/transcript-file.d.ts +0 -3
- package/dist/transcript-file.js +0 -9
- package/dist/transcript-file.js.map +1 -1
- package/dist/ui-api.js +1 -4
- package/dist/ui-api.js.map +1 -1
- package/dist/ux-usability/analysis.d.ts +14 -0
- package/dist/ux-usability/analysis.js +106 -0
- package/dist/ux-usability/analysis.js.map +1 -0
- package/dist/ux-usability/done-adjudication.d.ts +15 -0
- package/dist/ux-usability/done-adjudication.js +56 -0
- package/dist/ux-usability/done-adjudication.js.map +1 -0
- package/dist/ux-usability/hang.d.ts +5 -0
- package/dist/ux-usability/hang.js +41 -0
- package/dist/ux-usability/hang.js.map +1 -0
- package/dist/ux-usability/loop.d.ts +15 -0
- package/dist/ux-usability/loop.js +104 -0
- package/dist/ux-usability/loop.js.map +1 -0
- package/dist/ux-usability/observe.d.ts +7 -0
- package/dist/ux-usability/observe.js +89 -0
- package/dist/ux-usability/observe.js.map +1 -0
- package/dist/ux-usability/result.d.ts +11 -0
- package/dist/ux-usability/result.js +113 -0
- package/dist/ux-usability/result.js.map +1 -0
- package/dist/ux-usability/setup.d.ts +76 -0
- package/dist/ux-usability/setup.js +204 -0
- package/dist/ux-usability/setup.js.map +1 -0
- package/dist/ux-usability/teardown.d.ts +3 -0
- package/dist/ux-usability/teardown.js +13 -0
- package/dist/ux-usability/teardown.js.map +1 -0
- package/dist/ux-usability.d.ts +20 -17
- package/dist/ux-usability.js +15 -589
- package/dist/ux-usability.js.map +1 -1
- package/dist/verify-fix-api.js +1 -4
- package/dist/verify-fix-api.js.map +1 -1
- package/package.json +5 -6
- package/skills/jevitate-explore/SKILL.md +9 -1
- package/skills/jevitate-getting-started/SKILL.md +1 -0
- package/skills/jevitate-promotion-ready/SKILL.md +76 -0
- package/skills/jevitate-record/SKILL.md +3 -0
- package/skills/jevitate-run-journey/SKILL.md +3 -0
- package/skills/jevitate-test-campaign/SKILL.md +5 -3
package/dist/journey-cli.js
CHANGED
|
@@ -1,962 +1,37 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { MAX_STEP_RESPONSE_TIMEOUT_MS } from "./journey-network-checks.js";
|
|
14
|
-
import { ok, fail } from "./envelope.js";
|
|
15
|
-
import { SiteGateRefusedError } from "@jevitate/runtime";
|
|
16
|
-
import { runJourneyProgrammatically, promoteJourney, lintJourneyById, WeakJourneyError, UnknownJourneyError, JourneyRequiresAuthError, StaleReviewError } from "./journey-api.js";
|
|
17
|
-
import { reviewRenderOptions } from "./review-render-options.js";
|
|
18
|
-
import { ReviewSheetError, renderReviewMarkdown, renderReviewText, reviewSheetHash } from "./journey-review.js";
|
|
19
|
-
import { reviewJourneyById } from "./journey-review-api.js";
|
|
20
|
-
import { listStaleJourneys, renderStaleJourneys } from "./journey-stale-api.js";
|
|
21
|
-
import { migrateStepIds, renderMigrateStepIds } from "./journey-migrate-api.js";
|
|
22
|
-
import { migrateLabels, renderMigrateLabels } from "./journey-labels-migrate-api.js";
|
|
23
|
-
import { ReviewSidecarError } from "./journey-review-store.js";
|
|
24
|
-
import { JourneyProposalArgsError } from "./journey-api.js";
|
|
25
|
-
import { JourneyProposalInvalidError, JourneyProposalNotFoundError, JourneyProposalProofError, JourneyProposalStaleError, isProposalId } from "./journey-proposal-store.js";
|
|
26
|
-
import { rejectionProvenance } from "./approval-provenance.js";
|
|
27
|
-
import { UnvettedLinksError, resolveCatalogDir } from "./catalog-api.js";
|
|
28
|
-
import { CatalogInputError } from "./catalog.js";
|
|
29
|
-
import { ApprovalFindingsError } from "./pre-approval.js";
|
|
30
|
-
import { approvalRefusal, checkNonInteractiveReason, describeProvenance, makeApprovalConfirm } from "./approval-provenance.js";
|
|
31
|
-
import { TargetConfigError } from "./target-config.js";
|
|
32
|
-
import { ExtensionMismatchError } from "./browser-run-options.js";
|
|
33
|
-
import { parseScreenshotsArg } from "./run-screenshots.js";
|
|
34
|
-
import { withSiteGate } from "./site-gate-cli.js";
|
|
1
|
+
import { registerJourneyList } from "./journey-cli/list.js";
|
|
2
|
+
import { registerJourneyFind } from "./journey-cli/find.js";
|
|
3
|
+
import { registerJourneyRun } from "./journey-cli/run.js";
|
|
4
|
+
import { registerJourneyPromote } from "./journey-cli/promote.js";
|
|
5
|
+
import { registerJourneyReview } from "./journey-cli/review.js";
|
|
6
|
+
import { registerJourneyMigrate } from "./journey-cli/migrate.js";
|
|
7
|
+
import { registerJourneyLint } from "./journey-cli/lint.js";
|
|
8
|
+
import { registerJourneyVerify } from "./journey-cli/verify.js";
|
|
9
|
+
import { registerJourneyAnchors } from "./journey-cli/anchors.js";
|
|
10
|
+
import { registerJourneyPublish } from "./journey-cli/publish.js";
|
|
11
|
+
import { registerJourneyAnchorCommands } from "./journey-anchor-cli.js";
|
|
12
|
+
import { registerJourneyLinkCommand } from "./journey-link-cli.js";
|
|
35
13
|
import { registerJourneyAnnotateCommand } from "./journey-annotate-cli.js";
|
|
36
14
|
import { registerJourneyDemoCommand } from "./journey-demo-cli.js";
|
|
37
|
-
|
|
38
|
-
import { fixJourneyAnchors } from "./journey-anchor-api.js";
|
|
39
|
-
import { journeyIntentCoverage } from "./journey-annotate-api.js";
|
|
40
|
-
import { buildMissionFixtures, checkSetupRefs, withFixtureFlags } from "./fixture-cli.js";
|
|
41
|
-
import { FixtureSetupError, FixtureSpecError, UnboundSetupRefError } from "./mission-fixtures.js";
|
|
42
|
-
import { withEngine, currentEngineInfo } from "./engine.js";
|
|
43
|
-
import { renderJourneyLintSarif } from "./journey-lint-sarif.js";
|
|
44
|
-
import { verifyAssertionLine, verifyExitCode, verifyJourneyMutations } from "./journey-verify.js";
|
|
45
|
-
import { publishJourneyToSource, realGhPort, NotPromotedError, NoDeclaredOriginsError } from "./source-api.js";
|
|
46
|
-
import { UnknownSourceError, EmbeddedSecretError, UndeclaredOriginError } from "@jevitate/sources";
|
|
47
|
-
import { environmentFromFlags, isEnvironmentError, withEnvironmentFlags } from "./environments.js";
|
|
48
|
-
import { resolveDbPath, resolveJourneysDir, resolveSourceApiDeps, withBrowserLaunchFlags, browserRunFromFlags, withDemoFlags, withScreenshotsFlag, withEmulationFlags, emulationFromFlags, collectParam, emitJson, environmentSeams, writeRawResult, GatewaySelectionError, buildExploreGateways, } from "./cli-shared.js";
|
|
49
|
-
/** #401: one human line per lint finding — level, step (when it has one), rule, message. */
|
|
50
|
-
/** #432: the targets.json a review sheet reads its site's safety config from (the CLI's own seam). */
|
|
51
|
-
function targetsOpts(deps) {
|
|
52
|
-
const file = deps.explore?.targetsConfigPath;
|
|
53
|
-
return file === undefined ? {} : { targetsFile: file };
|
|
54
|
-
}
|
|
55
|
-
function lintFindingLine(finding) {
|
|
56
|
-
const message = finding.message.replace(/^step \d+: /, "");
|
|
57
|
-
return `${finding.level} ${finding.step === undefined ? "" : `step ${finding.step} `}${finding.rule} ${message}${finding.fix === undefined ? "" : ` — fix: ${finding.fix}`}`;
|
|
58
|
-
}
|
|
59
|
-
/** Registers `jevitate journey`: `list|find|run|promote|lint|verify|anchors|annotate|demo|publish`. */
|
|
60
|
-
const FULL_FLAG_HELP = "#496: show every detail section: catalog analysis, the full lint list, locator health, provenance, the full change diff";
|
|
15
|
+
/** Registers `jevitate journey`: `list|find|run|promote|lint|verify|anchors|anchor|link|annotate|demo|publish`. */
|
|
61
16
|
export function registerJourneyCommands(program, deps) {
|
|
62
17
|
const journey = program.command("journey").description("manage and run promoted Journeys (regression-test replays)");
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
journey
|
|
72
|
-
.command("list")
|
|
73
|
-
.option("--dir <path>", "journeys directory (default: the repo's .jevitate/journeys; outside a repo ~/.jevitate/journeys)")
|
|
74
|
-
.option("--json", "emit a JSON envelope")
|
|
75
|
-
.action(async function () {
|
|
76
|
-
const { dir, json } = this.opts();
|
|
77
|
-
try {
|
|
78
|
-
const store = new FsJourneyStore(resolveJourneysDir(deps, dir));
|
|
79
|
-
const metas = await store.list();
|
|
80
|
-
const envelope = ok(metas);
|
|
81
|
-
if (json) {
|
|
82
|
-
emitJson(program, envelope);
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
85
|
-
const out = program.configureOutput().writeOut;
|
|
86
|
-
if (metas.length === 0) {
|
|
87
|
-
out?.("no journeys yet — record one with `jevitate record` (see jevitate record --help)\n");
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
for (const m of metas) {
|
|
91
|
-
// #437: how a promoted Journey's approval was made ("approved non-interactively (likely an agent: CLAUDECODE)").
|
|
92
|
-
const how = m.promoted && m.approval !== undefined ? ` — ${describeProvenance(m.approval.provenance)}` : "";
|
|
93
|
-
out?.(`${m.id}\t${m.name}${m.promoted ? "" : " (unpromoted)"}${how}\n`);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
process.exitCode = 0;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
catch (err) {
|
|
100
|
-
emitJson(program, fail("E_JOURNEY_LIST", String(err)));
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
// RULING 5: uses `JourneyRegistry.find` (from `@jevitate/journey`) directly —
|
|
104
|
-
// NEVER `@jevitate/mcp-facade`'s `findCapabilities` — Slice 1 forbids the CLI
|
|
105
|
-
// depending on `@jevitate/mcp-facade`. `JourneyRegistry.find` is already
|
|
106
|
-
// promoted-only, so this is the same promoted-only view without the
|
|
107
|
-
// forbidden dependency.
|
|
108
|
-
journey
|
|
109
|
-
.command("find <query>")
|
|
110
|
-
.option("--dir <path>", "journeys directory (default: the repo's .jevitate/journeys; outside a repo ~/.jevitate/journeys)")
|
|
111
|
-
.option("--json", "emit a JSON envelope")
|
|
112
|
-
.action(async function (query) {
|
|
113
|
-
const { dir, json } = this.opts();
|
|
114
|
-
try {
|
|
115
|
-
const store = new FsJourneyStore(resolveJourneysDir(deps, dir));
|
|
116
|
-
const registry = new JourneyRegistry(store);
|
|
117
|
-
const metas = await registry.find(query);
|
|
118
|
-
const capabilities = metas.map((m) => ({
|
|
119
|
-
id: m.id,
|
|
120
|
-
name: m.name,
|
|
121
|
-
description: m.description,
|
|
122
|
-
params: m.params,
|
|
123
|
-
}));
|
|
124
|
-
const envelope = ok(capabilities);
|
|
125
|
-
if (json) {
|
|
126
|
-
emitJson(program, envelope);
|
|
127
|
-
}
|
|
128
|
-
else {
|
|
129
|
-
const out = program.configureOutput().writeOut;
|
|
130
|
-
for (const c of capabilities) {
|
|
131
|
-
out?.(`${c.id}\t${c.name}\tparams=[${c.params.join(", ")}]\n`);
|
|
132
|
-
}
|
|
133
|
-
process.exitCode = 0;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
catch (err) {
|
|
137
|
-
emitJson(program, fail("E_JOURNEY_FIND", String(err)));
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
withScreenshotsFlag(withEnvironmentFlags(withDemoFlags(withBrowserLaunchFlags(withEmulationFlags(withFixtureFlags(journey.command("run <id>")))), { recordVideo: true })))
|
|
141
|
-
.option("--dir <path>", "journeys directory (default: the repo's .jevitate/journeys; outside a repo ~/.jevitate/journeys)")
|
|
142
|
-
.option("--param <kv>", "param as key=value (repeatable)", collectParam, {})
|
|
143
|
-
.option("--storage-state <file>", "Playwright storageState JSON to start the session authenticated (#118: required when the journey declares metadata.requiresAuth); must exist")
|
|
144
|
-
// Ticket #7 (additive): opt a run into scoped self-healing. Default
|
|
145
|
-
// `fail-closed` preserves Slice 1 behavior exactly (no healer wired). A
|
|
146
|
-
// write/irreversible step NEVER auto-heals in any mode (enforced by the
|
|
147
|
-
// runtime's write floor). `hybrid`/`full` need an AI gateway, selected
|
|
148
|
-
// with --real/--fake-ai (mirrors `explore`); requesting a heal mode
|
|
149
|
-
// without one fails CLOSED, never a silent unhealed run.
|
|
150
|
-
.option("--self-heal <mode>", "self-heal policy mode: fail-closed | hybrid | full (#453: hybrid/full need --changes and/or --change-note; a heal is proposed for review, exit 5 — never a pass)", "fail-closed")
|
|
151
|
-
.option("--changes <range>", "#453: the git range that explains a break (e.g. HEAD~1..HEAD, main...HEAD; read-only, in the journeys dir's repo) — needs --self-heal hybrid|full")
|
|
152
|
-
.option("--change-note <text>", "#453: a change note that explains a break (e.g. 'renamed \"Create New\" to \"Create\"'; repeatable) — needs --self-heal hybrid|full", (v, prev) => [...prev, v], [])
|
|
153
|
-
.option("--heal-max-attempts <n>", "#453: candidates tried per broken step (default 2)", positiveIntArg)
|
|
154
|
-
.option("--heal-max-model-calls <n>", "#453: model calls per broken step (default 6)", positiveIntArg)
|
|
155
|
-
.option("--heal-max-ms <ms>", "#453: healing time per broken step in ms (default 60000)", positiveIntArg)
|
|
156
|
-
.option("--heal-max-run-attempts <n>", "#453: candidates tried in the whole run (default 4)", positiveIntArg)
|
|
157
|
-
.option("--heal-max-run-ms <ms>", "#453: healing time in the whole run in ms (default 180000)", positiveIntArg)
|
|
158
|
-
.option("--real", "use live Jev + OpenRouter gateways for self-heal (requires keys)", false)
|
|
159
|
-
.option("--fake-ai", "use deterministic fake gateways for self-heal (pipeline smoke only)", false)
|
|
160
|
-
.option("--jev-provider <provider>", JEV_PROVIDER_FLAG_HELP, jevProviderArg)
|
|
161
|
-
.option("--step-response-timeout <ms>", "#486: how long a step's responseStatus check waits for a matching request that was sent but has no response yet, e.g. a cold start after a deploy (default 15000). A late 2xx is an advisory slow-step finding (slowSteps), not a quarantine; a non-2xx, or no response within the bound, fails. A step's own responseTimeoutMs overrides it; at most 300000", intArg({ min: 1, max: MAX_STEP_RESPONSE_TIMEOUT_MS }))
|
|
162
|
-
.option("--action-deltas", "opt-in (#303): record what each replayed step changed on the page (redacted, a code verdict per step) and compare it with the delta its Recording stored — returned as actionDeltas")
|
|
163
|
-
.option("--json", "emit a JSON envelope")
|
|
164
|
-
.option(TAG_FLAG, TAG_HELP, collectTag, [])
|
|
165
|
-
.action(taggedAction(program, "journey run", async function (id) {
|
|
166
|
-
const { env: envName, baseUrl } = this.opts();
|
|
167
|
-
// #247: --env/--base-url choose where the Journey runs (unknown env / bad file → 64, nothing opened).
|
|
168
|
-
let environment;
|
|
169
|
-
try {
|
|
170
|
-
environment = environmentFromFlags({ ...(envName === undefined ? {} : { env: envName }), ...(baseUrl === undefined ? {} : { baseUrl }) }, environmentSeams(deps));
|
|
171
|
-
}
|
|
172
|
-
catch (err) {
|
|
173
|
-
if (!isEnvironmentError(err))
|
|
174
|
-
throw err;
|
|
175
|
-
emitJson(program, fail(err.code, err.message));
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
const ownFixtureFlags = this.opts();
|
|
179
|
-
// The environment's fixtures/hooks apply when the flags name none (hooks still need --allow-shell-hooks).
|
|
180
|
-
const fixtureFlags = {
|
|
181
|
-
...ownFixtureFlags,
|
|
182
|
-
...(ownFixtureFlags.before === undefined && environment?.hooks?.before !== undefined ? { before: environment.hooks.before } : {}),
|
|
183
|
-
...(ownFixtureFlags.after === undefined && environment?.hooks?.after !== undefined ? { after: environment.hooks.after } : {}),
|
|
184
|
-
};
|
|
185
|
-
const { dir, param, storageState: storageStateFlag, selfHeal, real, fakeAi, jevProvider, json, screenshots: _screenshots, actionDeltas, stepResponseTimeout, changes, changeNote, healMaxAttempts, healMaxModelCalls, healMaxMs, healMaxRunAttempts, healMaxRunMs, ...emulationFlags } = this.opts();
|
|
186
|
-
// --storage-state wins; else the environment's own session (~/.jevitate/targets.json[<origin>]).
|
|
187
|
-
const storageState = storageStateFlag ?? environment?.storageState;
|
|
188
|
-
if (storageState !== undefined && !existsSync(storageState)) {
|
|
189
|
-
emitJson(program, fail("E_JOURNEY_RUN_ARGS", `storage state not found: ${storageState}`));
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
// #245: demo mode, resolved (a headed run without a display refused) before any browser opens.
|
|
193
|
-
let browser;
|
|
194
|
-
try {
|
|
195
|
-
browser = browserRunFromFlags(this.opts(), deps.explore?.env ?? process.env);
|
|
196
|
-
}
|
|
197
|
-
catch (err) {
|
|
198
|
-
emitJson(program, fail("E_JOURNEY_RUN_ARGS", err instanceof Error ? err.message : String(err)));
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
// #251: an unusable --screenshots value is a usage error (64), before any browser opens.
|
|
202
|
-
let screenshots;
|
|
203
|
-
try {
|
|
204
|
-
screenshots = parseScreenshotsArg(this.opts().screenshots);
|
|
205
|
-
}
|
|
206
|
-
catch (err) {
|
|
207
|
-
emitJson(program, fail("E_JOURNEY_RUN_ARGS", err instanceof Error ? err.message : String(err)));
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
let journeyRunEmulation;
|
|
211
|
-
try {
|
|
212
|
-
journeyRunEmulation = emulationFromFlags(emulationFlags);
|
|
213
|
-
}
|
|
214
|
-
catch (err) {
|
|
215
|
-
emitJson(program, fail("E_JOURNEY_RUN_ARGS", err instanceof Error ? err.message : String(err)));
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
if (selfHeal !== "fail-closed" && selfHeal !== "hybrid" && selfHeal !== "full") {
|
|
219
|
-
emitJson(program, fail("E_SELF_HEAL_MODE", `--self-heal must be one of fail-closed | hybrid | full (got '${selfHeal}')`));
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
const selfHealMode = selfHeal;
|
|
223
|
-
// #453 Q1: a heal needs a change context, and the change/budget flags need a heal — refused
|
|
224
|
-
// (64) before any Journey lookup, git read, model gateway or browser.
|
|
225
|
-
const healRequest = {
|
|
226
|
-
selfHeal: selfHealMode,
|
|
227
|
-
...(changes === undefined ? {} : { changes }),
|
|
228
|
-
changeNotes: changeNote,
|
|
229
|
-
...(healMaxAttempts === undefined ? {} : { maxAttempts: healMaxAttempts }),
|
|
230
|
-
...(healMaxModelCalls === undefined ? {} : { maxModelCalls: healMaxModelCalls }),
|
|
231
|
-
...(healMaxMs === undefined ? {} : { maxMs: healMaxMs }),
|
|
232
|
-
...(healMaxRunAttempts === undefined ? {} : { maxRunAttempts: healMaxRunAttempts }),
|
|
233
|
-
...(healMaxRunMs === undefined ? {} : { maxRunMs: healMaxRunMs }),
|
|
234
|
-
};
|
|
235
|
-
let healScope;
|
|
236
|
-
try {
|
|
237
|
-
validateJourneyHeal(healRequest, CLI_HEAL_NAMES);
|
|
238
|
-
if (selfHealMode !== "fail-closed")
|
|
239
|
-
healScope = await readJourneyChangeScope(healRequest, resolveJourneysDir(deps, dir));
|
|
240
|
-
}
|
|
241
|
-
catch (err) {
|
|
242
|
-
if (err instanceof JourneyHealArgsError || err instanceof ChangesArgsError || err instanceof ChangesInputError) {
|
|
243
|
-
emitJson(program, fail(err.code, err.message));
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
throw err;
|
|
247
|
-
}
|
|
248
|
-
// When a heal mode is requested, build the SelfHealer HERE (this action
|
|
249
|
-
// owns `deps` + the credential preflight); a missing/unselected gateway
|
|
250
|
-
// fails CLOSED before any browser launch, rather than silently running
|
|
251
|
-
// with no healer. fail-closed needs no gateway (identical to today).
|
|
252
|
-
let selfHealer;
|
|
253
|
-
let policy = safeRunPolicy();
|
|
254
|
-
// #163: a self-healing run makes model calls — their usage (and full cost) lands on its result.
|
|
255
|
-
let healUsage;
|
|
256
|
-
if (selfHealMode !== "fail-closed") {
|
|
257
|
-
// #453: the healer is evidence-only — its model is an advisory ranker; judgment never decides a heal.
|
|
258
|
-
let gen;
|
|
259
|
-
try {
|
|
260
|
-
({ gen, usage: healUsage } = await buildExploreGateways(deps, { real: real ?? false, fakeAi: fakeAi ?? false, jevProvider }));
|
|
261
|
-
}
|
|
262
|
-
catch (err) {
|
|
263
|
-
if (err instanceof MissingCredentialError || err instanceof GatewaySelectionError) {
|
|
264
|
-
emitJson(program, fail("E_AI_SETUP_REQUIRED", err.message));
|
|
265
|
-
}
|
|
266
|
-
else {
|
|
267
|
-
emitJson(program, fail("E_JOURNEY_RUN", String(err instanceof Error ? err.message : err)));
|
|
268
|
-
}
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
selfHealer = makeEvidenceSelfHealer(gen, healUsage === undefined ? {} : { usage: healUsage });
|
|
272
|
-
policy = { ...policy, selfHeal: { mode: selfHealMode, budget: journeyHealBudget(healRequest) } };
|
|
273
|
-
}
|
|
274
|
-
try {
|
|
275
|
-
// `runJourneyProgrammatically` validates params UP FRONT (before any
|
|
276
|
-
// browser launch). The default policy stays `safeRunPolicy()`
|
|
277
|
-
// (fail-closed secret mode) — only `selfHeal.mode` is threaded from
|
|
278
|
-
// the flag; a `--secret-mode` override is a later slice's concern.
|
|
279
|
-
const result = await withSiteGate(resolveDbPath(deps), (siteGate) => runJourneyProgrammatically({
|
|
280
|
-
...(siteGate === undefined ? {} : { siteGate }),
|
|
281
|
-
dir: resolveJourneysDir(deps, dir),
|
|
282
|
-
id,
|
|
283
|
-
params: param,
|
|
284
|
-
policy,
|
|
285
|
-
selfHealer,
|
|
286
|
-
...(healScope === undefined ? {} : { heal: { scope: healScope } }),
|
|
287
|
-
browserPortFactory: deps.explore?.browserPortFactory,
|
|
288
|
-
...(browser === undefined ? {} : { browser }),
|
|
289
|
-
...(journeyRunEmulation === undefined ? {} : { emulation: journeyRunEmulation }),
|
|
290
|
-
...(screenshots === undefined ? {} : { screenshots }),
|
|
291
|
-
...(storageState !== undefined ? { storageState } : {}),
|
|
292
|
-
...(actionDeltas === true ? { actionDeltas: true } : {}),
|
|
293
|
-
...(stepResponseTimeout === undefined ? {} : { stepResponseTimeoutMs: stepResponseTimeout }),
|
|
294
|
-
...(environment === undefined ? {} : { environment }),
|
|
295
|
-
// #140: fixture HTTP steps may only reach the journey's own site (authenticated from --storage-state);
|
|
296
|
-
// #247: under an environment, its allowed origins.
|
|
297
|
-
fixtures: (site) => {
|
|
298
|
-
const fx = buildMissionFixtures(fixtureFlags, {
|
|
299
|
-
allowlist: environment === undefined ? [site] : environment.allowedOrigins,
|
|
300
|
-
baseUrl: site,
|
|
301
|
-
...(storageState !== undefined ? { storageState } : {}),
|
|
302
|
-
...(environment?.fixtures === undefined ? {} : { targetFixtures: environment.fixtures }),
|
|
303
|
-
});
|
|
304
|
-
checkSetupRefs({ "--param": Object.values(param) }, fx);
|
|
305
|
-
return fx;
|
|
306
|
-
},
|
|
307
|
-
})).then((r) => withEngine(healUsage === undefined ? r : { ...r, usage: healUsage.snapshot() }));
|
|
308
|
-
// #246 (informational, never failing): how many steps say why — `journey annotate` drafts the rest.
|
|
309
|
-
const intent = await journeyIntentCoverage(resolveJourneysDir(deps, dir), id);
|
|
310
|
-
if (intent !== undefined && intent.withoutObjective > 0 && !json) {
|
|
311
|
-
program.configureOutput().writeErr?.(`note: ${intent.withoutObjective} of ${intent.steps} step(s) have no objective — draft them with \`jevitate journey annotate ${id}\`\n`);
|
|
312
|
-
}
|
|
313
|
-
const envelope = ok(intent === undefined ? result : { ...result, intent });
|
|
314
|
-
if (json) {
|
|
315
|
-
emitJson(program, envelope);
|
|
316
|
-
// #453: ok → 0; healed-pending-review → 5 (a proposed revision, never a pass); else 1.
|
|
317
|
-
const code = journeyExitCode(result.outcome);
|
|
318
|
-
if (code !== 0)
|
|
319
|
-
process.exitCode = code;
|
|
320
|
-
}
|
|
321
|
-
else {
|
|
322
|
-
writeRawResult(program, envelope.data);
|
|
323
|
-
// #453: a self-heal run also says, in words, what happened and what a person does next.
|
|
324
|
-
if (selfHealMode !== "fail-closed")
|
|
325
|
-
program.configureOutput().writeErr?.(journeyRunSummary(id, result));
|
|
326
|
-
process.exitCode = journeyExitCode(result.outcome);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
catch (err) {
|
|
330
|
-
if (err instanceof SiteGateRefusedError || isEnvironmentError(err)) {
|
|
331
|
-
emitJson(program, fail(err.code, err.message));
|
|
332
|
-
}
|
|
333
|
-
else if (err instanceof UnknownJourneyError) {
|
|
334
|
-
emitJson(program, fail("E_UNKNOWN_JOURNEY", String(err.message)));
|
|
335
|
-
}
|
|
336
|
-
else if (err instanceof JourneyRequiresAuthError) {
|
|
337
|
-
emitJson(program, fail("E_JOURNEY_REQUIRES_AUTH", String(err.message)));
|
|
338
|
-
}
|
|
339
|
-
else if (err instanceof ExtensionMismatchError) {
|
|
340
|
-
emitJson(program, fail(err.code, err.message));
|
|
341
|
-
}
|
|
342
|
-
else if (err instanceof FixtureSetupError) {
|
|
343
|
-
// Never run on unknown state: inconclusive, a configuration error (exit 2).
|
|
344
|
-
emitJson(program, ok(withEngine({ outcome: "inconclusive", reason: err.message, failure: { kind: "configuration", message: err.message }, attribution: "configuration" })));
|
|
345
|
-
process.exitCode = 2;
|
|
346
|
-
}
|
|
347
|
-
else if (err instanceof FixtureSpecError || err instanceof UnboundSetupRefError) {
|
|
348
|
-
emitJson(program, fail(err.code, err.message));
|
|
349
|
-
}
|
|
350
|
-
else if (err instanceof ParamValidationError) {
|
|
351
|
-
emitJson(program, fail("E_INVALID_PARAMS", String(err.message)));
|
|
352
|
-
}
|
|
353
|
-
else {
|
|
354
|
-
emitJson(program, fail("E_JOURNEY_RUN", String(err)));
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
}));
|
|
358
|
-
// #124 — promote a local Journey so it becomes discoverable/runnable (journey
|
|
359
|
-
// find / MCP find_capabilities / run_journey), mirroring `mission target
|
|
360
|
-
// promote`'s human-approval-gate semantics: promoting is a deliberate,
|
|
361
|
-
// explicit act, never automatic (an authored Journey's `metadata.promoted`
|
|
362
|
-
// always starts `false` — see `explore-author-journey`/`jevitate record`).
|
|
363
|
-
journey
|
|
364
|
-
.command("promote <id>")
|
|
365
|
-
.description("promote a local Journey (human-approval gate) so it becomes discoverable/runnable; shows its review sheet first and records the approval")
|
|
366
|
-
.option("--dir <path>", "journeys directory (default: the repo's .jevitate/journeys; outside a repo ~/.jevitate/journeys)")
|
|
367
|
-
.option("--accept-weak <reason>", "#401: promote a Journey whose assertions cannot prove its outcome, recording the reason")
|
|
368
|
-
.option("--full", FULL_FLAG_HELP)
|
|
369
|
-
.option("--reviewed-hash <hash>", "#432: the content hash of the review sheet you read; refused (E_JOURNEY_REVIEW_STALE) if the Journey changed since")
|
|
370
|
-
.option("--review-sheet <file>", "#432: the review sheet file you read (journey review --out); its content hash binds the approval like --reviewed-hash")
|
|
371
|
-
.option("--accept-unvetted <reason>", "#433: promote although its linked job/persona is not approved (unknown, draft or stale), recording the reason in approval.waivers")
|
|
372
|
-
.option("--accept-findings <reason>", "#433: promote although pre-approval findings need an acknowledgment, recording the reason in approval.acceptedFindings")
|
|
373
|
-
.option("--real", `${REAL_JEV_FLAG_HELP}; a conflicting/duplicate pair classification at or above the documented threshold then needs --accept-findings`)
|
|
374
|
-
.option("--jev-provider <provider>", JEV_PROVIDER_FLAG_HELP, jevProviderArg)
|
|
375
|
-
.option("--non-interactive-approval <reason>", "#437: approve without a terminal confirmation (a scripted setup), recorded as channel non-interactive (ci under a CI marker) with the reason — never as a person's; check --require-approvals fails it. A coding agent never uses this: it hands the approval to a person")
|
|
376
|
-
.option("--proposal <pid>", "#453: accept this pending self-heal proposal (journey review shows it): the Journey is replaced by the proposed revision through every gate, bound to its proposedHash")
|
|
377
|
-
.option("--reject-proposal <pid>", "#453: reject this pending self-heal proposal (needs --reason); the stored Journey is untouched")
|
|
378
|
-
.option("--reason <text>", "#453: why the proposal is rejected (recorded with the rejection)")
|
|
379
|
-
.option("--json", "emit a JSON envelope")
|
|
380
|
-
.action(async function (id) {
|
|
381
|
-
const { dir, json, full, acceptWeak, reviewedHash: hashFlag, reviewSheet, acceptUnvetted, acceptFindings, real, jevProvider, nonInteractiveApproval, proposal, rejectProposal, reason } = this.opts();
|
|
382
|
-
try {
|
|
383
|
-
checkNonInteractiveReason(nonInteractiveApproval);
|
|
384
|
-
const journeysDir = resolveJourneysDir(deps, dir);
|
|
385
|
-
// #453: the proposal flags, validated before anything is read (an id is 12 hex characters — never a path).
|
|
386
|
-
for (const [flag, value] of [["--proposal", proposal], ["--reject-proposal", rejectProposal]]) {
|
|
387
|
-
if (value !== undefined && !isProposalId(value)) {
|
|
388
|
-
emitJson(program, fail("E_JOURNEY_PROPOSAL_ARGS", `${flag} needs a proposal id (12 hex characters, shown by \`journey review\`), not '${value}'`));
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
if (proposal !== undefined && rejectProposal !== undefined) {
|
|
393
|
-
emitJson(program, fail("E_JOURNEY_PROPOSAL_ARGS", "--proposal and --reject-proposal are exclusive: accept or reject, not both"));
|
|
394
|
-
return;
|
|
395
|
-
}
|
|
396
|
-
if (rejectProposal !== undefined && (reason ?? "").trim() === "") {
|
|
397
|
-
emitJson(program, fail("E_JOURNEY_PROPOSAL_ARGS", "--reject-proposal needs --reason <text> (it is recorded with the rejection)"));
|
|
398
|
-
return;
|
|
399
|
-
}
|
|
400
|
-
if (reason !== undefined && rejectProposal === undefined) {
|
|
401
|
-
emitJson(program, fail("E_JOURNEY_PROPOSAL_ARGS", "--reason belongs to --reject-proposal"));
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
if (proposal !== undefined && reviewSheet !== undefined) {
|
|
405
|
-
emitJson(program, fail("E_JOURNEY_PROPOSAL_ARGS", "--review-sheet binds the stored Journey's hash; with --proposal bind with --reviewed-hash <proposedHash>"));
|
|
406
|
-
return;
|
|
407
|
-
}
|
|
408
|
-
if (rejectProposal !== undefined) {
|
|
409
|
-
const rejected = await promoteJourney(journeysDir, id, {
|
|
410
|
-
rejectProposal,
|
|
411
|
-
reason: reason ?? "",
|
|
412
|
-
rejectProvenance: rejectionProvenance(deps.approval, reason ?? ""),
|
|
413
|
-
});
|
|
414
|
-
if (json)
|
|
415
|
-
emitJson(program, ok({ ...rejected.metadata, rejectedProposal: rejectProposal }));
|
|
416
|
-
else {
|
|
417
|
-
program.configureOutput().writeOut?.(`rejected proposal ${rejectProposal} for journey '${rejected.metadata.id}'; the stored Journey is unchanged\n`);
|
|
418
|
-
process.exitCode = 0;
|
|
419
|
-
}
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
422
|
-
// #432: what the reviewer read — a hash, a sheet file, or (human mode) the sheet shown below.
|
|
423
|
-
let reviewedHash = hashFlag?.trim().toLowerCase();
|
|
424
|
-
if (reviewedHash !== undefined && !/^[0-9a-f]{64}$/.test(reviewedHash)) {
|
|
425
|
-
emitJson(program, fail("E_JOURNEY_REVIEW_ARGS", "--reviewed-hash needs the 64-hex content hash a review sheet shows"));
|
|
426
|
-
return;
|
|
427
|
-
}
|
|
428
|
-
if (reviewSheet !== undefined) {
|
|
429
|
-
let text;
|
|
430
|
-
try {
|
|
431
|
-
text = await readFile(reviewSheet, "utf8");
|
|
432
|
-
}
|
|
433
|
-
catch (e) {
|
|
434
|
-
emitJson(program, fail("E_JOURNEY_REVIEW_ARGS", `cannot read --review-sheet ${reviewSheet}: ${e instanceof Error ? e.message : String(e)}`));
|
|
435
|
-
return;
|
|
436
|
-
}
|
|
437
|
-
const fromSheet = reviewSheetHash(text);
|
|
438
|
-
if (reviewedHash !== undefined && reviewedHash !== fromSheet) {
|
|
439
|
-
emitJson(program, fail("E_JOURNEY_REVIEW_ARGS", "--reviewed-hash and --review-sheet name different content hashes"));
|
|
440
|
-
return;
|
|
441
|
-
}
|
|
442
|
-
reviewedHash = fromSheet;
|
|
443
|
-
}
|
|
444
|
-
// #434/#435: the readiness checks and the catalog analysis run before every approval; Jev only with --real.
|
|
445
|
-
const jev = await jevSetupFor(deps, resolveCatalogDir(deps.catalogDir), { real, jevProvider });
|
|
446
|
-
let attention;
|
|
447
|
-
if (!json) {
|
|
448
|
-
// Human mode: the sheet is shown before promoting, and the approval binds to what was shown.
|
|
449
|
-
const { review } = await reviewJourneyById(journeysDir, id, { ...targetsOpts(deps), catalogDir: resolveCatalogDir(deps.catalogDir), readiness: true, jev, action: "journey promote" });
|
|
450
|
-
program.configureOutput().writeOut?.(`${renderReviewText(review, reviewRenderOptions({ full: full === true }))}\n`);
|
|
451
|
-
attention = review.attention;
|
|
452
|
-
reviewedHash ??= proposal === undefined ? review.contentHash : review.proposal?.proposedHash;
|
|
453
|
-
}
|
|
454
|
-
const journeyResult = await promoteJourney(journeysDir, id, {
|
|
455
|
-
jev,
|
|
456
|
-
...(proposal === undefined ? {} : { proposal }),
|
|
457
|
-
...(acceptWeak === undefined ? {} : { acceptWeak }),
|
|
458
|
-
...(reviewedHash === undefined ? {} : { reviewedHash }),
|
|
459
|
-
...(acceptUnvetted === undefined ? {} : { acceptUnvetted }),
|
|
460
|
-
...(acceptFindings === undefined ? {} : { acceptFindings }),
|
|
461
|
-
catalogDir: resolveCatalogDir(deps.catalogDir),
|
|
462
|
-
// #437: a typed confirmation on a TTY (or the escape hatch, or the MCP channel), recorded as provenance.
|
|
463
|
-
confirm: ((confirm) => (request) => confirm(attention === undefined ? request : { ...request, attention }))(makeApprovalConfirm(deps.approval, nonInteractiveApproval === undefined ? {} : { nonInteractiveReason: nonInteractiveApproval })),
|
|
464
|
-
});
|
|
465
|
-
const envelope = ok(journeyResult.metadata);
|
|
466
|
-
if (json) {
|
|
467
|
-
emitJson(program, envelope);
|
|
468
|
-
}
|
|
469
|
-
else {
|
|
470
|
-
program.configureOutput().writeOut?.(`promoted journey '${journeyResult.metadata.id}' (approved content hash ${journeyResult.metadata.approval?.contentHash ?? ""}; ${describeProvenance(journeyResult.metadata.approval?.provenance)})\n`);
|
|
471
|
-
process.exitCode = 0;
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
catch (err) {
|
|
475
|
-
const refusal = approvalRefusal(err);
|
|
476
|
-
if (refusal !== null) {
|
|
477
|
-
emitJson(program, fail(refusal.code, refusal.message));
|
|
478
|
-
}
|
|
479
|
-
else if (err instanceof UnknownJourneyError) {
|
|
480
|
-
emitJson(program, fail("E_UNKNOWN_JOURNEY", String(err.message)));
|
|
481
|
-
}
|
|
482
|
-
else if (err instanceof JourneyProposalArgsError ||
|
|
483
|
-
err instanceof JourneyProposalNotFoundError ||
|
|
484
|
-
err instanceof JourneyProposalStaleError ||
|
|
485
|
-
err instanceof JourneyProposalProofError ||
|
|
486
|
-
err instanceof JourneyProposalInvalidError) {
|
|
487
|
-
emitJson(program, fail(err.code, err.message));
|
|
488
|
-
}
|
|
489
|
-
else if (err instanceof StaleReviewError || err instanceof ReviewSheetError || err instanceof ReviewSidecarError || err instanceof TargetConfigError) {
|
|
490
|
-
emitJson(program, fail(err instanceof TargetConfigError ? "E_TARGET_CONFIG" : err.code, err.message));
|
|
491
|
-
}
|
|
492
|
-
else if (err instanceof WeakJourneyError) {
|
|
493
|
-
// #401: the findings printed before the refusal (human mode); the reason the gate is here.
|
|
494
|
-
if (!json) {
|
|
495
|
-
const out = program.configureOutput().writeOut;
|
|
496
|
-
for (const finding of err.findings)
|
|
497
|
-
out?.(`${lintFindingLine(finding)}\n`);
|
|
498
|
-
}
|
|
499
|
-
emitJson(program, fail("E_JOURNEY_WEAK", String(err.message)));
|
|
500
|
-
process.exitCode = 1;
|
|
501
|
-
}
|
|
502
|
-
else if (err instanceof UnvettedLinksError || err instanceof ApprovalFindingsError) {
|
|
503
|
-
// #433: a gate on the catalog — exit 1 (a gating finding), like E_JOURNEY_WEAK.
|
|
504
|
-
emitJson(program, fail(err.code, err.message));
|
|
505
|
-
process.exitCode = 1;
|
|
506
|
-
}
|
|
507
|
-
else if (err instanceof CatalogInputError) {
|
|
508
|
-
emitJson(program, fail(err.code, err.message));
|
|
509
|
-
}
|
|
510
|
-
else if (err instanceof GatewaySelectionError || err instanceof MissingCredentialError) {
|
|
511
|
-
emitJson(program, fail("E_AI_SETUP_REQUIRED", err.message));
|
|
512
|
-
}
|
|
513
|
-
else {
|
|
514
|
-
emitJson(program, fail("E_JOURNEY_PROMOTE", String(err instanceof Error ? err.message : err)));
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
});
|
|
518
|
-
// #432 — the review sheet a reviewer reads before `journey promote`: what the Journey does, what it
|
|
519
|
-
// changes, what it proves, what changed since its last approval, and the content hash to bind to.
|
|
520
|
-
journey
|
|
521
|
-
.command("review [id]")
|
|
522
|
-
.description("a human-readable review sheet for promotion sign-off: summary, steps, side effects, inputs (names only), proof, change since last approval, content hash. #467: --stale (no id) lists every promoted Journey whose approval is stale, labelling step-id-only changes")
|
|
523
|
-
.option("--dir <path>", "journeys directory (default: the repo's .jevitate/journeys; outside a repo ~/.jevitate/journeys)")
|
|
524
|
-
.option("--markdown", "render the sheet as Markdown")
|
|
525
|
-
.option("--full", FULL_FLAG_HELP)
|
|
526
|
-
.option("--out <file>", "write the sheet (JSON with --json, Markdown with --markdown, else text) to this file")
|
|
527
|
-
.option("--readiness", READINESS_FLAG_HELP)
|
|
528
|
-
.option("--real", REAL_JEV_FLAG_HELP)
|
|
529
|
-
.option("--jev-provider <provider>", JEV_PROVIDER_FLAG_HELP, jevProviderArg)
|
|
530
|
-
.option("--stale", "#467: instead of one sheet, list every promoted Journey whose approval is stale (needs re-approval), labelling the ones whose only change is minted step ids")
|
|
531
|
-
.option("--json", "emit a JSON envelope (the schema-checked sheet)")
|
|
532
|
-
.action(async function (id) {
|
|
533
|
-
const { dir, json, markdown, full, out: outFile, readiness, real, jevProvider, stale } = this.opts();
|
|
534
|
-
if (json === true && markdown === true) {
|
|
535
|
-
emitJson(program, fail("E_JOURNEY_REVIEW_ARGS", "--json and --markdown are exclusive: pick one rendering"));
|
|
536
|
-
return;
|
|
537
|
-
}
|
|
538
|
-
if (stale === true) {
|
|
539
|
-
// #467: the stale list — read-only; one sheet's flags do not apply to it.
|
|
540
|
-
if (id !== undefined || markdown === true || full === true || outFile !== undefined || readiness === true || real === true || jevProvider !== undefined) {
|
|
541
|
-
emitJson(program, fail("E_JOURNEY_REVIEW_ARGS", "--stale lists every stale Journey: it takes no <id> and none of --markdown/--full/--out/--readiness/--real/--jev-provider"));
|
|
542
|
-
return;
|
|
543
|
-
}
|
|
544
|
-
try {
|
|
545
|
-
const result = await listStaleJourneys({ journeysDir: resolveJourneysDir(deps, dir), catalogDir: resolveCatalogDir(deps.catalogDir) });
|
|
546
|
-
if (json)
|
|
547
|
-
emitJson(program, ok(result));
|
|
548
|
-
else
|
|
549
|
-
program.configureOutput().writeOut?.(renderStaleJourneys(result));
|
|
550
|
-
process.exitCode = 0;
|
|
551
|
-
}
|
|
552
|
-
catch (err) {
|
|
553
|
-
emitJson(program, fail("E_JOURNEY_REVIEW", String(err instanceof Error ? err.message : err)));
|
|
554
|
-
}
|
|
555
|
-
return;
|
|
556
|
-
}
|
|
557
|
-
if (id === undefined) {
|
|
558
|
-
emitJson(program, fail("E_JOURNEY_REVIEW_ARGS", "journey review needs a Journey <id> (or --stale for the list of Journeys needing re-approval)"));
|
|
559
|
-
return;
|
|
560
|
-
}
|
|
561
|
-
try {
|
|
562
|
-
const jev = await jevSetupFor(deps, resolveCatalogDir(deps.catalogDir), { real, jevProvider });
|
|
563
|
-
const { review } = await reviewJourneyById(resolveJourneysDir(deps, dir), id, { ...targetsOpts(deps), catalogDir: resolveCatalogDir(deps.catalogDir), readiness: readiness === true, jev });
|
|
564
|
-
const renderOpts = reviewRenderOptions({ full: full === true, toFile: outFile !== undefined });
|
|
565
|
-
const rendered = json ? `${JSON.stringify(review, null, 2)}\n` : markdown ? renderReviewMarkdown(review, renderOpts) : renderReviewText(review, renderOpts);
|
|
566
|
-
if (outFile !== undefined)
|
|
567
|
-
await writeFile(outFile, rendered, { mode: 0o600 });
|
|
568
|
-
if (json) {
|
|
569
|
-
emitJson(program, ok(review));
|
|
570
|
-
}
|
|
571
|
-
else if (outFile !== undefined) {
|
|
572
|
-
program.configureOutput().writeOut?.(`review sheet for journey '${review.id}' written to ${outFile} (content hash ${review.contentHash})\n`);
|
|
573
|
-
}
|
|
574
|
-
else {
|
|
575
|
-
program.configureOutput().writeOut?.(rendered);
|
|
576
|
-
}
|
|
577
|
-
emitUsageLine(program, review.jev);
|
|
578
|
-
// #434: a review never fails on its findings (readiness and Jev answers are advice).
|
|
579
|
-
process.exitCode = 0;
|
|
580
|
-
}
|
|
581
|
-
catch (err) {
|
|
582
|
-
if (err instanceof UnknownJourneyError) {
|
|
583
|
-
emitJson(program, fail("E_UNKNOWN_JOURNEY", String(err.message)));
|
|
584
|
-
}
|
|
585
|
-
else if (err instanceof ReviewSidecarError || err instanceof CatalogInputError) {
|
|
586
|
-
emitJson(program, fail(err.code, err.message));
|
|
587
|
-
}
|
|
588
|
-
else if (err instanceof TargetConfigError) {
|
|
589
|
-
emitJson(program, fail("E_TARGET_CONFIG", err.message));
|
|
590
|
-
}
|
|
591
|
-
else if (err instanceof GatewaySelectionError || err instanceof MissingCredentialError) {
|
|
592
|
-
emitJson(program, fail("E_AI_SETUP_REQUIRED", err.message));
|
|
593
|
-
}
|
|
594
|
-
else if (err instanceof Error && err.message.startsWith("Invalid journey id")) {
|
|
595
|
-
emitJson(program, fail("E_JOURNEY_REVIEW_ARGS", err.message));
|
|
596
|
-
}
|
|
597
|
-
else {
|
|
598
|
-
emitJson(program, fail("E_JOURNEY_REVIEW", String(err instanceof Error ? err.message : err)));
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
});
|
|
602
|
-
// #467b — the one-time step-id backfill: CLI only (a repo rewrite the operator runs and commits; no MCP tool).
|
|
603
|
-
// #497 — `--labels`: the opt-in label backfill; a real replay of each Journey (the app must be running). CLI only.
|
|
604
|
-
withEnvironmentFlags(withBrowserLaunchFlags(journey.command("migrate")))
|
|
605
|
-
.description("one-time repo rewrite, CLI only. #467: --step-ids mints a stable step id on every recorded step that has none (every Journey and recording in the project) and points anchors at them. #497: --labels REPLAYS the Journeys (the app must be running; side effects happen) and stores each step's visible label for the review sheet. Both change every promoted Journey's hash: re-approve them (journey review --stale). Never approves")
|
|
606
|
-
.option("--step-ids", "mint missing step ids")
|
|
607
|
-
.option("--labels", "#497: replay the Journeys and store each step's visible label (exclusive with --step-ids; needs the app running)")
|
|
608
|
-
.option("--id <id...>", "#497: with --labels, only these Journeys (default: every local Journey)")
|
|
609
|
-
.option("--param <kv>", "#497: with --labels, a replay param as key=value (repeatable; each Journey gets only the ones it declares)", collectParam, {})
|
|
610
|
-
.option("--storage-state <file>", "#497: with --labels, Playwright storageState JSON to start the replays authenticated; must exist")
|
|
611
|
-
.option("--dry-run", "report what would change, write nothing (with --labels the replay still runs)")
|
|
612
|
-
.option("--dir <path>", "journeys directory (default: the repo's .jevitate/journeys; outside a repo ~/.jevitate/journeys)")
|
|
613
|
-
.option("--json", "emit a JSON envelope")
|
|
614
|
-
.action(async function () {
|
|
615
|
-
const { stepIds, labels, id: ids, param, storageState: storageStateFlag, dryRun, dir, json } = this.opts();
|
|
616
|
-
if (labels === true && stepIds === true) {
|
|
617
|
-
emitJson(program, fail("E_JOURNEY_MIGRATE_ARGS", "--labels and --step-ids are exclusive: run one migration at a time"));
|
|
618
|
-
return;
|
|
619
|
-
}
|
|
620
|
-
if (stepIds !== true && labels !== true) {
|
|
621
|
-
emitJson(program, fail("E_JOURNEY_MIGRATE_ARGS", "name the migration: --step-ids or --labels"));
|
|
622
|
-
return;
|
|
623
|
-
}
|
|
624
|
-
if (labels !== true) {
|
|
625
|
-
const labelOnly = [ids !== undefined ? "--id" : "", Object.keys(param).length > 0 ? "--param" : "", storageStateFlag !== undefined ? "--storage-state" : "", this.opts().env !== undefined ? "--env" : "", this.opts().baseUrl !== undefined ? "--base-url" : ""].filter((f) => f !== "");
|
|
626
|
-
if (labelOnly.length > 0) {
|
|
627
|
-
emitJson(program, fail("E_JOURNEY_MIGRATE_ARGS", `${labelOnly.join(", ")} only apply to --labels (the replay); --step-ids does not run the app`));
|
|
628
|
-
return;
|
|
629
|
-
}
|
|
630
|
-
try {
|
|
631
|
-
const result = await migrateStepIds({ journeysDir: resolveJourneysDir(deps, dir), projectDir: resolveCatalogDir(deps.catalogDir), dryRun: dryRun === true });
|
|
632
|
-
if (json)
|
|
633
|
-
emitJson(program, ok(result));
|
|
634
|
-
else
|
|
635
|
-
program.configureOutput().writeOut?.(renderMigrateStepIds(result));
|
|
636
|
-
process.exitCode = 0;
|
|
637
|
-
}
|
|
638
|
-
catch (err) {
|
|
639
|
-
emitJson(program, fail("E_JOURNEY_MIGRATE", String(err instanceof Error ? err.message : err)));
|
|
640
|
-
}
|
|
641
|
-
return;
|
|
642
|
-
}
|
|
643
|
-
const { env: envName, baseUrl } = this.opts();
|
|
644
|
-
let environment;
|
|
645
|
-
try {
|
|
646
|
-
environment = environmentFromFlags({ ...(envName === undefined ? {} : { env: envName }), ...(baseUrl === undefined ? {} : { baseUrl }) }, environmentSeams(deps));
|
|
647
|
-
}
|
|
648
|
-
catch (err) {
|
|
649
|
-
if (!isEnvironmentError(err))
|
|
650
|
-
throw err;
|
|
651
|
-
emitJson(program, fail(err.code, err.message));
|
|
652
|
-
return;
|
|
653
|
-
}
|
|
654
|
-
const storageState = storageStateFlag ?? environment?.storageState;
|
|
655
|
-
if (storageState !== undefined && !existsSync(storageState)) {
|
|
656
|
-
emitJson(program, fail("E_JOURNEY_MIGRATE_ARGS", `storage state not found: ${storageState}`));
|
|
657
|
-
return;
|
|
658
|
-
}
|
|
659
|
-
let browser;
|
|
660
|
-
try {
|
|
661
|
-
browser = browserRunFromFlags(this.opts(), deps.explore?.env ?? process.env);
|
|
662
|
-
}
|
|
663
|
-
catch (err) {
|
|
664
|
-
emitJson(program, fail("E_JOURNEY_MIGRATE_ARGS", err instanceof Error ? err.message : String(err)));
|
|
665
|
-
return;
|
|
666
|
-
}
|
|
667
|
-
try {
|
|
668
|
-
const result = await withSiteGate(resolveDbPath(deps), (siteGate) => migrateLabels({
|
|
669
|
-
journeysDir: resolveJourneysDir(deps, dir),
|
|
670
|
-
...(ids === undefined ? {} : { ids }),
|
|
671
|
-
dryRun: dryRun === true,
|
|
672
|
-
params: param,
|
|
673
|
-
run: {
|
|
674
|
-
...(siteGate === undefined ? {} : { siteGate }),
|
|
675
|
-
...(deps.explore?.browserPortFactory === undefined ? {} : { browserPortFactory: deps.explore.browserPortFactory }),
|
|
676
|
-
...(browser === undefined ? {} : { browser }),
|
|
677
|
-
...(storageState === undefined ? {} : { storageState }),
|
|
678
|
-
...(environment === undefined ? {} : { environment }),
|
|
679
|
-
},
|
|
680
|
-
}));
|
|
681
|
-
if (json)
|
|
682
|
-
emitJson(program, ok(result));
|
|
683
|
-
else
|
|
684
|
-
program.configureOutput().writeOut?.(renderMigrateLabels(result));
|
|
685
|
-
process.exitCode = 0;
|
|
686
|
-
}
|
|
687
|
-
catch (err) {
|
|
688
|
-
emitJson(program, fail("E_JOURNEY_MIGRATE", String(err instanceof Error ? err.message : err)));
|
|
689
|
-
}
|
|
690
|
-
});
|
|
691
|
-
// #401 — the assertion-strength lint: which assertions cannot prove the Journey's outcome. CI can
|
|
692
|
-
// gate on it (`--json`/`--sarif`, exit 1 when any error); `promote` runs the same lint first.
|
|
693
|
-
journey
|
|
694
|
-
.command("lint <id>")
|
|
695
|
-
.description("report a Journey's weak assertions (writes without an asserted effect, visibility-only claims, nothing after the last write, …)")
|
|
696
|
-
.option("--dir <path>", "journeys directory (default: the repo's .jevitate/journeys; outside a repo ~/.jevitate/journeys)")
|
|
697
|
-
.option("--sarif <file>", "also write a SARIF 2.1.0 log here")
|
|
698
|
-
.option("--fix", "#487: stamp the step id of each anchor that references a step by number only, write the Journey (a draft; a promoted one is refused), and report what was fixed")
|
|
699
|
-
.option("--json", "emit a JSON envelope")
|
|
700
|
-
.action(async function (id) {
|
|
701
|
-
const { dir, json, sarif, fix } = this.opts();
|
|
702
|
-
try {
|
|
703
|
-
let fixed = [];
|
|
704
|
-
if (fix === true) {
|
|
705
|
-
try {
|
|
706
|
-
fixed = (await fixJourneyAnchors(resolveJourneysDir(deps, dir), id)).stamped;
|
|
707
|
-
}
|
|
708
|
-
catch (err) {
|
|
709
|
-
if (err instanceof UnknownJourneyError)
|
|
710
|
-
throw err;
|
|
711
|
-
anchorEditFailure(program, err);
|
|
712
|
-
return;
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
const result = await lintJourneyById(resolveJourneysDir(deps, dir), id, { catalogDir: resolveCatalogDir(deps.catalogDir) });
|
|
716
|
-
if (sarif !== undefined) {
|
|
717
|
-
const log = renderJourneyLintSarif({ id: result.id, findings: result.findings, version: currentEngineInfo().version });
|
|
718
|
-
await writeFile(sarif, `${JSON.stringify(log, null, 2)}\n`);
|
|
719
|
-
}
|
|
720
|
-
if (json) {
|
|
721
|
-
emitJson(program, ok({ id: result.id, findings: result.findings, errors: result.errors, warnings: result.warnings, ...(fix === true ? { fixed } : {}) }));
|
|
722
|
-
}
|
|
723
|
-
else {
|
|
724
|
-
const out = program.configureOutput().writeOut;
|
|
725
|
-
if (fix === true) {
|
|
726
|
-
for (const a of fixed)
|
|
727
|
-
out?.(`fixed: anchor '${a.name}' now carries stepId '${a.stepId}' (step ${a.step})\n`);
|
|
728
|
-
if (fixed.length === 0)
|
|
729
|
-
out?.("fixed: nothing — no anchor references a step by number only\n");
|
|
730
|
-
}
|
|
731
|
-
for (const finding of result.findings)
|
|
732
|
-
out?.(`${lintFindingLine(finding)}\n`);
|
|
733
|
-
out?.(`${result.errors} error(s), ${result.warnings} warning(s) — journey '${result.id}'\n`);
|
|
734
|
-
}
|
|
735
|
-
process.exitCode = result.errors > 0 ? 1 : 0;
|
|
736
|
-
}
|
|
737
|
-
catch (err) {
|
|
738
|
-
if (err instanceof UnknownJourneyError) {
|
|
739
|
-
emitJson(program, fail("E_UNKNOWN_JOURNEY", String(err.message)));
|
|
740
|
-
}
|
|
741
|
-
else {
|
|
742
|
-
emitJson(program, fail("E_JOURNEY_LINT_ARGS", String(err instanceof Error ? err.message : err)));
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
});
|
|
746
|
-
// #402 — the negative proof: replay the Journey once as recorded, then once per mutation (skip a
|
|
747
|
-
// write step, abort its writes, type an empty value into a checked fill); each assertion must fail
|
|
748
|
-
// under its paired mutation. `verify` is reserved for proofs: today only `--mutate` exists.
|
|
749
|
-
withEnvironmentFlags(withBrowserLaunchFlags(withFixtureFlags(journey.command("verify <id>"))))
|
|
750
|
-
.description("prove each assertion of a Journey can fail: --mutate replays it with each write step skipped or blocked, and each checked fill emptied")
|
|
751
|
-
.option("--mutate", "run the mutation proof (required)")
|
|
752
|
-
.option("--dir <path>", "journeys directory (default: the repo's .jevitate/journeys; outside a repo ~/.jevitate/journeys)")
|
|
753
|
-
.option("--param <kv>", "param as key=value (repeatable)", collectParam, {})
|
|
754
|
-
.option("--storage-state <file>", "Playwright storageState JSON to start each replay authenticated (as journey run)")
|
|
755
|
-
.option("--json", "emit a JSON envelope")
|
|
756
|
-
.action(async function (id) {
|
|
757
|
-
const { mutate, dir, param, storageState: storageStateFlag, json, env: envName, baseUrl } = this.opts();
|
|
758
|
-
if (mutate !== true) {
|
|
759
|
-
emitJson(program, fail("E_JOURNEY_VERIFY_ARGS", "journey verify needs --mutate (the mutation proof is the only verification it runs today)"));
|
|
760
|
-
return;
|
|
761
|
-
}
|
|
762
|
-
let environment;
|
|
763
|
-
try {
|
|
764
|
-
environment = environmentFromFlags({ ...(envName === undefined ? {} : { env: envName }), ...(baseUrl === undefined ? {} : { baseUrl }) }, environmentSeams(deps));
|
|
765
|
-
}
|
|
766
|
-
catch (err) {
|
|
767
|
-
if (!isEnvironmentError(err))
|
|
768
|
-
throw err;
|
|
769
|
-
emitJson(program, fail(err.code, err.message));
|
|
770
|
-
return;
|
|
771
|
-
}
|
|
772
|
-
const ownFixtureFlags = this.opts();
|
|
773
|
-
const fixtureFlags = {
|
|
774
|
-
...ownFixtureFlags,
|
|
775
|
-
...(ownFixtureFlags.before === undefined && environment?.hooks?.before !== undefined ? { before: environment.hooks.before } : {}),
|
|
776
|
-
...(ownFixtureFlags.after === undefined && environment?.hooks?.after !== undefined ? { after: environment.hooks.after } : {}),
|
|
777
|
-
};
|
|
778
|
-
const storageState = storageStateFlag ?? environment?.storageState;
|
|
779
|
-
if (storageState !== undefined && !existsSync(storageState)) {
|
|
780
|
-
emitJson(program, fail("E_JOURNEY_VERIFY_ARGS", `storage state not found: ${storageState}`));
|
|
781
|
-
return;
|
|
782
|
-
}
|
|
783
|
-
let browser;
|
|
784
|
-
try {
|
|
785
|
-
browser = browserRunFromFlags(this.opts(), deps.explore?.env ?? process.env);
|
|
786
|
-
}
|
|
787
|
-
catch (err) {
|
|
788
|
-
emitJson(program, fail("E_JOURNEY_VERIFY_ARGS", err instanceof Error ? err.message : String(err)));
|
|
789
|
-
return;
|
|
790
|
-
}
|
|
791
|
-
try {
|
|
792
|
-
const report = await withSiteGate(resolveDbPath(deps), (siteGate) => verifyJourneyMutations({
|
|
793
|
-
...(siteGate === undefined ? {} : { siteGate }),
|
|
794
|
-
dir: resolveJourneysDir(deps, dir),
|
|
795
|
-
id,
|
|
796
|
-
params: param,
|
|
797
|
-
browserPortFactory: deps.explore?.browserPortFactory,
|
|
798
|
-
...(browser === undefined ? {} : { browser }),
|
|
799
|
-
...(storageState !== undefined ? { storageState } : {}),
|
|
800
|
-
...(environment === undefined ? {} : { environment }),
|
|
801
|
-
// Fixtures run around EVERY replay, so each mutation starts from the same state as the base.
|
|
802
|
-
fixtures: (site) => {
|
|
803
|
-
const fx = buildMissionFixtures(fixtureFlags, {
|
|
804
|
-
allowlist: environment === undefined ? [site] : environment.allowedOrigins,
|
|
805
|
-
baseUrl: site,
|
|
806
|
-
...(storageState !== undefined ? { storageState } : {}),
|
|
807
|
-
...(environment?.fixtures === undefined ? {} : { targetFixtures: environment.fixtures }),
|
|
808
|
-
});
|
|
809
|
-
checkSetupRefs({ "--param": Object.values(param) }, fx);
|
|
810
|
-
return fx;
|
|
811
|
-
},
|
|
812
|
-
}));
|
|
813
|
-
const code = verifyExitCode(report.verdict);
|
|
814
|
-
if (json) {
|
|
815
|
-
emitJson(program, ok(withEngine(report)));
|
|
816
|
-
}
|
|
817
|
-
else {
|
|
818
|
-
const out = program.configureOutput().writeOut;
|
|
819
|
-
for (const a of report.assertions)
|
|
820
|
-
out?.(`${verifyAssertionLine(a)}\n`);
|
|
821
|
-
const s = report.summary;
|
|
822
|
-
out?.(`${report.verdict} — journey '${report.journeyId}': ${s.sensitive} sensitive, ${s.insensitive} insensitive, ${s.cascade} cascade, ${s.notApplied} not applied, ${s.error} error, ${s.unpaired} unpaired` +
|
|
823
|
-
`${report.reason === undefined ? "" : ` (${report.reason})`}\n`);
|
|
824
|
-
}
|
|
825
|
-
process.exitCode = code;
|
|
826
|
-
}
|
|
827
|
-
catch (err) {
|
|
828
|
-
if (err instanceof SiteGateRefusedError || isEnvironmentError(err)) {
|
|
829
|
-
emitJson(program, fail(err.code, err.message));
|
|
830
|
-
}
|
|
831
|
-
else if (err instanceof UnknownJourneyError) {
|
|
832
|
-
emitJson(program, fail("E_UNKNOWN_JOURNEY", String(err.message)));
|
|
833
|
-
}
|
|
834
|
-
else if (err instanceof JourneyRequiresAuthError) {
|
|
835
|
-
emitJson(program, fail("E_JOURNEY_REQUIRES_AUTH", String(err.message)));
|
|
836
|
-
}
|
|
837
|
-
else if (err instanceof ExtensionMismatchError) {
|
|
838
|
-
emitJson(program, fail(err.code, err.message));
|
|
839
|
-
}
|
|
840
|
-
else if (err instanceof FixtureSpecError || err instanceof UnboundSetupRefError) {
|
|
841
|
-
emitJson(program, fail(err.code, err.message));
|
|
842
|
-
}
|
|
843
|
-
else if (err instanceof ParamValidationError) {
|
|
844
|
-
emitJson(program, fail("E_INVALID_PARAMS", String(err.message)));
|
|
845
|
-
}
|
|
846
|
-
else if (err instanceof FixtureSetupError) {
|
|
847
|
-
emitJson(program, fail("E_JOURNEY_VERIFY", `fixture setup failed — nothing verified: ${err.message}`));
|
|
848
|
-
}
|
|
849
|
-
else {
|
|
850
|
-
emitJson(program, fail("E_JOURNEY_VERIFY", String(err instanceof Error ? err.message : err)));
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
});
|
|
854
|
-
// #293 — the named states worth exploring from (`explore --from-journey <id> --at-step <name>`).
|
|
855
|
-
journey
|
|
856
|
-
.command("anchors <id>")
|
|
857
|
-
.description("list a Journey's anchors (#293): named steps to branch a mission off with `explore --from-journey <id> --at-step <name>`, and their suggested probes")
|
|
858
|
-
.option("--suggest", "propose anchors where the recorded flow changes state (a new page, a form that became valid, a completed write), each with the `journey anchor add` command to accept it; advisory, writes nothing (#487)")
|
|
859
|
-
.option("--dir <path>", "journeys directory (default: the repo's .jevitate/journeys; outside a repo ~/.jevitate/journeys)")
|
|
860
|
-
.option("--json", "emit a JSON envelope")
|
|
861
|
-
.action(async function (id) {
|
|
862
|
-
const { dir, json, suggest } = this.opts();
|
|
863
|
-
if (suggest) {
|
|
864
|
-
await runAnchorSuggest(program, deps, id, { dir, json });
|
|
865
|
-
return;
|
|
866
|
-
}
|
|
867
|
-
try {
|
|
868
|
-
const found = await new JourneyRegistry(new FsJourneyStore(resolveJourneysDir(deps, dir))).get(id);
|
|
869
|
-
if (found === null) {
|
|
870
|
-
emitJson(program, fail("E_UNKNOWN_JOURNEY", `unknown journey '${id}'`));
|
|
871
|
-
return;
|
|
872
|
-
}
|
|
873
|
-
const data = { journeyId: found.metadata.id, promoted: found.metadata.promoted, steps: journeyStepCount(found), anchors: listJourneyAnchors(found) };
|
|
874
|
-
if (json) {
|
|
875
|
-
emitJson(program, ok(data));
|
|
876
|
-
}
|
|
877
|
-
else {
|
|
878
|
-
const out = program.configureOutput().writeOut;
|
|
879
|
-
if (data.anchors.length === 0) {
|
|
880
|
-
out?.(`journey '${data.journeyId}' declares no anchors — branch off a step number instead (--at-step 1..${data.steps}), or add metadata.anchors\n`);
|
|
881
|
-
}
|
|
882
|
-
for (const a of data.anchors) {
|
|
883
|
-
out?.(`${a.name}\tstep ${a.step}\tafter: ${a.afterStep}${a.description === undefined ? "" : `\t${a.description}`}${a.probes.length === 0 ? "" : `\tprobes: ${a.probes.join("; ")}`}\n`);
|
|
884
|
-
}
|
|
885
|
-
if (!data.promoted)
|
|
886
|
-
out?.(`note: journey '${data.journeyId}' is not promoted — missions branch only off promoted Journeys\n`);
|
|
887
|
-
process.exitCode = 0;
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
catch (err) {
|
|
891
|
-
emitJson(program, fail("E_JOURNEY_ANCHORS_ARGS", String(err instanceof Error ? err.message : err)));
|
|
892
|
-
}
|
|
893
|
-
});
|
|
18
|
+
registerJourneyList(journey, program, deps);
|
|
19
|
+
registerJourneyFind(journey, program, deps);
|
|
20
|
+
registerJourneyRun(journey, program, deps);
|
|
21
|
+
registerJourneyPromote(journey, program, deps);
|
|
22
|
+
registerJourneyReview(journey, program, deps);
|
|
23
|
+
registerJourneyMigrate(journey, program, deps);
|
|
24
|
+
registerJourneyLint(journey, program, deps);
|
|
25
|
+
registerJourneyVerify(journey, program, deps);
|
|
26
|
+
registerJourneyAnchors(journey, program, deps);
|
|
894
27
|
// #487 — add/remove a draft Journey's anchors with the step id written at once.
|
|
895
28
|
registerJourneyAnchorCommands(journey, program, deps);
|
|
29
|
+
// #523 — link a Journey to a catalog job / persona (checked against the catalog; never an approval).
|
|
30
|
+
registerJourneyLinkCommand(journey, program, deps);
|
|
896
31
|
// #246 — draft a Journey's intent on playback; `--approve` is the human gate that writes it.
|
|
897
32
|
registerJourneyAnnotateCommand(journey, program, deps);
|
|
898
33
|
// #248 — replay a Journey as a narrated demo: video + subtitles and/or a step-by-step guide.
|
|
899
34
|
registerJourneyDemoCommand(journey, program, deps);
|
|
900
|
-
|
|
901
|
-
// Preserves every publish-side guard in `@jevitate/sources` (promoted-only,
|
|
902
|
-
// secret-references-only, declared-origin coverage); writes onto a NEW
|
|
903
|
-
// `publish/<id>` branch and degrades gracefully when `gh` is absent.
|
|
904
|
-
journey
|
|
905
|
-
.command("publish <id>")
|
|
906
|
-
.requiredOption("--to <source>", "registered source name to publish into")
|
|
907
|
-
.option("--dir <path>", "journeys directory (default: the repo's .jevitate/journeys; outside a repo ~/.jevitate/journeys)")
|
|
908
|
-
.option("--declare-origin <origin>", "origin this Journey is authorized for (repeatable; default: derived from navigate steps)", (v, prev) => [...prev, v], [])
|
|
909
|
-
.option("--as <id>", "publish under a different id than the local one")
|
|
910
|
-
.option("--json", "emit a JSON envelope")
|
|
911
|
-
.action(async function (id) {
|
|
912
|
-
const { to, dir, declareOrigin, as: asId, json } = this.opts();
|
|
913
|
-
try {
|
|
914
|
-
const apiDeps = resolveSourceApiDeps(deps);
|
|
915
|
-
const gh = deps.sources?.gh ?? realGhPort;
|
|
916
|
-
const result = await publishJourneyToSource({ ...apiDeps, gh }, {
|
|
917
|
-
journeysDir: resolveJourneysDir(deps, dir),
|
|
918
|
-
id,
|
|
919
|
-
toSource: to,
|
|
920
|
-
declareOrigins: declareOrigin,
|
|
921
|
-
asId,
|
|
922
|
-
});
|
|
923
|
-
const envelope = ok(result);
|
|
924
|
-
if (json) {
|
|
925
|
-
emitJson(program, envelope);
|
|
926
|
-
}
|
|
927
|
-
else {
|
|
928
|
-
const out = program.configureOutput().writeOut;
|
|
929
|
-
out?.(`published '${id}' to '${to}' on branch ${result.branch}\n`);
|
|
930
|
-
if (result.prUrl)
|
|
931
|
-
out?.(`PR: ${result.prUrl}\n`);
|
|
932
|
-
else if (result.instructions)
|
|
933
|
-
out?.(`${result.instructions}\n`);
|
|
934
|
-
process.exitCode = 0;
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
catch (err) {
|
|
938
|
-
if (err instanceof UnknownSourceError) {
|
|
939
|
-
emitJson(program, fail("E_JOURNEY_PUBLISH_UNKNOWN_SOURCE", err.message));
|
|
940
|
-
}
|
|
941
|
-
else if (err instanceof UnknownJourneyError) {
|
|
942
|
-
emitJson(program, fail("E_JOURNEY_PUBLISH_UNKNOWN_JOURNEY", err.message));
|
|
943
|
-
}
|
|
944
|
-
else if (err instanceof NotPromotedError) {
|
|
945
|
-
emitJson(program, fail("E_JOURNEY_PUBLISH_NOT_PROMOTED", err.message));
|
|
946
|
-
}
|
|
947
|
-
else if (err instanceof NoDeclaredOriginsError) {
|
|
948
|
-
emitJson(program, fail("E_JOURNEY_PUBLISH_NO_ORIGINS", err.message));
|
|
949
|
-
}
|
|
950
|
-
else if (err instanceof EmbeddedSecretError) {
|
|
951
|
-
emitJson(program, fail("E_JOURNEY_PUBLISH_SECRET", err.message));
|
|
952
|
-
}
|
|
953
|
-
else if (err instanceof UndeclaredOriginError) {
|
|
954
|
-
emitJson(program, fail("E_JOURNEY_PUBLISH_ORIGIN", err.message));
|
|
955
|
-
}
|
|
956
|
-
else {
|
|
957
|
-
emitJson(program, fail("E_JOURNEY_PUBLISH", String(err instanceof Error ? err.message : err)));
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
});
|
|
35
|
+
registerJourneyPublish(journey, program, deps);
|
|
961
36
|
}
|
|
962
37
|
//# sourceMappingURL=journey-cli.js.map
|