@delegance/claude-autopilot 5.5.2 → 6.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/CHANGELOG.md +935 -6
  2. package/README.md +55 -0
  3. package/dist/src/adapters/council/openai.js +12 -6
  4. package/dist/src/adapters/deploy/_http.d.ts +43 -0
  5. package/dist/src/adapters/deploy/_http.js +99 -0
  6. package/dist/src/adapters/deploy/fly.d.ts +206 -0
  7. package/dist/src/adapters/deploy/fly.js +696 -0
  8. package/dist/src/adapters/deploy/index.d.ts +2 -0
  9. package/dist/src/adapters/deploy/index.js +33 -0
  10. package/dist/src/adapters/deploy/render.d.ts +181 -0
  11. package/dist/src/adapters/deploy/render.js +550 -0
  12. package/dist/src/adapters/deploy/types.d.ts +67 -3
  13. package/dist/src/adapters/deploy/vercel.d.ts +17 -1
  14. package/dist/src/adapters/deploy/vercel.js +29 -49
  15. package/dist/src/adapters/pricing.d.ts +36 -0
  16. package/dist/src/adapters/pricing.js +40 -0
  17. package/dist/src/adapters/review-engine/codex.js +10 -7
  18. package/dist/src/cli/autopilot.d.ts +71 -0
  19. package/dist/src/cli/autopilot.js +735 -0
  20. package/dist/src/cli/brainstorm.d.ts +23 -0
  21. package/dist/src/cli/brainstorm.js +131 -0
  22. package/dist/src/cli/costs.d.ts +15 -1
  23. package/dist/src/cli/costs.js +99 -10
  24. package/dist/src/cli/deploy.d.ts +3 -3
  25. package/dist/src/cli/deploy.js +34 -9
  26. package/dist/src/cli/fix.d.ts +18 -0
  27. package/dist/src/cli/fix.js +105 -11
  28. package/dist/src/cli/help-text.d.ts +52 -0
  29. package/dist/src/cli/help-text.js +400 -0
  30. package/dist/src/cli/implement.d.ts +91 -0
  31. package/dist/src/cli/implement.js +196 -0
  32. package/dist/src/cli/index.js +719 -245
  33. package/dist/src/cli/json-envelope.d.ts +187 -0
  34. package/dist/src/cli/json-envelope.js +270 -0
  35. package/dist/src/cli/json-mode.d.ts +33 -0
  36. package/dist/src/cli/json-mode.js +201 -0
  37. package/dist/src/cli/migrate.d.ts +111 -0
  38. package/dist/src/cli/migrate.js +305 -0
  39. package/dist/src/cli/plan.d.ts +81 -0
  40. package/dist/src/cli/plan.js +149 -0
  41. package/dist/src/cli/pr.d.ts +106 -0
  42. package/dist/src/cli/pr.js +191 -19
  43. package/dist/src/cli/preflight.js +26 -0
  44. package/dist/src/cli/review.d.ts +27 -0
  45. package/dist/src/cli/review.js +126 -0
  46. package/dist/src/cli/runs-watch-renderer.d.ts +45 -0
  47. package/dist/src/cli/runs-watch-renderer.js +275 -0
  48. package/dist/src/cli/runs-watch.d.ts +41 -0
  49. package/dist/src/cli/runs-watch.js +395 -0
  50. package/dist/src/cli/runs.d.ts +122 -0
  51. package/dist/src/cli/runs.js +902 -0
  52. package/dist/src/cli/scan.d.ts +93 -0
  53. package/dist/src/cli/scan.js +166 -40
  54. package/dist/src/cli/spec.d.ts +66 -0
  55. package/dist/src/cli/spec.js +132 -0
  56. package/dist/src/cli/validate.d.ts +29 -0
  57. package/dist/src/cli/validate.js +131 -0
  58. package/dist/src/core/config/schema.d.ts +9 -0
  59. package/dist/src/core/config/schema.js +7 -0
  60. package/dist/src/core/config/types.d.ts +11 -0
  61. package/dist/src/core/council/runner.d.ts +10 -1
  62. package/dist/src/core/council/runner.js +25 -3
  63. package/dist/src/core/council/types.d.ts +7 -0
  64. package/dist/src/core/errors.d.ts +1 -1
  65. package/dist/src/core/errors.js +11 -0
  66. package/dist/src/core/logging/redaction.d.ts +13 -0
  67. package/dist/src/core/logging/redaction.js +20 -0
  68. package/dist/src/core/migrate/schema-validator.js +15 -1
  69. package/dist/src/core/phases/static-rules.d.ts +5 -1
  70. package/dist/src/core/phases/static-rules.js +2 -5
  71. package/dist/src/core/run-state/budget.d.ts +88 -0
  72. package/dist/src/core/run-state/budget.js +141 -0
  73. package/dist/src/core/run-state/cli-internal.d.ts +21 -0
  74. package/dist/src/core/run-state/cli-internal.js +174 -0
  75. package/dist/src/core/run-state/events.d.ts +59 -0
  76. package/dist/src/core/run-state/events.js +504 -0
  77. package/dist/src/core/run-state/lock.d.ts +61 -0
  78. package/dist/src/core/run-state/lock.js +206 -0
  79. package/dist/src/core/run-state/phase-context.d.ts +60 -0
  80. package/dist/src/core/run-state/phase-context.js +108 -0
  81. package/dist/src/core/run-state/phase-registry.d.ts +137 -0
  82. package/dist/src/core/run-state/phase-registry.js +162 -0
  83. package/dist/src/core/run-state/phase-runner.d.ts +80 -0
  84. package/dist/src/core/run-state/phase-runner.js +447 -0
  85. package/dist/src/core/run-state/provider-readback.d.ts +130 -0
  86. package/dist/src/core/run-state/provider-readback.js +426 -0
  87. package/dist/src/core/run-state/replay-decision.d.ts +69 -0
  88. package/dist/src/core/run-state/replay-decision.js +144 -0
  89. package/dist/src/core/run-state/resolve-engine.d.ts +100 -0
  90. package/dist/src/core/run-state/resolve-engine.js +190 -0
  91. package/dist/src/core/run-state/resume-preflight.d.ts +66 -0
  92. package/dist/src/core/run-state/resume-preflight.js +116 -0
  93. package/dist/src/core/run-state/run-phase-with-lifecycle.d.ts +73 -0
  94. package/dist/src/core/run-state/run-phase-with-lifecycle.js +186 -0
  95. package/dist/src/core/run-state/runs.d.ts +57 -0
  96. package/dist/src/core/run-state/runs.js +288 -0
  97. package/dist/src/core/run-state/snapshot.d.ts +14 -0
  98. package/dist/src/core/run-state/snapshot.js +114 -0
  99. package/dist/src/core/run-state/state.d.ts +40 -0
  100. package/dist/src/core/run-state/state.js +164 -0
  101. package/dist/src/core/run-state/types.d.ts +278 -0
  102. package/dist/src/core/run-state/types.js +13 -0
  103. package/dist/src/core/run-state/ulid.d.ts +11 -0
  104. package/dist/src/core/run-state/ulid.js +95 -0
  105. package/dist/src/core/schema-alignment/extractor/index.d.ts +1 -1
  106. package/dist/src/core/schema-alignment/extractor/index.js +2 -2
  107. package/dist/src/core/schema-alignment/extractor/prisma.d.ts +13 -1
  108. package/dist/src/core/schema-alignment/extractor/prisma.js +65 -10
  109. package/dist/src/core/schema-alignment/git-history.d.ts +19 -0
  110. package/dist/src/core/schema-alignment/git-history.js +53 -0
  111. package/dist/src/core/static-rules/rules/brand-tokens.js +2 -2
  112. package/dist/src/core/static-rules/rules/schema-alignment.js +14 -4
  113. package/package.json +2 -1
  114. package/scripts/autoregress.ts +1 -1
  115. package/skills/claude-autopilot.md +1 -1
  116. package/skills/make-interfaces-feel-better/SKILL.md +104 -0
  117. package/skills/simplify-ui/SKILL.md +103 -0
  118. package/skills/ui/SKILL.md +117 -0
  119. package/skills/ui-ux-pro-max/SKILL.md +90 -0
