codexspec 0.6.2__tar.gz → 0.6.3__tar.gz

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 (58) hide show
  1. {codexspec-0.6.2 → codexspec-0.6.3}/PKG-INFO +41 -16
  2. {codexspec-0.6.2 → codexspec-0.6.3}/README.md +40 -15
  3. {codexspec-0.6.2 → codexspec-0.6.3}/pyproject.toml +1 -1
  4. {codexspec-0.6.2 → codexspec-0.6.3}/src/codexspec/__init__.py +134 -70
  5. {codexspec-0.6.2 → codexspec-0.6.3}/src/codexspec/i18n.py +53 -117
  6. {codexspec-0.6.2 → codexspec-0.6.3}/src/codexspec/translator.py +6 -0
  7. codexspec-0.6.3/templates/translations/de.json +151 -0
  8. {codexspec-0.6.2 → codexspec-0.6.3}/templates/translations/en.json +2 -0
  9. {codexspec-0.6.2 → codexspec-0.6.3}/templates/translations/es.json +2 -0
  10. {codexspec-0.6.2 → codexspec-0.6.3}/templates/translations/fr.json +2 -0
  11. {codexspec-0.6.2 → codexspec-0.6.3}/templates/translations/ja.json +2 -0
  12. {codexspec-0.6.2 → codexspec-0.6.3}/templates/translations/ko.json +2 -0
  13. {codexspec-0.6.2 → codexspec-0.6.3}/templates/translations/pt-BR.json +2 -0
  14. {codexspec-0.6.2 → codexspec-0.6.3}/templates/translations/zh-CN.json +2 -0
  15. codexspec-0.6.2/templates/translations/de.json +0 -149
  16. {codexspec-0.6.2 → codexspec-0.6.3}/.gitignore +0 -0
  17. {codexspec-0.6.2 → codexspec-0.6.3}/LICENSE +0 -0
  18. {codexspec-0.6.2 → codexspec-0.6.3}/codexspec-icon.svg +0 -0
  19. {codexspec-0.6.2 → codexspec-0.6.3}/codexspec-logo-dark.svg +0 -0
  20. {codexspec-0.6.2 → codexspec-0.6.3}/codexspec-logo-light.svg +0 -0
  21. {codexspec-0.6.2 → codexspec-0.6.3}/scripts/bash/check-i18n-completeness.sh +0 -0
  22. {codexspec-0.6.2 → codexspec-0.6.3}/scripts/bash/check-i18n-structure.sh +0 -0
  23. {codexspec-0.6.2 → codexspec-0.6.3}/scripts/bash/check-prerequisites.sh +0 -0
  24. {codexspec-0.6.2 → codexspec-0.6.3}/scripts/bash/common.sh +0 -0
  25. {codexspec-0.6.2 → codexspec-0.6.3}/scripts/bash/create-new-feature.sh +0 -0
  26. {codexspec-0.6.2 → codexspec-0.6.3}/scripts/powershell/check-prerequisites.ps1 +0 -0
  27. {codexspec-0.6.2 → codexspec-0.6.3}/scripts/powershell/common.ps1 +0 -0
  28. {codexspec-0.6.2 → codexspec-0.6.3}/scripts/powershell/create-new-feature.ps1 +0 -0
  29. {codexspec-0.6.2 → codexspec-0.6.3}/src/codexspec/commands/__init__.py +0 -0
  30. {codexspec-0.6.2 → codexspec-0.6.3}/src/codexspec/commands/installer.py +0 -0
  31. {codexspec-0.6.2 → codexspec-0.6.3}/src/codexspec/idea.md +0 -0
  32. {codexspec-0.6.2 → codexspec-0.6.3}/templates/commands/analyze.md +0 -0
  33. {codexspec-0.6.2 → codexspec-0.6.3}/templates/commands/checklist.md +0 -0
  34. {codexspec-0.6.2 → codexspec-0.6.3}/templates/commands/clarify.md +0 -0
  35. {codexspec-0.6.2 → codexspec-0.6.3}/templates/commands/commit-staged.md +0 -0
  36. {codexspec-0.6.2 → codexspec-0.6.3}/templates/commands/config.md +0 -0
  37. {codexspec-0.6.2 → codexspec-0.6.3}/templates/commands/constitution.md +0 -0
  38. {codexspec-0.6.2 → codexspec-0.6.3}/templates/commands/generate-spec.md +0 -0
  39. {codexspec-0.6.2 → codexspec-0.6.3}/templates/commands/implement-tasks.md +0 -0
  40. {codexspec-0.6.2 → codexspec-0.6.3}/templates/commands/plan-to-tasks.md +0 -0
  41. {codexspec-0.6.2 → codexspec-0.6.3}/templates/commands/pr.md +0 -0
  42. {codexspec-0.6.2 → codexspec-0.6.3}/templates/commands/quick.md +0 -0
  43. {codexspec-0.6.2 → codexspec-0.6.3}/templates/commands/review-code.md +0 -0
  44. {codexspec-0.6.2 → codexspec-0.6.3}/templates/commands/review-plan.md +0 -0
  45. {codexspec-0.6.2 → codexspec-0.6.3}/templates/commands/review-spec.md +0 -0
  46. {codexspec-0.6.2 → codexspec-0.6.3}/templates/commands/review-tasks.md +0 -0
  47. {codexspec-0.6.2 → codexspec-0.6.3}/templates/commands/spec-to-plan.md +0 -0
  48. {codexspec-0.6.2 → codexspec-0.6.3}/templates/commands/specify.md +0 -0
  49. {codexspec-0.6.2 → codexspec-0.6.3}/templates/commands/tasks-to-issues.md +0 -0
  50. {codexspec-0.6.2 → codexspec-0.6.3}/templates/docs/checklist-template.md +0 -0
  51. {codexspec-0.6.2 → codexspec-0.6.3}/templates/docs/constitution-template.md +0 -0
  52. {codexspec-0.6.2 → codexspec-0.6.3}/templates/docs/plan-template-detailed.md +0 -0
  53. {codexspec-0.6.2 → codexspec-0.6.3}/templates/docs/plan-template-simple.md +0 -0
  54. {codexspec-0.6.2 → codexspec-0.6.3}/templates/docs/requirements-template.md +0 -0
  55. {codexspec-0.6.2 → codexspec-0.6.3}/templates/docs/spec-template-detailed.md +0 -0
  56. {codexspec-0.6.2 → codexspec-0.6.3}/templates/docs/spec-template-simple.md +0 -0
  57. {codexspec-0.6.2 → codexspec-0.6.3}/templates/docs/tasks-template-detailed.md +0 -0
  58. {codexspec-0.6.2 → codexspec-0.6.3}/templates/docs/tasks-template-simple.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codexspec
