create-quiver 0.12.0 → 0.13.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 +52 -0
- package/README.md +65 -25
- package/README_FOR_AI.md +36 -29
- package/ROADMAP.md +22 -3
- package/docs/AI_ONBOARDING_PROMPT.md.template +7 -1
- package/docs/COMMANDS.md.template +53 -20
- package/docs/STATUS.md.template +5 -1
- package/docs/WORKFLOW.md.template +13 -11
- package/package.json +10 -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/specs/quiver-v27-reliability-ai-workflow-hardening/AUDIT_V24_V25_V26.md +67 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/COMMAND_CONTRACTS.md +125 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/COVERAGE_MATRIX.md +74 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/EVIDENCE_REPORT.md +179 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/EXECUTION_PLAN.md +71 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/SPEC.md +176 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/STATUS.md +37 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/pr.md +132 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-00-docs-audit-coverage-and-contracts/CLOSURE_BRIEF.md +36 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-00-docs-audit-coverage-and-contracts/EXECUTION_BRIEF.md +56 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-00-docs-audit-coverage-and-contracts/slice.json +75 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-01-core-state-resolver-and-canonical-statuses/CLOSURE_BRIEF.md +37 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-01-core-state-resolver-and-canonical-statuses/EXECUTION_BRIEF.md +54 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-01-core-state-resolver-and-canonical-statuses/slice.json +79 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-02-json-export-contract-and-machine-output/CLOSURE_BRIEF.md +34 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-02-json-export-contract-and-machine-output/EXECUTION_BRIEF.md +54 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-02-json-export-contract-and-machine-output/slice.json +75 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-03-approved-plan-to-spec-create/CLOSURE_BRIEF.md +36 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-03-approved-plan-to-spec-create/EXECUTION_BRIEF.md +55 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-03-approved-plan-to-spec-create/slice.json +78 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-04-ai-artifact-storage-redaction-and-token-compaction/CLOSURE_BRIEF.md +31 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-04-ai-artifact-storage-redaction-and-token-compaction/EXECUTION_BRIEF.md +55 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-04-ai-artifact-storage-redaction-and-token-compaction/slice.json +77 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-05-worktree-lifecycle-locks-and-recovery/CLOSURE_BRIEF.md +31 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-05-worktree-lifecycle-locks-and-recovery/EXECUTION_BRIEF.md +55 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-05-worktree-lifecycle-locks-and-recovery/slice.json +84 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-06-validation-gates-and-scope-safety/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-06-validation-gates-and-scope-safety/EXECUTION_BRIEF.md +57 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-06-validation-gates-and-scope-safety/slice.json +99 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-07-context-analysis-and-doctor-flow/CLOSURE_BRIEF.md +31 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-07-context-analysis-and-doctor-flow/EXECUTION_BRIEF.md +57 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-07-context-analysis-and-doctor-flow/slice.json +88 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-08-cross-platform-help-auth-and-dx/CLOSURE_BRIEF.md +31 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-08-cross-platform-help-auth-and-dx/EXECUTION_BRIEF.md +56 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-08-cross-platform-help-auth-and-dx/slice.json +85 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-09-fixtures-smoke-docs-and-release-readiness/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-09-fixtures-smoke-docs-and-release-readiness/EXECUTION_BRIEF.md +56 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-09-fixtures-smoke-docs-and-release-readiness/slice.json +91 -0
- package/src/create-quiver/commands/ai.js +652 -27
- package/src/create-quiver/commands/flow.js +58 -9
- package/src/create-quiver/commands/graph.js +11 -9
- package/src/create-quiver/commands/plan.js +7 -16
- package/src/create-quiver/commands/spec.js +282 -0
- package/src/create-quiver/index.js +409 -31
- package/src/create-quiver/lib/actionable-error.js +27 -0
- package/src/create-quiver/lib/agent-profiles.js +16 -4
- package/src/create-quiver/lib/ai/artifacts.js +318 -0
- package/src/create-quiver/lib/ai/context-packs.js +4 -0
- package/src/create-quiver/lib/ai/execution-plan.js +16 -1
- package/src/create-quiver/lib/ai/executor.js +272 -21
- package/src/create-quiver/lib/ai/export-state.js +679 -0
- package/src/create-quiver/lib/ai/github.js +162 -2
- package/src/create-quiver/lib/ai/onboarding-template.js +215 -2
- package/src/create-quiver/lib/ai/plan-review.js +7 -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 +84 -13
- package/src/create-quiver/lib/ai/spec-templates.js +150 -21
- package/src/create-quiver/lib/analyze.js +2 -2
- package/src/create-quiver/lib/approvals.js +36 -5
- package/src/create-quiver/lib/demo.js +189 -14
- package/src/create-quiver/lib/doctor.js +154 -0
- package/src/create-quiver/lib/git.js +40 -1
- package/src/create-quiver/lib/handoff.js +123 -12
- package/src/create-quiver/lib/init-docs.js +35 -13
- package/src/create-quiver/lib/init-layout.js +9 -0
- package/src/create-quiver/lib/json.js +53 -3
- package/src/create-quiver/lib/lifecycle.js +52 -3
- package/src/create-quiver/lib/locks.js +134 -0
- package/src/create-quiver/lib/package-safety.js +7 -0
- package/src/create-quiver/lib/paths.js +74 -0
- package/src/create-quiver/lib/project-scan.js +74 -0
- package/src/create-quiver/lib/project-state-resolver.js +236 -0
- package/src/create-quiver/lib/readiness.js +66 -10
- package/src/create-quiver/lib/scope.js +52 -8
- package/src/create-quiver/lib/slice-graph.js +138 -38
- package/src/create-quiver/lib/slice.js +14 -5
- package/src/create-quiver/lib/spec-worktrees.js +129 -32
- package/src/create-quiver/lib/statuses.js +115 -0
|
@@ -1,14 +1,31 @@
|
|
|
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');
|
|
6
|
+
const {
|
|
7
|
+
assertProviderPromptWithinLimit,
|
|
8
|
+
compactRevisionInput,
|
|
9
|
+
extractCleanProviderOutput,
|
|
10
|
+
writeRawProviderArtifact,
|
|
11
|
+
} = require('../lib/ai/artifacts');
|
|
4
12
|
const { buildContextPackMetadata, normalizeRole } = require('../lib/ai/context-packs');
|
|
5
13
|
const { runExecuteSlice, runPromptSlice } = require('../lib/ai/executor');
|
|
6
14
|
const { runExecutePlan } = require('../lib/ai/execution-plan');
|
|
7
15
|
const { buildPrCreatePlan, formatPreflightReport, formatPrCreateReport, preflightGitHubPr, runGhPrCreate } = require('../lib/ai/github');
|
|
8
16
|
const { buildContextPreparationDrafts, buildPlannerOnboardingPrompt } = require('../lib/ai/onboarding-template');
|
|
17
|
+
const {
|
|
18
|
+
collectLifecycleExport,
|
|
19
|
+
formatLifecycleExportMarkdown,
|
|
20
|
+
formatLifecycleInspect,
|
|
21
|
+
formatSlicesList,
|
|
22
|
+
formatSpecsList,
|
|
23
|
+
formatTraceReport,
|
|
24
|
+
} = require('../lib/ai/export-state');
|
|
9
25
|
const {
|
|
10
26
|
PLAN_REVIEW_PROMPT_SOURCE,
|
|
11
27
|
buildPlanReviewPrompt,
|
|
28
|
+
readPlanReview,
|
|
12
29
|
resolveReviewedTechnicalPlanInput,
|
|
13
30
|
resolveTechnicalPlanReviewInput,
|
|
14
31
|
savePlanReview,
|
|
@@ -16,8 +33,18 @@ const {
|
|
|
16
33
|
} = require('../lib/ai/plan-review');
|
|
17
34
|
const { buildSpecGenerationManifest, describeSpecGeneration, generateSpecArtifacts } = require('../lib/ai/spec-generator');
|
|
18
35
|
const { buildProviderInvocation, runProvider } = require('../lib/ai/providers');
|
|
36
|
+
const {
|
|
37
|
+
createAiRun,
|
|
38
|
+
ensureAiRun,
|
|
39
|
+
formatAiRunResume,
|
|
40
|
+
formatAiRunStatus,
|
|
41
|
+
recordAiRunApproval,
|
|
42
|
+
resolveAiRun,
|
|
43
|
+
updateAiRunPhase,
|
|
44
|
+
} = require('../lib/ai/run-state');
|
|
19
45
|
const {
|
|
20
46
|
agentProfilesPath,
|
|
47
|
+
buildAgentProfileState,
|
|
21
48
|
getAgentProfile,
|
|
22
49
|
listAgentProfiles,
|
|
23
50
|
resolveProfileProvider,
|
|
@@ -26,6 +53,7 @@ const {
|
|
|
26
53
|
const {
|
|
27
54
|
PLANNER_APPROVAL_PHASES,
|
|
28
55
|
approvePlannerPhase,
|
|
56
|
+
readPhaseApproval,
|
|
29
57
|
resolveApprovedPlannerInput,
|
|
30
58
|
savePlannerDraft,
|
|
31
59
|
summarizePlannerApproval,
|
|
@@ -39,6 +67,8 @@ const DEFAULT_PLAN_PROVIDER = 'codex';
|
|
|
39
67
|
const DEFAULT_PLAN_ROLE = 'planner';
|
|
40
68
|
const DEFAULT_PLAN_CONTEXT = 'planning';
|
|
41
69
|
const DEFAULT_PLAN_PHASE = 'acceptance';
|
|
70
|
+
const CONTEXT_PREP_START = '<!-- quiver:context-prep:start -->';
|
|
71
|
+
const CONTEXT_PREP_END = '<!-- quiver:context-prep:end -->';
|
|
42
72
|
|
|
43
73
|
function formatError(message) {
|
|
44
74
|
return `create-quiver: ${message}`;
|
|
@@ -85,7 +115,7 @@ function resolveProviderForProfile(repoRoot, role, provider, providerExplicit, f
|
|
|
85
115
|
return resolveProfileProvider(repoRoot, role, fallbackProvider);
|
|
86
116
|
}
|
|
87
117
|
|
|
88
|
-
function buildPlanContext({ role, context, phase, inputText, inputPath, repoRoot }) {
|
|
118
|
+
function buildPlanContext({ role, context, phase, inputText, inputPath, repoRoot, revise = false }) {
|
|
89
119
|
const phaseDetails = getPlannerPhaseDetails(phase);
|
|
90
120
|
const pack = buildContextPackMetadata({
|
|
91
121
|
role,
|
|
@@ -96,7 +126,9 @@ function buildPlanContext({ role, context, phase, inputText, inputPath, repoRoot
|
|
|
96
126
|
const sections = [
|
|
97
127
|
pack.prompt,
|
|
98
128
|
`Phase: ${phaseDetails.phase}`,
|
|
99
|
-
|
|
129
|
+
revise
|
|
130
|
+
? 'Task: revise the current draft and produce a new version only. Do not advance phase, approve, create specs, or modify product code.'
|
|
131
|
+
: phaseDetails.phase === 'acceptance'
|
|
100
132
|
? 'Task: produce acceptance criteria only. Do not create files or modify product code.'
|
|
101
133
|
: 'Task: produce a technical plan only. Do not create files or modify product code.',
|
|
102
134
|
];
|
|
@@ -167,6 +199,52 @@ function formatDryRunReport({ task, provider, role, contextPack, phase, invocati
|
|
|
167
199
|
return `${lines.join('\n')}\n`;
|
|
168
200
|
}
|
|
169
201
|
|
|
202
|
+
function formatPromptOnlyReport({ task, provider, role, contextPack, phase, invocation, prompt, onboardingPlan, promptSource, inputPath, inputKind, inputVersion }) {
|
|
203
|
+
const lines = [
|
|
204
|
+
`AI ${task} prompt-only`,
|
|
205
|
+
`Provider: ${provider}`,
|
|
206
|
+
`Role: ${role}`,
|
|
207
|
+
`Context pack: ${contextPack}`,
|
|
208
|
+
];
|
|
209
|
+
|
|
210
|
+
if (phase) {
|
|
211
|
+
lines.push(`Phase: ${phase}`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
lines.push(`Command: ${invocation.command} ${invocation.args.join(' ')}`);
|
|
215
|
+
lines.push(`Timeout: ${invocation.timeoutMs}ms`);
|
|
216
|
+
lines.push(`Prompt transport: ${invocation.promptTransport.mode}`);
|
|
217
|
+
lines.push(`Prompt length: ${invocation.promptLength} bytes`);
|
|
218
|
+
|
|
219
|
+
if (onboardingPlan) {
|
|
220
|
+
lines.push(`Prompt source: ${onboardingPlan.promptSource}`);
|
|
221
|
+
lines.push(`Selected docs: ${onboardingPlan.selectedDocs.length}`);
|
|
222
|
+
lines.push(`Documentation debt: ${onboardingPlan.missingDocs.length}`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (promptSource) {
|
|
226
|
+
lines.push(`Prompt source: ${promptSource}`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (inputPath) {
|
|
230
|
+
lines.push(`Input file: ${inputPath}`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (inputKind) {
|
|
234
|
+
lines.push(`Input kind: ${inputKind}`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (inputVersion) {
|
|
238
|
+
lines.push(`Input version: v${inputVersion}`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
lines.push('--- PROMPT START ---');
|
|
242
|
+
lines.push(String(prompt || '').trimEnd());
|
|
243
|
+
lines.push('--- PROMPT END ---');
|
|
244
|
+
|
|
245
|
+
return `${lines.join('\n')}\n`;
|
|
246
|
+
}
|
|
247
|
+
|
|
170
248
|
function formatPathList(items, emptyLabel = 'none') {
|
|
171
249
|
if (!Array.isArray(items) || items.length === 0) {
|
|
172
250
|
return [`- ${emptyLabel}`];
|
|
@@ -175,28 +253,41 @@ function formatPathList(items, emptyLabel = 'none') {
|
|
|
175
253
|
return items.map((item) => `- ${item}`);
|
|
176
254
|
}
|
|
177
255
|
|
|
178
|
-
function formatContextPreparationReport({ dryRun, plan,
|
|
256
|
+
function formatContextPreparationReport({ dryRun, plan, writePlan, writtenDocs, snapshot, completed = false }) {
|
|
179
257
|
const lines = [
|
|
180
|
-
dryRun ? 'AI prepare-context dry-run' : 'AI prepare-context completed',
|
|
258
|
+
dryRun ? 'AI prepare-context dry-run' : completed ? 'AI prepare-context completed' : 'AI prepare-context write plan',
|
|
181
259
|
`Mode: ${dryRun ? 'dry-run' : 'live'}`,
|
|
182
260
|
`Project: ${plan.projectName}`,
|
|
183
261
|
`Project slug: ${plan.projectSlug}`,
|
|
184
262
|
'Writes: docs-only',
|
|
185
263
|
'Product code: untouched',
|
|
186
|
-
`Proposed docs: ${
|
|
264
|
+
`Proposed docs: ${writePlan.length > 0 ? writePlan.map((item) => item.path).join(', ') : 'none'}`,
|
|
187
265
|
];
|
|
188
266
|
|
|
189
267
|
if (!dryRun) {
|
|
190
|
-
lines.push(
|
|
268
|
+
lines.push(`${completed ? 'Written docs' : 'Planned writes'}: ${writtenDocs.length > 0 ? writtenDocs.join(', ') : 'none'}`);
|
|
269
|
+
if (snapshot) {
|
|
270
|
+
lines.push(`Snapshot: ${snapshot.root}`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (completed) {
|
|
275
|
+
return `${lines.join('\n')}\n`;
|
|
191
276
|
}
|
|
192
277
|
|
|
193
278
|
lines.push(
|
|
279
|
+
'Proposed changes:',
|
|
280
|
+
...writePlan.map((item) => `- ${item.path}: ${item.action}${item.reason ? ` (${item.reason})` : ''}`),
|
|
281
|
+
'Diff preview:',
|
|
282
|
+
...formatDiffPreview(writePlan),
|
|
194
283
|
'Files considered:',
|
|
195
284
|
...plan.filesConsidered.map((item) => `- ${item.path}: ${item.present ? 'present' : 'absent'}${item.reason ? ` (${item.reason})` : ''}`),
|
|
196
285
|
'Assumptions:',
|
|
197
286
|
...formatPathList(plan.assumptions),
|
|
198
287
|
'Risks:',
|
|
199
288
|
...formatPathList(plan.risks),
|
|
289
|
+
'Contradictions:',
|
|
290
|
+
...formatPathList(plan.contradictions),
|
|
200
291
|
'Omitted paths:',
|
|
201
292
|
...formatPathList(plan.omittedPaths),
|
|
202
293
|
'Uncertainty markers: TODO | Assumption | Pending confirmation',
|
|
@@ -207,20 +298,188 @@ function formatContextPreparationReport({ dryRun, plan, docs, writtenDocs }) {
|
|
|
207
298
|
|
|
208
299
|
function writeProviderOutput(result) {
|
|
209
300
|
if (result.stdout) {
|
|
210
|
-
process.stdout.write(result.stdout);
|
|
301
|
+
process.stdout.write(redactSecrets(result.stdout));
|
|
211
302
|
}
|
|
212
303
|
if (result.stderr) {
|
|
213
|
-
process.stderr.write(result.stderr);
|
|
304
|
+
process.stderr.write(redactSecrets(result.stderr));
|
|
214
305
|
}
|
|
215
306
|
}
|
|
216
307
|
|
|
217
|
-
function
|
|
218
|
-
|
|
219
|
-
|
|
308
|
+
function normalizeText(value) {
|
|
309
|
+
return String(value || '').replace(/\r\n/g, '\n');
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function buildRevisionInput({ phase, feedbackPath, feedbackText, repoRoot, compactionOptions = {} }) {
|
|
313
|
+
const current = readPhaseApproval(repoRoot, phase);
|
|
314
|
+
if (!current.draft) {
|
|
315
|
+
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.`));
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const sections = [];
|
|
319
|
+
|
|
320
|
+
if (phase === 'technical-plan') {
|
|
321
|
+
const acceptance = resolveApprovedPlannerInput(repoRoot, phase, undefined);
|
|
322
|
+
const acceptanceText = readTextFile(acceptance.inputPath, repoRoot);
|
|
323
|
+
sections.push(`Approved acceptance input (${acceptance.inputPath}):`, acceptanceText.trimEnd());
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
sections.push(
|
|
327
|
+
`Current ${phase} draft (${current.draft.path}):`,
|
|
328
|
+
current.draft.contents.trimEnd(),
|
|
329
|
+
`Human feedback (${feedbackPath}):`,
|
|
330
|
+
feedbackText.trimEnd(),
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
return compactRevisionInput(sections.join('\n\n'), compactionOptions);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function buildManagedContextBlock(content) {
|
|
337
|
+
return `${CONTEXT_PREP_START}\n${String(content || '').trimEnd()}\n${CONTEXT_PREP_END}\n`;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function mergeContextDraft(existingContent, draftContent) {
|
|
341
|
+
const existing = normalizeText(existingContent);
|
|
342
|
+
const block = buildManagedContextBlock(draftContent);
|
|
343
|
+
const startIndex = existing.indexOf(CONTEXT_PREP_START);
|
|
344
|
+
const endIndex = existing.indexOf(CONTEXT_PREP_END);
|
|
345
|
+
|
|
346
|
+
if (startIndex >= 0 && endIndex > startIndex) {
|
|
347
|
+
const before = existing.slice(0, startIndex).trimEnd();
|
|
348
|
+
const after = existing.slice(endIndex + CONTEXT_PREP_END.length).trimStart();
|
|
349
|
+
return `${before}\n\n${block}${after ? `\n${after}` : ''}`;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return `${existing.trimEnd()}\n\n${block}`;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function firstChangedLineIndex(beforeLines, afterLines) {
|
|
356
|
+
const max = Math.max(beforeLines.length, afterLines.length);
|
|
357
|
+
for (let index = 0; index < max; index += 1) {
|
|
358
|
+
if (beforeLines[index] !== afterLines[index]) {
|
|
359
|
+
return index;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return -1;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function buildDiffSnippet(pathLabel, beforeContent, afterContent, maxLines = 10) {
|
|
366
|
+
const beforeLines = normalizeText(beforeContent).split('\n');
|
|
367
|
+
const afterLines = normalizeText(afterContent).split('\n');
|
|
368
|
+
const changedAt = firstChangedLineIndex(beforeLines, afterLines);
|
|
369
|
+
|
|
370
|
+
if (changedAt === -1) {
|
|
371
|
+
return [`diff -- ${pathLabel}`, ' no changes'];
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const start = Math.max(0, changedAt - 2);
|
|
375
|
+
const beforeSnippet = beforeLines.slice(start, start + maxLines);
|
|
376
|
+
const afterSnippet = afterLines.slice(start, start + maxLines);
|
|
377
|
+
const lines = [
|
|
378
|
+
`--- ${pathLabel} (current)`,
|
|
379
|
+
`+++ ${pathLabel} (proposed)`,
|
|
380
|
+
];
|
|
381
|
+
|
|
382
|
+
for (const line of beforeSnippet) {
|
|
383
|
+
if (line) {
|
|
384
|
+
lines.push(`- ${line}`);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
for (const line of afterSnippet) {
|
|
389
|
+
if (line) {
|
|
390
|
+
lines.push(`+ ${line}`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return lines;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function buildContextWritePlan(repoRoot, drafts) {
|
|
398
|
+
return drafts.map((draft) => {
|
|
220
399
|
const destinationPath = path.join(repoRoot, draft.path);
|
|
221
|
-
fs.
|
|
222
|
-
fs.
|
|
223
|
-
|
|
400
|
+
const exists = fs.existsSync(destinationPath);
|
|
401
|
+
const currentContent = exists ? fs.readFileSync(destinationPath, 'utf8') : '';
|
|
402
|
+
const proposedContent = exists
|
|
403
|
+
? mergeContextDraft(currentContent, draft.content)
|
|
404
|
+
: `${String(draft.content || '').replace(/\s+$/g, '')}\n`;
|
|
405
|
+
const changed = normalizeText(currentContent) !== normalizeText(proposedContent);
|
|
406
|
+
|
|
407
|
+
return {
|
|
408
|
+
path: draft.path,
|
|
409
|
+
destinationPath,
|
|
410
|
+
action: changed ? (exists ? 'update' : 'create') : 'skip',
|
|
411
|
+
reason: changed ? (exists ? 'human content preserved; Quiver block appended or refreshed' : 'missing approved context doc') : 'already up to date',
|
|
412
|
+
exists,
|
|
413
|
+
currentContent,
|
|
414
|
+
proposedContent,
|
|
415
|
+
diff: buildDiffSnippet(draft.path, currentContent, proposedContent),
|
|
416
|
+
};
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function formatDiffPreview(writePlan) {
|
|
421
|
+
const lines = [];
|
|
422
|
+
for (const item of writePlan) {
|
|
423
|
+
if (item.action === 'skip') {
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
lines.push(...item.diff);
|
|
427
|
+
}
|
|
428
|
+
return lines.length > 0 ? lines : ['- no changes'];
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function createContextSnapshots(repoRoot, run, writePlan, now = new Date()) {
|
|
432
|
+
const stamp = now.toISOString().replace(/[-:]/g, '').replace(/\.\d{3}Z$/, 'Z');
|
|
433
|
+
const snapshotRoot = path.join(repoRoot, '.quiver', 'runs', run.run_id, 'snapshots', stamp);
|
|
434
|
+
const manifest = {
|
|
435
|
+
schema_version: 1,
|
|
436
|
+
run_id: run.run_id,
|
|
437
|
+
created_at: now.toISOString(),
|
|
438
|
+
entries: [],
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
fs.mkdirSync(snapshotRoot, { recursive: true });
|
|
442
|
+
|
|
443
|
+
for (const item of writePlan) {
|
|
444
|
+
if (item.action === 'skip') {
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
const entry = {
|
|
448
|
+
path: item.path,
|
|
449
|
+
action: item.action,
|
|
450
|
+
existed: item.exists,
|
|
451
|
+
snapshot_path: null,
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
if (item.exists) {
|
|
455
|
+
const snapshotPath = path.join(snapshotRoot, item.path);
|
|
456
|
+
fs.mkdirSync(path.dirname(snapshotPath), { recursive: true });
|
|
457
|
+
fs.copyFileSync(item.destinationPath, snapshotPath);
|
|
458
|
+
entry.snapshot_path = path.relative(repoRoot, snapshotPath).split(path.sep).join('/');
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
manifest.entries.push(entry);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const manifestPath = path.join(snapshotRoot, 'manifest.json');
|
|
465
|
+
fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
466
|
+
|
|
467
|
+
return {
|
|
468
|
+
root: path.relative(repoRoot, snapshotRoot).split(path.sep).join('/'),
|
|
469
|
+
manifestPath: path.relative(repoRoot, manifestPath).split(path.sep).join('/'),
|
|
470
|
+
entries: manifest.entries,
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function writeDraftDocs(writePlan) {
|
|
475
|
+
const writtenDocs = [];
|
|
476
|
+
for (const item of writePlan) {
|
|
477
|
+
if (item.action === 'skip') {
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
fs.mkdirSync(path.dirname(item.destinationPath), { recursive: true });
|
|
481
|
+
fs.writeFileSync(item.destinationPath, item.proposedContent);
|
|
482
|
+
writtenDocs.push(item.path);
|
|
224
483
|
}
|
|
225
484
|
return writtenDocs;
|
|
226
485
|
}
|
|
@@ -350,6 +609,20 @@ async function runOnboard(repoRoot, options = {}) {
|
|
|
350
609
|
return report;
|
|
351
610
|
}
|
|
352
611
|
|
|
612
|
+
if (options.printPrompt) {
|
|
613
|
+
const report = {
|
|
614
|
+
task: 'onboard',
|
|
615
|
+
provider,
|
|
616
|
+
role,
|
|
617
|
+
contextPack: context,
|
|
618
|
+
invocation,
|
|
619
|
+
onboardingPlan: contextInfo.plan,
|
|
620
|
+
prompt,
|
|
621
|
+
};
|
|
622
|
+
process.stdout.write(formatPromptOnlyReport(report));
|
|
623
|
+
return report;
|
|
624
|
+
}
|
|
625
|
+
|
|
353
626
|
let result;
|
|
354
627
|
try {
|
|
355
628
|
result = await (options.runProviderFn || runProvider)(provider, {
|
|
@@ -386,33 +659,62 @@ async function runOnboard(repoRoot, options = {}) {
|
|
|
386
659
|
|
|
387
660
|
async function runPrepareContext(repoRoot, options = {}) {
|
|
388
661
|
const draftPack = buildContextPreparationDrafts(repoRoot);
|
|
662
|
+
const writePlan = buildContextWritePlan(repoRoot, draftPack.docs);
|
|
389
663
|
const report = {
|
|
390
664
|
task: 'prepare-context',
|
|
391
665
|
dryRun: options.dryRun === true,
|
|
392
666
|
docs: draftPack.docs.map((doc) => doc.path),
|
|
393
667
|
plan: draftPack.plan,
|
|
668
|
+
writePlan: writePlan.map((item) => ({
|
|
669
|
+
path: item.path,
|
|
670
|
+
action: item.action,
|
|
671
|
+
reason: item.reason,
|
|
672
|
+
})),
|
|
394
673
|
};
|
|
395
674
|
|
|
396
675
|
if (options.dryRun) {
|
|
397
676
|
process.stdout.write(formatContextPreparationReport({
|
|
398
677
|
dryRun: true,
|
|
399
|
-
docs: draftPack.docs,
|
|
400
678
|
plan: draftPack.plan,
|
|
679
|
+
writePlan,
|
|
401
680
|
writtenDocs: [],
|
|
402
681
|
}));
|
|
403
682
|
return report;
|
|
404
683
|
}
|
|
405
684
|
|
|
406
|
-
const
|
|
685
|
+
const lifecycleRun = ensureAiRun(repoRoot, {
|
|
686
|
+
command: 'ai prepare-context',
|
|
687
|
+
input: options.input || '',
|
|
688
|
+
runId: options.runId,
|
|
689
|
+
phase: 'created',
|
|
690
|
+
});
|
|
691
|
+
const snapshot = createContextSnapshots(repoRoot, lifecycleRun, writePlan, options.now || new Date());
|
|
692
|
+
const plannedDocs = writePlan.filter((item) => item.action !== 'skip').map((item) => item.path);
|
|
693
|
+
process.stdout.write(formatContextPreparationReport({
|
|
694
|
+
dryRun: false,
|
|
695
|
+
plan: draftPack.plan,
|
|
696
|
+
writePlan,
|
|
697
|
+
writtenDocs: plannedDocs,
|
|
698
|
+
snapshot,
|
|
699
|
+
}));
|
|
700
|
+
const writtenDocs = writeDraftDocs(writePlan);
|
|
701
|
+
updateAiRunPhase(repoRoot, lifecycleRun.run_id, 'onboarding-ready', {
|
|
702
|
+
artifact: snapshot.manifestPath,
|
|
703
|
+
command: 'ai prepare-context',
|
|
704
|
+
});
|
|
407
705
|
process.stdout.write(formatContextPreparationReport({
|
|
408
706
|
dryRun: false,
|
|
409
|
-
docs: draftPack.docs,
|
|
410
707
|
plan: draftPack.plan,
|
|
708
|
+
writePlan,
|
|
411
709
|
writtenDocs,
|
|
710
|
+
snapshot,
|
|
711
|
+
completed: true,
|
|
412
712
|
}));
|
|
413
713
|
|
|
414
714
|
return {
|
|
415
715
|
...report,
|
|
716
|
+
runId: lifecycleRun.run_id,
|
|
717
|
+
snapshot,
|
|
416
718
|
writtenDocs,
|
|
417
719
|
};
|
|
418
720
|
}
|
|
@@ -424,6 +726,7 @@ async function runPlan(repoRoot, options = {}) {
|
|
|
424
726
|
const context = options.context || DEFAULT_PLAN_CONTEXT;
|
|
425
727
|
const timeoutMs = normalizeTimeout(options.timeout);
|
|
426
728
|
let inputPath = options.input || '';
|
|
729
|
+
let inputCompaction = null;
|
|
427
730
|
|
|
428
731
|
if (phase === 'spec') {
|
|
429
732
|
const resolved = resolveReviewedTechnicalPlanInput(repoRoot, inputPath || undefined);
|
|
@@ -436,6 +739,17 @@ async function runPlan(repoRoot, options = {}) {
|
|
|
436
739
|
specSlug: options.specSlug,
|
|
437
740
|
});
|
|
438
741
|
|
|
742
|
+
if (options.printPrompt) {
|
|
743
|
+
const report = {
|
|
744
|
+
task: 'plan',
|
|
745
|
+
phase,
|
|
746
|
+
manifest,
|
|
747
|
+
};
|
|
748
|
+
process.stdout.write('AI plan prompt-only\nPhase: spec\nNo provider prompt is used for spec generation; showing the local generation plan instead.\n');
|
|
749
|
+
process.stdout.write(formatSpecDryRunReport({ manifest, repoRoot }));
|
|
750
|
+
return report;
|
|
751
|
+
}
|
|
752
|
+
|
|
439
753
|
if (options.dryRun) {
|
|
440
754
|
const report = {
|
|
441
755
|
task: 'plan',
|
|
@@ -464,7 +778,23 @@ async function runPlan(repoRoot, options = {}) {
|
|
|
464
778
|
|
|
465
779
|
assertPlannerPhaseReady(phase);
|
|
466
780
|
|
|
467
|
-
|
|
781
|
+
let inputText = '';
|
|
782
|
+
|
|
783
|
+
if (options.revise === true) {
|
|
784
|
+
if (!inputPath) {
|
|
785
|
+
throw new Error(formatError(`missing feedback input file for ai revise phase '${phase}'`));
|
|
786
|
+
}
|
|
787
|
+
const feedbackText = readTextFile(inputPath, repoRoot);
|
|
788
|
+
const revisionInput = buildRevisionInput({
|
|
789
|
+
phase,
|
|
790
|
+
feedbackPath: inputPath,
|
|
791
|
+
feedbackText,
|
|
792
|
+
repoRoot,
|
|
793
|
+
compactionOptions: options,
|
|
794
|
+
});
|
|
795
|
+
inputText = revisionInput.text;
|
|
796
|
+
inputCompaction = revisionInput.compaction;
|
|
797
|
+
} else if (phase === 'technical-plan') {
|
|
468
798
|
const resolved = resolveApprovedPlannerInput(repoRoot, phase, inputPath || undefined);
|
|
469
799
|
inputPath = resolved.inputPath;
|
|
470
800
|
}
|
|
@@ -473,7 +803,9 @@ async function runPlan(repoRoot, options = {}) {
|
|
|
473
803
|
throw new Error(formatError(`missing input file for ai plan phase '${phase}'`));
|
|
474
804
|
}
|
|
475
805
|
|
|
476
|
-
|
|
806
|
+
if (!inputText) {
|
|
807
|
+
inputText = readTextFile(inputPath, repoRoot);
|
|
808
|
+
}
|
|
477
809
|
const contextInfo = buildPlanContext({
|
|
478
810
|
role,
|
|
479
811
|
context,
|
|
@@ -481,8 +813,10 @@ async function runPlan(repoRoot, options = {}) {
|
|
|
481
813
|
inputText,
|
|
482
814
|
inputPath,
|
|
483
815
|
repoRoot,
|
|
816
|
+
revise: options.revise === true,
|
|
484
817
|
});
|
|
485
818
|
const prompt = contextInfo.prompt;
|
|
819
|
+
assertProviderPromptWithinLimit(prompt, options);
|
|
486
820
|
let invocation;
|
|
487
821
|
|
|
488
822
|
try {
|
|
@@ -508,6 +842,20 @@ async function runPlan(repoRoot, options = {}) {
|
|
|
508
842
|
return report;
|
|
509
843
|
}
|
|
510
844
|
|
|
845
|
+
if (options.printPrompt) {
|
|
846
|
+
const report = {
|
|
847
|
+
task: 'plan',
|
|
848
|
+
provider,
|
|
849
|
+
role,
|
|
850
|
+
contextPack: contextInfo.pack.packName,
|
|
851
|
+
phase,
|
|
852
|
+
invocation,
|
|
853
|
+
prompt,
|
|
854
|
+
};
|
|
855
|
+
process.stdout.write(formatPromptOnlyReport(report));
|
|
856
|
+
return report;
|
|
857
|
+
}
|
|
858
|
+
|
|
511
859
|
let result;
|
|
512
860
|
try {
|
|
513
861
|
result = await (options.runProviderFn || runProvider)(provider, {
|
|
@@ -531,7 +879,31 @@ async function runPlan(repoRoot, options = {}) {
|
|
|
531
879
|
throw annotateProviderError(result.error || new Error('provider run failed'), 'plan', phase);
|
|
532
880
|
}
|
|
533
881
|
|
|
534
|
-
|
|
882
|
+
const lifecycleRun = ensureAiRun(repoRoot, {
|
|
883
|
+
command: `ai plan --phase ${phase}`,
|
|
884
|
+
input: inputPath,
|
|
885
|
+
runId: options.runId,
|
|
886
|
+
});
|
|
887
|
+
const clean = extractCleanProviderOutput(result, { prompt, projectRoot: repoRoot });
|
|
888
|
+
const rawArtifact = writeRawProviderArtifact(repoRoot, lifecycleRun.run_id, `ai-plan-${phase}`, result, {
|
|
889
|
+
metadata: {
|
|
890
|
+
phase,
|
|
891
|
+
input_path: inputPath,
|
|
892
|
+
prompt_bytes: invocation.promptLength,
|
|
893
|
+
clean_output_source: clean.source,
|
|
894
|
+
stripped_prompt_echo: clean.strippedPromptEcho,
|
|
895
|
+
input_compaction: inputCompaction,
|
|
896
|
+
},
|
|
897
|
+
});
|
|
898
|
+
const draft = savePlannerDraft(repoRoot, phase, inputPath, clean.cleanOutput, {
|
|
899
|
+
rawArtifactPath: rawArtifact.path,
|
|
900
|
+
outputSource: clean.source,
|
|
901
|
+
inputCompaction,
|
|
902
|
+
});
|
|
903
|
+
updateAiRunPhase(repoRoot, lifecycleRun.run_id, phase === 'acceptance' ? 'acceptance-draft' : 'technical-plan-draft', {
|
|
904
|
+
artifact: path.relative(repoRoot, draft.filePath).split(path.sep).join('/'),
|
|
905
|
+
command: `ai plan --phase ${phase}`,
|
|
906
|
+
});
|
|
535
907
|
|
|
536
908
|
return {
|
|
537
909
|
task: 'plan',
|
|
@@ -562,6 +934,7 @@ async function runReviewPlan(repoRoot, options = {}) {
|
|
|
562
934
|
inputText,
|
|
563
935
|
inputPath,
|
|
564
936
|
});
|
|
937
|
+
assertProviderPromptWithinLimit(built.prompt, options);
|
|
565
938
|
let invocation;
|
|
566
939
|
|
|
567
940
|
try {
|
|
@@ -603,6 +976,24 @@ async function runReviewPlan(repoRoot, options = {}) {
|
|
|
603
976
|
return report;
|
|
604
977
|
}
|
|
605
978
|
|
|
979
|
+
if (options.printPrompt) {
|
|
980
|
+
const report = {
|
|
981
|
+
task: 'review-plan',
|
|
982
|
+
provider,
|
|
983
|
+
role: 'reviewer',
|
|
984
|
+
contextPack: pack.packName,
|
|
985
|
+
phase: 'plan-review',
|
|
986
|
+
invocation,
|
|
987
|
+
prompt: built.prompt,
|
|
988
|
+
promptSource: built.promptSource,
|
|
989
|
+
inputPath,
|
|
990
|
+
inputKind: resolved.kind,
|
|
991
|
+
inputVersion: resolved.version,
|
|
992
|
+
};
|
|
993
|
+
process.stdout.write(formatPromptOnlyReport(report));
|
|
994
|
+
return report;
|
|
995
|
+
}
|
|
996
|
+
|
|
606
997
|
let result;
|
|
607
998
|
try {
|
|
608
999
|
result = await (options.runProviderFn || runProvider)(provider, {
|
|
@@ -626,11 +1017,31 @@ async function runReviewPlan(repoRoot, options = {}) {
|
|
|
626
1017
|
throw annotateProviderError(result.error || new Error('provider run failed'), 'review-plan');
|
|
627
1018
|
}
|
|
628
1019
|
|
|
1020
|
+
const lifecycleRun = ensureAiRun(repoRoot, {
|
|
1021
|
+
command: 'ai review-plan',
|
|
1022
|
+
input: inputPath,
|
|
1023
|
+
runId: options.runId,
|
|
1024
|
+
phase: 'technical-plan-reviewed',
|
|
1025
|
+
});
|
|
1026
|
+
const clean = extractCleanProviderOutput(result, { prompt: built.prompt, projectRoot: repoRoot });
|
|
1027
|
+
const rawArtifact = writeRawProviderArtifact(repoRoot, lifecycleRun.run_id, 'ai-review-plan', result, {
|
|
1028
|
+
metadata: {
|
|
1029
|
+
phase: 'plan-review',
|
|
1030
|
+
input_path: inputPath,
|
|
1031
|
+
input_kind: resolved.kind,
|
|
1032
|
+
input_version: resolved.version || null,
|
|
1033
|
+
prompt_bytes: invocation.promptLength,
|
|
1034
|
+
clean_output_source: clean.source,
|
|
1035
|
+
stripped_prompt_echo: clean.strippedPromptEcho,
|
|
1036
|
+
},
|
|
1037
|
+
});
|
|
629
1038
|
const saved = savePlanReview(repoRoot, {
|
|
630
|
-
contents:
|
|
1039
|
+
contents: clean.cleanOutput,
|
|
631
1040
|
inputPath,
|
|
632
1041
|
inputKind: resolved.kind,
|
|
633
1042
|
inputVersion: resolved.version,
|
|
1043
|
+
outputSource: clean.source,
|
|
1044
|
+
rawArtifactPath: rawArtifact.path,
|
|
634
1045
|
});
|
|
635
1046
|
const relativePath = path.relative(repoRoot, saved.filePath).split(path.sep).join('/');
|
|
636
1047
|
process.stdout.write(`AI plan review saved\nArtifact: ${relativePath}\nPrompt source: ${PLAN_REVIEW_PROMPT_SOURCE}\n`);
|
|
@@ -649,17 +1060,46 @@ async function runReviewPlan(repoRoot, options = {}) {
|
|
|
649
1060
|
};
|
|
650
1061
|
}
|
|
651
1062
|
|
|
1063
|
+
async function runRevise(repoRoot, options = {}) {
|
|
1064
|
+
const phase = normalizePlannerPhase(options.phase || DEFAULT_PLAN_PHASE);
|
|
1065
|
+
if (phase === 'spec') {
|
|
1066
|
+
throw new Error(formatError(`ai revise does not support phase '${phase}'`));
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
const approval = readPhaseApproval(repoRoot, phase);
|
|
1070
|
+
if (approval.status !== 'draft' && approval.status !== 'stale') {
|
|
1071
|
+
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.`));
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
return runPlan(repoRoot, {
|
|
1075
|
+
...options,
|
|
1076
|
+
phase,
|
|
1077
|
+
revise: true,
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
|
|
652
1081
|
async function runApprove(repoRoot, options = {}) {
|
|
653
1082
|
const phase = normalizePlannerPhase(options.phase || DEFAULT_PLAN_PHASE);
|
|
654
1083
|
if (phase === 'spec') {
|
|
655
1084
|
throw new Error(formatError(`ai approve does not support phase '${phase}'`));
|
|
656
1085
|
}
|
|
657
1086
|
|
|
658
|
-
if (!options.
|
|
659
|
-
throw new Error(formatError(`
|
|
1087
|
+
if (!options.version) {
|
|
1088
|
+
throw new Error(formatError(`ai approve --phase ${phase} requires --version <n>. Review drafts with \`npx create-quiver ai approvals\`.`));
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
if (options.input) {
|
|
1092
|
+
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.`));
|
|
660
1093
|
}
|
|
661
1094
|
|
|
662
|
-
|
|
1095
|
+
if (phase === 'technical-plan') {
|
|
1096
|
+
const review = readPlanReview(repoRoot);
|
|
1097
|
+
if (review.status !== 'unapproved' && review.status !== 'reviewed') {
|
|
1098
|
+
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\`.`));
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
const inputText = '';
|
|
663
1103
|
|
|
664
1104
|
if (options.dryRun) {
|
|
665
1105
|
process.stdout.write(formatApprovalDryRunResult({ phase, input: options.input, version: options.version }));
|
|
@@ -675,6 +1115,21 @@ async function runApprove(repoRoot, options = {}) {
|
|
|
675
1115
|
const result = approvePlannerPhase(repoRoot, phase, options.input || '', inputText, {
|
|
676
1116
|
version: options.version || undefined,
|
|
677
1117
|
});
|
|
1118
|
+
const lifecycleRun = ensureAiRun(repoRoot, {
|
|
1119
|
+
command: `ai approve --phase ${phase}`,
|
|
1120
|
+
input: options.input || result.filePath,
|
|
1121
|
+
runId: options.runId,
|
|
1122
|
+
});
|
|
1123
|
+
recordAiRunApproval(repoRoot, lifecycleRun.run_id, {
|
|
1124
|
+
artifact: path.relative(repoRoot, result.filePath).split(path.sep).join('/'),
|
|
1125
|
+
phase,
|
|
1126
|
+
source_file: options.input || `draft version ${options.version}`,
|
|
1127
|
+
version: result.version || null,
|
|
1128
|
+
});
|
|
1129
|
+
updateAiRunPhase(repoRoot, lifecycleRun.run_id, phase === 'acceptance' ? 'acceptance-approved' : 'technical-plan-approved', {
|
|
1130
|
+
artifact: path.relative(repoRoot, result.filePath).split(path.sep).join('/'),
|
|
1131
|
+
command: `ai approve --phase ${phase}`,
|
|
1132
|
+
});
|
|
678
1133
|
process.stdout.write(formatApprovalResult({
|
|
679
1134
|
...result,
|
|
680
1135
|
sourceFile: options.input || `draft version ${options.version}`,
|
|
@@ -699,6 +1154,131 @@ async function runApprovalStatus(repoRoot) {
|
|
|
699
1154
|
};
|
|
700
1155
|
}
|
|
701
1156
|
|
|
1157
|
+
function runLifecycleStatus(repoRoot, options = {}) {
|
|
1158
|
+
const run = resolveAiRun(repoRoot, options.runId || '');
|
|
1159
|
+
const report = formatAiRunStatus(repoRoot, run);
|
|
1160
|
+
process.stdout.write(report);
|
|
1161
|
+
return {
|
|
1162
|
+
task: 'status',
|
|
1163
|
+
run,
|
|
1164
|
+
report,
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
function runLifecycleResume(repoRoot, options = {}) {
|
|
1169
|
+
const run = resolveAiRun(repoRoot, options.runId || '');
|
|
1170
|
+
const report = formatAiRunResume(repoRoot, run);
|
|
1171
|
+
process.stdout.write(report);
|
|
1172
|
+
return {
|
|
1173
|
+
task: 'resume',
|
|
1174
|
+
run,
|
|
1175
|
+
report,
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
function runInspect(repoRoot, options = {}) {
|
|
1180
|
+
const report = collectLifecycleExport(repoRoot, {
|
|
1181
|
+
includeCompleted: options.includeCompleted === true,
|
|
1182
|
+
});
|
|
1183
|
+
process.stdout.write(formatLifecycleInspect(report));
|
|
1184
|
+
return {
|
|
1185
|
+
task: 'inspect',
|
|
1186
|
+
report,
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
function runExport(repoRoot, options = {}) {
|
|
1191
|
+
const report = collectLifecycleExport(repoRoot, {
|
|
1192
|
+
includeCompleted: options.includeCompleted === true,
|
|
1193
|
+
});
|
|
1194
|
+
const format = String(options.format || 'json').trim().toLowerCase();
|
|
1195
|
+
|
|
1196
|
+
if (format === 'json') {
|
|
1197
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
1198
|
+
return {
|
|
1199
|
+
task: 'export',
|
|
1200
|
+
format,
|
|
1201
|
+
report,
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
if (format === 'markdown' || format === 'md') {
|
|
1206
|
+
process.stdout.write(formatLifecycleExportMarkdown(report));
|
|
1207
|
+
return {
|
|
1208
|
+
task: 'export',
|
|
1209
|
+
format: 'markdown',
|
|
1210
|
+
report,
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
throw new Error(formatError(`unsupported ai export format: ${format}. Supported formats: json, markdown`));
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
function runSpecsList(repoRoot, options = {}) {
|
|
1218
|
+
const report = collectLifecycleExport(repoRoot, {
|
|
1219
|
+
includeCompleted: options.includeCompleted === true,
|
|
1220
|
+
});
|
|
1221
|
+
if (options.json === true) {
|
|
1222
|
+
process.stdout.write(`${JSON.stringify({ specs: report.specs }, null, 2)}\n`);
|
|
1223
|
+
} else {
|
|
1224
|
+
process.stdout.write(formatSpecsList(report));
|
|
1225
|
+
}
|
|
1226
|
+
return {
|
|
1227
|
+
task: 'specs',
|
|
1228
|
+
specs: report.specs,
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
function runSlicesList(repoRoot, options = {}) {
|
|
1233
|
+
const report = collectLifecycleExport(repoRoot, {
|
|
1234
|
+
includeCompleted: options.includeCompleted === true,
|
|
1235
|
+
});
|
|
1236
|
+
if (options.json === true) {
|
|
1237
|
+
process.stdout.write(`${JSON.stringify({ slices: report.slices }, null, 2)}\n`);
|
|
1238
|
+
} else {
|
|
1239
|
+
process.stdout.write(formatSlicesList(report));
|
|
1240
|
+
}
|
|
1241
|
+
return {
|
|
1242
|
+
task: 'slices',
|
|
1243
|
+
slices: report.slices,
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
function runTraceReport(repoRoot, options = {}) {
|
|
1248
|
+
const report = collectLifecycleExport(repoRoot, {
|
|
1249
|
+
includeCompleted: options.includeCompleted === true,
|
|
1250
|
+
});
|
|
1251
|
+
process.stdout.write(formatTraceReport(report));
|
|
1252
|
+
return {
|
|
1253
|
+
task: 'trace',
|
|
1254
|
+
report,
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
function runLifecycleRun(repoRoot, options = {}) {
|
|
1259
|
+
const command = String(options.command || '').trim().toLowerCase();
|
|
1260
|
+
if (command !== 'create') {
|
|
1261
|
+
throw new Error(formatError(`unsupported ai run subcommand: ${command}. Supported tasks: create`));
|
|
1262
|
+
}
|
|
1263
|
+
if (!options.input) {
|
|
1264
|
+
throw new Error(formatError('ai run create requires --input <requirements.md>'));
|
|
1265
|
+
}
|
|
1266
|
+
const run = createAiRun(repoRoot, {
|
|
1267
|
+
command: 'ai run create',
|
|
1268
|
+
input: options.input,
|
|
1269
|
+
runId: options.runId,
|
|
1270
|
+
specSlug: options.specSlug,
|
|
1271
|
+
});
|
|
1272
|
+
const report = formatAiRunStatus(repoRoot, run);
|
|
1273
|
+
process.stdout.write(report);
|
|
1274
|
+
return {
|
|
1275
|
+
task: 'run',
|
|
1276
|
+
command,
|
|
1277
|
+
run,
|
|
1278
|
+
report,
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
|
|
702
1282
|
function formatAgentProfile(profile) {
|
|
703
1283
|
const lines = [
|
|
704
1284
|
`Role: ${profile.role}`,
|
|
@@ -725,16 +1305,47 @@ function formatAgentProfileList(profiles) {
|
|
|
725
1305
|
return `${lines.join('\n')}\n`;
|
|
726
1306
|
}
|
|
727
1307
|
|
|
1308
|
+
function formatAgentProfileDryRun(repoRoot, result) {
|
|
1309
|
+
const relativePath = path.relative(repoRoot, result.filePath).split(path.sep).join('/');
|
|
1310
|
+
const verb = result.action === 'update' ? 'update' : 'create';
|
|
1311
|
+
return [
|
|
1312
|
+
'AI agent profile dry-run',
|
|
1313
|
+
'- Writes: none',
|
|
1314
|
+
`- Would ${verb}: ${relativePath}`,
|
|
1315
|
+
'',
|
|
1316
|
+
formatAgentProfile(result.profile).trimEnd(),
|
|
1317
|
+
'',
|
|
1318
|
+
'No secrets or provider credentials are stored in agent profiles.',
|
|
1319
|
+
'',
|
|
1320
|
+
].join('\n');
|
|
1321
|
+
}
|
|
1322
|
+
|
|
728
1323
|
function runAgent(repoRoot, options = {}) {
|
|
729
1324
|
const command = String(options.command || '').trim().toLowerCase();
|
|
730
1325
|
|
|
731
1326
|
if (command === 'set') {
|
|
732
1327
|
if (!options.role) {
|
|
733
|
-
throw new Error(formatError('missing agent role. Use: npx create-quiver ai agent set <planner|executor|reviewer|
|
|
1328
|
+
throw new Error(formatError('missing agent role. Use: npx create-quiver ai agent set <planner|executor|reviewer|doctor> --provider <provider>'));
|
|
734
1329
|
}
|
|
735
1330
|
if (!options.provider) {
|
|
736
1331
|
throw new Error(formatError('ai agent set requires --provider. Supported providers: codex, claude, gemini.'));
|
|
737
1332
|
}
|
|
1333
|
+
if (options.dryRun) {
|
|
1334
|
+
const preview = buildAgentProfileState(repoRoot, options.role, {
|
|
1335
|
+
context: options.context,
|
|
1336
|
+
label: options.label,
|
|
1337
|
+
model: options.model,
|
|
1338
|
+
provider: options.provider,
|
|
1339
|
+
});
|
|
1340
|
+
process.stdout.write(formatAgentProfileDryRun(repoRoot, preview));
|
|
1341
|
+
return {
|
|
1342
|
+
task: 'agent',
|
|
1343
|
+
command,
|
|
1344
|
+
dryRun: true,
|
|
1345
|
+
profile: preview.profile,
|
|
1346
|
+
filePath: path.relative(repoRoot, preview.filePath).split(path.sep).join('/'),
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
738
1349
|
const result = setAgentProfile(repoRoot, options.role, {
|
|
739
1350
|
context: options.context,
|
|
740
1351
|
label: options.label,
|
|
@@ -754,11 +1365,16 @@ function runAgent(repoRoot, options = {}) {
|
|
|
754
1365
|
|
|
755
1366
|
if (command === 'show') {
|
|
756
1367
|
if (!options.role) {
|
|
757
|
-
throw new Error(formatError('missing agent role. Use: npx create-quiver ai agent show <planner|executor|reviewer|
|
|
1368
|
+
throw new Error(formatError('missing agent role. Use: npx create-quiver ai agent show <planner|executor|reviewer|doctor>'));
|
|
758
1369
|
}
|
|
759
1370
|
const profile = getAgentProfile(repoRoot, options.role);
|
|
760
1371
|
if (!profile) {
|
|
761
|
-
throw new Error(
|
|
1372
|
+
throw new Error(formatActionableError({
|
|
1373
|
+
failure: `agent profile '${options.role}' is not configured.`,
|
|
1374
|
+
impact: 'Quiver will fall back to default provider behavior and may use the wrong model/cost profile.',
|
|
1375
|
+
fix: `Configure the ${options.role} profile with a supported provider and optional model label.`,
|
|
1376
|
+
nextCommand: `npx create-quiver ai agent set ${options.role} --provider <provider> --model <label>`,
|
|
1377
|
+
}));
|
|
762
1378
|
}
|
|
763
1379
|
process.stdout.write(formatAgentProfile(profile));
|
|
764
1380
|
return {
|
|
@@ -903,12 +1519,21 @@ module.exports = {
|
|
|
903
1519
|
runDoctor,
|
|
904
1520
|
runExecutePlan,
|
|
905
1521
|
runExecuteSlice,
|
|
1522
|
+
runLifecycleResume,
|
|
1523
|
+
runLifecycleRun,
|
|
1524
|
+
runLifecycleStatus,
|
|
1525
|
+
runExport,
|
|
1526
|
+
runInspect,
|
|
906
1527
|
runPromptSlice,
|
|
907
1528
|
runApprove,
|
|
908
1529
|
runApprovalStatus,
|
|
909
1530
|
runPrepareContext,
|
|
910
1531
|
runReviewPlan,
|
|
1532
|
+
runRevise,
|
|
911
1533
|
runPr,
|
|
1534
|
+
runSlicesList,
|
|
1535
|
+
runSpecsList,
|
|
1536
|
+
runTraceReport,
|
|
912
1537
|
runOnboard,
|
|
913
1538
|
runPlan,
|
|
914
1539
|
writeProviderOutput,
|