create-quiver 0.4.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 (71) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +15 -0
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +15 -0
  3. package/.github/pull_request_template.md +4 -0
  4. package/.github/workflows/ci.yml +74 -0
  5. package/CHANGELOG.md +24 -0
  6. package/CODE_OF_CONDUCT.md +12 -0
  7. package/CONTRIBUTING.md +15 -0
  8. package/LICENSE +21 -0
  9. package/README.md +118 -0
  10. package/README_FOR_AI.md +90 -0
  11. package/ROADMAP.md +20 -0
  12. package/SECURITY.md +12 -0
  13. package/TEMPLATE.md +108 -0
  14. package/bin/create-quiver.js +8 -0
  15. package/docs/CONTEXTO.md.template +45 -0
  16. package/docs/DOCUMENTATION_GUIDE.md.template +34 -0
  17. package/docs/GITFLOW_PR_GUIDE.md.template +52 -0
  18. package/docs/INDEX.md.template +41 -0
  19. package/docs/MOCK_DATA_GUIDE.md.template +31 -0
  20. package/docs/MULTI_AGENT_WORKFLOW.md.template +31 -0
  21. package/docs/STATUS.md.template +26 -0
  22. package/docs/SUPPORT_MATRIX.md.template +31 -0
  23. package/docs/TESTING_GUIDE_FOR_AI.md.template +42 -0
  24. package/docs/TROUBLESHOOTING.md.template +70 -0
  25. package/docs/UI_STANDARDS.md.template +31 -0
  26. package/docs/WORKFLOW.md.template +56 -0
  27. package/docs/ai/LESSONS.md.template +24 -0
  28. package/docs/ai/PRINCIPLES.md +25 -0
  29. package/docs/ai/RULES.yaml +33 -0
  30. package/i18n/es/README.md +15 -0
  31. package/i18n/es/README_FOR_AI.md +6 -0
  32. package/i18n/es/TEMPLATE.md +18 -0
  33. package/i18n/es/docs/CONTEXTO.md.template +9 -0
  34. package/i18n/es/docs/DOCUMENTATION_GUIDE.md.template +4 -0
  35. package/i18n/es/docs/GITFLOW_PR_GUIDE.md.template +4 -0
  36. package/i18n/es/docs/INDEX.md.template +10 -0
  37. package/i18n/es/docs/MOCK_DATA_GUIDE.md.template +4 -0
  38. package/i18n/es/docs/MULTI_AGENT_WORKFLOW.md.template +4 -0
  39. package/i18n/es/docs/STATUS.md.template +9 -0
  40. package/i18n/es/docs/TESTING_GUIDE_FOR_AI.md.template +7 -0
  41. package/i18n/es/docs/UI_STANDARDS.md.template +4 -0
  42. package/i18n/es/docs/WORKFLOW.md.template +6 -0
  43. package/i18n/es/docs/ai/LESSONS.md.template +3 -0
  44. package/i18n/es/docs/ai/PRINCIPLES.md +7 -0
  45. package/i18n/es/docs/ai/RULES.yaml +7 -0
  46. package/package.json +19 -0
  47. package/package.template.json +10 -0
  48. package/scripts/check-pr-readiness.sh +138 -0
  49. package/scripts/check-scope.sh +150 -0
  50. package/scripts/check-slice-readiness.sh +319 -0
  51. package/scripts/cleanup-slice.sh +177 -0
  52. package/scripts/init-docs.sh +368 -0
  53. package/scripts/migrate-project.sh +218 -0
  54. package/scripts/package-quiver.sh +124 -0
  55. package/scripts/refresh-active-slices.sh +232 -0
  56. package/scripts/release-quiver.sh +77 -0
  57. package/scripts/start-slice.sh +429 -0
  58. package/specs/[project-name]/EVIDENCE_REPORT.md.template +15 -0
  59. package/specs/[project-name]/SPEC.md.template +39 -0
  60. package/specs/[project-name]/STATUS.md.template +22 -0
  61. package/specs/[project-name]/slices/pr.md.template +97 -0
  62. package/specs/[project-name]/slices/slice-template/slice.json +69 -0
  63. package/specs/quiver-v05-readme-adoption-contract/EVIDENCE_REPORT.md +21 -0
  64. package/specs/quiver-v05-readme-adoption-contract/SPEC.md +40 -0
  65. package/specs/quiver-v05-readme-adoption-contract/STATUS.md +24 -0
  66. package/specs/quiver-v05-readme-adoption-contract/slices/slice-01-readme-adoption-contract/slice.json +68 -0
  67. package/specs/quiver-v06-release-readiness/EVIDENCE_REPORT.md +23 -0
  68. package/specs/quiver-v06-release-readiness/SPEC.md +40 -0
  69. package/specs/quiver-v06-release-readiness/STATUS.md +24 -0
  70. package/specs/quiver-v06-release-readiness/slices/slice-01-first-npm-release-readiness/slice.json +71 -0
  71. package/src/create-quiver/index.js +329 -0
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -euo pipefail
4
+
5
+ usage() {
6
+ cat <<'EOF'
7
+ Uso:
8
+ bash tools/scripts/check-pr-readiness.sh <ruta-al-slice.json>
9
+
10
+ Valida que un slice este listo para abrir PR:
11
+ - gate de validacion del slice
12
+ - pr.md con secciones obligatorias
13
+ - detailed testing steps
14
+ - rollback explicito
15
+ - branch del slice limpio y con diferencias contra develop
16
+ EOF
17
+ }
18
+
19
+ fail() {
20
+ echo "FAIL: $1" >&2
21
+ exit 1
22
+ }
23
+
24
+ pass() {
25
+ echo "PASS: $1"
26
+ }
27
+
28
+ warn() {
29
+ echo "WARN: $1"
30
+ }
31
+
32
+ [[ $# -eq 1 ]] || {
33
+ usage
34
+ exit 1
35
+ }
36
+
37
+ slice_input="$1"
38
+
39
+ command -v git >/dev/null 2>&1 || fail "git no esta disponible en PATH."
40
+ command -v node >/dev/null 2>&1 || fail "node no esta disponible en PATH."
41
+
42
+ repo_root="$(git rev-parse --show-toplevel)"
43
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
44
+
45
+ [[ -f "$slice_input" ]] || fail "No existe el slice '$slice_input'."
46
+
47
+ slice_abs="$(cd "$(dirname "$slice_input")" && pwd)/$(basename "$slice_input")"
48
+
49
+ "$script_dir/check-slice-readiness.sh" "$slice_abs" --gate validation
50
+ "$script_dir/check-scope.sh" "$slice_abs"
51
+
52
+ slice_meta=()
53
+ while IFS= read -r line; do
54
+ slice_meta+=("$line")
55
+ done < <(node - "$slice_abs" <<'NODE'
56
+ const fs = require('fs');
57
+ const path = require('path');
58
+
59
+ const slicePath = process.argv[2];
60
+ const json = JSON.parse(fs.readFileSync(slicePath, 'utf8'));
61
+
62
+ const branchName = String(json.git?.branch_name || '').trim();
63
+ const sliceId = String(json.slice_id || '').trim();
64
+ const prPath = path.join(path.dirname(slicePath), 'pr.md');
65
+
66
+ console.log(branchName);
67
+ console.log(sliceId);
68
+ console.log(prPath);
69
+ NODE
70
+ )
71
+
72
+ [[ ${#slice_meta[@]} -eq 3 ]] || fail "No se pudo leer la metadata del PR del slice."
73
+
74
+ branch_name="${slice_meta[0]}"
75
+ slice_id="${slice_meta[1]}"
76
+ pr_abs="${slice_meta[2]}"
77
+
78
+ [[ -n "$branch_name" ]] || fail "Falta git.branch_name en el slice."
79
+ [[ -f "$pr_abs" ]] || fail "Falta pr.md junto al slice."
80
+
81
+ current_branch="$(git branch --show-current)"
82
+ [[ "$current_branch" == "$branch_name" ]] || fail "Debes ejecutar este check desde la rama del slice. Actual: $current_branch Esperada: $branch_name"
83
+ pass "La rama actual coincide con la rama declarada por el slice."
84
+
85
+ if [[ -n "$(git status --porcelain)" ]]; then
86
+ fail "El worktree no esta limpio. Cerra la implementacion antes de abrir el PR."
87
+ fi
88
+ pass "El worktree esta limpio."
89
+
90
+ ahead_count="$(git rev-list --count origin/develop..HEAD)"
91
+ if [[ "$ahead_count" -le 0 ]]; then
92
+ if git merge-base --is-ancestor HEAD origin/develop; then
93
+ fail "La rama ya fue absorbida por origin/develop. Este gate aplica antes del merge."
94
+ fi
95
+ fail "La rama no tiene commits propios respecto de origin/develop."
96
+ fi
97
+ pass "La rama tiene commits propios contra origin/develop."
98
+
99
+ for heading in \
100
+ "## Title" \
101
+ "## Summary" \
102
+ "## Scope" \
103
+ "## Files" \
104
+ "## How to Test (DETAILED - REQUIRED)" \
105
+ "## Evidence" \
106
+ "## Rollback" \
107
+ "## Risks / Notes"
108
+ do
109
+ grep -Fxq "$heading" "$pr_abs" || fail "Falta la seccion obligatoria '$heading' en pr.md."
110
+ done
111
+ pass "pr.md contiene las secciones obligatorias."
112
+
113
+ for subheading in \
114
+ "### Required Environment" \
115
+ "### Worktree Access" \
116
+ "### Run the Project" \
117
+ "### Use Cases" \
118
+ "### Technical Verification"
119
+ do
120
+ grep -Fxq "$subheading" "$pr_abs" || fail "Falta la subseccion '$subheading' dentro de How to Test."
121
+ done
122
+ pass "How to Test incluye entorno, acceso al worktree, arranque, casos de uso y verificación técnica."
123
+
124
+ grep -Eq '#### Case [0-9]+:' "$pr_abs" || fail "How to Test debe tener al menos un caso de uso documentado (#### Case 1: ...)."
125
+ pass "Al menos un caso de uso documentado."
126
+
127
+ grep -Eq 'git revert ' "$pr_abs" || fail "Rollback debe incluir al menos un comando git revert."
128
+ pass "Rollback incluye comando git revert."
129
+
130
+ grep -Eiq '^\s*-\s*`manual review`$|^\s*-\s*`visual check`$|^\s*-\s*`screen test`$|^\s*-\s*`visual validation`$' "$pr_abs" && fail "How to Test cannot rely only on generic phrases."
131
+
132
+ node -e "JSON.parse(require('fs').readFileSync(process.argv[1], 'utf8'))" "$slice_abs" >/dev/null
133
+ pass "slice.json parsea correctamente."
134
+
135
+ git diff --check >/dev/null
136
+ pass "git diff --check no reporta problemas."
137
+
138
+ echo "PASS: Gate PR listo para '$slice_id'."
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -euo pipefail
4
+
5
+ usage() {
6
+ cat <<'EOF'
7
+ Uso:
8
+ bash tools/scripts/check-scope.sh <ruta-al-slice.json> [--strict]
9
+
10
+ Compara los archivos tocados en la rama del slice contra los declarados
11
+ en slice.json.files. Detecta scope creep: archivos modificados fuera del
12
+ alcance declarado.
13
+
14
+ Opciones:
15
+ --strict Convierte cualquier archivo fuera de scope en error (default: warning)
16
+ EOF
17
+ }
18
+
19
+ fail() {
20
+ echo "FAIL: $1" >&2
21
+ exit 1
22
+ }
23
+
24
+ pass() {
25
+ echo "PASS: $1"
26
+ }
27
+
28
+ warn() {
29
+ echo "WARN: $1"
30
+ }
31
+
32
+ strict="false"
33
+ slice_input=""
34
+
35
+ while [[ $# -gt 0 ]]; do
36
+ case "$1" in
37
+ -h|--help)
38
+ usage
39
+ exit 0
40
+ ;;
41
+ --strict)
42
+ strict="true"
43
+ ;;
44
+ -*)
45
+ fail "Opcion desconocida: $1"
46
+ ;;
47
+ *)
48
+ if [[ -n "$slice_input" ]]; then
49
+ fail "Solo se acepta un slice por ejecucion."
50
+ fi
51
+ slice_input="$1"
52
+ ;;
53
+ esac
54
+ shift
55
+ done
56
+
57
+ [[ -n "$slice_input" ]] || fail "Debes indicar la ruta del slice."
58
+
59
+ command -v git >/dev/null 2>&1 || fail "git no esta disponible en PATH."
60
+ command -v node >/dev/null 2>&1 || fail "node no esta disponible en PATH."
61
+
62
+ repo_root="$(git rev-parse --show-toplevel)"
63
+ [[ -f "$slice_input" ]] || fail "No existe el slice '$slice_input'."
64
+ slice_abs="$(cd "$(dirname "$slice_input")" && pwd)/$(basename "$slice_input")"
65
+
66
+ # Leer archivos declarados en slice.json
67
+ declared_b64=$(node - "$slice_abs" <<'NODE'
68
+ const fs = require('fs');
69
+ let json;
70
+ try {
71
+ json = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
72
+ } catch (e) {
73
+ process.stderr.write('Error: No se pudo parsear slice.json: ' + e.message + '\n');
74
+ process.exit(1);
75
+ }
76
+ const files = Array.isArray(json.files) ? json.files : [];
77
+ process.stdout.write(Buffer.from(JSON.stringify(files)).toString('base64'));
78
+ NODE
79
+ )
80
+
81
+ # Obtener archivos tocados en la rama respecto de origin/develop
82
+ touched_raw=""
83
+ if git rev-parse --verify "origin/develop" >/dev/null 2>&1; then
84
+ touched_raw=$(git diff --name-only "origin/develop...HEAD" 2>/dev/null || echo "")
85
+ elif git rev-parse --verify "develop" >/dev/null 2>&1; then
86
+ touched_raw=$(git diff --name-only "develop...HEAD" 2>/dev/null || echo "")
87
+ else
88
+ warn "No se encontro rama origin/develop ni develop. Saltando check de scope."
89
+ exit 0
90
+ fi
91
+
92
+ if [[ -z "$touched_raw" ]]; then
93
+ warn "No se encontraron archivos modificados respecto de develop."
94
+ exit 0
95
+ fi
96
+
97
+ touched_b64=$(echo "$touched_raw" | base64)
98
+
99
+ # Comparar con node
100
+ scope_result=$(node - "$declared_b64" "$touched_b64" <<'NODE'
101
+ const declared = JSON.parse(Buffer.from(process.argv[2], 'base64').toString('utf8'));
102
+ const touched = Buffer.from(process.argv[3], 'base64').toString('utf8')
103
+ .trim().split('\n').filter(Boolean);
104
+
105
+ // Archivos generados automaticamente por el workflow — siempre permitidos
106
+ const autoAllowed = [
107
+ /^specs\//,
108
+ /^docs\//,
109
+ /^\.worktrees\//,
110
+ /WORKTREE_CONTEXT\.md$/,
111
+ /EVIDENCE_REPORT\.md$/,
112
+ /STATUS\.md$/,
113
+ /SPEC\.md$/,
114
+ /\/pr\.md$/,
115
+ /\/slice\.json$/,
116
+ ];
117
+
118
+ const outOfScope = touched.filter(file => {
119
+ if (declared.includes(file)) return false;
120
+ if (autoAllowed.some(re => re.test(file))) return false;
121
+ return true;
122
+ });
123
+
124
+ process.stdout.write(outOfScope.join('\n'));
125
+ NODE
126
+ )
127
+
128
+ if [[ -z "$scope_result" ]]; then
129
+ pass "Todos los archivos tocados estan dentro del scope declarado en slice.json."
130
+ exit 0
131
+ fi
132
+
133
+ violation_count=0
134
+ while IFS= read -r file; do
135
+ [[ -n "$file" ]] || continue
136
+ violation_count=$((violation_count + 1))
137
+ if [[ "$strict" == "true" ]]; then
138
+ echo "FAIL: Archivo fuera de scope: $file" >&2
139
+ else
140
+ warn "Archivo fuera de scope: $file"
141
+ fi
142
+ done <<< "$scope_result"
143
+
144
+ if [[ "$violation_count" -gt 0 ]]; then
145
+ if [[ "$strict" == "true" ]]; then
146
+ fail "$violation_count archivo(s) fuera del scope declarado. Actualiza slice.json.files o revierte los cambios fuera de alcance."
147
+ else
148
+ warn "$violation_count archivo(s) fuera del scope declarado. Considera actualizar slice.json.files o revertir los cambios no previstos."
149
+ fi
150
+ fi
@@ -0,0 +1,319 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -euo pipefail
4
+
5
+ usage() {
6
+ cat <<'EOF'
7
+ Uso:
8
+ bash tools/scripts/check-slice-readiness.sh <ruta-al-slice.json> [--gate execution|validation] [--strict-overlap]
9
+
10
+ Valida que un slice tenga las precondiciones mínimas para ejecutarse o pasar a validación.
11
+
12
+ Opciones:
13
+ --gate <mode> Gate a validar. Default: execution
14
+ ready -> requiere status=ready (gate entre Track 1 y Track 2)
15
+ execution -> metadata, spec base mergeado y overlap
16
+ validation -> execution + estado completado y timestamps
17
+ --strict-overlap Convierte overlap con worktrees activos en error
18
+ EOF
19
+ }
20
+
21
+ fail() {
22
+ echo "FAIL: $1" >&2
23
+ exit 1
24
+ }
25
+
26
+ pass() {
27
+ echo "PASS: $1"
28
+ }
29
+
30
+ warn() {
31
+ echo "WARN: $1"
32
+ }
33
+
34
+ gate="execution"
35
+ strict_overlap="false"
36
+ slice_input=""
37
+
38
+ while [[ $# -gt 0 ]]; do
39
+ case "$1" in
40
+ -h|--help)
41
+ usage
42
+ exit 0
43
+ ;;
44
+ --gate)
45
+ shift
46
+ [[ $# -gt 0 ]] || fail "Falta valor para --gate."
47
+ gate="$1"
48
+ ;;
49
+ --strict-overlap)
50
+ strict_overlap="true"
51
+ ;;
52
+ -*)
53
+ fail "Opcion desconocida: $1"
54
+ ;;
55
+ *)
56
+ if [[ -n "$slice_input" ]]; then
57
+ fail "Solo se acepta un slice por ejecucion."
58
+ fi
59
+ slice_input="$1"
60
+ ;;
61
+ esac
62
+ shift
63
+ done
64
+
65
+ [[ -n "$slice_input" ]] || fail "Debes indicar la ruta del slice."
66
+
67
+ case "$gate" in
68
+ ready|execution|validation) ;;
69
+ *) fail "Gate invalido: $gate. Usa ready, execution o validation." ;;
70
+ esac
71
+
72
+ command -v git >/dev/null 2>&1 || fail "git no esta disponible en PATH."
73
+ command -v node >/dev/null 2>&1 || fail "node no esta disponible en PATH."
74
+
75
+ repo_root="$(git rev-parse --show-toplevel)"
76
+
77
+ [[ -f "$slice_input" ]] || fail "No existe el slice '$slice_input'."
78
+
79
+ slice_abs="$(cd "$(dirname "$slice_input")" && pwd)/$(basename "$slice_input")"
80
+ slice_rel="${slice_abs#$repo_root/}"
81
+
82
+ slice_meta=()
83
+ while IFS= read -r line; do
84
+ slice_meta+=("$line")
85
+ done < <(node - "$slice_abs" "$slice_rel" <<'NODE'
86
+ const fs = require('fs');
87
+ const path = require('path');
88
+
89
+ const [slicePath, sliceRel] = process.argv.slice(2);
90
+
91
+ function fail(message) {
92
+ console.error(`Error: ${message}`);
93
+ process.exit(1);
94
+ }
95
+
96
+ let json;
97
+ try {
98
+ json = JSON.parse(fs.readFileSync(slicePath, 'utf8'));
99
+ } catch (error) {
100
+ fail(`No se pudo parsear '${slicePath}' como JSON: ${error.message}`);
101
+ }
102
+
103
+ const ticket = typeof json.ticket === 'string' ? json.ticket.trim() : '';
104
+ const sliceId = typeof json.slice_id === 'string' ? json.slice_id.trim() : '';
105
+ const branchName = typeof json.git?.branch_name === 'string' ? json.git.branch_name.trim() : '';
106
+ const files = Array.isArray(json.files) ? json.files : [];
107
+ const acceptance = Array.isArray(json.acceptance) ? json.acceptance : [];
108
+ const specDir = path.dirname(path.dirname(path.dirname(sliceRel)));
109
+
110
+ if (!sliceId) {
111
+ fail('Falta "slice_id" en el slice.');
112
+ }
113
+
114
+ if (!ticket) {
115
+ fail('Falta "ticket" en el slice.');
116
+ }
117
+
118
+ if (!branchName) {
119
+ fail('Falta "git.branch_name" en el slice.');
120
+ }
121
+
122
+ if (files.length === 0) {
123
+ fail('El slice debe declarar al menos un archivo en "files".');
124
+ }
125
+
126
+ if (acceptance.length === 0) {
127
+ fail('El slice debe declarar criterios en "acceptance".');
128
+ }
129
+
130
+ console.log(sliceId);
131
+ console.log(ticket);
132
+ console.log(branchName);
133
+ console.log(json.status || 'pending');
134
+ console.log(sliceId.startsWith('slice-00') ? 'true' : 'false');
135
+ console.log(specDir);
136
+ console.log(Buffer.from(JSON.stringify(files)).toString('base64'));
137
+ console.log(String(json.actual_hours ?? ''));
138
+ console.log(json.started_at ?? '');
139
+ console.log(json.completed_at ?? '');
140
+ NODE
141
+ )
142
+
143
+ [[ ${#slice_meta[@]} -eq 10 ]] || fail "No se pudo leer la metadata del slice."
144
+
145
+ slice_id="${slice_meta[0]}"
146
+ ticket="${slice_meta[1]}"
147
+ branch_name="${slice_meta[2]}"
148
+ slice_status="${slice_meta[3]}"
149
+ is_baseline="${slice_meta[4]}"
150
+ spec_dir_rel="${slice_meta[5]}"
151
+ files_b64="${slice_meta[6]}"
152
+ actual_hours="${slice_meta[7]}"
153
+ started_at="${slice_meta[8]}"
154
+ completed_at="${slice_meta[9]}"
155
+
156
+ for spec_file in SPEC.md STATUS.md EVIDENCE_REPORT.md; do
157
+ [[ -f "$repo_root/$spec_dir_rel/$spec_file" ]] || fail "Falta '$spec_dir_rel/$spec_file'."
158
+ done
159
+ pass "El spec local tiene SPEC.md, STATUS.md y EVIDENCE_REPORT.md."
160
+
161
+ if git cat-file -e "origin/develop:$slice_rel" 2>/dev/null; then
162
+ pass "El slice ya existe en origin/develop (PR base documental mergeado)."
163
+ else
164
+ if [[ "$gate" == "validation" ]]; then
165
+ 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."
166
+ else
167
+ fail "El slice no existe en origin/develop. Mergea primero el PR base documental."
168
+ fi
169
+ fi
170
+
171
+ overlap_output="$(node - "$repo_root" "$branch_name" "$files_b64" <<'NODE'
172
+ const fs = require('fs');
173
+ const path = require('path');
174
+ const cp = require('child_process');
175
+
176
+ const [repoRoot, currentBranch, currentFilesB64] = process.argv.slice(2);
177
+ const currentFiles = JSON.parse(Buffer.from(currentFilesB64, 'base64').toString('utf8'));
178
+
179
+ function run(cmd, cwd = repoRoot) {
180
+ try {
181
+ return cp.execSync(cmd, {
182
+ cwd,
183
+ encoding: 'utf8',
184
+ stdio: ['ignore', 'pipe', 'pipe']
185
+ }).trim();
186
+ } catch {
187
+ return '';
188
+ }
189
+ }
190
+
191
+ function walkSlices(rootDir, acc) {
192
+ if (!fs.existsSync(rootDir)) {
193
+ return;
194
+ }
195
+
196
+ for (const entry of fs.readdirSync(rootDir, { withFileTypes: true })) {
197
+ const fullPath = path.join(rootDir, entry.name);
198
+ if (entry.isDirectory()) {
199
+ walkSlices(fullPath, acc);
200
+ continue;
201
+ }
202
+
203
+ if (entry.isFile() && entry.name === 'slice.json' && fullPath.includes(`${path.sep}slices${path.sep}`)) {
204
+ const json = JSON.parse(fs.readFileSync(fullPath, 'utf8'));
205
+ const branchName = json.git?.branch_name;
206
+ if (!branchName) {
207
+ continue;
208
+ }
209
+
210
+ acc.set(branchName, {
211
+ sliceId: json.slice_id || '',
212
+ files: Array.isArray(json.files) ? json.files : []
213
+ });
214
+ }
215
+ }
216
+ }
217
+
218
+ function parseWorktrees(text) {
219
+ const entries = [];
220
+ const chunks = text.trim().split('\n\n').filter(Boolean);
221
+
222
+ for (const chunk of chunks) {
223
+ const entry = {};
224
+ for (const line of chunk.split('\n')) {
225
+ const idx = line.indexOf(' ');
226
+ if (idx === -1) {
227
+ continue;
228
+ }
229
+ entry[line.slice(0, idx)] = line.slice(idx + 1);
230
+ }
231
+ entries.push(entry);
232
+ }
233
+
234
+ return entries;
235
+ }
236
+
237
+ const sliceMap = new Map();
238
+ walkSlices(path.join(repoRoot, 'specs'), sliceMap);
239
+ walkSlices(path.join(repoRoot, 'specs-fix'), sliceMap);
240
+
241
+ const worktrees = parseWorktrees(run('git worktree list --porcelain'));
242
+ const warnings = [];
243
+
244
+ for (const entry of worktrees) {
245
+ const worktreePath = entry.worktree;
246
+ const branchRef = entry.branch || '';
247
+ const branchName = branchRef.replace('refs/heads/', '');
248
+
249
+ if (!branchName || branchName === currentBranch || worktreePath === repoRoot) {
250
+ continue;
251
+ }
252
+
253
+ const meta = sliceMap.get(branchName);
254
+ if (!meta || meta.sliceId.startsWith('slice-00')) {
255
+ continue;
256
+ }
257
+
258
+ const dirty = run('git status --porcelain', worktreePath) !== '';
259
+ const aheadCount = Number(run('git rev-list --count origin/develop..HEAD', worktreePath) || '0');
260
+ const active = dirty || aheadCount > 0;
261
+
262
+ if (!active) {
263
+ continue;
264
+ }
265
+
266
+ const overlap = currentFiles.filter((item) => meta.files.includes(item));
267
+ if (overlap.length > 0) {
268
+ warnings.push(`${branchName}|${overlap.join(', ')}`);
269
+ }
270
+ }
271
+
272
+ process.stdout.write(warnings.join('\n'));
273
+ NODE
274
+ )"
275
+
276
+ if [[ -n "$overlap_output" ]]; then
277
+ while IFS= read -r overlap_line; do
278
+ [[ -n "$overlap_line" ]] || continue
279
+ overlap_branch="${overlap_line%%|*}"
280
+ overlap_files="${overlap_line#*|}"
281
+ if [[ "$strict_overlap" == "true" ]]; then
282
+ fail "Overlap con worktree activo '$overlap_branch': $overlap_files"
283
+ fi
284
+ warn "Overlap con worktree activo '$overlap_branch': $overlap_files"
285
+ done <<< "$overlap_output"
286
+ else
287
+ pass "No se detecto overlap con worktrees activos."
288
+ fi
289
+
290
+ case "$gate" in
291
+ ready)
292
+ [[ "$slice_status" == "ready" ]] || fail "Gate ready: slice.json debe estar en status=ready. Estado actual: $slice_status. Completa la especificacion en el Track 1 antes de pasar a ejecucion."
293
+ pass "Gate ready: el slice esta marcado como ready para ejecucion."
294
+ ;;
295
+ execution)
296
+ if [[ "$slice_status" == "blocked" ]]; then
297
+ fail "El slice esta bloqueado (status=blocked). Resolve el bloqueante antes de ejecutar."
298
+ fi
299
+ if [[ "$slice_status" == "cancelled" ]]; then
300
+ fail "El slice esta cancelado (status=cancelled)."
301
+ fi
302
+ if [[ "$slice_status" == "completed" ]]; then
303
+ warn "El slice ya figura como completed. Revisa si realmente corresponde reejecutarlo."
304
+ fi
305
+ if [[ "$slice_status" == "draft" ]]; then
306
+ warn "El slice esta en estado 'draft'. Considera marcarlo como 'ready' antes de ejecutar."
307
+ fi
308
+ pass "Gate execution: metadata y precondiciones minimas OK."
309
+ ;;
310
+ validation)
311
+ [[ "$slice_status" == "completed" ]] || fail "Para gate validation, slice.json debe estar en status=completed."
312
+ [[ -n "$completed_at" ]] || fail "Para gate validation, slice.json debe tener completed_at."
313
+ [[ -n "$started_at" ]] || fail "Para gate validation, slice.json debe tener started_at."
314
+ if [[ -z "$actual_hours" ]] || ! node -e "process.exit(Number(process.argv[1]) > 0 ? 0 : 1)" "$actual_hours" >/dev/null 2>&1; then
315
+ fail "Para gate validation, slice.json debe tener actual_hours > 0."
316
+ fi
317
+ pass "Gate validation: slice marcado como completado y con trazabilidad minima."
318
+ ;;
319
+ esac