3
- Version: 0.6.2
3
+ Version: 0.6.3
4
4
  Summary: CodexSpec - A Requirements-First SDD toolkit for Claude Code
5
5
  Project-URL: Homepage, https://github.com/Zts0hg/codexspec
6
6
  Project-URL: Repository, https://github.com/Zts0hg/codexspec
@@ -528,12 +528,15 @@ Implementation follows **conditional TDD workflow**:
528
528
 
529
529
  | Option | Description |
530
530
  | --------------- | ------------------------------------- |
531
- | `PROJECT_NAME` | Project directory name |
532
- | `--here`, `-h` | Initialize in current directory |
531
+ | `PROJECT_NAME` | Project directory name (`.` or `--here` for current dir) |
532
+ | `--here`, `-h` | Initialize in the current directory |
533
533
  | `--ai`, `-a` | AI assistant to use (default: claude) |
534
- | `--lang`, `-l` | Output language (e.g., en, zh-CN, ja) |
535
- | `--force`, `-f` | Force overwrite existing files |
536
- | `--no-git` | Skip git initialization |
534
+ | `--lang`, `-l` | Output (base) language; interaction/document/commit fall back to it (e.g., en, zh-CN, ja) |
535
+ | `--interaction-lang` | Interaction language (LLM dialogue + CLI output); overrides `--lang` |
536
+ | `--document-lang` | Document language (generated spec/plan/tasks); overrides `--lang` |
537
+ | `--commit-lang` | Commit-message language; overrides `--lang` |
538
+ | `--force`, `-f` | Overwrite files + auto-confirm prompts; never regenerates `config.yml` |
539
+ | `--no-git` | Skip git repository initialization |
537
540
  | `--debug`, `-d` | Enable debug output |
