create-quiver 0.10.0 → 0.12.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 (165) hide show
  1. package/BACKLOG.md +16 -17
  2. package/CHANGELOG.md +34 -0
  3. package/README.md +174 -39
  4. package/README_FOR_AI.md +48 -24
  5. package/ROADMAP.md +22 -11
  6. package/docs/AI_CONTEXT.md.template +2 -0
  7. package/docs/AI_ONBOARDING_PROMPT.md.template +25 -18
  8. package/docs/COMMANDS.md.template +59 -11
  9. package/docs/CONTEXTO.md.template +2 -0
  10. package/docs/DECISIONS.md.template +1 -0
  11. package/docs/INDEX.md.template +20 -18
  12. package/docs/STATUS.md.template +1 -0
  13. package/docs/SUPPORT_MATRIX.md.template +2 -2
  14. package/docs/TROUBLESHOOTING.md.template +50 -0
  15. package/docs/WORKFLOW.md.template +25 -17
  16. package/package.json +19 -2
  17. package/package.template.json +13 -1
  18. package/scripts/init-docs.sh +11 -4
  19. package/scripts/package-quiver.sh +18 -2
  20. package/specs/quiver-v22-guided-ai-workflow/EVIDENCE_REPORT.md +58 -0
  21. package/specs/quiver-v22-guided-ai-workflow/EXECUTION_PLAN.md +88 -0
  22. package/specs/quiver-v22-guided-ai-workflow/SPEC.md +228 -0
  23. package/specs/quiver-v22-guided-ai-workflow/STATUS.md +42 -0
  24. package/specs/quiver-v22-guided-ai-workflow/pr.md +104 -0
  25. package/specs/quiver-v22-guided-ai-workflow/slices/slice-00-spec-foundation/CLOSURE_BRIEF.md +35 -0
  26. package/specs/quiver-v22-guided-ai-workflow/slices/slice-00-spec-foundation/EXECUTION_BRIEF.md +61 -0
  27. package/specs/quiver-v22-guided-ai-workflow/slices/slice-00-spec-foundation/slice.json +51 -0
  28. package/specs/quiver-v22-guided-ai-workflow/slices/slice-01-docs-source-of-truth-sync/CLOSURE_BRIEF.md +31 -0
  29. package/specs/quiver-v22-guided-ai-workflow/slices/slice-01-docs-source-of-truth-sync/EXECUTION_BRIEF.md +58 -0
  30. package/specs/quiver-v22-guided-ai-workflow/slices/slice-01-docs-source-of-truth-sync/slice.json +55 -0
  31. package/specs/quiver-v22-guided-ai-workflow/slices/slice-02-prepare-command-diagnostics/CLOSURE_BRIEF.md +30 -0
  32. package/specs/quiver-v22-guided-ai-workflow/slices/slice-02-prepare-command-diagnostics/EXECUTION_BRIEF.md +57 -0
  33. package/specs/quiver-v22-guided-ai-workflow/slices/slice-02-prepare-command-diagnostics/slice.json +57 -0
  34. package/specs/quiver-v22-guided-ai-workflow/slices/slice-03-context-doc-refresh/CLOSURE_BRIEF.md +32 -0
  35. package/specs/quiver-v22-guided-ai-workflow/slices/slice-03-context-doc-refresh/EXECUTION_BRIEF.md +56 -0
  36. package/specs/quiver-v22-guided-ai-workflow/slices/slice-03-context-doc-refresh/slice.json +56 -0
  37. package/specs/quiver-v22-guided-ai-workflow/slices/slice-04-planner-approval-state/CLOSURE_BRIEF.md +33 -0
  38. package/specs/quiver-v22-guided-ai-workflow/slices/slice-04-planner-approval-state/EXECUTION_BRIEF.md +56 -0
  39. package/specs/quiver-v22-guided-ai-workflow/slices/slice-04-planner-approval-state/slice.json +58 -0
  40. package/specs/quiver-v22-guided-ai-workflow/slices/slice-05-spec-worktree-lifecycle/CLOSURE_BRIEF.md +32 -0
  41. package/specs/quiver-v22-guided-ai-workflow/slices/slice-05-spec-worktree-lifecycle/EXECUTION_BRIEF.md +56 -0
  42. package/specs/quiver-v22-guided-ai-workflow/slices/slice-05-spec-worktree-lifecycle/slice.json +54 -0
  43. package/specs/quiver-v22-guided-ai-workflow/slices/slice-06-executor-commit-recovery/CLOSURE_BRIEF.md +32 -0
  44. package/specs/quiver-v22-guided-ai-workflow/slices/slice-06-executor-commit-recovery/EXECUTION_BRIEF.md +58 -0
  45. package/specs/quiver-v22-guided-ai-workflow/slices/slice-06-executor-commit-recovery/slice.json +57 -0
  46. package/specs/quiver-v22-guided-ai-workflow/slices/slice-07-execution-waves-delegation/CLOSURE_BRIEF.md +32 -0
  47. package/specs/quiver-v22-guided-ai-workflow/slices/slice-07-execution-waves-delegation/EXECUTION_BRIEF.md +58 -0
  48. package/specs/quiver-v22-guided-ai-workflow/slices/slice-07-execution-waves-delegation/slice.json +55 -0
  49. package/specs/quiver-v22-guided-ai-workflow/slices/slice-08-pr-create-gh-ssh/CLOSURE_BRIEF.md +32 -0
  50. package/specs/quiver-v22-guided-ai-workflow/slices/slice-08-pr-create-gh-ssh/EXECUTION_BRIEF.md +58 -0
  51. package/specs/quiver-v22-guided-ai-workflow/slices/slice-08-pr-create-gh-ssh/slice.json +53 -0
  52. package/specs/quiver-v22-guided-ai-workflow/slices/slice-09-post-merge-cleanup-release-safety/CLOSURE_BRIEF.md +33 -0
  53. package/specs/quiver-v22-guided-ai-workflow/slices/slice-09-post-merge-cleanup-release-safety/EXECUTION_BRIEF.md +59 -0
  54. package/specs/quiver-v22-guided-ai-workflow/slices/slice-09-post-merge-cleanup-release-safety/slice.json +59 -0
  55. package/specs/quiver-v22-guided-ai-workflow/slices/slice-10-docs-smokes-release-readiness/CLOSURE_BRIEF.md +34 -0
  56. package/specs/quiver-v22-guided-ai-workflow/slices/slice-10-docs-smokes-release-readiness/EXECUTION_BRIEF.md +58 -0
  57. package/specs/quiver-v22-guided-ai-workflow/slices/slice-10-docs-smokes-release-readiness/slice.json +60 -0
  58. package/specs/quiver-v23-guided-flow-productization/EVIDENCE_REPORT.md +80 -0
  59. package/specs/quiver-v23-guided-flow-productization/EXECUTION_PLAN.md +80 -0
  60. package/specs/quiver-v23-guided-flow-productization/SPEC.md +203 -0
  61. package/specs/quiver-v23-guided-flow-productization/STATUS.md +39 -0
  62. package/specs/quiver-v23-guided-flow-productization/pr.md +119 -0
  63. package/specs/quiver-v23-guided-flow-productization/slices/slice-00-spec-foundation/CLOSURE_BRIEF.md +30 -0
  64. package/specs/quiver-v23-guided-flow-productization/slices/slice-00-spec-foundation/EXECUTION_BRIEF.md +61 -0
  65. package/specs/quiver-v23-guided-flow-productization/slices/slice-00-spec-foundation/slice.json +51 -0
  66. package/specs/quiver-v23-guided-flow-productization/slices/slice-01-short-command-and-flow-entrypoint/CLOSURE_BRIEF.md +33 -0
  67. package/specs/quiver-v23-guided-flow-productization/slices/slice-01-short-command-and-flow-entrypoint/EXECUTION_BRIEF.md +35 -0
  68. package/specs/quiver-v23-guided-flow-productization/slices/slice-01-short-command-and-flow-entrypoint/slice.json +56 -0
  69. package/specs/quiver-v23-guided-flow-productization/slices/slice-02-flow-status-wizard/CLOSURE_BRIEF.md +31 -0
  70. package/specs/quiver-v23-guided-flow-productization/slices/slice-02-flow-status-wizard/EXECUTION_BRIEF.md +29 -0
  71. package/specs/quiver-v23-guided-flow-productization/slices/slice-02-flow-status-wizard/slice.json +55 -0
  72. package/specs/quiver-v23-guided-flow-productization/slices/slice-03-agent-profiles/CLOSURE_BRIEF.md +33 -0
  73. package/specs/quiver-v23-guided-flow-productization/slices/slice-03-agent-profiles/EXECUTION_BRIEF.md +29 -0
  74. package/specs/quiver-v23-guided-flow-productization/slices/slice-03-agent-profiles/slice.json +54 -0
  75. package/specs/quiver-v23-guided-flow-productization/slices/slice-04-context-preparation-onboarding/CLOSURE_BRIEF.md +32 -0
  76. package/specs/quiver-v23-guided-flow-productization/slices/slice-04-context-preparation-onboarding/EXECUTION_BRIEF.md +30 -0
  77. package/specs/quiver-v23-guided-flow-productization/slices/slice-04-context-preparation-onboarding/slice.json +59 -0
  78. package/specs/quiver-v23-guided-flow-productization/slices/slice-05-planner-iteration-history/CLOSURE_BRIEF.md +31 -0
  79. package/specs/quiver-v23-guided-flow-productization/slices/slice-05-planner-iteration-history/EXECUTION_BRIEF.md +29 -0
  80. package/specs/quiver-v23-guided-flow-productization/slices/slice-05-planner-iteration-history/slice.json +53 -0
  81. package/specs/quiver-v23-guided-flow-productization/slices/slice-06-production-plan-review/CLOSURE_BRIEF.md +33 -0
  82. package/specs/quiver-v23-guided-flow-productization/slices/slice-06-production-plan-review/EXECUTION_BRIEF.md +30 -0
  83. package/specs/quiver-v23-guided-flow-productization/slices/slice-06-production-plan-review/slice.json +54 -0
  84. package/specs/quiver-v23-guided-flow-productization/slices/slice-07-spec-create-experience/CLOSURE_BRIEF.md +33 -0
  85. package/specs/quiver-v23-guided-flow-productization/slices/slice-07-spec-create-experience/EXECUTION_BRIEF.md +30 -0
  86. package/specs/quiver-v23-guided-flow-productization/slices/slice-07-spec-create-experience/slice.json +55 -0
  87. package/specs/quiver-v23-guided-flow-productization/slices/slice-08-executor-prompt-generation/CLOSURE_BRIEF.md +32 -0
  88. package/specs/quiver-v23-guided-flow-productization/slices/slice-08-executor-prompt-generation/EXECUTION_BRIEF.md +30 -0
  89. package/specs/quiver-v23-guided-flow-productization/slices/slice-08-executor-prompt-generation/slice.json +55 -0
  90. package/specs/quiver-v23-guided-flow-productization/slices/slice-09-delegated-slice-execution/CLOSURE_BRIEF.md +33 -0
  91. package/specs/quiver-v23-guided-flow-productization/slices/slice-09-delegated-slice-execution/EXECUTION_BRIEF.md +34 -0
  92. package/specs/quiver-v23-guided-flow-productization/slices/slice-09-delegated-slice-execution/slice.json +57 -0
  93. package/specs/quiver-v23-guided-flow-productization/slices/slice-10-docs-smokes-release-readiness/CLOSURE_BRIEF.md +33 -0
  94. package/specs/quiver-v23-guided-flow-productization/slices/slice-10-docs-smokes-release-readiness/EXECUTION_BRIEF.md +32 -0
  95. package/specs/quiver-v23-guided-flow-productization/slices/slice-10-docs-smokes-release-readiness/slice.json +63 -0
  96. package/specs/quiver-v24-dx-onboarding-hardening/EVIDENCE_REPORT.md +55 -0
  97. package/specs/quiver-v24-dx-onboarding-hardening/EXECUTION_PLAN.md +43 -0
  98. package/specs/quiver-v24-dx-onboarding-hardening/SPEC.md +149 -0
  99. package/specs/quiver-v24-dx-onboarding-hardening/STATUS.md +31 -0
  100. package/specs/quiver-v24-dx-onboarding-hardening/pr.md +76 -0
  101. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-00-spec-foundation/CLOSURE_BRIEF.md +31 -0
  102. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-00-spec-foundation/EXECUTION_BRIEF.md +52 -0
  103. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-00-spec-foundation/slice.json +51 -0
  104. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-01-init-template-hygiene/CLOSURE_BRIEF.md +38 -0
  105. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-01-init-template-hygiene/EXECUTION_BRIEF.md +53 -0
  106. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-01-init-template-hygiene/slice.json +55 -0
  107. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-02-cli-command-routing-version-errors/CLOSURE_BRIEF.md +33 -0
  108. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-02-cli-command-routing-version-errors/EXECUTION_BRIEF.md +50 -0
  109. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-02-cli-command-routing-version-errors/slice.json +52 -0
  110. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-03-doctor-fix-doc-link-checks/CLOSURE_BRIEF.md +33 -0
  111. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-03-doctor-fix-doc-link-checks/EXECUTION_BRIEF.md +50 -0
  112. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-03-doctor-fix-doc-link-checks/slice.json +53 -0
  113. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-04-prepare-output-ai-context-drafts/CLOSURE_BRIEF.md +33 -0
  114. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-04-prepare-output-ai-context-drafts/EXECUTION_BRIEF.md +50 -0
  115. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-04-prepare-output-ai-context-drafts/slice.json +70 -0
  116. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-05-local-slice-validation-base-guidance/CLOSURE_BRIEF.md +36 -0
  117. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-05-local-slice-validation-base-guidance/EXECUTION_BRIEF.md +49 -0
  118. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-05-local-slice-validation-base-guidance/slice.json +52 -0
  119. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-06-plan-graph-next-history-views/CLOSURE_BRIEF.md +43 -0
  120. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-06-plan-graph-next-history-views/EXECUTION_BRIEF.md +53 -0
  121. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-06-plan-graph-next-history-views/slice.json +60 -0
  122. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-07-analyzer-command-map-hardening/CLOSURE_BRIEF.md +32 -0
  123. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-07-analyzer-command-map-hardening/EXECUTION_BRIEF.md +50 -0
  124. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-07-analyzer-command-map-hardening/slice.json +51 -0
  125. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-08-evidence-run-command/CLOSURE_BRIEF.md +34 -0
  126. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-08-evidence-run-command/EXECUTION_BRIEF.md +52 -0
  127. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-08-evidence-run-command/slice.json +54 -0
  128. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-09-spec-viewer-demo-scaffolding/CLOSURE_BRIEF.md +34 -0
  129. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-09-spec-viewer-demo-scaffolding/EXECUTION_BRIEF.md +51 -0
  130. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-09-spec-viewer-demo-scaffolding/slice.json +59 -0
  131. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-10-docs-smokes-release-readiness/CLOSURE_BRIEF.md +33 -0
  132. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-10-docs-smokes-release-readiness/EXECUTION_BRIEF.md +54 -0
  133. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-10-docs-smokes-release-readiness/slice.json +76 -0
  134. package/src/create-quiver/commands/ai.js +508 -35
  135. package/src/create-quiver/commands/demo.js +22 -0
  136. package/src/create-quiver/commands/evidence.js +37 -0
  137. package/src/create-quiver/commands/flow.js +561 -0
  138. package/src/create-quiver/commands/graph.js +14 -1
  139. package/src/create-quiver/commands/next.js +28 -0
  140. package/src/create-quiver/commands/plan.js +6 -3
  141. package/src/create-quiver/commands/prepare.js +236 -0
  142. package/src/create-quiver/commands/spec.js +133 -0
  143. package/src/create-quiver/index.js +688 -25
  144. package/src/create-quiver/lib/agent-profiles.js +148 -0
  145. package/src/create-quiver/lib/ai/context-packs.js +12 -0
  146. package/src/create-quiver/lib/ai/execution-plan.js +370 -10
  147. package/src/create-quiver/lib/ai/executor.js +376 -17
  148. package/src/create-quiver/lib/ai/github.js +196 -0
  149. package/src/create-quiver/lib/ai/onboarding-template.js +365 -0
  150. package/src/create-quiver/lib/ai/plan-review.js +283 -0
  151. package/src/create-quiver/lib/ai/providers.js +1 -0
  152. package/src/create-quiver/lib/ai/safety.js +5 -0
  153. package/src/create-quiver/lib/ai/spec-templates.js +2 -2
  154. package/src/create-quiver/lib/approvals.js +350 -0
  155. package/src/create-quiver/lib/demo.js +657 -0
  156. package/src/create-quiver/lib/doctor.js +234 -0
  157. package/src/create-quiver/lib/evidence.js +115 -0
  158. package/src/create-quiver/lib/init-docs.js +284 -17
  159. package/src/create-quiver/lib/init-layout.js +26 -1
  160. package/src/create-quiver/lib/lifecycle.js +6 -0
  161. package/src/create-quiver/lib/package-safety.js +117 -0
  162. package/src/create-quiver/lib/readiness.js +85 -18
  163. package/src/create-quiver/lib/slice-graph.js +1 -0
  164. package/src/create-quiver/lib/slice.js +8 -8
  165. package/src/create-quiver/lib/spec-worktrees.js +349 -0