@@ -0,0 +1,187 @@
1
+ import type { RunEvent, RunEventInput } from '../core/run-state/types.ts';
2
+ /** Envelope schema version. Bumped on breaking changes to JsonEnvelope shape.
3
+ * Mirrors RUN_STATE_SCHEMA_VERSION but is independent — events can change
4
+ * without forcing the envelope shape to change, and vice versa. */
5
+ export declare const JSON_ENVELOPE_SCHEMA_VERSION: 1;
6
+ export type JsonEnvelopeSchemaVersion = typeof JSON_ENVELOPE_SCHEMA_VERSION;
7
+ /** Custom exit code reserved for "interactive prompt would fire in --json
8
+ * mode". Keeps the contract observable to CI consumers — they can branch on
9
+ * this specific code to decide "needs-human, resume hint is in
10
+ * envelope.nextActions". 78 is borrowed from sysexits.h's EX_CONFIG; we
11
+ * redefine it as "needs-human in non-interactive mode" for our purposes. */
12
+ export declare const EXIT_NEEDS_HUMAN: 78;
13
+ /** Status surfaced in the envelope. Free-form so per-command results can use
14
+ * command-specific statuses (e.g. "applied" for migrate); the canonical
15
+ * three-state alphabet is `pass | fail | partial`. */
16
+ export type JsonEnvelopeStatus = 'pass' | 'fail' | 'partial' | string;
17
+ /** The canonical envelope shape. Command-specific result payloads ride on
18
+ * top via the index signature so individual commands can attach their own
19
+ * fields (`findings`, `runs`, `deploy`, etc.) without forcing a megaschema
20
+ * here. */
21
+ export interface JsonEnvelope {
22
+ /** Bumped when shape changes break consumers. Always 1 for Phase 5. */
23
+ schema_version: JsonEnvelopeSchemaVersion;
24
+ /** The CLI verb this envelope is for. e.g. "scan", "runs list", "deploy". */
25
+ command: string;
26
+ /** Optional run id — present for engine-aware verbs that produced or
27
+ * inspected a run. */
28
+ runId?: string;
29
+ /** Top-level pass/fail. Mirrors `exit === 0` for most verbs; some verbs
30
+ * use `partial` when they completed but with caveats. */
31
+ status: JsonEnvelopeStatus;
32
+ /** Wall-clock duration the verb took to execute, in milliseconds. */
33
+ durationMs: number;
34
+ /** Cumulative LLM spend during this verb. Optional — verbs that don't
35
+ * spend (doctor, runs list, etc.) omit the field entirely. */
36
+ costUSD?: number;
37
+ /** Process exit code. MUST equal what the CLI returns. */
38
+ exit: number;
39
+ /** Hints for resuming after a needs-human exit — surfaced in the envelope
40
+ * so CI can react without re-running the verb. Spec requires this on the
41
+ * EXIT_NEEDS_HUMAN path. */
42
+ nextActions?: string[];
43
+ /** Human-readable error code/message (e.g. when status === 'fail'). */
44
+ error?: string;
45
+ /** Free-form messages routed through console.* under JSON-mode discipline.
46
+ * Populated by json-mode.ts when stdout/stderr writes happen mid-verb so
47
+ * no bytes are lost; the envelope captures them as a typed channel
48
+ * instead of letting them pollute stdout. */
49
+ messages?: Array<{
50
+ level: 'log' | 'warn' | 'error' | 'info' | 'debug';
51
+ text: string;
52
+ }>;
53
+ /** Command-specific result payload. */
54
+ [key: string]: unknown;
55
+ }
56
+ export declare function stripAnsi(s: string): string;
57
+ /** Test seam — when set, emitEnvelope writes to this collector instead of
58
+ * process.stdout. installJsonModeChannelDiscipline uses the same seam for
59
+ * its inner shim's pass-through paths. Production code never sets this. */
60
+ export interface ChannelTestSink {
61
+ stdout: (line: string) => void;
62
+ stderr: (line: string) => void;
63
+ }
64
+ export declare function __setChannelTestSink(sink: ChannelTestSink | null): void;
65
+ export declare function __getChannelTestSink(): ChannelTestSink | null;
66
+ /** Emit the envelope as exactly one stdout line. The newline IS the
67
+ * separator — consumers calling `JSON.parse(stdout)` should split on `\n`
68
+ * if they spawn the CLI more than once. */
69
+ export declare function emitEnvelope(env: JsonEnvelope): void;
70
+ /** Emit a typed event as a single NDJSON line on stderr.
71
+ *
72
+ * In --json mode this is the ONLY thing that should appear on stderr (other
73
+ * than the universal envelope-on-stdout). The event is JSON.stringified
74
+ * whole — it carries its own seq / ts / runId / writerId from the appender
75
+ * if it came out of the run-state engine, or a partial shape if it's a
76
+ * CLI-synthetic warning routed via console.warn. */
77
+ export declare function emitStderrEvent(event: RunEvent | RunEventInput | Record<string, unknown>): void;
78
+ /** Channel options carried alongside per-verb opts. Uniform across every
79
+ * migrated verb so the dispatcher in index.ts can decide once. */
80
+ export interface ChannelOptions {
81
+ json: boolean;
82
+ /** True when --json is set, OR when stdin is non-TTY, OR when an explicit
83
+ * --non-interactive flag is set. Verbs use this to decide whether to
84
+ * prompt or hard-fail. */
85
+ nonInteractive: boolean;
86
+ }
87
+ /** Build a synthetic run.warning-shaped event for CLI-side warnings that
88
+ * predate the engine instrumentation. Has no runId / seq / writerId because
89
+ * the CLI invocation may not have a run attached. Consumers MAY still drop
90
+ * it; spec only requires the envelope-on-stdout discipline. */
91
+ export declare function syntheticRunWarning(message: string, details?: Record<string, unknown>): Record<string, unknown>;
92
+ /** Run an arbitrary CLI handler under --json channel discipline.
93
+ *
94
+ * The handler returns its exit code (the same shape every existing handler
95
+ * uses today). On the way out, we package up:
96
+ * - status (pass/fail derived from exit)
97
+ * - durationMs (wall clock from start)
98
+ * - messages[] (anything the handler wrote to stdout/stderr)
99
+ * - command-specific payload (whatever the handler returned via the
100
+ * `payload` callback if present, OR the last raw JSON envelope the
101
+ * handler emitted itself, whichever is more informative)
102
+ *
103
+ * This is the wrapper used by index.ts to migrate verbs one-by-one without
104
+ * rewriting their internals. Verbs that already emit a Phase 3 envelope
105
+ * (the `runs` verbs) bypass this wrapper since they already build their
106
+ * own envelope and can route directly to emitEnvelope.
107
+ *
108
+ * When `active` is false, this is a thin pass-through that just forwards
109
+ * the handler's exit code — text-mode behavior is unchanged. */
110
+ export interface RunUnderJsonModeOptions {
111
+ command: string;
112
+ active: boolean;
113
+ /** Optional callback that returns command-specific result fields to merge
114
+ * into the envelope. Called AFTER the handler completes. */
115
+ payload?: (capturedJsonStdout: unknown[]) => Record<string, unknown>;
116
+ /** Optional override for the envelope status. Default: derived from exit
117
+ * (0 → 'pass', otherwise 'fail'). */
118
+ statusFor?: (exit: number) => JsonEnvelopeStatus;
119
+ }
120
+ /** Bounded error-code enum. CI consumers MAY rely on these specific
121
+ * strings; new codes ship as minor versions of the envelope. The
122
+ * exit-code-to-errorCode mapping is documented adjacent to
123
+ * `computeAutopilotExitCode` so the contract is observable from a single
124
+ * place. */
125
+ export declare const AUTOPILOT_ERROR_CODES: readonly ["invalid_config", "budget_exceeded", "lock_held", "corrupted_state", "partial_write", "needs_human", "phase_failed", "internal_error"];
126
+ export type AutopilotErrorCode = typeof AUTOPILOT_ERROR_CODES[number];
127
+ /** Per-phase outcome surfaced inside the envelope. Mirrors the orchestrator
128
+ * internal `AutopilotPhaseSummary` but uses the `'success' | 'failed' |
129
+ * 'skipped-replay'` alphabet from the spec rather than the orchestrator's
130
+ * internal `'not-run'` placeholder. */
131
+ export interface AutopilotPhaseResult {
132
+ name: string;
133
+ status: 'success' | 'failed' | 'skipped-replay';
134
+ costUSD: number;
135
+ durationMs: number;
136
+ /** For skipped-replay phases (resume short-circuit). */
137
+ reason?: 'side-effect-already-applied' | 'idempotent-replay';
138
+ }
139
+ export interface AutopilotJsonResult {
140
+ runId: string | null;
141
+ status: 'success' | 'failed';
142
+ exitCode: 0 | 1 | 2 | 78;
143
+ phases: AutopilotPhaseResult[];
144
+ totalCostUSD: number;
145
+ durationMs: number;
146
+ errorCode?: AutopilotErrorCode;
147
+ errorMessage?: string;
148
+ failedAtPhase?: number;
149
+ failedPhaseName?: string;
150
+ }
151
+ /** Outer envelope shape (per spec). The autopilot envelope is pinned to
152
+ * `version: '1'` independently of the universal `JsonEnvelope`'s
153
+ * `schema_version` — the two evolve on separate cadences. */
154
+ export interface AutopilotJsonEnvelope {
155
+ version: '1';
156
+ verb: 'autopilot';
157
+ runId: string | null;
158
+ status: 'success' | 'failed';
159
+ exitCode: 0 | 1 | 2 | 78;
160
+ phases: AutopilotPhaseResult[];
161
+ totalCostUSD: number;
162
+ durationMs: number;
163
+ errorCode?: AutopilotErrorCode;
164
+ errorMessage?: string;
165
+ failedAtPhase?: number;
166
+ failedPhaseName?: string;
167
+ }
168
+ export declare function __resetAutopilotEnvelopeLatch(): void;
169
+ export declare function __isAutopilotEnvelopeWritten(): boolean;
170
+ /** Emit the outer JSON envelope on stdout. Idempotent — the second call is
171
+ * a no-op so the success path + a finalization-error fallback can both
172
+ * invoke this safely.
173
+ *
174
+ * ANSI codes are stripped from `errorMessage` defensively (the orchestrator
175
+ * builds it via interpolation and may include color codes from upstream
176
+ * errors).
177
+ *
178
+ * This function does NOT flush stdout. Callers in the orchestrator path
179
+ * should `await new Promise(r => process.stdout.write('', r))` after this
180
+ * call before invoking `process.exit` to guarantee bytes hit the pipe. */
181
+ export declare function writeAutopilotEnvelope(result: AutopilotJsonResult): void;
182
+ /** Translate the spec's exit-code-to-errorCode matrix into a deterministic
183
+ * number. CI scripts MUST be able to branch on this without re-reading
184
+ * the envelope. */
185
+ export declare function computeAutopilotExitCode(errorCode: AutopilotErrorCode | undefined): 0 | 1 | 2 | 78;
186
+ export declare function runUnderJsonMode(opts: RunUnderJsonModeOptions, handler: () => Promise<number | void>): Promise<number>;
187
+ //# sourceMappingURL=json-envelope.d.ts.map
@@ -0,0 +1,270 @@
1
+ // src/cli/json-envelope.ts
2
+ //
3
+ // v6 Phase 5 — typed JSON envelope + strict --json channel discipline helpers.
4
+ //
5
+ // Spec: docs/specs/v6-run-state-engine.md "CLI `--json` mode + strict channel
6
+ // discipline (Codex WARNING)". The contract:
7
+ //
8
+ // - When `--json` is set on ANY command, stdout receives EXACTLY ONE JSON
9
+ // envelope per command invocation. Nothing else.
10
+ // - stderr receives ONLY NDJSON event lines (one JSON object per line). No
11
+ // human-readable warnings, no progress bars, no color codes.
12
+ // - All warnings / prompts / human diagnostics route to typed events
13
+ // (run.warning, run.recovery, phase.needs-human, budget.check, ...).
14
+ // - Interactive prompts in --json mode hard-fail with exit:78 ("needs-human
15
+ // in non-interactive mode") and the envelope's nextActions field carries
16
+ // the resume hint.
17
+ //
18
+ // Phase 3 (`runs` verbs) shipped a v1 envelope without strict channel
19
+ // discipline; Phase 5 layers the discipline on top via `installJsonMode`
20
+ // in json-mode.ts. This module owns the pure types + emission helpers; the
21
+ // hook installer lives next door so the type surface here stays trivially
22
+ // importable from anywhere (helpers, formatters, tests) without dragging in
23
+ // the side-effecting console-wrap.
24
+ /** Envelope schema version. Bumped on breaking changes to JsonEnvelope shape.
25
+ * Mirrors RUN_STATE_SCHEMA_VERSION but is independent — events can change
26
+ * without forcing the envelope shape to change, and vice versa. */
27
+ export const JSON_ENVELOPE_SCHEMA_VERSION = 1;
28
+ /** Custom exit code reserved for "interactive prompt would fire in --json
29
+ * mode". Keeps the contract observable to CI consumers — they can branch on
30
+ * this specific code to decide "needs-human, resume hint is in
31
+ * envelope.nextActions". 78 is borrowed from sysexits.h's EX_CONFIG; we
32
+ * redefine it as "needs-human in non-interactive mode" for our purposes. */
33
+ export const EXIT_NEEDS_HUMAN = 78;
34
+ /** Strip all ANSI color escape sequences from a string. Color codes in
35
+ * --json mode are a contract violation (they're text noise inside what's
36
+ * supposed to be a clean machine-readable line). */
37
+ const ANSI_RE = /\x1b\[[0-9;]*m/g;
38
+ export function stripAnsi(s) {
39
+ return s.replace(ANSI_RE, '');
40
+ }
41
+ let _testSink = null;
42
+ export function __setChannelTestSink(sink) {
43
+ _testSink = sink;
44
+ }
45
+ export function __getChannelTestSink() {
46
+ return _testSink;
47
+ }
48
+ /** Emit the envelope as exactly one stdout line. The newline IS the
49
+ * separator — consumers calling `JSON.parse(stdout)` should split on `\n`
50
+ * if they spawn the CLI more than once. */
51
+ export function emitEnvelope(env) {
52
+ // Defensive: validate the shape so we fail loud rather than emitting a
53
+ // malformed envelope. This catches handler bugs at the seam where text
54
+ // would have been ok but JSON consumers will crash on JSON.parse.
55
+ if (env.schema_version !== JSON_ENVELOPE_SCHEMA_VERSION) {
56
+ throw new Error(`[json-envelope] schema_version must be ${JSON_ENVELOPE_SCHEMA_VERSION}, got ${env.schema_version}`);
57
+ }
58
+ if (typeof env.command !== 'string' || env.command.length === 0) {
59
+ throw new Error('[json-envelope] command must be a non-empty string');
60
+ }
61
+ if (typeof env.exit !== 'number' || !Number.isInteger(env.exit)) {
62
+ throw new Error('[json-envelope] exit must be an integer');
63
+ }
64
+ if (typeof env.durationMs !== 'number' || env.durationMs < 0) {
65
+ throw new Error('[json-envelope] durationMs must be a non-negative number');
66
+ }
67
+ // Strip ANSI from any error / message text — defence in depth against a
68
+ // handler that built its `error` string by interpolating a colorized
69
+ // formatted message.
70
+ const cleaned = { ...env };
71
+ if (typeof cleaned.error === 'string')
72
+ cleaned.error = stripAnsi(cleaned.error);
73
+ if (Array.isArray(cleaned.messages)) {
74
+ cleaned.messages = cleaned.messages.map(m => ({ ...m, text: stripAnsi(m.text) }));
75
+ }
76
+ const line = JSON.stringify(cleaned) + '\n';
77
+ if (_testSink)
78
+ _testSink.stdout(line);
79
+ else
80
+ process.stdout.write(line);
81
+ }
82
+ /** Emit a typed event as a single NDJSON line on stderr.
83
+ *
84
+ * In --json mode this is the ONLY thing that should appear on stderr (other
85
+ * than the universal envelope-on-stdout). The event is JSON.stringified
86
+ * whole — it carries its own seq / ts / runId / writerId from the appender
87
+ * if it came out of the run-state engine, or a partial shape if it's a
88
+ * CLI-synthetic warning routed via console.warn. */
89
+ export function emitStderrEvent(event) {
90
+ const line = JSON.stringify(event) + '\n';
91
+ if (_testSink)
92
+ _testSink.stderr(line);
93
+ else
94
+ process.stderr.write(line);
95
+ }
96
+ /** Build a synthetic run.warning-shaped event for CLI-side warnings that
97
+ * predate the engine instrumentation. Has no runId / seq / writerId because
98
+ * the CLI invocation may not have a run attached. Consumers MAY still drop
99
+ * it; spec only requires the envelope-on-stdout discipline. */
100
+ export function syntheticRunWarning(message, details) {
101
+ return {
102
+ schema_version: 1,
103
+ ts: new Date().toISOString(),
104
+ event: 'run.warning',
105
+ message: stripAnsi(message),
106
+ ...(details ? { details } : {}),
107
+ };
108
+ }
109
+ // ----------------------------------------------------------------------------
110
+ // v6.2.2 — autopilot --json envelope
111
+ //
112
+ // The autopilot orchestrator emits its OWN envelope shape (different from
113
+ // the universal `JsonEnvelope` Phase 5 wrapper) because it carries cross-
114
+ // phase result fields (`phases`, `failedAtPhase`, `failedPhaseName`,
115
+ // `totalCostUSD`) that the per-verb wrapper has no concept of.
116
+ //
117
+ // Per spec docs/specs/v6.2.2-json-envelope-and-docs.md:
118
+ // - exactly ONE envelope on stdout per autopilot invocation
119
+ // - NDJSON events continue to flow to stderr (existing v6 Phase 5
120
+ // channel discipline preserved)
121
+ // - bounded `AutopilotErrorCode` enum so CI consumers can branch on
122
+ // specific strings (codex NOTE #5)
123
+ // - single-write latch + uncaughtException handlers so the envelope is
124
+ // emitted exactly once even on async unhandled rejections (codex
125
+ // WARNING #2)
126
+ // ----------------------------------------------------------------------------
127
+ /** Bounded error-code enum. CI consumers MAY rely on these specific
128
+ * strings; new codes ship as minor versions of the envelope. The
129
+ * exit-code-to-errorCode mapping is documented adjacent to
130
+ * `computeAutopilotExitCode` so the contract is observable from a single
131
+ * place. */
132
+ export const AUTOPILOT_ERROR_CODES = [
133
+ 'invalid_config', // pre-run validation, --phases parse, engine off
134
+ 'budget_exceeded', // run-scope budget cap hit
135
+ 'lock_held', // engine lock collision
136
+ 'corrupted_state', // state.json mismatch / schemaVersion out of range
137
+ 'partial_write', // events.ndjson torn write
138
+ 'needs_human', // interactive prompt would fire in --json
139
+ 'phase_failed', // generic phase failure (LLM error, network, etc.)
140
+ 'internal_error', // catch-all for the uncaughtException handler
141
+ ];
142
+ // ---------------------------------------------------------------------------
143
+ // Single-write latch (codex WARNING #2). Module-scoped boolean. Flipped by
144
+ // `writeAutopilotEnvelope` BEFORE writing so subsequent calls (success path
145
+ // + uncaughtException handler racing) no-op. Tests can reset via
146
+ // `__resetAutopilotEnvelopeLatch()`.
147
+ // ---------------------------------------------------------------------------
148
+ let _autopilotEnvelopeWritten = false;
149
+ export function __resetAutopilotEnvelopeLatch() {
150
+ _autopilotEnvelopeWritten = false;
151
+ }
152
+ export function __isAutopilotEnvelopeWritten() {
153
+ return _autopilotEnvelopeWritten;
154
+ }
155
+ /** Emit the outer JSON envelope on stdout. Idempotent — the second call is
156
+ * a no-op so the success path + a finalization-error fallback can both
157
+ * invoke this safely.
158
+ *
159
+ * ANSI codes are stripped from `errorMessage` defensively (the orchestrator
160
+ * builds it via interpolation and may include color codes from upstream
161
+ * errors).
162
+ *
163
+ * This function does NOT flush stdout. Callers in the orchestrator path
164
+ * should `await new Promise(r => process.stdout.write('', r))` after this
165
+ * call before invoking `process.exit` to guarantee bytes hit the pipe. */
166
+ export function writeAutopilotEnvelope(result) {
167
+ if (_autopilotEnvelopeWritten)
168
+ return;
169
+ _autopilotEnvelopeWritten = true;
170
+ const env = {
171
+ version: '1',
172
+ verb: 'autopilot',
173
+ runId: result.runId,
174
+ status: result.status,
175
+ exitCode: result.exitCode,
176
+ phases: result.phases.map(p => ({ ...p })),
177
+ totalCostUSD: result.totalCostUSD,
178
+ durationMs: result.durationMs,
179
+ ...(result.errorCode !== undefined ? { errorCode: result.errorCode } : {}),
180
+ ...(result.errorMessage !== undefined
181
+ ? { errorMessage: stripAnsi(result.errorMessage) }
182
+ : {}),
183
+ ...(result.failedAtPhase !== undefined ? { failedAtPhase: result.failedAtPhase } : {}),
184
+ ...(result.failedPhaseName !== undefined ? { failedPhaseName: result.failedPhaseName } : {}),
185
+ };
186
+ const line = JSON.stringify(env) + '\n';
187
+ if (_testSink)
188
+ _testSink.stdout(line);
189
+ else
190
+ process.stdout.write(line);
191
+ }
192
+ /** Translate the spec's exit-code-to-errorCode matrix into a deterministic
193
+ * number. CI scripts MUST be able to branch on this without re-reading
194
+ * the envelope. */
195
+ export function computeAutopilotExitCode(errorCode) {
196
+ if (errorCode === undefined)
197
+ return 0;
198
+ switch (errorCode) {
199
+ case 'budget_exceeded':
200
+ case 'needs_human':
201
+ return 78;
202
+ case 'lock_held':
203
+ case 'corrupted_state':
204
+ case 'partial_write':
205
+ return 2;
206
+ case 'invalid_config':
207
+ case 'phase_failed':
208
+ case 'internal_error':
209
+ return 1;
210
+ default: {
211
+ const _exhaustive = errorCode;
212
+ void _exhaustive;
213
+ return 1;
214
+ }
215
+ }
216
+ }
217
+ export async function runUnderJsonMode(opts, handler) {
218
+ const { installJsonModeChannelDiscipline } = await import("./json-mode.js");
219
+ const start = Date.now();
220
+ const handle = installJsonModeChannelDiscipline({ active: opts.active });
221
+ let exit = 0;
222
+ let caughtError;
223
+ try {
224
+ const result = await handler();
225
+ exit = typeof result === 'number' ? result : 0;
226
+ }
227
+ catch (err) {
228
+ caughtError = err;
229
+ exit = 1;
230
+ }
231
+ finally {
232
+ handle.restore();
233
+ }
234
+ if (!opts.active) {
235
+ if (caughtError)
236
+ throw caughtError;
237
+ return exit;
238
+ }
239
+ const status = opts.statusFor ? opts.statusFor(exit) : (exit === 0 ? 'pass' : 'fail');
240
+ // Prefer the handler's own JSON envelope (last one wins) as the payload
241
+ // base — handlers that already emit Phase 3 envelopes get their fields
242
+ // surfaced through the wrapper's envelope.
243
+ let basePayload = {};
244
+ const lastJson = handle.capturedJsonStdout[handle.capturedJsonStdout.length - 1];
245
+ if (lastJson && typeof lastJson === 'object') {
246
+ basePayload = { ...lastJson };
247
+ // Strip envelope-internal fields the handler may have set so the wrapper
248
+ // doesn't double them up; the wrapper's own values are authoritative.
249
+ delete basePayload.schema_version;
250
+ delete basePayload.command;
251
+ delete basePayload.status;
252
+ delete basePayload.exit;
253
+ delete basePayload.durationMs;
254
+ }
255
+ const extra = opts.payload ? opts.payload(handle.capturedJsonStdout) : {};
256
+ const env = {
257
+ schema_version: JSON_ENVELOPE_SCHEMA_VERSION,
258
+ command: opts.command,
259
+ status,
260
+ exit,
261
+ durationMs: Date.now() - start,
262
+ ...basePayload,
263
+ ...extra,
264
+ ...(handle.capturedMessages.length > 0 ? { messages: handle.capturedMessages } : {}),
265
+ ...(caughtError instanceof Error ? { error: caughtError.message } : {}),
266
+ };
267
+ emitEnvelope(env);
268
+ return exit;
269
+ }
270
+ //# sourceMappingURL=json-envelope.js.map
@@ -0,0 +1,33 @@
1
+ export interface CapturedMessage {
2
+ level: 'log' | 'warn' | 'error' | 'info' | 'debug';
3
+ text: string;
4
+ }
5
+ export interface JsonModeChannelHandle {
6
+ /** Restore stdout/stderr/console to pre-install state. Idempotent. */
7
+ restore: () => void;
8
+ /** All non-JSON stdout writes captured during the handler's lifetime. */
9
+ capturedMessages: CapturedMessage[];
10
+ /** All raw JSON-parseable strings written to stdout, in order. The
11
+ * dispatcher's convention: if a handler already emits its own envelope,
12
+ * the LAST one wins and is reused as the envelope's command-specific
13
+ * payload base. */
14
+ capturedJsonStdout: unknown[];
15
+ }
16
+ /** Install the channel-discipline shim. Returns a handle the caller uses to
17
+ * collect captured output and to restore the original state.
18
+ *
19
+ * Safe to call when --json is OFF — the function is a no-op in that case
20
+ * (returns a handle whose restore() does nothing and whose buffers stay
21
+ * empty). Callers should still respect the active flag and skip the
22
+ * envelope emission in text mode. */
23
+ export declare function installJsonModeChannelDiscipline(opts?: {
24
+ active: boolean;
25
+ }): JsonModeChannelHandle;
26
+ /** Build a ChannelOptions value from the parsed --json flag plus
27
+ * process.stdin TTY-ness. The caller (dispatcher) usually wants the
28
+ * computed nonInteractive flag for prompt-or-hard-fail decisions. */
29
+ export declare function computeChannelOptions(json: boolean): {
30
+ json: boolean;
31
+ nonInteractive: boolean;
32
+ };
33
+ //# sourceMappingURL=json-mode.d.ts.map