@the-bearded-bear/claude-craft 8.23.0 → 8.23.1

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 (117) hide show
  1. package/Dev/i18n/base/Common/templates/AGENTS.md.template +92 -0
  2. package/Dev/i18n/de/Common/templates/settings.json.template +2 -11
  3. package/Dev/i18n/de/Workflow/commands/auto-sprint.md +1 -1
  4. package/Dev/i18n/de/Workflow/commands/start.md +40 -1
  5. package/Dev/i18n/en/Common/templates/settings.json.template +2 -11
  6. package/Dev/i18n/en/Workflow/commands/auto-sprint.md +1 -1
  7. package/Dev/i18n/en/Workflow/commands/start.md +40 -1
  8. package/Dev/i18n/es/Common/templates/settings.json.template +2 -11
  9. package/Dev/i18n/es/Workflow/commands/auto-sprint.md +1 -1
  10. package/Dev/i18n/es/Workflow/commands/start.md +40 -1
  11. package/Dev/i18n/fr/Common/templates/settings.json.template +2 -11
  12. package/Dev/i18n/fr/Workflow/commands/auto-sprint.md +1 -1
  13. package/Dev/i18n/fr/Workflow/commands/start.md +40 -1
  14. package/Dev/i18n/pt/Common/templates/settings.json.template +2 -11
  15. package/Dev/i18n/pt/Workflow/commands/auto-sprint.md +1 -1
  16. package/Dev/i18n/pt/Workflow/commands/start.md +40 -1
  17. package/Dev/scripts/check-config.sh +59 -65
  18. package/Dev/scripts/install-common-rules.sh +47 -3
  19. package/Dev/scripts/install-from-config.sh +12 -1
  20. package/Dev/scripts/lib/install-tech-common.sh +6 -1
  21. package/Dev/scripts/lib/stack-profiles.sh +74 -0
  22. package/Dev/stack-profiles/README.md +15 -0
  23. package/Dev/stack-profiles/angular/permissions.txt +5 -0
  24. package/Dev/stack-profiles/angular/quality.md +3 -0
  25. package/Dev/stack-profiles/angular/stack.txt +2 -0
  26. package/Dev/stack-profiles/ansible/permissions.txt +2 -0
  27. package/Dev/stack-profiles/ansible/quality.md +2 -0
  28. package/Dev/stack-profiles/ansible/stack.txt +2 -0
  29. package/Dev/stack-profiles/coolify/stack.txt +2 -0
  30. package/Dev/stack-profiles/csharp/permissions.txt +1 -0
  31. package/Dev/stack-profiles/csharp/quality.md +3 -0
  32. package/Dev/stack-profiles/csharp/stack.txt +2 -0
  33. package/Dev/stack-profiles/docker/permissions.txt +1 -0
  34. package/Dev/stack-profiles/docker/quality.md +2 -0
  35. package/Dev/stack-profiles/docker/stack.txt +2 -0
  36. package/Dev/stack-profiles/flutter/permissions.txt +2 -0
  37. package/Dev/stack-profiles/flutter/quality.md +3 -0
  38. package/Dev/stack-profiles/flutter/stack.txt +2 -0
  39. package/Dev/stack-profiles/frankenphp/stack.txt +2 -0
  40. package/Dev/stack-profiles/hcloud/stack.txt +2 -0
  41. package/Dev/stack-profiles/kubernetes/permissions.txt +3 -0
  42. package/Dev/stack-profiles/kubernetes/quality.md +2 -0
  43. package/Dev/stack-profiles/kubernetes/stack.txt +2 -0
  44. package/Dev/stack-profiles/laravel/permissions.txt +2 -0
  45. package/Dev/stack-profiles/laravel/quality.md +3 -0
  46. package/Dev/stack-profiles/laravel/stack.txt +2 -0
  47. package/Dev/stack-profiles/opentofu/permissions.txt +3 -0
  48. package/Dev/stack-profiles/opentofu/quality.md +3 -0
  49. package/Dev/stack-profiles/opentofu/stack.txt +2 -0
  50. package/Dev/stack-profiles/paperclip/permissions.txt +4 -0
  51. package/Dev/stack-profiles/paperclip/quality.md +2 -0
  52. package/Dev/stack-profiles/paperclip/stack.txt +2 -0
  53. package/Dev/stack-profiles/pgbouncer/stack.txt +2 -0
  54. package/Dev/stack-profiles/php/permissions.txt +1 -0
  55. package/Dev/stack-profiles/php/quality.md +3 -0
  56. package/Dev/stack-profiles/php/stack.txt +2 -0
  57. package/Dev/stack-profiles/python/permissions.txt +4 -0
  58. package/Dev/stack-profiles/python/quality.md +3 -0
  59. package/Dev/stack-profiles/python/stack.txt +2 -0
  60. package/Dev/stack-profiles/react/permissions.txt +4 -0
  61. package/Dev/stack-profiles/react/quality.md +3 -0
  62. package/Dev/stack-profiles/react/stack.txt +2 -0
  63. package/Dev/stack-profiles/reactnative/permissions.txt +5 -0
  64. package/Dev/stack-profiles/reactnative/quality.md +3 -0
  65. package/Dev/stack-profiles/reactnative/stack.txt +2 -0
  66. package/Dev/stack-profiles/symfony/permissions.txt +2 -0
  67. package/Dev/stack-profiles/symfony/quality.md +4 -0
  68. package/Dev/stack-profiles/symfony/stack.txt +2 -0
  69. package/Dev/stack-profiles/vercel/permissions.txt +5 -0
  70. package/Dev/stack-profiles/vercel/quality.md +2 -0
  71. package/Dev/stack-profiles/vercel/stack.txt +2 -0
  72. package/Dev/stack-profiles/vite/permissions.txt +4 -0
  73. package/Dev/stack-profiles/vite/quality.md +2 -0
  74. package/Dev/stack-profiles/vite/stack.txt +2 -0
  75. package/Dev/stack-profiles/vuejs/permissions.txt +4 -0
  76. package/Dev/stack-profiles/vuejs/quality.md +3 -0
  77. package/Dev/stack-profiles/vuejs/stack.txt +2 -0
  78. package/Project/i18n/de/Gate/commands/validate-sprint.md +14 -5
  79. package/Project/i18n/de/agents/tech-lead.md +62 -67
  80. package/Project/i18n/de/scaffold/CLAUDE.md +5 -25
  81. package/Project/i18n/en/Gate/commands/validate-sprint.md +14 -5
  82. package/Project/i18n/en/agents/tech-lead.md +60 -65
  83. package/Project/i18n/en/scaffold/CLAUDE.md +5 -25
  84. package/Project/i18n/es/Gate/commands/validate-sprint.md +14 -5
  85. package/Project/i18n/es/agents/tech-lead.md +60 -65
  86. package/Project/i18n/es/scaffold/CLAUDE.md +5 -25
  87. package/Project/i18n/fr/Gate/commands/validate-sprint.md +14 -5
  88. package/Project/i18n/fr/agents/tech-lead.md +60 -65
  89. package/Project/i18n/fr/scaffold/CLAUDE.md +5 -25
  90. package/Project/i18n/pt/Gate/commands/validate-sprint.md +14 -5
  91. package/Project/i18n/pt/agents/tech-lead.md +60 -65
  92. package/Project/i18n/pt/scaffold/CLAUDE.md +5 -25
  93. package/Project/install-project-commands.sh +45 -10
  94. package/README.md +8 -0
  95. package/bundles/cursor/.cursorrules +1 -1
  96. package/bundles/windsurf/.windsurfrules +1 -1
  97. package/cli/kanban/client/dist/assets/BurndownView-CQyFME8U.js +3 -0
  98. package/cli/kanban/client/dist/assets/{DepsView-A7Xe41FE.js → DepsView-DEAS0a20.js} +1 -1
  99. package/cli/kanban/client/dist/assets/{DocsView-DFXULSIu.js → DocsView-BsXYPJAu.js} +1 -1
  100. package/cli/kanban/client/dist/assets/{SprintsView-DZ3KdjDw.js → SprintsView-Bo2p1NbV.js} +1 -1
  101. package/cli/kanban/client/dist/assets/cytoscape-dagre-8BvEs68F.js +2 -0
  102. package/cli/kanban/client/dist/assets/cytoscape.esm-BiK1LiYw.js +321 -0
  103. package/cli/kanban/client/dist/assets/index-CI2O6JO9.js +4 -0
  104. package/cli/kanban/client/dist/assets/{marked.esm-BFjXurK6.js → marked.esm-CYIZq5lG.js} +4 -4
  105. package/cli/kanban/client/dist/assets/purify.es-ChwZkWde.js +3 -0
  106. package/cli/kanban/client/dist/index.html +1 -1
  107. package/cli/kanban/server/app.js +6 -3
  108. package/cli/kanban/server/services/frontmatter.js +10 -4
  109. package/cli/kanban/server/services/markdown-metadata.js +150 -0
  110. package/cli/kanban/server/services/repository.js +29 -7
  111. package/cli/lib/installer.js +9 -2
  112. package/package.json +9 -9
  113. package/cli/kanban/client/dist/assets/BurndownView-BrheHgEo.js +0 -10
  114. package/cli/kanban/client/dist/assets/cytoscape-dagre-YDByNmIW.js +0 -2
  115. package/cli/kanban/client/dist/assets/cytoscape.esm-Dd3Wh3PR.js +0 -321
  116. package/cli/kanban/client/dist/assets/index-TQv7sDCT.js +0 -4
  117. package/cli/kanban/client/dist/assets/purify.es-DuRL7t6i.js +0 -3
