@delegance/claude-autopilot 5.5.2 → 7.2.0

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 (150) hide show
  1. package/CHANGELOG.md +1776 -6
  2. package/README.md +65 -1
  3. package/bin/_launcher.js +38 -23
  4. package/dist/src/adapters/council/openai.js +12 -6
  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/index.d.ts +2 -0
  10. package/dist/src/adapters/deploy/index.js +33 -0
  11. package/dist/src/adapters/deploy/render.d.ts +181 -0
  12. package/dist/src/adapters/deploy/render.js +550 -0
  13. package/dist/src/adapters/deploy/types.d.ts +67 -3
  14. package/dist/src/adapters/deploy/vercel.d.ts +17 -1
  15. package/dist/src/adapters/deploy/vercel.js +29 -49
  16. package/dist/src/adapters/pricing.d.ts +36 -0
  17. package/dist/src/adapters/pricing.js +40 -0
  18. package/dist/src/adapters/review-engine/codex.js +10 -7
  19. package/dist/src/cli/autopilot.d.ts +75 -0
  20. package/dist/src/cli/autopilot.js +750 -0
  21. package/dist/src/cli/brainstorm.d.ts +23 -0
  22. package/dist/src/cli/brainstorm.js +131 -0
  23. package/dist/src/cli/costs.d.ts +15 -1
  24. package/dist/src/cli/costs.js +99 -10
  25. package/dist/src/cli/dashboard/index.d.ts +5 -0
  26. package/dist/src/cli/dashboard/index.js +49 -0
  27. package/dist/src/cli/dashboard/login.d.ts +22 -0
  28. package/dist/src/cli/dashboard/login.js +260 -0
  29. package/dist/src/cli/dashboard/logout.d.ts +12 -0
  30. package/dist/src/cli/dashboard/logout.js +45 -0
  31. package/dist/src/cli/dashboard/status.d.ts +30 -0
  32. package/dist/src/cli/dashboard/status.js +65 -0
  33. package/dist/src/cli/dashboard/upload.d.ts +16 -0
  34. package/dist/src/cli/dashboard/upload.js +48 -0
  35. package/dist/src/cli/deploy.d.ts +3 -3
  36. package/dist/src/cli/deploy.js +34 -9
  37. package/dist/src/cli/engine-flag-deprecation.d.ts +14 -0
  38. package/dist/src/cli/engine-flag-deprecation.js +20 -0
  39. package/dist/src/cli/fix.d.ts +18 -0
  40. package/dist/src/cli/fix.js +105 -11
  41. package/dist/src/cli/help-text.d.ts +52 -0
  42. package/dist/src/cli/help-text.js +416 -0
  43. package/dist/src/cli/implement.d.ts +91 -0
  44. package/dist/src/cli/implement.js +196 -0
  45. package/dist/src/cli/index.d.ts +2 -1
  46. package/dist/src/cli/index.js +774 -245
  47. package/dist/src/cli/json-envelope.d.ts +187 -0
  48. package/dist/src/cli/json-envelope.js +270 -0
  49. package/dist/src/cli/json-mode.d.ts +33 -0
  50. package/dist/src/cli/json-mode.js +201 -0
  51. package/dist/src/cli/migrate.d.ts +111 -0
  52. package/dist/src/cli/migrate.js +305 -0
  53. package/dist/src/cli/plan.d.ts +81 -0
  54. package/dist/src/cli/plan.js +149 -0
  55. package/dist/src/cli/pr.d.ts +106 -0
  56. package/dist/src/cli/pr.js +191 -19
  57. package/dist/src/cli/preflight.js +26 -0
  58. package/dist/src/cli/review.d.ts +27 -0
  59. package/dist/src/cli/review.js +126 -0
  60. package/dist/src/cli/runs-watch-renderer.d.ts +45 -0
  61. package/dist/src/cli/runs-watch-renderer.js +275 -0
  62. package/dist/src/cli/runs-watch.d.ts +41 -0
  63. package/dist/src/cli/runs-watch.js +395 -0
  64. package/dist/src/cli/runs.d.ts +122 -0
  65. package/dist/src/cli/runs.js +902 -0
  66. package/dist/src/cli/scaffold.d.ts +39 -0
  67. package/dist/src/cli/scaffold.js +287 -0
  68. package/dist/src/cli/scan.d.ts +93 -0
  69. package/dist/src/cli/scan.js +166 -40
  70. package/dist/src/cli/setup.d.ts +30 -0
  71. package/dist/src/cli/setup.js +137 -0
  72. package/dist/src/cli/spec.d.ts +66 -0
  73. package/dist/src/cli/spec.js +132 -0
  74. package/dist/src/cli/validate.d.ts +29 -0
  75. package/dist/src/cli/validate.js +131 -0
  76. package/dist/src/core/config/schema.d.ts +9 -0
  77. package/dist/src/core/config/schema.js +7 -0
  78. package/dist/src/core/config/types.d.ts +11 -0
  79. package/dist/src/core/council/runner.d.ts +10 -1
  80. package/dist/src/core/council/runner.js +25 -3
  81. package/dist/src/core/council/types.d.ts +7 -0
  82. package/dist/src/core/errors.d.ts +1 -1
  83. package/dist/src/core/errors.js +11 -0
  84. package/dist/src/core/logging/redaction.d.ts +13 -0
  85. package/dist/src/core/logging/redaction.js +20 -0
  86. package/dist/src/core/migrate/schema-validator.js +15 -1
  87. package/dist/src/core/phases/static-rules.d.ts +5 -1
  88. package/dist/src/core/phases/static-rules.js +2 -5
  89. package/dist/src/core/run-state/budget.d.ts +88 -0
  90. package/dist/src/core/run-state/budget.js +141 -0
  91. package/dist/src/core/run-state/cli-internal.d.ts +21 -0
  92. package/dist/src/core/run-state/cli-internal.js +174 -0
  93. package/dist/src/core/run-state/events.d.ts +59 -0
  94. package/dist/src/core/run-state/events.js +512 -0
  95. package/dist/src/core/run-state/lock.d.ts +61 -0
  96. package/dist/src/core/run-state/lock.js +206 -0
  97. package/dist/src/core/run-state/phase-context.d.ts +60 -0
  98. package/dist/src/core/run-state/phase-context.js +108 -0
  99. package/dist/src/core/run-state/phase-registry.d.ts +137 -0
  100. package/dist/src/core/run-state/phase-registry.js +162 -0
  101. package/dist/src/core/run-state/phase-runner.d.ts +80 -0
  102. package/dist/src/core/run-state/phase-runner.js +447 -0
  103. package/dist/src/core/run-state/provider-readback.d.ts +130 -0
  104. package/dist/src/core/run-state/provider-readback.js +426 -0
  105. package/dist/src/core/run-state/replay-decision.d.ts +69 -0
  106. package/dist/src/core/run-state/replay-decision.js +144 -0
  107. package/dist/src/core/run-state/resolve-engine.d.ts +45 -0
  108. package/dist/src/core/run-state/resolve-engine.js +74 -0
  109. package/dist/src/core/run-state/resume-preflight.d.ts +66 -0
  110. package/dist/src/core/run-state/resume-preflight.js +116 -0
  111. package/dist/src/core/run-state/run-phase-with-lifecycle.d.ts +69 -0
  112. package/dist/src/core/run-state/run-phase-with-lifecycle.js +193 -0
  113. package/dist/src/core/run-state/runs.d.ts +57 -0
  114. package/dist/src/core/run-state/runs.js +288 -0
  115. package/dist/src/core/run-state/snapshot.d.ts +14 -0
  116. package/dist/src/core/run-state/snapshot.js +114 -0
  117. package/dist/src/core/run-state/state.d.ts +40 -0
  118. package/dist/src/core/run-state/state.js +164 -0
  119. package/dist/src/core/run-state/types.d.ts +284 -0
  120. package/dist/src/core/run-state/types.js +19 -0
  121. package/dist/src/core/run-state/ulid.d.ts +11 -0
  122. package/dist/src/core/run-state/ulid.js +95 -0
  123. package/dist/src/core/schema-alignment/extractor/index.d.ts +1 -1
  124. package/dist/src/core/schema-alignment/extractor/index.js +2 -2
  125. package/dist/src/core/schema-alignment/extractor/prisma.d.ts +13 -1
  126. package/dist/src/core/schema-alignment/extractor/prisma.js +65 -10
  127. package/dist/src/core/schema-alignment/git-history.d.ts +19 -0
  128. package/dist/src/core/schema-alignment/git-history.js +53 -0
  129. package/dist/src/core/static-rules/rules/brand-tokens.js +2 -2
  130. package/dist/src/core/static-rules/rules/schema-alignment.js +14 -4
  131. package/dist/src/dashboard/auto-upload.d.ts +26 -0
  132. package/dist/src/dashboard/auto-upload.js +107 -0
  133. package/dist/src/dashboard/config.d.ts +22 -0
  134. package/dist/src/dashboard/config.js +109 -0
  135. package/dist/src/dashboard/upload/canonical.d.ts +3 -0
  136. package/dist/src/dashboard/upload/canonical.js +16 -0
  137. package/dist/src/dashboard/upload/chain.d.ts +9 -0
  138. package/dist/src/dashboard/upload/chain.js +27 -0
  139. package/dist/src/dashboard/upload/snapshot.d.ts +23 -0
  140. package/dist/src/dashboard/upload/snapshot.js +66 -0
  141. package/dist/src/dashboard/upload/uploader.d.ts +54 -0
  142. package/dist/src/dashboard/upload/uploader.js +330 -0
  143. package/package.json +19 -3
  144. package/scripts/autoregress.ts +1 -1
  145. package/scripts/test-runner.mjs +4 -0
  146. package/skills/claude-autopilot.md +1 -1
  147. package/skills/make-interfaces-feel-better/SKILL.md +104 -0
  148. package/skills/simplify-ui/SKILL.md +103 -0
  149. package/skills/ui/SKILL.md +117 -0
  150. package/skills/ui-ux-pro-max/SKILL.md +90 -0
