create-quiver 0.9.0 → 0.10.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 (111) hide show
  1. package/README.md +312 -124
  2. package/README_FOR_AI.md +59 -45
  3. package/ROADMAP.md +12 -11
  4. package/docs/AI_ONBOARDING_PROMPT.md.template +120 -52
  5. package/docs/COMMANDS.md.template +41 -6
  6. package/docs/GITFLOW_PR_GUIDE.md.template +11 -0
  7. package/docs/STANDARD.md.template +1 -1
  8. package/docs/SUPPORT_MATRIX.md.template +4 -0
  9. package/docs/TROUBLESHOOTING.md.template +29 -1
  10. package/docs/WORKFLOW.md.template +1 -1
  11. package/package.json +6 -1
  12. package/package.template.json +11 -6
  13. package/scripts/check-pr-readiness.sh +1 -1
  14. package/scripts/check-scope.sh +0 -1
  15. package/scripts/check-slice-readiness.sh +3 -4
  16. package/scripts/init-docs.sh +55 -9
  17. package/specs/quiver-v19-self-install-dev-dep/EVIDENCE_REPORT.md +2 -2
  18. package/specs/quiver-v19-self-install-dev-dep/STATUS.md +4 -4
  19. package/specs/quiver-v19-self-install-dev-dep/slices/slice-01-auto-install-dev-dep/slice.json +4 -4
  20. package/specs/quiver-v20-ai-cli-orchestration/EVIDENCE_REPORT.md +23 -0
  21. package/specs/quiver-v20-ai-cli-orchestration/EXECUTION_PLAN.md +57 -0
  22. package/specs/quiver-v20-ai-cli-orchestration/SPEC.md +202 -0
  23. package/specs/quiver-v20-ai-cli-orchestration/STATUS.md +35 -0
  24. package/specs/quiver-v20-ai-cli-orchestration/pr.md +100 -0
  25. package/specs/quiver-v20-ai-cli-orchestration/slices/slice-00-spec-foundation/CLOSURE_BRIEF.md +30 -0
  26. package/specs/quiver-v20-ai-cli-orchestration/slices/slice-00-spec-foundation/EXECUTION_BRIEF.md +61 -0
  27. package/specs/quiver-v20-ai-cli-orchestration/slices/slice-00-spec-foundation/slice.json +54 -0
  28. package/specs/quiver-v20-ai-cli-orchestration/slices/slice-01-ai-provider-runner/CLOSURE_BRIEF.md +39 -0
  29. package/specs/quiver-v20-ai-cli-orchestration/slices/slice-01-ai-provider-runner/EXECUTION_BRIEF.md +63 -0
  30. package/specs/quiver-v20-ai-cli-orchestration/slices/slice-01-ai-provider-runner/slice.json +55 -0
  31. package/specs/quiver-v20-ai-cli-orchestration/slices/slice-02-context-packs-token-budget/CLOSURE_BRIEF.md +40 -0
  32. package/specs/quiver-v20-ai-cli-orchestration/slices/slice-02-context-packs-token-budget/EXECUTION_BRIEF.md +60 -0
  33. package/specs/quiver-v20-ai-cli-orchestration/slices/slice-02-context-packs-token-budget/slice.json +54 -0
  34. package/specs/quiver-v20-ai-cli-orchestration/slices/slice-03-ai-phase-gated-planner/CLOSURE_BRIEF.md +43 -0
  35. package/specs/quiver-v20-ai-cli-orchestration/slices/slice-03-ai-phase-gated-planner/EXECUTION_BRIEF.md +62 -0
  36. package/specs/quiver-v20-ai-cli-orchestration/slices/slice-03-ai-phase-gated-planner/slice.json +62 -0
  37. package/specs/quiver-v20-ai-cli-orchestration/slices/slice-04-spec-slice-handoff-pr-generation/CLOSURE_BRIEF.md +36 -0
  38. package/specs/quiver-v20-ai-cli-orchestration/slices/slice-04-spec-slice-handoff-pr-generation/EXECUTION_BRIEF.md +63 -0
  39. package/specs/quiver-v20-ai-cli-orchestration/slices/slice-04-spec-slice-handoff-pr-generation/slice.json +59 -0
  40. package/specs/quiver-v20-ai-cli-orchestration/slices/slice-05-execution-plan-parallel-worktrees/CLOSURE_BRIEF.md +32 -0
  41. package/specs/quiver-v20-ai-cli-orchestration/slices/slice-05-execution-plan-parallel-worktrees/EXECUTION_BRIEF.md +61 -0
  42. package/specs/quiver-v20-ai-cli-orchestration/slices/slice-05-execution-plan-parallel-worktrees/slice.json +59 -0
  43. package/specs/quiver-v20-ai-cli-orchestration/slices/slice-06-ai-execute-slice-scope-enforcement/CLOSURE_BRIEF.md +36 -0
  44. package/specs/quiver-v20-ai-cli-orchestration/slices/slice-06-ai-execute-slice-scope-enforcement/EXECUTION_BRIEF.md +64 -0
  45. package/specs/quiver-v20-ai-cli-orchestration/slices/slice-06-ai-execute-slice-scope-enforcement/slice.json +65 -0
  46. package/specs/quiver-v20-ai-cli-orchestration/slices/slice-07-github-pr-preflight/CLOSURE_BRIEF.md +36 -0
  47. package/specs/quiver-v20-ai-cli-orchestration/slices/slice-07-github-pr-preflight/EXECUTION_BRIEF.md +66 -0
  48. package/specs/quiver-v20-ai-cli-orchestration/slices/slice-07-github-pr-preflight/slice.json +63 -0
  49. package/specs/quiver-v20-ai-cli-orchestration/slices/slice-08-docs-smokes-release-readiness/CLOSURE_BRIEF.md +35 -0
  50. package/specs/quiver-v20-ai-cli-orchestration/slices/slice-08-docs-smokes-release-readiness/EXECUTION_BRIEF.md +64 -0
  51. package/specs/quiver-v20-ai-cli-orchestration/slices/slice-08-docs-smokes-release-readiness/slice.json +77 -0
  52. package/specs/quiver-v21-ai-first-layout/EVIDENCE_REPORT.md +31 -0
  53. package/specs/quiver-v21-ai-first-layout/EXECUTION_PLAN.md +185 -0
  54. package/specs/quiver-v21-ai-first-layout/SPEC.md +212 -0
  55. package/specs/quiver-v21-ai-first-layout/STATUS.md +37 -0
  56. package/specs/quiver-v21-ai-first-layout/pr.md +110 -0
  57. package/specs/quiver-v21-ai-first-layout/slices/slice-00-spec-foundation/CLOSURE_BRIEF.md +30 -0
  58. package/specs/quiver-v21-ai-first-layout/slices/slice-00-spec-foundation/EXECUTION_BRIEF.md +63 -0
  59. package/specs/quiver-v21-ai-first-layout/slices/slice-00-spec-foundation/slice.json +45 -0
  60. package/specs/quiver-v21-ai-first-layout/slices/slice-01-init-profiles-dry-run/CLOSURE_BRIEF.md +31 -0
  61. package/specs/quiver-v21-ai-first-layout/slices/slice-01-init-profiles-dry-run/EXECUTION_BRIEF.md +59 -0
  62. package/specs/quiver-v21-ai-first-layout/slices/slice-01-init-profiles-dry-run/slice.json +57 -0
  63. package/specs/quiver-v21-ai-first-layout/slices/slice-02-internal-layout-template-resolver/CLOSURE_BRIEF.md +32 -0
  64. package/specs/quiver-v21-ai-first-layout/slices/slice-02-internal-layout-template-resolver/EXECUTION_BRIEF.md +60 -0
  65. package/specs/quiver-v21-ai-first-layout/slices/slice-02-internal-layout-template-resolver/slice.json +58 -0
  66. package/specs/quiver-v21-ai-first-layout/slices/slice-03-generation-profiles-visible-contract/CLOSURE_BRIEF.md +34 -0
  67. package/specs/quiver-v21-ai-first-layout/slices/slice-03-generation-profiles-visible-contract/EXECUTION_BRIEF.md +61 -0
  68. package/specs/quiver-v21-ai-first-layout/slices/slice-03-generation-profiles-visible-contract/slice.json +64 -0
  69. package/specs/quiver-v21-ai-first-layout/slices/slice-04-analyze-scan-relocation/CLOSURE_BRIEF.md +32 -0
  70. package/specs/quiver-v21-ai-first-layout/slices/slice-04-analyze-scan-relocation/EXECUTION_BRIEF.md +58 -0
  71. package/specs/quiver-v21-ai-first-layout/slices/slice-04-analyze-scan-relocation/slice.json +64 -0
  72. package/specs/quiver-v21-ai-first-layout/slices/slice-05-empty-specs-layout-doctor/CLOSURE_BRIEF.md +32 -0
  73. package/specs/quiver-v21-ai-first-layout/slices/slice-05-empty-specs-layout-doctor/EXECUTION_BRIEF.md +60 -0
  74. package/specs/quiver-v21-ai-first-layout/slices/slice-05-empty-specs-layout-doctor/slice.json +65 -0
  75. package/specs/quiver-v21-ai-first-layout/slices/slice-06-legacy-migration-optional-assets/CLOSURE_BRIEF.md +31 -0
  76. package/specs/quiver-v21-ai-first-layout/slices/slice-06-legacy-migration-optional-assets/EXECUTION_BRIEF.md +62 -0
  77. package/specs/quiver-v21-ai-first-layout/slices/slice-06-legacy-migration-optional-assets/slice.json +66 -0
  78. package/specs/quiver-v21-ai-first-layout/slices/slice-07-docs-guidance-alignment/CLOSURE_BRIEF.md +33 -0
  79. package/specs/quiver-v21-ai-first-layout/slices/slice-07-docs-guidance-alignment/EXECUTION_BRIEF.md +61 -0
  80. package/specs/quiver-v21-ai-first-layout/slices/slice-07-docs-guidance-alignment/slice.json +67 -0
  81. package/specs/quiver-v21-ai-first-layout/slices/slice-08-smokes-release-readiness/CLOSURE_BRIEF.md +35 -0
  82. package/specs/quiver-v21-ai-first-layout/slices/slice-08-smokes-release-readiness/EXECUTION_BRIEF.md +66 -0
  83. package/specs/quiver-v21-ai-first-layout/slices/slice-08-smokes-release-readiness/slice.json +62 -0
  84. package/src/create-quiver/commands/ai.js +442 -0
  85. package/src/create-quiver/index.js +421 -84
  86. package/src/create-quiver/lib/ai/context-packs.js +158 -0
  87. package/src/create-quiver/lib/ai/execution-plan.js +254 -0
  88. package/src/create-quiver/lib/ai/executor.js +323 -0
  89. package/src/create-quiver/lib/ai/github.js +329 -0
  90. package/src/create-quiver/lib/ai/phase-gates.js +72 -0
  91. package/src/create-quiver/lib/ai/preflight.js +58 -0
  92. package/src/create-quiver/lib/ai/prompt-transport.js +81 -0
  93. package/src/create-quiver/lib/ai/prompts.js +39 -0
  94. package/src/create-quiver/lib/ai/providers.js +314 -0
  95. package/src/create-quiver/lib/ai/safety.js +151 -0
  96. package/src/create-quiver/lib/ai/spec-generator.js +314 -0
  97. package/src/create-quiver/lib/ai/spec-templates.js +715 -0
  98. package/src/create-quiver/lib/doctor.js +114 -0
  99. package/src/create-quiver/lib/git.js +21 -0
  100. package/src/create-quiver/lib/init-docs.js +286 -25
  101. package/src/create-quiver/lib/init-layout.js +426 -0
  102. package/src/create-quiver/lib/lifecycle.js +2 -2
  103. package/src/create-quiver/lib/paths.js +63 -2
  104. package/src/create-quiver/lib/project-scan.js +66 -0
  105. package/src/create-quiver/lib/readiness.js +4 -2
  106. package/src/create-quiver/lib/scope.js +125 -0
  107. package/src/create-quiver/lib/slice-graph.js +6 -0
  108. package/src/create-quiver/lib/slice.js +51 -8
  109. package/src/create-quiver/lib/state.js +18 -1
  110. package/src/create-quiver/lib/template-resolver.js +74 -0
  111. package/.claude/settings.local.json +0 -52
