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,442 @@
1
+ const fs = require('node:fs');
2
+ const path = require('node:path');
3
+
4
+ const { buildContextPackMetadata, normalizeRole } = require('../lib/ai/context-packs');
5
+ const { runExecuteSlice } = require('../lib/ai/executor');
6
+ const { formatPreflightReport, preflightGitHubPr } = require('../lib/ai/github');
7
+ const { buildSpecGenerationManifest, describeSpecGeneration, generateSpecArtifacts } = require('../lib/ai/spec-generator');
8
+ const { buildProviderInvocation, runProvider } = require('../lib/ai/providers');
9
+ const { assertPlannerPhaseReady, getPlannerPhaseDetails, normalizePlannerPhase, PlannerPhaseError } = require('../lib/ai/phase-gates');
10
+
11
+ const DEFAULT_ONBOARD_PROVIDER = 'codex';
12
+ const DEFAULT_ONBOARD_ROLE = 'planner';
13
+ const DEFAULT_ONBOARD_CONTEXT = 'full';
14
+ const DEFAULT_PLAN_PROVIDER = 'codex';
15
+ const DEFAULT_PLAN_ROLE = 'planner';
16
+ const DEFAULT_PLAN_CONTEXT = 'planning';
17
+ const DEFAULT_PLAN_PHASE = 'acceptance';
18
+
19
+ function formatError(message) {
20
+ return `create-quiver: ${message}`;
21
+ }
22
+
23
+ function readTextFile(filePath, repoRoot) {
24
+ if (!filePath) {
25
+ return '';
26
+ }
27
+
28
+ const resolved = path.resolve(repoRoot, filePath);
29
+ if (!fs.existsSync(resolved)) {
30
+ throw new Error(formatError(`missing input file: ${filePath}`));
31
+ }
32
+
33
+ return fs.readFileSync(resolved, 'utf8');
34
+ }
35
+
36
+ function normalizeTimeout(timeoutMs) {
37
+ if (timeoutMs === undefined || timeoutMs === null || timeoutMs === '') {
38
+ return undefined;
39
+ }
40
+
41
+ const parsed = Number(timeoutMs);
42
+ if (!Number.isFinite(parsed) || parsed <= 0) {
43
+ throw new Error(formatError(`invalid timeout value: ${timeoutMs}`));
44
+ }
45
+
46
+ return parsed;
47
+ }
48
+
49
+ function buildPlanContext({ role, context, phase, inputText, inputPath, repoRoot }) {
50
+ const phaseDetails = getPlannerPhaseDetails(phase);
51
+ const pack = buildContextPackMetadata({
52
+ role,
53
+ packName: context || phaseDetails.contextPack,
54
+ repoRoot,
55
+ });
56
+ const relativeInputPath = inputPath ? path.relative(repoRoot, path.resolve(repoRoot, inputPath)).split(path.sep).join('/') : '';
57
+ const sections = [
58
+ pack.prompt,
59
+ `Phase: ${phaseDetails.phase}`,
60
+ phaseDetails.phase === 'acceptance'
61
+ ? 'Task: produce acceptance criteria only. Do not create files or modify product code.'
62
+ : 'Task: produce a technical plan only. Do not create files or modify product code.',
63
+ ];
64
+
65
+ if (relativeInputPath) {
66
+ sections.push(`Input file: ${relativeInputPath}`);
67
+ }
68
+
69
+ if (pack.scanArtifact) {
70
+ sections.push(`Project scan artifact: ${pack.scanArtifact.path} (${pack.scanArtifact.source})`);
71
+ }
72
+
73
+ if (inputText) {
74
+ sections.push('Input:', inputText.trimEnd());
75
+ }
76
+
77
+ return {
78
+ pack,
79
+ prompt: sections.join('\n\n'),
80
+ phaseDetails,
81
+ };
82
+ }
83
+
84
+ function buildOnboardContext({ role, context, inputText, inputPath, repoRoot }) {
85
+ const pack = buildContextPackMetadata({
86
+ role,
87
+ packName: context || DEFAULT_ONBOARD_CONTEXT,
88
+ repoRoot,
89
+ });
90
+ const relativeInputPath = inputPath ? path.relative(repoRoot, path.resolve(repoRoot, inputPath)).split(path.sep).join('/') : '';
91
+ const sections = [
92
+ pack.prompt,
93
+ 'Task: onboard the project context for planning.',
94
+ 'Read Quiver context, the WDD/SDD workflow, the project scan/map, assumptions, risks, and relevant docs.',
95
+ 'Do not modify product code.',
96
+ ];
97
+
98
+ if (relativeInputPath) {
99
+ sections.push(`Input file: ${relativeInputPath}`);
100
+ }
101
+
102
+ if (pack.scanArtifact) {
103
+ sections.push(`Project scan artifact: ${pack.scanArtifact.path} (${pack.scanArtifact.source})`);
104
+ }
105
+
106
+ if (inputText) {
107
+ sections.push('Input:', inputText.trimEnd());
108
+ }
109
+
110
+ return {
111
+ pack,
112
+ prompt: sections.join('\n\n'),
113
+ };
114
+ }
115
+
116
+ function formatDryRunReport({ task, provider, role, contextPack, phase, invocation }) {
117
+ const lines = [
118
+ `AI ${task} dry-run`,
119
+ `Provider: ${provider}`,
120
+ `Role: ${role}`,
121
+ `Context pack: ${contextPack}`,
122
+ ];
123
+
124
+ if (phase) {
125
+ lines.push(`Phase: ${phase}`);
126
+ }
127
+
128
+ lines.push(`Command: ${invocation.command} ${invocation.args.join(' ')}`);
129
+ lines.push(`Timeout: ${invocation.timeoutMs}ms`);
130
+ lines.push(`Prompt transport: ${invocation.promptTransport.mode}`);
131
+ lines.push(`Prompt length: ${invocation.promptLength} bytes`);
132
+
133
+ return `${lines.join('\n')}\n`;
134
+ }
135
+
136
+ function writeProviderOutput(result) {
137
+ if (result.stdout) {
138
+ process.stdout.write(result.stdout);
139
+ }
140
+ if (result.stderr) {
141
+ process.stderr.write(result.stderr);
142
+ }
143
+ }
144
+
145
+ function formatSpecDryRunReport({ manifest, repoRoot }) {
146
+ const preview = describeSpecGeneration(manifest, repoRoot);
147
+ const relativeSpecDir = path.relative(repoRoot, preview.specDir).split(path.sep).join('/');
148
+ const lines = [
149
+ 'AI plan dry-run',
150
+ 'Phase: spec',
151
+ `Spec slug: ${manifest.slug}`,
152
+ `Title: ${manifest.title}`,
153
+ `Input file: ${manifest.sourcePath}`,
154
+ `Target: ${relativeSpecDir}`,
155
+ `Planned files: ${preview.files.length}`,
156
+ ];
157
+
158
+ for (const file of preview.files) {
159
+ lines.push(`- ${file}`);
160
+ }
161
+
162
+ return `${lines.join('\n')}\n`;
163
+ }
164
+
165
+ function formatSpecGenerationResult(result, repoRoot) {
166
+ const relativeSpecDir = path.relative(repoRoot, result.specDir).split(path.sep).join('/');
167
+ const lines = [
168
+ 'AI plan spec generation completed',
169
+ `Spec slug: ${result.manifest.slug}`,
170
+ `Target: ${relativeSpecDir}`,
171
+ `Files written: ${result.files.length}`,
172
+ ];
173
+
174
+ for (const filePath of result.files) {
175
+ lines.push(`- ${path.relative(repoRoot, filePath).split(path.sep).join('/')}`);
176
+ }
177
+
178
+ return `${lines.join('\n')}\n`;
179
+ }
180
+
181
+ function annotateProviderError(error, scope, phase) {
182
+ const phaseLabel = phase ? ` phase '${phase}'` : '';
183
+ const message = error && error.message ? error.message : String(error);
184
+ const wrapped = new Error(formatError(`ai ${scope}${phaseLabel} failed: ${message}`));
185
+ wrapped.cause = error;
186
+ wrapped.code = error && error.code ? error.code : 'AI_PROVIDER_ERROR';
187
+ wrapped.details = error && error.details ? error.details : undefined;
188
+ return wrapped;
189
+ }
190
+
191
+ function annotateGitHubError(error, scope) {
192
+ const message = error && error.message ? error.message : String(error);
193
+ const wrapped = new Error(formatError(`ai ${scope} failed: ${message}`));
194
+ wrapped.cause = error;
195
+ wrapped.code = error && error.code ? error.code : 'AI_GITHUB_PR_ERROR';
196
+ wrapped.details = error && error.details ? error.details : undefined;
197
+ return wrapped;
198
+ }
199
+
200
+ async function runOnboard(repoRoot, options = {}) {
201
+ const provider = String(options.provider || DEFAULT_ONBOARD_PROVIDER).trim().toLowerCase();
202
+ const role = normalizeRole(options.role || DEFAULT_ONBOARD_ROLE);
203
+ const context = options.context || DEFAULT_ONBOARD_CONTEXT;
204
+ const timeoutMs = normalizeTimeout(options.timeout);
205
+ const inputText = readTextFile(options.input, repoRoot);
206
+ const prompt = buildOnboardContext({ role, context, inputText, inputPath: options.input, repoRoot }).prompt;
207
+ let invocation;
208
+
209
+ try {
210
+ invocation = buildProviderInvocation(provider, {
211
+ prompt,
212
+ cwd: repoRoot,
213
+ timeoutMs,
214
+ });
215
+ } catch (error) {
216
+ throw annotateProviderError(error, 'onboard');
217
+ }
218
+
219
+ if (options.dryRun) {
220
+ const report = {
221
+ task: 'onboard',
222
+ provider,
223
+ role,
224
+ contextPack: context,
225
+ invocation,
226
+ };
227
+ process.stdout.write(formatDryRunReport(report));
228
+ return report;
229
+ }
230
+
231
+ let result;
232
+ try {
233
+ result = await (options.runProviderFn || runProvider)(provider, {
234
+ prompt,
235
+ cwd: repoRoot,
236
+ timeoutMs,
237
+ dryRun: false,
238
+ probe: options.probe,
239
+ spawn: options.spawn,
240
+ tempRoot: options.tempRoot,
241
+ tempFileName: options.tempFileName,
242
+ tempFilePrefix: options.tempFilePrefix,
243
+ });
244
+ } catch (error) {
245
+ throw annotateProviderError(error, 'onboard');
246
+ }
247
+
248
+ writeProviderOutput(result);
249
+
250
+ if (!result.ok) {
251
+ throw annotateProviderError(result.error || new Error('provider run failed'), 'onboard');
252
+ }
253
+
254
+ return {
255
+ task: 'onboard',
256
+ provider,
257
+ role,
258
+ contextPack: context,
259
+ invocation,
260
+ result,
261
+ };
262
+ }
263
+
264
+ async function runPlan(repoRoot, options = {}) {
265
+ const phase = normalizePlannerPhase(options.phase || DEFAULT_PLAN_PHASE);
266
+ const role = normalizeRole(options.role || DEFAULT_PLAN_ROLE);
267
+ const provider = String(options.provider || DEFAULT_PLAN_PROVIDER).trim().toLowerCase();
268
+ const context = options.context || DEFAULT_PLAN_CONTEXT;
269
+ const timeoutMs = normalizeTimeout(options.timeout);
270
+
271
+ if (!options.input) {
272
+ throw new Error(formatError(`missing input file for ai plan phase '${phase}'`));
273
+ }
274
+
275
+ if (phase === 'spec') {
276
+ const inputText = readTextFile(options.input, repoRoot);
277
+ const manifest = buildSpecGenerationManifest({
278
+ inputPath: options.input,
279
+ inputText,
280
+ repoRoot,
281
+ specSlug: options.specSlug,
282
+ });
283
+
284
+ if (options.dryRun) {
285
+ const report = {
286
+ task: 'plan',
287
+ phase,
288
+ manifest,
289
+ };
290
+ process.stdout.write(formatSpecDryRunReport({ manifest, repoRoot }));
291
+ return report;
292
+ }
293
+
294
+ const result = generateSpecArtifacts(repoRoot, {
295
+ input: options.input,
296
+ specSlug: options.specSlug,
297
+ });
298
+ process.stdout.write(formatSpecGenerationResult(result, repoRoot));
299
+
300
+ return {
301
+ task: 'plan',
302
+ phase,
303
+ specSlug: result.manifest.slug,
304
+ specDir: path.relative(repoRoot, result.specDir).split(path.sep).join('/'),
305
+ files: result.files.map((filePath) => path.relative(repoRoot, filePath).split(path.sep).join('/')),
306
+ manifest: result.manifest,
307
+ };
308
+ }
309
+
310
+ assertPlannerPhaseReady(phase);
311
+
312
+ const inputText = readTextFile(options.input, repoRoot);
313
+ const contextInfo = buildPlanContext({
314
+ role,
315
+ context,
316
+ phase,
317
+ inputText,
318
+ inputPath: options.input,
319
+ repoRoot,
320
+ });
321
+ const prompt = contextInfo.prompt;
322
+ let invocation;
323
+
324
+ try {
325
+ invocation = buildProviderInvocation(provider, {
326
+ prompt,
327
+ cwd: repoRoot,
328
+ timeoutMs,
329
+ });
330
+ } catch (error) {
331
+ throw annotateProviderError(error, 'plan', phase);
332
+ }
333
+
334
+ if (options.dryRun) {
335
+ const report = {
336
+ task: 'plan',
337
+ provider,
338
+ role,
339
+ contextPack: contextInfo.pack.packName,
340
+ phase,
341
+ invocation,
342
+ };
343
+ process.stdout.write(formatDryRunReport(report));
344
+ return report;
345
+ }
346
+
347
+ let result;
348
+ try {
349
+ result = await (options.runProviderFn || runProvider)(provider, {
350
+ prompt,
351
+ cwd: repoRoot,
352
+ timeoutMs,
353
+ dryRun: false,
354
+ probe: options.probe,
355
+ spawn: options.spawn,
356
+ tempRoot: options.tempRoot,
357
+ tempFileName: options.tempFileName,
358
+ tempFilePrefix: options.tempFilePrefix,
359
+ });
360
+ } catch (error) {
361
+ throw annotateProviderError(error, 'plan', phase);
362
+ }
363
+
364
+ writeProviderOutput(result);
365
+
366
+ if (!result.ok) {
367
+ throw annotateProviderError(result.error || new Error('provider run failed'), 'plan', phase);
368
+ }
369
+
370
+ return {
371
+ task: 'plan',
372
+ provider,
373
+ role,
374
+ contextPack: contextInfo.pack.packName,
375
+ phase,
376
+ invocation,
377
+ result,
378
+ };
379
+ }
380
+
381
+ async function runGitHubTask(repoRoot, options = {}, mode = 'pr') {
382
+ const dryRun = options.dryRun === true;
383
+ let report;
384
+
385
+ try {
386
+ report = await (options.preflightFn || preflightGitHubPr)(repoRoot, {
387
+ remote: options.remote,
388
+ sshHostAlias: options.sshHostAlias,
389
+ identityFile: options.identityFile,
390
+ gitFlowGuidePath: options.gitFlowGuidePath,
391
+ ghCommand: options.ghCommand,
392
+ ghProbe: options.ghProbe,
393
+ ghAuthProbe: options.ghAuthProbe,
394
+ ghProbeArgs: options.ghProbeArgs,
395
+ ghAuthArgs: options.ghAuthArgs,
396
+ blockedBranches: options.blockedBranches,
397
+ });
398
+ } catch (error) {
399
+ throw annotateGitHubError(error, mode);
400
+ }
401
+
402
+ process.stdout.write(formatPreflightReport(report, { mode, dryRun }));
403
+
404
+ return {
405
+ task: mode,
406
+ dryRun,
407
+ preflight: report,
408
+ };
409
+ }
410
+
411
+ async function runPr(repoRoot, options = {}) {
412
+ return runGitHubTask(repoRoot, options, 'pr');
413
+ }
414
+
415
+ async function runDoctor(repoRoot, options = {}) {
416
+ return runGitHubTask(repoRoot, options, 'doctor');
417
+ }
418
+
419
+ module.exports = {
420
+ DEFAULT_ONBOARD_CONTEXT,
421
+ DEFAULT_ONBOARD_PROVIDER,
422
+ DEFAULT_ONBOARD_ROLE,
423
+ DEFAULT_PLAN_CONTEXT,
424
+ DEFAULT_PLAN_PHASE,
425
+ DEFAULT_PLAN_PROVIDER,
426
+ DEFAULT_PLAN_ROLE,
427
+ PlannerPhaseError,
428
+ annotateProviderError,
429
+ buildOnboardContext,
430
+ buildPlanContext,
431
+ formatDryRunReport,
432
+ formatSpecGenerationResult,
433
+ formatSpecDryRunReport,
434
+ normalizeTimeout,
435
+ readTextFile,
436
+ runDoctor,
437
+ runExecuteSlice,
438
+ runPr,
439
+ runOnboard,
440
+ runPlan,
441
+ writeProviderOutput,
442
+ };