@@ -0,0 +1,29 @@
1
+ export interface ValidateCommandOptions {
2
+ cwd?: string;
3
+ configPath?: string;
4
+ /**
5
+ * Optional context note injected into the validate log. The actual
6
+ * validation work (static checks, auto-fix, tests, Codex review,
7
+ * bugbot triage) is owned by the Claude Code `/validate` skill; this
8
+ * CLI verb is the engine-wrap shell so v6 pipeline runs can checkpoint
9
+ * a `validate` phase entry alongside `plan` / `review`.
10
+ */
11
+ context?: string;
12
+ /**
13
+ * Where to write the validate log file. Defaults to
14
+ * `.guardrail-cache/validate/<timestamp>-validate.md` so it lands inside
15
+ * the cache that's already gitignored. The path is recorded on
16
+ * ValidateOutput so the engine path can persist it as `result` for
17
+ * replay.
18
+ */
19
+ outputPath?: string;
20
+ /**
21
+ * v6.0.5 — engine knob inputs. Same shape and precedence as scan / costs /
22
+ * fix / plan / review (CLI > env > config > built-in default off in
23
+ * v6.0.x).
24
+ */
25
+ cliEngine?: boolean;
26
+ envEngine?: string;
27
+ }
28
+ export declare function runValidate(options?: ValidateCommandOptions): Promise<number>;
29
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1,131 @@
1
+ import * as path from 'node:path';
2
+ import * as fs from 'node:fs';
3
+ import { loadConfig } from "../core/config/loader.js";
4
+ import { runPhaseWithLifecycle } from "../core/run-state/run-phase-with-lifecycle.js";
5
+ const C = {
6
+ reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
7
+ green: '\x1b[32m', yellow: '\x1b[33m', cyan: '\x1b[36m', red: '\x1b[31m',
8
+ };
9
+ const fmt = (c, t) => `${C[c]}${t}${C.reset}`;
10
+ export async function runValidate(options = {}) {
11
+ const cwd = options.cwd ?? process.cwd();
12
+ const configPath = options.configPath ?? path.join(cwd, 'guardrail.config.yaml');
13
+ let config = { configVersion: 1 };
14
+ if (fs.existsSync(configPath)) {
15
+ const loaded = await loadConfig(configPath);
16
+ if (loaded)
17
+ config = loaded;
18
+ }
19
+ // INTENTIONAL DEVIATION FROM THE SPEC TABLE (preserved in v6.0.6):
20
+ // the v6 spec (docs/specs/v6-run-state-engine.md, line 161) lists
21
+ // `validate` with `idempotent: yes, hasSideEffects: no,
22
+ // externalRefs: sarif-artifact`. This wrap declares
23
+ // `idempotent: true, hasSideEffects: false` (matches the spec) but
24
+ // does **not** plumb a `sarif-artifact` externalRef. The reasoning:
25
+ // the `validate` CLI verb is an engine-wrap shell pointing at the
26
+ // Claude Code `/validate` skill — it does not itself emit a SARIF
27
+ // artifact. SARIF emission lives in `claude-autopilot run --format
28
+ // sarif --output <path>` (a separate verb, see help-text.ts → `run`
29
+ // Options block). The `sarif-artifact` externalRef is local-only file
30
+ // output (no remote upload), so the engine doesn't need a readback
31
+ // rule for it on resume — `idempotent: true` covers replay safety. If
32
+ // a future PR adds SARIF emission directly to this verb (or moves the
33
+ // `--format sarif` flag here), the wrap can add an
34
+ // `ctx.emitExternalRef({ kind: 'sarif-artifact', id: '<path>',
35
+ // observedAt: ... })` call after the file write lands. Until then, no
36
+ // ledger entry is needed because there's nothing to read back from.
37
+ const context = options.context ?? null;
38
+ const outputPath = options.outputPath
39
+ ? path.resolve(cwd, options.outputPath)
40
+ : path.join(cwd, '.guardrail-cache', 'validate', `${new Date().toISOString().replace(/[:.]/g, '-')}-validate.md`);
41
+ const validateInput = { cwd, context, outputPath };
42
+ // The wrapped phase body — writes a validate log stub to disk. The actual
43
+ // validation work (static checks → auto-fix → tests → Codex review →
44
+ // bugbot triage) is produced by the Claude Code `/validate` skill.
45
+ // Engine-off callers invoke this directly via `executeValidatePhase()`;
46
+ // engine-on callers route through `runPhase()`.
47
+ const phase = {
48
+ name: 'validate',
49
+ // Re-running the validate verb against the same context writes the same
50
+ // log file. Engine treats local file writes as overwrite-style — same
51
+ // precedent as scan's findings-cache and review's review-log.
52
+ idempotent: true,
53
+ // Local file write only — no PR comment posting, no git push, no
54
+ // provider-side mutation, no SARIF upload. See the long deviation note
55
+ // above where the engine resolution is computed for the externalRefs
56
+ // rationale.
57
+ hasSideEffects: false,
58
+ run: async (input) => executeValidatePhase(input),
59
+ };
60
+ // v6.0.6 — lifecycle wiring lives in `runPhaseWithLifecycle`.
61
+ let output;
62
+ try {
63
+ const result = await runPhaseWithLifecycle({
64
+ cwd,
65
+ phase,
66
+ input: validateInput,
67
+ config,
68
+ cliEngine: options.cliEngine,
69
+ envEngine: options.envEngine,
70
+ runEngineOff: () => executeValidatePhase(validateInput),
71
+ });
72
+ output = result.output;
73
+ }
74
+ catch {
75
+ return 1;
76
+ }
77
+ return renderValidateOutput(output, validateInput);
78
+ }
79
+ // ---------------------------------------------------------------------------
80
+ // Phase body — write a validate log stub. Pure: no console output, no exit
81
+ // codes. Returns a JSON-serializable ValidateOutput so the engine can persist
82
+ // it as `result` on the phase snapshot. The actual validation work is
83
+ // produced by the Claude Code `/validate` skill; this CLI verb's job is to
84
+ // provide a checkpointable phase shell.
85
+ // ---------------------------------------------------------------------------
86
+ async function executeValidatePhase(input) {
87
+ const { context, outputPath } = input;
88
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
89
+ const lines = [
90
+ '# Validate',
91
+ '',
92
+ `Generated: ${new Date().toISOString()}`,
93
+ '',
94
+ context ? `Context: ${context}` : 'Context: (none provided)',
95
+ '',
96
+ '<!--',
97
+ 'This is the v6 engine-wrap stub for the `validate` phase. The actual',
98
+ 'validation work (static checks, auto-fix, tests, Codex review with',
99
+ 'auto-fix, bugbot triage) is produced by the Claude Code `/validate`',
100
+ 'skill. The CLI verb exists to provide a checkpointable phase shell so',
101
+ '`claude-autopilot runs show <id>` reflects a `validate` phase entry',
102
+ 'when the pipeline includes one. SARIF emission lives in',
103
+ '`claude-autopilot run --format sarif --output <path>` (a separate',
104
+ 'verb).',
105
+ '-->',
106
+ '',
107
+ ];
108
+ fs.writeFileSync(outputPath, lines.join('\n'), 'utf8');
109
+ return {
110
+ validateLogPath: outputPath,
111
+ context,
112
+ };
113
+ }
114
+ // ---------------------------------------------------------------------------
115
+ // Render — translate ValidateOutput back to a stdout summary + exit code.
116
+ // Lives outside the wrapped phase because it's pure presentation.
117
+ // ---------------------------------------------------------------------------
118
+ function renderValidateOutput(output, input) {
119
+ const { validateLogPath, context } = output;
120
+ const { cwd } = input;
121
+ console.log('');
122
+ console.log(fmt('bold', '[validate]') + ' ' + fmt('dim', context ? `context: ${context}` : 'no context provided'));
123
+ console.log(fmt('dim', ` → ${path.relative(cwd, validateLogPath)}`));
124
+ console.log('');
125
+ console.log(fmt('cyan', 'Note:') + fmt('dim', ' the validation pipeline lives in Claude Code (/validate skill —'));
126
+ console.log(fmt('dim', ' static checks, auto-fix, tests, Codex review, bugbot triage).'));
127
+ console.log(fmt('dim', ' SARIF emission lives in `claude-autopilot run --format sarif --output <path>`.'));
128
+ console.log('');
129
+ return 0;
130
+ }
131
+ //# sourceMappingURL=validate.js.map
@@ -298,6 +298,15 @@ export declare const GUARDRAIL_CONFIG_SCHEMA: {
298
298
  readonly concurrency: {
299
299
  readonly type: "object";
300
300
  };
301
+ readonly engine: {
302
+ readonly type: "object";
303
+ readonly additionalProperties: false;
304
+ readonly properties: {
305
+ readonly enabled: {
306
+ readonly type: "boolean";
307
+ };
308
+ };
309
+ };
301
310
  readonly council: {
302
311
  readonly type: "object";
303
312
  readonly required: readonly ["models", "synthesizer"];
@@ -152,6 +152,13 @@ export const GUARDRAIL_CONFIG_SCHEMA = {
152
152
  cache: { type: 'object' },
153
153
  persistence: { type: 'object' },
154
154
  concurrency: { type: 'object' },
155
+ engine: {
156
+ type: 'object',
157
+ additionalProperties: false,
158
+ properties: {
159
+ enabled: { type: 'boolean' },
160
+ },
161
+ },
155
162
  council: {
156
163
  type: 'object',
157
164
  required: ['models', 'synthesizer'],
@@ -101,6 +101,17 @@ export interface GuardrailConfig {
101
101
  cache?: Record<string, unknown>;
102
102
  persistence?: Record<string, unknown>;
103
103
  concurrency?: Record<string, unknown>;
104
+ /**
105
+ * Run State Engine (v6) configuration. v6.0 ships the engine OFF by default
106
+ * to preserve v5.x behavior; v6.1+ flips the default to ON per
107
+ * `docs/specs/v6.1-default-flip.md`. The `engine.enabled` knob is the
108
+ * lowest-priority opt-in — env (`CLAUDE_AUTOPILOT_ENGINE`) and CLI flags
109
+ * (`--engine` / `--no-engine`) override it. See
110
+ * `src/core/run-state/resolve-engine.ts` for the precedence resolver.
111
+ */
112
+ engine?: {
113
+ enabled?: boolean;
114
+ };
104
115
  council?: {
105
116
  models: Array<{
106
117
  adapter: string;
@@ -8,5 +8,14 @@ export interface CouncilRunOutput {
8
8
  costUSD: number;
9
9
  };
10
10
  }
11
- export declare function runCouncil(config: CouncilConfig, adapters: CouncilAdapter[], synthesizer: CouncilAdapter, prompt: string, contextDoc: string): Promise<CouncilRunOutput>;
11
+ /** Phase 4 bounded recursion options. The current single-shot
12
+ * synthesizer never sets this; it's plumbed for future self-eat
13
+ * patterns where the synthesizer recursively calls runCouncil. */
14
+ export interface CouncilRunOptions {
15
+ /** Current recursion depth. Top-level callers omit this (default 0).
16
+ * A synthesizer that calls runCouncil internally MUST pass
17
+ * `currentDepth + 1` so the bound takes effect. */
18
+ currentDepth?: number;
19
+ }
20
+ export declare function runCouncil(config: CouncilConfig, adapters: CouncilAdapter[], synthesizer: CouncilAdapter, prompt: string, contextDoc: string, options?: CouncilRunOptions): Promise<CouncilRunOutput>;
12
21
  //# sourceMappingURL=runner.d.ts.map
@@ -32,8 +32,26 @@ async function consultWithTimeout(adapter, prompt, context, timeoutMs) {
32
32
  clearTimeout(timer);
33
33
  }
34
34
  }
35
- export async function runCouncil(config, adapters, synthesizer, prompt, contextDoc) {
35
+ export async function runCouncil(config, adapters, synthesizer, prompt, contextDoc, options = {}) {
36
36
  const run_id = crypto.randomUUID();
37
+ const currentDepth = options.currentDepth ?? 0;
38
+ // -- Recursion bound (Phase 4) ------------------------------------------
39
+ // Strict `>` so a maxDepth of N permits N nested self-calls — i.e.
40
+ // depth 0 (top-level) + N inner calls. Exceeding aborts with `partial`
41
+ // and never recurses deeper.
42
+ if (typeof config.councilMaxRecursionDepth === 'number'
43
+ && currentDepth > config.councilMaxRecursionDepth) {
44
+ return {
45
+ result: {
46
+ schema_version: 1,
47
+ run_id,
48
+ status: 'partial',
49
+ prompt,
50
+ responses: [],
51
+ },
52
+ usage: { inputTokens: 0, outputTokens: 0, costUSD: 0 },
53
+ };
54
+ }
37
55
  const context = windowContext(contextDoc, config.parallelInputMaxTokens);
38
56
  const responses = await Promise.all(adapters.map(a => consultWithTimeout(a, prompt, context, config.timeoutMs)));
39
57
  const aggregateUsage = (entries) => {
@@ -61,8 +79,12 @@ export async function runCouncil(config, adapters, synthesizer, prompt, contextD
61
79
  const responseSections = successful
62
80
  .map(r => `### ${r.label}\n${r.text}`)
63
81
  .join('\n\n');
64
- const synthesisDoc = `${contextDoc}\n\n---\n\n${responseSections}`;
65
- const synthesisCtx = windowContext(synthesisDoc, config.synthesisInputMaxTokens);
82
+ // Advisor responses go in synthesisPrompt only (structured form). The
83
+ // context the synthesizer sees is the original conversation document
84
+ // re-windowed for its own token budget — keeping responseSections out of
85
+ // it avoids duplicating them and also avoids letting large responses
86
+ // squeeze contextDoc out of synthesisInputMaxTokens.
87
+ const synthesisCtx = windowContext(contextDoc, config.synthesisInputMaxTokens);
66
88
  const synthesisPrompt = [
67
89
  `You have received responses from multiple technical advisors on the following question:\n\n## Original Question\n\n${prompt}`,
68
90
  `## Advisor Responses\n\n${responseSections}`,
@@ -10,6 +10,13 @@ export interface CouncilConfig {
10
10
  minSuccessfulResponses: number;
11
11
  parallelInputMaxTokens: number;
12
12
  synthesisInputMaxTokens: number;
13
+ /** Phase 4 (v6) — bounded recursion for the synthesizer. If set, every
14
+ * synthesizer self-call increments an internal depth counter; exceeding
15
+ * this value aborts the council with `partial` status (never deeper).
16
+ * Default: undefined (no bound; current single-shot synthesizer is
17
+ * unaffected — the bound only matters when the synthesizer itself
18
+ * recurses into runCouncil). */
19
+ councilMaxRecursionDepth?: number;
13
20
  }
14
21
  export type ModelResponseStatus = 'ok' | 'timeout' | 'error';
15
22
  export interface ModelResponse {
@@ -1,4 +1,4 @@
1
- export type ErrorCode = 'auth' | 'rate_limit' | 'transient_network' | 'invalid_config' | 'adapter_bug' | 'user_input' | 'budget_exceeded' | 'concurrency_lock' | 'superseded' | 'no_previous_deploy';
1
+ export type ErrorCode = 'auth' | 'rate_limit' | 'transient_network' | 'invalid_config' | 'adapter_bug' | 'user_input' | 'budget_exceeded' | 'concurrency_lock' | 'superseded' | 'no_previous_deploy' | 'not_found' | 'lock_held' | 'corrupted_state' | 'partial_write' | 'needs_human';
2
2
  export interface GuardrailErrorOptions {
3
3
  code: ErrorCode;
4
4
  retryable?: boolean;
@@ -4,6 +4,17 @@ const DEFAULT_RETRYABLE = {
4
4
  adapter_bug: false, user_input: false, budget_exceeded: false,
5
5
  concurrency_lock: false, superseded: false,
6
6
  no_previous_deploy: false,
7
+ // 404 — caller-fixable (slug typo, wrong scope). Not retryable; the
8
+ // resource won't materialize on its own.
9
+ not_found: false,
10
+ // v6 Run State Engine — none retry automatically; takeover/recovery is an
11
+ // explicit user-driven decision (--force-takeover / --force).
12
+ lock_held: false,
13
+ corrupted_state: false,
14
+ partial_write: false,
15
+ // v6.2.1 — needs_human is by definition a stop-the-pipeline signal; the
16
+ // user (or `--force-replay`) decides whether to retry.
17
+ needs_human: false,
7
18
  };
8
19
  export class GuardrailError extends Error {
9
20
  code;
@@ -1,4 +1,17 @@
1
1
  export declare const DEFAULT_REDACTION_PATTERNS: readonly string[];
2
2
  export declare function applyRedaction(text: string, patterns: readonly string[]): string;
3
3
  export declare function containsSecret(text: string, patterns: readonly string[]): boolean;
4
+ /**
5
+ * Convenience wrapper around {@link applyRedaction} that defaults to the
6
+ * built-in {@link DEFAULT_REDACTION_PATTERNS} list and accepts an optional
7
+ * caller-supplied extension. Designed for adapter `output` fields and other
8
+ * "last N lines" surfaces where a pattern list is rarely available at the
9
+ * call site (the v5.6 spec § "Log redaction" requires this for all new
10
+ * adapters).
11
+ *
12
+ * Pass extra patterns when the caller has loaded
13
+ * `config.persistence.redactionPatterns`; otherwise omit the argument and
14
+ * the defaults handle the well-known token shapes.
15
+ */
16
+ export declare function redactLogLines(text: string, patterns?: readonly string[]): string;
4
17
  //# sourceMappingURL=redaction.d.ts.map
@@ -15,4 +15,24 @@ export function applyRedaction(text, patterns) {
15
15
  export function containsSecret(text, patterns) {
16
16
  return patterns.some(p => new RegExp(p).test(text));
17
17
  }
18
+ /**
19
+ * Convenience wrapper around {@link applyRedaction} that defaults to the
20
+ * built-in {@link DEFAULT_REDACTION_PATTERNS} list and accepts an optional
21
+ * caller-supplied extension. Designed for adapter `output` fields and other
22
+ * "last N lines" surfaces where a pattern list is rarely available at the
23
+ * call site (the v5.6 spec § "Log redaction" requires this for all new
24
+ * adapters).
25
+ *
26
+ * Pass extra patterns when the caller has loaded
27
+ * `config.persistence.redactionPatterns`; otherwise omit the argument and
28
+ * the defaults handle the well-known token shapes.
29
+ */
30
+ export function redactLogLines(text, patterns) {
31
+ if (!text)
32
+ return text;
33
+ const merged = patterns && patterns.length > 0
34
+ ? [...DEFAULT_REDACTION_PATTERNS, ...patterns]
35
+ : DEFAULT_REDACTION_PATTERNS;
36
+ return applyRedaction(text, merged);
37
+ }
18
38
  //# sourceMappingURL=redaction.js.map
@@ -54,7 +54,20 @@ function buildValidator() {
54
54
  }
55
55
  return ajv.compile(schema);
56
56
  }
57
- const validate = buildValidator();
57
+ // Lazy-init: previously the validator was built at module load, which meant
58
+ // every `claude-autopilot --version` (or any CLI invocation that imports the
59
+ // migrate module via dispatch chain) eagerly read presets/aliases.lock.json
60
+ // + presets/schemas/migrate.schema.json. Missing files crashed the entire
61
+ // CLI with a stack trace before the user-facing entry point even started.
62
+ // Rebuilding on first use also keeps tests that don't touch validation
63
+ // from paying the AJV compile cost. Caught by the tombstone-bin test
64
+ // (`does not leak a node stack trace when claude-autopilot is unreachable`).
65
+ let _validate;
66
+ function getValidator() {
67
+ if (_validate === undefined)
68
+ _validate = buildValidator();
69
+ return _validate;
70
+ }
58
71
  function commandsEqual(a, b) {
59
72
  return JSON.stringify(a) === JSON.stringify(b);
60
73
  }
@@ -99,6 +112,7 @@ export function validateStackMd(yamlSource) {
99
112
  }],
100
113
  };
101
114
  }
115
+ const validate = getValidator();
102
116
  const ok = validate(parsed);
103
117
  const schemaErrors = ok ? [] : ajvErrorsToValidationErrors(validate.errors ?? []);
104
118
  const crossFieldErrors = ok ? checkDevCommandReuse(parsed) : [];
@@ -1,10 +1,14 @@
1
1
  import type { Finding, FixAttempt, FixStatus } from '../findings/types.ts';
2
2
  import type { GuardrailConfig } from '../config/types.ts';
3
3
  import type { ReviewEngine } from '../../adapters/review-engine/types.ts';
4
+ export interface StaticRuleContext {
5
+ config?: GuardrailConfig;
6
+ engine?: ReviewEngine;
7
+ }
4
8
  export interface StaticRule {
5
9
  name: string;
6
10
  severity: 'critical' | 'warning' | 'note';
7
- check(touchedFiles: string[], config?: Record<string, unknown>): Promise<Finding[]>;
11
+ check(touchedFiles: string[], ctx?: StaticRuleContext): Promise<Finding[]>;
8
12
  autofix?(finding: Finding): Promise<FixStatus>;
9
13
  }
10
14
  export interface StaticRulesPhaseInput {
@@ -42,13 +42,10 @@ export async function runStaticRulesPhase(input) {
42
42
  return { phase: 'static-rules', status, findings: preFixFindings, fixAttempts, durationMs: Date.now() - start };
43
43
  }
44
44
  async function runAllChecks(rules, files, config, engine) {
45
- const ruleConfig = {
46
- ...(config ? config : {}),
47
- _engine: engine,
48
- };
45
+ const ctx = { config, engine };
49
46
  const all = [];
50
47
  for (const rule of rules)
51
- all.push(...(await rule.check(files, ruleConfig)));
48
+ all.push(...(await rule.check(files, ctx)));
52
49
  return all;
53
50
  }
54
51
  function findRuleForFinding(rules, finding) {
@@ -0,0 +1,88 @@
1
+ /** Default Layer 2 reserve when none is configured. Conservative — phases
2
+ * without an `estimateCost` are assumed to consume at least this much,
3
+ * which keeps the cap from "failing open" the moment a phase forgets to
4
+ * declare its cost shape. */
5
+ export declare const DEFAULT_CONSERVATIVE_PHASE_RESERVE_USD = 5;
6
+ export interface BudgetConfig {
7
+ /** Total run cap (USD). Hard stop. Required — phases that don't want
8
+ * budget enforcement should not pass a `BudgetConfig` at all. */
9
+ perRunUSD: number;
10
+ /** Per-phase cap (USD). Phases that haven't declared `estimateCost`
11
+ * still pay the conservativePhaseReserve under Layer 2. Optional. */
12
+ perPhaseUSD?: number;
13
+ /** Bounded recursion for council synthesizer. Wired in
14
+ * `src/core/council/runner.ts`; no effect inside `runPhase`. */
15
+ councilMaxRecursionDepth?: number;
16
+ /** Bounded autopilot self-eat rounds (per spec). Reserved field —
17
+ * consumed by the autopilot orchestrator, not the runner. */
18
+ bgAutopilotMaxRoundsPerSelfEat?: number;
19
+ /** Used by Layer 2 (mandatory runtime guard) when a phase has no
20
+ * `estimateCost` — represents the "we don't know how big this gets,
21
+ * reserve at least this much from the cap" floor. Defaults to
22
+ * `DEFAULT_CONSERVATIVE_PHASE_RESERVE_USD` when omitted. */
23
+ conservativePhaseReserveUSD?: number;
24
+ /** v6.2.0 — budget scope. `'phase'` (default) keeps the legacy
25
+ * per-phase semantics where each `runPhase` invocation reasons against
26
+ * its own phase budget (back-compat for single-phase wrappers like
27
+ * `runPhaseWithLifecycle`). `'run'` is the orchestrator's
28
+ * cross-phase mode: the actualSoFar reservoir already sums every
29
+ * prior `phase.cost` event in the run, so `perRunUSD` is policed
30
+ * monotonically against the WHOLE pipeline's spend.
31
+ *
32
+ * In practice the policy math is identical between the two scopes —
33
+ * Layer 1 + Layer 2 both consume `actualSoFarUSD` regardless. The
34
+ * scope flag exists so the `budget.check` event tells observers
35
+ * which mode produced the decision (so a CI dashboard can attribute
36
+ * a reject to "run scope" vs "single-phase scope") and so future
37
+ * policy changes (e.g. divergent perPhase reserves under run scope)
38
+ * have a place to land without an event-shape break.
39
+ *
40
+ * Per spec docs/specs/v6.2-multi-phase-orchestrator.md "Budget
41
+ * enforcement": `checkPhaseBudget` gains `scope: 'phase' | 'run'`
42
+ * (default 'phase' for back-compat). Orchestrator passes
43
+ * `scope: 'run'`; per-phase callers keep the default. */
44
+ scope?: 'phase' | 'run';
45
+ }
46
+ /** The decision the runner consumes. Mirrors the `budget.check` event
47
+ * payload one-to-one so wiring is trivial. */
48
+ export interface BudgetCheck {
49
+ decision: 'proceed' | 'pause' | 'hard-fail';
50
+ phase: string;
51
+ phaseIdx: number;
52
+ /** `estimate.high` from the phase's `estimateCost` if it returned a
53
+ * value; null when the phase doesn't implement estimateCost. */
54
+ estimatedHigh: number | null;
55
+ actualSoFar: number;
56
+ /** The reserve the policy deducted against `perRunUSD` for this phase
57
+ * (the larger of `estimate.high` and `conservativePhaseReserveUSD`). */
58
+ reserveApplied: number;
59
+ /** USD remaining under `perRunUSD` after `actualSoFar` + the larger of
60
+ * `estimatedHigh` and `reserveApplied`. May be negative on hard-fail. */
61
+ capRemaining: number;
62
+ reason: string;
63
+ /** v6.2.0 — which scope produced the decision. Echoes `BudgetConfig.scope`
64
+ * back into the `budget.check` event so observers can attribute
65
+ * cross-phase rejections to the orchestrator vs single-phase wrappers
66
+ * passing the legacy default. */
67
+ scope: 'phase' | 'run';
68
+ }
69
+ export interface CheckPhaseBudgetOpts {
70
+ budget: BudgetConfig;
71
+ phaseName: string;
72
+ phaseIdx: number;
73
+ /** What `RunPhase.estimateCost(input)` returned, or null if absent. */
74
+ estimatedCost: {
75
+ lowUSD: number;
76
+ highUSD: number;
77
+ } | null;
78
+ /** Sum of every prior `phase.cost` event in the run, in USD. */
79
+ actualSoFarUSD: number;
80
+ /** When true, a `pause` decision becomes `hard-fail` (CI / `--json`
81
+ * mode can't prompt for human approval). */
82
+ nonInteractive: boolean;
83
+ }
84
+ /** Policy decision for a single about-to-run phase. Pure — no IO. The
85
+ * caller (`runPhase`) is responsible for emitting the `budget.check`
86
+ * event with this payload and acting on the decision. */
87
+ export declare function checkPhaseBudget(opts: CheckPhaseBudgetOpts): BudgetCheck;
88
+ //# sourceMappingURL=budget.d.ts.map
@@ -0,0 +1,141 @@
1
+ // src/core/run-state/budget.ts
2
+ //
3
+ // v6 Phase 4 — budget enforcement policy.
4
+ //
5
+ // Pure data + a pure decision function. No IO, no globals, no side effects.
6
+ // `checkPhaseBudget` is the authoritative answer to "may this phase run?"
7
+ // — `runPhase` consumes the result, emits a `budget.check` event with the
8
+ // full payload, and throws `budget_exceeded` on hard-fail.
9
+ //
10
+ // Two-layer policy per spec (Codex CRITICAL #3 fold-in — estimates can fail
11
+ // open, the runtime guard MUST run independently):
12
+ //
13
+ // - Layer 1 (advisory) — only fires when the phase declares
14
+ // `estimateCost`. Compares `actualSoFar + estimate.high` against
15
+ // `perRunUSD`. Pause-and-prompt (interactive) or hard-fail (CI mode)
16
+ // if it would exceed.
17
+ // - Layer 2 (mandatory) — ALWAYS runs. Compares `actualSoFar +
18
+ // conservativePhaseReserveUSD` against `perRunUSD`. Phases without
19
+ // estimates therefore still trigger budget gates. Default reserve is
20
+ // $5 (overridable in config).
21
+ // - `perPhaseUSD` gate — if set AND the larger of the per-phase estimate
22
+ // or reserve would push this phase's cost over the per-phase cap,
23
+ // applies the same pause/hard-fail rule.
24
+ //
25
+ // Spec: docs/specs/v6-run-state-engine.md "Budget enforcement".
26
+ /** Default Layer 2 reserve when none is configured. Conservative — phases
27
+ * without an `estimateCost` are assumed to consume at least this much,
28
+ * which keeps the cap from "failing open" the moment a phase forgets to
29
+ * declare its cost shape. */
30
+ export const DEFAULT_CONSERVATIVE_PHASE_RESERVE_USD = 5;
31
+ /** Policy decision for a single about-to-run phase. Pure — no IO. The
32
+ * caller (`runPhase`) is responsible for emitting the `budget.check`
33
+ * event with this payload and acting on the decision. */
34
+ export function checkPhaseBudget(opts) {
35
+ const { budget, phaseName, phaseIdx, estimatedCost, actualSoFarUSD, nonInteractive, } = opts;
36
+ // v6.2.0 — `'phase'` (default for back-compat) vs `'run'` (orchestrator).
37
+ // The math is intentionally identical between the two; `actualSoFarUSD`
38
+ // is already the cross-phase sum produced by `sumRunCost` in
39
+ // phase-runner.ts. The flag exists so the `budget.check` event tells
40
+ // observers which scope generated the decision and so future policy
41
+ // tweaks (e.g. divergent perPhase reserves under run scope) have a
42
+ // place to land without an event-shape break.
43
+ const scope = budget.scope ?? 'phase';
44
+ const reserveFloor = typeof budget.conservativePhaseReserveUSD === 'number'
45
+ ? budget.conservativePhaseReserveUSD
46
+ : DEFAULT_CONSERVATIVE_PHASE_RESERVE_USD;
47
+ // The reserve actually deducted is the larger of "what the phase says
48
+ // it will cost (high end)" and "the conservative floor we always apply".
49
+ // This is the core of Codex CRITICAL #3 — even if estimateCost is
50
+ // present and tiny, the floor still applies, and even if estimateCost
51
+ // is absent, the floor still applies.
52
+ const estimatedHigh = estimatedCost?.highUSD ?? null;
53
+ const reserveApplied = Math.max(estimatedHigh ?? 0, reserveFloor);
54
+ const projected = actualSoFarUSD + reserveApplied;
55
+ const capRemaining = budget.perRunUSD - projected;
56
+ // Layer 1 — ADVISORY using the explicit estimate. Runs FIRST so a precise
57
+ // estimate produces a precise reason ("estimate would exceed cap") instead
58
+ // of falling through to Layer 2's conservative-floor wording. Only fires
59
+ // when an estimate is present AND would push us past perRunUSD on its own.
60
+ // (Bugbot LOW on PR #89 caught the prior ordering, where Layer 2 always
61
+ // ran first and Layer 1 was provably unreachable since `reserveApplied =
62
+ // max(estimatedHigh, floor) >= estimatedHigh`.)
63
+ if (estimatedHigh !== null && actualSoFarUSD + estimatedHigh > budget.perRunUSD) {
64
+ const decision = nonInteractive ? 'hard-fail' : 'pause';
65
+ return {
66
+ decision,
67
+ phase: phaseName,
68
+ phaseIdx,
69
+ estimatedHigh,
70
+ actualSoFar: actualSoFarUSD,
71
+ reserveApplied,
72
+ capRemaining: budget.perRunUSD - (actualSoFarUSD + estimatedHigh),
73
+ reason: `advisory estimate would exceed run cap — actual ` +
74
+ `$${fmtUSD(actualSoFarUSD)} + estimate.high ` +
75
+ `$${fmtUSD(estimatedHigh)} > perRunUSD $${fmtUSD(budget.perRunUSD)}`,
76
+ scope,
77
+ };
78
+ }
79
+ // Layer 2 — MANDATORY floor against perRunUSD. Catches the case where the
80
+ // estimate is missing (Layer 1 didn't fire) OR present-but-tiny (estimate
81
+ // alone fits, but the conservative reserve floor pushes over). This is the
82
+ // safety net that prevents phases without `estimateCost` from sneaking
83
+ // past the cap.
84
+ if (projected > budget.perRunUSD) {
85
+ const decision = nonInteractive ? 'hard-fail' : 'pause';
86
+ return {
87
+ decision,
88
+ phase: phaseName,
89
+ phaseIdx,
90
+ estimatedHigh,
91
+ actualSoFar: actualSoFarUSD,
92
+ reserveApplied,
93
+ capRemaining,
94
+ reason: `run cap exceeded — actual $${fmtUSD(actualSoFarUSD)} + reserve ` +
95
+ `$${fmtUSD(reserveApplied)} = $${fmtUSD(projected)} > perRunUSD ` +
96
+ `$${fmtUSD(budget.perRunUSD)}`,
97
+ scope,
98
+ };
99
+ }
100
+ // perPhaseUSD gate — independent of the run cap. Applies the same
101
+ // reserve logic but compares against the per-phase cap.
102
+ if (typeof budget.perPhaseUSD === 'number' && reserveApplied > budget.perPhaseUSD) {
103
+ const decision = nonInteractive ? 'hard-fail' : 'pause';
104
+ return {
105
+ decision,
106
+ phase: phaseName,
107
+ phaseIdx,
108
+ estimatedHigh,
109
+ actualSoFar: actualSoFarUSD,
110
+ reserveApplied,
111
+ capRemaining,
112
+ reason: `per-phase cap exceeded — reserve $${fmtUSD(reserveApplied)} > ` +
113
+ `perPhaseUSD $${fmtUSD(budget.perPhaseUSD)}`,
114
+ scope,
115
+ };
116
+ }
117
+ return {
118
+ decision: 'proceed',
119
+ phase: phaseName,
120
+ phaseIdx,
121
+ estimatedHigh,
122
+ actualSoFar: actualSoFarUSD,
123
+ reserveApplied,
124
+ capRemaining,
125
+ reason: estimatedHigh !== null
126
+ ? `within budget — projected $${fmtUSD(projected)} of $${fmtUSD(budget.perRunUSD)}`
127
+ : `within budget (no estimate, applied $${fmtUSD(reserveApplied)} ` +
128
+ `reserve floor) — projected $${fmtUSD(projected)} of ` +
129
+ `$${fmtUSD(budget.perRunUSD)}`,
130
+ scope,
131
+ };
132
+ }
133
+ /** Format a USD amount with 2 decimal places for human-readable reasons.
134
+ * Kept local — the run-state module doesn't have a shared formatter and
135
+ * budget reasons are the only consumer. */
136
+ function fmtUSD(n) {
137
+ // toFixed(2) returns "0.00" for 0; we keep the trailing zeros so the
138
+ // reason strings line up visually in CLI output.
139
+ return n.toFixed(2);
140
+ }
141
+ //# sourceMappingURL=budget.js.map