@@ -235,91 +235,80 @@ check_common_installation() {
235
235
  }
236
236
 
237
237
  # ─────────────────────────────────────────────────────────────────────────────
238
- # Technology Registry (data-driven validation)
239
- # Format: tech_name:cmd_namespace:min_rules:min_commands:min_templates:min_checklists
238
+ # Techs connues (namespace de commandes = clé de la techno)
240
239
  # ─────────────────────────────────────────────────────────────────────────────
241
- TECH_REGISTRY=(
242
- "symfony:symfony:18:5:5:3"
243
- "flutter:flutter:14:5:3:2"
244
- "react:react:12:5:2:2"
245
- "reactnative:reactnative:15:5:2:2"
246
- "python:python:10:5:2:2"
247
- "angular:angular:10:5:2:2"
248
- "csharp:csharp:10:5:2:2"
249
- "laravel:laravel:10:5:2:2"
250
- "vuejs:vuejs:10:5:2:2"
251
- "php:php:10:5:2:2"
252
- )
240
+ DEV_TECHS=" symfony flutter react reactnative angular csharp laravel vuejs php python vite vercel paperclip "
241
+ INFRA_TECHS=" docker coolify kubernetes opentofu ansible hcloud pgbouncer frankenphp "
242
+
243
+ # Vérifie la présence d'agents d'un préfixe donné (agents infra : <tech>-*.md)
244
+ check_agents_prefix() {
245
+ local dir="$1" prefix="$2" count
246
+ count=$(find "$dir" -maxdepth 1 -name "${prefix}-*.md" 2>/dev/null | wc -l | tr -d ' ')
247
+ if [[ $count -lt 1 ]]; then
248
+ log_error "agents ${prefix}-*: non trouvé"
249
+ return 1
250
+ fi
251
+ log_ok "agents ${prefix}-*: $count fichiers"
252
+ }
253
253
 
