create-quiver 0.5.0 → 0.7.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 (69) hide show
  1. package/.github/workflows/ci.yml +7 -30
  2. package/AGENTS.md.template +41 -0
  3. package/CHANGELOG.md +5 -0
  4. package/README.md +53 -9
  5. package/README_FOR_AI.md +36 -14
  6. package/ROADMAP.md +78 -0
  7. package/docs/AI_CONTEXT.md.template +19 -25
  8. package/docs/AI_ONBOARDING_PROMPT.md.template +12 -0
  9. package/docs/CONTEXTO.md.template +4 -17
  10. package/docs/DECISIONS.md.template +18 -0
  11. package/docs/DEEP.md.template +34 -0
  12. package/docs/DOCUMENTATION_GUIDE.md.template +9 -7
  13. package/docs/GITFLOW_PR_GUIDE.md.template +7 -0
  14. package/docs/INDEX.md.template +9 -0
  15. package/docs/QUICK.md.template +27 -0
  16. package/docs/STANDARD.md.template +49 -0
  17. package/docs/STATUS.md.template +2 -2
  18. package/docs/SUPPORT_MATRIX.md.template +7 -4
  19. package/docs/TESTING_GUIDE_FOR_AI.md.template +4 -3
  20. package/docs/TROUBLESHOOTING.md.template +14 -0
  21. package/docs/WORKFLOW.md.template +19 -5
  22. package/package.json +3 -1
  23. package/package.template.json +13 -1
  24. package/scripts/cleanup-slice.sh +2 -172
  25. package/scripts/init-docs.sh +246 -44
  26. package/scripts/package-quiver.sh +5 -0
  27. package/scripts/start-slice.sh +3 -425
  28. package/specs/[project-name]/EVIDENCE_REPORT.md.template +3 -1
  29. package/specs/[project-name]/slices/slice-template/slice.json +2 -2
  30. package/specs/quiver-v11-existing-project-migration/EVIDENCE_REPORT.md +38 -0
  31. package/specs/quiver-v11-existing-project-migration/SPEC.md +59 -0
  32. package/specs/quiver-v11-existing-project-migration/STATUS.md +26 -0
  33. package/specs/quiver-v11-existing-project-migration/slices/slice-01-non-destructive-migrate-command/slice.json +73 -0
  34. package/specs/quiver-v11-existing-project-migration/slices/slice-02-version-metadata-doctor-upgrade-checks/slice.json +71 -0
  35. package/specs/quiver-v11-existing-project-migration/slices/slice-03-upgrade-docs-legacy-project-smokes/slice.json +78 -0
  36. package/specs/quiver-v12-cross-platform-native-runtime/EVIDENCE_REPORT.md +30 -0
  37. package/specs/quiver-v12-cross-platform-native-runtime/SPEC.md +86 -0
  38. package/specs/quiver-v12-cross-platform-native-runtime/STATUS.md +29 -0
  39. package/specs/quiver-v12-cross-platform-native-runtime/slices/slice-01-cross-platform-support-contract/slice.json +69 -0
  40. package/specs/quiver-v12-cross-platform-native-runtime/slices/slice-02-node-init-docs-runtime/slice.json +76 -0
  41. package/specs/quiver-v12-cross-platform-native-runtime/slices/slice-03-node-migrate-analyze-doctor-flow/slice.json +74 -0
  42. package/specs/quiver-v12-cross-platform-native-runtime/slices/slice-04-node-slice-lifecycle-commands/slice.json +81 -0
  43. package/specs/quiver-v12-cross-platform-native-runtime/slices/slice-05-generated-project-scripts-and-migration/slice.json +78 -0
  44. package/specs/quiver-v12-cross-platform-native-runtime/slices/slice-06-cross-platform-ci-release-readiness/slice.json +74 -0
  45. package/specs/quiver-v13-token-efficient-ai-context/EVIDENCE_REPORT.md +28 -0
  46. package/specs/quiver-v13-token-efficient-ai-context/SPEC.md +68 -0
  47. package/specs/quiver-v13-token-efficient-ai-context/STATUS.md +26 -0
  48. package/specs/quiver-v13-token-efficient-ai-context/slices/slice-01-token-efficient-ai-modes-guidance/slice.json +65 -0
  49. package/specs/quiver-v13-token-efficient-ai-context/slices/slice-02-decision-log-context-checkpoint/slice.json +64 -0
  50. package/specs/quiver-v13-token-efficient-ai-context/slices/slice-03-project-map-reading-order/slice.json +66 -0
  51. package/specs/quiver-v14-tiered-context-pack/EVIDENCE_REPORT.md +42 -0
  52. package/specs/quiver-v14-tiered-context-pack/SPEC.md +116 -0
  53. package/specs/quiver-v14-tiered-context-pack/STATUS.md +35 -0
  54. package/specs/quiver-v14-tiered-context-pack/slices/slice-01-tiered-context-pack/slice.json +77 -0
  55. package/specs/quiver-v14-tiered-context-pack/slices/slice-02-agents-md-router/slice.json +74 -0
  56. package/specs/quiver-v14-tiered-context-pack/slices/slice-03-active-slice-lifecycle/slice.json +74 -0
  57. package/specs/quiver-v14-tiered-context-pack/slices/slice-04-dedup-frontmatter/slice.json +83 -0
  58. package/specs/quiver-v14-tiered-context-pack/slices/slice-05-doctor-smokes-tiered-pack/slice.json +84 -0
  59. package/src/create-quiver/index.js +360 -40
  60. package/src/create-quiver/lib/analyze.js +9 -0
  61. package/src/create-quiver/lib/doctor.js +212 -0
  62. package/src/create-quiver/lib/git.js +154 -0
  63. package/src/create-quiver/lib/init-docs.js +616 -0
  64. package/src/create-quiver/lib/lifecycle.js +478 -0
  65. package/src/create-quiver/lib/paths.js +19 -0
  66. package/src/create-quiver/lib/readiness.js +300 -0
  67. package/src/create-quiver/lib/scope.js +5 -0
  68. package/src/create-quiver/lib/slice.js +194 -0
  69. package/src/create-quiver/lib/state.js +89 -0
