@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,100 @@
|
|
|
1
|
+
/** What the resolver decided plus the rationale. The `source` field tells
|
|
2
|
+
* callers which precedence layer won so they can surface it in diagnostics
|
|
3
|
+
* (`runs show`, `--json` envelopes, etc.). */
|
|
4
|
+
export interface ResolveEngineResult {
|
|
5
|
+
/** Final decision — whether the engine runs for this invocation. */
|
|
6
|
+
enabled: boolean;
|
|
7
|
+
/** Which precedence layer produced the decision. */
|
|
8
|
+
source: 'cli' | 'env' | 'config' | 'default';
|
|
9
|
+
/** Human-readable explanation, suitable for run.warning details / verbose
|
|
10
|
+
* CLI output / debug logs. */
|
|
11
|
+
reason: string;
|
|
12
|
+
/** When `source === 'env'` and the env value was malformed, this carries the
|
|
13
|
+
* raw string so the caller can route a `run.warning` describing the
|
|
14
|
+
* fallthrough. Absent on the happy path. */
|
|
15
|
+
invalidEnvValue?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface ResolveEngineOptions {
|
|
18
|
+
/** True if `--engine` was passed; false if `--no-engine`; undefined if
|
|
19
|
+
* neither flag was present. The CLI parser is responsible for rejecting
|
|
20
|
+
* the case where BOTH flags are passed before this function is called. */
|
|
21
|
+
cliEngine?: boolean;
|
|
22
|
+
/** Raw value of `process.env.CLAUDE_AUTOPILOT_ENGINE`. Undefined if the
|
|
23
|
+
* variable is unset. Empty string is treated as unset (matches Node's
|
|
24
|
+
* convention). Case-insensitive parsing of the string value. */
|
|
25
|
+
envValue?: string;
|
|
26
|
+
/** Value of `engine.enabled` from guardrail.config.yaml, or undefined if
|
|
27
|
+
* the config file is missing / does not declare the key. */
|
|
28
|
+
configEnabled?: boolean;
|
|
29
|
+
/** Built-in default. v6.0: false. Tests pin this explicitly to exercise
|
|
30
|
+
* the v6.1-flip behavior without needing to bump the constant globally. */
|
|
31
|
+
builtInDefault?: boolean;
|
|
32
|
+
}
|
|
33
|
+
/** v6.1+ ships with the engine ON by default — flipped from the v6.0
|
|
34
|
+
* default (`false`) per `docs/specs/v6.1-default-flip.md`. Exported so
|
|
35
|
+
* tests / future releases can pin a known value. */
|
|
36
|
+
export declare const ENGINE_DEFAULT_V6_1: true;
|
|
37
|
+
/** Historical v6.0 default. Preserved verbatim — its semantic meaning
|
|
38
|
+
* ("the v6.0 default was off") doesn't change just because the active
|
|
39
|
+
* default flipped. Out-of-tree consumers that pinned this constant get
|
|
40
|
+
* the value the name promises. Use `ENGINE_DEFAULT_V6_1` for the active
|
|
41
|
+
* default. Removed in v7.
|
|
42
|
+
* @deprecated Use `ENGINE_DEFAULT_V6_1` or omit `builtInDefault` to inherit
|
|
43
|
+
* the active default. */
|
|
44
|
+
export declare const ENGINE_DEFAULT_V6_0: false;
|
|
45
|
+
/** Parse a stringly-typed env value into a tri-state boolean.
|
|
46
|
+
* Accepts (case-insensitive): on, off, true, false, 1, 0, yes, no.
|
|
47
|
+
* Returns undefined for any other input INCLUDING empty / whitespace-only
|
|
48
|
+
* strings — that signals the caller to fall through to the next precedence
|
|
49
|
+
* layer. */
|
|
50
|
+
export declare function parseEngineEnvValue(raw: string | undefined): boolean | undefined;
|
|
51
|
+
/** Resolve whether the Run State Engine should run for this invocation.
|
|
52
|
+
* Pure function — does not touch process.env, fs, or anything I/O. */
|
|
53
|
+
export declare function resolveEngineEnabled(opts?: ResolveEngineOptions): ResolveEngineResult;
|
|
54
|
+
/** Stable copy emitted on stderr when a user explicitly opts out of the
|
|
55
|
+
* engine via `--no-engine`, `CLAUDE_AUTOPILOT_ENGINE=off`, or
|
|
56
|
+
* `engine.enabled: false`. v7 removes the escape hatch entirely.
|
|
57
|
+
*
|
|
58
|
+
* Exported for tests + downstream consumers (e.g. CI parsers) that want to
|
|
59
|
+
* match against the exact string. Kept on a single line so terminals don't
|
|
60
|
+
* wrap mid-message. */
|
|
61
|
+
export declare const ENGINE_OFF_DEPRECATION_MESSAGE = "[deprecation] --no-engine / engine.enabled: false will be removed in v7. Migrate to engine-on (default).";
|
|
62
|
+
/** Optional callback shape for the deprecation warner. Tests pass a capture
|
|
63
|
+
* function; production callers omit it and get the default `process.stderr`
|
|
64
|
+
* writer. Kept narrow (single-arg) so a `jest.fn` or a `(msg) => lines.push(msg)`
|
|
65
|
+
* array sink is trivially droppable. */
|
|
66
|
+
export type EngineDeprecationWarn = (message: string) => void;
|
|
67
|
+
/** Decide whether v6.1's `--no-engine` deprecation warning applies for a
|
|
68
|
+
* given resolver result. Returns `true` ONLY when the user explicitly
|
|
69
|
+
* opted out (via CLI flag, env var, or config) — never on the v6.1 default
|
|
70
|
+
* (which is `enabled: true`, so it can't trigger here anyway) and never
|
|
71
|
+
* when the engine is actually on. Pure: takes the resolver result, returns
|
|
72
|
+
* a boolean.
|
|
73
|
+
*
|
|
74
|
+
* Why this is a separate predicate (not collapsed into the warner): the
|
|
75
|
+
* CLI dispatcher wants to ALSO emit a typed `run.warning` event into a
|
|
76
|
+
* ledger when the engine ends up on but the resolver came from a layer
|
|
77
|
+
* that's about to be removed — except today, on v6.1, the only path that
|
|
78
|
+
* warns IS the "engine off, explicit opt-out" path. So the predicate
|
|
79
|
+
* collapses cleanly to that single condition. v7 removes both. */
|
|
80
|
+
export declare function shouldWarnEngineOffDeprecation(resolved: Pick<ResolveEngineResult, 'enabled' | 'source'>): boolean;
|
|
81
|
+
/** Emit the v6.1 `--no-engine` deprecation warning to stderr (or the
|
|
82
|
+
* supplied `warn` callback) when the resolver result indicates the user
|
|
83
|
+
* explicitly opted out of the engine. No-op when:
|
|
84
|
+
* - the engine is on (no opt-out happened);
|
|
85
|
+
* - the source is `'default'` (v6.1's flipped default = on, so a default
|
|
86
|
+
* result with `enabled: false` is impossible without a custom
|
|
87
|
+
* `builtInDefault` override — and even that path doesn't warn since
|
|
88
|
+
* it's not a user-driven opt-out).
|
|
89
|
+
*
|
|
90
|
+
* Pure-ish: side-effect is captured behind the optional `warn` callback so
|
|
91
|
+
* tests can assert on the message without spawning a subprocess. The
|
|
92
|
+
* default warner writes to `process.stderr` with a trailing newline.
|
|
93
|
+
*
|
|
94
|
+
* Returns `true` when the warning fired, `false` when it was a no-op. The
|
|
95
|
+
* return value is purely informational — callers can use it to decide
|
|
96
|
+
* whether to also append a `run.warning` event into a run ledger (only
|
|
97
|
+
* meaningful on the engine-on path; the v6.1 deprecation only fires on
|
|
98
|
+
* engine-off, where there's no run dir to write into). */
|
|
99
|
+
export declare function emitEngineOffDeprecationWarning(resolved: Pick<ResolveEngineResult, 'enabled' | 'source'>, warn?: EngineDeprecationWarn): boolean;
|
|
100
|
+
//# sourceMappingURL=resolve-engine.d.ts.map
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// src/core/run-state/resolve-engine.ts
|
|
2
|
+
//
|
|
3
|
+
// v6.0.1 — pure precedence resolver for whether the Run State Engine should
|
|
4
|
+
// run for a given CLI invocation. Spec: docs/specs/v6-run-state-engine.md
|
|
5
|
+
// "Migration path (v5.6 → v6) + precedence matrix" + docs/v6/migration-guide.md
|
|
6
|
+
// "How to opt in".
|
|
7
|
+
//
|
|
8
|
+
// v6.1 update — built-in default flipped from `false` → `true` per
|
|
9
|
+
// docs/specs/v6.1-default-flip.md. Users who opt out explicitly via
|
|
10
|
+
// `--no-engine`, `CLAUDE_AUTOPILOT_ENGINE=off|false|0|no`, or
|
|
11
|
+
// `engine.enabled: false` in `guardrail.config.yaml` keep the legacy
|
|
12
|
+
// (engine-off) behavior, but they now receive a deprecation warning —
|
|
13
|
+
// the escape hatch goes away in v7. See `emitEngineOffDeprecationWarning`
|
|
14
|
+
// below.
|
|
15
|
+
//
|
|
16
|
+
// Precedence (highest wins):
|
|
17
|
+
// 1. CLI flag — `--engine` / `--no-engine`
|
|
18
|
+
// 2. Env var — `CLAUDE_AUTOPILOT_ENGINE=on|off|true|false|1|0|yes|no`
|
|
19
|
+
// 3. Config — `engine.enabled: true|false` in guardrail.config.yaml
|
|
20
|
+
// 4. Built-in default — v6.1+: true (was false in v6.0)
|
|
21
|
+
//
|
|
22
|
+
// This module is intentionally pure and side-effect-free: it never reads from
|
|
23
|
+
// the environment or the config file directly. Callers (the CLI dispatcher)
|
|
24
|
+
// gather the inputs and pass them in — that keeps the function trivially
|
|
25
|
+
// testable and lets the dispatcher own all I/O.
|
|
26
|
+
//
|
|
27
|
+
// Invalid env values do NOT throw. The contract from the spec / migration
|
|
28
|
+
// guide is "treat as unset and emit a run.warning so observers can attribute
|
|
29
|
+
// the fallthrough." This module returns metadata — the resolver caller
|
|
30
|
+
// (cli/index.ts) is responsible for emitting the warning event.
|
|
31
|
+
/** v6.1+ ships with the engine ON by default — flipped from the v6.0
|
|
32
|
+
* default (`false`) per `docs/specs/v6.1-default-flip.md`. Exported so
|
|
33
|
+
* tests / future releases can pin a known value. */
|
|
34
|
+
export const ENGINE_DEFAULT_V6_1 = true;
|
|
35
|
+
/** Historical v6.0 default. Preserved verbatim — its semantic meaning
|
|
36
|
+
* ("the v6.0 default was off") doesn't change just because the active
|
|
37
|
+
* default flipped. Out-of-tree consumers that pinned this constant get
|
|
38
|
+
* the value the name promises. Use `ENGINE_DEFAULT_V6_1` for the active
|
|
39
|
+
* default. Removed in v7.
|
|
40
|
+
* @deprecated Use `ENGINE_DEFAULT_V6_1` or omit `builtInDefault` to inherit
|
|
41
|
+
* the active default. */
|
|
42
|
+
export const ENGINE_DEFAULT_V6_0 = false;
|
|
43
|
+
/** Parse a stringly-typed env value into a tri-state boolean.
|
|
44
|
+
* Accepts (case-insensitive): on, off, true, false, 1, 0, yes, no.
|
|
45
|
+
* Returns undefined for any other input INCLUDING empty / whitespace-only
|
|
46
|
+
* strings — that signals the caller to fall through to the next precedence
|
|
47
|
+
* layer. */
|
|
48
|
+
export function parseEngineEnvValue(raw) {
|
|
49
|
+
if (raw === undefined)
|
|
50
|
+
return undefined;
|
|
51
|
+
const normalized = raw.trim().toLowerCase();
|
|
52
|
+
if (normalized === '')
|
|
53
|
+
return undefined;
|
|
54
|
+
switch (normalized) {
|
|
55
|
+
case 'on':
|
|
56
|
+
case 'true':
|
|
57
|
+
case '1':
|
|
58
|
+
case 'yes':
|
|
59
|
+
return true;
|
|
60
|
+
case 'off':
|
|
61
|
+
case 'false':
|
|
62
|
+
case '0':
|
|
63
|
+
case 'no':
|
|
64
|
+
return false;
|
|
65
|
+
default:
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** Resolve whether the Run State Engine should run for this invocation.
|
|
70
|
+
* Pure function — does not touch process.env, fs, or anything I/O. */
|
|
71
|
+
export function resolveEngineEnabled(opts = {}) {
|
|
72
|
+
const { cliEngine, envValue, configEnabled, builtInDefault } = opts;
|
|
73
|
+
const builtIn = builtInDefault ?? ENGINE_DEFAULT_V6_1;
|
|
74
|
+
// Layer 1 — CLI flag wins outright.
|
|
75
|
+
if (cliEngine === true) {
|
|
76
|
+
return { enabled: true, source: 'cli', reason: '--engine flag' };
|
|
77
|
+
}
|
|
78
|
+
if (cliEngine === false) {
|
|
79
|
+
return { enabled: false, source: 'cli', reason: '--no-engine flag' };
|
|
80
|
+
}
|
|
81
|
+
// Layer 2 — env var.
|
|
82
|
+
if (envValue !== undefined && envValue.trim() !== '') {
|
|
83
|
+
const parsed = parseEngineEnvValue(envValue);
|
|
84
|
+
if (parsed !== undefined) {
|
|
85
|
+
return {
|
|
86
|
+
enabled: parsed,
|
|
87
|
+
source: 'env',
|
|
88
|
+
reason: `CLAUDE_AUTOPILOT_ENGINE=${envValue}`,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
// Invalid value — fall through, but record the raw value so the caller
|
|
92
|
+
// can emit a run.warning. Continue to config / default below.
|
|
93
|
+
// We bind it here so it survives the recursion-style fallthrough.
|
|
94
|
+
return resolveWithFallthrough({
|
|
95
|
+
configEnabled,
|
|
96
|
+
builtIn,
|
|
97
|
+
invalidEnvValue: envValue,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return resolveWithFallthrough({ configEnabled, builtIn });
|
|
101
|
+
}
|
|
102
|
+
/** Layers 3 + 4 — config, then built-in default. Factored out so the env
|
|
103
|
+
* invalid-value path can reach the same logic without recursing into
|
|
104
|
+
* resolveEngineEnabled (which would re-evaluate the env var and loop). */
|
|
105
|
+
function resolveWithFallthrough(opts) {
|
|
106
|
+
const { configEnabled, builtIn, invalidEnvValue } = opts;
|
|
107
|
+
const invalidSuffix = invalidEnvValue !== undefined
|
|
108
|
+
? `; invalid CLAUDE_AUTOPILOT_ENGINE=${JSON.stringify(invalidEnvValue)} ignored`
|
|
109
|
+
: '';
|
|
110
|
+
if (configEnabled === true) {
|
|
111
|
+
return {
|
|
112
|
+
enabled: true,
|
|
113
|
+
source: 'config',
|
|
114
|
+
reason: `engine.enabled: true in guardrail.config.yaml${invalidSuffix}`,
|
|
115
|
+
...(invalidEnvValue !== undefined ? { invalidEnvValue } : {}),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
if (configEnabled === false) {
|
|
119
|
+
return {
|
|
120
|
+
enabled: false,
|
|
121
|
+
source: 'config',
|
|
122
|
+
reason: `engine.enabled: false in guardrail.config.yaml${invalidSuffix}`,
|
|
123
|
+
...(invalidEnvValue !== undefined ? { invalidEnvValue } : {}),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
enabled: builtIn,
|
|
128
|
+
source: 'default',
|
|
129
|
+
reason: `built-in default (engine ${builtIn ? 'on' : 'off'} in v6.1+)${invalidSuffix}`,
|
|
130
|
+
...(invalidEnvValue !== undefined ? { invalidEnvValue } : {}),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
// v6.1 deprecation warning for explicit engine-off
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
/** Stable copy emitted on stderr when a user explicitly opts out of the
|
|
137
|
+
* engine via `--no-engine`, `CLAUDE_AUTOPILOT_ENGINE=off`, or
|
|
138
|
+
* `engine.enabled: false`. v7 removes the escape hatch entirely.
|
|
139
|
+
*
|
|
140
|
+
* Exported for tests + downstream consumers (e.g. CI parsers) that want to
|
|
141
|
+
* match against the exact string. Kept on a single line so terminals don't
|
|
142
|
+
* wrap mid-message. */
|
|
143
|
+
export const ENGINE_OFF_DEPRECATION_MESSAGE = '[deprecation] --no-engine / engine.enabled: false will be removed in v7. Migrate to engine-on (default).';
|
|
144
|
+
/** Decide whether v6.1's `--no-engine` deprecation warning applies for a
|
|
145
|
+
* given resolver result. Returns `true` ONLY when the user explicitly
|
|
146
|
+
* opted out (via CLI flag, env var, or config) — never on the v6.1 default
|
|
147
|
+
* (which is `enabled: true`, so it can't trigger here anyway) and never
|
|
148
|
+
* when the engine is actually on. Pure: takes the resolver result, returns
|
|
149
|
+
* a boolean.
|
|
150
|
+
*
|
|
151
|
+
* Why this is a separate predicate (not collapsed into the warner): the
|
|
152
|
+
* CLI dispatcher wants to ALSO emit a typed `run.warning` event into a
|
|
153
|
+
* ledger when the engine ends up on but the resolver came from a layer
|
|
154
|
+
* that's about to be removed — except today, on v6.1, the only path that
|
|
155
|
+
* warns IS the "engine off, explicit opt-out" path. So the predicate
|
|
156
|
+
* collapses cleanly to that single condition. v7 removes both. */
|
|
157
|
+
export function shouldWarnEngineOffDeprecation(resolved) {
|
|
158
|
+
if (resolved.enabled)
|
|
159
|
+
return false;
|
|
160
|
+
return (resolved.source === 'cli' ||
|
|
161
|
+
resolved.source === 'env' ||
|
|
162
|
+
resolved.source === 'config');
|
|
163
|
+
}
|
|
164
|
+
/** Emit the v6.1 `--no-engine` deprecation warning to stderr (or the
|
|
165
|
+
* supplied `warn` callback) when the resolver result indicates the user
|
|
166
|
+
* explicitly opted out of the engine. No-op when:
|
|
167
|
+
* - the engine is on (no opt-out happened);
|
|
168
|
+
* - the source is `'default'` (v6.1's flipped default = on, so a default
|
|
169
|
+
* result with `enabled: false` is impossible without a custom
|
|
170
|
+
* `builtInDefault` override — and even that path doesn't warn since
|
|
171
|
+
* it's not a user-driven opt-out).
|
|
172
|
+
*
|
|
173
|
+
* Pure-ish: side-effect is captured behind the optional `warn` callback so
|
|
174
|
+
* tests can assert on the message without spawning a subprocess. The
|
|
175
|
+
* default warner writes to `process.stderr` with a trailing newline.
|
|
176
|
+
*
|
|
177
|
+
* Returns `true` when the warning fired, `false` when it was a no-op. The
|
|
178
|
+
* return value is purely informational — callers can use it to decide
|
|
179
|
+
* whether to also append a `run.warning` event into a run ledger (only
|
|
180
|
+
* meaningful on the engine-on path; the v6.1 deprecation only fires on
|
|
181
|
+
* engine-off, where there's no run dir to write into). */
|
|
182
|
+
export function emitEngineOffDeprecationWarning(resolved, warn = (msg) => {
|
|
183
|
+
process.stderr.write(`${msg}\n`);
|
|
184
|
+
}) {
|
|
185
|
+
if (!shouldWarnEngineOffDeprecation(resolved))
|
|
186
|
+
return false;
|
|
187
|
+
warn(ENGINE_OFF_DEPRECATION_MESSAGE);
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=resolve-engine.js.map
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { ExternalRef } from './types.ts';
|
|
2
|
+
import type { ReadbackResult } from './provider-readback.ts';
|
|
3
|
+
/** What the orchestrator should do for this phase on resume. */
|
|
4
|
+
export type ResumeDecision =
|
|
5
|
+
/** No prior success → run the phase normally (no preflight needed). */
|
|
6
|
+
{
|
|
7
|
+
kind: 'proceed-fresh';
|
|
8
|
+
}
|
|
9
|
+
/** All postEffect refs `merged`/`live` → emit phase.success with
|
|
10
|
+
* `replayed: true` and skip the phase body. */
|
|
11
|
+
| {
|
|
12
|
+
kind: 'skip-already-applied';
|
|
13
|
+
readback: ReadbackResult[];
|
|
14
|
+
reason: string;
|
|
15
|
+
}
|
|
16
|
+
/** PreEffect ref `open` + postEffect incomplete → re-run the phase body;
|
|
17
|
+
* its own ledger handles partial-state dedup. */
|
|
18
|
+
| {
|
|
19
|
+
kind: 'retry';
|
|
20
|
+
readback: ReadbackResult[];
|
|
21
|
+
reason: string;
|
|
22
|
+
}
|
|
23
|
+
/** Ambiguous state → emit replay.override + throw needs_human. */
|
|
24
|
+
| {
|
|
25
|
+
kind: 'needs-human';
|
|
26
|
+
readback: ReadbackResult[];
|
|
27
|
+
reason: string;
|
|
28
|
+
refsConsulted: ExternalRef[];
|
|
29
|
+
};
|
|
30
|
+
export interface ResumePreflightInput {
|
|
31
|
+
/** The phase's contract — read out of `PHASE_REGISTRY[name]`. Empty
|
|
32
|
+
* arrays are valid (e.g. `pr` declares post-effect = []). */
|
|
33
|
+
preEffectRefKinds: readonly string[];
|
|
34
|
+
postEffectRefKinds: readonly string[];
|
|
35
|
+
/** Did the prior orchestrator attempt record `phase.success` for this
|
|
36
|
+
* phaseIdx? When false, we return `proceed-fresh` immediately. */
|
|
37
|
+
priorPhaseSuccess: boolean;
|
|
38
|
+
/** Refs persisted by the prior attempt — both pre-effect and post-effect
|
|
39
|
+
* kinds, in event order. The preflight filters them by kind to map
|
|
40
|
+
* to the contract. */
|
|
41
|
+
priorRefs: readonly ExternalRef[];
|
|
42
|
+
/** Test seam — replace `verifyRefs` so unit tests don't need the real
|
|
43
|
+
* github / supabase readbacks. Production callers omit this. */
|
|
44
|
+
verifyRefsImpl?: (refs: readonly ExternalRef[]) => Promise<ReadbackResult[]>;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Make the resume decision for one side-effecting phase.
|
|
48
|
+
*
|
|
49
|
+
* The contract:
|
|
50
|
+
* - When `priorPhaseSuccess === false` → `proceed-fresh` (no preflight).
|
|
51
|
+
* - When `priorRefs` is empty AND we have a prior phase.success →
|
|
52
|
+
* `needs-human` (the phase claimed success but didn't persist any
|
|
53
|
+
* ref — corrupted state we shouldn't auto-retry).
|
|
54
|
+
* - When all refs whose kind is in `postEffectRefKinds` read back as
|
|
55
|
+
* `merged` / `live` AND postEffect is non-empty → `skip-already-applied`.
|
|
56
|
+
* - When at least one preEffect ref reads back as `open` AND not all
|
|
57
|
+
* post-effect refs merged → `retry`.
|
|
58
|
+
* - Otherwise → `needs-human`.
|
|
59
|
+
*
|
|
60
|
+
* `pr`'s contract has `postEffectRefKinds: []`. For that case the
|
|
61
|
+
* pre-effect ref (`github-pr`) doubles as the reconciliation ref — we
|
|
62
|
+
* inspect IT against `merged`/`open` semantics. Any other state →
|
|
63
|
+
* needs-human.
|
|
64
|
+
*/
|
|
65
|
+
export declare function resumePreflight(input: ResumePreflightInput): Promise<ResumeDecision>;
|
|
66
|
+
//# sourceMappingURL=resume-preflight.d.ts.map
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// src/core/run-state/resume-preflight.ts
|
|
2
|
+
//
|
|
3
|
+
// v6.2.1 — orchestrator resume preflight for side-effecting phases.
|
|
4
|
+
//
|
|
5
|
+
// Background — the v6.2.1 side-effect idempotency contract requires every
|
|
6
|
+
// `hasSideEffects: true` phase to declare two ref kind sets:
|
|
7
|
+
//
|
|
8
|
+
// - `preEffectRefKinds` — recorded BEFORE the side effect runs.
|
|
9
|
+
// - `postEffectRefKinds` — recorded AFTER the side effect completes.
|
|
10
|
+
//
|
|
11
|
+
// On resume of a runId the orchestrator must consult the persisted refs +
|
|
12
|
+
// the platform of record (via `verifyRefs`) before re-invoking the phase
|
|
13
|
+
// body. This module owns that decision.
|
|
14
|
+
//
|
|
15
|
+
// The decision matrix (per spec "Resume preflight pseudocode"):
|
|
16
|
+
//
|
|
17
|
+
// - All postEffect refs read back as `merged` / `live`
|
|
18
|
+
// ⇒ `skip-already-applied` — orchestrator emits `phase.success
|
|
19
|
+
// { replayed: true, reason: 'side-effect-already-applied' }` and
|
|
20
|
+
// advances. NO retry.
|
|
21
|
+
// - PreEffect ref read back as `open` AND postEffect refs incomplete
|
|
22
|
+
// ⇒ `retry` — orchestrator runs the phase body normally; the phase's
|
|
23
|
+
// own ledger / preflight handles dedup against the partial state.
|
|
24
|
+
// - Otherwise (no prior success, ambiguous, or readback unknown)
|
|
25
|
+
// ⇒ `needs-human` — orchestrator emits `replay.override` event and
|
|
26
|
+
// throws `GuardrailError('needs_human')`.
|
|
27
|
+
//
|
|
28
|
+
// Fresh runs (no prior phase.success for this phaseIdx) get `proceed-fresh`
|
|
29
|
+
// — the orchestrator just runs the phase normally.
|
|
30
|
+
import { verifyRefs as defaultVerifyRefs } from "./provider-readback.js";
|
|
31
|
+
/**
|
|
32
|
+
* Make the resume decision for one side-effecting phase.
|
|
33
|
+
*
|
|
34
|
+
* The contract:
|
|
35
|
+
* - When `priorPhaseSuccess === false` → `proceed-fresh` (no preflight).
|
|
36
|
+
* - When `priorRefs` is empty AND we have a prior phase.success →
|
|
37
|
+
* `needs-human` (the phase claimed success but didn't persist any
|
|
38
|
+
* ref — corrupted state we shouldn't auto-retry).
|
|
39
|
+
* - When all refs whose kind is in `postEffectRefKinds` read back as
|
|
40
|
+
* `merged` / `live` AND postEffect is non-empty → `skip-already-applied`.
|
|
41
|
+
* - When at least one preEffect ref reads back as `open` AND not all
|
|
42
|
+
* post-effect refs merged → `retry`.
|
|
43
|
+
* - Otherwise → `needs-human`.
|
|
44
|
+
*
|
|
45
|
+
* `pr`'s contract has `postEffectRefKinds: []`. For that case the
|
|
46
|
+
* pre-effect ref (`github-pr`) doubles as the reconciliation ref — we
|
|
47
|
+
* inspect IT against `merged`/`open` semantics. Any other state →
|
|
48
|
+
* needs-human.
|
|
49
|
+
*/
|
|
50
|
+
export async function resumePreflight(input) {
|
|
51
|
+
if (!input.priorPhaseSuccess && input.priorRefs.length === 0) {
|
|
52
|
+
return { kind: 'proceed-fresh' };
|
|
53
|
+
}
|
|
54
|
+
const verify = input.verifyRefsImpl ?? defaultVerifyRefs;
|
|
55
|
+
const readback = input.priorRefs.length > 0 ? await verify(input.priorRefs) : [];
|
|
56
|
+
// postEffect path — if the contract declares post-effect kinds, the
|
|
57
|
+
// pre-effect breadcrumb's readback state is authoritative for the
|
|
58
|
+
// batch's "did all the planned work land?" question. Post-effect refs
|
|
59
|
+
// alone aren't authoritative because the resume doesn't know the total
|
|
60
|
+
// planned set just from the persisted ref count (a partial crash
|
|
61
|
+
// persists 3 of 5 migration-version refs and 0 readback for the missing
|
|
62
|
+
// 2 — counting the persisted set against itself always says "complete").
|
|
63
|
+
// The batch readback is the source of truth: it queries the dispatcher's
|
|
64
|
+
// ledger for the planned set vs the applied set.
|
|
65
|
+
if (input.postEffectRefKinds.length > 0) {
|
|
66
|
+
const preRefs = readback.filter(r => input.preEffectRefKinds.includes(r.refKind));
|
|
67
|
+
const postRefs = readback.filter(r => input.postEffectRefKinds.includes(r.refKind));
|
|
68
|
+
// Skip-already-applied: pre-effect breadcrumb confirms `merged` AND
|
|
69
|
+
// every persisted post-effect ref is live/merged. The pre-effect's
|
|
70
|
+
// `merged` state comes from the batch readback comparing planned to
|
|
71
|
+
// applied — so we trust it as the cross-set check.
|
|
72
|
+
const preMerged = preRefs.length > 0 && preRefs.every(r => r.currentState === 'merged' || r.currentState === 'live');
|
|
73
|
+
const postAllLive = postRefs.every(r => r.currentState === 'merged' || r.currentState === 'live');
|
|
74
|
+
if (preMerged && postAllLive) {
|
|
75
|
+
return {
|
|
76
|
+
kind: 'skip-already-applied',
|
|
77
|
+
readback,
|
|
78
|
+
reason: `pre-effect ref reports merged + ${postRefs.length} post-effect ref(s) live`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
// Retry: pre-effect breadcrumb is `open` (some planned items still
|
|
82
|
+
// pending). The phase body's own ledger guard handles dedup of the
|
|
83
|
+
// already-applied items.
|
|
84
|
+
const preOpen = preRefs.some(r => r.currentState === 'open');
|
|
85
|
+
if (preOpen) {
|
|
86
|
+
return {
|
|
87
|
+
kind: 'retry',
|
|
88
|
+
readback,
|
|
89
|
+
reason: 'pre-effect breadcrumb open + post-effect refs incomplete',
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
// Empty postEffectRefKinds — pre-effect ref doubles as reconciliation.
|
|
95
|
+
// Used by `pr`: the github-pr ref IS the post-effect ref.
|
|
96
|
+
const preRefs = readback.filter(r => input.preEffectRefKinds.includes(r.refKind));
|
|
97
|
+
if (preRefs.length > 0 && preRefs.every(r => r.currentState === 'merged' || r.currentState === 'live' || r.currentState === 'open')) {
|
|
98
|
+
// For pr: open/merged both mean "the PR exists, don't recreate it."
|
|
99
|
+
return {
|
|
100
|
+
kind: 'skip-already-applied',
|
|
101
|
+
readback,
|
|
102
|
+
reason: `pre-effect ref doubles as reconciliation; all ${preRefs.length} report live/merged/open`,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Anything else — refs missing, closed without merge, unknown — punt.
|
|
107
|
+
return {
|
|
108
|
+
kind: 'needs-human',
|
|
109
|
+
readback,
|
|
110
|
+
refsConsulted: [...input.priorRefs],
|
|
111
|
+
reason: readback.length === 0
|
|
112
|
+
? 'prior phase.success exists but no externalRefs persisted'
|
|
113
|
+
: 'readback could not confirm skip-already-applied or retry-safe',
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=resume-preflight.js.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { type RunPhase } from './phase-runner.ts';
|
|
2
|
+
import type { GuardrailConfig } from '../config/types.ts';
|
|
3
|
+
/** Caller-supplied inputs to drive a single-phase engine run.
|
|
4
|
+
*
|
|
5
|
+
* The helper is intentionally narrow: every wrapped verb passes the same
|
|
6
|
+
* shape today. Side-effecting verbs (`pr`, `migrate`, `implement`) will
|
|
7
|
+
* use the same shape too — they emit externalRefs from inside `phase.run`
|
|
8
|
+
* via `ctx.emitExternalRef()`, which the underlying `runPhase()` records.
|
|
9
|
+
* No new helper field is needed for them in v6.0.7+. */
|
|
10
|
+
export interface RunPhaseWithLifecycleOpts<I, O> {
|
|
11
|
+
/** Project working directory. Passed straight through to `createRun`
|
|
12
|
+
* so `.guardrail-cache/runs/<ulid>/` lands in the right place. */
|
|
13
|
+
cwd: string;
|
|
14
|
+
/** The pre-built phase definition (name, idempotent, hasSideEffects, run).
|
|
15
|
+
* Same `RunPhase<I, O>` the caller would pass to `runPhase()` directly. */
|
|
16
|
+
phase: RunPhase<I, O>;
|
|
17
|
+
/** Phase input — same shape the caller would pass to `runPhase()`. */
|
|
18
|
+
input: I;
|
|
19
|
+
/** Loaded `guardrail.config.yaml` (or the default `{ configVersion: 1 }`).
|
|
20
|
+
* Used only for the `engine.enabled` precedence layer. The helper does
|
|
21
|
+
* NOT re-load config — that's the caller's responsibility (and lets
|
|
22
|
+
* callers pass synthetic configs in tests). */
|
|
23
|
+
config: GuardrailConfig;
|
|
24
|
+
/** CLI flag override — `true` from `--engine`, `false` from `--no-engine`,
|
|
25
|
+
* `undefined` if neither was passed. */
|
|
26
|
+
cliEngine: boolean | undefined;
|
|
27
|
+
/** Raw value of `process.env.CLAUDE_AUTOPILOT_ENGINE`. `undefined` if
|
|
28
|
+
* unset. The helper passes this through to `resolveEngineEnabled` —
|
|
29
|
+
* invalid values fall through with a `run.warning` recorded automatically. */
|
|
30
|
+
envEngine: string | undefined;
|
|
31
|
+
/** Engine-off escape hatch — what to return when `resolveEngineEnabled`
|
|
32
|
+
* decides the engine should NOT run. Most callers pass an async function
|
|
33
|
+
* that runs the legacy code path (typically the same `phase.run` body
|
|
34
|
+
* invoked without the lifecycle wrapper). The helper does not invoke
|
|
35
|
+
* `phase.run` for engine-off so the caller has full control over the
|
|
36
|
+
* legacy path's behavior — keeps engine-off byte-for-byte identical to
|
|
37
|
+
* pre-v6 behavior even when the phase body's signature would otherwise
|
|
38
|
+
* pin the call shape. */
|
|
39
|
+
runEngineOff: () => Promise<O>;
|
|
40
|
+
}
|
|
41
|
+
/** What the helper hands back. `runId` and `runDir` are null on the
|
|
42
|
+
* engine-off path so callers can branch on whether engine artifacts exist
|
|
43
|
+
* (e.g. for a future `--json` envelope that surfaces the runId). */
|
|
44
|
+
export interface RunPhaseWithLifecycleResult<O> {
|
|
45
|
+
output: O;
|
|
46
|
+
/** ULID of the created run, or null when engine-off. */
|
|
47
|
+
runId: string | null;
|
|
48
|
+
/** Absolute path to the run dir, or null when engine-off. */
|
|
49
|
+
runDir: string | null;
|
|
50
|
+
}
|
|
51
|
+
/** Drive a single-phase engine run with full lifecycle instrumentation,
|
|
52
|
+
* OR fall through to the legacy `runEngineOff` callback when the engine
|
|
53
|
+
* is disabled by config / CLI / env precedence.
|
|
54
|
+
*
|
|
55
|
+
* Engine-on lifecycle (in order):
|
|
56
|
+
* createRun → (optional run.warning for invalid env) → runPhase →
|
|
57
|
+
* run.complete (success or failed) → refresh state.json → release lock.
|
|
58
|
+
*
|
|
59
|
+
* On phase failure the helper:
|
|
60
|
+
* 1. Emits `run.complete` with `status: 'failed'`.
|
|
61
|
+
* 2. Refreshes state.json from the replayed events.
|
|
62
|
+
* 3. Prints the legacy `[<phase>] engine: phase failed — <msg>` banner
|
|
63
|
+
* to stderr (byte-for-byte identical to the inline pattern that
|
|
64
|
+
* lived in 8 of 8 wrapped verbs pre-v6.0.6).
|
|
65
|
+
* 4. Releases the lock and re-throws so the caller can return its
|
|
66
|
+
* legacy non-zero exit code.
|
|
67
|
+
*
|
|
68
|
+
* The lock release in `finally` is best-effort. `release()` is idempotent
|
|
69
|
+
* (the runs lock module accepts double-release without throwing), so the
|
|
70
|
+
* catch block does not need to release the lock itself — `finally` covers
|
|
71
|
+
* both the success and failure exit paths. */
|
|
72
|
+
export declare function runPhaseWithLifecycle<I, O>(opts: RunPhaseWithLifecycleOpts<I, O>): Promise<RunPhaseWithLifecycleResult<O>>;
|
|
73
|
+
//# sourceMappingURL=run-phase-with-lifecycle.d.ts.map
|