create-quiver 0.12.1 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/README.md +24 -9
- package/README_FOR_AI.md +15 -6
- package/ROADMAP.md +15 -2
- package/docs/COMMANDS.md.template +12 -3
- package/docs/TROUBLESHOOTING.md.template +29 -0
- package/docs/WORKFLOW.md.template +13 -12
- package/package.json +2 -1
- package/specs/quiver-v26-0121-smoke-hardening/SPEC.md +2 -2
- package/specs/quiver-v26-0121-smoke-hardening/STATUS.md +5 -5
- 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/specs/quiver-v28-pixel-quiver-feedback-reconciliation/COVERAGE_MATRIX.md +117 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/EVIDENCE_REPORT.md +200 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/EXECUTION_PLAN.md +60 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/SPEC.md +132 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/STATUS.md +36 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/pr.md +128 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-00-reconciliation-and-evidence-freeze/CLOSURE_BRIEF.md +44 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-00-reconciliation-and-evidence-freeze/EXECUTION_BRIEF.md +56 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-00-reconciliation-and-evidence-freeze/slice.json +71 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-01-ai-run-state-approvals-and-clean-output/CLOSURE_BRIEF.md +38 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-01-ai-run-state-approvals-and-clean-output/EXECUTION_BRIEF.md +53 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-01-ai-run-state-approvals-and-clean-output/slice.json +83 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-02-structured-technical-plan-contract-and-repair-flow/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-02-structured-technical-plan-contract-and-repair-flow/EXECUTION_BRIEF.md +53 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-02-structured-technical-plan-contract-and-repair-flow/slice.json +85 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-03-active-slice-reconciliation-and-ai-inspect/CLOSURE_BRIEF.md +34 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-03-active-slice-reconciliation-and-ai-inspect/EXECUTION_BRIEF.md +52 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-03-active-slice-reconciliation-and-ai-inspect/slice.json +82 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-04-spec-validation-scope-and-worktree-reliability/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-04-spec-validation-scope-and-worktree-reliability/EXECUTION_BRIEF.md +55 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-04-spec-validation-scope-and-worktree-reliability/slice.json +85 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-05-review-plan-closure-and-agent-dx/CLOSURE_BRIEF.md +35 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-05-review-plan-closure-and-agent-dx/EXECUTION_BRIEF.md +59 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-05-review-plan-closure-and-agent-dx/slice.json +94 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-06-backward-compatibility-docs-and-release-readiness/CLOSURE_BRIEF.md +40 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-06-backward-compatibility-docs-and-release-readiness/EXECUTION_BRIEF.md +56 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-06-backward-compatibility-docs-and-release-readiness/slice.json +98 -0
- package/src/create-quiver/commands/ai.js +563 -21
- package/src/create-quiver/commands/flow.js +52 -4
- package/src/create-quiver/commands/graph.js +7 -7
- package/src/create-quiver/commands/plan.js +6 -15
- package/src/create-quiver/commands/spec.js +292 -0
- package/src/create-quiver/index.js +125 -25
- package/src/create-quiver/lib/agent-profiles.js +15 -3
- package/src/create-quiver/lib/ai/artifacts.js +318 -0
- package/src/create-quiver/lib/ai/context-packs.js +2 -2
- package/src/create-quiver/lib/ai/execution-plan.js +9 -0
- package/src/create-quiver/lib/ai/executor.js +3 -2
- package/src/create-quiver/lib/ai/export-state.js +287 -95
- package/src/create-quiver/lib/ai/github.js +93 -4
- package/src/create-quiver/lib/ai/plan-review.js +161 -0
- package/src/create-quiver/lib/ai/run-state.js +17 -2
- package/src/create-quiver/lib/ai/spec-generator.js +87 -13
- package/src/create-quiver/lib/ai/spec-templates.js +72 -12
- package/src/create-quiver/lib/analyze.js +2 -2
- package/src/create-quiver/lib/approvals.js +14 -2
- package/src/create-quiver/lib/doctor.js +79 -0
- package/src/create-quiver/lib/git.js +40 -1
- package/src/create-quiver/lib/handoff.js +43 -1
- package/src/create-quiver/lib/init-docs.js +11 -7
- package/src/create-quiver/lib/init-layout.js +1 -0
- 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 +430 -0
- package/src/create-quiver/lib/readiness.js +48 -7
- package/src/create-quiver/lib/scope.js +2 -1
- package/src/create-quiver/lib/slice.js +8 -4
- package/src/create-quiver/lib/spec-worktrees.js +169 -38
- package/src/create-quiver/lib/statuses.js +115 -0
|
@@ -3,6 +3,12 @@ const path = require('node:path');
|
|
|
3
3
|
|
|
4
4
|
const { redactSecrets } = require('../lib/evidence');
|
|
5
5
|
const { formatActionableError } = require('../lib/actionable-error');
|
|
6
|
+
const {
|
|
7
|
+
assertProviderPromptWithinLimit,
|
|
8
|
+
compactRevisionInput,
|
|
9
|
+
extractCleanProviderOutput,
|
|
10
|
+
writeRawProviderArtifact,
|
|
11
|
+
} = require('../lib/ai/artifacts');
|
|
6
12
|
const { buildContextPackMetadata, normalizeRole } = require('../lib/ai/context-packs');
|
|
7
13
|
const { runExecuteSlice, runPromptSlice } = require('../lib/ai/executor');
|
|
8
14
|
const { runExecutePlan } = require('../lib/ai/execution-plan');
|
|
@@ -20,24 +26,32 @@ const {
|
|
|
20
26
|
PLAN_REVIEW_PROMPT_SOURCE,
|
|
21
27
|
buildPlanReviewPrompt,
|
|
22
28
|
readPlanReview,
|
|
29
|
+
reviewBlocksApproval,
|
|
23
30
|
resolveReviewedTechnicalPlanInput,
|
|
24
31
|
resolveTechnicalPlanReviewInput,
|
|
25
32
|
savePlanReview,
|
|
26
33
|
summarizePlanReview,
|
|
27
34
|
} = require('../lib/ai/plan-review');
|
|
28
|
-
const {
|
|
35
|
+
const {
|
|
36
|
+
buildSpecGenerationManifest,
|
|
37
|
+
describeSpecGeneration,
|
|
38
|
+
generateSpecArtifacts,
|
|
39
|
+
validateTechnicalPlanSpecContract,
|
|
40
|
+
} = require('../lib/ai/spec-generator');
|
|
29
41
|
const { buildProviderInvocation, runProvider } = require('../lib/ai/providers');
|
|
30
42
|
const {
|
|
31
43
|
createAiRun,
|
|
32
44
|
ensureAiRun,
|
|
33
45
|
formatAiRunResume,
|
|
34
46
|
formatAiRunStatus,
|
|
47
|
+
listAiRuns,
|
|
35
48
|
recordAiRunApproval,
|
|
36
49
|
resolveAiRun,
|
|
37
50
|
updateAiRunPhase,
|
|
38
51
|
} = require('../lib/ai/run-state');
|
|
39
52
|
const {
|
|
40
53
|
agentProfilesPath,
|
|
54
|
+
buildAgentProfileState,
|
|
41
55
|
getAgentProfile,
|
|
42
56
|
listAgentProfiles,
|
|
43
57
|
resolveProfileProvider,
|
|
@@ -46,12 +60,15 @@ const {
|
|
|
46
60
|
const {
|
|
47
61
|
PLANNER_APPROVAL_PHASES,
|
|
48
62
|
approvePlannerPhase,
|
|
63
|
+
findDraftVersion,
|
|
64
|
+
latestDraftVersion,
|
|
49
65
|
readPhaseApproval,
|
|
50
66
|
resolveApprovedPlannerInput,
|
|
51
67
|
savePlannerDraft,
|
|
52
68
|
summarizePlannerApproval,
|
|
53
69
|
} = require('../lib/approvals');
|
|
54
70
|
const { assertPlannerPhaseReady, getPlannerPhaseDetails, normalizePlannerPhase, PlannerPhaseError } = require('../lib/ai/phase-gates');
|
|
71
|
+
const { collectActiveSliceState, resolveProjectState } = require('../lib/project-state-resolver');
|
|
55
72
|
|
|
56
73
|
const DEFAULT_ONBOARD_PROVIDER = 'codex';
|
|
57
74
|
const DEFAULT_ONBOARD_ROLE = 'planner';
|
|
@@ -126,6 +143,13 @@ function buildPlanContext({ role, context, phase, inputText, inputPath, repoRoot
|
|
|
126
143
|
: 'Task: produce a technical plan only. Do not create files or modify product code.',
|
|
127
144
|
];
|
|
128
145
|
|
|
146
|
+
if (phaseDetails.phase === 'technical-plan') {
|
|
147
|
+
sections.push(
|
|
148
|
+
'Required output contract: include a fenced json block with `{ "spec": { "slices": [...] } }` so Quiver can create specs after review and approval.',
|
|
149
|
+
'Each `spec.slices[]` item must include at least `slice_id`, `title`, `objective`, and `files`.',
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
129
153
|
if (relativeInputPath) {
|
|
130
154
|
sections.push(`Input file: ${relativeInputPath}`);
|
|
131
155
|
}
|
|
@@ -298,15 +322,19 @@ function writeProviderOutput(result) {
|
|
|
298
322
|
}
|
|
299
323
|
}
|
|
300
324
|
|
|
301
|
-
function
|
|
302
|
-
|
|
325
|
+
function writeCleanProviderOutput(clean) {
|
|
326
|
+
const output = String(clean?.cleanOutput || '');
|
|
327
|
+
if (!output) {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
process.stdout.write(output.endsWith('\n') ? output : `${output}\n`);
|
|
303
331
|
}
|
|
304
332
|
|
|
305
333
|
function normalizeText(value) {
|
|
306
334
|
return String(value || '').replace(/\r\n/g, '\n');
|
|
307
335
|
}
|
|
308
336
|
|
|
309
|
-
function buildRevisionInput({ phase, feedbackPath, feedbackText, repoRoot }) {
|
|
337
|
+
function buildRevisionInput({ phase, feedbackPath, feedbackText, repoRoot, compactionOptions = {} }) {
|
|
310
338
|
const current = readPhaseApproval(repoRoot, phase);
|
|
311
339
|
if (!current.draft) {
|
|
312
340
|
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.`));
|
|
@@ -327,7 +355,7 @@ function buildRevisionInput({ phase, feedbackPath, feedbackText, repoRoot }) {
|
|
|
327
355
|
feedbackText.trimEnd(),
|
|
328
356
|
);
|
|
329
357
|
|
|
330
|
-
return sections.join('\n\n');
|
|
358
|
+
return compactRevisionInput(sections.join('\n\n'), compactionOptions);
|
|
331
359
|
}
|
|
332
360
|
|
|
333
361
|
function buildManagedContextBlock(content) {
|
|
@@ -545,10 +573,262 @@ function formatApprovalDryRunResult({ phase, input, version }) {
|
|
|
545
573
|
return `${lines.join('\n')}\n`;
|
|
546
574
|
}
|
|
547
575
|
|
|
576
|
+
function stripCreateQuiverPrefix(message) {
|
|
577
|
+
return String(message || '').replace(/^create-quiver:\s*/, '');
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function readCurrentDraftForApproval(repoRoot, phase, version) {
|
|
581
|
+
const approval = readPhaseApproval(repoRoot, phase);
|
|
582
|
+
const selectedDraft = findDraftVersion(approval.meta, version);
|
|
583
|
+
if (!selectedDraft) {
|
|
584
|
+
throw new Error(formatError(`missing ${phase} draft version ${version}`));
|
|
585
|
+
}
|
|
586
|
+
const latestVersion = latestDraftVersion(approval.meta);
|
|
587
|
+
if (latestVersion && Number(selectedDraft.version) !== latestVersion) {
|
|
588
|
+
throw new Error(formatError(`${phase} draft version ${version} is not current; latest draft version is ${latestVersion}. Approve the latest version or revise again.`));
|
|
589
|
+
}
|
|
590
|
+
const draftPath = path.resolve(repoRoot, selectedDraft.path);
|
|
591
|
+
if (!fs.existsSync(draftPath)) {
|
|
592
|
+
throw new Error(formatError(`missing ${phase} draft artifact: ${selectedDraft.path}`));
|
|
593
|
+
}
|
|
594
|
+
return {
|
|
595
|
+
approval,
|
|
596
|
+
contents: fs.readFileSync(draftPath, 'utf8'),
|
|
597
|
+
draft: selectedDraft,
|
|
598
|
+
path: selectedDraft.path,
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function assertTechnicalPlanDraftHasSpecContract(repoRoot, version) {
|
|
603
|
+
const draft = readCurrentDraftForApproval(repoRoot, 'technical-plan', version);
|
|
604
|
+
try {
|
|
605
|
+
validateTechnicalPlanSpecContract(repoRoot, {
|
|
606
|
+
inputPath: draft.path,
|
|
607
|
+
inputText: draft.contents,
|
|
608
|
+
});
|
|
609
|
+
} catch (error) {
|
|
610
|
+
throw new Error(formatError([
|
|
611
|
+
`technical-plan draft v${version} cannot be approved because it cannot create specs.`,
|
|
612
|
+
stripCreateQuiverPrefix(error.message || error),
|
|
613
|
+
'Required contract: include a structured JSON block with `spec.slices[]` before approval.',
|
|
614
|
+
'Next safe command: npx create-quiver ai revise --phase technical-plan --input <feedback.md> --dry-run',
|
|
615
|
+
].join('\n')));
|
|
616
|
+
}
|
|
617
|
+
return draft;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function resolveApprovedTechnicalPlanForRepair(repoRoot, explicitInput = '') {
|
|
621
|
+
const approval = readPhaseApproval(repoRoot, 'technical-plan');
|
|
622
|
+
if (!approval.approved?.path) {
|
|
623
|
+
throw new Error(formatError('ai repair-plan requires an approved technical-plan artifact. Run `npx create-quiver ai approvals` to inspect planner state.'));
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
const approvedPath = approval.approved.path;
|
|
627
|
+
if (explicitInput) {
|
|
628
|
+
const explicit = path.resolve(repoRoot, explicitInput);
|
|
629
|
+
const approved = path.resolve(repoRoot, approvedPath);
|
|
630
|
+
if (explicit !== approved) {
|
|
631
|
+
throw new Error(formatError(`ai repair-plan input must match the approved technical-plan artifact: ${approvedPath}`));
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const contents = readTextFile(approvedPath, repoRoot);
|
|
636
|
+
try {
|
|
637
|
+
validateTechnicalPlanSpecContract(repoRoot, {
|
|
638
|
+
inputPath: approvedPath,
|
|
639
|
+
inputText: contents,
|
|
640
|
+
});
|
|
641
|
+
} catch (error) {
|
|
642
|
+
return {
|
|
643
|
+
approval,
|
|
644
|
+
contents,
|
|
645
|
+
path: approvedPath,
|
|
646
|
+
validationError: stripCreateQuiverPrefix(error.message || error),
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
throw new Error(formatError('approved technical-plan already includes a valid structured `spec.slices[]` contract. No repair draft is needed.'));
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function buildRepairPlanContext({ context, inputText, inputPath, repoRoot, role, validationError }) {
|
|
654
|
+
const pack = buildContextPackMetadata({
|
|
655
|
+
role,
|
|
656
|
+
packName: context,
|
|
657
|
+
repoRoot,
|
|
658
|
+
});
|
|
659
|
+
const prompt = [
|
|
660
|
+
pack.prompt,
|
|
661
|
+
'Phase: technical-plan',
|
|
662
|
+
'Task: repair the approved technical plan into a new draft only. Do not approve it, create specs, modify product code, or expand scope.',
|
|
663
|
+
'Preserve the approved intent, scope, risks, and decisions.',
|
|
664
|
+
'Add the required Quiver structured JSON contract in a fenced json block.',
|
|
665
|
+
'The JSON must include `{ "spec": { "slug": "...", "title": "...", "objective": "...", "slices": [...] } }`.',
|
|
666
|
+
'Each item in `spec.slices[]` must include at least `slice_id`, `title`, `objective`, and `files`.',
|
|
667
|
+
`Validation failure to repair: ${validationError}`,
|
|
668
|
+
`Approved technical-plan artifact: ${inputPath}`,
|
|
669
|
+
'Approved technical-plan contents:',
|
|
670
|
+
inputText.trimEnd(),
|
|
671
|
+
].join('\n\n');
|
|
672
|
+
|
|
673
|
+
return {
|
|
674
|
+
pack,
|
|
675
|
+
prompt,
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
function formatRepairPlanResult(result, repoRoot) {
|
|
680
|
+
const relativePath = path.relative(repoRoot, result.filePath).split(path.sep).join('/');
|
|
681
|
+
return [
|
|
682
|
+
'AI technical-plan repair draft saved',
|
|
683
|
+
`Draft: ${relativePath}`,
|
|
684
|
+
`Version: v${result.version}`,
|
|
685
|
+
`Source approved artifact: ${result.sourcePath}`,
|
|
686
|
+
'Original approved artifact: preserved',
|
|
687
|
+
'Next safe commands:',
|
|
688
|
+
'- npx create-quiver ai review-plan --dry-run',
|
|
689
|
+
'- npx create-quiver ai review-plan',
|
|
690
|
+
`- npx create-quiver ai approve --phase technical-plan --version ${result.version}`,
|
|
691
|
+
].join('\n').concat('\n');
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function formatActiveSliceReconciliationReport(report, options = {}) {
|
|
695
|
+
const lines = [
|
|
696
|
+
'AI active-slice reconciliation',
|
|
697
|
+
`Mode: ${options.dryRun ? 'dry-run' : 'read-only'}`,
|
|
698
|
+
`Decision: ${report.reconciliation.decision}`,
|
|
699
|
+
`Reason: ${report.reconciliation.reason}`,
|
|
700
|
+
'',
|
|
701
|
+
'Supported sources:',
|
|
702
|
+
];
|
|
703
|
+
|
|
704
|
+
for (const source of report.supported_sources) {
|
|
705
|
+
lines.push(`- ${source.path}: ${source.exists ? 'exists' : 'missing'}`);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
lines.push('', 'Detected sources:');
|
|
709
|
+
if (report.sources.length === 0) {
|
|
710
|
+
lines.push('- none');
|
|
711
|
+
} else {
|
|
712
|
+
for (const source of report.sources) {
|
|
713
|
+
const ref = source.ref || '(unresolved)';
|
|
714
|
+
const status = source.status ? ` status=${source.status}` : '';
|
|
715
|
+
const issue = source.issue ? ` issue=${source.issue}` : '';
|
|
716
|
+
lines.push(`- ${source.source_id}: ${ref}${status}${issue}`);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
lines.push('', 'Planned changes:');
|
|
721
|
+
if (report.reconciliation.planned_changes.length === 0) {
|
|
722
|
+
lines.push('- none');
|
|
723
|
+
} else {
|
|
724
|
+
for (const change of report.reconciliation.planned_changes) {
|
|
725
|
+
lines.push(`- ${change}`);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
lines.push('', 'Risks:');
|
|
730
|
+
if (report.reconciliation.risks.length === 0) {
|
|
731
|
+
lines.push('- none');
|
|
732
|
+
} else {
|
|
733
|
+
for (const risk of report.reconciliation.risks) {
|
|
734
|
+
lines.push(`- ${risk}`);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
lines.push('', options.dryRun ? 'No files were changed.' : 'This command is read-only; use start-slice or cleanup-slice for intentional writes.');
|
|
739
|
+
return `${lines.join('\n')}\n`;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
function readRunApprovals(repoRoot, run) {
|
|
743
|
+
if (!run?.approvals_path) {
|
|
744
|
+
return [];
|
|
745
|
+
}
|
|
746
|
+
const filePath = path.resolve(repoRoot, run.approvals_path);
|
|
747
|
+
if (!fs.existsSync(filePath)) {
|
|
748
|
+
return [];
|
|
749
|
+
}
|
|
750
|
+
try {
|
|
751
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
752
|
+
return Array.isArray(parsed.approvals) ? parsed.approvals : [];
|
|
753
|
+
} catch {
|
|
754
|
+
return [];
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
function collectRunApprovalRows(repoRoot) {
|
|
759
|
+
const activeRun = resolveAiRun(repoRoot, '');
|
|
760
|
+
return listAiRuns(repoRoot)
|
|
761
|
+
.flatMap((run) => readRunApprovals(repoRoot, run).map((approval) => ({
|
|
762
|
+
run,
|
|
763
|
+
approval,
|
|
764
|
+
relation: activeRun && run.run_id === activeRun.run_id
|
|
765
|
+
? 'active'
|
|
766
|
+
: run.status === 'closed'
|
|
767
|
+
? 'historical'
|
|
768
|
+
: 'other-open',
|
|
769
|
+
})));
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
function approvalArtifactForRelation(report) {
|
|
773
|
+
return report?.approved?.path || report?.draft?.path || '';
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
function classifyGlobalApprovalRelation(report, runApprovalRows) {
|
|
777
|
+
const artifact = approvalArtifactForRelation(report);
|
|
778
|
+
if (!artifact || report.status === 'missing') {
|
|
779
|
+
return 'none';
|
|
780
|
+
}
|
|
781
|
+
const matches = runApprovalRows.filter((row) => row.approval?.artifact === artifact);
|
|
782
|
+
if (matches.some((row) => row.relation === 'active')) {
|
|
783
|
+
return 'active';
|
|
784
|
+
}
|
|
785
|
+
if (matches.length > 0) {
|
|
786
|
+
return 'historical';
|
|
787
|
+
}
|
|
788
|
+
return 'orphaned';
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
function formatRunScopedApprovals(repoRoot, runApprovalRows) {
|
|
792
|
+
const runs = listAiRuns(repoRoot);
|
|
793
|
+
const activeRun = resolveAiRun(repoRoot, '');
|
|
794
|
+
const lines = [
|
|
795
|
+
'Run-scoped approvals',
|
|
796
|
+
`Active run: ${activeRun ? activeRun.run_id : '(none)'}`,
|
|
797
|
+
];
|
|
798
|
+
|
|
799
|
+
if (runs.length === 0) {
|
|
800
|
+
lines.push('- no AI runs found');
|
|
801
|
+
return `${lines.join('\n')}\n`;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
for (const run of runs.slice().reverse()) {
|
|
805
|
+
const relation = activeRun && run.run_id === activeRun.run_id
|
|
806
|
+
? 'active'
|
|
807
|
+
: run.status === 'closed'
|
|
808
|
+
? 'historical'
|
|
809
|
+
: 'other-open';
|
|
810
|
+
const approvals = runApprovalRows.filter((row) => row.run.run_id === run.run_id);
|
|
811
|
+
lines.push(`Run: ${run.run_id} (${relation}, phase: ${run.phase}, status: ${run.status})`);
|
|
812
|
+
if (approvals.length === 0) {
|
|
813
|
+
lines.push('- no run-scoped approvals');
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
816
|
+
for (const row of approvals) {
|
|
817
|
+
const version = row.approval.version ? ` v${row.approval.version}` : '';
|
|
818
|
+
lines.push(`- ${row.approval.phase || 'unknown'}${version}: ${row.approval.artifact || '(missing artifact)'}`);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
return `${lines.join('\n')}\n`;
|
|
823
|
+
}
|
|
824
|
+
|
|
548
825
|
function formatApprovalStatusReport(repoRoot) {
|
|
549
|
-
const
|
|
826
|
+
const runApprovalRows = collectRunApprovalRows(repoRoot);
|
|
827
|
+
const sections = ['AI approvals status', formatRunScopedApprovals(repoRoot, runApprovalRows).trimEnd(), 'Global planner approvals'];
|
|
550
828
|
for (const phase of PLANNER_APPROVAL_PHASES) {
|
|
551
|
-
|
|
829
|
+
const summary = summarizePlannerApproval(repoRoot, phase).trimEnd();
|
|
830
|
+
const relation = classifyGlobalApprovalRelation(readPhaseApproval(repoRoot, phase), runApprovalRows);
|
|
831
|
+
sections.push(`${summary}\nRun relation: ${relation}`);
|
|
552
832
|
}
|
|
553
833
|
sections.push(summarizePlanReview(repoRoot).trimEnd());
|
|
554
834
|
return `${sections.join('\n\n')}\n`;
|
|
@@ -637,12 +917,14 @@ async function runOnboard(repoRoot, options = {}) {
|
|
|
637
917
|
throw annotateProviderError(error, 'onboard');
|
|
638
918
|
}
|
|
639
919
|
|
|
640
|
-
writeProviderOutput(result);
|
|
641
|
-
|
|
642
920
|
if (!result.ok) {
|
|
921
|
+
writeProviderOutput(result);
|
|
643
922
|
throw annotateProviderError(result.error || new Error('provider run failed'), 'onboard');
|
|
644
923
|
}
|
|
645
924
|
|
|
925
|
+
const clean = extractCleanProviderOutput(result, { prompt, projectRoot: repoRoot });
|
|
926
|
+
writeCleanProviderOutput(clean);
|
|
927
|
+
|
|
646
928
|
return {
|
|
647
929
|
task: 'onboard',
|
|
648
930
|
provider,
|
|
@@ -723,6 +1005,7 @@ async function runPlan(repoRoot, options = {}) {
|
|
|
723
1005
|
const context = options.context || DEFAULT_PLAN_CONTEXT;
|
|
724
1006
|
const timeoutMs = normalizeTimeout(options.timeout);
|
|
725
1007
|
let inputPath = options.input || '';
|
|
1008
|
+
let inputCompaction = null;
|
|
726
1009
|
|
|
727
1010
|
if (phase === 'spec') {
|
|
728
1011
|
const resolved = resolveReviewedTechnicalPlanInput(repoRoot, inputPath || undefined);
|
|
@@ -781,12 +1064,15 @@ async function runPlan(repoRoot, options = {}) {
|
|
|
781
1064
|
throw new Error(formatError(`missing feedback input file for ai revise phase '${phase}'`));
|
|
782
1065
|
}
|
|
783
1066
|
const feedbackText = readTextFile(inputPath, repoRoot);
|
|
784
|
-
|
|
1067
|
+
const revisionInput = buildRevisionInput({
|
|
785
1068
|
phase,
|
|
786
1069
|
feedbackPath: inputPath,
|
|
787
1070
|
feedbackText,
|
|
788
1071
|
repoRoot,
|
|
1072
|
+
compactionOptions: options,
|
|
789
1073
|
});
|
|
1074
|
+
inputText = revisionInput.text;
|
|
1075
|
+
inputCompaction = revisionInput.compaction;
|
|
790
1076
|
} else if (phase === 'technical-plan') {
|
|
791
1077
|
const resolved = resolveApprovedPlannerInput(repoRoot, phase, inputPath || undefined);
|
|
792
1078
|
inputPath = resolved.inputPath;
|
|
@@ -809,6 +1095,7 @@ async function runPlan(repoRoot, options = {}) {
|
|
|
809
1095
|
revise: options.revise === true,
|
|
810
1096
|
});
|
|
811
1097
|
const prompt = contextInfo.prompt;
|
|
1098
|
+
assertProviderPromptWithinLimit(prompt, options);
|
|
812
1099
|
let invocation;
|
|
813
1100
|
|
|
814
1101
|
try {
|
|
@@ -865,18 +1152,33 @@ async function runPlan(repoRoot, options = {}) {
|
|
|
865
1152
|
throw annotateProviderError(error, 'plan', phase);
|
|
866
1153
|
}
|
|
867
1154
|
|
|
868
|
-
writeProviderOutput(result);
|
|
869
|
-
|
|
870
1155
|
if (!result.ok) {
|
|
1156
|
+
writeProviderOutput(result);
|
|
871
1157
|
throw annotateProviderError(result.error || new Error('provider run failed'), 'plan', phase);
|
|
872
1158
|
}
|
|
873
1159
|
|
|
874
|
-
const draft = savePlannerDraft(repoRoot, phase, inputPath, getRedactedProviderText(result));
|
|
875
1160
|
const lifecycleRun = ensureAiRun(repoRoot, {
|
|
876
1161
|
command: `ai plan --phase ${phase}`,
|
|
877
1162
|
input: inputPath,
|
|
878
1163
|
runId: options.runId,
|
|
879
1164
|
});
|
|
1165
|
+
const clean = extractCleanProviderOutput(result, { prompt, projectRoot: repoRoot });
|
|
1166
|
+
writeCleanProviderOutput(clean);
|
|
1167
|
+
const rawArtifact = writeRawProviderArtifact(repoRoot, lifecycleRun.run_id, `ai-plan-${phase}`, result, {
|
|
1168
|
+
metadata: {
|
|
1169
|
+
phase,
|
|
1170
|
+
input_path: inputPath,
|
|
1171
|
+
prompt_bytes: invocation.promptLength,
|
|
1172
|
+
clean_output_source: clean.source,
|
|
1173
|
+
stripped_prompt_echo: clean.strippedPromptEcho,
|
|
1174
|
+
input_compaction: inputCompaction,
|
|
1175
|
+
},
|
|
1176
|
+
});
|
|
1177
|
+
const draft = savePlannerDraft(repoRoot, phase, inputPath, clean.cleanOutput, {
|
|
1178
|
+
rawArtifactPath: rawArtifact.path,
|
|
1179
|
+
outputSource: clean.source,
|
|
1180
|
+
inputCompaction,
|
|
1181
|
+
});
|
|
880
1182
|
updateAiRunPhase(repoRoot, lifecycleRun.run_id, phase === 'acceptance' ? 'acceptance-draft' : 'technical-plan-draft', {
|
|
881
1183
|
artifact: path.relative(repoRoot, draft.filePath).split(path.sep).join('/'),
|
|
882
1184
|
command: `ai plan --phase ${phase}`,
|
|
@@ -911,6 +1213,7 @@ async function runReviewPlan(repoRoot, options = {}) {
|
|
|
911
1213
|
inputText,
|
|
912
1214
|
inputPath,
|
|
913
1215
|
});
|
|
1216
|
+
assertProviderPromptWithinLimit(built.prompt, options);
|
|
914
1217
|
let invocation;
|
|
915
1218
|
|
|
916
1219
|
try {
|
|
@@ -987,20 +1290,41 @@ async function runReviewPlan(repoRoot, options = {}) {
|
|
|
987
1290
|
throw annotateProviderError(error, 'review-plan');
|
|
988
1291
|
}
|
|
989
1292
|
|
|
990
|
-
writeProviderOutput(result);
|
|
991
|
-
|
|
992
1293
|
if (!result.ok) {
|
|
1294
|
+
writeProviderOutput(result);
|
|
993
1295
|
throw annotateProviderError(result.error || new Error('provider run failed'), 'review-plan');
|
|
994
1296
|
}
|
|
995
1297
|
|
|
1298
|
+
const lifecycleRun = ensureAiRun(repoRoot, {
|
|
1299
|
+
command: 'ai review-plan',
|
|
1300
|
+
input: inputPath,
|
|
1301
|
+
runId: options.runId,
|
|
1302
|
+
phase: 'technical-plan-reviewed',
|
|
1303
|
+
});
|
|
1304
|
+
const clean = extractCleanProviderOutput(result, { prompt: built.prompt, projectRoot: repoRoot });
|
|
1305
|
+
const rawArtifact = writeRawProviderArtifact(repoRoot, lifecycleRun.run_id, 'ai-review-plan', result, {
|
|
1306
|
+
metadata: {
|
|
1307
|
+
phase: 'plan-review',
|
|
1308
|
+
input_path: inputPath,
|
|
1309
|
+
input_kind: resolved.kind,
|
|
1310
|
+
input_version: resolved.version || null,
|
|
1311
|
+
prompt_bytes: invocation.promptLength,
|
|
1312
|
+
clean_output_source: clean.source,
|
|
1313
|
+
stripped_prompt_echo: clean.strippedPromptEcho,
|
|
1314
|
+
},
|
|
1315
|
+
});
|
|
1316
|
+
writeCleanProviderOutput(clean);
|
|
996
1317
|
const saved = savePlanReview(repoRoot, {
|
|
997
|
-
contents:
|
|
1318
|
+
contents: clean.cleanOutput,
|
|
998
1319
|
inputPath,
|
|
999
1320
|
inputKind: resolved.kind,
|
|
1000
1321
|
inputVersion: resolved.version,
|
|
1322
|
+
outputSource: clean.source,
|
|
1323
|
+
rawArtifactPath: rawArtifact.path,
|
|
1001
1324
|
});
|
|
1002
1325
|
const relativePath = path.relative(repoRoot, saved.filePath).split(path.sep).join('/');
|
|
1003
|
-
|
|
1326
|
+
const summary = summarizePlanReview(repoRoot).trimEnd();
|
|
1327
|
+
process.stdout.write(`AI plan review saved\nArtifact: ${relativePath}\nPrompt source: ${PLAN_REVIEW_PROMPT_SOURCE}\n${summary}\n`);
|
|
1004
1328
|
|
|
1005
1329
|
return {
|
|
1006
1330
|
task: 'review-plan',
|
|
@@ -1016,6 +1340,146 @@ async function runReviewPlan(repoRoot, options = {}) {
|
|
|
1016
1340
|
};
|
|
1017
1341
|
}
|
|
1018
1342
|
|
|
1343
|
+
async function runRepairPlan(repoRoot, options = {}) {
|
|
1344
|
+
const role = normalizeRole(options.role || DEFAULT_PLAN_ROLE);
|
|
1345
|
+
const provider = resolveProviderForProfile(repoRoot, role, options.provider, options.providerExplicit, DEFAULT_PLAN_PROVIDER);
|
|
1346
|
+
const context = options.context || DEFAULT_PLAN_CONTEXT;
|
|
1347
|
+
const timeoutMs = normalizeTimeout(options.timeout);
|
|
1348
|
+
const source = resolveApprovedTechnicalPlanForRepair(repoRoot, options.input || '');
|
|
1349
|
+
const built = buildRepairPlanContext({
|
|
1350
|
+
context,
|
|
1351
|
+
inputText: source.contents,
|
|
1352
|
+
inputPath: source.path,
|
|
1353
|
+
repoRoot,
|
|
1354
|
+
role,
|
|
1355
|
+
validationError: source.validationError,
|
|
1356
|
+
});
|
|
1357
|
+
assertProviderPromptWithinLimit(built.prompt, options);
|
|
1358
|
+
let invocation;
|
|
1359
|
+
|
|
1360
|
+
try {
|
|
1361
|
+
invocation = buildProviderInvocation(provider, {
|
|
1362
|
+
prompt: built.prompt,
|
|
1363
|
+
cwd: repoRoot,
|
|
1364
|
+
timeoutMs,
|
|
1365
|
+
});
|
|
1366
|
+
} catch (error) {
|
|
1367
|
+
throw annotateProviderError(error, 'repair-plan');
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
if (options.dryRun) {
|
|
1371
|
+
const report = {
|
|
1372
|
+
task: 'repair-plan',
|
|
1373
|
+
provider,
|
|
1374
|
+
role,
|
|
1375
|
+
contextPack: built.pack.packName,
|
|
1376
|
+
phase: 'technical-plan',
|
|
1377
|
+
invocation,
|
|
1378
|
+
};
|
|
1379
|
+
process.stdout.write(formatDryRunReport(report));
|
|
1380
|
+
process.stdout.write(`Source approved artifact: ${source.path}\n`);
|
|
1381
|
+
process.stdout.write(`Validation failure: ${source.validationError}\n`);
|
|
1382
|
+
return report;
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
if (options.printPrompt) {
|
|
1386
|
+
const report = {
|
|
1387
|
+
task: 'repair-plan',
|
|
1388
|
+
provider,
|
|
1389
|
+
role,
|
|
1390
|
+
contextPack: built.pack.packName,
|
|
1391
|
+
phase: 'technical-plan',
|
|
1392
|
+
invocation,
|
|
1393
|
+
prompt: built.prompt,
|
|
1394
|
+
inputPath: source.path,
|
|
1395
|
+
inputKind: 'approved',
|
|
1396
|
+
inputVersion: source.approval.meta?.approved?.version || null,
|
|
1397
|
+
};
|
|
1398
|
+
process.stdout.write(formatPromptOnlyReport(report));
|
|
1399
|
+
return report;
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
let providerResult;
|
|
1403
|
+
try {
|
|
1404
|
+
providerResult = await (options.runProviderFn || runProvider)(provider, {
|
|
1405
|
+
prompt: built.prompt,
|
|
1406
|
+
cwd: repoRoot,
|
|
1407
|
+
timeoutMs,
|
|
1408
|
+
dryRun: false,
|
|
1409
|
+
probe: options.probe,
|
|
1410
|
+
spawn: options.spawn,
|
|
1411
|
+
tempRoot: options.tempRoot,
|
|
1412
|
+
tempFileName: options.tempFileName,
|
|
1413
|
+
tempFilePrefix: options.tempFilePrefix,
|
|
1414
|
+
});
|
|
1415
|
+
} catch (error) {
|
|
1416
|
+
throw annotateProviderError(error, 'repair-plan');
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
if (!providerResult.ok) {
|
|
1420
|
+
writeProviderOutput(providerResult);
|
|
1421
|
+
throw annotateProviderError(providerResult.error || new Error('provider run failed'), 'repair-plan');
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
const lifecycleRun = ensureAiRun(repoRoot, {
|
|
1425
|
+
command: 'ai repair-plan',
|
|
1426
|
+
input: source.path,
|
|
1427
|
+
runId: options.runId,
|
|
1428
|
+
});
|
|
1429
|
+
const clean = extractCleanProviderOutput(providerResult, { prompt: built.prompt, projectRoot: repoRoot });
|
|
1430
|
+
const rawArtifact = writeRawProviderArtifact(repoRoot, lifecycleRun.run_id, 'ai-repair-plan', providerResult, {
|
|
1431
|
+
metadata: {
|
|
1432
|
+
phase: 'technical-plan-repair',
|
|
1433
|
+
input_path: source.path,
|
|
1434
|
+
prompt_bytes: invocation.promptLength,
|
|
1435
|
+
clean_output_source: clean.source,
|
|
1436
|
+
stripped_prompt_echo: clean.strippedPromptEcho,
|
|
1437
|
+
validation_failure: source.validationError,
|
|
1438
|
+
},
|
|
1439
|
+
});
|
|
1440
|
+
|
|
1441
|
+
try {
|
|
1442
|
+
validateTechnicalPlanSpecContract(repoRoot, {
|
|
1443
|
+
inputPath: source.path,
|
|
1444
|
+
inputText: clean.cleanOutput,
|
|
1445
|
+
});
|
|
1446
|
+
} catch (error) {
|
|
1447
|
+
throw new Error(formatError([
|
|
1448
|
+
'ai repair-plan provider output is still missing the required structured `spec.slices[]` contract.',
|
|
1449
|
+
stripCreateQuiverPrefix(error.message || error),
|
|
1450
|
+
`Raw provider artifact: ${rawArtifact.path}`,
|
|
1451
|
+
'No technical-plan draft was written.',
|
|
1452
|
+
].join('\n')));
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
writeCleanProviderOutput(clean);
|
|
1456
|
+
const draft = savePlannerDraft(repoRoot, 'technical-plan', source.path, clean.cleanOutput, {
|
|
1457
|
+
rawArtifactPath: rawArtifact.path,
|
|
1458
|
+
outputSource: clean.source,
|
|
1459
|
+
});
|
|
1460
|
+
updateAiRunPhase(repoRoot, lifecycleRun.run_id, 'technical-plan-draft', {
|
|
1461
|
+
artifact: path.relative(repoRoot, draft.filePath).split(path.sep).join('/'),
|
|
1462
|
+
command: 'ai repair-plan',
|
|
1463
|
+
});
|
|
1464
|
+
process.stdout.write(formatRepairPlanResult({
|
|
1465
|
+
...draft,
|
|
1466
|
+
sourcePath: source.path,
|
|
1467
|
+
}, repoRoot));
|
|
1468
|
+
|
|
1469
|
+
return {
|
|
1470
|
+
task: 'repair-plan',
|
|
1471
|
+
provider,
|
|
1472
|
+
role,
|
|
1473
|
+
contextPack: built.pack.packName,
|
|
1474
|
+
phase: 'technical-plan',
|
|
1475
|
+
inputPath: source.path,
|
|
1476
|
+
filePath: path.relative(repoRoot, draft.filePath).split(path.sep).join('/'),
|
|
1477
|
+
version: draft.version || null,
|
|
1478
|
+
invocation,
|
|
1479
|
+
result: providerResult,
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1019
1483
|
async function runRevise(repoRoot, options = {}) {
|
|
1020
1484
|
const phase = normalizePlannerPhase(options.phase || DEFAULT_PLAN_PHASE);
|
|
1021
1485
|
if (phase === 'spec') {
|
|
@@ -1051,8 +1515,14 @@ async function runApprove(repoRoot, options = {}) {
|
|
|
1051
1515
|
if (phase === 'technical-plan') {
|
|
1052
1516
|
const review = readPlanReview(repoRoot);
|
|
1053
1517
|
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\`.`));
|
|
1518
|
+
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 --dry-run\`, then \`npx create-quiver ai review-plan\`.`));
|
|
1055
1519
|
}
|
|
1520
|
+
if (reviewBlocksApproval(review)) {
|
|
1521
|
+
const result = review.meta.review_result;
|
|
1522
|
+
const requiredFixes = Array.isArray(result.required_fixes) ? result.required_fixes.length : 0;
|
|
1523
|
+
throw new Error(formatError(`ai approve --phase technical-plan is blocked by plan review; approval recommendation is ${result.approval_recommendation}. Required fixes: ${requiredFixes}. Next command: ${result.next_command}`));
|
|
1524
|
+
}
|
|
1525
|
+
assertTechnicalPlanDraftHasSpecContract(repoRoot, options.version);
|
|
1056
1526
|
}
|
|
1057
1527
|
|
|
1058
1528
|
const inputText = '';
|
|
@@ -1211,14 +1681,53 @@ function runTraceReport(repoRoot, options = {}) {
|
|
|
1211
1681
|
};
|
|
1212
1682
|
}
|
|
1213
1683
|
|
|
1684
|
+
function runActiveSlice(repoRoot, options = {}) {
|
|
1685
|
+
const command = String(options.command || 'status').trim().toLowerCase();
|
|
1686
|
+
if (command !== 'status' && command !== 'reconcile') {
|
|
1687
|
+
throw new Error(formatError(`unsupported ai active-slice subcommand: ${command}. Supported tasks: status, reconcile`));
|
|
1688
|
+
}
|
|
1689
|
+
if (command === 'reconcile' && options.dryRun !== true) {
|
|
1690
|
+
throw new Error(formatError('ai active-slice reconcile is dry-run first. Run `npx create-quiver ai active-slice reconcile --dry-run`.'));
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
const state = resolveProjectState(repoRoot, { allowGraphErrors: true });
|
|
1694
|
+
const report = collectActiveSliceState(repoRoot, { slices: state.graph.nodes });
|
|
1695
|
+
process.stdout.write(formatActiveSliceReconciliationReport(report, {
|
|
1696
|
+
dryRun: options.dryRun === true,
|
|
1697
|
+
}));
|
|
1698
|
+
return {
|
|
1699
|
+
task: 'active-slice',
|
|
1700
|
+
command,
|
|
1701
|
+
dryRun: options.dryRun === true,
|
|
1702
|
+
report,
|
|
1703
|
+
};
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1214
1706
|
function runLifecycleRun(repoRoot, options = {}) {
|
|
1215
1707
|
const command = String(options.command || '').trim().toLowerCase();
|
|
1216
|
-
if (command !== 'create') {
|
|
1217
|
-
throw new Error(formatError(`unsupported ai run subcommand: ${command}. Supported tasks: create`));
|
|
1708
|
+
if (command !== 'create' && command !== 'close') {
|
|
1709
|
+
throw new Error(formatError(`unsupported ai run subcommand: ${command}. Supported tasks: create, close`));
|
|
1218
1710
|
}
|
|
1219
|
-
if (!options.input) {
|
|
1711
|
+
if (command === 'create' && !options.input) {
|
|
1220
1712
|
throw new Error(formatError('ai run create requires --input <requirements.md>'));
|
|
1221
1713
|
}
|
|
1714
|
+
if (command === 'close') {
|
|
1715
|
+
const current = resolveAiRun(repoRoot, options.runId || '');
|
|
1716
|
+
if (!current) {
|
|
1717
|
+
throw new Error(formatError('ai run close requires an active run or --run <id>'));
|
|
1718
|
+
}
|
|
1719
|
+
const run = updateAiRunPhase(repoRoot, current.run_id, 'closed', {
|
|
1720
|
+
command: 'ai run close',
|
|
1721
|
+
});
|
|
1722
|
+
const report = `AI run closed\n${formatAiRunStatus(repoRoot, run)}`;
|
|
1723
|
+
process.stdout.write(report);
|
|
1724
|
+
return {
|
|
1725
|
+
task: 'run',
|
|
1726
|
+
command,
|
|
1727
|
+
run,
|
|
1728
|
+
report,
|
|
1729
|
+
};
|
|
1730
|
+
}
|
|
1222
1731
|
const run = createAiRun(repoRoot, {
|
|
1223
1732
|
command: 'ai run create',
|
|
1224
1733
|
input: options.input,
|
|
@@ -1261,6 +1770,21 @@ function formatAgentProfileList(profiles) {
|
|
|
1261
1770
|
return `${lines.join('\n')}\n`;
|
|
1262
1771
|
}
|
|
1263
1772
|
|
|
1773
|
+
function formatAgentProfileDryRun(repoRoot, result) {
|
|
1774
|
+
const relativePath = path.relative(repoRoot, result.filePath).split(path.sep).join('/');
|
|
1775
|
+
const verb = result.action === 'update' ? 'update' : 'create';
|
|
1776
|
+
return [
|
|
1777
|
+
'AI agent profile dry-run',
|
|
1778
|
+
'- Writes: none',
|
|
1779
|
+
`- Would ${verb}: ${relativePath}`,
|
|
1780
|
+
'',
|
|
1781
|
+
formatAgentProfile(result.profile).trimEnd(),
|
|
1782
|
+
'',
|
|
1783
|
+
'No secrets or provider credentials are stored in agent profiles.',
|
|
1784
|
+
'',
|
|
1785
|
+
].join('\n');
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1264
1788
|
function runAgent(repoRoot, options = {}) {
|
|
1265
1789
|
const command = String(options.command || '').trim().toLowerCase();
|
|
1266
1790
|
|
|
@@ -1271,6 +1795,22 @@ function runAgent(repoRoot, options = {}) {
|
|
|
1271
1795
|
if (!options.provider) {
|
|
1272
1796
|
throw new Error(formatError('ai agent set requires --provider. Supported providers: codex, claude, gemini.'));
|
|
1273
1797
|
}
|
|
1798
|
+
if (options.dryRun) {
|
|
1799
|
+
const preview = buildAgentProfileState(repoRoot, options.role, {
|
|
1800
|
+
context: options.context,
|
|
1801
|
+
label: options.label,
|
|
1802
|
+
model: options.model,
|
|
1803
|
+
provider: options.provider,
|
|
1804
|
+
});
|
|
1805
|
+
process.stdout.write(formatAgentProfileDryRun(repoRoot, preview));
|
|
1806
|
+
return {
|
|
1807
|
+
task: 'agent',
|
|
1808
|
+
command,
|
|
1809
|
+
dryRun: true,
|
|
1810
|
+
profile: preview.profile,
|
|
1811
|
+
filePath: path.relative(repoRoot, preview.filePath).split(path.sep).join('/'),
|
|
1812
|
+
};
|
|
1813
|
+
}
|
|
1274
1814
|
const result = setAgentProfile(repoRoot, options.role, {
|
|
1275
1815
|
context: options.context,
|
|
1276
1816
|
label: options.label,
|
|
@@ -1441,6 +1981,7 @@ module.exports = {
|
|
|
1441
1981
|
normalizeTimeout,
|
|
1442
1982
|
readTextFile,
|
|
1443
1983
|
runAgent,
|
|
1984
|
+
runActiveSlice,
|
|
1444
1985
|
runDoctor,
|
|
1445
1986
|
runExecutePlan,
|
|
1446
1987
|
runExecuteSlice,
|
|
@@ -1453,6 +1994,7 @@ module.exports = {
|
|
|
1453
1994
|
runApprove,
|
|
1454
1995
|
runApprovalStatus,
|
|
1455
1996
|
runPrepareContext,
|
|
1997
|
+
runRepairPlan,
|
|
1456
1998
|
runReviewPlan,
|
|
1457
1999
|
runRevise,
|
|
1458
2000
|
runPr,
|