codexspec 0.7.4__tar.gz → 0.7.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {codexspec-0.7.4 → codexspec-0.7.5}/PKG-INFO +2 -2
- {codexspec-0.7.4 → codexspec-0.7.5}/README.md +1 -1
- {codexspec-0.7.4 → codexspec-0.7.5}/pyproject.toml +1 -1
- codexspec-0.7.5/scripts/bash/review-context.sh +538 -0
- codexspec-0.7.5/scripts/powershell/review-context.ps1 +573 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/src/codexspec/commands/installer.py +1 -1
- codexspec-0.7.5/templates/commands/implement-tasks.md +281 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/templates/commands/review-code.md +270 -22
- {codexspec-0.7.4 → codexspec-0.7.5}/templates/translations/de.json +2 -2
- {codexspec-0.7.4 → codexspec-0.7.5}/templates/translations/en.json +2 -2
- {codexspec-0.7.4 → codexspec-0.7.5}/templates/translations/es.json +2 -2
- {codexspec-0.7.4 → codexspec-0.7.5}/templates/translations/fr.json +2 -2
- {codexspec-0.7.4 → codexspec-0.7.5}/templates/translations/ja.json +2 -2
- {codexspec-0.7.4 → codexspec-0.7.5}/templates/translations/ko.json +2 -2
- {codexspec-0.7.4 → codexspec-0.7.5}/templates/translations/pt-BR.json +2 -2
- {codexspec-0.7.4 → codexspec-0.7.5}/templates/translations/zh-CN.json +2 -2
- codexspec-0.7.4/templates/commands/implement-tasks.md +0 -235
- {codexspec-0.7.4 → codexspec-0.7.5}/.gitignore +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/LICENSE +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/codexspec-icon.svg +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/codexspec-logo-dark.svg +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/codexspec-logo-light.svg +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/scripts/bash/check-i18n-completeness.sh +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/scripts/bash/check-i18n-structure.sh +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/scripts/bash/check-prerequisites.sh +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/scripts/bash/common.sh +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/scripts/bash/create-new-feature.sh +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/scripts/powershell/check-prerequisites.ps1 +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/scripts/powershell/common.ps1 +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/scripts/powershell/create-new-feature.ps1 +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/src/codexspec/__init__.py +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/src/codexspec/commands/__init__.py +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/src/codexspec/i18n.py +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/src/codexspec/idea.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/src/codexspec/integrations/__init__.py +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/src/codexspec/integrations/base.py +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/src/codexspec/integrations/claude.py +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/src/codexspec/integrations/codex.py +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/src/codexspec/translator.py +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/templates/commands/analyze.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/templates/commands/checklist.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/templates/commands/clarify.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/templates/commands/commit-staged.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/templates/commands/config.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/templates/commands/constitution.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/templates/commands/generate-spec.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/templates/commands/plan-to-tasks.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/templates/commands/pr.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/templates/commands/quick.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/templates/commands/review-plan.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/templates/commands/review-spec.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/templates/commands/review-tasks.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/templates/commands/spec-to-plan.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/templates/commands/specify.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/templates/commands/tasks-to-issues.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/templates/docs/checklist-template.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/templates/docs/constitution-template.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/templates/docs/plan-template-detailed.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/templates/docs/plan-template-simple.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/templates/docs/requirements-template.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/templates/docs/spec-template-detailed.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/templates/docs/spec-template-simple.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/templates/docs/tasks-template-detailed.md +0 -0
- {codexspec-0.7.4 → codexspec-0.7.5}/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.5
|
|
4
4
|
Summary: CodexSpec - A Requirements-First SDD toolkit for Claude Code
|
|
5
5
|
Project-URL: Homepage, https://github.com/Zts0hg/codexspec
|
|
6
6
|
Project-URL: Repository, https://github.com/Zts0hg/codexspec
|
|
@@ -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
|
|
|
@@ -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
|
|
|
@@ -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"
|