538
541
 
539
542
  </details>
@@ -543,8 +546,10 @@ Implementation follows **conditional TDD workflow**:
543
546
 
544
547
  | Option | Description |
545
548
  | ------------------------- | ---------------------------- |
546
- | `--set-lang`, `-l` | Set output language |
547
- | `--set-commit-lang`, `-c` | Set commit message language |
549
+ | `--set-lang`, `-l` | Set the output (base) language |
550
+ | `--set-interaction-lang` | Set the interaction language |
551
+ | `--set-document-lang` | Set the document language |
552
+ | `--set-commit-lang`, `-c` | Set the commit-message language |
548
553
  | `--list-langs` | List all supported languages |
549
554
 
550
555
  </details>
@@ -628,28 +633,46 @@ CodexSpec is inspired by GitHub spec-kit with key differences:
628
633
 
629
634
  CodexSpec supports multiple languages through **LLM dynamic translation**. No translation templates to maintain - Claude translates content at runtime based on your language configuration.
630
635
 
636
+ ### Language Dimensions
637
+
638
+ CodexSpec splits language into four independently-configurable dimensions. `output` is the base; the others override it and fall back to it (then `en`) when unset — so you can converse with Claude in one language while keeping generated artifacts or commit messages in another.
639
+
640
+ | Dimension | `config.yml` key | Set at init | Set later | Controls | Falls back to |
641
+ |-----------|------------------|-------------|-----------|----------|---------------|
642
+ | Output (base) | `output` | `--lang` | `config --set-lang` | base for the other three | `en` |
643
+ | Interaction | `interaction` | `--interaction-lang` | `config --set-interaction-lang` | LLM dialogue + CLI output | output → `en` |
644
+ | Document | `document` | `--document-lang` | `config --set-document-lang` | generated spec/plan/tasks | output → `en` |
645
+ | Commit | `commit` | `--commit-lang` | `config --set-commit-lang` | git commit messages | output → `en` |
646
+ | Templates | `templates` | — | — | template source (always `en`) | — |
647
+
631
648
  ### Setting Language
632
649
 
633
650
  **During initialization:**
634
651
 
635
652
  ```bash
636
- # Create Chinese output project
653
+ # Chinese output (sets the output base)
637
654
  codexspec init my-project --lang zh-CN
638
655
 
639
- # Create Japanese output project
640
- codexspec init my-project --lang ja
656
+ # Fully non-interactive: zh-CN base, English commit messages
657
+ codexspec init my-project --lang zh-CN --commit-lang en
658
+
659
+ # Set every dimension explicitly (scriptable, no prompts)
660
+ codexspec init my-project \
661
+ --interaction-lang zh-CN --document-lang en --commit-lang en
641
662
  ```
642
663
 
664
+ First-time init in a TTY without `--lang` (and without all three dimension flags) prompts for a base language; in a non-TTY (CI/scripts) it defaults to `en`. Re-running `init` preserves any language key you did not specify.
665
+
643
666
  **After initialization:**
644
667
 
645
668
  ```bash
646
669
  # View current configuration
647
670
  codexspec config
648
671
 
649
- # Change output language
672
+ # Change a single dimension
650
673
  codexspec config --set-lang zh-CN
651
-
652
- # Set commit message language
674
+ codexspec config --set-interaction-lang zh-CN
675
+ codexspec config --set-document-lang en
653
676
  codexspec config --set-commit-lang en
654
677
  ```
655
678
 
