@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.
- package/CHANGELOG.md +935 -6
- package/README.md +55 -0
- package/dist/src/adapters/council/openai.js +12 -6
- package/dist/src/adapters/deploy/_http.d.ts +43 -0
- package/dist/src/adapters/deploy/_http.js +99 -0
- package/dist/src/adapters/deploy/fly.d.ts +206 -0
- package/dist/src/adapters/deploy/fly.js +696 -0
- package/dist/src/adapters/deploy/index.d.ts +2 -0
- package/dist/src/adapters/deploy/index.js +33 -0
- package/dist/src/adapters/deploy/render.d.ts +181 -0
- package/dist/src/adapters/deploy/render.js +550 -0
- package/dist/src/adapters/deploy/types.d.ts +67 -3
- package/dist/src/adapters/deploy/vercel.d.ts +17 -1
- package/dist/src/adapters/deploy/vercel.js +29 -49
- package/dist/src/adapters/pricing.d.ts +36 -0
- package/dist/src/adapters/pricing.js +40 -0
- package/dist/src/adapters/review-engine/codex.js +10 -7
- package/dist/src/cli/autopilot.d.ts +71 -0
- package/dist/src/cli/autopilot.js +735 -0
- package/dist/src/cli/brainstorm.d.ts +23 -0
- package/dist/src/cli/brainstorm.js +131 -0
- package/dist/src/cli/costs.d.ts +15 -1
- package/dist/src/cli/costs.js +99 -10
- package/dist/src/cli/deploy.d.ts +3 -3
- package/dist/src/cli/deploy.js +34 -9
- package/dist/src/cli/fix.d.ts +18 -0
- package/dist/src/cli/fix.js +105 -11
- package/dist/src/cli/help-text.d.ts +52 -0
- package/dist/src/cli/help-text.js +400 -0
- package/dist/src/cli/implement.d.ts +91 -0
- package/dist/src/cli/implement.js +196 -0
- package/dist/src/cli/index.js +719 -245
- package/dist/src/cli/json-envelope.d.ts +187 -0
- package/dist/src/cli/json-envelope.js +270 -0
- package/dist/src/cli/json-mode.d.ts +33 -0
- package/dist/src/cli/json-mode.js +201 -0
- package/dist/src/cli/migrate.d.ts +111 -0
- package/dist/src/cli/migrate.js +305 -0
- package/dist/src/cli/plan.d.ts +81 -0
- package/dist/src/cli/plan.js +149 -0
- package/dist/src/cli/pr.d.ts +106 -0
- package/dist/src/cli/pr.js +191 -19
- package/dist/src/cli/preflight.js +26 -0
- package/dist/src/cli/review.d.ts +27 -0
- package/dist/src/cli/review.js +126 -0
- package/dist/src/cli/runs-watch-renderer.d.ts +45 -0
- package/dist/src/cli/runs-watch-renderer.js +275 -0
- package/dist/src/cli/runs-watch.d.ts +41 -0
- package/dist/src/cli/runs-watch.js +395 -0
- package/dist/src/cli/runs.d.ts +122 -0
- package/dist/src/cli/runs.js +902 -0
- package/dist/src/cli/scan.d.ts +93 -0
- package/dist/src/cli/scan.js +166 -40
- package/dist/src/cli/spec.d.ts +66 -0
- package/dist/src/cli/spec.js +132 -0
- package/dist/src/cli/validate.d.ts +29 -0
- package/dist/src/cli/validate.js +131 -0
- package/dist/src/core/config/schema.d.ts +9 -0
- package/dist/src/core/config/schema.js +7 -0
- package/dist/src/core/config/types.d.ts +11 -0
- package/dist/src/core/council/runner.d.ts +10 -1
- package/dist/src/core/council/runner.js +25 -3
- package/dist/src/core/council/types.d.ts +7 -0
- package/dist/src/core/errors.d.ts +1 -1
- package/dist/src/core/errors.js +11 -0
- package/dist/src/core/logging/redaction.d.ts +13 -0
- package/dist/src/core/logging/redaction.js +20 -0
- package/dist/src/core/migrate/schema-validator.js +15 -1
- package/dist/src/core/phases/static-rules.d.ts +5 -1
- package/dist/src/core/phases/static-rules.js +2 -5
- package/dist/src/core/run-state/budget.d.ts +88 -0
- package/dist/src/core/run-state/budget.js +141 -0
- package/dist/src/core/run-state/cli-internal.d.ts +21 -0
- package/dist/src/core/run-state/cli-internal.js +174 -0
- package/dist/src/core/run-state/events.d.ts +59 -0
- package/dist/src/core/run-state/events.js +504 -0
- package/dist/src/core/run-state/lock.d.ts +61 -0
- package/dist/src/core/run-state/lock.js +206 -0
- package/dist/src/core/run-state/phase-context.d.ts +60 -0
- package/dist/src/core/run-state/phase-context.js +108 -0
- package/dist/src/core/run-state/phase-registry.d.ts +137 -0
- package/dist/src/core/run-state/phase-registry.js +162 -0
- package/dist/src/core/run-state/phase-runner.d.ts +80 -0
- package/dist/src/core/run-state/phase-runner.js +447 -0
- package/dist/src/core/run-state/provider-readback.d.ts +130 -0
- package/dist/src/core/run-state/provider-readback.js +426 -0
- package/dist/src/core/run-state/replay-decision.d.ts +69 -0
- package/dist/src/core/run-state/replay-decision.js +144 -0
- package/dist/src/core/run-state/resolve-engine.d.ts +100 -0
- package/dist/src/core/run-state/resolve-engine.js +190 -0
- package/dist/src/core/run-state/resume-preflight.d.ts +66 -0
- package/dist/src/core/run-state/resume-preflight.js +116 -0
- package/dist/src/core/run-state/run-phase-with-lifecycle.d.ts +73 -0
- package/dist/src/core/run-state/run-phase-with-lifecycle.js +186 -0
- package/dist/src/core/run-state/runs.d.ts +57 -0
- package/dist/src/core/run-state/runs.js +288 -0
- package/dist/src/core/run-state/snapshot.d.ts +14 -0
- package/dist/src/core/run-state/snapshot.js +114 -0
- package/dist/src/core/run-state/state.d.ts +40 -0
- package/dist/src/core/run-state/state.js +164 -0
- package/dist/src/core/run-state/types.d.ts +278 -0
- package/dist/src/core/run-state/types.js +13 -0
- package/dist/src/core/run-state/ulid.d.ts +11 -0
- package/dist/src/core/run-state/ulid.js +95 -0
- package/dist/src/core/schema-alignment/extractor/index.d.ts +1 -1
- package/dist/src/core/schema-alignment/extractor/index.js +2 -2
- package/dist/src/core/schema-alignment/extractor/prisma.d.ts +13 -1
- package/dist/src/core/schema-alignment/extractor/prisma.js +65 -10
- package/dist/src/core/schema-alignment/git-history.d.ts +19 -0
- package/dist/src/core/schema-alignment/git-history.js +53 -0
- package/dist/src/core/static-rules/rules/brand-tokens.js +2 -2
- package/dist/src/core/static-rules/rules/schema-alignment.js +14 -4
- package/package.json +2 -1
- package/scripts/autoregress.ts +1 -1
- package/skills/claude-autopilot.md +1 -1
- package/skills/make-interfaces-feel-better/SKILL.md +104 -0
- package/skills/simplify-ui/SKILL.md +103 -0
- package/skills/ui/SKILL.md +117 -0
- 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
|