@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.
Files changed (130) hide show
  1. package/CHANGELOG.md +1027 -1
  2. package/README.md +104 -17
  3. package/dist/src/adapters/council/claude.js +2 -1
  4. package/dist/src/adapters/council/openai.js +14 -7
  5. package/dist/src/adapters/deploy/_http.d.ts +43 -0
  6. package/dist/src/adapters/deploy/_http.js +99 -0
  7. package/dist/src/adapters/deploy/fly.d.ts +206 -0
  8. package/dist/src/adapters/deploy/fly.js +696 -0
  9. package/dist/src/adapters/deploy/generic.d.ts +39 -0
  10. package/dist/src/adapters/deploy/generic.js +98 -0
  11. package/dist/src/adapters/deploy/index.d.ts +15 -0
  12. package/dist/src/adapters/deploy/index.js +78 -0
  13. package/dist/src/adapters/deploy/render.d.ts +181 -0
  14. package/dist/src/adapters/deploy/render.js +550 -0
  15. package/dist/src/adapters/deploy/types.d.ts +221 -0
  16. package/dist/src/adapters/deploy/types.js +15 -0
  17. package/dist/src/adapters/deploy/vercel.d.ts +143 -0
  18. package/dist/src/adapters/deploy/vercel.js +426 -0
  19. package/dist/src/adapters/pricing.d.ts +36 -0
  20. package/dist/src/adapters/pricing.js +40 -0
  21. package/dist/src/adapters/review-engine/claude.js +2 -1
  22. package/dist/src/adapters/review-engine/codex.js +12 -8
  23. package/dist/src/adapters/review-engine/gemini.js +2 -1
  24. package/dist/src/adapters/review-engine/openai-compatible.js +2 -1
  25. package/dist/src/adapters/sdk-loader.d.ts +15 -0
  26. package/dist/src/adapters/sdk-loader.js +77 -0
  27. package/dist/src/cli/autopilot.d.ts +71 -0
  28. package/dist/src/cli/autopilot.js +735 -0
  29. package/dist/src/cli/brainstorm.d.ts +23 -0
  30. package/dist/src/cli/brainstorm.js +131 -0
  31. package/dist/src/cli/costs.d.ts +15 -1
  32. package/dist/src/cli/costs.js +99 -10
  33. package/dist/src/cli/deploy.d.ts +71 -0
  34. package/dist/src/cli/deploy.js +539 -0
  35. package/dist/src/cli/fix.d.ts +18 -0
  36. package/dist/src/cli/fix.js +105 -11
  37. package/dist/src/cli/help-text.d.ts +52 -0
  38. package/dist/src/cli/help-text.js +400 -0
  39. package/dist/src/cli/implement.d.ts +91 -0
  40. package/dist/src/cli/implement.js +196 -0
  41. package/dist/src/cli/index.js +784 -222
  42. package/dist/src/cli/json-envelope.d.ts +187 -0
  43. package/dist/src/cli/json-envelope.js +270 -0
  44. package/dist/src/cli/json-mode.d.ts +33 -0
  45. package/dist/src/cli/json-mode.js +201 -0
  46. package/dist/src/cli/migrate.d.ts +111 -0
  47. package/dist/src/cli/migrate.js +305 -0
  48. package/dist/src/cli/plan.d.ts +81 -0
  49. package/dist/src/cli/plan.js +149 -0
  50. package/dist/src/cli/pr.d.ts +106 -0
  51. package/dist/src/cli/pr.js +191 -19
  52. package/dist/src/cli/preflight.js +102 -1
  53. package/dist/src/cli/review.d.ts +27 -0
  54. package/dist/src/cli/review.js +126 -0
  55. package/dist/src/cli/runs-watch-renderer.d.ts +45 -0
  56. package/dist/src/cli/runs-watch-renderer.js +275 -0
  57. package/dist/src/cli/runs-watch.d.ts +41 -0
  58. package/dist/src/cli/runs-watch.js +395 -0
  59. package/dist/src/cli/runs.d.ts +122 -0
  60. package/dist/src/cli/runs.js +902 -0
  61. package/dist/src/cli/scan.d.ts +93 -0
  62. package/dist/src/cli/scan.js +166 -40
  63. package/dist/src/cli/spec.d.ts +66 -0
  64. package/dist/src/cli/spec.js +132 -0
  65. package/dist/src/cli/validate.d.ts +29 -0
  66. package/dist/src/cli/validate.js +131 -0
  67. package/dist/src/core/config/schema.d.ts +43 -0
  68. package/dist/src/core/config/schema.js +25 -0
  69. package/dist/src/core/config/types.d.ts +17 -0
  70. package/dist/src/core/council/runner.d.ts +10 -1
  71. package/dist/src/core/council/runner.js +25 -3
  72. package/dist/src/core/council/types.d.ts +7 -0
  73. package/dist/src/core/errors.d.ts +1 -1
  74. package/dist/src/core/errors.js +12 -0
  75. package/dist/src/core/logging/redaction.d.ts +13 -0
  76. package/dist/src/core/logging/redaction.js +20 -0
  77. package/dist/src/core/migrate/detector-rules.js +6 -0
  78. package/dist/src/core/migrate/schema-validator.js +22 -1
  79. package/dist/src/core/phases/static-rules.d.ts +5 -1
  80. package/dist/src/core/phases/static-rules.js +2 -5
  81. package/dist/src/core/run-state/budget.d.ts +88 -0
  82. package/dist/src/core/run-state/budget.js +141 -0
  83. package/dist/src/core/run-state/cli-internal.d.ts +21 -0
  84. package/dist/src/core/run-state/cli-internal.js +174 -0
  85. package/dist/src/core/run-state/events.d.ts +59 -0
  86. package/dist/src/core/run-state/events.js +504 -0
  87. package/dist/src/core/run-state/lock.d.ts +61 -0
  88. package/dist/src/core/run-state/lock.js +206 -0
  89. package/dist/src/core/run-state/phase-context.d.ts +60 -0
  90. package/dist/src/core/run-state/phase-context.js +108 -0
  91. package/dist/src/core/run-state/phase-registry.d.ts +137 -0
  92. package/dist/src/core/run-state/phase-registry.js +162 -0
  93. package/dist/src/core/run-state/phase-runner.d.ts +80 -0
  94. package/dist/src/core/run-state/phase-runner.js +447 -0
  95. package/dist/src/core/run-state/provider-readback.d.ts +130 -0
  96. package/dist/src/core/run-state/provider-readback.js +426 -0
  97. package/dist/src/core/run-state/replay-decision.d.ts +69 -0
  98. package/dist/src/core/run-state/replay-decision.js +144 -0
  99. package/dist/src/core/run-state/resolve-engine.d.ts +100 -0
  100. package/dist/src/core/run-state/resolve-engine.js +190 -0
  101. package/dist/src/core/run-state/resume-preflight.d.ts +66 -0
  102. package/dist/src/core/run-state/resume-preflight.js +116 -0
  103. package/dist/src/core/run-state/run-phase-with-lifecycle.d.ts +73 -0
  104. package/dist/src/core/run-state/run-phase-with-lifecycle.js +186 -0
  105. package/dist/src/core/run-state/runs.d.ts +57 -0
  106. package/dist/src/core/run-state/runs.js +288 -0
  107. package/dist/src/core/run-state/snapshot.d.ts +14 -0
  108. package/dist/src/core/run-state/snapshot.js +114 -0
  109. package/dist/src/core/run-state/state.d.ts +40 -0
  110. package/dist/src/core/run-state/state.js +164 -0
  111. package/dist/src/core/run-state/types.d.ts +278 -0
  112. package/dist/src/core/run-state/types.js +13 -0
  113. package/dist/src/core/run-state/ulid.d.ts +11 -0
  114. package/dist/src/core/run-state/ulid.js +95 -0
  115. package/dist/src/core/schema-alignment/extractor/index.d.ts +1 -1
  116. package/dist/src/core/schema-alignment/extractor/index.js +2 -2
  117. package/dist/src/core/schema-alignment/extractor/prisma.d.ts +13 -1
  118. package/dist/src/core/schema-alignment/extractor/prisma.js +65 -10
  119. package/dist/src/core/schema-alignment/git-history.d.ts +19 -0
  120. package/dist/src/core/schema-alignment/git-history.js +53 -0
  121. package/dist/src/core/static-rules/rules/brand-tokens.js +2 -2
  122. package/dist/src/core/static-rules/rules/schema-alignment.js +14 -4
  123. package/package.json +9 -5
  124. package/scripts/autoregress.ts +3 -2
  125. package/skills/claude-autopilot.md +1 -1
  126. package/skills/make-interfaces-feel-better/SKILL.md +104 -0
  127. package/skills/migrate/SKILL.md +193 -47
  128. package/skills/simplify-ui/SKILL.md +103 -0
  129. package/skills/ui/SKILL.md +117 -0
  130. 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
@@ -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
@@ -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
- export async function runPr(options = {}) {
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
- // Resolve PR number
39
- let prNumber = options.prNumber;
40
- if (!prNumber) {
41
- const detected = ghJson(['pr', 'view', '--json', 'number'], cwd);
42
- if (!detected) {
43
- console.error(fmt('red', '[pr] No PR number given and no open PR found for current branch.'));
44
- console.error(fmt('dim', ' Usage: guardrail pr <number>'));
45
- return 1;
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
- const pr = ghJson(['pr', 'view', prNumber, '--json', 'number,baseRefName,headRefName,title'], cwd);
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
- console.error(fmt('red', `[pr] Could not fetch PR #${prNumber} — is gh authenticated?`));
52
- return 1;
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
- const fetched = gitFetch('origin', pr.baseRefName, cwd);
58
- if (!fetched) {
59
- console.log(fmt('yellow', ` [pr] Warning: could not fetch origin/${pr.baseRefName} — diff may be stale`));
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
- return runCommand({
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
- base: `origin/${pr.baseRefName}`,
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