@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
@@ -8,6 +8,8 @@ import { loadIgnoreRules, parseConfigIgnore, applyIgnoreRules } from "../core/ig
8
8
  import { saveCachedFindings } from "../core/persist/findings-cache.js";
9
9
  import { appendCostLog } from "../core/persist/cost-log.js";
10
10
  import { detectLLMKey, LLM_KEY_HINTS } from "../core/detect/llm-key.js";
11
+ import { resolveEngineEnabled } from "../core/run-state/resolve-engine.js";
12
+ import { runPhaseWithLifecycle } from "../core/run-state/run-phase-with-lifecycle.js";
11
13
  const C = {
12
14
  reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
13
15
  green: '\x1b[32m', yellow: '\x1b[33m', red: '\x1b[31m', cyan: '\x1b[36m',
@@ -50,7 +52,17 @@ function collectFiles(target, cwd) {
50
52
  function collectAllFiles(cwd) {
51
53
  return collectFiles(cwd, cwd);
52
54
  }
53
- export async function runScan(options = {}) {
55
+ /**
56
+ * v6.2.0 — extract the `RunPhase<ScanInput, ScanOutput>` construction out of
57
+ * `runScan(options)` so the new top-level `autopilot` orchestrator can drive
58
+ * `runPhase` itself with a shared `phaseIdx` against the same run dir.
59
+ *
60
+ * The legacy `runScan(options)` keeps calling this builder internally (then
61
+ * `runPhaseWithLifecycle` for single-phase runs) so direct CLI behavior is
62
+ * byte-for-byte identical to v6.1. Parity is asserted by
63
+ * `tests/cli/scan-builder-parity.test.ts`.
64
+ */
65
+ export async function buildScanPhase(options) {
54
66
  const cwd = options.cwd ?? process.cwd();
55
67
  const configPath = options.configPath ?? path.join(cwd, 'guardrail.config.yaml');
56
68
  let config = { configVersion: 1 };
@@ -73,46 +85,52 @@ export async function runScan(options = {}) {
73
85
  console.error(fmt('dim', ' guardrail scan src/auth/'));
74
86
  console.error(fmt('dim', ' guardrail scan --all'));
75
87
  console.error(fmt('dim', ' guardrail scan --ask "is there SQL injection?" src/db/'));
76
- return 1;
88
+ return { kind: 'early-exit', exitCode: 1 };
77
89
  }
78
90
  // Deduplicate
79
91
  files = [...new Set(files)];
80
92
  if (files.length === 0) {
81
93
  console.log(fmt('yellow', '[scan] No code files found at the specified path(s)'));
82
- return 0;
94
+ return { kind: 'early-exit', exitCode: 0 };
83
95
  }
84
96
  if (options.dryRun) {
85
97
  console.log(fmt('bold', `[scan] Would scan ${files.length} file(s):`));
86
98
  for (const f of files)
87
99
  console.log(fmt('dim', ` ${path.relative(cwd, f)}`));
88
- return 0;
100
+ return { kind: 'early-exit', exitCode: 0 };
89
101
  }
90
102
  // Auto-detect stack if not in config
91
103
  if (!config.stack) {
92
104
  config = { ...config, stack: detectStack(cwd) ?? undefined };
93
105
  }
94
106
  // Build review engine
95
- if (!detectLLMKey().hasKey) {
96
- console.error(fmt('red', '[scan] No LLM API key — set one of:'));
97
- for (const { name, url, note } of LLM_KEY_HINTS) {
98
- const suffix = note ? ` (${note})` : '';
99
- console.error(fmt('dim', ` ${name.padEnd(18)} ${url}${suffix}`));
100
- }
101
- return 1;
102
- }
103
- const engineRef = typeof config.reviewEngine === 'string' ? config.reviewEngine
104
- : (config.reviewEngine?.adapter ?? 'auto');
105
107
  let engine;
106
- try {
107
- engine = await loadAdapter({
108
- point: 'review-engine',
109
- ref: engineRef,
110
- options: typeof config.reviewEngine === 'object' ? config.reviewEngine.options : undefined,
111
- });
108
+ if (options.__testReviewEngine) {
109
+ // Test-only fast path — skip the LLM key check and the adapter loader.
110
+ engine = options.__testReviewEngine;
112
111
  }
113
- catch (err) {
114
- console.error(fmt('red', `[scan] Could not load review engine: ${err instanceof Error ? err.message : String(err)}`));
115
- return 1;
112
+ else {
113
+ if (!detectLLMKey().hasKey) {
114
+ console.error(fmt('red', '[scan] No LLM API key — set one of:'));
115
+ for (const { name, url, note } of LLM_KEY_HINTS) {
116
+ const suffix = note ? ` (${note})` : '';
117
+ console.error(fmt('dim', ` ${name.padEnd(18)} ${url}${suffix}`));
118
+ }
119
+ return { kind: 'early-exit', exitCode: 1 };
120
+ }
121
+ const engineRef = typeof config.reviewEngine === 'string' ? config.reviewEngine
122
+ : (config.reviewEngine?.adapter ?? 'auto');
123
+ try {
124
+ engine = await loadAdapter({
125
+ point: 'review-engine',
126
+ ref: engineRef,
127
+ options: typeof config.reviewEngine === 'object' ? config.reviewEngine.options : undefined,
128
+ });
129
+ }
130
+ catch (err) {
131
+ console.error(fmt('red', `[scan] Could not load review engine: ${err instanceof Error ? err.message : String(err)}`));
132
+ return { kind: 'early-exit', exitCode: 1 };
133
+ }
116
134
  }
117
135
  const focusLabel = options.focus && options.focus !== 'all' ? options.focus : null;
118
136
  const relFiles = files.map(f => path.relative(cwd, f));
@@ -123,9 +141,94 @@ export async function runScan(options = {}) {
123
141
  console.log(fmt('dim', ` question: ${options.ask}`));
124
142
  if (focusLabel)
125
143
  console.log(fmt('dim', ` focus: ${focusLabel}`));
144
+ // Pre-flight resolution just for the banner; the helper re-resolves
145
+ // internally with identical precedence so the engine path stays
146
+ // deterministic. Keeping this inline preserves the legacy "engine: on
147
+ // (<source>)" banner that scan emitted before v6.0.6.
148
+ const engineBanner = resolveEngineEnabled({
149
+ ...(options.cliEngine !== undefined ? { cliEngine: options.cliEngine } : {}),
150
+ ...(options.envEngine !== undefined ? { envValue: options.envEngine } : {}),
151
+ ...(typeof config.engine?.enabled === 'boolean' ? { configEnabled: config.engine.enabled } : {}),
152
+ });
153
+ if (engineBanner.enabled) {
154
+ console.log(fmt('dim', ` engine: on (${engineBanner.source})`));
155
+ }
126
156
  console.log('');
127
157
  // Build a focused git summary / prompt context
128
158
  const focusHint = buildFocusHint(options.ask, focusLabel);
159
+ const scanInput = {
160
+ files,
161
+ relFiles,
162
+ cwd,
163
+ config,
164
+ engine,
165
+ focusHint,
166
+ ...(options.ask !== undefined ? { ask: options.ask } : {}),
167
+ focusLabel,
168
+ all: options.all === true,
169
+ };
170
+ // The wrapped phase body — pure call-the-LLM-and-process-findings work.
171
+ // Extracted into a RunPhase so the engine path and the legacy path share
172
+ // the exact same logic. Engine-off callers invoke this directly via
173
+ // `executeScanPhase()`; engine-on callers route through `runPhase()`.
174
+ const phase = {
175
+ name: 'scan',
176
+ // scan re-issues identical LLM queries against the same code — re-running
177
+ // is safe and cheap-ish to retry.
178
+ idempotent: true,
179
+ // No git push, no PR comment, no provider-side mutation. The cost-log
180
+ // append + findings-cache write are local file IO that's already
181
+ // overwrite-style; replays are safe.
182
+ hasSideEffects: false,
183
+ run: executeScanPhase,
184
+ };
185
+ return {
186
+ kind: 'phase',
187
+ phase,
188
+ input: scanInput,
189
+ config,
190
+ renderResult: (output) => renderScanOutput(output, scanInput),
191
+ };
192
+ }
193
+ export async function runScan(options = {}) {
194
+ const built = await buildScanPhase(options);
195
+ if (built.kind === 'early-exit')
196
+ return built.exitCode;
197
+ const { phase, input, config, renderResult } = built;
198
+ // v6.0.6 — lifecycle wiring (createRun → runPhase → run.complete + state
199
+ // snapshot + lock release) lives in `runPhaseWithLifecycle`. The helper
200
+ // owns the engine-on/engine-off branch and the failure banner; the caller
201
+ // just supplies the phase, the input, and the engine-off escape hatch.
202
+ let output;
203
+ try {
204
+ const result = await runPhaseWithLifecycle({
205
+ cwd: input.cwd,
206
+ phase,
207
+ input,
208
+ config,
209
+ cliEngine: options.cliEngine,
210
+ envEngine: options.envEngine,
211
+ runEngineOff: () => executeScanPhase(input),
212
+ });
213
+ output = result.output;
214
+ }
215
+ catch {
216
+ // Helper already printed the failure banner + emitted run.complete
217
+ // failed + refreshed state.json + released the lock. Surface the
218
+ // legacy non-zero exit so existing CI / scripts are unaffected.
219
+ return 1;
220
+ }
221
+ return renderResult(output);
222
+ }
223
+ // ---------------------------------------------------------------------------
224
+ // Phase body — the LLM call + finding processing + cost-log append + findings
225
+ // cache write. Extracted from runScan so the engine-on path can wrap it via
226
+ // `runPhase` and the engine-off path can call it directly. Returns a
227
+ // JSON-serializable ScanOutput so the engine can persist it as `result` on
228
+ // the phase snapshot.
229
+ // ---------------------------------------------------------------------------
230
+ async function executeScanPhase(input) {
231
+ const { files, relFiles, cwd, config, engine, focusHint, ask } = input;
129
232
  const result = await runReviewPhase({
130
233
  touchedFiles: relFiles,
131
234
  engine,
@@ -149,11 +252,47 @@ export async function runScan(options = {}) {
149
252
  // Apply ignore rules
150
253
  const ignoreRules = [...loadIgnoreRules(cwd), ...parseConfigIgnore(config.ignore)];
151
254
  const findings = applyIgnoreRules(result.findings, ignoreRules);
255
+ // Persist findings so `guardrail fix` can read them
256
+ saveCachedFindings(cwd, findings);
257
+ // Persist run to cost log so `claude-autopilot costs` reflects scans, not
258
+ // just full pipeline runs. Previously scan never wrote to the log, so the
259
+ // costs report stayed frozen at whatever the last `run` invocation produced.
260
+ appendCostLog(cwd, {
261
+ timestamp: new Date().toISOString(),
262
+ files: files.length,
263
+ inputTokens: result.usage?.input ?? 0,
264
+ outputTokens: result.usage?.output ?? 0,
265
+ costUSD: result.costUSD ?? 0,
266
+ durationMs: result.durationMs,
267
+ });
268
+ return {
269
+ fileCount: files.length,
270
+ findings: findings.map(f => ({
271
+ severity: f.severity,
272
+ message: f.message,
273
+ ...(f.file !== undefined ? { file: f.file } : {}),
274
+ ...(f.line !== undefined ? { line: f.line } : {}),
275
+ ...(f.suggestion !== undefined ? { suggestion: f.suggestion } : {}),
276
+ })),
277
+ ...(result.costUSD !== undefined ? { costUSD: result.costUSD } : {}),
278
+ durationMs: result.durationMs,
279
+ ...(result.rawOutputs !== undefined ? { rawOutputs: result.rawOutputs } : {}),
280
+ };
281
+ }
282
+ // ---------------------------------------------------------------------------
283
+ // Render — translate ScanOutput back to the legacy stdout banner + exit code.
284
+ // Lives outside the wrapped phase because it's pure presentation; doing the
285
+ // rendering inside the phase would couple the engine path's idempotency to
286
+ // console output, which we don't want.
287
+ // ---------------------------------------------------------------------------
288
+ function renderScanOutput(output, input) {
289
+ const { findings, costUSD, durationMs, rawOutputs } = output;
290
+ const { ask } = input;
152
291
  // Print results
153
- if (findings.length === 0 && options.ask && result.rawOutputs && result.rawOutputs.length > 0) {
292
+ if (findings.length === 0 && ask && rawOutputs && rawOutputs.length > 0) {
154
293
  // --ask returned prose rather than structured findings — surface raw response
155
294
  console.log(fmt('cyan', `Answer:`));
156
- for (const raw of result.rawOutputs) {
295
+ for (const raw of rawOutputs) {
157
296
  // Strip markdown fences and the ## Findings / ## Review Summary headers if present
158
297
  const cleaned = raw.replace(/^##\s+Review Summary\s*\n/gm, '').replace(/^##\s+Findings\s*\n/gm, '').trim();
159
298
  console.log(cleaned);
@@ -196,21 +335,8 @@ export async function runScan(options = {}) {
196
335
  console.log('');
197
336
  }
198
337
  }
199
- // Persist findings so `guardrail fix` can read them
200
- saveCachedFindings(cwd, findings);
201
- // Persist run to cost log so `claude-autopilot costs` reflects scans, not
202
- // just full pipeline runs. Previously scan never wrote to the log, so the
203
- // costs report stayed frozen at whatever the last `run` invocation produced.
204
- appendCostLog(cwd, {
205
- timestamp: new Date().toISOString(),
206
- files: files.length,
207
- inputTokens: result.usage?.input ?? 0,
208
- outputTokens: result.usage?.output ?? 0,
209
- costUSD: result.costUSD ?? 0,
210
- durationMs: result.durationMs,
211
- });
212
- if (result.costUSD !== undefined) {
213
- console.log(fmt('dim', ` $${result.costUSD.toFixed(4)} · ${result.durationMs}ms`));
338
+ if (costUSD !== undefined) {
339
+ console.log(fmt('dim', ` $${costUSD.toFixed(4)} · ${durationMs}ms`));
214
340
  }
215
341
  const fixable = findings.filter(f => f.severity === 'critical' || f.severity === 'warning');
216
342
  if (fixable.length > 0) {
@@ -1,3 +1,4 @@
1
+ import { type DetectionResult } from './detector.ts';
1
2
  export type ProfileName = 'security-strict' | 'team' | 'solo';
2
3
  export interface SetupOptions {
3
4
  cwd?: string;
@@ -6,4 +7,33 @@ export interface SetupOptions {
6
7
  profile?: ProfileName;
7
8
  }
8
9
  export declare function runSetup(options?: SetupOptions): Promise<void>;
10
+ /**
11
+ * Append `entries` to `<cwd>/.gitignore` if missing. Returns the entries
12
+ * actually added (empty array when all already present, .gitignore is empty
13
+ * + we don't want to create one, etc.).
14
+ *
15
+ * Behavior:
16
+ * - .gitignore exists: parse line-by-line, skip entries already present
17
+ * (exact match after trim, ignoring leading `!`), append the rest.
18
+ * - .gitignore missing: create it with the entries. Reasonable default
19
+ * for a fresh `setup` since the user is opting into autopilot's cache.
20
+ *
21
+ * Idempotent: safe to call twice with the same entries.
22
+ */
23
+ export declare function ensureGitignoreEntries(cwd: string, entries: string[]): Promise<string[]>;
24
+ /**
25
+ * Write a starter `<cwd>/CLAUDE.md` if none exists. Pulls stack-detection
26
+ * info from the same `detection` result that drove preset selection, so the
27
+ * scaffolded conventions match the actual project.
28
+ *
29
+ * The starter doc is intentionally short (~35 lines) — a real project will
30
+ * grow it. The goal is to give downstream agents an anchor for the most
31
+ * common "I had to guess" decisions the v7.1.6 benchmark agent reported:
32
+ * commit-message style, test command, error class shape, prompt location.
33
+ *
34
+ * Returns true when the file was written (false if it already exists; we
35
+ * never overwrite — operator opted into autopilot, not into us nuking
36
+ * their docs).
37
+ */
38
+ export declare function ensureStarterClaudeMd(cwd: string, detection: DetectionResult): Promise<boolean>;
9
39
  //# sourceMappingURL=setup.d.ts.map
@@ -138,6 +138,26 @@ export async function runSetup(options = {}) {
138
138
  for (const line of presetContent.trimEnd().split('\n')) {
139
139
  console.log(` ${DIM(line)}`);
140
140
  }
141
+ // v7.1.7 — Auto-add `.guardrail-cache/` and `node_modules/` to .gitignore.
142
+ // Per the v7.1.6 blank-repo benchmark, these are the two most common
143
+ // day-1 paper cuts: `setup` creates the cache dir on first run, and (for
144
+ // Node projects) `npm install` creates `node_modules` — neither belongs
145
+ // in git. Skipped silently if already present or no .gitignore exists
146
+ // and we don't want to create one without consent.
147
+ const gitignoreAdds = await ensureGitignoreEntries(cwd, [
148
+ '.guardrail-cache/',
149
+ 'node_modules/',
150
+ ]);
151
+ if (gitignoreAdds.length > 0) {
152
+ console.log(`\n ${PASS} Added to .gitignore: ${DIM(gitignoreAdds.join(', '))}`);
153
+ }
154
+ // v7.1.7 — Auto-scaffold a starter CLAUDE.md if none exists. Closes ~5 of
155
+ // 6 friction points the benchmark agent hit on a blank repo (commit
156
+ // style, error class shape, test runner choice, etc.).
157
+ const claudeMdAdded = await ensureStarterClaudeMd(cwd, detection);
158
+ if (claudeMdAdded) {
159
+ console.log(` ${PASS} Wrote starter CLAUDE.md`);
160
+ }
141
161
  let hookInstalled = false;
142
162
  if (!options.skipHook) {
143
163
  const hookCode = await runHook('install', { cwd, silent: true });
@@ -152,6 +172,19 @@ export async function runSetup(options = {}) {
152
172
  console.log('\nChecking prerequisites…');
153
173
  await runDoctor();
154
174
  console.log(`\n${BOLD('Next steps:')}\n`);
175
+ // v7.1.9 — Generic+low-confidence detection prompt. The v7.1.8 benchmark
176
+ // re-run on a truly blank repo (no package.json / go.mod / language signal)
177
+ // surfaced this: setup runs fine but downstream agents get a CLAUDE.md
178
+ // saying "Detected: Generic (low confidence)" with no concrete next step
179
+ // to improve detection. Surfacing the actionable "scaffold a stack file
180
+ // first" hint converts a paper-cut into a one-liner.
181
+ if (detection.preset === 'generic' && detection.confidence === 'low') {
182
+ console.log(` ${WARN} ${CYAN('Stack detection: Generic (low confidence).')}`);
183
+ console.log(` For higher-quality reviews + stack-specific presets, scaffold a`);
184
+ console.log(` package manifest first, then re-run setup:`);
185
+ console.log(` npm init -y ${DIM('# or: pnpm init, go mod init, cargo init')}`);
186
+ console.log(` npx claude-autopilot setup --force ${DIM('# re-detect with the new manifest')}\n`);
187
+ }
155
188
  if (!hasKey) {
156
189
  console.log(` 1. ${CYAN('Set an LLM API key')} — guardrail needs one to review code:`);
157
190
  console.log(` export ANTHROPIC_API_KEY=sk-ant-... # https://console.anthropic.com/`);
@@ -175,4 +208,108 @@ export async function runSetup(options = {}) {
175
208
  }
176
209
  }
177
210
  }
211
+ // ---------------------------------------------------------------------------
212
+ // v7.1.7 — setup-verb day-1 polish helpers
213
+ // ---------------------------------------------------------------------------
214
+ /**
215
+ * Append `entries` to `<cwd>/.gitignore` if missing. Returns the entries
216
+ * actually added (empty array when all already present, .gitignore is empty
217
+ * + we don't want to create one, etc.).
218
+ *
219
+ * Behavior:
220
+ * - .gitignore exists: parse line-by-line, skip entries already present
221
+ * (exact match after trim, ignoring leading `!`), append the rest.
222
+ * - .gitignore missing: create it with the entries. Reasonable default
223
+ * for a fresh `setup` since the user is opting into autopilot's cache.
224
+ *
225
+ * Idempotent: safe to call twice with the same entries.
226
+ */
227
+ export async function ensureGitignoreEntries(cwd, entries) {
228
+ const gitignorePath = path.join(cwd, '.gitignore');
229
+ let existing = [];
230
+ let existingContent = '';
231
+ try {
232
+ existingContent = await fsAsync.readFile(gitignorePath, 'utf8');
233
+ existing = existingContent
234
+ .split('\n')
235
+ .map((l) => l.trim())
236
+ .filter((l) => l.length > 0 && !l.startsWith('#'));
237
+ }
238
+ catch {
239
+ // File doesn't exist — that's fine, we'll create it below.
240
+ }
241
+ const present = new Set(existing.map((l) => l.replace(/^!/, '')));
242
+ const missing = entries.filter((e) => !present.has(e.replace(/^!/, '')));
243
+ if (missing.length === 0)
244
+ return [];
245
+ // Build the appended block. Add a trailing newline first so we don't
246
+ // collide with a no-final-newline file.
247
+ const needsLeadingNewline = existingContent.length > 0 && !existingContent.endsWith('\n');
248
+ const block = (needsLeadingNewline ? '\n' : '') +
249
+ (existingContent.length > 0 ? '# claude-autopilot (v7.1.7+)\n' : '') +
250
+ missing.join('\n') +
251
+ '\n';
252
+ await fsAsync.writeFile(gitignorePath, existingContent + block, 'utf8');
253
+ return missing;
254
+ }
255
+ /**
256
+ * Write a starter `<cwd>/CLAUDE.md` if none exists. Pulls stack-detection
257
+ * info from the same `detection` result that drove preset selection, so the
258
+ * scaffolded conventions match the actual project.
259
+ *
260
+ * The starter doc is intentionally short (~35 lines) — a real project will
261
+ * grow it. The goal is to give downstream agents an anchor for the most
262
+ * common "I had to guess" decisions the v7.1.6 benchmark agent reported:
263
+ * commit-message style, test command, error class shape, prompt location.
264
+ *
265
+ * Returns true when the file was written (false if it already exists; we
266
+ * never overwrite — operator opted into autopilot, not into us nuking
267
+ * their docs).
268
+ */
269
+ export async function ensureStarterClaudeMd(cwd, detection) {
270
+ const dest = path.join(cwd, 'CLAUDE.md');
271
+ if (fs.existsSync(dest))
272
+ return false;
273
+ const stackLabel = PRESET_LABELS[detection.preset] ?? detection.preset;
274
+ const today = new Date().toISOString().slice(0, 10);
275
+ const body = [
276
+ `# CLAUDE.md`,
277
+ ``,
278
+ `Project conventions for AI-assisted contributions. Auto-scaffolded by`,
279
+ `\`claude-autopilot setup\` on ${today}; edit freely.`,
280
+ ``,
281
+ `## Stack`,
282
+ ``,
283
+ `- **Detected:** ${stackLabel} (${detection.confidence} confidence)`,
284
+ `- **Test command:** \`${detection.testCommand}\``,
285
+ `- **Evidence:** ${detection.evidence}`,
286
+ ``,
287
+ `## Conventions`,
288
+ ``,
289
+ `- **Commit messages:** Conventional Commits (\`feat:\`, \`fix:\`,`,
290
+ ` \`docs:\`, \`refactor:\`, \`test:\`, \`chore:\`). One sentence first`,
291
+ ` line, optional body.`,
292
+ `- **Branches:** \`feat/<topic>\`, \`fix/<topic>\`, \`chore/<topic>\`.`,
293
+ `- **Errors:** prefer custom \`Error\` subclasses with a string \`code\``,
294
+ ` field for programmatic handling. Example:`,
295
+ ` \`\`\`ts`,
296
+ ` class FetchFailed extends Error { code = 'fetch_failed' as const; }`,
297
+ ` \`\`\``,
298
+ `- **Tests:** colocated with source under \`tests/\` or \`__tests__/\`.`,
299
+ ` Run via \`${detection.testCommand}\`.`,
300
+ ``,
301
+ `## Patterns to mimic`,
302
+ ``,
303
+ `- TODO: as the project grows, list 2-3 example files agents should`,
304
+ ` read first to learn local style.`,
305
+ ``,
306
+ `## Common pitfalls`,
307
+ ``,
308
+ `- TODO: list any non-obvious gotchas — env-var quirks, ordering`,
309
+ ` requirements, footguns the test suite won't catch.`,
310
+ ``,
311
+ ].join('\n');
312
+ await fsAsync.writeFile(dest, body, 'utf8');
313
+ return true;
314
+ }
178
315
  //# sourceMappingURL=setup.js.map
@@ -0,0 +1,66 @@
1
+ import type { GuardrailConfig } from '../core/config/types.ts';
2
+ import { type RunPhase } from '../core/run-state/phase-runner.ts';
3
+ export interface SpecCommandOptions {
4
+ cwd?: string;
5
+ configPath?: string;
6
+ /**
7
+ * v6.0.3 — engine knob inputs. Same shape and precedence as scan / costs /
8
+ * fix / brainstorm (CLI > env > config > built-in default off in v6.0.x).
9
+ */
10
+ cliEngine?: boolean;
11
+ envEngine?: string;
12
+ /**
13
+ * Test-only seam — when true, the phase body returns its result without
14
+ * printing the advisory banner. Lets engine-smoke tests assert the
15
+ * `state.json` + `events.ndjson` lifecycle without polluting stdout.
16
+ * Production callers (the CLI dispatcher) MUST NOT pass this.
17
+ */
18
+ __silent?: boolean;
19
+ }
20
+ /**
21
+ * Phase input — minimal. The CLI verb body is a print-and-exit advisory.
22
+ * Captured as a struct so the engine path's phase body matches the
23
+ * engine-off path call signature.
24
+ *
25
+ * Exported so the v6.2.0 orchestrator's phase registry can carry the typed
26
+ * I/O shape on its `PhaseRegistration<SpecInput, SpecOutput>` slot.
27
+ */
28
+ export interface SpecInput {
29
+ cwd: string;
30
+ silent: boolean;
31
+ }
32
+ /**
33
+ * Phase output — JSON-serializable acknowledgment. Mirrors the shape of
34
+ * `BrainstormOutput`. Persisted as `result` on `phases/spec.json`.
35
+ */
36
+ export interface SpecOutput {
37
+ /** Always 'advisory' for v6.0.3 — the CLI verb is a Claude Code pointer. */
38
+ kind: 'advisory';
39
+ nextActions: string[];
40
+ }
41
+ /** v6.2.0 — see scan.ts for the kind='early-exit' rationale. Spec has no
42
+ * early-exit branches today (it just creates a config struct and returns
43
+ * an advisory) so this discriminant is included for shape parity with the
44
+ * other builders. */
45
+ export interface BuildSpecPhaseEarlyExit {
46
+ kind: 'early-exit';
47
+ exitCode: number;
48
+ }
49
+ export interface BuildSpecPhaseResult {
50
+ kind: 'phase';
51
+ phase: RunPhase<SpecInput, SpecOutput>;
52
+ input: SpecInput;
53
+ config: GuardrailConfig;
54
+ renderResult: (output: SpecOutput) => number;
55
+ }
56
+ /**
57
+ * v6.2.0 — extract the `RunPhase<SpecInput, SpecOutput>` construction out of
58
+ * `runSpec(options)` so the new top-level `autopilot` orchestrator can drive
59
+ * `runPhase` itself with a shared `phaseIdx` against the same run dir.
60
+ *
61
+ * Parity with `runSpec(options)` is asserted by
62
+ * `tests/cli/spec-builder-parity.test.ts`.
63
+ */
64
+ export declare function buildSpecPhase(options: SpecCommandOptions): Promise<BuildSpecPhaseResult | BuildSpecPhaseEarlyExit>;
65
+ export declare function runSpec(options?: SpecCommandOptions): Promise<number>;
66
+ //# sourceMappingURL=spec.d.ts.map
@@ -0,0 +1,132 @@
1
+ // src/cli/spec.ts
2
+ //
3
+ // v6.0.3 — wrap the `spec` pipeline phase through `runPhase`.
4
+ //
5
+ // `spec` is the second phase of the autopilot pipeline (brainstorm → spec →
6
+ // plan → implement → migrate → validate → pr → review). Like `brainstorm`, it
7
+ // is implemented primarily as a Claude Code skill, not as a standalone CLI
8
+ // subcommand. The CLI verb that ships in this binary is an advisory shim: it
9
+ // points the user at the Claude Code skill and the next pipeline verbs. There
10
+ // is no LLM call in the CLI verb body and no provider side effects. The
11
+ // pure-LLM spec writing happens in Claude Code; the spec markdown produced
12
+ // there lands at `docs/superpowers/specs/<slug>.md` (a local file write —
13
+ // the recipe treats local file writes as acceptable inside the phase body,
14
+ // identical precedent to `fix.ts` editing local source files).
15
+ //
16
+ // Idempotency / side effects (deviation note vs. spec table):
17
+ // - The spec table at docs/specs/v6-run-state-engine.md says
18
+ // `idempotent: no` for `spec` because re-running produces NEW LLM
19
+ // content each invocation. v6.0.3 declares `idempotent: true` to match
20
+ // the engine's actual semantics ("safe to retry without
21
+ // reconciliation"): the CLI verb itself is a printed advisory that is
22
+ // byte-for-byte identical on every invocation, has no externalRefs to
23
+ // reconcile, and no provider state to roll back. Same reasoning as the
24
+ // brainstorm wrap. See `src/cli/brainstorm.ts` for the longer
25
+ // deviation rationale.
26
+ // - `hasSideEffects: false` — the CLI verb prints to stdout. No provider
27
+ // calls, no git push, no PR creation, no remote API write.
28
+ import * as path from 'node:path';
29
+ import * as fs from 'node:fs';
30
+ import { loadConfig } from "../core/config/loader.js";
31
+ import { runPhaseWithLifecycle } from "../core/run-state/run-phase-with-lifecycle.js";
32
+ const C = {
33
+ reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
34
+ cyan: '\x1b[36m', red: '\x1b[31m',
35
+ };
36
+ const fmt = (c, t) => `${C[c]}${t}${C.reset}`;
37
+ /**
38
+ * v6.2.0 — extract the `RunPhase<SpecInput, SpecOutput>` construction out of
39
+ * `runSpec(options)` so the new top-level `autopilot` orchestrator can drive
40
+ * `runPhase` itself with a shared `phaseIdx` against the same run dir.
41
+ *
42
+ * Parity with `runSpec(options)` is asserted by
43
+ * `tests/cli/spec-builder-parity.test.ts`.
44
+ */
45
+ export async function buildSpecPhase(options) {
46
+ const cwd = options.cwd ?? process.cwd();
47
+ const configPath = options.configPath ?? path.join(cwd, 'guardrail.config.yaml');
48
+ let config = { configVersion: 1 };
49
+ if (fs.existsSync(configPath)) {
50
+ const loaded = await loadConfig(configPath);
51
+ if (loaded)
52
+ config = loaded;
53
+ }
54
+ const specInput = { cwd, silent: options.__silent === true };
55
+ const phase = {
56
+ name: 'spec',
57
+ // Pure-LLM spec writing happens in the Claude Code skill, not here.
58
+ // The CLI verb is an advisory print with no externalRefs to reconcile
59
+ // and no provider state to roll back. Safe to retry. (Deviation from
60
+ // the spec table noted at the top of the file.)
61
+ idempotent: true,
62
+ // No provider calls, no git push, no PR creation. Identical to costs
63
+ // and brainstorm.
64
+ hasSideEffects: false,
65
+ run: async (input) => executeSpecPhase(input),
66
+ };
67
+ return {
68
+ kind: 'phase',
69
+ phase,
70
+ input: specInput,
71
+ config,
72
+ renderResult: (output) => renderSpecOutput(output, specInput),
73
+ };
74
+ }
75
+ export async function runSpec(options = {}) {
76
+ const built = await buildSpecPhase(options);
77
+ if (built.kind === 'early-exit')
78
+ return built.exitCode;
79
+ const { phase, input, config, renderResult } = built;
80
+ // v6.0.6 — lifecycle wiring lives in `runPhaseWithLifecycle`.
81
+ let output;
82
+ try {
83
+ const result = await runPhaseWithLifecycle({
84
+ cwd: input.cwd,
85
+ phase,
86
+ input,
87
+ config,
88
+ cliEngine: options.cliEngine,
89
+ envEngine: options.envEngine,
90
+ runEngineOff: () => executeSpecPhase(input),
91
+ });
92
+ output = result.output;
93
+ }
94
+ catch {
95
+ return 1;
96
+ }
97
+ return renderResult(output);
98
+ }
99
+ // ---------------------------------------------------------------------------
100
+ // Phase body — produce the advisory payload. Pure: no provider calls.
101
+ // ---------------------------------------------------------------------------
102
+ async function executeSpecPhase(_input) {
103
+ return {
104
+ kind: 'advisory',
105
+ nextActions: [
106
+ 'Approve a brainstorm output, then invoke /autopilot from Claude Code',
107
+ 'The autopilot skill writes the implementation plan + executes the pipeline',
108
+ ],
109
+ };
110
+ }
111
+ // ---------------------------------------------------------------------------
112
+ // Render — translate SpecOutput back to the stdout advisory + exit code.
113
+ // ---------------------------------------------------------------------------
114
+ function renderSpecOutput(_output, input) {
115
+ if (input.silent)
116
+ return 0;
117
+ console.log(`
118
+ ${fmt('bold', '[spec]')} Spec writing is a Claude Code skill, not a standalone CLI subcommand.
119
+
120
+ Invoke it from Claude Code:
121
+
122
+ ${fmt('cyan', '/brainstorm')} Interactive spec writing (entry point)
123
+ ${fmt('cyan', '/autopilot')} Full pipeline from an approved spec
124
+ ${fmt('cyan', '/migrate')} Database migration phase (stack-dependent)
125
+
126
+ Specs land at ${fmt('dim', 'docs/superpowers/specs/<slug>.md')} — once approved, /autopilot consumes them.
127
+
128
+ Full pipeline docs: https://github.com/axledbetter/claude-autopilot#the-pipeline-phase-by-phase
129
+ `);
130
+ return 0;
131
+ }
132
+ //# sourceMappingURL=spec.js.map