@@ -0,0 +1,616 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { writeState } = require('./state');
4
+
5
+ function ensureDir(dirPath) {
6
+ fs.mkdirSync(dirPath, { recursive: true });
7
+ }
8
+
9
+ function toProjectSlug(projectName) {
10
+ return projectName
11
+ .normalize('NFKD')
12
+ .replace(/[\u0300-\u036f]/g, '')
13
+ .toLowerCase()
14
+ .replace(/[^a-z0-9]+/g, '-')
15
+ .replace(/^-+|-+$/g, '') || 'quiver-project';
16
+ }
17
+
18
+ function detectPackageManager(projectRoot) {
19
+ const packageJsonPath = path.join(projectRoot, 'package.json');
20
+ if (fs.existsSync(packageJsonPath)) {
21
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
22
+ if (typeof packageJson.packageManager === 'string' && packageJson.packageManager.length > 0) {
23
+ return packageJson.packageManager.split('@')[0];
24
+ }
25
+ }
26
+
27
+ const candidates = [
28
+ ['pnpm', 'pnpm-lock.yaml'],
29
+ ['yarn', 'yarn.lock'],
30
+ ['bun', 'bun.lockb'],
31
+ ['bun', 'bun.lock'],
32
+ ['npm', 'package-lock.json'],
33
+ ];
34
+
35
+ for (const [manager, lockFile] of candidates) {
36
+ if (fs.existsSync(path.join(projectRoot, lockFile))) {
37
+ return manager;
38
+ }
39
+ }
40
+
41
+ return 'npm';
42
+ }
43
+
44
+ function renderTemplate(text, replacements) {
45
+ return text
46
+ .replace(/{{PROJECT_NAME}}/g, replacements.projectName)
47
+ .replace(/{{PROJECT_SLUG}}/g, replacements.projectSlug)
48
+ .replace(/\[project\]/g, replacements.projectSlug)
49
+ .replace(/\[project-name\]/g, replacements.projectSlug)
50
+ .replace(/\[project-slug\]/g, replacements.projectSlug)
51
+ .replace(/{{FECHA}}/g, replacements.currentDate)
52
+ .replace(/{{FECHA_PROXIMA}}/g, replacements.datePlus7)
53
+ .replace(/{{FECHA_PROXIMA_MES}}/g, replacements.datePlus30)
54
+ .replace(/{{FECHA_LAUNCH}}/g, replacements.datePlus35)
55
+ .replace(/{{ESTADO}}/g, 'En planificación')
56
+ .replace(/{{FASE}}/g, 'Fase 1')
57
+ .replace(/{{X}}%/g, '0%')
58
+ .replace(/{{PACKAGE_MANAGER}}/g, replacements.packageManager || 'npm')
59
+ .replace(/{{STACK_SUMMARY}}/g, replacements.stackSummary || 'unknown until analyze')
60
+ .replace(/{{PRIMARY_INSTALL}}/g, replacements.primaryInstall || 'npm install')
61
+ .replace(/{{PRIMARY_DEV}}/g, replacements.primaryDev || 'not defined')
62
+ .replace(/{{PRIMARY_TEST}}/g, replacements.primaryTest || 'not defined')
63
+ .replace(/{{ANALYZE_COMMAND}}/g, replacements.analyzeCommand || 'npx create-quiver analyze')
64
+ .replace(/{{DOCTOR_COMMAND}}/g, replacements.doctorCommand || 'npx create-quiver doctor')
65
+ .replace(/{{START_SLICE_COMMAND}}/g, replacements.startSliceCommand || 'npx create-quiver start-slice <slice.json>')
66
+ .replace(/{{CHECK_SLICE_COMMAND}}/g, replacements.checkSliceCommand || 'npx create-quiver check-slice <slice.json>')
67
+ .replace(/{{CHECK_PR_COMMAND}}/g, replacements.checkPrCommand || 'npx create-quiver check-pr <slice.json>')
68
+ .replace(/{{CLEANUP_SLICE_COMMAND}}/g, replacements.cleanupSliceCommand || 'npx create-quiver cleanup-slice <slice.json>')
69
+ .replace(/{{CHECK_SCOPE_COMMAND}}/g, replacements.checkScopeCommand || 'npx create-quiver check-scope <slice.json>')
70
+ .replace(/{{REFRESH_ACTIVE_SLICES_COMMAND}}/g, replacements.refreshActiveSlicesCommand || 'npx create-quiver refresh-active-slices');
71
+ }
72
+
73
+ function copyRenderedFile(sourcePath, destinationPath, replacements, skipIfExists, frontMatterFactory) {
74
+ const sourceText = fs.readFileSync(sourcePath, 'utf8');
75
+ const renderedText = renderTemplate(sourceText, replacements);
76
+
77
+ if (skipIfExists && fs.existsSync(destinationPath)) {
78
+ if (typeof frontMatterFactory === 'function') {
79
+ const existingText = fs.readFileSync(destinationPath, 'utf8');
80
+ const body = stripFrontMatter(existingText);
81
+ writeFrontMatter(destinationPath, frontMatterFactory({
82
+ body,
83
+ existingText,
84
+ renderedText,
85
+ replacements,
86
+ destinationPath,
87
+ }));
88
+ return 'frontmatter-updated';
89
+ }
90
+
91
+ return 'skipped';
92
+ }
93
+
94
+ ensureDir(path.dirname(destinationPath));
95
+ fs.writeFileSync(destinationPath, renderedText);
96
+
97
+ if (typeof frontMatterFactory === 'function') {
98
+ writeFrontMatter(destinationPath, frontMatterFactory({
99
+ body: renderedText,
100
+ existingText: null,
101
+ renderedText,
102
+ replacements,
103
+ destinationPath,
104
+ }));
105
+ return 'created-with-frontmatter';
106
+ }
107
+
108
+ return 'created';
109
+ }
110
+
111
+ function copyBinaryFile(sourcePath, destinationPath, skipIfExists) {
112
+ if (skipIfExists && fs.existsSync(destinationPath)) {
113
+ return 'skipped';
114
+ }
115
+
116
+ ensureDir(path.dirname(destinationPath));
117
+ fs.copyFileSync(sourcePath, destinationPath);
118
+
119
+ try {
120
+ const mode = fs.statSync(sourcePath).mode & 0o777;
121
+ fs.chmodSync(destinationPath, mode);
122
+ } catch {
123
+ // Best effort. Windows and some filesystems may not honor POSIX modes.
124
+ }
125
+
126
+ return 'created';
127
+ }
128
+
129
+ function copyIfSourceExists(sourcePath, destinationPath, skipIfExists) {
130
+ if (!fs.existsSync(sourcePath)) {
131
+ return 'missing';
132
+ }
133
+
134
+ return copyBinaryFile(sourcePath, destinationPath, skipIfExists);
135
+ }
136
+
137
+ function stripFrontMatter(text) {
138
+ if (!text.startsWith('---\n')) {
139
+ return text;
140
+ }
141
+
142
+ const closing = text.indexOf('\n---\n', 4);
143
+ if (closing === -1) {
144
+ return text;
145
+ }
146
+
147
+ return text.slice(closing + 5).replace(/^\n+/, '');
148
+ }
149
+
150
+ function estimateTokenCost(text) {
151
+ return Math.max(1, Math.ceil(Buffer.byteLength(text, 'utf8') / 4));
152
+ }
153
+
154
+ function serializeFrontMatterValue(value) {
155
+ if (value === null) {
156
+ return 'null';
157
+ }
158
+
159
+ if (typeof value === 'number') {
160
+ return String(value);
161
+ }
162
+
163
+ return JSON.stringify(String(value));
164
+ }
165
+
166
+ function serializeFrontMatter(fields) {
167
+ return [
168
+ '---',
169
+ `purpose: ${serializeFrontMatterValue(fields.purpose)}`,
170
+ `applies_when: ${serializeFrontMatterValue(fields.applies_when)}`,
171
+ `token_cost: ${serializeFrontMatterValue(fields.token_cost)}`,
172
+ `last_updated: ${serializeFrontMatterValue(fields.last_updated)}`,
173
+ `supersedes: ${serializeFrontMatterValue(fields.supersedes)}`,
174
+ '---',
175
+ ].join('\n');
176
+ }
177
+
178
+ function buildFrontMatterFields({ purpose, appliesWhen, body, currentDate, supersedes = null }) {
179
+ return {
180
+ purpose,
181
+ applies_when: appliesWhen,
182
+ token_cost: estimateTokenCost(body),
183
+ last_updated: currentDate,
184
+ supersedes,
185
+ };
186
+ }
187
+
188
+ function writeFrontMatter(filePath, fields) {
189
+ const existing = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : '';
190
+ const body = stripFrontMatter(existing);
191
+ const frontMatter = serializeFrontMatter(fields);
192
+ const nextContent = body.length > 0 ? `${frontMatter}\n\n${body.replace(/\s+$/, '')}\n` : `${frontMatter}\n`;
193
+ ensureDir(path.dirname(filePath));
194
+ fs.writeFileSync(filePath, nextContent);
195
+ return nextContent;
196
+ }
197
+
198
+ function mergePackageJson(projectRoot, templateRoot, skipIfExists) {
199
+ const packageTemplate = path.join(templateRoot, 'package.template.json');
200
+ const packageJsonPath = path.join(projectRoot, 'package.json');
201
+
202
+ if (!fs.existsSync(packageTemplate)) {
203
+ return 'missing';
204
+ }
205
+
206
+ if (!fs.existsSync(packageJsonPath)) {
207
+ fs.copyFileSync(packageTemplate, packageJsonPath);
208
+ return 'created';
209
+ }
210
+
211
+ const existing = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
212
+ const template = JSON.parse(fs.readFileSync(packageTemplate, 'utf8'));
213
+
214
+ existing.scripts = {
215
+ ...(existing.scripts || {}),
216
+ ...(template.scripts || {}),
217
+ };
218
+
219
+ fs.writeFileSync(packageJsonPath, `${JSON.stringify(existing, null, 2)}\n`);
220
+ return skipIfExists ? 'merged' : 'updated';
221
+ }
222
+
223
+ function buildReadme(projectName, projectSlug) {
224
+ return `# ${projectName}
225
+
226
+ [Descripción breve del proyecto]
227
+
228
+ ## Quick Start
229
+
230
+ Run Quiver from this project root. Do not install it globally.
231
+
232
+ \`\`\`bash
233
+ npm install
234
+ npx create-quiver analyze
235
+ npx create-quiver doctor
236
+ \`\`\`
237
+
238
+ If this project needs a pinned Quiver version, install it as a devDependency:
239
+
240
+ \`\`\`bash
241
+ npm install --save-dev create-quiver
242
+ \`\`\`
243
+
244
+ If you need to target another directory from outside the project, pass \`--dir\` explicitly. Quote paths that contain spaces.
245
+
246
+ After you run \`analyze\`, open \`docs/PROJECT_MAP.md\` for the detected stack, package manager, and command surface.
247
+
248
+ ## Project NPM Scripts
249
+
250
+ The generated project includes \`quiver:*\` npm scripts that call the Node CLI and are the preferred repeatable workflow:
251
+
252
+ \`\`\`bash
253
+ npm run quiver:analyze
254
+ npm run quiver:doctor
255
+ npm run quiver:migrate
256
+ npm run quiver:start-slice -- specs/${projectSlug}/slices/slice-01/slice.json
257
+ npm run quiver:check-slice -- specs/${projectSlug}/slices/slice-01/slice.json
258
+ npm run quiver:check-pr -- specs/${projectSlug}/slices/slice-01/slice.json
259
+ npm run quiver:cleanup-slice -- specs/${projectSlug}/slices/slice-01/slice.json
260
+ npm run quiver:check-scope -- specs/${projectSlug}/slices/slice-01/slice.json
261
+ npm run quiver:refresh-active-slices
262
+ \`\`\`
263
+
264
+ The legacy Bash wrappers remain in \`tools/scripts/\` for compatibility, but new project-level automation should prefer the \`quiver:*\` scripts and the direct \`npx create-quiver ...\` commands below.
265
+
266
+ ## Cross-Platform Support
267
+
268
+ Quiver is targeting native support on macOS, Linux, and Windows PowerShell/CMD. Bash is a legacy compatibility path until the runtime slices land, so the generated workflow should be read as a native Node-first contract rather than a Bash-first one. Windows support is only considered verified once the CI matrix is green.
269
+
270
+ ## Upgrading Existing Projects
271
+
272
+ If the project already existed before this Quiver version, upgrade it from the project root:
273
+
274
+ \`\`\`bash
275
+ cd /path/to/your-project
276
+ npx create-quiver migrate
277
+ npx create-quiver analyze
278
+ npx create-quiver doctor
279
+ \`\`\`
280
+
281
+ If your team prefers a pinned local dependency, update the package first and then run the same flow:
282
+
283
+ \`\`\`bash
284
+ npm install --save-dev create-quiver@latest
285
+ npx create-quiver migrate
286
+ npx create-quiver analyze
287
+ npx create-quiver doctor
288
+ \`\`\`
289
+
290
+ ## AI Context Onboarding
291
+
292
+ Read \`AGENTS.md\` first, then open \`docs/AI_ONBOARDING_PROMPT.md\` after analysis.
293
+
294
+ After analysis and doctor validation, open your AI agent in this project and run:
295
+
296
+ \`\`\`text
297
+ Read docs/AI_ONBOARDING_PROMPT.md and execute it.
298
+ Do not modify product code unless I explicitly authorize it.
299
+ Prepare the project context docs and report assumptions, risks, and files changed.
300
+ \`\`\`
301
+
302
+ Review the AI changes to docs/AI_CONTEXT.md, docs/CONTEXTO.md, docs/STATUS.md, and specs/${projectSlug}/SPEC.md before starting implementation work. Use \`docs/PROJECT_MAP.md\` for stack and command details.
303
+
304
+ ## Decision Log
305
+
306
+ Record durable decisions in \`docs/DECISIONS.md\` so future AI agents do not re-litigate the same choices.
307
+
308
+ ## First Slice Workflow
309
+
310
+ 1. Review or refine specs/${projectSlug}/SPEC.md.
311
+ 2. Create the first slice from specs/${projectSlug}/slices/slice-template/slice.json.
312
+ 3. Start work with \`npx create-quiver start-slice <slice.json>\` or \`npm run quiver:start-slice -- <slice.json>\`.
313
+ 4. Make one commit per slice.
314
+ 5. Open one PR per spec.
315
+
316
+ ## Verification Checklist
317
+
318
+ - [ ] npm install completes
319
+ - [ ] npx create-quiver analyze completes
320
+ - [ ] npx create-quiver doctor completes
321
+ - [ ] AI agent executed docs/AI_ONBOARDING_PROMPT.md
322
+ - [ ] Context docs were reviewed before the first slice
323
+
324
+ ## Documentation
325
+
326
+ - [AI Context](./docs/AI_CONTEXT.md) - Contexto resumido para IA
327
+ - [Decision Log](./docs/DECISIONS.md) - Decisiones durables del proyecto
328
+ - [AI Onboarding Prompt](./docs/AI_ONBOARDING_PROMPT.md) - Handoff exacto para agentes después del análisis
329
+ - [Contexto](./docs/CONTEXTO.md) - Qué es ${projectName}
330
+ - [Workflow](./docs/WORKFLOW.md) - Cómo implementar
331
+ - [Support Matrix](./docs/SUPPORT_MATRIX.md) - Qué entornos están soportados
332
+ - [Troubleshooting](./docs/TROUBLESHOOTING.md) - Cómo recuperarse de fallos comunes
333
+ - [Status](./docs/STATUS.md) - Estado del proyecto
334
+ - [API Docs](./docs/api/) - Endpoint documentation (si aplica)
335
+ `;
336
+ }
337
+
338
+ function initializeProjectDocs(options) {
339
+ const {
340
+ projectRoot,
341
+ projectName,
342
+ cliVersion,
343
+ migrateMode = false,
344
+ } = options;
345
+
346
+ const templateRoot = path.join(projectRoot, 'docs-template');
347
+ const replacements = {
348
+ projectName,
349
+ projectSlug: toProjectSlug(projectName),
350
+ currentDate: new Date().toISOString().slice(0, 10),
351
+ datePlus7: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10),
352
+ datePlus30: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10),
353
+ datePlus35: new Date(Date.now() + 35 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10),
354
+ };
355
+
356
+ const dirs = [
357
+ 'docs',
358
+ 'docs/ai',
359
+ 'docs/tools',
360
+ 'docs/archive',
361
+ `specs/${replacements.projectSlug}/slices/slice-template`,
362
+ 'tools/scripts',
363
+ ];
364
+
365
+ for (const dir of dirs) {
366
+ ensureDir(path.join(projectRoot, dir));
367
+ }
368
+
369
+ const operations = [];
370
+ const agentsSourcePath = path.join(templateRoot, 'AGENTS.md.template');
371
+ if (fs.existsSync(agentsSourcePath)) {
372
+ const agentsDestinationPath = path.join(projectRoot, 'AGENTS.md');
373
+ const result = copyRenderedFile(agentsSourcePath, agentsDestinationPath, replacements, true);
374
+ operations.push({ source: 'AGENTS.md.template', destination: 'AGENTS.md', result });
375
+ }
376
+ const frontMatterFor = (purpose, appliesWhen, supersedes = null) => ({ body }) => buildFrontMatterFields({
377
+ purpose,
378
+ appliesWhen,
379
+ body,
380
+ currentDate: replacements.currentDate,
381
+ supersedes,
382
+ });
383
+ const templateCopies = [
384
+ ['docs/INDEX.md.template', 'docs/INDEX.md'],
385
+ ['docs/DECISIONS.md.template', 'docs/DECISIONS.md'],
386
+ ['docs/AI_CONTEXT.md.template', 'docs/AI_CONTEXT.md', frontMatterFor('Agent-facing project context pack', 'onboarding, implementation, review')],
387
+ ['docs/AI_ONBOARDING_PROMPT.md.template', 'docs/AI_ONBOARDING_PROMPT.md', frontMatterFor('AI onboarding handoff prompt', 'onboarding after analysis')],
388
+ ['docs/CONTEXTO.md.template', 'docs/CONTEXTO.md', frontMatterFor('Human-readable project overview', 'onboarding, review')],
389
+ ['docs/STATUS.md.template', 'docs/STATUS.md', frontMatterFor('Project status snapshot', 'progress review, planning')],
390
+ ['docs/WORKFLOW.md.template', 'docs/WORKFLOW.md', frontMatterFor('Execution workflow contract', 'planning, implementation')],
391
+ ['docs/SUPPORT_MATRIX.md.template', 'docs/SUPPORT_MATRIX.md'],
392
+ ['docs/TROUBLESHOOTING.md.template', 'docs/TROUBLESHOOTING.md'],
393
+ ['docs/MULTI_AGENT_WORKFLOW.md.template', 'docs/MULTI_AGENT_WORKFLOW.md'],
394
+ ['docs/MOCK_DATA_GUIDE.md.template', 'docs/MOCK_DATA_GUIDE.md'],
395
+ ['docs/UI_STANDARDS.md.template', 'docs/UI_STANDARDS.md'],
396
+ ['docs/GITFLOW_PR_GUIDE.md.template', 'docs/GITFLOW_PR_GUIDE.md'],
397
+ ['docs/DOCUMENTATION_GUIDE.md.template', 'docs/DOCUMENTATION_GUIDE.md'],
398
+ ['docs/TESTING_GUIDE_FOR_AI.md.template', 'docs/TESTING_GUIDE_FOR_AI.md'],
399
+ ['docs/ai/LESSONS.md.template', 'docs/ai/LESSONS.md', frontMatterFor('Slice learnings log', 'after slice completion')],
400
+ ['specs/[project-name]/SPEC.md.template', `specs/${replacements.projectSlug}/SPEC.md`],
401
+ ['specs/[project-name]/STATUS.md.template', `specs/${replacements.projectSlug}/STATUS.md`],
402
+ ['specs/[project-name]/EVIDENCE_REPORT.md.template', `specs/${replacements.projectSlug}/EVIDENCE_REPORT.md`],
403
+ ['specs/[project-name]/slices/slice-template/slice.json', `specs/${replacements.projectSlug}/slices/slice-template/slice.json`],
404
+ ['specs/[project-name]/slices/pr.md.template', `specs/${replacements.projectSlug}/slices/slice-template/pr.md.template`],
405
+ ];
406
+
407
+ for (const [source, destination, frontMatterFactory] of templateCopies) {
408
+ const sourcePath = path.join(templateRoot, source);
409
+ if (!fs.existsSync(sourcePath)) {
410
+ continue;
411
+ }
412
+
413
+ const destinationPath = path.join(projectRoot, destination);
414
+ const result = copyRenderedFile(sourcePath, destinationPath, replacements, migrateMode, frontMatterFactory);
415
+ operations.push({ source, destination, result });
416
+ }
417
+
418
+ const binaryCopies = [
419
+ ['docs/UI_STANDARDS.md', 'docs/UI_STANDARDS.md'],
420
+ ['docs/MOCK_DATA_GUIDE.md', 'docs/MOCK_DATA_GUIDE.md'],
421
+ ['docs/ai/RULES.yaml', 'docs/ai/RULES.yaml'],
422
+ ['LICENSE', 'LICENSE'],
423
+ ['CONTRIBUTING.md', 'CONTRIBUTING.md'],
424
+ ['CODE_OF_CONDUCT.md', 'CODE_OF_CONDUCT.md'],
425
+ ['SECURITY.md', 'SECURITY.md'],
426
+ ['CHANGELOG.md', 'CHANGELOG.md'],
427
+ ['ROADMAP.md', 'ROADMAP.md'],
428
+ ['.github/pull_request_template.md', '.github/pull_request_template.md'],
429
+ ['.github/ISSUE_TEMPLATE/bug_report.md', '.github/ISSUE_TEMPLATE/bug_report.md'],
430
+ ['.github/ISSUE_TEMPLATE/feature_request.md', '.github/ISSUE_TEMPLATE/feature_request.md'],
431
+ ['.github/workflows/ci.yml', '.github/workflows/ci.yml'],
432
+ ['scripts/start-slice.sh', 'tools/scripts/start-slice.sh'],
433
+ ['scripts/refresh-active-slices.sh', 'tools/scripts/refresh-active-slices.sh'],
434
+ ['scripts/check-slice-readiness.sh', 'tools/scripts/check-slice-readiness.sh'],
435
+ ['scripts/check-pr-readiness.sh', 'tools/scripts/check-pr-readiness.sh'],
436
+ ['scripts/cleanup-slice.sh', 'tools/scripts/cleanup-slice.sh'],
437
+ ['scripts/check-scope.sh', 'tools/scripts/check-scope.sh'],
438
+ ['scripts/migrate-project.sh', 'tools/scripts/migrate-project.sh'],
439
+ ];
440
+
441
+ for (const [source, destination] of binaryCopies) {
442
+ const sourcePath = path.join(templateRoot, source);
443
+ const destinationPath = path.join(projectRoot, destination);
444
+ if (!fs.existsSync(sourcePath)) {
445
+ continue;
446
+ }
447
+
448
+ const result = copyIfSourceExists(sourcePath, destinationPath, migrateMode);
449
+ operations.push({ source, destination, result });
450
+ }
451
+
452
+ const aiPrinciplesSource = path.join(templateRoot, 'docs/ai/PRINCIPLES.md');
453
+ if (fs.existsSync(aiPrinciplesSource)) {
454
+ const aiPrinciplesDestination = path.join(projectRoot, 'docs/ai/PRINCIPLES.md');
455
+ const result = copyRenderedFile(
456
+ aiPrinciplesSource,
457
+ aiPrinciplesDestination,
458
+ replacements,
459
+ migrateMode,
460
+ frontMatterFor('AI operating principles', 'all AI work'),
461
+ );
462
+ operations.push({ source: 'docs/ai/PRINCIPLES.md', destination: 'docs/ai/PRINCIPLES.md', result });
463
+ }
464
+
465
+ const packageResult = mergePackageJson(projectRoot, templateRoot, migrateMode);
466
+ operations.push({ source: 'package.template.json', destination: 'package.json', result: packageResult });
467
+
468
+ const mergedPackageJsonPath = path.join(projectRoot, 'package.json');
469
+ const mergedPackageJson = fs.existsSync(mergedPackageJsonPath)
470
+ ? JSON.parse(fs.readFileSync(mergedPackageJsonPath, 'utf8'))
471
+ : {};
472
+ const packageScripts = mergedPackageJson.scripts || {};
473
+
474
+ const tierReplacements = {
475
+ ...replacements,
476
+ packageManager: detectPackageManager(projectRoot),
477
+ stackSummary: 'unknown until analyze',
478
+ primaryInstall: 'npm install',
479
+ primaryDev: packageScripts.dev || packageScripts.start || 'not defined',
480
+ primaryTest: packageScripts.test || 'not defined',
481
+ analyzeCommand: 'npx create-quiver analyze',
482
+ doctorCommand: 'npx create-quiver doctor',
483
+ startSliceCommand: 'npx create-quiver start-slice <slice.json>',
484
+ checkSliceCommand: 'npx create-quiver check-slice <slice.json>',
485
+ checkPrCommand: 'npx create-quiver check-pr <slice.json>',
486
+ cleanupSliceCommand: 'npx create-quiver cleanup-slice <slice.json>',
487
+ checkScopeCommand: 'npx create-quiver check-scope <slice.json>',
488
+ refreshActiveSlicesCommand: 'npx create-quiver refresh-active-slices',
489
+ };
490
+
491
+ const tierCopies = [
492
+ ['docs/QUICK.md.template', 'docs/ai/QUICK.md'],
493
+ ['docs/STANDARD.md.template', 'docs/ai/STANDARD.md'],
494
+ ['docs/DEEP.md.template', 'docs/ai/DEEP.md'],
495
+ ];
496
+
497
+ for (const [source, destination] of tierCopies) {
498
+ const sourcePath = path.join(templateRoot, source);
499
+ if (!fs.existsSync(sourcePath)) {
500
+ continue;
501
+ }
502
+
503
+ const destinationPath = path.join(projectRoot, destination);
504
+ const result = copyRenderedFile(sourcePath, destinationPath, tierReplacements, migrateMode, ({
505
+ body,
506
+ }) => buildFrontMatterFields({
507
+ purpose: destination.endsWith('QUICK.md')
508
+ ? 'Minimum execution briefing'
509
+ : destination.endsWith('STANDARD.md')
510
+ ? 'Default context pack'
511
+ : 'Deep project context',
512
+ appliesWhen: destination.endsWith('QUICK.md')
513
+ ? 'execution'
514
+ : destination.endsWith('STANDARD.md')
515
+ ? 'planning, implementation'
516
+ : 'planning, escalation',
517
+ body,
518
+ currentDate: replacements.currentDate,
519
+ supersedes: null,
520
+ }));
521
+ operations.push({ source, destination, result });
522
+ }
523
+
524
+ const currentState = fs.existsSync(path.join(projectRoot, '.quiver', 'state.json'))
525
+ ? JSON.parse(fs.readFileSync(path.join(projectRoot, '.quiver', 'state.json'), 'utf8'))
526
+ : null;
527
+ const nextState = migrateMode
528
+ ? {
529
+ ...(currentState || {}),
530
+ quiver_version: cliVersion,
531
+ project_name: projectName || currentState?.project_name || '',
532
+ initialized_version: currentState?.initialized_version ?? null,
533
+ migrated_version: cliVersion,
534
+ last_initialized_at: currentState?.last_initialized_at ?? null,
535
+ last_migration_at: new Date().toISOString(),
536
+ last_analysis_at: currentState?.last_analysis_at ?? null,
537
+ }
538
+ : {
539
+ ...(currentState || {}),
540
+ quiver_version: cliVersion,
541
+ project_name: projectName || currentState?.project_name || '',
542
+ initialized_version: currentState?.initialized_version || cliVersion,
543
+ migrated_version: currentState?.migrated_version ?? null,
544
+ last_initialized_at: currentState?.last_initialized_at || new Date().toISOString(),
545
+ last_migration_at: currentState?.last_migration_at ?? null,
546
+ last_analysis_at: currentState?.last_analysis_at ?? null,
547
+ };
548
+ writeState(projectRoot, nextState);
549
+
550
+ const searchPath = path.join(projectRoot, 'docs', 'SEARCH.md');
551
+ if (!(migrateMode && fs.existsSync(searchPath))) {
552
+ const searchContent = `# Búsqueda por Tema
553
+
554
+ **Última actualización:** ${replacements.currentDate}
555
+
556
+ ---
557
+
558
+ ## AI Context
559
+
560
+ - **Agent context pack:** \`docs/AI_CONTEXT.md\`
561
+ - **Project overview:** \`docs/CONTEXTO.md\`
562
+ - **Workflow:** \`docs/WORKFLOW.md\`
563
+
564
+ ---
565
+
566
+ ## Autenticación
567
+
568
+ - **Spec:** \`../specs/${replacements.projectSlug}/slices/slice-01/slice.json\`
569
+ - **PR del slice:** \`../specs/${replacements.projectSlug}/slices/slice-01/pr.md\`
570
+ - **Bootstrap del slice:** \`npx create-quiver start-slice ../specs/${replacements.projectSlug}/slices/slice-01/slice.json\`
571
+ - **Hook:** \`hooks/useAuth.ts\`
572
+ - **API:** \`docs/api/auth/README.md\`
573
+ - **Componentes:** \`app/(auth)/\`
574
+
575
+ ---
576
+
577
+ ## IA Configuración
578
+
579
+ - **Principios:** \`docs/ai/PRINCIPLES.md\`
580
+ - **Reglas:** \`docs/ai/RULES.yaml\`
581
+ - **Lessons:** \`docs/ai/LESSONS.md\`
582
+
583
+ ## Soporte
584
+
585
+ - **Support Matrix:** \`docs/SUPPORT_MATRIX.md\`
586
+ - **Troubleshooting:** \`docs/TROUBLESHOOTING.md\`
587
+
588
+ ---
589
+
590
+ **Fin de la búsqueda**
591
+ `;
592
+ fs.writeFileSync(searchPath, `${searchContent}\n`);
593
+ operations.push({ source: 'docs/SEARCH.md', destination: 'docs/SEARCH.md', result: 'created' });
594
+ } else {
595
+ operations.push({ source: 'docs/SEARCH.md', destination: 'docs/SEARCH.md', result: 'skipped' });
596
+ }
597
+
598
+ const readmePath = path.join(projectRoot, 'README.md');
599
+ if (!fs.existsSync(readmePath)) {
600
+ fs.writeFileSync(readmePath, `${buildReadme(projectName, replacements.projectSlug)}\n`);
601
+ operations.push({ source: 'README.md template', destination: 'README.md', result: 'created' });
602
+ } else {
603
+ operations.push({ source: 'README.md template', destination: 'README.md', result: 'skipped' });
604
+ }
605
+
606
+ return {
607
+ projectSlug: replacements.projectSlug,
608
+ operations,
609
+ };
610
+ }
611
+
612
+ module.exports = {
613
+ initializeProjectDocs,
614
+ writeFrontMatter,
615
+ toProjectSlug,
616
+ };