@@ -680,8 +703,10 @@ codexspec config --set-commit-lang en
680
703
  version: "1.0"
681
704
 
682
705
  language:
683
- output: "zh-CN" # Output language
684
- commit: "zh-CN" # Commit message language (defaults to output)
706
+ output: "zh-CN" # Base language; the three below fall back to it, then "en"
707
+ interaction: "zh-CN" # LLM dialogue + codexspec CLI output (optional → defaults to output)
708
+ document: "en" # Generated requirements/spec/plan/tasks (optional → defaults to output)
709
+ commit: "en" # Git commit messages (optional → defaults to output)
685
710
  templates: "en" # Keep as "en"
686
711
 
687
712
  project:
@@ -483,12 +483,15 @@ Implementation follows **conditional TDD workflow**:
483
483
 
484
484
  | Option | Description |
485
485
  | --------------- | ------------------------------------- |
486
- | `PROJECT_NAME` | Project directory name |
487
- | `--here`, `-h` | Initialize in current directory |
486
+ | `PROJECT_NAME` | Project directory name (`.` or `--here` for current dir) |
487
+ | `--here`, `-h` | Initialize in the current directory |
488
488
  | `--ai`, `-a` | AI assistant to use (default: claude) |
489
- | `--lang`, `-l` | Output language (e.g., en, zh-CN, ja) |
490
- | `--force`, `-f` | Force overwrite existing files |
491
- | `--no-git` | Skip git initialization |
489
+ | `--lang`, `-l` | Output (base) language; interaction/document/commit fall back to it (e.g., en, zh-CN, ja) |
490
+ | `--interaction-lang` | Interaction language (LLM dialogue + CLI output); overrides `--lang` |
491
+ | `--document-lang` | Document language (generated spec/plan/tasks); overrides `--lang` |
492
+ | `--commit-lang` | Commit-message language; overrides `--lang` |
493
+ | `--force`, `-f` | Overwrite files + auto-confirm prompts; never regenerates `config.yml` |
494
+ | `--no-git` | Skip git repository initialization |
492
495
  | `--debug`, `-d` | Enable debug output |
493
496
 
494
497
  </details>
@@ -498,8 +501,10 @@ Implementation follows **conditional TDD workflow**:
498
501
 
499
502
  | Option | Description |
500
503
  | ------------------------- | ---------------------------- |
501
- | `--set-lang`, `-l` | Set output language |
502
- | `--set-commit-lang`, `-c` | Set commit message language |
504
+ | `--set-lang`, `-l` | Set the output (base) language |
505
+ | `--set-interaction-lang` | Set the interaction language |
506
+ | `--set-document-lang` | Set the document language |
507
+ | `--set-commit-lang`, `-c` | Set the commit-message language |
503
508
  | `--list-langs` | List all supported languages |
504
509
 
505
510
  </details>
@@ -583,28 +588,46 @@ CodexSpec is inspired by GitHub spec-kit with key differences:
583
588
 
584
589
  CodexSpec supports multiple languages through **LLM dynamic translation**. No translation templates to maintain - Claude translates content at runtime based on your language configuration.
585
590
 
591
+ ### Language Dimensions
592
+
593
+ CodexSpec splits language into four independently-configurable dimensions. `output` is the base; the others override it and fall back to it (then `en`) when unset — so you can converse with Claude in one language while keeping generated artifacts or commit messages in another.
594
+
595
+ | Dimension | `config.yml` key | Set at init | Set later | Controls | Falls back to |
596
+ |-----------|------------------|-------------|-----------|----------|---------------|
597
+ | Output (base) | `output` | `--lang` | `config --set-lang` | base for the other three | `en` |
598
+ | Interaction | `interaction` | `--interaction-lang` | `config --set-interaction-lang` | LLM dialogue + CLI output | output → `en` |
599
+ | Document | `document` | `--document-lang` | `config --set-document-lang` | generated spec/plan/tasks | output → `en` |
600
+ | Commit | `commit` | `--commit-lang` | `config --set-commit-lang` | git commit messages | output → `en` |
601
+ | Templates | `templates` | — | — | template source (always `en`) | — |
602
+
586
603
  ### Setting Language
