codexspec 0.6.3__tar.gz → 0.6.5__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.3 → codexspec-0.6.5}/PKG-INFO +1 -1
- {codexspec-0.6.3 → codexspec-0.6.5}/pyproject.toml +1 -1
- {codexspec-0.6.3 → codexspec-0.6.5}/src/codexspec/__init__.py +63 -11
- {codexspec-0.6.3 → codexspec-0.6.5}/src/codexspec/commands/installer.py +49 -1
- {codexspec-0.6.3 → codexspec-0.6.5}/src/codexspec/i18n.py +18 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/src/codexspec/translator.py +34 -72
- {codexspec-0.6.3 → codexspec-0.6.5}/templates/commands/implement-tasks.md +98 -5
- {codexspec-0.6.3 → codexspec-0.6.5}/.gitignore +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/LICENSE +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/README.md +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/codexspec-icon.svg +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/codexspec-logo-dark.svg +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/codexspec-logo-light.svg +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/scripts/bash/check-i18n-completeness.sh +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/scripts/bash/check-i18n-structure.sh +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/scripts/bash/check-prerequisites.sh +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/scripts/bash/common.sh +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/scripts/bash/create-new-feature.sh +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/scripts/powershell/check-prerequisites.ps1 +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/scripts/powershell/common.ps1 +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/scripts/powershell/create-new-feature.ps1 +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/src/codexspec/commands/__init__.py +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/src/codexspec/idea.md +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/templates/commands/analyze.md +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/templates/commands/checklist.md +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/templates/commands/clarify.md +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/templates/commands/commit-staged.md +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/templates/commands/config.md +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/templates/commands/constitution.md +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/templates/commands/generate-spec.md +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/templates/commands/plan-to-tasks.md +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/templates/commands/pr.md +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/templates/commands/quick.md +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/templates/commands/review-code.md +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/templates/commands/review-plan.md +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/templates/commands/review-spec.md +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/templates/commands/review-tasks.md +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/templates/commands/spec-to-plan.md +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/templates/commands/specify.md +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/templates/commands/tasks-to-issues.md +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/templates/docs/checklist-template.md +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/templates/docs/constitution-template.md +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/templates/docs/plan-template-detailed.md +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/templates/docs/plan-template-simple.md +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/templates/docs/requirements-template.md +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/templates/docs/spec-template-detailed.md +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/templates/docs/spec-template-simple.md +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/templates/docs/tasks-template-detailed.md +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/templates/docs/tasks-template-simple.md +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/templates/translations/de.json +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/templates/translations/en.json +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/templates/translations/es.json +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/templates/translations/fr.json +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/templates/translations/ja.json +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/templates/translations/ko.json +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/templates/translations/pt-BR.json +0 -0
- {codexspec-0.6.3 → codexspec-0.6.5}/templates/translations/zh-CN.json +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codexspec
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.5
|
|
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
|
|
@@ -26,9 +26,11 @@ 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,
|
|
33
|
+
get_explicit_language_key,
|
|
32
34
|
get_interaction_language,
|
|
33
35
|
get_language_name,
|
|
34
36
|
get_supported_languages,
|
|
@@ -36,7 +38,7 @@ from .i18n import (
|
|
|
36
38
|
normalize_locale,
|
|
37
39
|
update_language_field,
|
|
38
40
|
)
|
|
39
|
-
from .translator import translate
|
|
41
|
+
from .translator import SUPPORTED_LANGUAGES, translate
|
|
40
42
|
|
|
41
43
|
# Version info
|
|
42
44
|
__version__ = "0.6.0"
|
|
@@ -270,6 +272,7 @@ def config(
|
|
|
270
272
|
else:
|
|
271
273
|
console.print("[red]Failed to update language setting[/red]")
|
|
272
274
|
raise typer.Exit(1)
|
|
275
|
+
_rerender_command_frontmatter(config_file)
|
|
273
276
|
return
|
|
274
277
|
|
|
275
278
|
# Handle set commit language
|
|
@@ -348,6 +351,7 @@ def config(
|
|
|
348
351
|
else:
|
|
349
352
|
console.print("[red]Failed to update interaction language setting[/red]")
|
|
350
353
|
raise typer.Exit(1)
|
|
354
|
+
_rerender_command_frontmatter(config_file)
|
|
351
355
|
return
|
|
352
356
|
|
|
353
357
|
# Handle set document language
|
|
@@ -467,9 +471,11 @@ def init(
|
|
|
467
471
|
"--force",
|
|
468
472
|
"-f",
|
|
469
473
|
help=(
|
|
470
|
-
"Overwrite
|
|
471
|
-
"prompt and command-update/migration confirms).
|
|
472
|
-
"
|
|
474
|
+
"Overwrite regenerable files (commands/scripts) and auto-confirm prompts "
|
|
475
|
+
"(skips the language prompt and command-update/migration confirms). "
|
|
476
|
+
"User-authored content is preserved: language config is updated surgically "
|
|
477
|
+
"and an existing CLAUDE.md body is never overwritten (only the constitution "
|
|
478
|
+
"@import is prepended if missing)."
|
|
473
479
|
),
|
|
474
480
|
),
|
|
475
481
|
no_git: bool = typer.Option(
|
|
@@ -553,21 +559,40 @@ def init(
|
|
|
553
559
|
)
|
|
554
560
|
prompted_base = False
|
|
555
561
|
if base_determinable:
|
|
556
|
-
|
|
562
|
+
# Output base already resolved (or None when only dimension flags were
|
|
563
|
+
# given); no prompt needed to resolve it.
|
|
564
|
+
pass
|
|
557
565
|
elif config_exists:
|
|
558
|
-
|
|
566
|
+
# Existing project: preserve every unspecified key (REQ-007); no prompt.
|
|
567
|
+
pass
|
|
559
568
|
elif sys.stdin.isatty():
|
|
560
569
|
try:
|
|
561
570
|
output_value = normalize_locale(prompt_language_selection())
|
|
562
571
|
prompted_base = True
|
|
563
|
-
normalized_lang = output_value
|
|
564
572
|
except KeyboardInterrupt:
|
|
565
573
|
console.print()
|
|
566
574
|
console.print("[yellow]Selection cancelled, using default language (en)[/yellow]")
|
|
567
575
|
output_value = "en"
|
|
568
|
-
normalized_lang = "en"
|
|
569
576
|
else:
|
|
570
577
|
output_value = "en"
|
|
578
|
+
|
|
579
|
+
# Render/CLI-message language = the effective interaction language for THIS
|
|
580
|
+
# run (frontmatter descriptions are an interaction-language artifact). This is
|
|
581
|
+
# deliberately decoupled from output_value/prompted_base above, which only
|
|
582
|
+
# drive config writing and the first-time prompt. Precedence: explicit
|
|
583
|
+
# --interaction-lang -> explicitly-configured interaction -> --lang (output
|
|
584
|
+
# base) -> explicitly-configured output -> "en". Existing interaction is
|
|
585
|
+
# deliberately stronger than --lang; existing output is only a fallback when
|
|
586
|
+
# this run did not provide a new base.
|
|
587
|
+
if "interaction" in lang_overrides:
|
|
588
|
+
normalized_lang = lang_overrides["interaction"]
|
|
589
|
+
elif config_exists and (explicit_interaction := get_explicit_language_key(config_file, "interaction")) is not None:
|
|
590
|
+
normalized_lang = explicit_interaction
|
|
591
|
+
elif output_value is not None:
|
|
592
|
+
normalized_lang = output_value
|
|
593
|
+
elif config_exists and (explicit_output := get_explicit_language_key(config_file, "output")) is not None:
|
|
594
|
+
normalized_lang = explicit_output
|
|
595
|
+
else:
|
|
571
596
|
normalized_lang = "en"
|
|
572
597
|
|
|
573
598
|
if debug:
|
|
@@ -749,15 +774,19 @@ def init(
|
|
|
749
774
|
console.print(f"[dim]{translate('cli.init.language_dimensions_hint', normalized_lang)}[/dim]")
|
|
750
775
|
|
|
751
776
|
# Create CLAUDE.md
|
|
777
|
+
# CLAUDE.md is user-authored content (like the constitution), not a
|
|
778
|
+
# regenerable artifact (like commands/scripts). Never overwrite an existing
|
|
779
|
+
# body -- even under --force. --force only auto-confirms the one safe,
|
|
780
|
+
# idempotent change: prepending the constitution @import if it is missing.
|
|
752
781
|
claude_md = target_dir / "CLAUDE.md"
|
|
753
|
-
if not claude_md.exists()
|
|
782
|
+
if not claude_md.exists():
|
|
754
783
|
project_name = target_dir.name
|
|
755
784
|
claude_md.write_text(_get_claude_md_content(project_name), encoding="utf-8")
|
|
756
785
|
console.print(f"[green]{translate('cli.init.created_file', normalized_lang, file='CLAUDE.md')}[/green]")
|
|
757
786
|
else:
|
|
758
|
-
#
|
|
787
|
+
# Existing CLAUDE.md: ensure the compliance @import, never clobber the body.
|
|
759
788
|
if not has_compliance_section(claude_md):
|
|
760
|
-
if confirm_add_compliance(normalized_lang):
|
|
789
|
+
if force or confirm_add_compliance(normalized_lang):
|
|
761
790
|
prepend_compliance_section(claude_md)
|
|
762
791
|
console.print(f"[green]{translate('cli.init.compliance_added', normalized_lang)}[/green]")
|
|
763
792
|
|
|
@@ -802,6 +831,29 @@ def init(
|
|
|
802
831
|
console.print(f"[yellow]{translate('cli.init.important_action', normalized_lang)}[/yellow]")
|
|
803
832
|
|
|
804
833
|
|
|
834
|
+
def _rerender_command_frontmatter(config_file: Path) -> None:
|
|
835
|
+
"""Re-render installed command frontmatter in the current interaction language.
|
|
836
|
+
|
|
837
|
+
Called after a ``config`` language change so ``.claude/commands/codexspec/*.md``
|
|
838
|
+
descriptions follow the new interaction language immediately, mirroring what
|
|
839
|
+
``init`` does. No-op when commands are not installed. Languages without a
|
|
840
|
+
pre-translated cache are not re-rendered here (that path would invoke the
|
|
841
|
+
``claude`` CLI once per command); the user is advised to run ``codexspec init``.
|
|
842
|
+
"""
|
|
843
|
+
commands_subdir = Path.cwd() / ".claude" / "commands" / COMMANDS_SUBDIR
|
|
844
|
+
if not commands_subdir.exists():
|
|
845
|
+
return
|
|
846
|
+
language = get_interaction_language(config_file) # re-read AFTER the config write
|
|
847
|
+
if language != "en" and language not in SUPPORTED_LANGUAGES:
|
|
848
|
+
console.print(f"[dim]Run [cyan]codexspec init[/cyan] to update command descriptions for {language}.[/dim]")
|
|
849
|
+
return
|
|
850
|
+
templates_dir = get_templates_dir() / "commands"
|
|
851
|
+
if not templates_dir.exists():
|
|
852
|
+
return
|
|
853
|
+
update_installed_command_frontmatter(commands_subdir, templates_dir, language=language)
|
|
854
|
+
console.print(f"[dim]Updated command descriptions to: {get_language_name(language)}[/dim]")
|
|
855
|
+
|
|
856
|
+
|
|
805
857
|
def _print_command_summary(language: str = "en") -> None:
|
|
806
858
|
"""Print a summary of installed commands grouped by category."""
|
|
807
859
|
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
|
|
|
@@ -290,6 +290,24 @@ def _resolve_language(config_file: Path, primary_key: str) -> str:
|
|
|
290
290
|
return "en"
|
|
291
291
|
|
|
292
292
|
|
|
293
|
+
def get_explicit_language_key(config_file: Optional[Path], key: str) -> Optional[str]:
|
|
294
|
+
"""Return one explicitly configured ``language.<key>`` value, if present.
|
|
295
|
+
|
|
296
|
+
This differs from the public language-resolution helpers because it never
|
|
297
|
+
falls back to ``language.output`` or ``"en"``.
|
|
298
|
+
"""
|
|
299
|
+
path = config_file or _default_config_path()
|
|
300
|
+
if not path.exists():
|
|
301
|
+
return None
|
|
302
|
+
try:
|
|
303
|
+
value = _read_language_key(path.read_text(encoding="utf-8"), key)
|
|
304
|
+
if value:
|
|
305
|
+
return normalize_locale(value) or None
|
|
306
|
+
except (OSError, re.error):
|
|
307
|
+
pass
|
|
308
|
+
return None
|
|
309
|
+
|
|
310
|
+
|
|
293
311
|
def _default_config_path() -> Path:
|
|
294
312
|
return Path.cwd() / ".codexspec" / "config.yml"
|
|
295
313
|
|
|
@@ -7,8 +7,6 @@ frontmatter fields (description, argument-hint).
|
|
|
7
7
|
import json
|
|
8
8
|
import logging
|
|
9
9
|
import re
|
|
10
|
-
import shutil
|
|
11
|
-
import subprocess
|
|
12
10
|
from pathlib import Path
|
|
13
11
|
from typing import Optional
|
|
14
12
|
|
|
@@ -336,87 +334,56 @@ def apply_translations_to_template(content: str, translations: dict) -> str:
|
|
|
336
334
|
Returns:
|
|
337
335
|
Template content with translated frontmatter
|
|
338
336
|
"""
|
|
337
|
+
frontmatter_match = re.match(r"^(---\n)(.*?)(\n---(?:\n|$))", content, re.DOTALL)
|
|
338
|
+
if not frontmatter_match:
|
|
339
|
+
return content
|
|
340
|
+
|
|
341
|
+
prefix, frontmatter, suffix = frontmatter_match.groups()
|
|
342
|
+
|
|
339
343
|
# Replace description
|
|
340
344
|
if "description" in translations:
|
|
341
|
-
|
|
345
|
+
frontmatter = re.sub(
|
|
346
|
+
r"^(description:\s*).+$",
|
|
347
|
+
lambda match: f"{match.group(1)}{translations['description']}",
|
|
348
|
+
frontmatter,
|
|
349
|
+
flags=re.MULTILINE,
|
|
350
|
+
)
|
|
342
351
|
|
|
343
352
|
# Replace argument-hint (handle both single-line and multiline)
|
|
344
353
|
if "argument-hint" in translations:
|
|
345
354
|
new_hint = translations["argument-hint"]
|
|
346
355
|
# Check if original hint is multiline (uses |)
|
|
347
|
-
if re.search(r"^argument-hint:\s*\|",
|
|
356
|
+
if re.search(r"^argument-hint:\s*\|", frontmatter, re.MULTILINE):
|
|
348
357
|
# Build new multiline content with proper indentation
|
|
349
358
|
# Only add indentation to non-empty lines
|
|
350
359
|
hint_lines = new_hint.split("\n")
|
|
351
360
|
indented_lines = [(" " + line) if line else "" for line in hint_lines]
|
|
352
361
|
indented_hint = "\n".join(indented_lines)
|
|
353
362
|
# Replace multiline hint - match the entire multiline block (supports empty lines)
|
|
354
|
-
|
|
363
|
+
frontmatter = re.sub(
|
|
355
364
|
r"(argument-hint:\s*\|)\s*\n((?: .+\n?|\s*\n)+)",
|
|
356
|
-
|
|
357
|
-
|
|
365
|
+
lambda match: f"{match.group(1)}\n{indented_hint}\n",
|
|
366
|
+
frontmatter,
|
|
358
367
|
flags=re.MULTILINE,
|
|
359
368
|
)
|
|
360
369
|
else:
|
|
361
370
|
# Replace single-line hint (with or without quotes)
|
|
362
|
-
if re.search(r'^argument-hint:\s*"',
|
|
363
|
-
|
|
371
|
+
if re.search(r'^argument-hint:\s*"', frontmatter, re.MULTILINE):
|
|
372
|
+
frontmatter = re.sub(
|
|
373
|
+
r'^(argument-hint:\s*)"[^"]+"$',
|
|
374
|
+
lambda match: f'{match.group(1)}"{new_hint}"',
|
|
375
|
+
frontmatter,
|
|
376
|
+
flags=re.MULTILINE,
|
|
377
|
+
)
|
|
364
378
|
else:
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
"""Check if Claude CLI is available.
|
|
372
|
-
|
|
373
|
-
Returns:
|
|
374
|
-
True if claude command is available
|
|
375
|
-
"""
|
|
376
|
-
return shutil.which("claude") is not None
|
|
379
|
+
frontmatter = re.sub(
|
|
380
|
+
r"^(argument-hint:\s*).+$",
|
|
381
|
+
lambda match: f"{match.group(1)}{new_hint}",
|
|
382
|
+
frontmatter,
|
|
383
|
+
flags=re.MULTILINE,
|
|
384
|
+
)
|
|
377
385
|
|
|
378
|
-
|
|
379
|
-
def translate_with_claude_cli(
|
|
380
|
-
texts: dict[str, dict[str, str]], target_lang: str, timeout: int = 60
|
|
381
|
-
) -> Optional[dict[str, dict[str, str]]]:
|
|
382
|
-
"""Translate texts using Claude CLI.
|
|
383
|
-
|
|
384
|
-
Args:
|
|
385
|
-
texts: Dictionary of {command_name: {field: text}}
|
|
386
|
-
target_lang: Target language code
|
|
387
|
-
timeout: Timeout in seconds
|
|
388
|
-
|
|
389
|
-
Returns:
|
|
390
|
-
Translated dictionary or None on failure
|
|
391
|
-
"""
|
|
392
|
-
if not check_claude_cli_available():
|
|
393
|
-
return None
|
|
394
|
-
|
|
395
|
-
# Build prompt
|
|
396
|
-
input_json = json.dumps(texts, ensure_ascii=False, indent=2)
|
|
397
|
-
prompt = f"""Translate the following JSON values to {target_lang}.
|
|
398
|
-
Keep all keys unchanged. Return only valid JSON with the same structure.
|
|
399
|
-
Do not translate technical terms like "spec.md", "plan.md", "TDD", etc.
|
|
400
|
-
|
|
401
|
-
{input_json}"""
|
|
402
|
-
|
|
403
|
-
try:
|
|
404
|
-
result = subprocess.run(["claude", "--print", prompt], capture_output=True, text=True, timeout=timeout)
|
|
405
|
-
|
|
406
|
-
if result.returncode != 0:
|
|
407
|
-
return None
|
|
408
|
-
|
|
409
|
-
# Parse JSON from output
|
|
410
|
-
output = result.stdout.strip()
|
|
411
|
-
# Remove potential markdown code blocks
|
|
412
|
-
if output.startswith("```"):
|
|
413
|
-
output = re.sub(r"^```(?:json)?\n?", "", output)
|
|
414
|
-
output = re.sub(r"\n?```$", "", output)
|
|
415
|
-
|
|
416
|
-
return json.loads(output)
|
|
417
|
-
|
|
418
|
-
except (subprocess.TimeoutExpired, json.JSONDecodeError, OSError):
|
|
419
|
-
return None
|
|
386
|
+
return f"{prefix}{frontmatter}{suffix}{content[frontmatter_match.end() :]}"
|
|
420
387
|
|
|
421
388
|
|
|
422
389
|
def translate_template_frontmatter(
|
|
@@ -445,13 +412,8 @@ def translate_template_frontmatter(
|
|
|
445
412
|
if cache and template_name in cache:
|
|
446
413
|
return apply_translations_to_template(template_content, cache[template_name])
|
|
447
414
|
|
|
448
|
-
#
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
translations = translate_with_claude_cli({template_name: fields}, target_lang)
|
|
453
|
-
if translations and template_name in translations:
|
|
454
|
-
return apply_translations_to_template(template_content, translations[template_name])
|
|
455
|
-
|
|
456
|
-
# Return original if all translation attempts fail
|
|
415
|
+
# No cache for this language: keep the original (English) frontmatter.
|
|
416
|
+
# Command templates are translated at runtime by Claude reading config.yml
|
|
417
|
+
# (see the template "Language Preference" section), never via a subprocess
|
|
418
|
+
# at install time.
|
|
457
419
|
return template_content
|
|
@@ -133,10 +133,103 @@ When encountering problems, create/update `issues.md` in the same directory as `
|
|
|
133
133
|
- **Status**: Blocked / Workaround Found / Needs Discussion
|
|
134
134
|
```
|
|
135
135
|
|
|
136
|
-
### 6.
|
|
136
|
+
### 6. Pre-Review Baseline
|
|
137
137
|
|
|
138
|
-
After all tasks:
|
|
138
|
+
After all tasks are implemented:
|
|
139
139
|
|
|
140
|
-
- Run full test suite
|
|
141
|
-
-
|
|
142
|
-
-
|
|
140
|
+
- Run the full test suite.
|
|
141
|
+
- Confirm the suite is green. This green state is the baseline for the Final
|
|
142
|
+
Code Review Loop; no auto-fix may regress it. If the suite is already red at
|
|
143
|
+
this point, stop and fix the implementation before reviewing.
|
|
144
|
+
|
|
145
|
+
### 7. Final Code Review Loop
|
|
146
|
+
|
|
147
|
+
After the baseline is green, review the implemented code and auto-fix verified
|
|
148
|
+
defects. This mirrors the `review-spec` / `review-plan` / `review-tasks` loops
|
|
149
|
+
the sibling generation commands run.
|
|
150
|
+
|
|
151
|
+
#### 7.1 Determine the Review Target
|
|
152
|
+
|
|
153
|
+
Review only the analyzable code changed by this implementation, not the whole
|
|
154
|
+
repository and not per task. Compute the candidate file set:
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
git diff --name-only $(git merge-base HEAD <main>)..HEAD
|
|
158
|
+
git diff --name-only # uncommitted tracked changes
|
|
159
|
+
git ls-files --others --exclude-standard # untracked files
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
`<main>` is the project's default branch, resolved from `git.main_branches` in
|
|
163
|
+
`.codexspec/config.yml` (default: `main`, `master`, `develop`).
|
|
164
|
+
|
|
165
|
+
Then filter the candidate set:
|
|
166
|
+
|
|
167
|
+
- Keep only analyzable source extensions: `.py .ts .tsx .js .jsx .go .rs .java
|
|
168
|
+
.kt .kts .rb .sh .bash .zsh .c .h .cpp .hpp .cc .cxx .cs .swift .php`.
|
|
169
|
+
- Exclude `.codexspec/specs/` and generated/vendored paths (e.g. `dist/`,
|
|
170
|
+
lockfiles, `.venv/`).
|
|
171
|
+
|
|
172
|
+
If the filtered set is empty (e.g. the implementation produced only docs,
|
|
173
|
+
config, or assets), report "no code to review", skip this loop, and proceed to
|
|
174
|
+
step 8.
|
|
175
|
+
|
|
176
|
+
Fallback: if git is unavailable or the current branch is not a feature branch
|
|
177
|
+
(so the diff base cannot be determined), review the project's primary source
|
|
178
|
+
directory as `/codexspec:review-code` would by default, and explicitly note the
|
|
179
|
+
degraded fidelity (the review may include pre-existing code).
|
|
180
|
+
|
|
181
|
+
#### 7.2 Invoke the Review
|
|
182
|
+
|
|
183
|
+
Invoke `/codexspec:review-code <filtered-paths>`.
|
|
184
|
+
|
|
185
|
+
`/codexspec:review-code` is **review-only**; it produces findings and scores but
|
|
186
|
+
does not edit code. Apply every fix **yourself**, under this command's tool
|
|
187
|
+
scope (which includes `Edit`/`Write` and running the test suite via `Bash`).
|
|
188
|
+
|
|
189
|
+
#### 7.3 Auto-Fix Scope
|
|
190
|
+
|
|
191
|
+
- Auto-fix **CRITICAL, HIGH, and MEDIUM** findings only.
|
|
192
|
+
- **LOW** (suggestion) findings are report-only; never auto-fix them.
|
|
193
|
+
- A **MEDIUM** finding is auto-fixed only when it is grounded in
|
|
194
|
+
`.codexspec/memory/constitution.md` and the confirmed requirements/spec, and
|
|
195
|
+
it concerns **maintainability, readability, or testability**. Ungrounded or
|
|
196
|
+
purely stylistic MEDIUM findings are report-only.
|
|
197
|
+
- Do not auto-fix advisories or design opportunities, and do not introduce any
|
|
198
|
+
new product decision.
|
|
199
|
+
|
|
200
|
+
#### 7.4 Test-Safe Fixes (never ship red)
|
|
201
|
+
|
|
202
|
+
Every fix must be test-safe. A fix that breaks tests is, by definition, an
|
|
203
|
+
incorrect change — it is never shipped and never silently skipped.
|
|
204
|
+
|
|
205
|
+
- **Functional defects** (logic, correctness, security): follow TDD — add a
|
|
206
|
+
failing test that reproduces the defect (red), apply the fix until that test
|
|
207
|
+
passes (green), then refactor while tests stay green.
|
|
208
|
+
- **Non-functional fixes** (refactors for maintainability, readability,
|
|
209
|
+
testability): run the suite before and after the change. If the change turns
|
|
210
|
+
any test red, revert it, confirm the suite is green again, and re-attempt the
|
|
211
|
+
refactor.
|
|
212
|
+
- If a fix cannot be made green after retry, treat it as **unresolved** and stop
|
|
213
|
+
(see 7.5).
|
|
214
|
+
|
|
215
|
+
#### 7.5 Loop Bounds and Stop Conditions
|
|
216
|
+
|
|
217
|
+
- Run at most **two** fix-and-review rounds. (The per-fix TDD retry in 7.4
|
|
218
|
+
happens within a round; the two-round limit bounds overall effort.)
|
|
219
|
+
- Stop when a defect repeats, remains unresolved, or requires a user or
|
|
220
|
+
architecture decision.
|
|
221
|
+
- After each green round, commit the fixes (see step 8).
|
|
222
|
+
|
|
223
|
+
#### 7.6 Terminal Status
|
|
224
|
+
|
|
225
|
+
- If CRITICAL or HIGH defects remain unresolved after the maximum rounds, the
|
|
226
|
+
status is **"needs work"** — do not claim success.
|
|
227
|
+
- Otherwise report the review outcome: scores, items fixed, items deferred, and
|
|
228
|
+
the final test status.
|
|
229
|
+
|
|
230
|
+
### 8. Final Report and Commit
|
|
231
|
+
|
|
232
|
+
- Commit any review-driven changes from step 7 that are not already committed.
|
|
233
|
+
- Report the completion summary: files modified, review status (scores, items
|
|
234
|
+
fixed, items deferred, final test status), and the overall status (success or
|
|
235
|
+
"needs work").
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|