create-quiver 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/README.md +187 -518
- package/README_FOR_AI.md +39 -27
- package/ROADMAP.md +11 -0
- package/assets/quiver-wordmark.svg +22 -0
- package/docs/AI_CONTEXT.md.template +2 -0
- package/docs/AI_ONBOARDING_PROMPT.md.template +9 -1
- package/docs/CLI_UX_GUIDE.md +185 -0
- package/docs/COMMANDS.md.template +22 -3
- package/docs/GITFLOW_PR_GUIDE.md +70 -0
- package/docs/getting-started/linux.md +84 -0
- package/docs/getting-started/macos.md +85 -0
- package/docs/getting-started/windows-git-bash-wsl.md +78 -0
- package/docs/getting-started/windows-powershell.md +96 -0
- package/docs/reference/commands.md +98 -0
- package/docs/workflows/existing-project.md +131 -0
- package/docs/workflows/full-ai-spec-to-pr.md +311 -0
- package/docs/workflows/legacy-quiver-project.md +102 -0
- package/docs/workflows/new-project.md +76 -0
- package/package.json +5 -1
- package/specs/quiver-v29-planner-prepare-context-cli-ux/EVIDENCE_REPORT.md +163 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/EXECUTION_PLAN.md +72 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/SPEC.md +173 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/STATUS.md +34 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/pr.md +95 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-00-cli-ux-spec-foundation/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-00-cli-ux-spec-foundation/EXECUTION_BRIEF.md +45 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-00-cli-ux-spec-foundation/slice.json +58 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-01-cli-ux-primitives-theme/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-01-cli-ux-primitives-theme/EXECUTION_BRIEF.md +49 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-01-cli-ux-primitives-theme/slice.json +75 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-02-planner-context-proposal-contract/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-02-planner-context-proposal-contract/EXECUTION_BRIEF.md +47 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-02-planner-context-proposal-contract/slice.json +71 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-03-prepare-context-planner-review-flow/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-03-prepare-context-planner-review-flow/EXECUTION_BRIEF.md +52 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-03-prepare-context-planner-review-flow/slice.json +82 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-04-ux-flag-matrix-compatibility/CLOSURE_BRIEF.md +34 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-04-ux-flag-matrix-compatibility/EXECUTION_BRIEF.md +46 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-04-ux-flag-matrix-compatibility/slice.json +73 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-05-progressive-command-adoption/CLOSURE_BRIEF.md +34 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-05-progressive-command-adoption/EXECUTION_BRIEF.md +46 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-05-progressive-command-adoption/slice.json +83 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-06-docs-tests-smoke-readiness/CLOSURE_BRIEF.md +31 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-06-docs-tests-smoke-readiness/EXECUTION_BRIEF.md +50 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-06-docs-tests-smoke-readiness/slice.json +95 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/EVIDENCE_REPORT.md +213 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/EXECUTION_PLAN.md +85 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/SPEC.md +213 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/STATUS.md +31 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/pr.md +103 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-00-spec-foundation/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-00-spec-foundation/EXECUTION_BRIEF.md +56 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-00-spec-foundation/slice.json +71 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-01-cli-ux-runtime-progress-engine/CLOSURE_BRIEF.md +31 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-01-cli-ux-runtime-progress-engine/EXECUTION_BRIEF.md +54 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-01-cli-ux-runtime-progress-engine/slice.json +69 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-02-agent-profile-selection-selectors/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-02-agent-profile-selection-selectors/EXECUTION_BRIEF.md +56 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-02-agent-profile-selection-selectors/slice.json +81 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-03-provider-model-selection-contract/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-03-provider-model-selection-contract/EXECUTION_BRIEF.md +54 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-03-provider-model-selection-contract/slice.json +75 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-04-planner-ia-progress-flows/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-04-planner-ia-progress-flows/EXECUTION_BRIEF.md +57 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-04-planner-ia-progress-flows/slice.json +85 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-05-executor-pr-progress-flows/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-05-executor-pr-progress-flows/EXECUTION_BRIEF.md +57 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-05-executor-pr-progress-flows/slice.json +85 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-06-doctor-visual-json-contract/CLOSURE_BRIEF.md +35 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-06-doctor-visual-json-contract/EXECUTION_BRIEF.md +55 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-06-doctor-visual-json-contract/slice.json +81 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-07-interactive-init-spec-create/CLOSURE_BRIEF.md +34 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-07-interactive-init-spec-create/EXECUTION_BRIEF.md +55 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-07-interactive-init-spec-create/slice.json +85 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-08-tests-docs-cross-platform-release/CLOSURE_BRIEF.md +34 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-08-tests-docs-cross-platform-release/EXECUTION_BRIEF.md +59 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-08-tests-docs-cross-platform-release/slice.json +95 -0
- package/src/create-quiver/commands/ai.js +809 -71
- package/src/create-quiver/commands/spec.js +167 -5
- package/src/create-quiver/index.js +582 -71
- package/src/create-quiver/lib/agent-profiles.js +111 -10
- package/src/create-quiver/lib/ai/context-proposal.js +389 -0
- package/src/create-quiver/lib/ai/context-proposal.schema.js +31 -0
- package/src/create-quiver/lib/ai/execution-plan.js +106 -8
- package/src/create-quiver/lib/ai/executor.js +284 -28
- package/src/create-quiver/lib/ai/providers.js +71 -1
- package/src/create-quiver/lib/cli/editor.js +118 -0
- package/src/create-quiver/lib/cli/selectors.js +107 -0
- package/src/create-quiver/lib/cli/theme.js +103 -0
- package/src/create-quiver/lib/cli/ux-flags.js +169 -0
- package/src/create-quiver/lib/cli/ux.js +225 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const fs = require('node:fs');
|
|
2
|
+
const os = require('node:os');
|
|
2
3
|
const path = require('node:path');
|
|
3
4
|
|
|
4
5
|
const { redactSecrets } = require('../lib/evidence');
|
|
@@ -10,6 +11,9 @@ const {
|
|
|
10
11
|
writeRawProviderArtifact,
|
|
11
12
|
} = require('../lib/ai/artifacts');
|
|
12
13
|
const { buildContextPackMetadata, normalizeRole } = require('../lib/ai/context-packs');
|
|
14
|
+
const { parseContextProposalOutput } = require('../lib/ai/context-proposal');
|
|
15
|
+
const { openEditor } = require('../lib/cli/editor');
|
|
16
|
+
const { createUx } = require('../lib/cli/ux');
|
|
13
17
|
const { runExecuteSlice, runPromptSlice } = require('../lib/ai/executor');
|
|
14
18
|
const { runExecutePlan } = require('../lib/ai/execution-plan');
|
|
15
19
|
const { buildPrCreatePlan, formatPreflightReport, formatPrCreateReport, preflightGitHubPr, runGhPrCreate } = require('../lib/ai/github');
|
|
@@ -53,7 +57,10 @@ const {
|
|
|
53
57
|
agentProfilesPath,
|
|
54
58
|
buildAgentProfileState,
|
|
55
59
|
getAgentProfile,
|
|
60
|
+
getAgentProfileById,
|
|
56
61
|
listAgentProfiles,
|
|
62
|
+
normalizeAgentProfileRole,
|
|
63
|
+
resolveAgentProfileDisplayName,
|
|
57
64
|
resolveProfileProvider,
|
|
58
65
|
setAgentProfile,
|
|
59
66
|
} = require('../lib/agent-profiles');
|
|
@@ -118,11 +125,119 @@ function normalizeTimeout(timeoutMs) {
|
|
|
118
125
|
return parsed;
|
|
119
126
|
}
|
|
120
127
|
|
|
121
|
-
function
|
|
122
|
-
|
|
123
|
-
|
|
128
|
+
function profileOptionForRole(options, role) {
|
|
129
|
+
const normalized = normalizeAgentProfileRole(role);
|
|
130
|
+
if (normalized === 'planner') {
|
|
131
|
+
return options.plannerProfile || options.profileId || '';
|
|
124
132
|
}
|
|
125
|
-
|
|
133
|
+
if (normalized === 'executor') {
|
|
134
|
+
return options.executorProfile || options.profileId || '';
|
|
135
|
+
}
|
|
136
|
+
if (normalized === 'reviewer') {
|
|
137
|
+
return options.reviewerProfile || options.profileId || '';
|
|
138
|
+
}
|
|
139
|
+
if (normalized === 'doctor') {
|
|
140
|
+
return options.doctorProfile || options.profileId || '';
|
|
141
|
+
}
|
|
142
|
+
return options.profileId || '';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function resolveRuntimeAgentProfile(repoRoot, role, options = {}, fallbackProvider = DEFAULT_PLAN_PROVIDER) {
|
|
146
|
+
const normalizedRole = normalizeAgentProfileRole(role);
|
|
147
|
+
const explicitProvider = options.providerExplicit === true || (options.provider && options.providerExplicit !== false);
|
|
148
|
+
const explicitModel = String(options.model || '').trim();
|
|
149
|
+
|
|
150
|
+
if (explicitProvider) {
|
|
151
|
+
const provider = String(options.provider || fallbackProvider).trim().toLowerCase();
|
|
152
|
+
return {
|
|
153
|
+
role: normalizedRole,
|
|
154
|
+
profile: null,
|
|
155
|
+
profileId: '',
|
|
156
|
+
displayName: provider,
|
|
157
|
+
provider,
|
|
158
|
+
model: explicitModel,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const profileId = profileOptionForRole(options, normalizedRole);
|
|
163
|
+
const profile = profileId
|
|
164
|
+
? getAgentProfileById(repoRoot, normalizedRole, profileId)
|
|
165
|
+
: getAgentProfile(repoRoot, normalizedRole);
|
|
166
|
+
const provider = profile?.provider || resolveProfileProvider(repoRoot, normalizedRole, fallbackProvider);
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
role: normalizedRole,
|
|
170
|
+
profile,
|
|
171
|
+
profileId: profile?.id || profileId || '',
|
|
172
|
+
displayName: profile ? resolveAgentProfileDisplayName(profile) : provider,
|
|
173
|
+
provider,
|
|
174
|
+
model: explicitModel || profile?.model || '',
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function createCommandUx(options = {}) {
|
|
179
|
+
if (options.ux) {
|
|
180
|
+
return options.ux;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return createUx({
|
|
184
|
+
env: options.env || process.env,
|
|
185
|
+
input: options.inputStream,
|
|
186
|
+
output: options.outputStream,
|
|
187
|
+
error: options.errorStream,
|
|
188
|
+
interactive: options.interactive,
|
|
189
|
+
json: options.json,
|
|
190
|
+
noColor: options.noColor,
|
|
191
|
+
prompts: options.prompts,
|
|
192
|
+
spinner: options.spinner,
|
|
193
|
+
stdinIsTTY: options.stdinIsTTY,
|
|
194
|
+
stdoutIsTTY: options.stdoutIsTTY,
|
|
195
|
+
stderrIsTTY: options.stderrIsTTY,
|
|
196
|
+
write: options.write,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function shouldShowHumanProgress(ux, options = {}) {
|
|
201
|
+
return options.progress !== false
|
|
202
|
+
&& options.dryRun !== true
|
|
203
|
+
&& options.printPrompt !== true
|
|
204
|
+
&& ux?.mode?.decoration === true;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function plannerProgressTitle(action, runtimeProfile) {
|
|
208
|
+
return `${action} con ${runtimeProfile.displayName || runtimeProfile.model || runtimeProfile.provider}`;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function writeProgressChecks(ux, enabled, title, checks = []) {
|
|
212
|
+
if (!enabled) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
ux.heading(title);
|
|
216
|
+
for (const check of checks) {
|
|
217
|
+
ux.check(check);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function runProviderWithProgress({ ux, enabled, message = 'Ejecutando agente...', successMessage = 'Agente finalizado', failureMessage = 'Fallo ejecutando agente', run }) {
|
|
222
|
+
async function runAndFailOnProviderResult() {
|
|
223
|
+
const result = await run();
|
|
224
|
+
if (result && result.ok === false) {
|
|
225
|
+
const error = new Error(result.error?.message || 'provider run failed');
|
|
226
|
+
error.code = result.error?.code || 'AI_PROVIDER_RUN_FAILED';
|
|
227
|
+
error.providerResult = result;
|
|
228
|
+
throw error;
|
|
229
|
+
}
|
|
230
|
+
return result;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (!enabled) {
|
|
234
|
+
return run();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return ux.withSpinner(message, runAndFailOnProviderResult, {
|
|
238
|
+
successMessage,
|
|
239
|
+
failureMessage,
|
|
240
|
+
});
|
|
126
241
|
}
|
|
127
242
|
|
|
128
243
|
function buildPlanContext({ role, context, phase, inputText, inputPath, repoRoot, revise = false }) {
|
|
@@ -206,6 +321,10 @@ function formatDryRunReport({ task, provider, role, contextPack, phase, invocati
|
|
|
206
321
|
lines.push(`Timeout: ${invocation.timeoutMs}ms`);
|
|
207
322
|
lines.push(`Prompt transport: ${invocation.promptTransport.mode}`);
|
|
208
323
|
lines.push(`Prompt length: ${invocation.promptLength} bytes`);
|
|
324
|
+
if (invocation.modelSelection && invocation.modelSelection.model) {
|
|
325
|
+
lines.push(`Model: ${invocation.modelSelection.model}`);
|
|
326
|
+
lines.push(`Model support: ${invocation.modelSelection.supported ? 'supported' : 'unsupported'} (${invocation.modelSelection.reason})`);
|
|
327
|
+
}
|
|
209
328
|
|
|
210
329
|
if (onboardingPlan) {
|
|
211
330
|
lines.push(`Prompt source: ${onboardingPlan.promptSource}`);
|
|
@@ -232,6 +351,10 @@ function formatPromptOnlyReport({ task, provider, role, contextPack, phase, invo
|
|
|
232
351
|
lines.push(`Timeout: ${invocation.timeoutMs}ms`);
|
|
233
352
|
lines.push(`Prompt transport: ${invocation.promptTransport.mode}`);
|
|
234
353
|
lines.push(`Prompt length: ${invocation.promptLength} bytes`);
|
|
354
|
+
if (invocation.modelSelection && invocation.modelSelection.model) {
|
|
355
|
+
lines.push(`Model: ${invocation.modelSelection.model}`);
|
|
356
|
+
lines.push(`Model support: ${invocation.modelSelection.supported ? 'supported' : 'unsupported'} (${invocation.modelSelection.reason})`);
|
|
357
|
+
}
|
|
235
358
|
|
|
236
359
|
if (onboardingPlan) {
|
|
237
360
|
lines.push(`Prompt source: ${onboardingPlan.promptSource}`);
|
|
@@ -313,6 +436,261 @@ function formatContextPreparationReport({ dryRun, plan, writePlan, writtenDocs,
|
|
|
313
436
|
return `${lines.join('\n')}\n`;
|
|
314
437
|
}
|
|
315
438
|
|
|
439
|
+
function truncatePromptSection(text, maxChars = 1200) {
|
|
440
|
+
const value = String(text || '').trimEnd();
|
|
441
|
+
if (value.length <= maxChars) {
|
|
442
|
+
return value;
|
|
443
|
+
}
|
|
444
|
+
return `${value.slice(0, maxChars).trimEnd()}\n[... truncated ${value.length - maxChars} chars ...]`;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function buildPrepareContextPlannerPrompt({ pack, draftPack }) {
|
|
448
|
+
const plan = draftPack.plan;
|
|
449
|
+
const allowedPaths = draftPack.docs.map((doc) => doc.path);
|
|
450
|
+
const sections = [
|
|
451
|
+
pack.prompt,
|
|
452
|
+
'Task: planner-assisted Quiver context preparation.',
|
|
453
|
+
'Goal: improve the docs-only onboarding context for future AI work while preserving WDD + SDD safety.',
|
|
454
|
+
'Rules:',
|
|
455
|
+
'- Return only valid JSON. Do not include Markdown outside the JSON object.',
|
|
456
|
+
'- Do not modify product code, UI code, tests, migrations, dependencies, lockfiles, build files, runtime config, generated files, or paths outside the repo.',
|
|
457
|
+
'- Only propose writes to the allowed docs paths listed below.',
|
|
458
|
+
'- If information is ambiguous, document assumptions and risks instead of inventing facts.',
|
|
459
|
+
'- Keep human-authored content safe; Quiver will merge proposals through managed context blocks.',
|
|
460
|
+
'',
|
|
461
|
+
'Allowed docs-only output paths:',
|
|
462
|
+
...allowedPaths.map((item) => `- ${item}`),
|
|
463
|
+
'',
|
|
464
|
+
'Project context:',
|
|
465
|
+
`- Project: ${plan.projectName}`,
|
|
466
|
+
`- Project slug: ${plan.projectSlug}`,
|
|
467
|
+
`- Package manager: ${plan.facts.packageManager}`,
|
|
468
|
+
`- Stack summary: ${plan.facts.stackSummary}`,
|
|
469
|
+
'',
|
|
470
|
+
'Files considered by deterministic prepare-context:',
|
|
471
|
+
...plan.filesConsidered.map((item) => `- ${item.path}: ${item.present ? 'present' : 'absent'}${item.reason ? ` (${item.reason})` : ''}`),
|
|
472
|
+
'',
|
|
473
|
+
'Known assumptions:',
|
|
474
|
+
...formatPathList(plan.assumptions),
|
|
475
|
+
'',
|
|
476
|
+
'Known risks:',
|
|
477
|
+
...formatPathList(plan.risks),
|
|
478
|
+
'',
|
|
479
|
+
'Known contradictions:',
|
|
480
|
+
...formatPathList(plan.contradictions),
|
|
481
|
+
'',
|
|
482
|
+
'Deterministic candidate docs:',
|
|
483
|
+
...draftPack.docs.flatMap((doc) => [
|
|
484
|
+
`### ${doc.path}`,
|
|
485
|
+
truncatePromptSection(doc.content),
|
|
486
|
+
]),
|
|
487
|
+
'',
|
|
488
|
+
'Required JSON output shape:',
|
|
489
|
+
JSON.stringify({
|
|
490
|
+
schema_version: 1,
|
|
491
|
+
kind: 'quiver-context-proposal',
|
|
492
|
+
summary: 'short summary',
|
|
493
|
+
assumptions: ['assumption to confirm'],
|
|
494
|
+
risks: ['risk to track'],
|
|
495
|
+
docs: [
|
|
496
|
+
{
|
|
497
|
+
path: 'docs/AI_CONTEXT.md',
|
|
498
|
+
action: 'update',
|
|
499
|
+
reason: 'why this doc should change',
|
|
500
|
+
content: '# AI Context\\n\\nFull proposed content or managed section content.\\n',
|
|
501
|
+
assumptions: [],
|
|
502
|
+
risks: [],
|
|
503
|
+
},
|
|
504
|
+
],
|
|
505
|
+
omitted_paths: ['paths intentionally omitted'],
|
|
506
|
+
next_steps: ['safe next step'],
|
|
507
|
+
}, null, 2),
|
|
508
|
+
];
|
|
509
|
+
|
|
510
|
+
return {
|
|
511
|
+
allowedPaths,
|
|
512
|
+
plan,
|
|
513
|
+
prompt: sections.join('\n'),
|
|
514
|
+
promptSource: 'quiver prepare-context planner proposal contract',
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function formatPrepareContextPlannerDryRunReport({ provider, role, context, invocation, promptInfo, review, interactive }) {
|
|
519
|
+
const plan = promptInfo.plan;
|
|
520
|
+
const lines = [
|
|
521
|
+
'AI prepare-context planner dry-run',
|
|
522
|
+
'Mode: dry-run',
|
|
523
|
+
'Planner: enabled',
|
|
524
|
+
`Provider: ${provider}`,
|
|
525
|
+
`Role: ${role}`,
|
|
526
|
+
`Context pack: ${context}`,
|
|
527
|
+
`Command: ${invocation.command} ${invocation.args.join(' ')}`.trim(),
|
|
528
|
+
`Prompt bytes: ${invocation.promptLength}`,
|
|
529
|
+
invocation.modelSelection && invocation.modelSelection.model
|
|
530
|
+
? `Model: ${invocation.modelSelection.model}`
|
|
531
|
+
: '',
|
|
532
|
+
invocation.modelSelection && invocation.modelSelection.model
|
|
533
|
+
? `Model support: ${invocation.modelSelection.supported ? 'supported' : 'unsupported'} (${invocation.modelSelection.reason})`
|
|
534
|
+
: '',
|
|
535
|
+
`Prompt source: ${promptInfo.promptSource}`,
|
|
536
|
+
`Review requested: ${review ? 'yes' : 'no'}`,
|
|
537
|
+
`Interactive requested: ${interactive ? 'yes' : 'no'}`,
|
|
538
|
+
'Provider execution: skipped',
|
|
539
|
+
'Writes: none',
|
|
540
|
+
'Product code: untouched',
|
|
541
|
+
`Candidate docs: ${promptInfo.allowedPaths.join(', ')}`,
|
|
542
|
+
'Files considered:',
|
|
543
|
+
...plan.filesConsidered.map((item) => `- ${item.path}: ${item.present ? 'present' : 'absent'}`),
|
|
544
|
+
'Allowed docs-only paths:',
|
|
545
|
+
...promptInfo.allowedPaths.map((item) => `- ${item}`),
|
|
546
|
+
'Next safe commands:',
|
|
547
|
+
'- npx create-quiver ai prepare-context --with-planner --print-prompt',
|
|
548
|
+
'- npx create-quiver ai prepare-context --with-planner --dry-run --review',
|
|
549
|
+
'- npx create-quiver ai prepare-context --with-planner',
|
|
550
|
+
];
|
|
551
|
+
|
|
552
|
+
return `${lines.filter(Boolean).join('\n')}\n`;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function serializeProposalForReview(proposal) {
|
|
556
|
+
return {
|
|
557
|
+
schema_version: proposal.schemaVersion,
|
|
558
|
+
kind: proposal.kind,
|
|
559
|
+
summary: proposal.summary,
|
|
560
|
+
assumptions: proposal.assumptions,
|
|
561
|
+
risks: proposal.risks,
|
|
562
|
+
docs: proposal.docs.map((doc) => ({
|
|
563
|
+
path: doc.path,
|
|
564
|
+
action: doc.action,
|
|
565
|
+
reason: doc.reason,
|
|
566
|
+
content: doc.content,
|
|
567
|
+
assumptions: doc.assumptions,
|
|
568
|
+
risks: doc.risks,
|
|
569
|
+
})),
|
|
570
|
+
omitted_paths: proposal.omittedPaths,
|
|
571
|
+
next_steps: proposal.nextSteps,
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function createProposalReviewFile(proposal, options = {}) {
|
|
576
|
+
const reviewDir = options.reviewDir || fs.mkdtempSync(path.join(os.tmpdir(), 'quiver-context-review-'));
|
|
577
|
+
const reviewPath = path.join(reviewDir, 'context-proposal.json');
|
|
578
|
+
fs.mkdirSync(reviewDir, { recursive: true });
|
|
579
|
+
fs.writeFileSync(reviewPath, `${JSON.stringify(serializeProposalForReview(proposal), null, 2)}\n`);
|
|
580
|
+
return reviewPath;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function makeReviewError(message, reviewPath, cause) {
|
|
584
|
+
const error = new Error(formatError(`${message}\nReview artifact: ${reviewPath}\nNext safe step: edit the artifact into valid proposal JSON or rerun with --with-planner --dry-run.`));
|
|
585
|
+
error.code = cause?.code || 'AI_CONTEXT_REVIEW_FAILED';
|
|
586
|
+
error.cause = cause;
|
|
587
|
+
error.reviewPath = reviewPath;
|
|
588
|
+
return error;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function createReviewTextFile(contents, options = {}) {
|
|
592
|
+
const reviewDir = options.reviewDir || fs.mkdtempSync(path.join(os.tmpdir(), 'quiver-review-'));
|
|
593
|
+
const filename = options.reviewFileName || 'review.md';
|
|
594
|
+
const reviewPath = path.join(reviewDir, filename);
|
|
595
|
+
fs.mkdirSync(reviewDir, { recursive: true });
|
|
596
|
+
fs.writeFileSync(reviewPath, String(contents || ''));
|
|
597
|
+
return reviewPath;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
async function reviewTextWithEditor(repoRoot, contents, options = {}) {
|
|
601
|
+
const reviewPath = createReviewTextFile(contents, options);
|
|
602
|
+
const hasEditorRunner = typeof options.openEditorFn === 'function';
|
|
603
|
+
const canOpenEditor = hasEditorRunner || options.stdinIsTTY === true || (options.stdinIsTTY !== false && Boolean(process.stdin.isTTY));
|
|
604
|
+
|
|
605
|
+
if (!canOpenEditor) {
|
|
606
|
+
throw makeReviewError(`${options.reviewLabel || 'review'} requires an interactive terminal or an injected editor runner.`, reviewPath);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const editorResult = hasEditorRunner
|
|
610
|
+
? options.openEditorFn(reviewPath, { cwd: repoRoot, env: options.env || process.env })
|
|
611
|
+
: openEditor(reviewPath, { cwd: repoRoot, env: options.env || process.env });
|
|
612
|
+
|
|
613
|
+
if (!editorResult || editorResult.ok !== true) {
|
|
614
|
+
throw makeReviewError(editorResult?.reason || `${options.reviewLabel || 'review'} was canceled.`, reviewPath);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
return {
|
|
618
|
+
reviewPath,
|
|
619
|
+
text: fs.readFileSync(reviewPath, 'utf8'),
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
async function confirmInteractiveAction(message, options = {}) {
|
|
624
|
+
if (options.interactive !== true) {
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
const ux = options.ux || createUx({
|
|
629
|
+
interactive: true,
|
|
630
|
+
promptConfirm: options.promptConfirm,
|
|
631
|
+
stdinIsTTY: options.stdinIsTTY,
|
|
632
|
+
stdoutIsTTY: options.stdoutIsTTY,
|
|
633
|
+
stderrIsTTY: options.stderrIsTTY,
|
|
634
|
+
write: options.write,
|
|
635
|
+
});
|
|
636
|
+
const confirmed = await ux.promptConfirm(message, {
|
|
637
|
+
initialValue: false,
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
if (!confirmed) {
|
|
641
|
+
const error = new Error(formatError('interactive approval declined. No files were written.'));
|
|
642
|
+
error.code = 'AI_INTERACTIVE_APPROVAL_DECLINED';
|
|
643
|
+
throw error;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
async function reviewPlannerContextProposal(repoRoot, proposal, options = {}) {
|
|
648
|
+
const reviewPath = createProposalReviewFile(proposal, options);
|
|
649
|
+
const hasEditorRunner = typeof options.openEditorFn === 'function';
|
|
650
|
+
const canOpenEditor = hasEditorRunner || options.stdinIsTTY === true || (options.stdinIsTTY !== false && Boolean(process.stdin.isTTY));
|
|
651
|
+
|
|
652
|
+
if (!canOpenEditor) {
|
|
653
|
+
throw makeReviewError('ai prepare-context review requires an interactive terminal or an injected editor runner.', reviewPath);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
const editorResult = hasEditorRunner
|
|
657
|
+
? options.openEditorFn(reviewPath, { cwd: repoRoot, env: options.env || process.env })
|
|
658
|
+
: openEditor(reviewPath, { cwd: repoRoot, env: options.env || process.env });
|
|
659
|
+
|
|
660
|
+
if (!editorResult || editorResult.ok !== true) {
|
|
661
|
+
throw makeReviewError(editorResult?.reason || 'ai prepare-context review was canceled before applying docs.', reviewPath);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
try {
|
|
665
|
+
return {
|
|
666
|
+
proposal: parseContextProposalOutput(fs.readFileSync(reviewPath, 'utf8')),
|
|
667
|
+
reviewPath,
|
|
668
|
+
};
|
|
669
|
+
} catch (error) {
|
|
670
|
+
throw makeReviewError('edited planner proposal is invalid after review.', reviewPath, error);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
async function confirmPlannerContextWrites(writePlan, options = {}) {
|
|
675
|
+
const changed = writePlan.filter((item) => item.action !== 'skip').length;
|
|
676
|
+
await confirmInteractiveAction(`Apply ${changed} docs-only context update${changed === 1 ? '' : 's'}?`, options);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
function buildPlannerContextWritePlan(repoRoot, proposal) {
|
|
680
|
+
const reasonByPath = new Map(proposal.docs.map((doc) => [doc.path, doc.reason]));
|
|
681
|
+
const draftDocs = proposal.docs
|
|
682
|
+
.filter((doc) => doc.action !== 'skip')
|
|
683
|
+
.map((doc) => ({
|
|
684
|
+
path: doc.path,
|
|
685
|
+
content: doc.content,
|
|
686
|
+
}));
|
|
687
|
+
|
|
688
|
+
return buildContextWritePlan(repoRoot, draftDocs).map((item) => ({
|
|
689
|
+
...item,
|
|
690
|
+
reason: item.action === 'skip' ? item.reason : reasonByPath.get(item.path) || item.reason,
|
|
691
|
+
}));
|
|
692
|
+
}
|
|
693
|
+
|
|
316
694
|
function writeProviderOutput(result) {
|
|
317
695
|
if (result.stdout) {
|
|
318
696
|
process.stdout.write(redactSecrets(result.stdout));
|
|
@@ -855,7 +1233,8 @@ function annotateGitHubError(error, scope) {
|
|
|
855
1233
|
|
|
856
1234
|
async function runOnboard(repoRoot, options = {}) {
|
|
857
1235
|
const role = normalizeRole(options.role || DEFAULT_ONBOARD_ROLE);
|
|
858
|
-
const
|
|
1236
|
+
const runtimeProfile = resolveRuntimeAgentProfile(repoRoot, role, options, DEFAULT_ONBOARD_PROVIDER);
|
|
1237
|
+
const provider = runtimeProfile.provider;
|
|
859
1238
|
const context = options.context || DEFAULT_ONBOARD_CONTEXT;
|
|
860
1239
|
const timeoutMs = normalizeTimeout(options.timeout);
|
|
861
1240
|
const inputText = readTextFile(options.input, repoRoot);
|
|
@@ -868,9 +1247,15 @@ async function runOnboard(repoRoot, options = {}) {
|
|
|
868
1247
|
prompt,
|
|
869
1248
|
cwd: repoRoot,
|
|
870
1249
|
timeoutMs,
|
|
1250
|
+
model: runtimeProfile.model,
|
|
1251
|
+
enforceModelSelection: false,
|
|
871
1252
|
});
|
|
872
1253
|
} catch (error) {
|
|
873
|
-
|
|
1254
|
+
if (error.providerResult) {
|
|
1255
|
+
result = error.providerResult;
|
|
1256
|
+
} else {
|
|
1257
|
+
throw annotateProviderError(error, 'onboard');
|
|
1258
|
+
}
|
|
874
1259
|
}
|
|
875
1260
|
|
|
876
1261
|
if (options.dryRun) {
|
|
@@ -881,6 +1266,7 @@ async function runOnboard(repoRoot, options = {}) {
|
|
|
881
1266
|
contextPack: context,
|
|
882
1267
|
invocation,
|
|
883
1268
|
onboardingPlan: contextInfo.plan,
|
|
1269
|
+
profile: runtimeProfile,
|
|
884
1270
|
};
|
|
885
1271
|
process.stdout.write(formatDryRunReport(report));
|
|
886
1272
|
return report;
|
|
@@ -895,23 +1281,39 @@ async function runOnboard(repoRoot, options = {}) {
|
|
|
895
1281
|
invocation,
|
|
896
1282
|
onboardingPlan: contextInfo.plan,
|
|
897
1283
|
prompt,
|
|
1284
|
+
profile: runtimeProfile,
|
|
898
1285
|
};
|
|
899
1286
|
process.stdout.write(formatPromptOnlyReport(report));
|
|
900
1287
|
return report;
|
|
901
1288
|
}
|
|
902
1289
|
|
|
1290
|
+
const ux = createCommandUx(options);
|
|
1291
|
+
const showProgress = shouldShowHumanProgress(ux, options);
|
|
1292
|
+
writeProgressChecks(
|
|
1293
|
+
ux,
|
|
1294
|
+
showProgress,
|
|
1295
|
+
plannerProgressTitle('Ejecutando onboarding', runtimeProfile),
|
|
1296
|
+
['Leyendo docs base', 'Detectando estructura', 'Preparando prompt'],
|
|
1297
|
+
);
|
|
1298
|
+
|
|
903
1299
|
let result;
|
|
904
1300
|
try {
|
|
905
|
-
result = await (
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
1301
|
+
result = await runProviderWithProgress({
|
|
1302
|
+
ux,
|
|
1303
|
+
enabled: showProgress,
|
|
1304
|
+
run: () => (options.runProviderFn || runProvider)(provider, {
|
|
1305
|
+
prompt,
|
|
1306
|
+
cwd: repoRoot,
|
|
1307
|
+
timeoutMs,
|
|
1308
|
+
dryRun: false,
|
|
1309
|
+
probe: options.probe,
|
|
1310
|
+
spawn: options.spawn,
|
|
1311
|
+
tempRoot: options.tempRoot,
|
|
1312
|
+
tempFileName: options.tempFileName,
|
|
1313
|
+
tempFilePrefix: options.tempFilePrefix,
|
|
1314
|
+
model: runtimeProfile.model,
|
|
1315
|
+
enforceModelSelection: Boolean(runtimeProfile.model),
|
|
1316
|
+
}),
|
|
915
1317
|
});
|
|
916
1318
|
} catch (error) {
|
|
917
1319
|
throw annotateProviderError(error, 'onboard');
|
|
@@ -932,11 +1334,16 @@ async function runOnboard(repoRoot, options = {}) {
|
|
|
932
1334
|
contextPack: context,
|
|
933
1335
|
invocation,
|
|
934
1336
|
onboardingPlan: contextInfo.plan,
|
|
1337
|
+
profile: runtimeProfile,
|
|
935
1338
|
result,
|
|
936
1339
|
};
|
|
937
1340
|
}
|
|
938
1341
|
|
|
939
1342
|
async function runPrepareContext(repoRoot, options = {}) {
|
|
1343
|
+
if (options.withPlanner === true) {
|
|
1344
|
+
return runPrepareContextWithPlanner(repoRoot, options);
|
|
1345
|
+
}
|
|
1346
|
+
|
|
940
1347
|
const draftPack = buildContextPreparationDrafts(repoRoot);
|
|
941
1348
|
const writePlan = buildContextWritePlan(repoRoot, draftPack.docs);
|
|
942
1349
|
const report = {
|
|
@@ -998,10 +1405,181 @@ async function runPrepareContext(repoRoot, options = {}) {
|
|
|
998
1405
|
};
|
|
999
1406
|
}
|
|
1000
1407
|
|
|
1408
|
+
async function runPrepareContextWithPlanner(repoRoot, options = {}) {
|
|
1409
|
+
const role = normalizeRole(options.role || DEFAULT_PLAN_ROLE);
|
|
1410
|
+
const runtimeProfile = resolveRuntimeAgentProfile(repoRoot, role, options, DEFAULT_PLAN_PROVIDER);
|
|
1411
|
+
const provider = runtimeProfile.provider;
|
|
1412
|
+
const context = options.context || DEFAULT_PLAN_CONTEXT;
|
|
1413
|
+
const timeoutMs = normalizeTimeout(options.timeout);
|
|
1414
|
+
const draftPack = buildContextPreparationDrafts(repoRoot);
|
|
1415
|
+
const pack = buildContextPackMetadata({
|
|
1416
|
+
role,
|
|
1417
|
+
packName: context,
|
|
1418
|
+
repoRoot,
|
|
1419
|
+
});
|
|
1420
|
+
const promptInfo = buildPrepareContextPlannerPrompt({ pack, draftPack });
|
|
1421
|
+
const prompt = promptInfo.prompt;
|
|
1422
|
+
assertProviderPromptWithinLimit(prompt, options.promptLimitOptions || {});
|
|
1423
|
+
let invocation;
|
|
1424
|
+
|
|
1425
|
+
try {
|
|
1426
|
+
invocation = buildProviderInvocation(provider, {
|
|
1427
|
+
prompt,
|
|
1428
|
+
cwd: repoRoot,
|
|
1429
|
+
timeoutMs,
|
|
1430
|
+
model: runtimeProfile.model,
|
|
1431
|
+
enforceModelSelection: false,
|
|
1432
|
+
});
|
|
1433
|
+
} catch (error) {
|
|
1434
|
+
if (error.providerResult) {
|
|
1435
|
+
result = error.providerResult;
|
|
1436
|
+
} else {
|
|
1437
|
+
throw annotateProviderError(error, 'prepare-context');
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
if (options.dryRun) {
|
|
1442
|
+
const report = {
|
|
1443
|
+
task: 'prepare-context',
|
|
1444
|
+
mode: 'planner',
|
|
1445
|
+
dryRun: true,
|
|
1446
|
+
provider,
|
|
1447
|
+
role,
|
|
1448
|
+
contextPack: context,
|
|
1449
|
+
invocation,
|
|
1450
|
+
candidateDocs: promptInfo.allowedPaths,
|
|
1451
|
+
plan: draftPack.plan,
|
|
1452
|
+
profile: runtimeProfile,
|
|
1453
|
+
};
|
|
1454
|
+
process.stdout.write(formatPrepareContextPlannerDryRunReport({
|
|
1455
|
+
provider,
|
|
1456
|
+
role,
|
|
1457
|
+
context,
|
|
1458
|
+
invocation,
|
|
1459
|
+
promptInfo,
|
|
1460
|
+
review: options.review === true,
|
|
1461
|
+
interactive: options.interactive === true,
|
|
1462
|
+
}));
|
|
1463
|
+
return report;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
if (options.printPrompt) {
|
|
1467
|
+
const report = {
|
|
1468
|
+
task: 'prepare-context',
|
|
1469
|
+
provider,
|
|
1470
|
+
role,
|
|
1471
|
+
contextPack: context,
|
|
1472
|
+
invocation,
|
|
1473
|
+
prompt,
|
|
1474
|
+
promptSource: promptInfo.promptSource,
|
|
1475
|
+
profile: runtimeProfile,
|
|
1476
|
+
};
|
|
1477
|
+
process.stdout.write(formatPromptOnlyReport(report));
|
|
1478
|
+
return report;
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
const ux = createCommandUx(options);
|
|
1482
|
+
const showProgress = shouldShowHumanProgress(ux, options);
|
|
1483
|
+
writeProgressChecks(
|
|
1484
|
+
ux,
|
|
1485
|
+
showProgress,
|
|
1486
|
+
plannerProgressTitle('Ejecutando onboarding', runtimeProfile),
|
|
1487
|
+
['Leyendo docs base', 'Detectando estructura', 'Preparando prompt'],
|
|
1488
|
+
);
|
|
1489
|
+
|
|
1490
|
+
let result;
|
|
1491
|
+
try {
|
|
1492
|
+
result = await runProviderWithProgress({
|
|
1493
|
+
ux,
|
|
1494
|
+
enabled: showProgress,
|
|
1495
|
+
run: () => (options.runProviderFn || runProvider)(provider, {
|
|
1496
|
+
prompt,
|
|
1497
|
+
cwd: repoRoot,
|
|
1498
|
+
timeoutMs,
|
|
1499
|
+
dryRun: false,
|
|
1500
|
+
probe: options.probe,
|
|
1501
|
+
spawn: options.spawn,
|
|
1502
|
+
tempRoot: options.tempRoot,
|
|
1503
|
+
tempFileName: options.tempFileName,
|
|
1504
|
+
tempFilePrefix: options.tempFilePrefix,
|
|
1505
|
+
model: runtimeProfile.model,
|
|
1506
|
+
enforceModelSelection: Boolean(runtimeProfile.model),
|
|
1507
|
+
}),
|
|
1508
|
+
});
|
|
1509
|
+
} catch (error) {
|
|
1510
|
+
throw annotateProviderError(error, 'prepare-context');
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
if (!result.ok) {
|
|
1514
|
+
writeProviderOutput(result);
|
|
1515
|
+
throw annotateProviderError(result.error || new Error('provider run failed'), 'prepare-context');
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
const clean = extractCleanProviderOutput(result, { prompt, projectRoot: repoRoot });
|
|
1519
|
+
let proposal = parseContextProposalOutput(clean.cleanOutput);
|
|
1520
|
+
let reviewPath = '';
|
|
1521
|
+
|
|
1522
|
+
if (options.review === true) {
|
|
1523
|
+
const reviewed = await reviewPlannerContextProposal(repoRoot, proposal, options);
|
|
1524
|
+
proposal = reviewed.proposal;
|
|
1525
|
+
reviewPath = reviewed.reviewPath;
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
const writePlan = buildPlannerContextWritePlan(repoRoot, proposal);
|
|
1529
|
+
await confirmPlannerContextWrites(writePlan, options);
|
|
1530
|
+
|
|
1531
|
+
const lifecycleRun = ensureAiRun(repoRoot, {
|
|
1532
|
+
command: 'ai prepare-context --with-planner',
|
|
1533
|
+
input: options.input || '',
|
|
1534
|
+
runId: options.runId,
|
|
1535
|
+
phase: 'created',
|
|
1536
|
+
});
|
|
1537
|
+
const snapshot = createContextSnapshots(repoRoot, lifecycleRun, writePlan, options.now || new Date());
|
|
1538
|
+
const plannedDocs = writePlan.filter((item) => item.action !== 'skip').map((item) => item.path);
|
|
1539
|
+
|
|
1540
|
+
process.stdout.write(formatContextPreparationReport({
|
|
1541
|
+
dryRun: false,
|
|
1542
|
+
plan: draftPack.plan,
|
|
1543
|
+
writePlan,
|
|
1544
|
+
writtenDocs: plannedDocs,
|
|
1545
|
+
snapshot,
|
|
1546
|
+
}));
|
|
1547
|
+
|
|
1548
|
+
const writtenDocs = writeDraftDocs(writePlan);
|
|
1549
|
+
updateAiRunPhase(repoRoot, lifecycleRun.run_id, 'onboarding-ready', {
|
|
1550
|
+
artifact: snapshot.manifestPath,
|
|
1551
|
+
command: 'ai prepare-context --with-planner',
|
|
1552
|
+
});
|
|
1553
|
+
process.stdout.write(formatContextPreparationReport({
|
|
1554
|
+
dryRun: false,
|
|
1555
|
+
plan: draftPack.plan,
|
|
1556
|
+
writePlan,
|
|
1557
|
+
writtenDocs,
|
|
1558
|
+
snapshot,
|
|
1559
|
+
completed: true,
|
|
1560
|
+
}));
|
|
1561
|
+
|
|
1562
|
+
return {
|
|
1563
|
+
task: 'prepare-context',
|
|
1564
|
+
mode: 'planner',
|
|
1565
|
+
dryRun: false,
|
|
1566
|
+
provider,
|
|
1567
|
+
role,
|
|
1568
|
+
contextPack: context,
|
|
1569
|
+
invocation,
|
|
1570
|
+
proposal,
|
|
1571
|
+
reviewPath,
|
|
1572
|
+
runId: lifecycleRun.run_id,
|
|
1573
|
+
snapshot,
|
|
1574
|
+
writtenDocs,
|
|
1575
|
+
};
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1001
1578
|
async function runPlan(repoRoot, options = {}) {
|
|
1002
1579
|
const phase = normalizePlannerPhase(options.phase || DEFAULT_PLAN_PHASE);
|
|
1003
1580
|
const role = normalizeRole(options.role || DEFAULT_PLAN_ROLE);
|
|
1004
|
-
const
|
|
1581
|
+
const runtimeProfile = resolveRuntimeAgentProfile(repoRoot, role, options, DEFAULT_PLAN_PROVIDER);
|
|
1582
|
+
const provider = runtimeProfile.provider;
|
|
1005
1583
|
const context = options.context || DEFAULT_PLAN_CONTEXT;
|
|
1006
1584
|
const timeoutMs = normalizeTimeout(options.timeout);
|
|
1007
1585
|
let inputPath = options.input || '';
|
|
@@ -1103,9 +1681,15 @@ async function runPlan(repoRoot, options = {}) {
|
|
|
1103
1681
|
prompt,
|
|
1104
1682
|
cwd: repoRoot,
|
|
1105
1683
|
timeoutMs,
|
|
1684
|
+
model: runtimeProfile.model,
|
|
1685
|
+
enforceModelSelection: false,
|
|
1106
1686
|
});
|
|
1107
1687
|
} catch (error) {
|
|
1108
|
-
|
|
1688
|
+
if (error.providerResult) {
|
|
1689
|
+
result = error.providerResult;
|
|
1690
|
+
} else {
|
|
1691
|
+
throw annotateProviderError(error, 'plan', phase);
|
|
1692
|
+
}
|
|
1109
1693
|
}
|
|
1110
1694
|
|
|
1111
1695
|
if (options.dryRun) {
|
|
@@ -1116,8 +1700,18 @@ async function runPlan(repoRoot, options = {}) {
|
|
|
1116
1700
|
contextPack: contextInfo.pack.packName,
|
|
1117
1701
|
phase,
|
|
1118
1702
|
invocation,
|
|
1703
|
+
profile: runtimeProfile,
|
|
1119
1704
|
};
|
|
1120
1705
|
process.stdout.write(formatDryRunReport(report));
|
|
1706
|
+
if (options.withPlanner === true) {
|
|
1707
|
+
process.stdout.write('Planner mode: already active for ai plan; --with-planner is accepted for UX consistency.\n');
|
|
1708
|
+
}
|
|
1709
|
+
if (options.review === true) {
|
|
1710
|
+
process.stdout.write('Review requested: provider output will be opened for review before saving the draft in live mode.\n');
|
|
1711
|
+
}
|
|
1712
|
+
if (options.interactive === true) {
|
|
1713
|
+
process.stdout.write('Interactive requested: live mode will ask before saving the draft.\n');
|
|
1714
|
+
}
|
|
1121
1715
|
return report;
|
|
1122
1716
|
}
|
|
1123
1717
|
|
|
@@ -1130,23 +1724,39 @@ async function runPlan(repoRoot, options = {}) {
|
|
|
1130
1724
|
phase,
|
|
1131
1725
|
invocation,
|
|
1132
1726
|
prompt,
|
|
1727
|
+
profile: runtimeProfile,
|
|
1133
1728
|
};
|
|
1134
1729
|
process.stdout.write(formatPromptOnlyReport(report));
|
|
1135
1730
|
return report;
|
|
1136
1731
|
}
|
|
1137
1732
|
|
|
1733
|
+
const ux = createCommandUx(options);
|
|
1734
|
+
const showProgress = shouldShowHumanProgress(ux, options);
|
|
1735
|
+
writeProgressChecks(
|
|
1736
|
+
ux,
|
|
1737
|
+
showProgress,
|
|
1738
|
+
plannerProgressTitle(`Ejecutando plan ${phase}`, runtimeProfile),
|
|
1739
|
+
['Leyendo entrada', 'Preparando contexto', 'Preparando prompt'],
|
|
1740
|
+
);
|
|
1741
|
+
|
|
1138
1742
|
let result;
|
|
1139
1743
|
try {
|
|
1140
|
-
result = await (
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1744
|
+
result = await runProviderWithProgress({
|
|
1745
|
+
ux,
|
|
1746
|
+
enabled: showProgress,
|
|
1747
|
+
run: () => (options.runProviderFn || runProvider)(provider, {
|
|
1748
|
+
prompt,
|
|
1749
|
+
cwd: repoRoot,
|
|
1750
|
+
timeoutMs,
|
|
1751
|
+
dryRun: false,
|
|
1752
|
+
probe: options.probe,
|
|
1753
|
+
spawn: options.spawn,
|
|
1754
|
+
tempRoot: options.tempRoot,
|
|
1755
|
+
tempFileName: options.tempFileName,
|
|
1756
|
+
tempFilePrefix: options.tempFilePrefix,
|
|
1757
|
+
model: runtimeProfile.model,
|
|
1758
|
+
enforceModelSelection: Boolean(runtimeProfile.model),
|
|
1759
|
+
}),
|
|
1150
1760
|
});
|
|
1151
1761
|
} catch (error) {
|
|
1152
1762
|
throw annotateProviderError(error, 'plan', phase);
|
|
@@ -1157,13 +1767,27 @@ async function runPlan(repoRoot, options = {}) {
|
|
|
1157
1767
|
throw annotateProviderError(result.error || new Error('provider run failed'), 'plan', phase);
|
|
1158
1768
|
}
|
|
1159
1769
|
|
|
1770
|
+
const clean = extractCleanProviderOutput(result, { prompt, projectRoot: repoRoot });
|
|
1771
|
+
let cleanOutput = clean.cleanOutput;
|
|
1772
|
+
let reviewPath = '';
|
|
1773
|
+
|
|
1774
|
+
if (options.review === true) {
|
|
1775
|
+
const reviewed = await reviewTextWithEditor(repoRoot, cleanOutput, {
|
|
1776
|
+
...options,
|
|
1777
|
+
reviewFileName: `ai-plan-${phase}-draft.md`,
|
|
1778
|
+
reviewLabel: `ai plan --phase ${phase} review`,
|
|
1779
|
+
});
|
|
1780
|
+
cleanOutput = reviewed.text;
|
|
1781
|
+
reviewPath = reviewed.reviewPath;
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
await confirmInteractiveAction(`Save ${phase} planner draft?`, options);
|
|
1160
1785
|
const lifecycleRun = ensureAiRun(repoRoot, {
|
|
1161
1786
|
command: `ai plan --phase ${phase}`,
|
|
1162
1787
|
input: inputPath,
|
|
1163
1788
|
runId: options.runId,
|
|
1164
1789
|
});
|
|
1165
|
-
|
|
1166
|
-
writeCleanProviderOutput(clean);
|
|
1790
|
+
writeCleanProviderOutput({ cleanOutput });
|
|
1167
1791
|
const rawArtifact = writeRawProviderArtifact(repoRoot, lifecycleRun.run_id, `ai-plan-${phase}`, result, {
|
|
1168
1792
|
metadata: {
|
|
1169
1793
|
phase,
|
|
@@ -1174,10 +1798,11 @@ async function runPlan(repoRoot, options = {}) {
|
|
|
1174
1798
|
input_compaction: inputCompaction,
|
|
1175
1799
|
},
|
|
1176
1800
|
});
|
|
1177
|
-
const draft = savePlannerDraft(repoRoot, phase, inputPath,
|
|
1801
|
+
const draft = savePlannerDraft(repoRoot, phase, inputPath, cleanOutput, {
|
|
1178
1802
|
rawArtifactPath: rawArtifact.path,
|
|
1179
1803
|
outputSource: clean.source,
|
|
1180
1804
|
inputCompaction,
|
|
1805
|
+
reviewPath,
|
|
1181
1806
|
});
|
|
1182
1807
|
updateAiRunPhase(repoRoot, lifecycleRun.run_id, phase === 'acceptance' ? 'acceptance-draft' : 'technical-plan-draft', {
|
|
1183
1808
|
artifact: path.relative(repoRoot, draft.filePath).split(path.sep).join('/'),
|
|
@@ -1192,12 +1817,14 @@ async function runPlan(repoRoot, options = {}) {
|
|
|
1192
1817
|
phase,
|
|
1193
1818
|
invocation,
|
|
1194
1819
|
result,
|
|
1820
|
+
reviewPath,
|
|
1195
1821
|
};
|
|
1196
1822
|
}
|
|
1197
1823
|
|
|
1198
1824
|
async function runReviewPlan(repoRoot, options = {}) {
|
|
1199
1825
|
const role = 'planner';
|
|
1200
|
-
const
|
|
1826
|
+
const runtimeProfile = resolveRuntimeAgentProfile(repoRoot, 'reviewer', options, DEFAULT_PLAN_PROVIDER);
|
|
1827
|
+
const provider = runtimeProfile.provider;
|
|
1201
1828
|
const context = options.context || DEFAULT_PLAN_CONTEXT;
|
|
1202
1829
|
const timeoutMs = normalizeTimeout(options.timeout);
|
|
1203
1830
|
const resolved = resolveTechnicalPlanReviewInput(repoRoot, options.input || undefined);
|
|
@@ -1221,9 +1848,15 @@ async function runReviewPlan(repoRoot, options = {}) {
|
|
|
1221
1848
|
prompt: built.prompt,
|
|
1222
1849
|
cwd: repoRoot,
|
|
1223
1850
|
timeoutMs,
|
|
1851
|
+
model: runtimeProfile.model,
|
|
1852
|
+
enforceModelSelection: false,
|
|
1224
1853
|
});
|
|
1225
1854
|
} catch (error) {
|
|
1226
|
-
|
|
1855
|
+
if (error.providerResult) {
|
|
1856
|
+
result = error.providerResult;
|
|
1857
|
+
} else {
|
|
1858
|
+
throw annotateProviderError(error, 'review-plan');
|
|
1859
|
+
}
|
|
1227
1860
|
}
|
|
1228
1861
|
|
|
1229
1862
|
if (options.dryRun) {
|
|
@@ -1237,6 +1870,7 @@ async function runReviewPlan(repoRoot, options = {}) {
|
|
|
1237
1870
|
inputPath,
|
|
1238
1871
|
inputKind: resolved.kind,
|
|
1239
1872
|
inputVersion: resolved.version,
|
|
1873
|
+
profile: runtimeProfile,
|
|
1240
1874
|
};
|
|
1241
1875
|
process.stdout.write(formatDryRunReport({
|
|
1242
1876
|
task: 'review-plan',
|
|
@@ -1268,23 +1902,39 @@ async function runReviewPlan(repoRoot, options = {}) {
|
|
|
1268
1902
|
inputPath,
|
|
1269
1903
|
inputKind: resolved.kind,
|
|
1270
1904
|
inputVersion: resolved.version,
|
|
1905
|
+
profile: runtimeProfile,
|
|
1271
1906
|
};
|
|
1272
1907
|
process.stdout.write(formatPromptOnlyReport(report));
|
|
1273
1908
|
return report;
|
|
1274
1909
|
}
|
|
1275
1910
|
|
|
1911
|
+
const ux = createCommandUx(options);
|
|
1912
|
+
const showProgress = shouldShowHumanProgress(ux, options);
|
|
1913
|
+
writeProgressChecks(
|
|
1914
|
+
ux,
|
|
1915
|
+
showProgress,
|
|
1916
|
+
plannerProgressTitle('Ejecutando revisión del plan', runtimeProfile),
|
|
1917
|
+
['Leyendo plan técnico', 'Preparando contexto', 'Preparando prompt'],
|
|
1918
|
+
);
|
|
1919
|
+
|
|
1276
1920
|
let result;
|
|
1277
1921
|
try {
|
|
1278
|
-
result = await (
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1922
|
+
result = await runProviderWithProgress({
|
|
1923
|
+
ux,
|
|
1924
|
+
enabled: showProgress,
|
|
1925
|
+
run: () => (options.runProviderFn || runProvider)(provider, {
|
|
1926
|
+
prompt: built.prompt,
|
|
1927
|
+
cwd: repoRoot,
|
|
1928
|
+
timeoutMs,
|
|
1929
|
+
dryRun: false,
|
|
1930
|
+
probe: options.probe,
|
|
1931
|
+
spawn: options.spawn,
|
|
1932
|
+
tempRoot: options.tempRoot,
|
|
1933
|
+
tempFileName: options.tempFileName,
|
|
1934
|
+
tempFilePrefix: options.tempFilePrefix,
|
|
1935
|
+
model: runtimeProfile.model,
|
|
1936
|
+
enforceModelSelection: Boolean(runtimeProfile.model),
|
|
1937
|
+
}),
|
|
1288
1938
|
});
|
|
1289
1939
|
} catch (error) {
|
|
1290
1940
|
throw annotateProviderError(error, 'review-plan');
|
|
@@ -1342,7 +1992,8 @@ async function runReviewPlan(repoRoot, options = {}) {
|
|
|
1342
1992
|
|
|
1343
1993
|
async function runRepairPlan(repoRoot, options = {}) {
|
|
1344
1994
|
const role = normalizeRole(options.role || DEFAULT_PLAN_ROLE);
|
|
1345
|
-
const
|
|
1995
|
+
const runtimeProfile = resolveRuntimeAgentProfile(repoRoot, role, options, DEFAULT_PLAN_PROVIDER);
|
|
1996
|
+
const provider = runtimeProfile.provider;
|
|
1346
1997
|
const context = options.context || DEFAULT_PLAN_CONTEXT;
|
|
1347
1998
|
const timeoutMs = normalizeTimeout(options.timeout);
|
|
1348
1999
|
const source = resolveApprovedTechnicalPlanForRepair(repoRoot, options.input || '');
|
|
@@ -1362,9 +2013,15 @@ async function runRepairPlan(repoRoot, options = {}) {
|
|
|
1362
2013
|
prompt: built.prompt,
|
|
1363
2014
|
cwd: repoRoot,
|
|
1364
2015
|
timeoutMs,
|
|
2016
|
+
model: runtimeProfile.model,
|
|
2017
|
+
enforceModelSelection: false,
|
|
1365
2018
|
});
|
|
1366
2019
|
} catch (error) {
|
|
1367
|
-
|
|
2020
|
+
if (error.providerResult) {
|
|
2021
|
+
providerResult = error.providerResult;
|
|
2022
|
+
} else {
|
|
2023
|
+
throw annotateProviderError(error, 'repair-plan');
|
|
2024
|
+
}
|
|
1368
2025
|
}
|
|
1369
2026
|
|
|
1370
2027
|
if (options.dryRun) {
|
|
@@ -1375,6 +2032,7 @@ async function runRepairPlan(repoRoot, options = {}) {
|
|
|
1375
2032
|
contextPack: built.pack.packName,
|
|
1376
2033
|
phase: 'technical-plan',
|
|
1377
2034
|
invocation,
|
|
2035
|
+
profile: runtimeProfile,
|
|
1378
2036
|
};
|
|
1379
2037
|
process.stdout.write(formatDryRunReport(report));
|
|
1380
2038
|
process.stdout.write(`Source approved artifact: ${source.path}\n`);
|
|
@@ -1394,23 +2052,39 @@ async function runRepairPlan(repoRoot, options = {}) {
|
|
|
1394
2052
|
inputPath: source.path,
|
|
1395
2053
|
inputKind: 'approved',
|
|
1396
2054
|
inputVersion: source.approval.meta?.approved?.version || null,
|
|
2055
|
+
profile: runtimeProfile,
|
|
1397
2056
|
};
|
|
1398
2057
|
process.stdout.write(formatPromptOnlyReport(report));
|
|
1399
2058
|
return report;
|
|
1400
2059
|
}
|
|
1401
2060
|
|
|
2061
|
+
const ux = createCommandUx(options);
|
|
2062
|
+
const showProgress = shouldShowHumanProgress(ux, options);
|
|
2063
|
+
writeProgressChecks(
|
|
2064
|
+
ux,
|
|
2065
|
+
showProgress,
|
|
2066
|
+
plannerProgressTitle('Ejecutando reparación del plan', runtimeProfile),
|
|
2067
|
+
['Leyendo plan aprobado', 'Preparando contexto', 'Preparando prompt'],
|
|
2068
|
+
);
|
|
2069
|
+
|
|
1402
2070
|
let providerResult;
|
|
1403
2071
|
try {
|
|
1404
|
-
providerResult = await (
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
2072
|
+
providerResult = await runProviderWithProgress({
|
|
2073
|
+
ux,
|
|
2074
|
+
enabled: showProgress,
|
|
2075
|
+
run: () => (options.runProviderFn || runProvider)(provider, {
|
|
2076
|
+
prompt: built.prompt,
|
|
2077
|
+
cwd: repoRoot,
|
|
2078
|
+
timeoutMs,
|
|
2079
|
+
dryRun: false,
|
|
2080
|
+
probe: options.probe,
|
|
2081
|
+
spawn: options.spawn,
|
|
2082
|
+
tempRoot: options.tempRoot,
|
|
2083
|
+
tempFileName: options.tempFileName,
|
|
2084
|
+
tempFilePrefix: options.tempFilePrefix,
|
|
2085
|
+
model: runtimeProfile.model,
|
|
2086
|
+
enforceModelSelection: Boolean(runtimeProfile.model),
|
|
2087
|
+
}),
|
|
1414
2088
|
});
|
|
1415
2089
|
} catch (error) {
|
|
1416
2090
|
throw annotateProviderError(error, 'repair-plan');
|
|
@@ -1746,10 +2420,13 @@ function runLifecycleRun(repoRoot, options = {}) {
|
|
|
1746
2420
|
|
|
1747
2421
|
function formatAgentProfile(profile) {
|
|
1748
2422
|
const lines = [
|
|
2423
|
+
`ID: ${profile.id || 'default'}`,
|
|
1749
2424
|
`Role: ${profile.role}`,
|
|
1750
2425
|
`Provider: ${profile.provider}`,
|
|
1751
2426
|
`Model: ${profile.model || '(not set)'}`,
|
|
1752
2427
|
`Label: ${profile.label || '(not set)'}`,
|
|
2428
|
+
`Display name: ${resolveAgentProfileDisplayName(profile) || '(not set)'}`,
|
|
2429
|
+
`Default: ${profile.default === true ? 'yes' : 'no'}`,
|
|
1753
2430
|
`Context: ${profile.context || '(not set)'}`,
|
|
1754
2431
|
`Updated: ${profile.updated_at}`,
|
|
1755
2432
|
];
|
|
@@ -1765,7 +2442,9 @@ function formatAgentProfileList(profiles) {
|
|
|
1765
2442
|
}
|
|
1766
2443
|
const model = item.profile.model ? ` model=${item.profile.model}` : '';
|
|
1767
2444
|
const label = item.profile.label ? ` label=${item.profile.label}` : '';
|
|
1768
|
-
|
|
2445
|
+
const displayName = resolveAgentProfileDisplayName(item.profile);
|
|
2446
|
+
const alternatives = item.profiles.length > 1 ? ` options=${item.profiles.length}` : '';
|
|
2447
|
+
lines.push(`- ${item.role}: provider=${item.profile.provider}${model}${label} displayName=${displayName}${alternatives}`);
|
|
1769
2448
|
}
|
|
1770
2449
|
return `${lines.join('\n')}\n`;
|
|
1771
2450
|
}
|
|
@@ -1798,6 +2477,9 @@ function runAgent(repoRoot, options = {}) {
|
|
|
1798
2477
|
if (options.dryRun) {
|
|
1799
2478
|
const preview = buildAgentProfileState(repoRoot, options.role, {
|
|
1800
2479
|
context: options.context,
|
|
2480
|
+
default: options.defaultProfile,
|
|
2481
|
+
displayName: options.displayName,
|
|
2482
|
+
id: options.id,
|
|
1801
2483
|
label: options.label,
|
|
1802
2484
|
model: options.model,
|
|
1803
2485
|
provider: options.provider,
|
|
@@ -1813,6 +2495,9 @@ function runAgent(repoRoot, options = {}) {
|
|
|
1813
2495
|
}
|
|
1814
2496
|
const result = setAgentProfile(repoRoot, options.role, {
|
|
1815
2497
|
context: options.context,
|
|
2498
|
+
default: options.defaultProfile,
|
|
2499
|
+
displayName: options.displayName,
|
|
2500
|
+
id: options.id,
|
|
1816
2501
|
label: options.label,
|
|
1817
2502
|
model: options.model,
|
|
1818
2503
|
provider: options.provider,
|
|
@@ -1832,10 +2517,14 @@ function runAgent(repoRoot, options = {}) {
|
|
|
1832
2517
|
if (!options.role) {
|
|
1833
2518
|
throw new Error(formatError('missing agent role. Use: npx create-quiver ai agent show <planner|executor|reviewer|doctor>'));
|
|
1834
2519
|
}
|
|
1835
|
-
const profile =
|
|
2520
|
+
const profile = options.id
|
|
2521
|
+
? getAgentProfileById(repoRoot, options.role, options.id)
|
|
2522
|
+
: getAgentProfile(repoRoot, options.role);
|
|
1836
2523
|
if (!profile) {
|
|
1837
2524
|
throw new Error(formatActionableError({
|
|
1838
|
-
failure:
|
|
2525
|
+
failure: options.id
|
|
2526
|
+
? `agent profile '${options.role}/${options.id}' is not configured.`
|
|
2527
|
+
: `agent profile '${options.role}' is not configured.`,
|
|
1839
2528
|
impact: 'Quiver will fall back to default provider behavior and may use the wrong model/cost profile.',
|
|
1840
2529
|
fix: `Configure the ${options.role} profile with a supported provider and optional model label.`,
|
|
1841
2530
|
nextCommand: `npx create-quiver ai agent set ${options.role} --provider <provider> --model <label>`,
|
|
@@ -1896,20 +2585,32 @@ async function runGitHubTask(repoRoot, options = {}, mode = 'pr') {
|
|
|
1896
2585
|
async function runPr(repoRoot, options = {}) {
|
|
1897
2586
|
const dryRun = options.dryRun === true;
|
|
1898
2587
|
const create = options.create === true;
|
|
2588
|
+
const ux = createCommandUx(options);
|
|
2589
|
+
const showProgress = create && !dryRun && shouldShowHumanProgress(ux, options);
|
|
2590
|
+
if (showProgress) {
|
|
2591
|
+
ux.heading('Creando PR con gh');
|
|
2592
|
+
}
|
|
1899
2593
|
let preflight;
|
|
1900
2594
|
|
|
1901
2595
|
try {
|
|
1902
|
-
preflight = await (
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
2596
|
+
preflight = await runProviderWithProgress({
|
|
2597
|
+
ux,
|
|
2598
|
+
enabled: showProgress,
|
|
2599
|
+
message: 'Ejecutando preflight de GitHub...',
|
|
2600
|
+
successMessage: 'Preflight de GitHub listo',
|
|
2601
|
+
failureMessage: 'Falló preflight de GitHub',
|
|
2602
|
+
run: () => (options.preflightFn || preflightGitHubPr)(repoRoot, {
|
|
2603
|
+
remote: options.remote,
|
|
2604
|
+
sshHostAlias: options.sshHostAlias,
|
|
2605
|
+
identityFile: options.identityFile,
|
|
2606
|
+
gitFlowGuidePath: options.gitFlowGuidePath,
|
|
2607
|
+
ghCommand: options.ghCommand,
|
|
2608
|
+
ghProbe: options.ghProbe,
|
|
2609
|
+
ghAuthProbe: options.ghAuthProbe,
|
|
2610
|
+
ghProbeArgs: options.ghProbeArgs,
|
|
2611
|
+
ghAuthArgs: options.ghAuthArgs,
|
|
2612
|
+
blockedBranches: options.blockedBranches,
|
|
2613
|
+
}),
|
|
1913
2614
|
});
|
|
1914
2615
|
} catch (error) {
|
|
1915
2616
|
throw annotateGitHubError(error, 'pr');
|
|
@@ -1924,10 +2625,38 @@ async function runPr(repoRoot, options = {}) {
|
|
|
1924
2625
|
prBodyPath: options.prBodyPath,
|
|
1925
2626
|
title: options.title,
|
|
1926
2627
|
});
|
|
2628
|
+
if (showProgress) {
|
|
2629
|
+
ux.check('Cuerpo del PR preparado');
|
|
2630
|
+
}
|
|
1927
2631
|
} catch (error) {
|
|
1928
2632
|
throw annotateGitHubError(error, 'pr');
|
|
1929
2633
|
}
|
|
1930
2634
|
|
|
2635
|
+
if (options.review === true) {
|
|
2636
|
+
const hasEditorRunner = typeof options.openEditorFn === 'function';
|
|
2637
|
+
const canOpenEditor = hasEditorRunner || options.stdinIsTTY === true || (options.stdinIsTTY !== false && Boolean(process.stdin.isTTY));
|
|
2638
|
+
if (!canOpenEditor) {
|
|
2639
|
+
throw annotateGitHubError(makeReviewError('ai pr --review requires an interactive terminal or an injected editor runner.', plan.prBodyPath), 'pr');
|
|
2640
|
+
}
|
|
2641
|
+
const editorResult = hasEditorRunner
|
|
2642
|
+
? options.openEditorFn(plan.prBodyPath, { cwd: repoRoot, env: options.env || process.env })
|
|
2643
|
+
: openEditor(plan.prBodyPath, { cwd: repoRoot, env: options.env || process.env });
|
|
2644
|
+
if (!editorResult || editorResult.ok !== true) {
|
|
2645
|
+
throw annotateGitHubError(makeReviewError(editorResult?.reason || 'ai pr review was canceled.', plan.prBodyPath), 'pr');
|
|
2646
|
+
}
|
|
2647
|
+
try {
|
|
2648
|
+
plan = buildPrCreatePlan(repoRoot, preflight, {
|
|
2649
|
+
baseBranch: options.baseBranch,
|
|
2650
|
+
ghCommand: options.ghCommand,
|
|
2651
|
+
input: options.input,
|
|
2652
|
+
prBodyPath: options.prBodyPath,
|
|
2653
|
+
title: options.title,
|
|
2654
|
+
});
|
|
2655
|
+
} catch (error) {
|
|
2656
|
+
throw annotateGitHubError(error, 'pr');
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
|
|
1931
2660
|
if (dryRun || !create) {
|
|
1932
2661
|
process.stdout.write(formatPrCreateReport({ preflight, plan }, { dryRun, create }));
|
|
1933
2662
|
return {
|
|
@@ -1939,10 +2668,19 @@ async function runPr(repoRoot, options = {}) {
|
|
|
1939
2668
|
};
|
|
1940
2669
|
}
|
|
1941
2670
|
|
|
2671
|
+
await confirmInteractiveAction(`Create GitHub PR '${plan.title}'?`, options);
|
|
2672
|
+
|
|
1942
2673
|
let result;
|
|
1943
2674
|
try {
|
|
1944
|
-
result =
|
|
1945
|
-
|
|
2675
|
+
result = await runProviderWithProgress({
|
|
2676
|
+
ux,
|
|
2677
|
+
enabled: showProgress,
|
|
2678
|
+
message: 'Creando PR en GitHub...',
|
|
2679
|
+
successMessage: 'PR creado',
|
|
2680
|
+
failureMessage: 'Falló creación del PR',
|
|
2681
|
+
run: () => runGhPrCreate(plan, {
|
|
2682
|
+
ghCreateRunner: options.ghCreateRunner,
|
|
2683
|
+
}),
|
|
1946
2684
|
});
|
|
1947
2685
|
} catch (error) {
|
|
1948
2686
|
throw annotateGitHubError(error, 'pr');
|