specleap-framework 2.1.0 → 2.1.5

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 (75) hide show
  1. package/.agents/backend.md +3 -3
  2. package/.agents/frontend.md +2 -2
  3. package/.agents/producto.md +9 -11
  4. package/.claude/hooks/spec-guard.sh +132 -0
  5. package/.claude/settings.json.template +15 -0
  6. package/.clinerules +3 -3
  7. package/.coderabbit.yaml +2 -4
  8. package/.commands/compliance.md +89 -0
  9. package/.commands/inicio.md +15 -15
  10. package/.commands/nuevo/README.md +2 -2
  11. package/.commands/planificar.md +1 -1
  12. package/.continue/rules/04-git-workflow.md +5 -5
  13. package/.continuerules +3 -4
  14. package/.cursorrules +1 -1
  15. package/.github/copilot-instructions.md +1 -1
  16. package/.specleap/i18n/en.json +177 -0
  17. package/.specleap/i18n/es.json +177 -0
  18. package/.specleap/i18n.sh +63 -0
  19. package/CHANGELOG.md +276 -0
  20. package/CLAUDE.md +54 -13
  21. package/README.md +169 -528
  22. package/SETUP.md +16 -13
  23. package/openspec/INDEX.md +53 -0
  24. package/openspec/README.md +104 -0
  25. package/openspec/SPEC-FORMAT.md +168 -0
  26. package/openspec/changes/.gitkeep +0 -0
  27. package/openspec/cli/COMMAND_REFERENCE.md +817 -0
  28. package/openspec/cli/README.md +189 -0
  29. package/openspec/cli/apply.sh +229 -0
  30. package/openspec/cli/archive.sh +240 -0
  31. package/openspec/cli/code-review.sh +207 -0
  32. package/openspec/cli/common.sh +171 -0
  33. package/openspec/cli/enrich.sh +188 -0
  34. package/openspec/cli/ff.sh +329 -0
  35. package/openspec/cli/new.sh +260 -0
  36. package/openspec/cli/openspec +82 -0
  37. package/openspec/cli/report.sh +244 -0
  38. package/openspec/cli/status.sh +178 -0
  39. package/openspec/cli/verify.sh +246 -0
  40. package/openspec/config.yaml +76 -0
  41. package/openspec/examples/CHANGE-SAMPLE-001-user-authentication/00-original-user-story.md +5 -0
  42. package/openspec/examples/CHANGE-SAMPLE-001-user-authentication/01-refined-user-story.md +106 -0
  43. package/openspec/examples/CHANGE-SAMPLE-001-user-authentication/README.md +333 -0
  44. package/openspec/examples/CHANGE-SAMPLE-001-user-authentication/design.md +461 -0
  45. package/openspec/examples/CHANGE-SAMPLE-001-user-authentication/proposal.md +124 -0
  46. package/openspec/examples/CHANGE-SAMPLE-001-user-authentication/specs/functional/F001-authentication.spec.md +399 -0
  47. package/openspec/examples/CHANGE-SAMPLE-001-user-authentication/specs/technical/T001-jwt-implementation.spec.md +606 -0
  48. package/openspec/examples/CHANGE-SAMPLE-001-user-authentication/tasks.md +433 -0
  49. package/openspec/examples/MERMAID_DIAGRAMS.md +481 -0
  50. package/openspec/examples/README.md +334 -0
  51. package/openspec/specs/functional/.gitkeep +0 -0
  52. package/openspec/specs/integration/.gitkeep +0 -0
  53. package/openspec/specs/security/.gitkeep +0 -0
  54. package/openspec/specs/technical/.gitkeep +0 -0
  55. package/openspec/templates/.coderabbit.yaml +259 -0
  56. package/openspec/templates/design.md +181 -0
  57. package/openspec/templates/proposal.md +79 -0
  58. package/openspec/templates/tasks.md +193 -0
  59. package/package.json +10 -5
  60. package/rules/git-workflow.md +3 -3
  61. package/rules/session-protocol.md +3 -3
  62. package/scripts/README.md +13 -25
  63. package/scripts/compliance-audit.sh +325 -0
  64. package/scripts/generate-contract.sh +4 -4
  65. package/scripts/install-skills.sh +12 -11
  66. package/scripts/lib/render-contrato.py +1 -1
  67. package/scripts/quality-baseline.sh +210 -0
  68. package/scripts/quality-healing.sh +241 -0
  69. package/setup.sh +3 -3
  70. package/.claude/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-314.pyc +0 -0
  71. package/.claude/skills/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-314.pyc +0 -0
  72. package/.claude/skills/ui-ux-pro-max/scripts/__pycache__/search.cpython-314.pyc +0 -0
  73. package/scripts/lib/jira-project-utils.sh +0 -222
  74. package/scripts/setup-mcp.sh +0 -654
  75. package/scripts/test-cuestionario.sh +0 -428
