create-quiver 0.10.0 → 0.12.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/BACKLOG.md +16 -17
- package/CHANGELOG.md +34 -0
- package/README.md +174 -39
- package/README_FOR_AI.md +48 -24
- package/ROADMAP.md +22 -11
- package/docs/AI_CONTEXT.md.template +2 -0
- package/docs/AI_ONBOARDING_PROMPT.md.template +25 -18
- package/docs/COMMANDS.md.template +59 -11
- package/docs/CONTEXTO.md.template +2 -0
- package/docs/DECISIONS.md.template +1 -0
- package/docs/INDEX.md.template +20 -18
- package/docs/STATUS.md.template +1 -0
- package/docs/SUPPORT_MATRIX.md.template +2 -2
- package/docs/TROUBLESHOOTING.md.template +50 -0
- package/docs/WORKFLOW.md.template +25 -17
- package/package.json +19 -2
- package/package.template.json +13 -1
- package/scripts/init-docs.sh +11 -4
- package/scripts/package-quiver.sh +18 -2
- package/specs/quiver-v22-guided-ai-workflow/EVIDENCE_REPORT.md +58 -0
- package/specs/quiver-v22-guided-ai-workflow/EXECUTION_PLAN.md +88 -0
- package/specs/quiver-v22-guided-ai-workflow/SPEC.md +228 -0
- package/specs/quiver-v22-guided-ai-workflow/STATUS.md +42 -0
- package/specs/quiver-v22-guided-ai-workflow/pr.md +104 -0
- package/specs/quiver-v22-guided-ai-workflow/slices/slice-00-spec-foundation/CLOSURE_BRIEF.md +35 -0
- package/specs/quiver-v22-guided-ai-workflow/slices/slice-00-spec-foundation/EXECUTION_BRIEF.md +61 -0
- package/specs/quiver-v22-guided-ai-workflow/slices/slice-00-spec-foundation/slice.json +51 -0
- package/specs/quiver-v22-guided-ai-workflow/slices/slice-01-docs-source-of-truth-sync/CLOSURE_BRIEF.md +31 -0
- package/specs/quiver-v22-guided-ai-workflow/slices/slice-01-docs-source-of-truth-sync/EXECUTION_BRIEF.md +58 -0
- package/specs/quiver-v22-guided-ai-workflow/slices/slice-01-docs-source-of-truth-sync/slice.json +55 -0
- package/specs/quiver-v22-guided-ai-workflow/slices/slice-02-prepare-command-diagnostics/CLOSURE_BRIEF.md +30 -0
- package/specs/quiver-v22-guided-ai-workflow/slices/slice-02-prepare-command-diagnostics/EXECUTION_BRIEF.md +57 -0
- package/specs/quiver-v22-guided-ai-workflow/slices/slice-02-prepare-command-diagnostics/slice.json +57 -0
- package/specs/quiver-v22-guided-ai-workflow/slices/slice-03-context-doc-refresh/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v22-guided-ai-workflow/slices/slice-03-context-doc-refresh/EXECUTION_BRIEF.md +56 -0
- package/specs/quiver-v22-guided-ai-workflow/slices/slice-03-context-doc-refresh/slice.json +56 -0
- package/specs/quiver-v22-guided-ai-workflow/slices/slice-04-planner-approval-state/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v22-guided-ai-workflow/slices/slice-04-planner-approval-state/EXECUTION_BRIEF.md +56 -0
- package/specs/quiver-v22-guided-ai-workflow/slices/slice-04-planner-approval-state/slice.json +58 -0
- package/specs/quiver-v22-guided-ai-workflow/slices/slice-05-spec-worktree-lifecycle/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v22-guided-ai-workflow/slices/slice-05-spec-worktree-lifecycle/EXECUTION_BRIEF.md +56 -0
- package/specs/quiver-v22-guided-ai-workflow/slices/slice-05-spec-worktree-lifecycle/slice.json +54 -0
- package/specs/quiver-v22-guided-ai-workflow/slices/slice-06-executor-commit-recovery/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v22-guided-ai-workflow/slices/slice-06-executor-commit-recovery/EXECUTION_BRIEF.md +58 -0
- package/specs/quiver-v22-guided-ai-workflow/slices/slice-06-executor-commit-recovery/slice.json +57 -0
- package/specs/quiver-v22-guided-ai-workflow/slices/slice-07-execution-waves-delegation/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v22-guided-ai-workflow/slices/slice-07-execution-waves-delegation/EXECUTION_BRIEF.md +58 -0
- package/specs/quiver-v22-guided-ai-workflow/slices/slice-07-execution-waves-delegation/slice.json +55 -0
- package/specs/quiver-v22-guided-ai-workflow/slices/slice-08-pr-create-gh-ssh/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v22-guided-ai-workflow/slices/slice-08-pr-create-gh-ssh/EXECUTION_BRIEF.md +58 -0
- package/specs/quiver-v22-guided-ai-workflow/slices/slice-08-pr-create-gh-ssh/slice.json +53 -0
- package/specs/quiver-v22-guided-ai-workflow/slices/slice-09-post-merge-cleanup-release-safety/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v22-guided-ai-workflow/slices/slice-09-post-merge-cleanup-release-safety/EXECUTION_BRIEF.md +59 -0
- package/specs/quiver-v22-guided-ai-workflow/slices/slice-09-post-merge-cleanup-release-safety/slice.json +59 -0
- package/specs/quiver-v22-guided-ai-workflow/slices/slice-10-docs-smokes-release-readiness/CLOSURE_BRIEF.md +34 -0
- package/specs/quiver-v22-guided-ai-workflow/slices/slice-10-docs-smokes-release-readiness/EXECUTION_BRIEF.md +58 -0
- package/specs/quiver-v22-guided-ai-workflow/slices/slice-10-docs-smokes-release-readiness/slice.json +60 -0
- package/specs/quiver-v23-guided-flow-productization/EVIDENCE_REPORT.md +80 -0
- package/specs/quiver-v23-guided-flow-productization/EXECUTION_PLAN.md +80 -0
- package/specs/quiver-v23-guided-flow-productization/SPEC.md +203 -0
- package/specs/quiver-v23-guided-flow-productization/STATUS.md +39 -0
- package/specs/quiver-v23-guided-flow-productization/pr.md +119 -0
- package/specs/quiver-v23-guided-flow-productization/slices/slice-00-spec-foundation/CLOSURE_BRIEF.md +30 -0
- package/specs/quiver-v23-guided-flow-productization/slices/slice-00-spec-foundation/EXECUTION_BRIEF.md +61 -0
- package/specs/quiver-v23-guided-flow-productization/slices/slice-00-spec-foundation/slice.json +51 -0
- package/specs/quiver-v23-guided-flow-productization/slices/slice-01-short-command-and-flow-entrypoint/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v23-guided-flow-productization/slices/slice-01-short-command-and-flow-entrypoint/EXECUTION_BRIEF.md +35 -0
- package/specs/quiver-v23-guided-flow-productization/slices/slice-01-short-command-and-flow-entrypoint/slice.json +56 -0
- package/specs/quiver-v23-guided-flow-productization/slices/slice-02-flow-status-wizard/CLOSURE_BRIEF.md +31 -0
- package/specs/quiver-v23-guided-flow-productization/slices/slice-02-flow-status-wizard/EXECUTION_BRIEF.md +29 -0
- package/specs/quiver-v23-guided-flow-productization/slices/slice-02-flow-status-wizard/slice.json +55 -0
- package/specs/quiver-v23-guided-flow-productization/slices/slice-03-agent-profiles/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v23-guided-flow-productization/slices/slice-03-agent-profiles/EXECUTION_BRIEF.md +29 -0
- package/specs/quiver-v23-guided-flow-productization/slices/slice-03-agent-profiles/slice.json +54 -0
- package/specs/quiver-v23-guided-flow-productization/slices/slice-04-context-preparation-onboarding/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v23-guided-flow-productization/slices/slice-04-context-preparation-onboarding/EXECUTION_BRIEF.md +30 -0
- package/specs/quiver-v23-guided-flow-productization/slices/slice-04-context-preparation-onboarding/slice.json +59 -0
- package/specs/quiver-v23-guided-flow-productization/slices/slice-05-planner-iteration-history/CLOSURE_BRIEF.md +31 -0
- package/specs/quiver-v23-guided-flow-productization/slices/slice-05-planner-iteration-history/EXECUTION_BRIEF.md +29 -0
- package/specs/quiver-v23-guided-flow-productization/slices/slice-05-planner-iteration-history/slice.json +53 -0
- package/specs/quiver-v23-guided-flow-productization/slices/slice-06-production-plan-review/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v23-guided-flow-productization/slices/slice-06-production-plan-review/EXECUTION_BRIEF.md +30 -0
- package/specs/quiver-v23-guided-flow-productization/slices/slice-06-production-plan-review/slice.json +54 -0
- package/specs/quiver-v23-guided-flow-productization/slices/slice-07-spec-create-experience/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v23-guided-flow-productization/slices/slice-07-spec-create-experience/EXECUTION_BRIEF.md +30 -0
- package/specs/quiver-v23-guided-flow-productization/slices/slice-07-spec-create-experience/slice.json +55 -0
- package/specs/quiver-v23-guided-flow-productization/slices/slice-08-executor-prompt-generation/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v23-guided-flow-productization/slices/slice-08-executor-prompt-generation/EXECUTION_BRIEF.md +30 -0
- package/specs/quiver-v23-guided-flow-productization/slices/slice-08-executor-prompt-generation/slice.json +55 -0
- package/specs/quiver-v23-guided-flow-productization/slices/slice-09-delegated-slice-execution/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v23-guided-flow-productization/slices/slice-09-delegated-slice-execution/EXECUTION_BRIEF.md +34 -0
- package/specs/quiver-v23-guided-flow-productization/slices/slice-09-delegated-slice-execution/slice.json +57 -0
- package/specs/quiver-v23-guided-flow-productization/slices/slice-10-docs-smokes-release-readiness/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v23-guided-flow-productization/slices/slice-10-docs-smokes-release-readiness/EXECUTION_BRIEF.md +32 -0
- package/specs/quiver-v23-guided-flow-productization/slices/slice-10-docs-smokes-release-readiness/slice.json +63 -0
- package/specs/quiver-v24-dx-onboarding-hardening/EVIDENCE_REPORT.md +55 -0
- package/specs/quiver-v24-dx-onboarding-hardening/EXECUTION_PLAN.md +43 -0
- package/specs/quiver-v24-dx-onboarding-hardening/SPEC.md +149 -0
- package/specs/quiver-v24-dx-onboarding-hardening/STATUS.md +31 -0
- package/specs/quiver-v24-dx-onboarding-hardening/pr.md +76 -0
- package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-00-spec-foundation/CLOSURE_BRIEF.md +31 -0
- package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-00-spec-foundation/EXECUTION_BRIEF.md +52 -0
- package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-00-spec-foundation/slice.json +51 -0
- package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-01-init-template-hygiene/CLOSURE_BRIEF.md +38 -0
- package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-01-init-template-hygiene/EXECUTION_BRIEF.md +53 -0
- package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-01-init-template-hygiene/slice.json +55 -0
- package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-02-cli-command-routing-version-errors/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-02-cli-command-routing-version-errors/EXECUTION_BRIEF.md +50 -0
- package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-02-cli-command-routing-version-errors/slice.json +52 -0
- package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-03-doctor-fix-doc-link-checks/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-03-doctor-fix-doc-link-checks/EXECUTION_BRIEF.md +50 -0
- package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-03-doctor-fix-doc-link-checks/slice.json +53 -0
- package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-04-prepare-output-ai-context-drafts/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-04-prepare-output-ai-context-drafts/EXECUTION_BRIEF.md +50 -0
- package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-04-prepare-output-ai-context-drafts/slice.json +70 -0
- package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-05-local-slice-validation-base-guidance/CLOSURE_BRIEF.md +36 -0
- package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-05-local-slice-validation-base-guidance/EXECUTION_BRIEF.md +49 -0
- package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-05-local-slice-validation-base-guidance/slice.json +52 -0
- package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-06-plan-graph-next-history-views/CLOSURE_BRIEF.md +43 -0
- package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-06-plan-graph-next-history-views/EXECUTION_BRIEF.md +53 -0
- package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-06-plan-graph-next-history-views/slice.json +60 -0
- package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-07-analyzer-command-map-hardening/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-07-analyzer-command-map-hardening/EXECUTION_BRIEF.md +50 -0
- package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-07-analyzer-command-map-hardening/slice.json +51 -0
- package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-08-evidence-run-command/CLOSURE_BRIEF.md +34 -0
- package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-08-evidence-run-command/EXECUTION_BRIEF.md +52 -0
- package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-08-evidence-run-command/slice.json +54 -0
- package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-09-spec-viewer-demo-scaffolding/CLOSURE_BRIEF.md +34 -0
- package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-09-spec-viewer-demo-scaffolding/EXECUTION_BRIEF.md +51 -0
- package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-09-spec-viewer-demo-scaffolding/slice.json +59 -0
- package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-10-docs-smokes-release-readiness/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-10-docs-smokes-release-readiness/EXECUTION_BRIEF.md +54 -0
- package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-10-docs-smokes-release-readiness/slice.json +76 -0
- package/src/create-quiver/commands/ai.js +508 -35
- package/src/create-quiver/commands/demo.js +22 -0
- package/src/create-quiver/commands/evidence.js +37 -0
- package/src/create-quiver/commands/flow.js +561 -0
- package/src/create-quiver/commands/graph.js +14 -1
- package/src/create-quiver/commands/next.js +28 -0
- package/src/create-quiver/commands/plan.js +6 -3
- package/src/create-quiver/commands/prepare.js +236 -0
- package/src/create-quiver/commands/spec.js +133 -0
- package/src/create-quiver/index.js +688 -25
- package/src/create-quiver/lib/agent-profiles.js +148 -0
- package/src/create-quiver/lib/ai/context-packs.js +12 -0
- package/src/create-quiver/lib/ai/execution-plan.js +370 -10
- package/src/create-quiver/lib/ai/executor.js +376 -17
- package/src/create-quiver/lib/ai/github.js +196 -0
- package/src/create-quiver/lib/ai/onboarding-template.js +365 -0
- package/src/create-quiver/lib/ai/plan-review.js +283 -0
- package/src/create-quiver/lib/ai/providers.js +1 -0
- package/src/create-quiver/lib/ai/safety.js +5 -0
- package/src/create-quiver/lib/ai/spec-templates.js +2 -2
- package/src/create-quiver/lib/approvals.js +350 -0
- package/src/create-quiver/lib/demo.js +657 -0
- package/src/create-quiver/lib/doctor.js +234 -0
- package/src/create-quiver/lib/evidence.js +115 -0
- package/src/create-quiver/lib/init-docs.js +284 -17
- package/src/create-quiver/lib/init-layout.js +26 -1
- package/src/create-quiver/lib/lifecycle.js +6 -0
- package/src/create-quiver/lib/package-safety.js +117 -0
- package/src/create-quiver/lib/readiness.js +85 -18
- package/src/create-quiver/lib/slice-graph.js +1 -0
- package/src/create-quiver/lib/slice.js +8 -8
- package/src/create-quiver/lib/spec-worktrees.js +349 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
const fs = require('node:fs');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
|
|
4
|
+
const { assertSupportedProvider, formatProviderList } = require('./ai/providers');
|
|
5
|
+
const { quiverInternalPaths } = require('./init-layout');
|
|
6
|
+
|
|
7
|
+
const AGENT_PROFILE_ROLES = Object.freeze(['planner', 'executor', 'reviewer', 'researcher']);
|
|
8
|
+
const PROFILE_STATE_VERSION = 1;
|
|
9
|
+
|
|
10
|
+
function formatError(message) {
|
|
11
|
+
return `create-quiver: ${message}`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function agentProfilesPath(projectRoot) {
|
|
15
|
+
return path.join(quiverInternalPaths(projectRoot).root, 'agents', 'profiles.json');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function normalizeAgentProfileRole(role) {
|
|
19
|
+
const normalized = String(role || '').trim().toLowerCase();
|
|
20
|
+
if (!AGENT_PROFILE_ROLES.includes(normalized)) {
|
|
21
|
+
throw new Error(formatError(`unsupported agent profile role '${role}'. Expected one of: ${AGENT_PROFILE_ROLES.join(', ')}`));
|
|
22
|
+
}
|
|
23
|
+
return normalized;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function normalizeOptionalText(value, fieldName) {
|
|
27
|
+
if (value === undefined || value === null) {
|
|
28
|
+
return '';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const normalized = String(value).trim();
|
|
32
|
+
if (normalized.length > 160) {
|
|
33
|
+
throw new Error(formatError(`agent profile ${fieldName} is too long`));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
assertNotSecretLike(normalized, fieldName);
|
|
37
|
+
return normalized;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function assertNotSecretLike(value, fieldName) {
|
|
41
|
+
const text = String(value || '');
|
|
42
|
+
const secretPatterns = [
|
|
43
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----/,
|
|
44
|
+
/\bsk-[A-Za-z0-9_-]{16,}\b/,
|
|
45
|
+
/\bghp_[A-Za-z0-9_]{16,}\b/,
|
|
46
|
+
/\bgithub_pat_[A-Za-z0-9_]{16,}\b/,
|
|
47
|
+
/\bxox[baprs]-[A-Za-z0-9-]{16,}\b/,
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
if (secretPatterns.some((pattern) => pattern.test(text))) {
|
|
51
|
+
throw new Error(formatError(`agent profile ${fieldName} looks like a secret; store provider credentials in the provider CLI, not in Quiver profiles`));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function emptyProfilesState() {
|
|
56
|
+
return {
|
|
57
|
+
version: PROFILE_STATE_VERSION,
|
|
58
|
+
profiles: {},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function readAgentProfiles(projectRoot) {
|
|
63
|
+
const filePath = agentProfilesPath(projectRoot);
|
|
64
|
+
if (!fs.existsSync(filePath)) {
|
|
65
|
+
return emptyProfilesState();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const state = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
69
|
+
return {
|
|
70
|
+
version: state.version || PROFILE_STATE_VERSION,
|
|
71
|
+
profiles: state.profiles && typeof state.profiles === 'object' ? state.profiles : {},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function writeAgentProfiles(projectRoot, state) {
|
|
76
|
+
const filePath = agentProfilesPath(projectRoot);
|
|
77
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
78
|
+
fs.writeFileSync(filePath, `${JSON.stringify(state, null, 2)}\n`);
|
|
79
|
+
return filePath;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function getAgentProfile(projectRoot, role) {
|
|
83
|
+
const normalizedRole = normalizeAgentProfileRole(role);
|
|
84
|
+
const state = readAgentProfiles(projectRoot);
|
|
85
|
+
return state.profiles[normalizedRole] || null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function listAgentProfiles(projectRoot) {
|
|
89
|
+
const state = readAgentProfiles(projectRoot);
|
|
90
|
+
return AGENT_PROFILE_ROLES.map((role) => ({
|
|
91
|
+
role,
|
|
92
|
+
configured: Boolean(state.profiles[role]),
|
|
93
|
+
profile: state.profiles[role] || null,
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function setAgentProfile(projectRoot, role, options = {}) {
|
|
98
|
+
const normalizedRole = normalizeAgentProfileRole(role);
|
|
99
|
+
const provider = assertSupportedProvider(options.provider);
|
|
100
|
+
const model = normalizeOptionalText(options.model, 'model');
|
|
101
|
+
const label = normalizeOptionalText(options.label, 'label');
|
|
102
|
+
const context = normalizeOptionalText(options.context, 'context');
|
|
103
|
+
const state = readAgentProfiles(projectRoot);
|
|
104
|
+
const current = state.profiles[normalizedRole] || {};
|
|
105
|
+
const now = new Date().toISOString();
|
|
106
|
+
const profile = {
|
|
107
|
+
role: normalizedRole,
|
|
108
|
+
provider,
|
|
109
|
+
model: model || current.model || '',
|
|
110
|
+
label: label || current.label || '',
|
|
111
|
+
context: context || current.context || '',
|
|
112
|
+
updated_at: now,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
state.version = PROFILE_STATE_VERSION;
|
|
116
|
+
state.profiles = {
|
|
117
|
+
...state.profiles,
|
|
118
|
+
[normalizedRole]: profile,
|
|
119
|
+
};
|
|
120
|
+
state.updated_at = now;
|
|
121
|
+
|
|
122
|
+
const filePath = writeAgentProfiles(projectRoot, state);
|
|
123
|
+
return {
|
|
124
|
+
filePath,
|
|
125
|
+
profile,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function resolveProfileProvider(projectRoot, role, fallbackProvider) {
|
|
130
|
+
const profile = getAgentProfile(projectRoot, role);
|
|
131
|
+
if (profile?.provider) {
|
|
132
|
+
return assertSupportedProvider(profile.provider);
|
|
133
|
+
}
|
|
134
|
+
return assertSupportedProvider(fallbackProvider);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
module.exports = {
|
|
138
|
+
AGENT_PROFILE_ROLES,
|
|
139
|
+
PROFILE_STATE_VERSION,
|
|
140
|
+
agentProfilesPath,
|
|
141
|
+
formatProviderList,
|
|
142
|
+
getAgentProfile,
|
|
143
|
+
listAgentProfiles,
|
|
144
|
+
normalizeAgentProfileRole,
|
|
145
|
+
readAgentProfiles,
|
|
146
|
+
resolveProfileProvider,
|
|
147
|
+
setAgentProfile,
|
|
148
|
+
};
|
|
@@ -44,6 +44,13 @@ const DEFAULT_CONTEXT_PACK_BY_ROLE = Object.freeze({
|
|
|
44
44
|
});
|
|
45
45
|
|
|
46
46
|
const PACK_ORDER = ['full', 'planning', 'slice', 'minimal'];
|
|
47
|
+
const CONTEXT_PREPARED_DOC_PATHS = Object.freeze([
|
|
48
|
+
'docs/AI_CONTEXT.md',
|
|
49
|
+
'docs/AI_ONBOARDING_PROMPT.md',
|
|
50
|
+
'docs/CONTEXTO.md',
|
|
51
|
+
'docs/STATUS.md',
|
|
52
|
+
'docs/DECISIONS.md',
|
|
53
|
+
]);
|
|
47
54
|
|
|
48
55
|
function normalizeRole(role) {
|
|
49
56
|
const value = String(role || '').trim().toLowerCase();
|
|
@@ -66,6 +73,10 @@ function getDefaultContextPack(role) {
|
|
|
66
73
|
return DEFAULT_CONTEXT_PACK_BY_ROLE[normalizedRole];
|
|
67
74
|
}
|
|
68
75
|
|
|
76
|
+
function getPreparedContextDocPaths() {
|
|
77
|
+
return CONTEXT_PREPARED_DOC_PATHS.slice();
|
|
78
|
+
}
|
|
79
|
+
|
|
69
80
|
function resolveContextPack({ role, packName } = {}) {
|
|
70
81
|
const normalizedRole = normalizeRole(role);
|
|
71
82
|
const defaultPack = getDefaultContextPack(normalizedRole);
|
|
@@ -148,6 +159,7 @@ module.exports = {
|
|
|
148
159
|
ROLES,
|
|
149
160
|
buildContextPackMetadata,
|
|
150
161
|
buildPackSelection,
|
|
162
|
+
getPreparedContextDocPaths,
|
|
151
163
|
getDefaultContextPack,
|
|
152
164
|
normalizePackName,
|
|
153
165
|
normalizeRole,
|
|
@@ -1,16 +1,37 @@
|
|
|
1
|
+
const fs = require('node:fs');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
|
|
4
|
+
const { resolveProfileProvider } = require('../agent-profiles');
|
|
5
|
+
const { branchDelete, runGit, statusPorcelain, worktreeAdd, worktreePrune, worktreeRemove } = require('../git');
|
|
6
|
+
const { safeBranchName, worktreesRootForRepo } = require('../slice');
|
|
1
7
|
const { buildGraph, computeLevels, detectFileConflicts, isFoundationSliceId, readAllSlices, topoSort, SliceGraphError } = require('../slice-graph');
|
|
8
|
+
const { runExecuteSlice } = require('./executor');
|
|
2
9
|
|
|
3
10
|
const EXCLUDED_STATUSES = new Set(['completed', 'skipped', 'cancelled']);
|
|
11
|
+
const EXECUTION_MODES = new Set(['auto', 'manual', 'delegated']);
|
|
4
12
|
|
|
5
13
|
function formatError(message) {
|
|
6
14
|
return `create-quiver: ${message}`;
|
|
7
15
|
}
|
|
8
16
|
|
|
9
|
-
function
|
|
17
|
+
function toRelativePath(repoRoot, filePath) {
|
|
18
|
+
return path.relative(repoRoot, filePath).split(path.sep).join('/');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function normalizeExecutionMode(mode) {
|
|
22
|
+
const value = String(mode || 'auto').trim().toLowerCase() || 'auto';
|
|
23
|
+
if (!EXECUTION_MODES.has(value)) {
|
|
24
|
+
throw new Error(formatError(`unsupported execution mode: ${mode}. Use auto, manual, or delegated.`));
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function summarizeSlice(node, repoRoot) {
|
|
10
30
|
return {
|
|
11
31
|
ref: node.ref,
|
|
12
32
|
spec_slug: node.specSlug,
|
|
13
33
|
slice_id: node.sliceId,
|
|
34
|
+
slice_path: node.slicePath ? toRelativePath(repoRoot, node.slicePath) : '',
|
|
14
35
|
title: node.title || node.sliceId,
|
|
15
36
|
status: node.status || 'draft',
|
|
16
37
|
files: Array.isArray(node.files) ? node.files : [],
|
|
@@ -92,8 +113,18 @@ function buildPendingGraph(graph, options = {}) {
|
|
|
92
113
|
};
|
|
93
114
|
}
|
|
94
115
|
|
|
95
|
-
function
|
|
96
|
-
if (
|
|
116
|
+
function buildFallbackReason({ conflicts, unknownScopeSlices }) {
|
|
117
|
+
if (unknownScopeSlices.length > 0) {
|
|
118
|
+
return `Unknown file scope: ${unknownScopeSlices.join(', ')}`;
|
|
119
|
+
}
|
|
120
|
+
if (conflicts.length > 0) {
|
|
121
|
+
return `File conflicts: ${conflicts.map((conflict) => conflict.slices.join(', ')).join('; ')}`;
|
|
122
|
+
}
|
|
123
|
+
return '';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function buildLevelStrategy(levelNodes, { parallelReady, conflicts, unknownScopeSlices }) {
|
|
127
|
+
if (parallelReady) {
|
|
97
128
|
return {
|
|
98
129
|
mode: 'temporary-per-slice',
|
|
99
130
|
temporary_worktrees: true,
|
|
@@ -101,6 +132,14 @@ function buildLevelStrategy(levelNodes) {
|
|
|
101
132
|
};
|
|
102
133
|
}
|
|
103
134
|
|
|
135
|
+
if (levelNodes.length > 1) {
|
|
136
|
+
return {
|
|
137
|
+
mode: 'sequential-fallback',
|
|
138
|
+
temporary_worktrees: false,
|
|
139
|
+
reason: buildFallbackReason({ conflicts, unknownScopeSlices }) || 'Run sequentially because this level is not parallel-ready.',
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
104
143
|
return {
|
|
105
144
|
mode: 'shared-worktree',
|
|
106
145
|
temporary_worktrees: false,
|
|
@@ -108,21 +147,45 @@ function buildLevelStrategy(levelNodes) {
|
|
|
108
147
|
};
|
|
109
148
|
}
|
|
110
149
|
|
|
111
|
-
function
|
|
112
|
-
|
|
150
|
+
function buildExecutionGroups(slices, parallelReady, fallbackReason) {
|
|
151
|
+
if (parallelReady) {
|
|
152
|
+
return [{
|
|
153
|
+
mode: 'parallel',
|
|
154
|
+
reason: 'No file-scope conflicts detected.',
|
|
155
|
+
slice_refs: slices.map((slice) => slice.ref),
|
|
156
|
+
}];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return slices.map((slice) => ({
|
|
160
|
+
mode: 'sequential',
|
|
161
|
+
reason: fallbackReason || 'Sequential execution is the safe default.',
|
|
162
|
+
slice_refs: [slice.ref],
|
|
163
|
+
}));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function summarizeLevel(levelNodes, index, repoRoot) {
|
|
167
|
+
const slices = levelNodes.map((node) => summarizeSlice(node, repoRoot));
|
|
113
168
|
const sliceRefs = slices.map((slice) => slice.ref);
|
|
114
169
|
const conflicts = detectFileConflicts(levelNodes).map((group) => ({
|
|
115
170
|
files: group.files,
|
|
116
171
|
slices: group.slices,
|
|
117
172
|
}));
|
|
173
|
+
const unknownScopeSlices = slices
|
|
174
|
+
.filter((slice) => !Array.isArray(slice.files) || slice.files.length === 0)
|
|
175
|
+
.map((slice) => slice.ref);
|
|
176
|
+
const fallbackReason = buildFallbackReason({ conflicts, unknownScopeSlices });
|
|
177
|
+
const parallelReady = levelNodes.length > 1 && conflicts.length === 0 && unknownScopeSlices.length === 0;
|
|
118
178
|
|
|
119
179
|
return {
|
|
120
180
|
index,
|
|
121
181
|
slice_refs: sliceRefs,
|
|
122
|
-
parallel_ready:
|
|
123
|
-
requires_temporary_worktrees:
|
|
124
|
-
worktree_strategy: buildLevelStrategy(levelNodes),
|
|
182
|
+
parallel_ready: parallelReady,
|
|
183
|
+
requires_temporary_worktrees: parallelReady,
|
|
184
|
+
worktree_strategy: buildLevelStrategy(levelNodes, { parallelReady, conflicts, unknownScopeSlices }),
|
|
125
185
|
conflicts,
|
|
186
|
+
unknown_scope_slices: unknownScopeSlices,
|
|
187
|
+
fallback_reason: fallbackReason || null,
|
|
188
|
+
execution_groups: buildExecutionGroups(slices, parallelReady, fallbackReason),
|
|
126
189
|
slices,
|
|
127
190
|
};
|
|
128
191
|
}
|
|
@@ -153,7 +216,7 @@ function collectExecutionPlan(repoRoot, options = {}) {
|
|
|
153
216
|
}
|
|
154
217
|
|
|
155
218
|
const readyLevels = computeLevels(pendingGraph);
|
|
156
|
-
const readyLevelReports = readyLevels.map((levelNodes, index) => summarizeLevel(levelNodes, index));
|
|
219
|
+
const readyLevelReports = readyLevels.map((levelNodes, index) => summarizeLevel(levelNodes, index, repoRoot));
|
|
157
220
|
const executionOrder = topoSort(pendingGraph).map((node) => node.ref);
|
|
158
221
|
const integrationOrder = readyLevelReports.flatMap((level) => level.slice_refs);
|
|
159
222
|
const foundationRefs = pendingGraph.foundationRefs;
|
|
@@ -210,9 +273,12 @@ function formatHumanExecutionPlan(report) {
|
|
|
210
273
|
}
|
|
211
274
|
|
|
212
275
|
for (const level of report.ready_levels) {
|
|
213
|
-
const modeLabel = level.parallel_ready ? 'parallel' : 'sequential';
|
|
276
|
+
const modeLabel = level.parallel_ready ? 'parallel-ready' : 'sequential';
|
|
214
277
|
lines.push(`Level ${level.index} (${modeLabel})`);
|
|
215
278
|
lines.push(`Worktree strategy: ${level.worktree_strategy.mode}`);
|
|
279
|
+
if (level.fallback_reason) {
|
|
280
|
+
lines.push(`Fallback: ${level.fallback_reason}`);
|
|
281
|
+
}
|
|
216
282
|
|
|
217
283
|
for (const slice of level.slices) {
|
|
218
284
|
lines.push(`- ${slice.ref} [${slice.status}]`);
|
|
@@ -225,6 +291,13 @@ function formatHumanExecutionPlan(report) {
|
|
|
225
291
|
}
|
|
226
292
|
}
|
|
227
293
|
|
|
294
|
+
if (level.unknown_scope_slices.length > 0) {
|
|
295
|
+
lines.push('Unknown scope:');
|
|
296
|
+
for (const ref of level.unknown_scope_slices) {
|
|
297
|
+
lines.push(`- ${ref}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
228
301
|
lines.push('');
|
|
229
302
|
}
|
|
230
303
|
|
|
@@ -236,6 +309,290 @@ function formatHumanExecutionPlan(report) {
|
|
|
236
309
|
return `${lines.join('\n')}\n`;
|
|
237
310
|
}
|
|
238
311
|
|
|
312
|
+
function formatExecutePlanDryRun(report, options = {}) {
|
|
313
|
+
const provider = options.resolvedProvider || options.provider || 'codex';
|
|
314
|
+
const commitEnabled = options.commit === true;
|
|
315
|
+
const executionMode = normalizeExecutionMode(options.mode || options.executionMode);
|
|
316
|
+
const lines = [
|
|
317
|
+
'AI execute-plan dry-run',
|
|
318
|
+
`Execution mode: ${executionMode}`,
|
|
319
|
+
`Provider: ${provider}`,
|
|
320
|
+
`Commit after each slice: ${commitEnabled ? 'enabled' : 'disabled'}`,
|
|
321
|
+
`Total slices: ${report.summary.total_slices}`,
|
|
322
|
+
'',
|
|
323
|
+
'Waves',
|
|
324
|
+
];
|
|
325
|
+
|
|
326
|
+
const commandForSlice = (slice) => {
|
|
327
|
+
const parts = [
|
|
328
|
+
'npx create-quiver ai execute-slice',
|
|
329
|
+
`--slice ${JSON.stringify(slice.slice_path)}`,
|
|
330
|
+
`--provider ${provider}`,
|
|
331
|
+
];
|
|
332
|
+
if (commitEnabled) {
|
|
333
|
+
parts.push('--commit');
|
|
334
|
+
}
|
|
335
|
+
return parts.join(' ');
|
|
336
|
+
};
|
|
337
|
+
const promptCommandForSlice = (slice) => [
|
|
338
|
+
'npx create-quiver ai prompt-slice',
|
|
339
|
+
`--slice ${JSON.stringify(slice.slice_path)}`,
|
|
340
|
+
'--dry-run',
|
|
341
|
+
].join(' ');
|
|
342
|
+
|
|
343
|
+
for (const level of report.ready_levels) {
|
|
344
|
+
lines.push(`Wave ${level.index}: ${level.parallel_ready ? 'parallel-ready' : 'sequential'}`);
|
|
345
|
+
lines.push(`Workspace strategy: ${level.worktree_strategy.mode}`);
|
|
346
|
+
if (level.fallback_reason) {
|
|
347
|
+
lines.push(`Fallback: ${level.fallback_reason}`);
|
|
348
|
+
}
|
|
349
|
+
for (const group of level.execution_groups) {
|
|
350
|
+
lines.push(`Group: ${group.mode}`);
|
|
351
|
+
for (const ref of group.slice_refs) {
|
|
352
|
+
const slice = level.slices.find((item) => item.ref === ref);
|
|
353
|
+
lines.push(`- Prompt: ${promptCommandForSlice(slice)}`);
|
|
354
|
+
if (executionMode !== 'manual') {
|
|
355
|
+
lines.push(` Execute: ${commandForSlice(slice)}`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return `${lines.join('\n')}\n`;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function buildRecoveryGuidance(ref, workspaces = []) {
|
|
365
|
+
const lines = [
|
|
366
|
+
'Recovery:',
|
|
367
|
+
`- Retry slice: npx create-quiver ai prompt-slice --slice <slice.json> --dry-run, then rerun only ${ref}.`,
|
|
368
|
+
'- Abort: inspect the active checkout and temporary worktrees before reverting, stashing, or removing anything.',
|
|
369
|
+
];
|
|
370
|
+
|
|
371
|
+
if (workspaces.length > 0) {
|
|
372
|
+
lines.push('- Temporary worktrees left for inspection:');
|
|
373
|
+
for (const workspace of workspaces) {
|
|
374
|
+
lines.push(` - ${workspace.worktreePath}`);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return lines.join('\n');
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function appendRecovery(error, ref, workspaces = []) {
|
|
382
|
+
const message = error && error.message ? error.message : String(error);
|
|
383
|
+
if (message.includes('Recovery:')) {
|
|
384
|
+
return error;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const wrapped = new Error(`${message}\n\n${buildRecoveryGuidance(ref, workspaces)}`);
|
|
388
|
+
wrapped.cause = error;
|
|
389
|
+
wrapped.code = error && error.code ? error.code : undefined;
|
|
390
|
+
wrapped.details = error && error.details ? error.details : undefined;
|
|
391
|
+
return wrapped;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function ensureCleanIntegrationWorktree(repoRoot) {
|
|
395
|
+
const status = statusPorcelain(repoRoot);
|
|
396
|
+
if (status !== '') {
|
|
397
|
+
throw new Error(formatError(`delegated parallel execution requires a clean active worktree before integration. Dirty files: ${status.split('\n').join(', ')}`));
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function buildDelegatedRunId() {
|
|
402
|
+
return new Date().toISOString().replace(/[^0-9A-Za-z]/g, '');
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function buildDelegatedWorkspace(repoRoot, slice, runId, index, options = {}) {
|
|
406
|
+
const safeRef = safeBranchName(slice.ref).slice(0, 80);
|
|
407
|
+
const branchName = `quiver-exec-${runId}-${index + 1}-${safeRef}`;
|
|
408
|
+
const worktreesRoot = options.worktreesRoot || path.join(worktreesRootForRepo(repoRoot, branchName), 'execute-plan');
|
|
409
|
+
const worktreePath = path.join(worktreesRoot, runId, safeRef);
|
|
410
|
+
|
|
411
|
+
return {
|
|
412
|
+
branchName,
|
|
413
|
+
ref: slice.ref,
|
|
414
|
+
slice,
|
|
415
|
+
worktreePath,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function createDelegatedWorkspace(repoRoot, workspace, baseRef) {
|
|
420
|
+
if (fs.existsSync(workspace.worktreePath)) {
|
|
421
|
+
throw new Error(formatError(`temporary worktree path already exists: ${workspace.worktreePath}`));
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
fs.mkdirSync(path.dirname(workspace.worktreePath), { recursive: true });
|
|
425
|
+
worktreeAdd(repoRoot, workspace.worktreePath, baseRef, { branch: workspace.branchName });
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function cleanupDelegatedWorkspace(repoRoot, workspace) {
|
|
429
|
+
try {
|
|
430
|
+
worktreeRemove(repoRoot, workspace.worktreePath);
|
|
431
|
+
} catch {
|
|
432
|
+
// Keep cleanup best-effort after successful integration.
|
|
433
|
+
}
|
|
434
|
+
try {
|
|
435
|
+
branchDelete(repoRoot, workspace.branchName, true);
|
|
436
|
+
} catch {
|
|
437
|
+
// The committed changes were already integrated; a leftover temp branch is non-blocking.
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
async function runSequentialGroup(repoRoot, level, group, options = {}) {
|
|
442
|
+
const runSlice = options.runExecuteSliceFn || runExecuteSlice;
|
|
443
|
+
const results = [];
|
|
444
|
+
|
|
445
|
+
for (const ref of group.slice_refs) {
|
|
446
|
+
const slice = level.slices.find((item) => item.ref === ref);
|
|
447
|
+
try {
|
|
448
|
+
const result = await runSlice(repoRoot, {
|
|
449
|
+
allowDirty: options.allowDirty === true,
|
|
450
|
+
commit: true,
|
|
451
|
+
context: options.context,
|
|
452
|
+
dryRun: false,
|
|
453
|
+
provider: options.provider,
|
|
454
|
+
providerExplicit: options.providerExplicit,
|
|
455
|
+
role: options.role,
|
|
456
|
+
slice: slice.slice_path,
|
|
457
|
+
timeout: options.timeout,
|
|
458
|
+
});
|
|
459
|
+
results.push({
|
|
460
|
+
level: level.index,
|
|
461
|
+
mode: 'sequential',
|
|
462
|
+
ref,
|
|
463
|
+
ok: true,
|
|
464
|
+
result,
|
|
465
|
+
workspace: repoRoot,
|
|
466
|
+
});
|
|
467
|
+
} catch (error) {
|
|
468
|
+
throw appendRecovery(error, ref);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return results;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
async function runParallelGroupInWorktrees(repoRoot, level, group, options = {}) {
|
|
476
|
+
ensureCleanIntegrationWorktree(repoRoot);
|
|
477
|
+
|
|
478
|
+
const runSlice = options.runExecuteSliceFn || runExecuteSlice;
|
|
479
|
+
const baseRef = runGit(['rev-parse', 'HEAD'], repoRoot);
|
|
480
|
+
const runId = options.runId || buildDelegatedRunId();
|
|
481
|
+
const slices = group.slice_refs.map((ref) => level.slices.find((item) => item.ref === ref));
|
|
482
|
+
const workspaces = slices.map((slice, index) => buildDelegatedWorkspace(repoRoot, slice, runId, index, options));
|
|
483
|
+
|
|
484
|
+
let runResults;
|
|
485
|
+
try {
|
|
486
|
+
worktreePrune(repoRoot);
|
|
487
|
+
for (const workspace of workspaces) {
|
|
488
|
+
createDelegatedWorkspace(repoRoot, workspace, baseRef);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
runResults = await Promise.all(workspaces.map(async (workspace) => {
|
|
492
|
+
const result = await runSlice(workspace.worktreePath, {
|
|
493
|
+
allowDirty: false,
|
|
494
|
+
commit: true,
|
|
495
|
+
context: options.context,
|
|
496
|
+
dryRun: false,
|
|
497
|
+
provider: options.provider,
|
|
498
|
+
providerExplicit: options.providerExplicit,
|
|
499
|
+
role: options.role,
|
|
500
|
+
slice: workspace.slice.slice_path,
|
|
501
|
+
timeout: options.timeout,
|
|
502
|
+
});
|
|
503
|
+
const commit = runGit(['rev-parse', 'HEAD'], workspace.worktreePath);
|
|
504
|
+
if (commit === baseRef) {
|
|
505
|
+
throw new Error(formatError(`delegated slice ${workspace.ref} finished without creating a slice commit.`));
|
|
506
|
+
}
|
|
507
|
+
return {
|
|
508
|
+
commit,
|
|
509
|
+
level: level.index,
|
|
510
|
+
mode: 'parallel-worktree',
|
|
511
|
+
ok: true,
|
|
512
|
+
ref: workspace.ref,
|
|
513
|
+
result,
|
|
514
|
+
workspace,
|
|
515
|
+
};
|
|
516
|
+
}));
|
|
517
|
+
|
|
518
|
+
ensureCleanIntegrationWorktree(repoRoot);
|
|
519
|
+
for (const item of runResults) {
|
|
520
|
+
runGit(['cherry-pick', item.commit], repoRoot);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
for (const workspace of workspaces) {
|
|
524
|
+
cleanupDelegatedWorkspace(repoRoot, workspace);
|
|
525
|
+
}
|
|
526
|
+
} catch (error) {
|
|
527
|
+
throw appendRecovery(error, group.slice_refs.join(', '), workspaces);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
return runResults.map((item) => ({
|
|
531
|
+
level: item.level,
|
|
532
|
+
mode: item.mode,
|
|
533
|
+
ref: item.ref,
|
|
534
|
+
ok: item.ok,
|
|
535
|
+
result: item.result,
|
|
536
|
+
workspace: item.workspace.worktreePath,
|
|
537
|
+
integratedCommit: item.commit,
|
|
538
|
+
}));
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
async function runExecutePlan(repoRoot, options = {}) {
|
|
542
|
+
const report = collectExecutionPlan(repoRoot, options);
|
|
543
|
+
const execute = options.execute === true;
|
|
544
|
+
const executionMode = normalizeExecutionMode(options.mode || options.executionMode);
|
|
545
|
+
const provider = options.providerExplicit === true || (options.provider && options.providerExplicit !== false)
|
|
546
|
+
? options.provider
|
|
547
|
+
: resolveProfileProvider(repoRoot, options.role || 'executor', 'codex');
|
|
548
|
+
const resolvedOptions = {
|
|
549
|
+
...options,
|
|
550
|
+
mode: executionMode,
|
|
551
|
+
provider,
|
|
552
|
+
resolvedProvider: provider,
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
if (options.json && !execute) {
|
|
556
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
557
|
+
return { task: 'execute-plan', dryRun: true, report };
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
if (!execute || options.dryRun === true) {
|
|
561
|
+
process.stdout.write(formatExecutePlanDryRun(report, resolvedOptions));
|
|
562
|
+
return { task: 'execute-plan', dryRun: true, report };
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (executionMode === 'manual') {
|
|
566
|
+
throw new Error(formatError('ai execute-plan --execute does not support --mode manual. Use the printed prompt-slice commands, or choose --mode delegated.'));
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
if (options.commit !== true) {
|
|
570
|
+
throw new Error(formatError('ai execute-plan --execute requires --commit so each successful slice creates one commit.'));
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const results = [];
|
|
574
|
+
|
|
575
|
+
for (const level of report.ready_levels) {
|
|
576
|
+
for (const group of level.execution_groups) {
|
|
577
|
+
try {
|
|
578
|
+
const groupResults = executionMode === 'delegated' && group.mode === 'parallel' && group.slice_refs.length > 1
|
|
579
|
+
? await runParallelGroupInWorktrees(repoRoot, level, group, resolvedOptions)
|
|
580
|
+
: await runSequentialGroup(repoRoot, level, group, resolvedOptions);
|
|
581
|
+
results.push(...groupResults);
|
|
582
|
+
} catch (error) {
|
|
583
|
+
const wrapped = new Error(formatError(`ai execute-plan stopped at wave ${level.index} group ${group.slice_refs.join(', ')}: ${error.message || error}`));
|
|
584
|
+
wrapped.cause = error;
|
|
585
|
+
wrapped.code = error.code || 'AI_EXECUTE_PLAN_FAILED';
|
|
586
|
+
wrapped.details = { level: level.index, group, results };
|
|
587
|
+
throw wrapped;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
process.stdout.write(`AI execute-plan completed\nSlices executed: ${results.length}\n`);
|
|
593
|
+
return { task: 'execute-plan', dryRun: false, report, results };
|
|
594
|
+
}
|
|
595
|
+
|
|
239
596
|
function runExecutionPlan(repoRoot, options = {}) {
|
|
240
597
|
const report = collectExecutionPlan(repoRoot, options);
|
|
241
598
|
if (options.json) {
|
|
@@ -249,6 +606,9 @@ function runExecutionPlan(repoRoot, options = {}) {
|
|
|
249
606
|
|
|
250
607
|
module.exports = {
|
|
251
608
|
collectExecutionPlan,
|
|
609
|
+
formatExecutePlanDryRun,
|
|
252
610
|
formatHumanExecutionPlan,
|
|
611
|
+
normalizeExecutionMode,
|
|
612
|
+
runExecutePlan,
|
|
253
613
|
runExecutionPlan,
|
|
254
614
|
};
|