@delegance/claude-autopilot 5.2.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 (130) hide show
  1. package/CHANGELOG.md +1027 -1
  2. package/README.md +104 -17
  3. package/dist/src/adapters/council/claude.js +2 -1
  4. package/dist/src/adapters/council/openai.js +14 -7
  5. package/dist/src/adapters/deploy/_http.d.ts +43 -0
  6. package/dist/src/adapters/deploy/_http.js +99 -0
  7. package/dist/src/adapters/deploy/fly.d.ts +206 -0
  8. package/dist/src/adapters/deploy/fly.js +696 -0
  9. package/dist/src/adapters/deploy/generic.d.ts +39 -0
  10. package/dist/src/adapters/deploy/generic.js +98 -0
  11. package/dist/src/adapters/deploy/index.d.ts +15 -0
  12. package/dist/src/adapters/deploy/index.js +78 -0
  13. package/dist/src/adapters/deploy/render.d.ts +181 -0
  14. package/dist/src/adapters/deploy/render.js +550 -0
  15. package/dist/src/adapters/deploy/types.d.ts +221 -0
  16. package/dist/src/adapters/deploy/types.js +15 -0
  17. package/dist/src/adapters/deploy/vercel.d.ts +143 -0
  18. package/dist/src/adapters/deploy/vercel.js +426 -0
  19. package/dist/src/adapters/pricing.d.ts +36 -0
  20. package/dist/src/adapters/pricing.js +40 -0
  21. package/dist/src/adapters/review-engine/claude.js +2 -1
  22. package/dist/src/adapters/review-engine/codex.js +12 -8
  23. package/dist/src/adapters/review-engine/gemini.js +2 -1
  24. package/dist/src/adapters/review-engine/openai-compatible.js +2 -1
  25. package/dist/src/adapters/sdk-loader.d.ts +15 -0
  26. package/dist/src/adapters/sdk-loader.js +77 -0
  27. package/dist/src/cli/autopilot.d.ts +71 -0
  28. package/dist/src/cli/autopilot.js +735 -0
  29. package/dist/src/cli/brainstorm.d.ts +23 -0
  30. package/dist/src/cli/brainstorm.js +131 -0
  31. package/dist/src/cli/costs.d.ts +15 -1
  32. package/dist/src/cli/costs.js +99 -10
  33. package/dist/src/cli/deploy.d.ts +71 -0
  34. package/dist/src/cli/deploy.js +539 -0
  35. package/dist/src/cli/fix.d.ts +18 -0
  36. package/dist/src/cli/fix.js +105 -11
  37. package/dist/src/cli/help-text.d.ts +52 -0
  38. package/dist/src/cli/help-text.js +400 -0
  39. package/dist/src/cli/implement.d.ts +91 -0
  40. package/dist/src/cli/implement.js +196 -0
  41. package/dist/src/cli/index.js +784 -222
  42. package/dist/src/cli/json-envelope.d.ts +187 -0
  43. package/dist/src/cli/json-envelope.js +270 -0
  44. package/dist/src/cli/json-mode.d.ts +33 -0
  45. package/dist/src/cli/json-mode.js +201 -0
  46. package/dist/src/cli/migrate.d.ts +111 -0
  47. package/dist/src/cli/migrate.js +305 -0
  48. package/dist/src/cli/plan.d.ts +81 -0
  49. package/dist/src/cli/plan.js +149 -0
  50. package/dist/src/cli/pr.d.ts +106 -0
  51. package/dist/src/cli/pr.js +191 -19
  52. package/dist/src/cli/preflight.js +102 -1
  53. package/dist/src/cli/review.d.ts +27 -0
  54. package/dist/src/cli/review.js +126 -0
  55. package/dist/src/cli/runs-watch-renderer.d.ts +45 -0
  56. package/dist/src/cli/runs-watch-renderer.js +275 -0
  57. package/dist/src/cli/runs-watch.d.ts +41 -0
  58. package/dist/src/cli/runs-watch.js +395 -0
  59. package/dist/src/cli/runs.d.ts +122 -0
  60. package/dist/src/cli/runs.js +902 -0
  61. package/dist/src/cli/scan.d.ts +93 -0
  62. package/dist/src/cli/scan.js +166 -40
  63. package/dist/src/cli/spec.d.ts +66 -0
  64. package/dist/src/cli/spec.js +132 -0
  65. package/dist/src/cli/validate.d.ts +29 -0
  66. package/dist/src/cli/validate.js +131 -0
  67. package/dist/src/core/config/schema.d.ts +43 -0
  68. package/dist/src/core/config/schema.js +25 -0
  69. package/dist/src/core/config/types.d.ts +17 -0
  70. package/dist/src/core/council/runner.d.ts +10 -1
  71. package/dist/src/core/council/runner.js +25 -3
  72. package/dist/src/core/council/types.d.ts +7 -0
  73. package/dist/src/core/errors.d.ts +1 -1
  74. package/dist/src/core/errors.js +12 -0
  75. package/dist/src/core/logging/redaction.d.ts +13 -0
  76. package/dist/src/core/logging/redaction.js +20 -0
  77. package/dist/src/core/migrate/detector-rules.js +6 -0
  78. package/dist/src/core/migrate/schema-validator.js +22 -1
  79. package/dist/src/core/phases/static-rules.d.ts +5 -1
  80. package/dist/src/core/phases/static-rules.js +2 -5
  81. package/dist/src/core/run-state/budget.d.ts +88 -0
  82. package/dist/src/core/run-state/budget.js +141 -0
  83. package/dist/src/core/run-state/cli-internal.d.ts +21 -0
  84. package/dist/src/core/run-state/cli-internal.js +174 -0
  85. package/dist/src/core/run-state/events.d.ts +59 -0
  86. package/dist/src/core/run-state/events.js +504 -0
  87. package/dist/src/core/run-state/lock.d.ts +61 -0
  88. package/dist/src/core/run-state/lock.js +206 -0
  89. package/dist/src/core/run-state/phase-context.d.ts +60 -0
  90. package/dist/src/core/run-state/phase-context.js +108 -0
  91. package/dist/src/core/run-state/phase-registry.d.ts +137 -0
  92. package/dist/src/core/run-state/phase-registry.js +162 -0
  93. package/dist/src/core/run-state/phase-runner.d.ts +80 -0
  94. package/dist/src/core/run-state/phase-runner.js +447 -0
  95. package/dist/src/core/run-state/provider-readback.d.ts +130 -0
  96. package/dist/src/core/run-state/provider-readback.js +426 -0
  97. package/dist/src/core/run-state/replay-decision.d.ts +69 -0
  98. package/dist/src/core/run-state/replay-decision.js +144 -0
  99. package/dist/src/core/run-state/resolve-engine.d.ts +100 -0
  100. package/dist/src/core/run-state/resolve-engine.js +190 -0
  101. package/dist/src/core/run-state/resume-preflight.d.ts +66 -0
  102. package/dist/src/core/run-state/resume-preflight.js +116 -0
  103. package/dist/src/core/run-state/run-phase-with-lifecycle.d.ts +73 -0
  104. package/dist/src/core/run-state/run-phase-with-lifecycle.js +186 -0
  105. package/dist/src/core/run-state/runs.d.ts +57 -0
  106. package/dist/src/core/run-state/runs.js +288 -0
  107. package/dist/src/core/run-state/snapshot.d.ts +14 -0
  108. package/dist/src/core/run-state/snapshot.js +114 -0
  109. package/dist/src/core/run-state/state.d.ts +40 -0
  110. package/dist/src/core/run-state/state.js +164 -0
  111. package/dist/src/core/run-state/types.d.ts +278 -0
  112. package/dist/src/core/run-state/types.js +13 -0
  113. package/dist/src/core/run-state/ulid.d.ts +11 -0
  114. package/dist/src/core/run-state/ulid.js +95 -0
  115. package/dist/src/core/schema-alignment/extractor/index.d.ts +1 -1
  116. package/dist/src/core/schema-alignment/extractor/index.js +2 -2
  117. package/dist/src/core/schema-alignment/extractor/prisma.d.ts +13 -1
  118. package/dist/src/core/schema-alignment/extractor/prisma.js +65 -10
  119. package/dist/src/core/schema-alignment/git-history.d.ts +19 -0
  120. package/dist/src/core/schema-alignment/git-history.js +53 -0
  121. package/dist/src/core/static-rules/rules/brand-tokens.js +2 -2
  122. package/dist/src/core/static-rules/rules/schema-alignment.js +14 -4
  123. package/package.json +9 -5
  124. package/scripts/autoregress.ts +3 -2
  125. package/skills/claude-autopilot.md +1 -1
  126. package/skills/make-interfaces-feel-better/SKILL.md +104 -0
  127. package/skills/migrate/SKILL.md +193 -47
  128. package/skills/simplify-ui/SKILL.md +103 -0
  129. package/skills/ui/SKILL.md +117 -0
  130. 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