@@ -0,0 +1,325 @@
1
+ #!/bin/bash
2
+ # SpecLeap — Compliance audit
3
+ # Checks 6 dimensions and produces a 0-100 score per dimension + total average.
4
+ # Writes a JSON with full detail to .quality/evidence/compliance/audit-TIMESTAMP.json
5
+
6
+ set +e # don't abort on individual check failures
7
+
8
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+ ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
10
+
11
+ source "$ROOT/.specleap/i18n.sh"
12
+
13
+ GREEN='\033[0;32m'
14
+ YELLOW='\033[1;33m'
15
+ RED='\033[0;31m'
16
+ BOLD='\033[1m'
17
+ NC='\033[0m'
18
+
19
+ # ---------- Utility: append an observation (tab-separated: dimension\ttext)
20
+ OBS_FILE=$(mktemp)
21
+ trap 'rm -f "$OBS_FILE"' EXIT
22
+
23
+ observe() {
24
+ echo -e "$1\t$2" >> "$OBS_FILE"
25
+ }
26
+
27
+ # ---------- 1. Version
28
+ check_version() {
29
+ local score=100
30
+ local version
31
+ version=$(grep -E '"version"' "$ROOT/package.json" | head -1 | sed 's/.*"version": *"\([^"]*\)".*/\1/')
32
+
33
+ local last_tag
34
+ last_tag=$(git -C "$ROOT" describe --tags --abbrev=0 2>/dev/null || echo "")
35
+
36
+ if [ -z "$last_tag" ]; then
37
+ # No tags yet: not penalised, common before first release
38
+ :
39
+ elif [ "v$version" != "$last_tag" ] && [ "$version" != "$last_tag" ]; then
40
+ score=50
41
+ observe "version" "$(t 'quality.compliance.version_mismatch') package.json=$version vs tag=$last_tag"
42
+ fi
43
+
44
+ VERSION_SCORE=$score
45
+ }
46
+
47
+ # ---------- 2. Skills count consistency
48
+ check_skills() {
49
+ local score=100
50
+ local skills_installed=0
51
+ if [ -d "$HOME/.skills" ]; then
52
+ skills_installed=$(find "$HOME/.skills" -maxdepth 1 -type d -not -path "$HOME/.skills" 2>/dev/null | wc -l | tr -d ' ')
53
+ fi
54
+
55
+ # Expected from baseline.json
56
+ local baseline_count=0
57
+ if [ -f "$ROOT/.quality/baselines/baseline.json" ] && command -v jq &>/dev/null; then
58
+ baseline_count=$(jq -r '.repo_snapshot.skills_count // 0' "$ROOT/.quality/baselines/baseline.json")
59
+ fi
60
+
61
+ if [ "$skills_installed" -ne "$baseline_count" ] && [ "$baseline_count" -gt 0 ]; then
62
+ score=$((score - 25))
63
+ observe "skills" "$(t 'quality.compliance.skills_mismatch') installed=$skills_installed, baseline=$baseline_count"
64
+ fi
65
+
66
+ # Check README mentions N skills consistently
67
+ local readme_counts
68
+ readme_counts=$(grep -oE '[0-9]+ Agent Skills' "$ROOT/README.md" 2>/dev/null | grep -oE '^[0-9]+' | sort -u)
69
+ if [ -n "$readme_counts" ]; then
70
+ for c in $readme_counts; do
71
+ if [ "$c" != "$skills_installed" ] && [ "$c" != "20" ] && [ "$c" != "14" ]; then
72
+ # 20 (tier 1) and 14 (tier 2) are valid partial counts
73
+ score=$((score - 25))
74
+ observe "skills" "$(t 'quality.compliance.skills_mismatch') README menciona $c Agent Skills"
75
+ fi
76
+ done
77
+ fi
78
+
79
+ [ $score -lt 0 ] && score=0
80
+ SKILLS_SCORE=$score
81
+ SKILLS_COUNT=$skills_installed
82
+ }
83
+
84
+ # ---------- 3. i18n parity
85
+ check_i18n() {
86
+ local score=100
87
+ local es_file="$ROOT/.specleap/i18n/es.json"
88
+ local en_file="$ROOT/.specleap/i18n/en.json"
89
+
90
+ if [ ! -f "$es_file" ] || [ ! -f "$en_file" ]; then
91
+ score=0
92
+ observe "i18n" "$(t 'quality.compliance.i18n_missing_file')"
93
+ I18N_SCORE=$score
94
+ return
95
+ fi
96
+
97
+ if ! command -v jq &>/dev/null; then
98
+ I18N_SCORE=$score # can't verify → generous default
99
+ return
100
+ fi
101
+
102
+ # Flatten keys: extract all leaf paths
103
+ local es_keys en_keys
104
+ es_keys=$(jq -r 'paths(scalars) | map(tostring) | join(".")' "$es_file" 2>/dev/null | sort -u)
105
+ en_keys=$(jq -r 'paths(scalars) | map(tostring) | join(".")' "$en_file" 2>/dev/null | sort -u)
106
+
107
+ local missing_in_en missing_in_es
108
+ missing_in_en=$(comm -23 <(echo "$es_keys") <(echo "$en_keys"))
109
+ missing_in_es=$(comm -13 <(echo "$es_keys") <(echo "$en_keys"))
110
+
111
+ local missing_count=0
112
+ if [ -n "$missing_in_en" ]; then
113
+ while IFS= read -r k; do
114
+ [ -z "$k" ] && continue
115
+ missing_count=$((missing_count + 1))
116
+ observe "i18n" "$(t 'quality.compliance.i18n_missing_key') en.json: $k"
117
+ done <<< "$missing_in_en"
118
+ fi
119
+ if [ -n "$missing_in_es" ]; then
120
+ while IFS= read -r k; do
121
+ [ -z "$k" ] && continue
122
+ missing_count=$((missing_count + 1))
123
+ observe "i18n" "$(t 'quality.compliance.i18n_missing_key') es.json: $k"
124
+ done <<< "$missing_in_es"
125
+ fi
126
+
127
+ score=$((100 - missing_count * 5))
128
+ [ $score -lt 0 ] && score=0
129
+ I18N_SCORE=$score
130
+ }
131
+
132
+ # ---------- 4. Hooks
133
+ check_hooks() {
134
+ local score=0
135
+ local hook="$ROOT/.claude/hooks/spec-guard.sh"
136
+ local template="$ROOT/.claude/settings.json.template"
137
+
138
+ if [ -f "$hook" ]; then
139
+ score=$((score + 25))
140
+ if head -1 "$hook" | grep -q '^#!/bin/bash\|^#!/usr/bin/env bash'; then
141
+ score=$((score + 25))
142
+ else
143
+ observe "hooks" "$(t 'quality.compliance.hook_not_shebang') spec-guard.sh"
144
+ fi
145
+ if [ -x "$hook" ]; then
146
+ score=$((score + 25))
147
+ else
148
+ observe "hooks" "$(t 'quality.compliance.hook_not_exec') spec-guard.sh"
149
+ fi
150
+ else
151
+ observe "hooks" "$(t 'quality.compliance.hook_missing') .claude/hooks/spec-guard.sh"
152
+ fi
153
+
154
+ if [ -f "$template" ] && grep -q "spec-guard.sh" "$template"; then
155
+ score=$((score + 25))
156
+ else
157
+ observe "hooks" "$(t 'quality.compliance.hook_not_in_template')"
158
+ fi
159
+
160
+ HOOKS_SCORE=$score
161
+ }
162
+
163
+ # ---------- 5. Baseline freshness
164
+ check_baseline() {
165
+ local score=100
166
+ local baseline="$ROOT/.quality/baselines/baseline.json"
167
+
168
+ if [ ! -f "$baseline" ] || ! command -v jq &>/dev/null; then
169
+ BASELINE_SCORE=$score
170
+ return
171
+ fi
172
+
173
+ local saved_loc saved_files saved_agents saved_commands
174
+ saved_loc=$(jq -r '.repo_snapshot.loc_total // 0' "$baseline")
175
+ saved_files=$(jq -r '.repo_snapshot.files_count // 0' "$baseline")
176
+ saved_agents=$(jq -r '.repo_snapshot.agents_count // 0' "$baseline")
177
+ saved_commands=$(jq -r '.repo_snapshot.commands_count // 0' "$baseline")
178
+
179
+ # Recompute
180
+ local real_files real_loc real_agents real_commands
181
+ real_files=$(find "$ROOT" -type f \
182
+ \( -name "*.md" -o -name "*.sh" -o -name "*.json" -o -name "*.mdc" \) \
183
+ -not -path "*/node_modules/*" -not -path "*/vendor/*" \
184
+ -not -path "*/dist/*" -not -path "*/.git/*" -not -path "*/.astro/*" \
185
+ -not -path "*/proyectos/*" 2>/dev/null | wc -l | tr -d ' ')
186
+
187
+ real_loc=0
188
+ if [ "$real_files" -gt 0 ]; then
189
+ real_loc=$(find "$ROOT" -type f \
190
+ \( -name "*.md" -o -name "*.sh" -o -name "*.json" -o -name "*.mdc" \) \
191
+ -not -path "*/node_modules/*" -not -path "*/vendor/*" \
192
+ -not -path "*/dist/*" -not -path "*/.git/*" -not -path "*/.astro/*" \
193
+ -not -path "*/proyectos/*" -exec cat {} + 2>/dev/null | wc -l | tr -d ' ')
194
+ fi
195
+
196
+ real_agents=$(find "$ROOT/.agents" -maxdepth 1 -type f -name "*.md" 2>/dev/null | wc -l | tr -d ' ')
197
+ real_commands=$(find "$ROOT/.commands" -maxdepth 1 -type f -name "*.md" 2>/dev/null | wc -l | tr -d ' ')
198
+
199
+ # Compute max diff pct
200
+ local max_diff=0
201
+ local fields=("loc:$saved_loc:$real_loc" "files:$saved_files:$real_files" "agents:$saved_agents:$real_agents" "commands:$saved_commands:$real_commands")
202
+ for fld in "${fields[@]}"; do
203
+ IFS=':' read -r name saved real <<< "$fld"
204
+ if [ "$real" -gt 0 ]; then
205
+ local diff
206
+ diff=$(awk "BEGIN { s=$saved; r=$real; d = (s>r?s-r:r-s); pct = (r>0 ? d/r*100 : 0); printf \"%.0f\", pct }")
207
+ [ "$diff" -gt "$max_diff" ] && max_diff=$diff
208
+ fi
209
+ done
210
+
211
+ if [ "$max_diff" -gt 20 ]; then
212
+ score=60
213
+ observe "baseline" "$(t 'quality.compliance.baseline_stale')"
214
+ elif [ "$max_diff" -gt 5 ]; then
215
+ score=85
216
+ observe "baseline" "$(t 'quality.compliance.baseline_stale')"
217
+ fi
218
+
219
+ BASELINE_SCORE=$score
220
+ }
221
+
222
+ # ---------- 6. Docs residuals
223
+ check_docs() {
224
+ local score=100
225
+ local found=0
226
+ local patterns="iapanderson archspec openclaw pamela"
227
+
228
+ for md in "$ROOT"/*.md; do
229
+ [ -f "$md" ] || continue
230
+ local filename
231
+ filename=$(basename "$md")
232
+ # CHANGELOG is history
233
+ [ "$filename" = "CHANGELOG.md" ] && continue
234
+
235
+ for p in $patterns; do
236
+ if grep -qi "$p" "$md"; then
237
+ found=$((found + 1))
238
+ observe "docs" "$(t 'quality.compliance.docs_residual') $filename: $p"
239
+ fi
240
+ done
241
+ done
242
+
243
+ score=$((100 - found * 10))
244
+ [ $score -lt 0 ] && score=0
245
+ DOCS_SCORE=$score
246
+ }
247
+
248
+ # ---------- Run all checks
249
+ check_version
250
+ check_skills
251
+ check_i18n
252
+ check_hooks
253
+ check_baseline
254
+ check_docs
255
+
256
+ TOTAL=$(( (VERSION_SCORE + SKILLS_SCORE + I18N_SCORE + HOOKS_SCORE + BASELINE_SCORE + DOCS_SCORE) / 6 ))
257
+
258
+ # ---------- Print human-readable summary
259
+ echo -e "${BOLD}$(t 'quality.compliance.header')${NC}"
260
+ echo "──────────────────────────────────────────"
261
+ printf " %-12s %3d/100\n" "$(t 'quality.compliance.version')" "$VERSION_SCORE"
262
+ printf " %-12s %3d/100\n" "$(t 'quality.compliance.skills')" "$SKILLS_SCORE"
263
+ printf " %-12s %3d/100\n" "$(t 'quality.compliance.i18n')" "$I18N_SCORE"
264
+ printf " %-12s %3d/100\n" "$(t 'quality.compliance.hooks')" "$HOOKS_SCORE"
265
+ printf " %-12s %3d/100\n" "$(t 'quality.compliance.baseline')" "$BASELINE_SCORE"
266
+ printf " %-12s %3d/100\n" "$(t 'quality.compliance.docs')" "$DOCS_SCORE"
267
+ echo "──────────────────────────────────────────"
268
+ echo -e " ${BOLD}$(t 'quality.compliance.total'): $TOTAL/100${NC}"
269
+
270
+ # Observations
271
+ if [ -s "$OBS_FILE" ]; then
272
+ echo ""
273
+ echo -e "${YELLOW}$(t 'quality.compliance.observations'):${NC}"
274
+ while IFS=$'\t' read -r dim msg; do
275
+ printf " [%s] %s\n" "$dim" "$msg"
276
+ done < "$OBS_FILE"
277
+ fi
278
+
279
+ # ---------- Write JSON detail
280
+ TS=$(date -u +%Y-%m-%dT%H:%M:%SZ)
281
+ OUT_DIR="$ROOT/.quality/evidence/compliance"
282
+ mkdir -p "$OUT_DIR"
283
+ OUT_FILE="$OUT_DIR/audit-$TS.json"
284
+
285
+ if command -v jq &>/dev/null; then
286
+ # Build observations array
287
+ OBS_JSON="[]"
288
+ if [ -s "$OBS_FILE" ]; then
289
+ OBS_JSON=$(jq -Rs '
290
+ split("\n")
291
+ | map(select(length > 0))
292
+ | map(split("\t"))
293
+ | map({dimension: .[0], message: .[1]})
294
+ ' "$OBS_FILE")
295
+ fi
296
+
297
+ jq -n \
298
+ --arg ts "$TS" \
299
+ --argjson version "$VERSION_SCORE" \
300
+ --argjson skills "$SKILLS_SCORE" \
301
+ --argjson i18n "$I18N_SCORE" \
302
+ --argjson hooks "$HOOKS_SCORE" \
303
+ --argjson baseline "$BASELINE_SCORE" \
304
+ --argjson docs "$DOCS_SCORE" \
305
+ --argjson total "$TOTAL" \
306
+ --argjson observations "$OBS_JSON" \
307
+ '{
308
+ timestamp: $ts,
309
+ scores: {
310
+ version: $version,
311
+ skills: $skills,
312
+ i18n: $i18n,
313
+ hooks: $hooks,
314
+ baseline: $baseline,
315
+ docs: $docs
316
+ },
317
+ total: $total,
318
+ observations: $observations
319
+ }' > "$OUT_FILE"
320
+
321
+ echo ""
322
+ echo -e "$(t 'quality.compliance.details_saved') $OUT_FILE"
323
+ fi
324
+
325
+ exit 0
@@ -197,10 +197,10 @@ constraints:
197
197
  budget_limit: ""
198
198
  out_of_scope: []
199
199
 
200
- jira:
201
- project_key: ""
202
- epic_count: 0
203
- story_count: 0
200
+ asana:
201
+ workspace_id: ""
202
+ section_count: 0
203
+ task_count: 0
204
204
  synced_at: ""
205
205
  ---
206
206
  EOF
@@ -1,6 +1,6 @@
1
1
  #!/bin/bash
2
2
  # SpecLeap — Instalador de Agent Skills TIER 1 + TIER 2
3
- # Versión 2.1 — 33 skills profesionales
3
+ # Versión 2.1.134 skills profesionales
4
4
 
5
5
  set -e
6
6
 
@@ -10,7 +10,7 @@ YELLOW='\033[1;33m'
10
10
  BLUE='\033[0;34m'
11
11
  NC='\033[0m'
12
12
 
13
- echo -e "${BLUE}━━━ Installing Agent Skills (33 total) ━━━${NC}"
13
+ echo -e "${BLUE}━━━ Installing Agent Skills (34 total) ━━━${NC}"
14
14
  echo ""
15
15
 
16
16
  # Lista de skills (repo:path:name)
@@ -47,7 +47,7 @@ SKILLS=(
47
47
  # Frontend (1) - jeffallan/claude-skills
48
48
  "jeffallan/claude-skills:skills/typescript-pro:typescript-pro"
49
49
 
50
- # ━━━ TIER 2 Skills (13 nuevas) ━━━
50
+ # ━━━ TIER 2 Skills (14 nuevas) ━━━
51
51
 
52
52
  # Design/Frontend (3) - vercel-labs
53
53
  "vercel-labs/agent-skills:skills/web-design-guidelines:web-design-guidelines"
@@ -71,8 +71,9 @@ SKILLS=(
71
71
  # Documentation (1) - jeffallan/claude-skills
72
72
  "jeffallan/claude-skills:skills/code-documenter:code-documenter"
73
73
 
74
- # Security (1) - jeffallan/claude-skills
74
+ # Security (2) - jeffallan/claude-skills + alirezarezvani/claude-skills
75
75
  "jeffallan/claude-skills:skills/security-reviewer:security-reviewer"
76
+ "alirezarezvani/claude-skills:ra-qm-team/gdpr-dsgvo-expert:gdpr-dsgvo-expert"
76
77
 
77
78
  # Testing Advanced (2) - jeffallan/claude-skills
78
79
  "jeffallan/claude-skills:skills/playwright-expert:playwright-expert"
@@ -108,31 +109,31 @@ for i in "${!SKILLS[@]}"; do
108
109
  # Skip si ya existe
109
110
  if [ -d "$SKILLS_DIR/$name" ]; then
110
111
  echo -e "${YELLOW}SKIP${NC} (already installed)"
111
- ((SKIPPED++))
112
+ SKIPPED=$((SKIPPED + 1))
112
113
  continue
113
114
  fi
114
-
115
+
115
116
  # Clonar repo si no existe
116
117
  repo_dir="$TMP_DIR/$(echo $repo | tr '/' '_')"
117
118
  if [ ! -d "$repo_dir" ]; then
118
119
  if ! git clone -q "https://github.com/$repo.git" "$repo_dir" 2>/dev/null; then
119
120
  echo -e "${RED}FAIL${NC} (repo not accessible)"
120
- ((FAILED++))
121
+ FAILED=$((FAILED + 1))
121
122
  continue
122
123
  fi
123
124
  fi
124
-
125
+
125
126
  # Verificar SKILL.md
126
127
  if [ ! -f "$repo_dir/$path/SKILL.md" ]; then
127
128
  echo -e "${RED}FAIL${NC} (SKILL.md not found)"
128
- ((FAILED++))
129
+ FAILED=$((FAILED + 1))
129
130
  continue
130
131
  fi
131
-
132
+
132
133
  # Copiar skill
133
134
  cp -r "$repo_dir/$path" "$SKILLS_DIR/$name"
134
135
  echo -e "${GREEN}OK${NC}"
135
- ((INSTALLED++))
136
+ INSTALLED=$((INSTALLED + 1))
136
137
  done
137
138
 
138
139
  echo ""
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
- ArchSpec — Renderizador de CONTRATO.md
3
+ SpecLeap — Renderizador de CONTRATO.md
4
4
  Convierte respuestas planas (project.name) a estructura anidada y renderiza con Jinja2
5
5
  """
6
6
 
@@ -0,0 +1,210 @@
1
+ #!/bin/bash
2
+ # SpecLeap — Quality Baseline helper
3
+ # Usage: bash scripts/quality-baseline.sh [init|snapshot|new-evidence FEATURE]
4
+
5
+ set -e
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
9
+
10
+ # Load i18n helper (exposes t "key.path" and SPECLEAP_LANG)
11
+ source "$ROOT/.specleap/i18n.sh"
12
+
13
+ QUALITY_DIR="$ROOT/.quality"
14
+ BASELINE_FILE="$QUALITY_DIR/baselines/baseline.json"
15
+ EVIDENCE_DIR="$QUALITY_DIR/evidence"
16
+
17
+ GREEN='\033[0;32m'
18
+ YELLOW='\033[1;33m'
19
+ RED='\033[0;31m'
20
+ NC='\033[0m'
21
+
22
+ cmd_init() {
23
+ echo -e "$(t 'quality.baseline.init_start')"
24
+
25
+ mkdir -p "$QUALITY_DIR/baselines"
26
+ mkdir -p "$EVIDENCE_DIR"
27
+
28
+ if [ ! -f "$EVIDENCE_DIR/.gitkeep" ]; then
29
+ touch "$EVIDENCE_DIR/.gitkeep"
30
+ fi
31
+
32
+ if [ -f "$BASELINE_FILE" ]; then
33
+ echo -e "${YELLOW}$(t 'quality.baseline.already_exists')${NC} $BASELINE_FILE"
34
+ return 0
35
+ fi
36
+
37
+ cat > "$BASELINE_FILE" <<EOF
38
+ {
39
+ "version": "1.0",
40
+ "updated_at": "$(date -u +%Y-%m-%d)",
41
+ "thresholds": {
42
+ "coverage_min": 80,
43
+ "coverage_target": 90,
44
+ "complexity_max_per_function": 10,
45
+ "duplicates_max_pct": 3
46
+ },
47
+ "repo_snapshot": {
48
+ "loc_total": 0,
49
+ "files_count": 0,
50
+ "agents_count": 0,
51
+ "commands_count": 0,
52
+ "skills_count": 0
53
+ }
54
+ }
55
+ EOF
56
+
57
+ echo -e "${GREEN}$(t 'quality.baseline.init_done')${NC}"
58
+ }
59
+
60
+ cmd_snapshot() {
61
+ echo -e "$(t 'quality.baseline.snapshot_start')"
62
+
63
+ if [ ! -f "$BASELINE_FILE" ]; then
64
+ echo -e "${RED}$(t 'quality.baseline.missing_baseline')${NC}"
65
+ echo "$(t 'quality.baseline.run_init_first')"
66
+ exit 1
67
+ fi
68
+
69
+ if ! command -v jq &> /dev/null; then
70
+ echo -e "${RED}$(t 'quality.baseline.jq_required')${NC}"
71
+ exit 1
72
+ fi
73
+
74
+ # Count source files (exclude node_modules, vendor, dist, .git, .astro)
75
+ local files_count
76
+ files_count=$(find "$ROOT" \
77
+ -type f \
78
+ \( -name "*.md" -o -name "*.sh" -o -name "*.json" -o -name "*.mdc" \) \
79
+ -not -path "*/node_modules/*" \
80
+ -not -path "*/vendor/*" \
81
+ -not -path "*/dist/*" \
82
+ -not -path "*/.git/*" \
83
+ -not -path "*/.astro/*" \
84
+ -not -path "*/proyectos/*" \
85
+ 2>/dev/null | wc -l | tr -d ' ')
86
+
87
+ # Count total lines of code in those files
88
+ local loc_total=0
89
+ if [ "$files_count" -gt 0 ]; then
90
+ loc_total=$(find "$ROOT" \
91
+ -type f \
92
+ \( -name "*.md" -o -name "*.sh" -o -name "*.json" -o -name "*.mdc" \) \
93
+ -not -path "*/node_modules/*" \
94
+ -not -path "*/vendor/*" \
95
+ -not -path "*/dist/*" \
96
+ -not -path "*/.git/*" \
97
+ -not -path "*/.astro/*" \
98
+ -not -path "*/proyectos/*" \
99
+ -exec cat {} + 2>/dev/null | wc -l | tr -d ' ')
100
+ fi
101
+
102
+ local agents_count
103
+ agents_count=$(find "$ROOT/.agents" -maxdepth 1 -type f -name "*.md" 2>/dev/null | wc -l | tr -d ' ')
104
+
105
+ local commands_count
106
+ commands_count=$(find "$ROOT/.commands" -maxdepth 1 -type f -name "*.md" 2>/dev/null | wc -l | tr -d ' ')
107
+
108
+ local skills_count=0
109
+ if [ -d "$HOME/.skills" ]; then
110
+ skills_count=$(find "$HOME/.skills" -maxdepth 1 -type d -not -path "$HOME/.skills" 2>/dev/null | wc -l | tr -d ' ')
111
+ fi
112
+
113
+ local tmp_file
114
+ tmp_file=$(mktemp)
115
+ jq \
116
+ --arg date "$(date -u +%Y-%m-%d)" \
117
+ --argjson loc "$loc_total" \
118
+ --argjson files "$files_count" \
119
+ --argjson agents "$agents_count" \
120
+ --argjson commands "$commands_count" \
121
+ --argjson skills "$skills_count" \
122
+ '.updated_at = $date
123
+ | .repo_snapshot.loc_total = $loc
124
+ | .repo_snapshot.files_count = $files
125
+ | .repo_snapshot.agents_count = $agents
126
+ | .repo_snapshot.commands_count = $commands
127
+ | .repo_snapshot.skills_count = $skills' \
128
+ "$BASELINE_FILE" > "$tmp_file"
129
+ mv "$tmp_file" "$BASELINE_FILE"
130
+
131
+ echo -e "${GREEN}$(t 'quality.baseline.snapshot_done')${NC}"
132
+ echo " LOC: $loc_total | files: $files_count | agents: $agents_count | commands: $commands_count | skills: $skills_count"
133
+ }
134
+
135
+ cmd_new_evidence() {
136
+ local slug="$1"
137
+
138
+ if [ -z "$slug" ]; then
139
+ echo -e "${RED}$(t 'quality.baseline.missing_slug')${NC}"
140
+ echo "$(t 'quality.baseline.usage')"
141
+ exit 1
142
+ fi
143
+
144
+ # Slug validation: lowercase, numbers, hyphens only
145
+ if ! [[ "$slug" =~ ^[a-z0-9-]+$ ]]; then
146
+ echo -e "${RED}$(t 'quality.baseline.invalid_slug')${NC} $slug"
147
+ exit 1
148
+ fi
149
+
150
+ local target="$EVIDENCE_DIR/$slug"
151
+ echo -e "$(t 'quality.baseline.evidence_start') $slug"
152
+
153
+ mkdir -p "$target/tests"
154
+ mkdir -p "$target/security"
155
+
156
+ cat > "$target/metadata.json" <<EOF
157
+ {
158
+ "feature_slug": "$slug",
159
+ "created_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
160
+ "ticket": "",
161
+ "author": "",
162
+ "status": "planning",
163
+ "baseline_version": "1.0"
164
+ }
165
+ EOF
166
+
167
+ cat > "$target/decisions.md" <<EOF
168
+ # Decisiones técnicas — $slug
169
+
170
+ ## Contexto
171
+
172
+ (Resumen del ticket y problema que resuelve)
173
+
174
+ ## Decisiones
175
+
176
+ ### [Título de la decisión]
177
+
178
+ **Fecha:** $(date -u +%Y-%m-%d)
179
+ **Contexto:** …
180
+ **Opciones consideradas:**
181
+ - …
182
+
183
+ **Decisión:** …
184
+ **Consecuencias:** …
185
+ EOF
186
+
187
+ touch "$target/tests/unit.txt"
188
+ touch "$target/tests/integration.txt"
189
+ touch "$target/tests/coverage.json"
190
+ touch "$target/security/audit.txt"
191
+
192
+ echo -e "${GREEN}$(t 'quality.baseline.evidence_done')${NC} $target"
193
+ }
194
+
195
+ # Dispatch
196
+ case "${1:-}" in
197
+ init)
198
+ cmd_init
199
+ ;;
200
+ snapshot)
201
+ cmd_snapshot
202
+ ;;
203
+ new-evidence)
204
+ cmd_new_evidence "$2"
205
+ ;;
206
+ *)
207
+ echo "$(t 'quality.baseline.usage')"
208
+ exit 1
209
+ ;;
210
+ esac