codexspec 0.6.4__tar.gz → 0.7.0__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.4 → codexspec-0.7.0}/PKG-INFO +9 -3
- {codexspec-0.6.4 → codexspec-0.7.0}/README.md +8 -2
- {codexspec-0.6.4 → codexspec-0.7.0}/pyproject.toml +1 -1
- {codexspec-0.6.4 → codexspec-0.7.0}/src/codexspec/__init__.py +167 -78
- codexspec-0.7.0/src/codexspec/integrations/__init__.py +20 -0
- codexspec-0.7.0/src/codexspec/integrations/base.py +20 -0
- codexspec-0.7.0/src/codexspec/integrations/claude.py +25 -0
- codexspec-0.7.0/src/codexspec/integrations/codex.py +170 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/templates/commands/implement-tasks.md +98 -5
- {codexspec-0.6.4 → codexspec-0.7.0}/.gitignore +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/LICENSE +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/codexspec-icon.svg +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/codexspec-logo-dark.svg +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/codexspec-logo-light.svg +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/scripts/bash/check-i18n-completeness.sh +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/scripts/bash/check-i18n-structure.sh +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/scripts/bash/check-prerequisites.sh +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/scripts/bash/common.sh +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/scripts/bash/create-new-feature.sh +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/scripts/powershell/check-prerequisites.ps1 +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/scripts/powershell/common.ps1 +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/scripts/powershell/create-new-feature.ps1 +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/src/codexspec/commands/__init__.py +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/src/codexspec/commands/installer.py +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/src/codexspec/i18n.py +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/src/codexspec/idea.md +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/src/codexspec/translator.py +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/templates/commands/analyze.md +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/templates/commands/checklist.md +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/templates/commands/clarify.md +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/templates/commands/commit-staged.md +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/templates/commands/config.md +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/templates/commands/constitution.md +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/templates/commands/generate-spec.md +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/templates/commands/plan-to-tasks.md +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/templates/commands/pr.md +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/templates/commands/quick.md +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/templates/commands/review-code.md +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/templates/commands/review-plan.md +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/templates/commands/review-spec.md +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/templates/commands/review-tasks.md +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/templates/commands/spec-to-plan.md +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/templates/commands/specify.md +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/templates/commands/tasks-to-issues.md +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/templates/docs/checklist-template.md +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/templates/docs/constitution-template.md +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/templates/docs/plan-template-detailed.md +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/templates/docs/plan-template-simple.md +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/templates/docs/requirements-template.md +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/templates/docs/spec-template-detailed.md +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/templates/docs/spec-template-simple.md +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/templates/docs/tasks-template-detailed.md +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/templates/docs/tasks-template-simple.md +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/templates/translations/de.json +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/templates/translations/en.json +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/templates/translations/es.json +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/templates/translations/fr.json +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/templates/translations/ja.json +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/templates/translations/ko.json +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/templates/translations/pt-BR.json +0 -0
- {codexspec-0.6.4 → codexspec-0.7.0}/templates/translations/zh-CN.json +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codexspec
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
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
|
|
@@ -251,6 +251,9 @@ uvx codexspec init my-project
|
|
|
251
251
|
# Initialize in existing project
|
|
252
252
|
cd your-existing-project
|
|
253
253
|
uvx codexspec init . --ai claude
|
|
254
|
+
|
|
255
|
+
# Initialize for Codex CLI
|
|
256
|
+
uvx codexspec init . --ai codex
|
|
254
257
|
```
|
|
255
258
|
|
|
256
259
|
#### Install Development Version from GitHub
|
|
@@ -530,7 +533,7 @@ Implementation follows **conditional TDD workflow**:
|
|
|
530
533
|
| --------------- | ------------------------------------- |
|
|
531
534
|
| `PROJECT_NAME` | Project directory name (`.` or `--here` for current dir) |
|
|
532
535
|
| `--here`, `-h` | Initialize in the current directory |
|
|
533
|
-
| `--ai`, `-a` | AI assistant to use (default: claude) |
|
|
536
|
+
| `--ai`, `-a` | AI assistant to use: `claude`, `codex`, or `both` (default: claude) |
|
|
534
537
|
| `--lang`, `-l` | Output (base) language; interaction/document/commit fall back to it (e.g., en, zh-CN, ja) |
|
|
535
538
|
| `--interaction-lang` | Interaction language (LLM dialogue + CLI output); overrides `--lang` |
|
|
536
539
|
| `--document-lang` | Document language (generated spec/plan/tasks); overrides `--lang` |
|
|
@@ -738,7 +741,10 @@ my-project/
|
|
|
738
741
|
│ └── extensions/ # Custom extensions
|
|
739
742
|
├── .claude/
|
|
740
743
|
│ └── commands/ # Claude Code slash commands
|
|
741
|
-
|
|
744
|
+
├── .agents/
|
|
745
|
+
│ └── skills/ # Codex skills (when initialized with --ai codex or both)
|
|
746
|
+
├── CLAUDE.md # Claude Code context
|
|
747
|
+
└── AGENTS.md # Codex context
|
|
742
748
|
```
|
|
743
749
|
|
|
744
750
|
---
|
|
@@ -206,6 +206,9 @@ uvx codexspec init my-project
|
|
|
206
206
|
# Initialize in existing project
|
|
207
207
|
cd your-existing-project
|
|
208
208
|
uvx codexspec init . --ai claude
|
|
209
|
+
|
|
210
|
+
# Initialize for Codex CLI
|
|
211
|
+
uvx codexspec init . --ai codex
|
|
209
212
|
```
|
|
210
213
|
|
|
211
214
|
#### Install Development Version from GitHub
|
|
@@ -485,7 +488,7 @@ Implementation follows **conditional TDD workflow**:
|
|
|
485
488
|
| --------------- | ------------------------------------- |
|
|
486
489
|
| `PROJECT_NAME` | Project directory name (`.` or `--here` for current dir) |
|
|
487
490
|
| `--here`, `-h` | Initialize in the current directory |
|
|
488
|
-
| `--ai`, `-a` | AI assistant to use (default: claude) |
|
|
491
|
+
| `--ai`, `-a` | AI assistant to use: `claude`, `codex`, or `both` (default: claude) |
|
|
489
492
|
| `--lang`, `-l` | Output (base) language; interaction/document/commit fall back to it (e.g., en, zh-CN, ja) |
|
|
490
493
|
| `--interaction-lang` | Interaction language (LLM dialogue + CLI output); overrides `--lang` |
|
|
491
494
|
| `--document-lang` | Document language (generated spec/plan/tasks); overrides `--lang` |
|
|
@@ -693,7 +696,10 @@ my-project/
|
|
|
693
696
|
│ └── extensions/ # Custom extensions
|
|
694
697
|
├── .claude/
|
|
695
698
|
│ └── commands/ # Claude Code slash commands
|
|
696
|
-
|
|
699
|
+
├── .agents/
|
|
700
|
+
│ └── skills/ # Codex skills (when initialized with --ai codex or both)
|
|
701
|
+
├── CLAUDE.md # Claude Code context
|
|
702
|
+
└── AGENTS.md # Codex context
|
|
697
703
|
```
|
|
698
704
|
|
|
699
705
|
---
|
|
@@ -6,6 +6,7 @@ This toolkit provides a structured approach to software development using
|
|
|
6
6
|
executable specifications that guide AI-assisted implementation.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
+
import re
|
|
9
10
|
import subprocess
|
|
10
11
|
import sys
|
|
11
12
|
from datetime import datetime
|
|
@@ -38,10 +39,11 @@ from .i18n import (
|
|
|
38
39
|
normalize_locale,
|
|
39
40
|
update_language_field,
|
|
40
41
|
)
|
|
42
|
+
from .integrations import get_integrations
|
|
41
43
|
from .translator import SUPPORTED_LANGUAGES, translate
|
|
42
44
|
|
|
43
45
|
# Version info
|
|
44
|
-
__version__ = "0.
|
|
46
|
+
__version__ = "0.7.0"
|
|
45
47
|
__author__ = "CodexSpec Team"
|
|
46
48
|
|
|
47
49
|
# Constitution file path constants
|
|
@@ -471,9 +473,11 @@ def init(
|
|
|
471
473
|
"--force",
|
|
472
474
|
"-f",
|
|
473
475
|
help=(
|
|
474
|
-
"Overwrite
|
|
475
|
-
"prompt and command-update/migration confirms).
|
|
476
|
-
"
|
|
476
|
+
"Overwrite regenerable files (commands/scripts) and auto-confirm prompts "
|
|
477
|
+
"(skips the language prompt and command-update/migration confirms). "
|
|
478
|
+
"User-authored content is preserved: language config is updated surgically "
|
|
479
|
+
"and an existing CLAUDE.md body is never overwritten (only the constitution "
|
|
480
|
+
"@import is prepended if missing)."
|
|
477
481
|
),
|
|
478
482
|
),
|
|
479
483
|
no_git: bool = typer.Option(
|
|
@@ -510,6 +514,14 @@ def init(
|
|
|
510
514
|
interaction_lang = interaction_lang if isinstance(interaction_lang, str) else None
|
|
511
515
|
document_lang = document_lang if isinstance(document_lang, str) else None
|
|
512
516
|
commit_lang = commit_lang if isinstance(commit_lang, str) else None
|
|
517
|
+
ai = ai if isinstance(ai, str) else "claude"
|
|
518
|
+
ai = ai.lower().strip()
|
|
519
|
+
try:
|
|
520
|
+
integrations = get_integrations(ai)
|
|
521
|
+
except ValueError as exc:
|
|
522
|
+
console.print(f"[red]Error:[/red] {exc}")
|
|
523
|
+
raise typer.Exit(1) from exc
|
|
524
|
+
integration_keys = {integration.key for integration in integrations}
|
|
513
525
|
|
|
514
526
|
# Determine target directory first — needed to detect an existing config.yml
|
|
515
527
|
# before resolving the language base (REQ-007).
|
|
@@ -659,65 +671,74 @@ def init(
|
|
|
659
671
|
else:
|
|
660
672
|
console.print("[yellow]Warning: Docs templates directory not found[/yellow]")
|
|
661
673
|
|
|
662
|
-
# Create .claude/commands directory for slash commands
|
|
663
|
-
claude_dir = target_dir / ".claude"
|
|
664
|
-
claude_commands_dir = claude_dir / "commands"
|
|
665
|
-
claude_commands_dir.mkdir(parents=True, exist_ok=True)
|
|
666
|
-
|
|
667
|
-
# Target subdirectory for CodexSpec commands
|
|
668
|
-
codexspec_commands_dir = claude_commands_dir / COMMANDS_SUBDIR
|
|
669
|
-
|
|
670
674
|
# Get templates directory
|
|
671
675
|
templates_dir = get_templates_dir() / "commands"
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
migration_happened = False
|
|
676
|
-
if old_files:
|
|
677
|
-
console.print()
|
|
676
|
+
if not templates_dir.exists():
|
|
677
|
+
# Templates directory is part of the wheel; if it's missing the
|
|
678
|
+
# install is broken and we cannot recover.
|
|
678
679
|
console.print(
|
|
679
|
-
f"[
|
|
680
|
+
f"[red]{translate('cli.init.error_templates_missing', normalized_lang, path=str(templates_dir))}[/red]"
|
|
680
681
|
)
|
|
681
|
-
|
|
682
|
-
console.print(f"[dim]{translate('cli.init.migration_new_structure', normalized_lang)}[/dim]")
|
|
682
|
+
raise typer.Exit(1)
|
|
683
683
|
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
if
|
|
697
|
-
|
|
698
|
-
codexspec_commands_dir, templates_dir, force=True, language=normalized_lang
|
|
699
|
-
)
|
|
700
|
-
console.print(f"[green]{translate('cli.init.commands_updated', normalized_lang, count=count)}[/green]")
|
|
701
|
-
elif should_update_commands(codexspec_dir):
|
|
702
|
-
console.print()
|
|
703
|
-
if force or Confirm.ask(translate("cli.init.update_confirm", normalized_lang), default=True):
|
|
704
|
-
count = install_commands_to_subdir(
|
|
705
|
-
codexspec_commands_dir, templates_dir, force=True, language=normalized_lang
|
|
706
|
-
)
|
|
707
|
-
console.print(f"[green]{translate('cli.init.commands_updated', normalized_lang, count=count)}[/green]")
|
|
708
|
-
else:
|
|
709
|
-
if not templates_dir.exists():
|
|
710
|
-
# Templates directory is part of the wheel; if it's missing the
|
|
711
|
-
# install is broken and we cannot recover.
|
|
684
|
+
if "claude" in integration_keys:
|
|
685
|
+
# Create .claude/commands directory for slash commands
|
|
686
|
+
claude_dir = target_dir / ".claude"
|
|
687
|
+
claude_commands_dir = claude_dir / "commands"
|
|
688
|
+
claude_commands_dir.mkdir(parents=True, exist_ok=True)
|
|
689
|
+
|
|
690
|
+
# Target subdirectory for CodexSpec commands
|
|
691
|
+
codexspec_commands_dir = claude_commands_dir / COMMANDS_SUBDIR
|
|
692
|
+
|
|
693
|
+
# Check for old structure and migrate if needed
|
|
694
|
+
old_files = detect_old_structure(claude_dir)
|
|
695
|
+
migration_happened = False
|
|
696
|
+
if old_files:
|
|
697
|
+
console.print()
|
|
712
698
|
console.print(
|
|
713
|
-
f"[
|
|
699
|
+
f"[yellow]{translate('cli.init.migration_found', normalized_lang, count=len(old_files))}[/yellow]"
|
|
714
700
|
)
|
|
715
|
-
|
|
701
|
+
console.print(f"[dim]{translate('cli.init.migration_old_structure', normalized_lang)}[/dim]")
|
|
702
|
+
console.print(f"[dim]{translate('cli.init.migration_new_structure', normalized_lang)}[/dim]")
|
|
703
|
+
|
|
704
|
+
if force or Confirm.ask(translate("cli.init.migration_confirm", normalized_lang), default=True):
|
|
705
|
+
if migrate_old_commands(claude_dir, old_files):
|
|
706
|
+
console.print(f"[green]{translate('cli.init.migration_complete', normalized_lang)}[/green]")
|
|
707
|
+
migration_happened = True
|
|
708
|
+
else:
|
|
709
|
+
console.print(f"[red]{translate('cli.init.migration_failed', normalized_lang)}[/red]")
|
|
710
|
+
else:
|
|
711
|
+
console.print(f"[dim]{translate('cli.init.migration_skipped', normalized_lang)}[/dim]")
|
|
712
|
+
|
|
713
|
+
# Install or update commands (translate after migration if user wants)
|
|
714
|
+
if migration_happened:
|
|
715
|
+
# Migration moved files, ask if user wants to update/translate them
|
|
716
|
+
if force or Confirm.ask(translate("cli.init.update_confirm", normalized_lang), default=True):
|
|
717
|
+
count = install_commands_to_subdir(
|
|
718
|
+
codexspec_commands_dir, templates_dir, force=True, language=normalized_lang
|
|
719
|
+
)
|
|
720
|
+
console.print(f"[green]{translate('cli.init.commands_updated', normalized_lang, count=count)}[/green]")
|
|
721
|
+
elif should_update_commands(codexspec_dir):
|
|
722
|
+
console.print()
|
|
723
|
+
if force or Confirm.ask(translate("cli.init.update_confirm", normalized_lang), default=True):
|
|
724
|
+
count = install_commands_to_subdir(
|
|
725
|
+
codexspec_commands_dir, templates_dir, force=True, language=normalized_lang
|
|
726
|
+
)
|
|
727
|
+
console.print(f"[green]{translate('cli.init.commands_updated', normalized_lang, count=count)}[/green]")
|
|
728
|
+
else:
|
|
729
|
+
count = install_commands_to_subdir(codexspec_commands_dir, templates_dir, language=normalized_lang)
|
|
730
|
+
cmd_path = f".claude/commands/{COMMANDS_SUBDIR}/"
|
|
731
|
+
msg = translate("cli.init.commands_installed", normalized_lang, count=count, path=cmd_path)
|
|
732
|
+
console.print(f"[green]{msg}[/green]")
|
|
716
733
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
734
|
+
for integration in integrations:
|
|
735
|
+
if integration.key == "claude":
|
|
736
|
+
continue
|
|
737
|
+
count = integration.install(target_dir, templates_dir, force=force, language=normalized_lang)
|
|
738
|
+
console.print(
|
|
739
|
+
f"[green]{translate('cli.init.commands_installed', normalized_lang, count=count, path='.agents/skills/')}"
|
|
740
|
+
"[/green]"
|
|
741
|
+
)
|
|
721
742
|
|
|
722
743
|
# Create constitution template
|
|
723
744
|
constitution_file = codexspec_dir / "memory" / "constitution.md"
|
|
@@ -747,6 +768,7 @@ def init(
|
|
|
747
768
|
commit=keys_to_write.get("commit"),
|
|
748
769
|
created=datetime.now().strftime("%Y-%m-%d"),
|
|
749
770
|
)
|
|
771
|
+
config_content = _set_config_ai_content(config_content, ai)
|
|
750
772
|
config_file.write_text(config_content, encoding="utf-8")
|
|
751
773
|
wrote_keys = list(keys_to_write.keys())
|
|
752
774
|
lang_name = get_language_name(normalized_lang)
|
|
@@ -761,6 +783,8 @@ def init(
|
|
|
761
783
|
for key, value in keys_to_write.items():
|
|
762
784
|
if update_language_field(config_file, key, value):
|
|
763
785
|
wrote_keys.append(key)
|
|
786
|
+
if config_file.exists():
|
|
787
|
+
_update_project_ai(config_file, ai)
|
|
764
788
|
|
|
765
789
|
# Non-blocking notice of which language keys were set (REQ-010).
|
|
766
790
|
if wrote_keys:
|
|
@@ -771,18 +795,23 @@ def init(
|
|
|
771
795
|
if prompted_base:
|
|
772
796
|
console.print(f"[dim]{translate('cli.init.language_dimensions_hint', normalized_lang)}[/dim]")
|
|
773
797
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
798
|
+
if "claude" in integration_keys:
|
|
799
|
+
# Create CLAUDE.md
|
|
800
|
+
# CLAUDE.md is user-authored content (like the constitution), not a
|
|
801
|
+
# regenerable artifact (like commands/scripts). Never overwrite an existing
|
|
802
|
+
# body -- even under --force. --force only auto-confirms the one safe,
|
|
803
|
+
# idempotent change: prepending the constitution @import if it is missing.
|
|
804
|
+
claude_md = target_dir / "CLAUDE.md"
|
|
805
|
+
if not claude_md.exists():
|
|
806
|
+
project_name = target_dir.name
|
|
807
|
+
claude_md.write_text(_get_claude_md_content(project_name), encoding="utf-8")
|
|
808
|
+
console.print(f"[green]{translate('cli.init.created_file', normalized_lang, file='CLAUDE.md')}[/green]")
|
|
809
|
+
else:
|
|
810
|
+
# Existing CLAUDE.md: ensure the compliance @import, never clobber the body.
|
|
811
|
+
if not has_compliance_section(claude_md):
|
|
812
|
+
if force or confirm_add_compliance(normalized_lang):
|
|
813
|
+
prepend_compliance_section(claude_md)
|
|
814
|
+
console.print(f"[green]{translate('cli.init.compliance_added', normalized_lang)}[/green]")
|
|
786
815
|
|
|
787
816
|
# Initialize git if requested
|
|
788
817
|
if not no_git and not (target_dir / ".git").exists():
|
|
@@ -794,7 +823,7 @@ def init(
|
|
|
794
823
|
|
|
795
824
|
# Print success message with command summary
|
|
796
825
|
console.print()
|
|
797
|
-
_print_command_summary(normalized_lang)
|
|
826
|
+
_print_command_summary(normalized_lang, ai=ai)
|
|
798
827
|
|
|
799
828
|
project_nav = project_name if project_name and project_name != "." else "."
|
|
800
829
|
console.print(
|
|
@@ -803,9 +832,9 @@ def init(
|
|
|
803
832
|
f"{translate('cli.init.success_project_dir', normalized_lang, path=str(target_dir))}\n\n"
|
|
804
833
|
f"[bold]{translate('cli.init.next_steps', normalized_lang)}[/bold]\n"
|
|
805
834
|
f"1. {translate('cli.init.next_step_navigate', normalized_lang, path=project_nav)}\n"
|
|
806
|
-
f"2. {
|
|
807
|
-
f"3. {
|
|
808
|
-
f"4. {
|
|
835
|
+
f"2. {_next_step_start(integration_keys, normalized_lang)}\n"
|
|
836
|
+
f"3. {_next_step_invocation(integrations, 'constitution')}\n"
|
|
837
|
+
f"4. {_next_step_invocation(integrations, 'specify')}",
|
|
809
838
|
title=translate("cli.init.success_title", normalized_lang),
|
|
810
839
|
)
|
|
811
840
|
)
|
|
@@ -813,7 +842,7 @@ def init(
|
|
|
813
842
|
# Git management tip
|
|
814
843
|
console.print()
|
|
815
844
|
console.print(f"[bold]{translate('cli.init.tips_header', normalized_lang)}[/bold]")
|
|
816
|
-
console.print(f" - {
|
|
845
|
+
console.print(f" - {_git_tip(integration_keys, normalized_lang)}")
|
|
817
846
|
console.print(f" - {translate('cli.init.tips_list_commands', normalized_lang)}")
|
|
818
847
|
console.print(f" - {translate('cli.init.tips_edit', normalized_lang)}")
|
|
819
848
|
|
|
@@ -822,7 +851,7 @@ def init(
|
|
|
822
851
|
important_header = translate("cli.init.important_header", normalized_lang)
|
|
823
852
|
important_msg = translate("cli.init.important_message", normalized_lang)
|
|
824
853
|
console.print(f"[yellow]{important_header}[/yellow] {important_msg}")
|
|
825
|
-
console.print(f"[yellow]{
|
|
854
|
+
console.print(f"[yellow]{_important_action(integrations, normalized_lang)}[/yellow]")
|
|
826
855
|
|
|
827
856
|
|
|
828
857
|
def _rerender_command_frontmatter(config_file: Path) -> None:
|
|
@@ -848,9 +877,65 @@ def _rerender_command_frontmatter(config_file: Path) -> None:
|
|
|
848
877
|
console.print(f"[dim]Updated command descriptions to: {get_language_name(language)}[/dim]")
|
|
849
878
|
|
|
850
879
|
|
|
851
|
-
def
|
|
880
|
+
def _set_config_ai_content(content: str, ai: str) -> str:
|
|
881
|
+
"""Set project.ai in freshly generated config content."""
|
|
882
|
+
return content.replace(' ai: "claude"', f' ai: "{ai}"')
|
|
883
|
+
|
|
884
|
+
|
|
885
|
+
def _update_project_ai(config_file: Path, ai: str) -> None:
|
|
886
|
+
"""Update the project.ai field in config.yml when present."""
|
|
887
|
+
try:
|
|
888
|
+
content = config_file.read_text(encoding="utf-8")
|
|
889
|
+
updated, count = re.subn(
|
|
890
|
+
r'^(\s*ai:\s*)["\']?[^"\'\n]+["\']?\s*$',
|
|
891
|
+
rf'\g<1>"{ai}"',
|
|
892
|
+
content,
|
|
893
|
+
flags=re.MULTILINE,
|
|
894
|
+
)
|
|
895
|
+
if count:
|
|
896
|
+
config_file.write_text(updated, encoding="utf-8")
|
|
897
|
+
except OSError:
|
|
898
|
+
return
|
|
899
|
+
|
|
900
|
+
|
|
901
|
+
def _next_step_start(integration_keys: set[str], language: str) -> str:
|
|
902
|
+
"""Return a target-aware start instruction."""
|
|
903
|
+
if integration_keys == {"codex"}:
|
|
904
|
+
return "Start Codex in this project directory: codex"
|
|
905
|
+
if integration_keys == {"claude", "codex"}:
|
|
906
|
+
return "Start Claude Code or Codex in this project directory"
|
|
907
|
+
return translate("cli.init.next_step_start_claude", language)
|
|
908
|
+
|
|
909
|
+
|
|
910
|
+
def _next_step_invocation(integrations: list, command_name: str) -> str:
|
|
911
|
+
"""Return a target-aware next-step command invocation."""
|
|
912
|
+
invocation = integrations[0].invocation_for(command_name)
|
|
913
|
+
if command_name == "constitution":
|
|
914
|
+
return f"Use {invocation} to establish project principles"
|
|
915
|
+
return f"Use {invocation} to create your first specification"
|
|
916
|
+
|
|
917
|
+
|
|
918
|
+
def _git_tip(integration_keys: set[str], language: str) -> str:
|
|
919
|
+
"""Return a target-aware git management tip."""
|
|
920
|
+
if integration_keys == {"codex"}:
|
|
921
|
+
return "Add .agents/ to Git if you want Codex skills versioned: git add .agents/"
|
|
922
|
+
if integration_keys == {"claude", "codex"}:
|
|
923
|
+
return "Add .claude/ and .agents/ to Git if you want AI entrypoints versioned"
|
|
924
|
+
return translate("cli.init.tips_git", language)
|
|
925
|
+
|
|
926
|
+
|
|
927
|
+
def _important_action(integrations: list, language: str) -> str:
|
|
928
|
+
"""Return target-aware constitution command reminder."""
|
|
929
|
+
if integrations and integrations[0].key == "codex":
|
|
930
|
+
return f"Run {integrations[0].invocation_for('constitution')} to customize it for your project."
|
|
931
|
+
return translate("cli.init.important_action", language)
|
|
932
|
+
|
|
933
|
+
|
|
934
|
+
def _print_command_summary(language: str = "en", ai: str = "claude") -> None:
|
|
852
935
|
"""Print a summary of installed commands grouped by category."""
|
|
853
936
|
metadata = get_commands_metadata()
|
|
937
|
+
integrations = get_integrations(ai)
|
|
938
|
+
primary = integrations[0]
|
|
854
939
|
|
|
855
940
|
# Group by category
|
|
856
941
|
categories: dict[str, list] = {
|
|
@@ -865,16 +950,20 @@ def _print_command_summary(language: str = "en") -> None:
|
|
|
865
950
|
if cat in categories:
|
|
866
951
|
categories[cat].append(cmd)
|
|
867
952
|
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
953
|
+
if primary.key == "claude":
|
|
954
|
+
summary = translate("cli.init.commands_summary", language, count=len(metadata), path=COMMANDS_SUBDIR)
|
|
955
|
+
console.print(f"[bold]{summary}[/bold]")
|
|
956
|
+
elif ai == "both":
|
|
957
|
+
console.print(f"[bold]Installed {len(metadata)} CodexSpec entrypoints for Claude Code and Codex CLI[/bold]")
|
|
958
|
+
else:
|
|
959
|
+
console.print(f"[bold]Installed {len(metadata)} CodexSpec skills to .agents/skills/[/bold]")
|
|
871
960
|
console.print()
|
|
872
961
|
|
|
873
962
|
for cat, commands in categories.items():
|
|
874
963
|
if commands:
|
|
875
964
|
console.print(f" [bold]{translate(f'cli.init.category_{cat}', language, count=len(commands))}[/bold]")
|
|
876
965
|
for cmd in commands:
|
|
877
|
-
console.print(f" [cyan]{cmd['
|
|
966
|
+
console.print(f" [cyan]{primary.invocation_for(cmd['name'])}[/cyan]")
|
|
878
967
|
console.print()
|
|
879
968
|
|
|
880
969
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""AI coding tool integrations for CodexSpec."""
|
|
2
|
+
|
|
3
|
+
from .base import Integration
|
|
4
|
+
from .claude import ClaudeIntegration
|
|
5
|
+
from .codex import CodexIntegration
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_integrations(ai: str) -> list[Integration]:
|
|
9
|
+
"""Resolve the requested AI target into concrete integrations."""
|
|
10
|
+
normalized = ai.strip().lower()
|
|
11
|
+
if normalized == "claude":
|
|
12
|
+
return [ClaudeIntegration()]
|
|
13
|
+
if normalized == "codex":
|
|
14
|
+
return [CodexIntegration()]
|
|
15
|
+
if normalized == "both":
|
|
16
|
+
return [ClaudeIntegration(), CodexIntegration()]
|
|
17
|
+
raise ValueError(f"Unsupported AI assistant: {ai}")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
__all__ = ["ClaudeIntegration", "CodexIntegration", "Integration", "get_integrations"]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Shared integration protocol."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Protocol
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Integration(Protocol):
|
|
8
|
+
"""Protocol implemented by each AI coding tool integration."""
|
|
9
|
+
|
|
10
|
+
key: str
|
|
11
|
+
display_name: str
|
|
12
|
+
context_file: str
|
|
13
|
+
|
|
14
|
+
def invocation_for(self, command_name: str) -> str:
|
|
15
|
+
"""Return the user-facing invocation for a CodexSpec command."""
|
|
16
|
+
...
|
|
17
|
+
|
|
18
|
+
def install(self, target_dir: Path, templates_dir: Path, *, force: bool = False, language: str = "en") -> int:
|
|
19
|
+
"""Install this integration into target_dir."""
|
|
20
|
+
...
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Claude Code integration."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from codexspec.commands.installer import COMMANDS_SUBDIR, install_commands_to_subdir
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ClaudeIntegration:
|
|
9
|
+
"""Installs CodexSpec as Claude Code slash commands."""
|
|
10
|
+
|
|
11
|
+
key = "claude"
|
|
12
|
+
display_name = "Claude Code"
|
|
13
|
+
context_file = "CLAUDE.md"
|
|
14
|
+
|
|
15
|
+
def invocation_for(self, command_name: str) -> str:
|
|
16
|
+
"""Return the Claude slash command invocation."""
|
|
17
|
+
return f"/codexspec:{command_name}"
|
|
18
|
+
|
|
19
|
+
def commands_dir(self, target_dir: Path) -> Path:
|
|
20
|
+
"""Return the Claude command destination."""
|
|
21
|
+
return target_dir / ".claude" / "commands" / COMMANDS_SUBDIR
|
|
22
|
+
|
|
23
|
+
def install(self, target_dir: Path, templates_dir: Path, *, force: bool = False, language: str = "en") -> int:
|
|
24
|
+
"""Install Claude slash command templates."""
|
|
25
|
+
return install_commands_to_subdir(self.commands_dir(target_dir), templates_dir, force=force, language=language)
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""Codex CLI integration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import yaml
|
|
11
|
+
|
|
12
|
+
from codexspec.commands.installer import get_commands_metadata
|
|
13
|
+
from codexspec.translator import load_translation_cache, translate_template_frontmatter
|
|
14
|
+
|
|
15
|
+
CODEXSPEC_CONTEXT_START = "<!-- CODEXSPEC START -->"
|
|
16
|
+
CODEXSPEC_CONTEXT_END = "<!-- CODEXSPEC END -->"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CodexIntegration:
|
|
20
|
+
"""Installs CodexSpec as Codex skills."""
|
|
21
|
+
|
|
22
|
+
key = "codex"
|
|
23
|
+
display_name = "Codex CLI"
|
|
24
|
+
context_file = "AGENTS.md"
|
|
25
|
+
|
|
26
|
+
def invocation_for(self, command_name: str) -> str:
|
|
27
|
+
"""Return the Codex skill mention invocation."""
|
|
28
|
+
return f"$codexspec:{command_name}"
|
|
29
|
+
|
|
30
|
+
def skills_dir(self, target_dir: Path) -> Path:
|
|
31
|
+
"""Return the Codex skills destination."""
|
|
32
|
+
return target_dir / ".agents" / "skills"
|
|
33
|
+
|
|
34
|
+
def install(self, target_dir: Path, templates_dir: Path, *, force: bool = False, language: str = "en") -> int:
|
|
35
|
+
"""Install Codex skills and the Codex context file."""
|
|
36
|
+
count = self.install_skills(target_dir, templates_dir, force=force, language=language)
|
|
37
|
+
self.ensure_context_file(target_dir)
|
|
38
|
+
return count
|
|
39
|
+
|
|
40
|
+
def install_skills(
|
|
41
|
+
self,
|
|
42
|
+
target_dir: Path,
|
|
43
|
+
templates_dir: Path,
|
|
44
|
+
*,
|
|
45
|
+
force: bool = False,
|
|
46
|
+
language: str = "en",
|
|
47
|
+
) -> int:
|
|
48
|
+
"""Render command templates into Codex SKILL.md files."""
|
|
49
|
+
if not templates_dir.exists():
|
|
50
|
+
return 0
|
|
51
|
+
|
|
52
|
+
descriptions = {cmd["file_name"]: cmd["description"] for cmd in get_commands_metadata()}
|
|
53
|
+
translation_cache = None
|
|
54
|
+
if language != "en":
|
|
55
|
+
translation_cache = load_translation_cache(language)
|
|
56
|
+
|
|
57
|
+
installed_count = 0
|
|
58
|
+
for template_file in sorted(templates_dir.glob("*.md")):
|
|
59
|
+
command_name = template_file.stem
|
|
60
|
+
skill_dir = self.skills_dir(target_dir) / f"codexspec-{command_name}"
|
|
61
|
+
skill_file = skill_dir / "SKILL.md"
|
|
62
|
+
if skill_file.exists() and not force:
|
|
63
|
+
continue
|
|
64
|
+
|
|
65
|
+
content = template_file.read_text(encoding="utf-8")
|
|
66
|
+
content = translate_template_frontmatter(content, command_name, language, translation_cache)
|
|
67
|
+
skill_content = self.render_skill(command_name, content, descriptions.get(template_file.name, ""))
|
|
68
|
+
skill_dir.mkdir(parents=True, exist_ok=True)
|
|
69
|
+
skill_file.write_text(skill_content, encoding="utf-8")
|
|
70
|
+
installed_count += 1
|
|
71
|
+
|
|
72
|
+
return installed_count
|
|
73
|
+
|
|
74
|
+
def ensure_context_file(self, target_dir: Path) -> None:
|
|
75
|
+
"""Create or update AGENTS.md with a managed CodexSpec section."""
|
|
76
|
+
context_path = target_dir / self.context_file
|
|
77
|
+
existing = context_path.read_text(encoding="utf-8") if context_path.exists() else ""
|
|
78
|
+
section = self._context_section()
|
|
79
|
+
|
|
80
|
+
pattern = re.compile(
|
|
81
|
+
rf"{re.escape(CODEXSPEC_CONTEXT_START)}.*?{re.escape(CODEXSPEC_CONTEXT_END)}",
|
|
82
|
+
re.DOTALL,
|
|
83
|
+
)
|
|
84
|
+
if pattern.search(existing):
|
|
85
|
+
updated = pattern.sub(section.strip(), existing)
|
|
86
|
+
elif existing.strip():
|
|
87
|
+
updated = existing.rstrip() + "\n\n" + section
|
|
88
|
+
else:
|
|
89
|
+
updated = "# AGENTS.md\n\n" + section
|
|
90
|
+
|
|
91
|
+
context_path.write_text(updated, encoding="utf-8")
|
|
92
|
+
|
|
93
|
+
def render_skill(self, command_name: str, content: str, fallback_description: str = "") -> str:
|
|
94
|
+
"""Render one command template into a Codex SKILL.md."""
|
|
95
|
+
frontmatter, body = _split_frontmatter(content)
|
|
96
|
+
description = str(frontmatter.get("description") or fallback_description or f"CodexSpec {command_name}")
|
|
97
|
+
body = _render_script_placeholders(body, frontmatter)
|
|
98
|
+
body = _render_codex_body(command_name, body)
|
|
99
|
+
|
|
100
|
+
return f"---\nname: codexspec:{command_name}\ndescription: {_yaml_scalar(description)}\n---\n\n{body.strip()}\n"
|
|
101
|
+
|
|
102
|
+
def _context_section(self) -> str:
|
|
103
|
+
return f"""{CODEXSPEC_CONTEXT_START}
|
|
104
|
+
## CodexSpec
|
|
105
|
+
|
|
106
|
+
This project uses CodexSpec for requirements-first spec-driven development.
|
|
107
|
+
|
|
108
|
+
Use these Codex skills when working on CodexSpec workflows:
|
|
109
|
+
|
|
110
|
+
- `$codexspec:constitution` to create or update project principles.
|
|
111
|
+
- `$codexspec:specify` to capture confirmed requirements.
|
|
112
|
+
- `$codexspec:generate-spec` to produce `spec.md`.
|
|
113
|
+
- `$codexspec:spec-to-plan` to produce `plan.md`.
|
|
114
|
+
- `$codexspec:plan-to-tasks` to produce `tasks.md`.
|
|
115
|
+
- `$codexspec:implement-tasks` to implement approved tasks.
|
|
116
|
+
|
|
117
|
+
Before making workflow decisions, read `.codexspec/memory/constitution.md`.
|
|
118
|
+
{CODEXSPEC_CONTEXT_END}
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _split_frontmatter(content: str) -> tuple[dict[str, Any], str]:
|
|
123
|
+
if not content.startswith("---"):
|
|
124
|
+
return {}, content
|
|
125
|
+
parts = content.split("---", 2)
|
|
126
|
+
if len(parts) < 3:
|
|
127
|
+
return {}, content
|
|
128
|
+
try:
|
|
129
|
+
parsed = yaml.safe_load(parts[1]) or {}
|
|
130
|
+
except yaml.YAMLError:
|
|
131
|
+
parsed = {}
|
|
132
|
+
if not isinstance(parsed, dict):
|
|
133
|
+
parsed = {}
|
|
134
|
+
return parsed, parts[2]
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _render_script_placeholders(body: str, frontmatter: dict[str, Any]) -> str:
|
|
138
|
+
scripts = frontmatter.get("scripts")
|
|
139
|
+
if not isinstance(scripts, dict):
|
|
140
|
+
return body
|
|
141
|
+
preferred = "ps" if sys.platform == "win32" else "sh"
|
|
142
|
+
default_command = str(scripts.get(preferred) or scripts.get("sh") or scripts.get("ps") or "")
|
|
143
|
+
sh_command = str(scripts.get("sh") or default_command)
|
|
144
|
+
ps_command = str(scripts.get("ps") or default_command)
|
|
145
|
+
|
|
146
|
+
lines: list[str] = []
|
|
147
|
+
for line in body.splitlines(keepends=True):
|
|
148
|
+
if "{SCRIPT}" not in line:
|
|
149
|
+
lines.append(line)
|
|
150
|
+
elif "PowerShell:" in line:
|
|
151
|
+
lines.append(line.replace("{SCRIPT}", ps_command))
|
|
152
|
+
elif "Bash:" in line:
|
|
153
|
+
lines.append(line.replace("{SCRIPT}", sh_command))
|
|
154
|
+
else:
|
|
155
|
+
lines.append(line.replace("{SCRIPT}", default_command))
|
|
156
|
+
return "".join(lines)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _render_codex_body(command_name: str, body: str) -> str:
|
|
160
|
+
arg_text = f"the text after the $codexspec:{command_name} skill mention"
|
|
161
|
+
body = body.replace("$ARGUMENTS", arg_text)
|
|
162
|
+
body = body.replace("{ARGS}", arg_text)
|
|
163
|
+
return re.sub(r"/codexspec:([A-Za-z0-9-]+)", r"$codexspec:\1", body)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _yaml_scalar(value: str) -> str:
|
|
167
|
+
if re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9 _.,:/()'\"-]*", value):
|
|
168
|
+
return value
|
|
169
|
+
escaped = value.replace("\\", "\\\\").replace('"', '\\"')
|
|
170
|
+
return f'"{escaped}"'
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|