254
254
  # ─────────────────────────────────────────────────────────────────────────────
255
- # Generic tech installation checker (data-driven)
255
+ # Vérifie l'installation d'une techno dans un module
256
256
  # ─────────────────────────────────────────────────────────────────────────────
257
257
  check_tech_installation() {
258
258
  local path="$1"
259
259
  local tech="$2"
260
260
  local issues=0
261
261
 
262
- # Look up tech in registry
263
- local entry=""
264
- for reg in "${TECH_REGISTRY[@]}"; do
265
- local reg_tech="${reg%%:*}"
266
- if [[ "$reg_tech" == "$tech" ]]; then
267
- entry="$reg"
268
- break
262
+ if [[ "$DEV_TECHS" == *" $tech "* ]]; then
263
+ check_dir_count "$path/.claude/commands/$tech" "commands/$tech" 1 || ((issues++)) || true
264
+ # Layout actuel : references/<tech> ; installations anciennes : rules/
265
+ if [[ -d "$path/.claude/references/$tech" ]]; then
266
+ check_dir_count "$path/.claude/references/$tech" "references/$tech" 1 || ((issues++)) || true
267
+ else
268
+ check_dir_count "$path/.claude/rules" "rules" 1 || ((issues++)) || true
269
269
  fi
270
- done
271
-
272
- if [[ -z "$entry" ]]; then
270
+ check_file_exists "$path/.claude/CLAUDE.md" "CLAUDE.md" || ((issues++)) || true
271
+ check_dir_count "$path/.claude/agents" "agents" 1 || ((issues++)) || true
272
+ elif [[ "$INFRA_TECHS" == *" $tech "* ]]; then
273
+ check_dir_count "$path/.claude/commands/$tech" "commands/$tech" 1 || ((issues++)) || true
274
+ check_agents_prefix "$path/.claude/agents" "$tech" || ((issues++)) || true
275
+ elif [[ "$tech" == "project" ]]; then
276
+ local ns
277
+ for ns in project sprint gate; do
278
+ check_dir_count "$path/.claude/commands/$ns" "commands/$ns" 1 || ((issues++)) || true
279
+ done
280
+ check_file_exists "$path/.claude/agents/tech-lead.md" "agents/tech-lead" || ((issues++)) || true
281
+ check_file_exists "$path/.claude/agents/product-owner.md" "agents/product-owner" || ((issues++)) || true
282
+ else
273
283
  log_warn "Technologie inconnue: $tech"
274
284
  return 1
275
285
  fi
276
286
 
277
- # Parse registry entry
278
- IFS=':' read -r _ cmd_ns min_rules min_cmds min_templates min_checklists <<< "$entry"
279
-
280
- if [[ ! -d "$path/.claude" ]]; then
281
- log_error ".claude/: non trouvé"
282
- return 1
283
- fi
284
-
285
- # Rules (generic + tech-specific)
286
- check_dir_count "$path/.claude/rules" "rules" "$min_rules" || ((issues++)) || true
287
-
288
- # Commands (tech namespace)
289
- check_dir_count "$path/.claude/commands/${cmd_ns}" "commands/${cmd_ns}" "$min_cmds" || ((issues++)) || true
290
-
291
- # Templates
292
- check_dir_count "$path/.claude/templates" "templates" "$min_templates" || ((issues++)) || true
293
-
294
- # Checklists
295
- check_dir_count "$path/.claude/checklists" "checklists" "$min_checklists" || ((issues++)) || true
296
-
297
- # CLAUDE.md
298
- check_file_exists "$path/.claude/CLAUDE.md" "CLAUDE.md" || ((issues++)) || true
299
-
300
- # Agents
301
- check_dir_count "$path/.claude/agents" "agents" 1 || ((issues++)) || true
302
-
303
- # Optional directories
304
- check_dir_optional "$path/.claude/adr" "adr"
305
- check_dir_optional "$path/.claude/examples" "examples"
306
-
307
287
  return $issues
308
288
  }