587
604
 
588
605
  **During initialization:**
589
606
 
590
607
  ```bash
591
- # Create Chinese output project
608
+ # Chinese output (sets the output base)
592
609
  codexspec init my-project --lang zh-CN
593
610
 
594
- # Create Japanese output project
595
- codexspec init my-project --lang ja
611
+ # Fully non-interactive: zh-CN base, English commit messages
612
+ codexspec init my-project --lang zh-CN --commit-lang en
613
+
614
+ # Set every dimension explicitly (scriptable, no prompts)
615
+ codexspec init my-project \
616
+ --interaction-lang zh-CN --document-lang en --commit-lang en
596
617
  ```
597
618
 
619
+ First-time init in a TTY without `--lang` (and without all three dimension flags) prompts for a base language; in a non-TTY (CI/scripts) it defaults to `en`. Re-running `init` preserves any language key you did not specify.
620
+
598
621
  **After initialization:**
599
622
 
600
623
  ```bash
601
624
  # View current configuration
602
625
  codexspec config
603
626
 
604
- # Change output language
627
+ # Change a single dimension
605
628
  codexspec config --set-lang zh-CN
606
-
607
- # Set commit message language
629
+ codexspec config --set-interaction-lang zh-CN
630
+ codexspec config --set-document-lang en
608
631
  codexspec config --set-commit-lang en
609
632
  ```
610
633
 
@@ -635,8 +658,10 @@ codexspec config --set-commit-lang en
635
658
  version: "1.0"
636
659
 
637
660
  language:
638
- output: "zh-CN" # Output language
639
- commit: "zh-CN" # Commit message language (defaults to output)
661
+ output: "zh-CN" # Base language; the three below fall back to it, then "en"
662
+ interaction: "zh-CN" # LLM dialogue + codexspec CLI output (optional → defaults to output)
663
+ document: "en" # Generated requirements/spec/plan/tasks (optional → defaults to output)
664
+ commit: "en" # Git commit messages (optional → defaults to output)
640
665
  templates: "en" # Keep as "en"
641
666
 
642
667
  project:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "codexspec"
3
- version = "0.6.2"
3
+ version = "0.6.3"
4
4
  description = "CodexSpec - A Requirements-First SDD toolkit for Claude Code"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -29,15 +29,12 @@ from .commands.installer import (
29
29
  )
30
30
  from .i18n import (
31
31
  generate_config_content,
32
- get_commit_language,
33
32
  get_interaction_language,
34
33
  get_language_name,
35
34
  get_supported_languages,
36
35
  is_supported_language,
37
36
  normalize_locale,
38
- update_config_language,
39
37
  update_language_field,
40
- update_output_language,
41
38
  )
42
39
  from .translator import translate
43
40
 
@@ -445,13 +442,35 @@ def init(
445
442
  None,
446
443
  "--lang",
447
444
  "-l",
448
- help="Output language (e.g., en, zh-CN, ja). Interactive prompt if not specified in TTY.",
445
+ help=(
446
+ "Output (base) language (e.g., en, zh-CN, ja). interaction/document/commit "
447
+ "fall back to it. Prompted on first-time init if not given in a TTY."
448
+ ),
449
+ ),
450
+ interaction_lang: Optional[str] = typer.Option(
451
+ None,
452
+ "--interaction-lang",
453
+ help=("Interaction language (LLM dialogue + codexspec CLI output); overrides --lang for this dimension."),
454
+ ),
455
+ document_lang: Optional[str] = typer.Option(
456
+ None,
457
+ "--document-lang",
458
+ help="Document language (generated requirements/spec/plan/tasks); overrides --lang.",
459
+ ),
460
+ commit_lang: Optional[str] = typer.Option(
461
+ None,
462
+ "--commit-lang",
463
+ help="Commit-message language; overrides --lang.",
449
464
  ),
