codexspec 0.6.2__tar.gz → 0.6.4__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.
- {codexspec-0.6.2 → codexspec-0.6.4}/PKG-INFO +41 -16
- {codexspec-0.6.2 → codexspec-0.6.4}/README.md +40 -15
- {codexspec-0.6.2 → codexspec-0.6.4}/pyproject.toml +1 -1
- {codexspec-0.6.2 → codexspec-0.6.4}/src/codexspec/__init__.py +181 -71
- {codexspec-0.6.2 → codexspec-0.6.4}/src/codexspec/commands/installer.py +49 -1
- {codexspec-0.6.2 → codexspec-0.6.4}/src/codexspec/i18n.py +71 -117
- {codexspec-0.6.2 → codexspec-0.6.4}/src/codexspec/translator.py +40 -72
- codexspec-0.6.4/templates/translations/de.json +151 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/templates/translations/en.json +2 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/templates/translations/es.json +2 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/templates/translations/fr.json +2 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/templates/translations/ja.json +2 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/templates/translations/ko.json +2 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/templates/translations/pt-BR.json +2 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/templates/translations/zh-CN.json +2 -0
- codexspec-0.6.2/templates/translations/de.json +0 -149
- {codexspec-0.6.2 → codexspec-0.6.4}/.gitignore +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/LICENSE +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/codexspec-icon.svg +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/codexspec-logo-dark.svg +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/codexspec-logo-light.svg +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/scripts/bash/check-i18n-completeness.sh +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/scripts/bash/check-i18n-structure.sh +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/scripts/bash/check-prerequisites.sh +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/scripts/bash/common.sh +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/scripts/bash/create-new-feature.sh +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/scripts/powershell/check-prerequisites.ps1 +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/scripts/powershell/common.ps1 +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/scripts/powershell/create-new-feature.ps1 +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/src/codexspec/commands/__init__.py +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/src/codexspec/idea.md +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/templates/commands/analyze.md +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/templates/commands/checklist.md +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/templates/commands/clarify.md +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/templates/commands/commit-staged.md +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/templates/commands/config.md +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/templates/commands/constitution.md +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/templates/commands/generate-spec.md +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/templates/commands/implement-tasks.md +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/templates/commands/plan-to-tasks.md +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/templates/commands/pr.md +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/templates/commands/quick.md +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/templates/commands/review-code.md +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/templates/commands/review-plan.md +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/templates/commands/review-spec.md +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/templates/commands/review-tasks.md +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/templates/commands/spec-to-plan.md +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/templates/commands/specify.md +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/templates/commands/tasks-to-issues.md +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/templates/docs/checklist-template.md +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/templates/docs/constitution-template.md +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/templates/docs/plan-template-detailed.md +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/templates/docs/plan-template-simple.md +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/templates/docs/requirements-template.md +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/templates/docs/spec-template-detailed.md +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/templates/docs/spec-template-simple.md +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/templates/docs/tasks-template-detailed.md +0 -0
- {codexspec-0.6.2 → codexspec-0.6.4}/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.
|
|
3
|
+
Version: 0.6.4
|
|
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
|
-
| `--
|
|
536
|
-
| `--
|
|
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-
|
|
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
|
-
#
|
|
653
|
+
# Chinese output (sets the output base)
|
|
637
654
|
codexspec init my-project --lang zh-CN
|
|
638
655
|
|
|
639
|
-
#
|
|
640
|
-
codexspec init my-project --lang
|
|
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
|
|
672
|
+
# Change a single dimension
|
|
650
673
|
codexspec config --set-lang zh-CN
|
|
651
|
-
|
|
652
|
-
|
|
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" #
|
|
684
|
-
|
|
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
|
-
| `--
|
|
491
|
-
| `--
|
|
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-
|
|
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
|
-
#
|
|
608
|
+
# Chinese output (sets the output base)
|
|
592
609
|
codexspec init my-project --lang zh-CN
|
|
593
610
|
|
|
594
|
-
#
|
|
595
|
-
codexspec init my-project --lang
|
|
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
|
|
627
|
+
# Change a single dimension
|
|
605
628
|
codexspec config --set-lang zh-CN
|
|
606
|
-
|
|
607
|
-
|
|
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" #
|
|
639
|
-
|
|
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:
|
|
@@ -26,20 +26,19 @@ from .commands.installer import (
|
|
|
26
26
|
install_commands_to_subdir,
|
|
27
27
|
migrate_old_commands,
|
|
28
28
|
should_update_commands,
|
|
29
|
+
update_installed_command_frontmatter,
|
|
29
30
|
)
|
|
30
31
|
from .i18n import (
|
|
31
32
|
generate_config_content,
|
|
32
|
-
|
|
33
|
+
get_explicit_language_key,
|
|
33
34
|
get_interaction_language,
|
|
34
35
|
get_language_name,
|
|
35
36
|
get_supported_languages,
|
|
36
37
|
is_supported_language,
|
|
37
38
|
normalize_locale,
|
|
38
|
-
update_config_language,
|
|
39
39
|
update_language_field,
|
|
40
|
-
update_output_language,
|
|
41
40
|
)
|
|
42
|
-
from .translator import translate
|
|
41
|
+
from .translator import SUPPORTED_LANGUAGES, translate
|
|
43
42
|
|
|
44
43
|
# Version info
|
|
45
44
|
__version__ = "0.6.0"
|
|
@@ -273,6 +272,7 @@ def config(
|
|
|
273
272
|
else:
|
|
274
273
|
console.print("[red]Failed to update language setting[/red]")
|
|
275
274
|
raise typer.Exit(1)
|
|
275
|
+
_rerender_command_frontmatter(config_file)
|
|
276
276
|
return
|
|
277
277
|
|
|
278
278
|
# Handle set commit language
|
|
@@ -351,6 +351,7 @@ def config(
|
|
|
351
351
|
else:
|
|
352
352
|
console.print("[red]Failed to update interaction language setting[/red]")
|
|
353
353
|
raise typer.Exit(1)
|
|
354
|
+
_rerender_command_frontmatter(config_file)
|
|
354
355
|
return
|
|
355
356
|
|
|
356
357
|
# Handle set document language
|
|
@@ -445,13 +446,35 @@ def init(
|
|
|
445
446
|
None,
|
|
446
447
|
"--lang",
|
|
447
448
|
"-l",
|
|
448
|
-
help=
|
|
449
|
+
help=(
|
|
450
|
+
"Output (base) language (e.g., en, zh-CN, ja). interaction/document/commit "
|
|
451
|
+
"fall back to it. Prompted on first-time init if not given in a TTY."
|
|
452
|
+
),
|
|
453
|
+
),
|
|
454
|
+
interaction_lang: Optional[str] = typer.Option(
|
|
455
|
+
None,
|
|
456
|
+
"--interaction-lang",
|
|
457
|
+
help=("Interaction language (LLM dialogue + codexspec CLI output); overrides --lang for this dimension."),
|
|
458
|
+
),
|
|
459
|
+
document_lang: Optional[str] = typer.Option(
|
|
460
|
+
None,
|
|
461
|
+
"--document-lang",
|
|
462
|
+
help="Document language (generated requirements/spec/plan/tasks); overrides --lang.",
|
|
463
|
+
),
|
|
464
|
+
commit_lang: Optional[str] = typer.Option(
|
|
465
|
+
None,
|
|
466
|
+
"--commit-lang",
|
|
467
|
+
help="Commit-message language; overrides --lang.",
|
|
449
468
|
),
|
|
450
469
|
force: bool = typer.Option(
|
|
451
470
|
False,
|
|
452
471
|
"--force",
|
|
453
472
|
"-f",
|
|
454
|
-
help=
|
|
473
|
+
help=(
|
|
474
|
+
"Overwrite existing files and auto-confirm prompts (skips the language "
|
|
475
|
+
"prompt and command-update/migration confirms). Language config is updated "
|
|
476
|
+
"surgically, never fully regenerated."
|
|
477
|
+
),
|
|
455
478
|
),
|
|
456
479
|
no_git: bool = typer.Option(
|
|
457
480
|
False,
|
|
@@ -478,37 +501,104 @@ def init(
|
|
|
478
501
|
codexspec init . --ai claude
|
|
479
502
|
codexspec init --here --ai claude
|
|
480
503
|
codexspec init . --force --ai claude
|
|
504
|
+
codexspec init my-project --interaction-lang en --document-lang zh-CN --commit-lang en
|
|
481
505
|
"""
|
|
482
|
-
#
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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
|
|
506
|
+
# When init() is invoked directly (not via the Typer CLI), typer.Option defaults
|
|
507
|
+
# arrive as OptionInfo sentinels rather than None. Coerce the language params to
|
|
508
|
+
# Optional[str] so the resolution logic below is robust in both call paths.
|
|
509
|
+
lang = lang if isinstance(lang, str) else None
|
|
510
|
+
interaction_lang = interaction_lang if isinstance(interaction_lang, str) else None
|
|
511
|
+
document_lang = document_lang if isinstance(document_lang, str) else None
|
|
512
|
+
commit_lang = commit_lang if isinstance(commit_lang, str) else None
|
|
513
|
+
|
|
514
|
+
# Determine target directory first — needed to detect an existing config.yml
|
|
515
|
+
# before resolving the language base (REQ-007).
|
|
500
516
|
if here or project_name == ".":
|
|
501
517
|
target_dir = Path.cwd()
|
|
502
518
|
elif project_name:
|
|
503
519
|
target_dir = Path.cwd() / project_name
|
|
504
520
|
else:
|
|
505
|
-
console.print(f"[red]{translate('cli.init.error_no_project_name',
|
|
521
|
+
console.print(f"[red]{translate('cli.init.error_no_project_name', 'en')}[/red]")
|
|
506
522
|
raise typer.Exit(1)
|
|
507
523
|
|
|
524
|
+
config_file = target_dir / ".codexspec" / "config.yml"
|
|
525
|
+
config_exists = config_file.exists()
|
|
526
|
+
|
|
527
|
+
def _normalize_lang_option(raw: str) -> str:
|
|
528
|
+
"""Normalize a language flag value; warn (do not error) if unsupported."""
|
|
529
|
+
normalized = normalize_locale(raw)
|
|
530
|
+
if not is_supported_language(normalized):
|
|
531
|
+
console.print(f"[yellow]Warning: '{raw}' is not in the list of commonly supported languages.[/yellow]")
|
|
532
|
+
console.print(
|
|
533
|
+
"It may still work if Claude supports it. "
|
|
534
|
+
"Run [cyan]codexspec config --list-langs[/cyan] to see supported languages."
|
|
535
|
+
)
|
|
536
|
+
return normalized
|
|
537
|
+
|
|
538
|
+
# Per-dimension overrides: each flag maps to exactly one config key, with no
|
|
539
|
+
# CLI-level precedence between them (DEC-001).
|
|
540
|
+
lang_overrides: dict[str, str] = {}
|
|
541
|
+
if interaction_lang is not None:
|
|
542
|
+
lang_overrides["interaction"] = _normalize_lang_option(interaction_lang)
|
|
543
|
+
if document_lang is not None:
|
|
544
|
+
lang_overrides["document"] = _normalize_lang_option(document_lang)
|
|
545
|
+
if commit_lang is not None:
|
|
546
|
+
lang_overrides["commit"] = _normalize_lang_option(commit_lang)
|
|
547
|
+
|
|
548
|
+
# The output base is determinable if --lang was given OR all three specific
|
|
549
|
+
# dimensions are explicit (output is then irrelevant). Otherwise it must be
|
|
550
|
+
# prompted (first-time, TTY) or defaulted (first-time, non-TTY); on an
|
|
551
|
+
# existing config with no flag, everything is preserved (REQ-007).
|
|
552
|
+
output_value: Optional[str] = None
|
|
553
|
+
if lang is not None:
|
|
554
|
+
output_value = _normalize_lang_option(lang)
|
|
555
|
+
base_determinable = output_value is not None or (
|
|
556
|
+
"interaction" in lang_overrides and "document" in lang_overrides and "commit" in lang_overrides
|
|
557
|
+
)
|
|
558
|
+
prompted_base = False
|
|
559
|
+
if base_determinable:
|
|
560
|
+
# Output base already resolved (or None when only dimension flags were
|
|
561
|
+
# given); no prompt needed to resolve it.
|
|
562
|
+
pass
|
|
563
|
+
elif config_exists:
|
|
564
|
+
# Existing project: preserve every unspecified key (REQ-007); no prompt.
|
|
565
|
+
pass
|
|
566
|
+
elif sys.stdin.isatty():
|
|
567
|
+
try:
|
|
568
|
+
output_value = normalize_locale(prompt_language_selection())
|
|
569
|
+
prompted_base = True
|
|
570
|
+
except KeyboardInterrupt:
|
|
571
|
+
console.print()
|
|
572
|
+
console.print("[yellow]Selection cancelled, using default language (en)[/yellow]")
|
|
573
|
+
output_value = "en"
|
|
574
|
+
else:
|
|
575
|
+
output_value = "en"
|
|
576
|
+
|
|
577
|
+
# Render/CLI-message language = the effective interaction language for THIS
|
|
578
|
+
# run (frontmatter descriptions are an interaction-language artifact). This is
|
|
579
|
+
# deliberately decoupled from output_value/prompted_base above, which only
|
|
580
|
+
# drive config writing and the first-time prompt. Precedence: explicit
|
|
581
|
+
# --interaction-lang -> explicitly-configured interaction -> --lang (output
|
|
582
|
+
# base) -> explicitly-configured output -> "en". Existing interaction is
|
|
583
|
+
# deliberately stronger than --lang; existing output is only a fallback when
|
|
584
|
+
# this run did not provide a new base.
|
|
585
|
+
if "interaction" in lang_overrides:
|
|
586
|
+
normalized_lang = lang_overrides["interaction"]
|
|
587
|
+
elif config_exists and (explicit_interaction := get_explicit_language_key(config_file, "interaction")) is not None:
|
|
588
|
+
normalized_lang = explicit_interaction
|
|
589
|
+
elif output_value is not None:
|
|
590
|
+
normalized_lang = output_value
|
|
591
|
+
elif config_exists and (explicit_output := get_explicit_language_key(config_file, "output")) is not None:
|
|
592
|
+
normalized_lang = explicit_output
|
|
593
|
+
else:
|
|
594
|
+
normalized_lang = "en"
|
|
595
|
+
|
|
508
596
|
if debug:
|
|
509
597
|
console.print(f"[dim]Target directory: {target_dir}[/dim]")
|
|
510
598
|
console.print(f"[dim]AI assistant: {ai}[/dim]")
|
|
511
|
-
console.print(
|
|
599
|
+
console.print(
|
|
600
|
+
f"[dim]Language base: {output_value} (messages: {normalized_lang}); overrides: {lang_overrides}[/dim]"
|
|
601
|
+
)
|
|
512
602
|
|
|
513
603
|
# Check if directory exists and handle accordingly
|
|
514
604
|
if target_dir.exists() and not here and project_name != ".":
|
|
@@ -591,7 +681,7 @@ def init(
|
|
|
591
681
|
console.print(f"[dim]{translate('cli.init.migration_old_structure', normalized_lang)}[/dim]")
|
|
592
682
|
console.print(f"[dim]{translate('cli.init.migration_new_structure', normalized_lang)}[/dim]")
|
|
593
683
|
|
|
594
|
-
if Confirm.ask(translate("cli.init.migration_confirm", normalized_lang), default=True):
|
|
684
|
+
if force or Confirm.ask(translate("cli.init.migration_confirm", normalized_lang), default=True):
|
|
595
685
|
if migrate_old_commands(claude_dir, old_files):
|
|
596
686
|
console.print(f"[green]{translate('cli.init.migration_complete', normalized_lang)}[/green]")
|
|
597
687
|
migration_happened = True
|
|
@@ -603,14 +693,14 @@ def init(
|
|
|
603
693
|
# Install or update commands (translate after migration if user wants)
|
|
604
694
|
if migration_happened:
|
|
605
695
|
# Migration moved files, ask if user wants to update/translate them
|
|
606
|
-
if Confirm.ask(translate("cli.init.update_confirm", normalized_lang), default=True):
|
|
696
|
+
if force or Confirm.ask(translate("cli.init.update_confirm", normalized_lang), default=True):
|
|
607
697
|
count = install_commands_to_subdir(
|
|
608
698
|
codexspec_commands_dir, templates_dir, force=True, language=normalized_lang
|
|
609
699
|
)
|
|
610
700
|
console.print(f"[green]{translate('cli.init.commands_updated', normalized_lang, count=count)}[/green]")
|
|
611
701
|
elif should_update_commands(codexspec_dir):
|
|
612
702
|
console.print()
|
|
613
|
-
if Confirm.ask(translate("cli.init.update_confirm", normalized_lang), default=True):
|
|
703
|
+
if force or Confirm.ask(translate("cli.init.update_confirm", normalized_lang), default=True):
|
|
614
704
|
count = install_commands_to_subdir(
|
|
615
705
|
codexspec_commands_dir, templates_dir, force=True, language=normalized_lang
|
|
616
706
|
)
|
|
@@ -636,53 +726,50 @@ def init(
|
|
|
636
726
|
msg = translate("cli.init.created_file", normalized_lang, file=".codexspec/memory/constitution.md")
|
|
637
727
|
console.print(f"[green]{msg}[/green]")
|
|
638
728
|
|
|
639
|
-
#
|
|
729
|
+
# Apply language settings surgically (DEC-001 / CON-005 / REQ-005 / REQ-010).
|
|
730
|
+
# Build the full {key: value} map to write: the output base (if resolved) plus
|
|
731
|
+
# the per-dimension overrides. config.yml is never fully regenerated -- not
|
|
732
|
+
# even under --force -- and keys the user did not specify are always preserved.
|
|
640
733
|
config_file = codexspec_dir / "config.yml"
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
734
|
+
keys_to_write: dict[str, str] = {}
|
|
735
|
+
if output_value is not None:
|
|
736
|
+
keys_to_write["output"] = output_value
|
|
737
|
+
keys_to_write.update(lang_overrides)
|
|
738
|
+
|
|
739
|
+
wrote_keys: list[str] = []
|
|
740
|
+
if keys_to_write:
|
|
741
|
+
if not config_exists:
|
|
742
|
+
# Fresh project: emit only the chosen keys (sparse config).
|
|
743
|
+
config_content = generate_config_content(
|
|
744
|
+
output=keys_to_write.get("output"),
|
|
745
|
+
interaction=keys_to_write.get("interaction"),
|
|
746
|
+
document=keys_to_write.get("document"),
|
|
747
|
+
commit=keys_to_write.get("commit"),
|
|
748
|
+
created=datetime.now().strftime("%Y-%m-%d"),
|
|
749
|
+
)
|
|
750
|
+
config_file.write_text(config_content, encoding="utf-8")
|
|
751
|
+
wrote_keys = list(keys_to_write.keys())
|
|
752
|
+
lang_name = get_language_name(normalized_lang)
|
|
659
753
|
msg = translate(
|
|
660
|
-
"cli.init.
|
|
754
|
+
"cli.init.created_file",
|
|
661
755
|
normalized_lang,
|
|
662
|
-
|
|
663
|
-
selected=normalized_lang,
|
|
756
|
+
file=f".codexspec/config.yml (language: {lang_name})",
|
|
664
757
|
)
|
|
665
|
-
|
|
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
|
-
)
|
|
758
|
+
console.print(f"[green]{msg}[/green]")
|
|
676
759
|
else:
|
|
677
|
-
#
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
760
|
+
# Existing project: update each chosen key in place, preserving the rest.
|
|
761
|
+
for key, value in keys_to_write.items():
|
|
762
|
+
if update_language_field(config_file, key, value):
|
|
763
|
+
wrote_keys.append(key)
|
|
764
|
+
|
|
765
|
+
# Non-blocking notice of which language keys were set (REQ-010).
|
|
766
|
+
if wrote_keys:
|
|
767
|
+
details = ", ".join(f"{k}={keys_to_write[k]}" for k in wrote_keys)
|
|
768
|
+
console.print(f"[dim]{translate('cli.init.language_key_set', normalized_lang, details=details)}[/dim]")
|
|
769
|
+
|
|
770
|
+
# Discoverability hint after a first-time interactive base selection (REQ-009).
|
|
771
|
+
if prompted_base:
|
|
772
|
+
console.print(f"[dim]{translate('cli.init.language_dimensions_hint', normalized_lang)}[/dim]")
|
|
686
773
|
|
|
687
774
|
# Create CLAUDE.md
|
|
688
775
|
claude_md = target_dir / "CLAUDE.md"
|
|
@@ -738,6 +825,29 @@ def init(
|
|
|
738
825
|
console.print(f"[yellow]{translate('cli.init.important_action', normalized_lang)}[/yellow]")
|
|
739
826
|
|
|
740
827
|
|
|
828
|
+
def _rerender_command_frontmatter(config_file: Path) -> None:
|
|
829
|
+
"""Re-render installed command frontmatter in the current interaction language.
|
|
830
|
+
|
|
831
|
+
Called after a ``config`` language change so ``.claude/commands/codexspec/*.md``
|
|
832
|
+
descriptions follow the new interaction language immediately, mirroring what
|
|
833
|
+
``init`` does. No-op when commands are not installed. Languages without a
|
|
834
|
+
pre-translated cache are not re-rendered here (that path would invoke the
|
|
835
|
+
``claude`` CLI once per command); the user is advised to run ``codexspec init``.
|
|
836
|
+
"""
|
|
837
|
+
commands_subdir = Path.cwd() / ".claude" / "commands" / COMMANDS_SUBDIR
|
|
838
|
+
if not commands_subdir.exists():
|
|
839
|
+
return
|
|
840
|
+
language = get_interaction_language(config_file) # re-read AFTER the config write
|
|
841
|
+
if language != "en" and language not in SUPPORTED_LANGUAGES:
|
|
842
|
+
console.print(f"[dim]Run [cyan]codexspec init[/cyan] to update command descriptions for {language}.[/dim]")
|
|
843
|
+
return
|
|
844
|
+
templates_dir = get_templates_dir() / "commands"
|
|
845
|
+
if not templates_dir.exists():
|
|
846
|
+
return
|
|
847
|
+
update_installed_command_frontmatter(commands_subdir, templates_dir, language=language)
|
|
848
|
+
console.print(f"[dim]Updated command descriptions to: {get_language_name(language)}[/dim]")
|
|
849
|
+
|
|
850
|
+
|
|
741
851
|
def _print_command_summary(language: str = "en") -> None:
|
|
742
852
|
"""Print a summary of installed commands grouped by category."""
|
|
743
853
|
metadata = get_commands_metadata()
|
|
@@ -8,7 +8,12 @@ import shutil
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from typing import Optional, TypedDict
|
|
10
10
|
|
|
11
|
-
from codexspec.translator import
|
|
11
|
+
from codexspec.translator import (
|
|
12
|
+
apply_translations_to_template,
|
|
13
|
+
extract_frontmatter_fields,
|
|
14
|
+
load_translation_cache,
|
|
15
|
+
translate_template_frontmatter,
|
|
16
|
+
)
|
|
12
17
|
|
|
13
18
|
# Constants
|
|
14
19
|
COMMANDS_SUBDIR = "codexspec" # Subdirectory name for commands
|
|
@@ -348,6 +353,49 @@ def install_commands_to_subdir(
|
|
|
348
353
|
return installed_count
|
|
349
354
|
|
|
350
355
|
|
|
356
|
+
def update_installed_command_frontmatter(
|
|
357
|
+
target_dir: Path,
|
|
358
|
+
templates_dir: Path,
|
|
359
|
+
language: str = "en",
|
|
360
|
+
translations_dir: Optional[Path] = None,
|
|
361
|
+
) -> int:
|
|
362
|
+
"""Update frontmatter translations for already-installed command files.
|
|
363
|
+
|
|
364
|
+
This preserves command bodies and any non-translated frontmatter while
|
|
365
|
+
refreshing the language-specific ``description`` and ``argument-hint`` fields.
|
|
366
|
+
Missing installed commands are intentionally skipped.
|
|
367
|
+
"""
|
|
368
|
+
if not target_dir.exists() or not templates_dir.exists():
|
|
369
|
+
return 0
|
|
370
|
+
|
|
371
|
+
translation_cache = None
|
|
372
|
+
if language != "en":
|
|
373
|
+
translation_cache = load_translation_cache(language, translations_dir)
|
|
374
|
+
|
|
375
|
+
updated_count = 0
|
|
376
|
+
for template_file in templates_dir.glob("*.md"):
|
|
377
|
+
target_path = target_dir / template_file.name
|
|
378
|
+
if not target_path.exists():
|
|
379
|
+
continue
|
|
380
|
+
|
|
381
|
+
template_content = template_file.read_text(encoding="utf-8")
|
|
382
|
+
template_name = template_file.stem
|
|
383
|
+
rendered_template = translate_template_frontmatter(template_content, template_name, language, translation_cache)
|
|
384
|
+
fields = {
|
|
385
|
+
key: value for key, value in extract_frontmatter_fields(rendered_template).items() if value is not None
|
|
386
|
+
}
|
|
387
|
+
if not fields:
|
|
388
|
+
continue
|
|
389
|
+
|
|
390
|
+
current_content = target_path.read_text(encoding="utf-8")
|
|
391
|
+
updated_content = apply_translations_to_template(current_content, fields)
|
|
392
|
+
if updated_content != current_content:
|
|
393
|
+
target_path.write_text(updated_content, encoding="utf-8")
|
|
394
|
+
updated_count += 1
|
|
395
|
+
|
|
396
|
+
return updated_count
|
|
397
|
+
|
|
398
|
+
|
|
351
399
|
def should_update_commands(codexspec_dir: Path) -> bool:
|
|
352
400
|
"""Check if commands need updating (subdirectory already exists).
|
|
353
401
|
|