create-quiver 0.12.0 → 0.12.1
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 +44 -0
- package/README.md +49 -17
- package/README_FOR_AI.md +31 -29
- package/ROADMAP.md +15 -3
- package/docs/AI_ONBOARDING_PROMPT.md.template +7 -1
- package/docs/COMMANDS.md.template +44 -18
- package/docs/STATUS.md.template +5 -1
- package/docs/WORKFLOW.md.template +13 -11
- package/package.json +9 -3
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/EVIDENCE_REPORT.md +293 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/EXECUTION_PLAN.md +58 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/SPEC.md +242 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/STATUS.md +35 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/pr.md +77 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-00-spec-foundation/CLOSURE_BRIEF.md +34 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-00-spec-foundation/EXECUTION_BRIEF.md +52 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-00-spec-foundation/slice.json +52 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-01-cli-contract-compatibility/CLOSURE_BRIEF.md +36 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-01-cli-contract-compatibility/EXECUTION_BRIEF.md +52 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-01-cli-contract-compatibility/slice.json +56 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-02-run-state-phase-locks/CLOSURE_BRIEF.md +43 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-02-run-state-phase-locks/EXECUTION_BRIEF.md +54 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-02-run-state-phase-locks/slice.json +52 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-03-safe-ai-onboarding-docs/CLOSURE_BRIEF.md +35 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-03-safe-ai-onboarding-docs/EXECUTION_BRIEF.md +53 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-03-safe-ai-onboarding-docs/slice.json +54 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-04-agent-profiles-adapters/CLOSURE_BRIEF.md +34 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-04-agent-profiles-adapters/EXECUTION_BRIEF.md +54 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-04-agent-profiles-adapters/slice.json +52 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-05-approval-gates/CLOSURE_BRIEF.md +34 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-05-approval-gates/EXECUTION_BRIEF.md +54 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-05-approval-gates/slice.json +53 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-06-spec-slice-generator/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-06-spec-slice-generator/EXECUTION_BRIEF.md +56 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-06-spec-slice-generator/slice.json +55 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-07-slice-execution-planner/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-07-slice-execution-planner/EXECUTION_BRIEF.md +54 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-07-slice-execution-planner/slice.json +52 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-08-controlled-slice-execution/CLOSURE_BRIEF.md +39 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-08-controlled-slice-execution/EXECUTION_BRIEF.md +56 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-08-controlled-slice-execution/slice.json +53 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-09-git-worktree-pr-lifecycle/CLOSURE_BRIEF.md +38 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-09-git-worktree-pr-lifecycle/EXECUTION_BRIEF.md +57 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-09-git-worktree-pr-lifecycle/slice.json +52 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-10-validation-errors-fixtures/CLOSURE_BRIEF.md +39 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-10-validation-errors-fixtures/EXECUTION_BRIEF.md +55 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-10-validation-errors-fixtures/slice.json +56 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-11-export-dashboard-migration/CLOSURE_BRIEF.md +36 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-11-export-dashboard-migration/EXECUTION_BRIEF.md +54 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-11-export-dashboard-migration/slice.json +53 -0
- package/specs/quiver-v26-0121-smoke-hardening/EVIDENCE_REPORT.md +208 -0
- package/specs/quiver-v26-0121-smoke-hardening/EXECUTION_PLAN.md +57 -0
- package/specs/quiver-v26-0121-smoke-hardening/SPEC.md +137 -0
- package/specs/quiver-v26-0121-smoke-hardening/STATUS.md +32 -0
- package/specs/quiver-v26-0121-smoke-hardening/pr.md +96 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-00-docs-foundation/CLOSURE_BRIEF.md +35 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-00-docs-foundation/EXECUTION_BRIEF.md +55 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-00-docs-foundation/slice.json +73 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-01-cli-help-version-contract/CLOSURE_BRIEF.md +38 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-01-cli-help-version-contract/EXECUTION_BRIEF.md +51 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-01-cli-help-version-contract/slice.json +76 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-02-init-doc-links-and-flow-guidance/CLOSURE_BRIEF.md +37 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-02-init-doc-links-and-flow-guidance/EXECUTION_BRIEF.md +52 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-02-init-doc-links-and-flow-guidance/slice.json +75 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-03-ai-approval-review-consistency/CLOSURE_BRIEF.md +37 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-03-ai-approval-review-consistency/EXECUTION_BRIEF.md +53 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-03-ai-approval-review-consistency/slice.json +77 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-04-local-validation-brief-contracts/CLOSURE_BRIEF.md +35 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-04-local-validation-brief-contracts/EXECUTION_BRIEF.md +52 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-04-local-validation-brief-contracts/slice.json +77 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-05-demo-scaffold-readiness/CLOSURE_BRIEF.md +34 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-05-demo-scaffold-readiness/EXECUTION_BRIEF.md +54 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-05-demo-scaffold-readiness/slice.json +84 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-06-plan-graph-scope-performance/CLOSURE_BRIEF.md +35 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-06-plan-graph-scope-performance/EXECUTION_BRIEF.md +53 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-06-plan-graph-scope-performance/slice.json +82 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-07-smoke-release-readiness/CLOSURE_BRIEF.md +35 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-07-smoke-release-readiness/EXECUTION_BRIEF.md +55 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-07-smoke-release-readiness/slice.json +92 -0
- package/src/create-quiver/commands/ai.js +577 -27
- package/src/create-quiver/commands/flow.js +6 -5
- package/src/create-quiver/commands/graph.js +6 -4
- package/src/create-quiver/commands/plan.js +3 -3
- package/src/create-quiver/index.js +328 -12
- package/src/create-quiver/lib/actionable-error.js +27 -0
- package/src/create-quiver/lib/agent-profiles.js +1 -1
- package/src/create-quiver/lib/ai/context-packs.js +4 -0
- package/src/create-quiver/lib/ai/execution-plan.js +7 -1
- package/src/create-quiver/lib/ai/executor.js +270 -20
- package/src/create-quiver/lib/ai/export-state.js +534 -0
- package/src/create-quiver/lib/ai/github.js +83 -0
- package/src/create-quiver/lib/ai/onboarding-template.js +215 -2
- package/src/create-quiver/lib/ai/plan-review.js +5 -2
- package/src/create-quiver/lib/ai/providers.js +4 -3
- package/src/create-quiver/lib/ai/run-state.js +414 -0
- package/src/create-quiver/lib/ai/spec-generator.js +12 -0
- package/src/create-quiver/lib/ai/spec-templates.js +78 -9
- package/src/create-quiver/lib/approvals.js +22 -3
- package/src/create-quiver/lib/demo.js +189 -14
- package/src/create-quiver/lib/doctor.js +75 -0
- package/src/create-quiver/lib/handoff.js +81 -12
- package/src/create-quiver/lib/init-docs.js +24 -6
- package/src/create-quiver/lib/init-layout.js +8 -0
- package/src/create-quiver/lib/json.js +53 -3
- package/src/create-quiver/lib/readiness.js +18 -3
- package/src/create-quiver/lib/scope.js +50 -7
- package/src/create-quiver/lib/slice-graph.js +138 -38
- package/src/create-quiver/lib/slice.js +6 -1
- package/src/create-quiver/lib/spec-worktrees.js +16 -2
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
const fs = require('node:fs');
|
|
2
2
|
const path = require('node:path');
|
|
3
3
|
|
|
4
|
+
const { redactSecrets } = require('../lib/evidence');
|
|
5
|
+
const { formatActionableError } = require('../lib/actionable-error');
|
|
4
6
|
const { buildContextPackMetadata, normalizeRole } = require('../lib/ai/context-packs');
|
|
5
7
|
const { runExecuteSlice, runPromptSlice } = require('../lib/ai/executor');
|
|
6
8
|
const { runExecutePlan } = require('../lib/ai/execution-plan');
|
|
7
9
|
const { buildPrCreatePlan, formatPreflightReport, formatPrCreateReport, preflightGitHubPr, runGhPrCreate } = require('../lib/ai/github');
|
|
8
10
|
const { buildContextPreparationDrafts, buildPlannerOnboardingPrompt } = require('../lib/ai/onboarding-template');
|
|
11
|
+
const {
|
|
12
|
+
collectLifecycleExport,
|
|
13
|
+
formatLifecycleExportMarkdown,
|
|
14
|
+
formatLifecycleInspect,
|
|
15
|
+
formatSlicesList,
|
|
16
|
+
formatSpecsList,
|
|
17
|
+
formatTraceReport,
|
|
18
|
+
} = require('../lib/ai/export-state');
|
|
9
19
|
const {
|
|
10
20
|
PLAN_REVIEW_PROMPT_SOURCE,
|
|
11
21
|
buildPlanReviewPrompt,
|
|
22
|
+
readPlanReview,
|
|
12
23
|
resolveReviewedTechnicalPlanInput,
|
|
13
24
|
resolveTechnicalPlanReviewInput,
|
|
14
25
|
savePlanReview,
|
|
@@ -16,6 +27,15 @@ const {
|
|
|
16
27
|
} = require('../lib/ai/plan-review');
|
|
17
28
|
const { buildSpecGenerationManifest, describeSpecGeneration, generateSpecArtifacts } = require('../lib/ai/spec-generator');
|
|
18
29
|
const { buildProviderInvocation, runProvider } = require('../lib/ai/providers');
|
|
30
|
+
const {
|
|
31
|
+
createAiRun,
|
|
32
|
+
ensureAiRun,
|
|
33
|
+
formatAiRunResume,
|
|
34
|
+
formatAiRunStatus,
|
|
35
|
+
recordAiRunApproval,
|
|
36
|
+
resolveAiRun,
|
|
37
|
+
updateAiRunPhase,
|
|
38
|
+
} = require('../lib/ai/run-state');
|
|
19
39
|
const {
|
|
20
40
|
agentProfilesPath,
|
|
21
41
|
getAgentProfile,
|
|
@@ -26,6 +46,7 @@ const {
|
|
|
26
46
|
const {
|
|
27
47
|
PLANNER_APPROVAL_PHASES,
|
|
28
48
|
approvePlannerPhase,
|
|
49
|
+
readPhaseApproval,
|
|
29
50
|
resolveApprovedPlannerInput,
|
|
30
51
|
savePlannerDraft,
|
|
31
52
|
summarizePlannerApproval,
|
|
@@ -39,6 +60,8 @@ const DEFAULT_PLAN_PROVIDER = 'codex';
|
|
|
39
60
|
const DEFAULT_PLAN_ROLE = 'planner';
|
|
40
61
|
const DEFAULT_PLAN_CONTEXT = 'planning';
|
|
41
62
|
const DEFAULT_PLAN_PHASE = 'acceptance';
|
|
63
|
+
const CONTEXT_PREP_START = '<!-- quiver:context-prep:start -->';
|
|
64
|
+
const CONTEXT_PREP_END = '<!-- quiver:context-prep:end -->';
|
|
42
65
|
|
|
43
66
|
function formatError(message) {
|
|
44
67
|
return `create-quiver: ${message}`;
|
|
@@ -85,7 +108,7 @@ function resolveProviderForProfile(repoRoot, role, provider, providerExplicit, f
|
|
|
85
108
|
return resolveProfileProvider(repoRoot, role, fallbackProvider);
|
|
86
109
|
}
|
|
87
110
|
|
|
88
|
-
function buildPlanContext({ role, context, phase, inputText, inputPath, repoRoot }) {
|
|
111
|
+
function buildPlanContext({ role, context, phase, inputText, inputPath, repoRoot, revise = false }) {
|
|
89
112
|
const phaseDetails = getPlannerPhaseDetails(phase);
|
|
90
113
|
const pack = buildContextPackMetadata({
|
|
91
114
|
role,
|
|
@@ -96,7 +119,9 @@ function buildPlanContext({ role, context, phase, inputText, inputPath, repoRoot
|
|
|
96
119
|
const sections = [
|
|
97
120
|
pack.prompt,
|
|
98
121
|
`Phase: ${phaseDetails.phase}`,
|
|
99
|
-
|
|
122
|
+
revise
|
|
123
|
+
? 'Task: revise the current draft and produce a new version only. Do not advance phase, approve, create specs, or modify product code.'
|
|
124
|
+
: phaseDetails.phase === 'acceptance'
|
|
100
125
|
? 'Task: produce acceptance criteria only. Do not create files or modify product code.'
|
|
101
126
|
: 'Task: produce a technical plan only. Do not create files or modify product code.',
|
|
102
127
|
];
|
|
@@ -167,6 +192,52 @@ function formatDryRunReport({ task, provider, role, contextPack, phase, invocati
|
|
|
167
192
|
return `${lines.join('\n')}\n`;
|
|
168
193
|
}
|
|
169
194
|
|
|
195
|
+
function formatPromptOnlyReport({ task, provider, role, contextPack, phase, invocation, prompt, onboardingPlan, promptSource, inputPath, inputKind, inputVersion }) {
|
|
196
|
+
const lines = [
|
|
197
|
+
`AI ${task} prompt-only`,
|
|
198
|
+
`Provider: ${provider}`,
|
|
199
|
+
`Role: ${role}`,
|
|
200
|
+
`Context pack: ${contextPack}`,
|
|
201
|
+
];
|
|
202
|
+
|
|
203
|
+
if (phase) {
|
|
204
|
+
lines.push(`Phase: ${phase}`);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
lines.push(`Command: ${invocation.command} ${invocation.args.join(' ')}`);
|
|
208
|
+
lines.push(`Timeout: ${invocation.timeoutMs}ms`);
|
|
209
|
+
lines.push(`Prompt transport: ${invocation.promptTransport.mode}`);
|
|
210
|
+
lines.push(`Prompt length: ${invocation.promptLength} bytes`);
|
|
211
|
+
|
|
212
|
+
if (onboardingPlan) {
|
|
213
|
+
lines.push(`Prompt source: ${onboardingPlan.promptSource}`);
|
|
214
|
+
lines.push(`Selected docs: ${onboardingPlan.selectedDocs.length}`);
|
|
215
|
+
lines.push(`Documentation debt: ${onboardingPlan.missingDocs.length}`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (promptSource) {
|
|
219
|
+
lines.push(`Prompt source: ${promptSource}`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (inputPath) {
|
|
223
|
+
lines.push(`Input file: ${inputPath}`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (inputKind) {
|
|
227
|
+
lines.push(`Input kind: ${inputKind}`);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (inputVersion) {
|
|
231
|
+
lines.push(`Input version: v${inputVersion}`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
lines.push('--- PROMPT START ---');
|
|
235
|
+
lines.push(String(prompt || '').trimEnd());
|
|
236
|
+
lines.push('--- PROMPT END ---');
|
|
237
|
+
|
|
238
|
+
return `${lines.join('\n')}\n`;
|
|
239
|
+
}
|
|
240
|
+
|
|
170
241
|
function formatPathList(items, emptyLabel = 'none') {
|
|
171
242
|
if (!Array.isArray(items) || items.length === 0) {
|
|
172
243
|
return [`- ${emptyLabel}`];
|
|
@@ -175,28 +246,41 @@ function formatPathList(items, emptyLabel = 'none') {
|
|
|
175
246
|
return items.map((item) => `- ${item}`);
|
|
176
247
|
}
|
|
177
248
|
|
|
178
|
-
function formatContextPreparationReport({ dryRun, plan,
|
|
249
|
+
function formatContextPreparationReport({ dryRun, plan, writePlan, writtenDocs, snapshot, completed = false }) {
|
|
179
250
|
const lines = [
|
|
180
|
-
dryRun ? 'AI prepare-context dry-run' : 'AI prepare-context completed',
|
|
251
|
+
dryRun ? 'AI prepare-context dry-run' : completed ? 'AI prepare-context completed' : 'AI prepare-context write plan',
|
|
181
252
|
`Mode: ${dryRun ? 'dry-run' : 'live'}`,
|
|
182
253
|
`Project: ${plan.projectName}`,
|
|
183
254
|
`Project slug: ${plan.projectSlug}`,
|
|
184
255
|
'Writes: docs-only',
|
|
185
256
|
'Product code: untouched',
|
|
186
|
-
`Proposed docs: ${
|
|
257
|
+
`Proposed docs: ${writePlan.length > 0 ? writePlan.map((item) => item.path).join(', ') : 'none'}`,
|
|
187
258
|
];
|
|
188
259
|
|
|
189
260
|
if (!dryRun) {
|
|
190
|
-
lines.push(
|
|
261
|
+
lines.push(`${completed ? 'Written docs' : 'Planned writes'}: ${writtenDocs.length > 0 ? writtenDocs.join(', ') : 'none'}`);
|
|
262
|
+
if (snapshot) {
|
|
263
|
+
lines.push(`Snapshot: ${snapshot.root}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (completed) {
|
|
268
|
+
return `${lines.join('\n')}\n`;
|
|
191
269
|
}
|
|
192
270
|
|
|
193
271
|
lines.push(
|
|
272
|
+
'Proposed changes:',
|
|
273
|
+
...writePlan.map((item) => `- ${item.path}: ${item.action}${item.reason ? ` (${item.reason})` : ''}`),
|
|
274
|
+
'Diff preview:',
|
|
275
|
+
...formatDiffPreview(writePlan),
|
|
194
276
|
'Files considered:',
|
|
195
277
|
...plan.filesConsidered.map((item) => `- ${item.path}: ${item.present ? 'present' : 'absent'}${item.reason ? ` (${item.reason})` : ''}`),
|
|
196
278
|
'Assumptions:',
|
|
197
279
|
...formatPathList(plan.assumptions),
|
|
198
280
|
'Risks:',
|
|
199
281
|
...formatPathList(plan.risks),
|
|
282
|
+
'Contradictions:',
|
|
283
|
+
...formatPathList(plan.contradictions),
|
|
200
284
|
'Omitted paths:',
|
|
201
285
|
...formatPathList(plan.omittedPaths),
|
|
202
286
|
'Uncertainty markers: TODO | Assumption | Pending confirmation',
|
|
@@ -207,20 +291,192 @@ function formatContextPreparationReport({ dryRun, plan, docs, writtenDocs }) {
|
|
|
207
291
|
|
|
208
292
|
function writeProviderOutput(result) {
|
|
209
293
|
if (result.stdout) {
|
|
210
|
-
process.stdout.write(result.stdout);
|
|
294
|
+
process.stdout.write(redactSecrets(result.stdout));
|
|
211
295
|
}
|
|
212
296
|
if (result.stderr) {
|
|
213
|
-
process.stderr.write(result.stderr);
|
|
297
|
+
process.stderr.write(redactSecrets(result.stderr));
|
|
214
298
|
}
|
|
215
299
|
}
|
|
216
300
|
|
|
217
|
-
function
|
|
218
|
-
|
|
219
|
-
|
|
301
|
+
function getRedactedProviderText(result) {
|
|
302
|
+
return redactSecrets([result.stdout, result.stderr].filter(Boolean).join(''));
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function normalizeText(value) {
|
|
306
|
+
return String(value || '').replace(/\r\n/g, '\n');
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function buildRevisionInput({ phase, feedbackPath, feedbackText, repoRoot }) {
|
|
310
|
+
const current = readPhaseApproval(repoRoot, phase);
|
|
311
|
+
if (!current.draft) {
|
|
312
|
+
throw new Error(formatError(`ai revise --phase ${phase} requires an existing draft; current status is ${current.status}. Run \`npx create-quiver ai plan --phase ${phase} --input <file>\` first.`));
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const sections = [];
|
|
316
|
+
|
|
317
|
+
if (phase === 'technical-plan') {
|
|
318
|
+
const acceptance = resolveApprovedPlannerInput(repoRoot, phase, undefined);
|
|
319
|
+
const acceptanceText = readTextFile(acceptance.inputPath, repoRoot);
|
|
320
|
+
sections.push(`Approved acceptance input (${acceptance.inputPath}):`, acceptanceText.trimEnd());
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
sections.push(
|
|
324
|
+
`Current ${phase} draft (${current.draft.path}):`,
|
|
325
|
+
current.draft.contents.trimEnd(),
|
|
326
|
+
`Human feedback (${feedbackPath}):`,
|
|
327
|
+
feedbackText.trimEnd(),
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
return sections.join('\n\n');
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function buildManagedContextBlock(content) {
|
|
334
|
+
return `${CONTEXT_PREP_START}\n${String(content || '').trimEnd()}\n${CONTEXT_PREP_END}\n`;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function mergeContextDraft(existingContent, draftContent) {
|
|
338
|
+
const existing = normalizeText(existingContent);
|
|
339
|
+
const block = buildManagedContextBlock(draftContent);
|
|
340
|
+
const startIndex = existing.indexOf(CONTEXT_PREP_START);
|
|
341
|
+
const endIndex = existing.indexOf(CONTEXT_PREP_END);
|
|
342
|
+
|
|
343
|
+
if (startIndex >= 0 && endIndex > startIndex) {
|
|
344
|
+
const before = existing.slice(0, startIndex).trimEnd();
|
|
345
|
+
const after = existing.slice(endIndex + CONTEXT_PREP_END.length).trimStart();
|
|
346
|
+
return `${before}\n\n${block}${after ? `\n${after}` : ''}`;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return `${existing.trimEnd()}\n\n${block}`;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function firstChangedLineIndex(beforeLines, afterLines) {
|
|
353
|
+
const max = Math.max(beforeLines.length, afterLines.length);
|
|
354
|
+
for (let index = 0; index < max; index += 1) {
|
|
355
|
+
if (beforeLines[index] !== afterLines[index]) {
|
|
356
|
+
return index;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return -1;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function buildDiffSnippet(pathLabel, beforeContent, afterContent, maxLines = 10) {
|
|
363
|
+
const beforeLines = normalizeText(beforeContent).split('\n');
|
|
364
|
+
const afterLines = normalizeText(afterContent).split('\n');
|
|
365
|
+
const changedAt = firstChangedLineIndex(beforeLines, afterLines);
|
|
366
|
+
|
|
367
|
+
if (changedAt === -1) {
|
|
368
|
+
return [`diff -- ${pathLabel}`, ' no changes'];
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const start = Math.max(0, changedAt - 2);
|
|
372
|
+
const beforeSnippet = beforeLines.slice(start, start + maxLines);
|
|
373
|
+
const afterSnippet = afterLines.slice(start, start + maxLines);
|
|
374
|
+
const lines = [
|
|
375
|
+
`--- ${pathLabel} (current)`,
|
|
376
|
+
`+++ ${pathLabel} (proposed)`,
|
|
377
|
+
];
|
|
378
|
+
|
|
379
|
+
for (const line of beforeSnippet) {
|
|
380
|
+
if (line) {
|
|
381
|
+
lines.push(`- ${line}`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
for (const line of afterSnippet) {
|
|
386
|
+
if (line) {
|
|
387
|
+
lines.push(`+ ${line}`);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return lines;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function buildContextWritePlan(repoRoot, drafts) {
|
|
395
|
+
return drafts.map((draft) => {
|
|
220
396
|
const destinationPath = path.join(repoRoot, draft.path);
|
|
221
|
-
fs.
|
|
222
|
-
fs.
|
|
223
|
-
|
|
397
|
+
const exists = fs.existsSync(destinationPath);
|
|
398
|
+
const currentContent = exists ? fs.readFileSync(destinationPath, 'utf8') : '';
|
|
399
|
+
const proposedContent = exists
|
|
400
|
+
? mergeContextDraft(currentContent, draft.content)
|
|
401
|
+
: `${String(draft.content || '').replace(/\s+$/g, '')}\n`;
|
|
402
|
+
const changed = normalizeText(currentContent) !== normalizeText(proposedContent);
|
|
403
|
+
|
|
404
|
+
return {
|
|
405
|
+
path: draft.path,
|
|
406
|
+
destinationPath,
|
|
407
|
+
action: changed ? (exists ? 'update' : 'create') : 'skip',
|
|
408
|
+
reason: changed ? (exists ? 'human content preserved; Quiver block appended or refreshed' : 'missing approved context doc') : 'already up to date',
|
|
409
|
+
exists,
|
|
410
|
+
currentContent,
|
|
411
|
+
proposedContent,
|
|
412
|
+
diff: buildDiffSnippet(draft.path, currentContent, proposedContent),
|
|
413
|
+
};
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function formatDiffPreview(writePlan) {
|
|
418
|
+
const lines = [];
|
|
419
|
+
for (const item of writePlan) {
|
|
420
|
+
if (item.action === 'skip') {
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
lines.push(...item.diff);
|
|
424
|
+
}
|
|
425
|
+
return lines.length > 0 ? lines : ['- no changes'];
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function createContextSnapshots(repoRoot, run, writePlan, now = new Date()) {
|
|
429
|
+
const stamp = now.toISOString().replace(/[-:]/g, '').replace(/\.\d{3}Z$/, 'Z');
|
|
430
|
+
const snapshotRoot = path.join(repoRoot, '.quiver', 'runs', run.run_id, 'snapshots', stamp);
|
|
431
|
+
const manifest = {
|
|
432
|
+
schema_version: 1,
|
|
433
|
+
run_id: run.run_id,
|
|
434
|
+
created_at: now.toISOString(),
|
|
435
|
+
entries: [],
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
fs.mkdirSync(snapshotRoot, { recursive: true });
|
|
439
|
+
|
|
440
|
+
for (const item of writePlan) {
|
|
441
|
+
if (item.action === 'skip') {
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
const entry = {
|
|
445
|
+
path: item.path,
|
|
446
|
+
action: item.action,
|
|
447
|
+
existed: item.exists,
|
|
448
|
+
snapshot_path: null,
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
if (item.exists) {
|
|
452
|
+
const snapshotPath = path.join(snapshotRoot, item.path);
|
|
453
|
+
fs.mkdirSync(path.dirname(snapshotPath), { recursive: true });
|
|
454
|
+
fs.copyFileSync(item.destinationPath, snapshotPath);
|
|
455
|
+
entry.snapshot_path = path.relative(repoRoot, snapshotPath).split(path.sep).join('/');
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
manifest.entries.push(entry);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const manifestPath = path.join(snapshotRoot, 'manifest.json');
|
|
462
|
+
fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
463
|
+
|
|
464
|
+
return {
|
|
465
|
+
root: path.relative(repoRoot, snapshotRoot).split(path.sep).join('/'),
|
|
466
|
+
manifestPath: path.relative(repoRoot, manifestPath).split(path.sep).join('/'),
|
|
467
|
+
entries: manifest.entries,
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function writeDraftDocs(writePlan) {
|
|
472
|
+
const writtenDocs = [];
|
|
473
|
+
for (const item of writePlan) {
|
|
474
|
+
if (item.action === 'skip') {
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
fs.mkdirSync(path.dirname(item.destinationPath), { recursive: true });
|
|
478
|
+
fs.writeFileSync(item.destinationPath, item.proposedContent);
|
|
479
|
+
writtenDocs.push(item.path);
|
|
224
480
|
}
|
|
225
481
|
return writtenDocs;
|
|
226
482
|
}
|
|
@@ -350,6 +606,20 @@ async function runOnboard(repoRoot, options = {}) {
|
|
|
350
606
|
return report;
|
|
351
607
|
}
|
|
352
608
|
|
|
609
|
+
if (options.printPrompt) {
|
|
610
|
+
const report = {
|
|
611
|
+
task: 'onboard',
|
|
612
|
+
provider,
|
|
613
|
+
role,
|
|
614
|
+
contextPack: context,
|
|
615
|
+
invocation,
|
|
616
|
+
onboardingPlan: contextInfo.plan,
|
|
617
|
+
prompt,
|
|
618
|
+
};
|
|
619
|
+
process.stdout.write(formatPromptOnlyReport(report));
|
|
620
|
+
return report;
|
|
621
|
+
}
|
|
622
|
+
|
|
353
623
|
let result;
|
|
354
624
|
try {
|
|
355
625
|
result = await (options.runProviderFn || runProvider)(provider, {
|
|
@@ -386,33 +656,62 @@ async function runOnboard(repoRoot, options = {}) {
|
|
|
386
656
|
|
|
387
657
|
async function runPrepareContext(repoRoot, options = {}) {
|
|
388
658
|
const draftPack = buildContextPreparationDrafts(repoRoot);
|
|
659
|
+
const writePlan = buildContextWritePlan(repoRoot, draftPack.docs);
|
|
389
660
|
const report = {
|
|
390
661
|
task: 'prepare-context',
|
|
391
662
|
dryRun: options.dryRun === true,
|
|
392
663
|
docs: draftPack.docs.map((doc) => doc.path),
|
|
393
664
|
plan: draftPack.plan,
|
|
665
|
+
writePlan: writePlan.map((item) => ({
|
|
666
|
+
path: item.path,
|
|
667
|
+
action: item.action,
|
|
668
|
+
reason: item.reason,
|
|
669
|
+
})),
|
|
394
670
|
};
|
|
395
671
|
|
|
396
672
|
if (options.dryRun) {
|
|
397
673
|
process.stdout.write(formatContextPreparationReport({
|
|
398
674
|
dryRun: true,
|
|
399
|
-
docs: draftPack.docs,
|
|
400
675
|
plan: draftPack.plan,
|
|
676
|
+
writePlan,
|
|
401
677
|
writtenDocs: [],
|
|
402
678
|
}));
|
|
403
679
|
return report;
|
|
404
680
|
}
|
|
405
681
|
|
|
406
|
-
const
|
|
682
|
+
const lifecycleRun = ensureAiRun(repoRoot, {
|
|
683
|
+
command: 'ai prepare-context',
|
|
684
|
+
input: options.input || '',
|
|
685
|
+
runId: options.runId,
|
|
686
|
+
phase: 'created',
|
|
687
|
+
});
|
|
688
|
+
const snapshot = createContextSnapshots(repoRoot, lifecycleRun, writePlan, options.now || new Date());
|
|
689
|
+
const plannedDocs = writePlan.filter((item) => item.action !== 'skip').map((item) => item.path);
|
|
407
690
|
process.stdout.write(formatContextPreparationReport({
|
|
408
691
|
dryRun: false,
|
|
409
|
-
docs: draftPack.docs,
|
|
410
692
|
plan: draftPack.plan,
|
|
693
|
+
writePlan,
|
|
694
|
+
writtenDocs: plannedDocs,
|
|
695
|
+
snapshot,
|
|
696
|
+
}));
|
|
697
|
+
const writtenDocs = writeDraftDocs(writePlan);
|
|
698
|
+
updateAiRunPhase(repoRoot, lifecycleRun.run_id, 'onboarding-ready', {
|
|
699
|
+
artifact: snapshot.manifestPath,
|
|
700
|
+
command: 'ai prepare-context',
|
|
701
|
+
});
|
|
702
|
+
process.stdout.write(formatContextPreparationReport({
|
|
703
|
+
dryRun: false,
|
|
704
|
+
plan: draftPack.plan,
|
|
705
|
+
writePlan,
|
|
411
706
|
writtenDocs,
|
|
707
|
+
snapshot,
|
|
708
|
+
completed: true,
|
|
412
709
|
}));
|
|
413
710
|
|
|
414
711
|
return {
|
|
415
712
|
...report,
|
|
713
|
+
runId: lifecycleRun.run_id,
|
|
714
|
+
snapshot,
|
|
416
715
|
writtenDocs,
|
|
417
716
|
};
|
|
418
717
|
}
|
|
@@ -436,6 +735,17 @@ async function runPlan(repoRoot, options = {}) {
|
|
|
436
735
|
specSlug: options.specSlug,
|
|
437
736
|
});
|
|
438
737
|
|
|
738
|
+
if (options.printPrompt) {
|
|
739
|
+
const report = {
|
|
740
|
+
task: 'plan',
|
|
741
|
+
phase,
|
|
742
|
+
manifest,
|
|
743
|
+
};
|
|
744
|
+
process.stdout.write('AI plan prompt-only\nPhase: spec\nNo provider prompt is used for spec generation; showing the local generation plan instead.\n');
|
|
745
|
+
process.stdout.write(formatSpecDryRunReport({ manifest, repoRoot }));
|
|
746
|
+
return report;
|
|
747
|
+
}
|
|
748
|
+
|
|
439
749
|
if (options.dryRun) {
|
|
440
750
|
const report = {
|
|
441
751
|
task: 'plan',
|
|
@@ -464,7 +774,20 @@ async function runPlan(repoRoot, options = {}) {
|
|
|
464
774
|
|
|
465
775
|
assertPlannerPhaseReady(phase);
|
|
466
776
|
|
|
467
|
-
|
|
777
|
+
let inputText = '';
|
|
778
|
+
|
|
779
|
+
if (options.revise === true) {
|
|
780
|
+
if (!inputPath) {
|
|
781
|
+
throw new Error(formatError(`missing feedback input file for ai revise phase '${phase}'`));
|
|
782
|
+
}
|
|
783
|
+
const feedbackText = readTextFile(inputPath, repoRoot);
|
|
784
|
+
inputText = buildRevisionInput({
|
|
785
|
+
phase,
|
|
786
|
+
feedbackPath: inputPath,
|
|
787
|
+
feedbackText,
|
|
788
|
+
repoRoot,
|
|
789
|
+
});
|
|
790
|
+
} else if (phase === 'technical-plan') {
|
|
468
791
|
const resolved = resolveApprovedPlannerInput(repoRoot, phase, inputPath || undefined);
|
|
469
792
|
inputPath = resolved.inputPath;
|
|
470
793
|
}
|
|
@@ -473,7 +796,9 @@ async function runPlan(repoRoot, options = {}) {
|
|
|
473
796
|
throw new Error(formatError(`missing input file for ai plan phase '${phase}'`));
|
|
474
797
|
}
|
|
475
798
|
|
|
476
|
-
|
|
799
|
+
if (!inputText) {
|
|
800
|
+
inputText = readTextFile(inputPath, repoRoot);
|
|
801
|
+
}
|
|
477
802
|
const contextInfo = buildPlanContext({
|
|
478
803
|
role,
|
|
479
804
|
context,
|
|
@@ -481,6 +806,7 @@ async function runPlan(repoRoot, options = {}) {
|
|
|
481
806
|
inputText,
|
|
482
807
|
inputPath,
|
|
483
808
|
repoRoot,
|
|
809
|
+
revise: options.revise === true,
|
|
484
810
|
});
|
|
485
811
|
const prompt = contextInfo.prompt;
|
|
486
812
|
let invocation;
|
|
@@ -508,6 +834,20 @@ async function runPlan(repoRoot, options = {}) {
|
|
|
508
834
|
return report;
|
|
509
835
|
}
|
|
510
836
|
|
|
837
|
+
if (options.printPrompt) {
|
|
838
|
+
const report = {
|
|
839
|
+
task: 'plan',
|
|
840
|
+
provider,
|
|
841
|
+
role,
|
|
842
|
+
contextPack: contextInfo.pack.packName,
|
|
843
|
+
phase,
|
|
844
|
+
invocation,
|
|
845
|
+
prompt,
|
|
846
|
+
};
|
|
847
|
+
process.stdout.write(formatPromptOnlyReport(report));
|
|
848
|
+
return report;
|
|
849
|
+
}
|
|
850
|
+
|
|
511
851
|
let result;
|
|
512
852
|
try {
|
|
513
853
|
result = await (options.runProviderFn || runProvider)(provider, {
|
|
@@ -531,7 +871,16 @@ async function runPlan(repoRoot, options = {}) {
|
|
|
531
871
|
throw annotateProviderError(result.error || new Error('provider run failed'), 'plan', phase);
|
|
532
872
|
}
|
|
533
873
|
|
|
534
|
-
savePlannerDraft(repoRoot, phase, inputPath,
|
|
874
|
+
const draft = savePlannerDraft(repoRoot, phase, inputPath, getRedactedProviderText(result));
|
|
875
|
+
const lifecycleRun = ensureAiRun(repoRoot, {
|
|
876
|
+
command: `ai plan --phase ${phase}`,
|
|
877
|
+
input: inputPath,
|
|
878
|
+
runId: options.runId,
|
|
879
|
+
});
|
|
880
|
+
updateAiRunPhase(repoRoot, lifecycleRun.run_id, phase === 'acceptance' ? 'acceptance-draft' : 'technical-plan-draft', {
|
|
881
|
+
artifact: path.relative(repoRoot, draft.filePath).split(path.sep).join('/'),
|
|
882
|
+
command: `ai plan --phase ${phase}`,
|
|
883
|
+
});
|
|
535
884
|
|
|
536
885
|
return {
|
|
537
886
|
task: 'plan',
|
|
@@ -603,6 +952,24 @@ async function runReviewPlan(repoRoot, options = {}) {
|
|
|
603
952
|
return report;
|
|
604
953
|
}
|
|
605
954
|
|
|
955
|
+
if (options.printPrompt) {
|
|
956
|
+
const report = {
|
|
957
|
+
task: 'review-plan',
|
|
958
|
+
provider,
|
|
959
|
+
role: 'reviewer',
|
|
960
|
+
contextPack: pack.packName,
|
|
961
|
+
phase: 'plan-review',
|
|
962
|
+
invocation,
|
|
963
|
+
prompt: built.prompt,
|
|
964
|
+
promptSource: built.promptSource,
|
|
965
|
+
inputPath,
|
|
966
|
+
inputKind: resolved.kind,
|
|
967
|
+
inputVersion: resolved.version,
|
|
968
|
+
};
|
|
969
|
+
process.stdout.write(formatPromptOnlyReport(report));
|
|
970
|
+
return report;
|
|
971
|
+
}
|
|
972
|
+
|
|
606
973
|
let result;
|
|
607
974
|
try {
|
|
608
975
|
result = await (options.runProviderFn || runProvider)(provider, {
|
|
@@ -627,7 +994,7 @@ async function runReviewPlan(repoRoot, options = {}) {
|
|
|
627
994
|
}
|
|
628
995
|
|
|
629
996
|
const saved = savePlanReview(repoRoot, {
|
|
630
|
-
contents:
|
|
997
|
+
contents: getRedactedProviderText(result),
|
|
631
998
|
inputPath,
|
|
632
999
|
inputKind: resolved.kind,
|
|
633
1000
|
inputVersion: resolved.version,
|
|
@@ -649,17 +1016,46 @@ async function runReviewPlan(repoRoot, options = {}) {
|
|
|
649
1016
|
};
|
|
650
1017
|
}
|
|
651
1018
|
|
|
1019
|
+
async function runRevise(repoRoot, options = {}) {
|
|
1020
|
+
const phase = normalizePlannerPhase(options.phase || DEFAULT_PLAN_PHASE);
|
|
1021
|
+
if (phase === 'spec') {
|
|
1022
|
+
throw new Error(formatError(`ai revise does not support phase '${phase}'`));
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
const approval = readPhaseApproval(repoRoot, phase);
|
|
1026
|
+
if (approval.status !== 'draft' && approval.status !== 'stale') {
|
|
1027
|
+
throw new Error(formatError(`ai revise --phase ${phase} requires an existing draft; current status is ${approval.status}. Run \`npx create-quiver ai plan --phase ${phase} --input <file>\` first.`));
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
return runPlan(repoRoot, {
|
|
1031
|
+
...options,
|
|
1032
|
+
phase,
|
|
1033
|
+
revise: true,
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
|
|
652
1037
|
async function runApprove(repoRoot, options = {}) {
|
|
653
1038
|
const phase = normalizePlannerPhase(options.phase || DEFAULT_PLAN_PHASE);
|
|
654
1039
|
if (phase === 'spec') {
|
|
655
1040
|
throw new Error(formatError(`ai approve does not support phase '${phase}'`));
|
|
656
1041
|
}
|
|
657
1042
|
|
|
658
|
-
if (!options.
|
|
659
|
-
throw new Error(formatError(`
|
|
1043
|
+
if (!options.version) {
|
|
1044
|
+
throw new Error(formatError(`ai approve --phase ${phase} requires --version <n>. Review drafts with \`npx create-quiver ai approvals\`.`));
|
|
660
1045
|
}
|
|
661
1046
|
|
|
662
|
-
|
|
1047
|
+
if (options.input) {
|
|
1048
|
+
throw new Error(formatError(`ai approve --phase ${phase} approves saved draft versions only. Use \`npx create-quiver ai revise --phase ${phase} --input ${options.input}\` to create a new draft first.`));
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
if (phase === 'technical-plan') {
|
|
1052
|
+
const review = readPlanReview(repoRoot);
|
|
1053
|
+
if (review.status !== 'unapproved' && review.status !== 'reviewed') {
|
|
1054
|
+
throw new Error(formatError(`ai approve --phase technical-plan requires a production review for the current draft; current review status is ${review.status}. Run \`npx create-quiver ai review-plan\`.`));
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
const inputText = '';
|
|
663
1059
|
|
|
664
1060
|
if (options.dryRun) {
|
|
665
1061
|
process.stdout.write(formatApprovalDryRunResult({ phase, input: options.input, version: options.version }));
|
|
@@ -675,6 +1071,21 @@ async function runApprove(repoRoot, options = {}) {
|
|
|
675
1071
|
const result = approvePlannerPhase(repoRoot, phase, options.input || '', inputText, {
|
|
676
1072
|
version: options.version || undefined,
|
|
677
1073
|
});
|
|
1074
|
+
const lifecycleRun = ensureAiRun(repoRoot, {
|
|
1075
|
+
command: `ai approve --phase ${phase}`,
|
|
1076
|
+
input: options.input || result.filePath,
|
|
1077
|
+
runId: options.runId,
|
|
1078
|
+
});
|
|
1079
|
+
recordAiRunApproval(repoRoot, lifecycleRun.run_id, {
|
|
1080
|
+
artifact: path.relative(repoRoot, result.filePath).split(path.sep).join('/'),
|
|
1081
|
+
phase,
|
|
1082
|
+
source_file: options.input || `draft version ${options.version}`,
|
|
1083
|
+
version: result.version || null,
|
|
1084
|
+
});
|
|
1085
|
+
updateAiRunPhase(repoRoot, lifecycleRun.run_id, phase === 'acceptance' ? 'acceptance-approved' : 'technical-plan-approved', {
|
|
1086
|
+
artifact: path.relative(repoRoot, result.filePath).split(path.sep).join('/'),
|
|
1087
|
+
command: `ai approve --phase ${phase}`,
|
|
1088
|
+
});
|
|
678
1089
|
process.stdout.write(formatApprovalResult({
|
|
679
1090
|
...result,
|
|
680
1091
|
sourceFile: options.input || `draft version ${options.version}`,
|
|
@@ -699,6 +1110,131 @@ async function runApprovalStatus(repoRoot) {
|
|
|
699
1110
|
};
|
|
700
1111
|
}
|
|
701
1112
|
|
|
1113
|
+
function runLifecycleStatus(repoRoot, options = {}) {
|
|
1114
|
+
const run = resolveAiRun(repoRoot, options.runId || '');
|
|
1115
|
+
const report = formatAiRunStatus(repoRoot, run);
|
|
1116
|
+
process.stdout.write(report);
|
|
1117
|
+
return {
|
|
1118
|
+
task: 'status',
|
|
1119
|
+
run,
|
|
1120
|
+
report,
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
function runLifecycleResume(repoRoot, options = {}) {
|
|
1125
|
+
const run = resolveAiRun(repoRoot, options.runId || '');
|
|
1126
|
+
const report = formatAiRunResume(repoRoot, run);
|
|
1127
|
+
process.stdout.write(report);
|
|
1128
|
+
return {
|
|
1129
|
+
task: 'resume',
|
|
1130
|
+
run,
|
|
1131
|
+
report,
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
function runInspect(repoRoot, options = {}) {
|
|
1136
|
+
const report = collectLifecycleExport(repoRoot, {
|
|
1137
|
+
includeCompleted: options.includeCompleted === true,
|
|
1138
|
+
});
|
|
1139
|
+
process.stdout.write(formatLifecycleInspect(report));
|
|
1140
|
+
return {
|
|
1141
|
+
task: 'inspect',
|
|
1142
|
+
report,
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
function runExport(repoRoot, options = {}) {
|
|
1147
|
+
const report = collectLifecycleExport(repoRoot, {
|
|
1148
|
+
includeCompleted: options.includeCompleted === true,
|
|
1149
|
+
});
|
|
1150
|
+
const format = String(options.format || 'json').trim().toLowerCase();
|
|
1151
|
+
|
|
1152
|
+
if (format === 'json') {
|
|
1153
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
1154
|
+
return {
|
|
1155
|
+
task: 'export',
|
|
1156
|
+
format,
|
|
1157
|
+
report,
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
if (format === 'markdown' || format === 'md') {
|
|
1162
|
+
process.stdout.write(formatLifecycleExportMarkdown(report));
|
|
1163
|
+
return {
|
|
1164
|
+
task: 'export',
|
|
1165
|
+
format: 'markdown',
|
|
1166
|
+
report,
|
|
1167
|
+
};
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
throw new Error(formatError(`unsupported ai export format: ${format}. Supported formats: json, markdown`));
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
function runSpecsList(repoRoot, options = {}) {
|
|
1174
|
+
const report = collectLifecycleExport(repoRoot, {
|
|
1175
|
+
includeCompleted: options.includeCompleted === true,
|
|
1176
|
+
});
|
|
1177
|
+
if (options.json === true) {
|
|
1178
|
+
process.stdout.write(`${JSON.stringify({ specs: report.specs }, null, 2)}\n`);
|
|
1179
|
+
} else {
|
|
1180
|
+
process.stdout.write(formatSpecsList(report));
|
|
1181
|
+
}
|
|
1182
|
+
return {
|
|
1183
|
+
task: 'specs',
|
|
1184
|
+
specs: report.specs,
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
function runSlicesList(repoRoot, options = {}) {
|
|
1189
|
+
const report = collectLifecycleExport(repoRoot, {
|
|
1190
|
+
includeCompleted: options.includeCompleted === true,
|
|
1191
|
+
});
|
|
1192
|
+
if (options.json === true) {
|
|
1193
|
+
process.stdout.write(`${JSON.stringify({ slices: report.slices }, null, 2)}\n`);
|
|
1194
|
+
} else {
|
|
1195
|
+
process.stdout.write(formatSlicesList(report));
|
|
1196
|
+
}
|
|
1197
|
+
return {
|
|
1198
|
+
task: 'slices',
|
|
1199
|
+
slices: report.slices,
|
|
1200
|
+
};
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
function runTraceReport(repoRoot, options = {}) {
|
|
1204
|
+
const report = collectLifecycleExport(repoRoot, {
|
|
1205
|
+
includeCompleted: options.includeCompleted === true,
|
|
1206
|
+
});
|
|
1207
|
+
process.stdout.write(formatTraceReport(report));
|
|
1208
|
+
return {
|
|
1209
|
+
task: 'trace',
|
|
1210
|
+
report,
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
function runLifecycleRun(repoRoot, options = {}) {
|
|
1215
|
+
const command = String(options.command || '').trim().toLowerCase();
|
|
1216
|
+
if (command !== 'create') {
|
|
1217
|
+
throw new Error(formatError(`unsupported ai run subcommand: ${command}. Supported tasks: create`));
|
|
1218
|
+
}
|
|
1219
|
+
if (!options.input) {
|
|
1220
|
+
throw new Error(formatError('ai run create requires --input <requirements.md>'));
|
|
1221
|
+
}
|
|
1222
|
+
const run = createAiRun(repoRoot, {
|
|
1223
|
+
command: 'ai run create',
|
|
1224
|
+
input: options.input,
|
|
1225
|
+
runId: options.runId,
|
|
1226
|
+
specSlug: options.specSlug,
|
|
1227
|
+
});
|
|
1228
|
+
const report = formatAiRunStatus(repoRoot, run);
|
|
1229
|
+
process.stdout.write(report);
|
|
1230
|
+
return {
|
|
1231
|
+
task: 'run',
|
|
1232
|
+
command,
|
|
1233
|
+
run,
|
|
1234
|
+
report,
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
1237
|
+
|
|
702
1238
|
function formatAgentProfile(profile) {
|
|
703
1239
|
const lines = [
|
|
704
1240
|
`Role: ${profile.role}`,
|
|
@@ -730,7 +1266,7 @@ function runAgent(repoRoot, options = {}) {
|
|
|
730
1266
|
|
|
731
1267
|
if (command === 'set') {
|
|
732
1268
|
if (!options.role) {
|
|
733
|
-
throw new Error(formatError('missing agent role. Use: npx create-quiver ai agent set <planner|executor|reviewer|
|
|
1269
|
+
throw new Error(formatError('missing agent role. Use: npx create-quiver ai agent set <planner|executor|reviewer|doctor> --provider <provider>'));
|
|
734
1270
|
}
|
|
735
1271
|
if (!options.provider) {
|
|
736
1272
|
throw new Error(formatError('ai agent set requires --provider. Supported providers: codex, claude, gemini.'));
|
|
@@ -754,11 +1290,16 @@ function runAgent(repoRoot, options = {}) {
|
|
|
754
1290
|
|
|
755
1291
|
if (command === 'show') {
|
|
756
1292
|
if (!options.role) {
|
|
757
|
-
throw new Error(formatError('missing agent role. Use: npx create-quiver ai agent show <planner|executor|reviewer|
|
|
1293
|
+
throw new Error(formatError('missing agent role. Use: npx create-quiver ai agent show <planner|executor|reviewer|doctor>'));
|
|
758
1294
|
}
|
|
759
1295
|
const profile = getAgentProfile(repoRoot, options.role);
|
|
760
1296
|
if (!profile) {
|
|
761
|
-
throw new Error(
|
|
1297
|
+
throw new Error(formatActionableError({
|
|
1298
|
+
failure: `agent profile '${options.role}' is not configured.`,
|
|
1299
|
+
impact: 'Quiver will fall back to default provider behavior and may use the wrong model/cost profile.',
|
|
1300
|
+
fix: `Configure the ${options.role} profile with a supported provider and optional model label.`,
|
|
1301
|
+
nextCommand: `npx create-quiver ai agent set ${options.role} --provider <provider> --model <label>`,
|
|
1302
|
+
}));
|
|
762
1303
|
}
|
|
763
1304
|
process.stdout.write(formatAgentProfile(profile));
|
|
764
1305
|
return {
|
|
@@ -903,12 +1444,21 @@ module.exports = {
|
|
|
903
1444
|
runDoctor,
|
|
904
1445
|
runExecutePlan,
|
|
905
1446
|
runExecuteSlice,
|
|
1447
|
+
runLifecycleResume,
|
|
1448
|
+
runLifecycleRun,
|
|
1449
|
+
runLifecycleStatus,
|
|
1450
|
+
runExport,
|
|
1451
|
+
runInspect,
|
|
906
1452
|
runPromptSlice,
|
|
907
1453
|
runApprove,
|
|
908
1454
|
runApprovalStatus,
|
|
909
1455
|
runPrepareContext,
|
|
910
1456
|
runReviewPlan,
|
|
1457
|
+
runRevise,
|
|
911
1458
|
runPr,
|
|
1459
|
+
runSlicesList,
|
|
1460
|
+
runSpecsList,
|
|
1461
|
+
runTraceReport,
|
|
912
1462
|
runOnboard,
|
|
913
1463
|
runPlan,
|
|
914
1464
|
writeProviderOutput,
|