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.
- package/.github/ISSUE_TEMPLATE/bug_report.md +15 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +15 -0
- package/.github/pull_request_template.md +4 -0
- package/.github/workflows/ci.yml +74 -0
- package/CHANGELOG.md +24 -0
- package/CODE_OF_CONDUCT.md +12 -0
- package/CONTRIBUTING.md +15 -0
- package/LICENSE +21 -0
- package/README.md +118 -0
- package/README_FOR_AI.md +90 -0
- package/ROADMAP.md +20 -0
- package/SECURITY.md +12 -0
- package/TEMPLATE.md +108 -0
- package/bin/create-quiver.js +8 -0
- package/docs/CONTEXTO.md.template +45 -0
- package/docs/DOCUMENTATION_GUIDE.md.template +34 -0
- package/docs/GITFLOW_PR_GUIDE.md.template +52 -0
- package/docs/INDEX.md.template +41 -0
- package/docs/MOCK_DATA_GUIDE.md.template +31 -0
- package/docs/MULTI_AGENT_WORKFLOW.md.template +31 -0
- package/docs/STATUS.md.template +26 -0
- package/docs/SUPPORT_MATRIX.md.template +31 -0
- package/docs/TESTING_GUIDE_FOR_AI.md.template +42 -0
- package/docs/TROUBLESHOOTING.md.template +70 -0
- package/docs/UI_STANDARDS.md.template +31 -0
- package/docs/WORKFLOW.md.template +56 -0
- package/docs/ai/LESSONS.md.template +24 -0
- package/docs/ai/PRINCIPLES.md +25 -0
- package/docs/ai/RULES.yaml +33 -0
- package/i18n/es/README.md +15 -0
- package/i18n/es/README_FOR_AI.md +6 -0
- package/i18n/es/TEMPLATE.md +18 -0
- package/i18n/es/docs/CONTEXTO.md.template +9 -0
- package/i18n/es/docs/DOCUMENTATION_GUIDE.md.template +4 -0
- package/i18n/es/docs/GITFLOW_PR_GUIDE.md.template +4 -0
- package/i18n/es/docs/INDEX.md.template +10 -0
- package/i18n/es/docs/MOCK_DATA_GUIDE.md.template +4 -0
- package/i18n/es/docs/MULTI_AGENT_WORKFLOW.md.template +4 -0
- package/i18n/es/docs/STATUS.md.template +9 -0
- package/i18n/es/docs/TESTING_GUIDE_FOR_AI.md.template +7 -0
- package/i18n/es/docs/UI_STANDARDS.md.template +4 -0
- package/i18n/es/docs/WORKFLOW.md.template +6 -0
- package/i18n/es/docs/ai/LESSONS.md.template +3 -0
- package/i18n/es/docs/ai/PRINCIPLES.md +7 -0
- package/i18n/es/docs/ai/RULES.yaml +7 -0
- package/package.json +19 -0
- package/package.template.json +10 -0
- package/scripts/check-pr-readiness.sh +138 -0
- package/scripts/check-scope.sh +150 -0
- package/scripts/check-slice-readiness.sh +319 -0
- package/scripts/cleanup-slice.sh +177 -0
- package/scripts/init-docs.sh +368 -0
- package/scripts/migrate-project.sh +218 -0
- package/scripts/package-quiver.sh +124 -0
- package/scripts/refresh-active-slices.sh +232 -0
- package/scripts/release-quiver.sh +77 -0
- package/scripts/start-slice.sh +429 -0
- package/specs/[project-name]/EVIDENCE_REPORT.md.template +15 -0
- package/specs/[project-name]/SPEC.md.template +39 -0
- package/specs/[project-name]/STATUS.md.template +22 -0
- package/specs/[project-name]/slices/pr.md.template +97 -0
- package/specs/[project-name]/slices/slice-template/slice.json +69 -0
- package/specs/quiver-v05-readme-adoption-contract/EVIDENCE_REPORT.md +21 -0
- package/specs/quiver-v05-readme-adoption-contract/SPEC.md +40 -0
- package/specs/quiver-v05-readme-adoption-contract/STATUS.md +24 -0
- package/specs/quiver-v05-readme-adoption-contract/slices/slice-01-readme-adoption-contract/slice.json +68 -0
- package/specs/quiver-v06-release-readiness/EVIDENCE_REPORT.md +23 -0
- package/specs/quiver-v06-release-readiness/SPEC.md +40 -0
- package/specs/quiver-v06-release-readiness/STATUS.md +24 -0
- package/specs/quiver-v06-release-readiness/slices/slice-01-first-npm-release-readiness/slice.json +71 -0
- package/src/create-quiver/index.js +329 -0
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
usage() {
|
|
6
|
+
cat <<'EOF'
|
|
7
|
+
Uso:
|
|
8
|
+
bash tools/scripts/start-slice.sh [--allow-draft] <ruta-al-slice.json>
|
|
9
|
+
|
|
10
|
+
Lee la metadata git del slice, valida la rama declarada y crea un worktree
|
|
11
|
+
afuera de la raiz trackeada del repo.
|
|
12
|
+
|
|
13
|
+
Variables opcionales:
|
|
14
|
+
SLICE_WORKTREES_DIR Directorio base para los worktrees.
|
|
15
|
+
Default: <repo-parent>/.worktrees/<repo-name>
|
|
16
|
+
--allow-draft Permite bootstraps intencionales de slices en draft.
|
|
17
|
+
EOF
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
append_unique_line() {
|
|
21
|
+
local file_path="$1"
|
|
22
|
+
local line="$2"
|
|
23
|
+
|
|
24
|
+
mkdir -p "$(dirname "$file_path")"
|
|
25
|
+
touch "$file_path"
|
|
26
|
+
|
|
27
|
+
if ! grep -Fxq "$line" "$file_path"; then
|
|
28
|
+
printf '%s\n' "$line" >> "$file_path"
|
|
29
|
+
fi
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
ensure_local_exclude() {
|
|
33
|
+
local workdir="$1"
|
|
34
|
+
local pattern="$2"
|
|
35
|
+
local exclude_path
|
|
36
|
+
local git_dir
|
|
37
|
+
|
|
38
|
+
git_dir="$(git -C "$workdir" rev-parse --absolute-git-dir)"
|
|
39
|
+
exclude_path="$git_dir/info/exclude"
|
|
40
|
+
append_unique_line "$exclude_path" "$pattern"
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
canonicalize_dir() {
|
|
44
|
+
local dir_path="$1"
|
|
45
|
+
(cd "$dir_path" && pwd -P)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
resolve_base_ref() {
|
|
49
|
+
local base_branch="$1"
|
|
50
|
+
|
|
51
|
+
if git show-ref --verify --quiet "refs/heads/$base_branch"; then
|
|
52
|
+
printf '%s\n' "$base_branch"
|
|
53
|
+
return 0
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
if git show-ref --verify --quiet "refs/remotes/origin/$base_branch"; then
|
|
57
|
+
printf '%s\n' "origin/$base_branch"
|
|
58
|
+
return 0
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
if git ls-remote --exit-code --heads origin "$base_branch" >/dev/null 2>&1; then
|
|
62
|
+
if git fetch origin "$base_branch:refs/remotes/origin/$base_branch" >/dev/null 2>&1; then
|
|
63
|
+
printf '%s\n' "origin/$base_branch"
|
|
64
|
+
return 0
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
echo "Error: origin existe pero no se pudo actualizar '$base_branch'. Revisa conectividad o crea la rama local '$base_branch' antes de correr start-slice.sh." >&2
|
|
68
|
+
return 1
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
echo "Error: no se encontró '$base_branch' como rama local ni como ref remota 'origin/$base_branch'. Crea la rama base localmente o configura origin antes de correr start-slice.sh." >&2
|
|
72
|
+
return 1
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
slice_alias() {
|
|
76
|
+
node - "$1" <<'NODE'
|
|
77
|
+
const ticket = String(process.argv[2] || '').trim();
|
|
78
|
+
const parts = ticket.split('-').filter(Boolean);
|
|
79
|
+
const prefix = (parts[0] || 'GEN').replace(/[^A-Za-z0-9]/g, '').toUpperCase();
|
|
80
|
+
const suffix = (parts[parts.length - 1] || '00').replace(/[^A-Za-z0-9]/g, '').toUpperCase();
|
|
81
|
+
const short = prefix.length <= 3 ? prefix : prefix.slice(0, 3);
|
|
82
|
+
process.stdout.write(`${short || 'GEN'}-${suffix || '00'}`);
|
|
83
|
+
NODE
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
write_worktree_context() {
|
|
87
|
+
local target_worktree="$1"
|
|
88
|
+
local target_branch="$2"
|
|
89
|
+
|
|
90
|
+
if [[ "$target_worktree" != "$repo_root" ]]; then
|
|
91
|
+
ensure_local_exclude "$target_worktree" "WORKTREE_CONTEXT.md"
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
node - "$slice_abs" "$target_worktree" "$target_branch" "$spec_family" "$spec_slug" <<'NODE' > "$target_worktree/WORKTREE_CONTEXT.md"
|
|
95
|
+
const fs = require('fs');
|
|
96
|
+
|
|
97
|
+
const [slicePath, worktreePath, branchName, specFamily, specSlug] = process.argv.slice(2);
|
|
98
|
+
const slice = JSON.parse(fs.readFileSync(slicePath, 'utf8'));
|
|
99
|
+
|
|
100
|
+
function toAlias(ticket) {
|
|
101
|
+
const parts = String(ticket || '').split('-').filter(Boolean);
|
|
102
|
+
const prefix = (parts[0] || 'GEN').replace(/[^A-Za-z0-9]/g, '').toUpperCase();
|
|
103
|
+
const suffix = (parts[parts.length - 1] || '00').replace(/[^A-Za-z0-9]/g, '').toUpperCase();
|
|
104
|
+
const short = prefix.length <= 3 ? prefix : prefix.slice(0, 3);
|
|
105
|
+
return `${short || 'GEN'}-${suffix || '00'}`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function listBlock(items) {
|
|
109
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
110
|
+
return '- n/a';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return items.map((item) => `- ${item}`).join('\n');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const alias = toAlias(slice.ticket);
|
|
117
|
+
const status = slice.status || 'pending';
|
|
118
|
+
const title = slice.title || slice.slice_id;
|
|
119
|
+
const objective = slice.objective || 'Sin objetivo declarado.';
|
|
120
|
+
|
|
121
|
+
const lines = [
|
|
122
|
+
'# Worktree Context',
|
|
123
|
+
'',
|
|
124
|
+
'> Archivo generado localmente por `tools/scripts/start-slice.sh`.',
|
|
125
|
+
'> No se trackea en git.',
|
|
126
|
+
'',
|
|
127
|
+
`**Alias:** ${alias}`,
|
|
128
|
+
`**Spec:** ${specSlug}`,
|
|
129
|
+
`**Spec family:** ${specFamily}`,
|
|
130
|
+
`**Slice:** ${slice.slice_id}`,
|
|
131
|
+
`**Ticket:** ${slice.ticket}`,
|
|
132
|
+
`**Branch:** ${branchName}`,
|
|
133
|
+
`**Worktree:** ${worktreePath}`,
|
|
134
|
+
`**Status:** ${status}`,
|
|
135
|
+
'',
|
|
136
|
+
'## Title',
|
|
137
|
+
'',
|
|
138
|
+
title,
|
|
139
|
+
'',
|
|
140
|
+
'## Objective',
|
|
141
|
+
'',
|
|
142
|
+
objective,
|
|
143
|
+
'',
|
|
144
|
+
'## Routes',
|
|
145
|
+
'',
|
|
146
|
+
listBlock(slice.ui_scope?.routes),
|
|
147
|
+
'',
|
|
148
|
+
'## Components',
|
|
149
|
+
'',
|
|
150
|
+
listBlock(slice.ui_scope?.components),
|
|
151
|
+
'',
|
|
152
|
+
'## Allowed Files',
|
|
153
|
+
'',
|
|
154
|
+
listBlock(slice.files),
|
|
155
|
+
'',
|
|
156
|
+
'## Constraints',
|
|
157
|
+
'',
|
|
158
|
+
listBlock(slice.not_included),
|
|
159
|
+
'',
|
|
160
|
+
'## Expected Validation',
|
|
161
|
+
'',
|
|
162
|
+
listBlock(slice.acceptance),
|
|
163
|
+
''
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
process.stdout.write(`${lines.join('\n')}\n`);
|
|
167
|
+
NODE
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
refresh_active_slices_board() {
|
|
171
|
+
if [[ -x "$repo_root/tools/scripts/refresh-active-slices.sh" ]]; then
|
|
172
|
+
"$repo_root/tools/scripts/refresh-active-slices.sh" >/dev/null 2>&1 || true
|
|
173
|
+
fi
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
allow_draft="0"
|
|
177
|
+
args=()
|
|
178
|
+
for arg in "$@"; do
|
|
179
|
+
case "$arg" in
|
|
180
|
+
-h|--help)
|
|
181
|
+
usage
|
|
182
|
+
exit 0
|
|
183
|
+
;;
|
|
184
|
+
--allow-draft)
|
|
185
|
+
allow_draft="1"
|
|
186
|
+
;;
|
|
187
|
+
*)
|
|
188
|
+
args+=("$arg")
|
|
189
|
+
;;
|
|
190
|
+
esac
|
|
191
|
+
done
|
|
192
|
+
|
|
193
|
+
[[ "${ALLOW_DRAFT_SLICE:-}" == "1" ]] && allow_draft="1"
|
|
194
|
+
|
|
195
|
+
if [[ ${#args[@]} -ne 1 ]]; then
|
|
196
|
+
usage
|
|
197
|
+
exit 1
|
|
198
|
+
fi
|
|
199
|
+
|
|
200
|
+
if ! command -v git >/dev/null 2>&1; then
|
|
201
|
+
echo "Error: git no esta disponible en PATH." >&2
|
|
202
|
+
exit 1
|
|
203
|
+
fi
|
|
204
|
+
|
|
205
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
206
|
+
echo "Error: node no esta disponible en PATH." >&2
|
|
207
|
+
exit 1
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
slice_input="${args[0]}"
|
|
211
|
+
repo_root="$(canonicalize_dir "$(git rev-parse --show-toplevel)")"
|
|
212
|
+
|
|
213
|
+
if [[ ! -f "$slice_input" ]]; then
|
|
214
|
+
echo "Error: no existe el slice '$slice_input'." >&2
|
|
215
|
+
exit 1
|
|
216
|
+
fi
|
|
217
|
+
|
|
218
|
+
slice_abs="$(canonicalize_dir "$(dirname "$slice_input")")/$(basename "$slice_input")"
|
|
219
|
+
slice_rel="${slice_abs#$repo_root/}"
|
|
220
|
+
|
|
221
|
+
case "$slice_rel" in
|
|
222
|
+
specs-fix/*/slices/*/slice.json)
|
|
223
|
+
spec_family="specs-fix"
|
|
224
|
+
spec_slug="$(printf '%s\n' "$slice_rel" | cut -d/ -f2)"
|
|
225
|
+
;;
|
|
226
|
+
specs/*/slices/*/slice.json)
|
|
227
|
+
spec_family="specs"
|
|
228
|
+
spec_slug="$(printf '%s\n' "$slice_rel" | cut -d/ -f2)"
|
|
229
|
+
;;
|
|
230
|
+
*)
|
|
231
|
+
echo "Error: el slice debe vivir dentro de specs/ o specs-fix/." >&2
|
|
232
|
+
exit 1
|
|
233
|
+
;;
|
|
234
|
+
esac
|
|
235
|
+
|
|
236
|
+
slice_meta=()
|
|
237
|
+
while IFS= read -r line; do
|
|
238
|
+
slice_meta+=("$line")
|
|
239
|
+
done < <(node - "$slice_abs" <<'NODE'
|
|
240
|
+
const fs = require('fs');
|
|
241
|
+
|
|
242
|
+
const slicePath = process.argv[2];
|
|
243
|
+
|
|
244
|
+
function fail(message) {
|
|
245
|
+
console.error(`Error: ${message}`);
|
|
246
|
+
process.exit(1);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
let json;
|
|
250
|
+
try {
|
|
251
|
+
json = JSON.parse(fs.readFileSync(slicePath, 'utf8'));
|
|
252
|
+
} catch (error) {
|
|
253
|
+
fail(`No se pudo parsear '${slicePath}' como JSON: ${error.message}`);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const ticket = typeof json.ticket === 'string' ? json.ticket.trim() : '';
|
|
257
|
+
const git = json.git ?? {};
|
|
258
|
+
const branchType = typeof git.branch_type === 'string' ? git.branch_type.trim() : '';
|
|
259
|
+
const baseBranch = typeof git.base_branch === 'string' ? git.base_branch.trim() : '';
|
|
260
|
+
const branchSlug = typeof git.branch_slug === 'string' ? git.branch_slug.trim() : '';
|
|
261
|
+
const branchName = typeof git.branch_name === 'string' ? git.branch_name.trim() : '';
|
|
262
|
+
const sliceId = typeof json.slice_id === 'string' ? json.slice_id.trim() : '';
|
|
263
|
+
|
|
264
|
+
if (!sliceId) {
|
|
265
|
+
fail('Falta "slice_id" en el slice.');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (!ticket) {
|
|
269
|
+
fail('Falta "ticket" en el slice.');
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (!branchType || !baseBranch || !branchSlug || !branchName) {
|
|
273
|
+
fail('El bloque "git" debe incluir "branch_type", "base_branch", "branch_slug" y "branch_name".');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const expectedBaseByType = {
|
|
277
|
+
feature: 'develop',
|
|
278
|
+
bugfix: 'develop',
|
|
279
|
+
hotfix: 'main'
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
if (!expectedBaseByType[branchType]) {
|
|
283
|
+
fail(`git.branch_type invalido: "${branchType}". Usa "feature", "bugfix" o "hotfix".`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const expectedBaseBranch = expectedBaseByType[branchType];
|
|
287
|
+
if (baseBranch !== expectedBaseBranch) {
|
|
288
|
+
fail(`git.base_branch invalido para ${branchType}. Esperado: "${expectedBaseBranch}".`);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const expectedBranchName = `${branchType}/${ticket}-${branchSlug}`;
|
|
292
|
+
if (branchName !== expectedBranchName) {
|
|
293
|
+
fail(`git.branch_name invalido. Esperado: "${expectedBranchName}".`);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
console.log(sliceId);
|
|
297
|
+
console.log(ticket);
|
|
298
|
+
console.log(branchType);
|
|
299
|
+
console.log(baseBranch);
|
|
300
|
+
console.log(branchSlug);
|
|
301
|
+
console.log(branchName);
|
|
302
|
+
console.log(String(json.status || 'draft'));
|
|
303
|
+
NODE
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
if [[ ${#slice_meta[@]} -ne 7 ]]; then
|
|
307
|
+
echo "Error: no se pudo leer la metadata git del slice." >&2
|
|
308
|
+
exit 1
|
|
309
|
+
fi
|
|
310
|
+
|
|
311
|
+
slice_id="${slice_meta[0]}"
|
|
312
|
+
ticket="${slice_meta[1]}"
|
|
313
|
+
branch_type="${slice_meta[2]}"
|
|
314
|
+
base_branch="${slice_meta[3]}"
|
|
315
|
+
branch_slug="${slice_meta[4]}"
|
|
316
|
+
branch_name="${slice_meta[5]}"
|
|
317
|
+
slice_status="${slice_meta[6]}"
|
|
318
|
+
|
|
319
|
+
if [[ "$slice_status" == "blocked" ]]; then
|
|
320
|
+
echo "Error: el slice esta bloqueado (status=blocked). Resolve el bloqueante antes de iniciar." >&2
|
|
321
|
+
exit 1
|
|
322
|
+
fi
|
|
323
|
+
|
|
324
|
+
if [[ "$slice_status" == "cancelled" ]]; then
|
|
325
|
+
echo "Error: el slice esta cancelado (status=cancelled)." >&2
|
|
326
|
+
exit 1
|
|
327
|
+
fi
|
|
328
|
+
|
|
329
|
+
if [[ "$slice_status" == "completed" ]]; then
|
|
330
|
+
echo "WARN: el slice ya figura como completed. Si realmente corresponde reejecutarlo, cambia el status a in_progress."
|
|
331
|
+
fi
|
|
332
|
+
|
|
333
|
+
if [[ "$slice_status" == "draft" && "$allow_draft" != "1" ]]; then
|
|
334
|
+
echo "Error: el slice esta en estado 'draft'. Marca el slice como 'ready' o usa --allow-draft para un bootstrap intencional." >&2
|
|
335
|
+
exit 1
|
|
336
|
+
fi
|
|
337
|
+
|
|
338
|
+
if [[ "$slice_status" == "draft" ]]; then
|
|
339
|
+
echo "WARN: bootstrap intencional para un slice en draft."
|
|
340
|
+
fi
|
|
341
|
+
|
|
342
|
+
repo_name="$(basename "$repo_root")"
|
|
343
|
+
repo_parent="$(dirname "$repo_root")"
|
|
344
|
+
worktrees_root="${SLICE_WORKTREES_DIR:-$repo_parent/.worktrees/$repo_name}"
|
|
345
|
+
safe_branch_name="$(printf '%s' "$branch_name" | sed 's#[^A-Za-z0-9._-]#-#g')"
|
|
346
|
+
worktree_path="$worktrees_root/$safe_branch_name"
|
|
347
|
+
|
|
348
|
+
git worktree prune >/dev/null 2>&1 || true
|
|
349
|
+
|
|
350
|
+
existing_worktree_path="$(
|
|
351
|
+
node - "$branch_name" <<'NODE'
|
|
352
|
+
const cp = require('child_process');
|
|
353
|
+
|
|
354
|
+
const branchRef = `refs/heads/${process.argv[2]}`;
|
|
355
|
+
const text = cp.execSync('git worktree list --porcelain', {
|
|
356
|
+
encoding: 'utf8',
|
|
357
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
let currentPath = '';
|
|
361
|
+
for (const line of text.trim().split('\n')) {
|
|
362
|
+
if (line.startsWith('worktree ')) {
|
|
363
|
+
currentPath = line.slice('worktree '.length);
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (line === `branch ${branchRef}`) {
|
|
368
|
+
process.stdout.write(currentPath);
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
NODE
|
|
373
|
+
)"
|
|
374
|
+
|
|
375
|
+
if [[ -n "$existing_worktree_path" && ! -d "$existing_worktree_path" ]]; then
|
|
376
|
+
existing_worktree_path=""
|
|
377
|
+
fi
|
|
378
|
+
|
|
379
|
+
if [[ -n "$existing_worktree_path" ]]; then
|
|
380
|
+
write_worktree_context "$existing_worktree_path" "$branch_name"
|
|
381
|
+
refresh_active_slices_board
|
|
382
|
+
cat <<EOF
|
|
383
|
+
La rama ya tiene un worktree asociado.
|
|
384
|
+
Alias: $(slice_alias "$ticket")
|
|
385
|
+
Spec: $spec_slug
|
|
386
|
+
Slice: $slice_id
|
|
387
|
+
Ticket: $ticket
|
|
388
|
+
Rama: $branch_name
|
|
389
|
+
Base: $base_branch
|
|
390
|
+
Worktree: $existing_worktree_path
|
|
391
|
+
EOF
|
|
392
|
+
exit 0
|
|
393
|
+
fi
|
|
394
|
+
|
|
395
|
+
if [[ -e "$worktree_path" && ! -f "$worktree_path/.git" && ! -d "$worktree_path/.git" ]]; then
|
|
396
|
+
echo "Error: la ruta '$worktree_path' ya existe y no parece un worktree git." >&2
|
|
397
|
+
exit 1
|
|
398
|
+
fi
|
|
399
|
+
|
|
400
|
+
mkdir -p "$worktrees_root"
|
|
401
|
+
|
|
402
|
+
git fetch origin --prune >/dev/null 2>&1 || true
|
|
403
|
+
|
|
404
|
+
if git show-ref --verify --quiet "refs/heads/$branch_name"; then
|
|
405
|
+
git worktree add "$worktree_path" "$branch_name"
|
|
406
|
+
elif git ls-remote --exit-code --heads origin "$branch_name" >/dev/null 2>&1; then
|
|
407
|
+
git fetch origin "$branch_name:$branch_name" >/dev/null 2>&1
|
|
408
|
+
git worktree add "$worktree_path" "$branch_name"
|
|
409
|
+
else
|
|
410
|
+
base_ref="$(resolve_base_ref "$base_branch")"
|
|
411
|
+
git worktree add -b "$branch_name" "$worktree_path" "$base_ref"
|
|
412
|
+
fi
|
|
413
|
+
|
|
414
|
+
write_worktree_context "$worktree_path" "$branch_name"
|
|
415
|
+
refresh_active_slices_board
|
|
416
|
+
|
|
417
|
+
cat <<EOF
|
|
418
|
+
Slice listo para trabajar.
|
|
419
|
+
Alias: $(slice_alias "$ticket")
|
|
420
|
+
Spec: $spec_slug
|
|
421
|
+
Slice: $slice_id
|
|
422
|
+
Ticket: $ticket
|
|
423
|
+
Tipo de rama: $branch_type
|
|
424
|
+
Base: $base_branch
|
|
425
|
+
Slug: $branch_slug
|
|
426
|
+
Rama: $branch_name
|
|
427
|
+
Worktree: $worktree_path
|
|
428
|
+
Contexto: $worktree_path/WORKTREE_CONTEXT.md
|
|
429
|
+
EOF
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# {{PROJECT_NAME}} Evidence Report
|
|
2
|
+
|
|
3
|
+
**Spec:** [Spec name]
|
|
4
|
+
**Last updated:** {{FECHA}}
|
|
5
|
+
**Status:** In progress
|
|
6
|
+
|
|
7
|
+
## Summary
|
|
8
|
+
|
|
9
|
+
| Slice | Acceptance criteria | Status | Evidence |
|
|
10
|
+
|-------|---------------------|--------|----------|
|
|
11
|
+
| slice-01 | [Total] | Pending | - |
|
|
12
|
+
|
|
13
|
+
## Evidence by Slice
|
|
14
|
+
|
|
15
|
+
_No evidence recorded yet._
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# {{PROJECT_NAME}}
|
|
2
|
+
|
|
3
|
+
**Date:** {{FECHA}}
|
|
4
|
+
**Status:** {{ESTADO}}
|
|
5
|
+
|
|
6
|
+
## Objective
|
|
7
|
+
|
|
8
|
+
[Describe the project objective in one or two sentences.]
|
|
9
|
+
|
|
10
|
+
## Scope
|
|
11
|
+
|
|
12
|
+
### Included
|
|
13
|
+
|
|
14
|
+
- [Feature 1]
|
|
15
|
+
- [Feature 2]
|
|
16
|
+
|
|
17
|
+
### Excluded
|
|
18
|
+
|
|
19
|
+
- [Out of scope item 1]
|
|
20
|
+
- [Out of scope item 2]
|
|
21
|
+
|
|
22
|
+
## Timeline
|
|
23
|
+
|
|
24
|
+
| Phase | Description | Target date |
|
|
25
|
+
|-------|-------------|-------------|
|
|
26
|
+
| 1 | [Description] | {{FECHA}} |
|
|
27
|
+
| 2 | [Description] | {{FECHA}} |
|
|
28
|
+
|
|
29
|
+
## Slices
|
|
30
|
+
|
|
31
|
+
| Slice | Title | Status | Spec |
|
|
32
|
+
|-------|-------|--------|------|
|
|
33
|
+
| 01 | [Name] | Pending | [slice-01](./slices/slice-01/slice.json) |
|
|
34
|
+
|
|
35
|
+
## Definition of Done
|
|
36
|
+
|
|
37
|
+
- All slices are `completed`
|
|
38
|
+
- No slice is `blocked` or `in_progress`
|
|
39
|
+
- `EVIDENCE_REPORT.md` has evidence for every slice
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# {{PROJECT_NAME}} Spec Status
|
|
2
|
+
|
|
3
|
+
**Spec:** [Spec name]
|
|
4
|
+
**Last updated:** {{FECHA}}
|
|
5
|
+
|
|
6
|
+
## Spec Status
|
|
7
|
+
|
|
8
|
+
| Slice | Title | Status | PR | Estimated hours | Actual hours |
|
|
9
|
+
|-------|-------|--------|----|-----------------|--------------|
|
|
10
|
+
| slice-01 | [Name] | Pending | - | 0 | - |
|
|
11
|
+
|
|
12
|
+
## Progress
|
|
13
|
+
|
|
14
|
+
- Completed slices: 0 / [total]
|
|
15
|
+
- Estimated hours: 0
|
|
16
|
+
- Actual hours: 0
|
|
17
|
+
|
|
18
|
+
## Blockers
|
|
19
|
+
|
|
20
|
+
| Slice | Blocker | Since | Action needed |
|
|
21
|
+
|-------|---------|-------|---------------|
|
|
22
|
+
| - | - | - | - |
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# PR - [TICKET] - [Slice Title]
|
|
2
|
+
|
|
3
|
+
## Title
|
|
4
|
+
|
|
5
|
+
[Short, clear title for the change]
|
|
6
|
+
|
|
7
|
+
## Summary
|
|
8
|
+
|
|
9
|
+
[Brief summary of what was implemented and why]
|
|
10
|
+
|
|
11
|
+
## Scope
|
|
12
|
+
|
|
13
|
+
- [Included change 1]
|
|
14
|
+
- [Included change 2]
|
|
15
|
+
- [Relevant exclusion, if any]
|
|
16
|
+
|
|
17
|
+
## Files
|
|
18
|
+
|
|
19
|
+
- `[file 1]`
|
|
20
|
+
- `[file 2]`
|
|
21
|
+
|
|
22
|
+
## How to Test (DETAILED - REQUIRED)
|
|
23
|
+
|
|
24
|
+
### Required Environment
|
|
25
|
+
|
|
26
|
+
- Node: [version, e.g. 20.x]
|
|
27
|
+
- [Additional prerequisite if any]
|
|
28
|
+
- [Environment variable or flag if needed]
|
|
29
|
+
|
|
30
|
+
### Worktree Access
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Navigate to the slice worktree if it already exists
|
|
34
|
+
cd ../.worktrees/[repo-name]/[sanitized-branch-name]
|
|
35
|
+
|
|
36
|
+
# Or create the worktree from scratch
|
|
37
|
+
npm run start:slice -- specs/{{PROJECT_SLUG}}/slices/[slice-id]/slice.json
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Run the Project
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# [Project-specific start command]
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Use Cases
|
|
47
|
+
|
|
48
|
+
#### Case 1: [Descriptive case name]
|
|
49
|
+
|
|
50
|
+
**Prerequisite:** [required initial state]
|
|
51
|
+
|
|
52
|
+
1. [Exact step 1]
|
|
53
|
+
2. [Exact step 2]
|
|
54
|
+
3. [Exact step 3]
|
|
55
|
+
|
|
56
|
+
**Expected result:** [what should happen]
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
#### Case 2: [Descriptive case name]
|
|
61
|
+
|
|
62
|
+
**Prerequisite:** [required initial state]
|
|
63
|
+
|
|
64
|
+
1. [Exact step 1]
|
|
65
|
+
2. [Exact step 2]
|
|
66
|
+
|
|
67
|
+
**Expected result:** [what should happen]
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
### Technical Verification
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# Slice validation gate
|
|
75
|
+
npm run check:slice -- specs/{{PROJECT_SLUG}}/slices/[slice-id]/slice.json --gate validation
|
|
76
|
+
|
|
77
|
+
# PR gate
|
|
78
|
+
npm run check:pr -- specs/{{PROJECT_SLUG}}/slices/[slice-id]/slice.json
|
|
79
|
+
|
|
80
|
+
# Whitespace and conflicts
|
|
81
|
+
git diff --check
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Evidence
|
|
85
|
+
|
|
86
|
+
- [Verifiable signal 1]
|
|
87
|
+
- [Verifiable signal 2]
|
|
88
|
+
|
|
89
|
+
## Rollback
|
|
90
|
+
|
|
91
|
+
1. `git revert [commit-hash]`
|
|
92
|
+
2. [Validate the rollback]
|
|
93
|
+
3. [Document any follow-up if needed]
|
|
94
|
+
|
|
95
|
+
## Risks / Notes
|
|
96
|
+
|
|
97
|
+
- [Risk, limitation, or follow-up]
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"slice_id": "slice-XX-[name]",
|
|
3
|
+
"ticket": "ABC-123",
|
|
4
|
+
"type": "feature",
|
|
5
|
+
"title": "[Slice title]",
|
|
6
|
+
"objective": "[One or two sentences describing what will be implemented.]",
|
|
7
|
+
"description": "[More detail about the slice and why it is needed.]",
|
|
8
|
+
"git": {
|
|
9
|
+
"branch_type": "feature",
|
|
10
|
+
"base_branch": "develop",
|
|
11
|
+
"branch_slug": "[kebab-case-slug]",
|
|
12
|
+
"branch_name": "feature/ABC-123-[kebab-case-slug]"
|
|
13
|
+
},
|
|
14
|
+
"ui_scope": {
|
|
15
|
+
"routes": [
|
|
16
|
+
"/route-1",
|
|
17
|
+
"/route-2"
|
|
18
|
+
],
|
|
19
|
+
"components": [
|
|
20
|
+
"Component1",
|
|
21
|
+
"Component2"
|
|
22
|
+
],
|
|
23
|
+
"layouts": [
|
|
24
|
+
"Layout1",
|
|
25
|
+
"Layout2"
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"must": [
|
|
29
|
+
"[Measurable and verifiable requirement 1]",
|
|
30
|
+
"[Measurable and verifiable requirement 2]",
|
|
31
|
+
"[Measurable and verifiable requirement 3]"
|
|
32
|
+
],
|
|
33
|
+
"not_included": [
|
|
34
|
+
"[What is not included in this slice]",
|
|
35
|
+
"[What belongs to another slice]"
|
|
36
|
+
],
|
|
37
|
+
"acceptance": [
|
|
38
|
+
"[Acceptance criterion 1]",
|
|
39
|
+
"[Acceptance criterion 2]",
|
|
40
|
+
"[Acceptance criterion 3]"
|
|
41
|
+
],
|
|
42
|
+
"files": [
|
|
43
|
+
"app/path/file.ts",
|
|
44
|
+
"lib/module/file.ts",
|
|
45
|
+
"components/Component.tsx"
|
|
46
|
+
],
|
|
47
|
+
"tests": [
|
|
48
|
+
"tests/e2e/module/test.spec.ts",
|
|
49
|
+
"tests/unit/module/test.test.ts"
|
|
50
|
+
],
|
|
51
|
+
"documentation": [
|
|
52
|
+
"docs/api/module/README.md",
|
|
53
|
+
"docs/STATUS.md",
|
|
54
|
+
"specs/{{PROJECT_SLUG}}/slices/[slice-id]/pr.md"
|
|
55
|
+
],
|
|
56
|
+
"dependencies": [
|
|
57
|
+
"slice-XX-previous (if any)"
|
|
58
|
+
],
|
|
59
|
+
"assumptions": [
|
|
60
|
+
"[Technical or business assumption]"
|
|
61
|
+
],
|
|
62
|
+
"estimated_hours": 0,
|
|
63
|
+
"actual_hours": 0,
|
|
64
|
+
"status": "draft",
|
|
65
|
+
"blocked_reason": null,
|
|
66
|
+
"ready_at": null,
|
|
67
|
+
"started_at": null,
|
|
68
|
+
"completed_at": null
|
|
69
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Quiver v0.5 Evidence Report
|
|
2
|
+
|
|
3
|
+
**Spec:** quiver-v05-readme-adoption-contract
|
|
4
|
+
**Last updated:** 2026-04-21
|
|
5
|
+
**Status:** Completed
|
|
6
|
+
|
|
7
|
+
## Summary
|
|
8
|
+
|
|
9
|
+
| Slice | Acceptance criteria | Status | Evidence |
|
|
10
|
+
|-------|---------------------|--------|----------|
|
|
11
|
+
| slice-01 | 6 | Completed | README commands and links were checked against the current CLI, package scripts, and repository paths |
|
|
12
|
+
|
|
13
|
+
## Evidence by Slice
|
|
14
|
+
|
|
15
|
+
- `node bin/create-quiver.js --help`
|
|
16
|
+
- `bash scripts/release-quiver.sh --help`
|
|
17
|
+
- `node -e "const pkg=require('./package.json'); for (const name of ['package:quiver','smoke:create-quiver','release:quiver']) if (!pkg.scripts?.[name]) process.exit(1)"`
|
|
18
|
+
- `test -f README_FOR_AI.md`
|
|
19
|
+
- `test -f docs/SUPPORT_MATRIX.md.template`
|
|
20
|
+
- `test -f docs/TROUBLESHOOTING.md.template`
|
|
21
|
+
- `git diff --check`
|