codexspec 0.7.17__tar.gz → 0.7.18__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.7.17 → codexspec-0.7.18}/PKG-INFO +9 -1
- {codexspec-0.7.17 → codexspec-0.7.18}/README.md +8 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/pyproject.toml +1 -1
- {codexspec-0.7.17 → codexspec-0.7.18}/scripts/bash/create-new-feature.sh +11 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/scripts/powershell/create-new-feature.ps1 +28 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/__init__.py +135 -3
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/automation.py +17 -7
- codexspec-0.7.18/src/codexspec/worktrees.py +477 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/commands/analyze.md +72 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/commands/auto-dev.md +72 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/commands/blueprint.md +72 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/commands/checklist.md +72 -0
- codexspec-0.7.18/templates/commands/clarify.md +159 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/commands/commit-staged.md +72 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/commands/config.md +83 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/commands/constitution.md +72 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/commands/debug.md +72 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/commands/distill.md +72 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/commands/evolve.md +72 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/commands/generate-spec.md +72 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/commands/implement-tasks.md +72 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/commands/onboard.md +72 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/commands/plan-to-tasks.md +72 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/commands/pr.md +72 -0
- codexspec-0.7.18/templates/commands/quick.md +141 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/commands/release-notes.md +72 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/commands/reverse-spec.md +79 -7
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/commands/review-code.md +72 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/commands/review-design.md +72 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/commands/review-plan.md +72 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/commands/review-spec.md +72 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/commands/review-tasks.md +72 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/commands/spec-to-design.md +72 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/commands/spec-to-plan.md +72 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/commands/specify.md +76 -1
- codexspec-0.7.18/templates/commands/tasks-to-issues.md +216 -0
- codexspec-0.7.17/templates/commands/clarify.md +0 -87
- codexspec-0.7.17/templates/commands/quick.md +0 -69
- codexspec-0.7.17/templates/commands/tasks-to-issues.md +0 -144
- {codexspec-0.7.17 → codexspec-0.7.18}/.gitignore +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/LICENSE +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/codexspec-icon.svg +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/codexspec-logo-dark.svg +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/codexspec-logo-light.svg +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/scripts/bash/check-i18n-completeness.sh +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/scripts/bash/check-i18n-structure.sh +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/scripts/bash/check-prerequisites.sh +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/scripts/bash/common.sh +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/scripts/bash/review-context.sh +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/scripts/powershell/check-prerequisites.ps1 +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/scripts/powershell/common.ps1 +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/scripts/powershell/review-context.ps1 +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/blueprint.py +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/commands/__init__.py +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/commands/installer.py +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/distill_review/__init__.py +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/distill_review/assets/app.js +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/distill_review/assets/i18n/ar.json +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/distill_review/assets/i18n/de.json +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/distill_review/assets/i18n/en.json +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/distill_review/assets/i18n/es.json +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/distill_review/assets/i18n/fr.json +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/distill_review/assets/i18n/hi.json +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/distill_review/assets/i18n/it.json +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/distill_review/assets/i18n/ja.json +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/distill_review/assets/i18n/ko.json +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/distill_review/assets/i18n/pt.json +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/distill_review/assets/i18n/ru.json +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/distill_review/assets/i18n/zh-CN.json +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/distill_review/assets/i18n/zh-TW.json +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/distill_review/assets/index.html +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/distill_review/assets/styles.css +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/distill_review/domain.py +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/distill_review/lease.py +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/distill_review/models.py +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/distill_review/records.py +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/distill_review/server.py +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/distill_review/session.py +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/distill_review/terminal.py +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/distill_review/transaction.py +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/i18n.py +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/idea.md +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/integrations/__init__.py +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/integrations/base.py +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/integrations/claude.py +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/integrations/codex.py +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/profile.py +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/src/codexspec/translator.py +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/docs/checklist-template.md +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/docs/constitution-template.md +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/docs/design-template.md +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/docs/plan-template-detailed.md +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/docs/plan-template-simple.md +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/docs/requirements-template.md +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/docs/spec-template-detailed.md +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/docs/spec-template-simple.md +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/docs/tasks-template-detailed.md +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/docs/tasks-template-simple.md +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/translations/de.json +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/translations/en.json +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/translations/es.json +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/translations/fr.json +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/translations/ja.json +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/translations/ko.json +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/translations/pt-BR.json +0 -0
- {codexspec-0.7.17 → codexspec-0.7.18}/templates/translations/zh-CN.json +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: codexspec
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.18
|
|
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
|
|
@@ -565,6 +565,7 @@ Implementation follows **conditional TDD workflow**:
|
|
|
565
565
|
| `--list-langs` | List all supported languages |
|
|
566
566
|
| `--auto-next` | Toggle/set `workflow.auto_next` (bare toggles; or on/off) |
|
|
567
567
|
| `--auto-distill` | Toggle/set `workflow.auto_distill` (default on; bare toggles; or on/off) |
|
|
568
|
+
| `--worktrees` | Toggle/set checkout-local worktree isolation (default on; bare toggles; or on/off) |
|
|
568
569
|
|
|
569
570
|
</details>
|
|
570
571
|
|
|
@@ -837,3 +838,10 @@ MIT License - see [LICENSE](LICENSE) for details.
|
|
|
837
838
|
|
|
838
839
|
- Inspired by [GitHub spec-kit](https://github.com/github/spec-kit)
|
|
839
840
|
- Built for [Claude Code](https://claude.ai/code)
|
|
841
|
+
|
|
842
|
+
Ordinary features develop in separate worktrees under the main checkout's sibling
|
|
843
|
+
`<repository-name>-codexspec-worktrees/`, starting before the first requirements write.
|
|
844
|
+
Blueprint and auto-dev retain their fixed shared worktree. Standalone project edits
|
|
845
|
+
use a maintenance worktree; settings take effect per checkout and reach other checkouts
|
|
846
|
+
through Git integration. See [worktree development](docs/en/user-guide/workflow.md#worktree-development)
|
|
847
|
+
and [configuration](docs/en/reference/configuration.md#workflowworktrees).
|
|
@@ -520,6 +520,7 @@ Implementation follows **conditional TDD workflow**:
|
|
|
520
520
|
| `--list-langs` | List all supported languages |
|
|
521
521
|
| `--auto-next` | Toggle/set `workflow.auto_next` (bare toggles; or on/off) |
|
|
522
522
|
| `--auto-distill` | Toggle/set `workflow.auto_distill` (default on; bare toggles; or on/off) |
|
|
523
|
+
| `--worktrees` | Toggle/set checkout-local worktree isolation (default on; bare toggles; or on/off) |
|
|
523
524
|
|
|
524
525
|
</details>
|
|
525
526
|
|
|
@@ -792,3 +793,10 @@ MIT License - see [LICENSE](LICENSE) for details.
|
|
|
792
793
|
|
|
793
794
|
- Inspired by [GitHub spec-kit](https://github.com/github/spec-kit)
|
|
794
795
|
- Built for [Claude Code](https://claude.ai/code)
|
|
796
|
+
|
|
797
|
+
Ordinary features develop in separate worktrees under the main checkout's sibling
|
|
798
|
+
`<repository-name>-codexspec-worktrees/`, starting before the first requirements write.
|
|
799
|
+
Blueprint and auto-dev retain their fixed shared worktree. Standalone project edits
|
|
800
|
+
use a maintenance worktree; settings take effect per checkout and reach other checkouts
|
|
801
|
+
through Git integration. See [worktree development](docs/en/user-guide/workflow.md#worktree-development)
|
|
802
|
+
and [configuration](docs/en/reference/configuration.md#workflowworktrees).
|
|
@@ -50,6 +50,17 @@ if [ -z "$FEATURE_SUFFIX" ]; then
|
|
|
50
50
|
exit 1
|
|
51
51
|
fi
|
|
52
52
|
|
|
53
|
+
# Resolve the switch before creating directories or changing Git state.
|
|
54
|
+
if ! command_exists codexspec; then
|
|
55
|
+
log_error "CodexSpec runtime is unavailable. Install codexspec before creating a feature."
|
|
56
|
+
exit 1
|
|
57
|
+
fi
|
|
58
|
+
WORKTREE_SETTING=$(codexspec _worktree-helper setting) || exit $?
|
|
59
|
+
case "$WORKTREE_SETTING" in
|
|
60
|
+
*'"enabled": false'*) ;; # Explicit opt-out retains the original in-place flow below.
|
|
61
|
+
*) exec codexspec _worktree-helper create --name "$FEATURE_SUFFIX" ;;
|
|
62
|
+
esac
|
|
63
|
+
|
|
53
64
|
TIMESTAMP=$(date +"%Y-%m%d-%H%M")
|
|
54
65
|
RANDOM_SUFFIX=$(LC_ALL=C tr -dc 'a-z0-9' </dev/urandom | head -c 2)
|
|
55
66
|
FEATURE_ID="${TIMESTAMP}${RANDOM_SUFFIX}"
|
|
@@ -132,6 +132,34 @@ if (-not $branchSuffix) {
|
|
|
132
132
|
exit 1
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
# Resolve isolation before any feature directory or branch mutation.
|
|
136
|
+
if (-not (Get-Command codexspec -ErrorAction SilentlyContinue)) {
|
|
137
|
+
Write-Error "CodexSpec runtime is unavailable. Install codexspec before creating a feature."
|
|
138
|
+
exit 1
|
|
139
|
+
}
|
|
140
|
+
$settingOutput = & codexspec _worktree-helper setting
|
|
141
|
+
if ($LASTEXITCODE -ne 0) { Write-Output $settingOutput; exit $LASTEXITCODE }
|
|
142
|
+
$setting = $settingOutput | ConvertFrom-Json
|
|
143
|
+
if ($setting.enabled) {
|
|
144
|
+
$workspaceOutput = & codexspec _worktree-helper create --name $branchSuffix
|
|
145
|
+
$workspaceExit = $LASTEXITCODE
|
|
146
|
+
if ($workspaceExit -ne 0) { Write-Output $workspaceOutput; exit $workspaceExit }
|
|
147
|
+
$workspace = $workspaceOutput | ConvertFrom-Json
|
|
148
|
+
$env:CODEXSPEC_FEATURE = $workspace.branch
|
|
149
|
+
$result = [PSCustomObject]@{
|
|
150
|
+
BRANCH_NAME = $workspace.branch
|
|
151
|
+
REQUIREMENTS_FILE = $workspace.requirements_file
|
|
152
|
+
FEATURE_ID = $workspace.feature_id
|
|
153
|
+
WORKTREE_PATH = $workspace.workspace
|
|
154
|
+
HAS_GIT = $true
|
|
155
|
+
STATUS = $workspace.status
|
|
156
|
+
WARNINGS = $workspace.warnings
|
|
157
|
+
}
|
|
158
|
+
if ($Json) { $result | ConvertTo-Json -Compress }
|
|
159
|
+
else { $result | Format-List | Out-String | Write-Output }
|
|
160
|
+
exit 0
|
|
161
|
+
}
|
|
162
|
+
|
|
135
163
|
$specsDir = Join-Path $repoRoot '.codexspec/specs'
|
|
136
164
|
New-Item -ItemType Directory -Path $specsDir -Force | Out-Null
|
|
137
165
|
|
|
@@ -14,7 +14,7 @@ import sys
|
|
|
14
14
|
import webbrowser
|
|
15
15
|
from datetime import datetime
|
|
16
16
|
from pathlib import Path
|
|
17
|
-
from typing import Optional
|
|
17
|
+
from typing import Any, Optional
|
|
18
18
|
|
|
19
19
|
import typer
|
|
20
20
|
from rich.console import Console
|
|
@@ -64,9 +64,18 @@ from .i18n import (
|
|
|
64
64
|
from .integrations import get_integrations
|
|
65
65
|
from .profile import ensure_profile_scaffold, inject_profile_block
|
|
66
66
|
from .translator import SUPPORTED_LANGUAGES, translate
|
|
67
|
+
from .worktrees import (
|
|
68
|
+
MAINTENANCE_NAME,
|
|
69
|
+
WorkspaceManager,
|
|
70
|
+
feature_name,
|
|
71
|
+
read_worktrees,
|
|
72
|
+
validate_write_path,
|
|
73
|
+
write_destination,
|
|
74
|
+
write_worktrees,
|
|
75
|
+
)
|
|
67
76
|
|
|
68
77
|
# Version info
|
|
69
|
-
__version__ = "0.7.
|
|
78
|
+
__version__ = "0.7.18"
|
|
70
79
|
__author__ = "CodexSpec Team"
|
|
71
80
|
|
|
72
81
|
# Constitution file path constants
|
|
@@ -82,6 +91,51 @@ app = typer.Typer(
|
|
|
82
91
|
console = Console()
|
|
83
92
|
|
|
84
93
|
|
|
94
|
+
@app.command("_worktree-helper", hidden=True)
|
|
95
|
+
def worktree_helper(
|
|
96
|
+
action: str = typer.Argument(...),
|
|
97
|
+
name: Optional[str] = typer.Option(None, "--name"),
|
|
98
|
+
feature: Optional[str] = typer.Option(None, "--feature"),
|
|
99
|
+
verification: Optional[Path] = typer.Option(None, "--verification"),
|
|
100
|
+
) -> None:
|
|
101
|
+
"""Return structured workspace facts; never emit shell code for callers to evaluate."""
|
|
102
|
+
recovery: dict[str, Any] = {}
|
|
103
|
+
result: dict[str, Any]
|
|
104
|
+
try:
|
|
105
|
+
from .worktrees import checkout_root
|
|
106
|
+
|
|
107
|
+
root = checkout_root(Path.cwd())
|
|
108
|
+
if action == "setting":
|
|
109
|
+
result = {"enabled": read_worktrees(root / ".codexspec/config.yml")}
|
|
110
|
+
elif action == "route":
|
|
111
|
+
result = {"status": "ready", "workspace": str(write_destination(root))}
|
|
112
|
+
elif action == "create" and not read_worktrees(root / ".codexspec/config.yml"):
|
|
113
|
+
result = {"status": "disabled", "workspace": str(root.resolve())}
|
|
114
|
+
else:
|
|
115
|
+
manager = WorkspaceManager(root)
|
|
116
|
+
if action == "create":
|
|
117
|
+
if bool(name) == bool(feature):
|
|
118
|
+
raise AutomationError("expected_name_or_feature")
|
|
119
|
+
identity = feature if feature else feature_name(name or "")
|
|
120
|
+
branch, path, _ = manager._identity(identity)
|
|
121
|
+
recovery = {"branch": branch, "workspace": str(path)}
|
|
122
|
+
result = manager.create(identity, reuse=feature is not None)
|
|
123
|
+
elif action == "resolve" and feature:
|
|
124
|
+
result = manager.resolve(feature)
|
|
125
|
+
elif action == "maintenance":
|
|
126
|
+
result = manager.create(MAINTENANCE_NAME)
|
|
127
|
+
elif action == "finish" and feature and verification:
|
|
128
|
+
result = manager.finish(feature, json.loads(verification.read_text(encoding="utf-8")))
|
|
129
|
+
else:
|
|
130
|
+
raise AutomationError("invalid_workspace_action", action)
|
|
131
|
+
print(json.dumps(result, ensure_ascii=False))
|
|
132
|
+
if result.get("status") in {"creating", "merge_requires_resolution", "merge_requires_verification"}:
|
|
133
|
+
raise typer.Exit(3)
|
|
134
|
+
except (AutomationError, OSError, ValueError) as exc:
|
|
135
|
+
print(json.dumps({**recovery, "status": "blocked", "error": str(exc)}, ensure_ascii=False))
|
|
136
|
+
raise typer.Exit(1) from exc
|
|
137
|
+
|
|
138
|
+
|
|
85
139
|
@app.command("_distill-review-helper", hidden=True)
|
|
86
140
|
def distill_review_helper(
|
|
87
141
|
project_root: Path = typer.Option(Path("."), "--project-root"),
|
|
@@ -99,6 +153,17 @@ def distill_review_helper(
|
|
|
99
153
|
if not root.is_dir() or not (root / ".codexspec" / "profile").is_dir():
|
|
100
154
|
_review_result(language, {"status": "invalid_input", "error": "invalid_project_root"})
|
|
101
155
|
raise typer.Exit(2)
|
|
156
|
+
try:
|
|
157
|
+
root = write_destination(root)
|
|
158
|
+
except (AutomationError, OSError) as exc:
|
|
159
|
+
_review_result(language, {"status": "invalid_input", "error": str(exc)})
|
|
160
|
+
raise typer.Exit(2) from exc
|
|
161
|
+
language = get_interaction_language(root / ".codexspec" / "config.yml")
|
|
162
|
+
if not (root / ".codexspec" / "profile").is_dir():
|
|
163
|
+
_review_result(
|
|
164
|
+
language, {"status": "invalid_input", "error": "missing_destination_profile", "project_root": str(root)}
|
|
165
|
+
)
|
|
166
|
+
raise typer.Exit(2)
|
|
102
167
|
store = SessionStore(root)
|
|
103
168
|
lease = ReviewLease(root)
|
|
104
169
|
try:
|
|
@@ -526,6 +591,9 @@ def config(
|
|
|
526
591
|
"--auto-distill",
|
|
527
592
|
help="Toggle workflow.auto_distill (bare), or set it (on/off|true/false|1/0|yes/no).",
|
|
528
593
|
),
|
|
594
|
+
worktrees: Optional[str] = typer.Option(
|
|
595
|
+
None, "--worktrees", help="Toggle checkout-local worktree isolation (default on), or set on/off."
|
|
596
|
+
),
|
|
529
597
|
) -> None:
|
|
530
598
|
"""
|
|
531
599
|
View or modify CodexSpec project configuration.
|
|
@@ -561,6 +629,58 @@ def config(
|
|
|
561
629
|
console.print("Run [cyan]codexspec init[/cyan] to create a new project.")
|
|
562
630
|
raise typer.Exit(1)
|
|
563
631
|
|
|
632
|
+
# Validate values before creating any destination workspace.
|
|
633
|
+
for value, sentinel, option in (
|
|
634
|
+
(auto_next, _AUTO_NEXT_SENTINEL, "--auto-next"),
|
|
635
|
+
(auto_distill, _AUTO_DISTILL_SENTINEL, "--auto-distill"),
|
|
636
|
+
(worktrees, "__toggle_worktrees__", "--worktrees"),
|
|
637
|
+
):
|
|
638
|
+
if value is not None and value != sentinel:
|
|
639
|
+
try:
|
|
640
|
+
parse_auto_next_value(value)
|
|
641
|
+
except ValueError:
|
|
642
|
+
console.print(f"[red]Invalid {option} value:[/red] {value!r}")
|
|
643
|
+
raise typer.Exit(1)
|
|
644
|
+
modifying = any(
|
|
645
|
+
value is not None
|
|
646
|
+
for value in (
|
|
647
|
+
auto_next,
|
|
648
|
+
auto_distill,
|
|
649
|
+
worktrees,
|
|
650
|
+
set_lang,
|
|
651
|
+
set_commit_lang,
|
|
652
|
+
set_interaction_lang,
|
|
653
|
+
set_document_lang,
|
|
654
|
+
)
|
|
655
|
+
)
|
|
656
|
+
if modifying:
|
|
657
|
+
try:
|
|
658
|
+
isolated = read_worktrees(config_file)
|
|
659
|
+
destination = write_destination(config_file.parent.parent)
|
|
660
|
+
config_file = destination / ".codexspec" / "config.yml"
|
|
661
|
+
if isolated:
|
|
662
|
+
validate_write_path(destination, config_file)
|
|
663
|
+
if set_lang is not None or set_interaction_lang is not None:
|
|
664
|
+
_validate_command_frontmatter(destination)
|
|
665
|
+
if not config_file.is_file():
|
|
666
|
+
raise AutomationError("missing_destination_config", str(config_file))
|
|
667
|
+
console.print(f"Configuration destination: {config_file}", markup=False, soft_wrap=True)
|
|
668
|
+
console.print("Changes apply to this checkout; other checkouts receive them through Git integration.")
|
|
669
|
+
except (AutomationError, OSError) as exc:
|
|
670
|
+
console.print(str(exc), markup=False)
|
|
671
|
+
raise typer.Exit(1) from exc
|
|
672
|
+
if worktrees is not None:
|
|
673
|
+
target = (
|
|
674
|
+
not read_worktrees(config_file) if worktrees == "__toggle_worktrees__" else parse_auto_next_value(worktrees)
|
|
675
|
+
)
|
|
676
|
+
try:
|
|
677
|
+
write_worktrees(config_file, target)
|
|
678
|
+
except (AutomationError, OSError) as exc:
|
|
679
|
+
console.print(str(exc), markup=False)
|
|
680
|
+
raise typer.Exit(1) from exc
|
|
681
|
+
console.print(f"workflow.worktrees = {str(target).lower()}")
|
|
682
|
+
return
|
|
683
|
+
|
|
564
684
|
# Handle auto-next toggle/set
|
|
565
685
|
if auto_next is not None:
|
|
566
686
|
if auto_next == _AUTO_NEXT_SENTINEL:
|
|
@@ -740,6 +860,7 @@ def config(
|
|
|
740
860
|
return
|
|
741
861
|
|
|
742
862
|
# Display current configuration
|
|
863
|
+
console.print(f"Effective workflow.worktrees: {str(read_worktrees(config_file)).lower()}")
|
|
743
864
|
console.print(
|
|
744
865
|
Panel(
|
|
745
866
|
config_file.read_text(encoding="utf-8"),
|
|
@@ -1228,6 +1349,15 @@ def init(
|
|
|
1228
1349
|
console.print(f"[yellow]{_important_action(integrations, normalized_lang)}[/yellow]")
|
|
1229
1350
|
|
|
1230
1351
|
|
|
1352
|
+
def _validate_command_frontmatter(root: Path) -> None:
|
|
1353
|
+
commands = root / ".claude" / "commands" / COMMANDS_SUBDIR
|
|
1354
|
+
validate_write_path(root, commands)
|
|
1355
|
+
if commands.exists() and not commands.is_dir():
|
|
1356
|
+
raise AutomationError("unsafe_write_path", str(commands))
|
|
1357
|
+
for command in commands.glob("*.md"):
|
|
1358
|
+
validate_write_path(root, command)
|
|
1359
|
+
|
|
1360
|
+
|
|
1231
1361
|
def _rerender_command_frontmatter(config_file: Path) -> None:
|
|
1232
1362
|
"""Re-render installed command frontmatter in the current interaction language.
|
|
1233
1363
|
|
|
@@ -1237,7 +1367,7 @@ def _rerender_command_frontmatter(config_file: Path) -> None:
|
|
|
1237
1367
|
pre-translated cache are not re-rendered here (that path would invoke the
|
|
1238
1368
|
``claude`` CLI once per command); the user is advised to run ``codexspec init``.
|
|
1239
1369
|
"""
|
|
1240
|
-
commands_subdir =
|
|
1370
|
+
commands_subdir = config_file.parent.parent / ".claude" / "commands" / COMMANDS_SUBDIR
|
|
1241
1371
|
if not commands_subdir.exists():
|
|
1242
1372
|
return
|
|
1243
1373
|
language = get_interaction_language(config_file) # re-read AFTER the config write
|
|
@@ -1919,6 +2049,8 @@ def main() -> None:
|
|
|
1919
2049
|
sys.argv = _normalize_auto_next_argv(sys.argv)
|
|
1920
2050
|
if "--auto-distill" in sys.argv:
|
|
1921
2051
|
sys.argv = _normalize_auto_distill_argv(sys.argv)
|
|
2052
|
+
if "--worktrees" in sys.argv:
|
|
2053
|
+
sys.argv = _normalize_optional_value_argv(sys.argv, "--worktrees", "__toggle_worktrees__")
|
|
1922
2054
|
app()
|
|
1923
2055
|
|
|
1924
2056
|
|
|
@@ -173,8 +173,12 @@ def locate_repository(cwd: Path | str, *, runner: GitRunner | None = None) -> Re
|
|
|
173
173
|
current = Path(cwd).resolve()
|
|
174
174
|
top = git.run(current, "rev-parse", "--show-toplevel", check=False)
|
|
175
175
|
if top.returncode != 0:
|
|
176
|
-
|
|
177
|
-
|
|
176
|
+
bare = git.run(current, "rev-parse", "--is-bare-repository", check=False)
|
|
177
|
+
if bare.returncode != 0 or bare.stdout.strip() != "true":
|
|
178
|
+
raise AutomationError("not_git_repository")
|
|
179
|
+
invoking_root = Path(git.run(current, "rev-parse", "--absolute-git-dir").stdout.strip()).resolve()
|
|
180
|
+
else:
|
|
181
|
+
invoking_root = Path(top.stdout.strip()).resolve()
|
|
178
182
|
worktrees = _parse_worktrees(git.run(invoking_root, "worktree", "list", "--porcelain").stdout)
|
|
179
183
|
if not worktrees:
|
|
180
184
|
raise AutomationError("worktree_registry_empty")
|
|
@@ -188,7 +192,7 @@ def locate_repository(cwd: Path | str, *, runner: GitRunner | None = None) -> Re
|
|
|
188
192
|
default_branch = remote_branch or _local_default_branch(git, primary_root, worktrees[0].get("branch"))
|
|
189
193
|
if remote_name and remote_ref is None:
|
|
190
194
|
remote_ref = f"refs/remotes/{remote_name}/{default_branch}"
|
|
191
|
-
worktree_path = Path(f"{primary_root}-worktrees") / WORKTREE_BASENAME
|
|
195
|
+
worktree_path = Path(f"{primary_root}-codexspec-worktrees") / WORKTREE_BASENAME
|
|
192
196
|
return RepositoryContext(
|
|
193
197
|
repository_root=primary_root,
|
|
194
198
|
common_git_dir=common_git_dir,
|
|
@@ -303,16 +307,22 @@ def _remote_default(git: GitRunner, root: Path) -> tuple[str | None, str | None,
|
|
|
303
307
|
if symbolic.returncode != 0:
|
|
304
308
|
return remote, None, None
|
|
305
309
|
ref = symbolic.stdout.strip()
|
|
306
|
-
return remote, ref, ref.
|
|
310
|
+
return remote, ref, ref.removeprefix(f"refs/remotes/{remote}/")
|
|
307
311
|
|
|
308
312
|
|
|
309
313
|
def _local_default_branch(git: GitRunner, root: Path, primary_branch_ref: str | None) -> str:
|
|
310
314
|
for candidate in ("main", "master"):
|
|
311
315
|
if git.run(root, "show-ref", "--verify", "--quiet", f"refs/heads/{candidate}", check=False).returncode == 0:
|
|
312
316
|
return candidate
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
317
|
+
configured = git.run(root, "config", "--get", "init.defaultBranch", check=False).stdout.strip()
|
|
318
|
+
if (
|
|
319
|
+
configured
|
|
320
|
+
and git.run(root, "show-ref", "--verify", "--quiet", f"refs/heads/{configured}", check=False).returncode == 0
|
|
321
|
+
):
|
|
322
|
+
return configured
|
|
323
|
+
raise AutomationError(
|
|
324
|
+
"default_branch_not_found", "Configure the remote default branch or a valid init.defaultBranch."
|
|
325
|
+
)
|
|
316
326
|
|
|
317
327
|
|
|
318
328
|
def _parse_worktrees(output: str) -> list[dict[str, Any]]:
|