create-quiver 0.15.1 → 0.15.3
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/.github/workflows/ci.yml +3 -0
- package/README.md +3 -0
- package/README_FOR_AI.md +11 -7
- package/docs/CLI_UX_GUIDE.md +16 -2
- package/docs/COMMANDS.md.template +16 -4
- package/docs/INDEX.md +87 -0
- package/docs/WORKFLOW.md.template +4 -4
- package/docs/reference/commands.md +23 -5
- package/package.json +3 -1
- package/specs/quiver-v33-approval-ux-and-planner-progress/EVIDENCE_REPORT.md +93 -0
- package/specs/quiver-v33-approval-ux-and-planner-progress/EXECUTION_PLAN.md +83 -0
- package/specs/quiver-v33-approval-ux-and-planner-progress/SPEC.md +158 -0
- package/specs/quiver-v33-approval-ux-and-planner-progress/STATUS.md +31 -0
- package/specs/quiver-v33-approval-ux-and-planner-progress/pr.md +109 -0
- package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-00-approval-ux-foundation/CLOSURE_BRIEF.md +30 -0
- package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-00-approval-ux-foundation/EXECUTION_BRIEF.md +56 -0
- package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-00-approval-ux-foundation/slice.json +65 -0
- package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-01-approval-candidates-model/CLOSURE_BRIEF.md +19 -0
- package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-01-approval-candidates-model/EXECUTION_BRIEF.md +51 -0
- package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-01-approval-candidates-model/slice.json +74 -0
- package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-02-approve-interactive-selection/CLOSURE_BRIEF.md +20 -0
- package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-02-approve-interactive-selection/EXECUTION_BRIEF.md +53 -0
- package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-02-approve-interactive-selection/slice.json +79 -0
- package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-03-technical-plan-review-decision-data/CLOSURE_BRIEF.md +19 -0
- package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-03-technical-plan-review-decision-data/EXECUTION_BRIEF.md +50 -0
- package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-03-technical-plan-review-decision-data/slice.json +75 -0
- package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-04-revise-input-guardrails/CLOSURE_BRIEF.md +19 -0
- package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-04-revise-input-guardrails/EXECUTION_BRIEF.md +51 -0
- package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-04-revise-input-guardrails/slice.json +73 -0
- package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-05-provider-progress-alignment/CLOSURE_BRIEF.md +20 -0
- package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-05-provider-progress-alignment/EXECUTION_BRIEF.md +51 -0
- package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-05-provider-progress-alignment/slice.json +91 -0
- package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-06-workflow-surface-integration/CLOSURE_BRIEF.md +20 -0
- package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-06-workflow-surface-integration/EXECUTION_BRIEF.md +53 -0
- package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-06-workflow-surface-integration/slice.json +87 -0
- package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-07-docs-tests-release-readiness/CLOSURE_BRIEF.md +21 -0
- package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-07-docs-tests-release-readiness/EXECUTION_BRIEF.md +55 -0
- package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-07-docs-tests-release-readiness/slice.json +97 -0
- package/specs/quiver-v34-cli-dashboard-status/EVIDENCE_REPORT.md +119 -0
- package/specs/quiver-v34-cli-dashboard-status/EXECUTION_PLAN.md +77 -0
- package/specs/quiver-v34-cli-dashboard-status/SPEC.md +152 -0
- package/specs/quiver-v34-cli-dashboard-status/STATUS.md +29 -0
- package/specs/quiver-v34-cli-dashboard-status/pr.md +99 -0
- package/specs/quiver-v34-cli-dashboard-status/slices/slice-00-dashboard-foundation/CLOSURE_BRIEF.md +19 -0
- package/specs/quiver-v34-cli-dashboard-status/slices/slice-00-dashboard-foundation/EXECUTION_BRIEF.md +49 -0
- package/specs/quiver-v34-cli-dashboard-status/slices/slice-00-dashboard-foundation/slice.json +68 -0
- package/specs/quiver-v34-cli-dashboard-status/slices/slice-01-dashboard-report-contract/CLOSURE_BRIEF.md +19 -0
- package/specs/quiver-v34-cli-dashboard-status/slices/slice-01-dashboard-report-contract/EXECUTION_BRIEF.md +53 -0
- package/specs/quiver-v34-cli-dashboard-status/slices/slice-01-dashboard-report-contract/slice.json +78 -0
- package/specs/quiver-v34-cli-dashboard-status/slices/slice-02-dashboard-command-rendering/CLOSURE_BRIEF.md +19 -0
- package/specs/quiver-v34-cli-dashboard-status/slices/slice-02-dashboard-command-rendering/EXECUTION_BRIEF.md +53 -0
- package/specs/quiver-v34-cli-dashboard-status/slices/slice-02-dashboard-command-rendering/slice.json +82 -0
- package/specs/quiver-v34-cli-dashboard-status/slices/slice-03-dashboard-edge-cases-and-guardrails/CLOSURE_BRIEF.md +19 -0
- package/specs/quiver-v34-cli-dashboard-status/slices/slice-03-dashboard-edge-cases-and-guardrails/EXECUTION_BRIEF.md +54 -0
- package/specs/quiver-v34-cli-dashboard-status/slices/slice-03-dashboard-edge-cases-and-guardrails/slice.json +77 -0
- package/specs/quiver-v34-cli-dashboard-status/slices/slice-04-docs-templates-and-scripts/CLOSURE_BRIEF.md +19 -0
- package/specs/quiver-v34-cli-dashboard-status/slices/slice-04-docs-templates-and-scripts/EXECUTION_BRIEF.md +54 -0
- package/specs/quiver-v34-cli-dashboard-status/slices/slice-04-docs-templates-and-scripts/slice.json +88 -0
- package/specs/quiver-v34-cli-dashboard-status/slices/slice-05-tests-smokes-release-readiness/CLOSURE_BRIEF.md +26 -0
- package/specs/quiver-v34-cli-dashboard-status/slices/slice-05-tests-smokes-release-readiness/EXECUTION_BRIEF.md +57 -0
- package/specs/quiver-v34-cli-dashboard-status/slices/slice-05-tests-smokes-release-readiness/slice.json +85 -0
- package/specs/quiver-v35-compact-dashboard-version-ux/EVIDENCE_REPORT.md +43 -0
- package/specs/quiver-v35-compact-dashboard-version-ux/EXECUTION_PLAN.md +85 -0
- package/specs/quiver-v35-compact-dashboard-version-ux/SPEC.md +147 -0
- package/specs/quiver-v35-compact-dashboard-version-ux/STATUS.md +33 -0
- package/specs/quiver-v35-compact-dashboard-version-ux/pr.md +106 -0
- package/specs/quiver-v35-compact-dashboard-version-ux/slices/slice-00-foundation-and-doc-router/CLOSURE_BRIEF.md +19 -0
- package/specs/quiver-v35-compact-dashboard-version-ux/slices/slice-00-foundation-and-doc-router/EXECUTION_BRIEF.md +38 -0
- package/specs/quiver-v35-compact-dashboard-version-ux/slices/slice-00-foundation-and-doc-router/slice.json +64 -0
- package/specs/quiver-v35-compact-dashboard-version-ux/slices/slice-01-dashboard-cli-contract/CLOSURE_BRIEF.md +19 -0
- package/specs/quiver-v35-compact-dashboard-version-ux/slices/slice-01-dashboard-cli-contract/EXECUTION_BRIEF.md +49 -0
- package/specs/quiver-v35-compact-dashboard-version-ux/slices/slice-01-dashboard-cli-contract/slice.json +80 -0
- package/specs/quiver-v35-compact-dashboard-version-ux/slices/slice-02-dashboard-compact-renderer/CLOSURE_BRIEF.md +19 -0
- package/specs/quiver-v35-compact-dashboard-version-ux/slices/slice-02-dashboard-compact-renderer/EXECUTION_BRIEF.md +48 -0
- package/specs/quiver-v35-compact-dashboard-version-ux/slices/slice-02-dashboard-compact-renderer/slice.json +73 -0
- package/specs/quiver-v35-compact-dashboard-version-ux/slices/slice-03-dashboard-details-sections/CLOSURE_BRIEF.md +19 -0
- package/specs/quiver-v35-compact-dashboard-version-ux/slices/slice-03-dashboard-details-sections/EXECUTION_BRIEF.md +48 -0
- package/specs/quiver-v35-compact-dashboard-version-ux/slices/slice-03-dashboard-details-sections/slice.json +72 -0
- package/specs/quiver-v35-compact-dashboard-version-ux/slices/slice-04-version-command/CLOSURE_BRIEF.md +19 -0
- package/specs/quiver-v35-compact-dashboard-version-ux/slices/slice-04-version-command/EXECUTION_BRIEF.md +49 -0
- package/specs/quiver-v35-compact-dashboard-version-ux/slices/slice-04-version-command/slice.json +82 -0
- package/specs/quiver-v35-compact-dashboard-version-ux/slices/slice-05-docs-help-generated-guidance/CLOSURE_BRIEF.md +19 -0
- package/specs/quiver-v35-compact-dashboard-version-ux/slices/slice-05-docs-help-generated-guidance/EXECUTION_BRIEF.md +50 -0
- package/specs/quiver-v35-compact-dashboard-version-ux/slices/slice-05-docs-help-generated-guidance/slice.json +90 -0
- package/specs/quiver-v35-compact-dashboard-version-ux/slices/slice-06-tests-smokes-release-readiness/CLOSURE_BRIEF.md +21 -0
- package/specs/quiver-v35-compact-dashboard-version-ux/slices/slice-06-tests-smokes-release-readiness/EXECUTION_BRIEF.md +51 -0
- package/specs/quiver-v35-compact-dashboard-version-ux/slices/slice-06-tests-smokes-release-readiness/slice.json +85 -0
- package/specs/quiver-v35-compact-dashboard-version-ux/slices/slice-07-package-and-cross-platform-smoke/CLOSURE_BRIEF.md +20 -0
- package/specs/quiver-v35-compact-dashboard-version-ux/slices/slice-07-package-and-cross-platform-smoke/EXECUTION_BRIEF.md +49 -0
- package/specs/quiver-v35-compact-dashboard-version-ux/slices/slice-07-package-and-cross-platform-smoke/slice.json +79 -0
- package/specs/quiver-v36-ai-run-watch-portable/EVIDENCE_REPORT.md +33 -0
- package/specs/quiver-v36-ai-run-watch-portable/EXECUTION_PLAN.md +39 -0
- package/specs/quiver-v36-ai-run-watch-portable/SPEC.md +166 -0
- package/specs/quiver-v36-ai-run-watch-portable/STATUS.md +31 -0
- package/specs/quiver-v36-ai-run-watch-portable/pr.md +78 -0
- package/specs/quiver-v36-ai-run-watch-portable/slices/slice-00-foundation-and-handoffs/CLOSURE_BRIEF.md +19 -0
- package/specs/quiver-v36-ai-run-watch-portable/slices/slice-00-foundation-and-handoffs/EXECUTION_BRIEF.md +43 -0
- package/specs/quiver-v36-ai-run-watch-portable/slices/slice-00-foundation-and-handoffs/slice.json +61 -0
- package/specs/quiver-v36-ai-run-watch-portable/slices/slice-01-run-schema-path-safety/CLOSURE_BRIEF.md +18 -0
- package/specs/quiver-v36-ai-run-watch-portable/slices/slice-01-run-schema-path-safety/EXECUTION_BRIEF.md +43 -0
- package/specs/quiver-v36-ai-run-watch-portable/slices/slice-01-run-schema-path-safety/slice.json +65 -0
- package/specs/quiver-v36-ai-run-watch-portable/slices/slice-02-event-writer-redacted-logs/CLOSURE_BRIEF.md +18 -0
- package/specs/quiver-v36-ai-run-watch-portable/slices/slice-02-event-writer-redacted-logs/EXECUTION_BRIEF.md +43 -0
- package/specs/quiver-v36-ai-run-watch-portable/slices/slice-02-event-writer-redacted-logs/slice.json +66 -0
- package/specs/quiver-v36-ai-run-watch-portable/slices/slice-03-provider-streaming-integration/CLOSURE_BRIEF.md +18 -0
- package/specs/quiver-v36-ai-run-watch-portable/slices/slice-03-provider-streaming-integration/EXECUTION_BRIEF.md +41 -0
- package/specs/quiver-v36-ai-run-watch-portable/slices/slice-03-provider-streaming-integration/slice.json +71 -0
- package/specs/quiver-v36-ai-run-watch-portable/slices/slice-04-ai-run-watch-command/CLOSURE_BRIEF.md +18 -0
- package/specs/quiver-v36-ai-run-watch-portable/slices/slice-04-ai-run-watch-command/EXECUTION_BRIEF.md +44 -0
- package/specs/quiver-v36-ai-run-watch-portable/slices/slice-04-ai-run-watch-command/slice.json +73 -0
- package/specs/quiver-v36-ai-run-watch-portable/slices/slice-05-main-progress-user-guidance/CLOSURE_BRIEF.md +18 -0
- package/specs/quiver-v36-ai-run-watch-portable/slices/slice-05-main-progress-user-guidance/EXECUTION_BRIEF.md +44 -0
- package/specs/quiver-v36-ai-run-watch-portable/slices/slice-05-main-progress-user-guidance/slice.json +71 -0
- package/specs/quiver-v36-ai-run-watch-portable/slices/slice-06-docs-generated-guidance/CLOSURE_BRIEF.md +19 -0
- package/specs/quiver-v36-ai-run-watch-portable/slices/slice-06-docs-generated-guidance/EXECUTION_BRIEF.md +45 -0
- package/specs/quiver-v36-ai-run-watch-portable/slices/slice-06-docs-generated-guidance/slice.json +77 -0
- package/specs/quiver-v36-ai-run-watch-portable/slices/slice-07-tests-cross-platform-readiness/CLOSURE_BRIEF.md +22 -0
- package/specs/quiver-v36-ai-run-watch-portable/slices/slice-07-tests-cross-platform-readiness/EXECUTION_BRIEF.md +46 -0
- package/specs/quiver-v36-ai-run-watch-portable/slices/slice-07-tests-cross-platform-readiness/slice.json +87 -0
- package/src/create-quiver/commands/ai.js +112 -12
- package/src/create-quiver/commands/dashboard.js +31 -0
- package/src/create-quiver/commands/flow.js +74 -12
- package/src/create-quiver/commands/spec.js +8 -1
- package/src/create-quiver/index.js +107 -4
- package/src/create-quiver/lib/ai/approval-candidates.js +86 -0
- package/src/create-quiver/lib/ai/plan-review.js +77 -1
- package/src/create-quiver/lib/ai/run-state.js +21 -4
- package/src/create-quiver/lib/approvals.js +103 -0
- package/src/create-quiver/lib/cli/ux-flags.js +6 -0
- package/src/create-quiver/lib/dashboard.js +896 -0
- package/src/create-quiver/lib/git.js +13 -4
- package/src/create-quiver/lib/init-docs.js +6 -0
- package/src/create-quiver/lib/init-layout.js +2 -0
- package/src/create-quiver/lib/paths.js +45 -3
- package/src/create-quiver/lib/project-state-resolver.js +22 -2
- package/src/create-quiver/lib/version.js +157 -0
|
@@ -2,6 +2,7 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
|
|
4
4
|
const { readPhaseApproval } = require('../lib/approvals');
|
|
5
|
+
const { approvalCandidateCommand, buildApprovalCandidateReport, formatCandidateSummary } = require('../lib/ai/approval-candidates');
|
|
5
6
|
const { readPlanReview } = require('../lib/ai/plan-review');
|
|
6
7
|
const { listAgentProfiles } = require('../lib/agent-profiles');
|
|
7
8
|
const { readProjectScanStatus } = require('../lib/project-scan');
|
|
@@ -89,6 +90,25 @@ function safeReadPlanReview(projectRoot) {
|
|
|
89
90
|
}
|
|
90
91
|
}
|
|
91
92
|
|
|
93
|
+
function safeBuildApprovalCandidateReport(projectRoot, phase) {
|
|
94
|
+
try {
|
|
95
|
+
return buildApprovalCandidateReport(projectRoot, phase);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
return {
|
|
98
|
+
phase,
|
|
99
|
+
approval_status: 'invalid',
|
|
100
|
+
latest_version: null,
|
|
101
|
+
current: null,
|
|
102
|
+
recommended: null,
|
|
103
|
+
candidates: [],
|
|
104
|
+
history: [],
|
|
105
|
+
approved: null,
|
|
106
|
+
next_command: 'npx create-quiver ai approvals',
|
|
107
|
+
error: error.message,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
92
112
|
function summarizeDocs(projectRoot) {
|
|
93
113
|
const docs = {
|
|
94
114
|
hasProjectMap: exists(projectRoot, 'docs/PROJECT_MAP.md'),
|
|
@@ -250,11 +270,23 @@ function detectFlowState(projectRoot) {
|
|
|
250
270
|
acceptance: safeReadApproval(projectRoot, 'acceptance'),
|
|
251
271
|
technicalPlan: safeReadApproval(projectRoot, 'technical-plan'),
|
|
252
272
|
};
|
|
273
|
+
const approvalCandidates = {
|
|
274
|
+
acceptance: safeBuildApprovalCandidateReport(projectRoot, 'acceptance'),
|
|
275
|
+
technicalPlan: safeBuildApprovalCandidateReport(projectRoot, 'technical-plan'),
|
|
276
|
+
};
|
|
253
277
|
const planReview = safeReadPlanReview(projectRoot);
|
|
254
278
|
const agents = summarizeAgentProfiles(projectRoot);
|
|
255
279
|
const packageManager = detectPackageManager(projectRoot);
|
|
256
280
|
const specSlugs = listSpecSlugs(projectRoot);
|
|
257
281
|
const facts = buildFacts({ initialized, docs, approvals, planReview, agents, packageManager, specSlugs, state });
|
|
282
|
+
const acceptanceApprovalCommand = approvalCandidateCommand(
|
|
283
|
+
approvalCandidates.acceptance,
|
|
284
|
+
'npx create-quiver ai approve --phase acceptance --version <n>',
|
|
285
|
+
);
|
|
286
|
+
const technicalPlanApprovalCommand = approvalCandidateCommand(
|
|
287
|
+
approvalCandidates.technicalPlan,
|
|
288
|
+
'npx create-quiver ai approve --phase technical-plan --version <n>',
|
|
289
|
+
);
|
|
258
290
|
|
|
259
291
|
if (!initialized) {
|
|
260
292
|
return baseReport({
|
|
@@ -334,12 +366,12 @@ function detectFlowState(projectRoot) {
|
|
|
334
366
|
return baseReport({
|
|
335
367
|
stage: 'criteria-draft',
|
|
336
368
|
label: 'acceptance criteria need approval',
|
|
337
|
-
blockers: ['Acceptance criteria draft exists but is not approved.'],
|
|
338
|
-
nextCommand:
|
|
369
|
+
blockers: ['Acceptance criteria draft exists but is not approved.', formatCandidateSummary(approvalCandidates.acceptance.current)].filter(Boolean),
|
|
370
|
+
nextCommand: acceptanceApprovalCommand,
|
|
339
371
|
suggestedCommands: [
|
|
340
372
|
'npx create-quiver ai approvals',
|
|
341
373
|
'npx create-quiver ai revise --phase acceptance --input feedback.md --dry-run',
|
|
342
|
-
|
|
374
|
+
acceptanceApprovalCommand,
|
|
343
375
|
],
|
|
344
376
|
facts,
|
|
345
377
|
});
|
|
@@ -349,12 +381,12 @@ function detectFlowState(projectRoot) {
|
|
|
349
381
|
return baseReport({
|
|
350
382
|
stage: 'criteria-stale',
|
|
351
383
|
label: 'acceptance criteria approval is stale',
|
|
352
|
-
blockers: ['Acceptance criteria changed after approval.'],
|
|
353
|
-
nextCommand:
|
|
384
|
+
blockers: ['Acceptance criteria changed after approval.', formatCandidateSummary(approvalCandidates.acceptance.current)].filter(Boolean),
|
|
385
|
+
nextCommand: acceptanceApprovalCommand,
|
|
354
386
|
suggestedCommands: [
|
|
355
387
|
'npx create-quiver ai approvals',
|
|
356
388
|
'npx create-quiver ai revise --phase acceptance --input feedback.md --dry-run',
|
|
357
|
-
|
|
389
|
+
acceptanceApprovalCommand,
|
|
358
390
|
],
|
|
359
391
|
facts,
|
|
360
392
|
});
|
|
@@ -390,14 +422,29 @@ function detectFlowState(projectRoot) {
|
|
|
390
422
|
});
|
|
391
423
|
}
|
|
392
424
|
|
|
425
|
+
if (!approvalCandidates.technicalPlan.current?.approvable) {
|
|
426
|
+
return baseReport({
|
|
427
|
+
stage: 'technical-plan-review-blocked',
|
|
428
|
+
label: 'technical plan review requires revision',
|
|
429
|
+
blockers: ['Technical plan review blocks approval.', formatCandidateSummary(approvalCandidates.technicalPlan.current)].filter(Boolean),
|
|
430
|
+
nextCommand: approvalCandidates.technicalPlan.current?.next_command || 'npx create-quiver ai revise --phase technical-plan --input <feedback.md> --dry-run',
|
|
431
|
+
suggestedCommands: [
|
|
432
|
+
'npx create-quiver ai approvals',
|
|
433
|
+
approvalCandidates.technicalPlan.current?.next_command || 'npx create-quiver ai revise --phase technical-plan --input <feedback.md> --dry-run',
|
|
434
|
+
'npx create-quiver ai review-plan --dry-run',
|
|
435
|
+
],
|
|
436
|
+
facts,
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
|
|
393
440
|
return baseReport({
|
|
394
441
|
stage: 'technical-plan-draft',
|
|
395
442
|
label: 'technical plan needs approval',
|
|
396
|
-
blockers: ['Technical plan draft was reviewed but is not approved.'],
|
|
397
|
-
nextCommand:
|
|
443
|
+
blockers: ['Technical plan draft was reviewed but is not approved.', formatCandidateSummary(approvalCandidates.technicalPlan.current)].filter(Boolean),
|
|
444
|
+
nextCommand: technicalPlanApprovalCommand,
|
|
398
445
|
suggestedCommands: [
|
|
399
446
|
'npx create-quiver ai approvals',
|
|
400
|
-
|
|
447
|
+
technicalPlanApprovalCommand,
|
|
401
448
|
],
|
|
402
449
|
facts,
|
|
403
450
|
});
|
|
@@ -419,14 +466,29 @@ function detectFlowState(projectRoot) {
|
|
|
419
466
|
});
|
|
420
467
|
}
|
|
421
468
|
|
|
469
|
+
if (!approvalCandidates.technicalPlan.current?.approvable) {
|
|
470
|
+
return baseReport({
|
|
471
|
+
stage: 'technical-plan-review-blocked',
|
|
472
|
+
label: 'technical plan review requires revision',
|
|
473
|
+
blockers: ['Technical plan changed after approval and latest review blocks approval.', formatCandidateSummary(approvalCandidates.technicalPlan.current)].filter(Boolean),
|
|
474
|
+
nextCommand: approvalCandidates.technicalPlan.current?.next_command || 'npx create-quiver ai revise --phase technical-plan --input <feedback.md> --dry-run',
|
|
475
|
+
suggestedCommands: [
|
|
476
|
+
'npx create-quiver ai approvals',
|
|
477
|
+
approvalCandidates.technicalPlan.current?.next_command || 'npx create-quiver ai revise --phase technical-plan --input <feedback.md> --dry-run',
|
|
478
|
+
'npx create-quiver ai review-plan --dry-run',
|
|
479
|
+
],
|
|
480
|
+
facts,
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
|
|
422
484
|
return baseReport({
|
|
423
485
|
stage: 'technical-plan-stale',
|
|
424
486
|
label: 'technical plan approval is stale',
|
|
425
|
-
blockers: ['Technical plan changed after approval and the latest draft was reviewed.'],
|
|
426
|
-
nextCommand:
|
|
487
|
+
blockers: ['Technical plan changed after approval and the latest draft was reviewed.', formatCandidateSummary(approvalCandidates.technicalPlan.current)].filter(Boolean),
|
|
488
|
+
nextCommand: technicalPlanApprovalCommand,
|
|
427
489
|
suggestedCommands: [
|
|
428
490
|
'npx create-quiver ai approvals',
|
|
429
|
-
|
|
491
|
+
technicalPlanApprovalCommand,
|
|
430
492
|
],
|
|
431
493
|
facts,
|
|
432
494
|
});
|
|
@@ -7,6 +7,7 @@ const { selectOption } = require('../lib/cli/selectors');
|
|
|
7
7
|
const { createUx } = require('../lib/cli/ux');
|
|
8
8
|
const { parseJsonWithComments } = require('../lib/json');
|
|
9
9
|
const { assertPathInsideRoot, validateProjectRelativePaths } = require('../lib/paths');
|
|
10
|
+
const { buildApprovalCandidateReport, formatReviewSummary } = require('../lib/ai/approval-candidates');
|
|
10
11
|
const { resolveReviewedTechnicalPlanInput } = require('../lib/ai/plan-review');
|
|
11
12
|
const {
|
|
12
13
|
buildSpecGenerationManifest,
|
|
@@ -439,6 +440,11 @@ async function resolveInteractiveSpecCreateOptions(repoRoot, preview, options =
|
|
|
439
440
|
stdinIsTTY: options.stdinIsTTY,
|
|
440
441
|
stdoutIsTTY: options.stdoutIsTTY,
|
|
441
442
|
};
|
|
443
|
+
const technicalPlanCandidates = buildApprovalCandidateReport(repoRoot, 'technical-plan');
|
|
444
|
+
const approvedVersion = technicalPlanCandidates.approved?.version
|
|
445
|
+
? `v${technicalPlanCandidates.approved.version}`
|
|
446
|
+
: 'approved';
|
|
447
|
+
const reviewSummary = formatReviewSummary(technicalPlanCandidates.review);
|
|
442
448
|
const selectedMethodology = await selectOption('¿Qué metodología aplica esta spec?', SPEC_METHODOLOGY_OPTIONS, {
|
|
443
449
|
...selectorOptions,
|
|
444
450
|
defaultValue: 'wdd-sdd',
|
|
@@ -450,7 +456,7 @@ async function resolveInteractiveSpecCreateOptions(repoRoot, preview, options =
|
|
|
450
456
|
{
|
|
451
457
|
label: preview.inputPath,
|
|
452
458
|
value: preview.inputPath,
|
|
453
|
-
hint: 'Plan técnico revisado y aprobado',
|
|
459
|
+
hint: ['Plan técnico revisado y aprobado', approvedVersion, reviewSummary].filter(Boolean).join(', '),
|
|
454
460
|
default: true,
|
|
455
461
|
},
|
|
456
462
|
], {
|
|
@@ -484,6 +490,7 @@ async function resolveInteractiveSpecCreateOptions(repoRoot, preview, options =
|
|
|
484
490
|
{ label: 'Spec', value: preview.manifest.slug },
|
|
485
491
|
{ label: 'Metodologia', value: selectedMethodology.label },
|
|
486
492
|
{ label: 'Input', value: selectedInput.value },
|
|
493
|
+
{ label: 'Plan tecnico', value: ['aprobado', approvedVersion, reviewSummary].filter(Boolean).join(', ') },
|
|
487
494
|
{ label: 'Target', value: preview.relativeSpecDir },
|
|
488
495
|
{ label: 'Archivos planificados', value: preview.preview.files.length },
|
|
489
496
|
{ label: 'Revision', value: selectedReview.value === 'review' ? 'abrir preview' : 'confirmacion directa' },
|
|
@@ -35,6 +35,7 @@ const {
|
|
|
35
35
|
runSpecsList: runAiSpecsList,
|
|
36
36
|
runTraceReport: runAiTraceReport,
|
|
37
37
|
} = require('./commands/ai');
|
|
38
|
+
const { runDashboard } = require('./commands/dashboard');
|
|
38
39
|
const { runDemo } = require('./commands/demo');
|
|
39
40
|
const { runPrepare } = require('./commands/prepare');
|
|
40
41
|
const { runEvidence } = require('./commands/evidence');
|
|
@@ -43,6 +44,7 @@ const { runGraph } = require('./commands/graph');
|
|
|
43
44
|
const { runNext } = require('./commands/next');
|
|
44
45
|
const { runPlan } = require('./commands/plan');
|
|
45
46
|
const { runCreateSpec, runValidateSpec } = require('./commands/spec');
|
|
47
|
+
const { collectVersionReport, formatHumanVersionReport } = require('./lib/version');
|
|
46
48
|
const { buildInitLayout, formatInitLayoutPlan } = require('./lib/init-layout');
|
|
47
49
|
const {
|
|
48
50
|
formatInstallSelfCommand,
|
|
@@ -82,7 +84,9 @@ function formatError(message) {
|
|
|
82
84
|
|
|
83
85
|
const SUPPORTED_COMMAND_MODES = new Set([
|
|
84
86
|
'init',
|
|
87
|
+
'version',
|
|
85
88
|
'flow',
|
|
89
|
+
'dashboard',
|
|
86
90
|
'plan',
|
|
87
91
|
'graph',
|
|
88
92
|
'next',
|
|
@@ -152,6 +156,8 @@ const COMMAND_HELP_GROUPS = [
|
|
|
152
156
|
['analyze', 'Scan the project and write docs/PROJECT_MAP.md plus .quiver scan data.'],
|
|
153
157
|
['doctor', 'Validate the Quiver layout, generated docs, environment, and next safe steps.'],
|
|
154
158
|
['flow', 'Show the read-only guided workflow stage, blockers, and next safe command.'],
|
|
159
|
+
['dashboard', 'Show compact read-only project, spec, slice, run, approval, and agent status.'],
|
|
160
|
+
['version', 'Show a Quiver-branded version report; use --json for metadata.'],
|
|
155
161
|
['prepare', 'Run setup diagnostics for providers, GitHub, SSH, and project readiness.'],
|
|
156
162
|
['migrate', 'Upgrade an already initialized Quiver project to the current contract.'],
|
|
157
163
|
],
|
|
@@ -251,6 +257,8 @@ function printUsage() {
|
|
|
251
257
|
npx create-quiver init [options]
|
|
252
258
|
npx create-quiver analyze [options]
|
|
253
259
|
npx create-quiver flow [options]
|
|
260
|
+
npx create-quiver dashboard [options]
|
|
261
|
+
npx create-quiver version [--json]
|
|
254
262
|
npx create-quiver plan [options]
|
|
255
263
|
npx create-quiver ai <task> [options]
|
|
256
264
|
npx create-quiver ai run create --input <requirements.md>
|
|
@@ -294,12 +302,15 @@ ${formatCommandHelpGroups()}
|
|
|
294
302
|
Options:
|
|
295
303
|
-n, --name <project-name> Project name to generate
|
|
296
304
|
-d, --dir <target-dir> Target directory to scaffold into or inspect
|
|
297
|
-
--spec <slug> Restrict plan output to one spec
|
|
305
|
+
--spec <slug> Restrict plan, graph, next, or dashboard output to one spec
|
|
298
306
|
--format <name> Graph or AI export output format (tree, mermaid, dot, json, markdown)
|
|
299
307
|
--show-conflicts Show shared file paths in graph output
|
|
300
308
|
--level <n> Restrict graph output to one level
|
|
301
309
|
--json Emit machine-readable JSON
|
|
302
|
-
--include-completed Include completed slices in plan, graph, or next history output
|
|
310
|
+
--include-completed Include completed slices in dashboard, plan, graph, or next history output
|
|
311
|
+
--details Show the full human dashboard report
|
|
312
|
+
--section <name> Show one human dashboard section
|
|
313
|
+
--limit <n> Limit dashboard human lists (1-100)
|
|
303
314
|
--only-ready Show only slices with no pending dependencies
|
|
304
315
|
--all-ready List every ready slice returned by next
|
|
305
316
|
--auto-start Prompt for confirmation and run start-slice on next
|
|
@@ -345,6 +356,11 @@ Examples:
|
|
|
345
356
|
npx create-quiver --name "My Project"
|
|
346
357
|
npx create-quiver --name "My Project" --dir ./my-project
|
|
347
358
|
cd ./my-project && npx create-quiver flow
|
|
359
|
+
cd ./my-project && npx create-quiver dashboard
|
|
360
|
+
cd ./my-project && npx create-quiver dashboard --section slices --limit 10
|
|
361
|
+
cd ./my-project && npx create-quiver dashboard --json
|
|
362
|
+
cd ./my-project && npx create-quiver version
|
|
363
|
+
cd ./my-project && npx create-quiver version --json
|
|
348
364
|
cd ./my-project && npx create-quiver analyze
|
|
349
365
|
cd ./my-project && npx create-quiver plan --json
|
|
350
366
|
cd ./my-project && npx create-quiver ai onboard --dry-run
|
|
@@ -437,6 +453,10 @@ function parseArgs(argv) {
|
|
|
437
453
|
interactive: false,
|
|
438
454
|
review: false,
|
|
439
455
|
includeCompleted: false,
|
|
456
|
+
dashboardDetails: false,
|
|
457
|
+
dashboardSection: '',
|
|
458
|
+
dashboardLimit: null,
|
|
459
|
+
dashboardOptionErrors: [],
|
|
440
460
|
onlyReady: false,
|
|
441
461
|
allReady: false,
|
|
442
462
|
autoStart: false,
|
|
@@ -525,6 +545,14 @@ function parseArgs(argv) {
|
|
|
525
545
|
|
|
526
546
|
const positional = [];
|
|
527
547
|
|
|
548
|
+
function missingInputValueMessage() {
|
|
549
|
+
const aiCommandHint = result.mode === 'ai' ? positional[0] : '';
|
|
550
|
+
if (aiCommandHint === 'revise') {
|
|
551
|
+
return `missing feedback input file for ai revise --phase ${result.aiPhase}. Use: npx create-quiver ai revise --phase ${result.aiPhase} --input <feedback.md> --dry-run`;
|
|
552
|
+
}
|
|
553
|
+
return 'missing value for --input';
|
|
554
|
+
}
|
|
555
|
+
|
|
528
556
|
for (let index = 0; index < args.length; index += 1) {
|
|
529
557
|
const arg = args[index];
|
|
530
558
|
|
|
@@ -692,6 +720,33 @@ function parseArgs(argv) {
|
|
|
692
720
|
continue;
|
|
693
721
|
}
|
|
694
722
|
|
|
723
|
+
if (arg === '--details') {
|
|
724
|
+
result.dashboardDetails = true;
|
|
725
|
+
continue;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
if (arg === '--section') {
|
|
729
|
+
const value = args[index + 1];
|
|
730
|
+
if (!value || String(value).startsWith('--')) {
|
|
731
|
+
result.dashboardOptionErrors.push('missing value for --section');
|
|
732
|
+
continue;
|
|
733
|
+
}
|
|
734
|
+
result.dashboardSection = value;
|
|
735
|
+
index += 1;
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
if (arg === '--limit') {
|
|
740
|
+
const value = args[index + 1];
|
|
741
|
+
if (!value || String(value).startsWith('--')) {
|
|
742
|
+
result.dashboardOptionErrors.push('missing value for --limit');
|
|
743
|
+
continue;
|
|
744
|
+
}
|
|
745
|
+
result.dashboardLimit = value;
|
|
746
|
+
index += 1;
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
|
|
695
750
|
if (arg === '--show-conflicts') {
|
|
696
751
|
result.showConflicts = true;
|
|
697
752
|
continue;
|
|
@@ -875,8 +930,8 @@ function parseArgs(argv) {
|
|
|
875
930
|
|
|
876
931
|
if (arg === '--input') {
|
|
877
932
|
const value = args[++index];
|
|
878
|
-
if (!value) {
|
|
879
|
-
throw new Error(formatError(
|
|
933
|
+
if (!value || String(value).startsWith('--')) {
|
|
934
|
+
throw new Error(formatError(missingInputValueMessage()));
|
|
880
935
|
}
|
|
881
936
|
result.aiInput = value;
|
|
882
937
|
continue;
|
|
@@ -1041,6 +1096,14 @@ function parseArgs(argv) {
|
|
|
1041
1096
|
if (positional.length > 0) {
|
|
1042
1097
|
throw new Error(formatError('flow does not accept positional arguments'));
|
|
1043
1098
|
}
|
|
1099
|
+
} else if (result.mode === 'version') {
|
|
1100
|
+
if (positional.length > 0) {
|
|
1101
|
+
throw new Error(formatError('version does not accept positional arguments'));
|
|
1102
|
+
}
|
|
1103
|
+
} else if (result.mode === 'dashboard') {
|
|
1104
|
+
if (positional.length > 0) {
|
|
1105
|
+
throw new Error(formatError('dashboard does not accept positional arguments; use --spec <slug>'));
|
|
1106
|
+
}
|
|
1044
1107
|
} else if (result.mode === 'ai') {
|
|
1045
1108
|
if (!result.aiCommand && positional.length > 0) {
|
|
1046
1109
|
result.aiCommand = positional.shift();
|
|
@@ -1134,6 +1197,14 @@ function parseArgs(argv) {
|
|
|
1134
1197
|
throw new Error(formatError('too many positional arguments'));
|
|
1135
1198
|
}
|
|
1136
1199
|
|
|
1200
|
+
const requestedDashboardFlags = [];
|
|
1201
|
+
if (result.dashboardDetails) requestedDashboardFlags.push('--details');
|
|
1202
|
+
if (result.dashboardSection || result.dashboardOptionErrors.some((error) => error.includes('--section'))) requestedDashboardFlags.push('--section');
|
|
1203
|
+
if (result.dashboardLimit !== null || result.dashboardOptionErrors.some((error) => error.includes('--limit'))) requestedDashboardFlags.push('--limit');
|
|
1204
|
+
if (result.mode !== 'dashboard' && requestedDashboardFlags.length > 0) {
|
|
1205
|
+
throw new Error(formatError(`${requestedDashboardFlags.join(', ')} ${requestedDashboardFlags.length === 1 ? 'is' : 'are'} only supported by dashboard. Use: npx create-quiver dashboard ${requestedDashboardFlags[0]}${requestedDashboardFlags[0] === '--section' ? ' <name>' : requestedDashboardFlags[0] === '--limit' ? ' <n>' : ''}`));
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1137
1208
|
return result;
|
|
1138
1209
|
}
|
|
1139
1210
|
|
|
@@ -2819,6 +2890,37 @@ async function run(argv) {
|
|
|
2819
2890
|
return;
|
|
2820
2891
|
}
|
|
2821
2892
|
|
|
2893
|
+
if (args.mode === 'dashboard') {
|
|
2894
|
+
runDashboard(process.cwd(), {
|
|
2895
|
+
details: args.dashboardDetails,
|
|
2896
|
+
includeCompleted: args.includeCompleted,
|
|
2897
|
+
json: args.json,
|
|
2898
|
+
limit: args.dashboardLimit,
|
|
2899
|
+
noColor: args.noColor,
|
|
2900
|
+
optionErrors: args.dashboardOptionErrors,
|
|
2901
|
+
section: args.dashboardSection,
|
|
2902
|
+
specSlug: args.specSlug,
|
|
2903
|
+
});
|
|
2904
|
+
return;
|
|
2905
|
+
}
|
|
2906
|
+
|
|
2907
|
+
if (args.mode === 'version') {
|
|
2908
|
+
const report = collectVersionReport(process.cwd(), {
|
|
2909
|
+
cliVersion: CLI_VERSION,
|
|
2910
|
+
env: process.env,
|
|
2911
|
+
});
|
|
2912
|
+
if (args.json) {
|
|
2913
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
2914
|
+
} else {
|
|
2915
|
+
process.stdout.write(formatHumanVersionReport(report, {
|
|
2916
|
+
env: process.env,
|
|
2917
|
+
noColor: args.noColor,
|
|
2918
|
+
stdoutIsTTY: Boolean(process.stdout.isTTY),
|
|
2919
|
+
}));
|
|
2920
|
+
}
|
|
2921
|
+
return;
|
|
2922
|
+
}
|
|
2923
|
+
|
|
2822
2924
|
if (args.mode === 'plan') {
|
|
2823
2925
|
runPlan(process.cwd(), {
|
|
2824
2926
|
includeCompleted: args.includeCompleted,
|
|
@@ -3388,6 +3490,7 @@ async function run(argv) {
|
|
|
3388
3490
|
module.exports = {
|
|
3389
3491
|
resolveInteractiveInitOptions,
|
|
3390
3492
|
runAnalyze,
|
|
3493
|
+
runDashboard,
|
|
3391
3494
|
runDoctor,
|
|
3392
3495
|
runFlow,
|
|
3393
3496
|
runMigrate,
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const { buildPlannerApprovalCandidates } = require('../approvals');
|
|
2
|
+
const { buildTechnicalPlanApprovalCandidates } = require('./plan-review');
|
|
3
|
+
|
|
4
|
+
function buildApprovalCandidateReport(projectRoot, phase) {
|
|
5
|
+
return phase === 'technical-plan'
|
|
6
|
+
? buildTechnicalPlanApprovalCandidates(projectRoot)
|
|
7
|
+
: buildPlannerApprovalCandidates(projectRoot, phase);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function formatReviewSummary(review) {
|
|
11
|
+
if (!review || !review.recommendation) {
|
|
12
|
+
return '';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const parts = [`review=${review.recommendation}`];
|
|
16
|
+
if (review.blocking) {
|
|
17
|
+
parts.push('blocking');
|
|
18
|
+
}
|
|
19
|
+
if (review.required_fixes_count) {
|
|
20
|
+
parts.push(`required fixes=${review.required_fixes_count}`);
|
|
21
|
+
}
|
|
22
|
+
if (review.optional_hardening_count) {
|
|
23
|
+
parts.push(`optional=${review.optional_hardening_count}`);
|
|
24
|
+
}
|
|
25
|
+
if (review.risks_count) {
|
|
26
|
+
parts.push(`risks=${review.risks_count}`);
|
|
27
|
+
}
|
|
28
|
+
return parts.join(', ');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function formatCandidateSummary(candidate) {
|
|
32
|
+
if (!candidate) {
|
|
33
|
+
return '';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const parts = [
|
|
37
|
+
candidate.label,
|
|
38
|
+
candidate.current ? 'current' : 'history',
|
|
39
|
+
candidate.approvable ? 'approvable' : 'blocked',
|
|
40
|
+
candidate.reason,
|
|
41
|
+
formatReviewSummary(candidate.review),
|
|
42
|
+
].filter(Boolean);
|
|
43
|
+
return parts.join(', ');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function approvalCandidateCommand(report, fallback = 'npx create-quiver ai approvals') {
|
|
47
|
+
if (report?.recommended?.next_command) {
|
|
48
|
+
return report.recommended.next_command;
|
|
49
|
+
}
|
|
50
|
+
if (report?.current?.next_command) {
|
|
51
|
+
return report.current.next_command;
|
|
52
|
+
}
|
|
53
|
+
return report?.next_command || fallback;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function formatApprovalDecisionLines(report) {
|
|
57
|
+
const lines = [];
|
|
58
|
+
if (!report || !Array.isArray(report.candidates)) {
|
|
59
|
+
return lines;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
lines.push(`Candidates: ${report.candidates.length}`);
|
|
63
|
+
if (report.latest_version) {
|
|
64
|
+
lines.push(`Latest draft: v${report.latest_version}`);
|
|
65
|
+
}
|
|
66
|
+
if (report.current) {
|
|
67
|
+
lines.push(`Current candidate: ${formatCandidateSummary(report.current)}`);
|
|
68
|
+
}
|
|
69
|
+
if (report.recommended) {
|
|
70
|
+
lines.push(`Recommended approval: ${report.recommended.next_command}`);
|
|
71
|
+
} else if (report.next_command) {
|
|
72
|
+
lines.push(`Recommended next command: ${report.next_command}`);
|
|
73
|
+
}
|
|
74
|
+
if (report.review?.status) {
|
|
75
|
+
lines.push(`Review status: ${report.review.status}`);
|
|
76
|
+
}
|
|
77
|
+
return lines;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = {
|
|
81
|
+
approvalCandidateCommand,
|
|
82
|
+
buildApprovalCandidateReport,
|
|
83
|
+
formatApprovalDecisionLines,
|
|
84
|
+
formatCandidateSummary,
|
|
85
|
+
formatReviewSummary,
|
|
86
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const fs = require('node:fs');
|
|
2
2
|
const path = require('node:path');
|
|
3
3
|
|
|
4
|
-
const { readPhaseApproval, resolveApprovedPlannerInput } = require('../approvals');
|
|
4
|
+
const { buildPlannerApprovalCandidates, readPhaseApproval, resolveApprovedPlannerInput } = require('../approvals');
|
|
5
5
|
const { quiverInternalPaths } = require('../init-layout');
|
|
6
6
|
|
|
7
7
|
const PLAN_REVIEW_PROMPT_SOURCE = 'packaged production-readiness plan review template';
|
|
@@ -186,6 +186,81 @@ function reviewBlocksApproval(review) {
|
|
|
186
186
|
return result.blocking === true || result.approval_recommendation === 'revise';
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
+
function normalizeReviewDecision(review) {
|
|
190
|
+
const result = review?.meta?.review_result || null;
|
|
191
|
+
const requiredFixes = normalizeList(result?.required_fixes);
|
|
192
|
+
const optionalHardening = normalizeList(result?.optional_hardening);
|
|
193
|
+
const risks = normalizeList(result?.risks);
|
|
194
|
+
const blocking = reviewBlocksApproval(review);
|
|
195
|
+
const recommendation = result?.approval_recommendation || (review.status === 'missing' || review.status === 'stale' ? 'review-required' : 'approve-with-risk');
|
|
196
|
+
const nextCommand = result?.next_command || (review.status === 'unapproved' || review.status === 'reviewed'
|
|
197
|
+
? 'npx create-quiver ai approve --phase technical-plan --version <n>'
|
|
198
|
+
: 'npx create-quiver ai review-plan --dry-run');
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
status: review.status,
|
|
202
|
+
recommendation,
|
|
203
|
+
blocking: blocking || review.status === 'missing' || review.status === 'stale',
|
|
204
|
+
required_fixes: requiredFixes,
|
|
205
|
+
optional_hardening: optionalHardening,
|
|
206
|
+
risks,
|
|
207
|
+
required_fixes_count: requiredFixes.length,
|
|
208
|
+
optional_hardening_count: optionalHardening.length,
|
|
209
|
+
risks_count: risks.length,
|
|
210
|
+
source_file: review.meta?.source_file || '',
|
|
211
|
+
source_kind: review.meta?.source_kind || null,
|
|
212
|
+
source_version: review.meta?.source_version || null,
|
|
213
|
+
reviewed_at: review.meta?.reviewed_at || '',
|
|
214
|
+
next_command: nextCommand,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function buildTechnicalPlanApprovalCandidates(projectRoot) {
|
|
219
|
+
const base = buildPlannerApprovalCandidates(projectRoot, 'technical-plan');
|
|
220
|
+
const review = readPlanReview(projectRoot);
|
|
221
|
+
const reviewDecision = normalizeReviewDecision(review);
|
|
222
|
+
const reviewReady = (review.status === 'unapproved' || review.status === 'reviewed') && reviewDecision.blocking !== true;
|
|
223
|
+
const candidates = base.candidates.map((candidate) => {
|
|
224
|
+
const matchesReviewVersion = !reviewDecision.source_version
|
|
225
|
+
|| !candidate.version
|
|
226
|
+
|| Number(reviewDecision.source_version) === Number(candidate.version);
|
|
227
|
+
const approvable = candidate.approvable === true && reviewReady && matchesReviewVersion;
|
|
228
|
+
const reason = approvable
|
|
229
|
+
? `latest draft has plan-review recommendation ${reviewDecision.recommendation}`
|
|
230
|
+
: review.status === 'missing'
|
|
231
|
+
? 'technical-plan requires production review before approval'
|
|
232
|
+
: review.status === 'stale'
|
|
233
|
+
? 'technical-plan review is stale'
|
|
234
|
+
: reviewDecision.blocking
|
|
235
|
+
? `plan-review blocks approval with recommendation ${reviewDecision.recommendation}`
|
|
236
|
+
: matchesReviewVersion
|
|
237
|
+
? candidate.reason
|
|
238
|
+
: `plan-review targets v${reviewDecision.source_version}; candidate is v${candidate.version}`;
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
...candidate,
|
|
242
|
+
approvable,
|
|
243
|
+
recommended: candidate.current === true && approvable,
|
|
244
|
+
blocked: !approvable,
|
|
245
|
+
status: approvable ? 'approvable' : candidate.current ? 'blocked' : 'history',
|
|
246
|
+
reason,
|
|
247
|
+
recommended_action: approvable ? 'approve' : reviewDecision.blocking || review.status === 'missing' || review.status === 'stale' ? 'review-or-revise' : 'inspect',
|
|
248
|
+
next_command: approvable ? candidate.next_command : reviewDecision.next_command,
|
|
249
|
+
review: reviewDecision,
|
|
250
|
+
};
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
...base,
|
|
255
|
+
candidates,
|
|
256
|
+
current: candidates.find((candidate) => candidate.current) || null,
|
|
257
|
+
recommended: candidates.find((candidate) => candidate.recommended) || null,
|
|
258
|
+
history: candidates.filter((candidate) => !candidate.current),
|
|
259
|
+
review: reviewDecision,
|
|
260
|
+
next_command: candidates.find((candidate) => candidate.recommended)?.next_command || reviewDecision.next_command,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
189
264
|
function latestTechnicalPlanDraft(approval) {
|
|
190
265
|
const version = Number(approval.meta?.draft?.version || 0);
|
|
191
266
|
if (!version) {
|
|
@@ -435,6 +510,7 @@ module.exports = {
|
|
|
435
510
|
PLAN_REVIEW_RECOMMENDATIONS,
|
|
436
511
|
assertPlanReviewed,
|
|
437
512
|
buildPlanReviewPrompt,
|
|
513
|
+
buildTechnicalPlanApprovalCandidates,
|
|
438
514
|
derivePlanReviewResult,
|
|
439
515
|
planReviewMetaPath,
|
|
440
516
|
planReviewPath,
|
|
@@ -2,6 +2,7 @@ const fs = require('node:fs');
|
|
|
2
2
|
const os = require('node:os');
|
|
3
3
|
const path = require('node:path');
|
|
4
4
|
|
|
5
|
+
const { buildApprovalCandidateReport } = require('./approval-candidates');
|
|
5
6
|
const { quiverInternalPaths } = require('../init-layout');
|
|
6
7
|
|
|
7
8
|
const AI_RUN_PHASES = Object.freeze([
|
|
@@ -104,8 +105,24 @@ function phaseRank(phase) {
|
|
|
104
105
|
return AI_RUN_PHASES.indexOf(phase);
|
|
105
106
|
}
|
|
106
107
|
|
|
107
|
-
function nextCommandForPhase(phase) {
|
|
108
|
+
function nextCommandForPhase(phase, projectRoot = '') {
|
|
108
109
|
assertKnownPhase(phase);
|
|
110
|
+
if (projectRoot && phase === 'acceptance-draft') {
|
|
111
|
+
try {
|
|
112
|
+
const report = buildApprovalCandidateReport(projectRoot, 'acceptance');
|
|
113
|
+
return report.recommended?.next_command || report.current?.next_command || PHASE_NEXT_COMMAND[phase];
|
|
114
|
+
} catch {
|
|
115
|
+
return PHASE_NEXT_COMMAND[phase];
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (projectRoot && phase === 'technical-plan-reviewed') {
|
|
119
|
+
try {
|
|
120
|
+
const report = buildApprovalCandidateReport(projectRoot, 'technical-plan');
|
|
121
|
+
return report.recommended?.next_command || report.current?.next_command || PHASE_NEXT_COMMAND[phase];
|
|
122
|
+
} catch {
|
|
123
|
+
return PHASE_NEXT_COMMAND[phase];
|
|
124
|
+
}
|
|
125
|
+
}
|
|
109
126
|
return PHASE_NEXT_COMMAND[phase];
|
|
110
127
|
}
|
|
111
128
|
|
|
@@ -374,12 +391,12 @@ function formatAiRunStatus(projectRoot, run) {
|
|
|
374
391
|
if (otherOpenRuns.length > 0) {
|
|
375
392
|
lines.push('Other open runs:');
|
|
376
393
|
for (const item of otherOpenRuns) {
|
|
377
|
-
lines.push(`- ${item.run_id}: ${item.phase} (${item.status}) -> ${nextCommandForPhase(item.phase)}`);
|
|
394
|
+
lines.push(`- ${item.run_id}: ${item.phase} (${item.status}) -> ${nextCommandForPhase(item.phase, projectRoot)}`);
|
|
378
395
|
}
|
|
379
396
|
}
|
|
380
397
|
|
|
381
398
|
lines.push(
|
|
382
|
-
`Next safe command: ${nextCommandForPhase(run.phase)}`,
|
|
399
|
+
`Next safe command: ${nextCommandForPhase(run.phase, projectRoot)}`,
|
|
383
400
|
'',
|
|
384
401
|
);
|
|
385
402
|
|
|
@@ -400,7 +417,7 @@ function formatAiRunResume(projectRoot, run) {
|
|
|
400
417
|
'AI run resume',
|
|
401
418
|
`Run: ${run.run_id}`,
|
|
402
419
|
`Current phase: ${run.phase}`,
|
|
403
|
-
`Next safe command: ${nextCommandForPhase(run.phase)}`,
|
|
420
|
+
`Next safe command: ${nextCommandForPhase(run.phase, projectRoot)}`,
|
|
404
421
|
`State: ${toRelativePosix(projectRoot, runStatePath(projectRoot, run.run_id))}`,
|
|
405
422
|
'',
|
|
406
423
|
].join('\n');
|