@@ -0,0 +1,158 @@
1
+ const { filterContextPaths, shouldExcludeContextPath } = require('./safety');
2
+ const { buildRolePrompt } = require('./prompts');
3
+ const { readProjectScanArtifact } = require('../project-scan');
4
+
5
+ const ROLES = Object.freeze({
6
+ PLANNER: 'planner',
7
+ EXECUTOR: 'executor',
8
+ });
9
+
10
+ const CONTEXT_PACKS = Object.freeze({
11
+ full: Object.freeze({
12
+ name: 'full',
13
+ description: 'Broad planner onboarding context.',
14
+ role: ROLES.PLANNER,
15
+ tokenBudgetHint: 14000,
16
+ roleGuidance: 'Use broad onboarding context, project map, workflow docs, and relevant specs.',
17
+ }),
18
+ planning: Object.freeze({
19
+ name: 'planning',
20
+ description: 'Focused planner context for acceptance criteria and technical planning.',
21
+ role: ROLES.PLANNER,
22
+ tokenBudgetHint: 8000,
23
+ roleGuidance: 'Use project map, workflow docs, and only the specs needed for the current planning step.',
24
+ }),
25
+ slice: Object.freeze({
26
+ name: 'slice',
27
+ description: 'Executor context for a single slice handoff.',
28
+ role: ROLES.EXECUTOR,
29
+ tokenBudgetHint: 3200,
30
+ roleGuidance: 'Use slice handoff, allowed files, acceptance criteria, and validation commands only.',
31
+ }),
32
+ minimal: Object.freeze({
33
+ name: 'minimal',
34
+ description: 'Smallest executor context for narrowly-scoped tasks.',
35
+ role: ROLES.EXECUTOR,
36
+ tokenBudgetHint: 1200,
37
+ roleGuidance: 'Use the smallest safe set of slice details and avoid onboarding context.',
38
+ }),
39
+ });
40
+
41
+ const DEFAULT_CONTEXT_PACK_BY_ROLE = Object.freeze({
42
+ [ROLES.PLANNER]: 'planning',
43
+ [ROLES.EXECUTOR]: 'slice',
44
+ });
45
+
46
+ const PACK_ORDER = ['full', 'planning', 'slice', 'minimal'];
47
+
48
+ function normalizeRole(role) {
49
+ const value = String(role || '').trim().toLowerCase();
50
+ if (value === ROLES.PLANNER || value === ROLES.EXECUTOR) {
51
+ return value;
52
+ }
53
+ throw new Error(`create-quiver: unsupported role '${role}'. Expected planner or executor.`);
54
+ }
55
+
56
+ function normalizePackName(packName) {
57
+ const value = String(packName || '').trim().toLowerCase();
58
+ if (CONTEXT_PACKS[value]) {
59
+ return value;
60
+ }
61
+ throw new Error(`create-quiver: unsupported context pack '${packName}'. Expected one of: ${PACK_ORDER.join(', ')}`);
62
+ }
63
+
64
+ function getDefaultContextPack(role) {
65
+ const normalizedRole = normalizeRole(role);
66
+ return DEFAULT_CONTEXT_PACK_BY_ROLE[normalizedRole];
67
+ }
68
+
69
+ function resolveContextPack({ role, packName } = {}) {
70
+ const normalizedRole = normalizeRole(role);
71
+ const defaultPack = getDefaultContextPack(normalizedRole);
72
+ const resolvedPackName = packName ? normalizePackName(packName) : defaultPack;
73
+
74
+ if (normalizedRole === ROLES.EXECUTOR && resolvedPackName === 'full') {
75
+ throw new Error('create-quiver: executor context cannot use the full pack by default.');
76
+ }
77
+
78
+ const pack = CONTEXT_PACKS[resolvedPackName];
79
+ if (pack.role !== normalizedRole && !(normalizedRole === ROLES.PLANNER && resolvedPackName === 'slice')) {
80
+ throw new Error(`create-quiver: context pack '${resolvedPackName}' is not valid for role '${normalizedRole}'.`);
81
+ }
82
+
83
+ return {
84
+ role: normalizedRole,
85
+ packName: resolvedPackName,
86
+ defaultPack,
87
+ isDefault: resolvedPackName === defaultPack,
88
+ tokenBudgetHint: pack.tokenBudgetHint,
89
+ pack,
90
+ };
91
+ }
92
+
93
+ function buildPackSelection({ role, packName, paths = [] } = {}) {
94
+ const resolved = resolveContextPack({ role, packName });
95
+ const { included, excluded } = filterContextPaths(paths);
96
+
97
+ return {
98
+ ...resolved,
99
+ includedPaths: included,
100
+ excludedPaths: excluded,
101
+ };
102
+ }
103
+
104
+ function resolveScanArtifactMetadata(repoRoot) {
105
+ if (!repoRoot) {
106
+ return null;
107
+ }
108
+
109
+ const artifact = readProjectScanArtifact(repoRoot);
110
+ if (!artifact) {
111
+ return null;
112
+ }
113
+
114
+ return {
115
+ path: artifact.relativePath,
116
+ source: artifact.source,
117
+ };
118
+ }
119
+
120
+ function buildContextPackMetadata(options = {}) {
121
+ const selection = buildPackSelection(options);
122
+
123
+ return {
124
+ role: selection.role,
125
+ packName: selection.packName,
126
+ isDefault: selection.isDefault,
127
+ tokenBudgetHint: selection.tokenBudgetHint,
128
+ description: selection.pack.description,
129
+ includedPaths: selection.includedPaths,
130
+ excludedPaths: selection.excludedPaths,
131
+ scanArtifact: resolveScanArtifactMetadata(options.repoRoot),
132
+ prompt: buildRolePrompt(selection.role, selection.pack),
133
+ };
134
+ }
135
+
136
+ function selectSafePaths(paths, options = {}) {
137
+ const selection = buildPackSelection({ ...options, paths });
138
+ return {
139
+ included: selection.includedPaths,
140
+ excluded: selection.excludedPaths,
141
+ };
142
+ }
143
+
144
+ module.exports = {
145
+ CONTEXT_PACKS,
146
+ DEFAULT_CONTEXT_PACK_BY_ROLE,
147
+ PACK_ORDER,
148
+ ROLES,
149
+ buildContextPackMetadata,
150
+ buildPackSelection,
151
+ getDefaultContextPack,
152
+ normalizePackName,
153
+ normalizeRole,
154
+ resolveScanArtifactMetadata,
155
+ resolveContextPack,
156
+ selectSafePaths,
157
+ shouldExcludeContextPath,
158
+ };
@@ -0,0 +1,254 @@
1
+ const { buildGraph, computeLevels, detectFileConflicts, isFoundationSliceId, readAllSlices, topoSort, SliceGraphError } = require('../slice-graph');
2
+
3
+ const EXCLUDED_STATUSES = new Set(['completed', 'skipped', 'cancelled']);
4
+
5
+ function formatError(message) {
6
+ return `create-quiver: ${message}`;
7
+ }
8
+
9
+ function summarizeSlice(node) {
10
+ return {
11
+ ref: node.ref,
12
+ spec_slug: node.specSlug,
13
+ slice_id: node.sliceId,
14
+ title: node.title || node.sliceId,
15
+ status: node.status || 'draft',
16
+ files: Array.isArray(node.files) ? node.files : [],
17
+ depends_on: Array.isArray(node.depends_on) ? node.depends_on : [],
18
+ parallel_safe: node.parallel_safe || null,
19
+ parallel_safe_reason: node.parallel_safe_reason || null,
20
+ branch_name: node.json?.git?.branch_name || '',
21
+ };
22
+ }
23
+
24
+ function buildPendingGraph(graph, options = {}) {
25
+ const specSlug = options.specSlug ? String(options.specSlug).trim() : '';
26
+ const pendingNodes = graph.nodes.filter((node) => {
27
+ if (specSlug && node.specSlug !== specSlug) {
28
+ return false;
29
+ }
30
+ return !EXCLUDED_STATUSES.has(String(node.status || '').toLowerCase());
31
+ });
32
+ const pendingRefs = new Set(pendingNodes.map((node) => node.ref));
33
+ const nodesBySpec = new Map();
34
+ const foundationRefsBySpec = new Map();
35
+
36
+ for (const node of pendingNodes) {
37
+ if (!nodesBySpec.has(node.specSlug)) {
38
+ nodesBySpec.set(node.specSlug, []);
39
+ }
40
+ nodesBySpec.get(node.specSlug).push(node);
41
+
42
+ if (isFoundationSliceId(node.sliceId)) {
43
+ if (!foundationRefsBySpec.has(node.specSlug)) {
44
+ foundationRefsBySpec.set(node.specSlug, []);
45
+ }
46
+ foundationRefsBySpec.get(node.specSlug).push(node.ref);
47
+ }
48
+ }
49
+
50
+ const edgeKey = (from, to) => `${from}::${to}`;
51
+ const edges = [];
52
+ const seenEdges = new Set();
53
+
54
+ for (const edge of graph.edges) {
55
+ if (!pendingRefs.has(edge.from) || !pendingRefs.has(edge.to)) {
56
+ continue;
57
+ }
58
+ const key = edgeKey(edge.from, edge.to);
59
+ if (seenEdges.has(key)) {
60
+ continue;
61
+ }
62
+ seenEdges.add(key);
63
+ edges.push({ from: edge.from, to: edge.to });
64
+ }
65
+
66
+ for (const [specSlug, specNodes] of nodesBySpec.entries()) {
67
+ const foundationRefs = foundationRefsBySpec.get(specSlug) || [];
68
+ if (foundationRefs.length === 0) {
69
+ continue;
70
+ }
71
+
72
+ for (const node of specNodes) {
73
+ if (isFoundationSliceId(node.sliceId)) {
74
+ continue;
75
+ }
76
+
77
+ for (const foundationRef of foundationRefs) {
78
+ const key = edgeKey(foundationRef, node.ref);
79
+ if (seenEdges.has(key)) {
80
+ continue;
81
+ }
82
+ seenEdges.add(key);
83
+ edges.push({ from: foundationRef, to: node.ref, implicit: true, reason: 'slice-00 foundation barrier' });
84
+ }
85
+ }
86
+ }
87
+
88
+ return {
89
+ nodes: pendingNodes,
90
+ edges,
91
+ foundationRefs: Array.from(foundationRefsBySpec.values()).flat().sort((left, right) => String(left).localeCompare(String(right))),
92
+ };
93
+ }
94
+
95
+ function buildLevelStrategy(levelNodes) {
96
+ if (levelNodes.length > 1) {
97
+ return {
98
+ mode: 'temporary-per-slice',
99
+ temporary_worktrees: true,
100
+ reason: 'Run each ready slice in its own temporary worktree, then integrate the commits in stable level order.',
101
+ };
102
+ }
103
+
104
+ return {
105
+ mode: 'shared-worktree',
106
+ temporary_worktrees: false,
107
+ reason: 'A single ready slice can reuse the active worktree.',
108
+ };
109
+ }
110
+
111
+ function summarizeLevel(levelNodes, index) {
112
+ const slices = levelNodes.map(summarizeSlice);
113
+ const sliceRefs = slices.map((slice) => slice.ref);
114
+ const conflicts = detectFileConflicts(levelNodes).map((group) => ({
115
+ files: group.files,
116
+ slices: group.slices,
117
+ }));
118
+
119
+ return {
120
+ index,
121
+ slice_refs: sliceRefs,
122
+ parallel_ready: levelNodes.length > 1,
123
+ requires_temporary_worktrees: levelNodes.length > 1,
124
+ worktree_strategy: buildLevelStrategy(levelNodes),
125
+ conflicts,
126
+ slices,
127
+ };
128
+ }
129
+
130
+ function collectExecutionPlan(repoRoot, options = {}) {
131
+ try {
132
+ const allSlices = readAllSlices(repoRoot);
133
+ const graph = buildGraph(allSlices);
134
+ topoSort(graph);
135
+
136
+ const specSlug = options.specSlug ? String(options.specSlug).trim() : '';
137
+ const pendingGraph = buildPendingGraph(graph, { specSlug });
138
+ if (pendingGraph.nodes.length === 0) {
139
+ return {
140
+ root: repoRoot,
141
+ summary: {
142
+ total_slices: 0,
143
+ ready_levels: 0,
144
+ parallel_levels: 0,
145
+ foundation_refs: [],
146
+ },
147
+ ready_levels: [],
148
+ levels: [],
149
+ execution_order: [],
150
+ integration_order: [],
151
+ sequential_order: [],
152
+ };
153
+ }
154
+
155
+ const readyLevels = computeLevels(pendingGraph);
156
+ const readyLevelReports = readyLevels.map((levelNodes, index) => summarizeLevel(levelNodes, index));
157
+ const executionOrder = topoSort(pendingGraph).map((node) => node.ref);
158
+ const integrationOrder = readyLevelReports.flatMap((level) => level.slice_refs);
159
+ const foundationRefs = pendingGraph.foundationRefs;
160
+
161
+ return {
162
+ root: repoRoot,
163
+ summary: {
164
+ total_slices: pendingGraph.nodes.length,
165
+ ready_levels: readyLevelReports.length,
166
+ parallel_levels: readyLevelReports.filter((level) => level.parallel_ready).length,
167
+ foundation_refs: foundationRefs,
168
+ },
169
+ ready_levels: readyLevelReports,
170
+ levels: readyLevelReports,
171
+ execution_order: executionOrder,
172
+ integration_order: integrationOrder,
173
+ sequential_order: executionOrder,
174
+ options: {
175
+ specSlug,
176
+ },
177
+ };
178
+ } catch (error) {
179
+ if (error instanceof SliceGraphError) {
180
+ throw error;
181
+ }
182
+ throw new Error(formatError(`unable to build execution plan: ${error.message || error}`));
183
+ }
184
+ }
185
+
186
+ function formatHumanExecutionPlan(report) {
187
+ const lines = [
188
+ 'Execution plan',
189
+ `Total slices: ${report.summary.total_slices}`,
190
+ `Ready levels: ${report.summary.ready_levels}`,
191
+ `Parallel levels: ${report.summary.parallel_levels}`,
192
+ `Foundation refs: ${report.summary.foundation_refs.length > 0 ? report.summary.foundation_refs.join(', ') : '-'}`,
193
+ '',
194
+ 'Sequential order',
195
+ ];
196
+
197
+ if (report.sequential_order.length === 0) {
198
+ lines.push('- none');
199
+ } else {
200
+ for (const [index, ref] of report.sequential_order.entries()) {
201
+ lines.push(`${index + 1}. ${ref}`);
202
+ }
203
+ }
204
+
205
+ lines.push('', 'Ready levels');
206
+
207
+ if (report.ready_levels.length === 0) {
208
+ lines.push('- none');
209
+ return `${lines.join('\n')}\n`;
210
+ }
211
+
212
+ for (const level of report.ready_levels) {
213
+ const modeLabel = level.parallel_ready ? 'parallel' : 'sequential';
214
+ lines.push(`Level ${level.index} (${modeLabel})`);
215
+ lines.push(`Worktree strategy: ${level.worktree_strategy.mode}`);
216
+
217
+ for (const slice of level.slices) {
218
+ lines.push(`- ${slice.ref} [${slice.status}]`);
219
+ }
220
+
221
+ if (level.conflicts.length > 0) {
222
+ lines.push('Conflicts:');
223
+ for (const conflict of level.conflicts) {
224
+ lines.push(`- ${conflict.slices.join(', ')} :: ${conflict.files.join(', ')}`);
225
+ }
226
+ }
227
+
228
+ lines.push('');
229
+ }
230
+
231
+ lines.push('Integration order');
232
+ for (const [index, ref] of report.integration_order.entries()) {
233
+ lines.push(`${index + 1}. ${ref}`);
234
+ }
235
+
236
+ return `${lines.join('\n')}\n`;
237
+ }
238
+
239
+ function runExecutionPlan(repoRoot, options = {}) {
240
+ const report = collectExecutionPlan(repoRoot, options);
241
+ if (options.json) {
242
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
243
+ return report;
244
+ }
245
+
246
+ process.stdout.write(formatHumanExecutionPlan(report, options));
247
+ return report;
248
+ }
249
+
250
+ module.exports = {
251
+ collectExecutionPlan,
252
+ formatHumanExecutionPlan,
253
+ runExecutionPlan,
254
+ };