@pugi/cli 0.1.0-beta.96 → 0.1.0-beta.98

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.
@@ -75,9 +75,15 @@ export const beta1DefaultBudgets = {
75
75
  // real per-call token use is ~30-40% lower than legacy. Bump headroom
76
76
  // so multi-file refactors no longer trip the cap. Anvil clamps per-call
77
77
  // max_tokens to 128k (PR) so the engine envelope still safe.
78
- fix: { maxTokens: 80_000, maxToolCalls: 20 },
79
- code: { maxTokens: 120_000, maxToolCalls: 25 },
80
- build: { maxTokens: 200_000, maxToolCalls: 30 },
78
+ // CEO escalation 2026-06-05: REPL React tic-tac-toe blew budget at
79
+ // 120214 > 120000 mid-build. the upstream customer expectation = 200K+
80
+ // context per session с recharge. Bump к 400K for code/fix + raise
81
+ // tool-call ceilings so multi-file front-end builds (React + tests +
82
+ // index.css + index.html + main.jsx) fit without forcing the operator
83
+ // to escalate к `--intensity=deep`.
84
+ fix: { maxTokens: 250_000, maxToolCalls: 40 },
85
+ code: { maxTokens: 400_000, maxToolCalls: 50 },
86
+ build: { maxTokens: 500_000, maxToolCalls: 60 },
81
87
  plan: { maxTokens: 200_000, maxToolCalls: 8 },
82
88
  explain: { maxTokens: 60_000, maxToolCalls: 10 },
83
89
  review_triple: { maxTokens: 100_000, maxToolCalls: 10 },
@@ -49,26 +49,31 @@ const PROFILES = {
49
49
  allowParallelAgents: false,
50
50
  maxParallelAgents: 0,
51
51
  },
52
+ // CEO 2026-06-05: 80K standard exhausted React multi-file build mid-
53
+ // turn (120K hardcoded budget). Customers compare to the upstream = 200K
54
+ // context per session. Bump standard к 200K so default REPL doesn't
55
+ // trip mid-build; deep к 500K for complex multi-file refactors;
56
+ // marathon к 1.5M for long-running autonomous work.
52
57
  standard: {
53
58
  level: 'standard',
54
- maxTurns: 15,
55
- budgetTokens: 80_000,
59
+ maxTurns: 30,
60
+ budgetTokens: 200_000,
56
61
  modelTag: 'standard',
57
62
  allowParallelAgents: false,
58
63
  maxParallelAgents: 0,
59
64
  },
60
65
  deep: {
61
66
  level: 'deep',
62
- maxTurns: 50,
63
- budgetTokens: 200_000,
67
+ maxTurns: 80,
68
+ budgetTokens: 500_000,
64
69
  modelTag: 'standard',
65
70
  allowParallelAgents: true,
66
71
  maxParallelAgents: 3,
67
72
  },
68
73
  marathon: {
69
74
  level: 'marathon',
70
- maxTurns: 200,
71
- budgetTokens: 800_000,
75
+ maxTurns: 300,
76
+ budgetTokens: 1_500_000,
72
77
  modelTag: 'heavy',
73
78
  allowParallelAgents: true,
74
79
  maxParallelAgents: 3,
@@ -19,6 +19,12 @@ import { dispatchEnterWorktree, enterWorktreeJsonSchema, } from '../../tools/ent
19
19
  import { dispatchExitWorktree, exitWorktreeJsonSchema, } from '../../tools/exit-worktree.js';
20
20
  import { webFetchTool } from '../../tools/web-fetch.js';
21
21
  import { webSearchTool } from '../../tools/web-search.js';
22
+ // Phase 1 runtime evidence pack (PUGI-291..295): server_* + http_request
23
+ // dispatchers + JSON schema fragments. Every primitive returns a
24
+ // sentinel string on validation failure (sleep/brief convention) so
25
+ // the engine adapter surfaces it as a recoverable tool result.
26
+ import { dispatchHttpRequest, httpRequestJsonSchema, } from '../../tools/http-request.js';
27
+ import { dispatchServerHealth, dispatchServerLogs, dispatchServerStart, dispatchServerStop, serverHealthJsonSchema, serverLogsJsonSchema, serverStartJsonSchema, serverStopJsonSchema, } from '../../tools/server-tools.js';
22
28
  import { agentTool } from '../../tools/agent-tool.js';
23
29
  import { multiEdit } from '../../tools/multi-edit.js';
24
30
  import { recordVerificationCall } from '../session.js';
@@ -217,6 +223,15 @@ const WIRED_TOOLS = new Set([
217
223
  'symbols_signature',
218
224
  'symbols_type_definition',
219
225
  'symbols_workspace_symbols',
226
+ // Phase 1 runtime evidence pack (PUGI-291..295): server_* + http_request.
227
+ // Off by default in plan mode (mutation surface for start/stop; even
228
+ // health/logs/http_request cross the plan-mode read-only contract
229
+ // because they are runtime evidence primitives, not source reads).
230
+ 'http_request',
231
+ 'server_health',
232
+ 'server_logs',
233
+ 'server_start',
234
+ 'server_stop',
220
235
  ]);
221
236
  export function buildToolsSchema(kind, options = { allowFetch: false, allowSearch: false }) {
222
237
  const planMode = kind === 'plan';
@@ -831,6 +846,31 @@ export function buildToolsSchema(kind, options = { allowFetch: false, allowSearc
831
846
  },
832
847
  },
833
848
  },
849
+ },
850
+ // Phase 1 runtime evidence pack (PUGI-291..295) - server lifecycle
851
+ // primitives + generic HTTP request. Off in plan mode because each
852
+ // tool produces side effects (a spawned process, an outbound HTTP
853
+ // call) the plan-mode read-only contract refuses.
854
+ {
855
+ name: 'server_start',
856
+ description: 'Spawn a server via /bin/sh -c <command> in the workspace and poll the health URL until it returns the expected status (default 200) or the timeout elapses. Persists pid + meta to `.pugi/runs/<runId>/server.json` and a stdout/stderr tail to `server.log`. Returns {ok, pid, port, healthStatus, logPath, durationMs, error?}. Use to materialise concrete pid + health 200 evidence rather than asserting a server is up in prose.',
857
+ parameters: serverStartJsonSchema,
858
+ }, {
859
+ name: 'server_stop',
860
+ description: 'Send SIGTERM to the supplied pid, wait graceMs (default 5000ms), then escalate to SIGKILL. Idempotent - a pid that already exited returns ok=true with signal=undefined. Returns {ok, pid, exitCode?, signal?, durationMs, error?}.',
861
+ parameters: serverStopJsonSchema,
862
+ }, {
863
+ name: 'server_health',
864
+ description: 'One-shot HTTP GET against the supplied URL with a short timeout (default 5000ms, max 60000ms). Returns {ok, status, durationMs, body?, error?} where ok=true only when the response status equals expectStatus (default 200). Body is capped at 1 KB so the envelope stays compact.',
865
+ parameters: serverHealthJsonSchema,
866
+ }, {
867
+ name: 'server_logs',
868
+ description: 'Read the trailing N lines of `.pugi/runs/<runId>/server.log`. Defaults to the most recent run when runId is omitted; pid-based lookup walks every run dir. Returns {ok, logPath, lines, totalLines, error?}.',
869
+ parameters: serverLogsJsonSchema,
870
+ }, {
871
+ name: 'http_request',
872
+ description: 'Issue an HTTP request (GET/POST/PUT/PATCH/DELETE/HEAD/OPTIONS) against a URL. Loopback hosts (localhost / 127.0.0.0/8 / ::1) always pass; non-loopback hosts require allowExternal: true so the default posture stays private. Body objects/arrays are JSON-serialised. Returns {ok, status, headers, body, json?, durationMs, error?, truncated?}.',
873
+ parameters: httpRequestJsonSchema,
834
874
  });
