@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.
- package/CHANGELOG.md +1776 -6
- package/README.md +65 -1
- package/bin/_launcher.js +38 -23
- package/dist/src/adapters/council/openai.js +12 -6
- package/dist/src/adapters/deploy/_http.d.ts +43 -0
- package/dist/src/adapters/deploy/_http.js +99 -0
- package/dist/src/adapters/deploy/fly.d.ts +206 -0
- package/dist/src/adapters/deploy/fly.js +696 -0
- package/dist/src/adapters/deploy/index.d.ts +2 -0
- package/dist/src/adapters/deploy/index.js +33 -0
- package/dist/src/adapters/deploy/render.d.ts +181 -0
- package/dist/src/adapters/deploy/render.js +550 -0
- package/dist/src/adapters/deploy/types.d.ts +67 -3
- package/dist/src/adapters/deploy/vercel.d.ts +17 -1
- package/dist/src/adapters/deploy/vercel.js +29 -49
- package/dist/src/adapters/pricing.d.ts +36 -0
- package/dist/src/adapters/pricing.js +40 -0
- package/dist/src/adapters/review-engine/codex.js +10 -7
- package/dist/src/cli/autopilot.d.ts +75 -0
- package/dist/src/cli/autopilot.js +750 -0
- package/dist/src/cli/brainstorm.d.ts +23 -0
- package/dist/src/cli/brainstorm.js +131 -0
- package/dist/src/cli/costs.d.ts +15 -1
- package/dist/src/cli/costs.js +99 -10
- package/dist/src/cli/dashboard/index.d.ts +5 -0
- package/dist/src/cli/dashboard/index.js +49 -0
- package/dist/src/cli/dashboard/login.d.ts +22 -0
- package/dist/src/cli/dashboard/login.js +260 -0
- package/dist/src/cli/dashboard/logout.d.ts +12 -0
- package/dist/src/cli/dashboard/logout.js +45 -0
- package/dist/src/cli/dashboard/status.d.ts +30 -0
- package/dist/src/cli/dashboard/status.js +65 -0
- package/dist/src/cli/dashboard/upload.d.ts +16 -0
- package/dist/src/cli/dashboard/upload.js +48 -0
- package/dist/src/cli/deploy.d.ts +3 -3
- package/dist/src/cli/deploy.js +34 -9
- package/dist/src/cli/engine-flag-deprecation.d.ts +14 -0
- package/dist/src/cli/engine-flag-deprecation.js +20 -0
- package/dist/src/cli/fix.d.ts +18 -0
- package/dist/src/cli/fix.js +105 -11
- package/dist/src/cli/help-text.d.ts +52 -0
- package/dist/src/cli/help-text.js +416 -0
- package/dist/src/cli/implement.d.ts +91 -0
- package/dist/src/cli/implement.js +196 -0
- package/dist/src/cli/index.d.ts +2 -1
- package/dist/src/cli/index.js +774 -245
- package/dist/src/cli/json-envelope.d.ts +187 -0
- package/dist/src/cli/json-envelope.js +270 -0
- package/dist/src/cli/json-mode.d.ts +33 -0
- package/dist/src/cli/json-mode.js +201 -0
- package/dist/src/cli/migrate.d.ts +111 -0
- package/dist/src/cli/migrate.js +305 -0
- package/dist/src/cli/plan.d.ts +81 -0
- package/dist/src/cli/plan.js +149 -0
- package/dist/src/cli/pr.d.ts +106 -0
- package/dist/src/cli/pr.js +191 -19
- package/dist/src/cli/preflight.js +26 -0
- package/dist/src/cli/review.d.ts +27 -0
- package/dist/src/cli/review.js +126 -0
- package/dist/src/cli/runs-watch-renderer.d.ts +45 -0
- package/dist/src/cli/runs-watch-renderer.js +275 -0
- package/dist/src/cli/runs-watch.d.ts +41 -0
- package/dist/src/cli/runs-watch.js +395 -0
- package/dist/src/cli/runs.d.ts +122 -0
- package/dist/src/cli/runs.js +902 -0
- package/dist/src/cli/scaffold.d.ts +39 -0
- package/dist/src/cli/scaffold.js +287 -0
- package/dist/src/cli/scan.d.ts +93 -0
- package/dist/src/cli/scan.js +166 -40
- package/dist/src/cli/setup.d.ts +30 -0
- package/dist/src/cli/setup.js +137 -0
- package/dist/src/cli/spec.d.ts +66 -0
- package/dist/src/cli/spec.js +132 -0
- package/dist/src/cli/validate.d.ts +29 -0
- package/dist/src/cli/validate.js +131 -0
- package/dist/src/core/config/schema.d.ts +9 -0
- package/dist/src/core/config/schema.js +7 -0
- package/dist/src/core/config/types.d.ts +11 -0
- package/dist/src/core/council/runner.d.ts +10 -1
- package/dist/src/core/council/runner.js +25 -3
- package/dist/src/core/council/types.d.ts +7 -0
- package/dist/src/core/errors.d.ts +1 -1
- package/dist/src/core/errors.js +11 -0
- package/dist/src/core/logging/redaction.d.ts +13 -0
- package/dist/src/core/logging/redaction.js +20 -0
- package/dist/src/core/migrate/schema-validator.js +15 -1
- package/dist/src/core/phases/static-rules.d.ts +5 -1
- package/dist/src/core/phases/static-rules.js +2 -5
- package/dist/src/core/run-state/budget.d.ts +88 -0
- package/dist/src/core/run-state/budget.js +141 -0
- package/dist/src/core/run-state/cli-internal.d.ts +21 -0
- package/dist/src/core/run-state/cli-internal.js +174 -0
- package/dist/src/core/run-state/events.d.ts +59 -0
- package/dist/src/core/run-state/events.js +512 -0
- package/dist/src/core/run-state/lock.d.ts +61 -0
- package/dist/src/core/run-state/lock.js +206 -0
- package/dist/src/core/run-state/phase-context.d.ts +60 -0
- package/dist/src/core/run-state/phase-context.js +108 -0
- package/dist/src/core/run-state/phase-registry.d.ts +137 -0
- package/dist/src/core/run-state/phase-registry.js +162 -0
- package/dist/src/core/run-state/phase-runner.d.ts +80 -0
- package/dist/src/core/run-state/phase-runner.js +447 -0
- package/dist/src/core/run-state/provider-readback.d.ts +130 -0
- package/dist/src/core/run-state/provider-readback.js +426 -0
- package/dist/src/core/run-state/replay-decision.d.ts +69 -0
- package/dist/src/core/run-state/replay-decision.js +144 -0
- package/dist/src/core/run-state/resolve-engine.d.ts +45 -0
- package/dist/src/core/run-state/resolve-engine.js +74 -0
- package/dist/src/core/run-state/resume-preflight.d.ts +66 -0
- package/dist/src/core/run-state/resume-preflight.js +116 -0
- package/dist/src/core/run-state/run-phase-with-lifecycle.d.ts +69 -0
- package/dist/src/core/run-state/run-phase-with-lifecycle.js +193 -0
- package/dist/src/core/run-state/runs.d.ts +57 -0
- package/dist/src/core/run-state/runs.js +288 -0
- package/dist/src/core/run-state/snapshot.d.ts +14 -0
- package/dist/src/core/run-state/snapshot.js +114 -0
- package/dist/src/core/run-state/state.d.ts +40 -0
- package/dist/src/core/run-state/state.js +164 -0
- package/dist/src/core/run-state/types.d.ts +284 -0
- package/dist/src/core/run-state/types.js +19 -0
- package/dist/src/core/run-state/ulid.d.ts +11 -0
- package/dist/src/core/run-state/ulid.js +95 -0
- package/dist/src/core/schema-alignment/extractor/index.d.ts +1 -1
- package/dist/src/core/schema-alignment/extractor/index.js +2 -2
- package/dist/src/core/schema-alignment/extractor/prisma.d.ts +13 -1
- package/dist/src/core/schema-alignment/extractor/prisma.js +65 -10
- package/dist/src/core/schema-alignment/git-history.d.ts +19 -0
- package/dist/src/core/schema-alignment/git-history.js +53 -0
- package/dist/src/core/static-rules/rules/brand-tokens.js +2 -2
- package/dist/src/core/static-rules/rules/schema-alignment.js +14 -4
- package/dist/src/dashboard/auto-upload.d.ts +26 -0
- package/dist/src/dashboard/auto-upload.js +107 -0
- package/dist/src/dashboard/config.d.ts +22 -0
- package/dist/src/dashboard/config.js +109 -0
- package/dist/src/dashboard/upload/canonical.d.ts +3 -0
- package/dist/src/dashboard/upload/canonical.js +16 -0
- package/dist/src/dashboard/upload/chain.d.ts +9 -0
- package/dist/src/dashboard/upload/chain.js +27 -0
- package/dist/src/dashboard/upload/snapshot.d.ts +23 -0
- package/dist/src/dashboard/upload/snapshot.js +66 -0
- package/dist/src/dashboard/upload/uploader.d.ts +54 -0
- package/dist/src/dashboard/upload/uploader.js +330 -0
- package/package.json +19 -3
- package/scripts/autoregress.ts +1 -1
- package/scripts/test-runner.mjs +4 -0
- package/skills/claude-autopilot.md +1 -1
- package/skills/make-interfaces-feel-better/SKILL.md +104 -0
- package/skills/simplify-ui/SKILL.md +103 -0
- package/skills/ui/SKILL.md +117 -0
- package/skills/ui-ux-pro-max/SKILL.md +90 -0
package/dist/src/cli/scan.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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 &&
|
|
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
|
|
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
|
-
|
|
200
|
-
|
|
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) {
|
package/dist/src/cli/setup.d.ts
CHANGED
|
@@ -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
|
package/dist/src/cli/setup.js
CHANGED
|
@@ -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
|