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