835
875
  }
836
876
  // β4 M1/M3: append MCP tools last. Plan mode skips them because every
@@ -1235,6 +1275,26 @@ export function buildExecutor(input) {
1235
1275
  sessionId,
1236
1276
  });
1237
1277
  }
1278
+ // Phase 1 runtime evidence pack (PUGI-291..295). Each tool returns
1279
+ // a JSON-string envelope or a sentinel string on validation
1280
+ // failure - both paths are recoverable from the model's POV, so
1281
+ // the engine adapter routes them as plain tool results and the
1282
+ // model can self-correct.
1283
+ if (name === 'server_start') {
1284
+ return dispatchServerStart({ workspaceRoot }, args);
1285
+ }
1286
+ if (name === 'server_stop') {
1287
+ return dispatchServerStop({ workspaceRoot }, args);
1288
+ }
1289
+ if (name === 'server_health') {
1290
+ return dispatchServerHealth({ workspaceRoot }, args);
1291
+ }
1292
+ if (name === 'server_logs') {
1293
+ return dispatchServerLogs({ workspaceRoot }, args);
1294
+ }
1295
+ if (name === 'http_request') {
1296
+ return dispatchHttpRequest({}, args);
1297
+ }
1238
1298
  if (name === 'multi_edit') {
1239
1299
  return dispatchMultiEdit(args, ctx);
1240
1300
  }