@@ -56,7 +56,7 @@ function parseWorktrees(text) {
56
56
  return entries;
57
57
  }
58
58
 
59
- function collectOverlapWarnings(repoRoot, currentBranchName, currentFiles) {
59
+ function collectOverlapWarnings(repoRoot, currentBranchName, currentFiles, baseRef = 'origin/develop') {
60
60
  const sliceMap = new Map();
61
61
  walkSlices(path.join(repoRoot, 'specs'), sliceMap, repoRoot);
62
62
  walkSlices(path.join(repoRoot, 'specs-fix'), sliceMap, repoRoot);
@@ -79,7 +79,7 @@ function collectOverlapWarnings(repoRoot, currentBranchName, currentFiles) {
79
79
  }
80
80
 
81
81
  const dirty = statusPorcelain(worktreePath) !== '';
82
- const aheadCount = revListCount(worktreePath, 'origin/develop..HEAD');
82
+ const aheadCount = revListCount(worktreePath, `${baseRef}..HEAD`);
83
83
  const active = dirty || aheadCount > 0;
84
84
 
85
85
  if (!active) {
@@ -95,6 +95,65 @@ function collectOverlapWarnings(repoRoot, currentBranchName, currentFiles) {
95
95
  return warnings;
96
96
  }
97
97
 
98
+ function validateLocalSliceArtifacts(repoRoot, slice) {
99
+ const sliceDir = path.dirname(slice.sliceAbs);
100
+ for (const file of ['EXECUTION_BRIEF.md', 'CLOSURE_BRIEF.md']) {
101
+ ensureExists(path.join(sliceDir, file), `create-quiver: falta '${path.posix.join(path.dirname(slice.sliceRel), file)}'.`);
102
+ }
103
+ console.log('PASS: El slice local tiene EXECUTION_BRIEF.md y CLOSURE_BRIEF.md.');
104
+
105
+ if (!Array.isArray(slice.json.files) || slice.json.files.length === 0) {
106
+ throw new Error('create-quiver: slice.json debe declarar al menos un archivo en files para validacion local.');
107
+ }
108
+
109
+ const invalidFiles = slice.json.files.filter((file) => typeof file !== 'string' || file.trim().length === 0);
110
+ if (invalidFiles.length > 0) {
111
+ throw new Error('create-quiver: slice.json.files contiene entradas invalidas.');
112
+ }
113
+ console.log('PASS: slice.json declara archivos de alcance.');
114
+ }
115
+
116
+ function baseRecoveryMessage(remote, baseBranch) {
117
+ return `No se encontro la base '${baseBranch}' como rama local ni como '${remote}/${baseBranch}'. Para validacion estructural usa --local; para validacion contra otra base usa --base <branch>; o configura/fetchea el remoto '${remote}'.`;
118
+ }
119
+
120
+ function validateSliceDocumentedOnBase(repoRoot, slice, options = {}) {
121
+ const gate = options.gate || 'execution';
122
+ const remote = options.remote || 'origin';
123
+ const baseBranch = options.baseBranch || slice.baseBranch || 'develop';
124
+ const remoteRef = `${remote}/${baseBranch}`;
125
+ const hasRemoteBase = hasRemoteBranch(repoRoot, baseBranch, remote);
126
+ const hasLocalBase = hasLocalBranch(repoRoot, baseBranch);
127
+
128
+ if (hasRemoteBase && catFileExists(repoRoot, `${remoteRef}:${slice.sliceRel}`)) {
129
+ console.log(`PASS: El slice ya existe en ${remoteRef} (PR base documental mergeado).`);
130
+ return remoteRef;
131
+ }
132
+
133
+ if (hasLocalBase && catFileExists(repoRoot, `${baseBranch}:${slice.sliceRel}`)) {
134
+ console.log(`PASS: El slice ya existe en ${baseBranch} local (modo sin remote).`);
135
+ return baseBranch;
136
+ }
137
+
138
+ if (!hasRemoteBase && !hasLocalBase) {
139
+ const guidance = baseRecoveryMessage(remote, baseBranch);
140
+ if (gate === 'validation') {
141
+ console.log(`WARN: ${guidance}`);
142
+ return null;
143
+ }
144
+
145
+ throw new Error(`create-quiver: ${guidance}`);
146
+ }
147
+
148
+ const expectedBase = hasRemoteBase ? remoteRef : baseBranch;
149
+ if (gate === 'validation') {
150
+ console.log(`WARN: El slice no existe todavia en ${expectedBase}. El PR base documental sigue pendiente de merge. Podes abrir el PR del slice igual si el humano mergea en orden.`);
151
+ return expectedBase;
152
+ }
153
+
154
+ throw new Error(`create-quiver: el slice no existe en ${expectedBase}. Mergea primero el PR base documental o usa --local para validar solo estructura local.`);
155
+ }
156
+
98
157
  function validateDeclaredDependencyContract(repoRoot, slice) {
99
158
  const declaredDependsOn = Array.isArray(slice.json.depends_on) ? slice.json.depends_on : null;
100
159
  const declaredParallelSafe = typeof slice.json.parallel_safe === 'string' ? slice.json.parallel_safe.trim() : '';
@@ -147,35 +206,43 @@ function validateDeclaredDependencyContract(repoRoot, slice) {
147
206
 
148
207
  function checkSliceReadiness(sliceInput, options = {}) {
149
208
  const gate = options.gate || 'execution';
209
+ const localMode = options.local === true;
150
210
  const strictOverlap = options.strictOverlap === true;
211
+ const remote = options.remote || 'origin';
151
212
  const repoRoot = runGit(['rev-parse', '--show-toplevel'], process.cwd());
152
213
  const slice = resolveSliceContext(repoRoot, sliceInput);
214
+ const baseBranch = options.baseBranch || slice.baseBranch || 'develop';
153
215
 
154
216
  for (const specFile of ['SPEC.md', 'STATUS.md', 'EVIDENCE_REPORT.md']) {
155
217
  ensureExists(path.join(repoRoot, slice.specDirRel, specFile), `create-quiver: falta '${slice.specDirRel}/${specFile}'.`);
156
218
  }
157
219
  console.log('PASS: El spec local tiene SPEC.md, STATUS.md y EVIDENCE_REPORT.md.');
158
220
 
159
- if (catFileExists(repoRoot, `origin/develop:${slice.sliceRel}`)) {
160
- console.log('PASS: El slice ya existe en origin/develop (PR base documental mergeado).');
161
- } else if (catFileExists(repoRoot, `develop:${slice.sliceRel}`)) {
162
- console.log('PASS: El slice ya existe en develop local (modo sin origin).');
163
- } else if (gate === 'validation') {
164
- console.log('WARN: El slice no existe todavia en origin/develop. El PR base documental sigue pendiente de merge. Podes abrir el PR del slice igual — el humano mergea en orden.');
221
+ let baseRef = null;
222
+ if (localMode) {
223
+ validateLocalSliceArtifacts(repoRoot, slice);
224
+ console.log(`INFO: Modo local: se omite validacion de existencia del slice en ${remote}/${baseBranch} o ${baseBranch}.`);
225
+ console.log('INFO: Modo local: se omite validacion de overlap contra worktrees activos basada en rama remota/base.');
165
226
  } else {
166
- throw new Error('create-quiver: el slice no existe en origin/develop. Mergea primero el PR base documental.');
227
+ baseRef = validateSliceDocumentedOnBase(repoRoot, slice, {
228
+ baseBranch,
229
+ gate,
230
+ remote,
231
+ });
167
232
  }
168
233
 
169
- const overlapWarnings = collectOverlapWarnings(repoRoot, currentBranch(repoRoot), slice.files);
170
- if (overlapWarnings.length === 0) {
171
- console.log('PASS: No se detecto overlap con worktrees activos.');
172
- } else {
173
- for (const warning of overlapWarnings) {
174
- const [overlapBranch, overlapFiles] = warning.split('|');
175
- if (strictOverlap) {
176
- throw new Error(`create-quiver: Overlap con worktree activo '${overlapBranch}': ${overlapFiles}`);
234
+ if (!localMode) {
235
+ const overlapWarnings = collectOverlapWarnings(repoRoot, currentBranch(repoRoot), slice.files, baseRef || `${remote}/${baseBranch}`);
236
+ if (overlapWarnings.length === 0) {
237
+ console.log('PASS: No se detecto overlap con worktrees activos.');
238
+ } else {
239
+ for (const warning of overlapWarnings) {
240
+ const [overlapBranch, overlapFiles] = warning.split('|');
241
+ if (strictOverlap) {
242
+ throw new Error(`create-quiver: Overlap con worktree activo '${overlapBranch}': ${overlapFiles}`);
243
+ }
244
+ console.log(`WARN: Overlap con worktree activo '${overlapBranch}': ${overlapFiles}`);
177
245
  }
178
- console.log(`WARN: Overlap con worktree activo '${overlapBranch}': ${overlapFiles}`);
179
246
  }
180
247
  }
181
248
 
@@ -143,6 +143,7 @@ function readAllSlices(rootDir) {
143
143
  parallel_safe: typeof json.parallel_safe === 'string' ? json.parallel_safe : null,
144
144
  parallel_safe_reason: typeof json.parallel_safe_reason === 'string' ? json.parallel_safe_reason : null,
145
145
  status: typeof json.status === 'string' ? json.status : 'draft',
146
+ ticket: typeof json.ticket === 'string' ? json.ticket : '',
146
147
  title: typeof json.title === 'string' ? json.title : sliceId,
147
148
  json,
148
149
  });
@@ -74,19 +74,19 @@ function validateSliceMetaForStart(slice) {
74
74
  throw new Error('create-quiver: el bloque "git" debe incluir "branch_type", "base_branch", "branch_slug" y "branch_name".');
75
75
  }
76
76
 
77
- const expectedBaseByType = {
78
- feature: 'develop',
79
- bugfix: 'develop',
80
- hotfix: 'main',
77
+ const allowedBaseByType = {
78
+ feature: ['main', 'develop'],
79
+ bugfix: ['main', 'develop'],
80
+ hotfix: ['main'],
81
81
  };
82
82
 
83
- if (!expectedBaseByType[slice.branchType]) {
83
+ if (!allowedBaseByType[slice.branchType]) {
84
84
  throw new Error(`create-quiver: git.branch_type invalido: "${slice.branchType}". Usa "feature", "bugfix" o "hotfix".`);
85
85
  }
86
86
 
87
- const expectedBaseBranch = expectedBaseByType[slice.branchType];
88
- if (slice.baseBranch !== expectedBaseBranch) {
89
- throw new Error(`create-quiver: git.base_branch invalido para ${slice.branchType}. Esperado: "${expectedBaseBranch}".`);
87
+ const allowedBaseBranches = allowedBaseByType[slice.branchType];
88
+ if (!allowedBaseBranches.includes(slice.baseBranch)) {
89
+ throw new Error(`create-quiver: git.base_branch invalido para ${slice.branchType}. Usa "${allowedBaseBranches.join('" o "')}".`);
90
90
  }
91
91
 
92
92
  const expectedBranchName = `${slice.branchType}/${slice.ticket}-${slice.branchSlug}`;
@@ -0,0 +1,349 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const {
5
+ currentBranch,
6
+ fetchRemote,
7
+ hasLocalBranch,
8
+ hasRemoteBranch,
9
+ isCleanWorktree,
10
+ lsRemoteHeads,
11
+ mergeBaseIsAncestor,
12
+ runGit,
13
+ statusPorcelain,
14
+ worktreeAdd,
15
+ worktreeList,
16
+ worktreePrune,
17
+ worktreeRemove,
18
+ } = require('./git');
19
+ const { parseJsonWithComments } = require('./json');
20
+ const { safeBranchName, worktreesRootForRepo } = require('./slice');
21
+
22
+ function formatError(message) {
23
+ return `create-quiver: ${message}`;
24
+ }
25
+
26
+ function toPosix(relativePath) {
27
+ return relativePath.split(path.sep).join('/');
28
+ }
29
+
30
+ function findSpecDir(repoRoot, specInput) {
31
+ const value = String(specInput || '').trim();
32
+ if (!value || value === '.') {
33
+ throw new Error(formatError('missing spec directory. Use: npx create-quiver spec status specs/<spec-slug>'));
34
+ }
35
+
36
+ const resolved = path.resolve(repoRoot, value);
37
+ if (!fs.existsSync(path.join(resolved, 'SPEC.md'))) {
38
+ throw new Error(formatError(`missing SPEC.md in ${toPosix(path.relative(repoRoot, resolved))}`));
39
+ }
40
+
41
+ return resolved;
42
+ }
43
+
44
+ function listSpecSlices(specDir) {
45
+ const slicesDir = path.join(specDir, 'slices');
46
+ if (!fs.existsSync(slicesDir)) {
47
+ return [];
48
+ }
49
+
50
+ return fs.readdirSync(slicesDir, { withFileTypes: true })
51
+ .filter((entry) => entry.isDirectory())
52
+ .map((entry) => {
53
+ const slicePath = path.join(slicesDir, entry.name, 'slice.json');
54
+ if (!fs.existsSync(slicePath)) {
55
+ return null;
56
+ }
57
+ const json = parseJsonWithComments(fs.readFileSync(slicePath, 'utf8'));
58
+ return {
59
+ id: json.slice_id || entry.name,
60
+ path: slicePath,
61
+ status: String(json.status || 'draft'),
62
+ title: json.title || json.slice_id || entry.name,
63
+ };
64
+ })
65
+ .filter(Boolean)
66
+ .sort((left, right) => left.id.localeCompare(right.id));
67
+ }
68
+
69
+ function resolveSpecIdentity(repoRoot, specDir) {
70
+ const relativeSpecDir = toPosix(path.relative(repoRoot, specDir));
71
+ const specSlug = path.basename(specDir);
72
+ const branchName = `feature/${specSlug}`;
73
+ return {
74
+ branchName,
75
+ relativeSpecDir,
76
+ specSlug,
77
+ worktreePath: path.join(worktreesRootForRepo(repoRoot, branchName), safeBranchName(branchName)),
78
+ };
79
+ }
80
+
81
+ function findExistingWorktree(repoRoot, branchName) {
82
+ for (const entry of worktreeList(repoRoot)) {
83
+ const branchRef = entry.branch || '';
84
+ if (branchRef.replace('refs/heads/', '') === branchName) {
85
+ return entry.worktree;
86
+ }
87
+ }
88
+ return '';
89
+ }
90
+
91
+ function resolveBaseRef(repoRoot, preferred = '') {
92
+ const candidates = [preferred, 'main', 'develop'].filter(Boolean);
93
+ for (const candidate of candidates) {
94
+ if (hasLocalBranch(repoRoot, candidate)) {
95
+ return candidate;
96
+ }
97
+ if (hasRemoteBranch(repoRoot, candidate)) {
98
+ return `origin/${candidate}`;
99
+ }
100
+ if (lsRemoteHeads(repoRoot, candidate)) {
101
+ return `origin/${candidate}`;
102
+ }
103
+ }
104
+ throw new Error(formatError('missing base branch. Expected local or remote main/develop.'));
105
+ }
106
+
107
+ function resolveMergedBaseRef(repoRoot, preferred = '', remote = 'origin') {
108
+ const candidates = [preferred, 'main', 'develop'].filter(Boolean);
109
+ for (const candidate of candidates) {
110
+ if (hasRemoteBranch(repoRoot, candidate, remote)) {
111
+ return {
112
+ baseBranch: candidate,
113
+ baseRef: `${remote}/${candidate}`,
114
+ remote,
115
+ };
116
+ }
117
+ if (hasLocalBranch(repoRoot, candidate)) {
118
+ return {
119
+ baseBranch: candidate,
120
+ baseRef: candidate,
121
+ remote: '',
122
+ };
123
+ }
124
+ }
125
+ throw new Error(formatError('missing merge base branch. Expected local or remote main/develop.'));
126
+ }
127
+
128
+ function buildSpecStatus(repoRoot, specInput) {
129
+ const specDir = findSpecDir(repoRoot, specInput);
130
+ const identity = resolveSpecIdentity(repoRoot, specDir);
131
+ const slices = listSpecSlices(specDir);
132
+ const slice00 = slices.find((slice) => slice.id.startsWith('slice-00')) || null;
133
+ const pendingSlices = slices.filter((slice) => slice.status !== 'completed');
134
+ const laterSlicesBlocked = !slice00 || slice00.status !== 'completed';
135
+ const existingWorktree = findExistingWorktree(repoRoot, identity.branchName);
136
+ const worktreeDirty = existingWorktree ? !isCleanWorktree(existingWorktree) : false;
137
+
138
+ return {
139
+ ...identity,
140
+ existingWorktree,
141
+ laterSlicesBlocked,
142
+ pendingSlices,
143
+ slice00,
144
+ slices,
145
+ specDir,
146
+ worktreeDirty,
147
+ };
148
+ }
149
+
150
+ function formatSpecStatus(status) {
151
+ const lines = [
152
+ 'Spec worktree status',
153
+ `Spec: ${status.relativeSpecDir}`,
154
+ `Branch: ${status.branchName}`,
155
+ `Worktree: ${status.existingWorktree || status.worktreePath}`,
156
+ `Worktree dirty: ${status.worktreeDirty ? 'yes' : 'no'}`,
157
+ `slice-00: ${status.slice00 ? status.slice00.status : 'missing'}`,
158
+ `Later slices blocked: ${status.laterSlicesBlocked ? 'yes' : 'no'}`,
159
+ 'Pending slices:',
160
+ ];
161
+
162
+ if (status.pendingSlices.length === 0) {
163
+ lines.push('- none');
164
+ } else {
165
+ for (const slice of status.pendingSlices) {
166
+ lines.push(`- ${slice.id}: ${slice.status}`);
167
+ }
168
+ }
169
+
170
+ return `${lines.join('\n')}\n`;
171
+ }
172
+
173
+ function startSpecWorktree(repoRoot, specInput, options = {}) {
174
+ const specDir = findSpecDir(repoRoot, specInput);
175
+ const identity = resolveSpecIdentity(repoRoot, specDir);
176
+ const existingWorktree = findExistingWorktree(repoRoot, identity.branchName);
177
+ const slices = listSpecSlices(specDir);
178
+ const slice00 = slices.find((slice) => slice.id.startsWith('slice-00')) || null;
179
+ const baseRef = resolveBaseRef(repoRoot, options.baseBranch);
180
+
181
+ if (existingWorktree) {
182
+ if (!isCleanWorktree(existingWorktree)) {
183
+ throw new Error(formatError(`existing spec worktree is dirty: ${existingWorktree}`));
184
+ }
185
+ return {
186
+ ...identity,
187
+ baseRef,
188
+ reused: true,
189
+ slice00,
190
+ worktreePath: existingWorktree,
191
+ };
192
+ }
193
+
194
+ if (fs.existsSync(identity.worktreePath)) {
195
+ throw new Error(formatError(`worktree path already exists and is not registered for ${identity.branchName}: ${identity.worktreePath}`));
196
+ }
197
+
198
+ if (!isCleanWorktree(repoRoot)) {
199
+ throw new Error(formatError('current checkout is not clean. Commit or stash before starting a spec worktree.'));
200
+ }
201
+
202
+ worktreePrune(repoRoot);
203
+ fs.mkdirSync(path.dirname(identity.worktreePath), { recursive: true });
204
+
205
+ if (hasLocalBranch(repoRoot, identity.branchName)) {
206
+ worktreeAdd(repoRoot, identity.worktreePath, identity.branchName);
207
+ } else {
208
+ worktreeAdd(repoRoot, identity.worktreePath, baseRef, { branch: identity.branchName });
209
+ }
210
+
211
+ return {
212
+ ...identity,
213
+ baseRef,
214
+ currentBranch: currentBranch(repoRoot),
215
+ reused: false,
216
+ slice00,
217
+ };
218
+ }
219
+
220
+ function formatSpecStartResult(result) {
221
+ return `${[
222
+ 'Spec worktree ready',
223
+ `Branch: ${result.branchName}`,
224
+ `Base: ${result.baseRef}`,
225
+ `Worktree: ${result.worktreePath}`,
226
+ `Reused: ${result.reused ? 'yes' : 'no'}`,
227
+ `slice-00: ${result.slice00 ? result.slice00.status : 'missing'}`,
228
+ ].join('\n')}\n`;
229
+ }
230
+
231
+ function closeSpecWorktree(repoRoot, specInput, options = {}) {
232
+ const specDir = findSpecDir(repoRoot, specInput);
233
+ const identity = resolveSpecIdentity(repoRoot, specDir);
234
+ const existingWorktree = findExistingWorktree(repoRoot, identity.branchName);
235
+ const discard = options.discard === true;
236
+ const dryRun = options.dryRun === true;
237
+ const force = options.force === true;
238
+ const remote = options.remote || 'origin';
239
+ const base = resolveMergedBaseRef(repoRoot, options.baseBranch, remote);
240
+
241
+ if (!existingWorktree) {
242
+ throw new Error(formatError(`missing spec worktree for branch ${identity.branchName}.`));
243
+ }
244
+
245
+ if (!discard && !isCleanWorktree(existingWorktree)) {
246
+ throw new Error(formatError(`spec worktree is dirty: ${existingWorktree}. Commit or stash before closing, or pass --discard intentionally.`));
247
+ }
248
+
249
+ if (!discard) {
250
+ const branch = currentBranch(repoRoot);
251
+ if (branch !== base.baseBranch) {
252
+ throw new Error(formatError(`spec close must run from ${base.baseBranch}. Current branch: ${branch || '(detached)'}.`));
253
+ }
254
+ if (statusPorcelain(repoRoot) !== '') {
255
+ throw new Error(formatError('main checkout is dirty. Commit or stash before closing the spec worktree.'));
256
+ }
257
+ if (base.remote) {
258
+ try {
259
+ fetchRemote(repoRoot, base.remote, [base.baseBranch]);
260
+ } catch {
261
+ // Local-only test repos and offline environments can still validate against the current remote ref.
262
+ }
263
+ }
264
+ if (hasLocalBranch(repoRoot, identity.branchName) && !mergeBaseIsAncestor(repoRoot, identity.branchName, base.baseRef)) {
265
+ throw new Error(formatError(`spec branch ${identity.branchName} is not merged into ${base.baseRef}. Merge the PR before cleanup, or pass --discard intentionally.`));
266
+ }
267
+ }
268
+
269
+ if (dryRun) {
270
+ return {
271
+ ...identity,
272
+ baseBranch: base.baseBranch,
273
+ baseRef: base.baseRef,
274
+ discarded: discard,
275
+ dryRun: true,
276
+ pulled: false,
277
+ remote: base.remote,
278
+ removed: false,
279
+ worktreePath: existingWorktree,
280
+ };
281
+ }
282
+
283
+ worktreeRemove(repoRoot, existingWorktree, force || discard);
284
+ if (!discard && base.remote) {
285
+ runGit(['pull', '--ff-only', remote, base.baseBranch], repoRoot);
286
+ }
287
+
288
+ return {
289
+ ...identity,
290
+ baseBranch: base.baseBranch,
291
+ baseRef: base.baseRef,
292
+ discarded: discard,
293
+ dryRun: false,
294
+ pulled: !discard && Boolean(base.remote),
295
+ remote: base.remote,
296
+ removed: true,
297
+ worktreePath: existingWorktree,
298
+ };
299
+ }
300
+
301
+ function formatSpecCloseResult(result) {
302
+ const lines = [
303
+ result.dryRun ? 'Spec close dry-run' : 'Spec worktree closed',
304
+ `Spec: ${result.relativeSpecDir}`,
305
+ `Branch: ${result.branchName}`,
306
+ `Base: ${result.baseRef}`,
307
+ `Worktree: ${result.worktreePath}`,
308
+ `Discard: ${result.discarded ? 'yes' : 'no'}`,
309
+ ];
310
+
311
+ if (result.dryRun) {
312
+ lines.push(`Would remove worktree: ${result.worktreePath}`);
313
+ if (!result.discarded && result.remote) {
314
+ lines.push(`Would pull: git pull --ff-only ${result.remote} ${result.baseBranch}`);
315
+ }
316
+ } else {
317
+ lines.push(`Removed worktree: ${result.removed ? 'yes' : 'no'}`);
318
+ lines.push(`Pulled main checkout: ${result.pulled ? 'yes' : 'no'}`);
319
+ }
320
+
321
+ return `${lines.join('\n')}\n`;
322
+ }
323
+
324
+ function ensureSpecSliceZeroComplete(repoRoot, specInput) {
325
+ const status = buildSpecStatus(repoRoot, specInput);
326
+ if (!status.slice00) {
327
+ throw new Error(formatError(`spec ${status.relativeSpecDir} requires slice-00 completed before starting later slices; slice-00 is missing.`));
328
+ }
329
+ if (status.slice00.status !== 'completed') {
330
+ throw new Error(formatError(`spec ${status.relativeSpecDir} requires slice-00 completed before starting later slices; current status: ${status.slice00.status}.`));
331
+ }
332
+ return status.slice00;
333
+ }
334
+
335
+ module.exports = {
336
+ buildSpecStatus,
337
+ closeSpecWorktree,
338
+ ensureSpecSliceZeroComplete,
339
+ findSpecDir,
340
+ findExistingWorktree,
341
+ formatSpecCloseResult,
342
+ formatSpecStartResult,
343
+ formatSpecStatus,
344
+ listSpecSlices,
345
+ resolveBaseRef,
346
+ resolveMergedBaseRef,
347
+ resolveSpecIdentity,
348
+ startSpecWorktree,
349
+ };