450
465
  force: bool = typer.Option(
451
466
  False,
452
467
  "--force",
453
468
  "-f",
454
- help="Force merge/overwrite when initializing in current directory",
469
+ help=(
470
+ "Overwrite existing files and auto-confirm prompts (skips the language "
471
+ "prompt and command-update/migration confirms). Language config is updated "
472
+ "surgically, never fully regenerated."
473
+ ),
455
474
  ),
456
475
  no_git: bool = typer.Option(
457
476
  False,
@@ -478,37 +497,85 @@ def init(
478
497
  codexspec init . --ai claude
479
498
  codexspec init --here --ai claude
480
499
  codexspec init . --force --ai claude
500
+ codexspec init my-project --interaction-lang en --document-lang zh-CN --commit-lang en
481
501
  """
482
- # Handle interactive language selection when --lang not specified
483
- if lang is None:
484
- if sys.stdin.isatty():
485
- # TTY environment: show interactive prompt
486
- try:
487
- lang = prompt_language_selection()
488
- except KeyboardInterrupt:
489
- console.print()
490
- console.print("[yellow]Selection cancelled, using default language (en)[/yellow]")
491
- lang = "en"
492
- else:
493
- # Non-TTY environment: use default
494
- lang = "en"
495
-
496
- # Normalize language code early for use throughout the function
497
- normalized_lang = normalize_locale(lang)
498
-
499
- # Determine target directory
502
+ # When init() is invoked directly (not via the Typer CLI), typer.Option defaults
503
+ # arrive as OptionInfo sentinels rather than None. Coerce the language params to
504
+ # Optional[str] so the resolution logic below is robust in both call paths.
505
+ lang = lang if isinstance(lang, str) else None
506
+ interaction_lang = interaction_lang if isinstance(interaction_lang, str) else None
507
+ document_lang = document_lang if isinstance(document_lang, str) else None
508
+ commit_lang = commit_lang if isinstance(commit_lang, str) else None
509
+
510
+ # Determine target directory first — needed to detect an existing config.yml
511
+ # before resolving the language base (REQ-007).
500
512
  if here or project_name == ".":
501
513
  target_dir = Path.cwd()
502
514
  elif project_name:
503
515
  target_dir = Path.cwd() / project_name
504
516
  else:
505
- console.print(f"[red]{translate('cli.init.error_no_project_name', normalized_lang)}[/red]")
517
+ console.print(f"[red]{translate('cli.init.error_no_project_name', 'en')}[/red]")
506
518
  raise typer.Exit(1)
507
519
 
520
+ config_file = target_dir / ".codexspec" / "config.yml"
521
+ config_exists = config_file.exists()
522
+
523
+ def _normalize_lang_option(raw: str) -> str:
524
+ """Normalize a language flag value; warn (do not error) if unsupported."""
525
+ normalized = normalize_locale(raw)
526
+ if not is_supported_language(normalized):
527
+ console.print(f"[yellow]Warning: '{raw}' is not in the list of commonly supported languages.[/yellow]")
528
+ console.print(
529
+ "It may still work if Claude supports it. "
530
+ "Run [cyan]codexspec config --list-langs[/cyan] to see supported languages."
531
+ )
532
+ return normalized
533
+
534
+ # Per-dimension overrides: each flag maps to exactly one config key, with no
535
+ # CLI-level precedence between them (DEC-001).
536
+ lang_overrides: dict[str, str] = {}
537
+ if interaction_lang is not None:
538
+ lang_overrides["interaction"] = _normalize_lang_option(interaction_lang)
539
+ if document_lang is not None:
540
+ lang_overrides["document"] = _normalize_lang_option(document_lang)
541
+ if commit_lang is not None:
542
+ lang_overrides["commit"] = _normalize_lang_option(commit_lang)
543
+
544
+ # The output base is determinable if --lang was given OR all three specific
545
+ # dimensions are explicit (output is then irrelevant). Otherwise it must be
546
+ # prompted (first-time, TTY) or defaulted (first-time, non-TTY); on an
547
+ # existing config with no flag, everything is preserved (REQ-007).
548
+ output_value: Optional[str] = None
549
+ if lang is not None:
550
+ output_value = _normalize_lang_option(lang)
551
+ base_determinable = output_value is not None or (
552
+ "interaction" in lang_overrides and "document" in lang_overrides and "commit" in lang_overrides
553
+ )
554
+ prompted_base = False
555
+ if base_determinable:
556
+ normalized_lang = normalize_locale(output_value) if output_value else "en"
557
+ elif config_exists:
558
+ normalized_lang = get_interaction_language(config_file)
559
+ elif sys.stdin.isatty():
560
+ try:
561
+ output_value = normalize_locale(prompt_language_selection())
562
+ prompted_base = True
563
+ normalized_lang = output_value
564
+ except KeyboardInterrupt:
565
+ console.print()
566
+ console.print("[yellow]Selection cancelled, using default language (en)[/yellow]")
567
+ output_value = "en"
568
+ normalized_lang = "en"
569
+ else:
570
+ output_value = "en"
571
+ normalized_lang = "en"
572
+
508
573
  if debug:
509
574
  console.print(f"[dim]Target directory: {target_dir}[/dim]")
510
575
  console.print(f"[dim]AI assistant: {ai}[/dim]")
511
- console.print(f"[dim]Language: {lang} (normalized: {normalized_lang})[/dim]")
576
+ console.print(
577
+ f"[dim]Language base: {output_value} (messages: {normalized_lang}); overrides: {lang_overrides}[/dim]"
578
+ )
512
579
 
513
580
  # Check if directory exists and handle accordingly
514
581
  if target_dir.exists() and not here and project_name != ".":
@@ -591,7 +658,7 @@ def init(
591
658
  console.print(f"[dim]{translate('cli.init.migration_old_structure', normalized_lang)}[/dim]")
592
659
  console.print(f"[dim]{translate('cli.init.migration_new_structure', normalized_lang)}[/dim]")
593
660
 
594
- if Confirm.ask(translate("cli.init.migration_confirm", normalized_lang), default=True):
661
+ if force or Confirm.ask(translate("cli.init.migration_confirm", normalized_lang), default=True):
595
662
  if migrate_old_commands(claude_dir, old_files):
596
663
  console.print(f"[green]{translate('cli.init.migration_complete', normalized_lang)}[/green]")
597
664
  migration_happened = True
@@ -603,14 +670,14 @@ def init(
603
670
  # Install or update commands (translate after migration if user wants)
604
671
  if migration_happened:
605
672
  # Migration moved files, ask if user wants to update/translate them
606
- if Confirm.ask(translate("cli.init.update_confirm", normalized_lang), default=True):
673
+ if force or Confirm.ask(translate("cli.init.update_confirm", normalized_lang), default=True):
607
674
  count = install_commands_to_subdir(
608
675
  codexspec_commands_dir, templates_dir, force=True, language=normalized_lang
609
676
  )
610
677
  console.print(f"[green]{translate('cli.init.commands_updated', normalized_lang, count=count)}[/green]")
611
678
  elif should_update_commands(codexspec_dir):
612
679
  console.print()
613
- if Confirm.ask(translate("cli.init.update_confirm", normalized_lang), default=True):
680
+ if force or Confirm.ask(translate("cli.init.update_confirm", normalized_lang), default=True):
614
681
  count = install_commands_to_subdir(
615
682
  codexspec_commands_dir, templates_dir, force=True, language=normalized_lang
616
683
  )
@@ -636,53 +703,50 @@ def init(
636
703
  msg = translate("cli.init.created_file", normalized_lang, file=".codexspec/memory/constitution.md")
637
704
  console.print(f"[green]{msg}[/green]")
638
705
 
639
- # Create config.yml with language settings
706
+ # Apply language settings surgically (DEC-001 / CON-005 / REQ-005 / REQ-010).
707
+ # Build the full {key: value} map to write: the output base (if resolved) plus
708
+ # the per-dimension overrides. config.yml is never fully regenerated -- not
709
+ # even under --force -- and keys the user did not specify are always preserved.
640
710
  config_file = codexspec_dir / "config.yml"
641
- if not config_file.exists() or force:
642
- config_content = generate_config_content(
643
- language=normalized_lang,
644
- created=datetime.now().strftime("%Y-%m-%d"),
645
- )
646
- config_file.write_text(config_content, encoding="utf-8")
647
- lang_name = get_language_name(normalized_lang)
648
- msg = translate(
649
- "cli.init.created_file",
650
- normalized_lang,
651
- file=f".codexspec/config.yml (language: {lang_name})",
652
- )
653
- console.print(f"[green]{msg}[/green]")
654
- elif lang != "en": # User explicitly provided --lang (not default)
655
- # Check if commit language differs from selected language
656
- current_commit_lang = get_commit_language(config_file)
657
- if current_commit_lang and current_commit_lang != normalized_lang:
658
- # Prompt user to update commit language
711
+ keys_to_write: dict[str, str] = {}
712
+ if output_value is not None:
713
+ keys_to_write["output"] = output_value
714
+ keys_to_write.update(lang_overrides)
715
+
716
+ wrote_keys: list[str] = []
717
+ if keys_to_write:
718
+ if not config_exists:
719
+ # Fresh project: emit only the chosen keys (sparse config).
720
+ config_content = generate_config_content(
721
+ output=keys_to_write.get("output"),
722
+ interaction=keys_to_write.get("interaction"),
723
+ document=keys_to_write.get("document"),
724
+ commit=keys_to_write.get("commit"),
725
+ created=datetime.now().strftime("%Y-%m-%d"),
726
+ )
727
+ config_file.write_text(config_content, encoding="utf-8")
728
+ wrote_keys = list(keys_to_write.keys())
729
+ lang_name = get_language_name(normalized_lang)
659
730
  msg = translate(
660
- "cli.init.commit_lang_differs",
731
+ "cli.init.created_file",
661
732
  normalized_lang,
662
- current=current_commit_lang,
663
- selected=normalized_lang,
733
+ file=f".codexspec/config.yml (language: {lang_name})",
664
734
  )
665
- if Confirm.ask(msg, default=True):
666
- update_config_language(config_file, normalized_lang)
667
- console.print(
668
- f"[green]{translate('cli.init.commit_lang_updated', normalized_lang, lang=normalized_lang)}[/green]"
669
- )
670
- else:
671
- # Update only output language, keep commit language
672
- update_output_language(config_file, normalized_lang)
673
- console.print(
674
- f"[dim]{translate('cli.init.commit_lang_kept', normalized_lang, lang=current_commit_lang)}[/dim]"
675
- )
735
+ console.print(f"[green]{msg}[/green]")
676
736
  else:
677
- # No difference or commit lang not set, update both
678
- if update_config_language(config_file, normalized_lang):
679
- lang_name = get_language_name(normalized_lang)
680
- msg = translate(
681
- "cli.init.language_updated",
682
- normalized_lang,
683
- lang_name=lang_name,
684
- )
685
- console.print(f"[green]{msg}[/green]")
737
+ # Existing project: update each chosen key in place, preserving the rest.
738
+ for key, value in keys_to_write.items():
739
+ if update_language_field(config_file, key, value):
740
+ wrote_keys.append(key)
741
+
742
+ # Non-blocking notice of which language keys were set (REQ-010).
743
+ if wrote_keys:
744
+ details = ", ".join(f"{k}={keys_to_write[k]}" for k in wrote_keys)
745
+ console.print(f"[dim]{translate('cli.init.language_key_set', normalized_lang, details=details)}[/dim]")
746
+
747
+ # Discoverability hint after a first-time interactive base selection (REQ-009).
748
+ if prompted_base:
749
+ console.print(f"[dim]{translate('cli.init.language_dimensions_hint', normalized_lang)}[/dim]")
686
750
 
687
751
  # Create CLAUDE.md
688
752
  claude_md = target_dir / "CLAUDE.md"