@@ -47,11 +47,25 @@ const BUILT_IN_TOOL_CLASSES = Object.freeze({
47
47
  skill: 'read',
48
48
  task_get: 'read',
49
49
  task_list: 'read',
50
+ // Phase 1 runtime evidence pack (PUGI-291..295): server_health and
51
+ // server_logs are read-only probes (HTTP GET + tail of a log file
52
+ // already on disk). Classifying them as `read` keeps the ask-mode
53
+ // prompts honest; the dispatcher still applies the network-perm gate
54
+ // for server_health and the workspace-read gate for server_logs.
55
+ server_health: 'read',
56
+ server_logs: 'read',
50
57
  // Mutating actions.
51
58
  write: 'write',
52
59
  edit: 'write',
53
60
  multi_edit: 'write',
54
61
  bash: 'write',
62
+ // Phase 1 runtime evidence pack: server_start spawns a process,
63
+ // server_stop terminates one, http_request can POST/PUT/DELETE
64
+ // arbitrary data against a localhost service. All three are write
65
+ // class so plan mode refuses and ask mode prompts.
66
+ server_start: 'write',
67
+ server_stop: 'write',
68
+ http_request: 'write',
55
69
  task_create: 'write',
56
70
  task_update: 'write',
57
71
  todo_write: 'write',
@@ -2831,6 +2831,24 @@ export class ReplSession {
2831
2831
  try {
2832
2832
  if (useDirectEngine) {
2833
2833
  const persona = personaSlugFor('code');
2834
+ // PR C (PUGI-538-FU): thread the recent conversation
2835
+ // into the engine prompt so multi-turn refinements work. Without
2836
+ // this, the engine sees only the literal current brief — a
2837
+ // follow-up like "react" after "сделай крестики нолики" arrives
2838
+ // as a bare "react" with no prior context, and the engine ships
2839
+ // arbitrary nonsense or asks again ("нет конкретного feature
2840
+ // request"). The CEO reproduction 2026-06-05 (Python tic-tac-toe
2841
+ // shipped когда customer wanted React браузер game, then engine
2842
+ // claimed "нет feature request" on the correction turn) is
2843
+ // exactly this gap.
2844
+ //
2845
+ // Display channels (system line, transcript) keep using the bare
2846
+ // `brief` for UX cleanliness; only the engine's task.prompt gets
2847
+ // the full conversational context via the new `enginePrompt`
2848
+ // field. Engine-bridge falls back to brief when enginePrompt is
2849
+ // undefined (server-emitted parser-built tags), preserving the
2850
+ // legacy behaviour for those surfaces.
2851
+ const enginePrompt = this.buildEnginePromptWithContext(brief);
2834
2852
  const tag = {
2835
2853
  command: 'code',
2836
2854
  brief,
@@ -2842,6 +2860,7 @@ export class ReplSession {
2842
2860
  signature: signatureForToolRoute('code', persona, brief),
2843
2861
  start: 0,
2844
2862
  end: 0,
2863
+ ...(enginePrompt !== brief ? { enginePrompt } : {}),
2845
2864
  };
2846
2865
  await this.runEngineBridge(tag);
2847
2866
  }
@@ -2862,6 +2881,54 @@ export class ReplSession {
2862
2881
  this.markDispatchFailed('post_brief_failed');
2863
2882
  }
2864
2883
  }
2884
+ /**
2885
+ * PR C (PUGI-538-FU): build the engine prompt with recent
2886
+ * conversation context prepended. The current brief is preserved as
2887
+ * the explicit "Current request:" terminal so the engine knows what
2888
+ * the user is asking right now, while the prior turns give it the
2889
+ * stack/framework/format hints from earlier in the dialog.
2890
+ *
2891
+ * Returns `brief` unchanged when there is no prior conversation —
2892
+ * the empty preamble would just waste tokens.
2893
+ *
2894
+ * Window policy: last 4 conversational exchanges (operator + persona
2895
+ * pairs), text truncated к 400 chars per row. Drops the trailing
2896
+ * operator row if it matches `brief` (which has already been appended
2897
+ * to the transcript by `appendOperatorLine` at line 3429 above and
2898
+ * would otherwise duplicate inside the prompt).
2899
+ *
2900
+ * Doc strings stay в English per repo convention; the rendered
2901
+ * preamble uses neutral English labels ("User", "Pugi") so the
2902
+ * engine's model treats it as standard transcript context rather
2903
+ * than a localized field name.
2904
+ */
2905
+ buildEnginePromptWithContext(brief) {
2906
+ const MAX_TURNS = 4;
2907
+ const MAX_ROW_CHARS = 400;
2908
+ const conversational = this.state.transcript.filter((r) => r.source === 'operator' || r.source === 'persona');
2909
+ if (conversational.length === 0)
2910
+ return brief;
2911
+ // Take the last MAX_TURNS * 2 rows (each turn = 1 operator + 1 persona).
2912
+ const recent = conversational.slice(-(MAX_TURNS * 2));
2913
+ // Drop trailing operator row when it equals the brief we're about
2914
+ // to dispatch — the brief is the "current request" and already
2915
+ // landed in the transcript via `appendOperatorLine` earlier in
2916
+ // `dispatchBrief`. Including it twice would confuse the engine.
2917
+ const lastRow = recent[recent.length - 1];
2918
+ const trimmed = lastRow && lastRow.source === 'operator' && lastRow.text === brief
2919
+ ? recent.slice(0, -1)
2920
+ : recent;
2921
+ if (trimmed.length === 0)
2922
+ return brief;
2923
+ const lines = trimmed.map((r) => {
2924
+ const role = r.source === 'operator' ? 'User' : 'Pugi';
2925
+ const truncated = r.text.length > MAX_ROW_CHARS
2926
+ ? r.text.slice(0, MAX_ROW_CHARS) + '...'
2927
+ : r.text;
2928
+ return `- ${role}: ${truncated}`;
2929
+ });
2930
+ return `Recent conversation:\n${lines.join('\n')}\n\nCurrent request: ${brief}`;
2931
+ }
2865
2932
  /**
2866
2933
  * : reset the FSM to `idle` after a terminal transition so the
2867
2934
  * next brief can start. The FSM does not allow direct
@@ -4137,7 +4204,11 @@ export class ReplSession {
4137
4204
  result = await bridge({
4138
4205
  command: tag.command,
4139
4206
  persona: tag.persona,
4140
- brief: tag.brief,
4207
+ // PR C (PUGI-538-FU): prefer the contextualized
4208
+ // engine prompt when the direct-engine path set it. Falls back
4209
+ // к the bare brief for parser-built tags from the server-emitted
4210
+ // envelope path (no conversation context available there).
4211
+ brief: tag.enginePrompt ?? tag.brief,
4141
4212
  bridgeId,
4142
4213
  signal: abort.signal,
4143
4214
  onEvent,
@@ -142,16 +142,21 @@ const DENY_ALL_WRITES_READONLY = Object.freeze([
142
142
  /* ------------------------------------------------------------------ */
143
143
  /* Default budgets */
144
144
  /* ------------------------------------------------------------------ */
145
+ // CEO escalation 2026-06-05: 120K coder budget exhausted mid-React-
146
+ // build (120214 > 120000). Match the engine-level `code` task bump
147
+ // (apps/pugi-cli/src/core/engine/budgets.ts:149 — 400K). Subagent
148
+ // dispatches inherit the upstream caller's headroom, so this needs
149
+ // to track the engine envelope.
145
150
  const DEFAULT_BUDGETS = Object.freeze({
146
- orchestrator: { tokens: 200_000, dollars: 5, wallClockMs: 600_000 },
147
- architect: { tokens: 80_000, dollars: 2, wallClockMs: 300_000 },
148
- coder: { tokens: 120_000, dollars: 3, wallClockMs: 600_000 },
149
- verifier: { tokens: 60_000, dollars: 2, wallClockMs: 300_000 },
150
- reviewer: { tokens: 80_000, dollars: 2, wallClockMs: 300_000 },
151
- researcher: { tokens: 60_000, dollars: 1.5, wallClockMs: 300_000 },
152
- release: { tokens: 40_000, dollars: 1, wallClockMs: 180_000 },
153
- devops: { tokens: 60_000, dollars: 2, wallClockMs: 300_000 },
154
- design_qa: { tokens: 60_000, dollars: 1.5, wallClockMs: 300_000 },
151
+ orchestrator: { tokens: 400_000, dollars: 8, wallClockMs: 900_000 },
152
+ architect: { tokens: 200_000, dollars: 4, wallClockMs: 600_000 },
153
+ coder: { tokens: 400_000, dollars: 8, wallClockMs: 900_000 },
154
+ verifier: { tokens: 150_000, dollars: 3, wallClockMs: 600_000 },
155
+ reviewer: { tokens: 200_000, dollars: 4, wallClockMs: 600_000 },
156
+ researcher: { tokens: 150_000, dollars: 3, wallClockMs: 600_000 },
157
+ release: { tokens: 80_000, dollars: 2, wallClockMs: 300_000 },
158
+ devops: { tokens: 150_000, dollars: 3, wallClockMs: 600_000 },
159
+ design_qa: { tokens: 150_000, dollars: 3, wallClockMs: 600_000 },
155
160
  });
156
161
  /**
157
162
  * Resolve the effective budget for a dispatch by merging task overrides
@@ -5021,48 +5021,26 @@ function renderLocalSessionList(rows, projectSlug) {
5021
5021
  * 1 is reserved for the credential gate (engine_unavailable) so
5022
5022
  * existing shell wrappers that branch on "any non-zero" still work.
5023
5023
  */
5024
- const ENGINE_EXIT_CODES = {
5025
- done: 0,
5026
- failed: 8,
5027
- blocked: 9,
5028
- engine_unavailable: 1,
5029
- // PUGI-VERIFY-GATE: needs_verification is the engine telling the
5030
- // operator "you did not run a verification command — I cannot
5031
- // confirm correctness". CI scripts treating any non-zero as
5032
- // failure keep working; exit 2 historically means "misuse" (the
5033
- // engine completed but the operator missed a required step),
5034
- // which matches the semantic here.
5035
- needs_verification: 2,
5036
- };
5037
- /**
5038
- * PUGI-VERIFY-GATE Codex dogfood 2026-06-04 surfaced a P0 where
5039
- * `pugi code` returned exit 0 while npm test failed. The spec
5040
- * locks the contract to exit 1 on a verification failure AND exit
5041
- * 2 on `needs_verification`. Legacy callers reading exit 8/9 still
5042
- * see "any non-zero = failure" but the new codes (1/2) are the
5043
- * authoritative signal for the verification gate.
5044
- *
5045
- * The override fires when `result.status` carries the new
5046
- * `needs_verification` literal OR when `result.unverifiedReason`
5047
- * is set to `verification_command_failed` — the latter pins exit
5048
- * 1 even if a future producer left `status: 'failed'` while
5049
- * inferring the cause from the reason field.
5050
- */
5051
- function resolveEngineExitCode(result) {
5052
- if (result.status === 'needs_verification') {
5053
- return ENGINE_EXIT_CODES.needs_verification;
5054
- }
5055
- if (result.unverifiedReason === 'verification_command_failed') {
5056
- // Spec: verified=false because a verification command failed
5057
- // maps to CLI exit 1 (clear "this is broken" signal).
5058
- return 1;
5059
- }
5060
- if (result.status === 'done')
5061
- return ENGINE_EXIT_CODES.done;
5062
- if (result.status === 'failed')
5063
- return ENGINE_EXIT_CODES.failed;
5064
- return ENGINE_EXIT_CODES.blocked;
5065
- }
5024
+ // PUGI-VERIFY-GATE - Codex dogfood 2026-06-04 surfaced a P0 where
5025
+ // `pugi code` returned exit 0 while npm test failed. The spec locks
5026
+ // the contract to exit 1 on a verification failure AND exit 2 on
5027
+ // `needs_verification`. Legacy callers reading exit 8/9 still see
5028
+ // "any non-zero = failure" but the new codes (1/2) are the
5029
+ // authoritative signal for the verification gate.
5030
+ //
5031
+ // Phase 1 (PUGI-299) - runtime invariant additions:
5032
+ // 3. `verificationFailures` non-empty array exit 1
5033
+ // 4. `verified === false && status === 'done'` exit 2
5034
+ //
5035
+ // The two new rules are defensive: today `computeVerificationOutcome`
5036
+ // always populates `unverifiedReason` (rule 2) or downgrades the
5037
+ // status (rule 1) so the new branches never fire, but the invariant
5038
+ // locks the contract for future engine adapters.
5039
+ //
5040
+ // Implementation lives in `engine-exit-code.ts` so the runtime spec
5041
+ // (`test/engine-exit-code.spec.ts`) can exercise it without mounting
5042
+ // this 9700-LOC entry module.
5043
+ import { ENGINE_EXIT_CODES, resolveEngineExitCode, } from './engine-exit-code.js';
5066
5044
  function commandLabel(kind) {
5067
5045
  return kind === 'build_task' ? 'build' : kind;
5068
5046
  }
@@ -8132,5 +8110,12 @@ export const __test__ = {
8132
8110
  // env-driven $PUGI_HOME redirect) without going через the full
8133
8111
  // engine-task dispatch path.
8134
8112
  readPersistedContextTier,
8113
+ // Phase 1 (PUGI-299) - runtime verification gate exit-code resolver.
8114
+ // Re-exported from `engine-exit-code.ts` so the invariant spec can
8115
+ // assert that a `verificationFailures` non-empty array OR a
8116
+ // `verified=false` outcome MUST surface as a non-zero CLI exit
8117
+ // even when a malformed adapter forgets to flip the status.
8118
+ resolveEngineExitCode,
8119
+ ENGINE_EXIT_CODES,
8135
8120
  };
8136
8121
  //# sourceMappingURL=cli.js.map
@@ -0,0 +1,50 @@
1
+ /**
2
+ * engine-exit-code - Phase 1 verification gate invariant (PUGI-299).
3
+ *
4
+ * The runtime resolves the CLI exit code from the engine outcome via a
5
+ * deterministic ladder. Extracted from `cli.ts` so the invariant is
6
+ * exercisable from a focused spec without mounting the 9700-LOC entry
7
+ * module.
8
+ *
9
+ * Contract (in priority order):
10
+ * 1. `status === 'needs_verification'` → exit 2
11
+ * 2. `unverifiedReason === 'verification_command_failed'` → exit 1
12
+ * 3. `verificationFailures` is a non-empty array → exit 1
13
+ * 4. `verified === false && status === 'done'` → exit 2
14
+ * 5. `status === 'done'` → exit 0
15
+ * 6. `status === 'failed'` → exit 8
16
+ * 7. `status === 'blocked'` (catch-all) → exit 9
17
+ *
18
+ * The rule fires defensively: rules 3 + 4 cover producer bugs where a
19
+ * future engine adapter forgets to flip `unverifiedReason` or
20
+ * downgrade the status to `needs_verification`. Today
21
+ * `computeVerificationOutcome` always synthesises one of the two,
22
+ * but the invariant locks the contract.
23
+ */
24
+ export const ENGINE_EXIT_CODES = {
25
+ done: 0,
26
+ failed: 8,
27
+ blocked: 9,
28
+ engine_unavailable: 1,
29
+ needs_verification: 2,
30
+ };
31
+ export function resolveEngineExitCode(result) {
32
+ if (result.status === 'needs_verification') {
33
+ return ENGINE_EXIT_CODES.needs_verification;
34
+ }
35
+ if (result.unverifiedReason === 'verification_command_failed') {
36
+ return 1;
37
+ }
38
+ if (Array.isArray(result.verificationFailures) && result.verificationFailures.length > 0) {
39
+ return 1;
40
+ }
41
+ if (result.verified === false && result.status === 'done') {
42
+ return ENGINE_EXIT_CODES.needs_verification;
43
+ }
44
+ if (result.status === 'done')
45
+ return ENGINE_EXIT_CODES.done;
46
+ if (result.status === 'failed')
47
+ return ENGINE_EXIT_CODES.failed;
48
+ return ENGINE_EXIT_CODES.blocked;
49
+ }
50
+ //# sourceMappingURL=engine-exit-code.js.map
@@ -44,7 +44,7 @@ export function sanitizeSemver(raw) {
44
44
  * during import). When bumping the CLI version BOTH literals must be
45
45
  * updated; the release smoke-test (`pack:smoke`) verifies they agree.
46
46
  */
47
- export const PUGI_CLI_VERSION = sanitizeSemver('0.1.0-beta.96');
47
+ export const PUGI_CLI_VERSION = sanitizeSemver('0.1.0-beta.98');
48
48
  /**
49
49
  * Outbound: the CLI's installed semver. Read at request time by
50
50
  * `version-interceptor.ts` and injected on every `fetch` call.