@delegance/claude-autopilot 5.5.2 → 6.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +935 -6
- package/README.md +55 -0
- package/dist/src/adapters/council/openai.js +12 -6
- package/dist/src/adapters/deploy/_http.d.ts +43 -0
- package/dist/src/adapters/deploy/_http.js +99 -0
- package/dist/src/adapters/deploy/fly.d.ts +206 -0
- package/dist/src/adapters/deploy/fly.js +696 -0
- package/dist/src/adapters/deploy/index.d.ts +2 -0
- package/dist/src/adapters/deploy/index.js +33 -0
- package/dist/src/adapters/deploy/render.d.ts +181 -0
- package/dist/src/adapters/deploy/render.js +550 -0
- package/dist/src/adapters/deploy/types.d.ts +67 -3
- package/dist/src/adapters/deploy/vercel.d.ts +17 -1
- package/dist/src/adapters/deploy/vercel.js +29 -49
- package/dist/src/adapters/pricing.d.ts +36 -0
- package/dist/src/adapters/pricing.js +40 -0
- package/dist/src/adapters/review-engine/codex.js +10 -7
- package/dist/src/cli/autopilot.d.ts +71 -0
- package/dist/src/cli/autopilot.js +735 -0
- package/dist/src/cli/brainstorm.d.ts +23 -0
- package/dist/src/cli/brainstorm.js +131 -0
- package/dist/src/cli/costs.d.ts +15 -1
- package/dist/src/cli/costs.js +99 -10
- package/dist/src/cli/deploy.d.ts +3 -3
- package/dist/src/cli/deploy.js +34 -9
- package/dist/src/cli/fix.d.ts +18 -0
- package/dist/src/cli/fix.js +105 -11
- package/dist/src/cli/help-text.d.ts +52 -0
- package/dist/src/cli/help-text.js +400 -0
- package/dist/src/cli/implement.d.ts +91 -0
- package/dist/src/cli/implement.js +196 -0
- package/dist/src/cli/index.js +719 -245
- package/dist/src/cli/json-envelope.d.ts +187 -0
- package/dist/src/cli/json-envelope.js +270 -0
- package/dist/src/cli/json-mode.d.ts +33 -0
- package/dist/src/cli/json-mode.js +201 -0
- package/dist/src/cli/migrate.d.ts +111 -0
- package/dist/src/cli/migrate.js +305 -0
- package/dist/src/cli/plan.d.ts +81 -0
- package/dist/src/cli/plan.js +149 -0
- package/dist/src/cli/pr.d.ts +106 -0
- package/dist/src/cli/pr.js +191 -19
- package/dist/src/cli/preflight.js +26 -0
- package/dist/src/cli/review.d.ts +27 -0
- package/dist/src/cli/review.js +126 -0
- package/dist/src/cli/runs-watch-renderer.d.ts +45 -0
- package/dist/src/cli/runs-watch-renderer.js +275 -0
- package/dist/src/cli/runs-watch.d.ts +41 -0
- package/dist/src/cli/runs-watch.js +395 -0
- package/dist/src/cli/runs.d.ts +122 -0
- package/dist/src/cli/runs.js +902 -0
- package/dist/src/cli/scan.d.ts +93 -0
- package/dist/src/cli/scan.js +166 -40
- package/dist/src/cli/spec.d.ts +66 -0
- package/dist/src/cli/spec.js +132 -0
- package/dist/src/cli/validate.d.ts +29 -0
- package/dist/src/cli/validate.js +131 -0
- package/dist/src/core/config/schema.d.ts +9 -0
- package/dist/src/core/config/schema.js +7 -0
- package/dist/src/core/config/types.d.ts +11 -0
- package/dist/src/core/council/runner.d.ts +10 -1
- package/dist/src/core/council/runner.js +25 -3
- package/dist/src/core/council/types.d.ts +7 -0
- package/dist/src/core/errors.d.ts +1 -1
- package/dist/src/core/errors.js +11 -0
- package/dist/src/core/logging/redaction.d.ts +13 -0
- package/dist/src/core/logging/redaction.js +20 -0
- package/dist/src/core/migrate/schema-validator.js +15 -1
- package/dist/src/core/phases/static-rules.d.ts +5 -1
- package/dist/src/core/phases/static-rules.js +2 -5
- package/dist/src/core/run-state/budget.d.ts +88 -0
- package/dist/src/core/run-state/budget.js +141 -0
- package/dist/src/core/run-state/cli-internal.d.ts +21 -0
- package/dist/src/core/run-state/cli-internal.js +174 -0
- package/dist/src/core/run-state/events.d.ts +59 -0
- package/dist/src/core/run-state/events.js +504 -0
- package/dist/src/core/run-state/lock.d.ts +61 -0
- package/dist/src/core/run-state/lock.js +206 -0
- package/dist/src/core/run-state/phase-context.d.ts +60 -0
- package/dist/src/core/run-state/phase-context.js +108 -0
- package/dist/src/core/run-state/phase-registry.d.ts +137 -0
- package/dist/src/core/run-state/phase-registry.js +162 -0
- package/dist/src/core/run-state/phase-runner.d.ts +80 -0
- package/dist/src/core/run-state/phase-runner.js +447 -0
- package/dist/src/core/run-state/provider-readback.d.ts +130 -0
- package/dist/src/core/run-state/provider-readback.js +426 -0
- package/dist/src/core/run-state/replay-decision.d.ts +69 -0
- package/dist/src/core/run-state/replay-decision.js +144 -0
- package/dist/src/core/run-state/resolve-engine.d.ts +100 -0
- package/dist/src/core/run-state/resolve-engine.js +190 -0
- package/dist/src/core/run-state/resume-preflight.d.ts +66 -0
- package/dist/src/core/run-state/resume-preflight.js +116 -0
- package/dist/src/core/run-state/run-phase-with-lifecycle.d.ts +73 -0
- package/dist/src/core/run-state/run-phase-with-lifecycle.js +186 -0
- package/dist/src/core/run-state/runs.d.ts +57 -0
- package/dist/src/core/run-state/runs.js +288 -0
- package/dist/src/core/run-state/snapshot.d.ts +14 -0
- package/dist/src/core/run-state/snapshot.js +114 -0
- package/dist/src/core/run-state/state.d.ts +40 -0
- package/dist/src/core/run-state/state.js +164 -0
- package/dist/src/core/run-state/types.d.ts +278 -0
- package/dist/src/core/run-state/types.js +13 -0
- package/dist/src/core/run-state/ulid.d.ts +11 -0
- package/dist/src/core/run-state/ulid.js +95 -0
- package/dist/src/core/schema-alignment/extractor/index.d.ts +1 -1
- package/dist/src/core/schema-alignment/extractor/index.js +2 -2
- package/dist/src/core/schema-alignment/extractor/prisma.d.ts +13 -1
- package/dist/src/core/schema-alignment/extractor/prisma.js +65 -10
- package/dist/src/core/schema-alignment/git-history.d.ts +19 -0
- package/dist/src/core/schema-alignment/git-history.js +53 -0
- package/dist/src/core/static-rules/rules/brand-tokens.js +2 -2
- package/dist/src/core/static-rules/rules/schema-alignment.js +14 -4
- package/package.json +2 -1
- package/scripts/autoregress.ts +1 -1
- package/skills/claude-autopilot.md +1 -1
- package/skills/make-interfaces-feel-better/SKILL.md +104 -0
- package/skills/simplify-ui/SKILL.md +103 -0
- package/skills/ui/SKILL.md +117 -0
- package/skills/ui-ux-pro-max/SKILL.md +90 -0
package/dist/src/cli/scan.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type { ReviewEngine } from '../adapters/review-engine/types.ts';
|
|
2
|
+
import type { GuardrailConfig } from '../core/config/types.ts';
|
|
3
|
+
import { type RunPhase } from '../core/run-state/phase-runner.ts';
|
|
1
4
|
export interface ScanCommandOptions {
|
|
2
5
|
cwd?: string;
|
|
3
6
|
configPath?: string;
|
|
@@ -6,6 +9,96 @@ export interface ScanCommandOptions {
|
|
|
6
9
|
ask?: string;
|
|
7
10
|
focus?: 'security' | 'logic' | 'performance' | 'brand' | 'all';
|
|
8
11
|
dryRun?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* v6.0.1 — engine knob inputs. The CLI dispatcher gathers these and the
|
|
14
|
+
* resolver picks a winner against (cli > env > config > built-in default).
|
|
15
|
+
* Both fields are optional; an absent CLI flag + absent env value falls
|
|
16
|
+
* through to the loaded config and then to the built-in default (off in v6.0).
|
|
17
|
+
*/
|
|
18
|
+
cliEngine?: boolean;
|
|
19
|
+
envEngine?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Test-only seam — injects a pre-built ReviewEngine so tests can exercise
|
|
22
|
+
* the engine-wrap path without hitting `loadAdapter()` (and therefore
|
|
23
|
+
* without needing an LLM API key in the environment). Production callers
|
|
24
|
+
* MUST NOT pass this; the CLI dispatcher does not expose a flag that sets
|
|
25
|
+
* it. Underscore-prefixed to make the test-seam intent obvious in code
|
|
26
|
+
* search.
|
|
27
|
+
*/
|
|
28
|
+
__testReviewEngine?: ReviewEngine;
|
|
9
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Input handed to the wrapped `RunPhase<ScanInput, ScanOutput>` body. Captures
|
|
32
|
+
* everything the phase needs that's already been resolved by the outer scope
|
|
33
|
+
* (file list, engine, focus hint, etc.) so the phase body itself is a thin
|
|
34
|
+
* await on `runReviewPhase` + finding post-processing.
|
|
35
|
+
*
|
|
36
|
+
* Exported (along with `ScanOutput`) so the v6.2.0 orchestrator's phase
|
|
37
|
+
* registry (`src/core/run-state/phase-registry.ts`) can carry the typed
|
|
38
|
+
* I/O shape on its `PhaseRegistration<ScanInput, ScanOutput>` slot.
|
|
39
|
+
*/
|
|
40
|
+
export interface ScanInput {
|
|
41
|
+
files: string[];
|
|
42
|
+
relFiles: string[];
|
|
43
|
+
cwd: string;
|
|
44
|
+
config: GuardrailConfig;
|
|
45
|
+
engine: ReviewEngine;
|
|
46
|
+
focusHint: string;
|
|
47
|
+
ask?: string;
|
|
48
|
+
focusLabel: string | null;
|
|
49
|
+
all: boolean;
|
|
50
|
+
}
|
|
51
|
+
/** What the wrapped phase returns. The outer scope translates this back to
|
|
52
|
+
* the legacy stdout banner + exit code path. Keeping the shape JSON-
|
|
53
|
+
* serializable means runPhase persists it into phases/<name>.json so a
|
|
54
|
+
* future skip-already-applied can restore it without re-running the LLM. */
|
|
55
|
+
export interface ScanOutput {
|
|
56
|
+
/** Total files actually sent to the review engine. */
|
|
57
|
+
fileCount: number;
|
|
58
|
+
/** Findings after ignore-rule filtering. Tagged with severity so a JSON
|
|
59
|
+
* consumer can reason about pass/fail without re-reading the cache. */
|
|
60
|
+
findings: Array<{
|
|
61
|
+
severity: 'critical' | 'warning' | 'note';
|
|
62
|
+
message: string;
|
|
63
|
+
file?: string;
|
|
64
|
+
line?: number;
|
|
65
|
+
suggestion?: string;
|
|
66
|
+
}>;
|
|
67
|
+
costUSD?: number;
|
|
68
|
+
durationMs: number;
|
|
69
|
+
/** Pass-through of the LLM's raw text outputs when --ask returned prose
|
|
70
|
+
* rather than structured findings — replicates the legacy "Answer:" path. */
|
|
71
|
+
rawOutputs?: string[];
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* v6.2.0 — sentinel returned by `buildScanPhase` when the verb's pre-flight
|
|
75
|
+
* (no targets, no files found, dry-run, missing LLM key, …) decided it can
|
|
76
|
+
* exit early without running the engine. The CLI dispatcher treats this as
|
|
77
|
+
* "render-and-exit"; the orchestrator skips registry dispatch and returns
|
|
78
|
+
* the exit code straight through. Mirrors the legacy in-place `return N`
|
|
79
|
+
* branches inside `runScan` byte-for-byte.
|
|
80
|
+
*/
|
|
81
|
+
export interface BuildScanPhaseEarlyExit {
|
|
82
|
+
kind: 'early-exit';
|
|
83
|
+
exitCode: number;
|
|
84
|
+
}
|
|
85
|
+
export interface BuildScanPhaseResult {
|
|
86
|
+
kind: 'phase';
|
|
87
|
+
phase: RunPhase<ScanInput, ScanOutput>;
|
|
88
|
+
input: ScanInput;
|
|
89
|
+
config: GuardrailConfig;
|
|
90
|
+
renderResult: (output: ScanOutput) => number;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* v6.2.0 — extract the `RunPhase<ScanInput, ScanOutput>` construction out of
|
|
94
|
+
* `runScan(options)` so the new top-level `autopilot` orchestrator can drive
|
|
95
|
+
* `runPhase` itself with a shared `phaseIdx` against the same run dir.
|
|
96
|
+
*
|
|
97
|
+
* The legacy `runScan(options)` keeps calling this builder internally (then
|
|
98
|
+
* `runPhaseWithLifecycle` for single-phase runs) so direct CLI behavior is
|
|
99
|
+
* byte-for-byte identical to v6.1. Parity is asserted by
|
|
100
|
+
* `tests/cli/scan-builder-parity.test.ts`.
|
|
101
|
+
*/
|
|
102
|
+
export declare function buildScanPhase(options: ScanCommandOptions): Promise<BuildScanPhaseResult | BuildScanPhaseEarlyExit>;
|
|
10
103
|
export declare function runScan(options?: ScanCommandOptions): Promise<number>;
|
|
11
104
|
//# sourceMappingURL=scan.d.ts.map
|
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) {
|
|
@@ -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
|
|
@@ -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
|