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.
Files changed (65) hide show
  1. {codexspec-0.7.4 → codexspec-0.7.6}/PKG-INFO +4 -4
  2. {codexspec-0.7.4 → codexspec-0.7.6}/README.md +3 -3
  3. {codexspec-0.7.4 → codexspec-0.7.6}/pyproject.toml +1 -1
  4. codexspec-0.7.6/scripts/bash/review-context.sh +538 -0
  5. codexspec-0.7.6/scripts/powershell/review-context.ps1 +573 -0
  6. {codexspec-0.7.4 → codexspec-0.7.6}/src/codexspec/commands/installer.py +1 -1
  7. codexspec-0.7.6/templates/commands/analyze.md +99 -0
  8. codexspec-0.7.6/templates/commands/implement-tasks.md +294 -0
  9. {codexspec-0.7.4 → codexspec-0.7.6}/templates/commands/plan-to-tasks.md +10 -5
  10. {codexspec-0.7.4 → codexspec-0.7.6}/templates/commands/review-code.md +270 -22
  11. {codexspec-0.7.4 → codexspec-0.7.6}/templates/translations/de.json +2 -2
  12. {codexspec-0.7.4 → codexspec-0.7.6}/templates/translations/en.json +2 -2
  13. {codexspec-0.7.4 → codexspec-0.7.6}/templates/translations/es.json +2 -2
  14. {codexspec-0.7.4 → codexspec-0.7.6}/templates/translations/fr.json +2 -2
  15. {codexspec-0.7.4 → codexspec-0.7.6}/templates/translations/ja.json +2 -2
  16. {codexspec-0.7.4 → codexspec-0.7.6}/templates/translations/ko.json +2 -2
  17. {codexspec-0.7.4 → codexspec-0.7.6}/templates/translations/pt-BR.json +2 -2
  18. {codexspec-0.7.4 → codexspec-0.7.6}/templates/translations/zh-CN.json +2 -2
  19. codexspec-0.7.4/templates/commands/analyze.md +0 -84
  20. codexspec-0.7.4/templates/commands/implement-tasks.md +0 -235
  21. {codexspec-0.7.4 → codexspec-0.7.6}/.gitignore +0 -0
  22. {codexspec-0.7.4 → codexspec-0.7.6}/LICENSE +0 -0
  23. {codexspec-0.7.4 → codexspec-0.7.6}/codexspec-icon.svg +0 -0
  24. {codexspec-0.7.4 → codexspec-0.7.6}/codexspec-logo-dark.svg +0 -0
  25. {codexspec-0.7.4 → codexspec-0.7.6}/codexspec-logo-light.svg +0 -0
  26. {codexspec-0.7.4 → codexspec-0.7.6}/scripts/bash/check-i18n-completeness.sh +0 -0
  27. {codexspec-0.7.4 → codexspec-0.7.6}/scripts/bash/check-i18n-structure.sh +0 -0
  28. {codexspec-0.7.4 → codexspec-0.7.6}/scripts/bash/check-prerequisites.sh +0 -0
  29. {codexspec-0.7.4 → codexspec-0.7.6}/scripts/bash/common.sh +0 -0
  30. {codexspec-0.7.4 → codexspec-0.7.6}/scripts/bash/create-new-feature.sh +0 -0
  31. {codexspec-0.7.4 → codexspec-0.7.6}/scripts/powershell/check-prerequisites.ps1 +0 -0
  32. {codexspec-0.7.4 → codexspec-0.7.6}/scripts/powershell/common.ps1 +0 -0
  33. {codexspec-0.7.4 → codexspec-0.7.6}/scripts/powershell/create-new-feature.ps1 +0 -0
  34. {codexspec-0.7.4 → codexspec-0.7.6}/src/codexspec/__init__.py +0 -0
  35. {codexspec-0.7.4 → codexspec-0.7.6}/src/codexspec/commands/__init__.py +0 -0
  36. {codexspec-0.7.4 → codexspec-0.7.6}/src/codexspec/i18n.py +0 -0
  37. {codexspec-0.7.4 → codexspec-0.7.6}/src/codexspec/idea.md +0 -0
  38. {codexspec-0.7.4 → codexspec-0.7.6}/src/codexspec/integrations/__init__.py +0 -0
  39. {codexspec-0.7.4 → codexspec-0.7.6}/src/codexspec/integrations/base.py +0 -0
  40. {codexspec-0.7.4 → codexspec-0.7.6}/src/codexspec/integrations/claude.py +0 -0
  41. {codexspec-0.7.4 → codexspec-0.7.6}/src/codexspec/integrations/codex.py +0 -0
  42. {codexspec-0.7.4 → codexspec-0.7.6}/src/codexspec/translator.py +0 -0
  43. {codexspec-0.7.4 → codexspec-0.7.6}/templates/commands/checklist.md +0 -0
  44. {codexspec-0.7.4 → codexspec-0.7.6}/templates/commands/clarify.md +0 -0
  45. {codexspec-0.7.4 → codexspec-0.7.6}/templates/commands/commit-staged.md +0 -0
  46. {codexspec-0.7.4 → codexspec-0.7.6}/templates/commands/config.md +0 -0
  47. {codexspec-0.7.4 → codexspec-0.7.6}/templates/commands/constitution.md +0 -0
  48. {codexspec-0.7.4 → codexspec-0.7.6}/templates/commands/generate-spec.md +0 -0
  49. {codexspec-0.7.4 → codexspec-0.7.6}/templates/commands/pr.md +0 -0
  50. {codexspec-0.7.4 → codexspec-0.7.6}/templates/commands/quick.md +0 -0
  51. {codexspec-0.7.4 → codexspec-0.7.6}/templates/commands/review-plan.md +0 -0
  52. {codexspec-0.7.4 → codexspec-0.7.6}/templates/commands/review-spec.md +0 -0
  53. {codexspec-0.7.4 → codexspec-0.7.6}/templates/commands/review-tasks.md +0 -0
  54. {codexspec-0.7.4 → codexspec-0.7.6}/templates/commands/spec-to-plan.md +0 -0
  55. {codexspec-0.7.4 → codexspec-0.7.6}/templates/commands/specify.md +0 -0
  56. {codexspec-0.7.4 → codexspec-0.7.6}/templates/commands/tasks-to-issues.md +0 -0
  57. {codexspec-0.7.4 → codexspec-0.7.6}/templates/docs/checklist-template.md +0 -0
  58. {codexspec-0.7.4 → codexspec-0.7.6}/templates/docs/constitution-template.md +0 -0
  59. {codexspec-0.7.4 → codexspec-0.7.6}/templates/docs/plan-template-detailed.md +0 -0
  60. {codexspec-0.7.4 → codexspec-0.7.6}/templates/docs/plan-template-simple.md +0 -0
  61. {codexspec-0.7.4 → codexspec-0.7.6}/templates/docs/requirements-template.md +0 -0
  62. {codexspec-0.7.4 → codexspec-0.7.6}/templates/docs/spec-template-detailed.md +0 -0
  63. {codexspec-0.7.4 → codexspec-0.7.6}/templates/docs/spec-template-simple.md +0 -0
  64. {codexspec-0.7.4 → codexspec-0.7.6}/templates/docs/tasks-template-detailed.md +0 -0
  65. {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.4
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 (read-only, severity-based) |
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` | Review code in any language (idiomatic clarity, correctness, robustness, architecture) |
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 | Read-only, severity-based, constitution-aware |
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 (read-only, severity-based) |
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` | Review code in any language (idiomatic clarity, correctness, robustness, architecture) |
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 | Read-only, severity-based, constitution-aware |
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 |
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "codexspec"
3
- version = "0.7.4"
3
+ version = "0.7.6"
4
4
  description = "CodexSpec - A Requirements-First SDD toolkit for Claude Code"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -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"