codexspec 0.7.4__tar.gz → 0.7.6__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.4 → codexspec-0.7.6}/PKG-INFO +4 -4
- {codexspec-0.7.4 → codexspec-0.7.6}/README.md +3 -3
- {codexspec-0.7.4 → codexspec-0.7.6}/pyproject.toml +1 -1
- codexspec-0.7.6/scripts/bash/review-context.sh +538 -0
- codexspec-0.7.6/scripts/powershell/review-context.ps1 +573 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/src/codexspec/commands/installer.py +1 -1
- codexspec-0.7.6/templates/commands/analyze.md +99 -0
- codexspec-0.7.6/templates/commands/implement-tasks.md +294 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/templates/commands/plan-to-tasks.md +10 -5
- {codexspec-0.7.4 → codexspec-0.7.6}/templates/commands/review-code.md +270 -22
- {codexspec-0.7.4 → codexspec-0.7.6}/templates/translations/de.json +2 -2
- {codexspec-0.7.4 → codexspec-0.7.6}/templates/translations/en.json +2 -2
- {codexspec-0.7.4 → codexspec-0.7.6}/templates/translations/es.json +2 -2
- {codexspec-0.7.4 → codexspec-0.7.6}/templates/translations/fr.json +2 -2
- {codexspec-0.7.4 → codexspec-0.7.6}/templates/translations/ja.json +2 -2
- {codexspec-0.7.4 → codexspec-0.7.6}/templates/translations/ko.json +2 -2
- {codexspec-0.7.4 → codexspec-0.7.6}/templates/translations/pt-BR.json +2 -2
- {codexspec-0.7.4 → codexspec-0.7.6}/templates/translations/zh-CN.json +2 -2
- codexspec-0.7.4/templates/commands/analyze.md +0 -84
- codexspec-0.7.4/templates/commands/implement-tasks.md +0 -235
- {codexspec-0.7.4 → codexspec-0.7.6}/.gitignore +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/LICENSE +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/codexspec-icon.svg +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/codexspec-logo-dark.svg +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/codexspec-logo-light.svg +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/scripts/bash/check-i18n-completeness.sh +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/scripts/bash/check-i18n-structure.sh +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/scripts/bash/check-prerequisites.sh +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/scripts/bash/common.sh +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/scripts/bash/create-new-feature.sh +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/scripts/powershell/check-prerequisites.ps1 +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/scripts/powershell/common.ps1 +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/scripts/powershell/create-new-feature.ps1 +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/src/codexspec/__init__.py +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/src/codexspec/commands/__init__.py +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/src/codexspec/i18n.py +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/src/codexspec/idea.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/src/codexspec/integrations/__init__.py +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/src/codexspec/integrations/base.py +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/src/codexspec/integrations/claude.py +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/src/codexspec/integrations/codex.py +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/src/codexspec/translator.py +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/templates/commands/checklist.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/templates/commands/clarify.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/templates/commands/commit-staged.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/templates/commands/config.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/templates/commands/constitution.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/templates/commands/generate-spec.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/templates/commands/pr.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/templates/commands/quick.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/templates/commands/review-plan.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/templates/commands/review-spec.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/templates/commands/review-tasks.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/templates/commands/spec-to-plan.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/templates/commands/specify.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/templates/commands/tasks-to-issues.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/templates/docs/checklist-template.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/templates/docs/constitution-template.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/templates/docs/plan-template-detailed.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/templates/docs/plan-template-simple.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/templates/docs/requirements-template.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/templates/docs/spec-template-detailed.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/templates/docs/spec-template-simple.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/templates/docs/tasks-template-detailed.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.6}/templates/docs/tasks-template-simple.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codexspec
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.6
|
|
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
|
|
@@ -585,7 +585,7 @@ Implementation follows **conditional TDD workflow**:
|
|
|
585
585
|
| ---------------------------- | --------------------------------------------------------------- |
|
|
586
586
|
| `/codexspec:config` | Manage project configuration (create/view/modify/reset) |
|
|
587
587
|
| `/codexspec:clarify` | Scan spec for ambiguities (4 categories, max 5 questions) |
|
|
588
|
-
| `/codexspec:analyze` | Cross-artifact consistency analysis (
|
|
588
|
+
| `/codexspec:analyze` | Cross-artifact consistency analysis (auto-remediating, severity-based) |
|
|
589
589
|
| `/codexspec:checklist` | Generate requirements quality checklist |
|
|
590
590
|
| `/codexspec:tasks-to-issues` | Convert tasks to GitHub Issues |
|
|
591
591
|
|
|
@@ -600,7 +600,7 @@ Implementation follows **conditional TDD workflow**:
|
|
|
600
600
|
|
|
601
601
|
| Command | Description |
|
|
602
602
|
| ------------------------------- | --------------------------------------------------------------- |
|
|
603
|
-
| `/codexspec:review-code` |
|
|
603
|
+
| `/codexspec:review-code` | Change-scoped defect gate; use `--audit` for the path quality scorecard |
|
|
604
604
|
|
|
605
605
|
---
|
|
606
606
|
|
|
@@ -617,7 +617,7 @@ CodexSpec is inspired by GitHub spec-kit with key differences:
|
|
|
617
617
|
| Two-Phase Spec | No | Yes (clarify + generate) |
|
|
618
618
|
| Review Commands | Optional | 3 dedicated review commands + scoring |
|
|
619
619
|
| Clarify Command | Yes | 4 focus categories, review integration |
|
|
620
|
-
| Analyze Command | Yes |
|
|
620
|
+
| Analyze Command | Yes | Auto-remediating, severity-based, constitution-aware |
|
|
621
621
|
| TDD in Tasks | Optional | Conditional on requirements, risk, and policy |
|
|
622
622
|
| Implementation | Standard | Conditional TDD (code vs docs/config) |
|
|
623
623
|
| Extension System | Yes | Yes |
|
|
@@ -540,7 +540,7 @@ Implementation follows **conditional TDD workflow**:
|
|
|
540
540
|
| ---------------------------- | --------------------------------------------------------------- |
|
|
541
541
|
| `/codexspec:config` | Manage project configuration (create/view/modify/reset) |
|
|
542
542
|
| `/codexspec:clarify` | Scan spec for ambiguities (4 categories, max 5 questions) |
|
|
543
|
-
| `/codexspec:analyze` | Cross-artifact consistency analysis (
|
|
543
|
+
| `/codexspec:analyze` | Cross-artifact consistency analysis (auto-remediating, severity-based) |
|
|
544
544
|
| `/codexspec:checklist` | Generate requirements quality checklist |
|
|
545
545
|
| `/codexspec:tasks-to-issues` | Convert tasks to GitHub Issues |
|
|
546
546
|
|
|
@@ -555,7 +555,7 @@ Implementation follows **conditional TDD workflow**:
|
|
|
555
555
|
|
|
556
556
|
| Command | Description |
|
|
557
557
|
| ------------------------------- | --------------------------------------------------------------- |
|
|
558
|
-
| `/codexspec:review-code` |
|
|
558
|
+
| `/codexspec:review-code` | Change-scoped defect gate; use `--audit` for the path quality scorecard |
|
|
559
559
|
|
|
560
560
|
---
|
|
561
561
|
|
|
@@ -572,7 +572,7 @@ CodexSpec is inspired by GitHub spec-kit with key differences:
|
|
|
572
572
|
| Two-Phase Spec | No | Yes (clarify + generate) |
|
|
573
573
|
| Review Commands | Optional | 3 dedicated review commands + scoring |
|
|
574
574
|
| Clarify Command | Yes | 4 focus categories, review integration |
|
|
575
|
-
| Analyze Command | Yes |
|
|
575
|
+
| Analyze Command | Yes | Auto-remediating, severity-based, constitution-aware |
|
|
576
576
|
| TDD in Tasks | Optional | Conditional on requirements, risk, and policy |
|
|
577
577
|
| Implementation | Standard | Conditional TDD (code vs docs/config) |
|
|
578
578
|
| Extension System | Yes | Yes |
|
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Resolve a deterministic, read-only review target for review-code.
|
|
3
|
+
|
|
4
|
+
set -u
|
|
5
|
+
|
|
6
|
+
SCHEMA_VERSION="1"
|
|
7
|
+
|
|
8
|
+
json_escape() {
|
|
9
|
+
local value="$1"
|
|
10
|
+
value=${value//\\/\\\\}
|
|
11
|
+
value=${value//\"/\\\"}
|
|
12
|
+
value=${value//$'\b'/\\b}
|
|
13
|
+
value=${value//$'\f'/\\f}
|
|
14
|
+
value=${value//$'\n'/\\n}
|
|
15
|
+
value=${value//$'\r'/\\r}
|
|
16
|
+
value=${value//$'\t'/\\t}
|
|
17
|
+
printf '%s' "$value"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
json_string() {
|
|
21
|
+
printf '"%s"' "$(json_escape "$1")"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
json_nullable_string() {
|
|
25
|
+
if [ -n "$1" ]; then
|
|
26
|
+
json_string "$1"
|
|
27
|
+
else
|
|
28
|
+
printf 'null'
|
|
29
|
+
fi
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
emit_error() {
|
|
33
|
+
local code="$1"
|
|
34
|
+
local message="$2"
|
|
35
|
+
local hint="$3"
|
|
36
|
+
printf '{"schema_version":"%s","status":"error","error":{"code":' "$SCHEMA_VERSION"
|
|
37
|
+
json_string "$code"
|
|
38
|
+
printf ',"message":'
|
|
39
|
+
json_string "$message"
|
|
40
|
+
printf ',"hint":'
|
|
41
|
+
json_string "$hint"
|
|
42
|
+
printf '}}\n'
|
|
43
|
+
exit 2
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
selector="default"
|
|
47
|
+
base_override=""
|
|
48
|
+
commit_input=""
|
|
49
|
+
parent_override=""
|
|
50
|
+
feature_override=""
|
|
51
|
+
focus_values=()
|
|
52
|
+
|
|
53
|
+
set_selector() {
|
|
54
|
+
local requested="$1"
|
|
55
|
+
if [ "$selector" != "default" ]; then
|
|
56
|
+
emit_error "conflicting_selectors" "Review target selectors are mutually exclusive." \
|
|
57
|
+
"Choose exactly one of --committed, --uncommitted, or --commit <sha>."
|
|
58
|
+
fi
|
|
59
|
+
selector="$requested"
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
require_option_value() {
|
|
63
|
+
local option="$1"
|
|
64
|
+
local remaining="$2"
|
|
65
|
+
if [ "$remaining" -lt 2 ]; then
|
|
66
|
+
emit_error "missing_option_value" "Missing value for $option." "Pass a value immediately after $option."
|
|
67
|
+
fi
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
while [ "$#" -gt 0 ]; do
|
|
71
|
+
case "$1" in
|
|
72
|
+
--committed)
|
|
73
|
+
set_selector "committed"
|
|
74
|
+
shift
|
|
75
|
+
;;
|
|
76
|
+
--uncommitted)
|
|
77
|
+
set_selector "uncommitted"
|
|
78
|
+
shift
|
|
79
|
+
;;
|
|
80
|
+
--commit)
|
|
81
|
+
require_option_value "$1" "$#"
|
|
82
|
+
set_selector "commit"
|
|
83
|
+
commit_input="$2"
|
|
84
|
+
shift 2
|
|
85
|
+
;;
|
|
86
|
+
--base)
|
|
87
|
+
require_option_value "$1" "$#"
|
|
88
|
+
if [ -n "$base_override" ]; then
|
|
89
|
+
emit_error "duplicate_base" "--base may be supplied only once." "Remove the duplicate --base argument."
|
|
90
|
+
fi
|
|
91
|
+
base_override="$2"
|
|
92
|
+
shift 2
|
|
93
|
+
;;
|
|
94
|
+
--parent)
|
|
95
|
+
require_option_value "$1" "$#"
|
|
96
|
+
if [ -n "$parent_override" ]; then
|
|
97
|
+
emit_error "duplicate_parent" "--parent may be supplied only once." "Remove the duplicate --parent argument."
|
|
98
|
+
fi
|
|
99
|
+
parent_override="$2"
|
|
100
|
+
shift 2
|
|
101
|
+
;;
|
|
102
|
+
--feature)
|
|
103
|
+
require_option_value "$1" "$#"
|
|
104
|
+
if [ -n "$feature_override" ]; then
|
|
105
|
+
emit_error "duplicate_feature" "--feature may be supplied only once." "Remove the duplicate --feature argument."
|
|
106
|
+
fi
|
|
107
|
+
feature_override="$2"
|
|
108
|
+
shift 2
|
|
109
|
+
;;
|
|
110
|
+
--focus)
|
|
111
|
+
require_option_value "$1" "$#"
|
|
112
|
+
focus_values+=("$2")
|
|
113
|
+
shift 2
|
|
114
|
+
;;
|
|
115
|
+
--*)
|
|
116
|
+
emit_error "unknown_argument" "Unknown review-code argument: $1" \
|
|
117
|
+
"Use a documented defect target, --feature, --focus, or explicit --audit mode."
|
|
118
|
+
;;
|
|
119
|
+
*)
|
|
120
|
+
emit_error "bare_path_argument" "Bare path arguments are not valid defect-review targets: $1" \
|
|
121
|
+
"Use review-code --audit $1 for a whole-path quality audit."
|
|
122
|
+
;;
|
|
123
|
+
esac
|
|
124
|
+
done
|
|
125
|
+
|
|
126
|
+
if [ -n "$base_override" ] && [ "$selector" != "default" ] && [ "$selector" != "committed" ]; then
|
|
127
|
+
emit_error "invalid_base_modifier" "--base is valid only for the default or --committed target." \
|
|
128
|
+
"Remove --base or select the default/--committed target."
|
|
129
|
+
fi
|
|
130
|
+
if [ -n "$parent_override" ] && [ "$selector" != "commit" ]; then
|
|
131
|
+
emit_error "parent_requires_commit" "--parent is valid only with --commit <sha>." \
|
|
132
|
+
"Add --commit <sha> or remove --parent."
|
|
133
|
+
fi
|
|
134
|
+
if [ -n "$parent_override" ] && ! [[ "$parent_override" =~ ^[1-9][0-9]*$ ]]; then
|
|
135
|
+
emit_error "invalid_parent" "--parent must be a positive integer." "Pass a valid merge-parent index such as --parent 2."
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
if ! repo_root=$(git rev-parse --show-toplevel 2>/dev/null); then
|
|
139
|
+
emit_error "not_a_git_repository" "review-code defect mode requires a Git repository." \
|
|
140
|
+
"Run the command inside a Git repository or use --audit for a path audit."
|
|
141
|
+
fi
|
|
142
|
+
cd "$repo_root" || emit_error "repository_unreadable" "Cannot enter the Git repository root." "Check repository permissions."
|
|
143
|
+
|
|
144
|
+
current_branch=$(git branch --show-current 2>/dev/null || true)
|
|
145
|
+
head_sha=$(git rev-parse --verify HEAD 2>/dev/null || true)
|
|
146
|
+
if [ -z "$head_sha" ]; then
|
|
147
|
+
emit_error "head_not_found" "The repository has no resolvable HEAD commit." "Create an initial commit before defect review."
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
base_ref=""
|
|
151
|
+
base_sha=""
|
|
152
|
+
merge_base_sha=""
|
|
153
|
+
commit_sha=""
|
|
154
|
+
parent_sha=""
|
|
155
|
+
parent_number=""
|
|
156
|
+
complete_feature="false"
|
|
157
|
+
|
|
158
|
+
selected_remote() {
|
|
159
|
+
local upstream remote_count
|
|
160
|
+
upstream=$(git rev-parse --abbrev-ref --symbolic-full-name '@{upstream}' 2>/dev/null || true)
|
|
161
|
+
if [[ "$upstream" == */* ]]; then
|
|
162
|
+
printf '%s' "${upstream%%/*}"
|
|
163
|
+
return
|
|
164
|
+
fi
|
|
165
|
+
if git remote get-url origin >/dev/null 2>&1; then
|
|
166
|
+
printf 'origin'
|
|
167
|
+
return
|
|
168
|
+
fi
|
|
169
|
+
remote_count=$(git remote 2>/dev/null | wc -l | tr -d ' ')
|
|
170
|
+
if [ "$remote_count" = "1" ]; then
|
|
171
|
+
git remote 2>/dev/null | head -n 1
|
|
172
|
+
fi
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
resolve_base() {
|
|
176
|
+
local remote candidate remote_head remote_branch fallback
|
|
177
|
+
if [ -n "$base_override" ]; then
|
|
178
|
+
base_ref="$base_override"
|
|
179
|
+
else
|
|
180
|
+
remote=$(selected_remote)
|
|
181
|
+
if [ -n "$remote" ]; then
|
|
182
|
+
candidate=$(git symbolic-ref --quiet --short "refs/remotes/$remote/HEAD" 2>/dev/null || true)
|
|
183
|
+
if [ -n "$candidate" ] && git rev-parse --verify "${candidate}^{commit}" >/dev/null 2>&1; then
|
|
184
|
+
base_ref="$candidate"
|
|
185
|
+
fi
|
|
186
|
+
if [ -z "$base_ref" ]; then
|
|
187
|
+
remote_head=$(git ls-remote --symref "$remote" HEAD 2>/dev/null || true)
|
|
188
|
+
remote_branch=$(printf '%s\n' "$remote_head" | awk '$1 == "ref:" {print $2; exit}')
|
|
189
|
+
if [[ "$remote_branch" == refs/heads/* ]]; then
|
|
190
|
+
candidate="$remote/${remote_branch#refs/heads/}"
|
|
191
|
+
if git rev-parse --verify "${candidate}^{commit}" >/dev/null 2>&1; then
|
|
192
|
+
base_ref="$candidate"
|
|
193
|
+
fi
|
|
194
|
+
fi
|
|
195
|
+
fi
|
|
196
|
+
fi
|
|
197
|
+
if [ -z "$base_ref" ]; then
|
|
198
|
+
for fallback in origin/main origin/master main master; do
|
|
199
|
+
if git rev-parse --verify "${fallback}^{commit}" >/dev/null 2>&1; then
|
|
200
|
+
base_ref="$fallback"
|
|
201
|
+
break
|
|
202
|
+
fi
|
|
203
|
+
done
|
|
204
|
+
fi
|
|
205
|
+
fi
|
|
206
|
+
|
|
207
|
+
if [ -z "$base_ref" ]; then
|
|
208
|
+
emit_error "base_not_found" "Could not determine the repository base branch." \
|
|
209
|
+
"Pass --base <branch> explicitly or configure the remote default HEAD."
|
|
210
|
+
fi
|
|
211
|
+
base_sha=$(git rev-parse --verify "${base_ref}^{commit}" 2>/dev/null || true)
|
|
212
|
+
if [ -z "$base_sha" ]; then
|
|
213
|
+
emit_error "base_not_found" "Base ref cannot be resolved: $base_ref" "Pass an existing local or remote-tracking ref."
|
|
214
|
+
fi
|
|
215
|
+
merge_base_sha=$(git merge-base "$head_sha" "$base_sha" 2>/dev/null || true)
|
|
216
|
+
if [ -z "$merge_base_sha" ]; then
|
|
217
|
+
emit_error "merge_base_not_found" "No merge base exists between HEAD and $base_ref." \
|
|
218
|
+
"Pass the correct --base ref or reconcile unrelated histories."
|
|
219
|
+
fi
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
inventory_paths=()
|
|
223
|
+
inventory_old_paths=()
|
|
224
|
+
inventory_statuses=()
|
|
225
|
+
inventory_segments=()
|
|
226
|
+
inventory_modes=()
|
|
227
|
+
inventory_kinds=()
|
|
228
|
+
target_segment_names=()
|
|
229
|
+
target_segment_from=()
|
|
230
|
+
target_segment_to=()
|
|
231
|
+
|
|
232
|
+
find_inventory_index() {
|
|
233
|
+
local target="$1" i
|
|
234
|
+
i=0
|
|
235
|
+
while [ "$i" -lt "${#inventory_paths[@]}" ]; do
|
|
236
|
+
if [ "${inventory_paths[$i]}" = "$target" ]; then
|
|
237
|
+
printf '%s' "$i"
|
|
238
|
+
return
|
|
239
|
+
fi
|
|
240
|
+
i=$((i + 1))
|
|
241
|
+
done
|
|
242
|
+
printf '%s' "-1"
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
append_segment() {
|
|
246
|
+
local existing="$1" segment="$2"
|
|
247
|
+
case ",$existing," in
|
|
248
|
+
*",$segment,"*) printf '%s' "$existing" ;;
|
|
249
|
+
",,") printf '%s' "$segment" ;;
|
|
250
|
+
*) printf '%s,%s' "$existing" "$segment" ;;
|
|
251
|
+
esac
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
add_inventory_record() {
|
|
255
|
+
local status="$1" path="$2" old_path="$3" segment="$4" index
|
|
256
|
+
status=${status:0:1}
|
|
257
|
+
index=$(find_inventory_index "$path")
|
|
258
|
+
if [ "$index" -lt 0 ]; then
|
|
259
|
+
inventory_paths+=("$path")
|
|
260
|
+
inventory_old_paths+=("$old_path")
|
|
261
|
+
inventory_statuses+=("$status")
|
|
262
|
+
inventory_segments+=("$segment")
|
|
263
|
+
inventory_modes+=("")
|
|
264
|
+
inventory_kinds+=("")
|
|
265
|
+
else
|
|
266
|
+
inventory_statuses[$index]="$status"
|
|
267
|
+
inventory_segments[$index]=$(append_segment "${inventory_segments[$index]}" "$segment")
|
|
268
|
+
if [ -n "$old_path" ]; then
|
|
269
|
+
inventory_old_paths[$index]="$old_path"
|
|
270
|
+
fi
|
|
271
|
+
fi
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
add_target_segment() {
|
|
275
|
+
target_segment_names+=("$1")
|
|
276
|
+
target_segment_from+=("$2")
|
|
277
|
+
target_segment_to+=("$3")
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
collect_diff() {
|
|
281
|
+
local segment="$1" status path old_path
|
|
282
|
+
shift
|
|
283
|
+
while IFS= read -r -d '' status; do
|
|
284
|
+
old_path=""
|
|
285
|
+
if [[ "$status" == R* || "$status" == C* ]]; then
|
|
286
|
+
IFS= read -r -d '' old_path || true
|
|
287
|
+
IFS= read -r -d '' path || true
|
|
288
|
+
else
|
|
289
|
+
IFS= read -r -d '' path || true
|
|
290
|
+
fi
|
|
291
|
+
add_inventory_record "$status" "$path" "$old_path" "$segment"
|
|
292
|
+
done < <("$@" 2>/dev/null)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
collect_untracked() {
|
|
296
|
+
local path
|
|
297
|
+
while IFS= read -r -d '' path; do
|
|
298
|
+
add_inventory_record "A" "$path" "" "untracked"
|
|
299
|
+
done < <(git ls-files --others --exclude-standard -z 2>/dev/null)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
has_uncommitted_work() {
|
|
303
|
+
if ! git diff --quiet --ignore-submodules=none -- 2>/dev/null; then
|
|
304
|
+
return 0
|
|
305
|
+
fi
|
|
306
|
+
if ! git diff --cached --quiet --ignore-submodules=none -- 2>/dev/null; then
|
|
307
|
+
return 0
|
|
308
|
+
fi
|
|
309
|
+
if [ -n "$(git ls-files --others --exclude-standard 2>/dev/null | head -n 1)" ]; then
|
|
310
|
+
return 0
|
|
311
|
+
fi
|
|
312
|
+
return 1
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
collect_uncommitted_segments() {
|
|
316
|
+
add_target_segment "staged" "$head_sha" "index"
|
|
317
|
+
collect_diff "staged" git diff --cached --name-status -z --find-renames --
|
|
318
|
+
add_target_segment "unstaged" "index" "worktree"
|
|
319
|
+
collect_diff "unstaged" git diff --name-status -z --find-renames --
|
|
320
|
+
add_target_segment "untracked" "none" "worktree"
|
|
321
|
+
collect_untracked
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
case "$selector" in
|
|
325
|
+
default)
|
|
326
|
+
resolve_base
|
|
327
|
+
base_branch_name=${base_ref#refs/heads/}
|
|
328
|
+
base_branch_name=${base_branch_name#refs/remotes/}
|
|
329
|
+
base_branch_name=${base_branch_name#*/}
|
|
330
|
+
if [ -n "$current_branch" ] && [ "$current_branch" = "$base_branch_name" ]; then
|
|
331
|
+
collect_uncommitted_segments
|
|
332
|
+
complete_feature="false"
|
|
333
|
+
else
|
|
334
|
+
add_target_segment "committed" "$merge_base_sha" "$head_sha"
|
|
335
|
+
collect_diff "committed" git diff --name-status -z --find-renames "$merge_base_sha" "$head_sha" --
|
|
336
|
+
collect_uncommitted_segments
|
|
337
|
+
complete_feature="true"
|
|
338
|
+
fi
|
|
339
|
+
;;
|
|
340
|
+
committed)
|
|
341
|
+
resolve_base
|
|
342
|
+
add_target_segment "committed" "$merge_base_sha" "$head_sha"
|
|
343
|
+
collect_diff "committed" git diff --name-status -z --find-renames "$merge_base_sha" "$head_sha" --
|
|
344
|
+
if has_uncommitted_work; then
|
|
345
|
+
complete_feature="false"
|
|
346
|
+
else
|
|
347
|
+
complete_feature="true"
|
|
348
|
+
fi
|
|
349
|
+
;;
|
|
350
|
+
uncommitted)
|
|
351
|
+
collect_uncommitted_segments
|
|
352
|
+
complete_feature="false"
|
|
353
|
+
;;
|
|
354
|
+
commit)
|
|
355
|
+
commit_sha=$(git rev-parse --verify "${commit_input}^{commit}" 2>/dev/null || true)
|
|
356
|
+
if [ -z "$commit_sha" ]; then
|
|
357
|
+
emit_error "commit_not_found" "Commit cannot be resolved: $commit_input" "Pass an existing commit SHA or ref."
|
|
358
|
+
fi
|
|
359
|
+
commit_line=$(git rev-list --parents -n 1 "$commit_sha" 2>/dev/null || true)
|
|
360
|
+
read -r -a commit_parts <<< "$commit_line"
|
|
361
|
+
parent_count=$((${#commit_parts[@]} - 1))
|
|
362
|
+
if [ "$parent_count" -eq 0 ]; then
|
|
363
|
+
if [ -n "$parent_override" ]; then
|
|
364
|
+
emit_error "invalid_parent" "Root commits have no selectable parent." "Remove --parent for a root commit."
|
|
365
|
+
fi
|
|
366
|
+
parent_sha=$(git hash-object -t tree --stdin </dev/null 2>/dev/null || true)
|
|
367
|
+
parent_number="0"
|
|
368
|
+
add_target_segment "commit" "$parent_sha" "$commit_sha"
|
|
369
|
+
collect_diff "commit" git diff-tree --root --no-commit-id -r --name-status -z --find-renames "$commit_sha" --
|
|
370
|
+
else
|
|
371
|
+
if [ -n "$parent_override" ]; then
|
|
372
|
+
parent_number="$parent_override"
|
|
373
|
+
else
|
|
374
|
+
parent_number="1"
|
|
375
|
+
fi
|
|
376
|
+
if [ "$parent_number" -gt "$parent_count" ]; then
|
|
377
|
+
emit_error "invalid_parent" "Commit has no parent $parent_number." \
|
|
378
|
+
"Choose a parent index from 1 through $parent_count."
|
|
379
|
+
fi
|
|
380
|
+
parent_sha=${commit_parts[$parent_number]}
|
|
381
|
+
add_target_segment "commit" "$parent_sha" "$commit_sha"
|
|
382
|
+
collect_diff "commit" git diff --name-status -z --find-renames "$parent_sha" "$commit_sha" --
|
|
383
|
+
fi
|
|
384
|
+
complete_feature="false"
|
|
385
|
+
;;
|
|
386
|
+
esac
|
|
387
|
+
|
|
388
|
+
classify_inventory() {
|
|
389
|
+
local i path status mode kind
|
|
390
|
+
i=0
|
|
391
|
+
while [ "$i" -lt "${#inventory_paths[@]}" ]; do
|
|
392
|
+
path=${inventory_paths[$i]}
|
|
393
|
+
status=${inventory_statuses[$i]}
|
|
394
|
+
mode=$(git ls-files -s -- "$path" 2>/dev/null | awk 'NR == 1 {print $1}')
|
|
395
|
+
kind="file"
|
|
396
|
+
if [ "$mode" = "160000" ]; then
|
|
397
|
+
kind="submodule"
|
|
398
|
+
elif [ "$mode" = "120000" ] || [ -L "$path" ]; then
|
|
399
|
+
kind="symlink"
|
|
400
|
+
elif [ "$status" = "D" ] && [ ! -e "$path" ] && [ ! -L "$path" ]; then
|
|
401
|
+
kind="missing"
|
|
402
|
+
elif [ -f "$path" ]; then
|
|
403
|
+
if [ -s "$path" ] && ! LC_ALL=C grep -Iq . "$path" 2>/dev/null; then
|
|
404
|
+
kind="binary"
|
|
405
|
+
fi
|
|
406
|
+
fi
|
|
407
|
+
inventory_modes[$i]="$mode"
|
|
408
|
+
inventory_kinds[$i]="$kind"
|
|
409
|
+
i=$((i + 1))
|
|
410
|
+
done
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
classify_inventory
|
|
414
|
+
|
|
415
|
+
feature_status="not_resolved"
|
|
416
|
+
feature_source="none"
|
|
417
|
+
feature_path=""
|
|
418
|
+
if [ -n "$feature_override" ]; then
|
|
419
|
+
if [ ! -d "$feature_override" ]; then
|
|
420
|
+
emit_error "feature_not_found" "Feature directory does not exist: $feature_override" \
|
|
421
|
+
"Pass an existing CodexSpec feature directory."
|
|
422
|
+
fi
|
|
423
|
+
feature_path=$(cd "$feature_override" 2>/dev/null && pwd -P)
|
|
424
|
+
feature_status="resolved"
|
|
425
|
+
feature_source="explicit"
|
|
426
|
+
elif [ -n "$current_branch" ] && [ -d "$repo_root/.codexspec/specs/$current_branch" ]; then
|
|
427
|
+
feature_path=$(cd "$repo_root/.codexspec/specs/$current_branch" 2>/dev/null && pwd -P)
|
|
428
|
+
feature_status="resolved"
|
|
429
|
+
feature_source="branch"
|
|
430
|
+
fi
|
|
431
|
+
|
|
432
|
+
printf '{"schema_version":"%s","status":"ok","mode":"defect","selector":' "$SCHEMA_VERSION"
|
|
433
|
+
json_string "$selector"
|
|
434
|
+
printf ',"arguments":{"base_override":'
|
|
435
|
+
json_nullable_string "$base_override"
|
|
436
|
+
printf ',"commit":'
|
|
437
|
+
json_nullable_string "$commit_input"
|
|
438
|
+
printf ',"parent":'
|
|
439
|
+
if [ -n "$parent_override" ]; then printf '%s' "$parent_override"; else printf 'null'; fi
|
|
440
|
+
printf ',"feature_override":'
|
|
441
|
+
json_nullable_string "$feature_override"
|
|
442
|
+
printf ',"focus":['
|
|
443
|
+
i=0
|
|
444
|
+
while [ "$i" -lt "${#focus_values[@]}" ]; do
|
|
445
|
+
[ "$i" -gt 0 ] && printf ','
|
|
446
|
+
json_string "${focus_values[$i]}"
|
|
447
|
+
i=$((i + 1))
|
|
448
|
+
done
|
|
449
|
+
printf ']},"repository":{"root":'
|
|
450
|
+
json_string "$repo_root"
|
|
451
|
+
printf ',"current_branch":'
|
|
452
|
+
json_nullable_string "$current_branch"
|
|
453
|
+
printf ',"head_sha":'
|
|
454
|
+
json_string "$head_sha"
|
|
455
|
+
printf '},"target":{"complete_feature":%s,"empty":' "$complete_feature"
|
|
456
|
+
if [ "${#inventory_paths[@]}" -eq 0 ]; then printf 'true'; else printf 'false'; fi
|
|
457
|
+
printf ',"base_ref":'
|
|
458
|
+
json_nullable_string "$base_ref"
|
|
459
|
+
printf ',"base_sha":'
|
|
460
|
+
json_nullable_string "$base_sha"
|
|
461
|
+
printf ',"merge_base_sha":'
|
|
462
|
+
json_nullable_string "$merge_base_sha"
|
|
463
|
+
printf ',"commit_sha":'
|
|
464
|
+
json_nullable_string "$commit_sha"
|
|
465
|
+
printf ',"parent_sha":'
|
|
466
|
+
json_nullable_string "$parent_sha"
|
|
467
|
+
printf ',"parent_number":'
|
|
468
|
+
if [ -n "$parent_number" ]; then printf '%s' "$parent_number"; else printf 'null'; fi
|
|
469
|
+
printf ',"segments":['
|
|
470
|
+
i=0
|
|
471
|
+
while [ "$i" -lt "${#target_segment_names[@]}" ]; do
|
|
472
|
+
[ "$i" -gt 0 ] && printf ','
|
|
473
|
+
printf '{"name":'
|
|
474
|
+
json_string "${target_segment_names[$i]}"
|
|
475
|
+
printf ',"from":'
|
|
476
|
+
json_nullable_string "${target_segment_from[$i]}"
|
|
477
|
+
printf ',"to":'
|
|
478
|
+
json_nullable_string "${target_segment_to[$i]}"
|
|
479
|
+
printf '}'
|
|
480
|
+
i=$((i + 1))
|
|
481
|
+
done
|
|
482
|
+
printf ']},"feature":{"status":'
|
|
483
|
+
json_string "$feature_status"
|
|
484
|
+
printf ',"source":'
|
|
485
|
+
json_string "$feature_source"
|
|
486
|
+
printf ',"path":'
|
|
487
|
+
json_nullable_string "$feature_path"
|
|
488
|
+
printf ',"artifacts":['
|
|
489
|
+
if [ -n "$feature_path" ]; then
|
|
490
|
+
artifact_index=0
|
|
491
|
+
for artifact in requirements.md spec.md plan.md tasks.md; do
|
|
492
|
+
[ "$artifact_index" -gt 0 ] && printf ','
|
|
493
|
+
printf '{"name":'
|
|
494
|
+
json_string "$artifact"
|
|
495
|
+
printf ',"readable":'
|
|
496
|
+
if [ -r "$feature_path/$artifact" ]; then printf 'true'; else printf 'false'; fi
|
|
497
|
+
printf '}'
|
|
498
|
+
artifact_index=$((artifact_index + 1))
|
|
499
|
+
done
|
|
500
|
+
fi
|
|
501
|
+
printf ']},"inventory":['
|
|
502
|
+
i=0
|
|
503
|
+
committed_count=0
|
|
504
|
+
staged_count=0
|
|
505
|
+
unstaged_count=0
|
|
506
|
+
untracked_count=0
|
|
507
|
+
while [ "$i" -lt "${#inventory_paths[@]}" ]; do
|
|
508
|
+
[ "$i" -gt 0 ] && printf ','
|
|
509
|
+
printf '{"path":'
|
|
510
|
+
json_string "${inventory_paths[$i]}"
|
|
511
|
+
printf ',"old_path":'
|
|
512
|
+
json_nullable_string "${inventory_old_paths[$i]}"
|
|
513
|
+
printf ',"status":'
|
|
514
|
+
json_string "${inventory_statuses[$i]}"
|
|
515
|
+
printf ',"segments":['
|
|
516
|
+
IFS=',' read -r -a record_segments <<< "${inventory_segments[$i]}"
|
|
517
|
+
segment_index=0
|
|
518
|
+
while [ "$segment_index" -lt "${#record_segments[@]}" ]; do
|
|
519
|
+
[ "$segment_index" -gt 0 ] && printf ','
|
|
520
|
+
segment_name=${record_segments[$segment_index]}
|
|
521
|
+
json_string "$segment_name"
|
|
522
|
+
case "$segment_name" in
|
|
523
|
+
committed|commit) committed_count=$((committed_count + 1)) ;;
|
|
524
|
+
staged) staged_count=$((staged_count + 1)) ;;
|
|
525
|
+
unstaged) unstaged_count=$((unstaged_count + 1)) ;;
|
|
526
|
+
untracked) untracked_count=$((untracked_count + 1)) ;;
|
|
527
|
+
esac
|
|
528
|
+
segment_index=$((segment_index + 1))
|
|
529
|
+
done
|
|
530
|
+
printf '],"object_mode":'
|
|
531
|
+
json_nullable_string "${inventory_modes[$i]}"
|
|
532
|
+
printf ',"kind":'
|
|
533
|
+
json_string "${inventory_kinds[$i]}"
|
|
534
|
+
printf '}'
|
|
535
|
+
i=$((i + 1))
|
|
536
|
+
done
|
|
537
|
+
printf '],"counts":{"total":%s,"committed":%s,"staged":%s,"unstaged":%s,"untracked":%s}}\n' \
|
|
538
|
+
"${#inventory_paths[@]}" "$committed_count" "$staged_count" "$unstaged_count" "$untracked_count"
|