@delegance/claude-autopilot 5.2.2 → 6.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1027 -1
- package/README.md +104 -17
- package/dist/src/adapters/council/claude.js +2 -1
- package/dist/src/adapters/council/openai.js +14 -7
- 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/generic.d.ts +39 -0
- package/dist/src/adapters/deploy/generic.js +98 -0
- package/dist/src/adapters/deploy/index.d.ts +15 -0
- package/dist/src/adapters/deploy/index.js +78 -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 +221 -0
- package/dist/src/adapters/deploy/types.js +15 -0
- package/dist/src/adapters/deploy/vercel.d.ts +143 -0
- package/dist/src/adapters/deploy/vercel.js +426 -0
- package/dist/src/adapters/pricing.d.ts +36 -0
- package/dist/src/adapters/pricing.js +40 -0
- package/dist/src/adapters/review-engine/claude.js +2 -1
- package/dist/src/adapters/review-engine/codex.js +12 -8
- package/dist/src/adapters/review-engine/gemini.js +2 -1
- package/dist/src/adapters/review-engine/openai-compatible.js +2 -1
- package/dist/src/adapters/sdk-loader.d.ts +15 -0
- package/dist/src/adapters/sdk-loader.js +77 -0
- 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 +71 -0
- package/dist/src/cli/deploy.js +539 -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 +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 +784 -222
- 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 +102 -1
- 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 +43 -0
- package/dist/src/core/config/schema.js +25 -0
- package/dist/src/core/config/types.d.ts +17 -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 +12 -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/detector-rules.js +6 -0
- package/dist/src/core/migrate/schema-validator.js +22 -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 +9 -5
- package/scripts/autoregress.ts +3 -2
- package/skills/claude-autopilot.md +1 -1
- package/skills/make-interfaces-feel-better/SKILL.md +104 -0
- package/skills/migrate/SKILL.md +193 -47
- 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
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { GuardrailConfig } from '../core/config/types.ts';
|
|
2
|
+
import { type RunPhase } from '../core/run-state/phase-runner.ts';
|
|
3
|
+
export interface PlanCommandOptions {
|
|
4
|
+
cwd?: string;
|
|
5
|
+
configPath?: string;
|
|
6
|
+
/**
|
|
7
|
+
* Path to a spec file the planner should read. Optional — when absent, the
|
|
8
|
+
* phase falls back to "no spec provided" and just records that fact in the
|
|
9
|
+
* plan-file output. The actual LLM-driven planning lives in the Claude Code
|
|
10
|
+
* superpowers:writing-plans skill; this CLI verb is the engine-wrap shell so
|
|
11
|
+
* v6 pipeline runs can checkpoint a `plan` phase even when the planner
|
|
12
|
+
* itself is invoked from inside Claude Code.
|
|
13
|
+
*/
|
|
14
|
+
specPath?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Where to write the plan markdown file. Defaults to
|
|
17
|
+
* `.guardrail-cache/plans/<timestamp>-plan.md` so it lands inside the
|
|
18
|
+
* cache that's already gitignored. The path is recorded on PlanOutput so
|
|
19
|
+
* the engine path can persist it as `result` for replay.
|
|
20
|
+
*/
|
|
21
|
+
outputPath?: string;
|
|
22
|
+
/**
|
|
23
|
+
* v6.0.4 — engine knob inputs. Same shape and precedence as scan / costs /
|
|
24
|
+
* fix (CLI > env > config > built-in default off in v6.0.x). The CLI
|
|
25
|
+
* dispatcher wires `cliEngine` from `--engine` / `--no-engine`;
|
|
26
|
+
* `envEngine` from `process.env.CLAUDE_AUTOPILOT_ENGINE`.
|
|
27
|
+
*/
|
|
28
|
+
cliEngine?: boolean;
|
|
29
|
+
envEngine?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Phase input — captured as a struct so the engine path's phase body matches
|
|
33
|
+
* the engine-off path call signature. Resolved by the outer scope (config,
|
|
34
|
+
* spec path, output path).
|
|
35
|
+
*
|
|
36
|
+
* Exported so the v6.2.0 orchestrator's phase registry can carry the typed
|
|
37
|
+
* I/O shape on its `PhaseRegistration<PlanInput, PlanOutput>` slot.
|
|
38
|
+
*/
|
|
39
|
+
export interface PlanInput {
|
|
40
|
+
cwd: string;
|
|
41
|
+
specPath: string | null;
|
|
42
|
+
outputPath: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Phase output — JSON-serializable summary suitable for persistence as
|
|
46
|
+
* `result` on phases/plan.json. Mirrors what the legacy summary line
|
|
47
|
+
* computes. A future skip-already-applied (Phase 6) could restore this
|
|
48
|
+
* without re-running the planner by reading the persisted plan-file path.
|
|
49
|
+
*/
|
|
50
|
+
export interface PlanOutput {
|
|
51
|
+
/** Absolute path to the written plan markdown file. */
|
|
52
|
+
planFilePath: string;
|
|
53
|
+
/** Whether the planner had a spec to consume. */
|
|
54
|
+
specProvided: boolean;
|
|
55
|
+
/** Echoed for the render layer / future skip-already-applied. */
|
|
56
|
+
specPath: string | null;
|
|
57
|
+
}
|
|
58
|
+
/** v6.2.0 — see scan.ts for the kind='early-exit' rationale. Plan has no
|
|
59
|
+
* early-exit branches today; the discriminant is included for shape parity
|
|
60
|
+
* with the other builders. */
|
|
61
|
+
export interface BuildPlanPhaseEarlyExit {
|
|
62
|
+
kind: 'early-exit';
|
|
63
|
+
exitCode: number;
|
|
64
|
+
}
|
|
65
|
+
export interface BuildPlanPhaseResult {
|
|
66
|
+
kind: 'phase';
|
|
67
|
+
phase: RunPhase<PlanInput, PlanOutput>;
|
|
68
|
+
input: PlanInput;
|
|
69
|
+
config: GuardrailConfig;
|
|
70
|
+
renderResult: (output: PlanOutput) => number;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* v6.2.0 — extract the `RunPhase<PlanInput, PlanOutput>` construction out of
|
|
74
|
+
* `runPlan(options)` so the new top-level `autopilot` orchestrator can drive
|
|
75
|
+
* `runPhase` itself with a shared `phaseIdx` against the same run dir.
|
|
76
|
+
*
|
|
77
|
+
* Parity asserted by `tests/cli/plan-builder-parity.test.ts`.
|
|
78
|
+
*/
|
|
79
|
+
export declare function buildPlanPhase(options: PlanCommandOptions): Promise<BuildPlanPhaseResult | BuildPlanPhaseEarlyExit>;
|
|
80
|
+
export declare function runPlan(options?: PlanCommandOptions): Promise<number>;
|
|
81
|
+
//# sourceMappingURL=plan.d.ts.map
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import { loadConfig } from "../core/config/loader.js";
|
|
4
|
+
import { runPhaseWithLifecycle } from "../core/run-state/run-phase-with-lifecycle.js";
|
|
5
|
+
const C = {
|
|
6
|
+
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
|
|
7
|
+
green: '\x1b[32m', yellow: '\x1b[33m', cyan: '\x1b[36m', red: '\x1b[31m',
|
|
8
|
+
};
|
|
9
|
+
const fmt = (c, t) => `${C[c]}${t}${C.reset}`;
|
|
10
|
+
/**
|
|
11
|
+
* v6.2.0 — extract the `RunPhase<PlanInput, PlanOutput>` construction out of
|
|
12
|
+
* `runPlan(options)` so the new top-level `autopilot` orchestrator can drive
|
|
13
|
+
* `runPhase` itself with a shared `phaseIdx` against the same run dir.
|
|
14
|
+
*
|
|
15
|
+
* Parity asserted by `tests/cli/plan-builder-parity.test.ts`.
|
|
16
|
+
*/
|
|
17
|
+
export async function buildPlanPhase(options) {
|
|
18
|
+
const cwd = options.cwd ?? process.cwd();
|
|
19
|
+
const configPath = options.configPath ?? path.join(cwd, 'guardrail.config.yaml');
|
|
20
|
+
let config = { configVersion: 1 };
|
|
21
|
+
if (fs.existsSync(configPath)) {
|
|
22
|
+
const loaded = await loadConfig(configPath);
|
|
23
|
+
if (loaded)
|
|
24
|
+
config = loaded;
|
|
25
|
+
}
|
|
26
|
+
// Resolve spec path (optional) and output path. Default output lives under
|
|
27
|
+
// .guardrail-cache/plans/ so it's gitignored alongside other cache state.
|
|
28
|
+
const specPath = options.specPath ? path.resolve(cwd, options.specPath) : null;
|
|
29
|
+
const outputPath = options.outputPath
|
|
30
|
+
? path.resolve(cwd, options.outputPath)
|
|
31
|
+
: path.join(cwd, '.guardrail-cache', 'plans', `${new Date().toISOString().replace(/[:.]/g, '-')}-plan.md`);
|
|
32
|
+
const planInput = {
|
|
33
|
+
cwd,
|
|
34
|
+
specPath,
|
|
35
|
+
outputPath,
|
|
36
|
+
};
|
|
37
|
+
// The wrapped phase body — writes a plan markdown stub to disk. The actual
|
|
38
|
+
// LLM-driven planning lives in the Claude Code superpowers:writing-plans
|
|
39
|
+
// skill; this CLI verb is the engine-wrap shell so pipeline runs can
|
|
40
|
+
// checkpoint a `plan` phase deterministically. Engine-off callers invoke
|
|
41
|
+
// this directly via `executePlanPhase()`; engine-on callers route through
|
|
42
|
+
// `runPhase()`.
|
|
43
|
+
const phase = {
|
|
44
|
+
name: 'plan',
|
|
45
|
+
// Re-running the planner against the same spec writes the same plan
|
|
46
|
+
// file. The engine treats local file writes as overwrite-style — same
|
|
47
|
+
// precedent as scan's findings-cache. Re-running is safe and cheap.
|
|
48
|
+
idempotent: true,
|
|
49
|
+
// Local file write only — no provider calls, no PR comment, no git
|
|
50
|
+
// push. Per the recipe table, "side effects" means platform-side
|
|
51
|
+
// mutations. The plan file lives under .guardrail-cache/plans/ which
|
|
52
|
+
// is gitignored.
|
|
53
|
+
hasSideEffects: false,
|
|
54
|
+
run: async (input) => executePlanPhase(input),
|
|
55
|
+
};
|
|
56
|
+
return {
|
|
57
|
+
kind: 'phase',
|
|
58
|
+
phase,
|
|
59
|
+
input: planInput,
|
|
60
|
+
config,
|
|
61
|
+
renderResult: (output) => renderPlanOutput(output, planInput),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export async function runPlan(options = {}) {
|
|
65
|
+
const built = await buildPlanPhase(options);
|
|
66
|
+
if (built.kind === 'early-exit')
|
|
67
|
+
return built.exitCode;
|
|
68
|
+
const { phase, input, config, renderResult } = built;
|
|
69
|
+
// v6.0.6 — lifecycle wiring lives in `runPhaseWithLifecycle`.
|
|
70
|
+
let output;
|
|
71
|
+
try {
|
|
72
|
+
const result = await runPhaseWithLifecycle({
|
|
73
|
+
cwd: input.cwd,
|
|
74
|
+
phase,
|
|
75
|
+
input,
|
|
76
|
+
config,
|
|
77
|
+
cliEngine: options.cliEngine,
|
|
78
|
+
envEngine: options.envEngine,
|
|
79
|
+
runEngineOff: () => executePlanPhase(input),
|
|
80
|
+
});
|
|
81
|
+
output = result.output;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return 1;
|
|
85
|
+
}
|
|
86
|
+
return renderResult(output);
|
|
87
|
+
}
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Phase body — write a plan markdown stub. Pure: no console output, no exit
|
|
90
|
+
// codes. Returns a JSON-serializable PlanOutput so the engine can persist it
|
|
91
|
+
// as `result` on the phase snapshot. The actual LLM-driven planning content
|
|
92
|
+
// is produced by the Claude Code superpowers:writing-plans skill; this CLI
|
|
93
|
+
// verb's job is to provide a checkpointable phase shell.
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
async function executePlanPhase(input) {
|
|
96
|
+
const { specPath, outputPath } = input;
|
|
97
|
+
// Track whether the referenced spec file exists so the stub can record
|
|
98
|
+
// a "(file not found)" annotation when the user pointed at a missing
|
|
99
|
+
// path. We don't need to read the content — the planning content itself
|
|
100
|
+
// is owned by the Claude Code skill, not the engine-wrap shell.
|
|
101
|
+
// (Bugbot LOW PR #98: prior version did `readFileSync` whose result
|
|
102
|
+
// was unused — only its nullness was checked.)
|
|
103
|
+
const specExists = !!(specPath && fs.existsSync(specPath));
|
|
104
|
+
// Ensure output directory exists, then write the plan-file stub.
|
|
105
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
106
|
+
const lines = [
|
|
107
|
+
'# Plan',
|
|
108
|
+
'',
|
|
109
|
+
`Generated: ${new Date().toISOString()}`,
|
|
110
|
+
'',
|
|
111
|
+
specPath
|
|
112
|
+
? `Spec: ${specPath}${specExists ? '' : ' (file not found)'}`
|
|
113
|
+
: 'Spec: (none provided)',
|
|
114
|
+
'',
|
|
115
|
+
'<!--',
|
|
116
|
+
'This is the v6 engine-wrap stub for the `plan` phase. The actual',
|
|
117
|
+
'LLM-driven planning content is produced by the Claude Code',
|
|
118
|
+
'superpowers:writing-plans skill. The CLI verb exists to provide a',
|
|
119
|
+
'checkpointable phase shell so `claude-autopilot runs show <id>`',
|
|
120
|
+
'reflects a `plan` phase entry when the pipeline includes one.',
|
|
121
|
+
'-->',
|
|
122
|
+
'',
|
|
123
|
+
];
|
|
124
|
+
fs.writeFileSync(outputPath, lines.join('\n'), 'utf8');
|
|
125
|
+
return {
|
|
126
|
+
planFilePath: outputPath,
|
|
127
|
+
specProvided: specPath !== null,
|
|
128
|
+
specPath,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
// Render — translate PlanOutput back to a stdout summary + exit code. Lives
|
|
133
|
+
// outside the wrapped phase because it's pure presentation; doing the
|
|
134
|
+
// rendering inside the phase would couple the engine path's idempotency to
|
|
135
|
+
// console output.
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
function renderPlanOutput(output, input) {
|
|
138
|
+
const { planFilePath, specProvided, specPath } = output;
|
|
139
|
+
const { cwd } = input;
|
|
140
|
+
console.log('');
|
|
141
|
+
console.log(fmt('bold', '[plan]') + ' ' + fmt('dim', specProvided ? `spec: ${specPath}` : 'no spec provided'));
|
|
142
|
+
console.log(fmt('dim', ` → ${path.relative(cwd, planFilePath)}`));
|
|
143
|
+
console.log('');
|
|
144
|
+
console.log(fmt('cyan', 'Note:') + fmt('dim', ' the LLM-driven planner lives in Claude Code (superpowers:writing-plans skill).'));
|
|
145
|
+
console.log(fmt('dim', ' This CLI verb provides the v6 engine-wrap checkpoint only.'));
|
|
146
|
+
console.log('');
|
|
147
|
+
return 0;
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=plan.js.map
|
package/dist/src/cli/pr.d.ts
CHANGED
|
@@ -1,9 +1,115 @@
|
|
|
1
|
+
import type { GuardrailConfig } from '../core/config/types.ts';
|
|
2
|
+
import { type RunPhase } from '../core/run-state/phase-runner.ts';
|
|
1
3
|
export interface PrCommandOptions {
|
|
2
4
|
cwd?: string;
|
|
3
5
|
configPath?: string;
|
|
4
6
|
prNumber?: string;
|
|
5
7
|
noPostComments?: boolean;
|
|
6
8
|
noInlineComments?: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* v6.0.9 — engine knob inputs. Same shape and precedence as scan / costs /
|
|
11
|
+
* fix / plan / review / validate (CLI > env > config > built-in default off
|
|
12
|
+
* in v6.0.x). The CLI dispatcher wires `cliEngine` from `--engine` /
|
|
13
|
+
* `--no-engine`; `envEngine` from `process.env.CLAUDE_AUTOPILOT_ENGINE`.
|
|
14
|
+
*/
|
|
15
|
+
cliEngine?: boolean;
|
|
16
|
+
envEngine?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Test-only seam — replaces the PR-metadata lookup (normally `gh pr view`)
|
|
19
|
+
* with a static metadata struct so the engine smoke test can exercise the
|
|
20
|
+
* full lifecycle without invoking `gh`. Mirrors scan / fix's
|
|
21
|
+
* `__testReviewEngine` seam: production callers MUST NOT pass this.
|
|
22
|
+
*/
|
|
23
|
+
__testPrMeta?: PrMeta;
|
|
24
|
+
/**
|
|
25
|
+
* Test-only seam — replaces the inner `runCommand` invocation with a stub
|
|
26
|
+
* so tests can assert engine lifecycle without running the full pipeline
|
|
27
|
+
* (which loads adapters, requires an LLM key, posts real PR comments,
|
|
28
|
+
* etc.). The stub receives the resolved options and returns the exit
|
|
29
|
+
* code it would like the verb to surface. Production callers MUST NOT
|
|
30
|
+
* pass this.
|
|
31
|
+
*/
|
|
32
|
+
__testRunCommand?: (opts: {
|
|
33
|
+
cwd: string;
|
|
34
|
+
configPath: string;
|
|
35
|
+
base: string;
|
|
36
|
+
postComments: boolean;
|
|
37
|
+
inlineComments: boolean;
|
|
38
|
+
}) => Promise<number>;
|
|
7
39
|
}
|
|
40
|
+
interface PrMeta {
|
|
41
|
+
number: number;
|
|
42
|
+
baseRefName: string;
|
|
43
|
+
headRefName: string;
|
|
44
|
+
title: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Phase input — captured as a struct so the engine path's phase body matches
|
|
48
|
+
* the engine-off path call signature. Resolved by the outer scope (PR number
|
|
49
|
+
* detection → metadata lookup → base ref fetch → post-comment knobs).
|
|
50
|
+
*
|
|
51
|
+
* Exported so the v6.2.1 orchestrator's phase registry can carry the typed
|
|
52
|
+
* I/O shape on its `PhaseRegistration<PrInput, PrOutput>` slot.
|
|
53
|
+
*/
|
|
54
|
+
export interface PrInput {
|
|
55
|
+
cwd: string;
|
|
56
|
+
configPath: string;
|
|
57
|
+
pr: PrMeta;
|
|
58
|
+
postComments: boolean;
|
|
59
|
+
inlineComments: boolean;
|
|
60
|
+
runCommandImpl: (opts: {
|
|
61
|
+
cwd: string;
|
|
62
|
+
configPath: string;
|
|
63
|
+
base: string;
|
|
64
|
+
postComments: boolean;
|
|
65
|
+
inlineComments: boolean;
|
|
66
|
+
}) => Promise<number>;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Phase output — JSON-serializable summary suitable for persistence as
|
|
70
|
+
* `result` on phases/pr.json. The PR number is echoed so a future
|
|
71
|
+
* skip-already-applied (Phase 6) can reconcile against the externalRef
|
|
72
|
+
* ledger entry without re-running the review pipeline.
|
|
73
|
+
*
|
|
74
|
+
* Exported alongside `PrInput` for the registry's typed I/O slot.
|
|
75
|
+
*/
|
|
76
|
+
export interface PrOutput {
|
|
77
|
+
prNumber: number;
|
|
78
|
+
baseRefName: string;
|
|
79
|
+
headRefName: string;
|
|
80
|
+
postedComments: boolean;
|
|
81
|
+
postedInlineComments: boolean;
|
|
82
|
+
exitCode: number;
|
|
83
|
+
}
|
|
84
|
+
/** v6.2.1 — builder discriminants (parity with scan / spec / plan / implement
|
|
85
|
+
* / migrate). `pr` has multiple early-exit branches today (PR not found, gh
|
|
86
|
+
* not authenticated) — the builder surfaces them as `kind: 'early-exit'`. */
|
|
87
|
+
export interface BuildPrPhaseEarlyExit {
|
|
88
|
+
kind: 'early-exit';
|
|
89
|
+
exitCode: number;
|
|
90
|
+
}
|
|
91
|
+
export interface BuildPrPhaseResult {
|
|
92
|
+
kind: 'phase';
|
|
93
|
+
phase: RunPhase<PrInput, PrOutput>;
|
|
94
|
+
input: PrInput;
|
|
95
|
+
config: GuardrailConfig;
|
|
96
|
+
renderResult: (output: PrOutput) => number;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* v6.2.1 — extract the `RunPhase<PrInput, PrOutput>` construction out of
|
|
100
|
+
* `runPr(options)` so the new top-level `autopilot` orchestrator can drive
|
|
101
|
+
* `runPhase` itself with a shared `phaseIdx` against the same run dir.
|
|
102
|
+
* Mirrors the v6.2.0 builder pattern in scan / spec / plan / implement.
|
|
103
|
+
*
|
|
104
|
+
* The v6.2.1 idempotency contract for `pr` was already satisfied by the
|
|
105
|
+
* v6.0.9 wrap: `executePrPhase` emits the `github-pr` externalRef BEFORE
|
|
106
|
+
* `runCommand`. The contract registration in `phase-registry.ts` declares
|
|
107
|
+
* `preEffectRefKinds: ['github-pr'], postEffectRefKinds: []` — the same ref
|
|
108
|
+
* serves both purposes (its id is recorded pre-effect with the same value
|
|
109
|
+
* `gh` reports post-create), so no post-effect ref is needed for the
|
|
110
|
+
* orchestrator's resume preflight.
|
|
111
|
+
*/
|
|
112
|
+
export declare function buildPrPhase(options: PrCommandOptions): Promise<BuildPrPhaseResult | BuildPrPhaseEarlyExit>;
|
|
8
113
|
export declare function runPr(options?: PrCommandOptions): Promise<number>;
|
|
114
|
+
export {};
|
|
9
115
|
//# sourceMappingURL=pr.d.ts.map
|
package/dist/src/cli/pr.js
CHANGED
|
@@ -2,6 +2,8 @@ import * as path from 'node:path';
|
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
3
|
import { spawnSync } from 'node:child_process';
|
|
4
4
|
import { runCommand } from "./run.js";
|
|
5
|
+
import { loadConfig } from "../core/config/loader.js";
|
|
6
|
+
import { runPhaseWithLifecycle } from "../core/run-state/run-phase-with-lifecycle.js";
|
|
5
7
|
const C = {
|
|
6
8
|
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
|
|
7
9
|
green: '\x1b[32m', yellow: '\x1b[33m', red: '\x1b[31m',
|
|
@@ -22,7 +24,21 @@ function gitFetch(remote, ref, cwd) {
|
|
|
22
24
|
const r = spawnSync('git', ['fetch', remote, ref], { cwd, encoding: 'utf8', stdio: 'pipe' });
|
|
23
25
|
return r.status === 0;
|
|
24
26
|
}
|
|
25
|
-
|
|
27
|
+
/**
|
|
28
|
+
* v6.2.1 — extract the `RunPhase<PrInput, PrOutput>` construction out of
|
|
29
|
+
* `runPr(options)` so the new top-level `autopilot` orchestrator can drive
|
|
30
|
+
* `runPhase` itself with a shared `phaseIdx` against the same run dir.
|
|
31
|
+
* Mirrors the v6.2.0 builder pattern in scan / spec / plan / implement.
|
|
32
|
+
*
|
|
33
|
+
* The v6.2.1 idempotency contract for `pr` was already satisfied by the
|
|
34
|
+
* v6.0.9 wrap: `executePrPhase` emits the `github-pr` externalRef BEFORE
|
|
35
|
+
* `runCommand`. The contract registration in `phase-registry.ts` declares
|
|
36
|
+
* `preEffectRefKinds: ['github-pr'], postEffectRefKinds: []` — the same ref
|
|
37
|
+
* serves both purposes (its id is recorded pre-effect with the same value
|
|
38
|
+
* `gh` reports post-create), so no post-effect ref is needed for the
|
|
39
|
+
* orchestrator's resume preflight.
|
|
40
|
+
*/
|
|
41
|
+
export async function buildPrPhase(options) {
|
|
26
42
|
const cwd = options.cwd ?? process.cwd();
|
|
27
43
|
const configPath = options.configPath ?? path.join(cwd, 'guardrail.config.yaml');
|
|
28
44
|
// 5.2.2 — pr previously hard-failed when guardrail.config.yaml was missing
|
|
@@ -35,35 +51,191 @@ export async function runPr(options = {}) {
|
|
|
35
51
|
console.log(fmt('dim', `[pr] guardrail.config.yaml not found — auto-detecting from stack signals.`));
|
|
36
52
|
console.log(fmt('dim', ` Run \`claude-autopilot setup\` first to commit a config.`));
|
|
37
53
|
}
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
54
|
+
// Load config for the engine-resolution layer ONLY. The inner runCommand
|
|
55
|
+
// re-loads it via its own (graceful-fallback) path, so a missing /
|
|
56
|
+
// unreadable config is not fatal here either — we just default to an
|
|
57
|
+
// empty config object so `runPhaseWithLifecycle` can still consult
|
|
58
|
+
// `engine.enabled` (and fall through to env / CLI / built-in default).
|
|
59
|
+
let config = { configVersion: 1 };
|
|
60
|
+
if (fs.existsSync(configPath)) {
|
|
61
|
+
try {
|
|
62
|
+
const loaded = await loadConfig(configPath);
|
|
63
|
+
if (loaded)
|
|
64
|
+
config = loaded;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// Same gentle fallback as the existence check above — engine resolution
|
|
68
|
+
// doesn't care about a malformed config; runCommand will surface the
|
|
69
|
+
// real error with a typed message when it re-loads.
|
|
46
70
|
}
|
|
47
|
-
prNumber = String(detected.number);
|
|
48
71
|
}
|
|
49
|
-
|
|
72
|
+
// Resolve PR number — the test seam can short-circuit by providing
|
|
73
|
+
// __testPrMeta directly (skipping `gh` entirely).
|
|
74
|
+
let pr = options.__testPrMeta ?? null;
|
|
50
75
|
if (!pr) {
|
|
51
|
-
|
|
52
|
-
|
|
76
|
+
let prNumber = options.prNumber;
|
|
77
|
+
if (!prNumber) {
|
|
78
|
+
const detected = ghJson(['pr', 'view', '--json', 'number'], cwd);
|
|
79
|
+
if (!detected) {
|
|
80
|
+
console.error(fmt('red', '[pr] No PR number given and no open PR found for current branch.'));
|
|
81
|
+
console.error(fmt('dim', ' Usage: guardrail pr <number>'));
|
|
82
|
+
return { kind: 'early-exit', exitCode: 1 };
|
|
83
|
+
}
|
|
84
|
+
prNumber = String(detected.number);
|
|
85
|
+
}
|
|
86
|
+
// Look up PR metadata
|
|
87
|
+
const meta = ghJson(['pr', 'view', prNumber, '--json', 'number,baseRefName,headRefName,title'], cwd);
|
|
88
|
+
if (!meta) {
|
|
89
|
+
console.error(fmt('red', `[pr] Could not fetch PR #${prNumber} — is gh authenticated?`));
|
|
90
|
+
return { kind: 'early-exit', exitCode: 1 };
|
|
91
|
+
}
|
|
92
|
+
pr = meta;
|
|
53
93
|
}
|
|
54
94
|
console.log(`\n${fmt('bold', `[pr]`)} #${pr.number} ${fmt('dim', pr.title)}`);
|
|
55
95
|
console.log(fmt('dim', ` base: ${pr.baseRefName} head: ${pr.headRefName}`));
|
|
56
|
-
// Fetch base ref so diff works locally
|
|
57
|
-
|
|
58
|
-
if (!
|
|
59
|
-
|
|
96
|
+
// Fetch base ref so diff works locally. Skipped under the test seam —
|
|
97
|
+
// tests don't need a real git remote.
|
|
98
|
+
if (!options.__testPrMeta) {
|
|
99
|
+
const fetched = gitFetch('origin', pr.baseRefName, cwd);
|
|
100
|
+
if (!fetched) {
|
|
101
|
+
console.log(fmt('yellow', ` [pr] Warning: could not fetch origin/${pr.baseRefName} — diff may be stale`));
|
|
102
|
+
}
|
|
60
103
|
}
|
|
61
|
-
|
|
104
|
+
// INTENTIONAL DECLARATION (verified against the existing impl, v6.0.9):
|
|
105
|
+
//
|
|
106
|
+
// The v6 spec table (docs/specs/v6-run-state-engine.md) lists `pr` with
|
|
107
|
+
// `idempotent: no, hasSideEffects: yes, externalRefs: github-pr`. The
|
|
108
|
+
// wrap below MATCHES the spec — `pr` is genuinely side-effecting:
|
|
109
|
+
//
|
|
110
|
+
// 1. Inside `runCommand` (src/cli/run.ts), when `postComments` is true,
|
|
111
|
+
// `postPrComment(...)` is called which either creates a brand-new
|
|
112
|
+
// issue comment via `gh pr comment` OR PATCHes an existing one
|
|
113
|
+
// identified by the `<!-- guardrail-review -->` marker. Re-runs are
|
|
114
|
+
// effectively idempotent on the comment body (marker-based dedup),
|
|
115
|
+
// but the underlying gh API call is still mutating.
|
|
116
|
+
// 2. When `inlineComments` is true, `postReviewComments(...)` is called
|
|
117
|
+
// which (a) DISMISSES any prior autopilot review (PUT
|
|
118
|
+
// reviews/<id>/dismissals) and (b) POSTS a new review with inline
|
|
119
|
+
// comments. A re-run produces a DIFFERENT review ID each time —
|
|
120
|
+
// not byte-identical, definitively not safe to replay without
|
|
121
|
+
// gating. Per the spec, this is the textbook hasSideEffects: true
|
|
122
|
+
// case.
|
|
123
|
+
//
|
|
124
|
+
// ExternalRef plumbing: the phase records a `github-pr` externalRef with
|
|
125
|
+
// the PR number as soon as it has resolved metadata (via
|
|
126
|
+
// `ctx.emitExternalRef`). This is recorded BEFORE `runCommand` runs so a
|
|
127
|
+
// crash mid-pipeline still leaves a breadcrumb pointing at the PR. A
|
|
128
|
+
// future v6.0.x extension may add `github-comment` externalRefs after
|
|
129
|
+
// `postPrComment` returns the comment URL — that requires plumbing the
|
|
130
|
+
// post-comment URL out of `runCommand` (currently it's only logged), so
|
|
131
|
+
// it's deferred to a follow-up PR. For v6.0.9 the `github-pr` ref is
|
|
132
|
+
// sufficient: a Phase 6 readback can `gh pr view <id>` to confirm the PR
|
|
133
|
+
// is still open before deciding whether a replay is safe.
|
|
134
|
+
//
|
|
135
|
+
// Why `noPostComments` / `noInlineComments` don't change the declaration:
|
|
136
|
+
// `idempotent` and `hasSideEffects` describe the verb's behavior shape,
|
|
137
|
+
// not the runtime decision a particular flag combination produces. Even
|
|
138
|
+
// if both flags are passed, the verb's contract is "side-effecting by
|
|
139
|
+
// default"; the engine's gating layer doesn't try to introspect runtime
|
|
140
|
+
// flag combinations. (If users want a read-only PR review with no
|
|
141
|
+
// platform mutation, the right verb today is `claude-autopilot run`
|
|
142
|
+
// without `--post-comments` / `--inline-comments`.)
|
|
143
|
+
const phase = {
|
|
144
|
+
name: 'pr',
|
|
145
|
+
// Per the spec table — re-running can produce different externalRefs
|
|
146
|
+
// (a new review ID on each `postReviewComments` call). Engine gates
|
|
147
|
+
// replays accordingly: a prior phase.success requires either
|
|
148
|
+
// `--force-replay` or a successful provider readback before retrying.
|
|
149
|
+
idempotent: false,
|
|
150
|
+
// Posts to GitHub via `gh` CLI inside runCommand. See the long
|
|
151
|
+
// declaration note above for the per-call breakdown.
|
|
152
|
+
hasSideEffects: true,
|
|
153
|
+
run: async (input, ctx) => executePrPhase(input, ctx),
|
|
154
|
+
};
|
|
155
|
+
const prInput = {
|
|
62
156
|
cwd,
|
|
63
157
|
configPath,
|
|
64
|
-
|
|
158
|
+
pr,
|
|
65
159
|
postComments: !options.noPostComments,
|
|
66
160
|
inlineComments: !options.noInlineComments,
|
|
161
|
+
runCommandImpl: options.__testRunCommand ?? (opts => runCommand(opts)),
|
|
162
|
+
};
|
|
163
|
+
return {
|
|
164
|
+
kind: 'phase',
|
|
165
|
+
phase,
|
|
166
|
+
input: prInput,
|
|
167
|
+
config,
|
|
168
|
+
renderResult: (output) => output.exitCode,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
export async function runPr(options = {}) {
|
|
172
|
+
const built = await buildPrPhase(options);
|
|
173
|
+
if (built.kind === 'early-exit')
|
|
174
|
+
return built.exitCode;
|
|
175
|
+
const { phase, input: prInput, config, renderResult } = built;
|
|
176
|
+
// v6.0.9 — lifecycle wiring lives in `runPhaseWithLifecycle`. The helper
|
|
177
|
+
// owns the engine-on/engine-off branch and the failure banner; the caller
|
|
178
|
+
// just supplies the phase, the input, and the engine-off escape hatch.
|
|
179
|
+
let output;
|
|
180
|
+
try {
|
|
181
|
+
const result = await runPhaseWithLifecycle({
|
|
182
|
+
cwd: prInput.cwd,
|
|
183
|
+
phase,
|
|
184
|
+
input: prInput,
|
|
185
|
+
config,
|
|
186
|
+
cliEngine: options.cliEngine,
|
|
187
|
+
envEngine: options.envEngine,
|
|
188
|
+
// Engine-off escape hatch — runs the same phase body without the
|
|
189
|
+
// lifecycle wrapper. No PhaseContext available off-engine, so the
|
|
190
|
+
// emitExternalRef call is a no-op (ctx is null) — same precedent as
|
|
191
|
+
// every other wrapped verb's engine-off path.
|
|
192
|
+
runEngineOff: () => executePrPhase(prInput, null),
|
|
193
|
+
});
|
|
194
|
+
output = result.output;
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
// Helper already printed the failure banner + emitted run.complete
|
|
198
|
+
// failed + refreshed state.json + released the lock.
|
|
199
|
+
return 1;
|
|
200
|
+
}
|
|
201
|
+
return renderResult(output);
|
|
202
|
+
}
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
// Phase body — record the PR externalRef, then delegate to runCommand. The
|
|
205
|
+
// phase body itself is small; the heavy lifting (review pipeline, comment
|
|
206
|
+
// posting) is owned by runCommand. INTENTIONAL DEVIATION from the
|
|
207
|
+
// "pure phase body" recipe default: runCommand emits its own console output
|
|
208
|
+
// (phase summaries, finding tables, comment-posting status). Same precedent
|
|
209
|
+
// as scan keeping its LLM call inside the phase body — runCommand is the
|
|
210
|
+
// existing engine of pr's value, not something to extract.
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
async function executePrPhase(input, ctx) {
|
|
213
|
+
const { cwd, configPath, pr, postComments, inlineComments, runCommandImpl } = input;
|
|
214
|
+
// Record the github-pr externalRef BEFORE the runCommand invocation so a
|
|
215
|
+
// crash mid-pipeline still leaves a breadcrumb pointing at the PR. The
|
|
216
|
+
// engine path's Phase 6 resume logic can then `gh pr view <id>` to
|
|
217
|
+
// confirm the PR is still open before deciding whether a replay is safe.
|
|
218
|
+
if (ctx) {
|
|
219
|
+
ctx.emitExternalRef({
|
|
220
|
+
kind: 'github-pr',
|
|
221
|
+
id: String(pr.number),
|
|
222
|
+
provider: 'github',
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
const exitCode = await runCommandImpl({
|
|
226
|
+
cwd,
|
|
227
|
+
configPath,
|
|
228
|
+
base: `origin/${pr.baseRefName}`,
|
|
229
|
+
postComments,
|
|
230
|
+
inlineComments,
|
|
67
231
|
});
|
|
232
|
+
return {
|
|
233
|
+
prNumber: pr.number,
|
|
234
|
+
baseRefName: pr.baseRefName,
|
|
235
|
+
headRefName: pr.headRefName,
|
|
236
|
+
postedComments: postComments,
|
|
237
|
+
postedInlineComments: inlineComments,
|
|
238
|
+
exitCode,
|
|
239
|
+
};
|
|
68
240
|
}
|
|
69
241
|
//# sourceMappingURL=pr.js.map
|