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,314 @@
1
+ const fs = require('node:fs');
2
+ const os = require('node:os');
3
+ const path = require('node:path');
4
+
5
+ const { parseJsonWithComments } = require('../json');
6
+ const {
7
+ buildClosureBrief,
8
+ buildEvidenceMarkdown,
9
+ buildExecutionBrief,
10
+ buildExecutionPlanMarkdown,
11
+ buildManifest,
12
+ buildPrMarkdown,
13
+ buildSliceJson,
14
+ buildSpecMarkdown,
15
+ buildStatusMarkdown,
16
+ normalizeApprovedSource,
17
+ slugify,
18
+ } = require('./spec-templates');
19
+
20
+ function formatError(message) {
21
+ return `create-quiver: ${message}`;
22
+ }
23
+
24
+ function readSourceText(inputPath, repoRoot) {
25
+ if (!inputPath) {
26
+ throw new Error(formatError('missing approved input file for spec generation'));
27
+ }
28
+
29
+ const resolved = path.resolve(repoRoot, inputPath);
30
+ if (!fs.existsSync(resolved)) {
31
+ throw new Error(formatError(`missing approved input file: ${inputPath}`));
32
+ }
33
+
34
+ return fs.readFileSync(resolved, 'utf8');
35
+ }
36
+
37
+ function parseApprovedManifest(sourceText, options = {}) {
38
+ const normalizedText = normalizeApprovedSource(sourceText);
39
+
40
+ if (!normalizedText) {
41
+ return {
42
+ sourceText: '',
43
+ source: {},
44
+ };
45
+ }
46
+
47
+ try {
48
+ const parsed = parseJsonWithComments(normalizedText);
49
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
50
+ return {
51
+ sourceText: normalizedText,
52
+ source: parsed,
53
+ };
54
+ }
55
+ } catch {
56
+ // Fall back to the markdown parser below.
57
+ }
58
+
59
+ const titleMatch = normalizedText.match(/^#\s+(.+)$/m);
60
+ const objective = extractSectionText(normalizedText, ['Objective', 'Objetivo']);
61
+ const scope = {
62
+ included: extractNestedSectionBullets(normalizedText, ['Scope', 'Alcance'], ['Included', 'Incluido', 'Incluidos', 'Incluye', 'Entra en alcance']),
63
+ excluded: extractNestedSectionBullets(normalizedText, ['Scope', 'Alcance'], ['Excluded', 'Excluido', 'Excluidos', 'No incluido', 'No incluidos', 'Fuera de alcance']),
64
+ };
65
+ const acceptance = extractSectionBullets(normalizedText, ['Acceptance Criteria', 'Criterios de aceptacion', 'Criterios de aceptación']);
66
+ const risks = extractSectionBullets(normalizedText, ['Risks', 'Riesgos']);
67
+ const assumptions = extractSectionBullets(normalizedText, ['Assumptions', 'Suposiciones', 'Supuestos']);
68
+
69
+ return {
70
+ sourceText: normalizedText,
71
+ source: {
72
+ title: titleMatch ? titleMatch[1].trim() : options.fallbackTitle || '',
73
+ objective: objective || '',
74
+ scope,
75
+ acceptance,
76
+ risks,
77
+ assumptions,
78
+ },
79
+ };
80
+ }
81
+
82
+ function extractSectionText(text, headings) {
83
+ const lines = String(text || '').split(/\r?\n/);
84
+ const normalizedHeadings = new Set(headings.map((heading) => normalizeHeading(heading)));
85
+ let capture = false;
86
+ const sectionLines = [];
87
+
88
+ for (const line of lines) {
89
+ const headingMatch = line.match(/^##\s+(.+)$/);
90
+ if (headingMatch) {
91
+ const heading = normalizeHeading(headingMatch[1]);
92
+ if (normalizedHeadings.has(heading)) {
93
+ capture = true;
94
+ continue;
95
+ }
96
+ if (capture) {
97
+ break;
98
+ }
99
+ }
100
+
101
+ if (capture) {
102
+ sectionLines.push(line);
103
+ }
104
+ }
105
+
106
+ return sectionLines.join('\n').trim();
107
+ }
108
+
109
+ function normalizeHeading(heading) {
110
+ return String(heading || '')
111
+ .normalize('NFKD')
112
+ .replace(/[\u0300-\u036f]/g, '')
113
+ .trim()
114
+ .toLowerCase();
115
+ }
116
+
117
+ function extractSectionBullets(text, headings) {
118
+ const section = extractSectionText(text, headings);
119
+ if (!section) {
120
+ return [];
121
+ }
122
+
123
+ return section
124
+ .split(/\r?\n/)
125
+ .map((line) => line.trim())
126
+ .filter((line) => /^[-*]\s+/.test(line))
127
+ .map((line) => line.replace(/^[-*]\s+/, '').trim())
128
+ .filter(Boolean);
129
+ }
130
+
131
+ function extractNestedSectionBullets(text, sectionHeadings, nestedHeadings) {
132
+ const section = extractSectionText(text, sectionHeadings);
133
+ if (!section) {
134
+ return [];
135
+ }
136
+
137
+ const nested = new Set(nestedHeadings.map((heading) => normalizeHeading(heading)));
138
+ let capture = false;
139
+ const bullets = [];
140
+
141
+ for (const rawLine of section.split(/\r?\n/)) {
142
+ const line = rawLine.trim();
143
+ const headingMatch = line.match(/^###\s+(.+)$/);
144
+ if (headingMatch) {
145
+ capture = nested.has(normalizeHeading(headingMatch[1]));
146
+ continue;
147
+ }
148
+
149
+ if (capture && /^[-*]\s+/.test(line)) {
150
+ bullets.push(line.replace(/^[-*]\s+/, '').trim());
151
+ }
152
+ }
153
+
154
+ return bullets.filter(Boolean);
155
+ }
156
+
157
+ function ensureDir(dirPath) {
158
+ fs.mkdirSync(dirPath, { recursive: true });
159
+ }
160
+
161
+ function writeTextFile(filePath, contents) {
162
+ ensureDir(path.dirname(filePath));
163
+ fs.writeFileSync(filePath, contents);
164
+ }
165
+
166
+ function validateGeneratedSliceJson(filePath, expectedSliceId) {
167
+ const parsed = parseJsonWithComments(fs.readFileSync(filePath, 'utf8'));
168
+
169
+ if (!parsed || typeof parsed !== 'object') {
170
+ throw new Error(formatError(`invalid slice.json payload at ${filePath}`));
171
+ }
172
+
173
+ if (expectedSliceId && parsed.slice_id !== expectedSliceId) {
174
+ throw new Error(formatError(`slice.json id mismatch at ${filePath}: expected ${expectedSliceId}, got ${parsed.slice_id}`));
175
+ }
176
+
177
+ if (!parsed.git || typeof parsed.git !== 'object') {
178
+ throw new Error(formatError(`slice.json missing git block at ${filePath}`));
179
+ }
180
+
181
+ if (!Array.isArray(parsed.files)) {
182
+ throw new Error(formatError(`slice.json files must be an array at ${filePath}`));
183
+ }
184
+
185
+ if (!Array.isArray(parsed.depends_on)) {
186
+ throw new Error(formatError(`slice.json depends_on must be an array at ${filePath}`));
187
+ }
188
+
189
+ return parsed;
190
+ }
191
+
192
+ function buildSpecGenerationManifest({ inputText, inputPath, repoRoot, specSlug }) {
193
+ const { sourceText, source } = parseApprovedManifest(inputText, {
194
+ fallbackTitle: specSlug ? specSlug.replace(/-/g, ' ') : path.basename(inputPath || 'generated-spec.md', path.extname(inputPath || '.md')),
195
+ });
196
+
197
+ const manifest = buildManifest({
198
+ ...source,
199
+ sourcePath: path.relative(repoRoot, path.resolve(repoRoot, inputPath)).split(path.sep).join('/'),
200
+ sourceText,
201
+ }, { specSlug });
202
+
203
+ if (!manifest.slug) {
204
+ throw new Error(formatError('unable to derive a spec slug from the approved input'));
205
+ }
206
+
207
+ return manifest;
208
+ }
209
+
210
+ function validateSpecCollision(specDir) {
211
+ if (fs.existsSync(specDir)) {
212
+ throw new Error(formatError(`spec directory already exists: ${path.relative(process.cwd(), specDir)}`));
213
+ }
214
+ }
215
+
216
+ function renderSpecTree(manifest, specDir) {
217
+ const files = [];
218
+ const sliceDirs = [];
219
+
220
+ writeTextFile(path.join(specDir, 'SPEC.md'), buildSpecMarkdown(manifest));
221
+ files.push('SPEC.md');
222
+ writeTextFile(path.join(specDir, 'STATUS.md'), buildStatusMarkdown(manifest));
223
+ files.push('STATUS.md');
224
+ writeTextFile(path.join(specDir, 'EVIDENCE_REPORT.md'), buildEvidenceMarkdown(manifest));
225
+ files.push('EVIDENCE_REPORT.md');
226
+ writeTextFile(path.join(specDir, 'EXECUTION_PLAN.md'), buildExecutionPlanMarkdown(manifest));
227
+ files.push('EXECUTION_PLAN.md');
228
+ writeTextFile(path.join(specDir, 'pr.md'), buildPrMarkdown(manifest));
229
+ files.push('pr.md');
230
+
231
+ for (let index = 0; index < manifest.slices.length; index += 1) {
232
+ const slice = manifest.slices[index];
233
+ const sliceDir = path.join(specDir, 'slices', slice.slice_id);
234
+ ensureDir(sliceDir);
235
+ writeTextFile(path.join(sliceDir, 'slice.json'), `${JSON.stringify(buildSliceJson(manifest, slice, index), null, 2)}\n`);
236
+ writeTextFile(path.join(sliceDir, 'EXECUTION_BRIEF.md'), buildExecutionBrief(manifest, slice));
237
+ writeTextFile(path.join(sliceDir, 'CLOSURE_BRIEF.md'), buildClosureBrief(manifest, slice));
238
+ validateGeneratedSliceJson(path.join(sliceDir, 'slice.json'), slice.slice_id);
239
+ files.push(path.relative(specDir, path.join(sliceDir, 'slice.json')).split(path.sep).join('/'));
240
+ files.push(path.relative(specDir, path.join(sliceDir, 'EXECUTION_BRIEF.md')).split(path.sep).join('/'));
241
+ files.push(path.relative(specDir, path.join(sliceDir, 'CLOSURE_BRIEF.md')).split(path.sep).join('/'));
242
+ sliceDirs.push(sliceDir);
243
+ }
244
+
245
+ return {
246
+ files,
247
+ sliceDirs,
248
+ };
249
+ }
250
+
251
+ function generateSpecArtifacts(repoRoot, options = {}) {
252
+ const inputPath = options.input;
253
+ const inputText = readSourceText(inputPath, repoRoot);
254
+ const manifest = buildSpecGenerationManifest({
255
+ inputPath,
256
+ inputText,
257
+ repoRoot,
258
+ specSlug: options.specSlug,
259
+ });
260
+
261
+ const specDir = path.join(repoRoot, 'specs', slugify(manifest.slug));
262
+ validateSpecCollision(specDir);
263
+
264
+ const parentDir = path.dirname(specDir);
265
+ ensureDir(parentDir);
266
+ const tempDir = fs.mkdtempSync(path.join(parentDir, `.${manifest.slug}-build-`));
267
+
268
+ try {
269
+ const renderResult = renderSpecTree(manifest, tempDir);
270
+ for (const slice of manifest.slices) {
271
+ validateGeneratedSliceJson(path.join(tempDir, 'slices', slice.slice_id, 'slice.json'), slice.slice_id);
272
+ }
273
+ fs.renameSync(tempDir, specDir);
274
+ return {
275
+ manifest,
276
+ specDir,
277
+ files: renderResult.files.map((file) => path.join(specDir, file)),
278
+ };
279
+ } catch (error) {
280
+ fs.rmSync(tempDir, { recursive: true, force: true });
281
+ throw error;
282
+ }
283
+ }
284
+
285
+ function describeSpecGeneration(manifest, repoRoot) {
286
+ const specDir = path.join(repoRoot, 'specs', slugify(manifest.slug));
287
+ const files = [
288
+ 'SPEC.md',
289
+ 'STATUS.md',
290
+ 'EVIDENCE_REPORT.md',
291
+ 'EXECUTION_PLAN.md',
292
+ 'pr.md',
293
+ ...manifest.slices.flatMap((slice) => [
294
+ `slices/${slice.slice_id}/slice.json`,
295
+ `slices/${slice.slice_id}/EXECUTION_BRIEF.md`,
296
+ `slices/${slice.slice_id}/CLOSURE_BRIEF.md`,
297
+ ]),
298
+ ];
299
+
300
+ return {
301
+ specDir,
302
+ files,
303
+ manifest,
304
+ };
305
+ }
306
+
307
+ module.exports = {
308
+ buildSpecGenerationManifest,
309
+ describeSpecGeneration,
310
+ generateSpecArtifacts,
311
+ parseApprovedManifest,
312
+ readSourceText,
313
+ validateGeneratedSliceJson,
314
+ };