309
289
 
310
290
  # ─────────────────────────────────────────────────────────────────────────────
311
- # Dispatcher - vérifie un module selon sa technologie
291
+ # Dispatcher - vérifie un module pour chacune de ses technologies
292
+ # Args: $1=path, $2=label, $3..=techs
312
293
  # ─────────────────────────────────────────────────────────────────────────────
313
294
  check_module() {
314
295
  local path="$1"
315
- local tech="$2"
316
- local label="$3"
317
- local issues=0
296
+ local label="$2"
297
+ shift 2
298
+ local issues=0 tech tech_issues
318
299
 
319
300
  echo -e " ${BOLD}$label:${NC}"
320
301
 
321
- check_tech_installation "$path" "$tech"
322
- issues=$?
302
+ if [[ ! -d "$path/.claude" ]]; then
303
+ log_error ".claude/: non trouvé"
304
+ return 1
305
+ fi
306
+
307
+ for tech in "$@"; do
308
+ tech_issues=0
309
+ check_tech_installation "$path" "$tech" || tech_issues=$?
310
+ issues=$((issues + tech_issues))
311
+ done
323
312
 
324
313
  return $issues
325
314
  }
@@ -365,9 +354,11 @@ check_project() {
365
354
 
366
355
  # Vérifier chaque module
367
356
  for ((j=0; j<module_count; j++)); do
368
- local path tech
357
+ local path tech techs
369
358
  path=$(yq e ".projects[$project_index].modules[$j].path" "$config_file")
370
- tech=$(yq e ".projects[$project_index].modules[$j].tech" "$config_file")
359
+ # tech : string ou tableau YAML
360
+ mapfile -t techs < <(yq e ".projects[$project_index].modules[$j].tech | [.] | flatten | .[]" "$config_file")
361
+ tech=$(IFS=,; echo "${techs[*]}")
371
362
 
372
363
  local target_path
373
364
  if [[ "$path" == "." ]]; then
@@ -394,7 +385,7 @@ check_project() {
394
385
  fi
395
386
 
396
387
  local module_issues=0
397
- check_module "$target_path" "$tech" "$module_label" || module_issues=$?
388
+ check_module "$target_path" "$module_label" "${techs[@]}" || module_issues=$?
398
389
  if [[ $module_issues -gt 0 ]]; then
399
390
  issues_list+=("$name:$tech:$target_path")
400
391
  ((total_issues += module_issues)) || true
@@ -540,4 +531,7 @@ main() {
540
531
  exit 0
541
532
  }
542
533
 
543
- main "$@"
534
+ # Exécuté directement (pas quand le script est sourcé, ex. par les tests)
535
+ if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
536
+ main "$@"
537
+ fi
@@ -46,6 +46,7 @@ VERSION=$(get_claude_craft_version)
46
46
  # Shared UI (colors + logging)
47
47
  # shellcheck source=lib/shell-ui.sh
48
48
  source "${SCRIPT_DIR}/lib/shell-ui.sh"
49
+ source "${SCRIPT_DIR}/lib/stack-profiles.sh"
49
50
 
50
51
  # Default options
51
52
  action="install"
@@ -61,6 +62,7 @@ templates_only=false
61
62
  checklists_only=false
62
63
  target_dir=""
63
64
  lang="en"
65
+ tech_arg=""
64
66
 
65
67
  #-------------------------------------------------------------------------------
66
68
  # Load i18n messages
@@ -165,6 +167,7 @@ ${MSG_HELP_OPTIONS}
165
167
  --backup ${MSG_HELP_BACKUP}
166
168
  --interactive ${MSG_HELP_INTERACTIVE}
167
169
  --lang=XX ${MSG_HELP_LANG}
170
+ --tech=a,b Project stack: scopes AGENTS.md reviewers and settings.json permissions
168
171
  --agents-only Install agents only
169
172
  --commands-only Install commands only
170
173
  --templates-only Install templates only
@@ -218,6 +221,10 @@ parse_args() {
218
221
  lang="${1#--lang=}"
219
222
  shift
220
223
  ;;
224
+ --tech=*)
225
+ tech_arg="${1#--tech=}"
226
+ shift
227
+ ;;
221
228
  --agents-only)
222
229
  agents_only=true
223
230
  shift
@@ -558,6 +565,43 @@ install_checklists() {
558
565
  fi
559
566
  }
560
567
 
568
+ # Selected stack (--tech) or, without it, every profiled stack
569
+ selected_techs() {
570
+ if [[ -n "$tech_arg" ]]; then
571
+ parse_tech_list "$tech_arg"
572
+ else
573
+ all_profile_techs
574
+ fi
575
+ }
576
+
577
+ # Replace the __STACK_PERMISSIONS__ marker line with the stacks' permission rules
578
+ render_settings_json() {
579
+ local techs line
580
+ IFS=' ' read -ra techs <<< "$(selected_techs)"
581
+ while IFS= read -r line || [[ -n "$line" ]]; do
582
+ if [[ "$line" == *'"__STACK_PERMISSIONS__"'* ]]; then
583
+ if [[ ${#techs[@]} -gt 0 ]]; then render_permission_lines "${techs[@]}"; fi
584
+ else
585
+ printf '%s\n' "$line"
586
+ fi
587
+ done < "$1"
588
+ }
589
+
590
+ # With --tech, drop the "- `@<tech>-reviewer`" lines of stacks not selected
591
+ render_agents_md() {
592
+ if [[ -z "$tech_arg" ]]; then
593
+ cat "$1"
594
+ return
595
+ fi
596
+ awk -v keep=" $(parse_tech_list "$tech_arg") " '
597
+ match($0, /^- `@[a-z]+-reviewer`/) {
598
+ tech = substr($0, 5, RLENGTH - 14)
599
+ if (index(keep, " " tech " ") == 0) next
600
+ }
601
+ { print }
602
+ ' "$1"
603
+ }
604
+
561
605
  install_config() {
562
606
  log_info "${MSG_INSTALLING_CONFIG:-Installing configuration files...}"
563
607
 
@@ -587,7 +631,7 @@ install_config() {
587
631
  log_dry_run "${MSG_CREATING:-Creating} .claude/settings.json"
588
632
  else
589
633
  mkdir -p "$(dirname "$settings_dest")"
590
- cp "$settings_src" "$settings_dest"
634
+ render_settings_json "$settings_src" > "$settings_dest"
591
635
  log_success "${MSG_CREATING:-Creating} .claude/settings.json"
592
636
  fi
593
637
  ((++files_created))
@@ -873,13 +917,13 @@ main() {
873
917
 
874
918
  # Copy AGENTS.md template to target root (idempotent — won't overwrite existing)
875
919
  local agents_template
876
- agents_template="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../../.claude/templates/AGENTS.md.template"
920
+ agents_template="$I18N_DIR/base/Common/templates/AGENTS.md.template"
877
921
  local agents_target="$target_dir/AGENTS.md"
878
922
  if [[ -f "$agents_template" && ! -f "$agents_target" ]]; then
879
923
  if $dry_run; then
880
924
  log_dry_run "Would create $agents_target from AGENTS.md.template"
881
925
  else
882
- cp "$agents_template" "$agents_target"
926
+ render_agents_md "$agents_template" > "$agents_target"
883
927
  log_success "[install-common-rules] Created $agents_target from template"
884
928
  ((++files_created))
885
929
  fi
@@ -46,6 +46,7 @@ list_only=false
46
46
  force_mode=false
47
47
  preserve_config=false
48
48
  backup_mode=false
49
+ project_techs=""
49
50
  default_lang="en"
50
51
 
51
52
  # Compteurs
@@ -502,7 +503,8 @@ install_module() {
502
503
  fi
503
504
 
504
505
  # Construire les options selon les capacités de chaque script
505
- # - "project" : --lang, --skip-common uniquement
506
+ # - "project" : --lang, --force, --tech
507
+ # - "common" : options standard + --tech (stack du projet pour AGENTS.md / settings.json)
506
508
  # - infra (docker, coolify, kubernetes, opentofu, ansible, hcloud, pgbouncer, frankenphp) :
507
509
  # --install, --force, --backup, --lang (pas --preserve-config ni --skip-common)
508
510
  # - autres : --install, --force, --preserve-config, --backup, --lang, --skip-common
@@ -523,9 +525,15 @@ install_module() {
523
525
  if [[ "$backup_mode" == "true" ]]; then
524
526
  opts+=("--backup")
525
527
  fi
528
+ elif [[ "$force_mode" == "true" ]]; then
529
+ opts=("--force")
526
530
  fi
527
531
  # Ajouter la langue
528
532
  opts+=("--lang=$lang")
533
+ # Stack du projet (tous modules) : common et project tournent avant les modules
534
+ if [[ ( "$tech" == "common" || "$tech" == "project" ) && -n "$project_techs" ]]; then
535
+ opts+=("--tech=$project_techs")
536
+ fi
529
537
  # Skip common si multi-tech (2ème+ technologie) - pas supporté par project et infra
530
538
  if [[ -n "$skip_common" && "$tech" != "project" && "$is_infra" == "false" ]]; then
531
539
  opts+=("$skip_common")
@@ -611,6 +619,9 @@ install_project() {
611
619
  total_steps=$((total_steps + 1))
612
620
  fi
613
621
 
622
+ # Stack complète du projet, transmise à common et project (rendu CLAUDE.md, AGENTS.md, settings.json)
623
+ project_techs=$(yq e ".projects[$project_index].modules[].tech | [.] | flatten | .[]" "$config_file" | paste -sd, -)
624
+
614
625
  # Installer les règles communes si demandé
615
626
  if [[ "$common" == "true" ]]; then
616
627
  print_section "[$step/$total_steps] Installation des règles communes"
@@ -525,7 +525,12 @@ main() {
525
525
 
526
526
  load_messages
527
527
 
528
- [ "$target_dir" != "." ] && [ -d "$target_dir" ] && target_dir="$(cd "${target_dir}" && pwd)" || target_dir="$(pwd)"
528
+ # Never fall back to $(pwd) for a missing target: it would install into the caller's cwd
529
+ if [[ ! -d "$target_dir" ]]; then
530
+ log_error "Directory does not exist: $target_dir"
531
+ exit 1
532
+ fi
533
+ target_dir="$(cd "${target_dir}" && pwd)"
529
534
 
530
535
  echo ""
531
536
  echo "Installing Claude Code rules - ${TECH_DISPLAY_NAME} (TCL)"
@@ -0,0 +1,74 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ # Stack profiles — render stack-aware snippets from Dev/stack-profiles/<tech>/.
4
+ # Pure bash (no yq/jq): sourced by install-common-rules.sh and install-project-commands.sh.
5
+
6
+ STACK_PROFILES_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../stack-profiles" && pwd)"
7
+
8
+ # "project, python,react,python" -> "python react" (deduped, order kept, unknown techs dropped)
9
+ parse_tech_list() {
10
+ local IFS=$', \t\n' tech result=" "
11
+ for tech in $1; do
12
+ [[ "$tech" == "project" || "$tech" == "common" ]] && continue
13
+ [[ "$result" == *" $tech "* ]] && continue
14
+ if [[ ! -f "$STACK_PROFILES_DIR/$tech/stack.txt" ]]; then
15
+ echo "Warning: no stack profile for '$tech' (skipped)" >&2
16
+ continue
17
+ fi
18
+ result+="$tech "
19
+ done
20
+ result="${result# }"
21
+ echo "${result% }"
22
+ }
23
+
24
+ all_profile_techs() {
25
+ local dir
26
+ for dir in "$STACK_PROFILES_DIR"/*/; do
27
+ [[ -f "$dir/stack.txt" ]] && basename "$dir"
28
+ done | tr '\n' ' '
29
+ }
30
+
31
+ _profile_line() { sed -n "${2}p" "$STACK_PROFILES_DIR/$1/stack.txt"; }
32
+
33
+ # $1 = separator, rest = techs -> "Python, React"
34
+ stack_display_names() {
35
+ local sep="$1" out="" tech
36
+ shift
37
+ for tech in "$@"; do
38
+ out+="${out:+$sep}$(_profile_line "$tech" 1)"
39
+ done
40
+ echo "$out"
41
+ }
42
+
43
+ # "python: FastAPI, ..." per tech (body of the CLAUDE.md yaml block)
44
+ render_stack_block() {
45
+ local tech
46
+ for tech in "$@"; do
47
+ echo "$tech: $(_profile_line "$tech" 2)"
48
+ done
49
+ }
50
+
51
+ # "### Python" + quality bullets per tech having a quality.md
52
+ render_quality_block() {
53
+ local tech first=true
54
+ for tech in "$@"; do
55
+ [[ -f "$STACK_PROFILES_DIR/$tech/quality.md" ]] || continue
56
+ $first || echo ""
57
+ first=false
58
+ echo "### $(_profile_line "$tech" 1)"
59
+ cat "$STACK_PROFILES_DIR/$tech/quality.md"
60
+ done
61
+ }
62
+
63
+ # JSON array entries (' "Bash(x)",') for the settings.json allow list, deduped
64
+ render_permission_lines() {
65
+ local tech rule seen=$'\n'
66
+ for tech in "$@"; do
67
+ [[ -f "$STACK_PROFILES_DIR/$tech/permissions.txt" ]] || continue
68
+ while IFS= read -r rule; do
69
+ [[ -z "$rule" || "$seen" == *$'\n'"$rule"$'\n'* ]] && continue
70
+ seen+="$rule"$'\n'
71
+ echo " \"$rule\","
72
+ done < "$STACK_PROFILES_DIR/$tech/permissions.txt"
73
+ done
74
+ }
@@ -0,0 +1,15 @@
1
+ # Stack profiles
2
+
3
+ Language-agnostic per-technology data used by the installers to render
4
+ stack-aware root files (`CLAUDE.md`, `AGENTS.md`, `.claude/settings.json`)
5
+ from the `--tech=a,b,c` list. Read by `Dev/scripts/lib/stack-profiles.sh`.
6
+
7
+ One directory per tech key (same keys as `cli/lib/tech-registry.js` plus infra techs):
8
+
9
+ | File | Required | Format |
10
+ |------|----------|--------|
11
+ | `stack.txt` | yes | Line 1: display name. Line 2: main components (comma-separated) |
12
+ | `quality.md` | no | Markdown bullets, tool names only (no prose — shared by all languages) |
13
+ | `permissions.txt` | no | One Claude Code permission rule per line, e.g. `Bash(pytest:*)` |
14
+
15
+ Adding a tech: create its directory here; `tests/content/stack-profiles.test.mjs` checks every registry tech has one.
@@ -0,0 +1,5 @@
1
+ Bash(npm run:*)
2
+ Bash(npm test:*)
3
+ Bash(pnpm:*)
4
+ Bash(yarn:*)
5
+ Bash(ng:*)
@@ -0,0 +1,3 @@
1
+ - ESLint (angular-eslint) + TypeScript strict
2
+ - ng test
3
+ - Playwright (E2E)
@@ -0,0 +1,2 @@
1
+ Angular
2
+ Angular, TypeScript, Signals, RxJS
@@ -0,0 +1,2 @@
1
+ Bash(ansible-lint:*)
2
+ Bash(ansible-playbook --syntax-check:*)
@@ -0,0 +1,2 @@
1
+ - ansible-lint
2
+ - Molecule
@@ -0,0 +1,2 @@
1
+ Ansible
2
+ Ansible (playbooks, roles)
@@ -0,0 +1,2 @@
1
+ Coolify
2
+ Coolify (self-hosted PaaS)
@@ -0,0 +1 @@
1
+ Bash(dotnet:*)
@@ -0,0 +1,3 @@
1
+ - dotnet format
2
+ - Roslyn analyzers (warnings as errors)
3
+ - xUnit
@@ -0,0 +1,2 @@
1
+ .NET / C#
2
+ .NET, C#, ASP.NET Core, EF Core
@@ -0,0 +1 @@
1
+ Bash(hadolint:*)
@@ -0,0 +1,2 @@
1
+ - hadolint
2
+ - Multi-stage images, non-root user
@@ -0,0 +1,2 @@
1
+ Docker
2
+ Docker, Docker Compose
@@ -0,0 +1,2 @@
1
+ Bash(flutter:*)
2
+ Bash(dart:*)
@@ -0,0 +1,3 @@
1
+ - dart analyze (strict)
2
+ - flutter test (widget + golden)
3
+ - BLoC / Riverpod
@@ -0,0 +1,2 @@
1
+ Flutter
2
+ Flutter, Dart, BLoC / Riverpod, Material 3
@@ -0,0 +1,2 @@
1
+ FrankenPHP
2
+ FrankenPHP (Caddy, worker mode)
@@ -0,0 +1,2 @@
1
+ Hetzner Cloud
2
+ Hetzner Cloud (hcloud CLI)
@@ -0,0 +1,3 @@
1
+ Bash(kubectl get:*)
2
+ Bash(kubectl describe:*)
3
+ Bash(helm template:*)
@@ -0,0 +1,2 @@
1
+ - kubeconform
2
+ - kube-linter
@@ -0,0 +1,2 @@
1
+ Kubernetes
2
+ Kubernetes, Helm
@@ -0,0 +1,2 @@
1
+ Bash(composer:*)
2
+ Bash(php artisan:*)
@@ -0,0 +1,3 @@
1
+ - Larastan (level max)
2
+ - Laravel Pint
3
+ - Pest
@@ -0,0 +1,2 @@
1
+ Laravel
2
+ Laravel, Eloquent, Sanctum, Queues, Pest
@@ -0,0 +1,3 @@
1
+ Bash(tofu fmt:*)
2
+ Bash(tofu validate:*)
3
+ Bash(tofu plan:*)
@@ -0,0 +1,3 @@
1
+ - tofu fmt
2
+ - tofu validate
3
+ - tflint
@@ -0,0 +1,2 @@
1
+ OpenTofu
2
+ OpenTofu (Infrastructure as Code)
@@ -0,0 +1,4 @@
1
+ Bash(npm run:*)
2
+ Bash(npm test:*)
3
+ Bash(pnpm:*)
4
+ Bash(yarn:*)
@@ -0,0 +1,2 @@
1
+ - ESLint + TypeScript strict
2
+ - Vitest
@@ -0,0 +1,2 @@
1
+ Paperclip
2
+ Paperclip, Node.js, TypeScript, PostgreSQL, Vitest
@@ -0,0 +1,2 @@
1
+ PgBouncer
2
+ PgBouncer (PostgreSQL connection pooling)
@@ -0,0 +1 @@
1
+ Bash(composer:*)
@@ -0,0 +1,3 @@
1
+ - PHPStan (level max)
2
+ - PHP-CS-Fixer (PER-CS 3.0)
3
+ - Pest / PHPUnit
@@ -0,0 +1,2 @@
1
+ PHP
2
+ PHP, Composer, PHPStan, Pest
@@ -0,0 +1,4 @@
1
+ Bash(uv:*)
2
+ Bash(pytest:*)
3
+ Bash(ruff:*)
4
+ Bash(mypy:*)
@@ -0,0 +1,3 @@
1
+ - Ruff (lint + format)
2
+ - mypy --strict
3
+ - pytest + pytest-cov (>= 80 %)
@@ -0,0 +1,2 @@
1
+ Python
2
+ Python, FastAPI, Pydantic v2, SQLAlchemy 2 (async), uv
@@ -0,0 +1,4 @@
1
+ Bash(npm run:*)
2
+ Bash(npm test:*)
3
+ Bash(pnpm:*)
4
+ Bash(yarn:*)
@@ -0,0 +1,3 @@
1
+ - ESLint + TypeScript strict
2
+ - Vitest + Testing Library
3
+ - Playwright (E2E)
@@ -0,0 +1,2 @@
1
+ React
2
+ React, TypeScript, Vite, TanStack Query, Zustand
@@ -0,0 +1,5 @@
1
+ Bash(npm run:*)
2
+ Bash(npm test:*)
3
+ Bash(pnpm:*)
4
+ Bash(yarn:*)
5
+ Bash(npx expo:*)
@@ -0,0 +1,3 @@
1
+ - ESLint + TypeScript strict
2
+ - Jest + React Native Testing Library
3
+ - Maestro (E2E)
@@ -0,0 +1,2 @@
1
+ React Native
2
+ React Native (New Architecture), Expo, TypeScript, React Navigation
@@ -0,0 +1,2 @@
1
+ Bash(composer:*)
2
+ Bash(php bin/console:*)
@@ -0,0 +1,4 @@
1
+ - PHPStan (level max)
2
+ - PHP-CS-Fixer (PER-CS 3.0)
3
+ - PHPUnit / Pest
4
+ - DDD / Hexagonal
@@ -0,0 +1,2 @@
1
+ Symfony
2
+ Symfony, Doctrine ORM, API Platform, Messenger, PHPUnit / Pest