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,479 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { branchDelete, catFileExists, currentBranch, fetchBranch, fetchRemote, hasLocalBranch, hasRemoteBranch, lsRemoteHeads, mergeBaseIsAncestor, revListCount, runGit, statusPorcelain, worktreeAdd, worktreeList, worktreePrune, worktreeRemove } = require('./git');
4
+ const { parseJsonWithComments } = require('./json');
5
+ const { writeFrontMatter } = require('./init-docs');
6
+ const { resolveTargetRoot } = require('./paths');
7
+ const { activeSlicePath, renderActiveSlice, resolveSliceContext, safeBranchName, toAlias, validateSliceMetaForStart, worktreesRootForRepo } = require('./slice');
8
+
9
+ function ensureDir(dirPath) {
10
+ fs.mkdirSync(dirPath, { recursive: true });
11
+ }
12
+
13
+ function estimateTokenCost(text) {
14
+ return Math.max(1, Math.ceil(Buffer.byteLength(text, 'utf8') / 4));
15
+ }
16
+
17
+ function appendUniqueLine(filePath, line) {
18
+ ensureDir(path.dirname(filePath));
19
+ if (!fs.existsSync(filePath)) {
20
+ fs.writeFileSync(filePath, '');
21
+ }
22
+ const existing = fs.readFileSync(filePath, 'utf8');
23
+ const normalized = existing.endsWith('\n') || existing.length === 0 ? existing : `${existing}\n`;
24
+ if (!normalized.split('\n').includes(line)) {
25
+ fs.writeFileSync(filePath, `${normalized}${line}\n`);
26
+ }
27
+ }
28
+
29
+ function ensureLocalExclude(workdir, pattern) {
30
+ const gitDir = runGit(['rev-parse', '--absolute-git-dir'], workdir);
31
+ appendUniqueLine(path.join(gitDir, 'info', 'exclude'), pattern);
32
+ }
33
+
34
+ function writeWorktreeContext(targetWorktree, slice, branchName) {
35
+ if (targetWorktree !== slice.repoRoot) {
36
+ ensureLocalExclude(targetWorktree, 'WORKTREE_CONTEXT.md');
37
+ }
38
+
39
+ const lines = [
40
+ '# Worktree Context',
41
+ '',
42
+ '> Archivo generado localmente por `tools/scripts/start-slice.sh`.',
43
+ '> No se trackea en git.',
44
+ '',
45
+ `**Alias:** ${toAlias(slice.ticket)}`,
46
+ `**Spec:** ${slice.specSlug}`,
47
+ `**Spec family:** ${slice.specFamily}`,
48
+ `**Slice:** ${slice.sliceId}`,
49
+ `**Ticket:** ${slice.ticket}`,
50
+ `**Branch:** ${branchName}`,
51
+ `**Worktree:** ${targetWorktree}`,
52
+ `**Status:** ${slice.status}`,
53
+ '',
54
+ '## Title',
55
+ '',
56
+ slice.json.title || slice.sliceId,
57
+ '',
58
+ '## Objective',
59
+ '',
60
+ slice.json.objective || 'Sin objetivo declarado.',
61
+ '',
62
+ '## Routes',
63
+ '',
64
+ Array.isArray(slice.json.ui_scope?.routes) && slice.json.ui_scope.routes.length > 0 ? slice.json.ui_scope.routes.map((item) => `- ${item}`).join('\n') : '- n/a',
65
+ '',
66
+ '## Components',
67
+ '',
68
+ Array.isArray(slice.json.ui_scope?.components) && slice.json.ui_scope.components.length > 0 ? slice.json.ui_scope.components.map((item) => `- ${item}`).join('\n') : '- n/a',
69
+ '',
70
+ '## Allowed Files',
71
+ '',
72
+ Array.isArray(slice.files) && slice.files.length > 0 ? slice.files.map((item) => `- ${item}`).join('\n') : '- n/a',
73
+ '',
74
+ '## Active Slice Brief',
75
+ '',
76
+ `- ${activeSlicePath(slice.repoRoot)}`,
77
+ '',
78
+ '## Constraints',
79
+ '',
80
+ Array.isArray(slice.json.not_included) && slice.json.not_included.length > 0 ? slice.json.not_included.map((item) => `- ${item}`).join('\n') : '- n/a',
81
+ '',
82
+ '## Expected Validation',
83
+ '',
84
+ Array.isArray(slice.acceptance) && slice.acceptance.length > 0 ? slice.acceptance.map((item) => `- ${item}`).join('\n') : '- n/a',
85
+ '',
86
+ ];
87
+
88
+ fs.writeFileSync(path.join(targetWorktree, 'WORKTREE_CONTEXT.md'), `${lines.join('\n')}\n`);
89
+ }
90
+
91
+ function writeActiveSlice(repoRoot, slice) {
92
+ const activePath = activeSlicePath(repoRoot);
93
+ ensureLocalExclude(repoRoot, 'docs/ai/ACTIVE_SLICE.md');
94
+
95
+ const existed = fs.existsSync(activePath);
96
+ fs.mkdirSync(path.dirname(activePath), { recursive: true });
97
+ const body = renderActiveSlice(slice);
98
+ fs.writeFileSync(activePath, body);
99
+ writeFrontMatter(activePath, {
100
+ purpose: 'Active slice execution brief',
101
+ applies_when: 'implementation',
102
+ token_cost: estimateTokenCost(body),
103
+ last_updated: new Date().toISOString().slice(0, 10),
104
+ supersedes: null,
105
+ });
106
+ return existed ? { path: activePath, replaced: true } : { path: activePath, replaced: false };
107
+ }
108
+
109
+ function removeActiveSlice(repoRoot) {
110
+ const activePath = activeSlicePath(repoRoot);
111
+ if (fs.existsSync(activePath)) {
112
+ fs.rmSync(activePath);
113
+ return true;
114
+ }
115
+ return false;
116
+ }
117
+
118
+ function refreshActiveSlicesBoard(repoRoot) {
119
+ const outputPath = path.join(repoRoot, 'ACTIVE_SLICES.md');
120
+ const gitDir = runGit(['rev-parse', '--absolute-git-dir'], repoRoot);
121
+ appendUniqueLine(path.join(gitDir, 'info', 'exclude'), 'ACTIVE_SLICES.md');
122
+
123
+ const sliceMap = new Map();
124
+ const walk = (rootDir) => {
125
+ if (!fs.existsSync(rootDir)) {
126
+ return;
127
+ }
128
+ for (const entry of fs.readdirSync(rootDir, { withFileTypes: true })) {
129
+ const fullPath = path.join(rootDir, entry.name);
130
+ if (entry.isDirectory()) {
131
+ walk(fullPath);
132
+ continue;
133
+ }
134
+ if (entry.isFile() && entry.name === 'slice.json' && fullPath.includes(`${path.sep}slices${path.sep}`)) {
135
+ const json = parseJsonWithComments(fs.readFileSync(fullPath, 'utf8'));
136
+ const relPath = path.relative(repoRoot, fullPath);
137
+ const parts = relPath.split(path.sep);
138
+ const specFamily = parts[0];
139
+ const specSlug = parts[1];
140
+ const branchName = json.git?.branch_name;
141
+ if (!branchName) {
142
+ continue;
143
+ }
144
+ sliceMap.set(branchName, {
145
+ specFamily,
146
+ specSlug,
147
+ relPath,
148
+ sliceId: json.slice_id || path.basename(path.dirname(fullPath)),
149
+ title: json.title || json.slice_id || path.basename(path.dirname(fullPath)),
150
+ ticket: json.ticket || '',
151
+ status: json.status || 'pending',
152
+ });
153
+ }
154
+ }
155
+ };
156
+
157
+ walk(path.join(repoRoot, 'specs'));
158
+ walk(path.join(repoRoot, 'specs-fix'));
159
+
160
+ const worktrees = worktreeList(repoRoot);
161
+ const primary = [];
162
+ const active = [];
163
+ const frozen = [];
164
+ const auxiliary = [];
165
+
166
+ const toAliasLocal = (ticket) => toAlias(ticket);
167
+
168
+ for (const entry of worktrees) {
169
+ const worktreePath = entry.worktree;
170
+ const branchRef = entry.branch || '';
171
+ const branchName = branchRef.replace('refs/heads/', '');
172
+ if (worktreePath === repoRoot) {
173
+ primary.push({ branch: branchName || '(detached)', path: worktreePath });
174
+ continue;
175
+ }
176
+ const slice = sliceMap.get(branchName);
177
+ if (!slice) {
178
+ auxiliary.push({ branch: branchName || '(detached)', path: worktreePath });
179
+ continue;
180
+ }
181
+ let liveStatus = slice.status;
182
+ const liveSlicePath = path.join(worktreePath, slice.relPath);
183
+ if (fs.existsSync(liveSlicePath)) {
184
+ try {
185
+ const liveJson = parseJsonWithComments(fs.readFileSync(liveSlicePath, 'utf8'));
186
+ liveStatus = liveJson.status || liveStatus;
187
+ } catch {
188
+ // ignore
189
+ }
190
+ }
191
+ const row = {
192
+ alias: toAliasLocal(slice.ticket),
193
+ spec: slice.specSlug,
194
+ slice: slice.sliceId,
195
+ branch: branchName,
196
+ path: worktreePath,
197
+ status: liveStatus,
198
+ title: slice.title,
199
+ };
200
+ if (slice.sliceId.startsWith('slice-00')) {
201
+ frozen.push(row);
202
+ } else {
203
+ active.push(row);
204
+ }
205
+ }
206
+
207
+ active.sort((a, b) => a.alias.localeCompare(b.alias));
208
+ frozen.sort((a, b) => a.alias.localeCompare(b.alias));
209
+ auxiliary.sort((a, b) => a.branch.localeCompare(b.branch));
210
+
211
+ const lines = [
212
+ '# Active Slices',
213
+ '',
214
+ '> Archivo local generado por `tools/scripts/refresh-active-slices.sh`.',
215
+ '> No se trackea en git.',
216
+ '',
217
+ `**Actualizado:** ${new Date().toISOString()}`,
218
+ '',
219
+ '## Convencion',
220
+ '',
221
+ '- `Alias`: identificador corto para hablar del slice sin ambiguedad',
222
+ '- `Spec`: directorio del spec',
223
+ '- `slice-00`: baseline congelado, solo referencia',
224
+ '- `slice-01+`: implementacion o revalidacion',
225
+ '',
226
+ '## Checkout Principal',
227
+ '',
228
+ '| Branch | Path |',
229
+ '|--------|------|',
230
+ ];
231
+
232
+ for (const row of primary) {
233
+ lines.push(`| ${row.branch.replace(/\|/g, '\\|')} | ${row.path.replace(/\|/g, '\\|')} |`);
234
+ }
235
+
236
+ lines.push('', '## Implementacion Activa', '', '| Alias | Spec | Slice | Branch | Estado | Path |', '|-------|------|-------|--------|--------|------|');
237
+ if (active.length === 0) {
238
+ lines.push('| - | - | - | - | - | - |');
239
+ } else {
240
+ for (const row of active) {
241
+ lines.push(`| ${row.alias.replace(/\|/g, '\\|')} | ${row.spec.replace(/\|/g, '\\|')} | ${row.slice.replace(/\|/g, '\\|')} | ${row.branch.replace(/\|/g, '\\|')} | ${row.status.replace(/\|/g, '\\|')} | ${row.path.replace(/\|/g, '\\|')} |`);
242
+ }
243
+ }
244
+
245
+ lines.push('', '## Baselines Congelados', '', '| Alias | Spec | Slice | Branch | Estado | Path |', '|-------|------|-------|--------|--------|------|');
246
+ if (frozen.length === 0) {
247
+ lines.push('| - | - | - | - | - | - |');
248
+ } else {
249
+ for (const row of frozen) {
250
+ lines.push(`| ${row.alias.replace(/\|/g, '\\|')} | ${row.spec.replace(/\|/g, '\\|')} | ${row.slice.replace(/\|/g, '\\|')} | ${row.branch.replace(/\|/g, '\\|')} | congelado | ${row.path.replace(/\|/g, '\\|')} |`);
251
+ }
252
+ }
253
+
254
+ lines.push('', '## Worktrees Auxiliares', '', '| Branch | Path |', '|--------|------|');
255
+ if (auxiliary.length === 0) {
256
+ lines.push('| - | - |');
257
+ } else {
258
+ for (const row of auxiliary) {
259
+ lines.push(`| ${row.branch.replace(/\|/g, '\\|')} | ${row.path.replace(/\|/g, '\\|')} |`);
260
+ }
261
+ }
262
+
263
+ lines.push('', '## Recomendacion Operativa', '', '- En VS Code, dejar visibles solo `develop` y la tabla de `Implementacion Activa`.', '- Tratar la tabla de `Baselines Congelados` como solo lectura.', '- Cerrar visualmente los `Worktrees Auxiliares` salvo cuando estes trabajando ese PR o esa tarea de proceso.');
264
+ fs.writeFileSync(outputPath, `${lines.join('\n')}\n`);
265
+ return outputPath;
266
+ }
267
+
268
+ function resolveBaseRef(repoRoot, baseBranch) {
269
+ if (hasLocalBranch(repoRoot, baseBranch)) {
270
+ return baseBranch;
271
+ }
272
+ if (hasRemoteBranch(repoRoot, baseBranch)) {
273
+ return `origin/${baseBranch}`;
274
+ }
275
+ if (lsRemoteHeads(repoRoot, baseBranch)) {
276
+ try {
277
+ fetchBranch(repoRoot, baseBranch);
278
+ return `origin/${baseBranch}`;
279
+ } catch {
280
+ throw new Error(`create-quiver: origin existe pero no se pudo actualizar '${baseBranch}'. Revisa conectividad o crea la rama local '${baseBranch}' antes de correr start-slice.`);
281
+ }
282
+ }
283
+ throw new Error(`create-quiver: no se encontró '${baseBranch}' como rama local ni como ref remota 'origin/${baseBranch}'. Crea la rama base localmente o configura origin antes de correr start-slice.`);
284
+ }
285
+
286
+ function findExistingWorktreeForBranch(repoRoot, branchName) {
287
+ for (const entry of worktreeList(repoRoot)) {
288
+ const branchRef = entry.branch || '';
289
+ const currentBranch = branchRef.replace('refs/heads/', '');
290
+ if (currentBranch === branchName) {
291
+ return entry.worktree;
292
+ }
293
+ }
294
+ return '';
295
+ }
296
+
297
+ function startSlice(sliceInput, options = {}) {
298
+ const allowDraft = options.allowDraft === true || process.env.ALLOW_DRAFT_SLICE === '1';
299
+ const repoRoot = runGit(['rev-parse', '--show-toplevel'], process.cwd());
300
+ const slice = resolveSliceContext(repoRoot, sliceInput);
301
+ slice.repoRoot = repoRoot;
302
+ validateSliceMetaForStart(slice);
303
+
304
+ if (slice.status === 'blocked') {
305
+ throw new Error('create-quiver: el slice esta bloqueado (status=blocked). Resolve el bloqueante antes de iniciar.');
306
+ }
307
+ if (slice.status === 'cancelled') {
308
+ throw new Error('create-quiver: el slice esta cancelado (status=cancelled).');
309
+ }
310
+ if (slice.status === 'completed') {
311
+ console.log('WARN: el slice ya figura como completed. Si realmente corresponde reejecutarlo, cambia el status a in_progress.');
312
+ }
313
+ if (slice.status === 'draft' && !allowDraft) {
314
+ throw new Error("create-quiver: el slice esta en estado 'draft'. Marca el slice como 'ready' o usa --allow-draft para un bootstrap intencional.");
315
+ }
316
+ if (slice.status === 'draft') {
317
+ console.log('WARN: bootstrap intencional para un slice en draft.');
318
+ }
319
+
320
+ const worktreesRoot = worktreesRootForRepo(repoRoot, slice.branchName);
321
+ const worktreePath = path.join(worktreesRoot, safeBranchName(slice.branchName));
322
+ const existingWorktreePath = findExistingWorktreeForBranch(repoRoot, slice.branchName);
323
+
324
+ worktreePrune(repoRoot);
325
+
326
+ if (existingWorktreePath && fs.existsSync(existingWorktreePath)) {
327
+ writeWorktreeContext(existingWorktreePath, slice, slice.branchName);
328
+ const activeSlice = writeActiveSlice(repoRoot, slice);
329
+ if (activeSlice.replaced) {
330
+ console.log('WARN: Reemplazando docs/ai/ACTIVE_SLICE.md existente.');
331
+ } else {
332
+ console.log('Wrote docs/ai/ACTIVE_SLICE.md');
333
+ }
334
+ refreshActiveSlicesBoard(repoRoot);
335
+ console.log('La rama ya tiene un worktree asociado.');
336
+ console.log(`Alias: ${toAlias(slice.ticket)}`);
337
+ console.log(`Spec: ${slice.specSlug}`);
338
+ console.log(`Slice: ${slice.sliceId}`);
339
+ console.log(`Ticket: ${slice.ticket}`);
340
+ console.log(`Rama: ${slice.branchName}`);
341
+ console.log(`Base: ${slice.baseBranch}`);
342
+ console.log(`Worktree: ${existingWorktreePath}`);
343
+ return { worktreePath: existingWorktreePath, reused: true };
344
+ }
345
+
346
+ if (fs.existsSync(worktreePath) && !fs.existsSync(path.join(worktreePath, '.git'))) {
347
+ throw new Error(`create-quiver: la ruta '${worktreePath}' ya existe y no parece un worktree git.`);
348
+ }
349
+
350
+ ensureDir(worktreesRoot);
351
+ try {
352
+ fetchRemote(repoRoot, 'origin', ['--prune']);
353
+ } catch {
354
+ // ignore network issues in smoke environments
355
+ }
356
+
357
+ if (hasLocalBranch(repoRoot, slice.branchName)) {
358
+ worktreeAdd(repoRoot, worktreePath, slice.branchName);
359
+ } else if (lsRemoteHeads(repoRoot, slice.branchName)) {
360
+ try {
361
+ fetchBranch(repoRoot, slice.branchName);
362
+ } catch {
363
+ // ignore and let worktree add try the remote ref path
364
+ }
365
+ worktreeAdd(repoRoot, worktreePath, slice.branchName);
366
+ } else {
367
+ const baseRef = resolveBaseRef(repoRoot, slice.baseBranch);
368
+ worktreeAdd(repoRoot, worktreePath, baseRef, { branch: slice.branchName });
369
+ }
370
+
371
+ writeWorktreeContext(worktreePath, slice, slice.branchName);
372
+ const activeSlice = writeActiveSlice(repoRoot, slice);
373
+ if (activeSlice.replaced) {
374
+ console.log('WARN: Reemplazando docs/ai/ACTIVE_SLICE.md existente.');
375
+ } else {
376
+ console.log('Wrote docs/ai/ACTIVE_SLICE.md');
377
+ }
378
+ refreshActiveSlicesBoard(repoRoot);
379
+
380
+ console.log('Slice listo para trabajar.');
381
+ console.log(`Alias: ${toAlias(slice.ticket)}`);
382
+ console.log(`Spec: ${slice.specSlug}`);
383
+ console.log(`Slice: ${slice.sliceId}`);
384
+ console.log(`Ticket: ${slice.ticket}`);
385
+ console.log(`Tipo de rama: ${slice.branchType}`);
386
+ console.log(`Base: ${slice.baseBranch}`);
387
+ console.log(`Slug: ${slice.branchSlug}`);
388
+ console.log(`Rama: ${slice.branchName}`);
389
+ console.log(`Worktree: ${worktreePath}`);
390
+ console.log(`Contexto: ${worktreePath}/WORKTREE_CONTEXT.md`);
391
+ return { worktreePath, reused: false };
392
+ }
393
+
394
+ function cleanupSlice(sliceInput, options = {}) {
395
+ const closeBaseline = options.closeBaseline === true;
396
+ const discard = options.discard === true;
397
+ const forceDelete = options.force === true;
398
+ const dryRun = options.dryRun === true;
399
+ const repoRoot = resolveTargetRoot(process.cwd(), '.');
400
+ const slice = resolveSliceContext(repoRoot, sliceInput);
401
+ const worktreesRoot = worktreesRootForRepo(repoRoot, slice.branchName);
402
+ const worktreePath = path.join(worktreesRoot, safeBranchName(slice.branchName));
403
+
404
+ if (slice.isBaseline && !closeBaseline) {
405
+ console.log(`INFO: '${slice.sliceId}' es baseline. El worktree queda congelado por default.`);
406
+ console.log('INFO: Usa --close-baseline solo cuando la primera ola del spec ya este estable o mergeada.');
407
+ return;
408
+ }
409
+
410
+ if (!discard) {
411
+ const branch = currentBranch(repoRoot);
412
+ if (branch !== 'develop') {
413
+ throw new Error(`create-quiver: el cleanup normal debe correrse desde develop. Rama actual: ${branch}`);
414
+ }
415
+ if (statusPorcelain(repoRoot) !== '') {
416
+ throw new Error('create-quiver: el checkout actual no esta limpio. Limpialo antes del cleanup.');
417
+ }
418
+
419
+ try {
420
+ fetchRemote(repoRoot, 'origin', ['develop']);
421
+ } catch {
422
+ // ignore
423
+ }
424
+
425
+ const localDevelopSha = runGit(['rev-parse', 'HEAD'], repoRoot);
426
+ const remoteDevelopSha = runGit(['rev-parse', 'origin/develop'], repoRoot);
427
+ if (localDevelopSha !== remoteDevelopSha) {
428
+ throw new Error('create-quiver: develop local no esta actualizado. Ejecuta git pull --ff-only antes del cleanup.');
429
+ }
430
+
431
+ if (hasLocalBranch(repoRoot, slice.branchName)) {
432
+ if (!mergeBaseIsAncestor(repoRoot, slice.branchName, 'origin/develop')) {
433
+ throw new Error(`create-quiver: la rama '${slice.branchName}' no esta mergeada en origin/develop. Usa --discard si el slice se descarta.`);
434
+ }
435
+ }
436
+ }
437
+
438
+ const worktreeExists = fs.existsSync(worktreePath);
439
+ const branchExists = hasLocalBranch(repoRoot, slice.branchName);
440
+ if (!worktreeExists && !branchExists) {
441
+ throw new Error(`create-quiver: no existe worktree ni rama local para '${slice.sliceId}'.`);
442
+ }
443
+
444
+ const removeWorktree = () => worktreeRemove(repoRoot, worktreePath, forceDelete || discard);
445
+ const removeBranch = () => branchDelete(repoRoot, slice.branchName, forceDelete || discard);
446
+
447
+ if (dryRun) {
448
+ console.log(`DRY RUN: slice=${slice.sliceId} branch=${slice.branchName}`);
449
+ if (worktreeExists) {
450
+ console.log(`DRY RUN: git worktree remove ${forceDelete || discard ? '--force ' : ''}${worktreePath}`);
451
+ }
452
+ if (branchExists) {
453
+ console.log(`DRY RUN: git branch ${forceDelete || discard ? '-D' : '-d'} ${slice.branchName}`);
454
+ }
455
+ return;
456
+ }
457
+
458
+ if (worktreeExists) {
459
+ removeWorktree();
460
+ console.log(`PASS: Worktree eliminado: ${worktreePath}`);
461
+ }
462
+ if (branchExists) {
463
+ removeBranch();
464
+ console.log(`PASS: Rama local eliminada: ${slice.branchName}`);
465
+ }
466
+
467
+ if (removeActiveSlice(repoRoot)) {
468
+ console.log('PASS: ACTIVE_SLICE.md eliminado');
469
+ }
470
+
471
+ refreshActiveSlicesBoard(repoRoot);
472
+ console.log(`PASS: Cleanup finalizado para '${slice.sliceId}'.`);
473
+ }
474
+
475
+ module.exports = {
476
+ cleanupSlice,
477
+ refreshActiveSlicesBoard,
478
+ startSlice,
479
+ };
@@ -0,0 +1,19 @@
1
+ const path = require('path');
2
+
3
+ function resolveTargetRoot(cwd, targetDir, pathLib = path) {
4
+ return pathLib.resolve(cwd, targetDir);
5
+ }
6
+
7
+ function toPosixPath(filePath, pathLib = path) {
8
+ return filePath.split(pathLib.sep).join('/');
9
+ }
10
+
11
+ function relativePosixPath(root, absolutePath, pathLib = path) {
12
+ return toPosixPath(pathLib.relative(root, absolutePath), pathLib);
13
+ }
14
+
15
+ module.exports = {
16
+ relativePosixPath,
17
+ resolveTargetRoot,
18
+ toPosixPath,
19
+ };