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.
Files changed (165) hide show
  1. package/BACKLOG.md +16 -17
  2. package/CHANGELOG.md +34 -0
  3. package/README.md +174 -39
  4. package/README_FOR_AI.md +48 -24
  5. package/ROADMAP.md +22 -11
  6. package/docs/AI_CONTEXT.md.template +2 -0
  7. package/docs/AI_ONBOARDING_PROMPT.md.template +25 -18
  8. package/docs/COMMANDS.md.template +59 -11
  9. package/docs/CONTEXTO.md.template +2 -0
  10. package/docs/DECISIONS.md.template +1 -0
  11. package/docs/INDEX.md.template +20 -18
  12. package/docs/STATUS.md.template +1 -0
  13. package/docs/SUPPORT_MATRIX.md.template +2 -2
  14. package/docs/TROUBLESHOOTING.md.template +50 -0
  15. package/docs/WORKFLOW.md.template +25 -17
  16. package/package.json +19 -2
  17. package/package.template.json +13 -1
  18. package/scripts/init-docs.sh +11 -4
  19. package/scripts/package-quiver.sh +18 -2
  20. package/specs/quiver-v22-guided-ai-workflow/EVIDENCE_REPORT.md +58 -0
  21. package/specs/quiver-v22-guided-ai-workflow/EXECUTION_PLAN.md +88 -0
  22. package/specs/quiver-v22-guided-ai-workflow/SPEC.md +228 -0
  23. package/specs/quiver-v22-guided-ai-workflow/STATUS.md +42 -0
  24. package/specs/quiver-v22-guided-ai-workflow/pr.md +104 -0
  25. package/specs/quiver-v22-guided-ai-workflow/slices/slice-00-spec-foundation/CLOSURE_BRIEF.md +35 -0
  26. package/specs/quiver-v22-guided-ai-workflow/slices/slice-00-spec-foundation/EXECUTION_BRIEF.md +61 -0
  27. package/specs/quiver-v22-guided-ai-workflow/slices/slice-00-spec-foundation/slice.json +51 -0
  28. package/specs/quiver-v22-guided-ai-workflow/slices/slice-01-docs-source-of-truth-sync/CLOSURE_BRIEF.md +31 -0
  29. package/specs/quiver-v22-guided-ai-workflow/slices/slice-01-docs-source-of-truth-sync/EXECUTION_BRIEF.md +58 -0
  30. package/specs/quiver-v22-guided-ai-workflow/slices/slice-01-docs-source-of-truth-sync/slice.json +55 -0
  31. package/specs/quiver-v22-guided-ai-workflow/slices/slice-02-prepare-command-diagnostics/CLOSURE_BRIEF.md +30 -0
  32. package/specs/quiver-v22-guided-ai-workflow/slices/slice-02-prepare-command-diagnostics/EXECUTION_BRIEF.md +57 -0
  33. package/specs/quiver-v22-guided-ai-workflow/slices/slice-02-prepare-command-diagnostics/slice.json +57 -0
  34. package/specs/quiver-v22-guided-ai-workflow/slices/slice-03-context-doc-refresh/CLOSURE_BRIEF.md +32 -0
  35. package/specs/quiver-v22-guided-ai-workflow/slices/slice-03-context-doc-refresh/EXECUTION_BRIEF.md +56 -0
  36. package/specs/quiver-v22-guided-ai-workflow/slices/slice-03-context-doc-refresh/slice.json +56 -0
  37. package/specs/quiver-v22-guided-ai-workflow/slices/slice-04-planner-approval-state/CLOSURE_BRIEF.md +33 -0
  38. package/specs/quiver-v22-guided-ai-workflow/slices/slice-04-planner-approval-state/EXECUTION_BRIEF.md +56 -0
  39. package/specs/quiver-v22-guided-ai-workflow/slices/slice-04-planner-approval-state/slice.json +58 -0
  40. package/specs/quiver-v22-guided-ai-workflow/slices/slice-05-spec-worktree-lifecycle/CLOSURE_BRIEF.md +32 -0
  41. package/specs/quiver-v22-guided-ai-workflow/slices/slice-05-spec-worktree-lifecycle/EXECUTION_BRIEF.md +56 -0
  42. package/specs/quiver-v22-guided-ai-workflow/slices/slice-05-spec-worktree-lifecycle/slice.json +54 -0
  43. package/specs/quiver-v22-guided-ai-workflow/slices/slice-06-executor-commit-recovery/CLOSURE_BRIEF.md +32 -0
  44. package/specs/quiver-v22-guided-ai-workflow/slices/slice-06-executor-commit-recovery/EXECUTION_BRIEF.md +58 -0
  45. package/specs/quiver-v22-guided-ai-workflow/slices/slice-06-executor-commit-recovery/slice.json +57 -0
  46. package/specs/quiver-v22-guided-ai-workflow/slices/slice-07-execution-waves-delegation/CLOSURE_BRIEF.md +32 -0
  47. package/specs/quiver-v22-guided-ai-workflow/slices/slice-07-execution-waves-delegation/EXECUTION_BRIEF.md +58 -0
  48. package/specs/quiver-v22-guided-ai-workflow/slices/slice-07-execution-waves-delegation/slice.json +55 -0
  49. package/specs/quiver-v22-guided-ai-workflow/slices/slice-08-pr-create-gh-ssh/CLOSURE_BRIEF.md +32 -0
  50. package/specs/quiver-v22-guided-ai-workflow/slices/slice-08-pr-create-gh-ssh/EXECUTION_BRIEF.md +58 -0
  51. package/specs/quiver-v22-guided-ai-workflow/slices/slice-08-pr-create-gh-ssh/slice.json +53 -0
  52. package/specs/quiver-v22-guided-ai-workflow/slices/slice-09-post-merge-cleanup-release-safety/CLOSURE_BRIEF.md +33 -0
  53. package/specs/quiver-v22-guided-ai-workflow/slices/slice-09-post-merge-cleanup-release-safety/EXECUTION_BRIEF.md +59 -0
  54. package/specs/quiver-v22-guided-ai-workflow/slices/slice-09-post-merge-cleanup-release-safety/slice.json +59 -0
  55. package/specs/quiver-v22-guided-ai-workflow/slices/slice-10-docs-smokes-release-readiness/CLOSURE_BRIEF.md +34 -0
  56. package/specs/quiver-v22-guided-ai-workflow/slices/slice-10-docs-smokes-release-readiness/EXECUTION_BRIEF.md +58 -0
  57. package/specs/quiver-v22-guided-ai-workflow/slices/slice-10-docs-smokes-release-readiness/slice.json +60 -0
  58. package/specs/quiver-v23-guided-flow-productization/EVIDENCE_REPORT.md +80 -0
  59. package/specs/quiver-v23-guided-flow-productization/EXECUTION_PLAN.md +80 -0
  60. package/specs/quiver-v23-guided-flow-productization/SPEC.md +203 -0
  61. package/specs/quiver-v23-guided-flow-productization/STATUS.md +39 -0
  62. package/specs/quiver-v23-guided-flow-productization/pr.md +119 -0
  63. package/specs/quiver-v23-guided-flow-productization/slices/slice-00-spec-foundation/CLOSURE_BRIEF.md +30 -0
  64. package/specs/quiver-v23-guided-flow-productization/slices/slice-00-spec-foundation/EXECUTION_BRIEF.md +61 -0
  65. package/specs/quiver-v23-guided-flow-productization/slices/slice-00-spec-foundation/slice.json +51 -0
  66. package/specs/quiver-v23-guided-flow-productization/slices/slice-01-short-command-and-flow-entrypoint/CLOSURE_BRIEF.md +33 -0
  67. package/specs/quiver-v23-guided-flow-productization/slices/slice-01-short-command-and-flow-entrypoint/EXECUTION_BRIEF.md +35 -0
  68. package/specs/quiver-v23-guided-flow-productization/slices/slice-01-short-command-and-flow-entrypoint/slice.json +56 -0
  69. package/specs/quiver-v23-guided-flow-productization/slices/slice-02-flow-status-wizard/CLOSURE_BRIEF.md +31 -0
  70. package/specs/quiver-v23-guided-flow-productization/slices/slice-02-flow-status-wizard/EXECUTION_BRIEF.md +29 -0
  71. package/specs/quiver-v23-guided-flow-productization/slices/slice-02-flow-status-wizard/slice.json +55 -0
  72. package/specs/quiver-v23-guided-flow-productization/slices/slice-03-agent-profiles/CLOSURE_BRIEF.md +33 -0
  73. package/specs/quiver-v23-guided-flow-productization/slices/slice-03-agent-profiles/EXECUTION_BRIEF.md +29 -0
  74. package/specs/quiver-v23-guided-flow-productization/slices/slice-03-agent-profiles/slice.json +54 -0
  75. package/specs/quiver-v23-guided-flow-productization/slices/slice-04-context-preparation-onboarding/CLOSURE_BRIEF.md +32 -0
  76. package/specs/quiver-v23-guided-flow-productization/slices/slice-04-context-preparation-onboarding/EXECUTION_BRIEF.md +30 -0
  77. package/specs/quiver-v23-guided-flow-productization/slices/slice-04-context-preparation-onboarding/slice.json +59 -0
  78. package/specs/quiver-v23-guided-flow-productization/slices/slice-05-planner-iteration-history/CLOSURE_BRIEF.md +31 -0
  79. package/specs/quiver-v23-guided-flow-productization/slices/slice-05-planner-iteration-history/EXECUTION_BRIEF.md +29 -0
  80. package/specs/quiver-v23-guided-flow-productization/slices/slice-05-planner-iteration-history/slice.json +53 -0
  81. package/specs/quiver-v23-guided-flow-productization/slices/slice-06-production-plan-review/CLOSURE_BRIEF.md +33 -0
  82. package/specs/quiver-v23-guided-flow-productization/slices/slice-06-production-plan-review/EXECUTION_BRIEF.md +30 -0
  83. package/specs/quiver-v23-guided-flow-productization/slices/slice-06-production-plan-review/slice.json +54 -0
  84. package/specs/quiver-v23-guided-flow-productization/slices/slice-07-spec-create-experience/CLOSURE_BRIEF.md +33 -0
  85. package/specs/quiver-v23-guided-flow-productization/slices/slice-07-spec-create-experience/EXECUTION_BRIEF.md +30 -0
  86. package/specs/quiver-v23-guided-flow-productization/slices/slice-07-spec-create-experience/slice.json +55 -0
  87. package/specs/quiver-v23-guided-flow-productization/slices/slice-08-executor-prompt-generation/CLOSURE_BRIEF.md +32 -0
  88. package/specs/quiver-v23-guided-flow-productization/slices/slice-08-executor-prompt-generation/EXECUTION_BRIEF.md +30 -0
  89. package/specs/quiver-v23-guided-flow-productization/slices/slice-08-executor-prompt-generation/slice.json +55 -0
  90. package/specs/quiver-v23-guided-flow-productization/slices/slice-09-delegated-slice-execution/CLOSURE_BRIEF.md +33 -0
  91. package/specs/quiver-v23-guided-flow-productization/slices/slice-09-delegated-slice-execution/EXECUTION_BRIEF.md +34 -0
  92. package/specs/quiver-v23-guided-flow-productization/slices/slice-09-delegated-slice-execution/slice.json +57 -0
  93. package/specs/quiver-v23-guided-flow-productization/slices/slice-10-docs-smokes-release-readiness/CLOSURE_BRIEF.md +33 -0
  94. package/specs/quiver-v23-guided-flow-productization/slices/slice-10-docs-smokes-release-readiness/EXECUTION_BRIEF.md +32 -0
  95. package/specs/quiver-v23-guided-flow-productization/slices/slice-10-docs-smokes-release-readiness/slice.json +63 -0
  96. package/specs/quiver-v24-dx-onboarding-hardening/EVIDENCE_REPORT.md +55 -0
  97. package/specs/quiver-v24-dx-onboarding-hardening/EXECUTION_PLAN.md +43 -0
  98. package/specs/quiver-v24-dx-onboarding-hardening/SPEC.md +149 -0
  99. package/specs/quiver-v24-dx-onboarding-hardening/STATUS.md +31 -0
  100. package/specs/quiver-v24-dx-onboarding-hardening/pr.md +76 -0
  101. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-00-spec-foundation/CLOSURE_BRIEF.md +31 -0
  102. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-00-spec-foundation/EXECUTION_BRIEF.md +52 -0
  103. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-00-spec-foundation/slice.json +51 -0
  104. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-01-init-template-hygiene/CLOSURE_BRIEF.md +38 -0
  105. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-01-init-template-hygiene/EXECUTION_BRIEF.md +53 -0
  106. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-01-init-template-hygiene/slice.json +55 -0
  107. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-02-cli-command-routing-version-errors/CLOSURE_BRIEF.md +33 -0
  108. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-02-cli-command-routing-version-errors/EXECUTION_BRIEF.md +50 -0
  109. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-02-cli-command-routing-version-errors/slice.json +52 -0
  110. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-03-doctor-fix-doc-link-checks/CLOSURE_BRIEF.md +33 -0
  111. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-03-doctor-fix-doc-link-checks/EXECUTION_BRIEF.md +50 -0
  112. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-03-doctor-fix-doc-link-checks/slice.json +53 -0
  113. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-04-prepare-output-ai-context-drafts/CLOSURE_BRIEF.md +33 -0
  114. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-04-prepare-output-ai-context-drafts/EXECUTION_BRIEF.md +50 -0
  115. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-04-prepare-output-ai-context-drafts/slice.json +70 -0
  116. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-05-local-slice-validation-base-guidance/CLOSURE_BRIEF.md +36 -0
  117. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-05-local-slice-validation-base-guidance/EXECUTION_BRIEF.md +49 -0
  118. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-05-local-slice-validation-base-guidance/slice.json +52 -0
  119. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-06-plan-graph-next-history-views/CLOSURE_BRIEF.md +43 -0
  120. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-06-plan-graph-next-history-views/EXECUTION_BRIEF.md +53 -0
  121. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-06-plan-graph-next-history-views/slice.json +60 -0
  122. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-07-analyzer-command-map-hardening/CLOSURE_BRIEF.md +32 -0
  123. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-07-analyzer-command-map-hardening/EXECUTION_BRIEF.md +50 -0
  124. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-07-analyzer-command-map-hardening/slice.json +51 -0
  125. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-08-evidence-run-command/CLOSURE_BRIEF.md +34 -0
  126. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-08-evidence-run-command/EXECUTION_BRIEF.md +52 -0
  127. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-08-evidence-run-command/slice.json +54 -0
  128. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-09-spec-viewer-demo-scaffolding/CLOSURE_BRIEF.md +34 -0
  129. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-09-spec-viewer-demo-scaffolding/EXECUTION_BRIEF.md +51 -0
  130. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-09-spec-viewer-demo-scaffolding/slice.json +59 -0
  131. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-10-docs-smokes-release-readiness/CLOSURE_BRIEF.md +33 -0
  132. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-10-docs-smokes-release-readiness/EXECUTION_BRIEF.md +54 -0
  133. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-10-docs-smokes-release-readiness/slice.json +76 -0
  134. package/src/create-quiver/commands/ai.js +508 -35
  135. package/src/create-quiver/commands/demo.js +22 -0
  136. package/src/create-quiver/commands/evidence.js +37 -0
  137. package/src/create-quiver/commands/flow.js +561 -0
  138. package/src/create-quiver/commands/graph.js +14 -1
  139. package/src/create-quiver/commands/next.js +28 -0
  140. package/src/create-quiver/commands/plan.js +6 -3
  141. package/src/create-quiver/commands/prepare.js +236 -0
  142. package/src/create-quiver/commands/spec.js +133 -0
  143. package/src/create-quiver/index.js +688 -25
  144. package/src/create-quiver/lib/agent-profiles.js +148 -0
  145. package/src/create-quiver/lib/ai/context-packs.js +12 -0
  146. package/src/create-quiver/lib/ai/execution-plan.js +370 -10
  147. package/src/create-quiver/lib/ai/executor.js +376 -17
  148. package/src/create-quiver/lib/ai/github.js +196 -0
  149. package/src/create-quiver/lib/ai/onboarding-template.js +365 -0
  150. package/src/create-quiver/lib/ai/plan-review.js +283 -0
  151. package/src/create-quiver/lib/ai/providers.js +1 -0
  152. package/src/create-quiver/lib/ai/safety.js +5 -0
  153. package/src/create-quiver/lib/ai/spec-templates.js +2 -2
  154. package/src/create-quiver/lib/approvals.js +350 -0
  155. package/src/create-quiver/lib/demo.js +657 -0
  156. package/src/create-quiver/lib/doctor.js +234 -0
  157. package/src/create-quiver/lib/evidence.js +115 -0
  158. package/src/create-quiver/lib/init-docs.js +284 -17
  159. package/src/create-quiver/lib/init-layout.js +26 -1
  160. package/src/create-quiver/lib/lifecycle.js +6 -0
  161. package/src/create-quiver/lib/package-safety.js +117 -0
  162. package/src/create-quiver/lib/readiness.js +85 -18
  163. package/src/create-quiver/lib/slice-graph.js +1 -0
  164. package/src/create-quiver/lib/slice.js +8 -8
  165. 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 summarizeSlice(node) {
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 buildLevelStrategy(levelNodes) {
96
- if (levelNodes.length > 1) {
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 summarizeLevel(levelNodes, index) {
112
- const slices = levelNodes.map(summarizeSlice);
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: levelNodes.length > 1,
123
- requires_temporary_worktrees: levelNodes.length > 1,
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
  };